上一篇文章介绍了环信的解决方案,见Android 即时音视频解决方案1——环信,这篇文章,介绍一下更加靠谱,也就是腾讯云的解决方案,毕竟腾讯是是这方面的头头,比较靠谱。当然,集成腾讯云比集成环信稍微复杂那么一点,需要有一点点的耐心。
官方地址音视频云通信 AVC
SDK下载AV Andriod1.3
文档地址音视频云通讯
先讲讲腾讯云的原理,使用腾讯云的时候,要有一个账号体系,这个账号体系比较灵活,可以使用独立模式也可以只用第三方账号体系,这里使用独立模式。
使用独立模式,要使用腾讯云的服务的时候,我们无需将用户的账号密码同步到腾讯,但是我们的服务端需要进行一定的处理。 用户在APP客户端输入帐号密码后到APP自有帐号登录服务器验证,验证成功后自有帐号登录服务器使用私钥派发签名(sig)给客户端;客户端提交用户帐号和私钥签名IM云(通过IMSDK或者音视频SDK接口),验证签名成功后向终端派发相应票据,进而使用IM云服务。
- 开发者需要保证私钥安全,腾讯完全信赖私钥签名;
- 签名有效期由开发者指定,并加密在私钥签名中;
- 该集成方式无需将APP自有帐号的帐号密码同步腾讯,最大程度保证开发者用户数据的私密性。
下面贴出官方文档的一个例子说明
假设开发者开发的APP为天天互动,天天互动自有服务器(开发自己开发)支持用户注册功能,同时天天互动接入了腾讯音视频通讯服务。某一天用户A在天天互动注册了帐号,登录天天互动后使用腾讯的音视频服务与家人视频。那么天天互动是如何实现这一功能呢?下面描述具体登录流程:
- 用户A在天天互动的客户端输入自己的帐号及密码后,客户端传给天天互动的帐号服务器进行验证;
- 天天互动的帐号服务器验证用户A的信息成功后,使用天天互动的私钥通过TLS提供的后台API生成签名(sig);
- 天天互动的帐号服务器将生成的签名派发给天天互动的客户端;
- 天天互动的客户端使用音视频云通讯相关服务时,需要提交帐号类型(accounttype)、sdkappid、identifier(也就是我们常说的用户id)和签名(由天天互动账号服务器调用TLS后台API生成并派发的)到音视频SDK或者IMSDK的login接口进行验证,验证成功后即可使用相关服务。
下面我们进行服务器端的编码
几个有用的文档和链接
- TLS后台API开发指引
- windows 64位 API
- 音视频云通讯账号登录集成
服务器端签名的函数,这里使用PHP
<?phpfunction signature($account_type, $identifier, $appid_at_3rd, $sdk_appid, $expiry_after, $private_key_path){ $command = 'signature.exe' . ' '. escapeshellarg($private_key_path) . ' ' . escapeshellarg($expiry_after) . ' ' . escapeshellarg($sdk_appid) . ' ' . escapeshellarg($account_type) . ' ' . escapeshellarg($appid_at_3rd) . ' ' .escapeshellarg($identifier); $ret = exec($command, $out, $status); if( $status == -1){ return null; } return $ret;}?>
当然你还需要将服务器sdk中的signarure.exe放到当前目录下,此外还有私钥
当我们服务接收到客户端请求时,登陆成功后会进行签名,需要将签名下发到客户端,客户端利用该签名向腾讯服务器验证
<?phpinclude_once('function.php');if(isset($_POST['username']) && isset($_POST['password'])){ $account['username']=$_POST['username'] ; $account['password']=$_POST['password']; //这里处理自己服务器登录的流程 //自己服务器登录成功后向客户端下发加密的rsa串 $result=signature("1071",$account['username'],"1400001973","1400001973",60*60*24*30,"./private_key"); if($result==null){ $res['status']=500; $res['message']="server error"; echo json_encode($res); }else{ $res['status']=200; $res['message']=$result; echo json_encode($res); }}else{ $res['status']=404; $res['message']="params is not right"; echo json_encode($res);}?>
签名的参数在腾讯云的后台管理可以看到
注意有一个时间戳参数,该参数代表多久之后过期,由开发者控制。
接下来就是客户端的事了。首先就是集成
权限
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <uses-permission android:name="android.permission.GET_TASKS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> <uses-permission android:name="android.permission.READ_LOGS"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.BROADCAST_STICKY"/>
声明组件
<application android:name=".app.App"></application >
<activity android:name=".activity.AvActivity" android:configChanges="keyboardHidden|orientation|locale|screenSize" android:screenOrientation="portrait" />
App类的内容很简单,就是获得QavsdkControl
public class App extends Application{ private QavsdkControl mQavsdkControl = null; @Override public void onCreate() { super.onCreate(); mQavsdkControl = new QavsdkControl(this); } public QavsdkControl getQavsdkControl() { return mQavsdkControl; }}
然后需要拷几个现成的类
你需要修改内容,让他不报错,大部分都是资源相关的东西。
然后需要修改两个变量,共两处,值在腾讯云后台获得
客户端的注册功能由开发者自己实现,因为腾讯云不干涉注册,而登陆需要先登录自己的服务器,然后拿签名向腾讯服务器验证。
private void login() { String u=username.getText().toString(); String p=password.getText().toString(); if (TextUtils.isEmpty(u)||TextUtils.isEmpty(p)){ Toast.makeText(getApplicationContext(),"账号或密码不能为空!",Toast.LENGTH_LONG).show(); return ; } //首先登录自己的服务器 //自己服务器登录成功后,服务器需要返回rsa加密后的串 RequestBody requestBody= new FormEncodingBuilder() .add("username",u) .add("password",p) .build(); String url="http://10.0.0.24/tencent/index.php"; Request request=new Request.Builder().url(url).post(requestBody).build(); mOkHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { Log.e("TAG", "Error,register failure."); } @Override public void onResponse(Response response) throws IOException { String result = response.body().string(); LoginModel bean = gson.fromJson(result, LoginModel.class); Message message = Message.obtain(); message.obj = bean; message.what = LOGIN; mHandler.sendMessage(message); } }); //获得rsa加密串后发起音视频前需要向腾讯服务器验证。 }
private Handler mHandler=new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case LOGIN: LoginModel bean= (LoginModel) msg.obj; if (bean.getStatus()==200){ sign=bean.getMessage(); //保存sign Log.e("TAG","sign:"+sign); Toast.makeText(getApplicationContext(),"登录成功,请稍后!",Toast.LENGTH_LONG).show(); startTencentContext(); }else{ Toast.makeText(getApplicationContext(),"登录失败!"+bean.getMessage(),Toast.LENGTH_LONG).show(); } break; default: break; } super.handleMessage(msg); } };
登陆成功后就可以拿到该签名了。之后开始向腾讯服务验证
private void startTencentContext() { //发起音视频前需要向腾讯服务器验证。 String u=username.getText().toString(); String p=password.getText().toString(); if (TextUtils.isEmpty(u)||TextUtils.isEmpty(p)){ Toast.makeText(getApplicationContext(),"账号或密码不能为空!",Toast.LENGTH_LONG).show(); return ; } if(TextUtils.isEmpty(sign)){ Toast.makeText(MainActivity.this, "请先登录", Toast.LENGTH_SHORT).show(); return ; } if (!mQavsdkControl.hasAVContext()) { if (!mQavsdkControl.isDefaultAppid()) { Toast.makeText(getApplicationContext(), getString(R.string.help_msg_appid_not_default), Toast.LENGTH_LONG).show(); } if (!mQavsdkControl.isDefaultUid()) { Toast.makeText(getApplicationContext(), getString(R.string.help_msg_uid_not_default), Toast.LENGTH_LONG).show(); } mLoginErrorCode = mQavsdkControl.startContext(u, sign); if (mLoginErrorCode != AVConstants.AV_ERROR_OK) { Toast.makeText(getApplicationContext(),"错误码:"+mLoginErrorCode, Toast.LENGTH_LONG).show(); } } }
验证的结果是广播异步返回的,因此我们需要注册一个广播接收器,接收这个消息
private void initStartContextBroadcast() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Util.ACTION_START_CONTEXT_COMPLETE); intentFilter.addAction(Util.ACTION_CLOSE_CONTEXT_COMPLETE); registerReceiver(mStartContextBroadcastReceiver, intentFilter);}private BroadcastReceiver mStartContextBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Log.e("TAG", "WL_DEBUG onReceive action = " + action); Log.e("TAG", "WL_DEBUG ANR StartContextActivity onReceive action = " + action + " In"); if (action.equals(Util.ACTION_START_CONTEXT_COMPLETE)) { mLoginErrorCode = intent.getIntExtra( Util.EXTRA_AV_ERROR_RESULT, AVConstants.AV_ERROR_OK); if (mLoginErrorCode == AVConstants.AV_ERROR_OK) { Intent i=new Intent(MainActivity.this,CallActivity.class); Bundle bundle=new Bundle(); bundle.putString("from",username.getText().toString()); i.putExtras(bundle); startActivity(i); } else { Toast.makeText(getApplicationContext(), getString(R.string.help_msg_login_error), Toast.LENGTH_LONG).show(); } } else if (action.equals(Util.ACTION_CLOSE_CONTEXT_COMPLETE)) { } Log.e("TAG", "WL_DEBUG ANR StartContextActivity onReceive action = " + action + " Out"); } };
之后我们就跳到了另外一个Activity当中,这个Activity中用来处理发起视频通话,处理通话失败等特殊情况的逻辑了,基本上直接从原Demo中复制代码修改即可,直接看代码
public class CallActivity extends AppCompatActivity implements View.OnClickListener{ private String from; private EditText to; private Button video,voice; private QavsdkControl mQavsdkControl; private int mCreateRoomErrorCode = AVConstants.AV_ERROR_OK; private int mCloseRoomErrorCode = AVConstants.AV_ERROR_OK; private int mAcceptErrorCode = AVConstants.AV_ERROR_OK; private int mInviteErrorCode = AVConstants.AV_ERROR_OK; private int mRefuseErrorCode = AVConstants.AV_ERROR_OK; private int mJoinRoomErrorCode = AVConstants.AV_ERROR_OK; private String mReceiveIdentifier = ""; private String mSelfIdentifier = ""; private boolean mIsVideo = false; private boolean isSender = false; private boolean isReceiver = false; private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Log.e("TAG", "WL_DEBUG onReceive action = " + action); Log.e("TAG", "WL_DEBUG ANR CreateRoomActivity onReceive action = " + action + " In"); if (action.equals(Util.ACTION_ACCEPT_COMPLETE)) { String identifier = intent.getStringExtra(Util.EXTRA_IDENTIFIER); mSelfIdentifier = intent.getStringExtra(Util.EXTRA_SELF_IDENTIFIER); mReceiveIdentifier = identifier; long roomId = intent.getLongExtra(Util.EXTRA_ROOM_ID, -1); mAcceptErrorCode = intent.getIntExtra( Util.EXTRA_AV_ERROR_RESULT, AVConstants.AV_ERROR_OK); if (mAcceptErrorCode == AVConstants.AV_ERROR_OK) { mQavsdkControl.enterRoom(roomId, identifier, mIsVideo); } else { Toast.makeText(CallActivity.this,R.string.accept_failed,Toast.LENGTH_SHORT).show(); } } else if (action.equals(Util.ACTION_CLOSE_ROOM_COMPLETE)) { } else if (action.equals(Util.ACTION_INVITE_ACCEPTED)) { startActivity(mReceiveIdentifier,mSelfIdentifier); } else if (action.equals(Util.ACTION_INVITE_CANCELED)) { Toast.makeText(getApplicationContext(), R.string.invite_canceled_toast, Toast.LENGTH_LONG).show(); } else if (action.equals(Util.ACTION_INVITE_COMPLETE)) { if (isReceiver) { Toast.makeText(getApplicationContext(), R.string.notify_conflict, Toast.LENGTH_LONG).show(); } mInviteErrorCode = intent.getIntExtra( Util.EXTRA_AV_ERROR_RESULT, AVConstants.AV_ERROR_OK); if (mInviteErrorCode == AVConstants.AV_ERROR_OK) { //等待对方接受邀请 Toast.makeText(getApplicationContext(), R.string.dialog_waitting_title, Toast.LENGTH_LONG).show(); } else { //邀请失败 Toast.makeText(getApplicationContext(), R.string.invite_failed, Toast.LENGTH_LONG).show(); closeRoom(); } } else if (action.equals(Util.ACTION_INVITE_REFUSED)) { Toast.makeText(getApplicationContext(), R.string.invite_refused_toast, Toast.LENGTH_LONG).show(); } else if (action.equals(Util.ACTION_RECV_INVITE)) { if (isSender) { Toast.makeText(getApplicationContext(), R.string.notify_conflict, Toast.LENGTH_LONG).show(); } isReceiver = true; mIsVideo = intent.getBooleanExtra(Util.EXTRA_IS_VIDEO, false); new AlertDialog.Builder(CallActivity.this) .setTitle(R.string.invite_title) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mQavsdkControl.accept(); } }) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mQavsdkControl.refuse(); isSender = isReceiver = false; } }) .setOnCancelListener( new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { Log.e("TAG", "WL_DEBUG onCancel"); mQavsdkControl.refuse(); isSender = isReceiver = false; } }).create().show(); } else if (action.equals(Util.ACTION_REFUSE_COMPLETE)) { mRefuseErrorCode = intent.getIntExtra( Util.EXTRA_AV_ERROR_RESULT, AVConstants.AV_ERROR_OK); if (mRefuseErrorCode != AVConstants.AV_ERROR_OK) { Toast.makeText(getApplicationContext(), R.string.refuse_failed, Toast.LENGTH_LONG).show(); } } else if (action.equals(Util.ACTION_ROOM_CREATE_COMPLETE)) { mCreateRoomErrorCode = intent.getIntExtra( Util.EXTRA_AV_ERROR_RESULT, AVConstants.AV_ERROR_OK); if (mCreateRoomErrorCode != AVConstants.AV_ERROR_OK) { Toast.makeText(getApplicationContext(), R.string.create_room_failed, Toast.LENGTH_LONG).show(); } } else if (action.equals(Util.ACTION_ROOM_JOIN_COMPLETE)) { mJoinRoomErrorCode = intent.getIntExtra( Util.EXTRA_AV_ERROR_RESULT, AVConstants.AV_ERROR_OK); if (mJoinRoomErrorCode != AVConstants.AV_ERROR_OK) { Toast.makeText(getApplicationContext(), R.string.join_room_failed, Toast.LENGTH_LONG).show(); } else { startActivity(mReceiveIdentifier,mSelfIdentifier); } } Log.e("TAG", "WL_DEBUG ANR CreateRoomActivity onReceive action = " + action + " Out"); } }; private void closeRoom() { mCloseRoomErrorCode = mQavsdkControl.exitRoom(); if (mCloseRoomErrorCode != AVConstants.AV_ERROR_OK) { Toast.makeText(CallActivity.this,R.string.close_room_failed,Toast.LENGTH_SHORT).show(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_call); initIntent(); initView(); initInviteBroadcast(); mQavsdkControl = ((App) getApplication()).getQavsdkControl(); } private void initIntent() { Intent intent=getIntent(); Bundle bundle=intent.getExtras(); from=bundle.getString("from"); Log.e("TAG","from:"+from); } private void initInviteBroadcast() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Util.ACTION_ACCEPT_COMPLETE); intentFilter.addAction(Util.ACTION_CLOSE_ROOM_COMPLETE); intentFilter.addAction(Util.ACTION_INVITE_ACCEPTED); intentFilter.addAction(Util.ACTION_INVITE_CANCELED); intentFilter.addAction(Util.ACTION_INVITE_COMPLETE); intentFilter.addAction(Util.ACTION_INVITE_REFUSED); intentFilter.addAction(Util.ACTION_RECV_INVITE); intentFilter.addAction(Util.ACTION_REFUSE_COMPLETE); intentFilter.addAction(Util.ACTION_ROOM_CREATE_COMPLETE); intentFilter.addAction(Util.ACTION_ROOM_JOIN_COMPLETE); registerReceiver(mBroadcastReceiver, intentFilter); } private void initView() { to= (EditText) findViewById(R.id.to); video= (Button) findViewById(R.id.video); voice= (Button) findViewById(R.id.voice); video.setOnClickListener(this); voice.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.voice: voice(); break; case R.id.video: video(); break; default: break; } } private void voice() { mSelfIdentifier=from; mReceiveIdentifier=to.getText().toString(); mIsVideo=false; if (Util.isNetworkAvailable(getApplicationContext())) { if (TextUtils.isEmpty(mSelfIdentifier)) { Toast.makeText(getApplicationContext(), getString(R.string.help_msg_send_account_error), Toast.LENGTH_SHORT).show(); } else if (TextUtils.isEmpty(mReceiveIdentifier)) { Toast.makeText(getApplicationContext(), getString(R.string.help_msg_recv_account_error), Toast.LENGTH_SHORT).show(); } else { if (mSelfIdentifier.equals(mReceiveIdentifier)) { Toast.makeText(getApplicationContext(), getString(R.string.help_msg_account_send_equal_recv), Toast.LENGTH_SHORT).show(); } else { invite(mIsVideo); isSender = true; } } } else { Toast.makeText(getApplicationContext(), getString(R.string.notify_no_network), Toast.LENGTH_SHORT).show(); } } private void video() { mSelfIdentifier=from; mReceiveIdentifier=to.getText().toString(); mIsVideo=true; if (Util.isNetworkAvailable(getApplicationContext())) { if (TextUtils.isEmpty(mSelfIdentifier)) { Toast.makeText(getApplicationContext(), getString(R.string.help_msg_send_account_error), Toast.LENGTH_SHORT).show(); } else if (TextUtils.isEmpty(mReceiveIdentifier)) { Toast.makeText(getApplicationContext(), getString(R.string.help_msg_recv_account_error), Toast.LENGTH_SHORT).show(); } else { if (mSelfIdentifier.equals(mReceiveIdentifier)) { Toast.makeText(getApplicationContext(), getString(R.string.help_msg_account_send_equal_recv), Toast.LENGTH_SHORT).show(); } else { invite(mIsVideo); isSender = true; } } } else { Toast.makeText(getApplicationContext(), getString(R.string.notify_no_network), Toast.LENGTH_SHORT).show(); } } private void startActivity(String mReceiveIdentifier,String mSelfIdentifier) { Log.e("TAG", "WL_DEBUG startActivity"); if ((mQavsdkControl != null) && (mQavsdkControl.getAVContext() != null) && (mQavsdkControl.getAVContext().getAudioCtrl() != null)) { mQavsdkControl.getAVContext().getAudioCtrl().startTRAEService(); } startActivityForResult( new Intent(Intent.ACTION_MAIN) .putExtra(Util.EXTRA_IDENTIFIER, mReceiveIdentifier) .putExtra(Util.EXTRA_SELF_IDENTIFIER, mSelfIdentifier) .setClass(this, AvActivity.class), 0); } @Override protected void onDestroy() { super.onDestroy(); if (mBroadcastReceiver != null) { unregisterReceiver(mBroadcastReceiver); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.e("TAG", "WL_DEBUG onActivityResult"); isSender = isReceiver = false; if ((mQavsdkControl != null) && (mQavsdkControl.getAVContext() != null) && (mQavsdkControl.getAVContext().getAudioCtrl() != null)) { mQavsdkControl.getAVContext().getAudioCtrl().stopTRAEService(); } closeRoom(); } private void invite(boolean isVideo) { if (TextUtils.isEmpty(mReceiveIdentifier)) return; mQavsdkControl.invite(mReceiveIdentifier, isVideo); }}
这样就差不多可以跑一个试试了。
其他细节看腾讯的文档把,多看几次就能把握个大概了。
源码下载
http://download.csdn.net/detail/sbsujjbcy/9139613
Github
https://github.com/lizhangqu/VoiceAndVideo
版权声明:本文为博主原创文章,未经博主允许不得转载。
- 2楼mayfla4天前 19:35
- 云以后会发展的越来越好的。节省资源
- 1楼u0125834595天前 10:41
- 66