how to capture the keydown event of the AVPlayer? - swift

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.

Related

AVPlayer audio only works when ringer is on

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

How to redirect to external URL when AVPlayer finishs video playing

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

Swiftui avplayer handle stop video

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

How can I update what video is playing on AVPlayerLayer in SwiftUI?

I have a #State variable in my main ContentView called url that is the source to an mp4 file. How can I modify either PlayerView or VideoView (both below) so that when something causes url to change in ContentView, the VideoView updates itself to play the new video at url?
I feel like I am on the right track by adding a Coordinator in VideoView, but this is something I saw in Apple's tutorials, and I don't really understand how to use it.
PlayerView.swift
import UIKit
import AVKit
class PlayerView: UIView {
private let playerLayer = AVPlayerLayer()
private var playerLooper: AVPlayerLooper?
init(frame: CGRect, url: URL) {
super.init(frame: frame)
// Obtain asset and create an item from it
let asset = AVAsset(url: url)
let item = AVPlayerItem(asset: asset)
// Create the video player using the URL passed in.
let player = AVQueuePlayer()
// Add the player to our Player Layer
playerLayer.player = player
playerLayer.videoGravity = .resizeAspect // Resizes content to fill whole video layer.
playerLayer.backgroundColor = UIColor.black.cgColor
layer.addSublayer(playerLayer)
// Create new player looper
playerLooper = AVPlayerLooper(player: player, templateItem: item)
// Start the movie
player.volume = 0
player.play()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
}
VideoView
(wrapper)
import SwiftUI
import AVKit
struct VideoView: UIViewRepresentable {
#Binding var videoURL: URL
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UIView {
return PlayerView(frame: .zero, url: videoURL)
}
func updateUIView(_ playerView: UIView, context: Context) {
}
class Coordinator: NSObject {
var parent: VideoView
init(_ videoView: VideoView) {
self.parent = videoView
}
}
}

Embedding videos in a SwiftUI view

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