Live Share core capabilities

Screenshot shows an example of users playing agile poker game in a Teams meeting, which showcases the Live share capability.

The Live Share SDK can be added to your meeting extension's sidePanel and meetingStage contexts with minimal effort.

This article focuses on how to integrate the Live Share SDK into your app and key capabilities of the SDK.

Prerequisites

Install the JavaScript SDK

The Live Share SDK is a JavaScript package published on npm, and you can download through npm or yarn. You must also install Live Share peer dependencies, which include fluid-framework and @fluidframework/azure-client. If you're using Live Share in your tab application, you must also install @microsoft/teams-js version 2.11.0 or later.

npm

npm install @microsoft/live-share fluid-framework @fluidframework/azure-client --save
npm install @microsoft/teams-js --save

yarn

yarn add @microsoft/live-share fluid-framework @fluidframework/azure-client
yarn add @microsoft/teams-js

Register RSC permissions

To enable the Live Share SDK for your meeting extension, you must first add the following RSC permissions into your app manifest:

{
  // ...rest of your manifest here
  "configurableTabs": [
    {
        "configurationUrl": "<<YOUR_CONFIGURATION_URL>>",
        "canUpdateConfiguration": true,
        "scopes": [
            "groupchat"
        ],
        "context": [
            "meetingSidePanel",
            "meetingStage"
        ]
    }
  ],
  "validDomains": [
    "<<BASE_URI_ORIGIN>>"
  ],
  "authorization": {​
    "permissions": {​
      "resourceSpecific": [
        // ...other permissions here​
        {​
          "name": "LiveShareSession.ReadWrite.Chat",​
          "type": "Delegated"
        },
        {​
          "name": "LiveShareSession.ReadWrite.Group",​
          "type": "Delegated"
        },
        {​
          "name": "MeetingStage.Write.Chat",​
          "type": "Delegated"
        },
        {​
          "name": "ChannelMeetingStage.Write.Group",​
          "type": "Delegated"
        }
      ]​
    }​
  }​
}

Join a meeting session

Follow the steps to join a session that's associated with a user's meeting:

  1. Initialize LiveShareClient.
  2. Define the data structures you want to synchronize. For example, LiveState or SharedMap.
  3. Join the container.

Example:

import { LiveShareClient, LiveState } from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js";
import { SharedMap } from "fluid-framework";

// Join the Fluid container
const host = LiveShareHost.create();
const liveShare = new LiveShareClient(host);
const schema = {
  initialObjects: {
    liveState: LiveState,
    sharedMap: SharedMap,
  },
};
const { container } = await liveShare.joinContainer(schema);

// ... ready to start app sync logic

That's all it took to setup your container and join the meeting's session. Now, let's review the different types of distributed data structures that you can use with the Live Share SDK.

Tip

Ensure that the Teams Client SDK is initialized before calling LiveShareHost.create().

Live Share data structures

The Live Share SDK includes a set of new distributed-data structures that extend Fluid's DataObject class, providing new types of stateful and stateless objects. Unlike Fluid data structures, Live Share's LiveDataObject classes don’t write changes to the Fluid container, enabling faster synchronization. Further, these classes were designed from the ground up for common meeting scenarios in Teams meetings. Common scenarios include synchronizing what content the presenter is viewing, displaying metadata for each user in the meeting, or displaying a countdown timer.

Live Object Description
LivePresence See which users are online, set custom properties for each user, and broadcast changes to their presence.
LiveState Synchronize any JSON serializable state value.
LiveTimer Synchronize a countdown timer for a given interval.
LiveEvent Broadcast individual events with any custom data attributes in the payload.

LivePresence example

Screenshot shows an example of showing people who available in a sessionTeams using Live Share presence.

The LivePresence class makes tracking who is in the session easier than ever. When calling the .initialize() or .updatePresence() methods, you can assign custom metadata for that user, such as profile picture, the identifier for content they're viewing, and more. By listening to presenceChanged events, each client receives the latest LivePresenceUser object, collapsing all presence updates into a single record for each unique userId.

The following are a few examples in which LivePresence can be used in your application:

  • Getting the Microsoft Teams userId, displayName, and roles of each user in the session.
  • Displaying custom information about each user connected to the session, such as a profile picture URL.
  • Synchronizing the coordinates in a 3D scene where each user's avatar is located.
  • Reporting each user's cursor position in a text document.
  • Posting each user's answer to an ice-breaker question during a group activity.
import {
  LiveShareClient,
  LivePresence,
  PresenceState,
} from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js";

// Join the Fluid container
const host = LiveShareHost.create();
const liveShare = new LiveShareClient(host);
const schema = {
  initialObjects: {
    presence: LivePresence,
  },
};
const { container } = await liveShare.joinContainer(schema);
const presence = container.initialObjects.presence;

// Register listener for changes to each user's presence.
// This should be done before calling `.initialize()`.
presence.on("presenceChanged", (user, local) => {
  console.log("A user presence changed:")
  console.log("- display name:", user.displayName);
  console.log("- state:", user.state);
  console.log("- custom data:", user.data);
  console.log("- change from local client", local);
  console.log("- change impacts local user", user.isLocalUser);
});

