使用AVPlayer播放m3u8视频时尺寸显示异常的解决咨询
AVPlayer流媒体视频尺寸适配解决方案
要解决不同视频显示尺寸异常的问题,必须动态获取视频的实际宽高比并计算适配容器的frame——因为不同视频的原生宽高比和你的容器视图宽高比不一定匹配,固定设置playerLayer.frame = view.bounds只会适配部分视频。
一、核心步骤
- 获取视频的实际显示尺寸:注意要考虑视频轨道的旋转变换(比如竖屏视频可能是横拍后旋转存储的,直接取
naturalSize会得到错误的宽高)。 - 根据容器尺寸计算适配frame:根据需求选择「完全显示不裁剪」或「填充容器裁剪超出」的适配逻辑。
- 监听状态与布局变化:确保视频加载完成后再计算尺寸,同时在容器视图尺寸变化时(如屏幕旋转)重新适配。
二、具体实现代码
1. 获取视频实际显示尺寸
func getVideoActualSize(from playerItem: AVPlayerItem) -> CGSize { guard let videoTrack = playerItem.asset.tracks(withMediaType: .video).first else { return .zero } // 应用视频轨道的旋转变换,得到实际显示的宽高 let transformedSize = videoTrack.naturalSize.applying(videoTrack.preferredTransform) return CGSize(width: abs(transformedSize.width), height: abs(transformedSize.height)) }
2. 计算适配容器的frame
根据你的需求选择下面两种计算逻辑:
(1)完全显示不裁剪(对应AVLayerVideoGravity.resizeAspect)
func calculateFitFrame(videoSize: CGSize, containerSize: CGSize) -> CGRect { let videoRatio = videoSize.width / videoSize.height let containerRatio = containerSize.width / containerSize.height var targetSize: CGSize if videoRatio > containerRatio { // 视频更宽,按容器宽度适配,高度自动缩放 targetSize = CGSize(width: containerSize.width, height: containerSize.width / videoRatio) } else { // 视频更高,按容器高度适配,宽度自动缩放 targetSize = CGSize(width: containerSize.height * videoRatio, height: containerSize.height) } // 居中显示 let originX = (containerSize.width - targetSize.width) / 2 let originY = (containerSize.height - targetSize.height) / 2 return CGRect(x: originX, y: originY, width: targetSize.width, height: targetSize.height) }
(2)填充容器裁剪超出(对应AVLayerVideoGravity.resizeAspectFill)
func calculateFillFrame(videoSize: CGSize, containerSize: CGSize) -> CGRect { let videoRatio = videoSize.width / videoSize.height let containerRatio = containerSize.width / containerSize.height var targetSize: CGSize if videoRatio < containerRatio { // 视频更窄,按容器高度适配,宽度超出部分裁剪 targetSize = CGSize(width: containerSize.height * videoRatio, height: containerSize.height) } else { // 视频更宽,按容器宽度适配,高度超出部分裁剪 targetSize = CGSize(width: containerSize.width, height: containerSize.width / videoRatio) } // 居中显示,超出容器的部分会被自动裁剪 let originX = (containerSize.width - targetSize.width) / 2 let originY = (containerSize.height - targetSize.height) / 2 return CGRect(x: originX, y: originY, width: targetSize.width, height: targetSize.height) }
3. 整合到你的播放逻辑中
class VideoPlayerViewController: UIViewController { var player: AVPlayer! var playerLayer: AVPlayerLayer! override func viewDidLoad() { super.viewDidLoad() // 初始化player(替换成你的流媒体URL) guard let url = URL(string: "你的流媒体地址") else { return } let playerItem = AVPlayerItem(url: url) player = AVPlayer(playerItem: playerItem) // 初始化playerLayer playerLayer = AVPlayerLayer(player: player) playerLayer.videoGravity = .resizeAspect // 或.resizeAspectFill,与计算逻辑对应 view.layer.addSublayer(playerLayer) // 监听playerItem加载状态 playerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil) } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard let playerItem = object as? AVPlayerItem, keyPath == "status" else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } if playerItem.status == .readyToPlay { // 视频加载完成,计算并设置frame let videoSize = getVideoActualSize(from: playerItem) let containerSize = view.bounds.size // 根据需求选择fit或fill逻辑 playerLayer.frame = calculateFitFrame(videoSize: videoSize, containerSize: containerSize) // 开始播放 player.play() } } // 监听容器视图尺寸变化(如屏幕旋转) override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() guard let playerItem = player.currentItem, playerItem.status == .readyToPlay else { return } let videoSize = getVideoActualSize(from: playerItem) let containerSize = view.bounds.size playerLayer.frame = calculateFitFrame(videoSize: videoSize, containerSize: containerSize) } // 移除观察者 deinit { player.currentItem?.removeObserver(self, forKeyPath: "status") } }
三、关键注意点
- 必须等
playerItem.status变为.readyToPlay后再获取视频尺寸,否则可能拿不到有效轨道信息。 - 视频的旋转变换是核心坑点,直接用
naturalSize会导致竖屏视频显示异常,一定要应用preferredTransform。 - 容器视图尺寸变化时(比如屏幕旋转、父视图布局更新),必须重新计算frame,否则适配会失效。
内容的提问来源于stack exchange,提问作者eokhjqcztkxkhoghbv
相关产品推荐
相关产品推荐

