Detect Headphones Unplugged - Monotouch - iphone

Is there a way to detect if headphones are unplugged in Monotouch? I am trying to look for the AudioSessionAddPropertyListener method but don't see it. What this method ported over?
Here is Apple's docs: http://developer.apple.com/library/ios/#documentation/AudioToolbox/Reference/AudioSessionServicesReference/Reference/reference.html#//apple_ref/doc/constant_group/Audio_Session_Interruption_States
If anyone wants to see the code for how to do this, you can do the following:
AudioSession.PropertyListener p = delegate(AudioSessionProperty prop, int size, IntPtr data) {
NSDictionary propertyDictionary = new NSDictionary(data);
if (propertyDictionary.ContainsKey(NSObject.FromObject("OutputDeviceDidChange_OldRoute")))
{
string oldRoute = propertyDictionary.ValueForKey(new NSString("OutputDeviceDidChange_OldRoute")).ToString();
if (oldRoute == "Headphone")
{
if (audioPlayer != null)
{
audioPlayer.Pause();
}
}
}
};
AudioSession.AddListener(AudioSessionProperty.AudioRouteChange, p);

Is there a way to detect if headphones are unplugged in Monotouch?
I'm not sure but...
I am trying to look for the AudioSessionAddPropertyListener method but don't see it. What this method ported over?
The native call to AudioSessionAddPropertyListener maps to MonoTouch's AudioSession.AddListener static method.

Related

Wrong device count after connecting a new device

I'm integrating Agora with Unity, and we have a device selection screen for the user to select and test their devices before joining a call.
The problem I'm having is that Agora is not detecting device changes accordingly in runtime, which won't let me update my UI to reflect these changes.
void Start()
{
// get Agora engine, should be initialized already in the AgoraIOController component
agoraEngine = GetComponent<AgoraIOController>().GetAgoraEngine();
agoraEngine.OnAudioDeviceStateChanged += DeviceChangedHandler;
InitializeDeviceManager();
}
...
void DeviceChangedHandler(string deviceId, int deviceType, int deviceState)
{
devicesDirty = true;
onDevicesChanged.Invoke();
}
...
void RefreshDeviceList()
{
devices.Clear();
int audioDeviceCount = audioDeviceManager.GetAudioPlaybackDeviceCount();
if (audioDeviceCount == (int)ERROR_CODE.ERROR_NOT_INIT_ENGINE)
{
Debug.LogError("Agora engine not initialized, can't refresh devices");
return;
}
else if (audioDeviceCount < (int)ERROR_CODE.ERROR_OK)
{
Debug.LogError($"Unknown error while trying to get devices. Error code: {audioDeviceCount}");
return;
}
Debug.Log($"Found {audioDeviceCount} audio devices.");
for (int i = 0; i < audioDeviceCount; i++)
{
string deviceName = null;
string deviceId = null;
int result = audioDeviceManager.GetAudioPlaybackDevice(i, ref deviceName, ref deviceId);
if (result != (int)ERROR_CODE.ERROR_OK)
{
Debug.LogError("Error when trying to get audio device");
continue;
}
devices.Add(new AgoraDevice()
{
deviceId = deviceId,
deviceName = deviceName,
type = MEDIA_DEVICE_TYPE.AUDIO_RECORDING_DEVICE
});
}
}
If I connect a new microphone and restart the application, it's detected as expected, but if I connect a new device in runtime, I get the event for agoraEngine.OnAudioDeviceStateChanged but when I refresh the device list, the device count and device info is not being updated, so my UI is not showing the new state accordingly.
This happens if I have one mic and I connect a second one, or if I have two mics and I disconnect one. In either case Agora is not reflecting these changes after the devices changed event.
I also tried refreshing the device list in the next frame, or adding a button to manually refresh the list, to check if there was some delay in Agora for doing that update, but it's not happening.
Without this feature we're gonna have lots of issues with clients, connecting new devices in runtime happens all the time and we need to make this software robust and support these scenarios.
Any help is greatly appreciated!
EDIT:
Releasing and recreating the device manager helped, and the device list is updated, but this looks really weird and I don't think that the API should be used like this.
void DeviceChangedHandler(string deviceId, int deviceType, int deviceState)
{
devicesDirty = true;
audioDeviceManager.ReleaseAAudioRecordingDeviceManager();
audioDeviceManager.CreateAAudioRecordingDeviceManager();
onDevicesChanged.Invoke();
}
Since you are testing with mic plug/unplug, did you intend to call AudioPlaybackDeviceManager instead of AudioRecordingDeviceManager in your first part of the code?

