From 8632980212cbdafb42ed1d28d6d79cc5318dbba9 Mon Sep 17 00:00:00 2001 From: Guus van Meerveld Date: Thu, 4 Apr 2024 19:22:39 +0200 Subject: [PATCH] watch: added fullscreen support, added loading bar --- package.json | 1 + src/app/watch/Player/index.tsx | 169 ++++++++++++++++++++------------- src/components/Author.tsx | 39 ++++---- yarn.lock | 5 + 4 files changed, 129 insertions(+), 85 deletions(-) diff --git a/package.json b/package.json index c342491..41b438a 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "react-player": "^2.15.1", "reactjs-visibility": "^0.1.4", "sanitize-html": "^2.13.0", + "screenfull": "^6.0.2", "use-debounce": "^10.0.0", "zod": "^3.22.4", "zustand": "^4.5.2" diff --git a/src/app/watch/Player/index.tsx b/src/app/watch/Player/index.tsx index 846b6c0..0bb2496 100644 --- a/src/app/watch/Player/index.tsx +++ b/src/app/watch/Player/index.tsx @@ -1,5 +1,6 @@ "use client"; +import screenfull from "screenfull"; import { useDebounce } from "use-debounce"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ -8,7 +9,6 @@ import { FiMinimize as MinimizeIcon, FiVolumeX as MutedIcon, FiPause as PauseIcon, - FiFastForward as PlaybackRateIcon, FiPlay as PlayIcon, FiVolume as VolumeIcon, FiVolume1 as VolumeIcon1, @@ -33,9 +33,12 @@ import { Component } from "@/typings/component"; export const Player: Component<{ streams: Stream[] }> = ({ streams }) => { const stream = streams.find((stream) => stream.type === StreamType.Hls); - const player = useRef(null); + const playerRef = useRef(null); + + const videoPlayerId = "video-player"; const [progress, setProgress] = useState(0); + const [loaded, setLoaded] = useState(0); const [duration, setDuration] = useState(0); const [maximized, setMaximized] = useState(false); const [volume, setVolume] = useState(40); @@ -44,11 +47,9 @@ export const Player: Component<{ streams: Stream[] }> = ({ streams }) => { const [userSetProgress, setUserSetProgress] = useState(0); - const [seek] = useDebounce(userSetProgress, 100); - useEffect(() => { - player.current?.seekTo(seek); - }, [seek]); + playerRef.current?.seekTo(userSetProgress); + }, [userSetProgress]); const volumeIcons = useMemo( () => [ @@ -60,43 +61,77 @@ export const Player: Component<{ streams: Stream[] }> = ({ streams }) => { ); const playbackRateCategories = useMemo( - () => [ - { key: 0.25, label: "0.25" }, - { key: 0.5, label: "0.5" }, - { key: 1, label: "1.0" }, - { key: 1.25, label: "1.25" }, - { key: 1.5, label: "1.5" }, - { key: 2, label: "2.0" } - ], + () => + [0.25, 0.5, 1, 1.25, 1.5, 2].map((speed) => ({ + key: speed, + label: speed.toString() + })), [] ); - const handleBuffering = useCallback(() => {}, []); + const updateMaximized = useCallback(() => { + setMaximized(screenfull.isFullscreen); + }, [setMaximized]); + + useEffect(() => { + if (screenfull.isEnabled) { + screenfull.on("change", updateMaximized); + } + + return () => screenfull.off("change", updateMaximized); + }, [updateMaximized]); + + useEffect(() => { + if (screenfull.isEnabled) { + const playerElement = document.getElementById(videoPlayerId) ?? undefined; + + if (maximized) screenfull.request(playerElement); + else screenfull.exit(); + } + }, [maximized]); return ( <> - {stream && ( -
-
-
- { - if (typeof value === "number") { - setUserSetProgress(value / 100); - setProgress(value / 100); - } - }} - className="cursor-pointer" - value={progress * 100} - /> +
+
+
+
setPlaying((state) => !state)} + >
+
+
+ { + if (typeof value === "number") { + setProgress(value / 100); + } + }} + onChangeEnd={(value) => { + if (typeof value === "number") { + setUserSetProgress(value / 100); + } + }} + value={progress * 100} + /> +
+
+
@@ -181,29 +213,32 @@ export const Player: Component<{ streams: Stream[] }> = ({ streams }) => {
- setPlaying(false)} - onPlay={() => setPlaying(true)} - onDuration={(duration) => setDuration(duration)} - onBuffer={handleBuffering} - onProgress={({ played }) => { - setProgress(played); - }} - // onPlaybackQualityChange={(e: unknown) => - // console.log("onPlaybackQualityChange", e) - // } - url={(stream as HlsStream).url} - /> + {stream && ( + setPlaying(false)} + onPlay={() => setPlaying(true)} + onDuration={(duration) => setDuration(duration)} + // onBuffer={({}) => {}} + onProgress={({ played, loaded }) => { + setProgress(played); + setLoaded(loaded); + }} + // onPlaybackQualityChange={(e: unknown) => + // console.log("onPlaybackQualityChange", e) + // } + url={(stream as HlsStream).url} + /> + )}
- )} +
); }; diff --git a/src/components/Author.tsx b/src/components/Author.tsx index 5e8dbf3..3e25ce4 100644 --- a/src/components/Author.tsx +++ b/src/components/Author.tsx @@ -10,33 +10,36 @@ import formatBigNumber from "@/utils/formatBigNumber"; import { channelUrl } from "@/utils/urls"; export const Author: FC<{ data: AuthorProps }> = ({ data }) => { + const url = data.id ? channelUrl(data.id) : "#"; + return ( - +
{data.avatar && ( - + + + )}
-
-

{data.name}

- -
+ +
+

{data.name}

+ +
+ + {data.subscribers && (

{formatBigNumber(data.subscribers)} subscribers

)}
- +
); }; diff --git a/yarn.lock b/yarn.lock index c851f9e..1af77df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6201,6 +6201,11 @@ schema-utils@^3.1.1: ajv "^6.12.5" ajv-keywords "^3.5.2" +screenfull@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-6.0.2.tgz#3dbe4b8c4f8f49fb8e33caa8f69d0bca730ab238" + integrity sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw== + scroll-into-view-if-needed@3.0.10: version "3.0.10" resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz#38fbfe770d490baff0fb2ba34ae3539f6ec44e13"