@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.
- package/README.md +56 -8
- package/android/build.gradle +14 -5
- package/android/src/main/java/ee/forgr/audio/AudioAsset.java +276 -133
- package/android/src/main/java/ee/forgr/audio/AudioCompletionListener.java +1 -1
- package/android/src/main/java/ee/forgr/audio/AudioDispatcher.java +168 -182
- package/android/src/main/java/ee/forgr/audio/Constant.java +20 -21
- package/android/src/main/java/ee/forgr/audio/NativeAudio.java +596 -506
- package/android/src/main/java/ee/forgr/audio/RemoteAudioAsset.java +599 -166
- package/android/src/main/java/ee/forgr/audio/StreamAudioAsset.java +499 -0
- package/dist/docs.json +96 -3
- package/dist/esm/definitions.d.ts +33 -2
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +4 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +4 -3
- package/dist/esm/web.js +23 -20
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +22 -19
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +22 -19
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/AudioAsset.swift +200 -94
- package/ios/Plugin/Plugin.swift +55 -49
- package/ios/Plugin/RemoteAudioAsset.swift +217 -62
- package/package.json +18 -20
@@ -1,178 +1,611 @@
|
|
1
1
|
package ee.forgr.audio;
|
2
2
|
|
3
|
-
import android.
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
}
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
}
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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
|
}
|