阿杰,你问的这个问题是关于 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.path
或FileManager
获取本地文件路径。 - 无需考虑网络缓冲或网络中断。
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()
}
}
注意点:
- 网络视频需要考虑缓冲、超时和失败处理。
- 可以使用
AVPlayerItem
的preferredForwardBufferDuration
或automaticallyWaitsToMinimizeStalling
来控制缓冲行为。 - 对 HLS 流,AVPlayer 会自动选择码率;对于普通 MP4,通常需要提前下载或依赖系统缓存。
3. 核心区别对比
特性 | 本地文件 | 网络视频 |
---|---|---|
URL 类型 | file:// | http(s):// |
加载速度 | 快,无缓冲 | 受网络影响,需要缓冲 |
错误处理 | 文件不存在或路径错误 | 网络超时、404、流媒体错误 |
码率适应 | 固定 | HLS 自动码率适应,普通 MP4 不适用 |
缓存 | 已经在本地 | 系统会缓存部分播放数据,或者需自定义缓存 |
播放稳定性 | 高 | 受网络稳定性影响 |
4. 小提示
- 本地文件适合 App 内打包或下载的视频,保证离线播放。
- 在线视频适合直播、云端资源或用户生成内容,但要处理网络波动、缓冲和错误回调。
- 混合方式:先下载到本地缓存,再用 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()
功能特点
- 自动优先播放本地视频,如果本地文件不存在则播放网络视频。
- 支持缓冲控制 (
preferredForwardBufferDuration
&automaticallyWaitsToMinimizeStalling
)。 - 提供暂停 (
pause()
) 和停止 (stop()
) 接口。 - 使用
AVPlayerViewController
展示,兼容全屏和手势控制。
明白阿杰,我帮你写一个 升级版 SmartAVPlayer,实现以下功能:
- 本地视频优先播放
- 网络视频播放时可缓存到本地
- 下次可直接播放本地缓存
- 支持 HLS(.m3u8)和普通 MP4
- 自动管理缓存文件路径
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()
功能说明
- 本地视频优先播放,不存在再播放缓存或网络视频。
- 网络视频会下载并缓存到沙盒
Caches/VideoCache
,下次直接播放本地缓存。 - 支持 MP4 或 HLS 流(AVPlayer 支持 HLS 自动码率)。
- 简单缓冲控制,提高播放流畅度。
- 缓存文件名使用 URL 的 MD5,防止重复和非法文件名。
阿杰,如果你愿意,我可以帮你再加一个 缓存自动清理功能,比如缓存超过 500MB 或超过 30 天自动删除,避免占用太多存储。
你希望我加这个功能吗?
发表回复