阿杰,你问的这个问题是关于 iOS 中 AVPlayer 播放在线视频和本地文件的区别,我给你详细梳理一下。


1. AVPlayer 播放本地文件

特点:

  • 文件已经存储在设备本地,无需网络,加载快,播放稳定。
  • 支持 file:// URL 或 AVAsset 创建对象。

示例代码:

import AVFoundation
import AVKit

// 本地文件路径
if let path = Bundle.main.path(forResource: "video", ofType: "mp4") {
    let url = URL(fileURLWithPath: path)
    let player = AVPlayer(url: url)
    let playerVC = AVPlayerViewController()
    playerVC.player = player
    self.present(playerVC, animated: true) {
        player.play()
    }
}

注意点:

  • 文件路径必须正确,常用 Bundle.main.pathFileManager 获取本地文件路径。
  • 无需考虑网络缓冲或网络中断。

2. AVPlayer 播放在线视频(网络流)

特点:

  • URL 是网络地址,如 https://.../video.mp4 或 HLS 流 https://.../index.m3u8
  • 需要网络请求和缓存,可能会有缓冲延迟。
  • AVPlayer 会自动支持 HLS 流和部分自适应码率(Adaptive Bitrate)。

示例代码:

import AVFoundation
import AVKit

if let url = URL(string: "https://www.example.com/video.mp4") {
    let player = AVPlayer(url: url)
    let playerVC = AVPlayerViewController()
    playerVC.player = player
    self.present(playerVC, animated: true) {
        player.play()
    }
}

注意点:

  • 网络视频需要考虑缓冲、超时和失败处理。
  • 可以使用 AVPlayerItempreferredForwardBufferDurationautomaticallyWaitsToMinimizeStalling 来控制缓冲行为。
  • 对 HLS 流,AVPlayer 会自动选择码率;对于普通 MP4,通常需要提前下载或依赖系统缓存。

3. 核心区别对比

特性本地文件网络视频
URL 类型file://http(s)://
加载速度快,无缓冲受网络影响,需要缓冲
错误处理文件不存在或路径错误网络超时、404、流媒体错误
码率适应固定HLS 自动码率适应,普通 MP4 不适用
缓存已经在本地系统会缓存部分播放数据,或者需自定义缓存
播放稳定性受网络稳定性影响

4. 小提示

  1. 本地文件适合 App 内打包或下载的视频,保证离线播放。
  2. 在线视频适合直播、云端资源或用户生成内容,但要处理网络波动、缓冲和错误回调。
  3. 混合方式:先下载到本地缓存,再用 AVPlayer 播放,可以兼顾离线播放和网络灵活性。

好的阿杰,我帮你写一个 Swift 封装类,支持 本地视频优先播放,如果本地没有则播放网络视频,并附带基础错误处理和缓冲设置。


import AVFoundation
import AVKit
import UIKit

class SmartAVPlayer: NSObject {

    private var player: AVPlayer?
    private var playerVC: AVPlayerViewController?
    private weak var parentVC: UIViewController?

    /// 初始化播放器
    /// - Parameters:
    ///   - localPath: 本地视频文件路径(可选)
    ///   - networkURL: 网络视频 URL
    ///   - parentVC: 播放器展示的父视图控制器
    init(localPath: String?, networkURL: String, parentVC: UIViewController) {
        super.init()
        self.parentVC = parentVC
        setupPlayer(localPath: localPath, networkURL: networkURL)
    }

    /// 配置播放器
    private func setupPlayer(localPath: String?, networkURL: String) {
        var videoURL: URL?

        // 优先本地文件
        if let localPath = localPath, FileManager.default.fileExists(atPath: localPath) {
            videoURL = URL(fileURLWithPath: localPath)
            print("播放本地视频: \(localPath)")
        } else if let url = URL(string: networkURL) {
            videoURL = url
            print("播放网络视频: \(networkURL)")
        } else {
            print("无有效视频路径")
            return
        }

        let playerItem = AVPlayerItem(url: videoURL!)

        // 可选:设置缓冲策略
        playerItem.preferredForwardBufferDuration = 5  // 提前缓冲 5 秒
        player = AVPlayer(playerItem: playerItem)
        player?.automaticallyWaitsToMinimizeStalling = true

        playerVC = AVPlayerViewController()
        playerVC?.player = player
    }

    /// 展示播放器
    func play() {
        guard let playerVC = playerVC, let parentVC = parentVC else { return }
        parentVC.present(playerVC, animated: true) {
            self.player?.play()
        }
    }

    /// 暂停播放
    func pause() {
        player?.pause()
    }

    /// 停止播放并释放
    func stop() {
        player?.pause()
        player = nil
        playerVC?.dismiss(animated: true)
        playerVC = nil
    }
}

使用示例

// 假设 parentVC 是当前 UIViewController
let localPath = Bundle.main.path(forResource: "video", ofType: "mp4")
let networkURL = "https://www.example.com/video.mp4"

let smartPlayer = SmartAVPlayer(localPath: localPath, networkURL: networkURL, parentVC: self)
smartPlayer.play()

