Search code examples
iosswiftnsnotificationcenteravplayeritem

Somehow generating copy of AVPlayerItem instead accessing reference


I have a simple audio playlist using an AVQueuePlayer object. The queue is populated by iterating through a UITableView which contains custom UITableViewCell objects, PlayableAudioCell. These objects have an AVPlayerItem property, audioTrack, that I insert into the AVQueueplayer's queue.

I want to add and remove AVPlayerItemDidPlayToEndTime observers to these tracks as the items are played and finished, so that I can update the labels of a music player I created.

For some reason, if the queue is longer than 2 tracks, the third track in the queue doesn't get the observer added to it, as the function that applies it is passed a copy of the track, and not the track that was saved to the PlayableAudioCell object.

I can't understand how this is happening, but I must be creating a copy of the track somewhere instead of referencing the audio track of the cell. I can tell that I am, from the print statements of the memory location of the audio track.

Just some explanation on the layout of my app... it contains one view that is a tableViewController, allowing a user to select a cell and play a track. I also have a SplitViewController that allows a FooterView to pop up and displaying the playing track information (labels, duration, track slider).

Order of operation:

  1. A user presses play on a cell in the TableView

  2. AudioQueuePlayer's Queue is populated by the track pressed and every track below it

  3. TableViewController's delegate (FooterViewController) calls it's delegated play method

  4. AVPlayerItemDidPlayToEndTime observer is added to first item on play

  5. When track ends, selector is called from the observer. Within this selector, observer for current track is removed, and code to add AVPlayerItemDidPlayToEndTime observer to next track is added.

Does any of this code pop out as me accessing a copy of a cell or a copy of an audiotrack as opposed to referencing the audiotrack property of currentlySelectedAudioCell?

I am going to omit my code in the TableViewController... I can tell that it is being populated correctly within the table and the issue must lie within the code of the FooterViewController:

func play(cell: PlayableAudioCell){

    if let previousCell = currentlySelectedAudioCell, let prevFilename = currentlySelectedAudioCell?.chapter?.value(forKey: "Filename") as? String, let filename = cell.chapter?.value(forKey: "Filename") as? String{

        if filename != prevFilename{
            runningTime = 0
        }
    }

    updateWithObserver(cell: cell)

    //show footerview if it is hidden.
    if(footerView.superview?.isHidden)!{
        hideShowFooterView()
    }

    if(self.isPlaying)!{
        //print("Should pause cell")
        audioQueuePlayer?.pause()
        switchState(state: "Pause")

        playPauseRunTime(state: "Pause")

    }else{
        //print("Should play cell")
        audioQueuePlayer?.play()
        switchState(state: "Play")

        playPauseRunTime(state: "Play")
    }

    updateFooterView()
}

func updateWithObserver(cell: PlayableAudioCell){

    self.currentlySelectedAudioCell = cell
    delegate?.currentlySelectedAudioCell = cell

    let track = cell.audioTrack!

    NotificationCenter.default.addObserver(self, selector: #selector(FooterViewController.playerItemDidReachEnd(notification:)), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: track)

}

func playerItemDidReachEnd(notification: Notification){
    NotificationCenter.default.removeObserver(self, name: notification.name, object: audioQueuePlayer?.currentItem)

    if didAudioPlayEntirely(){
        recordAudioHistoryToFirebase(didFinish: true)
        if let currentlySelectedAudioCell = delegate?.currentlySelectedAudioCell{
            revealCheckMark(cell: currentlySelectedAudioCell)
        }
    }

    runningTime = 0

    //If there is just one more item in the queue, regardless of whether playAll() has been toggled...
    if (audioQueuePlayer?.items().count)! <= 1 {

        playPauseRunTime(state: "Pause")

        audioQueuePlayer?.removeAllItems()
        resetFooterViewUI()
        switchState(state: "Pause")
        hideShowFooterView()

        audioQueuePlayer?.rate = 0.0

        delegate?.currentlySelectedAudioCell = nil
        currentlySelectedAudioCell = nil
        delegate?.indexPathOfSelectedCell = nil
        indexPathOfSelectedCell = nil

    }else{

        if let indexPathOfSelectedCell = indexPathOfSelectedCell{

            playPauseRunTime(state: "Pause")
            playPauseRunTime(state: "Play")

            let row = indexPathOfSelectedCell.row + 1
            let newIndexPath = IndexPath(row: row, section: 0)
            self.indexPathOfSelectedCell = newIndexPath
            delegate?.indexPathOfSelectedCell = newIndexPath

            let newCell = self.tableView!.cellForRow(at: newIndexPath) as? PlayableAudioCell
            updateWithObserver(cell: newCell!)

            updateFooterView()
            self.tableView!.reloadData()

        }
    }
}

Solution

  • Apparently AVQueuePlayer removes an AVPlayerItem each time it finishes playing an item within its queue. My logic was trying to match the index of the queue with the array of tableview cells, after a few iterations through, the indices would be off and the observer wouldn't get added to the right playeritem. Fixed by replacing the AVQueuePlayer with an AVPlayer and an array of AVPlayerItems.