mirror of
https://github.com/sussy-code/smov.git
synced 2025-01-01 16:37:39 +01:00
volume control + progress listener
This commit is contained in:
parent
bb14d63a9c
commit
c3b409631e
15 changed files with 276 additions and 24 deletions
|
@ -1,16 +1,18 @@
|
||||||
import { useVideoPlayerState } from "@/../__old/VideoContext";
|
import { useControls } from "@/video/state/logic/controls";
|
||||||
|
import { useMediaPlaying } from "@/video/state/logic/mediaplaying";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export function useVolumeControl() {
|
export function useVolumeControl(descriptor: string) {
|
||||||
const [storedVolume, setStoredVolume] = useState(1);
|
const [storedVolume, setStoredVolume] = useState(1);
|
||||||
const { videoState } = useVideoPlayerState();
|
const controls = useControls(descriptor);
|
||||||
|
const mediaPlaying = useMediaPlaying(descriptor);
|
||||||
|
|
||||||
const toggleVolume = () => {
|
const toggleVolume = () => {
|
||||||
if (videoState.volume > 0) {
|
if (mediaPlaying.volume > 0) {
|
||||||
setStoredVolume(videoState.volume);
|
setStoredVolume(mediaPlaying.volume);
|
||||||
videoState.setVolume(0);
|
controls.setVolume(0);
|
||||||
} else {
|
} else {
|
||||||
videoState.setVolume(storedVolume > 0 ? storedVolume : 1);
|
controls.setVolume(storedVolume > 0 ? storedVolume : 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ if (key) {
|
||||||
initializeChromecast();
|
initializeChromecast();
|
||||||
|
|
||||||
// TODO video todos:
|
// TODO video todos:
|
||||||
|
// - mobile controls start showing when resizing
|
||||||
// - captions
|
// - captions
|
||||||
// - chrome cast support
|
// - chrome cast support
|
||||||
// - safari fullscreen will make video overlap player controls
|
// - safari fullscreen will make video overlap player controls
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { QualityDisplayAction } from "@/video/components/actions/QualityDisplayA
|
||||||
import { ShowTitleAction } from "@/video/components/actions/ShowTitleAction";
|
import { ShowTitleAction } from "@/video/components/actions/ShowTitleAction";
|
||||||
import { SkipTimeAction } from "@/video/components/actions/SkipTimeAction";
|
import { SkipTimeAction } from "@/video/components/actions/SkipTimeAction";
|
||||||
import { TimeAction } from "@/video/components/actions/TimeAction";
|
import { TimeAction } from "@/video/components/actions/TimeAction";
|
||||||
|
import { VolumeAction } from "@/video/components/actions/VolumeAction";
|
||||||
import { VideoPlayerError } from "@/video/components/parts/VideoPlayerError";
|
import { VideoPlayerError } from "@/video/components/parts/VideoPlayerError";
|
||||||
import {
|
import {
|
||||||
VideoPlayerBase,
|
VideoPlayerBase,
|
||||||
|
@ -54,7 +55,7 @@ function LeftSideControls() {
|
||||||
>
|
>
|
||||||
<PauseAction />
|
<PauseAction />
|
||||||
<SkipTimeAction />
|
<SkipTimeAction />
|
||||||
{/* <VolumeControl className="mr-2" /> */}
|
<VolumeAction className="mr-2" />
|
||||||
<TimeAction />
|
<TimeAction />
|
||||||
</div>
|
</div>
|
||||||
<ShowTitleAction />
|
<ShowTitleAction />
|
||||||
|
@ -73,10 +74,9 @@ export function VideoPlayer(props: Props) {
|
||||||
[setShow]
|
[setShow]
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO autoplay
|
|
||||||
// TODO safe area only if full screen or fill screen
|
// TODO safe area only if full screen or fill screen
|
||||||
return (
|
return (
|
||||||
<VideoPlayerBase>
|
<VideoPlayerBase autoPlay={props.autoPlay}>
|
||||||
<PageTitleAction />
|
<PageTitleAction />
|
||||||
<VideoPlayerError onGoBack={props.onGoBack}>
|
<VideoPlayerError onGoBack={props.onGoBack}>
|
||||||
<BackdropAction onBackdropChange={onBackdropChange}>
|
<BackdropAction onBackdropChange={onBackdropChange}>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { VideoElementInternal } from "./internal/VideoElementInternal";
|
||||||
|
|
||||||
export interface VideoPlayerBaseProps {
|
export interface VideoPlayerBaseProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
autoPlay?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VideoPlayerBase(props: VideoPlayerBaseProps) {
|
export function VideoPlayerBase(props: VideoPlayerBaseProps) {
|
||||||
|
@ -19,7 +20,7 @@ export function VideoPlayerBase(props: VideoPlayerBaseProps) {
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="is-video-player relative h-full w-full select-none overflow-hidden bg-black [border-left:env(safe-area-inset-left)_solid_transparent] [border-right:env(safe-area-inset-right)_solid_transparent]"
|
className="is-video-player relative h-full w-full select-none overflow-hidden bg-black [border-left:env(safe-area-inset-left)_solid_transparent] [border-right:env(safe-area-inset-right)_solid_transparent]"
|
||||||
>
|
>
|
||||||
<VideoElementInternal />
|
<VideoElementInternal autoPlay={props.autoPlay} />
|
||||||
<WrapperRegisterInternal wrapper={ref.current} />
|
<WrapperRegisterInternal wrapper={ref.current} />
|
||||||
<div className="absolute inset-0">{props.children}</div>
|
<div className="absolute inset-0">{props.children}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
92
src/video/components/actions/VolumeAction.tsx
Normal file
92
src/video/components/actions/VolumeAction.tsx
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
|
import {
|
||||||
|
makePercentage,
|
||||||
|
makePercentageString,
|
||||||
|
useProgressBar,
|
||||||
|
} from "@/hooks/useProgressBar";
|
||||||
|
import { useVolumeControl } from "@/hooks/useVolumeToggle";
|
||||||
|
import { canChangeVolume } from "@/utils/detectFeatures";
|
||||||
|
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
|
import { useControls } from "@/video/state/logic/controls";
|
||||||
|
import { useInterface } from "@/video/state/logic/interface";
|
||||||
|
import { useMediaPlaying } from "@/video/state/logic/mediaplaying";
|
||||||
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VolumeAction(props: Props) {
|
||||||
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
|
const controls = useControls(descriptor);
|
||||||
|
const mediaPlaying = useMediaPlaying(descriptor);
|
||||||
|
const videoInterface = useInterface(descriptor);
|
||||||
|
const { setStoredVolume, toggleVolume } = useVolumeControl(descriptor);
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const [hoveredOnce, setHoveredOnce] = useState(false);
|
||||||
|
|
||||||
|
const commitVolume = useCallback(
|
||||||
|
(percentage) => {
|
||||||
|
controls.setVolume(percentage);
|
||||||
|
setStoredVolume(percentage);
|
||||||
|
},
|
||||||
|
[controls, setStoredVolume]
|
||||||
|
);
|
||||||
|
const { dragging, dragPercentage, dragMouseDown } = useProgressBar(
|
||||||
|
ref,
|
||||||
|
commitVolume,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!videoInterface.leftControlHovering) setHoveredOnce(false);
|
||||||
|
}, [videoInterface]);
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
toggleVolume();
|
||||||
|
}, [toggleVolume]);
|
||||||
|
|
||||||
|
const handleMouseEnter = useCallback(async () => {
|
||||||
|
if (await canChangeVolume()) setHoveredOnce(true);
|
||||||
|
}, [setHoveredOnce]);
|
||||||
|
|
||||||
|
let percentage = makePercentage(mediaPlaying.volume * 100);
|
||||||
|
if (dragging) percentage = makePercentage(dragPercentage);
|
||||||
|
const percentageString = makePercentageString(percentage);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={props.className}>
|
||||||
|
<div
|
||||||
|
className="pointer-events-auto flex cursor-pointer items-center"
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
>
|
||||||
|
<div className="px-4 text-2xl text-white" onClick={handleClick}>
|
||||||
|
<Icon icon={percentage > 0 ? Icons.VOLUME : Icons.VOLUME_X} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`linear -ml-2 w-0 overflow-hidden transition-[width,opacity] duration-300 ${
|
||||||
|
hoveredOnce || dragging ? "!w-24 opacity-100" : "w-4 opacity-0"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className="flex h-10 w-20 items-center px-2"
|
||||||
|
onMouseDown={dragMouseDown}
|
||||||
|
onTouchStart={dragMouseDown}
|
||||||
|
>
|
||||||
|
<div className="relative h-1 flex-1 rounded-full bg-gray-500 bg-opacity-50">
|
||||||
|
<div
|
||||||
|
className="absolute inset-y-0 left-0 flex items-center justify-end rounded-full bg-bink-500"
|
||||||
|
style={{
|
||||||
|
width: percentageString,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="absolute h-3 w-3 translate-x-1/2 rounded-full bg-white" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
|
import throttle from "lodash.throttle";
|
||||||
|
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
|
import { useMediaPlaying } from "@/video/state/logic/mediaplaying";
|
||||||
|
import { useProgress } from "@/video/state/logic/progress";
|
||||||
|
import { useControls } from "@/video/state/logic/controls";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
startAt?: number;
|
||||||
|
onProgress?: (time: number, duration: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProgressListenerController(props: Props) {
|
||||||
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
|
const mediaPlaying = useMediaPlaying(descriptor);
|
||||||
|
const progress = useProgress(descriptor);
|
||||||
|
const controls = useControls(descriptor);
|
||||||
|
const didInitialize = useRef<true | null>(null);
|
||||||
|
|
||||||
|
// time updates (throttled)
|
||||||
|
const updateTime = useMemo(
|
||||||
|
() => throttle((a: number, b: number) => props.onProgress?.(a, b), 1000),
|
||||||
|
[props]
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!mediaPlaying.isPlaying) return;
|
||||||
|
if (progress.duration === 0 || progress.time === 0) return;
|
||||||
|
updateTime(progress.time, progress.duration);
|
||||||
|
}, [progress, mediaPlaying, updateTime]);
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
updateTime.cancel();
|
||||||
|
};
|
||||||
|
}, [updateTime]);
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
useEffect(() => {
|
||||||
|
if (didInitialize.current) return;
|
||||||
|
if (mediaPlaying.isFirstLoading || Number.isNaN(progress.duration)) return;
|
||||||
|
if (props.startAt !== undefined) {
|
||||||
|
controls.setTime(props.startAt);
|
||||||
|
}
|
||||||
|
didInitialize.current = true;
|
||||||
|
}, [didInitialize, props, progress, mediaPlaying, controls]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
25
src/video/components/hooks/volumeStore.ts
Normal file
25
src/video/components/hooks/volumeStore.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { versionedStoreBuilder } from "@/utils/storage";
|
||||||
|
|
||||||
|
export const volumeStore = versionedStoreBuilder()
|
||||||
|
.setKey("mw-volume")
|
||||||
|
.addVersion({
|
||||||
|
version: 0,
|
||||||
|
create() {
|
||||||
|
return {
|
||||||
|
volume: 1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
export function getStoredVolume(): number {
|
||||||
|
const store = volumeStore.get();
|
||||||
|
return store.volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setStoredVolume(volume: number) {
|
||||||
|
const store = volumeStore.get();
|
||||||
|
store.save({
|
||||||
|
volume,
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,10 +1,16 @@
|
||||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
|
import { useMediaPlaying } from "@/video/state/logic/mediaplaying";
|
||||||
import { setProvider, unsetStateProvider } from "@/video/state/providers/utils";
|
import { setProvider, unsetStateProvider } from "@/video/state/providers/utils";
|
||||||
import { createVideoStateProvider } from "@/video/state/providers/videoStateProvider";
|
import { createVideoStateProvider } from "@/video/state/providers/videoStateProvider";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
export function VideoElementInternal() {
|
interface Props {
|
||||||
|
autoPlay?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VideoElementInternal(props: Props) {
|
||||||
const descriptor = useVideoPlayerDescriptor();
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
|
const mediaPlaying = useMediaPlaying(descriptor);
|
||||||
const ref = useRef<HTMLVideoElement>(null);
|
const ref = useRef<HTMLVideoElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -18,7 +24,16 @@ export function VideoElementInternal() {
|
||||||
};
|
};
|
||||||
}, [descriptor]);
|
}, [descriptor]);
|
||||||
|
|
||||||
// TODO autoplay and muted
|
// TODO shortcuts
|
||||||
|
|
||||||
// this element is remotely controlled by a state provider
|
// this element is remotely controlled by a state provider
|
||||||
return <video ref={ref} playsInline className="h-full w-full" />;
|
return (
|
||||||
|
<video
|
||||||
|
ref={ref}
|
||||||
|
autoPlay={props.autoPlay}
|
||||||
|
muted={mediaPlaying.volume === 0}
|
||||||
|
playsInline
|
||||||
|
className="h-full w-full"
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ function initPlayer(): VideoPlayerState {
|
||||||
isSeeking: false,
|
isSeeking: false,
|
||||||
isFirstLoading: true,
|
isFirstLoading: true,
|
||||||
hasPlayedOnce: false,
|
hasPlayedOnce: false,
|
||||||
|
volume: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
progress: {
|
progress: {
|
||||||
|
@ -30,9 +31,7 @@ function initPlayer(): VideoPlayerState {
|
||||||
source: null,
|
source: null,
|
||||||
error: null,
|
error: null,
|
||||||
|
|
||||||
volume: 0,
|
|
||||||
pausedWhenSeeking: false,
|
pausedWhenSeeking: false,
|
||||||
hasInitialized: false,
|
|
||||||
canAirplay: false,
|
canAirplay: false,
|
||||||
|
|
||||||
stateProvider: null,
|
stateProvider: null,
|
||||||
|
|
|
@ -40,6 +40,9 @@ export function useControls(
|
||||||
enterFullscreen() {
|
enterFullscreen() {
|
||||||
state.stateProvider?.enterFullscreen();
|
state.stateProvider?.enterFullscreen();
|
||||||
},
|
},
|
||||||
|
setVolume(volume) {
|
||||||
|
state.stateProvider?.setVolume(volume);
|
||||||
|
},
|
||||||
|
|
||||||
// other controls
|
// other controls
|
||||||
setLeftControlsHover(hovering) {
|
setLeftControlsHover(hovering) {
|
||||||
|
|
|
@ -10,6 +10,7 @@ export type VideoMediaPlayingEvent = {
|
||||||
isSeeking: boolean;
|
isSeeking: boolean;
|
||||||
hasPlayedOnce: boolean;
|
hasPlayedOnce: boolean;
|
||||||
isFirstLoading: boolean;
|
isFirstLoading: boolean;
|
||||||
|
volume: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getMediaPlayingFromState(
|
function getMediaPlayingFromState(
|
||||||
|
@ -22,6 +23,7 @@ function getMediaPlayingFromState(
|
||||||
isPlaying: state.mediaPlaying.isPlaying,
|
isPlaying: state.mediaPlaying.isPlaying,
|
||||||
isSeeking: state.mediaPlaying.isSeeking,
|
isSeeking: state.mediaPlaying.isSeeking,
|
||||||
isFirstLoading: state.mediaPlaying.isFirstLoading,
|
isFirstLoading: state.mediaPlaying.isFirstLoading,
|
||||||
|
volume: state.mediaPlaying.volume,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ export type VideoPlayerStateController = {
|
||||||
setSeeking(active: boolean): void;
|
setSeeking(active: boolean): void;
|
||||||
exitFullscreen(): void;
|
exitFullscreen(): void;
|
||||||
enterFullscreen(): void;
|
enterFullscreen(): void;
|
||||||
|
setVolume(volume: number): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type VideoPlayerStateProvider = VideoPlayerStateController & {
|
export type VideoPlayerStateProvider = VideoPlayerStateController & {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
import fscreen from "fscreen";
|
import fscreen from "fscreen";
|
||||||
import {
|
import {
|
||||||
|
canChangeVolume,
|
||||||
canFullscreen,
|
canFullscreen,
|
||||||
canFullscreenAnyElement,
|
canFullscreenAnyElement,
|
||||||
canWebkitFullscreen,
|
canWebkitFullscreen,
|
||||||
|
@ -8,6 +9,10 @@ import {
|
||||||
import { MWStreamType } from "@/backend/helpers/streams";
|
import { MWStreamType } from "@/backend/helpers/streams";
|
||||||
import { updateInterface } from "@/video/state/logic/interface";
|
import { updateInterface } from "@/video/state/logic/interface";
|
||||||
import { updateSource } from "@/video/state/logic/source";
|
import { updateSource } from "@/video/state/logic/source";
|
||||||
|
import {
|
||||||
|
getStoredVolume,
|
||||||
|
setStoredVolume,
|
||||||
|
} from "@/video/components/hooks/volumeStore";
|
||||||
import { getPlayerState } from "../cache";
|
import { getPlayerState } from "../cache";
|
||||||
import { updateMediaPlaying } from "../logic/mediaplaying";
|
import { updateMediaPlaying } from "../logic/mediaplaying";
|
||||||
import { VideoPlayerStateProvider } from "./providerTypes";
|
import { VideoPlayerStateProvider } from "./providerTypes";
|
||||||
|
@ -67,6 +72,19 @@ export function createVideoStateProvider(
|
||||||
state.pausedWhenSeeking = state.mediaPlaying.isPaused;
|
state.pausedWhenSeeking = state.mediaPlaying.isPaused;
|
||||||
this.pause();
|
this.pause();
|
||||||
},
|
},
|
||||||
|
async setVolume(v) {
|
||||||
|
// clamp time between 0 and 1
|
||||||
|
let volume = Math.min(v, 1);
|
||||||
|
volume = Math.max(0, volume);
|
||||||
|
|
||||||
|
// update state
|
||||||
|
if (await canChangeVolume()) player.volume = volume;
|
||||||
|
state.mediaPlaying.volume = volume;
|
||||||
|
updateMediaPlaying(descriptor, state);
|
||||||
|
|
||||||
|
// update localstorage
|
||||||
|
setStoredVolume(volume);
|
||||||
|
},
|
||||||
setSource(source) {
|
setSource(source) {
|
||||||
if (!source) {
|
if (!source) {
|
||||||
player.src = "";
|
player.src = "";
|
||||||
|
@ -118,7 +136,8 @@ export function createVideoStateProvider(
|
||||||
updateSource(descriptor, state);
|
updateSource(descriptor, state);
|
||||||
},
|
},
|
||||||
providerStart() {
|
providerStart() {
|
||||||
// TODO stored volume
|
this.setVolume(getStoredVolume());
|
||||||
|
|
||||||
const pause = () => {
|
const pause = () => {
|
||||||
state.mediaPlaying.isPaused = true;
|
state.mediaPlaying.isPaused = true;
|
||||||
state.mediaPlaying.isPlaying = false;
|
state.mediaPlaying.isPlaying = false;
|
||||||
|
@ -167,7 +186,37 @@ export function createVideoStateProvider(
|
||||||
state.interface.isFullscreen = !!document.fullscreenElement;
|
state.interface.isFullscreen = !!document.fullscreenElement;
|
||||||
updateInterface(descriptor, state);
|
updateInterface(descriptor, state);
|
||||||
};
|
};
|
||||||
|
const volumechange = async () => {
|
||||||
|
if (await canChangeVolume()) {
|
||||||
|
state.mediaPlaying.volume = player.volume;
|
||||||
|
updateMediaPlaying(descriptor, state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const isFocused = (evt: any) => {
|
||||||
|
state.interface.isFocused = evt.type !== "mouseleave";
|
||||||
|
updateInterface(descriptor, state);
|
||||||
|
};
|
||||||
|
const canAirplay = (e: any) => {
|
||||||
|
if (e.availability === "available") {
|
||||||
|
state.canAirplay = true;
|
||||||
|
// TODO dispatch airplay
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const error = () => {
|
||||||
|
console.error("Native video player threw error", player.error);
|
||||||
|
state.error = player.error
|
||||||
|
? {
|
||||||
|
description: player.error.message,
|
||||||
|
name: `Error ${player.error.code}`,
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
// TODO dispatch error
|
||||||
|
};
|
||||||
|
|
||||||
|
state.wrapperElement?.addEventListener("click", isFocused);
|
||||||
|
state.wrapperElement?.addEventListener("mouseenter", isFocused);
|
||||||
|
state.wrapperElement?.addEventListener("mouseleave", isFocused);
|
||||||
|
player.addEventListener("volumechange", volumechange);
|
||||||
player.addEventListener("pause", pause);
|
player.addEventListener("pause", pause);
|
||||||
player.addEventListener("playing", playing);
|
player.addEventListener("playing", playing);
|
||||||
player.addEventListener("seeking", seeking);
|
player.addEventListener("seeking", seeking);
|
||||||
|
@ -178,18 +227,33 @@ export function createVideoStateProvider(
|
||||||
player.addEventListener("loadedmetadata", loadedmetadata);
|
player.addEventListener("loadedmetadata", loadedmetadata);
|
||||||
player.addEventListener("canplay", canplay);
|
player.addEventListener("canplay", canplay);
|
||||||
fscreen.addEventListener("fullscreenchange", fullscreenchange);
|
fscreen.addEventListener("fullscreenchange", fullscreenchange);
|
||||||
|
player.addEventListener("error", error);
|
||||||
|
player.addEventListener(
|
||||||
|
"webkitplaybacktargetavailabilitychanged",
|
||||||
|
canAirplay
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
destroy: () => {
|
destroy: () => {
|
||||||
player.removeEventListener("pause", pause);
|
player.removeEventListener("pause", pause);
|
||||||
player.removeEventListener("playing", playing);
|
player.removeEventListener("playing", playing);
|
||||||
player.removeEventListener("seeking", seeking);
|
player.removeEventListener("seeking", seeking);
|
||||||
|
player.removeEventListener("volumechange", volumechange);
|
||||||
player.removeEventListener("seeked", seeked);
|
player.removeEventListener("seeked", seeked);
|
||||||
player.removeEventListener("timeupdate", timeupdate);
|
player.removeEventListener("timeupdate", timeupdate);
|
||||||
player.removeEventListener("loadedmetadata", loadedmetadata);
|
player.removeEventListener("loadedmetadata", loadedmetadata);
|
||||||
player.removeEventListener("progress", progress);
|
player.removeEventListener("progress", progress);
|
||||||
player.removeEventListener("waiting", waiting);
|
player.removeEventListener("waiting", waiting);
|
||||||
|
player.removeEventListener("error", error);
|
||||||
player.removeEventListener("canplay", canplay);
|
player.removeEventListener("canplay", canplay);
|
||||||
fscreen.removeEventListener("fullscreenchange", fullscreenchange);
|
fscreen.removeEventListener("fullscreenchange", fullscreenchange);
|
||||||
|
state.wrapperElement?.removeEventListener("click", isFocused);
|
||||||
|
state.wrapperElement?.removeEventListener("mouseenter", isFocused);
|
||||||
|
state.wrapperElement?.removeEventListener("mouseleave", isFocused);
|
||||||
|
player.removeEventListener(
|
||||||
|
"webkitplaybacktargetavailabilitychanged",
|
||||||
|
canAirplay
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -31,8 +31,9 @@ export type VideoPlayerState = {
|
||||||
isPaused: boolean;
|
isPaused: boolean;
|
||||||
isSeeking: boolean; // seeking with progress bar
|
isSeeking: boolean; // seeking with progress bar
|
||||||
isLoading: boolean; // buffering or not
|
isLoading: boolean; // buffering or not
|
||||||
isFirstLoading: boolean; // first buffering of the video, used to show
|
isFirstLoading: boolean; // first buffering of the video, when set to false the video can start playing
|
||||||
hasPlayedOnce: boolean; // has the video played at all?
|
hasPlayedOnce: boolean; // has the video played at all?
|
||||||
|
volume: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
// state related to video progress
|
// state related to video progress
|
||||||
|
@ -55,9 +56,7 @@ export type VideoPlayerState = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// misc
|
// misc
|
||||||
volume: number;
|
pausedWhenSeeking: boolean; // when seeking, used to store if paused when started to seek
|
||||||
pausedWhenSeeking: boolean;
|
|
||||||
hasInitialized: boolean;
|
|
||||||
canAirplay: boolean;
|
canAirplay: boolean;
|
||||||
|
|
||||||
// backing fields
|
// backing fields
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { MetaController } from "@/video/components/controllers/MetaController";
|
||||||
import { SourceController } from "@/video/components/controllers/SourceController";
|
import { SourceController } from "@/video/components/controllers/SourceController";
|
||||||
import { Icons } from "@/components/Icon";
|
import { Icons } from "@/components/Icon";
|
||||||
import { VideoPlayerHeader } from "@/video/components/parts/VideoPlayerHeader";
|
import { VideoPlayerHeader } from "@/video/components/parts/VideoPlayerHeader";
|
||||||
|
import { ProgressListenerController } from "@/video/components/controllers/ProgressListenerController";
|
||||||
import { useWatchedItem } from "@/state/watched";
|
import { useWatchedItem } from "@/state/watched";
|
||||||
import { MediaFetchErrorView } from "./MediaErrorView";
|
import { MediaFetchErrorView } from "./MediaErrorView";
|
||||||
import { MediaScrapeLog } from "./MediaScrapeLog";
|
import { MediaScrapeLog } from "./MediaScrapeLog";
|
||||||
|
@ -112,17 +113,17 @@ export function MediaViewPlayer(props: MediaViewPlayerProps) {
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<html data-full="true" />
|
<html data-full="true" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<VideoPlayer onGoBack={goBack}>
|
<VideoPlayer autoPlay onGoBack={goBack}>
|
||||||
<MetaController meta={props.meta.meta} />
|
<MetaController meta={props.meta.meta} />
|
||||||
<SourceController
|
<SourceController
|
||||||
source={props.stream.streamUrl}
|
source={props.stream.streamUrl}
|
||||||
type={props.stream.type}
|
type={props.stream.type}
|
||||||
quality={props.stream.quality}
|
quality={props.stream.quality}
|
||||||
/>
|
/>
|
||||||
{/* <ProgressListenerControl
|
<ProgressListenerController
|
||||||
startAt={firstStartTime.current}
|
startAt={firstStartTime.current}
|
||||||
onProgress={updateProgress}
|
onProgress={updateProgress}
|
||||||
/> */}
|
/>
|
||||||
{/* {props.selected.type === MWMediaType.SERIES &&
|
{/* {props.selected.type === MWMediaType.SERIES &&
|
||||||
props.meta.meta.type === MWMediaType.SERIES ? (
|
props.meta.meta.type === MWMediaType.SERIES ? (
|
||||||
<ShowControl
|
<ShowControl
|
||||||
|
|
Loading…
Reference in a new issue