Updates from: 05/25/2023 02:17:15
Service Microsoft Docs article Related commit history on GitHub Change details
platform Meeting Apps Apis https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/apps-in-teams-meetings/meeting-apps-apis.md
The following table provides the response codes:
* [Get the attendance report for an online meeting](/graph/api/meetingattendancereport-get) * [Build in-meeting notification for Teams meeting](in-meeting-notification-for-meeting.md) * [Get notifications for Teams meeting call updates](/graph/changenotifications-for-onlinemeeting)
+* [Get participants presence API](/graph/api/presence-get?&tabs=http)
platform Teams Live Share Canvas https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/apps-in-teams-meetings/teams-live-share-canvas.md
To enable more seamless collaboration, Microsoft created PowerPoint Live, which
## Install
-To add the latest version of the SDK to your application using npm:
+Live Share canvas is a JavaScript package published on [npm](https://www.npmjs.com/package/@microsoft/live-share-media), and you can download through npm or yarn. You must also install its peer dependencies, which includes `@microsoft/live-share`, `fluid-framework`, and `@fluidframework/azure-client`. If you are using Live Share in your tab application, you must also install `@microsoft/teams-js` version `2.11.0` or later.
```bash
-npm install @microsoft/live-share@next --save
-npm install @microsoft/live-share-canvas@next --save
+npm install @microsoft/live-share @microsoft/live-share-canvas fluid-framework @fluidframework/azure-client --save
+npm install @microsoft/teams-js --save
``` OR
OR
To add the latest version of the SDK to your application using [Yarn](https://yarnpkg.com/): ```bash
-yarn add @microsoft/live-share@next
-yarn add @microsoft/live-share-canvas@next
+yarn add @microsoft/live-share @microsoft/live-share-canvas fluid-framework @fluidframework/azure-client
+yarn add @microsoft/teams-js
``` ## Setting up the package
await liveCanvas.initialize(inkingManager);
inkingManager.activate(); ```
+# [React](#tab/react-js)
+
+```jsx
+import { useLiveCanvas } from "@microsoft/live-share-react";
+import { InkingTool } from "@microsoft/live-share-canvas";
+import { useRef } from "react";
+
+// Unique identifier that distinguishes this useLiveCanvas from others in your app
+const UNIQUE_KEY = "CUSTOM-LIVE-CANVAS";
+
+// Example component
+export const ExampleLiveCanvas = () => {
+ const liveCanvasRef = useRef(null);
+ const { liveCanvas, inkingManager } = useLiveCanvas(
+ "CUSTOM-LIVE-CANVAS",
+ liveCanvasRef,
+ );
+
+ return (
+ {/** Canvas currently needs to be a child of a parent with absolute styling */}
+ <div style={{ position: "absolute"}}>
+ <div
+ ref={liveCanvasRef}
+ // Best practice is to not define inline styles
+ style={{ width: "556px", height: "224px" }}
+ />
+ {!!liveCanvas && (
+ <div>
+ <button
+ onClick={() => {
+ inkingManager.tool = InkingTool.pen;
+ }}
+ >
+ {"Pen"}
+ </button>
+ <button
+ onClick={() => {
+ inkingManager.tool = InkingTool.laserPointer;
+ }}
+ >
+ {"Laser pointer"}
+ </button>
+ </div>
+ )}
+ </div>
+ );
+};
+```
+ ## Canvas tools and cursors
document.getElementById("line-arrow").onclick = () => {
inkingManager.lineBrush.endArrow = "open"; }; // Change the selected color for lineBrush
-document.getElementById("line-color").onchange = () => {
+document.getElementById("line-color").onclick = () => {
const colorPicker = document.getElementById("line-color"); inkingManager.lineBrush.color = fromCssColor(colorPicker.value); };
You can enable live cursors in your application for users to track each other's
```javascript // Optional. Set user display info
-liveCanvas.onGetLocalUserInfo = () => {
- return {
- displayName: "YOUR USER NAME",
- pictureUri: "YOUR USER PICTURE URI",
- };
-};
+liveCanvas.onGetLocalUserPictureUrl = () => "YOUR USER PICTURE URI";
// Toggle Live Canvas cursor enabled state liveCanvas.isCursorShared = !isCursorShared; ```
platform Teams Live Share Capabilities https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/apps-in-teams-meetings/teams-live-share-capabilities.md
Last updated 04/07/2022
The Live Share SDK can be added to your meeting extension's `sidePanel` and `meetingStage` contexts with minimal effort.
-> [!NOTE]
-> The Live Share SDK isn't supported for anonymous users.
- This article focuses on how to integrate the Live Share SDK into your app and key capabilities of the SDK.
-## Install the JavaScript SDK
+## Prerequisites
+
+### Install the JavaScript SDK
-The [Live Share SDK](https://github.com/microsoft/live-share-sdk) is a JavaScript package published on [npm](https://www.npmjs.com/package/@microsoft/live-share), and you can download through npm or Yarn.
+The [Live Share SDK](https://github.com/microsoft/live-share-sdk) is a JavaScript package published on [npm](https://www.npmjs.com/package/@microsoft/live-share), 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
```bash
-npm install @microsoft/live-share@next --save
+npm install @microsoft/live-share fluid-framework @fluidframework/azure-client --save
+npm install @microsoft/teams-js --save
```
-### yarn
+#### yarn
```bash
-yarn add @microsoft/live-share@next
+yarn add @microsoft/live-share fluid-framework @fluidframework/azure-client
+yarn add @microsoft/teams-js
```
-## Register RSC permissions
+### 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:
To enable the Live Share SDK for your meeting extension, you must first add the
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, `SharedMap`.
+2. Define the data structures you want to synchronize. For example, `LiveState` or `SharedMap`.
3. Join the container. Example:
Example:
# [JavaScript](#tab/javascript) ```javascript
-import { LiveShareClient } from "@microsoft/live-share";
+import { LiveShareClient, LiveState } from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js"; import { SharedMap } from "fluid-framework";
import { SharedMap } from "fluid-framework";
const host = LiveShareHost.create(); const liveShare = new LiveShareClient(host); const schema = {
- initialObjects: { exampleMap: SharedMap },
+ initialObjects: {
+ liveState: LiveState,
+ sharedMap: SharedMap,
+ },
}; const { container } = await liveShare.joinContainer(schema);
const { container } = await liveShare.joinContainer(schema);
# [TypeScript](#tab/typescript) ```TypeScript
-import { LiveShareClient } from "@microsoft/live-share";
+import { LiveShareClient, LiveState } from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js"; import { ContainerSchema, SharedMap } from "fluid-framework";
import { ContainerSchema, SharedMap } from "fluid-framework";
const host = LiveShareHost.create(); const liveShare = new LiveShareClient(host); const schema: ContainerSchema = {
- initialObjects: { exampleMap: SharedMap },
+ initialObjects: {
+ exampleMap: SharedMap,
+ liveState: LiveState,
+ },
}; const { container } = await liveShare.joinContainer(schema); // ... ready to start app sync logic ```
+# [React](#tab/react)
+
+```jsx
+import { LiveShareHost } from "@microsoft/teams-js";
+import { LiveShareProvider, useLiveShareContext } from "@microsoft/live-share-react";
+import { useState } from "react";
+
+export const App = () => {
+ // Create the host as React state so that it doesn't get reset on mount
+ const [host] = useState(LiveShareHost.create());
+
+ // Live Share for React does not require that you define a custom Fluid schema
+ return (
+ <LiveShareProvider host={host} joinOnLoad>
+ <LiveShareLoading />
+ </LiveShareProvider>
+ );
+}
+
+const LiveShareLoading = () => {
+ // Any live-share-react hook (e.g., useLiveShareContext, useLiveState, etc.) must be a child of <LiveShareProvider>
+ const { joined } = useLiveShareContext();
+ if (joined) {
+ return <p>{"Loading..."}</p>;
+ }
+ return <p>{"Your app here..."}</p>;
+}
+```
+ 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 using the Live Share APIs.
+> Ensure that the Teams Client SDK is initialized before calling `LiveShareHost.create()`.
-## Fluid distributed data structures
+## Live Share data structures
-The Live Share SDK supports any [distributed data structure](https://fluidframework.com/docs/data-structures/overview/) 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>`.
+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.
-Following are the different types of objects available:
+| Live Object | Description |
+| | |
+| [LivePresence](/javascript/api/@microsoft/live-share/livepresence) | See which users are online, set custom properties for each user, and broadcast changes to their presence. |
+| [LiveState](/javascript/api/@microsoft/live-share/livestate) | Synchronize any JSON serializable `state` value. |
+| [LiveTimer](/javascript/api/@microsoft/live-share/livetimer) | Synchronize a countdown timer for a given interval. |
+| [LiveEvent](/javascript/api/@microsoft/live-share/liveevent) | Broadcast individual events with any custom data attributes in the payload. |
-| Shared Object | Description |
-| -- | |
-| [SharedMap](https://fluidframework.com/docs/data-structures/map/) | A distributed key-value store. Set any JSON-serializable object for a given key to synchronize that object for everyone in the session. |
-| [SharedSegmentSequence](https://fluidframework.com/docs/data-structures/sequences/) | A list-like data structure for storing a set of items (called segments) at set positions. |
-| [SharedString](https://fluidframework.com/docs/data-structures/string/) | Distributed-string sequence optimized for editing the text of documents or text areas. |
+### LivePresence example
-Let's see how `SharedMap` works. In this example, we've used `SharedMap` to build a playlist feature.
+
+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.
# [JavaScript](#tab/javascript) ```javascript
-import { LiveShareClient } from "@microsoft/live-share";
+import {
+ LiveShareClient,
+ LivePresence,
+ PresenceState,
+} 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 },
+ initialObjects: {
+ presence: LivePresence,
+ },
}; const { container } = await liveShare.joinContainer(schema);
-const playlistMap = container.initialObjects.playlistMap as SharedMap;
+const presence = container.initialObjects.presence;
-// 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
+// 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);
});
-function onClickAddToPlaylist(video) {
- // Add video to map
- playlistMap.set(video.id, video);
-}
+// 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,
+});
``` # [TypeScript](#tab/typescript) ```TypeScript
-import { LiveShareClient } from "@microsoft/live-share";
+import {
+ LiveShareClient,
+ LivePresence,
+ PresenceState,
+ LivePresenceUser,
+} from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js";
-import { ContainerSchema, SharedMap, IValueChanged } from "fluid-framework";
+
+// Declare interface for type of custom data for user
+interface ICustomUserData {
+ picture: string;
+ readyToStart: boolean;
+}
// Join the Fluid container const host = LiveShareHost.create(); const liveShare = new LiveShareClient(host);
-const schema: ContainerSchema = {
- initialObjects: { exampleMap: SharedMap },
+const schema = {
+ initialObjects: {
+ presence: LivePresence<ICustomUserData>,
+ },
}; const { container } = await liveShare.joinContainer(schema);
-const playlistMap = container.initialObjects.playlistMap as SharedMap;
+const presence = container.initialObjects.presence as LivePresence<ICustomUserData>;
-// Declare interface for object being stored in map
-interface IVideo {
- id: string;
- url: string;
-}
+// Register listener for changes to each user's presence.
+// This should be done before calling `.initialize()`.
+presence.on("presenceChanged", (user: LivePresenceUser<ICustomUserData>, local: boolean) => {
+ console.log("A user presence changed:")
+ console.log("- display name:", user.displayName);
+ console.log("- custom data:", user.data);
+ console.log("- state:", user.state);
+ console.log("- roles", user.roles);
+ console.log("- change from local client", local);
+ console.log("- change impacts local user", user.isLocalUser);
+});
-// Register listener for changes to values in the map
-playlistMap.on("valueChanged", (changed: IValueChanged, local: boolean) => {
- const video: IVideo | undefined = playlistMap.get(changed.key);
- // Update UI with added video
+// Define the initial custom data for the local user (optional).
+const customUserData: ICustomUserData = {
+ 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({
+ ...initialData,
+ readyToStart: true,
});
+```
-function onClickAddToPlaylist(video: IVideo) {
- // Add video to map
- playlistMap.set(video.id, video);
+# [React](#tab/react)
+
+```jsx
+import { useLivePresence } from "@microsoft/live-share-react";
+
+// Define a unique key that differentiates this usage of `useLivePresence` from others in your app
+const MY_UNIQUE_KEY = "presence-key";
+
+// Example component for using useLivePresence
+export const MyCustomPresence = () => {
+ const { allUsers, localUser, updatePresence } = useLivePresence(MY_UNIQUE_KEY, {
+ picture: "DEFAULT_PROFILE_PICTURE_URL",
+ readyToStart: false,
+ });
+
+ // Callback to update the user's presence
+ const onToggleReady = () => {
+ updatePresence({
+ ...localUser.data,
+ readyToStart: !localUser.data.readyToStart,
+ });
+ }
+
+ // Render UI
+ return (
+ {allUsers.map((user) => (
+ <div key={user.userId}>
+ <div>
+ {user.displayName}
+ </div>
+ <div>
+ {`Ready: ${user.data.readyToStart}`}
+ </div>
+ {user.isLocalUser && (
+ <button onClick={onToggleReady}>
+ {"Toggle ready"}
+ </button>
+ )}
+ </div>
+ ))}
+ );
} ```
-> [!NOTE]
-> Core Fluid Framework DDS objects don't support meeting role verification. Everyone in the meeting can change data stored through these objects.
-
-## Live Share data structures
+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.
-The Live Share SDK includes a set of new distributed-data structures that extend Fluid's `SharedObject` class, providing new types of stateful and stateless objects. Unlike Fluid data structures, Live Share's `SharedObject` 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.
+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`.
-| Live Object | Description |
-| -- | |
-| `LivePresence` | See which users are online, set custom properties for each user, and broadcast changes to their presence. |
-| `LiveEvent` | Broadcast individual events with any custom data attributes in the payload. |
-| `LiveState` | Similar to SharedMap, a distributed key-value store that allows for restricted state changes based on role, for example, the presenter. |
-| `LiveTimer` | Synchronize a countdown timer for a given interval. |
+> [!NOTE]
+> It can take up to 20 seconds for an `LivePresenceUser`'s `state` to update to `offline` after leaving a meeting.
-### LivePresence example
+### LiveState 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 name, profile picture, or the identifier for content they are viewing. 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 `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 `LivePresence` can be used in your application:
+The following are a few examples in which `LiveState` can be used in your application:
-- Displaying profile pictures and names of each user connected to the session.-- 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.
+- 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]
-> The default `userId` assigned to each `LivePresenceUser` is a random UUID and is not directly tied to an AAD identity. You can override this by setting a custom `userId` to be the primary key, as shown in the example below.
+> Unlike `SharedMap`, the `state` value in `LiveState` will be reset after all the users disconnect from a session.
+
+Example:
# [JavaScript](#tab/javascript) ```javascript
-import {
- LiveShareClient,
- LivePresence,
- PresenceState,
-} from "@microsoft/live-share";
+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: {
- presence: LivePresence,
- },
+ initialObjects: { appState: LiveState },
}; const { container } = await liveShare.joinContainer(schema);
-const presence = container.initialObjects.presence;
+const { appState } = container.initialObjects;
-// Register listener for changes to presence
-presence.on("presenceChanged", (userPresence, local) => {
- // Update UI with presence
+// 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.
});
-// Start tracking presence
-presence.initialize("YOUR_CUSTOM_USER_ID", {
- name: "Anonymous",
- picture: "DEFAULT_PROFILE_PICTURE_URL",
-});
+// 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);
-function onUserDidLogIn(userName, profilePicture) {
- presence.updatePresence(PresenceState.online, {
- name: userName,
- picture: profilePicture,
- });
-}
+// `.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");
``` # [TypeScript](#tab/typescript) ```TypeScript
-import {
- LiveShareClient,
- LivePresence,
- PresenceState,
- LivePresenceUser,
-} from "@microsoft/live-share";
+import { LiveShareClient, LiveState } from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js";
-// Declare interface for type of custom data for user
-interface ICustomUserData {
- name: string;
- picture: string;
+enum PlanetName {
+ MERCURY = "Mercury",
+ VENUS = "Venus",
+ EARTH = "Earth",
+ MARS = "Mars",
+ JUPITER = "Jupiter",
+ SATURN = "Saturn",
+ URANUS = "Uranus",
+ NEPTUNE = "Neptune",
} // Join the Fluid container
const host = LiveShareHost.create();
const liveShare = new LiveShareClient(host); const schema = { initialObjects: {
- presence: LivePresence<ICustomUserData>,
+ appState: LiveState<PlanetName>,
}, }; const { container } = await liveShare.joinContainer(schema);
-const presence = container.initialObjects.presence as LivePresence<ICustomUserData>;
+const appState = container.initialObjects.appState as LiveState<PlanetName>;
-// Register listener for changes to presence
-presence.on("presenceChanged", (userPresence: LivePresenceUser<ICustomUserData>, local: boolean) => {
- // Update UI with presence
+// Register listener for changes to the state.
+// This should be done before calling `.initialize()`.
+appState.on("stateChanged", (planetName: PlanetName, local: boolean, clientId: string) => {
+ // 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.
});
-// Start tracking presence
-presence.initialize("YOUR_CUSTOM_USER_ID", {
- name: "Anonymous",
- picture: "DEFAULT_PROFILE_PICTURE_URL",
-});
+// Set a default value and start listening for changes.
+// This default value will not override existing for others in the session.
+const defaultState = PlanetName.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(PlanetName.EARTH);
+```
-function onUserDidLogIn(userName: string, profilePicture: string) {
- presence.updatePresence(PresenceState.online, {
- name: userName,
- picture: profilePicture,
- });
+# [React](#tab/react)
+
+```jsx
+import { useLiveState } from "@microsoft/live-share-react";
+
+const planets = [
+ "Mercury",
+ "Venus",
+ "Earth",
+ "Mars",
+ "Jupiter",
+ "Saturn",
+ "Uranus",
+ "Neptune",
+];
+
+// Define a unique key that differentiates this usage of `useLiveState` from others in your app
+const MY_UNIQUE_KEY = "selected-planet-key";
+
+// Example component for using useLiveState
+export const MyCustomState = () => {
+ const [planet, setPlanet] = useLiveState(MY_UNIQUE_KEY, planets[0]);
+
+ // Render UI
+ return (
+ <div>
+ {`Current planet: ${planet}`}
+ {'Select a planet below:'}
+ {planets.map((planet) => (
+ <button key={planet} onClick={() => {
+ setPlanet(planet);
+ }}>
+ {planet}
+ </button>
+ ))}
+ </div>
+ );
} ```
import { LiveShareHost } from "@microsoft/teams-js";
const host = LiveShareHost.create(); const liveShare = new LiveShareClient(host); const schema = {
- initialObjects: { notifications: LiveEvent },
+ initialObjects: { customReactionEvent: LiveEvent },
}; const { container } = await liveShare.joinContainer(schema);
-const { notifications } = container.initialObjects;
-
-// Register listener for incoming notifications
-notifications.on("received", (event, local) => {
- let notificationToDisplay;
- if (local) {
- notificationToDisplay = `You ${event.text}`;
- } else {
- notificationToDisplay = `${event.senderName} ${event.text}`;
- }
+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 notifications
-await notifications.initialize();
+// Start listening for incoming events
+await customReactionEvent.initialize();
-notifications.sendEvent({
- senderName: "LOCAL_USER_NAME",
- text: "joined the session",
-});
+// `.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);
``` # [TypeScript](#tab/typescript) ```TypeScript
-import { LiveShareClient, LiveEvent, ILiveEvent } from "@microsoft/live-share";
+import { LiveShareClient, LiveEvent } from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js"; // Declare interface for type of custom data for user
-interface ICustomEvent extends ILiveEvent {
- senderName: string;
- text: string;
+interface ICustomReaction {
+ emoji: string,
+ forUserId: "SOME_OTHER_USER_ID",
} // Join the Fluid container
const host = LiveShareHost.create();
const liveShare = new LiveShareClient(host); const schema = { initialObjects: {
- notifications: LiveEvent<ICustomEvent>,
+ customReactionEvent: LiveEvent<ICustomEvent>,
}, }; const { container } = await liveShare.joinContainer(schema);
-const notifications = container.initialObjects.notifications as LiveEvent<ICustomEvent>;
-
-// Register listener for incoming notifications
-notifications.on("received", (event: ICustomEvent, local: boolean) => {
- let notificationToDisplay: string;
- if (local) {
- notificationToDisplay = `You ${event.text}`;
- } else {
- notificationToDisplay = `${event.senderName} ${event.text}`;
- }
+const customReactionEvent = container.initialObjects.customReactionEvent as LiveEvent<ICustomReaction>;
+
+// Register listener to receive events sent through this object.
+// This should be done before calling `.initialize()`.
+customReactionEvent.on("received", async (event: ICustomReaction, local: boolean, clientId: string) => {
+ 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 notifications
-await notifications.initialize();
+// Start listening for incoming events
+await customReactionEvent.initialize();
-notifications.sendEvent({
- senderName: "LOCAL_USER_NAME",
- text: "joined the session",
-});
+// `.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: ICustomReaction = {
+ emoji: "❤️",
+ forUserId: "SOME_OTHER_USER_ID",
+};
+await customReactionEvent.send(kudosReaction);
+```
+
+# [React](#tab/react)
+
+```jsx
+import { useLiveEvent } from "@microsoft/live-share-react";
+
+const emojis = [
+ "❤️",
+ "😂",
+ "👍",
+ "👎",
+];
+
+// Define a unique key that differentiates this usage of `useLiveEvent` from others in your app
+const MY_UNIQUE_KEY = "event-key";
+
+// Example component for using useLiveEvent
+export const MyCustomEvent = () => {
+ const {
+ latestEvent,
+ sendEvent,
+ } = useLiveEvent(MY_UNIQUE_KEY);
+
+ // Render UI
+ return (
+ <div>
+ {`Latest event: ${latestEvent?.value}, from local user: ${latestEvent?.local}`}
+ {'Select a planet below:'}
+ {emojis.map((emoji) => (
+ <button key={emoji} onClick={() => {
+ sendEvent(emoji);
+ }}>
+ {emoji}
+ </button>
+ ))}
+ </div>
+ );
+}
```
const schema = {
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
timer.on("onTick", (milliRemaining) => {
// Start synchronizing timer events for users in session await timer.initialize();
-// Start a 60 second timer
+// 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;
-timer.start(durationInMilliseconds);
+await timer.start(durationInMilliseconds);
// Pause the timer for users in session
-timer.pause();
+// 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
-timer.play();
+// If using role verification, this will throw an error if the user doesn't have the required role.
+await timer.play();
``` # [TypeScript](#tab/typescript)
const schema = {
const { container } = await liveShare.joinContainer(schema); const timer = container.initialObjects.timer as LiveTimer;
+// Register listeners for timer changes
+// This should be done before calling `.initialize()`.
+ // Register listener for when the timer starts its countdown timer.on(LiveTimerEvents.started, (config: ITimerConfig, local: boolean) => { // Update UI to show timer has started
timer.on(LiveTimerEvents.onTick, (milliRemaining: number) => {
await timer.initialize(); // Start a 60 second timer for users in session
+// If using role verification, this will throw an error if the user doesn't have the required role.
const durationInMilliseconds = 1000 * 60;
-timer.start(durationInMilliseconds);
+await timer.start(durationInMilliseconds);
// Pause the timer for users in session
-timer.pause();
+// 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
-timer.play();
+// If using role verification, this will throw an error if the user doesn't have the required role.
+await timer.play();
``` --
-### LiveState example
-
+# [React](#tab/react)
+
+```jsx
+import { useLiveTimer } from "@microsoft/live-share-react";
+
+// Define a unique key that differentiates this usage of `useLiveTimer` from others in your app
+const MY_UNIQUE_KEY = "timer-key";
+
+// Example component for using useLiveTimer
+export function CountdownTimer() {
+ const { milliRemaining, timerConfig, start, pause, play } = useLiveTimer("TIMER-ID");
+
+ return (
+ <div>
+ <button
+ onClick={() => {
+ start(60 * 1000);
+ }}
+ >
+ { timerConfig === undefined ? "Start" : "Reset" }
+ </button>
+ { timerConfig !== undefined && (
+ <button
+ onClick={() => {
+ if (timerConfig.running) {
+ pause();
+ } else {
+ play();
+ }
+ }}
+ >
+ {timerConfig.running ? "Pause" : "Play" }
+ </button>
+ )}
+ { milliRemaining !== undefined && (
+ <p>
+ { `${Math.round(milliRemaining / 1000)} / ${Math.round(timerConfig.duration) / 1000}` }
+ </p>
+ )}
+ </div>
+ );
+}
+```
-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:
+## Role verification for live data structures
-- Tracking the user identifier of the current presenter to build a **take control** feature.-- 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.
+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]
-> Unlike `SharedMap`, the `state` value in `LiveState` will be reset after all the users disconnect from a session.
+> The `LivePresence` class doesn't support role verification. The `LivePresenceUser` object has a `getRoles` method, which returns the meeting roles for a given user.
-Example:
+In the following example where only presenters and organizers can take control, `LiveState` is used to synchronize which user is the active presenter:
# [JavaScript](#tab/javascript) ```javascript
-import { LiveShareClient, LiveState } from "@microsoft/live-share";
+import {
+ LiveShareClient,
+ LiveState,
+ UserMeetingRole,
+} from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js"; // Join the Fluid container
const { container } = await liveShare.joinContainer(schema);
const { appState } = container.initialObjects; // Register listener for changes to state
-appState.on("stateChanged", (planetName, local) => {
- // Update app with newly selected planet
+appState.on("stateChanged", (state, local) => {
+ // Update local app state
});
-// Set a default value and start listening for changes
-await appState.initialize("Mercury");
+// 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);
+ }
+}
-function onSelectPlanet(planetName) {
- appState.set(planetName);
+async function onSelectPresentMode(documentId) {
+ try {
+ await appState.set({
+ documentId,
+ presentingUserId: "LOCAL_USER_ID",
+ });
+ } catch (error) {
+ console.error(error);
+ }
} ``` # [TypeScript](#tab/typescript) ```TypeScript
-import { LiveShareClient, LiveState } from "@microsoft/live-share";
+import { LiveShareClient, LiveState, UserMeetingRole } from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js";
-enum PlanetName {
- MERCURY = "Mercury",
- VENUS = "Venus",
- EARTH = "Earth",
- MARS = "Mars",
- JUPITER = "Jupiter",
- SATURN = "Saturn",
- URANUS = "Uranus",
- NEPTUNE = "Neptune",
+// Declare interface for type of custom data for user
+interface ICustomState {
+ documentId: string;
+ presentingUserId?: string;
} // Join the Fluid container
const host = LiveShareHost.create();
const liveShare = new LiveShareClient(host); const schema = { initialObjects: {
- appState: LiveState<PlanetName>,
+ appState: LiveState<ICustomState>,
}, }; const { container } = await liveShare.joinContainer(schema);
-const appState = container.initialObjects.appState as LiveState<PlanetName>;
+const appState = container.initialObjects.appState as LiveState<ICustomState>;
// Register listener for changes to state
-appState.on("stateChanged", (planetName: PlanetName, local: boolean) => {
- // Update app to display an image of the selected planet for the selected solar system
+appState.on("stateChanged", (state: ICustomState, local: boolean) => {
+ // Update local app state
});
-// Set a default value and start listening for changes
-await appState.initialize(PlanetName.MERCURY);
+// Set roles who can change state and start listening for changes
+const initialState: ICustomState = {
+ documentId: "INITIAL_DOCUMENT_ID",
+};
+const allowedRoles: UserMeetingRole[] = [UserMeetingRole.organizer, UserMeetingRole.presenter];
+await appState.initialize(initialState, allowedRoles);
-function onSelectPlanet(planetName: PlanetName) {
- appState.set(planetName);
+async function onSelectEditMode(documentId: string) {
+ try {
+ await appState.set({
+ documentId,
+ });
+ } catch (error: Error) {
+ console.error(error);
+ }
+}
+
+async function onSelectPresentMode(documentId: string) {
+ try {
+ await appState.set({
+ documentId,
+ presentingUserId: "LOCAL_USER_ID",
+ });
+ } catch (error: Error) {
+ console.error(error);
+ }
+}
+```
+
+# [React](#tab/react)
+
+```jsx
+import { useLiveState } from "@microsoft/live-share-react";
+
+// Define a unique key that differentiates this usage of `useLiveState` from others in your app
+const MY_UNIQUE_KEY = "unique-key";
+// Define the initial state
+const INITIAL_STATE = {
+ documentId: "INITIAL_DOCUMENT_ID",
+};
+// Define the allowed roles
+const ALLOWED_ROLES = [UserMeetingRole.organizer, UserMeetingRole.presenter];
+
+// Example component for using useLiveState
+export const MyCustomState = () => {
+ const [state, setState] = useLiveState(MY_UNIQUE_KEY, INITIAL_STATE, ALLOWED_ROLES);
+
+ const onTakeControl = async () => {
+ try {
+ await setState({
+ ...state,
+ presentingUserId: "<LOCAL_USER_ID>",
+ });
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ // Render UI
+ return (
+ <div>
+ {`Current document: ${state.documentId}`}
+ {`Current presenter: ${state.presentingUserId}`}
+ <button onClick={onTakeControl}>
+ Take control
+ </button>
+ </div>
+ );
} ```
-## Role verification for live data structures
+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.
-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.
+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 `LivePresence` class doesn't support role verification. The `LivePresenceUser` object has a `getRoles` method, which returns the meeting roles for a given user.
+> 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](https://fluidframework.com/docs/data-structures/overview/) 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](https://fluidframework.com/docs/data-structures/map/) | A distributed key-value store. Set any JSON-serializable object for a given key to synchronize that object for everyone in the session. |
+| [SharedSegmentSequence](https://fluidframework.com/docs/data-structures/sequences/) | A list-like data structure for storing a set of items (called segments) at set positions. |
+| [SharedString](https://fluidframework.com/docs/data-structures/string/) | A distributed-string sequence optimized for editing the text of documents or text areas. |
-In the following example where only presenters and organizers can take control, `LiveState` is used to synchronize which user is the active presenter.
+Let's see how `SharedMap` works. In this example, we've used `SharedMap` to build a playlist feature.
# [JavaScript](#tab/javascript) ```javascript
-import {
- LiveShareClient,
- LiveState,
- UserMeetingRole,
-} from "@microsoft/live-share";
+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: { appState: LiveState },
+ initialObjects: { playlistMap: SharedMap },
}; const { container } = await liveShare.joinContainer(schema);
-const { appState } = container.initialObjects;
+const playlistMap = container.initialObjects.playlistMap as SharedMap;
-// Register listener for changes to state
-appState.on("stateChanged", (state, local) => {
- // Update local app state
+// 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
});
-// 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);
-
-function onSelectEditMode(documentId) {
- appState.set({
- documentId,
- });
-}
-
-function onSelectPresentMode(documentId) {
- appState.set({
- documentId,
- presentingUserId: "LOCAL_USER_ID",
- });
+function onClickAddToPlaylist(video) {
+ // Add video to map
+ playlistMap.set(video.id, video);
} ``` # [TypeScript](#tab/typescript) ```TypeScript
-import { LiveShareClient, LiveState, UserMeetingRole } from "@microsoft/live-share";
+import { LiveShareClient } from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js";-
-// Declare interface for type of custom data for user
-interface ICustomState {
- documentId: string;
- presentingUserId?: string;
-}
+import { ContainerSchema, SharedMap, IValueChanged } from "fluid-framework";
// Join the Fluid container const host = LiveShareHost.create(); const liveShare = new LiveShareClient(host);
-const schema = {
- initialObjects: {
- appState: LiveState<ICustomState>,
- },
+const schema: ContainerSchema = {
+ initialObjects: { exampleMap: SharedMap },
}; const { container } = await liveShare.joinContainer(schema);
-const appState = container.initialObjects.appState as LiveState<ICustomState>;
+const playlistMap = container.initialObjects.playlistMap as SharedMap;
-// Register listener for changes to state
-appState.on("stateChanged", (state: ICustomState, local: boolean) => {
- // Update local app state
-});
+// Declare interface for object being stored in map
+interface IVideo {
+ id: string;
+ url: string;
+ Title: string;
+}
-// Set roles who can change state and start listening for changes
-const initialState: ICustomState = {
- documentId: "INITIAL_DOCUMENT_ID",
-};
-const allowedRoles: UserMeetingRole[] = [UserMeetingRole.organizer, UserMeetingRole.presenter];
-await appState.initialize(initialState, allowedRoles);
+// Register listener for changes to values in the map
+playlistMap.on("valueChanged", (changed: IValueChanged, local: boolean) => {
+ const video: IVideo | undefined = playlistMap.get(changed.key);
+ // Update UI with added video
+});
-function onSelectEditMode(documentId: string) {
- appState.set({
- documentId,
- });
+function onClickAddToPlaylist(video: IVideo) {
+ // Add video to map
+ playlistMap.set(video.id, video);
}
+```
-function onSelectPresentMode(documentId: string) {
- appState.set({
- documentId,
- presentingUserId: "LOCAL_USER_ID",
- });
+# [React](#tab/react)
+
+```jsx
+import { useSharedMap } from "@microsoft/live-share-react";
+import { v4 as uuid } from "uuid";
+
+// Unique key that distinguishes this useSharedMap from others in your app
+const UNIQUE_KEY = "CUSTOM-MAP-ID"
+
+export function PlaylistMapExample() {
+ const { map, setEntry, deleteEntry } = useSharedMap(UNIQUE_KEY);
+ return (
+ <div>
+ <h2>{"Videos"}</h2>
+ <button
+ onClick={() => {
+ const id = uuid();
+ setEntry(id, {
+ id,
+ url: "<YOUR_VIDEO_URL>",
+ Title: "<VIDEO_TITLE>",
+ });
+ }}
+ >
+ {"+ Add video"}
+ </button>
+ <div>
+ {[...map.values()].map((video) => (
+ <div key={video.id}>
+ {video.title}
+ </div>
+ ))}
+ </div>
+ </div>
+ );
} ```
-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 will be 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.
+> [!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
platform Teams Live Share Faq https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/apps-in-teams-meetings/teams-live-share-faq.md
Get answers to common questions when using Live Share.<br>
<summary><b>Can I use my own Azure Fluid Relay service?</b></summary>
-Yes! When initializing Live Share, you can define your own `AzureConnectionConfig`. Live Share associates containers you create with meetings, but you'll need to implement the `ITokenProvider` interface to sign tokens for your containers. For example, you can use a provided `AzureFunctionTokenProvider`, which uses an Azure cloud function to request an access token from a server.
+Yes! When initializing Live Share, you can define your own `AzureConnectionConfig`. Live Share associates containers you create with meetings, but you need to implement the `ITokenProvider` interface to sign tokens for your containers. For example, you can use a provided `AzureFunctionTokenProvider`, which uses an Azure cloud function to request an access token from a server.
While most of you find it beneficial to use our free hosted service, there may still be times where it's beneficial to use your own Azure Fluid Relay service for your Live Share app. Consider using a custom AFR service connection if you:
For more information, see [how to guide](./teams-live-share-how-to/how-to-custom
<summary><b>How long is data stored in Live Share's hosted service accessible?</b></summary>
-Any data sent or stored through Fluid containers created by Live Share's hosted Azure Fluid Relay service is accessible for 24 hours. If you want to persist data beyond 24 hours, you can replace our hosted Azure Fluid Relay service with your own. Alternatively, you can use your own storage provider in parallel to Live Share's hosted service.
+Any data sent or stored through Fluid containers created by Live Share's hosted Azure Fluid Relay service might be accessible for up to 24 hours, though in most cases it's deleted within six hours. If you want to persist data beyond 24 hours, you can replace our hosted Azure Fluid Relay service with your own. Alternatively, you can use your own storage provider in parallel to Live Share's hosted service.
<br>
Currently, Live Share supports a maximum of 100 attendees per session. If this i
<details> <summary><b>Can I use Live Share's data structures outside of Teams?</b></summary>
-Currently, Live Share packages require the Teams Client SDK to function properly. Features in `@microsoft/live-share` or `@microsoft/live-share-media` won't work outside Microsoft Teams. If this is something you're interested in, you can [start a discussion here](https://github.com/microsoft/live-share-sdk/discussions).
+Currently, Live Share packages require the Teams Client SDK to function properly. Features in `@microsoft/live-share` or `@microsoft/live-share-media` don't work outside Microsoft Teams. If this is something you're interested in, you can [start a discussion here](https://github.com/microsoft/live-share-sdk/discussions).
<br>
Currently, Live Share doesn't support adding new `initialObjects` to the Fluid `
To fix errors resulting from changes to `initialObjects` when testing locally in your browser, remove the hashed container ID from your URL and reload the page. If you're testing in a Teams meeting, start a new meeting and try again.
-If you plan to update your app with new `SharedObject` or `LiveObject` instances frequently, you should consider how you deploy new schema changes to production. While the actual risk is relatively low and short lasting, there may be active sessions at the time you roll out the change. Existing users in the session shouldn't be impacted, but users joining that session after you deployed a breaking change may have issues connecting to the session. To mitigate this, you may consider some of the following solutions:
+If you plan to update your app with new `SharedObject`, `DataObject`, or `LiveDataObject` instances, you must consider how you deploy new schema changes to production. While the actual risk is relatively low and short lasting, there might be active sessions at the time you roll out the change. Existing users in the session must not be impacted, but users joining that session after you deployed a breaking change may have issues connecting to the session. To mitigate this, you might consider some of the following solutions:
+* Use our experimental [Live Share Turbo](https://aka.ms/liveshareturbo) or [Live Share for React](https://aka.ms/livesharereact) packages.
* Deploy schema changes for your web application outside of normal business hours. * Use `dynamicObjectTypes` for any changes made to your schema, rather than changing `initialObjects`.
If you plan to update your app with new `SharedObject` or `LiveObject` instances
<details> <summary><b>Are there limits to how many change events I can emit through Live Share?</b></summary>
-While Live Share is in Preview, any limit to events emitted through Live Share isn't enforced. For optimal performance, you must debounce changes emitted through `SharedObject` or `LiveObject` instances to one message per 50 milliseconds or more. This is especially important when sending changes based on mouse or touch coordinates, such as when synchronizing cursor positions, inking, and dragging objects around a page.
+While there aren't any enforced limits, you must be mindful of how many messages you send. For optimal performance, you must debounce changes emitted through Live Share to one message per 50 milliseconds or more. This is especially important when sending changes based on mouse or touch coordinates, such as when synchronizing cursor positions, inking, and dragging objects around a page.
<br>
Live Share isn't supported for GCC, GCC-High, and DOD tenants.
</details>
+<details>
+<summary><b>Does Live Share support external and guest users?</b></summary>
+
+Yes, Live Share supports guest and external users for most meeting types. However, guest users aren't supported in channel meetings.
+
+<br>
+
+</details>
+
+<details>
+<summary><b>Does Live Share support Teams Rooms devices?</b></summary>
+
+No, Live Share doesn't support Teams Rooms devices.
+
+<br>
+
+</details>
+
+<details>
+<summary><b>Do Live Share apps support meeting recordings?</b></summary>
+
+No, Live Share doesn't support Teams Rooms devices.
+
+<br>
+
+</details>
+ ## Have more questions or feedback? Submit issues and feature requests to the SDK repository for [Live Share SDK](https://github.com/microsoft/live-share-sdk). Use the `live-share` and `microsoft-teams` tag to post how-to questions about the SDK at [Stack Overflow](https://stackoverflow.com/questions/tagged/live-share+microsoft-teams).
platform How To Custom Azure Fluid Relay https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/apps-in-teams-meetings/teams-live-share-how-to/how-to-custom-azure-fluid-relay.md
Last updated 07/21/2022
# Custom Azure Fluid Relay service
-While you likely will prefer using our free hosted service, there are situations where it's beneficial to use your own Azure Fluid Relay service for your Live Share app.
+While you likely prefer using our free hosted service, there are situations where it's beneficial to use your own Azure Fluid Relay service for your Live Share app.
-## Pre-requisites
+## Prerequisites
1. Build a meeting side panel and stage app meeting extension, as shown in the [dice roller tutorial](../teams-live-share-tutorial.md). 2. Update your app manifest to include all [necessary permissions](../teams-live-share-capabilities.md#register-rsc-permissions).
While you likely will prefer using our free hosted service, there are situations
## Connect to Azure Fluid Relay service
-When calling initializing `LiveShareClient`, you can define your own `AzureConnectionConfig`. Live Share associates containers you create with meetings, but you'll need to implement the `ITokenProvider` interface to sign tokens for your containers. This example explains Azure's `AzureFunctionTokenProvider`, which uses an Azure cloud function to request an access token from a server.
+When calling initializing `LiveShareClient`, you can define your own `AzureConnectionConfig`. Live Share associates containers you create with meetings, but you need to implement the `ITokenProvider` interface to sign tokens for your containers. This example explains Azure's `AzureFunctionTokenProvider`, which uses an Azure cloud function to request an access token from a server.
# [JavaScript](#tab/javascript)
Live Share has features that are beneficial to common meeting scenarios that aug
* [Container mapping](#container-mapping) * [Live objects and role verification](#live-objects-and-role-verification)
-* [Media synchronization](#media-synchronization)
### Container mapping
Live Share's live data structures such as `LivePresence`, `LiveState`, and `Live
For more information, see [core capabilities](../teams-live-share-capabilities.md) page.
-### Media synchronization
+## Use Live Share without LiveShareClient
-Packages from `@microsoft/live-share-media` aren't supported in Fluid containers used outside of Microsoft Teams.
+You can still use Live Share even if you use don't want to use the `LiveShareClient` class for your custom Azure Fluid Relay service. This is helpful if you want to control when a container is created or how it's shared with meeting participants.
-For more information, see [media capabilities](../teams-live-share-media-capabilities.md) page.
+The following is an example of how you might do this in your application:
+
+# [JavaScript](#tab/javascript)
+
+```javascript
+import {
+ LiveShareClient,
+ LivePresence,
+ getLiveShareContainerSchemaProxy,
+} from "@microsoft/live-share";
+import { SharedMap } from "fluid-framework";
+import {
+ AzureFunctionTokenProvider,
+ AzureClient,
+} from "@fluidframework/azure-client";
+import { LiveShareHost } from "@microsoft/teams-js";
+
+// Define a custom connection for your app
+const options = {
+ connection: {
+ tenantId: "MY_TENANT_ID",
+ tokenProvider: new AzureFunctionTokenProvider(
+ "MY_SERVICE_ENDPOINT_URL" + "/api/GetAzureToken",
+ { userId: "userId", userName: "Test User" }
+ ),
+ endpoint: "MY_SERVICE_ENDPOINT_URL",
+ type: "remote",
+ },
+};
+// Initialize your AzureClient instance
+const client = new AzureClient(options);
+// Define your Fluid schema
+const schema = {
+ initialObjects: {
+ presence: LivePresence,
+ ticTacToePositions: SharedMap,
+ },
+};
+// Create your host
+const host = LiveShareHost.create();
+// Create the LiveShareRuntime, which is needed for `LiveDataObject` instances to work
+const runtime = new LiveShareRuntime(this._host);
+// Inject the LiveShareRuntime dependency into the ContainerSchema
+const injectedSchema = getLiveShareContainerSchemaProxy(
+ schema,
+ runtime,
+);
+// Create (or get) your container
+const { container } = await client.createContainer(injectedSchema);
+
+// ... ready to start app sync logic
+```
+
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import {
+ LiveShareClient,
+ ILiveShareClientOptions,
+ LivePresence,
+ getLiveShareContainerSchemaProxy,
+} from "@microsoft/live-share";
+import {
+ SharedMap,
+ ContainerSchema,
+} from "fluid-framework";
+import {
+ AzureFunctionTokenProvider,
+ AzureClientProps,
+} from "@fluidframework/azure-client";
+import { LiveShareHost } from "@microsoft/teams-js";
+
+// Define a custom connection for your app
+const options: AzureClientProps = {
+ connection: {
+ tenantId: "MY_TENANT_ID",
+ tokenProvider: new AzureFunctionTokenProvider(
+ "MY_FUNCTION_ENDPOINT_URL" + "/api/GetAzureToken",
+ { userId: "userId", userName: "Test User" }
+ ),
+ endpoint: "MY_SERVICE_ENDPOINT_URL",
+ type: "remote",
+ },
+};
+// Initialize your AzureClient instance
+const client = new AzureClient(options);
+// Define your Fluid schema
+const schema: ContainerSchema = {
+ initialObjects: {
+ presence: LivePresence,
+ ticTacToePositions: SharedMap,
+ },
+};
+// Create your host
+const host = LiveShareHost.create();
+// Create the LiveShareRuntime, which is needed for `LiveDataObject` instances to work
+const runtime = new LiveShareRuntime(this._host);
+// Inject the LiveShareRuntime dependency into the ContainerSchema
+const injectedSchema: ContainerSchema = getLiveShareContainerSchemaProxy(
+ schema,
+ runtime,
+);
+// Create (or get) your container
+const { container } = await client.createContainer(injectedSchema);
+
+// ... ready to start app sync logic
+```
+++
+Alternatively, you can use or override the `AzureLiveShareHost`. This allows you to get custom user display names and roles from your `AzureAudience`, rather than through Microsoft Teams.
+
+# [JavaScript](#tab/javascript)
+
+```javascript
+import {
+ LiveShareClient,
+ LivePresence,
+ AzureLiveShareHost,
+ getLiveShareContainerSchemaProxy,
+} from "@microsoft/live-share";
+import { SharedMap } from "fluid-framework";
+import {
+ AzureFunctionTokenProvider,
+ AzureClient,
+} from "@fluidframework/azure-client";
+
+// Define a custom connection for your app
+const options = {
+ connection: {
+ tenantId: "MY_TENANT_ID",
+ tokenProvider: new AzureFunctionTokenProvider(
+ "MY_SERVICE_ENDPOINT_URL" + "/api/GetAzureToken",
+ { userId: "userId", userName: "Test User" }
+ ),
+ endpoint: "MY_SERVICE_ENDPOINT_URL",
+ type: "remote",
+ },
+};
+// Initialize your AzureClient instance
+const client = new AzureClient(options);
+// Define your Fluid schema
+const schema = {
+ initialObjects: {
+ presence: LivePresence,
+ ticTacToePositions: SharedMap,
+ },
+};
+// Create your AzureLiveShareHost
+const host = AzureLiveShareHost.create();
+// Create the LiveShareRuntime, which is needed for `LiveDataObject` instances to work
+const runtime = new LiveShareRuntime(this._host);
+// Inject the LiveShareRuntime dependency into the ContainerSchema
+const injectedSchema = getLiveShareContainerSchemaProxy(
+ schema,
+ runtime,
+);
+// Create (or get) your container
+const { container } = await client.createContainer(injectedSchema);
+// Set AzureAudience into the AzureLiveShareHost
+host.setAudience(services.audience);
+
+// ... ready to start app sync logic
+```
+
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import {
+ LiveShareClient,
+ ILiveShareClientOptions,
+ LivePresence,
+ AzureLiveShareHost,
+ getLiveShareContainerSchemaProxy,
+} from "@microsoft/live-share";
+import {
+ SharedMap,
+ ContainerSchema,
+} from "fluid-framework";
+import {
+ AzureFunctionTokenProvider,
+ AzureClientProps,
+} from "@fluidframework/azure-client";
+
+// Define a custom connection for your app
+const options: AzureClientProps = {
+ connection: {
+ tenantId: "MY_TENANT_ID",
+ tokenProvider: new AzureFunctionTokenProvider(
+ "MY_FUNCTION_ENDPOINT_URL" + "/api/GetAzureToken",
+ { userId: "userId", userName: "Test User" }
+ ),
+ endpoint: "MY_SERVICE_ENDPOINT_URL",
+ type: "remote",
+ },
+};
+// Initialize your AzureClient instance
+const client = new AzureClient(options);
+// Define your Fluid schema
+const schema: ContainerSchema = {
+ initialObjects: {
+ presence: LivePresence,
+ ticTacToePositions: SharedMap,
+ },
+};
+// Create your AzureLiveShareHost
+const host = AzureLiveShareHost.create();
+// Create the LiveShareRuntime, which is needed for `LiveDataObject` instances to work
+const runtime = new LiveShareRuntime(this._host);
+// Inject the LiveShareRuntime dependency into the ContainerSchema
+const injectedSchema: ContainerSchema = getLiveShareContainerSchemaProxy(
+ schema,
+ runtime,
+);
+// Create (or get) your container
+const { container, services } = await client.createContainer(injectedSchema);
+// Set AzureAudience into the AzureLiveShareHost
+host.setAudience(services.audience);
+
+// ... ready to start app sync logic
+```
+++
+Many Live Share APIs depend on a global timestamp API, which allows `LiveDataObject` objects to determine the order of remote messages. If you're using data structures that rely on the `TimestampProvider` class, then you must either use the `LiveShareHost` from the `teams-js` library or override the `getTimestamp()` function in `AzureLiveShareHost` with a value returned by your server.
## See also
platform Teams Live Share Media Capabilities https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/apps-in-teams-meetings/teams-live-share-media-capabilities.md
The Live Share SDK enables robust **media synchronization** for any HTML `<video
## Install
-To add the latest version of the SDK to your application using npm:
+Live Share media is a JavaScript package published on [npm](https://www.npmjs.com/package/@microsoft/live-share-media), and you can download through npm or yarn. You must also install its peer dependencies, which include `@microsoft/live-share`, `fluid-framework` and `@fluidframework/azure-client`. If you are using Live Share in your tab application, you must also install `@microsoft/teams-js` version `2.11.0` or later.
```bash
-npm install @microsoft/live-share@next --save
-npm install @microsoft/live-share-media@next --save
+npm install @microsoft/live-share @microsoft/live-share-media fluid-framework @fluidframework/azure-client --save
+npm install @microsoft/teams-js --save
``` OR
OR
To add the latest version of the SDK to your application using [Yarn](https://yarnpkg.com/): ```bash
-yarn add @microsoft/live-share@next
-yarn add @microsoft/live-share-media@next
+yarn add @microsoft/live-share @microsoft/live-share-media fluid-framework @fluidframework/azure-client
+yarn add @microsoft/teams-js
``` ## Media sync overview
const allowedRoles: UserMeetingRole[] = [UserMeetingRole.organizer, UserMeetingR
await mediaSession.initialize(allowedRoles); ```
+# [React](#tab/react)
+
+```jsx
+import { useMediaSynchronizer } from "@microsoft/live-share-react";
+import { UserMeetingRole } from "@microsoft/live-share";
+import { useRef } from "react";
+
+const ALLOWED_ROLES = [UserMeetingRole.organizer, UserMeetingRole.presenter];
+
+const INITIAL_TRACK = "<YOUR_VIDEO_URL>";
+
+// Define a unique key that distinguishes this `useMediaSynchronizer` from others in your app
+const UNIQUE_KEY = "MEDIA-SESSION-ID";
+
+export function VideoPlayer() {
+ const videoRef = useRef(null);
+ const { play, pause, seekTo } = useMediaSynchronizer(
+ UNIQUE_KEY,
+ videoRef,
+ INITIAL_TRACK,
+ ALLOWED_ROLES
+ );
+
+ return (
+ <div>
+ <video ref={videoRef} />
+ <button onClick={play}>
+ Play
+ </button>
+ <button onClick={pause}>
+ Pause
+ </button>
+ <button onClick={() => {
+ seekTo(0);
+ }}>
+ Start over
+ </button>
+ </div>
+ );
+}
+```
+ The `LiveMediaSession` automatically listens for changes to the group's playback state. `MediaPlayerSynchronizer` listens to state changes emitted by `LiveMediaSession` and applies them to the provided `IMediaPlayer` object, such as an HTML5 `<video>` or `<audio>` element. To avoid playback state changes that a user didn't intentionally initiate, such as a buffer event, we must call transport controls through the synchronizer, rather than directly through the player.
Example:
```javascript // ...
-document.getElementById("play-button").onclick = () => {
- synchronizer.play();
+document.getElementById("play-button").onclick = async () => {
+ // Will play for all users in the session.
+ // If using role verification, this will throw an error if the user doesn't have the required role.
+ await synchronizer.play();
};
-document.getElementById("pause-button").onclick = () => {
- synchronizer.pause();
+document.getElementById("pause-button").onclick = async () => {
+ // Will pause for all users in the session.
+ // If using role verification, this will throw an error if the user doesn't have the required role.
+ await synchronizer.pause();
};
-document.getElementById("restart-button").onclick = () => {
- synchronizer.seekTo(0);
+document.getElementById("restart-button").onclick = async () => {
+ // Will seek for all users in the session.
+ // If using role verification, this will throw an error if the user doesn't have the required role.
+ await synchronizer.seekTo(0);
}; document.getElementById("change-track-button").onclick = () => {
+ // Will change the track for all users in the session.
+ // If using role verification, this will throw an error if the user doesn't have the required role.
synchronizer.setTrack({ trackIdentifier: "SOME_OTHER_VIDEO_SRC", });
const suspension: MediaSessionCoordinatorSuspension = mediaSession.coordinator.b
suspension.end(); ```
+# [React](#tab/react)
+
+```jsx
+import { useMediaSynchronizer } from "@microsoft/live-share-react";
+import { useRef } from "react";
+
+// Define a unique key that distinguishes this `useMediaSynchronizer` from others in your app
+const UNIQUE_KEY = "MEDIA-SESSION-ID";
+
+// Example component
+export function VideoPlayer() {
+ const videoRef = useRef(null);
+ const { suspended, beginSuspension, endSuspension } = useMediaSynchronizer(
+ UNIQUE_KEY,
+ videoRef,
+ "<YOUR_INITIAL_VIDEO_URL>",
+ );
+
+ return (
+ <div>
+ <video ref={videoRef} />
+ {!suspended && (
+ <button onClick={() => {
+ beginSuspension();
+ }}>
+ Stop following
+ </button>
+ )}
+ {suspended && (
+ <button onClick={() => {
+ endSuspension();
+ }}>
+ Sync to presenter
+ </button>
+ )}
+ </div>
+ );
+}
+```
+ When beginning a suspension, you can also include an optional [CoordinationWaitPoint](/javascript/api/@microsoft/live-share-media/coordinationwaitpoint) parameter, which allows users to define the timestamps in which a suspension should occur for all users. Synchronization won't resume until all users have ended the suspension for that wait point.
document.getElementById("ready-up-button")!.onclick = () => {
}; ```
+# [React](#tab/react)
+
+```jsx
+import { useMediaSynchronizer } from "@microsoft/live-share-react";
+import { useRef } from "react";
+
+// Define a unique key that distinguishes this `useMediaSynchronizer` from others in your app
+const UNIQUE_KEY = "MEDIA-SESSION-ID";
+
+// Example component
+export function VideoPlayer() {
+ const videoRef = useRef(null);
+ const { suspended, beginSuspension, endSuspension } = useMediaSynchronizer(
+ UNIQUE_KEY,
+ videoRef,
+ "<YOUR_INITIAL_VIDEO_URL>",
+ );
+
+ return (
+ <div>
+ <video ref={videoRef} />
+ {!suspended && (
+ <button onClick={() => {
+ const waitPoint = {
+ position: 0,
+ reason: "ReadyUp", // Optional.
+ };
+ beginSuspension(waitPoint);
+ }}>
+ Wait until ready
+ </button>
+ )}
+ {suspended && (
+ <button onClick={() => {
+ endSuspension();
+ }}>
+ Ready up
+ </button>
+ )}
+ </div>
+ );
+}
+```
+ ## Audio ducking
meeting.registerSpeakingStateChangeHandler((speakingState: meeting.ISpeakingStat
}); ```
+# [React](#tab/react)
+
+```jsx
+import { useMediaSynchronizer } from "@microsoft/live-share-react";
+import { meeting } from "@microsoft/teams-js";
+import { useRef, useEffect } from "react";
+
+// Define a unique key that distinguishes this `useMediaSynchronizer` from others in your app
+const UNIQUE_KEY = "MEDIA-SESSION-ID";
+
+// Example component
+export function VideoPlayer() {
+ const videoRef = useRef(null);
+ const { synchronizer } = useMediaSynchronizer(
+ UNIQUE_KEY,
+ videoRef,
+ "<YOUR_INITIAL_VIDEO_URL>",
+ );
+
+ const enableSmartSound = () => {
+ let volumeTimer;
+ meeting.registerSpeakingStateChangeHandler((speakingState) => {
+ if (speakingState.isSpeakingDetected && !volumeTimer) {
+ // If someone in the meeting starts speaking, periodically
+ // lower the volume using your MediaPlayerSynchronizer's
+ // VolumeLimiter.
+ synchronizer.volumeLimiter?.lowerVolume();
+ volumeTimer = setInterval(() => {
+ synchronizer.volumeLimiter?.lowerVolume();
+ }, 250);
+ } else if (volumeTimer) {
+ // If everyone in the meeting stops speaking and the
+ // interval timer is active, clear the interval.
+ clearInterval(volumeTimer);
+ volumeTimer = undefined;
+ }
+ });
+ }
+
+ return (
+ <div>
+ <video ref={videoRef} />
+ <button onClick={enableSmartSound}>
+ Enable smart sound
+ </button>
+ </div>
+ );
+}
+```
+ Additionally, add the following [RSC](/microsoftteams/platform/graph-api/rsc/resource-specific-consent) permissions into your app manifest:
platform Teams Live Share Overview https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/apps-in-teams-meetings/teams-live-share-overview.md
Last updated 04/07/2022
# Live Share SDK
-> [!VIDEO https://www.youtube.com/embed/971YIvosuUk]
-
-Live Share is an SDK designed to transform Teams apps into collaborative multiuser experiences without writing any dedicated back-end code. With Live Share, your users can co-watch, co-create, and co-edit during meetings.
+Live Share is an SDK designed to transform Teams apps into collaborative multi-user experiences without writing any dedicated back-end code. With Live Share, your users can co-watch, co-create, and co-edit during meetings.
Sometimes screen sharing just isn't enough, which is why Microsoft built tools like PowerPoint Live and Whiteboard directly into Teams. By bringing your web application directly to center stage in the meeting interface, your users can seamlessly collaborate during meetings and calls. > [!div class="nextstepaction"] > [Get started](teams-live-share-quick-start.md)
-> [!NOTE]
-> Live Share SDK is available only in [public developer preview](../resources/dev-preview/developer-preview-intro.md).
- ## Feature overview
-Live Share has three packages that support limitless collaborative scenarios. These packages expose a set of distributed data structures (DDS), including primitive building blocks and turn-key scenarios.
+Live Share has three primary packages that support limitless collaborative scenarios. These packages expose a set of distributed data structures (DDS), including primitive building blocks and turn-key scenarios.
Live Share seamlessly integrates meetings with [Fluid Framework](https://fluidframework.com/). Fluid Framework is a collection of client libraries for distributing and synchronizing shared state. Live Share provides a free, fully managed, and ready to use [Azure Fluid Relay](/azure/azure-fluid-relay/) backed by the security and global scale of Teams.
Features supported by the Live Share core package include:
- Join a meeting's Live Share session with `LiveShareClient`. - Track meeting presence and synchronize user metadata with `LivePresence`.-- Send real-time events to other clients in the session with `LiveEvent`. - Coordinate app state that disappears when users leave the session with `LiveState`. - Synchronize a countdown timer with `LiveTimer`.
+- Send real-time events to other clients in the session with `LiveEvent`.
- Leverage any feature of Fluid Framework, such as `SharedMap` and `SharedString`. You can find more information about this package on the [core capabilities page](./teams-live-share-capabilities.md).
Consider using a custom service if you:
For more information, see the custom Azure Fluid Relay service [how-to guide](./teams-live-share-how-to/how-to-custom-azure-fluid-relay.md).
+## React integration
+
+Live Share has a dedicated React integration, making Live Share features even easier to integrate into React apps. Rather than using `LiveShareClient` directly, you can use the `LiveShareProvider` component to join a Live Share session when the component first mounts. Each `LiveDataObject` has a corresponding React hook, designed to make using Live Share incredibly easy. For more information, see Live Share for React [GitHub page](https://aka.ms/livesharereact) for more information.
+ ## User scenarios | Scenario | Example |
platform Teams Live Share Quick Start https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/apps-in-teams-meetings/teams-live-share-quick-start.md
Last updated 04/07/2022
# Quick start guide
-Get started with Live Share SDK using the Dice Roller sample is an evolution of the [Fluid Framework Quick Start](https://fluidframework.com/docs/start/quick-start/) and is designed to quickly run a Live Share SDK based [Dice Roller sample](https://github.com/microsoft/live-share-sdk/tree/main/samples/javascript/01.dice-roller) on your computer's localhost.
+Get started with Live Share SDK using the Dice Roller sample is designed to quickly run a Live Share SDK based [Dice Roller sample](https://github.com/microsoft/live-share-sdk/tree/main/samples/javascript/01.dice-roller) on your computer's localhost.
:::image type="content" source="../assets/images/teams-live-share/dice-roller.png" alt-text="DiceRoller Sample":::
platform Teams Live Share Tutorial https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/apps-in-teams-meetings/teams-live-share-tutorial.md
In the Dice Roller sample app, users are shown a dice with a button to roll it.
1. [Set up the application](#set-up-the-application) 2. [Join a Fluid container](#join-a-fluid-container)
-3. [Write the stage view](#write-the-stage-view)
-4. [Connect stage view to Fluid data](#connect-stage-view-to-fluid-data)
+3. [Write the meeting stage view](#write-the-stage-view)
+4. [Connect meeting stage view to Live Share](#connect-meeting-stage-view-to-live-share)
5. [Write the side panel view](#write-the-side-panel-view) 6. [Write the settings view](#write-the-settings-view)
In the Dice Roller sample app, users are shown a dice with a button to roll it.
You can start by importing the required modules. The sample uses the [LiveState DDS](/javascript/api/@microsoft/live-share/livestate) and [LiveShareClient](/javascript/api/@microsoft/live-share/liveshareclient) from the Live Share SDK. The sample supports Teams Meeting Extensibility so you must include the [Microsoft Teams JavaScript client library (TeamsJS)](https://github.com/OfficeDev/microsoft-teams-library-js). Finally, the sample is designed to run both locally and in a Teams meeting so you need to include more Fluid Framework pieces to [test the sample locally](https://fluidframework.com/docs/testing/testing/#azure-fluid-relay-as-an-abstraction-for-tinylicious).
-Applications create Fluid containers using a schema that defines a set of _initial objects_ that are available to the container. The sample uses a SharedMap to store the current dice value that was rolled. For more information, see [data modeling](https://fluidframework.com/docs/build/data-modeling/).
+Applications create Fluid containers using a schema that defines a set of _initial objects_ that are available to the container. The sample uses a LiveState to store the current dice value that was rolled.
Teams meeting apps require multiple views, such as content, configuration, and stage. You can create a `start()` function to help identify the view. This helps to render and perform any initialization that's required. The app supports running both locally in a web browser and from within a Teams meeting. The `start()` function looks for an `inTeams=true` query parameter to determine if it's running in Teams.
Teams meeting apps require multiple views, such as content, configuration, and s
In addition to the `inTeams=true` query parameter, you can use a `view=content|config|stage` query parameter to determine the view that needs to be rendered. ```js
-import { SharedMap } from "fluid-framework";
import { app, pages, LiveShareHost } from "@microsoft/teams-js";
-import { LiveShareClient, TestLiveShareHost } from "@microsoft/live-share";
-import { InsecureTokenProvider } from "@fluidframework/test-client-utils";
+import { LiveShareClient, TestLiveShareHost, LiveState } from "@microsoft/live-share";
const searchParams = new URL(window.location).searchParams; const root = document.getElementById("content"); // Define container schema
-const diceValueKey = "dice-value-key";
- const containerSchema = {
- initialObjects: { diceMap: SharedMap },
+ initialObjects: { diceState: LiveState },
};
-function onContainerFirstCreated(container) {
- // Set initial state of the rolled dice to 1.
- container.initialObjects.diceMap.set(diceValueKey, 1);
-}
- // STARTUP LOGIC async function start() {
async function start() {
if (!!searchParams.get("inTeams")) { // Initialize teams app await app.initialize();-
- // Get our frameContext from context of our app in Teams
- const context = await app.getContext();
- if (context.page.frameContext == "meetingStage") {
- view = "stage";
- }
} // Load the requested view
async function start() {
case "stage": default: const { container } = await joinContainer();
- renderStage(container.initialObjects.diceMap, root);
+ renderStage(container.initialObjects.diceState, root);
break; } }
async function joinContainer() {
? LiveShareHost.create() : TestLiveShareHost.create(); // Create client
- const liveShare = new LiveShareClient(host);
+ const client = new LiveShareClient(host);
// Join container
- return await liveShare.joinContainer(containerSchema, onContainerFirstCreated);
+ return await client.joinContainer(containerSchema, onContainerFirstCreated);
} ```
-When testing locally, `TestLiveShareHost` updates the browser URL to contain the ID of the test container that was created. Copying that link to other browser tabs causes the `LiveShareClient` to join the test container that was created. If the modification of the applications URL interferers with the operation of the application, the strategy used to store the test containers ID can be customized using the [setLocalTestContainerId](/javascript/api/@microsoft/live-share/iliveshareclientoptions) and [getLocalTestContainerId](/javascript/api/@microsoft/live-share/iliveshareclientoptions) options passed to `LiveShareClient`.
+When testing locally, `TestLiveShareHost` updates the browser URL to contain the ID of the test container that was created. Copying that link to other browser tabs causes the `LiveShareClient` to join the test container that was created. If the modification of the applications URL interferes with the operation of the application, the strategy used to store the test containers ID can be customized using the [setLocalTestContainerId](/javascript/api/@microsoft/live-share/iliveshareclientoptions) and [getLocalTestContainerId](/javascript/api/@microsoft/live-share/iliveshareclientoptions) options passed to `LiveShareClient`.
## Write the stage view
Many Teams Meeting Extensibility applications are designed to use React for thei
It's easy to create the view using local data without any Fluid functionality, then add Fluid by changing some key pieces of the app.
-The `renderStage` function appends the `stageTemplate` to the passed HTML element and creates a working dice roller with a random dice value each time the **Roll** button is selected. The `diceMap` is used in the next few steps.
+The `renderStage` function appends the `stageTemplate` to the passed HTML element and creates a working dice roller with a random dice value each time the **Roll** button is selected. The `diceState` is used in the next few steps.
```js const stageTemplate = document.createElement("template");
stageTemplate["innerHTML"] = `
<button class="roll"> Roll </button> </div> `;
-function renderStage(diceMap, elem) {
+function renderStage(diceState, elem) {
elem.appendChild(stageTemplate.content.cloneNode(true)); const rollButton = elem.querySelector(".roll"); const dice = elem.querySelector(".dice");
- rollButton.onclick = () => updateDice(Math.floor(Math.random() * 6) + 1);
-
- const updateDice = (value) => {
+ const updateDice = () => {
+ // Get a random value between 1 and 6
+ const diceValue = Math.floor(Math.random() * 6) + 1;
// Unicode 0x2680-0x2685 are the sides of a die (⚀⚁⚂⚃⚄⚅). dice.textContent = String.fromCodePoint(0x267f + value); };
+ rollButton.onclick = () => updateDice();
updateDice(1); } ```
-## Connect stage view to Fluid data
+## Connect meeting stage view to Live Share
-### Modify Fluid data
+### Modify LiveState
-To begin using Fluid in the application, the first thing to change is what happens when the user selects the `rollButton`. Instead of updating the local state directly, the button updates the number stored in the `value` key of the passed in `diceMap`. Because the `diceMap` is a Fluid `SharedMap`, changes are distributed to all clients. Any changes to the `diceMap` can cause a `valueChanged` event to be emitted, and an event handler can trigger an update of the view.
+To begin using Live Share in the application, the first thing to change is what happens when the user selects the `rollButton`. Instead of updating the local state directly, the button updates the number stored as a `state` value in `diceState`. Whenever you call `.set()` with a new `state`, that value is distributed to all clients. Any changes to the `diceState` can cause a `stateChanged` event to be emitted, and an event handler can trigger an update of the view.
-This pattern is common in Fluid because it enables the view to behave the same way for both local and remote changes.
+This pattern is common in Fluid and Live Share distributed data structures because it enables the view to behave the same way for both local and remote changes.
```js rollButton.onclick = () =>
- diceMap.set("dice-value-key", Math.floor(Math.random() * 6) + 1);
+ diceState.set(Math.floor(Math.random() * 6) + 1);
``` ### Rely on Fluid data
-The next change that needs to be made is to change the `updateDice` function, as it no longer accepts an arbitrary value. This means the app can no longer directly modify the local dice value. Instead, the value is retrieved from the `SharedMap` each time `updateDice` is called.
+The next change that needs to be made is to change the `updateDice` function to retrieve the latest dice value from the `LiveState` each time `updateDice` is called.
```js const updateDice = () => {
- const diceValue = diceMap.get("dice-value-key");
+ const diceValue = diceState.state;
dice.textContent = String.fromCodePoint(0x267f + diceValue); };
-updateDice();
``` ### Handle remote changes
-The values returned from `diceMap` are only a snapshot in time. To keep the data up to date as it changes an event handler must be set on the `diceMap` to call `updateDice` each time that the `valueChanged` event is sent. To get a list of events fired and the values passed to those events, see [SharedMap](https://fluidframework.com/docs/data-structures/map/).
+The values returned from `diceState` are only a snapshot in time. To keep the data up-to-date as it changes, an event handler must be registered with `diceState` to call `updateDice` each time that the `stateChanged` event is sent.
```js
-diceMap.on("valueChanged", updateDice);
+diceState.on("stateChanged", updateDice);
+```
+
+### Initialize LiveState
+
+Before you can begin receiving the Live Share changes in the application, you must first call `initialize()` on your `LiveState` object with an initial value. This initial value doesn't overwrite any existing state that was sent by other users.
+
+After you've initialized `LiveState`, the `stateChanged` event you registered earlier starts to trigger whenever a change is made. However, to update the UI within the initial value, call `updateDice()`.
+
+```js
+await diceState.initialize(1);
+updateDice();
``` ## Write the side panel view
platform Conversation Ai Quick Start https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/bots/how-to/Teams conversational AI/conversation-ai-quick-start.md
To get started, ensure that you have the following tools:
``` > [!NOTE]
- > If youΓÇÖre cloning the [ChefBot sample](https://github.com/microsoft/teams-ai/tree/main/js/samples) through Teams Toolkit, youΓÇÖll find the `.env.local.user` file in the setup that is created automatically. If you can't find the file, create the `.env.local.user` file and update the OpenAI key to get started.
+ > If youΓÇÖre cloning the [ChefBot sample](https://github.com/microsoft/teams-ai/tree/main/js/samples) through Teams Toolkit, youΓÇÖll find the `.env.local.user` file in the setup that is created automatically. If the file isn't available, create the `.env.local.user` file and update the OpenAI key to get started.
1. Go to **Visual Studio Code**.
platform Coversational Ai Faq https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/bots/how-to/Teams conversational AI/coversational-ai-faq.md
Last updated 04/07/2022
<details> <summary>What does the Teams AI library do?</summary>
-Teams AI library provides abstractions for developers to build robust applications that utilize OpenAI large language model (LLM)s.
+Teams AI library provides abstractions for you to build robust applications that utilize OpenAI large language model (LLM)s.
</details> <br>
The two are independent and can't be integrated.
<br> <details>
-<summary> How does Teams AI library co-exist against the hero-story of developers building for the Skills ecosystem in Microsoft 365?</summary>
+<summary> How does Teams AI library co-exist against the hero-story of developers building for the skills ecosystem in Microsoft 365?</summary>
Teams AI library story is targeted towards Pro-developers and separate from the hero-story around skills ecosystem in Microsoft 365.
platform How Conversation Ai Core Capabilities https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/bots/how-to/Teams conversational AI/how-conversation-ai-core-capabilities.md
Teams AI library supports the following capabilities:
You need to use the AI library to scaffold bot and Adaptive Card handlers to the source file.
-In the following section, we'll explain each capability and their path to migration. We'll be using the samples from the [AI library](https://github.com/microsoft/teams-ai/tree/main) to explain the migration method:
+In the following section, we've used the samples from the [AI library](https://github.com/microsoft/teams-ai/tree/main) to explain each capability and the path to migration:
## Send or receive message
app.messageExtensions.selectItem(async (context, state, item) => {
A simple interface for actions and predictions allows bots to react when they have high confidence for taking action. Ambient presence lets bots learn intent, use prompts based on business logic, and generate responses.
-Thanks to our AI library, the prompt need only outline the actions supported by the bot, and supply a few-shot examples of how to employ those actions. We also use conversation-history to enable a more natural dialogue between the user and bot, such as *add cereal to groceries list*, followed by *also add coffee*, which should indicate that coffee is to be added to the groceries list.
+Thanks to our AI library, the prompt needs only to outline the actions supported by the bot, and supply a few-shot examples of how to employ those actions. Conversation history helps with a natural dialogue between the user and bot, such as *add cereal to groceries list*, followed by *also add coffee*, which should indicate that coffee is to be added to the groceries list.
All entities are required parameters to actions.
Human: {{activity.text}}
## AI
-The bot logic is simplified to provide handlers for actions such as addItem, removeItem and , findItem. This clear delineation between actions and the prompts that instruct the AI on how to execute them is an incredibly potent tool.
+The bot logic is simplified to provide handlers for actions such as `addItem`, `removeItem`, and `findItem`. This clear delineation between actions and the prompts that instruct the AI on how to execute them is an incredibly potent tool.
Example: [List bot](https://github.com/microsoft/teams-ai/tree/main/js/samples/04.ai.d.chainedActions.listBot)
platform How Conversation Ai Get Started https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/bots/how-to/Teams conversational AI/how-conversation-ai-get-started.md
Teams AI library streamlines the process to build intelligent Microsoft Teams applications by using the AI components. It provides APIs to access and manipulate data, as well as a range of controls and components to create custom user interfaces.
-You can easily integrate Teams AI library, prompt management, and safety moderation into your apps and enhance the user experience. It also facilitates the creation of bots that uses an OpenAI API key or AzureOpenAI to provide an AI-driven conversational experience.
+You can easily integrate Teams AI library, prompt management, and safety moderation into your apps and enhance the user experience. It also facilitates the creation of bots that uses an OpenAI API key or Azure OpenAI to provide an AI-driven conversational experience.
## Initial setup
import { Application, ConversationHistory, DefaultPromptManager, DefaultTurnStat
import path from "path"; ```
-## Create AI Components
+## Create AI components
-You can take your existing or a new bot framework app and add AI capabilities.
+Add AI capabilities to your existing app or a new Bot Framework app.
-**Planner**: OpenAI planner is the main component that calls the large language model (LLM) OpenAI or AzureOpenAI. The OpenAI API is powered by a diverse set of models with different capabilities. You can also make limited customizations to our original base models for your specific use case.
+**Planner**: OpenAI planner is the main component that calls the large language model (LLM) OpenAI or Azure OpenAI. The OpenAI API is powered by a diverse set of models with different capabilities. You can also make limited customizations to our original base models for your specific use case.
-**Prompt manager**: The prompt manager manages prompt creation. It calls functions and injects from your code into the prompt. It can copy the conversation state and the user state into the prompt for you automatically.
+**Prompt manager**: The prompt manager manages prompt creation. It calls functions and injects from your code into the prompt. It copies the conversation state and the user state into the prompt for you automatically.
**Moderator**: A moderator adds safety moderation to the input and output. It allows you to identify the user input, flag prompt injection techniques, review the output from the bot, and run it through a business logic for filtering to ensure that the bot complies with OpenAI's usage policies. You can either moderate the input or the output, or both. OpenAI moderator is the default moderator.
The `MemoryStorage()` function stores all the state for your bot. The `Applicati
## Prompt
-Prompts are pieces of text that can be used to create conversational experiences. Prompts are used to start conversations, ask questions, and generate responses. The use of prompts can help reduce the complexity of creating conversational experiences and make them more engaging for the user.
+Prompts are pieces of text that can be used to create conversational experiences. Prompts are used to start conversations, ask questions, and generate responses. The use of prompts helps reduce the complexity of creating conversational experiences and make them more engaging for the user.
The following are a few guidelines to create prompts:
Create a folder called prompts and define your prompts in the folder. When the u
* `skprompt.txt`: Contains the prompts text and supports template variables and functions. Define all your text prompts in the `skprompt.txt` file.
-* `config.json`: Contains the prompt model settings. Provide the right configuration to ensure bot responses are aligned with your requirement. Configure `max_tokens`, `temperature`, and other properties to pass into OpenAI or AzureOpenAI.
+* `config.json`: Contains the prompt model settings. Provide the right configuration to ensure bot responses are aligned with your requirement. Configure `max_tokens`, `temperature`, and other properties to pass into OpenAI or Azure OpenAI.
[Sample code reference](https://github.com/microsoft/teams-ai/blob/main/js/samples/04.ai.a.teamsChefBot/src/prompts/chat/config.json)
The following table includes the query parameters:
### Prompt actions
-Plans let the model perform actions or say things to the user. You can create a schema of the plan and add a list of actions that you support. It can perform an action and pass arguments. The OpenAI endpoint can figure out what actions it wants to use and then extract all the entities and pass those as arguments to the action call.
+Plans let the model perform actions or respond to the user. You can create a schema of the plan and add a list of actions that you support to perform an action and pass arguments. The OpenAI endpoint figures out the actions required to be used, extracts all the entities, and passes those as arguments to the action call.
```text The following is a conversation with an AI assistant.
Plans let the model perform actions or say things to the user. You can create a
AI: ```
-### Prompt Template
+### Prompt template
-Prompt template is a simple and powerful way to define and compose AI functions using plain text. You can use it to create natural language prompts, generate responses, extract information, invoke other prompts, or perform any other task that can be expressed with text.
+Prompt template is a simple and powerful way to define and compose AI functions using plain text. You can use prompt template to create natural language prompts, generate responses, extract information, invoke other prompts, or perform any other task that can be expressed with text.
The language supports features that allow you to include variables, call external functions, and pass parameters to functions. You don't need to write any code or import any external libraries, just use the curly braces {{...}} to embed expressions in your prompts. Teams parses your template and execute the logic behind it. This way, you can easily integrate AI into your apps with minimal effort and maximum flexibility.
platform Teams Conversation Ai Overview https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/bots/how-to/Teams conversational AI/teams-conversation-ai-overview.md
Bots can run in-context and assist when the bot recognizes a user intent that ma
## Prompt engineering
-Prompt engineering helps you design prompts considering user's intent, context of the conversation, and the bot personality. Bots can be personalized, customized, and tailor-made to meet user needs.
+Prompt engineering helps you design prompts considering user's intent, context of the conversation, and the bot personality. Bots can be personalized and customized to meet user needs.
## Conversational session history
A simple interface for actions and predictions allows bots to react when the bot
## Why build apps with Teams AI library?
-Teams AI library makes Teams apps conversational, not driven by rigid command structures. The library is designed to seamlessly integrate with the Teams Bot Framework SDK. Building apps for Teams is drastically simpler, with rich natural language features that bring any app experience into the conversation.
+Teams AI library makes Teams apps conversational, not driven by rigid command structures. The library is designed to seamlessly integrate with the Teams Bot Framework SDK. Building apps for Teams is simpler, with rich natural language features that bring any app experience into the conversation.
Here are some key benefits of the Teams AI library:
Here are some key benefits of the Teams AI library:
* You can select any LLM.
-* ΓÇïYou can talk to Teams apps like youΓÇÖd talk to a human as opposed to a set of commandsΓÇï.
+* You can talk to Teams apps like youΓÇÖd talk to a human as opposed to a set of commandsΓÇï.
* ΓÇïAny app that uses the library offers consistency for interactions, and the app handles steering natural conversation into actionable outcomes.
platform Whats New https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/whats-new.md
Discover Microsoft Teams platform features that are generally available (GA). Yo
| **Date** | **Update** | **Find here** | | -- | | -|
-| 23/05/2023 | Design your app for new Teams. | Design your app > [Overview](concepts/design/design-teams-app-overview.md)|
-|17/05/2023 | Distribute your app to specific countries. | Distribute your app > Publish to the Teams store > Prepare your Teams store submission > [Distribute your app to specific countries](concepts/deploy-and-publish/appsource/prepare/submission-checklist.md#distribute-your-app-to-specific-countries)|
+|23/05/2023 | Live Share SDK is now generally available. | Build apps for Teams meetings and calls > Enhanced collaboration with Live Share > [Live Share SDK](apps-in-teams-meetings/teams-live-share-overview.md)|
+|23/05/2023 | Design your app for new Teams. | Design your app > [Overview](concepts/design/design-teams-app-overview.md)|
+|17/05/2023 | Distribute your app to specific countries. | Distribute your app > Publish to the Teams store > Prepare your Teams store submission > [Distribute your app to specific countries](concepts/deploy-and-publish/appsource/prepare/submission-checklist.md#distribute-your-app-to-specific-countries)|
| 17/05/2023 | Introduced the Teams Toolkit v5 extension within Visual Studio Code. | Tools and SDKs > Teams Toolkit > [Teams Toolkit Overview](toolkit/teams-toolkit-fundamentals.md)| | 17/05/2023 | Updated Get started module with GitHub Codespaces and step-by-step guides aligned with Teams Toolkit v5. It also includes details for extending Teams app over Microsoft 365 and Outlook. | [Get started](get-started/get-started-overview.md)|