sezo-audio-engine 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/android/build.gradle +1 -1
- package/android/src/main/java/expo/modules/audioengine/ExpoAudioEngineModule.kt +62 -35
- package/ios/AudioEngine/AudioEngineError.swift +32 -0
- package/ios/AudioEngine/AudioEngineFacade.swift +602 -0
- package/ios/AudioEngine/AudioSessionCoordinator.swift +192 -0
- package/ios/AudioEngine/AudioSessionManager.swift +7 -82
- package/ios/AudioEngine/BackgroundMediaController.swift +230 -0
- package/ios/AudioEngine/EngineEventEmitter.swift +17 -0
- package/ios/AudioEngine/EngineRuntime.swift +257 -0
- package/ios/AudioEngine/EngineState.swift +50 -0
- package/ios/AudioEngine/ExtractionController.swift +315 -0
- package/ios/AudioEngine/NativeAudioEngine.swift +2 -1365
- package/ios/AudioEngine/RecordingController.swift +414 -0
- package/ios/ExpoAudioEngineModule.swift +26 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ allprojects {
|
|
|
38
38
|
Optionally pin the engine version in `android/gradle.properties`:
|
|
39
39
|
|
|
40
40
|
```properties
|
|
41
|
-
sezoAudioEngineVersion=android-engine-v0.1.
|
|
41
|
+
sezoAudioEngineVersion=android-engine-v0.1.7
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
If you want to build from source instead, include the local engine module in
|
package/android/build.gradle
CHANGED
|
@@ -46,7 +46,7 @@ dependencies {
|
|
|
46
46
|
if (androidEngineProject != null) {
|
|
47
47
|
implementation androidEngineProject
|
|
48
48
|
} else {
|
|
49
|
-
def sezoAudioEngineVersion = project.findProperty("sezoAudioEngineVersion") ?: "android-engine-v0.1.
|
|
49
|
+
def sezoAudioEngineVersion = project.findProperty("sezoAudioEngineVersion") ?: "android-engine-v0.1.7"
|
|
50
50
|
implementation "com.github.Sepzie:SezoAudioEngine:${sezoAudioEngineVersion}"
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -5,6 +5,7 @@ import android.os.Bundle
|
|
|
5
5
|
import android.util.Log
|
|
6
6
|
import com.sezo.audioengine.AudioEngine
|
|
7
7
|
import expo.modules.kotlin.Promise
|
|
8
|
+
import expo.modules.kotlin.exception.CodedException
|
|
8
9
|
import expo.modules.kotlin.modules.Module
|
|
9
10
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
10
11
|
import java.util.Collections
|
|
@@ -60,6 +61,10 @@ class ExpoAudioEngineModule : Module() {
|
|
|
60
61
|
val operation: String
|
|
61
62
|
)
|
|
62
63
|
|
|
64
|
+
private fun requireEngine(): AudioEngine {
|
|
65
|
+
return audioEngine ?: throw CodedException("ENGINE_NOT_INITIALIZED", "Engine not initialized")
|
|
66
|
+
}
|
|
67
|
+
|
|
63
68
|
override fun definition() = ModuleDefinition {
|
|
64
69
|
Name("ExpoAudioEngineModule")
|
|
65
70
|
|
|
@@ -151,7 +156,7 @@ class ExpoAudioEngineModule : Module() {
|
|
|
151
156
|
val success = audioEngine?.initialize(sampleRate, maxTracks) ?: false
|
|
152
157
|
|
|
153
158
|
if (!success) {
|
|
154
|
-
throw
|
|
159
|
+
throw CodedException("ENGINE_INIT_FAILED", "Failed to initialize audio engine", null)
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
ensureAudioFocusManager()
|
|
@@ -194,18 +199,24 @@ class ExpoAudioEngineModule : Module() {
|
|
|
194
199
|
}
|
|
195
200
|
|
|
196
201
|
AsyncFunction("loadTracks") { tracks: List<Map<String, Any?>> ->
|
|
197
|
-
val engine =
|
|
202
|
+
val engine = requireEngine()
|
|
198
203
|
|
|
199
204
|
tracks.forEach { track ->
|
|
200
|
-
val id = track["id"] as? String
|
|
201
|
-
|
|
205
|
+
val id = track["id"] as? String
|
|
206
|
+
?: throw CodedException("TRACK_LOAD_FAILED", "Missing track id", null)
|
|
207
|
+
val uri = track["uri"] as? String
|
|
208
|
+
?: throw CodedException("TRACK_LOAD_FAILED", "Missing track uri", null)
|
|
202
209
|
val startTimeMs = (track["startTimeMs"] as? Number)?.toDouble() ?: 0.0
|
|
203
210
|
|
|
204
211
|
Log.d(TAG, "Loading track: id=$id, uri=$uri")
|
|
205
212
|
val filePath = convertUriToPath(uri)
|
|
206
213
|
|
|
207
214
|
if (!engine.loadTrack(id, filePath, startTimeMs)) {
|
|
208
|
-
throw
|
|
215
|
+
throw CodedException(
|
|
216
|
+
"TRACK_LOAD_FAILED",
|
|
217
|
+
"Failed to load track: $id from $filePath",
|
|
218
|
+
null
|
|
219
|
+
)
|
|
209
220
|
}
|
|
210
221
|
|
|
211
222
|
loadedTrackIds.add(id)
|
|
@@ -214,10 +225,10 @@ class ExpoAudioEngineModule : Module() {
|
|
|
214
225
|
}
|
|
215
226
|
|
|
216
227
|
AsyncFunction("unloadTrack") { trackId: String ->
|
|
217
|
-
val engine =
|
|
228
|
+
val engine = requireEngine()
|
|
218
229
|
|
|
219
230
|
if (!engine.unloadTrack(trackId)) {
|
|
220
|
-
throw
|
|
231
|
+
throw CodedException("TRACK_UNLOAD_FAILED", "Failed to unload track: $trackId", null)
|
|
221
232
|
}
|
|
222
233
|
|
|
223
234
|
loadedTrackIds.remove(trackId)
|
|
@@ -225,7 +236,7 @@ class ExpoAudioEngineModule : Module() {
|
|
|
225
236
|
}
|
|
226
237
|
|
|
227
238
|
AsyncFunction("unloadAllTracks") {
|
|
228
|
-
val engine =
|
|
239
|
+
val engine = requireEngine()
|
|
229
240
|
engine.unloadAllTracks()
|
|
230
241
|
loadedTrackIds.clear()
|
|
231
242
|
Log.d(TAG, "All tracks unloaded")
|
|
@@ -236,27 +247,27 @@ class ExpoAudioEngineModule : Module() {
|
|
|
236
247
|
}
|
|
237
248
|
|
|
238
249
|
AsyncFunction("play") {
|
|
239
|
-
val engine =
|
|
250
|
+
val engine = requireEngine()
|
|
240
251
|
if (!startPlaybackInternal(engine, fromSystem = false)) {
|
|
241
|
-
throw
|
|
252
|
+
throw CodedException("PLAYBACK_START_FAILED", "Failed to start playback", null)
|
|
242
253
|
}
|
|
243
254
|
Log.d(TAG, "Playback started")
|
|
244
255
|
}
|
|
245
256
|
|
|
246
257
|
Function("pause") {
|
|
247
|
-
val engine =
|
|
258
|
+
val engine = requireEngine()
|
|
248
259
|
pausePlaybackInternal(engine, fromSystem = false)
|
|
249
260
|
Log.d(TAG, "Playback paused")
|
|
250
261
|
}
|
|
251
262
|
|
|
252
263
|
Function("stop") {
|
|
253
|
-
val engine =
|
|
264
|
+
val engine = requireEngine()
|
|
254
265
|
stopPlaybackInternal(engine, fromSystem = false)
|
|
255
266
|
Log.d(TAG, "Playback stopped")
|
|
256
267
|
}
|
|
257
268
|
|
|
258
269
|
Function("seek") { positionMs: Double ->
|
|
259
|
-
val engine =
|
|
270
|
+
val engine = requireEngine()
|
|
260
271
|
engine.seek(positionMs)
|
|
261
272
|
Log.d(TAG, "Seeked to position: $positionMs ms")
|
|
262
273
|
}
|
|
@@ -274,27 +285,27 @@ class ExpoAudioEngineModule : Module() {
|
|
|
274
285
|
}
|
|
275
286
|
|
|
276
287
|
Function("setTrackVolume") { trackId: String, volume: Double ->
|
|
277
|
-
val engine =
|
|
288
|
+
val engine = requireEngine()
|
|
278
289
|
engine.setTrackVolume(trackId, volume.toFloat())
|
|
279
290
|
}
|
|
280
291
|
|
|
281
292
|
Function("setTrackMuted") { trackId: String, muted: Boolean ->
|
|
282
|
-
val engine =
|
|
293
|
+
val engine = requireEngine()
|
|
283
294
|
engine.setTrackMuted(trackId, muted)
|
|
284
295
|
}
|
|
285
296
|
|
|
286
297
|
Function("setTrackSolo") { trackId: String, solo: Boolean ->
|
|
287
|
-
val engine =
|
|
298
|
+
val engine = requireEngine()
|
|
288
299
|
engine.setTrackSolo(trackId, solo)
|
|
289
300
|
}
|
|
290
301
|
|
|
291
302
|
Function("setTrackPan") { trackId: String, pan: Double ->
|
|
292
|
-
val engine =
|
|
303
|
+
val engine = requireEngine()
|
|
293
304
|
engine.setTrackPan(trackId, pan.toFloat())
|
|
294
305
|
}
|
|
295
306
|
|
|
296
307
|
Function("setMasterVolume") { volume: Double ->
|
|
297
|
-
val engine =
|
|
308
|
+
val engine = requireEngine()
|
|
298
309
|
engine.setMasterVolume(volume.toFloat())
|
|
299
310
|
}
|
|
300
311
|
|
|
@@ -303,7 +314,7 @@ class ExpoAudioEngineModule : Module() {
|
|
|
303
314
|
}
|
|
304
315
|
|
|
305
316
|
Function("setPitch") { semitones: Double ->
|
|
306
|
-
val engine =
|
|
317
|
+
val engine = requireEngine()
|
|
307
318
|
engine.setPitch(semitones.toFloat())
|
|
308
319
|
}
|
|
309
320
|
|
|
@@ -312,7 +323,7 @@ class ExpoAudioEngineModule : Module() {
|
|
|
312
323
|
}
|
|
313
324
|
|
|
314
325
|
Function("setSpeed") { rate: Double ->
|
|
315
|
-
val engine =
|
|
326
|
+
val engine = requireEngine()
|
|
316
327
|
engine.setSpeed(rate.toFloat())
|
|
317
328
|
}
|
|
318
329
|
|
|
@@ -321,13 +332,13 @@ class ExpoAudioEngineModule : Module() {
|
|
|
321
332
|
}
|
|
322
333
|
|
|
323
334
|
Function("setTempoAndPitch") { tempo: Double, pitch: Double ->
|
|
324
|
-
val engine =
|
|
335
|
+
val engine = requireEngine()
|
|
325
336
|
engine.setSpeed(tempo.toFloat())
|
|
326
337
|
engine.setPitch(pitch.toFloat())
|
|
327
338
|
}
|
|
328
339
|
|
|
329
340
|
Function("setTrackPitch") { trackId: String, semitones: Double ->
|
|
330
|
-
val engine =
|
|
341
|
+
val engine = requireEngine()
|
|
331
342
|
engine.setTrackPitch(trackId, semitones.toFloat())
|
|
332
343
|
}
|
|
333
344
|
|
|
@@ -336,7 +347,7 @@ class ExpoAudioEngineModule : Module() {
|
|
|
336
347
|
}
|
|
337
348
|
|
|
338
349
|
Function("setTrackSpeed") { trackId: String, rate: Double ->
|
|
339
|
-
val engine =
|
|
350
|
+
val engine = requireEngine()
|
|
340
351
|
engine.setTrackSpeed(trackId, rate.toFloat())
|
|
341
352
|
}
|
|
342
353
|
|
|
@@ -345,7 +356,7 @@ class ExpoAudioEngineModule : Module() {
|
|
|
345
356
|
}
|
|
346
357
|
|
|
347
358
|
AsyncFunction("startRecording") { config: Map<String, Any?>? ->
|
|
348
|
-
val engine =
|
|
359
|
+
val engine = requireEngine()
|
|
349
360
|
|
|
350
361
|
val sampleRate = (config?.get("sampleRate") as? Number)?.toInt() ?: 44100
|
|
351
362
|
val channels = (config?.get("channels") as? Number)?.toInt() ?: 1
|
|
@@ -376,7 +387,7 @@ class ExpoAudioEngineModule : Module() {
|
|
|
376
387
|
)
|
|
377
388
|
|
|
378
389
|
if (!success) {
|
|
379
|
-
throw
|
|
390
|
+
throw CodedException("RECORDING_START_FAILED", "Failed to start recording", null)
|
|
380
391
|
}
|
|
381
392
|
|
|
382
393
|
activeRecordingFormat = format
|
|
@@ -384,13 +395,17 @@ class ExpoAudioEngineModule : Module() {
|
|
|
384
395
|
}
|
|
385
396
|
|
|
386
397
|
AsyncFunction("stopRecording") {
|
|
387
|
-
val engine =
|
|
398
|
+
val engine = requireEngine()
|
|
388
399
|
|
|
389
400
|
Log.d(TAG, "Stopping recording")
|
|
390
401
|
val result = engine.stopRecording()
|
|
391
402
|
|
|
392
403
|
if (!result.success) {
|
|
393
|
-
throw
|
|
404
|
+
throw CodedException(
|
|
405
|
+
"RECORDING_STOP_FAILED",
|
|
406
|
+
"Failed to stop recording: ${result.errorMessage}",
|
|
407
|
+
null
|
|
408
|
+
)
|
|
394
409
|
}
|
|
395
410
|
|
|
396
411
|
Log.d(TAG, "Recording stopped: ${result.fileSize} bytes, ${result.durationSamples} samples")
|
|
@@ -426,12 +441,12 @@ class ExpoAudioEngineModule : Module() {
|
|
|
426
441
|
}
|
|
427
442
|
|
|
428
443
|
Function("setRecordingVolume") { volume: Double ->
|
|
429
|
-
val engine =
|
|
444
|
+
val engine = requireEngine()
|
|
430
445
|
engine.setRecordingVolume(volume.toFloat())
|
|
431
446
|
}
|
|
432
447
|
|
|
433
448
|
AsyncFunction("extractTrack") { trackId: String, config: Map<String, Any?>?, promise: Promise ->
|
|
434
|
-
val engine =
|
|
449
|
+
val engine = requireEngine()
|
|
435
450
|
|
|
436
451
|
val format = (config?.get("format") as? String) ?: "wav"
|
|
437
452
|
val bitrate = (config?.get("bitrate") as? Number)?.toInt() ?: 128000
|
|
@@ -471,7 +486,7 @@ class ExpoAudioEngineModule : Module() {
|
|
|
471
486
|
}
|
|
472
487
|
|
|
473
488
|
AsyncFunction("extractAllTracks") { config: Map<String, Any?>?, promise: Promise ->
|
|
474
|
-
val engine =
|
|
489
|
+
val engine = requireEngine()
|
|
475
490
|
|
|
476
491
|
val format = (config?.get("format") as? String) ?: "wav"
|
|
477
492
|
val bitrate = (config?.get("bitrate") as? Number)?.toInt() ?: 128000
|
|
@@ -510,7 +525,7 @@ class ExpoAudioEngineModule : Module() {
|
|
|
510
525
|
}
|
|
511
526
|
|
|
512
527
|
Function("cancelExtraction") { jobId: Double? ->
|
|
513
|
-
val engine =
|
|
528
|
+
val engine = requireEngine()
|
|
514
529
|
val resolvedJobId = jobId?.toLong() ?: lastExtractionJobId
|
|
515
530
|
if (resolvedJobId == null) {
|
|
516
531
|
false
|
|
@@ -527,7 +542,7 @@ class ExpoAudioEngineModule : Module() {
|
|
|
527
542
|
Function("getTrackLevel") { _trackId: String -> 0.0 }
|
|
528
543
|
|
|
529
544
|
AsyncFunction("enableBackgroundPlayback") { metadata: Map<String, Any?> ->
|
|
530
|
-
val engine =
|
|
545
|
+
val engine = requireEngine()
|
|
531
546
|
backgroundPlaybackEnabled = true
|
|
532
547
|
mergeNowPlayingMetadata(metadata)
|
|
533
548
|
ensureAudioFocusManager()
|
|
@@ -940,15 +955,27 @@ class ExpoAudioEngineModule : Module() {
|
|
|
940
955
|
uri.startsWith("content://") -> {
|
|
941
956
|
// TODO: Handle content:// URIs with ContentResolver
|
|
942
957
|
// For now, throw an error - this will be implemented when needed
|
|
943
|
-
throw
|
|
958
|
+
throw CodedException(
|
|
959
|
+
"UNSUPPORTED_URI",
|
|
960
|
+
"content:// URIs not yet supported. Use file:// or absolute paths.",
|
|
961
|
+
null
|
|
962
|
+
)
|
|
944
963
|
}
|
|
945
964
|
uri.startsWith("asset://") -> {
|
|
946
965
|
// TODO: Handle asset:// URIs by copying to temp file
|
|
947
966
|
// For now, throw an error - this will be implemented when needed
|
|
948
|
-
throw
|
|
967
|
+
throw CodedException(
|
|
968
|
+
"UNSUPPORTED_URI",
|
|
969
|
+
"asset:// URIs not yet supported. Use file:// or absolute paths.",
|
|
970
|
+
null
|
|
971
|
+
)
|
|
949
972
|
}
|
|
950
973
|
else -> {
|
|
951
|
-
throw
|
|
974
|
+
throw CodedException(
|
|
975
|
+
"UNSUPPORTED_URI",
|
|
976
|
+
"Unsupported URI scheme: $uri. Use file:// or absolute paths.",
|
|
977
|
+
null
|
|
978
|
+
)
|
|
952
979
|
}
|
|
953
980
|
}
|
|
954
981
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
struct AudioEngineError: CodedError, CustomNSError {
|
|
4
|
+
let code: String
|
|
5
|
+
let description: String
|
|
6
|
+
let details: [String: Any]?
|
|
7
|
+
|
|
8
|
+
init(_ code: String, _ description: String, details: [String: Any]? = nil) {
|
|
9
|
+
self.code = code
|
|
10
|
+
self.description = description
|
|
11
|
+
self.details = details
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static var errorDomain: String {
|
|
15
|
+
return "ExpoAudioEngine"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
var errorCode: Int {
|
|
19
|
+
return 0
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
var errorUserInfo: [String: Any] {
|
|
23
|
+
var info: [String: Any] = [
|
|
24
|
+
NSLocalizedDescriptionKey: description,
|
|
25
|
+
"code": code
|
|
26
|
+
]
|
|
27
|
+
if let details = details {
|
|
28
|
+
info["details"] = details
|
|
29
|
+
}
|
|
30
|
+
return info
|
|
31
|
+
}
|
|
32
|
+
}
|