Spaces:
Paused
Paused
import type { OdFileObject } from '../../types' | |
import { FC, useEffect, useState } from 'react' | |
import { useRouter } from 'next/router' | |
import { useTranslation } from 'next-i18next' | |
import axios from 'axios' | |
import toast from 'react-hot-toast' | |
import Plyr from 'plyr-react' | |
import { useAsync } from 'react-async-hook' | |
import { useClipboard } from 'use-clipboard-copy' | |
import { getBaseUrl } from '../../utils/getBaseUrl' | |
import { getExtension } from '../../utils/getFileIcon' | |
import { getStoredToken } from '../../utils/protectedRouteHandler' | |
import { DownloadButton } from '../DownloadBtnGtoup' | |
import { DownloadBtnContainer, PreviewContainer } from './Containers' | |
import FourOhFour from '../FourOhFour' | |
import Loading from '../Loading' | |
import CustomEmbedLinkMenu from '../CustomEmbedLinkMenu' | |
import 'plyr-react/plyr.css' | |
const VideoPlayer: FC<{ | |
videoName: string | |
videoUrl: string | |
width?: number | |
height?: number | |
thumbnail: string | |
subtitle: string | |
isFlv: boolean | |
mpegts: any | |
}> = ({ videoName, videoUrl, width, height, thumbnail, subtitle, isFlv, mpegts }) => { | |
useEffect(() => { | |
// Really really hacky way to inject subtitles as file blobs into the video element | |
axios | |
.get(subtitle, { responseType: 'blob' }) | |
.then(resp => { | |
const track = document.querySelector('track') | |
track?.setAttribute('src', URL.createObjectURL(resp.data)) | |
}) | |
.catch(() => { | |
console.log('Could not load subtitle.') | |
}) | |
if (isFlv) { | |
const loadFlv = () => { | |
// Really hacky way to get the exposed video element from Plyr | |
const video = document.getElementById('plyr') | |
const flv = mpegts.createPlayer({ url: videoUrl, type: 'flv' }) | |
flv.attachMediaElement(video) | |
flv.load() | |
} | |
loadFlv() | |
} | |
}, [videoUrl, isFlv, mpegts, subtitle]) | |
// Common plyr configs, including the video source and plyr options | |
const plyrSource = { | |
type: 'video', | |
title: videoName, | |
poster: thumbnail, | |
tracks: [{ kind: 'captions', label: videoName, src: '', default: true }], | |
} | |
const plyrOptions: Plyr.Options = { | |
ratio: `${width ?? 16}:${height ?? 9}`, | |
fullscreen: { iosNative: true }, | |
} | |
if (!isFlv) { | |
// If the video is not in flv format, we can use the native plyr and add sources directly with the video URL | |
plyrSource['sources'] = [{ src: videoUrl }] | |
} | |
return <Plyr id="plyr" source={plyrSource as Plyr.SourceInfo} options={plyrOptions} /> | |
} | |
const VideoPreview: FC<{ file: OdFileObject }> = ({ file }) => { | |
const { asPath } = useRouter() | |
const hashedToken = getStoredToken(asPath) | |
const clipboard = useClipboard() | |
const [menuOpen, setMenuOpen] = useState(false) | |
const { t } = useTranslation() | |
// OneDrive generates thumbnails for its video files, we pick the thumbnail with the highest resolution | |
const thumbnail = `/api/thumbnail/?path=${asPath}&size=large${hashedToken ? `&odpt=${hashedToken}` : ''}` | |
// We assume subtitle files are beside the video with the same name, only webvtt '.vtt' files are supported | |
const vtt = `${asPath.substring(0, asPath.lastIndexOf('.'))}.vtt` | |
const subtitle = `/api/raw/?path=${vtt}${hashedToken ? `&odpt=${hashedToken}` : ''}` | |
// We also format the raw video file for the in-browser player as well as all other players | |
const videoUrl = `/api/raw/?path=${asPath}${hashedToken ? `&odpt=${hashedToken}` : ''}` | |
const isFlv = getExtension(file.name) === 'flv' | |
const { | |
loading, | |
error, | |
result: mpegts, | |
} = useAsync(async () => { | |
if (isFlv) { | |
return (await import('mpegts.js')).default | |
} | |
}, [isFlv]) | |
return ( | |
<> | |
<CustomEmbedLinkMenu path={asPath} menuOpen={menuOpen} setMenuOpen={setMenuOpen} /> | |
<PreviewContainer> | |
{error ? ( | |
<FourOhFour errorMsg={error.message} /> | |
) : loading && isFlv ? ( | |
<Loading loadingText={t('Loading FLV extension...')} /> | |
) : ( | |
<VideoPlayer | |
videoName={file.name} | |
videoUrl={videoUrl} | |
width={file.video?.width} | |
height={file.video?.height} | |
thumbnail={thumbnail} | |
subtitle={subtitle} | |
isFlv={isFlv} | |
mpegts={mpegts} | |
/> | |
)} | |
</PreviewContainer> | |
<DownloadBtnContainer> | |
<div className="flex flex-wrap justify-center gap-2"> | |
<DownloadButton | |
onClickCallback={() => window.open(videoUrl)} | |
btnColor="blue" | |
btnText={t('Download')} | |
btnIcon="file-download" | |
/> | |
<DownloadButton | |
onClickCallback={() => { | |
clipboard.copy(`${getBaseUrl()}/api/raw/?path=${asPath}${hashedToken ? `&odpt=${hashedToken}` : ''}`) | |
toast.success(t('Copied direct link to clipboard.')) | |
}} | |
btnColor="pink" | |
btnText={t('Copy direct link')} | |
btnIcon="copy" | |
/> | |
<DownloadButton | |
onClickCallback={() => setMenuOpen(true)} | |
btnColor="teal" | |
btnText={t('Customise link')} | |
btnIcon="pen" | |
/> | |
<DownloadButton | |
onClickCallback={() => window.open(`iina://weblink?url=${getBaseUrl()}${videoUrl}`)} | |
btnText="IINA" | |
btnImage="/players/iina.png" | |
/> | |
<DownloadButton | |
onClickCallback={() => window.open(`vlc://${getBaseUrl()}${videoUrl}`)} | |
btnText="VLC" | |
btnImage="/players/vlc.png" | |
/> | |
<DownloadButton | |
onClickCallback={() => window.open(`potplayer://${getBaseUrl()}${videoUrl}`)} | |
btnText="PotPlayer" | |
btnImage="/players/potplayer.png" | |
/> | |
<DownloadButton | |
onClickCallback={() => window.open(`nplayer-http://${window?.location.hostname ?? ''}${videoUrl}`)} | |
btnText="nPlayer" | |
btnImage="/players/nplayer.png" | |
/> | |
</div> | |
</DownloadBtnContainer> | |
</> | |
) | |
} | |
export default VideoPreview | |