macOS: AVAssetWriter crash upon markAsFinished / finishWriting - swift

Using AVAssetWriter to save captured audio and video to a file. It works correctly on my own machines all the time, but Apple's App Review team reports a crash when finishing recording.
In the report the thread crashes upon finishWriting(completionHandler:).
My code to stop writing:
var videoWriterInput: AVAssetWriterInput!
var audioWriterInput: AVAssetWriterInput!
var videoWriter: AVAssetWriter!
var isRecording = false
func stopVideoWriter() async {
guard isRecording else { return }
isRecording = false
videoWriterInput.markAsFinished()
audioWriterInput.markAsFinished()
videoWriter.finishWriting { [self] in
videoWriter = nil
videoWriterInput = nil
audioWriterInput = nil
}
}
Some SO posts mention to check videoWriter.status before stopping, but this does not seem to help at all. Also there is no documentation that shows it is necessary to check the status before finishing.
Apple's documentation for finishWriting(completionHandler:) mentions the following:
To ensure the asset writer finishes writing all samples, call this
method only after all calls to append(:) or
append(:withPresentationTime:) return.
The app works fine on my own testing machines, so I cannot confirm that this is the actual part where the crash happens.
Any ideas?

Related

Carplay Resume Music

I am creating a CarPlay music app, everything works fine except when I get a call in between my music is playing. Carplay pauses music in between but when I end phone call its not resuming back.
Please help if anyone has faced same issue
CarPlay is not actually playing or pausing the music, it is still your app on the user's device that is doing all the work and CarPlay is just presenting an interface to interact with your app which is what you do in your CarPlaySceneDelegate
Therefore, the handling of how your app resumes after the call is not part of CarPlay.
You can try the following in your player management module:
1. Listen for audio interruption notifications
NotificationCenter.default
.addObserver(self,
selector: #selector(handleInterruption(_:)),
name: AVAudioSession.interruptionNotification,
object: nil)
2. Check the status of the interruption and handle accordingly
#objc
private func handleInterruption(_ notification: Notification)
{
// Check if we have valid information from the notification
// with regards to the interruption
guard let info = notification.userInfo,
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeValue)
else
{
return
}
// We check if the interruption has begun
if type == .began
{
// Pause the AVPlayer in that case
player?.pause()
}
else if type == .ended
{
// We check if we have instructions on how to handle the interruptions
guard let optionsValue = info[AVAudioSessionInterruptionOptionKey] as? UInt else
{
return
}
// Convert the optionsValue into an AVAudioSession.InterruptionOptions
// which can be tested
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
// Check if the playback can resume after the interruption
if options.contains(.shouldResume)
{
// Request AVPlayer to resume the playback
player?.play()
}
}
}
Give this a try and see if it helps your situation
Update based on OP comments
The audio not re-starting is not related to CarPlay, if you app resumes audio after a call, this will be carried over to CarPlay.
With that being said, if you have not already, add this code when your player initializes:
UIApplication.shared.beginReceivingRemoteControlEvents()
Maybe my code does not work for your exact scenario but I believe you will need to use the AVAudioSession.interruptionNotification which is what get's called when a phone call, siri or similar things interrupt your audio.
When this interruption has ended, you need to restart your audio.
I can't show you a CarPlay example, but here is an example of the above code in action when Siri interrupts the audio, the lock screen shows the status of the audio being paused, when Siri is dismissed, the app resumes the player as you can see.
So I found solution: - I was missing one line of code,
"try AVAudioSession.sharedInstance() .setCategory(AVAudioSession.Category.playback)" this is must for maintaining audio session

Check if Screen is recording swift

