Saving a Screen Recording with Rpscreenrecorder Start Capture

How to start ReplayKit screen recording in SpriteKit SKScene class

You should not create your button in the GameViewController, create it directly in your SKScene. Its not good practice to use the GameViewController for UI in SpriteKit games.

There is plenty tutorials around on how to create buttons in SpriteKit.

In regards to ReplayKit, you can use it directly in the SKScene you want, just take the code you have already and move it to the relevant Scene.

To present the preview view controller in a SKScene you can say this

view?.window?.rootViewController?.present(unwrappedPreview, animated: true)

I also noticed you are presenting the View controller after you stop recording. You sure you want to do this? Usually you have a separate button in your game over menu where you can watch the recording.

Here is the general code. I also reccomend you check out apples Sample game DemoBots.

I personally use a Singleton class to manager recording, this way its easier to manager calling all the methods incase you need it for different scenes. To start of the class crate a new swift file and add this code.

 class ScreenRecoder: NSObject {

/// Shared instance
static let shared = ScreenRecorder()

/// Preview controller
var previewController: RPPreviewViewController?

/// Private singleton init
private override init() { }
}

Than to start recording add this method to the ScreenRecorder class.

func start() {
let sharedRecorder = RPScreenRecorder.shared()

// Do nothing if screen recording is not available
guard sharedRecorder.isAvailable else { return }

// Stop previous recording if necessary
if sharedRecorder.isRecording {
stopScreenRecording()
}

print("Starting screen recording")

// Register as the recorder's delegate to handle errors.
sharedRecorder.delegate = self

// Start recording
if #available(iOS 10.0, *) {
#if os(iOS)
sharedRecorder.isMicrophoneEnabled = true
//sharedRecorder.isCameraEnabled = true // fixme
#endif

sharedRecorder.startRecording { [unowned self] error in
if let error = error as? NSError, error.code != RPRecordingErrorCode.userDeclined.rawValue {
print(error.localizedDescription)
// Show alert
return
}
}
} else {
// Fallback on earlier versions
sharedRecorder.startRecording(withMicrophoneEnabled: true) { error in
if let error = error as? NSError, error.code != RPRecordingErrorCode.userDeclined.rawValue {
print(error.localizedDescription)
// Show alert
return
}
}
}
}

To stop recording call this. You notice I am not actually showing the preview yet, I am simply storing it for later use. This is the usual way you do it.

 func stop() {
let sharedRecorder = RPScreenRecorder.shared()

// Do nothing if screen recording is not available
guard sharedRecorder.isAvailable else { return }

// Stop recording
sharedRecorder.stopRecording { [unowned self] (previewViewController, error) in
if let error = error {
// If an error has occurred, display an alert to the user.
print(error.localizedDescription)
// Show alert
return
}

print("Stop screen recording")

if let previewViewController = previewViewController {
// Set delegate to handle view controller dismissal.
previewViewController.previewControllerDelegate = self

/*
Keep a reference to the `previewViewController` to
present when the user presses on preview button.
*/
self.previewViewController = previewViewController
}
}
}

Than create 2 extensions in the ScreenRecorder class conforming to the ReplayKit delegates.

The Preview Controller Delegate

 /// RPPreviewViewControllerDelegate
extension ScreenRecorder: RPPreviewViewControllerDelegate {

/// Preview controller did finish
func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
previewController.dismiss(animated: true, completion: nil)
}
}

and the recording delegate

extension ScreenRecorder: RPScreenRecorderDelegate {

/// Screen recoder did stop with error
func screenRecorder(_ screenRecorder: RPScreenRecorder, didStopRecordingWithError error: Error, previewViewController: RPPreviewViewController?) {

// Display the error the user to alert them that the recording failed.
let error = error as NSError
if error.code != RPRecordingErrorCode.userDeclined.rawValue {
print(message: error.localizedDescription)
// show alert
}

// Hold onto a reference of the `previewViewController` if not nil.
if let previewViewController = previewViewController {
self.previewViewController = previewViewController
}
}

/// Screen recoder did change availability
func screenRecorderDidChangeAvailability(_ screenRecorder: RPScreenRecorder) {
// e.g update your button UI etc
// you can use something like delegation to pass something to your SKScenes
}
}

And finally create a method to present the preview. Preferably you call this via a button in your game over menu.

func showPreview() {
guard let previewViewController = previewViewController else { return }

print("Showing screen recording preview")

// `RPPreviewViewController` only supports full screen modal presentation.
previewViewController.modalPresentationStyle = .fullScreen

let rootViewController = UIApplication.shared.keyWindow?.rootViewController
rootViewController?.present(previewViewController, animated: true, completion:nil)
}

Now you can call the methods anywhere you like in your project

ScreenRecorder.shared.start()
ScreenRecorder.shared.stop()
ScreenRecorder.shared.showPreview() // call stop before calling this

This code is pretty much straight out of DemoBots.

I think the nicest way to handle screen recording is by creating an auto-recording button in your main menu. Use UserDefaults to save the on/off state of it. If its turned on you call startRecording when your gameplay begins, and call stop recording when its game over. Than you show a preview button in your game over menu to watch the recording if the user wants to.

