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