VideoPlayer Autoplay SwiftUI Issue - swift

I am trying to get the VideoPlayer to autoplay with the video controller but I can't seem to figure it out! What should I do? I am fairly new to SwiftUI.
struct VideoView: View {
// MARK: - PROPERTIES
//: BINDING
#Binding var videoView: Bool
//: STATE
#State var player = AVPlayer()
#State var isplaying = true
#State var showcontrols = true
//: VAR
var allowsPictureInPicturePlayback : Bool = true
var wrkflw: Wrkflw
// MARK: - BODY
var body: some View {
VideoPlayer(wrkflw: wrkflw, player: $player)
.edgesIgnoringSafeArea(.all)
.onAppear {
player.play()
}
}
}
MARK: - VIDEO CONTROLLER
struct VideoPlayer : UIViewControllerRepresentable {
var wrkflw: Wrkflw
#Binding var player : AVPlayer
func makeUIViewController(context: Context) -> UIViewController {
let player = AVPlayer(url: wrkflw.wrkvideo)
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = true
controller.exitsFullScreenWhenPlaybackEnds = true
controller.allowsPictureInPicturePlayback = true
return controller
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}

The simplest solution would be to just pass the AVPlayer reference from your VideoView to the VideoPlayer. You can use player.replaceCurrentItem so that you can set the correct URL but maintain the same player reference.
Make sure you remove the let player... lines in VideoPlayer
struct VideoView: View { // MARK: - PROPERTIES
//: BINDING
#Binding var videoView: Bool
//: STATE
#State var player = AVPlayer()
#State var isplaying = true
#State var showcontrols = true
//: VAR
var allowsPictureInPicturePlayback : Bool = true
var wrkflw: Wrkflw
// MARK: - BODY
var body: some View {
VideoPlayer(wrkflw: wrkflw, player: player)
.edgesIgnoringSafeArea(.all)
.onAppear {
player.replaceCurrentItem(with: AVPlayerItem(url: wrkflw.wrkvideo)) //<-- Here
player.play()
}
}
}
// MARK: - VIDEO CONTROLLER
struct VideoPlayer : UIViewControllerRepresentable {
var wrkflw: Wrkflw
var player : AVPlayer
func makeUIViewController(context: Context) -> UIViewController {
//<-- Here
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = true
controller.exitsFullScreenWhenPlaybackEnds = true
controller.allowsPictureInPicturePlayback = true
return controller
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}

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

Method `enumerateChildNodes` is not finding nodes

I'm developing an ARKit app that has 2 buttons: "Play" and "Reset". When I click on Play, a pyramid node is displayed. When I click on Reset, all pyramid nodes should be deleted from the scene:
Another requirement is that I want to disable the Play button when I click on it.
Next is my contentView code:
import ARKit
struct ContentView: View {
#State var node = SCNNode()
#State var play: Bool = false
#State var reset: Bool = false
#State var isDisabled: Bool = false
#State var showBanner:Bool = true
var body: some View {
ZStack {
SceneKitView(node: $node, play: $play, reset: $reset, isDisabled: $isDisabled)
VStack {
Spacer()
HStack {
Button(action: {
play = true
}) {
Image("Play")
}.padding()
.disabled(isDisabled) /// <<<<< THIS DISABLES THE BUTTON
Spacer()
Button(action: {
play = false
reset = true
}) {
Image("Reset")
}.padding()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In order to disable the Play button, I'm using a .disabled and the Boolean isDisabled.
My code changes the value of isDisabled to TRUE in a Coordinator :
import ARKit
import SwiftUI
struct SceneKitView: UIViewRepresentable {
let arView = ARSCNView(frame: .zero)
let config = ARWorldTrackingConfiguration()
#Binding var node: SCNNode
#Binding var play: Bool
#Binding var reset: Bool
#Binding var isDisabled: Bool
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, ARSCNViewDelegate {
var control: SceneKitView
init(_ control: SceneKitView) {
self.control = control
}
func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
DispatchQueue.main.async {
if self.control.play {
self.control.addNode()
self.control.play = false
self.control.isDisabled = true // HERE I UPDATE THE VALUE !!!!!!!!
self.control.reset = false
}else{
}
}
}
}
func removeNode(){
self.arView.scene.rootNode.enumerateChildNodes { (node, _) in
print("existing nodes = \(node)")
node.removeFromParentNode()
}
}
func updateUIView(_ uiView: ARSCNView,
context: Context) {
if self.reset {
self.removeNode()
print("game reseted")
}
}
func makeUIView(context: Context) -> ARSCNView {
arView.scene = SCNScene()
arView.delegate = context.coordinator
arView.autoenablesDefaultLighting = true
//arView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
arView.showsStatistics = true
arView.session.run(self.config)
return arView
}
func addNode(){
let pyramid = SCNNode(geometry: SCNPyramid(width: 0.1, height: 0.1, length: 0.1))
pyramid.geometry?.firstMaterial?.diffuse.contents = UIColor.red
pyramid.geometry?.firstMaterial?.specular.contents = UIColor.white
pyramid.position = SCNVector3(0,0,-0.5)
self.arView.scene.rootNode.addChildNode(pyramid)
}
}
Problem: When the app is running and I click on RESET, the method removeNode is invoked and this method uses enumerateChildNodes to find the nodes and delete them using removeFromParentNode but the pyramid node is not removed ! :(
The crazy thing (for me) is that if I don't change the value of isDisabled in the Coordinator, i.e. I comment that line, removeNode works, and the node is removed !!!!!
Any comment, suggestion is welcome :)
This solves the issue:
func updateUIView(_ uiView: ARSCNView, context: Context) {
if self.reset {
DispatchQueue.main.async {
context.coordinator.control.removeNode()
context.coordinator.control.isDisabled = false
}
print("Game Reset")
}
}

Can't pause AVPlayer with custom button

I am trying to make a detailed view in swift, but I just can't figure out a way to pause the video with a custom button. And also when I go back to my list I can still hear the video playing in the background. Here is my code for the AVPlayer and for the button.
import SwiftUI
import AVKit
struct Workdetail: View {
var work: WorkoutDe
#State var player = AVPlayer()
#State var isplaying = true
var body: some View {
VStack {
ZStack {
VideoPlayer(player: $player, work: work)
.frame(height: UIScreen.main.bounds.height / 3.5)
Butto(player: $player, isplaying: $isplaying)
}
Spacer()
}
}
}
struct Butto : View {
#Binding var player : AVPlayer
#Binding var isplaying : Bool
var body : some View {
Button(action: {
if self.isplaying {
self.player.pause()
self.isplaying = false
} else {
self.player.play()
self.isplaying = true
}
}) {
Image(systemName: self.isplaying ? "pause.fill" : "play.fill")
.font(.title)
.foregroundColor(.white)
.padding(20)
}
}
}
struct VideoPlayer : UIViewControllerRepresentable {
var work : WorkoutDe
#Binding var player : AVPlayer
var playerLayer = AVPlayerLayer()
public func makeUIViewController(context: Context) -> AVPlayerViewController {
player = AVPlayer(url: URL(fileURLWithPath: String(work.url)))
let controller = AVPlayerViewController()
controller.player = player
controller.videoGravity = .resizeAspectFill
player.actionAtItemEnd = .none
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player.currentItem, queue: .main) { _ in
player.seek(to: CMTime.zero)
player.play()
}
player.play()
return controller
}
func rewindVideo(notification: Notification) {
playerLayer.player?.seek(to: .zero)
}
public func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<VideoPlayer>) {
}
}
The AVPlayer works but when I press the button nothing happens. The image for the button changes but the video won't stop playing. Can someone please explain to me how I can bind the button, because I can't figure it out
You work with different players in your sub views, try
public func makeUIViewController(context: Context) -> AVPlayerViewController {
let player = AVPlayer(url: URL(fileURLWithPath: String(work.url)))
let controller = AVPlayerViewController()
DispatchQueue.main.async {
self.player = player
}
binding should update parent state, which will update Butto, so it should work.

In SwiftUI how can I animate a Published View change?

I would like to animate a View change.
In particular, I have done a View where one of its children is a changing View.
struct MasterView: View {
#State var playerLeft: Bool = false
#ObservedObject var viewModel: MasterViewModel
var body: some View {
ZStack {
WaitPlayerBackView(isShowing: self.$playerLeft) {
self.viewModel.currentView
}
}
}
}
The view model is an ObservableObject class. currentView is changed by a method called from an async thread, like this:
class MasterViewModel: ObservableObject {
#Published var currentView: AnyView = AnyView(EmptyView())
func changeView() {
self.currentView = AnyView(NightView())
}
}
It's like a copy of Android fragments (pardon the comparison).
How can I animate this View change?
I have tried different options like:
self.viewModel.currentView.animate(.default)
or
struct MasterView: View {
#State var playerLeft: Bool = false
#ObservedObject var viewModel: MasterViewModel
var body: some View {
ZStack {
WaitPlayerBackView(isShowing: self.$playerLeft) {
self.viewModel.currentView
}
}.animate(self.viewModel.animate ? .easeIn(duration: 1) : .none)
}
}
class MasterViewModel: ObservableObject {
#Published var currentView: AnyView = AnyView(EmptyView())
#Published var animate = false
func changeView() {
self.animate = false
self.currentView = AnyView(NightView())
self.animate = true
}
}
However, none of them worked.
This is how I see a transition when changing the instance of currentView.
Moving your view initialization to MasterView makes everything easier:
struct MasterView: View {
#State var playerLeft: Bool = false
#ObservedObject var viewModel: MasterViewModel
var body: some View {
ZStack {
VStack { //this represents WaitPlayerBackView
if viewModel.day { //here are the posibles views that self.viewModel.currentView could have
Text("DayView").transition(AnyTransition.opacity.animation(.easeInOut(duration: 1.0)))
} else {
Text("NightView").transition(AnyTransition.opacity.animation(.(duration: 1.0)))
}
}
}
}
}
class MasterViewModel: ObservableObject {
#Published var day: Bool = false
init () {
_ = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(changeView), userInfo: nil, repeats: true) //Only for testing
}
#objc func changeView() { //#objc is only for testing
day.toggle()
}
}

swiftui dealloc and realloc AVplayer with many videos

I have many simultaneous videos. Through a Int var (var test1 and var test2) I would like to be able to add only a certain video and remove all the others so as not to have memory problems
As soon as the view is loaded, the value "nil" is assigned to each player and when test1 == test 2 it should load the video in a certain player and in the other "nil"
The problem is that despite being the testing variable in binding (struct VideoPlayer #Binding var testing) it does not update the state of the player which always remains in Nil
Below the best solution I have obtained so far
Some idea? Thank you all
struct CustomPlayer: View {
#Binding var test1:Int
#Binding var test2:Int
#State var path:String
#State var testing:AVPlayer? = nil
var body: some View {
if(test1 == test2 ) {
self.testing? = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "\(path)", ofType: "mp4")!) )
self.testing?.play()
} else {
self.testing?.replaceCurrentItem(with: nil)
}
return ZStack{
VideoPlayer(testing: self.$testing)
}
struct VideoPlayer : UIViewControllerRepresentable {
#Binding var testing : AVPlayer?
func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPlayer>) -> AVPlayerViewController {
let controller = AVPlayerViewController()
controller.player = testing
controller.showsPlaybackControls = false
controller.view.backgroundColor = UIColor.white
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<VideoPlayer>) {
}
}
Here an example how to load multiple videos simultaneous (i added an autoplay feuature), and remove all other, but not the selected one.
To remove videos tap on video you want to save
And i'm not sure that i solve your initial problem, but this can give you a clue where to look next
Code can be copy/pasted, as i declare it in one file to convenient stackoverflow use
import SwiftUI
import AVKit
struct VideoModel: Identifiable {
let id: Int
let name: String
let type: String = ".mp4"
}
final class VideoData: ObservableObject {
#Published var videos = [VideoModel(id: 100, name: "video"),
VideoModel(id: 101, name: "wow"),
VideoModel(id: 102, name: "okay")]
}
//Multiple item player
struct MultipleVideoPlayer: View {
#EnvironmentObject var userData: VideoData
var body: some View {
VStack(alignment: .center, spacing: 8) {
ForEach(userData.videos) { video in
VideoPlayer(video: .constant(video))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 250, maxHeight: 250, alignment: .center)
}
}
}
}
//Single item player
struct VideoPlayer : UIViewControllerRepresentable {
#EnvironmentObject var userData: VideoData
#Binding var video: VideoModel
func makeUIViewController(context: Context) -> AVPlayerViewController {
guard let path = Bundle.main.path(forResource: video.name, ofType: video.type) else {
fatalError("\(video.name)\(video.type) not found")
}
let url = URL(fileURLWithPath: path)
let playerItem = AVPlayerItem(url: url)
context.coordinator.player = AVPlayer(playerItem: playerItem)
context.coordinator.player?.isMuted = true
context.coordinator.player?.actionAtItemEnd = .none
NotificationCenter.default.addObserver(context.coordinator,
selector: #selector(Coordinator.playerItemDidReachEnd(notification:)),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: context.coordinator.player?.currentItem)
let controller = AVPlayerViewController()
controller.player = context.coordinator.player
controller.showsPlaybackControls = false
controller.view.backgroundColor = UIColor.white
controller.delegate = context.coordinator
let tapRecognizer = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.playerDidTap))
controller.view.addGestureRecognizer(tapRecognizer)
return controller
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
uiViewController.player?.play()
}
func makeCoordinator() -> Coordinator {
let coord = Coordinator(self)
return coord
}
class Coordinator: NSObject, AVPlayerViewControllerDelegate {
var parent: VideoPlayer
var player: AVPlayer?
init(_ playerViewController: VideoPlayer) {
self.parent = playerViewController
}
#objc func playerItemDidReachEnd(notification: NSNotification) {
if let playerItem: AVPlayerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: CMTime.zero, completionHandler: nil)
}
}
#objc func playerDidTap(){
parent.userData.videos = parent.userData.videos.filter { videoItem in
return videoItem.id == parent.video.id
}
}
}
}
//preview
struct AnotherEntry_Previews: PreviewProvider {
static var previews: some View {
MultipleVideoPlayer()
}
}
And in 'SceneDelegate.swift' replace app entry point with
window.rootViewController = UIHostingController(rootView: MultipleVideoPlayer().environmentObject(VideoData()))
The key thing of this move is to have "VideoData" resource, you can achieve it with EnvironmentObject, or with some other shared data example
Videos below i added to project, including them to Target Membership of a project
#Published var videos = [VideoModel(id: 100, name: "video"),
VideoModel(id: 101, name: "wow"),
VideoModel(id: 102, name: "okay")]
Your code seems to be ok. Are you sure the AVPlayer(...) path is correct
and testing is not nil. Put
.....
self.testing?.play()
print("-----> testing: \(testing.debugDescription)")
is testing = nil at that point?