feat: custom player controls

closes #34
This commit is contained in:
Kieran 2023-12-05 11:10:57 +00:00
parent 348759f652
commit 7c321c70a0
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
8 changed files with 141 additions and 81 deletions

View File

@ -30,9 +30,6 @@
<symbol id="link" viewBox="0 0 32 32" fill="none">
<path d="M22 14L22 10M22 10H18M22 10L16 16M14.6667 10H13.2C12.0799 10 11.5198 10 11.092 10.218C10.7157 10.4097 10.4097 10.7157 10.218 11.092C10 11.5198 10 12.0799 10 13.2V18.8C10 19.9201 10 20.4802 10.218 20.908C10.4097 21.2843 10.7157 21.5903 11.092 21.782C11.5198 22 12.0799 22 13.2 22H18.8C19.9201 22 20.4802 22 20.908 21.782C21.2843 21.5903 21.5903 21.2843 21.782 20.908C22 20.4802 22 19.9201 22 18.8V17.3333" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="zap-stream" viewBox="0 0 160 160" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M82.4852 54.5094L87.7882 48.2773C87.8525 48.2098 87.9174 48.1429 87.9826 48.0768C94.4927 41.1346 103.63 36.8165 113.748 36.8165C133.516 36.8165 149.541 53.2997 149.541 73.6327C149.541 82.0501 146.795 89.8077 142.174 96.0093L142.197 96.029L141.843 96.4456C141.126 97.3799 140.364 98.2774 139.563 99.1352L87.9613 160L43.5147 158.617L58.9832 140.033L112.875 76.6987C114.038 75.3317 113.873 73.2807 112.506 72.1175C111.139 70.9544 109.088 71.1196 107.925 72.4865L71.2247 115.617C64.7813 121.963 55.8992 125.885 46.0917 125.885C26.4118 125.885 10.458 110.093 10.458 90.6136C10.458 81.6851 13.8096 73.5314 19.3355 67.318L76.4941 3.75969e-05L120.334 8.27526e-08L51.0699 81.3993C49.9068 82.7663 50.072 84.8173 51.4389 85.9805C52.8059 87.1437 54.857 86.9784 56.0201 85.6115L72.1945 66.6032C72.207 66.6164 72.2194 66.6297 72.2319 66.643L82.4852 54.5094Z" fill="white"/>
</symbol>
<symbol id="camera-plus" viewBox="0 0 24 24" fill="none">
<g>
<path d="M22 11.5V14.6C22 16.8402 22 17.9603 21.564 18.816C21.1805 19.5686 20.5686 20.1805 19.816 20.564C18.9603 21 17.8402 21 15.6 21H8.4C6.15979 21 5.03969 21 4.18404 20.564C3.43139 20.1805 2.81947 19.5686 2.43597 18.816C2 17.9603 2 16.8402 2 14.6V9.4C2 7.15979 2 6.03969 2.43597 5.18404C2.81947 4.43139 3.43139 3.81947 4.18404 3.43597C5.03969 3 6.15979 3 8.4 3H12.5M19 8V2M16 5H22M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@ -58,20 +55,10 @@
<path d="M12 5V19M5 12H19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</symbol>
<symbol id="toggle-off" viewBox="0 0 24 24" fill="none">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8 5C4.13401 5 1 8.13401 1 12C1 15.866 4.13401 19 8 19H16C19.866 19 23 15.866 23 12C23 8.13401 19.866 5 16 5H8ZM12 12C12 14.2091 10.2091 16 8 16C5.79086 16 4 14.2091 4 12C4 9.79086 5.79086 8 8 8C10.2091 8 12 9.79086 12 12Z"
fill="currentColor"
/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 5C4.13401 5 1 8.13401 1 12C1 15.866 4.13401 19 8 19H16C19.866 19 23 15.866 23 12C23 8.13401 19.866 5 16 5H8ZM12 12C12 14.2091 10.2091 16 8 16C5.79086 16 4 14.2091 4 12C4 9.79086 5.79086 8 8 8C10.2091 8 12 9.79086 12 12Z" fill="currentColor" />
</symbol>
<symbol id="toggle-on" viewBox="0 0 24 24" fill="none">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M16 5C19.866 5 23 8.13401 23 12C23 15.866 19.866 19 16 19H8C4.13401 19 1 15.866 1 12C1 8.13401 4.13401 5 8 5H16ZM12 12C12 14.2091 13.7909 16 16 16C18.2091 16 20 14.2091 20 12C20 9.79086 18.2091 8 16 8C13.7909 8 12 9.79086 12 12Z"
fill="currentColor"
/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 5C19.866 5 23 8.13401 23 12C23 15.866 19.866 19 16 19H8C4.13401 19 1 15.866 1 12C1 8.13401 4.13401 5 8 5H16ZM12 12C12 14.2091 13.7909 16 16 16C18.2091 16 20 14.2091 20 12C20 9.79086 18.2091 8 16 8C13.7909 8 12 9.79086 12 12Z" fill="currentColor" />
</symbol>
<symbol id="badge" viewBox="0 0 24 24" fill="none">
<path d="M8.87625 13.0953L4.70122 7.87653C4.44132 7.55166 4.31138 7.38922 4.21897 7.20834C4.13698 7.04787 4.07706 6.87705 4.04084 6.70052C4 6.50155 4 6.29354 4 5.8775V5.2C4 4.0799 4 3.51984 4.21799 3.09202C4.40973 2.71569 4.71569 2.40973 5.09202 2.21799C5.51984 2 6.0799 2 7.2 2H16.8C17.9201 2 18.4802 2 18.908 2.21799C19.2843 2.40973 19.5903 2.71569 19.782 3.09202C20 3.51984 20 4.0799 20 5.2V5.8775C20 6.29354 20 6.50155 19.9592 6.70052C19.9229 6.87705 19.863 7.04787 19.781 7.20834C19.6886 7.38922 19.5587 7.55166 19.2988 7.87652L15.1238 13.0953M5.00005 3L12.0001 12L19 3M15.5355 13.4645C17.4882 15.4171 17.4882 18.5829 15.5355 20.5355C13.5829 22.4882 10.4171 22.4882 8.46446 20.5355C6.51185 18.5829 6.51185 15.4171 8.46446 13.4645C10.4171 11.5118 13.5829 11.5118 15.5355 13.4645Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
@ -95,5 +82,16 @@
<symbol id="widget" viewBox="0 0 24 24" fill="none">
<path d="M11 4H7.8C6.11984 4 5.27976 4 4.63803 4.32698C4.07354 4.6146 3.6146 5.07354 3.32698 5.63803C3 6.27976 3 7.11984 3 8.8V16.2C3 17.8802 3 18.7202 3.32698 19.362C3.6146 19.9265 4.07354 20.3854 4.63803 20.673C5.27976 21 6.11984 21 7.8 21H15.2C16.8802 21 17.7202 21 18.362 20.673C18.9265 20.3854 19.3854 19.9265 19.673 19.362C20 18.7202 20 17.8802 20 16.2V13M13 17H7M15 13H7M20.1213 3.87868C21.2929 5.05025 21.2929 6.94975 20.1213 8.12132C18.9497 9.29289 17.0503 9.29289 15.8787 8.12132C14.7071 6.94975 14.7071 5.05025 15.8787 3.87868C17.0503 2.70711 18.9497 2.70711 20.1213 3.87868Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="play" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.00625 2.8023C8.0182 2.81028 8.03019 2.81827 8.04222 2.82629L18.591 9.85878C18.8962 10.0622 19.1792 10.2509 19.3965 10.4261C19.6234 10.6091 19.8908 10.8628 20.0447 11.2339C20.2481 11.7244 20.2481 12.2756 20.0447 12.7661C19.8908 13.1372 19.6234 13.3909 19.3965 13.5738C19.1792 13.7491 18.8962 13.9377 18.591 14.1412L8.00628 21.1977C7.63319 21.4464 7.29772 21.6701 7.01305 21.8244C6.72818 21.9788 6.33717 22.1552 5.8808 22.1279C5.29705 22.0931 4.75779 21.8045 4.40498 21.3381C4.12916 20.9735 4.05905 20.5503 4.02949 20.2276C3.99994 19.9052 3.99997 19.502 4 19.0536L4 4.98962C4 4.97516 4 4.96075 4 4.94638C3.99997 4.49798 3.99994 4.09479 4.02949 3.77236C4.05905 3.44971 4.12916 3.02651 4.40498 2.6619C4.75779 2.19552 5.29705 1.90692 5.8808 1.87207C6.33717 1.84482 6.72818 2.02123 7.01305 2.17561C7.29771 2.32988 7.63317 2.55355 8.00625 2.8023Z" fill="currentColor"/>
</symbol>
<symbol id="pause" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 1C5.92487 1 1 5.92487 1 12C1 18.0751 5.92487 23 12 23C18.0751 23 23 18.0751 23 12C23 5.92487 18.0751 1 12 1ZM10.5 9C10.5 8.44772 10.0523 8 9.5 8C8.94772 8 8.5 8.44772 8.5 9V15C8.5 15.5523 8.94772 16 9.5 16C10.0523 16 10.5 15.5523 10.5 15V9ZM15.5 9C15.5 8.44772 15.0523 8 14.5 8C13.9477 8 13.5 8.44772 13.5 9V15C13.5 15.5523 13.9477 16 14.5 16C15.0523 16 15.5 15.5523 15.5 15V9Z" fill="currentColor"/>
</symbol>
<symbol id="volume" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.1639 4.18822C19.6123 3.8657 20.2372 3.96769 20.5597 4.41602C22.0953 6.55072 23 9.17119 23 12C23 14.8288 22.0953 17.4493 20.5597 19.584C20.2372 20.0323 19.6123 20.1343 19.1639 19.8118C18.7156 19.4893 18.6136 18.8644 18.9361 18.416C20.2352 16.6102 21 14.3959 21 12C21 9.60407 20.2352 7.38974 18.9361 5.58396C18.6136 5.13563 18.7156 4.51073 19.1639 4.18822Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.1732 7.17981C15.6262 6.86385 16.2495 6.97492 16.5655 7.4279C17.4696 8.7241 18 10.3016 18 12C18 13.6984 17.4696 15.2759 16.5655 16.5721C16.2495 17.0251 15.6262 17.1361 15.1732 16.8202C14.7202 16.5042 14.6092 15.8809 14.9251 15.4279C15.6027 14.4564 16 13.2761 16 12C16 10.7239 15.6027 9.54355 14.9251 8.57209C14.6092 8.11912 14.7202 7.49577 15.1732 7.17981Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.3823 2.71172C10.861 2.67405 11.3288 2.86781 11.6406 3.23293C11.9199 3.55988 11.9642 3.95313 11.9811 4.14402C12.0001 4.35799 12 4.62375 12 4.89413C12 4.90653 12 4.91895 12 4.93136L12 19.1059C12 19.3762 12.0001 19.642 11.9811 19.856C11.9642 20.0469 11.9199 20.4401 11.6406 20.7671C11.3288 21.1322 10.861 21.3259 10.3823 21.2883C9.95368 21.2545 9.64424 21.0078 9.4973 20.8848C9.33259 20.7469 9.14469 20.559 8.95353 20.3677L5.76153 17.1757C5.6689 17.0831 5.6225 17.037 5.58738 17.005L5.58472 17.0026L5.58114 17.0024C5.53365 17.0002 5.46826 17 5.33726 17L3.56812 17C3.31574 17 3.06994 17.0001 2.86178 16.983C2.63318 16.9644 2.36345 16.9203 2.09202 16.782C1.7157 16.5903 1.40974 16.2843 1.21799 15.908C1.07969 15.6366 1.03563 15.3668 1.01695 15.1382C0.999943 14.9301 0.999973 14.6843 1 14.4319L1.00001 9.59999C1.00001 9.58935 1 9.57872 1 9.5681C0.999973 9.31571 0.999943 9.06992 1.01695 8.86176C1.03563 8.63317 1.07969 8.36344 1.21799 8.09201C1.40974 7.71569 1.7157 7.40973 2.09202 7.21798C2.36345 7.07968 2.63318 7.03562 2.86178 7.01694C3.06993 6.99993 3.31572 6.99996 3.56811 6.99999C3.57873 6.99999 3.58936 6.99999 3.60001 6.99999H5.33726C5.46826 6.99999 5.53365 6.99976 5.58114 6.99758L5.58472 6.99741L5.58738 6.995C5.6225 6.96295 5.6689 6.91689 5.76153 6.82426L8.92721 3.65857C8.936 3.64979 8.94477 3.64101 8.95354 3.63224C9.1447 3.44102 9.33259 3.25308 9.4973 3.11518C9.64424 2.99217 9.95368 2.74546 10.3823 2.71172Z" fill="currentColor"/>
</symbol>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -157,7 +157,7 @@ export function ChatMessage({
const emojiName = e.replace(/:/g, "");
const emoji = isCustomEmojiReaction && getEmojiById(emojiName);
return (
<div className="message-reaction-container">
<div className="message-reaction-container" key={`${ev.id}-${emojiName}`}>
{isCustomEmojiReaction && emoji ? (
<span className="message-reaction">
<EmojiComponent name={emoji[1]} url={emoji[2]} />

View File

@ -1,6 +1,10 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import Hls from "hls.js";
import { useEffect, useMemo, useRef, useState } from "react";
import { WISH } from "@/wish";
import { FormattedMessage } from "react-intl";
import { StatePill } from "./state-pill";
import { StreamState } from "..";
import { Icon } from "./icon";
export enum VideoStatus {
Online = "online",
@ -15,9 +19,14 @@ export interface VideoPlayerProps {
export function LiveVideoPlayer(props: VideoPlayerProps) {
const video = useRef<HTMLVideoElement>(null);
const hlsObj = useRef<Hls>(null);
const streamCached = useMemo(() => props.stream, [props.stream]);
const [status, setStatus] = useState<VideoStatus>();
const [src, setSrc] = useState<string>();
const [levels, setLevels] = useState<Array<{ level: number, height: number }>>();
const [level, setLevel] = useState<number>(-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 (
<div className="video-overlay">
<div className={status}>
<div>{status}</div>
<div className="relative">
{status === VideoStatus.Online && <div className="absolute opacity-0 hover:opacity-100 transition-opacity w-full h-full z-20 bg-[#00000055]" onClick={() => {
if (video.current) {
if (playState) {
video.current.pause();
} else {
video.current.play();
}
}
}}>
<div className="absolute w-full h-full flex items-center justify-center pointer">
<Icon name={playState ? "pause" : "play"} size={80} />
</div>
<div className="absolute flex gap-1 bottom-0 w-full bg-[rgba(0,0,0,0.5)]" onClick={e => e.stopPropagation()}>
<div className="grow">
<StatePill state={StreamState.Live} />
</div>
<div className="flex gap-1 items-center">
<Icon name="volume" />
<div className="relative w-[104px] h-full border" onMouseDown={changeVolume} onMouseMove={e => {
if (e.buttons > 0) {
changeVolume(e);
}
}}>
<div className="absolute h-full w-[4px] bg-white" style={{
left: `${Math.floor(100 * volume)}px`
}}></div>
</div>
</div>
<div>
<select onChange={e => setLevel(Number(e.target.value))}>
<option value={-1}>
<FormattedMessage defaultMessage="Auto" id="NXI/XL" />
</option>
{levels?.map(v => <option value={v.level} key={v.level}>
<FormattedMessage defaultMessage="{n}p" id="YagVIe" values={{ n: v.height }} />
</option>)}
</select>
</div>
</div>
</div>}
{
status === VideoStatus.Offline && <div className="absolute w-full h-full z-20 bg-[#000000aa] flex items-center justify-center text-3xl font-bold uppercase">
<FormattedMessage defaultMessage="Offline" id="7UOvbT" />
</div>
}
<video
className="z-10"
ref={video}
autoPlay={true}
poster={props.poster}
src={src}
playsInline={true}
controls={status === VideoStatus.Online}
/>
</div >
);
}
export function WebRTCPlayer(props: VideoPlayerProps) {
const video = useRef<HTMLVideoElement>(null);
const streamCached = useMemo(
() => "https://customer-uu10flpvos4pfhgu.cloudflarestream.com/7634aee1af35a2de4ac13ca3d1718a8b/webRTC/play",
[props.stream]
);
const [status] = useState<VideoStatus>();
//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 (
<div className="video-overlay">
<div className={status}>
<div>{status}</div>
</div>
<video ref={video} autoPlay={true} poster={props.poster} controls={status === VideoStatus.Online} />
</div>
);
}

View File

@ -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({
<Link to={`/${link}`} className={`video-tile${contentWarning ? " nsfw" : ""}`} ref={ref} state={ev}>
<div
style={{
backgroundImage: `url(${inView ? ((image?.length ?? 0) > 0 ? image : ZapStream) : ""})`,
backgroundImage: `url(${inView ? ((image?.length ?? 0) > 0 ? image : "/zap-stream.svg") : ""})`,
}}></div>
<span className="pill-box">
{showStatus && <StatePill state={status as StreamState} />}

View File

@ -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}"
},

View File

@ -18,10 +18,6 @@
display: none;
}
.stream-page .video-overlay {
position: relative;
}
.stream-page .video-content video {
width: 100%;
aspect-ratio: 16/9;

View File

@ -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",

View File

@ -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: [],