@capgo/native-audio 8.2.11 → 8.2.13
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 +147 -34
- package/android/build.gradle +1 -1
- package/android/src/main/java/ee/forgr/audio/AudioAsset.java +352 -74
- package/android/src/main/java/ee/forgr/audio/AudioDispatcher.java +24 -3
- package/android/src/main/java/ee/forgr/audio/Constant.java +9 -1
- package/android/src/main/java/ee/forgr/audio/Logger.java +55 -0
- package/android/src/main/java/ee/forgr/audio/NativeAudio.java +336 -57
- package/android/src/main/java/ee/forgr/audio/RemoteAudioAsset.java +307 -98
- package/android/src/main/java/ee/forgr/audio/StreamAudioAsset.java +285 -96
- package/dist/docs.json +307 -41
- package/dist/esm/definitions.d.ts +116 -38
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +52 -41
- package/dist/esm/web.js +386 -41
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +386 -41
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +386 -41
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/NativeAudioPlugin/AudioAsset+Fade.swift +104 -0
- package/ios/Sources/NativeAudioPlugin/AudioAsset.swift +168 -324
- package/ios/Sources/NativeAudioPlugin/Constant.swift +17 -4
- package/ios/Sources/NativeAudioPlugin/Logger.swift +43 -0
- package/ios/Sources/NativeAudioPlugin/Plugin.swift +176 -87
- package/ios/Sources/NativeAudioPlugin/RemoteAudioAsset+Fade.swift +110 -0
- package/ios/Sources/NativeAudioPlugin/RemoteAudioAsset.swift +117 -273
- package/ios/Tests/NativeAudioPluginTests/PluginTests.swift +47 -72
- package/package.json +1 -1
package/dist/plugin.cjs.js
CHANGED
|
@@ -15,21 +15,62 @@ class AudioAsset {
|
|
|
15
15
|
class NativeAudioWeb extends core.WebPlugin {
|
|
16
16
|
constructor() {
|
|
17
17
|
super();
|
|
18
|
+
this.debugMode = false;
|
|
19
|
+
this.currentTimeIntervals = new Map();
|
|
20
|
+
this.zeroVolume = 0.0001;
|
|
18
21
|
}
|
|
19
22
|
async resume(options) {
|
|
23
|
+
var _a, _b;
|
|
20
24
|
const audio = this.getAudioAsset(options.assetId).audio;
|
|
25
|
+
const data = this.getAudioAssetData(options.assetId);
|
|
26
|
+
const targetVolume = (_b = (_a = data.volumeBeforePause) !== null && _a !== void 0 ? _a : data.volume) !== null && _b !== void 0 ? _b : 1;
|
|
27
|
+
if (options.fadeIn) {
|
|
28
|
+
const fadeDuration = options.fadeInDuration || NativeAudioWeb.DEFAULT_FADE_DURATION_SEC;
|
|
29
|
+
this.doFadeIn(audio, fadeDuration, targetVolume);
|
|
30
|
+
}
|
|
31
|
+
else if (audio.volume <= this.zeroVolume) {
|
|
32
|
+
audio.volume = targetVolume;
|
|
33
|
+
this.setGainNodeVolume(audio, targetVolume);
|
|
34
|
+
}
|
|
35
|
+
this.clearFadeOutToStopTimer(options.assetId);
|
|
36
|
+
return this.doResume(options.assetId);
|
|
37
|
+
}
|
|
38
|
+
async doResume(assetId) {
|
|
39
|
+
const audio = this.getAudioAsset(assetId).audio;
|
|
40
|
+
this.startCurrentTimeUpdates(assetId);
|
|
21
41
|
if (audio.paused) {
|
|
22
42
|
return audio.play();
|
|
23
43
|
}
|
|
24
44
|
}
|
|
25
45
|
async pause(options) {
|
|
46
|
+
var _a;
|
|
26
47
|
const audio = this.getAudioAsset(options.assetId).audio;
|
|
27
|
-
|
|
48
|
+
this.cancelGainNodeRamp(audio);
|
|
49
|
+
const data = this.getAudioAssetData(options.assetId);
|
|
50
|
+
data.volumeBeforePause = (_a = data.volume) !== null && _a !== void 0 ? _a : audio.volume;
|
|
51
|
+
this.setAudioAssetData(options.assetId, data);
|
|
52
|
+
if (!audio.paused && options.fadeOut) {
|
|
53
|
+
const fadeOutDuration = options.fadeOutDuration || NativeAudioWeb.DEFAULT_FADE_DURATION_SEC;
|
|
54
|
+
this.doFadeOut(audio, fadeOutDuration);
|
|
55
|
+
data.fadeOutToStopTimer = window.setTimeout(() => {
|
|
56
|
+
this.doPause(options.assetId).catch(() => {
|
|
57
|
+
// no-op
|
|
58
|
+
});
|
|
59
|
+
}, fadeOutDuration * 1000);
|
|
60
|
+
this.setAudioAssetData(options.assetId, data);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
return this.doPause(options.assetId);
|
|
64
|
+
}
|
|
65
|
+
async doPause(assetId) {
|
|
66
|
+
const audio = this.getAudioAsset(assetId).audio;
|
|
67
|
+
this.clearFadeOutToStopTimer(assetId);
|
|
68
|
+
this.stopCurrentTimeUpdates(assetId);
|
|
69
|
+
audio.pause();
|
|
28
70
|
}
|
|
29
71
|
async setCurrentTime(options) {
|
|
30
72
|
const audio = this.getAudioAsset(options.assetId).audio;
|
|
31
73
|
audio.currentTime = options.time;
|
|
32
|
-
return;
|
|
33
74
|
}
|
|
34
75
|
async getCurrentTime(options) {
|
|
35
76
|
const audio = this.getAudioAsset(options.assetId).audio;
|
|
@@ -45,6 +86,12 @@ class NativeAudioWeb extends core.WebPlugin {
|
|
|
45
86
|
}
|
|
46
87
|
return { duration: audio.duration };
|
|
47
88
|
}
|
|
89
|
+
async setDebugMode(options) {
|
|
90
|
+
this.debugMode = options.enabled;
|
|
91
|
+
if (this.debugMode) {
|
|
92
|
+
this.logInfo('Debug mode enabled');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
48
95
|
async configure(options) {
|
|
49
96
|
throw `configure is not supported for web: ${JSON.stringify(options)}`;
|
|
50
97
|
}
|
|
@@ -52,55 +99,70 @@ class NativeAudioWeb extends core.WebPlugin {
|
|
|
52
99
|
try {
|
|
53
100
|
return { found: !!this.getAudioAsset(options.assetId) };
|
|
54
101
|
}
|
|
55
|
-
catch (
|
|
102
|
+
catch (_a) {
|
|
56
103
|
return { found: false };
|
|
57
104
|
}
|
|
58
105
|
}
|
|
59
106
|
async preload(options) {
|
|
60
107
|
var _a;
|
|
108
|
+
this.logInfo(`Preloading audio asset with options: ${JSON.stringify(options)}`);
|
|
61
109
|
if (NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.has(options.assetId)) {
|
|
62
110
|
throw 'AssetId already exists. Unload first if like to change!';
|
|
63
111
|
}
|
|
64
112
|
if (!((_a = options.assetPath) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
65
113
|
throw 'no assetPath provided';
|
|
66
114
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
audio.
|
|
77
|
-
|
|
78
|
-
|
|
115
|
+
NativeAudioWeb.AUDIO_PRELOAD_OPTIONS_MAP.set(options.assetId, options);
|
|
116
|
+
await new Promise((resolve, reject) => {
|
|
117
|
+
var _a;
|
|
118
|
+
if (!options.isUrl && !new RegExp('^/?' + NativeAudioWeb.FILE_LOCATION).test(options.assetPath)) {
|
|
119
|
+
const slashPrefix = options.assetPath.startsWith('/') ? '' : '/';
|
|
120
|
+
options.assetPath = `${NativeAudioWeb.FILE_LOCATION}${slashPrefix}${options.assetPath}`;
|
|
121
|
+
}
|
|
122
|
+
const audio = document.createElement('audio');
|
|
123
|
+
audio.id = options.assetId;
|
|
124
|
+
audio.crossOrigin = 'anonymous';
|
|
125
|
+
audio.src = options.assetPath;
|
|
126
|
+
audio.autoplay = false;
|
|
127
|
+
audio.loop = false;
|
|
128
|
+
audio.preload = 'metadata';
|
|
129
|
+
audio.addEventListener('loadedmetadata', () => {
|
|
130
|
+
resolve();
|
|
131
|
+
});
|
|
132
|
+
audio.addEventListener('error', (errEvt) => {
|
|
133
|
+
this.logError(`Error loading audio file: ${options.assetPath}, error: ${String(errEvt)}`);
|
|
134
|
+
reject('Error loading audio file');
|
|
135
|
+
});
|
|
136
|
+
const data = this.getAudioAssetData(options.assetId);
|
|
137
|
+
if (typeof options.volume === 'number') {
|
|
138
|
+
audio.volume = options.volume;
|
|
139
|
+
data.volume = options.volume;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
data.volume = audio.volume;
|
|
143
|
+
}
|
|
144
|
+
NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.set(options.assetId, new AudioAsset(audio));
|
|
145
|
+
this.setAudioAssetData(options.assetId, data);
|
|
146
|
+
this.setGainNodeVolume(audio, (_a = data.volume) !== null && _a !== void 0 ? _a : 1);
|
|
147
|
+
});
|
|
79
148
|
}
|
|
80
149
|
async playOnce(options) {
|
|
81
150
|
var _a;
|
|
82
|
-
// Generate a unique temporary asset ID
|
|
83
151
|
const assetId = `playOnce_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
84
152
|
NativeAudioWeb.playOnceAssets.add(assetId);
|
|
85
|
-
const autoPlay = options.autoPlay !== false;
|
|
153
|
+
const autoPlay = options.autoPlay !== false;
|
|
86
154
|
const deleteAfterPlay = (_a = options.deleteAfterPlay) !== null && _a !== void 0 ? _a : false;
|
|
87
155
|
try {
|
|
88
|
-
// Preload the asset
|
|
89
156
|
await this.preload({
|
|
90
157
|
assetId,
|
|
91
158
|
assetPath: options.assetPath,
|
|
92
159
|
volume: options.volume,
|
|
93
160
|
isUrl: options.isUrl,
|
|
94
161
|
});
|
|
95
|
-
// Set up automatic cleanup on completion
|
|
96
|
-
const audio = this.getAudioAsset(assetId).audio;
|
|
97
162
|
const cleanupHandler = async () => {
|
|
98
163
|
try {
|
|
99
|
-
// Unload the asset
|
|
100
164
|
await this.unload({ assetId });
|
|
101
165
|
NativeAudioWeb.playOnceAssets.delete(assetId);
|
|
102
|
-
// Delete file if requested (Web can't actually delete files from disk)
|
|
103
|
-
// This is a no-op on web, but we keep the interface consistent
|
|
104
166
|
if (deleteAfterPlay) {
|
|
105
167
|
console.warn('[NativeAudio] deleteAfterPlay is not supported on web platform. File deletion is ignored.');
|
|
106
168
|
}
|
|
@@ -109,21 +171,23 @@ class NativeAudioWeb extends core.WebPlugin {
|
|
|
109
171
|
console.error('[NativeAudio] Error during playOnce cleanup:', error);
|
|
110
172
|
}
|
|
111
173
|
};
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
174
|
+
if (autoPlay) {
|
|
175
|
+
await this.doPlay({ assetId, volume: options.volume }, false);
|
|
176
|
+
}
|
|
177
|
+
const currentAudio = this.getAudioAsset(assetId).audio;
|
|
178
|
+
currentAudio.addEventListener('ended', () => {
|
|
179
|
+
cleanupHandler().catch((error) => {
|
|
180
|
+
console.error('[NativeAudio] Error during ended cleanup:', error);
|
|
181
|
+
});
|
|
182
|
+
}, { once: true });
|
|
183
|
+
currentAudio.addEventListener('error', () => {
|
|
115
184
|
cleanupHandler().catch((error) => {
|
|
116
185
|
console.error('[NativeAudio] Error during error cleanup:', error);
|
|
117
186
|
});
|
|
118
187
|
}, { once: true });
|
|
119
|
-
// Auto-play if requested
|
|
120
|
-
if (autoPlay) {
|
|
121
|
-
await this.play({ assetId });
|
|
122
|
-
}
|
|
123
188
|
return { assetId };
|
|
124
189
|
}
|
|
125
190
|
catch (error) {
|
|
126
|
-
// Cleanup on failure
|
|
127
191
|
try {
|
|
128
192
|
await this.unload({ assetId });
|
|
129
193
|
NativeAudioWeb.playOnceAssets.delete(assetId);
|
|
@@ -135,43 +199,196 @@ class NativeAudioWeb extends core.WebPlugin {
|
|
|
135
199
|
}
|
|
136
200
|
}
|
|
137
201
|
onEnded(assetId) {
|
|
202
|
+
this.logDebug(`Playback ended for assetId: ${assetId}`);
|
|
138
203
|
this.notifyListeners('complete', { assetId });
|
|
139
204
|
}
|
|
140
205
|
async play(options) {
|
|
206
|
+
this.logInfo(`Playing audio asset with options: ${JSON.stringify(options)}`);
|
|
207
|
+
this.clearFadeOutToStopTimer(options.assetId);
|
|
208
|
+
const { delay = 0 } = options;
|
|
209
|
+
if (delay > 0) {
|
|
210
|
+
const data = this.getAudioAssetData(options.assetId);
|
|
211
|
+
data.startTimer = window.setTimeout(() => {
|
|
212
|
+
this.doPlay(options).catch((error) => {
|
|
213
|
+
this.logError(`Delayed play failed: ${String(error)}`);
|
|
214
|
+
});
|
|
215
|
+
data.startTimer = undefined;
|
|
216
|
+
this.setAudioAssetData(options.assetId, data);
|
|
217
|
+
}, delay * 1000);
|
|
218
|
+
this.setAudioAssetData(options.assetId, data);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
await this.doPlay(options);
|
|
222
|
+
}
|
|
223
|
+
async doPlay(options, recreateAudioElement = true) {
|
|
141
224
|
const { assetId, time = 0 } = options;
|
|
225
|
+
if (!NativeAudioWeb.AUDIO_PRELOAD_OPTIONS_MAP.has(assetId)) {
|
|
226
|
+
throw `no asset for assetId "${assetId}" available. Call preload first!`;
|
|
227
|
+
}
|
|
228
|
+
if (recreateAudioElement) {
|
|
229
|
+
const preloadOptions = NativeAudioWeb.AUDIO_PRELOAD_OPTIONS_MAP.get(assetId);
|
|
230
|
+
await this.unload({ assetId });
|
|
231
|
+
await this.preload(preloadOptions);
|
|
232
|
+
}
|
|
142
233
|
const audio = this.getAudioAsset(assetId).audio;
|
|
143
|
-
|
|
234
|
+
audio.id = assetId;
|
|
144
235
|
audio.loop = false;
|
|
145
236
|
audio.currentTime = time;
|
|
146
237
|
audio.addEventListener('ended', () => this.onEnded(assetId), {
|
|
147
238
|
once: true,
|
|
148
239
|
});
|
|
149
|
-
|
|
240
|
+
const data = this.getAudioAssetData(assetId);
|
|
241
|
+
if (typeof options.volume === 'number') {
|
|
242
|
+
audio.volume = options.volume;
|
|
243
|
+
data.volume = options.volume;
|
|
244
|
+
this.setGainNodeVolume(audio, options.volume);
|
|
245
|
+
}
|
|
246
|
+
else if (typeof data.volume !== 'number') {
|
|
247
|
+
data.volume = audio.volume;
|
|
248
|
+
}
|
|
249
|
+
await audio.play();
|
|
250
|
+
this.startCurrentTimeUpdates(assetId);
|
|
251
|
+
if (options.fadeIn) {
|
|
252
|
+
this.logDebug(`Fading in audio asset with assetId: ${assetId}`);
|
|
253
|
+
const fadeDuration = options.fadeInDuration || NativeAudioWeb.DEFAULT_FADE_DURATION_SEC;
|
|
254
|
+
this.doFadeIn(audio, fadeDuration);
|
|
255
|
+
}
|
|
256
|
+
if (options.fadeOut && !Number.isNaN(audio.duration) && Number.isFinite(audio.duration)) {
|
|
257
|
+
this.logDebug(`Scheduling fade out for audio asset with assetId: ${assetId}`);
|
|
258
|
+
const fadeOutDuration = options.fadeOutDuration || NativeAudioWeb.DEFAULT_FADE_DURATION_SEC;
|
|
259
|
+
const fadeOutStartTime = options.fadeOutStartTime || audio.duration - fadeOutDuration;
|
|
260
|
+
data.fadeOut = true;
|
|
261
|
+
data.fadeOutStartTime = fadeOutStartTime;
|
|
262
|
+
data.fadeOutDuration = fadeOutDuration;
|
|
263
|
+
}
|
|
264
|
+
this.setAudioAssetData(assetId, data);
|
|
265
|
+
}
|
|
266
|
+
doFadeIn(audio, fadeDuration, targetVolume) {
|
|
267
|
+
var _a;
|
|
268
|
+
const data = this.getAudioAssetData(audio.id);
|
|
269
|
+
this.setGainNodeVolume(audio, this.zeroVolume);
|
|
270
|
+
const fadeToVolume = (_a = targetVolume !== null && targetVolume !== void 0 ? targetVolume : data.volume) !== null && _a !== void 0 ? _a : 1;
|
|
271
|
+
this.linearRampGainNodeVolume(audio, fadeToVolume, fadeDuration);
|
|
272
|
+
data.fadeInTimer = window.setTimeout(() => {
|
|
273
|
+
data.fadeInTimer = undefined;
|
|
274
|
+
this.setAudioAssetData(audio.id, data);
|
|
275
|
+
}, fadeDuration * 1000);
|
|
276
|
+
this.setAudioAssetData(audio.id, data);
|
|
277
|
+
}
|
|
278
|
+
doFadeOut(audio, fadeDuration) {
|
|
279
|
+
this.linearRampGainNodeVolume(audio, this.zeroVolume, fadeDuration);
|
|
150
280
|
}
|
|
151
281
|
async loop(options) {
|
|
282
|
+
this.logInfo(`Looping audio asset with options: ${JSON.stringify(options)}`);
|
|
152
283
|
const audio = this.getAudioAsset(options.assetId).audio;
|
|
153
|
-
|
|
284
|
+
this.reset(audio);
|
|
154
285
|
audio.loop = true;
|
|
286
|
+
this.startCurrentTimeUpdates(options.assetId);
|
|
155
287
|
return audio.play();
|
|
156
288
|
}
|
|
157
289
|
async stop(options) {
|
|
290
|
+
this.logInfo(`Stopping audio asset with options: ${JSON.stringify(options)}`);
|
|
158
291
|
const audio = this.getAudioAsset(options.assetId).audio;
|
|
292
|
+
const data = this.getAudioAssetData(options.assetId);
|
|
293
|
+
this.clearFadeOutToStopTimer(options.assetId);
|
|
294
|
+
this.cancelGainNodeRamp(audio);
|
|
295
|
+
if (!audio.paused && options.fadeOut) {
|
|
296
|
+
const fadeDuration = options.fadeOutDuration || NativeAudioWeb.DEFAULT_FADE_DURATION_SEC;
|
|
297
|
+
this.doFadeOut(audio, fadeDuration);
|
|
298
|
+
data.fadeOutToStopTimer = window.setTimeout(() => {
|
|
299
|
+
this.doStop(audio, options);
|
|
300
|
+
}, fadeDuration * 1000);
|
|
301
|
+
this.setAudioAssetData(options.assetId, data);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
this.doStop(audio, options);
|
|
305
|
+
}
|
|
306
|
+
doStop(audio, options) {
|
|
159
307
|
audio.pause();
|
|
160
|
-
|
|
308
|
+
this.onEnded(options.assetId);
|
|
309
|
+
this.reset(audio);
|
|
310
|
+
}
|
|
311
|
+
reset(audio) {
|
|
312
|
+
var _a;
|
|
161
313
|
audio.currentTime = 0;
|
|
314
|
+
for (const [assetId, asset] of NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.entries()) {
|
|
315
|
+
if (asset.audio === audio) {
|
|
316
|
+
this.stopCurrentTimeUpdates(assetId);
|
|
317
|
+
this.clearFadeOutToStopTimer(assetId);
|
|
318
|
+
this.clearStartTimer(assetId);
|
|
319
|
+
this.cancelGainNodeRamp(audio);
|
|
320
|
+
const data = this.getAudioAssetData(assetId);
|
|
321
|
+
const initialVolume = (_a = data.volume) !== null && _a !== void 0 ? _a : 1;
|
|
322
|
+
this.setGainNodeVolume(audio, initialVolume);
|
|
323
|
+
this.setAudioAssetData(assetId, data);
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
clearFadeOutToStopTimer(assetId) {
|
|
329
|
+
const data = this.getAudioAssetData(assetId);
|
|
330
|
+
if (data.fadeOutToStopTimer) {
|
|
331
|
+
clearTimeout(data.fadeOutToStopTimer);
|
|
332
|
+
data.fadeOutToStopTimer = undefined;
|
|
333
|
+
this.setAudioAssetData(assetId, data);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
clearStartTimer(assetId) {
|
|
337
|
+
const data = this.getAudioAssetData(assetId);
|
|
338
|
+
if (data.startTimer) {
|
|
339
|
+
clearTimeout(data.startTimer);
|
|
340
|
+
data.startTimer = undefined;
|
|
341
|
+
this.setAudioAssetData(assetId, data);
|
|
342
|
+
}
|
|
162
343
|
}
|
|
163
344
|
async unload(options) {
|
|
164
|
-
|
|
345
|
+
this.logInfo(`Unloading audio asset with options: ${JSON.stringify(options)}`);
|
|
346
|
+
const audio = this.getAudioAsset(options.assetId).audio;
|
|
347
|
+
this.reset(audio);
|
|
165
348
|
NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.delete(options.assetId);
|
|
349
|
+
NativeAudioWeb.AUDIO_PRELOAD_OPTIONS_MAP.delete(options.assetId);
|
|
350
|
+
NativeAudioWeb.AUDIO_DATA_MAP.delete(options.assetId);
|
|
351
|
+
this.cleanupAudioContext(audio);
|
|
352
|
+
}
|
|
353
|
+
cleanupAudioContext(audio) {
|
|
354
|
+
const gainNode = NativeAudioWeb.GAIN_NODE_MAP.get(audio);
|
|
355
|
+
if (gainNode) {
|
|
356
|
+
gainNode.disconnect();
|
|
357
|
+
NativeAudioWeb.GAIN_NODE_MAP.delete(audio);
|
|
358
|
+
}
|
|
359
|
+
const sourceNode = NativeAudioWeb.MEDIA_ELEMENT_SOURCE_MAP.get(audio);
|
|
360
|
+
if (sourceNode) {
|
|
361
|
+
sourceNode.disconnect();
|
|
362
|
+
NativeAudioWeb.MEDIA_ELEMENT_SOURCE_MAP.delete(audio);
|
|
363
|
+
}
|
|
364
|
+
const audioContext = NativeAudioWeb.AUDIO_CONTEXT_MAP.get(audio);
|
|
365
|
+
if (audioContext) {
|
|
366
|
+
audioContext.close().catch(() => {
|
|
367
|
+
// no-op
|
|
368
|
+
});
|
|
369
|
+
NativeAudioWeb.AUDIO_CONTEXT_MAP.delete(audio);
|
|
370
|
+
}
|
|
166
371
|
}
|
|
167
372
|
async setVolume(options) {
|
|
373
|
+
this.logInfo(`Setting volume for audio asset with options: ${JSON.stringify(options)}`);
|
|
168
374
|
if (typeof (options === null || options === void 0 ? void 0 : options.volume) !== 'number') {
|
|
169
375
|
throw 'no volume provided';
|
|
170
376
|
}
|
|
377
|
+
const { volume, duration = 0 } = options;
|
|
378
|
+
const data = this.getAudioAssetData(options.assetId);
|
|
379
|
+
data.volume = volume;
|
|
380
|
+
this.setAudioAssetData(options.assetId, data);
|
|
171
381
|
const audio = this.getAudioAsset(options.assetId).audio;
|
|
172
|
-
audio
|
|
382
|
+
this.cancelGainNodeRamp(audio);
|
|
383
|
+
if (duration > 0) {
|
|
384
|
+
this.exponentialRampGainNodeVolume(audio, volume, duration);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
audio.volume = volume;
|
|
388
|
+
this.setGainNodeVolume(audio, volume);
|
|
173
389
|
}
|
|
174
390
|
async setRate(options) {
|
|
391
|
+
this.logInfo(`Setting playback rate for audio asset with options: ${JSON.stringify(options)}`);
|
|
175
392
|
if (typeof (options === null || options === void 0 ? void 0 : options.rate) !== 'number') {
|
|
176
393
|
throw 'no rate provided';
|
|
177
394
|
}
|
|
@@ -183,8 +400,7 @@ class NativeAudioWeb extends core.WebPlugin {
|
|
|
183
400
|
return { isPlaying: !audio.paused };
|
|
184
401
|
}
|
|
185
402
|
async clearCache() {
|
|
186
|
-
|
|
187
|
-
return;
|
|
403
|
+
this.logWarning('clearCache is not supported for web. No cache to clear.');
|
|
188
404
|
}
|
|
189
405
|
getAudioAsset(assetId) {
|
|
190
406
|
this.checkAssetId(assetId);
|
|
@@ -201,19 +417,148 @@ class NativeAudioWeb extends core.WebPlugin {
|
|
|
201
417
|
throw 'no assetId provided';
|
|
202
418
|
}
|
|
203
419
|
}
|
|
420
|
+
getOrCreateAudioContext(audio) {
|
|
421
|
+
if (NativeAudioWeb.AUDIO_CONTEXT_MAP.has(audio)) {
|
|
422
|
+
return NativeAudioWeb.AUDIO_CONTEXT_MAP.get(audio);
|
|
423
|
+
}
|
|
424
|
+
const audioContext = new AudioContext();
|
|
425
|
+
NativeAudioWeb.AUDIO_CONTEXT_MAP.set(audio, audioContext);
|
|
426
|
+
return audioContext;
|
|
427
|
+
}
|
|
428
|
+
getOrCreateMediaElementSource(audioContext, audio) {
|
|
429
|
+
if (NativeAudioWeb.MEDIA_ELEMENT_SOURCE_MAP.has(audio)) {
|
|
430
|
+
return NativeAudioWeb.MEDIA_ELEMENT_SOURCE_MAP.get(audio);
|
|
431
|
+
}
|
|
432
|
+
const sourceNode = audioContext.createMediaElementSource(audio);
|
|
433
|
+
NativeAudioWeb.MEDIA_ELEMENT_SOURCE_MAP.set(audio, sourceNode);
|
|
434
|
+
return sourceNode;
|
|
435
|
+
}
|
|
436
|
+
getOrCreateGainNode(audio, track) {
|
|
437
|
+
const audioContext = this.getOrCreateAudioContext(audio);
|
|
438
|
+
if (NativeAudioWeb.GAIN_NODE_MAP.has(audio)) {
|
|
439
|
+
return NativeAudioWeb.GAIN_NODE_MAP.get(audio);
|
|
440
|
+
}
|
|
441
|
+
const gainNode = audioContext.createGain();
|
|
442
|
+
track.connect(gainNode).connect(audioContext.destination);
|
|
443
|
+
NativeAudioWeb.GAIN_NODE_MAP.set(audio, gainNode);
|
|
444
|
+
return gainNode;
|
|
445
|
+
}
|
|
446
|
+
setGainNodeVolume(audio, volume, time) {
|
|
447
|
+
const audioContext = this.getOrCreateAudioContext(audio);
|
|
448
|
+
const track = this.getOrCreateMediaElementSource(audioContext, audio);
|
|
449
|
+
const gainNode = this.getOrCreateGainNode(audio, track);
|
|
450
|
+
if (time !== undefined) {
|
|
451
|
+
gainNode.gain.setValueAtTime(volume, time);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
gainNode.gain.setValueAtTime(volume, audioContext.currentTime);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
exponentialRampGainNodeVolume(audio, volume, duration) {
|
|
458
|
+
const audioContext = this.getOrCreateAudioContext(audio);
|
|
459
|
+
const track = this.getOrCreateMediaElementSource(audioContext, audio);
|
|
460
|
+
const gainNode = this.getOrCreateGainNode(audio, track);
|
|
461
|
+
const adjustedVolume = volume < this.zeroVolume ? this.zeroVolume : volume;
|
|
462
|
+
gainNode.gain.exponentialRampToValueAtTime(adjustedVolume, audioContext.currentTime + duration);
|
|
463
|
+
}
|
|
464
|
+
linearRampGainNodeVolume(audio, volume, duration) {
|
|
465
|
+
const audioContext = this.getOrCreateAudioContext(audio);
|
|
466
|
+
const track = this.getOrCreateMediaElementSource(audioContext, audio);
|
|
467
|
+
const gainNode = this.getOrCreateGainNode(audio, track);
|
|
468
|
+
gainNode.gain.linearRampToValueAtTime(volume, audioContext.currentTime + duration);
|
|
469
|
+
}
|
|
470
|
+
cancelGainNodeRamp(audio) {
|
|
471
|
+
const gainNode = NativeAudioWeb.GAIN_NODE_MAP.get(audio);
|
|
472
|
+
if (gainNode) {
|
|
473
|
+
gainNode.gain.cancelScheduledValues(0);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
startCurrentTimeUpdates(assetId) {
|
|
477
|
+
this.stopCurrentTimeUpdates(assetId);
|
|
478
|
+
const audio = this.getAudioAsset(assetId).audio;
|
|
479
|
+
const intervalId = window.setInterval(() => {
|
|
480
|
+
var _a;
|
|
481
|
+
if (!audio.paused) {
|
|
482
|
+
const currentTime = Math.round(audio.currentTime * 10) / 10;
|
|
483
|
+
this.notifyListeners('currentTime', { assetId, currentTime });
|
|
484
|
+
this.logDebug(`Current time update for assetId: ${assetId}, currentTime: ${currentTime}`);
|
|
485
|
+
const data = this.getAudioAssetData(assetId);
|
|
486
|
+
if (data.fadeOut && typeof data.fadeOutStartTime === 'number' && currentTime >= data.fadeOutStartTime) {
|
|
487
|
+
this.cancelGainNodeRamp(audio);
|
|
488
|
+
const fadeOutDuration = (_a = data.fadeOutDuration) !== null && _a !== void 0 ? _a : NativeAudioWeb.DEFAULT_FADE_DURATION_SEC;
|
|
489
|
+
this.doFadeOut(audio, fadeOutDuration);
|
|
490
|
+
data.fadeOut = false;
|
|
491
|
+
this.setAudioAssetData(assetId, data);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
this.stopCurrentTimeUpdates(assetId);
|
|
496
|
+
}
|
|
497
|
+
}, NativeAudioWeb.CURRENT_TIME_UPDATE_INTERVAL);
|
|
498
|
+
this.currentTimeIntervals.set(assetId, intervalId);
|
|
499
|
+
}
|
|
500
|
+
stopCurrentTimeUpdates(assetId) {
|
|
501
|
+
if (assetId) {
|
|
502
|
+
const intervalId = this.currentTimeIntervals.get(assetId);
|
|
503
|
+
if (intervalId) {
|
|
504
|
+
clearInterval(intervalId);
|
|
505
|
+
this.currentTimeIntervals.delete(assetId);
|
|
506
|
+
}
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
for (const intervalId of this.currentTimeIntervals.values()) {
|
|
510
|
+
clearInterval(intervalId);
|
|
511
|
+
}
|
|
512
|
+
this.currentTimeIntervals.clear();
|
|
513
|
+
}
|
|
514
|
+
getAudioAssetData(assetId) {
|
|
515
|
+
return NativeAudioWeb.AUDIO_DATA_MAP.get(assetId) || {};
|
|
516
|
+
}
|
|
517
|
+
setAudioAssetData(assetId, data) {
|
|
518
|
+
const currentData = NativeAudioWeb.AUDIO_DATA_MAP.get(assetId) || {};
|
|
519
|
+
const newData = Object.assign(Object.assign({}, currentData), data);
|
|
520
|
+
NativeAudioWeb.AUDIO_DATA_MAP.set(assetId, newData);
|
|
521
|
+
}
|
|
522
|
+
logError(message) {
|
|
523
|
+
if (!this.debugMode)
|
|
524
|
+
return;
|
|
525
|
+
console.error(`${NativeAudioWeb.LOG_TAG} Error: ${message}`);
|
|
526
|
+
}
|
|
527
|
+
logWarning(message) {
|
|
528
|
+
if (!this.debugMode)
|
|
529
|
+
return;
|
|
530
|
+
console.warn(`${NativeAudioWeb.LOG_TAG} Warning: ${message}`);
|
|
531
|
+
}
|
|
532
|
+
logInfo(message) {
|
|
533
|
+
if (!this.debugMode)
|
|
534
|
+
return;
|
|
535
|
+
console.info(`${NativeAudioWeb.LOG_TAG} Info: ${message}`);
|
|
536
|
+
}
|
|
537
|
+
logDebug(message) {
|
|
538
|
+
if (!this.debugMode)
|
|
539
|
+
return;
|
|
540
|
+
console.debug(`${NativeAudioWeb.LOG_TAG} Debug: ${message}`);
|
|
541
|
+
}
|
|
204
542
|
async getPluginVersion() {
|
|
205
543
|
return { version: 'web' };
|
|
206
544
|
}
|
|
207
545
|
async deinitPlugin() {
|
|
208
|
-
// Stop and unload all audio assets
|
|
209
546
|
for (const [assetId] of NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID) {
|
|
210
547
|
await this.unload({ assetId });
|
|
211
548
|
}
|
|
212
|
-
|
|
549
|
+
this.stopCurrentTimeUpdates();
|
|
213
550
|
}
|
|
214
551
|
}
|
|
552
|
+
NativeAudioWeb.LOG_TAG = '[NativeAudioWeb]';
|
|
215
553
|
NativeAudioWeb.FILE_LOCATION = '';
|
|
554
|
+
NativeAudioWeb.DEFAULT_FADE_DURATION_SEC = 1;
|
|
555
|
+
NativeAudioWeb.CURRENT_TIME_UPDATE_INTERVAL = 100;
|
|
556
|
+
NativeAudioWeb.AUDIO_PRELOAD_OPTIONS_MAP = new Map();
|
|
557
|
+
NativeAudioWeb.AUDIO_DATA_MAP = new Map();
|
|
216
558
|
NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID = new Map();
|
|
559
|
+
NativeAudioWeb.AUDIO_CONTEXT_MAP = new Map();
|
|
560
|
+
NativeAudioWeb.MEDIA_ELEMENT_SOURCE_MAP = new Map();
|
|
561
|
+
NativeAudioWeb.GAIN_NODE_MAP = new Map();
|
|
217
562
|
NativeAudioWeb.playOnceAssets = new Set();
|
|
218
563
|
new NativeAudioWeb();
|
|
219
564
|
|