diff --git a/MediaStream.js b/MediaStream.js index 2116178a3..6f2d88096 100644 --- a/MediaStream.js +++ b/MediaStream.js @@ -2,7 +2,7 @@ import {NativeModules} from 'react-native'; import EventTarget from 'event-target-shim'; -import MediaStreamTrackEvent from './MediaStreamTrackEvent'; +import uuid from 'uuid'; import type MediaStreamTrack from './MediaStreamTrack'; @@ -34,31 +34,71 @@ export default class MediaStream extends EventTarget(MEDIA_STREAM_EVENTS) { * unambiguously differentiate it from a local MediaStream instance not added * to an RTCPeerConnection. */ - reactTag: string; - - constructor(id, reactTag) { - super(); - this.id = id; - // Local MediaStreams are created by WebRTCModule to have their id and - // reactTag equal because WebRTCModule follows the respective standard's - // recommendation for id generation i.e. uses UUID which is unique enough - // for the purposes of reactTag. - this.reactTag = (typeof reactTag === 'undefined') ? id : reactTag; + _reactTag: string; + + /** + * A MediaStream can be constructed in several ways, depending on the paramters + * that are passed here. + * + * - undefined: just a new stream, with no tracks. + * - MediaStream instance: a new stream, with a copy of the tracks of the passed stream. + * - Array of MediaStreamTrack: a new stream with a copy of the tracks in the array. + * - object: a new stream instance, represented by the passed info object, this is always + * done internally, when the stream is first created in native and the JS wrapper is + * built afterwards. + */ + constructor(arg) { + super(); + + // Assigm a UUID to start with. It may get overridden for remote streams. + this.id = uuid.v4(); + // Local MediaStreams are created by WebRTCModule to have their id and + // reactTag equal because WebRTCModule follows the respective standard's + // recommendation for id generation i.e. uses UUID which is unique enough + // for the purposes of reactTag. + this._reactTag = this.id; + + if (typeof arg === 'undefined') { + WebRTCModule.mediaStreamCreate(this.id); + } else if (arg instanceof MediaStream) { + WebRTCModule.mediaStreamCreate(this.id); + for (const track of arg.getTracks()) { + this.addTrack(track); + } + } else if (Array.isArray(arg)) { + WebRTCModule.mediaStreamCreate(this.id); + for (const track of arg) { + this.addTrack(track); + } + } else if (typeof arg === 'object' && arg.streamId && arg.streamReactTag && arg.tracks) { + this.id = arg.streamId; + this._reactTag = arg.streamReactTag; + for (const trackInfo of arg.tracks) { + // We are not using addTrack here because the track is already part of the + // stream, so there is no need to add it on the native side. + this._tracks.push(new MediaStreamTrack(trackInfo)); + } + } else { + throw new TypeError(`invalid type: ${typeof arg}`); + } } addTrack(track: MediaStreamTrack) { - this._tracks.push(track); - this.dispatchEvent(new MediaStreamTrackEvent('addtrack', {track})); + const index = this._tracks.indexOf(track); + if (index !== -1) { + return; + } + this._tracks.push(track); + WebRTCModule.mediaStreamAddTrack(this._reactTag, track.id); } removeTrack(track: MediaStreamTrack) { - let index = this._tracks.indexOf(track); - if (index === -1) { - return; - } - WebRTCModule.mediaStreamTrackRelease(this.reactTag, track.id); - this._tracks.splice(index, 1); - this.dispatchEvent(new MediaStreamTrackEvent('removetrack', {track})); + const index = this._tracks.indexOf(track); + if (index === -1) { + return; + } + this._tracks.splice(index, 1); + WebRTCModule.mediaStreamRemoveTrack(this._reactTag, track.id); } getTracks(): Array { @@ -82,10 +122,10 @@ export default class MediaStream extends EventTarget(MEDIA_STREAM_EVENTS) { } toURL() { - return this.reactTag; + return this._reactTag; } release() { - WebRTCModule.mediaStreamRelease(this.reactTag); + WebRTCModule.mediaStreamRelease(this._reactTag); } } diff --git a/MediaStreamTrack.js b/MediaStreamTrack.js index 851b8c29d..fc9125475 100644 --- a/MediaStreamTrack.js +++ b/MediaStreamTrack.js @@ -70,13 +70,9 @@ class MediaStreamTrack extends EventTarget(MEDIA_STREAM_TRACK_EVENTS) { } stop() { - if (this.remote) { - return; - } - WebRTCModule.mediaStreamTrackStop(this.id); - this._enabled = false; + WebRTCModule.mediaStreamTrackSetEnabled(this.id, false); this.readyState = 'ended'; - this.muted = !this._enabled; + // TODO: save some stopped flag? } /** @@ -115,6 +111,10 @@ class MediaStreamTrack extends EventTarget(MEDIA_STREAM_TRACK_EVENTS) { getSettings() { throw new Error('Not implemented.'); } + + release() { + WebRTCModule.mediaStreamTrackRelease(this.id); + } } export default MediaStreamTrack; diff --git a/RTCPeerConnection.js b/RTCPeerConnection.js index 32ba22217..ec1b42ca0 100644 --- a/RTCPeerConnection.js +++ b/RTCPeerConnection.js @@ -101,16 +101,21 @@ export default class RTCPeerConnection extends EventTarget(PEER_CONNECTION_EVENT } addStream(stream: MediaStream) { - WebRTCModule.peerConnectionAddStream(stream.reactTag, this._peerConnectionId); - this._localStreams.push(stream); + const index = this._localStreams.indexOf(stream); + if (index !== -1) { + return; + } + WebRTCModule.peerConnectionAddStream(stream._reactTag, this._peerConnectionId); + this._localStreams.push(stream); } removeStream(stream: MediaStream) { - WebRTCModule.peerConnectionRemoveStream(stream.reactTag, this._peerConnectionId); - let index = this._localStreams.indexOf(stream); - if (index !== -1) { + const index = this._localStreams.indexOf(stream); + if (index === -1) { + return; + } this._localStreams.splice(index, 1); - } + WebRTCModule.peerConnectionRemoveStream(stream._reactTag, this._peerConnectionId); } createOffer(options = DEFAULT_OFFER_OPTIONS) { @@ -240,7 +245,7 @@ export default class RTCPeerConnection extends EventTarget(PEER_CONNECTION_EVENT _getTrack(streamReactTag, trackId): MediaStreamTrack { const stream = this._remoteStreams.find( - stream => stream.reactTag === streamReactTag); + stream => stream._reactTag === streamReactTag); return stream && stream._tracks.find(track => track.id === trackId); } @@ -280,11 +285,7 @@ export default class RTCPeerConnection extends EventTarget(PEER_CONNECTION_EVENT if (ev.id !== this._peerConnectionId) { return; } - const stream = new MediaStream(ev.streamId, ev.streamReactTag); - const tracks = ev.tracks; - for (let i = 0; i < tracks.length; i++) { - stream.addTrack(new MediaStreamTrack(tracks[i])); - } + const stream = new MediaStream(ev); this._remoteStreams.push(stream); this.dispatchEvent(new MediaStreamEvent('addstream', {stream})); }), @@ -292,10 +293,10 @@ export default class RTCPeerConnection extends EventTarget(PEER_CONNECTION_EVENT if (ev.id !== this._peerConnectionId) { return; } - const stream = this._remoteStreams.find(s => s.reactTag === ev.streamId); + const stream = this._remoteStreams.find(s => s._reactTag === ev.streamId); if (stream) { const index = this._remoteStreams.indexOf(stream); - if (index > -1) { + if (index !== -1) { this._remoteStreams.splice(index, 1); } } diff --git a/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java b/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java index d00f4ea3c..01e77c8bc 100644 --- a/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java +++ b/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java @@ -217,30 +217,10 @@ void mediaStreamTrackSetEnabled(String trackId, final boolean enabled) { } } - void mediaStreamTrackStop(String id) { - MediaStreamTrack track = getTrack(id); - if (track == null) { - Log.d( - TAG, - "mediaStreamTrackStop() No local MediaStreamTrack with id " - + id); - return; - } - track.setEnabled(false); - removeTrack(id); - } - - private void removeTrack(String id) { + void disposeTrack(String id) { TrackPrivate track = tracks.remove(id); if (track != null) { - VideoCaptureController videoCaptureController - = track.videoCaptureController; - if (videoCaptureController != null) { - if (videoCaptureController.stopCapture()) { - videoCaptureController.dispose(); - } - } - track.mediaSource.dispose(); + track.dispose(); } } @@ -269,13 +249,18 @@ private static class TrackPrivate { */ public final VideoCaptureController videoCaptureController; + /** + * Whether this object has been disposed or not. + */ + private boolean disposed; + /** * Initializes a new {@code TrackPrivate} instance. * * @param track * @param mediaSource the {@code MediaSource} from which the specified * {@code code} was created - * @param videoCapturer the {@code VideoCapturer} from which the + * @param videoCaptureController the {@code VideoCaptureController} from which the * specified {@code mediaSource} was created if the specified * {@code track} is a {@link VideoTrack} */ @@ -286,6 +271,20 @@ public TrackPrivate( this.track = track; this.mediaSource = mediaSource; this.videoCaptureController = videoCaptureController; + this.disposed = false; + } + + public void dispose() { + if (!disposed) { + if (videoCaptureController != null) { + if (videoCaptureController.stopCapture()) { + videoCaptureController.dispose(); + } + } + mediaSource.dispose(); + track.dispose(); + disposed = true; + } } } } diff --git a/android/src/main/java/com/oney/WebRTCModule/PeerConnectionObserver.java b/android/src/main/java/com/oney/WebRTCModule/PeerConnectionObserver.java index 87770d469..7d9eb5807 100644 --- a/android/src/main/java/com/oney/WebRTCModule/PeerConnectionObserver.java +++ b/android/src/main/java/com/oney/WebRTCModule/PeerConnectionObserver.java @@ -343,8 +343,7 @@ public void onAddStream(MediaStream mediaStream) { // MediaStream instance with the label default that the implementation // reuses. if ("default".equals(streamId)) { - for (Map.Entry e - : remoteStreams.entrySet()) { + for (Map.Entry e : remoteStreams.entrySet()) { if (e.getValue().equals(mediaStream)) { streamReactTag = e.getKey(); break; diff --git a/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java b/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java index d8c5e2542..0364ecbe8 100644 --- a/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java +++ b/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java @@ -406,11 +406,8 @@ MediaStream getStreamForReactTag(String streamReactTag) { MediaStream stream = localStreams.get(streamReactTag); if (stream == null) { - for (int i = 0, size = mPeerConnectionObservers.size(); - i < size; - i++) { - PeerConnectionObserver pco - = mPeerConnectionObservers.valueAt(i); + for (int i = 0, size = mPeerConnectionObservers.size(); i < size; i++) { + PeerConnectionObserver pco = mPeerConnectionObservers.valueAt(i); stream = pco.remoteStreams.get(streamReactTag); if (stream != null) { break; @@ -425,11 +422,8 @@ private MediaStreamTrack getTrack(String trackId) { MediaStreamTrack track = getLocalTrack(trackId); if (track == null) { - for (int i = 0, size = mPeerConnectionObservers.size(); - i < size; - i++) { - PeerConnectionObserver pco - = mPeerConnectionObservers.valueAt(i); + for (int i = 0, size = mPeerConnectionObservers.size(); i < size; i++) { + PeerConnectionObserver pco = mPeerConnectionObservers.valueAt(i); track = pco.remoteTracks.get(trackId); if (track != null) { break; @@ -492,77 +486,57 @@ public void getUserMedia(ReadableMap constraints, } @ReactMethod - public void mediaStreamRelease(String id) { - ThreadUtils.runOnExecutor(() -> mediaStreamReleaseAsync(id)); + public void enumerateDevices(Callback callback) { + ThreadUtils.runOnExecutor(() -> + callback.invoke(getUserMediaImpl.enumerateDevices())); } - private void mediaStreamReleaseAsync(String id) { - MediaStream stream = localStreams.get(id); - if (stream == null) { - Log.d(TAG, "mediaStreamRelease() stream is null"); - } else { - // XXX Copy the lists of audio and video tracks because we'll be - // incrementally modifying them. Though a while loop with isEmpty() - // is generally a clearer approach (employed by MediaStream), we'll - // be searching through our own lists and these may (or may not) get - // out of sync with MediaStream's lists which raises the risk of - // entering infinite loops. - List tracks - = new ArrayList<>( - stream.audioTracks.size() + stream.videoTracks.size()); - - tracks.addAll(stream.audioTracks); - tracks.addAll(stream.videoTracks); - for (MediaStreamTrack track : tracks) { - mediaStreamTrackRelease(id, track.id()); - } - - localStreams.remove(id); - - // MediaStream.dispose() may be called without an exception only if - // it's no longer added to any PeerConnection. - for (int i = 0, size = mPeerConnectionObservers.size(); - i < size; - i++) { - mPeerConnectionObservers.valueAt(i).removeStream(stream); - } + @ReactMethod + public void mediaStreamCreate(String id) { + ThreadUtils.runOnExecutor(() -> mediaStreamCreateAsync(id)); + } - stream.dispose(); - } + private void mediaStreamCreateAsync(String id) { + MediaStream mediaStream = mFactory.createLocalMediaStream(id); + localStreams.put(id, mediaStream); } @ReactMethod - public void enumerateDevices(Callback callback) { + public void mediaStreamAddTrack(String streamId, String trackId) { ThreadUtils.runOnExecutor(() -> - callback.invoke(getUserMediaImpl.enumerateDevices())); + mediaStreamAddTrackAsync(streamId, trackId)); + } + + private void mediaStreamAddTrackAsync(String streamId, String trackId) { + MediaStream stream = localStreams.get(streamId); + MediaStreamTrack track = getLocalTrack(trackId); + + if (stream == null || track == null) { + Log.d(TAG, "mediaStreamAddTrack() stream || track is null"); + return; + } + + String kind = track.kind(); + if ("audio".equals(kind)) { + stream.addTrack((AudioTrack)track); + } else if ("video".equals(kind)) { + stream.addTrack((VideoTrack)track); + } } @ReactMethod - public void mediaStreamTrackRelease(String streamId, String trackId) { + public void mediaStreamRemoveTrack(String streamId, String trackId) { ThreadUtils.runOnExecutor(() -> - mediaStreamTrackReleaseAsync(streamId, trackId)); + mediaStreamRemoveTrackAsync(streamId, trackId)); } - private void mediaStreamTrackReleaseAsync(String streamId, String trackId) { + private void mediaStreamRemoveTrackAsync(String streamId, String trackId) { MediaStream stream = localStreams.get(streamId); - if (stream == null) { - Log.d(TAG, "mediaStreamTrackRelease() stream is null"); - return; - } MediaStreamTrack track = getLocalTrack(trackId); - if (track == null) { - // XXX The specified trackId may have already been stopped by - // mediaStreamTrackStop(). - track = getLocalTrack(stream, trackId); - if (track == null) { - Log.d( - TAG, - "mediaStreamTrackRelease() No local MediaStreamTrack with id " - + trackId); - return; - } - } else { - mediaStreamTrackStop(trackId); + + if (stream == null || track == null) { + Log.d(TAG, "mediaStreamRemoveTrack() stream || track is null"); + return; } String kind = track.kind(); @@ -571,7 +545,62 @@ private void mediaStreamTrackReleaseAsync(String streamId, String trackId) { } else if ("video".equals(kind)) { stream.removeTrack((VideoTrack)track); } - track.dispose(); + } + + @ReactMethod + public void mediaStreamRelease(String id) { + ThreadUtils.runOnExecutor(() -> mediaStreamReleaseAsync(id)); + } + + private void mediaStreamReleaseAsync(String id) { + MediaStream stream = localStreams.get(id); + if (stream == null) { + Log.d(TAG, "mediaStreamRelease() stream is null"); + return; + } + + // Remove and dispose any tracks ourselves before calling stream.dispose(). + // We need to remove the extra objects (TrackPrivate) we create. + + List audioTracks = new ArrayList<>(stream.audioTracks); + for (AudioTrack track : audioTracks) { + track.setEnabled(false); + stream.removeTrack(track); + getUserMediaImpl.disposeTrack(track.id()); + } + + List videoTracks = new ArrayList<>(stream.videoTracks); + for (VideoTrack track : videoTracks) { + track.setEnabled(false); + stream.removeTrack(track); + getUserMediaImpl.disposeTrack(track.id()); + } + + localStreams.remove(id); + + // MediaStream.dispose() may be called without an exception only if + // it's no longer added to any PeerConnection. + for (int i = 0, size = mPeerConnectionObservers.size(); i < size; i++) { + mPeerConnectionObservers.valueAt(i).removeStream(stream); + } + + stream.dispose(); + } + + @ReactMethod + public void mediaStreamTrackRelease(String id) { + ThreadUtils.runOnExecutor(() -> + mediaStreamTrackReleaseAsync(id)); + } + + private void mediaStreamTrackReleaseAsync(String id) { + MediaStreamTrack track = getLocalTrack(id); + if (track == null) { + Log.d(TAG, "mediaStreamTrackRelease() track is null"); + return; + } + track.setEnabled(false); + getUserMediaImpl.disposeTrack(id); } @ReactMethod @@ -592,11 +621,6 @@ private void mediaStreamTrackSetEnabledAsync(String id, boolean enabled) { getUserMediaImpl.mediaStreamTrackSetEnabled(id, enabled); } - @ReactMethod - public void mediaStreamTrackStop(String trackId) { - getUserMediaImpl.mediaStreamTrackStop(trackId); - } - @ReactMethod public void mediaStreamTrackSwitchCamera(String id) { MediaStreamTrack track = getLocalTrack(id); diff --git a/getUserMedia.js b/getUserMedia.js index 4506885b4..6fe15a9cb 100644 --- a/getUserMedia.js +++ b/getUserMedia.js @@ -5,7 +5,6 @@ import * as RTCUtil from './RTCUtil'; import MediaStream from './MediaStream'; import MediaStreamError from './MediaStreamError'; -import MediaStreamTrack from './MediaStreamTrack'; import permissions from './Permissions'; const { WebRTCModule } = NativeModules; @@ -61,36 +60,19 @@ export default function getUserMedia(constraints = {}) { audioPerm || (delete constraints.audio); videoPerm || (delete constraints.video); - WebRTCModule.getUserMedia( - constraints, - /* successCallback */ (id, tracks) => { - const stream = new MediaStream(id); - for (const track of tracks) { - stream.addTrack(new MediaStreamTrack(track)); - } + const success = (id, tracks) => { + const info = { + streamId: id, + streamReactTag: id, + tracks + }; - resolve(stream); - }, - /* errorCallback */ (type, message) => { + resolve(new MediaStream(info)); + }; + + const failure = (type, message) => { let error; switch (type) { - case 'DOMException': - // According to - // https://www.w3.org/TR/mediacapture-streams/#idl-def-MediaStreamError, - // MediaStreamError is either a DOMException object or an - // OverconstrainedError object. We are very likely to not have a - // definition of DOMException on React Native (unless the client has - // provided such a definition). If necessary, we will fall back to our - // definition of MediaStreamError. - if (typeof DOMException === 'function') { - error = new DOMException(/* message */ undefined, /* name */ message); - } - break; - case 'OverconstrainedError': - if (typeof OverconstrainedError === 'function') { - error = new OverconstrainedError(/* constraint */ undefined, message); - } - break; case 'TypeError': error = new TypeError(message); break; @@ -100,7 +82,9 @@ export default function getUserMedia(constraints = {}) { } reject(error); - }); + }; + + WebRTCModule.getUserMedia(constraints, success, failure); }); }); } diff --git a/ios/RCTWebRTC/WebRTCModule+RTCMediaStream.m b/ios/RCTWebRTC/WebRTCModule+RTCMediaStream.m index 4c9861060..450677ce1 100644 --- a/ios/RCTWebRTC/WebRTCModule+RTCMediaStream.m +++ b/ios/RCTWebRTC/WebRTCModule+RTCMediaStream.m @@ -65,7 +65,6 @@ - (RTCVideoTrack *)createVideoTrack:(NSDictionary *)constraints { RTCAudioTrack *audioTrack = nil; RTCVideoTrack *videoTrack = nil; - if (constraints[@"audio"]) { audioTrack = [self createAudioTrack:constraints]; } @@ -73,7 +72,6 @@ - (RTCVideoTrack *)createVideoTrack:(NSDictionary *)constraints { videoTrack = [self createVideoTrack:constraints]; } - if (audioTrack == nil && videoTrack == nil) { // Fail with DOMException with name AbortError as per: // https://www.w3.org/TR/mediacapture-streams/#dom-mediadevices-getusermedia @@ -153,53 +151,65 @@ - (RTCVideoTrack *)createVideoTrack:(NSDictionary *)constraints { callback(@[devices]); } +RCT_EXPORT_METHOD(mediaStreamCreate:(nonnull NSString *)streamID) +{ + RTCMediaStream *mediaStream = [self.peerConnectionFactory mediaStreamWithStreamId:streamID]; + self.localStreams[streamID] = mediaStream; +} + +RCT_EXPORT_METHOD(mediaStreamAddTrack:(nonnull NSString *)streamID : (nonnull NSString *)trackID) +{ + RTCMediaStream *mediaStream = self.localStreams[streamID]; + RTCMediaStreamTrack *track = self.localTracks[trackID]; + + if (mediaStream && track) { + if ([track.kind isEqualToString:@"audio"]) { + [mediaStream addAudioTrack:(RTCAudioTrack *)track]; + } else if([track.kind isEqualToString:@"video"]) { + [mediaStream addVideoTrack:(RTCVideoTrack *)track]; + } + } +} + +RCT_EXPORT_METHOD(mediaStreamRemoveTrack:(nonnull NSString *)streamID : (nonnull NSString *)trackID) +{ + RTCMediaStream *mediaStream = self.localStreams[streamID]; + RTCMediaStreamTrack *track = self.localTracks[trackID]; + + if (mediaStream && track) { + if ([track.kind isEqualToString:@"audio"]) { + [mediaStream removeAudioTrack:(RTCAudioTrack *)track]; + } else if([track.kind isEqualToString:@"video"]) { + [mediaStream removeVideoTrack:(RTCVideoTrack *)track]; + } + } +} + RCT_EXPORT_METHOD(mediaStreamRelease:(nonnull NSString *)streamID) { RTCMediaStream *stream = self.localStreams[streamID]; if (stream) { for (RTCVideoTrack *track in stream.videoTracks) { + track.isEnabled = NO; [track.videoCaptureController stopCapture]; [self.localTracks removeObjectForKey:track.trackId]; } for (RTCAudioTrack *track in stream.audioTracks) { + track.isEnabled = NO; [self.localTracks removeObjectForKey:track.trackId]; } [self.localStreams removeObjectForKey:streamID]; } } -RCT_EXPORT_METHOD(mediaStreamTrackRelease:(nonnull NSString *)streamID : (nonnull NSString *)trackID) -{ - // what's different to mediaStreamTrackStop? only call mediaStream explicitly? - RTCMediaStream *mediaStream = self.localStreams[streamID]; - RTCMediaStreamTrack *track = self.localTracks[trackID]; - if (mediaStream && track) { - track.isEnabled = NO; - [track.videoCaptureController stopCapture]; - // FIXME this is called when track is removed from the MediaStream, - // but it doesn't mean it can not be added back using MediaStream.addTrack - [self.localTracks removeObjectForKey:trackID]; - if ([track.kind isEqualToString:@"audio"]) { - [mediaStream removeAudioTrack:(RTCAudioTrack *)track]; - } else if([track.kind isEqualToString:@"video"]) { - [mediaStream removeVideoTrack:(RTCVideoTrack *)track]; - } - } -} - -- (RTCMediaStreamTrack*)trackForId:(NSString*)trackId +RCT_EXPORT_METHOD(mediaStreamTrackRelease:(nonnull NSString *)trackID) { - RTCMediaStreamTrack *track = self.localTracks[trackId]; - if (!track) { - for (NSNumber *peerConnectionId in self.peerConnections) { - RTCPeerConnection *peerConnection = self.peerConnections[peerConnectionId]; - track = peerConnection.remoteTracks[trackId]; - if (track) { - break; - } + RTCMediaStreamTrack *track = self.localTracks[trackID]; + if (track) { + track.isEnabled = NO; + [track.videoCaptureController stopCapture]; + [self.localTracks removeObjectForKey:trackID]; } - } - return track; } RCT_EXPORT_METHOD(mediaStreamTrackSetEnabled:(nonnull NSString *)trackID : (BOOL)enabled) @@ -207,10 +217,12 @@ - (RTCMediaStreamTrack*)trackForId:(NSString*)trackId RTCMediaStreamTrack *track = [self trackForId:trackID]; if (track && track.isEnabled != enabled) { track.isEnabled = enabled; - if (enabled) { - [track.videoCaptureController startCapture]; - } else { - [track.videoCaptureController stopCapture]; + if (track.videoCaptureController) { // It could be a remote track! + if (enabled) { + [track.videoCaptureController startCapture]; + } else { + [track.videoCaptureController stopCapture]; + } } } } @@ -224,14 +236,21 @@ - (RTCMediaStreamTrack*)trackForId:(NSString*)trackId } } -RCT_EXPORT_METHOD(mediaStreamTrackStop:(nonnull NSString *)trackID) +#pragma mark - Helpers + +- (RTCMediaStreamTrack*)trackForId:(NSString*)trackId { - RTCMediaStreamTrack *track = self.localTracks[trackID]; - if (track) { - track.isEnabled = NO; - [track.videoCaptureController stopCapture]; - [self.localTracks removeObjectForKey:trackID]; + RTCMediaStreamTrack *track = self.localTracks[trackId]; + if (!track) { + for (NSNumber *peerConnectionId in self.peerConnections) { + RTCPeerConnection *peerConnection = self.peerConnections[peerConnectionId]; + track = peerConnection.remoteTracks[trackId]; + if (track) { + break; + } + } } + return track; } @end diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..76021fbac --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "react-native-webrtc", + "version": "1.69.2", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } +} diff --git a/package.json b/package.json index 0630fbd61..35c03543d 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "dependencies": { "base64-js": "^1.1.2", "event-target-shim": "^1.0.5", - "prop-types": "^15.5.10" + "prop-types": "^15.5.10", + "uuid": "^3.3.2" }, "peerDependencies": { "react-native": ">=0.40.0"