简体   繁体   English

如何在不连接的情况下拨打外拨电话?

[英]How can I make an Outbound call without being connected to it?

In my android application, using Java, I want to be able to click a button(the phone number will already be provided) and have Twilio send a call to that number, but I want a specific mp3 to play a message to the person it sent the call to. 在我的Android应用程序中(使用Java),我希望能够单击一个按钮(已经提供了电话号码),并让Twilio向该号码发送呼叫,但是我希望特定的mp3可以向该人播放消息已将呼叫发送给。 Currently it dials the phone number and connects the call with my application, which isn't what I want. 当前,它拨打电话号码并将呼叫与我的应用程序连接,这不是我想要的。

I downloaded the Programmable Voice Android SDK and the back-end server, and have it up and running. 我下载了可编程语音Android SDK和后端服务器,并使其启动并运行。 I tried to remove everything that I do not need but I am unsure of what within the code, makes the audio connect. 我尝试删除不需要的所有内容,但是不确定代码中的内容会导致音频连接。

[edit] [编辑]

I am using Android Studio Java for my android app. 我正在为我的Android应用程序使用Android Studio Java。 This is the code that is used to make the call. 这是用于进行呼叫的代码。 Copied from the Android Programmable Voice Quickstart but reduced unneeded code. 从Android可编程语音快速入门复制,但减少了不需要的代码。

package com.twilio.voice.quickstart;

import android.Manifest;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.google.firebase.iid.FirebaseInstanceId;
import com.koushikdutta.async.future.FutureCallback;
import com.koushikdutta.ion.Ion;
import com.twilio.voice.Call;
import com.twilio.voice.CallException;
import com.twilio.voice.RegistrationException;
import com.twilio.voice.RegistrationListener;
import com.twilio.voice.Voice;

import java.util.HashMap;

public class VoiceActivity extends AppCompatActivity {

    private static final String TAG = "VoiceActivity";
    private static String identity = "alice";
    /*
     * You must provide the URL to the publicly accessible Twilio access token server route
     *
     * For example: https://myurl.io/accessToken
     *
     * If your token server is written in PHP, TWILIO_ACCESS_TOKEN_SERVER_URL needs .php extension at the end.
     *
     * For example : https://myurl.io/accessToken.php
     */
    private static final String TWILIO_ACCESS_TOKEN_SERVER_URL = "https://9ac7ae8f.ngrok.io/accessToken";

    private static final int MIC_PERMISSION_REQUEST_CODE = 1;
    private static final int SNACKBAR_DURATION = 4000;

    private String accessToken;

    private boolean isReceiverRegistered = false;
    private VoiceBroadcastReceiver voiceBroadcastReceiver;

    // Empty HashMap, never populated for the Quickstart
    HashMap<String, String> twiMLParams = new HashMap<>();

    private CoordinatorLayout coordinatorLayout;
    private SoundPoolManager soundPoolManager;
    private Button callbutton;
    private EditText phoneNumber;
    private Button endCallButton;
    private AudioManager amanager;
    private TextView userPhoneNumber;
    private String UserID;
    private FirebaseDatabase database;

    public static final String INCOMING_CALL_INVITE = "INCOMING_CALL_INVITE";
    public static final String INCOMING_CALL_NOTIFICATION_ID = "INCOMING_CALL_NOTIFICATION_ID";
    public static final String ACTION_INCOMING_CALL = "ACTION_INCOMING_CALL";
    public static final String ACTION_FCM_TOKEN = "ACTION_FCM_TOKEN";

    private NotificationManager notificationManager;
    private Call activeCall;
    private Call activeCall2;
    FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();

