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

SwiftUI中如何为AVPlayer视频循环添加平滑过渡效果?

给循环视频播放器添加循环间平滑过渡效果

嘿,我来帮你搞定这个循环过渡的需求!你的现有代码用了AVPlayerLooper实现自动循环,但它没法直接插入过渡动画,所以我们需要调整一下播放逻辑,手动控制循环,这样就能在每次循环之间加入你想要的模糊(或者其他)过渡效果了。

核心思路

  1. 移除自动循环的AVPlayerLooper,改为手动监听视频结束事件,触发过渡动画后再重新启动播放
  2. 添加一个过渡视图(比如模糊效果),在循环间隙显示
  3. 通过动画控制过渡视图的显示/隐藏,实现平滑衔接

修改后的完整代码

import SwiftUI
import AVFoundation

// MARK: ExerciseVideoPlayer: UIViewRepresentable
struct ExerciseVideoPlayer: UIViewRepresentable {
    var increaseRepetitions: () -> Void
    var videoUrl: URL
    
    func makeUIView(context: Context) -> UIView {
        LoopingVideoPlayerView(repetitions: increaseRepetitions, videoUrl: videoUrl)
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        // Do nothing here
    }
}

// MARK: LoopingVideoPlayerView: UIView
class LoopingVideoPlayerView: UIView {
    // MARK: Stored properties
    var increaseRepetitions: () -> Void
    var videoUrl: URL
    private var playerLayer = AVPlayerLayer()
    private var player: AVQueuePlayer!
    private var blurView: UIVisualEffectView! // 过渡用的模糊视图
    
    // MARK: Initialization
    init(repetitions: @escaping () -> Void, videoUrl: URL) {
        self.increaseRepetitions = repetitions
        self.videoUrl = videoUrl
        super.init(frame: .zero)
        
        // 1. 初始化模糊过渡视图
        let blurEffect = UIBlurEffect(style: .systemUltraThinMaterialDark)
        blurView = UIVisualEffectView(effect: blurEffect)
        blurView.frame = bounds
        blurView.alpha = 0 // 默认隐藏
        addSubview(blurView)
        
        // 2. 初始化播放器
        let playerItem = AVPlayerItem(url: videoUrl)
        player = AVQueuePlayer(playerItem: playerItem)
        playerLayer.player = player
        playerLayer.videoGravity = .resizeAspect
        layer.addSublayer(playerLayer)
        
        // 3. 监听视频播放结束事件
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleVideoEnd),
            name: .AVPlayerItemDidPlayToEndTime,
            object: playerItem
        )
        
        // 开始播放
        player.play()
    }
    
    // MARK: Methods
    override func layoutSubviews() {
        super.layoutSubviews()
        playerLayer.frame = bounds
        blurView.frame = bounds // 确保模糊视图铺满整个播放器
    }
    
    // 处理视频结束逻辑:触发过渡动画,然后重新播放
    @objc private func handleVideoEnd() {
        // 先调用计数增加
        increaseRepetitions()
        
        // 播放结束,显示模糊过渡动画
        UIView.animate(withDuration: 0.3) {
            self.blurView.alpha = 1
        } completion: { _ in
            // 延迟一会儿(比如0.2秒),然后隐藏模糊并重新播放
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                UIView.animate(withDuration: 0.3) {
                    self.blurView.alpha = 0
                } completion: { _ in
                    // 重新加载视频项并播放
                    let newPlayerItem = AVPlayerItem(url: self.videoUrl)
                    self.player.replaceCurrentItem(with: newPlayerItem)
                    // 重新监听新的播放项结束事件
                    NotificationCenter.default.addObserver(
                        self,
                        selector: #selector(self.handleVideoEnd),
                        name: .AVPlayerItemDidPlayToEndTime,
                        object: newPlayerItem
                    )
                    self.player.play()
                }
            }
        }
    }
    
    // MARK: 禁用coder初始化
    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// MARK: ExerciseVideoPlayer_Previews: PreviewProvider
struct ExerciseVideoPlayer_Previews: PreviewProvider {
    static var previews: some View {
        guard let videoUrl = URL(string: "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/1080/Big_Buck_Bunny_1080_10s_10MB.mp4") else { fatalError("videoURL not found") }
        return ExerciseVideoPlayer(increaseRepetitions: {}, videoUrl: videoUrl)
    }
}

关键改动说明

  • 移除AVPlayerLooper:自动循环会跳过我们想要插入的过渡环节,改为手动控制每次循环的结束和重启,给过渡效果留出空间
  • 添加模糊过渡视图:用UIVisualEffectView实现系统原生模糊效果,你可以根据需求换成其他样式(比如浅色模糊、自定义毛玻璃效果)
  • 修改结束事件处理:视频播放结束后,先执行计数增加逻辑,再触发模糊淡入动画;延迟片刻后淡出模糊,重新加载视频项并启动播放,同时绑定新播放项的结束监听
  • 动态布局适配:确保模糊视图在播放器尺寸变化时能铺满整个区域,避免出现错位

自定义过渡效果

如果你想换其他过渡,比如淡入淡出黑色背景,只需要把模糊视图换成普通UIView,修改动画逻辑即可:

// 替换模糊视图为黑色背景
let fadeView = UIView()
fadeView.backgroundColor = .black
fadeView.alpha = 0
addSubview(fadeView)

然后调整动画中的透明度变化参数,就能实现不同的过渡风格,非常灵活。

内容的提问来源于stack exchange,提问作者Arno Claude

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 21:47:48