mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-27 08:11:48 +01:00
commit
24fa1c449f
@ -57,6 +57,8 @@
|
||||
"backToHome": "Back to home",
|
||||
"backToHomeShort": "Back",
|
||||
"seasonAndEpisode": "S{{season}} E{{episode}}",
|
||||
"timeLeft": "{{timeLeft}} left",
|
||||
"finishAt": "Finish at {{timeFinished}}",
|
||||
"buttons": {
|
||||
"episodes": "Episodes",
|
||||
"source": "Source",
|
||||
|
@ -39,13 +39,16 @@
|
||||
"backToHome": "Retour à la page d'accueil",
|
||||
"backToHomeShort": "Retour",
|
||||
"seasonAndEpisode": "S{{season}} E{{episode}}",
|
||||
"timeLeft": "{{timeLeft}} restant",
|
||||
"finishAt": "Terminer à {{timeFinished}}",
|
||||
"buttons": {
|
||||
"episodes": "Épisodes",
|
||||
"source": "Source",
|
||||
"captions": "Sous-titres",
|
||||
"download": "Télécharger",
|
||||
"settings": "Paramètres",
|
||||
"pictureInPicture": "Image dans l'image"
|
||||
"pictureInPicture": "Image dans l'image",
|
||||
"playbackSpeed": "Vitesse"
|
||||
},
|
||||
"popouts": {
|
||||
"sources": "Sources",
|
||||
|
@ -1,6 +1,11 @@
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMediaPlaying } from "@/video/state/logic/mediaplaying";
|
||||
import { useProgress } from "@/video/state/logic/progress";
|
||||
import { useInterface } from "@/video/state/logic/interface";
|
||||
import { VideoPlayerTimeFormat } from "@/video/state/types";
|
||||
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||
import { useControls } from "@/video/state/logic/controls";
|
||||
|
||||
function durationExceedsHour(secs: number): boolean {
|
||||
return secs > 60 * 60;
|
||||
@ -37,19 +42,71 @@ export function TimeAction(props: Props) {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
const videoTime = useProgress(descriptor);
|
||||
const mediaPlaying = useMediaPlaying(descriptor);
|
||||
const { setTimeFormat } = useControls(descriptor);
|
||||
const { timeFormat } = useInterface(descriptor);
|
||||
const { isMobile } = useIsMobile();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const hasHours = durationExceedsHour(videoTime.duration);
|
||||
const time = formatSeconds(
|
||||
|
||||
const currentTime = formatSeconds(
|
||||
mediaPlaying.isDragSeeking ? videoTime.draggingTime : videoTime.time,
|
||||
hasHours
|
||||
);
|
||||
const duration = formatSeconds(videoTime.duration, hasHours);
|
||||
const timeLeft = formatSeconds(
|
||||
(videoTime.duration - videoTime.time) / mediaPlaying.playbackSpeed,
|
||||
hasHours
|
||||
);
|
||||
const timeFinished = new Date(
|
||||
new Date().getTime() +
|
||||
(videoTime.duration * 1000) / mediaPlaying.playbackSpeed
|
||||
).toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: true,
|
||||
});
|
||||
const formattedTimeFinished = ` - ${t("videoPlayer.finishAt", {
|
||||
timeFinished,
|
||||
})}`;
|
||||
|
||||
let formattedTime: string;
|
||||
|
||||
if (timeFormat === VideoPlayerTimeFormat.REGULAR) {
|
||||
formattedTime = `${currentTime} ${props.noDuration ? "" : `/ ${duration}`}`;
|
||||
} else if (timeFormat === VideoPlayerTimeFormat.REMAINING && !isMobile) {
|
||||
formattedTime = `${t("videoPlayer.timeLeft", {
|
||||
timeLeft,
|
||||
})}${videoTime.time === videoTime.duration ? "" : formattedTimeFinished} `;
|
||||
} else if (timeFormat === VideoPlayerTimeFormat.REMAINING && isMobile) {
|
||||
formattedTime = `-${timeLeft}`;
|
||||
} else {
|
||||
formattedTime = "";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<p className="select-none text-white">
|
||||
{time} {props.noDuration ? "" : `/ ${duration}`}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
"group pointer-events-auto text-white transition-transform duration-100 active:scale-110",
|
||||
].join(" ")}
|
||||
onClick={() => {
|
||||
setTimeFormat(
|
||||
timeFormat === VideoPlayerTimeFormat.REGULAR
|
||||
? VideoPlayerTimeFormat.REMAINING
|
||||
: VideoPlayerTimeFormat.REGULAR
|
||||
);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
"flex items-center justify-center rounded-full bg-denim-600 bg-opacity-0 p-2 transition-colors duration-100 group-hover:bg-opacity-50 group-active:bg-denim-500 group-active:bg-opacity-100 sm:px-4",
|
||||
].join(" ")}
|
||||
>
|
||||
<div className={props.className}>
|
||||
<p className="select-none text-white">{formattedTime}</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ function initPlayer(): VideoPlayerState {
|
||||
isFocused: false,
|
||||
leftControlHovering: false,
|
||||
popoutBounds: null,
|
||||
timeFormat: 0,
|
||||
},
|
||||
|
||||
mediaPlaying: {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { updateInterface } from "@/video/state/logic/interface";
|
||||
import { updateMeta } from "@/video/state/logic/meta";
|
||||
import { updateProgress } from "@/video/state/logic/progress";
|
||||
import { VideoPlayerMeta } from "@/video/state/types";
|
||||
import { VideoPlayerMeta, VideoPlayerTimeFormat } from "@/video/state/types";
|
||||
import { getPlayerState } from "../cache";
|
||||
import { VideoPlayerStateController } from "../providers/providerTypes";
|
||||
|
||||
@ -15,6 +15,7 @@ export type ControlMethods = {
|
||||
setDraggingTime(num: number): void;
|
||||
togglePictureInPicture(): void;
|
||||
setPlaybackSpeed(num: number): void;
|
||||
setTimeFormat(num: VideoPlayerTimeFormat): void;
|
||||
};
|
||||
|
||||
export function useControls(
|
||||
@ -110,5 +111,9 @@ export function useControls(
|
||||
state.stateProvider?.setPlaybackSpeed(num);
|
||||
updateInterface(descriptor, state);
|
||||
},
|
||||
setTimeFormat(format) {
|
||||
state.interface.timeFormat = format;
|
||||
updateInterface(descriptor, state);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { getPlayerState } from "../cache";
|
||||
import { listenEvent, sendEvent, unlistenEvent } from "../events";
|
||||
import { VideoPlayerState } from "../types";
|
||||
import { VideoPlayerState, VideoPlayerTimeFormat } from "../types";
|
||||
|
||||
export type VideoInterfaceEvent = {
|
||||
popout: string | null;
|
||||
@ -9,6 +9,7 @@ export type VideoInterfaceEvent = {
|
||||
isFocused: boolean;
|
||||
isFullscreen: boolean;
|
||||
popoutBounds: null | DOMRect;
|
||||
timeFormat: VideoPlayerTimeFormat;
|
||||
};
|
||||
|
||||
function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent {
|
||||
@ -18,6 +19,7 @@ function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent {
|
||||
isFocused: state.interface.isFocused,
|
||||
isFullscreen: state.interface.isFullscreen,
|
||||
popoutBounds: state.interface.popoutBounds,
|
||||
timeFormat: state.interface.timeFormat,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,11 @@ export type VideoPlayerMeta = {
|
||||
}[];
|
||||
};
|
||||
|
||||
export enum VideoPlayerTimeFormat {
|
||||
REGULAR = 0,
|
||||
REMAINING = 1,
|
||||
}
|
||||
|
||||
export type VideoPlayerState = {
|
||||
// state related to the user interface
|
||||
interface: {
|
||||
@ -30,6 +35,7 @@ export type VideoPlayerState = {
|
||||
isFocused: boolean; // is the video player the users focus? (shortcuts only works when its focused)
|
||||
leftControlHovering: boolean; // is the cursor hovered over the left side of player controls
|
||||
popoutBounds: null | DOMRect; // bounding box of current popout
|
||||
timeFormat: VideoPlayerTimeFormat; // Time format of the video player
|
||||
};
|
||||
|
||||
// state related to the playing state of the media
|
||||
|
Loading…
Reference in New Issue
Block a user