You need to enable JavaScript to run this app.
文档中心
实时音视频

实时音视频

复制全文
下载 pdf
视频管理
移动端多前台任务
复制全文
下载 pdf
移动端多前台任务
移动端用户可以通过悬浮的小窗口边观看视频、收听音频,边浏览主屏幕或与其他应用进行交互,实现多前台任务处理。​
如果你希望在应用内实现悬浮窗口布局,可以通过 setLocalVideoCanvas 和 setRemoteVideoCanvas 设置画布大小和位置实现,参考 设置视频参数。例如,在 1 v 1 音视频通话中,将远端画面作为背景铺满设备屏幕,同时在屏幕一角展示本端画面。​
前提条件​
  • 你已经集成 RTC SDK,实现了基本的音视频通话。​
  • 设备要求:​
  • iOS 16 及以上版本​
  • Android 8.0 及以上版本,API 级别 26​
Android 端功能实现​
你可以通过构建悬浮窗口在 Android 端实现前台多任务处理。悬浮窗口既可以用于播放视频,也可以播放纯音频。​
你还可以通过 Android 的画中画功能实现多前台任务。​
  1. 检查是否有悬浮窗展示权限,如果没有权限需跳转到设置中心开启。​
​
// 悬浮窗需要先请求权限​
private void requestFloatingWindowPermission() {​
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {​
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));​
startActivityForResult(intent, REQUEST_CODE_FLOATING_WINDOW);​
} else {​
// 已经获得了悬浮窗口权限,可以添加悬浮窗口​
addFloatingWindow();​
}​
}​
​
@Override​
protected void onActivityResult(int requestCode, int resultCode, Intent data) {​
super.onActivityResult(requestCode, resultCode, data);​
if (requestCode == REQUEST_CODE_FLOATING_WINDOW) {​
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this)) {​
addFloatingWindow();​
} else {​
ToastUtil.showAlert(this, "未授予悬浮窗口权限,无法添加悬浮窗口");​
}​
}​
}​
​
@Override​
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {​
super.onRequestPermissionsResult(requestCode, permissions, grantResults);​
if (requestCode == REQUEST_CODE_FLOATING_WINDOW) {​
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {​
addFloatingWindow();​
} else {​
ToastUtil.showAlert(this, "未授予悬浮窗口权限,无法添加悬浮窗口");​
}​
}​
}​
​
  1. 在 layout_float_window 中构建悬浮窗对应 UI。​
​
private View createFloatWindowView(Context context, @LayoutRes int floatWindowLayoutResId) {​
return LayoutInflater.from(context).inflate(R.layout.layout_float_window, null);​
}​
​
  1. 构建悬浮窗 Window。​
​
public FloatWindowManager(Context context, TextureView mainTextureView) {​
this.context = context;​
this.mainTextureView = mainTextureView;​
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);​
DisplayMetrics displayMetrics = new DisplayMetrics();​
windowManager.getDefaultDisplay().getMetrics(displayMetrics);​
int windowSizeInPixel = convertDpToPixel(windowSizeInDp, displayMetrics.density);​
windowParams = new WindowManager.LayoutParams(​
windowSizeInPixel,​
windowSizeInPixel,​
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,​
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,​
PixelFormat.TRANSLUCENT​
);​
windowParams.gravity = Gravity.TOP | Gravity.START;​
​
// 视频渲染 View 的容器,将 SDK 进行视频渲染的目标 View 添加到此 ViewGroup 中​
floatView = new FrameLayout(context);​
closeButton = new Button(context);​
closeButton.setText("Close");​
floatView.addView(closeButton, new FrameLayout.LayoutParams(​
FrameLayout.LayoutParams.WRAP_CONTENT,​
FrameLayout.LayoutParams.WRAP_CONTENT,​
Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL​
));​
floatView.setBackgroundResource(R.color.grey);​
isWindowOpen = false;​
​
// 悬浮窗拖动监听​
floatView.setOnTouchListener(new View.OnTouchListener() {​
@Override​
public boolean onTouch(View v, MotionEvent event) {​
switch (event.getAction()) {​
case MotionEvent.ACTION_DOWN:​
initialTouchX = event.getRawX();​
initialTouchY = event.getRawY();​
initialWindowX = windowParams.x;​
initialWindowY = windowParams.y;​
return true;​
case MotionEvent.ACTION_MOVE:​
float dx = event.getRawX() - initialTouchX;​
float dy = event.getRawY() - initialTouchY;​
windowParams.x = (int) (initialWindowX + dx);​
windowParams.y = (int) (initialWindowY + dy);​
windowManager.updateViewLayout(floatView, windowParams);​
return true;​
}​
return false;​
}​
});​
}​
​
  1. 通过 WindowManager 展示悬浮窗。​
