feat: add volume control
This commit is contained in:
@ -404,6 +404,9 @@
|
|||||||
"x82IOl": {
|
"x82IOl": {
|
||||||
"defaultMessage": "Mute"
|
"defaultMessage": "Mute"
|
||||||
},
|
},
|
||||||
|
"y867Vs": {
|
||||||
|
"defaultMessage": "Volume"
|
||||||
|
},
|
||||||
"yzKwBQ": {
|
"yzKwBQ": {
|
||||||
"defaultMessage": "eg. nsec1xyz"
|
"defaultMessage": "eg. nsec1xyz"
|
||||||
},
|
},
|
||||||
|
@ -27,6 +27,7 @@ function ZapAlertConfiguration({ npub, baseUrl }: ZapAlertConfigurationProps) {
|
|||||||
const [textToSpeech, setTextToSpeech] = useState<boolean>(false);
|
const [textToSpeech, setTextToSpeech] = useState<boolean>(false);
|
||||||
const [voice, setVoice] = useState<string | null>(null);
|
const [voice, setVoice] = useState<string | null>(null);
|
||||||
const [minSatsForTextToSpeech, setMinSatsForTextToSpeech] = useState<string>("21");
|
const [minSatsForTextToSpeech, setMinSatsForTextToSpeech] = useState<string>("21");
|
||||||
|
const [volume, setVolume] = useState<number>(1);
|
||||||
|
|
||||||
// Google propietary voices are not available on OBS browser
|
// Google propietary voices are not available on OBS browser
|
||||||
const voices = getVoices().filter(v => !v.name.includes("Google"));
|
const voices = getVoices().filter(v => !v.name.includes("Google"));
|
||||||
@ -47,14 +48,15 @@ function ZapAlertConfiguration({ npub, baseUrl }: ZapAlertConfigurationProps) {
|
|||||||
const params = toTextToSpeechParams({
|
const params = toTextToSpeechParams({
|
||||||
voiceURI: voice,
|
voiceURI: voice,
|
||||||
minSats: voice ? Number(minSatsForTextToSpeech) : null,
|
minSats: voice ? Number(minSatsForTextToSpeech) : null,
|
||||||
|
volume,
|
||||||
});
|
});
|
||||||
const queryParams = params.toString();
|
const queryParams = params.toString();
|
||||||
return queryParams.length > 0 ? `?${queryParams}` : "";
|
return queryParams.length > 0 ? `?${queryParams}` : "";
|
||||||
}, [voice, minSatsForTextToSpeech]);
|
}, [voice, volume, minSatsForTextToSpeech]);
|
||||||
|
|
||||||
function testVoice() {
|
function testVoice() {
|
||||||
if (selectedVoice) {
|
if (selectedVoice) {
|
||||||
speak(selectedVoice, testText);
|
speak(selectedVoice, testText, volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +105,20 @@ function ZapAlertConfiguration({ npub, baseUrl }: ZapAlertConfigurationProps) {
|
|||||||
onChange={ev => setMinSatsForTextToSpeech(ev.target.value)}
|
onChange={ev => setMinSatsForTextToSpeech(ev.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="paper labeled-input">
|
||||||
|
<label htmlFor="volume">
|
||||||
|
<FormattedMessage defaultMessage="Volume" />
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="volume"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
step="0.1"
|
||||||
|
value={volume}
|
||||||
|
onChange={ev => setVolume(Number(ev.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="paper labeled-input">
|
<div className="paper labeled-input">
|
||||||
<label htmlFor="voice-selector">
|
<label htmlFor="voice-selector">
|
||||||
<FormattedMessage defaultMessage="Voice" />
|
<FormattedMessage defaultMessage="Voice" />
|
||||||
|
@ -39,7 +39,7 @@ export function ZapAlerts({ link }: { link: NostrLink }) {
|
|||||||
const zaps = useZaps(currentLink, true);
|
const zaps = useZaps(currentLink, true);
|
||||||
const zap = useZapQueue(zaps);
|
const zap = useZapQueue(zaps);
|
||||||
const mutedPubkeys = useMutedPubkeys(host, true);
|
const mutedPubkeys = useMutedPubkeys(host, true);
|
||||||
const { voiceURI, minSats } = useTextToSpeechParams();
|
const { voiceURI, minSats, volume } = useTextToSpeechParams();
|
||||||
const voices = getVoices();
|
const voices = getVoices();
|
||||||
const voice = useMemo(() => {
|
const voice = useMemo(() => {
|
||||||
return voices.find(v => v.voiceURI === voiceURI);
|
return voices.find(v => v.voiceURI === voiceURI);
|
||||||
@ -56,7 +56,7 @@ export function ZapAlerts({ link }: { link: NostrLink }) {
|
|||||||
if (text.length > 0 && voice) {
|
if (text.length > 0 && voice) {
|
||||||
try {
|
try {
|
||||||
if (zap.amount >= minSats) {
|
if (zap.amount >= minSats) {
|
||||||
speak(voice, text);
|
speak(voice, text, volume);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -9,18 +9,21 @@ function useQuery() {
|
|||||||
interface TextToSpeechConfig {
|
interface TextToSpeechConfig {
|
||||||
voiceURI: string | null;
|
voiceURI: string | null;
|
||||||
minSats: number;
|
minSats: number;
|
||||||
|
volume: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTextToSpeechParams(): TextToSpeechConfig {
|
export function useTextToSpeechParams(): TextToSpeechConfig {
|
||||||
const q = useQuery();
|
const q = useQuery();
|
||||||
const voiceURI = q.get("voiceURI");
|
const voiceURI = q.get("voiceURI");
|
||||||
const minSats = Number(q.get("minSats")) ?? 21;
|
const minSats = Number(q.get("minSats")) ?? 21;
|
||||||
return { voiceURI, minSats };
|
const volume = Number(q.get("volume")) ?? 1;
|
||||||
|
return { voiceURI, minSats, volume };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TextToSpeechConfigParams {
|
interface TextToSpeechConfigParams {
|
||||||
voiceURI: string | null;
|
voiceURI: string | null;
|
||||||
minSats: number | null;
|
minSats: number | null;
|
||||||
|
volume: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toTextToSpeechParams(config: TextToSpeechConfigParams): URLSearchParams {
|
export function toTextToSpeechParams(config: TextToSpeechConfigParams): URLSearchParams {
|
||||||
@ -31,6 +34,9 @@ export function toTextToSpeechParams(config: TextToSpeechConfigParams): URLSearc
|
|||||||
if (config.minSats) {
|
if (config.minSats) {
|
||||||
params.set("minSats", String(config.minSats));
|
params.set("minSats", String(config.minSats));
|
||||||
}
|
}
|
||||||
|
if (config.volume) {
|
||||||
|
params.set("volume", String(config.volume));
|
||||||
|
}
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,10 +47,11 @@ export function getVoices() {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function speak(voice: SpeechSynthesisVoice, text: string) {
|
export function speak(voice: SpeechSynthesisVoice, text: string, volume: number) {
|
||||||
try {
|
try {
|
||||||
const utterance = new SpeechSynthesisUtterance(text);
|
const utterance = new SpeechSynthesisUtterance(text);
|
||||||
utterance.voice = voice;
|
utterance.voice = voice;
|
||||||
|
utterance.volume = volume;
|
||||||
utterance.rate = 0.8;
|
utterance.rate = 0.8;
|
||||||
speechSynthesis.speak(utterance);
|
speechSynthesis.speak(utterance);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -134,6 +134,7 @@
|
|||||||
"wOy57k": "Add stream goal",
|
"wOy57k": "Add stream goal",
|
||||||
"wzWWzV": "Top zappers",
|
"wzWWzV": "Top zappers",
|
||||||
"x82IOl": "Mute",
|
"x82IOl": "Mute",
|
||||||
|
"y867Vs": "Volume",
|
||||||
"yzKwBQ": "eg. nsec1xyz",
|
"yzKwBQ": "eg. nsec1xyz",
|
||||||
"zVDHAu": "Zap Alert"
|
"zVDHAu": "Zap Alert"
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user