现在直播特别火,绝大多数的中小型都没有独立开发这种功能的能力.比较火的第三方就是腾讯云了.但是腾讯云有些坑,入行须知.
先说它的Demo吧,Demo是有点大,运用MVP设计模式,SDK1.7到1.8实现了代码重构,基本上方法放置的位置全都变了,但是把 调用流程是不变的.
这里面主要有两种sdk 1,AVSDK 这个主要是管理视频的 包括直播,推流.... 2,IMSDK这个主要是管理聊天的.就是所说的即时通讯
调用流程:--------------1-----------------------------------------------------------------------------------
在 ReleaseLiveActivity中调用的
直播主页面之前的输入直播标题的页面
1,向自己后台获取sig sig相当于一个登录腾讯云服务器的平台,在申请sig异步任务回调的接口初始化IMSDK
2,初始化IMSDK 就是初始化聊天系统的sdk,初始化代码如下 ,其中 new TIMCallBack() 这个是个接口回调,如果登录成功就回调onSuccess()方法,失败就回调onError()方法.
TIMManager.getInstance().init(mContext); TIMUser userId = new TIMUser(); userId.setAccountType(DemoConstants.ACCOUNTTYPE); userId.setAppIdAt3rd(mConfig.appIdAt3rd); userId.setIdentifier(mConfig.identifier); /** * 登陆所需信息 * 1.sdkAppId : 创建应用时页面上分配的 sdkappid * 2.uid : 创建应用账号集成配置页面上分配的 accounttype * 3.app_id_at3rd : 第三方开放平台账号 appid,如果是自有的账号,那么直接填 sdkappid 的字符串形式 * 4.identifier :用户标示符,也就是我们常说的用户 id * 5.user_sig :使用 tls 后台 api tls_gen_signature_ex 或者工具生成的 user_sig */ TIMManager.getInstance().login( DemoConstants.APPID, userId, mUserSig, new TIMCallBack() { @Override public void onSuccess() { Log.w(TAG, "initAVSDKStep 3 : IMLogin succ "); Log.i(TAG, "init successfully. tiny id = " + IMSdkInt.get().getTinyId()); onLogin(true, IMSdkInt.get().getTinyId(), 0); } @Override public void onError(int code, String desc) { Log.w(TAG, "initAVSDKStep 3 : IMLogin fail "); Log.e(TAG, "init failed, imsdk error code = " + code + ", desc = " + desc); onLogin(false, 0, code); } });
3,在初始化IMSDK的回调接口之后,1.8.2是开启了两个线程,初始化AVSDK 也就是得到AVContext 我是用1.7开发的 在1.8.2中 官方Demo换了一下顺序,先申请房间号roomId再进行初始化AVSDK 其实结果都是一样的.初始化AVSDK也是一个接口回调 ,下面是初始化AVSDK的代码
private void onLogin(boolean result, long tinyId, int errorCode) { if (result) { mAVContext = AVContext.createInstance(mContext,false); Log.w(TAG, "initAVSDKStep 4 : AVContext createContext "+mAVContext); Log.d(TAG, "WL_DEBUG startContext mAVContext is null? " + (mAVContext == null)); mSelfIdentifier = mConfig.identifier; int ret = mAVContext.start(mConfig, mStartContextCompleteCallback); Log.w(TAG, "initAVSDKStep 5 : AVContext createContext startContext "+ret); mIsInStartContext = true; } else { mStartContextCompleteCallback.onComplete(errorCode,""); } }初始化AVSDK的回调接口
/** * 启动SDK系统的回调函数 */ private AVCallback mStartContextCompleteCallback = new AVCallback() { public void onComplete(int result, String s) { mIsInStartContext = false; mContext.sendBroadcast(new Intent( Util.ACTION_START_CONTEXT_COMPLETE).putExtra( Util.EXTRA_AV_ERROR_RESULT, result)); if (result != AVError.AV_OK) { mAVContext = null; Log.d(TAG, "WL_DEBUG mStartContextCompleteCallback mAVContext is null"); } } };
4,不知道别人怎么个调用的次序,我就是这个调用次序,是可以出来的 可能顺序不一样也能成功,不过1.7的demo里面是这个循序,,,,我在这之后 在开始直播的点击按钮的点击事件中 申请房间号roomid的 这个申请数据是向自己的后台申请的房间号,具体实现如下
/** * 向后台申请房间号 */ public void createRoomNum() { Log.i(TAG, "CreateRoomStep 1 : Apply AV room num"); new Thread() { @Override public void run() { super.run(); // String response = // HttpUtil.PostUrl(HttpUtil.GETROOMNUM_URL+HttpUtil.GETROOMNUM_URL_TOKEN+token+HttpUtil.GETROOMNUM_URL_CONTENT+mLiveTitleString+HttpUtil.GETROOMNUM_URL_BKTYPE+DemoConstants.BKTYPE_ZHIBO, // new ArrayList()); Listpairlist = new ArrayList (); pairlist.add(new BasicNameValuePair("token", token)); pairlist.add(new BasicNameValuePair("content", mLiveTitleString)); pairlist.add(new BasicNameValuePair("bkType", "3")); Log.i(TAG, "mLiveTitleString" + mLiveTitleString); String response = HttpUtil.PostUrl(HttpUtil.GETROOMNUM_URL_01, pairlist); Log.d(TAG, "createRoomNum response" + response); if (!response.endsWith("}")) { Log.e(TAG, "onSmsRegisterInfo response is not json style" + response); return; } JSONTokener jsonTokener = new JSONTokener(response); try { JSONObject object = (JSONObject) jsonTokener.nextValue(); JSONObject dataJsonObject = object .getJSONObject(Util.JSON_KEY_DATA); roomNum = dataJsonObject.getInt("bokeId"); myCoverPath=dataJsonObject.getString("coverPath"); myShareUrl=dataJsonObject.getString("shareUrl"); PreferenceUtil.putString(ReleaseLiveActivity.this, "bokeId", roomNum + ""); Log.d(TAG, "roomnum = " + roomNum); } catch (JSONException e) { e.printStackTrace(); } mmHandler.sendEmptyMessage(MSG_SHAREUM); } }.start(); }
5,如果已经申请到roomId的话 ,在申请完房间的回调接口中调用进入房间
/** * 加入直播房间 * * @param roomNum * 讨论房间号 */ private void createRoom(int roomNum) { Log.i(TAG, "CreateRoomStep 2: begin create a AVSDK Room "); if (Util.isNetworkAvailable(ctx)) { int room = roomNum; mQavsdkControl.enterRoom(room); mmHandler.sendEmptyMessageDelayed(MSG_CREATEROOM_TIMEOUT, MAX_TIMEOUT); Toast.makeText(ctx, "正在创建视频房间中...", Toast.LENGTH_SHORT).show(); refreshWaitingDialog(); } else { Toast.makeText(ctx, getString(R.string.notify_no_network), Toast.LENGTH_SHORT).show(); } }
在mQavsdkControl.enterRoom(room)中 往里面走就回到这个最终的方法:
/** * 创建房间 * * @param relationId 讨论组号 */ void enterRoom(int relationId) { Log.d(TAG, "WL_DEBUG enterRoom relationId = " + relationId); // int roomType = AVRoom.AV_ROOM_MULTI; // int roomId = 0; // int authBufferSize = 0;//权限位加密串长度;TODO:请业务侧填上自己的加密串长度。 // String controlRole = "";//角色名;多人房间专用。该角色名就是web端音视频参数配置工具所设置的角色名。TODO:请业务侧填根据自己的情况填上自己的角色名。 // int audioCategory = audioCat; QavsdkControl qavsdk = ((Myapplication) mContext).getQavsdkControl(); AVContext avContext = qavsdk.getAVContext(); long authBits = AVRoomMulti.AUTH_BITS_DEFAULT;//权限位;默认值是拥有所有权限。TODO:请业务侧填根据自己的情况填上权限位。 byte[] authBuffer = null;//权限位加密串;TODO:请业务侧填上自己的加密串。 // AVRoom.EnterRoomParam enterRoomParam = new AVRoomMulti.EnterRoomParam(relationId, authBits, authBuffer, "", audioCat, true); AVRoomMulti.EnterParam.Builder enterRoomParam = new AVRoomMulti.EnterParam.Builder(relationId); enterRoomParam.auth(authBits, authBuffer).avControlRole("Host").autoCreateRoom(true).isEnableMic(true).isEnableSpeaker(true);//;TODO:主播权限 所有权限 enterRoomParam.audioCategory(0).videoRecvMode(AVRoomMulti.VIDEO_RECV_MODE_SEMI_AUTO_RECV_CAMERA_VIDEO); if (avContext == null) { // Toast.makeText(mContext, "avContext is null", Toast.LENGTH_SHORT); Log.e(TAG, "enterRoom avContext is null"); // retryStartContext(); // try { // Thread.sleep(500); // } catch (InterruptedException e) { // e.printStackTrace(); // } // enterRoom(relationId); return; } avContext.enterRoom(mRoomDelegate, enterRoomParam.build()); Log.d(TAG, "enterRoom done !!!!"); // AVRoom.Info roomInfo = new AVRoom.Info(roomType, roomId, relationId, AVRoom.AV_MODE_AUDIO, "", authBits, authBuffer, authBufferSize, audioCategory, controlRole); // // create room // avContext.enterRoom(mRoomDelegate, roomInfo); mIsInCreateRoom = true; }
这个是进入房间回调
/* 房间回调 */ private AVRoomMulti.EventListener mRoomDelegate = new AVRoomMulti.EventListener() { // 创建房间成功回调 public void onEnterRoomComplete(int result) { Log.d(TAG, "WL_DEBUG mRoomDelegate.onEnterRoomComplete result = " + result); mIsInCreateRoom = false; mContext.sendBroadcast(new Intent(Util.ACTION_ROOM_CREATE_COMPLETE).putExtra(Util.EXTRA_AV_ERROR_RESULT, result)); } // 离开房间成功回调 public void onExitRoomComplete() { mIsInCloseRoom = false; mMemberList.clear(); mContext.sendBroadcast(new Intent(Util.ACTION_CLOSE_ROOM_COMPLETE)); } @Override public void onRoomDisconnect(int i) { mIsInCloseRoom = false; mMemberList.clear(); mContext.sendBroadcast(new Intent(Util.ACTION_CLOSE_ROOM_COMPLETE)); } @Override public void onEndpointsUpdateInfo(int i, String[] strings) { } @Override public void onPrivilegeDiffNotify(int privilege) { Log.d(TAG, "OnPrivilegeDiffNotify. privilege = " + privilege); } @Override public void onSemiAutoRecvCameraVideo(String[] strings) { } @Override public void onCameraSettingNotify(int i, int i1, int i2) { } @Override public void onRoomEvent(int i, int i1, Object o) { } };
之后创建聊天室
createGroup();
/** * IMSDK创建聊天室 */ private void createGroup() { Log.d(TAG, "CreateRoomStep 4 : Create IMChatRoom"); ArrayListlist = new ArrayList (); list.add(mSelfUserInfo.getUserPhone()); TIMGroupManager.getInstance().createGroup("ChatRoom", list, etContent.getText().toString(), new TIMValueCallBack () { @Override public void onError(int i, String s) { Log.e(TAG, "create group failed: " + i + " :" + s); Toast.makeText(ctx, "创建群失败:" + i + ":" + s, Toast.LENGTH_SHORT).show(); } @Override public void onSuccess(String s) { Log.d(TAG, "create group succ: " + s); groupid = s; PreferenceUtil .putString(ReleaseLiveActivity.this, "liveChatId", s); ctx.sendBroadcast(new Intent( Util.ACTION_CREATE_GROUP_ID_COMPLETE)); } }); }
同步个人信息到后台
/** * 同步个人信息到后台 */
public void createLive() { Log.i(TAG, "CreateRoomStep 5: upload info to user server "); new Thread() { @Override public void run() { super.run(); JSONObject object = new JSONObject(); try { object.put(Util.EXTRA_ROOM_NUM, roomNum); object.put(Util.EXTRA_USER_PHONE, userPhone); object.put(Util.EXTRA_LIVE_TITLE, mLiveTitleString); object.put(Util.EXTRA_GROUP_ID, groupid); object.put("imagetype", 2); Log.d(TAG, "userServer testhere " + roomNum + " phone " + userPhone + " roomtitle " + mLiveTitleString); System.out.println(object.toString()); ImageUtil tool = new ImageUtil(); mQavsdkApplication.setRoomName(mLiveTitleString); mQavsdkApplication.setRoomCoverPath(coverPath); Log.i(TAG, "CreateRoomStep 5.1: upload info to user server begin"); } catch (JSONException e) { e.printStackTrace(); } } }.start(); mmHandler.sendEmptyMessage(MSG_CREATEROOM_SERVER_OK); }
最后是跳转到AVActivity中去跳入到下一个页面中去
//调到AvActivity中去 startActivityForResult(new Intent(ReleaseLiveActivity.this, AvActivity.class) ); finish();
6,至此 准备工作已经完成 接下来是直播了把
/
/
/
7,在AVActivity中实现:
首先在这个页面里面 一开始的时候 你会发现很突然的就调用摄像头 很突然的就可能一些东西就出来,完全找不到这些调用的头
1,初始化QavsdkControl
if (mQavsdkControl.getAVContext() != null) { mQavsdkControl.onCreate((Application) getApplication(), findViewById(android.R.id.content)); } else { finish(); }
2,其实初始化QavsdkControl的里面是这样子的 初始化了好几个control,其中 重点介绍初始化mAVUIControl
public void onCreate(Context context, View contentView) { mAVUIControl = new AVUIControl(context, contentView.findViewById(R.id.av_video_layer_ui)); mAVVideoControl.initAVVideo(); mAVAudioControl.initAVAudio(); }
3这里是初始化AVUIControl的类
public AVUIControl(Context context, View rootView) { mContext = context; mRootView = rootView; mGraphicRenderMgr = GraphicRendererMgr.getInstance(); initQQGlView(); initCameraPreview(); initVideoParam(); mapViewAndIdentifier = new HashMap(); mQavsdkControl = ((Application) mContext).getQavsdkControl(); }
4,这个里面 你要注意这个方法
void initCameraPreview() { // SurfaceView localVideo = (SurfaceView) mRootView.findViewById(R.id.av_video_surfaceView); // SurfaceHolder holder = localVideo.getHolder(); // holder.addCallback(mSurfaceHolderListener); // holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);// 3.0以下必须在初始化时调用,否则不能启动预览 // localVideo.setZOrderMediaOverlay(true); WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); layoutParams.width = 1; layoutParams.height = 1; layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; // layoutParams.flags |= LayoutParams.FLAG_NOT_TOUCHABLE; layoutParams.format = PixelFormat.TRANSLUCENT; layoutParams.windowAnimations = 0;// android.R.style.Animation_Toast; layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST; layoutParams.gravity = Gravity.LEFT | Gravity.TOP; //layoutParams.setTitle("Toast"); try { mSurfaceView = new SurfaceView(mContext); SurfaceHolder holder = mSurfaceView.getHolder(); holder.addCallback(mSurfaceHolderListener); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);// 3.0以下必须在初始化时调用,否则不能启动预览 mSurfaceView.setZOrderMediaOverlay(true); windowManager.addView(mSurfaceView, layoutParams); } catch (IllegalStateException e) { windowManager.updateViewLayout(mSurfaceView, layoutParams); if (QLog.isColorLevel()) { QLog.d(TAG, QLog.CLR, "add camera surface view fail: IllegalStateException." + e); } } catch (Exception e) { if (QLog.isColorLevel()) { QLog.d(TAG, QLog.CLR, "add camera surface view fail." + e); } } Log.i(TAG, "initCameraPreview"); }
5,其中
holder.addCallback(mSurfaceHolderListener);
这个方法是创建摄像头的接口回调,它的回调接口为
private SurfaceHolder.Callback mSurfaceHolderListener = new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { mContext.sendBroadcast(new Intent(Util.ACTION_SURFACE_CREATED)); mCameraSurfaceCreated = true; QavsdkControl qavsdk = ((Application) mContext).getQavsdkControl(); qavsdk.getAVContext().setRenderMgrAndHolder(mGraphicRenderMgr, holder); Log.e("memoryLeak", "memoryLeak surfaceCreated"); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { if (holder.getSurface() == null) { return; } holder.setFixedSize(width, height); Log.e("memoryLeak", "memoryLeak surfaceChanged"); } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.e("memoryLeak", "memoryLeak surfaceDestroyed"); } };
这个创建按摄像头成功的回调成功的方法中 发送广播进行下面的操作
if (action.equals(Util.ACTION_SURFACE_CREATED)) { locateCameraPreview(); wakeLock.acquire(); // mQavsdkControl.toggleEnableCamera(); if (mSelfUserInfo.isCreater() == true) { initTIMGroup(); mEditTextInputMsg.setClickable(true); mIsSuccess = true; mVideoTimer = new Timer(true); mVideoTimerTask = new VideoTimerTask(); mVideoTimer.schedule(mVideoTimerTask, 1000, 1000); mQavsdkControl.toggleEnableCamera(); boolean isEnable = mQavsdkControl.getIsEnableCamera(); refreshCameraUI(); if (mOnOffCameraErrorCode != AVError.AV_OK) { showDialog(isEnable ? DIALOG_OFF_CAMERA_FAILED : DIALOG_ON_CAMERA_FAILED); mQavsdkControl.setIsInOnOffCamera(false); refreshCameraUI(); } // Log.d(TAG, "getMemberInfo isHandleMemberRoomSuccess " + // mQavsdkApplication.isHandleMemberRoomSuccess()); // if (mQavsdkApplication.isHandleMemberRoomSuccess()) { // Log.d(TAG, "getMemberInfo isHandleMemberRoomSuccess " + // mQavsdkApplication.isHandleMemberRoomSuccess() + // " yes do it normally "); mHandler.sendEmptyMessageDelayed(GET_ROOM_INFO, 0); // } else { // Log.w(TAG, "getMemberInfo isHandleMemberRoomSuccess " + // mQavsdkApplication.isHandleMemberRoomSuccess() + // " no wait for call"); // } mQavsdkControl.setRequestCount(0); // 上报主播心跳 mHeartClickTimer.schedule(mHeartClickTask, 1000, 10000); }
这样 直播可以实现了,
这个里面 其中
mVideoTimer = new Timer(true); mVideoTimerTask = new VideoTimerTask(); mVideoTimer.schedule(mVideoTimerTask, 1000, 1000);这个方法 是研究的 时间的
这个方法
initTIMGroup();
里面的代码如下
private void initTIMGroup() { Log.d(TAG, "initTIMGroup groupId" + groupId); if (groupId != null) { mConversation = TIMManager.getInstance().getConversation( TIMConversationType.Group, groupId); Log.d(TAG, "initTIMGroup mConversation" + mConversation); } else { } mSystemConversation = TIMManager.getInstance().getConversation( TIMConversationType.System, ""); mArrayListChatEntity = new ArrayList这里面主要是研究的信息发送功能的相当于一个初始化把(); mChatMsgListAdapter = new ChatMsgListAdapter(this, mArrayListChatEntity, mMemberList, mSelfUserInfo); mListViewMsgItems.setAdapter(mChatMsgListAdapter); if (mListViewMsgItems.getCount() > 1) mListViewMsgItems.setSelection(mListViewMsgItems.getCount() - 1); mListViewMsgItems.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { hideMsgIputKeyboard(); mEditTextInputMsg.setVisibility(View.VISIBLE); return false; } }); mListViewMsgItems .setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { switch (scrollState) { case AbsListView.OnScrollListener.SCROLL_STATE_IDLE: if (view.getFirstVisiblePosition() == 0 && !mIsLoading && bMore) { bNeverLoadMore = false; mIsLoading = true; mLoadMsgNum += MAX_PAGE_NUM; // getMessage(); } break; } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }); // getMessage(); TIMManager.getInstance().addMessageListener(msgListener); mChatTimer = new Timer(true); time = System.currentTimeMillis() / 1000; mChatTimerTask = new ChatTimerTask(); mChatTimer.schedule(mChatTimerTask, 8000, 2000); }
当然还有一些其他别的功能 ,但是大体上这么多,其他像美颜功能 还有邀请直播功能,自行去研究...........
笔者到公司的时候,观看端已经做好,所以这里发微博只是播放端,但是观看端的话,笔者正要研究,,,播放端比观看端逻辑代码要简单....在这里 就不叙述了