From 5dd0faa76aa4a259326fbe0db3cbba9efbdf1d6e Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Feb 2026 15:18:18 -0800 Subject: [PATCH 1/2] Add AudioSink API for capturing call playback audio This adds the ability to capture audio samples directly from WebRTC's audio device module before they are played to the speaker. This enables call recording without requiring system audio loopback permissions. Changes: - Add AudioSink trait in media.rs (similar to existing VideoSink) - Add audio_sink field and SetAudioSink event to AudioDeviceModule - Modify init_playout() data_callback to send samples to registered sink - Add set_audio_sink() method to PeerConnectionFactory - Add LastAudioFramesSink buffer in electron.rs for JS polling - Export setAudioCaptureEnabled() and receiveAudioSamples() to Node.js - Add TypeScript bindings in Service.ts for Call and GroupCall classes API usage: call.setAudioCaptureEnabled(true); const result = call.receiveAudioSamples(int16Buffer); // result: { samplesWritten: number, sampleRate: number } | undefined --- src/node/ringrtc/Service.ts | 40 ++++++ src/rust/src/electron.rs | 123 ++++++++++++++++++ src/rust/src/webrtc/audio_device_module.rs | 37 ++++++ src/rust/src/webrtc/media.rs | 16 +++ .../src/webrtc/peer_connection_factory.rs | 16 +++ 5 files changed, 232 insertions(+) diff --git a/src/node/ringrtc/Service.ts b/src/node/ringrtc/Service.ts index 52c2615e..d2a3b8a5 100644 --- a/src/node/ringrtc/Service.ts +++ b/src/node/ringrtc/Service.ts @@ -116,6 +116,10 @@ class NativeCallManager { (NativeCallManager.prototype as any).sendVideoFrame = Native.cm_sendVideoFrame; (NativeCallManager.prototype as any).receiveVideoFrame = Native.cm_receiveVideoFrame; +(NativeCallManager.prototype as any).setAudioCaptureEnabled = + Native.cm_setAudioCaptureEnabled; +(NativeCallManager.prototype as any).receiveAudioSamples = + Native.cm_receiveAudioSamples; (NativeCallManager.prototype as any).receiveGroupCallVideoFrame = Native.cm_receiveGroupCallVideoFrame; (NativeCallManager.prototype as any).createGroupCallClient = @@ -2290,6 +2294,20 @@ export class Call { return this._callManager.receiveVideoFrame(buffer, maxWidth, maxHeight); } + // Enable or disable audio capture from the call. + // When enabled, call audio will be buffered and can be retrieved via receiveAudioSamples. + setAudioCaptureEnabled(enabled: boolean): void { + this._callManager.setAudioCaptureEnabled(enabled); + } + + // Receive audio samples from the call. + // Returns { samplesWritten: number, sampleRate: number } or undefined if no samples available. + receiveAudioSamples( + buffer: Int16Array + ): { samplesWritten: number; sampleRate: number } | undefined { + return this._callManager.receiveAudioSamples(buffer); + } + updateDataMode(dataMode: DataMode): void { sillyDeadlockProtection(() => { try { @@ -2567,6 +2585,20 @@ export class GroupCall { this._observer.onLocalDeviceStateChanged(this); } + // Enable or disable audio capture from the call. + // When enabled, call audio will be buffered and can be retrieved via receiveAudioSamples. + setAudioCaptureEnabled(enabled: boolean): void { + this._callManager.setAudioCaptureEnabled(enabled); + } + + // Receive audio samples from the call. + // Returns { samplesWritten: number, sampleRate: number } or undefined if no samples available. + receiveAudioSamples( + buffer: Int16Array + ): { samplesWritten: number; sampleRate: number } | undefined { + return this._callManager.receiveAudioSamples(buffer); + } + // Called by UI setOutgoingAudioMutedRemotely(source: number): void { this._localDeviceState.audioMuted = true; @@ -2999,6 +3031,14 @@ export interface CallManager { maxWidth: number, maxHeight: number ): [number, number] | undefined; + // Enable or disable audio capture from the call. + // When enabled, call audio will be buffered and can be retrieved via receiveAudioSamples. + setAudioCaptureEnabled(enabled: boolean): void; + // Receive audio samples from the call. + // Returns { samplesWritten: number, sampleRate: number } or undefined if no samples available. + receiveAudioSamples( + buffer: Int16Array + ): { samplesWritten: number; sampleRate: number } | undefined; receivedOffer( remoteUserId: UserId, remoteDeviceId: DeviceId, diff --git a/src/rust/src/electron.rs b/src/rust/src/electron.rs index 26a4afa8..6040dfaf 100644 --- a/src/rust/src/electron.rs +++ b/src/rust/src/electron.rs @@ -417,6 +417,10 @@ pub struct CallEndpoint { outgoing_video_track: VideoTrack, // Boxed so we can pass it as a Box incoming_video_sink: Box, + // Audio sink for capturing playback audio (Observer Vault) + incoming_audio_sink: Box, + // Whether audio capture is enabled + audio_capture_enabled: bool, peer_connection_factory: PeerConnectionFactory, @@ -509,6 +513,7 @@ impl CallEndpoint { peer_connection_factory.create_outgoing_video_track(&outgoing_video_source)?; outgoing_video_track.set_enabled(false); let incoming_video_sink = Box::::default(); + let incoming_audio_sink = Box::::default(); // After initializing logs, log the backend in use. let backend = peer_connection_factory.audio_backend(); @@ -540,6 +545,8 @@ impl CallEndpoint { outgoing_video_source, outgoing_video_track, incoming_video_sink, + incoming_audio_sink, + audio_capture_enabled: false, peer_connection_factory, js_object, most_recent_overlarge_frame_dimensions: (0, 0), @@ -578,6 +585,54 @@ impl LastFramesVideoSink { } } +/// Audio buffer that stores samples for JavaScript to poll. +/// Samples are appended by the audio device module and popped by JavaScript. +#[derive(Clone, Default, Debug)] +struct LastAudioFramesSink { + /// Circular buffer of audio samples (48kHz, mono, i16) + samples: Arc>>, + /// The sample rate of the audio + sample_rate: Arc>, +} + +impl crate::webrtc::media::AudioSink for LastAudioFramesSink { + fn on_audio_samples(&self, samples: &[i16], sample_rate: u32) { + let mut buffer = self.samples.lock().unwrap(); + // Limit buffer size to ~1 second of audio to prevent unbounded growth + const MAX_SAMPLES: usize = 48000; + if buffer.len() + samples.len() > MAX_SAMPLES { + // Drop oldest samples to make room + let to_remove = buffer.len() + samples.len() - MAX_SAMPLES; + let drain_count = to_remove.min(buffer.len()); + buffer.drain(0..drain_count); + } + buffer.extend_from_slice(samples); + + let mut rate = self.sample_rate.lock().unwrap(); + *rate = sample_rate; + } + + fn box_clone(&self) -> Box { + Box::new(self.clone()) + } +} + +impl LastAudioFramesSink { + /// Pop up to `max_samples` audio samples from the buffer. + fn pop(&self, max_samples: usize) -> (Vec, u32) { + let mut buffer = self.samples.lock().unwrap(); + let sample_rate = *self.sample_rate.lock().unwrap(); + + let count = max_samples.min(buffer.len()); + let samples: Vec = buffer.drain(0..count).collect(); + (samples, sample_rate) + } + + fn clear(&self) { + self.samples.lock().unwrap().clear(); + } +} + fn js_num_to_u64(num: f64) -> u64 { // Convert safely from signed. num as i32 as u32 as u64 @@ -1464,6 +1519,72 @@ fn receiveVideoFrame(mut cx: FunctionContext) -> JsResult { receive_video_frame(&mut cx, rgba_buffer, 0, max_width, max_height) } +/// Enable or disable audio capture from the call. +/// When enabled, call audio will be buffered and can be retrieved via receiveAudioSamples. +/// This allows capturing call audio directly without system audio loopback. +#[allow(non_snake_case)] +fn setAudioCaptureEnabled(mut cx: FunctionContext) -> JsResult { + let enabled = cx.argument::(0)?.value(&mut cx); + + with_call_endpoint(&mut cx, |endpoint| { + if enabled && !endpoint.audio_capture_enabled { + // Enable audio capture - set the audio sink on the audio device module + // Clone the inner sink and box it + let sink: Box = + Box::new((*endpoint.incoming_audio_sink).clone()); + endpoint.peer_connection_factory.set_audio_sink(Some(sink))?; + endpoint.audio_capture_enabled = true; + info!("Audio capture enabled"); + } else if !enabled && endpoint.audio_capture_enabled { + // Disable audio capture + endpoint.peer_connection_factory.set_audio_sink(None)?; + endpoint.incoming_audio_sink.clear(); + endpoint.audio_capture_enabled = false; + info!("Audio capture disabled"); + } + Ok(()) + }) + .or_else(|err: anyhow::Error| cx.throw_error(format!("{}", err)))?; + + Ok(cx.undefined()) +} + +/// Receive audio samples from the call. +/// Takes a pre-allocated Int16Array buffer and fills it with samples. +/// Returns an object with { samplesWritten: number, sampleRate: number } or undefined if no samples available. +#[allow(non_snake_case)] +fn receiveAudioSamples(mut cx: FunctionContext) -> JsResult { + let mut buffer = cx.argument::>(0)?; + let max_samples = buffer.len(&mut cx); + + let result: anyhow::Result<(Vec, u32)> = with_call_endpoint(&mut cx, |endpoint| { + let (samples, sample_rate) = endpoint.incoming_audio_sink.pop(max_samples); + Ok((samples, sample_rate)) + }); + + match result { + Ok((samples, sample_rate)) => { + if samples.is_empty() { + Ok(cx.undefined().upcast()) + } else { + // Copy samples to the provided buffer + let slice = buffer.as_mut_slice(&mut cx); + let count = samples.len().min(slice.len()); + slice[..count].copy_from_slice(&samples[..count]); + + let js_samples_written = cx.number(count as f64); + let js_sample_rate = cx.number(sample_rate); + + let result = cx.empty_object(); + result.set(&mut cx, "samplesWritten", js_samples_written)?; + result.set(&mut cx, "sampleRate", js_sample_rate)?; + Ok(result.upcast()) + } + } + Err(err) => cx.throw_error(format!("{}", err)), + } +} + // Group Calls #[allow(non_snake_case)] @@ -3278,6 +3399,8 @@ fn register(mut cx: ModuleContext) -> NeonResult<()> { )?; cx.export_function("cm_sendVideoFrame", sendVideoFrame)?; cx.export_function("cm_receiveVideoFrame", receiveVideoFrame)?; + cx.export_function("cm_setAudioCaptureEnabled", setAudioCaptureEnabled)?; + cx.export_function("cm_receiveAudioSamples", receiveAudioSamples)?; cx.export_function("cm_receiveGroupCallVideoFrame", receiveGroupCallVideoFrame)?; cx.export_function("cm_createGroupCallClient", createGroupCallClient)?; cx.export_function("cm_createCallLinkCallClient", createCallLinkCallClient)?; diff --git a/src/rust/src/webrtc/audio_device_module.rs b/src/rust/src/webrtc/audio_device_module.rs index eccfa472..e5fa17cb 100644 --- a/src/rust/src/webrtc/audio_device_module.rs +++ b/src/rust/src/webrtc/audio_device_module.rs @@ -97,6 +97,7 @@ enum Event { PlayoutDelay, Terminate, RegisterAudioObserver(Box), + SetAudioSink(Option>), } #[derive(Debug, Clone)] @@ -126,6 +127,8 @@ struct Worker { audio_transport: Arc>, audio_device_observer: Option>, send_to_webrtc: Arc, + // Optional audio sink for capturing playback audio (Observer Vault) + audio_sink: Option>>>>, } impl Worker { @@ -280,6 +283,8 @@ impl Worker { .take(); let mut builder = cubeb::StreamBuilder::::new(); let transport = Arc::clone(&self.audio_transport); + // Clone the audio sink Arc for use in the closure + let audio_sink = self.audio_sink.clone(); let min_latency = self.ctx.min_latency(¶ms).unwrap_or_else(|e| { warn!( "Could not get min latency for playout; using default: {:?}", @@ -334,6 +339,16 @@ impl Worker { error!("need_more_play_data returned too much data"); return -1; } + + // Send audio to the audio sink if one is registered (Observer Vault) + if let Some(ref sink_arc) = audio_sink { + if let Ok(sink_guard) = sink_arc.lock() { + if let Some(ref sink) = *sink_guard { + sink.on_audio_samples(&play_data.data, SAMPLE_FREQUENCY); + } + } + } + // Put data into the right format and add it to the output // array for cubeb to play. // If there's more data than was requested, add it to the @@ -638,6 +653,14 @@ impl Worker { self.audio_device_observer = Some(audio_device_observer); continue; } + Event::SetAudioSink(sink) => { + if let Some(ref sink_arc) = self.audio_sink { + if let Ok(mut guard) = sink_arc.lock() { + *guard = sink; + } + } + continue; + } } { warn!("{:?} failed: {:?}", received, e); } @@ -816,6 +839,7 @@ impl Worker { })), audio_device_observer: None, send_to_webrtc: Arc::new(AtomicBool::new(true)), + audio_sink: Some(Arc::new(Mutex::new(None))), }; if let Err(e) = worker.register_device_collection_changed(DeviceType::INPUT) { error!("Failed to register input device callback: {}", e); @@ -1677,6 +1701,19 @@ impl AudioDeviceModule { } Ok(()) } + + /// Set an audio sink to receive playback audio samples. + /// This allows capturing call audio directly from WebRTC without system permissions. + /// Pass None to remove an existing sink. + pub fn set_audio_sink( + &mut self, + sink: Option>, + ) -> anyhow::Result<()> { + if let Err(e) = self.mpsc_sender.send(Event::SetAudioSink(sink)) { + bail!("Failed to send SetAudioSink request: {}", e); + } + Ok(()) + } } #[cfg(test)] diff --git a/src/rust/src/webrtc/media.rs b/src/rust/src/webrtc/media.rs index d15e9d64..05fa4649 100644 --- a/src/rust/src/webrtc/media.rs +++ b/src/rust/src/webrtc/media.rs @@ -333,6 +333,22 @@ impl Clone for Box { } } +/// Trait for receiving audio samples from the audio device module. +/// This allows capturing the audio that would be played to the speaker. +pub trait AudioSink: Sync + Send + std::fmt::Debug { + /// Called with audio samples that are about to be played. + /// samples: interleaved PCM samples (mono i16) + /// sample_rate: samples per second (typically 48000) + fn on_audio_samples(&self, samples: &[i16], sample_rate: u32); + fn box_clone(&self) -> Box; +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.box_clone() + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "call_sim", derive(clap::ValueEnum))] #[repr(i32)] diff --git a/src/rust/src/webrtc/peer_connection_factory.rs b/src/rust/src/webrtc/peer_connection_factory.rs index a66b367a..7b416a5a 100644 --- a/src/rust/src/webrtc/peer_connection_factory.rs +++ b/src/rust/src/webrtc/peer_connection_factory.rs @@ -752,4 +752,20 @@ impl PeerConnectionFactory { }; Ok(()) } + + /// Set an audio sink to receive playback audio samples. + /// This allows capturing call audio directly from WebRTC without system permissions. + /// Pass None to remove an existing sink. + #[cfg(all(not(feature = "sim"), feature = "native"))] + pub fn set_audio_sink( + &mut self, + sink: Option>, + ) -> Result<()> { + self.adm + .as_ref() + .and_then(|adm| adm.lock().ok()) + .map_or(Err(anyhow!("couldn't access ADM")), |mut adm| { + adm.set_audio_sink(sink) + }) + } } From 89747ee31304198e3da7dc675eca2587bf7e6cf6 Mon Sep 17 00:00:00 2001 From: Micah Lee Date: Sun, 1 Feb 2026 17:00:53 -0800 Subject: [PATCH 2/2] Rebrand as @lockdown-systems/ringrtc with GitHub Releases publishing Change package name from @signalapp/ringrtc to @lockdown-systems/ringrtc and set up automated publishing workflow for the fork. Changes: - Update package name to @lockdown-systems/ringrtc - Bump version to 2.63.0-audiosink.1 to indicate fork with audio capture - Change repository URL to lockdown-systems GitHub organization - Update prebuildUrl to download from GitHub Releases instead of Signal's GCS - Add GitHub Actions workflow for automated releases on version tags - Builds native binaries for Linux (x64, arm64), macOS (x64, arm64), and Windows (x64, arm64) - Creates GitHub Release with prebuild tarball - Publishes to npm with checksum validation This enables publishing the AudioSink API additions as a separate npm package that can be consumed by Observer Vault. --- .github/workflows/lockdown_release.yml | 247 +++++++++++++++++++++++++ src/node/package-lock.json | 8 +- src/node/package.json | 8 +- 3 files changed, 255 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/lockdown_release.yml diff --git a/.github/workflows/lockdown_release.yml b/.github/workflows/lockdown_release.yml new file mode 100644 index 00000000..2c2e13f6 --- /dev/null +++ b/.github/workflows/lockdown_release.yml @@ -0,0 +1,247 @@ +name: Lockdown Systems Release + +on: + push: + tags: + - "v*" + +env: + CARGO_TERM_COLOR: always + +jobs: + build_linux_x64: + name: Build Linux x64 + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y protobuf-compiler libpulse-dev cmake build-essential + + - name: Install Rust + run: | + curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + + - uses: actions/setup-node@v4 + with: + node-version-file: "src/node/.nvmrc" + + - name: Fetch WebRTC artifact + run: ./bin/fetch-artifact --platform linux-x64 --release + + - name: Build RingRTC + run: ./bin/build-desktop --ringrtc-only --release + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-linux-x64 + path: src/node/build/ + retention-days: 1 + + build_linux_arm64: + name: Build Linux ARM64 + runs-on: ubuntu-22.04-arm64-4-cores + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y protobuf-compiler libpulse-dev cmake build-essential + + - uses: actions/setup-node@v4 + with: + node-version-file: "src/node/.nvmrc" + + - name: Fetch WebRTC artifact + run: ./bin/fetch-artifact --platform linux-arm64 --release + + - name: Build RingRTC + run: TARGET_ARCH=arm64 ./bin/build-desktop --ringrtc-only --release + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-linux-arm64 + path: src/node/build/ + retention-days: 1 + + build_macos: + name: Build macOS (x64 + ARM64) + runs-on: macos-14 + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: brew install protobuf cmake + + - uses: actions/setup-node@v4 + with: + node-version-file: "src/node/.nvmrc" + + # Build x64 + - name: Fetch WebRTC artifact (x64) + run: ./bin/fetch-artifact --platform mac-x64 --release + + - name: Build RingRTC (x64) + run: TARGET_ARCH=x64 ./bin/build-desktop --ringrtc-only --release + + # Build ARM64 + - name: Fetch WebRTC artifact (ARM64) + run: ./bin/fetch-artifact --platform mac-arm64 --release -o out-arm + + - name: Build RingRTC (ARM64) + run: OUTPUT_DIR=out-arm TARGET_ARCH=arm64 ./bin/build-desktop --ringrtc-only --release + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-macos + path: src/node/build/ + retention-days: 1 + + build_windows: + name: Build Windows (x64 + ARM64) + runs-on: windows-2022 + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + run: | + rustup toolchain install stable --profile minimal + rustup target add aarch64-pc-windows-msvc + + - name: Install protoc + run: choco install protoc + shell: cmd + + - uses: actions/setup-node@v4 + with: + node-version-file: "src/node/.nvmrc" + + # Build x64 + - name: Fetch WebRTC artifact (x64) + run: sh ./bin/fetch-artifact --platform windows-x64 --release + + - name: Build RingRTC (x64) + run: sh ./bin/build-desktop --ringrtc-only --release + + # Build ARM64 + - name: Fetch WebRTC artifact (ARM64) + run: sh ./bin/fetch-artifact --platform windows-arm64 --release -o out-arm + + - name: Build RingRTC (ARM64) + run: | + echo "TARGET_ARCH=arm64" >> $env:GITHUB_ENV + echo "OUTPUT_DIR=out-arm" >> $env:GITHUB_ENV + + - name: Build RingRTC (ARM64) - continued + run: sh ./bin/build-desktop --ringrtc-only --release + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-windows + path: src/node/build/ + retention-days: 1 + + publish: + name: Publish to npm + needs: [build_linux_x64, build_linux_arm64, build_macos, build_windows] + runs-on: ubuntu-latest + + permissions: + contents: write # Needed for creating releases + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version-file: "src/node/.nvmrc" + registry-url: "https://registry.npmjs.org/" + + # Download all build artifacts + - name: Download Linux x64 build + uses: actions/download-artifact@v4 + with: + name: build-linux-x64 + path: src/node/build/ + + - name: Download Linux ARM64 build + uses: actions/download-artifact@v4 + with: + name: build-linux-arm64 + path: src/node/build/ + + - name: Download macOS build + uses: actions/download-artifact@v4 + with: + name: build-macos + path: src/node/build/ + + - name: Download Windows build + uses: actions/download-artifact@v4 + with: + name: build-windows + path: src/node/build/ + + - name: List build directory + run: ls -la src/node/build/ + + # Determine version from package.json + - name: Determine version + id: version + run: | + VERSION=$(jq -r .version src/node/package.json) + echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Create tarball for GitHub Releases + - name: Create prebuild archive + run: tar czf "ringrtc-desktop-build-v${{ steps.version.outputs.version }}.tar.gz" build + working-directory: src/node + + - name: Calculate checksum + id: checksum + run: | + CHECKSUM=$(sha256sum "ringrtc-desktop-build-v${{ steps.version.outputs.version }}.tar.gz" | cut -d ' ' -f 1) + echo "sha256=$CHECKSUM" >> $GITHUB_OUTPUT + echo "Checksum: $CHECKSUM" + working-directory: src/node + + # Upload to GitHub Releases + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: src/node/ringrtc-desktop-build-v${{ steps.version.outputs.version }}.tar.gz + generate_release_notes: true + + # Update package.json with checksum + - name: Update prebuild checksum + run: | + sed -i 's/"prebuildChecksum": ""/"prebuildChecksum": "${{ steps.checksum.outputs.sha256 }}"/' package.json + working-directory: src/node + + # Install and build + - name: Install dependencies + run: npm ci + working-directory: src/node + + - name: Build TypeScript + run: npm run build + working-directory: src/node + + # Publish to npm + - name: Publish to npm + run: npm publish --access public + working-directory: src/node + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/src/node/package-lock.json b/src/node/package-lock.json index 0fb1524e..5a6233a3 100644 --- a/src/node/package-lock.json +++ b/src/node/package-lock.json @@ -1,12 +1,12 @@ { - "name": "@signalapp/ringrtc", - "version": "2.63.0", + "name": "@lockdown-systems/ringrtc", + "version": "2.63.0-audiosink.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@signalapp/ringrtc", - "version": "2.63.0", + "name": "@lockdown-systems/ringrtc", + "version": "2.63.0-audiosink.1", "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { diff --git a/src/node/package.json b/src/node/package.json index c66abd0c..9c69bbcd 100644 --- a/src/node/package.json +++ b/src/node/package.json @@ -1,9 +1,9 @@ { - "name": "@signalapp/ringrtc", - "version": "2.63.0", + "name": "@lockdown-systems/ringrtc", + "version": "2.63.0-audiosink.1", "repository": { "type": "git", - "url": "https://github.com/signalapp/ringrtc.git", + "url": "https://github.com/lockdown-systems/ringrtc.git", "directory": "src/node" }, "description": "Signal Messenger voice and video calling library.", @@ -32,7 +32,7 @@ "prepublishOnly": "node scripts/prepublish.js" }, "config": { - "prebuildUrl": "https://build-artifacts.signal.org/libraries/ringrtc-desktop-build-v${npm_package_version}.tar.gz", + "prebuildUrl": "https://github.com/lockdown-systems/ringrtc/releases/download/v${npm_package_version}/ringrtc-desktop-build-v${npm_package_version}.tar.gz", "prebuildChecksum": "" }, "author": "",