yl_player v0.2 development API
yl_player is the application package. It requires Dart 3.12 and Flutter 3.44
and endorses Android, iOS, and macOS implementations. Version 0.2 is a breaking
cutover; there is no v0.1 compatibility shim.
Apple platforms provide opt-in AirPlay video with YlLoadOptions(airPlay: true),
YlAirPlayButton and player.airPlayState. HLS live/VOD uses AVPlayer directly;
compatible custom H.264/AAC VOD and RTMP/RTMPS/HTTP-FLV live can be remuxed to HLS. See
AirPlay setup, limits and verification.
Lifecycle
Create one player during asynchronous startup or initState, never from
build. A Load returns a handle for one committed session. Ready and First
Frame are later, independent milestones. The following example is exercised by
test/documentation_examples_test.dart:
import 'package:yl_player/yl_player.dart';
Future<void> playVideo(Uri uri, {bool expectVideo = true}) async {
final player = await YlPlayerController.create(
options: const YlPlayerOptions(
decoderPolicy: YlDecoderPolicy.hardwarePreferred,
audioPolicy: YlAudioPolicy.appManaged,
),
);
try {
final source = YlNetworkSource(
uri,
intent: YlStreamIntent.onDemand,
format: YlMediaFormat.mp4,
request: YlHttpRequest(
headers: const {'User-Agent': 'yl-player-demo'},
),
);
final assessment = await player.assess(source);
if (assessment.outcome == YlSourceAssessmentOutcome.incompatible) {
throw YlPlayerException(assessment.rejection!);
}
final session = await player.load(
source,
options: const YlLoadOptions(
bufferStrategy: YlBufferStrategy.automatic(),
),
);
await session.ready;
await session.play();
if (expectVideo) await session.firstFrame;
await session.seekTo(const Duration(seconds: 10));
final replacement = await player.load(source);
try {
await session.pause();
throw StateError('The replaced session unexpectedly remained current.');
} on YlPlayerException catch (error) {
if (error.failure.code != YlFailureCodes.sessionStale) rethrow;
}
await replacement.play();
await player.stop();
} finally {
await player.dispose();
}
}
assess is decoder-free where possible. compatible means the known route and
requested policies can be enforced; it does not prove reachability, media
integrity, codec initialization, or runtime timing. requiresInspection means
Load must inspect the source before it can decide. Load can therefore still
fail after either non-incompatible result.
Load completes only after the candidate commits and its matching authoritative
state has been accepted. session.ready means decoder/media readiness.
session.firstFrame means a frame from that session reached the committed
public output. Do not await First Frame for audio-only media. A newer Load,
successful Stop, or Dispose makes the old handle fail locally with
session.stale; a rejected replacement or rejected Stop leaves the accepted
session usable.
Session commands are play, pause, seekTo, seekToLiveEdge,
setPlaybackSpeed, selectAudioTrack, and setVideoConstraints. Player-wide
commands are setVolume, stop, and dispose. Await or otherwise observe every
returned Future. Stop keeps the player and texture reusable; Dispose is
idempotent and terminal.
Rendering and observation
YlPlayerView(
controller: player,
placeholder: const Center(child: CircularProgressIndicator()),
)
YlPlayerView contains no controls or application state. It defaults to a
centered BoxFit.contain texture on black and keeps the placeholder visible
until the current session's First Frame. Subscribe to player.states,
player.events, or the controller's Listenable notifications. State and
events are authoritative native observations; command rejection does not
invent either.
Download rate
Subscribe to player.downloadBytesPerSecond before calling load to observe
opening traffic as well as later buffering. The stream yields nullable bytes
per second: null means no reliable observation; 0 is an observed idle window.
The SDK measures changes in actual received network bytes using elapsed time;
applications do not need to poll metrics or calculate rates. Cancel the
subscription when it is no longer needed. Sampling starts with the first
subscriber, stops with the last, and is cleaned up by dispose.
The stream follows the newest requested load, independently of the committed playback state, and resets on replacement, stop, and failure. A rejected load does not change the committed playback session. A first counter observation establishes a baseline, so subscribing mid-playback never reports all earlier bytes as a new burst. This is download only, not upload or media bitrate. AVPlayer observations come from actual access-log byte counts and can arrive in batches or remain unavailable. When the media URL points to a local proxy, the rate measures bytes received by the player, not the proxy's origin fetch.
Sources and policies
Use YlFileSource, YlNetworkSource, or Android-only
YlAndroidContentSource. YlHttpRequest.headers is ordinary metadata.
Authorization, cookies, API keys, tokens, and every other credential-bearing
value belongs in credentials so the origin policy can strip it permanently
after a cross-origin transition.
Player-wide decoder and audio ownership choices live in YlPlayerOptions.
Autoplay, start position, buffer strategy, video constraints, and an optional
decoder override live in YlLoadOptions. See the repository
policy semantics, support matrix,
and diagnostics guide.
Errors
Public asynchronous failures are YlPlayerException values carrying a
YlFailure. Branch on stable category, code, retryability, and scope. Log the
opaque diagnostic ID for correlation. Do not log source locators or request
metadata. See diagnostics.
External subtitles (opt-in)
Create the controller with enableTextSubtitles: true to use the Dart-owned
external SRT/WebVTT/TTML/ASS/SSA text and PGS/VobSub bitmap runtime on Android, iOS or macOS. YlPlayerView then draws
selected subtitles over ordinary video without an extra widget:
final player = await YlPlayerController.create(enableTextSubtitles: true);
final session = await player.load(const YlFileSource('/absolute/video.mp4'));
await session.replaceExternalSubtitles([
const YlExternalSubtitle(
id: 'english',
source: YlFileSource('/absolute/english.srt'),
format: YlSubtitleFormat.srt,
language: 'en',
),
]);
await session.selectSubtitleTrack('english');
await session.setSubtitleOffset(const Duration(milliseconds: 500)); // Delay.
// Build YlPlayerView(controller: player) in the widget tree.
await session.selectSubtitleTrack(null); // Immediately hide subtitles.
// Dispose the controller when its owning widget is removed.
Use YlNetworkSource for HTTP(S), including separately scoped credentials.
Both network policies are supported. platformDefault uses a 30-second total
read deadline, five redirects and no automatic retries. For
networkPolicy: const YlNetworkPolicy.managed(), the defaults are 10 seconds
through response headers, 15 seconds of body inactivity, three retries,
500 ms exponential backoff capped at 8 seconds, and five redirects across all
attempts. Nonempty body chunks reset only the read deadline; managed requests
have no total-call timeout. Valid Retry-After seconds or HTTP dates override
backoff within its cap; excessive values stop retries. TLS/certificate errors,
permanent HTTP errors, unsupported encodings, oversized input and parse errors
are never retried. Cancelling a session or replacement also cancels backoff.
Cross-origin redirects permanently strip credentials, including retries and a
later return to the original origin. File/HTTP input is streamed within a
4 MiB aggregate catalog budget, with at most 10,000 cues and bounded text spans.
Replacing the catalog is atomic; a failed download or parse leaves the previous
catalog usable and reports a sanitized YlPlayerException. A newer replacement,
load, stop or dispose cancels pending I/O. New sessions reset subtitles and offset.
In this opt-in mode, event revision identifies the public snapshot at delivery;
occurredAt retains the original event time. This avoids mixing native revisions
with locally published subtitle state.
Selection starts off; [] removes all external entries. Same-ID replacement
updates content while preserving current selection. Positive offsets delay cues.
SRT/WebVTT rendering supports plain UTF-8 text, balanced b/i/u markup, common entities,
and horizontal WebVTT position/align settings. Unsupported markup is rejected.
Cue timing follows published media positions, so precision depends on
YlPlayerOptions.positionUpdateInterval; no independent timer advances captions
while paused or buffering. Seek hides text until a post-ACK position snapshot.
Text stays upright in the visible video aperture after fit, crop, PAR and rotation.
For TTML, set format: YlSubtitleFormat.ttml with a file or network source.
The supported TTML1 subset uses the http://www.w3.org/ns/ttml namespace,
media clock or h/m/s/ms timing, parallel body/div containers and finite paragraph
intervals. It renders nested untimed spans, line breaks, inherited/referenced
bold/italic/underline/color/text alignment, and percentage regions with
before/center/after vertical alignment. XML whitespace and entities are decoded
once; text resembling HTML is displayed literally. Regions are clipped to the
visible video aperture. DTD, external references, timed spans, sequential/frame
or tick timing, custom fonts/sizing, animation, vertical text and unsupported
properties are rejected explicitly. See the TTML subset contract
for precise limits; this is not a full TTML/IMSC conformance claim.
For ASS/SSA, set format: YlSubtitleFormat.ass or .ssa. The UTF-8 text
subset requires explicit PlayResX/Y and WrapStyle 1 or 2. It renders font
fallback, basic styles/colors, outline/shadow, margins/alignment, positioned
text, layers, movement and fade driven by media time. Smart wrapping 0/3,
font attachments, vector drawing/clips, karaoke, transformations and unsupported
style fields fail explicitly. This Flutter renderer does not promise libass
font-metric or pixel equivalence. See the ASS/SSA subset contract
for accepted tags and budgets.
For PGS, set format: YlSubtitleFormat.pgs with a local .sup file or HTTP(S)
source. The bounded binary SUP subset renders palettes, partial palette updates,
fragmented RLE objects, cropping, windows, overlap and clear events. Selection,
offset and seeking use media time; ordinary-video fit and source cropping apply.
Expanded bitmap data is capped at 64 MiB per catalog; only active frames create
engine images, with late asynchronous results disposed after a switch or reset.
Unsupported segment/plane semantics fail explicitly. See the PGS subset contract.
For VobSub, set format: YlSubtitleFormat.vobsub, source to the .sub
binary and companionSource to its .idx index. Each resource uses its own file
or managed HTTP policy. A unique matching language selects a substream;
otherwise the index default (or first stream) is used. The DVD subset renders
field RLE, RGB palettes, contrast, positioning and timed control updates, sharing
the 64 MiB bitmap catalog budget with PGS. Unsupported effects fail explicitly.
See the VobSub subset contract.
This opt-in owns the subtitle catalog and cannot be combined with a native subtitle SPI. Embedded support covers the Matroska and MP4 text routes below. It does not support WebVTT CSS/regions/karaoke or protected video overlays. HDR subtitle brightness and native-device synchronization remain unqualified. Without the opt-in, the existing native capability/SPI gates continue to apply.
Matroska, WebM and MP4 embedded subtitles
With the same enableTextSubtitles: true controller, discover supported text and bitmap tracks from
its committed file or HTTP(S) Matroska/WebM/MP4 source, then select a returned identity:
final session = await player.load(YlFileSource('/media/movie.mkv'));
await session.discoverEmbeddedSubtitles();
final tracks = player.state.subtitles.tracks.where((t) => !t.isExternal);
if (tracks.isNotEmpty) await session.selectSubtitleTrack(tracks.first.id);
YlPlayerView renders Matroska UTF8/SRT, ASS/SSA, WebVTT, PGS and VobSub, and non-fragmented MP4
tx3g (mov_text), wvtt (WebVTT) and stpp (TTML) tracks using media time.
Fragmented MP4 also supports tx3g/wvtt/stpp when initialization and fragments
are in the same complete file or stable HTTP resource.
Discovery is explicit and bounded; Matroska TrackUID (mkv:<UID>) or MP4 Track ID
(mp4:<ID>) identifies a track within the session. Replacing/clearing external subtitles preserves embedded entries;
rediscovery preserves external entries and a still-present selection. A new
session starts with subtitles off. Unsupported codecs/effects fail atomically.
For a network MKV or MP4, load a YlNetworkSource with its request metadata and network
policy, then call the same discovery method. HTTP(S) servers must return single
206 byte ranges with a stable strong ETag and total length. Managed connect/read
deadlines, retry/redirect budgets and credential origin restrictions apply to the
whole discovery. Later ranges use If-Match; changed versions fail atomically.
The scan uses a bounded cache and request/byte budgets, with no full-body fallback.
For clear static DASH, the same discovery API also reads MPD text tracks:
WebVTT/TTML and fragmented MP4 wvtt/stpp, including separate initialization
and media URLs. Selected current/next segments use the source network policy,
with Period/PTO mapped to programme time. Seek, stop and replacement discard
stale cues; subtitle failure is isolated and video continues. HLS subtitle
discovery and native subtitle callbacks remain unsupported. This does not claim
physical-device acceptance. See the embedded subset contract
the network range contract
and MP4 contract.
MP4 tx3g text is decoded literally (UTF8 or BOM UTF16), including basic styles,
font fallback, size, color and horizontal alignment. format: srt on its catalog
entry denotes normalized standard text, not the encoded sample format. Complex
edits, positioning, karaoke and per-sample forced effects are rejected. Font
metrics, track geometry and device synchronization are not QuickTime-certified.
MP4 wvtt entries retain format: webvtt. Sample-local vttc payloads support
basic b/i/u markup, entities and horizontal align/position settings; vtte
clears cues. Multiple cues in one sample share its media interval, including
edit-list timing. Unsupported CSS/regions/advanced settings and malformed boxes
fail atomically. See the MP4 WebVTT contract
for the bounded subset and validation limits.
MP4 stpp entries use format: ttml and the existing bounded TTML styled-text
and percentage-region renderer. XML times are track composition times, clipped
to each sample and mapped through supported edits. Documents keep their own
style/region scope, and their raw bytes count toward the shared catalog budget.
Auxiliary images, cell/pixel font styling and advanced TTML/IMSC remain unsupported;
see the MP4 TTML contract.
For fMP4, trex/tfhd/tfdt/trun supply bounded sample locations and timing.
The same track selection, tx3g/WebVTT/TTML rendering and managed range rules apply.
This is explicit discovery of a complete resource, not live segment polling.
See the fMP4 contract
for defaults, addressing, edits and unsupported layouts.
Fragmented tx3g uses the same literal styled text and normalized srt catalog
format as non-fragmented MP4; sample-description changes update default styles.
See the fMP4 tx3g contract.
MP4 tracks can also combine initial sample-table subtitles and later fragments. Discovery joins them by Track ID, checks decode continuity and subtitle byte extents across both portions, and publishes the catalog atomically. See the hybrid MP4 contract.
Matroska WebVTT supports S_TEXT/WEBVTT and the FFmpeg-compatible
D_WEBVTT/SUBTITLES / D_WEBVTT/CAPTIONS mappings through the same file/managed
HTTP discovery API. Cue text, identifier and horizontal position/alignment are
rendered using the existing strict WebVTT subset. Global headers and NOTE blocks
are validated and budgeted. Advanced CSS/regions/inline timestamps remain
unsupported; see the bounded contract.
WebM embedded WebVTT (D_WEBVTT/SUBTITLES / CAPTIONS) is available through
discoverEmbeddedSubtitles() for files and managed HTTP(S). Use automatic for
content detection or YlMediaFormat.webm to require a WebM DocType; explicit
Matroska and WebM hints reject mismatched content. Track identities retain the
mkv:<UID> EBML namespace. The existing bounded WebVTT renderer and managed
Range rules apply; this does not expand native video decoding support. See the
WebM contract.
Matroska S_HDMV/PGS bitmap tracks also use discoverEmbeddedSubtitles() and
mkv:<TrackUID> selection. Raw binary segments use container presentation times
and optional duration caps; palette updates and clear displays reuse the PGS
overlay. Embedded and external tracks share the 4MiB encoded / 64MiB RGBA catalog
budgets. See the PGS container contract.
Matroska S_VOBSUB bitmap tracks use the same discovery/selection API. The
container carries global IDX size/palette settings and raw DVD SPU blocks, so no
external IDX/SUB pair is needed. Container time and duration caps constrain SPU
start/stop and alpha updates. Existing two-field RLE rendering, managed ranges,
atomic catalogs and shared encoded/RGBA budgets apply. See the
VobSub container contract.
Matroska subtitle tracks may carry up to four ordered ContentEncoding stages for blocks,
CodecPrivate, or both: zlib or header stripping. Restoration precedes the existing text/bitmap decoder, with
strict zlib framing/checksum validation and bounded output. Stored compressed
bytes/settings, every intermediate output and final content count toward the shared input budget,
including later external subtitle replacements. Encryption,
next-encoding scope and WebM compression remain unsupported. See the
compression-chain contract.
Compressed CodecPrivate is restored before UTF-8 and codec-specific header validation. Private-only scope leaves blocks unchanged; combined scope restores the header and each block independently. Both stored and restored header bytes remain charged, including empty tracks and subsequent external replacements. See the private-header contract.
MP4 subtitle discovery shares its 200,000 structural-box allowance across the
container index, tx3g configuration/sample-modifier boxes, WebVTT
configuration/sample/cue boxes and TTML configuration boxes. Tracks, sample-description changes, initial samples and later fragments
use the same allowance. Exceeding it reports subtitleTooLarge atomically,
retaining the previous subtitle catalog and selection. Existing local child,
payload and cue limits also apply; this is not a whole-process memory bound.
MP4 extended subtitle language
Embedded tx3g/wvtt/stpp tracks in ordinary, fragmented and hybrid MP4 now read
elng as a well-formed BCP 47 tag, preserving its spelling and overriding the
packed mdhd language. Missing elng retains the packed language. Track identity
and selection remain based on track ID. Tags are limited to 128 ASCII characters;
malformed metadata rejects discovery atomically. Existing mdhd validation still
applies. This performs syntax checking, without registry validation, canonicalization
or automatic language selection. File and managed HTTP use the existing bounded
loader. See the contract.
Audio track identity and Apple DASH
Select audio using an ID from the current session's audioTracks catalog and
selectAudioTrack(id). Treat IDs as opaque: labels and languages may repeat,
including main and commentary tracks in the same language. Command completion
acknowledges selection; it does not prove physical audible output.
The retained Apple DASH-to-HLS adapter supports up to 16 AAC-LC audio
AdaptationSets with one Representation each and a stable semantic identity set.
The owned DASH programme path additionally handles changing Period catalogs,
independent track timing and managed networking. Added tracks preserve an
existing selection; missing explicitly selected content reports track.not_found
without substituting another track. Same-session quality replacement and
restoration retain the choice; new loads reset it. Ordinary AVPlayer restoration
uses system option identities. This does not add audio ABR or mixing. See the
programme support matrix
for codec, timing and policy boundaries.
Android exposes one logical audio choice per Media3 group. DASH choices use
declared AdaptationSet identity and semantic metadata, so Representation rename,
bitrate changes and group reordering do not redirect a selection. Other formats
require stable available Format IDs and metadata; missing IDs are not replaced
by array positions, and ambiguous content is rejected. Explicit selection
survives same-session restoration and is applied to reading-ahead Periods. If the
chosen content disappears, track.not_found can arrive before the Period boundary
during prebuffering. Transient empty catalogs preserve intent; new loads reset it.
Libraries
- yl_player
- Explicit player/session playback API.