diff --git a/packages/audioplayers/example/ios/Podfile.lock b/packages/audioplayers/example/ios/Podfile.lock index 168467f4e..3c5db9d18 100644 --- a/packages/audioplayers/example/ios/Podfile.lock +++ b/packages/audioplayers/example/ios/Podfile.lock @@ -51,7 +51,7 @@ DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) SPEC REPOS: trunk: @@ -70,19 +70,19 @@ EXTERNAL SOURCES: integration_test: :path: ".symlinks/plugins/integration_test/ios" path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/ios" + :path: ".symlinks/plugins/path_provider_foundation/darwin" SPEC CHECKSUMS: audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40 DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95 + file_picker: ce3938a0df3cc1ef404671531facef740d03f920 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 - path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 + integration_test: 13825b8a9334a850581300559b8839134b124670 + path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 SDWebImage: e5cc87bf736e60f49592f307bdf9e157189298a3 SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/packages/audioplayers/example/ios/Runner.xcodeproj/project.pbxproj b/packages/audioplayers/example/ios/Runner.xcodeproj/project.pbxproj index 6877dc296..8c618c4f4 100644 --- a/packages/audioplayers/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/audioplayers/example/ios/Runner.xcodeproj/project.pbxproj @@ -204,6 +204,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( diff --git a/packages/audioplayers/example/macos/Podfile.lock b/packages/audioplayers/example/macos/Podfile.lock index 47ca65031..0945af2c7 100644 --- a/packages/audioplayers/example/macos/Podfile.lock +++ b/packages/audioplayers/example/macos/Podfile.lock @@ -9,7 +9,7 @@ PODS: DEPENDENCIES: - audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) EXTERNAL SOURCES: audioplayers_darwin: @@ -17,13 +17,13 @@ EXTERNAL SOURCES: FlutterMacOS: :path: Flutter/ephemeral path_provider_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin SPEC CHECKSUMS: audioplayers_darwin: dcad41de4fbd0099cb3749f7ab3b0cb8f70b810c FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 + path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/packages/audioplayers_darwin/darwin/Classes/SwiftAudioplayersDarwinPlugin.swift b/packages/audioplayers_darwin/darwin/Classes/SwiftAudioplayersDarwinPlugin.swift index 36764622c..42e930dc7 100644 --- a/packages/audioplayers_darwin/darwin/Classes/SwiftAudioplayersDarwinPlugin.swift +++ b/packages/audioplayers_darwin/darwin/Classes/SwiftAudioplayersDarwinPlugin.swift @@ -99,7 +99,7 @@ public class SwiftAudioplayersDarwinPlugin: NSObject, FlutterPlugin { try globalContext.apply() } catch AudioPlayerError.warning(let warnMsg) { globalEvents.onLog(message: warnMsg) - } catch { + } catch { result(FlutterError(code: "DarwinAudioError", message: "Error configuring global audio session: \(error)", details: nil)) } } else if method == "emitLog" { @@ -333,19 +333,19 @@ class AudioPlayersStreamHandler: NSObject, FlutterStreamHandler { func onCurrentPosition(millis: Int) { if let eventSink = self.sink { - eventSink(["event": "audio.onCurrentPosition", "value": millis]) + eventSink(["event": "audio.onCurrentPosition", "value": millis] as [String: Any]) } } func onDuration(millis: Int) { if let eventSink = self.sink { - eventSink(["event": "audio.onDuration", "value": millis]) + eventSink(["event": "audio.onDuration", "value": millis] as [String: Any]) } } func onPrepared(isPrepared: Bool) { if let eventSink = self.sink { - eventSink(["event": "audio.onPrepared", "value": isPrepared]) + eventSink(["event": "audio.onPrepared", "value": isPrepared] as [String: Any]) } } diff --git a/packages/audioplayers_darwin/darwin/Classes/WrappedMediaPlayer.swift b/packages/audioplayers_darwin/darwin/Classes/WrappedMediaPlayer.swift index 13f2a0b2b..10c976404 100644 --- a/packages/audioplayers_darwin/darwin/Classes/WrappedMediaPlayer.swift +++ b/packages/audioplayers_darwin/darwin/Classes/WrappedMediaPlayer.swift @@ -8,70 +8,96 @@ typealias Completer = () -> Void typealias CompleterError = () -> Void class WrappedMediaPlayer { - var reference: SwiftAudioplayersDarwinPlugin - var eventHandler: AudioPlayersStreamHandler - - var player: AVPlayer? - - var observers: [TimeObserver] - var keyValueObservation: NSKeyValueObservation? - - var isPlaying: Bool - var playbackRate: Double - var volume: Double + private(set) var eventHandler: AudioPlayersStreamHandler + private(set) var isPlaying: Bool var looping: Bool - var url: String? - + private var reference: SwiftAudioplayersDarwinPlugin + private var player: AVPlayer? + private var playbackRate: Double + private var volume: Double + private var url: String? + + private var observers: [TimeObserver] + private var playerItemStatusObservation: NSKeyValueObservation? + init( - reference: SwiftAudioplayersDarwinPlugin, - eventHandler: AudioPlayersStreamHandler, - player: AVPlayer? = nil, - playbackRate: Double = defaultPlaybackRate, - volume: Double = defaultVolume, - looping: Bool = defaultLooping, - url: String? = nil + reference: SwiftAudioplayersDarwinPlugin, + eventHandler: AudioPlayersStreamHandler, + player: AVPlayer? = nil, + playbackRate: Double = defaultPlaybackRate, + volume: Double = defaultVolume, + looping: Bool = defaultLooping, + url: String? = nil ) { self.reference = reference self.eventHandler = eventHandler self.player = player self.observers = [] - self.keyValueObservation = nil - + self.playerItemStatusObservation = nil + self.isPlaying = false self.playbackRate = playbackRate self.volume = volume self.looping = looping self.url = url } - - func getDurationCMTime() -> CMTime? { - return player?.currentItem?.asset.duration + + func setSourceUrl( + url: String, + isLocal: Bool, + completer: Completer? = nil, + completerError: CompleterError? = nil + ) { + let playbackStatus = player?.currentItem?.status + + if self.url != url || playbackStatus == .failed || playbackStatus == nil { + let playerItem = createPlayerItem(url, isLocal) + let player: AVPlayer + if let existingPlayer = self.player { + reset() + self.url = url + existingPlayer.replaceCurrentItem(with: playerItem) + player = existingPlayer + } else { + player = AVPlayer.init(playerItem: playerItem) + configParameters(player: player) + + self.player = player + self.observers = [] + self.url = url + + setUpPositionObserver(player) + } + + setUpSoundCompletedObserver(player, playerItem) + setUpPlayerItemStatusObservation(playerItem, completer, completerError) + } else { + if playbackStatus == .readyToPlay { + completer?() + } + } } - + func getDuration() -> Int? { guard let duration = getDurationCMTime() else { return nil } return fromCMTime(time: duration) } - - private func getCurrentCMTime() -> CMTime? { - return player?.currentTime() - } - + func getCurrentPosition() -> Int? { guard let time = getCurrentCMTime() else { return nil } return fromCMTime(time: time) } - + func pause() { isPlaying = false player?.pause() } - + func resume() { isPlaying = true if let player = self.player { @@ -84,17 +110,17 @@ class WrappedMediaPlayer { updateDuration() } } - + func setVolume(volume: Double) { self.volume = volume player?.volume = Float(volume) } - + func setPlaybackRate(playbackRate: Double) { self.playbackRate = playbackRate player?.rate = Float(playbackRate) } - + func seek(time: CMTime, completer: Completer? = nil) { guard let currentItem = player?.currentItem else { completer?() @@ -111,24 +137,15 @@ class WrappedMediaPlayer { } } } - + func stop(completer: Completer? = nil) { pause() seek(time: toCMTime(millis: 0), completer: completer) } - - func releaseSync() { - keyValueObservation?.invalidate() - for observer in observers { - NotificationCenter.default.removeObserver(observer.observer) - } - observers = [] - player?.replaceCurrentItem(with: nil) - } func release(completer: Completer? = nil) { stop { - self.releaseSync() + self.reset() completer?() } } @@ -138,30 +155,82 @@ class WrappedMediaPlayer { completer?() } } + + private func getDurationCMTime() -> CMTime? { + return player?.currentItem?.asset.duration + } + + private func getCurrentCMTime() -> CMTime? { + return player?.currentTime() + } + + private func createPlayerItem(_ url: String, _ isLocal: Bool) -> AVPlayerItem { + let parsedUrl = isLocal ? URL.init(fileURLWithPath: url.deletingPrefix("file://")) : URL.init(string: url)! + let playerItem = AVPlayerItem.init(url: parsedUrl) + playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithm.timeDomain + return playerItem + } + + private func setUpPlayerItemStatusObservation(_ playerItem: AVPlayerItem, _ completer: Completer?, _ completerError: CompleterError?) { + let newplayerItemStatusObservation = playerItem.observe(\AVPlayerItem.status) { (playerItem, change) in + let status = playerItem.status + self.eventHandler.onLog(message: "player status: \(status), change: \(change)") + + switch playerItem.status { + case .readyToPlay: + self.updateDuration() + completer?() + case .failed: + self.reset() + completerError?() + default: + break + } + } - func onSoundComplete() { - if !isPlaying { - return + playerItemStatusObservation?.invalidate() + playerItemStatusObservation = newplayerItemStatusObservation + } + + private func setUpPositionObserver(_ player: AVPlayer) { + let interval = toCMTime(millis: 200) + let observer = player.addPeriodicTimeObserver(forInterval: interval, queue: nil) { + [weak self] time in + self?.onTimeInterval(time: time) } + self.observers.append(TimeObserver(player: player, observer: observer)) + } - seek(time: toCMTime(millis: 0)) { - if self.looping { - self.resume() - } else { - self.isPlaying = false - } + private func setUpSoundCompletedObserver(_ player: AVPlayer, _ playerItem: AVPlayerItem) { + let observer = NotificationCenter.default.addObserver( + forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, + object: playerItem, + queue: nil + ) { + [weak self] (notification) in + self?.onSoundComplete() } - - reference.controlAudioSession() - eventHandler.onComplete() + self.observers.append(TimeObserver(player: player, observer: observer)) } - - func onTimeInterval(time: CMTime) { - let millis = fromCMTime(time: time) - eventHandler.onCurrentPosition(millis: millis) + + private func configParameters(player: AVPlayer) { + if (isPlaying) { + player.volume = Float(volume) + player.rate = Float(playbackRate) + } } - - func updateDuration() { + + private func reset() { + playerItemStatusObservation?.invalidate() + playerItemStatusObservation = nil + for observer in observers { + NotificationCenter.default.removeObserver(observer.observer) + } + observers = [] + player?.replaceCurrentItem(with: nil) + } + + private func updateDuration() { guard let duration = player?.currentItem?.asset.duration else { return } @@ -170,79 +239,26 @@ class WrappedMediaPlayer { eventHandler.onDuration(millis: millis) } } - - func setSourceUrl ( - url: String, - isLocal: Bool, - completer: Completer? = nil, - completerError: CompleterError? = nil - ) { - let playbackStatus = player?.currentItem?.status - - if self.url != url || playbackStatus == .failed || playbackStatus == nil { - let parsedUrl = isLocal ? URL.init(fileURLWithPath: url.deletingPrefix("file://")) : URL.init(string: url)! - let playerItem = AVPlayerItem.init(url: parsedUrl) - playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithm.timeDomain - let player: AVPlayer - if let existingPlayer = self.player { - releaseSync() - self.url = url - existingPlayer.replaceCurrentItem(with: playerItem) - player = existingPlayer + + private func onSoundComplete() { + if !isPlaying { + return + } + + seek(time: toCMTime(millis: 0)) { + if self.looping { + self.resume() } else { - player = AVPlayer.init(playerItem: playerItem) - configParameters(player: player) - - self.player = player - self.observers = [] - self.url = url - - // stream player position - let interval = toCMTime(millis: 200) - let timeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: nil) { - [weak self] time in - self?.onTimeInterval(time: time) - } - self.observers.append(TimeObserver(player: player, observer: timeObserver)) - } - - let anObserver = NotificationCenter.default.addObserver( - forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, - object: playerItem, - queue: nil - ) { - [weak self] (notification) in - self?.onSoundComplete() - } - self.observers.append(TimeObserver(player: player, observer: anObserver)) - - // is sound ready - let newKeyValueObservation = playerItem.observe(\AVPlayerItem.status) { (playerItem, change) in - let status = playerItem.status - self.eventHandler.onLog(message: "player status: \(status) change: \(change)") - - if status == .readyToPlay { - self.updateDuration() - completer?() - } else if status == .failed { - self.releaseSync() - completerError?() - } - } - - keyValueObservation?.invalidate() - keyValueObservation = newKeyValueObservation - } else { - if playbackStatus == .readyToPlay { - completer?() + self.isPlaying = false } } + + reference.controlAudioSession() + eventHandler.onComplete() } - func configParameters(player: AVPlayer) { - if (isPlaying) { - player.volume = Float(volume) - player.rate = Float(playbackRate) - } + private func onTimeInterval(time: CMTime) { + let millis = fromCMTime(time: time) + eventHandler.onCurrentPosition(millis: millis) } } diff --git a/packages/audioplayers_darwin/macos/Classes/AudioContext.swift b/packages/audioplayers_darwin/macos/Classes/AudioContext.swift index 93a1092d0..1e4489037 100644 --- a/packages/audioplayers_darwin/macos/Classes/AudioContext.swift +++ b/packages/audioplayers_darwin/macos/Classes/AudioContext.swift @@ -2,7 +2,8 @@ import MediaPlayer // no-op impl of AudioContext for macos struct AudioContext { - func activateAudioSession(active: Bool) throws {} + func activateAudioSession(active: Bool) throws { + } func apply() throws { throw AudioPlayerError.warning("AudioContext configuration is not available on macOS")