屏幕共享

更新时间: 2026/06/03 11:11:24

在大型会议或在线教育等场景中,为了满足提升沟通效率的需求,主讲人或老师需要将本端屏幕内容分享给远端参会者或在线学生观看。NERTC 鸿蒙 SDK 支持屏幕共享功能,帮助您实时分享本端设备的屏幕内容。

功能介绍

通过 NERTC 鸿蒙 SDK 可以在视频通话或互动直播过程中实现屏幕共享,主播或连麦者可以将自己的屏幕内容,以视频的方式分享给远端参会者或在线观众观看,从而提升沟通效率,一般适用于多人视频聊天、在线会议以及在线教育场景。

  • 视频会议场景中,参会者可以在会议中将本地的文件、数据、网页、PPT 等画面分享给其他与会者,让其他与会者更加直观地了解讨论的内容和主题。
  • 在线课堂场景中,老师可以通过屏幕共享将课件、笔记、教学内容等画面展示给远端学生观看,降低传统教学模式下的沟通成本,提升教育场景的用户体验。

网易云信 NERTC SDK 以辅流的形式实现屏幕共享,即单独为屏幕共享开启一路上行的视频流。摄像头视频流作为主流,屏幕共享视频流作为辅流,两路视频流并行,主播可以同时上行摄像头画面和屏幕画面。

此外 NERTC 鸿蒙 SDK 还支持共享屏幕的同时,也共享本地播放的系统背景音。具体请参考音频共享相关接口 enableLoopBackRecording

注意事项

  • 请使用支持屏幕共享的 NERTC 鸿蒙 SDK 版本。若房间内存在 Android、iOS、Windows、macOS、Web 等其他端,请确保这些端均支持辅流屏幕共享,否则可能因同时发送主流和辅流造成通话异常。
  • 如果您的 App 无法针对所有端进行强制升级,屏幕共享场景中仅部分端支持辅流屏幕共享,为避免兼容性问题,建议保证通话过程中单人同时只有一路上行视频流。当需要将视频流切换为屏幕共享流时,请先关闭本地摄像头视频,再调用 startScreenCapture 启动屏幕共享。反向切换同理。
  • 鸿蒙端调用 startScreenCapture 时不需要传入 IntentMediaProjection 对象,SDK 会通过系统屏幕采集能力触发用户授权流程。用户拒绝授权或系统中断采集时,可通过 onScreenCaptureStatusChanged 回调获知状态。
  • 如果需要在应用退到后台后继续保持会议音频或屏幕共享业务,请按 HarmonyOS 后台任务要求在 module.json5 中配置 backgroundModes,并按下文 后台长时任务处理 开启和停止长时任务。示例工程中配置了 audioRecordingaudioPlayback 后台模式。
  • 屏幕共享本身以视频辅流发送;本地预览或远端观看都需要设置辅流画布。

示例项目源码

网易云信提供 鸿蒙端屏幕共享的示例项目源码 ScreenShare ,您可以参考该源码实现屏幕共享。

本端共享屏幕

API 调用时序

开发 HarmonyOS 应用时,您需要使用 NERTC API 和系统屏幕采集授权流程共同实现屏幕共享。API 调用时序如下图所示:

sequenceDiagram
    participant 应用层
    participant HarmonyOS 系统
    participant NERtcSDK

    应用层 ->> HarmonyOS 系统: startBackgroundRunning 开启后台长时任务(需要退后台继续共享时)
    应用层 ->> NERtcSDK: startScreenCapture 开启屏幕共享
    NERtcSDK ->> HarmonyOS 系统: 请求屏幕采集授权
    HarmonyOS 系统 -->> NERtcSDK: 返回授权结果或采集状态
    NERtcSDK -->> 应用层: onScreenCaptureStatusChanged 通知采集状态

    应用层 ->> NERtcSDK: setupLocalSubStreamVideoCanvas 设置本端辅流画布
    应用层 ->> NERtcSDK: stopScreenCapture 关闭屏幕共享
    应用层 ->> HarmonyOS 系统: stopBackgroundRunning 停止后台长时任务

第一步:配置权限和后台能力

配置权限

基础音视频通话通常需要在 module.json5 中声明摄像头、麦克风和网络相关权限。如果共享屏幕时还需要后台音频采集或播放能力,请同时配置后台模式。

示例配置 如下:

json5{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "backgroundModes": ["audioRecording", "audioPlayback"]
      }
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.CAMERA"
      },
      {
        "name": "ohos.permission.MICROPHONE"
      },
      {
        "name": "ohos.permission.GET_NETWORK_INFO"
      },
      {
        "name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
      }
    ]
  }
}