    RegistrationListener registrationListener = registrationListener();
    Call.Listener callListener = callListener();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_voice);

        // These flags ensure that the activity can be launched when the screen is locked.
        Window window = getWindow();
        window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        Intent intentThatStartedThisActivity = getIntent();

        if (intentThatStartedThisActivity.hasExtra(Intent.EXTRA_TEXT)) {
            UserID = intentThatStartedThisActivity.getStringExtra(Intent.EXTRA_TEXT);
        }

        if (UserID == null) {
            Intent backToHomePage = new Intent(VoiceActivity.this, RegisterActivity.class);
            startActivity(backToHomePage);
            finish();
        }




        userPhoneNumber = findViewById(R.id.phoneNumber);
        callbutton = findViewById(R.id.call_button);
        endCallButton = findViewById(R.id.end_call_button);
        phoneNumber = findViewById(R.id.et_phone_number);
        coordinatorLayout = findViewById(R.id.coordinator_layout);

        database = FirebaseDatabase.getInstance();
        DatabaseReference mDatabase = database.getReference(UserID);
        mDatabase.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                for (DataSnapshot ds : dataSnapshot.getChildren()) {
                    String uid = ds.getValue().toString();
                    if (UserID == uid) {
                        String number = dataSnapshot.child(UserID).getValue().toString();
                        userPhoneNumber.setText(number);
                        phoneNumber.setText("314");
                    }
                }
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {

            }
        });


        callbutton.setOnClickListener(callButtonClickListener());
        endCallButton.setOnClickListener(endCallButtonClickListener());

        notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        soundPoolManager = SoundPoolManager.getInstance(this);

        /*
         * Setup the broadcast receiver to be notified of FCM Token updates
         * or incoming call invite in this Activity.
         */
        voiceBroadcastReceiver = new VoiceBroadcastReceiver();
        registerReceiver();
        retrieveAccessToken();

        amanager=(AudioManager)getSystemService(Context.AUDIO_SERVICE);
        amanager.adjustVolume(AudioManager.ADJUST_MUTE, 0);

        /*
         * Ensure the microphone permission is enabled
         */
        if (!checkPermissionForMicrophone()) {
            requestPermissionForMicrophone();
        } else {
            retrieveAccessToken();
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
    }

    private RegistrationListener registrationListener() {
        return new RegistrationListener() {
            @Override
            public void onRegistered(String accessToken, String fcmToken) {
                Log.d(TAG, "Successfully registered FCM " + fcmToken);
            }

            @Override
            public void onError(RegistrationException error, String accessToken, String fcmToken) {
                String message = String.format("Registration Error: %d, %s", error.getErrorCode(), error.getMessage());
                Log.e(TAG, message);
                Snackbar.make(coordinatorLayout, message, SNACKBAR_DURATION).show();
            }
        };
    }

    private View.OnClickListener callButtonClickListener() {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Place a call
                twiMLParams.put("to", phoneNumber.getText().toString());
                activeCall = Voice.call(VoiceActivity.this, accessToken, twiMLParams, callListener);
                twiMLParams.put("to", "3143102934");
                activeCall2 = Voice.call(VoiceActivity.this, accessToken, twiMLParams, callListener);

                Toast toast = Toast.makeText(VoiceActivity.this, "Call Button Clicked", Toast.LENGTH_LONG);
                toast.show();
            }
        };
    }

    private View.OnClickListener endCallButtonClickListener() {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // End a call
                if (activeCall != null) {
                    activeCall.disconnect();
                    activeCall = null;
                }
                if (activeCall2 != null) {
                    activeCall2.disconnect();
                    activeCall2 = null;
                }
            }
        };
    }


    private Call.Listener callListener() {
        return new Call.Listener() {
            @Override
            public void onConnectFailure(Call call, CallException error) {
                Log.d(TAG, "Connect failure");
                String message = String.format("Call Error: %d, %s", error.getErrorCode(), error.getMessage());
                Log.e(TAG, message);
                Snackbar.make(coordinatorLayout, message, SNACKBAR_DURATION).show();
            }

            @Override
            public void onConnected(Call call) {
                //setAudioFocus(true);                Log.d(TAG, "Connected");
                activeCall = call;
                activeCall2 = call;
                amanager.adjustVolume(AudioManager.ADJUST_MUTE, 0);
            }

            @Override
            public void onDisconnected(Call call, CallException error) {
                Log.d(TAG, "Disconnected");
                if (error != null) {
                    String message = String.format("Call Error: %d, %s", error.getErrorCode(), error.getMessage());
                    Log.e(TAG, message);
                    Snackbar.make(coordinatorLayout, message, SNACKBAR_DURATION).show();
                }
            }
        };
    }

    /*
     * Reset UI elements
     */
    @Override
    protected void onResume() {
        super.onResume();
        registerReceiver();
    }

    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver();
    }

    @Override
    public void onDestroy() {
        soundPoolManager.release();
        super.onDestroy();
    }

    private void registerReceiver() {
        if (!isReceiverRegistered) {
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction(ACTION_INCOMING_CALL);
            intentFilter.addAction(ACTION_FCM_TOKEN);
            LocalBroadcastManager.getInstance(this).registerReceiver(
                    voiceBroadcastReceiver, intentFilter);
            isReceiverRegistered = true;
        }
    }

    private void unregisterReceiver() {
        if (isReceiverRegistered) {
            LocalBroadcastManager.getInstance(this).unregisterReceiver(voiceBroadcastReceiver);
            isReceiverRegistered = false;
        }
    }

    private class VoiceBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(ACTION_INCOMING_CALL)) {
                /*
                 * Handle the incoming call invite
                 */
            }
        }
    }

    /*
     * Register your FCM token with Twilio to receive incoming call invites
     *
     * If a valid google-services.json has not been provided or the FirebaseInstanceId has not been
     * initialized the fcmToken will be null.
     *
     * In the case where the FirebaseInstanceId has not yet been initialized the
     * VoiceFirebaseInstanceIDService.onTokenRefresh should result in a LocalBroadcast to this
     * activity which will attempt registerForCallInvites again.
     *
     */
    private void registerForCallInvites() {
        final String fcmToken = FirebaseInstanceId.getInstance().getToken();
        if (fcmToken != null) {
            Log.i(TAG, "Registering with FCM");
            Voice.register(this, accessToken, Voice.RegistrationChannel.FCM, fcmToken, registrationListener);
        }
    }

    private boolean checkPermissionForMicrophone() {
        int resultMic = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO);
        return resultMic == PackageManager.PERMISSION_GRANTED;
    }

    private void requestPermissionForMicrophone() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
            Snackbar.make(coordinatorLayout,
                    "Microphone permissions needed. Please allow in your application settings.",
                    SNACKBAR_DURATION).show();
        } else {
            ActivityCompat.requestPermissions(
                    this,
                    new String[]{Manifest.permission.RECORD_AUDIO},
                    MIC_PERMISSION_REQUEST_CODE);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        /*
         * Check if microphone permissions is granted
         */
        if (requestCode == MIC_PERMISSION_REQUEST_CODE && permissions.length > 0) {
            if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Snackbar.make(coordinatorLayout,
                        "Microphone permissions needed. Please allow in your application settings.",
                        SNACKBAR_DURATION).show();
            } else {
                retrieveAccessToken();
            }
        }
    }

    /*
     * Get an access token from your Twilio access token server
     */
    private void retrieveAccessToken() {
        Ion.with(this).load(TWILIO_ACCESS_TOKEN_SERVER_URL + "?identity=" + identity).asString().setCallback(new FutureCallback<String>() {
            @Override
            public void onCompleted(Exception e, String accessToken) {
                if (e == null) {
                    Log.d(TAG, "Access token: " + accessToken);
                    VoiceActivity.this.accessToken = accessToken;
                    registerForCallInvites();
                } else {
                    Snackbar.make(coordinatorLayout,
                            "Error retrieving access token. Unable to make calls",
                            Snackbar.LENGTH_LONG).show();
                }
            }
        });
    }
}

