Skip to content

Commit

Permalink
🐛 fix: Fix autoplay
Browse files Browse the repository at this point in the history
  • Loading branch information
canisminor1990 committed Dec 6, 2023
1 parent b13cff2 commit 1fafadc
Show file tree
Hide file tree
Showing 13 changed files with 83 additions and 49 deletions.
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ A high-quality & reliable TTS/STT library for Server and Browser
[![][github-forks-shield]][github-forks-link]
[![][github-stars-shield]][github-stars-link]
[![][github-issues-shield]][github-issues-link]
[![][github-license-shield]][github-license-link]
[![][github-license-shield]][github-license-link]<br/>
[![][sponsor-shield]][sponsor-link]

[Changelog](./CHANGELOG.md) · [Report Bug][github-issues-link] · [Request Feature][github-issues-link]

Expand All @@ -37,6 +38,7 @@ A high-quality & reliable TTS/STT library for Server and Browser
- [Compile with Next.js](#compile-with-nextjs)
- [⌨️ Local Development](#️-local-development)
- [🤝 Contributing](#-contributing)
- [🩷 Sponsor](#-sponsor)
- [🔗 More Products](#-more-products)

####
Expand Down Expand Up @@ -195,6 +197,23 @@ Contributions of all types are more than welcome, if you are interested in contr

</div>

## 🩷 Sponsor

Every bit counts and your one-time donation sparkles in our galaxy of support! You're a shooting star, making a swift and bright impact on our journey. Thank you for believing in us – your generosity guides us toward our mission, one brilliant flash at a time.

<a href="https://opencollective.com/lobehub" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://readme-wizard.lobehub.com/api/sponsor?themeMode=dark">
<img src="https://readme-wizard.lobehub.com/api/sponsor">
</picture>
</a>

<div align="right">

[![][back-to-top]](#readme-top)

</div>

## 🔗 More Products

- **[🤖 Lobe Chat](https://github.com/lobehub/lobe-chat)** - An open-source, extensible (Function Calling), high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application.
Expand Down Expand Up @@ -241,3 +260,5 @@ This project is [MIT](./LICENSE) licensed.
[pr-welcome-link]: https://github.com/lobehub/lobe-tts/pulls
[pr-welcome-shield]: https://img.shields.io/badge/%F0%9F%A4%AF%20PR%20WELCOME-%E2%86%92-ffcb47?labelColor=black&style=for-the-badge
[profile-link]: https://github.com/lobehub
[sponsor-link]: https://opencollective.com/lobehub 'Become 🩷 LobeHub Sponsor'
[sponsor-shield]: https://img.shields.io/badge/-Sponsor%20LobeHub-f04f88?logo=opencollective&logoColor=white&style=flat-square
1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export {
type OpenaiVoice,
} from '@/core/OpenAITTS';
export { SpeechSynthesisTTS } from '@/core/SpeechSynthesisTTS';
export { cleanContent } from '@/core/utils/cleanContent';
export { getRecordMineType, type RecordMineType } from '@/core/utils/getRecordMineType';
export { VoiceList } from '@/core/VoiceList';
2 changes: 1 addition & 1 deletion src/react/AudioPlayer/demos/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default () => {

return (
<StoryBook levaStore={store}>
<AudioPlayer audio={audio} isLoading={isLoading} {...options} />
<AudioPlayer audio={audio} autoplay isLoading={isLoading} {...options} />
</StoryBook>
);
};
2 changes: 2 additions & 0 deletions src/react/AudioPlayer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface AudioProps {
export interface AudioPlayerProps {
allowPause?: boolean;
audio: AudioProps;
autoplay?: boolean;
buttonActive?: boolean;
buttonSize?: ActionIconProps['size'];
buttonStyle?: CSSProperties;
Expand Down Expand Up @@ -48,6 +49,7 @@ const AudioPlayer = memo<AudioPlayerProps>(
className,
onLoadingStop,
audio = {
canPlay: false,
currentTime: 0,
download: () => {},
duration: 0,
Expand Down
63 changes: 38 additions & 25 deletions src/react/hooks/useAudioPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import useSWR from 'swr';

import { AudioProps } from '@/react/AudioPlayer';

export interface AudioPlayerReturn extends AudioProps {
export interface AudioPlayerResponse extends AudioProps {
arrayBuffers: ArrayBuffer[];
download: () => void;
isLoading?: boolean;
Expand All @@ -20,43 +20,43 @@ export interface AudioPlayerOptions {
export const useAudioPlayer = ({
src,
type = 'audio/mp3',
}: AudioPlayerOptions = {}): AudioPlayerReturn => {
const audioRef = useRef<HTMLAudioElement>(new Audio());
}: AudioPlayerOptions = {}): AudioPlayerResponse => {
const audioRef = useRef<HTMLAudioElement>();
const [arrayBuffers, setArrayBuffers] = useState<ArrayBuffer[]>([]);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
const [isGlobalLoading, setIsGlobalLoading] = useState(true);

const { isLoading } = useSWR(
src || null,
async () => {
if (!src) return;
setIsGlobalLoading(true);
const data = await fetch(src);
const arrayBuffer = await data.arrayBuffer();
setArrayBuffers([arrayBuffer]);
const newBlob = new Blob([arrayBuffer], { type: type });
audioRef.current.pause();
audioRef.current.currentTime = 0;
if (audioRef.current.src) URL.revokeObjectURL(audioRef.current.src);
audioRef.current.src = URL.createObjectURL(newBlob);
audioRef.current.load();
},
{ revalidateOnFocus: false },
);
const { isLoading } = useSWR(src || null, async () => {
if (!src) return;
setIsGlobalLoading(true);
const data = await fetch(src);
const arrayBuffer = await data.arrayBuffer();
setArrayBuffers([arrayBuffer]);
const newBlob = new Blob([arrayBuffer], { type: type });
if (!audioRef.current) audioRef.current = new Audio();
audioRef.current.pause();
audioRef.current.currentTime = 0;
if (audioRef.current.src) URL.revokeObjectURL(audioRef.current.src);
audioRef.current.src = URL.createObjectURL(newBlob);
audioRef.current.load();
});

useEffect(() => {
if (!audioRef.current) return;
if (!audioRef.current) audioRef.current = new Audio();
const onLoadedMetadata = () => {
if (!audioRef.current) return;
setDuration(audioRef.current.duration);
setIsGlobalLoading(false);
};
const onTimeUpdate = () => {
if (!audioRef.current) return;
setCurrentTime(audioRef.current.currentTime);
};

const onEnded = async () => {
if (!audioRef.current) return;
setIsPlaying(false);
audioRef.current.currentTime = 0;
setCurrentTime(0);
Expand All @@ -68,6 +68,7 @@ export const useAudioPlayer = ({
audioRef.current.addEventListener('timeupdate', onTimeUpdate);

return () => {
if (!audioRef.current) return;
audioRef.current.pause();
audioRef.current.load();
audioRef.current.removeEventListener('ended', onEnded);
Expand All @@ -79,27 +80,38 @@ export const useAudioPlayer = ({
}, []);

const handlePlay = useCallback(() => {
setIsPlaying(true);
audioRef.current.play();
try {
if (!audioRef.current) return;
setIsPlaying(true);
audioRef.current?.play();
} catch {
setTimeout(() => {
handlePlay();
}, 200);
}
}, []);

const handlePause = useCallback(() => {
if (!audioRef.current) return;
setIsPlaying(false);
audioRef.current.pause();
}, []);

const handleStop = useCallback(() => {
if (!audioRef.current) return;
setIsPlaying(false);
audioRef.current.pause();
audioRef.current.currentTime = 0;
}, []);

const setTime = useCallback((value: number) => {
if (!audioRef.current) return;
setCurrentTime(value);
audioRef.current.currentTime = value;
}, []);

const reset = useCallback(() => {
if (!audioRef.current) return;
audioRef.current.pause();
audioRef.current.currentTime = 0;
if (audioRef.current.src) URL.revokeObjectURL(audioRef.current.src);
Expand All @@ -109,6 +121,7 @@ export const useAudioPlayer = ({
}, []);

const handleDownload = useCallback(async () => {
if (!audioRef.current) return;
const a = document.createElement('a');
a.href = audioRef.current.src;
a.download = 'audio.mp3';
Expand All @@ -124,10 +137,10 @@ export const useAudioPlayer = ({
isPlaying,
pause: handlePause,
play: handlePlay,
ref: audioRef,
ref: audioRef as any,
reset,
setTime,
stop: handleStop,
url: audioRef.current.src,
url: audioRef?.current?.src || '',
};
};
1 change: 0 additions & 1 deletion src/react/hooks/useBlobUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export const useBlobUrl = (src: string) => {
} catch {}
setIsGlobalLoading(false);
},
revalidateOnFocus: false,
},
);

Expand Down
4 changes: 2 additions & 2 deletions src/react/hooks/useStreamAudioPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { RefObject, useCallback, useEffect, useRef, useState } from 'react';

import { AudioProps } from '@/react/AudioPlayer';

export interface StreamAudioPlayerReturn extends AudioProps {
export interface StreamAudioPlayerResponse extends AudioProps {
arrayBuffers: ArrayBuffer[];
download: () => void;
load: (arrayBuffer: ArrayBuffer) => void;
Expand All @@ -11,7 +11,7 @@ export interface StreamAudioPlayerReturn extends AudioProps {
url: string;
}

export const useStreamAudioPlayer = (): StreamAudioPlayerReturn => {
export const useStreamAudioPlayer = (): StreamAudioPlayerResponse => {
const audioRef = useRef<HTMLAudioElement>();
const [arrayBuffers, setArrayBuffers] = useState<ArrayBuffer[]>([]);
const [currentTime, setCurrentTime] = useState(0);
Expand Down
4 changes: 2 additions & 2 deletions src/react/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { default as AudioPlayer, type AudioPlayerProps } from './AudioPlayer';
export { default as AudioVisualizer, type AudioVisualizerProps } from './AudioVisualizer';
export { type AudioPlayerReturn, useAudioPlayer } from './hooks/useAudioPlayer';
export { type AudioPlayerResponse, useAudioPlayer } from './hooks/useAudioPlayer';
export { useAudioVisualizer } from './hooks/useAudioVisualizer';
export { useBlobUrl } from './hooks/useBlobUrl';
export { useStreamAudioPlayer } from './hooks/useStreamAudioPlayer';
Expand All @@ -11,4 +11,4 @@ export { type OpenAISTTOptions, useOpenAISTT } from './useOpenAISTT';
export { type OpenAITTSOptions, useOpenAITTS } from './useOpenAITTS';
export { type SpeechRecognitionOptions, useSpeechRecognition } from './useSpeechRecognition';
export { type SpeechSynthesOptions, useSpeechSynthes } from './useSpeechSynthes';
export { type TTSHook } from './useTTS';
export { type TTSOptions } from './useTTS';
4 changes: 2 additions & 2 deletions src/react/useEdgeSpeech/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useState } from 'react';

import { type EdgeSpeechAPI, type EdgeSpeechPayload, EdgeSpeechTTS } from '@/core/EdgeSpeechTTS';
import { type TTSConfig, useTTS } from '@/react/useTTS';
import { type TTSOptions, useTTS } from '@/react/useTTS';

export interface EdgeSpeechOptions extends Pick<EdgeSpeechPayload, 'options'>, TTSConfig {
export interface EdgeSpeechOptions extends Pick<EdgeSpeechPayload, 'options'>, TTSOptions {
api?: EdgeSpeechAPI;
locale?: string;
}
Expand Down
6 changes: 4 additions & 2 deletions src/react/useMicrosoftSpeech/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {
type MicrosoftSpeechPayload,
MicrosoftSpeechTTS,
} from '@/core/MicrosoftSpeechTTS';
import { type TTSConfig, useTTS } from '@/react/useTTS';
import { type TTSOptions, useTTS } from '@/react/useTTS';

export interface MicrosoftSpeechOptions extends Pick<MicrosoftSpeechPayload, 'options'>, TTSConfig {
export interface MicrosoftSpeechOptions
extends Pick<MicrosoftSpeechPayload, 'options'>,
TTSOptions {
api?: MicrosoftSpeechAPI;
locale?: string;
}
Expand Down
2 changes: 1 addition & 1 deletion src/react/useOpenAISTT/useOpenAISTTCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ export const useOpenAISTTCore = (init: OpenAISTTCoreOptions) => {
const instance = new OpenaiSTT(api);
return instance.create({ options, speech });
},
{ revalidateOnFocus: false, ...swrConfig },
swrConfig,
);
};
4 changes: 2 additions & 2 deletions src/react/useOpenAITTS/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useState } from 'react';

import { OpenAITTS, type OpenAITTSAPI, type OpenAITTSPayload } from '@/core/OpenAITTS';
import { type TTSConfig, useTTS } from '@/react/useTTS';
import { type TTSOptions, useTTS } from '@/react/useTTS';

export interface OpenAITTSOptions extends Pick<OpenAITTSPayload, 'options'>, TTSConfig {
export interface OpenAITTSOptions extends Pick<OpenAITTSPayload, 'options'>, TTSOptions {
api?: OpenAITTSAPI;
}

Expand Down
16 changes: 6 additions & 10 deletions src/react/useTTS/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { useCallback, useEffect, useState } from 'react';
import useSWR, { type SWRConfiguration, type SWRResponse } from 'swr';

import { cleanContent } from '@/core/utils/cleanContent';
import { splitTextIntoSegments } from '@/core/utils/splitTextIntoSegments';
import { type AudioProps } from '@/react/AudioPlayer';
import { useStreamAudioPlayer } from '@/react/hooks/useStreamAudioPlayer';

export interface TTSHook extends SWRConfiguration, Pick<SWRResponse, 'error' | 'mutate'> {
export interface TTSResponse extends SWRConfiguration, Pick<SWRResponse, 'error' | 'mutate'> {
audio: AudioProps & {
arrayBuffers: ArrayBuffer[];
};
Expand All @@ -17,7 +16,7 @@ export interface TTSHook extends SWRConfiguration, Pick<SWRResponse, 'error' | '
stop: () => void;
}

export interface TTSConfig extends SWRConfiguration {
export interface TTSOptions extends SWRConfiguration {
onFinish?: SWRConfiguration['onSuccess'];
onStart?: () => void;
onStop?: () => void;
Expand All @@ -27,8 +26,8 @@ export const useTTS = (
key: string,
text: string,
fetchTTS: (segmentText: string) => Promise<ArrayBuffer>,
{ onError, onSuccess, onFinish, onStart, onStop, ...restSWRConfig }: TTSConfig = {},
): TTSHook => {
{ onError, onSuccess, onFinish, onStart, onStop, ...restSWRConfig }: TTSOptions = {},
): TTSResponse => {
const [shouldFetch, setShouldFetch] = useState<boolean>(false);
const [isGlobalLoading, setIsGlobalLoading] = useState<boolean>(false);
const [index, setIndex] = useState<number>(0);
Expand Down Expand Up @@ -68,7 +67,6 @@ export const useTTS = (
setIsGlobalLoading(false);
}
},
revalidateOnFocus: false,
...restSWRConfig,
},
);
Expand All @@ -82,10 +80,8 @@ export const useTTS = (
}, [text, isLoading]);

useEffect(() => {
cleanContent(text).then((content) => {
const texts = splitTextIntoSegments(content);
handleReset(texts);
});
const texts = splitTextIntoSegments(text);
handleReset(texts);
return () => {
handleReset();
};
Expand Down

0 comments on commit 1fafadc

Please sign in to comment.