Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
+191
View File
@@ -0,0 +1,191 @@
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_unit_test")
config_setting(
name = "debug_build",
values = {
"compilation_mode": "dbg",
},
)
optimization_flags = select({
":debug_build": [
],
"//conditions:default": ["-DNDEBUG"],
})
sources = glob([
"Sources/**/*.m",
"Sources/**/*.mm",
"Sources/**/*.h",
"tgcalls/tgcalls/**/*.h",
"tgcalls/tgcalls/**/*.hpp",
"tgcalls/tgcalls/**/*.cpp",
"tgcalls/tgcalls/**/*.mm",
"tgcalls/tgcalls/**/*.m",
], allow_empty=True, exclude = [
"tgcalls/tgcalls/legacy/**",
"tgcalls/tgcalls/platform/tdesktop/**",
"tgcalls/tgcalls/platform/android/**",
"tgcalls/tgcalls/platform/windows/**",
"tgcalls/tgcalls/platform/uwp/**",
"tgcalls/tgcalls/platform/darwin/SQueue*",
"tgcalls/tgcalls/platform/darwin/macOS/**",
"tgcalls/tgcalls/platform/darwin/VideoCameraCapturerMac.*",
"tgcalls/tgcalls/platform/darwin/VideoMetalViewMac.*",
"tgcalls/tgcalls/platform/darwin/VideoSampleBufferViewMac.*",
"tgcalls/tgcalls/platform/darwin/GLVideoView.*",
"tgcalls/tgcalls/platform/darwin/GLVideoViewMac.*",
"tgcalls/tgcalls/platform/darwin/ScreenCapturer.*",
"tgcalls/tgcalls/platform/darwin/DesktopSharingCapturer.*",
"tgcalls/tgcalls/platform/darwin/DesktopCaptureSourceViewMac.*",
"tgcalls/tgcalls/platform/darwin/DesktopCaptureSourceView.*",
"tgcalls/tgcalls/platform/darwin/TGCMIODevice.*",
"tgcalls/tgcalls/platform/darwin/TGCMIOCapturer.*",
"tgcalls/tgcalls/platform/darwin/VideoCMIOCapture.*",
"tgcalls/tgcalls/desktop_capturer/**",
])
sources = glob([
"Sources/**/*.m",
"Sources/**/*.mm",
"Sources/**/*.h",
"tgcalls/tgcalls/**/*.h",
"tgcalls/tgcalls/**/*.hpp",
"tgcalls/tgcalls/platform/darwin/**/*.cpp",
"tgcalls/tgcalls/platform/darwin/**/*.m",
"tgcalls/tgcalls/platform/darwin/**/*.mm",
"tgcalls/tgcalls/platform/darwin/**/*.cc",
"tgcalls/tgcalls/platform/darwin/**/*.c",
"tgcalls/tgcalls/third-party/**/*.cpp",
"tgcalls/tgcalls/utils/**/*.cpp",
], allow_empty=True, exclude = [
"tgcalls/tgcalls/legacy/**",
"tgcalls/tgcalls/platform/tdesktop/**",
"tgcalls/tgcalls/platform/android/**",
"tgcalls/tgcalls/platform/windows/**",
"tgcalls/tgcalls/platform/uwp/**",
"tgcalls/tgcalls/platform/darwin/SQueue*",
"tgcalls/tgcalls/platform/darwin/macOS/**",
"tgcalls/tgcalls/platform/darwin/VideoCameraCapturerMac.*",
"tgcalls/tgcalls/platform/darwin/VideoMetalViewMac.*",
"tgcalls/tgcalls/platform/darwin/VideoSampleBufferViewMac.*",
"tgcalls/tgcalls/platform/darwin/GLVideoView.*",
"tgcalls/tgcalls/platform/darwin/GLVideoViewMac.*",
"tgcalls/tgcalls/platform/darwin/ScreenCapturer.*",
"tgcalls/tgcalls/platform/darwin/DesktopSharingCapturer.*",
"tgcalls/tgcalls/platform/darwin/DesktopCaptureSourceViewMac.*",
"tgcalls/tgcalls/platform/darwin/DesktopCaptureSourceView.*",
"tgcalls/tgcalls/platform/darwin/TGCMIODevice.*",
"tgcalls/tgcalls/platform/darwin/TGCMIOCapturer.*",
"tgcalls/tgcalls/platform/darwin/VideoCMIOCapture.*",
"tgcalls/tgcalls/desktop_capturer/**",
]) + [
"tgcalls/tgcalls/Manager.cpp",
"tgcalls/tgcalls/MediaManager.cpp",
"tgcalls/tgcalls/AudioDeviceHelper.cpp",
"tgcalls/tgcalls/ChannelManager.cpp",
"tgcalls/tgcalls/CodecSelectHelper.cpp",
"tgcalls/tgcalls/CryptoHelper.cpp",
"tgcalls/tgcalls/EncryptedConnection.cpp",
"tgcalls/tgcalls/FakeAudioDeviceModule.cpp",
"tgcalls/tgcalls/FakeVideoTrackSource.cpp",
"tgcalls/tgcalls/FieldTrialsConfig.cpp",
"tgcalls/tgcalls/Instance.cpp",
"tgcalls/tgcalls/InstanceImpl.cpp",
"tgcalls/tgcalls/LogSinkImpl.cpp",
"tgcalls/tgcalls/Message.cpp",
"tgcalls/tgcalls/NetworkManager.cpp",
"tgcalls/tgcalls/SctpDataChannelProviderInterfaceImpl.cpp",
"tgcalls/tgcalls/StaticThreads.cpp",
"tgcalls/tgcalls/ThreadLocalObject.cpp",
"tgcalls/tgcalls/TurnCustomizerImpl.cpp",
"tgcalls/tgcalls/VideoCaptureInterface.cpp",
"tgcalls/tgcalls/VideoCaptureInterfaceImpl.cpp",
"tgcalls/tgcalls/group/AudioStreamingPart.cpp",
"tgcalls/tgcalls/group/AudioStreamingPartInternal.cpp",
"tgcalls/tgcalls/group/AudioStreamingPartPersistentDecoder.cpp",
"tgcalls/tgcalls/group/AVIOContextImpl.cpp",
"tgcalls/tgcalls/group/GroupInstanceCustomImpl.cpp",
"tgcalls/tgcalls/group/GroupJoinPayloadInternal.cpp",
"tgcalls/tgcalls/group/GroupNetworkManager.cpp",
"tgcalls/tgcalls/group/StreamingMediaContext.cpp",
"tgcalls/tgcalls/group/VideoStreamingPart.cpp",
"tgcalls/tgcalls/v2/ContentNegotiation.cpp",
"tgcalls/tgcalls/v2/DirectNetworkingImpl.cpp",
"tgcalls/tgcalls/v2/ExternalSignalingConnection.cpp",
"tgcalls/tgcalls/v2/InstanceV2Impl.cpp",
"tgcalls/tgcalls/v2/InstanceV2ReferenceImpl.cpp",
"tgcalls/tgcalls/v2/NativeNetworkingImpl.cpp",
"tgcalls/tgcalls/v2/RawTcpSocket.cpp",
"tgcalls/tgcalls/v2/ReflectorPort.cpp",
"tgcalls/tgcalls/v2/ReflectorRelayPortFactory.cpp",
"tgcalls/tgcalls/v2/Signaling.cpp",
"tgcalls/tgcalls/v2/SignalingConnection.cpp",
"tgcalls/tgcalls/v2/SignalingEncryption.cpp",
"tgcalls/tgcalls/v2/SignalingSctpConnection.cpp",
]
objc_library(
name = "TgVoipWebrtc",
enable_modules = True,
module_name = "TgVoipWebrtc",
srcs = sources,
hdrs = glob([
"PublicHeaders/**/*.h",
]),
copts = [
"-I{}/tgcalls/tgcalls".format(package_name()),
"-Ithird-party/webrtc/webrtc",
"-Ithird-party/webrtc/dependencies",
"-Ithird-party/webrtc/webrtc/sdk/objc",
"-Ithird-party/webrtc/webrtc/sdk/objc/base",
"-Ithird-party/webrtc/webrtc/sdk/objc/components/renderer/metal",
"-Ithird-party/webrtc/webrtc/sdk/objc/components/renderer/opengl",
"-Ithird-party/webrtc/webrtc/sdk/objc/components/video_codec",
"-Ithird-party/libyuv",
"-Ithird-party/webrtc/webrtc/sdk/objc/api/video_codec",
"-Ithird-party/webrtc/absl",
"-DWEBRTC_IOS",
"-DWEBRTC_MAC",
"-DWEBRTC_POSIX",
"-DRTC_ENABLE_VP9",
"-DTGVOIP_NAMESPACE=tgvoip_webrtc",
"-std=c++17",
"-w",
#"-Werror",
] + optimization_flags,
includes = [
"PublicHeaders",
],
deps = [
"//third-party/webrtc",
"//third-party/webrtc:webrtc_objc",
"//submodules/MtProtoKit:MtProtoKit",
"//third-party/boringssl:crypto",
"//third-party/boringssl:ssl",
"//third-party/ogg:ogg",
"//third-party/opusfile:opusfile",
"//submodules/ffmpeg:ffmpeg",
"//third-party/rnnoise:rnnoise",
"//third-party/libyuv",
"//third-party/td:TdBinding",
],
sdk_frameworks = [
"Foundation",
"UIKit",
"AudioToolbox",
"VideoToolbox",
"CoreTelephony",
"CoreMedia",
"GLKit",
"AVFoundation",
],
sdk_dylibs = [
"libz",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,23 @@
#ifndef TgVoipWebrtc_MediaStreaming_h
#define TgVoipWebrtc_MediaStreaming_h
#import <Foundation/Foundation.h>
#import <TgVoipWebrtc/OngoingCallThreadLocalContext.h>
@interface MediaStreamingContext : NSObject
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue
requestCurrentTime:(id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull)(void (^ _Nonnull)(int64_t)))requestAudioBroadcastPart
requestAudioBroadcastPart:(id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull)(int64_t, int64_t, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestAudioBroadcastPart
requestVideoBroadcastPart:(id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull)(int64_t, int64_t, int32_t, OngoingGroupCallRequestedVideoQuality, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestVideoBroadcastPart;
- (void)start;
- (void)stop;
- (GroupCallDisposable * _Nonnull)addVideoOutput:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink;
- (void)getAudio:(int16_t * _Nonnull)audioSamples numSamples:(NSInteger)numSamples numChannels:(NSInteger)numChannels samplesPerSecond:(NSInteger)samplesPerSecond;
@end
#endif
@@ -0,0 +1,496 @@
#ifndef OngoingCallContext_h
#define OngoingCallContext_h
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#import <CoreMedia/CoreMedia.h>
#else
#import <AppKit/AppKit.h>
#define UIView NSView
#endif
@interface OngoingCallConnectionDescription : NSObject
@property (nonatomic, readonly) int64_t connectionId;
@property (nonatomic, strong, readonly) NSString * _Nonnull ip;
@property (nonatomic, strong, readonly) NSString * _Nonnull ipv6;
@property (nonatomic, readonly) int32_t port;
@property (nonatomic, strong, readonly) NSData * _Nonnull peerTag;
- (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId ip:(NSString * _Nonnull)ip ipv6:(NSString * _Nonnull)ipv6 port:(int32_t)port peerTag:(NSData * _Nonnull)peerTag;
@end
@protocol OngoingCallThreadLocalContextQueue <NSObject>
- (void)dispatch:(void (^ _Nonnull)())f;
- (bool)isCurrent;
@end
@interface VoipProxyServer : NSObject
@property (nonatomic, strong, readonly) NSString * _Nonnull host;
@property (nonatomic, readonly) int32_t port;
@property (nonatomic, strong, readonly) NSString * _Nullable username;
@property (nonatomic, strong, readonly) NSString * _Nullable password;
- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password;
@end
@interface CallAudioTone : NSObject
@property (nonatomic, strong, readonly) NSData * _Nonnull samples;
@property (nonatomic, readonly) NSInteger sampleRate;
@property (nonatomic, readonly) NSInteger loopCount;
- (instancetype _Nonnull)initWithSamples:(NSData * _Nonnull)samples sampleRate:(NSInteger)sampleRate loopCount:(NSInteger)loopCount;
@end
@interface SharedCallAudioDevice : NSObject
- (instancetype _Nonnull)initWithDisableRecording:(bool)disableRecording enableSystemMute:(bool)enableSystemMute;
+ (void)setupAudioSession;
- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive;
- (void)setTone:(CallAudioTone * _Nullable)tone;
@end
@interface OngoingCallConnectionDescriptionWebrtc : NSObject
@property (nonatomic, readonly) uint8_t reflectorId;
@property (nonatomic, readonly) bool hasStun;
@property (nonatomic, readonly) bool hasTurn;
@property (nonatomic, readonly) bool hasTcp;
@property (nonatomic, strong, readonly) NSString * _Nonnull ip;
@property (nonatomic, readonly) int32_t port;
@property (nonatomic, strong, readonly) NSString * _Nonnull username;
@property (nonatomic, strong, readonly) NSString * _Nonnull password;
- (instancetype _Nonnull)initWithReflectorId:(uint8_t)reflectorId hasStun:(bool)hasStun hasTurn:(bool)hasTurn hasTcp:(bool)hasTcp ip:(NSString * _Nonnull)ip port:(int32_t)port username:(NSString * _Nonnull)username password:(NSString * _Nonnull)password;
@end
typedef NS_ENUM(int32_t, OngoingCallStateWebrtc) {
OngoingCallStateInitializing,
OngoingCallStateConnected,
OngoingCallStateFailed,
OngoingCallStateReconnecting
};
typedef NS_ENUM(int32_t, OngoingCallVideoStateWebrtc) {
OngoingCallVideoStateInactive,
OngoingCallVideoStateActive,
OngoingCallVideoStatePaused
};
typedef NS_ENUM(int32_t, OngoingCallRemoteVideoStateWebrtc) {
OngoingCallRemoteVideoStateInactive,
OngoingCallRemoteVideoStateActive,
OngoingCallRemoteVideoStatePaused
};
typedef NS_ENUM(int32_t, OngoingCallRemoteAudioStateWebrtc) {
OngoingCallRemoteAudioStateMuted,
OngoingCallRemoteAudioStateActive,
};
typedef NS_ENUM(int32_t, OngoingCallRemoteBatteryLevelWebrtc) {
OngoingCallRemoteBatteryLevelNormal,
OngoingCallRemoteBatteryLevelLow
};
typedef NS_ENUM(int32_t, OngoingCallVideoOrientationWebrtc) {
OngoingCallVideoOrientation0,
OngoingCallVideoOrientation90,
OngoingCallVideoOrientation180,
OngoingCallVideoOrientation270
};
typedef NS_ENUM(int32_t, OngoingCallNetworkTypeWebrtc) {
OngoingCallNetworkTypeWifi,
OngoingCallNetworkTypeCellularGprs,
OngoingCallNetworkTypeCellularEdge,
OngoingCallNetworkTypeCellular3g,
OngoingCallNetworkTypeCellularLte
};
typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
OngoingCallDataSavingNever,
OngoingCallDataSavingCellular,
OngoingCallDataSavingAlways
};
@interface GroupCallDisposable : NSObject
- (instancetype _Nonnull)initWithBlock:(dispatch_block_t _Nonnull)block;
- (void)dispose;
@end
@protocol OngoingCallThreadLocalContextQueueWebrtc <NSObject>
- (void)dispatch:(void (^ _Nonnull)())f;
- (bool)isCurrent;
- (GroupCallDisposable * _Nonnull)scheduleBlock:(void (^ _Nonnull)())f after:(double)timeout;
@end
@interface VoipProxyServerWebrtc : NSObject
@property (nonatomic, strong, readonly) NSString * _Nonnull host;
@property (nonatomic, readonly) int32_t port;
@property (nonatomic, strong, readonly) NSString * _Nullable username;
@property (nonatomic, strong, readonly) NSString * _Nullable password;
- (instancetype _Nonnull)initWithHost:(NSString * _Nonnull)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password;
@end
@protocol OngoingCallThreadLocalContextWebrtcVideoView <NSObject>
@property (nonatomic, readonly) OngoingCallVideoOrientationWebrtc orientation;
@property (nonatomic, readonly) CGFloat aspect;
- (void)setOnFirstFrameReceived:(void (^ _Nullable)(float))onFirstFrameReceived;
- (void)setOnOrientationUpdated:(void (^ _Nullable)(OngoingCallVideoOrientationWebrtc, CGFloat))onOrientationUpdated;
- (void)setOnIsMirroredUpdated:(void (^ _Nullable)(bool))onIsMirroredUpdated;
- (void)updateIsEnabled:(bool)isEnabled;
#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
- (void)setVideoContentMode:(CALayerContentsGravity _Nonnull )mode;
- (void)setForceMirrored:(bool)forceMirrored;
- (void)setIsPaused:(bool)paused;
- (void)renderToSize:(NSSize)size animated: (bool)animated;
#endif
@end
@protocol CallVideoFrameBuffer
@end
@interface CallVideoFrameNativePixelBuffer : NSObject<CallVideoFrameBuffer>
@property (nonatomic, readonly) CVPixelBufferRef _Nonnull pixelBuffer;
@end
@interface CallVideoFrameNV12Buffer : NSObject<CallVideoFrameBuffer>
@property (nonatomic, readonly) int width;
@property (nonatomic, readonly) int height;
@property (nonatomic, strong, readonly) NSData * _Nonnull y;
@property (nonatomic, readonly) int strideY;
@property (nonatomic, strong, readonly) NSData * _Nonnull uv;
@property (nonatomic, readonly) int strideUV;
@end
@interface CallVideoFrameI420Buffer : NSObject<CallVideoFrameBuffer>
@property (nonatomic, readonly) int width;
@property (nonatomic, readonly) int height;
@property (nonatomic, strong, readonly) NSData * _Nonnull y;
@property (nonatomic, readonly) int strideY;
@property (nonatomic, strong, readonly) NSData * _Nonnull u;
@property (nonatomic, readonly) int strideU;
@property (nonatomic, strong, readonly) NSData * _Nonnull v;
@property (nonatomic, readonly) int strideV;
@end
@interface CallVideoFrameData : NSObject
@property (nonatomic, strong, readonly) id<CallVideoFrameBuffer> _Nonnull buffer;
@property (nonatomic, readonly) int width;
@property (nonatomic, readonly) int height;
@property (nonatomic, readonly) OngoingCallVideoOrientationWebrtc orientation;
@property (nonatomic, readonly) bool hasDeviceRelativeOrientation;
@property (nonatomic, readonly) OngoingCallVideoOrientationWebrtc deviceRelativeOrientation;
@property (nonatomic, readonly) bool mirrorHorizontally;
@property (nonatomic, readonly) bool mirrorVertically;
@end
@interface OngoingCallThreadLocalContextVideoCapturer : NSObject
- (instancetype _Nonnull)initWithDeviceId:(NSString * _Nonnull)deviceId keepLandscape:(bool)keepLandscape;
#if TARGET_OS_IOS
+ (instancetype _Nonnull)capturerWithExternalSampleBufferProvider;
#endif
- (void)switchVideoInput:(NSString * _Nonnull)deviceId;
- (void)setIsVideoEnabled:(bool)isVideoEnabled;
- (void)makeOutgoingVideoView:(bool)requestClone completion:(void (^_Nonnull)(UIView<OngoingCallThreadLocalContextWebrtcVideoView> * _Nullable, UIView<OngoingCallThreadLocalContextWebrtcVideoView> * _Nullable))completion;
- (void)setOnFatalError:(dispatch_block_t _Nullable)onError;
- (void)setOnPause:(void (^ _Nullable)(bool))onPause;
- (void)setOnIsActiveUpdated:(void (^ _Nonnull)(bool))onIsActiveUpdated;
#if TARGET_OS_IOS
- (void)submitSampleBuffer:(CMSampleBufferRef _Nonnull)sampleBuffer rotation:(OngoingCallVideoOrientationWebrtc)rotation completion:(void (^_Nonnull)())completion;
#endif
- (GroupCallDisposable * _Nonnull)addVideoOutput:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink;
@end
@protocol OngoingCallDirectConnection <NSObject>
- (NSData * _Nonnull)addOnIncomingPacket:(void (^_Nonnull)(NSData * _Nonnull))addOnIncomingPacket;
- (void)removeOnIncomingPacket:(NSData * _Nonnull)token;
- (void)sendPacket:(NSData * _Nonnull)packet;
@end
@interface OngoingCallThreadLocalContextWebrtc : NSObject
+ (void)logMessage:(NSString * _Nonnull)string;
+ (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction;
+ (void)applyServerConfig:(NSString * _Nullable)data;
+ (int32_t)maxLayer;
+ (NSArray<NSString *> * _Nonnull)versionsWithIncludeReference:(bool)includeReference;
+ (void)setupAudioSession;
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc, OngoingCallRemoteAudioStateWebrtc, OngoingCallRemoteBatteryLevelWebrtc, float);
@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t);
@property (nonatomic, copy) void (^ _Nullable audioLevelUpdated)(float);
- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version
customParameters:(NSString * _Nullable)customParameters
queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue
proxy:(VoipProxyServerWebrtc * _Nullable)proxy
networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving
derivedState:(NSData * _Nonnull)derivedState
key:(NSData * _Nonnull)key
isOutgoing:(bool)isOutgoing
connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer
allowP2P:(BOOL)allowP2P
allowTCP:(BOOL)allowTCP
enableStunMarking:(BOOL)enableStunMarking
logPath:(NSString * _Nonnull)logPath
statsLogPath:(NSString * _Nonnull)statsLogPath
sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer
preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec
audioInputDeviceId:(NSString * _Nonnull)audioInputDeviceId
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
directConnection:(id<OngoingCallDirectConnection> _Nullable)directConnection;
- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive;
- (void)beginTermination;
- (void)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion;
- (bool)needRate;
- (NSString * _Nullable)debugInfo;
- (NSString * _Nullable)version;
- (NSData * _Nonnull)getDerivedState;
- (void)setIsMuted:(bool)isMuted;
- (void)setIsLowBatteryLevel:(bool)isLowBatteryLevel;
- (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType;
- (GroupCallDisposable * _Nonnull)addVideoOutputWithIsIncoming:(bool)isIncoming sink:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink;
- (void)makeIncomingVideoView:(void (^_Nonnull)(UIView<OngoingCallThreadLocalContextWebrtcVideoView> * _Nullable))completion;
- (void)requestVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer;
- (void)setRequestedVideoAspect:(float)aspect;
- (void)disableVideo;
- (void)addSignalingData:(NSData * _Nonnull)data;
- (void)switchAudioOutput:(NSString * _Nonnull)deviceId;
- (void)switchAudioInput:(NSString * _Nonnull)deviceId;
- (void)addExternalAudioData:(NSData * _Nonnull)data;
- (void)deactivateIncomingAudio;
@end
typedef struct {
bool isConnected;
bool isTransitioningFromBroadcastToRtc;
bool isBroadcast;
} GroupCallNetworkState;
typedef NS_ENUM(int32_t, OngoingGroupCallMediaChannelType) {
OngoingGroupCallMediaChannelTypeAudio,
OngoingGroupCallMediaChannelTypeVideo
};
@interface OngoingGroupCallMediaChannelDescription : NSObject
@property (nonatomic, readonly) OngoingGroupCallMediaChannelType type;
@property (nonatomic, readonly) uint64_t peerId;
@property (nonatomic, readonly) uint32_t audioSsrc;
@property (nonatomic, strong, readonly) NSString * _Nullable videoDescription;
- (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type
peerId:(int64_t)peerId
audioSsrc:(uint32_t)audioSsrc
videoDescription:(NSString * _Nullable)videoDescription;
@end
@protocol OngoingGroupCallBroadcastPartTask <NSObject>
- (void)cancel;
@end
@protocol OngoingGroupCallMediaChannelDescriptionTask <NSObject>
- (void)cancel;
@end
typedef NS_ENUM(int32_t, OngoingCallConnectionMode) {
OngoingCallConnectionModeNone,
OngoingCallConnectionModeRtc,
OngoingCallConnectionModeBroadcast
};
typedef NS_ENUM(int32_t, OngoingGroupCallBroadcastPartStatus) {
OngoingGroupCallBroadcastPartStatusSuccess,
OngoingGroupCallBroadcastPartStatusNotReady,
OngoingGroupCallBroadcastPartStatusResyncNeeded
};
typedef NS_ENUM(int32_t, OngoingGroupCallVideoContentType) {
OngoingGroupCallVideoContentTypeNone,
OngoingGroupCallVideoContentTypeGeneric,
OngoingGroupCallVideoContentTypeScreencast,
};
@interface OngoingGroupCallBroadcastPart : NSObject
@property (nonatomic, readonly) int64_t timestampMilliseconds;
@property (nonatomic, readonly) double responseTimestamp;
@property (nonatomic, readonly) OngoingGroupCallBroadcastPartStatus status;
@property (nonatomic, strong, readonly) NSData * _Nonnull oggData;
- (instancetype _Nonnull)initWithTimestampMilliseconds:(int64_t)timestampMilliseconds responseTimestamp:(double)responseTimestamp status:(OngoingGroupCallBroadcastPartStatus)status oggData:(NSData * _Nonnull)oggData;
@end
typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) {
OngoingGroupCallRequestedVideoQualityThumbnail,
OngoingGroupCallRequestedVideoQualityMedium,
OngoingGroupCallRequestedVideoQualityFull,
};
@interface OngoingGroupCallSsrcGroup : NSObject
@property (nonatomic, strong, readonly) NSString * _Nonnull semantics;
@property (nonatomic, strong, readonly) NSArray<NSNumber *> * _Nonnull ssrcs;
- (instancetype _Nonnull)initWithSemantics:(NSString * _Nonnull)semantics ssrcs:(NSArray<NSNumber *> * _Nonnull)ssrcs;
@end
@interface OngoingGroupCallRequestedVideoChannel : NSObject
@property (nonatomic, readonly) uint32_t audioSsrc;
@property (nonatomic, readonly) int64_t userId;
@property (nonatomic, strong, readonly) NSString * _Nonnull endpointId;
@property (nonatomic, strong, readonly) NSArray<OngoingGroupCallSsrcGroup *> * _Nonnull ssrcGroups;
@property (nonatomic, readonly) OngoingGroupCallRequestedVideoQuality minQuality;
@property (nonatomic, readonly) OngoingGroupCallRequestedVideoQuality maxQuality;
- (instancetype _Nonnull)initWithAudioSsrc:(uint32_t)audioSsrc userId:(int64_t)userId endpointId:(NSString * _Nonnull)endpointId ssrcGroups:(NSArray<OngoingGroupCallSsrcGroup *> * _Nonnull)ssrcGroups minQuality:(OngoingGroupCallRequestedVideoQuality)minQuality maxQuality:(OngoingGroupCallRequestedVideoQuality)maxQuality;
@end
@interface OngoingGroupCallIncomingVideoStats : NSObject
@property (nonatomic, readonly) int receivingQuality;
@property (nonatomic, readonly) int availableQuality;
- (instancetype _Nonnull)initWithReceivingQuality:(int)receivingQuality availableQuality:(int)availableQuality;
@end
@interface OngoingGroupCallStats : NSObject
@property (nonatomic, strong, readonly) NSDictionary<NSString *, OngoingGroupCallIncomingVideoStats *> * _Nonnull incomingVideoStats;
- (instancetype _Nonnull)initWithIncomingVideoStats:(NSDictionary<NSString *, OngoingGroupCallIncomingVideoStats *> * _Nonnull)incomingVideoStats;
@end
@interface GroupCallThreadLocalContext : NSObject
@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t);
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue
networkStateUpdated:(void (^ _Nonnull)(GroupCallNetworkState))networkStateUpdated
audioLevelsUpdated:(void (^ _Nonnull)(NSArray<NSNumber *> * _Nonnull))audioLevelsUpdated
activityUpdated:(void (^ _Nonnull)(NSArray<NSNumber *> * _Nonnull))activityUpdated
inputDeviceId:(NSString * _Nonnull)inputDeviceId
outputDeviceId:(NSString * _Nonnull)outputDeviceId
videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer
requestMediaChannelDescriptions:(id<OngoingGroupCallMediaChannelDescriptionTask> _Nonnull (^ _Nonnull)(NSArray<NSNumber *> * _Nonnull, void (^ _Nonnull)(NSArray<OngoingGroupCallMediaChannelDescription *> * _Nonnull)))requestMediaChannelDescriptions
requestCurrentTime:(id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull)(void (^ _Nonnull)(int64_t)))requestAudioBroadcastPart
requestAudioBroadcastPart:(id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull)(int64_t, int64_t, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestAudioBroadcastPart
requestVideoBroadcastPart:(id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull)(int64_t, int64_t, int32_t, OngoingGroupCallRequestedVideoQuality, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestVideoBroadcastPart
outgoingAudioBitrateKbit:(int32_t)outgoingAudioBitrateKbit
videoContentType:(OngoingGroupCallVideoContentType)videoContentType
enableNoiseSuppression:(bool)enableNoiseSuppression
disableAudioInput:(bool)disableAudioInput
enableSystemMute:(bool)enableSystemMute
prioritizeVP8:(bool)prioritizeVP8
logPath:(NSString * _Nonnull)logPath
statsLogPath:(NSString * _Nonnull)statsLogPath
onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDetected
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
isConference:(bool)isConference
isActiveByDefault:(bool)isActiveByDefault
encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, int64_t, bool, int32_t))encryptDecrypt;
- (void)stop:(void (^ _Nullable)())completion;
- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive;
- (void)setTone:(CallAudioTone * _Nullable)tone;
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled isUnifiedBroadcast:(bool)isUnifiedBroadcast;
- (void)emitJoinPayload:(void (^ _Nonnull)(NSString * _Nonnull, uint32_t))completion;
- (void)setJoinResponsePayload:(NSString * _Nonnull)payload;
- (void)removeSsrcs:(NSArray<NSNumber *> * _Nonnull)ssrcs;
- (void)removeIncomingVideoSource:(uint32_t)ssrc;
- (void)setIsMuted:(bool)isMuted;
- (void)setIsNoiseSuppressionEnabled:(bool)isNoiseSuppressionEnabled;
- (void)requestVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer completion:(void (^ _Nonnull)(NSString * _Nonnull, uint32_t))completion;
- (void)disableVideo:(void (^ _Nonnull)(NSString * _Nonnull, uint32_t))completion;
- (void)setVolumeForSsrc:(uint32_t)ssrc volume:(double)volume;
- (void)setRequestedVideoChannels:(NSArray<OngoingGroupCallRequestedVideoChannel *> * _Nonnull)requestedVideoChannels;
- (void)switchAudioOutput:(NSString * _Nonnull)deviceId;
- (void)switchAudioInput:(NSString * _Nonnull)deviceId;
- (void)makeIncomingVideoViewWithEndpointId:(NSString * _Nonnull)endpointId requestClone:(bool)requestClone completion:(void (^_Nonnull)(UIView<OngoingCallThreadLocalContextWebrtcVideoView> * _Nullable, UIView<OngoingCallThreadLocalContextWebrtcVideoView> * _Nullable))completion;
- (GroupCallDisposable * _Nonnull)addVideoOutputWithEndpointId:(NSString * _Nonnull)endpointId sink:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink;
- (void)addExternalAudioData:(NSData * _Nonnull)data;
- (void)getStats:(void (^ _Nonnull)(OngoingGroupCallStats * _Nonnull))completion;
- (void)activateIncomingAudio;
@end
#endif
@@ -0,0 +1,275 @@
#import <TgVoipWebrtc/MediaStreaming.h>
#import "MediaUtils.h"
#include "StaticThreads.h"
#include "group/StreamingMediaContext.h"
#include "api/video/video_sink_interface.h"
#include "sdk/objc/native/src/objc_frame_buffer.h"
#include "api/video/video_frame.h"
#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
#import "platform/darwin/TGRTCCVPixelBuffer.h"
#include <memory>
namespace {
class BroadcastPartTaskImpl : public tgcalls::BroadcastPartTask {
public:
BroadcastPartTaskImpl(id<OngoingGroupCallBroadcastPartTask> task) {
_task = task;
}
virtual ~BroadcastPartTaskImpl() {
}
virtual void cancel() override {
[_task cancel];
}
private:
id<OngoingGroupCallBroadcastPartTask> _task;
};
class VideoSinkAdapter : public rtc::VideoSinkInterface<webrtc::VideoFrame> {
public:
VideoSinkAdapter(void (^frameReceived)(webrtc::VideoFrame const &)) {
_frameReceived = [frameReceived copy];
}
void OnFrame(const webrtc::VideoFrame& nativeVideoFrame) override {
@autoreleasepool {
if (_frameReceived) {
_frameReceived(nativeVideoFrame);
}
}
}
private:
void (^_frameReceived)(webrtc::VideoFrame const &);
};
}
@interface MediaStreamingVideoSink : NSObject {
std::shared_ptr<VideoSinkAdapter> _adapter;
}
@end
@implementation MediaStreamingVideoSink
- (instancetype)initWithSink:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink {
self = [super init];
if (self != nil) {
void (^storedSink)(CallVideoFrameData * _Nonnull) = [sink copy];
_adapter.reset(new VideoSinkAdapter(^(webrtc::VideoFrame const &videoFrame) {
id<CallVideoFrameBuffer> mappedBuffer = nil;
bool mirrorHorizontally = false;
bool mirrorVertically = false;
if (videoFrame.video_frame_buffer()->type() == webrtc::VideoFrameBuffer::Type::kNative) {
id<RTC_OBJC_TYPE(RTCVideoFrameBuffer)> nativeBuffer = static_cast<webrtc::ObjCFrameBuffer *>(videoFrame.video_frame_buffer().get())->wrapped_frame_buffer();
if ([nativeBuffer isKindOfClass:[RTC_OBJC_TYPE(RTCCVPixelBuffer) class]]) {
RTCCVPixelBuffer *pixelBuffer = (RTCCVPixelBuffer *)nativeBuffer;
mappedBuffer = [[CallVideoFrameNativePixelBuffer alloc] initWithPixelBuffer:pixelBuffer.pixelBuffer];
}
if ([nativeBuffer isKindOfClass:[TGRTCCVPixelBuffer class]]) {
if (((TGRTCCVPixelBuffer *)nativeBuffer).shouldBeMirrored) {
switch (videoFrame.rotation()) {
case webrtc::kVideoRotation_0:
case webrtc::kVideoRotation_180:
mirrorHorizontally = true;
break;
case webrtc::kVideoRotation_90:
case webrtc::kVideoRotation_270:
mirrorVertically = true;
break;
default:
break;
}
}
}
} else if (videoFrame.video_frame_buffer()->type() == webrtc::VideoFrameBuffer::Type::kNV12) {
rtc::scoped_refptr<webrtc::NV12BufferInterface> nv12Buffer(static_cast<webrtc::NV12BufferInterface *>(videoFrame.video_frame_buffer().get()));
mappedBuffer = [[CallVideoFrameNV12Buffer alloc] initWithBuffer:nv12Buffer];
} else if (videoFrame.video_frame_buffer()->type() == webrtc::VideoFrameBuffer::Type::kI420) {
rtc::scoped_refptr<webrtc::I420BufferInterface> i420Buffer(static_cast<webrtc::I420BufferInterface *>(videoFrame.video_frame_buffer().get()));
mappedBuffer = [[CallVideoFrameI420Buffer alloc] initWithBuffer:i420Buffer];
}
if (storedSink && mappedBuffer) {
storedSink([[CallVideoFrameData alloc] initWithBuffer:mappedBuffer frame:videoFrame mirrorHorizontally:mirrorHorizontally mirrorVertically:mirrorVertically hasDeviceRelativeVideoRotation:false deviceRelativeVideoRotation:OngoingCallVideoOrientation0]);
}
}));
}
return self;
}
- (std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>>)sink {
return _adapter;
}
@end
@interface MediaStreamingContext () {
id<OngoingCallThreadLocalContextQueueWebrtc> _queue;
id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull _requestCurrentTime)(void (^ _Nonnull)(int64_t));
id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull _requestAudioBroadcastPart)(int64_t, int64_t, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable));
id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull _requestVideoBroadcastPart)(int64_t, int64_t, int32_t, OngoingGroupCallRequestedVideoQuality, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable));
std::unique_ptr<tgcalls::StreamingMediaContext> _context;
int _nextSinkId;
NSMutableDictionary<NSNumber *, MediaStreamingVideoSink *> *_sinks;
}
@end
@implementation MediaStreamingContext
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue
requestCurrentTime:(id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull)(void (^ _Nonnull)(int64_t)))requestCurrentTime
requestAudioBroadcastPart:(id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull)(int64_t, int64_t, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestAudioBroadcastPart
requestVideoBroadcastPart:(id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull)(int64_t, int64_t, int32_t, OngoingGroupCallRequestedVideoQuality, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestVideoBroadcastPart {
self = [super init];
if (self != nil) {
_queue = queue;
_requestCurrentTime = [requestCurrentTime copy];
_requestAudioBroadcastPart = [requestAudioBroadcastPart copy];
_requestVideoBroadcastPart = [requestVideoBroadcastPart copy];
_sinks = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc {
}
- (void)resetContext {
tgcalls::StreamingMediaContext::StreamingMediaContextArguments arguments;
arguments.threads = tgcalls::StaticThreads::getThreads();
arguments.isUnifiedBroadcast = true;
arguments.requestCurrentTime = [requestCurrentTime = _requestCurrentTime](std::function<void(int64_t)> completion) -> std::shared_ptr<tgcalls::BroadcastPartTask> {
id<OngoingGroupCallBroadcastPartTask> task = requestCurrentTime(^(int64_t result) {
completion(result);
});
return std::make_shared<BroadcastPartTaskImpl>(task);
};
arguments.requestAudioBroadcastPart = nullptr;
arguments.requestVideoBroadcastPart = [requestVideoBroadcastPart = _requestVideoBroadcastPart](int64_t timestampMilliseconds, int64_t durationMilliseconds, int32_t channelId, tgcalls::VideoChannelDescription::Quality quality, std::function<void(tgcalls::BroadcastPart &&)> completion) -> std::shared_ptr<tgcalls::BroadcastPartTask> {
OngoingGroupCallRequestedVideoQuality mappedQuality;
switch (quality) {
case tgcalls::VideoChannelDescription::Quality::Thumbnail: {
mappedQuality = OngoingGroupCallRequestedVideoQualityThumbnail;
break;
}
case tgcalls::VideoChannelDescription::Quality::Medium: {
mappedQuality = OngoingGroupCallRequestedVideoQualityMedium;
break;
}
case tgcalls::VideoChannelDescription::Quality::Full: {
mappedQuality = OngoingGroupCallRequestedVideoQualityFull;
break;
}
default: {
mappedQuality = OngoingGroupCallRequestedVideoQualityThumbnail;
break;
}
}
id<OngoingGroupCallBroadcastPartTask> task = requestVideoBroadcastPart(timestampMilliseconds, durationMilliseconds, channelId, mappedQuality, ^(OngoingGroupCallBroadcastPart * _Nullable part) {
tgcalls::BroadcastPart parsedPart;
parsedPart.timestampMilliseconds = part.timestampMilliseconds;
parsedPart.responseTimestamp = part.responseTimestamp;
tgcalls::BroadcastPart::Status mappedStatus;
switch (part.status) {
case OngoingGroupCallBroadcastPartStatusSuccess: {
mappedStatus = tgcalls::BroadcastPart::Status::Success;
break;
}
case OngoingGroupCallBroadcastPartStatusNotReady: {
mappedStatus = tgcalls::BroadcastPart::Status::NotReady;
break;
}
case OngoingGroupCallBroadcastPartStatusResyncNeeded: {
mappedStatus = tgcalls::BroadcastPart::Status::ResyncNeeded;
break;
}
default: {
mappedStatus = tgcalls::BroadcastPart::Status::NotReady;
break;
}
}
parsedPart.status = mappedStatus;
parsedPart.data.resize(part.oggData.length);
[part.oggData getBytes:parsedPart.data.data() length:part.oggData.length];
completion(std::move(parsedPart));
});
return std::make_shared<BroadcastPartTaskImpl>(task);
};
arguments.updateAudioLevel = nullptr;
_context = std::make_unique<tgcalls::StreamingMediaContext>(std::move(arguments));
for (MediaStreamingVideoSink *storedSink in _sinks.allValues) {
_context->addVideoSink("unified", [storedSink sink]);
}
}
- (void)start {
[self resetContext];
}
- (void)stop {
_context.reset();
}
- (GroupCallDisposable * _Nonnull)addVideoOutput:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink {
int sinkId = _nextSinkId;
_nextSinkId += 1;
MediaStreamingVideoSink *storedSink = [[MediaStreamingVideoSink alloc] initWithSink:sink];
_sinks[@(sinkId)] = storedSink;
if (_context) {
_context->addVideoSink("unified", [storedSink sink]);
}
__weak MediaStreamingContext *weakSelf = self;
id<OngoingCallThreadLocalContextQueueWebrtc> queue = _queue;
return [[GroupCallDisposable alloc] initWithBlock:^{
[queue dispatch:^{
__strong MediaStreamingContext *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
[strongSelf->_sinks removeObjectForKey:@(sinkId)];
}];
}];
}
- (void)getAudio:(int16_t * _Nonnull)audioSamples numSamples:(NSInteger)numSamples numChannels:(NSInteger)numChannels samplesPerSecond:(NSInteger)samplesPerSecond {
if (_context) {
_context->getAudio(audioSamples, (size_t)numSamples, (size_t)numChannels, (uint32_t)samplesPerSecond);
} else {
memset(audioSamples, 0, numSamples * numChannels * sizeof(int16_t));
}
}
@end
@@ -0,0 +1,57 @@
#import <TgVoipWebrtc/OngoingCallThreadLocalContext.h>
#import "Instance.h"
#import "InstanceImpl.h"
#import "v2/InstanceV2Impl.h"
#include "StaticThreads.h"
#import "VideoCaptureInterface.h"
#import "platform/darwin/VideoCameraCapturer.h"
#ifndef WEBRTC_IOS
#import "platform/darwin/VideoMetalViewMac.h"
#import "platform/darwin/GLVideoViewMac.h"
#import "platform/darwin/VideoSampleBufferViewMac.h"
#define UIViewContentModeScaleAspectFill kCAGravityResizeAspectFill
#define UIViewContentModeScaleAspect kCAGravityResizeAspect
#else
#import "platform/darwin/VideoMetalView.h"
#import "platform/darwin/VideoSampleBufferView.h"
#import "platform/darwin/VideoCaptureView.h"
#import "platform/darwin/CustomExternalCapturer.h"
#endif
#import "group/GroupInstanceImpl.h"
#import "group/GroupInstanceCustomImpl.h"
#import "VideoCaptureInterfaceImpl.h"
#include "sdk/objc/native/src/objc_frame_buffer.h"
#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
#import "platform/darwin/TGRTCCVPixelBuffer.h"
@interface CallVideoFrameNativePixelBuffer (Initialization)
- (instancetype _Nonnull)initWithPixelBuffer:(CVPixelBufferRef _Nonnull)pixelBuffer;
@end
@interface CallVideoFrameI420Buffer (Initialization)
- (instancetype _Nonnull)initWithBuffer:(rtc::scoped_refptr<webrtc::I420BufferInterface>)i420Buffer;
@end
@interface CallVideoFrameNV12Buffer (Initialization)
- (instancetype _Nonnull)initWithBuffer:(rtc::scoped_refptr<webrtc::NV12BufferInterface>)nv12Buffer;
@end
@interface CallVideoFrameData (Initialization)
- (instancetype _Nonnull)initWithBuffer:(id<CallVideoFrameBuffer> _Nonnull)buffer frame:(webrtc::VideoFrame const &)frame mirrorHorizontally:(bool)mirrorHorizontally mirrorVertically:(bool)mirrorVertically hasDeviceRelativeVideoRotation:(bool)hasDeviceRelativeVideoRotation deviceRelativeVideoRotation:(OngoingCallVideoOrientationWebrtc)deviceRelativeVideoRotation;
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,705 @@
#import <XCTest/XCTest.h>
#include "api/task_queue/default_task_queue_factory.h"
#include "media/engine/webrtc_media_engine.h"
#include "api/audio_codecs/audio_encoder_factory_template.h"
#include "api/audio_codecs/audio_decoder_factory_template.h"
#include "api/audio_codecs/opus/audio_decoder_opus.h"
#include "api/audio_codecs/opus/audio_decoder_multi_channel_opus.h"
#include "api/audio_codecs/opus/audio_encoder_opus.h"
#include "api/audio_codecs/L16/audio_decoder_L16.h"
#include "api/audio_codecs/L16/audio_encoder_L16.h"
#include "StaticThreads.h"
#include "FakeAudioDeviceModule.h"
#include "platform/PlatformInterface.h"
#include "v2/ContentNegotiation.h"
namespace {
class Context {
public:
Context(bool isOutgoing) :
_isOutgoing(isOutgoing),
_threads(tgcalls::StaticThreads::getThreads()),
_taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()),
_uniqueRandomIdGenerator(std::make_unique<rtc::UniqueRandomIdGenerator>()) {
_threads->getWorkerThread()->Invoke<void>(RTC_FROM_HERE, [&]() {
cricket::MediaEngineDependencies mediaDeps;
mediaDeps.task_queue_factory = _taskQueueFactory.get();
mediaDeps.audio_encoder_factory = webrtc::CreateAudioEncoderFactory<webrtc::AudioEncoderOpus, webrtc::AudioEncoderL16>();
mediaDeps.audio_decoder_factory = webrtc::CreateAudioDecoderFactory<webrtc::AudioDecoderOpus, webrtc::AudioDecoderL16>();
mediaDeps.video_encoder_factory = tgcalls::PlatformInterface::SharedInstance()->makeVideoEncoderFactory(true);
mediaDeps.video_decoder_factory = tgcalls::PlatformInterface::SharedInstance()->makeVideoDecoderFactory();
tgcalls::FakeAudioDeviceModule::Options options;
options.num_channels = 1;
_audioDeviceModule = tgcalls::FakeAudioDeviceModule::Creator(nullptr, nullptr, options)(_taskQueueFactory.get());
mediaDeps.adm = _audioDeviceModule;
_mediaEngine = cricket::CreateMediaEngine(std::move(mediaDeps));
_channelManager = cricket::ChannelManager::Create(
std::move(_mediaEngine),
true,
_threads->getWorkerThread(),
_threads->getNetworkThread()
);
});
_contentNegotiationContext = std::make_unique<tgcalls::ContentNegotiationContext>(isOutgoing, _uniqueRandomIdGenerator.get());
_contentNegotiationContext->copyCodecsFromChannelManager(_channelManager.get(), isOutgoing);
}
~Context() {
_contentNegotiationContext.reset();
_threads->getWorkerThread()->Invoke<void>(RTC_FROM_HERE, [&]() {
_channelManager.reset();
_mediaEngine.reset();
_audioDeviceModule = nullptr;
});
}
public:
tgcalls::ContentNegotiationContext *contentNegotiationContext() const {
return _contentNegotiationContext.get();
}
void assertSsrcs(std::vector<uint32_t> const &outgoingSsrcs, std::vector<uint32_t> const &incomingSsrcs) {
std::set<uint32_t> incomingSsrcsSet;
for (auto ssrc : incomingSsrcs) {
incomingSsrcsSet.insert(ssrc);
}
std::set<uint32_t> outgoingSsrcsSet;
for (auto ssrc : outgoingSsrcs) {
outgoingSsrcsSet.insert(ssrc);
}
std::set<uint32_t> actualIncomingSsrcs;
std::set<uint32_t> actualOutgoingSsrcs;
auto coordinatedState = _contentNegotiationContext->coordinatedState();
XCTAssert(coordinatedState != nullptr);
for (const auto &content : coordinatedState->incomingContents) {
actualIncomingSsrcs.insert(content.ssrc);
}
for (const auto &content : coordinatedState->outgoingContents) {
actualOutgoingSsrcs.insert(content.ssrc);
}
XCTAssert(incomingSsrcsSet == actualIncomingSsrcs);
XCTAssert(outgoingSsrcsSet == actualOutgoingSsrcs);
}
bool isContentsEqualToRemote(Context &remoteContext) {
auto localCoordinatedState = _contentNegotiationContext->coordinatedState();
auto remoteCoordinatedState = remoteContext.contentNegotiationContext()->coordinatedState();
auto mediaContentComparator = [](tgcalls::signaling::MediaContent const &lhs, tgcalls::signaling::MediaContent const &rhs) -> bool {
return lhs.ssrc < rhs.ssrc;
};
auto localIncomingContents = localCoordinatedState->incomingContents;
std::sort(localIncomingContents.begin(), localIncomingContents.end(), mediaContentComparator);
auto localOutgoingContents = localCoordinatedState->outgoingContents;
std::sort(localOutgoingContents.begin(), localOutgoingContents.end(), mediaContentComparator);
auto remoteIncomingContents = remoteCoordinatedState->incomingContents;
std::sort(remoteIncomingContents.begin(), remoteIncomingContents.end(), mediaContentComparator);
auto remoteOutgoingContents = remoteCoordinatedState->outgoingContents;
std::sort(remoteOutgoingContents.begin(), remoteOutgoingContents.end(), mediaContentComparator);
if (localIncomingContents != remoteOutgoingContents) {
return false;
}
if (localOutgoingContents != remoteIncomingContents) {
return false;
}
return true;
}
private:
__unused bool _isOutgoing = false;
std::shared_ptr<tgcalls::Threads> _threads;
std::unique_ptr<webrtc::TaskQueueFactory> _taskQueueFactory;
std::unique_ptr<rtc::UniqueRandomIdGenerator> _uniqueRandomIdGenerator;
rtc::scoped_refptr<webrtc::AudioDeviceModule> _audioDeviceModule;
std::unique_ptr<cricket::MediaEngineInterface> _mediaEngine;
std::unique_ptr<cricket::ChannelManager> _channelManager;
std::unique_ptr<tgcalls::ContentNegotiationContext> _contentNegotiationContext;
};
std::unique_ptr<tgcalls::ContentNegotiationContext::NegotiationContents> copyNegotiationContents(tgcalls::ContentNegotiationContext::NegotiationContents *value) {
if (!value) {
return nullptr;
}
auto result = std::make_unique<tgcalls::ContentNegotiationContext::NegotiationContents>();
result->exchangeId = value->exchangeId;
result->contents = value->contents;
return result;
}
void runUntilStableSequential(Context &localContext, Context &remoteContext) {
for (int i = 0; i < 6; i++) {
auto localOffer = localContext.contentNegotiationContext()->getPendingOffer();
if (localOffer) {
auto remoteAnswer = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(localOffer.get()));
XCTAssert(remoteAnswer != nullptr);
auto localResponse = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(remoteAnswer.get()));
XCTAssert(localResponse == nullptr);
} else {
auto remoteOffer = remoteContext.contentNegotiationContext()->getPendingOffer();
if (remoteOffer) {
auto localAnswer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(remoteOffer.get()));
XCTAssert(localAnswer != nullptr);
auto remoteResponse = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(localAnswer.get()));
XCTAssert(remoteResponse == nullptr);
} else {
return;
}
}
}
XCTFail(@"Did not complete");
}
void runUntilStableConcurrent(Context &localContext, Context &remoteContext) {
std::vector<std::unique_ptr<tgcalls::ContentNegotiationContext::NegotiationContents>> localNegotiationContent;
std::vector<std::unique_ptr<tgcalls::ContentNegotiationContext::NegotiationContents>> remoteNegotiationContent;
for (int i = 0; i < 6; i++) {
std::unique_ptr<tgcalls::ContentNegotiationContext::NegotiationContents> nextLocalNegotiationContent;
std::unique_ptr<tgcalls::ContentNegotiationContext::NegotiationContents> nextRemoteNegotiationContent;
while (!localNegotiationContent.empty()) {
auto content = std::move(localNegotiationContent[0]);
localNegotiationContent.erase(localNegotiationContent.begin());
nextRemoteNegotiationContent = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(content.get()));
}
while (!remoteNegotiationContent.empty()) {
auto content = std::move(remoteNegotiationContent[0]);
remoteNegotiationContent.erase(remoteNegotiationContent.begin());
nextLocalNegotiationContent = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(content.get()));
}
if (nextLocalNegotiationContent) {
localNegotiationContent.push_back(std::move(nextLocalNegotiationContent));
}
if (nextRemoteNegotiationContent) {
remoteNegotiationContent.push_back(std::move(nextRemoteNegotiationContent));
}
auto localOffer = localContext.contentNegotiationContext()->getPendingOffer();
if (localOffer) {
localNegotiationContent.push_back(std::move(localOffer));
}
auto remoteOffer = remoteContext.contentNegotiationContext()->getPendingOffer();
if (remoteOffer) {
remoteNegotiationContent.push_back(std::move(remoteOffer));
}
if (localNegotiationContent.empty() && remoteNegotiationContent.empty()) {
return;
}
}
XCTFail(@"Did not complete");
}
}
@interface NegotiationTests : XCTestCase
@end
@implementation NegotiationTests
- (void)setUp {
[super setUp];
self.continueAfterFailure = false;
}
- (void)tearDown {
[super tearDown];
}
- (void)testNegotiateEmpty {
Context localContext(true);
Context remoteContext(false);
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() != nullptr);
XCTAssert(remoteContext.contentNegotiationContext()->getPendingOffer() != nullptr);
}
- (void)testNegotiateAudioOnewayOutgoing {
Context localContext(true);
Context remoteContext(false);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto offer = localContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(offer != nullptr);
XCTAssert(offer->contents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto response = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(offer.get()));
XCTAssert(response != nullptr);
XCTAssert(response->contents.size() == offer->contents.size());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto backOffer = remoteContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(backOffer != nullptr);
XCTAssert(backOffer->contents.size() == 0);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto responseToAnswer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(response.get()));
XCTAssert(responseToAnswer == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto nextOffer = localContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(nextOffer == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
}
- (void)testNegotiateAudioOnewayIncoming {
Context localContext(true);
Context remoteContext(false);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto offer = localContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(offer != nullptr);
XCTAssert(offer->contents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto response = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(offer.get()));
XCTAssert(response != nullptr);
XCTAssert(response->contents.size() == offer->contents.size());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto backOffer = remoteContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(backOffer != nullptr);
XCTAssert(backOffer->contents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto responseToAnswer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(response.get()));
XCTAssert(responseToAnswer == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto responseToBackOffer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(backOffer.get()));
XCTAssert(responseToBackOffer != nullptr);
XCTAssert(responseToBackOffer->contents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == backOffer->contents[0].ssrc);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto responseToBackOfferAnswer = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(responseToBackOffer.get()));
XCTAssert(responseToBackOfferAnswer == nullptr);
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(remoteContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == backOffer->contents[0].ssrc);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == backOffer->contents[0].ssrc);
}
- (void)testNegotiateAudioTwoway {
Context localContext(true);
Context remoteContext(false);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto offer = localContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(offer != nullptr);
XCTAssert(offer->contents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto response = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(offer.get()));
XCTAssert(response != nullptr);
XCTAssert(response->contents.size() == offer->contents.size());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto backOffer = remoteContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(backOffer != nullptr);
XCTAssert(backOffer->contents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto responseToAnswer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(response.get()));
XCTAssert(responseToAnswer == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() == nullptr);
auto responseToBackOffer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(backOffer.get()));
XCTAssert(responseToBackOffer != nullptr);
XCTAssert(responseToBackOffer->contents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == backOffer->contents[0].ssrc);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto responseToBackOfferAnswer = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(responseToBackOffer.get()));
XCTAssert(responseToBackOfferAnswer == nullptr);
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(remoteContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == backOffer->contents[0].ssrc);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == backOffer->contents[0].ssrc);
}
- (void)testConcurrentOffers {
Context localContext(true);
Context remoteContext(false);
auto localOffer = localContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(localOffer != nullptr);
auto remoteOffer = remoteContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(remoteOffer != nullptr);
auto localAnswer = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(localOffer.get()));
XCTAssert(localAnswer != nullptr);
XCTAssert(localAnswer->exchangeId == localOffer->exchangeId);
auto remoteAnswer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(remoteOffer.get()));
XCTAssert(remoteAnswer == nullptr);
}
- (void)service_runUntilStable1Using:(std::function<void(Context &localContext, Context &remoteContext)>)runUntilStable {
Context localContext(true);
Context remoteContext(false);
runUntilStable(localContext, remoteContext);
localContext.assertSsrcs({}, {});
remoteContext.assertSsrcs({}, {});
localContext.isContentsEqualToRemote(remoteContext);
auto localAudioId = localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
runUntilStable(localContext, remoteContext);
auto localAudioSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localAudioId);
XCTAssert(localAudioSsrc);
localContext.assertSsrcs({ localAudioSsrc.value() }, {});
remoteContext.assertSsrcs({}, { localAudioSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
auto remoteAudioId = remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
runUntilStable(localContext, remoteContext);
auto remoteAudioSsrc = remoteContext.contentNegotiationContext()->outgoingChannelSsrc(remoteAudioId);
XCTAssert(remoteAudioSsrc);
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value() }, { localAudioSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
auto remoteVideoId = remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
auto remoteVideoSsrc = remoteContext.contentNegotiationContext()->outgoingChannelSsrc(remoteVideoId);
XCTAssert(remoteVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value() }, { localAudioSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
auto localVideoId = localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
auto localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(localVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value(), localVideoSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
auto remoteScreencastId = remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
auto remoteScreencastSsrc = remoteContext.contentNegotiationContext()->outgoingChannelSsrc(remoteScreencastId);
XCTAssert(remoteScreencastSsrc);
localContext.assertSsrcs({ localAudioSsrc.value(), localVideoSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
localContext.contentNegotiationContext()->removeOutgoingChannel(localVideoId);
// local removal is reflected right away
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(!localVideoSsrc);
runUntilStable(localContext, remoteContext);
localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(!localVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
localVideoId = localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(localVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value(), localVideoSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
}
- (void)service_runUntilStable2Using:(std::function<void(Context &localContext, Context &remoteContext)>)runUntilStable {
Context localContext(true);
Context remoteContext(false);
runUntilStable(localContext, remoteContext);
localContext.assertSsrcs({}, {});
remoteContext.assertSsrcs({}, {});
localContext.isContentsEqualToRemote(remoteContext);
auto localAudioId = localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
auto remoteAudioId = remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
runUntilStable(localContext, remoteContext);
auto localAudioSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localAudioId);
XCTAssert(localAudioSsrc);
auto remoteAudioSsrc = remoteContext.contentNegotiationContext()->outgoingChannelSsrc(remoteAudioId);
XCTAssert(remoteAudioSsrc);
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value() }, { localAudioSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
auto remoteVideoId = remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
auto localVideoId = localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
auto remoteVideoSsrc = remoteContext.contentNegotiationContext()->outgoingChannelSsrc(remoteVideoId);
XCTAssert(remoteVideoSsrc);
auto localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(localVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value(), localVideoSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
auto remoteScreencastId = remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
auto remoteScreencastSsrc = remoteContext.contentNegotiationContext()->outgoingChannelSsrc(remoteScreencastId);
XCTAssert(remoteScreencastSsrc);
localContext.assertSsrcs({ localAudioSsrc.value(), localVideoSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
localContext.contentNegotiationContext()->removeOutgoingChannel(localVideoId);
// local removal is reflected right away
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(!localVideoSsrc);
runUntilStable(localContext, remoteContext);
localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(!localVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
localVideoId = localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(localVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value(), localVideoSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
}
- (void)testConvergenceSequential1 {
[self service_runUntilStable1Using:&runUntilStableSequential];
}
- (void)testConvergenceSequential2 {
[self service_runUntilStable2Using:&runUntilStableSequential];
}
- (void)testConvergenceConcurrent1 {
[self service_runUntilStable1Using:&runUntilStableConcurrent];
}
- (void)testConvergenceConcurrent2 {
[self service_runUntilStable2Using:&runUntilStableConcurrent];
}
@end