@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()
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
const stream = await cam.live();
stream.on("video", (frame) => write(frame.data)); // Annex-B
stream.stop(); // detach this consumeropenReadable()?
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()
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()?
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
snapshotLive()
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()?
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()?
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>