@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 CHANGED
@@ -481,7 +481,7 @@ Check if an audio file is playing
481
481
  ### addListener('complete', ...)
482
482
 
483
483
  ```typescript
484
- addListener(eventName: "complete", listenerFunc: CompletedListener) => Promise<PluginListenerHandle>
484
+ addListener(eventName: 'complete', listenerFunc: CompletedListener) => Promise<PluginListenerHandle>
485
485
  ```
486
486
 
487
487
  Listen for complete event
@@ -499,6 +499,41 @@ return {@link CompletedEvent}
499
499
  --------------------
500
500
 
501
501
 
502
+ ### addListener('currentTime', ...)
503
+
504
+ ```typescript
505
+ addListener(eventName: 'currentTime', listenerFunc: CurrentTimeListener) => Promise<PluginListenerHandle>
506
+ ```
507
+
508
+ Listen for current time updates
509
+ Emits every 100ms while audio is playing
510
+
511
+ | Param | Type |
512
+ | ------------------ | ------------------------------------------------------------------- |
513
+ | **`eventName`** | <code>'currentTime'</code> |
514
+ | **`listenerFunc`** | <code><a href="#currenttimelistener">CurrentTimeListener</a></code> |
515
+
516
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
517
+
518
+ **Since:** 6.5.0
519
+ return {@link CurrentTimeEvent}
520
+
521
+ --------------------
522
+
523
+
524
+ ### clearCache()
525
+
526
+ ```typescript
527
+ clearCache() => Promise<void>
528
+ ```
529
+
530
+ Clear the audio cache for remote audio files
531
+
532
+ **Since:** 6.5.0
533
+
534
+ --------------------
535
+
536
+
502
537
  ### Interfaces
503
538
 
504
539
 
@@ -513,13 +548,13 @@ return {@link CompletedEvent}
513
548
 
514
549
  #### PreloadOptions
515
550
 
