diff --git a/QNDroidRTCDemo/app/build.gradle b/QNDroidRTCDemo/app/build.gradle index 380b49a..5c8deb2 100644 --- a/QNDroidRTCDemo/app/build.gradle +++ b/QNDroidRTCDemo/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 28 + compileSdkVersion 29 defaultConfig { applicationId "com.qiniu.droid.rtc.demo" minSdkVersion 18 - targetSdkVersion 28 - versionCode 31 - versionName "3.0.2" + targetSdkVersion 29 + versionCode 32 + versionName "3.1.0" buildConfigField "long", "BUILD_TIMESTAMP", System.currentTimeMillis() + "L" } diff --git a/QNDroidRTCDemo/app/libs/qndroid-rtc-3.0.2.jar b/QNDroidRTCDemo/app/libs/qndroid-rtc-3.1.0.jar similarity index 55% rename from QNDroidRTCDemo/app/libs/qndroid-rtc-3.0.2.jar rename to QNDroidRTCDemo/app/libs/qndroid-rtc-3.1.0.jar index fecba5a..cd09601 100644 Binary files a/QNDroidRTCDemo/app/libs/qndroid-rtc-3.0.2.jar and b/QNDroidRTCDemo/app/libs/qndroid-rtc-3.1.0.jar differ diff --git a/QNDroidRTCDemo/app/src/main/AndroidManifest.xml b/QNDroidRTCDemo/app/src/main/AndroidManifest.xml index 09a7ad6..231dbb3 100644 --- a/QNDroidRTCDemo/app/src/main/AndroidManifest.xml +++ b/QNDroidRTCDemo/app/src/main/AndroidManifest.xml @@ -11,9 +11,9 @@ - + + + + diff --git a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/RTCApplication.java b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/RTCApplication.java index 8d92fc0..9931509 100644 --- a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/RTCApplication.java +++ b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/RTCApplication.java @@ -16,7 +16,7 @@ public void onCreate() { * init must be called before any other func */ QNRTCEnv.init(getApplicationContext()); - QNRTCEnv.setLogFileEnabled(true); + QNRTCEnv.setLogFileEnabled(true, "牛会议"); // 设置自定义 DNS manager,不设置则使用 SDK 默认 DNS 服务 new Thread(() -> QNRTCEnv.setDnsManager(Utils.getDefaultDnsManager(getApplicationContext()))).start(); } 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 f956207..8040bf2 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 @@ -4,9 +4,11 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.FragmentTransaction; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Color; @@ -14,6 +16,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; import android.util.DisplayMetrics; @@ -48,6 +51,7 @@ 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.service.ForegroundService; import com.qiniu.droid.rtc.demo.ui.CircleTextView; import com.qiniu.droid.rtc.demo.ui.MergeLayoutConfigView; import com.qiniu.droid.rtc.demo.ui.UserTrackView; @@ -65,13 +69,13 @@ import org.webrtc.Size; import org.webrtc.VideoFrame; -import java.util.concurrent.Semaphore; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import static com.qiniu.droid.rtc.demo.utils.Config.DEFAULT_BITRATE; @@ -269,7 +273,9 @@ public boolean onLongClick(View v) { protected void onResume() { super.onResume(); // 开始视频采集 - startCaptureAfterAcquire(); + if (mCaptureMode == Config.CAMERA_CAPTURE || mCaptureMode == Config.MUTI_TRACK_CAPTURE) { + startCaptureAfterAcquire(); + } if (!mIsJoinedRoom) { // 加入房间 mEngine.joinRoom(mRoomToken); @@ -292,7 +298,9 @@ private void startCaptureAfterAcquire() { protected void onPause() { super.onPause(); // 停止视频采集 - mEngine.stopCapture(); + if (mCaptureMode == Config.CAMERA_CAPTURE || mCaptureMode == Config.MUTI_TRACK_CAPTURE) { + mEngine.stopCapture(); + } if (mPopWindow != null && mPopWindow.isShowing()) { mPopWindow.dismiss(); } @@ -340,12 +348,18 @@ private void initQNRTCEngine() { * 如果打开分辨率保持开关,则只会调整帧率来适应网络波动。 */ boolean isMaintainRes = preferences.getBoolean(Config.MAINTAIN_RES, false); + + /** + * 如果您的使用场景需要双讲,建议按照默认设置,保持 QNRTCSetting#setLowAudioSampleRateEnabled + * 和 QNRTCSetting#setAEC3Enabled 为 true 以防止出现对讲回声 + */ boolean isLowSampleRateEnabled = preferences.getInt(Config.SAMPLE_RATE, Config.HIGH_SAMPLE_RATE) == Config.LOW_SAMPLE_RATE; - boolean isAec3Enabled = preferences.getBoolean(Config.AEC3_ENABLE, false); + boolean isAec3Enabled = preferences.getBoolean(Config.AEC3_ENABLE, true); mCaptureMode = preferences.getInt(Config.CAPTURE_MODE, Config.CAMERA_CAPTURE); - // 1. VideoPreviewFormat 和 VideoEncodeFormat 建议保持一致 - // 2. 如果远端连麦出现回声的现象,可以通过配置 setLowAudioSampleRateEnabled(true) 和 setAEC3Enabled(true) 后再做进一步测试,并将设备信息反馈给七牛技术支持 + /** + * VideoPreviewFormat 和 VideoEncodeFormat 建议保持一致 + */ QNVideoFormat format = new QNVideoFormat(videoWidth, videoHeight, fps); QNRTCSetting setting = new QNRTCSetting(); setting.setCameraID(QNRTCSetting.CAMERA_FACING_ID.FRONT) @@ -454,7 +468,6 @@ public int onEncrypt(ByteBuffer frame, int frameSize, ByteBuffer encryptedFrame) }); mLocalTrackList.add(mLocalAudioTrack); - QNVideoFormat screenEncodeFormat = new QNVideoFormat(mScreenWidth/2, mScreenHeight/2, 15); switch (mCaptureMode) { case Config.CAMERA_CAPTURE: // 创建 Camera 采集的视频 Track @@ -469,23 +482,13 @@ public int onEncrypt(ByteBuffer frame, int frameSize, ByteBuffer encryptedFrame) break; case Config.SCREEN_CAPTURE: // 创建屏幕录制的视频 Track - mLocalScreenTrack = mEngine.createTrackInfoBuilder() - .setVideoPreviewFormat(screenEncodeFormat) - .setBitrate(BITRATE_FOR_SCREEN_VIDEO) - .setSourceType(QNSourceType.VIDEO_SCREEN) - .setMaster(true) - .setTag(UserTrackView.TAG_SCREEN).create(); + createScreenTrack(); mLocalTrackList.add(mLocalScreenTrack); mControlFragment.setAudioOnly(true); break; case Config.MUTI_TRACK_CAPTURE: // 视频通话 + 屏幕共享两路 track - mLocalScreenTrack = mEngine.createTrackInfoBuilder() - .setSourceType(QNSourceType.VIDEO_SCREEN) - .setVideoPreviewFormat(screenEncodeFormat) - .setBitrate(BITRATE_FOR_SCREEN_VIDEO) - .setMaster(true) - .setTag(UserTrackView.TAG_SCREEN).create(); + createScreenTrack(); mLocalVideoTrack = mEngine.createTrackInfoBuilder() .setSourceType(QNSourceType.VIDEO_CAMERA) .setTag(UserTrackView.TAG_CAMERA).create(); @@ -495,6 +498,47 @@ public int onEncrypt(ByteBuffer frame, int frameSize, ByteBuffer encryptedFrame) } } + + private final ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + QNVideoFormat screenEncodeFormat = new QNVideoFormat(mScreenWidth / 2, mScreenHeight / 2, 15); + mLocalScreenTrack = mEngine.createTrackInfoBuilder() + .setSourceType(QNSourceType.VIDEO_SCREEN) + .setVideoPreviewFormat(screenEncodeFormat) + .setBitrate(BITRATE_FOR_SCREEN_VIDEO) + .setMaster(true) + .setTag(UserTrackView.TAG_SCREEN).create(); + mLocalTrackList.add(mLocalScreenTrack); + if (mEngine.getRoomState().equals(QNRoomState.CONNECTED) || mEngine.getRoomState().equals(QNRoomState.RECONNECTED)) { + mEngine.publishTracks(Collections.singletonList(mLocalScreenTrack)); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + + } + }; + + // 处理 Build.VERSION_CODES.Q 及以上的兼容问题 + private void createScreenTrack() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Intent intent = new Intent(this, ForegroundService.class); + startForegroundService(intent); + bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); + Log.i(TAG, "start service for Q"); + } else { + QNVideoFormat screenEncodeFormat = new QNVideoFormat(mScreenWidth / 2, mScreenHeight / 2, 15); + mLocalScreenTrack = mEngine.createTrackInfoBuilder() + .setSourceType(QNSourceType.VIDEO_SCREEN) + .setVideoPreviewFormat(screenEncodeFormat) + .setBitrate(BITRATE_FOR_SCREEN_VIDEO) + .setMaster(true) + .setTag(UserTrackView.TAG_SCREEN).create(); + } + } + /** * 合流转推、单路转推相关处理 */ @@ -507,6 +551,9 @@ private void initMergeLayoutConfig() { mMergeLayoutConfigView.setOnClickedListener(new MergeLayoutConfigView.OnClickedListener() { @Override public void onConfirmClicked() { + // 保存当前用户选择的配置信息 + mMergeLayoutConfigView.updateMergeOptions(); + if (mEngine == null) { return; } @@ -546,8 +593,12 @@ public void onConfirmClicked() { mCurrentMergeJob = mergeJob; // 创建自定义合流任务 mEngine.createMergeJob(mCurrentMergeJob); + } else { + // 更新合流布局到自定义合流任务 + setMergeStreamLayouts(); } } else { + // 更新合流布局到默认合流任务 setMergeStreamLayouts(); } if (mPopWindow != null) { @@ -613,23 +664,26 @@ private void updateRemoteLogText(final String logText) { mControlFragment.updateRemoteLogText(logText); } + /** + * 当新的本地、远端 Track 变化时,重新排列合流画面配置 + */ private void resetMergeStream() { Log.d(TAG, "resetMergeStream()"); List configuredMergeTracksOptions = new ArrayList<>(); // video tracks merge layout options. - List remoteVideoTrackInfoList = mRoomUsersMergeOption.getRTCVideoMergeOptions(); - if (!remoteVideoTrackInfoList.isEmpty()) { - List mergeTrackOptions = SplitUtils.split(remoteVideoTrackInfoList.size(), + List roomVideoTrackInfoList = mRoomUsersMergeOption.getRTCVideoMergeOptions(); + if (!roomVideoTrackInfoList.isEmpty()) { + List mergeTrackOptions = SplitUtils.split(roomVideoTrackInfoList.size(), mCurrentMergeJob == null ? QNAppServer.STREAMING_WIDTH : mCurrentMergeJob.getWidth(), mCurrentMergeJob == null ? QNAppServer.STREAMING_HEIGHT : mCurrentMergeJob.getHeight()); - if (mergeTrackOptions.size() != remoteVideoTrackInfoList.size()) { + if (mergeTrackOptions.size() != roomVideoTrackInfoList.size()) { Log.e(TAG, "split option error."); return; } for (int i = 0; i < mergeTrackOptions.size(); i++) { - RTCTrackMergeOption trackMergeOption = remoteVideoTrackInfoList.get(i); + RTCTrackMergeOption trackMergeOption = roomVideoTrackInfoList.get(i); if (!trackMergeOption.isTrackInclude()) { continue; @@ -641,9 +695,9 @@ private void resetMergeStream() { } // audio tracks merge layout options - List remoteAudioTrackInfoList = mRoomUsersMergeOption.getRTCAudioTracks(); - if (!remoteAudioTrackInfoList.isEmpty()) { - for (RTCTrackMergeOption trackMergeOption : remoteAudioTrackInfoList) { + List roomAudioTrackInfoList = mRoomUsersMergeOption.getRTCAudioTracks(); + if (!roomAudioTrackInfoList.isEmpty()) { + for (RTCTrackMergeOption trackMergeOption : roomAudioTrackInfoList) { if (!trackMergeOption.isTrackInclude()) { continue; } @@ -663,8 +717,12 @@ private void userJoinedForStreaming(String userId, String userData) { } } - private void userLeftForStreaming(String userId) { - mRoomUsersMergeOption.onUserLeft(userId); + private void userLeftForStreaming(String userId, boolean localLeft) { + if (localLeft) { + mRoomUsersMergeOption.onUserLeft(); + } else { + mRoomUsersMergeOption.onUserLeft(userId); + } if (mUserListAdapter != null) { mUserListAdapter.notifyDataSetChanged(); } @@ -676,13 +734,22 @@ private int updateSerialNum() { } /** - * 配置合流的布局信息 + * 配置各个用户当前选中的 Track 信息到合流布局 * * 如果使用的默认合流任务,则无需手动 createMergeJob,setMergeStreamLayouts 中 jobId 参数传 null 即可 */ private void setMergeStreamLayouts() { - // 配置合流布局信息 - List userTracks = mMergeLayoutConfigView.updateMergeOptions(); + List userTracks = new ArrayList<>(); + for (int user = 0; user < mRoomUsersMergeOption.size(); user++) { + RTCUserMergeOptions userMergeOptions = mRoomUsersMergeOption.getRoomUserByPosition(user); + if (userMergeOptions.getAudioTrack() != null) { + userTracks.add(userMergeOptions.getAudioTrack()); + } + if (userMergeOptions.getVideoTracks().size() > 0) { + userTracks.addAll(userMergeOptions.getVideoTracks()); + } + } + List addedTrackOptions = new ArrayList<>(); List removedTrackOptions = new ArrayList<>(); for (RTCTrackMergeOption item : userTracks) { @@ -728,7 +795,7 @@ public void onRoomStateChanged(QNRoomState state) { switch (state) { case IDLE: if (mIsAdmin) { - userLeftForStreaming(mUserId); + userLeftForStreaming(mUserId, true); } break; case RECONNECTING: @@ -744,6 +811,16 @@ public void onRoomStateChanged(QNRoomState state) { logAndToast(getString(R.string.connected_to_room)); mIsJoinedRoom = true; mControlFragment.startTimer(); + + // 重连失败后再次加入房间后,恢复无效的合流任务 + if (mIsMergeJobStreaming && mMergeLayoutConfigView.isCustomMergeJob() && !mMergeLayoutConfigView.isMergeJobValid()) { + QNMergeJob mergeJob = mMergeLayoutConfigView.getCustomMergeJob(); + if (mergeJob != null) { + mCurrentMergeJob = mergeJob; + // 创建自定义合流任务 + mEngine.createMergeJob(mCurrentMergeJob); + } + } break; case RECONNECTED: logAndToast(getString(R.string.connected_to_room)); @@ -797,7 +874,7 @@ public void onRemoteUserReconnected(String remoteUserId) { public void onRemoteUserLeft(final String remoteUserId) { updateRemoteLogText("onRemoteUserLeft:remoteUserId = " + remoteUserId); if (mIsAdmin) { - userLeftForStreaming(remoteUserId); + userLeftForStreaming(remoteUserId, false); } } @@ -1043,6 +1120,7 @@ public void onCreateForwardJobSuccess(String forwardJobId) { mEngine.stopMergeStream(mCurrentMergeJob.getMergeJobId(), JOB_STOP_DELAY_TIME); mIsMergeJobStreaming = false; mMergeLayoutConfigView.updateStreamingStatus(false); + mMergeLayoutConfigView.updateMergeJobValid(false); } } @@ -1076,6 +1154,7 @@ public void onError(int errorCode, String description) { * 3 )请确认您的网络状况是否正常 * 3. QNErrorCode.ERROR_RECONNECT_TOKEN_ERROR 内部重连后出错,一般出现在网络非常不稳定时出现,建议提示用户并尝试重新加入房间; + * 另外,当前用户之前创建的合流任务、单路转推任务将会被服务销毁,重新加入房间后应该重新创建合流,单路转推任务 !!! * 4. QNErrorCode.ERROR_INVALID_PARAMETER 服务交互参数错误,请在开发时注意合流、踢人动作等参数的设置。 * 5. QNErrorCode.ERROR_DEVICE_CAMERA 系统摄像头错误, 建议提醒用户检查 */ @@ -1122,6 +1201,10 @@ public void onError(int errorCode, String description) { mTrackWindowMgr.addTrackInfo(mUserId, localTrackListExcludeScreenTrack); if (errorCode == QNErrorCode.ERROR_RECONNECT_TOKEN_ERROR) { logAndToast("ERROR_RECONNECT_TOKEN_ERROR 即将重连,请注意网络质量!"); + // 当重连超时后,用户创建的合流任务默认被销毁;需要重新创建合流任务 + if (mIsMergeJobStreaming && mMergeLayoutConfigView.isCustomMergeJob()) { + mMergeLayoutConfigView.updateMergeJobValid(false); + } } if (errorCode == QNErrorCode.ERROR_AUTH_FAIL) { logAndToast("ERROR_AUTH_FAIL 即将重连"); @@ -1291,7 +1374,7 @@ public void onToggleForwardJob() { } /** - * 合流配置相关 + * 用户合流配置相关 */ private class UserListAdapter extends RecyclerView.Adapter { int[] mColor = { 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 61b4907..3ed8888 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 @@ -20,6 +20,7 @@ import android.widget.Switch; import android.widget.TextView; +import com.qiniu.droid.rtc.QNFileLogHelper; import com.qiniu.droid.rtc.demo.BuildConfig; import com.qiniu.droid.rtc.demo.R; import com.qiniu.droid.rtc.demo.ui.SpinnerPopupWindow; @@ -38,6 +39,7 @@ public class SettingActivity extends AppCompatActivity { private EditText mUserNameEditText; private TextView mConfigTextView; private TextView mVersionCodeTextView; + private TextView mUploadTextView; private RadioGroup mCodecModeRadioGroup; private RadioButton mHwCodecMode; private RadioButton mSwCodecMode; @@ -57,8 +59,10 @@ public class SettingActivity extends AppCompatActivity { private boolean mMaintainResolution = false; private boolean mIsAec3Enabled = false; private List mDefaultConfiguration = new ArrayList<>(); - private ArrayAdapter mAdapter; - private SpinnerPopupWindow mSpinnerPopupWindow; + private ArrayAdapter mConfigAdapter; + private SpinnerPopupWindow mConfigPopupWindow; + private List mLogFileNames; + private SpinnerPopupWindow mLogFilePopupWindow; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -71,6 +75,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { mUserNameEditText = (EditText) findViewById(R.id.user_name_edit_text); mConfigTextView = (TextView) findViewById(R.id.config_text_view); mVersionCodeTextView = (TextView) findViewById(R.id.version_code); + mUploadTextView = (TextView) findViewById(R.id.report_log); mCodecModeRadioGroup = (RadioGroup) findViewById(R.id.codec_mode_button); mCodecModeRadioGroup.setOnCheckedChangeListener(mOnCheckedChangeListener); mHwCodecMode = (RadioButton) findViewById(R.id.hw_radio_button); @@ -122,7 +127,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { mSwCodecMode.setChecked(true); } - int sampleRatePos = preferences.getInt(Config.SAMPLE_RATE, Config.HIGH_SAMPLE_RATE); + int sampleRatePos = preferences.getInt(Config.SAMPLE_RATE, Config.LOW_SAMPLE_RATE); if (sampleRatePos == Config.LOW_SAMPLE_RATE) { mLowSampleRateBtn.setChecked(true); } else { @@ -136,13 +141,13 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { mMaintainResolutionNo.setChecked(true); } - mIsAec3Enabled = preferences.getBoolean(Config.AEC3_ENABLE, false); + mIsAec3Enabled = preferences.getBoolean(Config.AEC3_ENABLE, true); mAec3Switch.setChecked(mIsAec3Enabled); - mSpinnerPopupWindow = new SpinnerPopupWindow(this); - mSpinnerPopupWindow.setOnSpinnerItemClickListener(mOnSpinnerItemClickListener); + mConfigPopupWindow = new SpinnerPopupWindow(this); + mConfigPopupWindow.setOnSpinnerItemClickListener(mOnSpinnerItemClickListener); - mAdapter = new ArrayAdapter(this, R.layout.spinner_item, mDefaultConfiguration); + mConfigAdapter = new ArrayAdapter(this, R.layout.spinner_item, mDefaultConfiguration); } public void onClickBack(View v) { @@ -150,7 +155,11 @@ public void onClickBack(View v) { } public void onClickConfigParams(View v) { - showPopupWindow(); + showConfigPopupWindow(); + } + + public void onClickUploadLog(View v) { + showLogFilePopupWindow(); } public void onClickSaveConfiguration(View v) { @@ -216,10 +225,43 @@ private void saveTestMode(SharedPreferences.Editor editor) { } } - private void showPopupWindow() { - mSpinnerPopupWindow.setAdapter(mAdapter); - mSpinnerPopupWindow.setWidth(mConfigTextView.getWidth()); - mSpinnerPopupWindow.showAsDropDown(mConfigTextView); + private void showConfigPopupWindow() { + mConfigPopupWindow.setAdapter(mConfigAdapter); + mConfigPopupWindow.setWidth(mConfigTextView.getWidth()); + mConfigPopupWindow.showAsDropDown(mConfigTextView); + } + + private void showLogFilePopupWindow() { + if (mLogFilePopupWindow == null) { + mLogFilePopupWindow = new SpinnerPopupWindow(this); + mLogFilePopupWindow.setOnSpinnerItemClickListener(new SpinnerPopupWindow.OnSpinnerItemClickListener() { + @Override + public void onItemClick(int pos) { + QNFileLogHelper.getInstance().reportLogFile(mLogFileNames.get(pos), new QNFileLogHelper.LogReportCallback() { + @Override + public void onReportSuccess(String name) { + ToastUtils.s(SettingActivity.this, "上传成功:" + name); + } + + @Override + public void onReportError(String name, String errorMsg) { + ToastUtils.s(SettingActivity.this, "上传失败:" + name + ";" + errorMsg); + } + }); + mLogFilePopupWindow.dismiss(); + } + }); + } + mLogFileNames = QNFileLogHelper.getInstance().getLogFiles(); + if (mLogFileNames == null || mLogFileNames.size() == 0) { + ToastUtils.s(SettingActivity.this, "当前无可上报日志"); + return; + } + + ArrayAdapter adapter = new ArrayAdapter<>(this, R.layout.spinner_item, mLogFileNames); + mLogFilePopupWindow.setAdapter(adapter); + mLogFilePopupWindow.setWidth(mAppIdEditText.getWidth()); + mLogFilePopupWindow.showAsDropDown(mUploadTextView); } private String getVersionDescription() { @@ -253,7 +295,7 @@ private boolean isTestMode() { public void onItemClick(int pos) { mSelectPos = pos; mConfigTextView.setText(mDefaultConfiguration.get(mSelectPos)); - mSpinnerPopupWindow.dismiss(); + mConfigPopupWindow.dismiss(); } }; 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 index 6a82f89..1f9d8b1 100644 --- 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 @@ -56,6 +56,12 @@ public void onUserLeft(String userId) { } } + public void onUserLeft() { + mRTCVideoMergeOptions.clear(); + mRTCUsers.clear(); + mRTCUserMap.clear(); + } + public void onTracksPublished(String userId, List trackInfoList) { RTCUserMergeOptions userMergeOptions = getRoomUserByUserId(userId); if (userMergeOptions == null) { diff --git a/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/service/ForegroundService.java b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/service/ForegroundService.java new file mode 100644 index 0000000..3a1fa15 --- /dev/null +++ b/QNDroidRTCDemo/app/src/main/java/com/qiniu/droid/rtc/demo/service/ForegroundService.java @@ -0,0 +1,62 @@ +package com.qiniu.droid.rtc.demo.service; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Build; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; +import android.util.Log; + +public class ForegroundService extends Service { + + private static final String TAG = "ForegroundService"; + private final IBinder mBinder = new LocalBinder(); + + public class LocalBinder extends Binder { + ForegroundService getService() { + return ForegroundService.this; + } + } + + @Override + public void onCreate() { + super.onCreate(); + if (Build.VERSION.SDK_INT >= 26) { + String CHANNEL_ID = "screen share"; + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, + "screen share", + NotificationManager.IMPORTANCE_DEFAULT); + + ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel); + Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle("") + .setContentText("").build(); + startForeground(1, notification); + Log.i(TAG, "start foreground"); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (Build.VERSION.SDK_INT >= 26) { + stopForeground(true); + } + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + @Override + public int onStartCommand(final Intent intent, int flags, int startId) { + return super.onStartCommand(intent, flags, startId); + } +} + 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 3ffc26d..11abc20 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,7 +6,6 @@ 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; @@ -68,6 +67,7 @@ public class MergeLayoutConfigView extends FrameLayout { private RadioGroup mStretchModeRadioGroup; private QNStretchMode mStretchMode; private QNMergeJob mCurrentMergeJob; + private boolean mCurrentMergeJobValid; private String mRoomId; private boolean mIsStreamingEnabled; @@ -160,15 +160,11 @@ public void updateConfigInfo(RTCUserMergeOptions chooseUser) { } /** - * 获取选中用户更新后的合流配置信息 - * - * @return 选中用户的合流配置信息 + * 同步 UI 选择的合流参数到合流配置类 */ - public List updateMergeOptions() { - List result = new ArrayList<>(); + public void updateMergeOptions() { if (mUserAudioTrack != null) { mUserAudioTrack.setTrackInclude(mAudioSwitch.isChecked()); - result.add(mUserAudioTrack); } if (mUserFirstVideoTrack != null) { mUserFirstVideoTrack.setTrackInclude(mFirstVideoSwitch.isChecked()); @@ -187,7 +183,6 @@ public List updateMergeOptions() { } catch (Exception e) { ToastUtils.s(getContext(), "请输入所有值");//处理空值 } - result.add(mUserFirstVideoTrack); } if (mUserSecondVideoTrack != null) { mUserSecondVideoTrack.setTrackInclude(mSecondVideoSwitch.isChecked()); @@ -206,9 +201,7 @@ public List updateMergeOptions() { } catch (Exception e) { ToastUtils.s(getContext(), "请输入所有值");//处理空值 } - result.add(mUserSecondVideoTrack); } - return result; } /** @@ -233,6 +226,7 @@ public QNMergeJob getCustomMergeJob() { mCurrentMergeJob.setMaxBitrate(Integer.parseInt(mStreamMaxBitrateText.getText().toString().trim()) * 1000); mCurrentMergeJob.setFps(Integer.parseInt(mStreamFpsText.getText().toString().trim())); mCurrentMergeJob.setStretchMode(mStretchMode); + mCurrentMergeJobValid = true; return mCurrentMergeJob; } @@ -271,6 +265,22 @@ public void updateMergeJobConfigInfo() { } } + /** + * 更新当前合流任务是否可用 + * @param valid 是否可用 + */ + public void updateMergeJobValid(boolean valid) { + mCurrentMergeJobValid = valid; + } + + /** + * 当前合流任务是否可用 + * @return valid 是否可用 + */ + public boolean isMergeJobValid() { + return mCurrentMergeJobValid; + } + public void updateSerialNum(int serialNum) { mSerialNum = serialNum; } @@ -454,8 +464,9 @@ private boolean isNeedUpdateMergeJob() { if (mCurrentMergeJob == null) { return true; } - return !mCustomJobIdText.getText().toString().trim().equals(mCurrentMergeJob.getMergeJobId()) - || !mPublishUrlText.getText().toString().trim().equals(mCurrentMergeJob.getPublishUrl()) + return !mCurrentMergeJobValid + || !mCustomJobIdText.getText().toString().trim().equals(mCurrentMergeJob.getMergeJobId()) + || !isPublishUrlIdentity() || Integer.parseInt(mStreamWidthText.getText().toString().trim()) != mCurrentMergeJob.getWidth() || Integer.parseInt(mStreamHeightText.getText().toString().trim()) != mCurrentMergeJob.getHeight() || Integer.parseInt(mStreamBitrateText.getText().toString().trim()) != mCurrentMergeJob.getBitrate() / 1000 @@ -464,4 +475,10 @@ private boolean isNeedUpdateMergeJob() { || Integer.parseInt(mStreamFpsText.getText().toString().trim()) != mCurrentMergeJob.getFps() || mStretchMode != mCurrentMergeJob.getStretchMode(); } + + private boolean isPublishUrlIdentity() { + String url1 = mPublishUrlText.getText().toString().trim(); + String url2 = mCurrentMergeJob.getPublishUrl(); + return url1.substring(0, url1.indexOf("?serialnum")).equals(url2.substring(0, url2.indexOf("?serialnum"))); + } } 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 6edee01..8b4641e 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 @@ -1,5 +1,6 @@ package com.qiniu.droid.rtc.demo.utils; +import android.util.Log; import android.view.Gravity; import android.view.View; import android.widget.FrameLayout; @@ -8,8 +9,6 @@ import com.qiniu.droid.rtc.QNTrackInfo; import com.qiniu.droid.rtc.demo.ui.UserTrackView; -import org.webrtc.Logging; - import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -54,7 +53,7 @@ public TrackWindowMgr(String currentUserId, int screenWidth, int screenHeight, f @Override public void onClick(View v) { if (mUserWindowMap.size() <= 1) { - Logging.d(TAG, "skip for single user."); + Log.d(TAG, "skip for single user."); } else if (mUserWindowMap.size() == 2) { // swap switchToFullScreenWindow(mUserWindowMap.getOrderedValues(mTrackFullScreenWin).get(0)); @@ -81,7 +80,7 @@ public void onClick(View v) { @Override public void onClick(View v) { if (mUserWindowMap.size() <= 1) { - Logging.d(TAG, "skip for single user."); + Log.d(TAG, "skip for single user."); } else if (mUserWindowMap.size() == 2) { // swap switchToFullScreenWindow(view); @@ -97,7 +96,7 @@ public void onClick(View v) { public void addTrackInfo(String userId, List trackInfoList) { if (mTrackCandidateWins.size() == 0) { - Logging.e(TAG, "There were more than 9 published users in the room, with no unUsedWindow to draw."); + Log.e(TAG, "There were more than 9 published users in the room, with no unUsedWindow to draw."); return; } UserTrackView userTrackView = mUserWindowMap.get(userId); @@ -182,21 +181,21 @@ private void switchToFullScreenWindow(UserTrackView userTrackView) { UserTrackView.swap(mEngine, mTrackFullScreenWin, userTrackView); if (mTrackFullScreenWin.isTaken()) { - Logging.d(TAG, "put " + mTrackFullScreenWin.getUserId() + " to " + mTrackFullScreenWin.getResourceName()); + Log.d(TAG, "put " + mTrackFullScreenWin.getUserId() + " to " + mTrackFullScreenWin.getResourceName()); mTrackFullScreenWin.setVisibility(View.VISIBLE); mUserWindowMap.put(mTrackFullScreenWin.getUserId(), mTrackFullScreenWin, mTrackFullScreenWin.getUserId().equals(mCurrentUserId)); } else { - Logging.d(TAG, "recycle " + mTrackFullScreenWin.getResourceName()); + Log.d(TAG, "recycle " + mTrackFullScreenWin.getResourceName()); mTrackFullScreenWin.reset(); mTrackFullScreenWin.setVisibility(View.GONE); } if (userTrackView.isTaken()) { - Logging.d(TAG, "put " + userTrackView.getUserId() + " to " + userTrackView.getResourceName()); + Log.d(TAG, "put " + userTrackView.getUserId() + " to " + userTrackView.getResourceName()); userTrackView.setVisibility(View.VISIBLE); mUserWindowMap.put(userTrackView.getUserId(), userTrackView, userTrackView.getUserId().equals(mCurrentUserId)); } else { - Logging.d(TAG, "recycle " + userTrackView.getResourceName()); + Log.d(TAG, "recycle " + userTrackView.getResourceName()); userTrackView.reset(); userTrackView.setVisibility(View.GONE); // recycle @@ -220,12 +219,12 @@ private void updateTrackWindowMode(boolean trackWindowP2PMode) { return; } if (trackWindowP2PMode) { - Logging.d(TAG, "switch to p2p mode"); + Log.d(TAG, "switch to p2p mode"); // relayout. switch to p2p mode. put first user to full screen. // ( 0 user -> 1 user || 3 users -> 2 users) switchToFullScreenWindow(mUserWindowMap.getOrderedValues().get(0)); } else { - Logging.d(TAG, "switch to multi user mode"); + Log.d(TAG, "switch to multi user mode"); // relayout. switch to multi user mode. // (2 users -> 3 users) if (mTrackFullScreenWin.isTaken()) { 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 d4696dc..d4e4f2b 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 7ddb0b9..e8dbee8 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/libQPlayer.so b/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libQPlayer.so deleted file mode 100755 index 1fb002b..0000000 Binary files a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libQPlayer.so and /dev/null differ diff --git a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqcCodec.so b/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqcCodec.so deleted file mode 100755 index 93732c5..0000000 Binary files a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqcCodec.so and /dev/null differ diff --git a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqcOpenSSL.so b/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqcOpenSSL.so deleted file mode 100755 index 6a93a22..0000000 Binary files a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqcOpenSSL.so and /dev/null differ diff --git a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_amix.so b/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_amix.so deleted file mode 100755 index 207bf45..0000000 Binary files a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_amix.so and /dev/null differ diff --git a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_beauty.so b/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_beauty.so deleted file mode 100755 index 0bbf7e6..0000000 Binary files a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_beauty.so and /dev/null differ diff --git a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_rtc.so b/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_rtc.so deleted file mode 100755 index 2d520f2..0000000 Binary files a/QNDroidRTCDemo/app/src/main/jniLibs/armeabi/libqndroid_rtc.so and /dev/null differ diff --git a/QNDroidRTCDemo/app/src/main/jniLibs/x86/libqndroid_rtc.so b/QNDroidRTCDemo/app/src/main/jniLibs/x86/libqndroid_rtc.so index 7aa86d7..6940e1f 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_upload.xml b/QNDroidRTCDemo/app/src/main/res/drawable/ic_upload.xml new file mode 100644 index 0000000..4ab9ad3 --- /dev/null +++ b/QNDroidRTCDemo/app/src/main/res/drawable/ic_upload.xml @@ -0,0 +1,12 @@ + + + + diff --git a/QNDroidRTCDemo/app/src/main/res/layout/activity_setting.xml b/QNDroidRTCDemo/app/src/main/res/layout/activity_setting.xml index fb06290..ecd49cb 100644 --- a/QNDroidRTCDemo/app/src/main/res/layout/activity_setting.xml +++ b/QNDroidRTCDemo/app/src/main/res/layout/activity_setting.xml @@ -226,6 +226,7 @@ android:id="@+id/webrtc_aec3_enable_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:switchPadding="10dp" android:layout_below="@id/app_id_edit_text" android:layout_marginTop="16dp" android:layout_marginLeft="45dp" @@ -313,11 +314,27 @@ + +