crowdplaysdk 0.4.1 → 0.5.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/Examples/App.tsx +2 -2
- package/README.md +201 -119
- package/ios/CrowdPlayRNModule.swift +61 -9
- package/ios/CrowdPlayRNVideoView.swift +1 -1
- package/ios/CrowdPlayReactNative.m +4 -1
- package/ios/wire.rb +4 -4
- package/lib/ConsentScreen.js +1 -1
- package/lib/VideoView.d.ts +1 -1
- package/lib/VideoView.js +1 -1
- package/lib/VoiceView.d.ts +4 -4
- package/lib/VoiceView.js +5 -5
- package/lib/index.d.ts +54 -12
- package/lib/index.js +43 -7
- package/llms.txt +64 -33
- package/package.json +1 -1
- package/src/ConsentScreen.tsx +1 -1
- package/src/VideoView.tsx +1 -1
- package/src/VoiceView.tsx +5 -5
- package/src/index.ts +82 -14
package/src/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* crowdplaysdk
|
|
2
|
+
* crowdplaysdk: CrowdPlay's lossless capture SDK for React Native (iOS).
|
|
3
3
|
*
|
|
4
4
|
* The recording engine is 100% native (CrowdPlaySDK): studio-grade audio
|
|
5
5
|
* (48 kHz / 24-bit, unprocessed) + 1080p30 video captured locally during a
|
|
6
6
|
* live call, clock-synced across participants, uploaded to CrowdPlay with
|
|
7
|
-
* retries and crash recovery. **No media ever crosses the JS bridge
|
|
8
|
-
* only control calls and state events
|
|
7
|
+
* retries and crash recovery. **No media ever crosses the JS bridge**,
|
|
8
|
+
* only control calls and state events, so recording quality is identical
|
|
9
9
|
* to a fully native app.
|
|
10
10
|
*
|
|
11
11
|
* import CrowdPlay, { CrowdPlayConsentScreen, CrowdPlayVideoView } from 'crowdplaysdk';
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* await CrowdPlay.leave(); // recording stops; uploads continue automatically
|
|
18
18
|
*
|
|
19
19
|
* Consent is enforced twice: join() throws here without a grant, and the
|
|
20
|
-
* native layer requires the consent record structurally
|
|
20
|
+
* native layer requires the consent record structurally; there is no code
|
|
21
21
|
* path that records without it.
|
|
22
22
|
*/
|
|
23
23
|
|
|
@@ -45,7 +45,7 @@ export interface CrowdPlayConfig {
|
|
|
45
45
|
serverUrl: string;
|
|
46
46
|
/** From the CrowdPlay dashboard. Also your data-access credential. */
|
|
47
47
|
appKey: string;
|
|
48
|
-
/** AUDIO-ONLY app: the camera is never touched
|
|
48
|
+
/** AUDIO-ONLY app: the camera is never touched: no camera permission,
|
|
49
49
|
* no video captured or uploaded, voice-only calls. The audio path is
|
|
50
50
|
* identical to an A/V app. Requires engine 0.3.0+. Default false. */
|
|
51
51
|
audioOnly?: boolean;
|
|
@@ -54,14 +54,22 @@ export interface CrowdPlayConfig {
|
|
|
54
54
|
videoUploadsOnCellular?: boolean;
|
|
55
55
|
/** Upload segments while the call records, so the post-call wait is
|
|
56
56
|
* roughly the final segment + manifests. Mid-call transfers run one at
|
|
57
|
-
* a time and pause automatically when call quality drops
|
|
57
|
+
* a time and pause automatically when call quality drops; the call
|
|
58
58
|
* always wins the uplink. Default true. */
|
|
59
59
|
uploadDuringCall?: boolean;
|
|
60
|
+
/** What happens to OTHER apps' audio (Apple Music, Spotify, podcasts)
|
|
61
|
+
* while a call is live. 'interrupt' (default) pauses them at join, the
|
|
62
|
+
* classic call behaviour. 'mix' keeps them playing alongside the call
|
|
63
|
+
* and the recording, for shared-listening and DJ apps; 'mixDucked' also
|
|
64
|
+
* lowers them for the whole call. Music stays on the device and never
|
|
65
|
+
* rides the call; listeners must wear headphones or it lands in the
|
|
66
|
+
* recording. Requires engine 0.4.0+. */
|
|
67
|
+
otherAudio?: 'interrupt' | 'mix' | 'mixDucked';
|
|
60
68
|
callQuality?: CallQuality;
|
|
61
69
|
}
|
|
62
70
|
|
|
63
|
-
/** Proof the participant agreed. Produce it with CrowdPlayConsentScreen, or
|
|
64
|
-
* if you render your own consent UI
|
|
71
|
+
/** Proof the participant agreed. Produce it with CrowdPlayConsentScreen, or,
|
|
72
|
+
* if you render your own consent UI, call CrowdPlay.consentText() to show the
|
|
65
73
|
* REQUIRED wording and construct the grant at the moment of agreement. */
|
|
66
74
|
export interface ConsentGrant {
|
|
67
75
|
/** Date.now() at the moment the participant agreed. */
|
|
@@ -76,6 +84,18 @@ export interface ConsentGrant {
|
|
|
76
84
|
* apply to the whole room. The app can never change the provider, the
|
|
77
85
|
* model, or the API key from here.
|
|
78
86
|
*/
|
|
87
|
+
/** A tool the app offers the voice AI for one session (D-103). Requires
|
|
88
|
+
* the app's dashboard config to use the GPT-Live provider with an AI brain
|
|
89
|
+
* (delegation) enabled; ignored otherwise. */
|
|
90
|
+
export interface AgentTool {
|
|
91
|
+
/** Letters, digits, underscore, dash; max 64 chars. */
|
|
92
|
+
name: string;
|
|
93
|
+
/** What the tool does and when to use it, for the model. */
|
|
94
|
+
description: string;
|
|
95
|
+
/** JSON-schema object describing the arguments. Omit for none. */
|
|
96
|
+
parameters?: Record<string, unknown>;
|
|
97
|
+
}
|
|
98
|
+
|
|
79
99
|
export interface AgentOptions {
|
|
80
100
|
/** A voice of the app's configured provider. Gemini: Charon, Aoede,
|
|
81
101
|
* Fenrir, Kore, Puck. OpenAI: marin, cedar, alloy, ash, ballad, coral,
|
|
@@ -89,6 +109,9 @@ export interface AgentOptions {
|
|
|
89
109
|
* keep a few sentences per user in your own storage and pass them
|
|
90
110
|
* here. Max 4000 chars. */
|
|
91
111
|
context?: string;
|
|
112
|
+
/** Tools the AI may call this session (D-103). Handle calls with
|
|
113
|
+
* CrowdPlay.onAgentToolCall(). Max 16. */
|
|
114
|
+
tools?: AgentTool[];
|
|
92
115
|
}
|
|
93
116
|
|
|
94
117
|
export type Phase = 'idle' | 'connecting' | 'connected' | 'failed';
|
|
@@ -100,10 +123,10 @@ export interface RecordingSnapshot {
|
|
|
100
123
|
droppedSamples: number;
|
|
101
124
|
/** Live input level. Speech sits around −25…−45 dBFS. */
|
|
102
125
|
inputLevelDbfs: number;
|
|
103
|
-
/** True after 10 s of silence while unmuted
|
|
126
|
+
/** True after 10 s of silence while unmuted: a dead/disconnected mic.
|
|
104
127
|
* Surface this loudly; it is fixable mid-call. */
|
|
105
128
|
inputSilent: boolean;
|
|
106
|
-
/** Samples at digital full scale
|
|
129
|
+
/** Samples at digital full scale: a too-hot mic, irreversibly clipped. */
|
|
107
130
|
fullScaleSamples: number;
|
|
108
131
|
}
|
|
109
132
|
|
|
@@ -141,7 +164,7 @@ export interface CrowdPlayEvents {
|
|
|
141
164
|
/** Connection lifecycle. `error` is set when phase === 'failed'. */
|
|
142
165
|
phase: { phase: Phase; error?: string };
|
|
143
166
|
/** Recording state. `error` non-null means the session is NOT being
|
|
144
|
-
* captured
|
|
167
|
+
* captured; show it prominently and offer retryRecording(). */
|
|
145
168
|
recording: { isRecording: boolean; error?: string };
|
|
146
169
|
/** Upload progress for every session with outstanding or recent work. */
|
|
147
170
|
uploads: { sessions: UploadProgress[]; onWifi: boolean };
|
|
@@ -156,6 +179,9 @@ export interface CrowdPlayEvents {
|
|
|
156
179
|
* "iPhone speaker"), connected candidates, and whether headphones are
|
|
157
180
|
* attached. Drive the call screen's output menu from this (D-079). */
|
|
158
181
|
audioRoute: { currentOutputName: string; detectedOutputs: string[]; headphonesConnected: boolean };
|
|
182
|
+
/** The voice AI asked the app to run a tool (D-103). Prefer
|
|
183
|
+
* CrowdPlay.onAgentToolCall(), which answers for you. */
|
|
184
|
+
agentToolCall: { callId: string; name: string; argumentsJSON: string };
|
|
159
185
|
}
|
|
160
186
|
|
|
161
187
|
export type CrowdPlayEventName = keyof CrowdPlayEvents;
|
|
@@ -169,6 +195,8 @@ interface NativeCrowdPlay {
|
|
|
169
195
|
consentText(): Promise<string>;
|
|
170
196
|
join(displayName: string, roomCode: string, consentGrantedAtMs: number,
|
|
171
197
|
agent: Record<string, string> | null): Promise<void>;
|
|
198
|
+
respondToAgentToolCall(callId: string, output: string): void;
|
|
199
|
+
updateAgentContext(text: string): void;
|
|
172
200
|
leave(): Promise<void>;
|
|
173
201
|
setMicMuted(muted: boolean): Promise<void>;
|
|
174
202
|
setCameraEnabled(enabled: boolean): Promise<void>;
|
|
@@ -189,8 +217,8 @@ function native(): NativeCrowdPlay {
|
|
|
189
217
|
"crowdplaysdk: native module not found. iOS setup: add the CrowdPlaySDK " +
|
|
190
218
|
"Swift package (https://github.com/symbiateam/crowdplaysdk) to your Xcode " +
|
|
191
219
|
"project and add the two bridge files from node_modules/crowdplaysdk/ios " +
|
|
192
|
-
"to your app target
|
|
193
|
-
(Platform.OS !== 'ios' ? `(platform '${Platform.OS}' is not supported yet
|
|
220
|
+
"to your app target; see the package README. " +
|
|
221
|
+
(Platform.OS !== 'ios' ? `(platform '${Platform.OS}' is not supported yet; iOS only.)` : '')
|
|
194
222
|
);
|
|
195
223
|
}
|
|
196
224
|
return module;
|
|
@@ -240,7 +268,7 @@ const CrowdPlay = {
|
|
|
240
268
|
* Join a room. Recording starts automatically on join and stops on
|
|
241
269
|
* leave(). Rejects if the connection fails.
|
|
242
270
|
*
|
|
243
|
-
* `consent` is REQUIRED
|
|
271
|
+
* `consent` is REQUIRED; produce it with <CrowdPlayConsentScreen> or from
|
|
244
272
|
* your own UI at the moment of agreement. There is no way to record
|
|
245
273
|
* without it, here or natively.
|
|
246
274
|
*/
|
|
@@ -271,6 +299,11 @@ const CrowdPlay = {
|
|
|
271
299
|
put('voice', agent.voice, 60);
|
|
272
300
|
put('systemPrompt', agent.systemPrompt, 8192);
|
|
273
301
|
put('context', agent.context, 4000);
|
|
302
|
+
// Tools cross the bridge as one JSON string; the native layer and the
|
|
303
|
+
// server both re-validate (D-103).
|
|
304
|
+
if (Array.isArray(agent.tools) && agent.tools.length) {
|
|
305
|
+
put('toolsJSON', JSON.stringify(agent.tools.slice(0, 16)), 8192);
|
|
306
|
+
}
|
|
274
307
|
}
|
|
275
308
|
return native().join(displayName.trim(), roomCode, consent.grantedAtMs, agentFields);
|
|
276
309
|
},
|
|
@@ -322,6 +355,41 @@ const CrowdPlay = {
|
|
|
322
355
|
return native().doctor();
|
|
323
356
|
},
|
|
324
357
|
|
|
358
|
+
/** Handle the voice AI's tool calls (D-103). The handler gets the tool
|
|
359
|
+
* name and its arguments (parsed JSON object) and returns the result as
|
|
360
|
+
* an object or JSON string; return null to decline on this device
|
|
361
|
+
* (another participant's device may answer; first answer wins). Ordinary
|
|
362
|
+
* app code, no AI here. The AI waits about 12 seconds per call. Returns
|
|
363
|
+
* a subscription; call .remove(). */
|
|
364
|
+
onAgentToolCall(
|
|
365
|
+
handler: (name: string, args: Record<string, unknown>) =>
|
|
366
|
+
Promise<Record<string, unknown> | string | null> | Record<string, unknown> | string | null
|
|
367
|
+
): { remove(): void } {
|
|
368
|
+
const subscription = events().addListener(
|
|
369
|
+
'crowdplay:agentToolCall',
|
|
370
|
+
(payload: { callId: string; name: string; argumentsJSON: string }) => {
|
|
371
|
+
void (async () => {
|
|
372
|
+
let args: Record<string, unknown> = {};
|
|
373
|
+
try { args = JSON.parse(payload.argumentsJSON); } catch { /* {} */ }
|
|
374
|
+
const result = await handler(payload.name, args);
|
|
375
|
+
if (result === null || result === undefined) return;
|
|
376
|
+
const output = typeof result === 'string' ? result : JSON.stringify(result);
|
|
377
|
+
native().respondToAgentToolCall(payload.callId, output);
|
|
378
|
+
})();
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
return { remove: () => subscription.remove() };
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
/** Tell the AI what just happened in the app, mid-session ("Night ended.
|
|
385
|
+
* Sam was eliminated."). Keep it to a sentence or two; long pushes are
|
|
386
|
+
* truncated. GPT-Live provider only; other providers ignore it. */
|
|
387
|
+
updateAgentContext(text: string): void {
|
|
388
|
+
if (typeof text === 'string' && text.trim()) {
|
|
389
|
+
native().updateAgentContext(text.trim());
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
|
|
325
393
|
/** Subscribe to state events. Returns a subscription; call .remove(). */
|
|
326
394
|
addListener<E extends CrowdPlayEventName>(
|
|
327
395
|
event: E,
|