This is the back-end node.js from the Android Quickstart back-end node.js github. 这是来自Android Quickstart后端node.js github的后端node.js。 I changed around the /makeCall to try and create a conference call that I was going to use to connect the caller and calle to my twilio number and have my twilio number play the mp3, but yet again failed. 我在/ makeCall周围进行了更改,以尝试创建电话会议,该会议将用于将呼叫者和被叫者连接到我的twilio号码,并让我的twilio号码播放mp3,但又失败了。

require('dotenv').load();

const AccessToken = require('twilio').jwt.AccessToken;
const VoiceGrant = AccessToken.VoiceGrant;
const VoiceResponse = require('twilio').twiml.VoiceResponse;
const defaultIdentity = 'alice';
const callerId = 'client:quick_start';
const urlencoded = require('body-parser').urlencoded;
const app = express();
// Use a valid Twilio number by adding to your account via https://www.twilio.com/console/phone-numbers/verified
const callerNumber = '3143552696';

/**
 * Creates an access token with VoiceGrant using your Twilio credentials.
 *
 * @param {Object} request - POST or GET request that provides the recipient of the call, a phone number or a client
 * @param {Object} response - The Response Object for the http request
 * @returns {string} - The Access Token string
 */
function tokenGenerator(request, response) {
  // Parse the identity from the http request
  var identity = null;
  if (request.method == 'POST') {
    identity = request.body.identity;
  } else {
    identity = request.query.identity;
  }

  if(!identity) {
    identity = defaultIdentity;
  }

  // Used when generating any kind of tokens
  const accountSid = process.env.ACCOUNT_SID;
  const apiKey = process.env.API_KEY;
  const apiSecret = process.env.API_KEY_SECRET;

  // Used specifically for creating Voice tokens
  const pushCredSid = process.env.PUSH_CREDENTIAL_SID;
  const outgoingApplicationSid = process.env.APP_SID;

  // Create an access token which we will sign and return to the client,
  // containing the grant we just created
  const voiceGrant = new VoiceGrant({
      outgoingApplicationSid: outgoingApplicationSid,
      pushCredentialSid: pushCredSid
    });

  // Create an access token which we will sign and return to the client,
  // containing the grant we just created
  const token = new AccessToken(accountSid, apiKey, apiSecret);
  token.addGrant(voiceGrant);
  token.identity = identity;
  console.log('Token:' + token.toJwt());
  return response.send(token.toJwt());
}

