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 { VideoPlayerButton } from "@/components/player/internals/Button";
import { usePlayerStore } from "@/stores/player/store";
import { isSafari } from "@/utils/detectFeatures";
export function Airplay() {
const canAirplay = usePlayerStore((s) => s.interface.canAirplay);
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 (
<VideoPlayerButton

View file

@ -38,6 +38,8 @@ export function Chromecast({ className }: ChromecastProps) {
const context = castFramework.CastContext.getInstance();
const updateVisibility = () => {
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);
};

View file

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