// // JCManager.m // watch // // Created by Apple on 2019/4/29. // Copyright © 2019年 xTT. All rights reserved. // #import "JCManager.h" #import "AppDelegate.h" //菊风重拨等待时间 #define JuphoonReCallWaitTime 2 @interface JCManager () @property (strong, nonatomic) LGAlertView *alertView; /** 查询次数 */ @property (nonatomic) int SeachCount; /** 拨打超时定时器 */ @property (strong, nonatomic) NSTimer *timeoutTimer; /** 视频通话 限制定时器 */ @property (strong, nonatomic) NSTimer *limitVideoTimeoutTimer; /** 视频通话 限制提前通知定时器 */ @property (strong, nonatomic) NSTimer *limitVideoBeforeShowTimeoutTimer; @end static JCManager* _manager; @implementation JCManager + (JCManager*)shared { static dispatch_once_t predicate; dispatch_once(&predicate, ^{ _manager = [[self alloc] init]; }); return _manager; } -(bool)initializeWithAPPKey:(NSString*)appkey { if(!(_client && _mediaDevice && _call && _account)){ _client = [JCClient create:appkey callback:self createParam:nil]; _mediaDevice = [JCMediaDevice create:_client callback:self]; _call = [JCCall create:_client mediaDevice:_mediaDevice callback:self]; _account = [JCAccount create:self]; _push = [JCPush create:_client]; // 现在设置屏幕方向固定,竖直角度 // _mediaDevice.autoRotate = NO; } return _client.state == JCClientStateIdle; } -(void)uninitialize { if (_client != nil) { [JCPush destroy]; [JCCall destroy];; [JCAccount destroy]; [JCMediaDevice destroy]; [JCClient destroy]; _push = nil; _call = nil; _mediaDevice = nil; _client = nil; _account = nil; } } -(void)setModel:(DeviceVideoConfigModel *)model { _model = model; //MARK: 设置APP 屏幕共享采集的 宽,高,帧率 ---> 传给设备的 [_mediaDevice setScreenCaptureProperty:model.width height:model.height framerate:15]; // JCVideoModeOtherParam *param = [JCVideoModeOtherParam new]; // param.agc = 1; // NSLog(@"开启声音增益"); // [_mediaDevice setVideoMode: JCMediaDeviceVideoModeLS otherParam:param]; NSLog(@"声音模式: %@",[_mediaDevice getAudioOutputType]); } -(void)setIsSeachAndCall:(BOOL)isSeachAndCall{ _isSeachAndCall = isSeachAndCall; if (isSeachAndCall == NO) { if(self.canReCallBlock){ self.canReCallBlock(); } } } /** MARK: 拨打视频电话 */ -(void)callVideoWithUserName { //先查询用户是否存在 //self.callViewController.statusLabel.hidden = NO; self.isSeachAndCall = YES; [_account queryUserStatus:@[self.model.deviceUserName]]; //MARK: 启动拨打超时定时器 [self _startCallTimeoutTimer]; } /** 开始定时器 */ - (void)_startCallTimeoutTimer { if(self.wait_time == 0){ self.wait_time = self.model.callInterval; } if(self.timeoutTimer){ [self.timeoutTimer invalidate]; self.timeoutTimer = nil; } self.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:self.wait_time target:self selector:@selector(_timeoutBeforeCallAnswered) userInfo:nil repeats:NO]; } /** 结束定时器 */ - (void)_stopCallTimeoutTimer { //NO self.isSeachAndCall = NO; if (self.timeoutTimer == nil) { return; } [self.timeoutTimer invalidate]; self.timeoutTimer = nil; } #pragma mark - Call Timeout Before Answered - (void)_timeoutBeforeCallAnswered { //超时挂断 if(self.call.callItems.count != 0){ bool res = [self.call term:self.call.callItems[0] reason:JCCallReasonTimeOut description:@"超时挂断"]; if (!res) { NSLog(@"超时挂断未成功"); } } //超时自动置 NO self.isSeachAndCall = NO; [self _stopCallTimeoutTimer]; [UICommon HidenLoading]; UIViewController *viewController = self.gMianVC.navigationController.viewControllers.lastObject; if([viewController isKindOfClass:[JuPhoonCallViewController class]]) { kWeakSelf(self) EasyAlertView *alertV = [EasyAlertView alertViewWithTitle:GJText(@"提示") subtitle:GJText(@"宝贝长时间未接听,请稍后再试") AlertViewType:AlertViewTypeSystemAlert config:nil]; [alertV addAlertItem:^EasyAlertItem *{ return [EasyAlertItem itemWithTitle:GJText(@"OK") type:AlertItemTypeSystemDefault callback:^(EasyAlertView *showview, long index) { [weakself.callViewController dismissViewControllerAnimated:YES completion:nil]; }]; }]; [alertV showAlertView]; } else { [UICommon MessageErrorText:@"宝贝长时间未接听,请稍后再试"]; } } - (void)_videoLimitAction{ //通话限制到了 if(self.call.callItems.count != 0){ bool res = [self.call term:self.call.callItems[0] reason:JCCallReasonTimeOut description:@"通话限制挂断"]; if (!res) { NSLog(@"通话限制挂断未成功"); } } //停止定时器 [self _stopVideoLimitTimeoutTimer]; } - (void)_showVideoLimitAction{ //为防止设备温度过高,即将结束通话,稍后可正常使用 //避免与宝贝长时间视频通话,服务将于%d秒内自动挂断 EasyAlertView *alertV = [EasyAlertView alertViewWithTitle:GJText(@"提示") subtitle:GJText(@"为防止设备温度过高,即将结束通话,稍后可正常使用") AlertViewType:AlertViewTypeSystemAlert config:nil]; [alertV addAlertItem:^EasyAlertItem *{ return [EasyAlertItem itemWithTitle:GJText(@"取消") type:AlertItemTypeSystemCancel callback:nil]; }]; [alertV addAlertItem:^EasyAlertItem *{ return [EasyAlertItem itemWithTitle:GJText(@"确定") type:AlertItemTypeSystemDefault callback:nil]; }]; [alertV showAlertView]; } - (void)_startVideoLimitTimeoutTimer { self.limit_time = self.model.allCallTime; if (!self.limitVideoTimeoutTimer) { self.limitVideoTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:self.limit_time target:self selector:@selector(_videoLimitAction) userInfo:nil repeats:NO]; } if (!self.limitVideoBeforeShowTimeoutTimer) { self.limitVideoBeforeShowTimeoutTimer= [NSTimer scheduledTimerWithTimeInterval:self.limit_time-DefineVideoLimitBeforeShowTime target:self selector:@selector(_showVideoLimitAction) userInfo:nil repeats:NO]; } } - (void)_stopVideoLimitTimeoutTimer { if (self.limitVideoTimeoutTimer) { [self.limitVideoTimeoutTimer invalidate]; self.limitVideoTimeoutTimer = nil; } if (self.limitVideoBeforeShowTimeoutTimer) { [self.limitVideoBeforeShowTimeoutTimer invalidate]; self.limitVideoBeforeShowTimeoutTimer = nil; } } #pragma mark -- JCCallCallback 通话中接通的函数回调--- -(void)onCallItemAdd:(JCCallItem*)item { if([item.serverCallId isEqualToString:@"-1"]) { return; } // 只有一个拨打 且是 呼入 if (self.call.callItems.count == 1 &&item.direction == JCCallDirectionIn) { //获取 imei eg : imei-XXXXX NSString *imei = [[item.userId componentsSeparatedByString:@"-"] lastObject].uppercaseString; //MARK: 调起页面 if([UIApplication sharedApplication].applicationState == UIApplicationStateBackground){ //在后台的情况 显示提示 //如果 这时候APP标记是死的状态则 不生成自定义通知 (标记是死的(已经调用了applicationWillTerminate)但是还是能够执行代码,很奇怪,弄不懂) NSUserDefaults* userDefault = [[NSUserDefaults alloc] initWithSuiteName:@"group.tongxin"]; if([userDefault valueForKey:@"APPStatus"]){ AppDelegate *app = (AppDelegate *)[UIApplication sharedApplication].delegate; NSString * alertStr; if(item.video){ alertStr = F(GJText(@"%@ %@ 邀请您视频通话"),APIManager.sharedManager.deviceModel.name,APIManager.sharedManager.deviceModel.imei); }else{ alertStr = F(GJText(@"%@ %@ 邀请您语音通话"),APIManager.sharedManager.deviceModel.name,APIManager.sharedManager.deviceModel.imei); } [app generateLocalNotificationsWithAlertStr:alertStr AndDataInfo:self.pushData]; } } if(self.model){ //如果已经存在配置了,可能是之前流程的情况 if(!self.callViewController){ self.model.videoType = !item.video; [self showReceiveCallVC]; }else{ NSLog(@"可能是设备同时拨过来..."); [self.call answer:item video:item.video]; // 根据通话类型开启 扬声器 [self.mediaDevice enableSpeaker:item.video]; } }else{ //如果没有 model 那么调用新接口获取,新流程 kWeakSelf(self) //声网和菊风共用一个 NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; [parameters setValue:@(!item.video) forKey:@"callType"];/// 视频通话类型(0:视频,1:语音) [parameters setValue:APIManager.sharedManager.deviceModel.imei forKey:@"imei"]; [parameters setValue:@"APP" forKey:@"type"];/// 学生证发起传:device,APP发起传:APP [[[APIManager sharedManager] APGET:UserQueryVideo_URL parameters:parameters resultClass:DeviceVideoConfigModel.class] subscribeNext:^(DeviceVideoConfigModel *model) { [UICommon HidenLoading]; self.model = model; if(!item.video){ //语音通话 weakself.model.call_type = 1; }else{ //视频通话 weakself.model.call_type = 0; } [weakself showReceiveCallVC]; } error:^(NSError * _Nullable error) { NSDictionary *dic = error.userInfo; [UICommon MessageErrorText:dic[NSLocalizedDescriptionKey]]; }]; } } } /** 显示 被叫的 视频界面 */ -(void)showReceiveCallVC { UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]; JuPhoonCallViewController *callVC = [sb instantiateViewControllerWithIdentifier:@"JuPhoonCallViewController"]; callVC.video_id = self.model.videoId; callVC.distinguishability_width = self.model.width; callVC.distinguishability_heiger = self.model.height; callVC.device = APIManager.sharedManager.deviceModel; callVC.isReceiveCall = YES; //被叫 if(self.model.call_type == 0){ [self.mediaDevice enableSpeaker:YES]; [callVC openLocationCarmeraViewWithFullSScreen:YES]; }else{ [self.mediaDevice enableSpeaker:NO]; [callVC removeCanvas]; } callVC.modalPresentationStyle = UIModalPresentationFullScreen; self.callViewController = callVC; [self.gMianVC.navigationController presentViewController:callVC animated:YES completion:nil]; } /** * @brief 移除通话 * @param item JCCallItem 对象 * @param reason 通话结束原因 * @param description 通话结束原因的描述,只有被动挂断的时候,才会收到这个值,其他情况下则返回空字符串 * @see JCCallReason */ -(void)onCallItemRemove:(JCCallItem*)item reason:(JCCallReason)reason description:(NSString *)description { if(item.renderId.length == 0 && description.length == 0){ return; } if(item){ if(item.userId != nil && [item.userId containsString:@"-"]){ if(![item.userId containsString:APIManager.sharedManager.deviceModel.imei]){ //不是自己的当前的imei的情况忽略 return; } } } // 通道挂断的说明 NSString *showStatusStr = @""; switch (item.state) { case JCCallStateOk:{ //通话正常结束 switch (reason) { case JCCallReasonNone:{ //无异常 showStatusStr = @"通话结束"; } break; case JCCallReasonTermBySelf:{ //自己挂断 showStatusStr = @""; } break; case JCCallReasonBusy:{ //忙 showStatusStr = [NSString stringWithFormat:@"%@ 忙线",APIManager.sharedManager.deviceModel.name]; } break; case JCCallReasonDecline:{ //拒接 showStatusStr = [NSString stringWithFormat:@"%@ 拒绝了通话",APIManager.sharedManager.deviceModel.name]; } break; case JCCallReasonNetWork:{ //网络异常 showStatusStr = @"网络异常..."; } break; default: NSLog(@"通话正常结束,其他原因, reason:%ld", (long)reason); break; } } break; case JCCallStateCancel:{ //未接通挂断 showStatusStr = [NSString stringWithFormat:@"未接通挂断"]; } break; case JCCallStateCanceled:{ //未接通被挂断 if(reason == JCCallReasonBusy){ showStatusStr = [NSString stringWithFormat:@"%@ 正在通话中...",APIManager.sharedManager.deviceModel.name]; }else{ showStatusStr = [NSString stringWithFormat:@"%@ 拒绝了通话",APIManager.sharedManager.deviceModel.name]; } } break; case JCCallStateMissed:{ //未接 if(item.direction == JCCallDirectionOut){ //呼出 showStatusStr = [NSString stringWithFormat:@"%@ 未接听",APIManager.sharedManager.deviceModel.name]; }else{ // JCCallDirectionIn 呼入 showStatusStr = [NSString stringWithFormat:@"%@ 挂断",APIManager.sharedManager.deviceModel.name]; } } break; case JCCallStateError:{ //异常 showStatusStr = [NSString stringWithFormat:@"异常结束了通话 reason:%ld , des:%@",(long)reason ,description]; } break; default: break; } //MARK: 取消本地推送 AppDelegate *app = (AppDelegate *)[UIApplication sharedApplication].delegate; if(app.backgroudMsg){ [app cleanVideoNotification]; [app setNormallyAlertStr:F(GJText(@"设备 %@ 邀请您通话未接通"),APIManager.sharedManager.deviceModel.imei) UserInfo:nil]; [[JX_GCDTimerManager sharedInstance] cancelTimerWithName:VideoTimerIdentify]; [[JX_GCDTimerManager sharedInstance] cancelTimerWithName:PlayVibrationIdentify]; app.backgroudMsg = nil; app.stopVibration = YES; } [self callEndWithItem:item WithStr:showStatusStr]; } #pragma mark ----方法---- /** MARK:结束通话退出 视频界面 @param item 通话对象 */ -(void)callEndWithItem:(JCCallItem*)item WithStr:(NSString*)str{ [self _stopCallTimeoutTimer]; [self _stopVideoLimitTimeoutTimer]; [self.mediaDevice stopAudio]; [self.mediaDevice stopVideo:self.callViewController.localCanvas]; [self.mediaDevice stopVideo:self.callViewController.remoteCanvas]; if(item && item.talkingBeginTime > 0) { //有通话过的 //MARK:上传视频时间 kWeakSelf(self) int callDuration = [[NSDate date] timeIntervalSince1970] - item.talkingBeginTime; NSMutableDictionary *dics = [NSMutableDictionary dictionary]; [dics setValue:@(callDuration) forKey:@"duration"]; [dics setValue:@(0) forKey:@"waitDuration"]; [dics setValue:APIManager.sharedManager.deviceModel.imei forKey:@"imei"]; [dics setValue:self.model.videoId forKey:@"videoId"]; [[[APIManager sharedManager] APPOST:VideoRecord_URL parameters:dics isJson:YES resultClass:nil] subscribeNext:^(id _Nullable x) { [UICommon HidenLoading]; NSLog(@"上传视频时间成功"); //MARK: 上传完视频时间后删除存储的 Key [weakself endCallVCWith:str]; [UserDefaults removeObjectForKey:VideoTimeAndIDAndWHRecordKey]; } error:^(NSError * _Nullable error) { [weakself endCallVCWith:str]; NSDictionary *dic = error.userInfo; [UICommon MessageErrorText:dic[NSLocalizedDescriptionKey]]; }]; }else{ //没有通话的 [self endCallVCWith:str]; } } -(void)endCallVCWith:(NSString*)str { [self.alertView dismissAnimated:YES completionHandler:nil]; if(self.callViewController){ [self.callViewController stopRing]; [self.callViewController dismissViewControllerAnimated:YES completion:^{ if(str.length != 0){ [UICommon MessageSuccessText:str]; } }]; self.callViewController = nil; } [self _stopVideoLimitTimeoutTimer]; [self _stopCallTimeoutTimer]; //退出登陆 // [self.client logout]; } /** * @brief 通话状态更新回调(当上层收到此回调时,可以根据 JCCallItem 对象获得该通话的所有信息及状态,从而更新该通话相关UI) * @param item JCCallItem 对象 * @param changeParam 更新标识类 */ -(void)onCallItemUpdate:(JCCallItem *)item changeParam:(JCCallChangeParam *)changeParam { NSLog(@"通话状态——->%d",item.state == JCCallStateOk); //会频繁调用 这个回调更新状态 [[NSNotificationCenter defaultCenter] postNotificationName:kCallNotification object:nil]; if(item.state == JCCallStateTalking){ //停止拨打超时计时器 [self _stopCallTimeoutTimer]; [self _startVideoLimitTimeoutTimer]; } else if(item.state == JCCallStateOk || item.state == JCCallStateCancel || item.state == JCCallStateCanceled || item.state == JCCallStateMissed || item.state == JCCallStateError){ //统统 停止拨打超时计时 和 通话时间限制计时器 [self _stopCallTimeoutTimer]; [self _stopVideoLimitTimeoutTimer]; } else if(item.state > 2){ NSLog(@"********************************存在问题... state: %d", item.state); [self _stopCallTimeoutTimer]; [self _stopVideoLimitTimeoutTimer]; } if(item.audioNetSendStatus == JCCallNetWorkDisconnected || item.audioNetReceiveStatus == JCCallNetWorkDisconnected || item.videoNetSendStatus == JCCallNetWorkDisconnected || item.videoNetReceiveStatus == JCCallNetWorkDisconnected){ [self _stopCallTimeoutTimer]; [self _stopVideoLimitTimeoutTimer]; //网络状态无网络, 可能对方掉线了,挂断 [self callEndWithItem:item WithStr:@"通话异常无网络..."]; } } -(void)onMissedCallItem:(JCCallItem *)item { } - (void)onDtmfReceived:(JCCallItem *)item value:(JCCallDtmf)valu { } - (void)onMessageReceive:(JCCallItem *)item type:(NSString *)type content:(NSString *)content { // NSDictionary *dict = @{kMessageTypeKey : type ? type : @"", // kMessageContentKey : content ? content : @""}; // // [[NSNotificationCenter defaultCenter] postNotificationName:kCallMessageNotification object:nil userInfo:dict]; } - (void)onClientStateChange:(JCClientState)state oldState:(JCClientState)oldState { } - (void)onLogin:(bool)result reason:(JCClientReason)reason { if (result) { NSLog(@"登录成功"); _islogin = YES; [[NSNotificationCenter defaultCenter] postNotificationName:kClientOnLoginSuccessNotification object:nil]; //MARK: 菊风登录成功注册VOIP 推送 //AppDelegate *app = (AppDelegate *)[UIApplication sharedApplication].delegate; // // 设置苹果服务器获取的token // [_push addPushInfo:[[JCPushTemplate alloc] initWithToken:app.deviceVoIPTokenData voip:YES debug:YES] ]; // // 设置通话推送信息 设置默认铃声 失效时间为 28 天 // [_push addPushInfo:[[JCPushTemplate alloc] initWithCall:nil expiration:2419200]]; NSLog(@"登录菊风账号成功"); if(self.callViewController && self.callViewController.isReceiveCall == NO){ [self callVideoWithUserName]; } } else { NSLog(@"登录失败"); _islogin = NO; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (!self.islogin) { [self.client login:APIManager.sharedManager.loginModel.openid password:@"123" loginParam:nil]; } }); [[NSNotificationCenter defaultCenter] postNotificationName:kClientOnLoginFailNotification object:nil]; } } -(void)onLogout:(JCClientReason)reason { _islogin = NO; NSLog(@"账号登出: reason: %ld",(long)reason); [[NSNotificationCenter defaultCenter] postNotificationName:kClientOnLogoutNotification object:nil]; } /** * @brief 摄像头变化 */ - (void)onCameraUpdate { } #pragma mark -------JCMediaDeviceCallback ----- /** * @brief 音频输出变化 * @param audioOutputType 音频输出类型 */ - (void)onAudioOutputTypeChange:(NSString *)audioOutputType { NSLog(@"-------------音频输出改变---------audioOutputType : %@ -----------------",audioOutputType); } #pragma mark ---- JCAccountCallback ---- //MARK: 查询设备账号是否在线 /** * @brief 查询账号状态结果回调 * @param operationId 操作ID,由queryUserStatus接口返回 * @param queryResult 查询是否成功 * @param accountItemList 查询结果 */ -(void)onQueryUserStatusResult:(int)operationId result:(BOOL)queryResult accountItemList:(NSArray *)accountItemList{ JCAccountItem* item = accountItemList.firstObject; NSLog(@"查询账号状态结果回调: 查询是否成功:%d 查询ID:%@ , 查询状态:%ld",queryResult,item.userId,(long)item.status); if(!queryResult){ //查询失败 等会重试 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(JuphoonReCallWaitTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if(self.isSeachAndCall){ [self.account queryUserStatus:@[self.model.deviceUserName]]; } }); return; } if(self.isSeachAndCall){ //MARK: 处理一下大小写问题 if ([item.userId.uppercaseString isEqualToString:self.model.deviceUserName.uppercaseString]){ if(item.status == JCAccountUserStatusOnline){ //MARK: 查询到 用户在线后拨打操作, 重新计算拨打超时定时器 [self _startCallTimeoutTimer]; //修改状态,并开启 self.callViewController.statusLabel.text = F(GJText(@"正在等待 %@ 接听..."),APIManager.sharedManager.deviceModel.name); BOOL isVideo = (BOOL)!self.model.call_type; self.call.maxCallNum = 1; //[self.call call:self.model.deviceUserName video:isVideo extraParam:self.model.mj_JSONString ]; [self.call call:self.model.deviceUserName video:isVideo callParam:[JCCallParam callParamWithExtraParam:@"video" ticket:@""]]; }else{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(JuphoonReCallWaitTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if(self.isSeachAndCall){ [self.account queryUserStatus:@[self.model.deviceUserName]]; } }); if(item.status == JCAccountUserStatusError){ NSLog(@"查询账号状态结果异常"); } } } } } -(JCCallItem*)getActiveCall { for (JCCallItem* item in self.call.callItems) { if (item.active) { return item; } } return nil; } @end