mirror of
https://github.com/streamich/react-use.git
synced 2026-01-18 14:06:52 +00:00
refactor: (useSpeech,usePermission) improved types
This commit is contained in:
parent
90e72a5340
commit
99208ffced
@ -1,45 +1,59 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { noop, off, on } from './misc/util';
|
||||
|
||||
type PermissionDesc =
|
||||
export type IState = PermissionState | '';
|
||||
|
||||
interface IPushPermissionDescriptor extends PermissionDescriptor {
|
||||
name: 'push';
|
||||
userVisibleOnly?: boolean;
|
||||
}
|
||||
|
||||
interface IMidiPermissionDescriptor extends PermissionDescriptor {
|
||||
name: 'midi';
|
||||
sysex?: boolean;
|
||||
}
|
||||
|
||||
interface IDevicePermissionDescriptor extends PermissionDescriptor {
|
||||
name: 'camera' | 'microphone' | 'speaker';
|
||||
deviceId?: string;
|
||||
}
|
||||
|
||||
export type IPermissionDescriptor =
|
||||
| PermissionDescriptor
|
||||
| DevicePermissionDescriptor
|
||||
| MidiPermissionDescriptor
|
||||
| PushPermissionDescriptor;
|
||||
| IPushPermissionDescriptor
|
||||
| IMidiPermissionDescriptor
|
||||
| IDevicePermissionDescriptor;
|
||||
|
||||
type State = PermissionState | '';
|
||||
|
||||
const usePermission = (permissionDesc: PermissionDesc): State => {
|
||||
let mounted = true;
|
||||
let permissionStatus: PermissionStatus | null = null;
|
||||
|
||||
const [state, setState] = useState<State>('');
|
||||
|
||||
const onChange = () => {
|
||||
if (mounted && permissionStatus) {
|
||||
setState(permissionStatus.state);
|
||||
}
|
||||
};
|
||||
|
||||
const changeState = () => {
|
||||
onChange();
|
||||
on(permissionStatus, 'change', onChange);
|
||||
};
|
||||
// const usePermission = <T extends PermissionDescriptor>(permissionDesc: T): IState => {
|
||||
const usePermission = (permissionDesc: IPermissionDescriptor): IState => {
|
||||
const [state, setState] = useState<IState>('');
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
let permissionStatus: PermissionStatus | null = null;
|
||||
|
||||
const onChange = () => {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() => permissionStatus?.state ?? '');
|
||||
};
|
||||
|
||||
navigator.permissions
|
||||
.query(permissionDesc)
|
||||
.then((status) => {
|
||||
permissionStatus = status;
|
||||
changeState();
|
||||
on(permissionStatus, 'change', onChange);
|
||||
onChange();
|
||||
})
|
||||
.catch(noop);
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
permissionStatus && off(permissionStatus, 'change', onChange);
|
||||
mounted = false;
|
||||
permissionStatus = null;
|
||||
};
|
||||
}, []);
|
||||
}, [permissionDesc]);
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
116
src/useSpeech.ts
116
src/useSpeech.ts
@ -1,54 +1,90 @@
|
||||
import { useRef } from 'react';
|
||||
import useMount from './useMount';
|
||||
import useSetState from './useSetState';
|
||||
import { isBrowser } from './misc/util';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
export interface SpeechState {
|
||||
isPlaying: boolean;
|
||||
type SpeechOptions = {
|
||||
lang: string;
|
||||
voice: SpeechSynthesisVoice;
|
||||
voice?: SpeechSynthesisVoice;
|
||||
rate: number;
|
||||
pitch: number;
|
||||
volume: number;
|
||||
};
|
||||
|
||||
export type ISpeechOptions = Partial<SpeechOptions>;
|
||||
|
||||
export type VoiceInfo = Pick<SpeechSynthesisVoice, 'lang' | 'name'>;
|
||||
|
||||
export type ISpeechState = SpeechOptions & {
|
||||
isPlaying: boolean;
|
||||
status: string;
|
||||
voiceInfo: VoiceInfo;
|
||||
};
|
||||
|
||||
enum Status {
|
||||
init,
|
||||
play,
|
||||
pause,
|
||||
end,
|
||||
}
|
||||
|
||||
export interface SpeechOptions {
|
||||
lang?: string;
|
||||
voice?: SpeechSynthesisVoice;
|
||||
rate?: number;
|
||||
pitch?: number;
|
||||
volume?: number;
|
||||
}
|
||||
|
||||
const voices =
|
||||
isBrowser && typeof window.speechSynthesis === 'object' ? window.speechSynthesis.getVoices() : [];
|
||||
|
||||
const useSpeech = (text: string, opts: SpeechOptions = {}): SpeechState => {
|
||||
const [state, setState] = useSetState<SpeechState>({
|
||||
isPlaying: false,
|
||||
lang: opts.lang || 'default',
|
||||
voice: opts.voice || voices[0],
|
||||
rate: opts.rate || 1,
|
||||
pitch: opts.pitch || 1,
|
||||
volume: opts.volume || 1,
|
||||
const useSpeech = (text: string, options: ISpeechOptions): ISpeechState => {
|
||||
let mounted = useRef<boolean>(false);
|
||||
const [state, setState] = useState<ISpeechState>(() => {
|
||||
const { lang = 'default', name = '' } = options.voice || {};
|
||||
return {
|
||||
isPlaying: false,
|
||||
status: Status[Status.init],
|
||||
lang: options.lang || 'default',
|
||||
voiceInfo: { lang, name },
|
||||
rate: options.rate || 1,
|
||||
pitch: options.pitch || 1,
|
||||
volume: options.volume || 1,
|
||||
};
|
||||
});
|
||||
|
||||
const uterranceRef = useRef<SpeechSynthesisUtterance | null>(null);
|
||||
const handlePlay = useCallback(() => {
|
||||
if (!mounted.current) {
|
||||
return;
|
||||
}
|
||||
setState((preState) => {
|
||||
return { ...preState, isPlaying: true, status: Status[Status.play] };
|
||||
});
|
||||
}, []);
|
||||
|
||||
useMount(() => {
|
||||
const handlePause = useCallback(() => {
|
||||
if (!mounted.current) {
|
||||
return;
|
||||
}
|
||||
setState((preState) => {
|
||||
return { ...preState, isPlaying: false, status: Status[Status.pause] };
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleEnd = useCallback(() => {
|
||||
if (!mounted.current) {
|
||||
return;
|
||||
}
|
||||
setState((preState) => {
|
||||
return { ...preState, isPlaying: false, status: Status[Status.end] };
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
opts.lang && (utterance.lang = opts.lang);
|
||||
opts.voice && (utterance.voice = opts.voice);
|
||||
utterance.rate = opts.rate || 1;
|
||||
utterance.pitch = opts.pitch || 1;
|
||||
utterance.volume = opts.volume || 1;
|
||||
utterance.onstart = () => setState({ isPlaying: true });
|
||||
utterance.onresume = () => setState({ isPlaying: true });
|
||||
utterance.onend = () => setState({ isPlaying: false });
|
||||
utterance.onpause = () => setState({ isPlaying: false });
|
||||
uterranceRef.current = utterance;
|
||||
window.speechSynthesis.speak(uterranceRef.current);
|
||||
});
|
||||
options.lang && (utterance.lang = options.lang);
|
||||
options.voice && (utterance.voice = options.voice);
|
||||
utterance.rate = options.rate || 1;
|
||||
utterance.pitch = options.pitch || 1;
|
||||
utterance.volume = options.volume || 1;
|
||||
utterance.onstart = handlePlay;
|
||||
utterance.onpause = handlePause;
|
||||
utterance.onresume = handlePlay;
|
||||
utterance.onend = handleEnd;
|
||||
window.speechSynthesis.speak(utterance);
|
||||
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
@ -3,9 +3,8 @@ import * as React from 'react';
|
||||
import { useSpeech } from '../src';
|
||||
import ShowDocs from './util/ShowDocs';
|
||||
|
||||
const voices = window.speechSynthesis.getVoices();
|
||||
|
||||
const Demo = () => {
|
||||
const voices = window.speechSynthesis.getVoices();
|
||||
const state = useSpeech('Hello world!', { rate: 0.8, pitch: 0.5, voice: voices[0] });
|
||||
|
||||
return <pre>{JSON.stringify(state, null, 2)}</pre>;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user