mirror of
https://git.vectorsigma.ru/public/tubearchivist.git
synced 2026-08-04 19:09:29 +00:00
234 lines
8.0 KiB
TypeScript
234 lines
8.0 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react';
|
|
import { VideoType } from '../pages/Home';
|
|
import updateVideoProgressById from '../api/actions/updateVideoProgressById';
|
|
|
|
const getURL = () => {
|
|
return window.location.origin;
|
|
};
|
|
|
|
function shiftCurrentTime(contentCurrentTime: number | undefined) {
|
|
console.log(contentCurrentTime);
|
|
if (contentCurrentTime === undefined) {
|
|
return 0;
|
|
}
|
|
|
|
// Shift media back 3 seconds to prevent missing some of the content
|
|
if (contentCurrentTime > 5) {
|
|
return contentCurrentTime - 3;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
async function castVideoProgress(
|
|
player: {
|
|
mediaInfo: { contentId: string | string[] };
|
|
currentTime: number;
|
|
duration: number;
|
|
},
|
|
video: VideoType | undefined,
|
|
onWatchStateChanged?: (status: boolean) => void,
|
|
) {
|
|
if (!video) {
|
|
console.log('castVideoProgress: Video to cast not found...');
|
|
return;
|
|
}
|
|
const videoId = video.youtube_id;
|
|
|
|
if (player.mediaInfo.contentId.includes(videoId)) {
|
|
const currentTime = player.currentTime;
|
|
const duration = player.duration;
|
|
|
|
if (currentTime % 10 <= 1.0 && currentTime !== 0 && duration !== 0) {
|
|
// Check progress every 10 seconds or else progress is checked a few times a second
|
|
const videoProgressResponse = await updateVideoProgressById({
|
|
youtubeId: videoId,
|
|
currentProgress: currentTime,
|
|
});
|
|
|
|
const { data: videoProgressResponseData } = videoProgressResponse ?? {};
|
|
|
|
if (
|
|
videoProgressResponseData?.watched &&
|
|
video.player.watched !== videoProgressResponseData.watched
|
|
) {
|
|
onWatchStateChanged?.(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async function castVideoPaused(
|
|
player: {
|
|
currentTime: number;
|
|
duration: number;
|
|
mediaInfo: { contentId: string | string[] } | null;
|
|
},
|
|
video: VideoType | undefined,
|
|
) {
|
|
if (!video) {
|
|
console.log('castVideoPaused: Video to cast not found...');
|
|
return;
|
|
}
|
|
|
|
const videoId = video?.youtube_id;
|
|
|
|
const currentTime = player.currentTime;
|
|
const duration = player.duration;
|
|
|
|
if (player.mediaInfo != null) {
|
|
if (player.mediaInfo.contentId.includes(videoId)) {
|
|
if (currentTime !== 0 && duration !== 0) {
|
|
await updateVideoProgressById({
|
|
youtubeId: videoId,
|
|
currentProgress: currentTime,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type GoogleCastProps = {
|
|
video?: VideoType;
|
|
setRefresh?: () => void;
|
|
onWatchStateChanged?: (status: boolean) => void;
|
|
};
|
|
|
|
const GoogleCast = ({ video, setRefresh, onWatchStateChanged }: GoogleCastProps) => {
|
|
const [isConnected, setIsConnected] = useState(false);
|
|
|
|
const setup = useCallback(() => {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const cast = (globalThis as any).cast;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const chrome = (globalThis as any).chrome;
|
|
|
|
cast.framework.CastContext.getInstance().setOptions({
|
|
receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, // Use built in receiver app on cast device, see https://developers.google.com/cast/docs/styled_receiver if you want to be able to add a theme, splash screen or watermark. Has a $5 one time fee.
|
|
autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
|
|
});
|
|
|
|
const player = new cast.framework.RemotePlayer();
|
|
|
|
const playerController = new cast.framework.RemotePlayerController(player);
|
|
|
|
// Add event listerner to check if a connection to a cast device is initiated
|
|
playerController.addEventListener(
|
|
cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED,
|
|
function () {
|
|
setIsConnected(player.isConnected);
|
|
},
|
|
);
|
|
|
|
playerController.addEventListener(
|
|
cast.framework.RemotePlayerEventType.CURRENT_TIME_CHANGED,
|
|
function () {
|
|
castVideoProgress(player, video, onWatchStateChanged);
|
|
},
|
|
);
|
|
|
|
playerController.addEventListener(
|
|
cast.framework.RemotePlayerEventType.IS_PAUSED_CHANGED,
|
|
function () {
|
|
castVideoPaused(player, video);
|
|
setRefresh?.();
|
|
},
|
|
);
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [setRefresh, video]);
|
|
|
|
const startPlayback = useCallback(() => {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const chrome = (globalThis as any).chrome;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const cast = (globalThis as any).cast;
|
|
const castSession = cast.framework.CastContext.getInstance().getCurrentSession();
|
|
|
|
const mediaUrl = video?.media_url;
|
|
const vidThumbUrl = video?.vid_thumb_url;
|
|
const contentTitle = video?.title;
|
|
const contentId = `${getURL()}${mediaUrl}`;
|
|
const contentImage = `${getURL()}${vidThumbUrl}`;
|
|
const contentType = 'video/mp4'; // Set content type, only videos right now so it is hard coded
|
|
|
|
const contentSubtitles = [];
|
|
const videoSubtitles = video?.subtitles; // Array of subtitles
|
|
if (typeof videoSubtitles !== 'undefined') {
|
|
for (let i = 0; i < videoSubtitles.length; i++) {
|
|
const subtitle = new chrome.cast.media.Track(i, chrome.cast.media.TrackType.TEXT);
|
|
|
|
subtitle.trackContentId = videoSubtitles[i].media_url;
|
|
subtitle.trackContentType = 'text/vtt';
|
|
subtitle.subtype = chrome.cast.media.TextTrackType.SUBTITLES;
|
|
subtitle.name = videoSubtitles[i].name;
|
|
subtitle.language = videoSubtitles[i].lang;
|
|
subtitle.customData = null;
|
|
|
|
contentSubtitles.push(subtitle);
|
|
}
|
|
}
|
|
|
|
const mediaInfo = new chrome.cast.media.MediaInfo(contentId, contentType); // Create MediaInfo var that contains url and content type
|
|
// mediaInfo.streamType = chrome.cast.media.StreamType.BUFFERED; // Set type of stream, BUFFERED, LIVE, OTHER
|
|
mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata(); // Create metadata var and add it to MediaInfo
|
|
mediaInfo.metadata.title = contentTitle?.replace('&', '&'); // Set the video title
|
|
mediaInfo.metadata.images = [new chrome.cast.Image(contentImage)]; // Set the video thumbnail
|
|
// mediaInfo.textTrackStyle = new chrome.cast.media.TextTrackStyle();
|
|
mediaInfo.tracks = contentSubtitles;
|
|
|
|
const request = new chrome.cast.media.LoadRequest(mediaInfo); // Create request with the previously set MediaInfo.
|
|
// request.queueData = new chrome.cast.media.QueueData(); // See https://developers.google.com/cast/docs/reference/web_sender/chrome.cast.media.QueueData for playlist support.
|
|
request.currentTime = shiftCurrentTime(video?.player?.position); // Set video start position based on the browser video position
|
|
// request.activeTrackIds = contentActiveSubtitle; // Set active subtitle based on video player
|
|
|
|
castSession.loadMedia(request).then(
|
|
function () {
|
|
console.log('media loaded');
|
|
},
|
|
function (error: { code: string }) {
|
|
console.log('Error', error, 'Error code: ' + error.code);
|
|
},
|
|
); // Send request to cast device
|
|
|
|
// Do not add videoProgress?.position, this will cause loops!
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [video?.media_url, video?.subtitles, video?.title, video?.vid_thumb_url]);
|
|
|
|
useEffect(() => {
|
|
// @ts-expect-error __onGCastApiAvailable is the google cast window hook ( source: https://developers.google.com/cast/docs/web_sender/integrate )
|
|
window['__onGCastApiAvailable'] = function (isAvailable: boolean) {
|
|
if (isAvailable) {
|
|
setup();
|
|
}
|
|
};
|
|
}, [setup]);
|
|
|
|
useEffect(() => {
|
|
console.log('isConnected', isConnected);
|
|
if (isConnected) {
|
|
startPlayback();
|
|
}
|
|
}, [isConnected, startPlayback]);
|
|
|
|
if (!video) {
|
|
return <p>Video for cast not found...</p>;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<>
|
|
<script
|
|
type="text/javascript"
|
|
src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"
|
|
></script>
|
|
|
|
{/* @ts-expect-error React does not know what to do with the google-cast-launcher, but it works. */}
|
|
<google-cast-launcher id="castbutton"></google-cast-launcher>
|
|
</>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default GoogleCast;
|