I am having trouble with initializing the SharePlay. It works but we have to leave the game (click the close button) and rejoin it, sometimes several times, for it to establish the connection.
I am also having trouble sharing images over SharePlay with GroupSessionJournal. I am not able to get it to transfer any amount of data or even get recognition on the other participants in the SharePlay that an image is being sent. We have look at all the information we can find online and are not able to establish a connection. I am not sure if I am missing a step, or if I am incorrectly sending the data through the GroupSessionJournal.
Here are the steps I took take to replicate the issue I have:
- FaceTime another person with the app.
- Open the app and click the SharePlay button to SharePlay it with the other person.
- Establish the SharePlay and by making sure that the board states are syncronized across participants. If its not click the close button and click open app again to rejoin the SharePlay. (This is one of the bugs that I would like to fix. This is just a work around we developed to establish the SharePlay. We would like it so that when you click SharePLay and they join the session it works.)
- Once the SharePlay has been established, change the image by clicking change 1 image.
- Select a jpg image.
- The image that represents 1 should be not set. If you dont see the image click on any of the X in the squares and it will change to the image.
- The image should appear on the other participant in the SharePlay. (This does not happen and is what we have not been able to figure out how to get working.)
Here are the classes for the example project I created:
import SwiftUI import RealityKit import RealityKitContent import GroupActivities struct ContentView: View { @Environment(GameModel.self) var gameModel @State var openFiles: Bool = false @State private var session: GroupSession? = nil @StateObject private var groupStateObserver = GroupStateObserver() var body: some View { VStack { HStack { ForEach(0..<3) { index in Image(uiImage: imageForValue(gameModel.board[index])) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 100, height: 100) .onTapGesture { gameModel.board[index] = gameModel.board[index] == 1 ? 0 : 1 sendBoardUpdate(gameModel.board) } } } Button("Close", systemImage: "xmark.circle") { sessionInfo?.session?.leave() exit(0) } Button { if sessionInfo != nil { gameModel.resetBoard() sendBoardUpdate(gameModel.board) } } label: { Label("Reset", systemImage: "arrow.circlepath") } Button { Task { do { try await startSession() } catch { print("SharePlay session failure", error) } } } label: { Label("SharePlay", systemImage: "shareplay") } .disabled(!groupStateObserver.isEligibleForGroupSession) Button("Change 1 Image") { openFiles = true } .fileImporter(isPresented: $openFiles, allowedContentTypes: [.jpeg]) { result in do { let file = try result.get() if file.startAccessingSecurityScopedResource() { gameModel.setNumberImage(num: 1, data: try! Data(contentsOf: file)) file.stopAccessingSecurityScopedResource() Task { print("Setting 1 Image") let result = try await sessionInfo?.journal?.add(gameModel.imageData[1]!, metadata: ImageMetadata(num: 1)) print("Adding 1 Image to journal result: \(result)") } } openFiles = false } catch { print("Failed to read file") } } Text("Hello, world!") } .padding() .task { // Initialize sessionInfo sessionInfo = .init() // Wait for a SharePlay session to start for await newSession in Activity.sessions() { print("New GroupActivities session", newSession) session = newSession sessionInfo?.session = newSession // Configure spatial coordination if let coordinator = await newSession.systemCoordinator { var config = SystemCoordinator.Configuration() config.spatialTemplatePreference = .sideBySide config.supportsGroupImmersiveSpace = true coordinator.configuration = config } // Join the session newSession.join() do { print("Waiting before starting group activity.") while newSession.activeParticipants.isEmpty { try await Task.sleep(for: .seconds(3)) } } catch { print("Couldn't sleep.", error) } // Set up messengers for SharePlay communication sessionInfo?.messenger = GroupSessionMessenger(session: newSession, deliveryMode: .unreliable) sessionInfo?.reliableMessenger = GroupSessionMessenger(session: newSession, deliveryMode: .reliable) sessionInfo?.journal = GroupSessionJournal(session: newSession) // Update player list when participants join Task { for try await updatedPlayerList in newSession.$activeParticipants.values { for participant in updatedPlayerList { try await sessionInfo!.reliableMessenger!.send(ArrayMessage(board: gameModel.board), to: .only(participant)) let potentialNewPlayer = Player(name: String(participant.id.asPlayerName), selection: 0) if !gameModel.players.contains(where: { $0.name == potentialNewPlayer.name }) { gameModel.players.append(potentialNewPlayer) } } } } // Handle board state updates Task { for await (message, _) in sessionInfo!.reliableMessenger!.messages(of: ArrayMessage.self) { gameModel.board = message.board } } // Handle image updates for board states Task { for await files in sessionInfo!.journal!.attachments { print("New attachments") for attachment in files { print("New attachment: \(attachment)") let data = try await attachment.load(Data.self) let metadata = try await attachment.loadMetadata(of: ImageMetadata.self) gameModel.setNumberImage(num: metadata.num, data: data) } } } } } } /// Sends the new Board to all other SharePlay participants. /// - Parameters: /// - board: The new Board. func sendBoardUpdate(_ board: [Int]) { if let sessionInfo = sessionInfo, let session = sessionInfo.session, let messenger = sessionInfo.reliableMessenger { let everyoneElse = session.activeParticipants.subtracting([session.localParticipant]) messenger.send(ArrayMessage(board: board), to: .only(everyoneElse)) { error in if let error = error { print("Message failure:", error) } } } } /// Returns the appropriate UIImage based on the board value. /// - Parameter value: The value from the board array. /// - Returns: The UIImage to display. func imageForValue(_ value: Int) -> UIImage { if value == 1 { if let data = gameModel.imageData[1], let image = UIImage(data: data) { return image } else { return UIImage(systemName: "xmark.circle")! } } else { return UIImage(systemName: "xmark.square")! } } } extension UUID { var asPlayerName: String { String(uuidString.split(separator: "-").last!) } } #Preview(windowStyle: .automatic) { ContentView().environment(GameModel()) }
// // GameModel.swift // mixed_game // // Created by Charles Ciampa on 9/3/24. // import Foundation @Observable class GameModel { /// Contains the current board state as a 2d array. It starts off as None in every spot. This is what is rendered in all the views to represent the board. var board: [Int] = Array(repeating: 0, count: 3) /// Saves the data for the image for the state representation. var imageData: [Int: Data] = [Int: Data]() /// Sets the image representation of a state. /// - Parameters: /// - num: The num to set the image of. /// - data: The data of the image to set it as. func setNumberImage(num: Int, data: Data) { imageData[num] = data } /// Saves the current set of players, to identify who is who in the game. var players: [Player] = [Player(name: "Sam", selection: 1)] func setBoardPiece(num: Int, index: Int) { board[index] = num } func getBoardPiece(index: Int) -> Int { return board[index] } func resetBoard() { self.board = Array(repeating: 0, count: 3) } } struct Player { let name: String let selection: Int static var localName: String = "" }
import GroupActivities import SwiftUI func startSession() async throws { let activity = Activity() let result = await activity.prepareForActivation() if result == .activationPreferred { let activationSuccess = try await activity.activate() print("Group Activities session activation: ", activationSuccess) } else { print("Group Activities session activation: ", result) } } struct Activity: GroupActivity { static let activityIdentifier: String = "com.example.code" var metadata: GroupActivityMetadata { var metadata = GroupActivityMetadata() metadata.type = .generic metadata.title = "MixedGame" metadata.supportsContinuationOnTV = false return metadata } } struct ArrayMessage: Codable { let board: [Int] } struct ImageMetadata: Codable { let num: Int } var sessionInfo: ActivitySessionInfo? = nil class ActivitySessionInfo: ObservableObject { @Published var session: GroupSession? var messenger: GroupSessionMessenger? var reliableMessenger: GroupSessionMessenger? var journal: GroupSessionJournal? }
// // SharePlay_Test_CodeApp.swift // SharePlay Test Code // // Created by Charles Ciampa on 9/25/24. // import SwiftUI @main struct SharePlay_Test_CodeApp: App { @State private var gameModel: GameModel = .init() var body: some Scene { WindowGroup { ContentView().environment(gameModel) } } }
Replies
0
Boosts
0
Views
443
Participants
1