Monday, 19 October 2020

Displaying and Playing MPMediaItem songs in retrieved + last played order

I'm building an app that makes use of MPMusicPlayerApplicationController to play music from a user's local music library.

The songs from the library are retrieved like so:

func retrieveMusic() {
    mediaQueryQueue.async {
        let songsQuery = MPMediaQuery.songs()
        let songsCollections = songsQuery.collections
        for songCollection in songsCollections ?? [] {
            let songs = songCollection.items
            self.retrievedSongs.append(contentsOf: songs)
        }

        DispatchQueue.main.async {
            self.setLoadedSongsUIState()
            self.setupTableView()
            self.sortOrderDidChange()
        }
    }
}

These songs are then displayed in a table view:

func setTableViewItems(_ songs: [MPMediaItem]) {
    let section = self.tableViewAdaptor!.sections[0] as! TableViewAdaptorSection<SongTableViewCell, MPMediaItem>
    section.items = songs
    tableViewAdaptor.tableView.reloadData()
}

I also have a segment control that will determine whether to display the songs in the order in which they were retrieved, or in the last played order; and then update the table view based on which choice was selected:

var retrievedSongs: [MPMediaItem] = []
var sortedSongs: [MPMediaItem] = []

private func sortOrderDidChange() {
        switch sortOrder {
        case .playDateDescending:
            let unixDate = Date(timeIntervalSince1970: 1000)
            sortedSongs = retrievedSongs.sorted {
                $0.lastPlayedDate ?? unixDate > $1.lastPlayedDate ?? unixDate
            }
        default:
            sortedSongs = retrievedSongs
        }
        
        setTableViewItems(sortedSongs)
        
        let queueDescriptor = MPMusicPlayerMediaItemQueueDescriptor(itemCollection: MPMediaItemCollection(items: sortedSongs))
        queueDescriptor.startItem = musicPlayer.nowPlayingItem
        musicPlayer.setQueue(with: queueDescriptor)
        
        selectPlayingItem(scrollToVisible: true)
    }

The user is able to use buttons to play/pause, and go to the next or previous tracks:

@IBAction func forwardButtonPressed(_ sender: Any) {
    musicPlayer.skipToNextItem()
    selectPlayingItem(scrollToVisible: true)
}

@IBAction func backButtonPressed(_ sender: Any) {
    musicPlayer.skipToPreviousItem()
    selectPlayingItem(scrollToVisible: true)
}

Additionally, if you select a track in the table view, that track will be played:

    tableViewAdaptor = TableViewAdaptor(
        tableView: songTableView,
        sections: [songsLibrarySection],
        didChangeHandler: {
            let selectedSongIndex = self.songTableView.indexPathForSelectedRow
            if selectedSongIndex != nil {
                let selectedSong = self.retrievedSongs[selectedSongIndex!.row]
                self.musicPlayer.nowPlayingItem = selectedSong
                self.musicPlayer.play()
                self.updatePlaybackUI(scrollNowPlayingToVisible: true)
            }
        })
    setTableViewItems(retrievedSongs)

When the songs are displayed in the order in which they were retrieved, everything works perfectly. If I click back or forwards, or select a song to play; the correct song will play in the next order.

Right now if you're in the "order retrieved" tab, and you go to the "ordered by date" tab and click backwards or forward, the next song will not be whatever is next that's displayed in the table view, but in the "retrieved" by order.

What I'm trying to do is change the code so that no matter whether the retrieved or ordered songs are displayed, the next or previous song will correspond to what's displayed.



from Displaying and Playing MPMediaItem songs in retrieved + last played order

No comments:

Post a Comment