Podcast player: calmer line-following scroll, and a voice you can pick
"The scrolling works kinda weirdly" + "maybe a less robotic voice, preferably male". Scroll: the transcript now follows by LINE, not by word - words on the same line never move the page (that was the jitter), a line only scrolls once it leaves a comfortable band, small corrections are instant rather than animated, and a wheel/touch/drag on the transcript pauses the following for four seconds instead of fighting the reader. Chapter jumps and word taps re-centre at once. Voice: lib/podcast-voices ranks what the platform installed (natural / neural / online names up, espeak / SAM / compact down, a male name preferred) and the player picks the best one automatically; a new picker in the chrome lists everything in the episode's language, applies it live and remembers the choice per language. A note points at the system settings when only robotic voices exist. The engine takes the chosen voice instead of guessing. podcast-test: 34 checks now (voice ranking, filtered picker, persisted pick, same-line silence, line following, manual-scroll suspension). Version 0.7.4, SW v96.
This commit is contained in:
parent
d95c5c7151
commit
4ec986da12
8 changed files with 684 additions and 46 deletions
|
|
@ -16,7 +16,7 @@
|
|||
* "jump to a chapter" and "resume where you left off" the same operation.
|
||||
*/
|
||||
|
||||
import { pickVoice, speechAvailable } from "./speech";
|
||||
import { speechAvailable } from "./speech";
|
||||
import type { PodcastSection } from "./schemas";
|
||||
|
||||
export type PodcastVoice = { section: number; word: number };
|
||||
|
|
@ -25,6 +25,8 @@ export type PodcastSpeechEvents = {
|
|||
sections: PodcastSection[];
|
||||
locale: string;
|
||||
rate: number;
|
||||
/** The chosen voice (lib/podcast-voices); null lets the platform decide. */
|
||||
voice?: SpeechSynthesisVoice | null;
|
||||
/** Global word position changed (word = index inside its section). */
|
||||
onWord: (position: PodcastVoice) => void;
|
||||
/** Playback actually began (after the engine accepted it). */
|
||||
|
|
@ -134,8 +136,7 @@ export function createPodcastSpeech(events: PodcastSpeechEvents): PodcastSpeech
|
|||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = events.locale;
|
||||
utterance.rate = events.rate;
|
||||
const voice = pickVoice(events.locale);
|
||||
if (voice) utterance.voice = voice;
|
||||
if (events.voice) utterance.voice = events.voice;
|
||||
|
||||
const offsetFor = (charIndex: number) => {
|
||||
/* First token whose span ends after the reported character. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue