Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
dpearson2699 avatar

Shareplay Activities

  • 2.5k installs
  • 944 repo stars
  • Updated July 15, 2026
  • dpearson2699/swift-ios-skills

shareplay-activities is an iOS skill for GroupActivities SharePlay sessions, sync, and coordinated playback.

About

SharePlay Activities documents GroupActivities framework patterns for shared real-time experiences across iOS, macOS, tvOS, and visionOS targeting Swift 6.3 and iOS 26 plus. Setup adds com.apple.developer.group-session entitlement and optional NSSupportsGroupActivities for starting sessions without an active FaceTime call on iOS 17 plus. GroupActivity structs provide Codable metadata with types like watchTogether, listenTogether, createTogether, and workoutTogether plus fallback URLs. Session lifecycle covers GroupSession joining, state synchronization, messenger send and receive, and coordinated AVPlaybackCoordinator media playback. GroupSessionJournal enables file transfers between participants. Starting SharePlay from the app uses GroupActivityActivationResult and UIActivationSession patterns when eligible per GroupStateObserver. Common mistakes address missing entitlements, non-Codable activity payloads, main-thread UI assumptions, and cleanup on session end. Review checklist sections validate eligibility checks, session error handling, and playback coordination. The skill targets FaceTime and iMessage integrated group experiences rather than generic WebRTC implementations.

  • GroupActivity metadata types include watchTogether and listenTogether.
  • Requires group-session entitlement and Codable activity structs.
  • GroupStateObserver.isEligibleForGroupSession gates SharePlay UI.
  • Coordinated media uses AVPlaybackCoordinator integration patterns.
  • GroupSessionJournal supports participant file transfer flows.

Shareplay Activities by the numbers

  • 2,536 all-time installs (skills.sh)
  • +103 installs in the week ending Jul 29, 2026 (Skillselion tracking)
  • Ranked #85 of 1,039 Mobile Development skills by installs in the Skillselion catalog
  • Security screen: MEDIUM risk (skills.sh audit)
  • Data as of Jul 31, 2026 (Skillselion catalog sync)
At a glance

shareplay-activities capabilities & compatibility

Capabilities
groupactivity metadata and type selection · session join and messenger synchronization · coordinated avplaybackcoordinator media · eligibility observation with groupstateobserver · groupsessionjournal file transfer guidance
Use cases
frontend · ui design · api development
Platforms
macOS
Runs
Runs locally
Pricing
Free
From the docs

What shareplay-activities says it does

Check if a FaceTime call or iMessage group is active
SKILL.md
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill shareplay-activities

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs2.5k
repo stars944
Security audit2 / 3 scanners passed
Last updatedJuly 15, 2026
Repositorydpearson2699/swift-ios-skills

How do I add SharePlay for shared video or app state over FaceTime?

Implement GroupActivities SharePlay for synchronized media, collaborative state, and FaceTime-integrated sessions on Apple platforms.

Who is it for?

Apple apps adding FaceTime or iMessage integrated shared experiences.

Skip if: Skip for cross-platform WebRTC or Android group calls without GroupActivities.

When should I use this skill?

User mentions SharePlay, GroupActivities, watch together, or group session sync.

What you get

Working GroupActivity definition, session join flow, and messenger or playback coordination.

  • GroupActivity Swift type
  • Session lifecycle handlers
  • Synchronized state messaging code

By the numbers

  • Targets Swift 6.3 and iOS 26+
  • Supports 4 Apple platforms: iOS, macOS, tvOS, and visionOS

Files

SKILL.mdMarkdownGitHub ↗

GroupActivities / SharePlay

Build shared real-time experiences using the GroupActivities framework. SharePlay connects people over FaceTime or iMessage, synchronizing media playback, app state, or custom data. Targets Swift 6.3 / iOS 26+.

Contents

Setup

Entitlements

Add the Group Activities entitlement to your app:

<key>com.apple.developer.group-session</key>
<true/>

Info.plist

For apps that start SharePlay without a FaceTime call (iOS 17+), add:

<key>NSSupportsGroupActivities</key>
<true/>

Checking Eligibility

import GroupActivities

let observer = GroupStateObserver()

// Check if a FaceTime call or iMessage group is active
if observer.isEligibleForGroupSession {
    showSharePlayButton()
}

Observe changes reactively:

for await isEligible in observer.$isEligibleForGroupSession.values {
    showSharePlayButton(isEligible)
}

Defining a GroupActivity

Conform to GroupActivity and provide metadata:

import GroupActivities
import CoreTransferable

struct WatchTogetherActivity: GroupActivity {
    let movieID: String
    let movieTitle: String

