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) {
}
}
Related
With SwiftUI, I have a custom avplayer that auto plays and loops the video. The problem is whether or not I specifically tell avplayer to unmute, it is still muted. The physical volume buttons have no effect. The only way to toggle mute on/off is to physically switch the ringer to silent (muted) or not silent (unmute).
Here is the parent view:
struct VideoCacheView: View {
#State private var avPlayer: AVPlayer? = nil
public let url: String
public let thumbnailURL: String
var body: some View {
if self.avPlayer != nil {
CustomVideoPlayer(player: Binding(self.$avPlayer)!)
.onAppear {
self.avPlayer?.isMuted = false
self.avPlayer?.play()
}
}
}
}
and the child:
struct CustomVideoPlayer: UIViewControllerRepresentable {
#EnvironmentObject var cvm: CameraViewModel
#Binding var player: AVPlayer
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeUIViewController(context: Context) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = self.player
controller.showsPlaybackControls = false
controller.videoGravity = self.cvm.videoGravity
player.actionAtItemEnd = .none
NotificationCenter.default.addObserver(context.coordinator, selector: #selector(context.coordinator.restartPlayback), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) { }
class Coordinator: NSObject {
public var parent: CustomVideoPlayer
init(_ parent: CustomVideoPlayer) {
self.parent = parent
}
#objc func restartPlayback () {
self.parent.player.seek(to: .zero)
}
}
}
Why is the only volume control my avplayer has is with the physicaly silent switch?
https://developer.apple.com/documentation/avfoundation/avplayer/1390127-volume
Turns out that the volume is set to 0.0 when ringer is in silent mode. By setting the volume to 1.0 by default, there is volume all the time.
Added this:
self.player?.volume = 1.0
inside of the child view below the videoGravity line
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'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.
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}"))
)
}
}