Live
Playdeck reports whether the active source is live on PlayerState.live, and
offers one command to catch up to its edge, seekToLiveEdge. The two are
gated separately: knowing a source is live and being able to seek to its edge
are different facts, and a provider can have the first without the second.
capabilities.liveEdge says whether the second exists per source, and
Player.LiveIndicator is the one part built over both — see the Contract
guide's live entry for its data attributes, and the Capabilities matrix
guide for the general capability-gating shape this bends.
The state
Two things to read, and they answer different questions:
live, aPlayerLiveState:nullwhile the source is not live or its liveness is not yet known, otherwise{ isLive, atLiveEdge, offsetFromEdge }.isLiveistruewhenever the value is non-null — the shared derivation never publishesisLive: false, so there is no third, reachable "known not live" case beyondnullitself.atLiveEdgemarks playback within ten seconds of the provider's own edge, the tolerance every adapter measures against.offsetFromEdgeis how far behind that edge playback has fallen, in whole seconds and never negative —0at or ahead of it. It is rounded deliberately: an unrounded float would differ on nearly everytimeupdate, and whole seconds is whatPlayer.Timerenders at besides.capabilities.liveEdge, anAvailability: whether the provider can report a live edge to seek to at all, told apart the waychaptersis —unavailable/providermeans the provider has no way to answer this for any source,unavailable/sourcemeans this particular source is not live.
The two are independent facts. A source can be known live (live non-null)
while there is nothing to seek to — see Wistia below.
Which providers can report it
| Provider | live |
liveEdge |
|---|---|---|
| native, native-HLS engines | Derived from the element's own raw duration and moving seekable window |
available once live and the seekable window has a finite end; unavailable/source otherwise |
| hls.js engine | The same derivation, with hls.js's own live flag taken as an authoritative hint over the raw duration | available once hls.js reports a finite liveSyncPosition over a live, scrubbable window; unavailable/source otherwise |
| vimeo | Never populated — @vimeo/player has no live concept anywhere in its type definitions or its shipped code, so live stays null on a live-shaped embed |
Fixed unavailable/provider |
| youtube | Never populated — the adapter recognises /live/<id>, the canonical URL for a broadcast, at the source-detection stage (Provider setup), but reports nothing about liveness once attached, so live stays null for the whole session |
Fixed unavailable/provider |
| wistia | Derived from the embed's own mediaData.mediaType, "LiveStream" being the one live value; duration() stands in for the edge, since PublicApi has no seekable-range accessor |
Fixed unavailable/provider |
Wistia is the instructive middle case: it can tell you the stream is live and
how far behind its own notion of the edge playback is, and still cannot
honour seekToLiveEdge — PublicApi gives this adapter neither a seekable
range nor a dedicated live-edge member to build a real target from.
Player.LiveIndicator renders there as a permanently disabled badge, never a
button.
seekToLiveEdge
One command, issued through controller.seekToLiveEdge() directly, or
through Player.LiveIndicator's own click handler. It is gated ahead of
dispatch rather than left to fail once issued: unlike every other command on
PlayerController, there is no controller-computed fallback position, so a
provider that cannot answer is refused before ever being asked —
capabilities.liveEdge not available, or no seekToLiveEdge method on the
active provider, both refuse the same way.
Where it lands is the provider's own notion of the edge, never one the
controller computes: hls.js's liveSyncPosition, deliberately behind the raw
seekable end, on the hls.js engine; the raw seekable end itself — the only
notion of an edge a plain media element has — on the native and native-HLS
engines. hls.js also withholds liveEdge while its seekable window is too
narrow to be worth scrubbing — the same width check that, on a narrow live
window, takes seek itself back to unavailable/source.
Player.LiveIndicator
Unlike every other capability-gated part in this package, LiveIndicator
does not vanish when its gating capability is unavailable — a deliberate,
named exception, because it reports a property of the stream (live) rather
than offering a control (seekToLiveEdge), and being live is true whether or
not a seek-to-edge command exists on the active provider:
- Renders nothing while
liveisnull. - Otherwise always renders
data-playdeck-part="live", withdata-state"at-edge"or"behind-edge"fromlive.atLiveEdge. disabledreflectscapabilities.liveEdgerather thanlive— set wheneverliveEdgeis notavailable, so Wistia's badge stays permanently non-interactive while the same badge over a native or hls.js source becomes a real button onceliveEdgeresolves.- Clicking it issues
controller.seekToLiveEdge(), only where it is notdisabled. - The default
aria-labelis"Go to live"only where pressing it would act —liveEdgeseekable and behind the edge — and"Live"in every other reachable state, including at the edge with seeking available. A consumeraria-labelalways wins, in every state.
It renders no other data-playdeck-part: live is the whole of its own
contract, documented alongside every other part's in the Contract guide.
The control bar on a live source
Player.SeekSlider and Player.Time both take a live source with no special
prop for it, and each reads a different slice of state to do it.
SeekSlider never reads live at all — it infers a scrubbable window purely
from duration and seekable: [0, duration] for a source with one, or the
extent of seekable where there is none, which is what a live source with a
moving seekable window publishes instead. Where neither exists yet — a live stream
reporting no seekable window at all — there is nothing to scrub, and the
slider carries data-state="idle" rather than a range that cannot move; the
Contract guide's own styling example for the part covers exactly that
state.
Player.Time does read live directly, and reports something different for
type="current" on a live source: not elapsed time, but
live.offsetFromEdge — how far behind the edge playback is — displaying the
word LIVE (overridable through the liveLabel prop) once live.atLiveEdge,
or a negative offset (-0:42) while behind it. type="duration" and
type="remaining" have no total to report on a live source, since its
duration is never a finite number, and display no text of their own — the
untimed <span> the Contract guide's time entry documents, rather than
a 0:00 that would misstate a live stream as a zero-length video.
Accessibility
Player.LiveIndicator's trigger is a real <button> in both its states,
disabled rather than aria-disabled where liveEdge is unavailable — out
of the tab order and announced unavailable rather than merely styled that
way. Its label follows the rule above, and satisfies WCAG 2.5.3 Label in
Name the same way every other stateful control in this package does: the
visible text stays "Live" throughout, and "Go to live" contains it.
Out of scope
A standalone live-edge button, outside LiveIndicator, does not exist —
seekToLiveEdge is issued through its click handler or through
controller.seekToLiveEdge() directly, with no dedicated menu or slider
affordance of its own. Markers on the seek slider for the live edge, or for
how much of the seekable window is scrubbable versus already lost, do not
exist either; a consumer composes those over Player.SeekSlider's own
seekable-derived geometry.