mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-12 23:33:43 +08:00
@@ -13,7 +13,7 @@ declare global {
|
||||
interface Window {
|
||||
chrome: any;
|
||||
cast: any;
|
||||
__onGCastApiAvailable: (isAvailable: boolean) => void;
|
||||
__onGCastApiAvailable?: (isAvailable: boolean) => void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,69 +24,37 @@ export function useCastControls({
|
||||
setIsCasting
|
||||
}: UseCastControlsProps) {
|
||||
const castContextRef = useRef<any>(null);
|
||||
const loadMediaRef = useRef<() => void>(() => {});
|
||||
|
||||
useEffect(() => {
|
||||
// Function to initialize Cast
|
||||
const initializeCastApi = () => {
|
||||
if (!window.cast || !window.cast.framework) return;
|
||||
const isCastSdkReady = useCallback(() => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
|
||||
const castContext = window.cast.framework.CastContext.getInstance();
|
||||
castContextRef.current = castContext;
|
||||
|
||||
castContext.setOptions({
|
||||
receiverApplicationId: window.chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,
|
||||
autoJoinPolicy: window.chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED
|
||||
});
|
||||
|
||||
// SDK loaded — show cast button immediately.
|
||||
// requestSession() will re-scan and show Chrome's native device picker.
|
||||
setIsCastAvailable(true);
|
||||
|
||||
// Monitor session state
|
||||
castContext.addEventListener(
|
||||
window.cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
|
||||
(event: any) => {
|
||||
const sessionState = event.sessionState;
|
||||
const isSessionActive = sessionState === window.cast.framework.SessionState.SESSION_STARTED ||
|
||||
sessionState === window.cast.framework.SessionState.SESSION_RESUMED;
|
||||
|
||||
setIsCasting(isSessionActive);
|
||||
|
||||
if (isSessionActive && videoRef.current) {
|
||||
videoRef.current.pause();
|
||||
loadMedia();
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// If API is already loaded
|
||||
if (window.cast && window.cast.framework) {
|
||||
initializeCastApi();
|
||||
} else {
|
||||
// Wait for API to be available
|
||||
window.__onGCastApiAvailable = (isAvailable: boolean) => {
|
||||
if (isAvailable) {
|
||||
initializeCastApi();
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [setIsCastAvailable, setIsCasting, videoRef]);
|
||||
return Boolean(
|
||||
window.cast?.framework?.CastContext?.getInstance &&
|
||||
window.cast?.framework?.CastContextEventType?.SESSION_STATE_CHANGED &&
|
||||
window.cast?.framework?.SessionState &&
|
||||
window.chrome?.cast?.media?.DEFAULT_MEDIA_RECEIVER_APP_ID &&
|
||||
window.chrome?.cast?.media?.MediaInfo &&
|
||||
window.chrome?.cast?.media?.LoadRequest &&
|
||||
window.chrome?.cast?.AutoJoinPolicy?.ORIGIN_SCOPED
|
||||
);
|
||||
}, []);
|
||||
|
||||
const loadMedia = useCallback(() => {
|
||||
if (!castContextRef.current || !src) return;
|
||||
const castMedia = window.chrome?.cast?.media;
|
||||
if (!castContextRef.current || !src || !castMedia?.MediaInfo || !castMedia?.LoadRequest) return;
|
||||
|
||||
const castContext = castContextRef.current;
|
||||
const session = castContext.getCurrentSession();
|
||||
if (!session) return;
|
||||
|
||||
const mediaInfo = new window.chrome.cast.media.MediaInfo(src, 'video/mp4');
|
||||
const mediaInfo = new castMedia.MediaInfo(src, 'video/mp4');
|
||||
// Handle HLS specifically if possible, though DEFAULT_MEDIA_RECEIVER supports it
|
||||
if (src.includes('.m3u8')) {
|
||||
mediaInfo.contentType = 'application/x-mpegurl';
|
||||
}
|
||||
|
||||
const request = new window.chrome.cast.media.LoadRequest(mediaInfo);
|
||||
const request = new castMedia.LoadRequest(mediaInfo);
|
||||
|
||||
// Sync current time
|
||||
if (videoRef.current) {
|
||||
@@ -99,11 +67,108 @@ export function useCastControls({
|
||||
);
|
||||
}, [src, videoRef]);
|
||||
|
||||
const showCastMenu = useCallback(() => {
|
||||
if (window.cast && window.cast.framework) {
|
||||
window.cast.framework.CastContext.getInstance().requestSession();
|
||||
useEffect(() => {
|
||||
loadMediaRef.current = loadMedia;
|
||||
}, [loadMedia]);
|
||||
|
||||
useEffect(() => {
|
||||
let sessionStateListener: ((event: any) => void) | null = null;
|
||||
let onGCastApiAvailable: ((isAvailable: boolean) => void) | null = null;
|
||||
|
||||
const markCastUnavailable = () => {
|
||||
castContextRef.current = null;
|
||||
setIsCastAvailable(false);
|
||||
setIsCasting(false);
|
||||
};
|
||||
|
||||
// Function to initialize Cast
|
||||
const initializeCastApi = () => {
|
||||
if (!isCastSdkReady()) {
|
||||
markCastUnavailable();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const castContext = window.cast.framework.CastContext.getInstance();
|
||||
castContextRef.current = castContext;
|
||||
|
||||
castContext.setOptions({
|
||||
receiverApplicationId: window.chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,
|
||||
autoJoinPolicy: window.chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED
|
||||
});
|
||||
|
||||
// SDK loaded — show cast button immediately.
|
||||
// requestSession() will re-scan and show Chrome's native device picker.
|
||||
setIsCastAvailable(true);
|
||||
|
||||
// Monitor session state
|
||||
sessionStateListener = (event: any) => {
|
||||
const sessionState = event.sessionState;
|
||||
const isSessionActive = sessionState === window.cast.framework.SessionState.SESSION_STARTED ||
|
||||
sessionState === window.cast.framework.SessionState.SESSION_RESUMED;
|
||||
|
||||
setIsCasting(isSessionActive);
|
||||
|
||||
if (isSessionActive && videoRef.current) {
|
||||
videoRef.current.pause();
|
||||
loadMediaRef.current();
|
||||
}
|
||||
};
|
||||
|
||||
castContext.addEventListener(
|
||||
window.cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
|
||||
sessionStateListener
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('Cast SDK is not usable in this browser context.', error);
|
||||
markCastUnavailable();
|
||||
}
|
||||
};
|
||||
|
||||
// If API is already loaded
|
||||
if (isCastSdkReady()) {
|
||||
initializeCastApi();
|
||||
} else {
|
||||
// Wait for API to be available
|
||||
onGCastApiAvailable = (isAvailable: boolean) => {
|
||||
if (isAvailable) {
|
||||
initializeCastApi();
|
||||
} else {
|
||||
markCastUnavailable();
|
||||
}
|
||||
};
|
||||
window.__onGCastApiAvailable = onGCastApiAvailable;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return () => {
|
||||
if (onGCastApiAvailable && window.__onGCastApiAvailable === onGCastApiAvailable) {
|
||||
delete window.__onGCastApiAvailable;
|
||||
}
|
||||
|
||||
const castContext = castContextRef.current;
|
||||
if (
|
||||
sessionStateListener &&
|
||||
castContext?.removeEventListener &&
|
||||
window.cast?.framework?.CastContextEventType?.SESSION_STATE_CHANGED
|
||||
) {
|
||||
castContext.removeEventListener(
|
||||
window.cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
|
||||
sessionStateListener
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [isCastSdkReady, setIsCastAvailable, setIsCasting, videoRef]);
|
||||
|
||||
const showCastMenu = useCallback(() => {
|
||||
if (!isCastSdkReady()) return;
|
||||
|
||||
try {
|
||||
window.cast.framework.CastContext.getInstance().requestSession();
|
||||
} catch (error) {
|
||||
console.warn('Cast session request failed.', error);
|
||||
setIsCastAvailable(false);
|
||||
}
|
||||
}, [isCastSdkReady, setIsCastAvailable]);
|
||||
|
||||
const castActions = useMemo(() => ({
|
||||
showCastMenu
|
||||
|
||||
Reference in New Issue
Block a user