在实时通信中,如果你希望用户可以分享本端设备的屏幕和设备播放的音频,可以使用 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 版本仅支持屏幕视频采集,不支持屏幕音频采集。
在 3.60 单路流版本中,存在如下变动:
createRTCVideo接口已变更为createRTCEngine。publishScreen和unpublishScreen接口已不存在。请使用publishStreamVideo和publishStreamAudio控制是否发布音视频。如果您已经调用startScreenCapture,那么设置publishStreamVideo(true)将会直接开始发布屏幕流。- 在屏幕准备好后,您会收到
onVideoDeviceStateChanged回调。onUserUnPublishScreen回调已不存在。请使用onUserPublishStreamVideo回调替代。使用streamInfo.isScreen来判断是否为屏幕流。
Android 10 (API 级别 29) 及以上进行屏幕采集需要用到前台服务,在应用的 Android 清单文件中添加如下前台服务声明。
录音权限声明已包含在 RTC SDK 中,App 清单文件无需添加。
<application> ... <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> ... <service android:name="com.ss.bytertc.base.media.screen.RXScreenCaptureService" android:enabled="true" android:exported="false" android:foregroundServiceType="mediaProjection" /> </application>
Android 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","当前系统版本过低,无法支持屏幕共享"); } }
在权限申请的响应中,开启屏幕共享。
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); } }
private void startScreenShare(Intent data) { startRXScreenCaptureService(data); // 编码参数 ScreenVideoEncoderConfig config = new ScreenVideoEncoderConfig(); config.width = 720; config.height = 1280; config.frameRate = 15; config.maxBitrate = 1600; mRTCEngine.setScreenVideoEncoderConfig(config); // 开启屏幕视频数据采集 // 如果在进入房间的时候设置了 isAutoSubscribeVideo = true,那么此步骤将直接开始屏幕共享。 // 否则,您需要调用 publishStreamVideo(true) 来开始共享屏幕。 mRTCEngine.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)); } }
IRTCEngineEventHandler 实现类的回调方法 onVideoDeviceStateChanged 。此步骤不需要调用关于屏幕采集的 API,只需要更新相关 UI。@Override public void onVideoDeviceStateChanged(String deviceId, VideoDeviceType deviceType, int deviceState, int deviceError) { Log.i(TAG, "onVideoDeviceStateChanged, type: " + deviceType + " state:" + deviceState); }
onUserPublishStreamVideo 和 onFirstRemoteVideoFrameDecoded 回调监视视频流和屏幕共享流。建议您在 onFirstRemoteVideoFrameDecoded 回调中设置视频渲染,确保视频渲染在视频解码完成后进行,以避免卡顿。// 房间内远端屏幕共享音视频流移除的回调。 @Override public void onFirstRemoteVideoFrameDecoded(String streamId, StreamInfo streamInfo, VideoFrameInfo frameInfo) { Log.i(TAG, "onUserPublishStreamVideo, streamId: " + streamId); runOnUiThread(() -> { // 设置远端视频渲染视图 setRemoteRenderView(streamId); }); } @Override public void onUserPublishStreamVideo(String streamId, StreamInfo streamInfo, boolean isPublish) { if (!isPublish) { Log.i(TAG, "onUserUnpublishStreamVideo, uid: " + streamId); runOnUiThread(() -> { // 解除远端视频渲染视图绑定 removeRemoteView(streamId); }); } }
private void setRemoteRenderView(String streamId, String roomID, String userID, 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.renderMode = VideoCanvas.RENDER_MODE_HIDDEN; // 设置远端用户视频渲染视图 mRTCEngine.setRemoteVideoCanvas(streamId, videoCanvas); }
private void stopScreenShare() { // 停止屏幕数据采集和屏幕共享 mRTCEngine.stopScreenCapture(); }
RTC 强烈建议你使用内部采集。如果你仍然希望使用自定义采集,参看以下步骤。
实现屏幕音视频流采集逻辑。
指定为外部输入源。调用 setVideoSourceType 设置屏幕视频自定义采集。
调用 pushScreenAudioFrame 将采集得到的音频帧推送到 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 的参数有变更。