Skip to content

Commit

Permalink
Merge pull request #167 from voximplant/call_issues
Browse files Browse the repository at this point in the history
Call quality issues
  • Loading branch information
pe1ros authored Jul 15, 2022
2 parents b7c86ca + 02fd4c8 commit cd3d6c4
Show file tree
Hide file tree
Showing 20 changed files with 885 additions and 11 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Changelog

### 1.33.1
### 1.34.0
- Introduce new APIs to monitor issues that affect call quality:
- Call.qualityIssues - Instance of a class that may be used to subscribe to call quality issues events.
- Call.currentQualityIssues - get current status for all quality issues.
- Fix for [#164](https://github.com/voximplant/react-native-voximplant/issues/164)

### 1.33.0
Expand Down
25 changes: 25 additions & 0 deletions android/src/main/java/com/voximplant/reactnative/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ class Constants {
static final String EVENT_ENDPOINT_VOICE_ACTIVITY_STARTED = "VIVoiceActivityStarted";
static final String EVENT_ENDPOINT_VOICE_ACTIVITY_STOPPED = "VIVoiceActivityStopped";

static final String EVENT_CALL_QUALITY_ISSUE_PACKET_LOSS = "VIQualityIssuePacketLoss";
static final String EVENT_CALL_QUALITY_ISSUE_CODEC_MISMATCH = "VIQualityIssueCodecMismatch";
static final String EVENT_CALL_QUALITY_ISSUE_LOCAL_VIDEO_DEGRADATION = "VIQualityIssueLocalVideoDegradation";
static final String EVENT_CALL_QUALITY_ISSUE_ICE_DISCONNECTED = "VIQualityIssueIceDisconnected";
static final String EVENT_CALL_QUALITY_ISSUE_HIGH_MEDIA_LATENCY = "VIQualityIssueHighMediaLatency";
static final String EVENT_CALL_QUALITY_ISSUE_NO_AUDIO_SIGNAL = "VIQualityIssueNoAudioSignal";
static final String EVENT_CALL_QUALITY_ISSUE_NO_AUDIO_RECEIVE = "VIQualityIssueNoAudioReceive";
static final String EVENT_CALL_QUALITY_ISSUE_NO_VIDEO_RECEIVE = "VIQualityIssueNoVideoReceive";

static final String EVENT_ENDPOINT_STOP_RECEIVING_VIDEO_STREAM_SUCCESS = "VIStopReceivingVideoStreamSuccess";
static final String EVENT_ENDPOINT_STOP_RECEIVING_VIDEO_STREAM_FAILURE = "VIStopReceivingVideoStreamFailure";
static final String EVENT_ENDPOINT_START_RECEIVING_VIDEO_STREAM_SUCCESS = "VIStartReceivingVideoStreamSuccess";
Expand Down Expand Up @@ -91,6 +100,15 @@ class Constants {
static final String EVENT_NAME_VOICE_ACTIVITY_STARTED = "VoiceActivityStarted";
static final String EVENT_NAME_VOICE_ACTIVITY_STOPPED = "VoiceActivityStopped";

static final String EVENT_NAME_CALL_QUALITY_ISSUE_PACKET_LOSS = "PacketLoss";
static final String EVENT_NAME_CALL_QUALITY_ISSUE_CODEC_MISMATCH = "CodecMismatch";
static final String EVENT_NAME_CALL_QUALITY_ISSUE_LOCAL_VIDEO_DEGRADATION = "LocalVideoDegradation";
static final String EVENT_NAME_CALL_QUALITY_ISSUE_ICE_DISCONNECTED = "IceDisconnected";
static final String EVENT_NAME_CALL_QUALITY_ISSUE_HIGH_MEDIA_LATENCY = "HighMediaLatency";
static final String EVENT_NAME_CALL_QUALITY_ISSUE_NO_AUDIO_SIGNAL = "NoAudioSignal";
static final String EVENT_NAME_CALL_QUALITY_ISSUE_NO_AUDIO_RECEIVE = "NoAudioReceive";
static final String EVENT_NAME_CALL_QUALITY_ISSUE_NO_VIDEO_RECEIVE = "NoVideoReceive";

static final String EVENT_NAME_AUDIO_DEVICE_CHANGED = "DeviceChanged";
static final String EVENT_NAME_AUDIO_DEVICE_LIST_CHANGED = "DeviceListChanged";

Expand All @@ -114,6 +132,12 @@ class Constants {
static final String EVENT_PARAM_CODE = "code";
static final String EVENT_PARAM_MESSAGE = "message";
static final String EVENT_PARAM_INCOMING_VIDEO = "video";
static final String EVENT_PARAM_ISSUE_LEVEL = "issueLevel";
static final String EVENT_PARAM_LATENCY = "latency";
static final String EVENT_PARAM_ACTUAL_SIZE = "actualSize";
static final String EVENT_PARAM_TARGET_SIZE = "targetSize";
static final String EVENT_PARAM_PACKET_LOSS = "packetLoss";
static final String EVENT_PARAM_CODEC = "codec";

static final String EVENT_PARAM_LOG_LEVEL = "level";
static final String EVENT_PARAM_LOG_MESSAGE = "message";
Expand All @@ -136,6 +160,7 @@ class Constants {
static final String EVENT_PARAM_VIDEO_STREAM_ID = "videoStreamId";
static final String EVENT_PARAM_IS_LOCAL = "isLocal";
static final String EVENT_PARAM_VIDEO_STREAM_TYPE = "videoStreamType";
static final String EVENT_PARAM_AUDIO_STREAM_ID = "audioStreamId";

static final String EVENT_PARAM_AUDIO_FILE_ID = "fileId";

Expand Down
46 changes: 46 additions & 0 deletions android/src/main/java/com/voximplant/reactnative/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.voximplant.sdk.call.CallSettings;
import com.voximplant.sdk.call.QualityIssue;
import com.voximplant.sdk.call.QualityIssueLevel;
import com.voximplant.sdk.call.VideoCodec;
import com.voximplant.sdk.call.VideoFlags;
import com.voximplant.sdk.call.VideoStreamType;
Expand Down Expand Up @@ -630,4 +632,48 @@ static ClientConfig convertClientConfigFromMap(ReadableMap settings) {
clientConfig.forceRelayTraffic = settings.getBoolean("forceRelayTraffic");
return clientConfig;
}

static String convertQualityIssueLevelToString(QualityIssueLevel level) {
switch (level) {
case MINOR:
return "Minor";
case MAJOR:
return "Major";
case CRITICAL:
return "Critical";
case NONE:
default:
return "None";
}
}

static String convertQualityIssueToString(QualityIssue issue) {
switch (issue) {
case CODEC_MISMATCH:
return "CodecMismatch";
case LOCAL_VIDEO_DEGRADATION:
return "LocalVideoDegradation";
case HIGH_MEDIA_LATENCY:
return "HighMediaLatency";
case NO_AUDIO_SIGNAL:
return "NoAudioSignal";
case PACKET_LOSS:
return "PacketLoss";
case NO_AUDIO_RECEIVE:
return "NoAudioReceive";
case NO_VIDEO_RECEIVE:
return "NoVideoReceive";
case ICE_DISCONNECTED:
default:
return "IceDisconnected";
}
}

static WritableMap convertQualityIssuesMapToWritableMap(Map<QualityIssue, QualityIssueLevel> issues) {
WritableMap map = Arguments.createMap();
for(Map.Entry<QualityIssue, QualityIssueLevel> pair : issues.entrySet()) {
map.putString(convertQualityIssueToString(pair.getKey()), convertQualityIssueLevelToString(pair.getValue()));
}
return map;
}
}
115 changes: 114 additions & 1 deletion android/src/main/java/com/voximplant/reactnative/VICallModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,23 @@
import com.voximplant.sdk.call.IEndpoint;
import com.voximplant.sdk.call.IEndpointListener;
import com.voximplant.sdk.call.ILocalVideoStream;
import com.voximplant.sdk.call.IQualityIssueListener;
import com.voximplant.sdk.call.IRemoteAudioStream;
import com.voximplant.sdk.call.IRemoteVideoStream;
import com.voximplant.sdk.call.QualityIssue;
import com.voximplant.sdk.call.QualityIssueLevel;
import com.voximplant.sdk.call.RejectMode;
import com.voximplant.sdk.call.VideoFlags;

import java.text.DecimalFormat;
import java.util.Map;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import static com.voximplant.reactnative.Constants.*;

public class VICallModule extends ReactContextBaseJavaModule implements ICallListener, IEndpointListener {
public class VICallModule extends ReactContextBaseJavaModule implements ICallListener, IEndpointListener, IQualityIssueListener {
private ReactApplicationContext mReactContext;

public VICallModule(ReactApplicationContext reactContext) {
Expand Down Expand Up @@ -62,6 +67,7 @@ public void internalSetup(String callId) {
ICall call = CallManager.getInstance().getCallById(callId);
if (call != null) {
call.addCallListener(this);
call.setQualityIssueListener(this);
}
}

Expand Down Expand Up @@ -273,6 +279,17 @@ public void getCallDuration(String callId, final Promise promise) {
}
}

@ReactMethod
public void currentQualityIssues(String callId, final Promise promise) {
ICall call = CallManager.getInstance().getCallById(callId);
if (call != null) {
Map<QualityIssue, QualityIssueLevel> issues = call.getCurrentQualityIssues();
promise.resolve(Utils.convertQualityIssuesMapToWritableMap(issues));
} else {
promise.reject(CallError.INTERNAL_ERROR.toString(), "Call.currentQualityIssues(): call is no more unavailable, already ended or failed");
}
}

@Override
public void onCallConnected(ICall call, Map<String, String> headers) {
WritableMap params = Arguments.createMap();
Expand All @@ -285,6 +302,7 @@ public void onCallConnected(ICall call, Map<String, String> headers) {
@Override
public void onCallDisconnected(ICall call, Map<String, String> headers, boolean answeredElsewhere) {
call.removeCallListener(this);
call.setQualityIssueListener(null);
CallManager.getInstance().removeCall(call);
WritableMap params = Arguments.createMap();
params.putString(EVENT_PARAM_NAME, EVENT_NAME_CALL_DISCONNECTED);
Expand All @@ -306,6 +324,7 @@ public void onCallRinging(ICall call, Map<String, String> headers) {
@Override
public void onCallFailed(ICall call, int code, String description, Map<String, String> headers) {
call.removeCallListener(this);
call.setQualityIssueListener(null);
CallManager.getInstance().removeCall(call);
WritableMap params = Arguments.createMap();
params.putString(EVENT_PARAM_NAME, EVENT_NAME_CALL_FAILED);
Expand Down Expand Up @@ -481,6 +500,100 @@ public void onVoiceActivityStopped(IEndpoint endpoint) {
sendEvent(EVENT_ENDPOINT_VOICE_ACTIVITY_STOPPED, params);
}

@Override
public void onPacketLoss(@NonNull ICall call, @NonNull QualityIssueLevel level, double packetLoss) {
WritableMap params = Arguments.createMap();
params.putString(EVENT_PARAM_NAME, EVENT_NAME_CALL_QUALITY_ISSUE_PACKET_LOSS);
params.putString(EVENT_PARAM_CALLID, call.getCallId());
params.putDouble(EVENT_PARAM_PACKET_LOSS, packetLoss);
params.putString(EVENT_PARAM_ISSUE_LEVEL, Utils.convertQualityIssueLevelToString(level));
sendEvent(EVENT_CALL_QUALITY_ISSUE_PACKET_LOSS, params);
}

@Override
public void onCodecMismatch(@NonNull ICall call, @NonNull QualityIssueLevel level, @Nullable String sendCodec) {
WritableMap params = Arguments.createMap();
params.putString(EVENT_PARAM_NAME, EVENT_NAME_CALL_QUALITY_ISSUE_CODEC_MISMATCH);
params.putString(EVENT_PARAM_CALLID, call.getCallId());
if (sendCodec != null) {
params.putString(EVENT_PARAM_CODEC, sendCodec);
} else {
params.putNull(EVENT_PARAM_CODEC);
}
params.putString(EVENT_PARAM_ISSUE_LEVEL, Utils.convertQualityIssueLevelToString(level));
sendEvent(EVENT_CALL_QUALITY_ISSUE_CODEC_MISMATCH, params);
}

@Override
public void onLocalVideoDegradation(@NonNull ICall call, @NonNull QualityIssueLevel level, int targetWidth, int targetHeight, int actualWidth, int actualHeight) {
WritableMap params = Arguments.createMap();
params.putString(EVENT_PARAM_NAME, EVENT_NAME_CALL_QUALITY_ISSUE_LOCAL_VIDEO_DEGRADATION);
params.putString(EVENT_PARAM_CALLID, call.getCallId());

WritableMap actual = Arguments.createMap();
actual.putInt("width", actualWidth);
actual.putInt("height", actualHeight);

WritableMap target = Arguments.createMap();
actual.putInt("width", targetWidth);
actual.putInt("height", targetHeight);

params.putMap(EVENT_PARAM_ACTUAL_SIZE, actual);
params.putMap(EVENT_PARAM_TARGET_SIZE, target);
params.putString(EVENT_PARAM_ISSUE_LEVEL, Utils.convertQualityIssueLevelToString(level));
sendEvent(EVENT_CALL_QUALITY_ISSUE_LOCAL_VIDEO_DEGRADATION, params);
}

@Override
public void onIceDisconnected(@NonNull ICall call, @NonNull QualityIssueLevel level) {
WritableMap params = Arguments.createMap();
params.putString(EVENT_PARAM_NAME, EVENT_NAME_CALL_QUALITY_ISSUE_ICE_DISCONNECTED);
params.putString(EVENT_PARAM_CALLID, call.getCallId());
params.putString(EVENT_PARAM_ISSUE_LEVEL, Utils.convertQualityIssueLevelToString(level));
sendEvent(EVENT_CALL_QUALITY_ISSUE_ICE_DISCONNECTED, params);
}

@Override
public void onHighMediaLatency(@NonNull ICall call, @NonNull QualityIssueLevel level, double latency) {
WritableMap params = Arguments.createMap();
params.putString(EVENT_PARAM_NAME, EVENT_NAME_CALL_QUALITY_ISSUE_HIGH_MEDIA_LATENCY);
params.putString(EVENT_PARAM_CALLID, call.getCallId());
params.putDouble(EVENT_PARAM_LATENCY, latency);
params.putString(EVENT_PARAM_ISSUE_LEVEL, Utils.convertQualityIssueLevelToString(level));
sendEvent(EVENT_CALL_QUALITY_ISSUE_HIGH_MEDIA_LATENCY, params);
}

@Override
public void onNoAudioSignal(@NonNull ICall call, @NonNull QualityIssueLevel level) {
WritableMap params = Arguments.createMap();
params.putString(EVENT_PARAM_NAME, EVENT_NAME_CALL_QUALITY_ISSUE_NO_AUDIO_SIGNAL);
params.putString(EVENT_PARAM_CALLID, call.getCallId());
params.putString(EVENT_PARAM_ISSUE_LEVEL, Utils.convertQualityIssueLevelToString(level));
sendEvent(EVENT_CALL_QUALITY_ISSUE_NO_AUDIO_SIGNAL, params);
}

@Override
public void onNoAudioReceive(@NonNull ICall call, @NonNull QualityIssueLevel level, @NonNull IRemoteAudioStream audioStream, @NonNull IEndpoint endpoint) {
WritableMap params = Arguments.createMap();
params.putString(EVENT_PARAM_NAME, EVENT_NAME_CALL_QUALITY_ISSUE_NO_AUDIO_RECEIVE);
params.putString(EVENT_PARAM_CALLID, call.getCallId());
params.putString(EVENT_PARAM_AUDIO_STREAM_ID, audioStream.getAudioStreamId());
params.putString(EVENT_PARAM_ENDPOINTID, endpoint.getEndpointId());
params.putString(EVENT_PARAM_ISSUE_LEVEL, Utils.convertQualityIssueLevelToString(level));
sendEvent(EVENT_CALL_QUALITY_ISSUE_NO_AUDIO_RECEIVE, params);
}

@Override
public void onNoVideoReceive(@NonNull ICall call, @NonNull QualityIssueLevel level, @NonNull IRemoteVideoStream videoStream, @NonNull IEndpoint endpoint) {
WritableMap params = Arguments.createMap();
params.putString(EVENT_PARAM_NAME, EVENT_NAME_CALL_QUALITY_ISSUE_NO_VIDEO_RECEIVE);
params.putString(EVENT_PARAM_CALLID, call.getCallId());
params.putString(EVENT_PARAM_VIDEO_STREAM_ID, videoStream.getVideoStreamId());
params.putString(EVENT_PARAM_ENDPOINTID, endpoint.getEndpointId());
params.putString(EVENT_PARAM_ISSUE_LEVEL, Utils.convertQualityIssueLevelToString(level));
sendEvent(EVENT_CALL_QUALITY_ISSUE_NO_VIDEO_RECEIVE, params);
}

private void sendEvent(String eventName, @Nullable WritableMap params) {
mReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public String getName() {
//region React methods
@ReactMethod
public void init(ReadableMap settings) {
Voximplant.subVersion = "react-1.33.1";
Voximplant.subVersion = "react-1.34.0";
ClientConfig config = Utils.convertClientConfigFromMap(settings);
mClient = Voximplant.getClientInstance(Executors.newSingleThreadExecutor(), mReactContext, config);
mClient.setClientIncomingCallListener(this);
Expand Down
2 changes: 1 addition & 1 deletion ios/RNVICallModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
#import <VoxImplant/VoxImplant.h>


@interface RNVICallModule : RCTEventEmitter <RCTBridgeModule, VICallDelegate, VIEndpointDelegate>
@interface RNVICallModule : RCTEventEmitter <RCTBridgeModule, VICallDelegate, VIEndpointDelegate, VIQualityIssueDelegate>
@end
Loading

0 comments on commit cd3d6c4

Please sign in to comment.