Sample: Working Example of Background Audio Mode with Displaying Information in Command Center and Handling Remote Control Commands

I have previously written separate articles:

Here is the sample project: MediaPlayerExample

Get device token for remote push notifications in Swift 3

When registering device to receive remote notifications, we can use device token to uniquely identify each device and even send the token to our server.

Consider the following code

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {

    let tokenParts = deviceToken.map { data -> String in
        return String(format: "%02.2hhx", data)
    }

    let token = tokenParts.joined()

    // process token

}

Here is gist for the example

Source: RayWenderlich

Custom Sorting an Array in Swift

Let’s assume we have following Business struct

struct Business {
    let businessId: Int
    let rating: Int
}

And we have following data set for business with left value being businessId and right being rating

167 1
196 2
171 3
147 4
153 5
191 1
125 2
126 3
174 4
175 5
103 1
119 2
186 3
157 4
178 5
110 1
130 2
168 3
129 4
120 5

We should sort businesses in descending order of rating. For the businesses that have same rating, the original order of business should remain same.

To solve this, we can have following function:

func sortBusinesses(_ businesses: [Business]) -> [Business] {

    let set = NSOrderedSet(array: businesses)
    let newSet = set.sortedArray(comparator: { first, second -> ComparisonResult in
        let firstBusiness = first as! Business
        let secondBusiness = second as! Business
        if firstBusiness.rating == secondBusiness.rating {
            return .orderedSame
        }
        return .orderedDescending
    })
    return newSet as! [Business]

}

let sortedBusinesses = sortBusinesses(businesses)

The above will give us following output:

153 5
175 5
178 5
120 5
147 4
174 4
157 4
129 4
171 3
126 3
186 3
168 3
196 2
125 2
119 2
130 2
167 1
191 1
103 1
110 1

Here is gist for the example

Handle Remote Control Commands

Handle playback controls for background audio mode

iOS Applications that play audio in background mode, can be controlled from remote control commands inside command center or the lock screen of device.

We will need to import MediaPlayer initially.

import MediaPlayer

And also player in our UIViewController subclass

var player: AVPlayer?

We will need setup function which can enable/disable remote commands

