简体   繁体   中英

Change view on video end in SwiftUI

I get an error when trying to find the end time of the video

import SwiftUI
import AVKit

struct ContentViewC: View {

    
    private let player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "videoToPlay", ofType: "mp4")!))
    
    // throw multiple errors
    NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: nil)

    func playerDidFinishPlaying(note: NSNotification) {
        print("video finished")
        // how to change view here?
    }
    
    
    var body: some View {
        
        VStack {
            AVPlayerControllerRepresented(player: player)
                .edgesIgnoringSafeArea(.all)
                .onAppear {
                    player.play()
                }
                .onDisappear {
                    player.pause()
                    player.seek(to: .zero)
                }
        }
        .background(Color(.black))
        .edgesIgnoringSafeArea(.all)
    }
}

Once that is resolved, how do I change the view in the func playerDidFinishPlaying?

My app has this 'HomeView' which has three buttons, when clicked, they navigate to a new View which will play video. On Video end in the new view, I want to navigate back to this 'HomeView'

import SwiftUI

struct ContentView: View {
    
    
    var body: some View {
        
        let width: CGFloat = 400
        let height: CGFloat = 400
        
        let txtWidth: CGFloat = 300
        let txtHeight: CGFloat = 100
        
            
        NavigationView {
            ZStack {
                Image("app_bg3")
                    .resizable()
                    .edgesIgnoringSafeArea(.all)
            VStack {
                ZStack {
                    NavigationLink(destination: ContentViewC()) {
                        HStack(alignment: .bottom) {
                            Spacer()
                            
                            VStack  {
                                VStack {
                                    Image("OneCirclePlayBtn")
                                        .resizable()
                                        .aspectRatio(contentMode: .fill)
                                        .frame(width: width, height: height)
                                    
                                    Text("The")
                                        .font(Font.custom("Nunito-Regular", size: 43))
                                        .font(.largeTitle)
                                        .fontWeight(.bold)
                                        .foregroundColor(Color(.white))
                                        .frame(width: txtWidth, height: txtHeight, alignment: .center)
                                    
                                    Text("Voice")
                                        .font(Font.custom("Nunito-Regular", size: 43))
                                        .font(.largeTitle)
                                        .fontWeight(.bold)
                                        .foregroundColor(Color(.white))
                                        .frame(width: txtWidth, height: txtHeight, alignment: .center)
                                        .offset(y: -50)
                                }
                            }
                            Spacer()
                        }
                    }
                }.offset(y: 100)
                HStack(spacing: 350) {
                    NavigationLink(destination: ContentViewA()) {
                        VStack {
                            VStack {
                               Image("TwoCirclePlayBtn")
                                .resizable()
                                .aspectRatio(contentMode: .fill)
                                .frame(width: width, height: height)
                                
                                Text("The Cast")
                                    .font(Font.custom("Nunito-Regular", size: 33))
                                    .font(.largeTitle)
                                    .fontWeight(.bold)
                                    .foregroundColor(Color(.white))
                                    .frame(width: txtWidth, height: txtHeight, alignment: .center)
                            }
                        }
                    }
                    NavigationLink(destination: ContentViewB()) {
                        VStack {
                            VStack {
                                Image("ThreeCirclePlayBtn")
                                    .resizable()
                                    .aspectRatio(contentMode: .fill)
                                    .frame(width: width, height: height, alignment: .center)



                                Text("The Artist")
                                    .font(Font.custom("Nunito-Regular", size: 33))
                                    .font(.largeTitle)
                                    .fontWeight(.bold)
                                    .foregroundColor(Color(.white))
                                    .frame(width: txtWidth, height: txtHeight, alignment: .center)
                                }
                            }
                        }
                    }.offset(y: -150)
                }
            }
        }.navigationViewStyle(StackNavigationViewStyle())
        .accentColor(.white)
        .statusBar(hidden: true)
        .background(StatusBarHideHelperView())
    }
}


class StatusBarHideHelper: UIViewController {
    override var prefersStatusBarHidden: Bool { true }    // << important !!
}

struct StatusBarHideHelperView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController {
        StatusBarHideHelper()
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
    }
}
class PlayerViewModel : ObservableObject {
    @Published var videoDone = false
    
    let player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "Early Riser", ofType: "mp4")!))
    
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime)
            .sink { (_) in
                self.videoDone = true
                print("Video done")
                DispatchQueue.main.async {
                    self.videoDone = false //reset it on the next run loop
                }
            }.store(in: &cancellables)
    }
}

struct PlayerContentView: View {

    @ObservedObject var viewModel = PlayerViewModel()
    
    var body: some View {
        VStack {
            AVPlayerControllerRepresented(player: viewModel.player)
                .edgesIgnoringSafeArea(.all)
                .onAppear {
                    viewModel.player.play()
                }
                .onDisappear {
                    viewModel.player.pause()
                    viewModel.player.seek(to: .zero)
                }
            if viewModel.videoDone {
                NavigationLink(destination: DetailView(), isActive: $viewModel.videoDone) {
                    EmptyView()
                }
            }
        }
        .background(Color(.black))
        .edgesIgnoringSafeArea(.all)
    }
}

struct DetailView : View {
    var body: some View {
        Text("Detail")
    }
}


struct ContentView: View {
    var body: some View {
        NavigationView {
            PlayerContentView()
        }.navigationViewStyle(StackNavigationViewStyle())
    }
}

I'm making the assumption here that you want to navigate using a NavigationView , but I can edit the answer if that's not the case.

What's going on here:

  1. I moved the player and its 'done' state to an ObservableObject -- that way, I have a little more control of how I deal with the notification.

  2. Upon receiving the AVPlayerItemDidPlayToEndTime notification, I set videoDone to true

  3. When videoDone is true, I render a NavigationLink in the view hierarchy, with isActive set to videoDone

  4. Note that this will not work if your view isn't embedded in a NavigationView (see my ContentView ) -- the NavigationLink won't do anything if it exists outside of that hierarchy.

Update : In the event that you want to go back and not to a new View, the following would work, using presentationMode (same PlayerViewModel as before):

struct PlayerContentView: View {

    @ObservedObject var viewModel = PlayerViewModel()
    @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
    
    var body: some View {
        VStack {
            AVPlayerControllerRepresented(player: viewModel.player)
                .edgesIgnoringSafeArea(.all)
                .onAppear {
                    viewModel.player.play()
                }
                .onDisappear {
                    viewModel.player.pause()
                    viewModel.player.seek(to: .zero)
                }
        }
        .background(Color(.black))
        .edgesIgnoringSafeArea(.all)
        .onReceive(viewModel.$videoDone) { (videoDone) in
            if videoDone {
                presentationMode.wrappedValue.dismiss()
            }
        }
    }
}
  • I updated the answer completely after the explanation in the comment

Your view should be like this;

struct PlayerContentView: View {

@State var isVideoPlayed = false
let playerDidFinishNotification = NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime)

private let player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "videoToPlay", ofType: "mp4")!))

var body: some View {
    
    VStack {
        AVPlayerControllerRepresented(player: player)
            .edgesIgnoringSafeArea(.all)
            .onAppear {
                player.play()
            }
            .onDisappear {
                player.pause()
                player.seek(to: .zero)
            }
    }
    .background(Color(.black))
    .edgesIgnoringSafeArea(.all)
    .onReceive(playerDidFinishNotification, perform: { _ in
        isVideoPlayed = true
        NavigationLink("Content View", destination: ContentView(), isActive: $isVideoPlayed)
    })
 }
}

Instead of navigation link , you can also use, present or sheet

ps you should replace PlayerContentView with ContentViewC

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM