本文介绍如何实现 iOS 开播 SDK 的进阶功能。
您已集成 iOS 开播 SDK。详见 集成 iOS 开播 SDK 。
主持人可以实时共享屏幕画面,而无需有线或无线连接的繁琐步骤,大大降低了直播门槛。录屏直播适用于游戏直播或演示效果直播等场景。
注意
完成以下步骤实现该功能:
在工程的 Podfile
文件中添加 ScreenShare
依赖。
# 只集成开播 SDK pod'BDLive', '1.44.0', :subspecs => [ 'LiveStreaming', 'ScreenShare' ] # 同时集成开播 SDK 和观播 SDK pod'BDLive', '1.44.0', :subspecs => [ 'LiveStreaming', 'Viewer', 'ScreenShare' ] end
打开终端窗口,执行 cd
命令进入您的工程目录。执行 pod install
命令安装依赖。
在 Xcode 中,完成以下步骤,创建屏幕共享扩展的 Target。
说明
在 Xcode 中完成以下步骤,实现屏幕共享扩展和 App 之间的数据共享。
进入步骤 3 中创建的扩展 Target 中。
单击 Signing & Capabilities 页签并单击 + Capability。
双击对话框中的 App Groups。
单击 App Groups 区域的 +。
说明
如果您有可用的 App Group,可直接选中该 App Group 而无需新建。请将该 App Group 的 Container ID 记录在某个安全的地方。在步骤 6 和 7 中,您必须将该 Container ID 传入 SDK。
在弹出的对话框中,输入以 group.
开头的 Container ID 并单击 OK。
注意
请将该 Container ID 记录在某个安全的地方。在步骤 6 和 7 中,您必须将该 Container ID 传入 SDK。
App Group 新建成功后,会自动在 App Groups 区域中被选中。
进入 App Target 中,重复步骤 4-b 和 4-c。在 App Groups 区域中,选择在扩展 Target 中被选中的 App Group。
在 Xcode 中,完成以下步骤,实现屏幕采集的逻辑。
VolcEngineRTCScreenCapturer.xcframework
的嵌入方法设置为 Do Not Embed。说明
录屏直播功能仅对 Deployment Info 区域选定版本及以上的 iOS 设备可见。
在步骤 3 中创建的扩展 Target 中,Xcode 自动创建以下文件:SampleHandler.h
和 SampleHandler.m
。在 Xcode 中打开 SampleHandler.m
文件,实现屏幕采集逻辑。示例代码如下所示。
#import "SampleHandler.h" #import <VolcEngineRTCScreenCapturer/ByteRTCScreenCapturerExt.h> @interface SampleHandler () <ByteRtcScreenCapturerExtDelegate> @property (nonatomic, assign) BOOL shouldScreenShare; @end @implementation SampleHandler - (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo { // groupId:步骤 4 中选中的 App Group 的 Container ID [[ByteRtcScreenCapturerExt shared] startWithDelegate:self groupId:xxx]; // 如果主持人在 App 的预览页(即可选择录屏直播的页面)中,屏幕共享扩展将收到 App 发出的 onNotifyAppRunning 回调。如果扩展在 2 秒内没有收到 onNotifyAppRunning 回调,则认为主持人未在 App 的预览页,您应该通过调用 finishBroadcastWithError: 方法停止屏幕采集 // 按需自定义 NSLocalizedFailureReasonErrorKey 的值 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (!self.shouldScreenShare) { NSDictionary *dic = @{ NSLocalizedFailureReasonErrorKey : @"The host is not in the live room"}; NSError *error = [NSError errorWithDomain:RPRecordingErrorDomain code:RPRecordingErrorUserDeclined userInfo:dic]; [self finishBroadcastWithError:error]; } }); } - (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType { // 混流 switch (sampleBufferType) { case RPSampleBufferTypeVideo: // 采集到的屏幕视频流 case RPSampleBufferTypeAudioApp: // 采集到的设备音频流 [[ByteRtcScreenCapturerExt shared] processSampleBuffer:sampleBuffer withType:sampleBufferType]; // 使用该方法,将采集到的屏幕视频流和设备音频流推送至观看页 break; case RPSampleBufferTypeAudioMic: // 采集到的麦克风音频流 // 开播 SDK 实现了麦克风音频流的采集并将其推送至观看页。因此,您无需在此处理麦克风音频流 break; default: break; } } /// 在主持人停止共享屏幕时触发该回调,通知您停止屏幕采集 - (void)onQuitFromApp { // 按需自定义 NSLocalizedFailureReasonErrorKey 的值 NSDictionary *dic = @{ NSLocalizedFailureReasonErrorKey : @"You stopped sharing the screen"}; NSError *error = [NSError errorWithDomain:RPRecordingErrorDomain code:RPRecordingErrorUserDeclined userInfo:dic]; [self finishBroadcastWithError:error]; } /// App 在后台被终止时触发该回调,通知您停止屏幕采集 - (void)onSocketDisconnect { // 按需自定义 NSLocalizedFailureReasonErrorKey 的值 NSDictionary *dic = @{ NSLocalizedFailureReasonErrorKey : @"Disconnected"}; NSError *error = [NSError errorWithDomain:RPRecordingErrorDomain code:RPRecordingErrorUserDeclined userInfo:dic]; [self finishBroadcastWithError:error]; } /// 检测到 App 正在运行时触发该回调 - (void)onNotifyAppRunning { self.shouldScreenShare = YES; } @end
在 Xcode 的 App Target 中,打开定义如何进入直播间的文件并添加以下代码。详见进入直播间。
if (@available(iOS 12.0, *)) { model.extensionBundleId = @"BUNDLE_ID"; // 将 BUNDLE_ID 替换为屏幕共享扩展的 Bundle Identifier model.groupId = @"GROUP_ID"; // 将 GROUP_ID 替换为步骤 4 中选中的 App Group 的 Container ID }
收到自定义的 IM(即时消息)信令。
您可以通过观播 SDK 的信道在进入直播间期间触发您的界面逻辑,例如即时动画、自行实现的抽奖等。
自定义的 IM 信令收到回调。
- (void)onReceiveIMString:(NSString *)string;
参数
名称 | 类型 | 说明 |
---|---|---|
string | NSString | 通过 SendCustomSystemMessageAPI 接口发送的自定义的 IM 信令。 |
IM 建立连接成功回调。
- (void)onConnected;
IM 建立连接失败回调。
- (void)onConnectFailed;
新增、修改或删除美颜。
注意
以下示例代码新增、修改、删除了指定美颜:
model.effectConfig.customizeBeauties = ^(NSMutableArray<BDLEffectBeautyModel *> * _Nonnull beauties) { // 美颜文件的 bundle 目录 NSString *beautyPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"ComposeMakeup.bundle"]; [beauties enumerateObjectsUsingBlock:^(BDLEffectBeautyModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { // 修改美颜在开播页的图标名称和样式。此处以修改 beautyKey 为 whiten 的美颜为例。请联系企业直播技术支持获取美颜对应的 beautyKey if ([obj.beautyKey isEqualToString:@"whiten"]) { obj.name = [NSString stringWithFormat:@"TT %@", beauties.firstObject.name]; // 美颜图标在开播页展示的名称 // 您可以通过 iconURL(图片的本地路径或网络地址)或 iconName(assets 中的图片名称)设置美颜图标。此处以通过 iconName 设置美颜图标为例 obj.iconURL = nil; obj.iconName = @"uncheck"; *stop = YES; } }]; [beauties.copy enumerateObjectsUsingBlock:^(BDLEffectBeautyModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { // 删除美颜。此处以删除 beautyKey 为 Internal_Deform_Overall 的美颜为例。请联系企业直播技术支持获取美颜对应的 beautyKey if ([obj.beautyKey isEqualToString:@"Internal_Deform_Overall"]) { [beauties removeObject:obj]; *stop = YES; } }]; // 新增美颜 BDLEffectBeautyModel *model = [[BDLEffectBeautyModel alloc] init]; // 美颜图标在开播页展示的名称 model.name = @"Name"; // 美颜文件的资源目录。请联系 CV 技术支持获取美颜文件 model.resFilePath = [beautyPath stringByAppendingPathComponent:@"ComposeMakeup/reshape_lite"]; // 您可以通过 iconURL(图片的本地路径或网络地址)或 iconName(assets 中的图片名称)设置美颜图标。此处以通过 iconName 设置美颜图标为例 model.iconName = @"bdl_bottom_button_product"; // 您可以通过 iconURL(图片的本地路径或网络地址)或 iconName(assets 中的图片名称)设置美颜图标。此处以通过 iconURL 设置美颜图标为例 // model.iconURL = [NSURL fileURLWithPath:[beautyPath stringByAppendingPathComponent:@"icon/icon_naixiong.png"]]; // 美颜对应的 beautyKey。请联系企业直播技术支持获取 model.beautyKey = @"Internal_Deform_Overall"; // 美颜配置是否正确 BOOL isValid = [model isValid]; if (!isValid) { NSLog(@"Model is not Valid, model: %@", model); } // 将美颜添加至美颜列表中的第一个 [beauties insertObject:model atIndex:0]; };
新增、修改或删除滤镜。
注意
以下示例代码新增、修改、删除了指定滤镜:
model.effectConfig.customizeFilters = ^(NSMutableArray<BDLEffectFilterModel *> * _Nonnull filters) { // 滤镜文件的 bundle 目录 NSString *filterPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"FilterResource.bundle"]; [filters enumerateObjectsUsingBlock:^(BDLEffectFilterModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { // 修改滤镜在开播页的图标名称和样式。此处以修改滤镜 ID 为 01 的滤镜为例 if ([obj.filterId isEqualToString:@"01"]) { obj.name = [NSString stringWithFormat:@"TT %@", filters.firstObject.name]; // 滤镜图标在开播页展示的名称 // 您可以通过 iconURL(图片的本地路径或网络地址)或 iconName(assets 中的图片名称)设置滤镜图标。此处以通过 iconName 设置滤镜图标为例 obj.iconURL = nil; obj.iconName = @"AppIcon"; *stop = YES; } }]; // 删除滤镜。此处以删除滤镜 ID 为 33 的滤镜为例 [filters.copy enumerateObjectsUsingBlock:^(BDLEffectFilterModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj.filterId isEqualToString:@"33"]) { [filters removeObject:obj]; *stop = YES; } }]; // 新增滤镜 BDLEffectFilterModel *model = [[BDLEffectFilterModel alloc] init]; // 滤镜图标在开播页展示的名称 model.name = @"Name"; // 滤镜文件的资源目录。请联系 CV 技术支持获取滤镜文件 model.resFilePath = [filterPath stringByAppendingPathComponent:@"Filter/Filter_33_L1"]; // 您可以通过 iconURL(图片的本地路径或网络地址)或 iconName(assets 中的图片名称)设置滤镜图标。此处以通过 iconName 设置滤镜图标为例 model.iconName = @"AppIcon"; // 您可以通过 iconURL(图片的本地路径或网络地址)或 iconName(assets 中的图片名称)设置滤镜图标。此处以通过 iconURL 设置滤镜图标为例 // model.iconURL = [NSURL fileURLWithPath:[filterPath stringByAppendingPathComponent:@"icon/xx.png"]]; // 滤镜配置是否正确 BOOL isValid = [model isValid]; if (!isValid) { NSLog(@"Model is not Valid, model: %@", model); } // 将滤镜添加至滤镜列表中的第一个 [filters insertObject:model atIndex:0]; };
新增、修改或删除道具贴纸。
注意
以下示例代码新增、修改、删除了指定道具贴纸:
model.effectConfig.customizeStickers = ^(NSMutableArray<BDLEffectStickerModel *> * _Nonnull stickers) { // 贴纸文件的 bundle 目录 NSString *stickerPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"StickerResource.bundle"]; // 修改道具贴纸在开播页的图标样式。此处以修改文件目录包含 stickers_sd_gan 的贴纸为例 [stickers enumerateObjectsUsingBlock:^(BDLEffectStickerModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj.resFilePath containsString:@"stickers_sd_gan"]) { // 您可以通过 iconURL(图片的本地路径或网络地址)或 iconName(assets 中的图片名称)设置贴纸图标。此处以通过 iconName 设置贴纸图标为例 obj.iconURL = nil; obj.iconName = @"check"; *stop = YES; } }]; // 删除道具贴纸。此处以删除文件目录包含 tryon_nail_zhuanhongse 的贴纸为例 [stickers.copy enumerateObjectsUsingBlock:^(BDLEffectStickerModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj.resFilePath containsString:@"tryon_nail_zhuanhongse"]) { [stickers removeObject:obj]; *stop = YES; } }]; // 新增道具贴纸 BDLEffectStickerModel *model = [[BDLEffectStickerModel alloc] init]; // 贴纸文件的资源目录。请联系 CV 技术支持获取贴纸文件 model.resFilePath = [stickerPath stringByAppendingPathComponent:@"stickers/tryon_nail_zhuanhongse"]; // 您可以通过 iconURL(图片的本地路径或网络地址)或 iconName(assets 中的图片名称)设置贴纸图标。此处以通过 iconName 设置贴纸图标为例 // model.iconName = @"AppIcon"; // 您可以通过 iconURL(图片的本地路径或网络地址)或 iconName(assets 中的图片名称)设置贴纸图标。此处以通过 iconURL 设置贴纸图标为例 model.iconURL = [NSURL fileURLWithPath:[stickerPath stringByAppendingPathComponent:@"icon/xx.png"]]; // 贴纸配置是否正确 BOOL isValid = [model isValid]; if (!isValid) { NSLog(@"Model is not Valid, model: %@", model); } // 将贴纸添加至贴纸列表中的第一个 [stickers insertObject:model atIndex:0]; };