## Introduction to SharePlay

With SharePlay you can enjoy shows, movies, and music in sync with friends and family while being on a FaceTime call together. During a SharePlay session, playback is kept in sync across multiple devices and each participant is allowed to control playback.

SharePlay is supported starting with Bitmovin Player iOS SDK `3.31.0` on iOS / tvOS `15.0+`. Earlier iOS / tvOS versions do not contain the [`GroupActivities`](🔗) framework that is required for SharePlay.

## Enable SharePlay with the Bitmovin Player iOS SDK

To enable the SharePlay experience in your own app using Bitmovin Player iOS SDK, follow the steps below. Make sure to use Bitmovin Player iOS SDK `3.31.0` or later. This guide is based on the [SharePlay sample application](🔗) that is published in our [samples repository](🔗). The [sample application](🔗) and this guide are kept simple and straightforward for demonstration purposes. It can be adapted and extended to fit individual use cases and application needs.

Before getting started, make sure the "Group Activities" capability is added in the project settings under "Signing & Capabilities".

### Create an Activity

To define a shareable group watching experience, the sample contains the [`MediaWatchingActivity`](🔗) class that adopts the [`GroupActivity`](🔗) protocol. The activity stores the asset to share with the group and provides supporting metadata that the system displays when a user shares an activity. `GroupActivity` extends [`Codable`](🔗), so any data that an activity stores must also conform to `Codable`.

### GroupSession Management

The [`CoordinationManager`](🔗) takes care of sharing an activity with the group if one of the assets were selected for playback. In this process, a [`GroupSession`](🔗) instance is created by the system and provided to every participant. This `GroupSession` is a central part in the group watching experience. Later, it also needs to be passed to the Bitmovin `Player` instance in order to allow coordinated playback.

When a user selects a movie, in `CoordinationManager.prepareToPlay(asset:)` we first check if there is already a `GroupSession` available. If that's the case, we check if the selected `asset` is maybe already the current activity of the group. In that case, we do not need to update `groupSession.activity` and just have to make sure that the selected `asset` is loaded into the local player.

If the selected `asset` is different from the current activity of the `GroupSession`, we simply set a new activity for every participant in the group: `groupSession.activity = MediaWatchingActivity(asset: asset)`.

When there is no `GroupSession` available yet and the local user selects an asset to play back, we need to determine whether it needs to play the movie for the local user only, or share it with the group. It makes this determination by calling the activity’s asynchronous `prepareForActivation()` method, which enables the system to present an interface for the user to select their preferred action.

When a `MovieWatchingActivity` is activated, the system creates a group session. `CoordinationManager` accesses the session by calling the [`sessions()`](🔗) method, which returns available sessions as an asynchronous sequence. When the sample receives a new session, it sets it as the active group session, and then joins it, which makes the app eligible to participate in group watching. Then, it subscribes to the session’s activity publisher and, when it receives a new value, it finally enqueues the activity’s movie for playback.

### Coordinated Playback

In [`AssetsTableViewController`](🔗) we listen to changes to the selected `asset` of `CoordinationManger`. As soon as we receive a new `asset`, the [`PlaybackViewController`](🔗) is presented.

The `PlaybackViewController`, when setting up the Bitmovin `Player` instance, checks if there is an active `GroupSession` available and coordinates the player with the session. This step is important as otherwise only local non-synchronized playback would happen. The player instance would not know about any `GroupSession`'s.

When the player instance is prepared, we can create a `SourceConfig` based on the selected `asset` and load it into the player.

Every participant of the `GroupSession` now has a player presented with the selected asset loaded into it. When one of the participants hits the play button, synchronized playback starts for the whole group.

### Suspensions

Suspensions can be used to prevent local interruptions of one participant from impacting other participants in the same `GroupSession`. For example, if the local participant wants to answer an incoming phone call, local playback needs to be paused without pausing the whole group. Suspensions disconnect the local participant from the group temporarily.

For more details on the APIs Bitmovin Player offers regarding suspensions, see chapters [Bitmovin Player SharePlay APIs](🔗) and [Suspension Handling](🔗).

### SharePlay Related Events

There are a couple of SharePlay related events that Bitmovin `Player` offers. They are available in the `PlayerListener` protocol alongside all previously existing player events. For more information on those events, have a look at the [Events](🔗) section.

### Starting a Shared Experience

Given that there is now a SharePlay enabled app ready to use, the following steps are necessary to start or join a shared experience. They are again based on our [SharePlay](🔗) sample app, but at the same time apply to every SharePlay enabled app in general.

**iOS / iPadOS Devices**

