crowdplaysdk 0.6.1 → 0.6.3

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 CHANGED
@@ -97,7 +97,7 @@ Bare React Native (no Expo):
97
97
  npm install crowdplaysdk
98
98
  cd ios && pod install
99
99
  gem install xcodeproj --user-install # one time
100
- ruby node_modules/crowdplaysdk/ios/wire.rb <YourProjectName>
100
+ ruby ../node_modules/crowdplaysdk/ios/wire.rb <YourProjectName> # from ios/
101
101
  ```
102
102
 
103
103
  `wire.rb` is idempotent. It adds the compiled CrowdPlaySDK Swift package,
@@ -107,6 +107,18 @@ keeps explicit Swift modules on for the app target (React Native 0.8x turns
107
107
  them off project-wide on Xcode 26, which breaks importing the compiled
108
108
  engine). There is no CocoaPod of our own, so your Podfile stays untouched.
109
109
 
110
+ **Updating.** `npm install crowdplaysdk@latest`, then wire again so the
111
+ engine moves with the bridge: Expo, `npx expo prebuild` (or a build that
112
+ runs prebuild; an `ios/` folder committed to git is not re-wired by
113
+ `eas build`, so run prebuild yourself and commit the result); bare React
114
+ Native, re-run `wire.rb`. Since 0.6.3 that also re-pins the engine to the
115
+ version this package ships with. 0.6.2 and older only added the engine
116
+ the first time, so upgrading in place kept the old engine. Before the
117
+ next local build, clean (Xcode: Product > Clean Build Folder): an
118
+ incremental build can reuse Xcode's cached copy of the previous engine's
119
+ interface and fail on new symbols such as `storageWarning`. EAS Build
120
+ starts clean every time.
121
+
110
122
  **2. Declare permissions in Info.plist** (bare React Native only; the Expo
111
123
  plugin does this for you).
112
124
 
@@ -165,6 +177,7 @@ CrowdPlay.addListener('phase', …) // idle / connecting / connected / f
165
177
  CrowdPlay.addListener('participants', …) // who to render tiles for
166
178
  CrowdPlay.addListener('uploads', …) // per-session progress
167
179
  CrowdPlay.addListener('warning', …) // micSilent / clipping / crossTalk: show these
180
+ CrowdPlay.addListener('storageWarning', …) // optional: message set = storage low (audio only or stopped)
168
181
  CrowdPlay.addListener('recording', …) // error means NOT capturing; offer retryRecording()
169
182
  CrowdPlay.addListener('audioRoute', …) // current output device, for the output menu
170
183
  CrowdPlay.setAudioOutput('speaker') // or 'automatic' (the connected device)
@@ -223,29 +236,39 @@ backend reachability and your app key. Every failing check names its own
223
236
  fix. Then record a one-minute session on a real device and confirm the
224
237
  upload finishes.
225
238
 
226
- ## Accessing your recordings
239
+ ## Checking your sessions
227
240
 
228
- Your app key is also your data-access credential: plain HTTPS, no AWS
241
+ Your app key also lists the sessions it recorded, over plain HTTPS: no AWS
229
242
  account, no SDK required.
230
243
 
231
244
  ```bash
