diff --git a/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java b/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java index abc41042f..ede36d2ac 100644 --- a/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java +++ b/android/src/main/java/com/oney/WebRTCModule/GetUserMediaImpl.java @@ -425,8 +425,12 @@ VideoTrack createVideoTrack(AbstractVideoCaptureController videoCaptureControlle VideoTrack track = pcFactory.createVideoTrack(id, videoSource); + // Add dimension detection for local video tracks immediately when created + VideoTrackAdapter localTrackAdapter = new VideoTrackAdapter(webRTCModule, -1); // Use -1 for local tracks + localTrackAdapter.addDimensionDetector(track); + track.setEnabled(true); - tracks.put(id, new TrackPrivate(track, videoSource, videoCaptureController, surfaceTextureHelper)); + tracks.put(id, new TrackPrivate(track, videoSource, videoCaptureController, surfaceTextureHelper, localTrackAdapter)); videoCaptureController.startCapture(); @@ -444,8 +448,14 @@ MediaStreamTrack cloneTrack(String trackId) { String id = UUID.randomUUID().toString(); MediaStreamTrack nativeTrack = track.track; final MediaStreamTrack clonedNativeTrack; + VideoTrackAdapter clonedVideoTrackAdapter = null; + if (nativeTrack instanceof VideoTrack) { clonedNativeTrack = pcFactory.createVideoTrack(id, (VideoSource) track.mediaSource); + + // Create dimension detection for cloned video tracks + clonedVideoTrackAdapter = new VideoTrackAdapter(webRTCModule, -1); + clonedVideoTrackAdapter.addDimensionDetector((VideoTrack) clonedNativeTrack); } else { clonedNativeTrack = pcFactory.createAudioTrack(id, (AudioSource) track.mediaSource); } @@ -455,7 +465,8 @@ MediaStreamTrack cloneTrack(String trackId) { clonedNativeTrack, track.mediaSource, track.videoCaptureController, - track.surfaceTextureHelper + track.surfaceTextureHelper, + clonedVideoTrackAdapter ); clone.setParent(track); tracks.put(id, clone); @@ -519,6 +530,11 @@ private static class TrackPrivate { private final SurfaceTextureHelper surfaceTextureHelper; + /** + * The {@code VideoTrackAdapter} for dimension detection if {@link #track} is a {@link VideoTrack}. + */ + public final VideoTrackAdapter videoTrackAdapter; + /** * Whether this object has been disposed or not. */ @@ -538,16 +554,28 @@ private static class TrackPrivate { * @param videoCaptureController the {@code AbstractVideoCaptureController} from which the * specified {@code mediaSource} was created if the specified * {@code track} is a {@link VideoTrack} + * @param surfaceTextureHelper the {@code SurfaceTextureHelper} for video rendering + * @param videoTrackAdapter the {@code VideoTrackAdapter} for dimension detection if video track */ public TrackPrivate(MediaStreamTrack track, MediaSource mediaSource, - AbstractVideoCaptureController videoCaptureController, SurfaceTextureHelper surfaceTextureHelper) { + AbstractVideoCaptureController videoCaptureController, SurfaceTextureHelper surfaceTextureHelper, + VideoTrackAdapter videoTrackAdapter) { this.track = track; this.mediaSource = mediaSource; this.videoCaptureController = videoCaptureController; this.surfaceTextureHelper = surfaceTextureHelper; + this.videoTrackAdapter = videoTrackAdapter; this.disposed = false; } + /** + * Backwards compatibility constructor for audio tracks + */ + public TrackPrivate(MediaStreamTrack track, MediaSource mediaSource, + AbstractVideoCaptureController videoCaptureController, SurfaceTextureHelper surfaceTextureHelper) { + this(track, mediaSource, videoCaptureController, surfaceTextureHelper, null); + } + public void dispose() { final boolean isClone = this.isClone(); if (!disposed) { @@ -557,6 +585,11 @@ public void dispose() { } } + // Clean up VideoTrackAdapter for video tracks + if (!isClone && videoTrackAdapter != null && track instanceof VideoTrack) { + videoTrackAdapter.removeDimensionDetector((VideoTrack) track); + } + /* * As per webrtc library documentation - The caller still has ownership of {@code * surfaceTextureHelper} and is responsible for making sure surfaceTextureHelper.dispose() is diff --git a/android/src/main/java/com/oney/WebRTCModule/PeerConnectionObserver.java b/android/src/main/java/com/oney/WebRTCModule/PeerConnectionObserver.java index dfc2901a8..32b028f82 100644 --- a/android/src/main/java/com/oney/WebRTCModule/PeerConnectionObserver.java +++ b/android/src/main/java/com/oney/WebRTCModule/PeerConnectionObserver.java @@ -41,7 +41,7 @@ class PeerConnectionObserver implements PeerConnection.Observer { final Map remoteStreamIds; // Stream ID -> React tag final Map remoteStreams; // React tag -> MediaStream final Map remoteTracks; - private final VideoTrackAdapter videoTrackAdapters; + final VideoTrackAdapter videoTrackAdapters; private final WebRTCModule webRTCModule; PeerConnectionObserver(WebRTCModule webRTCModule, int id) { @@ -75,6 +75,16 @@ void dispose() { for (MediaStreamTrack track : this.remoteTracks.values()) { if (track instanceof VideoTrack) { videoTrackAdapters.removeAdapter((VideoTrack) track); + videoTrackAdapters.removeDimensionDetector((VideoTrack) track); + } + } + + // Remove video track adapters for local tracks (from senders) + for (RtpSender sender : this.peerConnection.getSenders()) { + MediaStreamTrack track = sender.track(); + if (track instanceof VideoTrack) { + videoTrackAdapters.removeAdapter((VideoTrack) track); + // Note: dimension detection for local tracks is cleaned up when track is disposed } } @@ -432,6 +442,7 @@ public void onAddTrack(final RtpReceiver receiver, final MediaStream[] mediaStre if (!existingTrack) { if (track.kind().equals(MediaStreamTrack.VIDEO_TRACK_KIND)) { videoTrackAdapters.addAdapter((VideoTrack) track); + videoTrackAdapters.addDimensionDetector((VideoTrack) track); } remoteTracks.put(track.id(), track); } diff --git a/android/src/main/java/com/oney/WebRTCModule/VideoTrackAdapter.java b/android/src/main/java/com/oney/WebRTCModule/VideoTrackAdapter.java index 43ddbcbd7..486bfe6b7 100644 --- a/android/src/main/java/com/oney/WebRTCModule/VideoTrackAdapter.java +++ b/android/src/main/java/com/oney/WebRTCModule/VideoTrackAdapter.java @@ -23,6 +23,7 @@ public class VideoTrackAdapter { static final long MUTE_DELAY = 1500; private Map muteImplMap = new HashMap<>(); + private Map dimensionDetectorMap = new HashMap<>(); private Timer timer = new Timer("VideoTrackMutedTimer"); @@ -62,6 +63,32 @@ public void removeAdapter(VideoTrack videoTrack) { Log.d(TAG, "Deleted adapter for " + trackId); } + public void addDimensionDetector(VideoTrack videoTrack) { + String trackId = videoTrack.id(); + if (dimensionDetectorMap.containsKey(trackId)) { + Log.w(TAG, "Attempted to add dimension detector twice for track ID: " + trackId); + return; + } + + VideoDimensionDetectorImpl dimensionDetector = new VideoDimensionDetectorImpl(trackId); + Log.d(TAG, "Created dimension detector for " + trackId); + dimensionDetectorMap.put(trackId, dimensionDetector); + videoTrack.addSink(dimensionDetector); + } + + public void removeDimensionDetector(VideoTrack videoTrack) { + String trackId = videoTrack.id(); + VideoDimensionDetectorImpl dimensionDetector = dimensionDetectorMap.remove(trackId); + if (dimensionDetector == null) { + Log.w(TAG, "removeDimensionDetector - no detector for " + trackId); + return; + } + + videoTrack.removeSink(dimensionDetector); + dimensionDetector.dispose(); + Log.d(TAG, "Deleted dimension detector for " + trackId); + } + /** * Implements 'mute'/'unmute' events for remote video tracks through * the {@link VideoSink} interface. @@ -134,4 +161,58 @@ void dispose() { } } } + + /** + * Implements dimension change events for remote video tracks through + * the {@link VideoSink} interface. + */ + private class VideoDimensionDetectorImpl implements VideoSink { + private volatile boolean disposed; + private int currentWidth = 0; + private int currentHeight = 0; + private boolean hasInitialSize = false; + private final String trackId; + + VideoDimensionDetectorImpl(String trackId) { + this.trackId = trackId; + } + + @Override + public void onFrame(VideoFrame frame) { + if (disposed) { + return; + } + + int width = frame.getBuffer().getWidth(); + int height = frame.getBuffer().getHeight(); + + // Check if this is a meaningful size change + if (!hasInitialSize) { + currentWidth = width; + currentHeight = height; + hasInitialSize = true; + emitDimensionChangeEvent(width, height); + } else if (currentWidth != width || currentHeight != height) { + currentWidth = width; + currentHeight = height; + emitDimensionChangeEvent(width, height); + } + } + + private void emitDimensionChangeEvent(int width, int height) { + WritableMap params = Arguments.createMap(); + params.putInt("pcId", peerConnectionId); + params.putString("trackId", trackId); + params.putInt("width", width); + params.putInt("height", height); + + Log.d(TAG, "Dimension change event pcId: " + peerConnectionId + " trackId: " + trackId + " dimensions: " + width + "x" + height); + + VideoTrackAdapter.this.webRTCModule.sendEvent("videoTrackDimensionChanged", params); + } + + void dispose() { + disposed = true; + } + } } diff --git a/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java b/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java index c560d7b74..23528fe12 100644 --- a/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java +++ b/android/src/main/java/com/oney/WebRTCModule/WebRTCModule.java @@ -504,6 +504,11 @@ public WritableMap peerConnectionAddTransceiver(int id, ReadableMap options) { MediaStreamTrack track = getLocalTrack(trackId); transceiver = pco.addTransceiver( track, SerializeUtils.parseTransceiverOptions(options.getMap("init"))); + + // Add mute detection for local video tracks (dimension detection is handled at track creation) + if (track instanceof VideoTrack) { + pco.videoTrackAdapters.addAdapter((VideoTrack) track); + } } else { // This should technically never happen as the JS side checks for that. @@ -556,6 +561,11 @@ public WritableMap peerConnectionAddTrack(int id, String trackId, ReadableMap op } } RtpSender sender = pco.getPeerConnection().addTrack(track, streamIds); + + // Add mute detection for local video tracks (dimension detection is handled at track creation) + if (track instanceof VideoTrack) { + pco.videoTrackAdapters.addAdapter((VideoTrack) track); + } // Need to get the corresponding transceiver as well RtpTransceiver transceiver = pco.getTransceiver(sender.id()); @@ -591,6 +601,13 @@ public boolean peerConnectionRemoveTrack(int id, String senderId) { return false; } + // Remove video track adapters for local tracks + MediaStreamTrack track = sender.track(); + if (track instanceof VideoTrack) { + pco.videoTrackAdapters.removeAdapter((VideoTrack) track); + // Note: dimension detection for local tracks is cleaned up when track is disposed + } + return pco.getPeerConnection().removeTrack(sender); }) .get(); diff --git a/ios/RCTWebRTC/WebRTCModule+RTCMediaStream.h b/ios/RCTWebRTC/WebRTCModule+RTCMediaStream.h index da281ac80..5b6353fd0 100644 --- a/ios/RCTWebRTC/WebRTCModule+RTCMediaStream.h +++ b/ios/RCTWebRTC/WebRTCModule+RTCMediaStream.h @@ -10,4 +10,7 @@ (CaptureController * (^)(RTCVideoSource *))captureControllerCreator; - (NSArray *)createMediaStream:(NSArray *)tracks; +- (void)addLocalVideoTrackDimensionDetection:(RTCVideoTrack *)videoTrack; +- (void)removeLocalVideoTrackDimensionDetection:(RTCVideoTrack *)videoTrack; + @end \ No newline at end of file diff --git a/ios/RCTWebRTC/WebRTCModule+RTCMediaStream.m b/ios/RCTWebRTC/WebRTCModule+RTCMediaStream.m index eafae4394..3d730a83f 100644 --- a/ios/RCTWebRTC/WebRTCModule+RTCMediaStream.m +++ b/ios/RCTWebRTC/WebRTCModule+RTCMediaStream.m @@ -10,6 +10,7 @@ #import "WebRTCModuleOptions.h" #import "WebRTCModule+RTCMediaStream.h" #import "WebRTCModule+RTCPeerConnection.h" +#import "WebRTCModule+VideoTrackAdapter.h" #import "ProcessorProvider.h" #import "ScreenCaptureController.h" @@ -60,6 +61,9 @@ - (RTCVideoTrack *)createVideoTrackWithCaptureController: videoTrack.captureController = captureController; [captureController startCapture]; + // Add dimension detection for local video tracks immediately + [self addLocalVideoTrackDimensionDetection:videoTrack]; + return videoTrack; #endif } @@ -136,6 +140,9 @@ - (RTCVideoTrack *)createVideoTrack:(NSDictionary *)constraints { [videoCaptureController startCapture]; #endif + // Add dimension detection for local video tracks immediately + [self addLocalVideoTrackDimensionDetection:videoTrack]; + return videoTrack; #endif } @@ -159,6 +166,9 @@ - (RTCVideoTrack *)createScreenCaptureVideoTrack { videoTrack.captureController = screenCaptureController; [screenCaptureController startCapture]; + // Add dimension detection for local video tracks immediately + [self addLocalVideoTrackDimensionDetection:videoTrack]; + return videoTrack; } @@ -276,7 +286,7 @@ - (RTCVideoTrack *)createScreenCaptureVideoTrack { #endif } -#pragma mark - Other stream related APIs +#pragma mark - enumerateDevices RCT_EXPORT_METHOD(enumerateDevices : (RCTResponseSenderBlock)callback) { #if TARGET_OS_TV @@ -332,6 +342,45 @@ - (RTCVideoTrack *)createScreenCaptureVideoTrack { #endif } +#pragma mark - Local Video Track Dimension Detection + +- (void)addLocalVideoTrackDimensionDetection:(RTCVideoTrack *)videoTrack { + if (!videoTrack) { + return; + } + + // Create a dimension detector for this local track + VideoDimensionDetector *detector = [[VideoDimensionDetector alloc] initWith:@(-1) // -1 for local tracks + trackId:videoTrack.trackId + webRTCModule:self]; + + // Store the detector using associated objects on the track itself + objc_setAssociatedObject(videoTrack, @selector(addLocalVideoTrackDimensionDetection:), detector, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // Add the detector as a renderer to the track + [videoTrack addRenderer:detector]; + + RCTLogTrace(@"[VideoTrackAdapter] Local dimension detector created for track %@", videoTrack.trackId); +} + +- (void)removeLocalVideoTrackDimensionDetection:(RTCVideoTrack *)videoTrack { + if (!videoTrack) { + return; + } + + // Get the associated detector + VideoDimensionDetector *detector = objc_getAssociatedObject(videoTrack, @selector(addLocalVideoTrackDimensionDetection:)); + + if (detector) { + [videoTrack removeRenderer:detector]; + [detector dispose]; + objc_setAssociatedObject(videoTrack, @selector(addLocalVideoTrackDimensionDetection:), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + RCTLogTrace(@"[VideoTrackAdapter] Local dimension detector removed for track %@", videoTrack.trackId); + } +} + +#pragma mark - Other stream related APIs + RCT_EXPORT_METHOD(mediaStreamCreate : (nonnull NSString *)streamID) { RTCMediaStream *mediaStream = [self.peerConnectionFactory mediaStreamWithStreamId:streamID]; self.localStreams[streamID] = mediaStream; @@ -393,6 +442,11 @@ - (RTCVideoTrack *)createScreenCaptureVideoTrack { RTCMediaStreamTrack *track = self.localTracks[trackID]; if (track) { + // Clean up dimension detection for local video tracks + if ([track.kind isEqualToString:@"video"]) { + [self removeLocalVideoTrackDimensionDetection:(RTCVideoTrack *)track]; + } + track.isEnabled = NO; [track.captureController stopCapture]; [self.localTracks removeObjectForKey:trackID]; @@ -425,6 +479,10 @@ - (RTCVideoTrack *)createScreenCaptureVideoTrack { RTCVideoSource *videoSource = originalVideoTrack.source; RTCVideoTrack *videoTrack = [self.peerConnectionFactory videoTrackWithSource:videoSource trackId:trackUUID]; videoTrack.isEnabled = originalTrack.isEnabled; + + // Add dimension detection for cloned local video tracks + [self addLocalVideoTrackDimensionDetection:videoTrack]; + [self.localTracks setObject:videoTrack forKey:trackUUID]; for (NSString* streamId in self.localStreams) { RTCMediaStream* stream = [self.localStreams objectForKey:streamId]; diff --git a/ios/RCTWebRTC/WebRTCModule+RTCPeerConnection.m b/ios/RCTWebRTC/WebRTCModule+RTCPeerConnection.m index 20db4a56e..379ab295f 100644 --- a/ios/RCTWebRTC/WebRTCModule+RTCPeerConnection.m +++ b/ios/RCTWebRTC/WebRTCModule+RTCPeerConnection.m @@ -94,6 +94,7 @@ @implementation WebRTCModule (RTCPeerConnection) peerConnection.remoteStreams = [NSMutableDictionary new]; peerConnection.remoteTracks = [NSMutableDictionary new]; peerConnection.videoTrackAdapters = [NSMutableDictionary new]; + peerConnection.videoDimensionDetectors = [NSMutableDictionary new]; peerConnection.webRTCModule = self; self.peerConnections[objectID] = peerConnection; @@ -329,6 +330,16 @@ @implementation WebRTCModule (RTCPeerConnection) RTCMediaStreamTrack *track = peerConnection.remoteTracks[key]; if (track.kind == kRTCMediaStreamTrackKindVideo) { [peerConnection removeVideoTrackAdapter:(RTCVideoTrack *)track]; + [peerConnection removeVideoDimensionDetector:(RTCVideoTrack *)track]; + } + } + + // Remove video track adapters for local tracks (from senders) + for (RTCRtpSender *sender in peerConnection.senders) { + RTCMediaStreamTrack *track = sender.track; + if (track && track.kind == kRTCMediaStreamTrackKindVideo) { + [peerConnection removeVideoTrackAdapter:(RTCVideoTrack *)track]; + // Note: dimension detection for local tracks cleaned up at track release } } @@ -454,6 +465,13 @@ @implementation WebRTCModule (RTCPeerConnection) NSArray *streamIds = [options objectForKey:@"streamIds"]; RTCRtpSender *sender = [peerConnection addTrack:track streamIds:streamIds]; + + // Add mute detection for local video tracks (dimension detection handled at track creation) + if (track.kind == kRTCMediaStreamTrackKindVideo) { + RTCVideoTrack *videoTrack = (RTCVideoTrack *)track; + [peerConnection addVideoTrackAdapter:videoTrack]; + } + RTCRtpTransceiver *transceiver = nil; for (RTCRtpTransceiver *t in peerConnection.transceivers) { @@ -517,6 +535,12 @@ @implementation WebRTCModule (RTCPeerConnection) RTCRtpTransceiverInit *transceiverInit = [SerializeUtils parseTransceiverOptions:initOptions]; transceiver = [peerConnection addTransceiverWithTrack:track init:transceiverInit]; + + // Add mute detection for local video tracks (dimension detection handled at track creation) + if (track && track.kind == kRTCMediaStreamTrackKindVideo && self.localTracks[trackId]) { + RTCVideoTrack *videoTrack = (RTCVideoTrack *)track; + [peerConnection addVideoTrackAdapter:videoTrack]; + } } else { RCTLogWarn(@"peerConnectionAddTransceiver() no type nor trackId provided in options"); return; @@ -562,6 +586,13 @@ @implementation WebRTCModule (RTCPeerConnection) return; } + // Remove mute detection for local tracks (dimension detection cleaned up at track release) + RTCMediaStreamTrack *track = sender.track; + if (track && track.kind == kRTCMediaStreamTrackKindVideo) { + RTCVideoTrack *videoTrack = (RTCVideoTrack *)track; + [peerConnection removeVideoTrackAdapter:videoTrack]; + } + ret = [peerConnection removeTrack:sender]; }); @@ -877,6 +908,7 @@ - (void)peerConnection:(RTC_OBJC_TYPE(RTCPeerConnection) *)peerConnection if (track.kind == kRTCMediaStreamTrackKindVideo) { RTCVideoTrack *videoTrack = (RTCVideoTrack *)track; [peerConnection addVideoTrackAdapter:videoTrack]; + [peerConnection addVideoDimensionDetector:videoTrack]; } peerConnection.remoteTracks[track.trackId] = track; diff --git a/ios/RCTWebRTC/WebRTCModule+VideoTrackAdapter.h b/ios/RCTWebRTC/WebRTCModule+VideoTrackAdapter.h index 9ea8fa964..c80098aad 100644 --- a/ios/RCTWebRTC/WebRTCModule+VideoTrackAdapter.h +++ b/ios/RCTWebRTC/WebRTCModule+VideoTrackAdapter.h @@ -1,12 +1,28 @@ #import +#import #import "WebRTCModule.h" +@interface VideoDimensionDetector : NSObject + +@property(copy, nonatomic) NSNumber *peerConnectionId; +@property(copy, nonatomic) NSString *trackId; +@property(weak, nonatomic) WebRTCModule *module; + +- (instancetype)initWith:(NSNumber *)peerConnectionId trackId:(NSString *)trackId webRTCModule:(WebRTCModule *)module; +- (void)dispose; + +@end + @interface RTCPeerConnection (VideoTrackAdapter) @property(nonatomic, strong) NSMutableDictionary *videoTrackAdapters; +@property(nonatomic, strong) NSMutableDictionary *videoDimensionDetectors; - (void)addVideoTrackAdapter:(RTCVideoTrack *)track; - (void)removeVideoTrackAdapter:(RTCVideoTrack *)track; +- (void)addVideoDimensionDetector:(RTCVideoTrack *)track; +- (void)removeVideoDimensionDetector:(RTCVideoTrack *)track; + @end diff --git a/ios/RCTWebRTC/WebRTCModule+VideoTrackAdapter.m b/ios/RCTWebRTC/WebRTCModule+VideoTrackAdapter.m index af4e610ae..49ae665aa 100644 --- a/ios/RCTWebRTC/WebRTCModule+VideoTrackAdapter.m +++ b/ios/RCTWebRTC/WebRTCModule+VideoTrackAdapter.m @@ -121,6 +121,74 @@ - (void)setSize:(CGSize)size { @end +/* Entity responsible for detecting video dimension changes. It's implemented + * as a video renderer, which monitors the setSize: method to detect when + * video dimensions change and emits events accordingly. + */ + +@implementation VideoDimensionDetector { + BOOL _disposed; + CGSize _currentSize; + BOOL _hasInitialSize; +} + +- (instancetype)initWith:(NSNumber *)peerConnectionId trackId:(NSString *)trackId webRTCModule:(WebRTCModule *)module { + self = [super init]; + if (self) { + self.peerConnectionId = peerConnectionId; + self.trackId = trackId; + self.module = module; + + _disposed = NO; + _currentSize = CGSizeZero; + _hasInitialSize = NO; + } + + return self; +} + +- (void)dispose { + _disposed = YES; +} + +- (void)emitDimensionChangeEvent:(CGSize)newSize { + [self.module sendEventWithName:kEventVideoTrackDimensionChanged + body:@{ + @"pcId" : self.peerConnectionId, + @"trackId" : self.trackId, + @"width" : @(newSize.width), + @"height" : @(newSize.height) + }]; + RCTLog(@"[VideoDimensionDetector] Dimension change event for pc %@ track %@: %fx%f", + self.peerConnectionId, + self.trackId, + newSize.width, + newSize.height); +} + +- (void)renderFrame:(nullable RTCVideoFrame *)frame { + // We don't need to do anything with frames for dimension detection + // The setSize: method will be called automatically when dimensions change +} + +- (void)setSize:(CGSize)size { + if (_disposed) { + return; + } + + // Check if this is a meaningful size change + if (!_hasInitialSize) { + _currentSize = size; + _hasInitialSize = YES; + [self emitDimensionChangeEvent:size]; + } else if (!CGSizeEqualToSize(_currentSize, size)) { + _currentSize = size; + [self emitDimensionChangeEvent:size]; + } +} + +@end + @implementation RTCPeerConnection (VideoTrackAdapter) - (NSMutableDictionary *)videoTrackAdapters { @@ -132,6 +200,15 @@ - (void)setVideoTrackAdapters:(NSMutableDictionary *)videoTrackA self, @selector(videoTrackAdapters), videoTrackAdapters, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (NSMutableDictionary *)videoDimensionDetectors { + return objc_getAssociatedObject(self, _cmd); +} + +- (void)setVideoDimensionDetectors:(NSMutableDictionary *)videoDimensionDetectors { + objc_setAssociatedObject( + self, @selector(videoDimensionDetectors), videoDimensionDetectors, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + - (void)addVideoTrackAdapter:(RTCVideoTrack *)track { NSString *trackId = track.trackId; if ([self.videoTrackAdapters objectForKey:trackId] != nil) { @@ -163,4 +240,34 @@ - (void)removeVideoTrackAdapter:(RTCVideoTrack *)track { RCTLogTrace(@"[VideoTrackAdapter] Adapter removed for track %@", trackId); } +- (void)addVideoDimensionDetector:(RTCVideoTrack *)track { + NSString *trackId = track.trackId; + if ([self.videoDimensionDetectors objectForKey:trackId] != nil) { + RCTLogWarn(@"[VideoDimensionDetector] Detector already exists for track %@", trackId); + return; + } + + VideoDimensionDetector *dimensionDetector = [[VideoDimensionDetector alloc] initWith:self.reactTag + trackId:trackId + webRTCModule:self.webRTCModule]; + [self.videoDimensionDetectors setObject:dimensionDetector forKey:trackId]; + [track addRenderer:dimensionDetector]; + + RCTLogTrace(@"[VideoDimensionDetector] Detector created for track %@", trackId); +} + +- (void)removeVideoDimensionDetector:(RTCVideoTrack *)track { + NSString *trackId = track.trackId; + VideoDimensionDetector *dimensionDetector = [self.videoDimensionDetectors objectForKey:trackId]; + if (dimensionDetector == nil) { + RCTLogWarn(@"[VideoDimensionDetector] Detector doesn't exist for track %@", trackId); + return; + } + + [track removeRenderer:dimensionDetector]; + [dimensionDetector dispose]; + [self.videoDimensionDetectors removeObjectForKey:trackId]; + RCTLogTrace(@"[VideoDimensionDetector] Detector removed for track %@", trackId); +} + @end diff --git a/ios/RCTWebRTC/WebRTCModule.h b/ios/RCTWebRTC/WebRTCModule.h index e1f555428..3ff079e0e 100644 --- a/ios/RCTWebRTC/WebRTCModule.h +++ b/ios/RCTWebRTC/WebRTCModule.h @@ -18,6 +18,7 @@ static NSString *const kEventDataChannelDidChangeBufferedAmount = @"dataChannelD static NSString *const kEventDataChannelStateChanged = @"dataChannelStateChanged"; static NSString *const kEventDataChannelReceiveMessage = @"dataChannelReceiveMessage"; static NSString *const kEventMediaStreamTrackMuteChanged = @"mediaStreamTrackMuteChanged"; +static NSString *const kEventVideoTrackDimensionChanged = @"videoTrackDimensionChanged"; static NSString *const kEventMediaStreamTrackEnded = @"mediaStreamTrackEnded"; static NSString *const kEventPeerConnectionOnRemoveTrack = @"peerConnectionOnRemoveTrack"; static NSString *const kEventPeerConnectionOnTrack = @"peerConnectionOnTrack"; diff --git a/ios/RCTWebRTC/WebRTCModule.m b/ios/RCTWebRTC/WebRTCModule.m index 6917aee12..6951cad90 100644 --- a/ios/RCTWebRTC/WebRTCModule.m +++ b/ios/RCTWebRTC/WebRTCModule.m @@ -131,6 +131,7 @@ - (dispatch_queue_t)methodQueue { kEventDataChannelStateChanged, kEventDataChannelReceiveMessage, kEventMediaStreamTrackMuteChanged, + kEventVideoTrackDimensionChanged, kEventMediaStreamTrackEnded, kEventPeerConnectionOnRemoveTrack, kEventPeerConnectionOnTrack diff --git a/src/EventEmitter.ts b/src/EventEmitter.ts index 48adb14fb..b21e49d47 100644 --- a/src/EventEmitter.ts +++ b/src/EventEmitter.ts @@ -22,6 +22,7 @@ const NATIVE_EVENTS = [ 'dataChannelReceiveMessage', 'dataChannelDidChangeBufferedAmount', 'mediaStreamTrackMuteChanged', + 'videoTrackDimensionChanged', 'mediaStreamTrackEnded', ]; diff --git a/src/MediaStreamTrack.ts b/src/MediaStreamTrack.ts index 1781a9179..4eed72819 100644 --- a/src/MediaStreamTrack.ts +++ b/src/MediaStreamTrack.ts @@ -164,6 +164,24 @@ export default class MediaStreamTrack extends EventTarget { + // Only handle local tracks (pcId === -1) and only for this track + if (ev.pcId !== -1 || ev.trackId !== this.id) { + return; + } + + this._setVideoTrackDimensions(ev.width, ev.height); + }); + } } release(): void { diff --git a/src/RTCPeerConnection.ts b/src/RTCPeerConnection.ts index cc88293d7..e2dc79111 100644 --- a/src/RTCPeerConnection.ts +++ b/src/RTCPeerConnection.ts @@ -725,6 +725,22 @@ export default class RTCPeerConnection extends EventTarget { + // Only handle remote tracks (pcId !== -1) and only for this peer connection + if (ev.pcId === -1 || ev.pcId !== this._pcId) { + return; + } + + const [ + track + ] = this.getReceivers().map(r => r.track).filter(t => t?.id === ev.trackId); + + if (track) { + track._setVideoTrackDimensions(ev.width, ev.height); + } + }); } /**