pstream is dead; long live pstream taciturnaxolotl.github.io/pstream-ng/
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

attempt to fix casting methods

Pas cca925bf dc14662c

+96 -16
+8 -1
src/components/player/display/base.ts
··· 388 388 containerElement = container; 389 389 }, 390 390 setMeta() {}, 391 - setCaption() {}, 391 + setCaption(caption) { 392 + // If we have a video element and captions are available, 393 + // set up the tracks - AirPlay requires VTT format 394 + if (videoElement && caption && caption.srtData) { 395 + // Subtitles are handled via the track element in VideoContainer.tsx 396 + // convertSubtitlesToObjectUrl already handles the conversion to VTT 397 + } 398 + }, 392 399 393 400 pause() { 394 401 videoElement?.pause();
+88 -15
src/components/player/display/chromecast.ts
··· 7 7 DisplayInterfaceEvents, 8 8 DisplayMeta, 9 9 } from "@/components/player/display/displayInterface"; 10 + import { 11 + convertSubtitlesToObjectUrl, 12 + convertSubtitlesToVtt, 13 + } from "@/components/player/utils/captions"; 10 14 import { LoadableSource } from "@/stores/player/utils/qualities"; 11 15 import { processCdnLink } from "@/utils/cdn"; 12 16 import { ··· 22 26 instance: cast.framework.CastContext; 23 27 } 24 28 25 - /* 26 - ** Chromecasting is unfinished, here is its limitations: 27 - ** 1. Captions - chromecast requires only VTT, but needs it from a URL. we only have SRT urls 28 - ** 2. HLS - we've having some issues with content types. sometimes it loads, sometimes it doesn't 29 - */ 30 - 31 29 export function makeChromecastDisplayInterface( 32 30 ops: ChromeCastDisplayInterfaceOptions, 33 31 ): DisplayInterface { ··· 45 43 title: "", 46 44 type: MWMediaType.MOVIE, 47 45 }; 48 - // eslint-disable-next-line @typescript-eslint/no-unused-vars 49 46 let caption: DisplayCaption | null = null; 47 + let captionUrl: string | null = null; 50 48 51 49 function listenForEvents() { 52 50 const listen = async (e: cast.framework.RemotePlayerChangedEvent) => { ··· 100 98 }; 101 99 } 102 100 101 + function setupCaptions(): chrome.cast.media.Track[] | null { 102 + if (!caption || !caption.srtData) return null; 103 + 104 + try { 105 + // Convert SRT to VTT and create an object URL 106 + captionUrl = convertSubtitlesToObjectUrl(caption.srtData); 107 + 108 + // Create a text track for Chromecast 109 + const track = new chrome.cast.media.Track( 110 + 1, // trackId 111 + chrome.cast.media.TrackType.TEXT, 112 + ); 113 + 114 + track.trackContentId = captionUrl; 115 + track.trackContentType = "text/vtt"; 116 + track.subtype = chrome.cast.media.TextTrackType.SUBTITLES; 117 + track.name = caption.language; 118 + track.language = caption.language; 119 + 120 + return [track]; 121 + } catch (error) { 122 + console.error("Error setting up captions for Chromecast:", error); 123 + return null; 124 + } 125 + } 126 + 103 127 function setupSource() { 104 128 if (!source) { 105 129 ops.controller?.stop(); 106 130 return; 107 131 } 108 132 109 - let type = "video/mp4"; 110 - if (source.type === "hls") type = "application/x-mpegurl"; 133 + // Determine correct content type 134 + let contentType = "video/mp4"; 135 + if (source.type === "hls") { 136 + // Use MIME type that's best supported by Chromecast for HLS 137 + contentType = "application/vnd.apple.mpegurl"; 138 + } 111 139 112 140 const metaData = new chrome.cast.media.GenericMediaMetadata(); 113 141 metaData.title = meta.title; 114 142 115 - const mediaInfo = new chrome.cast.media.MediaInfo("video", type); 116 - (mediaInfo as any).contentUrl = processCdnLink(source.url); 143 + // Create media info with proper content ID and content type 144 + const mediaInfo = new chrome.cast.media.MediaInfo( 145 + processCdnLink(source.url), // Use processed URL as the content ID 146 + contentType, 147 + ); 148 + 149 + // The contentUrl property doesn't exist on the type, use properly typed properties instead 117 150 mediaInfo.streamType = chrome.cast.media.StreamType.BUFFERED; 118 151 mediaInfo.metadata = metaData; 119 152 mediaInfo.customData = { 120 153 playbackRate, 121 154 }; 122 155 156 + // Set up captions if available 157 + const tracks = setupCaptions(); 158 + if (tracks && tracks.length > 0) { 159 + mediaInfo.tracks = tracks; 160 + } 161 + 123 162 const request = new chrome.cast.media.LoadRequest(mediaInfo); 124 163 request.autoplay = true; 125 164 request.currentTime = startAt; 126 165 127 166 if (source.type === "hls") { 128 167 const staticMedia = chrome.cast.media as any; 129 - const media = request.media as any; 130 - media.hlsSegmentFormat = staticMedia.HlsSegmentFormat.FMP4; 131 - media.hlsVideoSegmentFormat = staticMedia.HlsVideoSegmentFormat.FMP4; 168 + // Set HLS-specific properties to improve reliability 169 + if (staticMedia.HlsSegmentFormat) { 170 + const media = request.media as any; 171 + media.hlsSegmentFormat = staticMedia.HlsSegmentFormat.FMP4; 172 + media.hlsVideoSegmentFormat = staticMedia.HlsVideoSegmentFormat.FMP4; 173 + // Set additional properties to improve HLS compatibility 174 + media.streamType = chrome.cast.media.StreamType.BUFFERED; 175 + media.hlsPreload = true; 176 + } 132 177 } 133 178 179 + // Load the media on the Chromecast session 134 180 const session = ops.instance.getCurrentSession(); 135 - session?.loadMedia(request); 181 + if (session) { 182 + session.loadMedia(request).catch((error) => { 183 + console.error("Error loading media on Chromecast:", error); 184 + emit("error", { 185 + message: `Chromecast error: ${error.message || "Failed to load media"}`, 186 + errorName: "ChromecastLoadError", 187 + type: "global", 188 + }); 189 + }); 190 + } 136 191 } 137 192 138 193 function setSource() { ··· 166 221 stopListening(); 167 222 destroyVideoElement(); 168 223 fscreen.removeEventListener("fullscreenchange", fullscreenChange); 224 + // Clean up caption URL object if it exists 225 + if (captionUrl) { 226 + try { 227 + URL.revokeObjectURL(captionUrl); 228 + } catch (e) { 229 + // Ignore errors during cleanup 230 + } 231 + } 169 232 }, 170 233 load(loadOps) { 171 234 source = loadOps.source; ··· 177 240 // cant control qualities 178 241 }, 179 242 setCaption(newCaption) { 243 + // Clean up previous caption URL if it exists 244 + if (captionUrl) { 245 + try { 246 + URL.revokeObjectURL(captionUrl); 247 + captionUrl = null; 248 + } catch (e) { 249 + // Ignore errors during cleanup 250 + } 251 + } 252 + 180 253 caption = newCaption; 181 254 setSource(); 182 255 },