项目设置
- 在你的项目文件夹根目录,前往 /ios 文件夹,双击 Runner.xcworkspace 文件使用 Xcode 打开。
- 在 Xcode 菜单栏,点击 File > New > Target..., 在弹出的窗口中选择 Broadcast Upload Extension, 点击 Next。
3.在弹出的窗口中填写 Product Name 等信息,取消勾选 Include UI Extension,点击 Finish。Xcode 会自动创建该 Extension 的文件夹,其中包含 SampleHandler.m 文件。Product Name 可以起名为 “ScreenSharing”
4.在 Target 下选中刚创建的 Extension,点击 General,在 Deployment Info 下将 iOS 的版本设置为 12.0 或以上。
5.修改项目设置以实现屏幕共享的代码逻辑。根据实际业务需求选择以下两种方式其中之一即可:
- 如果你只需使用声网提供的 AgoraReplayKitExtension.xcframework 动态库中的功能,修改方式为:选中 Target 为刚刚创建的 Extension,在 Info 中将 NSExtension > NSExtensionPrincipalClass 所对应的 Value 从 SampleHandler 改为 AgoraReplayKitHandler。
6.修改项目中的 /ios/Podfile 文件,找到 target 'Runner' do 这一行,参考以下代码进行修改。随后在终端中 cd 到"项目根目录/ios"文件夹下,执行命令 "pod install"
# Uncomment this line to define a global platform for your project
platform :ios, '12.0'
# ... 省略若干代码 ...
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
# 注意: 这个 target 要和你在 Xcode 里创建的 target 名称一致
target 'ScreenSharing' do
use_frameworks!
use_modular_headers!
inherit! :search_paths
end
end
# ... 省略若干代码 ...
7.跟据您 iOS 项目初始的原生语言区别(Swift 或者 Objective-C),修改 AppDelegate.swift 或者 AppDelegate.m 文件。
AppDelegate.swift 文件参考如下:
import Flutter
import ReplayKit
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 在低电量模式下,系统只会强制保持屏幕亮30秒
// 我们保持屏幕常亮以帮助内部测试
application.isIdleTimerDisabled = true
let controller = window?.rootViewController as! FlutterViewController
let screensharingIOSChannel = FlutterMethodChannel(name: "example_screensharing_ios", binaryMessenger: controller.binaryMessenger)
screensharingIOSChannel.setMethodCallHandler { [weak self] call, result in
switch call.method {
case "showRPSystemBroadcastPickerView":
self?.showRPSystemBroadcastPickerView()
result(nil)
default:
result(FlutterMethodNotImplemented)
}
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// 这个原生方法会弹出屏幕录制选项框,方便用户选择
private func showRPSystemBroadcastPickerView() {
if #available(iOS 12.0, *) {
DispatchQueue.main.async {
if let url = Bundle.main.url(forResource: nil, withExtension: "appex", subdirectory: "PlugIns"),
let bundle = Bundle(url: url)
{
let picker = RPSystemBroadcastPickerView(frame: CGRect(x: 0, y: 0, width: 100, height: 200))
picker.showsMicrophoneButton = true
picker.preferredExtension = bundle.bundleIdentifier
for view in picker.subviews {
if let button = view as? UIButton {
button.sendActions(for: .allTouchEvents)
}
}
}
}
}
}
}
AppDelegate.m 文件参考如下:
#import <Flutter/Flutter.h>
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
#import <ReplayKit/ReplayKit.h>
#import "VideoRawDataController.h"
@interface AppDelegate ()
@property(nonatomic, strong, nullable) VideoRawDataController *videoRawDataController;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// On low power mode, the system force keep the screen on for 30s only,
// we keep the screen on to help internal testing.
[application setIdleTimerDisabled: YES];
FlutterViewController* controller = (FlutterViewController*) self.window.rootViewController;
FlutterMethodChannel* screensharingIOSChannel = [FlutterMethodChannel
methodChannelWithName:@"example_screensharing_ios"
binaryMessenger:controller.binaryMessenger];
[screensharingIOSChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if (@available(iOS 12.0, *)) {
dispatch_async(dispatch_get_main_queue(), ^{
// 这个原生方法会弹出屏幕录制选项框,方便用户选择
NSURL *url = [[NSBundle mainBundle] URLForResource:nil withExtension:@"appex" subdirectory:@"PlugIns"];
NSBundle *bundle = [NSBundle bundleWithURL:url];
if (bundle) {
RPSystemBroadcastPickerView *picker = [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(0, 0, 100, 200)];
picker.showsMicrophoneButton = YES;
picker.preferredExtension = bundle.bundleIdentifier;
for (UIView *view in [picker subviews]) {
if ([view isKindOfClass:UIButton.class]) {
[((UIButton*)view) sendActionsForControlEvents:UIControlEventAllTouchEvents];
}
}
}
});
}
}];
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
8.项目配置完成,接下来在 Flutter Dart 代码中可以参考以下代码进行屏幕分享
final _iosScreenShareChannel = const MethodChannel('example_screensharing_ios');
late RtcEngineEx _engine;
void initEngine() async {
// 创建 RtcEngine
_engine = createAgoraRtcEngineEx();
// 初始化 RtcEngine,设置频道场景为 channelProfileLiveBroadcasting(直播场景)
await _engine.initialize(RtcEngineContext(
appId: '替换为您的 appID',
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
));
// 添加回调事件
_engine.registerEventHandler(
RtcEngineEventHandler(
// 成功加入频道回调
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
// 成功加入频道后,立刻开始屏幕分享
startSharing();
}),
);
await _engine.enableVideo();
await _engine.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
// 加入频道
await _engine.joinChannelEx(
connection: RtcConnection(channelId: "替换为您的 channelId", localUid: "替换为您的 int 类型 Uid"),
token: token,
options: const ChannelMediaOptions(
// 自动订阅所有音频流
autoSubscribeAudio: true,
// 发布麦克风采集的音频
publishMicrophoneTrack: true,
publishScreenTrack: true,
publishScreenCaptureAudio: false,
publishScreenCaptureVideo: true,
// 设置用户角色为 clientRoleBroadcaster(主播)或 clientRoleAudience(观众)
clientRoleType: ClientRoleType.clientRoleBroadcaster),
);
}
// 开始屏幕分享
void startSharing() async {
await _engine.startScreenCapture(
const ScreenCaptureParameters2(captureAudio: true, captureVideo: true));
if (Platform.isIOS) {
// 调用 iOS 端的原生代码弹出屏幕共享选择框,这个方法虽然是 Futrue 签名,但是调用时不要添加 await 关键字,防止阻塞无法执行下一步
_iosScreenShareChannel.invokeMethod('showRPSystemBroadcastPickerView');
}
// 必须要执行 updateChannelMediaOptionsEx,观看端才会看到画面更新
await _updateScreenShareChannelMediaOptions(true);
}
// 停止屏幕分享
void stopScreenSharing() async {
await _engine.stopScreenCapture();
await _updateScreenShareChannelMediaOptions(false);
}
Future<void> _updateScreenShareChannelMediaOptions(bool isOpen) async {
final shareShareUid = "替换为您的 int 类型 Uid";
await _engine.updateChannelMediaOptionsEx(
options: ChannelMediaOptions(
// 自动订阅所有音频流
autoSubscribeAudio: true,
// 发布麦克风采集的音频
publishMicrophoneTrack: true,
// 屏幕分享
publishScreenTrack: isOpen,
publishScreenCaptureAudio: false,
publishScreenCaptureVideo: isOpen,
publishSecondaryScreenTrack: isOpen,
publishCameraTrack: false,
clientRoleType: ClientRoleType.clientRoleBroadcaster,
),
connection: RtcConnection(channelId: roomID, localUid: shareShareUid),
);
}
9.观看端显示屏幕分享的Flutter Dart 代码可以参考以下 Widget 用法
AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: engine, // 您的 engine 实例
canvas: VideoCanvas(
uid: "替换为屏幕分享者的 uid",
renderMode: RenderModeType.renderModeFit),
connection: RtcConnection(
channelId: "替换为您的 channelId",
localUid: "替换为您的 localUid")))
赞赞赞,优化文档就靠你了!