iKK
iKK

Reputation: 7012

VideoPlayer in SwiftUI stops playing when parent-View updates

Using Swift5.3.2, iOS14.4.1, Xcode 12.4,

I am successfully running a VideoPlayer in SwiftUI.

I am calling the Player view with this code: VideoPlayer(player: AVPlayer(url: url)).

The problem is that the video stops playing whenever a parent-View of the VideoPlayer updates (i.e. re-renders).

Since in SwiftUI I don't have any control over when such a re-render moment takes place, I don't know how to overcome this problem.

Any ideas ?

Here is the entire Code:

The VideoPlayer View is called as such:

struct MediaTabView: View {
    
    @State private var url: URL
    
    var body: some View {
        
        // CALL TO VIDEOPLAYER IS HERE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        VideoPlayer(player: AVPlayer(url: url))
    }
}

The MediaTabView is called as such:

import SwiftUI

struct PageViewiOS: View {
    
    var body: some View {
        
        ZStack {
            
            Color.black
            
            // CALL TO MEDIATABVIEW IS HERE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!d
            MediaTabView(url: URL(string: "https://someurel.com"))
                
            CloseButtonView()
        }
    }
}

The PageViewiOS View is called as such:

struct MainView: View {
    
    @EnvironmentObject var someState: AppStateService
    
    var body: some View {
        NavigationView {
            Group {                                                    
                if someState = .stateOne {

                    // CALL TO PAGEVIEWIOS IS HERE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                    PageViewiOS()
                } else {
                    Text("hello")
                }
            }
        }
    }
}

Upvotes: 0

Views: 2355

Answers (4)

Jim Bengtsson
Jim Bengtsson

Reputation: 1

I have done a simplified version of the accepted answer without the need to have a view model.

private struct CustomPlayerView: View {
    @State private var avPlayer: AVPlayer
    
    init(url: URL) {
        _avPlayer = State(initialValue: AVPlayer(url: url))
    }

    var body: some View {
        VideoPlayer(player: avPlayer)
    }
}

which can be used in the same way.

Upvotes: 0

iKK
iKK

Reputation: 7012

With the solution from @jnpdx, everything works now.

Here is the final solution (full credit to @jnpdx):

import SwiftUI
import AVKit

class PlayerViewModel: ObservableObject {
    
    @Published var avPlayer: AVPlayer?
    
    func loadFromUrl(url: URL) {
        avPlayer = AVPlayer(url: url)
    }
}

struct CustomPlayerView: View {
    
    var url : URL
    @StateObject private var playerViewModel = PlayerViewModel()

    var body: some View {
        ZStack {
            if let avPlayer = playerViewModel.avPlayer {
                VideoPlayer(player: avPlayer)
            }
        }.onAppear {
            playerViewModel.loadFromUrl(url: url)
        }
    }
}

With that in hand, it is enough to call the CustomPlayerVideo like that:

CustomPlayerView(url: url)

Remark: I needed to use ZStack instead of Group in my CustomPlayerView in order for it to work.

Upvotes: 2

jnpdx
jnpdx

Reputation: 52565

This is in response to our comment thread on the other answer:

class PlayerViewModel: ObservableObject {
    
    @Published var avPlayer: AVPlayer?
    
    func loadFromUrl(url: URL) {
        avPlayer = AVPlayer(url: url)
    }
}

struct CustomPlayerView: View {
    
    var url : URL
    @StateObject private var playerViewModel = PlayerViewModel()

    var body: some View {
        ZStack {
            if let avPlayer = playerViewModel.avPlayer {
                VideoPlayer(player: avPlayer)
            }
        }.onAppear {
            playerViewModel.loadFromUrl(url: url)
        }
    }
}

I'm not sure that this is definitively better, so it's worth testing. But, it does control when AVPlayer gets created and avoids re-creating PlayerViewModel on every render of the parent as well.

Upvotes: 0

iKK
iKK

Reputation: 7012

I have found a solution.

Call the following :

CustomPlayerView(url: url)

...instead of :

VideoPlayer(player: AVPlayer(url: url))

Not sure why this works, tough.

Maybe somebody can explain further ?

Here is the CustomVideoPlayer code:

struct CustomPlayerView: View {

    private let url: URL

    init(url: URL) {
        self.url = url
    }

    var body: some View {
        VideoPlayer(player: AVPlayer(url: url))
    }
}

With this minor change, the Video keeps on playing even tough the parent-View gets re-rendered. Still, not sure why ???

----------- Answer with the hint of @jnpdx --------

I changed the CustomVideoPlayer even more :

CustomPlayerView(playerViewModel: PlayerViewModel(avPlayer: AVPlayer(url: url)))
import SwiftUI
import AVKit

class PlayerViewModel: ObservableObject {
    
    @Published var avPlayer: AVPlayer
    
    init(avPlayer: AVPlayer) {
        self.avPlayer = avPlayer
    }
}

struct CustomPlayerView: View {
    
    @StateObject var playerViewModel: PlayerViewModel

    var body: some View {
        VideoPlayer(player: playerViewModel.avPlayer)
    }
}

Upvotes: 0

Related Questions