@capgo/capacitor-native-audio 8.4.4 → 8.4.5

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.
@@ -82,7 +82,7 @@ dependencies {
82
82
  implementation 'androidx.media3:media3-transformer:1.10.0'
83
83
  implementation 'androidx.media3:media3-ui:1.10.0'
84
84
  implementation 'androidx.media3:media3-database:1.10.0'
85
- implementation 'androidx.media3:media3-common:1.10.0'
85
+ implementation 'androidx.media3:media3-common:1.10.1'
86
86
  // Media notification support
87
87
  implementation 'androidx.media:media:1.7.1'
88
88
  implementation 'androidx.core:core:1.13.1'
@@ -729,8 +729,6 @@ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChan
729
729
  if (audioAssetList.containsKey(audioId)) {
730
730
  AudioAsset asset = audioAssetList.get(audioId);
731
731
  if (asset != null) {
732
- boolean wasPlaying = asset.isPlaying();
733
-
734
732
  JSObject data = getAudioAssetData(audioId);
735
733
  data.put("volumeBeforePause", asset.getVolume());
736
734
  setAudioAssetData(audioId, data);
@@ -741,9 +739,7 @@ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChan
741
739
  asset.pause();
742
740
  }
743
741
 
744
- if (wasPlaying) {
745
- resumeList.add(asset);
746
- }
742
+ resumeList.removeIf((candidate) -> candidate == asset);
747
743
 
748
744
  updateTrackedPlaybackState(audioId, PlaybackStateCompat.STATE_PAUSED);
749
745
 
@@ -792,7 +788,7 @@ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChan
792
788
 
793
789
  data.remove("volumeBeforePause");
794
790
  setAudioAssetData(audioId, data);
795
- resumeList.add(asset);
791
+ resumeList.removeIf((candidate) -> candidate == asset);
796
792
  updateTrackedPlaybackState(audioId, PlaybackStateCompat.STATE_PLAYING);
797
793
 
798
794
  // Update notification when resumed
@@ -0,0 +1,62 @@
1
+ package ee.forgr.audio;
2
+
3
+ import android.os.Handler;
4
+ import android.os.Looper;
5
+ import java.util.concurrent.CountDownLatch;
6
+ import java.util.concurrent.TimeUnit;
7
+
8
+ final class PlayerThread {
9
+
10
+ private PlayerThread() {}
11
+
12
+ static void run(PlayerRunnable runnable) throws Exception {
13
+ call(() -> {
14
+ runnable.run();
15
+ return null;
16
+ });
17
+ }
18
+
19
+ @SuppressWarnings("unchecked")
20
+ static <T> T call(PlayerCallable<T> callable) throws Exception {
21
+ if (Looper.myLooper() == Looper.getMainLooper()) {
22
+ return callable.call();
23
+ }
24
+
25
+ final CountDownLatch latch = new CountDownLatch(1);
26
+ final Object[] result = new Object[1];
27
+ final Exception[] error = new Exception[1];
28
+
29
+ new Handler(Looper.getMainLooper()).post(() -> {
30
+ try {
31
+ result[0] = callable.call();
32
+ } catch (Exception e) {
33
+ error[0] = e;
34
+ } finally {
35
+ latch.countDown();
36
+ }
37
+ });
38
+
39
+ try {
40
+ if (!latch.await(2, TimeUnit.SECONDS)) {
41
+ throw new Exception("Timed out waiting for ExoPlayer operation on main thread");
42
+ }
43
+ } catch (InterruptedException e) {
44
+ Thread.currentThread().interrupt();
45
+ throw new Exception("Interrupted waiting for ExoPlayer operation on main thread", e);
46
+ }
47
+
48
+ if (error[0] != null) {
49
+ throw error[0];
50
+ }
51
+
52
+ return (T) result[0];
53
+ }
54
+
55
+ interface PlayerRunnable {
56
+ void run() throws Exception;
57
+ }
58
+
59
+ interface PlayerCallable<T> {
60
+ T call() throws Exception;
61
+ }
62
+ }
@@ -233,44 +233,30 @@ public class RemoteAudioAsset extends AudioAsset {
233
233
 
234
234
  @Override
235
235
  public boolean pause() throws Exception {
236
- final boolean[] wasPlaying = { false };
237
- owner
238
- .getActivity()
239
- .runOnUiThread(
240
- new Runnable() {
241
- @Override
242
- public void run() {
243
- cancelFade();
244
- for (ExoPlayer player : players) {
245
- if (player != null && player.isPlaying()) {
246
- player.pause();
247
- stopCurrentTimeUpdates();
248
- wasPlaying[0] = true;
249
- }
250
- }
251
- }
236
+ return PlayerThread.call(() -> {
237
+ boolean wasPlaying = false;
238
+ cancelFade();
239
+ for (ExoPlayer player : players) {
240
+ if (player != null && player.isPlaying()) {
241
+ player.pause();
242
+ stopCurrentTimeUpdates();
243
+ wasPlaying = true;
252
244
  }
253
- );
254
- return wasPlaying[0];
245
+ }
246
+ return wasPlaying;
247
+ });
255
248
  }
256
249
 
257
250
  @Override
258
251
  public void resume() throws Exception {
259
- owner
260
- .getActivity()
261
- .runOnUiThread(
262
- new Runnable() {
263
- @Override
264
- public void run() {
265
- for (ExoPlayer player : players) {
266
- if (player != null && !player.isPlaying()) {
267
- player.play();
268
- }
269
- }
270
- startCurrentTimeUpdates();
271
- }
252
+ PlayerThread.run(() -> {
253
+ for (ExoPlayer player : players) {
254
+ if (player != null && !player.isPlaying()) {
255
+ player.play();
272
256
  }
273
- );
257
+ }
258
+ startCurrentTimeUpdates();
259
+ });
274
260
  }
275
261
 
276
262
  @Override
@@ -390,79 +376,75 @@ public class RemoteAudioAsset extends AudioAsset {
390
376
 
391
377
  @Override
392
378
  public float getVolume() throws Exception {
393
- if (players.isEmpty()) {
394
- throw new Exception("No ExoPlayer available");
395
- }
379
+ return PlayerThread.call(() -> {
380
+ if (players.isEmpty()) {
381
+ throw new Exception("No ExoPlayer available");
382
+ }
396
383
 
397
- final ExoPlayer player = players.get(playIndex);
398
- return player != null ? player.getVolume() : 0;
384
+ final ExoPlayer player = players.get(playIndex);
385
+ return player != null ? player.getVolume() : 0f;
386
+ });
399
387
  }
400
388
 
401
389
  @Override
402
390
  public boolean isPlaying() throws Exception {
403
- if (players.isEmpty() || !isPrepared) return false;
391
+ return PlayerThread.call(() -> {
392
+ if (players.isEmpty() || !isPrepared) return false;
404
393
 
405
- ExoPlayer player = players.get(playIndex);
406
- return player != null && player.isPlaying();
394
+ ExoPlayer player = players.get(playIndex);
395
+ return player != null && player.isPlaying();
396
+ });
407
397
  }
408
398
 
409
399
  @Override
410
400
  public double getDuration() {
411
- logger.debug("getDuration called, players empty: " + players.isEmpty() + ", isPrepared: " + isPrepared);
412
- if (!players.isEmpty() && isPrepared) {
413
- final double[] duration = { 0 };
414
- owner
415
- .getActivity()
416
- .runOnUiThread(
417
- new Runnable() {
418
- @Override
419
- public void run() {
420
- ExoPlayer player = players.get(playIndex);
421
- int state = player.getPlaybackState();
422
- logger.debug("Player state: " + state + " (READY=" + Player.STATE_READY + ")");
423
- if (state == Player.STATE_READY) {
424
- long rawDuration = player.getDuration();
425
- logger.debug("Raw duration: " + rawDuration + ", TIME_UNSET=" + androidx.media3.common.C.TIME_UNSET);
426
- if (rawDuration != androidx.media3.common.C.TIME_UNSET) {
427
- duration[0] = rawDuration / 1000.0;
428
- logger.debug("Final duration in seconds: " + duration[0]);
429
- } else {
430
- logger.debug("Duration is TIME_UNSET");
431
- }
432
- } else {
433
- logger.debug("Player not in READY state");
434
- }
401
+ try {
402
+ return PlayerThread.call(() -> {
403
+ logger.debug("getDuration called, players empty: " + players.isEmpty() + ", isPrepared: " + isPrepared);
404
+ if (!players.isEmpty() && isPrepared) {
405
+ ExoPlayer player = players.get(playIndex);
406
+ int state = player.getPlaybackState();
407
+ logger.debug("Player state: " + state + " (READY=" + Player.STATE_READY + ")");
408
+ if (state == Player.STATE_READY) {
409
+ long rawDuration = player.getDuration();
410
+ logger.debug("Raw duration: " + rawDuration + ", TIME_UNSET=" + androidx.media3.common.C.TIME_UNSET);
411
+ if (rawDuration != androidx.media3.common.C.TIME_UNSET) {
412
+ double duration = rawDuration / 1000.0;
413
+ logger.debug("Final duration in seconds: " + duration);
414
+ return duration;
435
415
  }
416
+ logger.debug("Duration is TIME_UNSET");
417
+ } else {
418
+ logger.debug("Player not in READY state");
436
419
  }
437
- );
438
- return duration[0];
420
+ }
421
+ logger.debug("No players or not prepared for duration");
422
+ return 0.0;
423
+ });
424
+ } catch (Exception e) {
425
+ logger.error("Error getting duration", e);
426
+ return 0;
439
427
  }
440
- logger.debug("No players or not prepared for duration");
441
- return 0;
442
428
  }
443
429
 
444
430
  @Override
445
431
  public double getCurrentPosition() {
446
- if (!players.isEmpty() && isPrepared) {
447
- final double[] position = { 0 };
448
- owner
449
- .getActivity()
450
- .runOnUiThread(
451
- new Runnable() {
452
- @Override
453
- public void run() {
454
- ExoPlayer player = players.get(playIndex);
455
- if (player.getPlaybackState() == Player.STATE_READY) {
456
- long rawPosition = player.getCurrentPosition();
457
- logger.debug("Raw position: " + rawPosition);
458
- position[0] = rawPosition / 1000.0;
459
- }
460
- }
432
+ try {
433
+ return PlayerThread.call(() -> {
434
+ if (!players.isEmpty() && isPrepared) {
435
+ ExoPlayer player = players.get(playIndex);
436
+ if (player.getPlaybackState() == Player.STATE_READY) {
437
+ long rawPosition = player.getCurrentPosition();
438
+ logger.debug("Raw position: " + rawPosition);
439
+ return rawPosition / 1000.0;
461
440
  }
462
- );
463
- return position[0];
441
+ }
442
+ return 0.0;
443
+ });
444
+ } catch (Exception e) {
445
+ logger.error("Error getting current position", e);
446
+ return 0;
464
447
  }
465
- return 0;
466
448
  }
467
449
 
468
450
  @Override
@@ -185,28 +185,25 @@ public class StreamAudioAsset extends AudioAsset {
185
185
 
186
186
  @Override
187
187
  public boolean pause() throws Exception {
188
- final boolean[] wasPlaying = { false };
189
- owner
190
- .getActivity()
191
- .runOnUiThread(() -> {
192
- cancelFade();
193
- if (player != null && player.isPlaying()) {
194
- player.setPlayWhenReady(false);
195
- stopCurrentTimeUpdates();
196
- wasPlaying[0] = true;
197
- }
198
- });
199
- return wasPlaying[0];
188
+ return PlayerThread.call(() -> {
189
+ cancelFade();
190
+ if (player != null && player.isPlaying()) {
191
+ player.setPlayWhenReady(false);
192
+ stopCurrentTimeUpdates();
193
+ return true;
194
+ }
195
+ return false;
196
+ });
200
197
  }
201
198
 
202
199
  @Override
203
200
  public void resume() throws Exception {
204
- owner
205
- .getActivity()
206
- .runOnUiThread(() -> {
201
+ PlayerThread.run(() -> {
202
+ if (player != null) {
207
203
  player.setPlayWhenReady(true);
208
204
  startCurrentTimeUpdates();
209
- });
205
+ }
206
+ });
210
207
  }
211
208
 
212
209
  @Override
@@ -323,50 +320,50 @@ public class StreamAudioAsset extends AudioAsset {
323
320
 
324
321
  @Override
325
322
  public float getVolume() throws Exception {
326
- if (player != null) {
327
- return player.getVolume();
328
- }
329
- return 0;
323
+ return PlayerThread.call(() -> {
324
+ if (player != null) {
325
+ return player.getVolume();
326
+ }
327
+ return 0f;
328
+ });
330
329
  }
331
330
 
332
331
  @Override
333
332
  public boolean isPlaying() throws Exception {
334
- return player != null && player.isPlaying();
333
+ return PlayerThread.call(() -> player != null && player.isPlaying());
335
334
  }
336
335
 
337
336
  @Override
338
337
  public double getDuration() {
339
- if (isPrepared) {
340
- final double[] duration = { 0 };
341
- owner
342
- .getActivity()
343
- .runOnUiThread(() -> {
344
- if (player.getPlaybackState() == Player.STATE_READY) {
345
- long rawDuration = player.getDuration();
346
- if (rawDuration != androidx.media3.common.C.TIME_UNSET) {
347
- duration[0] = rawDuration / 1000.0;
348
- }
338
+ try {
339
+ return PlayerThread.call(() -> {
340
+ if (isPrepared && player != null && player.getPlaybackState() == Player.STATE_READY) {
341
+ long rawDuration = player.getDuration();
342
+ if (rawDuration != androidx.media3.common.C.TIME_UNSET) {
343
+ return rawDuration / 1000.0;
349
344
  }
350
- });
351
- return duration[0];
345
+ }
346
+ return 0.0;
347
+ });
348
+ } catch (Exception e) {
349
+ logger.error("Error getting duration", e);
350
+ return 0;
352
351
  }
353
- return 0;
354
352
  }
355
353
 
356
354
  @Override
357
355
  public double getCurrentPosition() {
358
- if (isPrepared) {
359
- final double[] position = { 0 };
360
- owner
361
- .getActivity()
362
- .runOnUiThread(() -> {
363
- if (player.getPlaybackState() == Player.STATE_READY) {
364
- position[0] = player.getCurrentPosition() / 1000.0;
365
- }
366
- });
367
- return position[0];
356
+ try {
357
+ return PlayerThread.call(() -> {
358
+ if (isPrepared && player != null && player.getPlaybackState() == Player.STATE_READY) {
359
+ return player.getCurrentPosition() / 1000.0;
360
+ }
361
+ return 0.0;
362
+ });
363
+ } catch (Exception e) {
364
+ logger.error("Error getting current position", e);
365
+ return 0;
368
366
  }
369
- return 0;
370
367
  }
371
368
 
372
369
  @Override
@@ -18,7 +18,7 @@ private enum PlaybackStateValue: String {
18
18
  @objc(NativeAudio)
19
19
  // swiftlint:disable:next type_body_length
20
20
  public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate, CAPBridgedPlugin {
21
- private let pluginVersion: String = "8.4.4"
21
+ private let pluginVersion: String = "8.4.5"
22
22
  public let identifier = "NativeAudio"
23
23
  public let jsName = "NativeAudio"
24
24
  public let pluginMethods: [CAPPluginMethod] = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-native-audio",
3
- "version": "8.4.4",
3
+ "version": "8.4.5",
4
4
  "description": "A native plugin for native audio engine",
5
5
  "license": "MPL-2.0",
6
6
  "main": "dist/plugin.cjs.js",