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.
I hope the problem for those who struggled before has been resolved
In my case,
override func viewDidLoad()neededRPScreenRecorder.shared().delegate = selfsyntax. Of course, even the delegate extension that comes with it.I was implementing RPScreenRecorder in a new view, which was working normally in other views, and I encountered the same problem as the author in the process.
It was a problem that the delegate was not imported while looking for a difference from the previous one.
Hope this helps anyone who finds this page in the future.