tts: read from last read sentence in tts (#1291)

This commit is contained in:
Huang Xin
2025-05-31 16:53:36 +08:00
committed by GitHub
parent 5c9c11fafd
commit 84fc4cec5e
6 changed files with 35 additions and 8 deletions
@@ -109,11 +109,10 @@ const FooterBar: React.FC<FooterBarProps> = ({
const handleSpeakText = async () => {
if (!view || !progress) return;
const { range } = progress;
if (eventDispatcher.dispatchSync('tts-is-speaking')) {
eventDispatcher.dispatch('tts-stop', { bookKey });
} else {
eventDispatcher.dispatch('tts-speak', { bookKey, range });
eventDispatcher.dispatch('tts-speak', { bookKey });
}
};
@@ -12,6 +12,7 @@ import { parseSSMLLang } from '@/utils/ssml';
import { getOSPlatform } from '@/utils/misc';
import { throttle } from '@/utils/throttle';
import { invokeUseBackgroundAudio } from '@/utils/bridge';
import { CFI } from '@/libs/document';
import Popup from '@/components/Popup';
import TTSPanel from './TTSPanel';
import TTSIcon from './TTSIcon';
@@ -107,7 +108,7 @@ const TTSControl = () => {
const progress = getProgress(bookKey);
const { sectionLabel } = progress || {};
const mark = (e as CustomEvent<TTSMark>).detail;
if ('mediaSession' in navigator) {
if (appService?.isMobileApp && 'mediaSession' in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: mark.text,
artist: sectionLabel || title,
@@ -127,9 +128,10 @@ const TTSControl = () => {
const handleTTSSpeak = async (event: CustomEvent) => {
const { bookKey, range } = event.detail;
const view = getView(bookKey);
const progress = getProgress(bookKey);
const viewSettings = getViewSettings(bookKey);
const bookData = getBookData(bookKey);
if (!view || !viewSettings || !bookData || !bookData.book) return;
if (!view || !progress || !viewSettings || !bookData || !bookData.book) return;
if (bookData.book?.format === 'PDF') {
eventDispatcher.dispatch('toast', {
message: _('TTS not supported for PDF'),
@@ -138,6 +140,21 @@ const TTSControl = () => {
return;
}
let ttsFromRange = range || progress.range;
if (viewSettings.ttsLocation) {
const { location } = progress;
const ttsCfi = viewSettings.ttsLocation;
const start = CFI.collapse(location);
const end = CFI.collapse(location, true);
if (CFI.compare(start, ttsCfi) * CFI.compare(end, ttsCfi) <= 0) {
const { index, anchor } = view.resolveCFI(ttsCfi);
const { doc } = view.renderer.getContents().find((x) => (x.index = index)) || {};
if (doc) {
ttsFromRange = anchor(doc) || ttsFromRange;
}
}
}
const primaryLang = bookData.book.primaryLanguage;
setBookKey(bookKey);
@@ -160,7 +177,7 @@ const TTSControl = () => {
const ttsController = new TTSController(view);
await ttsController.init();
await ttsController.initViewTTS();
const ssml = view.tts?.from(range);
const ssml = view.tts?.from(ttsFromRange);
if (ssml) {
let lang = parseSSMLLang(ssml) || 'en';
// We will not trust 'en' language from ssml, as it may be a fallback or hardcoded value
@@ -12,10 +12,10 @@ export const useTextSelector = (
handleDismissPopup: () => void,
) => {
const { getBookData } = useBookDataStore();
const { getView, getViewSettings } = useReaderStore();
const { getView, getViewState, getViewSettings, setViewSettings } = useReaderStore();
const view = getView(bookKey);
const viewSettings = getViewSettings(bookKey)!;
const bookData = getBookData(bookKey)!;
const viewSettings = getViewSettings(bookKey)!;
const primaryLang = bookData.book?.primaryLanguage || 'en';
const osPlatform = getOSPlatform();
@@ -81,9 +81,17 @@ export const useTextSelector = (
// On Android no proper events are fired to notify selection done,
// we make the popup show when the selection is changed
// note that selection may be initiated by a tts speak
if (osPlatform === 'android' && isTouchStarted.current) {
if (isTouchStarted.current && osPlatform === 'android') {
makeSelection(sel, index, false);
}
if (!isTouchStarted.current && getViewState(bookKey)?.ttsEnabled) {
const viewSettings = getViewSettings(bookKey)!;
const cfi = view?.getCFI(index, sel.getRangeAt(0));
if (cfi) {
viewSettings.ttsLocation = cfi || '';
setViewSettings(bookKey, viewSettings);
}
}
isUpToPopup.current = true;
};
const handlePointerup = (doc: Document, index: number) => {
@@ -146,6 +146,7 @@ export const DEFAULT_VIEW_CONFIG: ViewConfig = {
export const DEFAULT_TTS_CONFIG: TTSConfig = {
ttsRate: 1.3,
ttsVoice: '',
ttsLocation: '',
};
export const DEFAULT_TRANSLATOR_CONFIG: TranslatorConfig = {
+1
View File
@@ -125,6 +125,7 @@ export interface ViewConfig {
export interface TTSConfig {
ttsRate: number;
ttsVoice: string;
ttsLocation: string;
}
export interface TranslatorConfig {
+1
View File
@@ -15,6 +15,7 @@ export interface FoliateView extends HTMLElement {
goLeft: () => void;
goRight: () => void;
getCFI: (index: number, range: Range) => string;
resolveCFI: (cfi: string) => { index: number; anchor: (doc: Document) => Range };
addAnnotation: (note: BookNote, remove?: boolean) => { index: number; label: string };
search: (config: BookSearchConfig) => AsyncGenerator<BookSearchResult | string, void, void>;
clearSearch: () => void;