fix: make audio player safe with other app audio/music session

This commit is contained in:
CMK 2021-06-30 17:56:31 +08:00
parent 001d216d12
commit cdbb9cf2d1
4 changed files with 42 additions and 33 deletions

View File

@ -69,7 +69,8 @@ final class VideoPlayerViewModel {
case .gif:
break
case .video:
try? AVAudioSession.sharedInstance().setCategory(.soloAmbient, mode: .default)
break
// try? AVAudioSession.sharedInstance().setCategory(.soloAmbient, mode: .default)
}
}
.store(in: &disposeBag)
@ -107,7 +108,8 @@ extension VideoPlayerViewModel {
case .gif:
break
case .video:
try? AVAudioSession.sharedInstance().setCategory(.soloAmbient, mode: .default)
break
// try? AVAudioSession.sharedInstance().setCategory(.soloAmbient, mode: .default)
}
player.play()

View File

@ -23,7 +23,6 @@ final class AudioPlaybackService: NSObject {
var statusObserver: Any?
var attachment: Attachment?
let session = AVAudioSession.sharedInstance()
let playbackState = CurrentValueSubject<PlaybackState, Never>(PlaybackState.unknown)
let currentTimeSubject = CurrentValueSubject<TimeInterval, Never>(0)
@ -31,6 +30,23 @@ final class AudioPlaybackService: NSObject {
override init() {
super.init()
addObserver()
playbackState
.receive(on: RunLoop.main)
.sink { status in
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: audio status: %s", ((#file as NSString).lastPathComponent), #line, #function, status.description)
switch status {
case .unknown, .buffering, .readyToPlay:
break
case .playing:
try? AVAudioSession.sharedInstance().setCategory(.soloAmbient)
try? AVAudioSession.sharedInstance().setActive(true)
case .paused, .stopped, .failed:
try? AVAudioSession.sharedInstance().setCategory(.ambient)
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
}
}
.store(in: &disposeBag)
}
}
@ -39,12 +55,6 @@ extension AudioPlaybackService {
guard let url = URL(string: audioAttachment.url) else {
return
}
do {
try session.setCategory(.playback)
} catch {
print(error)
return
}
notifyWillPlayAudioNotification()
if audioAttachment == attachment {
@ -64,27 +74,6 @@ extension AudioPlaybackService {
}
func addObserver() {
UIDevice.current.isProximityMonitoringEnabled = true
NotificationCenter.default.publisher(for: UIDevice.proximityStateDidChangeNotification, object: nil)
.sink { [weak self] _ in
guard let self = self else { return }
if UIDevice.current.proximityState == true {
do {
try self.session.setCategory(.playAndRecord)
} catch {
print(error)
return
}
} else {
do {
try self.session.setCategory(.playback)
} catch {
print(error)
return
}
}
}
.store(in: &disposeBag)
NotificationCenter.default.publisher(for: VideoPlayerViewModel.appWillPlayVideoNotification)
.sink { [weak self] _ in
guard let self = self else { return }
@ -96,7 +85,7 @@ extension AudioPlaybackService {
guard let self = self else { return }
self.currentTimeSubject.value = time.seconds
})
player.publisher(for: \.status, options: .new)
player.publisher(for: \.status, options: [.initial, .new])
.sink(receiveValue: { [weak self] status in
guard let self = self else { return }
switch status {

View File

@ -23,3 +23,21 @@ public enum PlaybackState : Int {
case failed = 6
}
// MARK: - CustomStringConvertible
extension PlaybackState: CustomStringConvertible {
public var description: String {
switch self {
case .unknown: return "unknown"
case .buffering: return "buffering"
case .readyToPlay: return "readyToPlay"
case .playing: return "playing"
case .paused: return "paused"
case .stopped: return "stopped"
case .failed: return "failed"
default:
assertionFailure()
return "<nil>"
}
}
}

View File

@ -40,7 +40,7 @@ extension VideoPlaybackService {
} else {
if latestPlayingVideoPlayerViewModel === playerViewModel {
latestPlayingVideoPlayerViewModel = nil
try? AVAudioSession.sharedInstance().setCategory(.soloAmbient, mode: .default)
// try? AVAudioSession.sharedInstance().setCategory(.soloAmbient, mode: .default)
}
}
}
@ -111,7 +111,7 @@ extension VideoPlaybackService {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
// note: do not retain view controller
// pause all player when view disppear exclude full screen player and other transitioning scene
// pause all player when view disappear exclude full screen player and other transitioning scene
for viewModel in viewPlayerViewModelDict.values {
guard !viewModel.isTransitioning else {
viewModel.isTransitioning = false