屏幕采集授权由系统在调用 startScreenCapture 后触发。业务侧仍需按实际使用场景申请摄像头、麦克风、网络和后台运行权限。

后台长时任务处理

如果业务要求用户将 App 切到后台后仍继续共享屏幕或保持会议音频,需要在启动屏幕共享时同步开启 HarmonyOS 后台长时任务,并在屏幕共享结束后及时停止。

startBackgroundRunning传入的后台模式需要与 module.json5backgroundModes 保持一致。

推荐处理流程如下:

  1. 启动屏幕共享前创建 AVSession,并通过 backgroundTaskManager.startBackgroundRunning 开启长时任务。
  2. WantAgent 指向应用入口 Ability,用户点击系统后台任务通知时可以回到应用。
  3. startScreenCapture 返回失败时立即停止长时任务,避免后台任务通知残留。
  4. stopScreenCapture 成功、onScreenCaptureStatusChanged 收到 STOPCANCELABORT 或离开房间时停止长时任务,并销毁 AVSession

示例代码 如下:

tsimport common from '@ohos.app.ability.common';
import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager';
import wantAgent, { WantAgent } from '@ohos.app.ability.wantAgent';
import avSession from '@ohos.multimedia.avsession';
import { BusinessError } from '@ohos.base';

const BUNDLE_NAME = 'com.example.demo';
const ABILITY_NAME = 'EntryAbility';
const SESSION_TAG = 'ScreenShare';
const BACKGROUND_MODES: string[] = ['audioRecording', 'audioPlayback'];

let mediaSession: avSession.AVSession | null = null;
let backgroundRunning = false;
let backgroundStarting = false;
let backgroundStopPending = false;

function startScreenShareBackgroundTask(context: common.UIAbilityContext): void {
  createAVSession(context);
  startBackgroundRunning(context);
}

function stopScreenShareBackgroundTask(context: common.UIAbilityContext): void {
  stopBackgroundRunning(context);
  destroyAVSession();
}

function createAVSession(context: common.UIAbilityContext): void {
  if (mediaSession) {
    return;
  }

  avSession.createAVSession(context, SESSION_TAG, 'video')
    .then((session: avSession.AVSession): void => {
      mediaSession = session;
    })
    .catch((err: BusinessError): void => {
      console.error(`createAVSession failed, code:${err.code}, message:${err.message}`);
    });
}

function destroyAVSession(): void {
  if (!mediaSession) {
    return;
  }

  const session = mediaSession;
  mediaSession = null;
  session.deactivate()
    .then((): Promise<void> => {
      return session.destroy();
    })
    .catch((err: BusinessError): void => {
      console.error(`destroyAVSession failed, code:${err.code}, message:${err.message}`);
    });
}

function startBackgroundRunning(context: common.UIAbilityContext): void {
  if (backgroundRunning || backgroundStarting) {
    return;
  }

  backgroundStarting = true;
  backgroundStopPending = false;
  const wantAgentInfo: wantAgent.WantAgentInfo = {
    wants: [
      {
        bundleName: BUNDLE_NAME,
        abilityName: ABILITY_NAME
      }
    ],
    actionType: wantAgent.OperationType.START_ABILITY,
    requestCode: 0,
    wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
  };

  wantAgent.getWantAgent(wantAgentInfo)
    .then((wantAgentObj: WantAgent): Promise<backgroundTaskManager.ContinuousTaskNotification> => {
      return backgroundTaskManager.startBackgroundRunning(context, BACKGROUND_MODES, wantAgentObj);
    })
    .then((): void => {
      backgroundStarting = false;
      backgroundRunning = true;
      if (backgroundStopPending) {
        stopBackgroundRunning(context);
      }
    })
    .catch((err: BusinessError): void => {
      backgroundStarting = false;
      backgroundRunning = false;
      backgroundStopPending = false;
      console.error(`startBackgroundRunning failed, code:${err.code}, message:${err.message}`);
    });
}

function stopBackgroundRunning(context: common.UIAbilityContext): void {
  if (backgroundStarting && !backgroundRunning) {
    backgroundStopPending = true;
    return;
  }
  if (!backgroundRunning) {
    backgroundStopPending = false;
    return;
  }

  backgroundRunning = false;
  backgroundStopPending = false;
  backgroundTaskManager.stopBackgroundRunning(context)
    .catch((err: BusinessError): void => {
      console.error(`stopBackgroundRunning failed, code:${err.code}, message:${err.message}`);
    });
}

上述示例中的 BUNDLE_NAMEABILITY_NAMEBACKGROUND_MODES 需要替换为您自己的应用配置。长时任务只负责保持后台运行能力,不会替代 startScreenCapture 触发的系统屏幕采集授权。

