Visual_Novel_iOS/crush/Crush/Src/Components/Audio/AudioPlayTool.swift

219 lines
6.4 KiB
Swift
Raw Normal View History

2025-10-09 10:29:35 +00:00
//
// AudioPlayTool.swift
// LegendTeam
//
// Created by dong on 2022/1/2.
//
import Foundation
import UIKit
import AVFAudio
enum VoicePlayViewState {
case normal
case loading
case playing
}
class AudioPlayTool: NSObject {
public var loadCompleteBlock: ((_ url: URL?) -> Void)?
public var playFinishBlock: ((_ url: URL?) -> Void)?
public var playProgressBlock: ((_ secconds: Int?) -> Void)?
private(set) var audioPlayer: AVAudioPlayer?
private(set) var playingUrl: URL?
private(set) var state: VoicePlayViewState = .normal
private var timer: Timer?
private var allowPlay: Bool = false
override init() {
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(enterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
deinit {
if let isPlaying = audioPlayer?.isPlaying, isPlaying == true {
audioPlayer?.stop()
audioPlayer = nil
}
NotificationCenter.default.removeObserver(self)
print("♻️ dealloc audio player")
}
public class func audioChannelFreeToUse() -> Bool{
// guard PhoneManager.isInPhoneChannel() == false && ChatRoomRoute.isChatRoomOpen() == false && PhoneManager.shared().isPlayingRing == false else{
// UIWindow.key?.makeToast("Currently using other voice communication services, please try again later")
// return false
// }
return true
}
// 线
public func load(url: URL?, complete: ((_ url: URL?) -> Void)?) {
stop()
allowPlay = true
loadCompleteBlock = complete
guard let audioUrl = url else {
return
}
playingUrl = audioUrl
state = .loading
DispatchQueue.global().async { [weak self] in
do {
let data = try Data(contentsOf: url!)
if let allow = self?.allowPlay, allow == true {
self?.audioPlayer = try AVAudioPlayer(data: data)
// try audioPlayer = AVAudioPlayer(contentsOf: audioUrl)
self?.audioPlayer?.delegate = self
self?.audioPlayer!.prepareToPlay()
if self?.loadCompleteBlock != nil {
DispatchQueue.main.async { [weak self] in
self?.loadCompleteBlock!(url)
}
}
} else { // eg:
self?.stop()
}
} catch {}
}
}
// base64
public func load(base64String: String?, complete: ((_ url: URL?) -> Void)?) {
stop()
allowPlay = true
loadCompleteBlock = complete
guard let base64Str = base64String, !base64Str.isEmpty else {
complete?(nil)
return
}
state = .loading
DispatchQueue.global().async { [weak self] in
do {
guard let data = Data(base64Encoded: base64Str) else {
DispatchQueue.main.async { [weak self] in
self?.loadCompleteBlock?(nil)
}
return
}
if let allow = self?.allowPlay, allow == true {
self?.audioPlayer = try AVAudioPlayer(data: data)
self?.audioPlayer?.delegate = self
self?.audioPlayer!.prepareToPlay()
if self?.loadCompleteBlock != nil {
DispatchQueue.main.async { [weak self] in
self?.loadCompleteBlock?(nil) // base64 URL nil
}
}
} else { // eg:
self?.stop()
}
} catch {
DispatchQueue.main.async { [weak self] in
self?.loadCompleteBlock?(nil)
}
}
}
}
func isPlaying() -> Bool {
audioPlayer?.isPlaying ?? false
}
public func play() {
let session = AVAudioSession.sharedInstance()
try? session.setCategory(AVAudioSession.Category.playAndRecord)
try? session.overrideOutputAudioPort(.speaker)
try? session.setActive(true)
if playProgressBlock != nil {
startTimer()
}
audioPlayer?.play()
state = .playing
}
public func stop() {
state = .normal
if let isPlaying = audioPlayer?.isPlaying, isPlaying == true {
audioPlayer?.stop()
playFinishBlock?(playingUrl)
clearTimer()
}
allowPlay = false
}
public func setupListenPlayProgress(block: ((_ secconds: Int?) -> Void)?) {
playProgressBlock = block
}
// MARK: - helper
private func clearTimer() {
if timer != nil {
timer?.invalidate()
timer = nil
}
}
private func startTimer() {
clearTimer()
let myTimer = Timer(timeInterval: 0.1, target: self, selector: #selector(timerDidChanged), userInfo: nil, repeats: true)
RunLoop.current.add(myTimer, forMode: .common)
myTimer.fire()
timer = myTimer
}
// MARK: - action
@objc private func timerDidChanged() {
if let audiotime = audioPlayer?.currentTime, let duration = audioPlayer?.duration {
let seconds = Int(ceil(duration - audiotime))
// dlog("player's current time: \(String(describing: audioPlayer?.currentTime)) seconds: \(seconds)")
playProgressBlock?(seconds)
}
}
// MARK: - noti
@objc private func enterBackground() {
stop()
}
}
// MARK: - AVAudioPlayerDelegate
extension AudioPlayTool: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
state = .normal
clearTimer()
DispatchQueue.main.async { [weak self] in
self?.playFinishBlock?(self?.playingUrl)
}
}
func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {}
func audioPlayerBeginInterruption(_ player: AVAudioPlayer) {}
func audioPlayerEndInterruption(_ player: AVAudioPlayer, withOptions flags: Int) {}
}