@polytts/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +123 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/provider.d.ts +151 -0
- package/dist/provider.js +214 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DengQing dengqing0821@gmail.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# @polytts/react
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@polytts/react)
|
|
4
|
+
|
|
5
|
+
React bindings for [`polytts`](https://github.com/Dunqing/polytts) runtimes.
|
|
6
|
+
|
|
7
|
+
Use this package when you already have a runtime and want React hooks and provider state around it.
|
|
8
|
+
|
|
9
|
+
`@polytts/react` intentionally wraps a low-level `TTSRuntime`, not the higher-level `BrowserTTS` controller. In browser apps, you can use either:
|
|
10
|
+
|
|
11
|
+
- `createBrowserTTSRuntime()` directly
|
|
12
|
+
- `createBrowserTTS(...).runtime` if you want the simple browser controller elsewhere in your app
|
|
13
|
+
|
|
14
|
+
If you want the higher-level browser controller state in React, use `BrowserTTSProvider` and `useBrowserTTS()`.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @polytts/react react
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { createBrowserTTSRuntime } from "polytts";
|
|
26
|
+
import { TTSProvider, useTTS } from "@polytts/react";
|
|
27
|
+
|
|
28
|
+
const runtime = createBrowserTTSRuntime({
|
|
29
|
+
initialModelId: "browser-speech",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
function SpeakButton() {
|
|
33
|
+
const { speak, isPreparing, isSpeaking } = useTTS();
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<button disabled={isPreparing || isSpeaking} onClick={() => void speak("Hello from React.")}>
|
|
37
|
+
Speak
|
|
38
|
+
</button>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function App() {
|
|
43
|
+
return (
|
|
44
|
+
<TTSProvider runtime={runtime}>
|
|
45
|
+
<SpeakButton />
|
|
46
|
+
</TTSProvider>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
You can also pair it with the simple browser controller:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { createBrowserTTS } from "polytts";
|
|
55
|
+
import { BrowserTTSProvider, useBrowserTTS } from "@polytts/react";
|
|
56
|
+
|
|
57
|
+
const tts = createBrowserTTS({
|
|
58
|
+
initialModelId: "browser-speech",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
function FamilyName() {
|
|
62
|
+
const family = useBrowserTTS((value) => value.getSelectedFamily()?.name ?? "none");
|
|
63
|
+
return <span>{family}</span>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function App() {
|
|
67
|
+
return (
|
|
68
|
+
<BrowserTTSProvider tts={tts}>
|
|
69
|
+
<FamilyName />
|
|
70
|
+
</BrowserTTSProvider>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Which provider to use
|
|
76
|
+
|
|
77
|
+
Use `TTSProvider` when:
|
|
78
|
+
|
|
79
|
+
- you already work with a `TTSRuntime`
|
|
80
|
+
- you want lower-level runtime state such as `isPreparing`, `phase`, and raw voice resolution
|
|
81
|
+
- your app owns model grouping outside `polytts`
|
|
82
|
+
|
|
83
|
+
Use `BrowserTTSProvider` when:
|
|
84
|
+
|
|
85
|
+
- you want model families, install state, and the higher-level browser controller API
|
|
86
|
+
- your app UI switches between families and models directly
|
|
87
|
+
- you want controller helpers such as `downloadModel()`, `selectFamily()`, and `isInstalled()`
|
|
88
|
+
|
|
89
|
+
## Browser controller example
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { createBrowserTTS } from "polytts";
|
|
93
|
+
import { BrowserTTSProvider, useBrowserTTS } from "@polytts/react";
|
|
94
|
+
|
|
95
|
+
const tts = createBrowserTTS({
|
|
96
|
+
initialModelId: "browser-speech",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
function DownloadState() {
|
|
100
|
+
const selectedModelId = useBrowserTTS((value) => value.selectedModelId);
|
|
101
|
+
const isInstalled = useBrowserTTS((value) =>
|
|
102
|
+
selectedModelId ? value.isInstalled(selectedModelId) : false,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return <span>{isInstalled ? "downloaded" : "not downloaded"}</span>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function App() {
|
|
109
|
+
return (
|
|
110
|
+
<BrowserTTSProvider tts={tts}>
|
|
111
|
+
<DownloadState />
|
|
112
|
+
</BrowserTTSProvider>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## SSR and lifecycle notes
|
|
118
|
+
|
|
119
|
+
- The React providers support server rendering.
|
|
120
|
+
- `createBrowserTTS()` and `createBrowserTTSRuntime()` are still browser entrypoints, so create them in browser/client code.
|
|
121
|
+
- `initialModelId` and `initialVoiceId` set the starting selection, but do not eagerly load the model.
|
|
122
|
+
- Some models resolve their final voices only after preparation, so a voice picker may need to wait for `ready()` or `selectModel()`.
|
|
123
|
+
- Preference persistence is app-owned. Store selected model, voice, and speed in your own state if you want them restored across reloads.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { BrowserTTSContextValue, BrowserTTSController, BrowserTTSFamilyValue, BrowserTTSModelValue, BrowserTTSProvider, BrowserTTSProviderProps, BrowserTTSStateValue, TTSContextValue, TTSProvider, TTSProviderProps, useBrowserTTS, useBrowserTTSController, useBrowserTTSRuntime, useTTS, useTTSRuntime } from "./provider.js";
|
|
2
|
+
export { BrowserTTSContextValue, BrowserTTSController, BrowserTTSFamilyValue, BrowserTTSModelValue, BrowserTTSProvider, BrowserTTSProviderProps, BrowserTTSStateValue, TTSContextValue, TTSProvider, TTSProviderProps, useBrowserTTS, useBrowserTTSController, useBrowserTTSRuntime, useTTS, useTTSRuntime };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { BrowserTTSProvider, TTSProvider, useBrowserTTS, useBrowserTTSController, useBrowserTTSRuntime, useTTS, useTTSRuntime } from "./provider.js";
|
|
2
|
+
export { BrowserTTSProvider, TTSProvider, useBrowserTTS, useBrowserTTSController, useBrowserTTSRuntime, useTTS, useTTSRuntime };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { AudioData, InstallState, ModelId, SpeakOptions, TTSRuntime, VoiceId } from "@polytts/core";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
|
+
import { BrowserTTS, BrowserTTSFamily, BrowserTTSModel, BrowserTTSSpeakOptions, BrowserTTSState } from "@polytts/browser";
|
|
5
|
+
|
|
6
|
+
//#region src/provider.d.ts
|
|
7
|
+
/** Context value provided by {@link TTSProvider}, exposing TTS runtime state and actions. */
|
|
8
|
+
interface TTSContextValue {
|
|
9
|
+
/** The underlying core TTS runtime. */
|
|
10
|
+
runtime: TTSRuntime;
|
|
11
|
+
/** All registered model specs. */
|
|
12
|
+
models: ReturnType<TTSRuntime["listModels"]>;
|
|
13
|
+
/** IDs of models supported by the current environment. */
|
|
14
|
+
supportedModelIds: string[];
|
|
15
|
+
activeModelId: string | null;
|
|
16
|
+
activeVoiceId: string | null;
|
|
17
|
+
/** Voices available for the active model. */
|
|
18
|
+
voices: Awaited<ReturnType<TTSRuntime["listVoices"]>>;
|
|
19
|
+
/** True while a model is being downloaded or loaded. */
|
|
20
|
+
isPreparing: boolean;
|
|
21
|
+
/** True while audio is being played. */
|
|
22
|
+
isSpeaking: boolean;
|
|
23
|
+
/** Granular runtime phase (e.g. installing, loading, speaking). */
|
|
24
|
+
phase: ReturnType<TTSRuntime["getState"]>["phase"];
|
|
25
|
+
/** Model that the current phase applies to, if any. */
|
|
26
|
+
phaseModelId: ReturnType<TTSRuntime["getState"]>["phaseModelId"];
|
|
27
|
+
/** Progress of the current phase from 0 to 1, or null. */
|
|
28
|
+
phaseProgress: ReturnType<TTSRuntime["getState"]>["phaseProgress"];
|
|
29
|
+
/** Per-model runtime information (e.g. WebGPU availability). */
|
|
30
|
+
runtimeInfoByModel: ReturnType<TTSRuntime["getState"]>["runtimeInfoByModel"];
|
|
31
|
+
error: string | null;
|
|
32
|
+
/** Per-model install/download states. */
|
|
33
|
+
installStates: ReturnType<TTSRuntime["getState"]>["installStates"];
|
|
34
|
+
/** Current playback speed multiplier. */
|
|
35
|
+
selectedSpeed: number;
|
|
36
|
+
/** Updates the playback speed multiplier. */
|
|
37
|
+
setSpeed: (speed: number) => void;
|
|
38
|
+
/** Activates a voice on the current model. */
|
|
39
|
+
setVoice: (voiceId: VoiceId) => Promise<void>;
|
|
40
|
+
/** Loads a model (and optionally a voice) so it is ready to speak. */
|
|
41
|
+
prepareModel: (modelId: ModelId, voiceId?: VoiceId) => Promise<void>;
|
|
42
|
+
/** Downloads model assets, with optional progress callback. */
|
|
43
|
+
installModel: (modelId: ModelId, onProgress?: (progress: number) => void) => Promise<void>;
|
|
44
|
+
/** Removes previously downloaded model assets. */
|
|
45
|
+
uninstallModel: (modelId: ModelId) => Promise<void>;
|
|
46
|
+
/** Synthesizes and plays text, using the selected speed. */
|
|
47
|
+
speak: (text: string, options?: SpeakOptions) => Promise<void>;
|
|
48
|
+
/** Returns an async iterable of audio chunks for streaming playback. */
|
|
49
|
+
synthesizeStream: (text: string, options?: SpeakOptions) => AsyncIterable<AudioData>;
|
|
50
|
+
/** Synthesizes text and returns the complete audio data. */
|
|
51
|
+
synthesize: (text: string, options?: SpeakOptions) => Promise<AudioData>;
|
|
52
|
+
/** Stops any in-progress playback. */
|
|
53
|
+
stop: () => void;
|
|
54
|
+
}
|
|
55
|
+
/** Re-export of the underlying BrowserTTS controller for direct access. */
|
|
56
|
+
type BrowserTTSController = BrowserTTS;
|
|
57
|
+
/** Re-export of the BrowserTTSModel type for convenience. */
|
|
58
|
+
type BrowserTTSModelValue = BrowserTTSModel;
|
|
59
|
+
/** Re-export of the BrowserTTSFamily type for convenience. */
|
|
60
|
+
type BrowserTTSFamilyValue = BrowserTTSFamily;
|
|
61
|
+
/** Re-export of the BrowserTTSState type for convenience. */
|
|
62
|
+
type BrowserTTSStateValue = BrowserTTSState;
|
|
63
|
+
/** Context value provided by {@link BrowserTTSProvider}, exposing browser TTS state and actions. */
|
|
64
|
+
interface BrowserTTSContextValue extends BrowserTTSState {
|
|
65
|
+
/** The underlying BrowserTTS controller. */
|
|
66
|
+
tts: BrowserTTS;
|
|
67
|
+
/** The underlying core TTS runtime. */
|
|
68
|
+
runtime: TTSRuntime;
|
|
69
|
+
/** Current playback speed multiplier. */
|
|
70
|
+
selectedSpeed: number;
|
|
71
|
+
/** Updates the playback speed multiplier. */
|
|
72
|
+
setSpeed: (speed: number) => void;
|
|
73
|
+
/** Returns the install state for the given or active model. */
|
|
74
|
+
getInstallState: (modelId?: ModelId | null) => InstallState | null;
|
|
75
|
+
/** Returns true if the given or active model's assets are fully available. */
|
|
76
|
+
isInstalled: (modelId?: ModelId | null) => boolean;
|
|
77
|
+
/** Activates a model, downloading assets if necessary. */
|
|
78
|
+
selectModel: BrowserTTS["selectModel"];
|
|
79
|
+
/** Activates a model family, picking the best supported variant. */
|
|
80
|
+
selectFamily: BrowserTTS["selectFamily"];
|
|
81
|
+
/** Activates a voice, resolving its parent model automatically. */
|
|
82
|
+
setVoice: BrowserTTS["selectVoice"];
|
|
83
|
+
/** Ensures the selected model is loaded and ready to speak. */
|
|
84
|
+
ready: BrowserTTS["ready"];
|
|
85
|
+
/** Downloads model assets without activating the model. */
|
|
86
|
+
downloadModel: BrowserTTS["download"];
|
|
87
|
+
/** Removes previously downloaded model assets. */
|
|
88
|
+
removeDownload: BrowserTTS["removeDownload"];
|
|
89
|
+
/** Synthesizes and plays text, using the selected speed. */
|
|
90
|
+
speak: (text: string, options?: BrowserTTSSpeakOptions) => Promise<void>;
|
|
91
|
+
/** Returns an async iterable of audio chunks for streaming playback. */
|
|
92
|
+
synthesizeStream: (text: string, options?: BrowserTTSSpeakOptions) => AsyncIterable<AudioData>;
|
|
93
|
+
/** Synthesizes text and returns the complete audio data. */
|
|
94
|
+
synthesize: (text: string, options?: BrowserTTSSpeakOptions) => Promise<AudioData>;
|
|
95
|
+
/** Stops any in-progress playback. */
|
|
96
|
+
stop: () => void;
|
|
97
|
+
}
|
|
98
|
+
/** Props for the {@link TTSProvider} component. */
|
|
99
|
+
interface TTSProviderProps {
|
|
100
|
+
runtime: TTSRuntime;
|
|
101
|
+
children: ReactNode;
|
|
102
|
+
/** Starting playback speed multiplier (defaults to 1). */
|
|
103
|
+
initialSpeed?: number;
|
|
104
|
+
}
|
|
105
|
+
/** Props for the {@link BrowserTTSProvider} component. */
|
|
106
|
+
interface BrowserTTSProviderProps {
|
|
107
|
+
/** The BrowserTTS instance to expose via context. */
|
|
108
|
+
tts: BrowserTTS;
|
|
109
|
+
children: ReactNode;
|
|
110
|
+
/** Starting playback speed multiplier (defaults to 1). */
|
|
111
|
+
initialSpeed?: number;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* React context provider that wraps a TTSRuntime and exposes TTS state and actions to descendants.
|
|
115
|
+
* Use with {@link useTTS} or {@link useTTSRuntime} to consume the context.
|
|
116
|
+
*/
|
|
117
|
+
declare function TTSProvider({
|
|
118
|
+
runtime,
|
|
119
|
+
children,
|
|
120
|
+
initialSpeed
|
|
121
|
+
}: TTSProviderProps): _$react_jsx_runtime0.JSX.Element;
|
|
122
|
+
/**
|
|
123
|
+
* React context provider for browser-based TTS, wrapping a BrowserTTS instance. Use with
|
|
124
|
+
* {@link useBrowserTTS}, {@link useBrowserTTSRuntime}, or {@link useBrowserTTSController} to consume
|
|
125
|
+
* the context.
|
|
126
|
+
*/
|
|
127
|
+
declare function BrowserTTSProvider({
|
|
128
|
+
tts,
|
|
129
|
+
children,
|
|
130
|
+
initialSpeed
|
|
131
|
+
}: BrowserTTSProviderProps): _$react_jsx_runtime0.JSX.Element;
|
|
132
|
+
/**
|
|
133
|
+
* Returns the full TTS context value, or a selected slice of it via an optional selector. Must be
|
|
134
|
+
* used within a {@link TTSProvider}. Use a selector to avoid unnecessary re-renders.
|
|
135
|
+
*/
|
|
136
|
+
declare function useTTS(): TTSContextValue;
|
|
137
|
+
declare function useTTS<T>(selector: (value: TTSContextValue) => T): T;
|
|
138
|
+
/** Returns the underlying TTSRuntime from the nearest {@link TTSProvider}. */
|
|
139
|
+
declare function useTTSRuntime(): TTSRuntime;
|
|
140
|
+
/**
|
|
141
|
+
* Returns the full browser TTS context value, or a selected slice via an optional selector. Must be
|
|
142
|
+
* used within a {@link BrowserTTSProvider}. Use a selector to avoid unnecessary re-renders.
|
|
143
|
+
*/
|
|
144
|
+
declare function useBrowserTTS(): BrowserTTSContextValue;
|
|
145
|
+
declare function useBrowserTTS<T>(selector: (value: BrowserTTSContextValue) => T): T;
|
|
146
|
+
/** Returns the underlying TTSRuntime from the nearest {@link BrowserTTSProvider}. */
|
|
147
|
+
declare function useBrowserTTSRuntime(): TTSRuntime;
|
|
148
|
+
/** Returns the underlying BrowserTTS controller from the nearest {@link BrowserTTSProvider}. */
|
|
149
|
+
declare function useBrowserTTSController(): BrowserTTSController;
|
|
150
|
+
//#endregion
|
|
151
|
+
export { BrowserTTSContextValue, BrowserTTSController, BrowserTTSFamilyValue, BrowserTTSModelValue, BrowserTTSProvider, BrowserTTSProviderProps, BrowserTTSStateValue, TTSContextValue, TTSProvider, TTSProviderProps, useBrowserTTS, useBrowserTTSController, useBrowserTTSRuntime, useTTS, useTTSRuntime };
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { normalizeSpeakSpeed } from "@polytts/core";
|
|
2
|
+
import { createContext, useCallback, useContext, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/provider.tsx
|
|
5
|
+
const TTSContext = createContext(null);
|
|
6
|
+
const BrowserTTSContext = createContext(null);
|
|
7
|
+
/**
|
|
8
|
+
* React context provider that wraps a TTSRuntime and exposes TTS state and actions to descendants.
|
|
9
|
+
* Use with {@link useTTS} or {@link useTTSRuntime} to consume the context.
|
|
10
|
+
*/
|
|
11
|
+
function TTSProvider({ runtime, children, initialSpeed }) {
|
|
12
|
+
const runtimeState = useSyncExternalStore(runtime.subscribe.bind(runtime), runtime.getState.bind(runtime), runtime.getState.bind(runtime));
|
|
13
|
+
const [selectedSpeed, setSelectedSpeedState] = useState(() => normalizeSpeakSpeed(initialSpeed));
|
|
14
|
+
const setSpeed = useCallback((speed) => {
|
|
15
|
+
setSelectedSpeedState(normalizeSpeakSpeed(speed));
|
|
16
|
+
}, []);
|
|
17
|
+
const prepareModel = useCallback(async (modelId, voiceId) => {
|
|
18
|
+
await runtime.prepare(modelId, { voiceId });
|
|
19
|
+
}, [runtime]);
|
|
20
|
+
const setVoice = useCallback(async (voiceId) => {
|
|
21
|
+
const activeModelId = runtime.getState().activeModelId;
|
|
22
|
+
if (!activeModelId) return;
|
|
23
|
+
await runtime.prepare(activeModelId, { voiceId });
|
|
24
|
+
}, [runtime]);
|
|
25
|
+
const installModel = useCallback(async (modelId, onProgress) => {
|
|
26
|
+
await runtime.install(modelId, onProgress);
|
|
27
|
+
}, [runtime]);
|
|
28
|
+
const uninstallModel = useCallback(async (modelId) => {
|
|
29
|
+
await runtime.uninstall(modelId);
|
|
30
|
+
}, [runtime]);
|
|
31
|
+
const speak = useCallback(async (text, options) => {
|
|
32
|
+
await runtime.speak(text, {
|
|
33
|
+
...options,
|
|
34
|
+
voiceId: options?.voiceId ?? runtime.getState().activeVoiceId ?? void 0,
|
|
35
|
+
speed: options?.speed ?? selectedSpeed
|
|
36
|
+
});
|
|
37
|
+
}, [runtime, selectedSpeed]);
|
|
38
|
+
const synthesize = useCallback(async (text, options) => {
|
|
39
|
+
return runtime.synthesize(text, {
|
|
40
|
+
...options,
|
|
41
|
+
voiceId: options?.voiceId ?? runtime.getState().activeVoiceId ?? void 0,
|
|
42
|
+
speed: options?.speed ?? selectedSpeed
|
|
43
|
+
});
|
|
44
|
+
}, [runtime, selectedSpeed]);
|
|
45
|
+
const synthesizeStream = useCallback((text, options) => runtime.synthesizeStream(text, {
|
|
46
|
+
...options,
|
|
47
|
+
voiceId: options?.voiceId ?? runtime.getState().activeVoiceId ?? void 0,
|
|
48
|
+
speed: options?.speed ?? selectedSpeed
|
|
49
|
+
}), [runtime, selectedSpeed]);
|
|
50
|
+
const value = useMemo(() => ({
|
|
51
|
+
runtime,
|
|
52
|
+
models: runtime.listModels(),
|
|
53
|
+
supportedModelIds: runtimeState.supportedModelIds,
|
|
54
|
+
activeModelId: runtimeState.activeModelId,
|
|
55
|
+
activeVoiceId: runtimeState.activeVoiceId,
|
|
56
|
+
voices: runtimeState.voices,
|
|
57
|
+
isPreparing: runtimeState.isPreparing,
|
|
58
|
+
isSpeaking: runtimeState.isSpeaking,
|
|
59
|
+
phase: runtimeState.phase,
|
|
60
|
+
phaseModelId: runtimeState.phaseModelId,
|
|
61
|
+
phaseProgress: runtimeState.phaseProgress,
|
|
62
|
+
runtimeInfoByModel: runtimeState.runtimeInfoByModel,
|
|
63
|
+
error: runtimeState.error,
|
|
64
|
+
installStates: runtimeState.installStates,
|
|
65
|
+
selectedSpeed,
|
|
66
|
+
setSpeed,
|
|
67
|
+
setVoice,
|
|
68
|
+
prepareModel,
|
|
69
|
+
installModel,
|
|
70
|
+
uninstallModel,
|
|
71
|
+
speak,
|
|
72
|
+
synthesizeStream,
|
|
73
|
+
synthesize,
|
|
74
|
+
stop: runtime.stop.bind(runtime)
|
|
75
|
+
}), [
|
|
76
|
+
installModel,
|
|
77
|
+
prepareModel,
|
|
78
|
+
runtime,
|
|
79
|
+
runtimeState,
|
|
80
|
+
selectedSpeed,
|
|
81
|
+
setSpeed,
|
|
82
|
+
setVoice,
|
|
83
|
+
speak,
|
|
84
|
+
synthesize,
|
|
85
|
+
synthesizeStream,
|
|
86
|
+
uninstallModel
|
|
87
|
+
]);
|
|
88
|
+
return /* @__PURE__ */ jsx(TTSContext.Provider, {
|
|
89
|
+
value,
|
|
90
|
+
children
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* React context provider for browser-based TTS, wrapping a BrowserTTS instance. Use with
|
|
95
|
+
* {@link useBrowserTTS}, {@link useBrowserTTSRuntime}, or {@link useBrowserTTSController} to consume
|
|
96
|
+
* the context.
|
|
97
|
+
*/
|
|
98
|
+
function BrowserTTSProvider({ tts, children, initialSpeed }) {
|
|
99
|
+
const browserStore = useMemo(() => {
|
|
100
|
+
let snapshot = tts.getState();
|
|
101
|
+
return {
|
|
102
|
+
subscribe(listener) {
|
|
103
|
+
return tts.subscribe((state) => {
|
|
104
|
+
snapshot = state;
|
|
105
|
+
listener();
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
getSnapshot() {
|
|
109
|
+
return snapshot;
|
|
110
|
+
},
|
|
111
|
+
getServerSnapshot() {
|
|
112
|
+
return snapshot;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}, [tts]);
|
|
116
|
+
const browserState = useSyncExternalStore(browserStore.subscribe.bind(browserStore), browserStore.getSnapshot.bind(browserStore), browserStore.getServerSnapshot.bind(browserStore));
|
|
117
|
+
const [selectedSpeed, setSelectedSpeedState] = useState(() => normalizeSpeakSpeed(initialSpeed));
|
|
118
|
+
const setSpeed = useCallback((speed) => {
|
|
119
|
+
setSelectedSpeedState(normalizeSpeakSpeed(speed));
|
|
120
|
+
}, []);
|
|
121
|
+
const speak = useCallback(async (text, options) => {
|
|
122
|
+
await tts.speak(text, {
|
|
123
|
+
...options,
|
|
124
|
+
voiceId: options?.voiceId ?? browserState.selectedVoiceId ?? void 0,
|
|
125
|
+
speed: options?.speed ?? selectedSpeed
|
|
126
|
+
});
|
|
127
|
+
}, [
|
|
128
|
+
browserState.selectedVoiceId,
|
|
129
|
+
selectedSpeed,
|
|
130
|
+
tts
|
|
131
|
+
]);
|
|
132
|
+
const synthesize = useCallback(async (text, options) => {
|
|
133
|
+
return tts.synthesize(text, {
|
|
134
|
+
...options,
|
|
135
|
+
voiceId: options?.voiceId ?? browserState.selectedVoiceId ?? void 0,
|
|
136
|
+
speed: options?.speed ?? selectedSpeed
|
|
137
|
+
});
|
|
138
|
+
}, [
|
|
139
|
+
browserState.selectedVoiceId,
|
|
140
|
+
selectedSpeed,
|
|
141
|
+
tts
|
|
142
|
+
]);
|
|
143
|
+
const synthesizeStream = useCallback((text, options) => tts.synthesizeStream(text, {
|
|
144
|
+
...options,
|
|
145
|
+
voiceId: options?.voiceId ?? browserState.selectedVoiceId ?? void 0,
|
|
146
|
+
speed: options?.speed ?? selectedSpeed
|
|
147
|
+
}), [
|
|
148
|
+
browserState.selectedVoiceId,
|
|
149
|
+
selectedSpeed,
|
|
150
|
+
tts
|
|
151
|
+
]);
|
|
152
|
+
const value = useMemo(() => ({
|
|
153
|
+
tts,
|
|
154
|
+
runtime: tts.runtime,
|
|
155
|
+
...browserState,
|
|
156
|
+
selectedSpeed,
|
|
157
|
+
setSpeed,
|
|
158
|
+
getInstallState: tts.getInstallState.bind(tts),
|
|
159
|
+
isInstalled: tts.isInstalled.bind(tts),
|
|
160
|
+
selectModel: tts.selectModel.bind(tts),
|
|
161
|
+
selectFamily: tts.selectFamily.bind(tts),
|
|
162
|
+
setVoice: tts.selectVoice.bind(tts),
|
|
163
|
+
ready: tts.ready.bind(tts),
|
|
164
|
+
downloadModel: tts.download.bind(tts),
|
|
165
|
+
removeDownload: tts.removeDownload.bind(tts),
|
|
166
|
+
speak,
|
|
167
|
+
synthesizeStream,
|
|
168
|
+
synthesize,
|
|
169
|
+
stop: tts.stop.bind(tts)
|
|
170
|
+
}), [
|
|
171
|
+
browserState,
|
|
172
|
+
selectedSpeed,
|
|
173
|
+
setSpeed,
|
|
174
|
+
speak,
|
|
175
|
+
synthesize,
|
|
176
|
+
synthesizeStream,
|
|
177
|
+
tts
|
|
178
|
+
]);
|
|
179
|
+
return /* @__PURE__ */ jsx(BrowserTTSContext.Provider, {
|
|
180
|
+
value,
|
|
181
|
+
children
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function useStableSelector(context, selector) {
|
|
185
|
+
const selectedRef = useRef(void 0);
|
|
186
|
+
const selected = selector(context);
|
|
187
|
+
if (!Object.is(selectedRef.current, selected)) selectedRef.current = selected;
|
|
188
|
+
return selectedRef.current;
|
|
189
|
+
}
|
|
190
|
+
const identity = (v) => v;
|
|
191
|
+
function useTTS(selector) {
|
|
192
|
+
const context = useContext(TTSContext);
|
|
193
|
+
if (!context) throw new Error("useTTS must be used within a TTSProvider");
|
|
194
|
+
return useStableSelector(context, selector ?? identity);
|
|
195
|
+
}
|
|
196
|
+
/** Returns the underlying TTSRuntime from the nearest {@link TTSProvider}. */
|
|
197
|
+
function useTTSRuntime() {
|
|
198
|
+
return useTTS((value) => value.runtime);
|
|
199
|
+
}
|
|
200
|
+
function useBrowserTTS(selector) {
|
|
201
|
+
const context = useContext(BrowserTTSContext);
|
|
202
|
+
if (!context) throw new Error("useBrowserTTS must be used within a BrowserTTSProvider");
|
|
203
|
+
return useStableSelector(context, selector ?? identity);
|
|
204
|
+
}
|
|
205
|
+
/** Returns the underlying TTSRuntime from the nearest {@link BrowserTTSProvider}. */
|
|
206
|
+
function useBrowserTTSRuntime() {
|
|
207
|
+
return useBrowserTTS((value) => value.runtime);
|
|
208
|
+
}
|
|
209
|
+
/** Returns the underlying BrowserTTS controller from the nearest {@link BrowserTTSProvider}. */
|
|
210
|
+
function useBrowserTTSController() {
|
|
211
|
+
return useBrowserTTS((value) => value.tts);
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
export { BrowserTTSProvider, TTSProvider, useBrowserTTS, useBrowserTTSController, useBrowserTTSRuntime, useTTS, useTTSRuntime };
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@polytts/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React bindings for polytts runtimes.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"polytts",
|
|
7
|
+
"react",
|
|
8
|
+
"text-to-speech",
|
|
9
|
+
"tts"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/Dunqing/polytts/tree/main/packages/react#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/Dunqing/polytts/issues"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/Dunqing/polytts.git",
|
|
19
|
+
"directory": "packages/react"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@polytts/browser": "0.1.0",
|
|
37
|
+
"@polytts/core": "0.1.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@testing-library/react": "latest",
|
|
41
|
+
"@types/react": "latest",
|
|
42
|
+
"@types/react-dom": "latest",
|
|
43
|
+
"jsdom": "latest",
|
|
44
|
+
"react": "latest",
|
|
45
|
+
"react-dom": "latest",
|
|
46
|
+
"vite-plus": "latest"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": ">=19"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "vp pack",
|
|
53
|
+
"test": "vp test",
|
|
54
|
+
"test:run": "vp test run"
|
|
55
|
+
},
|
|
56
|
+
"main": "./dist/index.js",
|
|
57
|
+
"module": "./dist/index.js",
|
|
58
|
+
"types": "./dist/index.d.ts"
|
|
59
|
+
}
|