Skip to content

Commit

Permalink
Make the current task's input selection observable
Browse files Browse the repository at this point in the history
This will be used by the UI code to highlight currently spoken text,
as discussed in issue #1.

The TaskProgressObserver interface has been renamed to TaskObserver.
  • Loading branch information
drmfinlay committed May 21, 2023
1 parent 931a1cd commit 52aeaf9
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 32 deletions.
41 changes: 27 additions & 14 deletions app/src/main/java/com/danefinlay/ttsutil/ApplicationEx.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,27 @@ class ApplicationEx : Application(), OnInitListener {
private val taskQueue = ArrayDeque<TaskData>()
private val userTaskExecService by lazy { Executors.newSingleThreadExecutor() }
private val notifyingExecService by lazy { Executors.newSingleThreadExecutor() }
private val progressObservers = mutableSetOf<TaskProgressObserver>()
private val taskObservers = mutableSetOf<TaskObserver>()
private var notificationBuilder: NotificationCompat.Builder? = null

@Volatile
private var notificationsEnabled: Boolean = false

private val asyncProgressObserver = object : TaskProgressObserver {
private val asyncTaskObserver = object : TaskObserver {
override fun notifyProgress(progress: Int, taskId: Int,
remainingTasks: Int) {
// Submit a executor task to call the application's notifyProgress()
// procedure.
// Submit an executor task to call the application procedure.
notifyingExecService.submit {
this@ApplicationEx.notifyProgress(progress, taskId, remainingTasks)
}
}

override fun notifyInputSelection(start: Long, end: Long, taskId: Int) {
// Submit an executor task to call the application procedure.
notifyingExecService.submit {
this@ApplicationEx.notifyInputSelection(start, end, taskId)
}
}
}

val readingTaskInProgress: Boolean
Expand Down Expand Up @@ -352,7 +358,7 @@ class ApplicationEx : Application(), OnInitListener {
currentTask?.finalize()
currentTask = null
if (idle) {
asyncProgressObserver.notifyProgress(100, TASK_ID_IDLE, 0)
asyncTaskObserver.notifyProgress(100, TASK_ID_IDLE, 0)
}
return success
}
Expand Down Expand Up @@ -435,12 +441,12 @@ class ApplicationEx : Application(), OnInitListener {
setupTTS(initListener, preferredEngine)
}

fun addProgressObserver(observer: TaskProgressObserver) {
progressObservers.add(observer)
fun addTaskObserver(observer: TaskObserver) {
taskObservers.add(observer)
}

fun deleteProgressObserver(observer: TaskProgressObserver) {
progressObservers.remove(observer)
fun deleteTaskObserver(observer: TaskObserver) {
taskObservers.remove(observer)
}

private fun postNotification(taskData: TaskData, remainingTasks: Int) {
Expand Down Expand Up @@ -500,7 +506,7 @@ class ApplicationEx : Application(), OnInitListener {
}

// Notify other observers.
for (observer in progressObservers) {
for (observer in taskObservers) {
observer.notifyProgress(progress, taskId, totalUnfinishedTasks)
}

Expand All @@ -526,6 +532,13 @@ class ApplicationEx : Application(), OnInitListener {
}
}

private fun notifyInputSelection(start: Long, end: Long, taskId: Int) {
// Notify other observers.
for (observer in taskObservers) {
observer.notifyInputSelection(start, end, taskId)
}
}

@Synchronized
private fun speak(taskData: TaskData.ReadInputTaskData): Int {
val tts = mTTS
Expand All @@ -552,7 +565,7 @@ class ApplicationEx : Application(), OnInitListener {

// Initialize the task, begin it asynchronously and return.
val task = ReadInputTask(this, tts, inputStream, inputSize,
taskData.queueMode, asyncProgressObserver)
taskData.queueMode, asyncTaskObserver)
currentTask = task
userTaskExecService.submit { task.begin() }
return SUCCESS
Expand Down Expand Up @@ -595,7 +608,7 @@ class ApplicationEx : Application(), OnInitListener {

// Initialize the task, begin it asynchronously and return.
val task = FileSynthesisTask(this, tts, inputStream, inputSize,
waveFilename, inWaveFiles, asyncProgressObserver)
waveFilename, inWaveFiles, asyncTaskObserver)
currentTask = task
userTaskExecService.submit { task.begin() }
return SUCCESS
Expand All @@ -618,7 +631,7 @@ class ApplicationEx : Application(), OnInitListener {
waveFilename, "audio/x-wav") ?: return UNWRITABLE_OUT_DIR

// Initialize the task, begin it asynchronously and return.
val task = ProcessWaveFilesTask(this, asyncProgressObserver,
val task = ProcessWaveFilesTask(this, asyncTaskObserver,
prevTaskData.inWaveFiles, outputStream, waveFilename)
currentTask = task
userTaskExecService.submit { task.begin() }
Expand All @@ -631,7 +644,7 @@ class ApplicationEx : Application(), OnInitListener {

// If the specified task is at the head of the queue, begin it.
// Otherwise, notify the observer.
val observer = asyncProgressObserver
val observer = asyncTaskObserver
val result: Int
if (taskQueue.peek() === taskData && ttsInitialized) {
// Begin the task, taking note of information that might be used later.
Expand Down
52 changes: 41 additions & 11 deletions app/src/main/java/com/danefinlay/ttsutil/TTSTasks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,12 @@ abstract class TTSTask(ctx: Context, tts: TextToSpeech,
private val inputStream: InputStream,
private val inputSize: Long,
private val taskId: Int,
private val observer: TaskProgressObserver) :
private val observer: TaskObserver) :
MyUtteranceProgressListener(ctx, tts), Task {

private class UtteranceInfo(val id: String,
val text: CharSequence,
val inputStartIndex: Long,
val bytesRead: Int,
val silenceDuration: Long,
@Volatile
Expand All @@ -110,11 +111,14 @@ abstract class TTSTask(ctx: Context, tts: TextToSpeech,
protected var finalize: Boolean = false

@Volatile
protected var inputProcessed: Long = 0
protected var inputRead: Long = 0

@Volatile
private var inputFiltered: Long = 0

@Volatile
protected var inputProcessed: Long = 0

@Volatile
private var streamHasFurtherInput: Boolean = true

Expand Down Expand Up @@ -312,14 +316,14 @@ abstract class TTSTask(ctx: Context, tts: TextToSpeech,

// Gather utterance information and add it to the utterance info list.
val utteranceId = nextUtteranceId()
if (silenceDuration > 0L) {
// Scale silence by the speech rate, if appropriate.
if (scaleSilenceToRate) {
silenceDuration = (silenceDuration / speechRate).toLong()
}
val inputStartIndex = inputRead
inputRead += bytesRead
// Scale silence by the speech rate, if appropriate.
if (silenceDuration > 0L && scaleSilenceToRate) {
silenceDuration = (silenceDuration / speechRate).toLong()
}
val utteranceInfo = UtteranceInfo(utteranceId, text, bytesRead,
silenceDuration, false)
val utteranceInfo = UtteranceInfo(utteranceId, text, inputStartIndex,
bytesRead, silenceDuration, false)
utteranceInfoList.add(utteranceInfo)

// Use the utterance info to enqueue text and silence.
Expand Down Expand Up @@ -364,6 +368,32 @@ abstract class TTSTask(ctx: Context, tts: TextToSpeech,
if (utteranceId == null || utteranceInfoList.size == 0) return

// Log.e(TAG, "onStart(): $utteranceId")

val utteranceInfo = utteranceInfoList[0]
if (utteranceId == utteranceInfo.id) {
// Call onRangeStart() with the appropriate parameters. This is done in
// order to accommodate engines that do not support the callback.
// Debugging note: text.length may be shorter than bytesRead due to
// silent utterance substitution and filtering.
onRangeStart(utteranceId, 0, utteranceInfo.bytesRead, 0)
}
}

override fun onRangeStart(utteranceId: String?, start: Int, end: Int,
frame: Int) {
super.onRangeStart(utteranceId, start, end, frame)

val utteranceInfo = utteranceInfoList[0]
if (utteranceId == utteranceInfo.id) {

// Calculate the range of what is about to be spoken relative to the
// whole input. We will call this the current input selection.
val selectionStart = utteranceInfo.inputStartIndex + start
val selectionEnd = utteranceInfo.inputStartIndex + end

// Notify the observer of the current input selection.
observer.notifyInputSelection(selectionStart, selectionEnd, taskId)
}
}

override fun onError(utteranceId: String?) { // deprecated
Expand Down Expand Up @@ -473,7 +503,7 @@ abstract class TTSTask(ctx: Context, tts: TextToSpeech,

class ReadInputTask(ctx: Context, tts: TextToSpeech, inputStream: InputStream,
inputSize: Long, private val queueMode: Int,
observer: TaskProgressObserver) :
observer: TaskObserver) :
TTSTask(ctx, tts, inputStream, inputSize, TASK_ID_READ_TEXT,
observer) {

Expand Down Expand Up @@ -535,7 +565,7 @@ class FileSynthesisTask(ctx: Context, tts: TextToSpeech,
inputStream: InputStream, inputSize: Long,
private val waveFilename: String,
private val inWaveFiles: MutableList<File>,
observer: TaskProgressObserver) :
observer: TaskObserver) :
TTSTask(ctx, tts, inputStream, inputSize,
TASK_ID_WRITE_FILE, observer) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

package com.danefinlay.ttsutil

interface TaskProgressObserver {
interface TaskObserver {
fun notifyProgress(progress: Int, taskId: Int, remainingTasks: Int)
fun notifyInputSelection(start: Long, end: Long, taskId: Int)
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import java.io.FileOutputStream
import java.io.OutputStream

class ProcessWaveFilesTask(private val ctx: Context,
private val observer: TaskProgressObserver,
private val observer: TaskObserver,
private val inWaveFiles: MutableList<File>,
private val outputStream: OutputStream,
private val finalWaveFilename: String):
Expand All @@ -40,7 +40,7 @@ class ProcessWaveFilesTask(private val ctx: Context,
private var finalize: Boolean = false

override fun begin(): Boolean {
// Notify the progress observer that post-processing has begun.
// Notify the observer that post-processing has begun.
observer.notifyProgress(0, TASK_ID, 0)

// Find the first proper wave file and read its header.
Expand Down
14 changes: 10 additions & 4 deletions app/src/main/java/com/danefinlay/ttsutil/ui/TTSActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import java.util.*
* way.
*/
abstract class TTSActivity: MyAppCompatActivity(), TextToSpeech.OnInitListener,
ActivityInterface, TaskProgressObserver {
ActivityInterface, TaskObserver {

private val idleStatusEvent =
ActivityEvent.StatusUpdateEvent(100, TASK_ID_IDLE, 0)
Expand Down Expand Up @@ -135,8 +135,8 @@ abstract class TTSActivity: MyAppCompatActivity(), TextToSpeech.OnInitListener,
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Register as a task progress observer.
myApplication.addProgressObserver(this)
// Register as a task observer.
myApplication.addTaskObserver(this)

// Restore instance data.
savedInstanceState?.run {
Expand Down Expand Up @@ -359,6 +359,12 @@ abstract class TTSActivity: MyAppCompatActivity(), TextToSpeech.OnInitListener,
runOnUiThread { handleActivityEvent(event) }
}

override fun notifyInputSelection(start: Long, end: Long, taskId: Int) {
// Log.e(TAG, "notifyInputSelection(): $start, $end, $taskId")

// TODO
}

override fun onActivityResult(requestCode: Int, resultCode: Int,
data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Expand Down Expand Up @@ -494,6 +500,6 @@ abstract class TTSActivity: MyAppCompatActivity(), TextToSpeech.OnInitListener,

override fun onDestroy() {
super.onDestroy()
myApplication.deleteProgressObserver(this)
myApplication.deleteTaskObserver(this)
}
}

0 comments on commit 52aeaf9

Please sign in to comment.