DRAFT: Add Tubearchivist Frontend React dev docker setup (#768)

* Add development docker-compose file

* Add /new/ path in nginx conf

* Add frontend production setup

* Fix lint

* Refac move prod docker compose into non suffixed file

* Fix run.sh fileendings on windows

* Fix docker file naming consistancies

* Add frontend dev setup

* Add docker compose dev command

* Refac remove docker network

* Fix potential error causes

* Chore update react-router-dom

* Add redirect to login after logout

* Refac allow basic auth for session login in api

* Fix loginresponsetype optional property

* Refac move isAdmin check into page Base

* Refac use node lts for dev container

* Refac remove old setup in readme

* Refac move getisAdmin into loader and rename

* Refac remove manual csrf cookie handing from actions and loader

* Fix post requiring csrf header & cookie

* Fix remove empty files

* Refac revert dockerfile changes

* Refac revert gitatrributes changes

* Refac revert docker changes

* Refac revert nginx changes

* Refac revert docker change

* Refac move frontend into frontend folder

* Add production steps to dockerfile

* Refac implement endpoint renaming

* Refac remove frontend dockerfile

* Add credentials include for dev env

* Fix allow cors with credentials for dev environment

* Fix images in dev mode

* Add credentials for dev mode to all loader and actions, except signin

* Revert cors config

* Revert cors config

* Fix nginx not serving /youtube/

* Fix video url missing api

* Fix media url missing api

* Add application settings page

* Add continue vids

* Add csrf to delete requests

* Refac use api/video endpoint with filter to home, channel, playlist pages

* Add channel nav request

* Add channel playlists

* Fix filterbar for playlist in channel

* Add playlist_nav to video page

* Add downloads aggs

* Refac remove basic auth

* Fix credentials include in signin

* Refac user config to user me config

* Add ApiToken get
This commit is contained in:
Merlin
2024-08-10 19:53:50 +02:00
committed by GitHub
parent 4dd7ac496a
commit 83bb7f678b
221 changed files with 16165 additions and 2 deletions

View File

@@ -0,0 +1,230 @@
import { useCallback, useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
import { VideoType } from '../pages/Home';
import updateWatchedState from '../api/actions/updateWatchedState';
import updateVideoProgressById from '../api/actions/updateVideoProgressById';
import watchedThreshold from '../functions/watchedThreshold';
import { VideoProgressType } from './VideoPlayer';
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,
) {
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
await updateVideoProgressById({
youtubeId: videoId,
currentProgress: currentTime,
});
if (!video.player.watched) {
// Check if video is already marked as watched
if (watchedThreshold(currentTime, duration)) {
await updateWatchedState({
id: videoId,
is_watched: 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;
videoProgress?: VideoProgressType;
setRefresh?: () => void;
};
const GoogleCast = ({ video, videoProgress, setRefresh }: GoogleCastProps) => {
const [isConnected, setIsConnected] = useState(false);
const setup = useCallback(() => {
const cast = globalThis.cast;
const chrome = globalThis.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);
},
);
playerController.addEventListener(
cast.framework.RemotePlayerEventType.IS_PAUSED_CHANGED,
function () {
castVideoPaused(player, video);
setRefresh?.();
},
);
}, [setRefresh, video]);
const startPlayback = useCallback(() => {
const chrome = globalThis.chrome;
const cast = globalThis.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('&amp;', '&'); // 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(videoProgress?.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 (
<>
<>
<Helmet>
<script
type="text/javascript"
src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"
></script>
</Helmet>
{/* @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;