iosswiftshareplay

How to make a custom AVPlayer support SharePlay adjust coordinate or play-pause control


This app was written using SwiftUI and I also consulted Apple's official documentation and some third-party websites. So I wrote something like the following code

This is the Swift code where the player is called and the SharePlay-related logic

import SwiftUI
import AVFoundation
import AVKit
import GroupActivities
import Combine

struct EpisodeDetail: View {
    @State var episode: CommonResponse<Episode>
    @State var videoPlayer: CustomVideoPlayer
    @State private var groupSession: GroupSession<EpisodeActivity>?
    @State private var subscriptions = Set<AnyCancellable>()
    
    var body: some View {
        VStack {
            videoPlayer
                .transition(.move(edge: .bottom))
                .edgesIgnoringSafeArea(.all)
            
            ScrollView {
                VStack(alignment: .center) {
                    Button {
                        prepareToPlay(episode.attributes)
                    } label: {
                        Label("同播共享", systemImage: "shareplay")
                    }
                    .buttonStyle(.bordered)
                    .cornerRadius(20)
                }
                
                VStack(alignment: .leading, spacing: 20) {
                    Text(episode.attributes.title)
                        .font(.title2)
                        .fontWeight(.bold)
                    
                    Text(episode.attributes.description)
                        .font(.body)
                        .foregroundColor(.gray)
                }
            }
            .padding(12)
        }
        .task {
            for await session in EpisodeActivity.sessions() {
                configureGroupSession(session)
            }
        }
    }

    private func configureGroupSession(_ session: GroupSession<EpisodeActivity>) {
        groupSession = session
        videoPlayer.player.playbackCoordinator.coordinateWithSession(session)

        session.$state
            .sink { state in
            if case .invalidated = state {
                groupSession = nil
                subscriptions.removeAll()
            }
        }
        .store(in: &subscriptions)

        session.$activity
            .sink { activity in
                print("Activity Changed: \(activity.metadata.title ?? "No title for this shitty video LOL")")
            }
            .store(in: &subscriptions)

        session.join()
    }

    private func prepareToPlay(_ playerEpisodeData: Episode) {
        let activity = EpisodeActivity(episode: playerEpisodeData)

        Task {
            switch await activity.prepareForActivation() {
            case .activationDisabled:
                videoPlayer.player.replaceCurrentItem(with: AVPlayerItem(url: playerEpisodeData.videoUrl))

                break

            case .activationPreferred:
                videoPlayer.player.replaceCurrentItem(with: AVPlayerItem(url: playerEpisodeData.videoUrl))

                _ = try await activity.activate()
            case .cancelled:
                break

            @unknown default:
                break
            }
        }
    }
}

Then the following code is a custom AVPlayer

import SwiftUI
import AVFoundation
import AVKit

struct CustomVideoPlayer: UIViewControllerRepresentable {
    @State var videoUrl: URL
    
    var player: AVPlayer {
        return AVPlayer(url: videoUrl)
    }
    
    func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {
        playerController.modalPresentationStyle = .fullScreen
        playerController.allowsPictureInPicturePlayback = true
        playerController.canStartPictureInPictureAutomaticallyFromInline = true
        playerController.player = player
    }
    
    func makeUIViewController(context: Context) -> AVPlayerViewController {
        return AVPlayerViewController()
    }
}

The SharePlay prompt pops up properly and shows the current activity correctly

This is a screenshot of the SharePlay pop-up box, I couldn't insert an image, so I had to insert a link

https://i.sstatic.net/QNqBE.jpg

But when I do something with the player, like pause, or adjust the playback progress, the other iPhone doesn't sync

So what should I do?

Can't thank you enough :-)


Solution

  • Solved, by adding @State annotation to AVPlayer, thanks guys, this question is closed

    struct CustomVideoPlayer: UIViewControllerRepresentable {
        @State var player: AVPlayer? = nil
        
        func updateUIViewController(_ playerController: AVPlayerViewController, context: Context) {
            playerController.modalPresentationStyle = .fullScreen
            playerController.allowsPictureInPicturePlayback = true
            playerController.canStartPictureInPictureAutomaticallyFromInline = true
            playerController.player = player
        }
        
        func makeUIViewController(context: Context) -> AVPlayerViewController {
            return AVPlayerViewController()
        }
    }