Mac/BasicDemo/BasicDemo/ChannelViewController.m (308 lines of code) (raw):

// // ChannelViewController.m // BasicDemo // // Copyright © 2024 DingTalk. All rights reserved. // #import "ChannelViewController.h" #import "ChannelInfo.h" #import "UserInfo.h" @interface ChannelViewController () <DingRtcEngineDelegate> @property (weak) IBOutlet NSView *remote1View; @property (weak) IBOutlet NSView *thumbnail1View; @property (weak) IBOutlet NSView *thumbnail2View; @property (weak) IBOutlet NSView *local1View; @property (weak) IBOutlet NSView *local2View; @property (weak) IBOutlet NSButton *sendAudio; @property (weak) IBOutlet NSButton *sendVideo; @property (weak) IBOutlet NSButton *sendScreen; @property (weak) IBOutlet NSTextField *message; @property (strong, nonatomic) DingRtcEngine * engine; @property (strong, nonatomic) UserManager * userManager; @end @implementation ChannelViewController - (void)viewDidLoad { [super viewDidLoad]; // Do view setup here. [self initUserManager]; [self createEngineKit]; [self joinChannel]; } - (void)viewDidAppear { [super viewDidAppear]; self.view.window.title = ChannelInfo.channelId; } - (IBAction)clickSendAudio:(id)sender { NSLog(@"click \"Send Audio\" button with %ld", self.sendAudio.state); BOOL isSend = (self.sendAudio.state == NSControlStateValueOn) ? YES : NO; [self.engine muteLocalMic:!isSend mode:DingRtcMuteAudioModeDefault]; } - (IBAction)clickSendVideo:(id)sender { NSLog(@"click \"Send Video\" button with %ld", self.sendVideo.state); BOOL isSend = (self.sendVideo.state == NSControlStateValueOn) ? YES : NO; if (isSend) { [self startVideo]; } else { [self stopVideo]; } } - (IBAction)clickSendScreen:(id)sender { NSLog(@"click \"Send Screen\" button with %ld", self.sendScreen.state); BOOL isSend = (self.sendScreen.state == NSControlStateValueOn) ? YES : NO; if (isSend) { [self startScreenShare]; } else { [self stopScreenShare]; } } - (IBAction)leaveChannel:(id)sender { NSLog(@"click \"Leave Channel\" button"); [self leaveChannel]; [self destroyEngineKit]; [self.view.window performClose:nil]; } - (void)onBye:(DingRtcOnByeType)code { dispatch_async(dispatch_get_main_queue(), ^{ [self displayMessage:[NSString stringWithFormat:@"Leave channel with indication: %ld.", (long)code]]; [self.view.window performClose:nil]; }); } - (void)onOccurError:(int)error message:(NSString *_Nonnull)message { dispatch_async(dispatch_get_main_queue(), ^{ [self displayMessage:[NSString stringWithFormat:@"Occur error: %ld, message:%@.", (long)error, message]]; }); } - (void)onOccurWarning:(int)warn message:(NSString *_Nonnull)message { dispatch_async(dispatch_get_main_queue(), ^{ [self displayMessage:[NSString stringWithFormat:@"Occur warning: %ld, message:%@.", (long)warn, message]]; }); } - (void)onRemoteUserOnLineNotify:(NSString *_Nonnull)uid elapsed:(int)elapsed { dispatch_async(dispatch_get_main_queue(), ^{ NSDictionary *infoDic = [self.engine getUserInfo:uid]; NSString *userName = infoDic[@"displayName"]; [self displayMessage:[NSString stringWithFormat:@"User %@(%@) join channel.", uid, userName]]; [self.userManager addUser:uid withName:userName]; }); } - (void)onRemoteUserOffLineNotify:(NSString *_Nonnull)uid offlineReason:(DingRtcUserOfflineReason)reason { dispatch_async(dispatch_get_main_queue(), ^{ UserInfo * user = [self.userManager removeUser:uid]; [self displayMessage:[NSString stringWithFormat:@"User %@(%@) leave channel with reason: %ld.", uid, user.userName, reason]]; if (user.videoView) { // Release render view [self stopRemoteVideo:uid withView:user.videoView]; // Find a waiting user and subscribe it. UserInfo * waitingUser = [self.userManager findWatingUser]; if (waitingUser) { waitingUser.videoView = user.videoView; [self subscribeVideo:waitingUser.userId withView:waitingUser.videoView]; } // Clear video view of the leave user. user.videoView = nil; } }); } - (void)onRemoteTrackAvailableNotify:(NSString *_Nonnull)uid audioTrack:(DingRtcAudioTrack)audioTrack videoTrack:(DingRtcVideoTrack)videoTrack { dispatch_async(dispatch_get_main_queue(), ^{ UserInfo * user = [self.userManager findUser:uid]; BOOL audioEnable = audioTrack != DingRtcAudioTrackNo; BOOL videoEnable = videoTrack == DingRtcVideoTrackBoth || videoTrack == DingRtcVideoTrackCamera; BOOL screenEnable = videoTrack == DingRtcVideoTrackBoth || videoTrack == DingRtcVideoTrackScreen; if (audioEnable != user.audioEnable) { user.audioEnable = audioEnable; if (audioEnable) { [self displayMessage:[NSString stringWithFormat:@"User %@(%@) start audio.", uid, user.userName]]; } else { [self displayMessage:[NSString stringWithFormat:@"User %@(%@) stop audio.", uid, user.userName]]; } } if (videoEnable != user.videoEnable) { user.videoEnable = videoEnable; if (videoEnable) { [self displayMessage:[NSString stringWithFormat:@"User %@(%@) start video.", uid, user.userName]]; user.videoView = [self findIdleVideoView]; if (user.videoView) { [self subscribeVideo:uid withView:user.videoView]; } } else { [self displayMessage:[NSString stringWithFormat:@"User %@(%@) stop video.", uid, user.userName]]; if (user.videoView) { // Release render view [self stopRemoteVideo:uid withView:user.videoView]; if (user.videoView == self.remote1View) { // Move thumbnail view user to big remote view if exist UserInfo *tmpUser = [self.userManager findUserWithView:self.thumbnail2View]; if (tmpUser) { [self stopRemoteVideo:tmpUser.userId withView:tmpUser.videoView]; tmpUser.videoView = self.remote1View; [self subscribeVideo:tmpUser.userId withView:tmpUser.videoView]; } user.videoView = self.thumbnail2View; } if (user.screenView) { // Release render screen share [self stopRemoteScreen:uid withView:user.screenView]; user.screenView = nil; } // Find a waiting user and subscribe it. UserInfo * waitingUser = [self.userManager findWatingUser]; if (waitingUser) { waitingUser.videoView = user.videoView; [self subscribeVideo:waitingUser.userId withView:waitingUser.videoView]; } // Clear video view of the stoped video user. user.videoView = nil; } } } if (screenEnable != user.screenEnable) { user.screenEnable = screenEnable; if (screenEnable) { [self displayMessage:[NSString stringWithFormat:@"User %@(%@) start screen share.", uid, user.userName]]; if (user.videoView == self.remote1View) { // Only display remote view user screen share [self subscribeScreen:uid withView:self.thumbnail1View]; user.screenView = self.thumbnail1View; } } else { [self displayMessage:[NSString stringWithFormat:@"User %@(%@) stop screen share.", uid, user.userName]]; if (user.screenView) { // Release render screen share [self stopRemoteScreen:uid withView:user.screenView]; user.screenView = nil; } } } }); } - (void)onUserAudioMuted:(NSString *_Nonnull)uid audioMuted:(BOOL)isMute { dispatch_async(dispatch_get_main_queue(), ^{ UserInfo * user = [self.userManager findUser:uid]; if (isMute) { [self displayMessage:[NSString stringWithFormat:@"User %@(%@) mute audio.", uid, user.userName]]; } else { [self displayMessage:[NSString stringWithFormat:@"User %@(%@) unmute audio.", uid, user.userName]]; } }); } - (void)onUserVideoMuted:(NSString *_Nonnull)uid videoMuted:(BOOL)isMute track:(DingRtcVideoTrack)track { dispatch_async(dispatch_get_main_queue(), ^{ UserInfo * user = [self.userManager findUser:uid]; if (isMute) { [self displayMessage:[NSString stringWithFormat:@"User %@(%@) mute video.", uid, user.userName]]; } else { [self displayMessage:[NSString stringWithFormat:@"User %@(%@) unmute video.", uid, user.userName]]; } }); } - (void)onAudioPublishStateChanged:(DingRtcPublishState)oldState newState:(DingRtcPublishState)newState elapseSinceLastState:(NSInteger)elapseSinceLastState channel:(NSString *_Nonnull)channel { dispatch_async(dispatch_get_main_queue(), ^{ [self displayMessage:[NSString stringWithFormat:@"Audio publish state changed with oldState: %ld newState: %ld.", (long)oldState, (long)newState]]; }); } - (void)onVideoPublishStateChanged:(DingRtcPublishState)oldState newState:(DingRtcPublishState)newState elapseSinceLastState:(NSInteger)elapseSinceLastState channel:(NSString *_Nonnull)channel { dispatch_async(dispatch_get_main_queue(), ^{ [self displayMessage:[NSString stringWithFormat:@"Video publish state changed with oldState: %ld newState: %ld.", (long)oldState, (long)newState]]; }); } - (void)createEngineKit { self.engine = [DingRtcEngine sharedInstance:self extras:nil]; } - (void)destroyEngineKit { [DingRtcEngine destroy]; self.engine = nil; } - (void)joinChannel { DingRtcAuthInfo *info = [[DingRtcAuthInfo alloc] init]; info.channelId = ChannelInfo.channelId; info.userId = ChannelInfo.userId; info.appId = ChannelInfo.appId; info.token = ChannelInfo.token; info.gslbServer = ChannelInfo.gslbServer; [self.engine publishLocalAudioStream:YES]; [self.engine publishLocalVideoStream:YES]; [self.engine subscribeAllRemoteVideoStreams:NO]; NSString *userName = [@"Mac_" stringByAppendingString:ChannelInfo.userId]; int result = [self.engine joinChannel:info name:userName onResultWithUserId:^(NSInteger errCode, NSString * _Nonnull channel, NSString * _Nonnull userId, NSInteger elapsed) { if (!errCode) { [self displayMessage:[NSString stringWithFormat:@"Join channel successfully."]]; [self startVideo]; } else { [self displayMessage:[NSString stringWithFormat:@"Join channel failed with error: %ld.", (long)errCode]]; } }]; if (result != 0) { [self displayMessage:[NSString stringWithFormat:@"Join channel failed with error: %ld.", (long)result]]; } } - (void)leaveChannel { [self.engine leaveChannel]; } - (void)initUserManager { self.userManager = [[UserManager alloc] init]; } - (void)displayMessage:(NSString *)message { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"%@", message); self.message.stringValue = message; self.message.hidden = NO; }); } - (NSView *)findIdleVideoView { NSArray<NSView *> *videoViews = @[self.remote1View, self.thumbnail2View]; NSView * videoView = nil; for (videoView in videoViews) { if ([self.userManager findUserWithView:videoView] == nil) { break; } } return videoView; } - (void)startVideo { [self.engine muteLocalCamera:NO forTrack:DingRtcVideoTrackCamera]; [self.engine enableLocalVideo:YES]; //开启预览 DingRtcVideoCanvas *canvasConfig = [self getCanvasWithView:self.local1View isPub:YES trackType:DingRtcVideoTrackCamera]; [self.engine setLocalViewConfig:canvasConfig forTrack:DingRtcVideoTrackCamera]; [self.engine startPreview]; } - (void)stopVideo { [self.engine muteLocalCamera:YES forTrack:DingRtcVideoTrackCamera]; [self.engine enableLocalVideo:NO]; //关闭预览 DingRtcVideoCanvas *canvasConfig = [self getCanvasWithView:self.local1View isPub:YES trackType:DingRtcVideoTrackCamera]; canvasConfig.toBeRemoved = YES; [self.engine setLocalViewConfig:canvasConfig forTrack:DingRtcVideoTrackCamera]; [self.engine stopPreview]; } - (void)startScreenShare { //开启屏幕共享 NSArray<DingRtcScreenSourceInfo *> *deskSources = [self.engine getScreenShareSourceInfoWithType:DingRtcScreenShareDesktop]; DingRtcVideoCanvas *canvasConfig = [self getCanvasWithView:self.local2View isPub:YES trackType:DingRtcVideoTrackScreen]; [self.engine setLocalViewConfig:canvasConfig forTrack:DingRtcVideoTrackScreen]; [self.engine startScreenShareWithDesktopId:deskSources.firstObject.sourceId config:[DingRtcScreenShareConfig new]]; } - (void)stopScreenShare { //关闭渲染 DingRtcVideoCanvas *canvasConfig = [self getCanvasWithView:self.local2View isPub:YES trackType:DingRtcVideoTrackScreen]; canvasConfig.toBeRemoved = YES; [self.engine setLocalViewConfig:canvasConfig forTrack:DingRtcVideoTrackScreen]; [self.engine stopScreenShare]; } - (void)subscribeVideo:(NSString *)userId withView:(NSView *)view { [self.engine subscribeRemoteVideoStream:userId track:DingRtcVideoTrackCamera sub:YES]; DingRtcVideoCanvas *canvasConfig = [self getCanvasWithView:view isPub:NO trackType:DingRtcVideoTrackCamera]; [self.engine setRemoteViewConfig:canvasConfig uid:userId forTrack:DingRtcVideoTrackCamera]; } - (void)stopRemoteVideo:(NSString *)userId withView:(NSView *)view { DingRtcVideoCanvas *canvasConfig = [self getCanvasWithView:view isPub:NO trackType:DingRtcVideoTrackCamera]; canvasConfig.toBeRemoved = YES; [self.engine setRemoteViewConfig:canvasConfig uid:userId forTrack:DingRtcVideoTrackCamera]; } - (void)subscribeScreen:(NSString *)userId withView:(NSView *)view { [self.engine subscribeRemoteVideoStream:userId track:DingRtcVideoTrackScreen sub:YES]; DingRtcVideoCanvas *canvasConfig = [self getCanvasWithView:view isPub:NO trackType:DingRtcVideoTrackScreen]; [self.engine setRemoteViewConfig:canvasConfig uid:userId forTrack:DingRtcVideoTrackScreen]; } - (void)stopRemoteScreen:(NSString *)userId withView:(NSView *)view { DingRtcVideoCanvas *canvasConfig = [self getCanvasWithView:view isPub:NO trackType:DingRtcVideoTrackScreen]; canvasConfig.toBeRemoved = YES; [self.engine setRemoteViewConfig:canvasConfig uid:userId forTrack:DingRtcVideoTrackScreen]; } - (DingRtcVideoCanvas *)getCanvasWithView:(NSView *)view isPub:(BOOL)isPub trackType:(DingRtcVideoTrack )trackType { // mirror mode DingRtcRenderMirrorMode mirrorMode = DingRtcRenderMirrorModeAllDisabled; if (isPub && trackType == DingRtcVideoTrackCamera) { mirrorMode = DingRtcRenderMirrorModeAllEnabled; } DingRtcRotationMode rotationMode = DingRtcRotationMode_0; DingRtcRenderMode renderMode = DingRtcRenderModeFill; DingRtcVideoCanvas *canvas = [[DingRtcVideoCanvas alloc] init]; canvas.view = view; canvas.renderMode = renderMode; canvas.mirrorMode = mirrorMode; canvas.backgroundColor = 0x000000; canvas.rotationMode = rotationMode; return canvas; } @end