iOS 後臺播放靜音音頻保證應用不會被無端殺掉(2)針對swift4.2

上一篇後臺播放靜音音頻,在生產上還是有點用處的,如果不是客戶自己手動殺掉APP 一般還是能在後臺保存很久的。下面這個是根據之前的稍作修改主要是針對swift4.2以後使用

創建

AudioManager.swift

import Foundation
import AVFoundation
import UIKit
class AudioManager: NSObject {
    
    static let shared = AudioManager()
    fileprivate let audioSession = AVAudioSession.sharedInstance()
    fileprivate var backgroundAudioPlayer: AVAudioPlayer?
    fileprivate var backgroundTimeLength = 0
    fileprivate var timer: Timer?
    
    // 是否開啓後臺自動播放無聲音樂
    var openBackgroundAudioAutoPlay = false {
        didSet {
            if self.openBackgroundAudioAutoPlay {
                self.setupAudioSession()
                self.setupBackgroundAudioPlayer()
            } else {
                if let player = self.backgroundAudioPlayer {
                    if player.isPlaying {
                        player.stop()
                    }
                }
                self.backgroundAudioPlayer = nil
                try? self.audioSession.setActive(false, options:  AVAudioSession.SetActiveOptions.notifyOthersOnDeactivation)
            }
        }
    }
    
    override init() {
        super.init()
        self.setupListener()
    }
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    private func setupAudioSession() {
        do {
            try self.audioSession.setCategory(AVAudioSession.Category.playback, options: AVAudioSession.CategoryOptions.mixWithOthers)
            try self.audioSession.setActive(false)
        } catch let error {
            debugPrint("\(type(of:self)):\(error)")
        }
    }
    private func setupBackgroundAudioPlayer() {
        do {
            self.backgroundAudioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: Bundle.main.path(forResource: "WhatYouWant", ofType: "mp3")!))
            debugPrint(Bundle.main.path(forResource: "WhatYouWant", ofType: "mp3")!)
        } catch let error {
            debugPrint("\(type(of:self)):\(error)")
        }
        self.backgroundAudioPlayer?.numberOfLoops = -1
        self.backgroundAudioPlayer?.volume = 1
        self.backgroundAudioPlayer?.delegate = self
    }
    
//主要修改了這裏
    private func setupListener() {
        
        NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
        
        NotificationCenter.default.addObserver(self, selector: #selector(audioSessionInterruption(notification:)), name: AVAudioSession.interruptionNotification, object: nil)
        
       
    }
    
}

// MARK: - 擴展 監聽通知
extension AudioManager {
    /// 進入後臺 播放無聲音樂
    @objc public func didEnterBackground() {
        self.setupTimer()
        guard self.openBackgroundAudioAutoPlay else {return}
        
        do {
            try self.audioSession.setActive(true)
        } catch let error {
            debugPrint("\(type(of:self)):\(error))")
        }
        self.backgroundAudioPlayer?.prepareToPlay()
        self.backgroundAudioPlayer?.play()
    }
    
    /// 進入前臺,暫停播放音樂
    @objc public func didBecomeActive() {
        self.removeTimer()
        self.hintBackgroundTimeLength()
        self.backgroundTimeLength = 0
        guard self.openBackgroundAudioAutoPlay else {return}
        
        self.backgroundAudioPlayer?.pause()
        do {
            try self.audioSession.setActive(false, options:  AVAudioSession.SetActiveOptions.notifyOthersOnDeactivation)
        } catch let error {
            debugPrint("\(type(of:self)):\(error))")
        }
        
        
    }
    
    /// 音樂中斷處理
    @objc fileprivate func audioSessionInterruption(notification: NSNotification) {
        guard self.openBackgroundAudioAutoPlay else {return}
        guard let userinfo = notification.userInfo else {return}
        guard let interruptionType: UInt = userinfo[AVAudioSessionInterruptionTypeKey] as! UInt?  else {return}
        if interruptionType == AVAudioSession.InterruptionType.began.rawValue {
            // 中斷開始,音樂被暫停
            debugPrint("\(type(of:self)): 中斷開始 userinfo:\(userinfo)")
        } else if interruptionType == AVAudioSession.InterruptionType.ended.rawValue {
            // 中斷結束,恢復播放
            debugPrint("\(type(of:self)): 中斷結束 userinfo:\(userinfo)")
            guard let player = self.backgroundAudioPlayer else {return}
            if player.isPlaying == false {
                debugPrint("\(type(of:self)): 音樂未播放,準備開始播放")
                do {
                    try self.audioSession.setActive(true)
                } catch let error {
                    debugPrint("\(type(of:self)):\(error)")
                }
                player.prepareToPlay()
                player.play()
            } else {
                debugPrint("\(type(of:self)): 音樂正在播放")
            }
        }
    }
}
// MARK: - 擴展 定時器任務
extension AudioManager {
    fileprivate func setupTimer() {
        self.removeTimer()
        self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerTask), userInfo: nil, repeats: true)
        RunLoop.main.add(self.timer!, forMode: RunLoop.Mode.common)
//         RunLoop.main.add(self.timer!, forMode: RunLoop.Mode.init(rawValue: ""))
    }
    fileprivate func removeTimer() {
        self.timer?.invalidate()
        self.timer = nil;
    }
    @objc func timerTask() {
        self.backgroundTimeLength += 1
    }
    fileprivate func hintBackgroundTimeLength() {
        let message = "本次後臺持續時間:\(self.backgroundTimeLength)s"
        HintTool.hint(message)
    }
}

