@capgo/native-audio 7.1.8 → 7.3.9
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 +83 -14
- 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 +103 -3
- package/dist/esm/definitions.d.ts +37 -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 +320 -96
- package/ios/Plugin/Constant.swift +13 -0
- package/ios/Plugin/Plugin.swift +190 -127
- package/ios/Plugin/RemoteAudioAsset.swift +350 -55
- package/package.json +20 -20
package/README.md
CHANGED
@@ -30,7 +30,9 @@
|
|
30
30
|
# Capacitor Native Audio Plugin
|
31
31
|
|
32
32
|
Capacitor plugin for native audio engine.
|
33
|
-
Capacitor
|
33
|
+
Capacitor V7 - ✅ Support!
|
34
|
+
|
35
|
+
Support local file, remote URL, and m3u8 stream
|
34
36
|
|
35
37
|
Click on video to see example 💥
|
36
38
|
|
@@ -481,7 +483,7 @@ Check if an audio file is playing
|
|
481
483
|
### addListener('complete', ...)
|
482
484
|
|
483
485
|
```typescript
|
484
|
-
addListener(eventName:
|
486
|
+
addListener(eventName: 'complete', listenerFunc: CompletedListener) => Promise<PluginListenerHandle>
|
485
487
|
```
|
486
488
|
|
487
489
|
Listen for complete event
|
@@ -499,27 +501,63 @@ return {@link CompletedEvent}
|
|
499
501
|
--------------------
|
500
502
|
|
501
503
|
|
504
|
+
### addListener('currentTime', ...)
|
505
|
+
|
506
|
+
```typescript
|
507
|
+
addListener(eventName: 'currentTime', listenerFunc: CurrentTimeListener) => Promise<PluginListenerHandle>
|
508
|
+
```
|
509
|
+
|
510
|
+
Listen for current time updates
|
511
|
+
Emits every 100ms while audio is playing
|
512
|
+
|
513
|
+
| Param | Type |
|
514
|
+
| ------------------ | ------------------------------------------------------------------- |
|
515
|
+
| **`eventName`** | <code>'currentTime'</code> |
|
516
|
+
| **`listenerFunc`** | <code><a href="#currenttimelistener">CurrentTimeListener</a></code> |
|
517
|
+
|
518
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
519
|
+
|
520
|
+
**Since:** 6.5.0
|
521
|
+
return {@link CurrentTimeEvent}
|
522
|
+
|
523
|
+
--------------------
|
524
|
+
|
525
|
+
|
526
|
+
### clearCache()
|
527
|
+
|
528
|
+
```typescript
|
529
|
+
clearCache() => Promise<void>
|
530
|
+
```
|
531
|
+
|
532
|
+
Clear the audio cache for remote audio files
|
533
|
+
|
534
|
+
**Since:** 6.5.0
|
535
|
+
|
536
|
+
--------------------
|
537
|
+
|
538
|
+
|
502
539
|
### Interfaces
|
503
540
|
|
504
541
|
|
505
542
|
#### ConfigureOptions
|
506
543
|
|
507
|
-
| Prop
|
508
|
-
|
|
509
|
-
| **`fade`**
|
510
|
-
| **`focus`**
|
511
|
-
| **`background`**
|
544
|
+
| Prop | Type | Description |
|
545
|
+
| ------------------ | -------------------- | ----------------------------------------------------------------------------- |
|
546
|
+
| **`fade`** | <code>boolean</code> | Play the audio with Fade effect, only available for IOS |
|
547
|
+
| **`focus`** | <code>boolean</code> | focus the audio with Audio Focus |
|
548
|
+
| **`background`** | <code>boolean</code> | Play the audio in the background |
|
549
|
+
| **`ignoreSilent`** | <code>boolean</code> | Ignore silent mode, works only on iOS setting this will nuke other audio apps |
|
512
550
|
|
513
551
|
|
514
552
|
#### PreloadOptions
|
515
553
|
|
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
|
554
|
+
| Prop | Type | Description |
|
555
|
+
| --------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
556
|
+
| **`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) |
|
557
|
+
| **`assetId`** | <code>string</code> | Asset Id, unique identifier of the file |
|
558
|
+
| **`volume`** | <code>number</code> | Volume of the audio, between 0.1 and 1.0 |
|
559
|
+
| **`audioChannelNum`** | <code>number</code> | Audio channel number, default is 1 |
|
560
|
+
| **`isUrl`** | <code>boolean</code> | Is the audio file a URL, pass true if assetPath is a `file://` url or a streaming URL (m3u8) |
|
523
561
|
|
524
562
|
|
525
563
|
#### Assets
|
@@ -543,6 +581,14 @@ return {@link CompletedEvent}
|
|
543
581
|
| **`assetId`** | <code>string</code> | Emit when a play completes | 5.0.0 |
|
544
582
|
|
545
583
|
|
584
|
+
#### CurrentTimeEvent
|
585
|
+
|
586
|
+
| Prop | Type | Description | Since |
|
587
|
+
| ----------------- | ------------------- | ------------------------------------ | ----- |
|
588
|
+
| **`currentTime`** | <code>number</code> | Current time of the audio in seconds | 6.5.0 |
|
589
|
+
| **`assetId`** | <code>string</code> | Asset Id of the audio | 6.5.0 |
|
590
|
+
|
591
|
+
|
546
592
|
### Type Aliases
|
547
593
|
|
548
594
|
|
@@ -550,4 +596,27 @@ return {@link CompletedEvent}
|
|
550
596
|
|
551
597
|
<code>(state: <a href="#completedevent">CompletedEvent</a>): void</code>
|
552
598
|
|
599
|
+
|
600
|
+
#### CurrentTimeListener
|
601
|
+
|
602
|
+
<code>(state: <a href="#currenttimeevent">CurrentTimeEvent</a>): void</code>
|
603
|
+
|
553
604
|
</docgen-api>
|
605
|
+
|
606
|
+
## Development and Testing
|
607
|
+
|
608
|
+
### Building
|
609
|
+
|
610
|
+
```bash
|
611
|
+
npm run build
|
612
|
+
```
|
613
|
+
|
614
|
+
### Testing
|
615
|
+
|
616
|
+
This plugin includes a comprehensive test suite for iOS:
|
617
|
+
|
618
|
+
1. Open the iOS project in Xcode: `npx cap open ios`
|
619
|
+
2. Navigate to the `PluginTests` directory
|
620
|
+
3. Run tests using Product > Test (⌘+U)
|
621
|
+
|
622
|
+
The tests cover core functionality including audio asset initialization, playback, volume control, fade effects, and more. See the [test documentation](ios/PluginTests/README.md) for more details.
|
package/android/build.gradle
CHANGED
@@ -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.
|
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
|
-
|
39
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
float volume
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
30
|
-
|
44
|
+
public void dispatchComplete() {
|
45
|
+
this.owner.dispatchComplete(this.assetId);
|
31
46
|
}
|
32
47
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
44
|
-
|
45
|
-
}
|
61
|
+
public double getDuration() {
|
62
|
+
if (audioList.size() != 1) return 0;
|
46
63
|
|
47
|
-
|
48
|
-
AudioDispatcher audio = audioList.get(playIndex);
|
64
|
+
AudioDispatcher audio = audioList.get(playIndex);
|
49
65
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
60
|
-
|
72
|
+
public void setCurrentPosition(double time) {
|
73
|
+
if (audioList.size() != 1) return;
|
61
74
|
|
62
|
-
|
75
|
+
AudioDispatcher audio = audioList.get(playIndex);
|
63
76
|
|
64
|
-
|
65
|
-
|
77
|
+
if (audio != null) {
|
78
|
+
audio.setCurrentPosition(time);
|
79
|
+
}
|
66
80
|
}
|
67
|
-
return 0;
|
68
|
-
}
|
69
81
|
|
70
|
-
|
71
|
-
|
82
|
+
public double getCurrentPosition() {
|
83
|
+
if (audioList.size() != 1) return 0;
|
72
84
|
|
73
|
-
|
85
|
+
AudioDispatcher audio = audioList.get(playIndex);
|
74
86
|
|
75
|
-
|
76
|
-
|
87
|
+
if (audio != null) {
|
88
|
+
return audio.getCurrentPosition();
|
89
|
+
}
|
90
|
+
return 0;
|
77
91
|
}
|
78
|
-
}
|
79
92
|
|
80
|
-
|
81
|
-
|
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
|
-
|
102
|
+
return wasPlaying;
|
103
|
+
}
|
84
104
|
|
85
|
-
|
86
|
-
|
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
|
-
|
92
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
100
|
-
|
143
|
+
public void unload() throws Exception {
|
144
|
+
this.stop();
|
101
145
|
|
102
|
-
|
103
|
-
|
104
|
-
AudioDispatcher audio = audioList.get(0);
|
146
|
+
for (int x = 0; x < audioList.size(); x++) {
|
147
|
+
AudioDispatcher audio = audioList.get(x);
|
105
148
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
127
|
-
|
181
|
+
public boolean isPlaying() throws Exception {
|
182
|
+
if (audioList.size() != 1) return false;
|
128
183
|
|
129
|
-
|
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
|
-
|
139
|
-
|
187
|
+
public void setCompletionListener(AudioCompletionListener listener) {
|
188
|
+
this.completionListener = listener;
|
189
|
+
}
|
140
190
|
|
141
|
-
|
142
|
-
|
191
|
+
protected void notifyCompletion() {
|
192
|
+
if (completionListener != null) {
|
193
|
+
completionListener.onCompletion(this.assetId);
|
194
|
+
}
|
195
|
+
}
|
143
196
|
|
144
|
-
|
145
|
-
|
146
|
-
} else {
|
147
|
-
throw new Exception("AudioDispatcher is null");
|
148
|
-
}
|
197
|
+
protected String getAssetId() {
|
198
|
+
return assetId;
|
149
199
|
}
|
150
200
|
|
151
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
-
|
177
|
-
|
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
|
-
|
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
|
-
|
183
|
-
|
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
|
-
|
187
|
-
|
188
|
-
|
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
|
}
|