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