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.