Skip to content

Commit

Permalink
Player volume button (#27)
Browse files Browse the repository at this point in the history
* Player volume button

* Added style

* Added visible trigger
  • Loading branch information
matvp91 authored Sep 19, 2024
1 parent 48e693e commit 6c4eca4
Show file tree
Hide file tree
Showing 14 changed files with 172 additions and 67 deletions.
2 changes: 1 addition & 1 deletion packages/dashboard/src/components/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "./worker";

const uri = monaco.Uri.parse("mixwave/body.json");
const model = monaco.editor.createModel(
["{", ' "assetId": ""', "}"].join("\n"),
["{", ' "assetId": "f2617a1f-57be-4d41-836f-350e4ac7a765"', "}"].join("\n"),
"json",
uri,
);
Expand Down
15 changes: 2 additions & 13 deletions packages/dashboard/src/pages/ApiPage.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
import { StretchLoader } from "@/components/StretchLoader";
import { lazy, Suspense } from "react";
import { OpenApiReference } from "@/components/OpenApiReference";
import "@scalar/api-reference-react/style.css";

const LazyOpenApiReference = lazy(() =>
import("@/components/OpenApiReference").then((mod) => ({
default: mod.OpenApiReference,
})),
);

export function ApiPage() {
return (
<Suspense fallback={<StretchLoader />}>
<LazyOpenApiReference url={import.meta.env.VITE_API_URL} />
</Suspense>
);
return <OpenApiReference url={import.meta.env.VITE_API_URL} />;
}
89 changes: 40 additions & 49 deletions packages/dashboard/src/pages/PlayerPage.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import { useEffect, useState, lazy, Suspense } from "react";
import { useEffect, useState } from "react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Alert } from "@/components/ui/alert";
import { StretchLoader } from "@/components/StretchLoader";

const LazyEditor = lazy(() =>
import("@/components/editor/Editor").then((mod) => ({ default: mod.Editor })),
);

const LazyPlayer = lazy(() =>
import("@/components/player/Player").then((mod) => ({ default: mod.Player })),
);
import { Editor } from "@/components/editor/Editor";
import { Player } from "@/components/player/Player";

export function PlayerPage() {
const [schema, setSchema] = useState<object>();
Expand Down Expand Up @@ -53,45 +46,43 @@ export function PlayerPage() {
};

return (
<Suspense fallback={<StretchLoader />}>
<div className="min-h-full flex grow">
<div className="basis-1/2 min-w-0">
<LazyEditor
schema={schema}
title={
<div className="text-xs">
<span className="font-bold bg-white/20 py-1 px-2 mr-1 rounded-md">
POST
</span>{" "}
{import.meta.env.VITE_STITCHER_URL}/session
</div>
}
onSave={onSave}
/>
</div>
<div className="basis-1/2 p-4">
{masterUrl ? (
<>
<LazyPlayer url={masterUrl} />
<div className="mt-4">
<Label>Playlist URL</Label>
<Input
value={masterUrl}
onClick={(event) => {
(event.target as HTMLInputElement).select();
}}
onChange={() => {}}
/>
</div>
</>
) : null}
{error ? (
<Alert variant="destructive" className="text-xs">
<pre>{JSON.stringify(error, null, 2)}</pre>
</Alert>
) : null}
</div>
<div className="min-h-full flex grow">
<div className="basis-1/2 min-w-0">
<Editor
schema={schema}
title={
<div className="text-xs">
<span className="font-bold bg-white/20 py-1 px-2 mr-1 rounded-md">
POST
</span>{" "}
{import.meta.env.VITE_STITCHER_URL}/session
</div>
}
onSave={onSave}
/>
</div>
<div className="basis-1/2 p-4">
{masterUrl ? (
<>
<Player url={masterUrl} />
<div className="mt-4">
<Label>Playlist URL</Label>
<Input
value={masterUrl}
onClick={(event) => {
(event.target as HTMLInputElement).select();
}}
onChange={() => {}}
/>
</div>
</>
) : null}
{error ? (
<Alert variant="destructive" className="text-xs">
<pre>{JSON.stringify(error, null, 2)}</pre>
</Alert>
) : null}
</div>
</Suspense>
</div>
);
}
2 changes: 1 addition & 1 deletion packages/dashboard/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
"./node_modules/@mixwave/player/dist/**/*.{js,ts,jsx,tsx}",
"./node_modules/@mixwave/player/dist/*.{js,ts,jsx,tsx}",
],
prefix: "",
theme: {
Expand Down
9 changes: 9 additions & 0 deletions packages/player/lib/facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export class HlsFacade extends EventEmitter<Events> {
mediaOn("seeked", () => {
this.pollTime_();
});

mediaOn("volumechange", () => {
this.setState_({ volume: this.media_.volume });
});
}

private initHlsListeners_() {
Expand Down Expand Up @@ -185,6 +189,7 @@ export class HlsFacade extends EventEmitter<Events> {
audioTracks,
subtitleTracks,
slot: null,
volume: this.media_.volume,
};

this.pollTime_();
Expand Down Expand Up @@ -268,6 +273,10 @@ export class HlsFacade extends EventEmitter<Events> {
this.mgr_.integrated.seekTo(targetTime);
}

setVolume(volume: number) {
this.media_.volume = volume;
}

setQuality(id: number | null) {
this.setState_({ autoQuality: id === null });
this.hls.nextLevel = id ? id - 1 : -1;
Expand Down
1 change: 1 addition & 0 deletions packages/player/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type State = {
audioTracks: AudioTrack[];
subtitleTracks: SubtitleTrack[];
slot: Slot | null;
volume: number;
};

export type Events = {
Expand Down
5 changes: 5 additions & 0 deletions packages/player/lib/ui/components/Controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useState } from "react";
import { Center } from "./Center";
import { Label } from "./Label";
import { useFullscreen } from "../hooks/useFullscreen";
import { VolumeButton } from "./VolumeButton";
import type { State, HlsFacade } from "../..";
import type { Metadata } from "../types";

Expand Down Expand Up @@ -102,6 +103,10 @@ export function Controls({ facade, state, metadata }: ControlsProps) {
<ForwardIcon className="w-6 h-6 group-hover:scale-110 transition-transform origin-center" />
</SqButton>
) : null}
<VolumeButton
volume={state.volume}
setVolume={(volume) => facade.setVolume(volume)}
/>
<Label slot={state.slot} metadata={metadata} />
<div className="grow" />
<SqButton
Expand Down
4 changes: 3 additions & 1 deletion packages/player/lib/ui/components/SqButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ type SqButtonProps = {
children: React.ReactNode;
onClick: MouseEventHandler<HTMLButtonElement>;
onIdle?: () => void;
idleTime?: number;
selected?: boolean;
};

export function SqButton({
children,
onClick,
onIdle,
idleTime,
selected,
...rest
}: SqButtonProps) {
Expand All @@ -22,7 +24,7 @@ export function SqButton({
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
onIdle?.();
}, 200);
}, idleTime ?? 200);
};

