I want to save AVAssetWriter
output to the camera roll, I'm currently saving it to the documents directory.
I have tried to use the UISaveVideoAtPathToSavedPhotosAlbum(_:_:_:_:)
. I am currently using AVAssetWriter
to write to the .documentsdirectotry
. However, when I try to write, it fails silently. To write, I'll call startRecording()
and I'll call stopRecording()
to finish writing.
let captureSession = AVCaptureSession()
var videoOutput = AVCaptureVideoDataOutput()
var assetWriter: AVAssetWriter!
var assetWriterInput: AVAssetWriterInput!
var isCameraSetup = false
var hasStartedWritingCurrentVideo = false
var isWriting = false
let queue = DispatchQueue(label: "com.name.camera-queue")
// Camera preview setup code
//Setting up Asset Writer to save videos
public func setUpAssetWriter() {
do {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
let outputURL = URL(fileURLWithPath: documentsPath!).appendingPathComponent("test.m4v")
assetWriter = try! AVAssetWriter(outputURL: outputURL, fileType: .mp4)
assetWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoOutput.recommendedVideoSettingsForAssetWriter(writingTo: .mp4))
assetWriterInput.expectsMediaDataInRealTime = true
if assetWriter.canAdd(assetWriterInput) {
assetWriter.add(assetWriterInput)
} else {
print("no input added")
}
assetWriter.startWriting()
} catch let error {
debugPrint(error.localizedDescription)
}
}
public func tearDownAssetWriter() {
assetWriter = nil
assetWriterInput = nil
}
public func startWriting(){
if isWriting{ return }
setUpAssetWriter()
hasStartedWritingCurrentVideo = false
isWriting = true
}
public func finishWriting(_ completion: @escaping (URL) -> Void) {
if isWriting == false { return }
isWriting = false
assetWriterInput.markAsFinished()
let url = self.assetWriter.outputURL
assetWriter.finishWriting {
completion(url)
self.tearDownAssetWriter()
}
hasStartedWritingCurrentVideo = false
}
// MARK: Starts the capture session
public func start() {
if !captureSession.isRunning {
captureSession.startRunning()
}
}
public func stop() {
if captureSession.isRunning {
captureSession.stopRunning()
}
}
// MARK: Records after camera is set up
public func startRecording() {
startWriting()
isWriting = true
}
public func stopRecording() {
assetWriterInput.markAsFinished()
assetWriter.finishWriting {
[weak self] in return
}
}
}
extension VideoCapture: AVCaptureVideoDataOutputSampleBufferDelegate {
public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
// Because lowering the capture device's FPS looks ugly in the preview,
// we capture at full speed but only call the delegate at its desired
// framerate.
let timestamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
let deltaTime = timestamp - lastTimestamp
if deltaTime >= CMTimeMake(value: 1, timescale: Int32(fps)) {
lastTimestamp = timestamp
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
delegate?.videoCapture(self, didCaptureVideoFrame: imageBuffer, timestamp: timestamp)
}
// Asset Writer
guard let assetWriter = assetWriter, let assetWriterInput = assetWriterInput else { return }
if isWriting == false{ return }
if self.assetWriter.status == .failed {
setUpAssetWriter()
hasStartedWritingCurrentVideo = false
}
if hasStartedWritingCurrentVideo == false && output === videoOutput { return }
if hasStartedWritingCurrentVideo == false {
hasStartedWritingCurrentVideo = true
let sourceTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
assetWriter.startSession(atSourceTime: sourceTime)
}
if output === videoOutput && assetWriterInput.isReadyForMoreMediaData{
if isWriting == false{return}
assetWriterInput.append(sampleBuffer)
}
}
}
The current implementation sets up the camera and preview, but then nothing is saved to the output.
It should save to the .documentDirectory, however, it's not saving. I would like to instead get it to save to the camera roll, but I'm not sure where exactly I should call UISaveVideoAtPathToSavedPhotosAlbum(_:_:_:_:)
.
The problem is likely in my extension delegate.
Thank you in advance for your help.
I am not familiar with UISaveVideoAtPathToSavedPhotosAlbum. But browsing stack overflow and git, many people use PHPhotoLibrary and so do I. Regardless of url, code below adds the video to photoLibrary.
1) Info.plist Add new key-value pair by + button. Select "Privary - Photo Library Usage Description" as a key. Set value something like "save video in photo library"
2) code
fileWriter.finishWriting(completionHandler: {
let status = PHPhotoLibrary.authorizationStatus()
//no access granted yet
if status == .notDetermined || status == .denied{
PHPhotoLibrary.requestAuthorization({auth in
if auth == .authorized{
saveInPhotoLibrary(url)
}else{
print("user denied access to photo Library")
}
})
//access granted by user already
}else{
saveInPhotoLibrary(url)
}
})
private func saveInPhotoLibrary(_ url:URL){
PHPhotoLibrary.shared().performChanges({
//add video to PhotoLibrary here
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)
}) { completed, error in
if completed {
print("save complete! path : " + url.absoluteString)
}else{
print("save failed")
}
}
}
Hope this helps. GW