    var metadata: GroupActivityMetadata {
        var meta = GroupActivityMetadata()
        meta.title = movieTitle
        meta.type = .watchTogether
        meta.fallbackURL = URL(string: "https://example.com/movie/\(movieID)")
        return meta
    }
}

Activity Types

TypeUse Case
.genericDefault for custom activities
.watchTogetherVideo playback
.listenTogetherAudio playback
.createTogetherCollaborative creation (drawing, editing)
.workoutTogetherShared fitness sessions

The activity struct must conform to Codable so the system can transfer it between devices.

Session Lifecycle

Listening for Sessions

Set up a long-lived task to receive sessions when another participant starts the activity:

@Observable
@MainActor
final class SharePlayManager {
    private var session: GroupSession<WatchTogetherActivity>?
    private var messenger: GroupSessionMessenger?
    private var tasks = TaskGroup()

    func observeSessions() {
        Task {
            for await session in WatchTogetherActivity.sessions() {
                self.configureSession(session)
            }
        }
    }

    private func configureSession(
        _ session: GroupSession<WatchTogetherActivity>
    ) {
        self.session = session
        self.messenger = GroupSessionMessenger(session: session)

        // Observe session state changes
        Task {
            for await state in session.$state.values {
                handleState(state)
            }
        }

        // Observe participant changes
        Task {
            for await participants in session.$activeParticipants.values {
                handleParticipants(participants)
            }
        }

        // Join the session
        session.join()
    }
}

Session States

StateDescription
.waitingSession exists but local participant has not joined
.joinedLocal participant is actively in the session
.invalidated(reason:)Session ended (check reason for details)

Handling State Changes

private func handleState(_ state: GroupSession<WatchTogetherActivity>.State) {
    switch state {
    case .waiting:
        print("Waiting to join")
    case .joined:
        print("Joined session")
        loadActivity(session?.activity)
    case .invalidated(let reason):
        print("Session ended: \(reason)")
        cleanUp()
    @unknown default:
        break
    }
}

private func handleParticipants(_ participants: Set<Participant>) {
    print("Active participants: \(participants.count)")
}

Leaving and Ending

// Leave the session (other participants continue)
session?.leave()

// End the session for all participants
session?.end()

Sending and Receiving Messages

Use GroupSessionMessenger to sync app state between participants.

Defining Messages

Messages must be Codable:

struct SyncMessage: Codable {
    let action: String
    let timestamp: Date
    let data: [String: String]
}

Sending

func sendSync(_ message: SyncMessage) async throws {
    guard let messenger else { return }

    try await messenger.send(message, to: .all)
}

// Send to specific participants
try await messenger.send(message, to: .only(participant))

Receiving

func observeMessages() {
    guard let messenger else { return }

    Task {
        for await (message, context) in messenger.messages(of: SyncMessage.self) {
            let sender = context.source
            handleReceivedMessage(message, from: sender)
        }
    }
}

Delivery Modes

// Reliable (default) -- guaranteed delivery, ordered
let reliableMessenger = GroupSessionMessenger(
    session: session,
    deliveryMode: .reliable
)

// Unreliable -- faster, no guarantees (good for frequent position updates)
let unreliableMessenger = GroupSessionMessenger(
    session: session,
    deliveryMode: .unreliable
)

Use .reliable for state-changing actions (play/pause, selections). Use .unreliable for high-frequency ephemeral data (cursor positions, drawing strokes).

Coordinated Media Playback

For video/audio, use AVPlaybackCoordinator with AVPlayer:

import AVFoundation
import GroupActivities

func configurePlayback(
    session: GroupSession<WatchTogetherActivity>,
    player: AVPlayer
) {
    // Connect the player's coordinator to the session
    let coordinator = player.playbackCoordinator
    coordinator.coordinateWithSession(session)
}

Once connected, play/pause/seek actions on any participant's player are automatically synchronized to all other participants. No manual message passing is needed for playback controls.

Handling Playback Events

// Notify participants about playback events
let event = GroupSessionEvent(
    originator: session.localParticipant,
    action: .play,
    url: nil
)
session.showNotice(event)

Starting SharePlay from Your App

Using GroupActivitySharingController (UIKit)

import GroupActivities
import UIKit

func startSharePlay() async throws {
    let activity = WatchTogetherActivity(
        movieID: "123",
        movieTitle: "Great Movie"
    )

    switch await activity.prepareForActivation() {
    case .activationPreferred:
        // Already in a FaceTime/iMessage session — activate directly
        _ = try await activity.activate()

    case .activationDisabled:
        // SharePlay is disabled or unavailable
        print("SharePlay not available")

    case .cancelled:
        break

    @unknown default:
        break
    }
}

