Skip to content

Commit

Permalink
Reactions
Browse files Browse the repository at this point in the history
  • Loading branch information
inaqui-signal authored Sep 28, 2023
1 parent 25cf300 commit c617467
Show file tree
Hide file tree
Showing 24 changed files with 599 additions and 5 deletions.
11 changes: 11 additions & 0 deletions src/android/api/org/signal/ringrtc/CallManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -1682,6 +1682,17 @@ private void handleLowBandwidthForVideo(long clientId, boolean recovered) {
groupCall.handleLowBandwidthForVideo(recovered);
}

@CalledByNative
private void handleReactions(long clientId, List<GroupCall.Reaction> reactions) {
GroupCall groupCall = this.groupCallByClientId.get(clientId);
if (groupCall == null) {
Log.w(TAG, "groupCall not found by clientId: " + clientId);
return;
}

groupCall.handleReactions(reactions);
}

@CalledByNative
private void handleJoinStateChanged(long clientId, GroupCall.JoinState joinState) {
Log.i(TAG, "handleJoinStateChanged():");
Expand Down
46 changes: 46 additions & 0 deletions src/android/api/org/signal/ringrtc/GroupCall.java
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,21 @@ public void setMembershipProof(@NonNull byte[] proof)
ringrtcSetMembershipProof(nativeCallManager, this.clientId, proof);
}

/**
*
* Send a reaction to the group.
*
* @param value The string representing the reaction
*
* @throws CallException for native code failures
*/
public void react(@NonNull String value)
throws CallException {
Log.i(TAG, "react(): value: " + value);

ringrtcReact(nativeCallManager, this.clientId, value);
}

/**
*
* Callback from RingRTC when the group call object needs an updated
Expand Down Expand Up @@ -706,6 +721,10 @@ void handleLowBandwidthForVideo(boolean recovered) {
this.observer.onLowBandwidthForVideo(this, recovered);
}

void handleReactions(List<Reaction> reactions) {
this.observer.onReactions(this, reactions);
}

/**
*
* Callback from RingRTC when the remote device states have changed.
Expand Down Expand Up @@ -1124,6 +1143,19 @@ public VideoRequest( long demuxId,
}
}

/**
* A class used to store a reaction from a group member.
*/
public static class Reaction {
public long demuxId;
public @NonNull String value;

public Reaction(long demuxId, @NonNull String value) {
this.demuxId = demuxId;
this.value = value;
}
}

/**
* The client must provide an observer for each group call object
* which is used to convey callbacks and notifications from
Expand Down Expand Up @@ -1164,6 +1196,14 @@ public interface Observer {
*/
void onLowBandwidthForVideo(GroupCall groupCall, boolean recovered);

/**
* Notification that one or more reactions were received.
*
* @param reactions A list of reactions received by the client ordered
* from oldest to newest.
*/
void onReactions(GroupCall groupCall, List<Reaction> reactions);

/**
* Notification that the remote device states have changed.
*/
Expand Down Expand Up @@ -1302,4 +1342,10 @@ void ringrtcSetMembershipProof(long nativeCallManager,
long clientId,
byte[] proof)
throws CallException;

private native
void ringrtcReact(long nativeCallManager,
long clientId,
String value)
throws CallException;
}
14 changes: 14 additions & 0 deletions src/ios/SignalRingRTC/SignalRingRTC/CallManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,20 @@ public class CallManager<CallType, CallManagerDelegateType>: CallManagerInterfac
}
}

func handleReactions(clientId: UInt32, reactions: [Reaction]) {
Logger.debug("handleReactions")

DispatchQueue.main.async {
Logger.debug("handleReactions - main.async")

guard let groupCall = self.groupCallByClientId[clientId] else {
return
}

groupCall.handleReactions(reactions: reactions)
}
}

