Skip to content
This repository was archived by the owner on May 6, 2022. It is now read-only.

Commit d3d55bb

Browse files
committed
feat(destroy): add destroy method
1 parent d89c265 commit d3d55bb

File tree

8 files changed

+307
-122
lines changed

8 files changed

+307
-122
lines changed

README.md

Lines changed: 154 additions & 72 deletions
Large diffs are not rendered by default.

android/src/main/java/com/reactnativespokestack/SpokestackModule.kt

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import javax.annotation.Nullable
1616
class SpokestackModule(private val reactContext: ReactApplicationContext): ReactContextBaseJavaModule(reactContext) {
1717
private val adapter = SpokestackAdapter { event: String, data: WritableMap -> sendEvent(event, data) }
1818
private var spokestack: Spokestack? = null
19+
private var audioPlayer: SpokestackTTSOutput? = null
20+
private var downloader: Downloader? = null
1921
private val promises = mutableMapOf<SpokestackPromise, Promise>()
2022
private var say: ((url: String) -> Unit)? = null
21-
private lateinit var audioPlayer: SpokestackTTSOutput
22-
private lateinit var downloader: Downloader
2323
private val filenameToProp = mapOf(
2424
"detect.tflite" to "wake-detect-path",
2525
"encode.tflite" to "wake-encode-path",
@@ -206,7 +206,7 @@ class SpokestackModule(private val reactContext: ReactApplicationContext): React
206206
// Wakeword needs all three config paths
207207
if (wakeDownloads.size == 3) {
208208
Log.d(name, "Building with wakeword.")
209-
downloader.downloadAll(wakeDownloads).forEach { (filename, loc) ->
209+
downloader!!.downloadAll(wakeDownloads).forEach { (filename, loc) ->
210210
builder.setProperty(filenameToProp[filename], loc)
211211
}
212212
} else {
@@ -253,7 +253,7 @@ class SpokestackModule(private val reactContext: ReactApplicationContext): React
253253
// Keyword at least needs filter, detect, and encode
254254
if (keywordDownloads.size >= 3) {
255255
Log.d(name, "Building with keyword.")
256-
downloader.downloadAll(keywordDownloads).forEach { (filename, loc) ->
256+
downloader!!.downloadAll(keywordDownloads).forEach { (filename, loc) ->
257257
builder.setProperty(filenameToProp[filename], loc)
258258
}
259259
}
@@ -283,7 +283,7 @@ class SpokestackModule(private val reactContext: ReactApplicationContext): React
283283

284284
if (nluDownloads.size == 3) {
285285
Log.d(name, "Building with NLU.")
286-
downloader.downloadAll(nluDownloads).forEach { (filename, loc) ->
286+
downloader!!.downloadAll(nluDownloads).forEach { (filename, loc) ->
287287
builder.setProperty(filenameToProp[filename], loc)
288288
}
289289
} else {
@@ -296,8 +296,8 @@ class SpokestackModule(private val reactContext: ReactApplicationContext): React
296296

297297
// Initialize the audio player for speaking
298298
audioPlayer = SpokestackTTSOutput(null)
299-
audioPlayer.setAndroidContext(reactContext.applicationContext)
300-
audioPlayer.addListener(adapter)
299+
audioPlayer!!.setAndroidContext(reactContext.applicationContext)
300+
audioPlayer!!.addListener(adapter)
301301

302302
spokestack = builder.build()
303303
promise.resolve(null)
@@ -381,12 +381,16 @@ class SpokestackModule(private val reactContext: ReactApplicationContext): React
381381

382382
@ReactMethod
383383
fun speak(input: String, format: Int, voice: String, promise: Promise) {
384+
if (!initialized()) {
385+
promise.reject(Exception("Call Spokestack.initialize() before calling Spokestack.speak()."))
386+
return
387+
}
384388
promises[SpokestackPromise.SPEAK] = promise
385389
say = { url ->
386390
Log.d(name,"Playing audio from URL: $url")
387391
val uri = Uri.parse(url)
388392
val response = AudioResponse(uri)
389-
audioPlayer.audioReceived(response)
393+
audioPlayer?.audioReceived(response)
390394

391395
// Resolve RN promise
392396
promise.resolve(null)
@@ -424,4 +428,18 @@ class SpokestackModule(private val reactContext: ReactApplicationContext): React
424428
fun isActivated(promise: Promise) {
425429
promise.resolve(activated())
426430
}
431+
432+
@ReactMethod
433+
fun destroy(promise: Promise) {
434+
spokestack?.close()
435+
spokestack?.removeListener(adapter)
436+
spokestack = null
437+
438+
audioPlayer?.close()
439+
audioPlayer = null
440+
441+
downloader = null
442+
443+
promise.resolve(null)
444+
}
427445
}

example/src/App.tsx

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -37,30 +37,23 @@ export default function App() {
3737
}
3838
// This example app demonstrates both ways
3939
// to pass model files, but we recommend using one or the other.
40-
Spokestack.initialize(clientId, clientSecret, {
41-
wakeword: {
42-
filter: require('../models/filter.tflite'),
43-
detect: require('../models/detect.tflite'),
44-
encode: require('../models/encode.tflite')
45-
},
46-
nlu: {
47-
model: 'https://s.spokestack.io/u/7fYxV/nlu.tflite',
48-
metadata: require('../models/metadata.sjson'),
49-
vocab: require('../models/vocab.txt')
50-
}
51-
})
52-
.then(Spokestack.start)
53-
.then(async () => {
54-
console.log(`Initialized: ${await Spokestack.isInitialized()}`)
55-
console.log(`Started: ${await Spokestack.isStarted()}`)
56-
console.log(`Activated: ${await Spokestack.isActivated()}`)
57-
})
58-
.catch((error) => {
59-
setError(error.message)
40+
try {
41+
await Spokestack.initialize(clientId, clientSecret, {
42+
wakeword: {
43+
filter: require('../models/filter.tflite'),
44+
detect: require('../models/detect.tflite'),
45+
encode: require('../models/encode.tflite')
46+
},
47+
nlu: {
48+
model: 'https://s.spokestack.io/u/7fYxV/nlu.tflite',
49+
metadata: require('../models/metadata.sjson'),
50+
vocab: require('../models/vocab.txt')
51+
}
6052
})
61-
}
62-
63-
React.useEffect(() => {
53+
} catch (e) {
54+
console.error(e)
55+
setError(e.message)
56+
}
6457
Spokestack.addEventListener('activate', () => setListening(true))
6558
Spokestack.addEventListener('deactivate', () => setListening(false))
6659
Spokestack.addEventListener(
@@ -80,12 +73,22 @@ export default function App() {
8073
Spokestack.addEventListener('play', ({ playing }: SpokestackPlayEvent) =>
8174
setPlaying(playing)
8275
)
76+
try {
77+
await Spokestack.start()
78+
console.log(`Initialized: ${await Spokestack.isInitialized()}`)
79+
console.log(`Started: ${await Spokestack.isStarted()}`)
80+
console.log(`Activated: ${await Spokestack.isActivated()}`)
81+
} catch (e) {
82+
console.error(e)
83+
setError(e.message)
84+
}
85+
}
8386

87+
React.useEffect(() => {
8488
init()
8589

8690
return () => {
87-
Spokestack.removeAllListeners()
88-
Spokestack.stop()
91+
Spokestack.destroy()
8992
}
9093
}, [])
9194

@@ -102,21 +105,32 @@ export default function App() {
102105
<Button
103106
title={listening ? 'Listening...' : 'Listen'}
104107
onPress={async () => {
105-
if (listening) {
106-
Spokestack.deactivate()
107-
} else {
108-
Spokestack.activate()
108+
try {
109+
if (listening) {
110+
await Spokestack.deactivate()
111+
} else {
112+
await Spokestack.activate()
113+
}
114+
} catch (e) {
115+
console.error(e)
116+
setError(e.message)
109117
}
110118
}}
111119
color="#2f5bea"
112120
/>
113121
<Button
114122
title={playing ? 'Playing...' : `Play transcript`}
115-
onPress={() => {
116-
Spokestack.speak(transcript || noTranscriptMessage)
123+
onPress={async () => {
124+
try {
125+
await Spokestack.speak(transcript || noTranscriptMessage)
126+
} catch (e) {
127+
console.error(e)
128+
setError(e.message)
129+
}
117130
}}
118131
/>
119132
</View>
133+
120134
{!!error && <Text style={styles.error}>{error}</Text>}
121135
<View style={styles.results}>
122136
<Text style={styles.transcript}>Transcript</Text>

ios/RNSpokestack.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,7 @@ @interface RCT_EXTERN_REMAP_MODULE(Spokestack, RNSpokestack, RCTEventEmitter)
4646
RCT_EXTERN_METHOD(isActivated:(RCTPromiseResolveBlock)resolve
4747
withRejecter:(RCTPromiseRejectBlock)reject)
4848

49+
RCT_EXTERN_METHOD(destroy:(RCTPromiseResolveBlock)resolve
50+
withRejecter:(RCTPromiseRejectBlock)reject)
51+
4952
@end

ios/RNSpokestack.swift

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -470,22 +470,30 @@ class RNSpokestack: RCTEventEmitter, SpokestackDelegate {
470470
// from the cache and the last one builds the pipeline
471471
resolvers[RNSpokestackPromise.initialize] = resolve
472472
rejecters[RNSpokestackPromise.initialize] = reject
473-
474-
if wakeDownloads.count == 3 {
475-
numRequests += wakeDownloads.count
473+
let doWakeword = wakeDownloads.count == 3
474+
let doKeyword = keywordDownloads.count >= 3
475+
let doNLU = nluDownloads.count == 3
476+
477+
// Set all request counts up-front
478+
// in case some downloads complete synchronously
479+
// from cache. This avoids building the pipeline
480+
// before all downloads finish.
481+
numRequests = (doWakeword ? wakeDownloads.count : 0) +
482+
(doKeyword ? keywordDownloads.count : 0) +
483+
(doNLU ? nluDownloads.count : 0)
484+
485+
if doWakeword {
476486
wakeDownloads.forEach { (url, complete) in
477487
d.downloadModel(url, complete)
478488
}
479489
}
480-
if keywordDownloads.count >= 3 {
481-
numRequests += keywordDownloads.count
490+
if doKeyword {
482491
keywordDownloads.forEach { (url, complete) in
483492
d.downloadModel(url, complete)
484493
}
485494
}
486-
if nluDownloads.count == 3 {
495+
if doNLU {
487496
makeClassifer = true
488-
numRequests += nluDownloads.count
489497
nluDownloads.forEach { (url, complete) in
490498
d.downloadModel(url, complete)
491499
}
@@ -634,4 +642,23 @@ class RNSpokestack: RCTEventEmitter, SpokestackDelegate {
634642
resolve(false)
635643
}
636644
}
645+
646+
/// Destroys the speech pipeline and frees up all resources
647+
@objc(destroy:withRejecter:)
648+
func destroy(resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
649+
if let pipeline = speechPipeline {
650+
pipeline.stop()
651+
speechPipeline = nil
652+
653+
synthesizer?.stopSpeaking()
654+
synthesizer = nil
655+
656+
classifier = nil
657+
658+
downloader = nil
659+
660+
speechContext = nil
661+
}
662+
resolve(nil)
663+
}
637664
}

0 commit comments

Comments
 (0)