I am trying to check if the screen is recording before allowing a following action, I initially tried to use ReplayKit to automatically record, but this isn't a viable solution because it doesn't allow for recording outside the app, so basically what I want to do is check if the user has began screen recording using the IOS Control Centre recorder, before allowing them to execute another piece of code.
Something like:
func handleScreen() {
var isRecording: Bool = false
if ScreenIsRecording { //(this is what i don't know how to check)
isRecording = true
}
if isRecording == true {
// execute this code.
}
}
I am open to other solutions of being able to execute screen recording, but it has to be able to record all screens not just the in-app screen.
Thanks
UIScreen includes the UIScreen.isCaptured property which you should be able to reference to determine if the screen is likely being recorded. However this will also return true if the device is being AirPlayed or otherwise broadcast:
A value of YES indicates the system is actively recording, mirroring,
or using AirPlay to stream the contents of the screen.
if UIScreen.mainScreen().isCaptured {
isRecording = true
}
I just tested this on iOS 16, and even though the documentation says that isCaptured should return true for AirPlay. It does not when I tested it!
Observe changes to isCaptured with NotificationCenter:
NotificationCenter.default.addObserver(self, selector: #selector(screenCaptureDidChange),
name: UIScreen.capturedDidChangeNotification,
object: nil)
Handle the notification:
#objc func screenCaptureDidChange() {
debugPrint("screenCaptureDidChange.. isCapturing: \(UIScreen.main.isCaptured)")
if !UIScreen.main.isCaptured {
//TODO: They stopped capturing..
} else {
//TODO: They started capturing..
debugPrint("screenCaptureDidChange - is recording screen")
}
}
This notification does NOT get fired when you start AirPlay, and if you start screen recording while AirPlaying, when you stop recording the notification will get fired but UIScreen.main.isCaptured is false even though AirPlay is still active.

Problems with NSOpenPanel and massive memory leak

I am developing a GUI for rsync (RsyncOSX) and in next version the plan is add a GUI for choosing catalogs. I have some time ago experienced some stability issues utilizing NSOpenPanel but now I wanted to try again. But still there seems to issues utilizing NSOpenPanel, the console is producing the following error:
Class FIFinderSyncExtensionHost is implemented in both /System/Library/PrivateFrameworks/FinderKit.framework/Versions/A/FinderKit (0x7fff8c017210) and /System/Library/PrivateFrameworks/FileProvider.framework/OverrideBundles/FinderSyncCollaborationFileProviderOverride.bundle/Contents/MacOS/FinderSyncCollaborationFileProviderOverride (0x10f1d5dc8). One of the two will be used. Which one is undefined.
I have also run the app through Xcode instruments and checked for memory leaks and every time opening the NSOpenPanel there is a huge memory leak..
Has anyone found a workaround? The code for opening the GUI is very simple :
private func openfiledlg (title: String, message: String) {
let openPanel = NSOpenPanel()
openPanel.prompt = "Select"
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = true
openPanel.canCreateDirectories = true
openPanel.canChooseFiles = false
openPanel.resolvesAliases = true
openPanel.title = title
openPanel.message = message
if self.modal {
let OK = openPanel.runModal()
if OK.rawValue == NSApplication.ModalResponse.OK.rawValue {
self.urlpath = openPanel.url
}
} else {
openPanel.begin(completionHandler: { response in
if response.rawValue == NSFileHandlingPanelOKButton {
self.urlpath = openPanel.url
}
openPanel.close()
})
}
}
Setting weak var openPanel = NSOpenPanel() only reduces the memory leak..
If the window is set to be released when closed, a release message is sent to the object after the current event is completed. For an NSWindow object, the default is to be released on closing, while for an NSPanel object, the default is not to be released. You can use the isReleasedWhenClosed property to change the default behavior.

Programmatically disabling screenshot in App

I want to prevent taking screenshot of a page in app.
how to do it programmatically so that screenshots cannot be taken.
Found code to detect screenshot. Can it be deleted as soon as a screenshot is taken?
let mainQueue = NSOperationQueue.mainQueue()
NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationUserDidTakeScreenshotNotification,
object: nil,
queue: mainQueue) { notification in
// executes after screenshot
}
There is no way to prevent ScreenShots but you can prevent Screen Recording
through this code.
func detectScreenRecording(action: #escaping () -> ()) {
let mainQueue = OperationQueue.main
NotificationCenter.default.addObserver(forName: UIScreen.capturedDidChangeNotification, object: nil, queue: mainQueue) { notification in
// executes after screenshot
action()
}
}
//Call in vewWillApper
detectScreenRecording {
print(UIScreen.main.isCaptured)
if UIScreen.main.isCaptured {
//your vier hide code
print("self.toHide()")
} else {
// self.sceneDeleg(ate?.window?.isHidden = false
//your view show code
print("self.toShow()")
}
}
There is absolutely no way to completely prevent user from taking screenshot during the app process, and that's because you do not have access to delete photos in the photo gallery of the user. It would totally be a security issue if you could access your user's photos.
However, there are ways to partially prevent screenshots, as described here: Prevent screen capture in an iOS app
Technically that is possible, via the Photos framework, the docs for which can be found here.
Example code can be found here.
However, this will ask the user's permission first, and then again to confirm deletion; so possibly not the ideal solution. Unfortunately this is as good as it gets as Apple has the Camera Roll fairly locked down.
You cannot prevent user from taking screenshot, however, you can hide the content while a screenshot is taken, Use this code to do so..
extension UIView {
func hideContentOnScreenCapture() {
DispatchQueue.main.async {
let field = UITextField()
field.isSecureTextEntry = true
self.addSubview(field)
field.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
field.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
self.layer.superlayer?.addSublayer(field.layer)
field.layer.sublayers?.first?.addSublayer(self.layer)
}
}
}
Usage:
yourView.hideContentOnScreenCapture()

