IN THIS ARTICLE

    With the steady rise of Apple Music, Apple has released an API allowing developers to check if a user is an Apple Music member, check which country the user account is associated with, queue up songs using song id’s for playback and can look at playlists the user already has or create a new one. This opens up a vast amount of possibilities to developers to create exciting new music applications, especially with how prevalent digital music services are now in peoples lives.

     

    What the Radio Station App Does

    In this tutorial we’ll create a realtime radio station. If a user is a Apple music member, they’ll be able to search for songs with the iTunes search API to queue up for playback and DJ their own radio station. On top of that, they’ll receive listener feedback through upvotes and downvotes in real time. A user will also be able to be a listener by connecting to a radio station and upvote or downvote that station.

     

    Getting Started with the PubNub Swift SDK

    We’ll start off by including the SDK into our project. The easiest way to do this is with CocoaPods, which is a dependency manager for Swift projects. If you’ve never used CocoaPods before, check out how to install CocoaPods and use it!

    After you have installed cocoapods, go into your project’s root directory and type:

    $ (your favorite editor) Podfile

    Paste the following code into your Podfile:

    source 'https://github.com/CocoaPods/Specs.git'

    use_frameworks!

    platform :ios, '9.0'

    target 'YourProjectName' do

    pod 'PubNub'

    end

    Finish it off by typing:

    $ pod install

    Now open the file YourProjectName.xcworkspace

     

    App Setup in Account

    You will need a PubNub account in order to get your own unique keys and to turn on some PubNub features we will need for this project. If you don’t already have an account, go ahead and sign up for a free account to get your own publish and subscribe keys. Head over to the PubNub Developer Dashboard and create a new app, enable the Storage and Playback feature and the Presence feature.

    Connect to PubNub from the AppDelegate

    In order to use the PubNub features, we must create a PubNub instance property.

    Open your AppDelegate.swift file and add this before the class declaration:

    import PubNub

    We will then create the PubNub instance, replace the “demo” keys with your own respective publish and subscribe keys in the parameters of the PNConfiguration.

    class AppDelegate: UIResponder, UIApplicationDelegate, PNObjectEventListener {
        var window: UIWindow?
        lazy var client: PubNub = {
            let config = PNConfiguration(publishKey: "demo", subscribeKey: "demo")
            let pub = PubNub.clientWithConfiguration(config)
            return pub
        }()

     

    Request App Access to Apple Music

    Before anything, we need to request access to the user’s Apple Music library and then check if their device is capable of Apple Music playback when they first open the app.

    This is achieved using the SKCloudServiceController inside of the AppDelegate.swift file within the applicationDidBecomeActive function:

    func applicationDidBecomeActive(application: UIApplication) {
            // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
            //Ask user for for Apple Music access
            SKCloudServiceController.requestAuthorization { (status) in
                if status == .Authorized {
                    let controller = SKCloudServiceController()
                    //Check if user is a Apple Music member
                    controller.requestCapabilitiesWithCompletionHandler({ (capabilities, error) in
                        if error != nil {
                            dispatch_async(dispatch_get_main_queue(), {
                                self.showAlert("Capabilites error", error: "You must be an Apple Music member to use this application")
                            })
                        }
                    })
                } else {
                    dispatch_async(dispatch_get_main_queue(), {
                        self.showAlert("Denied", error: "User has denied access to Apple Music library")
                    })
                }
            }
        }

     

    Search iTunes and display results

    Once it’s confirmed that the user is a Apple Music member, the searchItunes() function will use the iTunes Search API to make a GET request for whatever input the user provided from the searchBarSearchButtonClicked() function:

    //Search iTunes and display results in table view
    func searchItunes(searchTerm: String) {
        Alamofire.request(.GET, "https://itunes.apple.com/search?term=\(searchTerm)&entity=song")
        .validate()
        .responseJSON { response in
            switch response.result {
            case .Success:
                if let responseData = response.result.value as? NSDictionary {
                    if let songResults = responseData.valueForKey("results") as? [NSDictionary] {
                        self.tableData = songResults
                        self.tableView!.reloadData()
                    }
                }
             case .Failure(let error):
                 self.showAlert("Error", error: error.description)
             }
          }
    }
    func searchBarSearchButtonClicked(searchBar: UISearchBar) {
        //Search iTunes with user input
        if searchBar.text != nil {
            let search = searchBar.text!.stringByReplacingOccurrencesOfString(" ", withString: "+")
            searchItunes(search)
            searchBar.resignFirstResponder()
        }
    }

     

    Once we get this data, it will be displayed in a table view allowing the user to pick a track and add it to their playback queue:

    //Display iTunes search results
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: nil)
        if let rowData: NSDictionary = self.tableData[indexPath.row] as? NSDictionary,
           urlString = rowData["artworkUrl60"] as? String,
           imgURL = NSURL(string: urlString),
           imgData = NSData(contentsOfURL: imgURL) {
           cell.imageView?.image = UIImage(data: imgData)
           cell.textLabel?.text = rowData["trackName"] as? String
           cell.detailTextLabel?.text = rowData["artistName"] as? String
        }
        return cell
    }
    //Add song to playback queue if user selects a cell
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let indexPath = tableView.indexPathForSelectedRow
        if let rowData: NSDictionary = self.tableData[indexPath!.row] as? NSDictionary, urlString = rowData["artworkUrl60"] as? String,
            imgURL = NSURL(string: urlString),
            imgData = NSData(contentsOfURL: imgURL) {
            queue.append(SongData(artWork: UIImage(data: imgData), trackName: rowData["trackName"] as? String, artistName: rowData["artistName"] as? String, trackId: String (rowData["trackId"]!)))
            //Show alert telling the user the song was added to the playback queue
            let addedTrackAlert = UIAlertController(title: nil, message: "Added track!", preferredStyle: .Alert)
            self.presentViewController(addedTrackAlert, animated: true, completion: nil)
            let delay = 0.5 * Double(NSEC_PER_SEC)
            let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
            dispatch_after(time, dispatch_get_main_queue(), {
                addedTrackAlert.dismissViewControllerAnimated(true, completion: nil)
            })
            tableView.deselectRowAtIndexPath(indexPath!, animated: true)
        }
    }

     

     

    Searching with the iTunes API and displaying the song data in a table view

     

    Creating a Radio Station

    After the user finishes adding tracks to their playback queue, they can create a radio station. A dialogue is presented for the user to name their radio station. This name is also used for their channel. A PubNub channel cannot contain a comma, colon, asterisk, slash or backslash, the createValidPNChannel() makes sure of this by deleting any of those characters in the name. It then gets the current timestamp to concatenate to the name so the channel will have a unique name.

    //Create station name and segue to radio station if playback queue isn't empty
    @IBAction func takeInputAndSegue(sender: AnyObject) {
        let alert = UIAlertController(title: "Name your radio station!", message: nil, preferredStyle: .Alert)
        alert.addTextFieldWithConfigurationHandler(nil)
        alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in
        if !self.queue.isEmpty {
            let radioStationName = alert.textFields![0] as UITextField
        if !radioStationName.text!.isEmpty && radioStationName.text?.characters.count <= 60 {
            let stationName = radioStationName.text!
            //Adds a timestamp to the station name to make it a unique channel name
            let channelName = self.createValidPNChannel(stationName)
            //Publish station to a list of all stations created
            self.appDelegate.client.publish(["stationName" : stationName, "channelName" : channelName], toChannel: "All_Stations", withCompletion: { (status) in
                if status.error {
                    self.showAlert("Error", error: "Network error")
                }
                self.appDelegate.client.subscribeToChannels([channelName], withPresence: true)
                dispatch_async(dispatch_get_main_queue(), {
                    //Segue to the radio station
                    let musicPlayerVC = self.storyboard?.instantiateViewControllerWithIdentifier("MusicPlayerViewController") as! MusicPlayerViewController
                    musicPlayerVC.queue = self.queue
                    musicPlayerVC.channelName = channelName
                    self.navigationController?.pushViewController(musicPlayerVC, animated: true)
                })
              })
          } else {
              dispatch_async(dispatch_get_main_queue(), {
                  self.showAlert("Try again", error: "Radio station name can't be empty or more than 60 characters")
              })
          }
       } else {
          dispatch_async(dispatch_get_main_queue(), {
              self.showAlert("Try again", error: "Playlist cannot be empty")
          })
       }
      }))
     self.presentViewController(alert, animated: true, completion: nil)
    }
    //Create unique PubNub channel by concatenating the current timestamp to the name of the radio station
    func createValidPNChannel(channelName: String) -> String {
        let regex = try? NSRegularExpression(pattern: "[\\W]", options: .CaseInsensitive)
        var validChannelName = regex!.stringByReplacingMatchesInString(channelName, options: [], range: NSRange(0..<channelName.characters.count), withTemplate: "")
        validChannelName += "\(NSDate().timeIntervalSince1970)"
        validChannelName = validChannelName.stringByReplacingOccurrencesOfString(".", withString: "")
        return validChannelName
    }
    

     

    Selecting a song and adding it to a radio station

     

    DJ the Radio Station

    Now that the user has their own radio station, the tracks they added to their queue will begin to play. With PubNub’s Presence feature, we can detect when a user has joined our channel. Once they do, we will send out the track data and current playback time so they can listen at the same playback position on their device.

    //Listen if a user joins and and publish the trackId, currentPlaybackTime, trackName and artistName to the current channel
    func client(client: PubNub, didReceivePresenceEvent event: PNPresenceEventResult) {
        var playbackTime: Double!
        if controller.currentPlaybackTime.isNaN || controller.currentPlaybackTime.isInfinite {
            playbackTime = 0.0
        } else {
            playbackTime = controller.currentPlaybackTime
        }
        if event.data.presenceEvent == "join" {
            appDelegate.client.publish(["trackId" : trackIds[controller.indexOfNowPlayingItem], "currentPlaybackTime" : playbackTime, "trackName" : queue[controller.indexOfNowPlayingItem].trackName!, "artistName" : queue[controller.indexOfNowPlayingItem].artistName!], toChannel:  channelName, withCompletion: { (status) in
                if status.error {
                    self.showAlert("Error", error: "Network error")
                }
            })
        }
    }
    

     

    If the DJ skips forwards or backwards, we’ll publish a message on the channel with the track data and current playback time again.

    //Skip to the next track and publish the trackId, currentPlaybackTime, trackName and artistName to the current channel
        @IBAction func skipForwards(sender: AnyObject) {
            controller.skipToNextItem()
            if controller.indexOfNowPlayingItem < queue.count {
            trackName.text = queue[controller.indexOfNowPlayingItem].trackName
            artistName.text = queue[controller.indexOfNowPlayingItem].artistName
            appDelegate.client.publish(["trackId" : trackIds[controller.indexOfNowPlayingItem], "currentPlaybackTime" : controller.currentPlaybackTime, "trackName" : queue[controller.indexOfNowPlayingItem].trackName!, "artistName" : queue[controller.indexOfNowPlayingItem].artistName!], toChannel: channelName, withCompletion: { (status) in
                if !status.error {
                    self.controller.play()
                    dispatch_async(dispatch_get_main_queue(), {
                        self.thumbsUpCount.text = "0"
                        self.thumbsDownCount.text = "0"
                    })
                } else {
                    self.showAlert("Error", error: "Network error")
                }
            })
            }
        }
        
        //Skip to the previous track and publish the trackId, currentPlaybackTime, trackName and artistName to the current channel
        @IBAction func skipBackwards(sender: AnyObject) {
            controller.skipToPreviousItem()
            if controller.indexOfNowPlayingItem < queue.count {
            trackName.text = queue[controller.indexOfNowPlayingItem].trackName
            artistName.text = queue[controller.indexOfNowPlayingItem].artistName
            appDelegate.client.publish(["trackId" : trackIds[controller.indexOfNowPlayingItem], "currentPlaybackTime" : controller.currentPlaybackTime, "trackName" : queue[controller.indexOfNowPlayingItem].trackName!, "artistName" : queue[controller.indexOfNowPlayingItem].artistName!], toChannel: channelName, withCompletion: { (status) in
                if !status.error {
                    self.controller.play()
                    dispatch_async(dispatch_get_main_queue(), {
                        self.thumbsUpCount.text = "0"
                        self.thumbsDownCount.text = "0"
                    })
                } else {
                    self.showAlert("Error", error: "Network error")
                }
            })
            }
        }

     

    The DJ is also listening for up and down vote messages on this channel to know if the listeners like what is playing.

    //Update thumbs up and thumbs down counts
        func client(client: PubNub, didReceiveMessage message: PNMessageResult) {
            if "thumbsUp" == message.data.message!["action"] as? String {
                let count = Int(thumbsUpCount.text!)
                thumbsUpCount.text = String(count! + 1)
            } else if "thumbsDown" == message.data.message!["action"] as? String {
                let count = Int(thumbsDownCount.text!)
                thumbsDownCount.text = String(count! + 1)
            }
        }

    Broadcasting the radio station to all users listening in real time

     

    Listen to a Radio Station

    To listen to a radio station, a user chooses from a table view of radio stations and they automatically subscribe to the channel from the cell they selected. In order to get these radio stations, we use PubNub’s storage and playback feature to retrieve all of the radio stations that have been created.

    override func viewDidAppear(animated: Bool) {
            stationNames.removeAll()
            channelNames.removeAll()
            //Go through the history of the channel holding all stations created
            //Update table view with history list
            appDelegate.client.historyForChannel("All_Stations") { (result, status) in
                for message in (result?.data.messages)! {
                    if let stationName = message["stationName"] as? String, channelName = message["channelName"] as? String{
                        self.stationNames.append(stationName)
                        self.channelNames.append(channelName)
                    }
                }
                dispatch_async(dispatch_get_main_queue(), {
                    self.tableView.reloadData()
                })
            }
        }

     

    Subscribing to a radio station in real time
    Once a user selects a cell, the channel name and station name is passed to the next view.

    //Segue to that radio station and pass the channel name and station name
        func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
            let cell = tableView.cellForRowAtIndexPath(indexPath)
            let stationVC = self.storyboard?.instantiateViewControllerWithIdentifier("StationViewController") as! StationViewController
            stationVC.channelName = (cell?.detailTextLabel?.text)!
            stationVC.stationName = (cell?.textLabel?.text)!
            self.navigationController?.pushViewController(stationVC, animated: true)
        }

    Now the user can subscribe to that channel with just one line of code.

    override func viewDidAppear(animated: Bool) {
            appDelegate.client.addListener(self)
            appDelegate.client.subscribeToChannels([channelName], withPresence: true)
            self.title = "Radio station - \(stationName)"
        }

     

    The user is also listening for messages on this channel with the addListener() function in viewDidAppear() above . As explained before, the DJ will send a message to whoever joins the channel. The user listening to the radio station will then listen for the incoming song data.

    //Recieve song data to play
        func client(client: PubNub, didReceiveMessage message: PNMessageResult) {
            if let trackId = message.data.message!["trackId"] as? String, currentPlaybackTime = message.data.message!["currentPlaybackTime"] as? Double, trackName = message.data.message!["trackName"] as? String, artistName = message.data.message!["artistName"] as? String {
                controller.setQueueWithStoreIDs([trackId])
                controller.play()
                controller.currentPlaybackTime = currentPlaybackTime
                self.trackName.text = trackName
                self.artistName.text = artistName
                thumbsDownButton.backgroundColor = UIColor.clearColor()
                thumbsUpButton.backgroundColor = UIColor.clearColor()
            }
        }

     

    They can also send their upvotes and downvotes by simply publishing to the channel.

    //Publish a upvote to the subscribed channel
        @IBAction func thumbsUp(sender: AnyObject) {
            appDelegate.client.publish(["action" : "thumbsUp"], toChannel: channelName) { (status) in
                if !status.error {
                    self.thumbsDownButton.backgroundColor = UIColor.clearColor()
                    self.thumbsUpButton.backgroundColor = UIColor(red: 44/255, green: 62/255, blue: 80/255, alpha: 1.0)
                } else {
                    self.showAlert("Error", error: "Network error")
                }
            }
        }
        
        //Publish a downvote to the subscribed channel
        @IBAction func thumbsDown(sender: AnyObject) {
            appDelegate.client.publish(["action" : "thumbsDown"], toChannel: channelName) { (status) in
                if !status.error {
                    self.thumbsUpButton.backgroundColor = UIColor.clearColor()
                    self.thumbsDownButton.backgroundColor = UIColor(red: 44/255, green: 62/255, blue: 80/255, alpha: 1.0)
                } else {
                    self.showAlert("Error", error: "Network error")
                }
            }
        }

     

    Listening to a radio station in real time

     

    Making It Even Better

    That’s all there is to it! You are now able to become the DJ you’ve always wanted to be or see what other people are listening to and give your opinion on it. All in real time! You can easily expand on this by adding some extra features. You could restrict the range of your broadcasting with BLE (bluetooth low energy), provide stats on how well you’re doing as a DJ in relevance to the amount of upvotes and downvotes you get or even add a chat to it so listeners could talk to whoever is the DJ.

    Try PubNub today!

    Build realtime applications that perform reliably and securely, at global scale.
    Try Our APIs
    Try PubNub today!
    More From PubNub