在陪玩系统源码中,用户之间主要的交流方式就是语音通话,实时互动性的语音通话能让人产生面对面交谈的感觉,所以在陪玩系统源码中,语音通话功能的开发非常重要,今天我们就一起来看看如何用腾讯即时通讯IM和实时音视频实现陪玩系统源码的语音通话功能吧。

大致分为以下几步:

  • 初步实现语音通话
  • 完善通话逻辑
  • 铃声震动实现、悬浮窗实现

初步实现陪玩系统源码的语音通话

1、集成SDK

  • 在模块的build.gradle中的 dependencies中添加
    dependencies {implementation 'com.tencent.liteav:LiteAVSDK_TRTC:latest.release'}
  • 在defaultCOnfig中,指定CPU架构
    defaultConfig {ndk {abiFilters "armeabi", "armeabi-v7a", "arm64-v8a"}}
  • 配置权限
    <uses-permission android:name="android.permission.INTERNET" />    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses- permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.RECORD_AUDIO" /><uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /><uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.CAMERA" /><uses-permission android:name="android.permission.READ_PHONE_STATE" /><uses-feature android:name="android.hardware.camera" /><uses-feature android:name="android.hardware.camera.autofocus" />
  • 设置混淆
    -keep class com.tencent.** { *; }
  • 设置打包参数
   packagingOptions {pickFirst '**/libc++_shared.so'doNotStrip "*/armeabi/libYTCommon.so"doNotStrip "*/armeabi-v7a/libYTCommon.so"doNotStrip "*/x86/libYTCommon.so"doNotStrip "*/arm64-v8a/libYTCommon.so"}

2、实现通话

复制源码文件夹trtcaudiocalldemo 中的ui和model到项目中。这里看自己的需求进行选择,实现陪玩系统源码的语音通话,我们只需要TRTCAudioCallActivity.java文件
复制CallService 到项目中,这个Service主要负责处理接听电话的事务(接听电话需要进房需要查询用户信息,生成一个beingCallUserModel传入)
调用 TRTCAudioCallActivity.startCallSomeone(getContext(), mContactList);发起陪玩系统源码的语音通话,这里的mContactList 如果是单聊或者群聊只邀请一个人,只会有一个model,查询设置这个model的avatar、phone、userid、username、groupId即可。到此初步集成完毕,可以进行语音通话了。

完善陪玩系统源码通话逻辑

1、Android端的通话逻辑并不完善,让我们来看看它的问题

  • 不会发送结束消息,任何情况下的挂断都是发送 取消命令
  • 群通话远端用户离开房间不会触发通话挂断

问题所在:TRTCAuduiCallImpl中的hangup 在通话进行中或者发起人主动挂断的情况下只会发送取消通话命令

根据正常的打电话逻辑,A打给B,会有以下几种情况

  • 未通话:A取消,B拒绝,
  • 通话中:A挂断 ,B挂断

首先B拒绝,会在hangup方法中进入reject()方法中,发送一个拒绝的消息,这个我们不用处理;然后是A取消的情况,可以通过判断邀请列表的人,如果邀请列表的人大于0,这个时候挂断,那么一定是A取消;再是A挂断和B挂断,这里得区分一下在陪玩系统源码中是群聊通话,还是单聊通话,如果是单聊通话,那么A挂断 就是A判断房间中用户数未0,发送一个通话结束消息出去,同理B一样。如果是群聊中,那么就是最后一个退出房间的人判断,发送一个通话结束的消息出去。

