I am using the the iOS client quick start project hosted on https://github.com/twilio/voice-callkit-quickstart-objc
on server I am using python (as recommended on github project)
When I clicked on "Place outgoing call" it worked fine and I got "Welcome to Twilio" voice. Great!
Then I changed the code a bit and tried to make an outgoing call to specific number. Here's the modified code
Button click event
- (IBAction)placeCall:(id)sender {
NSUUID *uuid = [NSUUID UUID];
NSString *handle = #"Real Number";
[self performStartCallActionWithUUID:uuid handle:handle];
}
Here's the CallKit handle
- (void)performStartCallActionWithUUID:(NSUUID *)uuid handle:(NSString *)handle {
if (uuid == nil || handle == nil) {
return;
}
CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
CXStartCallAction *startCallAction = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:callHandle];
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:startCallAction];
[self.callKitCallController requestTransaction:transaction completion:^(NSError *error) {
if (error) {
NSLog(#"StartCallAction transaction request failed: %#", [error localizedDescription]);
} else {
NSLog(#"StartCallAction transaction request successful");
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
callUpdate.remoteHandle = callHandle;
callUpdate.supportsDTMF = YES;
callUpdate.supportsHolding = NO;
callUpdate.supportsGrouping = NO;
callUpdate.supportsUngrouping = NO;
callUpdate.hasVideo = NO;
[self.callKitProvider reportCallWithUUID:uuid updated:callUpdate];
}
}];
}
And the number to call
- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action {
NSLog(#"provider:performStartCallAction:");
[[VoiceClient sharedInstance] configureAudioSession];
NSDictionary *toParam = #{#"To": #"+14805058877"};
//THIS IS WHERE WE NEED TO INSERT CALLING NUMBER
self.outgoingCall = [[VoiceClient sharedInstance] call:[self fetchAccessToken]
params:toParam
delegate:self];
if (!self.outgoingCall) {
[action fail];
} else {
self.outgoingCall.uuid = action.callUUID;
[self toggleUIState:NO];
[self startSpin];
[action fulfillWithDateStarted:[NSDate date]];
}
}
No matter what I enter in the parameter value I always get "Welcome to Twilio" msg. I need to know if I need change anything on the Python server or in the iOS client code. Please help!
Twilio developer evangelist here.
Have you set your TwiML application up correctly? The Voice Request URL should be pointing to your python server. I only ask as the message from the Python server, which comes from this line in the code, should be "Congratulations! You have made your first oubound call! Good bye." and you said it was "Welcome to Twilio"
Once you are definitely set up pointing at your Python app, once you have made your first outbound call you will get that message. Now you need to update your Python app as well as the iOS app.
You're sending a parameter To with the number you're trying to call. You need to change the Python so that it reads that number and outputs the TwiML that will dial that number.
That should look a bit like this:
#app.route('/outgoing', methods=['GET', 'POST'])
def outgoing():
resp = twilio.twiml.Response()
resp.dial(request.form['To'])
return str(resp)
Let me know if that helps at all.
Related
How to disconnect call in CallKit, when a call is in the Ringing state? I am using below code for disconnect callKit Call.
This code is working when I Disconnect call after Accept. but when a call is in the Ringing state then this code is not working. call keep Ringing.
I have checked that UUID is not nil .
Please let me know what is the proper way to do this
#property (nonatomic, strong) CXCallController *callKitCallController;
- (void)performEndCallActionWithUUID:(NSUUID *)uuid {
if (uuid == nil) {
return;
}
CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:uuid];
CXTransaction *transaction = [[CXTransaction alloc] initWithAction:endCallAction];
[self.callKitCallController requestTransaction:transaction completion:^(NSError *error) {
if (error) {
NSLog(#"EndCallAction transaction request failed: %#", [error localizedDescription]);
}
else {
NSLog(#"EndCallAction transaction request successful");
}
}];
}
I have tried this link also ->. Callkit Call End
i am getting this error..please check screenshot
Add this method in your class,
+ (CXTransaction *)transactionWithActions:(NSArray <CXAction *> *)actions {
CXTransaction *transcation = [[CXTransaction alloc] init];
for (CXAction *action in actions) {
[transcation addAction:action];
}
return transcation;
}
I've done as the guide says
This is the message manager
[GNSMessageManager setDebugLoggingEnabled:YES];
messageManager = [[GNSMessageManager alloc] initWithAPIKey:API_KEY paramsBlock:^(GNSMessageManagerParams *params) {
params.bluetoothPowerErrorHandler = ^(BOOL hasError) {
// Update the UI for Bluetooth power
};
params.bluetoothPermissionErrorHandler = ^(BOOL hasError) {
// Update the UI for Bluetooth permission
};
}];
These are my methods to publish and subscribe with the Nearby API.
- (IBAction)onPublish:(id)sender {
NSLog(#"publish");
NSString* str = #"hello world";
NSData* data = [str dataUsingEncoding:NSUTF8StringEncoding];
GNSMessage* message = [GNSMessage messageWithContent:data];
id<GNSPublication> publication = [messageManager publicationWithMessage:message paramsBlock:^(GNSPublicationParams *publicationParams) {
publicationParams.strategy = [GNSStrategy strategyWithParamsBlock:^(GNSStrategyParams * strategyParams) {
strategyParams.allowInBackground = YES;
strategyParams.discoveryMediums = kGNSDiscoveryMediumsBLE;
strategyParams.discoveryMode = kGNSDiscoveryModeDefault;
}];;
}];
}
- (IBAction)onSubscribe:(id)sender {
NSLog(#"subscribe");
id<GNSSubscription> subscription = [messageManager subscriptionWithMessageFoundHandler:^(GNSMessage *msg) {
// Add the name to a list for display
NSLog(#"message found %#", [msg description]);
} messageLostHandler:^(GNSMessage *msg) {
// Add the name to a list for display
NSLog(#"message lost %#", [msg description]);
} paramsBlock:^(GNSSubscriptionParams *subscriptionParams) {
subscriptionParams.strategy = [GNSStrategy strategyWithParamsBlock:^(GNSStrategyParams * strategyParams) {
strategyParams.allowInBackground = YES;
strategyParams.discoveryMediums = kGNSDiscoveryMediumsBLE;
strategyParams.discoveryMode = kGNSDiscoveryModeDefault;
}];;
}];
}
Both Bletooth central and peripheral background capabilities are enabled, and the permission string for the peripheral is set.
Finally I subscribe on an iOS device and publish from another one but nothing happens.
Be sure to retain the publication and subscription objects. They stop publishing/subscribing when they're deallocated. The usual way is to store them as properties/ivars in one of your classes.
The developer docs are misleading on this point, and I apologize. We'll improve the docs in the next release.
I am working on a VOIP application (let's call it SampleApp) using CallKit and am struggling with an issue.
I can answer an inbound SampleApp call in a way that appears to work correctly using CallKit. However, when I reject a call, the call is instantiated anyway, which is incorrect behaviour.
To the extend of my understanding I need to get a boolean somehow depending on whether the call was accepted and rejected and then use this to decide whether the phone controller answers the incoming call.
PhoneViewController.m
- (void) presentIncomingCallAlertForCall:(SampleAppClientCall*)call
{
callIdentifier = [[NSUUID alloc] init];
// Use CallKit if iPhone
if(UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad)
{
[_pDelegate reportIncomingCall:callIdentifier
handle:call.remoteAddress
hasVideo:hasVideo
completionHandler: nil];
[self answerIncomingCall];
}
// Use Notification mechanism if iPad
else
{
// etc.
}
}
ProviderDelegate.m
-(void) reportIncomingCall: (NSUUID *) uuid
handle: (NSString *) handle
hasVideo: (BOOL) hasVideo
completionHandler:
(nullable void (^) (NSArray * _Nullable results, NSError * _Nonnull error))completionHandler
{
CXCallUpdate *update = [[CXCallUpdate alloc] init];
update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber
value:handle];
update.hasVideo = hasVideo;
[provider reportNewIncomingCallWithUUID:uuid
update:update
completion: ^(NSError *error)
{
// not sure what to put here?
}];
}
- (void) provider: (CXProvider *)provider
performAnswerCallAction : (CXAnswerCallAction *)action
{
NSDate *now = [[NSDate alloc] init];
[action fulfillWithDateConnected: now];
}
This is my first question, I've read the Stack Overflow guidelines but please tell me if I did anything wrong.
However, when I reject a call, the call is instantiated anyway, which
is incorrect behaviour.
It's unclear what you are trying to do in your code here. You always call [self answerIncomingCall]; (which we do not see the code to, but which I assume reports to your app logic and to your backend that the user has answered the call) right after [_pDelegate reportIncomingCall:...], so it seems like you are assuming that the user has decided to answer it, even before the user has done anything.
Rather, you should be telling your app logic and your backend that the user has answered the call, only in your -provider:performAnswerCallAction: method, and in your -provider:performEndCallAction:, tell your app logic and your backend that the user has declined the call (if it was a ringing incoming call).
Here is the solution based on the advice of #user102008 that resulted in what I wanted to achieve.
I moved the responsibility for answering the incoming call to the provider delegate instead of the phone view controller, which invokes the answer call or end call method depending on whether a call is accepted or rejected respectively in its provider methods.
PhoneViewController.m
- (void) presentIncomingCallAlertForCall:(SampleAppClientCall*)call {
callIdentifier = [[NSUUID alloc] init];
// Use CallKit if iPhone
if(UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
[_pDelegate reportIncomingCall:callIdentifier
handle:call.remoteAddress
hasVideo:hasVideo
completionHandler: nil];
}
// Use Notification mechanism if iPad
else {
...
}
}
ProviderDelegate.h
-(void) reportIncomingCall: (NSUUID *) uuid
handle: (NSString *) handle
hasVideo: (BOOL) hasVideo
completionHandler: (nullable void (^) (NSArray * _Nullable results, NSError * _Nonnull error))
completionHandler {
CXCallUpdate *update = [[CXCallUpdate alloc] init];
update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value:handle];
update.hasVideo = hasVideo;
[provider reportNewIncomingCallWithUUID:uuid
update:update
completion: ^(NSError *error) {
if (error) {
[self reportCallEnded:uuid reason:(NSInteger *)CXCallEndedReasonFailed];
}
}];
}
- (void) provider: (CXProvider *)provider performAnswerCallAction: (CXAnswerCallAction *)action {
[_phoneVC answerIncomingCall];
NSDate *now = [[NSDate alloc] init];
[action fulfillWithDateConnected: now];
}
- (void) provider: (CXProvider *)provider performEndCallAction: (CXEndCallAction *)action {
[_phoneVC rejectIncomingCall];
NSDate *now = [[NSDate alloc] init];
[action fulfillWithDateEnded: now];
}
I'm developing an iOS application with Simple Notification Service (SNS) from Amazon Web Services. At this point the app registers the device to a Topic and can receive push notifications, which are published to the Topic. It is possible to subscribe a device to many Topics.
Now I'm trying to unsubscribe a device from a specific Topic, but the SNSUnsubscribeRequest needs a SubscriptionARN. I've tried to use the EndpointARN from the device, but it seems I've to use an extra SubscriptionARN for the combination of EndpointARN and TopicARN. How do I get this ARN?
In this post: How do you get the arn of a subscription? they ask for the whole list of subscribers and compare each EndpointARN with the EndpointARN of the device. This cant be the right way i think.
Subscribe to Topic
// Check if endpoint exist
if (endpointARN == nil) {
dispatch_async(dispatch_get_main_queue(), ^{
[[self universalAlertsWithTitle:#"endpointARN not found!" andMessage:#"Please create an endpoint for this device before subscribe to topic"] show];
});
return NO;
}
// Create topic if not exist
NSString *topicARN = [self findTopicARNFor:topic];
if (!topicARN) {
[self createTopic:topic];
topicARN = [self findTopicARNFor:topic];
}
// Subscribe to topic if exist
if (topicARN) {
SNSSubscribeRequest *subscribeRequest = [[SNSSubscribeRequest alloc] initWithTopicArn:topicARN andProtocol:#"application" andEndpoint:endpointARN];
SNSSubscribeResponse *subscribeResponse = [snsClient subscribe:subscribeRequest];
if (subscribeResponse.error != nil) {
NSLog(#"Error: %#", subscribeResponse.error);
dispatch_async(dispatch_get_main_queue(), ^{
[[self universalAlertsWithTitle:#"Subscription Error" andMessage:subscribeResponse.error.userInfo.description] show];
});
return NO;
}
}
return YES;
The method findTopicARNForTopic already iterates over the list of Topics and compare the suffix with the topic name. I really don't know if this is the best practice.
Unsubscribe from Topic
NSString *topicARN = [self findTopicARNFor:topic];
if (topicARN) {
SNSUnsubscribeRequest *unsubscribeRequest = [[SNSUnsubscribeRequest alloc] initWithSubscriptionArn:topicARN];
SNSUnsubscribeResponse *unsubscribeResponse = [snsClient unsubscribe:unsubscribeRequest];
if (unsubscribeResponse.error) {
NSLog(#"Error: %#", unsubscribeResponse.error);
}
}
For now I ask for the whole subscriber list and compare the EndpointARN with the EndpointARN of the Device. With the following method i get the subscription arn:
- (NSString *)findSubscriptionARNForTopicARN:(NSString *)topicARN
{
// Iterate over each subscription arn list for a topic arn
NSString *nextToken = nil;
do {
SNSListSubscriptionsByTopicRequest *listSubscriptionRequest = [[SNSListSubscriptionsByTopicRequest alloc] initWithTopicArn:topicARN];
SNSListSubscriptionsByTopicResponse *response = [snsClient listSubscriptionsByTopic:listSubscriptionRequest];
if (response.error) {
NSLog(#"Error: %#", response.error);
return nil;
}
// Compare endpoint arn of subscription arn with endpoint arn of this device
for (SNSSubscription *subscription in response.subscriptions) {
if ([subscription.endpoint isEqualToString:endpointARN]) {
return subscription.subscriptionArn;
}
}
nextToken = response.nextToken;
} while (nextToken != nil);
return nil;
}
and with this method i remove the device from a topic:
- (void)unsubscribeDeviceFromTopic:(NSString *)topic
{
NSString *subscriptionARN = [self findSubscriptionARNForTopic:topic];
if (subscriptionARN) {
SNSUnsubscribeRequest *unsubscribeRequest = [[SNSUnsubscribeRequest alloc] initWithSubscriptionArn:subscriptionARN];
SNSUnsubscribeResponse *unsubscribeResponse = [snsClient unsubscribe:unsubscribeRequest];
if (unsubscribeResponse.error) {
NSLog(#"Error: %#", unsubscribeResponse.error);
}
}
}
You could also store the SubscriptionArn in the SubscribeResponse and use this value in the UnSubscribeRequest.
Alright, this involves a lot of network coding from this part of a multiplayer tutorial.
Basically, I'm trying to implement a multiplayer game using GameKit as per the tutorial linked above. I put in all of the necessary network coding and more or less understand it, however I've hit a snag somewhere along the line of method calls. Basically, the setup that I have is that one device acts as the host and the rest act as the clients. I have two separate UIViewcontrollers for the host and clients respectively where the connection is established.
Now the thing is, the connection gets established, but it's only the host that recognizes the connection, not the client. The problem is here:
- (void)sendPacketToAllClients:(Packet *)packet
{
[_players enumerateKeysAndObjectsUsingBlock:^(id key, Player *obj, BOOL *stop)
{
obj.receivedResponse = [_session.peerID isEqualToString:obj.peerID];
}];
GKSendDataMode dataMode = GKSendDataReliable;
NSData *data = [packet data];
NSError *error;
if (![_session sendDataToAllPeers:data withDataMode:dataMode error:&error])
{
NSLog(#"Error sending data to clients: %#", error);
}
}
This is implemented in GameMultiplayer, where the actual game will be implemented. What this method is supposed to be doing is sending data packets to each of the clients saying that the host received the connection request and is able to connect with them. After [_session sendDataToAllPeers:data withDataMode:dataMode error:&error] is called (the method in the if statement), this method is supposed to be triggered:
- (void)receiveData:(NSData *)data fromPeer:(NSString *)peerID inSession:(GKSession *)session context:(void *)context
{
#ifdef DEBUG
NSLog(#"Game: receive data from peer: %#, data: %#, length: %d", peerID, data, [data length]);
#endif
Packet *packet = [Packet packetWithData:data];
if (packet == nil)
{
NSLog(#"Invalid packet: %#", data);
return;
}
Player *player = [self playerWithPeerID:peerID];
if (player != nil)
{
player.receivedResponse = YES; // this is the new bit
}
if (self.isServer)
[self serverReceivedPacket:packet fromPlayer:player];
else
[self clientReceivedPacket:packet];
}
This method is in the next part of the tutorial I linked above (which is here) and is supposed to receive the packets that the host sends to all clients and implement the next methods in this networking chain. However, the method never gets called. No debug breakpoints are triggered and I get nothing in the console.
I understand if I need to provide more source material, but there is a lot of network coding already implemented, so I want to keep it down to what people need to see. Also, [_session setDataReceiveHandler:self withContext:nil] and _session.delegate = self are written in another method that is called in GameMultiplayer, so that's not the problem. Does anyone know what I need to fix?
EDIT: As requested, here's where GKSession is initialized:
#property (nonatomic, strong, readonly) GKSession *session; //This is done in the header file
#synthesize session = _session; //This is done in the main file
- (void)startAcceptingConnectionsForSessionID:(NSString *)sessionID
{
if (_serverState == ServerStateIdle)
{
_serverState = ServerStateAcceptingConnections;
_connectedClients = [NSMutableArray arrayWithCapacity:self.maxClients];
_session = [[GKSession alloc] initWithSessionID:sessionID displayName:nil sessionMode:GKSessionModeServer];
_session.delegate = self;
_session.available = YES;
}
}
The session is initialized in MatchmakingServer, which is used in the host view controller. The session is then passed on to the main view controller of the app, which then initializes GameMultiplayer and sends the GKSession to it. Here's where the host view controller sends it to the main view controller:
- (IBAction)startAction:(id)sender
{
if (_matchmakingServer != nil && [_matchmakingServer connectedClientCount] > 0)
{
NSString *name = [self.nameTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([name length] == 0)
name = _matchmakingServer.session.displayName;
[_matchmakingServer stopAcceptingConnections];
[self.delegate hostViewController:self startGameWithSession:_matchmakingServer.session playerName:name clients:_matchmakingServer.connectedClients];
}
}
and then the main view controller handles that method call here:
- (void)hostViewController:(MatchmakerHost *)controller startGameWithSession:(GKSession *)session playerName:(NSString *)name clients:(NSArray *)clients
{
[self dismissViewControllerAnimated:NO completion:^
{
[self startGameWithBlock:^(GameMultiplayer *aGame)
{
[aGame startServerGameWithSession:session playerName:name clients:clients];
}];
}];
}
and finally, this is where that method call is implemented in GameMultiplayer:
- (void)startServerGameWithSession:(GKSession *)session playerName:(NSString *)name clients:(NSArray *)clients
{
_clients = clients;
const char* className = class_getName([[_clients objectAtIndex:0] class]);
NSLog(#"yourObject is a: %s", className);
self.isServer = YES;
_session = session;
_session.available = NO;
_session.delegate = self;
[_session setDataReceiveHandler:self withContext:nil];
_state = GameStateWaitingForSignIn;
[self.delegate gameWaitingForClientsReady:self];
// Create the Player object for the server.
Player *player = [[Player alloc] init];
player.name = name;
player.peerID = _session.peerID;
player.position = PlayerPositionBottom;
[_players setObject:player forKey:player.peerID];
// Add a Player object for each client.
int index = 0;
for (NSString *peerID in clients)
{
Player *player = [[Player alloc] init];
player.peerID = peerID;
[_players setObject:player forKey:player.peerID];
if (index == 0)
player.position = ([clients count] == 1) ? PlayerPositionTop : PlayerPositionLeft;
else if (index == 1)
player.position = PlayerPositionTop;
else
player.position = PlayerPositionRight;
index++;
}
NSLog(#"Players:");
Packet *packet = [Packet packetWithType:PacketTypeSignInRequest];
[self sendPacketToAllClients:packet];
// for (int i = 0; i < [_players count]; i++) {
// NSLog([NSString stringWithFormat:#"%#", [clients objectAtIndex:i]]);
// }
}
I think you are calling send to fast. When server realize about connection it will send confirmation to client to really establish connection - so client knows about it succeed.
If you are sending packets before that happens - it will be lost.
Just do this:
[self performSelector:#selector(sendPacketToAllClients) withObject:nil afterDelay:1.0];
instead of:
[self sendPacketToAllClients];
I had the same problem that connection is established in different moment with small delay on client. The best is to send first packet from client that he is ready to receive packets from server - and than proceed normally from there.
Also try debugging:
- (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state
On both devices (server and client).
I have also had my troubles with GKSession. I was interested to learn (on this site) today that GKSession is being deprecated in favor of using the Multipeer Connectivity Framework. With luck, Wenderlich et al. will do a tutorial using the new technology. :)
The system has some similarities to GKSession, so is not too hard to wrap your head around.
Apple's doc link.