@capgo/native-audio 7.1.8 → 7.3.0

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.
@@ -1,178 +1,611 @@
1
1
  package ee.forgr.audio;
2
2
 
3
- import android.media.MediaPlayer;
3
+ import android.content.Context;
4
4
  import android.net.Uri;
5
+ import android.os.Handler;
6
+ import android.os.Looper;
5
7
  import android.util.Log;
8
+ import androidx.media3.common.MediaItem;
9
+ import androidx.media3.common.Player;
10
+ import androidx.media3.common.util.UnstableApi;
11
+ import androidx.media3.database.StandaloneDatabaseProvider;
12
+ import androidx.media3.datasource.DefaultHttpDataSource;
13
+ import androidx.media3.datasource.cache.CacheDataSource;
14
+ import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor;
15
+ import androidx.media3.datasource.cache.SimpleCache;
16
+ import androidx.media3.exoplayer.ExoPlayer;
17
+ import androidx.media3.exoplayer.source.MediaSource;
18
+ import androidx.media3.exoplayer.source.ProgressiveMediaSource;
19
+ import java.io.File;
6
20
  import java.util.ArrayList;
7
21
 
22
+ @UnstableApi
8
23
  public class RemoteAudioAsset extends AudioAsset {
9
24
 
10
- private static final String TAG = "RemoteAudioAsset";
11
- private final ArrayList<MediaPlayer> mediaPlayers;
12
- private int playIndex = 0;
13
- private final Uri uri;
14
- private float volume;
15
- private boolean isPrepared = false;
16
-
17
- public RemoteAudioAsset(
18
- NativeAudio owner,
19
- String assetId,
20
- Uri uri,
21
- int audioChannelNum,
22
- float volume
23
- ) throws Exception {
24
- super(owner, assetId, null, 0, volume);
25
- this.uri = uri;
26
- this.volume = volume;
27
- this.mediaPlayers = new ArrayList<>();
28
-
29
- if (audioChannelNum < 1) {
30
- audioChannelNum = 1;
31
- }
32
-
33
- for (int i = 0; i < audioChannelNum; i++) {
34
- MediaPlayer mediaPlayer = new MediaPlayer();
35
- mediaPlayers.add(mediaPlayer);
36
- initializeMediaPlayer(mediaPlayer);
37
- }
38
- }
39
-
40
- private void initializeMediaPlayer(MediaPlayer mediaPlayer) {
41
- try {
42
- mediaPlayer.setDataSource(owner.getContext(), uri);
43
- mediaPlayer.setVolume(volume, volume);
44
- mediaPlayer.setOnPreparedListener(mp -> {
45
- isPrepared = true;
46
- Log.d(TAG, "MediaPlayer prepared for " + uri.toString());
47
- });
48
- mediaPlayer.setOnCompletionListener(mp -> {
49
- notifyCompletion();
50
- });
51
- mediaPlayer.setOnErrorListener((mp, what, extra) -> {
52
- Log.e(TAG, "MediaPlayer error: " + what + ", " + extra);
53
- return false;
54
- });
55
- mediaPlayer.prepareAsync();
56
- } catch (Exception e) {
57
- Log.e(TAG, "Error initializing MediaPlayer", e);
58
- }
59
- }
60
-
61
- @Override
62
- public void play(Double time) throws Exception {
63
- if (mediaPlayers.isEmpty()) {
64
- throw new Exception("No MediaPlayer available");
65
- }
66
-
67
- MediaPlayer mediaPlayer = mediaPlayers.get(playIndex);
68
- if (!isPrepared) {
69
- Log.d(TAG, "MediaPlayer not yet prepared, waiting...");
70
- mediaPlayer.setOnPreparedListener(mp -> {
71
- isPrepared = true;
25
+ private static final String TAG = "RemoteAudioAsset";
26
+ private final ArrayList<ExoPlayer> players;
27
+ private int playIndex = 0;
28
+ private final Uri uri;
29
+ private float volume;
30
+ private boolean isPrepared = false;
31
+ private static SimpleCache cache;
32
+ private static final long MAX_CACHE_SIZE = 100 * 1024 * 1024; // 100MB cache
33
+ protected AudioCompletionListener completionListener;
34
+ private static final float FADE_STEP = 0.05f;
35
+ private static final int FADE_DELAY_MS = 80; // 80ms between steps
36
+ private float initialVolume;
37
+ private Handler currentTimeHandler;
38
+ private Runnable currentTimeRunnable;
39
+
40
+ public RemoteAudioAsset(NativeAudio owner, String assetId, Uri uri, int audioChannelNum, float volume) throws Exception {
41
+ super(owner, assetId, null, 0, volume);
42
+ this.uri = uri;
43
+ this.volume = volume;
44
+ this.initialVolume = volume;
45
+ this.players = new ArrayList<>();
46
+
47
+ if (audioChannelNum < 1) {
48
+ audioChannelNum = 1;
49
+ }
50
+
51
+ final int channels = audioChannelNum;
52
+ owner
53
+ .getActivity()
54
+ .runOnUiThread(
55
+ new Runnable() {
56
+ @Override
57
+ public void run() {
58
+ try {
59
+ for (int i = 0; i < channels; i++) {
60
+ ExoPlayer player = new ExoPlayer.Builder(owner.getContext()).build();
61
+ player.setPlaybackSpeed(1.0f);
62
+ players.add(player);
63
+ initializePlayer(player);
64
+ }
65
+ } catch (Exception e) {
66
+ Log.e(TAG, "Error initializing players", e);
67
+ }
68
+ }
69
+ }
70
+ );
71
+ }
72
+
73
+ @UnstableApi
74
+ private void initializePlayer(ExoPlayer player) {
75
+ Log.d(TAG, "Initializing player");
76
+
77
+ // Initialize cache if not already done
78
+ if (cache == null) {
79
+ File cacheDir = new File(owner.getContext().getCacheDir(), "media");
80
+ if (!cacheDir.exists()) {
81
+ cacheDir.mkdirs();
82
+ }
83
+ cache = new SimpleCache(
84
+ cacheDir,
85
+ new LeastRecentlyUsedCacheEvictor(MAX_CACHE_SIZE),
86
+ new StandaloneDatabaseProvider(owner.getContext())
87
+ );
88
+ }
89
+
90
+ // Create cached data source factory
91
+ DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory()
92
+ .setAllowCrossProtocolRedirects(true)
93
+ .setConnectTimeoutMs(15000)
94
+ .setReadTimeoutMs(15000);
95
+
96
+ CacheDataSource.Factory cacheDataSourceFactory = new CacheDataSource.Factory()
97
+ .setCache(cache)
98
+ .setUpstreamDataSourceFactory(httpDataSourceFactory)
99
+ .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
100
+
101
+ // Create media source
102
+ MediaSource mediaSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory).createMediaSource(MediaItem.fromUri(uri));
103
+
104
+ player.setMediaSource(mediaSource);
105
+ player.setVolume(volume);
106
+ player.prepare();
107
+
108
+ // Add listener for duration
109
+ player.addListener(
110
+ new Player.Listener() {
111
+ @Override
112
+ public void onPlaybackStateChanged(int playbackState) {
113
+ Log.d(TAG, "Player state changed to: " + getStateString(playbackState));
114
+ if (playbackState == Player.STATE_READY) {
115
+ isPrepared = true;
116
+ long duration = player.getDuration();
117
+ Log.d(TAG, "Duration available on STATE_READY: " + duration + " ms");
118
+ if (duration != androidx.media3.common.C.TIME_UNSET) {
119
+ double durationSec = duration / 1000.0;
120
+ Log.d(TAG, "Notifying duration: " + durationSec + " seconds");
121
+ owner.notifyDurationAvailable(assetId, durationSec);
122
+ }
123
+ }
124
+ }
125
+
126
+ @Override
127
+ public void onIsPlayingChanged(boolean isPlaying) {
128
+ Log.d(TAG, "isPlaying changed to: " + isPlaying + ", state: " + getStateString(player.getPlaybackState()));
129
+ }
130
+
131
+ @Override
132
+ public void onIsLoadingChanged(boolean isLoading) {
133
+ Log.d(TAG, "isLoading changed to: " + isLoading + ", state: " + getStateString(player.getPlaybackState()));
134
+ }
135
+ }
136
+ );
137
+
138
+ Log.d(TAG, "Player initialization complete");
139
+ }
140
+
141
+ private String getStateString(int state) {
142
+ switch (state) {
143
+ case Player.STATE_IDLE:
144
+ return "IDLE";
145
+ case Player.STATE_BUFFERING:
146
+ return "BUFFERING";
147
+ case Player.STATE_READY:
148
+ return "READY";
149
+ case Player.STATE_ENDED:
150
+ return "ENDED";
151
+ default:
152
+ return "UNKNOWN(" + state + ")";
153
+ }
154
+ }
155
+
156
+ @Override
157
+ public void play(Double time) throws Exception {
158
+ if (players.isEmpty()) {
159
+ throw new Exception("No ExoPlayer available");
160
+ }
161
+
162
+ final ExoPlayer player = players.get(playIndex);
163
+ owner
164
+ .getActivity()
165
+ .runOnUiThread(
166
+ new Runnable() {
167
+ @Override
168
+ public void run() {
169
+ if (!isPrepared) {
170
+ player.addListener(
171
+ new Player.Listener() {
172
+ @Override
173
+ public void onPlaybackStateChanged(int playbackState) {
174
+ if (playbackState == Player.STATE_READY) {
175
+ isPrepared = true;
176
+ try {
177
+ playInternal(player, time);
178
+ startCurrentTimeUpdates();
179
+ } catch (Exception e) {
180
+ Log.e(TAG, "Error playing after prepare", e);
181
+ }
182
+ } else if (playbackState == Player.STATE_ENDED) {
183
+ owner.dispatchComplete(getAssetId());
184
+ notifyCompletion();
185
+ }
186
+ }
187
+ }
188
+ );
189
+ } else {
190
+ try {
191
+ playInternal(player, time);
192
+ startCurrentTimeUpdates();
193
+ } catch (Exception e) {
194
+ Log.e(TAG, "Error playing", e);
195
+ }
196
+ }
197
+ }
198
+ }
199
+ );
200
+
201
+ playIndex = (playIndex + 1) % players.size();
202
+ }
203
+
204
+ private void playInternal(final ExoPlayer player, final Double time) throws Exception {
205
+ owner
206
+ .getActivity()
207
+ .runOnUiThread(
208
+ new Runnable() {
209
+ @Override
210
+ public void run() {
211
+ if (time != null) {
212
+ player.seekTo(Math.round(time * 1000));
213
+ }
214
+ player.play();
215
+ }
216
+ }
217
+ );
218
+ }
219
+
220
+ @Override
221
+ public boolean pause() throws Exception {
222
+ final boolean[] wasPlaying = { false };
223
+ owner
224
+ .getActivity()
225
+ .runOnUiThread(
226
+ new Runnable() {
227
+ @Override
228
+ public void run() {
229
+ for (ExoPlayer player : players) {
230
+ if (player.isPlaying()) {
231
+ player.pause();
232
+ wasPlaying[0] = true;
233
+ }
234
+ }
235
+ }
236
+ }
237
+ );
238
+ return wasPlaying[0];
239
+ }
240
+
241
+ @Override
242
+ public void resume() throws Exception {
243
+ owner
244
+ .getActivity()
245
+ .runOnUiThread(
246
+ new Runnable() {
247
+ @Override
248
+ public void run() {
249
+ for (ExoPlayer player : players) {
250
+ if (!player.isPlaying()) {
251
+ player.play();
252
+ }
253
+ }
254
+ startCurrentTimeUpdates();
255
+ }
256
+ }
257
+ );
258
+ }
259
+
260
+ @Override
261
+ public void stop() throws Exception {
262
+ owner
263
+ .getActivity()
264
+ .runOnUiThread(
265
+ new Runnable() {
266
+ @Override
267
+ public void run() {
268
+ for (ExoPlayer player : players) {
269
+ if (player.isPlaying()) {
270
+ player.stop();
271
+ }
272
+ // Reset the ExoPlayer to make it ready for future playback
273
+ initializePlayer(player);
274
+ }
275
+ isPrepared = false;
276
+ }
277
+ }
278
+ );
279
+ }
280
+
281
+ @Override
282
+ public void loop() throws Exception {
283
+ owner
284
+ .getActivity()
285
+ .runOnUiThread(
286
+ new Runnable() {
287
+ @Override
288
+ public void run() {
289
+ if (!players.isEmpty()) {
290
+ ExoPlayer player = players.get(playIndex);
291
+ player.setRepeatMode(Player.REPEAT_MODE_ONE);
292
+ player.play();
293
+ playIndex = (playIndex + 1) % players.size();
294
+ startCurrentTimeUpdates();
295
+ }
296
+ }
297
+ }
298
+ );
299
+ }
300
+
301
+ @Override
302
+ public void unload() throws Exception {
303
+ for (ExoPlayer player : players) {
304
+ player.release();
305
+ }
306
+ players.clear();
307
+ }
308
+
309
+ @Override
310
+ public void setVolume(final float volume) throws Exception {
311
+ this.volume = volume;
312
+ owner
313
+ .getActivity()
314
+ .runOnUiThread(
315
+ new Runnable() {
316
+ @Override
317
+ public void run() {
318
+ for (ExoPlayer player : players) {
319
+ player.setVolume(volume);
320
+ }
321
+ }
322
+ }
323
+ );
324
+ }
325
+
326
+ @Override
327
+ public boolean isPlaying() throws Exception {
328
+ if (players.isEmpty() || !isPrepared) return false;
329
+
330
+ ExoPlayer player = players.get(playIndex);
331
+ return player != null && player.isPlaying();
332
+ }
333
+
334
+ @Override
335
+ public double getDuration() {
336
+ Log.d(TAG, "getDuration called, players empty: " + players.isEmpty() + ", isPrepared: " + isPrepared);
337
+ if (!players.isEmpty() && isPrepared) {
338
+ final double[] duration = { 0 };
339
+ owner
340
+ .getActivity()
341
+ .runOnUiThread(
342
+ new Runnable() {
343
+ @Override
344
+ public void run() {
345
+ ExoPlayer player = players.get(playIndex);
346
+ int state = player.getPlaybackState();
347
+ Log.d(TAG, "Player state: " + state + " (READY=" + Player.STATE_READY + ")");
348
+ if (state == Player.STATE_READY) {
349
+ long rawDuration = player.getDuration();
350
+ Log.d(TAG, "Raw duration: " + rawDuration + ", TIME_UNSET=" + androidx.media3.common.C.TIME_UNSET);
351
+ if (rawDuration != androidx.media3.common.C.TIME_UNSET) {
352
+ duration[0] = rawDuration / 1000.0;
353
+ Log.d(TAG, "Final duration in seconds: " + duration[0]);
354
+ } else {
355
+ Log.d(TAG, "Duration is TIME_UNSET");
356
+ }
357
+ } else {
358
+ Log.d(TAG, "Player not in READY state");
359
+ }
360
+ }
361
+ }
362
+ );
363
+ return duration[0];
364
+ }
365
+ Log.d(TAG, "No players or not prepared for duration");
366
+ return 0;
367
+ }
368
+
369
+ @Override
370
+ public double getCurrentPosition() {
371
+ if (!players.isEmpty() && isPrepared) {
372
+ final double[] position = { 0 };
373
+ owner
374
+ .getActivity()
375
+ .runOnUiThread(
376
+ new Runnable() {
377
+ @Override
378
+ public void run() {
379
+ ExoPlayer player = players.get(playIndex);
380
+ if (player.getPlaybackState() == Player.STATE_READY) {
381
+ long rawPosition = player.getCurrentPosition();
382
+ Log.d(TAG, "Raw position: " + rawPosition);
383
+ position[0] = rawPosition / 1000.0;
384
+ }
385
+ }
386
+ }
387
+ );
388
+ return position[0];
389
+ }
390
+ return 0;
391
+ }
392
+
393
+ @Override
394
+ public void setCurrentTime(double time) throws Exception {
395
+ if (players.isEmpty()) {
396
+ throw new Exception("No ExoPlayer available");
397
+ }
398
+
399
+ final ExoPlayer player = players.get(playIndex);
400
+ owner
401
+ .getActivity()
402
+ .runOnUiThread(
403
+ new Runnable() {
404
+ @Override
405
+ public void run() {
406
+ if (isPrepared) {
407
+ player.seekTo(Math.round(time * 1000));
408
+ } else {
409
+ player.addListener(
410
+ new Player.Listener() {
411
+ @Override
412
+ public void onPlaybackStateChanged(int playbackState) {
413
+ if (playbackState == Player.STATE_READY) {
414
+ isPrepared = true;
415
+ player.seekTo(Math.round(time * 1000));
416
+ }
417
+ }
418
+ }
419
+ );
420
+ }
421
+ }
422
+ }
423
+ );
424
+ }
425
+
426
+ @UnstableApi
427
+ public static void clearCache(Context context) {
72
428
  try {
73
- playInternal(mediaPlayer, time);
429
+ if (cache != null) {
430
+ cache.release();
431
+ cache = null;
432
+ }
433
+ File cacheDir = new File(context.getCacheDir(), "media");
434
+ if (cacheDir.exists()) {
435
+ deleteDir(cacheDir);
436
+ }
74
437
  } catch (Exception e) {
75
- Log.e(TAG, "Error playing after prepare", e);
438
+ Log.e(TAG, "Error clearing audio cache", e);
439
+ }
440
+ }
441
+
442
+ private static boolean deleteDir(File dir) {
443
+ if (dir.isDirectory()) {
444
+ String[] children = dir.list();
445
+ if (children != null) {
446
+ for (String child : children) {
447
+ boolean success = deleteDir(new File(dir, child));
448
+ if (!success) {
449
+ return false;
450
+ }
451
+ }
452
+ }
453
+ }
454
+ return dir.delete();
455
+ }
456
+
457
+ public void setCompletionListener(AudioCompletionListener listener) {
458
+ this.completionListener = listener;
459
+ }
460
+
461
+ protected void notifyCompletion() {
462
+ if (completionListener != null) {
463
+ completionListener.onCompletion(getAssetId());
464
+ }
465
+ }
466
+
467
+ public void playWithFade(Double time) throws Exception {
468
+ if (players.isEmpty()) {
469
+ throw new Exception("No ExoPlayer available");
76
470
  }
77
- });
78
- } else {
79
- playInternal(mediaPlayer, time);
80
- }
81
-
82
- playIndex = (playIndex + 1) % mediaPlayers.size();
83
- }
84
-
85
- private void playInternal(MediaPlayer mediaPlayer, Double time)
86
- throws Exception {
87
- if (time != null) {
88
- mediaPlayer.seekTo((int) (time * 1000));
89
- }
90
- mediaPlayer.start();
91
- }
92
-
93
- @Override
94
- public boolean pause() throws Exception {
95
- boolean wasPlaying = false;
96
- for (MediaPlayer mediaPlayer : mediaPlayers) {
97
- if (mediaPlayer.isPlaying()) {
98
- mediaPlayer.pause();
99
- wasPlaying = true;
100
- }
101
- }
102
- return wasPlaying;
103
- }
104
-
105
- @Override
106
- public void resume() throws Exception {
107
- for (MediaPlayer mediaPlayer : mediaPlayers) {
108
- if (!mediaPlayer.isPlaying()) {
109
- mediaPlayer.start();
110
- }
111
- }
112
- }
113
-
114
- @Override
115
- public void stop() throws Exception {
116
- for (MediaPlayer mediaPlayer : mediaPlayers) {
117
- if (mediaPlayer.isPlaying()) {
118
- mediaPlayer.stop();
119
- }
120
- // Reset the MediaPlayer to make it ready for future playback
121
- mediaPlayer.reset();
122
- initializeMediaPlayer(mediaPlayer);
123
- }
124
- isPrepared = false;
125
- }
126
-
127
- @Override
128
- public void loop() throws Exception {
129
- if (!mediaPlayers.isEmpty()) {
130
- MediaPlayer mediaPlayer = mediaPlayers.get(playIndex);
131
- mediaPlayer.setLooping(true);
132
- mediaPlayer.start();
133
- playIndex = (playIndex + 1) % mediaPlayers.size();
134
- }
135
- }
136
-
137
- @Override
138
- public void unload() throws Exception {
139
- for (MediaPlayer mediaPlayer : mediaPlayers) {
140
- mediaPlayer.release();
141
- }
142
- mediaPlayers.clear();
143
- }
144
-
145
- @Override
146
- public void setVolume(float volume) throws Exception {
147
- this.volume = volume;
148
- for (MediaPlayer mediaPlayer : mediaPlayers) {
149
- mediaPlayer.setVolume(volume, volume);
150
- }
151
- }
152
-
153
- @Override
154
- public boolean isPlaying() throws Exception {
155
- for (MediaPlayer mediaPlayer : mediaPlayers) {
156
- if (mediaPlayer.isPlaying()) {
157
- return true;
158
- }
159
- }
160
- return false;
161
- }
162
-
163
- @Override
164
- public double getDuration() {
165
- if (!mediaPlayers.isEmpty() && isPrepared) {
166
- return mediaPlayers.get(0).getDuration() / 1000.0;
167
- }
168
- return 0;
169
- }
170
-
171
- @Override
172
- public double getCurrentPosition() {
173
- if (!mediaPlayers.isEmpty() && isPrepared) {
174
- return mediaPlayers.get(0).getCurrentPosition() / 1000.0;
175
- }
176
- return 0;
177
- }
471
+
472
+ final ExoPlayer player = players.get(playIndex);
473
+ owner
474
+ .getActivity()
475
+ .runOnUiThread(
476
+ new Runnable() {
477
+ @Override
478
+ public void run() {
479
+ if (!player.isPlaying()) {
480
+ if (time != null) {
481
+ player.seekTo(Math.round(time * 1000));
482
+ }
483
+ player.setVolume(0);
484
+ player.play();
485
+ startCurrentTimeUpdates();
486
+ fadeIn(player);
487
+ }
488
+ }
489
+ }
490
+ );
491
+ }
492
+
493
+ private void fadeIn(final ExoPlayer player) {
494
+ final Handler handler = new Handler(Looper.getMainLooper());
495
+ final Runnable fadeRunnable = new Runnable() {
496
+ float currentVolume = 0;
497
+
498
+ @Override
499
+ public void run() {
500
+ if (currentVolume < initialVolume) {
501
+ currentVolume += FADE_STEP;
502
+ player.setVolume(currentVolume);
503
+ handler.postDelayed(this, FADE_DELAY_MS);
504
+ }
505
+ }
506
+ };
507
+ handler.post(fadeRunnable);
508
+ }
509
+
510
+ public void stopWithFade() throws Exception {
511
+ if (players.isEmpty()) {
512
+ return;
513
+ }
514
+
515
+ final ExoPlayer player = players.get(playIndex);
516
+ owner
517
+ .getActivity()
518
+ .runOnUiThread(
519
+ new Runnable() {
520
+ @Override
521
+ public void run() {
522
+ if (player.isPlaying()) {
523
+ fadeOut(player);
524
+ }
525
+ }
526
+ }
527
+ );
528
+ }
529
+
530
+ private void fadeOut(final ExoPlayer player) {
531
+ final Handler handler = new Handler(Looper.getMainLooper());
532
+ final Runnable fadeRunnable = new Runnable() {
533
+ float currentVolume = player.getVolume();
534
+
535
+ @Override
536
+ public void run() {
537
+ if (currentVolume > FADE_STEP) {
538
+ currentVolume -= FADE_STEP;
539
+ player.setVolume(currentVolume);
540
+ handler.postDelayed(this, FADE_DELAY_MS);
541
+ } else {
542
+ player.setVolume(0);
543
+ player.stop();
544
+ }
545
+ }
546
+ };
547
+ handler.post(fadeRunnable);
548
+ }
549
+
550
+ @Override
551
+ protected void startCurrentTimeUpdates() {
552
+ Log.d(TAG, "Starting timer updates in RemoteAudioAsset");
553
+ if (currentTimeHandler == null) {
554
+ currentTimeHandler = new Handler(Looper.getMainLooper());
555
+ }
556
+
557
+ // Wait for player to be truly ready
558
+ currentTimeHandler.postDelayed(
559
+ new Runnable() {
560
+ @Override
561
+ public void run() {
562
+ if (!players.isEmpty()) {
563
+ ExoPlayer player = players.get(playIndex);
564
+ if (player.getPlaybackState() == Player.STATE_READY) {
565
+ startTimeUpdateLoop();
566
+ } else {
567
+ // Check again in 100ms
568
+ currentTimeHandler.postDelayed(this, 100);
569
+ }
570
+ }
571
+ }
572
+ },
573
+ 100
574
+ );
575
+ }
576
+
577
+ private void startTimeUpdateLoop() {
578
+ currentTimeRunnable = new Runnable() {
579
+ @Override
580
+ public void run() {
581
+ try {
582
+ if (!players.isEmpty()) {
583
+ ExoPlayer player = players.get(playIndex);
584
+ if (player.getPlaybackState() == Player.STATE_READY && player.isPlaying()) {
585
+ double currentTime = player.getCurrentPosition() / 1000.0; // Get time directly
586
+ Log.d(TAG, "Timer update: currentTime = " + currentTime);
587
+ owner.notifyCurrentTime(assetId, currentTime);
588
+ currentTimeHandler.postDelayed(this, 100);
589
+ return;
590
+ }
591
+ }
592
+ Log.d(TAG, "Stopping timer - not playing or not ready");
593
+ stopCurrentTimeUpdates();
594
+ } catch (Exception e) {
595
+ Log.e(TAG, "Error getting current time", e);
596
+ stopCurrentTimeUpdates();
597
+ }
598
+ }
599
+ };
600
+ currentTimeHandler.post(currentTimeRunnable);
601
+ }
602
+
603
+ @Override
604
+ void stopCurrentTimeUpdates() {
605
+ Log.d(TAG, "Stopping timer updates in RemoteAudioAsset");
606
+ if (currentTimeHandler != null) {
607
+ currentTimeHandler.removeCallbacks(currentTimeRunnable);
608
+ currentTimeHandler = null;
609
+ }
610
+ }
178
611
  }