1
0
Fork 0
mirror of https://github.com/sussy-code/smov.git synced 2024-12-22 14:57:40 +01:00
smov/src/components/video/hooks/useVideoPlayer.ts

220 lines
5.6 KiB
TypeScript
Raw Normal View History

2023-01-10 19:53:55 +01:00
import { canChangeVolume } from "@/utils/detectFeatures";
2023-01-10 01:01:51 +01:00
import fscreen from "fscreen";
2023-01-10 19:53:55 +01:00
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
2023-01-08 15:37:16 +01:00
import {
initialControls,
PlayerControls,
populateControls,
} from "./controlVideo";
2023-01-08 17:51:38 +01:00
import { handleBuffered } from "./utils";
2023-01-08 15:37:16 +01:00
export type PlayerState = {
isPlaying: boolean;
isPaused: boolean;
2023-01-08 16:23:42 +01:00
isSeeking: boolean;
isLoading: boolean;
2023-01-14 00:27:40 +01:00
isFirstLoading: boolean;
2023-01-08 16:23:42 +01:00
isFullscreen: boolean;
2023-01-08 17:51:38 +01:00
time: number;
duration: number;
volume: number;
buffered: number;
2023-01-10 19:53:55 +01:00
pausedWhenSeeking: boolean;
hasInitialized: boolean;
leftControlHovering: boolean;
hasPlayedOnce: boolean;
seasonData: {
isSeries: boolean;
current?: {
episodeId: string;
seasonId: string;
};
};
2023-01-15 16:51:55 +01:00
error: null | {
name: string;
description: string;
};
2023-01-10 19:53:55 +01:00
};
export type PlayerContext = PlayerState & PlayerControls;
2023-01-08 15:37:16 +01:00
2023-01-10 19:53:55 +01:00
export const initialPlayerState: PlayerContext = {
2023-01-08 15:37:16 +01:00
isPlaying: false,
isPaused: true,
2023-01-08 16:23:42 +01:00
isFullscreen: false,
isLoading: false,
2023-01-08 16:23:42 +01:00
isSeeking: false,
2023-01-14 00:27:40 +01:00
isFirstLoading: true,
2023-01-08 17:51:38 +01:00
time: 0,
duration: 0,
volume: 0,
buffered: 0,
2023-01-10 19:53:55 +01:00
pausedWhenSeeking: false,
hasInitialized: false,
leftControlHovering: false,
hasPlayedOnce: false,
2023-01-15 16:51:55 +01:00
error: null,
seasonData: {
isSeries: false,
},
2023-01-08 15:37:16 +01:00
...initialControls,
};
2023-01-10 19:53:55 +01:00
type SetPlayer = (s: React.SetStateAction<PlayerContext>) => void;
2023-01-08 15:37:16 +01:00
function readState(player: HTMLVideoElement, update: SetPlayer) {
const state = {
...initialPlayerState,
};
state.isPaused = player.paused;
state.isPlaying = !player.paused;
2023-01-08 16:23:42 +01:00
state.isFullscreen = !!document.fullscreenElement;
state.isSeeking = player.seeking;
2023-01-08 17:51:38 +01:00
state.time = player.currentTime;
state.duration = player.duration;
state.volume = player.volume;
state.buffered = handleBuffered(player.currentTime, player.buffered);
state.isLoading = false;
2023-01-10 19:53:55 +01:00
state.hasInitialized = true;
2023-01-15 16:51:55 +01:00
state.error = null;
2023-01-08 15:37:16 +01:00
2023-01-10 19:53:55 +01:00
update((s) => ({
...state,
pausedWhenSeeking: s.pausedWhenSeeking,
hasPlayedOnce: s.hasPlayedOnce,
2023-01-14 00:27:40 +01:00
isFirstLoading: s.isFirstLoading,
2023-01-10 19:53:55 +01:00
}));
2023-01-08 15:37:16 +01:00
}
function registerListeners(player: HTMLVideoElement, update: SetPlayer) {
2023-01-08 17:51:38 +01:00
const pause = () => {
update((s) => ({
...s,
isPaused: true,
isPlaying: false,
}));
2023-01-08 17:51:38 +01:00
};
const playing = () => {
update((s) => ({
...s,
isPaused: false,
isPlaying: true,
isLoading: false,
2023-01-10 19:53:55 +01:00
hasPlayedOnce: true,
}));
2023-01-08 17:51:38 +01:00
};
const seeking = () => {
2023-01-08 16:23:42 +01:00
update((s) => ({ ...s, isSeeking: true }));
2023-01-08 17:51:38 +01:00
};
const seeked = () => {
2023-01-08 16:23:42 +01:00
update((s) => ({ ...s, isSeeking: false }));
2023-01-08 17:51:38 +01:00
};
const waiting = () => {
update((s) => ({ ...s, isLoading: true }));
};
2023-01-08 17:51:38 +01:00
const fullscreenchange = () => {
2023-01-08 16:23:42 +01:00
update((s) => ({ ...s, isFullscreen: !!document.fullscreenElement }));
2023-01-08 17:51:38 +01:00
};
const timeupdate = () => {
update((s) => ({
...s,
duration: player.duration,
time: player.currentTime,
}));
};
const loadedmetadata = () => {
update((s) => ({
...s,
duration: player.duration,
}));
};
2023-01-10 19:53:55 +01:00
const volumechange = async () => {
if (await canChangeVolume())
update((s) => ({
...s,
volume: player.volume,
}));
2023-01-08 17:51:38 +01:00
};
const progress = () => {
update((s) => ({
...s,
buffered: handleBuffered(player.currentTime, player.buffered),
}));
};
2023-01-14 00:27:40 +01:00
const canplay = () => {
update((s) => ({
...s,
isFirstLoading: false,
}));
};
2023-01-15 16:51:55 +01:00
const error = () => {
console.error("Native video player threw error", player.error);
update((s) => ({
...s,
error: player.error
? {
description: player.error.message,
name: `Error ${player.error.code}`,
}
: null,
}));
};
2023-01-08 17:51:38 +01:00
player.addEventListener("pause", pause);
player.addEventListener("playing", playing);
2023-01-08 17:51:38 +01:00
player.addEventListener("seeking", seeking);
player.addEventListener("seeked", seeked);
2023-01-10 01:01:51 +01:00
fscreen.addEventListener("fullscreenchange", fullscreenchange);
2023-01-08 17:51:38 +01:00
player.addEventListener("timeupdate", timeupdate);
player.addEventListener("loadedmetadata", loadedmetadata);
player.addEventListener("volumechange", volumechange);
player.addEventListener("progress", progress);
player.addEventListener("waiting", waiting);
2023-01-14 00:27:40 +01:00
player.addEventListener("canplay", canplay);
2023-01-15 16:51:55 +01:00
player.addEventListener("error", error);
2023-01-08 17:51:38 +01:00
return () => {
player.removeEventListener("pause", pause);
player.removeEventListener("playing", playing);
2023-01-08 17:51:38 +01:00
player.removeEventListener("seeking", seeking);
player.removeEventListener("seeked", seeked);
2023-01-10 01:01:51 +01:00
fscreen.removeEventListener("fullscreenchange", fullscreenchange);
2023-01-08 17:51:38 +01:00
player.removeEventListener("timeupdate", timeupdate);
player.removeEventListener("loadedmetadata", loadedmetadata);
player.removeEventListener("volumechange", volumechange);
player.removeEventListener("progress", progress);
player.removeEventListener("waiting", waiting);
2023-01-14 00:27:40 +01:00
player.removeEventListener("canplay", canplay);
2023-01-15 16:51:55 +01:00
player.removeEventListener("error", error);
2023-01-08 17:51:38 +01:00
};
2023-01-08 15:37:16 +01:00
}
2023-01-08 16:23:42 +01:00
export function useVideoPlayer(
ref: MutableRefObject<HTMLVideoElement | null>,
wrapperRef: MutableRefObject<HTMLDivElement | null>
) {
2023-01-08 15:37:16 +01:00
const [state, setState] = useState(initialPlayerState);
2023-01-10 19:53:55 +01:00
const stateRef = useRef<PlayerState | null>(null);
2023-01-08 15:37:16 +01:00
useEffect(() => {
const player = ref.current;
2023-01-08 16:23:42 +01:00
const wrapper = wrapperRef.current;
if (player && wrapper) {
2023-01-08 15:37:16 +01:00
readState(player, setState);
registerListeners(player, setState);
2023-01-10 19:53:55 +01:00
setState((s) => ({
...s,
...populateControls(player, wrapper, setState as any, stateRef),
}));
2023-01-08 15:37:16 +01:00
}
2023-01-10 19:53:55 +01:00
}, [ref, wrapperRef, stateRef]);
useEffect(() => {
stateRef.current = state;
}, [state, stateRef]);
2023-01-08 15:37:16 +01:00
return {
playerState: state,
};
}