1
0

feat: add private profile media and realtime contracts

This commit is contained in:
2026-08-11 19:21:49 -07:00
parent a2a52bfdae
commit 1c37ef8f3e
22 changed files with 2321 additions and 8 deletions
+62
View File
@@ -0,0 +1,62 @@
import type { VideoTexture } from "three";
import type { MediaAuthorizationDecision } from "./types.ts";
export interface MediaSurfaceBinding {
/** All objects are caller-owned. This adapter never obtains or fetches them. */
video: HTMLVideoElement;
texture: VideoTexture;
track?: MediaStreamTrack;
}
export interface MediaSurfaceLifecycle {
bind(binding: MediaSurfaceBinding): void;
apply(decision: MediaAuthorizationDecision): void;
texture(): VideoTexture | null;
optedIn(): boolean;
dispose(): void;
}
/**
* Gate an already-created video texture behind a server decision and explicit
* viewer consent. `play`, `getUserMedia`, `fetch`, `track.stop`, and
* `texture.dispose` are intentionally absent: acquisition and ownership remain
* with the caller.
*/
export function createMediaSurfaceLifecycle(): MediaSurfaceLifecycle {
let binding: MediaSurfaceBinding | null = null;
let allowed = false;
let consent = false;
let disposed = false;
function silence(video: HTMLVideoElement): void {
video.autoplay = false;
video.muted = true;
video.pause();
}
return {
bind(next) {
if (disposed) throw new Error("media surface lifecycle is disposed");
if (binding) silence(binding.video);
binding = next;
silence(next.video);
},
apply(decision) {
if (disposed) return;
allowed = decision.canView && decision.surface.source !== null;
consent = decision.optedIn;
if (!allowed && binding) silence(binding.video);
},
texture: () => (!disposed && allowed && consent ? binding?.texture ?? null : null),
optedIn: () => consent,
dispose() {
if (disposed) return;
disposed = true;
if (binding) silence(binding.video);
binding = null;
allowed = false;
consent = false;
},
};
}