// Define the initial custom data for the local user (optional).
const customUserData = {
  picture: "DEFAULT_PROFILE_PICTURE_URL",
  readyToStart: false,
};
// Start receiving incoming presence updates from the session.
// This will also broadcast the user's `customUserData` to others in the session.
await presence.initialize(customUserData);

// Send a presence update, in this case once a user is ready to start an activity.
// If using role verification, this will throw an error if the user doesn't have the required role.
await presence.update({
  ...customUserData,
  readyToStart: true,
});

Users joining a session from a single device have a single LivePresenceUser record that is shared to all their devices. To access the latest data and state for each of their active connections, you can use the getConnections() API from the LivePresenceUser class. This returns a list of LivePresenceConnection objects. You can see if a given LivePresenceConnection instance is from the local device using the isLocalConnection property.

Each LivePresenceUser and LivePresenceConnection instance has a state property, which can be either online, offline, or away. An presenceChanged event is emitted when a user's state changes. For example, if a user leaves a meeting, their state changes to offline.

Note

It can take up to 20 seconds for an LivePresenceUser's state to update to offline after leaving a meeting.

LiveState example

Screenshot shows an example of Live Share state to synchronize what planet in the solar system is actively presented to the meeting.

The LiveState class enables synchronizing simple application state for everyone in a meeting. LiveState synchronizes a single state value, allowing you to synchronize any JSON serializable value, such as a string, number, or object.

The following are a few examples in which LiveState can be used in your application:

  • Setting the user identifier of the current presenter to build a take control feature.
  • Synchronizing the current route path for your application to ensure everyone is on the same page. For example, /whiteboard/:whiteboardId.
  • Maintaining the content identifier that the current presenter is viewing. For example, an taskId on a task board.
  • Synchronizing the current step in a multi-round group activity. For example, the guessing phase during the Agile Poker game.
  • Keeping a scroll position in sync for a follow me feature.

Note

Unlike SharedMap, the state value in LiveState will be reset after all the users disconnect from a session.

Example:

import { LiveShareClient, LiveState } from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js";

// Join the Fluid container
const host = LiveShareHost.create();
const liveShare = new LiveShareClient(host);
const schema = {
  initialObjects: { appState: LiveState },
};
const { container } = await liveShare.joinContainer(schema);
const { appState } = container.initialObjects;

// Register listener for changes to the state.
// This should be done before calling `.initialize()`.
appState.on("stateChanged", (planetName, local, clientId) => {
  // Update app with newly selected planet.
  // To know which user made this change, you can pass the `clientId` to the `getUserForClient()` API from the `LivePresence` class.
});

// Set a default value and start listening for changes.
// This default value will not override existing for others in the session.
const defaultState = "Mercury";
await appState.initialize(defaultState);

// `.set()` will change the state for everyone in the session.
// If using role verification, this will throw an error if the user doesn't have the required role.
await appState.set("Earth");

LiveEvent example

Screenshot shows an example of Teams client displaying notification when there's a change in the event.

LiveEvent is a great way to send simple events to other clients in a meeting that are only needed at the time of delivery. It's useful for scenarios like sending session notifications or implementing custom reactions.

import { LiveEvent, LiveShareClient } from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js";

// Join the Fluid container
const host = LiveShareHost.create();
const liveShare = new LiveShareClient(host);
const schema = {
  initialObjects: { customReactionEvent: LiveEvent },
};
const { container } = await liveShare.joinContainer(schema);
const { customReactionEvent } = container.initialObjects;

// Register listener to receive events sent through this object.
// This should be done before calling `.initialize()`.
customReactionEvent.on("received", (kudosReaction, local, clientId) => {
  console.log("Received reaction:", kudosReaction, "from clientId", clientId);
  // To know which user made this change, you can pass the `clientId` to the `getUserForClient()` API from the `LivePresence` class.
  // Display notification in your UI
});

// Start listening for incoming events
await customReactionEvent.initialize();

// `.send()` will send your event value to everyone in the session.
// If using role verification, this will throw an error if the user doesn't have the required role.
const kudosReaction = {
  emoji: "❤️",
  forUserId: "SOME_OTHER_USER_ID",
};
await customReactionEvent.send(kudosReaction);

LiveTimer example

Screenshot shows an example of a count down timer with 9 seconds remaining.

LiveTimer provides a simple countdown timer that is synchronized for everyone in a meeting. It’s useful for scenarios that have a time limit, such as a group meditation timer or a round timer for a game.

import { LiveShareClient, LiveTimer } from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js";

// Join the Fluid container
const host = LiveShareHost.create();
const liveShare = new LiveShareClient(host);
const schema = {
  initialObjects: { timer: LiveTimer },
};
const { container } = await liveShare.joinContainer(schema);
const { timer } = container.initialObjects;

// Register listeners for timer changes
// This should be done before calling `.initialize()`.

// Register listener for when the timer starts its countdown
timer.on("started", (config, local) => {
  // Update UI to show timer has started
});

// Register listener for when a paused timer has resumed
timer.on("played", (config, local) => {
  // Update UI to show timer has resumed
});

