dart_cast 0.7.4
dart_cast: ^0.7.4 copied to clipboard
A pure Dart cross-platform casting package supporting Chromecast (CASTV2), AirPlay, and DLNA with built-in HTTP proxy for header injection.
0.7.4 #
New #
CastMedia.defaultSubtitlenames the subtitle track playback starts with (matched by URL againstsubtitles). Honoured by Chromecast (activeTrackIdson LOAD) and DLNA (picked oversubtitles.first)
Fixed #
- Chromecast no longer activates the first subtitle track when none was chosen — the track-list order silently picked the on-screen language (a French-first list produced French subtitles regardless of what the app showed as selected). No
defaultSubtitlenow means no subtitle shown - Chromecast re-asserts the active subtitle after every pause → resume, including pauses from the TV remote. The Default Media Receiver can stop rendering cues after a pause/resume cycle while still reporting the track active; toggling the track set (deactivate + reactivate) re-attaches its renderer, which is what users were doing by hand
- Chromecast LOAD retries are no longer failed by the previous attempt's dying receiver session. An unsolicited
IDLE/ERRORbroadcast from a session that neither answers the in-flight LOAD'srequestIdnor matches its session id is now ignored as a stale corpse (and its session deprecated). Observed live: a next-episode LOAD was declared failed 1ms after being sent — the corpse of the failed first attempt — while the receiver went on to play the episode fine, leaving the app convinced the old episode was still playing
0.7.3 #
New #
MediaSourcereaders may now open asynchronously — anasyncfunction satisfies the reader, so sources backed by Androidcontent://URIs, network handles or a decrypt handshake no longer need aStream.fromFuture(...)workaround (#12). Existing synchronous readers are unaffected; anasync*reader needs a declared return type, which theMediaSourcedocs show
0.7.2 #
Fixed #
- AirPlay: receivers advertising both AirPlay 1 and AirPlay 2 video (Apple TVs, the macOS receiver) now get one AirPlay 1 attempt if the AirPlay 2
/playreturns 404. 0.7.0 sent every AirPlay 2 device down the V2 path and never offered V1, so a device implementing only the older endpoints could not play at all. The retry is gated on the device advertising bit 0 — it is not a return of the blind version probe 0.7.0 removed
0.7.1 #
Documentation only — no code changes.
- AirPlay's status, the reason video fails on the receivers tested, and how to check your own device moved out of the README into
doc/AIRPLAY.md - Protocol status is scoped to the devices actually tested rather than reading as a verdict on AirPlay itself; results from other hardware, an Apple TV especially, are welcome
- The feature table distinguishes "implemented but blocked by the receiver" from "not built yet"
0.7.0 #
AirPlay 2 and DLNA fixes plus caller-supplied byte sources, driven by testing against a TCL Google TV.
Breaking #
-
AirPlay selects its protocol version from the advertised feature bits instead of probing
/play; devices with no video bit throwUnsupportedFeatureExceptionimmediately -
AirPlaySession.connect()pairs with every AirPlay 2 receiver, so it can now throwNeedsPairingExceptionwhere it previously returned a session. If you callconnect()on an AirPlay 2 device and do not catch that exception, this release will start throwing at you. The old behaviour was not useful — the session it returned had fallen back to a legacy path those devices reject, so it could not cast anything — but it did not fail either. Handle it by prompting for the on-screen passcode and callingpairSetup(pin), or by catching it and offering Chromecast/DLNA instead:try { await session.connect(); } on NeedsPairingException { // Show the code on the TV, then: await session.pairSetup(pinTheUserTyped); await session.connect(); }Most smart TVs use PIN-less transient pairing and never reach this path; it is devices that demand a passcode which now surface it.
-
AirPlayMediaController.dispose()returnsFuture<void>;play()takesstartPositionSeconds:instead ofstartPosition:
Fixed #
- DLNA: seeking works when casting HLS — it previously stopped playback outright
- DLNA: HLS casts report the real duration instead of one second
- DLNA: a playback command no longer crashes with
ClientException: Connection closed before full header was receivedwhen the renderer drops an idle connection — the action is retried once (#9) - DLNA: a renderer that accepts a connection then stalls fails after 10s instead of hanging the caller forever
- DLNA: routine connection churn no longer floods the log with errors
- AirPlay: AirPlay 2 receivers pair and establish a session correctly — PIN-less transient pairing, RTSP setup and keep-alive, all confirmed against a real TV. The package previously fell back to a legacy path these devices reject
- AirPlay: failures report a cause instead of being swallowed — an unsupported device, a rejected handshake or a receiver-reported error now surfaces
- AirPlay: further video-path corrections (protocol version selection, playback-state parsing, request ordering,
startPosition). Unverified end to end — no receiver tested accepts a video URL, so nothing past the handshake can be exercised yet
New #
MediaSource+MediaProxy.registerSource()+CastMedia.source()— cast bytes the package cannot open itself: Androidcontent://URIs, Flutter assets, decrypted or in-memory content (#12). The application supplies a range-aware reader; the proxy handles HTTP, byte ranges and seeking. Works on Chromecast, DLNA and AirPlay, since it uses the same route local files already useAirPlayTimingServer, transient pairing,PlaybackInfo.errorHlsParser.totalDuration/extractSegments,MediaProxy.probeHlsDuration/parseTimeSeekRangetool/airplay_probe.dart,tool/airplay_hardware_check.dart,tool/dlna_hardware_check.dart,tool/chromecast_hardware_check.dart— hardware verification scripts
Known limitations #
- AirPlay video does not play on the TV tested and cannot: that receiver exposes no
/playendpoint, and Apple's own QuickTime fails on it identically — use Chromecast - DLNA sidecar subtitles did not render on the TV tested; embed SRT in MKV
0.6.0 #
New #
- Alt-audio HLS sources (split video + audio) now play on Chromecast and DLNA — pure-Dart MPEG-TS remuxer combines them into one muxed stream
MediaProxy.registerAltAudioMuxed()— synthetic single-stream HLS master backed by per-segment muxingHlsParser.extractAudioRenditions+audioGrouponextractVariantsentries- TS DVB-table stripper for proxied
video/mp2t, on by default; opt out viaregisterMedia(stripDvbTables: false) MediaLoadFailedExceptionfromloadMedia()onLOAD_FAILED/IDLE+ERROR/ 15s timeout, carrying the receiver-reported reason- HLS LOAD retries pass-through → muxer + stripper on failure, with
requestId/mediaSessionIdfiltering against stale responses ChromecastSession.enableReceiverDebugNamespaces(defaultfalse) — opt-in CaC + debugoverlay subscribe and verbose receiver logging for debugging
Fixed #
setSubtitle()activates the correct track (was alwaystrackId=1)- CORS
Access-Control-Allow-Originechoes the receiverOriginwhen the LOAD includes atracksarray - State machine accepts
loading → {buffering, paused, idle}andidle → {buffering, playing, paused} - DLNA: failed
loadMediareturns toidleinstead of stuck inloading Content-Lengthonly forwarded when the body streams through unchanged- Proxy segment / subtitle URLs end in
.ts/.vttfor the Chromecast URL-extension probe image/*Content-Type on proxied MPEG-TS subresources rewritten tovideo/mp2t
0.5.1 #
Fixed #
- Pub points (static analysis): Resolved the six pana lints under
lib/that kept the 0.5.0 static-analysis score at 40/50:curly_braces_in_flow_control_structuresints_keyframe_scanner.dartandairplay/auth/airplay_auth.dart— single-statementifbodies now wrapped in braces.use_super_parameterson the twoCastMessage_*enum constructors — converted to super-parameter syntax.camel_case_typeson the hand-writtenCastMessage_ProtocolVersion/CastMessage_PayloadTypebindings inchromecast/proto/cast_channel.dart— suppressed via a file-level// ignore_for_file: camel_case_types, since these identifiers intentionally mirror the namesprotoc --dart_outwould emit for Chromium'scast_channel.proto.
0.5.0 #
Changed #
- protobuf: Upgraded
protobufdependency from^3.1.0to^6.0.0for the latest performance improvements and bug fixes (#6). Hand-written CastMessage bindings remain compatible; no API changes. - Minimum Dart SDK: Bumped
sdkconstraint from^3.0.0to^3.7.0(required byprotobuf4.1.1+).
0.4.3 #
Fixed #
- Analyzer warnings: Resolved pre-existing unused variable warnings in test files
- Code formatting: Applied
dart formatto all source files
0.4.2 #
Fixed #
- Proxy IP selection: MediaProxy now picks the local interface on the same subnet as the target cast device, fixing casting failures on Android where the proxy would bind to a non-WiFi address unreachable by the cast device
- Concurrent loadMedia guard: All protocol sessions (Chromecast, AirPlay, DLNA) now ignore duplicate
loadMedia()calls while one is already in progress, preventing multiple LOAD messages from being sent to the device - Socket disconnect detection: Chromecast session now handles message stream errors and unexpected closures, transitioning to disconnected state so the app can react (previously the session stayed "connected" after a network drop)
0.4.1 #
Fixed #
- DLNA start position: seek now deferred until TV confirms PLAYING state (immediate seek was ignored by TVs still loading)
- Chromecast subtitles for local files: subtitle files now served via HTTP/1.1 with CORS headers (HTTP/1.0 path lacked
Access-Control-Allow-Originrequired by Shaka Player) Http10FileServer: returns 416 Range Not Satisfiable for invalid/out-of-bounds rangesSubtitleConverter.vttToSrt(): uses targeted regex for timestamp dots (no longer corrupts non-timestamp content), expands 2-component MM:SS timestamps to HH:MM:SS- Removed stray
response.close()after socket detach in synthetic content handler
New #
- Updated example app with optimistic slider state, keyboard shortcuts, mute toggle, and responsive layout
- Added protocol feature indicators in example device picker
- Updated README with "What Works Where" feature matrix, protocol notes, and DLNA local files guide
0.4.0 #
Breaking #
- DLNA file serving now uses HTTP/1.0 via raw sockets instead of Dart's HttpServer response. This fixes playback on TCL Google TV and other renderers that reject HTTP/1.1.
- Removed DLNA-specific HTTP headers (
transferMode.dlna.org,contentFeatures.dlna.org,Connection: close) from file responses — these caused some DLNA renderers to reject content. - Cleared Dart's default security headers (
x-frame-options,x-xss-protection,x-content-type-options) from the proxy server.
New #
Http10FileServer— reusable HTTP/1.0 file server class for DLNA compatibilityCastMediaType.mkv— MKV container support for casting with embedded subtitlesSubtitleConverter.vttToSrt()— WebVTT to SRT conversionSubtitleConverter.toAss()— VTT/SRT to ASS conversion with customizable styling (font, outline, shadow, margins)MediaProxy.registerSubtitleVariants()— registers both SRT and VTT subtitle variants for maximum TV compatibility- DLNA subtitle variants: DIDL-Lite now includes both SRT and VTT subtitle URLs with proper format/type attributes
- DLNA seek instantly updates position without waiting for next polling cycle
- MKV content type detection and proxy URL extension support
Fixed #
- DLNA playback failing on TCL Google TV and similar renderers that reject HTTP/1.1 responses
- DLNA flags mismatch between DIDL-Lite protocolInfo (
21500000) and HTTP headers (01700000) — now aligned to01700000matching VLC and MiniDLNA - Missing
DLNA.ORG_PNprofile name in protocolInfo (AVC_MP4_HP_HD_AACfor MP4,MPEG_TS_HD_NA_ISOfor TS) - DLNA seeking not working — Range responses now correctly return 206 with Content-Range
- Subtitle file proxy URLs missing file extensions (
.vtt,.srt) — some TVs need extensions to recognize subtitle files - Synthetic content (subtitle playlists, converted subtitles) now served via HTTP/1.0 for DLNA compatibility
0.3.1 #
- Added protocol status table to README with testing coverage and known limitations
- Documented DLNA MP4 playback issues (TV-dependent, some reject proxy-served MP4)
- Documented DLNA subtitle limitations (
sec:CaptionInfoExnot universally supported) - Documented AirPlay video casting limitations (404 on some Google TV devices)
- Recommended Chromecast as the primary tested protocol for local file casting
0.3.0 #
Breaking changes #
DefaultMediaTransformerno longer wraps local TS files in HLS — it serves them directly via the proxy. UseTsHlsMediaTransformerorFfmpegMediaTransformerfor Chromecast-compatible local TS casting.CastMedia.useChunkedHlsis deprecated and will be removed in a future release.TsHlsMediaTransformeralways uses chunked HLS.ChromecastSessionnow defaults toTsHlsMediaTransformerinstead ofDefaultMediaTransformer.
New #
TsHlsMediaTransformer— wraps local TS files in keyframe-aligned HLS playlists for Chromecast compatibilityMediaProxy.setPatPmt()/MediaProxy.setFirstPts()— enable correct PAT/PMT prepending and PTS offset for virtual HLS segmentsFfmpegMediaTransformerreference implementation in example app — remuxes TS→MP4 via ffmpeg with progress callbacks and mobile platform supportdoc/LOCAL_FILE_CASTING.md— comprehensive guide covering remux, HLS wrapping, and transcode approaches with tradeoffs
Migration guide #
Replace direct DefaultMediaTransformer usage for local TS files:
// Before (0.2.x) — DefaultMediaTransformer handled local TS→HLS internally
final session = await device.connect();
// After (0.3.0) — choose your transformer explicitly
// Option A: FFmpeg remux (recommended)
final session = await device.connect(
mediaTransformer: FfmpegMediaTransformer(),
);
// Option B: Built-in HLS wrapping (no external tools)
final session = await device.connect(
mediaTransformer: TsHlsMediaTransformer(),
);
0.2.1 #
Local file casting #
- Local file support with
CastMedia.file()constructor MediaTransformerinterface for extensible media format preparationTsKeyframeScannerfor keyframe-aligned HLS segment boundaries- Virtual segment URLs for Chromecast compatibility (replaces EXT-X-BYTERANGE)
useChunkedHlsflag for chunked vs single-segment HLS- Local subtitle support with automatic SRT-to-VTT conversion
Chromecast fixes #
- Fixed local file casting — HLS playlists and file routes were destroyed by cleanup before the device could fetch them
- CORS preflight (OPTIONS) handler for HLS segment requests
- RFC 8216-compliant TARGETDURATION calculation
- Consistent
application/x-mpegURLcontent type across all HLS responses - File extension on proxy URLs for HLS player format detection
- Volume updates via RECEIVER_STATUS instead of optimistic update
DLNA improvements #
- Duration metadata via DIDL-Lite
<res duration="HH:MM:SS">attribute - DLNA-specific HTTP headers (
transferMode.dlna.org,DLNA.ORG_OP=01flags) - Serve local TS files directly (not piped through HLS)
Other #
- Retry mDNS queries 3 times for slow-responding devices
- Comprehensive logging for all discovery providers and sessions
- Subtitle proxy for Chromecast (CORS + SRT conversion)
- Log viewer and custom media input in example app
0.2.0 #
- AirPlay feature flag detection via mDNS TXT records (
AirPlayFeaturesclass parsesfeatures/ftbitmask) AirPlayMediaControllerwith V1/V2/playformat auto-negotiation (V1 binary plist → V1 text/parameters → V2 with RTSP SETUP)UnsupportedFeatureExceptionthrown immediately when a device lacks video support bits (0 and 49)PlaybackExceptionthrown when all/playformat attempts are rejected by the device- Breaking:
HapSessionno longer hasplay,stop,scrub, orratemethods — useAirPlayMediaControllerinstead - Added
doc/PROTOCOL_REFERENCES.mdwith links to AirPlay, Chromecast, and DLNA specs - Added
doc/FUTURE_WORK.mddocumenting AirPlay screen mirroring and RAOP audio streaming roadmap
0.1.0 #
- Initial release
- Chromecast (CASTV2) protocol support with default media receiver
- AirPlay 1 video casting support
- DLNA/UPnP protocol support with AVTransport and RenderingControl
- Built-in HTTP proxy server for custom header injection
- HLS m3u8 playlist URL rewriting through proxy
- Local file serving for downloaded content
- Subtitle support across all protocols (WebVTT, SRT)
- Cross-platform: Android, iOS, macOS, Windows, Linux
- Pluggable device discovery (default: multicast_dns, injectable: bonsoir)
- 366+ tests with mock servers for each protocol
- Flutter example app with device picker and remote control