mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-24 19:11:49 +01:00
implement more progres controls
This commit is contained in:
parent
c5a8065db9
commit
a0c24209bb
14
src/video/components/actions/LoadingAction.tsx
Normal file
14
src/video/components/actions/LoadingAction.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { Spinner } from "@/components/layout/Spinner";
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||
import { useMediaPlaying } from "@/video/state/logic/mediaplaying";
|
||||
|
||||
export function LoadingAction() {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
const mediaPlaying = useMediaPlaying(descriptor);
|
||||
|
||||
const isLoading = mediaPlaying.isFirstLoading || mediaPlaying.isLoading;
|
||||
|
||||
if (!isLoading) return null;
|
||||
|
||||
return <Spinner />;
|
||||
}
|
32
src/video/components/actions/MiddlePauseAction.tsx
Normal file
32
src/video/components/actions/MiddlePauseAction.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||
import { useControls } from "@/video/state/logic/controls";
|
||||
import { useMediaPlaying } from "@/video/state/logic/mediaplaying";
|
||||
import { useCallback } from "react";
|
||||
|
||||
export function MiddlePauseAction() {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
const controls = useControls(descriptor);
|
||||
const mediaPlaying = useMediaPlaying(descriptor);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (mediaPlaying?.isPlaying) controls.pause();
|
||||
else controls.play();
|
||||
}, [controls, mediaPlaying]);
|
||||
|
||||
if (mediaPlaying.hasPlayedOnce) return null;
|
||||
if (mediaPlaying.isPlaying) return null;
|
||||
if (mediaPlaying.isFirstLoading) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleClick}
|
||||
className="group pointer-events-auto flex h-16 w-16 items-center justify-center rounded-full bg-denim-400 text-white transition-[background-color,transform] hover:scale-125 hover:bg-denim-500 active:scale-100"
|
||||
>
|
||||
<Icon
|
||||
icon={Icons.PLAY}
|
||||
className="text-2xl transition-transform group-hover:scale-125"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
81
src/video/components/actions/ProgressAction.tsx
Normal file
81
src/video/components/actions/ProgressAction.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import {
|
||||
makePercentage,
|
||||
makePercentageString,
|
||||
useProgressBar,
|
||||
} from "@/hooks/useProgressBar";
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||
import { useControls } from "@/video/state/logic/controls";
|
||||
import { useProgress } from "@/video/state/logic/progress";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
|
||||
export function ProgressAction() {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
const controls = useControls(descriptor);
|
||||
const videoTime = useProgress(descriptor);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const dragRef = useRef<boolean>(false);
|
||||
|
||||
const commitTime = useCallback(
|
||||
(percentage) => {
|
||||
controls.setTime(percentage * videoTime.duration);
|
||||
},
|
||||
[controls, videoTime]
|
||||
);
|
||||
const { dragging, dragPercentage, dragMouseDown } = useProgressBar(
|
||||
ref,
|
||||
commitTime
|
||||
);
|
||||
|
||||
// TODO make dragging update timer
|
||||
useEffect(() => {
|
||||
if (dragRef.current === dragging) return;
|
||||
dragRef.current = dragging;
|
||||
controls.setSeeking(dragging);
|
||||
}, [dragRef, dragging, controls]);
|
||||
|
||||
let watchProgress = makePercentageString(
|
||||
makePercentage((videoTime.time / videoTime.duration) * 100)
|
||||
);
|
||||
if (dragging)
|
||||
watchProgress = makePercentageString(makePercentage(dragPercentage));
|
||||
|
||||
const bufferProgress = makePercentageString(
|
||||
makePercentage((videoTime.buffered / videoTime.duration) * 100)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="group pointer-events-auto w-full cursor-pointer rounded-full px-2">
|
||||
<div
|
||||
ref={ref}
|
||||
className="-my-3 flex h-8 items-center"
|
||||
onMouseDown={dragMouseDown}
|
||||
onTouchStart={dragMouseDown}
|
||||
>
|
||||
<div
|
||||
className={`relative h-1 flex-1 rounded-full bg-gray-500 bg-opacity-50 transition-[height] duration-100 group-hover:h-2 ${
|
||||
dragging ? "!h-2" : ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-y-0 left-0 flex items-center justify-end rounded-full bg-gray-300 bg-opacity-20"
|
||||
style={{
|
||||
width: bufferProgress,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="absolute inset-y-0 left-0 flex items-center justify-end rounded-full bg-bink-600"
|
||||
style={{
|
||||
width: watchProgress,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`absolute h-1 w-1 translate-x-1/2 rounded-full bg-white opacity-0 transition-[transform,opacity] group-hover:scale-[400%] group-hover:opacity-100 ${
|
||||
dragging ? "!scale-[400%] !opacity-100" : ""
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
48
src/video/components/actions/SkipTimeAction.tsx
Normal file
48
src/video/components/actions/SkipTimeAction.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { Icons } from "@/components/Icon";
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||
import { useControls } from "@/video/state/logic/controls";
|
||||
import { useProgress } from "@/video/state/logic/progress";
|
||||
import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function SkipTimeBackwardAction() {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
const controls = useControls(descriptor);
|
||||
const videoTime = useProgress(descriptor);
|
||||
|
||||
const skipBackward = () => {
|
||||
controls.setTime(videoTime.time - 10);
|
||||
};
|
||||
|
||||
return (
|
||||
<VideoPlayerIconButton icon={Icons.SKIP_BACKWARD} onClick={skipBackward} />
|
||||
);
|
||||
}
|
||||
|
||||
export function SkipTimeForwardAction() {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
const controls = useControls(descriptor);
|
||||
const videoTime = useProgress(descriptor);
|
||||
|
||||
const skipForward = () => {
|
||||
controls.setTime(videoTime.time + 10);
|
||||
};
|
||||
|
||||
return (
|
||||
<VideoPlayerIconButton icon={Icons.SKIP_FORWARD} onClick={skipForward} />
|
||||
);
|
||||
}
|
||||
|
||||
export function SkipTimeAction(props: Props) {
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<div className="flex select-none items-center text-white">
|
||||
<SkipTimeBackwardAction />
|
||||
<SkipTimeForwardAction />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
50
src/video/components/actions/TimeAction.tsx
Normal file
50
src/video/components/actions/TimeAction.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||
import { useProgress } from "@/video/state/logic/progress";
|
||||
|
||||
function durationExceedsHour(secs: number): boolean {
|
||||
return secs > 60 * 60;
|
||||
}
|
||||
|
||||
function formatSeconds(secs: number, showHours = false): string {
|
||||
if (Number.isNaN(secs)) {
|
||||
if (showHours) return "0:00:00";
|
||||
return "0:00";
|
||||
}
|
||||
|
||||
let time = secs;
|
||||
const seconds = Math.floor(time % 60);
|
||||
|
||||
time /= 60;
|
||||
const minutes = Math.floor(time % 60);
|
||||
|
||||
time /= 60;
|
||||
const hours = Math.floor(time);
|
||||
|
||||
const paddedSecs = seconds.toString().padStart(2, "0");
|
||||
const paddedMins = minutes.toString().padStart(2, "0");
|
||||
|
||||
if (!showHours) return [paddedMins, paddedSecs].join(":");
|
||||
return [hours, paddedMins, paddedSecs].join(":");
|
||||
}
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
noDuration?: boolean;
|
||||
}
|
||||
|
||||
export function TimeAction(props: Props) {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
const videoTime = useProgress(descriptor);
|
||||
|
||||
const hasHours = durationExceedsHour(videoTime.duration);
|
||||
const time = formatSeconds(videoTime.time, hasHours);
|
||||
const duration = formatSeconds(videoTime.duration, hasHours);
|
||||
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<p className="select-none text-white">
|
||||
{time} {props.noDuration ? "" : `/ ${duration}`}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
24
src/video/components/controllers/SourceController.tsx
Normal file
24
src/video/components/controllers/SourceController.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||
import { useControls } from "@/video/state/logic/controls";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
interface SourceControllerProps {
|
||||
source: string;
|
||||
type: MWStreamType;
|
||||
quality: MWStreamQuality;
|
||||
}
|
||||
|
||||
export function SourceController(props: SourceControllerProps) {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
const controls = useControls(descriptor);
|
||||
const didInitialize = useRef<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (didInitialize.current) return;
|
||||
controls.setSource(props);
|
||||
didInitialize.current = true;
|
||||
}, [props, controls]);
|
||||
|
||||
return null;
|
||||
}
|
@ -19,12 +19,6 @@ export function VideoElementInternal() {
|
||||
}, [descriptor]);
|
||||
|
||||
// TODO autoplay and muted
|
||||
return (
|
||||
<video
|
||||
ref={ref}
|
||||
playsInline
|
||||
className="h-full w-full"
|
||||
src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"
|
||||
/>
|
||||
);
|
||||
// this element is remotely controlled by a state provider
|
||||
return <video ref={ref} playsInline className="h-full w-full" />;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export type VideoPlayerEvent = "mediaplaying";
|
||||
export type VideoPlayerEvent = "mediaplaying" | "source" | "progress";
|
||||
|
||||
function createEventString(id: string, event: VideoPlayerEvent): string {
|
||||
return `_vid:::${id}:::${event}`;
|
||||
|
@ -25,6 +25,8 @@ function initPlayer(): VideoPlayerState {
|
||||
isSeries: false,
|
||||
},
|
||||
canAirplay: false,
|
||||
stateProvider: null,
|
||||
source: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -11,5 +11,14 @@ export function useControls(descriptor: string): VideoPlayerStateController {
|
||||
play() {
|
||||
state.stateProvider?.play();
|
||||
},
|
||||
setSource(source) {
|
||||
state.stateProvider?.setSource(source);
|
||||
},
|
||||
setSeeking(active) {
|
||||
state.stateProvider?.setSeeking(active);
|
||||
},
|
||||
setTime(time) {
|
||||
state.stateProvider?.setTime(time);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -7,7 +7,9 @@ export type VideoMediaPlayingEvent = {
|
||||
isPlaying: boolean;
|
||||
isPaused: boolean;
|
||||
isLoading: boolean;
|
||||
isSeeking: boolean;
|
||||
hasPlayedOnce: boolean;
|
||||
isFirstLoading: boolean;
|
||||
};
|
||||
|
||||
function getMediaPlayingFromState(
|
||||
@ -18,6 +20,8 @@ function getMediaPlayingFromState(
|
||||
isLoading: state.isLoading,
|
||||
isPaused: state.isPaused,
|
||||
isPlaying: state.isPlaying,
|
||||
isSeeking: state.isSeeking,
|
||||
isFirstLoading: state.isFirstLoading,
|
||||
};
|
||||
}
|
||||
|
||||
|
45
src/video/state/logic/progress.ts
Normal file
45
src/video/state/logic/progress.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { getPlayerState } from "../cache";
|
||||
import { listenEvent, sendEvent, unlistenEvent } from "../events";
|
||||
import { VideoPlayerState } from "../types";
|
||||
|
||||
export type VideoProgressEvent = {
|
||||
time: number;
|
||||
duration: number;
|
||||
buffered: number;
|
||||
};
|
||||
|
||||
function getProgressFromState(state: VideoPlayerState): VideoProgressEvent {
|
||||
return {
|
||||
time: state.time,
|
||||
duration: state.duration,
|
||||
buffered: state.buffered,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateProgress(descriptor: string, state: VideoPlayerState) {
|
||||
sendEvent<VideoProgressEvent>(
|
||||
descriptor,
|
||||
"progress",
|
||||
getProgressFromState(state)
|
||||
);
|
||||
}
|
||||
|
||||
export function useProgress(descriptor: string): VideoProgressEvent {
|
||||
const state = getPlayerState(descriptor);
|
||||
const [data, setData] = useState<VideoProgressEvent>(
|
||||
getProgressFromState(state)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
function update(payload: CustomEvent<VideoProgressEvent>) {
|
||||
setData(payload.detail);
|
||||
}
|
||||
listenEvent(descriptor, "progress", update);
|
||||
return () => {
|
||||
unlistenEvent(descriptor, "progress", update);
|
||||
};
|
||||
}, [descriptor]);
|
||||
|
||||
return data;
|
||||
}
|
40
src/video/state/logic/source.ts
Normal file
40
src/video/state/logic/source.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getPlayerState } from "../cache";
|
||||
import { listenEvent, sendEvent, unlistenEvent } from "../events";
|
||||
import { VideoPlayerState } from "../types";
|
||||
|
||||
export type VideoSourceEvent = {
|
||||
source: null | {
|
||||
quality: MWStreamQuality;
|
||||
url: string;
|
||||
type: MWStreamType;
|
||||
};
|
||||
};
|
||||
|
||||
function getSourceFromState(state: VideoPlayerState): VideoSourceEvent {
|
||||
return {
|
||||
source: state.source ? { ...state.source } : null,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateSource(descriptor: string, state: VideoPlayerState) {
|
||||
sendEvent<VideoSourceEvent>(descriptor, "source", getSourceFromState(state));
|
||||
}
|
||||
|
||||
export function useSource(descriptor: string): VideoSourceEvent {
|
||||
const state = getPlayerState(descriptor);
|
||||
const [data, setData] = useState<VideoSourceEvent>(getSourceFromState(state));
|
||||
|
||||
useEffect(() => {
|
||||
function update(payload: CustomEvent<VideoSourceEvent>) {
|
||||
setData(payload.detail);
|
||||
}
|
||||
listenEvent(descriptor, "source", update);
|
||||
return () => {
|
||||
unlistenEvent(descriptor, "source", update);
|
||||
};
|
||||
}, [descriptor]);
|
||||
|
||||
return data;
|
||||
}
|
@ -1,6 +1,17 @@
|
||||
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
|
||||
|
||||
type VideoPlayerSource = {
|
||||
source: string;
|
||||
type: MWStreamType;
|
||||
quality: MWStreamQuality;
|
||||
} | null;
|
||||
|
||||
export type VideoPlayerStateController = {
|
||||
pause: () => void;
|
||||
play: () => void;
|
||||
setSource: (source: VideoPlayerSource) => void;
|
||||
setTime(time: number): void;
|
||||
setSeeking(active: boolean): void;
|
||||
};
|
||||
|
||||
export type VideoPlayerStateProvider = VideoPlayerStateController & {
|
||||
|
@ -16,3 +16,12 @@ export function unsetStateProvider(descriptor: string) {
|
||||
const state = getPlayerState(descriptor);
|
||||
state.stateProvider = null;
|
||||
}
|
||||
|
||||
export function handleBuffered(time: number, buffered: TimeRanges): number {
|
||||
for (let i = 0; i < buffered.length; i += 1) {
|
||||
if (buffered.start(buffered.length - 1 - i) < time) {
|
||||
return buffered.end(buffered.length - 1 - i);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,11 +1,16 @@
|
||||
import Hls from "hls.js";
|
||||
import { MWStreamType } from "@/backend/helpers/streams";
|
||||
import { getPlayerState } from "../cache";
|
||||
import { updateMediaPlaying } from "../logic/mediaplaying";
|
||||
import { VideoPlayerStateProvider } from "./providerTypes";
|
||||
import { updateProgress } from "../logic/progress";
|
||||
import { handleBuffered } from "./utils";
|
||||
|
||||
export function createVideoStateProvider(
|
||||
descriptor: string,
|
||||
player: HTMLVideoElement
|
||||
playerEl: HTMLVideoElement
|
||||
): VideoPlayerStateProvider {
|
||||
const player = playerEl;
|
||||
const state = getPlayerState(descriptor);
|
||||
|
||||
return {
|
||||
@ -15,7 +20,72 @@ export function createVideoStateProvider(
|
||||
pause() {
|
||||
player.pause();
|
||||
},
|
||||
setTime(t) {
|
||||
// clamp time between 0 and max duration
|
||||
let time = Math.min(t, player.duration);
|
||||
time = Math.max(0, time);
|
||||
|
||||
if (Number.isNaN(time)) return;
|
||||
|
||||
// update state
|
||||
player.currentTime = time;
|
||||
state.time = time;
|
||||
updateProgress(descriptor, state);
|
||||
},
|
||||
setSeeking(active) {
|
||||
// if it was playing when starting to seek, play again
|
||||
if (!active) {
|
||||
if (!state.pausedWhenSeeking) this.play();
|
||||
return;
|
||||
}
|
||||
|
||||
// when seeking we pause the video
|
||||
// this variables isnt reactive, just used so the state can be remembered next unseek
|
||||
state.pausedWhenSeeking = state.isPaused;
|
||||
this.pause();
|
||||
},
|
||||
setSource(source) {
|
||||
if (!source) {
|
||||
player.src = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (source?.type === MWStreamType.HLS) {
|
||||
if (player.canPlayType("application/vnd.apple.mpegurl")) {
|
||||
player.src = source.source;
|
||||
} else {
|
||||
// HLS support
|
||||
if (!Hls.isSupported()) {
|
||||
state.error = {
|
||||
name: `Not supported`,
|
||||
description: "Your browser does not support HLS video",
|
||||
};
|
||||
// TODO dispatch error
|
||||
return;
|
||||
}
|
||||
|
||||
const hls = new Hls({ enableWorker: false });
|
||||
|
||||
hls.on(Hls.Events.ERROR, (event, data) => {
|
||||
if (data.fatal) {
|
||||
state.error = {
|
||||
name: `error ${data.details}`,
|
||||
description: data.error?.message ?? "Something went wrong",
|
||||
};
|
||||
// TODO dispatch error
|
||||
}
|
||||
console.error("HLS error", data);
|
||||
});
|
||||
|
||||
hls.attachMedia(player);
|
||||
hls.loadSource(source.source);
|
||||
}
|
||||
} else if (source.type === MWStreamType.MP4) {
|
||||
player.src = source.source;
|
||||
}
|
||||
},
|
||||
providerStart() {
|
||||
// TODO stored volume
|
||||
const pause = () => {
|
||||
state.isPaused = true;
|
||||
state.isPlaying = false;
|
||||
@ -28,13 +98,56 @@ export function createVideoStateProvider(
|
||||
state.hasPlayedOnce = true;
|
||||
updateMediaPlaying(descriptor, state);
|
||||
};
|
||||
const waiting = () => {
|
||||
state.isLoading = true;
|
||||
updateMediaPlaying(descriptor, state);
|
||||
};
|
||||
const seeking = () => {
|
||||
state.isSeeking = true;
|
||||
updateMediaPlaying(descriptor, state);
|
||||
};
|
||||
const seeked = () => {
|
||||
state.isSeeking = false;
|
||||
updateMediaPlaying(descriptor, state);
|
||||
};
|
||||
const loadedmetadata = () => {
|
||||
state.duration = player.duration;
|
||||
updateProgress(descriptor, state);
|
||||
};
|
||||
const timeupdate = () => {
|
||||
state.duration = player.duration;
|
||||
state.time = player.currentTime;
|
||||
updateProgress(descriptor, state);
|
||||
};
|
||||
const progress = () => {
|
||||
state.buffered = handleBuffered(player.currentTime, player.buffered);
|
||||
updateProgress(descriptor, state);
|
||||
};
|
||||
const canplay = () => {
|
||||
state.isFirstLoading = false;
|
||||
updateMediaPlaying(descriptor, state);
|
||||
};
|
||||
|
||||
player.addEventListener("pause", pause);
|
||||
player.addEventListener("playing", playing);
|
||||
player.addEventListener("seeking", seeking);
|
||||
player.addEventListener("seeked", seeked);
|
||||
player.addEventListener("progress", progress);
|
||||
player.addEventListener("waiting", waiting);
|
||||
player.addEventListener("timeupdate", timeupdate);
|
||||
player.addEventListener("loadedmetadata", loadedmetadata);
|
||||
player.addEventListener("canplay", canplay);
|
||||
return {
|
||||
destroy: () => {
|
||||
player.removeEventListener("pause", pause);
|
||||
player.removeEventListener("playing", playing);
|
||||
player.removeEventListener("seeking", seeking);
|
||||
player.removeEventListener("seeked", seeked);
|
||||
player.removeEventListener("timeupdate", timeupdate);
|
||||
player.removeEventListener("loadedmetadata", loadedmetadata);
|
||||
player.removeEventListener("progress", progress);
|
||||
player.removeEventListener("waiting", waiting);
|
||||
player.removeEventListener("canplay", canplay);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
|
||||
import { VideoPlayerStateProvider } from "./providers/providerTypes";
|
||||
|
||||
export type VideoPlayerState = {
|
||||
@ -30,10 +31,16 @@ export type VideoPlayerState = {
|
||||
episodes?: { id: string; number: number; title: string }[];
|
||||
}[];
|
||||
};
|
||||
|
||||
error: null | {
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
canAirplay: boolean;
|
||||
stateProvider: VideoPlayerStateProvider | null;
|
||||
source: null | {
|
||||
quality: MWStreamQuality;
|
||||
url: string;
|
||||
type: MWStreamType;
|
||||
};
|
||||
};
|
||||
|
@ -4,7 +4,14 @@
|
||||
// } from "@/hooks/useChromecastAvailable";
|
||||
// import { useEffect, useRef } from "react";
|
||||
|
||||
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
|
||||
import { LoadingAction } from "@/video/components/actions/LoadingAction";
|
||||
import { MiddlePauseAction } from "@/video/components/actions/MiddlePauseAction";
|
||||
import { PauseAction } from "@/video/components/actions/PauseAction";
|
||||
import { ProgressAction } from "@/video/components/actions/ProgressAction";
|
||||
import { SkipTimeAction } from "@/video/components/actions/SkipTimeAction";
|
||||
import { TimeAction } from "@/video/components/actions/TimeAction";
|
||||
import { SourceController } from "@/video/components/controllers/SourceController";
|
||||
import { VideoPlayerBase } from "@/video/components/VideoPlayerBase";
|
||||
|
||||
// function ChromeCastButton() {
|
||||
@ -25,6 +32,16 @@ export function TestView() {
|
||||
return (
|
||||
<VideoPlayerBase>
|
||||
<PauseAction />
|
||||
<SourceController
|
||||
quality={MWStreamQuality.QUNKNOWN}
|
||||
source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"
|
||||
type={MWStreamType.MP4}
|
||||
/>
|
||||
<MiddlePauseAction />
|
||||
<ProgressAction />
|
||||
<LoadingAction />
|
||||
<TimeAction />
|
||||
<SkipTimeAction />
|
||||
</VideoPlayerBase>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user