diff --git a/src/js/components/AudioControl.tsx b/src/js/components/AudioControl.tsx index 280d1b856..2d2eb0090 100644 --- a/src/js/components/AudioControl.tsx +++ b/src/js/components/AudioControl.tsx @@ -3,64 +3,80 @@ import type { Surah, TSurah, Ayah, TAyah } from "Quran"; import React, { useEffect, useMemo, useState } from "react"; import { SoundOnIcon, SoundOffIcon } from "~/components/Icon"; +type TAudioStatus = "play" | "pause" | "wait" | "end"; + type Props = { + autoPlay?: boolean; + onStatusChange?: (s: TAudioStatus) => void; + audio: HTMLAudioElement; surah: Surah; ayah: Ayah; - onStall?: (e?: Event) => void; - onPlay?: (e?: Event) => void; - onPlaying?: (e?: Event) => void; - onPause?: (e?: Event) => void; - onEnd?: (turnOffSound: () => void) => void; }; export function AudioControl({ + autoPlay = false, + onStatusChange = () => null, + audio, surah, ayah, - onPlay = () => null, - onPlaying = () => null, - onPause = () => null, - onStall = () => null, - onEnd = () => null, }: Props) { - const [soundOn, setSoundOn] = useState(false); - const audio = useMemo(() => new Audio(), []); - const turnOnSound = () => setSoundOn(true); - const turnOffSound = () => setSoundOn(false); - const recover = () => { - if (!soundOn) return; - onStall(); - audio.play().catch(() => setTimeout(recover, 50)); - }; + const [enabled, setEnabled] = useState(false); + const [audioStatus, setAudioStatus] = useState(null); + const play = (audio: HTMLAudioElement) => audio.play().catch(() => null); + const pause = (audio: HTMLAudioElement) => audio.pause(); useEffect(() => { - audio.addEventListener("ended", () => onEnd(turnOffSound)); - audio.addEventListener("stalled", recover); - audio.addEventListener("waiting", onStall); - audio.addEventListener("play", onPlay); - audio.addEventListener("playing", onPlaying); - }, []); - - useEffect(() => { - const src = [ - "https://al-quran.reflectslight.io", - "audio", - "alafasy", - surah.id, - `${ayah.id}.mp3`, - ].join("/"); - if (soundOn) { - audio.src = src; - audio.play(); - } else { - audio.pause(); - onPause(); + if (audio) { + audio.src = [ + "https://al-quran.reflectslight.io", + "audio", + "alafasy", + surah.id, + `${ayah.id}.mp3`, + ].join("/"); + if (autoPlay) { + play(audio); + } } - }, [soundOn, ayah.id]); + }, [ayah.id]); + + useEffect(() => { + if (audioStatus === "end") { + setEnabled(false); + } + onStatusChange(audioStatus); + }, [audioStatus]); + + useEffect(() => { + if (!audio) return; + const onPlay = () => setAudioStatus("play"); + const onPause = () => setAudioStatus("pause"); + const onEnd = () => setAudioStatus("end"); + const onWait = () => [setAudioStatus("wait"), play(audio)]; + audio.addEventListener("play", onPlay); + audio.addEventListener("playing", onPlay); + audio.addEventListener("pause", onPause); + audio.addEventListener("ended", onEnd); + audio.addEventListener("stalled", onWait); + audio.addEventListener("waiting", onWait); + return () => { + audio.removeEventListener("play", onPlay); + audio.removeEventListener("playing", onPlay); + audio.removeEventListener("pause", onPause); + audio.removeEventListener("ended", onEnd); + audio.removeEventListener("stalled", onWait); + audio.removeEventListener("waiting", onWait); + }; + }, [ayah.id]); return ( <> - {soundOn && } - {!soundOn && } + {enabled && ( + [setEnabled(false), pause(audio)]} /> + )} + {!enabled && ( + [setEnabled(true), play(audio)]} /> + )} ); } diff --git a/src/js/components/Stream.tsx b/src/js/components/Stream.tsx index 5e79c05e4..15739226d 100644 --- a/src/js/components/Stream.tsx +++ b/src/js/components/Stream.tsx @@ -45,11 +45,7 @@ export function Stream({ className={classNames("flex h-8 items-center", { "mb-2": rtl })} > {(isPaused || endOfStream) && ( - turnOffSound()} - /> + )} {t(locale, "surah")} {formatNumber(surah.id, locale)} diff --git a/src/js/components/SurahStream.tsx b/src/js/components/SurahStream.tsx index 5652bf80c..fcc4c486c 100644 --- a/src/js/components/SurahStream.tsx +++ b/src/js/components/SurahStream.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useMemo, useRef } from "react"; import classNames from "classnames"; import { Surah, TSurah, TAyat, TLocale } from "Quran"; import { useTheme } from "~/hooks/useTheme"; @@ -23,14 +23,14 @@ type Props = { export function SurahStream({ surah, locale, t }: Props) { const [stream, setStream] = useState([]); const [isPaused, setIsPaused] = useState(false); - const [soundOn, setSoundOn] = useState(false); - const [isStalled, setIsStalled] = useState(false); + const [audioStatus, setAudioStatus] = useState(null); const [endOfStream, setEndOfStream] = useState(false); const [theme, setTheme] = useTheme(); const readyToRender = stream.length > 0; const ayah = stream[stream.length - 1]; const [ms, setMs] = useState(null); const ref = useRef(); + const audio = useMemo(() => new Audio(), []); useEffect(() => { if (ref.current) { @@ -84,16 +84,15 @@ export function SurahStream({ surah, locale, t }: Props) { {readyToRender && !endOfStream && (
setSoundOn(true)} - onPause={() => setSoundOn(false)} - onPlaying={() => setIsStalled(false)} - onStall={() => setIsStalled(true)} + onStatusChange={s => setAudioStatus(s)} />
)} - {readyToRender && !endOfStream && !isStalled && ( + {readyToRender && !endOfStream && audioStatus !== "wait" && ( )} - {readyToRender && soundOn && isStalled && } + {readyToRender && audioStatus === "wait" && } {readyToRender && endOfStream && ( setStream([])} /> )} diff --git a/src/js/main/surah-index.tsx b/src/js/main/surah-index.tsx index 5938f6055..963d40917 100644 --- a/src/js/main/surah-index.tsx +++ b/src/js/main/surah-index.tsx @@ -7,8 +7,10 @@ import { SurahIndex } from "~/components/SurahIndex"; (function () { const root: HTMLElement = document.querySelector(".root")!; const locale = root.getAttribute("data-locale") as TLocale; - const surahs: Surah[] = require("@json/surahs").map((e: TSurah) => new Surah(e)); const t = T(require("@json/t.json")); + const surahs: Surah[] = require("@json/surahs").map( + (e: TSurah) => new Surah(e), + ); ReactDOM.createRoot(root).render( , );