Поделиться через


Основные возможности Live Share

Снимок экрана: пример пользователей, играющих в гибкую игру в покер на собрании Teams, где демонстрируется возможность live share.

Пакет Live Share SDK можно добавить в расширения sidePanel и meetingStage контексты ваших собраний с минимальными усилиями. Пакет SDK также можно использовать в контекстах чата и каналов content , таких как настраиваемые вкладки, статические вкладки и представление этапов совместной работы.

Примечание.

Контексты live Share content в чате и каналах поддерживаются только в классических и веб-клиентах Teams.

В этой статье рассматривается интеграция пакета Live Share SDK в ваше приложение, а также основные возможности пакета SDK.

Предварительные условия

Установка пакета SDK для JavaScript

Пакет SDK Live Share — это пакет JavaScript, опубликованный в npm, который можно скачать через npm или yarn. Необходимо также установить одноранговые зависимости Live Share, в том числе fluid-framework и @fluidframework/azure-client. Если вы используете Live Share в приложении вкладки, также необходимо установить @microsoft/teams-js версию 2.23.0 или более позднюю. Если вы хотите использовать класс для разработки локального TestLiveShareHost браузера, необходимо установить @fluidframework/test-client-utils пакеты и start-server-and-test в .devDependencies

npm

npm install @microsoft/live-share fluid-framework @fluidframework/azure-client --save
npm install @microsoft/teams-js --save
npm install @fluidframework/test-client-utils start-server-and-test --save-dev

Пряжи

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

Регистрация разрешений RSC

Чтобы включить пакет SDK Live Share для расширения вкладки, необходимо сначала добавить следующие разрешения RSC в манифест приложения:

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

Присоединение к сеансу

Выполните действия, чтобы присоединиться к сеансу, связанному с собранием, чатом или каналом пользователя.

  1. Инициализируйте LiveShareClient.
  2. Определение структур данных для синхронизации. Например, LiveState или SharedMap.
  3. Присоединение к контейнеру.

Пример.

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

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

// ... ready to start app sync logic

Это все, что потребовалось для настройки контейнера и присоединения к сеансу, сопоставленного с собранием, чатом или каналом. Теперь давайте рассмотрим различные типы распределенных структур данных, которые можно использовать с Live Share SDK.

Совет

Убедитесь, что пакет SDK для клиента Teams инициализирован перед вызовом LiveShareHost.create().

Структуры данных Live Share

Пакет SDK Live Share включает набор новых структур распределенных данных, которые расширяют класс Fluid DataObject , предоставляя новые типы объектов с отслеживанием состояния и без отслеживания состояния. В отличие от структур данных Fluid, классы Live Share LiveDataObject не записывают изменения в контейнер Fluid, что обеспечивает более быструю синхронизацию. Кроме того, эти классы были разработаны с нуля для распространенных сценариев в собраниях, чатах и каналах Teams. Распространенные сценарии включают синхронизацию содержимого, которое просматривает выступающий, отображение метаданных для каждого пользователя в сеансе или отображение таймера обратного отсчета.

Динамический объект Описание
LivePresence Узнайте, какие пользователи находятся в сети, задайте настраиваемые свойства для каждого пользователя и транслируйте изменения, связанные с их присутствием.
LiveState Синхронизация любого сериализуемого state значения JSON.
LiveTimer Синхронизация таймера обратного отсчета для заданного интервала.
LiveEvent Трансляция отдельных событий с помощью любых настраиваемых атрибутов данных в полезных данных.
LiveFollowMode Следите за конкретными пользователями, представляемые всем участникам сеанса, а также запуск или завершение приостановки.

Пример LivePresence

Снимок экрана: пример отображения пользователей, доступных в сеансеСборы с использованием присутствия Live Share.

