实现 Co-Doing API

本页介绍了如何使用 Co-Doing API 来支持协同操作场景。

初始设置

如需准备该库以供使用,实时共享应用应初始化一个代表协作活动会话的 CoDoingClient 对象。

如需使用 Meet 实时共享 SDK,请调用 AddonClientFactory.getClient 方法。这将返回一个 AddonClient,作为共同操作会话的入口点。

如需使用客户端,请从 AddonClient 调用 newSessionBuilder 方法,以返回新 AddonSession 的构建器。newSessionBuilder 会实现 AddonSessionHandler 接口,以处理该插件为会话提供的回调。

如需开始会话,请将 withCoDoing 方法添加到构建器上。

以下代码示例展示了“共同操作”客户端对象的基本初始化:

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();
}

暂停视频

参与实时共享体验时,如果用户暂停其本地视频应用中的播放,您必须确保实时共享体验的所有参与者也会暂停其视频。

为此,请撰写一条显示视频已暂停的 CoDoingState 消息,并告知 Google Meet 使用 setGlobalState 方法向所有其他参与者广播。共享的全局状态会成为所有参与者(现有或新参与者)的默认状态,直到设置新状态为止。

以下代码示例展示了如何通知用户已暂停状态:

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);
};

此代码示例会触发序列化 videoState 对象,该对象将广播到参与实时共享体验的所有其他 Meet 实例。如需详细了解如何从其他参与者接收广播更新,请参阅处理传入更新部分。

下图描述了触发暂停操作后的事件序列:

启动实时共享 API 示意图。

取消暂停视频

暂停视频类似,如果用户在其本地应用中取消暂停视频,则 Meet 必须将此操作广播给其他实时共享参与者。

在发送方(即取消暂停视频的用户)上,与暂停示例的唯一区别是 isPaused 状态会更新。

以下代码示例展示了如何从发送者处通知用户已取消暂停状态:

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);
}

跳转视频

就像暂停视频取消暂停视频一样,如果用户将本地应用中的时间轴拖动到新的时间戳,Meet 必须向所有参与者广播此操作。

以下代码示例展示了如何从发送方端通知用户时间戳已更新:

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);
}

播放其他视频

如果用户还通过在本地应用中选择其他视频来更改正在观看的视频,则 Meet 必须为所有实时共享参与者播放新视频。更改后的视频存储在 videoState.videoUrl 中。

以下代码示例展示了如何通知用户视频网址已更新:

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);
}

结束共享活动

当用户选择结束 activity 时,endSession 方法会断开与 Meet 应用的连接。这不会强制 Meet 结束会议,也不会导致用户退出会议。

以下代码示例展示了如何通知用户会话已停止:

Java

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

处理传入的更新

当其他参与者的 Meet 应用收到广播时,会触发 onGlobalStateChanged() 回调。通常情况下,您应做出明智的决策,决定在响应传入的更新时应采取什么措施,例如仅匹配传入视频的时间戳(如果它们与本地时间戳明显不同)。

以下代码示例展示了如何处理不同的传入更新:

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();
    }
  }
}