232
- BASE=https://dashboard.crowdplay.ai
233
-
234
- curl -H "x-liva-key: $APP_KEY" $BASE/sessions
235
- curl -H "x-liva-key: $APP_KEY" $BASE/sessions/<sessionId>/files
245
+ curl -H "x-liva-key: $APP_KEY" https://dashboard.crowdplay.ai/sessions
236
246
  ```
237
247
 
238
- `deliverable/` holds what you actually use: one clean audio file per
239
- person, aligned so they play together with no offset, plus a mixed version
240
- and a quality report. You can only reach sessions recorded with your key.
248
+ Each row carries the session id, its state (`uploading`, `ok`, `partial`
249
+ or `failed`), the participants, participant minutes and any quality
250
+ warnings. You only see sessions recorded with your key, and the dashboard
251
+ shows the same list. Use it to confirm that sessions upload and render.
252
+
253
+ The recordings themselves stay in CrowdPlay's storage. There is no
254
+ download endpoint for builders: `GET /sessions/<sessionId>/files` answers
255
+ 403 to an app key, and the dashboard does not offer downloads either.
241
256
 
242
257
  ## Things to know
243
258
 
244
259
  - **Don't force-quit the app** after a session: iOS pauses background
245
260
  transfers until next launch. They resume automatically and nothing is
246
261
  lost.
247
- - Sessions are roughly 2 GB per participant-hour, dominated by video. The
248
- SDK refuses to join below 8 GB free disk.
262
+ - With video, a session uses about 6.8 GB per participant-hour on the phone
263
+ (1080p HEVC at 14 Mbps plus 24-bit audio); audio-only is about 0.5 GB.
264
+ Files are deleted as each one uploads. Video waits for WiFi, so on
265
+ cellular it stays on the phone until then. The SDK never fills the phone
266
+ (0.6.2+, engine 0.5.4): under 2 GB free the recording continues audio
267
+ only, under 500 MB it stops, and the call itself always continues. This
268
+ happens silently: the SDK shows nothing on screen. The `storageWarning`
269
+ event says when it happens, if you want to ask the user to free up space.
270
+ `doctor()` also checks free disk (8 GiB by default); joining never
271
+ refuses for storage.
249
272
  - Participants should wear **wired or closed-back headphones**. A
250
273
  loudspeaker pipes the far end into the local mic and contaminates the
251
274
  track; the SDK surfaces this live through the `warning` event.
@@ -63,6 +63,15 @@ final class CrowdPlayRNCore {
63
63
  ])
64
64
  }.store(in: &cancellables)
65
65
 
66
+ // Storage (D-148): its own event, not a 'warning' kind, so an app
67
+ // that renders every warning does not start showing it. Sent on
68
+ // every change, since the text moves from audio-only to stopped.
69
+ engine.$storageWarning
70
+ .removeDuplicates()
71
+ .sink { [weak self] message in
72
+ self?.send("crowdplay:storageWarning", ["message": message as Any])
73
+ }.store(in: &cancellables)
74
+
66
75
  engine.$uploads
67
76
  .combineLatest(engine.$uploadsOnWiFi)
68
77
  .sink { [weak self] uploads, onWifi in
@@ -231,7 +240,7 @@ public final class CrowdPlayRNModule: RCTEventEmitter {
231
240
  public override static func requiresMainQueueSetup() -> Bool { true }
232
241
 
233
242
  public override func supportedEvents() -> [String]! {
234
- ["crowdplay:phase", "crowdplay:recording", "crowdplay:uploads", "crowdplay:participants", "crowdplay:warning", "crowdplay:audioRoute", "crowdplay:voiceActivity", "crowdplay:caption", "crowdplay:agentToolCall"]
243
+ ["crowdplay:phase", "crowdplay:recording", "crowdplay:uploads", "crowdplay:participants", "crowdplay:warning", "crowdplay:storageWarning", "crowdplay:audioRoute", "crowdplay:voiceActivity", "crowdplay:caption", "crowdplay:agentToolCall"]
235
244
  }
236
245
 
237
246
  @objc public func startVoiceActivityUpdates() {
package/ios/wire.rb CHANGED
@@ -6,7 +6,8 @@
6
6
  # ruby node_modules/crowdplaysdk/ios/wire.rb <YourProjectName>
7
7
  #
8
8
  # What it does (idempotent; safe to re-run):
9
- # 1. adds the CrowdPlaySDK Swift package (compiled engine) to the project
9
+ # 1. adds the CrowdPlaySDK Swift package (compiled engine) to the project,
10
+ # or re-pins an existing one to SDK_VERSION (an upgrade)
10
11
  # 2. copies the three bridge files into your app group + target
11
12
  # 3. creates/points the Swift bridging header at the React headers, raises
12
13
  # the app target to iOS 17 and keeps explicit Swift modules ON for it
@@ -30,7 +31,8 @@ SDK_URL = 'https://github.com/symbiateam/crowdplaysdk'
30
31
  # D-103 the bridge calls 0.5.0 APIs, and npm 0.5.0 to 0.5.2 shipped with
31
32
  # this still at 0.4.0, which broke every install (D-121). publish_rn.sh now
32
33
  # compiles the bridge against exactly this release before publishing.
33
- SDK_VERSION = '0.5.3'
34
+ # 0.5.4: the storage guard and engine.storageWarning (D-148).
35
+ SDK_VERSION = '0.5.4'
34
36
  BRIDGE_FILES = ['CrowdPlayRNModule.swift', 'CrowdPlayRNVideoView.swift', 'CrowdPlayReactNative.m'].freeze
35
37
 
36
38
  project_name = ARGV[0] or abort 'usage: ruby wire.rb <YourProjectName>'
@@ -43,17 +45,30 @@ target = project.targets.find { |t| t.name == project_name } ||
43
45
  project.targets.find { |t| t.product_type == 'com.apple.product-type.application' }
44
46
  abort 'no application target found' unless target
45
47
 
46
- # 1. CrowdPlaySDK Swift package (skip when already present).
47
- unless project.root_object.package_references.any? { |r| r.respond_to?(:repositoryURL) && r.repositoryURL == SDK_URL }
48
+ # 1. CrowdPlaySDK Swift package, pinned to exactly SDK_VERSION. An existing
49
+ # reference is re-pinned, never skipped: step 2 always replaces the bridge
50
+ # files, and a new bridge against the old engine does not compile (0.6.2
51
+ # upgraded in place kept engine 0.5.3 and failed on storageWarning, D-151).
52
+ requirement = { 'kind' => 'exactVersion', 'version' => SDK_VERSION }
53
+ existing = project.root_object.package_references.select { |r| r.respond_to?(:repositoryURL) && r.repositoryURL == SDK_URL }
54
+ if existing.empty?
48
55
  pkg = project.new(Xcodeproj::Project::Object::XCRemoteSwiftPackageReference)
49
56
  pkg.repositoryURL = SDK_URL
50
- pkg.requirement = { 'kind' => 'exactVersion', 'version' => SDK_VERSION }
57
+ pkg.requirement = requirement
51
58
  project.root_object.package_references << pkg
52
59
  dep = project.new(Xcodeproj::Project::Object::XCSwiftPackageProductDependency)
53
60
  dep.package = pkg
54
61
  dep.product_name = 'CrowdPlaySDK'
55
62
  target.package_product_dependencies << dep
56
63
  puts "+ CrowdPlaySDK package #{SDK_VERSION}"
64
+ else
65
+ existing.each do |pkg|
66
+ next if pkg.requirement == requirement
67
+
68
+ was = (pkg.requirement || {})['version'] || (pkg.requirement || {})['minimumVersion'] || 'unpinned'
69
+ pkg.requirement = requirement
70
+ puts "+ CrowdPlaySDK package #{was} -> #{SDK_VERSION}"
71
+ end
57
72
  end
58
73
 
59
74
  # 2. Bridge files into the app group + target.
package/lib/index.d.ts CHANGED
@@ -62,7 +62,7 @@ export interface CrowdPlayConfig {
62
62
  }
63
63
  /** The CrowdPlay Terms of Use and Privacy Notice every participant accepts,
64
64
  * as a PDF. CrowdPlayConsentScreen links here; a custom consent UI must too. */
65
- export declare const CROWDPLAY_TERMS_URL = "https://dashboard.crowdplay.ai/legal/terms.pdf";
65
+ export declare const CROWDPLAY_TERMS_URL = "https://www.crowdplay.ai/legal/consumer-terms";
66
66
  /** Proof the participant agreed. Produce it with CrowdPlayConsentScreen, or,
67
67
  * if you render your own consent UI, call CrowdPlay.consentText() to show the
68
68
  * REQUIRED wording and construct the grant at the moment of agreement. */
@@ -177,6 +177,14 @@ export interface CrowdPlayEvents {
177
177
  active: boolean;
178
178
  message: string;
179
179
  };
180
+ /** This iPhone is running out of storage (D-148): under 2 GB free the
181
+ * recording continues audio only, under 500 MB it stops; the call always
182
+ * continues. `message` is null when storage is fine. The SDK shows
183
+ * nothing on screen; listening is optional. Separate from `warning` so
184
+ * apps that render every warning do not start showing it. */
185
+ storageWarning: {
186
+ message: string | null;
187
+ };
180
188
  /** The audio output route: current device by name ("AirPods Pro",
181
189
  * "iPhone speaker"), connected candidates, and whether headphones are
182
190
  * attached. Drive the call screen's output menu from this (D-079). */
package/lib/index.js CHANGED
@@ -26,7 +26,7 @@ exports.useLatestCaption = exports.useVoiceActivity = exports.VoiceReactive = ex
26
26
  const react_native_1 = require("react-native");
27
27
  /** The CrowdPlay Terms of Use and Privacy Notice every participant accepts,
28
28
  * as a PDF. CrowdPlayConsentScreen links here; a custom consent UI must too. */
29
- exports.CROWDPLAY_TERMS_URL = 'https://dashboard.crowdplay.ai/legal/terms.pdf';
29
+ exports.CROWDPLAY_TERMS_URL = 'https://www.crowdplay.ai/legal/consumer-terms';
30
30
  function native() {
31
31
  const module = react_native_1.NativeModules.CrowdPlayReactNative;
32
32
  if (!module) {
package/llms.txt CHANGED
@@ -81,6 +81,14 @@ EXPO (managed, prebuild or EAS Build; app builders such as X1):
81
81
  Xcode project. Do not add those Info.plist keys or any native code
82
82
  yourself. Expo SDK 50 or newer.
83
83
  3. Rebuild: npx expo prebuild, a development build, or eas build.
84
+ UPDATING the package later: npx expo prebuild again so the plugin
85
+ re-pins the engine to the version the package ships with (0.6.3+). An
86
+ ios/ folder committed to git is not re-wired by eas build on its own:
87
+ run prebuild and commit the result. Then clean before a LOCAL build
88
+ (Xcode: Product > Clean Build Folder); an incremental build can reuse
89
+ the previous engine's cached interface and fail with "has no member
90
+ '$storageWarning'". That error after an update means "clean", never
91
+ "edit the bridge". EAS Build starts clean.
84
92
  4. Expo Go and any preview that never ran the plugin cannot load native
85
93
  modules: there the consent text, the call and recording fail with a
86
94
  clear error and the video view shows a placeholder. That is EXPECTED;
@@ -92,7 +100,8 @@ BARE REACT NATIVE (no Expo):
92
100
  2. cd ios && pod install (installs RN's own pods; CrowdPlay has none)
93
101
  3. gem install xcodeproj --user-install (one-time; wire.rb needs it
94
102
  and says so itself if missing)
95
- ruby node_modules/crowdplaysdk/ios/wire.rb <YourProjectName>
103
+ ruby ../node_modules/crowdplaysdk/ios/wire.rb <YourProjectName>
104
+ (from ios/, where step 2 left you; wire.rb must run there)
96
105
  Idempotent. It: adds the CrowdPlaySDK binary Swift package to the Xcode
97
106
  project, copies the three native bridge files into the app target,
98
107
  creates/points the Swift bridging header at the React headers, raises
@@ -100,7 +109,10 @@ BARE REACT NATIVE (no Expo):
100
109
  sets SWIFT_ENABLE_EXPLICIT_MODULES = YES on the app target (React Native
101
110
  0.8x sets it to NO project-wide on Xcode 26, and with implicit modules
102
111
  the compiled engine's interface cannot be imported). Rerun it any time;
103
- it never duplicates. Never undo that setting to fix an unrelated error.
112
+ it never duplicates, and after updating the package it re-pins the
113
+ engine to the version the package ships with (0.6.3+); then Product >
114
+ Clean Build Folder before building. Never undo that setting to fix an
115
+ unrelated error.
104
116
  4. Info.plist: NSMicrophoneUsageDescription, NSCameraUsageDescription,
105
117
  UIBackgroundModes = [audio]. A missing usage string makes iOS kill
106
118
  the app at first mic/camera access (uncatchable).
@@ -324,22 +336,40 @@ Events (CrowdPlay.addListener):
324
336
  // mic; crossTalk = loudspeaker leaking the far end into
325
337
  // this mic (wear headphones); micPolicy = unexpected
326
338
  // input device.
339
+ 'storageWarning' { message: string | null }
340
+ // 0.6.2+: non-null = the phone is low on storage: under
341
+ // 2 GB free the recording is audio only, under 500 MB it
342
+ // stopped. The call continues. The SDK shows nothing
343
+ // itself; listening is optional. Kept out of 'warning' so
344
+ // apps that render every warning do not show it.
327
345
 
328
- ## Accessing recordings (no AWS, no SDK, plain HTTPS)
346
+ ## Checking sessions (no AWS, no SDK, plain HTTPS)
329
347
 
330
- The app key doubles as the data credential:
348
+ The app key lists the sessions it recorded, metadata only:
331
349
  GET https://dashboard.crowdplay.ai/sessions (x-liva-key header)
332
- GET https://dashboard.crowdplay.ai/sessions/<id>/files (x-liva-key header)
333
- -> [{path, bytes, url}] // url = 1-hour presigned download
334
- deliverable/ holds the usable output: speaker-<name>.wav per participant
335
- (drift-corrected; all files play together with NO offset),
336
- conversation.wav, qc.json. raw/ is the untouched per-device capture.
337
- Humans use https://dashboard.crowdplay.ai directly (sign in, download).
350
+ -> [{sessionId, state ("uploading"|"ok"|"partial"|"failed"),
351
+ participants, participantMinutes, warnings, warningNotes,
352
+ uploadedAt}]
353
+ A key sees only its own app's sessions; `state: "ok"` proves the
354
+ integration works end to end. The dashboard shows the same list.
355
+
356
+ Recordings NEVER leave CrowdPlay's storage. There is no download endpoint
357
+ for builders: GET /sessions/<id>/files answers 403 to an app key and to
358
+ dashboard accounts, and the dashboard has no download button. Do NOT
359
+ build any feature that fetches, plays, shows, or analyses a recording
360
+ after the session; the app has no way to obtain one. During the session
361
+ the app can use the `caption` event (voice-AI transcript lines) and
362
+ `snapshot()` (capture health).
338
363
 
339
364
  ## Facts, limits, troubleshooting
340
365
 
341
- - iOS 17+, React Native >= 0.71. Storage ~2 GB per participant-hour;
342
- join refuses below 8 GB free.
366
+ - iOS 17+, React Native >= 0.71. Storage ~6.8 GB per participant-hour with
367
+ video, ~0.5 GB audio-only (deleted as files upload; video waits for WiFi,
368
+ so on cellular it stays on the phone until then). The SDK never fills the
369
+ phone (0.6.2+): under 2 GB free the recording continues audio only, under
370
+ 500 MB it stops, the call always continues, silently ('storageWarning'
371
+ says so if you listen). doctor() also checks free disk (8 GiB default);
372
+ join never refuses for storage.
343
373
  - Keep the screen awake during sessions (e.g. react-native-keep-awake or
344
374
  the idle-timer API); a locked phone suspends capture. Force-quitting
345
375
  pauses uploads until next launch (nothing lost).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crowdplaysdk",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "CrowdPlay lossless conversation capture for React Native (iOS). Studio-grade per-participant recording during live video calls, delivered to CrowdPlay automatically.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
package/src/index.ts CHANGED
@@ -71,7 +71,7 @@ export interface CrowdPlayConfig {
71
71
 
72
72
  /** The CrowdPlay Terms of Use and Privacy Notice every participant accepts,
73
73
  * as a PDF. CrowdPlayConsentScreen links here; a custom consent UI must too. */
74
- export const CROWDPLAY_TERMS_URL = 'https://dashboard.crowdplay.ai/legal/terms.pdf';
74
+ export const CROWDPLAY_TERMS_URL = 'https://www.crowdplay.ai/legal/consumer-terms';
75
75
 
76
76
  /** Proof the participant agreed. Produce it with CrowdPlayConsentScreen, or,
77
77
  * if you render your own consent UI, call CrowdPlay.consentText() to show the
@@ -180,6 +180,12 @@ export interface CrowdPlayEvents {
180
180
  * micSilent (dead mic), clipping (too-hot mic), crossTalk (loudspeaker
181
181
  * leaking the far end into this mic), micPolicy (unexpected input). */
182
182
  warning: { kind: 'audioStalled' | 'micSilent' | 'clipping' | 'crossTalk' | 'micPolicy'; active: boolean; message: string };
183
+ /** This iPhone is running out of storage (D-148): under 2 GB free the
184
+ * recording continues audio only, under 500 MB it stops; the call always
185
+ * continues. `message` is null when storage is fine. The SDK shows
186
+ * nothing on screen; listening is optional. Separate from `warning` so
187
+ * apps that render every warning do not start showing it. */
188
+ storageWarning: { message: string | null };
183
189
  /** The audio output route: current device by name ("AirPods Pro",
184
190
  * "iPhone speaker"), connected candidates, and whether headphones are
185
191
  * attached. Drive the call screen's output menu from this (D-079). */