所以在群聊和单聊中我们可以这样判断:

                    Log.d(TAG, "Hangup: " + mCurRoomUserSet + " " + mCurInvitedList + "  " + mIsInRoom);if (mIsInRoom) {if (isCollectionEmpty(mCurRoomUserSet)) {if (mCurInvitedList.size() > 0) {//取消sendModel("", CallModel.VIDEO_CALL_ACTION_SPONSOR_CANCEL);} else {//通话结束sendModel("", CallModel.VIDEO_CALL_ACTION_HANGUP);}}}stopCall();exitRoom();}

并且如果是群聊 ,需要在远端用户退出群主,并且群主里面没有用户的时候发送通话结束的消息即 在preExitRoom方法里面调用groupHangup方法,并且退房相关操作需要注释掉,因为groupHangup方法里面会对房间参数进行判断,需要发消息,然后退房。

当然发送消息并退房并不是陪玩系统源码中所有情况都适用,比如忙线,拒接、超时的时候,就只需要执行退房操作,所以在这些情况下不能调用groupHangup方法,只判断执行退房操作。

2、解析陪玩系统源码自定义消息

这个东西看需求,一般情况下,一次通话都会有两条消息,即一条发起通话消息,一条结束(拒绝、忙线、挂断、超时等情况)。

  private void buildVoiceCallView(ICustomMessageViewGroup parent, MessageInfo info, TRTCAudioCallImpl.CallModel data) {if (data.action == TRTCAudioCallImpl.CallModel.VIDEO_CALL_ACTION_DIALING) {// 把自定义消息view添加到TUIKit内部的父容器里View view = LayoutInflater.from(AndroidApplication.getInstance()).inflate(R.layout.dial_senc_call_message, null, false);parent.addMessageItemView(view);TextView tv = view.findViewById(R.id.tv_content);if (info.isSelf()) {tv.setText("您发起了语音通话");} else {tv.setText("对方发起了语音通话");}return;}// 把自定义消息view添加到TUIKit内部的父容器里View view = LayoutInflater.from(AndroidApplication.getInstance()).inflate(R.layout.dial_custom_message, null, false);parent.addMessageContentView(view);// 自定义消息view的实现,这里仅仅展示文本信息,并且实现超链接跳转TextView textView = view.findViewById(R.id.tv_dial_status);ImageView ivLeft = view.findViewById(R.id.iv_left);ImageView ivRight = view.findViewById(R.id.iv_right);if (info.isSelf()) {ivRight.setVisibility(View.VISIBLE);ivLeft.setVisibility(View.GONE);textView.setTextColor(getResources().getColor(R.color.white));} else {ivRight.setVisibility(View.GONE);ivLeft.setVisibility(View.VISIBLE);textView.setTextColor(getResources().getColor(R.color.color_333333));}String text;switch (data.action) {case TRTCAudioCallImpl.CallModel.VIDEO_CALL_ACTION_SPONSOR_CANCEL:text = "已取消";break;case TRTCAudioCallImpl.CallModel.VIDEO_CALL_ACTION_REJECT:text = "已拒绝";break;case TRTCAudioCallImpl.CallModel.VIDEO_CALL_ACTION_SPONSOR_TIMEOUT:text = "无人接听";break;case TRTCAudioCallImpl.CallModel.VIDEO_CALL_ACTION_HANGUP:if (data.duration == 0) {text = "通话结束";} else {text = "通话结束 " + TimeUtils.millis2StringByCorrect(data.duration * 1000, data.duration >= 60 * 60 ? "HH:mm:ss" : "mm:ss");}break;case TRTCAudioCallImpl.CallModel.VIDEO_CALL_ACTION_LINE_BUSY:text = "忙线中";break;default:text = "未知通话错误";break;}textView.setText(text);}

铃声震动实现、悬浮窗实现

1、铃声震动(呼叫和待接听响铃,接听和挂断停止响铃)

  • 陪玩系统源码呼叫方邀请页面响铃或震动,在showInvitingView()方法中添加
//开始呼叫响铃
if (mRingVibrateHelper != null) {    mRingVibrateHelper.initLocalCallRinging();}
  • 用户在陪玩系统源码中通话中停止响铃或震动,在showCallingView()方法中使用
//停止响铃if (mRingVibrateHelper != null) {    mRingVibrateHelper.stopRing();}
  • 陪玩系统源码接听方在,接听等待页面响铃或震动,在showWaitingResponseView()方法中使用
//响铃或者震动mRingVibrateHelper.initRemoteCallRinging();
  • 页面退出,停止响铃
 if (mRingVibrateHelper != null) {mRingVibrateHelper.stopRing();mRingVibrateHelper.releaseMediaPlayer();}

分享一下响铃震动帮助类TimRingVibrateHelper

/*** @author leary* 响铃震动帮助类*/
public class TimRingVibrateHelper {private static final String TAG = TimRingVibrateHelper.class.getSimpleName();/*** =============响铃 震动相关*/private MediaPlayer mMediaPlayer;private Vibrator mVibrator;private static TimRingVibrateHelper instance;public static TimRingVibrateHelper getInstance() {if (instance == null) {synchronized (TimRingVibrateHelper.class) {if (instance == null) {instance = new TimRingVibrateHelper();}}}return instance;}private TimRingVibrateHelper() {//铃声相关mMediaPlayer = new MediaPlayer();mMediaPlayer.setOnPreparedListener(mp -> {if (mp != null) {mp.setLooping(true);mp.start();}});}/*** ==============响铃、震动相关方法========================*/public void initLocalCallRinging() {try {AssetFileDescriptor assetFileDescriptor = AndroidApplication.getInstance().getResources().openRawResourceFd(R.raw.voip_outgoing_ring);mMediaPlayer.reset();mMediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(),assetFileDescriptor.getStartOffset(), assetFileDescriptor.getLength());assetFileDescriptor.close();// 设置 MediaPlayer 播放的声音用途if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {AudioAttributes attributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build();mMediaPlayer.setAudioAttributes(attributes);} else {mMediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);}mMediaPlayer.prepareAsync();final AudioManager am = (AudioManager) AndroidApplication.getInstance().getSystemService(Context.AUDIO_SERVICE);if (am != null) {am.setSpeakerphoneOn(false);// 设置此值可在拨打时控制响铃音量am.setMode(AudioManager.MODE_IN_COMMUNICATION);// 设置拨打时响铃音量默认值am.setStreamVolume(AudioManager.STREAM_VOICE_CALL, 8, AudioManager.STREAM_VOICE_CALL);}} catch (IOException e) {e.printStackTrace();}}/*** 判断系统响铃正东相关设置* 1、系统静音 不震动 就两个都不设置* 2、静音震动* 3、只响铃不震动* 4、响铃且震动*/public void initRemoteCallRinging() {int ringerMode = getRingerMode(AndroidApplication.getInstance());if (ringerMode != AudioManager.RINGER_MODE_SILENT) {if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {startVibrator();} else {if (isVibrateWhenRinging()) {startVibrator();}startRing();}}}private int getRingerMode(Context context) {AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);return audio.getRingerMode();}/*** 开始响铃*/private void startRing() {Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);try {mMediaPlayer.setDataSource(AndroidApplication.getInstance(), uri);mMediaPlayer.prepareAsync();} catch (Exception e) {e.printStackTrace();Log.e(TAG, "Ringtone not found : " + uri);try {uri = RingtoneManager.getValidRingtoneUri(AndroidApplication.getInstance());mMediaPlayer.setDataSource(AndroidApplication.getInstance(), uri);mMediaPlayer.prepareAsync();} catch (Exception e1) {e1.printStackTrace();Log.e(TAG, "Ringtone not found: " + uri);}}}/*** 开始震动*/private void startVibrator() {if (mVibrator == null) {mVibrator = (Vibrator) AndroidApplication.getInstance().getSystemService(Context.VIBRATOR_SERVICE);} else {mVibrator.cancel();}mVibrator.vibrate(new long[]{500, 1000}, 0);}/*** 判断系统是否设置了 响铃时振动*/private boolean isVibrateWhenRinging() {ContentResolver resolver = AndroidApplication.getInstance().getApplicationContext().getContentResolver();if (Build.MANUFACTURER.equals("Xiaomi")) {return Settings.System.getInt(resolver, "vibrate_in_normal", 0) == 1;} else if (Build.MANUFACTURER.equals("smartisan")) {return Settings.Global.getInt(resolver, "telephony_vibration_enabled", 0) == 1;} else {return Settings.System.getInt(resolver, "vibrate_when_ringing", 0) == 1;}}/*** 停止震动和响铃*/public void stopRing() {if (mMediaPlayer != null) {mMediaPlayer.reset();}if (mVibrator != null) {mVibrator.cancel();}if (AndroidApplication.getInstance() != null) {//通话时控制音量AudioManager audioManager = (AudioManager) AndroidApplication.getInstance().getApplicationContext().getSystemService(AUDIO_SERVICE);audioManager.setMode(AudioManager.MODE_NORMAL);}}/*** 释放资源*/public void releaseMediaPlayer() {if (mMediaPlayer != null) {mMediaPlayer.release();mMediaPlayer = null;}if (instance != null) {instance = null;}// 退出此页面后应设置成正常模式,否则按下音量键无法更改其他音频类型的音量if (AndroidApplication.getInstance() != null) {AudioManager am = (AudioManager) AndroidApplication.getInstance().getApplicationContext().getSystemService(Context.AUDIO_SERVICE);if (am != null) {am.setMode(AudioManager.MODE_NORMAL);}}}
}

2、陪玩系统源码中悬浮窗的实现

  • 申请权限
  • 将当前通话Activity移动到后台执行
  • 开启悬浮窗服务

1)申请权限

   @TargetApi(19)public static boolean canDrawOverlays(final Context context, boolean needOpenPermissionSetting) {boolean result = true;if (Build.VERSION.SDK_INT >= 23) {try {boolean booleanValue = (Boolean) Settings.class.getDeclaredMethod("canDrawOverlays", Context.class).invoke((Object) null, context);if (!booleanValue && needOpenPermissionSetting) {ArrayList<String> permissionList = new ArrayList();permissionList.add("android.settings.action.MANAGE_OVERLAY_PERMISSION");showPermissionAlert(context, context.getString(R.string.tim_float_window_not_allowed), new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {if (-1 == which) {Intent intent = new Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION", Uri.parse("package:" + context.getPackageName()));context.startActivity(intent);}if (-2 == which) {Toasty.warning(context, "抱歉,您已拒绝DBC获得您的悬浮窗权限,将影响您接听对方发起的语音通话。").show();}}});}Log.i(TAG, "isFloatWindowOpAllowed allowed: " + booleanValue);return booleanValue;} catch (Exception var7) {Log.e(TAG, String.format("getDeclaredMethod:canDrawOverlays! Error:%s, etype:%s", var7.getMessage(), var7.getClass().getCanonicalName()));return true;}} else if (Build.VERSION.SDK_INT < 19) {return true;} else {Object systemService = context.getSystemService(Context.APP_OPS_SERVICE);Method method;try {method = Class.forName("android.app.AppOpsManager").getMethod("checkOp", Integer.TYPE, Integer.TYPE, String.class);} catch (NoSuchMethodException var9) {Log.e(TAG, String.format("NoSuchMethodException method:checkOp! Error:%s", var9.getMessage()));method = null;} catch (ClassNotFoundException var10) {var10.printStackTrace();method = null;}if (method != null) {try {Integer tmp = (Integer) method.invoke(systemService, 24, context.getApplicationInfo().uid, context.getPackageName());result = tmp == 0;} catch (Exception var8) {Log.e(TAG, String.format("call checkOp failed: %s etype:%s", var8.getMessage(), var8.getClass().getCanonicalName()));}}Log.i(TAG, "isFloatWindowOpAllowed allowed: " + result);return result;}}

当然申请悬浮窗全选会有跳转到设置界面这个过程,所以还需要添加判断是否具有悬浮窗权限的判断过程。

2)将当前通话Activity移动到后台执行
这个很简单,就是将Activity的lunchMode改为SingleInstance模式,然后直接调用moveTaskToBack(true);方法,这里传true,表示任何情况下 都会将Acitivty移动到后台。
3)绑定悬浮窗服务,开启陪玩系统源码悬浮窗
创建一个悬浮窗Service,获取WindowManager,在windowManager添加一个自定义的悬浮窗View即可,当然要想悬浮窗可以移动,得重写悬浮窗的,触摸事件。在悬浮窗里面注册一个本地广播,方便改变通话状态,记录通话时间等等。

public class TimFloatWindowService extends Service implements View.OnTouchListener {private WindowManager mWindowManager;private WindowManager.LayoutParams wmParams;private LayoutInflater inflater;/*** 浮动布局view*/private View mFloatingLayout;/*** 容器父布局*/private View mMainView;/*** 开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)*/private int mTouchStartX, mTouchStartY, mTouchCurrentX, mTouchCurrentY;/*** 开始时的坐标和结束时的坐标(相对于自身控件的坐标)*/private int mStartX, mStartY, mStopX, mStopY;/*** 判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件*/private boolean isMove;/*** 判断是否绑定了服务*/private boolean isServiceBind;/*** 通话状态*/private TextView mAcceptStatus;public class TimBinder extends Binder {public TimFloatWindowService getService() {return TimFloatWindowService.this;}}private BroadcastReceiver mTimBroadCastReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {if (isServiceBind && CommonI.TIM.BROADCAST_FLAG_FLOAT_STATUS.equals(intent.getAction())&& mAcceptStatus != null) {String status = intent.getStringExtra(CommonI.TIM.KEY_ACCEPT_STATUS);mAcceptStatus.setText(status);}}};@Overridepublic IBinder onBind(Intent intent) {isServiceBind = true;initFloating();//悬浮框点击事件的处理return new TimBinder();}@Overridepublic void onCreate() {super.onCreate();//设置悬浮窗基本参数(位置、宽高等)initWindow();//注册 BroadcastReceiver 监听情景模式的切换IntentFilter filter = new IntentFilter();filter.addAction(CommonI.TIM.BROADCAST_FLAG_FLOAT_STATUS);LocalBroadcastManager.getInstance(this).registerReceiver(mTimBroadCastReceiver, filter);}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {super.onDestroy();isServiceBind = false;if (mFloatingLayout != null) {// 移除悬浮窗口mWindowManager.removeView(mFloatingLayout);mFloatingLayout = null;}LocalBroadcastManager.getInstance(this).unregisterReceiver(mTimBroadCastReceiver);}/*** 设置悬浮框基本参数(位置、宽高等)*/private void initWindow() {mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);//设置好悬浮窗的参数wmParams = getParams();// 悬浮窗默认显示以右上角为起始坐标wmParams.gravity = Gravity.RIGHT | Gravity.TOP;// 不设置这个弹出框的透明遮罩显示为黑色wmParams.format = PixelFormat.TRANSLUCENT;//悬浮窗的开始位置,因为设置的是从右上角开始,所以屏幕左上角是x=0;y=0wmParams.x = 40;wmParams.y = 160;//得到容器,通过这个inflater来获得悬浮窗控件inflater = LayoutInflater.from(getApplicationContext());// 获取浮动窗口视图所在布局mFloatingLayout = inflater.inflate(R.layout.layout_tim_float_window, null);// 添加悬浮窗的视图mWindowManager.addView(mFloatingLayout, wmParams);}private WindowManager.LayoutParams getParams() {wmParams = new WindowManager.LayoutParams();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;} else {wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;}//设置可以显示在状态栏上wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;//设置悬浮窗口长宽数据wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;return wmParams;}//加载远端视屏:在这对悬浮窗内内容做操作private void initFloating() {//将子View加载进悬浮窗View//悬浮窗父布局mMainView = mFloatingLayout.findViewById(R.id.layout_dial_float);//加载进悬浮窗的子View,这个VIew来自天转过来的那个Activity里面的那个需要加载的ViewmAcceptStatus = mFloatingLayout.findViewById(R.id.tv_accept_status);
//        View mChildView = renderView.getChildView();
//        mMainView.addView(mChildView);//将需要悬浮显示的Viewadd到mTXCloudVideoView中//悬浮框触摸事件,设置悬浮框可拖动mMainView.setOnTouchListener(this);//悬浮框点击事件mMainView.setOnClickListener(v -> {//绑定了服务才跳转,不绑定服务不跳转if (!isServiceBind) {return;}//在这里实现点击重新回到Activity//从该service跳转至该activity会将该activity从后台唤醒,所以activity会走onReStart()Intent intent = new Intent(TimFloatWindowService.this, TRTCAudioCallActivity.class);//需要Intent.FLAG_ACTIVITY_NEW_TASK,不然会崩溃intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);});}@Overridepublic boolean onTouch(View v, MotionEvent event) {int action = event.getAction();switch (action) {case MotionEvent.ACTION_DOWN:isMove = false;mTouchStartX = (int) event.getRawX();mTouchStartY = (int) event.getRawY();mStartX = (int) event.getX();mStartY = (int) event.getY();break;case MotionEvent.ACTION_MOVE:mTouchCurrentX = (int) event.getRawX();mTouchCurrentY = (int) event.getRawY();wmParams.x -= mTouchCurrentX - mTouchStartX;wmParams.y += mTouchCurrentY - mTouchStartY;Log.i("Tim_FloatingListener", " Cx: " + mTouchCurrentX + " Sx: " + mTouchStartX + " Cy: " + mTouchCurrentY + " Sy: " + mTouchStartY);if (mFloatingLayout != null) {mWindowManager.updateViewLayout(mFloatingLayout, wmParams);}mTouchStartX = mTouchCurrentX;mTouchStartY = mTouchCurrentY;break;case MotionEvent.ACTION_UP:mStopX = (int) event.getX();mStopY = (int) event.getY();if (Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1) {isMove = true;}break;default:break;}//如果是移动事件不触发OnClick事件,防止移动的时候一放手形成点击事件return isMove;}
}

以上就是“用腾讯即时通讯IM和实时音视频实现陪玩系统源码的语音通话功能”的全部内容,希望对大家有帮助。

用腾讯即时通讯IM和实时音视频实现陪玩系统源码的语音通话功能相关推荐

  1. 用腾讯即时通讯IM和实时音视频实现完整语音通话功能

    说来奇怪,即时通讯领域的霸主QQ,微信,旗下产品出的腾讯即时通讯IM就像个残疾人一样,这里不对那里不对,要达到生产级别,就不得不去改它很多源码才行.今天先不吐槽其他的,我们看看如何在腾讯Im里面完成语 ...

  2. 分享即时通讯开发之实时音视频技术基础知识

    随着移动网络速度越来越快.质量越来越来,实时音视频技术已经在各种应用场景下全面开花,语音通话.视频通话.视频会议.远程白板.远程监控等等.实时音视频技术的开发也越来越受到重视,但是由于音视频开发涉及知 ...

  3. 即时通讯开发之实时音视频中的基本架构和协议栈

    为了便于理解,我们来看一个最基本的三角形WebRTC架构(见下图): 在这个架构中,移动电话用"浏览器M"表示,笔记本电脑用"浏览器L"表示,通过Web服务器将 ...

  4. [Web端接入经验分享] 腾讯云即时通信TIM、实时音视频TRTC

    [Web端接入经验分享] 腾讯云即时通信TIM.实时音视频TRTC 即时通信TIM官网地址 即时通信TIM SDK API文档地址 实时音视频TRTC官网地址 实时音视频TRTC SDK API文档地 ...

  5. 即时通讯项目 java版本qq (含服务器和客户端)源码_即时通讯安卓-QQ互联网和即时通讯云,如何实现即时通讯,这是Android还是Java...

    Android是系统平台. 应用程序所做的是应用程序的开发和完成 也就是说,通信是网络通信,但在手机环境中,网络的情况更复杂,所以我们必须做好结构 安卓即时通讯. 怎么做?安卓版TT即时通讯排行. A ...

  6. Cable Messenger 多人实时音视频聊天:系统架构

    文章目录 通信方式 STUN 与 TURN 服务 信令系统 处理架构 做音视频聊天是一件有趣的事,因为你可以有机会去学到很多其它领域的东西.Cable Messenger 的音视频聊天主要采用Goog ...

  7. 【新知实验室】——腾讯云TRTC实时音视频体验

    [新知实验室]--腾讯云TRTC实时音视频体验 前言 一.腾讯实时音视频TRTC是什么? 二.DEMO体验 1.注册腾讯云账号 2.使用实时音视频(需先开通) 3.查看项目(查看密钥和快速上手操作) ...

  8. 腾讯实时音视频又放大招!移动端语音聊天室组件正式来袭!

    随着移动互联网技术飞速发展,语音社交逐渐崛起.越来越多的社交App增加了语音聊天功能,更有专门的语音社交软件应运而生,占据了不少"90后""00后"的空闲时间. ...

  9. 实时音视频技术(WebRTC/voip/Linphone/P2P)

    实时音视频技术(WebRTC/voip/Linphone/P2P) 视频社交与语音社交???    实时视频(直播)/语音通信.多媒体技术团队在音视频编解码.前后处理.传输等技术:   在语音社交.视 ...

最新文章

  1. c#中volatile关键字的作用
  2. python 爬取图片_使用python爬取英雄联盟官方英雄皮肤图片
  3. 未定义标识符 stringc/c++(20)_20款奔驰G63AMG霸气颜值 运动造型
  4. 红旗桌面版本最新运用方式和题目问题解答100例-4
  5. 最短路径 floyd java_java实现Floyd算法求最短路径
  6. InVEST model中生境质量
  7. linux覆盖文件如何还原_大数据笔试真题集锦---第十九章Linux面试题
  8. 物联网架构----EMQ-Hook了解、连接Kafka发送消息
  9. css的基本定位机制
  10. sqlyog怎么查找表_sqlyog各种搜索方法
  11. 如何在excel中使用REFPROP属性库
  12. 【echarts】柱状图上方显示数值
  13. dataframe新增一列的方法
  14. 愿岁月流成花海,你我结伴而行
  15. 安装infinity后主页始终显示百度页面?
  16. mysql修改database名_MySQL中修改database的名字
  17. python for data analysis 操作usagov_bitly_data示例
  18. php数字和字母互换,PHP实现十进制数字与二十六进制字母串相互转换操作示例
  19. Android 11.0 Camera2 默认选择拍照尺寸修改及流程分析
  20. Day3 分支和循环

热门文章

  1. iOS9 NetworkExtension使用
  2. 二维插值,内插法和外插法
  3. 小狼毫 Rime 输入方案 设置
  4. 电脑必备软件---绿色PDF阅读工具福昕PDF阅读器.exe
  5. chrome网页调试工具的使用步骤
  6. #第23篇分享:一个北京二手房价格数据挖掘实例(python语言:sklearn随机森林)
  7. 2019成都php平均薪资,成都平均工资2019 成都最低工资标准2019
  8. 极力推荐|2021年,非常适合大数据从业者考的证书-DAMA
  9. 基于ssm的服装销售管理系统毕业论文
  10. 智能服装行业ERP软件帮助您提高工作效率和利润