@capgo/capacitor-native-audio 8.4.3

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.
Files changed (46) hide show
  1. package/CapgoCapacitorNativeAudio.podspec +16 -0
  2. package/LICENSE +373 -0
  3. package/Package.swift +31 -0
  4. package/README.md +1229 -0
  5. package/android/build.gradle +89 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/java/ee/forgr/audio/AudioAsset.java +611 -0
  8. package/android/src/main/java/ee/forgr/audio/AudioCompletionListener.java +5 -0
  9. package/android/src/main/java/ee/forgr/audio/AudioDispatcher.java +208 -0
  10. package/android/src/main/java/ee/forgr/audio/Constant.java +36 -0
  11. package/android/src/main/java/ee/forgr/audio/HlsAvailabilityChecker.java +84 -0
  12. package/android/src/main/java/ee/forgr/audio/Logger.java +55 -0
  13. package/android/src/main/java/ee/forgr/audio/NativeAudio.java +2022 -0
  14. package/android/src/main/java/ee/forgr/audio/RemoteAudioAsset.java +886 -0
  15. package/android/src/main/java/ee/forgr/audio/StreamAudioAsset.java +708 -0
  16. package/android/src/main/res/values/colors.xml +3 -0
  17. package/android/src/main/res/values/strings.xml +3 -0
  18. package/android/src/main/res/values/styles.xml +3 -0
  19. package/dist/docs.json +1470 -0
  20. package/dist/esm/audio-asset.d.ts +4 -0
  21. package/dist/esm/audio-asset.js +6 -0
  22. package/dist/esm/audio-asset.js.map +1 -0
  23. package/dist/esm/definitions.d.ts +597 -0
  24. package/dist/esm/definitions.js +2 -0
  25. package/dist/esm/definitions.js.map +1 -0
  26. package/dist/esm/index.d.ts +4 -0
  27. package/dist/esm/index.js +7 -0
  28. package/dist/esm/index.js.map +1 -0
  29. package/dist/esm/web.d.ts +82 -0
  30. package/dist/esm/web.js +553 -0
  31. package/dist/esm/web.js.map +1 -0
  32. package/dist/plugin.cjs.js +571 -0
  33. package/dist/plugin.cjs.js.map +1 -0
  34. package/dist/plugin.js +574 -0
  35. package/dist/plugin.js.map +1 -0
  36. package/ios/Sources/NativeAudioPlugin/AudioAsset+Fade.swift +157 -0
  37. package/ios/Sources/NativeAudioPlugin/AudioAsset.swift +403 -0
  38. package/ios/Sources/NativeAudioPlugin/Constant.swift +52 -0
  39. package/ios/Sources/NativeAudioPlugin/Logger.swift +43 -0
  40. package/ios/Sources/NativeAudioPlugin/Plugin.swift +1786 -0
  41. package/ios/Sources/NativeAudioPlugin/RemoteAudioAsset+Fade.swift +152 -0
  42. package/ios/Sources/NativeAudioPlugin/RemoteAudioAsset.swift +405 -0
  43. package/ios/Tests/NativeAudioPluginTests/PluginTests.swift +648 -0
  44. package/ios/Tests/README.md +39 -0
  45. package/package.json +101 -0
  46. package/scripts/configure-dependencies.js +251 -0
