You need to enable JavaScript to run this app.
导航

Android 端屏幕共享

最近更新时间2023.11.13 19:57:25

首次发布时间2022.07.14 17:30:18

在实时通信中,如果你希望用户可以分享本端设备的屏幕和设备播放的音频,可以使用 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 版本仅支持屏幕视频采集,不支持屏幕音频采集。

接入流程

API flow

工程配置

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>

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);
    }
}

API 调用

  1. 开启屏幕采集
private 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));
    }
}
  1. 发布/停止发布屏幕流
    在 IRTCVideoEventHandler 实现类的回调方法 onVideoDeviceStateChanged 中调用 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);
        }
    }
}
  1. 监听远端视频首帧解码成功回调
//SDK收到第一帧远端视频解码数据后,用户收到此回调。

@Override
public void onFirstRemoteVideoFrameDecoded(RemoteStreamKey remoteStreamKey, VideoFrameInfo frameInfo) {
    super.onFirstRemoteVideoFrameDecoded(remoteStreamKey, frameInfo);
    runOnUiThread(() -> setRemoteView(remoteStreamKey));
}
  1. 渲染远端的屏幕分享流
private 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);
}
  1. 停止屏幕分享
private void stopScreenShare() {
    // 停止屏幕数据采集
    mInstance.stopScreenCapture();
}
  1. 监听远端移除屏幕分享流回调
// 房间内远端屏幕共享音视频流移除的回调。

@Override
public void onUserUnPublishScreen(String uid, MediaStreamType type, StreamRemoveReason reason) {
    super.onUserUnPublishScreen(uid, type, reason);
    runOnUiThread(() -> removeRemoteView(getScreenShareUserId(uid)));
}

自定义采集屏幕视频流

RTC 强烈建议你使用内部采集。如果你仍然希望使用自定义采集,参看以下步骤。

  1. 实现屏幕音视频流采集逻辑。

  2. 指定为外部输入源。

  3. 调用 pushScreenAudioFramepushScreenVideoFrame 将采集得到的音视频帧推送到 RTC SDK 用于编码传输。

常见问题

  1. 共享屏幕后,对端只能看到画面,听不到音频。

    • 当前的系统版本低于 Android 10,系统不支持屏幕音频采集。
    • 其他 App 独占了屏幕音频捕获。切换至其他 App,查看是否开启了独占设置。
    • 已向系统申请录制权限,并在使用中允许录制请求。
  2. 收到以下错误提示:
    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 创建失败,并收到上述错误提示。
  3. 没有实现功能,且无其他错误提示。

    • 检查启动 RXScreenCaptureService 时,RXScreenCaptureService.KEY_RESULT_DATA 是否传入 。
    • 在Android 清单文件中增加前台服务声明
    • 在代码中启动前台服务
    • 请求录制权限的结果需要作为参数传递给前台服务
  4. 使用 Android 5.0 ~ 10 版本 设备进行屏幕共享后自动断开。
    上述系统版本的个别 Android 机型可能存在兼容性问题。建议用户升级系统版本。

功能变更日志

本文最近更新时的 SDK 版本为 3.50.1。如果你使用的 SDK 为之前版本,请查看以下变动,并进行相应适配。

  • 3.50.1 版本中
    • 设置共享流的编码参数类型名称变更为 ScreenVideoEncoderConfig
    • 自定义屏幕流相关的接口名称和行为有变更。
  • 3.43.1 版本中
    • publishScreenunpublishScreen 的类名由 RTCEngine 变更为 RTCRoom。其他方法的类名由 RTCEngine 变更为 RTCVideo
    • 回调类名由 IRTCEngineEventHandler 变更为 IRTCVideoEventHandler
  • 3.36.1 版本中
    • 设置共享流的编码参数方法由 setVideoEncoderConfig 变更为 setScreenVideoEncoderConfig
    • publishScreen 等 API 的参数有变更。