7

I have created a simple AVPlayer. (If I didn't create it correctly, please help me fixing it... ) I want to display it in my VStack with swift ui, but I'm kind of stuck...

If at least there was the AVPlayer View component in the library, it would have been easier, but I didn't found it in the library...

Here is my code in the ContentView.swift:

//
//  ContentView.swift
//  test
//
//  Created by Francis Dolbec on 2019-06-26.
//  Copyright © 2019 Francis Dolbec. All rights reserved.
//

import SwiftUI
import AVKit
import AVFoundation

// MARK: variables
var hauteurMenuBar = NSApplication.shared.mainMenu?.menuBarHeight
var urlVideo = URL(string: "/Users/francisdolbec/Movies/Séries Télé/Rick et Morty/Rick.and.Morty.S01E01.VFQ.HDTV.1080p.x264-Kamek.mp4")
let player = AVPlayer(url: urlVideo!)


struct ContentView : View {
    var body: some View {

        VStack {
            Text("Hello World")
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            Text("PATATE!")
            //player.play()
            }

        .frame(minWidth: 1024, idealWidth: 1440, maxWidth: .infinity, minHeight: (640-hauteurMenuBar!), idealHeight: (900-hauteurMenuBar!), maxHeight: .infinity)

    }
}


#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

By the way, if there's a tutorial that show how to make a video player with swift ui, I would really appreciate it!

EDIT I forgot to say that I am developing a macOS app.

Francis Dolbec
  • 175
  • 1
  • 11

2 Answers2

13

Sure, here's the tutorial, by Chris Mash on Medium.

Basically you embed an AVPlayerLayer into a struct conforming to UIViewRepresentable, something you'll do for any UIView component you want to use with SwiftUI.

struct PlayerView: UIViewRepresentable {
  func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
  }

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

The "meat" of the implementation is done in the PlayerUIView class:

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.play()
    
    playerLayer.player = player
    layer.addSublayer(playerLayer)
  }

  required init?(coder: NSCoder) {
     fatalError("init(coder:) has not been implemented")
  }

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

And then you use it like this:

var body: some View {
  PlayerView()
}
Mike
  • 6,285
  • 4
  • 18
  • 22
Bogdan Farca
  • 3,856
  • 26
  • 38
  • Hmm... This tutorial, and example, is for iOS... I am developing for **macOS**. – Francis Dolbec Jul 01 '19 at 04:57
  • Sorry I forgot to mention it in my initial post... :) – Francis Dolbec Jul 01 '19 at 05:07
  • @FrancisDolbec : Both `UIViewRepresentable` and `AVPlayerLayer` work on MacOs too. – Bogdan Farca Jul 01 '19 at 06:30
  • Oh really? Maybe I type something wrong because, when I tried your answer, it says it can't import UIKit... And I read that it's for iOS and the equivalent is AppKit... – Francis Dolbec Jul 01 '19 at 06:39
  • @FrancisDolbec: Sorry, I meant NSViewRepresentable, see https://developer.apple.com/documentation/swiftui/nsviewrepresentable – Bogdan Farca Jul 01 '19 at 09:08
  • Oh ok no problem! I will try back your solution and notify if it works for me! :) – Francis Dolbec Jul 01 '19 at 22:27
  • So... After try the code and changing the UIkit stuff to their equivalent in AppKit (NS) I get the following error: `Type 'PlayerView' does not conform to protocol 'NSViewRepresentable'`. It suggest me to add protocol stubs... – Francis Dolbec Jul 01 '19 at 23:17
  • It was a typo error that generated that `Type 'PlayerView' does not conform to protocol 'NSViewRepresentable'` error. I fix it... – Francis Dolbec Jul 01 '19 at 23:30
  • Few questions for you: First, is it normal that only hear the audio but don't see the picture? I guess there is something that I need to tweek to get the picture, right? Also, I tried to used the url of a local file on my computer but it tells me that it found nil when unwrapping... Any particular format so I can access that local video? Finally, what would (in code) in my button so I can manually start the video and not when the app launch? – Francis Dolbec Jul 01 '19 at 23:44
  • @BogdanFarca Thanks so much. Do you know how I can play a local video instead of one from a link ? So i added a short video to my Xcode project but can't load it – PhillipJacobs Jul 17 '19 at 13:31
  • never mind Thanks. I found it `let videoPath = Bundle.main.path( forResource: "AstrocyteLogoAnimated", ofType: "mp4") let videoPathURL = URL(fileURLWithPath: videoPath!) let player = AVPlayer(url: videoPathURL)` – PhillipJacobs Jul 17 '19 at 13:50
  • how i can use like PlayerView("video1.mp4") PlayerView("video2.mp4") – q8yas Nov 14 '19 at 02:00
  • Hello. Is there a way to update the URL string to reload / relaunch the player? So for example, I will have a user tap on a View which is connected to a data model. Each view will have values associated with it, such as a URL to a video. So the goal is when the user taps on that view, the player will update to the correct video. – Luke Irvin Mar 18 '20 at 02:41
4

Thanks to Bogdan for the quick answer and for the like to the tutorial!

Here is the code, converted to NSView so it could work for macOS apps...

Here is the struct PlayerView :

struct PlayerView: NSViewRepresentable {
    func updateNSView(_ nsView: NSView, context: NSViewRepresentableContext<PlayerView>) {

    }
    func makeNSView(context: Context) -> NSView {
        return PlayerNSView(frame: .zero)
    }
}

Here is the class, "the meat" like Bogdan said:

class PlayerNSView: NSView{
    private let playerLayer = AVPlayerLayer()

    override init(frame:CGRect){
        super.init(frame: frame)
        let urlVideo = URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!
        let player = AVPlayer(url: urlVideo)
        player.play()
        playerLayer.player = player
        if layer == nil{
            layer = CALayer()
        }
        layer?.addSublayer(playerLayer)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func layout() {
        super.layout()
        playerLayer.frame = bounds
    }
}

Finally you can use PlayerView() to add the AVPlayer in the struct ContentView.

Francis Dolbec
  • 175
  • 1
  • 11
  • The `PlayerUIView` implementation worked just fine for me, out of the box, on macOS Catalina. Using an iOS app with macOS turned on. – Jonny Aug 21 '19 at 08:38
  • how i can use like PlayerView("video1.mp4") PlayerView("video2.mp4") – q8yas Nov 14 '19 at 03:19