You will need at least two devices that have the [SharePlay](🔗) sample app installed.

  1. Join the same FaceTime call with each device that should be part of the shared experience. For simplicity, this guide will assume two devices, A and B.

  2. On one of the devices, let's assume A, open the SharePlay enabled app. The system will notify us that we are eligible to use SharePlay by showing the hint "Choose Content to Use SharePlay" (screenshot 1).

  3. Tap one of the assets from the list and a pop-up appears, asking if we want to initiate a SharePlay `GroupSession` or if the content should be played only locally. Choose "SharePlay" (screenshot 2) and the video player should appear, ready to start playback (screenshot 3).

  4. On device B, the user is asked to join the SharePlay `GroupSession` that was just started on device A. Tap "Join SharePlay" to join the shared experience (screenshot 4).

  5. Now, playback can be initiated on any device by tapping the play button. Playback starts perfectly in sync on both devices (screenshot 5). Also, when the playback time is changed on one of the devices by seeking to a certain position, both devices jump to the new playback time and continue playback in sync.

Screenshot 1:<br />Choose ContentScreenshot 2:<br />Start SharePlayScreenshot 3:<br />SharePlay StartedScreenshot 4:<br />Join SharePlayScreenshot 5:<br />Playback Started
Device A Choose Content
Device A Start Shareplay
device A Started Shareplay
Device B Join Shareplay
Device A Started Playback

**tvOS Devices**

To use SharePlay from your Apple TV, follow the steps from this guide: [Use SharePlay to watch movies and TV shows together on your Apple TV](🔗).

## Bitmovin Player SharePlay APIs

### sharePlay Player API Namespace

For Bitmovin `Player`, all SharePlay related API calls reside in their own player API namespace named `sharePlay`. It is available on iOS and tvOS 15.0 and above and can only be used from Swift. When the `sharePlay` namespace is accessed from Obj-C, it does not contain any APIs. This is due to the limitation of Apple's Swift-only `GroupActivities` framework.

**Joining a GroupSession**

To let a player instance join a group session, the `GroupSession` object simply needs to be passed to `coordinate(with groupSession:)`. The player will then react to state changes to the group session and also start receiving and sending playback commands through it.

Obtaining a `GroupSession` instance is not concern of the Bitmovin Player SDK and needs to be done and handled in the app that is integrating the player. `GroupSession` is part of Apple's `GroupActivities` framework.

**Leaving a `GroupSession`**

There is no specific API on the Bitmovin `Player` for this. The app that is integrating the player is responsible for managing the `GroupSession` object's life-cycle. To let a player leave a group session, [`GroupSession.leave()`](🔗) or [`GroupSession.end()`](🔗) can be used. The player instance will react to state changes of the group session instance accordingly.

**Beginning a Suspension**

A suspension is used to tell the player that it cannot, or should not, participate in coordinated group playback temporarily. Once a suspension is started, the player will not respond to playback commands coming from the group, and it will also not coordinate any commands with the group.

To resume synchronized group playback, end an active suspension by calling one of the `endSuspension(_:)` methods available on the `sharePlay` API namespace.

It is possible to start multiple suspensions for the same participant. `player.sharePlay.suspensionReasons` always returns the full list of all active suspensions. Only if all active suspensions are ended, the participant joins synchronized group playback again.

The `suspensionReason` indicates the reason for the suspension that is shared with other participants. Apple provides predefined suspension reasons, which can be used for common use cases (`AVCoordinatedPlaybackSuspensionReason`). However, also custom user-defined suspension reasons can be used.

**Ending a Suspension**

Use the above methods to end an active suspension and join group playback again. When ending a suspension, a new group playback time can be proposed optionally.

After ending a suspension, the player will receive the current group playback state and applies it locally so that the unsuspended participant is in-sync again. If a new playback time was proposed when ending a suspension, the rest of the group will be brought in sync accordingly.

**Retrieving SharePlay State**

If the player is coordinated with a valid session, `isInGroupSession` returns `true`. A valid session is a `GroupSession` that is either in state `.waiting` or `.joined`. An invalid session is a `GroupSession` that is in state `.invalidated`. In this case, `isInGroupSession` returns `false`.

If the player is coordinated with a valid session (`isInGroupSession` returns `true`) and the player is currently suspended, the list of suspension reasons contains at least one entry. In this case, `isSuspended` returns `true`.

`suspensionReasons` always contains the current list of reasons why the player is suspended. If it is empty, the player is not suspended, and `isSuspended` returns `false`.

### Configuration for Source Identifier

All participants in a group session need to have the same source loaded into their local player instance in order to allow synchronized playback. By default, `sourceConfig.url.absoluteString` is used as the asset identifier that is communicated to the group session. To allow use cases where participants in the same group session use different URLs for the same media content (for instance, there might be an access token or user ID encoded into the source URL for some participants) `SourceOptions.sharePlayIdentifier` can be used. If `SourceOptions.sharePlayIdentifier` is set, it is used instead of `sourceConfig.url.absoluteString` to identify an asset within a group session.

