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

219 lines
6.4 KiB
Swift
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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) {}
}