I am relatively new to iOS development and Swift but I have an app I'm working on which is supposed to record the activity on the screen and save the resulting video to the camera roll. I am using ReplayKit.
What is working now:
This is the code I have beginning the recording and ending the recording
the startRecording()
function is run by a button that says "start" and the stopRecording()
function is called by a button that says "stop".
var preview : RPPreviewViewController?
func startRecording() {
let recorder = RPScreenRecorder.sharedRecorder()
recorder.startRecordingWithMicrophoneEnabled(true) {
[unowned self] (error) in
print(recorder)
if let unwrappedError = error {
print(unwrappedError.localizedDescription)
}
}
}
func stopRecording() {
let recorder = RPScreenRecorder.sharedRecorder()
recorder.stopRecordingWithHandler {
[unowned self] (preview, error) in
if let unwrappedError = error {
print(unwrappedError.localizedDescription)
}
if let unwrappedPreview = preview {
print("end")
unwrappedPreview.previewControllerDelegate = self
unwrappedPreview.modalPresentationStyle=UIModalPresentationStyle.FullScreen
self.presentViewController(unwrappedPreview, animated: true, completion: nil)
}
}
The screen records fine. I have a button which says "Finish" which will call the stopRecording() function. When that button is clicked, a preview will show up which will play the recorded video and allow the user to manually edit and save the video.
What I'm trying to do:
I need to make the button simply save the video as is to the camera roll. I want to bypass the preview screen which allows the user to edit and manually save. Is this possible? If so, how would you approach the problem?
The preview is of type RPPreviewViewController?
and try as I might, I just can't seem to access the video for saving. Since ReplayKit is an extension of UIKit, I tried using the
UISaveVideoAtPathToSavedPhotosAlbum(_ videoPath: String, _ completionTarget: AnyObject?, _ completionSelector: Selector, _ contextInfo: UnsafeMutablePointer<Void>)
method but none of those attributes exist!
If you need anymore info, please let me know. If I'm an idiot, please let me know! This is my first post here so be nice! and Thanks.
As mentioned by Geoff H, Replay Kit 2 now allows you to record the screen and save it either within your app or to the gallery without having to use the preview.
The documentation is sparse but after some trial and experiment the below code works in iOS 12.
Note this only captures video and not audio, although that should be straightforward to add, and you may want to add more error checking if using it. The functions below can be triggered by UI buttons, for example.
@objc func startRecording() {
//Use ReplayKit to record the screen
//Create the file path to write to
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
self.videoOutputURL = URL(fileURLWithPath: documentsPath.appendingPathComponent("MyVideo.mp4"))
//Check the file does not already exist by deleting it if it does
do {
try FileManager.default.removeItem(at: videoOutputURL)
} catch {}
do {
try videoWriter = AVAssetWriter(outputURL: videoOutputURL, fileType: AVFileType.mp4)
} catch let writerError as NSError {
os_log("Error opening video file", writerError);
videoWriter = nil;
return;
}
//Create the video settings
let videoSettings: [String : Any] = [
AVVideoCodecKey : AVVideoCodecType.h264,
AVVideoWidthKey : 1920, //Replace as you need
AVVideoHeightKey : 1080 //Replace as you need
]
//Create the asset writer input object whihc is actually used to write out the video
//with the video settings we have created
videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings);
videoWriter.add(videoWriterInput);
//Tell the screen recorder to start capturing and to call the handler when it has a
//sample
RPScreenRecorder.shared().startCapture(handler: { (cmSampleBuffer, rpSampleType, error) in
guard error == nil else {
//Handle error
os_log("Error starting capture");
return;
}
switch rpSampleType {
case RPSampleBufferType.video:
os_log("writing sample....");
if self.videoWriter.status == AVAssetWriter.Status.unknown {
if (( self.videoWriter?.startWriting ) != nil) {
os_log("Starting writing");
self.videoWriter.startWriting()
self.videoWriter.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(cmSampleBuffer))
}
}
if self.videoWriter.status == AVAssetWriter.Status.writing {
if (self.videoWriterInput.isReadyForMoreMediaData == true) {
os_log("Writting a sample");
if self.videoWriterInput.append(cmSampleBuffer) == false {
print(" we have a problem writing video")
}
}
}
default:
os_log("not a video sample, so ignore");
}
} )
}
@objc func stoprecording() {
//Stop Recording the screen
RPScreenRecorder.shared().stopCapture( handler: { (error) in
os_log("stopping recording");
})
self.videoWriterInput.markAsFinished();
self.videoWriter.finishWriting {
os_log("finished writing video");
//Now save the video
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: self.videoOutputURL)
}) { saved, error in
if saved {
let alertController = UIAlertController(title: "Your video was successfully saved", message: nil, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
if error != nil {
os_log("Video did not save for some reason", error.debugDescription);
debugPrint(error?.localizedDescription ?? "error is nil");
}
}
}
I too wanted to do what you have asked, but as of now RPScreenRecorder
doesn't provide any of those functionalities.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With