forked from akai/readest
tts: read from last read sentence in tts (#1291)
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -125,6 +125,7 @@ export interface ViewConfig {
|
||||
export interface TTSConfig {
|
||||
ttsRate: number;
|
||||
ttsVoice: string;
|
||||
ttsLocation: string;
|
||||
}
|
||||
|
||||
export interface TranslatorConfig {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user