Capturing Video with Swift using AVCaptureVideoDataOutput or AVCaptureMovieFileOutput

I need some guidance on how to capture video without having to use an UIImagePicker. The video needs to start and stop on a button click and then this data be saved to the NSDocumentDirectory. I am new to swift so any help will be useful.
The section of code that I need help with is starting and stopping a video session and turning that to data. I created a picture taking version that runs captureStillImageAsynchronouslyFromConnection and saves this data to the NSDocumentDirectory. I have set up a video capturing session and have the code ready to save data but do not know how to get the data from the session.
var previewLayer : AVCaptureVideoPreviewLayer?
var captureDevice : AVCaptureDevice?
var videoCaptureOutput = AVCaptureVideoDataOutput()
let captureSession = AVCaptureSession()
override func viewDidLoad() {
super.viewDidLoad()
captureSession.sessionPreset = AVCaptureSessionPreset640x480
let devices = AVCaptureDevice.devices()
for device in devices {
if (device.hasMediaType(AVMediaTypeVideo)) {
if device.position == AVCaptureDevicePosition.Back {
captureDevice = device as? AVCaptureDevice
if captureDevice != nil {
beginSession()
}
}
}
}
}
func beginSession() {
var err : NSError? = nil
captureSession.addInput(AVCaptureDeviceInput(device: captureDevice, error: &err))
if err != nil {
println("Error: \(err?.localizedDescription)")
}
videoCaptureOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey:kCVPixelFormatType_32BGRA]
videoCaptureOutput.alwaysDiscardsLateVideoFrames = true
captureSession.addOutput(videoCaptureOutput)
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
self.view.layer.addSublayer(previewLayer)
previewLayer?.frame = CGRectMake(0, 0, screenWidth, screenHeight)
captureSession.startRunning()
var startVideoBtn = UIButton(frame: CGRectMake(0, screenHeight/2, screenWidth, screenHeight/2))
startVideoBtn.addTarget(self, action: "startVideo", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(startVideoBtn)
var stopVideoBtn = UIButton(frame: CGRectMake(0, 0, screenWidth, screenHeight/2))
stopVideoBtn.addTarget(self, action: "stopVideo", forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(stopVideoBtn)
}
I can supply more code or explanation if needed.
For best results, read the Still and Video Media Capture section from the AV Foundation Programming Guide.
To process frames from AVCaptureVideoDataOutput, you will need a delegate that adopts the AVCaptureVideoDataOutputSampleBufferDelegate protocol. The delegate's captureOutput method will be called whenever a new frame is written. When you set the output’s delegate, you must also provide a queue on which callbacks should be invoked. It will look something like this:
let cameraQueue = dispatch_queue_create("cameraQueue", DISPATCH_QUEUE_SERIAL)
videoCaptureOutput.setSampleBufferDelegate(myDelegate, queue: cameraQueue)
captureSession.addOutput(videoCaptureOutput)
NB: If you just want to save the movie to a file, you may prefer the AVCaptureMovieFileOutput class instead of AVCaptureVideoDataOutput. In that case, you won't need a queue. But you'll still need a delegate, this time adopting the AVCaptureFileOutputRecordingDelegate protocol instead. (The relevant method is still called captureOutput.)
Here's one excerpt from the part about AVCaptureMovieFileOutput from the guide linked to above:
Starting a Recording
You start recording a QuickTime movie using startRecordingToOutputFileURL:recordingDelegate:. You need to supply a
file-based URL and a delegate. The URL must not identify an existing
file, because the movie file output does not overwrite existing
resources. You must also have permission to write to the specified
location. The delegate must conform to the
AVCaptureFileOutputRecordingDelegate protocol, and must implement the
captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:
method.
AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
NSURL *fileURL = <#A file URL that identifies the output location#>;
[aMovieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:<#The delegate#>];
In the implementation of
captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:,
the delegate might write the resulting movie to the Camera Roll album.
It should also check for any errors that might have occurred.