WatchOS: SDWebImageLottieCoder load new animation while watch app is running

I'm following this tutorial to use Lottie animations library on WatchOS. It's working until I try to change the animation while the app is running. Even though I change it, the animation stays the same until I run the watch app from Xcode. (If I simply close the app and open again it doesn't change.)
What I tried:
clearing the cache from the watch file system
clearing URLCache
clearing SDImageCache memory and disk
You were right that caching causes your problem. However, you didn't go deep enough.
You use SDWebImageLottieCoder to display your animations, which in turn uses rlottie.
According to their readme:
"rlottie is a platform independent standalone c++ library for rendering vector based animations and art in realtime."
The point is that the animation is cached by the rlottie framework, so neither clearing URLCache nor SDImageCache will solve this.
The easiest way to solve this is to modify the code in the rlottie C++ library. In your Xcode project navigate to Pods -> Pods -> librlottie and look for the following file: lottieanimation_capi.cpp
In this file search for the following code:
RLOTTIE_API Lottie_Animation_S *lottie_animation_from_data(const char *data, const char *key, const char *resourcePath)
{
if (auto animation = Animation::loadFromData(data, key, resourcePath) ) {
Lottie_Animation_S *handle = new Lottie_Animation_S();
handle->mAnimation = std::move(animation);
return handle;
} else {
return nullptr;
}
}
This is the code that loads your Lottie animation on your device and is responsible for caching. loadFromData takes an additional fourth parameter called cachePolicy, which is a boolean, and nothing is passed for it in this case.
So if you don't want your animation to be cached then pass false into this call.
Modify the code to this and it should work:
RLOTTIE_API Lottie_Animation_S *lottie_animation_from_data(const char *data, const char *key, const char *resourcePath)
{
if (auto animation = Animation::loadFromData(data, key, resourcePath, false) ) {
Lottie_Animation_S *handle = new Lottie_Animation_S();
handle->mAnimation = std::move(animation);
return handle;
} else {
return nullptr;
}
}

Does Apple allow to monitor springboard events?

I am trying to detect when a user locks the device (vs. pressing home button for instance).
Found this:
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
NULL, // observer
lockStateChanged, // callback
CFSTR("com.apple.springboard.lockstate"), // event name
NULL, // object
CFNotificationSuspensionBehaviorDeliverImmediately);
static void lockStateChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
NSLog(#"event received!");
// you might try inspecting the `userInfo` dictionary, to see
// if it contains any useful info
if (userInfo != nil) {
CFShow(userInfo);
}
}
I can imagine that com.apple.springboard.lockstate is like calling private API? Or is this fine?
Assuming all the CF... functions are public you are probably OK, but in a murky area for sure. Next release of iOS could break your code if Apple changes that string.
What I did in a similar situation for an approved shipping app was to avoid using the string directly. Create an array of the strings, then use the NSString method to combine them with a period separator instead of using com.apple.springboard.lockstate directly.
YMMV

BluetoothManager Framework Notifications List

