深入解析 iOS 视频录制(三):完整录制流程的实现与整合

深入解析 iOS 视频录制(一):录制管理核心MWRecordingController 类的设计与实现

深入解析iOS视频录制(二):自定义UI的实现​​​​​​​

深入解析 iOS 视频录制(三):完整录制流程的实现与整合

引言

在之前的两篇博客中,我们详细探讨了如何实现 iOS 视频录制功能的核心部分。第一篇博客《深入解析 iOS 视频录制(一):录制管理核心MWRecordingController 类的设计与实现》介绍了如何设计并实现一个录制管理类MWRecordingController,该类负责管理视频录制的核心功能,包括启动会话、设置输入输出、切换摄像头以及控制录制的开始与停止。第二篇《深入解析 iOS 视频录制(二):自定义 UI 的实现》中,我们详细探讨了如何通过自定义 UI 提供更流畅的用户体验,包括录制按钮、预览视图、控制视图等组件的实现与布局。

本篇博客将以这些基础为依托,重点讲解如何在一个 ViewController 中实现完整的视频录制流程。我们将把前两篇博客中的内容整合,展示如何在单一视图控制器中实现从视频预览到录制控制、从开始录制到保存视频文件的全过程。通过这篇博客,你将了解如何将这些不同模块融合成一个完整、可交互的视频录制应用。

希望通过这篇博客,你能深入理解如何实现一个简单而强大的视频录制功能,并为未来的应用开发打下坚实的基础。

准备工作

在开始实现视频录制功能之前,我们需要做好一些必要的准备工作。确保项目配置正确,依赖库安装完毕,以及设备权限已正确设置,才能顺利进行开发和测试。

项目设置与依赖

首先,确保你的 Xcode 项目已经设置好并配置了正确的依赖项。在本项目中,我们主要依赖 AVFoundation 框架来实现视频录制功能。

Swift 复制代码
import AVFoundation

配置 Info.plist 权限请求

iOS 中,应用访问设备的硬件(如摄像头和麦克风)时需要请求响应的权限。为了确保应用能顺利进行示例录制,我们需要在 Info.plist 文件中添加以下权限描述:

Swift 复制代码
<key>NSCameraUsageDescription</key>
<string>需要访问摄像头来进行视频录制</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风来录制音频</string>

这些描述会在应用首次请求权限时,向用户展示权限请求弹窗。确保根据你应用的功能,调整描述内容,提供清晰且易懂的理由,以提升用户体验。

设备要求与测试

为了保证视频录制功能能够顺利运行,请确保测试设备支持摄像头和麦克风,在模拟器中一般无法使用真实摄像头进行录制,因此必须使用真实设备进行开发和调试。

录制完整流程代码实现

在这一部分,我们将会结合代码一步一步实现录制视频的完整流程。

录制管理与UI组件集成

MWRecordingViewController是本次实现的核心视图控制,它将 MWRecordingController 作为录制的核心控制器,并通过自定义的视图组件(如MWRecordingPreview、MWRecordingControlView、MWRecordingNavgationView)构建了一个完成的视频录制界面。

MWRecordingController

录制控制器(MWRecordingController):负责启动会话、开始和停止录制、切换摄像头。

Swift 复制代码
    /// 录制控制器
    private let recordingController = MWRecordingController()
Swift 复制代码
  public override func viewDidLoad() {
        super.viewDidLoad()
        setupRecording()
        addPreviewView()
        addNavgationView()
        addControlView()
    }
    
    private func setupRecording() {
        recordingController.recordingSource = self.recordingSource
        // 设置
        recordingController.setupSession()
        // 开始会话
        recordingController.startSession()
        recordingController.delegate = self
    }
  

MWRecordingPreview

预览视图(MWRecordingPreview):负责展示视频录制过程中的实时画面。

Swift 复制代码
    /// 预览视图
    private let previewView = MWRecordingPreview()

MWRecordingControlView

控制器视图(MWRecordingControlView):提供开始/暂停录制、重新录制、完成录制等按钮。

Swift 复制代码
    /// 底部控制视图
    private let controlView = MWRecordingControlView()
