crowdplaysdk 0.4.1 → 0.4.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/Examples/App.tsx CHANGED
@@ -91,7 +91,7 @@ export default function App(): React.JSX.Element {
91
91
 
92
92
  return (
93
93
  <SafeAreaView style={styles.screen}>
94
- {/* The session is NOT being recorded when this is non-null always show it. */}
94
+ {/* The session is NOT being recorded when this is non-null; always show it. */}
95
95
  {recordingError ? (
96
96
  <View style={styles.banner}>
97
97
  <Text style={styles.bannerText}>NOT RECORDING: {recordingError}</Text>
@@ -121,7 +121,7 @@ export default function App(): React.JSX.Element {
121
121
  />
122
122
  </View>
123
123
 
124
- {/* Audio output menu (D-079): standard in every build shows the
124
+ {/* Audio output menu (D-079): standard in every build; shows the
125
125
  device by name and lets the user flip to Speaker and back. */}
126
126
  <View style={styles.controls}>
127
127
  <Text>{route?.currentOutputName ?? 'audio output'}</Text>
package/README.md CHANGED
@@ -1,150 +1,232 @@
1
1
  # crowdplaysdk
2
2
 
3
- CrowdPlay's lossless conversation capture for **React Native (iOS)**. Your app
4
- hosts a live video call; every participant's audio (**48 kHz / 24-bit
5
- unprocessed WAV**) and video (**1080p30 HEVC**) is recorded **locally, at
6
- full quality**, clock-aligned across participants, and delivered to CrowdPlay
7
- automatically uploads retry, resume, and survive crashes.
3
+ CrowdPlaySDK adds live calls and high-quality conversation capture to React
4
+ Native iOS apps. It is the same compiled engine as the native CrowdPlaySDK
5
+ Swift package, driven from JavaScript with control calls and state events.
6
+ No media ever crosses the JS bridge, so recording quality is identical to a
7
+ fully native app.
8
8
 
9
- **No media ever crosses the JS bridge.** The engine is the same compiled
10
- native core (CrowdPlaySDK) our reference app runs; React Native drives it with
11
- control calls and receives state events. Recording quality is identical to
12
- a fully native app.
9
+ ## What the SDK handles
13
10
 
14
- ## Non-technical? Paste this to your AI agent
11
+ * Joining and leaving calls
12
+ * Microphone and camera access
13
+ * Participant audio and video
14
+ * Mute, camera, and audio-output controls
15
+ * User consent before capture begins
16
+ * Per-participant data capture and synchronization
17
+ * Uploads, retries, and recovery
18
+ * Session lifecycle and common interruptions
15
19
 
20
+ Once a user consents and joins a call, capture and delivery happen
21
+ automatically.
22
+
23
+ ## What you build
24
+
25
+ You build the actual product and user experience:
26
+
27
+ * Your app's screens, navigation, and design
28
+ * What users do in your app
29
+ * When and why a call happens
30
+ * The UI around the call
31
+ * Your own accounts, profiles, content, or other product features
32
+
33
+ For example, if you're building a tutoring app, **you build the tutoring
34
+ experience**. CrowdPlaySDK provides the call and capture infrastructure
35
+ underneath it.
36
+
37
+ Your integration is essentially:
38
+
39
+ **Your app → consent → join call → CrowdPlay handles the rest**
40
+
41
+ ## Three kinds of calls
42
+
43
+ You pick one when you create your app on the dashboard, and you can change
44
+ it later:
45
+
46
+ * **Video + audio.** Face-to-face calls between people. Both are recorded.
47
+ * **Audio-only.** Voice calls between people, no camera anywhere in the app.
48
+ * **Voice AI.** Your users talk with a live AI voice instead of another
49
+ person. Audio only, no camera.
50
+
51
+ ## Getting started
52
+
53
+ Sign in at **https://dashboard.crowdplay.ai**, create your app, and press
54
+ **Copy agent prompt**. The prompt is pre-filled with your key and tailored
55
+ to the tool you build with, so paste it into your coding agent and it will
56
+ build the app. Your dashboard then tracks setup and shows your recordings.
57
+
58
+ **Building in Swift?** Use the CrowdPlaySDK Swift package instead:
59
+ https://github.com/symbiateam/crowdplaysdk
60
+
61
+ ## Setup (iOS)
62
+
63
+ Requirements: iOS 17+, React Native 0.71 or newer, a real device to test
64
+ (the Simulator has no camera or mic), and an **app key** from CrowdPlay.
65
+ iOS only for now; calls to the module on other platforms throw with a
66
+ clear message.
67
+
68
+ **1. Install the package and wire the engine.**
69
+
70
+ ```bash
71
+ npm install crowdplaysdk
72
+ cd ios && pod install
73
+ gem install xcodeproj --user-install # one time
74
+ ruby node_modules/crowdplaysdk/ios/wire.rb <YourProjectName>
75
+ ```
76
+
77
+ `wire.rb` is idempotent. It adds the compiled CrowdPlaySDK Swift package,
78
+ copies the three bridge files into your app target, and points the Swift
79
+ bridging header at the React headers. There is no CocoaPod of our own, so
80
+ your Podfile stays untouched.
81
+
82
+ **2. Declare permissions in Info.plist.**
83
+
84
+ ```
85
+ NSMicrophoneUsageDescription = "Records your microphone during sessions."
86
+ NSCameraUsageDescription = "Records your camera during sessions."
87
+ UIBackgroundModes = [audio]
16
88
  ```
17
- I'm building this app: <YOUR APP IDEA — e.g., a tutoring app where
18
- students and tutors meet over video>
19
-
20
- Build it as a React Native iOS app. For the video-call feature, use the
21
- crowdplaysdk package.
22
-
23
- What the app is about — read carefully:
24
- - The app's name, screens, and flows are about MY IDEA above. The SDK
25
- records calls in the background for CrowdPlay; recording is NOT the
26
- app's purpose and must not be presented as it. It appears in the UI in
27
- the SDK's consent screen before joining a call,
28
- a small REC indicator during the call, join/leave, and a small
29
- audio-output menu.
30
- - Keep the SDK integration minimal: consent → join/leave → call screen.
31
- The call screen MUST include a small audio-output menu showing the
32
- current output device by name (from the 'audioRoute' event) with a
33
- switch between the device and Speaker (CrowdPlay.setAudioOutput). Do
34
- NOT build upload screens, diagnostics screens, or SDK settings.
35
- Uploads happen automatically in the background.
36
-
37
- - npm install crowdplaysdk
38
- - In ios/: pod install, then run
39
- gem install xcodeproj --user-install # one-time
40
- ruby node_modules/crowdplaysdk/ios/wire.rb <MyProjectName>
41
- (adds the compiled CrowdPlay engine + bridge to the Xcode project)
42
- - Info.plist: NSMicrophoneUsageDescription, NSCameraUsageDescription,
43
- UIBackgroundModes = [audio]
44
- - My CrowdPlay app key: <APP KEY from the CrowdPlay dashboard>
45
- - Follow node_modules/crowdplaysdk/llms.txt exactly: configure at
46
- startup, the CrowdPlayConsentScreen before joining (join() throws
47
- without a ConsentGrant), CrowdPlayVideoView tiles from the participants
48
- event, and surface the warning events on the call screen.
49
- - Never add, pin or import LiveKit/WebRTC yourself, and never write your
50
- own AVAudioSession/AVCaptureSession code — the engine owns the mic and
51
- camera.
52
- - When done, run CrowdPlay.doctor() as a development check (not an app
53
- screen) and fix anything actionable.
54
- - Your FINAL message must walk me through testing on my real iPhone,
55
- step by step (the Simulator has no camera or mic, so nothing is proven
56
- until then): plugging it in, signing/Developer Mode, running from
57
- Xcode, allowing permissions, doing a ~1-minute test call, and checking
58
- my session at https://dashboard.crowdplay.ai. Do not just say the
59
- implementation is complete.
89
+
90
+ **3. Let uploads finish while the app is suspended** (recommended). In your
91
+ AppDelegate:
92
+
93
+ ```objc
94
+ // AppDelegate.mm, with #import "<YourProjectName>-Swift.h" at the top
95
+ - (void)application:(UIApplication *)application
96
+ handleEventsForBackgroundURLSession:(NSString *)identifier
97
+ completionHandler:(void (^)(void))completionHandler {
98
+ [CrowdPlayRNBackground handleWithCompletionHandler:completionHandler];
99
+ }
60
100
  ```
61
101
 
62
- ## Manual setup (iOS)
63
-
64
- 1. `npm install crowdplaysdk`
65
- 2. `cd ios && pod install`
66
- (one-time: `gem install xcodeproj --user-install` — the wiring script needs it)
67
- 3. `ruby node_modules/crowdplaysdk/ios/wire.rb <YourProjectName>` —
68
- idempotent; adds the CrowdPlaySDK Swift package (the compiled engine), copies
69
- the three bridge files into your app target, and points the Swift
70
- bridging header at the React headers. (No CocoaPod of our own: the
71
- engine ships as a binary Swift package, which keeps your Podfile
72
- untouched.)
73
- 4. Info.plist: `NSMicrophoneUsageDescription`, `NSCameraUsageDescription`,
74
- `UIBackgroundModes = [audio]`.
75
- 5. Recommended, for uploads that finish while your app is suspended — in
76
- your AppDelegate:
77
-
78
- ```objc
79
- // AppDelegate.mm — add #import "<YourProjectName>-Swift.h" at the top
80
- - (void)application:(UIApplication *)application
81
- handleEventsForBackgroundURLSession:(NSString *)identifier
82
- completionHandler:(void (^)(void))completionHandler {
83
- [CrowdPlayRNBackground handleWithCompletionHandler:completionHandler];
84
- }
85
- ```
86
-
87
- (Swift AppDelegates call `CrowdPlaySDK.handleBackgroundURLSessionEvents(completionHandler:)`.)
102
+ Swift AppDelegates call `CrowdPlaySDK.handleBackgroundURLSessionEvents(completionHandler:)`.
88
103
 
89
104
  ## Usage
90
105
 
91
106
  ```tsx
92
107
  import CrowdPlay, { CrowdPlayConsentScreen, CrowdPlayVideoView } from 'crowdplaysdk';
93
108
 
94
- CrowdPlay.configure({ serverUrl: 'https://…', appKey: 'liva_pk_…' });
95
- // Voice-only app? add audioOnly: true — the camera is never touched
96
- // (no camera permission, no video captured or uploaded).
109
+ CrowdPlay.configure({
110
+ serverUrl: 'https://dashboard.crowdplay.ai',
111
+ appKey: 'liva_pk_…',
112
+ // audioOnly: true, // audio-only or Voice AI app: no camera at all
113
+ // otherAudio: 'mix', // keep Apple Music / Spotify playing (0.4.0+)
114
+ });
97
115
 
98
- // 1. Consent (REQUIRED join() throws without it):
116
+ // 1. Consent (required; join() throws without it):
99
117
  <CrowdPlayConsentScreen onConsent={(grant) => setConsent(grant)} />
100
118
 
101
- // 2. Join recording starts automatically:
119
+ // 2. Join. Recording starts automatically:
102
120
  await CrowdPlay.join({ displayName, roomCode, consent });
103
121
 
104
122
  // 3. Render the call:
105
- <CrowdPlayVideoView participant="local" style={…} /> // self view
123
+ <CrowdPlayVideoView participant="local" style={…} /> // self view
106
124
  {participants.map(p => <CrowdPlayVideoView participant={p.identity} … />)}
107
125
 
108
126
  // 4. Controls:
109
- await CrowdPlay.setMicMuted(true); // mutes the call AND records silence
127
+ await CrowdPlay.setMicMuted(true); // mutes the call and records silence
110
128
  await CrowdPlay.setCameraEnabled(false); // black frames, timeline continuous
111
129
  await CrowdPlay.leave(); // stops recording; uploads continue
130
+ ```
131
+
132
+ State arrives as events:
112
133
 
113
- // State: subscribe to events
114
- CrowdPlay.addListener('phase', …) // idle/connecting/connected/failed
134
+ ```tsx
135
+ CrowdPlay.addListener('phase', …) // idle / connecting / connected / failed
115
136
  CrowdPlay.addListener('participants', …) // who to render tiles for
116
137
  CrowdPlay.addListener('uploads', …) // per-session progress
117
- CrowdPlay.addListener('warning', …) // micSilent / clipping / crossTalk SHOW THESE
118
- CrowdPlay.addListener('recording', …) // error NOT capturing; offer retryRecording()
119
- CrowdPlay.addListener('audioRoute', …) // current output device drive the output menu
138
+ CrowdPlay.addListener('warning', …) // micSilent / clipping / crossTalk: show these
139
+ CrowdPlay.addListener('recording', …) // error means NOT capturing; offer retryRecording()
140
+ CrowdPlay.addListener('audioRoute', …) // current output device, for the output menu
120
141
  CrowdPlay.setAudioOutput('speaker') // or 'automatic' (the connected device)
121
- CrowdPlay.snapshot() // poll 1 Hz for REC timer + input level
122
- CrowdPlay.doctor() // integration self-check
142
+ CrowdPlay.snapshot() // poll at 1 Hz for the REC timer and input level
143
+ ```
144
+
145
+ That's the whole integration. Capture, sync, upload, retry and crash
146
+ recovery are invisible. The complete example lives in this package's
147
+ `Examples` folder: one file, consent → join → tiles → warnings → uploads.
148
+
149
+ ## Voice AI
150
+
151
+ If you picked **Voice AI**, a live AI voice joins the call as an ordinary
152
+ participant, greets your user, and holds a real spoken conversation. Users
153
+ can interrupt it and it responds with natural timing. Both sides are
154
+ recorded and transcribed like any other call.
155
+
156
+ **Your app writes no AI code.** No model API calls, no API keys, no speech
157
+ recognition, no text-to-speech. You choose the AI's voice and personality
158
+ on the dashboard and can change them any time; everything else runs on
159
+ CrowdPlay's servers.
160
+
161
+ The only thing your app draws is what the AI looks like on screen. It has
162
+ no camera, so never give it a video tile. The SDK ships an animated orb
163
+ that breathes when idle, ripples with your user's voice, and pulses when
164
+ the AI speaks:
165
+
166
+ ```tsx
167
+ <CrowdPlayVoiceView />
168
+ ```
169
+
170
+ Restyle it with props, wrap your own artwork in `<VoiceReactive>`, or drive
171
+ any visual from the `useVoiceActivity()` hook. Show live subtitles with
172
+ `useLatestCaption()`, and identify the AI participant by name `agent`.
173
+
174
+ You can also override the voice, personality and context for a single call,
175
+ which is how you give the AI memory of a particular user:
176
+
177
+ ```tsx
178
+ await CrowdPlay.join({
179
+ displayName, roomCode, consent,
180
+ agent: { voice: 'Aoede', context: 'Their name is Sam. Last time you discussed Kyoto.' },
181
+ });
182
+ ```
183
+
184
+ See `llms.txt` in this package for the full menu of visuals and options.
185
+
186
+ ## Verifying your integration
187
+
188
+ ```tsx
189
+ const checks = await CrowdPlay.doctor();
190
+ ```
191
+
192
+ Doctor verifies configuration, Info.plist strings, permissions, free disk,
193
+ backend reachability and your app key. Every failing check names its own
194
+ fix. Then record a one-minute session on a real device and confirm the
195
+ upload finishes.
196
+
197
+ ## Accessing your recordings
198
+
199
+ Your app key is also your data-access credential: plain HTTPS, no AWS
200
+ account, no SDK required.
201
+
202
+ ```bash
203
+ BASE=https://dashboard.crowdplay.ai
204
+
205
+ curl -H "x-liva-key: $APP_KEY" $BASE/sessions
206
+ curl -H "x-liva-key: $APP_KEY" $BASE/sessions/<sessionId>/files
123
207
  ```
124
208
 
125
- The complete example lives in this repo's `Examples` (one file, ~170
126
- lines): consent join tiles warnings uploads.
127
-
128
- ## Accessing recordings
129
-
130
- Your app key doubles as your data credential — see the CrowdPlay dashboard
131
- (browse + download per session) or the REST endpoints (`GET /sessions`,
132
- `GET /sessions/<id>/files`) documented in the CrowdPlaySDK README.
133
-
134
- ## Requirements & limits
135
-
136
- - iOS 17+, React Native ≥ 0.71. **iOS only for now** — no Android engine
137
- yet, calls to the module on other platforms throw with a clear message.
138
- - Real device required to record (the Simulator has no camera/mic).
139
- - The npm version can run ahead of the compiled engine version wire.rb
140
- pins bridge and doc fixes ship without a new engine binary. wire.rb
141
- always pins the engine release this bridge was tested against.
142
- - Keep the screen awake during sessions; force-quitting the app pauses
143
- uploads until next launch (they resume automatically).
144
- - **Slow join + glitchy first seconds = bad radio, not a bug.** Congested
145
- 2.4 GHz WiFi plus Bluetooth headphones (they share the antenna) slows the
146
- call connect and can chop the first seconds of audio while the Bluetooth
147
- link settles; capture self-heals and the rest of the session is
148
- unaffected. Prefer a good network and wired headphones, and evaluate
149
- quality with the app launched from the home screen — Metro/debugger
150
- overhead can reproduce the same symptoms.
209
+ `deliverable/` holds what you actually use: one clean audio file per
210
+ person, aligned so they play together with no offset, plus a mixed version
211
+ and a quality report. You can only reach sessions recorded with your key.
212
+
213
+ ## Things to know
214
+
215
+ - **Don't force-quit the app** after a session: iOS pauses background
216
+ transfers until next launch. They resume automatically and nothing is
217
+ lost.
218
+ - Sessions are roughly 2 GB per participant-hour, dominated by video. The
219
+ SDK refuses to join below 8 GB free disk.
220
+ - Participants should wear **wired or closed-back headphones**. A
221
+ loudspeaker pipes the far end into the local mic and contaminates the
222
+ track; the SDK surfaces this live through the `warning` event.
223
+ - **Slow join or glitchy first seconds is bad radio, not a bug.** Congested
224
+ 2.4 GHz WiFi and Bluetooth headphones share an antenna. Capture
225
+ self-heals and the rest of the session is fine. Metro and the debugger
226
+ cause the same symptoms, so launch from the home screen when judging
227
+ quality.
228
+ - The npm version can run ahead of the engine version `wire.rb` pins;
229
+ bridge and doc fixes ship without a new engine binary.
230
+
231
+ `llms.txt` carries the full reference, including every event and warning
232
+ and the voice-AI options. It is written for coding agents.
@@ -113,19 +113,19 @@ final class CrowdPlayRNCore {
113
113
  let snapshot = engine.recordingSnapshot()
114
114
  updateWarning("audioStalled",
115
115
  active: snapshot?.audioStalled == true,
116
- message: "Audio capture stalled the engine is being restarted. "
116
+ message: "Audio capture stalled; the engine is being restarted. "
117
117
  + "If this persists, leave and rejoin.")
118
118
  updateWarning("micSilent",
119
119
  active: snapshot?.inputSilent == true && !engine.isMicMuted,
120
- message: "No sound is reaching the microphone check the headset "
120
+ message: "No sound is reaching the microphone; check the headset "
121
121
  + "plug or switch to the phone microphone.")
122
122
  updateWarning("clipping",
123
123
  active: (snapshot?.fullScaleSamples ?? 0) > 100,
124
- message: "The microphone is clipping audio is distorting. Move "
124
+ message: "The microphone is clipping; audio is distorting. Move "
125
125
  + "the mic away or lower the input.")
126
126
  updateWarning("crossTalk",
127
127
  active: engine.crossTalkRisk,
128
- message: "Playing through the loudspeaker the other person's "
128
+ message: "Playing through the loudspeaker; the other person's "
129
129
  + "voice is being recorded into this microphone. Use headphones.")
130
130
  updateWarning("micPolicy",
131
131
  active: engine.micPolicyViolation != nil,
@@ -136,7 +136,7 @@ final class CrowdPlayRNCore {
136
136
 
137
137
  private var voiceTimer: Timer?
138
138
 
139
- /// Fast (0.15 s) voice-activity stream for the animated voice UI
139
+ /// Fast (0.15 s) voice-activity stream for the animated voice UI ,
140
140
  /// runs only while some JS component is subscribed, so the bridge
141
141
  /// stays quiet for apps that never render it.
142
142
  func startVoiceActivity() {
@@ -205,7 +205,7 @@ final class CrowdPlayRNCore {
205
205
  /// [CrowdPlayRNBackground handleWithCompletionHandler:completionHandler];
206
206
  ///
207
207
  /// iOS eventually stops delivering background upload events to apps that
208
- /// skip this uploads still finish, just less promptly.
208
+ /// skip this; uploads still finish, just less promptly.
209
209
  @objc(CrowdPlayRNBackground)
210
210
  public final class CrowdPlayRNBackground: NSObject {
211
211
  @objc public static func handle(completionHandler: @escaping () -> Void) {
@@ -258,6 +258,12 @@ public final class CrowdPlayRNModule: RCTEventEmitter {
258
258
  if let duringCall = config["uploadDuringCall"] as? Bool {
259
259
  configuration.uploadDuringCall = duringCall
260
260
  }
261
+ // D-093: other apps' audio during the call. Unknown strings keep
262
+ // the default (.interrupt) rather than failing configure.
263
+ if let policy = config["otherAudio"] as? String,
264
+ let parsed = CrowdPlayConfiguration.OtherAudioPolicy(rawValue: policy) {
265
+ configuration.otherAudio = parsed
266
+ }
261
267
  if let quality = config["callQuality"] as? [String: Any] {
262
268
  var callQuality = CallQuality()
263
269
  if let v = quality["videoWidth"] as? Int { callQuality.videoWidth = v }
@@ -5,7 +5,7 @@ import React
5
5
  import UIKit
6
6
 
7
7
  /// Native video tile hosted in the RN layout. Resolves the requested
8
- /// participant's track from the shared engine on a short poll idempotent
8
+ /// participant's track from the shared engine on a short poll; idempotent
9
9
  /// and reconnect-proof, and the video itself never touches JavaScript.
10
10
  final class CrowdPlayRNVideoView: UIView {
11
11
  private let videoView = VideoView()
@@ -1,6 +1,6 @@
1
1
  // Objective-C registration for the CrowdPlay RN bridge. Add this file AND the
2
2
  // two Swift files to your app target (they compile alongside your app, with
3
- // CrowdPlaySDK coming from the Swift package see the package README).
3
+ // CrowdPlaySDK coming from the Swift package; see the package README).
4
4
  #import <React/RCTBridgeModule.h>
5
5
  #import <React/RCTEventEmitter.h>
6
6
  #import <React/RCTViewManager.h>
package/ios/wire.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # ruby node_modules/crowdplaysdk/ios/wire.rb <YourProjectName>
5
5
  #
6
- # What it does (idempotent safe to re-run):
6
+ # What it does (idempotent; safe to re-run):
7
7
  # 1. adds the CrowdPlaySDK Swift package (compiled engine) to the project
8
8
  # 2. copies the three bridge files into your app group + target
9
9
  # 3. creates/points the Swift bridging header at the React headers
@@ -30,7 +30,7 @@ BRIDGE_FILES = ['CrowdPlayRNModule.swift', 'CrowdPlayRNVideoView.swift', 'CrowdP
30
30
 
31
31
  project_name = ARGV[0] or abort 'usage: ruby wire.rb <YourProjectName>'
32
32
  project_path = "#{project_name}.xcodeproj"
33
- abort "#{project_path} not found run from your app's ios/ directory" unless File.exist?(project_path)
33
+ abort "#{project_path} not found; run from your app's ios/ directory" unless File.exist?(project_path)
34
34
 
35
35
  package_dir = File.expand_path(File.dirname(__FILE__))
36
36
  project = Xcodeproj::Project.open(project_path)
@@ -68,7 +68,7 @@ end
68
68
  target.build_configurations.each do |config|
69
69
  config.build_settings['SWIFT_VERSION'] ||= '5.0'
70
70
  # CrowdPlaySDK requires iOS 17; RN templates default far lower. Only the APP
71
- # target is raised pods keep their own targets, which is fine.
71
+ # target is raised; pods keep their own targets, which is fine.
72
72
  current = config.build_settings['IPHONEOS_DEPLOYMENT_TARGET']
73
73
  if current.nil? || current.to_f < 17.0
74
74
  config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '17.0'
@@ -86,7 +86,7 @@ target.build_configurations.each do |config|
86
86
  end
87
87
  # The setting must point at wherever the file ACTUALLY went. When the
88
88
  # project has no group named after itself (newer RN templates), the
89
- # fallback above places files at the project root hardcoding
89
+ # fallback above places files at the project root; hardcoding
90
90
  # "<Project>/<header>" there produced a missing-input build failure
91
91
  # (field-reported on the RN 0.87 template). Derive it instead:
92
92
  # SRCROOT is the .xcodeproj's directory.
@@ -65,7 +65,7 @@ function CrowdPlayConsentScreen({ onConsent, style }) {
65
65
  })
66
66
  .catch(() => {
67
67
  if (!cancelled)
68
- setText('Consent text unavailable check that CrowdPlay.configure() ran and the native module is installed.');
68
+ setText('Consent text unavailable. Check that CrowdPlay.configure() ran and the native module is installed.');
69
69
  });
70
70
  return () => {
71
71
  cancelled = true;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Native video tile. Renders the LOCAL camera preview or a REMOTE
3
3
  * participant's live video (identified by the `identity` from the
4
- * `participants` event). The video never touches JavaScript this is a
4
+ * `participants` event). The video never touches JavaScript; this is a
5
5
  * native LiveKit view hosted in your RN layout.
6
6
  *
7
7
  * <CrowdPlayVideoView participant="local" style={{ width: 120, height: 160 }} />
package/lib/VideoView.js CHANGED
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Native video tile. Renders the LOCAL camera preview or a REMOTE
4
4
  * participant's live video (identified by the `identity` from the
5
- * `participants` event). The video never touches JavaScript this is a
5
+ * `participants` event). The video never touches JavaScript; this is a
6
6
  * native LiveKit view hosted in your RN layout.
7
7
  *
8
8
  * <CrowdPlayVideoView participant="local" style={{ width: 120, height: 160 }} />
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * The standard voice-AI visual (founders, 2026-08-22): a breathing orb in
3
- * the style of realtime assistants calm while idle, a cool ring while the
3
+ * the style of realtime assistants: calm while idle, a cool ring while the
4
4
  * user talks, an energetic pulse while the AI speaks. The React Native
5
5
  * counterpart of the native SDK's CrowdPlayVoiceView, driven by the same
6
6
  * engine signal over a fast bridge event that runs only while mounted.
7
7
  *
8
8
  * Two levels of customization:
9
- * 1. Props on <CrowdPlayVoiceView> colors and size of the built-in orb.
10
- * 2. useVoiceActivity() the phase + smoothed energy underneath, for apps
9
+ * 1. Props on <CrowdPlayVoiceView>: colors and size of the built-in orb.
10
+ * 2. useVoiceActivity(): the phase + smoothed energy underneath, for apps
11
11
  * that want to draw something entirely their own.
12
12
  */
13
13
  import React from 'react';
@@ -27,7 +27,7 @@ export interface VoiceCaption {
27
27
  /** The latest live transcript line (speech bubbles, subtitles). Captions
28
28
  * are broadcast by the voice agent as it speaks; null until the first one. */
29
29
  export declare function useLatestCaption(): VoiceCaption | null;
30
- /** Make ANY child view breathe, pulse, and glow with the conversation
30
+ /** Make ANY child view breathe, pulse, and glow with the conversation;
31
31
  * the one-line path from the app's own art (mascot, logo, character) to a
32
32
  * living AI presence. */
33
33
  export declare function VoiceReactive({ children, glowColor, }: {
package/lib/VoiceView.js CHANGED
@@ -1,14 +1,14 @@
1
1
  "use strict";
2
2
  /**
3
3
  * The standard voice-AI visual (founders, 2026-08-22): a breathing orb in
4
- * the style of realtime assistants calm while idle, a cool ring while the
4
+ * the style of realtime assistants: calm while idle, a cool ring while the
5
5
  * user talks, an energetic pulse while the AI speaks. The React Native
6
6
  * counterpart of the native SDK's CrowdPlayVoiceView, driven by the same
7
7
  * engine signal over a fast bridge event that runs only while mounted.
8
8
  *
9
9
  * Two levels of customization:
10
- * 1. Props on <CrowdPlayVoiceView> colors and size of the built-in orb.
11
- * 2. useVoiceActivity() the phase + smoothed energy underneath, for apps
10
+ * 1. Props on <CrowdPlayVoiceView>: colors and size of the built-in orb.
11
+ * 2. useVoiceActivity(): the phase + smoothed energy underneath, for apps
12
12
  * that want to draw something entirely their own.
13
13
  */
14
14
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
@@ -81,7 +81,7 @@ function useVoiceActivity() {
81
81
  phase = 'idle';
82
82
  target = 0;
83
83
  }
84
- // Fast attack, slow release speech onsets snap, tails breathe out.
84
+ // Fast attack, slow release: speech onsets snap, tails breathe out.
85
85
  const rate = target > energyRef.current ? 0.55 : 0.12;
86
86
  energyRef.current += (target - energyRef.current) * rate;
87
87
  setActivity({ phase, energy: energyRef.current });
@@ -108,7 +108,7 @@ function useLatestCaption() {
108
108
  }, []);
109
109
  return caption;
110
110
  }
111
- /** Make ANY child view breathe, pulse, and glow with the conversation
111
+ /** Make ANY child view breathe, pulse, and glow with the conversation;
112
112
  * the one-line path from the app's own art (mascot, logo, character) to a
113
113
  * living AI presence. */
114
114
  function VoiceReactive({ children, glowColor = '#598CFF', }) {
package/lib/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * crowdplaysdk CrowdPlay's lossless capture SDK for React Native (iOS).
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 so recording quality is identical
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 there is no code
20
+ * native layer requires the consent record structurally; there is no code
21
21
  * path that records without it.
22
22
  */
23
23
  /** Live-call (Path A) quality. The RECORDING is always 1080p30 + 24-bit WAV
@@ -37,7 +37,7 @@ export interface CrowdPlayConfig {
37
37
  serverUrl: string;
38
38
  /** From the CrowdPlay dashboard. Also your data-access credential. */
39
39
  appKey: string;
40
- /** AUDIO-ONLY app: the camera is never touched no camera permission,
40
+ /** AUDIO-ONLY app: the camera is never touched: no camera permission,
41
41
  * no video captured or uploaded, voice-only calls. The audio path is
42
42
  * identical to an A/V app. Requires engine 0.3.0+. Default false. */
43
43
  audioOnly?: boolean;
@@ -46,13 +46,21 @@ export interface CrowdPlayConfig {
46
46
  videoUploadsOnCellular?: boolean;
47
47
  /** Upload segments while the call records, so the post-call wait is
48
48
  * roughly the final segment + manifests. Mid-call transfers run one at
49
- * a time and pause automatically when call quality drops the call
49
+ * a time and pause automatically when call quality drops; the call
50
50
  * always wins the uplink. Default true. */
51
51
  uploadDuringCall?: boolean;
52
+ /** What happens to OTHER apps' audio (Apple Music, Spotify, podcasts)
53
+ * while a call is live. 'interrupt' (default) pauses them at join, the
54
+ * classic call behaviour. 'mix' keeps them playing alongside the call
55
+ * and the recording, for shared-listening and DJ apps; 'mixDucked' also
56
+ * lowers them for the whole call. Music stays on the device and never
57
+ * rides the call; listeners must wear headphones or it lands in the
58
+ * recording. Requires engine 0.4.0+. */
59
+ otherAudio?: 'interrupt' | 'mix' | 'mixDucked';
52
60
  callQuality?: CallQuality;
53
61
  }
54
- /** Proof the participant agreed. Produce it with CrowdPlayConsentScreen, or
55
- * if you render your own consent UI call CrowdPlay.consentText() to show the
62
+ /** Proof the participant agreed. Produce it with CrowdPlayConsentScreen, or,
63
+ * if you render your own consent UI, call CrowdPlay.consentText() to show the
56
64
  * REQUIRED wording and construct the grant at the moment of agreement. */
57
65
  export interface ConsentGrant {
58
66
  /** Date.now() at the moment the participant agreed. */
@@ -88,10 +96,10 @@ export interface RecordingSnapshot {
88
96
  droppedSamples: number;
89
97
  /** Live input level. Speech sits around −25…−45 dBFS. */
90
98
  inputLevelDbfs: number;
91
- /** True after 10 s of silence while unmuted a dead/disconnected mic.
99
+ /** True after 10 s of silence while unmuted: a dead/disconnected mic.
92
100
  * Surface this loudly; it is fixable mid-call. */
93
101
  inputSilent: boolean;
94
- /** Samples at digital full scale a too-hot mic, irreversibly clipped. */
102
+ /** Samples at digital full scale: a too-hot mic, irreversibly clipped. */
95
103
  fullScaleSamples: number;
96
104
  }
97
105
  export interface UploadKindProgress {
@@ -127,7 +135,7 @@ export interface CrowdPlayEvents {
127
135
  error?: string;
128
136
  };
129
137
  /** Recording state. `error` non-null means the session is NOT being
130
- * captured show it prominently and offer retryRecording(). */
138
+ * captured; show it prominently and offer retryRecording(). */
131
139
  recording: {
132
140
  isRecording: boolean;
133
141
  error?: string;
@@ -175,7 +183,7 @@ declare const CrowdPlay: {
175
183
  * Join a room. Recording starts automatically on join and stops on
176
184
  * leave(). Rejects if the connection fails.
177
185
  *
178
- * `consent` is REQUIRED produce it with <CrowdPlayConsentScreen> or from
186
+ * `consent` is REQUIRED; produce it with <CrowdPlayConsentScreen> or from
179
187
  * your own UI at the moment of agreement. There is no way to record
180
188
  * without it, here or natively.
181
189
  */
package/lib/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  "use strict";
2
2
  /**
3
- * crowdplaysdk CrowdPlay's lossless capture SDK for React Native (iOS).
3
+ * crowdplaysdk: CrowdPlay's lossless capture SDK for React Native (iOS).
4
4
  *
5
5
  * The recording engine is 100% native (CrowdPlaySDK): studio-grade audio
6
6
  * (48 kHz / 24-bit, unprocessed) + 1080p30 video captured locally during a
7
7
  * live call, clock-synced across participants, uploaded to CrowdPlay with
8
- * retries and crash recovery. **No media ever crosses the JS bridge** —
9
- * only control calls and state events so recording quality is identical
8
+ * retries and crash recovery. **No media ever crosses the JS bridge**,
9
+ * only control calls and state events, so recording quality is identical
10
10
  * to a fully native app.
11
11
  *
12
12
  * import CrowdPlay, { CrowdPlayConsentScreen, CrowdPlayVideoView } from 'crowdplaysdk';
@@ -18,7 +18,7 @@
18
18
  * await CrowdPlay.leave(); // recording stops; uploads continue automatically
19
19
  *
20
20
  * Consent is enforced twice: join() throws here without a grant, and the
21
- * native layer requires the consent record structurally there is no code
21
+ * native layer requires the consent record structurally; there is no code
22
22
  * path that records without it.
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -30,8 +30,8 @@ function native() {
30
30
  throw new Error("crowdplaysdk: native module not found. iOS setup: add the CrowdPlaySDK " +
31
31
  "Swift package (https://github.com/symbiateam/crowdplaysdk) to your Xcode " +
32
32
  "project and add the two bridge files from node_modules/crowdplaysdk/ios " +
33
- "to your app target see the package README. " +
34
- (react_native_1.Platform.OS !== 'ios' ? `(platform '${react_native_1.Platform.OS}' is not supported yet iOS only.)` : ''));
33
+ "to your app target; see the package README. " +
34
+ (react_native_1.Platform.OS !== 'ios' ? `(platform '${react_native_1.Platform.OS}' is not supported yet; iOS only.)` : ''));
35
35
  }
36
36
  return module;
37
37
  }
@@ -73,7 +73,7 @@ const CrowdPlay = {
73
73
  * Join a room. Recording starts automatically on join and stops on
74
74
  * leave(). Rejects if the connection fails.
75
75
  *
76
- * `consent` is REQUIRED produce it with <CrowdPlayConsentScreen> or from
76
+ * `consent` is REQUIRED; produce it with <CrowdPlayConsentScreen> or from
77
77
  * your own UI at the moment of agreement. There is no way to record
78
78
  * without it, here or natively.
79
79
  */
package/llms.txt CHANGED
@@ -29,7 +29,7 @@ Consequences:
29
29
  - Recording starts automatically on join() and stops on leave(). Do not
30
30
  build a separate record button.
31
31
 
32
- ## The HOST APP is the product not recording
32
+ ## The HOST APP is the product, not recording
33
33
 
34
34
  The builder came to you with an app idea (a tutoring app, a language-
35
35
  exchange app, a remote interview tool…). Build THAT app. The SDK records
@@ -37,11 +37,11 @@ its video calls invisibly; recording is infrastructure, not identity:
37
37
  - The app's NAME, description, and screens are about the builder's idea.
38
38
  Never name or describe the app around recording.
39
39
  - Recording surfaces in the UI in exactly four places: the consent
40
- screen before joining (required never minimized away), a small REC
40
+ screen before joining (required, never minimized away), a small REC
41
41
  indicator during the call, join/leave, and a small audio-output menu
42
42
  (REQUIRED standard control: show the current output device by name from
43
43
  the 'audioRoute' event, with a switch between the device and Speaker
44
- via CrowdPlay.setAudioOutput Bluetooth users expect it).
44
+ via CrowdPlay.setAudioOutput; Bluetooth users expect it).
45
45
  - Do NOT build upload-progress screens, diagnostics screens, SDK settings
46
46
  pages, or recording libraries/playback. Uploads run automatically in
47
47
  the background; doctor() is a development-time check, not a screen.
@@ -56,7 +56,7 @@ its video calls invisibly; recording is infrastructure, not identity:
56
56
  add audio-session-touching RN packages like react-native-incall-manager)
57
57
  in an app using this SDK. The engine owns the microphone and camera.
58
58
  3. NEVER skip or fake consent. CrowdPlay.join() THROWS without a ConsentGrant,
59
- and the user must actually SEE the consent wording first use
59
+ and the user must actually SEE the consent wording first; use
60
60
  <CrowdPlayConsentScreen/>, or show CrowdPlay.consentText() verbatim in your own
61
61
  UI and construct { grantedAtMs: Date.now() } at the moment of
62
62
  agreement. Fabricating a grant creates false legal evidence.
@@ -79,7 +79,7 @@ its video calls invisibly; recording is infrastructure, not identity:
79
79
  4. Info.plist: NSMicrophoneUsageDescription, NSCameraUsageDescription,
80
80
  UIBackgroundModes = [audio]. A missing usage string makes iOS kill
81
81
  the app at first mic/camera access (uncatchable).
82
- 5. AppDelegate (recommended lets uploads finish while suspended):
82
+ 5. AppDelegate (recommended; lets uploads finish while suspended):
83
83
  // AppDelegate.mm, with #import "<YourProjectName>-Swift.h"
84
84
  - (void)application:(UIApplication *)application
85
85
  handleEventsForBackgroundURLSession:(NSString *)identifier
@@ -112,18 +112,18 @@ its video calls invisibly; recording is infrastructure, not identity:
112
112
  already went out during the call). Show at most one quiet
113
113
  "finishing up…" line while an 'uploads' session is incomplete; do
114
114
  NOT build a dedicated upload screen.
115
- 10. Verify: const checks = await CrowdPlay.doctor(); every failing check's
115
+ 10. Verify: const checks = await CrowdPlay.doctor(); every failing check's
116
116
  detail names its own fix. NOTE: on a fresh install the "permissions"
117
- check reports "not requested yet NORMAL"; iOS prompts at first join.
118
- That is not an integration bug only a DENIED state needs action.
117
+ check reports "not requested yet (NORMAL)"; iOS prompts at first join.
118
+ That is not an integration bug; only a DENIED state needs action.
119
119
  11. Hand off (REQUIRED final message): the Simulator has no camera or
120
120
  mic, so nothing is proven until the app runs on a real iPhone. Do
121
- not end with "the implementation is complete" your FINAL message
121
+ not end with "the implementation is complete"; your FINAL message
122
122
  must walk the builder through: plugging in the iPhone (Trust +
123
123
  Developer Mode), picking a signing Team in Xcode and pressing Run on
124
124
  the device (via the .xcworkspace), allowing mic/camera permissions,
125
125
  a ~1-minute test call, and checking the session at
126
- https://dashboard.crowdplay.ai that is the end-to-end proof.
126
+ https://dashboard.crowdplay.ai; that is the end-to-end proof.
127
127
 
128
128
  ## API reference (exact TypeScript surface)
129
129
 
@@ -155,12 +155,12 @@ useVoiceActivity() -> { phase: 'connecting'|'idle'|'listening'|'speaking', energ
155
155
 
156
156
  The voice AI (apps with the Voice AI switch enabled on the dashboard):
157
157
 
158
- How it works all server-side, nothing to wire up. When any user joins a
158
+ How it works (all server-side, nothing to wire up). When any user joins a
159
159
  call, CrowdPlay's servers put a live AI participant into the room within
160
160
  ~3 seconds. It joins with name "agent" (identity "agent-<8hex>"), greets
161
161
  the user, and holds a real spoken conversation (speech-to-speech: it
162
162
  hears the user directly, answers in natural voice, can be interrupted).
163
- The app writes ZERO AI code no model API calls, no AI keys, no chat
163
+ The app writes ZERO AI code: no model API calls, no AI keys, no chat
164
164
  backend, no STT/TTS. Provider, model and API key are set by the BUILDER on
165
165
  the CrowdPlay dashboard, and so are the DEFAULT persona and voice.
166
166
  Per-join overrides (0.4.0+): the app MAY pass `agent` to join() to change
@@ -177,34 +177,34 @@ and key cannot be changed from the app. MEMORY is built with `context`:
177
177
  keep a few sentences per user in the app's own storage and pass them at
178
178
  every join.
179
179
  Conversations are recorded and transcribed by the platform automatically.
180
- RECOMMENDED: voice-AI apps should be AUDIO-ONLY (audioOnly: true no
180
+ RECOMMENDED: voice-AI apps should be AUDIO-ONLY (audioOnly: true; no
181
181
  camera permission, no video anywhere); a camera adds nothing to talking
182
182
  with an AI. Keep video only when the app ALSO has calls between people.
183
183
 
184
- The AI conversation screen the approved standard layout: the animated
184
+ The AI conversation screen, the approved standard layout: the animated
185
185
  visual front and center, large (it IS the screen); NO self camera
186
186
  preview and NO video tiles on this screen, ever; around it only the AI's
187
187
  name/persona, a small REC indicator, a leave button, and the small
188
188
  audio-output menu. One calm screen, one living visual, nothing competing
189
189
  with it.
190
190
 
191
- What the app builds the AI's on-screen presence. The AI publishes
191
+ What the app builds: the AI's on-screen presence. The AI publishes
192
192
  AUDIO ONLY: never a camera tile, an empty box, or a static image; never
193
193
  count it as a generic user. Detect it in the participants event by name
194
194
  "agent" (or identity prefix "agent-").
195
195
  - The visual is a MENU, zero-effort to fully custom (0.3.4+ for 2-5):
196
- 1. Orb (default): <CrowdPlayVoiceView /> breathes idle, rings with
196
+ 1. Orb (default): <CrowdPlayVoiceView />, breathes idle, rings with
197
197
  the user's voice, pulses with the AI's. Props: agentColor,
198
198
  listeningColor, idleColor, size.
199
- 2. Bars: <CrowdPlayVoiceView variant='bars' /> equalizer look, good
199
+ 2. Bars: <CrowdPlayVoiceView variant='bars' />, equalizer look, good
200
200
  under other content (scoreboards, question cards, quizzes).
201
201
  3. Halo around the app's own art: pass a mascot portrait / logo /
202
- character as children of <CrowdPlayVoiceView> animated ring +
202
+ character as children of <CrowdPlayVoiceView>, animated ring +
203
203
  glow around whatever you provide.
204
- 4. Make ANY view alive: wrap it in <VoiceReactive> breathing, pulse
204
+ 4. Make ANY view alive: wrap it in <VoiceReactive>, breathing, pulse
205
205
  and speech glow on the app's own art in one line.
206
206
  5. Fully custom (best for characters): drive ANY visual from the
207
- useVoiceActivity() hook { phase: 'connecting'|'idle'|'listening'|
207
+ useVoiceActivity() hook, returning { phase: 'connecting'|'idle'|'listening'|
208
208
  'speaking', energy: 0…1 smoothed loudness }. Example, an illustrated
209
209
  tutor that reacts to the conversation:
210
210
 
@@ -215,10 +215,10 @@ count it as a generic user. Detect it in the participants event by name
215
215
  pose={phase === 'listening' ? 'leanIn' : 'idleBlink'} />;
216
216
  }
217
217
 
218
- Any art pipeline works (Animated, Lottie, Rive, sprite frames) bind
218
+ Any art pipeline works (Animated, Lottie, Rive, sprite frames): bind
219
219
  mouth/pose/glow to phase + energy.
220
220
  - Live captions (0.3.4+): each line the AI and the user say is streamed
221
- as it is spoken the useLatestCaption() hook returns { role, text }.
221
+ as it is spoken; the useLatestCaption() hook returns { role, text }.
222
222
  For speech bubbles, subtitles, language-learning corrections. Optional
223
223
  UI; the full timestamped transcript is always delivered with the
224
224
  session regardless.
@@ -228,8 +228,14 @@ count it as a generic user. Detect it in the participants event by name
228
228
  Types:
229
229
  CrowdPlayConfig { serverUrl; appKey; audioOnly?=false;
230
230
  videoUploadsOnCellular?=false;
231
- uploadDuringCall?=true; callQuality? }
232
- // audioOnly (0.3.0+): the camera is never touched — no
231
+ uploadDuringCall?=true;
232
+ otherAudio?='interrupt'|'mix'|'mixDucked'; callQuality? }
233
+ // otherAudio (0.4.0+): 'interrupt' pauses other apps'
234
+ // audio at join (classic call); 'mix' keeps Apple Music /
235
+ // Spotify playing through the call and the recording, for
236
+ // shared-listening apps; 'mixDucked' also lowers it. Music
237
+ // never rides the call; listeners must wear headphones.
238
+ // audioOnly (0.3.0+): the camera is never touched; no
233
239
  // camera permission, no video captured or uploaded,
234
240
  // voice-only calls. Skip NSCameraUsageDescription, skip
235
241
  // CrowdPlayVideoView and every camera control.
@@ -242,8 +248,8 @@ Types:
242
248
  ConsentGrant { grantedAtMs: number }
243
249
  RecordingSnapshot { seconds; segmentsClosed; droppedSamples;
244
250
  inputLevelDbfs; // speech ≈ −25…−45 dBFS
245
- inputSilent; // dead mic surface loudly
246
- fullScaleSamples } // clipping mic too hot
251
+ inputSilent; // dead mic: surface loudly
252
+ fullScaleSamples } // clipping: mic too hot
247
253
  UploadProgress { sessionId; fraction; isComplete; failed;
248
254
  audio: UploadKindProgress; video: UploadKindProgress }
249
255
  DoctorCheck { id; passed; detail }
@@ -269,7 +275,7 @@ Events (CrowdPlay.addListener):
269
275
  // this mic (wear headphones); micPolicy = unexpected
270
276
  // input device.
271
277
 
272
- ## Accessing recordings (no AWS, no SDK plain HTTPS)
278
+ ## Accessing recordings (no AWS, no SDK, plain HTTPS)
273
279
 
274
280
  The app key doubles as the data credential:
275
281
  GET https://dashboard.crowdplay.ai/sessions (x-liva-key header)
@@ -289,21 +295,21 @@ Humans use https://dashboard.crowdplay.ai directly (sign in, download).
289
295
  pauses uploads until next launch (nothing lost).
290
296
  - Participants should wear wired or closed-back headphones (crossTalk
291
297
  warns live when they don't).
292
- - SIMULATOR detection (engine 0.3.0+): on any Simulator including CLOUD
293
- previews doctor()'s "platform" check fails and join() rejects with a
298
+ - SIMULATOR detection (engine 0.3.0+): on any Simulator, including CLOUD
299
+ previews, doctor()'s "platform" check fails and join() rejects with a
294
300
  clear message instead of a cryptic audio-engine error. Expected, not a
295
301
  bug: no microphone exists there. A doctor() run also flips the "health
296
302
  check" step on the builder's dashboard checklist automatically.
297
303
  - Slow join or glitchy first seconds of a recording = poor radio
298
304
  environment (congested 2.4 GHz WiFi + Bluetooth share the antenna) or
299
- Metro/debugger overhead NOT an integration bug. Capture self-heals.
305
+ Metro/debugger overhead; NOT an integration bug. Capture self-heals.
300
306
  Advise: better network, wired headphones, evaluate from a home-screen
301
307
  launch (release build).
302
308
  - Build error mentioning LiveKit/WebRTC duplicate symbols: the app added
303
- its own LiveKit dependency remove it (rule 1).
309
+ its own LiveKit dependency; remove it (rule 1).
304
310
  - "Swift bridging" or "<Project>-Swift.h not found" errors: rerun
305
311
  ios/wire.rb, then a clean build (the header is generated at build time).
306
- - 401/"unauthorized": wrong app key sign in at
312
+ - 401/"unauthorized": wrong app key; sign in at
307
313
  https://dashboard.crowdplay.ai, reveal the key, and copy it again.
308
314
  - Uploads crawling on an otherwise-fine network: check for a VPN, iCloud
309
315
  Private Relay, or Low Data Mode on the phone (a VPN can cut throughput
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crowdplaysdk",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
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",
@@ -33,7 +33,7 @@ export function CrowdPlayConsentScreen({ onConsent, style }: CrowdPlayConsentScr
33
33
  if (!cancelled) setText(wording);
34
34
  })
35
35
  .catch(() => {
36
- if (!cancelled) setText('Consent text unavailable check that CrowdPlay.configure() ran and the native module is installed.');
36
+ if (!cancelled) setText('Consent text unavailable. Check that CrowdPlay.configure() ran and the native module is installed.');
37
37
  });
38
38
  return () => {
39
39
  cancelled = true;
package/src/VideoView.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Native video tile. Renders the LOCAL camera preview or a REMOTE
3
3
  * participant's live video (identified by the `identity` from the
4
- * `participants` event). The video never touches JavaScript this is a
4
+ * `participants` event). The video never touches JavaScript; this is a
5
5
  * native LiveKit view hosted in your RN layout.
6
6
  *
7
7
  * <CrowdPlayVideoView participant="local" style={{ width: 120, height: 160 }} />
package/src/VoiceView.tsx CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * The standard voice-AI visual (founders, 2026-08-22): a breathing orb in
3
- * the style of realtime assistants calm while idle, a cool ring while the
3
+ * the style of realtime assistants: calm while idle, a cool ring while the
4
4
  * user talks, an energetic pulse while the AI speaks. The React Native
5
5
  * counterpart of the native SDK's CrowdPlayVoiceView, driven by the same
6
6
  * engine signal over a fast bridge event that runs only while mounted.
7
7
  *
8
8
  * Two levels of customization:
9
- * 1. Props on <CrowdPlayVoiceView> colors and size of the built-in orb.
10
- * 2. useVoiceActivity() the phase + smoothed energy underneath, for apps
9
+ * 1. Props on <CrowdPlayVoiceView>: colors and size of the built-in orb.
10
+ * 2. useVoiceActivity(): the phase + smoothed energy underneath, for apps
11
11
  * that want to draw something entirely their own.
12
12
  */
13
13
 
@@ -58,7 +58,7 @@ export function useVoiceActivity(): VoiceActivity {
58
58
  phase = 'idle';
59
59
  target = 0;
60
60
  }
61
- // Fast attack, slow release speech onsets snap, tails breathe out.
61
+ // Fast attack, slow release: speech onsets snap, tails breathe out.
62
62
  const rate = target > energyRef.current ? 0.55 : 0.12;
63
63
  energyRef.current += (target - energyRef.current) * rate;
64
64
  setActivity({ phase, energy: energyRef.current });
@@ -92,7 +92,7 @@ export function useLatestCaption(): VoiceCaption | null {
92
92
  return caption;
93
93
  }
94
94
 
95
- /** Make ANY child view breathe, pulse, and glow with the conversation
95
+ /** Make ANY child view breathe, pulse, and glow with the conversation;
96
96
  * the one-line path from the app's own art (mascot, logo, character) to a
97
97
  * living AI presence. */
98
98
  export function VoiceReactive({
package/src/index.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * crowdplaysdk CrowdPlay's lossless capture SDK for React Native (iOS).
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 so recording quality is identical
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 there is no code
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 no camera permission,
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 the call
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 call CrowdPlay.consentText() to show the
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. */
@@ -100,10 +108,10 @@ export interface RecordingSnapshot {
100
108
  droppedSamples: number;
101
109
  /** Live input level. Speech sits around −25…−45 dBFS. */
102
110
  inputLevelDbfs: number;
103
- /** True after 10 s of silence while unmuted a dead/disconnected mic.
111
+ /** True after 10 s of silence while unmuted: a dead/disconnected mic.
104
112
  * Surface this loudly; it is fixable mid-call. */
105
113
  inputSilent: boolean;
106
- /** Samples at digital full scale a too-hot mic, irreversibly clipped. */
114
+ /** Samples at digital full scale: a too-hot mic, irreversibly clipped. */
107
115
  fullScaleSamples: number;
108
116
  }
109
117
 
@@ -141,7 +149,7 @@ export interface CrowdPlayEvents {
141
149
  /** Connection lifecycle. `error` is set when phase === 'failed'. */
142
150
  phase: { phase: Phase; error?: string };
143
151
  /** Recording state. `error` non-null means the session is NOT being
144
- * captured show it prominently and offer retryRecording(). */
152
+ * captured; show it prominently and offer retryRecording(). */
145
153
  recording: { isRecording: boolean; error?: string };
146
154
  /** Upload progress for every session with outstanding or recent work. */
147
155
  uploads: { sessions: UploadProgress[]; onWifi: boolean };
@@ -189,8 +197,8 @@ function native(): NativeCrowdPlay {
189
197
  "crowdplaysdk: native module not found. iOS setup: add the CrowdPlaySDK " +
190
198
  "Swift package (https://github.com/symbiateam/crowdplaysdk) to your Xcode " +
191
199
  "project and add the two bridge files from node_modules/crowdplaysdk/ios " +
192
- "to your app target see the package README. " +
193
- (Platform.OS !== 'ios' ? `(platform '${Platform.OS}' is not supported yet iOS only.)` : '')
200
+ "to your app target; see the package README. " +
201
+ (Platform.OS !== 'ios' ? `(platform '${Platform.OS}' is not supported yet; iOS only.)` : '')
194
202
  );
195
203
  }
196
204
  return module;
@@ -240,7 +248,7 @@ const CrowdPlay = {
240
248
  * Join a room. Recording starts automatically on join and stops on
241
249
  * leave(). Rejects if the connection fails.
242
250
  *
243
- * `consent` is REQUIRED produce it with <CrowdPlayConsentScreen> or from
251
+ * `consent` is REQUIRED; produce it with <CrowdPlayConsentScreen> or from
244
252
  * your own UI at the moment of agreement. There is no way to record
245
253
  * without it, here or natively.
246
254
  */