Tra*_*ean 5 android twilio flutter
我正在尝试使用flutter创建IP语音(VOIP)移动应用程序。我还没有看到twilio语音api的flutter插件的实现,因此我使用MethodChannel将我的应用程序与本机android语音api集成在一起.twilio SDK似乎没有像它正确集成一样,我无法访问脚本中的twilio类和方法。这些是我得到的错误。
Running Gradle task 'assembleDebug'...
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:23: error: package android.support.annotation does not exist
import android.support.annotation.NonNull;
^
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:295: error: cannot find symbol
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
^
symbol: class NonNull
location: class MainActivity
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:295: error: cannot find symbol
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
^
symbol: class NonNull
location: class MainActivity
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:117: error: cannot find symbol
soundPoolManager = SoundPoolManager.getInstance(this.MainActivity);
^
symbol: variable MainActivity
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:186: error: cannot find symbol
public void onReconnecting(@NonNull Call call, @NonNull CallException callException) {
^
symbol: class NonNull
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:186: error: cannot find symbol
public void onReconnecting(@NonNull Call call, @NonNull CallException callException) {
^
symbol: class NonNull
/home/kudziesimz/voip20/android/app/src/main/java /com/workerbees/voip20/MainActivity.java:191: error: cannot find symbol
public void onReconnected(@NonNull Call call) {
^
symbol: class NonNull
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:279: error: cannot find symbol
int resultMic = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO);
^
symbol: variable ContextCompat
location: class MainActivity
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:284: error: method shouldShowRequestPermissionRationale in class Activity cannot be applied to given types;
if (MainActivity.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
^
required: String
found: MainActivity,String
reason: actual and formal argument lists differ in length
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:287: error: method requestPermissions in class Activity cannot be applied to given types;
MainActivity.requestPermissions(
^
required: String[],int
found: MainActivity,String[],int
reason: actual and formal argument lists differ in length
Note: /home/kudziesimz/voip20/android/app/src/main/java /com/workerbees/voip20/MainActivity.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
10 errors
Run Code Online (Sandbox Code Playgroud)
我遵循此处显示的voice-quickstart-android指南https://github.com/twilio/voice-quickstart-android
这是我的代码:main.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
//This is a test application which allows clients to make Voice Over The Internet Cal
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
static const platform = const MethodChannel("com.voip.call_management/calls");
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Call Management"),
),
bottomNavigationBar: Center(
child: IconButton(
icon: Icon(Icons.phone),
onPressed: () {
_makeCall;
}),
),
);
}
Future<void> _makeCall() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Row(
children: <Widget>[
Text('Call'),
Icon(
Icons.phone,
color: Colors.blue,
)
],
),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
TextField(
decoration: InputDecoration(
hintText: "client identity or phone number"),
),
SizedBox(
height: 20,
),
Text(
'Dial a client name or number.Leaving the field empty will result in an automated response.'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
IconButton(icon: Icon(Icons.phone), onPressed:()async {
try {
final result = await platform.invokeMethod("makecall");
} on PlatformException catch (e) {
print(e.message);
}
})
],
);
},
);
}
}
Run Code Online (Sandbox Code Playgroud)
MainActivity.java
package com.workerbees.voip20;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
//javacode imports
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.Log;
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.CallInvite;
import com.twilio.voice.ConnectOptions;
import com.twilio.voice.RegistrationException;
import com.twilio.voice.RegistrationListener;
import com.twilio.voice.Voice;
import java.util.HashMap;
//sound pool imports
import android.media.SoundPool;
import static android.content.Context.AUDIO_SERVICE;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.workerbees.voip/calls"; // MethodChannel Declaration
private static final String TAG = "VoiceActivity";
private static String identity = "alice";
private static String contact;
/*
* 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://bd107744.ngrok.io/accessToken";
private static final int MIC_PERMISSION_REQUEST_CODE = 1;
private String accessToken;
private AudioManager audioManager;
private int savedAudioMode = AudioManager.MODE_INVALID;
// Empty HashMap, never populated for the Quickstart
HashMap<String, String> params = new HashMap<>();
private SoundPoolManager soundPoolManager;
private Call activeCall;
Call.Listener callListener = callListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
// Note: this method is invoked on the main thread.
// TODO
if(call.method.equals("makecall")){
params.put("to", contact);
ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken)
.params(params)
.build();
activeCall = Voice.connect(MainActivity.this, connectOptions, callListener);
}
else if(call.method.equals("hangup")){
disconnect();
}
else if(call.method.equals("mute")){
mute();
}
else if (call.method.equals("hold")){
hold();
}
else{
Log.d(TAG,"invalid API call");
}
}
});
soundPoolManager = SoundPoolManager.getInstance(this.MainActivity);
/*
* Needed for setting/abandoning audio focus during a call
*/
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.setSpeakerphoneOn(true);
/*
* Enable changing the volume using the up/down keys during a conversation
*/
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
/*
* Displays a call dialog if the intent contains a call invite
*/
//handleIncomingCallIntent(getIntent());
/*
* Ensure the microphone permission is enabled
*/
if (!checkPermissionForMicrophone()) {
requestPermissionForMicrophone();
} else {
retrieveAccessToken();
}
}
private Call.Listener callListener() {
return new Call.Listener() {
/*
* This callback is emitted once before the Call.Listener.onConnected() callback when
* the callee is being alerted of a Call. The behavior of this callback is determined by
* the answerOnBridge flag provided in the Dial verb of your TwiML application
* associated with this client. If the answerOnBridge flag is false, which is the
* default, the Call.Listener.onConnected() callback will be emitted immediately after
* Call.Listener.onRinging(). If the answerOnBridge flag is true, this will cause the
* call to emit the onConnected callback only after the call is answered.
* See answeronbridge for more details on how to use it with the Dial TwiML verb. If the
* twiML response contains a Say verb, then the call will emit the
* Call.Listener.onConnected callback immediately after Call.Listener.onRinging() is
* raised, irrespective of the value of answerOnBridge being set to true or false
*/
@Override
public void onRinging(Call call) {
Log.d(TAG, "Ringing");
}
@Override
public void onConnectFailure(Call call, CallException error) {
setAudioFocus(false);
Log.d(TAG, "Connect failure");
String message = String.format("Call Error: %d, %s", error.getErrorCode(), error.getMessage());
Log.e(TAG, message);
}
@Override
public void onConnected(Call call) {
setAudioFocus(true);
Log.d(TAG, "Connected");
activeCall = call;
}
@Override
public void onReconnecting(@NonNull Call call, @NonNull CallException callException) {
Log.d(TAG, "onReconnecting");
}
@Override
public void onReconnected(@NonNull Call call) {
Log.d(TAG, "onReconnected");
}
@Override
public void onDisconnected(Call call, CallException error) {
setAudioFocus(false);
Log.d(TAG, "Disconnected");
if (error != null) {
String message = String.format("Call Error: %d, %s", error.getErrorCode(), error.getMessage());
Log.e(TAG, message);
}
}
};
}
private void disconnect() {
if (activeCall != null) {
activeCall.disconnect();
activeCall = null;
}
}
private void hold() {
if (activeCall != null) {
boolean hold = !activeCall.isOnHold();
activeCall.hold(hold);
}
}
private void mute() {
if (activeCall != null) {
boolean mute = !activeCall.isMuted();
activeCall.mute(mute);
}
}
private void setAudioFocus(boolean setFocus) {
if (audioManager != null) {
if (setFocus) {
savedAudioMode = audioManager.getMode();
// Request audio focus before making any device switch.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AudioAttributes playbackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
.setAudioAttributes(playbackAttributes)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int i) {
}
})
.build();
audioManager.requestAudioFocus(focusRequest);
} else {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.FROYO) {
int focusRequestResult = audioManager.requestAudioFocus(
new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange)
{
}
}, AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
}
}
/*
* Start by setting MODE_IN_COMMUNICATION as default audio mode. It is
* required to be in this mode when playout and/or recording starts for
* best possible VoIP performance. Some devices have difficulties with speaker mode
* if this is not set.
*/
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
} else {
audioManager.setMode(savedAudioMode);
audioManager.abandonAudioFocus(null);
}
}
}
private boolean checkPermissionForMicrophone() {
int resultMic = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO);
return resultMic == PackageManager.PERMISSION_GRANTED;
}
private void requestPermissionForMicrophone() {
if (MainActivity.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
} else {
MainActivity.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) {
Log.d(TAG, "Microphone permissions needed. Please allow in your application settings.");
} 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);
MainActivity.this.accessToken = accessToken;
} else {
Log.d(TAG, "Registration failed");
}
}
});
}
}
class SoundPoolManager {
private boolean playing = false;
private boolean loaded = false;
private boolean playingCalled = false;
private float actualVolume;
private float maxVolume;
private float volume;
private AudioManager audioManager;
private SoundPool soundPool;
private int ringingSoundId;
private int ringingStreamId;
private int disconnectSoundId;
private static SoundPoolManager instance;
private SoundPoolManager(Context context) {
// AudioManager audio settings for adjusting the volume
audioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE);
actualVolume = (float) audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
maxVolume = (float) audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
volume = actualVolume / maxVolume;
// Load the sounds
int maxStreams = 1;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
soundPool = new SoundPool.Builder()
.setMaxStreams(maxStreams)
.build();
} else {
soundPool = new SoundPool(maxStreams, AudioManager.STREAM_MUSIC, 0);
}
soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
loaded = true;
if (playingCalled) {
playRinging();
playingCalled = false;
}
}
});
ringingSoundId = soundPool.load(context, R.raw.incoming, 1);
disconnectSoundId = soundPool.load(context, R.raw.disconnect, 1);
}
public static SoundPoolManager getInstance(Context context) {
if (instance == null) {
instance = new SoundPoolManager(context);
}
return instance;
}
public void playRinging() {
if (loaded && !playing) {
ringingStreamId = soundPool.play(ringingSoundId, volume, volume, 1, -1, 1f);
playing = true;
} else {
playingCalled = true;
}
}
public void stopRinging() {
if (playing) {
soundPool.stop(ringingStreamId);
playing = false;
}
}
public void playDisconnect() {
if (loaded && !playing) {
soundPool.play(disconnectSoundId, volume, volume, 1, 0, 1f);
playing = false;
}
}
pub
您还有这个问题吗?按照Flutter 平台渠道指南,我能够毫无问题地使用 Twilio Android SDK。我在这个基于Twilio 的 Android 快速入门的演示中集成了 Twilio 所需的最少组件。
\n主程序.dart
\nimport \'package:flutter/material.dart\';\nimport \'package:flutter/services.dart\';\n\nvoid main() {\n runApp(MyApp());\n}\n\nclass MyApp extends StatelessWidget {\n @override\n Widget build(BuildContext context) {\n return MaterialApp(\n title: \'Flutter Demo\',\n theme: ThemeData(\n primarySwatch: Colors.blue,\n ),\n home: MyHomePage(title: \'Flutter Demo Home Page\'),\n );\n }\n}\n\nclass MyHomePage extends StatefulWidget {\n MyHomePage({Key key, this.title}) : super(key: key);\n\n final String title;\n\n @override\n _MyHomePageState createState() => _MyHomePageState();\n}\n\nclass _MyHomePageState extends State<MyHomePage> {\n static const platform = const MethodChannel(\'samples.flutter.dev/twilio\');\n\n Future<void> callTwilio() async{\n try {\n final String result = await platform.invokeMethod(\'callTwilio\');\n debugPrint(\'Result: $result\');\n } on PlatformException catch (e) {\n debugPrint(\'Failed: ${e.message}.\');\n }\n }\n\n @override\n Widget build(BuildContext context) {\n return Scaffold(\n appBar: AppBar(\n title: Text(widget.title),\n ),\n body: Center(\n child: Column(\n mainAxisAlignment: MainAxisAlignment.center,\n children: <Widget>[\n Text(\n \'Hello\',\n ),\n ],\n ),\n ),\n floatingActionButton: FloatingActionButton(\n onPressed: () => callTwilio(),\n tooltip: \'Call\',\n child: Icon(Icons.phone),\n ),\n );\n }\n}\nRun Code Online (Sandbox Code Playgroud)\nandroid/app/src/main/kotlin/{PACKAGE_NAME}/MainActivity.kt
\nclass MainActivity : FlutterActivity() {\n private val CHANNEL = "samples.flutter.dev/twilio"\n private val TAG = "MainActivity"\n\n override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {\n super.configureFlutterEngine(flutterEngine)\n MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->\n if (call.method == "callTwilio") {\n executeTwilioVoiceCall()\n result.success("Hello from Android")\n } else {\n result.notImplemented()\n }\n }\n }\n\n private val accessToken = ""\n var params = HashMap<String, String>()\n var callListener: Call.Listener = callListener()\n fun executeTwilioVoiceCall(){\n val connectOptions = ConnectOptions.Builder(accessToken)\n .params(params)\n .build()\n Voice.connect(this, connectOptions, callListener)\n }\n\n private fun callListener(): Call.Listener {\n return object : Call.Listener {\n override fun onRinging(call: Call) {\n Log.d(TAG, "Ringing")\n }\n\n override fun onConnectFailure(call: Call, error: CallException) {\n Log.d(TAG, "Connect failure")\n }\n\n override fun onConnected(call: Call) {\n Log.d(TAG, "Connected")\n }\n\n override fun onReconnecting(call: Call, callException: CallException) {\n Log.d(TAG, "onReconnecting")\n }\n\n override fun onReconnected(call: Call) {\n Log.d(TAG, "onReconnected")\n }\n\n override fun onDisconnected(call: Call, error: CallException?) {\n Log.d(TAG, "Disconnected")\n }\n\n override fun onCallQualityWarningsChanged(call: Call,\n currentWarnings: MutableSet<CallQualityWarning>,\n previousWarnings: MutableSet<CallQualityWarning>) {\n if (previousWarnings.size > 1) {\n val intersection: MutableSet<CallQualityWarning> = HashSet(currentWarnings)\n currentWarnings.removeAll(previousWarnings)\n intersection.retainAll(previousWarnings)\n previousWarnings.removeAll(intersection)\n }\n val message = String.format(\n Locale.US,\n "Newly raised warnings: $currentWarnings Clear warnings $previousWarnings")\n Log.e(TAG, message)\n }\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n至于Android中的依赖项,我已将它们添加到build.gradle配置中
\nandroid/build.gradle
\next.versions = [\n \'voiceAndroid\' : \'5.6.2\',\n \'audioSwitch\' : \'1.1.0\',\n]\nRun Code Online (Sandbox Code Playgroud)\n安卓/应用程序/build.grade
\ndependencies {\n ...\n implementation "com.twilio:audioswitch:${versions.audioSwitch}"\n implementation "com.twilio:voice-android:${versions.voiceAndroid}"\n}\nRun Code Online (Sandbox Code Playgroud)\n这是我的flutter doctor详细日志供参考
[\xe2\x9c\x93] Flutter (Channel master, 1.26.0-2.0.pre.281, on macOS 11.1 20C69 darwin-x64)\n \xe2\x80\xa2 Flutter version 1.26.0-2.0.pre.281\n \xe2\x80\xa2 Framework revision 4d5db88998 (3 weeks ago), 2021-01-11 10:29:26 -0800\n \xe2\x80\xa2 Engine revision d5cacaa3a6\n \xe2\x80\xa2 Dart version 2.12.0 (build 2.12.0-211.0.dev)\n\n[\xe2\x9c\x93] Android toolchain - develop for Android devices (Android SDK version 29.0.2)\n \xe2\x80\xa2 Platform android-30, build-tools 29.0.2\n \xe2\x80\xa2 Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java\n \xe2\x80\xa2 Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)\n \xe2\x80\xa2 All Android licenses accepted.\n\n[\xe2\x9c\x93] Xcode - develop for iOS and macOS (Xcode 12.0.1)\n \xe2\x80\xa2 Xcode at /Applications/Xcode.app/Contents/Developer\n \xe2\x80\xa2 Xcode 12.0.1, Build version 12A7300\n \xe2\x80\xa2 CocoaPods version 1.10.0\n\n[\xe2\x9c\x93] Chrome - develop for the web\n \xe2\x80\xa2 Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome\n\n[\xe2\x9c\x93] Android Studio (version 4.1)\n \xe2\x80\xa2 Android Studio at /Applications/Android Studio.app/Contents\n \xe2\x80\xa2 Flutter plugin can be installed from:\n https://plugins.jetbrains.com/plugin/9212-flutter\n \xe2\x80\xa2 Dart plugin can be installed from:\n https://plugins.jetbrains.com/plugin/6351-dart\n \xe2\x80\xa2 Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)\n\n[\xe2\x9c\x93] VS Code (version 1.52.1)\n \xe2\x80\xa2 VS Code at /Applications/Visual Studio Code.app/Contents\n \xe2\x80\xa2 Flutter extension version 3.18.1\n\n[\xe2\x9c\x93] Connected device (2 available)\n \xe2\x80\xa2 AOSP on IA Emulator (mobile) \xe2\x80\xa2 emulator-5554 \xe2\x80\xa2 android-x86 \xe2\x80\xa2 Android 9 (API 28) (emulator)\n \xe2\x80\xa2 Chrome (web) \xe2\x80\xa2 chrome \xe2\x80\xa2 web-javascript \xe2\x80\xa2 Google Chrome 88.0.4324.96\n\n\xe2\x80\xa2 No issues found!\nRun Code Online (Sandbox Code Playgroud)\n以下是示例应用程序运行时的外观。由于 API 密钥集无效,日志会抛出“Connect failure”和“Forbidden:403”错误,但这证明 Twilio Android SDK 可以通过 Flutter 平台通道正常运行。
\n\n您还可以在 pub.dev 中查看社区制作的可能适合您的用例的 Twilio Flutter 插件。
\n| 归档时间: |
|
| 查看次数: |
324 次 |
| 最近记录: |