NTESNetChatViewController.m 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054
  1. //
  2. // NTESNetChatViewController.m
  3. // NIM
  4. //
  5. // Created by chris on 15/5/18.
  6. // Copyright (c) 2015年 Netease. All rights reserved.
  7. //
  8. #import "NTESNetChatViewController.h"
  9. #import "UIAlertView+NTESBlock.h"
  10. #import "UIView+Toast.h"
  11. #import "NTESTimerHolder.h"
  12. #import "NTESNetCallChatInfo.h"
  13. #import "NTESBundleSetting.h"
  14. #import <AVFoundation/AVFoundation.h>
  15. #import <AssetsLibrary/ALAssetsLibrary.h>
  16. #import <Photos/Photos.h>
  17. #import "UIView+NTES.h"
  18. //十秒之后如果还是没有收到对方响应的control字段,则自己发起一个假的control,用来激活铃声并自己先进入房间
  19. #define DelaySelfStartControlTime 10
  20. //激活铃声后无人接听的超时时间
  21. #define NoBodyResponseTimeOut 45
  22. //周期性检查剩余磁盘空间
  23. #define DiskCheckTimeInterval 10
  24. //剩余磁盘空间不足的警告阈值
  25. #define MB (1024ll * 1024ll)
  26. #define FreeDiskSpaceWarningThreshold (10 * MB)
  27. @interface NTESNetChatViewController ()
  28. @property (nonatomic, strong) NTESTimerHolder *timer;
  29. @property (nonatomic, strong) NSMutableArray *chatRoom;
  30. @property (nonatomic, assign) BOOL recordWillStopForLackSpace;
  31. @property (nonatomic, strong) NTESTimerHolder *diskCheckTimer;
  32. @property (nonatomic, assign) BOOL userHangup;
  33. @property (nonatomic, strong) NTESTimerHolder *calleeResponseTimer; //被叫等待用户响应接听或者拒绝的时间
  34. @property (nonatomic, assign) BOOL calleeResponsed;
  35. @property (nonatomic, assign) int successRecords;
  36. //@property (nonatomic, strong) NTESRecordSelectView * recordView;
  37. @end
  38. @implementation NTESNetChatViewController
  39. {
  40. UInt64 CallId;
  41. }
  42. - (BOOL)forbidInteractivePop{return YES;}
  43. - (instancetype)initWithCallee:(NSString *)callee{
  44. self = [self initWithNibName:nil bundle:nil];
  45. if (self) {
  46. self.peerUid = callee;
  47. self.callInfo.callee = callee;
  48. self.callInfo.caller = [[NIMSDK sharedSDK].loginManager currentAccount];
  49. }
  50. return self;
  51. }
  52. - (instancetype)initWithCaller:(NSString *)caller callId:(uint64_t)callID{
  53. self = [self initWithNibName:nil bundle:nil];
  54. if (self) {
  55. self.peerUid = caller;
  56. self.callInfo.caller = caller;
  57. self.callInfo.callee = [[NIMSDK sharedSDK].loginManager currentAccount];
  58. self.callInfo.callID = callID;
  59. }
  60. return self;
  61. }
  62. - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
  63. self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  64. if (self) {
  65. self.edgesForExtendedLayout = UIRectEdgeAll;
  66. if (!self.callInfo) {
  67. [[NIMSDK sharedSDK].mediaManager switchAudioOutputDevice:NIMAudioOutputDeviceSpeaker];
  68. _callInfo = [[NTESNetCallChatInfo alloc] init];
  69. }
  70. _timer = [[NTESTimerHolder alloc] init];
  71. _diskCheckTimer = [[NTESTimerHolder alloc] init];
  72. //防止应用在后台状态,此时呼入,会走init但是不会走viewDidLoad,此时呼叫方挂断,导致被叫监听不到,界面无法消去的问题。
  73. id<NIMNetCallManager> manager = [NIMAVChatSDK sharedSDK].netCallManager;
  74. [manager addDelegate:self];
  75. }
  76. return self;
  77. }
  78. - (void)dealloc{
  79. NSLog(@"vc dealloc info : %@",self);
  80. [[NIMAVChatSDK sharedSDK].netCallManager removeDelegate:self];
  81. }
  82. - (void)viewDidLoad {
  83. [super viewDidLoad];
  84. __weak typeof(self) wself = self;
  85. [[NIMAVChatSDK sharedSDK].netCallManager addDelegate:self];
  86. [self checkServiceEnable:^(BOOL result) {
  87. if (result) {
  88. // [self response:YES];
  89. [wself afterCheckService];
  90. }else{
  91. //用户禁用服务,干掉界面
  92. if (wself.callInfo.callID) {
  93. //说明是被叫方
  94. [[NIMAVChatSDK sharedSDK].netCallManager response:wself.callInfo.callID accept:NO option:nil completion:nil];
  95. }
  96. [wself dismiss:nil];
  97. }
  98. }];
  99. }
  100. - (void)viewWillAppear:(BOOL)animated{
  101. [super viewWillAppear:animated];
  102. [self setUpStatusBar:UIStatusBarStyleLightContent];
  103. }
  104. - (void)viewDidAppear:(BOOL)animated{
  105. [super viewDidAppear:animated];
  106. [UIApplication sharedApplication].idleTimerDisabled = YES;
  107. }
  108. - (void)viewWillDisappear:(BOOL)animated{
  109. [super viewWillDisappear:animated];
  110. [self.player stop];
  111. // [[NIMAVChatSDK sharedSDK].netCallManager hangup:CallId];
  112. [UIApplication sharedApplication].idleTimerDisabled = NO;
  113. }
  114. - (void)afterCheckService{
  115. if (self.callInfo.isStart) {
  116. [self.timer startTimer:0.5 delegate:self repeats:YES];
  117. [self onCalling];
  118. }
  119. else if (self.callInfo.callID) {
  120. [self startByCallee];
  121. }
  122. else {
  123. [self startByCaller];
  124. }
  125. [self checkFreeDiskSpace];
  126. [self.diskCheckTimer startTimer:DiskCheckTimeInterval
  127. delegate:self
  128. repeats:YES];
  129. }
  130. #pragma mark - Subclass Impl
  131. - (void)startByCaller{
  132. if (self.callInfo.callType == NIMNetCallTypeVideo) {
  133. //视频呼叫马上发起
  134. [self doStartByCaller];
  135. }
  136. else {
  137. //语音呼叫先播放一断提示音,然后再发起
  138. [self playConnnetRing];
  139. __weak typeof(self) wself = self;
  140. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  141. if (!wself) {
  142. return;
  143. }
  144. if (wself.userHangup) {
  145. NSLog(@"Netcall request was cancelled before, ignore it.");
  146. return;
  147. }
  148. [wself doStartByCaller];
  149. });
  150. }
  151. }
  152. - (void)doStartByCaller
  153. {
  154. self.callInfo.isStart = YES;
  155. NSArray *callees = [NSArray arrayWithObjects:self.callInfo.callee, nil];
  156. NIMNetCallOption *option = [[NIMNetCallOption alloc] init];
  157. option.extendMessage = @"音视频请求扩展信息";
  158. option.apnsContent = [NSString stringWithFormat:@"%@请求", self.callInfo.callType == NIMNetCallTypeAudio ? @"网络通话" : @"视频聊天"];
  159. // option.apnsInuse = NO;
  160. option.apnsSound = @"video_chat_push.mp3";
  161. [self fillUserSetting:option];
  162. option.videoCaptureParam.startWithCameraOn = (self.callInfo.callType == NIMNetCallTypeVideo);
  163. __weak typeof(self) wself = self;
  164. [[NIMAVChatSDK sharedSDK].netCallManager start:callees type:wself.callInfo.callType option:option completion:^(NSError *error, UInt64 callID) {
  165. if (!error && wself) {
  166. NSLog(@"呼叫成功");
  167. CallId = callID;
  168. wself.callInfo.callID = callID;
  169. wself.chatRoom = [[NSMutableArray alloc]init];
  170. //十秒之后如果还是没有收到对方响应的control字段,则自己发起一个假的control,用来激活铃声并自己先进入房间
  171. NSTimeInterval delayTime = DelaySelfStartControlTime;
  172. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  173. [wself onControl:callID from:wself.callInfo.callee type:NIMNetCallControlTypeFeedabck];
  174. });
  175. }else{
  176. NSLog(@"呼叫失败");
  177. if (error) {
  178. NSLog(@"连接失败跳转%@",error);
  179. [wself.navigationController.view makeToast:@"连接失败"
  180. duration:2
  181. position:CSToastPositionCenter];
  182. }else{
  183. //说明在start的过程中把页面关了。。
  184. [[NIMAVChatSDK sharedSDK].netCallManager hangup:callID];
  185. }
  186. [wself dismiss:nil];
  187. }
  188. }];
  189. }
  190. - (void)startByCallee{
  191. //告诉对方可以播放铃声了
  192. self.callInfo.isStart = YES;
  193. NSMutableArray *room = [[NSMutableArray alloc] init];
  194. [room addObject:self.callInfo.caller];
  195. self.chatRoom = room;
  196. [[NIMAVChatSDK sharedSDK].netCallManager control:self.callInfo.callID type:NIMNetCallControlTypeFeedabck];
  197. [self playReceiverRing];
  198. _calleeResponseTimer = [[NTESTimerHolder alloc] init];
  199. [_calleeResponseTimer startTimer:NoBodyResponseTimeOut delegate:self repeats:NO];
  200. }
  201. - (void)hangup{
  202. _userHangup = YES;
  203. [[NIMAVChatSDK sharedSDK].netCallManager hangup:self.callInfo.callID];
  204. if (self.callInfo.localRecording) {
  205. __weak typeof(self) wself = self;
  206. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  207. wself.chatRoom = nil;
  208. [wself dismiss:nil];
  209. });
  210. }
  211. else {
  212. self.chatRoom = nil;
  213. [self dismiss:nil];
  214. }
  215. }
  216. - (void)response:(BOOL)accept{
  217. _calleeResponsed = YES;
  218. NIMNetCallOption *option = [[NIMNetCallOption alloc] init];
  219. [self fillUserSetting:option];
  220. __weak typeof(self) wself = self;
  221. [[NIMAVChatSDK sharedSDK].netCallManager response:self.callInfo.callID accept:accept option:option completion:^(NSError *error, UInt64 callID) {
  222. if (!error) {
  223. NSLog(@"被接成功");
  224. [wself onCalling];
  225. [wself.player stop];
  226. [wself.chatRoom addObject:wself.callInfo.callee];
  227. NSTimeInterval delay = 10.f; //10秒后判断下房间
  228. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  229. if (wself.chatRoom.count == 1) {
  230. [wself.navigationController.view makeToast:@"通话失败"
  231. duration:2
  232. position:CSToastPositionCenter];
  233. [wself hangup];
  234. }
  235. });
  236. }else{
  237. NSLog(@"被接失败");
  238. wself.chatRoom = nil;
  239. [wself.navigationController.view makeToast:@"连接失败"
  240. duration:2
  241. position:CSToastPositionCenter];
  242. [wself dismiss:nil];
  243. }
  244. }];
  245. //dismiss需要放在self后面,否在ios7下会有野指针
  246. if (accept) {
  247. [self waitForConnectiong];
  248. }else{
  249. [self dismiss:nil];
  250. }
  251. }
  252. //- (void)onReceive:(UInt64)callID from:(NSString *)caller type:(NIMNetCallMediaType)type message:(NSString *)extendMessage{
  253. //
  254. //// NTESMainTabController *tabVC = [NTESMainTabController instance];
  255. //// [tabVC.view endEditing:YES];
  256. //// UINavigationController *nav = tabVC.selectedViewController;
  257. ////
  258. //// if ([self shouldResponseBusy]){
  259. //// [[NIMAVChatSDK sharedSDK].netCallManager control:callID type:NIMNetCallControlTypeBusyLine];
  260. //// }
  261. //// else {
  262. ////
  263. //// if ([self shouldFireNotification:caller]) {
  264. //// NSString *text = [self textByCaller:caller
  265. //// type:type];
  266. //// [_notifier start:text];
  267. //// }
  268. //
  269. //
  270. //
  271. // UIViewController *vc;
  272. // switch (type) {
  273. // case NIMNetCallTypeVideo:{
  274. // vc = [[NTESNetChatViewController alloc] initWithCaller:caller callId:callID];
  275. //// [self response:NO];
  276. // }
  277. // break;
  278. // case NIMNetCallTypeAudio:{
  279. //// vc = [[NTESAudioChatViewController alloc] initWithCaller:caller callId:callID];
  280. // }
  281. // break;
  282. // default:
  283. // break;
  284. // }
  285. // if (!vc) {
  286. // return;
  287. // }
  288. //
  289. // // 由于音视频聊天里头有音频和视频聊天界面的切换,直接用present的话页面过渡会不太自然,这里还是用push,然后做出present的效果
  290. //// CATransition *transition = [CATransition animation];
  291. //// transition.duration = 0.25;
  292. //// transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
  293. //// transition.type = kCATransitionPush;
  294. //// transition.subtype = kCATransitionFromTop;
  295. //// [nav.view.layer addAnimation:transition forKey:nil];
  296. //// nav.navigationBarHidden = YES;
  297. //// if (nav.presentedViewController) {
  298. //// // fix bug MMC-1431
  299. //// [nav.presentedViewController dismissViewControllerAnimated:NO completion:nil];
  300. //// }
  301. //// [nav pushViewController:vc animated:NO];
  302. //// }
  303. //}
  304. - (void)dismiss:(void (^)(void))completion{
  305. //由于音视频聊天里头有音频和视频聊天界面的切换,直接用present的话页面过渡会不太自然,这里还是用push,然后做出present的效果
  306. CATransition *transition = [CATransition animation];
  307. transition.duration = 0.25;
  308. transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
  309. transition.type = kCATransitionPush;
  310. transition.subtype = kCATransitionFromBottom;
  311. [self.navigationController.view.layer addAnimation:transition forKey:nil];
  312. self.navigationController.navigationBarHidden = NO;
  313. [self.navigationController popViewControllerAnimated:NO];
  314. [self setUpStatusBar:UIStatusBarStyleDefault];
  315. if (completion) {
  316. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(transition.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  317. completion();
  318. });
  319. }
  320. }
  321. - (void)onCalling{
  322. //子类重写
  323. }
  324. - (void)waitForConnectiong{
  325. //子类重写
  326. }
  327. - (void)onCalleeBusy
  328. {
  329. //子类重写
  330. }
  331. -(BOOL)startAudioRecording
  332. {
  333. UInt64 timestamp =[[NSDate date]timeIntervalSince1970]*1000;
  334. NSString * pathComponent = [NSString stringWithFormat:@"record_audio%@_%llu",[[NIMSDK sharedSDK].loginManager currentAccount],timestamp];
  335. NSURL *filePath = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:pathComponent];
  336. return [[NIMAVChatSDK sharedSDK].netCallManager startAudioRecording:filePath error:nil];
  337. }
  338. - (BOOL)startLocalRecording
  339. {
  340. UInt64 timestamp =[[NSDate date]timeIntervalSince1970]*1000;
  341. NSString * pathComponent = [NSString stringWithFormat:@"record_%@_%llu.mp4",[[NIMSDK sharedSDK].loginManager currentAccount],timestamp];
  342. NSURL *filePath = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:pathComponent];
  343. BOOL startAccepted;
  344. startAccepted = [[NIMAVChatSDK sharedSDK].netCallManager startRecording:filePath
  345. videoBitrate:(UInt32)[[NTESBundleSetting sharedConfig] localRecordVideoKbps] * 1000
  346. uid:[[NIMSDK sharedSDK].loginManager currentAccount]
  347. ];
  348. return startAccepted;
  349. }
  350. - (BOOL)startOtherSideRecording
  351. {
  352. UInt64 timestamp =[[NSDate date]timeIntervalSince1970]*1000;
  353. NSString * pathComponent = [NSString stringWithFormat:@"record_%@_%llu.mp4",self.peerUid,timestamp];
  354. NSURL *filePath = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:pathComponent];
  355. BOOL startAccepted;
  356. startAccepted = [[NIMAVChatSDK sharedSDK].netCallManager startRecording:filePath
  357. videoBitrate:(UInt32)[[NTESBundleSetting sharedConfig] localRecordVideoKbps] * 1000
  358. uid:self.peerUid
  359. ];
  360. return startAccepted;
  361. }
  362. -(void)stopAudioRecording
  363. {
  364. [[NIMAVChatSDK sharedSDK].netCallManager stopAudioRecording];
  365. self.callInfo.audioConversation = NO;
  366. }
  367. - (BOOL)stopLocalRecording
  368. {
  369. return [[NIMAVChatSDK sharedSDK].netCallManager stopRecordingWithUid:[[NIMSDK sharedSDK].loginManager currentAccount]];
  370. }
  371. - (BOOL)stopOtherSideRecording
  372. {
  373. return [[NIMAVChatSDK sharedSDK].netCallManager stopRecordingWithUid:self.peerUid];
  374. }
  375. - (void)udpateLowSpaceWarning:(BOOL)show
  376. {
  377. //子类重写
  378. }
  379. -(void)recordWithAudioConversation:(BOOL)audioConversationOn myMedia:(BOOL)myMediaOn otherSideMedia:(BOOL)otherSideMediaOn video:(BOOL)isVideo
  380. {
  381. NSString *toastText = @"";
  382. int records = 0;
  383. _successRecords = 0;//不包括语音对话
  384. //语音对话
  385. if (audioConversationOn) {
  386. records++;
  387. if (!self.callInfo.audioConversation) {
  388. toastText =@"语音对话开始失败";
  389. }
  390. }
  391. //自己音视频
  392. if (myMediaOn) {
  393. records++;
  394. if (![self startLocalRecording]) {
  395. if(isVideo)
  396. {
  397. toastText = [toastText stringByAppendingString: [toastText isEqualToString:@""] ?@"自己音视频开始失败":@",自己音视频开始失败"];
  398. }
  399. else
  400. {
  401. toastText = [toastText stringByAppendingString: [toastText isEqualToString:@""] ?@"自己音频开始失败":@",自己音频开始失败"];
  402. }
  403. }
  404. else
  405. _successRecords++;
  406. }
  407. //对方音视频
  408. if (otherSideMediaOn) {
  409. records++;
  410. if (![self startOtherSideRecording]) {
  411. if (isVideo) {
  412. toastText =[toastText stringByAppendingString:[toastText isEqualToString:@""] ?@"对方音视频开始失败":@",对方音视频开始失败"];
  413. }
  414. else
  415. {
  416. toastText =[toastText stringByAppendingString:[toastText isEqualToString:@""] ?@"对方音频开始失败":@",对方音频开始失败"];
  417. }
  418. }
  419. else
  420. _successRecords++;
  421. }
  422. //判断是否需要提示
  423. if (![toastText isEqualToString:@""]) {
  424. if ([toastText componentsSeparatedByString:@","].count == records) {
  425. [self.view makeToast:@"开始录制失败"
  426. duration:2
  427. position:CSToastPositionCenter];
  428. }
  429. else
  430. {
  431. [self.view makeToast:toastText
  432. duration:2
  433. position:CSToastPositionCenter];
  434. }
  435. }
  436. }
  437. - (void)stopRecordTaskWithVideo:(BOOL)isVideo
  438. {
  439. NSString *toastText = @"";
  440. int records = 0;
  441. //结束自己音视频
  442. if (self.callInfo.localRecording) {
  443. records++;
  444. if (![self stopLocalRecording]) {
  445. if (isVideo) {
  446. toastText = [toastText stringByAppendingString: [toastText isEqualToString:@""] ?@"自己音视频结束失败":@",自己音视频结束失败"];
  447. }
  448. else
  449. {
  450. toastText = [toastText stringByAppendingString: [toastText isEqualToString:@""] ?@"自己音频结束失败":@",自己音频结束失败"];
  451. }
  452. }
  453. }
  454. //结束对方音视频
  455. if (self.callInfo.otherSideRecording) {
  456. records++;
  457. if (![self stopOtherSideRecording]) {
  458. if (isVideo) {
  459. toastText = [toastText stringByAppendingString: [toastText isEqualToString:@""] ?@"对方音视频结束失败":@",对方音视频结束失败"];
  460. }
  461. else
  462. {
  463. toastText = [toastText stringByAppendingString: [toastText isEqualToString:@""] ?@"对方音频结束失败":@",对方音频结束失败"];
  464. }
  465. }
  466. }
  467. //判断是否要提示
  468. if (![toastText isEqualToString:@""]) {
  469. if ([toastText componentsSeparatedByString:@","].count == records) {
  470. [self.view makeToast:@"结束录制失败"
  471. duration:3
  472. position:CSToastPositionCenter];
  473. }
  474. else
  475. {
  476. [self.view makeToast:toastText
  477. duration:3
  478. position:CSToastPositionCenter];
  479. }
  480. }
  481. }
  482. -(BOOL)allRecordsStopped
  483. {
  484. //当前没有录制任务
  485. if (!self.callInfo.localRecording&&!self.callInfo.otherSideRecording&&!self.callInfo.audioConversation) {
  486. return YES;
  487. }
  488. else
  489. {
  490. return NO;
  491. }
  492. }
  493. -(BOOL)allRecordsSucceeed
  494. {
  495. int num = self.callInfo.localRecording + self.callInfo.otherSideRecording;
  496. if (num == _successRecords) {
  497. return YES;
  498. }
  499. else
  500. {
  501. return NO;
  502. }
  503. }
  504. -(NSString *)peerUid
  505. {
  506. if (_peerUid) {
  507. return _peerUid;
  508. }
  509. else
  510. {
  511. if ([_callInfo.callee isEqualToString:[[NIMSDK sharedSDK].loginManager currentAccount]]) {
  512. _peerUid = _callInfo.caller;
  513. return _callInfo.caller;
  514. }
  515. else
  516. {
  517. _peerUid = _callInfo.callee;
  518. return _callInfo.callee;
  519. }
  520. }
  521. }
  522. //-(void)showRecordSelectView:(BOOL)isVideo
  523. //{
  524. // if (!_recordView) {
  525. // _recordView = [[NTESRecordSelectView alloc]initWithFrame:CGRectMake(0, 0, 250, 250) Video:isVideo];
  526. // _recordView.delegate = self;
  527. // _recordView.centerX = self.view.width/2;
  528. // _recordView.centerY = self.view.height/2;
  529. // }
  530. // [self.view addSubview:_recordView];
  531. //}
  532. #pragma mark - NIMNetCallManagerDelegate
  533. - (void)onControl:(UInt64)callID
  534. from:(NSString *)user
  535. type:(NIMNetCallControlType)control{
  536. if (user == [[NIMSDK sharedSDK].loginManager currentAccount]) {
  537. //多端登录时,自己会收到自己发出的控制指令,这里忽略他
  538. return;
  539. }
  540. if (callID != self.callInfo.callID) {
  541. return;
  542. }
  543. switch (control) {
  544. case NIMNetCallControlTypeFeedabck:{
  545. NSMutableArray *room = self.chatRoom;
  546. if (room && !room.count && !_userHangup) {
  547. [self playSenderRing];
  548. [room addObject:self.callInfo.caller];
  549. //40秒之后查看一下房间状态,如果房间还在一个人的话,就播放铃声超时
  550. __weak typeof(self) wself = self;
  551. uint64_t callId = self.callInfo.callID;
  552. NSTimeInterval delayTime = NoBodyResponseTimeOut;
  553. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  554. NSMutableArray *room = wself.chatRoom;
  555. if (wself && room && room.count == 1) {
  556. [[NIMAVChatSDK sharedSDK].netCallManager hangup:callId];
  557. wself.chatRoom = nil;
  558. [wself playTimeoutRing];
  559. [wself.navigationController.view makeToast:@"无人接听"
  560. duration:2
  561. position:CSToastPositionCenter];
  562. [wself startChat];
  563. [wself dismiss:nil];
  564. }
  565. });
  566. }
  567. break;
  568. }
  569. case NIMNetCallControlTypeBusyLine: {
  570. [self onCalleeBusy];
  571. [self playOnCallRing];
  572. _userHangup = YES;
  573. [[NIMAVChatSDK sharedSDK].netCallManager hangup:callID];
  574. __weak typeof(self) wself = self;
  575. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  576. [wself startChat];
  577. [wself dismiss:nil];
  578. });
  579. break;
  580. }
  581. case NIMNetCallControlTypeStartRecord:
  582. [self.view makeToast:@"对方开始了录制"
  583. duration:1
  584. position:CSToastPositionCenter];
  585. break;
  586. case NIMNetCallControlTypeStopRecord:
  587. [self.view makeToast:@"对方结束了录制"
  588. duration:1
  589. position:CSToastPositionCenter];
  590. break;
  591. default:
  592. break;
  593. }
  594. }
  595. - (void)onResponse:(UInt64)callID from:(NSString *)callee accepted:(BOOL)accepted{
  596. if (self.callInfo.callID == callID) {
  597. if (!accepted) {
  598. self.chatRoom = nil;
  599. [self.navigationController.view makeToast:@"对方拒绝接听"
  600. duration:2
  601. position:CSToastPositionCenter];
  602. [self playHangUpRing];
  603. __weak typeof(self) wself = self;
  604. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  605. [wself startChat];
  606. [wself dismiss:nil];
  607. });
  608. }else{
  609. [self.player stop];
  610. [self onCalling];
  611. [self.chatRoom addObject:callee];
  612. }
  613. }
  614. }
  615. -(void)onCallEstablished:(UInt64)callID
  616. {
  617. if (self.callInfo.callID == callID) {
  618. self.callInfo.startTime = [NSDate date].timeIntervalSince1970;
  619. [self.timer startTimer:0.5 delegate:self repeats:YES];
  620. }
  621. }
  622. - (void)onCallDisconnected:(UInt64)callID withError:(NSError *)error
  623. {
  624. if (self.callInfo.callID == callID) {
  625. [self.timer stopTimer];
  626. [self dismiss:nil];
  627. self.chatRoom = nil;
  628. }
  629. }
  630. - (void)onResponsedByOther:(UInt64)callID
  631. accepted:(BOOL)accepted{
  632. [self.view.window makeToast:@"已在其他端处理"
  633. duration:2
  634. position:CSToastPositionCenter];
  635. [self dismiss:nil];
  636. }
  637. - (void)onHangup:(UInt64)callID
  638. by:(NSString *)user{
  639. if (self.callInfo.callID == callID) {
  640. [self.player stop];
  641. if (self.callInfo.localRecording) {
  642. __weak typeof(self) wself = self;
  643. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  644. [wself dismiss:nil];
  645. });
  646. }
  647. else {
  648. [self dismiss:nil];
  649. }
  650. }
  651. }
  652. - (void)onRecordStarted:(UInt64)callID fileURL:(NSURL *)fileURL uid:(NSString *)userId
  653. {
  654. if (self.callInfo.callID == callID) {
  655. if ([userId isEqualToString:[[NIMSDK sharedSDK].loginManager currentAccount]]) {
  656. self.callInfo.localRecording = YES;
  657. }
  658. if ([userId isEqualToString:self.peerUid]) {
  659. self.callInfo.otherSideRecording = YES;
  660. }
  661. }
  662. }
  663. - (void)onRecordError:(NSError *)error
  664. callID:(UInt64)callID
  665. uid:(NSString *)userId
  666. {
  667. NSLog(@"Local record error %@ (%zd)", error.localizedDescription, error.code);
  668. if (self.callInfo.callID == callID) {
  669. [self.view makeToast:[NSString stringWithFormat:@"录制发生错误: %zd", error.code]
  670. duration:2
  671. position:CSToastPositionCenter];
  672. if ([userId isEqualToString:[[NIMSDK sharedSDK].loginManager currentAccount]]) {
  673. self.callInfo.localRecording = NO;
  674. }
  675. if ([userId isEqualToString:self.peerUid]) {
  676. self.callInfo.otherSideRecording = NO;
  677. }
  678. }
  679. if (error.code == NIMAVLocalErrorCodeRecordWillStopForLackSpace) {
  680. _recordWillStopForLackSpace = YES;
  681. }
  682. }
  683. - (void) onRecordStopped:(UInt64)callID
  684. fileURL:(NSURL *)fileURL
  685. uid:(NSString *)userId
  686. {
  687. if (self.callInfo.callID == callID) {
  688. if ([userId isEqualToString:[[NIMSDK sharedSDK].loginManager currentAccount]]) {
  689. self.callInfo.localRecording = NO;
  690. }
  691. if ([userId isEqualToString:self.peerUid]) {
  692. self.callInfo.otherSideRecording = NO;
  693. }
  694. //辅助验证: 写到系统相册
  695. [self requestAuthorizationSaveToPhotosAlbum:fileURL];
  696. }
  697. }
  698. #pragma mark - NTESRecordSelectViewDelegate
  699. -(void)onRecordWithAudioConversation:(BOOL)audioConversationOn myMedia:(BOOL)myMediaOn otherSideMedia:(BOOL)otherSideMediaOn
  700. {
  701. //子类重写
  702. }
  703. #pragma mark - M80TimerHolderDelegate
  704. - (void)onNTESTimerFired:(NTESTimerHolder *)holder{
  705. if (holder == self.diskCheckTimer) {
  706. [self checkFreeDiskSpace];
  707. }
  708. else if(holder == self.calleeResponseTimer) {
  709. if (!_calleeResponsed) {
  710. [self.navigationController.view makeToast:@"接听超时"
  711. duration:2
  712. position:CSToastPositionCenter];
  713. [self response:NO];
  714. }
  715. }
  716. }
  717. #pragma mark - Misc
  718. - (void)checkServiceEnable:(void(^)(BOOL))result{
  719. if ([[AVAudioSession sharedInstance] respondsToSelector:@selector(requestRecordPermission:)]) {
  720. [[AVAudioSession sharedInstance] performSelector:@selector(requestRecordPermission:) withObject:^(BOOL granted) {
  721. dispatch_async_main_safe(^{
  722. if (granted) {
  723. NSString *mediaType = AVMediaTypeVideo;
  724. AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
  725. if(authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied){
  726. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
  727. message:@"相机权限受限,无法视频聊天"
  728. delegate:nil
  729. cancelButtonTitle:@"确定"
  730. otherButtonTitles:nil];
  731. [alert showAlertWithCompletionHandler:^(NSInteger idx) {
  732. if (result) {
  733. result(NO);
  734. }
  735. }];
  736. }else{
  737. if (result) {
  738. result(YES);
  739. }
  740. }
  741. }
  742. else {
  743. UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
  744. message:@"麦克风权限受限,无法聊天"
  745. delegate:nil
  746. cancelButtonTitle:@"确定"
  747. otherButtonTitles:nil];
  748. [alert showAlertWithCompletionHandler:^(NSInteger idx) {
  749. if (result) {
  750. result(NO);
  751. }
  752. }];
  753. }
  754. });
  755. }];
  756. }
  757. }
  758. - (void)setUpStatusBar:(UIStatusBarStyle)style{
  759. [[UIApplication sharedApplication] setStatusBarStyle:style
  760. animated:NO];
  761. }
  762. - (void)requestAuthorizationSaveToPhotosAlbum:(NSURL *)fileURL
  763. {
  764. __weak typeof(self) wself = self;
  765. if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
  766. [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
  767. if (status == PHAuthorizationStatusRestricted || status == PHAuthorizationStatusDenied)
  768. {
  769. //没有权限
  770. dispatch_async_main_safe(^{
  771. [wself.navigationController.view makeToast:@"保存失败,没有相册权限"
  772. duration:3
  773. position:CSToastPositionCenter];
  774. });
  775. }
  776. else if (status == PHAuthorizationStatusAuthorized)
  777. {
  778. //有权限
  779. [wself saveToPhotosAlbum:fileURL];
  780. }
  781. }];
  782. }
  783. else
  784. {
  785. [self saveToPhotosAlbum:fileURL];
  786. }
  787. }
  788. - (void)saveToPhotosAlbum:(NSURL *)fileURL
  789. {
  790. if (fileURL) {
  791. __weak typeof(self) wself = self;
  792. if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
  793. ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
  794. [library writeVideoAtPathToSavedPhotosAlbum:fileURL
  795. completionBlock:^(NSURL *assetURL, NSError *error) {
  796. [wself checkVideoSaveToAlbum:error];
  797. }];
  798. }
  799. else
  800. {
  801. [[PHPhotoLibrary sharedPhotoLibrary]performChanges:^{
  802. [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:fileURL];
  803. }
  804. completionHandler:^(BOOL success, NSError * _Nullable error) {
  805. dispatch_async_main_safe(^{
  806. [wself checkVideoSaveToAlbum:error];
  807. });
  808. }];
  809. }
  810. }
  811. }
  812. - (void)checkVideoSaveToAlbum:(NSError *)error
  813. {
  814. NSString *toast = _recordWillStopForLackSpace ? @"你的手机内存不足,录制已结束\n" : @"录制已结束\n";
  815. if (error) {
  816. toast = [NSString stringWithFormat:@"%@保存至系统相册失败:%zd", toast, error.code];
  817. }
  818. else {
  819. toast = [toast stringByAppendingString:@"录制文件已保存至系统相册"];
  820. }
  821. if (!self.callInfo.localRecording&&!self.callInfo.otherSideRecording&&_successRecords==1) {
  822. [self.navigationController.view makeToast:toast
  823. duration:3
  824. position:CSToastPositionCenter];
  825. }
  826. _successRecords--;
  827. }
  828. - (void)checkFreeDiskSpace{
  829. if (self.callInfo.localRecording) {
  830. uint64_t freeSpace = 1000 * MB;
  831. NSError *error;
  832. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  833. NSDictionary *attrbites = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error: &error];
  834. if (attrbites) {
  835. NSNumber *freeFileSystemSizeInBytes = [attrbites objectForKey:NSFileSystemFreeSize];
  836. freeSpace = [freeFileSystemSizeInBytes unsignedLongLongValue];
  837. [self udpateLowSpaceWarning:(freeSpace < FreeDiskSpaceWarningThreshold)];
  838. }
  839. }
  840. }
  841. - (void)fillUserSetting:(NIMNetCallOption *)option
  842. {
  843. option.autoRotateRemoteVideo = [[NTESBundleSetting sharedConfig] videochatAutoRotateRemoteVideo];
  844. option.serverRecordAudio = [[NTESBundleSetting sharedConfig] serverRecordAudio];
  845. option.serverRecordVideo = [[NTESBundleSetting sharedConfig] serverRecordVideo];
  846. option.preferredVideoEncoder = [[NTESBundleSetting sharedConfig] perferredVideoEncoder];
  847. option.preferredVideoDecoder = [[NTESBundleSetting sharedConfig] perferredVideoDecoder];
  848. option.videoMaxEncodeBitrate = [[NTESBundleSetting sharedConfig] videoMaxEncodeKbps] * 1000;
  849. option.autoDeactivateAudioSession = [[NTESBundleSetting sharedConfig] autoDeactivateAudioSession];
  850. option.audioDenoise = [[NTESBundleSetting sharedConfig] audioDenoise];
  851. option.voiceDetect = [[NTESBundleSetting sharedConfig] voiceDetect];
  852. option.audioHowlingSuppress = [[NTESBundleSetting sharedConfig] audioHowlingSuppress];
  853. option.preferHDAudio = [[NTESBundleSetting sharedConfig] preferHDAudio];
  854. option.scene = [[NTESBundleSetting sharedConfig] scene];
  855. NIMNetCallVideoCaptureParam *param = [[NIMNetCallVideoCaptureParam alloc] init];
  856. [self fillVideoCaptureSetting:param];
  857. option.videoCaptureParam = param;
  858. }
  859. - (void)fillVideoCaptureSetting:(NIMNetCallVideoCaptureParam *)param
  860. {
  861. param.preferredVideoQuality = [[NTESBundleSetting sharedConfig] preferredVideoQuality];
  862. param.videoCrop = [[NTESBundleSetting sharedConfig] videochatVideoCrop];
  863. param.startWithBackCamera = [[NTESBundleSetting sharedConfig] startWithBackCamera];
  864. }
  865. #pragma mark - Ring
  866. //铃声 - 正在呼叫请稍后
  867. - (void)playConnnetRing{
  868. [self.player stop];
  869. NSURL *url = [[NSBundle mainBundle] URLForResource:@"video_connect_chat_tip_sender" withExtension:@"aac"];
  870. self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
  871. [self.player play];
  872. }
  873. //铃声 - 对方暂时无法接听
  874. - (void)playHangUpRing{
  875. [self.player stop];
  876. NSURL *url = [[NSBundle mainBundle] URLForResource:@"video_chat_tip_HangUp" withExtension:@"aac"];
  877. self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
  878. [self.player play];
  879. }
  880. //铃声 - 对方正在通话中
  881. - (void)playOnCallRing{
  882. [self.player stop];
  883. NSURL *url = [[NSBundle mainBundle] URLForResource:@"video_chat_tip_OnCall" withExtension:@"aac"];
  884. self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
  885. [self.player play];
  886. }
  887. //铃声 - 对方无人接听
  888. - (void)playTimeoutRing{
  889. [self.player stop];
  890. NSURL *url = [[NSBundle mainBundle] URLForResource:@"video_chat_tip_onTimer" withExtension:@"aac"];
  891. self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
  892. [self.player play];
  893. }
  894. //铃声 - 接收方铃声
  895. - (void)playReceiverRing{
  896. [self.player stop];
  897. NSURL *url = [[NSBundle mainBundle] URLForResource:@"video_chat_tip_receiver" withExtension:@"aac"];
  898. self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
  899. self.player.numberOfLoops = 20;
  900. [self.player play];
  901. }
  902. //铃声 - 拨打方铃声
  903. - (void)playSenderRing{
  904. [self.player stop];
  905. NSURL *url = [[NSBundle mainBundle] URLForResource:@"video_chat_tip_sender" withExtension:@"aac"];
  906. self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
  907. self.player.numberOfLoops = 20;
  908. [self.player play];
  909. }
  910. - (void)startChat{
  911. NSString *now = [Helper getCurrentTimes];
  912. NSString *str = [NSString stringWithFormat:@"%@order?action=doBegVChat&pk=%@&modelpk=%@&begdate=%@&hasanswer=0",imageURl,[Helper sharedAccount].accid,self.modelPk,now];
  913. NSLog(@"str========%@",str);
  914. [[AHHttpManager sharedManager]POST:[str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] parameters:nil success:^(id responseObject) {
  915. NSLog(@"%@",responseObject);
  916. if ([[responseObject objectForKey:@"msg"]isEqualToString:@"success"]) {
  917. }else{
  918. //[MBProgressHUD showTextHUD:@"超时" inView:self.view hideAfterDelay:1];
  919. }
  920. } failure:^(NSError *error) {
  921. NSLog(@"error = %@",error);
  922. //[MBProgressHUD showTextHUD:@"超时" inView:self.view hideAfterDelay:1];
  923. }];
  924. }
  925. @end