Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to start ReplayKit screen recording in SpriteKit SKScene class

I have implemented ReplayKit in my SpriteKit game, but since everything is done within the GameViewController the record button appears too early. Please see my GameViewController class below:

class GameViewController: UIViewController, RPPreviewViewControllerDelegate {

var videoRecButton: UIButton!
var videoRecImage: UIImage!

override func viewDidLoad() {
    super.viewDidLoad()

    let skView = self.view as? SKView

    if skView?.scene == nil  {
        skView?.showsFPS = true
        skView?.showsNodeCount = true
        skView?.showsPhysics = true
        skView?.ignoresSiblingOrder = false

        //starting the game with the Poster Scene
        let posterScene = PosterScene(size: skView!.bounds.size)
        posterScene.scaleMode = .aspectFill
        skView?.presentScene(posterScene)
    }

    videoRecButton = UIButton(type: .custom)
    videoRecImage = UIImage(named:"videoRecButton.png")

    videoRecButton.frame = CGRect(x:0, y: 0, width: (videoRecImage?.size.width)!, height: (videoRecImage?.size.height)!)
    videoRecButton.setImage(videoRecImage, for: .normal)
    videoRecButton.addTarget(self, action:#selector(self.videoRecButtonClicked), for: .touchUpInside)
    self.view.addSubview(videoRecButton)
}

func videoRecButtonClicked() {
    print("Button Clicked")
    startRecording()
}

func startRecording() {
    let recorder = RPScreenRecorder.shared()

    recorder.startRecording{ [unowned self] (error) in
        if let unwrappedError = error {
            print(unwrappedError.localizedDescription)
        } else {

            self.videoRecButton.addTarget(self, action:#selector(self.stopRecording), for: .touchUpInside)
        }
    }
}

func stopRecording() {
    let recorder = RPScreenRecorder.shared()

    recorder.stopRecording { [unowned self] (preview, error) in
        self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Start", style: .plain, target: self, action: #selector(self.startRecording))

        if let unwrappedPreview = preview {
            unwrappedPreview.previewControllerDelegate = self
            self.present(unwrappedPreview, animated: true)
        }
    }
}

func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
    dismiss(animated: true)
}

override var shouldAutorotate: Bool {
    return true
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    if UIDevice.current.userInterfaceIdiom == .phone {
        return .allButUpsideDown
    } else {
        return .all
    }
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Release any cached data, images, etc that aren't in use.
}

override var prefersStatusBarHidden: Bool {
    return true
}
}
  1. How can I call the startRecording and stopRecording functions from a class that inherits from SKScene like GameScene class?

  2. How can enable, disable and hide the videoRecButton button from the GameScene class?

UPDATE

Based on an answer from crashoverride777 a placed the following code in my SKScene class but the screen records for just a few seconds before the navigation controller with a preview of the recorded video appears. The recorded video is just a black screen and the cancel and save buttons are unresponsive.

    func startRecording() {
    let recorder = RPScreenRecorder.shared()

    if #available(iOS 10.0, *) {
        recorder.startRecording{ [unowned self] (error) in
            if let unwrappedError = error {
                print(unwrappedError.localizedDescription)
            } else {

                self.stopRecording()

            }
        }
    } else {
        // Fallback on earlier versions
    }
}

func stopRecording() {
    let recorder = RPScreenRecorder.shared()

    recorder.stopRecording { [unowned self] (preview, error) in
        self.view?.window?.rootViewController?.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Start", style: .plain, target: self, action: #selector(self.startRecording))

        if let unwrappedPreview = preview {
            unwrappedPreview.previewControllerDelegate = self
            self.view?.window?.rootViewController?.present(unwrappedPreview, animated: true)
        }
    }

}

func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
    view?.window?.rootViewController?.dismiss(animated: true)
}

I created a record button:

let videoRecButtonSprite = SKSpriteNode(imageNamed: "videoButton")
videoRecButtonSprite.position = CGPoint(x: self.frame.width/15, y: self.frame.height - self.frame.height/12)
self.addChild(videoRecButtonSprite)

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    for touch: AnyObject in touches {
        let location = touch.location(in: self)
        if videoRecButtonSprite.contains(location){
            startRecording()
        }
    }
}
like image 540
iGetIt Avatar asked Sep 30 '16 14:09

iGetIt


People also ask

What is ReplayKit recording?

ReplayKit is a framework that allows developers to add recording and live broadcasting capabilities to their apps. In addition, it allows users to annotate their recordings and broadcasts using the device's front-facing camera and microphone.

What is ReplayKit?

The ReplayKit namespace provides classes that allows screen recording of the developer's application. Additionally, it provides a standard RPPreviewViewController view controller that allows the user to preview, trim, and share the recording. Developers must use the SharedRecorder singleton to create replays.


1 Answers

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

like image 128
crashoverride777 Avatar answered Sep 27 '22 01:09

crashoverride777