Add AssetRegistry to call state, expose addAsset()

This commit is contained in:
adel-signal 2026-03-01 22:16:25 -08:00 committed by GitHub
parent 0db47267d0
commit e3c46a7cba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 268 additions and 3 deletions

View File

@ -337,6 +337,44 @@ public class CallManager {
ringrtcSetSelfUuid(nativeCallManager, Util.getBytesFromUuid(uuid));
}
/**
*
* Adds an asset to the asset manager by file path.
*
* @param assetGroup The asset identifier
* @param filePath Path to the asset file on disk
*
* @throws CallException for native code failures
*
*/
public void addAsset(@NonNull String assetGroup, @NonNull String filePath)
throws CallException
{
checkCallManagerExists();
Log.i(TAG, "addAsset(): by filePath");
ringrtcAddAsset(nativeCallManager, assetGroup, filePath, null);
}
/**
*
* Adds an asset to the asset manager by byte content.
*
* @param assetGroup The asset identifier
* @param content The raw asset bytes
*
* @throws CallException for native code failures
*
*/
public void addAsset(@NonNull String assetGroup, @NonNull byte[] content)
throws CallException
{
checkCallManagerExists();
Log.i(TAG, "addAsset(): by content");
ringrtcAddAsset(nativeCallManager, assetGroup, null, content);
}
/**
*
* Indication from application to start a new outgoing call
@ -2445,6 +2483,10 @@ public class CallManager {
void ringrtcSetSelfUuid(long nativeCallManager, byte[] uuid)
throws CallException;
private native
void ringrtcAddAsset(long nativeCallManager, String assetGroup, String filePath, byte[] content)
throws CallException;
private native
long ringrtcCreatePeerConnection(long nativePeerConnectionFactory,
long nativeConnection,

View File

@ -448,6 +448,42 @@ public class CallManager<CallType, CallManagerDelegateType>: CallManagerInterfac
}
}
@MainActor
public func addAsset(assetGroup: String, filePath: String) throws {
Logger.debug("addAsset by filePath")
let assetGroupSlice = allocatedAppByteSliceFromString(maybe_string: assetGroup)
let filePathSlice = allocatedAppByteSliceFromString(maybe_string: filePath)
let emptySlice = AppByteSlice(bytes: nil, len: 0)
defer {
assetGroupSlice.bytes?.deallocate()
filePathSlice.bytes?.deallocate()
}
let retPtr = ringrtcAddAsset(ringRtcCallManager, assetGroupSlice, filePathSlice, emptySlice)
if retPtr == nil {
throw CallManagerError.apiFailed(description: "addAsset() by filepath function failure")
}
}
@MainActor
public func addAsset(assetGroup: String, content: Data) throws {
Logger.debug("addAsset by content")
let assetGroupSlice = allocatedAppByteSliceFromString(maybe_string: assetGroup)
let emptySlice = AppByteSlice(bytes: nil, len: 0)
let contentSlice = allocatedAppByteSliceFromData(maybe_data: content)
defer {
assetGroupSlice.bytes?.deallocate()
contentSlice.bytes?.deallocate()
}
let retPtr = ringrtcAddAsset(ringRtcCallManager, assetGroupSlice, emptySlice, contentSlice)
if retPtr == nil {
throw CallManagerError.apiFailed(description: "addAsset() by content function failure")
}
}
deinit {
// Close the RingRTC Call Manager.
let retPtr = ringrtcClose(self.ringRtcCallManager)

View File

@ -79,6 +79,7 @@ class NativeCallManager {
// Mirror methods onto NativeCallManager.
// This is done through direct assignment rather than wrapper methods to avoid indirection.
(NativeCallManager.prototype as any).setSelfUuid = Native.cm_setSelfUuid;
(NativeCallManager.prototype as any).addAsset = Native.cm_addAsset;
(NativeCallManager.prototype as any).createOutgoingCall =
Native.cm_createOutgoingCall;
(NativeCallManager.prototype as any).proceed = Native.cm_proceed;
@ -494,6 +495,16 @@ export class RingRTCType {
this.callManager.setSelfUuid(uuid);
}
// Called by UX
addAsset(
assetGroup: string,
asset: { filePath: string } | { content: Uint8Array }
): void {
const filePath = 'filePath' in asset ? asset.filePath : null;
const content = 'content' in asset ? asset.content : null;
this.callManager.addAsset(assetGroup, filePath, content);
}
// Called by UX
startOutgoingCall(
remoteUserId: UserId,
@ -2940,6 +2951,11 @@ export enum RingCancelReason {
export interface CallManager {
setConfig(config: Config): void;
setSelfUuid(uuid: Uint8Array): void;
addAsset(
assetGroup: string,
filePath: string | null,
content: Uint8Array | null
): void;
createOutgoingCall(
remoteUserId: UserId,
isVideoCall: boolean,

View File

@ -111,6 +111,30 @@ pub unsafe extern "C" fn Java_org_signal_ringrtc_CallManager_ringrtcSetSelfUuid(
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn Java_org_signal_ringrtc_CallManager_ringrtcAddAsset(
mut env: JNIEnv,
_object: JObject,
call_manager: jlong,
asset_group: JString,
file_path: JString,
content: JByteArray,
) {
match call_manager::add_asset(
&mut env,
call_manager as *mut AndroidCallManager,
asset_group,
file_path,
content,
) {
Ok(v) => v,
Err(e) => {
error::throw_error(&mut env, e);
}
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub unsafe extern "C" fn Java_org_signal_ringrtc_CallManager_ringrtcCall(

View File

@ -169,6 +169,33 @@ pub fn set_self_uuid(
call_manager.set_self_uuid(env.convert_byte_array(uuid)?)
}
/// Adds an asset to the asset manager.
pub fn add_asset(
env: &mut JNIEnv,
call_manager: *mut AndroidCallManager,
asset_group: JString,
file_path: JString,
content: JByteArray,
) -> Result<()> {
use crate::core::assets::AssetHandle;
let asset_group: String = env.get_string(&asset_group)?.into();
let handle = if !content.is_null() {
AssetHandle::Content(env.convert_byte_array(content)?)
} else if !file_path.is_null() {
let path: String = env.get_string(&file_path)?.into();
AssetHandle::FilePath(path)
} else {
return Err(anyhow::anyhow!(
"addAsset requires either a filePath or content"
));
};
let call_manager = unsafe { ptr_as_mut(call_manager)? };
call_manager.add_asset(&asset_group, handle)
}
/// Application notification to start a new call
pub fn call(
env: &JNIEnv,

View File

@ -14,6 +14,7 @@ use log::info;
use ringrtc::{
common::{CallEndReason, units::DataRate},
core::{
assets::AssetRegistry,
call_mutex::CallMutex,
call_summary::CallSummary,
endorsements::EndorsementUpdateResultRef,
@ -26,8 +27,7 @@ use ringrtc::{
http::sim as sim_http,
sfu::{DemuxId, MemberMap, ObfuscatedResolver, PeekInfo, UserId},
},
protobuf,
protobuf::signaling::CallMessage,
protobuf::{self, signaling::CallMessage},
webrtc::{
media::{VideoFrame, VideoFrameMetadata, VideoPixelFormat, VideoSink, VideoTrack},
peer_connection::{AudioLevel, ReceivedAudioLevel, SendRates},
@ -302,6 +302,7 @@ fn main() {
ring_id: None,
audio_levels_interval: None,
group_send_endorsement_cache: None,
asset_registry: AssetRegistry::default(),
})
.unwrap();

View File

@ -26,6 +26,7 @@ use crate::{
actor::{Actor, Stopper},
},
core::{
assets::AssetRegistry,
call_fsm::{CallEvent, CallStateMachine},
call_manager::CallManager,
call_mutex::CallMutex,
@ -110,6 +111,8 @@ where
forking: Arc<CallMutex<Option<ForkingState<T>>>>,
/// Captures call statistics for reporting.
call_summary: DirectCallSummary,
/// Registry for accessing large, pre-loaded assets
asset_registry: AssetRegistry,
}
impl<T> fmt::Display for Call<T>
@ -192,6 +195,7 @@ where
),
forking: Arc::clone(&self.forking),
call_summary: self.call_summary.clone(),
asset_registry: self.asset_registry.clone(),
}
}
}
@ -210,6 +214,7 @@ where
call_manager: CallManager<T>,
) -> Result<Self> {
info!("new(): call_id: {}", call_id);
let asset_registry = call_manager.asset_registry()?;
// create a FSM worker for this connection
let (fsm_sender, fsm_receiver) = std::sync::mpsc::sync_channel(256);
@ -237,6 +242,7 @@ where
did_notify_application_of_remote_ringing: Arc::new(AtomicBool::new(false)),
forking: Arc::new(CallMutex::new(None, "forking")),
call_summary: DirectCallSummary::default(),
asset_registry,
};
Ok(call)

View File

@ -28,8 +28,10 @@ use crate::{
actor::{Actor, Stopper},
},
core::{
assets::{self, AssetManager, AssetRegistry},
call::Call,
call_mutex::CallMutex,
call_rwlock::CallRwLock,
call_summary::CallSummary,
connection::{Connection, ConnectionType},
endorsements::{EndorsementUpdateResultRef, EndorsementsCache},
@ -417,6 +419,12 @@ where
message_queue: Arc<CallMutex<SignalingMessageQueue<T>>>,
/// How to make HTTP requests to the SFU for group calls.
http_client: http::DelegatingClient,
/// Asset manager that holds a copy of assets in memory
/// Assets are initialized before the start of a call by
/// the application using the platform addAsset() function
/// Manager can be used to inject a registry handle into
/// call state.
asset_manager: Arc<CallRwLock<AssetManager>>,
}
impl<T> fmt::Display for CallManager<T>
@ -482,6 +490,7 @@ where
worker: self.worker.clone(),
message_queue: Arc::clone(&self.message_queue),
http_client: self.http_client.clone(),
asset_manager: self.asset_manager.clone(),
}
}
}
@ -529,6 +538,10 @@ where
"message_queue",
)),
http_client,
asset_manager: Arc::new(CallRwLock::new(
AssetManager::new(assets::manifest::supported_assets()),
"asset-manager",
)),
})
}
@ -536,6 +549,19 @@ where
&self.http_client
}
pub fn asset_registry(&self) -> Result<AssetRegistry> {
Ok(self.asset_manager.read()?.get_registry())
}
/// Adds an asset to the asset manager, verifying it against the supported assets manifest.
pub fn add_asset(&self, asset_group: &str, handle: assets::AssetHandle) -> Result<()> {
info!("Adding asset for asset group {asset_group}");
self.asset_manager
.write()?
.add_asset_for_feature(asset_group, handle)?;
Ok(())
}
/// Updates the current user's UUID.
pub fn set_self_uuid(&mut self, uuid: UserId) -> Result<()> {
info!("set_self_uuid():");
@ -3041,6 +3067,8 @@ where
let obfuscated_resolver =
ObfuscatedResolver::new(Arc::new(MemberMap::new(&[])), None, None);
let asset_registry = { self.asset_manager.read()?.get_registry() };
let client = Client::start(ClientStartParams {
group_id,
client_id,
@ -3050,6 +3078,7 @@ where
obfuscated_resolver,
busy: self.busy.clone(),
self_uuid: self.self_uuid.clone(),
asset_registry,
peer_connection_factory,
outgoing_audio_track,
outgoing_video_track: Some(outgoing_video_track),
@ -3144,6 +3173,8 @@ where
Some(endorsements_public_key),
);
let asset_registry = { self.asset_manager.read()?.get_registry() };
let client = Client::start(ClientStartParams {
group_id: room_id,
client_id,
@ -3153,6 +3184,7 @@ where
obfuscated_resolver,
busy: self.busy.clone(),
self_uuid: self.self_uuid.clone(),
asset_registry,
peer_connection_factory,
outgoing_audio_track,
outgoing_video_track: Some(outgoing_video_track),

View File

@ -35,6 +35,7 @@ use crate::{
units::DataRate,
},
core::{
assets::AssetRegistry,
call_mutex::CallMutex,
call_summary::{CallSummary, GroupCallSummary},
crypto::{self as frame_crypto, DecryptionErrorStats},
@ -1014,6 +1015,9 @@ struct State {
// Shared state with the CallManager that might change
busy: Arc<CallMutex<bool>>,
self_uuid: Arc<CallMutex<Option<UserId>>>,
#[allow(dead_code)]
/// Registry of large, preloaded assets
asset_registry: AssetRegistry,
// State that changes regularly and is sent to the observer
connection_state: ConnectionState,
@ -1234,6 +1238,7 @@ pub struct ClientStartParams {
pub observer: Box<dyn Observer + Send>,
pub busy: Arc<CallMutex<bool>>,
pub self_uuid: Arc<CallMutex<Option<UserId>>>,
pub asset_registry: AssetRegistry,
pub peer_connection_factory: Option<PeerConnectionFactory>,
pub outgoing_audio_track: AudioTrack,
pub outgoing_video_track: Option<VideoTrack>,
@ -1254,6 +1259,7 @@ impl Client {
observer,
busy,
self_uuid,
asset_registry,
peer_connection_factory,
outgoing_audio_track,
outgoing_video_track,
@ -1342,6 +1348,7 @@ impl Client {
observer,
busy,
self_uuid,
asset_registry,
local_ice_ufrag,
local_ice_pwd,
@ -5326,7 +5333,7 @@ mod tests {
use super::*;
use crate::{
common::time::saturating_epoch_time,
core::endorsements::EndorsementUpdateResult,
core::{assets::AssetRegistry, endorsements::EndorsementUpdateResult},
lite::{
call_links::{CallLinkMemberResolver, CallLinkRootKey},
sfu::{MemberResolver, PeekDeviceInfo},
@ -6105,6 +6112,7 @@ mod tests {
let observer = FakeObserver::new(user_id.clone());
let fake_busy = Arc::new(CallMutex::new(false, "fake_busy"));
let fake_self_uuid = Arc::new(CallMutex::new(Some(user_id.clone()), "fake_self_uuid"));
let fake_asset_registry = AssetRegistry::default();
let fake_audio_track = AudioTrack::new(
webrtc::Arc::from_owned(unsafe {
webrtc::ptr::OwnedRc::from_ptr(&FAKE_AUDIO_TRACK as *const u32)
@ -6127,6 +6135,7 @@ mod tests {
observer: Box::new(observer.clone()),
busy: fake_busy,
self_uuid: fake_self_uuid,
asset_registry: fake_asset_registry,
peer_connection_factory: None,
outgoing_audio_track: fake_audio_track,
outgoing_video_track: None,

View File

@ -26,6 +26,7 @@ use strum::IntoDiscriminant;
use crate::{
common::{CallConfig, CallId, CallMediaType, DataMode, DeviceId, Result},
core::{
assets::AssetHandle,
call_manager::CallManager,
call_summary::{CallSummary, MediaQualityStats, QualityStats},
group_call::{self, GroupId, SignalingMessageUrgency},
@ -856,6 +857,33 @@ fn setSelfUuid(mut cx: FunctionContext) -> JsResult<JsValue> {
Ok(cx.undefined().upcast())
}
#[allow(non_snake_case)]
fn addAsset(mut cx: FunctionContext) -> JsResult<JsValue> {
debug!("JsCallManager.addAsset()");
let asset_group = cx.argument::<JsString>(0)?.value(&mut cx);
let file_path = cx
.argument_opt(1)
.and_then(|v| v.downcast::<JsString, _>(&mut cx).ok())
.map(|s| s.value(&mut cx));
let content = cx
.argument_opt(2)
.and_then(|v| v.downcast::<JsUint8Array, _>(&mut cx).ok())
.map(|buf| buf.as_slice(&cx).to_vec());
let handle = match (file_path, content) {
(_, Some(bytes)) => AssetHandle::Content(bytes),
(Some(path), _) => AssetHandle::FilePath(path),
_ => return cx.throw_error("addAsset requires either a filePath or content"),
};
with_call_endpoint(&mut cx, |endpoint| {
endpoint.call_manager.add_asset(&asset_group, handle)
})
.or_else(|err: anyhow::Error| cx.throw_error(format!("{}", err)))?;
Ok(cx.undefined().upcast())
}
#[allow(non_snake_case)]
fn createOutgoingCall(mut cx: FunctionContext) -> JsResult<JsValue> {
let peer_id = cx.argument::<JsString>(0)?.value(&mut cx) as PeerId;
@ -3165,6 +3193,7 @@ fn register(mut cx: ModuleContext) -> NeonResult<()> {
cx.export_value("callEndpointPropertyKey", js_property_key)?;
cx.export_function("cm_setSelfUuid", setSelfUuid)?;
cx.export_function("cm_addAsset", addAsset)?;
cx.export_function("cm_createOutgoingCall", createOutgoingCall)?;
cx.export_function("cm_cancelGroupRing", cancelGroupRing)?;
cx.export_function("cm_proceed", proceed)?;

View File

@ -611,6 +611,39 @@ pub extern "C" fn ringrtcSetSelfUuid(callManager: *mut c_void, uuid: AppByteSlic
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub extern "C" fn ringrtcAddAsset(
callManager: *mut c_void,
assetGroup: AppByteSlice,
filePath: AppByteSlice,
content: AppByteSlice,
) -> *mut c_void {
use crate::core::assets::AssetHandle;
let asset_group = match string_from_app_slice(&assetGroup) {
Some(id) => id,
None => {
error!("Missing assetGroup");
return ptr::null_mut();
}
};
let handle = if let Some(path) = string_from_app_slice(&filePath) {
AssetHandle::FilePath(path)
} else if let Some(bytes) = byte_vec_from_app_slice(&content) {
AssetHandle::Content(bytes)
} else {
error!("addAsset requires either a filePath or content");
return ptr::null_mut();
};
match call_manager::add_asset(callManager as *mut IosCallManager, asset_group, handle) {
Ok(_) => callManager,
Err(_) => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
#[allow(non_snake_case)]
pub extern "C" fn ringrtcCall(

View File

@ -51,6 +51,16 @@ pub fn set_self_uuid(call_manager: *mut IosCallManager, uuid: UserId) -> Result<
call_manager.set_self_uuid(uuid)
}
/// Adds an asset to the asset manager.
pub fn add_asset(
call_manager: *mut IosCallManager,
asset_group: String,
handle: crate::core::assets::AssetHandle,
) -> Result<()> {
let call_manager = unsafe { ptr_as_mut(call_manager)? };
call_manager.add_asset(&asset_group, handle)
}
/// Application notification to start a new call.
pub fn call(
call_manager: *mut IosCallManager,