According to the company’s demand for audio files for background playback, take this opportunity to make a summary of audio playback. AVPlayer is explained in detail.

Compare iOS players

The name of the Using the environment advantages disadvantages
System Sound Services AVFoundation C language written at the bottom, save memory The supported formats are limited, the volume cannot be controlled by volume keys, and the playback mode is single.
AVAudioPlayer AVFoundation Express efficiency is higher, basically support all audio formats, to play the control, such as loop play, sound size, pause and so on more convenient. It will consume more memory. Streaming is not supported, that is, online music cannot be played.
AVPlayer AVFoundation Can play audio and video, can play online music, flexible use
MPMoviePlayerController MediaPlayer Simple and easy to use Not custom
AVPlayerViewController AVKit Simple and easy to use Not custom
IJKPlayer IJKMediaFramework Set high system, support streaming media playback Slightly more complicated to use

Use AVPlayer

Introduction to the

AVPlayer is a popular video player component on iOS. It supports common audio and video formats, supports streaming, and can play online music. Support video formats: WMV, AVI, MKV, RMVB, RM, XVID, MP4, 3GP, MPG, etc. Support audio formats: MP3, WMA, RM, ACC, OGG, APE, FLAC, FLV, etc.

Related classes

  • AVPlayer: player, control the player play, pause, play speed.
  • AVURLAsset: a subclass of AVAsset that instantiates the video resource using a URL. The instantiated object replaces all information about the video resource corresponding to the URL.
  • AVPlayerItem: Manages resource objects and provides play data sources.
  • AVPlayerLayer: is responsible for displaying video, if this class is not added, only sound without screen.

Simple to use

Create an AVPlayer using a URL

let player = AVPlayer(url: URL(string: "http://www.xxxx.mp3"))
Copy the code

Create AVPlayer with AVPlayerItem

if let url = URL(string: "http://www.***.mp3") {
    let asset = AVAsset(url: url)
    guard asset.isPlayable else{
        // Check whether the file is playable
        return
    }
    let playItem = AVPlayerItem(asset: asset)
    let player = AVPlayer(playerItem: playItem)
    player.play()
}
Copy the code

AVPlayer controls playback

player.play() / / play
player.pause() / / pause
player.rate = 1.0 // Playback speed
Copy the code

Listen for play state changes through notifications

// Play finished
AVPlayerItemDidPlayToEndTimeNotification
// Playback failed
AVPlayerItemFailedToPlayToEndTimeNotification
// Abnormal interrupt
AVPlayerItemPlaybackStalledNotification

// eg: Play the end notification
NotificationCenter.default.addObserver(self, selector: #selector(finish(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
Copy the code

Monitor playback progress

// Add a cycle time observer to execute a block once a second
let timeObserver = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: 1), queue: DispatchQueue.main, using: { [weak self] (cmTime) in
    if let totalTime = self? .currentPlayItem? .duration {self? .delegate?.player(self! , currentTime: cmTime.seconds, totalTime: totalTime.seconds) } })// Don't forget to remove
player.removeTimeObserver(observer)
Copy the code

AVPlayerItem create

// Create with AVAsset
if let url = URL(string: "http://www.***.mp3") {
    let asset = AVAsset(url: url)
    guard asset.isPlayable else{
        // Check whether the file is playable
        return
    }
    let playItem = AVPlayerItem(asset: asset)
}

// Create with URL
if let url = URL(string: "http://www.***.mp3") {
    let playItem = AVPlayerItem(url: url)
}
Copy the code

Listen for AVPlayerItem status and cache progress

// Listen for playerItem status changes
playItem.addObserver(self, forKeyPath: "status", options: .new, context: nil)
// Listen for cache time
playItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)

