Implement the Co-Watching API

This page describes how to use the Co-Watching API.

Initial setup

Before you begin, the live sharing app should initialize a CoWatchingClient object to prepare the library for use.

The following example shows a basic initialization use case:

Java

class AwesomeVideoAddonSessionHandler implements AddonSessionHandler {}

// For sample implementation, see the "Manage remote state" section below.
class AwesomeVideoCoWatchingHandler implements CoWatchingHandler {}

public ListenableFuture<AddonSession> initialSetup() {
  AddonClient meetClient = AddonClientFactory.getClient();
  return meetClient
      .newSessionBuilder(
          /* liveSharingApplicationName= */ "awesome-video-app",
          appContext,
          new AwesomeVideoAddonSessionHandler())
      .withCoWatching(new AwesomeVideoCoWatchingHandler())
      .begin();
}

Notify on user actions

When the local user performs actions—for example, pausing or seeking the media playout on their device—the library must be informed so those actions can be mirrored to other participants in the co-watching experience. For an example of how to notify the library for multiple states, see Get started.

The following example shows a basic use case:

Java

public void onVideoPaused(Duration currentTimestamp) {
  // Use Meet to broadcast the pause state to ensure other participants also pause.
  this.session.getCoWatching().notifyPauseState(/* paused= */ true, currentTimestamp);
};

Manage remote state

In order to apply incoming updates from remote participants, you must offer Meet a way to directly manage the local media playout state using the CoWatchingHandler.onCoWatchingStateChanged() callback.

Meet also needs to retrieve the current position of the media playout by calling the CoWatchingHandler.onStateQuery() callback. This is called regularly, so it should be written to be performant (for example, <100 ms).

The following example shows an implementation of the CoWatchingHandler:

Java

class AwesomeVideoCoWatchingHandler implements CoWatchingHandler {
  /** Applies incoming playback state to the local video. */
  public void onCoWatchingStateChanged(CoWatchingState newState) {
    // Handle transition to new video.
    if (!newState.mediaId().equals(this.videoPlayer.videoUrl)) {
      this.videoPlayer.loadVideo(newState.mediaId());
    }

    // Only adjust the local video playout if it's sufficiently diverged from the timestamp in the
    // applied update.
    if (newState
            .mediaPlayoutPosition()
            .minus(this.videoPlayer.videoTimestamp)
            .compareTo(Duration.ofMillis(500))
        > 0) {
      this.videoPlayer.seek(newState.mediaPlayoutPosition());
    }

    // Update pause state, if necessary.
    if (newState.playbackState().equals(PLAY) && this.videoPlayer.isPaused) {
      this.videoPlayer.unpause();
    } else if (newState.playbackState().equals(PAUSE) && !this.videoPlayer.isPaused) {
      this.videoPlayer.pause();
    }
  }

  /** Returns local video playback state. */
  public Optional<QueriedCoWatchingState> onStateQuery() {
    return Optional.of(QueriedCoWatchingState.of(
      /* mediaPlayoutPosition= */ this.videoPlayer.videoTimestamp));
  }
}