@capgo/native-audio 8.2.12 → 8.2.14
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 +147 -34
- package/android/build.gradle +1 -1
- package/android/src/main/java/ee/forgr/audio/AudioAsset.java +352 -74
- package/android/src/main/java/ee/forgr/audio/AudioDispatcher.java +24 -3
- package/android/src/main/java/ee/forgr/audio/Constant.java +9 -1
- package/android/src/main/java/ee/forgr/audio/Logger.java +55 -0
- package/android/src/main/java/ee/forgr/audio/NativeAudio.java +336 -57
- package/android/src/main/java/ee/forgr/audio/RemoteAudioAsset.java +307 -98
- package/android/src/main/java/ee/forgr/audio/StreamAudioAsset.java +285 -96
- package/dist/docs.json +307 -41
- package/dist/esm/definitions.d.ts +116 -38
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +52 -41
- package/dist/esm/web.js +386 -41
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +386 -41
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +386 -41
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/NativeAudioPlugin/AudioAsset+Fade.swift +104 -0
- package/ios/Sources/NativeAudioPlugin/AudioAsset.swift +168 -324
- package/ios/Sources/NativeAudioPlugin/Constant.swift +17 -4
- package/ios/Sources/NativeAudioPlugin/Logger.swift +43 -0
- package/ios/Sources/NativeAudioPlugin/Plugin.swift +176 -87
- package/ios/Sources/NativeAudioPlugin/RemoteAudioAsset+Fade.swift +110 -0
- package/ios/Sources/NativeAudioPlugin/RemoteAudioAsset.swift +117 -273
- package/ios/Tests/NativeAudioPluginTests/PluginTests.swift +47 -72
- package/package.json +1 -1
|
@@ -19,6 +19,7 @@ import androidx.media3.exoplayer.source.ProgressiveMediaSource;
|
|
|
19
19
|
import java.io.File;
|
|
20
20
|
import java.util.ArrayList;
|
|
21
21
|
import java.util.Map;
|
|
22
|
+
import java.util.concurrent.TimeUnit;
|
|
22
23
|
|
|
23
24
|
@UnstableApi
|
|
24
25
|
public class RemoteAudioAsset extends AudioAsset {
|
|
@@ -67,7 +68,7 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
67
68
|
initializePlayer(player);
|
|
68
69
|
}
|
|
69
70
|
} catch (Exception e) {
|
|
70
|
-
|
|
71
|
+
logger.error("Error initializing players", e);
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
}
|
|
@@ -76,7 +77,7 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
76
77
|
|
|
77
78
|
@UnstableApi
|
|
78
79
|
private void initializePlayer(ExoPlayer player) {
|
|
79
|
-
|
|
80
|
+
logger.debug("Initializing player");
|
|
80
81
|
|
|
81
82
|
// Initialize cache if not already done
|
|
82
83
|
if (cache == null) {
|
|
@@ -136,17 +137,17 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
136
137
|
|
|
137
138
|
@Override
|
|
138
139
|
public void onIsPlayingChanged(boolean isPlaying) {
|
|
139
|
-
|
|
140
|
+
logger.debug("isPlaying changed to: " + isPlaying + ", state: " + getStateString(player.getPlaybackState()));
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
@Override
|
|
143
144
|
public void onIsLoadingChanged(boolean isLoading) {
|
|
144
|
-
|
|
145
|
+
logger.debug("isLoading changed to: " + isLoading + ", state: " + getStateString(player.getPlaybackState()));
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
);
|
|
148
149
|
|
|
149
|
-
|
|
150
|
+
logger.debug("Player initialization complete");
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
private String getStateString(int state) {
|
|
@@ -165,7 +166,7 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
@Override
|
|
168
|
-
public void play(
|
|
169
|
+
public void play(double time, float volume) throws Exception {
|
|
169
170
|
if (players.isEmpty()) {
|
|
170
171
|
throw new Exception("No ExoPlayer available");
|
|
171
172
|
}
|
|
@@ -185,7 +186,7 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
185
186
|
if (playbackState == Player.STATE_READY) {
|
|
186
187
|
isPrepared = true;
|
|
187
188
|
try {
|
|
188
|
-
playInternal(player, time);
|
|
189
|
+
playInternal(player, time, volume);
|
|
189
190
|
startCurrentTimeUpdates();
|
|
190
191
|
} catch (Exception e) {
|
|
191
192
|
Log.e(TAG, "Error playing after prepare", e);
|
|
@@ -198,10 +199,10 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
198
199
|
);
|
|
199
200
|
} else {
|
|
200
201
|
try {
|
|
201
|
-
playInternal(player, time);
|
|
202
|
+
playInternal(player, time, volume);
|
|
202
203
|
startCurrentTimeUpdates();
|
|
203
204
|
} catch (Exception e) {
|
|
204
|
-
|
|
205
|
+
logger.error("Error playing", e);
|
|
205
206
|
}
|
|
206
207
|
}
|
|
207
208
|
}
|
|
@@ -211,16 +212,19 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
211
212
|
playIndex = (playIndex + 1) % players.size();
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
private void playInternal(final ExoPlayer player, final
|
|
215
|
+
private void playInternal(final ExoPlayer player, final double time, final float volume) throws Exception {
|
|
215
216
|
owner
|
|
216
217
|
.getActivity()
|
|
217
218
|
.runOnUiThread(
|
|
218
219
|
new Runnable() {
|
|
219
220
|
@Override
|
|
220
221
|
public void run() {
|
|
221
|
-
if (time !=
|
|
222
|
+
if (time != 0) {
|
|
222
223
|
player.seekTo(Math.round(time * 1000));
|
|
223
224
|
}
|
|
225
|
+
if (volume != 0) {
|
|
226
|
+
player.setVolume(volume);
|
|
227
|
+
}
|
|
224
228
|
player.play();
|
|
225
229
|
}
|
|
226
230
|
}
|
|
@@ -236,9 +240,11 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
236
240
|
new Runnable() {
|
|
237
241
|
@Override
|
|
238
242
|
public void run() {
|
|
243
|
+
cancelFade();
|
|
239
244
|
for (ExoPlayer player : players) {
|
|
240
|
-
if (player.isPlaying()) {
|
|
245
|
+
if (player != null && player.isPlaying()) {
|
|
241
246
|
player.pause();
|
|
247
|
+
stopCurrentTimeUpdates();
|
|
242
248
|
wasPlaying[0] = true;
|
|
243
249
|
}
|
|
244
250
|
}
|
|
@@ -257,7 +263,7 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
257
263
|
@Override
|
|
258
264
|
public void run() {
|
|
259
265
|
for (ExoPlayer player : players) {
|
|
260
|
-
if (!player.isPlaying()) {
|
|
266
|
+
if (player != null && !player.isPlaying()) {
|
|
261
267
|
player.play();
|
|
262
268
|
}
|
|
263
269
|
}
|
|
@@ -275,9 +281,11 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
275
281
|
new Runnable() {
|
|
276
282
|
@Override
|
|
277
283
|
public void run() {
|
|
284
|
+
cancelFade();
|
|
278
285
|
for (ExoPlayer player : players) {
|
|
279
|
-
if (player.isPlaying()) {
|
|
286
|
+
if (player != null && player.isPlaying()) {
|
|
280
287
|
player.stop();
|
|
288
|
+
dispatchComplete();
|
|
281
289
|
}
|
|
282
290
|
// Reset the ExoPlayer to make it ready for future playback
|
|
283
291
|
initializePlayer(player);
|
|
@@ -353,7 +361,7 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
353
361
|
}
|
|
354
362
|
|
|
355
363
|
@Override
|
|
356
|
-
public void setVolume(final float volume) throws Exception {
|
|
364
|
+
public void setVolume(final float volume, final double duration) throws Exception {
|
|
357
365
|
this.volume = volume;
|
|
358
366
|
owner
|
|
359
367
|
.getActivity()
|
|
@@ -361,14 +369,35 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
361
369
|
new Runnable() {
|
|
362
370
|
@Override
|
|
363
371
|
public void run() {
|
|
372
|
+
cancelFade();
|
|
364
373
|
for (ExoPlayer player : players) {
|
|
365
|
-
player
|
|
374
|
+
if (player == null) continue;
|
|
375
|
+
if (player.isPlaying() && duration > 0) {
|
|
376
|
+
fadeTo(player, (float) duration, volume);
|
|
377
|
+
} else {
|
|
378
|
+
player.setVolume(volume);
|
|
379
|
+
}
|
|
366
380
|
}
|
|
367
381
|
}
|
|
368
382
|
}
|
|
369
383
|
);
|
|
370
384
|
}
|
|
371
385
|
|
|
386
|
+
@Override
|
|
387
|
+
public void setVolume(final float volume) throws Exception {
|
|
388
|
+
setVolume(volume, 0);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
@Override
|
|
392
|
+
public float getVolume() throws Exception {
|
|
393
|
+
if (players.isEmpty()) {
|
|
394
|
+
throw new Exception("No ExoPlayer available");
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
final ExoPlayer player = players.get(playIndex);
|
|
398
|
+
return player != null ? player.getVolume() : 0;
|
|
399
|
+
}
|
|
400
|
+
|
|
372
401
|
@Override
|
|
373
402
|
public boolean isPlaying() throws Exception {
|
|
374
403
|
if (players.isEmpty() || !isPrepared) return false;
|
|
@@ -379,7 +408,7 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
379
408
|
|
|
380
409
|
@Override
|
|
381
410
|
public double getDuration() {
|
|
382
|
-
|
|
411
|
+
logger.debug("getDuration called, players empty: " + players.isEmpty() + ", isPrepared: " + isPrepared);
|
|
383
412
|
if (!players.isEmpty() && isPrepared) {
|
|
384
413
|
final double[] duration = { 0 };
|
|
385
414
|
owner
|
|
@@ -390,25 +419,25 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
390
419
|
public void run() {
|
|
391
420
|
ExoPlayer player = players.get(playIndex);
|
|
392
421
|
int state = player.getPlaybackState();
|
|
393
|
-
|
|
422
|
+
logger.debug("Player state: " + state + " (READY=" + Player.STATE_READY + ")");
|
|
394
423
|
if (state == Player.STATE_READY) {
|
|
395
424
|
long rawDuration = player.getDuration();
|
|
396
|
-
|
|
425
|
+
logger.debug("Raw duration: " + rawDuration + ", TIME_UNSET=" + androidx.media3.common.C.TIME_UNSET);
|
|
397
426
|
if (rawDuration != androidx.media3.common.C.TIME_UNSET) {
|
|
398
427
|
duration[0] = rawDuration / 1000.0;
|
|
399
|
-
|
|
428
|
+
logger.debug("Final duration in seconds: " + duration[0]);
|
|
400
429
|
} else {
|
|
401
|
-
|
|
430
|
+
logger.debug("Duration is TIME_UNSET");
|
|
402
431
|
}
|
|
403
432
|
} else {
|
|
404
|
-
|
|
433
|
+
logger.debug("Player not in READY state");
|
|
405
434
|
}
|
|
406
435
|
}
|
|
407
436
|
}
|
|
408
437
|
);
|
|
409
438
|
return duration[0];
|
|
410
439
|
}
|
|
411
|
-
|
|
440
|
+
logger.debug("No players or not prepared for duration");
|
|
412
441
|
return 0;
|
|
413
442
|
}
|
|
414
443
|
|
|
@@ -425,7 +454,7 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
425
454
|
ExoPlayer player = players.get(playIndex);
|
|
426
455
|
if (player.getPlaybackState() == Player.STATE_READY) {
|
|
427
456
|
long rawPosition = player.getCurrentPosition();
|
|
428
|
-
|
|
457
|
+
logger.debug("Raw position: " + rawPosition);
|
|
429
458
|
position[0] = rawPosition / 1000.0;
|
|
430
459
|
}
|
|
431
460
|
}
|
|
@@ -481,7 +510,7 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
481
510
|
deleteDir(cacheDir);
|
|
482
511
|
}
|
|
483
512
|
} catch (Exception e) {
|
|
484
|
-
|
|
513
|
+
logger.error("Error clearing audio cache", e);
|
|
485
514
|
}
|
|
486
515
|
}
|
|
487
516
|
|
|
@@ -500,17 +529,7 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
500
529
|
return dir.delete();
|
|
501
530
|
}
|
|
502
531
|
|
|
503
|
-
public void
|
|
504
|
-
this.completionListener = listener;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
protected void notifyCompletion() {
|
|
508
|
-
if (completionListener != null) {
|
|
509
|
-
completionListener.onCompletion(getAssetId());
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
public void playWithFade(Double time) throws Exception {
|
|
532
|
+
public void playWithFadeIn(double time, float volume, float fadeInDurationMs) throws Exception {
|
|
514
533
|
if (players.isEmpty()) {
|
|
515
534
|
throw new Exception("No ExoPlayer available");
|
|
516
535
|
}
|
|
@@ -522,38 +541,78 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
522
541
|
new Runnable() {
|
|
523
542
|
@Override
|
|
524
543
|
public void run() {
|
|
525
|
-
if (!player.isPlaying()) {
|
|
526
|
-
if (time !=
|
|
544
|
+
if (player != null && !player.isPlaying()) {
|
|
545
|
+
if (time != 0) {
|
|
527
546
|
player.seekTo(Math.round(time * 1000));
|
|
528
547
|
}
|
|
529
548
|
player.setVolume(0);
|
|
530
549
|
player.play();
|
|
531
550
|
startCurrentTimeUpdates();
|
|
532
|
-
fadeIn(player);
|
|
551
|
+
fadeIn(player, fadeInDurationMs, volume);
|
|
533
552
|
}
|
|
534
553
|
}
|
|
535
554
|
}
|
|
536
555
|
);
|
|
537
556
|
}
|
|
538
557
|
|
|
539
|
-
private void fadeIn(final ExoPlayer player) {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
558
|
+
private void fadeIn(final ExoPlayer player, float fadeInDurationMs, float volume) {
|
|
559
|
+
cancelFade();
|
|
560
|
+
fadeState = FadeState.FADE_IN;
|
|
561
|
+
|
|
562
|
+
final float targetVolume = volume;
|
|
563
|
+
final int steps = Math.max(1, (int) (fadeInDurationMs / FADE_DELAY_MS));
|
|
564
|
+
final float fadeStep = targetVolume / steps;
|
|
565
|
+
|
|
566
|
+
Log.d(
|
|
567
|
+
TAG,
|
|
568
|
+
"Beginning fade in at time " +
|
|
569
|
+
getCurrentPosition() +
|
|
570
|
+
" over " +
|
|
571
|
+
(fadeInDurationMs / 1000.0) +
|
|
572
|
+
"s to target volume " +
|
|
573
|
+
targetVolume +
|
|
574
|
+
" in " +
|
|
575
|
+
steps +
|
|
576
|
+
" steps (step duration: " +
|
|
577
|
+
(FADE_DELAY_MS / 1000.0) +
|
|
578
|
+
"s"
|
|
579
|
+
);
|
|
543
580
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
581
|
+
fadeTask = fadeExecutor.scheduleWithFixedDelay(
|
|
582
|
+
new Runnable() {
|
|
583
|
+
float currentVolume = 0;
|
|
584
|
+
|
|
585
|
+
@Override
|
|
586
|
+
public void run() {
|
|
587
|
+
if (fadeState != FadeState.FADE_IN || currentVolume >= targetVolume) {
|
|
588
|
+
fadeState = FadeState.NONE;
|
|
589
|
+
cancelFade();
|
|
590
|
+
logger.debug("Fade in complete at time " + getCurrentPosition());
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
final float previousCurrentVolume = currentVolume;
|
|
594
|
+
currentVolume += fadeStep;
|
|
595
|
+
final float resolvedTargetVolume = Math.min(currentVolume, targetVolume);
|
|
596
|
+
Log.v(
|
|
597
|
+
TAG,
|
|
598
|
+
"Fade in step: from " + previousCurrentVolume + " to " + currentVolume + " to target " + resolvedTargetVolume
|
|
599
|
+
);
|
|
600
|
+
owner
|
|
601
|
+
.getActivity()
|
|
602
|
+
.runOnUiThread(() -> {
|
|
603
|
+
if (player != null && player.isPlaying()) {
|
|
604
|
+
player.setVolume(currentVolume);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
550
607
|
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
|
|
608
|
+
},
|
|
609
|
+
0,
|
|
610
|
+
FADE_DELAY_MS,
|
|
611
|
+
TimeUnit.MILLISECONDS
|
|
612
|
+
);
|
|
554
613
|
}
|
|
555
614
|
|
|
556
|
-
public void stopWithFade() throws Exception {
|
|
615
|
+
public void stopWithFade(float fadeOutDurationMs, boolean asPause) throws Exception {
|
|
557
616
|
if (players.isEmpty()) {
|
|
558
617
|
return;
|
|
559
618
|
}
|
|
@@ -561,53 +620,194 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
561
620
|
final ExoPlayer player = players.get(playIndex);
|
|
562
621
|
owner
|
|
563
622
|
.getActivity()
|
|
564
|
-
.runOnUiThread(
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
public void run() {
|
|
568
|
-
if (player.isPlaying()) {
|
|
569
|
-
fadeOut(player);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
623
|
+
.runOnUiThread(() -> {
|
|
624
|
+
if (player != null && player.isPlaying()) {
|
|
625
|
+
fadeOut(player, fadeOutDurationMs, asPause);
|
|
572
626
|
}
|
|
573
|
-
);
|
|
627
|
+
});
|
|
574
628
|
}
|
|
575
629
|
|
|
576
|
-
private void fadeOut(final ExoPlayer player) {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
630
|
+
private void fadeOut(final ExoPlayer player, float fadeOutDurationMs, boolean asPause) {
|
|
631
|
+
cancelFade();
|
|
632
|
+
fadeState = FadeState.FADE_OUT;
|
|
633
|
+
|
|
634
|
+
final int steps = Math.max(1, (int) (fadeOutDurationMs / FADE_DELAY_MS));
|
|
635
|
+
final float initialVolume = player.getVolume();
|
|
636
|
+
final float fadeStep = initialVolume / steps;
|
|
637
|
+
|
|
638
|
+
Log.d(
|
|
639
|
+
TAG,
|
|
640
|
+
"Beginning fade out from volume " +
|
|
641
|
+
initialVolume +
|
|
642
|
+
" at time " +
|
|
643
|
+
getCurrentPosition() +
|
|
644
|
+
" over " +
|
|
645
|
+
(fadeOutDurationMs / 1000.0) +
|
|
646
|
+
"s in " +
|
|
647
|
+
steps +
|
|
648
|
+
" steps (step duration: " +
|
|
649
|
+
(FADE_DELAY_MS / 1000.0) +
|
|
650
|
+
"s)"
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
fadeTask = fadeExecutor.scheduleWithFixedDelay(
|
|
654
|
+
new Runnable() {
|
|
655
|
+
float currentVolume = initialVolume;
|
|
580
656
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
657
|
+
@Override
|
|
658
|
+
public void run() {
|
|
659
|
+
if (fadeState != FadeState.FADE_OUT || currentVolume <= 0) {
|
|
660
|
+
fadeState = FadeState.NONE;
|
|
661
|
+
owner
|
|
662
|
+
.getActivity()
|
|
663
|
+
.runOnUiThread(() -> {
|
|
664
|
+
if (player != null && player.isPlaying()) {
|
|
665
|
+
if (asPause) {
|
|
666
|
+
player.pause();
|
|
667
|
+
logger.verbose("Faded out to pause at time " + getCurrentPosition());
|
|
668
|
+
} else {
|
|
669
|
+
player.setVolume(0);
|
|
670
|
+
player.stop();
|
|
671
|
+
logger.verbose("Faded out to stop at time " + getCurrentPosition());
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
cancelFade();
|
|
676
|
+
logger.verbose("Fade out complete at time " + getCurrentPosition());
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
final float previousCurrentVolume = currentVolume;
|
|
680
|
+
currentVolume -= fadeStep;
|
|
681
|
+
final float thisTargetVolume = Math.max(currentVolume, 0);
|
|
682
|
+
logger.debug(
|
|
683
|
+
"Fade out step: from " + previousCurrentVolume + " to " + currentVolume + " to target " + thisTargetVolume
|
|
684
|
+
);
|
|
685
|
+
owner
|
|
686
|
+
.getActivity()
|
|
687
|
+
.runOnUiThread(() -> {
|
|
688
|
+
if (player != null && player.isPlaying()) {
|
|
689
|
+
player.setVolume(thisTargetVolume);
|
|
690
|
+
}
|
|
691
|
+
});
|
|
590
692
|
}
|
|
693
|
+
},
|
|
694
|
+
0,
|
|
695
|
+
FADE_DELAY_MS,
|
|
696
|
+
TimeUnit.MILLISECONDS
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
private void fadeTo(final ExoPlayer player, float fadeDurationMs, float targetVolume) {
|
|
701
|
+
cancelFade();
|
|
702
|
+
fadeState = FadeState.FADE_TO;
|
|
703
|
+
|
|
704
|
+
final int steps = Math.max(1, (int) (fadeDurationMs / FADE_DELAY_MS));
|
|
705
|
+
final float minVolume = zeroVolume;
|
|
706
|
+
final float maxVol = maxVolume;
|
|
707
|
+
final float initialVolume = Math.max(player.getVolume(), minVolume);
|
|
708
|
+
final float finalTargetVolume = Math.max(targetVolume, minVolume);
|
|
709
|
+
|
|
710
|
+
// Clamp values to avoid overflow/underflow and invalid pow inputs
|
|
711
|
+
final float safeInitialVolume = Math.max(initialVolume, minVolume);
|
|
712
|
+
final float safeFinalTargetVolume = Math.max(finalTargetVolume, minVolume);
|
|
713
|
+
|
|
714
|
+
double ratio;
|
|
715
|
+
if (steps <= 0 || safeInitialVolume <= 0f || safeFinalTargetVolume <= 0f) {
|
|
716
|
+
ratio = 1.0;
|
|
717
|
+
} else if (safeInitialVolume == safeFinalTargetVolume) {
|
|
718
|
+
ratio = 1.0;
|
|
719
|
+
} else {
|
|
720
|
+
ratio = Math.pow(safeFinalTargetVolume / safeInitialVolume, 1.0 / steps);
|
|
721
|
+
if (Double.isNaN(ratio) || Double.isInfinite(ratio) || ratio <= 0.0) {
|
|
722
|
+
ratio = 1.0;
|
|
591
723
|
}
|
|
592
|
-
}
|
|
593
|
-
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
Log.d(
|
|
727
|
+
TAG,
|
|
728
|
+
"Beginning exponential fade from volume " +
|
|
729
|
+
initialVolume +
|
|
730
|
+
" to " +
|
|
731
|
+
finalTargetVolume +
|
|
732
|
+
" over " +
|
|
733
|
+
(fadeDurationMs / 1000.0) +
|
|
734
|
+
"s in " +
|
|
735
|
+
steps +
|
|
736
|
+
" steps (step duration: " +
|
|
737
|
+
(FADE_DELAY_MS / 1000.0) +
|
|
738
|
+
"s, ratio: " +
|
|
739
|
+
ratio +
|
|
740
|
+
")"
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
double finalRatio = ratio;
|
|
744
|
+
fadeTask = fadeExecutor.scheduleWithFixedDelay(
|
|
745
|
+
new Runnable() {
|
|
746
|
+
int currentStep = 0;
|
|
747
|
+
float currentVolume = initialVolume;
|
|
748
|
+
|
|
749
|
+
@Override
|
|
750
|
+
public void run() {
|
|
751
|
+
if (fadeState != FadeState.FADE_TO || player == null || !player.isPlaying() || currentStep >= steps) {
|
|
752
|
+
fadeState = FadeState.NONE;
|
|
753
|
+
cancelFade();
|
|
754
|
+
logger.debug("Fade to complete at time " + getCurrentPosition());
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
try {
|
|
758
|
+
if (finalRatio == 1.0) {
|
|
759
|
+
currentVolume = safeFinalTargetVolume;
|
|
760
|
+
} else {
|
|
761
|
+
currentVolume *= (float) finalRatio;
|
|
762
|
+
}
|
|
763
|
+
// Clamp volume between minVolume and maxVolume
|
|
764
|
+
currentVolume = Math.min(Math.max(currentVolume, minVolume), maxVol);
|
|
765
|
+
logger.verbose("Fade to step " + currentStep + ": volume set to " + currentVolume);
|
|
766
|
+
owner
|
|
767
|
+
.getActivity()
|
|
768
|
+
.runOnUiThread(() -> {
|
|
769
|
+
if (player != null && player.isPlaying()) {
|
|
770
|
+
player.setVolume(currentVolume);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
currentStep++;
|
|
774
|
+
} catch (Exception e) {
|
|
775
|
+
logger.error("Error during fade to", e);
|
|
776
|
+
cancelFade();
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
0,
|
|
781
|
+
FADE_DELAY_MS,
|
|
782
|
+
TimeUnit.MILLISECONDS
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
protected void cancelFade() {
|
|
787
|
+
if (fadeTask != null && !fadeTask.isCancelled()) {
|
|
788
|
+
fadeTask.cancel(true);
|
|
789
|
+
}
|
|
790
|
+
fadeState = FadeState.NONE;
|
|
791
|
+
fadeTask = null;
|
|
594
792
|
}
|
|
595
793
|
|
|
596
794
|
@Override
|
|
597
795
|
protected void startCurrentTimeUpdates() {
|
|
598
|
-
|
|
796
|
+
logger.debug("Starting timer updates");
|
|
599
797
|
if (currentTimeHandler == null) {
|
|
600
798
|
currentTimeHandler = new Handler(Looper.getMainLooper());
|
|
601
799
|
}
|
|
800
|
+
// Reset completion status for this assetId
|
|
801
|
+
dispatchedCompleteMap.put(assetId, false);
|
|
602
802
|
|
|
603
803
|
// Wait for player to be truly ready
|
|
604
804
|
currentTimeHandler.postDelayed(
|
|
605
805
|
new Runnable() {
|
|
606
806
|
@Override
|
|
607
807
|
public void run() {
|
|
608
|
-
if (!players.isEmpty()) {
|
|
808
|
+
if (!players.isEmpty() && playIndex >= 0 && playIndex < players.size()) {
|
|
609
809
|
ExoPlayer player = players.get(playIndex);
|
|
610
|
-
if (player.getPlaybackState() == Player.STATE_READY) {
|
|
810
|
+
if (player != null && player.getPlaybackState() == Player.STATE_READY) {
|
|
611
811
|
startTimeUpdateLoop();
|
|
612
812
|
} else {
|
|
613
813
|
// Check again in 100ms
|
|
@@ -625,33 +825,42 @@ public class RemoteAudioAsset extends AudioAsset {
|
|
|
625
825
|
@Override
|
|
626
826
|
public void run() {
|
|
627
827
|
try {
|
|
628
|
-
|
|
828
|
+
boolean isPaused = false;
|
|
829
|
+
if (!players.isEmpty() && playIndex >= 0 && playIndex < players.size()) {
|
|
629
830
|
ExoPlayer player = players.get(playIndex);
|
|
630
|
-
if (player.getPlaybackState() == Player.STATE_READY
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
831
|
+
if (player != null && player.getPlaybackState() == Player.STATE_READY) {
|
|
832
|
+
if (player.isPlaying()) {
|
|
833
|
+
double currentTime = player.getCurrentPosition() / 1000.0; // Get time directly
|
|
834
|
+
logger.debug("Play timer update: currentTime = " + currentTime);
|
|
835
|
+
if (owner != null) owner.notifyCurrentTime(assetId, currentTime);
|
|
836
|
+
currentTimeHandler.postDelayed(this, 100);
|
|
837
|
+
return;
|
|
838
|
+
} else if (!player.getPlayWhenReady()) {
|
|
839
|
+
isPaused = true;
|
|
840
|
+
}
|
|
636
841
|
}
|
|
637
842
|
}
|
|
638
|
-
|
|
843
|
+
logger.debug("Stopping play timer - not playing or not ready");
|
|
639
844
|
stopCurrentTimeUpdates();
|
|
845
|
+
if (isPaused) {
|
|
846
|
+
logger.verbose("Playback is paused, not dispatching complete");
|
|
847
|
+
} else {
|
|
848
|
+
logger.verbose("Playback is stopped, dispatching complete");
|
|
849
|
+
dispatchComplete();
|
|
850
|
+
}
|
|
640
851
|
} catch (Exception e) {
|
|
641
|
-
|
|
852
|
+
logger.error("Error getting current time", e);
|
|
642
853
|
stopCurrentTimeUpdates();
|
|
643
854
|
}
|
|
644
855
|
}
|
|
645
856
|
};
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
currentTimeHandler.removeCallbacks(currentTimeRunnable);
|
|
654
|
-
currentTimeHandler = null;
|
|
857
|
+
try {
|
|
858
|
+
if (currentTimeHandler == null) {
|
|
859
|
+
currentTimeHandler = new Handler(Looper.getMainLooper());
|
|
860
|
+
}
|
|
861
|
+
currentTimeHandler.post(currentTimeRunnable);
|
|
862
|
+
} catch (Exception e) {
|
|
863
|
+
logger.error("Error starting current time updates", e);
|
|
655
864
|
}
|
|
656
865
|
}
|
|
657
866
|
}
|