Kaynağa Gözat

录播考试调整

Steven 4 yıl önce
ebeveyn
işleme
ee29371406

+ 14 - 0
MusicGradeExam/MusicGradeExam.xcodeproj/project.pbxproj

@@ -281,6 +281,7 @@
 		2748F8F624C138A0003F8E11 /* ExamLibraryListCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2748F8F424C1389F003F8E11 /* ExamLibraryListCell.xib */; };
 		27544CF824BC337D00EF58AF /* ExamTicketViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27544CF724BC337D00EF58AF /* ExamTicketViewController.m */; };
 		27544CFB24BC338900EF58AF /* UserCenterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27544CFA24BC338900EF58AF /* UserCenterViewController.m */; };
+		277279DC25396EE800B329B8 /* KSVideoRecordViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 277279DB25396EE800B329B8 /* KSVideoRecordViewController.m */; };
 		2774F24225184AB4003473A2 /* SimulationExamConfigController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2774F24125184AB4003473A2 /* SimulationExamConfigController.m */; };
 		2774F2DB251877E0003473A2 /* TZImageCropManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2774F2BE251877DF003473A2 /* TZImageCropManager.m */; };
 		2774F2DC251877E0003473A2 /* TZAssetCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2774F2C0251877DF003473A2 /* TZAssetCell.m */; };
