I have successfully been running this AVAudioRecorder example through a regular XCode based project. I would like to record from a daemon application running on a jailbroken device and tried to implement the same functionality from a simple command line app.
In my simple test, I start the recording from main and sleep for a few seconds before stopping the recording. The result is always the same: A truncated .caf file of size 4096 bytes (looks like a correct header and null data). It doesn't make any difference if I initiate the recording from a separately spawned thread. I tried both record() and recordForDuration().
All methods invoked return an OK result. If I invoke recording() on the AVAudioRecorder instance, it returns YES while recording.
Do I miss some fundamental initialization that a regular XCode based GUI app takes care of behind the scenes?
Related
My app (made with Flutter but this should not matter) has something like a timer functionality that makes a tick sound in regular periods (between 10s and 3min). I have the background mode Audio, AirPlay, and Picture in Picture activated and the following in my Info.plist.
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
but the audio will still stop when running in background.
This occurs when running the app in profile mode, when I run in debug mode, the audio continues when running in background.
What can I do to have the audio continue to run in background?
There is a relevant note in the audio_service 0.18.0 README which can help here:
Note that the audio background mode permits an app to run in the background only for the purpose of playing audio. The OS may kill your process if it sits idly without playing audio, for example, by using a timer to sleep for a few seconds. If your app needs to pause for a few seconds between audio tracks, consider playing a silent audio track to create that effect rather than using an idle timer.
Well, unless you did this in native code (Swift/Objective-C), your code is running inside the Flutter engine - probably with some Dart Timer.periodic.
The Flutter engine may be killed off at any point in time when the app is in the background. On Android this can even happen when simply switching to the camera and back to the app afterwards. On iOS usually after some fixed time or on high system load.
In this regard, Flutter (and most other cross-platform toolkits) are very different to native apps.
You can start with the official documentation here: https://flutter.dev/docs/development/packages-and-plugins/background-processes
This may be a good article: https://medium.com/vrt-digital-studio/flutter-workmanager-81e0cfbd6f6e
I don't know enough about iOS but I think there is no easy way to schedule execution in the small intervals you require. On Android something like the AlarmManager can be used.
You can try writing the scheduling code natively and schedule it from the app via a MethodChannel when the period is set.
You can look at these libraries:
https://pub.dev/packages/workmanager (probably can't wake up at the small intervals you need)
https://pub.dev/packages/android_alarm_manager_plus (only for Android)
https://pub.dev/packages/audio_service (may give you some idea on how to achieve background execution on iOS)
Edit:
After reading more about Enabling Background Audio on iOS, it seems to me, that this only works when using
an AVAudioSession. Which you are probably not using. To get this working you need some native code. The audio_service package uses such a session. You can try scheduling with Dart code and playing the sound via the audio_service package. Sounds like it could work but I have no experience with this package.
Please, pay attention to the answer from #RyanHeise – he's correct on the point of using AudioSession: in background you should play either sound or silence. As soon as audio will be paused, app can be suspended.
Also, Important Note: when app entering background, scheduled timers will pause. That's why you might think it stopped working. Do not use scheduling via Timers on background - rely on events from the system.
Issue description
AVSampleBufferDisplayLayer seems to hang on iOS 12.3.1 (>= iOS 12.2 is also affected) after
reboot. It looks that after 5 minutes everything works fine again.
The issue is not reproducible on iOS 11.
In our production code we don't use AVAssetReader, so please ignore any issues
with it, if minor.
I can make application hang on AVSampleBufferDisplayLayer init, enqueue and
requestMediaDataWhenReadyOnQueue.
Please advise how we should implement AVSampleBufferDisplayLayer correctly
(examples are welcome). Especially when we have to maintain and exchange
many display layers at once.
How to reproduce the issue:
Clone repo: https://github.com/mackode/AVSampleBufferDisplayLayer_Hanging
Download:
Tears of Steel segment
Put tearsofsteel_4k.mov_1918x852_2000.mp4 in Resources/ directory
Open AVSampleBufferDisplayLayer_BlackScreen.xcodeproj
Take iOS 12.3.1 device and reboot it
Start debugging AVSampleBufferDisplayLayer_BlackScreen
The video should start playing, then move a slider (seek) one or a few more times
Sample code is available here:
https://github.com/mackode/AVSampleBufferDisplayLayer_Hanging
Observed result:
Video hangs usually on AVSampleBufferDisplayLayer methods
Expected result:
Video seeks and continues to play
Can someone help me with solving this mistery?
I've come across an issue with Core MIDI across a couple of iOS apps I work on, and another app I downloaded from the App Store.
What appears to be happening is the Core MIDI server crashes. After this happens, any interaction with Core MIDI functions or properties makes the calling app unresponsive. The following line is in AppDelegate's didFinishLaunchingWithOptions::
[MIDINetworkSession defaultSession].enabled = YES;
Execution stops at this point, causing a forever hang when debugging or a #8badf00d crash otherwise.
This happens even if the Core MIDI function should return an OSStatus - the return value never comes back:
OSStatus s = MIDIObjectGetStringProperty(ref, kMIDIPropertyDisplayName, (CFStringRef*)&string);
This call (which is in the PGMidi library) also hangs, with s not being returned. The only way to fix the issue is to restart the device.
The Core MIDI server becoming unresponsive seems related to running multiple Network Sessions from a connected Mac, but I haven't been able to pinpoint a way to replicate reliably. As well as my own apps (which use the MIKMIDI library), I have experienced unresponsive behaviour with midimittr, downloaded from the App Store.
Of course, I have no control over whether the Core MIDI Server becomes unresponsive. But is there a way to check the status of the MIDI server before making calls that may make my app unresponsive?
Update: 2019-05-10
I've found that I can get the iOS Core MIDI server into this unresponsive state by following these steps:
Set up a Network MIDI session on a Mac in Audio/MIDI Setup.
In iOS app, connect to that Network MIDI session.
Let Mac go to sleep naturally (invoking sleep from the Apple Menu doesn't appear to trigger the issue).
Once the Mac has gone to sleep do something in iOS app that will call Core MIDI.
So it's like the iOS Core MIDI server has lost connection to the Mac's MIDI network, but continues to try and execute.
Update: 2019-05-17
Apple responded to my bug report (#50657978) saying it is a duplicate of another report (#49583498) and will be closed. At the very least they are aware of the bug.
Update: 2019-07-17
This Core MIDI hang state can also be entered more quickly (for debugging purposes) by connecting the Core MIDI network via Audio Music Setup, then backgrounding your app and switching on Airplane mode on the iOS device.
SEE BELOW FOR LATEST UPDATE AND FINAL SOLUTION
I've managed to come up with a partial solution to this problem. (Alas, as the root cause seems embedded in Apple's code a complete solution is not possible.)
In my AppDelegate I now run a routine which can detect whether calls to Core MIDI will time out:
func enableMIDINetworkSession() {
let midiNetworkGroup = DispatchGroup()
midiNetworkGroup.enter()
DispatchQueue.global(qos: .background).async {
MIDINetworkSession.default().isEnabled = true
midiNetworkGroup.leave()
}
// If `midiNetworkGroup.leave()` is not reached in the closure above, then the result below will be `.timeOut`
let midiNetworkTimeoutResult = midiNetworkGroup.wait(timeout: DispatchTime.now() + 10.0)
switch midiNetworkTimeoutResult {
case .timedOut:
{ ... } // Calls to Core MIDI will cause app to hang. Handle as required. Note: app will crash anyway at some point in the future.
case .success:
break // All good, continue as before
}
}
A thing to note here: if you intend to display an alert in the case that midiNetworkTimeoutResult == .timeOut then keep in mind that any calls to Core MIDI that occur straight after this may cause the alert to not be presented (as the main queue will be blocked).
Update: 2019-07-17
It's important to mention with the solution above that if you end up in the .timedOut case above, the app will crash at some point in the future anyway, as it catches up with the MIDINetworkSession.default().isEnabled call.
The gap before the crash is around 5 minutes, so there's enough time to display an alert and advise the user to restart their device.
Update: 2019-08-14
Recommended solution
It appears that this issue has been resolved by Apple in iOS 12.4. The solution then would be to advise your users to update to iOS 12.4 or later.
(Also, this issue seems only to affect iOS 12.2 and 12.3 versions.)
I have a Timer which plays AVAudio at a specific time while the screen is locked (app in foreground when locked and plist setting 'Application does not run in background' set to YES).
When I run my code while my iPhone is connected to Xcode it works as desired, playing audio while locked even when the iPhone has been locked for hours.
Now if I disconnect my iPhone and just open the app by tapping the icon the audio won't be played if it's set for more than a minute.
Is there a difference that causes this problem? If I would publish my App to the AppStore which behaviour would my users get?
That 'Application does not run in background' flag is a very old flag that means your app does not support multitasking and should be terminated when the user presses the home button. There's no good reason I can think of to set that flag on a newly developed application. Unless you understand exactly what it's for and are positive that setting it to yes is the correct thing to do, don't do that.
(That flag was added when iOS 4 was released, to allow apps that could not handle multi-tasking to request the old terminate-on-home-button behavior from iOS 3. Given that we're now 6 major iOS releases from iOS 4, it seems outdated.)
So remove that flag. It probably doesn't have any impact on your question, but remove it or set it to no in any case.
If you want to run a timer while the phone is locked and play sounds at designated times then you will probably need to set your app up as a background sound player, and ask for more background time when you get a message that you are going to the background.
That being said it's likely that Apple will reject your app. Running a timer from the background means that the processor on the phone has to run at full speed all the time, which will DRAMATICALLY reduce battery life.
Apple only allows a very small subset of apps to run in the background, and then only under specific circumstances (like a background sound playing app when the user is actively listening to music, or a navigation app that is actively tracking the user's location and notifying them when it's time to make a turn to follow directions.)
Should be no different. If you'd like to feel what do your users' feel then you can publish it to TestFlight which is the best app beta test environment. Find some testers or try it yourself.
The directions should be to verify the playing logic and the sound file relationship which link to the connection.
I am writing an app for personal use that needs continuous background execution. Apple tries to make this difficult, but I found that declaring that my app uses background location updates and then creating background task (with no content, just a blank task) allows my app to run indefinitely when I run the project from Xcode. I do not understand why this works. I noticed that after about three minutes, when presumably the task is supposed to be terminated, I get the following error:
Can't endBackgroundTask: no background task exists with identifier 174256fe0, or it may have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.
After this message, the app continues to run in the background normally. So, it seems that this indefinite execution is a bit of an exploit, which I'm fine with because I just want to make the app work.
However, when I run the app from the phone (i.e. not pressing the run button in my Xcode project), the background execution seems to stop after 3 minutes. This is a problem, and I can't seem to figure out why execution continues in one case but not in the other. What is the difference between running an app normally and running it through Xcode? Is there a simpler or better way to get the indefinite background execution I'm looking for given that I'm not trying to get this app published to the app store?
Since you're not submitting to the store - why not try this:
Enable the app for background audio services. Then, just play a silent audio file in the background on a constant loop?
The difference is that you are running through Xcode. Normal background limits are removed when running through the debugger.