Swift 复制代码
    // 控制视图
    private func addControlView() {
        self.view.addSubview(controlView)
        controlView.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview()
            make.height.equalTo(103.0)
            make.bottom.equalToSuperview().offset(-MW_BOTTOM_SAFE_HEIGHT - 14.0)
        }
        // 开始/暂停 录制
        controlView.recordButtonClickBlock = { [weak self] in
            guard let self = self else { return }
            ....
        }
        // 重新录制
        controlView.reRecordButtonClickBlock = { [weak self] in
            guard let self = self else { return }
           .....
        }
        // 完成
        controlView.finishButtonClickBlock = { [weak self] in
            guard let self = self else { return }
               ...
        }
    }

MWRecordingNavgationView

导航栏(MWRecordingNavgationView):提供切换摄像头和返回的按钮。

Swift 复制代码
  /// 导航栏
    private let navgationView = MWRecordingNavgationView()
  
Swift 复制代码
   // 导航
    private func addNavgationView() {
        self.view.addSubview(navgationView)
        navgationView.snp.makeConstraints { make in
            make.top.equalToSuperview().offset(MW_TOP_SAFE_HEIGHT)
            make.leading.trailing.equalToSuperview()
            make.height.equalTo(56.0)
        }
        // 返回
        navgationView.backButtonClickBlock = { [weak self] in
            guard let self = self else { return }
            self.exit()
        }
        // 切换摄像头
        navgationView.switchCameraButtonClickBlock = { [weak self] in
            guard let self = self else { return }
            self.recordingController.switchCamera()
        }
    }
    
 

实现录制的核心逻辑

在这一部分将会重点讲解如何在MWRecordingViewController中实现视频录制的核心功能,就是UI组件与用操作的交互,以及UI组件与录制核心类MWRecordingController的交互。

在创建MWRecordingController时,我们已经设置并启动了会话,那么第一步我们来实现控制视图的开始/暂停录制的功能,在MWRecordingControlView中我们创建了多个闭包。

录制/暂停

其中recordButtonClickBlock负责回调中间录制/暂停按钮的事件。

Swift 复制代码
        // 开始/暂停 录制
        controlView.recordButtonClickBlock = { [weak self] in
            guard let self = self else { return }
            if self.recordingState == .normal {
                // 正常状态 开始录制
                self.recordingState = .recording
                self.recordingController.startRecording()
            } else if self.recordingState == .recording {
                // 录制状态 暂停
                self.recordingState = .finish
                self.recordingController.stopRecording()
            } else if self.recordingState == .finish {
                // 完成状态 重新录制
                self.recordingState = .recording
                self.recordingController.startRecording()
            }
            if self.recordingState == .recording {
                self.currentDuration = 0
                self.startTimer()
            } else {
                self.stopTimer()
            }
            // 隐藏/显示 切换按钮
            self.navgationView.isHiddenSwitchCameraButton = self.recordingState != .normal

            self.controlView.setRecordingState(state: self.recordingState)
        }

该按钮的点击事件会根据当前的状态来执行不同的操作。

  1. normal:当状态为normal,调用MWRecordingController的开始录制startRecording()方法,并修改状态为recording。
  2. recording:当状态为recording,调用MWRecordingController的结束录制stopRecording()方法,并修改状态为finish。
  3. finish:当状态为finish时,直接重新录制,调用开始录制方法。
  4. 切换状态后,如果状态为recording,重新开启定时器,定时读取录制时长,否则停止定时器。
  5. 根据切换后的状态,更新MWRecordingControlView以及MWRecordingNavgationView视图的状态。

重新录制

点击重新录制按钮,直接切换状态为录制状态,并执行开始录制,启动定时器开始读取录制时长。

Swift 复制代码
        // 重新录制
        controlView.reRecordButtonClickBlock = { [weak self] in
            guard let self = self else { return }
            self.stopPlay()
            self.recordingState = .recording
            self.controlView.setRecordingState(state: self.recordingState)
            self.recordingController.startRecording()
            self.startTimer()
        }

完成事件

点击完成事件,首先判断录制时长是否符合要求,然后将录制视频的URL传递回调用的视图控制器,并隐藏当前录制的视图控制器。

Swift 复制代码
        // 完成
        controlView.finishButtonClickBlock = { [weak self] in
            guard let self = self else { return }
            // 判断时长
            if self.currentDuration < self.minDuration {
                MWToast.showToast("Video at least \(self.minDuration) seconds")
                return
            }
            
            self.recordingCompletionHandler?(self.coverImage,self.videoURL)
            self.exit()
            
        }

录制的代理

在MWRecordingController中我们定义了两个代理方法,用于回调录制的错误信息以及录制完成的视频地址。