Класс LivePresence упрощает отслеживание того, кто находится в сеансе. При вызове .initialize() методов или .updatePresence() можно назначить пользовательские метаданные для этого пользователя, такие как изображение профиля, идентификатор просматриваемого содержимого и многое другое. Прослушивая presenceChanged события, каждый клиент получает последний LivePresenceUser объект, сворав все обновления присутствия в одну запись для каждого уникального userIdобъекта .

Ниже приведено несколько примеров, в которых LivePresence можно использовать в приложении.

  • Получение Microsoft Teams userId, displayNameи roles каждого пользователя в сеансе.
  • Отображение пользовательских сведений о каждом пользователе, подключенном к сеансу, например URL-адрес изображения профиля.
  • Синхронизация координат в трехмерной сцене, где находится аватар каждого пользователя.
  • Отчет о положении курсора каждого пользователя в текстовом документе.
  • Публикация ответа каждого пользователя на вопрос ледокола во время действия группы.
import {
  LiveShareClient,
  LivePresence,
  PresenceState,
} from "@microsoft/live-share";
import { LiveShareHost } from "@microsoft/teams-js";

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

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

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

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

Пользователи, присоединяющиеся к сеансу с одного устройства, имеют одну LivePresenceUser запись, которая предоставляется всем их устройствам. Чтобы получить доступ к последним data и state для каждого из активных подключений, можно использовать getConnections() API из LivePresenceUser класса . Возвращается список LivePresenceConnection объектов. Вы можете увидеть, является ли данный LivePresenceConnection экземпляр локальным устройством isLocalConnection , с помощью свойства .

Каждый LivePresenceUser экземпляр и LivePresenceConnection имеет state свойство , которое может иметь значение online, offlineили away. Событие presenceChanged создается при изменении состояния пользователя. Например, если пользователь отключается от сеанса или закрывает приложение, его состояние изменяется на offline.

Примечание.

Обновление до может занять до 20 секунд LivePresenceUserstateoffline после отключения пользователя от сеанса.

Пример LiveState

Снимок экрана: пример состояния Live Share для синхронизации планеты в Солнечной системе, активно представленной на собрании.

Класс LiveState позволяет синхронизировать простое состояние приложения для подключенных участников. LiveState синхронизирует одно state значение, что позволяет синхронизировать любое сериализуемое значение JSON, например string, numberили object.

Ниже приведено несколько примеров, в которых LiveState можно использовать в приложении.

  • Задание идентификатора пользователя текущего докладчика для создания функции контроля .
  • Синхронизация текущего пути маршрута для приложения, чтобы убедиться, что все пользователи находятся на одной странице. Например, /whiteboard/:whiteboardId.
  • Поддержание идентификатора содержимого, просматриваемого текущим выступающим. Например, на taskId доске задач.
  • Синхронизация текущего шага в многокрутном групповом действии. Например, этап угадания во время игры Agile Poker.
  • Синхронизация позиции прокрутки для функции отслеживания.

Примечание.

В отличие от SharedMap, state значение в LiveState будет сброшено после отключения всех пользователей от сеанса.

Пример:

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

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

// Register listener for changes to the state.
// This should be done before calling `.initialize()`.
appState.on("stateChanged", (planetName, local, clientId) => {
  // Update app with newly selected planet.
  // See which user made the change (optional)
  const clientInfo = await appState.getClientInfo(clientId);
});

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

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

Пример LiveEvent

Снимок экрана: пример клиента Teams, отображающего уведомление при изменении события.

LiveEvent — это отличный способ отправки простых событий другим подключенным клиентам, которые необходимы только во время доставки. Он полезен для таких сценариев, как отправка уведомлений о сеансе или реализация пользовательских реакций.

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

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

// Register listener to receive events sent through this object.
// This should be done before calling `.initialize()`.
customReactionEvent.on("received", async (kudosReaction, local, clientId) => {
  console.log("Received reaction:", kudosReaction, "from clientId", clientId);
  // See which user made the change (optional)
  const clientInfo = await customReactionEvent.getClientInfo(clientId);
  // Display notification in your UI
});

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

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