需要将 RTC SDK 渲染远端视频的 View(TextureView 或 SurfaceView) 添加到 Window 中。​
​
public void openWindow() {​
if (!isWindowOpen) {​
floatView.addView(mainTextureView);​
windowManager.addView(floatView, windowParams);​
isWindowOpen = true;​
}​
}​
​
  1. 渲染视频,以渲染远端视频为例。​
​
private void setRemoteRenderView(String streamId) {​
if (textureView.getParent() == null) {​
remoteViewContainer.removeAllViews();​
remoteViewContainer.addView(textureView);​
}​
VideoCanvas videoCanvas = new VideoCanvas();​
videoCanvas.renderView = textureView;​
videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN;​
​
// 设置远端视频渲染视图​
rtcEngine.setRemoteVideoCanvas(streamId, videoCanvas);​
}​
​
  1. 关闭悬浮窗。​
​
// 关闭悬浮窗,并将 SDK 渲染的视频 View 添加到 activity 的页面中​
private void closeFloatingWindow() {​
floatWindowManager.closeWindow();​
if (textureView.getParent() == null) {​
remoteViewContainer.addView(textureView);​
}​
}​
​
public void closeWindow() {​
if (isWindowOpen) {​
floatView.removeView(mainTextureView);​
windowManager.removeView(floatView);​
isWindowOpen = false;​
}​
}​
​
iOS 端功能实现​
iOS 端可以通过画中画 (Picture in Picture)功能实现前台多任务处理。​
你也可以利用 UIWindow 创建悬浮窗口,实现该功能。​
  1. 配置项目开启画中画选项。​
​Image​​
  1. 创建画中画控制器。​
​
func setupPipController(with sourceView: UIView) {​
​
if #available(iOS 16, *) {​
let callViewController = AVPictureInPictureVideoCallViewController()​
callViewController.preferredContentSize = CGSize(width: 360, height: 640)​
callViewController.view.backgroundColor = UIColor.clear​
​
let source = AVPictureInPictureController.ContentSource(activeVideoCallSourceView: sourceView, contentViewController: callViewController)​
​
let pipVC = AVPictureInPictureController(contentSource: source)​
pipVC.canStartPictureInPictureAutomaticallyFromInline = true​
pipVC.delegate = self​
self.pipVC = pipVC​
} else {​
ToastComponents.shared.show(withMessage: "当前系统不支持画中画功能")​
}​
}​
​
  1. 使用系统 API 开启画中画。​
​
@objc func startPip() {​
if self.pipVC!.isPictureInPictureActive {​
self.pipVC!.stopPictureInPicture() // 关闭画中画​
} else {​
self.pipVC!.startPictureInPicture()​
}​
}​
​
  1. 开启画中画,将 Canvas.view 加载到画中画上。​
​
// 画中画已经开始​
func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {​
​
ToastComponents.shared.show(withMessage: "pictureInPictureControllerDidStart")​
​
if #available(iOS 16.0, *) {​
if let vc = pictureInPictureController.contentSource?.activeVideoCallContentViewController {​
vc.view.addSubview(self.customRenderView)​
self.customRenderView.translatesAutoresizingMaskIntoConstraints = false​
NSLayoutConstraint.activate([​
self.customRenderView.topAnchor.constraint(equalTo: vc.view.topAnchor),​
self.customRenderView.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor),​
self.customRenderView.leadingAnchor.constraint(equalTo: vc.view.leadingAnchor),​
self.customRenderView.trailingAnchor.constraint(equalTo: vc.view.trailingAnchor)​
])​
}​
}​
}​
​
  1. 开启外部渲染。使用内部渲染会导致黑屏。​
​
// 注册外部渲染​
func bindRemoteRenderView(view: CustomVideoRenderView, streamId: String) {​
// 使用外部渲染​
let sinkConfig = ByteRTCRemoteVideoSinkConfig()​
sinkConfig.requiredPixelFormat = .original​
self.rtcEngine?.setRemoteVideoSink(streamId, withSink: self.customRenderView, withRemoteRenderConfig: sinkConfig)​
}​
​
示例项目​
你可以前往示例项目,查看其中的源代码。​
API 参考​
​
​
平台​
Android​
iOS​
设置本端画面​
设置远端画面​
设置远端外部渲染器​
​
​
最近更新时间:2026.08.19 15:47:47
这个页面对您有帮助吗?
有用
有用
无用
无用