mirror of
https://github.com/Qortal/qapp-core.git
synced 2025-06-16 18:21:21 +00:00
switch sub
This commit is contained in:
parent
d3c4a4713e
commit
864d87697c
@ -1,5 +1,9 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { QortalGetMetadata, QortalMetadata, Service } from "../../types/interfaces/resources";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
QortalGetMetadata,
|
||||
QortalMetadata,
|
||||
Service,
|
||||
} from "../../types/interfaces/resources";
|
||||
import {
|
||||
alpha,
|
||||
Box,
|
||||
@ -15,8 +19,10 @@ import {
|
||||
Popover,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
|
||||
import ModeEditIcon from '@mui/icons-material/ModeEdit';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
|
||||
import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos";
|
||||
import ModeEditIcon from "@mui/icons-material/ModeEdit";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useListStore } from "../../state/lists";
|
||||
import { Resource, useResources } from "../../hooks/useResources";
|
||||
@ -37,8 +43,9 @@ interface SubtitleManagerProps {
|
||||
qortalMetadata: QortalGetMetadata;
|
||||
close: () => void;
|
||||
open: boolean;
|
||||
onSelect: (subtitle: SubtitlePublishedData)=> void;
|
||||
subtitleBtnRef: any
|
||||
onSelect: (subtitle: SubtitlePublishedData) => void;
|
||||
subtitleBtnRef: any;
|
||||
currentSubTrack: null | string;
|
||||
}
|
||||
export interface Subtitle {
|
||||
language: string | null;
|
||||
@ -65,32 +72,38 @@ const SubtitleManagerComponent = ({
|
||||
open,
|
||||
close,
|
||||
onSelect,
|
||||
subtitleBtnRef
|
||||
subtitleBtnRef,
|
||||
currentSubTrack,
|
||||
}: SubtitleManagerProps) => {
|
||||
const [mode, setMode] = useState(1);
|
||||
const { lists, identifierOperations, auth } = useGlobal();
|
||||
const { fetchResources } = useResources();
|
||||
// const [subtitles, setSubtitles] = useState([])
|
||||
const subtitles = useListReturn(`subs-${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`)
|
||||
const subtitles = useListReturn(
|
||||
`subs-${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`
|
||||
);
|
||||
|
||||
|
||||
console.log('subtitles222', subtitles)
|
||||
console.log("subtitles222", subtitles);
|
||||
const getPublishedSubtitles = useCallback(async () => {
|
||||
try {
|
||||
const videoId = `${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`;
|
||||
console.log('videoId', videoId)
|
||||
console.log("videoId", videoId);
|
||||
const postIdSearch = await identifierOperations.buildSearchPrefix(
|
||||
ENTITY_SUBTITLE,
|
||||
videoId,
|
||||
videoId
|
||||
);
|
||||
const searchParams = {
|
||||
service: SERVICE_SUBTITLE,
|
||||
identifier: postIdSearch,
|
||||
limit: 0
|
||||
limit: 0,
|
||||
};
|
||||
const res = await lists.fetchResources(searchParams, `subs-${videoId}`, "BASE64");
|
||||
const res = await lists.fetchResources(
|
||||
searchParams,
|
||||
`subs-${videoId}`,
|
||||
"BASE64"
|
||||
);
|
||||
lists.addList(`subs-${videoId}`, res || []);
|
||||
console.log('resres2', res)
|
||||
console.log("resres2", res);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
@ -104,7 +117,7 @@ const SubtitleManagerComponent = ({
|
||||
)
|
||||
return;
|
||||
|
||||
getPublishedSubtitles()
|
||||
getPublishedSubtitles();
|
||||
}, [
|
||||
qortalMetadata?.identifier,
|
||||
qortalMetadata?.service,
|
||||
@ -120,67 +133,71 @@ const SubtitleManagerComponent = ({
|
||||
// setHasMetadata(false);
|
||||
};
|
||||
|
||||
|
||||
const publishHandler = async (subtitles: Subtitle[]) => {
|
||||
try {
|
||||
const videoId = `${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`;
|
||||
|
||||
const identifier = await identifierOperations.buildIdentifier(ENTITY_SUBTITLE, videoId);
|
||||
const name = auth?.name
|
||||
console.log('identifier2', identifier)
|
||||
if(!name) return
|
||||
const resources: ResourceToPublish[] = []
|
||||
const tempResources: {qortalMetadata: QortalMetadata, data: any}[] = []
|
||||
for(const sub of subtitles ){
|
||||
const identifier = await identifierOperations.buildIdentifier(
|
||||
ENTITY_SUBTITLE,
|
||||
videoId
|
||||
);
|
||||
const name = auth?.name;
|
||||
console.log("identifier2", identifier);
|
||||
if (!name) return;
|
||||
const resources: ResourceToPublish[] = [];
|
||||
const tempResources: { qortalMetadata: QortalMetadata; data: any }[] = [];
|
||||
for (const sub of subtitles) {
|
||||
const data = {
|
||||
subtitleData: sub.base64,
|
||||
language: sub.language,
|
||||
filename: sub.filename,
|
||||
type: sub.type
|
||||
}
|
||||
type: sub.type,
|
||||
};
|
||||
|
||||
const base64Data = await objectToBase64(data)
|
||||
const base64Data = await objectToBase64(data);
|
||||
const resource = {
|
||||
name,
|
||||
identifier,
|
||||
service: SERVICE_SUBTITLE,
|
||||
base64: base64Data,
|
||||
filename: sub.filename,
|
||||
title: sub.language || undefined
|
||||
}
|
||||
resources.push(resource)
|
||||
title: sub.language || undefined,
|
||||
};
|
||||
resources.push(resource);
|
||||
tempResources.push({
|
||||
qortalMetadata: {
|
||||
identifier,
|
||||
service: SERVICE_SUBTITLE,
|
||||
name,
|
||||
size: 100,
|
||||
created: Date.now()
|
||||
created: Date.now(),
|
||||
},
|
||||
data: data,
|
||||
})
|
||||
});
|
||||
}
|
||||
console.log('resources', resources)
|
||||
console.log("resources", resources);
|
||||
|
||||
await qortalRequest({
|
||||
action: 'PUBLISH_MULTIPLE_QDN_RESOURCES',
|
||||
resources
|
||||
})
|
||||
action: "PUBLISH_MULTIPLE_QDN_RESOURCES",
|
||||
resources,
|
||||
});
|
||||
|
||||
|
||||
lists.addNewResources(`subs-${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`, tempResources)
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
lists.addNewResources(
|
||||
`subs-${qortalMetadata?.service}-${qortalMetadata?.name}-${qortalMetadata?.identifier}`,
|
||||
tempResources
|
||||
);
|
||||
} catch (error) {}
|
||||
};
|
||||
const onBack = () => {
|
||||
if (mode === 1) close();
|
||||
};
|
||||
|
||||
const onSelectHandler = (sub: SubtitlePublishedData) => {
|
||||
console.log('onSelectHandler')
|
||||
onSelect(sub);
|
||||
close();
|
||||
};
|
||||
const onBack = ()=> {
|
||||
if(mode === 1) close()
|
||||
}
|
||||
|
||||
const onSelectHandler = (sub: SubtitlePublishedData)=> {
|
||||
onSelect(sub)
|
||||
close()
|
||||
}
|
||||
return (
|
||||
<Popover
|
||||
open={!!open}
|
||||
@ -195,8 +212,8 @@ console.log('identifier2', identifier)
|
||||
},
|
||||
paper: {
|
||||
sx: {
|
||||
bgcolor: alpha('#181818', 0.98),
|
||||
color: 'white',
|
||||
bgcolor: alpha("#181818", 0.98),
|
||||
color: "white",
|
||||
opacity: 0.9,
|
||||
borderRadius: 2,
|
||||
boxShadow: 5,
|
||||
@ -206,38 +223,49 @@ console.log('identifier2', identifier)
|
||||
},
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
padding: "5px 0px 10px 0px",
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
padding: '5px 0px 10px 0px',
|
||||
display: 'flex',
|
||||
gap:'10px',
|
||||
width: '100%'
|
||||
}}>
|
||||
<ButtonBase onClick={onBack}>
|
||||
<ArrowBackIosIcon sx={{
|
||||
fontSize: '1.15em'
|
||||
}}/>
|
||||
<ArrowBackIosIcon
|
||||
sx={{
|
||||
fontSize: "1.15em",
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
<ButtonBase>
|
||||
<Typography onClick={onBack} sx={{
|
||||
fontSize: '0.85rem'
|
||||
}}>Subtitles</Typography>
|
||||
|
||||
<Typography
|
||||
onClick={onBack}
|
||||
sx={{
|
||||
fontSize: "0.85rem",
|
||||
}}
|
||||
>
|
||||
Subtitles
|
||||
</Typography>
|
||||
</ButtonBase>
|
||||
<ButtonBase sx={{
|
||||
marginLeft: 'auto',
|
||||
|
||||
}}>
|
||||
<ModeEditIcon sx={{
|
||||
fontSize: '1.15rem'
|
||||
}} />
|
||||
<ButtonBase
|
||||
sx={{
|
||||
marginLeft: "auto",
|
||||
}}
|
||||
>
|
||||
<ModeEditIcon
|
||||
sx={{
|
||||
fontSize: "1.15rem",
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
<Divider />
|
||||
@ -248,6 +276,7 @@ console.log('identifier2', identifier)
|
||||
setMode={setMode}
|
||||
onSelect={onSelectHandler}
|
||||
onBack={onBack}
|
||||
currentSubTrack={currentSubTrack}
|
||||
/>
|
||||
)}
|
||||
{/* <Box>
|
||||
@ -344,7 +373,8 @@ interface PublisherSubtitlesProps {
|
||||
subtitles: any[];
|
||||
setMode: (val: number) => void;
|
||||
onSelect: (subtitle: any) => void;
|
||||
onBack: ()=> void;
|
||||
onBack: () => void;
|
||||
currentSubTrack: string | null
|
||||
}
|
||||
|
||||
const PublisherSubtitles = ({
|
||||
@ -352,28 +382,29 @@ const PublisherSubtitles = ({
|
||||
subtitles,
|
||||
setMode,
|
||||
onSelect,
|
||||
onBack
|
||||
onBack,
|
||||
currentSubTrack
|
||||
}: PublisherSubtitlesProps) => {
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
{subtitles?.map((sub)=> {
|
||||
return <Subtitle onSelect={onSelect} sub={sub} key={`${sub?.qortalMetadata?.service}-${sub?.qortalMetadata?.name}-${sub?.qortalMetadata?.identifier}`}/>
|
||||
{subtitles?.map((sub) => {
|
||||
return (
|
||||
<Subtitle
|
||||
currentSubtrack={currentSubTrack}
|
||||
onSelect={onSelect}
|
||||
sub={sub}
|
||||
key={`${sub?.qortalMetadata?.service}-${sub?.qortalMetadata?.name}-${sub?.qortalMetadata?.identifier}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface PublishSubtitlesProps {
|
||||
publishHandler: (subs: Subtitle[])=> void
|
||||
publishHandler: (subs: Subtitle[]) => void;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const PublishSubtitles = ({ publishHandler }: PublishSubtitlesProps) => {
|
||||
const [language, setLanguage] = useState<null | string>(null);
|
||||
const [subtitles, setSubtitles] = useState<Subtitle[]>([]);
|
||||
@ -388,7 +419,7 @@ const PublishSubtitles = ({ publishHandler }: PublishSubtitlesProps) => {
|
||||
filename: file.name,
|
||||
size: file.size,
|
||||
};
|
||||
newSubtitles.push(newSubtitle)
|
||||
newSubtitles.push(newSubtitle);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse audio file:", error);
|
||||
}
|
||||
@ -412,7 +443,7 @@ const PublishSubtitles = ({ publishHandler }: PublishSubtitlesProps) => {
|
||||
maxSize: 2 * 1024 * 1024, // 2MB
|
||||
});
|
||||
|
||||
const onChangeValue = (field: string, data: any, index: number) => {
|
||||
const onChangeValue = (field: string, data: any, index: number) => {
|
||||
const sub = subtitles[index];
|
||||
if (!sub) return;
|
||||
|
||||
@ -423,8 +454,8 @@ const onChangeValue = (field: string, data: any, index: number) => {
|
||||
copyPrev[index] = copySub;
|
||||
return copyPrev;
|
||||
});
|
||||
};
|
||||
console.log('subtitles', subtitles)
|
||||
};
|
||||
console.log("subtitles", subtitles);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -441,8 +472,8 @@ console.log('subtitles', subtitles)
|
||||
<Box {...getRootProps()}>
|
||||
<Button
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: '10px',
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
}}
|
||||
variant="contained"
|
||||
>
|
||||
@ -455,7 +486,9 @@ console.log('subtitles', subtitles)
|
||||
<>
|
||||
<LanguageSelect
|
||||
value={sub.language}
|
||||
onChange={(val: string | null) => onChangeValue('language',val, i)}
|
||||
onChange={(val: string | null) =>
|
||||
onChangeValue("language", val, i)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@ -464,7 +497,7 @@ console.log('subtitles', subtitles)
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={()=> publishHandler(subtitles)}
|
||||
onClick={() => publishHandler(subtitles)}
|
||||
// disabled={disableButton}
|
||||
variant="contained"
|
||||
>
|
||||
@ -476,26 +509,38 @@ console.log('subtitles', subtitles)
|
||||
};
|
||||
|
||||
interface SubProps {
|
||||
sub: QortalGetMetadata
|
||||
onSelect: (subtitle: Subtitle)=> void;
|
||||
sub: QortalGetMetadata;
|
||||
onSelect: (subtitle: Subtitle) => void;
|
||||
currentSubtrack: null | string
|
||||
}
|
||||
const Subtitle = ({sub, onSelect}: SubProps)=> {
|
||||
const {resource, isLoading } = usePublish(2, 'JSON', sub)
|
||||
console.log('resource', resource)
|
||||
return <Typography
|
||||
onClick={()=> onSelect(resource?.data)}
|
||||
|
||||
sx={{
|
||||
const Subtitle = ({ sub, onSelect, currentSubtrack }: SubProps) => {
|
||||
const { resource, isLoading } = usePublish(2, "JSON", sub);
|
||||
console.log("resource", resource);
|
||||
const isSelected = currentSubtrack === resource?.data?.language
|
||||
return (
|
||||
<ButtonBase onClick={() => onSelect(isSelected ? null : resource?.data)} sx={{
|
||||
px: 2,
|
||||
py: 1,
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
cursor: 'pointer',
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||
},
|
||||
}}
|
||||
width: '100%',
|
||||
justifyContent: 'space-between'
|
||||
}}>
|
||||
<Typography
|
||||
|
||||
|
||||
>
|
||||
{resource?.data?.language}
|
||||
</Typography>
|
||||
}
|
||||
{isSelected ? (
|
||||
<CheckIcon />
|
||||
) : (
|
||||
<ArrowForwardIosIcon />
|
||||
)}
|
||||
|
||||
export const SubtitleManager = React.memo(SubtitleManagerComponent);
|
||||
</ButtonBase>
|
||||
);
|
||||
};
|
||||
|
||||
export const SubtitleManager = React.memo(SubtitleManagerComponent);
|
||||
|
@ -65,6 +65,7 @@ export const VideoControlsBar = ({subtitleBtnRef, showControls, playbackRate, in
|
||||
opacity: showControls ? 1 : 0,
|
||||
pointerEvents: showControls ? 'auto' : 'none',
|
||||
transition: 'opacity 0.4s ease-in-out',
|
||||
width: '100%'
|
||||
// ...additionalStyles
|
||||
// height: controlsHeight,
|
||||
}}
|
||||
@ -94,7 +95,7 @@ export const VideoControlsBar = ({subtitleBtnRef, showControls, playbackRate, in
|
||||
<VideoTime progress={progress} duration={duration}/>
|
||||
</Box>
|
||||
|
||||
<Box sx={controlGroupSX}>
|
||||
<Box sx={{...controlGroupSX, marginLeft: 'auto'}}>
|
||||
<PlaybackRate playbackRate={playbackRate} increaseSpeed={increaseSpeed} decreaseSpeed={decreaseSpeed} />
|
||||
<ObjectFitButton />
|
||||
<IconButton ref={subtitleBtnRef} onClick={openSubtitleManager}>
|
||||
|
@ -1,4 +1,13 @@
|
||||
import { ReactEventHandler, Ref, RefObject, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
ReactEventHandler,
|
||||
Ref,
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { QortalGetMetadata } from "../../types/interfaces/resources";
|
||||
import { VideoContainer, VideoElement } from "./VideoPlayer-styles";
|
||||
import { useVideoPlayerHotKeys } from "./useVideoPlayerHotKeys";
|
||||
@ -6,15 +15,21 @@ import { useProgressStore, useVideoStore } from "../../state/video";
|
||||
import { useVideoPlayerController } from "./useVideoPlayerController";
|
||||
import { LoadingVideo } from "./LoadingVideo";
|
||||
import { VideoControlsBar } from "./VideoControlsBar";
|
||||
import videojs from 'video.js';
|
||||
import 'video.js/dist/video-js.css';
|
||||
import videojs from "video.js";
|
||||
import "video.js/dist/video-js.css";
|
||||
|
||||
import Player from "video.js/dist/types/player";
|
||||
import { Subtitle, SubtitleManager, SubtitlePublishedData } from "./SubtitleManager";
|
||||
import {
|
||||
Subtitle,
|
||||
SubtitleManager,
|
||||
SubtitlePublishedData,
|
||||
} from "./SubtitleManager";
|
||||
import { base64ToBlobUrl } from "../../utils/base64";
|
||||
import convert from 'srt-webvtt';
|
||||
import convert from "srt-webvtt";
|
||||
|
||||
export async function srtBase64ToVttBlobUrl(base64Srt: string): Promise<string | null> {
|
||||
export async function srtBase64ToVttBlobUrl(
|
||||
base64Srt: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
// Step 1: Convert base64 string to a Uint8Array
|
||||
const binary = atob(base64Srt);
|
||||
@ -24,20 +39,19 @@ export async function srtBase64ToVttBlobUrl(base64Srt: string): Promise<string |
|
||||
}
|
||||
|
||||
// Step 2: Create a Blob from the Uint8Array with correct MIME type
|
||||
const srtBlob = new Blob([bytes], { type: 'application/x-subrip' });
|
||||
console.log('srtBlob', srtBlob)
|
||||
const srtBlob = new Blob([bytes], { type: "application/x-subrip" });
|
||||
console.log("srtBlob", srtBlob);
|
||||
// Step 3: Use convert() with the Blob
|
||||
const vttBlobUrl: string = await convert(srtBlob);
|
||||
return vttBlobUrl
|
||||
return vttBlobUrl;
|
||||
} catch (error) {
|
||||
console.error('Failed to convert SRT to VTT:', error);
|
||||
console.error("Failed to convert SRT to VTT:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
type StretchVideoType = "contain" | "fill" | "cover" | "none" | "scale-down";
|
||||
|
||||
|
||||
interface VideoPlayerProps {
|
||||
interface VideoPlayerProps {
|
||||
qortalVideoResource: QortalGetMetadata;
|
||||
videoRef: Ref<HTMLVideoElement>;
|
||||
retryAttempts?: number;
|
||||
@ -47,27 +61,30 @@ type StretchVideoType = "contain" | "fill" | "cover" | "none" | "scale-down";
|
||||
}
|
||||
|
||||
const videoStyles = {
|
||||
videoContainer: { },
|
||||
video: { },
|
||||
videoContainer: {},
|
||||
video: {},
|
||||
};
|
||||
|
||||
async function loadMediaInfo(wasmPath = '/MediaInfoModule.wasm') {
|
||||
const mediaInfoModule = await import('mediainfo.js');
|
||||
async function loadMediaInfo(wasmPath = "/MediaInfoModule.wasm") {
|
||||
const mediaInfoModule = await import("mediainfo.js");
|
||||
return await mediaInfoModule.default({
|
||||
format: 'JSON',
|
||||
format: "JSON",
|
||||
full: true,
|
||||
locateFile: () => wasmPath,
|
||||
});
|
||||
}
|
||||
|
||||
async function getVideoMimeTypeFromUrl(qortalVideoResource: any): Promise<string | null> {
|
||||
|
||||
async function getVideoMimeTypeFromUrl(
|
||||
qortalVideoResource: any
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const metadataResponse = await fetch(`/arbitrary/metadata/${qortalVideoResource.service}/${qortalVideoResource.name}/${qortalVideoResource.identifier}`)
|
||||
const metadataData = await metadataResponse.json()
|
||||
return metadataData?.mimeType || null
|
||||
const metadataResponse = await fetch(
|
||||
`/arbitrary/metadata/${qortalVideoResource.service}/${qortalVideoResource.name}/${qortalVideoResource.identifier}`
|
||||
);
|
||||
const metadataData = await metadataResponse.json();
|
||||
return metadataData?.mimeType || null;
|
||||
} catch (error) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
// const mediaInfo = await loadMediaInfo();
|
||||
// const chunkCache = new Map<string, Uint8Array>();
|
||||
@ -147,23 +164,26 @@ export const VideoPlayer = ({
|
||||
const containerRef = useRef<RefObject<HTMLDivElement> | null>(null);
|
||||
const [videoObjectFit] = useState<StretchVideoType>("contain");
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const { volume, setVolume, setPlaybackRate, playbackRate } = useVideoStore((state) => ({
|
||||
const { volume, setVolume, setPlaybackRate, playbackRate } = useVideoStore(
|
||||
(state) => ({
|
||||
volume: state.playbackSettings.volume,
|
||||
setVolume: state.setVolume,
|
||||
setPlaybackRate: state.setPlaybackRate,
|
||||
playbackRate: state.playbackSettings.playbackRate
|
||||
}));
|
||||
playbackRate: state.playbackSettings.playbackRate,
|
||||
})
|
||||
);
|
||||
const playerRef = useRef<Player | null>(null);
|
||||
const [isPlayerInitialized, setIsPlayerInitialized] = useState(false)
|
||||
const [videoCodec, setVideoCodec] = useState<null | false | string>(null)
|
||||
const [isPlayerInitialized, setIsPlayerInitialized] = useState(false);
|
||||
const [videoCodec, setVideoCodec] = useState<null | false | string>(null);
|
||||
const [isMuted, setIsMuted] = useState(false);
|
||||
const { setProgress } = useProgressStore();
|
||||
const [localProgress, setLocalProgress] = useState(0)
|
||||
const [duration, setDuration] = useState(0)
|
||||
const [localProgress, setLocalProgress] = useState(0);
|
||||
const [duration, setDuration] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [showControls, setShowControls] = useState(false)
|
||||
const [isOpenSubtitleManage, setIsOpenSubtitleManage] = useState(false)
|
||||
const subtitleBtnRef = useRef(null)
|
||||
const [showControls, setShowControls] = useState(false);
|
||||
const [isOpenSubtitleManage, setIsOpenSubtitleManage] = useState(false);
|
||||
const subtitleBtnRef = useRef(null);
|
||||
const [currentSubTrack, setCurrentSubTrack] = useState<null | string>(null)
|
||||
const {
|
||||
reloadVideo,
|
||||
togglePlay,
|
||||
@ -183,15 +203,15 @@ export const VideoPlayer = ({
|
||||
startPlay,
|
||||
setProgressAbsolute,
|
||||
setAlwaysShowControls,
|
||||
status, percentLoaded,
|
||||
status,
|
||||
percentLoaded,
|
||||
showControlsFullScreen,
|
||||
|
||||
} = useVideoPlayerController({
|
||||
autoPlay,
|
||||
playerRef,
|
||||
qortalVideoResource,
|
||||
retryAttempts,
|
||||
isPlayerInitialized
|
||||
isPlayerInitialized,
|
||||
});
|
||||
|
||||
const hotkeyHandlers = useMemo(
|
||||
@ -223,16 +243,12 @@ export const VideoPlayer = ({
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const closeSubtitleManager = useCallback(()=> {
|
||||
setIsOpenSubtitleManage(false)
|
||||
}, [])
|
||||
const openSubtitleManager = useCallback(()=> {
|
||||
setIsOpenSubtitleManage(true)
|
||||
}, [])
|
||||
const closeSubtitleManager = useCallback(() => {
|
||||
setIsOpenSubtitleManage(false);
|
||||
}, []);
|
||||
const openSubtitleManager = useCallback(() => {
|
||||
setIsOpenSubtitleManage(true);
|
||||
}, []);
|
||||
|
||||
const videoLocation = useMemo(() => {
|
||||
if (!qortalVideoResource) return null;
|
||||
@ -242,14 +258,14 @@ const closeSubtitleManager = useCallback(()=> {
|
||||
|
||||
const updateProgress = useCallback(() => {
|
||||
const player = playerRef?.current;
|
||||
if (!player || typeof player?.currentTime !== 'function') return;
|
||||
if (!player || typeof player?.currentTime !== "function") return;
|
||||
|
||||
const currentTime = player.currentTime();
|
||||
if (typeof currentTime === 'number' && videoLocation && currentTime > 0.1) {
|
||||
if (typeof currentTime === "number" && videoLocation && currentTime > 0.1) {
|
||||
setProgress(videoLocation, currentTime);
|
||||
setLocalProgress(currentTime);
|
||||
}
|
||||
}, [videoLocation]);
|
||||
}, [videoLocation]);
|
||||
// useEffect(() => {
|
||||
// const ref = videoRef as React.RefObject<HTMLVideoElement>;
|
||||
// if (!ref.current) return;
|
||||
@ -274,30 +290,27 @@ const closeSubtitleManager = useCallback(()=> {
|
||||
setVolume(video.volume);
|
||||
setIsMuted(video.muted);
|
||||
} catch (error) {
|
||||
console.error('onVolumeChangeHandler', onVolumeChangeHandler)
|
||||
console.error("onVolumeChangeHandler", onVolumeChangeHandler);
|
||||
}
|
||||
},
|
||||
[setIsMuted, setVolume]
|
||||
);
|
||||
|
||||
|
||||
|
||||
const videoStylesContainer = useMemo(() => {
|
||||
return {
|
||||
cursor: showControls ? 'auto' : 'none',
|
||||
aspectRatio: '16 / 9',
|
||||
cursor: showControls ? "auto" : "none",
|
||||
aspectRatio: "16 / 9",
|
||||
...videoStyles?.videoContainer,
|
||||
};
|
||||
}, [showControls]);
|
||||
|
||||
|
||||
const videoStylesVideo = useMemo(() => {
|
||||
return {
|
||||
...videoStyles?.video,
|
||||
objectFit: videoObjectFit,
|
||||
backgroundColor: "#000000",
|
||||
height: isFullscreen ? "calc(100vh - 40px)" : "100%",
|
||||
width: '100%'
|
||||
width: "100%",
|
||||
};
|
||||
}, [videoObjectFit, isFullscreen]);
|
||||
|
||||
@ -310,29 +323,28 @@ const closeSubtitleManager = useCallback(()=> {
|
||||
[onEnded]
|
||||
);
|
||||
|
||||
const handleCanPlay = useCallback(()=> {
|
||||
const handleCanPlay = useCallback(() => {
|
||||
setIsLoading(false);
|
||||
}, [setIsLoading])
|
||||
}, [setIsLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if(!isPlayerInitialized) return
|
||||
if (!isPlayerInitialized) return;
|
||||
const player = playerRef.current;
|
||||
if (!player || typeof player.on !== 'function') return;
|
||||
if (!player || typeof player.on !== "function") return;
|
||||
|
||||
const handleLoadedMetadata = () => {
|
||||
const duration = player.duration?.();
|
||||
if (typeof duration === 'number' && !isNaN(duration)) {
|
||||
if (typeof duration === "number" && !isNaN(duration)) {
|
||||
setDuration(duration);
|
||||
}
|
||||
};
|
||||
|
||||
player.on('loadedmetadata', handleLoadedMetadata);
|
||||
player.on("loadedmetadata", handleLoadedMetadata);
|
||||
|
||||
return () => {
|
||||
player.off('loadedmetadata', handleLoadedMetadata);
|
||||
player.off("loadedmetadata", handleLoadedMetadata);
|
||||
};
|
||||
}, [isPlayerInitialized]);
|
||||
|
||||
}, [isPlayerInitialized]);
|
||||
|
||||
const enterFullscreen = () => {
|
||||
const ref = containerRef?.current as any;
|
||||
@ -341,8 +353,6 @@ const closeSubtitleManager = useCallback(()=> {
|
||||
if (ref.requestFullscreen && !isFullscreen) {
|
||||
ref.requestFullscreen();
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
const exitFullscreen = () => {
|
||||
@ -353,23 +363,19 @@ const closeSubtitleManager = useCallback(()=> {
|
||||
isFullscreen ? exitFullscreen() : enterFullscreen();
|
||||
};
|
||||
|
||||
const canvasRef = useRef(null)
|
||||
const videoRefForCanvas = useRef<any>(null)
|
||||
const extractFrames = useCallback( (time: number): void => {
|
||||
const canvasRef = useRef(null);
|
||||
const videoRefForCanvas = useRef<any>(null);
|
||||
const extractFrames = useCallback((time: number): void => {
|
||||
// const video = videoRefForCanvas?.current;
|
||||
// const canvas: any = canvasRef.current;
|
||||
|
||||
// if (!video || !canvas) return null;
|
||||
|
||||
// // Avoid unnecessary resize if already correct
|
||||
// if (canvas.width !== video.videoWidth || canvas.height !== video.videoHeight) {
|
||||
// canvas.width = video.videoWidth;
|
||||
// canvas.height = video.videoHeight;
|
||||
// }
|
||||
|
||||
// const context = canvas.getContext("2d");
|
||||
// if (!context) return null;
|
||||
|
||||
// // If video is already near the correct time, don't seek again
|
||||
// const threshold = 0.01; // 10ms threshold
|
||||
// if (Math.abs(video.currentTime - time) > threshold) {
|
||||
@ -379,45 +385,39 @@ const extractFrames = useCallback( (time: number): void => {
|
||||
// video.currentTime = time;
|
||||
// });
|
||||
// }
|
||||
|
||||
// context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
// // Use a faster method for image export (optional tradeoff)
|
||||
// const blob = await new Promise<Blob | null>((resolve) => {
|
||||
// canvas.toBlob((blob: any) => resolve(blob), "image/webp", 0.7);
|
||||
// });
|
||||
|
||||
// if (!blob) return null;
|
||||
|
||||
// return URL.createObjectURL(blob);
|
||||
}, []);
|
||||
|
||||
}, []);
|
||||
|
||||
const hideTimeout = useRef<any>(null);
|
||||
|
||||
|
||||
const resetHideTimer = () => {
|
||||
const resetHideTimer = () => {
|
||||
setShowControls(true);
|
||||
if (hideTimeout.current) clearTimeout(hideTimeout.current);
|
||||
hideTimeout.current = setTimeout(() => {
|
||||
setShowControls(false);
|
||||
}, 2500); // 3s of inactivity
|
||||
};
|
||||
};
|
||||
|
||||
const handleMouseMove = () => {
|
||||
const handleMouseMove = () => {
|
||||
resetHideTimer();
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
resetHideTimer(); // initial show
|
||||
return () => {
|
||||
if (hideTimeout.current) clearTimeout(hideTimeout.current);
|
||||
};
|
||||
}, []);
|
||||
}, []);
|
||||
|
||||
const previousSubtitleUrlRef = useRef<string | null>(null);
|
||||
const previousSubtitleUrlRef = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Component unmount cleanup
|
||||
if (previousSubtitleUrlRef.current) {
|
||||
@ -425,35 +425,26 @@ useEffect(() => {
|
||||
previousSubtitleUrlRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, []);
|
||||
|
||||
const onSelectSubtitle = useCallback(async (subtitle: SubtitlePublishedData)=> {
|
||||
console.log('onSelectSubtitle', subtitle)
|
||||
const player = playerRef.current;
|
||||
if (!player || !subtitle.subtitleData || !subtitle.type) return;
|
||||
|
||||
// Cleanup: revoke previous Blob URL
|
||||
const onSelectSubtitle = useCallback(
|
||||
async (subtitle: SubtitlePublishedData) => {
|
||||
if(subtitle === null){
|
||||
setCurrentSubTrack(null)
|
||||
if (previousSubtitleUrlRef.current) {
|
||||
URL.revokeObjectURL(previousSubtitleUrlRef.current);
|
||||
previousSubtitleUrlRef.current = null;
|
||||
}
|
||||
let blobUrl
|
||||
if(subtitle?.type === "application/x-subrip"){
|
||||
blobUrl = await srtBase64ToVttBlobUrl(subtitle.subtitleData)
|
||||
} else {
|
||||
blobUrl = base64ToBlobUrl(subtitle.subtitleData, subtitle.type)
|
||||
const remoteTracksList = playerRef.current?.remoteTextTracks();
|
||||
|
||||
}
|
||||
|
||||
previousSubtitleUrlRef.current = blobUrl;
|
||||
|
||||
const remoteTracksList = playerRef.current?.remoteTextTracks();
|
||||
|
||||
if (remoteTracksList) {
|
||||
if (remoteTracksList) {
|
||||
const toRemove: TextTrack[] = [];
|
||||
|
||||
// Bypass TS restrictions safely
|
||||
const list = remoteTracksList as unknown as { length: number; [index: number]: TextTrack };
|
||||
const list = remoteTracksList as unknown as {
|
||||
length: number;
|
||||
[index: number]: TextTrack;
|
||||
};
|
||||
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const track = list[i];
|
||||
@ -463,69 +454,116 @@ if (remoteTracksList) {
|
||||
toRemove.forEach((track) => {
|
||||
playerRef.current?.removeRemoteTextTrack(track);
|
||||
});
|
||||
}
|
||||
playerRef.current?.addRemoteTextTrack({
|
||||
kind: 'subtitles',
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
console.log("onSelectSubtitle", subtitle);
|
||||
const player = playerRef.current;
|
||||
if (!player || !subtitle.subtitleData || !subtitle.type) return;
|
||||
|
||||
// Cleanup: revoke previous Blob URL
|
||||
if (previousSubtitleUrlRef.current) {
|
||||
URL.revokeObjectURL(previousSubtitleUrlRef.current);
|
||||
previousSubtitleUrlRef.current = null;
|
||||
}
|
||||
let blobUrl;
|
||||
if (subtitle?.type === "application/x-subrip") {
|
||||
blobUrl = await srtBase64ToVttBlobUrl(subtitle.subtitleData);
|
||||
} else {
|
||||
blobUrl = base64ToBlobUrl(subtitle.subtitleData, subtitle.type);
|
||||
}
|
||||
|
||||
previousSubtitleUrlRef.current = blobUrl;
|
||||
|
||||
const remoteTracksList = playerRef.current?.remoteTextTracks();
|
||||
|
||||
if (remoteTracksList) {
|
||||
const toRemove: TextTrack[] = [];
|
||||
|
||||
// Bypass TS restrictions safely
|
||||
const list = remoteTracksList as unknown as {
|
||||
length: number;
|
||||
[index: number]: TextTrack;
|
||||
};
|
||||
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const track = list[i];
|
||||
if (track) toRemove.push(track);
|
||||
}
|
||||
|
||||
toRemove.forEach((track) => {
|
||||
playerRef.current?.removeRemoteTextTrack(track);
|
||||
});
|
||||
}
|
||||
playerRef.current?.addRemoteTextTrack(
|
||||
{
|
||||
kind: "subtitles",
|
||||
src: blobUrl,
|
||||
srclang: 'en',
|
||||
label: 'English',
|
||||
default: true
|
||||
}, true);
|
||||
srclang: subtitle.language,
|
||||
label: subtitle.language,
|
||||
default: true,
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
// Remove all existing remote text tracks
|
||||
// try {
|
||||
// const remoteTracks = playerRef.current?.remoteTextTracks()?.tracks_
|
||||
// if (remoteTracks && remoteTracks?.length) {
|
||||
// const toRemove: TextTrack[] = [];
|
||||
// for (let i = 0; i < remoteTracks.length; i++) {
|
||||
// const track = remoteTracks[i];
|
||||
// toRemove.push(track);
|
||||
// }
|
||||
// toRemove.forEach((track) => {
|
||||
// console.log('removing track')
|
||||
// playerRef.current?.removeRemoteTextTrack(track);
|
||||
// });
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.log('error2', error)
|
||||
// }
|
||||
// try {
|
||||
// const remoteTracks = playerRef.current?.remoteTextTracks()?.tracks_
|
||||
// if (remoteTracks && remoteTracks?.length) {
|
||||
// const toRemove: TextTrack[] = [];
|
||||
// for (let i = 0; i < remoteTracks.length; i++) {
|
||||
// const track = remoteTracks[i];
|
||||
// toRemove.push(track);
|
||||
// }
|
||||
// toRemove.forEach((track) => {
|
||||
// console.log('removing track')
|
||||
// playerRef.current?.removeRemoteTextTrack(track);
|
||||
// });
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.log('error2', error)
|
||||
// }
|
||||
|
||||
await new Promise((res)=> {
|
||||
await new Promise((res) => {
|
||||
setTimeout(() => {
|
||||
res(null)
|
||||
res(null);
|
||||
}, 1000);
|
||||
})
|
||||
const tracksInfo = playerRef.current?.textTracks();
|
||||
console.log('tracksInfo', tracksInfo)
|
||||
if (!tracksInfo) return;
|
||||
});
|
||||
const tracksInfo = playerRef.current?.textTracks();
|
||||
console.log("tracksInfo", tracksInfo);
|
||||
if (!tracksInfo) return;
|
||||
|
||||
const tracks = Array.from({ length: (tracksInfo as any).length }, (_, i) => (tracksInfo as any)[i]);
|
||||
console.log('tracks', tracks)
|
||||
for (const track of tracks) {
|
||||
console.log('track', track)
|
||||
const tracks = Array.from(
|
||||
{ length: (tracksInfo as any).length },
|
||||
(_, i) => (tracksInfo as any)[i]
|
||||
);
|
||||
console.log("tracks", tracks);
|
||||
for (const track of tracks) {
|
||||
console.log("track", track);
|
||||
|
||||
if (track.kind === 'subtitles') {
|
||||
track.mode = 'showing'; // force display
|
||||
if (track.kind === "subtitles") {
|
||||
track.mode = "showing"; // force display
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
},[])
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setShowControls(false);
|
||||
if (hideTimeout.current) clearTimeout(hideTimeout.current);
|
||||
}, [setShowControls]);
|
||||
|
||||
const videoLocactionStringified = useMemo(() => {
|
||||
return JSON.stringify(qortalVideoResource);
|
||||
}, [qortalVideoResource]);
|
||||
|
||||
const videoLocactionStringified = useMemo(()=> {
|
||||
return JSON.stringify(qortalVideoResource)
|
||||
}, [qortalVideoResource])
|
||||
useEffect(() => {
|
||||
if (!resourceUrl || !isReady || !videoLocactionStringified || !startPlay)
|
||||
return;
|
||||
|
||||
useEffect(() => {
|
||||
if (!resourceUrl || !isReady || !videoLocactionStringified || !startPlay) return;
|
||||
|
||||
const resource = JSON.parse(videoLocactionStringified)
|
||||
const resource = JSON.parse(videoLocactionStringified);
|
||||
let canceled = false;
|
||||
|
||||
try {
|
||||
@ -539,11 +577,11 @@ useEffect(() => {
|
||||
responsive: true,
|
||||
fluid: true,
|
||||
poster: startPlay ? "" : poster,
|
||||
aspectRatio: '16:9' ,
|
||||
aspectRatio: "16:9",
|
||||
sources: [
|
||||
{
|
||||
src: resourceUrl,
|
||||
type: type || 'video/mp4', // fallback
|
||||
type: type || "video/mp4", // fallback
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -552,61 +590,96 @@ useEffect(() => {
|
||||
|
||||
if (!playerRef.current && ref.current) {
|
||||
playerRef.current = videojs(ref.current, options, () => {
|
||||
setIsPlayerInitialized(true)
|
||||
playerRef.current?.poster('');
|
||||
playerRef.current?.playbackRate(playbackRate)
|
||||
setIsPlayerInitialized(true);
|
||||
playerRef.current?.poster("");
|
||||
playerRef.current?.playbackRate(playbackRate);
|
||||
playerRef.current?.volume(volume);
|
||||
|
||||
|
||||
playerRef.current?.play();
|
||||
|
||||
const tracksInfo = playerRef.current?.textTracks();
|
||||
|
||||
const checkActiveSubtitle = () => {
|
||||
let activeTrack = null;
|
||||
|
||||
const tracks = Array.from(
|
||||
{ length: (tracksInfo as any).length },
|
||||
(_, i) => (tracksInfo as any)[i]
|
||||
);
|
||||
console.log("tracks", tracks);
|
||||
for (const track of tracks) {
|
||||
|
||||
if (track.kind === 'subtitles' || track.kind === 'captions') {
|
||||
if (track.mode === 'showing') {
|
||||
activeTrack = track;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (activeTrack) {
|
||||
console.log("Subtitle active:", {
|
||||
label: activeTrack.label,
|
||||
srclang: activeTrack.language || activeTrack.srclang, // srclang for native, language for VTT
|
||||
});
|
||||
playerRef.current?.on('error', () => {
|
||||
setCurrentSubTrack(activeTrack.language || activeTrack.srclang)
|
||||
} else {
|
||||
setCurrentSubTrack(null)
|
||||
console.log("No subtitle is currently showing");
|
||||
}
|
||||
};
|
||||
|
||||
// Initial check in case one is auto-enabled
|
||||
checkActiveSubtitle();
|
||||
|
||||
// Use Video.js event system
|
||||
tracksInfo?.on("change", checkActiveSubtitle);
|
||||
});
|
||||
playerRef.current?.on("error", () => {
|
||||
const error = playerRef.current?.error();
|
||||
console.error('Video.js playback error:', error);
|
||||
console.error("Video.js playback error:", error);
|
||||
// Optional: display user-friendly message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
setupPlayer();
|
||||
|
||||
} catch (error) {
|
||||
console.error('useEffect start player', error)
|
||||
console.error("useEffect start player", error);
|
||||
}
|
||||
return () => {
|
||||
canceled = true;
|
||||
const player = playerRef.current;
|
||||
|
||||
if (player && typeof player.dispose === 'function') {
|
||||
if (player && typeof player.dispose === "function") {
|
||||
try {
|
||||
player.dispose();
|
||||
} catch (err) {
|
||||
console.error('Error disposing Video.js player:', err);
|
||||
console.error("Error disposing Video.js player:", err);
|
||||
}
|
||||
playerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [isReady, resourceUrl, startPlay, poster, videoLocactionStringified]);
|
||||
};
|
||||
}, [isReady, resourceUrl, startPlay, poster, videoLocactionStringified]);
|
||||
|
||||
useEffect(() => {
|
||||
if(!isPlayerInitialized) return
|
||||
if (!isPlayerInitialized) return;
|
||||
const player = playerRef?.current;
|
||||
if (!player) return;
|
||||
|
||||
const handleRateChange = () => {
|
||||
const newRate = player?.playbackRate();
|
||||
if(newRate){
|
||||
if (newRate) {
|
||||
setPlaybackRate(newRate); // or any other state/action
|
||||
}
|
||||
};
|
||||
|
||||
player.on('ratechange', handleRateChange);
|
||||
player.on("ratechange", handleRateChange);
|
||||
|
||||
return () => {
|
||||
player.off('ratechange', handleRateChange);
|
||||
player.off("ratechange", handleRateChange);
|
||||
};
|
||||
}, [isPlayerInitialized]);
|
||||
}, [isPlayerInitialized]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -619,12 +692,17 @@ useEffect(() => {
|
||||
onMouseLeave={handleMouseLeave}
|
||||
ref={containerRef}
|
||||
>
|
||||
<LoadingVideo togglePlay={togglePlay} isReady={isReady} status={status} percentLoaded={percentLoaded} isLoading={isLoading} />
|
||||
<LoadingVideo
|
||||
togglePlay={togglePlay}
|
||||
isReady={isReady}
|
||||
status={status}
|
||||
percentLoaded={percentLoaded}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<VideoElement
|
||||
ref={videoRef}
|
||||
tabIndex={0}
|
||||
className="video-js"
|
||||
|
||||
src={isReady && startPlay ? resourceUrl || undefined : undefined}
|
||||
poster={startPlay ? "" : poster}
|
||||
onTimeUpdate={updateProgress}
|
||||
@ -638,17 +716,43 @@ useEffect(() => {
|
||||
onPause={onPause}
|
||||
onVolumeChange={onVolumeChangeHandler}
|
||||
controls={false}
|
||||
|
||||
/>
|
||||
{/* <canvas ref={canvasRef} style={{ display: "none" }}></canvas> */}
|
||||
|
||||
|
||||
{isReady && (
|
||||
<VideoControlsBar subtitleBtnRef={subtitleBtnRef} playbackRate={playbackRate} increaseSpeed={hotkeyHandlers.increaseSpeed}
|
||||
decreaseSpeed={hotkeyHandlers.decreaseSpeed} playerRef={playerRef} isFullScreen={isFullscreen} showControlsFullScreen={showControlsFullScreen} showControls={showControls} extractFrames={extractFrames} toggleFullscreen={toggleFullscreen} onVolumeChange={onVolumeChange} volume={volume} togglePlay={togglePlay} reloadVideo={hotkeyHandlers.reloadVideo} isPlaying={isPlaying} canPlay={true} isScreenSmall={false} controlsHeight={controlsHeight} duration={duration} progress={localProgress} openSubtitleManager={openSubtitleManager} />
|
||||
<VideoControlsBar
|
||||
subtitleBtnRef={subtitleBtnRef}
|
||||
playbackRate={playbackRate}
|
||||
increaseSpeed={hotkeyHandlers.increaseSpeed}
|
||||
decreaseSpeed={hotkeyHandlers.decreaseSpeed}
|
||||
playerRef={playerRef}
|
||||
isFullScreen={isFullscreen}
|
||||
showControlsFullScreen={showControlsFullScreen}
|
||||
showControls={showControls}
|
||||
extractFrames={extractFrames}
|
||||
toggleFullscreen={toggleFullscreen}
|
||||
onVolumeChange={onVolumeChange}
|
||||
volume={volume}
|
||||
togglePlay={togglePlay}
|
||||
reloadVideo={hotkeyHandlers.reloadVideo}
|
||||
isPlaying={isPlaying}
|
||||
canPlay={true}
|
||||
isScreenSmall={false}
|
||||
controlsHeight={controlsHeight}
|
||||
duration={duration}
|
||||
progress={localProgress}
|
||||
openSubtitleManager={openSubtitleManager}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SubtitleManager subtitleBtnRef={subtitleBtnRef} close={closeSubtitleManager} open={isOpenSubtitleManage} qortalMetadata={qortalVideoResource} onSelect={onSelectSubtitle} />
|
||||
<SubtitleManager
|
||||
subtitleBtnRef={subtitleBtnRef}
|
||||
close={closeSubtitleManager}
|
||||
open={isOpenSubtitleManage}
|
||||
qortalMetadata={qortalVideoResource}
|
||||
onSelect={onSelectSubtitle}
|
||||
currentSubTrack={currentSubTrack}
|
||||
/>
|
||||
</VideoContainer>
|
||||
</>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user