小视频拍摄swift3.0

拍摄
拍摄完成

直接粘贴复制就可以使用,具体代码注释在下面代码中,注释掉的代码属于断点续传的逻辑。

//
//  videoController.swift
//  SumDot
//
//  Created by apple on 17/1/6.
//  Copyright © 2017年 zowee. All rights reserved.
//

import UIKit
import AVFoundation
import Photos
import AVKit
typealias saveButtonClose = (_ saveImage:UIImage,_ saveURL:URL)->Void
class videoController: UIViewController  {
    
    //视频捕获会话。它是input和output的桥梁。它协调着intput到output的数据传输
    let captureSession = AVCaptureSession()
    //视频输入设备
    let videoDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
    //音频输入设备
    let audioDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio)
    //将捕获到的视频输出到文件
    let fileOutput = AVCaptureMovieFileOutput()
    
    //录制、保存 ,返回按钮
    var recordButton, saveButton ,backButton,againButton: UIButton!
    
    //保存所有的录像片段数组
    var videoAssets = [AVAsset]()
    //保存所有的录像片段url数组
    var assetURLs = [String]()
    //单独录像片段的index索引
    var appendix: Int32 = 1
    //播放器
    var player :AVPlayer?
    var playerItem:AVPlayerItem?
    //播放器layer
    var playerLayer:AVPlayerLayer?
    //完成后的avseet
    var Asset : AVURLAsset?
    
    
    //最大允许的录制时间(秒)
    let totalSeconds: Float64 = 9.00
    //每秒帧数
    var framesPerSecond:Int32 = 30
    //剩余时间
    var remainingTime : TimeInterval = 9.0
    
    //表示是否停止录像
    var stopRecording: Bool = false
    //剩余时间计时器
    var timer: Timer?
    //进度条计时器
    var progressBarTimer: Timer?
    //进度条计时器时间间隔
    var incInterval: TimeInterval = 0.05
    //进度条
    var progressBar: UIView = UIView()
    //当前进度条终点位置
    var oldX: CGFloat = 0
    //视频完成后保存的URL
    var saveURL : URL?
    //视频完成后保存的第一帧的图片
    var saveImage:UIImage?
    //完成按钮的闭包
    var saveClose : saveButtonClose?
    func setSaveButtonClickClose(mySaveClose : @escaping saveButtonClose)->Void {
        self.saveClose = mySaveClose
    }
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.black
        //添加视频、音频输入设备
        let videoInput = try? AVCaptureDeviceInput(device: self.videoDevice)
        self.captureSession.addInput(videoInput)
        let audioInput = try? AVCaptureDeviceInput(device: self.audioDevice)
        self.captureSession.addInput(audioInput)
        
        //添加视频捕获输出
        let maxDuration = CMTimeMakeWithSeconds(totalSeconds, framesPerSecond)
        self.fileOutput.maxRecordedDuration = maxDuration
        self.captureSession.addOutput(self.fileOutput)
        
        //使用AVCaptureVideoPreviewLayer可以将摄像头的拍摄的实时画面显示在ViewController上
        if let videoLayer = AVCaptureVideoPreviewLayer(session: self.captureSession) {
            videoLayer.frame = CGRect.init(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height-100)
            videoLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
            self.view.layer.addSublayer(videoLayer)
        }
        
        //创建按钮
        self.setupButton()
        //启动session会话
        self.captureSession.startRunning()
        
        //添加进度条
        progressBar.frame = CGRect(x: 0, y: self.view.bounds.size.height-100, width: self.view.bounds.width,height: 3)
        progressBar.isHidden = false
        progressBar.backgroundColor = UIColor(red: 4, green: 3, blue: 3, alpha: 0.5)
        self.view.addSubview(progressBar)
    }
    
    //创建按钮
    func setupButton(){
        //创建录制按钮
        self.recordButton = UIButton(frame: CGRect(x:0,y:0,width:74,height:74))
        self.recordButton.layer.masksToBounds = true
        self.recordButton.setImage(UIImage.init(named: "拍摄"), for: .normal)
        self.recordButton.layer.cornerRadius = 37.0
        self.recordButton.isHidden = false
        self.recordButton.layer.position = CGPoint(x: self.view.bounds.width/2,
                                                   y:self.view.bounds.height-50)
        self.recordButton.addTarget(self, action: #selector(onTouchDownRecordButton(_:)),
                                    for: .touchDown)
        self.recordButton.addTarget(self, action: #selector(onTouchUpRecordButton(_:)),
                                    for: .touchUpInside)
        
        //创建保存按钮
        self.saveButton = UIButton(frame: CGRect(x:0,y:0,width:74,height:74))
        self.saveButton.layer.masksToBounds = true
        self.saveButton.setImage(UIImage.init(named: "完成"), for: .normal)
        self.saveButton.layer.cornerRadius = 37.0
        self.saveButton.isHidden = true
        self.saveButton.layer.position = CGPoint(x: self.view.bounds.width - 60,
                                                 y:self.view.bounds.height-50)
        self.saveButton.addTarget(self, action: #selector(onClickStopButton(_:)),
                                  for: .touchUpInside)
        //创建返回
        self.backButton = UIButton(frame: CGRect(x:0,y:0,width:26,height:15))
        self.backButton.layer.masksToBounds = true
        self.backButton.layer.position = CGPoint(x: 60,
                                                 y:self.view.bounds.height-50)
        self.backButton.setImage(UIImage.init(named: "返回"), for: .normal)
        self.backButton.isHidden = false
        self.backButton.addTarget(self, action: #selector(onClickBackButton(_:)),
                                  for: .touchUpInside)
        
        //创建重拍
        self.againButton = UIButton(frame: CGRect(x:0,y:0,width:74,height:74))
        self.againButton.layer.masksToBounds = true
        self.againButton.layer.position = CGPoint(x: 60,
                                                 y:self.view.bounds.height-50)
        self.againButton.isHidden = true
        self.againButton.setImage(UIImage.init(named: "重拍"), for: .normal)

        self.againButton.layer.cornerRadius = 37.0
        self.againButton.addTarget(self, action: #selector(onClickAgainButton(_:)),
                                  for: .touchUpInside)

        //添加按钮到视图上
        self.view.addSubview(self.recordButton)
        self.view.addSubview(self.saveButton)
        self.view.addSubview(self.backButton)
        self.view.addSubview(self.againButton)
    }
    
    //MARK:按钮点击处理
    //按下录制按钮,开始录制片段
    func  onTouchDownRecordButton(_ sender: UIButton){
        if(!stopRecording) {
            let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory,
                                                            .userDomainMask, true)
            let documentsDirectory = paths[0] as String
            let outputFilePath = "\(documentsDirectory)/output-\(appendix).mov"
            appendix += 1
            let outputURL = URL(fileURLWithPath: outputFilePath)
            let fileManager = FileManager.default
            if(fileManager.fileExists(atPath: outputFilePath)) {
                
                do {
                    try fileManager.removeItem(atPath: outputFilePath)
                } catch _ {
                }
            }
            print("开始录制:\(outputFilePath) ")
            fileOutput.startRecording(toOutputFileURL: outputURL,
                                      recordingDelegate: self)
        }
    }
    
    //松开录制按钮,停止录制片段
    func  onTouchUpRecordButton(_ sender: UIButton){
        if(!stopRecording) {
            timer?.invalidate()
            progressBarTimer?.invalidate()
            fileOutput.stopRecording()
        }
    }
    //返回按钮
   func  onClickBackButton(_ sender:UIButton){
         self.dismiss(animated: true, completion: nil)
    }
    //保存按钮点击
    func onClickStopButton(_ sender: UIButton){
        self.saveClose!(self.saveImage!,self.saveURL!)
        self.dismiss(animated: true, completion: nil)
    }
    //重拍按钮
    func  onClickAgainButton(_ sender:UIButton){
        self.saveButton.isHidden = true
        self.progressBar.isHidden = false
        self.recordButton.isHidden = false
        self.backButton.isHidden = false
        self.againButton.isHidden = true
        self.playerLayer?.removeFromSuperlayer()
        self.player?.pause()
        self.reset()
        self.player = nil
    }

    //MARK:计时器
       //剩余时间计时器
    func startTimer() {
        timer = Timer(timeInterval: remainingTime, target: self,
                      selector: #selector(videoController.timeout), userInfo: nil,
                      repeats:true)
        RunLoop.current.add(timer!, forMode: RunLoopMode.defaultRunLoopMode)
    }
    
    //录制时间达到最大时间
    func timeout() {
        stopRecording = true
        print("时间到。")
        fileOutput.stopRecording()
        timer?.invalidate()
        progressBarTimer?.invalidate()
    }
    
    //进度条计时器
    func startProgressBarTimer() {
        progressBarTimer = Timer(timeInterval: incInterval, target: self,
                                 selector: #selector(videoController.progress),
                                 userInfo: nil, repeats: true)
        RunLoop.current.add(progressBarTimer!, forMode: .defaultRunLoopMode)
    }
    
    //修改进度条进度
    func progress() {
        let progressProportion: CGFloat = CGFloat(incInterval / totalSeconds)
        let progressInc: UIView = UIView()
        progressInc.backgroundColor = UIColor(red: 55/255, green: 186/255, blue: 89/255,
                                              alpha: 1)
        let newWidth = progressBar.frame.width * progressProportion
        progressInc.frame = CGRect(x: oldX , y: 0, width: newWidth,
                                   height: progressBar.frame.height)
        oldX = oldX + newWidth
        progressBar.addSubview(progressInc)
    }
    
    //合并视频片段 断点续传
    func mergeVideos() {
     //   let duration = totalSeconds
        
//        let composition = AVMutableComposition()
//        //合并视频、音频轨道
//        let firstTrack = composition.addMutableTrack(
//            withMediaType: AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID())
//        let audioTrack = composition.addMutableTrack(
//            withMediaType: AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID())
//        
//        var insertTime: CMTime = kCMTimeZero
//        for asset in videoAssets {
//            print("合并视频片段:\(asset)")
//            do {
//                try firstTrack.insertTimeRange(
//                    CMTimeRangeMake(kCMTimeZero, asset.duration),
//                    of: asset.tracks(withMediaType: AVMediaTypeVideo)[0] ,
//                    at: insertTime)
//            } catch _ {
//            }
//            do {
//                try audioTrack.insertTimeRange(
//                    CMTimeRangeMake(kCMTimeZero, asset.duration),
//                    of: asset.tracks(withMediaType: AVMediaTypeAudio)[0] ,
//                    at: insertTime)
//            } catch _ {
//            }
//            
//            insertTime = CMTimeAdd(insertTime, asset.duration)
//        }
//        //旋转视频图像,防止90度颠倒
//        firstTrack.preferredTransform = CGAffineTransform(rotationAngle: CGFloat(M_PI_2))
        
        //裁剪视频区域

//        //定义最终生成的视频尺寸(矩形的)
//        print("视频原始尺寸:", firstTrack.naturalSize)
//        let renderSize = CGSize.init(width: self.view.bounds.size.width, height: self.view.bounds.size.height)
//        print("最终渲染尺寸:", renderSize)
//        
//        //通过AVMutableVideoComposition实现视频的裁剪(矩形,截取正中心区域视频)
//        let videoComposition = AVMutableVideoComposition()
//        videoComposition.frameDuration = CMTimeMake(1, framesPerSecond)
//        videoComposition.renderSize = renderSize
//        
//        let instruction = AVMutableVideoCompositionInstruction()
//        instruction.timeRange = CMTimeRangeMake(
//            kCMTimeZero,CMTimeMakeWithSeconds(Float64(duration), framesPerSecond))
//        
//        let transformer: AVMutableVideoCompositionLayerInstruction =
//            AVMutableVideoCompositionLayerInstruction(assetTrack: firstTrack)
//        let t1 = CGAffineTransform(translationX: firstTrack.naturalSize.height,
//                                   y: 0)
//        let t2 = t1.rotated(by: CGFloat(M_PI_2))
//        let finalTransform: CGAffineTransform = t2
//        transformer.setTransform(finalTransform, at: kCMTimeZero)
//        
//        instruction.layerInstructions = [transformer]
//        videoComposition.instructions = [instruction]
        //获取合并后的视频路径
        let documentsPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory,
                                                                .userDomainMask,true)[0]
        let destinationPath = documentsPath + "/mergeVideo-\(arc4random()%1000).mov"
        print("合并后的视频:\(destinationPath)")
        let videoPath = URL(fileURLWithPath: destinationPath as String)
