Merge pull request #255 from zisra/movie-time

Time format
This commit is contained in:
mrjvs 2023-04-20 21:04:13 +02:00 committed by GitHub
commit 24fa1c449f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 85 additions and 9 deletions

View File

@ -57,6 +57,8 @@
"backToHome": "Back to home", "backToHome": "Back to home",
"backToHomeShort": "Back", "backToHomeShort": "Back",
"seasonAndEpisode": "S{{season}} E{{episode}}", "seasonAndEpisode": "S{{season}} E{{episode}}",
"timeLeft": "{{timeLeft}} left",
"finishAt": "Finish at {{timeFinished}}",
"buttons": { "buttons": {
"episodes": "Episodes", "episodes": "Episodes",
"source": "Source", "source": "Source",

View File

@ -39,13 +39,16 @@
"backToHome": "Retour à la page d'accueil", "backToHome": "Retour à la page d'accueil",
"backToHomeShort": "Retour", "backToHomeShort": "Retour",
"seasonAndEpisode": "S{{season}} E{{episode}}", "seasonAndEpisode": "S{{season}} E{{episode}}",
"timeLeft": "{{timeLeft}} restant",
"finishAt": "Terminer à {{timeFinished}}",
"buttons": { "buttons": {
"episodes": "Épisodes", "episodes": "Épisodes",
"source": "Source", "source": "Source",
"captions": "Sous-titres", "captions": "Sous-titres",
"download": "Télécharger", "download": "Télécharger",
"settings": "Paramètres", "settings": "Paramètres",
"pictureInPicture": "Image dans l'image" "pictureInPicture": "Image dans l'image",
"playbackSpeed": "Vitesse"
}, },
"popouts": { "popouts": {
"sources": "Sources", "sources": "Sources",

View File

@ -1,6 +1,11 @@
import { useVideoPlayerDescriptor } from "@/video/state/hooks"; import { useVideoPlayerDescriptor } from "@/video/state/hooks";
import { useTranslation } from "react-i18next";
import { useMediaPlaying } from "@/video/state/logic/mediaplaying"; import { useMediaPlaying } from "@/video/state/logic/mediaplaying";
import { useProgress } from "@/video/state/logic/progress"; 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 { function durationExceedsHour(secs: number): boolean {
return secs > 60 * 60; return secs > 60 * 60;
@ -37,19 +42,71 @@ export function TimeAction(props: Props) {
const descriptor = useVideoPlayerDescriptor(); const descriptor = useVideoPlayerDescriptor();
const videoTime = useProgress(descriptor); const videoTime = useProgress(descriptor);
const mediaPlaying = useMediaPlaying(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 hasHours = durationExceedsHour(videoTime.duration);
const time = formatSeconds(
const currentTime = formatSeconds(
mediaPlaying.isDragSeeking ? videoTime.draggingTime : videoTime.time, mediaPlaying.isDragSeeking ? videoTime.draggingTime : videoTime.time,
hasHours hasHours
); );
const duration = formatSeconds(videoTime.duration, 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 ( return (
<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}> <div className={props.className}>
<p className="select-none text-white"> <p className="select-none text-white">{formattedTime}</p>
{time} {props.noDuration ? "" : `/ ${duration}`}
</p>
</div> </div>
</div>
</button>
); );
} }

View File

@ -32,6 +32,7 @@ function initPlayer(): VideoPlayerState {
isFocused: false, isFocused: false,
leftControlHovering: false, leftControlHovering: false,
popoutBounds: null, popoutBounds: null,
timeFormat: 0,
}, },
mediaPlaying: { mediaPlaying: {

View File

@ -1,7 +1,7 @@
import { updateInterface } from "@/video/state/logic/interface"; import { updateInterface } from "@/video/state/logic/interface";
import { updateMeta } from "@/video/state/logic/meta"; import { updateMeta } from "@/video/state/logic/meta";
import { updateProgress } from "@/video/state/logic/progress"; 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 { getPlayerState } from "../cache";
import { VideoPlayerStateController } from "../providers/providerTypes"; import { VideoPlayerStateController } from "../providers/providerTypes";
@ -15,6 +15,7 @@ export type ControlMethods = {
setDraggingTime(num: number): void; setDraggingTime(num: number): void;
togglePictureInPicture(): void; togglePictureInPicture(): void;
setPlaybackSpeed(num: number): void; setPlaybackSpeed(num: number): void;
setTimeFormat(num: VideoPlayerTimeFormat): void;
}; };
export function useControls( export function useControls(
@ -110,5 +111,9 @@ export function useControls(
state.stateProvider?.setPlaybackSpeed(num); state.stateProvider?.setPlaybackSpeed(num);
updateInterface(descriptor, state); updateInterface(descriptor, state);
}, },
setTimeFormat(format) {
state.interface.timeFormat = format;
updateInterface(descriptor, state);
},
}; };
} }

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { getPlayerState } from "../cache"; import { getPlayerState } from "../cache";
import { listenEvent, sendEvent, unlistenEvent } from "../events"; import { listenEvent, sendEvent, unlistenEvent } from "../events";
import { VideoPlayerState } from "../types"; import { VideoPlayerState, VideoPlayerTimeFormat } from "../types";
export type VideoInterfaceEvent = { export type VideoInterfaceEvent = {
popout: string | null; popout: string | null;
@ -9,6 +9,7 @@ export type VideoInterfaceEvent = {
isFocused: boolean; isFocused: boolean;
isFullscreen: boolean; isFullscreen: boolean;
popoutBounds: null | DOMRect; popoutBounds: null | DOMRect;
timeFormat: VideoPlayerTimeFormat;
}; };
function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent { function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent {
@ -18,6 +19,7 @@ function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent {
isFocused: state.interface.isFocused, isFocused: state.interface.isFocused,
isFullscreen: state.interface.isFullscreen, isFullscreen: state.interface.isFullscreen,
popoutBounds: state.interface.popoutBounds, popoutBounds: state.interface.popoutBounds,
timeFormat: state.interface.timeFormat,
}; };
} }

View File

@ -22,6 +22,11 @@ export type VideoPlayerMeta = {
}[]; }[];
}; };
export enum VideoPlayerTimeFormat {
REGULAR = 0,
REMAINING = 1,
}
export type VideoPlayerState = { export type VideoPlayerState = {
// state related to the user interface // state related to the user interface
interface: { interface: {
@ -30,6 +35,7 @@ export type VideoPlayerState = {
isFocused: boolean; // is the video player the users focus? (shortcuts only works when its focused) 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 leftControlHovering: boolean; // is the cursor hovered over the left side of player controls
popoutBounds: null | DOMRect; // bounding box of current popout 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 // state related to the playing state of the media