Hope this helps

Use ReplayKit to record any app on screen

From the docs:

Apps on a user’s device can share the recording function, with each
app having its own instance of RPScreenRecorder. Your app can record
the audio and video inside of the app, along with user commentary
through the microphone

The only other way to record the screen is through a Broadcast Upload Extension, which requires the user to initiate it through Control Centre.

Replaykit, startCaptureWithHandler() not sending CMSampleBufferRef of Video type in captureHandler

So, I have come across some scenarios where Replay kit totally crashes and System recorder shows error every time unless you restart the device.

1st Scenario

When you start recording and stop it in completion handler

[RPScreenRecorder.sharedRecorder startCaptureWithHandler:^(CMSampleBufferRef  _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
printf("recording");
} completionHandler:^(NSError * _Nullable error) {
[RPScreenRecorder.sharedRecorder stopCaptureWithHandler:^(NSError * _Nullable error) {
printf("Ended");
}];
}];

2nd Scenario

When you start recording and stop it directly in capture handler

__block BOOL stopDone = NO;
[RPScreenRecorder.sharedRecorder startCaptureWithHandler:^(CMSampleBufferRef _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
if (!stopDone){
[RPScreenRecorder.sharedRecorder stopCaptureWithHandler:^(NSError * _Nullable error) {
printf("Ended");
}];
stopDone = YES;
}
printf("recording");
} completionHandler:^(NSError * _Nullable error) {}];

More Scenarios are yet to be discovered and I will keep updating the
answer

Update 1

It is true that the system screen recorded gives error when we stop recording right after the start, but it seem to work alright after we call startcapture again.

I have also encountered a scenario where I don't get video buffer in my
app only and the system screen recorder works fine, will update the
solution soon.

Update 2

So here is the issue, My actual app is old and it is being maintained and getting updated timely. When the replaykit becomes erroneous, My original app can't receive video buffers, I don't know if there is a configuration that is making this happen, or what?

But new sample app seem to work fine and after replay kit becomes erroneous. when I call startCapture next time, the replay kit becomes fine.
Weird

Update 3

I observed new issue. When the permission alert shows up, the app goes to background. Since I coded that whenever the app goes to background, some UI changes will occur and the recording will be stopped.
This led to the error of

Recording interrupted by multitasking and content resizing

I am not yet certain, which particular UI change is creating this failure, but it only comes when permission alert shows up and the UI changes are made.
If someone has noticed any particular case for this issue, please let us know.

ReplayKit saving video fails first try with mic

Added retry logic to circumvent the issue. Not the greatest solution but it works.

[self.screenRecorder startCaptureWithHandler:^(CMSampleBufferRef  _Nonnull sampleBuffer, RPSampleBufferType bufferType, NSError * _Nullable error) {
if(CMSampleBufferDataIsReady(sampleBuffer) == false || self.assetWriter == nil)
{
return;
}

if (self.assetWriter.status == AVAssetWriterStatusFailed) {
NSLog(@"AVWriter Failed!");
return;
}

if (CMSampleBufferDataIsReady(sampleBuffer)) {
if(self.assetWriter.status == AVAssetWriterStatusWriting) {
if (bufferType == RPSampleBufferTypeVideo) {
if (!self.startedSession) {

dispatch_async(dispatch_get_main_queue(), ^{
_startDate = [NSDate date];
_recordingTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(updateRecordingTime) userInfo:nil repeats:YES];

// Disable the idle timer while recording
[UIApplication sharedApplication].idleTimerDisabled = YES;
});

CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
[self.assetWriter startSessionAtSourceTime:pts];
self.startedSession = YES;
NSLog(@"MP4Writer: started session in appendVideoSample");
}

if (CMTimeCompare(kCMTimeInvalid, self.firstVideoFrameTime) == 0) {
self.firstVideoFrameTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
}

if (self.assetWriterVideoInput.readyForMoreMediaData) {
@try {
[self.assetWriterVideoInput appendSampleBuffer:sampleBuffer];
}
@catch(NSException *expection) {
NSLog(@"Missed Video Buffer: %@", self.assetWriter.error);
}
}
}

if (bufferType == RPSampleBufferTypeAudioMic) {
if (CMTimeCompare(kCMTimeInvalid, self.firstVideoFrameTime) == 0 ||
CMTimeCompare(self.firstVideoFrameTime, CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) == 1) {
return;
}

if (!self.startedSession) {
CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
[self.assetWriter startSessionAtSourceTime:pts];
self.startedSession = YES;
NSLog(@"MP4Writer: started session in appendAudioSample");
}

if (self.assetWriterAudioInput.isReadyForMoreMediaData) {
@try {
[self.assetWriterAudioInput appendSampleBuffer:sampleBuffer];
}
@catch(NSException *expection) {
NSLog(@"Missed Audio Buffer: %@", self.assetWriter.error);
}
}
}
}

}
} completionHandler:^(NSError * _Nullable error) {
if (!error) {
NSLog(@"Recording started successfully.");
}
}];


Related Topics



Leave a reply



Submit