update airplay and casting

This commit is contained in:
Pas 2026-01-15 11:16:01 -07:00
parent c0029577e2
commit 6f67a7c7fb
3 changed files with 50 additions and 14 deletions

View file

@ -1,12 +1,15 @@
import { Icons } from "@/components/Icon"; import { Icons } from "@/components/Icon";
import { VideoPlayerButton } from "@/components/player/internals/Button"; import { VideoPlayerButton } from "@/components/player/internals/Button";
import { usePlayerStore } from "@/stores/player/store"; import { usePlayerStore } from "@/stores/player/store";
import { isSafari } from "@/utils/detectFeatures";
export function Airplay() { export function Airplay() {
const canAirplay = usePlayerStore((s) => s.interface.canAirplay); const canAirplay = usePlayerStore((s) => s.interface.canAirplay);
const display = usePlayerStore((s) => s.display); const display = usePlayerStore((s) => s.display);
if (!canAirplay) return null; // Show Airplay button on Safari browsers (which support AirPlay natively)
// or when the webkit event has confirmed availability
if (!canAirplay && !isSafari) return null;
return ( return (
<VideoPlayerButton <VideoPlayerButton

View file

@ -38,6 +38,8 @@ export function Chromecast({ className }: ChromecastProps) {
const context = castFramework.CastContext.getInstance(); const context = castFramework.CastContext.getInstance();
const updateVisibility = () => { const updateVisibility = () => {
const state = context.getCastState(); const state = context.getCastState();
// Hide only if we know for sure there are no devices available
// Show the button for other states (NOT_CONNECTED, CONNECTING, CONNECTED)
setCastHidden(state === castFramework.CastState.NO_DEVICES_AVAILABLE); setCastHidden(state === castFramework.CastState.NO_DEVICES_AVAILABLE);
}; };

View file

@ -784,19 +784,41 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
proxiedUrl = source.url; // Already proxied or no headers needed proxiedUrl = source.url; // Already proxied or no headers needed
} }
} else if (source?.type === "mp4") { } else if (source?.type === "mp4") {
// TODO: Implement MP4 proxy for protected streams const allHeaders = {
const hasHeaders = ...source.preferredHeaders,
source.headers && Object.keys(source.headers).length > 0; ...source.headers,
if (hasHeaders) { };
const hasHeaders = Object.keys(allHeaders).length > 0;
if (!isUrlAlreadyProxied(source.url) && hasHeaders) {
// Use MP4 proxy for streams with headers // Use MP4 proxy for streams with headers
proxiedUrl = createMP4ProxyUrl(source.url, source.headers || {}); proxiedUrl = createMP4ProxyUrl(source.url, allHeaders);
} else { } else {
proxiedUrl = source.url; proxiedUrl = source.url;
} }
} }
// Function to restore original URL
const restoreOriginalUrl = () => {
if (source?.type === "hls") {
if (hls && originalUrl) {
hls.loadSource(originalUrl);
}
} else if (originalUrl) {
videoPlayer.src = originalUrl;
}
};
// Function to check airplay state and restore if needed
const checkAirplayState = () => {
const isWireless = videoPlayer.webkitCurrentPlaybackTargetIsWireless;
if (!isWireless) {
// Airplay didn't start or ended, restore original URL
restoreOriginalUrl();
}
};
if (proxiedUrl && proxiedUrl !== originalUrl) { if (proxiedUrl && proxiedUrl !== originalUrl) {
// Temporarily set the proxied URL for Airplay // Set the proxied URL for Airplay
if (source?.type === "hls") { if (source?.type === "hls") {
if (hls) { if (hls) {
hls.loadSource(proxiedUrl); hls.loadSource(proxiedUrl);
@ -809,16 +831,25 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
setTimeout(() => { setTimeout(() => {
videoPlayer.webkitShowPlaybackTargetPicker(); videoPlayer.webkitShowPlaybackTargetPicker();
// Restore original URL after a short delay // Check airplay state after user interaction
// Give user time to select device, then check if airplay started
setTimeout(() => { setTimeout(() => {
if (source?.type === "hls") { checkAirplayState();
if (hls && originalUrl) { }, 2000);
hls.loadSource(originalUrl);
} // Set up periodic check for airplay state changes
} else if (originalUrl) { const airplayCheckInterval = setInterval(() => {
videoPlayer.src = originalUrl; const isWireless =
videoPlayer.webkitCurrentPlaybackTargetIsWireless;
if (!isWireless) {
// Airplay ended, restore original URL
restoreOriginalUrl();
clearInterval(airplayCheckInterval);
} }
}, 1000); }, 1000);
// Clear interval after 5 minutes as safety measure
setTimeout(() => clearInterval(airplayCheckInterval), 300000);
}, 100); }, 100);
} else { } else {
// No proxying needed, just trigger Airplay // No proxying needed, just trigger Airplay