You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS 17使用AVMutableMovie/AVPlayer直播流出现黑屏问题求助

iOS 17 下 AVMutableMovie 分片直播黑屏问题解决方案

问题概述

在iOS 17系统中,通过WSS抓取5秒分片视频数据,使用AVMutableMovie初始化AVPlayerItem,并通过replaceCurrentItem(with:)切换分片实现直播播放时,出现持续黑屏现象,该功能在iOS 16上运行正常。尝试调整播放逻辑后虽能播放,但切换分片时会出现画面闪烁。

可能原因

iOS 17对AVMutableMovie的内存数据初始化逻辑做了调整,直接通过Data初始化的AVMutableMovie可能未正确解析或加载媒体轨道,导致AVPlayerItem无法渲染画面。此外,频繁调用replaceCurrentItem(with:)会触发播放器的资源重置,引发闪烁。

解决方案

1. 验证AVMutableMovie轨道有效性

在创建AVPlayerItem前,先检查AVMutableMovie是否包含有效轨道,避免加载无效资源:

private func playNext() {
    let nextSet = self.dataCollector.getNextItem(length: self.configuration.frameDelay)
    guard nextSet.count == self.configuration.frameDelay else { return }
    
    var playerTime = self.player.currentItem != nil ? Int(CMTimeGetSeconds(player.currentTime())) : 0
    var allData = Data()
    allData.appendAll(dataSet: dataCollector.getFileType())
    
    nextSet.forEach { data in
        playerTime += 1
        allData.append(data.getFragmentData())
        self.currentFragmentTimes.updateValue(data.getFragmentTime(), forKey: playerTime)
    }
    
    guard allData.count > 0 else { return }
    
    // 初始化AVMutableMovie并检查轨道
    guard let movie = AVMutableMovie(data: allData, options: nil),
          movie.tracks.count > 0 else {
        // 处理无效媒体数据情况
        return
    }
    
    let playerItem = AVPlayerItem(asset: movie)
    // 监听item状态,确保准备就绪后播放
    let statusObserver = playerItem.observe(\.status, options: [.new]) { [weak self] item, change in
        guard let self = self else { return }
        if item.status == .readyToPlay {
            self.player.replaceCurrentItem(with: item)
            self.player.play()
        } else if item.status == .failed {
            // 处理加载失败
            print("Player item failed: \(item.error?.localizedDescription ?? "Unknown error")")
        }
    }
    // 保存观察者引用避免被释放
    self.itemObservers.append(statusObserver)
    self.playerInitializedTime = nil
}

2. 改用AVURLAsset加载临时文件

iOS 17对内存媒体数据的兼容性下降,建议将分片数据写入临时文件,通过AVURLAsset加载,提升兼容性:

private func playNext() {
    let nextSet = self.dataCollector.getNextItem(length: self.configuration.frameDelay)
    guard nextSet.count == self.configuration.frameDelay else { return }
    
    var playerTime = self.player.currentItem != nil ? Int(CMTimeGetSeconds(player.currentTime())) : 0
    var allData = Data()
    allData.appendAll(dataSet: dataCollector.getFileType())
    
    nextSet.forEach { data in
        playerTime += 1
        allData.append(data.getFragmentData())
        self.currentFragmentTimes.updateValue(data.getFragmentTime(), forKey: playerTime)
    }
    
    guard allData.count > 0 else { return }
    
    // 创建临时文件URL
    let tempDir = NSTemporaryDirectory()
    let tempFileName = UUID().uuidString + ".mp4" // 根据实际媒体格式调整后缀
    let tempFileURL = URL(fileURLWithPath: tempDir).appendingPathComponent(tempFileName)
    
    do {
        try allData.write(to: tempFileURL)
        let asset = AVURLAsset(url: tempFileURL)
        let playerItem = AVPlayerItem(asset: asset)
        
        // 监听状态并播放
        let statusObserver = playerItem.observe(\.status, options: [.new]) { [weak self] item, change in
            guard let self = self else { return }
            if item.status == .readyToPlay {
                self.player.replaceCurrentItem(with: item)
                self.player.play()
                // 播放完成后删除临时文件
                DispatchQueue.global().async {
                    try? FileManager.default.removeItem(at: tempFileURL)
                }
            }
        }
        self.itemObservers.append(statusObserver)
        self.playerInitializedTime = nil
    } catch {
        print("Failed to write temp file: \(error)")
    }
}

3. 使用AVQueuePlayer实现无缝切换

频繁调用replaceCurrentItem(with:)是导致闪烁的主要原因,改用AVQueuePlayer预加载下一个分片,实现无缝播放:

// 初始化时替换为AVQueuePlayer
private let player = AVQueuePlayer()

private func playNext() {
    let nextSet = self.dataCollector.getNextItem(length: self.configuration.frameDelay)
    guard nextSet.count == self.configuration.frameDelay else { return }
    
    var playerTime = self.player.currentItem != nil ? Int(CMTimeGetSeconds(player.currentTime())) : 0
    var allData = Data()
    allData.appendAll(dataSet: dataCollector.getFileType())
    
    nextSet.forEach { data in
        playerTime += 1
        allData.append(data.getFragmentData())
        self.currentFragmentTimes.updateValue(data.getFragmentTime(), forKey: playerTime)
    }
    
    guard allData.count > 0 else { return }
    
    // 写入临时文件并创建AVPlayerItem
    let tempDir = NSTemporaryDirectory()
    let tempFileName = UUID().uuidString + ".mp4"
    let tempFileURL = URL(fileURLWithPath: tempDir).appendingPathComponent(tempFileName)
    
    do {
        try allData.write(to: tempFileURL)
        let asset = AVURLAsset(url: tempFileURL)
        let playerItem = AVPlayerItem(asset: asset)
        
        // 监听当前播放项结束事件,自动添加下一个项
        let endObserver = playerItem.observe(\.status, options: [.new]) { [weak self] item, change in
            guard let self = self else { return }
            if item.status == .readyToPlay {
                // 如果队列中没有正在播放的项,直接插入并播放
                if self.player.items().isEmpty {
                    self.player.insert(item, after: nil)
                    self.player.play()
                } else {
                    // 否则添加到队列末尾
                    self.player.insert(item, after: self.player.currentItem)
                }
                // 播放完成后删除临时文件
                NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: item, queue: .main) { _ in
                    DispatchQueue.global().async {
                        try? FileManager.default.removeItem(at: tempFileURL)
                    }
                }
            }
        }
        self.itemObservers.append(endObserver)
        self.playerInitializedTime = nil
    } catch {
        print("Failed to write temp file: \(error)")
    }
}

注意事项

  • 确保分片媒体数据格式一致,避免因格式切换导致播放异常。
  • 及时清理临时文件和观察者引用,避免内存泄漏。
  • 针对iOS 17,优先使用AVURLAsset而非内存数据初始化的AVMutableMovie,提升兼容性。

内容的提问来源于stack exchange,提问作者Rajeev Ghantasala

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 15:03:18