This page describes how to use the Co-Watching API.
Initial setup
Before you begin, the live sharing application should initialize a
CoWatchingClient
object to prepare the library for use.
The following code sample shows a basic initialization of the client object:
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(
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 code sample shows how to notify users:
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 code sample 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));
}
}