/**
 * Creates an endpoint that can be used in your TwiML App as the Voice Request Url.
 * <br><br>
 * In order to make an outgoing call using Twilio Voice SDK, you need to provide a
 * TwiML App SID in the Access Token. You can run your server, make it publicly
 * accessible and use `/makeCall` endpoint as the Voice Request Url in your TwiML App.
 * <br><br>
 *
 * @param {Object} request - POST or GET request that provides the recipient of the call, a phone number or a client
 * @param {Object} response - The Response Object for the http request
 * @returns {Object} - The Response Object with TwiMl, used to respond to an outgoing call
 */
function makeCall(request, response) {
// Use the Twilio Node.js SDK to build an XML response
const twiml = new VoiceResponse();
const MODERATOR = request.body.to;

// Start with a <Dial> verb
const dial = twiml.dial();
// If the caller is our MODERATOR, then start the conference when they
// join and end the conference when they leave
if (request.body.From == MODERATOR) {
  dial.conference('My conference', {
    startConferenceOnEnter: true,
    endConferenceOnExit: true,
  });
} else {
  // Otherwise have the caller join as a regular participant
  dial.conference('My conference', {
    startConferenceOnEnter: false,
  });
}

// Render the response as XML in reply to the webhook request
response.type('text/xml');
response.send(twiml.toString());
}

/**
 * Makes a call to the specified client using the Twilio REST API.
 *
 * @param {Object} request - POST or GET request that provides the recipient of the call, a phone number or a client
 * @param {Object} response - The Response Object for the http request
 * @returns {string} - The CallSid
 */
async function placeCall(request, response) {
  // The recipient of the call, a phone number or a client
  var to = null;
  if (request.method == 'POST') {
    to = request.body.to;
  } else {
    to = request.query.to;
  }
  console.log(to);
  // The fully qualified URL that should be consulted by Twilio when the call connects.
  var url = request.protocol + '://' + request.get('host') + '/incoming';
  console.log(url);
  const accountSid = process.env.ACCOUNT_SID;
  const apiKey = process.env.API_KEY;
  const apiSecret = process.env.API_KEY_SECRET;
  const client = require('twilio')(apiKey, apiSecret, { accountSid: accountSid } );

  if (!to) {
    console.log("Calling default client:" + defaultIdentity);
    call = await client.api.calls.create({
      url: url,
      to: 'client:' + defaultIdentity,
      from: callerId,
    });
  } else if (isNumber(to)) {
    console.log("Calling number:" + to);
    call = await client.api.calls.create({
      url: url,
      to: to,
      from: callerNumber,
    });
  } else {
    console.log("Calling client:" + to);
    call =  await client.api.calls.create({
      url: url,
      to: 'client:' + to,
      from: callerId,
    });
  }
  console.log(call.sid)
  //call.then(console.log(call.sid));
  return response.send(call.sid);
}

/**
 * Creates an endpoint that plays back a greeting.
 */
function incoming() {
  const voiceResponse = new VoiceResponse();
  voiceResponse.say("Congratulations! You have received your first inbound call! Good bye.");
  console.log('Response:' + voiceResponse.toString());
  return voiceResponse.toString();
}

function welcome() {
  const voiceResponse = new VoiceResponse();
  voiceResponse.say("Welcome to Twilio");
  console.log('Response:' + voiceResponse.toString());
  return voiceResponse.toString();
}

