@capgo/native-audio 6.1.36 → 6.2.1
Sign up to get free protection for your applications and to get access to all the features.
- package/android/src/main/java/ee/forgr/audio/NativeAudio.java +39 -10
- package/android/src/main/java/ee/forgr/audio/RemoteAudioAsset.java +87 -0
- package/ios/Plugin/AudioAsset.swift +2 -1
- package/ios/Plugin/Plugin.swift +34 -7
- package/ios/Plugin/RemoteAudioAsset.swift +74 -0
- package/package.json +1 -1
@@ -20,6 +20,7 @@ import android.content.Context;
|
|
20
20
|
import android.content.res.AssetFileDescriptor;
|
21
21
|
import android.content.res.AssetManager;
|
22
22
|
import android.media.AudioManager;
|
23
|
+
import android.net.Uri;
|
23
24
|
import android.os.Build;
|
24
25
|
import android.os.ParcelFileDescriptor;
|
25
26
|
import android.util.Log;
|
@@ -419,13 +420,15 @@ public class NativeAudio
|
|
419
420
|
private void preloadAsset(PluginCall call) {
|
420
421
|
double volume = 1.0;
|
421
422
|
int audioChannelNum = 1;
|
423
|
+
JSObject status = new JSObject();
|
424
|
+
status.put("STATUS", "OK");
|
422
425
|
|
423
426
|
try {
|
424
427
|
initSoundPool();
|
425
428
|
|
426
429
|
String audioId = call.getString(ASSET_ID);
|
427
430
|
|
428
|
-
boolean
|
431
|
+
boolean isLocalUrl = Boolean.TRUE.equals(call.getBoolean("isUrl", false));
|
429
432
|
|
430
433
|
if (!isStringValid(audioId)) {
|
431
434
|
call.reject(ERROR_AUDIO_ID_MISSING + " - " + audioId);
|
@@ -457,7 +460,7 @@ public class NativeAudio
|
|
457
460
|
}
|
458
461
|
|
459
462
|
AssetFileDescriptor assetFileDescriptor;
|
460
|
-
if (
|
463
|
+
if (isLocalUrl) {
|
461
464
|
File f = new File(new URI(fullPath));
|
462
465
|
ParcelFileDescriptor p = ParcelFileDescriptor.open(
|
463
466
|
f,
|
@@ -465,13 +468,41 @@ public class NativeAudio
|
|
465
468
|
);
|
466
469
|
assetFileDescriptor = new AssetFileDescriptor(p, 0, -1);
|
467
470
|
} else {
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
+
try {
|
472
|
+
Uri uri = Uri.parse(fullPath); // Now Uri class should be recognized
|
473
|
+
if (
|
474
|
+
uri.getScheme() != null &&
|
475
|
+
(
|
476
|
+
uri.getScheme().equals("http") ||
|
477
|
+
uri.getScheme().equals("https")
|
478
|
+
)
|
479
|
+
) {
|
480
|
+
// It's a remote URL
|
481
|
+
RemoteAudioAsset remoteAudioAsset = new RemoteAudioAsset(
|
482
|
+
this,
|
483
|
+
audioId,
|
484
|
+
uri,
|
485
|
+
audioChannelNum,
|
486
|
+
(float) volume
|
487
|
+
);
|
488
|
+
audioAssetList.put(audioId, remoteAudioAsset);
|
489
|
+
call.resolve(status);
|
490
|
+
return;
|
491
|
+
} else {
|
492
|
+
// It's a local file path
|
493
|
+
// Check if fullPath starts with "public/" and prepend if necessary
|
494
|
+
if (!fullPath.startsWith("public/")) {
|
495
|
+
fullPath = "public/".concat(fullPath);
|
496
|
+
}
|
497
|
+
Context ctx = getContext().getApplicationContext(); // Use getContext() directly
|
498
|
+
AssetManager am = ctx.getResources().getAssets();
|
499
|
+
// Remove the redefinition of assetFileDescriptor
|
500
|
+
assetFileDescriptor = am.openFd(fullPath);
|
501
|
+
}
|
502
|
+
} catch (Exception e) {
|
503
|
+
call.reject("Error loading audio", e);
|
504
|
+
return;
|
471
505
|
}
|
472
|
-
Context ctx = (Application) this.getContext().getApplicationContext();
|
473
|
-
AssetManager am = ctx.getResources().getAssets();
|
474
|
-
assetFileDescriptor = am.openFd(fullPath);
|
475
506
|
}
|
476
507
|
|
477
508
|
AudioAsset asset = new AudioAsset(
|
@@ -483,8 +514,6 @@ public class NativeAudio
|
|
483
514
|
);
|
484
515
|
audioAssetList.put(audioId, asset);
|
485
516
|
|
486
|
-
JSObject status = new JSObject();
|
487
|
-
status.put("STATUS", "OK");
|
488
517
|
call.resolve(status);
|
489
518
|
} else {
|
490
519
|
call.reject(ERROR_AUDIO_EXISTS);
|
@@ -0,0 +1,87 @@
|
|
1
|
+
package ee.forgr.audio;
|
2
|
+
|
3
|
+
import android.media.MediaPlayer;
|
4
|
+
import android.net.Uri;
|
5
|
+
|
6
|
+
public class RemoteAudioAsset extends AudioAsset {
|
7
|
+
|
8
|
+
private MediaPlayer mediaPlayer;
|
9
|
+
|
10
|
+
public RemoteAudioAsset(
|
11
|
+
NativeAudio owner,
|
12
|
+
String assetId,
|
13
|
+
Uri uri,
|
14
|
+
int audioChannelNum,
|
15
|
+
float volume
|
16
|
+
) throws Exception {
|
17
|
+
super(owner, assetId, null, audioChannelNum, volume);
|
18
|
+
mediaPlayer = new MediaPlayer();
|
19
|
+
mediaPlayer.setDataSource(owner.getContext(), uri);
|
20
|
+
mediaPlayer.setVolume(volume, volume);
|
21
|
+
mediaPlayer.prepareAsync(); // Prepare asynchronously to not block the main thread
|
22
|
+
}
|
23
|
+
|
24
|
+
@Override
|
25
|
+
public void play(Double time) throws Exception {
|
26
|
+
if (mediaPlayer != null) {
|
27
|
+
if (time != null) {
|
28
|
+
mediaPlayer.seekTo((int) (time * 1000));
|
29
|
+
}
|
30
|
+
mediaPlayer.start();
|
31
|
+
} else {
|
32
|
+
throw new Exception("MediaPlayer is null");
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
@Override
|
37
|
+
public boolean pause() throws Exception {
|
38
|
+
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
|
39
|
+
mediaPlayer.pause();
|
40
|
+
return true; // Return true to indicate that the audio was paused
|
41
|
+
}
|
42
|
+
return false;
|
43
|
+
}
|
44
|
+
|
45
|
+
@Override
|
46
|
+
public void resume() throws Exception {
|
47
|
+
if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
|
48
|
+
mediaPlayer.start();
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
@Override
|
53
|
+
public void stop() throws Exception {
|
54
|
+
if (mediaPlayer != null) {
|
55
|
+
mediaPlayer.stop();
|
56
|
+
mediaPlayer.prepare(); // Call prepare to reset the MediaPlayer to the Idle state
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
@Override
|
61
|
+
public void loop() throws Exception {
|
62
|
+
if (mediaPlayer != null) {
|
63
|
+
mediaPlayer.setLooping(true);
|
64
|
+
mediaPlayer.start();
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
@Override
|
69
|
+
public void unload() throws Exception {
|
70
|
+
if (mediaPlayer != null) {
|
71
|
+
mediaPlayer.release();
|
72
|
+
mediaPlayer = null;
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
@Override
|
77
|
+
public void setVolume(float volume) throws Exception {
|
78
|
+
if (mediaPlayer != null) {
|
79
|
+
mediaPlayer.setVolume(volume, volume);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
@Override
|
84
|
+
public boolean isPlaying() throws Exception {
|
85
|
+
return mediaPlayer != null && mediaPlayer.isPlaying();
|
86
|
+
}
|
87
|
+
}
|
@@ -32,6 +32,7 @@ public class AudioAsset: NSObject, AVAudioPlayerDelegate {
|
|
32
32
|
for _ in 0..<channels {
|
33
33
|
do {
|
34
34
|
let player: AVAudioPlayer! = try AVAudioPlayer(contentsOf: pathUrl)
|
35
|
+
player.delegate = owner
|
35
36
|
|
36
37
|
if player != nil {
|
37
38
|
player.enableRate = true
|
@@ -75,7 +76,7 @@ public class AudioAsset: NSObject, AVAudioPlayerDelegate {
|
|
75
76
|
player = channels[playIndex]
|
76
77
|
player.currentTime = time
|
77
78
|
player.numberOfLoops = 0
|
78
|
-
if
|
79
|
+
if delay > 0 {
|
79
80
|
player.play(atTime: player.deviceCurrentTime + delay)
|
80
81
|
} else {
|
81
82
|
player.play()
|
package/ios/Plugin/Plugin.swift
CHANGED
@@ -10,7 +10,7 @@ enum MyError: Error {
|
|
10
10
|
/// Please read the Capacitor iOS Plugin Development Guide
|
11
11
|
/// here: https://capacitor.ionicframework.com/docs/plugins/ios
|
12
12
|
@objc(NativeAudio)
|
13
|
-
public class NativeAudio: CAPPlugin {
|
13
|
+
public class NativeAudio: CAPPlugin, AVAudioPlayerDelegate {
|
14
14
|
|
15
15
|
var audioList: [String: Any] = [:]
|
16
16
|
var fadeMusic = false
|
@@ -101,6 +101,26 @@ public class NativeAudio: CAPPlugin {
|
|
101
101
|
preloadAsset(call, isComplex: true)
|
102
102
|
}
|
103
103
|
|
104
|
+
func activateSession() {
|
105
|
+
do {
|
106
|
+
try self.session.setActive(true)
|
107
|
+
} catch {
|
108
|
+
print("Failed to set session active")
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
func endSession() {
|
113
|
+
do {
|
114
|
+
try self.session.setActive(false, options: .notifyOthersOnDeactivation)
|
115
|
+
} catch {
|
116
|
+
print("Failed to deactivate audio session")
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
121
|
+
self.endSession()
|
122
|
+
}
|
123
|
+
|
104
124
|
@objc func play(_ call: CAPPluginCall) {
|
105
125
|
let audioId = call.getString(Constant.AssetIdKey) ?? ""
|
106
126
|
let time = call.getDouble("time") ?? 0
|
@@ -115,7 +135,7 @@ public class NativeAudio: CAPPlugin {
|
|
115
135
|
if asset != nil {
|
116
136
|
if asset is AudioAsset {
|
117
137
|
let audioAsset = asset as? AudioAsset
|
118
|
-
|
138
|
+
self.activateSession()
|
119
139
|
if self.fadeMusic {
|
120
140
|
audioAsset?.playWithFade(time: time)
|
121
141
|
} else {
|
@@ -124,6 +144,7 @@ public class NativeAudio: CAPPlugin {
|
|
124
144
|
call.resolve()
|
125
145
|
} else if asset is Int32 {
|
126
146
|
let audioAsset = asset as? NSNumber ?? 0
|
147
|
+
self.activateSession()
|
127
148
|
AudioServicesPlaySystemSound(SystemSoundID(audioAsset.intValue))
|
128
149
|
call.resolve()
|
129
150
|
} else {
|
@@ -175,7 +196,7 @@ public class NativeAudio: CAPPlugin {
|
|
175
196
|
guard let audioAsset: AudioAsset = self.getAudioAsset(call) else {
|
176
197
|
return
|
177
198
|
}
|
178
|
-
|
199
|
+
self.activateSession()
|
179
200
|
audioAsset.resume()
|
180
201
|
call.resolve()
|
181
202
|
}
|
@@ -186,6 +207,7 @@ public class NativeAudio: CAPPlugin {
|
|
186
207
|
}
|
187
208
|
|
188
209
|
audioAsset.pause()
|
210
|
+
self.endSession()
|
189
211
|
call.resolve()
|
190
212
|
}
|
191
213
|
|
@@ -194,6 +216,7 @@ public class NativeAudio: CAPPlugin {
|
|
194
216
|
|
195
217
|
do {
|
196
218
|
try stopAudio(audioId: audioId)
|
219
|
+
self.endSession()
|
197
220
|
} catch {
|
198
221
|
call.reject(Constant.ErrorAssetNotFound)
|
199
222
|
}
|
@@ -260,7 +283,7 @@ public class NativeAudio: CAPPlugin {
|
|
260
283
|
let channels: Int?
|
261
284
|
let volume: Float?
|
262
285
|
let delay: Float?
|
263
|
-
|
286
|
+
var isLocalUrl: Bool = call.getBool("isUrl") ?? false // Existing flag for local URLs
|
264
287
|
|
265
288
|
if audioId != "" {
|
266
289
|
var assetPath: String = call.getString(Constant.AssetPathKey) ?? ""
|
@@ -269,12 +292,11 @@ public class NativeAudio: CAPPlugin {
|
|
269
292
|
volume = call.getFloat("volume") ?? 1.0
|
270
293
|
channels = call.getInt("channels") ?? 1
|
271
294
|
delay = call.getFloat("delay") ?? 1.0
|
272
|
-
isUrl = call.getBool("isUrl") ?? false
|
273
295
|
} else {
|
274
296
|
channels = 0
|
275
297
|
volume = 0
|
276
298
|
delay = 0
|
277
|
-
|
299
|
+
isLocalUrl = false
|
278
300
|
}
|
279
301
|
|
280
302
|
if audioList.isEmpty {
|
@@ -286,7 +308,12 @@ public class NativeAudio: CAPPlugin {
|
|
286
308
|
queue.async {
|
287
309
|
if asset == nil {
|
288
310
|
var basePath: String?
|
289
|
-
if
|
311
|
+
if let url = URL(string: assetPath), url.scheme != nil {
|
312
|
+
// Handle remote URL
|
313
|
+
let remoteAudioAsset = RemoteAudioAsset(owner: self, withAssetId: audioId, withPath: assetPath, withChannels: channels, withVolume: volume, withFadeDelay: delay)
|
314
|
+
self.audioList[audioId] = remoteAudioAsset
|
315
|
+
call.resolve()
|
316
|
+
} else if isLocalUrl == false {
|
290
317
|
// if assetPath dont start with public/ add it
|
291
318
|
assetPath = assetPath.starts(with: "public/") ? assetPath : "public/" + assetPath
|
292
319
|
|
@@ -0,0 +1,74 @@
|
|
1
|
+
import AVFoundation
|
2
|
+
|
3
|
+
public class RemoteAudioAsset: AudioAsset {
|
4
|
+
var playerItem: AVPlayerItem?
|
5
|
+
var player: AVPlayer?
|
6
|
+
|
7
|
+
override init(owner: NativeAudio, withAssetId assetId: String, withPath path: String!, withChannels channels: Int!, withVolume volume: Float!, withFadeDelay delay: Float!) {
|
8
|
+
super.init(owner: owner, withAssetId: assetId, withPath: path, withChannels: channels, withVolume: volume, withFadeDelay: delay)
|
9
|
+
|
10
|
+
if let url = URL(string: path) {
|
11
|
+
self.playerItem = AVPlayerItem(url: url)
|
12
|
+
self.player = AVPlayer(playerItem: self.playerItem)
|
13
|
+
self.player?.volume = volume
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
override func play(time: TimeInterval, delay: TimeInterval) {
|
18
|
+
guard let player = self.player else { return }
|
19
|
+
if delay > 0 {
|
20
|
+
// Calculate the future time to play
|
21
|
+
let timeToPlay = CMTimeAdd(CMTimeMakeWithSeconds(player.currentTime().seconds, preferredTimescale: 1), CMTimeMakeWithSeconds(delay, preferredTimescale: 1))
|
22
|
+
player.seek(to: timeToPlay)
|
23
|
+
} else {
|
24
|
+
player.seek(to: CMTimeMakeWithSeconds(time, preferredTimescale: 1))
|
25
|
+
}
|
26
|
+
player.play()
|
27
|
+
}
|
28
|
+
|
29
|
+
override func pause() {
|
30
|
+
player?.pause()
|
31
|
+
}
|
32
|
+
|
33
|
+
override func resume() {
|
34
|
+
player?.play()
|
35
|
+
}
|
36
|
+
|
37
|
+
override func stop() {
|
38
|
+
player?.pause()
|
39
|
+
player?.seek(to: CMTime.zero)
|
40
|
+
}
|
41
|
+
|
42
|
+
override func loop() {
|
43
|
+
player?.actionAtItemEnd = .none
|
44
|
+
NotificationCenter.default.addObserver(self,
|
45
|
+
selector: #selector(playerItemDidReachEnd(notification:)),
|
46
|
+
name: .AVPlayerItemDidPlayToEndTime,
|
47
|
+
object: player?.currentItem)
|
48
|
+
player?.play()
|
49
|
+
}
|
50
|
+
|
51
|
+
@objc func playerItemDidReachEnd(notification: Notification) {
|
52
|
+
player?.seek(to: CMTime.zero)
|
53
|
+
player?.play()
|
54
|
+
}
|
55
|
+
|
56
|
+
override func unload() {
|
57
|
+
stop()
|
58
|
+
NotificationCenter.default.removeObserver(self)
|
59
|
+
player = nil
|
60
|
+
playerItem = nil
|
61
|
+
}
|
62
|
+
|
63
|
+
override func setVolume(volume: NSNumber!) {
|
64
|
+
player?.volume = volume.floatValue
|
65
|
+
}
|
66
|
+
|
67
|
+
override func setRate(rate: NSNumber!) {
|
68
|
+
player?.rate = rate.floatValue
|
69
|
+
}
|
70
|
+
|
71
|
+
override func isPlaying() -> Bool {
|
72
|
+
return player?.timeControlStatus == .playing
|
73
|
+
}
|
74
|
+
}
|