Tusker/Tusker/AudioSessionCoordinator.swift

93 lines
2.9 KiB
Swift

//
// AudioSessionCoordinator.swift
// Tusker
//
// Created by Shadowfacts on 7/8/24.
// Copyright © 2024 Shadowfacts. All rights reserved.
//
import Foundation
import AVFoundation
final class AudioSessionCoordinator {
static let shared = AudioSessionCoordinator()
private init() {}
private let queue = DispatchQueue(label: "AudioSessionCoordinator", qos: .userInitiated)
private var videoCount = 0
private var gifvCount = 0
func beginPlayback(mode: Mode, completionHandler: (() -> Void)? = nil) -> Token {
let token = Token(mode: mode)
queue.async {
switch mode {
case .video:
self.videoCount += 1
case .gifv:
self.gifvCount += 1
}
self.update(completionHandler: completionHandler)
}
return token
}
func endPlayback(token: Token, completionHandler: (() -> Void)? = nil) {
// mark the token as consumed, so when it's deinited we don't try to end again
token.consumed = true
// the enqueued block can't retain token, since it may be being dealloc'd right now
let mode = token.mode
queue.async {
switch mode {
case .video:
self.videoCount -= 1
case .gifv:
self.gifvCount -= 1
}
self.update(completionHandler: completionHandler)
}
}
private func update(completionHandler: (() -> Void)?) {
let currentCategory = AVAudioSession.sharedInstance().category
if videoCount > 0 {
try? AVAudioSession.sharedInstance().setCategory(.playback)
try? AVAudioSession.sharedInstance().setActive(true)
} else if gifvCount > 0 {
// if we're transitioning from video to gifv, fully deactivate first
// in order to let other (music) apps resume, then activate with the
// ambient category to "mix" with others
if currentCategory == .playback {
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
}
// only gifv modes requested, allow mixing with others
try? AVAudioSession.sharedInstance().setCategory(.ambient)
try? AVAudioSession.sharedInstance().setActive(true)
} else {
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
}
completionHandler?()
}
final class Token {
let mode: Mode
fileprivate var consumed = false
init(mode: Mode) {
self.mode = mode
}
deinit {
if !consumed {
AudioSessionCoordinator.shared.endPlayback(token: self)
}
}
}
enum Mode {
case video
case gifv
}
}