Skip to content

@mega-yfue/eufy-sdk / MediaProvider

Interface: MediaProvider

The media / device-query boundary — the second transport, for operations that RETURN data (a still, a live stream, a recording, a P2P request/reply query). The client implements it (P2P media plumbing); capability modules call it without knowing the protocol. Bound to one device serial, so methods take none. p2pQuery is a GENERIC request/reply primitive (transport only). Optional because an unbound model has no live client (hence ?. at the call site).

Methods

live()

ts
live(opts?): Promise<LiveStreamConsumer>;

Open a managed live stream.

Several cameras behind one station may stream at the same time only where the station serves them at the same time. Where it serves one camera at a time, a second viewer is refused with StationBusyError rather than admitted and degraded: accepting it does not make the station serve two, it makes both stutter. Which camera deserves the station is the caller's decision, so nothing is queued or pre-empted. Each handle receives only the frames the station tagged for ITS camera.

Parameters

opts?

SharedSourceHints & AbortableCall & Record<string, unknown>

Returns

Promise<LiveStreamConsumer>

Example

ts
const stream = await cam.live();
stream.on("video", (frame) => write(frame.data)); // Annex-B
stream.stop(); // detach this consumer

openReadable()?

ts
optional openReadable(opts?): Promise<Readable>;

Open a video-only node:stream Readable over a shared source consumer — raw Annex-B bytes (default) or objectMode LiveVideoFrames. Audio is available separately through live or muxed through recordFragments; it is never interleaved into raw video. The caller owns the Readable's lifetime, and destroying it releases the shared pull.

Parameters

opts?

object & SharedSourceHints & AbortableCall

Returns

Promise<Readable>


record()

ts
record(seconds, opts?): Promise<Buffer<ArrayBufferLike>>;

Record seconds of video → an mp4/h264 buffer.

Opens its OWN pull rather than joining the shared source, so it costs a second stream on a camera that is already streaming. recordFragments is a shared consumer like every other egress.

Always settles: it resolves once the requested window has elapsed — with the run the camera actually delivered inside it, which a camera that goes quiet mid-clip makes shorter than asked — and rejects when the pull fails or ends before that window is up, or when no keyframe arrives to start the clip at.

Parameters

seconds

number

opts?
skipKeyframes?

number

timeoutMs?

number

Returns

Promise<Buffer<ArrayBufferLike>>


recordFragments()?

ts
optional recordFragments(opts?): FragmentRecordingHandle;

Continuously record the live feed as fragmented-MP4 (CMAF). The caller-owned FragmentRecordingHandle yields an init segment then keyframe-bounded media fragments, emits battery-budget notices, and releases the shared pull on stop, break, or return.

SharedSourceHints.preBufferSeconds does double duty here: it configures the retained window when this call is the one that opens the pull, and it is the length this recording drains from a pull that was already open. The drain opens on the newest keyframe at or before the window starts, so it covers the request and exceeds it by however far back that keyframe sits.

Parameters

opts?

object & SharedSourceHints & AbortableCall

Returns

FragmentRecordingHandle


snapshotLive()

ts
snapshotLive(opts?): Promise<{
  height: number;
  jpeg: Buffer;
  retained?: true;
  width: number;
}>;

A fresh still decoded from a short live burst.

width/height describe the RETURNED IMAGE, read back out of it rather than taken from the stream's frame header: the header states the geometry at capture start, and a camera whose stream reconfigures mid-burst leaves it contradicting the bytes — which a caller sizing a buffer or caching by resolution cannot detect short of parsing the JPEG itself.

Rejects with LiveSnapshotUnavailableError, whose LiveSnapshotUnavailableError.retryable says whether another attempt could succeed.

Carries SharedSourceHints for the reason stated there: a still polled on an idle camera is routinely the call that OPENS the shared pull, so it decides the power budget and the retained window for every egress that joins later.

Parameters

opts?

object & SharedSourceHints & AbortableCall

Returns

Promise<{ height: number; jpeg: Buffer; retained?: true; width: number; }>


snapshotStored()?

ts
optional snapshotStored(): Promise<Buffer<ArrayBufferLike>>;

Return the latest validated push thumbnail retained in memory. This passive operation performs no network, storage, P2P, live-media, or transcoding work at call time. It rejects with StoredSnapshotUnavailableError when no image is retained. Optional because cache ownership and capability binding belong to the client.

Returns

Promise<Buffer<ArrayBufferLike>>


talkback()?

ts
optional talkback(opts?): Promise<TalkbackHandle>;

Open the camera's talkback path — audio travelling from the host TO the device, the opposite direction to everything else here. See TalkbackHandle for the accepted audio. Optional (an unbound model has no client), and absent on a device whose talkback wire is unverified.

Parameters

opts?

object & SharedSourceHints

Returns

Promise<TalkbackHandle>

Independent and unofficial. Not affiliated with, endorsed by, or sponsored by Anker Innovations, Anker eufy, or eufy. "Anker eufy", "eufy" and "Anker" are trademarks of their respective owners. Use responsibly — rapid or failed logins can trigger captcha or temporary cooldowns.