feat: stream private office screens
This commit is contained in:
@@ -60,11 +60,22 @@ export interface PresenterStart {
|
||||
stream: MediaStream;
|
||||
video: HTMLVideoElement;
|
||||
}
|
||||
export interface ViewerStart {
|
||||
/** A current `authorizeMediaSurface` decision with explicit viewer opt-in. */
|
||||
decision: MediaAuthorizationDecision;
|
||||
video: HTMLVideoElement;
|
||||
}
|
||||
export type ViewerStart =
|
||||
| {
|
||||
/** A current `authorizeMediaSurface` decision with explicit viewer opt-in. */
|
||||
decision: MediaAuthorizationDecision;
|
||||
viewerOptIn?: never;
|
||||
video: HTMLVideoElement;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* Literal user action for signaling servers that perform authorization
|
||||
* during join and intentionally expose no source locator to the client.
|
||||
*/
|
||||
decision?: never;
|
||||
viewerOptIn: true;
|
||||
video: HTMLVideoElement;
|
||||
};
|
||||
export interface RemoteOfficeMedia {
|
||||
startPresenter(input: PresenterStart): Promise<void>;
|
||||
startViewer(input: ViewerStart): Promise<void>;
|
||||
@@ -310,6 +321,10 @@ export function createRemoteOfficeMedia(options: RemoteOfficeMediaOptions): Remo
|
||||
cursor = advanced.value;
|
||||
if (message.type === "screen-share-stopped") {
|
||||
closeTransport();
|
||||
// The server has already destroyed the signaling session. Forget the
|
||||
// capability now so caller cleanup cannot issue a redundant leave that
|
||||
// is guaranteed to fail authorization.
|
||||
grant = null;
|
||||
setStatus("stopped");
|
||||
return;
|
||||
}
|
||||
@@ -412,7 +427,10 @@ export function createRemoteOfficeMedia(options: RemoteOfficeMediaOptions): Remo
|
||||
if (!message.continuous) resetPeerMesh();
|
||||
await reconcile(message.participants);
|
||||
scheduleRenewal();
|
||||
setStatus("connecting");
|
||||
// A presenter with no viewers is still live: the hosted signaling session
|
||||
// is accepting authorized viewers. A viewer remains connecting until its
|
||||
// receive-only peer actually reaches the connected state.
|
||||
setStatus(options.role === "presenter" && peers.size === 0 ? "live" : "connecting");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -492,6 +510,7 @@ export function createRemoteOfficeMedia(options: RemoteOfficeMediaOptions): Remo
|
||||
cursor = { sessionId: response.grant.credential.sessionId, sequence: response.sequence, timestampMs: response.timestampMs };
|
||||
await reconcile(response.participants);
|
||||
scheduleRenewal();
|
||||
if (role === "presenter" && peers.size === 0) setStatus("live");
|
||||
void startEvents().catch((reason) => { options.onError?.(asError(reason, "remote media events failed")); scheduleReconnect(); });
|
||||
}
|
||||
|
||||
@@ -525,9 +544,12 @@ export function createRemoteOfficeMedia(options: RemoteOfficeMediaOptions): Remo
|
||||
async function startViewer(input: ViewerStart): Promise<void> {
|
||||
if (options.role !== "viewer") throw new Error("remote media: adapter is not a viewer");
|
||||
if (status === "disposed" || status === "revoked") throw new Error("remote media: session is terminal");
|
||||
if (!input.decision.authorized || !input.decision.optedIn || !input.decision.canView ||
|
||||
input.decision.surface.screenId !== binding.screenId || input.decision.surface.officeId !== binding.officeId ||
|
||||
input.decision.surface.source?.kind !== "live-stream") {
|
||||
const ready = input.decision === undefined
|
||||
? input.viewerOptIn === true
|
||||
: input.decision.authorized && input.decision.optedIn && input.decision.canView &&
|
||||
input.decision.surface.screenId === binding.screenId && input.decision.surface.officeId === binding.officeId &&
|
||||
input.decision.surface.source?.kind === "live-stream";
|
||||
if (!ready) {
|
||||
throw new Error("remote media: a ready live-stream authorization decision is required");
|
||||
}
|
||||
input.video.autoplay = false;
|
||||
|
||||
Reference in New Issue
Block a user