I would like to add a feature for users to record their ARKit experience. I'm taking the capturedImage of the ARFrame supplied by session(_ session: ARSession, didUpdate frame: ARFrame) and concatenating them into a video.
Unfortunately, ARFrame.capturedImage shows the frame of video captured by the camera, but doesn't include nodes placed by ARKit.
Is there any way to capture video coming from an ARSCNView?
I've tried this library, but it has major bugs (no shadows, large stutter at beginning of recording). I'd also like to not use ReplayKit for this project.
Here is what I'm using to turn ARFrame.capturedImage into a UIImage, and subsequently, a video.
extension UIImage {
convenience init(pixelBuffer: CVPixelBuffer) {
let ciImage = CIImage(cvPixelBuffer: pixelBuffer)
let size = CGSize(width: CVPixelBufferGetWidth(pixelBuffer), height: CVPixelBufferGetHeight(pixelBuffer))
let tempContext = CIContext()
let image = tempContext.createCGImage(ciImage, from: CGRect(origin: CGPoint.zero, size: size))!
// This assumes we're using an iPhone in portrait.
self.init(cgImage: image, scale: 1, orientation: .right)
}
}
I actually found a library to do this. It's SceneKitVideoRecorder.
I don't fully understand how it works yet, but the important code is located in SceneKitVideoRecorder.swift.
You can try to use ReplayKit. I use ReplayKit in my AR App to record when I place a model to the scene and more.
Try this my snippet:
import ReplayKit
class YourController: UIViewController, RPPreviewViewControllerDelegate {
#IBAction func shotVideo(_ sender: UIButton) {
print("Video")
if !isRecording {
startRecording()
} else {
stopRecording()
}
}
func startRecording() {
guard recorder.isAvailable else {
print("The recording isn't available now.")
return
}
recorder.isMicrophoneEnabled = false
recorder.startRecording{ [unowned self] (error) in
guard error == nil else {
print("Trouble with starting this recording.")
return
}
print("Recording started with success.")
self.isRecording = true
}
}
func stopRecording() {
recorder.stopRecording { (preview, error) in
print("The recording is stopped")
guard preview != nil else {
print("The preview controller isn't available.")
return
}
let alert = UIAlertController(title: "End Recording", message: "Want to edit or delete this recording?", preferredStyle: .alert)
let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: { (action: UIAlertAction) in
self.recorder.discardRecording(handler: { () -> Void in
print("Recording suffessfully deleted.")
})
})
let editAction = UIAlertAction(title: "Edit", style: .default, handler: { (action: UIAlertAction) -> Void in
preview?.previewControllerDelegate = self
self.present(preview!, animated: true, completion: nil)
})
alert.addAction(editAction)
alert.addAction(deleteAction)
self.present(alert, animated: true, completion: nil)
self.isRecording = false
}
}
// RPPreviewViewControllerDelegate
func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
dismiss(animated: true)
}
}
This is my code, I hope I have been helpful :).
Related
Why won't RPScreenRecorder record the mic, even though it is enabled, if the permissions popup doesn't appear? It works when the popup appears but attempts after restarting the app don't record the mic.
here's the very simple app i made just to test this feature for a larger app.
I have tested this exact application on iOS 11 and it works every time. However on iOS 12+ it only works when the permission popup appears and that's every 8 minutes. It should work every time after giving permissions.
import ReplayKit
class ViewController: UIViewController, RPPreviewViewControllerDelegate {
private let recorder = RPScreenRecorder.shared()
private var isRecording = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func react() {
if !isRecording {
let alert = UIAlertController(title: "Record", message: "Would you like to record a video?", preferredStyle: .alert)
let okay = UIAlertAction(title: "Okay", style: .destructive, handler: { (action: UIAlertAction) in
self.startRecording()
})
alert.addAction(okay)
self.present(alert, animated: true, completion: nil)
} else {
stopRecording()
}
}
private func startRecording() {
guard self.recorder.isAvailable else {
print("Recording is not available at this time.")
return
}
self.recorder.isMicrophoneEnabled = true
self.recorder.startRecording{ [unowned self] (error) in
guard error == nil else {
print("There was an error starting the recording.")
return
}
print("Started Recording Successfully")
self.isRecording = true
}
}
private func stopRecording() {
recorder.stopRecording { [unowned self] (preview, error) in
print("Stopped recording")
guard preview != nil else {
print("Preview controller is not available.")
return
}
let alert = UIAlertController(title: "Recording Finished", message: "Would you like to edit or delete your recording?", preferredStyle: .alert)
let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: { (action: UIAlertAction) in
self.recorder.discardRecording(handler: { () -> Void in
print("Recording suffessfully deleted.")
})
})
let editAction = UIAlertAction(title: "Edit", style: .default, handler: { (action: UIAlertAction) -> Void in
preview?.previewControllerDelegate = self
self.present(preview!, animated: true, completion: nil)
})
alert.addAction(editAction)
alert.addAction(deleteAction)
self.present(alert, animated: true, completion: nil)
self.isRecording = false
}
}
func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
dismiss(animated: true)
}
}
I expect that the mic should record every single time after allowing the permissions however it appears to only be recording the mic during the sessions in which it asks for those permissions.
This appears to have been fixed in iOS 13. The OS now asks for permission every time you request to record the screen. I still don't have a fix for iOS 12 however.
I have a project that requires me to take a picture and send it to the provided API. I don't want to save it locally, the picture of the image should be sent to the API immediately once the picture is captured.
I've checked the Stack Overflow question swift Take a photo and save to photo library, but it seems like the image is stored locally.
Here's my code:
func cameraSetup(){
let captureDevice = AVCaptureDevice.default(for: AVMediaType.video)
do {
let input = try AVCaptureDeviceInput(device: captureDevice!)
captureSession = AVCaptureSession()
captureSession?.addInput(input)
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
videoPreviewLayer?.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height - 150)
self.cameraView.layer.addSublayer(videoPreviewLayer!)
captureSession?.startRunning()
} catch {
print(error)
}
}
}
How can I send it off immediately instead of having to save it locally first?
For task like this, you should consider using UIImagePickerController and its Delegate methods. Best practice for this is to show user options to choose between camera and gallery (Photo library).
class MyViewController: UIViewController {
func presentImagePickerActionSheet() {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
func addCameraAction() {
guard UIImagePickerController.isSourceTypeAvailable(.camera) else { return assertionFailure("No camera") }
let pickerAction = UIAlertAction(title: "Camera", style: .default) { _ in
self.pickImage(source: .camera)
}
actionSheet.addAction(pickerAction)
}
func addGalleryPickerAction() {
guard UIImagePickerController.isSourceTypeAvailable(.photoLibrary) else { return assertionFailure("No gallery") }
let pickerAction = UIAlertAction(title: "Gallery", style: .default) { _ in
self.pickImage(source: .photoLibrary)
}
actionSheet.addAction(pickerAction)
}
func addRemoveActionIfNeeded() {
return() // Do your logic if needed
let pickerAction = UIAlertAction(title: "Delete", style: .destructive) { _ in
}
actionSheet.addAction(pickerAction)
}
func addCancelAction() {
let cancelAction = UIAlertAction(title: "cancel", style: .cancel, handler: nil)
actionSheet.addAction(cancelAction)
}
addCameraAction()
addGalleryPickerAction()
addRemoveActionIfNeeded()
addCancelAction()
present(actionSheet, animated: true)
}
private func pickImage(source: UIImagePickerControllerSourceType) {
guard UIImagePickerController.isSourceTypeAvailable(source) else { return assertionFailure("Source not found") }
let imagePickerController = UIImagePickerController()
imagePickerController.sourceType = source
imagePickerController.delegate = self
imagePickerController.allowsEditing = true
present(imagePickerController, animated: true)
}
}
extension MyViewController: UIImagePickerControllerDelegate {
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
defer { picker.dismiss(animated: true) }
let editedImage = info[UIImagePickerControllerEditedImage]
let originalImage = info[UIImagePickerControllerOriginalImage]
guard let image = (editedImage ?? originalImage) as? UIImage else { return assertionFailure("Image not found")}
// Do anything you want with image here
// In case of need to convert it to data:
let quality = 1.0
guard let imageData = UIImageJPEGRepresentation(image, quality) else { return assertionFailure("No image data") }
// Do anything you want with image data here
}
}
Swift 3, Xcode 8, IOS:
I can't seem to figure out what I'm doing wrong, no errors show up, but when I click the button in my app, nothing happens and nothing is saved in my simulators camera roll.
This is what I've done for the button in the view controller:
import UIKit
class ViewController: ViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func buttonAction(_ sender: UIButton) {
func captureScreen() {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, UIScreen.main.scale)
view.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
}
}
}
The problem is you have put screenshot taking code inside nested function of your buttonAction method name captureScreen and never called that method, there is no need to add nested method. So simply remove that function and put the screenshot code directly inside the button action method. So replace your button action with this one.
#IBAction func buttonAction(_ sender: UIButton) {
UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, UIScreen.main.scale)
view.layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil)
}
In Swift 3.1
First you need to edit your info.plist file
import Photos
then add the uibutton:
#IBOutlet weak var shutterButton: UIButton!
#IBAction func shotAction(_ sender: Any) {
guard shutterButton.isEnabled else {
return
}
let takeScreenshotBlock = {
//Render and capture an UIView named view:
self.shutterButton.isHidden = true
UIGraphicsBeginImageContextWithOptions(self.view.frame.size, false, 0.0)
self.view.drawHierarchy(in: self.view.frame, afterScreenUpdates: true)
if let image = UIGraphicsGetImageFromCurrentImageContext() {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
UIGraphicsEndImageContext();
self.shutterButton.isHidden = false
DispatchQueue.main.async {
// briefly flash the screen
let flashOverlay = UIView(frame: self.sceneView.frame)
flashOverlay.backgroundColor = UIColor.white
self.sceneView.addSubview(flashOverlay)
UIView.animate(withDuration: 0.25, animations: {
flashOverlay.alpha = 0.0
}, completion: { _ in
flashOverlay.removeFromSuperview()
})
}
}
switch PHPhotoLibrary.authorizationStatus() {
case .authorized:
takeScreenshotBlock()
case .restricted, .denied:
let alertController = UIAlertController(title: "Photo access denied", message: "Please enable Photos Library access for this appliction in Settings > Privacy.", preferredStyle: UIAlertControllerStyle.alert)
let actionOK = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alertController.addAction(actionOK)
present(alertController, animated: true, completion: nil)
case .notDetermined:
PHPhotoLibrary.requestAuthorization({ (status) in
if status == .authorized {
takeScreenshotBlock()
}
})
}
}
I have audio playing in my app using MPMusicPlayerController and Im recording the screen using RPScreenRecorder. The problem Im having is that it only records the screen and not the audio in the app. The other problem I have is that when I press the cancel button for the previewController it doesnt dismiss the view for some reason. What am I doing wrong?
#IBAction func stopTheRecordingAction(sender: AnyObject) {
stopTheRecording.hidden = true
recordButton.hidden = false
RPScreenRecorder.sharedRecorder().stopRecordingWithHandler { (previewController: RPPreviewViewController?, error: NSError?) -> Void in
if previewController != nil {
let alertController = UIAlertController(title: "Recording", message: "Do you wish to discard or view your gameplay recording?", preferredStyle: .Alert)
let discardAction = UIAlertAction(title: "Discard", style: .Default) { (action: UIAlertAction) in
RPScreenRecorder.sharedRecorder().discardRecordingWithHandler({ () -> Void in
// Executed once recording has successfully been discarded
})
}
let viewAction = UIAlertAction(title: "View", style: .Default, handler: { (action: UIAlertAction) -> Void in
self.presentViewController(previewController!, animated: true, completion: nil)
})
alertController.addAction(discardAction)
alertController.addAction(viewAction)
self.presentViewController(alertController, animated: true, completion: nil)
} else {
// Handle error
}
}
}
#IBAction func recordScreen(sender: AnyObject) {
recordButton.hidden = true
stopTheRecording.hidden = false
if RPScreenRecorder.sharedRecorder().available {
RPScreenRecorder.sharedRecorder().startRecordingWithMicrophoneEnabled(true, handler: { (error: NSError?) -> Void in
if error == nil { // Recording has started
} else {
// Handle error
}
})
} else {
// Display UI for recording being unavailable
}
}
func previewControllerDidFinish(previewController: RPPreviewViewController) {
previewController.dismissViewControllerAnimated(true, completion: nil)
print("dismiss")
}
Okay I got it work but I had to use AVAudioPlayer instead of MPMusicPlayerController. For some reason replaykit doesnt record the audio using MPMedia.
I have a variable videoURL of type NSURL.
If I call println(videoURL) it would return something like this:
http://files.parsetfss.com/d540f71f-video.mp4
I have a button set up that should take this videoURL and save the video to the user's camera roll.
The best I have done is this:
UISaveVideoAtPathToSavedPhotosAlbum(videoPath: String!, completionTarget: AnyObject!, completionSelector: Selector, contextInfo: UnsafeMutablePointer<Void>)
While I'm not even sure if this will work or not, I can't figure out how to convert videoFile:NSURL into a videoPath.
Any help is appreciated on this.
Edit:
The following is unsuccessful:
UISaveVideoAtPathToSavedPhotosAlbum(videoURL.relativePath, self, nil, nil)
AssetsLibrary is deprecated
1: import Photos
import Photos
2: Use this code to save video from url to camera library.
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(nsUrlToYourVideo)
}) { 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.presentViewController(alertController, animated: true, completion: nil)
}
}
Swift 3 & Swift 4
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: urlToYourVideo)
}) { 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)
}
}
The accepted answer no longer works with Swift 3.0 & iOS 10.
First, you need to set the following permission in your app's plist file:
Privacy - Photo Library Usage Description
Provide a string that is presented to the user explaining why you are requesting the permission.
Next, import photos:
import Photos
Finally, here is the updated code for Swift 3.0:
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: fileURL)
}) { 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)
}
}
To save video from NSURL to user camera roll
func video(videoPath: NSString, didFinishSavingWithError error: NSError?, contextInfo info: AnyObject)
{
if let _ = error {
print("Error,Video failed to save")
}else{
print("Successfully,Video was saved")
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if let conversationField = self.conversation {
if (mediaType?.isEqual((kUTTypeMovie as NSString) as String))!
{
let theVideoURL: URL? = (info[UIImagePickerControllerMediaURL] as? URL)
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum((theVideoURL?.path)!))
{
UISaveVideoAtPathToSavedPhotosAlbum((theVideoURL?.path)!, self, #selector(ConversationDetailsViewController.video(videoPath:didFinishSavingWithError:contextInfo:)), nil)
}
}
self.dismiss(animated: true, completion: nil)
}
Reference from:: https://www.raywenderlich.com/94404/play-record-merge-videos-ios-swift
Try this instead for saving video in photo library in swift 4.2 and above
func requestAuthorization(completion: #escaping ()->Void) {
if PHPhotoLibrary.authorizationStatus() == .notDetermined {
PHPhotoLibrary.requestAuthorization { (status) in
DispatchQueue.main.async {
completion()
}
}
} else if PHPhotoLibrary.authorizationStatus() == .authorized{
completion()
}
}
func saveVideoToAlbum(_ outputURL: URL, _ completion: ((Error?) -> Void)?) {
requestAuthorization {
PHPhotoLibrary.shared().performChanges({
let request = PHAssetCreationRequest.forAsset()
request.addResource(with: .video, fileURL: outputURL, options: nil)
}) { (result, error) in
DispatchQueue.main.async {
if let error = error {
print(error.localizedDescription)
} else {
print("Saved successfully")
}
completion?(error)
}
}
}
}
Use of function
self.saveVideoToAlbum(/* pass your final url to save */) { (error) in
//Do what you want
}
Don't forgot to import Photos and add Privacy - Photo Library Usage Description to your info.plist
deprecated as of iOS 9
1: import AssetsLibrary
import AssetsLibrary
2: Use this code to save video from url to camera library.
ALAssetsLibrary().writeVideoAtPathToSavedPhotosAlbum(outputFileURL, completionBlock: nil)
Just use it and paste your video's url:
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
let createAssetRequest: PHAssetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(NSURL(string: /* your url */)!)!
createAssetRequest.placeholderForCreatedAsset
}) { (success, error) -> Void in
if success {
//popup alert success
}
else {
//popup alert unsuccess
}
}