jevstrudel.git / website / src / jev / AudioControls.jsx
AudioControls.jsx58 lines · 2.3 KB · raw
1// Beside play: pause, and the listener's volume (audioControls.mjs).
2import { useSyncExternalStore } from 'react';
3import { audioControls, pause, setVolume, unpause } from './audioControls.mjs';
4import { partyStore } from './party.mjs';
5import { sandbox } from './sandbox.mjs';
6
7const useAudioControls = () => useSyncExternalStore(audioControls.subscribe, audioControls.get, audioControls.get);
8
9// Shown while something plays, here or in the sandbox: pause holds it where
10// it is, and the same button goes on.
11export function PauseButton({ context, isEmbedded = false }) {
12  const { paused } = useAudioControls();
13  const sb = useSyncExternalStore(sandbox.subscribe, sandbox.get, sandbox.get);
14  const party = useSyncExternalStore(partyStore.subscribe, partyStore.get, partyStore.get);
15  const playing = context.started || sb.status === 'playing';
16  // a listening party runs on one clock for everyone: no pausing in it
17  const inParty = party.status !== 'off' && party.status !== 'ended';
18  if ((!playing && !paused) || (inParty && !paused)) return null;
19  return (
20    <button
21      onClick={() => (paused ? unpause() : pause())}
22      data-testid="jev-pause"
23      aria-pressed={paused}
24      title={paused ? 'resume: go on from where it paused' : 'pause: hold the song where it is'}
25      className="flex items-center space-x-1 px-2 hover:opacity-50"
26    >
27      <span aria-hidden>{paused ? '▶' : '⏸'}</span>
28      {!isEmbedded && <span className="hidden xl:inline">{paused ? 'resume' : 'pause'}</span>}
29    </button>
30  );
31}
32
33export function VolumeControl() {
34  const { volume } = useAudioControls();
35  return (
36    <label className="flex items-center space-x-1 px-2" title={`volume: ${volume}%`}>
37      <button
38        type="button"
39        aria-label={volume === 0 ? 'unmute' : 'mute'}
40        className="hover:opacity-50"
41        onClick={() => setVolume(volume === 0 ? 100 : 0)}
42      >
43        <span aria-hidden>{volume === 0 ? '🔇' : volume < 50 ? '🔉' : '🔊'}</span>
44      </button>
45      <input
46        type="range"
47        min="0"
48        max="100"
49        step="1"
50        value={volume}
51        onChange={(e) => setVolume(e.target.value)}
52        data-testid="jev-volume"
53        aria-label="volume"
54        className="hidden sm:block w-16 lg:w-20 accent-current"
55      />
56    </label>
57  );
58}