openrtc 0.1.0
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 +106 -0
- package/dist/OpenRTC.d.ts +71 -0
- package/dist/OpenRTC.js +130 -0
- package/dist/api/MediaTransport.d.ts +7 -0
- package/dist/api/MediaTransport.js +262 -0
- package/dist/api/PlutoPeerConnection.d.ts +50 -0
- package/dist/api/PlutoPeerConnection.js +434 -0
- package/dist/api/PlutoWebSocket.d.ts +24 -0
- package/dist/api/PlutoWebSocket.js +113 -0
- package/dist/api/PlutoWebTransport.d.ts +29 -0
- package/dist/api/PlutoWebTransport.js +105 -0
- package/dist/auth/host.d.ts +73 -0
- package/dist/auth/host.js +513 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/internal.d.ts +1 -0
- package/dist/auth/internal.js +1 -0
- package/dist/compat/index.d.ts +20 -0
- package/dist/compat/index.js +20 -0
- package/dist/core/Client.d.ts +352 -0
- package/dist/core/Client.js +3689 -0
- package/dist/core/Connection.d.ts +67 -0
- package/dist/core/Connection.js +404 -0
- package/dist/core/IEngineBridge.d.ts +18 -0
- package/dist/core/IEngineBridge.js +1 -0
- package/dist/core/ISignalingTransport.d.ts +13 -0
- package/dist/core/ISignalingTransport.js +1 -0
- package/dist/core/PeerLifecycleManager.d.ts +43 -0
- package/dist/core/PeerLifecycleManager.js +330 -0
- package/dist/core/PeerProjectionStore.d.ts +43 -0
- package/dist/core/PeerProjectionStore.js +327 -0
- package/dist/core/PlutoRTC.d.ts +13 -0
- package/dist/core/PlutoRTC.js +26 -0
- package/dist/core/Room.d.ts +47 -0
- package/dist/core/Room.js +376 -0
- package/dist/core/Signaling.d.ts +39 -0
- package/dist/core/Signaling.js +147 -0
- package/dist/core/bridges/BridgeCodecs.d.ts +19 -0
- package/dist/core/bridges/BridgeCodecs.js +317 -0
- package/dist/core/bridges/BrowserFirestoreRooms.d.ts +36 -0
- package/dist/core/bridges/BrowserFirestoreRooms.js +169 -0
- package/dist/core/bridges/BrowserFirestoreSignaling.d.ts +111 -0
- package/dist/core/bridges/BrowserFirestoreSignaling.js +1010 -0
- package/dist/core/bridges/NativeIpcBridge.d.ts +156 -0
- package/dist/core/bridges/NativeIpcBridge.js +1472 -0
- package/dist/core/bridges/RtdbPresence.d.ts +122 -0
- package/dist/core/bridges/RtdbPresence.js +201 -0
- package/dist/core/bridges/TauriBridge.d.ts +8 -0
- package/dist/core/bridges/TauriBridge.js +7 -0
- package/dist/core/bridges/WasmBridge.d.ts +233 -0
- package/dist/core/bridges/WasmBridge.js +1811 -0
- package/dist/core/config.d.ts +31 -0
- package/dist/core/config.js +138 -0
- package/dist/core/transports/ITransport.d.ts +12 -0
- package/dist/core/transports/ITransport.js +1 -0
- package/dist/core/transports/LocalDaemonBridge.d.ts +41 -0
- package/dist/core/transports/LocalDaemonBridge.js +227 -0
- package/dist/core/transports/TauriSignaling.d.ts +28 -0
- package/dist/core/transports/TauriSignaling.js +373 -0
- package/dist/core/transports/TransportFactory.d.ts +9 -0
- package/dist/core/transports/TransportFactory.js +59 -0
- package/dist/core/transports/WasmSignaling.d.ts +78 -0
- package/dist/core/transports/WasmSignaling.js +522 -0
- package/dist/core/transports/WebRTCTransport.d.ts +23 -0
- package/dist/core/transports/WebRTCTransport.js +132 -0
- package/dist/core/trust.d.ts +23 -0
- package/dist/core/trust.js +99 -0
- package/dist/core/types.d.ts +559 -0
- package/dist/core/types.js +19 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/moq/MoQMediaTransport.d.ts +6 -0
- package/dist/moq/MoQMediaTransport.js +106 -0
- package/dist/moq/MoQPublisher.d.ts +22 -0
- package/dist/moq/MoQPublisher.js +179 -0
- package/dist/moq/MoQSession.d.ts +35 -0
- package/dist/moq/MoQSession.js +166 -0
- package/dist/moq/MoQSubscriber.d.ts +19 -0
- package/dist/moq/MoQSubscriber.js +235 -0
- package/dist/moq/common.d.ts +47 -0
- package/dist/moq/common.js +304 -0
- package/dist/moq/messages.d.ts +105 -0
- package/dist/moq/messages.js +367 -0
- package/dist/presets/plutoHosted.d.ts +13 -0
- package/dist/presets/plutoHosted.js +18 -0
- package/dist/runtime/DelegatingRuntimeAdapter.d.ts +68 -0
- package/dist/runtime/DelegatingRuntimeAdapter.js +250 -0
- package/dist/runtime/IpcRuntimeAdapter.d.ts +9 -0
- package/dist/runtime/IpcRuntimeAdapter.js +13 -0
- package/dist/runtime/RuntimeClient.d.ts +257 -0
- package/dist/runtime/RuntimeClient.js +1484 -0
- package/dist/runtime/TauriRuntimeAdapter.d.ts +1 -0
- package/dist/runtime/TauriRuntimeAdapter.js +1 -0
- package/dist/runtime/WasmRuntimeAdapter.d.ts +6 -0
- package/dist/runtime/WasmRuntimeAdapter.js +11 -0
- package/dist/runtime/factories.d.ts +24 -0
- package/dist/runtime/factories.js +66 -0
- package/dist/runtime/index.d.ts +6 -0
- package/dist/runtime/index.js +5 -0
- package/dist/runtime/ipc.d.ts +8 -0
- package/dist/runtime/ipc.js +65 -0
- package/dist/runtime/ipcContract.d.ts +46 -0
- package/dist/runtime/ipcContract.js +1 -0
- package/dist/runtime/tauri.d.ts +16 -0
- package/dist/runtime/tauri.js +22 -0
- package/dist/runtime/types.d.ts +110 -0
- package/dist/runtime/types.js +1 -0
- package/dist/transports/TauriTransport.d.ts +17 -0
- package/dist/transports/TauriTransport.js +49 -0
- package/dist/transports/WebTransport.d.ts +23 -0
- package/dist/transports/WebTransport.js +71 -0
- package/dist/utils/debug.d.ts +4 -0
- package/dist/utils/debug.js +33 -0
- package/dist/utils/runtime.d.ts +1 -0
- package/dist/utils/runtime.js +10 -0
- package/package.json +97 -0
- package/wasm/README.md +64 -0
- package/wasm/openrtc.d.ts +284 -0
- package/wasm/openrtc.js +2132 -0
- package/wasm/openrtc_bg.wasm +0 -0
- package/wasm/openrtc_bg.wasm.d.ts +110 -0
- package/wasm/package.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# OpenRTC TypeScript Package
|
|
2
|
+
|
|
3
|
+
The TypeScript package is the host-facing layer for `openrtc`.
|
|
4
|
+
|
|
5
|
+
It does not implement a second realtime engine. It exposes the Rust runtime to:
|
|
6
|
+
- browser apps through WASM plus the scoped browser Firebase signaling layer
|
|
7
|
+
- native apps through a generic IPC contract, with first-party Tauri wiring in `openrtc-tauri`
|
|
8
|
+
|
|
9
|
+
## Recommended Usage
|
|
10
|
+
|
|
11
|
+
Use the OpenRTC client entry points for new integrations:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { OpenRTC } from 'openrtc';
|
|
15
|
+
|
|
16
|
+
const client = OpenRTC({
|
|
17
|
+
apiKey: 'pk_live_...',
|
|
18
|
+
authMode: 'external',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await client.connect();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For normal app code, the default flow is:
|
|
25
|
+
|
|
26
|
+
1. discover or select a `deviceId`
|
|
27
|
+
2. call `connectPeer(...)` or `connectToDevice(...)`
|
|
28
|
+
3. wait for `waitForConnectedPeer(deviceId)`
|
|
29
|
+
4. open peer-targeted streams with `openPeerBi(...)` / `openPeerUni(...)`
|
|
30
|
+
|
|
31
|
+
Low-level peer handles returned by runtime methods, along with `openPeerBi(deviceId)` and `openPeerUni(deviceId)`, remain available for advanced runtime flows, but they are not the default integration model.
|
|
32
|
+
|
|
33
|
+
## What This Package Owns
|
|
34
|
+
- runtime factories and adapters
|
|
35
|
+
- auth context injection
|
|
36
|
+
- Pluto hosted auth helpers
|
|
37
|
+
- browser Firebase signaling/rooms wiring
|
|
38
|
+
- host wiring for WASM and generic IPC
|
|
39
|
+
|
|
40
|
+
## What This Package Does Not Own
|
|
41
|
+
- native Tauri API bindings
|
|
42
|
+
- peer discovery or lifecycle policy beyond the browser Firebase host layer
|
|
43
|
+
- iroh connection lifecycle
|
|
44
|
+
|
|
45
|
+
Those responsibilities belong to the Rust runtime and the host platform adapter.
|
|
46
|
+
|
|
47
|
+
## Message Model
|
|
48
|
+
- `openrtc` is message-agnostic above the runtime layer.
|
|
49
|
+
- The runtime only defines internal control messages such as handshake and heartbeat.
|
|
50
|
+
- Application payload protocols, including file transfer, should be implemented on top of generic runtime connections and streams.
|
|
51
|
+
- If an app needs richer payload ergonomics, add generic stream/connection primitives to the runtime instead of adding app-specific message types to `pluto-rtc`.
|
|
52
|
+
|
|
53
|
+
## Protocol Extension Model
|
|
54
|
+
|
|
55
|
+
The recommended TypeScript extension path is:
|
|
56
|
+
|
|
57
|
+
1. wait for a Rust-backed connected peer session
|
|
58
|
+
2. open a bidirectional or unidirectional stream
|
|
59
|
+
3. implement your own framing and message semantics in app code
|
|
60
|
+
|
|
61
|
+
This keeps the runtime responsible for peer/session establishment while leaving
|
|
62
|
+
full control of channel/message specifics to the developer.
|
|
63
|
+
|
|
64
|
+
Advanced custom ALPN protocol plugins are a Rust-first/native-first feature.
|
|
65
|
+
Browser parity is guaranteed at the connected-peer stream layer, not at the
|
|
66
|
+
full ALPN plugin layer.
|
|
67
|
+
|
|
68
|
+
## Main Surfaces
|
|
69
|
+
|
|
70
|
+
### Root Package
|
|
71
|
+
- `OpenRTC()`
|
|
72
|
+
- `OpenRTC.native()`
|
|
73
|
+
- `Client` (re-export alias)
|
|
74
|
+
- `RuntimeClient`
|
|
75
|
+
|
|
76
|
+
### Runtime Secondary Entrypoint
|
|
77
|
+
- `openrtc/runtime`
|
|
78
|
+
- `createWasmClient()` and `createIpcClient(..., bridge)` (legacy compatibility)
|
|
79
|
+
- `IpcRuntimeAdapter`
|
|
80
|
+
- `WasmRuntimeAdapter`
|
|
81
|
+
- `PlutoIpcBridge` types for advanced host integrations
|
|
82
|
+
|
|
83
|
+
### Tauri Package
|
|
84
|
+
- `openrtc-tauri`
|
|
85
|
+
- `createTauriIpcBridge()`
|
|
86
|
+
- `TauriRuntimeAdapter`
|
|
87
|
+
- `openrtc/runtime/tauri` remains as a deprecated compatibility shim for one release cycle
|
|
88
|
+
|
|
89
|
+
## Auth Model
|
|
90
|
+
- `anonymous`: no Pluto auth required
|
|
91
|
+
- `required`: Pluto auth required; the SDK may initiate Pluto SSO
|
|
92
|
+
- `external`: Pluto auth still required, but the host app acquires it and injects it into the runtime
|
|
93
|
+
|
|
94
|
+
## Design Rule
|
|
95
|
+
If a feature requires discovery, signaling, presence, or lifecycle state, the TypeScript package should delegate to Rust rather than reimplement it locally, except for the scoped browser Firebase host layer where the official browser SDK is the source of truth.
|
|
96
|
+
|
|
97
|
+
## Allowed vs. Not Allowed
|
|
98
|
+
|
|
99
|
+
- Allowed:
|
|
100
|
+
- host/runtime adapter selection
|
|
101
|
+
- auth injection and hosted Pluto auth helpers
|
|
102
|
+
- thin, typed wrappers over Rust-owned state
|
|
103
|
+
- Not allowed:
|
|
104
|
+
- TypeScript-owned discovery or lifecycle engines
|
|
105
|
+
- TypeScript-owned peer identity policy
|
|
106
|
+
- app-specific protocol messages embedded into the runtime
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRTC — simplified developer-facing entry point.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { OpenRTC } from 'openrtc';
|
|
6
|
+
* const client = OpenRTC({ apiKey: 'pk_live_...' });
|
|
7
|
+
* await client.connect();
|
|
8
|
+
*
|
|
9
|
+
* For native (Tauri) apps:
|
|
10
|
+
* const client = OpenRTC.native({ apiKey: 'pk_live_...' });
|
|
11
|
+
*
|
|
12
|
+
* The returned Client object combines RuntimeClient methods with
|
|
13
|
+
* auth helpers (setAuth, syncPlutoAuth, bindPlutoAuthHost, signIn, signOut).
|
|
14
|
+
*
|
|
15
|
+
* Backward compatibility:
|
|
16
|
+
* - createWasmClient() and createIpcClient() remain available from `openrtc/runtime`
|
|
17
|
+
* - PlutoRuntimeAssembly remains available from `openrtc/runtime`
|
|
18
|
+
* - RuntimeClient is still re-exported as Client
|
|
19
|
+
*/
|
|
20
|
+
import type { AuthContext } from './core/IEngineBridge';
|
|
21
|
+
import type { ClientOptions } from './core/types';
|
|
22
|
+
import { RuntimeClient } from './runtime/RuntimeClient';
|
|
23
|
+
import { type PlutoRuntimeAssembly } from './runtime/factories';
|
|
24
|
+
import { type PlutoIpcBridge } from './runtime/ipc';
|
|
25
|
+
/**
|
|
26
|
+
* OpenRTCClient extends RuntimeClient with auth helpers that were
|
|
27
|
+
* previously only available on PlutoRuntimeAssembly.
|
|
28
|
+
*
|
|
29
|
+
* This is the unified type returned by OpenRTC() and OpenRTC.native().
|
|
30
|
+
*/
|
|
31
|
+
export interface OpenRTCClient extends RuntimeClient {
|
|
32
|
+
/** @deprecated Use setAuth() instead */
|
|
33
|
+
setAuthContext(context: AuthContext | null): Promise<void>;
|
|
34
|
+
/** Set auth context for the runtime */
|
|
35
|
+
setAuth(context: AuthContext | null): Promise<void>;
|
|
36
|
+
/** Sync Pluto-hosted auth to the runtime */
|
|
37
|
+
syncPlutoAuth(options?: {
|
|
38
|
+
userId?: string | null;
|
|
39
|
+
forceRefresh?: boolean;
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
/** Bind Pluto auth host to auto-sync on token changes */
|
|
42
|
+
bindPlutoAuthHost(): () => void;
|
|
43
|
+
/** Sign in via Pluto SSO */
|
|
44
|
+
signIn(): Promise<void>;
|
|
45
|
+
/** Sign out and clear auth context */
|
|
46
|
+
signOut(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Access to the underlying PlutoRuntimeAssembly for backward compatibility.
|
|
49
|
+
* @deprecated Access methods directly on the client instead.
|
|
50
|
+
*/
|
|
51
|
+
readonly runtime: RuntimeClient;
|
|
52
|
+
/**
|
|
53
|
+
* Access to the underlying PlutoRuntimeAssembly for backward compatibility.
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
readonly _assembly: PlutoRuntimeAssembly;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Create an OpenRTC client for browser/WASM environments.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* import { OpenRTC } from 'openrtc';
|
|
64
|
+
* const client = OpenRTC({ apiKey: 'pk_live_...' });
|
|
65
|
+
* await client.connect();
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare function OpenRTC(options: ClientOptions): OpenRTCClient;
|
|
69
|
+
export declare namespace OpenRTC {
|
|
70
|
+
var native: (options: ClientOptions, bridge?: PlutoIpcBridge) => OpenRTCClient;
|
|
71
|
+
}
|
package/dist/OpenRTC.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRTC — simplified developer-facing entry point.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { OpenRTC } from 'openrtc';
|
|
6
|
+
* const client = OpenRTC({ apiKey: 'pk_live_...' });
|
|
7
|
+
* await client.connect();
|
|
8
|
+
*
|
|
9
|
+
* For native (Tauri) apps:
|
|
10
|
+
* const client = OpenRTC.native({ apiKey: 'pk_live_...' });
|
|
11
|
+
*
|
|
12
|
+
* The returned Client object combines RuntimeClient methods with
|
|
13
|
+
* auth helpers (setAuth, syncPlutoAuth, bindPlutoAuthHost, signIn, signOut).
|
|
14
|
+
*
|
|
15
|
+
* Backward compatibility:
|
|
16
|
+
* - createWasmClient() and createIpcClient() remain available from `openrtc/runtime`
|
|
17
|
+
* - PlutoRuntimeAssembly remains available from `openrtc/runtime`
|
|
18
|
+
* - RuntimeClient is still re-exported as Client
|
|
19
|
+
*/
|
|
20
|
+
import { createWasmClient, createIpcClient } from './runtime/factories';
|
|
21
|
+
import { createCompatTauriIpcBridge } from './runtime/ipc';
|
|
22
|
+
function resolveOptions(options) {
|
|
23
|
+
// Map new option names to old ones (new names take priority)
|
|
24
|
+
const resolved = { ...options };
|
|
25
|
+
// auth → authMode
|
|
26
|
+
if ('auth' in options && !options.authMode) {
|
|
27
|
+
const auth = options.auth;
|
|
28
|
+
resolved.authMode = auth === 'none' ? 'anonymous' : auth;
|
|
29
|
+
}
|
|
30
|
+
// identity → nodeIdPersistence
|
|
31
|
+
if ('identity' in options && !options.nodeIdPersistence) {
|
|
32
|
+
const identity = options.identity;
|
|
33
|
+
resolved.nodeIdPersistence = identity === 'temporary' ? 'ephemeral' : identity;
|
|
34
|
+
}
|
|
35
|
+
// namespace → storagePrefix
|
|
36
|
+
if ('namespace' in options && !options.storagePrefix) {
|
|
37
|
+
resolved.storagePrefix = options.namespace;
|
|
38
|
+
}
|
|
39
|
+
// privacy → strictMode
|
|
40
|
+
if ('privacy' in options && options.strictMode === undefined) {
|
|
41
|
+
resolved.strictMode = !!options.privacy;
|
|
42
|
+
}
|
|
43
|
+
return resolved;
|
|
44
|
+
}
|
|
45
|
+
function wrapAssembly(assembly) {
|
|
46
|
+
const runtime = assembly.runtime;
|
|
47
|
+
// Create a proxy that delegates to RuntimeClient but adds assembly helpers
|
|
48
|
+
const client = Object.create(runtime);
|
|
49
|
+
// Assembly auth helpers as direct methods
|
|
50
|
+
Object.defineProperties(client, {
|
|
51
|
+
setAuth: {
|
|
52
|
+
value: (context) => assembly.setAuthContext(context),
|
|
53
|
+
enumerable: true,
|
|
54
|
+
writable: true,
|
|
55
|
+
configurable: true,
|
|
56
|
+
},
|
|
57
|
+
setAuthContext: {
|
|
58
|
+
value: (context) => assembly.setAuthContext(context),
|
|
59
|
+
enumerable: true,
|
|
60
|
+
writable: true,
|
|
61
|
+
configurable: true,
|
|
62
|
+
},
|
|
63
|
+
syncPlutoAuth: {
|
|
64
|
+
value: (options) => assembly.syncPlutoAuth(options),
|
|
65
|
+
enumerable: true,
|
|
66
|
+
writable: true,
|
|
67
|
+
configurable: true,
|
|
68
|
+
},
|
|
69
|
+
bindPlutoAuthHost: {
|
|
70
|
+
value: () => assembly.bindPlutoAuthHost(),
|
|
71
|
+
enumerable: true,
|
|
72
|
+
writable: true,
|
|
73
|
+
configurable: true,
|
|
74
|
+
},
|
|
75
|
+
signIn: {
|
|
76
|
+
value: () => assembly.signInWithPluto(),
|
|
77
|
+
enumerable: true,
|
|
78
|
+
writable: true,
|
|
79
|
+
configurable: true,
|
|
80
|
+
},
|
|
81
|
+
signOut: {
|
|
82
|
+
value: () => assembly.signOut(),
|
|
83
|
+
enumerable: true,
|
|
84
|
+
writable: true,
|
|
85
|
+
configurable: true,
|
|
86
|
+
},
|
|
87
|
+
// Backward compat: .runtime returns the RuntimeClient
|
|
88
|
+
runtime: {
|
|
89
|
+
get: () => runtime,
|
|
90
|
+
enumerable: false,
|
|
91
|
+
},
|
|
92
|
+
_assembly: {
|
|
93
|
+
get: () => assembly,
|
|
94
|
+
enumerable: false,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
return client;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Create an OpenRTC client for browser/WASM environments.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* import { OpenRTC } from 'openrtc';
|
|
105
|
+
* const client = OpenRTC({ apiKey: 'pk_live_...' });
|
|
106
|
+
* await client.connect();
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export function OpenRTC(options) {
|
|
110
|
+
const resolved = resolveOptions(options);
|
|
111
|
+
const assembly = createWasmClient(resolved);
|
|
112
|
+
return wrapAssembly(assembly);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Create an OpenRTC client for native (Tauri/IPC) environments.
|
|
116
|
+
* Auto-detects the Tauri IPC bridge.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* import { OpenRTC } from 'openrtc';
|
|
121
|
+
* const client = OpenRTC.native({ apiKey: 'pk_live_...' });
|
|
122
|
+
* await client.connect();
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
OpenRTC.native = function (options, bridge) {
|
|
126
|
+
const resolved = resolveOptions(options);
|
|
127
|
+
const ipcBridge = bridge ?? createCompatTauriIpcBridge();
|
|
128
|
+
const assembly = createIpcClient(resolved, ipcBridge);
|
|
129
|
+
return wrapAssembly(assembly);
|
|
130
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare class MediaTransport {
|
|
2
|
+
static sendTrack(track: MediaStreamTrack, sendFn: (data: Uint8Array) => Promise<void>): Promise<{
|
|
3
|
+
stop: () => void;
|
|
4
|
+
requestKeyFrame: () => void;
|
|
5
|
+
} | void>;
|
|
6
|
+
static receiveTrack(kind: 'audio' | 'video', onKeyFrameRequest?: () => void): MediaStreamTrack | null;
|
|
7
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// Protocol-ish logic for transforming MediaStreamTracks into Chunks and back.
|
|
2
|
+
// Since we don't have VideoEncoder/VideoDecoder in standard ts environment (it's web only),
|
|
3
|
+
// we will just define the interfaces or classes here.
|
|
4
|
+
// We use type 'any' for WebCodecs interfaces to avoid TS errors in environemnts without @types/dom-webcodecs
|
|
5
|
+
export class MediaTransport {
|
|
6
|
+
// Encodes a track into the connection
|
|
7
|
+
static async sendTrack(track, sendFn) {
|
|
8
|
+
console.log(`[MediaTransport] sendTrack called for ${track.kind}`);
|
|
9
|
+
if ((typeof VideoEncoder === 'undefined') && (typeof AudioEncoder === 'undefined')) {
|
|
10
|
+
console.error("WebCodecs API not supported.");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const processor = new window.MediaStreamTrackProcessor({ track });
|
|
14
|
+
const reader = processor.readable.getReader();
|
|
15
|
+
// Very basic encoder setup.
|
|
16
|
+
// In reality we need to negotiate codecs. We assume VP8 for video and Opus for audio.
|
|
17
|
+
let encoder = null;
|
|
18
|
+
if (track.kind === 'video') {
|
|
19
|
+
let seqNum = 0;
|
|
20
|
+
encoder = new window.VideoEncoder({
|
|
21
|
+
output: (chunk, metadata) => {
|
|
22
|
+
if (chunk.type === 'key') {
|
|
23
|
+
console.log(`[MediaTransport] Generated KEY frame for video. Size: ${chunk.byteLength}`);
|
|
24
|
+
}
|
|
25
|
+
// console.log(`[MediaTransport] Encoded video chunk. Key: ${chunk.type}, Size: ${chunk.byteLength}`);
|
|
26
|
+
// Send chunk
|
|
27
|
+
// We need to serialize the chunk.
|
|
28
|
+
// [MAGIC 1b][timestamp 8 bytes][isKey 1 byte][seq 2 bytes][data...]
|
|
29
|
+
const buffer = new ArrayBuffer(12 + chunk.byteLength);
|
|
30
|
+
const view = new DataView(buffer);
|
|
31
|
+
view.setUint8(0, 0x77); // Magic byte
|
|
32
|
+
view.setBigInt64(1, BigInt(chunk.timestamp), false); // Big Endian
|
|
33
|
+
view.setUint8(9, chunk.type === 'key' ? 1 : 0);
|
|
34
|
+
const seq = (seqNum++) % 65536;
|
|
35
|
+
view.setUint16(10, seq, false);
|
|
36
|
+
const data = new Uint8Array(buffer);
|
|
37
|
+
chunk.copyTo(new Uint8Array(buffer, 12));
|
|
38
|
+
sendFn(data).catch(e => {
|
|
39
|
+
const msg = (e.message || String(e)).toLowerCase();
|
|
40
|
+
if (msg.includes("connection is closed") || msg.includes("connection closed") || msg.includes("sending stopped by peer")) {
|
|
41
|
+
console.log(`[MediaTransport] Connection closed/stopped, stopping video encoder.`);
|
|
42
|
+
if (encoder) {
|
|
43
|
+
encoder.close();
|
|
44
|
+
encoder = null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
console.error("Send media failed", e);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
error: (e) => console.error("VideoEncoder error", e)
|
|
53
|
+
});
|
|
54
|
+
encoder.configure({
|
|
55
|
+
codec: 'vp8',
|
|
56
|
+
width: 640,
|
|
57
|
+
height: 480,
|
|
58
|
+
bitrate: 500000, // 500 Kbps - Reduce congestion
|
|
59
|
+
framerate: 24, // slightly lower framerate
|
|
60
|
+
latencyMode: 'realtime', // Critical for low latency streaming
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else if (track.kind === 'audio') {
|
|
64
|
+
encoder = new window.AudioEncoder({
|
|
65
|
+
output: (chunk, metadata) => {
|
|
66
|
+
const buffer = new ArrayBuffer(9 + chunk.byteLength);
|
|
67
|
+
const view = new DataView(buffer);
|
|
68
|
+
view.setUint8(0, 0x77); // Magic
|
|
69
|
+
view.setBigInt64(1, BigInt(chunk.timestamp), false);
|
|
70
|
+
const data = new Uint8Array(buffer);
|
|
71
|
+
chunk.copyTo(new Uint8Array(buffer, 9));
|
|
72
|
+
sendFn(data).catch(e => {
|
|
73
|
+
const msg = (e.message || String(e)).toLowerCase();
|
|
74
|
+
if (msg.includes("connection is closed") || msg.includes("connection closed")) {
|
|
75
|
+
console.log(`[MediaTransport] Connection closed, stopping audio encoder.`);
|
|
76
|
+
if (encoder) {
|
|
77
|
+
encoder.close();
|
|
78
|
+
encoder = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.error("Send audio failed", e);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
error: (e) => console.error("AudioEncoder error", e)
|
|
87
|
+
});
|
|
88
|
+
encoder.configure({
|
|
89
|
+
codec: 'opus',
|
|
90
|
+
sampleRate: 48000,
|
|
91
|
+
numberOfChannels: 1
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// Read loop - Run asynchronously so we can return the controller immediately
|
|
95
|
+
(async () => {
|
|
96
|
+
try {
|
|
97
|
+
while (true) {
|
|
98
|
+
const { done, value } = await reader.read();
|
|
99
|
+
if (done) {
|
|
100
|
+
console.log(`[MediaTransport] Reader done for ${track.kind}`);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
if (value) {
|
|
104
|
+
try {
|
|
105
|
+
if (encoder && encoder.state !== 'closed') {
|
|
106
|
+
const forceKey = encoder.forceKeyFrame;
|
|
107
|
+
if (forceKey) {
|
|
108
|
+
console.log(`[MediaTransport] Forcing Key Frame now.`);
|
|
109
|
+
encoder.encode(value, { keyFrame: true });
|
|
110
|
+
encoder.forceKeyFrame = false;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
encoder.encode(value);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
value.close();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
console.error(`[MediaTransport] Read loop error for ${track.kind}:`, e);
|
|
125
|
+
}
|
|
126
|
+
})();
|
|
127
|
+
// Return controller
|
|
128
|
+
return {
|
|
129
|
+
stop: () => {
|
|
130
|
+
// TODO: Close processor and reader
|
|
131
|
+
},
|
|
132
|
+
requestKeyFrame: () => {
|
|
133
|
+
if (encoder && encoder.state === 'configured') {
|
|
134
|
+
console.log(`[MediaTransport] Key Frame Requested for ${track.kind}`);
|
|
135
|
+
// We can't force it here directly because we are in a read loop.
|
|
136
|
+
// But we can set a flag that the loop checks.
|
|
137
|
+
encoder.forceKeyFrame = true;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.warn(`[MediaTransport] Impossible to request KeyFrame: Encoder state is ${encoder?.state}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// Receives chunks and outputs a MediaStreamTrack
|
|
146
|
+
static receiveTrack(kind, onKeyFrameRequest) {
|
|
147
|
+
if ((typeof VideoDecoder === 'undefined') && (typeof AudioDecoder === 'undefined')) {
|
|
148
|
+
console.warn("WebCodecs API not supported.");
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const generator = new window.MediaStreamTrackGenerator({ kind });
|
|
152
|
+
const writer = generator.writable.getWriter();
|
|
153
|
+
let decoder = null;
|
|
154
|
+
// Throttling for PLI
|
|
155
|
+
let lastPli = 0;
|
|
156
|
+
let expectedSeq = null;
|
|
157
|
+
if (kind === 'video') {
|
|
158
|
+
decoder = new window.VideoDecoder({
|
|
159
|
+
output: (frame) => {
|
|
160
|
+
try {
|
|
161
|
+
writer.write(frame);
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
frame.close();
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
error: (e) => console.error("VideoDecoder error", e)
|
|
168
|
+
});
|
|
169
|
+
decoder.configure({
|
|
170
|
+
codec: 'vp8'
|
|
171
|
+
});
|
|
172
|
+
// Force wait for key frame immediately
|
|
173
|
+
decoder.waitingForKeyFrame = true;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
decoder = new window.AudioDecoder({
|
|
177
|
+
output: (data) => {
|
|
178
|
+
writer.write(data);
|
|
179
|
+
data.close();
|
|
180
|
+
},
|
|
181
|
+
error: (e) => console.error("AudioDecoder error", e)
|
|
182
|
+
});
|
|
183
|
+
decoder.configure({
|
|
184
|
+
codec: 'opus',
|
|
185
|
+
sampleRate: 48000,
|
|
186
|
+
numberOfChannels: 1
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
// Attach a method to the track object or return a controller?
|
|
190
|
+
// We will just return the track, but we need a way to feed data into the decoder.
|
|
191
|
+
// We can monkey-patch the track or store it in a map in PeerConnection.
|
|
192
|
+
generator.feed = (data) => {
|
|
193
|
+
if (decoder.state === 'closed')
|
|
194
|
+
return;
|
|
195
|
+
// Verify Magic
|
|
196
|
+
if (data.byteLength < 1 || data[0] !== 0x77) {
|
|
197
|
+
console.error("[MediaTransport] Invalid magic byte in media stream! Dropping packet.");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (kind === 'video') {
|
|
201
|
+
// Parse
|
|
202
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
203
|
+
const timestamp = Number(view.getBigInt64(1, false));
|
|
204
|
+
const isKey = view.getUint8(9) === 1;
|
|
205
|
+
const seq = view.getUint16(10, false);
|
|
206
|
+
const chunkData = data.slice(12);
|
|
207
|
+
// Sequence Check
|
|
208
|
+
if (expectedSeq !== null) {
|
|
209
|
+
if (seq !== expectedSeq) {
|
|
210
|
+
console.warn(`[MediaTransport] Sequence Mismatch! Expected ${expectedSeq}, got ${seq}. Dropping and requesting PLI.`);
|
|
211
|
+
decoder.waitingForKeyFrame = true;
|
|
212
|
+
if (onKeyFrameRequest)
|
|
213
|
+
onKeyFrameRequest();
|
|
214
|
+
// We also reset expectedSeq to this new one + 1?
|
|
215
|
+
// No, we better wait for KeyFrame.
|
|
216
|
+
// But we must update expectedSeq to avoid infinite loops if the stream just continues.
|
|
217
|
+
expectedSeq = (seq + 1) % 65536;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
expectedSeq = (seq + 1) % 65536;
|
|
222
|
+
const chunk = new window.EncodedVideoChunk({
|
|
223
|
+
type: isKey ? 'key' : 'delta',
|
|
224
|
+
timestamp: timestamp,
|
|
225
|
+
data: chunkData
|
|
226
|
+
});
|
|
227
|
+
if (decoder.state === 'configured' && !isKey && decoder.waitingForKeyFrame !== false) {
|
|
228
|
+
// Drop delta frames until we get a keyframe
|
|
229
|
+
const now = Date.now();
|
|
230
|
+
if (now - lastPli > 1000) {
|
|
231
|
+
console.log("[MediaTransport] Dropping delta frame, waiting for key frame. Requesting PLI.");
|
|
232
|
+
if (onKeyFrameRequest)
|
|
233
|
+
onKeyFrameRequest();
|
|
234
|
+
lastPli = now;
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (isKey) {
|
|
239
|
+
decoder.waitingForKeyFrame = false;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
decoder.decode(chunk);
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
console.error("[MediaTransport] Decode error:", e);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
250
|
+
const timestamp = Number(view.getBigInt64(1, false));
|
|
251
|
+
const chunkData = data.slice(9);
|
|
252
|
+
const chunk = new window.EncodedAudioChunk({
|
|
253
|
+
type: 'key', // Audio usually all key frames or dependent? Opus is complicated.
|
|
254
|
+
timestamp: timestamp,
|
|
255
|
+
data: chunkData
|
|
256
|
+
});
|
|
257
|
+
decoder.decode(chunk);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
return generator;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Client } from '../core/Client';
|
|
2
|
+
import { Connection } from '../core/Connection';
|
|
3
|
+
export declare class PlutoPeerConnection extends EventTarget {
|
|
4
|
+
static defaultClient: Client | null;
|
|
5
|
+
static setDefaultClient(client: Client): void;
|
|
6
|
+
onicecandidate: ((ev: RTCPeerConnectionIceEvent) => any) | null;
|
|
7
|
+
oniceconnectionstatechange: ((ev: Event) => any) | null;
|
|
8
|
+
ontrack: ((ev: RTCTrackEvent) => any) | null;
|
|
9
|
+
onsignalingstatechange: ((ev: Event) => any) | null;
|
|
10
|
+
connectionState: RTCPeerConnectionState;
|
|
11
|
+
iceConnectionState: RTCIceConnectionState;
|
|
12
|
+
signalingState: RTCSignalingState;
|
|
13
|
+
localDescription: RTCSessionDescription | null;
|
|
14
|
+
remoteDescription: RTCSessionDescription | null;
|
|
15
|
+
private client;
|
|
16
|
+
private connection;
|
|
17
|
+
private get useMoq();
|
|
18
|
+
get transportInfo(): {
|
|
19
|
+
signaling: string;
|
|
20
|
+
media: string;
|
|
21
|
+
};
|
|
22
|
+
constructor(configuration?: RTCConfiguration, clientInstance?: Client);
|
|
23
|
+
createOffer(options?: RTCOfferOptions): Promise<RTCSessionDescriptionInit>;
|
|
24
|
+
createAnswer(options?: RTCAnswerOptions): Promise<RTCSessionDescriptionInit>;
|
|
25
|
+
setLocalDescription(desc: RTCSessionDescriptionInit): Promise<void>;
|
|
26
|
+
setRemoteDescription(desc: RTCSessionDescriptionInit): Promise<void>;
|
|
27
|
+
addIceCandidate(candidate: RTCIceCandidateInit | RTCIceCandidate): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Manually provide an existing connection (e.g. from an incoming connection listener)
|
|
30
|
+
*/
|
|
31
|
+
private bufferTracks;
|
|
32
|
+
handleIncomingConnection(connection: Connection): Promise<void>;
|
|
33
|
+
private receivers;
|
|
34
|
+
private setupHooks;
|
|
35
|
+
private handleLegacyMedia;
|
|
36
|
+
private moqDecoders;
|
|
37
|
+
private moqGenerators;
|
|
38
|
+
private handleMoQMedia;
|
|
39
|
+
private handleMoQData;
|
|
40
|
+
private fireTrackEvent;
|
|
41
|
+
addTrack(track: MediaStreamTrack, ...streams: MediaStream[]): RTCRtpSender;
|
|
42
|
+
private mediaSenders;
|
|
43
|
+
private startIrohStream;
|
|
44
|
+
private startMoQStream;
|
|
45
|
+
createDataChannel(label: string): RTCDataChannel;
|
|
46
|
+
sendData(data: any): void;
|
|
47
|
+
private connect;
|
|
48
|
+
close(): void;
|
|
49
|
+
private updateState;
|
|
50
|
+
}
|