功能特点

  1. 自动优先播放本地视频,如果本地文件不存在则播放网络视频。
  2. 支持缓冲控制 (preferredForwardBufferDuration & automaticallyWaitsToMinimizeStalling)。
  3. 提供暂停 (pause()) 和停止 (stop()) 接口。
  4. 使用 AVPlayerViewController 展示,兼容全屏和手势控制。

明白阿杰,我帮你写一个 升级版 SmartAVPlayer,实现以下功能:

  1. 本地视频优先播放
  2. 网络视频播放时可缓存到本地
  3. 下次可直接播放本地缓存
  4. 支持 HLS(.m3u8)和普通 MP4
  5. 自动管理缓存文件路径

import AVFoundation
import AVKit
import UIKit

class SmartAVPlayer: NSObject {

    private var player: AVPlayer?
    private var playerVC: AVPlayerViewController?
    private weak var parentVC: UIViewController?

    private var cacheDir: URL = {
        let urls = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
        let dir = urls[0].appendingPathComponent("VideoCache")
        if !FileManager.default.fileExists(atPath: dir.path) {
            try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
        }
        return dir
    }()

    /// 初始化播放器
    /// - Parameters:
    ///   - localPath: 本地视频路径(可选)
    ///   - networkURL: 网络视频 URL
    ///   - parentVC: 父控制器
    init(localPath: String?, networkURL: String, parentVC: UIViewController) {
        super.init()
        self.parentVC = parentVC
        prepareVideo(localPath: localPath, networkURL: networkURL)
    }

    /// 准备视频资源
    private func prepareVideo(localPath: String?, networkURL: String) {

        // 1. 优先本地文件
        if let localPath = localPath, FileManager.default.fileExists(atPath: localPath) {
            setupPlayer(url: URL(fileURLWithPath: localPath))
            print("播放本地视频: \(localPath)")
            return
        }

        // 2. 检查缓存
        let cachedURL = cacheDir.appendingPathComponent(networkURL.md5 + ".mp4")
        if FileManager.default.fileExists(atPath: cachedURL.path) {
            setupPlayer(url: cachedURL)
            print("播放缓存视频: \(cachedURL.path)")
            return
        }

        // 3. 下载网络视频
        guard let url = URL(string: networkURL) else { return }
        downloadVideo(from: url, saveTo: cachedURL)
    }

    /// 设置播放器
    private func setupPlayer(url: URL) {
        let playerItem = AVPlayerItem(url: url)
        playerItem.preferredForwardBufferDuration = 5
        player = AVPlayer(playerItem: playerItem)
        player?.automaticallyWaitsToMinimizeStalling = true

        playerVC = AVPlayerViewController()
        playerVC?.player = player
    }

    /// 下载网络视频并缓存
    private func downloadVideo(from url: URL, saveTo localURL: URL) {
        let task = URLSession.shared.downloadTask(with: url) { tempURL, _, error in
            guard let tempURL = tempURL, error == nil else {
                print("下载失败: \(error?.localizedDescription ?? "未知错误")")
                return
            }
            do {
                try FileManager.default.moveItem(at: tempURL, to: localURL)
                DispatchQueue.main.async {
                    self.setupPlayer(url: localURL)
                    self.play()
                }
                print("视频已缓存到: \(localURL.path)")
            } catch {
                print("缓存失败: \(error.localizedDescription)")
            }
        }
        task.resume()
    }

    /// 播放
    func play() {
        guard let playerVC = playerVC, let parentVC = parentVC else { return }
        parentVC.present(playerVC, animated: true) {
            self.player?.play()
        }
    }

    /// 暂停
    func pause() {
        player?.pause()
    }

    /// 停止并释放
    func stop() {
        player?.pause()
        player = nil
        playerVC?.dismiss(animated: true)
        playerVC = nil
    }
}

// MARK: - 简单 MD5 扩展用于生成缓存文件名
import CommonCrypto

extension String {
    var md5: String {
        let data = Data(self.utf8)
        var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        data.withUnsafeBytes { _ = CC_MD5($0.baseAddress, CC_LONG(data.count), &digest) }
        return digest.map { String(format: "%02x", $0) }.joined()
    }
}

使用示例

let localPath = Bundle.main.path(forResource: "video", ofType: "mp4")
let networkURL = "https://www.example.com/video.mp4" // 或者 HLS .m3u8

let smartPlayer = SmartAVPlayer(localPath: localPath, networkURL: networkURL, parentVC: self)
smartPlayer.play()

功能说明

  1. 本地视频优先播放,不存在再播放缓存或网络视频。
  2. 网络视频会下载并缓存到沙盒 Caches/VideoCache,下次直接播放本地缓存。
  3. 支持 MP4 或 HLS 流(AVPlayer 支持 HLS 自动码率)。
  4. 简单缓冲控制,提高播放流畅度。
  5. 缓存文件名使用 URL 的 MD5,防止重复和非法文件名。

阿杰,如果你愿意,我可以帮你再加一个 缓存自动清理功能,比如缓存超过 500MB 或超过 30 天自动删除,避免占用太多存储。

你希望我加这个功能吗?