func setupRemoteCommandCenter(enable: Bool) {

    let remoteCommandCenter = MPRemoteCommandCenter.shared()

    if enable {

        remoteCommandCenter.pauseCommand.addTarget(self, action: #selector(remoteCommandCenterPauseCommandHandler))
        remoteCommandCenter.playCommand.addTarget(self, action: #selector(remoteCommandCenterPlayCommandHandler))
        remoteCommandCenter.stopCommand.addTarget(self, action: #selector(remoteCommandCenterStopCommandHandler))
        remoteCommandCenter.togglePlayPauseCommand.addTarget(self, action: #selector(remoteCommandCenterPlayPauseCommandHandler))

    } else {

        remoteCommandCenter.pauseCommand.removeTarget(self, action: #selector(remoteCommandCenterPauseCommandHandler))
        remoteCommandCenter.playCommand.removeTarget(self, action: #selector(remoteCommandCenterPlayCommandHandler))
        remoteCommandCenter.stopCommand.removeTarget(self, action: #selector(remoteCommandCenterStopCommandHandler))
        remoteCommandCenter.togglePlayPauseCommand.removeTarget(self, action: #selector(remoteCommandCenterPlayPauseCommandHandler))

    }

    remoteCommandCenter.pauseCommand.isEnabled = enable
    remoteCommandCenter.playCommand.isEnabled = enable
    remoteCommandCenter.stopCommand.isEnabled = enable
    remoteCommandCenter.togglePlayPauseCommand.isEnabled = enable

}

This function will be called with true to enable

setupRemoteCommandCenter(enable: true)

And should be called with false in deinit to stop handling remote commands

deinit {        
    setupRemoteCommandCenter(enable: false)
}

And the actual function selectors to handle remote commands

func remoteCommandCenterPauseCommandHandler() {

    // handle pause
    player?.pause()

}

func remoteCommandCenterPlayCommandHandler() {

    // handle play
    player?.play()

}

func remoteCommandCenterStopCommandHandler() {

    // handle stop
    player?.pause()

}

func remoteCommandCenterPlayPauseCommandHandler() {

    // handle play pause
    if player?.rate == 0.0 {
        player?.play()
    } else {
        player?.pause()
    }

}

We will need setup player function

func setupPlayer() {

    let streamURL = URL(string: "https://audio.stream.m3u8")!
    self.player = AVPlayer(url: streamURL)
    self.player?.play()

}

And in our example, we have a button selector function to call necessary code

@IBAction func playButtonHandler(btn: UIButton) {

    setupRemoteCommandCenter(enable: true)

    setupPlayer()

}

Here is gist for the example

Display now playing information in Command Center

Previously, I covered an article Configure Audio Session for background audio mode (iOS Project). Next step is to show relevant information in Command Center for audio played from your iOS Application.

Let’s start by importing MediaPlayer

import MediaPlayer

Next, place the following code to set now playing information

let image = UIImage(named: "artwork")!
let mediaArtwork = MPMediaItemArtwork(boundsSize: image.size) { (size: CGSize) -> UIImage in
  return image
}

let nowPlayingInfo: [String: Any] = [
  MPMediaItemPropertyArtist: "hashaam.com",
  MPMediaItemPropertyTitle: "Live Streaming Example",
  MPMediaItemPropertyArtwork: mediaArtwork,
  MPNowPlayingInfoPropertyIsLiveStream: true
]

MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo  

Notice, setting the MPNowPlayingInfoPropertyIsLiveStream to true will show LIVE status in Command Center.

This gives required information to be displayed in Command Center
Display now playing information in Command Center

And also in the lock screen
Display now playing information in Lock Screen

Here is gist for the example

Configure Audio Session for background audio mode (iOS Project)

We use AVPlayer from AVFoundation to play audio in our applications. The moment we send the application to back ground mode, audio stops playing.

To fix this, we will need to configure AVAudioSession properly. We start by importing AVFoundation in AppDelegate or our iOS Project.

import AVFoundation

Next, add the following code in application didFinishLaunchingWithOptions:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    // enable playback category: this is required for background audio to function normally
    let audioSession = AVAudioSession.sharedInstance()
    try? audioSession.setCategory(AVAudioSessionCategoryPlayback, mode: AVAudioSessionModeDefault)
    try? audioSession.setActive(true, with: [])
    
    return true
}

Finally, enable Background Audio Mode in Project Settings.
Enable background audio mode in XCode Project Settings

Here is gist for the example

Strip HTML tags in Swift

There are many cases where we need to clean out text from an HTML source. Many people have solved this in their own way. But I have come across a simple solution to this problem. The solution was to use NSAttributedString with setting NSDocumentTypeDocumentAttribute to NSHTMLTextDocumentType.

Let’s start with the following HTML:

let htmlString = "LCD Soundsystem was the musical project of producer <a href='http://www.last.fm/music/James+Murphy' class='bbcode_artist'>James Murphy</a>, co-founder of <a href='http://www.last.fm/tag/dance-punk' class='bbcode_tag' rel='tag'>dance-punk</a> label <a href='http://www.last.fm/label/DFA' class='bbcode_label'>DFA</a> Records. Formed in 2001 in New York City, New York, United States, the music of LCD Soundsystem can also be described as a mix of <a href='http://www.last.fm/tag/alternative%20dance' class='bbcode_tag' rel='tag'>alternative dance</a> and <a href='http://www.last.fm/tag/post%20punk' class='bbcode_tag' rel='tag'>post punk</a>, along with elements of <a href='http://www.last.fm/tag/disco' class='bbcode_tag' rel='tag'>disco</a> and other styles. "

Below is a working example:

let htmlStringData = htmlString.data(using: String.Encoding.utf8)!

let options: [String: Any] = [
    NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
    NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue
]

let attributedString = try! NSAttributedString(data: htmlStringData, options: options, documentAttributes: nil)

let stringWithoutHTMLTags = attributedString.string

This process is slower when working on a long HTML source. Which can be easily offloaded onto a background process:

let htmlStringData = htmlString.data(using: String.Encoding.utf8)!

let options: [String: Any] = [
    NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
    NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue
]

DispatchQueue.global(qos: .userInitiated).async {

    // perform in background thread
    let attributedString = try! NSAttributedString(data: htmlStringData, options: options, documentAttributes: nil)

    DispatchQueue.main.async {
        // handle text in main thread
        let stringWithoutHTMLTags = attributedString.string
    }

}

 

Also in gist:

Source: Stack Overflow