@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/dist/plugin.js.map
CHANGED
@@ -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
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
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 =
|
76
|
+
player.volume = self.initialVolume
|
77
|
+
player.rate = 1.0
|
40
78
|
player.prepareToPlay()
|
41
79
|
self.channels.append(player)
|
42
|
-
|
43
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
97
|
+
}
|
58
98
|
|
59
|
-
|
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
|
-
|
64
|
-
|
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
|
-
|
74
|
-
|
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
|
-
|
156
|
+
owner?.executeOnAudioQueue { [self] in
|
157
|
+
guard !channels.isEmpty else { return }
|
84
158
|
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
90
|
-
|
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
|
95
|
-
|
178
|
+
playIndex = (playIndex + 1) % channels.count
|
179
|
+
startCurrentTimeUpdates()
|
96
180
|
}
|
97
181
|
}
|
98
182
|
|
99
183
|
func playWithFade(time: TimeInterval) {
|
100
|
-
|
101
|
-
|
184
|
+
owner?.executeOnAudioQueue { [self] in
|
185
|
+
guard !channels.isEmpty else { return }
|
102
186
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
119
|
-
|
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
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
131
|
-
|
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
|
-
|
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
|
-
|
139
|
-
player.
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
320
|
+
owner?.executeOnAudioQueue { [self] in
|
321
|
+
self.stop()
|
322
|
+
|
323
|
+
guard !channels.isEmpty && playIndex < channels.count else { return }
|
154
324
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
175
|
-
|
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
|
-
|
181
|
-
|
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
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
198
|
-
|
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
|
-
|
202
|
-
|
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"
|