I am using https://github.com/react-native-webrtc/react-native-callkeep to implement a VOIP app in React Native.
It has a built in method for checking if a Call is going by passing the uuid to method isCallActive, it didn't work correctly for me so i have implement my own native module to check using the same code with promise:
RCT_EXPORT_METHOD(isCallActive:
(NSString *)uuidString
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
CXCallObserver *callObserver = [[CXCallObserver alloc] init];
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
BOOL _mybool = false;
for(CXCall *call in callObserver.calls){
NSLog(#"[RNCallKeep] isCallActive %# %d ?", call.UUID, [call.UUID isEqual:uuid]);
if([call.UUID isEqual:[[NSUUID alloc] initWithUUIDString:uuidString]] && !call.hasConnected){
_mybool = true;
resolve(#"true");
}
}
if(!_mybool){
reject(false, false, false);
}
}
The code is working and I am able to access it like this:
NativeModules?.CallManager?.isCallActive(uuid)
.then((value) => {
console.log('isCallActive then', value);
})
.catch((error) => {
console.log('isCallActive catch', error);
});
Now the problem here is that, if I receive a call, it displays CallKit UI then in my console i see it resolves the promise quickly even though i haven't accepted call yet and its ringing in the phone. I am unable to understand why is this happening.
EDIT:
How does hasConnected work? Even though i have accepted the call it always returns false? If I wait for my app to start completely behind callKit UI and then accept the call, it gives me true for hasConnected and I am assuming because the Twilio audio session is started at that point. But if i accept call early, i don't get true for hasConnected because audio session was not started. Is my understanding correct?
Related
how to detect voice messages on whatsapp with whatsapp-web.js
I tried this but it seems like it doesn't work
client.on('voice', async (msg) => {}
I'm working on a project that saves the various types of files/content sent to my phone which is connected to whatsapp-web.js library.
I suggest you test the solution with some logs on the incoming message type.
You could solve this problem directly from the message reply (msg):
client.on('message', async msg => {
if(msg.type == 'ptt'){
// is a voice message
}
});
also with the mimetype of downloadMedia():
client.on('message', async msg => {
if(msg.hasMedia) {
const media = await msg.downloadMedia();
var mmtype = media.mimetype;
if(media.mimetype.contains('audio/ogg')){
// is a voice message
// don't know if .contains() is the solution try other comparators
}
}
});
I am have Xamarin Forms cross platform application for iOS, Android and UWP. I use the Xam.Plugin.Geolocator to get the location from each of the devices. My challenge with iOS is on the first launch of the app on a device. My code runs through and detects that IsGeolocationEnabled for the Plugin.Geolocator.Abstractions.IGeolocator object is false before the use is ever presented with the option to allow the application to use the device's location. This causes my app to inform the user that Location Services are not enabled for the application.
Basically I am hitting the line of code below before the use is ever asked about location services:
if (!App.gobj_RealGeoCoordinator.IsGeolocationEnabled)
ls_ErrorMessage = resourcestrings.GetValue("NoLocationServicesMessage");
On the other platforms, UWP at least, it seems that the app is paused while waiting for the user to respond to the request to use location services. Android just seems to automatically allow access to location if an app uses it.
Any idea how I can have the iOS detect if the request to use location services has been answered or not on the first run? Any suggestions would be greatly appreciated.
UPDATE(1):
I have all the correct items in my info.plist as seen below. I do eventually get the request to use the location just after my app has already checked IsGeolocationEnabled and decided the user has not enabled location services for the app.
UPDATE (2):
So I made a little progress using the following code.
try
{
while (!App.gobj_RealGeoCoordinator.IsGeolocationEnabled)
{
await Task.Delay(1000);
}
ViewModelObjects.AppSettings.CanAccessLocation = App.gobj_RealGeoCoordinator.IsGeolocationEnabled;
}
catch (Exception ex)
{
XXXXXXX
}
The challenge is that the plugin appears to provide me no way of knowing in the user has not responded to the location services dialog (i.e. IsGeolocationEnabled == false) versus the user said no to the location services dialog (also IsGeolocationEnabled == false). Any suggestions?
The way this type of permission request occurs on iOS is through an asynchronous dialog prompt, which is only shown if needed (and not until it is needed). Basically, you need to set up a callback from the CLLocation API. I have a helper class that I use for this purpose, which makes it even easier. Just call GetCurrentDeviceLocation() and pass it a callback function. The callback will only be invoked once the user has granted permission to the app, or if they previously granted permission:
public class GeoLocationService
{
readonly CLLocationManager _locationManager;
WeakReference<Action<Position>> _callback;
public GeoLocationService()
{
_locationManager = new CLLocationManager ();
_locationManager.AuthorizationChanged += AuthorizationChanged;
}
void AuthorizationChanged (object sender, CLAuthorizationChangedEventArgs e)
{
Action<Position> callback;
if (_callback == null || !_callback.TryGetTarget (out callback)) {
return;
}
if (IsAuthorized(e.Status)) {
var loc = _locationManager.Location;
var pos = new Position(loc.Coordinate.Latitude, loc.Coordinate.Longitude);
callback (pos);
}
}
static bool IsAuthorized(CLAuthorizationStatus status)
{
return
status == CLAuthorizationStatus.Authorized
|| status == CLAuthorizationStatus.AuthorizedAlways
|| status == CLAuthorizationStatus.AuthorizedWhenInUse;
}
public void GetCurrentDeviceLocation (Action<Position> callback)
{
_callback = new WeakReference<Action<Position>> (callback);
if (UIDevice.CurrentDevice.CheckSystemVersion (8, 0)) {
if (_locationManager.Location == null) {
_locationManager.RequestWhenInUseAuthorization ();
return;
}
}
AuthorizationChanged (null, new CLAuthorizationChangedEventArgs (CLAuthorizationStatus.Authorized));
}
}
I'm building an iOS Cordova plugin, in this plugin i have a variable that is always changing values.
When i call this plugin method that returns this variable from js, I want it to stay active and always get the new changed value from Objective-c.
This is my code:
/**
*This method will return the Volume of the user's speech ( It can be used as a UI feedback)
*/
- (void) getRecognitionVolume:(CDVInvokedUrlCommand*)command
{
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[NSString stringWithFormat:#"%1.6f", volumeLevel]];
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
js code:
getVolumeBtn.addEventListener('click', function() {
cordova.exec(
function successCallback(data) {
volumeDiv.innerHTML ="Volume: "+ data;
},
function errorCallback(err) {
alert('Error');
},
'VoiceControl',
'getRecognitionVolume',
[]
);
});
So to conclude I want the volumeDiv to always hold the new volume value.
Any help would be appreciated.
The problem was that I didn't fully understand the way setKeepCallback works:
I thought if set to true it will automatically keep the method running and sending the new value whenever the variable changes.
The way I have to use it is whenever the value changes I must send the result again.
Here's where I pass the result now:
(this is the handler that's always called when the volume changes in my code)
- (void) pollVolume
{
[self getRecognitionVolume:volumeCommand];
}
I'm writing a native iOS client to interact with a Node.js server using the Socket.IO-objc wrapper. I'm currently able to send individual events to the server just fine. However, the server is using Socket.IO-stream to handle upload file streams, and I need to make my iOS code interact with it somehow. My socket.io wrapper doesn't seem to have any relevant API for accomplishing this. The receiving server.js code is like:
io.sockets.on('connection', function (socket) {
console.log("connection occurring");
ss(socket).on('uploadFile', function (stream, data, callback) {
//...I can't get a 'stream' to this event for it to work with
});
socket.on('passwordLogin', function (data, callback) {
//...I can cause these kind of events just fine
});
//...other event declarations
}
I typically call events in this manner, and it works fine for 'socket.on' declared events:
[_socket sendEvent:#"passwordLogin"
withData:#{...login info...}
andAcknowledge:aCallbackBlock];
I figure I need to expand Socket.IO-objc, unless there is something I'm missing, with something like:
//TODO: Attempt to expand SocketIO to include streams.
- (void) sendEvent:(NSString *)eventName withStream:(NSStream*) stream withData:(id) data andAcknowledge:(SocketIOCallback) function
{
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:eventName forKey:#"name"];
// do not require arguments
if (data != nil) {
[dict setObject:[NSArray arrayWithObject:data] forKey:#"args"];
}
SocketIOPacket *packet = [[SocketIOPacket alloc] initWithType:#"event"];
packet.data = [SocketIOJSONSerialization JSONStringFromObject:dict error:nil];
packet.pId = [self addAcknowledge:function];
//Is this even a valid approach?
packet.stream = stream;//<--NEW BIT
if (function) {
packet.ack = #"data";
}
[self send:packet];
}
I'm pretty lost as to what exactly to do, having never really worked with server communications before. I've followed Apple documentation about the built-in NSStream and CFStream objects, but it doesn't seem to get me any closer to interacting with the server's event. Any help is appreciated, as I'm a bit at my wit's end.
I am trying to setup push notifications using Urban Airship and I believe I'm having a problem with the provisioning profiles or ssl certificates. The app is not prompting the user for Push Notifications permissions but I am not getting a 'no valid "aps-environment" entitlement string found for application' message, and I can see the aps-environment entitlement in the .mobileprovision.
I can't find any documentation on +[UAirship executeUnsafeTakeOff:] so I am wondering if anyone knows what that could mean?
Also, the device token is being returned as nil, as logged by Urban Airship:
[D] -[UAPush updateRegistrationForcefully:] [Line 544] Device token is
nil. Registration will be attempted at a later time
There is no Urban Airship code running prior to the [UAirship takeOff:config] call, and the app does not crash as a result of the error.
Without knowing much about Urban Airship I can offer a guess.
takeOff ensures that the actual implementation provided in executeUnsafeTakeoff only happens once. It makes sure that the current thread is the main thread and then ensures that it only happens once.
Therefore getting an executeUnsafeTakeoff error really only tells you that something went wrong, such as if the configfailed tovalidate` (see below).
You need to make sure that the app can receive push notifications, as you mentioned.
Here is takeOff:
+ (void)takeOff {
[UAirship takeOff:[UAConfig defaultConfig]];
}
+ (void)takeOff:(UAConfig *)config {
// takeOff needs to be run on the main thread
if (![[NSThread currentThread] isMainThread]) {
NSException *mainThreadException = [NSException exceptionWithName:UAirshipTakeOffBackgroundThreadException
reason:#"UAirship takeOff must be called on the main thread."
userInfo:nil];
[mainThreadException raise];
}
dispatch_once(&takeOffPred_, ^{
[UAirship executeUnsafeTakeOff:config];
});
}
Here is executeUnsafeTakeoff:
/*
* This is an unsafe version of takeOff - use takeOff: instead for dispatch_once
*/
+ (void)executeUnsafeTakeOff:(UAConfig *)config {
// Airships only take off once!
if (_sharedAirship) {
return;
}
[UAirship setLogLevel:config.logLevel];
_sharedAirship = [[UAirship alloc] init];
_sharedAirship.config = config;
// Ensure that app credentials have been passed in
if (![config validate]) {
UA_LERR(#"The AirshipConfig.plist file is missing and no application credentials were specified at runtime.");
// Bail now. Don't continue the takeOff sequence.
return;
}
UA_LINFO(#"App Key: %#", _sharedAirship.config.appKey);
UA_LINFO(#"App Secret: %#", _sharedAirship.config.appSecret);
UA_LINFO(#"Server: %#", _sharedAirship.config.deviceAPIURL);
if (config.automaticSetupEnabled) {
_sharedAirship.appDelegate = [[UAAppDelegateProxy alloc ]init];
//swap pointers with the initial app delegate
#synchronized ([UIApplication sharedApplication]) {
_sharedAirship.appDelegate.originalAppDelegate = [UIApplication sharedApplication].delegate;
_sharedAirship.appDelegate.airshipAppDelegate = [[UAAppDelegate alloc] init];
[UIApplication sharedApplication].delegate = _sharedAirship.appDelegate;
}
}
// Build a custom user agent with the app key and name
[_sharedAirship configureUserAgent];
// Set up analytics
_sharedAirship.analytics = [[UAAnalytics alloc] initWithConfig:_sharedAirship.config];
[_sharedAirship.analytics delayNextSend:UAAnalyticsFirstBatchUploadInterval];
/*
* Handle Debug Options
*/
//For testing, set this value in AirshipConfig to clear out
//the keychain credentials, as they will otherwise be persisted
//even when the application is uninstalled.
if (config.clearKeychain) {
UA_LDEBUG(#"Deleting the keychain credentials");
[UAKeychainUtils deleteKeychainValue:_sharedAirship.config.appKey];
UA_LDEBUG(#"Deleting the UA device ID");
[UAKeychainUtils deleteKeychainValue:kUAKeychainDeviceIDKey];
}
if (!config.inProduction) {
[_sharedAirship validate];
}
if (config.cacheDiskSizeInMB > 0) {
UA_LINFO("Registering UAURLProtocol");
[NSURLProtocol registerClass:[UAURLProtocol class]];
}
// The singleton is now ready for use!
_sharedAirship.ready = true;
//create/setup user (begin listening for device token changes)
[[UAUser defaultUser] initializeUser];
}