import { Link, useNavigate, useParams } from 'react-router-dom'; import loadVideoById from '../api/loader/loadVideoById'; import { Fragment, useEffect, useState } from 'react'; import { ConfigType, VideoType } from './Home'; import VideoPlayer from '../components/VideoPlayer'; import iconEye from '/img/icon-eye.svg'; import iconThumb from '/img/icon-thumb.svg'; import iconStarFull from '/img/icon-star-full.svg'; import iconStarEmpty from '/img/icon-star-empty.svg'; import iconStarHalf from '/img/icon-star-half.svg'; import iconClose from '/img/icon-close.svg'; import iconUnseen from '/img/icon-unseen.svg'; import iconSeen from '/img/icon-seen.svg'; import Routes from '../configuration/routes/RouteList'; import Linkify from '../components/Linkify'; import loadSimmilarVideosById from '../api/loader/loadSimmilarVideosById'; import VideoList from '../components/VideoList'; import updateWatchedState from '../api/actions/updateWatchedState'; import humanFileSize from '../functions/humanFileSize'; import ScrollToTopOnNavigate from '../components/ScrollToTop'; import ChannelOverview from '../components/ChannelOverview'; import deleteVideo from '../api/actions/deleteVideo'; import capitalizeFirstLetter from '../functions/capitalizeFirstLetter'; import formatDate from '../functions/formatDates'; import formatNumbers from '../functions/formatNumbers'; import queueReindex from '../api/actions/queueReindex'; import GoogleCast from '../components/GoogleCast'; import WatchedCheckBox from '../components/WatchedCheckBox'; import convertStarRating from '../functions/convertStarRating'; import loadPlaylistList from '../api/loader/loadPlaylistList'; import { PlaylistsResponseType } from './Playlists'; import PaginationDummy from '../components/PaginationDummy'; import updateCustomPlaylist from '../api/actions/updateCustomPlaylist'; import { PlaylistType } from './Playlist'; import loadCommentsbyVideoId from '../api/loader/loadCommentsbyVideoId'; import CommentBox, { CommentsType } from '../components/CommentBox'; import Button from '../components/Button'; import getApiUrl from '../configuration/getApiUrl'; import loadVideoNav, { VideoNavResponseType } from '../api/loader/loadVideoNav'; import loadIsAdmin from '../functions/getIsAdmin'; const isInPlaylist = (videoId: string, playlist: PlaylistType) => { return playlist.playlist_entries.some(entry => { return entry.youtube_id === videoId; }); }; type VideoParams = { videoId: string; }; type PlaylistNavPreviousItemType = { youtube_id: string; vid_thumb: string; idx: number; title: string; }; type PlaylistNavNextItemType = { youtube_id: string; vid_thumb: string; idx: number; title: string; }; type PlaylistNavItemType = { playlist_meta: { current_idx: string; playlist_id: string; playlist_name: string; playlist_channel: string; }; playlist_previous: PlaylistNavPreviousItemType; playlist_next: PlaylistNavNextItemType; }; type PlaylistNavType = PlaylistNavItemType[]; export type SponsorBlockSegmentType = { category: string; actionType: string; segment: number[]; UUID: string; videoDuration: number; locked: number; votes: number; }; export type SponsorBlockType = { last_refresh: number; has_unlocked: boolean; is_enabled: boolean; segments: SponsorBlockSegmentType[]; message?: string; }; export type SimilarVideosResponseType = { data: VideoType[]; config: ConfigType; }; export type VideoResponseType = { data: VideoType; config: ConfigType; }; type CommentsResponseType = { data: CommentsType[]; config: ConfigType; }; export type VideoCommentsResponseType = { data: VideoType; config: ConfigType; playlist_nav: PlaylistNavType; }; const Video = () => { const { videoId } = useParams() as VideoParams; const navigate = useNavigate(); const isAdmin = loadIsAdmin(); const [loading, setLoading] = useState(false); const [videoEnded, setVideoEnded] = useState(false); const [playlistAutoplay, setPlaylistAutoplay] = useState( localStorage.getItem('playlistAutoplay') === 'true', ); const [playlistIdForAutoplay, setPlaylistIDForAutoplay] = useState( localStorage.getItem('playlistIdForAutoplay') ?? '', ); const [descriptionExpanded, setDescriptionExpanded] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showAddToPlaylist, setShowAddToPlaylist] = useState(false); const [refreshVideoList, setRefreshVideoList] = useState(false); const [reindex, setReindex] = useState(false); const [videoResponse, setVideoResponse] = useState(); const [simmilarVideos, setSimmilarVideos] = useState(); const [videoPlaylistNav, setVideoPlaylistNav] = useState(); const [customPlaylistsResponse, setCustomPlaylistsResponse] = useState(); const [commentsResponse, setCommentsResponse] = useState(); useEffect(() => { (async () => { setLoading(true); const videoResponse = await loadVideoById(videoId); const simmilarVideosResponse = await loadSimmilarVideosById(videoId); const customPlaylistsResponse = await loadPlaylistList({ type: 'custom' }); const commentsResponse = await loadCommentsbyVideoId(videoId); const videoNavResponse = await loadVideoNav(videoId); setVideoResponse(videoResponse); setSimmilarVideos(simmilarVideosResponse); setVideoPlaylistNav(videoNavResponse); setCustomPlaylistsResponse(customPlaylistsResponse); setCommentsResponse(commentsResponse); setRefreshVideoList(false); setLoading(false); })(); }, [videoId, refreshVideoList]); useEffect(() => { localStorage.setItem('playlistAutoplay', playlistAutoplay.toString()); if (!playlistAutoplay) { localStorage.setItem('playlistIdForAutoplay', ''); return; } localStorage.setItem('playlistIdForAutoplay', playlistIdForAutoplay || ''); }, [playlistAutoplay, playlistIdForAutoplay]); useEffect(() => { if (videoEnded && playlistAutoplay) { const playlist = videoPlaylistNav?.find(playlist => { return playlist.playlist_meta.playlist_id === playlistIdForAutoplay; }); if (playlist) { const nextYoutubeId = playlist.playlist_next?.youtube_id; if (nextYoutubeId) { setVideoEnded(false); navigate(Routes.Video(nextYoutubeId)); } } } }, [videoEnded, playlistAutoplay]); if (videoResponse === undefined) { return []; } const video = videoResponse.data; const watched = videoResponse.data.player.watched; const config = videoResponse.config; const playlistNav = videoPlaylistNav; const sponsorBlock = videoResponse.data.sponsorblock; const customPlaylists = customPlaylistsResponse?.data; const starRating = convertStarRating(video?.stats?.average_rating); const comments = commentsResponse?.data; console.log('playlistNav', playlistNav); const cast = config.enable_cast; return ( <> {`TA | ${video.title}`} {!loading && ( { setVideoEnded(true); }} /> )}
{cast && ( { setRefreshVideoList(true); }} /> )}

