在 SwiftUI 中,如何将视频循环添加为全屏背景图像?

Posted

技术标签:

【中文标题】在 SwiftUI 中,如何将视频循环添加为全屏背景图像?【英文标题】:In SwiftUI, how can I add a video on loop as a fullscreen background image? 【发布时间】:2020-01-27 19:36:18 【问题描述】:

我有一个大约 10 秒长的视频,我想在我的一个 SwiftUI 视图中作为全屏背景图像循环播放。我该如何实现?

第一个想法是使用 Swift 的 import AVFoundation,但不确定这是否是正确的路径。

【问题讨论】:

我的回答有帮助吗? 【参考方案1】:

您可以使用AV 系列框架和UIViewRepresentable 来执行此操作:

import SwiftUI
import AVKit

struct PlayerView: UIViewRepresentable 
    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) 
    
    
    func makeUIView(context: Context) -> UIView 
        return PlayerUIView(frame: .zero)
    

为了让视频循环播放,我添加了一个观察者并将actionAtItemEnd 设置为.none 以支持循环播放。

当视频到达结尾时,它会执行playerItemDidReachEnd(...) 方法并寻找到视频的开头并继续循环。

该示例指向一个远程视频 URL。如果您想指向应用程序中的文件,您可以使用 Bundle.main.url 来代替:

if let fileURL = Bundle.main.url(forResource: "IMG_2770", withExtension: "MOV") 
    let player = AVPlayer(url: fileURL)
    // ...


class PlayerUIView: UIView 
    private let playerLayer = AVPlayerLayer()
    
    override init(frame: CGRect) 
        super.init(frame: frame)
        
        let url = URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!
        let player = AVPlayer(url: url)
        player.actionAtItemEnd = .none
        player.play()
        
        playerLayer.player = player
        playerLayer.videoGravity = .resizeAspectFill
        
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(playerItemDidReachEnd(notification:)),
                                               name: .AVPlayerItemDidPlayToEndTime,
                                               object: player.currentItem)

        layer.addSublayer(playerLayer)
    
    
    @objc func playerItemDidReachEnd(notification: Notification) 
        if let playerItem = notification.object as? AVPlayerItem 
            playerItem.seek(to: .zero, completionHandler: nil)
        
    
    
    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    
    
    override func layoutSubviews() 
        super.layoutSubviews()
        playerLayer.frame = bounds
    


struct ContentView: View 
    var body: some View 
        NavigationView 
            ZStack 
                PlayerView()
                    .edgesIgnoringSafeArea(.all)
            
        
    

【讨论】:

回答播放部分,但如何循环播放视频? 在我原来的问题标题和描述中,我还询问了循环部分? @user12533955 哦,是的,你做到了!道歉。我已经更新了答案,以便在视频结束时循环播放。 不用担心,效果很好。我的问题是,使用 NotificationCenter 循环而不是 AVPlayerLooper 有什么好处? 我想我总是发现实现NotificationCenterAVPlayerLooper 更快,你的问题是关于使用活动指示器,所以它是一个阻塞实现。【参考方案2】:

SwiftUI

作为一个对 swift 完全陌生的人,对于那些不想像我一样花几个小时调试这个的人。我的用例是尝试创建一个在后台播放视频的登录屏幕。我一直在努力解决循环不起作用,然后视频在几秒钟后停止并在持续时间后重新开始。这对我有用。

添加新视图:

import SwiftUI
import AVKit
import AVFoundation

struct WelcomeVideo: View 
    var body: some View 
        WelcomeVideoController()
    


struct WelcomeVideo_Previews: PreviewProvider 
    static var previews: some View 
        WelcomeVideo()
    


final class WelcomeVideoController : UIViewControllerRepresentable 
    var playerLooper: AVPlayerLooper?
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<WelcomeVideoController>) ->
        AVPlayerViewController 
            let controller = AVPlayerViewController()
            controller.showsPlaybackControls = false
            
            guard let path = Bundle.main.path(forResource: "welcome", ofType:"mp4") else 
                debugPrint("welcome.mp4 not found")
                return controller
            
                    
            let asset = AVAsset(url: URL(fileURLWithPath: path))
            let playerItem = AVPlayerItem(asset: asset)
            let queuePlayer = AVQueuePlayer()
            // OR let queuePlayer = AVQueuePlayer(items: [playerItem]) to pass in items
            
            playerLooper = AVPlayerLooper(player: queuePlayer, templateItem: playerItem)
            queuePlayer.play()
            controller.player = queuePlayer
                
            return controller
        
    
    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<WelcomeVideoController>) 
    


然后将其附加到视图背景:

.background(WelcomeVideo())

注意:

    确保您的视频已导入到您的项目中 将视频名称更新为您需要的名称或稍微重构以将其传入

干杯!

【讨论】:

Representables 被定义为结构体。 Coordinator 可以存储 Looper 引用。【参考方案3】:

这对我有用: source

var body: some View  
    ZStack 
        HStack
            Spacer()
                .frame(width: 50)
            AmbienceVid()
        
        .edgesIgnoringSafeArea(.all)
           



 struct AmbienceVid: UIViewRepresentable 
    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<AmbienceVid>) 
    

    func makeUIView(context: Context) -> UIView 
      return PlayerUIView(frame: .zero)
    
  


class PlayerUIView: UIView 
    private var playerLooper: AVPlayerLooper?
    private var playerLayer = AVPlayerLayer()
    
    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    override init(frame: CGRect) 
        super.init(frame: frame) 
        // Load the resource
        let fileUrl = Bundle.main.url(forResource: "ambiencevid", withExtension: "mp4")!
        let asset = AVAsset(url: fileUrl)
        let item = AVPlayerItem(asset: asset)
        
        // Setup the player
        let player = AVQueuePlayer()
        playerLayer.player = player
        playerLayer.videoGravity = .resizeAspectFill
        layer.addSublayer(playerLayer)
         
        // Create a new player looper with the queue player and template item
        playerLooper = AVPlayerLooper(player: player, templateItem: item)

        // Start the movie
        player.play()
    

    override func layoutSubviews() 
            super.layoutSubviews()
            playerLayer.frame = bounds
    

【讨论】:

【参考方案4】:

如果人们正在搜索它,这是一个循环的、无控制的 macOS 实现。


import SwiftUI
import AVKit

struct NSVideoPlayer: NSViewRepresentable 
    
    var videoURL: URL
    
    func makeNSView(context: Context) -> AVPlayerView 
        let item = AVPlayerItem(url: videoURL)
        let queue = AVQueuePlayer(playerItem: item)
        context.coordinator.looper = AVPlayerLooper(player: queue, templateItem: item)
        
        let view = AVPlayerView()
        view.player = queue
        view.controlsStyle = .none
        view.player?.playImmediately(atRate: 1)
        return view
    
    
    func updateNSView(_ nsView: AVPlayerView, context: Context) 
    
    func makeCoordinator() -> Coordinator 
        Coordinator()
    
    
    class Coordinator 
        var looper: AVPlayerLooper? = nil
    


【讨论】:

以上是关于在 SwiftUI 中,如何将视频循环添加为全屏背景图像?的主要内容,如果未能解决你的问题,请参考以下文章

旋转 90 时如何将 TextureView 大小调整为全屏?

想要为全屏视频背景上的音频创建自定义静音/取消静音按钮

将视频设为全屏尺寸 CSS

Silverlight播放器:点击按钮(html / javascript)将视频设置为全屏? [关闭]

如何在 SwiftUI 中更改 modal 或 sheet 的大小?

如何将启动画面设置为全屏?