return (
Expand Down
86 changes: 86 additions & 0 deletions packages/player/lib/ui/components/VolumeButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { SqButton } from "./SqButton";
import cn from "clsx";
import Volume0Icon from "../icons/volume-0.svg?react";
import Volume1Icon from "../icons/volume-1.svg?react";
import Volume2Icon from "../icons/volume-2.svg?react";
import VolumeMutedIcon from "../icons/volume-muted.svg?react";
import { CSSProperties, ReactEventHandler, useRef, useState } from "react";

const rangeStyle: CSSProperties = {
writingMode: "vertical-lr",
direction: "rtl",
verticalAlign: "middle",
};

type VolumeButtonProps = {
volume: number;
setVolume(volume: number): void;
};

export function VolumeButton({ volume, setVolume }: VolumeButtonProps) {
const [visible, setVisible] = useState(false);
const volumeRef = useRef<number>();

const onChange: ReactEventHandler<HTMLInputElement> = (event) => {
setVolume(event.currentTarget.valueAsNumber);
};

const onPointerLeave: ReactEventHandler<HTMLDivElement> = () => {
if (visible) {
setVisible(false);
}
};

let Icon = VolumeMutedIcon;
let iconStyle: CSSProperties = {};
if (volume > 0.8) {
Icon = Volume2Icon;
} else if (volume > 0.5) {
Icon = Volume1Icon;
iconStyle.left = "-1px";
} else if (volume > 0.2) {
Icon = Volume0Icon;
iconStyle.left = "-2px";
}

return (
<div className="group relative z-10" onPointerLeave={onPointerLeave}>
<div
className={cn(
"absolute w-full flex justify-center bottom-12 opacity-0 transition-opacity z-20 group-hover:opacity-100 pointer-events-none",
visible && "pointer-events-auto",
)}
>
<div className="px-2 py-3 mb-1 bg-black/85 text-white rounded-md border border-white/20">
<input
type="range"
style={rangeStyle}
className="cursor-pointer appearance-none w-4 bg-transparent [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-white/50 [&::-webkit-slider-runnable-track]:w-2 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:-ml-1 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white"
min={0}
max={1}
onChange={onChange}
value={volume}
step={0.1}
/>
</div>
</div>
<SqButton
onClick={() => {
if (volume !== 0) {
volumeRef.current = volume;
setVolume(0);
} else {
setVolume(volumeRef.current ?? 1);
}
}}
onIdle={() => setVisible(true)}
idleTime={0}
>
<Icon
className="relative w-6 h-6 group-hover:scale-110 transition-transform origin-center"
style={iconStyle}
/>
</SqButton>
</div>
);
}
4 changes: 4 additions & 0 deletions packages/player/lib/ui/icons/volume-0.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions packages/player/lib/ui/icons/volume-1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions packages/player/lib/ui/icons/volume-2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 6c4eca4

Please sign in to comment.