第二步:开启屏幕共享

监听屏幕共享状态

建议您在初始化 SDK 后设置 NERtcCallbackNERtcCallbackEx,并实现 onScreenCaptureStatusChanged 回调,用于处理用户取消授权、系统中断或共享停止等状态。

NERtcScreenCaptureStatus 状态说明如下表所示。

状态 说明
START 屏幕共享已开始。
STOP 屏幕共享已停止,通常由本端调用停止、用户停止共享、通话中断等触发。
CANCEL 用户取消或拒绝屏幕采集授权。
ABORT 屏幕共享异常中断,例如被其他屏幕采集任务打断。

示例代码 如下:

tsimport { NERtcCallbackEx, NERtcConstants } from '@nertc/nertc_sdk';
import common from '@ohos.app.ability.common';

// 在您已有的 NERtcCallback 或 NERtcCallbackEx 实现中处理此回调。
class ScreenShareCallback extends NERtcCallbackEx {
  private context: common.UIAbilityContext;

  constructor(context: common.UIAbilityContext) {
    super();
    this.context = context;
  }

  onScreenCaptureStatusChanged(status: NERtcConstants.NERtcScreenCaptureStatus): void {
    if (status === NERtcConstants.NERtcScreenCaptureStatus.START) {
      console.info('screen capture started');
    } else if (status === NERtcConstants.NERtcScreenCaptureStatus.STOP) {
      console.info('screen capture stopped');
      stopScreenShareBackgroundTask(this.context);
    } else if (status === NERtcConstants.NERtcScreenCaptureStatus.CANCEL) {
      console.info('screen capture canceled by user');
      stopScreenShareBackgroundTask(this.context);
    } else if (status === NERtcConstants.NERtcScreenCaptureStatus.ABORT) {
      console.info('screen capture aborted');
      stopScreenShareBackgroundTask(this.context);
    }
  }
}

开启屏幕共享

在加入房间之后调用 startScreenCapture 方法开启屏幕共享,以辅流形式发送屏幕共享内容。调用此方法时,您需要通过 NERtcScreenConfiguration 配置本地辅流的编码参数。

NERtcScreenConfiguration 参数说明如下表所示。

参数 参数说明
maxProfile 视频编码的分辨率。常用值包括 kNERtcVideoProfileHD720PkNERtcVideoProfileHD1080P
framerate 视频编码的帧率。常用值包括 kNERtcVideoFrameRateFps7kNERtcVideoFrameRateFps10kNERtcVideoFrameRateFps15kNERtcVideoFrameRateFps24kNERtcVideoFrameRateFps30
minFramerate 视频编码的最小帧率。默认值为 0,表示使用默认的最小帧率。
bitrate 视频编码的码率,单位为 Kbps。若设置为 0,SDK 会自行计算合理码率。
minBitrate 视频编码的最小码率,单位为 Kbps。
contentPrefer 屏幕共享编码策略倾向:kNERtcSubStreamContentPreferMotion 表示动画或动态内容;kNERtcSubStreamContentPreferDetails 表示图片、文字或 PPT 等静态细节内容。
degradationPreference 弱网时的视频降级偏好。默认值为 kNERtcDegradationDefault
preferColorSpaceRange 屏幕共享采集的色彩范围偏好。默认值为 VIDEO_COLOR_SPACE_RANGE_INVALID

常见场景下的参数推荐搭配如下表所示。

参数名称 常规推荐值 一起看视频 共享 PPT
contentPrefer kNERtcSubStreamContentPreferMotion kNERtcSubStreamContentPreferMotion kNERtcSubStreamContentPreferDetails
maxProfile kNERtcVideoProfileHD720P kNERtcVideoProfileHD720P kNERtcVideoProfileHD1080P
framerate kNERtcVideoFrameRateFps7 kNERtcVideoFrameRateFps30 kNERtcVideoFrameRateFps7
bitrate 0 0 0
  • 若屏幕共享内容为静态画面,设置较高帧率并不经济,推荐 7 fps 即可。
  • 若用户要共享的屏幕内容包含大量文字,可以适当提高分辨率和码率设置。
  • contentPrefer 设置为 kNERtcSubStreamContentPreferDetails 时,帧率最高按 10 fps 处理。

示例代码 如下:

tsimport { NERtcConstants, NERtcSDK } from '@nertc/nertc_sdk';
import common from '@ohos.app.ability.common';

