You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

674 lines
24 KiB

//
// 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 () <JCClientCallback, JCMediaDeviceCallback, JCCallCallback ,JCAccountCallback>
@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<JCAccountItem *> *)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