{video.title}

Published: {formatDate(video.published)}

Last refreshed: {formatDate(video.vid_last_refresh)}

Watched: { await updateWatchedState({ id: videoId, is_watched: status, }); }} onDone={() => { setRefreshVideoList(true); }} />

{video.active && (

Youtube:{' '} Active

)} {!video.active &&

Youtube: Deactivated

}

views: {formatNumbers(video.stats.view_count)}

thumbs-up: {formatNumbers(video.stats.like_count)}

{video.stats.dislike_count > 0 && (

thumbs-down:{' '} {video.stats.dislike_count}

)} {video?.stats && starRating && (
{starRating?.map?.((star, index) => { if (star === 'full') { return {star}; } if (star === 'half') { return {star}; } return {star}; })}
)}
{reindex &&

Reindex scheduled

} {!reindex && ( <> {isAdmin && (
)} )}
)} )}{' '} {!showAddToPlaylist && (
{video.media_size &&

File size: {humanFileSize(video.media_size)}

} {video.streams && video.streams.map(stream => { return (

{capitalizeFirstLetter(stream.type)}: {stream.codec}{' '} {humanFileSize(stream.bitrate)}/s {stream.width && ( <> | {stream.width}x{stream.height} )}{' '}

); })}
{video.tags && video.tags.length > 0 && (
{video.tags.map(tag => { return ( {tag} ); })}
)} {video.description && (

{video.description}

)} {playlistNav && ( <> {playlistNav.map(playlistItem => { return (

Playlist [{playlistItem.playlist_meta.current_idx + 1} ]: {playlistItem.playlist_meta.playlist_name}

Autoplay:

{ if (!playlistAutoplay) { setPlaylistIDForAutoplay(playlistItem.playlist_meta.playlist_id); } setPlaylistAutoplay(!playlistAutoplay); }} type="checkbox" /> {!playlistAutoplay && ( )} {playlistAutoplay && ( )}
{playlistItem.playlist_previous && ( <> previous thumbnail

Previous:

[{playlistItem.playlist_previous.idx + 1}]{' '} {playlistItem.playlist_previous.title}

)}
{playlistItem.playlist_next && ( <>

Next:

[{playlistItem.playlist_next.idx + 1}]{' '} {playlistItem.playlist_next.title}

previous thumbnail )}
); })} )}

Similar Videos

{video.comment_count == 0 && (
Video has no comments
)} {video.comment_count && (

Comments: {video.comment_count}

{comments?.map(comment => { return ( ); })}
)}
); }; export default Video;