feat: player overlay styles

This commit is contained in:
Kieran 2023-12-05 16:32:54 +00:00
parent 296789978c
commit 130c6048a2
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
19 changed files with 147 additions and 146 deletions

View File

@ -86,7 +86,7 @@
<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"/>
<path d="M20.25 4.5V19.5C20.25 19.8978 20.092 20.2794 19.8107 20.5607C19.5294 20.842 19.1478 21 18.75 21H15C14.6022 21 14.2206 20.842 13.9393 20.5607C13.658 20.2794 13.5 19.8978 13.5 19.5V4.5C13.5 4.10218 13.658 3.72064 13.9393 3.43934C14.2206 3.15804 14.6022 3 15 3H18.75C19.1478 3 19.5294 3.15804 19.8107 3.43934C20.092 3.72064 20.25 4.10218 20.25 4.5ZM9 3H5.25C4.85218 3 4.47064 3.15804 4.18934 3.43934C3.90804 3.72064 3.75 4.10218 3.75 4.5V19.5C3.75 19.8978 3.90804 20.2794 4.18934 20.5607C4.47064 20.842 4.85218 21 5.25 21H9C9.39782 21 9.77936 20.842 10.0607 20.5607C10.342 20.2794 10.5 19.8978 10.5 19.5V4.5C10.5 4.10218 10.342 3.72064 10.0607 3.43934C9.77936 3.15804 9.39782 3 9 3Z" 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"/>
@ -103,5 +103,8 @@
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.45711 15.5429C8.84763 15.9334 8.84763 16.5666 8.45711 16.9571L5.62868 19.7855C5.23815 20.1761 4.60499 20.1761 4.21447 19.7855C3.82394 19.395 3.82394 18.7618 4.21447 18.3713L7.04289 15.5429C7.43342 15.1524 8.06658 15.1524 8.45711 15.5429Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.42157 4.50157C4.8121 4.11105 5.44526 4.11105 5.83579 4.50157L7.95711 6.62289C8.34763 7.01342 8.34763 7.64658 7.95711 8.03711C7.56658 8.42763 6.93342 8.42763 6.54289 8.03711L4.42157 5.91579C4.03105 5.52526 4.03105 4.8921 4.42157 4.50157Z" fill="currentColor"/>
</symbol>
<symbol id="fullscreen" viewBox="0 0 24 24" fill="none">
<path d="M20.25 3.75H3.75C3.35218 3.75 2.97064 3.90804 2.68934 4.18934C2.40804 4.47064 2.25 4.85218 2.25 5.25V18.75C2.25 19.1478 2.40804 19.5294 2.68934 19.8107C2.97064 20.092 3.35218 20.25 3.75 20.25H20.25C20.6478 20.25 21.0294 20.092 21.3107 19.8107C21.592 19.5294 21.75 19.1478 21.75 18.75V5.25C21.75 4.85218 21.592 4.47064 21.3107 4.18934C21.0294 3.90804 20.6478 3.75 20.25 3.75ZM8.25 18H5.25C5.05109 18 4.86032 17.921 4.71967 17.7803C4.57902 17.6397 4.5 17.4489 4.5 17.25V14.25C4.5 14.0511 4.57902 13.8603 4.71967 13.7197C4.86032 13.579 5.05109 13.5 5.25 13.5C5.44891 13.5 5.63968 13.579 5.78033 13.7197C5.92098 13.8603 6 14.0511 6 14.25V16.5H8.25C8.44891 16.5 8.63968 16.579 8.78033 16.7197C8.92098 16.8603 9 17.0511 9 17.25C9 17.4489 8.92098 17.6397 8.78033 17.7803C8.63968 17.921 8.44891 18 8.25 18ZM19.5 9.75C19.5 9.94891 19.421 10.1397 19.2803 10.2803C19.1397 10.421 18.9489 10.5 18.75 10.5C18.5511 10.5 18.3603 10.421 18.2197 10.2803C18.079 10.1397 18 9.94891 18 9.75V7.5H15.75C15.5511 7.5 15.3603 7.42098 15.2197 7.28033C15.079 7.13968 15 6.94891 15 6.75C15 6.55109 15.079 6.36032 15.2197 6.21967C15.3603 6.07902 15.5511 6 15.75 6H18.75C18.9489 6 19.1397 6.07902 19.2803 6.21967C19.421 6.36032 19.5 6.55109 19.5 6.75V9.75Z" fill="currentColor"/>
</symbol>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -5,7 +5,7 @@
}
.collapsible-media a {
color: var(--text-link);
color: var(--primary);
word-wrap: break-word;
}
@ -15,7 +15,7 @@
}
.url-preview {
color: var(--text-link);
color: var(--primary);
cursor: zoom-in;
}