Пример LiveTimer

Снимок экрана: пример таймера отсчета с оставшимися 9 секундами.

LiveTimer предоставляет простой таймер обратного отсчета, который синхронизируется для всех подключенных участников. Он полезен для сценариев с ограничением по времени, таких как таймер групповой медитации или круглый таймер для игры. Его также можно использовать для планирования задач для всех участников сеанса, например для отображения запроса напоминания.

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

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

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

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

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

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

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

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

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

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

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

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

Пример LiveFollowMode

На рисунке показаны три клиента с тремя отдельными представлениями: выступающий, пользователь, который следует за выступающим, и пользователь с собственным частным представлением с возможностью синхронизации с выступающим.

Класс LiveFollowMode объединяет LivePresence и LiveState в один класс, что позволяет легко реализовать режимы подписчиков и выступающих в приложении. Это позволяет реализовать знакомые шаблоны из популярных приложений для совместной работы, таких как PowerPoint Live, Excel Live и Whiteboard. В отличие от общего доступа к экрану, LiveFollowMode позволяет отображать содержимое с высоким качеством, улучшенными специальными возможностями и повышенной производительностью. Пользователи могут легко переключаться между своими личными представлениями и следить за другими пользователями.

Функцию startPresenting() можно использовать для управления приложением для всех остальных пользователей в сеансе. Кроме того, можно разрешить пользователям по отдельности выбирать определенных пользователей, за которыми они хотят следовать с помощью followUser() функции . В обоих сценариях пользователи могут временно войти в закрытое представление с beginSuspension() функцией или синхронизироваться с выступающим с функцией endSuspension() . Между тем, update() функция позволяет локальному пользователю информировать других клиентов в сеансе о своих собственных личных stateValueданных . LivePresenceКак и , вы можете прослушивать изменения каждого пользователя stateValue с помощью presenceChanged прослушивателя событий.

LiveFollowMode также предоставляет state объект , который динамически обновляется в зависимости от пользователя, на который следует локальный пользователь. Например, если локальный пользователь ни за кем не следит, state.value свойство соответствует последней stateValue трансляции локального пользователя через update(). Однако если локальный пользователь следит за выступающим, state.value свойство соответствует последнему stateValue. Как и LiveState, вы можете прослушивать изменения state значения с помощью stateChanged прослушивателя событий. Дополнительные сведения об объекте см. в справочнике state по интерфейсу IFollowModeState.

Ниже приведено несколько примеров, которые можно использовать LiveFollowMode в приложении:

  • Синхронизация позиций камеры в трехмерной сцене для совместного выполнения проверки дизайна.
  • Обновите , slideId чтобы открыть в карусели для продуктивных презентаций и обсуждений.
  • Широковещательный path для открытия на маршрутизаторе приложения.

Пример:

import {
  LiveShareClient,
  LiveFollowMode,
  FollowModeType,
} 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: {
    followMode: LiveFollowMode,
  },
};
const { container } = await liveShare.joinContainer(schema);
const followMode = container.initialObjects.followMode;

// As an example, we will assume there is a button in the application document
const button = document.getElementById("action-button");
// As an example, we will assume there is a div with text showing the follow state
const infoText = document.getElementById("info-text");

