Add AudioControl
This commit is contained in:
parent
4debbe218e
commit
04fb949a28
13 changed files with 220 additions and 142 deletions
|
@ -1,3 +1,4 @@
|
||||||
-/node_modules/
|
-/node_modules/
|
||||||
-/tmp/
|
-/tmp/
|
||||||
-/build/
|
-/build/
|
||||||
|
-/.localgems/
|
||||||
|
|
|
@ -12,10 +12,15 @@
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
|
|
||||||
li.ayah {
|
li.ayah {
|
||||||
span.surah-id.ayah-id {
|
span.title {
|
||||||
display: block;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
span.title .svg.sound-on, span.title .svg.sound-off {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
p {
|
p {
|
||||||
margin: 3px 0 10px 0
|
margin: 3px 0 10px 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.body.stream li.ayah {
|
ul.body.stream li.ayah {
|
||||||
span.surah-id.ayah-id {
|
span.title {
|
||||||
color: $green;
|
color: $green;
|
||||||
}
|
}
|
||||||
p { }
|
p { }
|
||||||
|
@ -17,17 +17,6 @@
|
||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row svg g {
|
|
||||||
polygon {
|
|
||||||
stroke: $green;
|
|
||||||
stroke-width: 5;
|
|
||||||
}
|
|
||||||
path {
|
|
||||||
stroke: $green;
|
|
||||||
stroke-width: 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.row .shape.refresh {
|
.row .shape.refresh {
|
||||||
background: unset;
|
background: unset;
|
||||||
fill: $green;
|
fill: $green;
|
||||||
|
@ -36,4 +25,11 @@
|
||||||
.row .loading div {
|
.row .loading div {
|
||||||
background: $green;
|
background: $green;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.svg.sound-on, .svg.sound-off {
|
||||||
|
polygon {
|
||||||
|
fill: $green;
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
|
|
||||||
ul.body.stream {
|
ul.body.stream {
|
||||||
li.ayah {
|
li.ayah {
|
||||||
/* TODO "surah.id", "ayah.id" */
|
span.title {
|
||||||
span.surah-id.ayah-id {
|
|
||||||
color: $gold;
|
color: $gold;
|
||||||
}
|
}
|
||||||
p { }
|
p { }
|
||||||
|
@ -29,18 +28,14 @@
|
||||||
fill: $gold;
|
fill: $gold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row svg g {
|
|
||||||
polygon {
|
|
||||||
stroke: $gold;
|
|
||||||
stroke-width: 5;
|
|
||||||
}
|
|
||||||
path {
|
|
||||||
stroke: $gold;
|
|
||||||
stroke-width: 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.row .loading div {
|
.row .loading div {
|
||||||
background: $gold;
|
background: $gold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.svg.sound-on, .svg.sound-off {
|
||||||
|
polygon {
|
||||||
|
fill: $blue;
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
data-surah-id="<%= context.surah.id %>">
|
data-surah-id="<%= context.surah.id %>">
|
||||||
</div>
|
</div>
|
||||||
<%= inline_json("/i18n.json") %>
|
<%= inline_json("/i18n.json") %>
|
||||||
<%= inline_json("/reciters.json") %>
|
<%= inline_json("/recitations.json") %>
|
||||||
<script src="/js/loaders/surah-stream-loader.js"></script>
|
<script src="/js/loaders/surah-stream-loader.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
56
src/js/components/AudioControl.tsx
Normal file
56
src/js/components/AudioControl.tsx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import url from "url";
|
||||||
|
import * as Quran from "lib/Quran";
|
||||||
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
|
import { SoundOnShape, SoundOffShape } from "components/Shape";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
recitation: Quran.Recitation;
|
||||||
|
surah: Quran.Surah;
|
||||||
|
ayah: Quran.Ayah;
|
||||||
|
onStall?: (e?: Event) => void;
|
||||||
|
onPlay?: (e?: Event) => void;
|
||||||
|
onPlaying?: (e?: Event) => void;
|
||||||
|
onPause?: (e?: Event) => void;
|
||||||
|
onEnd?: (turnOffSound: () => void) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AudioControl({
|
||||||
|
recitation,
|
||||||
|
surah,
|
||||||
|
ayah,
|
||||||
|
onPlay = () => null,
|
||||||
|
onPlaying = () => null,
|
||||||
|
onPause = () => null,
|
||||||
|
onStall = () => null,
|
||||||
|
onEnd = () => null,
|
||||||
|
}: Props) {
|
||||||
|
const [soundOn, setSoundOn] = useState<boolean>(false);
|
||||||
|
const audio = useMemo(() => new Audio(), []);
|
||||||
|
const turnOnSound = () => setSoundOn(true);
|
||||||
|
const turnOffSound = () => setSoundOn(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
audio.addEventListener("ended", () => onEnd(turnOffSound));
|
||||||
|
audio.addEventListener("stalled", onStall);
|
||||||
|
audio.addEventListener("waiting", onStall);
|
||||||
|
audio.addEventListener("play", onPlay);
|
||||||
|
audio.addEventListener("playing", onPlaying);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (soundOn) {
|
||||||
|
audio.src = [url.format(recitation.url), surah.id, `${ayah.id}.mp3`].join("/");
|
||||||
|
audio.play();
|
||||||
|
} else {
|
||||||
|
audio.pause();
|
||||||
|
onPause();
|
||||||
|
}
|
||||||
|
}, [soundOn, ayah.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{soundOn && <SoundOnShape onClick={turnOffSound} />}
|
||||||
|
{!soundOn && <SoundOffShape onClick={turnOnSound} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ export function PauseShape({ onClick }: Props) {
|
||||||
|
|
||||||
export function SoundOnShape({ onClick }: Props) {
|
export function SoundOnShape({ onClick }: Props) {
|
||||||
return (
|
return (
|
||||||
<svg viewBox="0 0 100 100" className="svg sound-on" onClick={onClick}>
|
<svg viewBox="0 -18 100 100" className="svg sound-on" onClick={onClick}>
|
||||||
<g>
|
<g>
|
||||||
<polygon
|
<polygon
|
||||||
fill="none"
|
fill="none"
|
||||||
|
@ -54,7 +54,7 @@ export function SoundOnShape({ onClick }: Props) {
|
||||||
|
|
||||||
export function SoundOffShape({ onClick }: Props) {
|
export function SoundOffShape({ onClick }: Props) {
|
||||||
return (
|
return (
|
||||||
<svg viewBox="0 0 100 100" className="svg sound-off" onClick={onClick}>
|
<svg viewBox="0 -18 100 100" className="svg sound-off" onClick={onClick}>
|
||||||
<g>
|
<g>
|
||||||
<polygon
|
<polygon
|
||||||
fill="none"
|
fill="none"
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import * as Quran from "lib/Quran";
|
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
import * as Quran from "lib/Quran";
|
||||||
|
import { AudioControl } from "components/AudioControl";
|
||||||
import { formatNumber, TFunction } from "lib/i18n";
|
import { formatNumber, TFunction } from "lib/i18n";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
recitation: Quran.Recitation;
|
||||||
surah: Quran.Surah;
|
surah: Quran.Surah;
|
||||||
stream: Quran.Ayat;
|
stream: Quran.Ayat;
|
||||||
locale: Quran.Locale;
|
locale: Quran.Locale;
|
||||||
|
@ -12,16 +14,34 @@ interface Props {
|
||||||
t: TFunction;
|
t: TFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Stream({ surah, stream, locale, endOfStream, isPaused, t }: Props) {
|
export function Stream({
|
||||||
|
recitation,
|
||||||
|
surah,
|
||||||
|
stream,
|
||||||
|
locale,
|
||||||
|
endOfStream,
|
||||||
|
isPaused,
|
||||||
|
t,
|
||||||
|
}: Props) {
|
||||||
const className = classNames("body", "stream");
|
const className = classNames("body", "stream");
|
||||||
const style: React.CSSProperties =
|
const style: React.CSSProperties =
|
||||||
endOfStream || isPaused ? { overflowY: "auto" } : { overflowY: "hidden" };
|
endOfStream || isPaused ? { overflowY: "auto" } : { overflowY: "hidden" };
|
||||||
const ayat = stream.map((ayah: Quran.Ayah) => {
|
const ayat = stream.map((ayah: Quran.Ayah) => {
|
||||||
return (
|
return (
|
||||||
<li key={ayah.id} className="ayah fade">
|
<li key={ayah.id} className="ayah fade">
|
||||||
<span className="surah-id ayah-id">
|
<span className="title">
|
||||||
{t(locale, "surah")} {formatNumber(surah.id, locale)}
|
{(isPaused || endOfStream) && (
|
||||||
{t(locale, "comma")} {t(locale, "ayah")} {formatNumber(ayah.id, locale)}
|
<AudioControl
|
||||||
|
recitation={recitation}
|
||||||
|
surah={surah}
|
||||||
|
ayah={ayah}
|
||||||
|
onEnd={turnOffSound => turnOffSound()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
{t(locale, "surah")} {formatNumber(surah.id, locale)}
|
||||||
|
{t(locale, "comma")} {t(locale, "ayah")} {formatNumber(ayah.id, locale)}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<p>{ayah.text}</p>
|
<p>{ayah.text}</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -4,6 +4,17 @@ import { Surah } from "lib/Quran/Surah";
|
||||||
|
|
||||||
type Locale = "ar" | "en";
|
type Locale = "ar" | "en";
|
||||||
type Ayat = Ayah[];
|
type Ayat = Ayah[];
|
||||||
type Reciter = { id: string; name: string; nationality: string; url: string };
|
type Recitation = {
|
||||||
|
id: string;
|
||||||
|
author: {
|
||||||
|
name: string;
|
||||||
|
nationality: string;
|
||||||
|
};
|
||||||
|
url: {
|
||||||
|
protocol: string;
|
||||||
|
hostname: string;
|
||||||
|
pathname: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export { Surah, Ayah, Ayat, Reciter, Locale, JSON };
|
export { Surah, Ayah, Ayat, Recitation, Locale, JSON };
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import postman, { item } from "postman";
|
import postman, { item } from "postman";
|
||||||
|
import url from "url";
|
||||||
import * as Quran from "lib/Quran";
|
import * as Quran from "lib/Quran";
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
|
@ -7,8 +8,8 @@ import * as Quran from "lib/Quran";
|
||||||
const progressNumber: HTMLSpanElement = parent.querySelector(".percentage")!;
|
const progressNumber: HTMLSpanElement = parent.querySelector(".percentage")!;
|
||||||
const inlineStyle: HTMLStyleElement = document.querySelector(".css.postman")!;
|
const inlineStyle: HTMLStyleElement = document.querySelector(".css.postman")!;
|
||||||
const { locale, surahId } = document.querySelector<HTMLElement>(".root")!.dataset;
|
const { locale, surahId } = document.querySelector<HTMLElement>(".root")!.dataset;
|
||||||
const reciters = JSON.parse(
|
const recitations = JSON.parse(
|
||||||
document.querySelector<HTMLElement>(".json.reciters")!.innerText,
|
document.querySelector<HTMLElement>(".json.recitations")!.innerText,
|
||||||
);
|
);
|
||||||
|
|
||||||
postman(
|
postman(
|
||||||
|
@ -20,10 +21,10 @@ import * as Quran from "lib/Quran";
|
||||||
item.font("Vazirmatn Regular", "url(/fonts/vazirmatn-regular.ttf)"),
|
item.font("Vazirmatn Regular", "url(/fonts/vazirmatn-regular.ttf)"),
|
||||||
item.font("Roboto Mono Regular", "url(/fonts/roboto-mono-regular.ttf)"),
|
item.font("Roboto Mono Regular", "url(/fonts/roboto-mono-regular.ttf)"),
|
||||||
item.json(`/${locale}/${surahId}/surah.json`, { className: "surah" }),
|
item.json(`/${locale}/${surahId}/surah.json`, { className: "surah" }),
|
||||||
...reciters.map((reciter: Quran.Reciter) => {
|
...recitations.map((recitation: Quran.Recitation) => {
|
||||||
const { url: baseUrl } = reciter;
|
const ts = [url.format(recitation.url), "time_slots", `${surahId}.json`].join("/");
|
||||||
return item.json(`${baseUrl}/time_slots/${surahId}.json`, {
|
return item.json(ts, {
|
||||||
className: `reciter time-slots ${reciter.id}`,
|
className: `recitation time-slots ${recitation.id}`,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
item.progress((percent: number) => {
|
item.progress((percent: number) => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useRef, useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { get as getCookie } from "es-cookie";
|
import { get as getCookie } from "es-cookie";
|
||||||
|
@ -7,50 +7,38 @@ import { Stream } from "components/Stream";
|
||||||
import { SelectOption } from "components/Select";
|
import { SelectOption } from "components/Select";
|
||||||
import { ThemeSelect } from "components/ThemeSelect";
|
import { ThemeSelect } from "components/ThemeSelect";
|
||||||
import { LanguageSelect } from "components/LanguageSelect";
|
import { LanguageSelect } from "components/LanguageSelect";
|
||||||
import {
|
import { AudioControl } from "components/AudioControl";
|
||||||
PlayShape,
|
import { PlayShape, PauseShape, RefreshShape, LoadingShape } from "components/Shape";
|
||||||
PauseShape,
|
|
||||||
SoundOnShape,
|
|
||||||
SoundOffShape,
|
|
||||||
RefreshShape,
|
|
||||||
LoadingShape,
|
|
||||||
} from "components/Shape";
|
|
||||||
import * as Quran from "lib/Quran";
|
import * as Quran from "lib/Quran";
|
||||||
import { i18n, TFunction } from "lib/i18n";
|
import { i18n, TFunction } from "lib/i18n";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
node: HTMLScriptElement;
|
node: HTMLScriptElement;
|
||||||
reciters: Quran.Reciter[];
|
recitations: Quran.Recitation[];
|
||||||
locale: Quran.Locale;
|
locale: Quran.Locale;
|
||||||
paused: boolean;
|
paused: boolean;
|
||||||
t: TFunction;
|
t: TFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTimeSlots = (reciter: Quran.Reciter) => {
|
const getTimeSlots = (recitation: Quran.Recitation) => {
|
||||||
const selector = `script.reciter.time-slots.${reciter.id}`;
|
const selector = `script.recitation.time-slots.${recitation.id}`;
|
||||||
const timeSlots: HTMLScriptElement = document.querySelector(selector)!;
|
const timeSlots: HTMLScriptElement = document.querySelector(selector)!;
|
||||||
return timeSlots;
|
return timeSlots;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAudioURL = (reciter: Quran.Reciter, surah: Quran.Surah, stream: Quran.Ayat) => {
|
function SurahStream({ node, recitations, locale, paused, t }: Props) {
|
||||||
const { url: baseUrl } = reciter;
|
|
||||||
const ayah = stream[stream.length - 1];
|
|
||||||
return `${baseUrl}/${surah.id}/${ayah?.id}.mp3`;
|
|
||||||
};
|
|
||||||
|
|
||||||
function SurahStream({ node, reciters, locale, paused, t }: Props) {
|
|
||||||
const [stream, setStream] = useState<Quran.Ayat>([]);
|
const [stream, setStream] = useState<Quran.Ayat>([]);
|
||||||
const [isPaused, setIsPaused] = useState<boolean>(paused);
|
const [isPaused, setIsPaused] = useState<boolean>(paused);
|
||||||
const [soundOn, setSoundOn] = useState<boolean>(false);
|
const [soundOn, setSoundOn] = useState<boolean>(false);
|
||||||
const [isStalled, setIsStalled] = useState<boolean>(false);
|
const [isStalled, setIsStalled] = useState<boolean>(false);
|
||||||
const [endOfStream, setEndOfStream] = useState<boolean>(false);
|
const [endOfStream, setEndOfStream] = useState<boolean>(false);
|
||||||
const [theme, setTheme] = useState(getCookie("theme") || "moon");
|
const [theme, setTheme] = useState(getCookie("theme") || "moon");
|
||||||
const [reciter] = useState<Quran.Reciter>(reciters[0]);
|
const [recitation] = useState<Quran.Recitation>(recitations[0]);
|
||||||
const [surah] = useState<Quran.Surah>(
|
const [surah] = useState<Quran.Surah>(
|
||||||
Quran.Surah.fromDOMNode(locale, node, getTimeSlots(reciter)),
|
Quran.Surah.fromDOMNode(locale, node, getTimeSlots(recitation)),
|
||||||
);
|
);
|
||||||
const readyToRender = stream.length > 0;
|
const readyToRender = stream.length > 0;
|
||||||
const audioRef = useRef<HTMLAudioElement>(null);
|
const ayah = stream[stream.length - 1];
|
||||||
const onLanguageChange = (o: SelectOption) => {
|
const onLanguageChange = (o: SelectOption) => {
|
||||||
const locale = o.value;
|
const locale = o.value;
|
||||||
const params = [["paused", isPaused ? "t" : null]];
|
const params = [["paused", isPaused ? "t" : null]];
|
||||||
|
@ -66,40 +54,6 @@ function SurahStream({ node, reciters, locale, paused, t }: Props) {
|
||||||
setStream([surah.ayat[0]]);
|
setStream([surah.ayat[0]]);
|
||||||
}, [stream.length === 0]);
|
}, [stream.length === 0]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const audio = audioRef.current;
|
|
||||||
if (!audio) {
|
|
||||||
return;
|
|
||||||
} else if (isPaused) {
|
|
||||||
audio.pause();
|
|
||||||
} else if (audio.paused) {
|
|
||||||
audio.play().catch(() => setSoundOn(false));
|
|
||||||
}
|
|
||||||
}, [soundOn, isPaused, stream.length]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const audio = audioRef.current;
|
|
||||||
if (!audio) return;
|
|
||||||
const onEnded = () => {
|
|
||||||
const src = getAudioURL(reciter, surah, stream);
|
|
||||||
if (src !== audio.src) {
|
|
||||||
audio.src = src;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const isStalled = () => setIsStalled(true);
|
|
||||||
const unStalled = () => setIsStalled(false);
|
|
||||||
audio.addEventListener("ended", onEnded);
|
|
||||||
audio.addEventListener("stalled", isStalled);
|
|
||||||
audio.addEventListener("waiting", isStalled);
|
|
||||||
audio.addEventListener("playing", unStalled);
|
|
||||||
return () => {
|
|
||||||
audio.removeEventListener("ended", onEnded);
|
|
||||||
audio.removeEventListener("stalled", isStalled);
|
|
||||||
audio.removeEventListener("waiting", isStalled);
|
|
||||||
audio.removeEventListener("playing", unStalled);
|
|
||||||
};
|
|
||||||
}, [soundOn, stream.length]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames("content", "theme", theme, locale)}>
|
<div className={classNames("content", "theme", theme, locale)}>
|
||||||
<div className="header">
|
<div className="header">
|
||||||
|
@ -122,6 +76,7 @@ function SurahStream({ node, reciters, locale, paused, t }: Props) {
|
||||||
)}
|
)}
|
||||||
{readyToRender && (
|
{readyToRender && (
|
||||||
<Stream
|
<Stream
|
||||||
|
recitation={recitation}
|
||||||
surah={surah}
|
surah={surah}
|
||||||
stream={stream}
|
stream={stream}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
|
@ -137,11 +92,16 @@ function SurahStream({ node, reciters, locale, paused, t }: Props) {
|
||||||
{readyToRender && !isPaused && !endOfStream && (
|
{readyToRender && !isPaused && !endOfStream && (
|
||||||
<PauseShape onClick={() => setIsPaused(true)} />
|
<PauseShape onClick={() => setIsPaused(true)} />
|
||||||
)}
|
)}
|
||||||
{readyToRender && !endOfStream && soundOn && (
|
{readyToRender && !endOfStream && (
|
||||||
<SoundOnShape onClick={() => setSoundOn(false)} />
|
<AudioControl
|
||||||
)}
|
recitation={recitation}
|
||||||
{readyToRender && !endOfStream && !soundOn && (
|
surah={surah}
|
||||||
<SoundOffShape onClick={() => setSoundOn(true)} />
|
ayah={ayah}
|
||||||
|
onPlay={() => setSoundOn(true)}
|
||||||
|
onPause={() => setSoundOn(false)}
|
||||||
|
onPlaying={() => setIsStalled(false)}
|
||||||
|
onStall={() => setIsStalled(true)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{readyToRender && !endOfStream && (
|
{readyToRender && !endOfStream && (
|
||||||
<Timer
|
<Timer
|
||||||
|
@ -162,9 +122,6 @@ function SurahStream({ node, reciters, locale, paused, t }: Props) {
|
||||||
<LoadingShape />
|
<LoadingShape />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{readyToRender && soundOn && (
|
|
||||||
<audio ref={audioRef} src={getAudioURL(reciter, surah, stream)} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -177,12 +134,18 @@ function SurahStream({ node, reciters, locale, paused, t }: Props) {
|
||||||
str !== null && ["1", "t", "true", "yes"].includes(str);
|
str !== null && ["1", "t", "true", "yes"].includes(str);
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const paused = toBoolean(params.get("paused"));
|
const paused = toBoolean(params.get("paused"));
|
||||||
const reciters = JSON.parse(
|
const recitations = JSON.parse(
|
||||||
document.querySelector<HTMLElement>(".json.reciters")!.innerText,
|
document.querySelector<HTMLElement>(".json.recitations")!.innerText,
|
||||||
);
|
);
|
||||||
const t = i18n(document.querySelector<HTMLElement>(".json.i18n")!.innerText);
|
const t = i18n(document.querySelector<HTMLElement>(".json.i18n")!.innerText);
|
||||||
|
|
||||||
ReactDOM.createRoot(root).render(
|
ReactDOM.createRoot(root).render(
|
||||||
<SurahStream reciters={reciters} node={node} locale={locale} paused={paused} t={t} />,
|
<SurahStream
|
||||||
|
recitations={recitations}
|
||||||
|
node={node}
|
||||||
|
locale={locale}
|
||||||
|
paused={paused}
|
||||||
|
t={t}
|
||||||
|
/>,
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|
62
src/recitations.json
Normal file
62
src/recitations.json
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "mishari_alafasy",
|
||||||
|
"author": {
|
||||||
|
"name": "Mishari bin Rashed Alafasy",
|
||||||
|
"nationality": "KW"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"protocol": "https",
|
||||||
|
"hostname": "al-quran.reflectslight.io",
|
||||||
|
"pathname": "/audio/alafasy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aziz_alili",
|
||||||
|
"author": {
|
||||||
|
"name": "Aziz Alili",
|
||||||
|
"nationality": "MK"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"protocol": "https",
|
||||||
|
"hostname": "al-quran.reflectslight.io",
|
||||||
|
"pathname": "/audio/aziz_alili"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "abdullah_awad_al_juhany",
|
||||||
|
"author": {
|
||||||
|
"name": "Abdullah Awad Al Juhany",
|
||||||
|
"nationality": "SA"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"protocol": "https",
|
||||||
|
"hostname": "al-quran.reflectslight.io",
|
||||||
|
"pathname": "/audio/abdullah_awad_al_juhany"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ahmad_bin_ali_al_ajmi",
|
||||||
|
"author": {
|
||||||
|
"name": "Ahmad bin Ali Al-Ajmi",
|
||||||
|
"nationality": "SA"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"protocol": "https",
|
||||||
|
"hostname": "al-quran.reflectslight.io",
|
||||||
|
"pathname": "/audio/ahmad_bin_ali_al_ajmi"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sahl_yassin",
|
||||||
|
"author": {
|
||||||
|
"name": "Sahl Yassin",
|
||||||
|
"nationality": "SA"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"protocol": "https",
|
||||||
|
"hostname": "al-quran.reflectslight.io",
|
||||||
|
"pathname": "/audio/sahl_yassin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -1,32 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "mishari_alafasy",
|
|
||||||
"name": "Mishari bin Rashed Alafasy",
|
|
||||||
"nationality": "KW",
|
|
||||||
"url": "https://al-quran.reflectslight.io/audio/alafasy/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "aziz_alili",
|
|
||||||
"name": "Aziz Alili",
|
|
||||||
"nationality": "MK",
|
|
||||||
"url": "https://al-quran.reflectslight.io/audio/aziz_alili/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "abdullah_awad_al_juhany",
|
|
||||||
"name": "Abdullah Awad Al Juhany",
|
|
||||||
"nationality": "SA",
|
|
||||||
"url": "https://al-quran.reflectslight.io/audio/abdullah_awad_al_juhany/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ahmad_bin_ali_al_ajmi",
|
|
||||||
"name": "Ahmad bin Ali Al-Ajmi",
|
|
||||||
"nationality": "SA",
|
|
||||||
"url": "https://al-quran.reflectslight.io/audio/ahmad_bin_ali_al_ajmi/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "sahl_yassin",
|
|
||||||
"name": "Sahl Yassin",
|
|
||||||
"nationality": "SA",
|
|
||||||
"url": "https://al-quran.reflectslight.io/audio/sahl_yassin/"
|
|
||||||
}
|
|
||||||
]
|
|
Loading…
Reference in a new issue