KSChatConversationViewController.m 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. //
  2. // KSChatConversationViewController.m
  3. // KulexiuForTeacher
  4. //
  5. // Created by Kyle on 2022/3/23.
  6. //
  7. #import "KSChatConversationViewController.h"
  8. #import "GroupSettingViewController.h"
  9. #import "KSTabBarViewController.h"
  10. #import "UIButton+EnlargeEdge.h"
  11. #import "CustomNavViewController.h"
  12. #import <AVFoundation/AVFoundation.h>
  13. #import "KSBaseWKWebViewController.h"
  14. #import "WMPlayer.h"
  15. #import "KSRCloudMediaManager.h"
  16. #import "KSSelectConversationViewController.h"
  17. #import "GroupMemberModel.h"
  18. #define SHARE_MUSIC_TAG (2001)
  19. #import "KSChatLiveMessage.h"
  20. #import "KSChatLiveShareCell.h"
  21. #import "KSChatMusicShareCell.h"
  22. #import "KSChatMusicMessage.h"
  23. #import "KSEnterLiveroomManager.h"
  24. #import "KSAccompanyWebViewController.h"
  25. #import "KSPublicAlertView.h"
  26. @interface RCNaviDataInfo : NSObject
  27. @property (nonatomic, assign) NSTimeInterval uploadVideoDurationLimit;
  28. @end
  29. @interface RCNaviDataManager : NSObject
  30. @property (nonatomic, strong) RCNaviDataInfo *naviData;
  31. + (instancetype)sharedInstance;
  32. @end
  33. @interface RCSightMessage ()
  34. @property (nonatomic, readonly) AVAsset *asset;
  35. @property (nonatomic, assign) long long size;
  36. + (instancetype)messageWithAsset:(AVAsset *)asset thumbnail:(UIImage *)image duration:(NSUInteger)duration;
  37. @end
  38. @interface KSChatConversationViewController ()<RCMessageCellDelegate,UIAlertViewDelegate,UIActionSheetDelegate,WMPlayerDelegate>
  39. {
  40. WMPlayer *_wmPlayer;
  41. CGRect _playerFrame;
  42. }
  43. @property (nonatomic, strong) UIView *bgView;
  44. @property (nonatomic, assign) BOOL isRatation;
  45. @property (nonatomic, assign) BOOL isFirstLoad;
  46. @property (nonatomic, strong) KSPublicAlertView *alertView;
  47. @end
  48. @implementation KSChatConversationViewController
  49. - (void)viewDidLoad {
  50. [super viewDidLoad];
  51. // Do any additional setup after loading the view.
  52. if (self.conversationType == ConversationType_PRIVATE) {
  53. self.displayUserNameInCell = NO;
  54. }
  55. else if (self.conversationType == ConversationType_SYSTEM) {
  56. self.displayUserNameInCell = NO;
  57. self.chatSessionInputBarControl.hidden = YES;
  58. self.conversationMessageCollectionView.frame = CGRectMake(0, kNaviBarHeight, kScreenWidth, kScreenHeight - iPhoneXSafeBottomMargin - kNaviBarHeight);
  59. }
  60. if (self.conversationType != ConversationType_APPSERVICE &&
  61. self.conversationType != ConversationType_PUBLICSERVICE) {
  62. //加号区域增加发送文件功能,Kit中已经默认实现了该功能,但是为了SDK向后兼容性,目前SDK默认不开启该入口,可以参考以下代码在加号区域中增加发送文件功能。
  63. UIImage *imageFile = [UIImage imageNamed:@"actionbar_file_icon"];
  64. RCPluginBoardView *pluginBoardView = self.chatSessionInputBarControl.pluginBoardView;
  65. [pluginBoardView insertItem:imageFile
  66. highlightedImage:imageFile
  67. title:@"文件"
  68. atIndex:3
  69. tag:PLUGIN_BOARD_ITEM_FILE_TAG];
  70. }
  71. // 注册自定义消息
  72. [self registerClass:[KSChatLiveShareCell class] forMessageClass:[KSChatLiveMessage class]];
  73. [self registerClass:[KSChatMusicShareCell class] forMessageClass:[KSChatMusicMessage class]];
  74. [self leftRightButton];
  75. [self refreshUserInfoOrGroupInfo];
  76. self.isFirstLoad = YES;
  77. // [self.chatSessionInputBarControl.pluginBoardView removeItemAtIndex:2];
  78. }
  79. - (void)viewWillAppear:(BOOL)animated {
  80. [super viewWillAppear:animated];
  81. if (self.isFirstLoad == NO) {
  82. [self refreshUserInfoOrGroupInfo];
  83. }
  84. [self refreshTitle];
  85. }
  86. - (void)viewDidAppear:(BOOL)animated {
  87. [super viewDidAppear:animated];
  88. self.isFirstLoad = NO;
  89. [IQKeyboardManager sharedManager].enableAutoToolbar = NO;
  90. }
  91. - (void)viewWillDisappear:(BOOL)animated {
  92. [super viewWillDisappear:animated];
  93. int unreadMsgCount = [[RCIMClient sharedRCIMClient] getUnreadCount:@[
  94. @(ConversationType_PRIVATE), @(ConversationType_APPSERVICE), @(ConversationType_GROUP),@(ConversationType_SYSTEM)
  95. ]];
  96. if (unreadMsgCount >= 1) {
  97. [(KSTabBarViewController *)self.tabBarController noteNewsWithIndex:2 count:unreadMsgCount];
  98. } else {
  99. [(KSTabBarViewController *)self.tabBarController clearNewsWithIndex:2];
  100. }
  101. [IQKeyboardManager sharedManager].enableAutoToolbar = YES;
  102. }
  103. - (void)notifyUpdateUnreadMessageCount {
  104. if (self.allowsMessageCellSelection) {
  105. [super notifyUpdateUnreadMessageCount];
  106. return;
  107. }
  108. [self leftRightButton];
  109. }
  110. - (void)leftRightButton {
  111. dispatch_main_async_safe(^{
  112. UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"back_black"] style:UIBarButtonItemStylePlain target:self action:@selector(backAction)];
  113. leftButton.imageInsets = UIEdgeInsetsMake(0, -5, 0, 0);
  114. leftButton.tintColor = [UIColor blackColor];
  115. self.navigationItem.leftBarButtonItem = leftButton;
  116. if (self.conversationType == ConversationType_GROUP) {
  117. [self rightButtonWithImage:@"group_setting"];
  118. }
  119. else if (self.conversationType == ConversationType_SYSTEM || self.conversationType == ConversationType_PRIVATE) {
  120. self.navigationItem.rightBarButtonItem = nil;
  121. }
  122. });
  123. }
  124. - (void)rightButtonWithImage:(NSString *)imageName {
  125. UIButton *rightBt = [UIButton buttonWithType:UIButtonTypeCustom];
  126. rightBt.frame =CGRectMake(0, 0, 80, 40);
  127. rightBt.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, -40);
  128. [rightBt setEnlargeEdgeWithTop:10 right:10 bottom:10 left:10];
  129. rightBt.titleLabel.font = [UIFont systemFontOfSize:16];
  130. [rightBt setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  131. [rightBt setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
  132. [rightBt addTarget:self action:@selector(rightBtnClick) forControlEvents:UIControlEventTouchUpInside];
  133. UIBarButtonItem *rightItem = [[UIBarButtonItem alloc]initWithCustomView:rightBt];
  134. self.navigationItem.rightBarButtonItem = rightItem;
  135. }
  136. - (void)backAction {
  137. [self.navigationController popViewControllerAnimated:YES];
  138. }
  139. - (void)rightBtnClick {
  140. GroupSettingViewController *groupVC = [[GroupSettingViewController alloc] init];
  141. groupVC.groupId = self.targetId;
  142. [self.navigationController pushViewController:groupVC animated:YES];
  143. }
  144. - (NSArray<UIMenuItem *> *)getLongTouchMessageCellMenuList:(RCMessageModel *)model {
  145. if (self.conversationType == ConversationType_SYSTEM) {
  146. return [NSArray array];
  147. }
  148. return [super getLongTouchMessageCellMenuList:model];
  149. }
  150. // 转发
  151. - (void)forwardMessage:(NSInteger)index
  152. completed:(void (^)(NSArray<RCConversation *> *conversationList))completedBlock {
  153. KSSelectConversationViewController *forwardSelectedVC = [[KSSelectConversationViewController alloc]
  154. initSelectConversationViewControllerCompleted:^(NSArray<RCConversation *> *conversationList) {
  155. completedBlock(conversationList);
  156. }];
  157. [self.navigationController pushViewController:forwardSelectedVC animated:NO];
  158. }
  159. #pragma mark override
  160. // 点击消息的处理
  161. - (void)didTapMessageCell:(RCMessageModel *)model {
  162. if ([model.objectName isEqualToString:@"RC:CHATSHARE:LIVE"]) { // 直播消息
  163. KSChatLiveMessage *liveShareMsg = (KSChatLiveMessage *)model.content;
  164. [KSEnterLiveroomManager joinLiveWithRoomId:liveShareMsg.roomUID inController:(CustomNavViewController *)self.navigationController callback:^{
  165. }];
  166. }
  167. else if ([model.objectName isEqualToString:@"RC:CHATSHARE:MUSIC"]) { // 曲谱消息
  168. KSChatMusicMessage *musicShareMsg = (KSChatMusicMessage *)model.content;
  169. KSAccompanyWebViewController *detailCtrl = [[KSAccompanyWebViewController alloc] init];
  170. detailCtrl.url = [NSString stringWithFormat:@"%@/accompany?id=%@",hostURL, musicShareMsg.songId];
  171. detailCtrl.hiddenNavBar = YES;
  172. detailCtrl.parmDic = @{@"isOpenLight" : @(YES), @"orientation" : @(0),@"isHideTitle" : @(YES)};
  173. [self.navigationController pushViewController:detailCtrl animated:YES];
  174. }
  175. else {
  176. [super didTapMessageCell:model];
  177. }
  178. }
  179. - (void)playVideoWithUrl:(NSString *)fileUrl {
  180. fileUrl = [fileUrl stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
  181. _playerFrame = CGRectMake(0, iPhoneXSafeTopMargin, kScreenWidth, kScreenHeight - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin);
  182. _wmPlayer = [[WMPlayer alloc] initWithFrame:_playerFrame];
  183. WMPlayerModel *playModel = [[WMPlayerModel alloc] init];
  184. playModel.videoURL = [NSURL URLWithString:fileUrl];
  185. _wmPlayer.playerModel = playModel;
  186. _wmPlayer.delegate = self;
  187. _bgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight)];
  188. _bgView.backgroundColor = [UIColor blackColor];
  189. [[UIApplication sharedApplication].keyWindow addSubview:_bgView];
  190. [[UIApplication sharedApplication].keyWindow addSubview:_wmPlayer];
  191. [[UIApplication sharedApplication].keyWindow bringSubviewToFront:_wmPlayer];
  192. [_wmPlayer play];
  193. }
  194. - (void)wmplayer:(WMPlayer *)wmplayer clickedCloseButton:(UIButton *)backBtn {
  195. [wmplayer removePlayer];
  196. [_bgView removeFromSuperview];
  197. [self setNeedsStatusBarAppearanceUpdate];
  198. }
  199. - (void)wmplayer:(WMPlayer *)wmplayer clickedFullScreenButton:(UIButton *)fullScreenBtn {
  200. self.isRatation = !self.isRatation;
  201. if (self.isRatation) {
  202. [wmplayer removeFromSuperview];
  203. [UIView animateWithDuration:1.0f animations:^{
  204. wmplayer.transform = CGAffineTransformMakeRotation(M_PI_2);
  205. } completion:^(BOOL finished) {
  206. wmplayer.frame = CGRectMake(0, iPhoneXSafeTopMargin, kScreenWidth, kScreenHeight - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin);
  207. [[UIApplication sharedApplication].keyWindow addSubview:wmplayer];
  208. [[UIApplication sharedApplication].keyWindow bringSubviewToFront:wmplayer];
  209. }];
  210. }
  211. else {
  212. [wmplayer removeFromSuperview];
  213. [UIView animateWithDuration:1.0f animations:^{
  214. // 复原
  215. wmplayer.transform = CGAffineTransformIdentity;
  216. } completion:^(BOOL finished) {
  217. wmplayer.frame = CGRectMake(0, iPhoneXSafeTopMargin, kScreenWidth, kScreenHeight - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin);
  218. [[UIApplication sharedApplication].keyWindow addSubview:wmplayer];
  219. [[UIApplication sharedApplication].keyWindow bringSubviewToFront:wmplayer];
  220. }];
  221. }
  222. }
  223. - (void)didTapUrlInMessageCell:(NSString *)url model:(RCMessageModel *)model {
  224. if ([url containsString:@"dayaedu"]) {
  225. KSBaseWKWebViewController *ctrl = [[KSBaseWKWebViewController alloc] init];
  226. ctrl.url = url;
  227. [self.navigationController pushViewController:ctrl animated:YES];
  228. }
  229. else if ([url containsString:@"daya"] && [url hasSuffix:@".mp4"]) {
  230. [self playVideoWithUrl:url];
  231. }
  232. else {
  233. [super didTapUrlInMessageCell:url model:model];
  234. }
  235. }
  236. - (RCMessageContent *)willSendMessage:(RCMessageContent *)messageContent {
  237. //可以在这里修改将要发送的消息
  238. if ([messageContent isMemberOfClass:[RCTextMessage class]]) {
  239. // RCTextMessage *textMsg = (RCTextMessage *)messageContent;
  240. // textMsg.extra = @"";
  241. }
  242. return messageContent;
  243. }
  244. - (void)resendMessage:(RCMessageContent *)messageContent {
  245. if ([messageContent isKindOfClass:[RCSightMessage class]]) {
  246. RCSightMessage *sightMsg = (RCSightMessage *)messageContent;
  247. [self sendMediaMessage:sightMsg pushContent:nil appUpload:YES];
  248. }
  249. else {
  250. [super resendMessage:messageContent];
  251. }
  252. }
  253. #pragma mark --- refresh
  254. - (void)refreshUserInfoOrGroupInfo {
  255. if (self.conversationType == ConversationType_PRIVATE) {
  256. if (![self.targetId isEqualToString:[RCIM sharedRCIM].currentUserInfo.userId]) {
  257. [KSNetworkingManager imUserFriendQueryDetail:KS_POST userId:self.targetId success:^(NSDictionary * _Nonnull dic) {
  258. if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
  259. NSDictionary *userDic = [dic dictionaryValueForKey:@"data"];
  260. RCUserInfo *user = [[RCUserInfo alloc] initWithUserId:self.targetId name:[userDic stringValueForKey:@"friendNickname"] portrait:[userDic stringValueForKey:@"friendAvatar"]];
  261. [[RCIM sharedRCIM] refreshUserInfoCache:user withUserId:self.targetId];
  262. [self refreshTitle];
  263. }
  264. else {
  265. }
  266. } faliure:^(NSError * _Nonnull error) {
  267. }];
  268. }
  269. }
  270. else if (self.conversationType == ConversationType_GROUP) {
  271. // 获取群成员
  272. [KSNetworkingManager imGroupMemberAllRequest:KS_POST groupId:self.targetId success:^(NSDictionary * _Nonnull dic) {
  273. if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
  274. NSArray *sourceArray = [dic arrayValueForKey:@"data"];
  275. for (NSDictionary *parm in sourceArray) {
  276. if ([parm isKindOfClass:[NSNull class]]) {
  277. continue;
  278. }
  279. GroupMemberModel *model = [[GroupMemberModel alloc] initWithDictionary:parm];
  280. // 刷新缓存
  281. RCUserInfo *user = [[RCUserInfo alloc] initWithUserId:model.userId name:model.nickname portrait:model.avatar];
  282. [[RCIM sharedRCIM] refreshGroupUserInfoCache:user withUserId:model.userId withGroupId:self.targetId];
  283. }
  284. }
  285. else {
  286. }
  287. } faliure:^(NSError * _Nonnull error) {
  288. }];
  289. // 获取群组信息
  290. [KSNetworkingManager queryGroupDetail:KS_POST groupId:self.targetId success:^(NSDictionary * _Nonnull dic) {
  291. if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
  292. NSDictionary *result = [dic dictionaryValueForKey:@"data"];
  293. RCGroup *groupInfo = [[RCGroup alloc] initWithGroupId:self.targetId groupName:[result stringValueForKey:@"name"] portraitUri:[result stringValueForKey:@"img"]];
  294. [[RCIM sharedRCIM] refreshGroupInfoCache:groupInfo withGroupId:self.targetId];
  295. [self refreshTitle];
  296. }
  297. else if ([dic integerValueForKey:@"code"] == 204) {
  298. // 弹窗提示是否删除群组
  299. MJWeakSelf;
  300. self.alertView = [KSPublicAlertView shareInstanceWithTitle:@"提示" descMessage:@"当前群组已被解散,是否删除聊天记录?" leftTitle:@"取消" rightTitle:@"确定" cancelAction:^{
  301. } sureAction:^{
  302. [weakSelf removeNoExistGroup];
  303. }];
  304. }
  305. } faliure:^(NSError * _Nonnull error) {
  306. }];
  307. }
  308. }
  309. - (void)removeNoExistGroup {
  310. [[RCIMClient sharedRCIMClient] removeConversation:ConversationType_GROUP targetId:self.targetId];
  311. [self.navigationController popToRootViewControllerAnimated:YES];
  312. }
  313. - (void)refreshTitle {
  314. if (self.conversationType == ConversationType_GROUP) {
  315. RCGroup *group = [[RCIM sharedRCIM] getGroupInfoCache:self.targetId];
  316. if (![NSString isEmptyString:group.groupName]) {
  317. self.title = group.groupName;
  318. }
  319. }
  320. else {
  321. RCUserInfo *userInfo = [[RCIM sharedRCIM] getUserInfoCache:self.targetId];
  322. if (![NSString isEmptyString:userInfo.name]) {
  323. self.title = userInfo.name;
  324. }
  325. }
  326. }
  327. - (void)pluginBoardView:(RCPluginBoardView *)pluginBoardView clickedItemWithTag:(NSInteger)tag {
  328. switch (tag) {
  329. case SHARE_MUSIC_TAG: // 曲谱分享
  330. {
  331. }
  332. break;
  333. default:
  334. {
  335. [RCNaviDataManager sharedInstance].naviData.uploadVideoDurationLimit = 480.0f;
  336. [super pluginBoardView:pluginBoardView clickedItemWithTag:tag];
  337. }
  338. break;
  339. }
  340. }
  341. - (void)willDisplayMessageCell:(RCMessageBaseCell *)cell atIndexPath:(NSIndexPath *)indexPath {
  342. if ([cell isKindOfClass:[RCMessageCell class]]) {
  343. UIImageView *imageView = (UIImageView *)((RCMessageCell *)cell).portraitImageView;
  344. imageView.contentMode = UIViewContentModeScaleAspectFill;
  345. imageView.layer.masksToBounds = YES;
  346. }
  347. }
  348. #pragma mark ----- chatSessionInputBarControl delegate
  349. - (void)sightDidFinishRecord:(NSString *)url thumbnail:(UIImage *)image duration:(NSUInteger)duration {
  350. RCSightMessage *sightMessage = [RCSightMessage messageWithLocalPath:url thumbnail:image duration:duration];
  351. [self sendMediaMessage:sightMessage pushContent:nil appUpload:YES];
  352. }
  353. - (void)imageDataDidSelect:(NSArray *)selectedImages fullImageRequired:(BOOL)full {
  354. [self becomeFirstResponder];
  355. __weak KSChatConversationViewController *weakSelf = self;
  356. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  357. for (int i = 0; i < selectedImages.count; i++) {
  358. @autoreleasepool {
  359. id item = [selectedImages objectAtIndex:i];
  360. if ([item isKindOfClass:NSData.class]) {
  361. NSData *imageData = (NSData *)item;
  362. UIImage *image = [UIImage imageWithData:imageData];
  363. image = [RCKitUtility fixOrientation:image];
  364. // 保留原有逻辑并添加大图缩小的功能
  365. [[KSRCloudMediaManager sharedManager] downsizeImage:image completionBlock:^(UIImage * _Nullable outimage, BOOL doNothing) {
  366. RCImageMessage *imagemsg;
  367. if (doNothing || !outimage) {
  368. imagemsg = [RCImageMessage messageWithImage:image];
  369. imagemsg.full = full;
  370. } else if (outimage) {
  371. NSData *newImageData = UIImageJPEGRepresentation(outimage, 1);
  372. imagemsg = [RCImageMessage messageWithImageData:newImageData];
  373. imagemsg.full = full;
  374. }
  375. [weakSelf sendMessage:imagemsg pushContent:nil];
  376. } progressBlock:^(UIImage * _Nullable outimage, BOOL doNothing) {
  377. }];
  378. }
  379. else if ([item isKindOfClass:NSDictionary.class]) {
  380. NSDictionary *assertInfo = item;
  381. if ([assertInfo objectForKey:@"avAsset"]) {
  382. AVAsset *model = assertInfo[@"avAsset"];
  383. UIImage *image = assertInfo[@"thumbnail"];
  384. NSString *localPath = assertInfo[@"localPath"];
  385. if (![localPath isKindOfClass:[NSString class]]) {
  386. localPath = @"";
  387. }
  388. dispatch_sync(dispatch_get_main_queue(), ^{
  389. NSUInteger duration = round(CMTimeGetSeconds(model.duration));
  390. RCSightMessage *sightMsg =
  391. [RCSightMessage messageWithAsset:model thumbnail:image duration:duration];
  392. sightMsg.localPath = localPath;
  393. [weakSelf sendMediaMessage:sightMsg pushContent:nil appUpload:YES];
  394. });
  395. }
  396. }
  397. [NSThread sleepForTimeInterval:0.5];
  398. }
  399. }
  400. });
  401. }
  402. - (void)uploadMedia:(RCMessage *)message uploadListener:(RCUploadMediaStatusListener *)uploadListener {
  403. // 获取 asset
  404. if ([message.content isKindOfClass:[RCSightMessage class]]) {
  405. RCSightMessage *sightMessage = (RCSightMessage *)message.content;
  406. NSString *fileName = [NSString stringWithFormat:@"sight_%@", [[sightMessage.name componentsSeparatedByString:@"_"] lastObject]];
  407. NSString *savePath = [[self getSightSavePath] stringByAppendingPathComponent:fileName];
  408. // 判断文件是否存在于该文件夹
  409. if ([[NSFileManager defaultManager] fileExistsAtPath:savePath]) {
  410. sightMessage.localPath = savePath;
  411. [self submitFileWithMessage:sightMessage uploadListener:uploadListener];
  412. }
  413. else {
  414. if (([sightMessage.localPath containsString:@"var/mobile/Media/"])) {
  415. NSURL *fileUrl = [NSURL fileURLWithPath:sightMessage.localPath];
  416. AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:fileUrl options:nil];
  417. [self startExportVideoWithVideoAsset:avAsset presetName:AVAssetExportPreset960x540 savePath:savePath success:^(NSString *outputPath) {
  418. sightMessage.localPath = savePath;
  419. [self submitFileWithMessage:sightMessage uploadListener:uploadListener];
  420. } failure:^(NSString *errorMessage, NSError *error) {
  421. // 导出失败 直接拷贝上传
  422. [self copyFileToPath:savePath uploadListener:uploadListener];
  423. }];
  424. }
  425. else {
  426. // 复制文件到指定文件夹
  427. [self copyFileToPath:savePath uploadListener:uploadListener];
  428. }
  429. }
  430. }
  431. else {
  432. uploadListener.errorBlock(INVALID_PARAMETER);
  433. }
  434. }
  435. - (void)copyFileToPath:(NSString *)savePath uploadListener:(RCUploadMediaStatusListener *)uploadListener {
  436. RCMessage *message = uploadListener.currentMessage;
  437. if ([message.content isKindOfClass:[RCSightMessage class]]) {
  438. RCSightMessage *sightMessage = (RCSightMessage *)message.content;
  439. // 复制文件到指定文件夹
  440. BOOL isSuccess = [self copyMediaFile:sightMessage.localPath toPath:savePath];
  441. if (isSuccess) {
  442. sightMessage.localPath = savePath;
  443. [self submitFileWithMessage:sightMessage uploadListener:uploadListener];
  444. }
  445. else {
  446. uploadListener.errorBlock(INVALID_PARAMETER);
  447. }
  448. }
  449. }
  450. - (void)startExportVideoWithVideoAsset:(AVURLAsset *)videoAsset presetName:(NSString *)presetName savePath:(NSString *)savePath success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
  451. NSArray *presets = [AVAssetExportSession exportPresetsCompatibleWithAsset:videoAsset];
  452. if ([presets containsObject:presetName]) {
  453. NSError *error = nil;
  454. AVAssetTrack *assetVideoTrack = nil;
  455. AVAssetTrack *assetAudioTrack = nil;
  456. CMTime start = CMTimeMakeWithSeconds(0, videoAsset.duration.timescale);
  457. CMTimeRange range = CMTimeRangeMake(start, videoAsset.duration);
  458. // Check if the asset contains video and audio tracks
  459. if ([[videoAsset tracksWithMediaType:AVMediaTypeVideo] count] != 0) {
  460. assetVideoTrack = [videoAsset tracksWithMediaType:AVMediaTypeVideo][0];
  461. }
  462. if ([[videoAsset tracksWithMediaType:AVMediaTypeAudio] count] != 0) {
  463. assetAudioTrack = [videoAsset tracksWithMediaType:AVMediaTypeAudio][0];
  464. }
  465. CMTime insertionPoint = kCMTimeZero;
  466. AVMutableComposition *composition = [[AVMutableComposition alloc] init];
  467. // Insert the video and audio tracks from AVAsset
  468. if (assetVideoTrack != nil) {
  469. // 视频通道 工程文件中的轨道,有音频轨、视频轨等,里面可以插入各种对应的素材
  470. AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
  471. // 视频方向
  472. [compositionVideoTrack setPreferredTransform:assetVideoTrack.preferredTransform];
  473. // 把视频轨道数据加入到可变轨道中 这部分可以做视频裁剪TimeRange
  474. [compositionVideoTrack insertTimeRange:range ofTrack:assetVideoTrack atTime:insertionPoint error:&error];
  475. }
  476. if (assetAudioTrack != nil) {
  477. AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
  478. compositionAudioTrack.preferredTransform = assetAudioTrack.preferredTransform;
  479. [compositionAudioTrack insertTimeRange:range ofTrack:assetAudioTrack atTime:insertionPoint error:&error];
  480. }
  481. AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:composition presetName:presetName];
  482. // Optimize for network use.
  483. session.shouldOptimizeForNetworkUse = YES;
  484. NSArray *supportedTypeArray = session.supportedFileTypes;
  485. if ([supportedTypeArray containsObject:AVFileTypeMPEG4]) {
  486. session.outputFileType = AVFileTypeMPEG4;
  487. } else if (supportedTypeArray.count == 0) {
  488. if (failure) {
  489. failure(@"该视频类型暂不支持导出", nil);
  490. }
  491. NSLog(@"No supported file types 视频类型暂不支持导出");
  492. return;
  493. } else {
  494. session.outputFileType = [supportedTypeArray objectAtIndex:0];
  495. if (videoAsset.URL && videoAsset.URL.lastPathComponent) {
  496. savePath = [savePath stringByReplacingOccurrencesOfString:@".mp4" withString:[NSString stringWithFormat:@"-%@", videoAsset.URL.lastPathComponent]];
  497. }
  498. }
  499. session.outputURL = [NSURL fileURLWithPath:savePath];
  500. [session exportAsynchronouslyWithCompletionHandler:^{
  501. dispatch_async(dispatch_get_main_queue(), ^{
  502. switch (session.status) {
  503. case AVAssetExportSessionStatusUnknown: {
  504. NSLog(@"AVAssetExportSessionStatusUnknown");
  505. } break;
  506. case AVAssetExportSessionStatusWaiting: {
  507. NSLog(@"AVAssetExportSessionStatusWaiting");
  508. } break;
  509. case AVAssetExportSessionStatusExporting: {
  510. NSLog(@"AVAssetExportSessionStatusExporting");
  511. } break;
  512. case AVAssetExportSessionStatusCompleted: {
  513. NSLog(@"AVAssetExportSessionStatusCompleted");
  514. if (success) {
  515. success(savePath);
  516. }
  517. } break;
  518. case AVAssetExportSessionStatusFailed: {
  519. NSLog(@"AVAssetExportSessionStatusFailed");
  520. if (failure) {
  521. failure(@"视频导出失败", session.error);
  522. }
  523. } break;
  524. case AVAssetExportSessionStatusCancelled: {
  525. NSLog(@"AVAssetExportSessionStatusCancelled");
  526. if (failure) {
  527. failure(@"导出任务已被取消", nil);
  528. }
  529. } break;
  530. default: break;
  531. }
  532. });
  533. }];
  534. }
  535. else {
  536. if (failure) {
  537. NSString *errorMessage = [NSString stringWithFormat:@"当前设备不支持该预设:%@", presetName];
  538. failure(errorMessage, nil);
  539. }
  540. }
  541. }
  542. - (void)submitFileWithMessage:(RCSightMessage *)sightMessage uploadListener:(RCUploadMediaStatusListener *)uploadListener {
  543. NSURL *fileUrl = [NSURL fileURLWithPath:sightMessage.localPath];
  544. NSData *fileData = [NSData dataWithContentsOfURL:fileUrl];
  545. if (fileData.length) {
  546. NSString *suffix = [NSString stringWithFormat:@".%@",[fileUrl pathExtension]];
  547. [[KSUploadManager shareInstance] configBucketName:@"i-m"];
  548. [[KSUploadManager shareInstance] videoUpload:fileData fileName:@"sightVideo" fileSuffix:suffix progress:^(int64_t bytesWritten, int64_t totalBytes) {
  549. int progress = (int)(bytesWritten / totalBytes * 100);
  550. uploadListener.updateBlock(progress);
  551. } successCallback:^(NSMutableArray * _Nonnull fileUrlArray) {
  552. NSString *videoUrl = [fileUrlArray lastObject];
  553. sightMessage.remoteUrl = videoUrl;
  554. sightMessage.size = fileData.length;
  555. uploadListener.successBlock(sightMessage);
  556. } faliure:^(NSError * _Nullable error, NSString * _Nullable descMessaeg) {
  557. uploadListener.errorBlock(ERRORCODE_TIMEOUT);
  558. }];
  559. }
  560. else {
  561. uploadListener.errorBlock(INVALID_PARAMETER);
  562. }
  563. }
  564. - (BOOL)copyMediaFile:(NSString *)sourcePath toPath:(NSString *)toPath
  565. {
  566. BOOL retVal = YES;
  567. if ([[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) { // 如果原文件存在
  568. if (![[NSFileManager defaultManager] fileExistsAtPath:toPath])
  569. {
  570. retVal = [[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:toPath error:NULL];
  571. }
  572. }
  573. else {
  574. retVal = NO;
  575. }
  576. return retVal;
  577. }
  578. - (NSString *)getSightSavePath {
  579. NSString *componentString = [NSString stringWithFormat:@"RongCloud/%@/RCSightCache", self.targetId];
  580. NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject] stringByAppendingPathComponent:componentString];
  581. NSFileManager *fileManager = [NSFileManager defaultManager];
  582. BOOL isDir = FALSE;
  583. BOOL isDirExist = [fileManager fileExistsAtPath:path isDirectory:&isDir];
  584. if(!(isDirExist && isDir)) {
  585. BOOL bCreateDir = [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
  586. if(!bCreateDir){
  587. NSLog(@"创建文件夹失败!");
  588. }
  589. NSLog(@"创建文件夹成功,文件路径%@",path);
  590. }
  591. NSLog(@"文件路径%@",path);
  592. return path;
  593. }
  594. /*
  595. #pragma mark - Navigation
  596. // In a storyboard-based application, you will often want to do a little preparation before navigation
  597. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  598. // Get the new view controller using [segue destinationViewController].
  599. // Pass the selected object to the new view controller.
  600. }
  601. */
  602. @end