// MARK: - 擴展 播放代理
extension AudioManager: AVAudioPlayerDelegate {
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        
    }
    func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
        debugPrint("\(type(of:self))" + error.debugDescription)
    }
}

再寫個提示工具

HintTool.swift

import UIKit


class HintTool {
    
    private var hintView: UIView?
    
    static let shared = HintTool()
    
    static func hint(_ message: String) {
        self.shared.showHintView(hintView: self.hintView(with: message))
    }
    
    private func showHintView(hintView: UIView) {
        guard let window = UIApplication.shared.delegate?.window else {return}
        guard self.hintView == nil else {return}
        
        window!.addSubview(hintView)
        window!.bringSubviewToFront(hintView)
        self.hintView = hintView
        
        // 消失
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
            [weak self] in
            UIView.animate(withDuration: 0.5, animations: {
                [weak self] in
                self?.hintView?.alpha = 0.5
                }, completion: { (finished) in
                    self?.hintView?.removeFromSuperview()
                    self?.hintView = nil
            })
        }
    }
    
    private static func hintView(with message: String) -> UIView {
        let minWidth = 180.0
        let maxWidth = 260.0
        let padding = 10.0
        let font = UIFont.systemFont(ofSize: 14)
        
        let messageSize = message.ext_size(withBoundingSize: CGSize(width: maxWidth-2*padding, height: 0) , font: font)
        
        let labelFrame = CGRect(x: 0, y: 0, width: CGFloat(ceilf(Float(messageSize.width))), height: CGFloat(ceilf(Float(messageSize.height))))
        let viewFrame = CGRect(x: 0, y: 0, width: max(minWidth, Double(messageSize.width) + padding*2), height: Double(messageSize.height) + padding*2)
        
        let hintView = UIView()
        hintView.isUserInteractionEnabled = false
        hintView.backgroundColor = UIColor(white: 0, alpha: 0.7)
        hintView.layer.cornerRadius = 8
        hintView.layer.masksToBounds = true
        hintView.frame = viewFrame
        hintView.center = CGPoint(x: CGFloat(ceilf(Float(UIScreen.main.bounds.size.width*0.5))), y: CGFloat(ceilf(Float(UIScreen.main.bounds.size.height-100.0))))
        
        let hintLabel = UILabel()
        hintView.addSubview(hintLabel)
        hintView.isUserInteractionEnabled = false
        hintLabel.text = message
        hintLabel.textColor = UIColor.white
        hintLabel.textAlignment = .center
        hintLabel.font = font
        hintLabel.preferredMaxLayoutWidth = messageSize.width
        hintLabel.numberOfLines = 0
        hintLabel.frame = labelFrame
        hintLabel.center = CGPoint(x: CGFloat(ceilf(Float(hintView.bounds.size.width*0.5))), y: CGFloat(ceilf(Float(hintView.bounds.size.height*0.5))))
        
        return hintView
    }
}

extension String {
    func ext_size(withBoundingSize boundingSize: CGSize, font: UIFont) -> CGSize {
        let option = NSStringDrawingOptions.usesLineFragmentOrigin
        let attributes = [NSAttributedString.Key.font : font]
        let contentSize = self.boundingRect(with: boundingSize, options: option, attributes: attributes, context: nil).size
        return contentSize
    }
}

 

在viewcontroller裏使用 

import UIKit
import AVFoundation

let kBackgroundAudioAutoPlayOpenString = "後臺自動播放音樂 - 開啓"
let kBackgroundAudioAutoPlayCloseString = "後臺自動播放音樂 - 關閉"

class ViewController: UIViewController {
    
    let backgroundModeBtn = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.backgroundModeBtn.setTitle(kBackgroundAudioAutoPlayOpenString, for: .normal)
        self.backgroundModeBtn.setTitleColor(UIColor.black, for: .normal)
        self.backgroundModeBtn.addTarget(self, action: #selector(backgroundModeBtnDidClick), for: .touchUpInside)
        self.backgroundModeBtn.backgroundColor = UIColor.cyan
        self.view.addSubview(self.backgroundModeBtn)
        
        AudioManager.shared.openBackgroundAudioAutoPlay = false
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.backgroundModeBtn.sizeToFit()
        self.backgroundModeBtn.center = self.view.center
    }
    @objc func backgroundModeBtnDidClick() {
        guard let title = self.backgroundModeBtn.title(for: .normal) else {return}
        
        if title == kBackgroundAudioAutoPlayOpenString {
            AudioManager.shared.openBackgroundAudioAutoPlay = true
            self.backgroundModeBtn.setTitle(kBackgroundAudioAutoPlayCloseString, for: .normal)
        } else {
            AudioManager.shared.openBackgroundAudioAutoPlay = false
            self.backgroundModeBtn.setTitle(kBackgroundAudioAutoPlayOpenString, for: .normal)
        }
    }
    
    func didBecomeActive() {
        debugPrint("\(type(of:self)): 11111")
    }
    
}

在applegate裏初始化

  private func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        AudioManager.shared.openBackgroundAudioAutoPlay = true
        return true
    }

最後代碼在此 傳送門

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章