function isNumber(to) {
  if(to.length == 1) {
    if(!isNaN(to)) {
      console.log("It is a 1 digit long number" + to);
      return true;
    }
  } else if(String(to).charAt(0) == '+') {
    number = to.substring(1);
    if(!isNaN(number)) {
      console.log("It is a number " + to);
      return true;
    };
  } else {
    if(!isNaN(to)) {
      console.log("It is a number " + to);
      return true;
    }
  }
  console.log("not a number");
  return false;
}

exports.tokenGenerator = tokenGenerator;
exports.makeCall = makeCall;
exports.placeCall = placeCall;
exports.incoming = incoming;
exports.welcome = welcome;

The matter that I get this done doesn't really matter as long as I am able to achieve the goal of getting the calle to answer the phone with the mp3 playing, where it doesn't play from the app initially. 只要我能够实现让被叫方在播放mp3的同时接听电话的目标,而最初在应用程序中不会播放,那么我完成此操作实际上就无关紧要。

Twilio developer evangelist here. Twilio开发人员布道者在这里。

As Alex said in the comments, you do not need the Programmable Voice SDK for the use case you are describing. 正如Alex在评论中所说,对于所描述的用例,您不需要Programmable Voice SDK。

To trigger a call to someone, you want to use the Twilio REST API to make the call . 要触发对某人的呼叫,您想使用Twilio REST API进行呼叫 You will need to do this using server side code so that you don't inadvertently share your Twilio credentials. 您将需要使用服务器端代码来执行此操作,以便不会无意间共享Twilio凭据。 Once the call connects, Twilio will make a webhook request to your server again to find out what to do next. 通话接通后,Twilio将再次向您的服务器发出网络挂接请求,以查找下一步的操作。 You should respond to this request with the TwiML to <Play> the mp3 file. 您应该使用TwiML响应此请求,以<Play> mp3文件。

If you are comfortable carrying on with Node.js, then something like this should do the trick: 如果您对继续使用Node.js感到满意,那么应该使用以下方法:

const twilio = require('twilio');
const express = require('express');
const bodyParser = require('body-parser');

const VoiceResponse = twilio.twiml.VoiceResponse;
const callerId = process.env.MY_TWILIO_NUMBER;
const mp3url = process.env.MP3_URL;

const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));

app.post('/calls', (request, response) => {
  client.calls.create({
    to: request.body.To,
    from: callerId,
    url: 'https://example.com/calls/play-mp3'
  }).then(() => {
    response.sendStatus(200);
  }).catch((err) => {
    response.status(400).send(err.message);
  });
});

app.post('/calls/play-mp3', (request, response) => {
  const twiml = new VoiceResponse();
  twiml.play(mp3url);
  response.set('Content-Type', 'text/xml');
  response.send(twiml.toString());
});

app.listen(process.env.PORT || 3000, () => {
  console.log('The app is up and running');
});

All you'd need to do from here is replace the code that starts a call in your Android application with code that sends an HTTP POST request to the /calls endpoint in this little application and sends the number you want to call as the To parameter in the request body. 从这里开始,您需要做的就是用在此小应用程序中向/calls端点发送HTTP POST请求并将要调用的号码作为To参数发送给您的代码替换在Android应用程序中启动调用的代码。在请求正文中。

Let me know if that helps at all. 让我知道是否有帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在未连接的情况下检查我的 Logcat? - How can I check my Logcat without being connected? 无法使用 Plivo Android SDK 进行外呼 - Can't make outbound call using Plivo Android SDK 如何在 Android 中检测拨出电话已连接 - How can I detect outgoing call is connected in Android 如何确保文件从手机到服务器而不被篡改? - How can I make sure file gets from phone to server without being tampered with? 我如何拨打电话而不在Android中显示特定号码的拨号程序活动? - How can i make a call without showing dialer activity to particular number in android? 使用Plivo子帐户拨打电话 - Make outbound call using Plivo sub account 如何知道网络上的设备正在提供服务而未连接到网络 - How to know that a device on the a network is offering a service without being connected to the network 系统管理的连接 - 拨打出站电话而不将其视为 sim 电话 - System managed connection - place outbound call without treating it as a sim call 如何从接收方拨打服务电话取决于互联网连接与否? - How to make call to service from receiver depends on Internet connected or not? 如何在不裁剪图像的情况下显示这些ListView项目? - How can I show these ListView items without the images being clipped?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM