jat
jat

Reputation: 337

VLCKit with AVPlayerLayer SwiftUI

I am trying to use VLCKit along with AVPlayerLayer (in order to eventually use PIP) I have VLCKit working correctly but I am unable to use it with PlayerLayer

This is the most basic working example using AVPlayer as the player that I am trying to modify but with no luck. Any help appreciated, thanks.

import AVFoundation
import SwiftUI

class PlayerView: UIView {
    
    var player: AVPlayer? {
        get {
            return playerLayer.player
        }
        set {
            playerLayer.player = newValue
        }
    }
    
    init(player: AVPlayer) {
        super.init(frame: .zero)
        self.player = player
        self.backgroundColor = .black
    }
        
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    var playerLayer: AVPlayerLayer {
        return layer as! AVPlayerLayer
    }
    
    // Override UIView property
    override static var layerClass: AnyClass {
        return AVPlayerLayer.self
    }
}

struct PlayerContainerView: UIViewRepresentable {
    typealias UIViewType = PlayerView
    
    let player: AVPlayer
    
    init(player: AVPlayer) {
        self.player = player
    }
    
    func makeUIView(context: Context) -> PlayerView {
        return PlayerView(player: player)
    }
    
    func updateUIView(_ uiView: PlayerView, context: Context) { }
}

class PlayerViewModel: ObservableObject {

    let player: AVPlayer
    
    init(fileName: String) {
        let url = Bundle.main.url(forResource: fileName, withExtension: "mp4")
        self.player = AVPlayer(playerItem: AVPlayerItem(url: url!))
    }
    
    @Published var isPlaying: Bool = false {
        didSet {
            if isPlaying {
                play()
            } else {
                pause()
            }
        }
    }
    
    func play() {
        let currentItem = player.currentItem
        if currentItem?.currentTime() == currentItem?.duration {
            currentItem?.seek(to: .zero, completionHandler: nil)
        }
        
        player.play()
    }
    
    func pause() {
        player.pause()
    }
}

enum PlayerAction {
    case play
    case pause
}

struct ContentView: View {
    
    @ObservedObject var model: PlayerViewModel
    init() {
        model = PlayerViewModel(fileName: "video")
    }
    
    var body: some View {
        ZStack {
            VStack {
            
                PlayerContainerView(player: model.player)
                    .frame(height: 200)
                
                Button(action: {
                    self.model.isPlaying.toggle()
                }, label: {
                    Image(systemName: self.model.isPlaying ? "pause" : "play")
                        .font(.largeTitle)
                        .padding()
                })
            }
        }
        .ignoresSafeArea()
    }
}

Upvotes: 0

Views: 435

Answers (1)

malhal
malhal

Reputation: 30727

In SwiftUI you can't have classes in your View structs. So first rename PlayerViewModel as PlayerCoordinator and init it inside the UIViewRepresentable. It is also essential you implement updateUIView to copy the values from the struct to the objects, e.g. something like this:

class PlayerCoordinator {

    let player: AVPlayer
...

struct PlayerContainerView: UIViewRepresentable {

    @Binding var isPlaying: Bool

    func makeCoordinator() -> Coordinator {
        PlayerCoordinator()
    }

    func updateUIView(_ uiView: PlayerView, context: Context) { 

        context.coordinator.didPlay = nil
        context.coordinator.didPause = nil

        if isPlaying == true && context.coordinator.player.isPlayer == false {
            context.coordinator.player.play()
        }

       if isPlaying == false && context.coordinator.player.isPlayer == true {

            context.coordinator.player.pause()
        }

        coordinator.didPause = {
            isPlaying = false
        }

        coordinator.didPlay = {
            isPlaying = true
        }
    }
}

Then your View struct should be like this:

struct ContentView: View {
    
    @State var isPlaying = false
    
    var body: some View {
        ZStack {
            VStack {
            
                PlayerView(isPlaying: isPlaying)
                    .frame(height: 200)
                
                Button(action: {
                    isPlaying.toggle()
                }, label: {
                    Image(systemName: isPlaying ? "pause" : "play")
                        .font(.largeTitle)
                        .padding()
                })
            }
        }
        .ignoresSafeArea()
    }
}

Upvotes: 0

Related Questions