RPScreenRecorder.shared().isAvailable is always false

I'm trying to record my screen with a sample ios application.
But it does not work because RPScreen.shared().isAvailable always returns false.
These are my codes:

ViewController.swift

import UIKit

class ViewController: UIViewController {

  @IBOutlet weak var StartRecordingButton: UIButton!
  @IBOutlet weak var EndRecordingButton: UIButton!

  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    StartRecordingButton.addTarget(self, action: #selector(startRecord(_:)), for: .touchUpInside)
    EndRecordingButton.addTarget(self, action: #selector(stopRecord(_:)), for: .touchUpInside)
  }

  private lazy var recorder: ScreenRecorder = ScreenRecorder(configuration: ScreenRecorder.Configuration(), completion: {
    (url, error) in
    guard let url = url else {
      fatalError("\(#function) record failed \(String(describing: error))")
    }

    debugPrint(#function, "success", url)
  })

  @objc func startRecord(_ sender: UIButton) {
    recordStart()
  }

  @objc func stopRecord(_ sender: UIButton) {
    recordStop()
  }

  private func recordStart() {
    guard !recorder.isRecording else { return }

    do {
      try recorder.start()
    } catch {
      fatalError("start recording failed \(error)")
    }
  }

  private func recordStop() {
    guard recorder.isRecording else { return }

    do {
      try recorder.end()
    } catch {
      fatalError("finish recording failed \(error)")
    }
  }
}

ScreenRecorder.swift

import ReplayKit

@available(iOS 11.0, *)
public class ScreenRecorder: NSObject {
  let screenRecorder = RPScreenRecorder.shared()

  // Alias for arguments
  public typealias Completion = (URL?, Error?) -> ()

  let completion: Completion
  let configuration: Configuration

  public init (configuration: Configuration, completion: @escaping Completion) {
    self.configuration = configuration
    self.completion = completion
    super.init()
  }

  // Start recording screen
  public func start() throws {

    print(screenRecorder.isAvailable)

    guard screenRecorder.isAvailable else {
      throw ScreenRecorderError.notAvailable
    }

    guard !screenRecorder.isRecording else {
      throw ScreenRecorderError.alreadyRunning
    }

    try setUp()

    assetWriter?.startWriting()
    assetWriter?.startSession(atSourceTime: CMTime.zero)

    screenRecorder.startCapture(handler: { [weak self] (cmSampleBuffer, rpSampleBufferType, error) in
      if let error = error {
        debugPrint(#function, "something happened", error)
      }

      if RPSampleBufferType.video == rpSampleBufferType {
        self?.appendVideo(sampleBuffer: cmSampleBuffer)
      }
    }) { [weak self] (error) in
      if let error = error {
        self?.completion(nil, error)
      }
    }
  }

  public func end() throws {
    guard screenRecorder.isRecording else {
      throw ScreenRecorderError.notRunning
    }

    screenRecorder.stopCapture { [weak self] (error) in
      if let error = error {
        self?.completion(nil, error)
      }

      self?.videoAssetWriterInput?.markAsFinished()
      self?.assetWriter?.finishWriting {
        DispatchQueue.main.async {
          self?.completion(self?.cacheFileURL, nil)
        }
      }
    }
  }

  public var isRecording: Bool {
    return screenRecorder.isRecording
  }
  private var startTime: CMTime?
  private var assetWriter: AVAssetWriter?
  private var videoAssetWriterInput: AVAssetWriterInput?
  private var writerInputPixelBufferAdapter: AVAssetWriterInputPixelBufferAdaptor?

  private func setUp() throws {
    try createCacheDirectoryIfNeeded()
    try removeOldCachedFile()

    guard let cacheURL = cacheFileURL else {
      throw ScreenRecorderError.invalidURL
    }

    let assetWriter = try AVAssetWriter(url: cacheURL, fileType: configuration.fileType)

    let videoSettings: [String: Any] = [
      AVVideoCodecKey: configuration.codec,
      AVVideoWidthKey: UInt(configuration.videoSize.width),
      AVVideoHeightKey: UInt(configuration.videoSize.height),
    ]

    let videoAssetWriterInput = try AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
    videoAssetWriterInput.expectsMediaDataInRealTime = true

    if assetWriter.canAdd(videoAssetWriterInput) {
      assetWriter.add(videoAssetWriterInput)
    }

    self.assetWriter = assetWriter
    self.videoAssetWriterInput = videoAssetWriterInput
    self.writerInputPixelBufferAdapter = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoAssetWriterInput, sourcePixelBufferAttributes: [
      kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32ARGB)
    ])
  }

  private func appendVideo(sampleBuffer: CMSampleBuffer) {
    guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }

    let firstTime: CMTime
    if let startTime = self.startTime {
      firstTime = startTime
    } else {
      firstTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
      startTime = firstTime
    }

    let currentTime: CMTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
    let diffTime: CMTime = CMTimeSubtract(currentTime, firstTime)

    if writerInputPixelBufferAdapter?.assetWriterInput.isReadyForMoreMediaData ?? false {
      writerInputPixelBufferAdapter?.append(pixelBuffer, withPresentationTime: diffTime)
    }
  }

  private func createCacheDirectoryIfNeeded() throws {
    guard let cacheDirectoryURL = cacheDirectoryURL else { return }

    let fileManager = FileManager.default

    guard !fileManager.fileExists(atPath: cacheDirectoryURL.path) else { return }

    try fileManager.createDirectory(at: cacheDirectoryURL, withIntermediateDirectories: true, attributes: nil)
  }

  private func removeOldCachedFile() throws {
    guard let cacheURL = cacheFileURL else { return }

    let fileManager = FileManager.default
    guard fileManager.fileExists(atPath: cacheURL.path) else { return }
    try fileManager.removeItem(at: cacheURL)
  }

  private var cacheDirectoryURL: URL? = {
    guard let path = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first else {
      return nil
    }

    return URL(fileURLWithPath: path).appendingPathComponent("ScreenRecorder")
  }()

  private var cacheFileURL: URL? {
    guard let cacheDirectoryURL = cacheDirectoryURL else { return nil }

    return cacheDirectoryURL.appendingPathComponent("screenrecord.mp4")
  }
}

@available(iOS 11.0, *)
extension ScreenRecorder {
  public struct Configuration{
    public var codec: AVVideoCodecType = .h264
    public var fileType: AVFileType = .mp4
    public var videoSize: CGSize = CGSize(
      width: UIScreen.main.bounds.width,
      height: UIScreen.main.bounds.height
    )
    public var audioQuality: AVAudioQuality = .medium
    public var audioFormatID: AudioFormatID = kAudioFormatMPEG4AAC
    public var numberOfChannels: UInt = 2
    public var sampleRate: Double = 44100.0
    public var bitrate: UInt = 16

    public init() {}
  }

  public enum ScreenRecorderError: Error {
    case notAvailable
    case alreadyRunning
    case notRunning
    case invalidURL
  }
}

And it shows this fatal error which I wrote:

ios_record_screen[1258:213516] Fatal error: start recording failed notAvailable

I've enabled screen recording in Settings app in my iPhone8, and tried to run on my friend's iPhone X as well.
But both phones didn't work... I could not find helpful information in the Internet.

Hope a help.

This question is every specific to ReplayKit, and you’re unlikely to find anyone with ReplayKit experience here on Swift Forums. My recommendation is that you try over on the Graphics and Games > ReplayKit topic area of DevForums.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

Hey! I'm having a similar problem. Were you able to figure this out? Thanks!

Terms of Service

Privacy Policy

Cookie Policy