516
- | Prop | Type | Description |
517
- | --------------------- | -------------------- | -------------------------------------------------------------------------------------------------- |
518
- | **`assetPath`** | <code>string</code> | Path to the audio file, relative path of the file, absolute url (file://) or remote url (https://) |
519
- | **`assetId`** | <code>string</code> | Asset Id, unique identifier of the file |
520
- | **`volume`** | <code>number</code> | Volume of the audio, between 0.1 and 1.0 |
521
- | **`audioChannelNum`** | <code>number</code> | Audio channel number, default is 1 |
522
- | **`isUrl`** | <code>boolean</code> | Is the audio file a URL, pass true if assetPath is a `file://` url |
551
+ | Prop | Type | Description |
552
+ | --------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
553
+ | **`assetPath`** | <code>string</code> | Path to the audio file, relative path of the file, absolute url (file://) or remote url (https://) Supported formats: - MP3, WAV (all platforms) - M3U8/HLS streams (iOS and Android) |
554
+ | **`assetId`** | <code>string</code> | Asset Id, unique identifier of the file |
555
+ | **`volume`** | <code>number</code> | Volume of the audio, between 0.1 and 1.0 |
556
+ | **`audioChannelNum`** | <code>number</code> | Audio channel number, default is 1 |
557
+ | **`isUrl`** | <code>boolean</code> | Is the audio file a URL, pass true if assetPath is a `file://` url or a streaming URL (m3u8) |
523
558
 
524
559
 
525
560
  #### Assets
@@ -543,6 +578,14 @@ return {@link CompletedEvent}
543
578
  | **`assetId`** | <code>string</code> | Emit when a play completes | 5.0.0 |
544
579
 
545
580
 
581
+ #### CurrentTimeEvent
582
+
583
+ | Prop | Type | Description | Since |
584
+ | ----------------- | ------------------- | ------------------------------------ | ----- |
585
+ | **`currentTime`** | <code>number</code> | Current time of the audio in seconds | 6.5.0 |
586
+ | **`assetId`** | <code>string</code> | Asset Id of the audio | 6.5.0 |
587
+
588
+
546
589
  ### Type Aliases
547
590
 
548
591
 
@@ -550,4 +593,9 @@ return {@link CompletedEvent}
550
593
 
551
594
  <code>(state: <a href="#completedevent">CompletedEvent</a>): void</code>
552
595
 
596
+
597
+ #### CurrentTimeListener
598
+
599
+ <code>(state: <a href="#currenttimeevent">CurrentTimeEvent</a>): void</code>
600
+
553
601
  </docgen-api>
@@ -1,8 +1,8 @@
1
1
  ext {
2
2
  junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
3
4
  androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
4
5
  androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
5
- androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
6
6
  }
7
7
 
8
8
  buildscript {
@@ -11,7 +11,7 @@ buildscript {
11
11
  mavenCentral()
12
12
  }
13
13
  dependencies {
14
- classpath 'com.android.tools.build:gradle:8.7.2'
14
+ classpath 'com.android.tools.build:gradle:8.7.3'
15
15
  }
16
16
  }
17
17
 
@@ -35,8 +35,10 @@ android {
35
35
  }
36
36
  lintOptions {
37
37
  abortOnError false
38
- disable "UnsafeExperimentalUsageError",
39
- "UnsafeExperimentalUsageWarning"
38
+ }
39
+ compileOptions {
40
+ sourceCompatibility JavaVersion.VERSION_21
41
+ targetCompatibility JavaVersion.VERSION_21
40
42
  }
41
43
  }
42
44
 
@@ -47,10 +49,17 @@ repositories {
47
49
 
48
50
 
49
51
  dependencies {
50
- implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
51
52
  implementation fileTree(dir: 'libs', include: ['*.jar'])
52
53
  implementation project(':capacitor-android')
54
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
53
55
  testImplementation "junit:junit:$junitVersion"
54
56
  androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
55
57
  androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
58
+ implementation 'androidx.media3:media3-exoplayer:1.5.1'
59
+ implementation 'androidx.media3:media3-exoplayer-hls:1.5.1'
60
+ implementation 'androidx.media3:media3-session:1.5.1'
61
+ implementation 'androidx.media3:media3-transformer:1.5.1'
62
+ implementation 'androidx.media3:media3-ui:1.5.1'
63
+ implementation 'androidx.media3:media3-database:1.5.1'
64
+ implementation 'androidx.media3:media3-common:1.5.1'
56
65
  }
@@ -2,190 +2,333 @@ package ee.forgr.audio;
2
2
 
3
3
  import android.content.res.AssetFileDescriptor;
4
4
  import android.os.Build;
5
+ import android.os.Handler;
6
+ import android.os.Looper;
7
+ import android.util.Log;
5
8
  import androidx.annotation.RequiresApi;
6
9
  import java.util.ArrayList;
7
10
 
8
11
  public class AudioAsset {
9
12
 
10
- private final String TAG = "AudioAsset";
11
-
12
- private final ArrayList<AudioDispatcher> audioList;
13
- private int playIndex = 0;
14
- private final String assetId;
15
- protected final NativeAudio owner;
16
- protected AudioCompletionListener completionListener;
17
-
18
- AudioAsset(
19
- NativeAudio owner,
20
- String assetId,
21
- AssetFileDescriptor assetFileDescriptor,
22
- int audioChannelNum,
23
- float volume
24
- ) throws Exception {
25
- audioList = new ArrayList<>();
26
- this.owner = owner;
27
- this.assetId = assetId;
13
+ private final String TAG = "AudioAsset";
14
+
15
+ private final ArrayList<AudioDispatcher> audioList;
16
+ private int playIndex = 0;
17
+ protected final NativeAudio owner;
18
+ protected AudioCompletionListener completionListener;
19
+ protected String assetId;
20
+ private Handler currentTimeHandler;
21
+ private Runnable currentTimeRunnable;
22
+ private static final float FADE_STEP = 0.05f;
23
+ private static final int FADE_DELAY_MS = 80;
24
+ private float initialVolume;
25
+
26
+ AudioAsset(NativeAudio owner, String assetId, AssetFileDescriptor assetFileDescriptor, int audioChannelNum, float volume)
27
+ throws Exception {
28
+ audioList = new ArrayList<>();
29
+ this.owner = owner;
30
+ this.assetId = assetId;
31
+ this.initialVolume = volume;
32
+
33
+ if (audioChannelNum < 0) {
34
+ audioChannelNum = 1;
35
+ }
36
+
37
+ for (int x = 0; x < audioChannelNum; x++) {
38
+ AudioDispatcher audioDispatcher = new AudioDispatcher(assetFileDescriptor, volume);
39
+ audioList.add(audioDispatcher);
40
+ if (audioChannelNum == 1) audioDispatcher.setOwner(this);
41
+ }
42
+ }
28
43
 
29
- if (audioChannelNum < 0) {
30
- audioChannelNum = 1;
44
+ public void dispatchComplete() {
45
+ this.owner.dispatchComplete(this.assetId);
31
46
  }
32
47
 
33
- for (int x = 0; x < audioChannelNum; x++) {
34
- AudioDispatcher audioDispatcher = new AudioDispatcher(
35
- assetFileDescriptor,
36
- volume
37
- );
38
- audioList.add(audioDispatcher);
39
- if (audioChannelNum == 1) audioDispatcher.setOwner(this);
48
+ public void play(Double time) throws Exception {
49
+ AudioDispatcher audio = audioList.get(playIndex);
50
+ if (audio != null) {
51
+ audio.play(time);
52
+ playIndex++;
53
+ playIndex = playIndex % audioList.size();
54
+ Log.d(TAG, "Starting timer from play"); // Debug log
55
+ startCurrentTimeUpdates(); // Make sure this is called
56
+ } else {
57
+ throw new Exception("AudioDispatcher is null");
58
+ }
40
59
  }
41
- }
42
60
 
43
- public void dispatchComplete() {
44
- this.owner.dispatchComplete(this.assetId);
45
- }
61
+ public double getDuration() {
62
+ if (audioList.size() != 1) return 0;
46
63
 
47
- public void play(Double time) throws Exception {
48
- AudioDispatcher audio = audioList.get(playIndex);
64
+ AudioDispatcher audio = audioList.get(playIndex);
49
65
 
50
- if (audio != null) {
51
- audio.play(time);
52
- playIndex++;
53
- playIndex = playIndex % audioList.size();
54
- } else {
55
- throw new Exception("AudioDispatcher is null");
66
+ if (audio != null) {
67
+ return audio.getDuration();
68
+ }
69
+ return 0;
56
70
  }
57
- }
58
71
 
59
- public double getDuration() {
60
- if (audioList.size() != 1) return 0;
72
+ public void setCurrentPosition(double time) {
73
+ if (audioList.size() != 1) return;
61
74
 
62
- AudioDispatcher audio = audioList.get(playIndex);
75
+ AudioDispatcher audio = audioList.get(playIndex);
63
76
 
64
- if (audio != null) {
65
- return audio.getDuration();
77
+ if (audio != null) {
78
+ audio.setCurrentPosition(time);
79
+ }
66
80
  }
67
- return 0;
68
- }
69
81
 
70
- public void setCurrentPosition(double time) {
71
- if (audioList.size() != 1) return;
82
+ public double getCurrentPosition() {
83
+ if (audioList.size() != 1) return 0;
72
84
 
73
- AudioDispatcher audio = audioList.get(playIndex);
85
+ AudioDispatcher audio = audioList.get(playIndex);
74
86
 
75
- if (audio != null) {
76
- audio.setCurrentPosition(time);
87
+ if (audio != null) {
88
+ return audio.getCurrentPosition();
89
+ }
90
+ return 0;
77
91
  }
78
- }
79
92
 
80
- public double getCurrentPosition() {
81
- if (audioList.size() != 1) return 0;
93
+ public boolean pause() throws Exception {
94
+ stopCurrentTimeUpdates(); // Stop updates when pausing
95
+ boolean wasPlaying = false;
96
+
97
+ for (int x = 0; x < audioList.size(); x++) {
98
+ AudioDispatcher audio = audioList.get(x);
99
+ wasPlaying |= audio.pause();
100
+ }
82
101
 
83
- AudioDispatcher audio = audioList.get(playIndex);
102
+ return wasPlaying;
103
+ }
84
104
 
85
- if (audio != null) {
86
- return audio.getCurrentPosition();
105
+ public void resume() throws Exception {
106
+ if (!audioList.isEmpty()) {
107
+ AudioDispatcher audio = audioList.get(0);
108
+ if (audio != null) {
109
+ audio.resume();
110
+ Log.d(TAG, "Starting timer from resume"); // Debug log
111
+ startCurrentTimeUpdates(); // Make sure this is called
112
+ } else {
113
+ throw new Exception("AudioDispatcher is null");
114
+ }
115
+ }
87
116
  }
88
- return 0;
89
- }
90
117
 
91
- public boolean pause() throws Exception {
92
- boolean wasPlaying = false;
118
+ public void stop() throws Exception {
119
+ stopCurrentTimeUpdates(); // Stop updates when stopping
120
+ for (int x = 0; x < audioList.size(); x++) {
121
+ AudioDispatcher audio = audioList.get(x);
122
+
123
+ if (audio != null) {
124
+ audio.stop();
125
+ } else {
126
+ throw new Exception("AudioDispatcher is null");
127
+ }
128
+ }
129
+ }
93
130
 
94
- for (int x = 0; x < audioList.size(); x++) {
95
- AudioDispatcher audio = audioList.get(x);
96
- wasPlaying |= audio.pause();
131
+ public void loop() throws Exception {
132
+ AudioDispatcher audio = audioList.get(playIndex);
133
+ if (audio != null) {
134
+ audio.loop();
135
+ playIndex++;
136
+ playIndex = playIndex % audioList.size();
137
+ startCurrentTimeUpdates(); // Add timer start
138
+ } else {
139
+ throw new Exception("AudioDispatcher is null");
140
+ }
97
141
  }
98
142
 
99
- return wasPlaying;
100
- }
143
+ public void unload() throws Exception {
144
+ this.stop();
101
145
 
102
- public void resume() throws Exception {
103
- if (!audioList.isEmpty()) {
104
- AudioDispatcher audio = audioList.get(0);
146
+ for (int x = 0; x < audioList.size(); x++) {
147
+ AudioDispatcher audio = audioList.get(x);
105
148
 
106
- if (audio != null) {
107
- audio.resume();
108
- } else {
109
- throw new Exception("AudioDispatcher is null");
110
- }
149
+ if (audio != null) {
150
+ audio.unload();
151
+ } else {
152
+ throw new Exception("AudioDispatcher is null");
153
+ }
154
+ }
155
+
156
+ audioList.clear();
111
157
  }
112
- }
113
158
 
114
- public void stop() throws Exception {
115
- for (int x = 0; x < audioList.size(); x++) {
116
- AudioDispatcher audio = audioList.get(x);
159
+ public void setVolume(float volume) throws Exception {
160
+ for (int x = 0; x < audioList.size(); x++) {
161
+ AudioDispatcher audio = audioList.get(x);
162
+
163
+ if (audio != null) {
164
+ audio.setVolume(volume);
165
+ } else {
166
+ throw new Exception("AudioDispatcher is null");
167
+ }
168
+ }
169
+ }
117
170
 
118
- if (audio != null) {
119
- audio.stop();
120
- } else {
121
- throw new Exception("AudioDispatcher is null");
122
- }
171
+ @RequiresApi(api = Build.VERSION_CODES.M)
172
+ public void setRate(float rate) throws Exception {
173
+ for (int x = 0; x < audioList.size(); x++) {
174
+ AudioDispatcher audio = audioList.get(x);
175
+ if (audio != null) {
176
+ audio.setRate(rate);
177
+ }
178
+ }
123
179
  }
124
- }
125
180
 
126
- public void loop() throws Exception {
127
- AudioDispatcher audio = audioList.get(playIndex);
181
+ public boolean isPlaying() throws Exception {
182
+ if (audioList.size() != 1) return false;
128
183
 
129
- if (audio != null) {
130
- audio.loop();
131
- playIndex++;
132
- playIndex = playIndex % audioList.size();
133
- } else {
134
- throw new Exception("AudioDispatcher is null");
184
+ return audioList.get(playIndex).isPlaying();
135
185
  }
136
- }
137
186
 
138
- public void unload() throws Exception {
139
- this.stop();
187
+ public void setCompletionListener(AudioCompletionListener listener) {
188
+ this.completionListener = listener;
189
+ }
140
190
 
141
- for (int x = 0; x < audioList.size(); x++) {
142
- AudioDispatcher audio = audioList.get(x);
191
+ protected void notifyCompletion() {
192
+ if (completionListener != null) {
193
+ completionListener.onCompletion(this.assetId);
194
+ }
195
+ }
143
196
 
144
- if (audio != null) {
145
- audio.unload();
146
- } else {
147
- throw new Exception("AudioDispatcher is null");
148
- }
197
+ protected String getAssetId() {
198
+ return assetId;
149
199
  }
150
200
 
151
- audioList.clear();
152
- }
201
+ public void setCurrentTime(double time) throws Exception {
202
+ owner
203
+ .getActivity()
204
+ .runOnUiThread(
205
+ new Runnable() {
206
+ @Override
207
+ public void run() {
208
+ if (audioList.size() != 1) {
209
+ return;
210
+ }
211
+ AudioDispatcher audio = audioList.get(playIndex);
212
+ if (audio != null) {
213
+ audio.setCurrentPosition(time);
214
+ }
215
+ }
216
+ }
217
+ );
218
+ }
153
219
 
154
- public void setVolume(float volume) throws Exception {
155
- for (int x = 0; x < audioList.size(); x++) {
156
- AudioDispatcher audio = audioList.get(x);
220
+ protected void startCurrentTimeUpdates() {
221
+ Log.d(TAG, "Starting timer updates in AudioAsset");
222
+ if (currentTimeHandler == null) {
223
+ currentTimeHandler = new Handler(Looper.getMainLooper());
224
+ }
225
+
226
+ // Add small delay to let audio start playing
227
+ currentTimeHandler.postDelayed(
228
+ new Runnable() {
229
+ @Override
230
+ public void run() {
231
+ startTimeUpdateLoop();
232
+ }
233
+ },
234
+ 100
235
+ ); // 100ms delay
236
+ }
157
237
 
158
- if (audio != null) {
159
- audio.setVolume(volume);
160
- } else {
161
- throw new Exception("AudioDispatcher is null");
162
- }
238
+ private void startTimeUpdateLoop() {
239
+ currentTimeRunnable = new Runnable() {
240
+ @Override
241
+ public void run() {
242
+ try {
243
+ AudioDispatcher audio = audioList.get(playIndex);
244
+ if (audio != null && audio.isPlaying()) {
245
+ double currentTime = getCurrentPosition();
246
+ Log.d(TAG, "Timer update: currentTime = " + currentTime);
247
+ owner.notifyCurrentTime(assetId, currentTime);
248
+ currentTimeHandler.postDelayed(this, 100);
249
+ } else {
250
+ Log.d(TAG, "Stopping timer - not playing");
251
+ stopCurrentTimeUpdates();
252
+ }
253
+ } catch (Exception e) {
254
+ Log.e(TAG, "Error getting current time", e);
255
+ stopCurrentTimeUpdates();
256
+ }
257
+ }
258
+ };
259
+ currentTimeHandler.post(currentTimeRunnable);
163
260
  }
164
- }
165
261
 
166
- @RequiresApi(api = Build.VERSION_CODES.M)
167
- public void setRate(float rate) throws Exception {
168
- for (int x = 0; x < audioList.size(); x++) {
169
- AudioDispatcher audio = audioList.get(x);
170
- if (audio != null) {
171
- audio.setRate(rate);
172
- }
262
+ void stopCurrentTimeUpdates() {
263
+ Log.d(TAG, "Stopping timer updates in AudioAsset");
264
+ if (currentTimeHandler != null && currentTimeRunnable != null) {
265
+ currentTimeHandler.removeCallbacks(currentTimeRunnable);
266
+ }
173
267
  }
174
- }
175
268
 
176
- public boolean isPlaying() throws Exception {
177
- if (audioList.size() != 1) return false;
269
+ public void playWithFade(Double time) throws Exception {
270
+ AudioDispatcher audio = audioList.get(playIndex);
271
+ if (audio != null) {
272
+ audio.setVolume(0);
273
+ audio.play(time);
274
+ fadeIn(audio);
275
+ startCurrentTimeUpdates();
276
+ }
277
+ }
178
278
 
179
- return audioList.get(playIndex).isPlaying();
180
- }
279
+ private void fadeIn(final AudioDispatcher audio) {
280
+ final Handler handler = new Handler(Looper.getMainLooper());
281
+ final Runnable fadeRunnable = new Runnable() {
282
+ float currentVolume = 0;
283
+
284
+ @Override
285
+ public void run() {
286
+ if (currentVolume < initialVolume) {
287
+ currentVolume += FADE_STEP;
288
+ try {
289
+ audio.setVolume(currentVolume);
290
+ handler.postDelayed(this, FADE_DELAY_MS);
291
+ } catch (Exception e) {
292
+ Log.e(TAG, "Error during fade in", e);
293
+ }
294
+ }
295
+ }
296
+ };
297
+ handler.post(fadeRunnable);
298
+ }
181
299
 
182
- public void setCompletionListener(AudioCompletionListener listener) {
183
- this.completionListener = listener;
184
- }
300
+ public void stopWithFade() throws Exception {
301
+ AudioDispatcher audio = audioList.get(playIndex);
302
+ if (audio != null && audio.isPlaying()) {
303
+ fadeOut(audio);
304
+ }
305
+ }
185
306
 
186
- protected void notifyCompletion() {
187
- if (completionListener != null) {
188
- completionListener.onCompletion(this.assetId);
307
+ private void fadeOut(final AudioDispatcher audio) {
308
+ final Handler handler = new Handler(Looper.getMainLooper());
309
+ final Runnable fadeRunnable = new Runnable() {
310
+ float currentVolume = initialVolume;
311
+
312
+ @Override
313
+ public void run() {
314
+ if (currentVolume > FADE_STEP) {
315
+ currentVolume -= FADE_STEP;
316
+ try {
317
+ audio.setVolume(currentVolume);
318
+ handler.postDelayed(this, FADE_DELAY_MS);
319
+ } catch (Exception e) {
320
+ Log.e(TAG, "Error during fade out", e);
321
+ }
322
+ } else {
323
+ try {
324
+ audio.setVolume(0);
325
+ stop();
326
+ } catch (Exception e) {
327
+ Log.e(TAG, "Error stopping after fade", e);
328
+ }
329
+ }
330
+ }
331
+ };
332
+ handler.post(fadeRunnable);
189
333
  }
190
- }
191
334
  }
@@ -1,5 +1,5 @@
1
1
  package ee.forgr.audio;
2
2
 
3
3
  public interface AudioCompletionListener {
4
- void onCompletion(String assetId);
4
+ void onCompletion(String assetId);
5
5
  }