function startScreenShare(context: common.UIAbilityContext): void {
  startScreenShareBackgroundTask(context);

  const config = new NERtcConstants.NERtcScreenConfiguration();
  config.maxProfile = NERtcConstants.NERtcVideoProfileType.kNERtcVideoProfileHD720P;
  config.framerate = NERtcConstants.NERtcVideoFrameRate.kNERtcVideoFrameRateFps7;
  config.minFramerate = 0;
  config.bitrate = 0;
  config.minBitrate = 0;
  config.contentPrefer =
    NERtcConstants.NERtcSubStreamContentPrefer.kNERtcSubStreamContentPreferMotion;
  config.degradationPreference =
    NERtcConstants.NERtcDegradationPreference.kNERtcDegradationDefault;
  config.preferColorSpaceRange =
    NERtcConstants.NERtcVideoColorSpaceRange.VIDEO_COLOR_SPACE_RANGE_INVALID;

  const ret = NERtcSDK.getInstance().startScreenCapture(config);
  console.info(`startScreenCapture ret: ${ret}`);
  if (ret !== NERtcConstants.ErrorCode.NO_ERROR) {
    stopScreenShareBackgroundTask(context);
  }
}

第三步:设置本端辅流画布

调用 setupLocalSubStreamVideoCanvas 方法设置本端的辅流视频画布。调用此接口时,您需要传入 NERtcVideoCanvas,其中 canvasId 为页面中已创建的 NERtcVideoView 组件 ID。

您可以在开启屏幕共享前后设置本端辅流画布。若不需要本地预览,也可以不设置。建议在 NERtcVideoViewonLoad 回调中使用同一个 canvasId 设置画布,确保渲染组件已创建。

示例代码 如下:

tsimport { NERtcConstants, NERtcSDK, NERtcVideoView } from '@nertc/nertc_sdk';

@Component
struct LocalScreenPreview {
  private localScreenCanvasId: string = 'local-screen-view';

  private setupLocalScreenCanvas(canvasId: string): void {
    const canvas = new NERtcConstants.NERtcVideoCanvas();
    canvas.canvasId = canvasId;
    canvas.scalingMode = NERtcConstants.NERtcVideoScalingMode.kNERtcVideoScaleFit;
    canvas.mirrorMode = NERtcConstants.NERtcVideoMirrorMode.kNERtcVideoMirrorModeDisabled;

    const ret = NERtcSDK.getInstance().setupLocalSubStreamVideoCanvas(canvas);
    console.info(`setupLocalSubStreamVideoCanvas ret: ${ret}`);
  }

  build() {
    NERtcVideoView({
      canvasId: this.localScreenCanvasId,
      onLoad: (): void => {
        this.setupLocalScreenCanvas(this.localScreenCanvasId);
      },
      onDestroy: (): void => {
      }
    })
  }
}

第四步:停止屏幕共享

若您要结束屏幕共享,请调用 stopScreenCapture 方法关闭辅流形式的屏幕共享。停止成功后,远端会收到 onUserSubStreamVideoStop 回调。

示例代码 如下:

tsimport { NERtcConstants, NERtcSDK } from '@nertc/nertc_sdk';
import common from '@ohos.app.ability.common';

function stopScreenShare(context: common.UIAbilityContext): void {
  const ret = NERtcSDK.getInstance().stopScreenCapture();
  console.info(`stopScreenCapture ret: ${ret}`);
  if (ret === NERtcConstants.ErrorCode.NO_ERROR) {
    NERtcSDK.getInstance().setupLocalSubStreamVideoCanvas(null);
    stopScreenShareBackgroundTask(context);
  }
}

观看远端屏幕共享

API 调用时序

sequenceDiagram
    participant 应用层
    participant NERtcVideoView
    participant NERtcSDK
    NERtcSDK-->>应用层: onUserSubStreamVideoStart 远端用户开启屏幕共享回调
    应用层->>NERtcVideoView: 创建远端辅流视频组件
    NERtcVideoView-->>应用层: onLoad
    应用层->>NERtcSDK: setupRemoteSubStreamVideoCanvas 设置远端辅流视频画布
    应用层->>NERtcSDK: subscribeRemoteSubStreamVideo 订阅远端屏幕共享辅流
    NERtcSDK-->>应用层: onUserSubStreamVideoStop 远端用户关闭辅流回调

实现方法

  1. 远端用户加入房间。
  2. 本端收到 onUserSubStreamVideoStart 回调,表示远端用户开启屏幕共享辅流。
  3. 页面根据远端用户 ID 创建对应的远端辅流 NERtcVideoView
  4. NERtcVideoViewonLoad 回调中调用 setupRemoteSubStreamVideoCanvas 设置远端辅流视频回放画布。
  5. 调用 subscribeRemoteSubStreamVideo 订阅远端屏幕共享辅流,订阅之后才能接收远端辅流视频数据。
  6. 本端收到 onUserSubStreamVideoStop 回调,表示远端用户关闭屏幕共享辅流。