I want to get all the notifications of the BluetoothManager private framework. I've been searching but i only have found two (BluetoothAvailabilityChangedNotification and BluetoothDeviceDiscoveredNotification).
I'm interesting in a notification that reports if iphone connected/disconnected to a device. If anyone could get me a list of all notifications i will be appreciated.
I don't have a full list, but these are the ones you were interested in:
BluetoothDeviceConnectFailedNotification
BluetoothDeviceConnectSuccessNotification
BluetoothDeviceDisconnectFailedNotification // haven't confirmed this one
BluetoothDeviceDisconnectSuccessNotification
Here are some others:
BluetoothConnectabilityChangedNotification // fires when bluetooth is turned on/off
BluetoothAvailabilityChangedNotification // seems to fire once at app start)
BluetoothPowerChangedNotification
BluetoothDeviceDiscoveredNotification
BluetoothDeviceRemovedNotification
BluetoothPairingUserNumericComparisionNotification
BluetoothPairingPINResultSuccessNotification
Add before you call [BluetoothManager sharedInstance]:
CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(),
NULL,
bluetoothCallback,
NULL,
NULL,
CFNotificationSuspensionBehaviorDeliverImmediately);
and somewhere in this implementation the method void bluetoothCallback:
void bluetoothCallback (CFNotificationCenterRef center,
void *observer,
CFStringRef name,
const void *object,
CFDictionaryRef userInfo)
{
if (CFStringGetCharacterAtIndex(name, 0) == 'B') { // stupid way to filter for only 'B'luetooth notifications
NSLog(#"%#", name);
}
}
Your console log shows you all bluetooth notifications now.

how to run vibrate continuously in iphone?

In my application I'm using following coding pattern to vibrate my iPhone device
Include: AudioToolbox framework
Header File:
#import "AudioToolbox/AudioServices.h"
Code:
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
My problem is that when I run my application it gets vibrate but only for second but I want that it will vibrate continuously until I will stop it.
How could it be possible?
Thankfully, it's not possible to change the duration of the vibration. The only way to trigger the vibration is to play the kSystemSoundID_Vibrate as you have. If you really want to though, what you can do is to repeat the vibration indefinitely, resulting in a pulsing vibration effect instead of a long continuous one. To do this, you need to register a callback function that will get called when the vibration sound that you play is complete:
AudioServicesAddSystemSoundCompletion (
kSystemSoundID_Vibrate,
NULL,
NULL,
MyAudioServicesSystemSoundCompletionProc,
NULL
);
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
Then you define your callback function to replay the vibrate sound again:
#pragma mark AudioService callback function prototypes
void MyAudioServicesSystemSoundCompletionProc (
SystemSoundID ssID,
void *clientData
);
#pragma mark AudioService callback function implementation
// Callback that gets called after we finish buzzing, so we
// can buzz a second time.
void MyAudioServicesSystemSoundCompletionProc (
SystemSoundID ssID,
void *clientData
) {
if (iShouldKeepBuzzing) { // Your logic here...
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
} else {
//Unregister, so we don't get called again...
AudioServicesRemoveSystemSoundCompletion(kSystemSoundID_Vibrate);
}
}
There are numerous examples that show how to do this with a private CoreTelephony call: _CTServerConnectionSetVibratorState, but it's really not a sensible course of action since your app will get rejected for abusing the vibrate feature like that. Just don't do it.
Read the Apple Human Interaction Guidelines for iPhone. I believe this is not approved behavior in an app.
iOS 5 has implemented Custom Vibrations mode. So in some cases variable vibration is acceptable. The only thing is unknown what library deals with that (pretty sure not CoreTelephony) and if it is open for developers. So keep on searching.
The above answers are good and you can do it in a simple way also.
You can use the recursive method calls.
func vibrateTheDeviceContinuously() throws {
// Added concurrent queue for next & Vibrate device
DispatchQueue.global(qos: .utility).async {
//Vibrate the device
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
self.incrementalCount += 1
usleep(800000) // if you don't want pause in between, remove this line.
do {
if let isKeepBuzzing = self.iShouldKeepBuzzing , isKeepBuzzing == true {
try self.vibrateTheDeviceContinuously()
}
else {
return
}
} catch {
//Exception handle
print("exception")
}
}
}
To stop the device vibration use the following line.
self.iShouldKeepBuzzing = false
ios swift