//        let exporter = AVAssetExportSession(asset: composition,
//                                            presetName:AVAssetExportPresetHighestQuality)!
        let exporter = AVAssetExportSession(asset: self.Asset!,
                                                        presetName:AVAssetExportPresetMediumQuality)!

        exporter.outputURL = videoPath
        self.saveURL = videoPath
        exporter.outputFileType = AVFileTypeQuickTimeMovie
        exporter.shouldOptimizeForNetworkUse = true
       // exporter.videoComposition = videoComposition
       // exporter.timeRange = CMTimeRangeMake(
        //    kCMTimeZero,CMTimeMakeWithSeconds(Float64(duration), framesPerSecond))
       exporter.timeRange = CMTimeRangeMake(
                kCMTimeZero, self.Asset!.duration)
        exporter.exportAsynchronously(completionHandler: {
            //将合并后的视频保存到相册
            DispatchQueue.main.async {
                var duration : TimeInterval = 0.0
                duration = CMTimeGetSeconds(self.Asset!.duration)
                if duration < 1{
                    print("视频太短了")
                    self.onClickAgainButton(self.againButton)
                }else{
                    self.saveButton.isHidden = false
                    self.progressBar.isHidden = true
                    self.recordButton.isHidden = true
                    self.backButton.isHidden = true
                    self.againButton.isHidden = false
                    self.reviewRecord(outputURL: videoPath)
                    self.exportDidFinish(session: exporter)
                }
            }
        })
    }
    //将合并后的视频保存到相册
    func exportDidFinish(session: AVAssetExportSession) {
        print("视频合并成功!")
        let outputURL = session.outputURL!
        //将录制好的录像保存到照片库中
        PHPhotoLibrary.shared().performChanges({
            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL)
        }, completionHandler: { (isSuccess: Bool, error: Error?) in
            DispatchQueue.main.async {
                //重置参数
                self.reset()
                // self.reviewRecord(outputURL: outputURL)
            }
        })
    }
    
    //视频保存成功,重置各个参数,准备新视频录制
    func reset() {
        //删除视频片段
        for assetURL in assetURLs {
            if(FileManager.default.fileExists(atPath: assetURL)) {
                do {
                    try FileManager.default.removeItem(atPath: assetURL)
                } catch _ {
                }
                print("删除视频片段: \(assetURL)")
            }
        }
        
        //进度条还原
        let subviews = progressBar.subviews
        for subview in subviews {
            subview.removeFromSuperview()
        }
        
        //各个参数还原
        videoAssets.removeAll(keepingCapacity: false)
        assetURLs.removeAll(keepingCapacity: false)
        appendix = 1
        oldX = 0
        stopRecording = false
        remainingTime = totalSeconds
    }
    
    //录像回看
    func reviewRecord(outputURL: URL) {
        //定义一个视频播放器,通过本地文件路径初始化
        let playerItem = AVPlayerItem(url: outputURL)
        self.player = AVPlayer(playerItem: playerItem)
        self.playerItem = playerItem
        let playerLayer = AVPlayerLayer(player: player)
        self.playerLayer = playerLayer
         playerLayer.frame = CGRect.init(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height)
        self.view.layer.insertSublayer(playerLayer, at: 1)
        self.player?.play()
        NotificationCenter.default.addObserver(self, selector: #selector(loopVideo), name: .AVPlayerItemDidPlayToEndTime, object: self.playerItem)
         }
    func loopVideo()  {
        self.player?.seek(to:kCMTimeZero)
        self.player?.play()
    }
}