@@ -920,6 +921,8 @@
 		27544CF724BC337D00EF58AF /* ExamTicketViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExamTicketViewController.m; sourceTree = "<group>"; };
 		27544CF924BC338900EF58AF /* UserCenterViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserCenterViewController.h; sourceTree = "<group>"; };
 		27544CFA24BC338900EF58AF /* UserCenterViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UserCenterViewController.m; sourceTree = "<group>"; };
+		277279DA25396EE800B329B8 /* KSVideoRecordViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KSVideoRecordViewController.h; sourceTree = "<group>"; };
+		277279DB25396EE800B329B8 /* KSVideoRecordViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KSVideoRecordViewController.m; sourceTree = "<group>"; };
 		2774F24025184AB4003473A2 /* SimulationExamConfigController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SimulationExamConfigController.h; sourceTree = "<group>"; };
 		2774F24125184AB4003473A2 /* SimulationExamConfigController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SimulationExamConfigController.m; sourceTree = "<group>"; };
 		2774F2BC251877DF003473A2 /* TZPhotoPreviewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZPhotoPreviewCell.h; sourceTree = "<group>"; };
@@ -1478,6 +1481,7 @@
 		2747700324BC0C0100181362 /* UI */ = {
 			isa = PBXGroup;
 			children = (
+				277279D925396E5C00B329B8 /* RecordAction */,
 				2729F7D424C81CA200E1F3C4 /* RecordExam */,
 				2747700424BC0C0100181362 /* Home */,
 				2748F8EB24C05F30003F8E11 /* ExamLibrary */,
@@ -2434,6 +2438,15 @@
 			path = View;
 			sourceTree = "<group>";
 		};
+		277279D925396E5C00B329B8 /* RecordAction */ = {
+			isa = PBXGroup;
+			children = (
+				277279DA25396EE800B329B8 /* KSVideoRecordViewController.h */,
+				277279DB25396EE800B329B8 /* KSVideoRecordViewController.m */,
+			);
+			path = RecordAction;
+			sourceTree = "<group>";
+		};
 		2774F23C25184A48003473A2 /* SimulationExam */ = {
 			isa = PBXGroup;
 			children = (
@@ -3357,6 +3370,7 @@
 				274771F324BC0C0500181362 /* KSNetworking.m in Sources */,
 				274771C424BC0C0500181362 /* CustomNavViewController.m in Sources */,
 				2747722424BC0C0500181362 /* UIImage+UIImageScale.m in Sources */,
+				277279DC25396EE800B329B8 /* KSVideoRecordViewController.m in Sources */,
 				2747718D24BC0C0500181362 /* WMPlayer.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

+ 22 - 0
MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/warning_image.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "warning_image@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "warning_image@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/warning_image.imageset/warning_image@2x.png


BIN
MusicGradeExam/MusicGradeExam/Assets.xcassets/RecordExam/warning_image.imageset/warning_image@3x.png


+ 2 - 0
MusicGradeExam/MusicGradeExam/UI/Home/Controller/HomeViewController.m

@@ -195,8 +195,10 @@
     switch (type) {
         case HOMETYPE_NOTICE: // 通知
         {
+            
             NotifyMessageViewController *noticeVC = [[NotifyMessageViewController alloc] init];
             [self.navigationController pushViewController:noticeVC animated:YES];
+            
         }
             break;
         case HOMETYPE_BOOKRECORD: // 报考记录

+ 2 - 0
MusicGradeExam/MusicGradeExam/UI/Home/SimulationExam/Controller/SimulationExamConfigController.m

@@ -169,6 +169,8 @@
     SimulationExamRecordController *ctrl = [[SimulationExamRecordController alloc] init];
     ctrl.examRegistrationId = examRegistrationId;
     ctrl.sourceModel = model;
+    ctrl.subjectName = self.subjectName;
+    ctrl.leverName = self.examLevel;
     [self.navigationController pushViewController:ctrl animated:YES];
 }
 

+ 4 - 0
MusicGradeExam/MusicGradeExam/UI/Home/SimulationExam/Controller/SimulationExamRecordController.h

@@ -17,6 +17,10 @@ NS_ASSUME_NONNULL_BEGIN
 
 @property (nonatomic, strong) RecordExamModel *sourceModel;
 
+@property (nonatomic, strong) NSString *subjectName;
+
+@property (nonatomic, strong) NSString *leverName;
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 50 - 13
MusicGradeExam/MusicGradeExam/UI/Home/SimulationExam/Controller/SimulationExamRecordController.m

@@ -16,6 +16,8 @@
 #import "SongModel.h"
 #import "KSGuideMaskView.h"
 #import "KSNormalAlertView.h"
+#import "KSVideoRecordViewController.h"
+#import "TZImageManager.h"
 
 @interface SimulationExamRecordController ()<UITableViewDelegate, UITableViewDataSource,WMPlayerDelegate>
 {
@@ -158,31 +160,66 @@
     RecordListCell *cell = [tableView dequeueReusableCellWithIdentifier:@"RecordListCell"];
     MJWeakSelf;
     [cell configCellWithSource:model examRegistrationId:self.examRegistrationId operationCallback:^(RECORDTYPE type, NSString *fileKey) {
-        [weakSelf opreationCellType:type fileKey:fileKey];
+        [weakSelf opreationCellType:type fileKey:fileKey index:indexPath.row];
     }];
     return cell;
 }
 
-- (void)opreationCellType:(RECORDTYPE)type fileKey:(NSString *)fileKey {
+- (void)opreationCellType:(RECORDTYPE)type fileKey:(NSString *)fileKey index:(NSInteger)cellIndex {
     if (type == RECORDTYPE_RECORD) { // 录像
-        self.mediaManager = [[KSMediaManager alloc] init];
-        self.mediaManager.mediaType = MEDIATYPE_VIDEO;
-        self.mediaManager.maxPhotoNumber = 1;
-        self.mediaManager.baseCtrl = self;
-        self.mediaManager.maxDuration = self.sourceModel.singleSongRecordMinutes * 60;
-        MJWeakSelf;
-        [self.mediaManager noAlertCallback:^(NSString * _Nullable videoUrl, NSMutableArray * _Nullable imageArray, NSMutableArray * _Nullable imageAsset) {
-            NSLog(@"%@", videoUrl);
-            // 不上传回调
-            [weakSelf refreshView:videoUrl fileKey:fileKey];
+        NSMutableArray *songNameArray = [NSMutableArray array];
+        for (NSInteger i = 0; i < self.songArray.count; i++) {
+            if (i >= cellIndex) {
+                // 从选择的开始->后面未录制的添加到待录制里
+                SongModel *model = self.songArray[i];
+                NSString *fileKey = [NSString stringWithFormat:@"%@%@", self.examRegistrationId, model.songName];
+                NSDictionary *fileMessage = UserDefault(fileKey);
+                if (fileMessage == nil) {
+                    [songNameArray addObject:model.songName];
+                }
+
+            }
+        }
+        KSVideoRecordViewController *recordCtrl = [[KSVideoRecordViewController alloc] init];
+        recordCtrl.subjectName = self.subjectName;
+        recordCtrl.leverName = self.leverName;
+        recordCtrl.songMessageArray = songNameArray;
+        recordCtrl.singleSongRecordMinutes = self.sourceModel.singleSongRecordMinutes;
+        [recordCtrl configTime:self.topView.duration recordFinishBlock:^(VIDEORECORDACTION action, NSMutableArray *videoArray) {
+            [self refreshAllWithVideoArray:videoArray songArray:songNameArray];
         }];
-        [self.mediaManager takePhoto];
+        recordCtrl.modalPresentationStyle = UIModalPresentationFullScreen;
+        [self presentViewController:recordCtrl animated:YES completion:nil];
+        
     }
     else { // 删除
         [self deleteFileWithKey:fileKey];
     }
 }
 
+- (void)refreshAllWithVideoArray:(NSMutableArray *)videoArray songArray:(NSArray *)songArray {
+    dispatch_main_async_safe(^{
+        [MBProgressHUD ksShowHUDWithText:@"视频处理中..."];
+    });
+    MJWeakSelf;
+    for (NSInteger index = 0 ;index < videoArray.count; index++) {
+        PHAsset *assset = videoArray[index];
+        [[TZImageManager manager] getVideoOutputPathWithAsset:assset presetName:AVAssetExportPresetMediumQuality success:^(NSString *outputPath) {
+            NSLog(@"视频导出到本地完成,沙盒路径为:%@",outputPath);
+            NSData *outputData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:outputPath]]; //压缩后的视频
+            NSLog(@"导出后的视频:%@",[NSString stringWithFormat:@"%.2fM",(CGFloat)outputData.length/(1024*1024)]);
+            NSString *fileKey = [NSString stringWithFormat:@"%@%@", self.examRegistrationId, songArray[index]];
+            [weakSelf refreshView:outputPath fileKey:fileKey];
+            [MBProgressHUD ksHideHUD];
+        } failure:^(NSString *errorMessage, NSError *error) {
+            dispatch_main_async_safe(^{
+                [MBProgressHUD ksShowMessage:@"视频导出失败"];
+                [MBProgressHUD ksHideHUD];
+            });
+        }];
+    }
+}
+
 
 - (void)refreshView:(NSString *)videoUrl fileKey:(NSString *)fileKey {
     NSString *fileUrl = videoUrl;

+ 36 - 0
MusicGradeExam/MusicGradeExam/UI/RecordAction/KSVideoRecordViewController.h

@@ -0,0 +1,36 @@
+//
+//  KSVideoRecordViewController.h
+//  MusicGradeExam
+//
+//  Created by Kyle on 2020/10/16.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+typedef NS_ENUM(NSInteger, VIDEORECORDACTION) {
+    VIDEORECORDACTION_BACK = 1,
+    VIDEORECORDACTION_OVER,
+};
+
+typedef void(^VideoRecordBlock)(VIDEORECORDACTION action, NSMutableArray * _Nullable videoArray);
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface KSVideoRecordViewController : UIViewController
+
+@property (nonatomic, strong) NSString *subjectName;
+
+@property (nonatomic, strong) NSString *leverName;
+
+@property (nonatomic, strong) NSArray *songMessageArray;
+
+@property (nonatomic, assign) NSInteger singleSongRecordMinutes;
+
+- (void)stopDurationTimer;
+
+- (void)configTime:(NSInteger)subTime recordFinishBlock:(VideoRecordBlock)block;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 661 - 0
MusicGradeExam/MusicGradeExam/UI/RecordAction/KSVideoRecordViewController.m

@@ -0,0 +1,661 @@
+//
+//  KSVideoRecordViewController.m
+//  MusicGradeExam
+//
+//  Created by Kyle on 2020/10/16.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "KSVideoRecordViewController.h"
+#import "AppDelegate.h"
+#import "UIDevice+TFDevice.h"
+#import <AVFoundation/AVFoundation.h>
+#import <AssetsLibrary/AssetsLibrary.h>
+#import "KSNormalAlertView.h"
+#import "SubjectImageModel.h"
+#import "TZImageManager.h"
+
+@interface KSVideoRecordViewController ()<AVCaptureFileOutputRecordingDelegate>
+
+//会话 负责输入和输出设备之间的数据传递
+@property (nonatomic, strong) AVCaptureSession *captureSession;
+
+@property (nonatomic, strong) AVCaptureDeviceInput *videoCaptureDeviceInput;
+@property (nonatomic, strong) AVCaptureDeviceInput *audioCaptureDeviceInput;
+
+// 视频流输出
+@property (nonatomic, strong) AVCaptureMovieFileOutput *captureMovieFileOutput;
+
+// 相机拍摄预览图层
+@property (nonatomic, strong) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;
+
+// 自定义UI控件容器
+@property (nonatomic, strong) UIView *viewContainer;
+// 录制时常
+@property (nonatomic, strong) UILabel *timeLabel;
+
+// 计时器
+@property (nonatomic, strong) NSTimer *timer;
+
+@property (nonatomic, strong) UIButton *bottomButton;
+
+@property (nonatomic, strong) SubjectImageModel *guideImageModel;
+
+@property (nonatomic, strong) UIImageView *tipsImage;
+
+@property (nonatomic, assign) NSInteger prepareTime;
+
+@property (nonatomic, assign) BOOL isPrepare;
+
+@property (nonatomic, strong) UILabel *bottomCountLabel;
+
+@property (nonatomic, assign) NSInteger currentRecordIndex;
+
+@property (nonatomic, strong) UILabel *topTipsLabel;
+
+@property (nonatomic, strong) UIView *topTipsView;
+
+@property (nonatomic, strong) UIImageView *warningImage;
+
+@property (nonatomic, assign) NSInteger duration;
+
+@property (nonatomic, copy) VideoRecordBlock callback;
+
+@property (nonatomic, strong) NSMutableArray *videoAssetArray;
+
+@property (nonatomic, assign) BOOL isStartExam;
+
+@property (nonatomic, assign) BOOL isRecording;
+
+@property (nonatomic, assign) BOOL isQuiting;
+
+@property (nonatomic, strong) UIView *songCardView;
+
+@property (nonatomic, strong) UILabel *subjectLabel;
+
+@property (nonatomic, strong) UILabel *songTitle;
+
+@property (nonatomic, strong) UILabel *songNameLabel;
+
+@property (nonatomic, strong) UILabel *songTimeLabel;
+
+@property (nonatomic, assign) NSInteger songCountTime;
+
+@end
+
+@implementation KSVideoRecordViewController
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    self.zh_statusBarHidden = YES;
+    // 切换到横屏
+    AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
+    delegate.allowAutoRotate = YES;
+    [UIDevice switchNewOrientation:UIInterfaceOrientationLandscapeRight];
+    [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+    // 竖屏
+    AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
+    delegate.allowAutoRotate = NO;
+    [UIDevice switchNewOrientation:UIInterfaceOrientationPortrait];
+    [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
+    self.zh_statusBarHidden = NO;
+}
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    // Do any additional setup after loading the view.
+    self.view.backgroundColor = HexRGB(0x141414);
+    [self configMessage];
+    // 切换到横屏
+    AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
+    delegate.allowAutoRotate = YES;
+    [UIDevice switchNewOrientation:UIInterfaceOrientationLandscapeRight];
+    [self configUI];
+    [self.timer setFireDate:[NSDate distantPast]];
+}
+
+- (void)configMessage {
+    self.prepareTime = 10;
+    self.currentRecordIndex = 0;
+}
+
+- (void)configUI {
+    _captureSession = [[AVCaptureSession alloc] init];
+    // 初始化会话对象
+    if ([_captureSession canSetSessionPreset:AVCaptureSessionPresetInputPriority]) {
+        _captureSession.sessionPreset = AVCaptureSessionPresetInputPriority;
+    }
+    NSError *error = nil;
+    
+    // 获取视频输出对象
+    AVCaptureDevice *videoCaptureDevice = [self cameraDeviceWithPosition:(AVCaptureDevicePositionBack)];
+    if (!videoCaptureDevice) {
+        NSLog(@"获取后置摄像头失败!");
+        return;
+    }
+    _videoCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:videoCaptureDevice error:&error];
+    if (error) {
+        NSLog(@"取得视频设备输入对象错误");
+        return;
+    }
+    
+    // 获取音频输入对象
+    AVCaptureDevice *audioCaptureDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
+    _audioCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioCaptureDevice error:&error];
+    if (error) {
+        NSLog(@"取得音频设备输入对象时出错");
+        return;
+    }
+    
+    // 初始化设备输出对象
+    _captureMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
+    //将设备输入添加到会话中
+    if ([_captureSession canAddInput:_videoCaptureDeviceInput]) {
+        [_captureSession addInput:_videoCaptureDeviceInput];
+        [_captureSession addInput:_audioCaptureDeviceInput];
+        
+        //防抖功能
+        AVCaptureConnection *captureConnection = [_captureMovieFileOutput connectionWithMediaType:AVMediaTypeAudio];
+        if ([captureConnection isVideoStabilizationSupported]) {
+            captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
+        }
+    }
+    
+    //将设备输出添加到会话中
+    if ([_captureSession canAddOutput:_captureMovieFileOutput]) {
+        [_captureSession addOutput:_captureMovieFileOutput];
+    }
+    
+    //创建视频预览图层
+    _captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
+    self.viewContainer.layer.masksToBounds = YES;
+    _captureVideoPreviewLayer.frame = self.viewContainer.bounds;
+    _captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
+    _captureVideoPreviewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
+    [self.view.layer addSublayer:_captureVideoPreviewLayer];
+    
+    [self.view addSubview:self.viewContainer];
+    [self configSongCardView];
+    self.songCardView.hidden = YES;
+}
+
+- (void)configSongCardView {
+    [self.view addSubview:self.songCardView];
+    [self.songCardView mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.width.mas_equalTo(180);
+        make.right.mas_equalTo(self.view.mas_right);
+        make.centerY.mas_equalTo(self.view.mas_centerY);
+    }];
+    self.songCardView.layer.cornerRadius = 5.0f;
+    
+    [self.songCardView addSubview:self.subjectLabel];
+    self.subjectLabel.text = [NSString stringWithFormat:@"%@,%@", self.subjectName, self.leverName];
+    [self.subjectLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.mas_equalTo(self.songCardView.mas_left).offset(10);
+        make.right.mas_equalTo(self.songCardView.mas_right).offset(10);
+        make.top.mas_equalTo(self.songCardView.mas_top).offset(10);
+        make.height.mas_equalTo(20);
+    }];
+    [self.songCardView addSubview:self.songTitle];
+    [self.songTitle mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.mas_equalTo(self.songCardView.mas_left).offset(10);
+        make.right.mas_equalTo(self.songCardView.mas_right).offset(10);
+        make.top.mas_equalTo(self.subjectLabel.mas_bottom).offset(10);
+        make.height.mas_equalTo(20);
+    }];
+    
+    [self.songCardView addSubview:self.songNameLabel];
+    self.songNameLabel.text = [self.songMessageArray firstObject];
+    [self.songNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.mas_equalTo(self.songCardView.mas_left).offset(10);
+        make.right.mas_equalTo(self.songCardView.mas_right).offset(10);
+        make.top.mas_equalTo(self.songTitle.mas_bottom).offset(10);
+    }];
+    
+    [self.songCardView addSubview:self.songTimeLabel];
+    [self.songTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.mas_equalTo(self.songCardView.mas_left).offset(10);
+        make.right.mas_equalTo(self.songCardView.mas_right).offset(10);
+        make.top.mas_equalTo(self.songNameLabel.mas_bottom).offset(10);
+        make.bottom.mas_equalTo(self.songCardView.mas_bottom).offset(-10);
+    }];
+
+    self.songCountTime = self.singleSongRecordMinutes * 60;
+    self.songTimeLabel.text = [NSString stringWithFormat:@"当前曲目剩余时间:%zds",self.songCountTime];
+}
+
+-(void)viewDidAppear:(BOOL)animated{
+    [super viewDidAppear:animated];
+    [self.captureSession startRunning];
+}
+ 
+-(void)viewDidDisappear:(BOOL)animated{
+    [super viewDidDisappear:animated];
+    [self.captureSession stopRunning];
+    [self stopDurationTimer];
+}
+#pragma mark ---- buttonAction
+- (void)bottomButtonAction:(UIButton *)sender {
+    
+    if ([sender.titleLabel.text isEqualToString:@"已就位"]) {
+        [sender setTitle:@"开始考级" forState:UIControlStateNormal];
+    }
+    else if ([sender.titleLabel.text isEqualToString:@"开始考级"]) {
+        self.isPrepare = YES;
+        self.topTipsLabel.text = @"请考生在10秒内就位演奏";
+        self.topTipsView.hidden = NO;
+        self.bottomCountLabel.hidden = NO;
+        if (self.currentRecordIndex + 1 == self.songMessageArray.count) {
+            [sender setTitle:@"完成录制" forState:UIControlStateNormal];
+        }
+        else {
+            [sender setTitle:@"下一首" forState:UIControlStateNormal];
+        }
+        sender.hidden = YES;
+    }
+    else if ([sender.titleLabel.text isEqualToString:@"下一首"]) {
+        
+        [KSNormalAlertView ks_showAlertWithTitle:@"确定切换下一曲吗?" leftTitle:@"取消" rightTitle:@"确定" cancel:^{
+            
+        } confirm:^{
+            self.currentRecordIndex++;
+            self.topTipsLabel.text = @"请考生在10秒内就位演奏";
+            self.prepareTime = 10;
+            self.isPrepare = YES;
+            [self.captureMovieFileOutput stopRecording];
+            if (self.currentRecordIndex + 1 == self.songMessageArray.count) {
+                [sender setTitle:@"完成录制" forState:UIControlStateNormal];
+            }
+            else {
+                [sender setTitle:@"下一首" forState:UIControlStateNormal];
+            }
+            sender.hidden = YES;
+        }];
+        
+    }
+    else if ([sender.titleLabel.text isEqualToString:@"完成录制"]) {
+        [KSNormalAlertView ks_showAlertWithTitle:@"确定完成录制吗?" leftTitle:@"取消" rightTitle:@"确定" cancel:^{
+            
+        } confirm:^{
+            // 退出录播 返回数据
+            if (self.isRecording) {
+                if ([self.captureMovieFileOutput isRecording]) {
+                    [self.captureMovieFileOutput stopRecording];
+                }
+                self.isQuiting = YES;
+                [MBProgressHUD ksShowHUDWithText:@"视频保存中..."];
+            }
+            else {
+                self.callback(VIDEORECORDACTION_BACK, self.videoAssetArray);
+                [self quitRecordExam];
+            }
+        }];
+        
+    }
+}
+
+- (void)setCurrentRecordIndex:(NSInteger)currentRecordIndex {
+    _currentRecordIndex = currentRecordIndex;
+    self.songCountTime = self.singleSongRecordMinutes * 60;
+    self.songTimeLabel.text = [NSString stringWithFormat:@"当前曲目剩余时间:%zds",self.songCountTime];
+    if (self.songMessageArray.count > currentRecordIndex) {
+        NSString *songName = self.songMessageArray[currentRecordIndex];
+        self.songNameLabel.text = songName;
+    }
+    
+}
+#pragma mark --------  AVCaptureFileOutputRecordingDelegate  ----------
+- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections {
+    NSLog(@"开始录制");
+    _isRecording = YES;
+}
+ 
+- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error {
+    NSLog(@"录制结束");
+    // 是否保存到相册
+    [[TZImageManager manager] saveVideoWithUrl:outputFileURL completion:^(PHAsset *asset, NSError *error) {
+        self.isRecording = NO;
+        // 删除文件
+        [self removeVideoWithPath:outputFileURL.path];
+        // 记录当前相册asset
+        [self.videoAssetArray addObject:asset];
+        if (self.isQuiting) { // 保存完成退出
+            [MBProgressHUD ksHideHUD];
+            self.callback(VIDEORECORDACTION_BACK, self.videoAssetArray);
+            [self quitRecordExam];
+        }
+    }];
+}
+
+- (void)removeVideoWithPath:(NSString *)videoUrl {
+    NSFileManager *fileMamager = [NSFileManager defaultManager];
+    if ([fileMamager fileExistsAtPath:videoUrl]) {
+        [fileMamager removeItemAtPath:videoUrl error:nil];
+    }
+}
+
+/**取得指定位置的摄像头*/
+- (AVCaptureDevice *)cameraDeviceWithPosition:(AVCaptureDevicePosition)position {
+    NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
+    for (AVCaptureDevice *camera in cameras) {
+        if ([camera position] == position) {
+            return camera;
+        }
+    }
+    return nil;
+}
+
+
+#pragma mark ---- lazying
+// 乐曲卡片
+- (UIView *)songCardView {
+    if (!_songCardView) {
+        _songCardView = [[UIView alloc] init];
+        _songCardView.backgroundColor = HexRGBAlpha(0x141414, 0.5f);
+    }
+    return _songCardView;
+}
+
+- (UILabel *)subjectLabel {
+    if (!_subjectLabel) {
+        _subjectLabel = [[UILabel alloc] init];
+        _subjectLabel.textColor = HexRGB(0xffffff);
+        _subjectLabel.font = [UIFont systemFontOfSize:14.0f];
+    }
+    return _subjectLabel;
+}
+
+- (UILabel *)songTitle {
+    if (!_songTitle) {
+        _songTitle = [[UILabel alloc] init];
+        _songTitle.textColor = THEMECOLOR;
+        _songTitle.font = [UIFont systemFontOfSize:14.0f];
+        _songTitle.text = @"当前曲目:";
+    }
+    return _songTitle;
+}
+
+- (UILabel *)songNameLabel {
+    if (!_songNameLabel) {
+        _songNameLabel = [[UILabel alloc] init];
+        _songNameLabel.textColor = HexRGB(0xffffff);
+        _songNameLabel.numberOfLines = 0;
+        _songNameLabel.font = [UIFont systemFontOfSize:14.0f];
+    }
+    return _songNameLabel;
+}
+
+- (UILabel *)songTimeLabel {
+    if (!_songTimeLabel) {
+        _songTimeLabel = [[UILabel alloc] init];
+        _songTimeLabel.textColor = HexRGB(0xffffff);
+        _songTimeLabel.font = [UIFont systemFontOfSize:14.0f];
+    }
+    return _songTimeLabel;
+}
+
+- (NSMutableArray *)videoAssetArray {
+    if (!_videoAssetArray) {
+        _videoAssetArray = [NSMutableArray arrayWithCapacity:self.songMessageArray.count];
+    }
+    return _videoAssetArray;
+}
+- (UILabel *)timeLabel {
+    if (!_timeLabel) {
+        _timeLabel = [[UILabel alloc] init];
+        _timeLabel.font = [UIFont systemFontOfSize:14.0f];
+        _timeLabel.textColor = HexRGB(0xffffff);
+        
+    }
+    return _timeLabel;
+}
+
+- (void)configTime:(NSInteger)subTime recordFinishBlock:(VideoRecordBlock)block {
+    if (block) {
+        self.callback = block;
+    }
+    self.duration = subTime;
+}
+
+- (void)setDuration:(NSInteger)duration {
+    _duration = duration;
+    if (_duration < 300) {
+//        self.timeLabel.textColor = HexRGB(0xff3535);
+    }
+    if (_duration < 0) {
+        [self stopDurationTimer];
+        if (self.callback) {
+            self.callback(VIDEORECORDACTION_OVER,self.videoAssetArray);
+        }
+    }
+}
+
+- (void)stopDurationTimer {
+    [self.timer setFireDate:[NSDate distantFuture]];
+    if (self.timer.valid) {
+        [self.timer invalidate];
+        self.timer = nil;
+    }
+}
+- (NSString *)formatTime {
+    
+    if (self.duration < 0) {
+        return @"00分00秒";
+    }
+    NSInteger durationInteger = self.duration--;
+    NSInteger durationS = durationInteger % 60;
+    NSInteger durationM = (durationInteger - durationS) / 60;
+    NSString *minuteStr = [NSString stringWithFormat:@"%02ld", durationM];
+    NSString *secondStr = [NSString stringWithFormat:@"%02ld", durationS];
+    
+    return [NSString stringWithFormat:@"%@分%@秒", minuteStr, secondStr];
+}
+
+- (UILabel *)topTipsLabel {
+    if (!_topTipsLabel) {
+        _topTipsLabel = [[UILabel alloc] init];
+        _topTipsLabel.font = [UIFont systemFontOfSize:14.0f];
+        _topTipsLabel.textColor = HexRGB(0x00ffef);
+    }
+    return _topTipsLabel;
+}
+
+- (NSTimer *)timer {
+    if (_timer == nil) {
+        _timer = [NSTimer scheduledTimerWithTimeInterval:1
+                                                      target:self
+                                                    selector:@selector(timeFunction:)
+                                                    userInfo:nil
+                                                     repeats:YES];
+        [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
+    }
+    return _timer;
+}
+
+- (void)timeFunction:(NSTimer *)timer {
+    if (self.isPrepare) {
+        if (self.prepareTime > 0) {
+            self.bottomCountLabel.text = [NSString stringWithFormat:@"%zds",self.prepareTime];
+            self.bottomCountLabel.hidden = NO;
+            self.prepareTime--;
+        }
+        else {
+            self.isStartExam = YES;
+            self.isPrepare = NO;
+            self.topTipsLabel.text = @"正在考级,学生必须保持在画面之内";
+            self.songCardView.hidden = NO;
+            self.bottomCountLabel.hidden = YES;
+            self.bottomButton.hidden = NO;
+            NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject] stringByAppendingPathComponent:@"VideoData"];
+            NSFileManager *fileManager = [NSFileManager defaultManager];
+            BOOL isDir = FALSE;
+            BOOL isDirExist = [fileManager fileExistsAtPath:path isDirectory:&isDir];
+            if(!(isDirExist && isDir)) {
+                BOOL bCreateDir = [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
+                if(!bCreateDir){
+                    NSLog(@"创建文件夹失败!");
+                }
+                NSLog(@"创建文件夹成功,文件路径%@",path);
+            }
+            NSString *songName = [[self.songMessageArray objectAtIndex:self.currentRecordIndex] MD5];
+            NSString *fileName = [NSString stringWithFormat:@"%@.mp4",songName];
+            NSString *filePath = [path stringByAppendingPathComponent:fileName];
+                    NSLog(@"%@",filePath);
+                    [self.captureMovieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:filePath] recordingDelegate:self];
+        }
+    }
+    else {
+        if (_isStartExam) {
+            self.songCountTime--;
+            if (self.songCountTime >= 0) {
+                self.songTimeLabel.text = [NSString stringWithFormat:@"当前曲目剩余时间:%zds",self.songCountTime];
+            }
+            if (self.songCountTime == 0) {
+                _isStartExam = NO;
+                // 切换下一曲
+                [self bottomButtonAction:self.bottomButton];
+            }
+        }
+    }
+    self.timeLabel.text = [NSString stringWithFormat:@"考级剩余:%@",[self formatTime]];
+}
+
+- (UIImageView *)tipsImage {
+    if (!_tipsImage) {
+        _tipsImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight)];
+    }
+    return _tipsImage;
+}
+
+- (SubjectImageModel *)guideImageModel {
+    if (!_guideImageModel) {
+        _guideImageModel = [[SubjectImageModel alloc] init];
+        _guideImageModel.subjectName = self.subjectName;
+    }
+    return _guideImageModel;
+}
+- (UIButton *)bottomButton {
+    if (!_bottomButton) {
+        _bottomButton = [UIButton buttonWithType:UIButtonTypeCustom];
+        [_bottomButton setTitle:@"已就位" forState:UIControlStateNormal];
+        _bottomButton.frame = CGRectMake(kScreenWidth - 120 - 20, 10, 120, 44);
+        _bottomButton.layer.cornerRadius = 22;
+        _bottomButton.titleLabel.font = [UIFont systemFontOfSize:14.0f];
+        _bottomButton.backgroundColor = HexRGB(0xff5a5a);
+        [_bottomButton addTarget:self action:@selector(bottomButtonAction:) forControlEvents:UIControlEventTouchUpInside];
+    }
+    return _bottomButton;
+}
+
+- (UIView *)topTipsView {
+    if (!_topTipsView) {
+        _topTipsView = [[UIView alloc] init];
+        _warningImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"warning_image"]];
+        _warningImage.frame = CGRectMake(0, 4, 14, 12);
+        [_topTipsView addSubview:_warningImage];
+        [_topTipsView addSubview:self.topTipsLabel];
+    }
+    return _topTipsView;
+}
+
+- (UIView *)viewContainer {
+    if (!_viewContainer) {
+        _viewContainer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight)];
+        
+        // 声部示意图
+        NSString *tipsImageName = self.guideImageModel.guideImage;
+        if (![NSString isEmptyString:tipsImageName]) {
+            [self.tipsImage setImage:[UIImage imageNamed:tipsImageName]];
+            [self.viewContainer addSubview:self.tipsImage];
+        }
+        
+        UIView *headView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, 64)];
+        headView.backgroundColor = HexRGBAlpha(0x141414, 0.5f);
+        [_viewContainer addSubview:headView];
+        
+        UIButton *cancleButton = [UIButton buttonWithType:UIButtonTypeCustom];
+        cancleButton.frame = CGRectMake(kScreenWidth - 120 - iPhoneXSafeBottomMargin, 0, 120, 64);
+        cancleButton.titleLabel.font = [UIFont systemFontOfSize:14.0f];
+        [cancleButton addTarget:self action:@selector(cancleRecordAction) forControlEvents:UIControlEventTouchUpInside];
+        [cancleButton setTitle:@"放弃本次录制" forState:UIControlStateNormal];
+        [headView addSubview:cancleButton];
+        [headView addSubview:self.topTipsView];
+        [self.topTipsView mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.centerX.mas_equalTo(headView.mas_centerX);
+            make.centerY.mas_equalTo(headView.mas_centerY);
+            make.height.mas_equalTo(20);
+        }];
+        [self.topTipsLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.top.bottom.right.mas_equalTo(self.topTipsView);
+            make.left.mas_equalTo(self.warningImage.mas_right).offset(5);
+        }];
+        self.topTipsView.hidden = YES;
+        [headView addSubview:self.timeLabel];
+        [self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.mas_equalTo(headView.mas_left).offset(15);
+            make.centerY.mas_equalTo(headView.mas_centerY);
+            make.height.mas_equalTo(20);
+        }];
+        
+        
+        UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, kScreenHeight - 64, kScreenWidth, 64)];
+        bottomView.backgroundColor = HexRGBAlpha(0x141414, 0.5f);
+        [_viewContainer addSubview:bottomView];
+        
+        [bottomView addSubview:self.bottomButton];
+        
+        [bottomView addSubview:self.bottomCountLabel];
+        [self.bottomCountLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.height.mas_equalTo(20);
+            make.center.mas_equalTo(bottomView);
+            make.width.mas_equalTo(40);
+        }];
+        self.bottomCountLabel.hidden = YES;
+    }
+    return _viewContainer;
+}
+
+- (UILabel *)bottomCountLabel {
+    if (!_bottomCountLabel) {
+        _bottomCountLabel = [[UILabel alloc] init];
+        _bottomCountLabel.textColor = [UIColor whiteColor];
+        _bottomCountLabel.font = [UIFont systemFontOfSize:16.0f];
+    }
+    return _bottomCountLabel;
+}
+
+- (void)cancleRecordAction {
+    // 弹窗提示退出房间
+    [KSNormalAlertView ks_showAlertWithTitle:@"确定退出录播吗?" leftTitle:@"取消" rightTitle:@"确定" cancel:^{
+        
+    } confirm:^{
+        // 退出录播
+        [self quitRecordExam];
+    }];
+}
+
+- (void)quitRecordExam {
+    [self dismissViewControllerAnimated:NO completion:^{
+        if ([self.captureMovieFileOutput isRecording]) {
+            [self.captureMovieFileOutput stopRecording];
+        }
+    }];
+}
+
+
+
+/*
+#pragma mark - Navigation
+
+// In a storyboard-based application, you will often want to do a little preparation before navigation
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+    // Get the new view controller using [segue destinationViewController].
+    // Pass the selected object to the new view controller.
+}
+*/
+
+@end

+ 2 - 0
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordBodyView.h

@@ -21,6 +21,8 @@ NS_ASSUME_NONNULL_BEGIN
 
 @property (weak, nonatomic) IBOutlet UILabel *topTitleLabel;
 
+@property (nonatomic, assign) NSInteger duration;
+
 + (instancetype)shareInstance;
 
 - (void)configTime:(NSString *)subTime;

+ 0 - 2
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordBodyView.m

@@ -14,8 +14,6 @@
 
 @property (weak, nonatomic) IBOutlet UILabel *countLabel;
 
-@property (nonatomic, assign) NSInteger duration;
-
 @property (nonatomic, strong) NSTimer *timeTimer;
 @end
 

+ 1 - 1
MusicGradeExam/MusicGradeExam/UI/RecordExam/View/RecordListCell.h

@@ -14,7 +14,7 @@ typedef NS_ENUM(NSInteger, RECORDTYPE) {
     RECORDTYPE_DELETE = 2,
 };
 
-typedef void(^RecordAction)(RECORDTYPE type, NSString *fileKey);
+typedef void(^RecordAction)(RECORDTYPE type, NSString * _Nonnull fileKey);
 
 NS_ASSUME_NONNULL_BEGIN