### Events

  • `SharePlayStartedEvent` is emitted when `SharePlayApi.isInGroupSession` changes from `false` to `true` .

  • `SharePlayEndedEvent` is emitted when `SharePlayApi.isInGroupSession` changes from `true` to `false`.

  • `SharePlaySuspensionStartedEvent` is emitted when a suspension started. In this case the player transitions into `SharePlayApi.isSuspended` is `true`, if it was not already set to `true` by a previous suspension that has not yet ended.

  • `SharePlaySuspensionEndedEvent` is emitted when a specific suspension ended. After seeing this event, `SharePlayApi.isSuspended` can still return `true` in the case there is still another suspension that is ongoing. When all ongoing suspensions have ended (i.e. `suspensionReasons` is empty), `SharePlayApi.isSuspended` will return `false`.

## Advanced SharePlay topics

The following chapters provide a technical deep dive into certain SharePlay topics for those who are interested.

### Stall Recovery

During a SharePlay session, it is possible that one of the participants experiences bad network conditions, which prevents the participant from staying in sync with the group due to a playback stall. In general, there are two ways to deal with this:

  1. Group playback is paused until the stalling participant has recovered from the stall. Then, the whole group resumes playback in sync.

  2. Group playback is not interrupted and the stalling participant is suspended. While being suspended, the device tries to catch-up to the group playback time again. Once the device has caught-up to the group playback time, the recovered participant is unsuspended and re-joins group playback.

The first approach has the advantage that the stalling participant does not miss out on any watched content. However, the rest of the group has to wait for the stalling participant to be ready again, which is not ideal if it happens too often. This approach is probably only suitable for scenarios where a small group watches VOD content together from home, where the network is expected to be stable and network stalls only happen very rarely.

The second approach offers the better user experience in general. Only the stalling participant is affected by the stall. The downside is that the stalling participant might miss out on content that was watched by the group while being suspended. This approach is suitable for almost every scenario. When watching content at home under good network conditions, stalls are very unlikely and therefore missing out on content is not a problem. In larger groups, especially if one of the participants joins from a mobile network, group playback is not affected by stalls. Especially when watching live content it is important to stay on the live edge and not miss any content, which makes this approach suitable in this case as well.

Bitmovin `Player` implements the second approach. When a network stall is detected, the stalling participant is suspended. In this case, a `SharePlaySuspensionStartedEvent` is emitted by the player. The event contains a suspension object of type `SharePlaySuspension` with reason `.stallRecovery`. The stall recovery process itself happens automatically inside the player and the integrating app does not need to handle anything on its own. Once stall recovery is successful, a `SharePlaySuspensionEndedEvent` is emitted by the player and the stalling participant re-joins group playback again.

### Suspension Handling

As described in [Bitmovin Player SharePlay APIs](🔗), the `player.sharePlay` namespace offers APIs to start a suspension, end a suspension, check whether the local participant is suspended, and get a list of currently active suspensions.

To use suspensions, the `AVFoundation` framework offers a set of predefined suspension reasons as shown below. All of those reasons could be used in an app to start a suspension.

Additionally, if an app wants to start a suspension and none of the predefined system suspensions is suitable, a custom suspension reason can be created.

**Scrubbing Suspension**

In the chapter [Stall Recovery](🔗), we describe how the Bitmovin `Player` uses the `.stallRecovery` suspension reason to handle playback stalls that one of the participants might experience.

Another important suspension reason is `.userIsChangingCurrentTime`. Imagine that one of the participants starts scrubbing around, trying to find a certain scene within a movie. During this scrubbing process, the local playback time is changed. However, it is not desired to update the group playback state while scrubbing. Only the final playback position of the scrubbing operation should be coordinated with the group.

To implement such a scenario, the player UI which is in charge of the scrubbing, should suspend the local participant using the `.userIsChangingCurrentTime` reason and only end the suspension after scrubbing has finished. Please note, that a new playback time should be proposed when ending the suspension. Otherwise, the unsuspended participant would jump back to the group playback position.

## Limitations

With Bitmovin Player iOS SDK `3.31.0`, we released support for SharePlay for the first time. It provides a stable SharePlay experience, covering everything needed to get started with this exciting new feature. However, with this initial release there are still some technical limitations and missing features which we plan to tackle with future releases:

  • Trick play (slow/fast-forward and rewind) is not supported

  • Synchronized ad playback and ad break management is not supported

  • Casting is not supported

  • Playlists are not supported

  • System UI is not supported

  • AirPlay and Picture in Picture (PiP) are not fully supported. Playback changes done with the AirPlay receiver or PiP mini player are not synchronized with the group. Playback changes done on the AirPlay sender device are working as expected.

## Resources

  • [Bitmovin SharePlay Sample App](🔗)

  • [iPhone SharePlay User Guide](🔗)

  • [Use SharePlay to watch movies and TV shows together on your Apple TV](🔗)

  • [Apple Developer Landing Page for SharePlay](🔗)

  • [`GroupActivities` Framework Documentation](🔗)

  • Official Apple sample code: [Supporting Coordinated Media Playback](🔗)