···388388 containerElement = container;
389389 },
390390 setMeta() {},
391391- setCaption() {},
391391+ setCaption(caption) {
392392+ // If we have a video element and captions are available,
393393+ // set up the tracks - AirPlay requires VTT format
394394+ if (videoElement && caption && caption.srtData) {
395395+ // Subtitles are handled via the track element in VideoContainer.tsx
396396+ // convertSubtitlesToObjectUrl already handles the conversion to VTT
397397+ }
398398+ },
392399393400 pause() {
394401 videoElement?.pause();
+88-15
src/components/player/display/chromecast.ts
···77 DisplayInterfaceEvents,
88 DisplayMeta,
99} from "@/components/player/display/displayInterface";
1010+import {
1111+ convertSubtitlesToObjectUrl,
1212+ convertSubtitlesToVtt,
1313+} from "@/components/player/utils/captions";
1014import { LoadableSource } from "@/stores/player/utils/qualities";
1115import { processCdnLink } from "@/utils/cdn";
1216import {
···2226 instance: cast.framework.CastContext;
2327}
24282525-/*
2626- ** Chromecasting is unfinished, here is its limitations:
2727- ** 1. Captions - chromecast requires only VTT, but needs it from a URL. we only have SRT urls
2828- ** 2. HLS - we've having some issues with content types. sometimes it loads, sometimes it doesn't
2929- */
3030-3129export function makeChromecastDisplayInterface(
3230 ops: ChromeCastDisplayInterfaceOptions,
3331): DisplayInterface {
···4543 title: "",
4644 type: MWMediaType.MOVIE,
4745 };
4848- // eslint-disable-next-line @typescript-eslint/no-unused-vars
4946 let caption: DisplayCaption | null = null;
4747+ let captionUrl: string | null = null;
50485149 function listenForEvents() {
5250 const listen = async (e: cast.framework.RemotePlayerChangedEvent) => {
···10098 };
10199 }
102100101101+ function setupCaptions(): chrome.cast.media.Track[] | null {
102102+ if (!caption || !caption.srtData) return null;
103103+104104+ try {
105105+ // Convert SRT to VTT and create an object URL
106106+ captionUrl = convertSubtitlesToObjectUrl(caption.srtData);
107107+108108+ // Create a text track for Chromecast
109109+ const track = new chrome.cast.media.Track(
110110+ 1, // trackId
111111+ chrome.cast.media.TrackType.TEXT,
112112+ );
113113+114114+ track.trackContentId = captionUrl;
115115+ track.trackContentType = "text/vtt";
116116+ track.subtype = chrome.cast.media.TextTrackType.SUBTITLES;
117117+ track.name = caption.language;
118118+ track.language = caption.language;
119119+120120+ return [track];
121121+ } catch (error) {
122122+ console.error("Error setting up captions for Chromecast:", error);
123123+ return null;
124124+ }
125125+ }
126126+103127 function setupSource() {
104128 if (!source) {
105129 ops.controller?.stop();
106130 return;
107131 }
108132109109- let type = "video/mp4";
110110- if (source.type === "hls") type = "application/x-mpegurl";
133133+ // Determine correct content type
134134+ let contentType = "video/mp4";
135135+ if (source.type === "hls") {
136136+ // Use MIME type that's best supported by Chromecast for HLS
137137+ contentType = "application/vnd.apple.mpegurl";
138138+ }
111139112140 const metaData = new chrome.cast.media.GenericMediaMetadata();
113141 metaData.title = meta.title;
114142115115- const mediaInfo = new chrome.cast.media.MediaInfo("video", type);
116116- (mediaInfo as any).contentUrl = processCdnLink(source.url);
143143+ // Create media info with proper content ID and content type
144144+ const mediaInfo = new chrome.cast.media.MediaInfo(
145145+ processCdnLink(source.url), // Use processed URL as the content ID
146146+ contentType,
147147+ );
148148+149149+ // The contentUrl property doesn't exist on the type, use properly typed properties instead
117150 mediaInfo.streamType = chrome.cast.media.StreamType.BUFFERED;
118151 mediaInfo.metadata = metaData;
119152 mediaInfo.customData = {
120153 playbackRate,
121154 };
122155156156+ // Set up captions if available
157157+ const tracks = setupCaptions();
158158+ if (tracks && tracks.length > 0) {
159159+ mediaInfo.tracks = tracks;
160160+ }
161161+123162 const request = new chrome.cast.media.LoadRequest(mediaInfo);
124163 request.autoplay = true;
125164 request.currentTime = startAt;
126165127166 if (source.type === "hls") {
128167 const staticMedia = chrome.cast.media as any;
129129- const media = request.media as any;
130130- media.hlsSegmentFormat = staticMedia.HlsSegmentFormat.FMP4;
131131- media.hlsVideoSegmentFormat = staticMedia.HlsVideoSegmentFormat.FMP4;
168168+ // Set HLS-specific properties to improve reliability
169169+ if (staticMedia.HlsSegmentFormat) {
170170+ const media = request.media as any;
171171+ media.hlsSegmentFormat = staticMedia.HlsSegmentFormat.FMP4;
172172+ media.hlsVideoSegmentFormat = staticMedia.HlsVideoSegmentFormat.FMP4;
173173+ // Set additional properties to improve HLS compatibility
174174+ media.streamType = chrome.cast.media.StreamType.BUFFERED;
175175+ media.hlsPreload = true;
176176+ }
132177 }
133178179179+ // Load the media on the Chromecast session
134180 const session = ops.instance.getCurrentSession();
135135- session?.loadMedia(request);
181181+ if (session) {
182182+ session.loadMedia(request).catch((error) => {
183183+ console.error("Error loading media on Chromecast:", error);
184184+ emit("error", {
185185+ message: `Chromecast error: ${error.message || "Failed to load media"}`,
186186+ errorName: "ChromecastLoadError",
187187+ type: "global",
188188+ });
189189+ });
190190+ }
136191 }
137192138193 function setSource() {
···166221 stopListening();
167222 destroyVideoElement();
168223 fscreen.removeEventListener("fullscreenchange", fullscreenChange);
224224+ // Clean up caption URL object if it exists
225225+ if (captionUrl) {
226226+ try {
227227+ URL.revokeObjectURL(captionUrl);
228228+ } catch (e) {
229229+ // Ignore errors during cleanup
230230+ }
231231+ }
169232 },
170233 load(loadOps) {
171234 source = loadOps.source;
···177240 // cant control qualities
178241 },
179242 setCaption(newCaption) {
243243+ // Clean up previous caption URL if it exists
244244+ if (captionUrl) {
245245+ try {
246246+ URL.revokeObjectURL(captionUrl);
247247+ captionUrl = null;
248248+ } catch (e) {
249249+ // Ignore errors during cleanup
250250+ }
251251+ }
252252+180253 caption = newCaption;
181254 setSource();
182255 },