// Register listener for changes to the `state` value to use in your app.
// This should be done before calling `.initialize()`.
followMode.on("stateChanged", (state, local, clientId) => {
  console.log("The state changed:");
  console.log("- state value:", state.value);
  console.log("- follow mode type:", state.type);
  console.log("- following user id:", state.followingUserId);
  console.log(
    "- count of other users also following user",
    state.otherUsersCount
  );
  console.log(
    "- state.value references local user's stateValue",
    state.isLocalValue
  );
  // Can optionally get the relevant user's presence object
  const followingUser = followMode.getUserForClient(clientId);
  switch (state.type) {
    case FollowModeType.local: {
      // Update app to reflect that the user is not currently following anyone and there is no presenter.
      infoText.innerHTML = "";
      // Show a "Start presenting" button in your app.
      button.innerHTML = "Start presenting";
      button.onclick = followMode.startPresenting;
      // Note: state.isLocalValue will be true.
      break;
    }
    case FollowModeType.activeFollowers: {
      // Update app to reflect that the local user is being followed by other users.
      infoText.innerHTML = `${state.otherUsersCount} users are following you`;
      // Does not mean that the local user is presenting to everyone, so you can still show the "Start presenting" button.
      button.innerHTML = "Present to all";
      button.onclick = followMode.startPresenting;
      // Note: state.isLocalValue will be true.
      break;
    }
    case FollowModeType.activePresenter: {
      // Update app to reflect that the local user is actively presenting to everyone.
      infoText.innerHTML = `You are actively presenting to everyone`;
      // Show a "Stop presenting" button in your app.
      button.innerHTML = "Stop presenting";
      button.onclick = followMode.stopPresenting;
      // Note: state.isLocalValue will be true.
      break;
    }
    case FollowModeType.followPresenter: {
      // The local user is following a remote presenter.
      infoText.innerHTML = `${followingUser?.displayName} is presenting to everyone`;
      // Show a "Take control" button in your app.
      button.innerHTML = "Take control";
      button.onclick = followMode.startPresenting;
      // Note: state.isLocalValue will be false.
      break;
    }
    case FollowModeType.suspendFollowPresenter: {
      // The local user is following a remote presenter but has an active suspension.
      infoText.innerHTML = `${followingUser?.displayName} is presenting to everyone`;
      // Show a "Sync to presenter" button in your app.
      button.innerHTML = "Sync to presenter";
      button.onclick = followMode.endSuspension;
      // Note: state.isLocalValue will be true.
      break;
    }
    case FollowModeType.followUser: {
      // The local user is following a specific remote user.
      infoText.innerHTML = `You are following ${followingUser?.displayName}`;
      // Show a "Stop following" button in your app.
      button.innerHTML = "Stop following";
      button.onclick = followMode.stopFollowing;
      // Note: state.isLocalValue will be false.
      break;
    }
    case FollowModeType.suspendFollowUser: {
      // The local user is following a specific remote user but has an active suspension.
      infoText.innerHTML = `You were following ${followingUser?.displayName}`;
      // Show a "Resume following" button in your app.
      button.innerHTML = "Resume following";
      button.onclick = followMode.endSuspension;
      // Note: state.isLocalValue will be true.
      break;
    }
    default: {
      break;
    }
  }
  const newCameraPosition = state.value;
  // TODO: apply new camera position
});

// Register listener for changes to each user's personal state updates.
// This should be done before calling `.initialize()`.
followMode.on("presenceChanged", (user, local) => {
  console.log("A user presence changed:");
  console.log("- display name:", user.displayName);
  console.log("- state value:", user.data?.stateValue);
  console.log("- user id user is following:", user.data?.followingUserId);
  console.log("- change from local client", local);
  console.log("- change impacts local user", user.isLocalUser);
  // As an example, we will assume there is a button for each user in the session.
  document.getElementById(`follow-user-${user.userId}-button`).onclick = () => {
    followMode.followUser(user.userId);
  };
  // Update 3D scene to reflect this user's camera position (e.g., orb + display name)
  const userCameraPosition = user.data?.stateValue;
});

// Define the initial stateValue for the local user (optional).
const startingCameraPosition = {
  x: 0,
  y: 0,
  z: 0,
};
// Start receiving incoming presence updates from the session.
// This will also broadcast the user's `startingCameraPosition` to others in the session.
await followMode.initialize(startingCameraPosition);

