KSChatConversationViewController.m 28 KB

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