Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In SwiftUI, how can I add a video on loop as a fullscreen background image?

Tags:

uikit

swiftui

I have a video thats around 10 seconds long that I'd like to play on a loop as a fullscreen background image in one of my SwiftUI Views. How can I implement this?

First idea was working with Swift's import AVFoundation, but not sure if this is the right path.

like image 972
user12533955 Avatar asked Jan 27 '20 19:01

user12533955


People also ask

How do I add a background image in SwiftUI?

SwiftUI doesn't have a dedicated modifier for displaying background colors or images, but instead lets us specify any kind of background view using its background() modifier. To be clear, you can use any view as your background – another text view if you wanted, for example.


2 Answers

You can use the AV family of frameworks and UIViewRepresentable to do this:

import SwiftUI
import AVKit

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

In order for the video to loop I have added an observer and set the actionAtItemEnd to .none to support looping.

When the video reaches the end it will execute the playerItemDidReachEnd(...) method and seek to the beginning of the video and keep looping.

The example points to a remote video URL. If you want to point to a file within your application you can use Bundle.main.url to do so instead:

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)
            }
        }
    }
}
like image 173
fulvio Avatar answered Sep 25 '22 08:09

fulvio


SwiftUI

As someone completely new to swift and for anyone who doesn't want to spend hours debugging this like I did. My use case was trying to create a login screen with a video playing in the background. I was struggling with the looping not working and then with the video stopping after a few seconds and starting again after the duration. This works for me.

Add a new view:

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>) {
    }
}

Then attach it to a view background:

.background(WelcomeVideo())

NOTE:

  1. Make sure your video is imported to your project
  2. Update the name of the video to what you need or refactor slightly to pass it in

Cheers!

like image 35
mikeyseay Avatar answered Sep 25 '22 08:09

mikeyseay