My main goal is to embed a GIF/MP4 file in a view in a HStack/VStack using SwiftUI. I understand the code needs to conform to the 'Representable' protocols. My first attempt at doing this is below:
import SwiftUI
import AVKit
struct GIF : UIViewControllerRepresentable {
let url_name: String
func makeUIViewController(context: UIViewControllerRepresentableContext<GIF>) -> AVPlayerViewController {
return AVPlayerViewController()
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<GIF>) {
let url = URL(string: url_name)!
let player = AVPlayer(url: url)
let vc = AVPlayerViewController()
vc.player = player
//PROBLEM
self.present(vc, animated: true){
vc.player?.play()
}
}
}
The problem is that I don't think 'self' makes any sense here and we need to refer to a view. How would we do this? We can't just create a view in a story board as I want to integrate the view controller with SwiftUI.
Any help would be appreciated.
P.S. This is my first iOS app so if I'm missing something very obvious here please try to be kind!
it's easy to wrap old AVPlayerViewController
import AVKit
import SwiftUI
struct AVPlayerView: UIViewControllerRepresentable {
#Binding var videoURL: URL?
private var player: AVPlayer? {
guard let url = videoURL else {
return nil
}
return AVPlayer(url: url)
}
func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {
playerController.player = player
playerController.player?.play()
}
func makeUIViewController(context: Context) -> AVPlayerViewController {
AVPlayerViewController()
}
}
but if ur target is equal or higher than 14 u can use VideoPlayer component from swiftui without any wrappers
import SwiftUI
import AVKit
struct UrView: View {
var body: some View {
VideoPlayer(
player: AVPlayer(url: URL(string: "{ur url}"))
)
}
}
Related
I'm trying to do a redirect when a video finishes playing, i hide the controls and automate the playback, but I can't do the redirection, the video ends but nothing happens; This is my code:
import AVKit
import SwiftUI
struct ContentView: View {
var body: some View {
let player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "IMG_0226", ofType: "mp4")!))
AVPlayerControllerRepresented(player: player)
.onAppear {
player.play()
NotificationCenter.default.addObserver(self, selector: Selector(("playerDidFinishPlaying:")),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
func playerDidFinishPlaying(note: NSNotification) {
let lock = URL(string: "https://www.awesomepage.com")!
UIApplication.shared.open(lock)
}
}
}
}
struct AVPlayerControllerRepresented : UIViewControllerRepresentable {
var player : AVPlayer
func makeUIViewController(context: Context) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
}
}
Hope someone can help me! Thanks
I am trying to make a video player in my app (with swiftUI) that playes videos from youtube when the user creates a URL string. To practise I have seperated this into a new project to see that it works. But the screen is just black with a play button. Nothing happends when I press the play button or the screen.
This is my code:
import SwiftUI
import AVKit
struct ContentView: View {
let url = URL(string: "https://youtu.be/Wlf1T5nrO50")!
var body: some View {
VStack{
VideoPlayer(player: AVPlayer(url: url))
.scaledToFit()
}
}
I found another video with how to make embedded youtube videos but then you need to just copy the video ID and my user is not that advanced. I want the user to be able to just copy the URL.
Thankful for any help.
A better Solution I made
struct YoutubeVideoView: UIViewRepresentable {
var youtubeVideoID: String
func makeUIView(context: Context) -> WKWebView {
WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
let path = "https://www.youtube.com/embed/\(youtubeVideoID)"
guard let url = URL(string: path) else { return }
uiView.scrollView.isScrollEnabled = false
uiView.load(.init(url: url))
}
}
A YouTube url is not a valid video URL. You have to either add youtube's library or play it using a WKWebView.
Also check out this package, it's awesome!
Using WKWebView:
struct LinkView: View {
var link: String
var body: some View {
WebView(url: URL(string: link.embed)!)
}
}
struct WebView: UIViewRepresentable {
var url: URL
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
let request = URLRequest(url: url)
webView.load(request)
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
extension String {
var embed: String {
var strings = self.components(separatedBy: "/")
let videoId = strings.last ?? ""
strings.removeLast()
let embedURL = strings.joined(separator: "/") + "embed/\(videoId)"
return embedURL
}
}
Usage:
LinkView(link: "https://youtube.com/videoid")
Remember, this is a very basic WebView that does not handle errors and loading.
i am making avplayer with swiftui. With uiviewcontrollerrepresenta what I need is whether the video being played is finished or not, if it is done I need to take action accordingly.
struct VideoPlayerSwiftUI : UIViewControllerRepresentable {
var playerURL : String
#State var status : AVPlayer.Status
var videoGravity : AVLayerVideoGravity
let avPlayerController = AVPlayerViewController()
func makeUIViewController(context: Context) -> AVPlayerViewController {
let player = AVPlayer(url: URL(string: playerURL)!)
avPlayerController.player = player
avPlayerController.showsPlaybackControls = false
avPlayerController.player?.play()
avPlayerController.player?.actionAtItemEnd = .pause
avPlayerController.videoGravity = videoGravity
self.addPeriodicTimeObserver()
return avPlayerController
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
}
}
I'm writing a video player using the AVkit under macOS, the AVPlayer can response to the keyboard event, for example, space key down to pause/play the video, left arrow key down to move back the video.
I want to capture the keydown event, so that I can do more controls to the AVPlayer, how to do it?
the following is my sample code:
import SwiftUI
import AVKit
import AVFoundation
public class VideoItem: ObservableObject {
#Published var player: AVPlayer = AVPlayer()
#Published var playerItem: AVPlayerItem?
func open(_ url: URL) {
let asset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
self.playerItem = playerItem
player.replaceCurrentItem(with: playerItem)
}
}
public struct PlayerView: NSViewRepresentable {
#Binding var player: AVPlayer
var titles:[Title] = []
public init(player:Binding<AVPlayer>,currentSeconds:Binding<Double>,subtitleFile:Binding<String>,currentTitle:Binding<Title?>)
{
self._player = player
}
public func updateNSView(_ NSView: NSView, context: NSViewRepresentableContext<PlayerView>) {
guard let view = NSView as? AVPlayerView else {
debugPrint("unexpected view")
return
}
// status = player.timeControlStatus.rawValue
view.player = player
}
public func makeNSView(context: Context) -> NSView {
let av = AVPlayerView(frame: .zero)
return av
}
}
I'v got a solution, to capture the keydown event, We can create a subclass from AVPlayerView and override it's keydown event.
I have the following code snippet:
struct player : UIViewControllerRepresentable {
var url : String
var player1: AVPlayer
func makeUIViewController(context: UIViewControllerRepresentableContext<player>) -> AVPlayerViewController {
let controller = AVPlayerViewController()
player1 = AVPlayer(url: URL(string: url)!)
controller.player = player1
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<player>) {
}
func pause() {
player1.pause()
}
}
This gives the error:
'Cannot assign to property: 'self' is immutable'
I need to have the AVPlayer outside of the makeUIViewController function because I need to access it from the pause function. How can I do this?
The error you are seeing appears due to the fact that structs are value types and any method on them that changes their properties needs to be marked as mutating. Unfortunately you cannot mark makeUIViewController because it is defined in UIViewControllerRepresentable protocol, but there is a fairly easy solution.
You only actually use the url to construct an AVPlayer - there is no need to hold on to it. Write and initializer for your struct that takes a url string and constructs an AVPlayer. I have made the AVPlayer optional as URL(string: String) returns an Optional URL (as you can imagine not all strings are valid urls). The below code works as expected:
struct Player: UIViewControllerRepresentable {
var player1: AVPlayer?
public init(url string: String) {
guard let url = URL(string: string) else {
self.player1 = nil
return
}
self.player1 = AVPlayer(url: url)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<Player>) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = player1
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController,
context: UIViewControllerRepresentableContext<Player>) {}
func pause() {
player1?.pause()
}
}
A side note: all type names in Swift (classes, structs, enums) are by convention upper-cased: your struct should be called Player not player.
You should also look into Cordinator for UIViewControllerRepresentable - you need something to act as your AVPlayerViewControllerDelegate.
There are several possibilities to choose from (actually it is not a complete list of options, but hope it would be helpful)
Option1: Do it in initailizer
struct player : UIViewControllerRepresentable {
private var url : String
private var player1: AVPlayer
init(url: String) {
self.url = url
self.player1 = AVPlayer(url: URL(string: url)!)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<player>) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = player1
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<player>) {
}
}
Option2: Do it on first request to update controller
struct player : UIViewControllerRepresentable {
var url : String
var player1: AVPlayer
func makeUIViewController(context: UIViewControllerRepresentableContext<player>) -> AVPlayerViewController {
return AVPlayerViewController()
}
func updateUIViewController(_ playerController: AVPlayerViewController, context: UIViewControllerRepresentableContext<player>) {
if playerController.player == nil {
playerController.player = AVPlayer(url: URL(string: url)!)
}
}
}
Option3: If you need to modify player1 during lifetime, then you need to keep it in some external entity, say ObservableObject, because your player is View struct and cannot modify itself, actually what is said in compiler error.