@capgo/native-audio 8.2.11 → 8.2.13

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.
@@ -4,29 +4,55 @@ import android.content.res.AssetFileDescriptor;
4
4
  import android.os.Handler;
5
5
  import android.os.Looper;
6
6
  import android.util.Log;
7
+ import androidx.media3.common.util.UnstableApi;
7
8
  import java.util.ArrayList;
9
+ import java.util.Map;
10
+ import java.util.concurrent.ConcurrentHashMap;
11
+ import java.util.concurrent.Executors;
12
+ import java.util.concurrent.ScheduledExecutorService;
13
+ import java.util.concurrent.ScheduledFuture;
14
+ import java.util.concurrent.TimeUnit;
8
15
 
9
- public class AudioAsset {
16
+ @UnstableApi
17
+ public class AudioAsset implements AutoCloseable {
10
18
 
11
- private final String TAG = "AudioAsset";
19
+ public static final double DEFAULT_FADE_DURATION_MS = 1000.0;
20
+
21
+ private static final String TAG = "AudioAsset";
22
+ protected static final Logger logger = new Logger(TAG);
12
23
 
13
24
  private final ArrayList<AudioDispatcher> audioList;
14
- private int playIndex = 0;
25
+ protected int playIndex = 0;
15
26
  protected final NativeAudio owner;
16
27
  protected AudioCompletionListener completionListener;
17
28
  protected String assetId;
18
- private Handler currentTimeHandler;
19
- private Runnable currentTimeRunnable;
20
- private static final float FADE_STEP = 0.05f;
21
- private static final int FADE_DELAY_MS = 80;
22
- private float initialVolume;
29
+ protected Handler currentTimeHandler;
30
+ protected Runnable currentTimeRunnable;
31
+ protected static final int FADE_DELAY_MS = 80;
32
+
33
+ protected ScheduledExecutorService fadeExecutor;
34
+ protected ScheduledFuture<?> fadeTask;
35
+
36
+ protected Map<String, Boolean> dispatchedCompleteMap = new ConcurrentHashMap<>();
37
+
38
+ protected enum FadeState {
39
+ NONE,
40
+ FADE_IN,
41
+ FADE_OUT,
42
+ FADE_TO
43
+ }
44
+
45
+ protected FadeState fadeState = FadeState.NONE;
46
+
47
+ protected final float zeroVolume = 0.001f;
48
+ protected final float maxVolume = 1.0f;
23
49
 
24
50
  AudioAsset(NativeAudio owner, String assetId, AssetFileDescriptor assetFileDescriptor, int audioChannelNum, float volume)
25
51
  throws Exception {
26
52
  audioList = new ArrayList<>();
27
53
  this.owner = owner;
28
54
  this.assetId = assetId;
29
- this.initialVolume = volume;
55
+ this.fadeExecutor = Executors.newSingleThreadScheduledExecutor();
30
56
 
31
57
  if (audioChannelNum < 0) {
32
58
  audioChannelNum = 1;
@@ -40,27 +66,38 @@ public class AudioAsset {
40
66
  }
41
67
 
42
68
  public void dispatchComplete() {
69
+ if (dispatchedCompleteMap.getOrDefault(this.assetId, false)) {
70
+ return;
71
+ }
43
72
  this.owner.dispatchComplete(this.assetId);
73
+ dispatchedCompleteMap.put(this.assetId, true);
44
74
  }
45
75
 
46
- public void play(Double time) throws Exception {
76
+ public void play(double time, float volume) throws Exception {
77
+ if (audioList.isEmpty() || playIndex < 0 || playIndex >= audioList.size()) {
78
+ throw new Exception("AudioDispatcher is null or playIndex out of bounds");
79
+ }
47
80
  AudioDispatcher audio = audioList.get(playIndex);
48
81
  if (audio != null) {
82
+ cancelFade();
49
83
  audio.play(time);
84
+ audio.setVolume(volume);
50
85
  playIndex++;
51
86
  playIndex = playIndex % audioList.size();
52
- Log.d(TAG, "Starting timer from play"); // Debug log
87
+ logger.debug("Starting timer from play"); // Debug log
53
88
  startCurrentTimeUpdates(); // Make sure this is called
54
89
  } else {
55
90
  throw new Exception("AudioDispatcher is null");
56
91
  }
57
92
  }
58
93
 
59
- public double getDuration() {
60
- if (audioList.size() != 1) return 0;
94
+ public void play(double time) throws Exception {
95
+ play(time, 1.0f);
96
+ }
61
97
 
98
+ public double getDuration() {
99
+ if (audioList.size() != 1 || playIndex < 0 || playIndex >= audioList.size()) return 0;
62
100
  AudioDispatcher audio = audioList.get(playIndex);
63
-
64
101
  if (audio != null) {
65
102
  return audio.getDuration();
66
103
  }
@@ -68,20 +105,16 @@ public class AudioAsset {
68
105
  }
69
106
 
70
107
  public void setCurrentPosition(double time) {
71
- if (audioList.size() != 1) return;
72
-
108
+ if (audioList.size() != 1 || playIndex < 0 || playIndex >= audioList.size()) return;
73
109
  AudioDispatcher audio = audioList.get(playIndex);
74
-
75
110
  if (audio != null) {
76
111
  audio.setCurrentPosition(time);
77
112
  }
78
113
  }
79
114
 
80
115
  public double getCurrentPosition() {
81
- if (audioList.size() != 1) return 0;
82
-
116
+ if (audioList.size() != 1 || playIndex < 0 || playIndex >= audioList.size()) return 0;
83
117
  AudioDispatcher audio = audioList.get(playIndex);
84
-
85
118
  if (audio != null) {
86
119
  return audio.getCurrentPosition();
87
120
  }
@@ -94,6 +127,10 @@ public class AudioAsset {
94
127
 
95
128
  for (int x = 0; x < audioList.size(); x++) {
96
129
  AudioDispatcher audio = audioList.get(x);
130
+ if (audio == null) {
131
+ continue;
132
+ }
133
+ cancelFade();
97
134
  wasPlaying |= audio.pause();
98
135
  }
99
136
 
@@ -105,7 +142,7 @@ public class AudioAsset {
105
142
  AudioDispatcher audio = audioList.get(0);
106
143
  if (audio != null) {
107
144
  audio.resume();
108
- Log.d(TAG, "Starting timer from resume"); // Debug log
145
+ logger.debug("Starting timer from resume"); // Debug log
109
146
  startCurrentTimeUpdates(); // Make sure this is called
110
147
  } else {
111
148
  throw new Exception("AudioDispatcher is null");
@@ -115,10 +152,12 @@ public class AudioAsset {
115
152
 
116
153
  public void stop() throws Exception {
117
154
  stopCurrentTimeUpdates(); // Stop updates when stopping
155
+ dispatchComplete();
118
156
  for (int x = 0; x < audioList.size(); x++) {
119
157
  AudioDispatcher audio = audioList.get(x);
120
158
 
121
159
  if (audio != null) {
160
+ cancelFade();
122
161
  audio.stop();
123
162
  } else {
124
163
  throw new Exception("AudioDispatcher is null");
@@ -152,20 +191,40 @@ public class AudioAsset {
152
191
  }
153
192
 
154
193
  audioList.clear();
194
+ stopCurrentTimeUpdates();
195
+ close();
155
196
  }
156
197
 
157
- public void setVolume(float volume) throws Exception {
198
+ public void setVolume(float volume, double duration) throws Exception {
158
199
  for (int x = 0; x < audioList.size(); x++) {
159
200
  AudioDispatcher audio = audioList.get(x);
160
201
 
202
+ cancelFade();
161
203
  if (audio != null) {
162
- audio.setVolume(volume);
204
+ if (isPlaying() && duration > 0) {
205
+ fadeTo(audio, duration, volume);
206
+ } else {
207
+ audio.setVolume(volume);
208
+ }
163
209
  } else {
164
210
  throw new Exception("AudioDispatcher is null");
165
211
  }
166
212
  }
167
213
  }
168
214
 
215
+ public void setVolume(float volume) throws Exception {
216
+ setVolume(volume, 0);
217
+ }
218
+
219
+ public float getVolume() throws Exception {
220
+ if (audioList.size() != 1 || playIndex < 0 || playIndex >= audioList.size()) return 0;
221
+ AudioDispatcher audio = audioList.get(playIndex);
222
+ if (audio != null) {
223
+ return audio.getVolume();
224
+ }
225
+ throw new Exception("AudioDispatcher is null");
226
+ }
227
+
169
228
  public void setRate(float rate) throws Exception {
170
229
  for (int x = 0; x < audioList.size(); x++) {
171
230
  AudioDispatcher audio = audioList.get(x);
@@ -176,9 +235,10 @@ public class AudioAsset {
176
235
  }
177
236
 
178
237
  public boolean isPlaying() throws Exception {
179
- if (audioList.size() != 1) return false;
180
-
181
- return audioList.get(playIndex).isPlaying();
238
+ for (AudioDispatcher ad : audioList) {
239
+ if (ad != null && ad.isPlaying()) return true;
240
+ }
241
+ return false;
182
242
  }
183
243
 
184
244
  public void setCompletionListener(AudioCompletionListener listener) {
@@ -196,13 +256,14 @@ public class AudioAsset {
196
256
  }
197
257
 
198
258
  public void setCurrentTime(double time) throws Exception {
259
+ if (owner == null || owner.getActivity() == null) return;
199
260
  owner
200
261
  .getActivity()
201
262
  .runOnUiThread(
202
263
  new Runnable() {
203
264
  @Override
204
265
  public void run() {
205
- if (audioList.size() != 1) {
266
+ if (audioList.size() != 1 || playIndex < 0 || playIndex >= audioList.size()) {
206
267
  return;
207
268
  }
208
269
  AudioDispatcher audio = audioList.get(playIndex);
@@ -215,12 +276,11 @@ public class AudioAsset {
215
276
  }
216
277
 
217
278
  protected void startCurrentTimeUpdates() {
218
- Log.d(TAG, "Starting timer updates in AudioAsset");
279
+ logger.debug("Starting timer updates");
219
280
  if (currentTimeHandler == null) {
220
281
  currentTimeHandler = new Handler(Looper.getMainLooper());
221
282
  }
222
-
223
- // Add small delay to let audio start playing
283
+ dispatchedCompleteMap.put(assetId, false);
224
284
  currentTimeHandler.postDelayed(
225
285
  new Runnable() {
226
286
  @Override
@@ -229,103 +289,321 @@ public class AudioAsset {
229
289
  }
230
290
  },
231
291
  100
232
- ); // 100ms delay
292
+ );
233
293
  }
234
294
 
235
295
  private void startTimeUpdateLoop() {
236
296
  currentTimeRunnable = new Runnable() {
237
297
  @Override
238
298
  public void run() {
299
+ AudioDispatcher audio = null;
300
+ try {
301
+ if (audioList.isEmpty() || playIndex < 0 || playIndex >= audioList.size()) {
302
+ logger.verbose("Audio dispatcher does not exist at index " + playIndex);
303
+ return;
304
+ }
305
+ audio = audioList.get(playIndex);
306
+ } catch (Exception e) {
307
+ logger.verbose("Audio dispatcher does not exist at index " + playIndex);
308
+ }
309
+ if (audio == null) {
310
+ logger.debug("Audio dispatcher does not exist - aborting timer update");
311
+ return;
312
+ }
239
313
  try {
240
- AudioDispatcher audio = audioList.get(playIndex);
241
314
  if (audio != null && audio.isPlaying()) {
242
315
  double currentTime = getCurrentPosition();
243
- Log.d(TAG, "Timer update: currentTime = " + currentTime);
244
- owner.notifyCurrentTime(assetId, currentTime);
316
+ logger.verbose("Play timer update: currentTime = " + currentTime);
317
+ if (owner != null) owner.notifyCurrentTime(assetId, currentTime);
245
318
  currentTimeHandler.postDelayed(this, 100);
246
319
  } else {
247
- Log.d(TAG, "Stopping timer - not playing");
320
+ logger.debug("Audio is not not playing");
248
321
  stopCurrentTimeUpdates();
322
+ if (audio.isPaused()) {
323
+ logger.verbose("Audio is paused");
324
+ } else {
325
+ logger.verbose("Audio is not paused - dispatching complete");
326
+ dispatchComplete();
327
+ }
249
328
  }
250
329
  } catch (Exception e) {
251
- Log.e(TAG, "Error getting current time", e);
330
+ logger.error("Error getting current time", e);
252
331
  stopCurrentTimeUpdates();
253
332
  }
254
333
  }
255
334
  };
256
- currentTimeHandler.post(currentTimeRunnable);
335
+ try {
336
+ if (currentTimeHandler == null) {
337
+ currentTimeHandler = new Handler(Looper.getMainLooper());
338
+ }
339
+ currentTimeHandler.post(currentTimeRunnable);
340
+ } catch (Exception e) {
341
+ logger.error("Error starting current time updates", e);
342
+ }
257
343
  }
258
344
 
259
345
  void stopCurrentTimeUpdates() {
260
- Log.d(TAG, "Stopping timer updates in AudioAsset");
346
+ logger.verbose("Stopping play timer updates");
261
347
  if (currentTimeHandler != null && currentTimeRunnable != null) {
262
348
  currentTimeHandler.removeCallbacks(currentTimeRunnable);
349
+ currentTimeHandler = null;
350
+ currentTimeRunnable = null;
263
351
  }
264
352
  }
265
353
 
266
- public void playWithFade(Double time) throws Exception {
354
+ public void playWithFadeIn(double time, float volume, double fadeInDurationMs) throws Exception {
267
355
  AudioDispatcher audio = audioList.get(playIndex);
268
356
  if (audio != null) {
269
357
  audio.setVolume(0);
270
358
  audio.play(time);
271
- fadeIn(audio);
359
+ fadeIn(audio, fadeInDurationMs, volume);
272
360
  startCurrentTimeUpdates();
273
361
  }
274
362
  }
275
363
 
276
- private void fadeIn(final AudioDispatcher audio) {
277
- final Handler handler = new Handler(Looper.getMainLooper());
278
- final Runnable fadeRunnable = new Runnable() {
279
- float currentVolume = 0;
364
+ public void playWithFade(double time) throws Exception {
365
+ playWithFadeIn(time, 1.0f, DEFAULT_FADE_DURATION_MS);
366
+ }
280
367
 
281
- @Override
282
- public void run() {
283
- if (currentVolume < initialVolume) {
284
- currentVolume += FADE_STEP;
368
+ private void fadeIn(final AudioDispatcher audio, double fadeInDurationMs, float targetVolume) {
369
+ cancelFade();
370
+ fadeState = FadeState.FADE_IN;
371
+
372
+ final int steps = Math.max(1, (int) (fadeInDurationMs / FADE_DELAY_MS));
373
+ final float fadeStep = targetVolume / steps;
374
+
375
+ Log.d(
376
+ TAG,
377
+ "Beginning fade in at time " +
378
+ getCurrentPosition() +
379
+ " over " +
380
+ (fadeInDurationMs / 1000.0) +
381
+ "s to target volume " +
382
+ targetVolume +
383
+ " in " +
384
+ steps +
385
+ " steps (step duration: " +
386
+ (FADE_DELAY_MS / 1000.0) +
387
+ "s"
388
+ );
389
+
390
+ fadeTask = fadeExecutor.scheduleWithFixedDelay(
391
+ new Runnable() {
392
+ float currentVolume = 0;
393
+
394
+ @Override
395
+ public void run() {
396
+ if (fadeState != FadeState.FADE_IN || currentVolume >= targetVolume) {
397
+ fadeState = FadeState.NONE;
398
+ cancelFade();
399
+ logger.debug("Fade in complete at time " + getCurrentPosition());
400
+ return;
401
+ }
402
+ final float previousCurrentVolume = currentVolume;
403
+ currentVolume += fadeStep;
285
404
  try {
286
- audio.setVolume(currentVolume);
287
- handler.postDelayed(this, FADE_DELAY_MS);
405
+ final float resolvedTargetVolume = Math.min(Math.max(currentVolume, 0), targetVolume);
406
+ Log.v(
407
+ TAG,
408
+ "Fade in step: from " + previousCurrentVolume + " to " + currentVolume + " to target " + resolvedTargetVolume
409
+ );
410
+ if (audio != null) audio.setVolume(resolvedTargetVolume);
288
411
  } catch (Exception e) {
289
- Log.e(TAG, "Error during fade in", e);
412
+ logger.error("Error during fade in", e);
413
+ cancelFade();
290
414
  }
291
415
  }
292
- }
293
- };
294
- handler.post(fadeRunnable);
416
+ },
417
+ 0,
418
+ FADE_DELAY_MS,
419
+ TimeUnit.MILLISECONDS
420
+ );
295
421
  }
296
422
 
297
- public void stopWithFade() throws Exception {
423
+ public void stopWithFade(double fadeOutDurationMs, boolean toPause) throws Exception {
298
424
  AudioDispatcher audio = audioList.get(playIndex);
299
425
  if (audio != null && audio.isPlaying()) {
300
- fadeOut(audio);
426
+ cancelFade();
427
+ fadeOut(audio, fadeOutDurationMs, toPause);
301
428
  }
302
429
  }
303
430
 
304
- private void fadeOut(final AudioDispatcher audio) {
305
- final Handler handler = new Handler(Looper.getMainLooper());
306
- final Runnable fadeRunnable = new Runnable() {
307
- float currentVolume = initialVolume;
431
+ public void stopWithFade() throws Exception {
432
+ stopWithFade(DEFAULT_FADE_DURATION_MS, false);
433
+ }
434
+
435
+ private void fadeOut(final AudioDispatcher audio, double fadeOutDurationMs, boolean toPause) {
436
+ cancelFade();
437
+ fadeState = FadeState.FADE_OUT;
438
+
439
+ if (audio == null) return;
440
+
441
+ final int steps = Math.max(1, (int) (fadeOutDurationMs / FADE_DELAY_MS));
442
+ final float initialVolume = audio.getVolume();
443
+ final float fadeStep = initialVolume / steps;
444
+
445
+ Log.d(
446
+ TAG,
447
+ "Beginning fade out from volume " +
448
+ initialVolume +
449
+ " at time " +
450
+ getCurrentPosition() +
451
+ " over " +
452
+ (fadeOutDurationMs / 1000.0) +
453
+ "s in " +
454
+ steps +
455
+ " steps (step duration: " +
456
+ (FADE_DELAY_MS / 1000.0) +
457
+ "s)"
458
+ );
459
+
460
+ fadeTask = fadeExecutor.scheduleWithFixedDelay(
461
+ new Runnable() {
462
+ float currentVolume = initialVolume;
308
463
 
309
- @Override
310
- public void run() {
311
- if (currentVolume > FADE_STEP) {
312
- currentVolume -= FADE_STEP;
464
+ @Override
465
+ public void run() {
313
466
  try {
314
- audio.setVolume(currentVolume);
315
- handler.postDelayed(this, FADE_DELAY_MS);
467
+ if (fadeState != FadeState.FADE_OUT || currentVolume <= 0) {
468
+ fadeState = FadeState.NONE;
469
+ if (toPause) {
470
+ logger.verbose("Faded out to pause audio at time " + getCurrentPosition());
471
+ audio.pause();
472
+ } else {
473
+ logger.verbose("Faded out to stop at time " + getCurrentPosition());
474
+ stop();
475
+ }
476
+ cancelFade();
477
+ logger.debug("Fade out complete at time " + getCurrentPosition());
478
+ return;
479
+ }
480
+ final float previousCurrentVolume = currentVolume;
481
+ currentVolume -= fadeStep;
482
+
483
+ final float thisTargetVolume = Math.max(currentVolume, 0);
484
+ Log.v(
485
+ TAG,
486
+ "Fade out step: from " + previousCurrentVolume + " to " + currentVolume + " to target " + thisTargetVolume
487
+ );
488
+ if (audio != null) audio.setVolume(thisTargetVolume);
316
489
  } catch (Exception e) {
317
- Log.e(TAG, "Error during fade out", e);
490
+ logger.error("Error during fade out", e);
491
+ cancelFade();
318
492
  }
319
- } else {
493
+ }
494
+ },
495
+ 0,
496
+ FADE_DELAY_MS,
497
+ TimeUnit.MILLISECONDS
498
+ );
499
+ }
500
+
501
+ protected void fadeTo(final AudioDispatcher audio, double fadeDurationMs, float targetVolume) {
502
+ cancelFade();
503
+ fadeState = FadeState.FADE_TO;
504
+
505
+ if (audio == null) return;
506
+
507
+ final int steps = Math.max(1, (int) (fadeDurationMs / FADE_DELAY_MS));
508
+ final float minVolume = zeroVolume;
509
+ final float initialVolume = Math.max(audio.getVolume(), minVolume);
510
+ final float finalTargetVolume = Math.max(targetVolume, minVolume);
511
+
512
+ // Clamp values to avoid overflow/underflow and invalid pow inputs
513
+ final float safeInitialVolume = Math.max(initialVolume, minVolume);
514
+ final float safeFinalTargetVolume = Math.max(finalTargetVolume, minVolume);
515
+
516
+ double ratio;
517
+ if (steps <= 0 || safeInitialVolume <= 0f || safeFinalTargetVolume <= 0f) {
518
+ ratio = 1.0; // No fade or invalid, just set directly
519
+ } else if (safeInitialVolume == safeFinalTargetVolume) {
520
+ ratio = 1.0;
521
+ } else {
522
+ ratio = Math.pow(safeFinalTargetVolume / safeInitialVolume, 1.0 / steps);
523
+ // Clamp ratio to reasonable bounds to avoid overflow
524
+ if (Double.isNaN(ratio) || Double.isInfinite(ratio) || ratio <= 0.0) {
525
+ ratio = 1.0;
526
+ }
527
+ }
528
+
529
+ Log.d(
530
+ TAG,
531
+ "Beginning exponential fade from volume " +
532
+ initialVolume +
533
+ " to " +
534
+ finalTargetVolume +
535
+ " over " +
536
+ (fadeDurationMs / 1000.0) +
537
+ "s in " +
538
+ steps +
539
+ " steps (step duration: " +
540
+ (FADE_DELAY_MS / 1000.0) +
541
+ "s, ratio: " +
542
+ ratio +
543
+ ")"
544
+ );
545
+
546
+ double finalRatio = ratio;
547
+ fadeTask = fadeExecutor.scheduleWithFixedDelay(
548
+ new Runnable() {
549
+ int currentStep = 0;
550
+ float currentVolume = initialVolume;
551
+
552
+ @Override
553
+ public void run() {
554
+ if ((audio != null && fadeState != FadeState.FADE_TO) || !audio.isPlaying() || currentStep >= steps) {
555
+ fadeState = FadeState.NONE;
556
+ cancelFade();
557
+ logger.debug("Fade to complete at time " + getCurrentPosition());
558
+ return;
559
+ }
560
+
320
561
  try {
321
- audio.setVolume(0);
322
- stop();
562
+ if (finalRatio == 1.0) {
563
+ currentVolume = safeFinalTargetVolume;
564
+ } else {
565
+ currentVolume *= (float) finalRatio;
566
+ }
567
+ currentVolume = Math.min(Math.max(currentVolume, minVolume), maxVolume); // Clamp between minVolume and maxVolume
568
+ if (audio != null) audio.setVolume(currentVolume);
569
+ logger.verbose("Fade to step " + currentStep + ": volume set to " + currentVolume);
570
+ currentStep++;
323
571
  } catch (Exception e) {
324
- Log.e(TAG, "Error stopping after fade", e);
572
+ logger.error("Error during fade to", e);
573
+ cancelFade();
325
574
  }
326
575
  }
327
- }
328
- };
329
- handler.post(fadeRunnable);
576
+ },
577
+ 0,
578
+ FADE_DELAY_MS,
579
+ TimeUnit.MILLISECONDS
580
+ );
581
+ }
582
+
583
+ /**
584
+ * Cancels the fade task if it is running.
585
+ */
586
+ protected void cancelFade() {
587
+ if (fadeTask != null && !fadeTask.isCancelled()) {
588
+ fadeTask.cancel(true);
589
+ }
590
+ fadeState = FadeState.NONE;
591
+ fadeTask = null;
592
+ }
593
+
594
+ @Override
595
+ public void close() {
596
+ if (fadeExecutor != null && !fadeExecutor.isShutdown()) {
597
+ fadeExecutor.shutdown();
598
+ }
599
+ }
600
+
601
+ @Override
602
+ protected void finalize() throws Throwable {
603
+ try {
604
+ close();
605
+ } finally {
606
+ super.finalize();
607
+ }
330
608
  }
331
609
  }
@@ -13,15 +13,20 @@ import android.media.AudioAttributes;
13
13
  import android.media.MediaPlayer;
14
14
  import android.os.Build;
15
15
  import android.util.Log;
16
+ import androidx.media3.common.util.UnstableApi;
16
17
 
18
+ @UnstableApi
17
19
  public class AudioDispatcher
18
- implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnSeekCompleteListener {
20
+ implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnSeekCompleteListener
21
+ {
19
22
 
20
23
  private final String TAG = "AudioDispatcher";
21
24
  private final MediaPlayer mediaPlayer;
22
25
  private int mediaState;
23
26
  private AudioAsset owner;
24
27
 
28
+ private float currentVolume = 1.0f;
29
+
25
30
  public AudioDispatcher(AssetFileDescriptor assetFileDescriptor, float volume) throws Exception {
26
31
  mediaState = INVALID;
27
32
 
@@ -41,6 +46,7 @@ public class AudioDispatcher
41
46
  .build()
42
47
  );
43
48
  mediaPlayer.setVolume(volume, volume);
49
+ currentVolume = volume;
44
50
  mediaPlayer.setPlaybackParams(mediaPlayer.getPlaybackParams().setSpeed(1.0f));
45
51
  mediaPlayer.prepare();
46
52
  }
@@ -91,6 +97,11 @@ public class AudioDispatcher
91
97
 
92
98
  public void setVolume(float volume) throws Exception {
93
99
  mediaPlayer.setVolume(volume, volume);
100
+ currentVolume = volume;
101
+ }
102
+
103
+ public float getVolume() {
104
+ return currentVolume;
94
105
  }
95
106
 
96
107
  public void setRate(float rate) throws Exception {
@@ -181,7 +192,17 @@ public class AudioDispatcher
181
192
  }
182
193
  }
183
194
 
184
- public boolean isPlaying() throws Exception {
185
- return mediaPlayer.isPlaying();
195
+ public boolean isPlaying() {
196
+ boolean playing = false;
197
+ try {
198
+ playing = mediaPlayer.isPlaying();
199
+ } catch (IllegalStateException ex) {
200
+ Log.v(TAG, "Caught exception while checking if audio is playing: " + ex.getLocalizedMessage());
201
+ }
202
+ return playing;
203
+ }
204
+
205
+ public boolean isPaused() {
206
+ return mediaState == PAUSE;
186
207
  }
187
208
  }