mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-21 14:47:41 +01:00
204 lines
5.3 KiB
TypeScript
204 lines
5.3 KiB
TypeScript
import Hls from "hls.js";
|
|
import {
|
|
canChangeVolume,
|
|
canFullscreen,
|
|
canFullscreenAnyElement,
|
|
canWebkitFullscreen,
|
|
} from "@/utils/detectFeatures";
|
|
import { MWStreamType } from "@/backend/helpers/streams";
|
|
import fscreen from "fscreen";
|
|
import React, { RefObject } from "react";
|
|
import { PlayerState } from "./useVideoPlayer";
|
|
import { getStoredVolume, setStoredVolume } from "./volumeStore";
|
|
|
|
interface ShowData {
|
|
current?: {
|
|
episodeId: string;
|
|
seasonId: string;
|
|
};
|
|
isSeries: boolean;
|
|
seasons?: {
|
|
id: string;
|
|
number: number;
|
|
title: string;
|
|
episodes?: {
|
|
id: string;
|
|
number: number;
|
|
title: string;
|
|
}[];
|
|
}[];
|
|
}
|
|
|
|
export interface PlayerControls {
|
|
play(): void;
|
|
pause(): void;
|
|
exitFullscreen(): void;
|
|
enterFullscreen(): void;
|
|
setTime(time: number): void;
|
|
setVolume(volume: number): void;
|
|
setSeeking(active: boolean): void;
|
|
setLeftControlsHover(hovering: boolean): void;
|
|
initPlayer(sourceUrl: string, sourceType: MWStreamType): void;
|
|
setShowData(data: ShowData): void;
|
|
setCurrentEpisode(sId: string, eId: string): void;
|
|
startAirplay(): void;
|
|
openPopout(id: string): void;
|
|
closePopout(): void;
|
|
}
|
|
|
|
export const initialControls: PlayerControls = {
|
|
play: () => null,
|
|
pause: () => null,
|
|
enterFullscreen: () => null,
|
|
exitFullscreen: () => null,
|
|
setTime: () => null,
|
|
setVolume: () => null,
|
|
setSeeking: () => null,
|
|
setLeftControlsHover: () => null,
|
|
initPlayer: () => null,
|
|
setShowData: () => null,
|
|
startAirplay: () => null,
|
|
setCurrentEpisode: () => null,
|
|
openPopout: () => null,
|
|
closePopout: () => null,
|
|
};
|
|
|
|
export function populateControls(
|
|
playerEl: HTMLVideoElement,
|
|
wrapperEl: HTMLDivElement,
|
|
update: (s: React.SetStateAction<PlayerState>) => void,
|
|
state: RefObject<PlayerState>
|
|
): PlayerControls {
|
|
const player = playerEl;
|
|
const wrapper = wrapperEl;
|
|
|
|
return {
|
|
play() {
|
|
player.play();
|
|
},
|
|
pause() {
|
|
player.pause();
|
|
},
|
|
enterFullscreen() {
|
|
if (!canFullscreen() || fscreen.fullscreenElement) return;
|
|
if (canFullscreenAnyElement()) {
|
|
fscreen.requestFullscreen(wrapper);
|
|
return;
|
|
}
|
|
if (canWebkitFullscreen()) {
|
|
(player as any).webkitEnterFullscreen();
|
|
}
|
|
},
|
|
exitFullscreen() {
|
|
if (!fscreen.fullscreenElement) return;
|
|
fscreen.exitFullscreen();
|
|
},
|
|
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;
|
|
update((s) => ({ ...s, time }));
|
|
},
|
|
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;
|
|
update((s) => ({ ...s, volume }));
|
|
|
|
// update localstorage
|
|
setStoredVolume(volume);
|
|
},
|
|
setSeeking(active) {
|
|
const currentState = state.current;
|
|
if (!currentState) return;
|
|
|
|
// if it was playing when starting to seek, play again
|
|
if (!active) {
|
|
if (!currentState.pausedWhenSeeking) this.play();
|
|
return;
|
|
}
|
|
|
|
// when seeking we pause the video
|
|
update((s) => ({ ...s, pausedWhenSeeking: s.isPaused }));
|
|
this.pause();
|
|
},
|
|
setLeftControlsHover(hovering) {
|
|
update((s) => ({ ...s, leftControlHovering: hovering }));
|
|
},
|
|
openPopout(id: string) {
|
|
update((s) => ({ ...s, popout: id }));
|
|
},
|
|
closePopout() {
|
|
update((s) => ({ ...s, popout: null }));
|
|
},
|
|
setShowData(data) {
|
|
update((s) => ({ ...s, seasonData: data }));
|
|
},
|
|
setCurrentEpisode(sId: string, eId: string) {
|
|
update((s) => ({
|
|
...s,
|
|
seasonData: {
|
|
...s.seasonData,
|
|
current: {
|
|
seasonId: sId,
|
|
episodeId: eId,
|
|
},
|
|
},
|
|
}));
|
|
},
|
|
startAirplay() {
|
|
const videoPlayer = player as any;
|
|
if (videoPlayer.webkitShowPlaybackTargetPicker)
|
|
videoPlayer.webkitShowPlaybackTargetPicker();
|
|
},
|
|
initPlayer(sourceUrl: string, sourceType: MWStreamType) {
|
|
this.setVolume(getStoredVolume());
|
|
|
|
if (sourceType === MWStreamType.HLS) {
|
|
if (player.canPlayType("application/vnd.apple.mpegurl")) {
|
|
player.src = sourceUrl;
|
|
} else {
|
|
// HLS support
|
|
if (!Hls.isSupported()) {
|
|
update((s) => ({
|
|
...s,
|
|
error: {
|
|
name: `Not supported`,
|
|
description: "Your browser does not support HLS video",
|
|
},
|
|
}));
|
|
return;
|
|
}
|
|
|
|
const hls = new Hls({ enableWorker: false });
|
|
|
|
hls.on(Hls.Events.ERROR, (event, data) => {
|
|
if (data.fatal) {
|
|
update((s) => ({
|
|
...s,
|
|
error: {
|
|
name: `error ${data.details}`,
|
|
description: data.error?.message ?? "Something went wrong",
|
|
},
|
|
}));
|
|
}
|
|
console.error("HLS error", data);
|
|
});
|
|
|
|
hls.attachMedia(player);
|
|
hls.loadSource(sourceUrl);
|
|
}
|
|
} else if (sourceType === MWStreamType.MP4) {
|
|
player.src = sourceUrl;
|
|
}
|
|
},
|
|
};
|
|
}
|