@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.
@@ -4,115 +4,270 @@ public class RemoteAudioAsset: AudioAsset {
4
4
  var playerItems: [AVPlayerItem] = []
5
5
  var players: [AVPlayer] = []
6
6
  var playerObservers: [NSKeyValueObservation] = []
7
+ var duration: TimeInterval = 0
8
+ var asset: AVURLAsset?
7
9
 
8
10
  override init(owner: NativeAudio, withAssetId assetId: String, withPath path: String!, withChannels channels: Int!, withVolume volume: Float!, withFadeDelay delay: Float!) {
9
- super.init(owner: owner, withAssetId: assetId, withPath: path, withChannels: channels, withVolume: volume, withFadeDelay: delay)
10
-
11
- if let url = URL(string: path) {
12
- for _ in 0..<channels {
13
- let playerItem = AVPlayerItem(url: url)
14
- let player = AVPlayer(playerItem: playerItem)
15
- player.volume = volume
16
- self.playerItems.append(playerItem)
17
- self.players.append(player)
18
-
19
- // Add observer for playback finished
20
- let observer = player.observe(\.timeControlStatus) { [weak self] player, _ in
21
- if player.timeControlStatus == .paused && player.currentItem?.currentTime() == player.currentItem?.duration {
22
- self?.playerDidFinishPlaying(player: player)
11
+ super.init(owner: owner, withAssetId: assetId, withPath: path, withChannels: channels ?? 1, withVolume: volume ?? 1.0, withFadeDelay: delay ?? 0.0)
12
+
13
+ owner.executeOnAudioQueue { [self] in
14
+ if let url = URL(string: path) {
15
+ let asset = AVURLAsset(url: url, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
16
+ self.asset = asset
17
+
18
+ for _ in 0..<(channels ?? 1) {
19
+ let playerItem = AVPlayerItem(asset: asset)
20
+ let player = AVPlayer(playerItem: playerItem)
21
+ player.volume = volume ?? 1.0
22
+ player.rate = 1.0
23
+ self.playerItems.append(playerItem)
24
+ self.players.append(player)
25
+
26
+ // Add observer for duration
27
+ let durationObserver = playerItem.observe(\.status) { [weak self] item, _ in
28
+ self?.owner.executeOnAudioQueue { [self] in
29
+ if item.status == .readyToPlay {
30
+ self?.duration = item.duration.seconds
31
+ }
32
+ }
23
33
  }
34
+ self.playerObservers.append(durationObserver)
35
+
36
+ // Add observer for playback finished
37
+ let observer = player.observe(\.timeControlStatus) { [weak self] player, _ in
38
+ self?.owner.executeOnAudioQueue { [self] in
39
+ if player.timeControlStatus == .paused && player.currentItem?.currentTime() == player.currentItem?.duration {
40
+ self?.playerDidFinishPlaying(player: player)
41
+ }
42
+ }
43
+ }
44
+ self.playerObservers.append(observer)
24
45
  }
25
- self.playerObservers.append(observer)
26
46
  }
27
47
  }
28
48
  }
29
49
 
30
50
  func playerDidFinishPlaying(player: AVPlayer) {
31
- self.owner.notifyListeners("complete", data: [
32
- "assetId": self.assetId
33
- ])
51
+ owner.executeOnAudioQueue { [self] in
52
+ self.owner.notifyListeners("complete", data: [
53
+ "assetId": self.assetId
54
+ ])
55
+ }
34
56
  }
35
57
 
36
58
  override func play(time: TimeInterval, delay: TimeInterval) {
37
- guard !players.isEmpty else { return }
38
- let player = players[playIndex]
39
- if delay > 0 {
40
- let timeToPlay = CMTimeAdd(CMTimeMakeWithSeconds(player.currentTime().seconds, preferredTimescale: 1), CMTimeMakeWithSeconds(delay, preferredTimescale: 1))
41
- player.seek(to: timeToPlay)
42
- } else {
43
- player.seek(to: CMTimeMakeWithSeconds(time, preferredTimescale: 1))
59
+ owner.executeOnAudioQueue { [self] in
60
+ guard !players.isEmpty else { return }
61
+ let player = players[playIndex]
62
+ if delay > 0 {
63
+ // Convert delay to CMTime and add to current time
64
+ let currentTime = player.currentTime()
65
+ let delayTime = CMTimeMakeWithSeconds(delay, preferredTimescale: currentTime.timescale)
66
+ let timeToPlay = CMTimeAdd(currentTime, delayTime)
67
+ player.seek(to: timeToPlay)
68
+ } else {
69
+ player.seek(to: CMTimeMakeWithSeconds(time, preferredTimescale: 1))
70
+ }
71
+ player.play()
72
+ playIndex = (playIndex + 1) % players.count
73
+ NSLog("RemoteAudioAsset: About to start timer updates")
74
+ startCurrentTimeUpdates()
44
75
  }
45
- player.play()
46
- playIndex = (playIndex + 1) % players.count
47
76
  }
48
77
 
49
78
  override func pause() {
50
- guard !players.isEmpty else { return }
51
- let player = players[playIndex]
52
- player.pause()
79
+ owner.executeOnAudioQueue { [self] in
80
+ guard !players.isEmpty else { return }
81
+ let player = players[playIndex]
82
+ player.pause()
83
+ stopCurrentTimeUpdates()
84
+ }
53
85
  }
54
86
 
55
87
  override func resume() {
56
- guard !players.isEmpty else { return }
57
- let player = players[playIndex]
58
- player.play()
88
+ owner.executeOnAudioQueue { [self] in
89
+ guard !players.isEmpty else { return }
90
+ let player = players[playIndex]
91
+ player.play()
92
+ NSLog("RemoteAudioAsset Resume: About to start timer updates")
93
+ startCurrentTimeUpdates() // Add timer start
94
+ }
59
95
  }
60
96
 
61
97
  override func stop() {
62
- for player in players {
63
- player.pause()
64
- player.seek(to: CMTime.zero)
98
+ owner.executeOnAudioQueue { [self] in
99
+ stopCurrentTimeUpdates() // Stop timer first
100
+ for player in players {
101
+ // First pause
102
+ player.pause()
103
+ // Then reset to beginning
104
+ player.seek(to: .zero, completionHandler: { _ in
105
+ // Reset any loop settings
106
+ player.actionAtItemEnd = .pause
107
+ })
108
+ }
109
+ // Reset playback state
110
+ playIndex = 0
65
111
  }
66
112
  }
67
113
 
68
114
  override func loop() {
69
- for player in players {
70
- player.actionAtItemEnd = .none
71
- NotificationCenter.default.addObserver(self,
72
- selector: #selector(playerItemDidReachEnd(notification:)),
73
- name: .AVPlayerItemDidPlayToEndTime,
74
- object: player.currentItem)
75
- player.play()
115
+ owner.executeOnAudioQueue { [self] in
116
+ for player in players {
117
+ player.actionAtItemEnd = .none
118
+ NotificationCenter.default.removeObserver(self,
119
+ name: .AVPlayerItemDidPlayToEndTime,
120
+ object: player.currentItem)
121
+ NotificationCenter.default.addObserver(self,
122
+ selector: #selector(playerItemDidReachEnd(notification:)),
123
+ name: .AVPlayerItemDidPlayToEndTime,
124
+ object: player.currentItem)
125
+ player.seek(to: .zero)
126
+ player.play()
127
+ }
128
+ NSLog("RemoteAudioAsset Loop: About to start timer updates")
129
+ startCurrentTimeUpdates() // Add timer start
76
130
  }
77
131
  }
78
132
 
79
133
  @objc func playerItemDidReachEnd(notification: Notification) {
80
- guard let player = notification.object as? AVPlayer else { return }
81
- player.seek(to: CMTime.zero)
82
- player.play()
134
+ owner.executeOnAudioQueue { [self] in
135
+ if let playerItem = notification.object as? AVPlayerItem,
136
+ let player = players.first(where: { $0.currentItem == playerItem }) {
137
+ player.seek(to: .zero)
138
+ player.play()
139
+ }
140
+ }
83
141
  }
84
142
 
85
143
  override func unload() {
86
- stop()
87
- NotificationCenter.default.removeObserver(self)
88
- // Remove KVO observers
89
- for observer in playerObservers {
90
- observer.invalidate()
144
+ owner.executeOnAudioQueue { [self] in
145
+ stopCurrentTimeUpdates()
146
+ stop()
147
+ NotificationCenter.default.removeObserver(self)
148
+ // Remove KVO observers
149
+ for observer in playerObservers {
150
+ observer.invalidate()
151
+ }
152
+ playerObservers = []
153
+ players = []
154
+ playerItems = []
91
155
  }
92
- playerObservers = []
93
- players = []
94
- playerItems = []
95
156
  }
96
157
 
97
158
  override func setVolume(volume: NSNumber!) {
98
- for player in players {
99
- player.volume = volume.floatValue
159
+ owner.executeOnAudioQueue { [self] in
160
+ for player in players {
161
+ player.volume = volume.floatValue
162
+ }
100
163
  }
101
164
  }
102
165
 
103
166
  override func setRate(rate: NSNumber!) {
104
- for player in players {
105
- player.rate = rate.floatValue
167
+ owner.executeOnAudioQueue { [self] in
168
+ for player in players {
169
+ player.rate = rate.floatValue
170
+ }
106
171
  }
107
172
  }
108
173
 
109
174
  override func isPlaying() -> Bool {
110
- return players.contains { $0.timeControlStatus == .playing }
175
+ var result = false
176
+ owner.executeOnAudioQueue { [self] in
177
+ guard !players.isEmpty else {
178
+ result = false
179
+ return
180
+ }
181
+ let player = players[playIndex]
182
+ result = player.timeControlStatus == .playing
183
+ }
184
+ return result
111
185
  }
112
186
 
113
187
  override func getCurrentTime() -> TimeInterval {
114
- guard !players.isEmpty else { return 0 }
115
- let player = players[playIndex]
116
- return player.currentTime().seconds
188
+ var result: TimeInterval = 0
189
+ owner.executeOnAudioQueue { [self] in
190
+ guard !players.isEmpty else {
191
+ result = 0
192
+ return
193
+ }
194
+ let player = players[playIndex]
195
+ result = player.currentTime().seconds
196
+ }
197
+ return result
198
+ }
199
+
200
+ override func getDuration() -> TimeInterval {
201
+ var result: TimeInterval = 0
202
+ owner.executeOnAudioQueue { [self] in
203
+ guard !players.isEmpty else {
204
+ result = 0
205
+ return
206
+ }
207
+ let player = players[playIndex]
208
+ if player.currentItem?.duration == CMTime.indefinite {
209
+ result = 0
210
+ return
211
+ }
212
+ result = player.currentItem?.duration.seconds ?? 0
213
+ }
214
+ return result
215
+ }
216
+
217
+ override func playWithFade(time: TimeInterval) {
218
+ owner.executeOnAudioQueue { [self] in
219
+ guard !players.isEmpty else { return }
220
+ let player = players[playIndex]
221
+
222
+ if player.timeControlStatus != .playing {
223
+ player.seek(to: CMTimeMakeWithSeconds(time, preferredTimescale: 1))
224
+ player.volume = initialVolume
225
+ player.play()
226
+ playIndex = (playIndex + 1) % players.count
227
+ NSLog("RemoteAudioAsset PlayWithFade: About to start timer updates")
228
+ startCurrentTimeUpdates()
229
+ } else {
230
+ if player.volume < initialVolume {
231
+ player.volume += self.FADESTEP
232
+ }
233
+ }
234
+ }
235
+ }
236
+
237
+ override func stopWithFade() {
238
+ owner.executeOnAudioQueue { [self] in
239
+ guard !players.isEmpty else { return }
240
+ let player = players[playIndex]
241
+
242
+ if player.timeControlStatus == .playing {
243
+ if player.volume > self.FADESTEP {
244
+ player.volume -= self.FADESTEP
245
+ // Schedule next fade step
246
+ DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(Int(self.FADEDELAY * 1000))) { [weak self] in
247
+ self?.stopWithFade()
248
+ }
249
+ } else {
250
+ // Volume is near 0, actually stop
251
+ player.volume = 0
252
+ self.stop()
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ static func clearCache() {
259
+ DispatchQueue.global(qos: .background).sync {
260
+ let urls = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
261
+ if let cachePath = urls.first {
262
+ do {
263
+ let fileURLs = try FileManager.default.contentsOfDirectory(at: cachePath, includingPropertiesForKeys: nil)
264
+ for fileURL in fileURLs where fileURL.pathExtension == "mp3" || fileURL.pathExtension == "wav" {
265
+ try FileManager.default.removeItem(at: fileURL)
266
+ }
267
+ } catch {
268
+ print("Error clearing audio cache: \(error)")
269
+ }
270
+ }
271
+ }
117
272
  }
118
273
  }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@capgo/native-audio",
3
- "version": "7.1.7",
3
+ "version": "7.3.0",
4
4
  "description": "A native plugin for native audio engine",
5
- "main": "dist/plugin.js",
5
+ "license": "MIT",
6
+ "main": "dist/plugin.cjs.js",
6
7
  "module": "dist/esm/index.js",
7
8
  "types": "dist/esm/index.d.ts",
8
9
  "unpkg": "dist/plugin.js",
@@ -13,11 +14,20 @@
13
14
  "ios/Plugin/",
14
15
  "CapgoNativeAudio.podspec"
15
16
  ],
17
+ "author": "Martin Donadieu",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/Cap-go/native-audio.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/Cap-go/native-audio/issues"
24
+ },
16
25
  "keywords": [
17
26
  "capacitor",
18
27
  "plugin",
19
28
  "audio",
20
29
  "media",
30
+ "capgo",
21
31
  "native"
22
32
  ],
23
33
  "scripts": {
@@ -28,7 +38,7 @@
28
38
  "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
29
39
  "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --autocorrect --format",
30
40
  "eslint": "eslint . --ext .ts",
31
- "prettier": "prettier --config .prettierrc.js \"**/*.{css,html,ts,js,java}\"",
41
+ "prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
32
42
  "swiftlint": "node-swiftlint",
33
43
  "docgen": "docgen --api NativeAudio --output-readme README.md --output-json dist/docs.json",
34
44
  "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
@@ -36,8 +46,6 @@
36
46
  "watch": "tsc --watch",
37
47
  "prepublishOnly": "npm run build"
38
48
  },
39
- "author": "Martin Donadieu <martin@capgo.app>",
40
- "license": "MIT",
41
49
  "devDependencies": {
42
50
  "@capacitor/android": "^7.0.0",
43
51
  "@capacitor/cli": "^7.0.0",
@@ -61,6 +69,11 @@
61
69
  "peerDependencies": {
62
70
  "@capacitor/core": ">=7.0.0"
63
71
  },
72
+ "prettier": "@ionic/prettier-config",
73
+ "swiftlint": "@ionic/swiftlint-config",
74
+ "eslintConfig": {
75
+ "extends": "@ionic/eslint-config/recommended"
76
+ },
64
77
  "capacitor": {
65
78
  "ios": {
66
79
  "src": "ios"
@@ -68,20 +81,5 @@
68
81
  "android": {
69
82
  "src": "android"
70
83
  }
71
- },
72
- "eslintConfig": {
73
- "extends": "@ionic/eslint-config/recommended"
74
- },
75
- "prettier": "@ionic/prettier-config",
76
- "swiftlint": "@ionic/swiftlint-config",
77
- "repository": {
78
- "type": "git",
79
- "url": "https://github.com/Cap-go/native-audio"
80
- },
81
- "bugs": {
82
- "url": "https://github.com/Cap-go/native-audio/issues"
83
- },
84
- "publishConfig": {
85
- "access": "public"
86
84
  }
87
85
  }