sezo-audio-engine 0.0.8 → 0.0.9

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 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.4
41
+ sezoAudioEngineVersion=android-engine-v0.1.9
42
42
  ```
43
43
 
44
44
  If you want to build from source instead, include the local engine module in
@@ -44,7 +44,7 @@ dependencies {
44
44
  if (androidEngineProject != null) {
45
45
  implementation androidEngineProject
46
46
  } else {
47
- def sezoAudioEngineVersion = project.findProperty("sezoAudioEngineVersion") ?: "android-engine-v0.1.4"
47
+ def sezoAudioEngineVersion = project.findProperty("sezoAudioEngineVersion") ?: "android-engine-v0.1.9"
48
48
  implementation "com.github.Sepzie:SezoAudioEngine:${sezoAudioEngineVersion}"
49
49
  }
50
50
  }
@@ -40,6 +40,17 @@ class ExpoAudioEngineModule : Module() {
40
40
  throw Exception("Failed to initialize audio engine")
41
41
  }
42
42
 
43
+ audioEngine?.setPlaybackStateListener { state, positionMs, durationMs ->
44
+ sendEvent(
45
+ "playbackStateChange",
46
+ mapOf(
47
+ "state" to state,
48
+ "positionMs" to positionMs,
49
+ "durationMs" to durationMs
50
+ )
51
+ )
52
+ }
53
+
43
54
  Log.d(TAG, "Audio engine initialized successfully")
44
55
  }
45
56
 
@@ -53,6 +64,7 @@ class ExpoAudioEngineModule : Module() {
53
64
  progressLogState.clear()
54
65
  engine.setExtractionProgressListener(null)
55
66
  engine.setExtractionCompletionListener(null)
67
+ engine.setPlaybackStateListener(null)
56
68
  engine.release()
57
69
  engine.destroy()
58
70
  }
@@ -5,6 +5,13 @@ import UIKit
5
5
  /// Swift wrapper around `AVAudioEngine` that backs the Expo module API.
6
6
  /// Keeps state on a single serial queue to avoid thread-safety issues.
7
7
  final class NativeAudioEngine {
8
+ private enum PlaybackState: String {
9
+ case stopped
10
+ case playing
11
+ case paused
12
+ case recording
13
+ }
14
+
8
15
  /// Serial queue to protect engine state and avoid races with audio callbacks.
9
16
  private let queue = DispatchQueue(label: "sezo.audioengine.state")
10
17
  /// Core iOS audio graph.
@@ -42,6 +49,8 @@ final class NativeAudioEngine {
42
49
  private var pauseCommandTarget: Any?
43
50
  private var toggleCommandTarget: Any?
44
51
  private var lastConfig = AudioEngineConfig(dictionary: [:])
52
+ var onPlaybackStateChange: ((String, Double, Double) -> Void)?
53
+ var onPlaybackComplete: ((Double, Double) -> Void)?
45
54
 
46
55
  /// Bookkeeping for a live recording session.
47
56
  private struct RecordingState {
@@ -720,6 +729,8 @@ final class NativeAudioEngine {
720
729
  stopAllPlayers()
721
730
  stopEngineIfRunning()
722
731
  updateNowPlayingInfoInternal()
732
+ emitPlaybackState(.stopped, positionMs: currentPositionMs)
733
+ onPlaybackComplete?(currentPositionMs, durationMs)
723
734
  }
724
735
 
725
736
  /// Computes playback position from host time.
@@ -1191,6 +1202,7 @@ final class NativeAudioEngine {
1191
1202
  schedulePlayback(at: currentPositionMs)
1192
1203
  isPlayingFlag = true
1193
1204
  updateNowPlayingInfoInternal()
1205
+ emitPlaybackState(.playing)
1194
1206
  }
1195
1207
 
1196
1208
  /// Internal pause logic (expects to run on the queue).
@@ -1201,10 +1213,12 @@ final class NativeAudioEngine {
1201
1213
  stopAllPlayers()
1202
1214
  stopEngineIfRunning()
1203
1215
  updateNowPlayingInfoInternal()
1216
+ emitPlaybackState(.paused, positionMs: currentPositionMs)
1204
1217
  }
1205
1218
 
1206
1219
  /// Internal stop logic (expects to run on the queue).
1207
1220
  private func stopInternal() {
1221
+ let shouldEmit = isPlayingFlag || currentPositionMs != 0.0
1208
1222
  isPlayingFlag = false
1209
1223
  currentPositionMs = 0.0
1210
1224
  playbackStartHostTime = nil
@@ -1213,6 +1227,9 @@ final class NativeAudioEngine {
1213
1227
  stopAllPlayers()
1214
1228
  stopEngineIfRunning()
1215
1229
  updateNowPlayingInfoInternal()
1230
+ if shouldEmit {
1231
+ emitPlaybackState(.stopped, positionMs: currentPositionMs)
1232
+ }
1216
1233
  }
1217
1234
 
1218
1235
  /// Internal seek logic (expects to run on the queue).
@@ -1251,6 +1268,11 @@ final class NativeAudioEngine {
1251
1268
  MPNowPlayingInfoCenter.default().nowPlayingInfo = info
1252
1269
  }
1253
1270
 
1271
+ private func emitPlaybackState(_ state: PlaybackState, positionMs: Double? = nil) {
1272
+ let resolvedPosition = positionMs ?? (isPlayingFlag ? currentPlaybackPositionMs() : currentPositionMs)
1273
+ onPlaybackStateChange?(state.rawValue, resolvedPosition, durationMs)
1274
+ }
1275
+
1254
1276
  /// Registers basic play/pause remote command handlers.
1255
1277
  private func configureRemoteCommandsIfNeeded() {
1256
1278
  guard playCommandTarget == nil else { return }
@@ -5,6 +5,35 @@ public class ExpoAudioEngineModule: Module {
5
5
 
6
6
  public func definition() -> ModuleDefinition {
7
7
  Name("ExpoAudioEngineModule")
8
+ OnCreate {
9
+ engine.onPlaybackStateChange = { [weak self] state, positionMs, durationMs in
10
+ DispatchQueue.main.async {
11
+ self?.sendEvent(
12
+ "playbackStateChange",
13
+ [
14
+ "state": state,
15
+ "positionMs": positionMs,
16
+ "durationMs": durationMs
17
+ ]
18
+ )
19
+ }
20
+ }
21
+ engine.onPlaybackComplete = { [weak self] positionMs, durationMs in
22
+ DispatchQueue.main.async {
23
+ self?.sendEvent(
24
+ "playbackComplete",
25
+ [
26
+ "positionMs": positionMs,
27
+ "durationMs": durationMs
28
+ ]
29
+ )
30
+ }
31
+ }
32
+ }
33
+ OnDestroy {
34
+ engine.onPlaybackStateChange = nil
35
+ engine.onPlaybackComplete = nil
36
+ }
8
37
 
9
38
  AsyncFunction("initialize") { (config: [String: Any]) in
10
39
  engine.initialize(config: config)