From 84b8a67cea28455e53b3d734204f4845c6f8b1f2 Mon Sep 17 00:00:00 2001 From: Isra Date: Tue, 11 Apr 2023 16:16:06 -0500 Subject: [PATCH 1/6] Time format --- src/video/components/actions/TimeAction.tsx | 48 +++++++++++++++++-- src/video/state/init.ts | 1 + src/video/state/logic/controls.ts | 5 ++ src/video/state/logic/interface.ts | 6 +++ .../state/providers/castingStateProvider.ts | 4 ++ src/video/state/providers/providerTypes.ts | 1 + .../state/providers/videoStateProvider.ts | 4 ++ src/video/state/types.ts | 1 + 8 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/video/components/actions/TimeAction.tsx b/src/video/components/actions/TimeAction.tsx index 493f6d26..1020d093 100644 --- a/src/video/components/actions/TimeAction.tsx +++ b/src/video/components/actions/TimeAction.tsx @@ -1,6 +1,7 @@ import { useVideoPlayerDescriptor } from "@/video/state/hooks"; import { useMediaPlaying } from "@/video/state/logic/mediaplaying"; import { useProgress } from "@/video/state/logic/progress"; +import { useInterface } from "@/video/state/logic/interface"; function durationExceedsHour(secs: number): boolean { return secs > 60 * 60; @@ -37,6 +38,7 @@ export function TimeAction(props: Props) { const descriptor = useVideoPlayerDescriptor(); const videoTime = useProgress(descriptor); const mediaPlaying = useMediaPlaying(descriptor); + const { timeFormat, setTimeFormat } = useInterface(descriptor); const hasHours = durationExceedsHour(videoTime.duration); const time = formatSeconds( @@ -45,11 +47,47 @@ export function TimeAction(props: Props) { ); const duration = formatSeconds(videoTime.duration, hasHours); + const timeLeft = formatSeconds( + (videoTime.duration - videoTime.time) / mediaPlaying.playbackSpeed + ); + + const timeFinished = new Date( + new Date().getTime() + + (videoTime.duration * 1000) / mediaPlaying.playbackSpeed + ).toLocaleTimeString("en-US", { + hour: "numeric", + minute: "numeric", + hour12: true, + }); + return ( -
-

- {time} {props.noDuration ? "" : `/ ${duration}`} -

-
+ ); } diff --git a/src/video/state/init.ts b/src/video/state/init.ts index bd4037fe..6c60ad54 100644 --- a/src/video/state/init.ts +++ b/src/video/state/init.ts @@ -32,6 +32,7 @@ function initPlayer(): VideoPlayerState { isFocused: false, leftControlHovering: false, popoutBounds: null, + timeFormat: 0, }, mediaPlaying: { diff --git a/src/video/state/logic/controls.ts b/src/video/state/logic/controls.ts index e6d33369..535bd146 100644 --- a/src/video/state/logic/controls.ts +++ b/src/video/state/logic/controls.ts @@ -15,6 +15,7 @@ export type ControlMethods = { setDraggingTime(num: number): void; togglePictureInPicture(): void; setPlaybackSpeed(num: number): void; + setTimeFormat(num: 0 | 1): 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); + }, }; } diff --git a/src/video/state/logic/interface.ts b/src/video/state/logic/interface.ts index 2f22823f..8e761e4e 100644 --- a/src/video/state/logic/interface.ts +++ b/src/video/state/logic/interface.ts @@ -9,6 +9,8 @@ export type VideoInterfaceEvent = { isFocused: boolean; isFullscreen: boolean; popoutBounds: null | DOMRect; + timeFormat: 0 | 1 | 2; + setTimeFormat(timeFormat: 0 | 1 | 2): void; }; function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent { @@ -18,6 +20,10 @@ function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent { isFocused: state.interface.isFocused, isFullscreen: state.interface.isFullscreen, popoutBounds: state.interface.popoutBounds, + timeFormat: state.interface.timeFormat, + setTimeFormat(timeFormat: 0 | 1 | 2) { + state.stateProvider?.setTimeFormat(timeFormat); + }, }; } diff --git a/src/video/state/providers/castingStateProvider.ts b/src/video/state/providers/castingStateProvider.ts index faf34dc5..e668cebc 100644 --- a/src/video/state/providers/castingStateProvider.ts +++ b/src/video/state/providers/castingStateProvider.ts @@ -173,6 +173,10 @@ export function createCastingStateProvider( updateSource(descriptor, state); } }, + setTimeFormat(format) { + state.interface.timeFormat = format; + updateInterface(descriptor, state); + }, providerStart() { this.setVolume(getStoredVolume()); diff --git a/src/video/state/providers/providerTypes.ts b/src/video/state/providers/providerTypes.ts index ad09e812..6b3d7391 100644 --- a/src/video/state/providers/providerTypes.ts +++ b/src/video/state/providers/providerTypes.ts @@ -23,6 +23,7 @@ export type VideoPlayerStateController = { getId(): string; togglePictureInPicture(): void; setPlaybackSpeed(num: number): void; + setTimeFormat(format: 0 | 1 | 2): void; }; export type VideoPlayerStateProvider = VideoPlayerStateController & { diff --git a/src/video/state/providers/videoStateProvider.ts b/src/video/state/providers/videoStateProvider.ts index e527419b..e8aa9aa3 100644 --- a/src/video/state/providers/videoStateProvider.ts +++ b/src/video/state/providers/videoStateProvider.ts @@ -133,6 +133,10 @@ export function createVideoStateProvider( // update localstorage setStoredVolume(volume); }, + setTimeFormat(num) { + state.interface.timeFormat = num; + updateInterface(descriptor, state); + }, setSource(source) { if (!source) { resetStateForSource(descriptor, state); diff --git a/src/video/state/types.ts b/src/video/state/types.ts index 1ba9ef7a..3a482332 100644 --- a/src/video/state/types.ts +++ b/src/video/state/types.ts @@ -30,6 +30,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: 0 | 1 | 2; // Time format of the video player }; // state related to the playing state of the media From c330112dbc817ad2607c5366b1e02b4126a3f26a Mon Sep 17 00:00:00 2001 From: Isra Date: Tue, 11 Apr 2023 16:34:19 -0500 Subject: [PATCH 2/6] Translations --- src/setup/locales/en/translation.json | 2 ++ src/setup/locales/fr/translation.json | 5 ++++- src/video/components/actions/TimeAction.tsx | 10 ++++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/setup/locales/en/translation.json b/src/setup/locales/en/translation.json index ad479bb4..47423b34 100644 --- a/src/setup/locales/en/translation.json +++ b/src/setup/locales/en/translation.json @@ -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", diff --git a/src/setup/locales/fr/translation.json b/src/setup/locales/fr/translation.json index e5c669ce..fe9d73eb 100644 --- a/src/setup/locales/fr/translation.json +++ b/src/setup/locales/fr/translation.json @@ -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", diff --git a/src/video/components/actions/TimeAction.tsx b/src/video/components/actions/TimeAction.tsx index 1020d093..d355ba2d 100644 --- a/src/video/components/actions/TimeAction.tsx +++ b/src/video/components/actions/TimeAction.tsx @@ -1,4 +1,5 @@ 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"; @@ -39,6 +40,7 @@ export function TimeAction(props: Props) { const videoTime = useProgress(descriptor); const mediaPlaying = useMediaPlaying(descriptor); const { timeFormat, setTimeFormat } = useInterface(descriptor); + const { t } = useTranslation(); const hasHours = durationExceedsHour(videoTime.duration); const time = formatSeconds( @@ -80,10 +82,14 @@ export function TimeAction(props: Props) { {/* {time} {props.noDuration ? "" : `/ ${duration}`} */} {timeFormat === 0 ? `${time} ${props.noDuration ? "" : `/ ${duration}`}` - : `${timeLeft} left${ + : `${t("videoPlayer.timeLeft", { + timeLeft, + })}${ videoTime.time === videoTime.duration ? "" - : ` - finish at ${timeFinished}` + : ` - ${t("videoPlayer.finishAt", { + timeFinished, + })}` } `}

From 41fd23cf20595219a67d43e1313a1de514dadab4 Mon Sep 17 00:00:00 2001 From: Isra Date: Fri, 14 Apr 2023 14:11:13 -0500 Subject: [PATCH 3/6] Reviews --- src/video/components/actions/TimeAction.tsx | 54 +++++++++++++-------- src/video/state/logic/controls.ts | 4 +- src/video/state/logic/interface.ts | 8 +-- src/video/state/providers/providerTypes.ts | 3 +- src/video/state/types.ts | 7 ++- 5 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/video/components/actions/TimeAction.tsx b/src/video/components/actions/TimeAction.tsx index d355ba2d..d71d345c 100644 --- a/src/video/components/actions/TimeAction.tsx +++ b/src/video/components/actions/TimeAction.tsx @@ -3,6 +3,9 @@ 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; @@ -40,19 +43,20 @@ export function TimeAction(props: Props) { const videoTime = useProgress(descriptor); const mediaPlaying = useMediaPlaying(descriptor); const { timeFormat, setTimeFormat } = 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 + (videoTime.duration - videoTime.time) / mediaPlaying.playbackSpeed, + hasHours ); - const timeFinished = new Date( new Date().getTime() + (videoTime.duration * 1000) / mediaPlaying.playbackSpeed @@ -61,15 +65,38 @@ export function TimeAction(props: Props) { 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) { + formattedTime = `${t("videoPlayer.timeLeft", { + timeLeft, + })}${ + videoTime.time === videoTime.duration || isMobile + ? "" + : formattedTimeFinished + } `; + } else { + formattedTime = ""; + } return ( diff --git a/src/video/state/logic/controls.ts b/src/video/state/logic/controls.ts index 535bd146..ccd83add 100644 --- a/src/video/state/logic/controls.ts +++ b/src/video/state/logic/controls.ts @@ -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,7 +15,7 @@ export type ControlMethods = { setDraggingTime(num: number): void; togglePictureInPicture(): void; setPlaybackSpeed(num: number): void; - setTimeFormat(num: 0 | 1): void; + setTimeFormat(num: VideoPlayerTimeFormat): void; }; export function useControls( diff --git a/src/video/state/logic/interface.ts b/src/video/state/logic/interface.ts index 8e761e4e..5a5ea719 100644 --- a/src/video/state/logic/interface.ts +++ b/src/video/state/logic/interface.ts @@ -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,8 +9,8 @@ export type VideoInterfaceEvent = { isFocused: boolean; isFullscreen: boolean; popoutBounds: null | DOMRect; - timeFormat: 0 | 1 | 2; - setTimeFormat(timeFormat: 0 | 1 | 2): void; + timeFormat: VideoPlayerTimeFormat; + setTimeFormat(timeFormat: VideoPlayerTimeFormat): void; }; function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent { @@ -21,7 +21,7 @@ function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent { isFullscreen: state.interface.isFullscreen, popoutBounds: state.interface.popoutBounds, timeFormat: state.interface.timeFormat, - setTimeFormat(timeFormat: 0 | 1 | 2) { + setTimeFormat(timeFormat: VideoPlayerTimeFormat) { state.stateProvider?.setTimeFormat(timeFormat); }, }; diff --git a/src/video/state/providers/providerTypes.ts b/src/video/state/providers/providerTypes.ts index 6b3d7391..2cab148b 100644 --- a/src/video/state/providers/providerTypes.ts +++ b/src/video/state/providers/providerTypes.ts @@ -1,4 +1,5 @@ import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams"; +import { VideoPlayerTimeFormat } from "@/video/state/types"; type VideoPlayerSource = { source: string; @@ -23,7 +24,7 @@ export type VideoPlayerStateController = { getId(): string; togglePictureInPicture(): void; setPlaybackSpeed(num: number): void; - setTimeFormat(format: 0 | 1 | 2): void; + setTimeFormat(timeFormat: VideoPlayerTimeFormat): void; }; export type VideoPlayerStateProvider = VideoPlayerStateController & { diff --git a/src/video/state/types.ts b/src/video/state/types.ts index 3a482332..e5e403da 100644 --- a/src/video/state/types.ts +++ b/src/video/state/types.ts @@ -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,7 +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: 0 | 1 | 2; // Time format of the video player + timeFormat: VideoPlayerTimeFormat; // Time format of the video player }; // state related to the playing state of the media From c5251401e702a6772a9fa8a793ef18fe27fe573b Mon Sep 17 00:00:00 2001 From: Isra Date: Fri, 14 Apr 2023 14:18:17 -0500 Subject: [PATCH 4/6] Does this fix it? --- src/video/components/actions/TimeAction.tsx | 3 ++- src/video/state/logic/interface.ts | 5 +---- src/video/state/providers/castingStateProvider.ts | 4 ---- src/video/state/providers/providerTypes.ts | 2 -- src/video/state/providers/videoStateProvider.ts | 4 ---- 5 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/video/components/actions/TimeAction.tsx b/src/video/components/actions/TimeAction.tsx index d71d345c..184256ea 100644 --- a/src/video/components/actions/TimeAction.tsx +++ b/src/video/components/actions/TimeAction.tsx @@ -42,7 +42,8 @@ export function TimeAction(props: Props) { const descriptor = useVideoPlayerDescriptor(); const videoTime = useProgress(descriptor); const mediaPlaying = useMediaPlaying(descriptor); - const { timeFormat, setTimeFormat } = useInterface(descriptor); + const { setTimeFormat } = useControls(descriptor); + const { timeFormat } = useInterface(descriptor); const { isMobile } = useIsMobile(); const { t } = useTranslation(); diff --git a/src/video/state/logic/interface.ts b/src/video/state/logic/interface.ts index 5a5ea719..6d0357ab 100644 --- a/src/video/state/logic/interface.ts +++ b/src/video/state/logic/interface.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { useControls } from "@/video/state/logic/controls"; import { getPlayerState } from "../cache"; import { listenEvent, sendEvent, unlistenEvent } from "../events"; import { VideoPlayerState, VideoPlayerTimeFormat } from "../types"; @@ -10,7 +11,6 @@ export type VideoInterfaceEvent = { isFullscreen: boolean; popoutBounds: null | DOMRect; timeFormat: VideoPlayerTimeFormat; - setTimeFormat(timeFormat: VideoPlayerTimeFormat): void; }; function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent { @@ -21,9 +21,6 @@ function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent { isFullscreen: state.interface.isFullscreen, popoutBounds: state.interface.popoutBounds, timeFormat: state.interface.timeFormat, - setTimeFormat(timeFormat: VideoPlayerTimeFormat) { - state.stateProvider?.setTimeFormat(timeFormat); - }, }; } diff --git a/src/video/state/providers/castingStateProvider.ts b/src/video/state/providers/castingStateProvider.ts index e668cebc..faf34dc5 100644 --- a/src/video/state/providers/castingStateProvider.ts +++ b/src/video/state/providers/castingStateProvider.ts @@ -173,10 +173,6 @@ export function createCastingStateProvider( updateSource(descriptor, state); } }, - setTimeFormat(format) { - state.interface.timeFormat = format; - updateInterface(descriptor, state); - }, providerStart() { this.setVolume(getStoredVolume()); diff --git a/src/video/state/providers/providerTypes.ts b/src/video/state/providers/providerTypes.ts index 2cab148b..ad09e812 100644 --- a/src/video/state/providers/providerTypes.ts +++ b/src/video/state/providers/providerTypes.ts @@ -1,5 +1,4 @@ import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams"; -import { VideoPlayerTimeFormat } from "@/video/state/types"; type VideoPlayerSource = { source: string; @@ -24,7 +23,6 @@ export type VideoPlayerStateController = { getId(): string; togglePictureInPicture(): void; setPlaybackSpeed(num: number): void; - setTimeFormat(timeFormat: VideoPlayerTimeFormat): void; }; export type VideoPlayerStateProvider = VideoPlayerStateController & { diff --git a/src/video/state/providers/videoStateProvider.ts b/src/video/state/providers/videoStateProvider.ts index e8aa9aa3..e527419b 100644 --- a/src/video/state/providers/videoStateProvider.ts +++ b/src/video/state/providers/videoStateProvider.ts @@ -133,10 +133,6 @@ export function createVideoStateProvider( // update localstorage setStoredVolume(volume); }, - setTimeFormat(num) { - state.interface.timeFormat = num; - updateInterface(descriptor, state); - }, setSource(source) { if (!source) { resetStateForSource(descriptor, state); From 1472b21600212078d8de825b525abe7f1a9e1475 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Thu, 20 Apr 2023 20:52:06 +0200 Subject: [PATCH 5/6] negative sign thingy --- src/video/components/actions/TimeAction.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/video/components/actions/TimeAction.tsx b/src/video/components/actions/TimeAction.tsx index 184256ea..bab9e101 100644 --- a/src/video/components/actions/TimeAction.tsx +++ b/src/video/components/actions/TimeAction.tsx @@ -74,14 +74,12 @@ export function TimeAction(props: Props) { if (timeFormat === VideoPlayerTimeFormat.REGULAR) { formattedTime = `${currentTime} ${props.noDuration ? "" : `/ ${duration}`}`; - } else if (timeFormat === VideoPlayerTimeFormat.REMAINING) { + } else if (timeFormat === VideoPlayerTimeFormat.REMAINING && !isMobile) { formattedTime = `${t("videoPlayer.timeLeft", { timeLeft, - })}${ - videoTime.time === videoTime.duration || isMobile - ? "" - : formattedTimeFinished - } `; + })}${videoTime.time === videoTime.duration ? "" : formattedTimeFinished} `; + } else if (timeFormat === VideoPlayerTimeFormat.REMAINING && isMobile) { + formattedTime = `-${timeLeft}`; } else { formattedTime = ""; } From 43c8da9003b0e2d24babb044b39dfa16cbf80af2 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Thu, 20 Apr 2023 20:53:23 +0200 Subject: [PATCH 6/6] remove unsused useControls --- src/video/state/logic/interface.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/video/state/logic/interface.ts b/src/video/state/logic/interface.ts index 6d0357ab..4ac47a3a 100644 --- a/src/video/state/logic/interface.ts +++ b/src/video/state/logic/interface.ts @@ -1,5 +1,4 @@ import { useEffect, useState } from "react"; -import { useControls } from "@/video/state/logic/controls"; import { getPlayerState } from "../cache"; import { listenEvent, sendEvent, unlistenEvent } from "../events"; import { VideoPlayerState, VideoPlayerTimeFormat } from "../types";