Updates from: 08/25/2022 01:20:33
Service Microsoft Docs article Related commit history on GitHub Change details
platform Teams Live Share Capabilities https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/apps-in-teams-meetings/teams-live-share-capabilities.md
ms.localizationpriority: high
Last updated 04/07/2022 + # Live Share core capabilities The Live Share SDK can be added to your meeting extension's `sidePanel` and `meetingStage` contexts with minimal effort. This article focuses on how to integrate the Live Share SDK into your app and key capabilities of the SDK.
-> [!Note]
+> [!NOTE]
> Currently, only scheduled meetings are supported, and all participants must be on the meeting calendar. Meeting types such as, one-on-one calls, group calls, and meet now are currently not supported. :::image type="content" source="../assets/images/teams-live-share/Teams-live-share-dashboard.png" alt-text="Teams Live Share":::
The Live Share SDK can be added to your meeting extension's `sidePanel` and `mee
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.
-**npm**
+### npm
```bash npm install @microsoft/live-share --save ```
-**Yarn**
+### yarn
```bash yarn add @microsoft/live-share
Follow the steps to join a session that is associated with a user's meeting:
Example:
+# [JavaScript](#tab/javascript)
+ ```javascript import * as microsoftTeams from "@microsoft/teams-js"; import { TeamsFluidClient } from "@microsoft/live-share";
import { SharedMap } from "fluid-framework";
// Initialize the Teams Client SDK await microsoftTeams.app.initialize();
-// Setup the Fluid container
+// Join the Fluid container
const client = new TeamsFluidClient(); const schema = { initialObjects: { exampleMap: SharedMap },
const { container } = await client.joinContainer(schema);
// ... ready to start app sync logic ```
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import * as microsoftTeams from "@microsoft/teams-js";
+import { TeamsFluidClient } from "@microsoft/live-share";
+import { ContainerSchema, SharedMap } from "fluid-framework";
+
+// Initialize the Teams Client SDK
+await microsoftTeams.app.initialize();
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
+const schema: ContainerSchema = {
+ initialObjects: { exampleMap: SharedMap },
+};
+const { container } = await client.joinContainer(schema);
+
+// ... ready to start app sync logic
+```
+++ That's all it took to setup your container and join the meeting's session. Now, let's review the different types of _distributed data structures_ that you can use with the Live Share SDK. ## Fluid distributed data structures The Live Share SDK supports any [distributed data structure](https://fluidframework.com/docs/data-structures/overview/) included in Fluid Framework. Here's a quick overview of a few of the different types of objects available:
-| Shared Object | Description |
-| -- | -- |
+| 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 document text editing. |
+| [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 document text editing. |
Let's see how `SharedMap` works. In this example, we've used `SharedMap` to build a playlist feature.
+# [JavaScript](#tab/javascript)
+ ```javascript
+import { TeamsFluidClient } from "@microsoft/live-share";
import { SharedMap } from "fluid-framework";
-// ...
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
const schema = { initialObjects: { playlistMap: SharedMap }, }; const { container } = await client.joinContainer(schema);
-const { playlistMap } = container.initialObjects;
+const playlistMap = container.initialObjects.playlistMap as SharedMap;
-// Listen for changes to values in the map
+// Register listener for changes to values in the map
playlistMap.on("valueChanged", (changed, local) => { const video = playlistMap.get(changed.key); // Update UI with added video
function onClickAddToPlaylist(video) {
// Add video to map playlistMap.set(video.id, video); }
-// ...
```
-> [!Note]
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import { TeamsFluidClient } from "@microsoft/live-share";
+import { ContainerSchema, SharedMap, IValueChanged } from "fluid-framework";
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
+const schema: ContainerSchema = {
+ initialObjects: { exampleMap: SharedMap },
+};
+const { container } = await client.joinContainer(schema);
+const playlistMap = container.initialObjects.playlistMap as SharedMap;
+
+// Declare interface for object being stored in map
+interface IVideo {
+ id: string;
+ url: string;
+}
+
+// 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 onClickAddToPlaylist(video: IVideo) {
+ // Add video to map
+ playlistMap.set(video.id, video);
+}
+```
+++
+> [!NOTE]
> Core Fluid Framework DDS objects don't support meeting role verification. Everyone in the meeting can change data stored through these objects. ## Live Share ephemeral data structures The Live Share SDK includes a set of new ephemeral `SharedObject` classes, which provide stateful and stateless objects that aren't stored in the Fluid container. For example, if you want to create a laser-pointer feature into your app, such as the popular PowerPoint Live integration, you can use our `EphemeralEvent` or `EphemeralState` objects.
-| Ephemeral Object | Description |
-| - | - |
-| [EphemeralPresence](/javascript/api/@microsoft/live-share/ephemeralpresence) | See which users are online, set custom properties for each user, and broadcast changes to their presence. |
-| [EphemeralEvent](/javascript/api/@microsoft/live-share/ephemeralevent) | Broadcast individual events with any custom data attributes in the payload. |
-| [EphemeralState](/javascript/api/@microsoft/live-share/ephemeralstate) | Similar to SharedMap, a distributed key-value store that allows for restricted state changes based on role, for example, the presenter.|
+| Ephemeral Object | Description |
+| - | |
+| [EphemeralPresence](/javascript/api/@microsoft/live-share/ephemeralpresence) | See which users are online, set custom properties for each user, and broadcast changes to their presence. |
+| [EphemeralEvent](/javascript/api/@microsoft/live-share/ephemeralevent) | Broadcast individual events with any custom data attributes in the payload. |
+| [EphemeralState](/javascript/api/@microsoft/live-share/ephemeralstate) | Similar to SharedMap, a distributed key-value store that allows for restricted state changes based on role, for example, the presenter. |
### EphemeralPresence example
-The `EphemeralPresence` class makes tracking who is attending a meeting easier than ever. When calling the `.start()` or `.updatePresence()` methods, you can assign custom metadata for that user, such as a unique identifier or name.
+The `EphemeralPresence` 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 or profile picture. By listening to `presenceChanged` events, each client receives the latest `EphemeralPresenceUser` object, collapsing all presence updates into a single record for each unique `userId`.
+
+> [!NOTE]
+> The default `userId` assigned to each `EphemeralPresenceUser` 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.
Example:
+# [JavaScript](#tab/javascript)
+ ```javascript
-import { EphemeralPresence, PresenceState } from "@microsoft/live-share";
-// ...
+import {
+ TeamsFluidClient,
+ EphemeralPresence,
+ PresenceState,
+} from "@microsoft/live-share";
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
const schema = {
- initialObjects: { presence: EphemeralPresence },
+ initialObjects: {
+ presence: EphemeralPresence,
+ },
}; const { container } = await client.joinContainer(schema);
-const { presence } = container.initialObjects;
+const presence = container.initialObjects.presence;
-// Listen for changes to presence
+// Register listener for changes to presence
presence.on("presenceChanged", (userPresence, local) => { // Update UI with presence }); // Start tracking presence
-presence.start("YOUR_CUSTOM_USER_ID", {
+presence.initialize("YOUR_CUSTOM_USER_ID", {
name: "Anonymous", picture: "DEFAULT_PROFILE_PICTURE_URL", });
function onUserDidLogIn(userName, profilePicture) {
} ```
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import { TeamsFluidClient, EphemeralPresence, PresenceState, EphemeralPresenceUser } from "@microsoft/live-share";
+
+// Declare interface for type of custom data for user
+interface ICustomUserData {
+ name: string;
+ picture: string;
+}
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
+const schema = {
+ initialObjects: {
+ presence: EphemeralPresence<ICustomUserData>,
+ },
+};
+const { container } = await client.joinContainer(schema);
+const presence = container.initialObjects.presence as EphemeralPresence<ICustomUserData>;
+
+// Register listener for changes to presence
+presence.on("presenceChanged", (userPresence: EphemeralPresenceUser<ICustomUserData>, local: boolean) => {
+ // Update UI with presence
+});
+
+// Start tracking presence
+presence.initialize("YOUR_CUSTOM_USER_ID", {
+ name: "Anonymous",
+ picture: "DEFAULT_PROFILE_PICTURE_URL",
+});
+
+function onUserDidLogIn(userName: string, profilePicture: string) {
+ presence.updatePresence(PresenceState.online, {
+ name: userName,
+ picture: profilePicture,
+ });
+}
+```
+++ ### EphemeralEvent example `EphemeralEvent` is a great way to send simple events to other clients in a meeting. It's useful for scenarios like sending session notifications.
+# [JavaScript](#tab/javascript)
+ ```javascript
-import { EphemeralEvent } from "@microsoft/live-share";
-// ...
+import { TeamsFluidClient, EphemeralEvent } from "@microsoft/live-share";
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
const schema = { initialObjects: { notifications: EphemeralEvent }, }; const { container } = await client.joinContainer(schema); const { notifications } = container.initialObjects;
-// Listen for incoming notifications
+// Register listener for incoming notifications
notifications.on("received", (event, local) => { let notificationToDisplay; if (local) {
notifications.on("received", (event, local) => {
// Display notification in your UI });
-// Start tracking notifications
-await notifications.start();
+// Start listening for incoming notifications
+await notifications.initialize();
notifications.sendEvent({ senderName: "LOCAL_USER_NAME",
notifications.sendEvent({
}); ```
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import { TeamsFluidClient, EphemeralEvent, IEphemeralEvent } from "@microsoft/live-share";
+
+// Declare interface for type of custom data for user
+interface ICustomEvent extends IEphemeralEvent {
+ senderName: string;
+ text: string;
+}
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
+const schema = {
+ initialObjects: {
+ notifications: EphemeralEvent<ICustomEvent>,
+ },
+};
+const { container } = await client.joinContainer(schema);
+const notifications = container.initialObjects.notifications as EphemeralEvent<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}`;
+ }
+ // Display notification in your UI
+});
+
+// Start listening for incoming notifications
+await notifications.initialize();
+
+notifications.sendEvent({
+ senderName: "LOCAL_USER_NAME",
+ text: "joined the session",
+});
+```
+++
+### EphemeralTimer example
+
+`EphemeralTimer` enables scenarios that have a time limit, such as a group meditation timer or a round timer for a game.
+
+# [JavaScript](#tab/javascript)
+
+```javascript
+import { TeamsFluidClient, EphemeralTimer } from "@microsoft/live-share";
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
+const schema = {
+ initialObjects: { timer: EphemeralTimer },
+};
+const { container } = await client.joinContainer(schema);
+const { timer } = container.initialObjects;
+
+// Register listener for when the timer starts its countdown
+timer.on("started", (config, local) => {
+ // Update UI to show timer has started
+});
+
+// Register listener for when a paused timer has resumed
+timer.on("played", (config, local) => {
+ // Update UI to show timer has resumed
+});
+
+// Register listener for when a playing timer has paused
+timer.on("paused", (config, local) => {
+ // Update UI to show timer has paused
+});
+
+// Register listener for when a playing timer has finished
+timer.on("finished", (config) => {
+ // Update UI to show timer is finished
+});
+
+// Register listener for the timer progressed by 20 milliseconds
+timer.on("onTick", (milliRemaining) => {
+ // Update UI to show remaining time
+});
+
+// Start synchronizing timer events for users in session
+await timer.initialize();
+
+// Start a 60 second timer
+const durationInMilliseconds = 1000 * 60;
+timer.start(durationInMilliseconds);
+
+// Pause the timer for users in session
+timer.pause();
+
+// Resume the timer for users in session
+timer.play();
+```
+
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import {
+ TeamsFluidClient,
+ EphemeralTimer,
+ EphemeralTimerEvents,
+ ITimerConfig,
+} from "@microsoft/live-share";
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
+const schema = {
+ initialObjects: { timer: EphemeralTimer },
+};
+const { container } = await client.joinContainer(schema);
+const timer = container.initialObjects.timer as EphemeralTimer;
+
+// Register listener for when the timer starts its countdown
+timer.on(EphemeralTimerEvents.started, (config: ITimerConfig, local: boolean) => {
+ // Update UI to show timer has started
+});
+
+// Register listener for when a paused timer has resumed
+timer.on(EphemeralTimerEvents.played, (config: ITimerConfig, local: boolean) => {
+ // Update UI to show timer has resumed
+});
+
+// Register listener for when a playing timer has paused
+timer.on(EphemeralTimerEvents.paused, (config: ITimerConfig, local: boolean) => {
+ // Update UI to show timer has paused
+});
+
+// Register listener for when a playing timer has finished
+timer.on(EphemeralTimerEvents.finished, (config: ITimerConfig) => {
+ // Update UI to show timer is finished
+});
+
+// Register listener for the timer progressed by 20 milliseconds
+timer.on(EphemeralTimerEvents.onTick, (milliRemaining: number) => {
+ // Update UI to show remaining time
+});
+
+// Start synchronizing timer events
+await timer.initialize();
+
+// Start a 60 second timer for users in session
+const durationInMilliseconds = 1000 * 60;
+timer.start(durationInMilliseconds);
+
+// Pause the timer for users in session
+timer.pause();
+
+// Resume the timer for users in session
+timer.play();
+```
+++ ## Role verification for ephemeral data structures Meetings in Teams can range from one-on-one calls to all-hands meetings, and may include members across organizations. Ephemeral objects are designed to support role verification, allowing you to define the roles that are allowed to send messages for each individual ephemeral 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.
-Example:
+> [!NOTE]
+> The `EphemeralPresence` class doesn't support role verification. The `EphemeralPresenceUser` object has a `getRoles` method, which returns the meeting roles for a given user.
+
+Example using `EphemeralState`:
+
+# [JavaScript](#tab/javascript)
```javascript
-import { EphemeralState, UserMeetingRole } from "@microsoft/live-share";
-// ...
+import {
+ TeamsFluidClient,
+ EphemeralState,
+ UserMeetingRole,
+} from "@microsoft/live-share";
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
const schema = { initialObjects: { appState: EphemeralState }, }; const { container } = await client.joinContainer(schema); const { appState } = container.initialObjects;
-// Listen for changes to values in the map
-appState.on("stateChanged", (state, value, local) => {
+// Register listener for changes to state and corresponding custom data
+appState.on("stateChanged", (state, data, local) => {
// Update local app state });
-// Set roles who can change state and start
+// Set roles who can change state and start listening for changes
const allowedRoles = [UserMeetingRole.organizer, UserMeetingRole.presenter];
-appState.start(allowedRoles);
+appState.initialize(allowedRoles);
function onSelectEditMode(documentId) { appState.changeState("editing", {
function onSelectPresentMode(documentId) {
} ```
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import { TeamsFluidClient, EphemeralState, UserMeetingRole } from "@microsoft/live-share";
+
+// Declare interface for type of custom data for user
+interface ICustomState {
+ documentId: string;
+ presentingUserId?: string;
+}
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
+const schema = {
+ initialObjects: {
+ appState: EphemeralState<ICustomState>,
+ },
+};
+const { container } = await client.joinContainer(schema);
+const appState = container.initialObjects.appState as EphemeralState<ICustomState>;
+
+// Register listener for changes to state and corresponding custom data
+appState.on("stateChanged", (state: string, data: ICustomState | undefined, local: boolean) => {
+ // Update local app state
+});
+
+// Set roles who can change state and start listening for changes
+const allowedRoles: UserMeetingRole[] = [UserMeetingRole.organizer, UserMeetingRole.presenter];
+appState.initialize(allowedRoles);
+
+function onSelectEditMode(documentId: string) {
+ appState.changeState("editing", {
+ documentId,
+ });
+}
+
+function onSelectPresentMode(documentId: string) {
+ appState.changeState("presenting", {
+ documentId,
+ presentingUserId: "LOCAL_USER_ID",
+ });
+}
+```
+++ 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. ## Code samples
-| Sample name | Description | JavaScript |
-| -- | - | -- |
+| Sample name | Description | JavaScript |
+| -- | | - |
| Dice Roller | Enable all connected clients to roll a die and view the result. | [View](https://aka.ms/liveshare-diceroller) |
-| Agile Poker | Enable all connected clients to play Agile Poker.| [View](https://aka.ms/liveshare-agilepoker) |
+| Agile Poker | Enable all connected clients to play Agile Poker. | [View](https://aka.ms/liveshare-agilepoker) |
## Next step
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 constructing the `TeamsFluidClient` class, you can define your own `AzureConnectionConfig`. Live Share associates containers you create with meetings, but you'll need to create your own Azure `ITokenProvider` to sign tokens for your containers and regional requirements. For more information, see Azure [Fluid Relay documentation](/azure/azure-fluid-relay/).
+Yes! When constructing the `TeamsFluidClient` class, 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.
+
+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:
+
+* Require storage of data in Fluid containers beyond the lifetime of a meeting.
+* Transmit sensitive data through the service that requires a custom security policy.
+* Develop features through Fluid Framework, for example, `SharedMap`, for your application outside of Teams.
+
+For more information, see [how to guide](./teams-live-share-how-to/how-to-custom-azure-fluid-relay.md) or visit the [Azure Fluid Relay documentation](/azure/azure-fluid-relay/).
<br>
Any data sent or stored through Fluid containers created by Live Share's hosted
<summary><b>What meeting types does Live Share support?</b></summary>
-Currently, only scheduled meetings are supported and all participants must be on the meeting calendar. Meeting types such as, one-on-one calls, group calls, and meet now aren't supported.
+During Preview, only scheduled meetings are supported and all participants must be on the meeting calendar. Meeting types such as, one-on-one calls, group calls, and meet now aren't supported.
<br>
Currently, only scheduled meetings are supported and all participants must be on
<summary><b>Will Live Share's media package work with DRM content?</b></summary>
-No. Teams currently doesn't support encrypted media for tab applications.
+No. Teams currently doesn't support encrypted media for tab applications on desktop. Chrome, Edge, and mobile clients are supported. For more information, you can [track the issue here](https://github.com/microsoft/live-share-sdk/issues/14).
<br>
No. Teams currently doesn't support encrypted media for tab applications.
<details> <summary><b>How many people can attend a Live Share session?</b></summary>
-Currently, Live Share supports a maximum of 100 attendees per session.
+Currently, Live Share supports a maximum of 100 attendees per session. If this is something you're interested in, you can [start a discussion here](https://github.com/microsoft/live-share-sdk/discussions).
+
+<br>
+
+</details>
+
+<details>
+<summary><b>Can I use Live Share's ephemeral 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).
+
+<br>
+
+</details>
+
+<details>
+<summary><b>Can I use multiple Fluid containers?</b></summary>
+
+Currently, Live Share only supports having one container using our provided Azure Fluid Relay service. However, it's possible to use both a Live Share container and a container created by your own Azure Fluid Relay instance.
+
+<br>
+
+</details>
+
+<details>
+<summary><b>Can I change my Fluid container schema after creating the container?</b></summary>
+
+Currently, Live Share doesn't support adding new `initialObjects` to the Fluid `ContainerSchema` after creating or joining a container. Because Live Share sessions are short-lived, this is most commonly an issue during development after adding new features to your app.
+
+> [!NOTE]
+> If you are using the `dynamicObjectTypes` property in the `ContainerSchema`, you can add new types at any point. If you later remove types from the schema, existing DDS instances of those types will gracefully fail.
+
+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 `EphemeralObject` 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:
+
+* 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`.
+
+> [!NOTE]
+> Live Share does not currently support versioning your `ContainerSchema`, nor does it have any APIs dedicated to migrations.
+
+<br>
+
+</details>
+
+<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 is not enforced. For optimal performance, you must debounce changes emitted through `SharedObject` or `EphemeralObject` 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.
<br>
Submit issues and feature requests to the SDK repository for [Live Share SDK](ht
## See also -- [GitHub repository](https://github.com/microsoft/live-share-sdk)-- [Live Share SDK reference docs](/javascript/api/@microsoft/live-share/)-- [Live Share Media SDK reference docs](/javascript/api/@microsoft/live-share-media/)-- [Teams apps in meetings](teams-apps-in-meetings.md)
+* [GitHub repository](https://github.com/microsoft/live-share-sdk)
+* [Live Share SDK reference docs](/javascript/api/@microsoft/live-share/)
+* [Live Share Media SDK reference docs](/javascript/api/@microsoft/live-share-media/)
+* [Teams apps in meetings](teams-apps-in-meetings.md)
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
+
+ Title: How to use custom Azure Fluid Relay service
+
+description: In this module, learn how to use a custom Azure Fluid Relay service with Live Share.
+
+ms.localizationpriority: high
+ 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 is beneficial to use your own Azure Fluid Relay service for your Live Share app.
+
+## Pre-requisites
+
+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).
+3. Provision an Azure Fluid Relay service as outlined in this [tutorial](/azure/azure-fluid-relay/how-tos/provision-fluid-azure-portal).
+
+## Connect to Azure Fluid Relay service
+
+When constructing the `TeamsFluidClient` class, 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.
+
+# [JavaScript](#tab/javascript)
+
+```javascript
+import { TeamsFluidClient, EphemeralPresence } from "@microsoft/live-share";
+import { SharedMap } from "fluid-framework";
+import { AzureFunctionTokenProvider } from "@fluidframework/azure-client";
+
+// Define a custom connection for your app
+const clientProps = {
+ 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",
+ },
+};
+// Join the Fluid container
+const client = new TeamsFluidClient(clientProps);
+const schema = {
+ initialObjects: {
+ presence: EphemeralPresence,
+ ticTacToePositions: SharedMap,
+ },
+};
+const { container } = await client.joinContainer(schema);
+
+// ... ready to start app sync logic
+```
+
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import { TeamsFluidClient, EphemeralPresence, ITeamsFluidClientOptions } from "@microsoft/live-share";
+import { SharedMap } from "fluid-framework";
+import { AzureFunctionTokenProvider } from "@fluidframework/azure-client";
+
+// Define a custom connection for your app
+const clientProps: ITeamsFluidClientOptions = {
+ 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",
+ },
+};
+// Join the Fluid container
+const client = new TeamsFluidClient(clientProps);
+const schema = {
+ initialObjects: {
+ presence: EphemeralPresence,
+ ticTacToePositions: SharedMap,
+ },
+};
+const { container } = await client.joinContainer(schema);
+
+// ... ready to start app sync logic
+```
+++
+## Why use a custom Azure Fluid Relay service?
+
+Consider using a custom AFR service connection if you:
+
+* Require storage of data in Fluid containers beyond the lifetime of a meeting.
+* Transmit sensitive data through the service that requires a custom security policy.
+* Develop features through Fluid Framework for your application outside of Teams.
+
+## Why use Live Share with your custom service?
+
+Azure Fluid Relay is designed to work with any web-based application, meaning it works with or without Microsoft Teams. That raises an important question: if I build my own Azure Fluid Relay service, do I still need Live Share?
+
+Live Share has features that are beneficial to common meeting scenarios that augment other features in your app, including:
+
+* [Container mapping](#container-mapping)
+* [Ephemeral objects and role verification](#ephemeral-objects-and-role-verification)
+* [Media synchronization](#media-synchronization)
+
+### Container mapping
+
+Live Share's `TeamsFluidClient` class is responsible for mapping a unique meeting identifier to your Fluid containers, which ensures that all meeting participants join the same container. As part of this process, the client attempts to connect to a `containerId` mapped to the meeting that one already exists. If one doesn't exist, the `AzureClient` is used to create a container using your `AzureConnectionConfig` and then relay the `containerId` to other meeting participants.
+
+If your app already has a mechanism for creating Fluid containers and sharing them to other members, such as by inserting the `containerId` into the URL shared to the meeting stage, then this may not be necessary for your app.
+
+### Ephemeral objects and role verification
+
+Live Share's ephemeral data structures such as `EphemeralPresence`, `EphemeralState`, and `EphemeralEvent` are tailored to collaboration in meetings and thus aren't supported in Fluid containers used outside of Microsoft Teams. Features like role verification help your app align with expectations of our users.
+
+> [!NOTE]
+> As an added benefit, ephemeral objects also feature faster message latencies compared to traditional Fluid data structures.
+
+For more information, see [core capabilities](../teams-live-share-capabilities.md) page.
+
+### Media synchronization
+
+Packages from `@microsoft/live-share-media` aren't supported in Fluid containers used outside of Microsoft Teams.
+
+For more information, see [media capabilities](../teams-live-share-media-capabilities.md) page.
+
+## See also
+
+* [GitHub repository](https://github.com/microsoft/live-share-sdk)
+* [Live Share SDK reference docs](/javascript/api/@microsoft/live-share/)
+* [Live Share Media SDK reference docs](/javascript/api/@microsoft/live-share-media/)
+* [Live Share capabilities](../teams-live-share-capabilities.md)
+* [Live Share media capabilities](../teams-live-share-media-capabilities.md)
+* [Live Share FAQ](../teams-live-share-faq.md)
+* [Teams apps in meetings](../teams-apps-in-meetings.md)
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
Last updated 04/07/2022
# Live Share media capabilities + Video and audio are instrumental parts of the modern world and workplace. We've heard wide ranging feedback that there is more we can do to increase the quality, accessibility, and license protections of watching videos together in meetings.
-The Live Share SDK enables **media synchronization** into any HTML `<video>` and `<audio>` element simpler than ever before. By synchronizing media at the player state and transport controls layer, you can individually attribute views and license, while providing the highest possible quality available through your app.
+The Live Share SDK enables robust **media synchronization** for any HTML `<video>` and `<audio>` element with just a few lines of code. By synchronizing media at the player state and transport controls layer, you can individually attribute views and license, while providing the highest possible quality available through your app.
## Install
yarn add @microsoft/live-share-media
The Live Share SDK has two primary classes related to media synchronization:
-| Classes | Description |
-| | |
-| [EphemeralMediaSession](/javascript/api/@microsoft/live-share-media/ephemeralmediasession) | Custom ephemeral object designed to coordinate media transport controls and playback state in independent media streams. |
-| [MediaPlayerSynchronizer](/javascript/api/@microsoft/live-share-media/mediaplayersynchronizer) | Synchronizes a local HTML Media Element with a group of remote HTML Media Elements for an `EphemeralMediaSession`.|
+| Classes | Description |
+| - | - |
+| [EphemeralMediaSession](/javascript/api/@microsoft/live-share-media/ephemeralmediasession) | Custom ephemeral object designed to coordinate media transport controls and playback state in independent media streams. |
+| [MediaPlayerSynchronizer](/javascript/api/@microsoft/live-share-media/mediaplayersynchronizer) | Synchronizes any object that implements the `IMediaPlayer` interface -- including HTML5 `<video>` and `<audio>` -- using `EphemeralMediaSession`. |
Example:
Example:
</body> ```
+# [JavaScript](#tab/javascript)
+ ```javascript import * as microsoftTeams from "@microsoft/teams-js";
-import { TeamsFluidClient } from "@microsoft/live-share";
+import { TeamsFluidClient, UserMeetingRole } from "@microsoft/live-share";
import { EphemeralMediaSession } from "@microsoft/live-share-media"; // Initialize the Teams Client SDK
const player = document.getElementById("player");
const synchronizer = mediaSession.synchronize(player); // Define roles you want to allow playback control and start sync
-const allowedRoles = ["Organizer", "Presenter"];
-await mediaSession.start(allowedRoles);
+const allowedRoles = [UserMeetingRole.organizer, UserMeetingRole.presenter];
+await mediaSession.initialize(allowedRoles);
```
-The `EphemeralMediaSession` automatically listens for changes to the group's playback state and applies changes through the `MediaPlayerSynchronizer`. 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.
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import * as microsoftTeams from "@microsoft/teams-js";
+import { TeamsFluidClient, UserMeetingRole } from "@microsoft/live-share";
+import { EphemeralMediaSession, IMediaPlayer, MediaPlayerSynchronizer } from "@microsoft/live-share-media";
+import { ContainerSchema } from "fluid-framework";
+
+// Initialize the Teams Client SDK
+await microsoftTeams.app.initialize();
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
+const schema: ContainerSchema = {
+ initialObjects: { mediaSession: EphemeralMediaSession },
+};
+const { container } = await client.joinContainer(schema);
+const mediaSession = container.initialObjects.mediaSession as EphemeralMediaSession;
+
+// Get the player from your document and create synchronizer
+const player: IMediaPlayer = document.getElementById("player") as HTMLVideoElement;
+const synchronizer: MediaPlayerSynchronizer = mediaSession.synchronize(player);
+
+// Define roles you want to allow playback control and start sync
+const allowedRoles: UserMeetingRole[] = [UserMeetingRole.organizer, UserMeetingRole.presenter];
+await mediaSession.initialize(allowedRoles);
+```
+++
+The `EphemeralMediaSession` automatically listens for changes to the group's playback state. `MediaPlayerSynchronizer` listens to state changes emitted by `EphemeralMediaSession` 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:
document.getElementById("change-track-button").onclick = () => {
}; ```
- > [!Note]
- > While you can use the `EphemeralMediaSession` object to synchronize media directly, using the `MediaPlayerSynchronizer` unless you want more fine tuned control of the synchronization logic. Depending on the player you use in your app, you might want to create a delegate shim to make your web player's interface match the HTML media interface.
+> [!NOTE]
+> While you can use the `EphemeralMediaSession` object to synchronize media manually, it's generally recommend to use the `MediaPlayerSynchronizer`. Depending on the player you use in your app, you might need to create a delegate shim to make your web player's interface match the [IMediaPlayer](/javascript/api/@microsoft/live-share-media/imediaplayer) interface.
## Suspensions and wait points + If you want to temporarily suspend synchronization for the `EphemeralMediaSession` object, you can use suspensions. A [MediaSessionCoordinatorSuspension](/javascript/api/@microsoft/live-share-media/ephemeralmediasessioncoordinatorsuspension) object is local by default, which can be helpful in cases where a user might want to catch up on something they missed, take a break, and so on. If the user ends the suspension, synchronization resumes automatically.
+# [JavaScript](#tab/javascript)
+ ```javascript // Suspend the media session coordinator const suspension = mediaSession.coordinator.beginSuspension();
const suspension = mediaSession.coordinator.beginSuspension();
suspension.end(); ```
-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. This is useful for things like adding a quiz or survey at certain points in the video.
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import { MediaSessionCoordinatorSuspension } from "@microsoft/live-share-media";
+
+// Suspend the media session coordinator
+const suspension: MediaSessionCoordinatorSuspension = mediaSession.coordinator.beginSuspension();
+
+// End the suspension when ready
+suspension.end();
+```
+++
+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.
+
+Here are a few scenarios where wait points are especially useful:
+
+- Adding a quiz or survey at certain points in the video.
+- Waiting for everyone to suitably load a video before it starts or while buffering.
+- Allow a presenter to choose points in the video for group discussion.
+
+# [JavaScript](#tab/javascript)
```javascript // Suspend the media session coordinator
const waitPoint = {
position: 0, reason: "ReadyUp", // Optional. };
-const suspension = mediaSession.coordinator.beginSuspension();
+const suspension = mediaSession.coordinator.beginSuspension(waitPoint);
// End the suspension when the user readies up document.getElementById("ready-up-button").onclick = () => { // Sync will resume when everyone has ended suspension
document.getElementById("ready-up-button").onclick = () => {
}; ```
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import { MediaSessionCoordinatorSuspension, CoordinationWaitPoint } from "@microsoft/live-share-media";
+
+// Suspend the media session coordinator
+const waitPoint: CoordinationWaitPoint = {
+ position: 0,
+ reason: "ReadyUp", // Optional.
+};
+const suspension = mediaSession.coordinator.beginSuspension(waitPoint);
+
+// End the suspension when the user readies up
+document.getElementById("ready-up-button")!.onclick = () => {
+ // Sync will resume when everyone has ended suspension
+ suspension.end();
+};
+```
+++ ## Audio ducking
-The Live Share SDK supports intelligent audio ducking. You can use the _experimental_ feature in your application, add the following to your code:
+The Live Share SDK supports intelligent audio ducking. You can use the _experimental_ feature in your application by adding the following to your code:
+
+# [JavaScript](#tab/javascript)
```javascript import * as microsoftTeams from "@microsoft/teams-js";
-// ...
+// ... set up MediaPlayerSynchronizer
+// Register speaking state change handler through Teams Client SDK
let volumeTimer; microsoftTeams.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; } }); ```
-To enable audio ducking, add the following [RSC](/microsoftteams/platform/graph-api/rsc/resource-specific-consent) permissions into your app manifest:
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import * as microsoftTeams from "@microsoft/teams-js";
+
+// ... set up MediaPlayerSynchronizer
+
+// Register speaking state change handler through Teams Client SDK
+let volumeTimer: NodeJS.Timeout | undefined;
+microsoftTeams.meeting.registerSpeakingStateChangeHandler((speakingState: microsoftTeams.meeting.ISpeakingState) => {
+ 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;
+ }
+});
+```
+++
+Additionally, add the following [RSC](/microsoftteams/platform/graph-api/rsc/resource-specific-consent) permissions into your app manifest:
```json {
To enable audio ducking, add the following [RSC](/microsoftteams/platform/graph-
} ```
-> [!Note]
+> [!NOTE]
> The `registerSpeakingStateChangeHandler` API used for audio ducking currently works only for non-local users who are speaking. ## Code samples
-| Sample name | Description | JavaScript |
-| -- | -| --|
+| Sample name | Description | JavaScript |
+| -- | -- | - |
| React video | Basic example showing how the EphemeralMediaSession object works with HTML5 video. | [View](https://aka.ms/liveshare-reactvideo) | | React media template | Enable all connected clients to watch videos together, build a shared playlist, transfer whom is in control, and annotate over the video. | [View](https://aka.ms/liveshare-mediatemplate) |
To enable audio ducking, add the following [RSC](/microsoftteams/platform/graph-
## See also
-* [Live Share SDK FAQ](teams-live-share-faq.md)
-* [Live Share SDK reference docs](/javascript/api/@microsoft/live-share/)
-* [Live Share Media SDK reference docs](/javascript/api/@microsoft/live-share-media/)
-* [Reference docs](https://aka.ms/livesharedocs)
-* [Teams apps in meetings](teams-apps-in-meetings.md)
+- [Live Share SDK FAQ](teams-live-share-faq.md)
+- [Live Share SDK reference docs](/javascript/api/@microsoft/live-share/)
+- [Live Share Media SDK reference docs](/javascript/api/@microsoft/live-share-media/)
+- [Reference docs](https://aka.ms/livesharedocs)
+- [Teams apps in meetings](teams-apps-in-meetings.md)
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
-> [!Note]
-> The Live Share SDK is currently available only in [Public Developer Preview](../resources/dev-preview/developer-preview-intro.md). You must be part of the Public Developer Preview for Microsoft Teams to use the Live Share SDK.
+> [!NOTE]
+> The Live Share SDK is currently available in [Public Developer Preview](../resources/dev-preview/developer-preview-intro.md). You must be part of the Public Developer Preview for Microsoft Teams to use Live Share.
-Live Share is an SDK designed to transform Teams apps into collaborative multi-user experiences without writing any dedicated back-end code. The Live Share SDK 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.
+Live Share is an SDK designed to transform Teams apps into collaborative multi-user experiences without writing any dedicated back-end code. 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.
> [!div class="nextstepaction"] > [Get started](teams-live-share-quick-start.md)
-The Live Share SDK provides a `TeamsFluidClient` class for connecting to a special Fluid Container associated with each meeting in a few lines of code. In addition to the data structures provided by Fluid Framework, Live Share also supports a new set of distributed data structure (DDS) classes to simplify building applications for common meeting scenarios, such as shared media playback.
+Live Share includes an `TeamsFluidClient` class for connecting to a special Fluid Container associated with each meeting in a few lines of code. In addition to the data structures provided by Fluid Framework, Live Share also supports a new set of distributed data structure (DDS) classes to simplify building applications for common meeting scenarios, such as shared media playback.
:::image type="content" source="../assets/images/teams-live-share/teams-live-share-contoso-video.gif" alt-text="Live Share video sharing experience":::
-## Why build apps using the Live Share SDK?
+## Why build apps with Live Share?
Building collaborative apps can be difficult, time consuming, costly, and includes complex compliance requirements at scale. Teams users spend significant amount of time reviewing work with teammates, watching videos together, and brainstorming new ideas through screen sharing. The Live Share SDK enables you to transform your app into something more collaborative with minimal investment. Here are some key benefits of the Live Share SDK:
-* Zero-hassle session management and security.
-* Stateful and stateless distributed data structures.
-* Media extensions to easily synchronize video and audio.
-* Respect meeting privileges using role verification.
-* Free and fully managed service with low latency.
-* Intelligent audio ducking.
+- Zero-hassle session management and security.
+- Stateful and stateless distributed data structures.
+- Media extensions to easily synchronize video and audio.
+- Respect meeting privileges using role verification.
+- Free and fully managed service with low latency.
+- Intelligent audio ducking.
:::image type="content" source="../assets/images/teams-live-share/Teams-live-share-schematics.png" alt-text="Teams Live Share":::
-## User scenarios
+To understand if Live Share is right for your collaborative scenario, it is helpful to understand the differences between Live Share and other collaborative frameworks, including:
+
+- [Web sockets](#web-sockets)
+- [Azure Fluid Relay](#azure-fluid-relay)
+- [Live Share](#live-share-hosted-service)
+
+### Web sockets
+
+Web sockets are a ubiquitous technology for real-time communication in the web, and some apps may prefer to use their own custom web-socket backend. Unlike REST APIs, web sockets keep an open connection between a server and clients in a session.
+
+Like other custom API services, requirements typically include authenticating sessions, regional mapping, maintenance, and scale. Many collaborative scenarios also require maintaining session state in the server, which requires storage infrastructure, conflict resolutions, and more.
+
+### Azure Fluid Relay
+
+[Azure Fluid Relay](/azure/azure-fluid-relay/) is a managed offering for the Fluid Framework that helps developers build real-time collaborative experiences and replicate state across connected JavaScript clients. Microsoft Whiteboard, Loop, and OneNote are all examples of apps built with Fluid Framework today.
+
+Like other Azure services, Azure Fluid Relay is designed to tailor to your individual project needs with minimal complexity. Requirements include developing an authentication story for your Fluid containers and regional compliance. Once configured, developers can focus on delivering high quality collaborative experiences.
+
+### Live Share hosted service
+
+Live Share provides a turn-key Azure Fluid Relay service backed by the security of Microsoft Teams meetings. Live Share containers are restricted to meeting participants, maintain tenant residency requirements, and can be accessed in a few lines of client code.
+
+# [JavaScript](#tab/javascript)
+
+```javascript
+import { TeamsFluidClient, EphemeralPresence } from "@microsoft/live-share";
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
+const schema = {
+ initialObjects: { presence: EphemeralPresence },
+};
+const { container } = await client.joinContainer(schema);
+
+// ... ready to start app sync logic
+```
-|Scenario|Example|
-| :- | : |
-| Users and their coworkers scheduled a meeting to present an early edit of a marketing video at an upcoming leadership review and want to highlight specific sections for feedback. | Users share the video to the meeting stage and start the video. As needed, the user pauses the video to discuss the scene. Users can take turns drawing over parts of the screen to emphasize key points.|
-| You're a project manager for an agile team playing Agile Poker with your team to estimate the amount of work needed for an upcoming sprint.| You share an Agile Poker planning app to the meeting stage that uses the Live Share SDK and play the planning game until the team meets a consensus.|
+# [TypeScript](#tab/typescript)
+
+```TypeScript
+import { TeamsFluidClient, EphemeralPresence } from "@microsoft/live-share";
+import { ContainerSchema } from "fluid-framework";
+
+// Join the Fluid container
+const client = new TeamsFluidClient();
+const schema: ContainerSchema = {
+ initialObjects: { presence: EphemeralPresence },
+};
+const { container } = await client.joinContainer(schema);
+
+// ... ready to start app sync logic
+```
++ > [!IMPORTANT]
-> Any data sent or stored through the Live Share SDK's hosted Azure Fluid Relay service is accessible for 24 hours. For more information, see [Live Share FAQ](teams-live-share-faq.md).
+> Any data sent or stored through the Live Share SDK's hosted Azure Fluid Relay service is accessible up to 24 hours. For more information, see [Live Share FAQ](teams-live-share-faq.md).
+
+#### Using a custom Azure Fluid Relay service
+
+While most of you find it preferable to use our free hosted service, there are still situations where it is beneficial to use your own Azure Fluid Relay service for your Live Share app.
+
+Consider using a custom service if you:
+
+- Require storage of data in Fluid containers beyond the lifetime of a meeting.
+- Transmit sensitive data through the service that requires a custom security policy.
+- Develop features through Fluid Framework, for example, `SharedMap`, for your application outside of Teams.
+
+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).
+
+## User scenarios
+
+| Scenario | Example |
+| :-- | :- |
+| During a marketing review, a user wants to collect feedback on their latest video edit. | User shares the video to the meeting stage and starts the video. As needed, the user pauses the video to discuss the scene and participants draw over parts of the screen to emphasize key points. |
+| A project manager plays Agile Poker with their team during planning. | Manager shares an Agile Poker app to the meeting stage that enables playing the planning game until the team has consensus. |
+| A financial advisor reviews PDF documents with clients before signing. | The financial advisor shares the PDF contract to the meeting stage. All attendees can see each others cursors and highlighted text in the PDF, after which both parties sign the agreement. |
## Next step
Here are some key benefits of the Live Share SDK:
## See also
-* [GitHub repository](https://github.com/microsoft/live-share-sdk)
-* [Live Share SDK reference docs](/javascript/api/@microsoft/live-share/)
-* [Live Share Media SDK reference docs](/javascript/api/@microsoft/live-share-media/)
-* [Live Share capabilities](teams-live-share-capabilities.md)
-* [Live Share media capabilities](teams-live-share-media-capabilities.md)
-* [Live Share FAQ](teams-live-share-faq.md)
-* [Teams apps in meetings](teams-apps-in-meetings.md)
+- [GitHub repository](https://github.com/microsoft/live-share-sdk)
+- [Live Share SDK reference docs](/javascript/api/@microsoft/live-share/)
+- [Live Share Media SDK reference docs](/javascript/api/@microsoft/live-share-media/)
+- [Live Share capabilities](teams-live-share-capabilities.md)
+- [Live Share media capabilities](teams-live-share-media-capabilities.md)
+- [Live Share FAQ](teams-live-share-faq.md)
+- [Teams apps in meetings](teams-apps-in-meetings.md)
platform Teams Live Share Tutorial https://github.com/MicrosoftDocs/msteams-docs/commits/main/msteams-platform/apps-in-teams-meetings/teams-live-share-tutorial.md
Title: Live Share code tutorial
-description: In this module, learn how to get started with Live Share SDK and how to build Dice Roller sample using Live Share SDK
+description: In this module, learn how to get started with Live Share SDK and how to build Dice Roller sample using Live Share SDK
ms.localizationpriority: high
In the Dice Roller sample app, users are shown a die with a button to roll it. W
## Set up the application Start by importing the required modules. The sample uses the [SharedMap DDS](https://fluidframework.com/docs/data-structures/map/) from the Fluid Framework and the [TeamsFluidClient](/javascript/api/@microsoft/live-share/teamsfluidclient) from the Live Share SDK. The sample supports Teams Meeting Extensibility so we'll need to include the [Teams Client SDK](https://github.com/OfficeDev/microsoft-teams-library-js). Finally, the sample is designed to run both locally and in a Teams meeting so we'll need to include some additional Fluid Framework pieces needed 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 will be available to the container. The sample uses a SharedMap to store the most recent die 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 will be available to the container. The sample uses a SharedMap to store the most recent die value that was rolled. For more information, see [Data modeling](https://fluidframework.com/docs/build/data-modeling/).
Teams meeting apps require multiple views (content, configuration, and stage). We'll create a `start()` function to help identify the view to render and to perform any initialization that's required. We want our app to support running both locally in a web browser and from within a Teams Meeting so the `start()` function looks for an `inTeams=true` query parameter to determine if it's running in Teams. When running in Teams your application need to call `app.initialize()` prior to calling any other teams-js methods.
const root = document.getElementById("content");
const diceValueKey = "dice-value-key"; const containerSchema = {
- initialObjects: { diceMap: SharedMap }
+ initialObjects: { diceMap: SharedMap },
}; function onContainerFirstCreated(container) {
function onContainerFirstCreated(container) {
container.initialObjects.diceMap.set(diceValueKey, 1); } - // STARTUP LOGIC async function start() {- // Check for page to display
- let view = searchParams.get('view') || 'stage';
+ let view = searchParams.get("view") || "stage";
// Check if we are running on stage.
- if (!!searchParams.get('inTeams')) {
-
+ 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';
+ if (context.page.frameContext == "meetingStage") {
+ view = "stage";
} } // Load the requested view switch (view) {
- case 'content':
+ case "content":
renderSidePanel(root); break;
- case 'config':
+ case "config":
renderSettings(root); break;
- case 'stage':
+ case "stage":
default: const { container } = await joinContainer(); renderStage(container.initialObjects.diceMap, root);
start().catch((error) => console.error(error));
## Join a Fluid container
-Not all of your apps views will need to be collaborative. The `stage` view *always* needs collaborative features, the `content` view *may* need collaborative features, and the `config` view should *never* need collaborative features. For the views that do need collaborative features you'll need to join a Fluid container associated with the current meeting.
+Not all of your apps views will need to be collaborative. The `stage` view _always_ needs collaborative features, the `content` view _may_ need collaborative features, and the `config` view should _never_ need collaborative features. For the views that do need collaborative features you'll need to join a Fluid container associated with the current meeting.
-Joining the container for the meeting is as simple as creating a new [TeamsFluidClient](/javascript/api/@microsoft/live-share/teamsfluidclient), and then calling it's [joinContainer()](/javascript/api/@microsoft/live-share/teamsfluidclient#@microsoft-live-share-teamsfluidclient-joincontainer) method. When running locally you'll need to pass in a custom connection config with a special `LOCAL_MODE_TENANT_ID` but otherwise, join a local container is the same as joining a container in Teams.
+Joining the container for the meeting is as simple as creating a new [TeamsFluidClient](/javascript/api/@microsoft/live-share/teamsfluidclient), and then calling it's [joinContainer()](/javascript/api/@microsoft/live-share/teamsfluidclient#@microsoft-live-share-teamsfluidclient-joincontainer) method. When running locally you'll need to pass in a custom connection config with a special `LOCAL_MODE_TENANT_ID` but otherwise, join a local container is the same as joining a container in Teams.
```js async function joinContainer() { // Are we running in teams? let client;
- if (!!searchParams.get('inTeams')) {
- // Create client
- client = new TeamsFluidClient();
+ if (!!searchParams.get("inTeams")) {
+ // Create client
+ client = new TeamsFluidClient();
} else {
- // Create client and configure for testing
- client = new TeamsFluidClient({
- connection: {
- tenantId: LOCAL_MODE_TENANT_ID,
- tokenProvider: new InsecureTokenProvider("", { id: "123", name: "Test User" }),
- orderer: "http://localhost:7070",
- storage: "http://localhost:7070",
- }
- });
+ // Create client and configure for testing
+ client = new TeamsFluidClient({
+ connection: {
+ type: "local",
+ tokenProvider: new InsecureTokenProvider("", {
+ id: "123",
+ name: "Test User",
+ }),
+ endpoint: "http://localhost:7070",
+ },
+ });
} // Join container
stageTemplate["innerHTML"] = `
<div class="dice"></div> <button class="roll"> Roll </button> </div>
-`
-function renderStage(diceMap, 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) => {
-        // Unicode 0x2680-0x2685 are the sides of a die (⚀⚁⚂⚃⚄⚅).
-        dice.textContent = String.fromCodePoint(0x267f + value);
-    };
- updateDice(1);
+`;
+function renderStage(diceMap, 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) => {
+ // Unicode 0x2680-0x2685 are the sides of a die (⚀⚁⚂⚃⚄⚅).
+ dice.textContent = String.fromCodePoint(0x267f + value);
+ };
+ updateDice(1);
} ```
To begin using Fluid in the application, the first thing to change is what happe
This pattern is common in Fluid 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);
+rollButton.onclick = () =>
+ diceMap.set("dice-value-key", Math.floor(Math.random() * 6) + 1);
``` ### Rely on Fluid data
This pattern is common in Fluid because it enables the view to behave the same w
The next change that needs to be made is to change the `updateDice` function so 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. ```js
- const updateDice = () => {
- const diceValue = diceMap.get("dice-value-key");
-        dice.textContent = String.fromCodePoint(0x267f + diceValue);
-    };
- updateDice();
+const updateDice = () => {
+ const diceValue = diceMap.get("dice-value-key");
+ dice.textContent = String.fromCodePoint(0x267f + diceValue);
+};
+updateDice();
``` ### Handle remote changes
The next change that needs to be made is to change the `updateDice` function so
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/). ```js
- diceMap.on("valueChanged", updateDice);
+diceMap.on("valueChanged", updateDice);
``` ## Write the side panel view
-The side panel view, loaded through the tab `contentUrl` with the `sidePanel` frame context, is displayed to the user in a side panel when they open your app within a meeting. The goal of this view is to let a user select content for the app prior to sharing the app to the meeting stage. For the Live Share SDK apps, the side panel view can also be used as a companion experience for the app. Calling [joinContainer()](/javascript/api/@microsoft/live-share/teamsfluidclient#@microsoft-live-share-teamsfluidclient-joincontainer) from the side panel view connects to the same Fluid container the stage view is connected to. This container can then be used to communicate with the stage view. Ensure that you're communicating with everyone's stage view *and* side panel view.
+The side panel view, loaded through the tab `contentUrl` with the `sidePanel` frame context, is displayed to the user in a side panel when they open your app within a meeting. The goal of this view is to let a user select content for the app prior to sharing the app to the meeting stage. For the Live Share SDK apps, the side panel view can also be used as a companion experience for the app. Calling [joinContainer()](/javascript/api/@microsoft/live-share/teamsfluidclient#@microsoft-live-share-teamsfluidclient-joincontainer) from the side panel view connects to the same Fluid container the stage view is connected to. This container can then be used to communicate with the stage view. Ensure that you're communicating with everyone's stage view _and_ side panel view.
The sample's side panel view prompts the user to select the share to stage button.
sidePanelTemplate["innerHTML"] = `
`; function renderSidePanel(elem) {
- elem.appendChild(sidePanelTemplate.content.cloneNode(true));
+ elem.appendChild(sidePanelTemplate.content.cloneNode(true));
} ```
function renderSidePanel(elem) {
The settings view, loaded through `configurationUrl` in your app manifest, is shown to a user when they first add your app to a Teams Meeting. This view lets the developer configure the `contentUrl` for the tab that is pinned to the meeting based on user input. This page is currently required even if no user input is required to set the `contentUrl`.
-> [!Important]
+> [!IMPORTANT]
> The Live Share SDK's [joinContainer()](/javascript/api/@microsoft/live-share/teamsfluidclient#@microsoft-live-share-teamsfluidclient-joincontainer) is not supported in the tab `settings` context. The sample's settings view prompts the user to select the save button.
settingsTemplate["innerHTML"] = `
`; function renderSettings(elem) {
- elem.appendChild(settingsTemplate.content.cloneNode(true));
-
- // Save the configurable tab
- pages.config.registerOnSaveHandler(saveEvent => {
- pages.config.setConfig({
- websiteUrl: window.location.origin,
- contentUrl: window.location.origin + '?inTeams=1&view=content',
- entityId: 'DiceRollerFluidLiveShare',
- suggestedDisplayName: 'DiceRollerFluidLiveShare'
- });
- saveEvent.notifySuccess();
+ elem.appendChild(settingsTemplate.content.cloneNode(true));
+
+ // Save the configurable tab
+ pages.config.registerOnSaveHandler((saveEvent) => {
+ pages.config.setConfig({
+ websiteUrl: window.location.origin,
+ contentUrl: window.location.origin + "?inTeams=1&view=content",
+ entityId: "DiceRollerFluidLiveShare",
+ suggestedDisplayName: "DiceRollerFluidLiveShare",
});
+ saveEvent.notifySuccess();
+ });
- // Enable the Save button in config dialog
- pages.config.setValidityState(true);
+ // Enable the Save button in config dialog
+ pages.config.setValidityState(true);
} ```
After you've started running your app locally with `npm run start`, you can then
1. Use ngrok to create a tunnel with port 8080. Run the following command:
- ```
- `ngrok http 8080 --host-header=localhost`
- ```
+ ```bash
+ ngrok http 8080 --host-header=localhost
+ ```
- A new ngrok terminal opens with a new url, for example `https:...ngrok.io`. The new URL is the tunnel that points to your app, which needs to be updated in your app `manifest.json`.
+ A new ngrok terminal opens with a new url, for example `https:...ngrok.io`. The new URL is the tunnel that points to your app, which needs to be updated in your app `manifest.json`.
### Create the app package to sideload into Teams
After you've started running your app locally with `npm run start`, you can then
Replace `https://<<BASE_URI_DOMAIN>>` with your http endpoint from ngrok. 1. You can update the following fields:
- * Set `developer.name` to your name.
- * Update `developer.websiteUrl` with your website.
- * Update `developer.privacyUrl` with your privacy policy.
- * Update `developer.termsOfUseUrl` with your terms of use.
+
+ - Set `developer.name` to your name.
+ - Update `developer.websiteUrl` with your website.
+ - Update `developer.privacyUrl` with your privacy policy.
+ - Update `developer.termsOfUseUrl` with your terms of use.
1. Zip the contents of the manifest folder to create `manifest.zip`. Ensure that the `manifest.zip` contains only the `manifest.json` source file, `color` icon, and the `outline` icon. 1. On Windows, select all files in `.\manifest` directory and compress them.
-
+ > [!NOTE] >
- > * Do not zip the containing folder.
- > * Give your zip file a descriptive name. For example, `DiceRollerLiveShare`.
-
+ > - Do not zip the containing folder.
+ > - Give your zip file a descriptive name. For example, `DiceRollerLiveShare`.
+ For more information on manifest, visit the [Teams manifest documentation](../resources/schem) ### Sideload your app into a meeting
After you're ready to deploy your code, you can use [Teams Toolkit](../toolkit/p
## Code samples
-| Sample name | Description | JavaScript |
-| :- | - | |
+| Sample name | Description | JavaScript |
+| :- | | |
| Dice Roller | Enable all connected clients to roll a die and view the result. | [View](https://github.com/microsoft/live-share-sdk/tree/main/samples/01.dice-roller) | ## Next step
After you're ready to deploy your code, you can use [Teams Toolkit](../toolkit/p
## See also
-* [GitHub repository](https://github.com/microsoft/live-share-sdk)
-* [Live Share SDK reference docs](/javascript/api/@microsoft/live-share/)
-* [Live Share Media SDK reference docs](/javascript/api/@microsoft/live-share-media/)
-* [Live Share FAQ](teams-live-share-faq.md)
-* [Teams apps in meetings](teams-apps-in-meetings.md)
+- [GitHub repository](https://github.com/microsoft/live-share-sdk)
+- [Live Share SDK reference docs](/javascript/api/@microsoft/live-share/)
+- [Live Share Media SDK reference docs](/javascript/api/@microsoft/live-share-media/)
+- [Live Share FAQ](teams-live-share-faq.md)
+- [Teams apps in meetings](teams-apps-in-meetings.md)