在实时通信中,如果你希望用户可以分享本端设备的屏幕和设备播放的音频,可以使用 RTC 内建的屏幕采集功能,也可以自行实现屏幕采集逻辑(自定义采集),并通过屏幕共享功能,与远端用户共享。
在使用屏幕共享功能时,仅可见的用户可以发布屏幕流。
你可以在多种行业、多种场景下使用屏幕共享功能:
| 行业 | 场景 |
|---|---|
| 在线教育 | 老师共享屏幕给学生上课;美术老师共享屏幕给学生教画画。 |
| 游戏直播 | 主播共享屏幕给观众,展现自己的游戏画面。 |
| 互动直播 | 主播共享自己的屏幕和观众互动。 |
| 视频会议 | 会议成员共享屏幕观看 PPT 或者文档。 |
Android 端屏幕共享基于 Android 5 (API 级别 21) 中引入的媒体投影 API 和 RTC 提供的 API 共同实现。
你已经集成 Android SDK,实现了基本的音视频通话。
Android 5.0 (API 级别 21) 及以上版本。推荐使用 Android 10 (API 级别 29) 及以上版本
说明:Android 5.0 ~ 10 版本仅支持屏幕视频采集,不支持屏幕音频采集。
Android 10 (API 级别 29) 及以上进行屏幕采集需要用到前台服务,在应用的 Android 清单文件中添加如下前台服务声明。
录音权限声明已包含在 RTC SDK 中,App 清单文件无需添加。
<application
...
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
...
<service android:name="com.bytedance.realx.video.RXScreenCaptureService"
android:exported="false"
android:enabled="true"
android:foregroundServiceType="mediaProjection" />
</application>
XMLAndroid 14(API 级别 34)及以上还需声明如下权限,以免出现应用闪退等异常:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
向系统请求屏幕共享的权限。
public static final int REQUEST_CODE_OF_SCREEN_SHARING = 101;
// 向系统发起屏幕共享的权限请求
public void requestForScreenSharing() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
Log.e("ShareScreen","当前系统版本过低,无法支持屏幕共享");
return;
}
MediaProjectionManager projectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
if (projectionManager != null) {
startActivityForResult(projectionManager.createScreenCaptureIntent(), REQUEST_CODE_OF_SCREEN_SHARING);
} else {
Log.e("ShareScreen","当前系统版本过低,无法支持屏幕共享");
}
}
java在权限申请的响应中,开启屏幕共享。
public static final int REQUEST_CODE_OF_SCREEN_SHARING = 101;
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == REQUEST_CODE_OF_SCREEN_SHARING && resultCode == Activity.RESULT_OK) {
startScreenShare(data);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
javaprivate void startScreenShare(Intent data) {
startRXScreenCaptureService(data);
//编码参数
ScreenVideoEncoderConfig config = new ScreenVideoEncoderConfig();
config.width = 720;
config.height = 1280;
config.frameRate = 15;
config.maxBitrate = 1600;
mRTCVideo.setScreenVideoEncoderConfig(config);
// 开启屏幕视频数据采集
mRTCVideo.startScreenCapture(ScreenMediaType.SCREEN_MEDIA_TYPE_VIDEO_AND_AUDIO, data);
}
private void startRXScreenCaptureService(@NonNull Intent data) {
Context context = Utilities.getApplicationContext();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Intent intent = new Intent();
intent.putExtra(RXScreenCaptureService.KEY_LARGE_ICON, R.drawable.launcher_quick_start);
intent.putExtra(RXScreenCaptureService.KEY_SMALL_ICON, R.drawable.launcher_quick_start);
intent.putExtra(RXScreenCaptureService.KEY_LAUNCH_ACTIVITY, mHostActivity.getClass().getCanonicalName());
intent.putExtra(RXScreenCaptureService.KEY_CONTENT_TEXT, "正在录制/投射您的屏幕");
intent.putExtra(RXScreenCaptureService.KEY_RESULT_DATA, data);
context.startForegroundService(RXScreenCaptureService.getServiceIntent(context, RXScreenCaptureService.COMMAND_LAUNCH, intent));
}
}
javaonVideoDeviceStateChanged 中调用 publishScreen。@Override
public void onVideoDeviceStateChanged(String deviceId, VideoDeviceType deviceType, int deviceState, int deviceError) {
if (deviceType == VideoDeviceType.VIDEO_DEVICE_TYPE_SCREEN_CAPTURE_DEVICE) {
if (deviceState == MediaDeviceState.MEDIA_DEVICE_STATE_STARTED) {
mRTCRoom.publishScreen(MediaStreamType.RTC_MEDIA_STREAM_TYPE_BOTH);
mRTCVideo.setVideoSourceType(StreamIndex.STREAM_INDEX_SCREEN, VideoSourceType.VIDEO_SOURCE_TYPE_INTERNAL);
} else if (deviceState == MediaDeviceState.MEDIA_DEVICE_STATE_STOPPED
|| deviceState == MediaDeviceState.MEDIA_DEVICE_STATE_RUNTIMEERROR) {
mRTCRoom.unpublishScreen(MediaStreamType.RTC_MEDIA_STREAM_TYPE_BOTH);
}
}
}
java
//SDK收到第一帧远端视频解码数据后,用户收到此回调。
@Override
public void onFirstRemoteVideoFrameDecoded(RemoteStreamKey remoteStreamKey, VideoFrameInfo frameInfo) {
super.onFirstRemoteVideoFrameDecoded(remoteStreamKey, frameInfo);
runOnUiThread(() -> setRemoteView(remoteStreamKey));
}
javaprivate void setRemoteRenderView(RemoteStreamKey remoteStreamKey, FrameLayout container) {
TextureView renderView = new TextureView(this);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
container.removeAllViews();
container.addView(renderView, params);
VideoCanvas videoCanvas = new VideoCanvas();
videoCanvas.renderView = renderView;
videoCanvas.roomId = remoteStreamKey.getRoomId();
videoCanvas.uid = remoteStreamKey.getUserId();
videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN;
// 设置远端用户视频渲染视图
mInstance.setRemoteVideoCanvas(remoteStreamKey.getUserId(), remoteStreamKey.getStreamIndex(), videoCanvas);
}
javaprivate void stopScreenShare() {
// 停止屏幕数据采集
mInstance.stopScreenCapture();
}
java
// 房间内远端屏幕共享音视频流移除的回调。
@Override
public void onUserUnPublishScreen(String uid, MediaStreamType type, StreamRemoveReason reason) {
super.onUserUnPublishScreen(uid, type, reason);
runOnUiThread(() -> removeRemoteView(getScreenShareUserId(uid)));
}
javaRTC 强烈建议你使用内部采集。如果你仍然希望使用自定义采集,参看以下步骤。
实现屏幕音视频流采集逻辑。
指定为外部输入源。
setScreenAudioSourceType 设置屏幕音频自定义采集。setVideoSourceType 设置屏幕视频自定义采集。调用 pushScreenAudioFrame 和 pushScreenVideoFrame 将采集得到的音视频帧推送到 RTC SDK 用于编码传输。
共享屏幕后,对端只能看到画面,听不到音频。
收到以下错误提示:Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
原因和解决方法如下:
AndroidManifest.xml 中配置 android.permission.FOREGROUND_SERVICE 权限和 org.webrtc.RXScreenCaptureService 声明。RXScreenCaptureService 时,RXScreenCaptureService.KEY_LAUNCH_ACTIVITY 是否传入了全类名。 由于启动流程中开启 notification 时,需要获取全类名,以通过反射获取相关联的 activity,非全类名将导致 notification 创建失败,并收到上述错误提示。没有实现功能,且无其他错误提示。
RXScreenCaptureService 时,RXScreenCaptureService.KEY_RESULT_DATA 是否传入 。使用 Android 5.0 ~ 10 版本 设备进行屏幕共享后自动断开。
上述系统版本的个别 Android 机型可能存在兼容性问题。建议用户升级系统版本。
本文最近更新时的 SDK 版本为 3.50.1。如果你使用的 SDK 为之前版本,请查看以下变动,并进行相应适配。
ScreenVideoEncoderConfig。RTCEngine 变更为 RTCRoom。其他方法的类名由 RTCEngine 变更为 RTCVideo。IRTCEngineEventHandler 变更为 IRTCVideoEventHandler。setVideoEncoderConfig 变更为 setScreenVideoEncoderConfig 。publishScreen 等 API 的参数有变更。