// Remove the listenercurrentPlayItem? .removeObserver(self, forKeyPath: "status") currentPlayItem? .removeObserver(self, forKeyPath: "loadedTimeRanges")
Copy the code
override func observeValue(forKeyPath keyPath: String? , of object: Any? , change: [NSKeyValueChangeKey : Any]? , context: UnsafeMutableRawPointer?) {
    
    if object is AVPlayerItem {
        if keyPath == "status" {
            if let playerItem = object as? AVPlayerItem {
                switch playerItem.status {
                case .readyToPlay:
                    // Get ready to play
                case .failed:
                    // Load failed
                default:
                    // Unknown status}}}if keyPath == "loadedTimeRanges" {
            if let playerItem = object as? AVPlayerItem {
                if let timeRange = playerItem.loadedTimeRanges.first as? CMTimeRange {
                    let cache = timeRange.start.seconds + timeRange.duration.seconds // Total cache duration}}}}Copy the code

Audio Background playback

Enable the required background mode

AVAudioSession is used to apply for background playback permission

let session = AVAudioSession.sharedInstance()
do {
    try session.setActive(true)
    try session.setCategory(AVAudioSessionCategoryPlayback)}catch {
    print(error)
}
Copy the code

Accept Remote Control in the Play Control interface

Enable remote Control

/ / declare receiving Remote Control events UIApplication. Shared. BeginReceivingRemoteControlEvents ()Copy the code

Set the Remote Control response

// Respond to the Remote Control event
MPRemoteCommandCenter.shared().playCommand.addTarget(self, action: #selector(play))
MPRemoteCommandCenter.shared().nextTrackCommand.addTarget(self, action: #selector(next))
MPRemoteCommandCenter.shared().pauseCommand.addTarget(self, action: #selector(pause))
MPRemoteCommandCenter.shared().previousTrackCommand.addTarget(self, action: #selector(previous))
Copy the code

Remove the Remote Control response

// Remember to remove when closing the playback page
MPRemoteCommandCenter.shared().playCommand.removeTarget(self, action: #selector(play))
MPRemoteCommandCenter.shared().nextTrackCommand.removeTarget(self, action: #selector(next))
MPRemoteCommandCenter.shared().pauseCommand.removeTarget(self, action: #selector(pause))
MPRemoteCommandCenter.shared().previousTrackCommand.removeTarget(self, action: #selector(previous))
// Stop responding to Remote Control
UIApplication.shared.endReceivingRemoteControlEvents()
Copy the code

Respond to external events by overriding a parent class method

  • Enable receiving remote control

  • Make the current page the first responder

  • Rewrite remoteControlReceivedWithEvent method. UIEvent Type Values:

    • UIEventSubtypeRemoteControlTogglePlayPause / / pause
    • UIEventSubtypeRemoteControlPreviousTrack / / a
    • UIEventSubtypeRemoteControlNextTrack / / a
    • UIEventSubtypeRemoteControlPlay / / play
    • UIEventSubtypeRemoteControlPause / / pause
  • Close accept remote control

Lock screen page display Playing information (Now Playing Center)

Run the MPNowPlayingInfoCenter command to set the music information on the lock screen.

func setLockScreenPlayingInfo(_ info: YTTMediaInfo) {
    // Now Playing Center can display music information on the lock screen and enhance user experience.
    // https://www.jianshu.com/p/458b67f84f27
    var infoDic: [String : Any] = [:]
    infoDic[MPMediaItemPropertyTitle] = info.title / / song name
    infoDic[MPMediaItemPropertyArtist] = info.singer / / singer
    if let img = info.image {
        infoDic[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(image: img) // Album image
    }
    infoDic[MPMediaItemPropertyPlaybackDuration] = info.totalTime // Total song duration
    infoDic[MPNowPlayingInfoPropertyElapsedPlaybackTime] = info.currentTime // Current playback time
    infoDic[MPNowPlayingInfoPropertyPlaybackRate] = 1.0 // Playback speed
    MPNowPlayingInfoCenter.default().nowPlayingInfo = infoDic
}
Copy the code

Note: MPNowPlayingInfoPropertyElapsedPlaybackTime Settings is not always, he is according to you to set the value of the time, if you want to get the exact time the lock screen page, please refresh MPNowPlayingInfoPropertyElapsedPlaybackTime values. When suspended to suspend play time, just put the MPNowPlayingInfoPropertyPlaybackRate is set to 0. Set it back to 1 when playing.

added

IOS is very strict about background management. Any app has about 3 or 10 minutes of background execution time. After 3 or 10 minutes, the app will be forced to suspend. When using AVAudioSession to apply for background permission, it can ensure that local music can be played in the background for a long time. When network music is played, it cannot be played. In this case, beginBackgroundTask is used to set background task ID 10 minutes to perform background tasks. In order to be able to play network music in the background unlimited add timer, will suspend immediately when applying for background task ID again.

func applicationDidEnterBackground(_ application: UIApplication) {
    
    // To do this, press the home button to enter the background and play for a few minutes. However, network songs cannot be continuously played. To continuously play network songs, you need to apply for a background task ID
    bgTask = application.beginBackgroundTask(expirationHandler: nil)
    timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)}@objc func timerAction(a) {
    timerCount = timerCount + 1
    if timerCount < 500 {
        return
    }
    timerCount = 0
    let newTask = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
    ifbgTask ! =UIBackgroundTaskInvalid&& newTask ! =UIBackgroundTaskInvalid {
        UIApplication.shared.endBackgroundTask(bgTask)
        bgTask = newTask
    }
    
}
Copy the code

other

  • AVPlayer the pit
  • Project Reference Address
  • Implement Demo for caching