219 lines
6.4 KiB
Swift
Executable File
219 lines
6.4 KiB
Swift
Executable File
//
|
||
// 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) {}
|
||
}
|