// Example of an event listener for a camera position changed event.
// For something like a camera change event, you should use a debounce function to prevent sending updates too frequently.
// Note: it helps to distinguish changes initiated by the local user (e.g., drag mouse) separately from other change events.
function onCameraPositionChanged(position, isUserAction) {
  // Broadcast change to other users so that they have their latest camera position
  followMode.update(position);
  // If the local user changed the position while following another user, we want to suspend.
  // Note: helps to distinguish changes initiated by the local user (e.g., drag mouse) separately from other change events.
  if (!isUserAction) return;
  switch (state.type) {
    case FollowModeType.followPresenter:
    case FollowModeType.followUser: {
      // This will trigger a "stateChanged" event update for the local user only.
      followMode.beginSuspension();
      break;
    }
    default: {
      // No need to suspend for other types
      break;
    }
  }
}

В meetingStage контекстах пользователи синхронно взаимодействуют и представляют презентации, чтобы упростить более продуктивные обсуждения. Когда пользователь представляет содержимое на стадии собрания, необходимо вызвать startPresenting() API для первоначального докладчика. В content таких контекстах, как совместное представление этапов, содержимое чаще всего используется асинхронно. В этом случае лучше разрешить пользователям участвовать в совместной работе в режиме реального времени, например с помощью кнопки "Подписаться". teamsJs.app.getContext() С помощью API в пакете SDK для JavaScript для Teams можно легко настроить функциональность соответствующим образом.

Пример:

import {
  LiveShareClient,
  LiveFollowMode,
  FollowModeType,
} from "@microsoft/live-share";
import {
  app,
  meeting,
  FrameContexts,
  LiveShareHost,
} from "@microsoft/teams-js";

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

// Get teamsJs context
const context = await app.getContext();
// Take control if in meetingStage context and local user is initial presenter
if (context.page?.frameContext === FrameContexts.meetingStage) {
  // Take control if in meetingStage context and local user is initial presenter
  meeting.getAppContentStageSharingState((error, state) => {
    const isShareInitiator = state?.isShareInitiator;
    if (!isShareInitiator) return;
    // The user is the initial presenter, so we "take control"
    await followMode.startPresenting();
  });
}
// TODO: rest of app logic

Проверка ролей для динамических структур данных

Собрания в Teams включают звонки, собрания всех рук и онлайн-классы. Участники собрания могут охватывать различные организации, иметь разные привилегии или иметь разные цели. Следовательно, важно уважать привилегии различных ролей пользователей во время собраний. Динамические объекты предназначены для поддержки проверки ролей, что позволяет определить роли, которым разрешено отправлять сообщения для каждого отдельного динамического объекта. Например, вы выбрали параметр, разрешающий только выступающим и организаторам собраний управлять воспроизведением видео. Однако гости и участники по-прежнему могут запросить следующие видео для watch.

Примечание.

При доступе к Live Share из контекста чата content или канала все пользователи Organizer будут иметь роли и Presenter .

В следующем примере, где только выступающие и организаторы могут управлять, LiveState используется для синхронизации пользователя, являющегося активным выступающим:

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

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

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

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

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

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

Перед реализацией проверки роли в приложении, особенно роли организатора, выслушайте своих клиентов, чтобы понять их сценарии. Нет никакой гарантии, что организатор собрания будет присутствовать в собрании. Как правило, все пользователи являются организатором или выступающим при совместной работе в организации. Если пользователь является участником, то обычно это осознанное решение, принятое организатором собрания.

В некоторых случаях у пользователя может быть несколько ролей. Например, организатор также является выступающим. Кроме того, участники собрания, которые являются внешними для клиента, в котором размещено собрание, имеют роль гостя , но также могут иметь права докладчика . Это обеспечивает большую гибкость при использовании проверки ролей в приложении.

Примечание.

Пакет SDK Live Share не поддерживается для гостевых пользователей в собраниях каналов.

Гибко распределенные структуры данных