// Register listener for when a playing timer has paused
timer.on("paused", (config, local) => {
  // Update UI to show timer has paused
});

// Register listener for when a playing timer has finished
timer.on("finished", (config) => {
  // Update UI to show timer is finished
});

// Register listener for the timer progressed by 20 milliseconds
timer.on("onTick", (milliRemaining) => {
  // Update UI to show remaining time
});

// Start synchronizing timer events for users in session
await timer.initialize();

// Start a 60 second timer for users in the session.
// If using role verification, this will throw an error if the user doesn't have the required role.
const durationInMilliseconds = 1000 * 60;
await timer.start(durationInMilliseconds);

// Pause the timer for users in session
// If using role verification, this will throw an error if the user doesn't have the required role.
await timer.pause();

// Resume the timer for users in session
// If using role verification, this will throw an error if the user doesn't have the required role.
await timer.play();

Role verification for live data structures

Meetings in Teams include calls, all-hands meetings, and online classrooms. Meeting participants might span across organizations, have different privileges, or simply have different goals. Hence, it’s important to respect the privileges of different user roles during meetings. Live objects are designed to support role verification, allowing you to define the roles that are allowed to send messages for each individual live object. For example, you could choose that only meeting presenters and organizers can control video playback, but still allow guests and attendees to request videos to watch next.

Note

The LivePresence class doesn't support role verification. The LivePresenceUser object has a getRoles method, which returns the meeting roles for a given user.

In the following example where only presenters and organizers can take control, LiveState is used to synchronize which user is the active presenter:

import {
  LiveShareClient,
  LiveState,
  UserMeetingRole,
} from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js";

// Join the Fluid container
const host = LiveShareHost.create();
const liveShare = new LiveShareClient(host);
const schema = {
  initialObjects: { appState: LiveState },
};
const { container } = await liveShare.joinContainer(schema);
const { appState } = container.initialObjects;

// Register listener for changes to state
appState.on("stateChanged", (state, local) => {
  // Update local app state
});

// Set roles who can change state and start listening for changes
const initialState = {
  documentId: "INITIAL_DOCUMENT_ID",
};
const allowedRoles = [UserMeetingRole.organizer, UserMeetingRole.presenter];
await appState.initialize(initialState, allowedRoles);

async function onSelectEditMode(documentId) {
  try {
    await appState.set({
      documentId,
    });
  } catch (error) {
    console.error(error);
  }
}

async function onSelectPresentMode(documentId) {
  try {
    await appState.set({
      documentId,
      presentingUserId: "LOCAL_USER_ID",
    });
  } catch (error) {
    console.error(error);
  }
}

Listen to your customers to understand their scenarios before implementing role verification into your app, particularly for the Organizer role. There's no guarantee that a meeting organizer be present in the meeting. As a general rule of thumb, all users are either Organizer or Presenter when collaborating within an organization. If a user is an Attendee, it's usually an intentional decision on behalf of a meeting organizer.

In some cases, a user may have multiple roles. For example, an Organizer is also an Presenter. In addition, meeting participants that are external to the tenant hosting the meeting have the Guest role, but may also have Presenter privileges. This provides a lot of flexibility in how you use role verification in your application.

Note

The Live Share SDK isn't supported for Guest users in channel meetings.

Fluid distributed data structures

The Live Share SDK supports any distributed data structure included in Fluid Framework. These features serve as a set of primitives you can use to build robust collaborative scenarios, such as real-time updates of a task list or co-authoring text within an HTML <textarea>.

Unlike the LiveDataObject classes mentioned in this article, Fluid data structures don't reset after your application is closed. This is ideal for scenarios such as the meeting side panel, where users frequently close and reopen your app while using other tabs in the meeting, such as chat.

Fluid Framework officially supports the following types of distributed data structures:

Shared Object Description
SharedMap A distributed key-value store. Set any JSON-serializable object for a given key to synchronize that object for everyone in the session.
SharedSegmentSequence A list-like data structure for storing a set of items (called segments) at set positions.
SharedString A distributed-string sequence optimized for editing the text of documents or text areas.

Let's see how SharedMap works. In this example, we've used SharedMap to build a playlist feature.

import { LiveShareClient } from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js";
import { SharedMap } from "fluid-framework";

// Join the Fluid container
const host = LiveShareHost.create();
const liveShare = new LiveShareClient(host);
const schema = {
  initialObjects: { playlistMap: SharedMap },
};
const { container } = await liveShare.joinContainer(schema);
const playlistMap = container.initialObjects.playlistMap as SharedMap;

// Register listener for changes to values in the map
playlistMap.on("valueChanged", (changed, local) => {
  const video = playlistMap.get(changed.key);
  // Update UI with added video
});

function onClickAddToPlaylist(video) {
  // Add video to map
  playlistMap.set(video.id, video);
}

Note

Core Fluid Framework DDS objects don't support meeting role verification. Everyone in the meeting can change the data stored through these objects.

Code samples

Sample name Description JavaScript
Dice Roller Enable all connected clients to roll a die and view the result. View
Agile Poker Enable all connected clients to play Agile Poker. View

Next step

See also