VideoManager.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. //
  2. // VideoManager.m
  3. // SikeyComm
  4. //
  5. // Created by 刘振兴 on 2024/3/21.
  6. // Copyright © 2024 BaH Cy. All rights reserved.
  7. //
  8. #import "VideoManager.h"
  9. #import "VideoManager+timer.h"
  10. @interface VideoManager() <JCClientCallback, JCMediaDeviceCallback, JCCallCallback>
  11. @end
  12. @implementation VideoManager
  13. + (VideoManager *)shared {
  14. static VideoManager *manager;
  15. static dispatch_once_t token;
  16. dispatch_once(&token, ^{
  17. manager = [[VideoManager alloc] init];
  18. });
  19. return manager;
  20. }
  21. //初始化
  22. - (BOOL)initJuphoon {
  23. self.mJuphoonClient = [JCClient create:APP_KEY_JUPHOON callback:self createParam:nil];
  24. self.mJuphoonMediaDevice = [JCMediaDevice create:self.mJuphoonClient callback:self];
  25. self.mJuphoonCall = [JCCall create:self.mJuphoonClient mediaDevice:self.mJuphoonMediaDevice callback:self];
  26. self.mJuphoonCall.mediaConfig = [JCCallMediaConfig generateByMode:JCCallMediaConfigModeIOT];
  27. // 现在设置屏幕方向固定,竖直角度
  28. //self.mJuphoonMediaDevice.autoRotate = false;
  29. //[self.mJuphoonMediaDevice setSawtooth:YES];
  30. [self.mJuphoonMediaDevice setCameraProperty:640 height:360 framerate:10];
  31. //Zmf_VideoCaptureListenRotation(0, 0);
  32. //Zmf_VideoRenderListenRotation(0,0);
  33. return self.mJuphoonClient.state == JCClientStateIdle;
  34. }
  35. - (void)loginJuphoon {
  36. NSString* uid = [DataManager shared].loginModel.uid;
  37. NSString* password = @"123456";
  38. JCClientLoginParam* loginParam = [[JCClientLoginParam alloc] init];
  39. BOOL isOK = [self.mJuphoonClient login:uid password:password loginParam:loginParam];
  40. HDNormalLog(([NSString stringWithFormat:@"SKJuphoon: login call: %d", isOK]));
  41. }
  42. - (void)logoutJuphoon {
  43. if (self.mJuphoonClient) {
  44. [self.mJuphoonClient logout];
  45. }
  46. }
  47. #pragma mark - JCMediaDeviceCallback
  48. /**
  49. * 登录结果回调
  50. *
  51. * 在调用 {@link JCClient#login:password:loginParam: login} 方法成功后会收到此回调
  52. *
  53. * @param result 登录是否成功
  54. * - true:登录成功
  55. * - false:登录失败
  56. * @param reason 登录失败原因,当 result 为 false 时该值有效。参见:@ref JCClientReason "错误原因"
  57. */
  58. - (void)onLogin:(bool)result reason:(JCClientReason)reason {
  59. @weakify(self);
  60. HDNormalLog(([NSString stringWithFormat:@"SKJuphoon: login Result:%d Reason:%ld", result, reason]));
  61. if (result) {// 登录成功
  62. } else if (reason == JCClientReasonAuth) {// 账号密码错误
  63. } else {
  64. //如果没有成功//
  65. if (reason != JCClientReasonNone && reason != JCClientReasonStateCannotLogin) {
  66. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  67. [weak_self loginJuphoon];
  68. });
  69. }
  70. }
  71. }
  72. /**
  73. * 登出回调
  74. *
  75. * 在调用 {@link JCClient#logout logout} 方法成功会收到此回调
  76. *
  77. * @param reason 登出失败原因,参见:@ref JCClientReason "错误原因"
  78. */
  79. - (void)onLogout:(JCClientReason)reason {
  80. HDNormalLog(@"SKJuphoon: onLogout");
  81. }
  82. /**
  83. * 登录状态变化回调
  84. *
  85. * 在用户的登录状态发生改变时会收到此回调,登录状态:
  86. * - @ref JCClientStateNotInit : 未初始化
  87. * - @ref JCClientStateIdle : 未登录
  88. * - @ref JCClientStateLogining : 登录中
  89. * - @ref JCClientStateLogined : 登录成功
  90. * - @ref JCClientStateLogouting : 登出中
  91. *
  92. * @param state 当前状态值
  93. * @param oldState 之前状态值
  94. */
  95. - (void)onClientStateChange:(JCClientState)state oldState:(JCClientState)oldState {
  96. if (state == JCClientStateIdle) {
  97. [self loginJuphoon];
  98. }
  99. }
  100. #pragma mark - JCCallCallback
  101. /**
  102. * 新增通话回调
  103. *
  104. * 当上层收到此回调时,可以根据 JCCallItem 对象获得该通话的所有信息及状态,从而更新该通话相关UI
  105. *
  106. * @param item JCCallItem 对象
  107. */
  108. - (void)onCallItemAdd:(JCCallItem* __nonnull)item {
  109. HDNormalLog(([NSString stringWithFormat:@"SKJuphoon: onCallItemAdd direction: %ld", (long)item.direction]));
  110. NSDictionary *dic = @{kCallIetmKey:item};
  111. [[NSNotificationCenter defaultCenter] postNotificationName:kCallNotification object:nil userInfo:dic];
  112. if (self.mJuphoonCall.callItems.count == 1) {
  113. [self showInCallVC];
  114. }
  115. }
  116. /**
  117. * 移除通话回调
  118. *
  119. * 当上层收到此回调时,可以根据 JCCallItem 对象获得该通话的所有信息及状态,从而更新该通话相关UI
  120. *
  121. * @param item JCCallItem 对象
  122. * @param reason 通话结束原因,参见:@ref JCCallReason "通话结束原因"
  123. * @param description 通话结束原因的描述,只有被动挂断的时候,才会收到这个值,其他情况下则返回空字符串
  124. */
  125. - (void)onCallItemRemove:(JCCallItem *)item reason:(JCCallReason)reason description:(NSString *)description {
  126. HDNormalLog(([NSString stringWithFormat:@"SKJuphoon: onCallItemRemove reason:%ld", reason]));
  127. [[NSNotificationCenter defaultCenter] postNotificationName:kCallNotification object:nil];
  128. [self removeInCallVC];
  129. switch (reason) {
  130. case JCCallReasonNone:
  131. [EasyTextView showInfoText:[NSString stringWithFormat:NSLocalizedString(@"Video.Error.Hangup", nil)]];
  132. break;
  133. case JCCallReasonNotFound:
  134. [EasyTextView showErrorText:[NSString stringWithFormat:NSLocalizedString(@"Video.Error.User.No", nil)]];
  135. break;
  136. case JCCallReasonUserOffline:
  137. [EasyTextView showErrorText:[NSString stringWithFormat:NSLocalizedString(@"Video.Error.User.Offline", nil)]];
  138. [self loginJuphoon];
  139. break;
  140. case JCCallReasonNotLogin:
  141. [EasyTextView showErrorText:[NSString stringWithFormat:NSLocalizedString(@"Video.Error.User.Login", nil)]];
  142. break;
  143. case JCCallReasonTimeOut:
  144. //[EasyTextView showErrorText:[NSString stringWithFormat:NSLocalizedString(@"Video.Error.Timeout", nil)]];
  145. break;
  146. case JCCallReasonNetWork:
  147. [EasyTextView showErrorText:[NSString stringWithFormat:NSLocalizedString(@"Video.Error.Network", nil)]];
  148. break;
  149. default: {
  150. break;
  151. }
  152. }
  153. }
  154. /**
  155. * 通话状态更新回调
  156. *
  157. * 当上层收到此回调时,可以根据 JCCallItem 对象获得该通话的所有信息及状态,从而更新该通话相关UI
  158. *
  159. * @param item JCCallItem 对象
  160. * @param changeParam JCCallChangeParam 更新标识对象
  161. */
  162. - (void)onCallItemUpdate:(JCCallItem* __nonnull)item changeParam:(JCCallChangeParam * __nullable)changeParam {
  163. if (item.state == JCCallStateTalking && [self.mInCallVC getRemoteCanvas] == nil && item.uploadVideoStreamOther) {
  164. [self stopVideoTimer];
  165. }
  166. [[NSNotificationCenter defaultCenter] postNotificationName:kCallNotification object:nil];
  167. }
  168. - (void)requestVideo:(NSString*)sid ticket:(NSString*)ticket dial:(NSInteger)dial {
  169. HDNormalLog(([NSString stringWithFormat:@"SKJuphoon: requestVideo ticket:%@ dial:%ld", ticket, dial]));
  170. NSDictionary *param = @{
  171. @"sid":sid,
  172. @"roomId":ticket,
  173. @"dial":@(dial), //是否是拨号 -1 挂断 1 拨号
  174. };
  175. NSString* url = dial == 1? URL_VIDEO_DIAL : URL_VIDEO_HANGUP;
  176. [ERequest httpRequest:param httpURL:url httpMethod:@"GET" onSuccess:^(NSDictionary *result) {
  177. if ([ERequest isSuccessWithResult:result]) {
  178. } else {
  179. [EasyTextView showErrorText:result[@"message"]];
  180. }
  181. } onFailure:^(NSError *error) {
  182. [EasyTextView showErrorText:NSLocalizedString(@"Network.Error", nil)];
  183. }];
  184. }
  185. - (void)videoStart:(NSString*)sid ticket:(NSString*)ticket dial:(NSInteger)dial {
  186. BOOL isReachable = [[AFNetworkReachabilityManager sharedManager] isReachable];
  187. if (!isReachable) {
  188. [EasyTextView showErrorText:NSLocalizedString(@"Network.Error", nil)];
  189. return;
  190. }
  191. HDNormalLog(([NSString stringWithFormat:@"SKJuphoon: videoStart: sid:%@ ticket:%@ dial:%ld", sid, ticket, dial]));
  192. self.mSID = sid;
  193. self.mTicket = ticket;
  194. JCCallParam* callParam = [JCCallParam callParamWithExtraParam:@"" ticket:ticket];
  195. BOOL isok = [self.mJuphoonCall call:ticket video:true callParam:callParam];
  196. if (!isok) {
  197. //如果失败,很有可能登录失败,重新登录//
  198. [self loginJuphoon];
  199. [EasyTextView showErrorText:[NSString stringWithFormat:NSLocalizedString(@"Video.Error.User.Login", nil)]];
  200. } else {
  201. [self requestVideo:sid ticket:ticket dial:1];
  202. [self startVideoTimer];
  203. }
  204. }
  205. - (void)videoEnd:(BOOL)isIncomming isSendMessage:(BOOL)isSendMessage {
  206. HDNormalLog(([NSString stringWithFormat:@"SKJuphoon: videoEnd:%d", isIncomming]));
  207. //发消息
  208. if (isSendMessage) {
  209. JCCallItem *activeCall = self.mJuphoonCall.callItems.firstObject;
  210. if(activeCall.state == JCCallStateInit || activeCall.state == JCCallStatePending) {
  211. NSString* ticket = [self getJuphoonName];
  212. NSString* sid = [self getJuphoonSID];
  213. [self requestVideo:sid ticket:ticket dial:-1];
  214. }
  215. }
  216. [self autoTerm];
  217. }
  218. #pragma mark -
  219. - (void)actionFromSocket:(SocketModel*)model dial:(NSInteger)dial {
  220. if (dial <= 0) {
  221. NSString* talkId = [self getJuphoonName];
  222. ChildModel* childModel = [[DataManager shared] getChildWithTicket:talkId];
  223. BOOL isEquel = [model.sendId isEqualToString:childModel.cid];
  224. if (isEquel) {
  225. [EasyTextView showInfoText:NSLocalizedString(@"Video.Error.Hangup", nil)];
  226. [self autoTerm];
  227. }
  228. } else {
  229. [self loginJuphoon];
  230. }
  231. }
  232. - (void)showInCallVC {
  233. if (self.mInCallVC)
  234. return;
  235. NSLog(@"SKJuphoon: showInCallVC");
  236. UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
  237. self.mInCallVC = [storyboard instantiateViewControllerWithIdentifier:@"InCallVC"];
  238. [self.mInCallVC setHidesBottomBarWhenPushed:YES];
  239. [EUtil pushViewController:self.mInCallVC animated:YES];
  240. }
  241. - (void)removeInCallVC {
  242. if (self.mInCallVC) {
  243. [self.mInCallVC.navigationController popViewControllerAnimated:YES];
  244. }
  245. self.mInCallVC = nil;
  246. }
  247. //挂断
  248. - (void)autoTerm {
  249. for (JCCallItem* item in self.mJuphoonCall.callItems) {
  250. if (item.active) {
  251. [self.mJuphoonCall term:item reason:JCCallReasonNone description:@"test"];
  252. return;
  253. }
  254. }
  255. }
  256. - (void)autoTermWithTimeout {
  257. NSString* sid = [self getJuphoonSID];
  258. NSString* ticket = [self getJuphoonName];
  259. if (!ticket || ticket.length <= 0)
  260. return;
  261. [EasyTextView showErrorText:NSLocalizedString(@"Video.Error.Timeout", nil)];
  262. [self requestVideo:sid ticket:ticket dial:-1];
  263. [self autoTerm];
  264. }
  265. - (NSString*)getJuphoonSID {
  266. JCCallItem *activeCall = self.mJuphoonCall.callItems.firstObject;
  267. if (activeCall.direction == JCCallDirectionOut) {
  268. return self.mSID;
  269. } else {
  270. return activeCall.userId;
  271. }
  272. }
  273. - (NSString*)getJuphoonName {
  274. JCCallItem *activeCall = self.mJuphoonCall.callItems.firstObject;
  275. if (activeCall.direction == JCCallDirectionOut) {
  276. return self.mTicket;
  277. } else {
  278. return activeCall.userId;
  279. }
  280. }
  281. - (NSString*)getTalkName {
  282. NSString* talkId = [self getJuphoonName];
  283. ChildModel* model = [[DataManager shared] getChildWithTicket:talkId];
  284. return model ? model.name : @"";
  285. }
  286. - (NSString*)getTalkHeadImageURL {
  287. NSString* talkId = [self getJuphoonName];
  288. ChildModel* model = [[DataManager shared] getChildWithTicket:talkId];
  289. return model ? model.avatar : @"";
  290. }
  291. @end