一个AVPlayerItem不能与多个AVPlayer实例相关联。

4
我知道这个问题在Stack Overflow上已经有很多。但大多数答案都过时并且和我接下来要问的问题没有关联。
我有一个包含AVPlayerItems的数组和一个AVQueuePlayer。一开始我设置AVQueuePlayer播放数组中的项目。
func mediaPicker(mediaPicker: MPMediaPickerController!, didPickMediaItems mediaItemCollection: MPMediaItemCollection!) {

    mediaItems = mediaItemCollection.items

    for thisItem in mediaItems as [MPMediaItem] {

        var itemUrl = thisItem.valueForProperty(MPMediaItemPropertyAssetURL) as? NSURL
        var playerItem = AVPlayerItem(URL: itemUrl)
        mediaArray.append(playerItem)           

    }

    updateMetadata()

    audioPlayer = AVQueuePlayer(items: mediaArray)


    self.dismissViewControllerAnimated(true, completion: nil)

}

然而,当用户添加新项目或删除一个项目时,我尝试更新播放器(与一开始设置的方式相同),但是却出现了错误。我想知道是否有任何解决方法或解决方案。以下是用户删除歌曲的方式:

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == UITableViewCellEditingStyle.Delete{
            mediaArray.removeAtIndex(indexPath.row)
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
            audioPlayer = AVQueuePlayer(items: mediaArray) //here gives the error
        }
    }

我为此感到苦恼,如果有人能帮我,我将不胜感激。

你需要展示你的代码。 - rdelmar
这不是足够的代码。你需要展示整个方法,而不仅仅是单独的一行。同时也要展示当用户添加或删除一个项目时你会做什么。 - rdelmar
6个回答

5
问题在于,当您使用此行(在tableView:commitEditingStyle:forRowAtIndexPath:中)更新项目数组时,会创建一个新的播放器对象。
audioPlayer = AVQueuePlayer(items: mediaArray)

当您这样做时,玩家物品似乎仍与原始玩家相关联。您应该能够删除那行代码,以使其正常工作。从mediaArray中插入或删除项目就足以实现您想要的功能;您不需要创建新的玩家。


1
但是可悲的是,似乎当您首次将数组与播放器关联时,无论数组发生什么变化,播放器仍将播放第一个给定的数组。有趣的是,如果再次调用mediaPicker并选择相同的音乐,则可以正常播放。我想知道为什么苹果这样做,这似乎并不必要。无论如何,我认为我必须改变我的方法,感谢您的帮助。 - Gabriel Bitencourt

3
我解决这个问题的方法是将AVAsset对象的引用保存在数组中,然后将其映射到[AVPlayerItem]AVQUeuePlayer init接受的类型)。
具体来说,我们需要制作一个可以暂停的音频录制器(github上的代码),因此我使用了AVQueuePlayer,但遇到了与OP相同的问题,唯一干净的解决方案是使用新的数组重新创建此播放器对象。
相关的Swift 4.1代码:
class RecordViewController: UIViewController {

    var audioRecorder: AVAudioRecorder?

    var audioChunks = [AVURLAsset]()
    var queuePlayer:  AVQueuePlayer?

// irrelevant code omitted

    func stopRecorder() {
        self.audioRecorder?.stop()
        let assetURL  = self.audioRecorder!.url

        self.audioRecorder = nil

        let asset = AVURLAsset(url: assetURL)
        self.audioChunks.append(asset)

        let assetKeys = ["playable"]
        let playerItems = self.audioChunks.map {
            AVPlayerItem(asset: $0, automaticallyLoadedAssetKeys: assetKeys)
        }

        self.queuePlayer = AVQueuePlayer(items: playerItems)
        self.queuePlayer?.actionAtItemEnd = .advance
    }

    func startPlayer() {
        self.queuePlayer?.play()
    }

    func stopPlayer() {
        self.queuePlayer?.pause()
        self.queuePlayer = nil
    }
}

最后,使用AVAssetExportSession将音频块拼接在一起,因此存储AVAsset而不是AVPlayerItem在最终结果中更加有利。


2

来自苹果文档

在添加项目之前:

canInsertItem(_:afterItem:)

Returns a Boolean value that indicates whether a given player item can be inserted into the player’s queue.

func canInsertItem(_ item: AVPlayerItem!, afterItem afterItem: AVPlayerItem!) -> Bool

添加一个项目:

insertItem(_:afterItem:)

Places given player item after a specified item in the queue.

 func insertItem(_ item: AVPlayerItem!, afterItem afterItem: AVPlayerItem!)

移除一个项目:

removeItem(_:)

从队列中移除给定的播放器项目。

func removeItem(_ item: AVPlayerItem!)


是的,我知道,但是如果我尝试添加一个已经播放过的项目,它会给出错误提示,例如。 - Gabriel Bitencourt
在插入之前,您需要检查是否可以插入,我将其添加到答案中,希望能对您有所帮助。 - Icaro
我也已经知道了,这是一个无法解决的问题,多亏了苹果。但还是谢谢。 - Gabriel Bitencourt
请添加更多的代码,如果我们能看到更多你正在做的事情,我相信我们一定能够帮助你。 - Icaro

2
使用以下代码解决了我的问题:
player?.pause()                     
let playerItem = AVPlayerItem(asset: myAsset) 
player = nil 
player = AVPlayer()
player?.replaceCurrentItem(with: playerItem)

改为:

player?.pause()                     
player = nil 
player = AVPlayer()
player?.replaceCurrentItem(with: availablePlayerItem) 
//availablePlayerItem is a playerItem which has been created before and played once with this AVPlayer
注意: 在这两段代码中,“player”是我类中的全局变量,其定义如下:

var player : AVPlayer? = nil


1

我最初在单元格内初始化了一个AVPlayerItem,并将其添加到AVPlayer(也在单元格内)。后来,我呈现了一个新的vc,并将同一单元格的AVPlayerItem传递给了新vc中的不同AVPlayer,导致崩溃。为了解决这个问题,我只需使用.copy() as? AVPlayerItem复制playerItem即可。复制会使新副本具有与原始副本不同的内存地址。

单元格:

var cellPlayer: AVPlayer?
var cellsPlayerItem: AVPlayerItem?

cellsPlayerItem = AVPlayerItem(asset: AVAsset(url: url)) // cellsPlayerItem's memory address 0x1111111
cellPlayer = AVPlayer(playerItem: cellsPlayerItem!)

呈现的vc:
var vcPlayer: AVPlayer?

let playerItemCopy = cellsPlayerItem.copy() as? AVPlayerItem // playerItemCopy's memory address 0x2222222
let vcPlayer = AVPlayer(playerItem: playerItemCopy)

1
需要检查玩家项目是否可以插入到玩家队列中。然后根据以下条件添加、删除或替换玩家项目:
if player.canInsert(playerItem, after: nil) == true {

            player = AVQueuePlayer(playerItem: playerItem)

        } else {

            player.remove(self.playerItem)

            player.replaceCurrentItem(with: playerItem)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接