View File

@ -81,11 +81,11 @@
}
.live-chat .message.streamer .profile {
color: #f838d9;
color: var(--primary);
}
.live-chat .message a {
color: #f838d9;
color: var(--primary);
display: inline-flex;
}
@ -106,7 +106,7 @@
}
.live-chat .zap-content a {
color: var(--text-link);
color: var(--primary);
}
.top-zappers {

View File

@ -2,9 +2,10 @@
import Hls from "hls.js";
import { useEffect, useMemo, useRef, useState } from "react";
import { FormattedMessage } from "react-intl";
import { StatePill } from "./state-pill";
import { StreamState } from "..";
import { Icon } from "./icon";
import { ProgressBar } from "./progress-bar";
import { Menu, MenuItem } from "@szhsin/react-menu";
export enum VideoStatus {
Online = "online",
@ -52,12 +53,16 @@ export default function LiveVideoPlayer(props: VideoPlayerProps) {
});
hls.on(Hls.Events.MANIFEST_PARSED, () => {
setStatus(VideoStatus.Online);
setLevels(
hls.levels.map((a, i) => ({
setLevels([
{
level: -1,
height: 0,
},
...hls.levels.map((a, i) => ({
level: i,
height: a.height,
}))
);
})),
]);
});
hls.on(Hls.Events.LEVEL_SWITCHING, (_, l) => {
console.debug("HLS Level Switch", l);
@ -106,31 +111,6 @@ export default function LiveVideoPlayer(props: VideoPlayerProps) {
}
}, [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);
}
}
function seek(e: React.MouseEvent) {
if (e.currentTarget === e.target) {
const bb = (e.target as HTMLDivElement).getBoundingClientRect();
const x = e.clientX - bb.x;
const pos = Math.max(0, Math.min(1.0, x / bb.width));
if (video.current && maxPosition) {
const ct = maxPosition * pos;
video.current.currentTime = ct;
setPosition(ct);
}
}
}
function playStateToIcon() {
switch (playState) {
case "playing":
@ -141,68 +121,87 @@ export default function LiveVideoPlayer(props: VideoPlayerProps) {
return "loading";
}
}
function togglePlay() {
if (video.current) {
if (playState === "playing") {
video.current.pause();
} else if (playState === "paused") {
video.current.play();
}
}
}
function levelName(l: number) {
if (l === -1) {
return <FormattedMessage defaultMessage="AUTO" id="o8pHw3" />;
} else {
const h = levels?.find(a => a.level === l)?.height;
return <FormattedMessage defaultMessage="{n}p" id="YagVIe" values={{ n: h }} />;
}
}
return (
<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 === "playing") {
video.current.pause();
} else if (playState === "paused") {
video.current.play();
}
}
}}>
className="absolute opacity-0 hover:opacity-90 transition-opacity w-full h-full z-20 bg-[#00000055]"
onClick={() => togglePlay()}>
<div className="absolute w-full h-full flex items-center justify-center pointer">
<Icon
name={playStateToIcon()}
size={80}
className={playState === "loading" ? "animate-spin" : "animate-ping-once"}
/>
<Icon name={playStateToIcon()} size={80} className={playState === "loading" ? "animate-spin" : ""} />
</div>
<div className="absolute flex gap-1 bottom-0 w-full bg-[rgba(0,0,0,0.5)]" onClick={e => e.stopPropagation()}>
<div className="flex grow gap-1">
<StatePill state={props.status as StreamState} />
{props.status === StreamState.Ended && playState && maxPosition && position && (
<div className="relative w-full h-full border" onClick={seek}>
<div
className="absolute h-full w-[4px] bg-white"
style={{
width: `${((position / maxPosition) * 100).toFixed(1)}%`,
}}></div>
</div>
<div
className="absolute flex items-center gap-1 bottom-0 w-full bg-primary h-[40px]"
onClick={e => e.stopPropagation()}>
<div className="flex grow gap-1 items-center">
<div className="px-5 py-2 pointer" onClick={() => togglePlay()}>
<Icon name={playStateToIcon()} className={playState === "loading" ? "animate-spin" : ""} />
</div>
<div className="px-3 py-2 uppercase font-bold tracking-wide hover:bg-primary-hover">{props.status}</div>
{props.status === StreamState.Ended && maxPosition !== undefined && position !== undefined && (
<ProgressBar
value={position / maxPosition}
setValue={v => {
const ct = maxPosition * v;
if (video.current) {
video.current.currentTime = ct;
}
setPosition(ct);
}}
marker={<div className="w-[16px] h-[16px] mt-[-8px] rounded-full bg-white"></div>}
style={{ width: "100%", height: "4px" }}
/>
)}
</div>
<div className="flex gap-1 items-center">
<div className="flex gap-1 items-center h-full py-2">
<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>
<ProgressBar value={volume} setValue={v => setVolume(v)} style={{ width: "100px", height: "100%" }} />
</div>
<div>
<select onChange={e => setLevel(Number(e.target.value))}>
<option value={-1}>
<FormattedMessage defaultMessage="Auto" id="NXI/XL" />
</option>
<Menu
direction="top"
align="center"
menuButton={<div className="px-3 py-2 tracking-wide pointer">{levelName(level)}</div>}
menuClassName="bg-primary w-fit">
{levels?.map(v => (
<option value={v.level} key={v.level}>
<FormattedMessage defaultMessage="{n}p" id="YagVIe" values={{ n: v.height }} />
</option>
<MenuItem
value={v.level}
key={v.level}
onClick={() => setLevel(v.level)}
className="bg-primary px-3 py-2 text-white">
{levelName(v.level)}
</MenuItem>
))}
</select>
</Menu>
</div>
<div
className="px-3 py-2 pointer"
onClick={() => {
if (video.current) {
video.current.requestFullscreen();
}
}}>
<Icon name="fullscreen" size={24} />
</div>
</div>
</div>

View File

@ -5,7 +5,7 @@
}
.markdown a {
color: var(--text-link);
color: var(--primary);
}
.markdown blockquote {

View File

@ -48,7 +48,7 @@
.new-stream .tos-link {
cursor: pointer;
color: var(--text-link);
color: var(--primary);
}
.new-stream .tos-link:hover {

View File

@ -0,0 +1,35 @@
import { HTMLProps, ReactNode } from "react";
type ProgressBarProps = {
value: number;
setValue: (n: number) => void;
marker?: ReactNode;
} & Omit<HTMLProps<HTMLDivElement>, "width">;
export function ProgressBar({ value, setValue, marker, ...props }: ProgressBarProps) {
function onValue(e: React.MouseEvent) {
const bb = (e.currentTarget as HTMLDivElement).getBoundingClientRect();
const x = e.clientX - bb.x;
const pos = Math.max(0, Math.min(1.0, x / bb.width));
setValue(pos);
}
return (
<div
{...props}
className="relative pointer border bg-[rgba(255,255,255,0.25)]"
onMouseDown={onValue}
onMouseMove={e => {
if (e.buttons > 0) {
onValue(e);
}
}}>
<div
className="absolute h-full bg-white"
style={{
width: `${Math.ceil(100 * value)}%`,
}}>
{marker && <div className="absolute right-0 flex items-center justify-center">{marker}</div>}
</div>
</div>
);
}

View File

@ -1,6 +1,13 @@
import { HTMLProps } from "react";
import "./state-pill.css";
import { StreamState } from "@/index";
export function StatePill({ state }: { state: StreamState }) {
return <span className={`state pill${state === StreamState.Live ? " live" : ""}`}>{state}</span>;
type StatePillProps = { state: StreamState } & HTMLProps<HTMLSpanElement>;
export function StatePill({ state, ...props }: StatePillProps) {
return (
<span {...props} className={`uppercase font-white pill ${state === StreamState.Live ? "bg-primary" : "bg-gray-1"}`}>
{state}
</span>
);
}

View File

@ -138,7 +138,7 @@
}
.help-text a {
color: var(--text-link);
color: var(--primary);
}
.add-button {

View File

@ -15,12 +15,12 @@ export function Tags({ children, max, ev }: { children?: ReactNode; max?: number
<>
{children}
{status === StreamState.Planned && (
<span className="pill">
<span className="pill bg-gray-1">
{status === StreamState.Planned ? <FormattedMessage defaultMessage="Starts " id="0hNxBy" /> : ""}
</span>
)}
{tags.map(a => (
<a href={`/t/${encodeURIComponent(a)}`} className="pill" key={a}>
<a href={`/t/${encodeURIComponent(a)}`} className="pill bg-gray-1" key={a}>
{a}
</a>
))}

View File

@ -10,7 +10,6 @@
}
.rta__entity--selected .emoji-item {
text-decoration: none;
background: #f838d9;
}
.emoji-item,

View File

@ -23,5 +23,5 @@
color: white;
}
.toggle[data-state="on"] svg {
color: var(--text-link);
color: var(--primary);
}

View File

@ -17,7 +17,7 @@ body {
--gap-s: 16px;
--header-height: 48px;
--text-muted: #797979;
--text-link: #f838d9;
--primary: #f838d9;
--text-danger: #ff563f;
--surface: #222;
--border: #171717;
@ -65,7 +65,6 @@ a {
}
.pill {
background: #171717;
padding: 4px 8px;
border-radius: 9px;
font-weight: 700;
@ -75,11 +74,6 @@ a {
user-select: none;
}
.pill.live {
background: #f838d9;
color: white;
}
.w-max {
width: stretch;
width: -webkit-fill-available;

View File

@ -38,9 +38,6 @@
"2/2yg+": {
"defaultMessage": "Add"
},
"2CGh/0": {
"defaultMessage": "live"
},
"3HwrQo": {
"defaultMessage": "Zap!"
},
@ -179,9 +176,6 @@
"K3r6DQ": {
"defaultMessage": "Delete"
},
"K3uH1C": {
"defaultMessage": "offline"
},
"K7AkdL": {
"defaultMessage": "Show"
},
@ -197,9 +191,6 @@
"My6HwN": {
"defaultMessage": "Ok, it's safe"
},
"NXI/XL": {
"defaultMessage": "Auto"
},
"O2Cy6m": {
"defaultMessage": "Yes, I am over 18"
},
@ -375,6 +366,9 @@
"nwA8Os": {
"defaultMessage": "Add card"
},
"o8pHw3": {
"defaultMessage": "AUTO"
},
"oHPB8Q": {
"defaultMessage": "Zap {name}"
},

View File

@ -20,6 +20,7 @@ import { Text } from "@/element/text";
import { StreamState } from "@/index";
import { findTag } from "@/utils";
import { formatSats } from "@/number";
import { StatePill } from "@/element/state-pill";
function Zapper({ pubkey, total }: { pubkey: string; total: number }) {
return (
@ -87,20 +88,7 @@ export function ProfilePage() {
) : (
<img className="avatar" alt={profile?.name || link.id} src={placeholder} />
)}
<div className="status-indicator">
{isLive ? (
<div className="live-button pill live" onClick={goToLive}>
<Icon name="signal" />
<span>
<FormattedMessage defaultMessage="live" id="2CGh/0" />
</span>
</div>
) : (
<span className="pill offline">
<FormattedMessage defaultMessage="offline" id="K3uH1C" />
</span>
)}
</div>
<div className="status-indicator">{isLive && <StatePill state={StreamState.Live} onClick={goToLive} />}</div>
<div className="profile-actions">
{zapTarget && (
<SendZapsDialog

View File

@ -78,23 +78,6 @@
padding: 12px 16px;
}
.pill {
font-weight: 700;
font-size: 14px;
line-height: 18px;
color: #a7a7a7;
}
.pill.live {
color: inherit;
text-transform: uppercase;
}
.pill.viewers {
color: white;
background: rgba(23, 23, 23, 0.7);
}
.info h1 {
margin: 0 0 8px 0;
font-weight: 600;

View File

@ -59,12 +59,12 @@ function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedNostrEvent })
<div className="tags">
<StatePill state={status as StreamState} />
{viewers > 0 && (
<span className="pill viewers">
<span className="pill bg-gray-1">
<FormattedMessage defaultMessage="{n} viewers" id="3adEeb" values={{ n: formatSats(viewers) }} />
</span>
)}
{status === StreamState.Live && (
<span className="pill">
<span className="pill bg-gray-1">
<StreamTimer ev={ev} />
</span>
)}

View File

@ -12,7 +12,6 @@
"1EYCdR": "Tags",
"1qsXCO": "eg. name@wallet.com",
"2/2yg+": "Add",
"2CGh/0": "live",
"3HwrQo": "Zap!",
"3adEeb": "{n} viewers",
"3df560": "Login with private key",
@ -59,13 +58,11 @@
"JEsxDw": "Uploading...",
"Jq3FDz": "Content",
"K3r6DQ": "Delete",
"K3uH1C": "offline",
"K7AkdL": "Show",
"KdYELp": "Get stream key",
"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",
@ -124,6 +121,7 @@
"nBCvvJ": "Topup",
"nOaArs": "Setup Profile",
"nwA8Os": "Add card",
"o8pHw3": "AUTO",
"oHPB8Q": "Zap {name}",
"oZrFyI": "Stream type should be HLS",
"pO/lPX": "Scheduled for {date}",

View File

@ -6,6 +6,7 @@ module.exports = {
colors: {
"gray-1": "#171717",
"gray-2": "#222",
primary: "var(--primary)",
},
animation: {
"ping-once": "ping 1s cubic-bezier(0, 0, 0.2, 1);",