Flickering happening while video is played

110 views Asked by At

I'm working on an app where we store videos on Firebase, fetch the url, and then use the AVPlayer initializer that takes in a url to display the video player. I've been using SwiftUI to build out the views. I have a sheet that is presented and on this sheet we have a play button, when they select this play button, we present the video player as a sheet. For some reason, some users are experiencing a flickering throughout the video playing. There doesn't seem to be a correlation as far as the OS version or devices of the affected users from what I can tell. I've used the new VideoPlayer in SwiftUI and I've also used the AVPlayerViewController. I myself cannot reproduce this myself on the simulator or my device (iPhone 14 Pro) which makes it difficult to resolve this issue.

Here is the code for the view presenting the player:

import SwiftUI
import FirebaseStorage
import _AVKit_SwiftUI

struct WorkoutResourceListView: View {
    @StateObject var viewModel: WorkoutResourceListViewModel
    @Environment(\.dismiss) private var dismiss
    let exerciseImage: UIImage
    
    var body: some View {
        GeometryReader { geo in
            ScrollView {
                ZStack {
                    Image(uiImage: exerciseImage)
                        .resizable()
                        .scaledToFit()
                        .frame(width: geo.size.width)
                    
                    VStack {
                        HStack {
                            Button {
                                dismiss()
                            } label: {
                                Image("arrow-down")
                                    .resizable()
                                    .frame(width: 24, height: 24)
                                    .scaledToFit()
                            }
                            Spacer()
                        }
                        Spacer()
                    }
                    .padding([.top, .leading], 16)
                }
                
                VStack(spacing: 0) {
                    Text(viewModel.exercise.name)
                        .font(Font.custom("SF Pro Text", size: 20).weight(.semibold))
                        .foregroundColor(.black)
                        .padding(.top, 4)
                        .frame(maxWidth: .infinity, alignment: .leading)
                    
                    Text(viewModel.resources.first?.caption ?? "With your feet centered with width of your shoulders, keep your chest out and neck straight looking forward. Then with your arms straight, dip down so your knees come to a 90 degree angle")
                        .font(Font.custom("SF Pro Text", size: 17))
                        .foregroundColor(Color(red: 0.43, green: 0.43, blue: 0.43))
                        .frame(maxWidth: .infinity, alignment: .topLeading)
                        .padding(.top, 16)
                    
                    Button {
                        viewModel.playVideo(resourceId: viewModel.resources.first?.videoId ?? "")
                    } label: {
                        ZStack {
                            Text(viewModel.isBuffering ? "" : "Play")
                                .frame(maxWidth: .infinity)
                                .frame(height: 58)
                                .font(.system(size: 17, weight: .bold))
                                .foregroundColor(.white)
                                .background(Color(red: 52/255, green: 52/255, blue: 52/255))
                                .cornerRadius(14)
                                .padding([.top, .bottom], 16)
                            if viewModel.isBuffering {
                                ActivityIndicator(isAnimating: $viewModel.isBuffering) { indicator in
                                    indicator.color = .white
                                    indicator.hidesWhenStopped = true
                                }
                            }
                        }
                    }
                    
                    if viewModel.resources.count > 1 {
                        Text("Alternative Options")
                            .font(Font.custom("SF Pro Text", size: 15))
                            .foregroundColor(Color(red: 0.62, green: 0.62, blue: 0.62))
                            .frame(maxWidth: .infinity, alignment: .leading)
                            .padding(.top, 16)
                        
                        ForEach(1...viewModel.resources.count - 1, id: \.self) { index in
                            ResourceRow(viewModel: viewModel, index: index, exerciseImage: exerciseImage)
                        }
                    }
                }
                .padding()
                .sheet(isPresented: $viewModel.presentVideoPlayer) {
                    PlayerViewController(videoURL: viewModel.videoUrl)
//                        VideoPlayer(player: viewModel.videoPlayer)
//                            .onAppear{
//                                if viewModel.videoPlayer.currentItem == nil {
//                                    let item = AVPlayerItem(url: viewModel.videoUrl!)
//                                    viewModel.videoPlayer.replaceCurrentItem(with: item)
//                                }
//                                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
//                                    viewModel.videoPlayer.play()
//                                })
//                            }
//                            .onDisappear {
//                                viewModel.videoPlayer.pause()
//                            }
                }
            }
        }
    }
}

Here is the code for the AVPlayerViewController:

struct PlayerViewController: UIViewControllerRepresentable {
    var videoURL: URL?

    private var player: AVPlayer {
        return AVPlayer(url: videoURL!)
    }

    func makeUIViewController(context: Context) -> AVPlayerViewController {
        try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient)
        try? AVAudioSession.sharedInstance().setActive(true)
        let controller = AVPlayerViewController()
        controller.modalPresentationStyle = .fullScreen
        controller.player = player
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
            controller.player?.play()
        }
        
        return controller
    }

    func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {}
}

Here is the view model:

import Foundation
import AVFoundation
import AVKit
import FirebaseStorage

class WorkoutResourceListViewModel: ObservableObject {
    @Published var presentVideoPlayer = false
    @Published var presentExerciseResource = false
    @Published var isBuffering = false
    //var videoPlayer = AVPlayer()
    var resources: [Resource]
    var exercise: ExerciseTableViewDataContent
    var videoUrl: URL?
    var selectedExerciseImage: UIImage?
    var selectedResource: [Resource] = []
    
    func playVideo(resourceId: String) {
        isBuffering = true
        fetchVideo(videoId: resourceId) { url in
            if let url = url {
                self.videoUrl = url
                self.presentVideoPlayer = true
            }
            self.isBuffering = false
        }
    }
    
    private func fetchVideo(videoId: String, completion: @escaping(URL?) -> Void) {
        let fileManager = FileManager.default
        let documentDirectory = try! fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
        let rootDir = Keys.StorageBaseDirectories.exerciseVideos
        let localFile = documentDirectory.appendingPathComponent("\(videoId).mov")
        let storageRef = Storage.storage().reference(withPath: rootDir + "\(videoId).mov")
        storageRef.write(toFile: localFile) { (url, error) in
            if let error = error {
                print(error.localizedDescription)
                completion(nil)
            } else if let url = url {
                completion(url)
            }
        }
    }
}

As you can see, the SwiftUI VideoPlayer code is provided so you can see how I was handling both AVPlayerViewController and VideoPlayer. Any idea on what could be causing this flickering?

I've tried using the Dispatch asyncAfter method, basically adding a delay to the start of the video player to see if that could help.

What I'm expecting is the video to not flicker for any user.

0

There are 0 answers