mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-09-09 11:29:14 -04:00
TTS: add starting/resuming speaking from mark/range
Also: - Fix `ssml:alphabet` attribute being ignored when set on root - Convert `<br>` to `<break>`
This commit is contained in:
@@ -64,6 +64,8 @@ export const toSSML = doc => {
|
|||||||
el.setAttribute('name', node.dataset.name)
|
el.setAttribute('name', node.dataset.name)
|
||||||
}
|
}
|
||||||
else if (ps.includes(nodeName)) el = ssml.createElementNS(NS.SSML, 'p')
|
else if (ps.includes(nodeName)) el = ssml.createElementNS(NS.SSML, 'p')
|
||||||
|
else if (nodeName === 'br')
|
||||||
|
el = ssml.createElementNS(NS.SSML, 'break')
|
||||||
else if (nodeName === 'em' || nodeName === 'strong')
|
else if (nodeName === 'em' || nodeName === 'strong')
|
||||||
el = ssml.createElementNS(NS.SSML, 'emphasis')
|
el = ssml.createElementNS(NS.SSML, 'emphasis')
|
||||||
|
|
||||||
@@ -93,5 +95,7 @@ export const toSSML = doc => {
|
|||||||
}
|
}
|
||||||
return el
|
return el
|
||||||
}
|
}
|
||||||
return convert(doc.body, ssml.documentElement)
|
convert(doc.body, ssml.documentElement,
|
||||||
|
doc.documentElement.getAttributeNS(NS.SSML, 'alphabet'))
|
||||||
|
return ssml
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,11 @@ export class View extends HTMLElement {
|
|||||||
#tocProgress
|
#tocProgress
|
||||||
#pageProgress
|
#pageProgress
|
||||||
#searchResults = new Map()
|
#searchResults = new Map()
|
||||||
|
#ssml
|
||||||
|
#speechDoc
|
||||||
#speechRanges
|
#speechRanges
|
||||||
|
#speechGranularity
|
||||||
|
#lastSpeechRange
|
||||||
isFixedLayout = false
|
isFixedLayout = false
|
||||||
lastLocation
|
lastLocation
|
||||||
history = new History()
|
history = new History()
|
||||||
@@ -409,17 +413,43 @@ export class View extends HTMLElement {
|
|||||||
for (const item of list) this.deleteAnnotation(item)
|
for (const item of list) this.deleteAnnotation(item)
|
||||||
this.#searchResults.clear()
|
this.#searchResults.clear()
|
||||||
}
|
}
|
||||||
async speak(granularity) {
|
async initSpeech(granularity) {
|
||||||
const { insertMarks, toSSML } = await import('./tts.js')
|
|
||||||
const doc = this.renderer.getContents()[0].doc
|
const doc = this.renderer.getContents()[0].doc
|
||||||
|
if (this.#speechDoc === doc && this.#speechGranularity === granularity)
|
||||||
|
return this.#ssml
|
||||||
|
const { insertMarks, toSSML } = await import('./tts.js')
|
||||||
const { doc: markedDoc, ranges } = insertMarks(textWalker, doc, granularity)
|
const { doc: markedDoc, ranges } = insertMarks(textWalker, doc, granularity)
|
||||||
this.#speechRanges = new Map(ranges)
|
this.#speechRanges = new Map(ranges)
|
||||||
const ssml = toSSML(markedDoc)
|
this.#speechDoc = doc
|
||||||
|
this.#speechGranularity = granularity
|
||||||
|
this.#ssml = toSSML(markedDoc)
|
||||||
|
return this.#ssml
|
||||||
|
}
|
||||||
|
speak(mark) {
|
||||||
|
if (!mark) return new XMLSerializer().serializeToString(this.#ssml)
|
||||||
|
const ssml = document.implementation.createDocument(
|
||||||
|
'http://www.w3.org/2001/10/synthesis', 'speak')
|
||||||
|
ssml.documentElement.replaceWith(ssml.importNode(this.#ssml.documentElement, true))
|
||||||
|
let node = ssml.querySelector(`mark[name="${CSS.escape(mark)}"`)?.previousSibling
|
||||||
|
while (node) {
|
||||||
|
const next = node.previousSibling ?? node.parentNode?.previousSibling
|
||||||
|
node.parentNode.removeChild(node)
|
||||||
|
node = next
|
||||||
|
}
|
||||||
return new XMLSerializer().serializeToString(ssml)
|
return new XMLSerializer().serializeToString(ssml)
|
||||||
}
|
}
|
||||||
|
getSpeechMarkBefore(range) {
|
||||||
|
if (range) for (const [name, range_] of this.#speechRanges.entries())
|
||||||
|
if (range.compareBoundaryPoints(Range.START_TO_START, range_) <= 0)
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
resumeSpeech() {
|
||||||
|
return this.speak(this.getSpeechMarkBefore(this.#lastSpeechRange))
|
||||||
|
}
|
||||||
hightlightSpeechMark(name) {
|
hightlightSpeechMark(name) {
|
||||||
const range = this.#speechRanges.get(name)
|
const range = this.#speechRanges.get(name)
|
||||||
if (range) this.renderer.scrollToAnchor(range, true)
|
this.#lastSpeechRange = range
|
||||||
|
if (range) this.renderer.scrollToAnchor(range.cloneRange(), true)
|
||||||
else console.warn('Mark not found')
|
else console.warn('Mark not found')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user