Swift 复制代码
    //MARK: MWRecordingControllerDelegate
    /// 录制报错
    func recordingController(_ controller: MWRecordingController, didFailWithError error: any Error) {
        let image = UIImage(named: "login_toast_left_icon")
        MWToast.showTopToast(error.localizedDescription, icon: image)
    }
    
    /// 录制完成
    func recordingController(_ controller: MWRecordingController, didFinishRecordingTo outputFileURL: URL) {
        videoURL = outputFileURL
        let image = generateCoverImage()
        coverImage = image
        if currentDuration < minDuration {
            MWToast.showToast("Video at least \(minDuration) seconds")
            return
        }
    }
  1. 错误信息直接显示Toast。
  2. 录制完成后,根据视频地址读取视频封面。

计时器

开启和关闭计时器,在定时事件中更新录制时长,如果达到录制的最大时长自动结束录制。

Swift 复制代码
    /// 开启定时器
    private func startTimer() {
        if timer != nil {
            return
        }
        MWLogHelper.debug("开启定时器", context: "MWRecordingViewController")
        timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
    }
    
    /// 结束定时器
    private func stopTimer() {
        MWLogHelper.debug("结束定时器", context: "MWRecordingViewController")
        timer?.invalidate()
        timer = nil
    }
    
    
    /// 定时器事件
    @objc private func timerAction() {
        // 读取录制时长
        let duration = recordingController.duration
        currentDuration = CMTimeGetSeconds(duration)
        if currentDuration >= maxDuration {
            // 结束录制
            recordingController.stopRecording()
            recordingState = .finish
            controlView.setRecordingState(state: recordingState)
            stopTimer()
        } else {
            controlView.updateTime(time: currentDuration)
        }
    }

结语

在本篇博客中,我们详细介绍了如何在 ViewController 中实现完整的 iOS 视频录制功能。通过整合之前讨论的录制管理核心 MWRecordingController 和自定义的 UI 组件,我们构建了一个可交互的视频录制界面,涵盖了录制的各个方面:从视频预览、录制控制到视频保存与封面生成,提供了一个完整且流畅的用户体验。

通过这次实现,大家可以了解到如何使用 AVFoundation 框架来处理视频录制,同时也掌握了如何结合自定义 UI 和交互设计,提升应用的易用性与功能性。

在未来,视频录制功能的扩展性非常强,可以根据需求加入更多的特性,如视频特效、实时滤镜、录制过程中的实时预览调整等。随着技术的不断发展,如何优化视频录制的性能、降低资源消耗、提高录制质量等,仍然是值得我们不断探索的方向。

希望本系列博客能够帮助你更好地理解 iOS 视频录制的实现方式,也期待你能将这些知识应用到自己的项目中,打造出更丰富、更有趣的用户体验!

相关推荐
EasyNVR2 小时前
EasyRTC智能硬件:实时畅联、沉浸互动、消音护航
运维·服务器·网络·安全·音视频·webrtc·p2p
Erekys5 小时前
视觉分析之边缘检测算法
人工智能·计算机视觉·音视频
EasyNVR10 小时前
EasyRTC:全平台支持与自研算法驱动的智能音视频通讯解决方案
运维·服务器·小程序·音视频·webrtc·p2p·智能硬件
modest —YBW10 小时前
视频HDR技术详解,你的电脑怎么播放HDR视频?
音视频
Jack1530276827910 小时前
芯谷D668:便携式录音机与耳机式盒式录音机的理想音频解决方案
嵌入式硬件·音视频·家庭影院·麦克风阵列处理器·便携式录音机·耳机式盒式录音机
深圳市青牛科技实业有限公司 小芋圆11 小时前
芯谷D2761:为扬声器保护而生的音频限幅器
人工智能·科技·单片机·嵌入式硬件·机器人·音视频
xcg34012311 小时前
关于视频抽帧调用虹软人脸识别的BufferedImage读取优化策略
ffmpeg·音视频·视频抽帧
大数据AI人工智能培训专家培训讲师叶梓11 小时前
OmniHuman:一张图+音频生成逼真视频
人工智能·计算机视觉·音视频·实时音视频·图像识别·gpt-4·视频生成
Black蜡笔小新11 小时前
AI大模型驱动的智能音视频通信:视频通话SDK工具EasyRTC在嵌入式设备中的应用探索
人工智能·语言模型·大模型·音视频·webrtc·rtc
枪眼11 小时前
ok113i平台——多媒体播放器适配
音视频·嵌入式linux·t113i