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.
Related
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?
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
I have an AVQueuePlayer which is working as expected except for after returning from a suspended state.
It's playing a HLS audio stream. The logic for my pause button is very simple
isPlaying ? player?.play() : player?.pause()
I've checked the state of the player and the currentItem, all of which say it's "playing". But the audio does not progress or playback
player.rate = 1
player.currentItem.asset.isPlayable = true
player.status == readyToPlay
player.currentItem.status == readyToPlay
I'm also setting the session with:
do {
try session.setCategory(AVAudioSession.Category.playback, options: [])
try session.setActive(true, options: [])
} catch {
print("Failed to set session active")
}
Any advice on how to troubleshoot this would be greatly appreciated. Do I need to keep track of the app entering a suspended state and reload the AVPlayerItem?
Do I need to keep track of the app entering a suspended state and reload the AVPlayerItem?
It is not suspension of the app that you need to keep track of; it's interruption of the audio session. AVAudioSession provides a notification for exactly this purpose.
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()
I need to make views visible at fixed time while running a MP3 audio. I am using Swift. Any suggestions?
I don't like using timers if drawing depends on them. They may fire more frequently than the screen redraws or out of sync with the screen drawing. I use a CADisplayLink instead. It's similar to a timer but it is designed to fire in synch with the screen's refresh rate. You could implement something like this.
var messagePlaybackTimer: CADisplayLink?
var player: AVAudioPlayer?
func playAudioData(){
let path = NSBundle.mainBundle().pathForResource("audio", ofType: "mp3")
var url: NSURL
if let audioResourcePath = path {
url = NSURL.fileURLWithPath(audioResourcePath)!
}else{
return;
}
player = AVAudioPlayer(contentsOfURL: url, error: nil)
messagePlaybackTimer?.invalidate()
messagePlaybackTimer = nil;
messagePlaybackTimer = CADisplayLink(target: self, selector: Selector("messagePlaybackTimerFired"))
messagePlaybackTimer?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
player?.play()
}
func messagePlaybackTimerFired(){
println(player?.currentTime)
}
In messagePlaybackTimerFired() you can check the AVAudioPlayer's current time and fire events based on that. (E.g. if player?.currentTime > 5 && player?.currentTime < 6 { do something } )
This will allow you to fire the method at as close to 5 seconds as possible while still being optimal for doing UI work.
When you are done with the CADisplayLink (when the player stops playing) be sure to invalidate it. If you don't, it will keep firing when you don't need it anymore.
Another thing you can do is fire off an NSNotification in messagePlaybackTimerFired() and your UIViewController can subscribe to the notification and show the views when appropriate.
Use an NSTimer or the AVAudioPlayer's addPeriodicTimeObserverForInterval:queue:usingBlock: method to check the value, it's (very most likely) not possible to get updates when a certain time is reached.
Oh and for a Swiftier NSTimer I suggest this