From a9f67b7e4927106d9f2efcb4c2b71cb87d43d71f Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Thu, 6 Apr 2023 16:44:31 -0700 Subject: [PATCH 01/24] Roll back OpenTok iOS SDK to 2.23.1 --- CHANGELOG.md | 8 +++++++- opentok-react-native.podspec | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ababff91..f7bc12c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -# 0.21.3 +# 0.21.4 (April 2023) + +- [Update]: Revert iOS SDK back 2.23.1. There are issues with + linked libraries in the OpenTok iOS SDK v2.24.0+ that cause + issues when used in React Native. We are working on a bug fix. + +# 0.21.3 (March 2023) - [Update]: iOS SDK to 2.24.2 and Android to 2.24.2 - issue #629 diff --git a/opentok-react-native.podspec b/opentok-react-native.podspec index 42f3e967..0e65b99d 100644 --- a/opentok-react-native.podspec +++ b/opentok-react-native.podspec @@ -17,5 +17,5 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m,swift}" s.dependency 'React' - s.dependency 'OTXCFramework','2.24.2' + s.dependency 'OTXCFramework','2.23.1' end diff --git a/package-lock.json b/package-lock.json index 145887c5..0ddaccb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "opentok-react-native", - "version": "0.21.3", + "version": "0.21.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "opentok-react-native", - "version": "0.21.3", + "version": "0.21.4", "license": "MIT", "dependencies": { "axios": "^0.21.1", diff --git a/package.json b/package.json index b84cbc43..26ccc162 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opentok-react-native", - "version": "0.21.3", + "version": "0.21.4", "description": "React Native components for OpenTok iOS and Android SDKs", "main": "src/index.js", "homepage": "https://www.tokbox.com", From 9e68b507fc731dc2f18220c1fd47e81a5e6e5f33 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Mon, 10 Apr 2023 10:29:13 -0700 Subject: [PATCH 02/24] Implement OTPublisher.getRtcStatsReport() ... and OTPublisher rtcStatsReport event --- @types/index.d.ts | 18 ++++++++++ .../opentokreactnative/OTSessionManager.java | 33 +++++++++++++++++++ .../opentokreactnative/utils/EventUtils.java | 13 ++++++++ docs/EventData.md | 11 +++++++ docs/OTPublisher.md | 16 +++++++++ ios/OpenTokReactNative/OTPublisher.m | 5 +++ .../OTPublisherManager.swift | 13 ++++++++ ios/OpenTokReactNative/OTSessionManager.m | 2 ++ ios/OpenTokReactNative/OTSessionManager.swift | 16 +++++++++ ios/OpenTokReactNative/Utils/EventUtils.swift | 15 +++++++-- src/OTPublisher.js | 5 +++ src/helpers/OTPublisherHelper.js | 2 ++ 12 files changed, 147 insertions(+), 2 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index 5df8599a..55b4e19e 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -86,6 +86,11 @@ declare module "opentok-react-native" { timestamp: number, } + interface RtcStatsReportEvent { + connectionId: string, + jsonArrayOfReports: string, + } + interface SignalEvent { sessionId: string; fromConnection: string; @@ -273,6 +278,13 @@ declare module "opentok-react-native" { * Event handlers passed into native publsiher instance */ eventHandlers?: OTPublisherEventHandlers; + + /** + * Gets the RTC stats report for the subscriber. This is an asynchronous operation. + * The OTPublisher object dispatches an rtcStatsReport event when RTC statistics for + * the publisher are available. + */ + getRtcStatsReport?: any } interface OTPublisherProperties { @@ -359,6 +371,12 @@ declare module "opentok-react-native" { */ otrnError?: CallbackWithParam; + /** + * Sent when RTC stats reports are available for the publisher, + * in response to calling the OTPublisher.getRtcStatsReport() method + */ + rtcStatsReport?: CallbackWithParam; + /** * Sent when the publisher starts streaming. */ diff --git a/android/src/main/java/com/opentokreactnative/OTSessionManager.java b/android/src/main/java/com/opentokreactnative/OTSessionManager.java index dc3a921f..47409d71 100644 --- a/android/src/main/java/com/opentokreactnative/OTSessionManager.java +++ b/android/src/main/java/com/opentokreactnative/OTSessionManager.java @@ -17,6 +17,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.bridge.ReadableMap; @@ -46,6 +47,7 @@ public class OTSessionManager extends ReactContextBaseJavaModule implements Session.SessionListener, PublisherKit.PublisherListener, PublisherKit.AudioLevelListener, + PublisherKit.PublisherRtcStatsReportListener, SubscriberKit.SubscriberListener, Session.SignalListener, Session.ConnectionListener, @@ -182,6 +184,7 @@ public void initPublisher(String publisherId, ReadableMap properties, Callback c } mPublisher.setPublisherListener(this); mPublisher.setAudioLevelListener(this); + mPublisher.setRtcStatsReportListener(this); mPublisher.setAudioFallbackEnabled(audioFallbackEnabled); mPublisher.setPublishVideo(publishVideo); mPublisher.setPublishAudio(publishAudio); @@ -306,6 +309,16 @@ public void publishVideo(String publisherId, Boolean publishVideo) { } } + @ReactMethod + public void getRtcStatsReport(String publisherId) { + + ConcurrentHashMap mPublishers = sharedState.getPublishers(); + Publisher mPublisher = mPublishers.get(publisherId); + if (mPublisher != null) { + mPublisher.getRtcStatsReport(); + } + } + @ReactMethod public void subscribeToAudio(String streamId, Boolean subscribeToAudio) { @@ -506,6 +519,15 @@ private void sendEventMap(ReactContext reactContext, String eventName, @Nullable } } + private void sendEventArray(ReactContext reactContext, String eventName, @Nullable WritableArray eventData) { + + if (Utils.contains(jsEvents, eventName) || Utils.contains(componentEvents, eventName)) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, eventData); + } + } + private void sendEventWithString(ReactContext reactContext, String eventName, String eventString) { if (Utils.contains(jsEvents, eventName) || Utils.contains(componentEvents, eventName)) { @@ -717,6 +739,17 @@ public void onAudioLevelUpdated(PublisherKit publisher, float audioLevel) { } } + @Override + public void onRtcStatsReport(PublisherKit publisher, PublisherKit.PublisherRtcStats[] stats) { + + String publisherId = Utils.getPublisherId(publisher); + if (publisherId.length() > 0) { + WritableArray rtcStatsReportArray = EventUtils.preparePublisherRtcStats(stats); + String event = publisherId + ":" + publisherPreface + "onRtcStatsReport"; + sendEventArray(this.getReactApplicationContext(), event, rtcStatsReportArray); + } + } + @Override public void onConnected(SubscriberKit subscriberKit) { diff --git a/android/src/main/java/com/opentokreactnative/utils/EventUtils.java b/android/src/main/java/com/opentokreactnative/utils/EventUtils.java index 4eed4fa7..c3ecb430 100644 --- a/android/src/main/java/com/opentokreactnative/utils/EventUtils.java +++ b/android/src/main/java/com/opentokreactnative/utils/EventUtils.java @@ -1,12 +1,14 @@ package com.opentokreactnative.utils; import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.opentok.android.Connection; import com.opentok.android.OpentokError; import com.opentok.android.Session; import com.opentok.android.Stream; import com.opentok.android.SubscriberKit; +import com.opentok.android.PublisherKit; public final class EventUtils { @@ -113,6 +115,17 @@ public static WritableMap prepareVideoNetworkStats(SubscriberKit.SubscriberVideo return videoStats; } + public static WritableArray preparePublisherRtcStats(PublisherKit.PublisherRtcStats[] stats) { + WritableArray statsArrayMap = Arguments.createArray(); + for (PublisherKit.PublisherRtcStats stat : stats) { + WritableMap statMap = Arguments.createMap(); + statMap.putString("connectionId", stat.connectionId); + statMap.putString("jsonArrayOfReports", stat.jsonArrayOfReports); + statsArrayMap.pushMap(statMap); + } + return statsArrayMap; + } + public static WritableMap createError(String message) { WritableMap errorInfo = Arguments.createMap(); diff --git a/docs/EventData.md b/docs/EventData.md index 0e2168be..c14e47f7 100644 --- a/docs/EventData.md +++ b/docs/EventData.md @@ -119,6 +119,17 @@ You can find the structure of the object below: message: string, }; ``` + +## RtcStatsReportEvent +You can find the structure of the object below: + +```javascript + event = { + connectionId: string, + jsonArrayOfReports: string, + }; +``` + ## SessionConnectEvent ```javascript diff --git a/docs/OTPublisher.md b/docs/OTPublisher.md index 2c11f0eb..8b350100 100644 --- a/docs/OTPublisher.md +++ b/docs/OTPublisher.md @@ -120,6 +120,22 @@ see the Subscriber videoDisabled event and the OpenTok Media Router and media mo * **otrnError** (Object) -- Sent if there is an error with the communication between the native publisher instance and the JS component. +* **rtcStatsReport** (Object) -- Sent when RTC stats reports are available for the publisher, + in response to calling the `OTPublisher.getRtcStatsReport()` method. This event has an array of + objects. For a routed session (a seesion that uses the + [OpenTok Media Router](https://tokbox.com/developer/guides/create-session/#media-mode)), + this array includes one object, defining the statistics for the single video media stream that is sent + to the OpenTok Media Router. In a relayed session, the array includes an object for each subscriber + to the published stream. Each object includes two properties: + + * `connectionId` -- For a relayed session (in which a publisher sends individual media streams + to each subscriber), this is the unique ID of the client’s connection. + + * `jsonArrayOfReports` -- A JSON array of RTC stats reports for the media stream. The structure + of the JSON array is similar to the format of the RtcStatsReport object implemented in web browsers + (see the [Mozilla docs](https://developer.mozilla.org/en-US/docs/Web/API/RTCStatsReport)). + Also see [this W3C documentation](https://w3c.github.io/webrtc-stats/). + * **streamCreated** (Object) -- Sent when the publisher starts streaming. A [streamingEvent](./EventData.md#streamingEvent) object is passed into the event handler. diff --git a/ios/OpenTokReactNative/OTPublisher.m b/ios/OpenTokReactNative/OTPublisher.m index 78fdd5af..5c45e425 100644 --- a/ios/OpenTokReactNative/OTPublisher.m +++ b/ios/OpenTokReactNative/OTPublisher.m @@ -11,5 +11,10 @@ @interface RCT_EXTERN_MODULE(OTPublisherSwift, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(publisherId, NSString) + +/* +RCT_EXTERN_METHOD(getRtcStatsReport: + (NSString*)publisherId) +*/ @end diff --git a/ios/OpenTokReactNative/OTPublisherManager.swift b/ios/OpenTokReactNative/OTPublisherManager.swift index 522251f0..43a9da98 100644 --- a/ios/OpenTokReactNative/OTPublisherManager.swift +++ b/ios/OpenTokReactNative/OTPublisherManager.swift @@ -17,5 +17,18 @@ class OTPublisherManager: RCTViewManager { override static func requiresMainQueueSetup() -> Bool { return true; } + + /* + @objc func getRtcStatsReport(publisherId: String, callback: RCTResponseSenderBlock) -> Void { + var error: OTError? + guard let publisher = OTRN.sharedState.publishers[publisherId] else { + let errorInfo = EventUtils.createErrorMessage("getRtcStatsReport error. Could not find native publisher instance") + callback([errorInfo]) + return + } + publisher.getRtcStatsReport() + callback([NSNull()]) + } + */ } diff --git a/ios/OpenTokReactNative/OTSessionManager.m b/ios/OpenTokReactNative/OTSessionManager.m index 6b6806e1..bf8c7a1c 100644 --- a/ios/OpenTokReactNative/OTSessionManager.m +++ b/ios/OpenTokReactNative/OTSessionManager.m @@ -47,6 +47,8 @@ @interface RCT_EXTERN_MODULE(OTSessionManager, RCTEventEmitter) RCT_EXTERN_METHOD(publishVideo: (NSString*)publisherId pubVideo:(BOOL)pubVideo) +RCT_EXTERN_METHOD(getRtcStatsReport: + (NSString*)publisherId) RCT_EXTERN_METHOD(subscribeToAudio: (NSString*)streamId subAudio:(BOOL)subAudio) diff --git a/ios/OpenTokReactNative/OTSessionManager.swift b/ios/OpenTokReactNative/OTSessionManager.swift index aee81e05..1a3bf3fb 100644 --- a/ios/OpenTokReactNative/OTSessionManager.swift +++ b/ios/OpenTokReactNative/OTSessionManager.swift @@ -104,6 +104,7 @@ class OTSessionManager: RCTEventEmitter { publisher.publishAudio = Utils.sanitizeBooleanProperty(properties["publishAudio"] as Any); publisher.publishVideo = Utils.sanitizeBooleanProperty(properties["publishVideo"] as Any); publisher.audioLevelDelegate = self; + publisher.rtcStatsReportDelegate = self; callback([NSNull()]); } } @@ -202,6 +203,11 @@ class OTSessionManager: RCTEventEmitter { guard let publisher = OTRN.sharedState.publishers[publisherId] else { return } publisher.publishVideo = pubVideo; } + + @objc func getRtcStatsReport(_ publisherId: String) -> Void { + guard let publisher = OTRN.sharedState.publishers[publisherId] else { return } + publisher.getRtcStatsReport() + } @objc func subscribeToAudio(_ streamId: String, subAudio: Bool) -> Void { guard let subscriber = OTRN.sharedState.subscribers[streamId] else { return } @@ -531,6 +537,16 @@ extension OTSessionManager: OTPublisherKitAudioLevelDelegate { } } +extension OTSessionManager: OTPublisherKitRtcStatsReportDelegate { + func publisher(_ publisher: OTPublisherKit, rtcStatsReport stats: [OTPublisherRtcStats]) { + let publisherId = Utils.getPublisherId(publisher as! OTPublisher); + if (publisherId.count > 0) { + let statsArray: [Dictionary] = EventUtils.preparePublisherRtcStats(stats); + self.emitEvent("\(publisherId):\(EventUtils.publisherPreface)rtcStatsReport", data: statsArray) + } + } +} + extension OTSessionManager: OTSubscriberDelegate { func subscriberDidConnect(toStream subscriberKit: OTSubscriberKit) { if let stream = subscriberKit.stream { diff --git a/ios/OpenTokReactNative/Utils/EventUtils.swift b/ios/OpenTokReactNative/Utils/EventUtils.swift index 95e9aef3..05ed688b 100644 --- a/ios/OpenTokReactNative/Utils/EventUtils.swift +++ b/ios/OpenTokReactNative/Utils/EventUtils.swift @@ -55,7 +55,18 @@ class EventUtils { streamPropertyEventData["changedProperty"] = changedProperty; return streamPropertyEventData } - + + static func preparePublisherRtcStats(_ rtcStatsReport: [OTPublisherRtcStats]) -> [Dictionary] { + var statsArray:[Dictionary] = []; + for value in rtcStatsReport { + var stats:Dictionary = [:]; + stats["connectionId"] = value.connectionId; + stats["jsonArrayOfReports"] = value.jsonArrayOfReports; + statsArray.append(stats); + } + return statsArray; + } + static func prepareSubscriberVideoNetworkStatsEventData(_ videoStats: OTSubscriberKitVideoNetworkStats) -> Dictionary { var videoStatsEventData: Dictionary = [:]; videoStatsEventData["videoPacketsLost"] = videoStats.videoPacketsLost; @@ -83,7 +94,7 @@ class EventUtils { } static func getSupportedEvents() -> [String] { - return ["\(sessionPreface)streamCreated", "\(sessionPreface)streamDestroyed", "\(sessionPreface)sessionDidConnect", "\(sessionPreface)sessionDidDisconnect", "\(sessionPreface)connectionCreated", "\(sessionPreface)connectionDestroyed", "\(sessionPreface)didFailWithError", "\(publisherPreface)streamCreated", "\(sessionPreface)signal", "\(publisherPreface)streamDestroyed", "\(publisherPreface)didFailWithError", "\(publisherPreface)audioLevelUpdated", "\(subscriberPreface)subscriberDidConnect", "\(subscriberPreface)subscriberDidDisconnect", "\(subscriberPreface)didFailWithError", "\(subscriberPreface)videoNetworkStatsUpdated", "\(subscriberPreface)audioNetworkStatsUpdated", "\(subscriberPreface)audioLevelUpdated", "\(subscriberPreface)subscriberVideoEnabled", "\(subscriberPreface)subscriberVideoDisabled", "\(subscriberPreface)subscriberVideoDisableWarning", "\(subscriberPreface)subscriberVideoDisableWarningLifted", "\(subscriberPreface)subscriberVideoDataReceived", "\(sessionPreface)archiveStartedWithId", "\(sessionPreface)archiveStoppedWithId", "\(sessionPreface)sessionDidBeginReconnecting", "\(sessionPreface)sessionDidReconnect", "\(sessionPreface)streamPropertyChanged", "\(subscriberPreface)subscriberDidReconnect"]; + return ["\(sessionPreface)streamCreated", "\(sessionPreface)streamDestroyed", "\(sessionPreface)sessionDidConnect", "\(sessionPreface)sessionDidDisconnect", "\(sessionPreface)connectionCreated", "\(sessionPreface)connectionDestroyed", "\(sessionPreface)didFailWithError", "\(publisherPreface)streamCreated", "\(sessionPreface)signal", "\(publisherPreface)streamDestroyed", "\(publisherPreface)didFailWithError", "\(publisherPreface)audioLevelUpdated", "\(publisherPreface)rtcStatsReport", "\(subscriberPreface)subscriberDidConnect", "\(subscriberPreface)subscriberDidDisconnect", "\(subscriberPreface)didFailWithError", "\(subscriberPreface)videoNetworkStatsUpdated", "\(subscriberPreface)audioNetworkStatsUpdated", "\(subscriberPreface)audioLevelUpdated", "\(subscriberPreface)subscriberVideoEnabled", "\(subscriberPreface)subscriberVideoDisabled", "\(subscriberPreface)subscriberVideoDisableWarning", "\(subscriberPreface)subscriberVideoDisableWarningLifted", "\(subscriberPreface)subscriberVideoDataReceived", "\(sessionPreface)archiveStartedWithId", "\(sessionPreface)archiveStoppedWithId", "\(sessionPreface)sessionDidBeginReconnecting", "\(sessionPreface)sessionDidReconnect", "\(sessionPreface)streamPropertyChanged", "\(subscriberPreface)subscriberDidReconnect"]; } static func convertDateToString(_ creationTime: Date) -> String { diff --git a/src/OTPublisher.js b/src/OTPublisher.js index 065bcf87..d43e3f0d 100644 --- a/src/OTPublisher.js +++ b/src/OTPublisher.js @@ -152,6 +152,9 @@ class OTPublisher extends Component { } ); } + getRtcStatsReport() { + OT.getRtcStatsReport(this.state.publisherId); + } render() { const { publisher, publisherId } = this.state; const { sessionId } = this.context; @@ -172,10 +175,12 @@ OTPublisher.propTypes = { ...viewPropTypes, properties: PropTypes.object, // eslint-disable-line react/forbid-prop-types eventHandlers: PropTypes.object, // eslint-disable-line react/forbid-prop-types + getRtcStatsReport: PropTypes.object, // eslint-disable-line react/forbid-prop-types }; OTPublisher.defaultProps = { properties: {}, eventHandlers: {}, + getRtcStatsReport: {}, }; OTPublisher.contextType = OTContext; export default OTPublisher; diff --git a/src/helpers/OTPublisherHelper.js b/src/helpers/OTPublisherHelper.js index 8a75cd8a..7b34ccea 100644 --- a/src/helpers/OTPublisherHelper.js +++ b/src/helpers/OTPublisherHelper.js @@ -91,12 +91,14 @@ const sanitizePublisherEvents = (publisherId, events) => { streamDestroyed: 'streamDestroyed', error: 'didFailWithError', audioLevel: 'audioLevelUpdated', + rtcStatsReport: 'rtcStatsReport', }, android: { streamCreated: 'onStreamCreated', streamDestroyed: 'onStreamDestroyed', error: 'onError', audioLevel: 'onAudioLevelUpdated', + rtcStatsReport: 'onRtcStatsReport', }, }; return reassignEvents('publisher', customEvents, events, publisherId); From ee086d0e690713e5bb882810d718a534a8bdc084 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Mon, 10 Apr 2023 11:18:02 -0700 Subject: [PATCH 03/24] Docs corrections for OTPublisher.getRtcStatsReport() --- @types/index.d.ts | 2 +- docs/OTPublisher.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index 55b4e19e..5ecf443d 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -280,7 +280,7 @@ declare module "opentok-react-native" { eventHandlers?: OTPublisherEventHandlers; /** - * Gets the RTC stats report for the subscriber. This is an asynchronous operation. + * Gets the RTC stats report for the publisher. This is an asynchronous operation. * The OTPublisher object dispatches an rtcStatsReport event when RTC statistics for * the publisher are available. */ diff --git a/docs/OTPublisher.md b/docs/OTPublisher.md index 8b350100..baaa7c47 100644 --- a/docs/OTPublisher.md +++ b/docs/OTPublisher.md @@ -63,6 +63,11 @@ The OTPublisher component has the following properties, each of which is optiona * `eventHandlers` (Object) -- An object containing key-value pairs of event names and callback functions for event handlers. See [Events](#events). +## Methods + +**getRtcStatsReport()** Gets the RTC stats report for the publisher. This is an asynchronous operation. +The OTPublisher object dispatches an `rtcStatsReport` event when RTC statistics for the publisher are available. + ## properties object The `properties` object passed into the OTPublisher object has the following properties: From ca8d5ed00a34717113ead1a7858d953131d135cb Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Wed, 12 Apr 2023 12:31:57 -0700 Subject: [PATCH 04/24] Subscriber getRtcStatsReport() ... and Subscriber RtcStatsReport event --- @types/index.d.ts | 21 +++++++++--- .../opentokreactnative/OTSessionManager.java | 28 +++++++++++++++ docs/EventData.md | 34 +++++++++++++++++++ docs/OTPublisher.md | 4 ++- docs/OTSubscriber.md | 26 ++++++++++++-- ios/OpenTokReactNative/OTPublisher.m | 4 --- .../OTPublisherManager.swift | 13 ------- ios/OpenTokReactNative/OTSessionManager.m | 2 ++ ios/OpenTokReactNative/OTSessionManager.swift | 22 +++++++++++- src/OTSubscriber.js | 7 +++- src/helpers/OTSubscriberHelper.js | 4 ++- 11 files changed, 138 insertions(+), 27 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index 5ecf443d..0655cfde 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -86,7 +86,7 @@ declare module "opentok-react-native" { timestamp: number, } - interface RtcStatsReportEvent { + interface PublisherRtcStatsReport { connectionId: string, jsonArrayOfReports: string, } @@ -284,7 +284,7 @@ declare module "opentok-react-native" { * The OTPublisher object dispatches an rtcStatsReport event when RTC statistics for * the publisher are available. */ - getRtcStatsReport?: any + getRtcStatsReport(): void; } interface OTPublisherProperties { @@ -373,9 +373,9 @@ declare module "opentok-react-native" { /** * Sent when RTC stats reports are available for the publisher, - * in response to calling the OTPublisher.getRtcStatsReport() method + * in response to calling the OTPublisher.getRtcStatsReport() method. */ - rtcStatsReport?: CallbackWithParam; + rtcStatsReport?: CallbackWithParam, any>; /** * Sent when the publisher starts streaming. @@ -423,6 +423,13 @@ declare module "opentok-react-native" { * If set to true, the subscriber can subscribe to it's own publisher stream (default: false) */ subscribeToSelf?: boolean; + + /** + * Gets the RTC stats report for the subscriber. This is an asynchronous operation. + * The OTSubscriber object dispatches an rtcStatsReport event when RTC statistics for + * the publisher are available. + */ + getRtcStatsReport(): void; } interface OTSubscriberProperties { @@ -468,6 +475,12 @@ declare module "opentok-react-native" { */ otrnError?: CallbackWithParam; + /** + * Sent when an RTC stats report is available for the subscriber, + * in response to calling the OTSubscriber.getRtcStatsReport() method. + */ + rtcStatsReport?: CallbackWithParam, any>; + /** * Sent when a frame of video has been decoded. Although the subscriber will connect in a relatively short time, video can take more time to synchronize. This message is sent after the connected message is sent. */ diff --git a/android/src/main/java/com/opentokreactnative/OTSessionManager.java b/android/src/main/java/com/opentokreactnative/OTSessionManager.java index 47409d71..00b7210a 100644 --- a/android/src/main/java/com/opentokreactnative/OTSessionManager.java +++ b/android/src/main/java/com/opentokreactnative/OTSessionManager.java @@ -55,6 +55,7 @@ public class OTSessionManager extends ReactContextBaseJavaModule Session.ArchiveListener, Session.StreamPropertiesListener, SubscriberKit.AudioLevelListener, + SubscriberKit.SubscriberRtcStatsReportListener, SubscriberKit.AudioStatsListener, SubscriberKit.VideoStatsListener, SubscriberKit.VideoListener, @@ -226,6 +227,7 @@ public void subscribeToStream(String streamId, String sessionId, ReadableMap pro mSubscriber.setAudioLevelListener(this); mSubscriber.setAudioStatsListener(this); mSubscriber.setVideoStatsListener(this); + mSubscriber.setRtcStatsReportListener(this); mSubscriber.setVideoListener(this); mSubscriber.setStreamListener(this); mSubscriber.setSubscribeToAudio(properties.getBoolean("subscribeToAudio")); @@ -367,6 +369,16 @@ public void setPreferredFrameRate(String streamId, Float frameRate) { } } + @ReactMethod + public void getSubscriberRtcStatsReport(String streamId) { + + ConcurrentHashMap mSubscribers = sharedState.getSubscribers(); + Subscriber mSubscriber = mSubscribers.get(streamId); + if (mSubscriber != null) { + mSubscriber.getRtcStatsReport(); + } + } + @ReactMethod public void changeCameraPosition(String publisherId, String cameraPosition) { @@ -817,6 +829,22 @@ public void onError(SubscriberKit subscriberKit, OpentokError opentokError) { } + @Override + public void onRtcStatsReport(SubscriberKit subscriberKit, String stats) { + + String streamId = Utils.getStreamIdBySubscriber(subscriberKit); + if (streamId.length() > 0) { + ConcurrentHashMap streams = sharedState.getSubscriberStreams(); + Stream mStream = streams.get(streamId); + WritableMap subscriberInfo = Arguments.createMap(); + if (mStream != null) { + subscriberInfo.putMap("stream", EventUtils.prepareJSStreamMap(mStream, subscriberKit.getSession())); + } + subscriberInfo.putString("jsonArrayOfReports", stats); + sendEventMap(this.getReactApplicationContext(), subscriberPreface + "onRtcStatsReport", subscriberInfo); + } + } + @Override public void onSignalReceived(Session session, String type, String data, Connection connection) { diff --git a/docs/EventData.md b/docs/EventData.md index c14e47f7..38d380fe 100644 --- a/docs/EventData.md +++ b/docs/EventData.md @@ -274,3 +274,37 @@ You can find the structure of the object below: timestamp: number }; ``` + +## SubscriberRtcStatsReportEvent + +```javascript + event = { + stream: { + streamId: string; + name: string; + connectionId: string; + connection: { + connectionId: string, + creationTime: string, + data: string, + }, + hasAudio: boolean, + hasVideo: boolean, + sessionId: string, + creationTime: number, + height: number, + width: number, + videoType: string, // 'camera' or 'screen' + }, + jsonArrayOfReports: string + }; +``` + +## PublisherRtcStatsReportEvent + +```javascript + event = [ + connectionId: string + jsonArrayOfReports: string + ]; +``` diff --git a/docs/OTPublisher.md b/docs/OTPublisher.md index baaa7c47..690fb419 100644 --- a/docs/OTPublisher.md +++ b/docs/OTPublisher.md @@ -126,7 +126,9 @@ see the Subscriber videoDisabled event and the OpenTok Media Router and media mo * **otrnError** (Object) -- Sent if there is an error with the communication between the native publisher instance and the JS component. * **rtcStatsReport** (Object) -- Sent when RTC stats reports are available for the publisher, - in response to calling the `OTPublisher.getRtcStatsReport()` method. This event has an array of + in response to calling the `OTPublisher.getRtcStatsReport()` method. A + [PublisherRtcStatsReportEvent](./EventData.md#publisherRtcStatsReportEvent) object is passed into + the event handler. This event has an array of objects. For a routed session (a seesion that uses the [OpenTok Media Router](https://tokbox.com/developer/guides/create-session/#media-mode)), this array includes one object, defining the statistics for the single video media stream that is sent diff --git a/docs/OTSubscriber.md b/docs/OTSubscriber.md index e396cb82..cc83e78f 100644 --- a/docs/OTSubscriber.md +++ b/docs/OTSubscriber.md @@ -22,14 +22,22 @@ The `OTSubscriber` component will subscribe to a specified stream from a specified session upon mounting. The `OTSubscriber` component will stop subscribing and unsubscribing when it's unmounting. +## Methods + +**getRtcStatsReport(streamId)** Gets the RTC stats report for the subscriber to the stream with the +specified stream ID. This is an asynchronous operation. The OTSubscriber object dispatches an +`rtcStatsReport` event when RTC statistics for the subscriber are available. + ## Events + * **audioLevel** (SubscriberAudioLevelEvent) — Sent on a regular interval with the recent representative audio level. See [SubscriberAudioLevelEvent](./EventData.md#SubscriberAudioLevelEvent) * **audioNetworkStats** (Object) — Sent periodically to report audio statistics for the subscriber. - Am [SessionConnectEvent](./EventData.md#SessionConnectEvent) object is passed into the event handler. + A [SessionConnectEvent](./EventData.md#SessionConnectEvent) object is passed into the event handler. - * **connected** () — Sent when the subscriber successfully connects to the stream. + * **connected** () — Sent when the subscriber successfully connects to the stream. The event object + includes a `streamId` property, identifying the stream. * **disconnected** () — Called when the subscriber’s stream has been interrupted. @@ -37,6 +45,20 @@ The `OTSubscriber` component will subscribe to a specified stream from a specifi * **otrnError** (Object) — Sent if there is an error with the communication between the native subscriber instance and the JS component. +* **rtcStatsReport** (Object) -- Sent when RTC stats reports are available for the subscriber, + in response to calling the `OTSubscriber.getRtcStatsReport()` method. A + [SubscriberRtcStatsReportEvent](./EventData.md#subscriberRtcStatsReportEvent) object is passed + into the event handler. This event object has the following properties: + + * `jsonArrayOfReports` property, which is a JSON array of RTCStatsReport for the media stream. + The structure of the JSON array is similar to the format of the RtcStatsReport object implemented + in web browsers (see the + [Mozilla docs](https://developer.mozilla.org/en-US/docs/Web/API/RTCStatsReport)). + Also see [this W3C documentation](https://w3c.github.io/webrtc-stats/). + + * `stream` -- An object representing the subscriber's stream. This object includes a `streamId` + property, identifying the stream. + * **videoDataReceived** () - Sent when a frame of video has been decoded. Although the subscriber will connect in a relatively short time, video can take more time to synchronize. This message is sent after the `connected` message is sent. * **videoDisabled** (String) — This message is sent when the subscriber stops receiving video. Check the reason parameter for the reason why the video stopped. diff --git a/ios/OpenTokReactNative/OTPublisher.m b/ios/OpenTokReactNative/OTPublisher.m index 5c45e425..bbce4cfc 100644 --- a/ios/OpenTokReactNative/OTPublisher.m +++ b/ios/OpenTokReactNative/OTPublisher.m @@ -12,9 +12,5 @@ @interface RCT_EXTERN_MODULE(OTPublisherSwift, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(publisherId, NSString) -/* -RCT_EXTERN_METHOD(getRtcStatsReport: - (NSString*)publisherId) -*/ @end diff --git a/ios/OpenTokReactNative/OTPublisherManager.swift b/ios/OpenTokReactNative/OTPublisherManager.swift index 43a9da98..522251f0 100644 --- a/ios/OpenTokReactNative/OTPublisherManager.swift +++ b/ios/OpenTokReactNative/OTPublisherManager.swift @@ -17,18 +17,5 @@ class OTPublisherManager: RCTViewManager { override static func requiresMainQueueSetup() -> Bool { return true; } - - /* - @objc func getRtcStatsReport(publisherId: String, callback: RCTResponseSenderBlock) -> Void { - var error: OTError? - guard let publisher = OTRN.sharedState.publishers[publisherId] else { - let errorInfo = EventUtils.createErrorMessage("getRtcStatsReport error. Could not find native publisher instance") - callback([errorInfo]) - return - } - publisher.getRtcStatsReport() - callback([NSNull()]) - } - */ } diff --git a/ios/OpenTokReactNative/OTSessionManager.m b/ios/OpenTokReactNative/OTSessionManager.m index bf8c7a1c..558f3e44 100644 --- a/ios/OpenTokReactNative/OTSessionManager.m +++ b/ios/OpenTokReactNative/OTSessionManager.m @@ -49,6 +49,8 @@ @interface RCT_EXTERN_MODULE(OTSessionManager, RCTEventEmitter) pubVideo:(BOOL)pubVideo) RCT_EXTERN_METHOD(getRtcStatsReport: (NSString*)publisherId) +RCT_EXTERN_METHOD(getSubscriberRtcStatsReport: + (NSString*)subscriberId) RCT_EXTERN_METHOD(subscribeToAudio: (NSString*)streamId subAudio:(BOOL)subAudio) diff --git a/ios/OpenTokReactNative/OTSessionManager.swift b/ios/OpenTokReactNative/OTSessionManager.swift index 1a3bf3fb..7a329478 100644 --- a/ios/OpenTokReactNative/OTSessionManager.swift +++ b/ios/OpenTokReactNative/OTSessionManager.swift @@ -150,11 +150,13 @@ class OTSessionManager: RCTEventEmitter { OTRN.sharedState.subscribers.updateValue(subscriber, forKey: streamId) subscriber.networkStatsDelegate = self; subscriber.audioLevelDelegate = self; + subscriber.delegate = self; session.subscribe(subscriber, error: &error) subscriber.subscribeToAudio = Utils.sanitizeBooleanProperty(properties["subscribeToAudio"] as Any); subscriber.subscribeToVideo = Utils.sanitizeBooleanProperty(properties["subscribeToVideo"] as Any); subscriber.preferredFrameRate = Utils.sanitizePreferredFrameRate(properties["preferredFrameRate"] as Any); subscriber.preferredResolution = Utils.sanitizePreferredResolution(properties["preferredResolution"] as Any); + subscriber.rtcStatsReportDelegate = self; if let err = error { self.dispatchErrorViaCallback(callback, error: err) } else { @@ -204,7 +206,7 @@ class OTSessionManager: RCTEventEmitter { publisher.publishVideo = pubVideo; } - @objc func getRtcStatsReport(_ publisherId: String) -> Void { + @objc func getRtcStatsReport(_ publisherId: String) -> Void { guard let publisher = OTRN.sharedState.publishers[publisherId] else { return } publisher.getRtcStatsReport() } @@ -229,6 +231,11 @@ class OTSessionManager: RCTEventEmitter { subscriber.preferredFrameRate = Utils.sanitizePreferredFrameRate(frameRate); } + @objc func getSubscriberRtcStatsReport(_ streamId: String) -> Void { + guard let subscriber = OTRN.sharedState.subscribers[streamId] else { return } + subscriber.getRtcStatsReport() + } + @objc func changeCameraPosition(_ publisherId: String, cameraPosition: String) -> Void { guard let publisher = OTRN.sharedState.publishers[publisherId] else { return } publisher.cameraPosition = cameraPosition == "front" ? .front : .back; @@ -688,3 +695,16 @@ extension OTSessionManager: OTSubscriberKitAudioLevelDelegate { self.emitEvent("\(EventUtils.subscriberPreface)audioLevelUpdated", data: subscriberInfo); } } + +extension OTSessionManager: OTSubscriberKitRtcStatsReportDelegate { + func subscriber(_ subscriber: OTSubscriberKit, rtcStatsReport stats: String) { + var subscriberInfo: Dictionary = [:]; + subscriberInfo["jsonArrayOfReports"] = stats; + guard let stream = subscriber.stream else { + self.emitEvent("\(EventUtils.subscriberPreface)rtcStatsReport", data: subscriberInfo); + return; + } + subscriberInfo["stream"] = EventUtils.prepareJSStreamEventData(stream); + self.emitEvent("\(EventUtils.subscriberPreface)rtcStatsReport", data: subscriberInfo) + } +} diff --git a/src/OTSubscriber.js b/src/OTSubscriber.js index 943de5ad..440832da 100644 --- a/src/OTSubscriber.js +++ b/src/OTSubscriber.js @@ -98,6 +98,9 @@ export default class OTSubscriber extends Component { } }); } + getRtcStatsReport(streamId) { + OT.getSubscriberRtcStatsReport(streamId); + } render() { if (!this.props.children) { const containerStyle = this.props.containerStyle; @@ -120,6 +123,7 @@ OTSubscriber.propTypes = { eventHandlers: PropTypes.object, // eslint-disable-line react/forbid-prop-types streamProperties: PropTypes.object, // eslint-disable-line react/forbid-prop-types containerStyle: PropTypes.object, // eslint-disable-line react/forbid-prop-types + getRtcStatsReport: PropTypes.object, // eslint-disable-line react/forbid-prop-types subscribeToSelf: PropTypes.bool }; @@ -128,7 +132,8 @@ OTSubscriber.defaultProps = { eventHandlers: {}, streamProperties: {}, containerStyle: {}, - subscribeToSelf: false + subscribeToSelf: false, + getRtcStatsReport: {}, }; OTSubscriber.contextType = OTContext; diff --git a/src/helpers/OTSubscriberHelper.js b/src/helpers/OTSubscriberHelper.js index 52a9a001..08bc4b6b 100644 --- a/src/helpers/OTSubscriberHelper.js +++ b/src/helpers/OTSubscriberHelper.js @@ -21,6 +21,7 @@ const sanitizeSubscriberEvents = (events) => { audioNetworkStats: 'audioNetworkStatsUpdated', videoNetworkStats: 'videoNetworkStatsUpdated', audioLevel: 'audioLevelUpdated', + rtcStatsReport: 'rtcStatsReport', videoDisabled: 'subscriberVideoDisabled', videoEnabled: 'subscriberVideoEnabled', videoDisableWarning: 'subscriberVideoDisableWarning', @@ -33,6 +34,7 @@ const sanitizeSubscriberEvents = (events) => { reconnected: 'onReconnected', error: 'onError', audioNetworkStats: 'onAudioStats', + rtcStatsReport: 'onRtcStatsReport', videoNetworkStats: 'onVideoStats', audioLevel: 'onAudioLevelUpdated', videoDisabled: 'onVideoDisabled', @@ -102,7 +104,7 @@ const sanitizeProperties = (properties) => { subscribeToAudio: sanitizeBooleanProperty(properties.subscribeToAudio), subscribeToVideo: sanitizeBooleanProperty(properties.subscribeToVideo), preferredResolution: sanitizeResolution(properties.preferredResolution), - preferredFrameRate: sanitizeFrameRate(properties.preferredFrameRate) + preferredFrameRate: sanitizeFrameRate(properties.preferredFrameRate), }; }; From 137dd4b55e40edd238f5395ef44c0a6acfd1862d Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Fri, 14 Apr 2023 16:30:59 -0700 Subject: [PATCH 05/24] Add OTSession.getCapabilities() --- @types/index.d.ts | 9 +++++++++ .../com/opentokreactnative/OTSessionManager.java | 15 +++++++++++++++ docs/OTSession.md | 11 +++++++++++ ios/OpenTokReactNative/OTSessionManager.m | 3 +++ ios/OpenTokReactNative/OTSessionManager.swift | 10 ++++++++++ src/OTSession.js | 9 +++++++++ 6 files changed, 57 insertions(+) diff --git a/@types/index.d.ts b/@types/index.d.ts index 0655cfde..66338196 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -129,6 +129,15 @@ declare module "opentok-react-native" { */ getSessionInfo?: any + /** + * Used to get capabilities of the client + */ + getCapabilities(): { + canForceMute: boolean; + canPublish: boolean; + canSubscribe: boolean; + } + /** * Event handlers passed into the native session instance. */ diff --git a/android/src/main/java/com/opentokreactnative/OTSessionManager.java b/android/src/main/java/com/opentokreactnative/OTSessionManager.java index 00b7210a..de5715bc 100644 --- a/android/src/main/java/com/opentokreactnative/OTSessionManager.java +++ b/android/src/main/java/com/opentokreactnative/OTSessionManager.java @@ -513,6 +513,21 @@ public void getSessionInfo(String sessionId, Callback callback) { callback.invoke(sessionInfo); } + @ReactMethod + public void getSessionCapabilities(String sessionId, Callback callback) { + ConcurrentHashMap mSessions = sharedState.getSessions(); + Session mSession = mSessions.get(sessionId); + WritableMap sessionCapabilitiesMap = Arguments.createMap(); + if (mSession != null){ + Session.Capabilities sessionCapabilities = mSession.getCapabilities(); + sessionCapabilitiesMap.putBoolean("canForceMute", sessionCapabilities.canForceMute); + sessionCapabilitiesMap.putBoolean("canPublish", sessionCapabilities.canPublish); + // Bug in OT Android SDK. This should always be true, but it is set to false: + sessionCapabilitiesMap.putBoolean("canSubscribe", true); + } + callback.invoke(sessionCapabilitiesMap); + } + @ReactMethod public void enableLogs(Boolean logLevel) { setLogLevel(logLevel); diff --git a/docs/OTSession.md b/docs/OTSession.md index a4a5881f..8d8fa0fe 100644 --- a/docs/OTSession.md +++ b/docs/OTSession.md @@ -173,6 +173,17 @@ feature. See the [OpenTok IP Proxy](https://tokbox.com/developer/guides/ip-proxy - `data` (String) -- The [connection data](https://tokbox.com/developer/guides/create-token/#connection-data) for the local client. +***getCapabilities()*** Indicates whether the client can publish and subscribe to streams in the session, based on the role assigned to the token used to connect to the session. This method returns null until you have connected to a session and the `connectionCreated` event has been dispatched. After that event has been dispatched, this method returns an object with the following properties: + +* `canForceMute` (Boolean) -- Whether the client can force mute streams in the session or disable the active mute state in a session (`true`) or not (`false`). + +* `canPublish` (Boolean) -- Whether the client can publish streams to the session (`true`) or not (`false`). + +* `canSubscribe` (Boolean) -- Whether the client can subscribe to streams in the session (`true`) or not (`false`). + + For more information, see the + [OpenTok token documentation](https://tokbox.com/developer/guides/create-token). + **signal()** Sends a signal to clients connected to the session. The method has one parameter, an object that includes the following properties, each of which is optional (although you usually want to set the `data` property): diff --git a/ios/OpenTokReactNative/OTSessionManager.m b/ios/OpenTokReactNative/OTSessionManager.m index 558f3e44..0f852368 100644 --- a/ios/OpenTokReactNative/OTSessionManager.m +++ b/ios/OpenTokReactNative/OTSessionManager.m @@ -87,6 +87,9 @@ @interface RCT_EXTERN_MODULE(OTSessionManager, RCTEventEmitter) RCT_EXTERN_METHOD(getSessionInfo: (NSString*)sessionId callback:(RCTResponseSenderBlock*)callback) +RCT_EXTERN_METHOD(getSessionCapabilities: + (NSString*)sessionId + callback:(RCTResponseSenderBlock*)callback) RCT_EXTERN_METHOD(enableLogs: (BOOL)logLevel) @end diff --git a/ios/OpenTokReactNative/OTSessionManager.swift b/ios/OpenTokReactNative/OTSessionManager.swift index 7a329478..19d684ba 100644 --- a/ios/OpenTokReactNative/OTSessionManager.swift +++ b/ios/OpenTokReactNative/OTSessionManager.swift @@ -330,6 +330,16 @@ class OTSessionManager: RCTEventEmitter { callback([sessionInfo]); } + @objc func getSessionCapabilities(_ sessionId: String, callback: RCTResponseSenderBlock) -> Void{ + guard let session = OTRN.sharedState.sessions[sessionId] else { callback([NSNull()]); return } + var sessionCapabilities: Dictionary = [:]; + sessionCapabilities["canPublish"] = session.capabilities?.canPublish; + // Bug in OT iOS SDK. This is set to false, but it should be true: + sessionCapabilities["canSubscribe"] = true; + sessionCapabilities["canForceMute"] = session.capabilities?.canForceMute; + callback([sessionCapabilities]); + } + @objc func enableLogs(_ logLevel: Bool) -> Void { self.logLevel = logLevel; } diff --git a/src/OTSession.js b/src/OTSession.js index b0e4b3f6..433afe64 100644 --- a/src/OTSession.js +++ b/src/OTSession.js @@ -71,6 +71,12 @@ export default class OTSession extends Component { this.setState({ sessionInfo, }); + // TO DO: The capababilities are set after the sessionConnected event is dispatched. + const sessionCapabilities = OT.getSessionCapabilities(sessionId, (sessionCapabilities) => { + this.setState({ + sessionCapabilities, + }); + }); logOT({ apiKey, sessionId, action: 'rn_on_connect', proxyUrl: sessionOptions.proxyUrl, connectionId: session.connection.connectionId }); if (Object.keys(signal).length > 0) { this.signal(signal); @@ -93,6 +99,9 @@ export default class OTSession extends Component { getSessionInfo() { return this.state.sessionInfo; } + getCapabilities() { + return this.state.sessionCapabilities; + } signal(signal) { const signalData = sanitizeSignalData(signal); OT.sendSignal(this.props.sessionId, signalData.signal, signalData.errorHandler); From f1a772a9ff3679dae64fc0a8cfc123b73974cd31 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Mon, 17 Apr 2023 00:24:42 -0700 Subject: [PATCH 06/24] Add OTSession.reportIssue() --- @types/index.d.ts | 5 +++++ .../com/opentokreactnative/OTSessionManager.java | 12 ++++++++++++ docs/OTSession.md | 4 ++++ ios/OpenTokReactNative/OTSessionManager.m | 3 +++ ios/OpenTokReactNative/OTSessionManager.swift | 7 +++++++ src/OTSession.js | 11 +++++++++++ 6 files changed, 42 insertions(+) diff --git a/@types/index.d.ts b/@types/index.d.ts index 66338196..cc431ab1 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -138,6 +138,11 @@ declare module "opentok-react-native" { canSubscribe: boolean; } + /** + * Used to report an issue + */ + reportIssue(): string + /** * Event handlers passed into the native session instance. */ diff --git a/android/src/main/java/com/opentokreactnative/OTSessionManager.java b/android/src/main/java/com/opentokreactnative/OTSessionManager.java index de5715bc..138699b5 100644 --- a/android/src/main/java/com/opentokreactnative/OTSessionManager.java +++ b/android/src/main/java/com/opentokreactnative/OTSessionManager.java @@ -528,6 +528,18 @@ public void getSessionCapabilities(String sessionId, Callback callback) { callback.invoke(sessionCapabilitiesMap); } + @ReactMethod + public void reportIssue(String sessionId, Callback callback) { + ConcurrentHashMap mSessions = sharedState.getSessions(); + Session mSession = mSessions.get(sessionId); + if (mSession != null){ + callback.invoke(mSession.reportIssue()); + } else { + callback.invoke(null, "Error connecting to session. Could not find native session instance."); + + } + } + @ReactMethod public void enableLogs(Boolean logLevel) { setLogLevel(logLevel); diff --git a/docs/OTSession.md b/docs/OTSession.md index 8d8fa0fe..47b4bdbd 100644 --- a/docs/OTSession.md +++ b/docs/OTSession.md @@ -184,6 +184,10 @@ feature. See the [OpenTok IP Proxy](https://tokbox.com/developer/guides/ip-proxy For more information, see the [OpenTok token documentation](https://tokbox.com/developer/guides/create-token). +**reportIssue()** Lets you report that your app experienced an issue (to view with +[Inspector](http://tokbox.com/developer/tools/Inspector) or to discuss with the Vonage API +support team.) The method returns a Promise that resolves with a string, the issue ID. + **signal()** Sends a signal to clients connected to the session. The method has one parameter, an object that includes the following properties, each of which is optional (although you usually want to set the `data` property): diff --git a/ios/OpenTokReactNative/OTSessionManager.m b/ios/OpenTokReactNative/OTSessionManager.m index 0f852368..ded81dd3 100644 --- a/ios/OpenTokReactNative/OTSessionManager.m +++ b/ios/OpenTokReactNative/OTSessionManager.m @@ -90,6 +90,9 @@ @interface RCT_EXTERN_MODULE(OTSessionManager, RCTEventEmitter) RCT_EXTERN_METHOD(getSessionCapabilities: (NSString*)sessionId callback:(RCTResponseSenderBlock*)callback) +RCT_EXTERN_METHOD(reportIssue: + (NSString*)sessionId + callback:(RCTResponseSenderBlock*)callback) RCT_EXTERN_METHOD(enableLogs: (BOOL)logLevel) @end diff --git a/ios/OpenTokReactNative/OTSessionManager.swift b/ios/OpenTokReactNative/OTSessionManager.swift index 19d684ba..b84751e9 100644 --- a/ios/OpenTokReactNative/OTSessionManager.swift +++ b/ios/OpenTokReactNative/OTSessionManager.swift @@ -340,6 +340,13 @@ class OTSessionManager: RCTEventEmitter { callback([sessionCapabilities]); } + @objc func reportIssue(_ sessionId: String, callback: RCTResponseSenderBlock) -> Void{ + guard let session = OTRN.sharedState.sessions[sessionId] else { callback([NSNull()]); return } + var issueId:NSString? = "" + session.reportIssue(&issueId) + callback([issueId! as NSString]) + } + @objc func enableLogs(_ logLevel: Bool) -> Void { self.logLevel = logLevel; } diff --git a/src/OTSession.js b/src/OTSession.js index 433afe64..db922e07 100644 --- a/src/OTSession.js +++ b/src/OTSession.js @@ -102,6 +102,17 @@ export default class OTSession extends Component { getCapabilities() { return this.state.sessionCapabilities; } + async reportIssue() { + return new Promise((resolve, reject) => { + OT.reportIssue(this.props.sessionId, (reportIssueId, error) => { + if (reportIssueId) { + resolve(reportIssueId) + } else { + reject (new Error(error)) + } + }); + }) + } signal(signal) { const signalData = sanitizeSignalData(signal); OT.sendSignal(this.props.sessionId, signalData.signal, signalData.errorHandler); From bc04fe1d24906caba78c833644e56238056fc82c Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Mon, 17 Apr 2023 09:21:59 -0700 Subject: [PATCH 07/24] Minor docs correction --- docs/OTSession.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/OTSession.md b/docs/OTSession.md index 47b4bdbd..e092b245 100644 --- a/docs/OTSession.md +++ b/docs/OTSession.md @@ -150,9 +150,7 @@ The default value is false. **isCamera2Capable** (Boolean) -- Deprecated and ignored. Android only. -**proxyUrl** - -The proxy URL. This is an [add-on feature](https://www.vonage.com/communications-apis/video/pricing//plans) +**proxyUrl** (String, optional) -- The proxy URL. This is an [add-on feature](https://www.vonage.com/communications-apis/video/pricing//plans) feature. See the [OpenTok IP Proxy](https://tokbox.com/developer/guides/ip-proxy/) developer guide. **useTextureViews** (Boolean) -- Set to `true` to use texture views. The default is `false`. Android only. From fb3dd947f2b5b8c5acfab1ac87441f998708676d Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Mon, 17 Apr 2023 11:43:08 -0700 Subject: [PATCH 08/24] Add OTPublisher scalableScreenshare option --- .../main/java/com/opentokreactnative/OTSessionManager.java | 2 ++ docs/OTPublisher.md | 5 +++++ ios/OpenTokReactNative/OTSessionManager.swift | 3 +++ src/helpers/OTPublisherHelper.js | 2 ++ 4 files changed, 12 insertions(+) diff --git a/android/src/main/java/com/opentokreactnative/OTSessionManager.java b/android/src/main/java/com/opentokreactnative/OTSessionManager.java index 138699b5..71094dc9 100644 --- a/android/src/main/java/com/opentokreactnative/OTSessionManager.java +++ b/android/src/main/java/com/opentokreactnative/OTSessionManager.java @@ -152,6 +152,7 @@ public void initPublisher(String publisherId, ReadableMap properties, Callback c Boolean publishAudio = properties.getBoolean("publishAudio"); Boolean publishVideo = properties.getBoolean("publishVideo"); String videoSource = properties.getString("videoSource"); + Boolean scalableScreenshare = properties.getBoolean("scalableScreenshare"); Publisher mPublisher = null; if (videoSource.equals("screen")) { View view = getCurrentActivity().getWindow().getDecorView().getRootView(); @@ -165,6 +166,7 @@ public void initPublisher(String publisherId, ReadableMap properties, Callback c .resolution(Publisher.CameraCaptureResolution.valueOf(resolution)) .frameRate(Publisher.CameraCaptureFrameRate.valueOf(frameRate)) .capturer(capturer) + .scalableScreenshare(scalableScreenshare) .build(); mPublisher.setPublisherVideoType(PublisherKit.PublisherKitVideoType.PublisherKitVideoTypeScreen); } else { diff --git a/docs/OTPublisher.md b/docs/OTPublisher.md index 690fb419..6809855c 100644 --- a/docs/OTPublisher.md +++ b/docs/OTPublisher.md @@ -108,6 +108,11 @@ see the Subscriber videoDisabled event and the OpenTok Media Router and media mo * **publishVideo** (Boolean) -- Whether to publish video. The default is `true`. +* **scalableScreenshare** (Boolean) -- Whether to allow use of +{scalable video}(https://tokbox.com/developer/guides/scalable-video/) for a screen-sharing publisher +(true) or not (false, the default). This only applies to a publisher that has the `videoSource` set +to "screen". + * **resolution** (String) - The desired resolution of the video. The format of the string is "widthxheight", where the width and height are represented in pixels. Valid values are "1280x720", "640x480", and "352x288". The published video will only use the desired resolution if the client configuration supports it. Some devices and clients do not support each of these resolution settings. * **videoContentHint** (String) -- Sets the content hint of the video track of the publisher's stream. You can set this to one of the following values: "", "motion", "details" or "text". For additional information, see the [documentation](https://tokbox.com/developer/sdks/js/reference/OT.html#initPublisher) for the `videoContentHint` option of the diff --git a/ios/OpenTokReactNative/OTSessionManager.swift b/ios/OpenTokReactNative/OTSessionManager.swift index b84751e9..68c65777 100644 --- a/ios/OpenTokReactNative/OTSessionManager.swift +++ b/ios/OpenTokReactNative/OTSessionManager.swift @@ -93,6 +93,9 @@ class OTSessionManager: RCTEventEmitter { guard let screenView = RCTPresentedViewController()?.view else { let errorInfo = EventUtils.createErrorMessage("There was an error setting the videoSource as screen") callback([errorInfo]); + if let scalableScreenshare = properties["scalableScreenshare"] as? Bool { + publisherProperties.scalableScreenshare = scalableScreenshare; + } return } publisher.videoType = .screen; diff --git a/src/helpers/OTPublisherHelper.js b/src/helpers/OTPublisherHelper.js index 7b34ccea..0787c765 100644 --- a/src/helpers/OTPublisherHelper.js +++ b/src/helpers/OTPublisherHelper.js @@ -62,6 +62,7 @@ const sanitizeProperties = (properties) => { resolution: sanitizeResolution(), videoContentHint: '', videoSource: 'camera', + scalableScreenshare: false, }; } return { @@ -78,6 +79,7 @@ const sanitizeProperties = (properties) => { resolution: sanitizeResolution(properties.resolution), videoContentHint: sanitizeVideoContentHint(properties.videoContentHint), videoSource: sanitizeVideoSource(properties.videoSource), + scalableScreenshare: Boolean(properties.scalableScreenshare), }; }; From 6c3c8cf4c54531a9bb16513c0887c7f9f0479768 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Mon, 17 Apr 2023 14:07:03 -0700 Subject: [PATCH 09/24] Add audioNetworkStats and videoNetworkStats events For OTPublisher and OTSubscriber --- .../opentokreactnative/OTSessionManager.java | 26 ++++++++++++++++ .../opentokreactnative/utils/EventUtils.java | 30 +++++++++++++++++++ ios/OpenTokReactNative/OTSessionManager.swift | 18 +++++++++++ ios/OpenTokReactNative/Utils/EventUtils.swift | 30 +++++++++++++++++++ src/helpers/OTPublisherHelper.js | 4 +++ 5 files changed, 108 insertions(+) diff --git a/android/src/main/java/com/opentokreactnative/OTSessionManager.java b/android/src/main/java/com/opentokreactnative/OTSessionManager.java index 71094dc9..6021e1c4 100644 --- a/android/src/main/java/com/opentokreactnative/OTSessionManager.java +++ b/android/src/main/java/com/opentokreactnative/OTSessionManager.java @@ -48,6 +48,8 @@ public class OTSessionManager extends ReactContextBaseJavaModule PublisherKit.PublisherListener, PublisherKit.AudioLevelListener, PublisherKit.PublisherRtcStatsReportListener, + PublisherKit.AudioStatsListener, + PublisherKit.VideoStatsListener, SubscriberKit.SubscriberListener, Session.SignalListener, Session.ConnectionListener, @@ -191,6 +193,8 @@ public void initPublisher(String publisherId, ReadableMap properties, Callback c mPublisher.setAudioFallbackEnabled(audioFallbackEnabled); mPublisher.setPublishVideo(publishVideo); mPublisher.setPublishAudio(publishAudio); + mPublisher.setAudioStatsListener(this); + mPublisher.setVideoStatsListener(this); ConcurrentHashMap mPublishers = sharedState.getPublishers(); mPublishers.put(publisherId, mPublisher); callback.invoke(); @@ -791,6 +795,28 @@ public void onRtcStatsReport(PublisherKit publisher, PublisherKit.PublisherRtcSt } } + @Override + public void onAudioStats(PublisherKit publisher, PublisherKit.PublisherAudioStats[] stats) { + + String publisherId = Utils.getPublisherId(publisher); + if (publisherId.length() > 0) { + WritableArray publisherInfo = EventUtils.preparePublisherAudioStats(stats); + String event = publisherId + ":" + publisherPreface + "onAudioStats"; + sendEventArray(this.getReactApplicationContext(), event, publisherInfo); + } + } + + @Override + public void onVideoStats(PublisherKit publisher, PublisherKit.PublisherVideoStats[] stats) { + + String publisherId = Utils.getPublisherId(publisher); + if (publisherId.length() > 0) { + WritableArray publisherInfo = EventUtils.preparePublisherVideoStats(stats); + String event = publisherId + ":" + publisherPreface + "onVideoStats"; + sendEventArray(this.getReactApplicationContext(), event, publisherInfo); + } + } + @Override public void onConnected(SubscriberKit subscriberKit) { diff --git a/android/src/main/java/com/opentokreactnative/utils/EventUtils.java b/android/src/main/java/com/opentokreactnative/utils/EventUtils.java index c3ecb430..94fb9c0f 100644 --- a/android/src/main/java/com/opentokreactnative/utils/EventUtils.java +++ b/android/src/main/java/com/opentokreactnative/utils/EventUtils.java @@ -126,6 +126,36 @@ public static WritableArray preparePublisherRtcStats(PublisherKit.PublisherRtcSt return statsArrayMap; } + public static WritableArray preparePublisherAudioStats(PublisherKit.PublisherAudioStats[] stats) { + WritableArray statsArrayMap = Arguments.createArray(); + for (PublisherKit.PublisherAudioStats stat : stats) { + WritableMap audioStats = Arguments.createMap(); + audioStats.putString("connectionId", stat.connectionId); + audioStats.putString("subscriberId", stat.subscriberId); + audioStats.putDouble("audioBytesSent", stat.audioBytesSent); + audioStats.putDouble("audioPacketsLost", stat.audioPacketsLost); + audioStats.putDouble("audioPacketsSent", stat.audioPacketsSent); + audioStats.putDouble("startTime", stat.startTime); + statsArrayMap.pushMap(audioStats); + } + return statsArrayMap; + } + + public static WritableArray preparePublisherVideoStats(PublisherKit.PublisherVideoStats[] stats) { + WritableArray statsArrayMap = Arguments.createArray(); + for (PublisherKit.PublisherVideoStats stat : stats) { + WritableMap videoStats = Arguments.createMap(); + videoStats.putString("connectionId", stat.connectionId); + videoStats.putString("subscriberId", stat.subscriberId); + videoStats.putDouble("videoBytesSent", stat.videoBytesSent); + videoStats.putDouble("videoPacketsLost", stat.videoPacketsLost); + videoStats.putDouble("videoPacketsSent", stat.videoPacketsSent); + videoStats.putDouble("startTime", stat.startTime); + statsArrayMap.pushMap(videoStats); + } + return statsArrayMap; + } + public static WritableMap createError(String message) { WritableMap errorInfo = Arguments.createMap(); diff --git a/ios/OpenTokReactNative/OTSessionManager.swift b/ios/OpenTokReactNative/OTSessionManager.swift index 68c65777..5f2c7bfc 100644 --- a/ios/OpenTokReactNative/OTSessionManager.swift +++ b/ios/OpenTokReactNative/OTSessionManager.swift @@ -574,6 +574,24 @@ extension OTSessionManager: OTPublisherKitRtcStatsReportDelegate { } } +extension OTSessionManager: OTPublisherKitNetworkStatsDelegate { + func publisher(_ publisher: OTPublisherKit, audioNetworkStatsUpdated stats: [OTPublisherKitAudioNetworkStats]) { + let publisherId = Utils.getPublisherId(publisher as! OTPublisher); + if (publisherId.count > 0) { + let statsArray: [Dictionary] = EventUtils.preparePublisherAudioNetworkStats(stats); + self.emitEvent("\(publisherId):\(EventUtils.publisherPreface)audioNetworkStats", data: statsArray) + } + } + + func publisher(_ publisher: OTPublisherKit, videoNetworkStatsUpdated stats: [OTPublisherKitVideoNetworkStats]) { + let publisherId = Utils.getPublisherId(publisher as! OTPublisher); + if (publisherId.count > 0) { + let statsArray: [Dictionary] = EventUtils.preparePublisherVideoNetworkStats(stats); + self.emitEvent("\(publisherId):\(EventUtils.publisherPreface)videoNetworkStats", data: statsArray) + } + } +} + extension OTSessionManager: OTSubscriberDelegate { func subscriberDidConnect(toStream subscriberKit: OTSubscriberKit) { if let stream = subscriberKit.stream { diff --git a/ios/OpenTokReactNative/Utils/EventUtils.swift b/ios/OpenTokReactNative/Utils/EventUtils.swift index 05ed688b..b41eb838 100644 --- a/ios/OpenTokReactNative/Utils/EventUtils.swift +++ b/ios/OpenTokReactNative/Utils/EventUtils.swift @@ -67,6 +67,36 @@ class EventUtils { return statsArray; } + static func preparePublisherVideoNetworkStats(_ videoStats: [OTPublisherKitVideoNetworkStats]) -> [Dictionary] { + var statsArray:[Dictionary] = []; + for value in videoStats { + var stats: Dictionary = [:]; + stats["connectionId"] = value.connectionId; + stats["subscriberId"] = value.subscriberId; + stats["videoPacketsLost"] = value.videoPacketsLost; + stats["videoBytesSent"] = value.videoBytesSent; + stats["videoPacketsSent"] = value.videoPacketsSent; + stats["timestamp"] = value.timestamp; + statsArray.append(stats); + } + return statsArray; + } + + static func preparePublisherAudioNetworkStats(_ audioStats: [OTPublisherKitAudioNetworkStats]) -> [Dictionary] { + var statsArray:[Dictionary] = []; + for value in audioStats { + var stats: Dictionary = [:]; + stats["connectionId"] = value.connectionId; + stats["subscriberId"] = value.subscriberId; + stats["audioPacketsLost"] = value.audioPacketsLost; + stats["audioPacketsSent"] = value.audioPacketsSent; + stats["audioPacketsSent"] = value.audioPacketsSent; + stats["timestamp"] = value.timestamp; + statsArray.append(stats); + } + return statsArray; + } + static func prepareSubscriberVideoNetworkStatsEventData(_ videoStats: OTSubscriberKitVideoNetworkStats) -> Dictionary { var videoStatsEventData: Dictionary = [:]; videoStatsEventData["videoPacketsLost"] = videoStats.videoPacketsLost; diff --git a/src/helpers/OTPublisherHelper.js b/src/helpers/OTPublisherHelper.js index 0787c765..51807295 100644 --- a/src/helpers/OTPublisherHelper.js +++ b/src/helpers/OTPublisherHelper.js @@ -93,14 +93,18 @@ const sanitizePublisherEvents = (publisherId, events) => { streamDestroyed: 'streamDestroyed', error: 'didFailWithError', audioLevel: 'audioLevelUpdated', + audioNetworkStats: 'audioStats', rtcStatsReport: 'rtcStatsReport', + videoNetworkStats: 'videoStats', }, android: { streamCreated: 'onStreamCreated', streamDestroyed: 'onStreamDestroyed', error: 'onError', audioLevel: 'onAudioLevelUpdated', + audioNetworkStats: 'onAudioStats', rtcStatsReport: 'onRtcStatsReport', + videoNetworkStats: 'onVideoStats', }, }; return reassignEvents('publisher', customEvents, events, publisherId); From d98fa358f0c4ffb7c176071740575e87510f61cf Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Tue, 18 Apr 2023 12:31:10 -0700 Subject: [PATCH 10/24] Add support for 1080p video --- @types/index.d.ts | 4 ++-- docs/OTPublisher.md | 2 +- ios/OpenTokReactNative/Utils/Utils.swift | 2 ++ src/helpers/OTPublisherHelper.js | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index cc431ab1..ca6e277f 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -354,9 +354,9 @@ declare module "opentok-react-native" { publishVideo?: boolean; /** - * The desired resolution of the video. The format of the string is "widthxheight", where the width and height are represented in pixels. Valid values are "1280x720", "640x480", and "352x288". The published video will only use the desired resolution if the client configuration supports it. Some devices and clients do not support each of these resolution settings. + * The desired resolution of the video. The format of the string is "widthxheight", where the width and height are represented in pixels. Valid values are "1920x1080", "1280x720", "640x480", and "352x288". The published video will only use the desired resolution if the client configuration supports it. Some devices and clients do not support each of these resolution settings. */ - resolution?: "1280x720" | "640x480" | "352x288"; + resolution?: "1920x1080" | "1280x720" | "640x480" | "352x288"; /** * If this property is set to false, the video subsystem will not be initialized for the publisher, and setting the publishVideo property will have no effect. If your application does not require the use of video, it is recommended to set this property rather than use the publishVideo property, which only temporarily disables the video track. diff --git a/docs/OTPublisher.md b/docs/OTPublisher.md index 6809855c..1447ca46 100644 --- a/docs/OTPublisher.md +++ b/docs/OTPublisher.md @@ -113,7 +113,7 @@ see the Subscriber videoDisabled event and the OpenTok Media Router and media mo (true) or not (false, the default). This only applies to a publisher that has the `videoSource` set to "screen". -* **resolution** (String) - The desired resolution of the video. The format of the string is "widthxheight", where the width and height are represented in pixels. Valid values are "1280x720", "640x480", and "352x288". The published video will only use the desired resolution if the client configuration supports it. Some devices and clients do not support each of these resolution settings. +* **resolution** (String) - The desired resolution of the video. The format of the string is "widthxheight", where the width and height are represented in pixels. Valid values are "1920x1080", "1280x720", "640x480", and "352x288". The published video will only use the desired resolution if the client configuration supports it. Some devices and clients do not support each of these resolution settings. * **videoContentHint** (String) -- Sets the content hint of the video track of the publisher's stream. You can set this to one of the following values: "", "motion", "details" or "text". For additional information, see the [documentation](https://tokbox.com/developer/sdks/js/reference/OT.html#initPublisher) for the `videoContentHint` option of the `OT.initPublisher()` method of the OpenTok.js SDK. diff --git a/ios/OpenTokReactNative/Utils/Utils.swift b/ios/OpenTokReactNative/Utils/Utils.swift index 287d972f..bb10be3d 100644 --- a/ios/OpenTokReactNative/Utils/Utils.swift +++ b/ios/OpenTokReactNative/Utils/Utils.swift @@ -12,6 +12,8 @@ class Utils { static func sanitizeCameraResolution(_ resolution: Any) -> OTCameraCaptureResolution { guard let cameraResolution = resolution as? String else { return .medium }; switch cameraResolution { + case "HIGH_1080P": + return .high1080p; case "HIGH": return .high; case "LOW": diff --git a/src/helpers/OTPublisherHelper.js b/src/helpers/OTPublisherHelper.js index 51807295..2db91ab4 100644 --- a/src/helpers/OTPublisherHelper.js +++ b/src/helpers/OTPublisherHelper.js @@ -8,6 +8,8 @@ const sanitizeResolution = (resolution) => { return 'MEDIUM'; case '1280x720': return 'HIGH'; + case '1920x1080': + return 'HIGH_1080P'; default: return 'MEDIUM'; } From 35744358c4b5963605fe919b3fcb2e835ce6b089 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Tue, 18 Apr 2023 18:19:33 -0700 Subject: [PATCH 11/24] Add support for per-subscriber audio level API --- .../com/opentokreactnative/OTSessionManager.java | 13 +++++++++++++ docs/OTSubscriber.md | 1 + ios/OpenTokReactNative/OTSessionManager.m | 3 +++ ios/OpenTokReactNative/OTSessionManager.swift | 8 ++++++++ src/OTSubscriber.js | 3 +++ 5 files changed, 28 insertions(+) diff --git a/android/src/main/java/com/opentokreactnative/OTSessionManager.java b/android/src/main/java/com/opentokreactnative/OTSessionManager.java index 6021e1c4..551c347f 100644 --- a/android/src/main/java/com/opentokreactnative/OTSessionManager.java +++ b/android/src/main/java/com/opentokreactnative/OTSessionManager.java @@ -250,6 +250,9 @@ public void subscribeToStream(String streamId, String sessionId, ReadableMap pro preferredResolution.getInt("height")); mSubscriber.setPreferredResolution(resolution); } + if (properties.hasKey("audioVolume")) { + mSubscriber.setAudioVolume((float) properties.getDouble("audioVolume")); + } mSubscribers.put(streamId, mSubscriber); if (mSession != null) { mSession.subscribe(mSubscriber); @@ -375,6 +378,16 @@ public void setPreferredFrameRate(String streamId, Float frameRate) { } } + @ReactMethod + public void setAudioVolume(String streamId, Float audioVolume) { + + ConcurrentHashMap mSubscribers = sharedState.getSubscribers(); + Subscriber mSubscriber = mSubscribers.get(streamId); + if (mSubscriber != null) { + mSubscriber.setAudioVolume(audioVolume); + } + } + @ReactMethod public void getSubscriberRtcStatsReport(String streamId) { diff --git a/docs/OTSubscriber.md b/docs/OTSubscriber.md index cc83e78f..6cbcb684 100644 --- a/docs/OTSubscriber.md +++ b/docs/OTSubscriber.md @@ -19,6 +19,7 @@ * **preferredFrameRate** (Number) — Set this to the desired frame rate (in frames per second). Set this to null to remove the preferred frame rate, and the client will use the highest frame rate available. Valid values are 30, 15, 7, and 1. + * **audioVolume** (Number) — Sets the audio volume, between 0 and 100, of the subscriber. If the value is not in this range, it will be clamped to it. The `OTSubscriber` component will subscribe to a specified stream from a specified session upon mounting. The `OTSubscriber` component will stop subscribing and unsubscribing when it's unmounting. diff --git a/ios/OpenTokReactNative/OTSessionManager.m b/ios/OpenTokReactNative/OTSessionManager.m index ded81dd3..704c7346 100644 --- a/ios/OpenTokReactNative/OTSessionManager.m +++ b/ios/OpenTokReactNative/OTSessionManager.m @@ -63,6 +63,9 @@ @interface RCT_EXTERN_MODULE(OTSessionManager, RCTEventEmitter) RCT_EXTERN_METHOD(setPreferredFrameRate: (NSString*)streamId frameRate:(nonnull NSNumber*)frameRate) +RCT_EXTERN_METHOD(setAudioVolume: + (NSString*)streamId + audioVolume:(nonnull NSNumber*)audioVolume) RCT_EXTERN_METHOD(changeCameraPosition: (NSString*)publisherId cameraPosition:(NSString*)cameraPosition) diff --git a/ios/OpenTokReactNative/OTSessionManager.swift b/ios/OpenTokReactNative/OTSessionManager.swift index 5f2c7bfc..1c2d711b 100644 --- a/ios/OpenTokReactNative/OTSessionManager.swift +++ b/ios/OpenTokReactNative/OTSessionManager.swift @@ -159,6 +159,9 @@ class OTSessionManager: RCTEventEmitter { subscriber.subscribeToVideo = Utils.sanitizeBooleanProperty(properties["subscribeToVideo"] as Any); subscriber.preferredFrameRate = Utils.sanitizePreferredFrameRate(properties["preferredFrameRate"] as Any); subscriber.preferredResolution = Utils.sanitizePreferredResolution(properties["preferredResolution"] as Any); + if let audioVolume = properties["audioVolume"] as? Double { + subscriber.audioVolume = audioVolume; + } subscriber.rtcStatsReportDelegate = self; if let err = error { self.dispatchErrorViaCallback(callback, error: err) @@ -234,6 +237,11 @@ class OTSessionManager: RCTEventEmitter { subscriber.preferredFrameRate = Utils.sanitizePreferredFrameRate(frameRate); } + @objc func setAudioVolume(_ streamId: String, audioVolume: Double) -> Void { + guard let subscriber = OTRN.sharedState.subscribers[streamId] else { return } + subscriber.audioVolume = audioVolume; + } + @objc func getSubscriberRtcStatsReport(_ streamId: String) -> Void { guard let subscriber = OTRN.sharedState.subscribers[streamId] else { return } subscriber.getRtcStatsReport() diff --git a/src/OTSubscriber.js b/src/OTSubscriber.js index 440832da..8a1c7060 100644 --- a/src/OTSubscriber.js +++ b/src/OTSubscriber.js @@ -53,6 +53,9 @@ export default class OTSubscriber extends Component { if (preferredFrameRate !== undefined) { OT.setPreferredFrameRate(streamId, sanitizeFrameRate(preferredFrameRate)); } + if (audioVolume !== undefined) { + OT.setAudioVolume(streamId, audioVolume); + } }); this.setState({ streamProperties }); } From c1f3879608676974a5c3f2d0d530d6235e466f16 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Wed, 19 Apr 2023 13:53:04 -0700 Subject: [PATCH 12/24] Add docs on new features --- CHANGELOG.md | 18 ++++++++++++++++++ docs/EventData.md | 44 +++++++++++++++++++++++++++++++++++++++++++- docs/OTPublisher.md | 4 ++++ docs/OTSession.md | 2 +- docs/OTSubscriber.md | 2 +- docs/index.md | 16 ---------------- 6 files changed, 67 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f3ecb08..99a9e36a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# 0.21.5 (TBD 2023) + +- [Update]: Add API to implement functionality missing from the OpenTok Andriod and iOS SDKs: + + * `OTSession.getCapabilities()` method + * `reportIssue()` methods and `rtcStatsReport` events added to OTPublisher and OTSubscriber + * OTPublisher `scalableScreenshare` option (in the OTPublisher properties) + * OTPublisher `audioNetworkStats` and `videoNetworkStats` events + * `OTPublisher.getRtcStatsReport()` method and OTPublisher `rtcStatsReport` event + * "1920x1080" option for OTPublisher `resolution` (for FHD video support) + * OTSubscriber `audioVolume` property. + +For more info, see the docs: + +* [OTPublisher](/docs/OTPublisher.md) +* [OTSession](/docs/OTSession.md) +* [OTSubscriber](/docs/OTSubscriber.md) + # 0.21.4 (April 12 2023) - [Update]: Revert OpenTok iOS SDK back 2.23.1. There are issues with diff --git a/docs/EventData.md b/docs/EventData.md index 38d380fe..f330d512 100644 --- a/docs/EventData.md +++ b/docs/EventData.md @@ -114,12 +114,54 @@ You can find the structure of the object below: You can find the structure of the object below: ```javascript - error = { + event = { code: string, message: string, }; ``` +## PublisherVideoNetworkStatsEvent + +To get video data for a publisher, register an event listener for the OTPublisher +`videoNetworkStats` event. The object has the following structure: + +```javascript + event = [ + { + connectionId: string, + subscriberId: string, + videoPacketsLost: number, + videoBytesSent: number, + videoPacketsSent: number, + timestamp: number, + } + ]; +``` + +Note that this event object is an array of objects. See the docs for +the OTPublisher `videoNetworkStats` event. + +## PublisherAudioNetworkStatsEvent + +To get audio data for a publisher, register an event listener for the OTPublisher +`audioNetworkStats` event. The object has the following structure: + +```javascript + event = [ + { + connectionId: string, + subscriberId: string, + audioPacketsLost: number, + audioPacketsSent: number, + audioPacketsSent: number, + timestamp: number, + } + ]; +``` + +Note that this event object is an array of objects. See the docs for +the OTPublisher `audioNetworkStats` event. + ## RtcStatsReportEvent You can find the structure of the object below: diff --git a/docs/OTPublisher.md b/docs/OTPublisher.md index 1447ca46..0fa454fa 100644 --- a/docs/OTPublisher.md +++ b/docs/OTPublisher.md @@ -126,6 +126,10 @@ to "screen". * **audioLevel** (Number) -- The audio level, from 0 to 1.0. Adjust this value logarithmically for use in adjusting a user interface element, such as a volume meter. Use a moving average to smooth the data. +**audioNetworkStats** (Object) -- + +**videoNetworkStats** (Object) -- + * **error** (Object) -- Sent if the publisher encounters an error. After this message is sent, the publisher can be considered fully detached from a session and may be released. * **otrnError** (Object) -- Sent if there is an error with the communication between the native publisher instance and the JS component. diff --git a/docs/OTSession.md b/docs/OTSession.md index e092b245..76380a18 100644 --- a/docs/OTSession.md +++ b/docs/OTSession.md @@ -171,7 +171,7 @@ feature. See the [OpenTok IP Proxy](https://tokbox.com/developer/guides/ip-proxy - `data` (String) -- The [connection data](https://tokbox.com/developer/guides/create-token/#connection-data) for the local client. -***getCapabilities()*** Indicates whether the client can publish and subscribe to streams in the session, based on the role assigned to the token used to connect to the session. This method returns null until you have connected to a session and the `connectionCreated` event has been dispatched. After that event has been dispatched, this method returns an object with the following properties: +***getCapabilities()*** Indicates whether the client can publish and subscribe to streams in the session, , based on the roles assigned to the [client token](https://tokbox.com/developer/guides/create-token) used to connect to the session. This method returns null until you have connected to a session and the `connectionCreated` event has been dispatched. After that event has been dispatched, this method returns an object with the following properties: * `canForceMute` (Boolean) -- Whether the client can force mute streams in the session or disable the active mute state in a session (`true`) or not (`false`). diff --git a/docs/OTSubscriber.md b/docs/OTSubscriber.md index 6cbcb684..c0584862 100644 --- a/docs/OTSubscriber.md +++ b/docs/OTSubscriber.md @@ -35,7 +35,7 @@ specified stream ID. This is an asynchronous operation. The OTSubscriber object See [SubscriberAudioLevelEvent](./EventData.md#SubscriberAudioLevelEvent) * **audioNetworkStats** (Object) — Sent periodically to report audio statistics for the subscriber. - A [SessionConnectEvent](./EventData.md#SessionConnectEvent) object is passed into the event handler. + A [AudioNetworkStats](./EventData.md#AudioNetworkStats) object is passed into the event handler. * **connected** () — Sent when the subscriber successfully connects to the stream. The event object includes a `streamId` property, identifying the stream. diff --git a/docs/index.md b/docs/index.md index 25083f7e..db5249fa 100644 --- a/docs/index.md +++ b/docs/index.md @@ -56,32 +56,16 @@ the stream name and muting the microphone or camera. Publishers and subscribers * **Codec detection support** -- There is no way to detect [video codecs](https://tokbox.com/developer/guides/codecs/) supported on a device. -* **Scalable video for screen-sharing videos** -- [This](https://tokbox.com/developer/guides/scalable-video/) -is not supported for screen-sharing videos. - * **Force mute** -- Clients using OpenTok React Native can be forced to mute (by clients using the OpenTok REST API, server SDKs, or client SDKs). However, there is no support for events and methods related to the [force mute feature](https://tokbox.com/developer/guides/moderation/#force_mute). -* **Getting client capabilities** -- There is no method to determine whether the client -can publish or force mute, based on the roles assigned to the -[client token](https://tokbox.com/developer/guides/create-token). - -* **Reporting issues** -- There is no way to programmatically report that your app experienced an issue -(to view with [Inspector](http://tokbox.com/developer/tools/Inspector) or to discuss with -the Vonage API support team.) - * **iOS delegate callback queue** -- For iOS, you cannot assign the delegate callback queue (the GCD queue). See the docs for the [OTSession.apiQueue property](https://tokbox.com/developer/sdks/ios/reference/Classes/OTSession.html#//api/name/apiQueue) in the OpenTok iOS SDK. -* **Publisher audio level and video level events** -- These events (corresponding to the -PublisherKit.AudioStatsListener and PublisherKit.VideoStatsListener interfaces in the OpenTok Android -SDK and the `OTPublisherKitNetworkStatsDelegate` protocol in the OpenTok iOS SDK) have not been -implemented. - * **API enhancements from OpenTok Android and iOS SDK v2.23+** -- These features include per-subscriber audio volume control, scalable video support for screen sharing, and support for publishing FHD (1920x1080-pixel) streams. These have not been implemented. From 399afa65303628e1b5bf68a6de7ebb114a07ed58 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Wed, 19 Apr 2023 14:11:07 -0700 Subject: [PATCH 13/24] rev version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0ddaccb9..5521a76b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "opentok-react-native", - "version": "0.21.4", + "version": "0.21.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "opentok-react-native", - "version": "0.21.4", + "version": "0.21.5", "license": "MIT", "dependencies": { "axios": "^0.21.1", diff --git a/package.json b/package.json index 26ccc162..4a4f30d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opentok-react-native", - "version": "0.21.4", + "version": "0.21.5", "description": "React Native components for OpenTok iOS and Android SDKs", "main": "src/index.js", "homepage": "https://www.tokbox.com", From 9c1b80f92636161adba2b0d51d24c079fdc0db67 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Wed, 19 Apr 2023 15:27:12 -0700 Subject: [PATCH 14/24] Rev version to 2.22.0 --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a9e36a..01697750 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.21.5 (TBD 2023) +# 0.22.0 (TBD 2023) - [Update]: Add API to implement functionality missing from the OpenTok Andriod and iOS SDKs: diff --git a/package-lock.json b/package-lock.json index 5521a76b..3c9bae79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "opentok-react-native", - "version": "0.21.5", + "version": "0.22.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "opentok-react-native", - "version": "0.21.5", + "version": "0.22.0", "license": "MIT", "dependencies": { "axios": "^0.21.1", From f13077694f33d8f1c01178ce74c30a35212e47de Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Wed, 19 Apr 2023 15:45:32 -0700 Subject: [PATCH 15/24] Docs edit --- docs/index.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/index.md b/docs/index.md index db5249fa..35271398 100644 --- a/docs/index.md +++ b/docs/index.md @@ -66,10 +66,6 @@ GCD queue). See the docs for the [OTSession.apiQueue property](https://tokbox.com/developer/sdks/ios/reference/Classes/OTSession.html#//api/name/apiQueue) in the OpenTok iOS SDK. -* **API enhancements from OpenTok Android and iOS SDK v2.23+** -- These features include -per-subscriber audio volume control, scalable video support for screen sharing, and support -for publishing FHD (1920x1080-pixel) streams. These have not been implemented. - To build Android and iOS apps that use these features, use the [OpenTok Android SDK](https://tokbox.com/developer/sdks/android/) and the [OpenTok iOS SDK](https://tokbox.com/developer/sdks/ios/). From d99efb3998ae40459e4b9757d890a9264e85b3c0 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Thu, 20 Apr 2023 14:21:54 -0700 Subject: [PATCH 16/24] Have OTSession.getCapabilities() return Promise --- @types/index.d.ts | 6 +++--- docs/OTSession.md | 8 +++++--- src/OTSession.js | 16 +++++++++------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index ca6e277f..d94ffa9a 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -132,16 +132,16 @@ declare module "opentok-react-native" { /** * Used to get capabilities of the client */ - getCapabilities(): { + getCapabilities(): Promise<{ canForceMute: boolean; canPublish: boolean; canSubscribe: boolean; - } + }> /** * Used to report an issue */ - reportIssue(): string + reportIssue(): Promise /** * Event handlers passed into the native session instance. diff --git a/docs/OTSession.md b/docs/OTSession.md index 76380a18..c330ee97 100644 --- a/docs/OTSession.md +++ b/docs/OTSession.md @@ -171,7 +171,7 @@ feature. See the [OpenTok IP Proxy](https://tokbox.com/developer/guides/ip-proxy - `data` (String) -- The [connection data](https://tokbox.com/developer/guides/create-token/#connection-data) for the local client. -***getCapabilities()*** Indicates whether the client can publish and subscribe to streams in the session, , based on the roles assigned to the [client token](https://tokbox.com/developer/guides/create-token) used to connect to the session. This method returns null until you have connected to a session and the `connectionCreated` event has been dispatched. After that event has been dispatched, this method returns an object with the following properties: +***getCapabilities()*** Indicates whether the client can publish and subscribe to streams in the session, , based on the roles assigned to the [client token](https://tokbox.com/developer/guides/create-token) used to connect to the session. The method returns a Promise that resolves with an object with the following properties: * `canForceMute` (Boolean) -- Whether the client can force mute streams in the session or disable the active mute state in a session (`true`) or not (`false`). @@ -179,8 +179,10 @@ feature. See the [OpenTok IP Proxy](https://tokbox.com/developer/guides/ip-proxy * `canSubscribe` (Boolean) -- Whether the client can subscribe to streams in the session (`true`) or not (`false`). - For more information, see the - [OpenTok token documentation](https://tokbox.com/developer/guides/create-token). +The promise is rejected if you have not connected to the session and the `connectionCreated` event has been dispatched. + +For more information, see the +[OpenTok token documentation](https://tokbox.com/developer/guides/create-token). **reportIssue()** Lets you report that your app experienced an issue (to view with [Inspector](http://tokbox.com/developer/tools/Inspector) or to discuss with the Vonage API diff --git a/src/OTSession.js b/src/OTSession.js index db922e07..c4aaa3a3 100644 --- a/src/OTSession.js +++ b/src/OTSession.js @@ -71,12 +71,6 @@ export default class OTSession extends Component { this.setState({ sessionInfo, }); - // TO DO: The capababilities are set after the sessionConnected event is dispatched. - const sessionCapabilities = OT.getSessionCapabilities(sessionId, (sessionCapabilities) => { - this.setState({ - sessionCapabilities, - }); - }); logOT({ apiKey, sessionId, action: 'rn_on_connect', proxyUrl: sessionOptions.proxyUrl, connectionId: session.connection.connectionId }); if (Object.keys(signal).length > 0) { this.signal(signal); @@ -100,7 +94,15 @@ export default class OTSession extends Component { return this.state.sessionInfo; } getCapabilities() { - return this.state.sessionCapabilities; + return new Promise((resolve, reject ) => { + OT.getSessionCapabilities(this.props.sessionId, (sessionCapabilities) => { + if (sessionCapabilities) { + resolve(sessionCapabilities); + } else { + reject(new Error('Not connected to session.')); + } + }); + }); } async reportIssue() { return new Promise((resolve, reject) => { From 5a4924291f4113f0fd585a6b680644fc46126863 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Thu, 20 Apr 2023 16:52:46 -0700 Subject: [PATCH 17/24] Add OT.getSupportedCodecs() method --- .../opentokreactnative/OTSessionManager.java | 10 ++++++++ .../opentokreactnative/utils/EventUtils.java | 24 +++++++++++++++++++ docs/OT.md | 13 ++++++++++ ios/OpenTokReactNative/OTSessionManager.m | 3 +++ ios/OpenTokReactNative/OTSessionManager.swift | 10 ++++++++ 5 files changed, 60 insertions(+) diff --git a/android/src/main/java/com/opentokreactnative/OTSessionManager.java b/android/src/main/java/com/opentokreactnative/OTSessionManager.java index 551c347f..8b4bfa82 100644 --- a/android/src/main/java/com/opentokreactnative/OTSessionManager.java +++ b/android/src/main/java/com/opentokreactnative/OTSessionManager.java @@ -22,9 +22,11 @@ import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.Promise; import com.opentok.android.Session; import com.opentok.android.Connection; +import com.opentok.android.MediaUtils; import com.opentok.android.Publisher; import com.opentok.android.PublisherKit; import com.opentok.android.Stream; @@ -398,6 +400,14 @@ public void getSubscriberRtcStatsReport(String streamId) { } } + @ReactMethod + public void getSupportedCodecs(Promise promise) { + + MediaUtils.SupportedCodecs mSupportedCodecs = MediaUtils.getSupportedCodecs(this.getReactApplicationContext()); + WritableMap supportedCodecsMap = EventUtils.prepareMediaCodecsMap(mSupportedCodecs); + promise.resolve(supportedCodecsMap); + } + @ReactMethod public void changeCameraPosition(String publisherId, String cameraPosition) { diff --git a/android/src/main/java/com/opentokreactnative/utils/EventUtils.java b/android/src/main/java/com/opentokreactnative/utils/EventUtils.java index 94fb9c0f..f54fe1cb 100644 --- a/android/src/main/java/com/opentokreactnative/utils/EventUtils.java +++ b/android/src/main/java/com/opentokreactnative/utils/EventUtils.java @@ -4,6 +4,7 @@ import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.opentok.android.Connection; +import com.opentok.android.MediaUtils; import com.opentok.android.OpentokError; import com.opentok.android.Session; import com.opentok.android.Stream; @@ -156,6 +157,29 @@ public static WritableArray preparePublisherVideoStats(PublisherKit.PublisherVid return statsArrayMap; } + public static WritableMap prepareMediaCodecsMap(MediaUtils.SupportedCodecs supportedCodecs) { + WritableMap codecsMap = Arguments.createMap(); + WritableArray videoDecoderCodecsArray = Arguments.createArray(); + WritableArray videoEncoderCodecsArray = Arguments.createArray(); + for (MediaUtils.VideoCodecType decoderCodec : supportedCodecs.videoDecoderCodecs ) { + if (decoderCodec.equals(MediaUtils.VideoCodecType.VIDEO_CODEC_H264)) { + videoDecoderCodecsArray.pushString("H.264"); + } else { + videoDecoderCodecsArray.pushString("VP8"); + } + } + for (MediaUtils.VideoCodecType encoderCodec : supportedCodecs.videoEncoderCodecs ) { + if (encoderCodec.equals(MediaUtils.VideoCodecType.VIDEO_CODEC_H264)) { + videoEncoderCodecsArray.pushString("H.264"); + } else { + videoEncoderCodecsArray.pushString("VP8"); + } + } + codecsMap.putArray("videoDecoderCodecs", videoDecoderCodecsArray); + codecsMap.putArray("videoEncoderCodecs", videoEncoderCodecsArray); + return codecsMap; + } + public static WritableMap createError(String message) { WritableMap errorInfo = Arguments.createMap(); diff --git a/docs/OT.md b/docs/OT.md index 7c81dc07..7851c04a 100644 --- a/docs/OT.md +++ b/docs/OT.md @@ -18,3 +18,16 @@ By default, the native logs are disabled. Please using the following method to e ```javascript OT.enableLogs(false); ``` + +## To get supported codecs for the client device + +```javascript + const supportedCodecs = await OT.getSupportedCodecs(); + console.log(supportedCodecs); +``` + +The `OT.getSupportedCodecs()` method returns a promise that resolves with an object defining the supported codecs on the device. This object includes two properties: + +* `videoDecoderCodecs` -- An array of values, defining the video codecs for decoding that are supported on the device. Supported values are "VP8" and "H.264". + +* `videoEncoderCodecs` -- An array of values, defining the video codecs for encoding that are supported on the device.. Supported values are "VP8" and "H.264". diff --git a/ios/OpenTokReactNative/OTSessionManager.m b/ios/OpenTokReactNative/OTSessionManager.m index 704c7346..e3a94b7e 100644 --- a/ios/OpenTokReactNative/OTSessionManager.m +++ b/ios/OpenTokReactNative/OTSessionManager.m @@ -96,6 +96,9 @@ @interface RCT_EXTERN_MODULE(OTSessionManager, RCTEventEmitter) RCT_EXTERN_METHOD(reportIssue: (NSString*)sessionId callback:(RCTResponseSenderBlock*)callback) +RCT_EXTERN_METHOD(getSupportedCodecs: + (RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(enableLogs: (BOOL)logLevel) @end diff --git a/ios/OpenTokReactNative/OTSessionManager.swift b/ios/OpenTokReactNative/OTSessionManager.swift index 1c2d711b..b2857313 100644 --- a/ios/OpenTokReactNative/OTSessionManager.swift +++ b/ios/OpenTokReactNative/OTSessionManager.swift @@ -358,6 +358,16 @@ class OTSessionManager: RCTEventEmitter { callback([issueId! as NSString]) } + // The OpenTok iOS SDK does not implement a getVideoCodecs method, because iOS + // supported all supported codecs. But we will implement it here so that the + // OT.getVideoCodecs() method can be called cross-platform. + @objc func getSupportedCodecs(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void{ + var supportedCodecs: Dictionary = [:]; + supportedCodecs["videoDecoderCodecs"] = ["H.264", "VP8"]; + supportedCodecs["videoEncoderCodecs"] = ["H.264", "VP8"]; + resolve(supportedCodecs) + } + @objc func enableLogs(_ logLevel: Bool) -> Void { self.logLevel = logLevel; } From 20652ce3e75c0ddf38fa6dbe2ab2456f303daa80 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Thu, 20 Apr 2023 16:58:27 -0700 Subject: [PATCH 18/24] Add OT.getSupportedCodecs() docs --- CHANGELOG.md | 1 + docs/OT.md | 2 ++ docs/index.md | 3 --- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01697750..4c64d569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * `OTPublisher.getRtcStatsReport()` method and OTPublisher `rtcStatsReport` event * "1920x1080" option for OTPublisher `resolution` (for FHD video support) * OTSubscriber `audioVolume` property. + * OT.getSupportedCodecs() method. For more info, see the docs: diff --git a/docs/OT.md b/docs/OT.md index 7851c04a..41b3c75f 100644 --- a/docs/OT.md +++ b/docs/OT.md @@ -31,3 +31,5 @@ The `OT.getSupportedCodecs()` method returns a promise that resolves with an obj * `videoDecoderCodecs` -- An array of values, defining the video codecs for decoding that are supported on the device. Supported values are "VP8" and "H.264". * `videoEncoderCodecs` -- An array of values, defining the video codecs for encoding that are supported on the device.. Supported values are "VP8" and "H.264". + +See the OpenTok [video codecs](https://tokbox.com/developer/guides/codecs/) documentattion. diff --git a/docs/index.md b/docs/index.md index 35271398..1fb03e96 100644 --- a/docs/index.md +++ b/docs/index.md @@ -53,9 +53,6 @@ video renderer that renders streams and provides user interface controls for dis the stream name and muting the microphone or camera. Publishers and subscribers use `mPublisher.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL)` -* **Codec detection support** -- There is no way to detect -[video codecs](https://tokbox.com/developer/guides/codecs/) supported on a device. - * **Force mute** -- Clients using OpenTok React Native can be forced to mute (by clients using the OpenTok REST API, server SDKs, or client SDKs). However, there is no support for events and methods related to the From f2b58384611ce7be23fa0241d020d6e672623a07 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Mon, 24 Apr 2023 16:46:55 -0700 Subject: [PATCH 19/24] Add Force Mute API enhancements --- @types/index.d.ts | 15 ++++ CHANGELOG.md | 2 + .../opentokreactnative/OTSessionManager.java | 79 +++++++++++++++++++ docs/EventData.md | 8 ++ docs/OTPublisher.md | 10 ++- docs/OTSession.md | 22 ++++++ docs/index.md | 5 -- ios/OpenTokReactNative/OTSessionManager.m | 14 ++++ ios/OpenTokReactNative/OTSessionManager.swift | 55 +++++++++++++ src/OTSession.js | 9 +++ src/helpers/OTPublisherHelper.js | 2 + src/helpers/OTSessionHelper.js | 6 +- 12 files changed, 217 insertions(+), 10 deletions(-) diff --git a/@types/index.d.ts b/@types/index.d.ts index d94ffa9a..ba469d8e 100644 --- a/@types/index.d.ts +++ b/@types/index.d.ts @@ -138,6 +138,21 @@ declare module "opentok-react-native" { canSubscribe: boolean; }> + /** + * Mutes all streams in the session. + */ + forceMuteAll(excludedStreamIds: string[]): Promise<> + + /** + * Mutes a stream in the session. + */ + forceMuteStream(streamId: string): Promise<> + + /** + * Disables the force mute state for the session. + */ + disableForceMute(): Promise<> + /** * Used to report an issue */ diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c64d569..c9f74b71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ * "1920x1080" option for OTPublisher `resolution` (for FHD video support) * OTSubscriber `audioVolume` property. * OT.getSupportedCodecs() method. + * OT.forceMuteAll(), OT.forceMuteStream(), OT.disableForceMute() methods. OTPublisher + `muteForce` event and OTSession `muteForced` event. For more info, see the docs: diff --git a/android/src/main/java/com/opentokreactnative/OTSessionManager.java b/android/src/main/java/com/opentokreactnative/OTSessionManager.java index 8b4bfa82..7493fb90 100644 --- a/android/src/main/java/com/opentokreactnative/OTSessionManager.java +++ b/android/src/main/java/com/opentokreactnative/OTSessionManager.java @@ -27,6 +27,7 @@ import com.opentok.android.Session; import com.opentok.android.Connection; import com.opentok.android.MediaUtils; +import com.opentok.android.MuteForcedInfo; import com.opentok.android.Publisher; import com.opentok.android.PublisherKit; import com.opentok.android.Stream; @@ -51,12 +52,14 @@ public class OTSessionManager extends ReactContextBaseJavaModule PublisherKit.AudioLevelListener, PublisherKit.PublisherRtcStatsReportListener, PublisherKit.AudioStatsListener, + PublisherKit.MuteListener, PublisherKit.VideoStatsListener, SubscriberKit.SubscriberListener, Session.SignalListener, Session.ConnectionListener, Session.ReconnectionListener, Session.ArchiveListener, + Session.MuteListener, Session.StreamPropertiesListener, SubscriberKit.AudioLevelListener, SubscriberKit.SubscriberRtcStatsReportListener, @@ -122,6 +125,7 @@ public boolean useTextureViews() { mSession.setReconnectionListener(this); mSession.setArchiveListener(this); mSession.setStreamPropertiesListener(this); + mSession.setMuteListener(this); mSessions.put(sessionId, mSession); mAndroidOnTopMap.put(sessionId, androidOnTop); mAndroidZOrderMap.put(sessionId, androidZOrder); @@ -197,6 +201,7 @@ public void initPublisher(String publisherId, ReadableMap properties, Callback c mPublisher.setPublishAudio(publishAudio); mPublisher.setAudioStatsListener(this); mPublisher.setVideoStatsListener(this); + mPublisher.setMuteListener(this); ConcurrentHashMap mPublishers = sharedState.getPublishers(); mPublishers.put(publisherId, mPublisher); callback.invoke(); @@ -302,6 +307,59 @@ public void disconnectSession(String sessionId, Callback callback) { } } + @ReactMethod + public void forceMuteAll(String sessionId, ReadableArray excluededStreamIds, Promise promise) { + ConcurrentHashMap mSessions = sharedState.getSessions(); + Session mSession = mSessions.get(sessionId); + ConcurrentHashMap streams = sharedState.getSubscriberStreams(); + ArrayList mExcluededStreams = new ArrayList(); + if (mSession == null) { + promise.reject("Session not found."); + return; + } + for (int i = 0; i < excluededStreamIds.size(); i++) { + String streamId = excluededStreamIds.getString(i); + Stream mStream = streams.get(streamId); + if (mStream == null) { + promise.reject("Stream not found."); + return; + } + mExcluededStreams.add(mStream); + } + mSession.forceMuteAll(mExcluededStreams); + promise.resolve(null); + } + + @ReactMethod + public void forceMuteStream(String sessionId, String streamId, Promise promise) { + ConcurrentHashMap mSessions = sharedState.getSessions(); + Session mSession = mSessions.get(sessionId); + ConcurrentHashMap streams = sharedState.getSubscriberStreams(); + if (mSession == null) { + promise.reject("Session not found."); + return; + } + Stream mStream = streams.get(streamId); + if (mStream == null) { + promise.reject("Stream not found."); + return; + } + mSession.forceMuteStream(mStream); + promise.resolve(null); + } + + @ReactMethod + public void disableForceMute(String sessionId, Promise promise) { + ConcurrentHashMap mSessions = sharedState.getSessions(); + Session mSession = mSessions.get(sessionId); + if (mSession == null) { + promise.reject("Session not found."); + return; + } + mSession.disableForceMute(); + promise.resolve(true); + } + @ReactMethod public void publishAudio(String publisherId, Boolean publishAudio) { @@ -748,6 +806,17 @@ public void onStreamDropped(Session session, Stream stream) { sendEventMap(this.getReactApplicationContext(), session.getSessionId() + ":" + sessionPreface + "onStreamDropped", streamInfo); printLogs("onStreamDropped: Stream Dropped: "+stream.getStreamId() +" in session: "+session.getSessionId()); } + @Override + public void onMuteForced​(Session session, MuteForcedInfo info) { + + WritableMap muteForcedInfo = Arguments.createMap(); + String sessionId = session.getSessionId(); + muteForcedInfo.putString("sessionId", sessionId); + Boolean active = info.getActive(); + muteForcedInfo.putBoolean("active", active); + sendEventMap(this.getReactApplicationContext(), session.getSessionId() + ":" + sessionPreface + "onMuteForced​", muteForcedInfo); + printLogs("Mute forced -- active: " + active + " in session: " + sessionId); + } @Override public void onStreamCreated(PublisherKit publisherKit, Stream stream) { @@ -840,6 +909,16 @@ public void onVideoStats(PublisherKit publisher, PublisherKit.PublisherVideoStat } } + @Override + public void onMuteForced​(PublisherKit publisher) { + + String publisherId = Utils.getPublisherId(publisher); + if (publisherId.length() > 0) { + String event = publisherId + ":" + publisherPreface + "onMuteForced"; + sendEventMap(this.getReactApplicationContext(), event, null); + } + } + @Override public void onConnected(SubscriberKit subscriberKit) { diff --git a/docs/EventData.md b/docs/EventData.md index f330d512..b877291c 100644 --- a/docs/EventData.md +++ b/docs/EventData.md @@ -120,6 +120,14 @@ You can find the structure of the object below: }; ``` +## MuteForcedEvent + +```javascript +event = { + active: boolean; +} +``` + ## PublisherVideoNetworkStatsEvent To get video data for a publisher, register an event listener for the OTPublisher diff --git a/docs/OTPublisher.md b/docs/OTPublisher.md index 0fa454fa..048bee1d 100644 --- a/docs/OTPublisher.md +++ b/docs/OTPublisher.md @@ -126,12 +126,13 @@ to "screen". * **audioLevel** (Number) -- The audio level, from 0 to 1.0. Adjust this value logarithmically for use in adjusting a user interface element, such as a volume meter. Use a moving average to smooth the data. -**audioNetworkStats** (Object) -- - -**videoNetworkStats** (Object) -- +* **audioNetworkStats** (Object) — Sent periodically to report audio statistics for the publisher. + A [PublisherAudioNetworkStatsEvent](./EventData.md#PublisherAudioNetworkStatsEvent) object is passed into the event handler. * **error** (Object) -- Sent if the publisher encounters an error. After this message is sent, the publisher can be considered fully detached from a session and may be released. +* **muteForced** -- Sent when a moderator has forced this client to mute audio. + * **otrnError** (Object) -- Sent if there is an error with the communication between the native publisher instance and the JS component. * **rtcStatsReport** (Object) -- Sent when RTC stats reports are available for the publisher, @@ -157,3 +158,6 @@ A [streamingEvent](./EventData.md#streamingEvent) object is passed into the even * **streamDestroyed** (Object) -- Sent when the publisher stops streaming. A [streamingEvent](./EventData.md#streamingEvent) object is passed into the event handler. + +**videoNetworkStats** (Object) -- Sent periodically to report audio statistics for the publisher. + A [PublisherVideoNetworkStatsEvent](./EventData.md#PublisherVideoNetworkStatsEvent) object is passed into the event handler. diff --git a/docs/OTSession.md b/docs/OTSession.md index c330ee97..cd91c96c 100644 --- a/docs/OTSession.md +++ b/docs/OTSession.md @@ -157,6 +157,12 @@ feature. See the [OpenTok IP Proxy](https://tokbox.com/developer/guides/ip-proxy ## Methods +**disableForceMute()** Disables the active mute state of the session. After you call this method, new streams published to the session will no longer have audio muted. + +After you call to the Session.forceMuteAll() method (or a moderator in another client makes a call to mute all streams), any streams published after the moderation call are published with audio muted. Call the `OTSession.disableForceMute()` method to remove the mute state of a session (so that new published streams are not automatically muted). + +Check the `capabilities.canForceMute` property of the object returned by `OTSession.getCapbabilities()` to see if you can call this function successfully. This is reserved for clients that have connected with a token that has been assigned the moderator role (see the [Token Creation documentation](https://tokbox.com/developer/guides/create-token/)). + **getSessionInfo()** Returns an object with the following properties: * `sessionId` (String) -- The session ID. @@ -188,6 +194,20 @@ For more information, see the [Inspector](http://tokbox.com/developer/tools/Inspector) or to discuss with the Vonage API support team.) The method returns a Promise that resolves with a string, the issue ID. +**forceMuteAll()** Forces all publishers in the session (except for those publishing excluded streams) to mute audio. + +This method has one optional parameter -- `excludedStreams`, and array of stream IDs. A stream published by the moderator calling the forceMuteAll() method is muted along with other streams in the session, unless you add the moderator's stream (or streams) to the `excludedStreams` array. If you leave out the `excludedStreams` parameter, all streams in the session (including those of the moderator) will stop publishing audio. Also, any streams that are published after the call to the `forceMuteAll()` method are published with audio muted. You can remove the mute state of a session by calling the `OTSession.disableForceMute()` method. + +After you call the Session.disableForceMute() method, new streams published to the session will no longer have audio muted. +Calling this method causes the Publisher objects in the clients publishing the streams to dispatch muteForced events. Also, the Session object in each client connected to the session dispatches the muteForced event (with the active property of the event object set to true). + +Check the `capabilities.canForceMute` property of the object returned by `OTSession.getCapbabilities()` to see if you can call this function successfully. This is reserved for clients that have connected with a token that has been assigned the moderator role (see the [Token Creation documentation](https://tokbox.com/developer/guides/create-token/)). + +**forceMuteStream()** Forces a the publisher of a specified stream to mute its audio. Pass the stream ID +of the stream in as a parameter. + +Check the `capabilities.canForceMute` property of the object returned by `OTSession.getCapbabilities()` to see if you can call this function successfully. This is reserved for clients that have connected with a token that has been assigned the moderator role (see the [Token Creation documentation](https://tokbox.com/developer/guides/create-token/)). + **signal()** Sends a signal to clients connected to the session. The method has one parameter, an object that includes the following properties, each of which is optional (although you usually want to set the `data` property): @@ -219,6 +239,8 @@ A [ConnectionDestroyedEvent](./EventData.md#ConnectionDestroyedEvent) object is **error** (Object) — Sent if the attempt to connect to the session fails or if the connection to the session drops due to an error after a successful connection. An [ErrorEvent](./EventData.md#ErrorEvent) object is passed into the event handler. +* **muteForced** -- Sent when a moderator has forced clients publishing streams to the session to mute audio (the `active` property of this `MuteForcedEvent` object is set to `true`), or a moderator has disabled the mute audio state in the session (the active property of this `MuteForcedEvent` object is set to `false`). An [ErrorEvent](./EventData.md#MuteForcedEvent) object is passed into the event handler. + **otrnError** -- Sent if there is an error with the communication between the native session instance and the JS component. **sessionConnected** -- Sent when the client connects to the session. diff --git a/docs/index.md b/docs/index.md index 1fb03e96..9ae3fbec 100644 --- a/docs/index.md +++ b/docs/index.md @@ -53,11 +53,6 @@ video renderer that renders streams and provides user interface controls for dis the stream name and muting the microphone or camera. Publishers and subscribers use `mPublisher.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL)` -* **Force mute** -- Clients using OpenTok React Native can be forced to mute -(by clients using the OpenTok REST API, server SDKs, or client SDKs). However, there is -no support for events and methods related to the -[force mute feature](https://tokbox.com/developer/guides/moderation/#force_mute). - * **iOS delegate callback queue** -- For iOS, you cannot assign the delegate callback queue (the GCD queue). See the docs for the [OTSession.apiQueue property](https://tokbox.com/developer/sdks/ios/reference/Classes/OTSession.html#//api/name/apiQueue) diff --git a/ios/OpenTokReactNative/OTSessionManager.m b/ios/OpenTokReactNative/OTSessionManager.m index e3a94b7e..07091c9a 100644 --- a/ios/OpenTokReactNative/OTSessionManager.m +++ b/ios/OpenTokReactNative/OTSessionManager.m @@ -99,6 +99,20 @@ @interface RCT_EXTERN_MODULE(OTSessionManager, RCTEventEmitter) RCT_EXTERN_METHOD(getSupportedCodecs: (RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(forceMuteAll: + (NSString*)sessionId + excludedStreamIds:(NSArray*)excludedStreamIds + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(forceMuteStream: + (NSString*)sessionId + streamId:(NSString*)streamId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +RCT_EXTERN_METHOD(disableForceMute: + (NSString*)sessionId + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(enableLogs: (BOOL)logLevel) @end diff --git a/ios/OpenTokReactNative/OTSessionManager.swift b/ios/OpenTokReactNative/OTSessionManager.swift index b2857313..15803005 100644 --- a/ios/OpenTokReactNative/OTSessionManager.swift +++ b/ios/OpenTokReactNative/OTSessionManager.swift @@ -368,6 +368,46 @@ class OTSessionManager: RCTEventEmitter { resolve(supportedCodecs) } + @objc func forceMuteAll(_ sessionId: String, excludedStreamIds: NSArray, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void{ + guard let session = OTRN.sharedState.sessions[sessionId] else { + reject("event_failure", "Session ID not found", nil) + return + } + return resolve(true); + } + + @objc func forceMuteStream(_ sessionId: String, streamId: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void{ + guard let session = OTRN.sharedState.sessions[sessionId] else { + reject("event_failure", "Session ID not found", nil); + return + } + guard let stream = OTRN.sharedState.subscriberStreams[streamId] else { + reject("event_failure", "Stream ID not found", nil); + return + } + var error: OTError? + session.forceMuteStream(stream, error: &error) + if let error = error { + reject("event_failure", error.localizedDescription, nil); + return; + } + resolve(true); + } + + @objc func disableForceMute(_ sessionId: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void{ + guard let session = OTRN.sharedState.sessions[sessionId] else { + reject("event_failure", "Session not found.", nil); + return + } + var error: OTError? + session.disableForceMute(&error) + if let error = error { + reject("event_failure", error.localizedDescription, nil); + return; + } + resolve(true); + } + @objc func enableLogs(_ logLevel: Bool) -> Void { self.logLevel = logLevel; } @@ -526,6 +566,13 @@ extension OTSessionManager: OTSessionDelegate { self.emitEvent("\(session.sessionId):\(EventUtils.sessionPreface)signal", data: signalData) printLogs("OTRN: Session signal received") } + + func session(_ session: OTSession, info muteForced: OTMuteForcedInfo) { + var muteForcedInfo: Dictionary = [:]; + muteForcedInfo["active"] = muteForced.active; + self.emitEvent("\(session.sessionId):\(EventUtils.sessionPreface)muteFoced", data: muteForcedInfo) + printLogs("OTRN Session: Session muteForced - active: \(muteForced.active)") + } } extension OTSessionManager: OTPublisherDelegate { @@ -571,6 +618,14 @@ extension OTSessionManager: OTPublisherDelegate { } printLogs("OTRN: Publisher failed: \(error.localizedDescription)") } + + func muteForced(_ publisher: OTPublisherKit) { + let publisherId = Utils.getPublisherId(publisher as! OTPublisher); + if (publisherId.count > 0) { + self.emitEvent("\(publisherId):\(EventUtils.publisherPreface)muteForced", data: [NSNull()]); + } + printLogs("OTRN: Publisher mute forced") + } } extension OTSessionManager: OTPublisherKitAudioLevelDelegate { diff --git a/src/OTSession.js b/src/OTSession.js index c4aaa3a3..d981605d 100644 --- a/src/OTSession.js +++ b/src/OTSession.js @@ -119,6 +119,15 @@ export default class OTSession extends Component { const signalData = sanitizeSignalData(signal); OT.sendSignal(this.props.sessionId, signalData.signal, signalData.errorHandler); } + forceMuteAll(excludedStreamIds) { + return OT.forceMuteAll(this.props.sessionId, excludedStreamIds || []); + } + forceMuteStream(streamId) { + return OT.forceMuteStream(this.props.sessionId, streamId); + } + disableForceMute() { + return OT.disableForceMute(this.props.sessionId); + } render() { const { style, children, sessionId, apiKey, token } = this.props; const { sessionInfo } = this.state; diff --git a/src/helpers/OTPublisherHelper.js b/src/helpers/OTPublisherHelper.js index 2db91ab4..fa56c13f 100644 --- a/src/helpers/OTPublisherHelper.js +++ b/src/helpers/OTPublisherHelper.js @@ -98,6 +98,7 @@ const sanitizePublisherEvents = (publisherId, events) => { audioNetworkStats: 'audioStats', rtcStatsReport: 'rtcStatsReport', videoNetworkStats: 'videoStats', + muteForced: 'muteForced', }, android: { streamCreated: 'onStreamCreated', @@ -107,6 +108,7 @@ const sanitizePublisherEvents = (publisherId, events) => { audioNetworkStats: 'onAudioStats', rtcStatsReport: 'onRtcStatsReport', videoNetworkStats: 'onVideoStats', + muteForced: 'onMuteForced', }, }; return reassignEvents('publisher', customEvents, events, publisherId); diff --git a/src/helpers/OTSessionHelper.js b/src/helpers/OTSessionHelper.js index c14bba9b..6d38763a 100644 --- a/src/helpers/OTSessionHelper.js +++ b/src/helpers/OTSessionHelper.js @@ -37,7 +37,8 @@ const sanitizeSessionEvents = (sessionId, events) => { sessionReconnecting: 'sessionDidBeginReconnecting', archiveStarted: 'archiveStartedWithId', archiveStopped: 'archiveStoppedWithId', - streamPropertyChanged: 'streamPropertyChanged' + streamPropertyChanged: 'streamPropertyChanged', + muteForced: 'muteForced', }, android: { streamCreated: 'onStreamReceived', @@ -52,7 +53,8 @@ const sanitizeSessionEvents = (sessionId, events) => { sessionReconnecting: 'onReconnecting', archiveStarted: 'onArchiveStarted', archiveStopped: 'onArchiveStopped', - streamPropertyChanged: 'onStreamPropertyChanged' + streamPropertyChanged: 'onStreamPropertyChanged', + muteForced: 'onMuteForced', } }; return reassignEvents('session', customEvents, events, sessionId); From 5ef6c081af53c31e809480df423678f2926f2426 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Thu, 24 Aug 2023 09:39:18 -0700 Subject: [PATCH 20/24] Typo corrections --- CHANGELOG.md | 2 +- .../main/java/com/opentokreactnative/OTSessionManager.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44d0d4e2..4be5fb7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # 0.26.0 (August 2023) -- [Update]: Add API to implement functionality missing from the OpenTok Andriod and iOS SDKs: +- [Update]: Add API to implement functionality missing from the OpenTok Android and iOS SDKs: * `OTSession.getCapabilities()` method * `reportIssue()` methods and `rtcStatsReport` events added to OTPublisher and OTSubscriber diff --git a/android/src/main/java/com/opentokreactnative/OTSessionManager.java b/android/src/main/java/com/opentokreactnative/OTSessionManager.java index cd75f422..dfb4c9ed 100644 --- a/android/src/main/java/com/opentokreactnative/OTSessionManager.java +++ b/android/src/main/java/com/opentokreactnative/OTSessionManager.java @@ -312,7 +312,7 @@ public void disconnectSession(String sessionId, Callback callback) { } @ReactMethod - public void forceMuteAll(String sessionId, ReadableArray excluededStreamIds, Promise promise) { + public void forceMuteAll(String sessionId, ReadableArray excludedStreamIds, Promise promise) { ConcurrentHashMap mSessions = sharedState.getSessions(); Session mSession = mSessions.get(sessionId); ConcurrentHashMap streams = sharedState.getSubscriberStreams(); @@ -321,8 +321,8 @@ public void forceMuteAll(String sessionId, ReadableArray excluededStreamIds, Pro promise.reject("Session not found."); return; } - for (int i = 0; i < excluededStreamIds.size(); i++) { - String streamId = excluededStreamIds.getString(i); + for (int i = 0; i < excludedStreamIds.size(); i++) { + String streamId = excludedStreamIds.getString(i); Stream mStream = streams.get(streamId); if (mStream == null) { promise.reject("Stream not found."); From e85cf3aa7b10a55631f0fadd145544ca86f7dd71 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Thu, 24 Aug 2023 09:48:28 -0700 Subject: [PATCH 21/24] Rev version to 2.25.3 --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4be5fb7e..0575579f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.26.0 (August 2023) +# 2.25.3 (August 2023) - [Update]: Add API to implement functionality missing from the OpenTok Android and iOS SDKs: diff --git a/package-lock.json b/package-lock.json index 9e0a3226..73ebaeb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "opentok-react-native", - "version": "0.26.0", + "version": "2.25.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "opentok-react-native", - "version": "0.26.0", + "version": "2.25.3", "license": "MIT", "dependencies": { "axios": "^0.21.1", diff --git a/package.json b/package.json index 9bc4087d..2f4fbaeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opentok-react-native", - "version": "2.26.0", + "version": "2.25.3", "description": "React Native components for OpenTok iOS and Android SDKs", "main": "src/index.js", "homepage": "https://www.tokbox.com", From 48d6e2970c7772948591604c1ed23623281ce449 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 21 Sep 2023 14:46:17 +0800 Subject: [PATCH 22/24] Fix android app crash due to permission missing --- src/OT.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OT.js b/src/OT.js index 398d4a5b..9fd74e90 100644 --- a/src/OT.js +++ b/src/OT.js @@ -12,7 +12,8 @@ const checkAndroidPermissions = () => new Promise((resolve, reject) => { const permissionsError = {}; permissionsError.permissionsDenied = []; each(result, (permissionValue, permissionType) => { - if (permissionValue === 'denied') { + // Check if the permission is denied or set to 'never_ask_again'. + if (permissionValue === 'denied' || permissionValue === 'never_ask_again' ) { permissionsError.permissionsDenied.push(permissionType); permissionsError.type = 'Permissions error'; } From b170fb84a6cbdd9fb8015c329100d918e857744d Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Tue, 26 Sep 2023 09:46:10 -0700 Subject: [PATCH 23/24] Typo corrections --- .../main/java/com/opentokreactnative/OTSessionManager.java | 6 +++--- docs/EventData.md | 4 ++-- docs/OTSession.md | 2 +- ios/OpenTokReactNative/Utils/EventUtils.swift | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/opentokreactnative/OTSessionManager.java b/android/src/main/java/com/opentokreactnative/OTSessionManager.java index dfb4c9ed..b2303edd 100644 --- a/android/src/main/java/com/opentokreactnative/OTSessionManager.java +++ b/android/src/main/java/com/opentokreactnative/OTSessionManager.java @@ -316,7 +316,7 @@ public void forceMuteAll(String sessionId, ReadableArray excludedStreamIds, Prom ConcurrentHashMap mSessions = sharedState.getSessions(); Session mSession = mSessions.get(sessionId); ConcurrentHashMap streams = sharedState.getSubscriberStreams(); - ArrayList mExcluededStreams = new ArrayList(); + ArrayList mExcludedStreams = new ArrayList(); if (mSession == null) { promise.reject("Session not found."); return; @@ -328,9 +328,9 @@ public void forceMuteAll(String sessionId, ReadableArray excludedStreamIds, Prom promise.reject("Stream not found."); return; } - mExcluededStreams.add(mStream); + mExcludedStreams.add(mStream); } - mSession.forceMuteAll(mExcluededStreams); + mSession.forceMuteAll(mExcludedStreams); promise.resolve(null); } diff --git a/docs/EventData.md b/docs/EventData.md index b877291c..e82048c6 100644 --- a/docs/EventData.md +++ b/docs/EventData.md @@ -161,7 +161,7 @@ To get audio data for a publisher, register an event listener for the OTPublishe subscriberId: string, audioPacketsLost: number, audioPacketsSent: number, - audioPacketsSent: number, + audioBytesSent: number, timestamp: number, } ]; @@ -354,7 +354,7 @@ You can find the structure of the object below: ```javascript event = [ - connectionId: string + connectionId: string, jsonArrayOfReports: string ]; ``` diff --git a/docs/OTSession.md b/docs/OTSession.md index fb0c5d9b..e2172ce0 100644 --- a/docs/OTSession.md +++ b/docs/OTSession.md @@ -199,7 +199,7 @@ support team.) The method returns a Promise that resolves with a string, the iss This method has one optional parameter -- `excludedStreams`, and array of stream IDs. A stream published by the moderator calling the forceMuteAll() method is muted along with other streams in the session, unless you add the moderator's stream (or streams) to the `excludedStreams` array. If you leave out the `excludedStreams` parameter, all streams in the session (including those of the moderator) will stop publishing audio. Also, any streams that are published after the call to the `forceMuteAll()` method are published with audio muted. You can remove the mute state of a session by calling the `OTSession.disableForceMute()` method. -After you call the Session.disableForceMute() method, new streams published to the session will no longer have audio muted. +After you call the `Session.disableForceMute()` method, new streams published to the session will no longer have audio muted. Calling this method causes the Publisher objects in the clients publishing the streams to dispatch muteForced events. Also, the Session object in each client connected to the session dispatches the muteForced event (with the active property of the event object set to true). Check the `capabilities.canForceMute` property of the object returned by `OTSession.getCapbabilities()` to see if you can call this function successfully. This is reserved for clients that have connected with a token that has been assigned the moderator role (see the [Token Creation documentation](https://tokbox.com/developer/guides/create-token/)). diff --git a/ios/OpenTokReactNative/Utils/EventUtils.swift b/ios/OpenTokReactNative/Utils/EventUtils.swift index b41eb838..ebc3bcda 100644 --- a/ios/OpenTokReactNative/Utils/EventUtils.swift +++ b/ios/OpenTokReactNative/Utils/EventUtils.swift @@ -90,7 +90,7 @@ class EventUtils { stats["subscriberId"] = value.subscriberId; stats["audioPacketsLost"] = value.audioPacketsLost; stats["audioPacketsSent"] = value.audioPacketsSent; - stats["audioPacketsSent"] = value.audioPacketsSent; + stats["audioBytesSent"] = value.audioBytesSent; stats["timestamp"] = value.timestamp; statsArray.append(stats); } From 372a5d477aa4246c2719da9d5a9405e02b0c7812 Mon Sep 17 00:00:00 2001 From: Jeff Swartz Date: Tue, 26 Sep 2023 09:53:11 -0700 Subject: [PATCH 24/24] Docs edit --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0575579f..b0e20e87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 2.25.3 (August 2023) +# 2.25.3 (September 2023) - [Update]: Add API to implement functionality missing from the OpenTok Android and iOS SDKs: @@ -19,6 +19,8 @@ For more info, see the docs: * [OTSession](/docs/OTSession.md) * [OTSubscriber](/docs/OTSubscriber.md) +- [Fix]: Fix android app crash due to permission missing. + # 2.25.2 (July 5 2023) - [Fix]: Fix crash on iOS when publishing a screen-sharing stream.