示例代码

tsimport { NERtcCallbackEx, NERtcConstants, NERtcSDK, NERtcVideoView } from '@nertc/nertc_sdk';

interface RemoteScreenSlot {
  uid: bigint;
  canvasId: string;
}

class ScreenShareCallback extends NERtcCallbackEx {
  private page: ScreenShareRoom;

  constructor(page: ScreenShareRoom) {
    super();
    this.page = page;
  }

  onUserSubStreamVideoStart(
    uid: bigint,
    maxProfile: NERtcConstants.NERtcVideoProfileType
  ): void {
    // 回调里只通知页面创建远端辅流 NERtcVideoView。
    this.page.addRemoteScreen(uid);
  }

  onUserSubStreamVideoStop(uid: bigint): void {
    NERtcSDK.getInstance().subscribeRemoteSubStreamVideo(uid, false);
    NERtcSDK.getInstance().setupRemoteSubStreamVideoCanvas(null, uid);
    this.page.removeRemoteScreen(uid);
  }
}

@Component
struct ScreenShareRoom {
  @State remoteScreenSlots: RemoteScreenSlot[] = [];

  addRemoteScreen(uid: bigint): void {
    const found = this.remoteScreenSlots.find((slot: RemoteScreenSlot) => slot.uid === uid);
    if (found) {
      return;
    }
    this.remoteScreenSlots = this.remoteScreenSlots.concat({
      uid,
      canvasId: `remote-screen-${uid.toString()}`
    });
  }

  removeRemoteScreen(uid: bigint): void {
    this.remoteScreenSlots = this.remoteScreenSlots.filter((slot: RemoteScreenSlot) => slot.uid !== uid);
  }

  private attachRemoteScreen(slot: RemoteScreenSlot): void {
    const canvas = new NERtcConstants.NERtcVideoCanvas();
    canvas.canvasId = slot.canvasId;
    canvas.scalingMode = NERtcConstants.NERtcVideoScalingMode.kNERtcVideoScaleFit;
    canvas.mirrorMode = NERtcConstants.NERtcVideoMirrorMode.kNERtcVideoMirrorModeDisabled;

    NERtcSDK.getInstance().setupRemoteSubStreamVideoCanvas(canvas, slot.uid);
    NERtcSDK.getInstance().subscribeRemoteSubStreamVideo(slot.uid, true);
  }

  @Builder
  private RemoteScreenViews() {
    ForEach(this.remoteScreenSlots, (slot: RemoteScreenSlot) => {
      NERtcVideoView({
        canvasId: slot.canvasId,
        onLoad: (): void => {
          this.attachRemoteScreen(slot);
        },
        onDestroy: (): void => {
        }
      })
    }, (slot: RemoteScreenSlot): string => slot.uid.toString())
  }

  build() {
    Column() {
      this.RemoteScreenViews()
    }
  }
}

收到 onUserSubStreamVideoStart 后需要先让页面创建远端辅流对应的 NERtcVideoView,再在该组件的 onLoad 回调中调用 setupRemoteSubStreamVideoCanvassubscribeRemoteSubStreamVideo。取消订阅或远端停止共享时,可以将画布设置为 null 释放渲染资源。

API 参考

方法/回调 功能描述
startScreenCapture 开启屏幕共享,屏幕共享内容以辅流形式发送。
stopScreenCapture 关闭辅流形式的屏幕共享。
setupLocalSubStreamVideoCanvas 设置本端屏幕共享辅流画布。
setupRemoteSubStreamVideoCanvas 设置远端屏幕共享辅流画布。
subscribeRemoteSubStreamVideo 订阅或取消订阅远端屏幕共享辅流。
onScreenCaptureStatusChanged 通知本端屏幕共享采集状态变化。
onUserSubStreamVideoStart 通知本端远端用户开启视频辅流。
onUserSubStreamVideoStop 通知本端远端用户关闭视频辅流。
此文档是否对你有帮助?
有帮助
去反馈
  • 功能介绍
  • 注意事项
  • 示例项目源码
  • 本端共享屏幕
  • API 调用时序
  • 第一步:配置权限和后台能力
  • 配置权限
  • 后台长时任务处理
  • 第二步:开启屏幕共享
  • 监听屏幕共享状态
  • 开启屏幕共享
  • 第三步:设置本端辅流画布
  • 第四步:停止屏幕共享
  • 观看远端屏幕共享
  • API 调用时序
  • 实现方法
  • 示例代码
  • API 参考