@@ -0,0 +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 this.debugMode = false;\n this.currentTimeIntervals = new Map();\n this.zeroVolume = 0.0001;\n }\n async resume(options) {\n var _a, _b;\n const audio = this.getAudioAsset(options.assetId).audio;\n const data = this.getAudioAssetData(options.assetId);\n const targetVolume = (_b = (_a = data.volumeBeforePause) !== null && _a !== void 0 ? _a : data.volume) !== null && _b !== void 0 ? _b : 1;\n if (options.fadeIn) {\n const fadeDuration = options.fadeInDuration || NativeAudioWeb.DEFAULT_FADE_DURATION_SEC;\n this.doFadeIn(audio, fadeDuration, targetVolume);\n }\n else if (audio.volume <= this.zeroVolume) {\n audio.volume = targetVolume;\n this.setGainNodeVolume(audio, targetVolume);\n }\n this.clearFadeOutToStopTimer(options.assetId);\n return this.doResume(options.assetId);\n }\n async doResume(assetId) {\n const audio = this.getAudioAsset(assetId).audio;\n this.startCurrentTimeUpdates(assetId);\n if (audio.paused) {\n return audio.play();\n }\n }\n async pause(options) {\n var _a;\n const audio = this.getAudioAsset(options.assetId).audio;\n this.cancelGainNodeRamp(audio);\n const data = this.getAudioAssetData(options.assetId);\n data.volumeBeforePause = (_a = data.volume) !== null && _a !== void 0 ? _a : audio.volume;\n this.setAudioAssetData(options.assetId, data);\n if (!audio.paused && options.fadeOut) {\n const fadeOutDuration = options.fadeOutDuration || NativeAudioWeb.DEFAULT_FADE_DURATION_SEC;\n this.doFadeOut(audio, fadeOutDuration);\n data.fadeOutToStopTimer = window.setTimeout(() => {\n this.doPause(options.assetId).catch(() => {\n // no-op\n });\n }, fadeOutDuration * 1000);\n this.setAudioAssetData(options.assetId, data);\n return;\n }\n return this.doPause(options.assetId);\n }\n async doPause(assetId) {\n const audio = this.getAudioAsset(assetId).audio;\n this.clearFadeOutToStopTimer(assetId);\n this.stopCurrentTimeUpdates(assetId);\n audio.pause();\n }\n async setCurrentTime(options) {\n const audio = this.getAudioAsset(options.assetId).audio;\n audio.currentTime = options.time;\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 setDebugMode(options) {\n this.debugMode = options.enabled;\n if (this.debugMode) {\n this.logInfo('Debug mode enabled');\n }\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 (_a) {\n return { found: false };\n }\n }\n async preload(options) {\n var _a;\n this.logInfo(`Preloading audio asset with options: ${JSON.stringify(options)}`);\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 NativeAudioWeb.AUDIO_PRELOAD_OPTIONS_MAP.set(options.assetId, options);\n await new Promise((resolve, reject) => {\n var _a;\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 = document.createElement('audio');\n audio.id = options.assetId;\n audio.crossOrigin = 'anonymous';\n audio.src = options.assetPath;\n audio.autoplay = false;\n audio.loop = false;\n audio.preload = 'metadata';\n audio.addEventListener('loadedmetadata', () => {\n resolve();\n });\n audio.addEventListener('error', (errEvt) => {\n this.logError(`Error loading audio file: ${options.assetPath}, error: ${String(errEvt)}`);\n reject('Error loading audio file');\n });\n const data = this.getAudioAssetData(options.assetId);\n if (typeof options.volume === 'number') {\n audio.volume = options.volume;\n data.volume = options.volume;\n }\n else {\n data.volume = audio.volume;\n }\n NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.set(options.assetId, new AudioAsset(audio));\n this.setAudioAssetData(options.assetId, data);\n this.setGainNodeVolume(audio, (_a = data.volume) !== null && _a !== void 0 ? _a : 1);\n });\n }\n async playOnce(options) {\n var _a;\n const assetId = `playOnce_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n NativeAudioWeb.playOnceAssets.add(assetId);\n const autoPlay = options.autoPlay !== false;\n const deleteAfterPlay = (_a = options.deleteAfterPlay) !== null && _a !== void 0 ? _a : false;\n try {\n await this.preload({\n assetId,\n assetPath: options.assetPath,\n volume: options.volume,\n isUrl: options.isUrl,\n });\n const cleanupHandler = async () => {\n try {\n await this.unload({ assetId });\n NativeAudioWeb.playOnceAssets.delete(assetId);\n if (deleteAfterPlay) {\n console.warn('[NativeAudio] deleteAfterPlay is not supported on web platform. File deletion is ignored.');\n }\n }\n catch (error) {\n console.error('[NativeAudio] Error during playOnce cleanup:', error);\n }\n };\n if (autoPlay) {\n await this.doPlay({ assetId, volume: options.volume }, false);\n }\n const currentAudio = this.getAudioAsset(assetId).audio;\n currentAudio.addEventListener('ended', () => {\n cleanupHandler().catch((error) => {\n console.error('[NativeAudio] Error during ended cleanup:', error);\n });\n }, { once: true });\n currentAudio.addEventListener('error', () => {\n cleanupHandler().catch((error) => {\n console.error('[NativeAudio] Error during error cleanup:', error);\n });\n }, { once: true });\n return { assetId };\n }\n catch (error) {\n try {\n await this.unload({ assetId });\n NativeAudioWeb.playOnceAssets.delete(assetId);\n }\n catch (_b) {\n // Ignore cleanup errors\n }\n throw error;\n }\n }\n onEnded(assetId) {\n this.logDebug(`Playback ended for assetId: ${assetId}`);\n this.notifyListeners('complete', { assetId });\n }\n async play(options) {\n this.logInfo(`Playing audio asset with options: ${JSON.stringify(options)}`);\n this.clearFadeOutToStopTimer(options.assetId);\n const { delay = 0 } = options;\n if (delay > 0) {\n const data = this.getAudioAssetData(options.assetId);\n data.startTimer = window.setTimeout(() => {\n this.doPlay(options).catch((error) => {\n this.logError(`Delayed play failed: ${String(error)}`);\n });\n data.startTimer = undefined;\n this.setAudioAssetData(options.assetId, data);\n }, delay * 1000);\n this.setAudioAssetData(options.assetId, data);\n return;\n }\n await this.doPlay(options);\n }\n async doPlay(options, recreateAudioElement = true) {\n const { assetId, time = 0 } = options;\n if (!NativeAudioWeb.AUDIO_PRELOAD_OPTIONS_MAP.has(assetId)) {\n throw `no asset for assetId \"${assetId}\" available. Call preload first!`;\n }\n if (recreateAudioElement) {\n const preloadOptions = NativeAudioWeb.AUDIO_PRELOAD_OPTIONS_MAP.get(assetId);\n await this.unload({ assetId });\n await this.preload(preloadOptions);\n }\n const audio = this.getAudioAsset(assetId).audio;\n audio.id = assetId;\n audio.loop = false;\n audio.currentTime = time;\n audio.addEventListener('ended', () => this.onEnded(assetId), {\n once: true,\n });\n const data = this.getAudioAssetData(assetId);\n if (typeof options.volume === 'number') {\n audio.volume = options.volume;\n data.volume = options.volume;\n this.setGainNodeVolume(audio, options.volume);\n }\n else if (typeof data.volume !== 'number') {\n data.volume = audio.volume;\n }\n await audio.play();\n this.startCurrentTimeUpdates(assetId);\n if (options.fadeIn) {\n this.logDebug(`Fading in audio asset with assetId: ${assetId}`);\n const fadeDuration = options.fadeInDuration || NativeAudioWeb.DEFAULT_FADE_DURATION_SEC;\n this.doFadeIn(audio, fadeDuration);\n }\n if (options.fadeOut && !Number.isNaN(audio.duration) && Number.isFinite(audio.duration)) {\n this.logDebug(`Scheduling fade out for audio asset with assetId: ${assetId}`);\n const fadeOutDuration = options.fadeOutDuration || NativeAudioWeb.DEFAULT_FADE_DURATION_SEC;\n const fadeOutStartTime = options.fadeOutStartTime || audio.duration - fadeOutDuration;\n data.fadeOut = true;\n data.fadeOutStartTime = fadeOutStartTime;\n data.fadeOutDuration = fadeOutDuration;\n }\n this.setAudioAssetData(assetId, data);\n }\n doFadeIn(audio, fadeDuration, targetVolume) {\n var _a;\n const data = this.getAudioAssetData(audio.id);\n this.setGainNodeVolume(audio, this.zeroVolume);\n const fadeToVolume = (_a = targetVolume !== null && targetVolume !== void 0 ? targetVolume : data.volume) !== null && _a !== void 0 ? _a : 1;\n this.linearRampGainNodeVolume(audio, fadeToVolume, fadeDuration);\n data.fadeInTimer = window.setTimeout(() => {\n data.fadeInTimer = undefined;\n this.setAudioAssetData(audio.id, data);\n }, fadeDuration * 1000);\n this.setAudioAssetData(audio.id, data);\n }\n doFadeOut(audio, fadeDuration) {\n this.linearRampGainNodeVolume(audio, this.zeroVolume, fadeDuration);\n }\n async loop(options) {\n this.logInfo(`Looping audio asset with options: ${JSON.stringify(options)}`);\n const audio = this.getAudioAsset(options.assetId).audio;\n this.reset(audio);\n audio.loop = true;\n this.startCurrentTimeUpdates(options.assetId);\n return audio.play();\n }\n async stop(options) {\n this.logInfo(`Stopping audio asset with options: ${JSON.stringify(options)}`);\n const audio = this.getAudioAsset(options.assetId).audio;\n const data = this.getAudioAssetData(options.assetId);\n this.clearFadeOutToStopTimer(options.assetId);\n this.cancelGainNodeRamp(audio);\n if (!audio.paused && options.fadeOut) {\n const fadeDuration = options.fadeOutDuration || NativeAudioWeb.DEFAULT_FADE_DURATION_SEC;\n this.doFadeOut(audio, fadeDuration);\n data.fadeOutToStopTimer = window.setTimeout(() => {\n this.doStop(audio, options);\n }, fadeDuration * 1000);\n this.setAudioAssetData(options.assetId, data);\n return;\n }\n this.doStop(audio, options);\n }\n doStop(audio, options) {\n audio.pause();\n this.onEnded(options.assetId);\n this.reset(audio);\n }\n reset(audio) {\n var _a;\n audio.currentTime = 0;\n for (const [assetId, asset] of NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.entries()) {\n if (asset.audio === audio) {\n this.stopCurrentTimeUpdates(assetId);\n this.clearFadeOutToStopTimer(assetId);\n this.clearStartTimer(assetId);\n this.cancelGainNodeRamp(audio);\n const data = this.getAudioAssetData(assetId);\n const initialVolume = (_a = data.volume) !== null && _a !== void 0 ? _a : 1;\n this.setGainNodeVolume(audio, initialVolume);\n this.setAudioAssetData(assetId, data);\n break;\n }\n }\n }\n clearFadeOutToStopTimer(assetId) {\n const data = this.getAudioAssetData(assetId);\n if (data.fadeOutToStopTimer) {\n clearTimeout(data.fadeOutToStopTimer);\n data.fadeOutToStopTimer = undefined;\n this.setAudioAssetData(assetId, data);\n }\n }\n clearStartTimer(assetId) {\n const data = this.getAudioAssetData(assetId);\n if (data.startTimer) {\n clearTimeout(data.startTimer);\n data.startTimer = undefined;\n this.setAudioAssetData(assetId, data);\n }\n }\n async unload(options) {\n this.logInfo(`Unloading audio asset with options: ${JSON.stringify(options)}`);\n const audio = this.getAudioAsset(options.assetId).audio;\n this.reset(audio);\n NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID.delete(options.assetId);\n NativeAudioWeb.AUDIO_PRELOAD_OPTIONS_MAP.delete(options.assetId);\n NativeAudioWeb.AUDIO_DATA_MAP.delete(options.assetId);\n this.cleanupAudioContext(audio);\n }\n cleanupAudioContext(audio) {\n const gainNode = NativeAudioWeb.GAIN_NODE_MAP.get(audio);\n if (gainNode) {\n gainNode.disconnect();\n NativeAudioWeb.GAIN_NODE_MAP.delete(audio);\n }\n const sourceNode = NativeAudioWeb.MEDIA_ELEMENT_SOURCE_MAP.get(audio);\n if (sourceNode) {\n sourceNode.disconnect();\n NativeAudioWeb.MEDIA_ELEMENT_SOURCE_MAP.delete(audio);\n }\n const audioContext = NativeAudioWeb.AUDIO_CONTEXT_MAP.get(audio);\n if (audioContext) {\n audioContext.close().catch(() => {\n // no-op\n });\n NativeAudioWeb.AUDIO_CONTEXT_MAP.delete(audio);\n }\n }\n async setVolume(options) {\n this.logInfo(`Setting volume for audio asset with options: ${JSON.stringify(options)}`);\n if (typeof (options === null || options === void 0 ? void 0 : options.volume) !== 'number') {\n throw 'no volume provided';\n }\n const { volume, duration = 0 } = options;\n const data = this.getAudioAssetData(options.assetId);\n data.volume = volume;\n this.setAudioAssetData(options.assetId, data);\n const audio = this.getAudioAsset(options.assetId).audio;\n this.cancelGainNodeRamp(audio);\n if (duration > 0) {\n this.exponentialRampGainNodeVolume(audio, volume, duration);\n return;\n }\n audio.volume = volume;\n this.setGainNodeVolume(audio, volume);\n }\n async setRate(options) {\n this.logInfo(`Setting playback rate for audio asset with options: ${JSON.stringify(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 this.logWarning('clearCache is not supported for web. No cache to clear.');\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 getOrCreateAudioContext(audio) {\n if (NativeAudioWeb.AUDIO_CONTEXT_MAP.has(audio)) {\n return NativeAudioWeb.AUDIO_CONTEXT_MAP.get(audio);\n }\n const audioContext = new AudioContext();\n NativeAudioWeb.AUDIO_CONTEXT_MAP.set(audio, audioContext);\n return audioContext;\n }\n getOrCreateMediaElementSource(audioContext, audio) {\n if (NativeAudioWeb.MEDIA_ELEMENT_SOURCE_MAP.has(audio)) {\n return NativeAudioWeb.MEDIA_ELEMENT_SOURCE_MAP.get(audio);\n }\n const sourceNode = audioContext.createMediaElementSource(audio);\n NativeAudioWeb.MEDIA_ELEMENT_SOURCE_MAP.set(audio, sourceNode);\n return sourceNode;\n }\n getOrCreateGainNode(audio, track) {\n const audioContext = this.getOrCreateAudioContext(audio);\n if (NativeAudioWeb.GAIN_NODE_MAP.has(audio)) {\n return NativeAudioWeb.GAIN_NODE_MAP.get(audio);\n }\n const gainNode = audioContext.createGain();\n track.connect(gainNode).connect(audioContext.destination);\n NativeAudioWeb.GAIN_NODE_MAP.set(audio, gainNode);\n return gainNode;\n }\n setGainNodeVolume(audio, volume, time) {\n const audioContext = this.getOrCreateAudioContext(audio);\n const track = this.getOrCreateMediaElementSource(audioContext, audio);\n const gainNode = this.getOrCreateGainNode(audio, track);\n if (time !== undefined) {\n gainNode.gain.setValueAtTime(volume, time);\n }\n else {\n gainNode.gain.setValueAtTime(volume, audioContext.currentTime);\n }\n }\n exponentialRampGainNodeVolume(audio, volume, duration) {\n const audioContext = this.getOrCreateAudioContext(audio);\n const track = this.getOrCreateMediaElementSource(audioContext, audio);\n const gainNode = this.getOrCreateGainNode(audio, track);\n const adjustedVolume = volume < this.zeroVolume ? this.zeroVolume : volume;\n gainNode.gain.exponentialRampToValueAtTime(adjustedVolume, audioContext.currentTime + duration);\n }\n linearRampGainNodeVolume(audio, volume, duration) {\n const audioContext = this.getOrCreateAudioContext(audio);\n const track = this.getOrCreateMediaElementSource(audioContext, audio);\n const gainNode = this.getOrCreateGainNode(audio, track);\n gainNode.gain.linearRampToValueAtTime(volume, audioContext.currentTime + duration);\n }\n cancelGainNodeRamp(audio) {\n const gainNode = NativeAudioWeb.GAIN_NODE_MAP.get(audio);\n if (gainNode) {\n gainNode.gain.cancelScheduledValues(0);\n }\n }\n startCurrentTimeUpdates(assetId) {\n this.stopCurrentTimeUpdates(assetId);\n const audio = this.getAudioAsset(assetId).audio;\n const intervalId = window.setInterval(() => {\n var _a;\n if (!audio.paused) {\n const currentTime = Math.round(audio.currentTime * 10) / 10;\n this.notifyListeners('currentTime', { assetId, currentTime });\n this.logDebug(`Current time update for assetId: ${assetId}, currentTime: ${currentTime}`);\n const data = this.getAudioAssetData(assetId);\n if (data.fadeOut && typeof data.fadeOutStartTime === 'number' && currentTime >= data.fadeOutStartTime) {\n this.cancelGainNodeRamp(audio);\n const fadeOutDuration = (_a = data.fadeOutDuration) !== null && _a !== void 0 ? _a : NativeAudioWeb.DEFAULT_FADE_DURATION_SEC;\n this.doFadeOut(audio, fadeOutDuration);\n data.fadeOut = false;\n this.setAudioAssetData(assetId, data);\n }\n }\n else {\n this.stopCurrentTimeUpdates(assetId);\n }\n }, NativeAudioWeb.CURRENT_TIME_UPDATE_INTERVAL);\n this.currentTimeIntervals.set(assetId, intervalId);\n }\n stopCurrentTimeUpdates(assetId) {\n if (assetId) {\n const intervalId = this.currentTimeIntervals.get(assetId);\n if (intervalId) {\n clearInterval(intervalId);\n this.currentTimeIntervals.delete(assetId);\n }\n return;\n }\n for (const intervalId of this.currentTimeIntervals.values()) {\n clearInterval(intervalId);\n }\n this.currentTimeIntervals.clear();\n }\n getAudioAssetData(assetId) {\n return NativeAudioWeb.AUDIO_DATA_MAP.get(assetId) || {};\n }\n setAudioAssetData(assetId, data) {\n const currentData = NativeAudioWeb.AUDIO_DATA_MAP.get(assetId) || {};\n const newData = Object.assign(Object.assign({}, currentData), data);\n NativeAudioWeb.AUDIO_DATA_MAP.set(assetId, newData);\n }\n logError(message) {\n if (!this.debugMode)\n return;\n console.error(`${NativeAudioWeb.LOG_TAG} Error: ${message}`);\n }\n logWarning(message) {\n if (!this.debugMode)\n return;\n console.warn(`${NativeAudioWeb.LOG_TAG} Warning: ${message}`);\n }\n logInfo(message) {\n if (!this.debugMode)\n return;\n console.info(`${NativeAudioWeb.LOG_TAG} Info: ${message}`);\n }\n logDebug(message) {\n if (!this.debugMode)\n return;\n console.debug(`${NativeAudioWeb.LOG_TAG} Debug: ${message}`);\n }\n async getPluginVersion() {\n return { version: 'web' };\n }\n async deinitPlugin() {\n for (const [assetId] of NativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID) {\n await this.unload({ assetId });\n }\n this.stopCurrentTimeUpdates();\n }\n}\nNativeAudioWeb.LOG_TAG = '[NativeAudioWeb]';\nNativeAudioWeb.FILE_LOCATION = '';\nNativeAudioWeb.DEFAULT_FADE_DURATION_SEC = 1;\nNativeAudioWeb.CURRENT_TIME_UPDATE_INTERVAL = 100;\nNativeAudioWeb.AUDIO_PRELOAD_OPTIONS_MAP = new Map();\nNativeAudioWeb.AUDIO_DATA_MAP = new Map();\nNativeAudioWeb.AUDIO_ASSET_BY_ASSET_ID = new Map();\nNativeAudioWeb.AUDIO_CONTEXT_MAP = new Map();\nNativeAudioWeb.MEDIA_ELEMENT_SOURCE_MAP = new Map();\nNativeAudioWeb.GAIN_NODE_MAP = new Map();\nNativeAudioWeb.playOnceAssets = new Set();\nconst NativeAudio = new NativeAudioWeb();\nexport { NativeAudio };\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":"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"}
@@ -0,0 +1,157 @@
1
+ import AVFoundation
2
+
3
+ extension AudioAsset {
4
+
5
+ func dispatchComplete() {
6
+ if dispatchedCompleteMap[assetId] == true {
7
+ return
8
+ }
9
+ owner?.notifyListeners("complete", data: ["assetId": assetId])
10
+ dispatchedCompleteMap[assetId] = true
11
+ }
12
+
13
+ func cancelFade() {
14
+ fadeTask?.cancel()
15
+ fadeTask = nil
16
+ }
17
+
18
+ fileprivate func performLocalFadeOutPauseOnMain(audio: AVAudioPlayer, beforePause: ((TimeInterval, TimeInterval) -> Void)?) {
19
+ let elapsed = audio.currentTime
20
+ let duration = audio.duration.isFinite ? audio.duration : 0
21
+ beforePause?(elapsed, duration)
22
+ audio.pause()
23
+ }
24
+
25
+ fileprivate func scheduleLocalFadeOutPauseOnMain(audio: AVAudioPlayer, beforePause: ((TimeInterval, TimeInterval) -> Void)?) {
26
+ DispatchQueue.main.async { [weak self] in
27
+ guard let self else { return }
28
+ self.performLocalFadeOutPauseOnMain(audio: audio, beforePause: beforePause)
29
+ }
30
+ }
31
+
32
+ /// Same main-thread pause and `beforePause(elapsed, duration)` as fade-out-to-pause when no fade runs (e.g. volume already zero).
33
+ internal func schedulePauseWithPositionRecording(audio: AVAudioPlayer, beforePause: ((TimeInterval, TimeInterval) -> Void)?) {
34
+ scheduleLocalFadeOutPauseOnMain(audio: audio, beforePause: beforePause)
35
+ }
36
+
37
+ fileprivate func scheduleLocalFadeOutStopOnMain(audio: AVAudioPlayer, beforeStop: ((TimeInterval, TimeInterval) -> Void)?) {
38
+ DispatchQueue.main.async { [weak self] in
39
+ guard let self else { return }
40
+ self.performLocalFadeOutStopOnMain(audio: audio, beforeStop: beforeStop)
41
+ }
42
+ }
43
+
44
+ fileprivate func performLocalFadeOutStopOnMain(audio: AVAudioPlayer, beforeStop: ((TimeInterval, TimeInterval) -> Void)?) {
45
+ let elapsed = audio.currentTime
46
+ let duration = audio.duration.isFinite ? audio.duration : 0
47
+ beforeStop?(elapsed, duration)
48
+ audio.stop()
49
+ dispatchComplete()
50
+ }
51
+
52
+ func fadeIn(audio: AVAudioPlayer, fadeInDuration: TimeInterval, targetVolume: Float) {
53
+ cancelFade()
54
+ let steps = Int(fadeInDuration / TimeInterval(fadeDelaySecs))
55
+ guard steps > 0 else { return }
56
+ let fadeStep = targetVolume / Float(steps)
57
+ var currentVolume: Float = 0
58
+
59
+ var task: DispatchWorkItem?
60
+ task = DispatchWorkItem { [weak self] in
61
+ guard let self else { return }
62
+ for _ in 0..<steps {
63
+ guard let task, !task.isCancelled, self.isPlaying(), audio.isPlaying else { return }
64
+ currentVolume += fadeStep
65
+ DispatchQueue.main.async {
66
+ audio.volume = min(max(currentVolume, 0), targetVolume)
67
+ }
68
+ Thread.sleep(forTimeInterval: TimeInterval(self.fadeDelaySecs))
69
+ }
70
+ }
71
+ fadeTask = task
72
+ if let task {
73
+ fadeQueue.async(execute: task)
74
+ }
75
+ }
76
+
77
+ /// - Parameter beforeStop: Called on the main queue immediately before `stop()` when `toPause` is false,
78
+ /// so the plugin can refresh Now Playing (rate 0) at the final elapsed time.
79
+ /// - Parameter beforePause: Called on the main queue immediately before `pause()` when `toPause` is true,
80
+ /// so the plugin can persist `timeBeforePause` and update Now Playing at the actual stop position.
81
+ /// Kept last so call sites can use a trailing closure for fade-out-to-pause.
82
+ func fadeOut(
83
+ audio: AVAudioPlayer,
84
+ fadeOutDuration: TimeInterval,
85
+ toPause: Bool = false,
86
+ beforeStop: ((TimeInterval, TimeInterval) -> Void)? = nil,
87
+ beforePause: ((TimeInterval, TimeInterval) -> Void)? = nil
88
+ ) {
89
+ cancelFade()
90
+ let steps = max(0, Int(fadeOutDuration / TimeInterval(fadeDelaySecs)))
91
+ guard steps > 0 else {
92
+ if toPause {
93
+ scheduleLocalFadeOutPauseOnMain(audio: audio, beforePause: beforePause)
94
+ } else {
95
+ scheduleLocalFadeOutStopOnMain(audio: audio, beforeStop: beforeStop)
96
+ }
97
+ return
98
+ }
99
+ var currentVolume = audio.volume
100
+ let fadeStep = currentVolume / Float(steps)
101
+
102
+ var task: DispatchWorkItem?
103
+ task = DispatchWorkItem { [weak self] in
104
+ guard let self else { return }
105
+ for _ in 0..<steps {
106
+ guard let task, !task.isCancelled else { return }
107
+ guard self.isPlaying(), audio.isPlaying else { break }
108
+ currentVolume -= fadeStep
109
+ DispatchQueue.main.async {
110
+ audio.volume = max(currentVolume, 0)
111
+ }
112
+ Thread.sleep(forTimeInterval: TimeInterval(self.fadeDelaySecs))
113
+ }
114
+ guard let task, !task.isCancelled else { return }
115
+ DispatchQueue.main.async { [weak self] in
116
+ guard let self else { return }
117
+ if toPause {
118
+ self.performLocalFadeOutPauseOnMain(audio: audio, beforePause: beforePause)
119
+ } else {
120
+ self.performLocalFadeOutStopOnMain(audio: audio, beforeStop: beforeStop)
121
+ }
122
+ }
123
+ }
124
+ fadeTask = task
125
+ if let task {
126
+ fadeQueue.async(execute: task)
127
+ }
128
+ }
129
+
130
+ func fadeTo(audio: AVAudioPlayer, fadeDuration: TimeInterval, targetVolume: Float) {
131
+ cancelFade()
132
+ let steps = Int(fadeDuration / TimeInterval(fadeDelaySecs))
133
+ guard steps > 0 else { return }
134
+
135
+ let minVolume = zeroVolume
136
+ var currentVolume = max(audio.volume, minVolume)
137
+ let safeTargetVolume = max(targetVolume, minVolume)
138
+ let ratio = pow(safeTargetVolume / currentVolume, 1.0 / Float(steps))
139
+
140
+ var task: DispatchWorkItem?
141
+ task = DispatchWorkItem { [weak self] in
142
+ guard let self else { return }
143
+ for _ in 0..<steps {
144
+ guard let task, !task.isCancelled, self.isPlaying(), audio.isPlaying else { return }
145
+ currentVolume *= ratio
146
+ DispatchQueue.main.async {
147
+ audio.volume = min(max(currentVolume, minVolume), self.maxVolume)
148
+ }
149
+ Thread.sleep(forTimeInterval: TimeInterval(self.fadeDelaySecs))
150
+ }
151
+ }
152
+ fadeTask = task
153
+ if let task {
154
+ fadeQueue.async(execute: task)
155
+ }
156
+ }
157
+ }
@@ -0,0 +1,403 @@
1
+ @preconcurrency import AVFoundation
2
+
3
+ // swiftlint:disable:next type_body_length
4
+ public class AudioAsset: NSObject, AVAudioPlayerDelegate {
5
+
6
+ var channels: [AVAudioPlayer] = []
7
+ var playIndex: Int = 0
8
+ var assetId: String = ""
9
+ var initialVolume: Float = 1.0
10
+ let zeroVolume: Float = 0.001
11
+ let maxVolume: Float = 1.0
12
+ weak var owner: NativeAudio?
13
+ var onComplete: (() -> Void)?
14
+
15
+ let fadeDelaySecs: Float = 0.08
16
+ private var currentTimeTimer: Timer?
17
+ internal var fadeTimer: Timer?
18
+ var fadeTask: DispatchWorkItem?
19
+ let fadeQueue: DispatchQueue = DispatchQueue(label: "com.audioasset.fadeQueue")
20
+ var dispatchedCompleteMap: [String: Bool] = [:]
21
+
22
+ private var logger = Logger(logTag: "AudioAsset")
23
+
24
+ init(owner: NativeAudio, withAssetId assetId: String, withPath path: String, withChannels channels: Int?, withVolume volume: Float?) {
25
+ self.owner = owner
26
+ self.assetId = assetId
27
+ self.channels = []
28
+ self.initialVolume = min(max(volume ?? Constant.DefaultVolume, Constant.MinVolume), Constant.MaxVolume)
29
+
30
+ super.init()
31
+
32
+ guard let encodedPath = path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
33
+ logger.error("Failed to encode path: %@", String(describing: path))
34
+ return
35
+ }
36
+
37
+ let pathUrl = URL(string: encodedPath) ?? URL(fileURLWithPath: encodedPath)
38
+ let channelCount = min(max(channels ?? 1, 1), Constant.MaxChannels)
39
+
40
+ let setupBlock = { [weak self] in
41
+ guard let self else { return }
42
+ for _ in 0..<channelCount {
43
+ do {
44
+ let player = try AVAudioPlayer(contentsOf: pathUrl)
45
+ player.delegate = self
46
+ player.enableRate = true
47
+ player.volume = self.initialVolume
48
+ player.rate = 1.0
49
+ player.prepareToPlay()
50
+ self.channels.append(player)
51
+ } catch {
52
+ self.logger.error("Error loading audio file: %@", error.localizedDescription)
53
+ }
54
+ }
55
+ }
56
+
57
+ if owner.isRunningTests {
58
+ setupBlock()
59
+ } else {
60
+ owner.executeOnAudioQueue(setupBlock)
61
+ }
62
+ }
63
+
64
+ // Backward-compatible initializer signature
65
+ init(owner: NativeAudio, withAssetId assetId: String, withPath path: String, withChannels channels: Int?, withVolume volume: Float?, withFadeDelay _: Float?) {
66
+ self.owner = owner
67
+ self.assetId = assetId
68
+ self.channels = []
69
+ self.initialVolume = min(max(volume ?? Constant.DefaultVolume, Constant.MinVolume), Constant.MaxVolume)
70
+ super.init()
71
+
72
+ guard let encodedPath = path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
73
+ logger.error("Failed to encode path: %@", String(describing: path))
74
+ return
75
+ }
76
+
77
+ let pathUrl = URL(string: encodedPath) ?? URL(fileURLWithPath: encodedPath)
78
+ let channelCount = min(max(channels ?? 1, 1), Constant.MaxChannels)
79
+ let setupBlock = { [weak self] in
80
+ guard let self else { return }
81
+ for _ in 0..<channelCount {
82
+ do {
83
+ let player = try AVAudioPlayer(contentsOf: pathUrl)
84
+ player.delegate = self
85
+ player.enableRate = true
86
+ player.volume = self.initialVolume
87
+ player.rate = 1.0
88
+ player.prepareToPlay()
89
+ self.channels.append(player)
90
+ } catch {
91
+ self.logger.error("Error loading audio file: %@", error.localizedDescription)
92
+ }
93
+ }
94
+ }
95
+ if owner.isRunningTests {
96
+ setupBlock()
97
+ } else {
98
+ owner.executeOnAudioQueue(setupBlock)
99
+ }
100
+ }
101
+
102
+ deinit {
103
+ currentTimeTimer?.invalidate()
104
+ currentTimeTimer = nil
105
+ fadeTimer?.invalidate()
106
+ fadeTimer = nil
107
+ cancelFade()
108
+ for player in channels where player.isPlaying {
109
+ player.stop()
110
+ }
111
+ channels = []
112
+ }
113
+
114
+ func getCurrentTime() -> TimeInterval {
115
+ var result: TimeInterval = 0
116
+ owner?.readOnAudioQueue {
117
+ guard !self.channels.isEmpty, self.playIndex < self.channels.count else { return }
118
+ result = self.channels[self.playIndex].currentTime
119
+ }
120
+ return result
121
+ }
122
+
123
+ func setCurrentTime(time: TimeInterval, completion: (() -> Void)? = nil) {
124
+ guard let owner else {
125
+ completion?()
126
+ return
127
+ }
128
+ owner.executeOnAudioQueue { [weak self] in
129
+ guard let self else {
130
+ completion?()
131
+ return
132
+ }
133
+ guard !channels.isEmpty, playIndex < channels.count else {
134
+ completion?()
135
+ return
136
+ }
137
+ let player = channels[playIndex]
138
+ let validTime = min(max(time, 0), player.duration)
139
+ player.currentTime = validTime
140
+ completion?()
141
+ }
142
+ }
143
+
144
+ func getDuration() -> TimeInterval {
145
+ var result: TimeInterval = 0
146
+ owner?.executeOnAudioQueue { [weak self] in
147
+ guard let self else { return }
148
+ guard !channels.isEmpty, playIndex < channels.count else { return }
149
+ result = channels[playIndex].duration
150
+ }
151
+ return result
152
+ }
153
+
154
+ func play(time: TimeInterval, volume: Float? = nil) {
155
+ stopCurrentTimeUpdates()
156
+ owner?.executeOnAudioQueue { [weak self] in
157
+ guard let self else { return }
158
+ guard !channels.isEmpty else { return }
159
+ if playIndex >= channels.count {
160
+ playIndex = 0
161
+ }
162
+ owner?.activateSession()
163
+ cancelFade()
164
+
165
+ let player = channels[playIndex]
166
+ let validTime = min(max(time, 0), player.duration)
167
+ player.currentTime = validTime
168
+ player.numberOfLoops = 0
169
+ player.volume = volume ?? initialVolume
170
+ player.play()
171
+
172
+ playIndex = (playIndex + 1) % channels.count
173
+ startCurrentTimeUpdates()
174
+ }
175
+ }
176
+
177
+ // Backward-compatible signature
178
+ func play(time: TimeInterval, delay: TimeInterval) {
179
+ let validDelay = max(delay, 0)
180
+ if validDelay > 0 {
181
+ DispatchQueue.main.asyncAfter(deadline: .now() + validDelay) { [weak self] in
182
+ self?.play(time: time, volume: nil)
183
+ }
184
+ } else {
185
+ play(time: time, volume: nil)
186
+ }
187
+ }
188
+
189
+ func playWithFade(time: TimeInterval) {
190
+ playWithFade(time: time, volume: nil, fadeInDuration: TimeInterval(Constant.DefaultFadeDuration))
191
+ }
192
+
193
+ func playWithFade(time: TimeInterval, volume: Float?, fadeInDuration: TimeInterval) {
194
+ owner?.executeOnAudioQueue { [weak self] in
195
+ guard let self else { return }
196
+ guard !channels.isEmpty else { return }
197
+ if playIndex >= channels.count {
198
+ playIndex = 0
199
+ }
200
+
201
+ let player = channels[playIndex]
202
+ player.currentTime = time
203
+ player.numberOfLoops = 0
204
+ player.volume = 0
205
+ player.play()
206
+ playIndex = (playIndex + 1) % channels.count
207
+ startCurrentTimeUpdates()
208
+ fadeIn(audio: player, fadeInDuration: fadeInDuration, targetVolume: volume ?? initialVolume)
209
+ }
210
+ }
211
+
212
+ func pause() {
213
+ owner?.executeOnAudioQueue { [weak self] in
214
+ guard let self else { return }
215
+ guard !channels.isEmpty, playIndex < channels.count else { return }
216
+ cancelFade()
217
+ channels[playIndex].pause()
218
+ stopCurrentTimeUpdates()
219
+ }
220
+ }
221
+
222
+ func resume() {
223
+ owner?.executeOnAudioQueue { [weak self] in
224
+ guard let self else { return }
225
+ guard !channels.isEmpty, playIndex < channels.count else { return }
226
+ let player = channels[playIndex]
227
+ player.play()
228
+ startCurrentTimeUpdates()
229
+ }
230
+ }
231
+
232
+ func stop() {
233
+ owner?.executeOnAudioQueue { [weak self] in
234
+ guard let self else { return }
235
+ cancelFade()
236
+ stopCurrentTimeUpdates()
237
+ for player in channels {
238
+ if player.isPlaying {
239
+ player.stop()
240
+ }
241
+ player.currentTime = 0
242
+ player.numberOfLoops = 0
243
+ }
244
+ playIndex = 0
245
+ dispatchComplete()
246
+ }
247
+ }
248
+
249
+ func stopWithFade() {
250
+ stopWithFade(fadeOutDuration: TimeInterval(Constant.DefaultFadeDuration), toPause: false)
251
+ }
252
+
253
+ func stopWithFade(fadeOutDuration: TimeInterval, toPause: Bool = false) {
254
+ owner?.executeOnAudioQueue { [weak self] in
255
+ guard let self else { return }
256
+ guard !channels.isEmpty, playIndex < channels.count else {
257
+ if !toPause { stop() }
258
+ return
259
+ }
260
+ let player = channels[playIndex]
261
+ if player.isPlaying {
262
+ if toPause {
263
+ if player.volume > 0 {
264
+ fadeOut(audio: player, fadeOutDuration: fadeOutDuration, toPause: true, beforePause: { [weak self] elapsed, duration in
265
+ guard let self, let owner = self.owner else { return }
266
+ owner.recordPausePositionAfterFade(assetId: self.assetId, elapsedTime: elapsed, duration: duration)
267
+ })
268
+ } else {
269
+ cancelFade()
270
+ schedulePauseWithPositionRecording(audio: player) { [weak self] elapsed, duration in
271
+ guard let self, let owner = self.owner else { return }
272
+ owner.recordPausePositionAfterFade(assetId: self.assetId, elapsedTime: elapsed, duration: duration)
273
+ }
274
+ }
275
+ } else if player.volume > 0 {
276
+ fadeOut(audio: player, fadeOutDuration: fadeOutDuration, toPause: false, beforeStop: { [weak self] elapsed, duration in
277
+ guard let self, let owner = self.owner else { return }
278
+ owner.recordStoppedPlaybackStateAfterFade(assetId: self.assetId, elapsedTime: elapsed, duration: duration)
279
+ })
280
+ } else {
281
+ let elapsed = player.currentTime
282
+ let duration = player.duration.isFinite ? player.duration : 0
283
+ stop()
284
+ owner?.recordStoppedPlaybackStateAfterFade(assetId: assetId, elapsedTime: elapsed, duration: duration)
285
+ }
286
+ } else if !toPause {
287
+ stop()
288
+ }
289
+ }
290
+ }
291
+
292
+ func loop() {
293
+ owner?.executeOnAudioQueue { [weak self] in
294
+ guard let self else { return }
295
+ cancelFade()
296
+ stop()
297
+ guard !channels.isEmpty, playIndex < channels.count else { return }
298
+ let player = channels[playIndex]
299
+ player.delegate = self
300
+ player.numberOfLoops = -1
301
+ player.play()
302
+ playIndex = (playIndex + 1) % channels.count
303
+ startCurrentTimeUpdates()
304
+ }
305
+ }
306
+
307
+ func unload() {
308
+ owner?.executeOnAudioQueue { [weak self] in
309
+ guard let self else { return }
310
+ cancelFade()
311
+ stop()
312
+ stopCurrentTimeUpdates()
313
+ channels = []
314
+ }
315
+ }
316
+
317
+ func setVolume(volume: NSNumber) {
318
+ setVolume(volume: volume, fadeDuration: 0)
319
+ }
320
+
321
+ func setVolume(volume: NSNumber, fadeDuration: Double) {
322
+ owner?.executeOnAudioQueue { [weak self] in
323
+ guard let self else { return }
324
+ cancelFade()
325
+ let validVolume = min(max(volume.floatValue, Constant.MinVolume), Constant.MaxVolume)
326
+ for player in channels {
327
+ if player.isPlaying && fadeDuration > 0 {
328
+ fadeTo(audio: player, fadeDuration: fadeDuration, targetVolume: validVolume)
329
+ } else {
330
+ player.volume = validVolume
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ func setRate(rate: NSNumber) {
337
+ owner?.executeOnAudioQueue { [weak self] in
338
+ guard let self else { return }
339
+ let validRate = min(max(rate.floatValue, Constant.MinRate), Constant.MaxRate)
340
+ for player in channels {
341
+ player.rate = validRate
342
+ }
343
+ }
344
+ }
345
+
346
+ func isPlaying() -> Bool {
347
+ var result = false
348
+ owner?.readOnAudioQueue {
349
+ result = self.channels.contains(where: { $0.isPlaying })
350
+ }
351
+ return result
352
+ }
353
+
354
+ internal func shouldStopCurrentTimeUpdatesWhenNotPlaying() -> Bool {
355
+ true
356
+ }
357
+
358
+ public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
359
+ owner?.executeOnAudioQueue { [weak self] in
360
+ guard let self else { return }
361
+ dispatchComplete()
362
+ onComplete?()
363
+ owner?.audioPlayerDidFinishPlaying(player, successfully: flag)
364
+ }
365
+ }
366
+
367
+ func playerDecodeError(player: AVAudioPlayer, error: NSError?) {
368
+ if let error {
369
+ logger.error("AudioAsset decode error: %@", error.localizedDescription)
370
+ }
371
+ }
372
+
373
+ internal func startCurrentTimeUpdates() {
374
+ stopCurrentTimeUpdates()
375
+ dispatchedCompleteMap[assetId] = false
376
+ DispatchQueue.main.async { [weak self] in
377
+ guard let self else { return }
378
+ let timer = Timer(timeInterval: 0.1, repeats: true) { [weak self] _ in
379
+ guard let self, let owner else {
380
+ self?.stopCurrentTimeUpdates()
381
+ return
382
+ }
383
+ if self.isPlaying() {
384
+ owner.notifyCurrentTime(self)
385
+ } else if self.shouldStopCurrentTimeUpdatesWhenNotPlaying() {
386
+ self.stopCurrentTimeUpdates()
387
+ }
388
+ }
389
+ self.currentTimeTimer = timer
390
+ RunLoop.current.add(timer, forMode: .common)
391
+ }
392
+ }
393
+
394
+ internal func stopCurrentTimeUpdates() {
395
+ DispatchQueue.main.async { [weak self] in
396
+ guard let self else { return }
397
+ logger.debug("Stop current time updates")
398
+ self.currentTimeTimer?.invalidate()
399
+ self.currentTimeTimer = nil
400
+ }
401
+ }
402
+
403
+ }