app/src/main/kotlin/com/honjimaku/subrep/Feed.kt (4669 bytes)
1 package com.honjimaku.subrep 2 3 import android.os.Handler 4 import android.os.Looper 5 import android.os.SystemClock 6 import java.util.concurrent.CopyOnWriteArraySet 7 8 /** 9 * What the screen shows: the state of the service, the model download, and the last captions. 10 * The service writes it from its threads; the screen reads it on the main thread after each 11 * change. 12 */ 13 object Feed { 14 15 fun interface Listener { 16 fun onChange() 17 } 18 19 private val main = Handler(Looper.getMainLooper()) 20 private val listeners = CopyOnWriteArraySet<Listener>() 21 private val lock = Any() 22 private val finals = ArrayDeque<String>() 23 private var lastLevelAt = 0L 24 25 @Volatile var running = false 26 private set 27 28 /** [Engine.STATE_LOADING], [Engine.STATE_READY], or an error with its reason. */ 29 @Volatile var engine = "" 30 private set 31 32 /** The time Whisper took for the last piece, divided by the length of the piece. */ 33 @Volatile var speed = 0f 34 private set 35 @Volatile var late = 0 36 private set 37 @Volatile var relayUp = false 38 private set 39 @Volatile var relayOn = false 40 private set 41 @Volatile var overlayAnswer = "" 42 private set 43 @Volatile var error = "" 44 private set 45 46 /** The loudest sample of the last frame, 0 to 32768. Shows that sound comes in at all. */ 47 @Volatile var peak = 0 48 private set 49 @Volatile var partial = "" 50 private set 51 52 /** The model that downloads now, and its progress: bytes so far, total (-1 unknown). */ 53 @Volatile var downloading: Model? = null 54 private set 55 @Volatile var downloadDone = 0L 56 private set 57 @Volatile var downloadTotal = -1L 58 private set 59 @Volatile var downloadError = "" 60 private set 61 62 /** The seconds of cloud captions that the account has, -1 while unknown. */ 63 @Volatile var cloudSecondsLeft = -1L 64 private set 65 66 fun add(listener: Listener) = listeners.add(listener) 67 68 fun remove(listener: Listener) = listeners.remove(listener) 69 70 fun start(relayOn: Boolean) { 71 synchronized(lock) { finals.clear() } 72 partial = "" 73 error = "" 74 engine = "" 75 speed = 0f 76 late = 0 77 overlayAnswer = "" 78 peak = 0 79 relayUp = false 80 this.relayOn = relayOn 81 running = true 82 changed() 83 } 84 85 fun stop() { 86 running = false 87 relayUp = false 88 peak = 0 89 changed() 90 } 91 92 fun fail(reason: String) { 93 error = reason 94 changed() 95 } 96 97 fun engine(state: String) { 98 engine = state 99 changed() 100 } 101 102 fun progress(speed: Float, late: Int) { 103 this.speed = speed 104 this.late = late 105 } 106 107 fun relay(up: Boolean) { 108 relayUp = up 109 changed() 110 } 111 112 fun overlay(answer: String) { 113 if (answer == overlayAnswer) return 114 overlayAnswer = answer 115 changed() 116 } 117 118 /** The level meter of the screen, 0 to 10. */ 119 fun bars(): Int = (peak * 10 / 32768).coerceIn(0, 10) 120 121 fun level(peak: Int) { 122 val before = bars() 123 this.peak = peak 124 // Frames come ten times a second. The screen redraws only when the meter changes, and 125 // not more than twice a second: an e-ink screen cannot draw more. 126 val now = SystemClock.elapsedRealtime() 127 if (bars() == before || now - lastLevelAt < 500) return 128 lastLevelAt = now 129 changed() 130 } 131 132 fun caption(caption: Caption) { 133 synchronized(lock) { 134 when (caption.type) { 135 "final" -> { 136 finals.addLast(caption.text) 137 while (finals.size > LINES) finals.removeFirst() 138 partial = "" 139 } 140 "partial" -> partial = caption.text 141 "clear" -> { 142 finals.clear() 143 partial = "" 144 } 145 } 146 } 147 changed() 148 } 149 150 fun download(model: Model?, done: Long, total: Long, error: String = "") { 151 downloading = model 152 downloadDone = done 153 downloadTotal = total 154 downloadError = error 155 changed() 156 } 157 158 fun cloudLeft(seconds: Long) { 159 cloudSecondsLeft = seconds 160 changed() 161 } 162 163 /** The last lines, and the partial line after them. */ 164 fun text(): String { 165 val lines = synchronized(lock) { finals.toList() } 166 return (lines + listOf(partial).filter { it.isNotEmpty() }.map { "$it …" }).joinToString("\n") 167 } 168 169 private fun changed() = main.post { listeners.forEach { it.onChange() } } 170 171 private const val LINES = 8 172 }