When no conversation is active (i.e., isEligibleForGroupSession is false), use GroupActivitySharingController to let the user pick contacts first:

let controller = try GroupActivitySharingController(activity)
present(controller, animated: true)

For ShareLink (SwiftUI) and direct activity.activate() patterns, see references/shareplay-patterns.md.

GroupSessionJournal: File Transfer

For large data (images, files), use GroupSessionJournal instead of GroupSessionMessenger (which has a size limit):

import GroupActivities

let journal = GroupSessionJournal(session: session)

// Upload a file
let attachment = try await journal.add(imageData)

// Observe incoming attachments
Task {
    for await attachments in journal.attachments {
        for attachment in attachments {
            let data = try await attachment.load(Data.self)
            handleReceivedFile(data)
        }
    }
}

Common Mistakes

DON'T: Forget to call session.join()

// WRONG -- session is received but never joined
for await session in MyActivity.sessions() {
    self.session = session
    // Session stays in .waiting state forever
}

// CORRECT -- join after configuring
for await session in MyActivity.sessions() {
    self.session = session
    self.messenger = GroupSessionMessenger(session: session)
    session.join()
}

DON'T: Forget to leave or end sessions

// WRONG -- session stays alive after the user navigates away
func viewDidDisappear() {
    // Nothing -- session leaks
}

// CORRECT -- leave when the view is dismissed
func viewDidDisappear() {
    session?.leave()
    session = nil
    messenger = nil
}

DON'T: Assume all participants have the same state

// WRONG -- broadcasting state without handling late joiners
func onJoin() {
    // New participant has no idea what the current state is
}

// CORRECT -- send full state to new participants
func handleParticipants(_ participants: Set<Participant>) {
    let newParticipants = participants.subtracting(knownParticipants)
    for participant in newParticipants {
        Task {
            try await messenger?.send(currentState, to: .only(participant))
        }
    }
    knownParticipants = participants
}

DON'T: Use GroupSessionMessenger for large data

// WRONG -- messenger has a per-message size limit
let largeImage = try Data(contentsOf: imageURL)  // 5 MB
try await messenger.send(largeImage, to: .all)    // May fail

// CORRECT -- use GroupSessionJournal for files
let journal = GroupSessionJournal(session: session)
try await journal.add(largeImage)

DON'T: Send redundant messages for media playback

// WRONG -- manually syncing play/pause when using AVPlayer
func play() {
    player.play()
    try await messenger.send(PlayMessage(), to: .all)
}

// CORRECT -- let AVPlaybackCoordinator handle it
player.playbackCoordinator.coordinateWithSession(session)
player.play()  // Automatically synced to all participants

DON'T: Observe sessions in a view that gets recreated

// WRONG -- each time the view appears, a new listener is created
struct MyView: View {
    var body: some View {
        Text("Hello")
            .task {
                for await session in MyActivity.sessions() { }
            }
    }
}

// CORRECT -- observe sessions in a long-lived manager
@Observable
final class ActivityManager {
    init() {
        Task {
            for await session in MyActivity.sessions() {
                configureSession(session)
            }
        }
    }
}

Review Checklist

  • [ ] Group Activities entitlement (com.apple.developer.group-session) added
  • [ ] GroupActivity struct is Codable with meaningful metadata
  • [ ] sessions() observed in a long-lived object (not a SwiftUI view body)
  • [ ] session.join() called after receiving and configuring the session
  • [ ] session.leave() called when the user navigates away or dismisses
  • [ ] GroupSessionMessenger created with appropriate deliveryMode
  • [ ] Late-joining participants receive current state on connection
  • [ ] $state and $activeParticipants publishers observed for lifecycle changes
  • [ ] GroupSessionJournal used for large file transfers instead of messenger
  • [ ] AVPlaybackCoordinator used for media sync (not manual messages)
  • [ ] GroupStateObserver.isEligibleForGroupSession checked before showing SharePlay UI
  • [ ] prepareForActivation() called before presenting sharing controller
  • [ ] Session invalidation handled with cleanup of messenger, journal, and tasks

References

Related skills

How it compares

Choose shareplay-activities for native FaceTime and iMessage group sync, not for custom WebRTC or cross-platform signaling stacks.

FAQ

What entitlement is required?

Add com.apple.developer.group-session to the app target.

Can activities be plain structs?

They must conform to GroupActivity and Codable for system transfer between devices.

When show SharePlay without a call?

Set NSSupportsGroupActivities and follow iOS 17 plus in-app activation patterns.

Is Shareplay Activities safe to install?

skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

Mobile Developmentfrontendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.