extension videoController :AVCaptureFileOutputRecordingDelegate{
    //录像开始的代理方法
    func capture(_ captureOutput: AVCaptureFileOutput!,
                 didStartRecordingToOutputFileAt fileURL: URL!,
                 fromConnections connections: [Any]!) {
        startProgressBarTimer()
        startTimer()
    }
    
    //录像结束的代理方法
    func capture(_ captureOutput: AVCaptureFileOutput!,
                 didFinishRecordingToOutputFileAt outputFileURL: URL!,
                 fromConnections connections: [Any]!, error: Error!) {
        let asset = AVURLAsset(url: outputFileURL, options: nil)
        var duration : TimeInterval = 0.0
        duration = CMTimeGetSeconds(asset.duration)
        print("生成视频片段:\(asset)")
        videoAssets.append(asset)
        assetURLs.append(outputFileURL.path)
        remainingTime = remainingTime - duration
        self.Asset = asset
        //到达允许最大录制时间,自动合并视频
        //if remainingTime <= 0 {
            mergeVideos()
       // }
        //生成视频截图
        if duration > 1 {
            let generator = AVAssetImageGenerator(asset: asset)
            generator.appliesPreferredTrackTransform = true
            let time = CMTimeMakeWithSeconds(0.0,100)
            var actualTime:CMTime = CMTimeMake(0,0)
            let imageRef:CGImage = try! generator.copyCGImage(at: time, actualTime: &actualTime)
            let frameImg = UIImage(cgImage: imageRef)
            self.saveImage = frameImg
        }else{
            print("视频太短")
        }
       
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • 念奴嬌·赤壁懷古 宋 · 蘇軾 大江東去,浪淘盡,千古風流人物。故壘西邊,人道是,三國周郎赤壁。亂石穿空,驚濤拍岸...
    悠雲阅读 789评论 1 6
  • --《红楼梦》之贾瑞之死读后感 前段时间分享,以前跟雯雯读《红楼梦》,雯雯是肯定不感兴趣的。如今读了快2年的经典了...
    上海雯雯妈M6阅读 685评论 0 0