diff --git a/QNDroidRTCDemo/app/build.gradle b/QNDroidRTCDemo/app/build.gradle index 58d6c1e..bfa959d 100644 --- a/QNDroidRTCDemo/app/build.gradle +++ b/QNDroidRTCDemo/app/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.qiniu.droid.rtc.demo" minSdkVersion 18 targetSdkVersion 28 - versionCode 27 - versionName "2.4.0" + versionCode 28 + versionName "2.5.0" buildConfigField "long", "BUILD_TIMESTAMP", System.currentTimeMillis() + "L" } @@ -36,6 +36,7 @@ dependencies { implementation 'de.greenrobot:eventbus:2.4.0' implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.qiniu:happy-dns:0.2.16' // QNDroidRTCLibrary if (buildWithQNDroidRTCLibrary) { diff --git a/QNDroidRTCDemo/app/libs/qndroid-rtc-2.4.0.jar b/QNDroidRTCDemo/app/libs/qndroid-rtc-2.4.0.jar deleted file mode 100644 index 9a11b00..0000000 Binary files a/QNDroidRTCDemo/app/libs/qndroid-rtc-2.4.0.jar and /dev/null differ diff --git a/QNDroidRTCDemo/app/libs/qndroid-rtc-2.5.0.jar b/QNDroidRTCDemo/app/libs/qndroid-rtc-2.5.0.jar new file mode 100644 index 0000000..384e47b Binary files /dev/null and b/QNDroidRTCDemo/app/libs/qndroid-rtc-2.5.0.jar differ diff --git a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/activity/RoomActivity.java b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/activity/RoomActivity.java index e551579..7b1dc8a 100644 --- a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/activity/RoomActivity.java +++ b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/activity/RoomActivity.java @@ -42,9 +42,9 @@ import com.qiniu.droid.rtc.QNVideoFormat; import com.qiniu.droid.rtc.demo.R; import com.qiniu.droid.rtc.demo.fragment.ControlFragment; -import com.qiniu.droid.rtc.demo.model.UserTrack; -import com.qiniu.droid.rtc.demo.model.RTCUser; -import com.qiniu.droid.rtc.demo.model.RoomUserList; +import com.qiniu.droid.rtc.demo.model.RTCRoomUsersMergeOption; +import com.qiniu.droid.rtc.demo.model.RTCTrackMergeOption; +import com.qiniu.droid.rtc.demo.model.RTCUserMergeOptions; import com.qiniu.droid.rtc.demo.ui.CircleTextView; import com.qiniu.droid.rtc.demo.ui.MergeLayoutConfigView; import com.qiniu.droid.rtc.demo.ui.UserTrackView; @@ -53,7 +53,9 @@ import com.qiniu.droid.rtc.demo.utils.SplitUtils; import com.qiniu.droid.rtc.demo.utils.ToastUtils; import com.qiniu.droid.rtc.demo.utils.TrackWindowMgr; +import com.qiniu.droid.rtc.demo.utils.Utils; import com.qiniu.droid.rtc.model.QNAudioDevice; +import com.qiniu.droid.rtc.model.QNForwardJob; import com.qiniu.droid.rtc.model.QNMergeJob; import com.qiniu.droid.rtc.model.QNMergeTrackOption; @@ -117,7 +119,8 @@ public class RoomActivity extends Activity implements QNRTCEngineEventListener, /** * 合流相关 * - * 注意:一个房间仅需要一个用户可以配置合流布局即可,该用户可以基于 SDK 提供的远端用户相关回调对远端用户的动态进行监听, + * 注意: + * 一个房间仅需要一个用户可以配置合流布局即可,该用户可以基于 SDK 提供的远端用户相关回调对远端用户的动态进行监听, * 进而进行合流布局的实时更改。 * * demo 中默认 userId 为 "admin" 的用户可以控制合流布局的配置 @@ -125,9 +128,9 @@ public class RoomActivity extends Activity implements QNRTCEngineEventListener, private MergeLayoutConfigView mMergeLayoutConfigView; private PopupWindow mPopWindow; private UserListAdapter mUserListAdapter; - private RoomUserList mRoomUserList; - private RTCUser mChooseUser; - private volatile boolean mIsStreaming; + private RTCRoomUsersMergeOption mRoomUsersMergeOption; + private RTCUserMergeOptions mChooseUser; + private volatile boolean mIsMergeJobStreaming; /** * 如果 QNMergeJob 为 null,则表示使用默认合流任务 * @@ -137,6 +140,34 @@ public class RoomActivity extends Activity implements QNRTCEngineEventListener, */ private QNMergeJob mCurrentMergeJob; + /** + * 单路转推相关 + * + * 注意: + * 1. 单路转推仅支持配置一路音频和一路视频 + * 2. 单路转推场景需要在初始化的时候保证配置了 "固定分辨率"{@link QNRTCSetting#setMaintainResolution} 选项的开启,否则会出问题!!! + * + * demo 中默认 userId 为 "admin" 的用户可以开启单路转推功能 + */ + private QNForwardJob mForwardJob; + private volatile boolean mIsForwardJobStreaming; + + /** + * 如果您的场景包括合流转推和单路转推的切换,那么务必维护一个 serialNum 的参数,代表流的优先级, + * 使其不断自增来实现 rtmp 流的无缝切换,否则可能会出现抢流的现象 + * + * QNMergeJob 以及 QNForwardJob 中 publishUrl 的格式为:rtmp://domain/app/stream?serialnum=xxx + * + * 切换流程推荐为: + * 1. 单路转推 -> 创建合流任务(以创建成功的回调为准) -> 停止单路转推 + * 2. 合流转推 -> 创建单路转推任务(以创建成功的回调为准) -> 停止合流转推 + * + * 注意: + * 1. 两种合流任务,推流地址应该保持一致,只有 serialnum 存在差异 + * 2. 在两种推流任务切换的场景下,合流任务务必使用自定义合流任务,并指定推流地址的 serialnum + */ + private int mSerialNum = 0; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -210,6 +241,7 @@ public boolean onLongClick(View v) { // 初始化合流相关配置 initMergeLayoutConfig(); + // 多人显示窗口管理类 mTrackWindowMgr = new TrackWindowMgr(mUserId, mScreenWidth, mScreenHeight, outMetrics.density , mEngine, mTrackWindowFullScreen, mTrackWindowsList); @@ -218,6 +250,54 @@ public boolean onLongClick(View v) { mTrackWindowMgr.addTrackInfo(mUserId, localTrackListExcludeScreenTrack); } + @Override + protected void onResume() { + super.onResume(); + // 开始视频采集 + mEngine.startCapture(); + if (!mIsJoinedRoom) { + // 加入房间 + mEngine.joinRoom(mRoomToken); + } + } + + @Override + protected void onPause() { + super.onPause(); + // 停止视频采集 + mEngine.stopCapture(); + if (mPopWindow != null && mPopWindow.isShowing()) { + mPopWindow.dismiss(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mEngine != null) { + if (mIsAdmin && mIsMergeJobStreaming) { + // 如果当前正在合流,则停止 + mEngine.stopMergeStream(mCurrentMergeJob == null ? null : mCurrentMergeJob.getMergeJobId()); + mIsMergeJobStreaming = false; + } + if (mIsAdmin && mIsForwardJobStreaming) { + mEngine.stopForwardJob(mForwardJob.getForwardJobId()); + mIsForwardJobStreaming = false; + } + // 释放相关资源 + mEngine.destroy(); + mEngine = null; + } + if (mTrackWindowFullScreen != null) { + mTrackWindowFullScreen.dispose(); + } + for (UserTrackView item : mTrackWindowsList) { + item.dispose(); + } + mTrackWindowsList.clear(); + mPopWindow = null; + } + /** * 初始化 QNRTCEngine */ @@ -248,7 +328,8 @@ private void initQNRTCEngine() { .setLowAudioSampleRateEnabled(isLowSampleRateEnabled) .setAEC3Enabled(isAec3Enabled) .setVideoEncodeFormat(format) - .setVideoPreviewFormat(format); + .setVideoPreviewFormat(format) + .setDnsManager(Utils.getDefaultDnsManager(getApplicationContext())); mEngine = QNRTCEngine.createEngine(getApplicationContext(), setting, this); } @@ -305,11 +386,14 @@ private void initLocalTrackInfoList() { } } + /** + * 合流转推、单路转推相关处理 + */ private void initMergeLayoutConfig() { mMergeLayoutConfigView = new MergeLayoutConfigView(this); mMergeLayoutConfigView.setRoomId(mRoomId); mUserListAdapter = new UserListAdapter(); - mRoomUserList = new RoomUserList(); + mRoomUsersMergeOption = new RTCRoomUsersMergeOption(); mMergeLayoutConfigView.getUserListView().setAdapter(mUserListAdapter); mMergeLayoutConfigView.setOnClickedListener(new MergeLayoutConfigView.OnClickedListener() { @Override @@ -319,11 +403,11 @@ public void onConfirmClicked() { } if (!mMergeLayoutConfigView.isStreamingEnabled()) { // 处理停止合流逻辑 - if (mIsStreaming) { + if (mIsMergeJobStreaming) { // 如果正在推流,则停止之前的合流任务 // 传入 null,则处理默认的合流任务 mEngine.stopMergeStream(mCurrentMergeJob == null ? null : mCurrentMergeJob.getMergeJobId()); - mIsStreaming = false; + mIsMergeJobStreaming = false; ToastUtils.s(RoomActivity.this, "停止合流!!!"); } else { ToastUtils.s(RoomActivity.this, "未开启合流,配置未生效!!!"); @@ -333,90 +417,37 @@ public void onConfirmClicked() { } return; } + // 如果当前正在进行单路转推任务,那么切换到合流任务的时候,务必使用自定义合流任务,否则可能会出现抢流的现象 + if (mIsForwardJobStreaming && !mMergeLayoutConfigView.isCustomMergeJob()) { + Utils.showAlertDialog(RoomActivity.this, getString(R.string.create_merge_job_warning)); + mMergeLayoutConfigView.updateStreamingStatus(false); + if (mPopWindow != null) { + mPopWindow.dismiss(); + } + return; + } if (mMergeLayoutConfigView.isCustomMergeJob()) { // 处理自定义合流任务的逻辑 QNMergeJob mergeJob = mMergeLayoutConfigView.getCustomMergeJob(); if (mergeJob != null) { // 如果正在推流,则停止之前的合流任务 - if (mIsStreaming) { + if (mIsMergeJobStreaming) { mEngine.stopMergeStream(mCurrentMergeJob == null ? null : mCurrentMergeJob.getMergeJobId()); } mCurrentMergeJob = mergeJob; // 创建自定义合流任务 mEngine.createMergeJob(mCurrentMergeJob); } - } - List userTracks = mMergeLayoutConfigView.updateMergeOptions(); - List addedTrackOptions = new ArrayList<>(); - List removedTrackOptions = new ArrayList<>(); - for (UserTrack item : userTracks) { - if (item.isTrackInclude()) { - addedTrackOptions.add(item.getQNMergeTrackOption()); - } else { - removedTrackOptions.add(item.getQNMergeTrackOption()); - } - } - if (!addedTrackOptions.isEmpty()) { - // 配置对应 tracks 的合流配置信息 - mEngine.setMergeStreamLayouts(addedTrackOptions, mCurrentMergeJob == null ? null : mCurrentMergeJob.getMergeJobId()); - } - if (!removedTrackOptions.isEmpty()) { - // 移除对应 tracks 的合流配置,移除后相应 track 的数据将不会参与合流 - mEngine.removeMergeStreamLayouts(removedTrackOptions, mCurrentMergeJob == null ? null : mCurrentMergeJob.getMergeJobId()); + } else { + setMergeStreamLayouts(); } if (mPopWindow != null) { mPopWindow.dismiss(); } - mIsStreaming = true; - ToastUtils.s(RoomActivity.this, "已发送合流配置,请等待合流画面生效"); } }); } - @Override - protected void onResume() { - super.onResume(); - // 开始视频采集 - mEngine.startCapture(); - if (!mIsJoinedRoom) { - // 加入房间 - mEngine.joinRoom(mRoomToken); - } - } - - @Override - protected void onPause() { - super.onPause(); - // 停止视频采集 - mEngine.stopCapture(); - if (mPopWindow != null && mPopWindow.isShowing()) { - mPopWindow.dismiss(); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (mEngine != null) { - if (mIsAdmin && mIsStreaming) { - // 如果当前正在合流,则停止 - mEngine.stopMergeStream(mCurrentMergeJob == null ? null : mCurrentMergeJob.getMergeJobId()); - mIsStreaming = false; - } - // 释放相关资源 - mEngine.destroy(); - mEngine = null; - } - if (mTrackWindowFullScreen != null) { - mTrackWindowFullScreen.dispose(); - } - for (UserTrackView item : mTrackWindowsList) { - item.dispose(); - } - mTrackWindowsList.clear(); - mPopWindow = null; - } - private void logAndToast(final String msg) { Log.d(TAG, msg); if (mLogToast != null) { @@ -478,7 +509,7 @@ private void resetMergeStream() { List configuredMergeTracksOptions = new ArrayList<>(); // video tracks merge layout options. - List remoteVideoTrackInfoList = mRoomUserList.getRTCVideoTracks(); + List remoteVideoTrackInfoList = mRoomUsersMergeOption.getRTCVideoMergeOptions(); if (!remoteVideoTrackInfoList.isEmpty()) { List mergeTrackOptions = SplitUtils.split(remoteVideoTrackInfoList.size(), mCurrentMergeJob == null ? QNAppServer.STREAMING_WIDTH : mCurrentMergeJob.getWidth(), @@ -489,47 +520,85 @@ private void resetMergeStream() { } for (int i = 0; i < mergeTrackOptions.size(); i++) { - UserTrack userTrack = remoteVideoTrackInfoList.get(i); + RTCTrackMergeOption trackMergeOption = remoteVideoTrackInfoList.get(i); - if (!userTrack.isTrackInclude()) { + if (!trackMergeOption.isTrackInclude()) { continue; } QNMergeTrackOption item = mergeTrackOptions.get(i); - userTrack.updateQNMergeTrackOption(item); - configuredMergeTracksOptions.add(userTrack.getQNMergeTrackOption()); + trackMergeOption.updateQNMergeTrackOption(item); + configuredMergeTracksOptions.add(trackMergeOption.getQNMergeTrackOption()); } } // audio tracks merge layout options - List remoteAudioTrackInfoList = mRoomUserList.getRTCAudioTracks(); + List remoteAudioTrackInfoList = mRoomUsersMergeOption.getRTCAudioTracks(); if (!remoteAudioTrackInfoList.isEmpty()) { - for (UserTrack userTrack : remoteAudioTrackInfoList) { - if (!userTrack.isTrackInclude()) { + for (RTCTrackMergeOption trackMergeOption : remoteAudioTrackInfoList) { + if (!trackMergeOption.isTrackInclude()) { continue; } - configuredMergeTracksOptions.add(userTrack.getQNMergeTrackOption()); + configuredMergeTracksOptions.add(trackMergeOption.getQNMergeTrackOption()); } } - if (mIsStreaming) { + if (mIsMergeJobStreaming) { mEngine.setMergeStreamLayouts(configuredMergeTracksOptions, mCurrentMergeJob == null ? null : mCurrentMergeJob.getMergeJobId()); } } private void userJoinedForStreaming(String userId, String userData) { - mRoomUserList.onUserJoined(userId, userData); + mRoomUsersMergeOption.onUserJoined(userId, userData); if (mUserListAdapter != null) { mUserListAdapter.notifyDataSetChanged(); } } private void userLeftForStreaming(String userId) { - mRoomUserList.onUserLeft(userId); + mRoomUsersMergeOption.onUserLeft(userId); if (mUserListAdapter != null) { mUserListAdapter.notifyDataSetChanged(); } } + private int updateSerialNum() { + mMergeLayoutConfigView.updateSerialNum(++mSerialNum); + return mSerialNum; + } + + /** + * 配置合流的布局信息 + * + * 如果使用的默认合流任务,则无需手动 createMergeJob,setMergeStreamLayouts 中 jobId 参数传 null 即可 + */ + private void setMergeStreamLayouts() { + // 配置合流布局信息 + List userTracks = mMergeLayoutConfigView.updateMergeOptions(); + List addedTrackOptions = new ArrayList<>(); + List removedTrackOptions = new ArrayList<>(); + for (RTCTrackMergeOption item : userTracks) { + if (item.isTrackInclude()) { + addedTrackOptions.add(item.getQNMergeTrackOption()); + } else { + removedTrackOptions.add(item.getQNMergeTrackOption()); + } + } + if (!addedTrackOptions.isEmpty()) { + // 配置对应 tracks 的合流配置信息 + mEngine.setMergeStreamLayouts(addedTrackOptions, mCurrentMergeJob == null ? null : mCurrentMergeJob.getMergeJobId()); + } + if (!removedTrackOptions.isEmpty()) { + // 移除对应 tracks 的合流配置,移除后相应 track 的数据将不会参与合流 + mEngine.removeMergeStreamLayouts(removedTrackOptions, mCurrentMergeJob == null ? null : mCurrentMergeJob.getMergeJobId()); + } + mIsMergeJobStreaming = true; + ToastUtils.s(RoomActivity.this, "已发送合流配置,请等待合流画面生效"); + } + + private void showAlertDialog(String text) { + + } + @TargetApi(19) private static int getSystemUiVisibility() { int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN; @@ -548,14 +617,15 @@ private static int getSystemUiVisibility() { public void onRoomStateChanged(QNRoomState state) { Log.i(TAG, "onRoomStateChanged:" + state.name()); switch (state) { - case RECONNECTING: - logAndToast(getString(R.string.reconnecting_to_room)); - mControlFragment.stopTimer(); + case IDLE: if (mIsAdmin) { - mRoomUserList.onTracksUnPublished(mUserId, mLocalTrackList); userLeftForStreaming(mUserId); } break; + case RECONNECTING: + logAndToast(getString(R.string.reconnecting_to_room)); + mControlFragment.stopTimer(); + break; case CONNECTED: if (mIsAdmin) { userJoinedForStreaming(mUserId, ""); @@ -569,9 +639,6 @@ public void onRoomStateChanged(QNRoomState state) { case RECONNECTED: logAndToast(getString(R.string.connected_to_room)); mControlFragment.startTimer(); - if (mIsAdmin) { - userJoinedForStreaming(mUserId, ""); - } break; case CONNECTING: logAndToast(getString(R.string.connecting_to, mRoomId)); @@ -625,7 +692,7 @@ public void onLocalPublished(List trackInfoList) { updateRemoteLogText("onLocalPublished"); mEngine.enableStatistics(); if (mIsAdmin) { - mRoomUserList.onTracksPublished(mUserId, mLocalTrackList); + mRoomUsersMergeOption.onTracksPublished(mUserId, mLocalTrackList); resetMergeStream(); } } @@ -639,7 +706,7 @@ public void onLocalPublished(List trackInfoList) { @Override public void onRemotePublished(String remoteUserId, List trackInfoList) { updateRemoteLogText("onRemotePublished:remoteUserId = " + remoteUserId); - mRoomUserList.onTracksPublished(remoteUserId, trackInfoList); + mRoomUsersMergeOption.onTracksPublished(remoteUserId, trackInfoList); // 如果希望在远端发布音视频的时候,自动配置合流,则可以在此处重新调用 setMergeStreamLayouts 进行配置 if (mIsAdmin) { resetMergeStream(); @@ -658,7 +725,7 @@ public void onRemoteUnpublished(final String remoteUserId, List tra if (mTrackWindowMgr != null) { mTrackWindowMgr.removeTrackInfo(remoteUserId, trackInfoList); } - mRoomUserList.onTracksUnPublished(remoteUserId, trackInfoList); + mRoomUsersMergeOption.onTracksUnPublished(remoteUserId, trackInfoList); if (mIsAdmin) { resetMergeStream(); } @@ -692,6 +759,16 @@ public void onSubscribed(String remoteUserId, List trackInfoList) { } } + /** + * 订阅远端用户 Track 的 QNTrackSubConfiguration 变化时会回调此方法 + * + * @param remoteUserId 远端用户 userId + * @param trackInfoList 订阅的远端用户 tracks 列表 + */ + @Override + public void onSubscribedProfileChanged(String remoteUserId, List trackInfoList) { + } + /** * 当自己被踢出房间时会回调此方法 * @@ -767,7 +844,36 @@ public void onAudioRouteChanged(QNAudioDevice routing) { */ @Override public void onCreateMergeJobSuccess(String mergeJobId) { + updateSerialNum(); ToastUtils.s(RoomActivity.this, "合流任务 " + mergeJobId + " 创建成功!"); + setMergeStreamLayouts(); + + // 取消单路转推 + if (mIsForwardJobStreaming) { + mEngine.stopForwardJob(mForwardJob.getForwardJobId()); + mIsForwardJobStreaming = false; + mControlFragment.updateForwardJobText(getString(R.string.forward_job_btn_text)); + } + } + + /** + * 当单路流转推任务创建成功的时候会回调此方法 + * + * @param forwardJobId 转推任务 ID + */ + @Override + public void onCreateForwardJobSuccess(String forwardJobId) { + updateSerialNum(); + mControlFragment.updateForwardJobText(getString(R.string.stop_forward_job_text)); + ToastUtils.s(RoomActivity.this, "单路转推任务 " + forwardJobId + " 创建成功!"); + mIsForwardJobStreaming = true; + + // 取消合流转推 + if (mIsMergeJobStreaming && mCurrentMergeJob != null) { + mEngine.stopMergeStream(mCurrentMergeJob.getMergeJobId()); + mIsMergeJobStreaming = false; + mMergeLayoutConfigView.updateStreamingStatus(false); + } } /** @@ -959,14 +1065,14 @@ public boolean onToggleBeauty() { @Override public void onCallStreamingConfig() { if (!mIsAdmin) { - ToastUtils.s(RoomActivity.this, "只有 \"admin\" 用户可以开启推流!!!"); + ToastUtils.s(RoomActivity.this, "只有 \"admin\" 用户可以开启合流转推!!!"); return; } //配置页 - if (mRoomUserList.size() == 0) { + if (mRoomUsersMergeOption.size() == 0) { return; } - mChooseUser = mRoomUserList.getRoomUserByPosition(0); + mChooseUser = mRoomUsersMergeOption.getRoomUserByPosition(0); mMergeLayoutConfigView.updateConfigInfo(mChooseUser); mMergeLayoutConfigView.updateMergeJobConfigInfo(); mUserListAdapter.notifyDataSetChanged(); @@ -978,6 +1084,42 @@ public void onCallStreamingConfig() { mPopWindow.showAtLocation(getWindow().getDecorView().getRootView(), Gravity.BOTTOM, 0, 0); } + @Override + public void onToggleForwardJob() { + if (!mIsAdmin) { + ToastUtils.s(RoomActivity.this, "只有 \"admin\" 用户可以开启单流转推!!!"); + return; + } + if (!mIsForwardJobStreaming) { + // 如果当前正在进行默认合流任务的转推,则不允许切换成单路转推,需要停止默认合流任务或者使用自定义合流任务 + if (mIsMergeJobStreaming && mCurrentMergeJob == null) { + Utils.showAlertDialog(this, getString(R.string.create_forward_job_warning)); + return; + } + if (mForwardJob == null) { + mForwardJob = new QNForwardJob(); + mForwardJob.setForwardJobId(mRoomId); + mForwardJob.setAudioTrack(mLocalAudioTrack); + switch (mCaptureMode) { + case Config.CAMERA_CAPTURE: + case Config.MUTI_TRACK_CAPTURE: + mForwardJob.setVideoTrack(mLocalVideoTrack); + break; + case Config.SCREEN_CAPTURE: + mForwardJob.setVideoTrack(mLocalScreenTrack); + break; + } + } + mForwardJob.setPublishUrl(String.format(getResources().getString(R.string.publish_url), mRoomId, mSerialNum)); + mEngine.createForwardJob(mForwardJob); + } else { + mEngine.stopForwardJob(mForwardJob.getForwardJobId()); + mIsForwardJobStreaming = false; + mControlFragment.updateForwardJobText(getString(R.string.forward_job_btn_text)); + ToastUtils.s(RoomActivity.this, "已停止 id=" + mForwardJob.getForwardJobId() + " 的单流转推!!!"); + } + } + /** * 合流配置相关 */ @@ -996,8 +1138,8 @@ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { @Override public void onBindViewHolder(final ViewHolder holder, int position) { - RTCUser rtcUser = mRoomUserList.getRoomUserByPosition(position); - String userId = rtcUser.getUserId(); + RTCUserMergeOptions RTCUserMergeOptions = mRoomUsersMergeOption.getRoomUserByPosition(position); + String userId = RTCUserMergeOptions.getUserId(); holder.username.setText(userId); holder.username.setCircleColor(mColor[position % 4]); if (mChooseUser != null && mChooseUser.getUserId().equals(userId)) { @@ -1008,7 +1150,7 @@ public void onBindViewHolder(final ViewHolder holder, int position) { holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - mChooseUser = mRoomUserList.getRoomUserByPosition(holder.getAdapterPosition()); + mChooseUser = mRoomUsersMergeOption.getRoomUserByPosition(holder.getAdapterPosition()); mMergeLayoutConfigView.updateConfigInfo(mChooseUser); notifyDataSetChanged(); } @@ -1017,7 +1159,7 @@ public void onClick(View v) { @Override public int getItemCount() { - return mRoomUserList.size(); + return mRoomUsersMergeOption.size(); } } diff --git a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/activity/SettingActivity.java b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/activity/SettingActivity.java index 05f583b..61b4907 100644 --- a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/activity/SettingActivity.java +++ b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/activity/SettingActivity.java @@ -94,7 +94,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { } }); - mVersionCodeTextView.setText(String.format(getString(R.string.version_code), getVersionDescription(), getBuildTimeDescription())); + mVersionCodeTextView.setText(String.format(getString(R.string.version_code), getVersionDescription(), getBuildTimeDescription(), getSdkVersion())); SharedPreferences preferences = getSharedPreferences(getString(R.string.app_name), Context.MODE_PRIVATE); mUserName = preferences.getString(Config.USER_NAME, ""); @@ -237,6 +237,10 @@ protected String getBuildTimeDescription() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA).format(BuildConfig.BUILD_TIMESTAMP); } + protected String getSdkVersion() { + return com.qiniu.droid.rtc.BuildConfig.VERSION_NAME + "-" + com.qiniu.droid.rtc.BuildConfig.GIT_HASH; + } + private boolean isTestMode() { // if (mAppIdEditText.getText().toString().compareTo(QNAppServer.TEST_MODE_APP_ID) == 0) { // return true; @@ -259,11 +263,9 @@ public void onCheckedChanged(RadioGroup group, int checkedId) { switch (group.getCheckedRadioButtonId()) { case R.id.hw_radio_button: mEncodeMode = Config.HW; - mMaintainResRadioGroup.setVisibility(View.GONE); break; case R.id.sw_radio_button: mEncodeMode = Config.SW; - mMaintainResRadioGroup.setVisibility(View.VISIBLE); break; case R.id.low_sample_rate_button: mSampleRatePos = Config.LOW_SAMPLE_RATE; diff --git a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/fragment/ControlFragment.java b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/fragment/ControlFragment.java index c868cee..15a71d2 100644 --- a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/fragment/ControlFragment.java +++ b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/fragment/ControlFragment.java @@ -27,8 +27,9 @@ public class ControlFragment extends Fragment { private ImageButton mToggleSpeakerButton; private ImageButton mToggleVideoButton; private ImageButton mLogShownButton; - private ImageButton mStreamingConfigButton; private LinearLayout mLogView; + private TextView mStreamingConfigButton; + private TextView mForwardJobButton; private TextView mLocalTextViewForVideo; private TextView mLocalTextViewForAudio; private TextView mRemoteTextView; @@ -57,6 +58,8 @@ public interface OnCallEvents { boolean onToggleBeauty(); void onCallStreamingConfig(); + + void onToggleForwardJob(); } public void setScreenCaptureEnabled(boolean isScreenCaptureEnabled) { @@ -81,6 +84,7 @@ public View onCreateView( mLogShownButton = (ImageButton) mControlView.findViewById(R.id.log_shown_button); mLogView = (LinearLayout) mControlView.findViewById(R.id.log_text); mStreamingConfigButton = mControlView.findViewById(R.id.streaming_config_button); + mForwardJobButton = mControlView.findViewById(R.id.forward_job_button); mLocalTextViewForVideo = (TextView) mControlView.findViewById(R.id.local_log_text_video); mLocalTextViewForVideo.setMovementMethod(ScrollingMovementMethod.getInstance()); mLocalTextViewForAudio = (TextView) mControlView.findViewById(R.id.local_log_text_audio); @@ -157,6 +161,13 @@ public void onClick(View v) { mCallEvents.onCallStreamingConfig(); } }); + + mForwardJobButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mCallEvents.onToggleForwardJob(); + } + }); return mControlView; } @@ -190,6 +201,12 @@ public void updateRemoteLogText(String logText) { } } + public void updateForwardJobText(String forwardJobText) { + if (mForwardJobButton != null) { + mForwardJobButton.setText(forwardJobText); + } + } + @Override public void onStart() { super.onStart(); diff --git a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/model/RTCRoomUsersMergeOption.java b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/model/RTCRoomUsersMergeOption.java new file mode 100644 index 0000000..6a82f89 --- /dev/null +++ b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/model/RTCRoomUsersMergeOption.java @@ -0,0 +1,80 @@ +package com.qiniu.droid.rtc.demo.model; + +import com.qiniu.droid.rtc.QNTrackInfo; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RTCRoomUsersMergeOption { + + private Map mRTCUserMap; + private List mRTCUsers; + private List mRTCVideoMergeOptions; + + public RTCRoomUsersMergeOption() { + mRTCUserMap = new HashMap<>(); + mRTCUsers = new ArrayList<>(); + mRTCVideoMergeOptions = new ArrayList<>(); + } + + public RTCUserMergeOptions getRoomUserByPosition(int pos) { + return mRTCUsers.get(pos); + } + + public RTCUserMergeOptions getRoomUserByUserId(String userId) { + return mRTCUserMap.get(userId); + } + + public List getRTCVideoMergeOptions() { + return mRTCVideoMergeOptions; + } + + public List getRTCAudioTracks() { + List rtcAudioTracks = new ArrayList<>(); + for (RTCUserMergeOptions item : mRTCUsers) { + if (item.getAudioTrack() != null) { + rtcAudioTracks.add(item.getAudioTrack()); + } + } + return rtcAudioTracks; + } + + public void onUserJoined(String userId, String userData) { + if (mRTCUserMap.get(userId) == null) { + RTCUserMergeOptions userMergeOptions = new RTCUserMergeOptions(userId, userData); + mRTCUserMap.put(userId, userMergeOptions); + mRTCUsers.add(userMergeOptions); + } + } + + public void onUserLeft(String userId) { + RTCUserMergeOptions userMergeOptions = mRTCUserMap.remove(userId); + if (userMergeOptions != null) { + mRTCUsers.remove(userMergeOptions); + } + } + + public void onTracksPublished(String userId, List trackInfoList) { + RTCUserMergeOptions userMergeOptions = getRoomUserByUserId(userId); + if (userMergeOptions == null) { + return; + } + List userVideoTracks = userMergeOptions.addTracks(trackInfoList); + mRTCVideoMergeOptions.addAll(userVideoTracks); + } + + public void onTracksUnPublished(String userId, List trackInfoList) { + RTCUserMergeOptions userMergeOptions = getRoomUserByUserId(userId); + if (userMergeOptions == null) { + return; + } + List userVideoTracks = userMergeOptions.removeTracks(trackInfoList); + mRTCVideoMergeOptions.removeAll(userVideoTracks); + } + + public int size() { + return mRTCUsers.size(); + } +} diff --git a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/model/RTCTrackMergeOption.java b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/model/RTCTrackMergeOption.java new file mode 100644 index 0000000..96fe849 --- /dev/null +++ b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/model/RTCTrackMergeOption.java @@ -0,0 +1,69 @@ +package com.qiniu.droid.rtc.demo.model; + +import com.qiniu.droid.rtc.QNTrackInfo; +import com.qiniu.droid.rtc.demo.utils.QNAppServer; +import com.qiniu.droid.rtc.model.QNMergeTrackOption; + +public class RTCTrackMergeOption { + + private final String mTrackId; + private final QNTrackInfo mQNTrackInfo; + + private boolean mTrackInclude = true; + private final QNMergeTrackOption mQNMergeTrackOption; + + public RTCTrackMergeOption(QNTrackInfo QNTrackInfo) { + mQNTrackInfo = QNTrackInfo; + mTrackId = mQNTrackInfo.getTrackId(); + + mQNMergeTrackOption = new QNMergeTrackOption(); + mQNMergeTrackOption.setWidth(QNAppServer.STREAMING_WIDTH); + mQNMergeTrackOption.setHeight(QNAppServer.STREAMING_HEIGHT); + mQNMergeTrackOption.setTrackId(mTrackId); + } + + public String getTrackId() { + return mTrackId; + } + + public QNTrackInfo getQNTrackInfo() { + return mQNTrackInfo; + } + + public QNMergeTrackOption getQNMergeTrackOption() { + return mQNMergeTrackOption; + } + + public boolean isTrackInclude() { + return mTrackInclude; + } + + public void setTrackInclude(boolean trackInclude) { + mTrackInclude = trackInclude; + } + + public void updateQNMergeTrackOption(QNMergeTrackOption option) { + if (option == null) { + return; + } + mQNMergeTrackOption.setX(option.getX()); + mQNMergeTrackOption.setY(option.getY()); + mQNMergeTrackOption.setZ(option.getZ()); + mQNMergeTrackOption.setWidth(option.getWidth()); + mQNMergeTrackOption.setHeight(option.getHeight()); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof RTCTrackMergeOption) { + return mQNTrackInfo.equals(((RTCTrackMergeOption) obj).mQNTrackInfo); + } else { + return false; + } + } + + @Override + public int hashCode() { + return mQNTrackInfo.hashCode(); + } +} diff --git a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/model/RTCUserMergeOptions.java b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/model/RTCUserMergeOptions.java new file mode 100644 index 0000000..f729acb --- /dev/null +++ b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/model/RTCUserMergeOptions.java @@ -0,0 +1,72 @@ +package com.qiniu.droid.rtc.demo.model; + +import com.qiniu.droid.rtc.QNRTCUser; +import com.qiniu.droid.rtc.QNTrackInfo; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +public class RTCUserMergeOptions extends QNRTCUser { + + private RTCTrackMergeOption mAudioTrack; + private List mVideoTracks = new LinkedList<>(); + + public RTCUserMergeOptions(String userId, String userData) { + super(userId, userData); + } + + public RTCTrackMergeOption getAudioTrack() { + return mAudioTrack; + } + + public List getVideoTracks() { + return mVideoTracks; + } + + public List addTracks(List trackInfoList) { + List videoTracks = new ArrayList<>(); + for (QNTrackInfo item : trackInfoList) { + RTCTrackMergeOption newVideoTrack = addTrack(item); + if (newVideoTrack != null) { + videoTracks.add(newVideoTrack); + } + } + return videoTracks; + } + + private RTCTrackMergeOption addTrack(QNTrackInfo trackInfo) { + if (trackInfo.isAudio()) { + mAudioTrack = new RTCTrackMergeOption(trackInfo); + return null; + } else { + RTCTrackMergeOption newVideoTrack = new RTCTrackMergeOption(trackInfo); + // replace + mVideoTracks.remove(newVideoTrack); + mVideoTracks.add(newVideoTrack); + return newVideoTrack; + } + } + + public List removeTracks(List trackInfoList) { + List videoTracks = new ArrayList<>(); + for (QNTrackInfo item : trackInfoList) { + RTCTrackMergeOption removedVideoTrack = removeTracks(item); + if (removedVideoTrack != null) { + videoTracks.add(removedVideoTrack); + } + } + return videoTracks; + } + + private RTCTrackMergeOption removeTracks(QNTrackInfo trackInfo) { + if (trackInfo.isAudio()) { + mAudioTrack = null; + return null; + } else { + RTCTrackMergeOption newUserTrack = new RTCTrackMergeOption(trackInfo); + mVideoTracks.remove(newUserTrack); + return newUserTrack; + } + } +} diff --git a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/ui/MergeLayoutConfigView.java b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/ui/MergeLayoutConfigView.java index 37a2c87..3ffc26d 100644 --- a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/ui/MergeLayoutConfigView.java +++ b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/ui/MergeLayoutConfigView.java @@ -6,6 +6,7 @@ import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.AttributeSet; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; @@ -18,8 +19,8 @@ import android.widget.TextView; import com.qiniu.droid.rtc.demo.R; -import com.qiniu.droid.rtc.demo.model.UserTrack; -import com.qiniu.droid.rtc.demo.model.RTCUser; +import com.qiniu.droid.rtc.demo.model.RTCTrackMergeOption; +import com.qiniu.droid.rtc.demo.model.RTCUserMergeOptions; import com.qiniu.droid.rtc.demo.utils.ToastUtils; import com.qiniu.droid.rtc.model.QNMergeJob; import com.qiniu.droid.rtc.model.QNMergeTrackOption; @@ -50,9 +51,9 @@ public class MergeLayoutConfigView extends FrameLayout { private Switch mAudioSwitch; private Button mBtnConfirm; - private UserTrack mUserAudioTrack; - private UserTrack mUserFirstVideoTrack; - private UserTrack mUserSecondVideoTrack; + private RTCTrackMergeOption mUserAudioTrack; + private RTCTrackMergeOption mUserFirstVideoTrack; + private RTCTrackMergeOption mUserSecondVideoTrack; private LinearLayout mCustomMergeJobLayout; private Switch mCustomMergeJobSwitch; @@ -71,6 +72,7 @@ public class MergeLayoutConfigView extends FrameLayout { private String mRoomId; private boolean mIsStreamingEnabled; private boolean mIsCustomMergeJobEnabled; + private volatile int mSerialNum; public interface OnClickedListener { void onConfirmClicked(); @@ -97,86 +99,6 @@ public MergeLayoutConfigView(Context context, AttributeSet attrs, int defStyleAt init(context); } - private void init(Context context) { - if (mInit) { - return; - } - mInit = true; - View view = LayoutInflater.from(context).inflate(R.layout.layout_config_view, this, true); - intView(view); - } - - private void intView(View view) { - mUserListView = view.findViewById(R.id.user_list_view); - LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); - linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); - mUserListView.setLayoutManager(linearLayoutManager); - - mStreamingEnableSwitch = view.findViewById(R.id.streaming_enable_switch); - mCustomMergeJobLayout = view.findViewById(R.id.merge_job_layout); - mCustomMergeJobSwitch = view.findViewById(R.id.custom_mergejob_switch); - mFirstVideoSwitch = view.findViewById(R.id.first_video_switch); - mFirstEditTextX = view.findViewById(R.id.first_x_edit_text); - mFirstEditTextY = view.findViewById(R.id.first_y_edit_text); - mFirstEditTextZ = view.findViewById(R.id.first_z_edit_text); - mFirstEditTextWidth = view.findViewById(R.id.first_width_edit_text); - mFirstEditTextHeight = view.findViewById(R.id.first_height_edit_text); - - mSecondVideoSwitch = view.findViewById(R.id.second_video_switch); - mSecondEditTextX = view.findViewById(R.id.second_x_edit_text); - mSecondEditTextY = view.findViewById(R.id.second_y_edit_text); - mSecondEditTextZ = view.findViewById(R.id.second_z_edit_text); - mSecondEditTextWidth = view.findViewById(R.id.second_width_edit_text); - mSecondEditTextHeight = view.findViewById(R.id.second_height_edit_text); - - mAudioSwitch = view.findViewById(R.id.audio_switch); - mBtnConfirm = view.findViewById(R.id.btn_confirm); - mBtnConfirm.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mIsStreamingEnabled = mStreamingEnableSwitch.isChecked(); - mIsCustomMergeJobEnabled = mCustomMergeJobSwitch.isChecked(); - if (mOnClickedListener != null) { - mOnClickedListener.onConfirmClicked(); - } - } - }); - - mPublishUrlText = view.findViewById(R.id.publish_url_text); - mStreamWidthText = view.findViewById(R.id.stream_width); - mStreamHeightText = view.findViewById(R.id.stream_height); - mStreamFpsText = view.findViewById(R.id.stream_fps); - mCustomJobIdText = view.findViewById(R.id.edit_job_id); - mStreamBitrateText = view.findViewById(R.id.stream_bitrate); - mStreamMinBitrateText = view.findViewById(R.id.stream_bitrate_min); - mStreamMaxBitrateText = view.findViewById(R.id.stream_bitrate_max); - mStretchModeRadioGroup = view.findViewById(R.id.stretch_mode_radio_button); - - mStretchModeRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(RadioGroup group, int checkedId) { - switch (checkedId) { - case R.id.radio_aspect_fill: - mStretchMode = QNStretchMode.ASPECT_FILL; - break; - case R.id.radio_aspect_fit: - mStretchMode = QNStretchMode.ASPECT_FIT; - break; - case R.id.radio_scale_to_fit: - mStretchMode = QNStretchMode.SCALE_TO_FIT; - break; - } - } - }); - - mCustomMergeJobSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mCustomMergeJobLayout.setVisibility(isChecked ? VISIBLE : GONE); - } - }); - } - public void setOnClickedListener(OnClickedListener listener) { mOnClickedListener = listener; } @@ -184,7 +106,7 @@ public void setOnClickedListener(OnClickedListener listener) { public void setRoomId(String roomId) { mRoomId = roomId; mCustomJobIdText.setText(mRoomId); - String publishUrl = String.format(getResources().getString(R.string.publish_url), mRoomId); + String publishUrl = String.format(getResources().getString(R.string.publish_url), mRoomId, mSerialNum); mPublishUrlText.setText(publishUrl); } @@ -215,7 +137,7 @@ public boolean isCustomMergeJob() { * * @param chooseUser 对应 user */ - public void updateConfigInfo(RTCUser chooseUser) { + public void updateConfigInfo(RTCUserMergeOptions chooseUser) { if (chooseUser == null) { return; } @@ -223,7 +145,7 @@ public void updateConfigInfo(RTCUser chooseUser) { mUserAudioTrack = chooseUser.getAudioTrack(); updateSwitchState(mUserAudioTrack, mAudioSwitch); - List videoTracks = chooseUser.getVideoTracks(); + List videoTracks = chooseUser.getVideoTracks(); if (videoTracks.isEmpty()) { setFirstRemoteTrack(null); setSecondRemoteTrack(null); @@ -242,8 +164,8 @@ public void updateConfigInfo(RTCUser chooseUser) { * * @return 选中用户的合流配置信息 */ - public List updateMergeOptions() { - List result = new ArrayList<>(); + public List updateMergeOptions() { + List result = new ArrayList<>(); if (mUserAudioTrack != null) { mUserAudioTrack.setTrackInclude(mAudioSwitch.isChecked()); result.add(mUserAudioTrack); @@ -321,6 +243,8 @@ public void updateMergeJobConfigInfo() { mStreamingEnableSwitch.setChecked(mIsStreamingEnabled); mCustomMergeJobSwitch.setChecked(mCurrentMergeJob != null && mIsCustomMergeJobEnabled); mCustomMergeJobLayout.setVisibility(mCustomMergeJobSwitch.isChecked() ? VISIBLE : GONE); + String publishUrl = String.format(getResources().getString(R.string.publish_url), mRoomId, mSerialNum); + mPublishUrlText.setText(publishUrl); if (mIsCustomMergeJobEnabled) { mStreamWidthText.setText(String.valueOf(mCurrentMergeJob != null ? mCurrentMergeJob.getWidth() : 480)); mStreamHeightText.setText(String.valueOf(mCurrentMergeJob != null ? mCurrentMergeJob.getHeight() : 848)); @@ -347,9 +271,98 @@ public void updateMergeJobConfigInfo() { } } - private void updateSwitchState(UserTrack userTrack, Switch switchButton) { - if (userTrack != null) { - switchButton.setChecked(userTrack.isTrackInclude()); + public void updateSerialNum(int serialNum) { + mSerialNum = serialNum; + } + + public void updateStreamingStatus(boolean isStreaming) { + mIsStreamingEnabled = isStreaming; + mStreamingEnableSwitch.setChecked(isStreaming); + } + + private void init(Context context) { + if (mInit) { + return; + } + mInit = true; + View view = LayoutInflater.from(context).inflate(R.layout.layout_config_view, this, true); + intView(view); + } + + private void intView(View view) { + mUserListView = view.findViewById(R.id.user_list_view); + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); + linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); + mUserListView.setLayoutManager(linearLayoutManager); + + mStreamingEnableSwitch = view.findViewById(R.id.streaming_enable_switch); + mCustomMergeJobLayout = view.findViewById(R.id.merge_job_layout); + mCustomMergeJobSwitch = view.findViewById(R.id.custom_mergejob_switch); + mFirstVideoSwitch = view.findViewById(R.id.first_video_switch); + mFirstEditTextX = view.findViewById(R.id.first_x_edit_text); + mFirstEditTextY = view.findViewById(R.id.first_y_edit_text); + mFirstEditTextZ = view.findViewById(R.id.first_z_edit_text); + mFirstEditTextWidth = view.findViewById(R.id.first_width_edit_text); + mFirstEditTextHeight = view.findViewById(R.id.first_height_edit_text); + + mSecondVideoSwitch = view.findViewById(R.id.second_video_switch); + mSecondEditTextX = view.findViewById(R.id.second_x_edit_text); + mSecondEditTextY = view.findViewById(R.id.second_y_edit_text); + mSecondEditTextZ = view.findViewById(R.id.second_z_edit_text); + mSecondEditTextWidth = view.findViewById(R.id.second_width_edit_text); + mSecondEditTextHeight = view.findViewById(R.id.second_height_edit_text); + + mAudioSwitch = view.findViewById(R.id.audio_switch); + mBtnConfirm = view.findViewById(R.id.btn_confirm); + mBtnConfirm.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mIsStreamingEnabled = mStreamingEnableSwitch.isChecked(); + mIsCustomMergeJobEnabled = mCustomMergeJobSwitch.isChecked(); + if (mOnClickedListener != null) { + mOnClickedListener.onConfirmClicked(); + } + } + }); + + mPublishUrlText = view.findViewById(R.id.publish_url_text); + mStreamWidthText = view.findViewById(R.id.stream_width); + mStreamHeightText = view.findViewById(R.id.stream_height); + mStreamFpsText = view.findViewById(R.id.stream_fps); + mCustomJobIdText = view.findViewById(R.id.edit_job_id); + mStreamBitrateText = view.findViewById(R.id.stream_bitrate); + mStreamMinBitrateText = view.findViewById(R.id.stream_bitrate_min); + mStreamMaxBitrateText = view.findViewById(R.id.stream_bitrate_max); + mStretchModeRadioGroup = view.findViewById(R.id.stretch_mode_radio_button); + + mStretchModeRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + switch (checkedId) { + case R.id.radio_aspect_fill: + mStretchMode = QNStretchMode.ASPECT_FILL; + break; + case R.id.radio_aspect_fit: + mStretchMode = QNStretchMode.ASPECT_FIT; + break; + case R.id.radio_scale_to_fit: + mStretchMode = QNStretchMode.SCALE_TO_FIT; + break; + } + } + }); + + mCustomMergeJobSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mCustomMergeJobLayout.setVisibility(isChecked ? VISIBLE : GONE); + } + }); + } + + private void updateSwitchState(RTCTrackMergeOption trackMergeOption, Switch switchButton) { + if (trackMergeOption != null) { + switchButton.setChecked(trackMergeOption.isTrackInclude()); switchButton.setEnabled(true); } else { switchButton.setChecked(true); @@ -358,7 +371,7 @@ private void updateSwitchState(UserTrack userTrack, Switch switchButton) { } - private void setFirstRemoteTrack(UserTrack videoTrack) { + private void setFirstRemoteTrack(RTCTrackMergeOption videoTrack) { mUserFirstVideoTrack = videoTrack; updateSwitchState(mUserFirstVideoTrack, mFirstVideoSwitch); @@ -398,7 +411,7 @@ private void setFirstRemoteTrack(UserTrack videoTrack) { } } - private void setSecondRemoteTrack(UserTrack videoTrack) { + private void setSecondRemoteTrack(RTCTrackMergeOption videoTrack) { mUserSecondVideoTrack = videoTrack; updateSwitchState(mUserSecondVideoTrack, mSecondVideoSwitch); boolean hasTrack = mUserSecondVideoTrack != null; diff --git a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/utils/Config.java b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/utils/Config.java index 123cff4..0a65516 100644 --- a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/utils/Config.java +++ b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/utils/Config.java @@ -60,9 +60,9 @@ public class Config { }; public static int[] DEFAULT_BITRATE = { - 400 * 1000, // (200 - 400kbps) - 800 * 1000, // (500 - 800kbps) - 1200 * 1000, // (800 - 1200kbps) - 2000 * 1000 // (1500 - 2000kbps) + 600 * 1000, // (500 - 600kbps) + 1000 * 1000, // (900 - 1200kbps) + 1500 * 1000, // (1400 - 1500kbps) + 2000 * 1000 // (1800 - 2000kbps) }; } diff --git a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/utils/TrackWindowMgr.java b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/utils/TrackWindowMgr.java index 4ba02ed..6edee01 100644 --- a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/utils/TrackWindowMgr.java +++ b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/utils/TrackWindowMgr.java @@ -312,5 +312,6 @@ public void reset() { for (String userId : users) { removeTrackWindow(userId); } + mTrackWindowP2PMode = null; } } diff --git a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/utils/Utils.java b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/utils/Utils.java index 8af035d..a796d53 100644 --- a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/utils/Utils.java +++ b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/utils/Utils.java @@ -1,9 +1,20 @@ package com.qiniu.droid.rtc.demo.utils; +import android.app.AlertDialog; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import com.qiniu.android.dns.DnsManager; +import com.qiniu.android.dns.IResolver; +import com.qiniu.android.dns.NetworkInfo; +import com.qiniu.android.dns.http.DnspodFree; +import com.qiniu.android.dns.local.AndroidDnsServer; +import com.qiniu.android.dns.local.Resolver; + +import java.io.IOException; +import java.net.InetAddress; + public final class Utils { public static int appVersion(Context context) { @@ -16,4 +27,28 @@ public static int appVersion(Context context) { } return 0; } + + public static void showAlertDialog(Context context, String message) { + new AlertDialog.Builder(context) + .setMessage(message) + .setCancelable(false) + .setPositiveButton("确定", (dialog, which) -> dialog.dismiss()) + .create() + .show(); + } + + public static DnsManager getDefaultDnsManager(Context context) { + IResolver r0 = null; + try { + // 默认使用阿里云公共 DNS 服务,避免系统 DNS 解析可能出现的跨运营商、重定向等问题,详情可参考 https://www.alidns.com/ + r0 = new Resolver(InetAddress.getByName("223.5.5.5")); + } catch (IOException e) { + e.printStackTrace(); + } + // 默认 Dnspod 服务,使用腾讯公共 DNS 服务,详情可参考 https://www.dnspod.cn/Products/Public.DNS + IResolver r1 = new DnspodFree(); + // 系统默认 DNS 解析,可能会出现解析跨运营商等问题 + IResolver r2 = AndroidDnsServer.defaultResolver(context); + return new DnsManager(NetworkInfo.normal, new IResolver[]{r0, r1, r2}); + } } diff --git a/QNDroidRTCDemo/app/src/main/jniLibs/arm64-v8a/libqndroid_rtc.so b/QNDroidRTCDemo/app/src/main/jniLibs/arm64-v8a/libqndroid_rtc.so index d1e6122..30b5b05 100755 Binary files a/QNDroidRTCDemo/app/src/main/jniLibs/arm64-v8a/libqndroid_rtc.so and b/QNDroidRTCDemo/app/src/main/jniLibs/arm64-v8a/libqndroid_rtc.so differ diff --git a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi-v7a/libqndroid_rtc.so b/QNDroidRTCDemo/app/src/main/jniLibs/armeabi-v7a/libqndroid_rtc.so index f217a8a..3408318 100755 Binary files a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi-v7a/libqndroid_rtc.so and b/QNDroidRTCDemo/app/src/main/jniLibs/armeabi-v7a/libqndroid_rtc.so differ diff --git a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_rtc.so b/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_rtc.so index 493236e..17c11d6 100755 Binary files a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_rtc.so and b/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_rtc.so differ diff --git a/QNDroidRTCDemo/app/src/main/jniLibs/x86/libqndroid_rtc.so b/QNDroidRTCDemo/app/src/main/jniLibs/x86/libqndroid_rtc.so index 3eaa7b5..c209fc3 100755 Binary files a/QNDroidRTCDemo/app/src/main/jniLibs/x86/libqndroid_rtc.so and b/QNDroidRTCDemo/app/src/main/jniLibs/x86/libqndroid_rtc.so differ diff --git a/QNDroidRTCDemo/app/src/main/res/drawable/ic_forward_job.xml b/QNDroidRTCDemo/app/src/main/res/drawable/ic_forward_job.xml new file mode 100644 index 0000000..c5a8463 --- /dev/null +++ b/QNDroidRTCDemo/app/src/main/res/drawable/ic_forward_job.xml @@ -0,0 +1,9 @@ + + + diff --git a/QNDroidRTCDemo/app/src/main/res/layout/activity_live_room.xml b/QNDroidRTCDemo/app/src/main/res/layout/activity_live_room.xml index c4da2a6..918ef73 100644 --- a/QNDroidRTCDemo/app/src/main/res/layout/activity_live_room.xml +++ b/QNDroidRTCDemo/app/src/main/res/layout/activity_live_room.xml @@ -76,4 +76,4 @@ - + \ No newline at end of file diff --git a/QNDroidRTCDemo/app/src/main/res/layout/activity_setting.xml b/QNDroidRTCDemo/app/src/main/res/layout/activity_setting.xml index 3314245..fb06290 100644 --- a/QNDroidRTCDemo/app/src/main/res/layout/activity_setting.xml +++ b/QNDroidRTCDemo/app/src/main/res/layout/activity_setting.xml @@ -13,7 +13,7 @@ android:background="@drawable/background_niu" /> - + android:layout_below="@id/setting_back" + android:layout_above="@id/logo_container"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="wrap_content"> - + + + android:paddingEnd="15dp" + android:paddingStart="15dp" + android:paddingTop="8dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + +