NTESNetChatViewController.m 37 KB

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