From 7c321c70a0827ccc384111fbe5f264846a979733 Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 5 Dec 2023 11:10:57 +0000 Subject: [PATCH] feat: custom player controls closes #34 --- public/icons.svg | 30 +++--- src/element/chat-message.tsx | 18 ++-- src/element/live-video-player.tsx | 150 ++++++++++++++++++++---------- src/element/video-tile.tsx | 3 +- src/lang.json | 9 ++ src/pages/stream-page.css | 4 - src/translations/en.json | 5 +- tailwind.config.js | 3 + 8 files changed, 141 insertions(+), 81 deletions(-) diff --git a/public/icons.svg b/public/icons.svg index 7e8b218..6a4246f 100644 --- a/public/icons.svg +++ b/public/icons.svg @@ -30,9 +30,6 @@ - - - @@ -58,20 +55,10 @@ - + - + @@ -79,7 +66,7 @@ - + @@ -95,5 +82,16 @@ + + + + + + + + + + + diff --git a/src/element/chat-message.tsx b/src/element/chat-message.tsx index 74737c1..a2bfb32 100644 --- a/src/element/chat-message.tsx +++ b/src/element/chat-message.tsx @@ -157,7 +157,7 @@ export function ChatMessage({ const emojiName = e.replace(/:/g, ""); const emoji = isCustomEmojiReaction && getEmojiById(emojiName); return ( -
+
{isCustomEmojiReaction && emoji ? ( @@ -176,15 +176,15 @@ export function ChatMessage({ style={ isTablet ? { - display: showZapDialog || isHovering ? "flex" : "none", - } + display: showZapDialog || isHovering ? "flex" : "none", + } : { - position: "fixed", - top: topOffset ? topOffset - 12 : 0, - left: leftOffset ? leftOffset - 32 : 0, - opacity: showZapDialog || isHovering ? 1 : 0, - pointerEvents: showZapDialog || isHovering ? "auto" : "none", - } + position: "fixed", + top: topOffset ? topOffset - 12 : 0, + left: leftOffset ? leftOffset - 32 : 0, + opacity: showZapDialog || isHovering ? 1 : 0, + pointerEvents: showZapDialog || isHovering ? "auto" : "none", + } }> {zapTarget && ( (null); + const hlsObj = useRef(null); const streamCached = useMemo(() => props.stream, [props.stream]); const [status, setStatus] = useState(); const [src, setSrc] = useState(); + const [levels, setLevels] = useState>(); + const [level, setLevel] = useState(-1); + const [playState, setPlayState] = useState(true); + const [volume, setVolume] = useState(1); useEffect(() => { if (streamCached && video.current) { @@ -41,11 +50,21 @@ export function LiveVideoPlayer(props: VideoPlayerProps) { }); hls.on(Hls.Events.MANIFEST_PARSED, () => { setStatus(VideoStatus.Online); + setLevels(hls.levels.map((a, i) => ({ + level: i, + height: a.height + }))); }); - hls.on(Hls.Events.LEVEL_SWITCHING, (e, l) => { + hls.on(Hls.Events.LEVEL_SWITCHING, (_, l) => { console.debug("HLS Level Switch", l); }); - return () => hls.destroy(); + // @ts-ignore Can write anyway + hlsObj.current = hls; + return () => { + // @ts-ignore Can write anyway + hlsObj.current = null; + hls.destroy(); + } } catch (e) { console.error(e); setStatus(VideoStatus.Offline); @@ -59,58 +78,91 @@ export function LiveVideoPlayer(props: VideoPlayerProps) { } }, [video, streamCached, props.status]); + useEffect(() => { + if (hlsObj.current) { + hlsObj.current.nextLevel = level; + } + }, [hlsObj, level]); + + useEffect(() => { + if (video.current) { + video.current.onplaying = () => setPlayState(true); + video.current.onpause = () => setPlayState(false); + video.current.onvolumechange = () => setVolume(video.current?.volume ?? 1); + } + }, [video]); + + useEffect(() => { + if (video.current) { + video.current.volume = volume; + } + }, [video, volume]); + + function changeVolume(e: React.MouseEvent) { + if (e.currentTarget === e.target) { + const bb = (e.target as HTMLDivElement).getBoundingClientRect(); + + const x = e.clientX - bb.x; + const vol = Math.max(0, Math.min(1.0, x / bb.width)); + setVolume(vol); + } + } + return ( -
-
-
{status}
-
+
+ {status === VideoStatus.Online &&
{ + if (video.current) { + if (playState) { + video.current.pause(); + } else { + video.current.play(); + } + } + }}> +
+ +
+
e.stopPropagation()}> +
+ +
+
+ +
{ + if (e.buttons > 0) { + changeVolume(e); + } + }}> +
+
+
+
+ +
+
+
} + { + status === VideoStatus.Offline &&
+ +
+ }
+
); -} - -export function WebRTCPlayer(props: VideoPlayerProps) { - const video = useRef(null); - const streamCached = useMemo( - () => "https://customer-uu10flpvos4pfhgu.cloudflarestream.com/7634aee1af35a2de4ac13ca3d1718a8b/webRTC/play", - [props.stream] - ); - const [status] = useState(); - //https://customer-uu10flpvos4pfhgu.cloudflarestream.com/7634aee1af35a2de4ac13ca3d1718a8b/webRTC/play - - useEffect(() => { - if (video.current && streamCached) { - const client = new WISH(); - client.addEventListener("log", console.debug); - client.WithEndpoint(streamCached, true); - - client - .Play() - .then(s => { - if (video.current) { - video.current.srcObject = s; - } - }) - .catch(console.error); - return () => { - client.Disconnect().catch(console.error); - }; - } - }, [video, streamCached]); - - return ( -
-
-
{status}
-
-
- ); -} +} \ No newline at end of file diff --git a/src/element/video-tile.tsx b/src/element/video-tile.tsx index b26d272..05111c0 100644 --- a/src/element/video-tile.tsx +++ b/src/element/video-tile.tsx @@ -9,7 +9,6 @@ import { StatePill } from "./state-pill"; import { StreamState } from "@/index"; import { findTag, getHost } from "@/utils"; import { formatSats } from "@/number"; -import ZapStream from "/zap-stream.svg"; import { isContentWarningAccepted } from "./content-warning"; import { Tags } from "./tags"; @@ -37,7 +36,7 @@ export function VideoTile({
0 ? image : ZapStream) : ""})`, + backgroundImage: `url(${inView ? ((image?.length ?? 0) > 0 ? image : "/zap-stream.svg") : ""})`, }}>
{showStatus && } diff --git a/src/lang.json b/src/lang.json index 9b5e324..93a52f5 100644 --- a/src/lang.json +++ b/src/lang.json @@ -80,6 +80,9 @@ "79lLl+": { "defaultMessage": "Music" }, + "7UOvbT": { + "defaultMessage": "Offline" + }, "8YT6ja": { "defaultMessage": "Insert text to speak" }, @@ -191,6 +194,9 @@ "My6HwN": { "defaultMessage": "Ok, it's safe" }, + "NXI/XL": { + "defaultMessage": "Auto" + }, "O2Cy6m": { "defaultMessage": "Yes, I am over 18" }, @@ -261,6 +267,9 @@ "defaultMessage": "@ {rate}", "description": "Showing zap amount in USD @ rate" }, + "YagVIe": { + "defaultMessage": "{n}p" + }, "Z8ZOEY": { "defaultMessage": "This method is insecure. We recommend using a {nostrlink}" }, diff --git a/src/pages/stream-page.css b/src/pages/stream-page.css index 23b0804..5cf0c41 100644 --- a/src/pages/stream-page.css +++ b/src/pages/stream-page.css @@ -18,10 +18,6 @@ display: none; } -.stream-page .video-overlay { - position: relative; -} - .stream-page .video-content video { width: 100%; aspect-ratio: 16/9; diff --git a/src/translations/en.json b/src/translations/en.json index 6f69476..be761da 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -26,6 +26,7 @@ "6Z2pvJ": "Stream Providers", "6pr6hJ": "Minimum amount for text to speech", "79lLl+": "Music", + "7UOvbT": "Offline", "8YT6ja": "Insert text to speak", "9WRlF4": "Send", "9a9+ww": "Title", @@ -63,6 +64,7 @@ "KkIL3s": "No, I am under 18", "LknBsU": "Stream Key", "My6HwN": "Ok, it's safe", + "NXI/XL": "Auto", "O2Cy6m": "Yes, I am over 18", "OEW7yJ": "Zaps", "OKhRC6": "Share", @@ -86,6 +88,7 @@ "X2PZ7D": "Create Goal", "XgWvGA": "Reactions", "YPh5Nq": "@ {rate}", + "YagVIe": "{n}p", "Z8ZOEY": "This method is insecure. We recommend using a {nostrlink}", "ZmqxZs": "You can change this later", "acrOoz": "Continue", @@ -147,4 +150,4 @@ "y867Vs": "Volume", "yzKwBQ": "eg. nsec1xyz", "zVDHAu": "Zap Alert" -} +} \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index dba6799..c31cb38 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -7,6 +7,9 @@ module.exports = { "gray-1": "#171717", "gray-2": "#222", }, + animation: { + "ping-once": "ping 1s cubic-bezier(0, 0, 0.2, 1);", + }, }, }, plugins: [],