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

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

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

【问题讨论】:

  • 我的回答有帮助吗?

标签: uikit swiftui


【解决方案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())

注意:

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

干杯!

【讨论】:

  • 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
        }
    }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-08-17
      • 1970-01-01
      • 2019-12-30
      • 1970-01-01
      • 2023-03-03
      • 2018-07-25
      • 2012-03-07
      • 2022-07-21
      相关资源
      最近更新 更多