简体   繁体   English

在 SwiftUI 中更改视频端的视图

[英]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?解决后,如何更改 func playerDidFinishPlaying 中的视图?

My app has this 'HomeView' which has three buttons, when clicked, they navigate to a new View which will play video.我的应用程序有这个“HomeView”,它有三个按钮,当点击时,它们导航到一个将播放视频的新视图。 On Video end in the new view, I want to navigate back to this 'HomeView'在新视图的视频结束时,我想导航回这个“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.我在这里假设您想使用NavigationView进行导航,但如果不是这种情况,我可以编辑答案。

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.我将播放器及其“完成”的 state 移动到一个ObservableObject ——这样,我对如何处理通知有了更多的控制。

  2. Upon receiving the AVPlayerItemDidPlayToEndTime notification, I set videoDone to true收到AVPlayerItemDidPlayToEndTime通知后,我将videoDone设置为 true

  3. When videoDone is true, I render a NavigationLink in the view hierarchy, with isActive set to videoDonevideoDone为 true 时,我在视图层次结构中呈现NavigationLink ,并将isActive设置为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.请注意,如果您的视图没有嵌入到NavigationView中(请参阅我的ContentView ),这将不起作用 - 如果NavigationLink存在于该层次结构之外,它将不会做任何事情。

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):更新:如果您想返回go 而不是新视图,则使用presentationMode (与以前相同的PlayerViewModel)可以使用以下方法:

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 ps 你应该用ContentViewC替换PlayerContentView

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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