Пакет Live Share SDK поддерживает любую распределенную структуру данных, включенную в Fluid Framework. Эти функции служат набором примитивов, которые можно использовать для создания надежных сценариев совместной работы, таких как обновление списка задач в режиме реального времени или совместное редактирование текста в HTML <textarea>.

LiveDataObject В отличие от классов, упомянутых в этой статье, структуры данных Fluid не сбрасываются после закрытия приложения. Это идеально подходит для таких сценариев, как собрания sidePanel и content контексты, когда пользователи часто закрывают и повторно открывают приложение.

Fluid Framework официально поддерживает следующие типы распределенных структур данных:

Общий объект Описание
SharedMap Распределенное хранилище пары \"ключ -значение\". Задайте любой сериализуемый объект JSON для заданного ключа, чтобы синхронизировать этот объект для всех пользователей, находящихся в сеансе.
SharedSegmentSequence Структура данных, подобная списку, для хранения набора элементов (называемых сегментами) в заданных позициях.
SharedString Последовательность распределенных строк, оптимизированная для редактирования текста документов или текстовых областей.

Давайте рассмотрим принцип работы SharedMap. В этом примере мы использовали SharedMap для создания функции списка воспроизведения.

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

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

// 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);
}

Примечание.

Основные объекты Fluid Framework DDS не поддерживают проверку роли собрания. Все участники собрания могут изменять данные, хранящиеся в этих объектах.

Тестирование локального браузера

Вы можете локально протестировать пакет SDK Live Share в браузере TestLiveShareHost с помощью класса , не устанавливая приложение в Teams. Это полезно для тестирования основных возможностей совместной работы приложения в знакомой localhost среде.

Пример:

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

/**
 * Detect whether you are in Teams or local environment using your preferred method.
 * Options for this include: environment variables, URL params, Teams FX, etc.
 */
const inTeams = process.env.IN_TEAMS;
// Join the Fluid container
const host = inTeams ? LiveShareHost.create() : TestLiveShareHost.create();
const liveShare = new LiveShareClient(host);
const schema = {
  initialObjects: {
    liveState: LiveState,
    sharedMap: SharedMap,
  },
};
const { container } = await liveShare.joinContainer(schema);

// ... ready to start app sync logic

Класс TestLiveShareHost использует tinylicious тестовый сервер из Fluid Framework, а не рабочую службу Azure Fluid Relay. Для этого необходимо добавить несколько скриптов в свой package.json , чтобы запустить тестовый сервер. Также необходимо добавить @fluidframework/test-client-utils пакеты и start-server-and-test в devDependenciespackage.json.

{
  "scripts": {
    "start": "start-server-and-test start:server 7070 start:client",
    "start:client": "{YOUR START CLIENT COMMAND HERE}",
    "start:server": "npx tinylicious@latest"
  },
  "devDependencies": {
    "@fluidframework/test-client-utils": "^1.3.6",
    "start-server-and-test": "^2.0.0"
  }
}

При запуске приложения таким образом LiveShareClient объект добавляется #{containerId} в URL-адрес, если он не существует. Затем можно скопировать и вставить URL-адрес в новое окно браузера, чтобы подключиться к тому же контейнеру Fluid.

Примечание.

По умолчанию все клиенты, подключенные через , TestLiveShareHost будут иметь presenter роли и organizer .

Примеры кода

Название примера Описание JavaScript TypeScript
Dice Roller Включение во всех подключенных клиентах возможности прокрутки игральной кости и просмотра результата. View View
Игра в покер по гибкой методике Включите все подключенные клиенты для игры в покер по гибкой методике.. View Н/Д
Трехмерная модель Включите все подключенные клиенты для совместного просмотра трехмерной модели. Н/Д Просмотр
Timer Включите все подключенные клиенты для просмотра таймера обратного отсчета. Н/Д Просмотр
Аватары присутствия Отображение аватаров присутствия для всех подключенных клиентов. Н/Д Просмотр

Следующий этап

См. также