Skip to content

Commit

Permalink
implemented onWordMarked event listener
Browse files Browse the repository at this point in the history
  • Loading branch information
Florian Gyger committed Mar 25, 2020
1 parent 75922b7 commit 9c0e8cd
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 9 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ You can replace the whole speech output handling by using your own React compone

If you choose to use your own component, make sure it uses the `SpeechOutputProps` exported from this plugin.

## Event listeners

To be able to react to certain events you can register the following event listeners:

### `onWordMarked`

When a speech output is played the spoken words are highlighted in the text simultaneously. The `onWordMarked` listener is called as soon as a new word is highlighted and delivers the currently highlighted word as a string. When no word is highlighted (anymore) the string is empty.

## Contribute 馃Ω

Contributions are more than welcome! I would love to see text-to-speech becoming a thing in the already very accessible Gatsby ecosystem. If agree and would like to join me on this mission it would be awesome to get in touch! 馃槉
Expand Down
10 changes: 8 additions & 2 deletions src/SpeechOutput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const DefaultPlayButton: React.FunctionComponent<PlayButtonProps> = props => (
export interface SpeechOutputProps {
id: string;
customPlayButton?: React.FunctionComponent<PlayButtonProps>;
onWordMarked?: (word: string) => void;
}

const SpeechOutput: React.FunctionComponent<SpeechOutputProps> = props => {
Expand All @@ -32,7 +33,12 @@ const SpeechOutput: React.FunctionComponent<SpeechOutputProps> = props => {
fetchSpeechMarks();
}, [props.id]);

const currentWordIndex = useSpeechMarks(speechMarks, isPlaying);
const currentWord = useSpeechMarks(speechMarks, isPlaying);

React.useEffect(
() => props.onWordMarked && props.onWordMarked(currentWord.word),
[currentWord, props.onWordMarked]
);

const onPlayStopButtonClicked = () => {
if (isPlaying) {
Expand All @@ -54,7 +60,7 @@ const SpeechOutput: React.FunctionComponent<SpeechOutputProps> = props => {
return (
<>
<PlayButton isPlaying={isPlaying} onClick={onPlayStopButtonClicked} />
<WordMarker markedWordIndex={currentWordIndex}>
<WordMarker markedWordIndex={currentWord.index}>
{props.children}
</WordMarker>
</>
Expand Down
25 changes: 18 additions & 7 deletions src/internals/hooks/UseSpeechMarks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,21 @@ export interface SpeechMark {
value: string;
}

interface CurrentWord {
index: number;
word: string;
}

const useSpeechMarks = (
speechMarks: SpeechMark[],
isPlaying: boolean
): number => {
const noWordSelectedIndex = -1;
const [currentWordIndex, setCurrentWordIndex] = React.useState<number>(
noWordSelectedIndex
): CurrentWord => {
const noWordSelected: CurrentWord = {
index: -1,
word: ""
};
const [currentWord, setCurrentWord] = React.useState<CurrentWord>(
noWordSelected
);
const timeoutHandles = React.useRef<NodeJS.Timeout[]>([]);

Expand All @@ -38,17 +46,20 @@ const useSpeechMarks = (
return !isSsmlTag;
})
.map((speechMark: SpeechMark, index: number) =>
setTimeout(() => setCurrentWordIndex(index), speechMark.time)
setTimeout(() => setCurrentWord({
index,
word: speechMark.value
}), speechMark.time)
)
);
};

const stop = () => {
setCurrentWordIndex(noWordSelectedIndex);
setCurrentWord(noWordSelected);
timeoutHandles.current.forEach(clearTimeout);
};

return currentWordIndex;
return currentWord;
};

export default useSpeechMarks;

0 comments on commit 9c0e8cd

Please sign in to comment.