Implement the Co-Doing API

This page describes how to use the Co-Doing API to support a co-doing scenario.

Initial setup

To prepare the library for use, the live sharing application should initialize a CoDoingClient object which represents a co-doing session.

To use the Meet Live Sharing SDK, call the AddonClientFactory.getClient method. This returns an AddonClient that serves as the entry point for the co-doing session.

To use the client, call the newSessionBuilder method from the AddonClient to return a builder for a new AddonSession. The newSessionBuilder implements the AddonSessionHandler interface to handle the callbacks provided by the add-on for the session.

To begin a session, add the withCoDoing method onto the builder.

The following code sample shows a basic initialization of the co-doing client object:

Java

class AwesomeVideoAddonSessionHandler implements AddonSessionHandler {}

//For sample implementation, see the "Handle incoming updates" section.
class AwesomeVideoCoDoingHandler implements CoDoingHandler {}

public ListenableFuture<AddonSession> initialSetup() {
  AddonClient meetClient = AddonClientFactory.getClient();
  return meetClient
      .newSessionBuilder(
          new AwesomeVideoAddonSessionHandler())
      .withCoDoing(new AwesomeVideoCoDoingHandler())
      .begin();
}

Pause video

When participating in a live sharing experience, if a user pauses the playback on their local video app then you must make sure all participants in the live sharing experience also pause their video.

To do this, craft a CoDoingState message showing the video is paused, and tell Google Meet to broadcast to it all other participants using the setGlobalState method. The shared global state becomes the default state for all participants, existing or new, until a new state is set.

The following code sample shows how to notify users of the paused state:

Java

public void onVideoPaused(String videoUrl, Instant currentTimestamp) {
  // Create an internal state object to share with other participants. Note: It's
  // good practice to encode all metadata—even seemingly irrelevant data—into
  // ActivityState updates to guard against race conditions and other subtle
  // failures.
  AwesomeVideoState videoState = AwesomeVideoState
    .builder()
    .videoUrl(videoUrl)
    .videoTimestamp(currentTimestamp)
    .isPaused(true)
    .build();

  // Create the CoDoingState object to wrap the internal state
  CoDoingState coDoingState = new CoDoingState();
  coDoingState.state = SerializationUtils.serialize(videoState);

  // Use Meet to broadcast internal state update to all other participants
  this.coDoingClient.setGlobalState(coDoingState);
};

The code sample triggers the serialized videoState object to be broadcast to all other instances of Meet participating in the live sharing experience. For details on how to receive broadcast updates from other participants, see the Handle incoming updates section.

The following diagram describes the sequence of events after the pause action is triggered:

Start Live Sharing API diagram.

Unpause video

Similar to pause video, if a user unpauses the video on their local app then Meet must broadcast this operation to other live sharing participants.

On the sender side (the user who unpauses the video), the only difference from the pause example is the isPaused status is updated.

The following code sample shows how to notify users of the unpaused state from the sender side:

Java

public void onVideoUnpaused(String videoUrl, Instant currentTimestamp) {
  AwesomeVideoState videoState = AwesomeVideoState
    .builder()
    .videoUrl(videoUrl)
    .videoTimestamp(currentTimestamp)
    .isPaused(false)
    .build();

  CoDoingState coDoingState = new CoDoingState();
  coDoingState.state = SerializationUtils.serialize(videoState);

  this.coDoingClient.setGlobalState(coDoingState);
}

Seek video

Just like pause video and unpause video, if a user drags the timeline on the local app to a new timestamp, Meet must broadcast this operation to all participants.

The following code sample shows how to notify users of the updated timestamp from the sender side:

Java

public void onVideoSeeked(String videoUrl, Instant currentTimestamp, bool isPaused) {
  AwesomeVideoState videoState = AwesomeVideoState
    .builder()
    .videoUrl(videoUrl)
    .videoTimestamp(currentTimestamp)
    .isPaused(isPaused)
    .build();

  CoDoingState coDoingState = new CoDoingState();
  coDoingState.state = SerializationUtils.serialize(videoState);

  this.coDoingClient.setGlobalState(coDoingState);
}

Play a different video

If the user also changes the video being watched by selecting another video on the local app, Meet must play the new video for all live sharing participants. The changed video is stored in videoState.videoUrl.

The following code sample shows how to notify users of the updated video URL:

Java

public void onVideoChanged(String videoUrl, Duration currentTimestamp, bool isPaused) {
  AwesomeVideoState videoState = AwesomeVideoState
    .builder()
    .videoUrl(videoUrl)
    .videoTimestamp(currentTimestamp)
    .isPaused(isPaused)
    .build();

  CoDoingState coDoingState = new CoDoingState();
  coDoingState.state = SerializationUtils.serialize(videoState);

  this.coDoingClient.setGlobalState(coDoingState);
}

End co-doing

When a user elects to end the activity, the endSession method disconnects from the Meet app. This doesn't force Meet to end the meeting, nor does it cause the user to leave the meeting.

The following code sample shows how to notify users of the stopped session:

Java

public void endCoDoing() {
  this.session.endSession();
}

Handle incoming updates

When another participant's Meet app receives a broadcast, the onGlobalStateChanged() callback is triggered. Usually, it's important to make good decisions on what action to take in response to incoming updates such as only matching incoming video timestamps if they're sufficiently different from the local timestamp.

The following code sample shows how to handle the different incoming updates:

Java

class AwesomeVideoCoDoingHandler implements CoDoingHandler {
  public void onGlobalStateChanged(CoDoingState update) {
    AwesomeVideoState videoState = SerializationUtils.deserialize(update.state());

    // Handle transition to new video.
    if (!videoState.videoUrl.equals(this.videoPlayer.videoUrl)) {
      this.videoPlayer.loadVideo(videoState.videoUrl);
    }

    // If the timestamp in the arriving update has sufficiently diverged, adjust
    // the local video playout.
    if (videoState.videoTimestamp.minus(this.videoPlayer.videoTimestamp).abs() >
                                        Duration.ofSeconds(2)) {
      this.videoPlayer.seek(videoState.videoTimestamp);
    }

    // Update pause state, if necessary.
    if (!videoState.isPaused && this.videoPlayer.isPaused) {
      this.videoPlayer.unpause();
    } else if (videoState.isPaused && !this.videoPlayer.isPaused) {
      this.videoPlayer.pause();
    }
  }
}