Live Share core capabilities
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:
- Initialize
LiveShareClient
. - Define the data structures you want to synchronize. For example,
LiveState
orSharedMap
. - 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
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
, androles
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
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
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
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
Feedback
Submit and view feedback for