@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.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/audio-asset.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from \"@capacitor/core\";\nconst NativeAudio = registerPlugin(\"NativeAudio\", {\n web: () => import(\"./web\").then((m) => new m.NativeAudioWeb()),\n});\nexport * from \"./definitions\";\nexport { NativeAudio };\n//# sourceMappingURL=index.js.map","export class AudioAsset {\n constructor(audio) {\n this.audio = audio;\n }\n}\n//# sourceMappingURL=audio-asset.js.map","import { WebPlugin } from \"@capacitor/core\";\nimport { AudioAsset } from \"./audio-asset\";\nexport class NativeAudioWeb extends WebPlugin {\n constructor() {\n super();\n }\n async resume(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n if (audio.paused) {\n return audio.play();\n }\n }\n async pause(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n return audio.pause();\n }\n async setCurrentTime(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n audio.currentTime = options.time;\n return;\n }\n async getCurrentTime(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n return { currentTime: audio.currentTime };\n }\n async getDuration(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n if (Number.isNaN(audio.duration)) {\n throw \"no duration available\";\n }\n if (!Number.isFinite(audio.duration)) {\n throw \"duration not available => media resource is streaming\";\n }\n return { duration: audio.duration };\n }\n async configure(options) {\n throw `configure is not supported for web: ${JSON.stringify(options)}`;\n }\n async isPreloaded(options) {\n try {\n return { found: !!this.getAudioAsset(options.assetId) };\n }\n catch (e) {\n return { found: false };\n }\n }\n async preload(options) {\n var _a;\n if (NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.has(options.assetId)) {\n throw \"AssetId already exists. Unload first if like to change!\";\n }\n if (!((_a = options.assetPath) === null || _a === void 0 ? void 0 : _a.length)) {\n throw \"no assetPath provided\";\n }\n if (!options.isUrl &&\n !new RegExp(\"^/?\" + NativeAudioWeb.FILE_LOCATION).test(options.assetPath)) {\n const slashPrefix = options.assetPath.startsWith(\"/\") ? \"\" : \"/\";\n options.assetPath = `${NativeAudioWeb.FILE_LOCATION}${slashPrefix}${options.assetPath}`;\n }\n const audio = new Audio(options.assetPath);\n audio.autoplay = false;\n audio.loop = false;\n audio.preload = \"auto\";\n if (options.volume) {\n audio.volume = options.volume;\n }\n NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.set(options.assetId, new AudioAsset(audio));\n }\n onEnded(assetId) {\n this.notifyListeners(\"complete\", { assetId });\n }\n async play(options) {\n const { assetId, time = 0 } = options;\n const audio = this.getAudioAsset(assetId).audio;\n await this.stop(options);\n audio.loop = false;\n audio.currentTime = time;\n audio.addEventListener(\"ended\", () => this.onEnded(assetId), {\n once: true,\n });\n return audio.play();\n }\n async loop(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n await this.stop(options);\n audio.loop = true;\n return audio.play();\n }\n async stop(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n audio.pause();\n audio.loop = false;\n audio.currentTime = 0;\n }\n async unload(options) {\n await this.stop(options);\n NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.delete(options.assetId);\n }\n async setVolume(options) {\n if (typeof (options === null || options === void 0 ? void 0 : options.volume) !== \"number\") {\n throw \"no volume provided\";\n }\n const audio = this.getAudioAsset(options.assetId).audio;\n audio.volume = options.volume;\n }\n async setRate(options) {\n if (typeof (options === null || options === void 0 ? void 0 : options.rate) !== \"number\") {\n throw \"no rate provided\";\n }\n const audio = this.getAudioAsset(options.assetId).audio;\n audio.playbackRate = options.rate;\n }\n async isPlaying(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n return { isPlaying: !audio.paused };\n }\n getAudioAsset(assetId) {\n this.checkAssetId(assetId);\n if (!NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.has(assetId)) {\n throw `no asset for assetId \"${assetId}\" available. Call preload first!`;\n }\n return NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.get(assetId);\n }\n checkAssetId(assetId) {\n if (typeof assetId !== \"string\") {\n throw \"assetId must be a string\";\n }\n if (!(assetId === null || assetId === void 0 ? void 0 : assetId.length)) {\n throw \"no assetId provided\";\n }\n }\n}\nNativeAudioWeb.FILE_LOCATION = \"\";\nNativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID = new Map();\nconst NativeAudio = new NativeAudioWeb();\nexport { NativeAudio };\n//# sourceMappingURL=web.js.map"],"names":["NativeAudio","registerPlugin","WebPlugin"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/audio-asset.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst NativeAudio = registerPlugin('NativeAudio', {\n web: () => import('./web').then((m) => new m.NativeAudioWeb()),\n});\nexport * from './definitions';\nexport { NativeAudio };\n//# sourceMappingURL=index.js.map","export class AudioAsset {\n constructor(audio) {\n this.audio = audio;\n }\n}\n//# sourceMappingURL=audio-asset.js.map","import { WebPlugin } from '@capacitor/core';\nimport { AudioAsset } from './audio-asset';\nexport class NativeAudioWeb extends WebPlugin {\n constructor() {\n super();\n }\n async resume(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n if (audio.paused) {\n return audio.play();\n }\n }\n async pause(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n return audio.pause();\n }\n async setCurrentTime(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n audio.currentTime = options.time;\n return;\n }\n async getCurrentTime(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n return { currentTime: audio.currentTime };\n }\n async getDuration(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n if (Number.isNaN(audio.duration)) {\n throw 'no duration available';\n }\n if (!Number.isFinite(audio.duration)) {\n throw 'duration not available => media resource is streaming';\n }\n return { duration: audio.duration };\n }\n async configure(options) {\n throw `configure is not supported for web: ${JSON.stringify(options)}`;\n }\n async isPreloaded(options) {\n try {\n return { found: !!this.getAudioAsset(options.assetId) };\n }\n catch (e) {\n return { found: false };\n }\n }\n async preload(options) {\n var _a;\n if (NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.has(options.assetId)) {\n throw 'AssetId already exists. Unload first if like to change!';\n }\n if (!((_a = options.assetPath) === null || _a === void 0 ? void 0 : _a.length)) {\n throw 'no assetPath provided';\n }\n if (!options.isUrl && !new RegExp('^/?' + NativeAudioWeb.FILE_LOCATION).test(options.assetPath)) {\n const slashPrefix = options.assetPath.startsWith('/') ? '' : '/';\n options.assetPath = `${NativeAudioWeb.FILE_LOCATION}${slashPrefix}${options.assetPath}`;\n }\n const audio = new Audio(options.assetPath);\n audio.autoplay = false;\n audio.loop = false;\n audio.preload = 'auto';\n if (options.volume) {\n audio.volume = options.volume;\n }\n NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.set(options.assetId, new AudioAsset(audio));\n }\n onEnded(assetId) {\n this.notifyListeners('complete', { assetId });\n }\n async play(options) {\n const { assetId, time = 0 } = options;\n const audio = this.getAudioAsset(assetId).audio;\n await this.stop(options);\n audio.loop = false;\n audio.currentTime = time;\n audio.addEventListener('ended', () => this.onEnded(assetId), {\n once: true,\n });\n return audio.play();\n }\n async loop(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n await this.stop(options);\n audio.loop = true;\n return audio.play();\n }\n async stop(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n audio.pause();\n audio.loop = false;\n audio.currentTime = 0;\n }\n async unload(options) {\n await this.stop(options);\n NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.delete(options.assetId);\n }\n async setVolume(options) {\n if (typeof (options === null || options === void 0 ? void 0 : options.volume) !== 'number') {\n throw 'no volume provided';\n }\n const audio = this.getAudioAsset(options.assetId).audio;\n audio.volume = options.volume;\n }\n async setRate(options) {\n if (typeof (options === null || options === void 0 ? void 0 : options.rate) !== 'number') {\n throw 'no rate provided';\n }\n const audio = this.getAudioAsset(options.assetId).audio;\n audio.playbackRate = options.rate;\n }\n async isPlaying(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n return { isPlaying: !audio.paused };\n }\n async clearCache() {\n // Web audio doesn't have a persistent cache to clear\n return;\n }\n getAudioAsset(assetId) {\n this.checkAssetId(assetId);\n if (!NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.has(assetId)) {\n throw `no asset for assetId \"${assetId}\" available. Call preload first!`;\n }\n return NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.get(assetId);\n }\n checkAssetId(assetId) {\n if (typeof assetId !== 'string') {\n throw 'assetId must be a string';\n }\n if (!(assetId === null || assetId === void 0 ? void 0 : assetId.length)) {\n throw 'no assetId provided';\n }\n }\n}\nNativeAudioWeb.FILE_LOCATION = '';\nNativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID = new Map();\nconst NativeAudio = new NativeAudioWeb();\nexport { NativeAudio };\n//# sourceMappingURL=web.js.map"],"names":["NativeAudio","registerPlugin","WebPlugin"],"mappings":"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"}
@@ -8,6 +8,10 @@
8
8
 
9
9
  import AVFoundation
10
10
 
11
+ /**
12
+ * AudioAsset class handles local audio playback via AVAudioPlayer
13
+ * Supports volume control, fade effects, rate changes, and looping
14
+ */
11
15
  public class AudioAsset: NSObject, AVAudioPlayerDelegate {
12
16
 
13
17
  var channels: [AVAudioPlayer] = []
@@ -15,190 +19,410 @@ public class AudioAsset: NSObject, AVAudioPlayerDelegate {
15
19
  var assetId: String = ""
16
20
  var initialVolume: Float = 1.0
17
21
  var fadeDelay: Float = 1.0
18
- var owner: NativeAudio
22
+ weak var owner: NativeAudio?
19
23
 
24
+ // Constants for fade effect
20
25
  let FADESTEP: Float = 0.05
21
26
  let FADEDELAY: Float = 0.08
22
27
 
28
+ // Maximum number of channels to prevent excessive resource usage
29
+ private let MAX_CHANNELS = Constant.MaxChannels
30
+
31
+ private var currentTimeTimer: Timer?
32
+ internal var fadeTimer: Timer?
33
+
34
+ /**
35
+ * Initialize a new audio asset
36
+ * - Parameters:
37
+ * - owner: The plugin that owns this asset
38
+ * - assetId: Unique identifier for this asset
39
+ * - path: File path to the audio file
40
+ * - channels: Number of simultaneous playback channels (polyphony)
41
+ * - volume: Initial volume (0.0-1.0)
42
+ * - delay: Fade delay in seconds
43
+ */
23
44
  init(owner: NativeAudio, withAssetId assetId: String, withPath path: String!, withChannels channels: Int!, withVolume volume: Float!, withFadeDelay delay: Float!) {
24
45
 
25
46
  self.owner = owner
26
47
  self.assetId = assetId
27
48
  self.channels = []
49
+ self.initialVolume = min(max(volume ?? Constant.DefaultVolume, Constant.MinVolume), Constant.MaxVolume) // Validate volume range
50
+ self.fadeDelay = max(delay ?? Constant.DefaultFadeDelay, 0.0) // Ensure non-negative delay
28
51
 
29
52
  super.init()
30
53
 
31
- let pathUrl: URL = URL(string: path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)!
32
- for _ in 0..<channels {
33
- do {
34
- let player: AVAudioPlayer! = try AVAudioPlayer(contentsOf: pathUrl)
35
- player.delegate = owner
54
+ guard let encodedPath = path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
55
+ print("Failed to encode path: \(String(describing: path))")
56
+ return
57
+ }
58
+
59
+ // Try to create URL from string first, fall back to file URL if that fails
60
+ let pathUrl: URL
61
+ if let url = URL(string: encodedPath) {
62
+ pathUrl = url
63
+ } else {
64
+ pathUrl = URL(fileURLWithPath: encodedPath)
65
+ }
66
+
67
+ // Limit channels to a reasonable maximum to prevent resource issues
68
+ let channelCount = min(max(channels ?? 1, 1), MAX_CHANNELS)
36
69
 
37
- if player != nil {
70
+ owner.executeOnAudioQueue { [self] in
71
+ for _ in 0..<channelCount {
72
+ do {
73
+ let player = try AVAudioPlayer(contentsOf: pathUrl)
74
+ player.delegate = self
38
75
  player.enableRate = true
39
- player.volume = volume
76
+ player.volume = self.initialVolume
77
+ player.rate = 1.0
40
78
  player.prepareToPlay()
41
79
  self.channels.append(player)
42
- if channels == 1 {
43
- player.delegate = self
44
- }
80
+ } catch {
81
+ print("Error loading audio file: \(error.localizedDescription)")
82
+ print("Path: \(String(describing: path))")
45
83
  }
46
- } catch let error as NSError {
47
- print(error.debugDescription)
48
- print("Error loading \(String(describing: path))")
49
84
  }
50
85
  }
51
86
  }
52
87
 
53
- func getCurrentTime() -> TimeInterval {
54
- if channels.count != 1 {
55
- return 0
88
+ deinit {
89
+ stopCurrentTimeUpdates()
90
+ stopFadeTimer()
91
+ // Clean up any players that might still be playing
92
+ for player in channels {
93
+ if player.isPlaying {
94
+ player.stop()
95
+ }
56
96
  }
57
- let player: AVAudioPlayer = channels[playIndex]
97
+ }
58
98
 
59
- return player.currentTime
99
+ /**
100
+ * Get the current playback time
101
+ * - Returns: Current time in seconds
102
+ */
103
+ func getCurrentTime() -> TimeInterval {
104
+ var result: TimeInterval = 0
105
+ owner?.executeOnAudioQueue { [self] in
106
+ if channels.isEmpty || playIndex >= channels.count {
107
+ result = 0
108
+ return
109
+ }
110
+ let player = channels[playIndex]
111
+ result = player.currentTime
112
+ }
113
+ return result
60
114
  }
61
115
 
116
+ /**
117
+ * Set the current playback time
118
+ * - Parameter time: Time in seconds
119
+ */
62
120
  func setCurrentTime(time: TimeInterval) {
63
- if channels.count != 1 {
64
- return
121
+ owner?.executeOnAudioQueue { [self] in
122
+ if channels.isEmpty || playIndex >= channels.count {
123
+ return
124
+ }
125
+ let player = channels[playIndex]
126
+ // Ensure time is valid
127
+ let validTime = min(max(time, 0), player.duration)
128
+ player.currentTime = validTime
65
129
  }
66
-
67
- let player: AVAudioPlayer = channels[playIndex]
68
-
69
- player.currentTime = time
70
130
  }
71
131
 
132
+ /**
133
+ * Get the total duration of the audio file
134
+ * - Returns: Duration in seconds
135
+ */
72
136
  func getDuration() -> TimeInterval {
73
- if channels.count != 1 {
74
- return 0
137
+ var result: TimeInterval = 0
138
+ owner?.executeOnAudioQueue { [self] in
139
+ if channels.isEmpty || playIndex >= channels.count {
140
+ result = 0
141
+ return
142
+ }
143
+ let player = channels[playIndex]
144
+ result = player.duration
75
145
  }
76
-
77
- let player: AVAudioPlayer = channels[playIndex]
78
-
79
- return player.duration
146
+ return result
80
147
  }
81
148
 
149
+ /**
150
+ * Play the audio from the specified time with optional delay
151
+ * - Parameters:
152
+ * - time: Start time in seconds
153
+ * - delay: Delay before playback in seconds
154
+ */
82
155
  func play(time: TimeInterval, delay: TimeInterval) {
83
- let player: AVAudioPlayer
156
+ owner?.executeOnAudioQueue { [self] in
157
+ guard !channels.isEmpty else { return }
84
158
 
85
- if channels.count > 0 {
86
- player = channels[playIndex]
87
- player.currentTime = time
159
+ // Reset play index if it's out of bounds
160
+ if playIndex >= channels.count {
161
+ playIndex = 0
162
+ }
163
+
164
+ let player = channels[playIndex]
165
+ // Ensure time is within valid range
166
+ let validTime = min(max(time, 0), player.duration)
167
+ player.currentTime = validTime
88
168
  player.numberOfLoops = 0
89
- if delay > 0 {
90
- player.play(atTime: player.deviceCurrentTime + delay)
169
+
170
+ // Use a valid delay (non-negative)
171
+ let validDelay = max(delay, 0)
172
+
173
+ if validDelay > 0 {
174
+ player.play(atTime: player.deviceCurrentTime + validDelay)
91
175
  } else {
92
176
  player.play()
93
177
  }
94
- playIndex += 1
95
- playIndex = playIndex % channels.count
178
+ playIndex = (playIndex + 1) % channels.count
179
+ startCurrentTimeUpdates()
96
180
  }
97
181
  }
98
182
 
99
183
  func playWithFade(time: TimeInterval) {
100
- let player: AVAudioPlayer = channels[playIndex]
101
- player.currentTime = time
184
+ owner?.executeOnAudioQueue { [self] in
185
+ guard !channels.isEmpty else { return }
102
186
 
103
- if !player.isPlaying {
104
- player.numberOfLoops = 0
105
- player.volume = 0
106
- player.play()
107
- playIndex += 1
108
- playIndex = playIndex % channels.count
109
- } else {
110
- if player.volume < initialVolume {
111
- player.volume += self.FADESTEP
187
+ // Reset play index if it's out of bounds
188
+ if playIndex >= channels.count {
189
+ playIndex = 0
190
+ }
191
+
192
+ let player = channels[playIndex]
193
+ player.currentTime = time
194
+
195
+ if !player.isPlaying {
196
+ player.numberOfLoops = 0
197
+ player.volume = 0 // Start with volume at 0
198
+ player.play()
199
+ playIndex = (playIndex + 1) % channels.count
200
+ startCurrentTimeUpdates()
201
+
202
+ // Start fade-in
203
+ startVolumeRamp(from: 0, to: initialVolume, player: player)
204
+ } else {
205
+ if player.volume < initialVolume {
206
+ // Continue fade-in if already in progress
207
+ startVolumeRamp(from: player.volume, to: initialVolume, player: player)
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ private func startVolumeRamp(from startVolume: Float, to endVolume: Float, player: AVAudioPlayer) {
214
+ stopFadeTimer()
215
+
216
+ let steps = abs(endVolume - startVolume) / FADESTEP
217
+ guard steps > 0 else { return }
218
+
219
+ let timeInterval = FADEDELAY / steps
220
+ var currentStep = 0
221
+ let totalSteps = Int(ceil(steps))
222
+
223
+ player.volume = startVolume
224
+
225
+ fadeTimer = Timer.scheduledTimer(withTimeInterval: TimeInterval(timeInterval), repeats: true) { [weak self, weak player] timer in
226
+ guard let strongSelf = self, let strongPlayer = player else {
227
+ timer.invalidate()
228
+ return
229
+ }
230
+
231
+ currentStep += 1
232
+ let progress = Float(currentStep) / Float(totalSteps)
233
+ let newVolume = startVolume + progress * (endVolume - startVolume)
234
+
235
+ strongPlayer.volume = newVolume
236
+
237
+ if currentStep >= totalSteps {
238
+ strongPlayer.volume = endVolume
239
+ timer.invalidate()
240
+ strongSelf.fadeTimer = nil
112
241
  }
113
242
  }
243
+ RunLoop.current.add(fadeTimer!, forMode: .common)
244
+ }
114
245
 
246
+ internal func stopFadeTimer() {
247
+ DispatchQueue.main.async { [weak self] in
248
+ self?.fadeTimer?.invalidate()
249
+ self?.fadeTimer = nil
250
+ }
115
251
  }
116
252
 
117
253
  func pause() {
118
- let player: AVAudioPlayer = channels[playIndex]
119
- player.pause()
254
+ owner?.executeOnAudioQueue { [self] in
255
+ stopCurrentTimeUpdates()
256
+
257
+ // Check for valid playIndex
258
+ guard !channels.isEmpty && playIndex < channels.count else { return }
259
+
260
+ let player = channels[playIndex]
261
+ player.pause()
262
+ }
120
263
  }
121
264
 
122
265
  func resume() {
123
- let player: AVAudioPlayer = channels[playIndex]
124
-
125
- let timeOffset = player.deviceCurrentTime + 0.01
126
- player.play(atTime: timeOffset)
266
+ owner?.executeOnAudioQueue { [self] in
267
+ // Check for valid playIndex
268
+ guard !channels.isEmpty && playIndex < channels.count else { return }
269
+
270
+ let player = channels[playIndex]
271
+ let timeOffset = player.deviceCurrentTime + 0.01
272
+ player.play(atTime: timeOffset)
273
+ startCurrentTimeUpdates()
274
+ }
127
275
  }
128
276
 
129
277
  func stop() {
130
- for player in channels {
131
- player.stop()
278
+ owner?.executeOnAudioQueue { [self] in
279
+ stopCurrentTimeUpdates()
280
+ stopFadeTimer()
281
+
282
+ for player in channels {
283
+ if player.isPlaying {
284
+ player.stop()
285
+ }
286
+ player.currentTime = 0
287
+ player.numberOfLoops = 0
288
+ }
289
+ playIndex = 0
132
290
  }
133
291
  }
134
292
 
135
293
  func stopWithFade() {
136
- let player: AVAudioPlayer = channels[playIndex]
294
+ // Store current player locally to avoid race conditions with playIndex
295
+ owner?.executeOnAudioQueue { [self] in
296
+ guard !channels.isEmpty && playIndex < channels.count else {
297
+ stop()
298
+ return
299
+ }
137
300
 
138
- if !player.isPlaying {
139
- player.currentTime = 0.0
140
- player.numberOfLoops = 0
141
- player.volume = 0
142
- player.play()
143
- playIndex += 1
144
- playIndex = playIndex % channels.count
145
- } else {
146
- if player.volume < initialVolume {
147
- player.volume += self.FADESTEP
301
+ let player = channels[playIndex]
302
+ if player.isPlaying && player.volume > 0 {
303
+ startVolumeRamp(from: player.volume, to: 0, player: player)
304
+
305
+ // Schedule the stop when fade is complete
306
+ DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(Int(FADEDELAY * 1000))) { [weak self, weak player] in
307
+ guard let strongSelf = self, let strongPlayer = player else { return }
308
+
309
+ if strongPlayer.volume < strongSelf.FADESTEP {
310
+ strongSelf.stop()
311
+ }
312
+ }
313
+ } else {
314
+ stop()
148
315
  }
149
316
  }
150
317
  }
151
318
 
152
319
  func loop() {
153
- self.stop()
320
+ owner?.executeOnAudioQueue { [self] in
321
+ self.stop()
322
+
323
+ guard !channels.isEmpty && playIndex < channels.count else { return }
154
324
 
155
- let player: AVAudioPlayer = channels[playIndex]
156
- player.numberOfLoops = -1
157
- player.play()
158
- playIndex += 1
159
- playIndex = playIndex % channels.count
325
+ let player = channels[playIndex]
326
+ player.delegate = self
327
+ player.numberOfLoops = -1
328
+ player.play()
329
+ playIndex = (playIndex + 1) % channels.count
330
+ startCurrentTimeUpdates()
331
+ }
160
332
  }
161
333
 
162
334
  func unload() {
163
- self.stop()
164
-
165
- // for i in 0..<channels.count {
166
- // var player: AVAudioPlayer! = channels.object(at: i) as? AVAudioPlayer
167
- //
168
- // player = nil
169
- // }
170
- channels = []
335
+ owner?.executeOnAudioQueue { [self] in
336
+ self.stop()
337
+ stopCurrentTimeUpdates()
338
+ stopFadeTimer()
339
+ channels = []
340
+ }
171
341
  }
172
342
 
343
+ /**
344
+ * Set the volume for all audio channels
345
+ * - Parameter volume: Volume level (0.0-1.0)
346
+ */
173
347
  func setVolume(volume: NSNumber!) {
174
- for player in channels {
175
- player.volume = volume.floatValue
348
+ owner?.executeOnAudioQueue { [self] in
349
+ // Ensure volume is in valid range
350
+ let validVolume = min(max(volume.floatValue, Constant.MinVolume), Constant.MaxVolume)
351
+ for player in channels {
352
+ player.volume = validVolume
353
+ }
176
354
  }
177
355
  }
178
356
 
357
+ /**
358
+ * Set the playback rate for all audio channels
359
+ * - Parameter rate: Playback rate (0.5-2.0 is typical range)
360
+ */
179
361
  func setRate(rate: NSNumber!) {
180
- for player in channels {
181
- player.rate = rate.floatValue
362
+ owner?.executeOnAudioQueue { [self] in
363
+ // Ensure rate is in valid range
364
+ let validRate = min(max(rate.floatValue, Constant.MinRate), Constant.MaxRate)
365
+ for player in channels {
366
+ player.rate = validRate
367
+ }
182
368
  }
183
369
  }
184
370
 
185
371
  public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
186
- NSLog("playerDidFinish")
187
- self.owner.notifyListeners("complete", data: [
188
- "assetId": self.assetId
189
- ])
372
+ owner?.executeOnAudioQueue { [self] in
373
+ self.owner?.notifyListeners("complete", data: [
374
+ "assetId": self.assetId
375
+ ])
376
+ }
190
377
  }
191
378
 
192
379
  func playerDecodeError(player: AVAudioPlayer!, error: NSError!) {
193
-
380
+ if let error = error {
381
+ print("AudioAsset decode error: \(error.localizedDescription)")
382
+ }
194
383
  }
195
384
 
196
385
  func isPlaying() -> Bool {
197
- if channels.count != 1 {
198
- return false
386
+ var result: Bool = false
387
+ owner?.executeOnAudioQueue { [self] in
388
+ if channels.isEmpty || playIndex >= channels.count {
389
+ result = false
390
+ return
391
+ }
392
+ let player = channels[playIndex]
393
+ result = player.isPlaying
199
394
  }
395
+ return result
396
+ }
200
397
 
201
- let player: AVAudioPlayer = channels[playIndex]
202
- return player.isPlaying
398
+ internal func startCurrentTimeUpdates() {
399
+ DispatchQueue.main.async { [weak self] in
400
+ guard let strongSelf = self else { return }
401
+
402
+ strongSelf.stopCurrentTimeUpdates() // Ensure no duplicate timers
403
+
404
+ strongSelf.currentTimeTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
405
+ guard let strongSelf = self, let strongOwner = strongSelf.owner else {
406
+ self?.stopCurrentTimeUpdates()
407
+ return
408
+ }
409
+
410
+ if strongSelf.isPlaying() {
411
+ strongOwner.notifyCurrentTime(strongSelf)
412
+ } else {
413
+ strongSelf.stopCurrentTimeUpdates()
414
+ }
415
+ }
416
+ if let timer = strongSelf.currentTimeTimer {
417
+ RunLoop.current.add(timer, forMode: .common)
418
+ }
419
+ }
420
+ }
421
+
422
+ internal func stopCurrentTimeUpdates() {
423
+ DispatchQueue.main.async { [weak self] in
424
+ self?.currentTimeTimer?.invalidate()
425
+ self?.currentTimeTimer = nil
426
+ }
203
427
  }
204
428
  }
@@ -7,6 +7,7 @@
7
7
  //
8
8
 
9
9
  public class Constant {
10
+ // Parameter keys
10
11
  public static let FadeKey = "fade"
11
12
  public static let FocusAudio = "focus"
12
13
  public static let AssetPathKey = "assetPath"
@@ -17,6 +18,18 @@ public class Constant {
17
18
  public static let Background = "background"
18
19
  public static let IgnoreSilent = "ignoreSilent"
19
20
 
21
+ // Default values - used for consistency across the plugin
22
+ public static let DefaultVolume: Float = 1.0
23
+ public static let DefaultRate: Float = 1.0
24
+ public static let DefaultChannels: Int = 1
25
+ public static let DefaultFadeDelay: Float = 1.0
26
+ public static let MinRate: Float = 0.25
27
+ public static let MaxRate: Float = 4.0
28
+ public static let MinVolume: Float = 0.0
29
+ public static let MaxVolume: Float = 1.0
30
+ public static let MaxChannels: Int = 32
31
+
32
+ // Error messages
20
33
  public static let ErrorAssetId = "Asset Id is missing"
21
34
  public static let ErrorAssetPath = "Asset Path is missing"
22
35
  public static let ErrorAssetNotFound = "Asset is not loaded"