func handleJoinStateChanged(clientId: UInt32, joinState: JoinState) {
Logger.debug("handleJoinStateChanged")

Expand Down
36 changes: 36 additions & 0 deletions src/ios/SignalRingRTC/SignalRingRTC/CallManagerInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ protocol CallManagerInterfaceDelegate: AnyObject {
func handleNetworkRouteChanged(clientId: UInt32, networkRoute: NetworkRoute)
func handleAudioLevels(clientId: UInt32, capturedLevel: UInt16, receivedLevels: [ReceivedAudioLevel])
func handleLowBandwidthForVideo(clientId: UInt32, recovered: Bool)
func handleReactions(clientId: UInt32, reactions: [Reaction])
func handleJoinStateChanged(clientId: UInt32, joinState: JoinState)
func handleRemoteDevicesChanged(clientId: UInt32, remoteDeviceStates: [RemoteDeviceState])
func handleIncomingVideoTrack(clientId: UInt32, remoteDemuxId: UInt32, nativeVideoTrackBorrowedRc: UnsafeMutableRawPointer?)
Expand Down Expand Up @@ -92,6 +93,7 @@ class CallManagerInterface {
handleNetworkRouteChanged: callManagerInterfaceHandleNetworkRouteChanged,
handleAudioLevels: callManagerInterfaceHandleAudioLevels,
handleLowBandwidthForVideo: callManagerInterfaceHandleLowBandwidthForVideo,
handleReactions: callManagerInterfaceHandleReactions,
handleJoinStateChanged: callManagerInterfaceHandleJoinStateChanged,
handleRemoteDevicesChanged: callManagerInterfaceHandleRemoteDevicesChanged,
handleIncomingVideoTrack: callManagerInterfaceHandleIncomingVideoTrack,
Expand Down Expand Up @@ -298,6 +300,14 @@ class CallManagerInterface {
delegate.handleLowBandwidthForVideo(clientId: clientId, recovered: recovered)
}

func handleReactions(clientId: UInt32, reactions: [Reaction]) {
guard let delegate = self.callManagerObserverDelegate else {
return
}

delegate.handleReactions(clientId: clientId, reactions: reactions)
}

func handleJoinStateChanged(clientId: UInt32, joinState: JoinState) {
guard let delegate = self.callManagerObserverDelegate else {
return
Expand Down Expand Up @@ -877,6 +887,32 @@ func callManagerInterfaceHandleLowBandwidthForVideo(object: UnsafeMutableRawPoin
obj.handleLowBandwidthForVideo(clientId: clientId, recovered: recovered)
}

@available(iOSApplicationExtension, unavailable)
func callManagerInterfaceHandleReactions(object: UnsafeMutableRawPointer?, clientId: UInt32, reactions: AppReactionsArray) {
guard let object = object else {
owsFailDebug("object was unexpectedly nil")
return
}
let obj: CallManagerInterface = Unmanaged.fromOpaque(object).takeUnretainedValue()

var finalReactions: [Reaction] = []

for index in 0..<reactions.count {
let reaction = reactions.reactions[index]

guard let value = reaction.value.asString() else {
Logger.debug("missing reaction for demuxId: 0x\(String(reaction.demuxId, radix: 16))")
continue
}

finalReactions.append(Reaction(demuxId: reaction.demuxId, value: value))
}

if !finalReactions.isEmpty {
obj.handleReactions(clientId: clientId, reactions: finalReactions)
}
}

@available(iOSApplicationExtension, unavailable)
func callManagerInterfaceHandleJoinStateChanged(object: UnsafeMutableRawPointer?, clientId: UInt32, joinState: Int32) {
guard let object = object else {
Expand Down
38 changes: 38 additions & 0 deletions src/ios/SignalRingRTC/SignalRingRTC/GroupCall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ public class ReceivedAudioLevel {
}
}

@available(iOSApplicationExtension, unavailable)
public class Reaction {
public let demuxId: UInt32
public let value: String

init(demuxId: UInt32, value: String) {
self.demuxId = demuxId
self.value = value
}
}

/// All remote devices in a group call and their associated state.
@available(iOSApplicationExtension, unavailable)
public class RemoteDeviceState: Hashable {
Expand Down Expand Up @@ -186,6 +197,12 @@ public protocol GroupCallDelegate: AnyObject {
*/
func groupCall(onLowBandwidthForVideo groupCall: GroupCall, recovered: Bool)

/**
* Indication that the application should notify the user that one or more reactions
* were received.
*/
func groupCall(onReactions groupCall: GroupCall, reactions: [Reaction])

/**
* Indication that the application can retrieve an updated PeekInfo which
* includes a list of users that are actively in the group call.
Expand Down Expand Up @@ -424,6 +441,21 @@ public class GroupCall {
ringrtcDisconnect(self.ringRtcCallManager, clientId)
}

public func react(value: String) {
AssertIsOnMainThread()
Logger.debug("react")

guard let clientId = self.clientId else {
Logger.warn("no clientId defined for groupCall")
return
}

let valueSlice = allocatedAppByteSliceFromString(maybe_string: value)
defer { valueSlice.bytes?.deallocate() }

ringrtcReact(self.ringRtcCallManager, clientId, valueSlice)
}

private var _isOutgoingAudioMuted = false
public var isOutgoingAudioMuted: Bool {
get {
Expand Down Expand Up @@ -709,6 +741,12 @@ public class GroupCall {
self.delegate?.groupCall(onLowBandwidthForVideo: self, recovered: recovered)
}

func handleReactions(reactions: [Reaction]) {
AssertIsOnMainThread()

self.delegate?.groupCall(onReactions: self, reactions: reactions)
}

func handleJoinStateChanged(joinState: JoinState) {
AssertIsOnMainThread()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,10 @@ final class TestDelegate: CallManagerDelegate & HTTPDelegate {
Logger.debug("TestDelegate:onLowBandwidthForVideoFor - \(recovered)")
}

func callManager(_ callManager: CallManager<OpaqueCallData, TestDelegate>, onReactions call: OpaqueCallData, reactions: [Reaction]) {
Logger.debug("TestDelegate:onReactions - \(reactions)")
}

func callManager(_ callManager: CallManager<OpaqueCallData, TestDelegate>, shouldSendOffer callId: UInt64, call: OpaqueCallData, destinationDeviceId: UInt32?, opaque: Data, callMediaType: CallMediaType) {
Logger.debug("TestDelegate:shouldSendOffer")
generalInvocationDetected = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class TestGroupCallDelegate: GroupCallDelegate {
var onRemoteDeviceStatesChangedCount = 0
var onAudioLevelsCount = 0
var onLowBandwidthForVideoCount = 0
var onReactionsCount = 0
var onPeekChangedCount = 0
var onEndedCount = 0
var lastOnEndedReason: GroupCallEndReason? = nil
Expand Down Expand Up @@ -41,6 +42,10 @@ class TestGroupCallDelegate: GroupCallDelegate {
onLowBandwidthForVideoCount += 1
}

func groupCall(onReactions groupCall: GroupCall, reactions: [Reaction]) {
onReactionsCount += 1
}

func groupCall(onPeekChanged groupCall: GroupCall) {
onPeekChangedCount += 1
}
Expand Down
1 change: 1 addition & 0 deletions src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export {
PeekDeviceInfo,
PeekInfo,
PeekStatusCodes,
Reaction,
RemoteDeviceState,
RingCancelReason,
RingRTCType,
Expand Down
30 changes: 30 additions & 0 deletions src/node/ringrtc/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class NativeCallManager {
(NativeCallManager.prototype as any).leave = Native.cm_leave;
(NativeCallManager.prototype as any).disconnect = Native.cm_disconnect;
(NativeCallManager.prototype as any).groupRing = Native.cm_groupRing;
(NativeCallManager.prototype as any).groupReact = Native.cm_groupReact;
(NativeCallManager.prototype as any).setOutgoingAudioMuted =
Native.cm_setOutgoingAudioMuted;
(NativeCallManager.prototype as any).setOutgoingVideoMuted =
Expand Down Expand Up @@ -173,6 +174,11 @@ export interface PeekDeviceInfo {
userId?: GroupCallUserId;
}

export interface Reaction {
demuxId: number;
value: string;
}

export interface PeekInfo {
devices: Array<PeekDeviceInfo>;
creator?: GroupCallUserId;
Expand Down Expand Up @@ -1265,6 +1271,19 @@ export class RingRTCType {
});
}

// Called by Rust
handleReactions(
clientId: GroupCallClientId,
reactions: Array<Reaction>
): void {
sillyDeadlockProtection(() => {
const groupCall = this._groupCallByClientId.get(clientId);
if (groupCall) {
groupCall.handleReactions(reactions);
}
});
}

// Called by Rust
handleRemoteDevicesChanged(
clientId: GroupCallClientId,
Expand Down Expand Up @@ -2222,6 +2241,7 @@ export interface GroupCallObserver {
onRemoteDeviceStatesChanged(groupCall: GroupCall): void;
onAudioLevels(groupCall: GroupCall): void;
onLowBandwidthForVideo(groupCall: GroupCall, recovered: boolean): void;
onReactions(groupCall: GroupCall, reactions: Array<Reaction>): void;
onPeekChanged(groupCall: GroupCall): void;
onEnded(groupCall: GroupCall, reason: GroupCallEndReason): void;
}
Expand Down Expand Up @@ -2302,6 +2322,11 @@ export class GroupCall {
this._observer.onLocalDeviceStateChanged(this);
}

// Called by UI
react(value: string): void {
this._callManager.groupReact(this._clientId, value);
}

// Called by UI
setOutgoingVideoMuted(muted: boolean): void {
this._localDeviceState.videoMuted = muted;
Expand Down Expand Up @@ -2445,6 +2470,10 @@ export class GroupCall {
this._observer.onLowBandwidthForVideo(this, recovered);
}

handleReactions(reactions: Array<Reaction>): void {
this._observer.onReactions(this, reactions);
}

// Called by Rust via RingRTC object
handleRemoteDevicesChanged(
remoteDeviceStates: Array<RemoteDeviceState>
Expand Down Expand Up @@ -2785,6 +2814,7 @@ export interface CallManager {
isScreenShare: boolean
): void;
groupRing(clientId: GroupCallClientId, recipient: Buffer | undefined): void;
groupReact(clientId: GroupCallClientId, value: string): void;
resendMediaKeys(clientId: GroupCallClientId): void;
setDataMode(clientId: GroupCallClientId, dataMode: DataMode): void;
requestVideo(
Expand Down
2 changes: 2 additions & 0 deletions src/node/test/RingRTC-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
HttpMethod,
OfferType,
PeekStatusCodes,
Reaction,
RingRTC,
callIdFromEra,
callIdFromRingId,
Expand Down Expand Up @@ -908,6 +909,7 @@ describe('RingRTC', () => {
onRemoteDeviceStatesChanged(_call: GroupCall) {}
onAudioLevels(_call: GroupCall) {}
onLowBandwidthForVideo(_call: GroupCall, _recovered: boolean) {}
onReactions(_call: GroupCall, _reactions: Array<Reaction>) {}
onPeekChanged(_call: GroupCall) {}
onEnded(_call: GroupCall, _reason: GroupCallEndReason) {}
/* eslint-enable @typescript-eslint/no-empty-function */
Expand Down
8 changes: 7 additions & 1 deletion src/rust/protobuf/group_call.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@ message DeviceToDevice {
// When sent over signaling, you must indicate which device is leaving.
optional uint32 demux_id = 1;
}


// Sent over RTP data
message Reaction {
optional string value = 1;
}

optional bytes group_id = 1;
optional MediaKey media_key = 2;
optional Heartbeat heartbeat = 3;
optional Leaving leaving = 4;
optional Reaction reaction = 5;
}

message DeviceToSfu {
Expand Down
Loading

0 comments on commit c617467

Please sign in to comment.