FUVideoRenderViewController.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. //
  2. // FUVideoRenderViewController.m
  3. // FULiveDemo
  4. //
  5. // Created by 项林平 on 2022/8/8.
  6. //
  7. #import "FUVideoRenderViewController.h"
  8. #import "FUExportVideoView.h"
  9. #import "FULandmarkManager.h"
  10. @interface FUVideoRenderViewController ()<FUExportVideoViewDelegate>
  11. @property (nonatomic, strong) FUGLDisplayView *renderView;
  12. @property (nonatomic, strong) UIButton *playVideoButton;
  13. @property (nonatomic, strong) UIButton *downloadButton;
  14. @property (nonatomic, strong) UILabel *noTrackLabel;
  15. @property (nonatomic, strong) UILabel *tipLabel;
  16. @property (nonatomic, strong) FUExportVideoView *exportingView;
  17. @property (nonatomic, strong) NSURL *videoURL;
  18. @property (nonatomic, strong) FUVideoRenderViewModel *viewModel;
  19. @end
  20. @implementation FUVideoRenderViewController
  21. #pragma mark - Initializer
  22. - (instancetype)initWithViewModel:(FUVideoRenderViewModel *)viewModel {
  23. self = [super init];
  24. if (self) {
  25. self.viewModel = viewModel;
  26. self.viewModel.delegate = self;
  27. }
  28. return self;
  29. }
  30. #pragma mark - Life cycle
  31. - (void)viewDidLoad {
  32. [super viewDidLoad];
  33. [self configureUI];
  34. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive) name:UIApplicationWillResignActiveNotification object:nil];
  35. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
  36. [self.viewModel startPreviewing];
  37. }
  38. - (void)viewWillAppear:(BOOL)animated {
  39. [super viewWillAppear:animated];
  40. // 添加点位测试开关
  41. if ([FURenderKitManager sharedManager].showsLandmarks) {
  42. [FULandmarkManager show];
  43. }
  44. }
  45. - (void)viewWillDisappear:(BOOL)animated {
  46. [super viewWillDisappear:animated];
  47. // 移除点位测试开关
  48. if ([FURenderKitManager sharedManager].showsLandmarks) {
  49. [FULandmarkManager dismiss];
  50. }
  51. }
  52. - (void)dealloc {
  53. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
  54. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
  55. }
  56. #pragma mark - UI
  57. - (void)configureUI {
  58. self.view.backgroundColor = [UIColor blackColor];
  59. [self.view addSubview:self.renderView];
  60. [self.renderView mas_makeConstraints:^(MASConstraintMaker *make) {
  61. make.leading.trailing.equalTo(self.view);
  62. if (@available(iOS 11.0, *)) {
  63. make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
  64. if(FUDeviceIsiPhoneXStyle()) {
  65. make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).mas_offset(-50);
  66. } else {
  67. make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
  68. }
  69. } else {
  70. make.top.equalTo(self.view.mas_top);
  71. make.bottom.equalTo(self.view.mas_bottom);
  72. }
  73. make.leading.trailing.equalTo(self.view);
  74. }];
  75. UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
  76. [backButton setImage:[UIImage imageNamed:@"back_item"] forState:UIControlStateNormal];
  77. [backButton addTarget:self action:@selector(backAction:) forControlEvents:UIControlEventTouchUpInside];
  78. [self.view addSubview:backButton];
  79. [backButton mas_makeConstraints:^(MASConstraintMaker *make) {
  80. if (@available(iOS 11.0, *)) {
  81. make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(20);
  82. } else {
  83. make.top.equalTo(self.view.mas_top).offset(30);
  84. }
  85. make.leading.equalTo(self.view.mas_leading).offset(10);
  86. make.size.mas_offset(CGSizeMake(44, 44));
  87. }];
  88. [self.view addSubview:self.noTrackLabel];
  89. [self.noTrackLabel mas_makeConstraints:^(MASConstraintMaker *make) {
  90. make.center.equalTo(self.view);
  91. }];
  92. [self.view addSubview:self.tipLabel];
  93. [self.tipLabel mas_makeConstraints:^(MASConstraintMaker *make) {
  94. make.centerY.equalTo(self.view.mas_centerY).mas_offset(24);
  95. make.centerX.equalTo(self.view);
  96. make.width.mas_equalTo(300);
  97. make.height.mas_equalTo(32);
  98. }];
  99. [self.view addSubview:self.playVideoButton];
  100. [self.playVideoButton mas_makeConstraints:^(MASConstraintMaker *make) {
  101. make.center.equalTo(self.view);
  102. make.size.mas_offset(CGSizeMake(85, 85));
  103. }];
  104. [self.view addSubview:self.downloadButton];
  105. [self.downloadButton mas_makeConstraints:^(MASConstraintMaker *make) {
  106. make.bottom.equalTo(self.view.mas_bottom).offset(-self.viewModel.downloadButtonBottomConstant - 10);
  107. make.centerX.equalTo(self.view);
  108. make.size.mas_offset(CGSizeMake(85, 85));
  109. }];
  110. }
  111. #pragma mark - Instance methods
  112. - (void)updateBottomConstraintsOfDownloadButton:(CGFloat)constraints {
  113. [self.downloadButton mas_updateConstraints:^(MASConstraintMaker *make) {
  114. make.bottom.equalTo(self.view.mas_bottom).mas_offset(-constraints-10);
  115. }];
  116. [UIView animateWithDuration:0.15 animations:^{
  117. [self.view layoutIfNeeded];
  118. }];
  119. }
  120. #pragma mark - Private methods
  121. - (void)showExportVideoView {
  122. [self.view addSubview:self.exportingView];
  123. }
  124. - (void)hideExportVideoView {
  125. [UIView animateWithDuration:0.3 animations:^{
  126. self.exportingView.alpha = 0;
  127. } completion:^(BOOL finished) {
  128. [self.exportingView removeFromSuperview];
  129. self.exportingView.alpha = 1;
  130. [self.exportingView setExportProgress:0];
  131. // 显示播放按钮
  132. if (self.playVideoButton.hidden) {
  133. self.playVideoButton.hidden = NO;
  134. }
  135. }];
  136. }
  137. #pragma mark - Event response
  138. - (void)backAction:(UIButton *)sender {
  139. [self.viewModel destroy];
  140. [self.navigationController popViewControllerAnimated:YES];
  141. }
  142. - (void)playVideoAction:(UIButton *)sender {
  143. sender.hidden = YES;
  144. [self.viewModel startReading];
  145. }
  146. - (void)downloadAction:(UIButton *)sender {
  147. // 隐藏播放按钮
  148. if (!self.playVideoButton.hidden) {
  149. self.playVideoButton.hidden = YES;
  150. }
  151. // 显示导出进度视图
  152. [self showExportVideoView];
  153. if (self.viewModel.isReading) {
  154. // 停止播放
  155. [self.viewModel stopReading];
  156. }
  157. if (self.viewModel.isPreviewing) {
  158. // 停止预览
  159. [self.viewModel stopPreviewing];
  160. }
  161. // 开始重新解码编码
  162. [self.viewModel startProcessing];
  163. }
  164. - (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
  165. if (error) {
  166. [FUTipHUD showTips:FULocalizedString(@"保存视频失败") dismissWithDelay:1.5];
  167. } else {
  168. [FUTipHUD showTips:FULocalizedString(@"视频已保存到相册") dismissWithDelay:1.5];
  169. }
  170. [self hideExportVideoView];
  171. [self.viewModel startPreviewing];
  172. }
  173. - (void)applicationWillResignActive {
  174. if (self.viewModel.isReading) {
  175. [self.viewModel stopReading];
  176. self.playVideoButton.hidden = NO;
  177. }
  178. if (self.viewModel.isPreviewing) {
  179. [self.viewModel stopPreviewing];
  180. }
  181. if (self.viewModel.isProcessing) {
  182. [self.viewModel stopProcessing];
  183. [self hideExportVideoView];
  184. }
  185. }
  186. - (void)applicationDidBecomeActive {
  187. [self.viewModel startPreviewing];
  188. }
  189. #pragma mark - FUVideoRenderViewModelDelegate
  190. - (void)videoRenderDidOutputPixelBuffer:(CVPixelBufferRef)pixelBuffer {
  191. [self.renderView displayPixelBuffer:pixelBuffer];
  192. }
  193. - (void)videoRenderDidFinishReading {
  194. dispatch_async(dispatch_get_main_queue(), ^{
  195. self.playVideoButton.hidden = NO;
  196. });
  197. }
  198. - (void)videoRenderDidFinishProcessing {
  199. // 保存视频到相册
  200. dispatch_async(dispatch_get_main_queue(), ^{
  201. [self.exportingView setExportProgress:1];
  202. UISaveVideoAtPathToSavedPhotosAlbum(kFUFinalPath, self, @selector(video:didFinishSavingWithError:contextInfo:), NULL);
  203. });
  204. }
  205. - (void)videoRenderProcessingProgress:(CGFloat)progress {
  206. dispatch_async(dispatch_get_main_queue(), ^{
  207. [self.exportingView setExportProgress:progress];
  208. });
  209. }
  210. - (void)videoRenderShouldCheckDetectingStatus:(FUDetectingParts)parts {
  211. @autoreleasepool {
  212. BOOL detectingResult = YES;
  213. switch (parts) {
  214. case FUDetectingPartsFace:
  215. detectingResult = [FURenderKitManager faceTracked];
  216. break;
  217. case FUDetectingPartsHuman:
  218. detectingResult = [FURenderKitManager humanTracked];
  219. break;
  220. case FUDetectingPartsHand:
  221. detectingResult = [FURenderKitManager handTracked];
  222. break;
  223. default:
  224. break;
  225. }
  226. dispatch_async(dispatch_get_main_queue(), ^{
  227. self.noTrackLabel.hidden = detectingResult;
  228. if (!detectingResult) {
  229. self.noTrackLabel.text = parts == FUDetectingPartsFace ? FULocalizedString(@"未检测到人脸") : (parts == FUDetectingPartsHuman ? FULocalizedString(@"未检测到人体") : FULocalizedString(@"未检测到手势"));
  230. }
  231. });
  232. }
  233. }
  234. #pragma mark - FUExportVideoViewDelegate
  235. - (void)exportVideoViewDidClickCancel {
  236. [self.viewModel stopProcessing];
  237. [self hideExportVideoView];
  238. [self.viewModel startPreviewing];
  239. }
  240. #pragma mark - Getters
  241. - (UIButton *)playVideoButton {
  242. if (!_playVideoButton) {
  243. _playVideoButton = [UIButton buttonWithType:UIButtonTypeCustom];
  244. [_playVideoButton setBackgroundImage:[UIImage imageNamed:@"render_play"] forState:UIControlStateNormal];
  245. [_playVideoButton addTarget:self action:@selector(playVideoAction:) forControlEvents:UIControlEventTouchUpInside];
  246. }
  247. return _playVideoButton;
  248. }
  249. - (UIButton *)downloadButton {
  250. if (!_downloadButton) {
  251. _downloadButton = [UIButton buttonWithType:UIButtonTypeCustom];
  252. _downloadButton.frame = CGRectMake(CGRectGetWidth(self.view.bounds) / 2.0 - 42.5, CGRectGetHeight(self.view.bounds) - 85 - FUHeightIncludeBottomSafeArea(10), 85, 85);
  253. _downloadButton.backgroundColor = [UIColor whiteColor];
  254. _downloadButton.layer.cornerRadius = 42.5;
  255. [_downloadButton setBackgroundImage:[UIImage imageNamed:@"render_save"] forState:UIControlStateNormal];
  256. [_downloadButton addTarget:self action:@selector(downloadAction:) forControlEvents:UIControlEventTouchUpInside];
  257. }
  258. return _downloadButton;
  259. }
  260. - (UILabel *)noTrackLabel {
  261. if (!_noTrackLabel) {
  262. _noTrackLabel = [[UILabel alloc] init];
  263. _noTrackLabel.textColor = [UIColor whiteColor];
  264. _noTrackLabel.font = [UIFont systemFontOfSize:17];
  265. _noTrackLabel.text = FULocalizedString(@"未检测到人脸");
  266. _noTrackLabel.hidden = YES;
  267. }
  268. return _noTrackLabel;
  269. }
  270. - (UILabel *)tipLabel {
  271. if (!_tipLabel) {
  272. _tipLabel = [[UILabel alloc] init];
  273. _tipLabel.textColor = [UIColor whiteColor];
  274. _tipLabel.font = [UIFont systemFontOfSize:32];
  275. _tipLabel.textAlignment = NSTextAlignmentCenter;
  276. _tipLabel.hidden = YES;
  277. }
  278. return _tipLabel;
  279. }
  280. - (FUGLDisplayView *)renderView {
  281. if (!_renderView) {
  282. _renderView = [[FUGLDisplayView alloc] initWithFrame:self.view.bounds];
  283. _renderView.contentMode = FUGLDisplayViewContentModeScaleAspectFit;
  284. _renderView.origintation = (FUGLDisplayViewOrientation)self.viewModel.videoOrientation;
  285. }
  286. return _renderView;
  287. }
  288. - (FUExportVideoView *)exportingView {
  289. if (!_exportingView) {
  290. _exportingView = [[FUExportVideoView alloc] initWithFrame:self.view.bounds];
  291. _exportingView.delegate = self;
  292. }
  293. return _exportingView;
  294. }
  295. #pragma mark - Override methods
  296. - (BOOL)prefersStatusBarHidden {
  297. return !FUDeviceIsiPhoneXStyle();
  298. }
  299. - (UIStatusBarStyle)preferredStatusBarStyle {
  300. return UIStatusBarStyleLightContent;
  301. }
  302. @end