@capgo/native-audio 7.1.7 → 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.
@@ -20,8 +20,11 @@ import android.content.res.AssetManager;
20
20
  import android.media.AudioManager;
21
21
  import android.net.Uri;
22
22
  import android.os.Build;
23
+ import android.os.Handler;
24
+ import android.os.Looper;
23
25
  import android.os.ParcelFileDescriptor;
24
26
  import android.util.Log;
27
+ import androidx.media3.common.util.UnstableApi;
25
28
  import com.getcapacitor.JSObject;
26
29
  import com.getcapacitor.Plugin;
27
30
  import com.getcapacitor.PluginCall;
@@ -36,584 +39,671 @@ import java.net.URI;
36
39
  import java.net.URL;
37
40
  import java.util.ArrayList;
38
41
  import java.util.HashMap;
42
+ import java.util.Map;
39
43
 
44
+ @UnstableApi
40
45
  @CapacitorPlugin(
41
- permissions = {
42
- @Permission(strings = { Manifest.permission.MODIFY_AUDIO_SETTINGS }),
43
- @Permission(strings = { Manifest.permission.WRITE_EXTERNAL_STORAGE }),
44
- @Permission(strings = { Manifest.permission.READ_PHONE_STATE }),
45
- }
46
- )
47
- public class NativeAudio
48
- extends Plugin
49
- implements AudioManager.OnAudioFocusChangeListener {
50
-
51
- public static final String TAG = "NativeAudio";
52
-
53
- private static HashMap<String, AudioAsset> audioAssetList;
54
- private static ArrayList<AudioAsset> resumeList;
55
- private AudioManager audioManager;
56
-
57
- @Override
58
- public void load() {
59
- super.load();
60
-
61
- this.audioManager = (AudioManager) this.getActivity()
62
- .getSystemService(Context.AUDIO_SERVICE);
63
-
64
- audioAssetList = new HashMap<>();
65
- }
66
-
67
- @Override
68
- public void onAudioFocusChange(int focusChange) {
69
- if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {} else if (
70
- focusChange == AudioManager.AUDIOFOCUS_GAIN
71
- ) {} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {}
72
- }
73
-
74
- @Override
75
- protected void handleOnPause() {
76
- super.handleOnPause();
77
-
78
- try {
79
- if (audioAssetList != null) {
80
- for (HashMap.Entry<
81
- String,
82
- AudioAsset
83
- > entry : audioAssetList.entrySet()) {
84
- AudioAsset audio = entry.getValue();
85
-
86
- if (audio != null) {
87
- boolean wasPlaying = audio.pause();
88
-
89
- if (wasPlaying) {
90
- resumeList.add(audio);
91
- }
92
- }
93
- }
94
- }
95
- } catch (Exception ex) {
96
- Log.d(
97
- TAG,
98
- "Exception caught while listening for handleOnPause: " +
99
- ex.getLocalizedMessage()
100
- );
46
+ permissions = {
47
+ @Permission(strings = { Manifest.permission.MODIFY_AUDIO_SETTINGS }),
48
+ @Permission(strings = { Manifest.permission.WRITE_EXTERNAL_STORAGE }),
49
+ @Permission(strings = { Manifest.permission.READ_PHONE_STATE })
101
50
  }
102
- }
51
+ )
52
+ public class NativeAudio extends Plugin implements AudioManager.OnAudioFocusChangeListener {
53
+
54
+ public static final String TAG = "NativeAudio";
55
+
56
+ private static HashMap<String, AudioAsset> audioAssetList = new HashMap<>();
57
+ private static ArrayList<AudioAsset> resumeList;
58
+ private AudioManager audioManager;
59
+ private boolean fadeMusic = false;
60
+
61
+ private final Map<String, PluginCall> pendingDurationCalls = new HashMap<>();
62
+
63
+ @Override
64
+ public void load() {
65
+ super.load();
103
66
 
104
- @Override
105
- protected void handleOnResume() {
106
- super.handleOnResume();
67
+ this.audioManager = (AudioManager) this.getActivity().getSystemService(Context.AUDIO_SERVICE);
107
68
 
108
- try {
109
- if (resumeList != null) {
110
- while (!resumeList.isEmpty()) {
111
- AudioAsset audio = resumeList.remove(0);
69
+ audioAssetList = new HashMap<>();
70
+ }
112
71
 
113
- if (audio != null) {
114
- audio.resume();
115
- }
72
+ @Override
73
+ public void onAudioFocusChange(int focusChange) {
74
+ try {
75
+ if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
76
+ // Pause playback - temporary loss
77
+ for (AudioAsset audio : audioAssetList.values()) {
78
+ if (audio.isPlaying()) {
79
+ audio.pause();
80
+ resumeList.add(audio);
81
+ }
82
+ }
83
+ } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
84
+ // Resume playback
85
+ if (resumeList != null) {
86
+ while (!resumeList.isEmpty()) {
87
+ AudioAsset audio = resumeList.remove(0);
88
+ audio.resume();
89
+ }
90
+ }
91
+ } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
92
+ // Stop playback - permanent loss
93
+ for (AudioAsset audio : audioAssetList.values()) {
94
+ audio.stop();
95
+ }
96
+ audioManager.abandonAudioFocus(this);
97
+ }
98
+ } catch (Exception ex) {
99
+ Log.e(TAG, "Error handling audio focus change", ex);
116
100
  }
117
- }
118
- } catch (Exception ex) {
119
- Log.d(
120
- TAG,
121
- "Exception caught while listening for handleOnResume: " +
122
- ex.getLocalizedMessage()
123
- );
124
101
  }
125
- }
126
102
 
127
- @PluginMethod
128
- public void configure(PluginCall call) {
129
- initSoundPool();
103
+ @Override
104
+ protected void handleOnPause() {
105
+ super.handleOnPause();
130
106
 
131
- if (this.audioManager == null) {
132
- call.resolve();
133
- return;
107
+ try {
108
+ if (audioAssetList != null) {
109
+ for (HashMap.Entry<String, AudioAsset> entry : audioAssetList.entrySet()) {
110
+ AudioAsset audio = entry.getValue();
111
+
112
+ if (audio != null) {
113
+ boolean wasPlaying = audio.pause();
114
+
115
+ if (wasPlaying) {
116
+ resumeList.add(audio);
117
+ }
118
+ }
119
+ }
120
+ }
121
+ } catch (Exception ex) {
122
+ Log.d(TAG, "Exception caught while listening for handleOnPause: " + ex.getLocalizedMessage());
123
+ }
134
124
  }
135
125
 
136
- if (Boolean.TRUE.equals(call.getBoolean(OPT_FOCUS_AUDIO, false))) {
137
- this.audioManager.requestAudioFocus(
138
- this,
139
- AudioManager.STREAM_MUSIC,
140
- AudioManager.AUDIOFOCUS_GAIN
141
- );
142
- } else {
143
- this.audioManager.abandonAudioFocus(this);
144
- }
145
- call.resolve();
146
- }
126
+ @Override
127
+ protected void handleOnResume() {
128
+ super.handleOnResume();
147
129
 
148
- @PluginMethod
149
- public void isPreloaded(final PluginCall call) {
150
- new Thread(
151
- new Runnable() {
152
- @Override
153
- public void run() {
154
- initSoundPool();
130
+ try {
131
+ if (resumeList != null) {
132
+ while (!resumeList.isEmpty()) {
133
+ AudioAsset audio = resumeList.remove(0);
134
+
135
+ if (audio != null) {
136
+ audio.resume();
137
+ }
138
+ }
139
+ }
140
+ } catch (Exception ex) {
141
+ Log.d(TAG, "Exception caught while listening for handleOnResume: " + ex.getLocalizedMessage());
142
+ }
143
+ }
155
144
 
156
- String audioId = call.getString(ASSET_ID);
145
+ @PluginMethod
146
+ public void configure(PluginCall call) {
147
+ initSoundPool();
157
148
 
158
- if (!isStringValid(audioId)) {
159
- call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
149
+ if (this.audioManager == null) {
150
+ call.resolve();
160
151
  return;
161
- }
162
- call.resolve(
163
- new JSObject().put("found", audioAssetList.containsKey(audioId))
164
- );
165
152
  }
166
- }
167
- ).start();
168
- }
169
-
170
- @PluginMethod
171
- public void preload(final PluginCall call) {
172
- new Thread(
173
- new Runnable() {
174
- @Override
175
- public void run() {
176
- preloadAsset(call);
177
- }
178
- }
179
- ).start();
180
- }
181
-
182
- @PluginMethod
183
- public void play(final PluginCall call) {
184
- this.getActivity()
185
- .runOnUiThread(
186
- new Runnable() {
187
- @Override
188
- public void run() {
189
- playOrLoop("play", call);
190
- }
153
+
154
+ boolean focus = call.getBoolean(OPT_FOCUS_AUDIO, false);
155
+ boolean background = call.getBoolean("background", false);
156
+ this.fadeMusic = call.getBoolean("fade", false);
157
+
158
+ try {
159
+ if (focus) {
160
+ // Request audio focus for playback with ducking
161
+ int result =
162
+ this.audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK); // Allow other audio to play quietly
163
+ } else {
164
+ this.audioManager.abandonAudioFocus(this);
165
+ }
166
+
167
+ if (background) {
168
+ // Set playback to continue in background
169
+ this.audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
170
+ } else {
171
+ this.audioManager.setMode(AudioManager.MODE_NORMAL);
172
+ }
173
+ } catch (Exception ex) {
174
+ Log.e(TAG, "Error configuring audio", ex);
191
175
  }
192
- );
193
- }
194
176
 
195
- @PluginMethod
196
- public void getCurrentTime(final PluginCall call) {
197
- try {
198
- initSoundPool();
177
+ call.resolve();
178
+ }
199
179
 
200
- String audioId = call.getString(ASSET_ID);
180
+ @PluginMethod
181
+ public void isPreloaded(final PluginCall call) {
182
+ new Thread(
183
+ new Runnable() {
184
+ @Override
185
+ public void run() {
186
+ initSoundPool();
187
+
188
+ String audioId = call.getString(ASSET_ID);
189
+
190
+ if (!isStringValid(audioId)) {
191
+ call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
192
+ return;
193
+ }
194
+ call.resolve(new JSObject().put("found", audioAssetList.containsKey(audioId)));
195
+ }
196
+ }
197
+ ).start();
198
+ }
201
199
 
202
- if (!isStringValid(audioId)) {
203
- call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
204
- return;
205
- }
200
+ @PluginMethod
201
+ public void preload(final PluginCall call) {
202
+ this.getActivity()
203
+ .runOnUiThread(
204
+ new Runnable() {
205
+ @Override
206
+ public void run() {
207
+ preloadAsset(call);
208
+ }
209
+ }
210
+ );
211
+ }
206
212
 
207
- if (audioAssetList.containsKey(audioId)) {
208
- AudioAsset asset = audioAssetList.get(audioId);
209
- if (asset != null) {
210
- call.resolve(
211
- new JSObject().put("currentTime", asset.getCurrentPosition())
212
- );
213
- }
214
- } else {
215
- call.reject(ERROR_AUDIO_ASSET_MISSING + " - " + audioId);
216
- }
217
- } catch (Exception ex) {
218
- call.reject(ex.getMessage());
213
+ @PluginMethod
214
+ public void play(final PluginCall call) {
215
+ this.getActivity()
216
+ .runOnUiThread(
217
+ new Runnable() {
218
+ @Override
219
+ public void run() {
220
+ playOrLoop("play", call);
221
+ }
222
+ }
223
+ );
219
224
  }
220
- }
221
225
 
222
- @PluginMethod
223
- public void getDuration(final PluginCall call) {
224
- try {
225
- initSoundPool();
226
+ @PluginMethod
227
+ public void getCurrentTime(final PluginCall call) {
228
+ try {
229
+ initSoundPool();
226
230
 
227
- String audioId = call.getString(ASSET_ID);
231
+ String audioId = call.getString(ASSET_ID);
228
232
 
229
- if (!isStringValid(audioId)) {
230
- call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
231
- return;
232
- }
233
+ if (!isStringValid(audioId)) {
234
+ call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
235
+ return;
236
+ }
233
237
 
234
- if (audioAssetList.containsKey(audioId)) {
235
- AudioAsset asset = audioAssetList.get(audioId);
236
- if (asset != null) {
237
- call.resolve(new JSObject().put("duration", asset.getDuration()));
238
+ if (audioAssetList.containsKey(audioId)) {
239
+ AudioAsset asset = audioAssetList.get(audioId);
240
+ if (asset != null) {
241
+ call.resolve(new JSObject().put("currentTime", asset.getCurrentPosition()));
242
+ }
243
+ } else {
244
+ call.reject(ERROR_AUDIO_ASSET_MISSING + " - " + audioId);
245
+ }
246
+ } catch (Exception ex) {
247
+ call.reject(ex.getMessage());
238
248
  }
239
- } else {
240
- call.reject(ERROR_AUDIO_ASSET_MISSING + " - " + audioId);
241
- }
242
- } catch (Exception ex) {
243
- call.reject(ex.getMessage());
244
249
  }
245
- }
246
-
247
- @PluginMethod
248
- public void loop(final PluginCall call) {
249
- this.getActivity()
250
- .runOnUiThread(
251
- new Runnable() {
252
- @Override
253
- public void run() {
254
- playOrLoop("loop", call);
255
- }
256
- }
257
- );
258
- }
259
250
 
260
- @PluginMethod
261
- public void pause(PluginCall call) {
262
- try {
263
- initSoundPool();
264
- String audioId = call.getString(ASSET_ID);
251
+ @PluginMethod
252
+ public void getDuration(PluginCall call) {
253
+ try {
254
+ String audioId = call.getString(ASSET_ID);
255
+ if (!isStringValid(audioId)) {
256
+ call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
257
+ return;
258
+ }
265
259
 
266
- if (audioAssetList.containsKey(audioId)) {
267
- AudioAsset asset = audioAssetList.get(audioId);
268
- if (asset != null) {
269
- boolean wasPlaying = asset.pause();
270
-
271
- if (wasPlaying) {
272
- resumeList.add(asset);
273
- }
274
- call.resolve();
275
- } else {
276
- call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
260
+ if (audioAssetList.containsKey(audioId)) {
261
+ AudioAsset asset = audioAssetList.get(audioId);
262
+ if (asset != null) {
263
+ double duration = asset.getDuration();
264
+ if (duration > 0) {
265
+ JSObject ret = new JSObject();
266
+ ret.put("duration", duration);
267
+ call.resolve(ret);
268
+ } else {
269
+ // Save the call to resolve it later when duration is available
270
+ saveDurationCall(audioId, call);
271
+ }
272
+ } else {
273
+ call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
274
+ }
275
+ } else {
276
+ call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
277
+ }
278
+ } catch (Exception ex) {
279
+ call.reject(ex.getMessage());
277
280
  }
278
- } else {
279
- call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
280
- }
281
- } catch (Exception ex) {
282
- call.reject(ex.getMessage());
283
281
  }
284
- }
285
282
 
286
- @PluginMethod
287
- public void resume(PluginCall call) {
288
- try {
289
- initSoundPool();
290
- String audioId = call.getString(ASSET_ID);
283
+ @PluginMethod
284
+ public void loop(final PluginCall call) {
285
+ this.getActivity()
286
+ .runOnUiThread(
287
+ new Runnable() {
288
+ @Override
289
+ public void run() {
290
+ playOrLoop("loop", call);
291
+ }
292
+ }
293
+ );
294
+ }
291
295
 
292
- if (audioAssetList.containsKey(audioId)) {
293
- AudioAsset asset = audioAssetList.get(audioId);
294
- if (asset != null) {
295
- asset.resume();
296
- resumeList.add(asset);
297
- call.resolve();
298
- } else {
299
- call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
296
+ @PluginMethod
297
+ public void pause(PluginCall call) {
298
+ try {
299
+ initSoundPool();
300
+ String audioId = call.getString(ASSET_ID);
301
+
302
+ if (audioAssetList.containsKey(audioId)) {
303
+ AudioAsset asset = audioAssetList.get(audioId);
304
+ if (asset != null) {
305
+ boolean wasPlaying = asset.pause();
306
+
307
+ if (wasPlaying) {
308
+ resumeList.add(asset);
309
+ }
310
+ call.resolve();
311
+ } else {
312
+ call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
313
+ }
314
+ } else {
315
+ call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
316
+ }
317
+ } catch (Exception ex) {
318
+ call.reject(ex.getMessage());
300
319
  }
301
- } else {
302
- call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
303
- }
304
- } catch (Exception ex) {
305
- call.reject(ex.getMessage());
306
320
  }
307
- }
308
321
 
309
- @PluginMethod
310
- public void stop(PluginCall call) {
311
- try {
312
- initSoundPool();
313
- String audioId = call.getString(ASSET_ID);
322
+ @PluginMethod
323
+ public void resume(PluginCall call) {
324
+ try {
325
+ initSoundPool();
326
+ String audioId = call.getString(ASSET_ID);
327
+
328
+ if (audioAssetList.containsKey(audioId)) {
329
+ AudioAsset asset = audioAssetList.get(audioId);
330
+ if (asset != null) {
331
+ asset.resume();
332
+ resumeList.add(asset);
333
+ call.resolve();
334
+ } else {
335
+ call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
336
+ }
337
+ } else {
338
+ call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
339
+ }
340
+ } catch (Exception ex) {
341
+ call.reject(ex.getMessage());
342
+ }
343
+ }
314
344
 
315
- if (audioAssetList.containsKey(audioId)) {
316
- AudioAsset asset = audioAssetList.get(audioId);
317
- if (asset != null) {
318
- asset.stop();
319
- call.resolve();
320
- } else {
321
- call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
345
+ @PluginMethod
346
+ public void stop(final PluginCall call) {
347
+ this.getActivity()
348
+ .runOnUiThread(
349
+ new Runnable() {
350
+ @Override
351
+ public void run() {
352
+ try {
353
+ String audioId = call.getString(ASSET_ID);
354
+ if (!isStringValid(audioId)) {
355
+ call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
356
+ return;
357
+ }
358
+ stopAudio(audioId);
359
+ call.resolve();
360
+ } catch (Exception ex) {
361
+ call.reject(ex.getMessage());
362
+ }
363
+ }
364
+ }
365
+ );
366
+ }
367
+
368
+ @PluginMethod
369
+ public void unload(PluginCall call) {
370
+ try {
371
+ initSoundPool();
372
+ new JSObject();
373
+ JSObject status;
374
+
375
+ if (isStringValid(call.getString(ASSET_ID))) {
376
+ String audioId = call.getString(ASSET_ID);
377
+
378
+ if (audioAssetList.containsKey(audioId)) {
379
+ AudioAsset asset = audioAssetList.get(audioId);
380
+ if (asset != null) {
381
+ asset.unload();
382
+ audioAssetList.remove(audioId);
383
+ call.resolve();
384
+ } else {
385
+ call.reject(ERROR_AUDIO_ASSET_MISSING + " - " + audioId);
386
+ }
387
+ } else {
388
+ call.reject(ERROR_AUDIO_ASSET_MISSING + " - " + audioId);
389
+ }
390
+ } else {
391
+ call.reject(ERROR_AUDIO_ID_MISSING);
392
+ }
393
+ } catch (Exception ex) {
394
+ call.reject(ex.getMessage());
322
395
  }
323
- } else {
324
- call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
325
- }
326
- } catch (Exception ex) {
327
- call.reject(ex.getMessage());
328
396
  }
329
- }
330
-
331
- @PluginMethod
332
- public void unload(PluginCall call) {
333
- try {
334
- initSoundPool();
335
- new JSObject();
336
- JSObject status;
337
-
338
- if (isStringValid(call.getString(ASSET_ID))) {
339
- String audioId = call.getString(ASSET_ID);
340
-
341
- if (audioAssetList.containsKey(audioId)) {
342
- AudioAsset asset = audioAssetList.get(audioId);
343
- if (asset != null) {
344
- asset.unload();
345
- audioAssetList.remove(audioId);
346
- call.resolve();
347
- } else {
348
- call.reject(ERROR_AUDIO_ASSET_MISSING + " - " + audioId);
349
- }
350
- } else {
351
- call.reject(ERROR_AUDIO_ASSET_MISSING + " - " + audioId);
397
+
398
+ @PluginMethod
399
+ public void setVolume(PluginCall call) {
400
+ try {
401
+ initSoundPool();
402
+
403
+ String audioId = call.getString(ASSET_ID);
404
+ float volume = call.getFloat(VOLUME, 1F);
405
+
406
+ if (audioAssetList.containsKey(audioId)) {
407
+ AudioAsset asset = audioAssetList.get(audioId);
408
+ if (asset != null) {
409
+ asset.setVolume(volume);
410
+ call.resolve();
411
+ } else {
412
+ call.reject(ERROR_AUDIO_ASSET_MISSING);
413
+ }
414
+ } else {
415
+ call.reject(ERROR_AUDIO_ASSET_MISSING);
416
+ }
417
+ } catch (Exception ex) {
418
+ call.reject(ex.getMessage());
419
+ }
420
+ }
421
+
422
+ @PluginMethod
423
+ public void setRate(PluginCall call) {
424
+ try {
425
+ initSoundPool();
426
+
427
+ String audioId = call.getString(ASSET_ID);
428
+ float rate = call.getFloat(RATE, 1F);
429
+
430
+ if (audioAssetList.containsKey(audioId)) {
431
+ AudioAsset asset = audioAssetList.get(audioId);
432
+ if (asset != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
433
+ asset.setRate(rate);
434
+ }
435
+ call.resolve();
436
+ } else {
437
+ call.reject(ERROR_AUDIO_ASSET_MISSING);
438
+ }
439
+ } catch (Exception ex) {
440
+ call.reject(ex.getMessage());
352
441
  }
353
- } else {
354
- call.reject(ERROR_AUDIO_ID_MISSING);
355
- }
356
- } catch (Exception ex) {
357
- call.reject(ex.getMessage());
358
442
  }
359
- }
360
443
 
361
- @PluginMethod
362
- public void setVolume(PluginCall call) {
363
- try {
364
- initSoundPool();
444
+ @PluginMethod
445
+ public void isPlaying(final PluginCall call) {
446
+ try {
447
+ initSoundPool();
365
448
 
366
- String audioId = call.getString(ASSET_ID);
367
- float volume = call.getFloat(VOLUME, 1F);
449
+ String audioId = call.getString(ASSET_ID);
368
450
 
369
- if (audioAssetList.containsKey(audioId)) {
370
- AudioAsset asset = audioAssetList.get(audioId);
371
- if (asset != null) {
372
- asset.setVolume(volume);
373
- call.resolve();
374
- } else {
375
- call.reject(ERROR_AUDIO_ASSET_MISSING);
451
+ if (!isStringValid(audioId)) {
452
+ call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
453
+ return;
454
+ }
455
+
456
+ if (audioAssetList.containsKey(audioId)) {
457
+ AudioAsset asset = audioAssetList.get(audioId);
458
+ if (asset != null) {
459
+ call.resolve(new JSObject().put("isPlaying", asset.isPlaying()));
460
+ } else {
461
+ call.reject(ERROR_AUDIO_ASSET_MISSING + " - " + audioId);
462
+ }
463
+ } else {
464
+ call.reject(ERROR_AUDIO_ASSET_MISSING + " - " + audioId);
465
+ }
466
+ } catch (Exception ex) {
467
+ call.reject(ex.getMessage());
376
468
  }
377
- } else {
378
- call.reject(ERROR_AUDIO_ASSET_MISSING);
379
- }
380
- } catch (Exception ex) {
381
- call.reject(ex.getMessage());
382
469
  }
383
- }
384
470
 
385
- @PluginMethod
386
- public void setRate(PluginCall call) {
387
- try {
388
- initSoundPool();
471
+ @PluginMethod
472
+ public void clearCache(PluginCall call) {
473
+ RemoteAudioAsset.clearCache(getContext());
474
+ call.resolve();
475
+ }
476
+
477
+ @PluginMethod
478
+ public void setCurrentTime(final PluginCall call) {
479
+ try {
480
+ initSoundPool();
389
481
 
390
- String audioId = call.getString(ASSET_ID);
391
- float rate = call.getFloat(RATE, 1F);
482
+ String audioId = call.getString(ASSET_ID);
483
+ Double time = call.getDouble("time", 0.0);
392
484
 
393
- if (audioAssetList.containsKey(audioId)) {
394
- AudioAsset asset = audioAssetList.get(audioId);
395
- if (asset != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
396
- asset.setRate(rate);
485
+ if (!isStringValid(audioId)) {
486
+ call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
487
+ return;
488
+ }
489
+
490
+ if (audioAssetList.containsKey(audioId)) {
491
+ AudioAsset asset = audioAssetList.get(audioId);
492
+ if (asset != null) {
493
+ this.getActivity()
494
+ .runOnUiThread(
495
+ new Runnable() {
496
+ @Override
497
+ public void run() {
498
+ try {
499
+ asset.setCurrentTime(time);
500
+ call.resolve();
501
+ } catch (Exception e) {
502
+ call.reject("Error setting current time: " + e.getMessage());
503
+ }
504
+ }
505
+ }
506
+ );
507
+ } else {
508
+ call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
509
+ }
510
+ } else {
511
+ call.reject(ERROR_ASSET_NOT_LOADED + " - " + audioId);
512
+ }
513
+ } catch (Exception ex) {
514
+ call.reject(ex.getMessage());
397
515
  }
398
- call.resolve();
399
- } else {
400
- call.reject(ERROR_AUDIO_ASSET_MISSING);
401
- }
402
- } catch (Exception ex) {
403
- call.reject(ex.getMessage());
404
516
  }
405
- }
406
517
 
407
- @PluginMethod
408
- public void isPlaying(final PluginCall call) {
409
- try {
410
- initSoundPool();
518
+ public void dispatchComplete(String assetId) {
519
+ JSObject ret = new JSObject();
520
+ ret.put("assetId", assetId);
521
+ notifyListeners("complete", ret);
522
+ }
411
523
 
412
- String audioId = call.getString(ASSET_ID);
524
+ public void notifyCurrentTime(String assetId, double currentTime) {
525
+ // Round to nearest 100ms
526
+ double roundedTime = Math.round(currentTime * 10.0) / 10.0;
527
+ JSObject ret = new JSObject();
528
+ ret.put("currentTime", roundedTime);
529
+ ret.put("assetId", assetId);
530
+ notifyListeners("currentTime", ret);
531
+ }
413
532
 
414
- if (!isStringValid(audioId)) {
415
- call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
416
- return;
417
- }
533
+ private void preloadAsset(PluginCall call) {
534
+ float volume = 1F;
535
+ int audioChannelNum = 1;
536
+ JSObject status = new JSObject();
537
+ status.put("STATUS", "OK");
418
538
 
419
- if (audioAssetList.containsKey(audioId)) {
420
- AudioAsset asset = audioAssetList.get(audioId);
421
- if (asset != null) {
422
- call.resolve(new JSObject().put("isPlaying", asset.isPlaying()));
423
- } else {
424
- call.reject(ERROR_AUDIO_ASSET_MISSING + " - " + audioId);
539
+ try {
540
+ initSoundPool();
541
+
542
+ String audioId = call.getString(ASSET_ID);
543
+ if (!isStringValid(audioId)) {
544
+ call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
545
+ return;
546
+ }
547
+
548
+ String assetPath = call.getString(ASSET_PATH);
549
+ if (!isStringValid(assetPath)) {
550
+ call.reject(ERROR_ASSET_PATH_MISSING + " - " + audioId + " - " + assetPath);
551
+ return;
552
+ }
553
+
554
+ boolean isLocalUrl = call.getBoolean("isUrl", false);
555
+ boolean isComplex = call.getBoolean("isComplex", false);
556
+
557
+ Log.d("AudioPlugin", "Debug: audioId = " + audioId + ", assetPath = " + assetPath + ", isLocalUrl = " + isLocalUrl);
558
+
559
+ if (audioAssetList.containsKey(audioId)) {
560
+ call.reject(ERROR_AUDIO_EXISTS + " - " + audioId);
561
+ return;
562
+ }
563
+
564
+ if (isComplex) {
565
+ volume = call.getFloat(VOLUME, 1F);
566
+ audioChannelNum = call.getInt(AUDIO_CHANNEL_NUM, 1);
567
+ }
568
+
569
+ if (isLocalUrl) {
570
+ try {
571
+ Uri uri = Uri.parse(assetPath);
572
+ if (uri.getScheme() != null && (uri.getScheme().equals("http") || uri.getScheme().equals("https"))) {
573
+ // Remote URL
574
+ Log.d("AudioPlugin", "Debug: Remote URL detected: " + uri.toString());
575
+ if (assetPath.endsWith(".m3u8")) {
576
+ // HLS Stream - resolve immediately since it's a stream
577
+ StreamAudioAsset streamAudioAsset = new StreamAudioAsset(this, audioId, uri, volume);
578
+ audioAssetList.put(audioId, streamAudioAsset);
579
+ call.resolve(status);
580
+ } else {
581
+ // Regular remote audio
582
+ RemoteAudioAsset remoteAudioAsset = new RemoteAudioAsset(this, audioId, uri, audioChannelNum, volume);
583
+ remoteAudioAsset.setCompletionListener(this::dispatchComplete);
584
+ audioAssetList.put(audioId, remoteAudioAsset);
585
+ call.resolve(status);
586
+ }
587
+ } else if (uri.getScheme() != null && uri.getScheme().equals("file")) {
588
+ // Local file URL
589
+ Log.d("AudioPlugin", "Debug: Local file URL detected");
590
+ File file = new File(uri.getPath());
591
+ if (!file.exists()) {
592
+ Log.e("AudioPlugin", "Error: File does not exist - " + file.getAbsolutePath());
593
+ call.reject(ERROR_ASSET_PATH_MISSING + " - " + assetPath);
594
+ return;
595
+ }
596
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
597
+ AssetFileDescriptor afd = new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
598
+ AudioAsset asset = new AudioAsset(this, audioId, afd, audioChannelNum, volume);
599
+ asset.setCompletionListener(this::dispatchComplete);
600
+ audioAssetList.put(audioId, asset);
601
+ call.resolve(status);
602
+ } else {
603
+ throw new IllegalArgumentException("Invalid URL scheme: " + uri.getScheme());
604
+ }
605
+ } catch (Exception e) {
606
+ Log.e("AudioPlugin", "Error handling URL", e);
607
+ call.reject("Error handling URL: " + e.getMessage());
608
+ }
609
+ } else {
610
+ // Handle asset in public folder
611
+ Log.d("AudioPlugin", "Debug: Handling asset in public folder");
612
+ if (!assetPath.startsWith("public/")) {
613
+ assetPath = "public/" + assetPath;
614
+ }
615
+ try {
616
+ Context ctx = getContext().getApplicationContext();
617
+ AssetManager am = ctx.getResources().getAssets();
618
+ AssetFileDescriptor assetFileDescriptor = am.openFd(assetPath);
619
+ AudioAsset asset = new AudioAsset(this, audioId, assetFileDescriptor, audioChannelNum, volume);
620
+ audioAssetList.put(audioId, asset);
621
+ call.resolve(status);
622
+ } catch (IOException e) {
623
+ Log.e("AudioPlugin", "Error opening asset: " + assetPath, e);
624
+ call.reject(ERROR_ASSET_PATH_MISSING + " - " + assetPath);
625
+ }
626
+ }
627
+ } catch (Exception ex) {
628
+ Log.e("AudioPlugin", "Error in preloadAsset", ex);
629
+ call.reject("Error in preloadAsset: " + ex.getMessage());
425
630
  }
426
- } else {
427
- call.reject(ERROR_AUDIO_ASSET_MISSING + " - " + audioId);
428
- }
429
- } catch (Exception ex) {
430
- call.reject(ex.getMessage());
431
631
  }
432
- }
433
-
434
- public void dispatchComplete(String assetId) {
435
- JSObject ret = new JSObject();
436
- ret.put("assetId", assetId);
437
- notifyListeners("complete", ret);
438
- }
439
-
440
- private void preloadAsset(PluginCall call) {
441
- float volume = 1F;
442
- int audioChannelNum = 1;
443
- JSObject status = new JSObject();
444
- status.put("STATUS", "OK");
445
-
446
- try {
447
- initSoundPool();
448
-
449
- String audioId = call.getString(ASSET_ID);
450
- if (!isStringValid(audioId)) {
451
- call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
452
- return;
453
- }
454
-
455
- String assetPath = call.getString(ASSET_PATH);
456
- if (!isStringValid(assetPath)) {
457
- call.reject(
458
- ERROR_ASSET_PATH_MISSING + " - " + audioId + " - " + assetPath
459
- );
460
- return;
461
- }
462
-
463
- boolean isLocalUrl = call.getBoolean("isUrl", false);
464
- boolean isComplex = call.getBoolean("isComplex", false);
465
-
466
- Log.d(
467
- "AudioPlugin",
468
- "Debug: audioId = " +
469
- audioId +
470
- ", assetPath = " +
471
- assetPath +
472
- ", isLocalUrl = " +
473
- isLocalUrl
474
- );
475
-
476
- if (audioAssetList.containsKey(audioId)) {
477
- call.reject(ERROR_AUDIO_EXISTS + " - " + audioId);
478
- return;
479
- }
480
-
481
- if (isComplex) {
482
- volume = call.getFloat(VOLUME, 1F);
483
- audioChannelNum = call.getInt(AUDIO_CHANNEL_NUM, 1);
484
- }
485
-
486
- if (isLocalUrl) {
487
- // Handle URL (both remote and local file URLs)
488
- Log.d("AudioPlugin", "Debug: Handling URL");
632
+
633
+ private void playOrLoop(String action, final PluginCall call) {
489
634
  try {
490
- Uri uri = Uri.parse(assetPath);
491
- if (
492
- uri.getScheme() != null &&
493
- (uri.getScheme().equals("http") || uri.getScheme().equals("https"))
494
- ) {
495
- // Remote URL
496
- Log.d(
497
- "AudioPlugin",
498
- "Debug: Remote URL detected: " + uri.toString()
499
- );
500
- RemoteAudioAsset remoteAudioAsset = new RemoteAudioAsset(
501
- this,
502
- audioId,
503
- uri,
504
- audioChannelNum,
505
- volume
506
- );
507
- remoteAudioAsset.setCompletionListener(this::dispatchComplete);
508
- audioAssetList.put(audioId, remoteAudioAsset);
509
- } else if (
510
- uri.getScheme() != null && uri.getScheme().equals("file")
511
- ) {
512
- // Local file URL
513
- Log.d("AudioPlugin", "Debug: Local file URL detected");
514
- File file = new File(uri.getPath());
515
- if (!file.exists()) {
516
- Log.e(
517
- "AudioPlugin",
518
- "Error: File does not exist - " + file.getAbsolutePath()
519
- );
520
- call.reject(ERROR_ASSET_PATH_MISSING + " - " + assetPath);
521
- return;
635
+ final String audioId = call.getString(ASSET_ID);
636
+ final Double time = call.getDouble("time", 0.0);
637
+ Log.d(TAG, "Playing asset: " + audioId + ", action: " + action + ", assets count: " + audioAssetList.size());
638
+
639
+ if (audioAssetList.containsKey(audioId)) {
640
+ AudioAsset asset = audioAssetList.get(audioId);
641
+ Log.d(TAG, "Found asset: " + audioId + ", type: " + asset.getClass().getSimpleName());
642
+
643
+ if (asset != null) {
644
+ if (LOOP.equals(action)) {
645
+ asset.loop();
646
+ } else {
647
+ if (fadeMusic) {
648
+ asset.playWithFade(time);
649
+ } else {
650
+ asset.play(time);
651
+ }
652
+ }
653
+ call.resolve();
654
+ } else {
655
+ call.reject("Asset is null: " + audioId);
656
+ }
657
+ } else {
658
+ call.reject("Asset not found: " + audioId);
522
659
  }
523
- ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
524
- file,
525
- ParcelFileDescriptor.MODE_READ_ONLY
526
- );
527
- AssetFileDescriptor afd = new AssetFileDescriptor(
528
- pfd,
529
- 0,
530
- AssetFileDescriptor.UNKNOWN_LENGTH
531
- );
532
- AudioAsset asset = new AudioAsset(
533
- this,
534
- audioId,
535
- afd,
536
- audioChannelNum,
537
- volume
538
- );
539
- asset.setCompletionListener(this::dispatchComplete);
540
- audioAssetList.put(audioId, asset);
541
- } else {
542
- throw new IllegalArgumentException(
543
- "Invalid URL scheme: " + uri.getScheme()
544
- );
545
- }
546
- call.resolve(status);
547
- } catch (Exception e) {
548
- Log.e("AudioPlugin", "Error handling URL", e);
549
- call.reject("Error handling URL: " + e.getMessage());
660
+ } catch (Exception ex) {
661
+ Log.e(TAG, "Error in playOrLoop", ex);
662
+ call.reject(ex.getMessage());
550
663
  }
551
- } else {
552
- // Handle asset in public folder
553
- Log.d("AudioPlugin", "Debug: Handling asset in public folder");
554
- if (!assetPath.startsWith("public/")) {
555
- assetPath = "public/" + assetPath;
664
+ }
665
+
666
+ private void initSoundPool() {
667
+ if (audioAssetList == null) {
668
+ audioAssetList = new HashMap<>();
556
669
  }
557
- try {
558
- Context ctx = getContext().getApplicationContext();
559
- AssetManager am = ctx.getResources().getAssets();
560
- AssetFileDescriptor assetFileDescriptor = am.openFd(assetPath);
561
- AudioAsset asset = new AudioAsset(
562
- this,
563
- audioId,
564
- assetFileDescriptor,
565
- audioChannelNum,
566
- volume
567
- );
568
- audioAssetList.put(audioId, asset);
569
- call.resolve(status);
570
- } catch (IOException e) {
571
- Log.e("AudioPlugin", "Error opening asset: " + assetPath, e);
572
- call.reject(ERROR_ASSET_PATH_MISSING + " - " + assetPath);
670
+
671
+ if (resumeList == null) {
672
+ resumeList = new ArrayList<>();
573
673
  }
574
- }
575
- } catch (Exception ex) {
576
- Log.e("AudioPlugin", "Error in preloadAsset", ex);
577
- call.reject("Error in preloadAsset: " + ex.getMessage());
578
674
  }
579
- }
580
675
 
581
- private void playOrLoop(String action, final PluginCall call) {
582
- try {
583
- initSoundPool();
676
+ private boolean isStringValid(String value) {
677
+ return (value != null && !value.isEmpty() && !value.equals("null"));
678
+ }
679
+
680
+ private void stopAudio(String audioId) throws Exception {
681
+ if (!audioAssetList.containsKey(audioId)) {
682
+ throw new Exception(ERROR_ASSET_NOT_LOADED);
683
+ }
584
684
 
585
- final String audioId = call.getString(ASSET_ID);
586
- final Double time = call.getDouble("time", 0.0);
587
- if (audioAssetList.containsKey(audioId)) {
588
685
  AudioAsset asset = audioAssetList.get(audioId);
589
- if (LOOP.equals(action) && asset != null) {
590
- asset.loop();
591
- call.resolve();
592
- } else if (asset != null) {
593
- asset.play(time);
594
- call.resolve();
595
- } else {
596
- call.reject("Error with asset");
686
+ if (asset != null) {
687
+ if (fadeMusic) {
688
+ asset.stopWithFade();
689
+ } else {
690
+ asset.stop();
691
+ }
597
692
  }
598
- } else {
599
- call.reject("Error with asset");
600
- }
601
- } catch (Exception ex) {
602
- call.reject(ex.getMessage());
603
693
  }
604
- }
605
694
 
606
- private void initSoundPool() {
607
- if (audioAssetList == null) {
608
- audioAssetList = new HashMap<>();
695
+ private void saveDurationCall(String audioId, PluginCall call) {
696
+ Log.d(TAG, "Saving duration call for later: " + audioId);
697
+ pendingDurationCalls.put(audioId, call);
609
698
  }
610
699
 
611
- if (resumeList == null) {
612
- resumeList = new ArrayList<>();
700
+ public void notifyDurationAvailable(String assetId, double duration) {
701
+ Log.d(TAG, "Duration available for " + assetId + ": " + duration);
702
+ PluginCall savedCall = pendingDurationCalls.remove(assetId);
703
+ if (savedCall != null) {
704
+ JSObject ret = new JSObject();
705
+ ret.put("duration", duration);
706
+ savedCall.resolve(ret);
707
+ }
613
708
  }
614
- }
615
-
616
- private boolean isStringValid(String value) {
617
- return (value != null && !value.isEmpty() && !value.equals("null"));
618
- }
619
709
  }