使用Swift开发一个贝乐虎启蒙App - 音频播放封装

1,203 阅读2分钟

前言

由于只是简单的音频播放,所以我们准备自己用AVFoundation下的AVPlayer自己封装一个,就不去找第三方的了

封装

1、新建一个AudioPlayer类,在里面定义一个PlayState枚举,用来记录播放状态

enum PlayState {
    case stopped /// 停止播放
    case playing /// 正在播放
    case paused /// 暂停播放
    case failed /// 播放错误
}

2、增加一个AudioPlayerDelegate协议

@objc protocol AudioPlayerDelegate: AnyObject {
    /// 播放状态
    @objc optional func playerStateDidChange(_ player: AudioPlayer)
    /// 获取播放时间
    @objc optional func playerCurrentTimeDidChange(_ player: AudioPlayer)
    /// 播放结束
    @objc optional func playerPlaybackDidEnd(_ player: AudioPlayer)
    /// 播放错误
    @objc optional func player(_ player: AudioPlayer, didFailWithError error: Error?)
}

3、在AudioPlayer里面暴露一些属性,方便外面获取与设置

weak var delegate: AudioPlayerDelegate?

/// Pauses playback automatically when resigning active.
var playbackPausesWhenResigningActive: Bool = true

/// Pauses playback automatically when backgrounded.
var playbackPausesWhenBackgrounded: Bool = true

/// Resumes playback when became active.
var playbackResumesWhenBecameActive: Bool = true

/// Resumes playback when entering foreground.
var playbackResumesWhenEnteringForeground: Bool = true

/// 最大时间
var maximumDuration: TimeInterval {
    get {
        if let playerItem = self.playerItem {
            return CMTimeGetSeconds(playerItem.duration)
        } else {
            return CMTimeGetSeconds(CMTime.indefinite)
        }
    }
}

/// 当前时间
var currentTimeInterval: TimeInterval {
    get {
        if let playerItem = self.playerItem {
            return CMTimeGetSeconds(playerItem.currentTime())
        } else {
            return CMTimeGetSeconds(CMTime.indefinite)
        }
    }
}

/// 播放状态
var playState: PlayState = .stopped {
    didSet {
        self.delegate?.playerStateDidChange?(self)
    }
}

4、添加一个AVPlayerAVPlayerItem

private var player: AVPlayer?
private var playerItem: AVPlayerItem?

5、添加一些public方法

/// 播放
func play() {
    if let p = player {
        p.play()
    }
}

/// 暂停
func pause() {
    if let p = player, playState == .playing {
        p.pause()
    }
}

/// 停止
func stop() {
    if playState == .stopped {
        return
    }
    self.player?.pause()
    self.playState = .stopped
    self.delegate?.playerPlaybackDidEnd?(self)
}

/// 快进/快退
func seek(to time: CMTime) {
    if let playerItem = self.playerItem {
        return playerItem.seek(to: time, completionHandler: nil)
    }
}

/// 改变播放状态
func changePlay() {
    switch playState {
    case .playing:
        pause()
    case .paused:
        play()
    default:
        pause()
    }
}

6、添加player播放进度和状态的监听

private func addObserver() {
    player?.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 100), queue: .main, using: { [weak self] (cmTime) in
        guard let `self` = self else { return }
        self.delegate?.playerCurrentTimeDidChange?(self)
    })

    player?.addObserver(self, forKeyPath: "timeControlStatus", options: .new, context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "timeControlStatus", let p = player {
        switch p.timeControlStatus {
        case .paused:
            playState = .paused
        case .playing:
            playState = .playing
        case .waitingToPlayAtSpecifiedRate:
            fallthrough
        default:
            break
        }
    }
}

7、由于我们app退到后台和回到前台要设置是否播放处理,所以要监听app的活跃状态来做一些处理

private func addApplicationObservers() {
    NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationWillResignActive(_:)), name: UIApplication.willResignActiveNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationDidBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationDidEnterBackground(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(handleApplicationWillEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
}

@objc private func handleApplicationWillResignActive(_ aNotification: Notification) {
    if playState == .playing && playbackPausesWhenResigningActive {
        pause()
    }
}

@objc private func handleApplicationDidBecomeActive(_ aNotification: Notification) {
    if playState == .paused && playbackResumesWhenBecameActive {
        play()
    }
}

@objc private func handleApplicationDidEnterBackground(_ aNotification: Notification) {
    if playState == .playing && playbackPausesWhenBackgrounded {
        pause()
    }
}

@objc private func handleApplicationWillEnterForeground(_ aNoticiation: Notification) {
    if playState != .playing && playbackResumesWhenEnteringForeground {
        play()
    }
}

8、暴露一个设置url的方法来播放

func player(_ url: String) {
    guard let u = URL(string: url) else { return }
    playerItem = AVPlayerItem(url: u)
    if player == nil {
        player = AVPlayer(playerItem: playerItem)
        addObserver()
        addApplicationObservers()
        do {
            /// 使用这个category的应用不会随着手机静音键打开而静音,可在手机静音下播放声音
            try AVAudioSession.sharedInstance().setCategory(.playback)
        } catch {}
    } else {
        /// 替换播放的 playerItem
        player?.replaceCurrentItem(with: playerItem)
    }
    addPlayToEndTimeObserver()
    play()
}

音频播放封装就结束了,下一篇就可以开始使用它来播放