@capgo/capacitor-native-audio 8.4.3
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/CapgoCapacitorNativeAudio.podspec +16 -0
- package/LICENSE +373 -0
- package/Package.swift +31 -0
- package/README.md +1229 -0
- package/android/build.gradle +89 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/ee/forgr/audio/AudioAsset.java +611 -0
- package/android/src/main/java/ee/forgr/audio/AudioCompletionListener.java +5 -0
- package/android/src/main/java/ee/forgr/audio/AudioDispatcher.java +208 -0
- package/android/src/main/java/ee/forgr/audio/Constant.java +36 -0
- package/android/src/main/java/ee/forgr/audio/HlsAvailabilityChecker.java +84 -0
- package/android/src/main/java/ee/forgr/audio/Logger.java +55 -0
- package/android/src/main/java/ee/forgr/audio/NativeAudio.java +2022 -0
- package/android/src/main/java/ee/forgr/audio/RemoteAudioAsset.java +886 -0
- package/android/src/main/java/ee/forgr/audio/StreamAudioAsset.java +708 -0
- package/android/src/main/res/values/colors.xml +3 -0
- package/android/src/main/res/values/strings.xml +3 -0
- package/android/src/main/res/values/styles.xml +3 -0
- package/dist/docs.json +1470 -0
- package/dist/esm/audio-asset.d.ts +4 -0
- package/dist/esm/audio-asset.js +6 -0
- package/dist/esm/audio-asset.js.map +1 -0
- package/dist/esm/definitions.d.ts +597 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +82 -0
- package/dist/esm/web.js +553 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +571 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +574 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/NativeAudioPlugin/AudioAsset+Fade.swift +157 -0
- package/ios/Sources/NativeAudioPlugin/AudioAsset.swift +403 -0
- package/ios/Sources/NativeAudioPlugin/Constant.swift +52 -0
- package/ios/Sources/NativeAudioPlugin/Logger.swift +43 -0
- package/ios/Sources/NativeAudioPlugin/Plugin.swift +1786 -0
- package/ios/Sources/NativeAudioPlugin/RemoteAudioAsset+Fade.swift +152 -0
- package/ios/Sources/NativeAudioPlugin/RemoteAudioAsset.swift +405 -0
- package/ios/Tests/NativeAudioPluginTests/PluginTests.swift +648 -0
- package/ios/Tests/README.md +39 -0
- package/package.json +101 -0
- package/scripts/configure-dependencies.js +251 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
package ee.forgr.audio;
|
|
2
|
+
|
|
3
|
+
import static ee.forgr.audio.Constant.INVALID;
|
|
4
|
+
import static ee.forgr.audio.Constant.LOOPING;
|
|
5
|
+
import static ee.forgr.audio.Constant.PAUSE;
|
|
6
|
+
import static ee.forgr.audio.Constant.PENDING_LOOP;
|
|
7
|
+
import static ee.forgr.audio.Constant.PENDING_PLAY;
|
|
8
|
+
import static ee.forgr.audio.Constant.PLAYING;
|
|
9
|
+
import static ee.forgr.audio.Constant.PREPARED;
|
|
10
|
+
|
|
11
|
+
import android.content.res.AssetFileDescriptor;
|
|
12
|
+
import android.media.AudioAttributes;
|
|
13
|
+
import android.media.MediaPlayer;
|
|
14
|
+
import android.os.Build;
|
|
15
|
+
import android.util.Log;
|
|
16
|
+
import androidx.media3.common.util.UnstableApi;
|
|
17
|
+
|
|
18
|
+
@UnstableApi
|
|
19
|
+
public class AudioDispatcher
|
|
20
|
+
implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnSeekCompleteListener
|
|
21
|
+
{
|
|
22
|
+
|
|
23
|
+
private final String TAG = "AudioDispatcher";
|
|
24
|
+
private final MediaPlayer mediaPlayer;
|
|
25
|
+
private int mediaState;
|
|
26
|
+
private AudioAsset owner;
|
|
27
|
+
|
|
28
|
+
private float currentVolume = 1.0f;
|
|
29
|
+
|
|
30
|
+
public AudioDispatcher(AssetFileDescriptor assetFileDescriptor, float volume) throws Exception {
|
|
31
|
+
mediaState = INVALID;
|
|
32
|
+
|
|
33
|
+
mediaPlayer = new MediaPlayer();
|
|
34
|
+
mediaPlayer.setOnCompletionListener(this);
|
|
35
|
+
mediaPlayer.setOnPreparedListener(this);
|
|
36
|
+
mediaPlayer.setDataSource(
|
|
37
|
+
assetFileDescriptor.getFileDescriptor(),
|
|
38
|
+
assetFileDescriptor.getStartOffset(),
|
|
39
|
+
assetFileDescriptor.getLength()
|
|
40
|
+
);
|
|
41
|
+
mediaPlayer.setOnSeekCompleteListener(this);
|
|
42
|
+
mediaPlayer.setAudioAttributes(
|
|
43
|
+
new AudioAttributes.Builder()
|
|
44
|
+
.setUsage(AudioAttributes.USAGE_MEDIA)
|
|
45
|
+
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
|
46
|
+
.build()
|
|
47
|
+
);
|
|
48
|
+
mediaPlayer.setVolume(volume, volume);
|
|
49
|
+
currentVolume = volume;
|
|
50
|
+
mediaPlayer.setPlaybackParams(mediaPlayer.getPlaybackParams().setSpeed(1.0f));
|
|
51
|
+
mediaPlayer.prepare();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public void setOwner(AudioAsset asset) {
|
|
55
|
+
owner = asset;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public double getDuration() {
|
|
59
|
+
return mediaPlayer.getDuration() / 1000.0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public void setCurrentPosition(double time) {
|
|
63
|
+
if (mediaState == PLAYING || mediaState == PAUSE) {
|
|
64
|
+
mediaPlayer.seekTo((int) (time * 1000));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public double getCurrentPosition() {
|
|
69
|
+
return mediaPlayer.getCurrentPosition() / 1000.0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public void play(Double time) throws Exception {
|
|
73
|
+
invokePlay(time);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public boolean pause() throws Exception {
|
|
77
|
+
if (mediaPlayer.isPlaying()) {
|
|
78
|
+
mediaPlayer.pause();
|
|
79
|
+
mediaState = PAUSE;
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public void resume() throws Exception {
|
|
87
|
+
mediaPlayer.start();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public void stop() throws Exception {
|
|
91
|
+
if (mediaPlayer.isPlaying()) {
|
|
92
|
+
mediaState = INVALID;
|
|
93
|
+
mediaPlayer.pause();
|
|
94
|
+
mediaPlayer.seekTo(0);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public void setVolume(float volume) throws Exception {
|
|
99
|
+
mediaPlayer.setVolume(volume, volume);
|
|
100
|
+
currentVolume = volume;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public float getVolume() {
|
|
104
|
+
return currentVolume;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public void setRate(float rate) throws Exception {
|
|
108
|
+
mediaPlayer.setPlaybackParams(mediaPlayer.getPlaybackParams().setSpeed(rate));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public void loop() throws Exception {
|
|
112
|
+
mediaPlayer.setLooping(true);
|
|
113
|
+
mediaPlayer.start();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public void unload() throws Exception {
|
|
117
|
+
this.stop();
|
|
118
|
+
mediaPlayer.release();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@Override
|
|
122
|
+
public void onCompletion(MediaPlayer mp) {
|
|
123
|
+
try {
|
|
124
|
+
if (mediaState != LOOPING) {
|
|
125
|
+
this.mediaState = INVALID;
|
|
126
|
+
|
|
127
|
+
this.stop();
|
|
128
|
+
|
|
129
|
+
if (this.owner != null) {
|
|
130
|
+
this.owner.notifyCompletion();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (Exception ex) {
|
|
134
|
+
Log.d(TAG, "Caught exception while listening for onCompletion: " + ex.getLocalizedMessage());
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@Override
|
|
139
|
+
public void onPrepared(MediaPlayer mp) {
|
|
140
|
+
try {
|
|
141
|
+
if (mediaState == PENDING_PLAY) {
|
|
142
|
+
mediaPlayer.setLooping(false);
|
|
143
|
+
} else if (mediaState == PENDING_LOOP) {
|
|
144
|
+
mediaPlayer.setLooping(true);
|
|
145
|
+
} else {
|
|
146
|
+
mediaState = PREPARED;
|
|
147
|
+
}
|
|
148
|
+
} catch (Exception ex) {
|
|
149
|
+
Log.d(TAG, "Caught exception while listening for onPrepared: " + ex.getLocalizedMessage());
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private void seek(Double time) {
|
|
154
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
155
|
+
mediaPlayer.seekTo((int) (time * 1000), MediaPlayer.SEEK_NEXT_SYNC);
|
|
156
|
+
} else {
|
|
157
|
+
mediaPlayer.seekTo((int) (time * 1000));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private void invokePlay(Double time) {
|
|
162
|
+
try {
|
|
163
|
+
boolean playing = mediaPlayer.isPlaying();
|
|
164
|
+
|
|
165
|
+
if (playing) {
|
|
166
|
+
mediaPlayer.pause();
|
|
167
|
+
mediaPlayer.setLooping(false);
|
|
168
|
+
mediaState = PENDING_PLAY;
|
|
169
|
+
seek(time);
|
|
170
|
+
} else {
|
|
171
|
+
if (mediaState == PREPARED) {
|
|
172
|
+
mediaState = (PENDING_PLAY);
|
|
173
|
+
onPrepared(mediaPlayer);
|
|
174
|
+
seek(time);
|
|
175
|
+
} else {
|
|
176
|
+
mediaState = (PENDING_PLAY);
|
|
177
|
+
mediaPlayer.setLooping(false);
|
|
178
|
+
seek(time);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} catch (Exception ex) {
|
|
182
|
+
Log.d(TAG, "Caught exception while invoking audio: " + ex.getLocalizedMessage());
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@Override
|
|
187
|
+
public void onSeekComplete(MediaPlayer mp) {
|
|
188
|
+
if (mediaState == PENDING_PLAY || mediaState == PENDING_LOOP) {
|
|
189
|
+
Log.w("AudioDispatcher", "play " + mediaState);
|
|
190
|
+
mediaPlayer.start();
|
|
191
|
+
mediaState = PLAYING;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public boolean isPlaying() {
|
|
196
|
+
boolean playing = false;
|
|
197
|
+
try {
|
|
198
|
+
playing = mediaPlayer.isPlaying();
|
|
199
|
+
} catch (IllegalStateException ex) {
|
|
200
|
+
Log.v(TAG, "Caught exception while checking if audio is playing: " + ex.getLocalizedMessage());
|
|
201
|
+
}
|
|
202
|
+
return playing;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public boolean isPaused() {
|
|
206
|
+
return mediaState == PAUSE;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package ee.forgr.audio;
|
|
2
|
+
|
|
3
|
+
public class Constant {
|
|
4
|
+
|
|
5
|
+
public static final String ERROR_AUDIO_ID_MISSING = "Audio Id is missing";
|
|
6
|
+
public static final String ERROR_AUDIO_ASSET_MISSING = "Audio Asset is missing";
|
|
7
|
+
public static final String ERROR_AUDIO_EXISTS = "Audio Asset already exists";
|
|
8
|
+
public static final String ERROR_ASSET_PATH_MISSING = "Asset Path is missing";
|
|
9
|
+
public static final String ERROR_ASSET_NOT_LOADED = "Asset is not loaded";
|
|
10
|
+
|
|
11
|
+
public static final String ASSET_ID = "assetId";
|
|
12
|
+
public static final String ASSET_PATH = "assetPath";
|
|
13
|
+
public static final String OPT_FOCUS_AUDIO = "focus";
|
|
14
|
+
public static final String TIME = "time";
|
|
15
|
+
public static final String DELAY = "delay";
|
|
16
|
+
public static final String VOLUME = "volume";
|
|
17
|
+
public static final String RATE = "rate";
|
|
18
|
+
public static final String DURATION = "duration";
|
|
19
|
+
public static final String AUDIO_CHANNEL_NUM = "audioChannelNum";
|
|
20
|
+
public static final String LOOP = "loop";
|
|
21
|
+
public static final String PLAY = "play";
|
|
22
|
+
public static final String FADE_IN = "fadeIn";
|
|
23
|
+
public static final String FADE_OUT = "fadeOut";
|
|
24
|
+
public static final String FADE_IN_DURATION = "fadeInDuration";
|
|
25
|
+
public static final String FADE_OUT_DURATION = "fadeOutDuration";
|
|
26
|
+
public static final String FADE_OUT_START_TIME = "fadeOutStartTime";
|
|
27
|
+
public static final String SHOW_NOTIFICATION = "showNotification";
|
|
28
|
+
public static final String NOTIFICATION_METADATA = "notificationMetadata";
|
|
29
|
+
public static final int INVALID = 0;
|
|
30
|
+
public static final int PREPARED = 1;
|
|
31
|
+
public static final int PENDING_PLAY = 2;
|
|
32
|
+
public static final int PLAYING = 3;
|
|
33
|
+
public static final int PENDING_LOOP = 4;
|
|
34
|
+
public static final int LOOPING = 5;
|
|
35
|
+
public static final int PAUSE = 6;
|
|
36
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package ee.forgr.audio;
|
|
2
|
+
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Utility class to check if HLS (m3u8) streaming dependencies are available at runtime.
|
|
7
|
+
*
|
|
8
|
+
* This allows the plugin to gracefully handle cases where the HLS dependency
|
|
9
|
+
* (media3-exoplayer-hls) is excluded to reduce APK size.
|
|
10
|
+
*
|
|
11
|
+
* Users who don't need HLS streaming support can disable it in capacitor.config.ts:
|
|
12
|
+
*
|
|
13
|
+
* plugins: {
|
|
14
|
+
* NativeAudio: {
|
|
15
|
+
* hls: false // Reduces APK size by ~4MB
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
public class HlsAvailabilityChecker {
|
|
20
|
+
|
|
21
|
+
private static final String TAG = "HlsAvailabilityChecker";
|
|
22
|
+
private static Boolean hlsAvailable = null;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if a class is available at runtime using reflection.
|
|
26
|
+
*/
|
|
27
|
+
private static boolean isClassAvailable(String className) {
|
|
28
|
+
try {
|
|
29
|
+
Class.forName(className);
|
|
30
|
+
return true;
|
|
31
|
+
} catch (ClassNotFoundException e) {
|
|
32
|
+
return false;
|
|
33
|
+
} catch (Exception e) {
|
|
34
|
+
Log.e(TAG, "Unexpected error checking class availability: " + className, e);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if HLS streaming dependencies are available.
|
|
41
|
+
* Results are cached for performance.
|
|
42
|
+
*
|
|
43
|
+
* @return true if HLS classes are available, false otherwise
|
|
44
|
+
*/
|
|
45
|
+
public static boolean isHlsAvailable() {
|
|
46
|
+
if (hlsAvailable != null) {
|
|
47
|
+
return hlsAvailable;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check for the critical HLS classes from media3-exoplayer-hls
|
|
51
|
+
String[] hlsClasses = { "androidx.media3.exoplayer.hls.HlsMediaSource", "androidx.media3.exoplayer.hls.HlsMediaSource$Factory" };
|
|
52
|
+
|
|
53
|
+
boolean allAvailable = true;
|
|
54
|
+
for (String className : hlsClasses) {
|
|
55
|
+
if (!isClassAvailable(className)) {
|
|
56
|
+
allAvailable = false;
|
|
57
|
+
Log.w(TAG, "HLS dependency class not available: " + className);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
hlsAvailable = allAvailable;
|
|
62
|
+
|
|
63
|
+
if (!allAvailable) {
|
|
64
|
+
Log.i(
|
|
65
|
+
TAG,
|
|
66
|
+
"HLS streaming support is not available. " +
|
|
67
|
+
"To enable m3u8 streaming, set 'hls: true' in capacitor.config.ts under NativeAudio plugin config " +
|
|
68
|
+
"and run 'npx cap sync'."
|
|
69
|
+
);
|
|
70
|
+
} else {
|
|
71
|
+
Log.d(TAG, "HLS streaming support is available.");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return allAvailable;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Reset the cached availability check.
|
|
79
|
+
* Useful for testing purposes.
|
|
80
|
+
*/
|
|
81
|
+
public static void resetCache() {
|
|
82
|
+
hlsAvailable = null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
package ee.forgr.audio;
|
|
2
|
+
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
import androidx.media3.common.util.UnstableApi;
|
|
5
|
+
|
|
6
|
+
@UnstableApi
|
|
7
|
+
public class Logger {
|
|
8
|
+
|
|
9
|
+
private String logTag;
|
|
10
|
+
|
|
11
|
+
// constructor
|
|
12
|
+
public Logger(String logTag) {
|
|
13
|
+
this.logTag = logTag;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public void setLogTag(String logTag) {
|
|
17
|
+
this.logTag = logTag;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public void error(String message) {
|
|
21
|
+
if (NativeAudio.debugEnabled) {
|
|
22
|
+
Log.e(logTag, message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public void error(String message, Throwable throwable) {
|
|
27
|
+
if (NativeAudio.debugEnabled) {
|
|
28
|
+
Log.e(logTag, message, throwable);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public void warning(String message) {
|
|
33
|
+
if (NativeAudio.debugEnabled) {
|
|
34
|
+
Log.w(logTag, message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public void info(String message) {
|
|
39
|
+
if (NativeAudio.debugEnabled) {
|
|
40
|
+
Log.i(logTag, message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public void debug(String message) {
|
|
45
|
+
if (NativeAudio.debugEnabled) {
|
|
46
|
+
Log.d(logTag, message);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public void verbose(String message) {
|
|
51
|
+
if (NativeAudio.debugEnabled) {
|
|
52
|
+
Log.v(logTag, message);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|