Skip to content

Commit

Permalink
fix: store audio ref inside context
Browse files Browse the repository at this point in the history
  • Loading branch information
bamdadfr committed Oct 9, 2024
1 parent 64f71dd commit bcca581
Show file tree
Hide file tree
Showing 13 changed files with 121 additions and 92 deletions.
10 changes: 6 additions & 4 deletions src/components/audio/audio.component.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {type ReactElement} from 'react';
import React from 'react';
import {useAudioRefContext} from 'src/contexts/audio-ref-context';

import {Invisible} from './audio.component.styles';
import {useAudioModule} from './hooks/use-audio-component';
Expand All @@ -7,13 +8,14 @@ interface Props {
url: string;
}

export function AudioComponent({url}: Props): ReactElement {
const {ref} = useAudioModule(url);
export function AudioComponent({url}: Props) {
const audioRef = useAudioRefContext();
useAudioModule(url);

return (
<Invisible>
<audio
ref={ref}
ref={audioRef}
aria-label="player"
// controls
>
Expand Down
10 changes: 7 additions & 3 deletions src/components/audio/hooks/use-audio-buffered.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {useAtom} from 'jotai';
import {useEffect} from 'react';
import {setBufferedAtom} from 'src/atoms/buffered.atoms';
import {useAudioRefContext} from 'src/contexts/audio-ref-context';

const getBufferedAmount = (audio: HTMLAudioElement) => {
try {
Expand All @@ -10,14 +11,17 @@ const getBufferedAmount = (audio: HTMLAudioElement) => {
}
};

export function useAudioBuffered(audio: HTMLAudioElement | null): void {
export function useAudioBuffered() {
const ref = useAudioRefContext();
const [, setBuffered] = useAtom(setBufferedAtom);

useEffect(() => {
if (!audio) {
if (ref.current === null) {
return;
}

const audio = ref.current;

const handleLoad = () => {
setBuffered(getBufferedAmount(audio));
};
Expand All @@ -33,5 +37,5 @@ export function useAudioBuffered(audio: HTMLAudioElement | null): void {
audio.removeEventListener('canplaythrough', handleLoad);
audio.removeEventListener('progress', handleProgress);
};
}, [audio, setBuffered]);
}, [ref, setBuffered]);
}
30 changes: 10 additions & 20 deletions src/components/audio/hooks/use-audio-component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import {type RefObject, useRef} from 'react';

import {useAudioBuffered} from './use-audio-buffered';
import {useAudioLoad} from './use-audio-load';
import {useAudioLoop} from './use-audio-loop';
Expand All @@ -11,24 +9,16 @@ import {useAudioSeek} from './use-audio-seek';
import {useAudioVolume} from './use-audio-volume';
import {useKeyboardToggle} from './use-keyboard-toggle';

interface UseAudioComponent {
ref: RefObject<HTMLAudioElement>;
}

export function useAudioModule(url: string): UseAudioComponent {
export function useAudioModule(url: string) {
useKeyboardToggle('Space');

const ref = useRef<HTMLAudioElement>(null);

useAudioLoad(ref.current, url);
useAudioLoop(ref.current);
useAudioPitch(ref.current);
useAudioPlaybackRate(ref.current);
useAudioVolume(ref.current);
useAudioPlayPause(ref.current);
useAudioProgress(ref.current);
useAudioSeek(ref.current);
useAudioBuffered(ref.current);

return {ref};
useAudioLoad(url);
useAudioLoop();
useAudioPitch();
useAudioPlaybackRate();
useAudioVolume();
useAudioPlayPause();
useAudioProgress();
useAudioSeek();
useAudioBuffered();
}
22 changes: 10 additions & 12 deletions src/components/audio/hooks/use-audio-load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@ import {useAtom} from 'jotai';
import {useCallback, useEffect, useState} from 'react';
import {setDurationAtom} from 'src/atoms/duration.atoms';
import {setLoadedAtom} from 'src/atoms/load.atoms';
import {useAudioRefContext} from 'src/contexts/audio-ref-context';

/**
* Hook to load audio element
*/
export function useAudioLoad(
audio: HTMLAudioElement | null,
url: string,
): void {
export function useAudioLoad(url: string) {
const ref = useAudioRefContext();
const [savedUrl, setSavedUrl] = useState<string>();
const [, setLoaded] = useAtom(setLoadedAtom);
const [, setDuration] = useAtom(setDurationAtom);
Expand All @@ -19,18 +15,20 @@ export function useAudioLoad(
}, [setLoaded]);

const handleMetadata = useCallback(() => {
if (!(audio instanceof HTMLAudioElement)) {
if (ref.current === null) {
return;
}

setDuration(audio.duration);
}, [audio, setDuration]);
setDuration(ref.current.duration);
}, [ref, setDuration]);

useEffect(() => {
if (!(audio instanceof HTMLAudioElement)) {
if (ref.current === null) {
return;
}

const audio = ref.current;

if (url === savedUrl) {
return;
}
Expand All @@ -45,5 +43,5 @@ export function useAudioLoad(
audio.removeEventListener('canplay', () => handleCanPlay());
audio.removeEventListener('loadedmetadata', () => handleMetadata());
};
}, [audio, handleCanPlay, handleMetadata, savedUrl, setDuration, url]);
}, [ref, handleCanPlay, handleMetadata, savedUrl, setDuration, url]);
}
13 changes: 6 additions & 7 deletions src/components/audio/hooks/use-audio-loop.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import {useAtom} from 'jotai';
import {useEffect} from 'react';
import {isRepeatingAtom} from 'src/atoms/repeat.atoms';
import {useAudioRefContext} from 'src/contexts/audio-ref-context';

/**
* Hook to set the loop on audio element
*/
export function useAudioLoop(audio: HTMLAudioElement | null): void {
export function useAudioLoop() {
const ref = useAudioRefContext();
const [isRepeating] = useAtom(isRepeatingAtom);

useEffect(() => {
if (!(audio instanceof HTMLAudioElement)) {
if (ref.current === null) {
return;
}

audio.loop = isRepeating;
}, [audio, isRepeating]);
ref.current.loop = isRepeating;
}, [ref, isRepeating]);
}
16 changes: 8 additions & 8 deletions src/components/audio/hooks/use-audio-pitch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {useEffect} from 'react';
import {useAudioRefContext} from 'src/contexts/audio-ref-context';

declare global {
interface HTMLAudioElement {
Expand All @@ -7,16 +8,15 @@ declare global {
}
}

/**
* Hook to set the pitch of the audio element
*/
export function useAudioPitch(audio: HTMLAudioElement | null): void {
export function useAudioPitch() {
const ref = useAudioRefContext();

useEffect(() => {
if (!(audio instanceof HTMLAudioElement)) {
if (ref.current === null) {
return;
}

audio.mozPreservesPitch = false;
audio.preservesPitch = false;
}, [audio]);
ref.current.mozPreservesPitch = false;
ref.current.preservesPitch = false;
}, [ref]);
}
19 changes: 11 additions & 8 deletions src/components/audio/hooks/use-audio-play-pause.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import {useAtom} from 'jotai';
import {useEffect} from 'react';
import {isPlayingAtom} from 'src/atoms/play-pause.atoms';
import {useAudioRefContext} from 'src/contexts/audio-ref-context';

/**
* Hook to use audio play/pause
*/
export function useAudioPlayPause(audio: HTMLAudioElement | null): void {
export function useAudioPlayPause() {
const ref = useAudioRefContext();
const [isPlaying] = useAtom(isPlayingAtom);

useEffect(() => {
if (!(audio instanceof HTMLAudioElement)) {
if (ref.current === null) {
return;
}

(async () => {
const audio = ref.current;

const toggle = async () => {
if (isPlaying) {
await audio.play();
return;
}

audio.pause();
})();
}, [isPlaying, audio]);
};

toggle().then();
}, [ref, isPlaying]);
}
13 changes: 6 additions & 7 deletions src/components/audio/hooks/use-audio-playback-rate.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import {useAtom} from 'jotai';
import {useEffect} from 'react';
import {speedAtom} from 'src/atoms/speed.atoms';
import {useAudioRefContext} from 'src/contexts/audio-ref-context';

/**
* Hook to set audio playback rate
*/
export function useAudioPlaybackRate(audio: HTMLAudioElement | null): void {
export function useAudioPlaybackRate() {
const ref = useAudioRefContext();
const [speed] = useAtom(speedAtom);

useEffect(() => {
if (!(audio instanceof HTMLAudioElement)) {
if (ref.current === null) {
return;
}

audio.playbackRate = speed;
}, [audio, speed]);
ref.current.playbackRate = speed;
}, [ref, speed]);
}
17 changes: 10 additions & 7 deletions src/components/audio/hooks/use-audio-progress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,31 @@ import {useAtom} from 'jotai';
import {useEffect} from 'react';
import {isPlayingAtom} from 'src/atoms/play-pause.atoms';
import {setProgressAtom} from 'src/atoms/progress.atoms';
import {useAudioRefContext} from 'src/contexts/audio-ref-context';

/**
* Hook to set the progress of audio element
*/
export function useAudioProgress(audio: HTMLAudioElement | null): void {
export function useAudioProgress() {
const ref = useAudioRefContext();
const [isPlaying] = useAtom(isPlayingAtom);
const [, setProgress] = useAtom(setProgressAtom);
const fps = 10;

useEffect(() => {
let i1: NodeJS.Timeout | undefined = undefined;

if (!(audio instanceof HTMLAudioElement)) {
if (ref.current === null) {
return;
}

const audio = ref.current;

if (isPlaying) {
i1 = setInterval(() => {
setProgress(Math.floor(audio.currentTime));
}, 1000 / fps);
}

return () => clearInterval(i1);
}, [audio, isPlaying, setProgress]);
return () => {
clearInterval(i1);
};
}, [ref, isPlaying, setProgress]);
}
13 changes: 6 additions & 7 deletions src/components/audio/hooks/use-audio-seek.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import {useAtom} from 'jotai';
import {useEffect} from 'react';
import {seekAtom} from 'src/atoms/seek.atoms';
import {useAudioRefContext} from 'src/contexts/audio-ref-context';

/**
* Hook to set audio player's current time
*/
export function useAudioSeek(audio: HTMLAudioElement | null): void {
export function useAudioSeek() {
const ref = useAudioRefContext();
const [seek] = useAtom(seekAtom);

useEffect(() => {
if (!(audio instanceof HTMLAudioElement)) {
if (ref.current === null) {
return;
}

audio.currentTime = seek;
}, [audio, seek]);
ref.current.currentTime = seek;
}, [ref, seek]);
}
17 changes: 10 additions & 7 deletions src/components/audio/hooks/use-audio-volume.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import {useAtom} from 'jotai';
import {useEffect} from 'react';
import {setVolumeAtom, volumeAtom} from 'src/atoms/volume.atoms';
import {useAudioRefContext} from 'src/contexts/audio-ref-context';

/**
* Hook to set audio volume
*/
export function useAudioVolume(audio: HTMLAudioElement | null): void {
export function useAudioVolume() {
const ref = useAudioRefContext();
const [volume] = useAtom(volumeAtom);
const [, setVolume] = useAtom(setVolumeAtom);

useEffect(() => {
if (!(audio instanceof HTMLAudioElement)) {
if (ref.current === null) {
return;
}

const audio = ref.current;

audio.volume = volume;

const handleVolumeChange = () => setVolume(audio.volume);

audio.addEventListener('volumechange', handleVolumeChange);

return () => audio.removeEventListener('volumechange', handleVolumeChange);
}, [audio, setVolume, volume]);
return () => {
audio.removeEventListener('volumechange', handleVolumeChange);
};
}, [ref, setVolume, volume]);
}
26 changes: 26 additions & 0 deletions src/contexts/audio-ref-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, {
createContext,
type MutableRefObject,
useContext,
useRef,
} from 'react';

type Audio = HTMLAudioElement | null;

const AudioRefContext = createContext<MutableRefObject<Audio>>(
{} as MutableRefObject<Audio>,
);

export function AudioRefContextProvider({children}) {
const audioRef = useRef<Audio>(null);

return (
<AudioRefContext.Provider value={audioRef}>
{children}
</AudioRefContext.Provider>
);
}

export function useAudioRefContext() {
return useContext(AudioRefContext);
}
Loading

0 comments on commit bcca581

Please sign in to comment.