普通网友 2026-02-07 00:10 采纳率: 98.4%
浏览 0

SJBaseVideoPlayer全屏时画面拉伸变形,如何保持原始宽高比?

在使用 SJBaseVideoPlayer 实现全屏播放时,常出现画面拉伸变形问题——视频内容被强制撑满全屏,丢失原始宽高比(如 16:9 视频被压成 4:3 或拉宽失真)。根本原因在于:默认 `videoGravity` 属性未正确配置,且全屏切换时未同步更新 `layer` 的 `contentsGravity` 及 `UIViewContentMode`;同时,若父容器约束或 frame 未适配视频固有比例,Auto Layout 也可能覆盖播放器内部缩放逻辑。此外,部分版本中 `SJBaseVideoPlayer` 在横竖屏切换时未自动重算 `preferredTransform` 或未触发 `layoutIfNeeded`,加剧变形。该问题高频出现在 iOS 15+、iPhone X 及以上全面屏设备,尤其当视频源分辨率不规则(如 4096×2160)或使用自定义 controlView 时更易复现。解决需协同设置 `playerLayer.contentsGravity = kCAGravityResizeAspect`、校准 view.contentMode,并在全屏回调中主动调用 `setNeedsLayout` 与 `layoutIfNeeded`,必要时重写 `viewWillTransition` 以动态调整布局。
  • 写回答

1条回答 默认 最新

  • 马迪姐 2026-02-07 00:10
    关注
    ```html

    一、现象层:全屏播放画面拉伸变形的典型表现

    • iOS 15+ 设备(尤其是 iPhone X/XS/12/13/14/15 系列)横屏全屏时,16:9 视频被强制填充为 9:16 或 1:1,出现明显“胖脸”或“瘦长”失真;
    • 4K 超高清源(如 4096×2160)在全面屏上渲染后边缘裁切严重,关键信息(字幕/UI 元素)被截断;
    • 启用自定义 controlView 后,视频视图尺寸突变,contentMode 重置为 UIViewContentModeScaleToFill
    • 竖屏→横屏切换瞬间闪现黑边或拉伸帧,持续约 1–2 帧,肉眼可辨;
    • 部分用户反馈:仅在真机复现,Simulator 表现正常——暴露底层与 Core Animation 渲染管线强耦合。

    二、机制层:视频缩放行为的三重控制权博弈

    视频画面最终呈现由以下三层协同(或冲突)决定:

    层级控制主体关键属性默认值(SJBaseVideoPlayer v3.4.2+)
    ① AVPlayerLayerplayerLayercontentsGravitykCAGravityResize(⚠️非 Aspect)
    ② UIView 容器playerViewcontentModeUIViewContentModeScaleToFill
    ③ Auto Layout 约束父视图约束链width/height ratio / aspectRatio constraint常缺失或硬编码为 16:9,无视视频元数据

    三、根因层:iOS 15+ 渲染栈升级引发的兼容性断层

    • preferredTransform 失效:AVPlayerItem 的 asset.tracks[0].preferredTransform 在横竖屏切换后未被 SJBaseVideoPlayer 主动刷新,导致 layer 变换矩阵残留旧方向;
    • layout 生命周期脱节:全屏回调(didEnterFullscreen)中未触发 setNeedsLayoutlayoutIfNeeded,Auto Layout 未重算子视图 frame;
    • contentGravity 同步缺失videoGravity 属性变更仅影响 AVPlayerLayer 内部逻辑,但未透传至 playerLayer.contentsGravity
    • 全面屏 Safe Area 干扰:iPhone X+ 的 safeAreaLayoutGuide 若参与约束,且未设置 translatesAutoresizingMaskIntoConstraints = false,将覆盖比例约束。

    四、解决方案层:四阶协同修复策略

    1. 初始化阶段:显式设置播放器基础缩放语义
      player.videoGravity = .resizeAspect;
      playerView.contentMode = .scaleAspectFit;
    2. 全屏生命周期钩子:在 didEnterFullscreen 中同步底层 layer:
      player.playerLayer.contentsGravity = kCAGravityResizeAspect;
      playerView.setNeedsLayout(); playerView.layoutIfNeeded();
    3. 横竖屏适配增强:重写 viewWillTransition(to:with:),动态注入视频固有宽高比约束:
      if let asset = player.currentItem?.asset, let track = asset.tracks(withMediaType: .video).first {
        let naturalSize = track.naturalSize.applying(track.preferredTransform);
        updateAspectRatioConstraint(naturalSize.width / naturalSize.height);
      }
    4. 防御性兜底:监听 AVPlayerItemDidPlayToEndTimeNotificationAVPlayerItemFailedToPlayToEndTimeNotification,异常时强制重置 layer 缩放。

    五、验证层:跨设备/跨版本回归测试清单

    graph TD A[启动播放器] --> B{iOS 版本} B -->|iOS 15.0-15.4| C[检查 preferredTransform 是否被 reset] B -->|iOS 16.0+| D[验证 safeAreaInsets 对 layout 的影响权重] C --> E[注入 transform 监听器] D --> F[添加 safeAreaLayoutGuide 依赖约束] E --> G[录制 videoGravity 切换日志] F --> G G --> H[自动化截图比对:16:9 vs 4:3 vs 21:9 源]

    六、进阶实践:构建自适应视频容器组件

    建议封装 SJAdaptiveVideoView 继承自 SJBaseVideoPlayer,内置:

    • 基于 AVAssetTrack.naturalSize + preferredTransform 的实时宽高比探测;
    • 响应式约束管理器(支持 @IBInspectable 配置缩放策略);
    • UIWindowScene 生命周期联动的旋转事件分发;
    • 性能埋点:记录每次 contentsGravity 变更耗时及 layout pass 次数。
    ```
    评论

报告相同问题?

问题事件

  • 创建了问题 今天