openrtc 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/{core/types.d.ts → DelegatingRuntimeAdapter-CW8d78dZ.d.ts} +275 -95
- package/dist/IpcRuntimeAdapter-CT-W0Mtp.d.ts +24 -0
- package/dist/auth/index.d.ts +75 -1
- package/dist/auth/index.js +1 -1
- package/dist/auth/internal.d.ts +3 -1
- package/dist/auth/internal.js +1 -1
- package/dist/chunk-4MH42GUI.js +2 -0
- package/dist/chunk-CQAKCS2S.js +4 -0
- package/dist/chunk-D54VGRRH.js +1 -0
- package/dist/chunk-DIQYFAGL.js +2 -0
- package/dist/chunk-PZ25OCVG.js +2 -0
- package/dist/chunk-UVASANPG.js +1 -0
- package/dist/factories-0TxDPMeu.d.ts +1041 -0
- package/dist/index.d.ts +160 -3
- package/dist/index.js +1 -3
- package/dist/openrtc_bg.wasm +0 -0
- package/dist/runtime/WasmRuntimeAdapter.d.ts +5 -3
- package/dist/runtime/WasmRuntimeAdapter.js +1 -11
- package/dist/runtime/index.d.ts +796 -6
- package/dist/runtime/index.js +1 -5
- package/dist/runtime/tauri.d.ts +12 -8
- package/dist/runtime/tauri.js +1 -22
- package/package.json +11 -7
- package/wasm/openrtc.d.ts +61 -25
- package/wasm/openrtc.js +417 -259
- package/wasm/openrtc_bg.wasm +0 -0
- package/wasm/openrtc_bg.wasm.d.ts +27 -24
- package/wasm/package.json +1 -1
- package/dist/OpenRTC.d.ts +0 -71
- package/dist/OpenRTC.js +0 -130
- package/dist/api/MediaTransport.d.ts +0 -7
- package/dist/api/MediaTransport.js +0 -262
- package/dist/api/PlutoPeerConnection.d.ts +0 -50
- package/dist/api/PlutoPeerConnection.js +0 -434
- package/dist/api/PlutoWebSocket.d.ts +0 -24
- package/dist/api/PlutoWebSocket.js +0 -113
- package/dist/api/PlutoWebTransport.d.ts +0 -29
- package/dist/api/PlutoWebTransport.js +0 -105
- package/dist/auth/host.d.ts +0 -73
- package/dist/auth/host.js +0 -513
- package/dist/compat/index.d.ts +0 -20
- package/dist/compat/index.js +0 -20
- package/dist/core/Client.d.ts +0 -352
- package/dist/core/Client.js +0 -3689
- package/dist/core/Connection.d.ts +0 -67
- package/dist/core/Connection.js +0 -404
- package/dist/core/IEngineBridge.d.ts +0 -18
- package/dist/core/IEngineBridge.js +0 -1
- package/dist/core/ISignalingTransport.d.ts +0 -13
- package/dist/core/ISignalingTransport.js +0 -1
- package/dist/core/PeerLifecycleManager.d.ts +0 -43
- package/dist/core/PeerLifecycleManager.js +0 -330
- package/dist/core/PeerProjectionStore.d.ts +0 -43
- package/dist/core/PeerProjectionStore.js +0 -327
- package/dist/core/PlutoRTC.d.ts +0 -13
- package/dist/core/PlutoRTC.js +0 -26
- package/dist/core/Room.d.ts +0 -47
- package/dist/core/Room.js +0 -376
- package/dist/core/Signaling.d.ts +0 -39
- package/dist/core/Signaling.js +0 -147
- package/dist/core/bridges/BridgeCodecs.d.ts +0 -19
- package/dist/core/bridges/BridgeCodecs.js +0 -317
- package/dist/core/bridges/BrowserFirestoreRooms.d.ts +0 -36
- package/dist/core/bridges/BrowserFirestoreRooms.js +0 -169
- package/dist/core/bridges/BrowserFirestoreSignaling.d.ts +0 -111
- package/dist/core/bridges/BrowserFirestoreSignaling.js +0 -1010
- package/dist/core/bridges/NativeIpcBridge.d.ts +0 -156
- package/dist/core/bridges/NativeIpcBridge.js +0 -1472
- package/dist/core/bridges/RtdbPresence.d.ts +0 -122
- package/dist/core/bridges/RtdbPresence.js +0 -201
- package/dist/core/bridges/TauriBridge.d.ts +0 -8
- package/dist/core/bridges/TauriBridge.js +0 -7
- package/dist/core/bridges/WasmBridge.d.ts +0 -233
- package/dist/core/bridges/WasmBridge.js +0 -1811
- package/dist/core/config.d.ts +0 -31
- package/dist/core/config.js +0 -138
- package/dist/core/transports/ITransport.d.ts +0 -12
- package/dist/core/transports/ITransport.js +0 -1
- package/dist/core/transports/LocalDaemonBridge.d.ts +0 -41
- package/dist/core/transports/LocalDaemonBridge.js +0 -227
- package/dist/core/transports/TauriSignaling.d.ts +0 -28
- package/dist/core/transports/TauriSignaling.js +0 -373
- package/dist/core/transports/TransportFactory.d.ts +0 -9
- package/dist/core/transports/TransportFactory.js +0 -59
- package/dist/core/transports/WasmSignaling.d.ts +0 -78
- package/dist/core/transports/WasmSignaling.js +0 -522
- package/dist/core/transports/WebRTCTransport.d.ts +0 -23
- package/dist/core/transports/WebRTCTransport.js +0 -132
- package/dist/core/trust.d.ts +0 -23
- package/dist/core/trust.js +0 -99
- package/dist/core/types.js +0 -19
- package/dist/moq/MoQMediaTransport.d.ts +0 -6
- package/dist/moq/MoQMediaTransport.js +0 -106
- package/dist/moq/MoQPublisher.d.ts +0 -22
- package/dist/moq/MoQPublisher.js +0 -179
- package/dist/moq/MoQSession.d.ts +0 -35
- package/dist/moq/MoQSession.js +0 -166
- package/dist/moq/MoQSubscriber.d.ts +0 -19
- package/dist/moq/MoQSubscriber.js +0 -235
- package/dist/moq/common.d.ts +0 -47
- package/dist/moq/common.js +0 -304
- package/dist/moq/messages.d.ts +0 -105
- package/dist/moq/messages.js +0 -367
- package/dist/presets/plutoHosted.d.ts +0 -13
- package/dist/presets/plutoHosted.js +0 -18
- package/dist/runtime/DelegatingRuntimeAdapter.d.ts +0 -68
- package/dist/runtime/DelegatingRuntimeAdapter.js +0 -250
- package/dist/runtime/IpcRuntimeAdapter.d.ts +0 -9
- package/dist/runtime/IpcRuntimeAdapter.js +0 -13
- package/dist/runtime/RuntimeClient.d.ts +0 -257
- package/dist/runtime/RuntimeClient.js +0 -1484
- package/dist/runtime/TauriRuntimeAdapter.d.ts +0 -1
- package/dist/runtime/TauriRuntimeAdapter.js +0 -1
- package/dist/runtime/factories.d.ts +0 -24
- package/dist/runtime/factories.js +0 -66
- package/dist/runtime/ipc.d.ts +0 -8
- package/dist/runtime/ipc.js +0 -65
- package/dist/runtime/ipcContract.d.ts +0 -46
- package/dist/runtime/ipcContract.js +0 -1
- package/dist/runtime/types.d.ts +0 -110
- package/dist/runtime/types.js +0 -1
- package/dist/transports/TauriTransport.d.ts +0 -17
- package/dist/transports/TauriTransport.js +0 -49
- package/dist/transports/WebTransport.d.ts +0 -23
- package/dist/transports/WebTransport.js +0 -71
- package/dist/utils/debug.d.ts +0 -4
- package/dist/utils/debug.js +0 -33
- package/dist/utils/runtime.d.ts +0 -1
- package/dist/utils/runtime.js +0 -10
package/dist/core/trust.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export declare const TRANSPORT_TRUST_VERSION = 1;
|
|
2
|
-
export interface TrustedDeviceMetadataRecord {
|
|
3
|
-
version: number;
|
|
4
|
-
identityPublicKey: string;
|
|
5
|
-
identityFingerprint: string;
|
|
6
|
-
}
|
|
7
|
-
export interface LocalTrustedIdentityRecord {
|
|
8
|
-
publicKey: Uint8Array;
|
|
9
|
-
secretKey: Uint8Array;
|
|
10
|
-
fingerprint: string;
|
|
11
|
-
}
|
|
12
|
-
export interface TransportTrustPayload {
|
|
13
|
-
version: number;
|
|
14
|
-
deviceId: string;
|
|
15
|
-
identityFingerprint: string;
|
|
16
|
-
challenge?: string;
|
|
17
|
-
signedChallenge?: string;
|
|
18
|
-
}
|
|
19
|
-
export declare function createTransportTrustChallenge(byteLength?: number): string;
|
|
20
|
-
export declare function signTransportTrustChallenge(challenge: string, identitySecretKey: Uint8Array): string;
|
|
21
|
-
export declare function verifyTransportTrustChallenge(challenge: string, signedChallenge: string, identityPublicKey: string): boolean;
|
|
22
|
-
export declare function parseTrustedDeviceMetadata(metadata?: string | null): TrustedDeviceMetadataRecord | null;
|
|
23
|
-
export declare function getLocalTrustedIdentity(): Promise<LocalTrustedIdentityRecord | null>;
|
package/dist/core/trust.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import * as nacl from 'tweetnacl';
|
|
2
|
-
const TRUSTED_DEVICE_METADATA_KEY = 'trustedDevice';
|
|
3
|
-
const TRUSTED_IDENTITY_STORAGE_KEY = 'plutonium:e2ee:identity:v1';
|
|
4
|
-
export const TRANSPORT_TRUST_VERSION = 1;
|
|
5
|
-
function decodeBase64(value) {
|
|
6
|
-
const binary = atob(value);
|
|
7
|
-
const bytes = new Uint8Array(binary.length);
|
|
8
|
-
for (let index = 0; index < binary.length; index += 1) {
|
|
9
|
-
bytes[index] = binary.charCodeAt(index);
|
|
10
|
-
}
|
|
11
|
-
return bytes;
|
|
12
|
-
}
|
|
13
|
-
function encodeBase64(value) {
|
|
14
|
-
let binary = '';
|
|
15
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
16
|
-
binary += String.fromCharCode(value[index]);
|
|
17
|
-
}
|
|
18
|
-
return btoa(binary);
|
|
19
|
-
}
|
|
20
|
-
async function computeFingerprint(publicKey) {
|
|
21
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', publicKey);
|
|
22
|
-
const hashBytes = new Uint8Array(hashBuffer);
|
|
23
|
-
return Array.from(hashBytes)
|
|
24
|
-
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
25
|
-
.join('');
|
|
26
|
-
}
|
|
27
|
-
export function createTransportTrustChallenge(byteLength = 32) {
|
|
28
|
-
const challenge = crypto.getRandomValues(new Uint8Array(byteLength));
|
|
29
|
-
return encodeBase64(challenge);
|
|
30
|
-
}
|
|
31
|
-
export function signTransportTrustChallenge(challenge, identitySecretKey) {
|
|
32
|
-
const signature = nacl.sign.detached(decodeBase64(challenge), identitySecretKey);
|
|
33
|
-
return encodeBase64(signature);
|
|
34
|
-
}
|
|
35
|
-
export function verifyTransportTrustChallenge(challenge, signedChallenge, identityPublicKey) {
|
|
36
|
-
try {
|
|
37
|
-
return nacl.sign.detached.verify(decodeBase64(challenge), decodeBase64(signedChallenge), decodeBase64(identityPublicKey));
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
export function parseTrustedDeviceMetadata(metadata) {
|
|
44
|
-
if (!metadata || !metadata.trim()) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
try {
|
|
48
|
-
const parsed = JSON.parse(metadata);
|
|
49
|
-
const record = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
50
|
-
? parsed[TRUSTED_DEVICE_METADATA_KEY]
|
|
51
|
-
: null;
|
|
52
|
-
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
const normalized = record;
|
|
56
|
-
const version = typeof normalized.version === 'number' ? normalized.version : null;
|
|
57
|
-
const identityPublicKey = typeof normalized.identityPublicKey === 'string' ? normalized.identityPublicKey : null;
|
|
58
|
-
const identityFingerprint = typeof normalized.identityFingerprint === 'string'
|
|
59
|
-
? normalized.identityFingerprint
|
|
60
|
-
: null;
|
|
61
|
-
if (!version || !identityPublicKey || !identityFingerprint) {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
version,
|
|
66
|
-
identityPublicKey,
|
|
67
|
-
identityFingerprint,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
export async function getLocalTrustedIdentity() {
|
|
75
|
-
if (typeof localStorage === 'undefined') {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
const raw = localStorage.getItem(TRUSTED_IDENTITY_STORAGE_KEY);
|
|
79
|
-
if (!raw) {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
try {
|
|
83
|
-
const parsed = JSON.parse(raw);
|
|
84
|
-
if (typeof parsed.publicKey !== 'string' || typeof parsed.secretKey !== 'string') {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
const publicKey = decodeBase64(parsed.publicKey);
|
|
88
|
-
const secretKey = decodeBase64(parsed.secretKey);
|
|
89
|
-
const fingerprint = await computeFingerprint(publicKey);
|
|
90
|
-
return {
|
|
91
|
-
publicKey,
|
|
92
|
-
secretKey,
|
|
93
|
-
fingerprint,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
package/dist/core/types.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// --- Billing / Plan Types ---
|
|
2
|
-
export const DEFAULT_FREE_LIMITS = {
|
|
3
|
-
devicesPerUser: 2,
|
|
4
|
-
maxRooms: 10,
|
|
5
|
-
maxMembersPerRoom: 5,
|
|
6
|
-
maxPersonalDevices: 50, // free: 10 rooms * 5 members = 50
|
|
7
|
-
};
|
|
8
|
-
// --- Constants ---
|
|
9
|
-
export const DEFAULT_MOQ_RELAY_URL = 'https://relay.cloudflare.mediaoverquic.com';
|
|
10
|
-
export const DEFAULT_ICE_SERVERS = [
|
|
11
|
-
{ urls: 'stun:stun.l.google.com:19302' },
|
|
12
|
-
{ urls: 'stun:stun.cloudflare.com:3478' }
|
|
13
|
-
];
|
|
14
|
-
export const DEFAULT_WEBRTC_CONFIG = {
|
|
15
|
-
iceServers: DEFAULT_ICE_SERVERS
|
|
16
|
-
};
|
|
17
|
-
export const DEFAULT_MOQ_CONFIG = {
|
|
18
|
-
relayUrl: DEFAULT_MOQ_RELAY_URL
|
|
19
|
-
};
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
export class MoQMediaTransport {
|
|
2
|
-
constructor(publisher) {
|
|
3
|
-
this.publisher = publisher;
|
|
4
|
-
}
|
|
5
|
-
async sendTrack(track, namespace, name) {
|
|
6
|
-
// Create publisher track
|
|
7
|
-
const trackPub = await this.publisher.createTrack(namespace, name);
|
|
8
|
-
if (!trackPub) {
|
|
9
|
-
console.log(`[MoQ] Track ${namespace}/${name} already being published. Skipping.`);
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
if ((typeof VideoEncoder === 'undefined') && (typeof AudioEncoder === 'undefined')) {
|
|
13
|
-
console.error("WebCodecs API not supported.");
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
const processor = new window.MediaStreamTrackProcessor({ track });
|
|
17
|
-
const reader = processor.readable.getReader();
|
|
18
|
-
let encoder = null;
|
|
19
|
-
if (track.kind === 'video') {
|
|
20
|
-
encoder = new window.VideoEncoder({
|
|
21
|
-
output: (chunk, metadata) => {
|
|
22
|
-
// Framing: [timestamp 8B big-endian][encoded data]
|
|
23
|
-
const buffer = new ArrayBuffer(8 + chunk.byteLength);
|
|
24
|
-
const view = new DataView(buffer);
|
|
25
|
-
view.setBigInt64(0, BigInt(chunk.timestamp), false);
|
|
26
|
-
const data = new Uint8Array(buffer);
|
|
27
|
-
chunk.copyTo(new Uint8Array(buffer, 8));
|
|
28
|
-
trackPub.push(data, { isKey: chunk.type === 'key', timestamp: chunk.timestamp });
|
|
29
|
-
},
|
|
30
|
-
error: (e) => console.error("MoQ VideoEncoder error", e)
|
|
31
|
-
});
|
|
32
|
-
encoder.configure({
|
|
33
|
-
codec: 'vp09.00.10.08',
|
|
34
|
-
width: 640,
|
|
35
|
-
height: 480,
|
|
36
|
-
bitrate: 500000,
|
|
37
|
-
framerate: 24,
|
|
38
|
-
latencyMode: 'realtime',
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
// Audio
|
|
43
|
-
let lastAudioKeyTimestamp = null;
|
|
44
|
-
const AUDIO_GROUP_INTERVAL_US = 100000; // ~100ms (matches reference moq-dev)
|
|
45
|
-
encoder = new window.AudioEncoder({
|
|
46
|
-
output: (chunk, metadata) => {
|
|
47
|
-
// Determine if this audio chunk starts a new group
|
|
48
|
-
let isKey = false;
|
|
49
|
-
if (lastAudioKeyTimestamp === null ||
|
|
50
|
-
chunk.timestamp - lastAudioKeyTimestamp >= AUDIO_GROUP_INTERVAL_US) {
|
|
51
|
-
isKey = true;
|
|
52
|
-
lastAudioKeyTimestamp = chunk.timestamp;
|
|
53
|
-
}
|
|
54
|
-
// Framing: [timestamp 8B big-endian][encoded data]
|
|
55
|
-
const buffer = new ArrayBuffer(8 + chunk.byteLength);
|
|
56
|
-
const view = new DataView(buffer);
|
|
57
|
-
view.setBigInt64(0, BigInt(chunk.timestamp), false);
|
|
58
|
-
const data = new Uint8Array(buffer);
|
|
59
|
-
chunk.copyTo(new Uint8Array(buffer, 8));
|
|
60
|
-
trackPub.push(data, { isKey, timestamp: chunk.timestamp });
|
|
61
|
-
},
|
|
62
|
-
error: (e) => console.error("MoQ AudioEncoder error", e)
|
|
63
|
-
});
|
|
64
|
-
encoder.configure({
|
|
65
|
-
codec: 'opus',
|
|
66
|
-
sampleRate: 48000,
|
|
67
|
-
numberOfChannels: 1
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
// Read Loop
|
|
71
|
-
(async () => {
|
|
72
|
-
let frameCount = 0;
|
|
73
|
-
try {
|
|
74
|
-
while (true) {
|
|
75
|
-
const { done, value } = await reader.read();
|
|
76
|
-
if (done)
|
|
77
|
-
break;
|
|
78
|
-
if (value) {
|
|
79
|
-
try {
|
|
80
|
-
if (encoder && encoder.state !== 'closed') {
|
|
81
|
-
// For video: force a key frame every ~2 seconds (48 frames at 24fps)
|
|
82
|
-
if (track.kind === 'video' && frameCount % 48 === 0) {
|
|
83
|
-
encoder.encode(value, { keyFrame: true });
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
encoder.encode(value);
|
|
87
|
-
}
|
|
88
|
-
frameCount++;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
finally {
|
|
92
|
-
value.close();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
catch (e) {
|
|
98
|
-
console.error(`Read error`, e);
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
if (trackPub && trackPub.close)
|
|
102
|
-
trackPub.close();
|
|
103
|
-
}
|
|
104
|
-
})();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { MoQSession } from './MoQSession';
|
|
2
|
-
export declare class MoQPublisher {
|
|
3
|
-
private session;
|
|
4
|
-
private tracks;
|
|
5
|
-
private activePublishers;
|
|
6
|
-
constructor(session: MoQSession);
|
|
7
|
-
private pendingAnnounces;
|
|
8
|
-
announce(namespace: string): Promise<void>;
|
|
9
|
-
onAnnounceResult(namespace: string, error?: {
|
|
10
|
-
code: number;
|
|
11
|
-
reason: string;
|
|
12
|
-
}): void;
|
|
13
|
-
onSubscribe(msg: any): void;
|
|
14
|
-
onUnsubscribe(subscribeId: number): void;
|
|
15
|
-
createTrack(namespace: string, name: string): Promise<{
|
|
16
|
-
close: () => void;
|
|
17
|
-
push: (data: Uint8Array, init: {
|
|
18
|
-
isKey: boolean;
|
|
19
|
-
timestamp: number;
|
|
20
|
-
}) => Promise<void>;
|
|
21
|
-
} | null>;
|
|
22
|
-
}
|
package/dist/moq/MoQPublisher.js
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { BufferWriter, Mutex } from './common';
|
|
2
|
-
export class MoQPublisher {
|
|
3
|
-
constructor(session) {
|
|
4
|
-
this.tracks = new Map();
|
|
5
|
-
this.activePublishers = new Map();
|
|
6
|
-
this.pendingAnnounces = new Map();
|
|
7
|
-
this.session = session;
|
|
8
|
-
}
|
|
9
|
-
async announce(namespace) {
|
|
10
|
-
// Send ANNOUNCE (MsgType 0x6)
|
|
11
|
-
console.log(`[MoQ] Announcing namespace: ${namespace}`);
|
|
12
|
-
const msg = {
|
|
13
|
-
type: 0x6, // ANNOUNCE
|
|
14
|
-
trackNamespace: namespace,
|
|
15
|
-
params: {} // Draft-07: Announce Params
|
|
16
|
-
};
|
|
17
|
-
return new Promise(async (resolve, reject) => {
|
|
18
|
-
this.pendingAnnounces.set(namespace, { resolve, reject });
|
|
19
|
-
// Set a timeout strictly for the announce response
|
|
20
|
-
const timeout = setTimeout(() => {
|
|
21
|
-
if (this.pendingAnnounces.has(namespace)) {
|
|
22
|
-
this.pendingAnnounces.delete(namespace);
|
|
23
|
-
reject(new Error("Announce timed out"));
|
|
24
|
-
}
|
|
25
|
-
}, 5000);
|
|
26
|
-
try {
|
|
27
|
-
await this.session.sendControlMessage(msg);
|
|
28
|
-
}
|
|
29
|
-
catch (e) {
|
|
30
|
-
clearTimeout(timeout);
|
|
31
|
-
this.pendingAnnounces.delete(namespace);
|
|
32
|
-
reject(e);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
onAnnounceResult(namespace, error) {
|
|
37
|
-
const pending = this.pendingAnnounces.get(namespace);
|
|
38
|
-
if (pending) {
|
|
39
|
-
this.pendingAnnounces.delete(namespace);
|
|
40
|
-
if (error && error.code !== 0) { // Code 0 is usually success/no-error in some contexts, but here we expect OK msg for success.
|
|
41
|
-
// Actually ANNOUNCE_ERROR is strictly error.
|
|
42
|
-
pending.reject(new Error(`Announce failed: code=${error.code} reason=${error.reason}`));
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
pending.resolve();
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
onSubscribe(msg) {
|
|
50
|
-
const trackId = `${msg.trackNamespace}/${msg.trackName}`;
|
|
51
|
-
console.log(`[MoQ] Publisher received SUBSCRIBE for ${trackId} -> subId=${msg.subscribeId} alias=${msg.trackAlias}`);
|
|
52
|
-
this.tracks.set(trackId, { subscribeId: msg.subscribeId, trackAlias: msg.trackAlias });
|
|
53
|
-
}
|
|
54
|
-
onUnsubscribe(subscribeId) {
|
|
55
|
-
for (const [key, val] of this.tracks.entries()) {
|
|
56
|
-
if (val.subscribeId === subscribeId) {
|
|
57
|
-
console.log(`[MoQ] Publisher received UNSUBSCRIBE for ${key} (subId=${subscribeId})`);
|
|
58
|
-
this.tracks.delete(key);
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
async createTrack(namespace, name) {
|
|
64
|
-
const trackId = `${namespace}/${name}`;
|
|
65
|
-
if (this.activePublishers.has(trackId)) {
|
|
66
|
-
// console.log(`[MoQ] Already publishing ${trackId} - skipping duplicate publisher.`);
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
// We return a pusher immediately. Data will be dropped inside push() if no subscriber exists.
|
|
70
|
-
// Log occasionally?
|
|
71
|
-
// console.log(`[MoQ] Push ${data.byteLength} bytes to ${trackId}`);
|
|
72
|
-
let currentGroupWriter = null;
|
|
73
|
-
let currentGroupId = 0;
|
|
74
|
-
let currentObjectId = 0;
|
|
75
|
-
const mutex = new Mutex(); // Serialize access to writer
|
|
76
|
-
const closeCurrentGroup = () => {
|
|
77
|
-
if (currentGroupWriter) {
|
|
78
|
-
const oldWriter = currentGroupWriter;
|
|
79
|
-
currentGroupWriter = null;
|
|
80
|
-
// Fire-and-forget: close old stream in background
|
|
81
|
-
// This prevents blocking audio push() during group transition
|
|
82
|
-
oldWriter.close().catch(() => { });
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
const cleanup = () => {
|
|
86
|
-
console.log(`[MoQ] Stopping publisher for ${trackId}`);
|
|
87
|
-
closeCurrentGroup();
|
|
88
|
-
this.activePublishers.delete(trackId);
|
|
89
|
-
};
|
|
90
|
-
const openNewGroup = async () => {
|
|
91
|
-
closeCurrentGroup();
|
|
92
|
-
currentGroupId++;
|
|
93
|
-
currentObjectId = 0;
|
|
94
|
-
// Resolve track info dynamically
|
|
95
|
-
const trackInfo = this.tracks.get(trackId);
|
|
96
|
-
if (trackInfo === undefined) {
|
|
97
|
-
// console.warn(`[MoQ] Dropping group for ${trackId} - No alias/subscription`);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
const { subscribeId, trackAlias: currentAlias } = trackInfo;
|
|
101
|
-
// Open UniStream
|
|
102
|
-
const stream = await this.session.transport.createUnidirectionalStream();
|
|
103
|
-
const writer = stream.getWriter();
|
|
104
|
-
if (currentGroupId <= 2) {
|
|
105
|
-
console.log(`[MoQ] Opening Group ${currentGroupId} for ${trackId} (subId=${subscribeId} alias=${currentAlias})`);
|
|
106
|
-
}
|
|
107
|
-
const w = new BufferWriter();
|
|
108
|
-
w.writeVarInt(0x04); // StreamType: STREAM_HEADER_SUBGROUP (0x4)
|
|
109
|
-
w.writeVarInt(subscribeId); // Subscribe ID (Draft-07)
|
|
110
|
-
w.writeVarInt(currentAlias); // Track Alias
|
|
111
|
-
w.writeVarInt(currentGroupId); // Group ID
|
|
112
|
-
w.writeVarInt(0); // Subgroup ID (0 for now)
|
|
113
|
-
w.writeUint8(0); // Publisher Priority (8-bit)
|
|
114
|
-
await writer.write(w.toUint8Array());
|
|
115
|
-
currentGroupWriter = writer;
|
|
116
|
-
};
|
|
117
|
-
const publisher = {
|
|
118
|
-
close: cleanup,
|
|
119
|
-
push: async (data, init) => {
|
|
120
|
-
await mutex.dispatch(async () => {
|
|
121
|
-
// Check if we have a valid alias (subscription active)
|
|
122
|
-
// If not, we MUST NOT send data (Relay will STOP_SENDING)
|
|
123
|
-
if (!this.tracks.has(trackId)) {
|
|
124
|
-
// We are not subscribed yet. Drop data.
|
|
125
|
-
// Ideally we buffer keyframes, but for now just drop to avoid crash.
|
|
126
|
-
// process.stdout.write('.'); // dropped
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
// Decision logic:
|
|
130
|
-
// 1. If isKey, we MUST start a new group (GOP boundary).
|
|
131
|
-
// 2. If !isKey but we have a writer, we continue current group.
|
|
132
|
-
// 3. If !isKey and NO writer, we MUST DROP the frame (cannot start group with delta).
|
|
133
|
-
if (init.isKey) {
|
|
134
|
-
await openNewGroup();
|
|
135
|
-
}
|
|
136
|
-
else if (!currentGroupWriter) {
|
|
137
|
-
// Orphan delta — drop it to enforce Group=Keyframe start
|
|
138
|
-
// This happens if group was closed (error/timeout) or joining mid-stream logic
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
// If openNewGroup failed (e.g. no alias), currentGroupWriter is null
|
|
142
|
-
if (!currentGroupWriter)
|
|
143
|
-
return;
|
|
144
|
-
// Write Object
|
|
145
|
-
// ObjectHeader:
|
|
146
|
-
// VarInt ObjectId
|
|
147
|
-
// VarInt Length
|
|
148
|
-
// Payload
|
|
149
|
-
const w = new BufferWriter();
|
|
150
|
-
w.writeVarInt(currentObjectId++);
|
|
151
|
-
w.writeVarInt(data.length);
|
|
152
|
-
w.writeBytes(data);
|
|
153
|
-
if (currentGroupWriter) {
|
|
154
|
-
try {
|
|
155
|
-
// @ts-ignore
|
|
156
|
-
await currentGroupWriter.write(w.toUint8Array());
|
|
157
|
-
}
|
|
158
|
-
catch (e) {
|
|
159
|
-
// Check for STOP_SENDING or similar WebTransport errors
|
|
160
|
-
const msg = e.message || "";
|
|
161
|
-
if (msg.includes("STOP_SENDING") || e.name === "WebTransportError") {
|
|
162
|
-
// STOP_SENDING on a Group Stream just means the subscriber dropped this specific group
|
|
163
|
-
// (e.g. because it was late or they skipped ahead).
|
|
164
|
-
// We should continue publishing the next group.
|
|
165
|
-
console.warn(`[MoQ] Group stream closed by remote (STOP_SENDING) for ${trackId}.`);
|
|
166
|
-
currentGroupWriter = null;
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
console.error(`[MoQ] Writer error for ${trackId}:`, e);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
this.activePublishers.set(trackId, publisher);
|
|
177
|
-
return publisher;
|
|
178
|
-
}
|
|
179
|
-
}
|
package/dist/moq/MoQSession.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { MoQPublisher } from './MoQPublisher';
|
|
2
|
-
import { MoQSubscriber } from './MoQSubscriber';
|
|
3
|
-
export interface TransportInterface {
|
|
4
|
-
createBidirectionalStream(): Promise<{
|
|
5
|
-
readable: ReadableStream<Uint8Array>;
|
|
6
|
-
writable: WritableStream<Uint8Array>;
|
|
7
|
-
}>;
|
|
8
|
-
createUnidirectionalStream(): Promise<WritableStream<Uint8Array>>;
|
|
9
|
-
incomingBidirectionalStreams: ReadableStream<any>;
|
|
10
|
-
incomingUnidirectionalStreams: ReadableStream<ReadableStream<Uint8Array>>;
|
|
11
|
-
close(): void;
|
|
12
|
-
ready: Promise<void>;
|
|
13
|
-
}
|
|
14
|
-
export declare class MoQSession {
|
|
15
|
-
transport: TransportInterface;
|
|
16
|
-
private controlStreamWriter;
|
|
17
|
-
private controlStreamReader;
|
|
18
|
-
private role;
|
|
19
|
-
setupComplete: boolean;
|
|
20
|
-
private setupResolve;
|
|
21
|
-
private setupPromise;
|
|
22
|
-
maxSubscribeId: number;
|
|
23
|
-
private streamBuffer;
|
|
24
|
-
publisher: MoQPublisher;
|
|
25
|
-
subscriber: MoQSubscriber;
|
|
26
|
-
constructor(transport: TransportInterface, onObject?: (ns: string, name: string, obj: Uint8Array, meta: any) => void, role?: number);
|
|
27
|
-
/** Wait for handshake to complete before sending ANNOUNCE/SUBSCRIBE */
|
|
28
|
-
waitForSetup(): Promise<void>;
|
|
29
|
-
init(): Promise<void>;
|
|
30
|
-
private readUniStreams;
|
|
31
|
-
sendControlMessage(msg: any): Promise<void>;
|
|
32
|
-
private readLoop;
|
|
33
|
-
private processBuffer;
|
|
34
|
-
private handleControlMessage;
|
|
35
|
-
}
|
package/dist/moq/MoQSession.js
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { MoQSerializer, MsgType } from './messages';
|
|
2
|
-
import { StreamBuffer } from './common';
|
|
3
|
-
import { MoQPublisher } from './MoQPublisher';
|
|
4
|
-
import { MoQSubscriber } from './MoQSubscriber';
|
|
5
|
-
export class MoQSession {
|
|
6
|
-
constructor(transport, onObject, role = 3) {
|
|
7
|
-
this.controlStreamWriter = null;
|
|
8
|
-
this.controlStreamReader = null;
|
|
9
|
-
this.setupComplete = false;
|
|
10
|
-
this.setupResolve = null;
|
|
11
|
-
this.maxSubscribeId = 0;
|
|
12
|
-
this.streamBuffer = new StreamBuffer();
|
|
13
|
-
this.transport = transport;
|
|
14
|
-
this.role = role;
|
|
15
|
-
this.setupPromise = new Promise((resolve) => {
|
|
16
|
-
this.setupResolve = resolve;
|
|
17
|
-
});
|
|
18
|
-
this.publisher = new MoQPublisher(this);
|
|
19
|
-
this.subscriber = new MoQSubscriber(this, onObject || ((ns, n, o, m) => console.log(`[MoQ] Object: ${ns}/${n}`, m)));
|
|
20
|
-
}
|
|
21
|
-
/** Wait for handshake to complete before sending ANNOUNCE/SUBSCRIBE */
|
|
22
|
-
async waitForSetup() {
|
|
23
|
-
return this.setupPromise;
|
|
24
|
-
}
|
|
25
|
-
async init() {
|
|
26
|
-
await this.transport.ready;
|
|
27
|
-
// Initiate Control Stream
|
|
28
|
-
// For Client, we open a BiStream.
|
|
29
|
-
const stream = await this.transport.createBidirectionalStream();
|
|
30
|
-
this.controlStreamWriter = stream.writable.getWriter();
|
|
31
|
-
this.controlStreamReader = stream.readable.getReader();
|
|
32
|
-
// Send CLIENT_SETUP
|
|
33
|
-
const setupMsg = {
|
|
34
|
-
type: MsgType.CLIENT_SETUP,
|
|
35
|
-
supportedVersions: [0xff000007],
|
|
36
|
-
role: this.role,
|
|
37
|
-
};
|
|
38
|
-
await this.sendControlMessage(setupMsg);
|
|
39
|
-
// Start Reading Control
|
|
40
|
-
this.readLoop();
|
|
41
|
-
// Start Reading UniStreams (Data)
|
|
42
|
-
this.readUniStreams();
|
|
43
|
-
}
|
|
44
|
-
async readUniStreams() {
|
|
45
|
-
const uniReader = this.transport.incomingUnidirectionalStreams.getReader();
|
|
46
|
-
try {
|
|
47
|
-
while (true) {
|
|
48
|
-
const { done, value } = await uniReader.read();
|
|
49
|
-
if (done)
|
|
50
|
-
break;
|
|
51
|
-
if (value && this.subscriber) {
|
|
52
|
-
this.subscriber.handleIncomingUniStream(value);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
catch (e) {
|
|
57
|
-
console.error("UniStream Loop Error", e);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
async sendControlMessage(msg) {
|
|
61
|
-
const bytes = MoQSerializer.serialize(msg);
|
|
62
|
-
if (!this.controlStreamWriter)
|
|
63
|
-
throw new Error("Control stream not open");
|
|
64
|
-
await this.controlStreamWriter.write(bytes);
|
|
65
|
-
}
|
|
66
|
-
async readLoop() {
|
|
67
|
-
if (!this.controlStreamReader)
|
|
68
|
-
return;
|
|
69
|
-
try {
|
|
70
|
-
while (true) {
|
|
71
|
-
const { done, value } = await this.controlStreamReader.read();
|
|
72
|
-
if (done) {
|
|
73
|
-
console.warn("[MoQ] Control Stream Closed by Remote");
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
if (value) {
|
|
77
|
-
console.log(`[MoQ] Control Stream RX: ${value.byteLength} bytes`);
|
|
78
|
-
this.streamBuffer.append(value);
|
|
79
|
-
this.processBuffer();
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
catch (e) {
|
|
84
|
-
console.error("Control stream error", e);
|
|
85
|
-
}
|
|
86
|
-
console.log("[MoQ] Read Loop Exited");
|
|
87
|
-
}
|
|
88
|
-
processBuffer() {
|
|
89
|
-
while (true) {
|
|
90
|
-
// console.log(`[MoQ] Processing Buffer. Offset: ${(this.streamBuffer as any).offset}, Remaining: ${(this.streamBuffer as any).buffer.length - (this.streamBuffer as any).offset}`);
|
|
91
|
-
// Try to deserialize a message
|
|
92
|
-
let msg;
|
|
93
|
-
try {
|
|
94
|
-
msg = MoQSerializer.deserialize(this.streamBuffer);
|
|
95
|
-
}
|
|
96
|
-
catch (e) {
|
|
97
|
-
console.error("[MoQ] Deserialization Error:", e);
|
|
98
|
-
// fatal?
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
if (!msg) {
|
|
102
|
-
// Not enough data yet (encountered EOF during parse)
|
|
103
|
-
// Rewind happens in deserialize if it catches EOF
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
// Msg Successfully Parsed
|
|
107
|
-
this.handleControlMessage(msg);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
handleControlMessage(msg) {
|
|
111
|
-
switch (msg.type) {
|
|
112
|
-
case MsgType.SERVER_SETUP:
|
|
113
|
-
console.log(`[MoQ] Handshake Complete. Server selected version: 0x${msg.selectedVersion.toString(16)}`);
|
|
114
|
-
this.setupComplete = true;
|
|
115
|
-
if (this.setupResolve) {
|
|
116
|
-
this.setupResolve();
|
|
117
|
-
this.setupResolve = null;
|
|
118
|
-
}
|
|
119
|
-
break;
|
|
120
|
-
case MsgType.MAX_SUBSCRIBE_ID:
|
|
121
|
-
this.maxSubscribeId = msg.subscribeId;
|
|
122
|
-
console.log(`[MoQ] Max Subscribe ID updated: ${this.maxSubscribeId}`);
|
|
123
|
-
break;
|
|
124
|
-
case MsgType.SUBSCRIBE:
|
|
125
|
-
// Relay (or Peer) wants to subscribe to our track.
|
|
126
|
-
this.publisher.onSubscribe(msg);
|
|
127
|
-
// Send SUBSCRIBE_OK
|
|
128
|
-
const subOk = {
|
|
129
|
-
type: MsgType.SUBSCRIBE_OK,
|
|
130
|
-
subscribeId: msg.subscribeId,
|
|
131
|
-
expires: 0
|
|
132
|
-
};
|
|
133
|
-
this.sendControlMessage(subOk).catch(console.error);
|
|
134
|
-
break;
|
|
135
|
-
case MsgType.SUBSCRIBE_OK:
|
|
136
|
-
console.log(`[MoQ] SUBSCRIBE_OK for id=${msg.subscribeId}`);
|
|
137
|
-
this.subscriber.onSubscribeOk(msg.subscribeId);
|
|
138
|
-
break;
|
|
139
|
-
case MsgType.SUBSCRIBE_ERROR:
|
|
140
|
-
console.error(`[MoQ] SUBSCRIBE_ERROR for id=${msg.subscribeId} code=${msg.code}`);
|
|
141
|
-
this.subscriber.onSubscribeError(msg.subscribeId, msg.code).catch(console.error);
|
|
142
|
-
break;
|
|
143
|
-
case MsgType.ANNOUNCE_OK:
|
|
144
|
-
console.log(`[MoQ] ANNOUNCE_OK for ns=${msg.trackNamespace}`);
|
|
145
|
-
this.publisher.onAnnounceResult(msg.trackNamespace);
|
|
146
|
-
break;
|
|
147
|
-
case MsgType.ANNOUNCE_ERROR:
|
|
148
|
-
console.error(`[MoQ] ANNOUNCE_ERROR for ns=${msg.trackNamespace} code=${msg.code}`);
|
|
149
|
-
this.publisher.onAnnounceResult(msg.trackNamespace, { code: msg.code, reason: msg.reasonPhrase || "Unknown" });
|
|
150
|
-
break;
|
|
151
|
-
case MsgType.UNSUBSCRIBE:
|
|
152
|
-
// Relay (or Peer) wants to stop receiving our track.
|
|
153
|
-
this.publisher.onUnsubscribe(msg.subscribeId);
|
|
154
|
-
break;
|
|
155
|
-
case MsgType.SUBSCRIBE_DONE:
|
|
156
|
-
// Relay (or Peer) says the stream is finished.
|
|
157
|
-
this.subscriber.onSubscribeDone(msg.subscribeId, msg.code, msg.reason);
|
|
158
|
-
break;
|
|
159
|
-
case MsgType.GOAWAY:
|
|
160
|
-
console.warn(`[MoQ] GOAWAY received`);
|
|
161
|
-
break;
|
|
162
|
-
default:
|
|
163
|
-
console.log(`[MoQ] Received Control Message type=0x${msg.type.toString(16)}`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { MoQSession } from './MoQSession';
|
|
2
|
-
export declare class MoQSubscriber {
|
|
3
|
-
private session;
|
|
4
|
-
private onObject;
|
|
5
|
-
constructor(session: MoQSession, onObject: (ns: string, name: string, obj: Uint8Array, meta: any) => void);
|
|
6
|
-
private aliasMap;
|
|
7
|
-
private nextAlias;
|
|
8
|
-
private nextSubscribeId;
|
|
9
|
-
private pendingSubscriptions;
|
|
10
|
-
private deliveryState;
|
|
11
|
-
private activeSubscriptions;
|
|
12
|
-
subscribe(namespace: string, name: string): Promise<void>;
|
|
13
|
-
onSubscribeOk(subscribeId: number): void;
|
|
14
|
-
onSubscribeError(subscribeId: number, code: number): Promise<void>;
|
|
15
|
-
onSubscribeDone(subscribeId: number, code: number, reason: string): void;
|
|
16
|
-
private wakeDelivery;
|
|
17
|
-
private runDeliveryLoop;
|
|
18
|
-
handleIncomingUniStream(stream: ReadableStream<Uint8Array>): Promise<void>;
|
|
19
|
-
}
|