import {
LiveShareClient,
UserMeetingRole,
MediaPlayerSynchronizerEvents,
} from "@microsoft/live-share";
import { LiveMediaSession } from "@microsoft/live-share-media";
import { LiveShareHost } from "@microsoft/teams-js";
// Setup the Fluid container
const host = LiveShareHost.create();
const liveShare = new LiveShareClient(host);
const schema = {
initialObjects: { mediaSession: LiveMediaSession },
};
const { container } = await liveShare.joinContainer(schema);
const { mediaSession } = container.initialObjects;
// Get the player from your document and create synchronizer
const player = document.getElementById("player");
const synchronizer = mediaSession.synchronize(player);
// Listen for groupaction events (optional)
synchronizer.addEventListener(MediaPlayerSynchronizerEvents.groupaction, async (evt) => {
// See which user made the change (e.g., to display a notification)
const clientInfo = await synchronizer.mediaSession.getClientInfo(evt.details.clientId);
});
// Define roles you want to allow playback control and start sync
const allowedRoles = [UserMeetingRole.organizer, UserMeetingRole.presenter];
await mediaSession.initialize(allowedRoles);
import {
LiveShareClient,
UserMeetingRole,
MediaPlayerSynchronizerEvents,
} from "@microsoft/live-share";
import { LiveMediaSession, IMediaPlayer, MediaPlayerSynchronizer } from "@microsoft/live-share-media";
import { LiveShareHost } from "@microsoft/teams-js";
import { ContainerSchema } from "fluid-framework";
// Join the Fluid container
const host = LiveShareHost.create();
const liveShare = new LiveShareClient(host);
const schema: ContainerSchema = {
initialObjects: { mediaSession: LiveMediaSession },
};
const { container } = await liveShare.joinContainer(schema);
// Force casting is necessary because Fluid does not maintain type recognition for `container.initialObjects`.
// Casting here is always safe, as the `initialObjects` is constructed based on the schema you provide to `.joinContainer`.
const mediaSession = container.initialObjects.mediaSession as unknown as LiveMediaSession;
// Get the player from your document and create synchronizer
const player: IMediaPlayer = document.getElementById("player") as HTMLVideoElement;
const synchronizer: MediaPlayerSynchronizer = mediaSession.synchronize(player);
// Listen for groupaction events (optional)
synchronizer.addEventListener(MediaPlayerSynchronizerEvents.groupaction, async (evt) => {
// See which user made the change (e.g., to display a notification)
const clientInfo = await synchronizer.mediaSession.getClientInfo(evt.details.clientId);
});
// Define roles you want to allow playback control and start sync
const allowedRoles: UserMeetingRole[] = [UserMeetingRole.organizer, UserMeetingRole.presenter];
await mediaSession.initialize(allowedRoles);
import { useMediaSynchronizer } from "@microsoft/live-share-react";
import { UserMeetingRole } from "@microsoft/live-share";
import { MediaPlayerSynchronizerEvents } from "@microsoft/live-share-media";
import { useRef, useEffect } from "react";
const ALLOWED_ROLES = [UserMeetingRole.organizer, UserMeetingRole.presenter];
const INITIAL_TRACK = "<YOUR_VIDEO_URL>";
// Define a unique key that distinguishes this `useMediaSynchronizer` from others in your app
const UNIQUE_KEY = "MEDIA-SESSION-ID";
export function VideoPlayer() {
const videoRef = useRef(null);
const { play, pause, seekTo, mediaSynchronizer } = useMediaSynchronizer(
UNIQUE_KEY,
videoRef,
INITIAL_TRACK,
ALLOWED_ROLES
);
// Listen for groupaction events (optional)
useEffect(() => {
// Listen for player group actions for errors (e.g., play error)
const onGroupAction = (evt: IMediaPlayerSynchronizerEvent) => {
// See which user made the change (e.g., to display a notification)
const clientInfo = await synchronizer.mediaSession.getClientInfo(evt.details.clientId);
};
mediaSynchronizer?.addEventListener(
MediaPlayerSynchronizerEvents.groupaction,
onGroupAction
);
return () => {
mediaSynchronizer?.removeEventListener(
MediaPlayerSynchronizerEvents.groupaction,
onGroupAction
);
};
}, [mediaSynchronizer]);
return (
<div>
<video ref={videoRef} />
<button onClick={play}>
Play
</button>
<button onClick={pause}>
Pause
</button>
<button onClick={() => {
seekTo(0);
}}>
Start over
</button>
</div>
);
}
// ...
document.getElementById("play-button").onclick = async () => {
// Plays for all users in the session.
// If using role verification, this throws an error if the user doesn't have the required role.
await synchronizer.play();
};
document.getElementById("pause-button").onclick = async () => {
// Pauses for all users in the session.
// If using role verification, this throws an error if the user doesn't have the required role.
await synchronizer.pause();
};
document.getElementById("restart-button").onclick = async () => {
// Seeks for all users in the session.
// If using role verification, this throws an error if the user doesn't have the required role.
await synchronizer.seekTo(0);
};
document.getElementById("change-track-button").onclick = () => {
// Changes the track for all users in the session.
// If using role verification, this throws an error if the user doesn't have the required role.
synchronizer.setTrack({
trackIdentifier: "SOME_OTHER_VIDEO_SRC",
});
};
注意
虽然可以使用 LiveMediaSession 对象手动同步媒体,但通常建议使用 MediaPlayerSynchronizer。 根据你在应用中使用的播放器,你可能需要创建一个委托填充码,以使 Web 播放器的界面与 IMediaPlayer 界面匹配。
// Suspend the media session coordinator
const suspension = mediaSession.coordinator.beginSuspension();
// End the suspension when ready
suspension.end();
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();
import { useMediaSynchronizer } from "@microsoft/live-share-react";
import { useRef } from "react";
// Define a unique key that distinguishes this `useMediaSynchronizer` from others in your app
const UNIQUE_KEY = "MEDIA-SESSION-ID";
// Example component
export function VideoPlayer() {
const videoRef = useRef(null);
const { suspended, beginSuspension, endSuspension } = useMediaSynchronizer(
UNIQUE_KEY,
videoRef,
"<YOUR_INITIAL_VIDEO_URL>",
);
return (
<div>
<video ref={videoRef} />
{!suspended && (
<button onClick={() => {
beginSuspension();
}}>
Stop following
</button>
)}
{suspended && (
<button onClick={() => {
endSuspension();
}}>
Sync to presenter
</button>
)}
</div>
);
}
// Suspend the media session coordinator
const waitPoint = {
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 resumes when everyone ends suspension
suspension.end();
};
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 resumes when everyone ends suspension
suspension.end();
};
import { useMediaSynchronizer } from "@microsoft/live-share-react";
import { useRef } from "react";
// Define a unique key that distinguishes this `useMediaSynchronizer` from others in your app
const UNIQUE_KEY = "MEDIA-SESSION-ID";
// Example component
export function VideoPlayer() {
const videoRef = useRef(null);
const { suspended, beginSuspension, endSuspension } = useMediaSynchronizer(
UNIQUE_KEY,
videoRef,
"<YOUR_INITIAL_VIDEO_URL>",
);
return (
<div>
<video ref={videoRef} />
{!suspended && (
<button onClick={() => {
const waitPoint = {
position: 0,
reason: "ReadyUp", // Optional.
};
beginSuspension(waitPoint);
}}>
Wait until ready
</button>
)}
{suspended && (
<button onClick={() => {
endSuspension();
}}>
Ready up
</button>
)}
</div>
);
}
音频闪避
Live Share SDK 支持智能音频闪避。 通过在代码中添加以下内容,可以在应用程序中使用此功能:
import { meeting } from "@microsoft/teams-js";
// ... set up MediaPlayerSynchronizer
// Register speaking state change handler through Microsoft Teams JavaScript client library (TeamsJS)
let volumeTimer;
meeting.registerSpeakingStateChangeHandler((speakingState) => {
if (speakingState.isSpeakingDetected && !volumeTimer) {
// If someone in the meeting starts speaking, periodically
// lower the volume using your MediaPlayerSynchronizer's
// VolumeLimiter.
synchronizer.volumeLimiter?.lowerVolume();
volumeTimer = setInterval(() => {
synchronizer.volumeLimiter?.lowerVolume();
}, 250);
} else if (volumeTimer) {
// If everyone in the meeting stops speaking and the
// interval timer is active, clear the interval.
clearInterval(volumeTimer);
volumeTimer = undefined;
}
});
import { meeting } from "@microsoft/teams-js";
// ... set up MediaPlayerSynchronizer
// Register speaking state change handler through TeamsJS library
let volumeTimer: NodeJS.Timeout | undefined;
meeting.registerSpeakingStateChangeHandler((speakingState: 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;
}
});
import { useMediaSynchronizer } from "@microsoft/live-share-react";
import { meeting } from "@microsoft/teams-js";
import { useRef, useEffect } from "react";
// Define a unique key that distinguishes this `useMediaSynchronizer` from others in your app
const UNIQUE_KEY = "MEDIA-SESSION-ID";
// Example component
export function VideoPlayer() {
const videoRef = useRef(null);
const { synchronizer } = useMediaSynchronizer(
UNIQUE_KEY,
videoRef,
"<YOUR_INITIAL_VIDEO_URL>",
);
const enableSmartSound = () => {
let volumeTimer;
meeting.registerSpeakingStateChangeHandler((speakingState) => {
if (speakingState.isSpeakingDetected && !volumeTimer) {
// If someone in the meeting starts speaking, periodically
// lower the volume using your MediaPlayerSynchronizer's
// VolumeLimiter.
synchronizer.volumeLimiter?.lowerVolume();
volumeTimer = setInterval(() => {
synchronizer.volumeLimiter?.lowerVolume();
}, 250);
} else if (volumeTimer) {
// If everyone in the meeting stops speaking and the
// interval timer is active, clear the interval.
clearInterval(volumeTimer);
volumeTimer = undefined;
}
});
}
return (
<div>
<video ref={videoRef} />
<button onClick={enableSmartSound}>
Enable smart sound
</button>
</div>
);
}