iOS屏幕共享

作者: 新生代农民工No1 | 来源:发表于2021-07-10 21:18 被阅读0次

前言:
由于最近项目中需要使用到屏幕共享,所以对iOS屏幕共享进行了一番调研,在这里也分享下踩坑之路。

屏幕共享简介

屏幕共享是把屏幕上的内容分享给其他人观看,这分为两项关键技术:

  • 屏幕内容采集:涉及到系统权限,以及需要系统提供采集屏幕内容的Api;(苹果提供的屏幕共享框架ReplayKit
  • 流媒体服务:通过采集到的视频流以及音频流推送给流媒体服务器广播给用户;(通常是以RTC的推流形式进行处理)

屏幕共享的应用场景:
手机游戏直播、客服指导、商务会议、教学白板等;

屏幕共享采集:

  • 添加Target

  • 创建Broadcast Upload Extension

  • 添加AppGroups

可以在苹果开发者官网申请AppGroupID,并且把相关的profile文件,证书相关联,也可以在Xcode中添加后,由Xcode自动生成;
添加AppGroups需要在主App中和拓展中都添加;

  • 添加完成后
    添加完

最终项目中会存在两个文件,AppGroupID要保持一致;

到这里就可以开始处理屏幕共享了,但是由于涉及到进程之间(主App与拓展App之间)的通信问题,所以这里采用通知的方式来处理开始、结束等事件;对于屏幕共享采集数据目前有采用NSUserDefaultSocket等方式,但是鉴于苹果扩展App有50M的内存限制,如果不需要帧数太高,可以使用NSUserDefault传输sampleBuffer。但是如果对屏幕共享要求很高(帧率高、分辨率高),使用Socket可以越过内存的限制,可以避免扩展程序被系统强制KILL,但Socket有一定的不稳定性,需要额外处理断线以及网络异常等问题;

推荐的进程通信方式:

  • 事件通知:CFNotification;
  • 简单的值传递:NSUserDefault
  • 复杂的数据传递:Socket
开始屏幕共享

这个方法有不确定性,🤷不知道未来某天是否还能使用,能用就先用吧!苹果并没有给出一个明确的方法,只有一个控件可以调起屏幕共享弹窗,所以这里是以一种取巧的方式封装起来的方法,给App调用;
在调起屏幕共享后,到我们真正开启屏幕共享,还有一步用户确认操作,所以我们需要知道屏幕共享扩展程序的事件回调后,再去处理某些逻辑,才能真正形成闭环;

// 系统弹窗
- (RPSystemBroadcastPickerView *)systemPicker {
    if (!_systemPicker) {
        RPSystemBroadcastPickerView* picker =
        [[RPSystemBroadcastPickerView alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
        picker.showsMicrophoneButton = NO;
        picker.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin;
        _systemPicker = picker;
    }
    return _systemPicker;
}

- (void)launchBroadcaster API_AVAILABLE(ios(12.0)) {
    NSArray *contents = [NSFileManager.defaultManager contentsOfDirectoryAtPath:plugInPath error:nil];
    for (NSString *content in contents) {
        NSURL *url = [NSURL fileURLWithPath:plugInPath];
        NSBundle *bundle = [NSBundle bundleWithPath:[url URLByAppendingPathComponent:content].path];
        
        NSDictionary *extension = [bundle.infoDictionary objectForKey:@"NSExtension"];
        if (extension == nil) { continue; }
        NSString *identifier = [extension objectForKey:@"NSExtensionPointIdentifier"];
        if ([identifier isEqualToString:@"com.apple.broadcast-services-upload"]) {
            self.systemPicker.preferredExtension = bundle.bundleIdentifier;
            break;
        }
    }
    for (UIView *view in self.systemPicker.subviews) {
        UIButton *button = (UIButton *)view;
        [button sendActionsForControlEvents:UIControlEventAllTouchEvents];
    }
}
停止屏幕共享

停止屏幕共享方法是由ReplayKit扩展中的提供的,从MainApp中无法直接调用,可以通过通知的方式去调用ExtensionApp 的方法;

// MainApp 发送通知给 ExtensionApp, 扩展程序收到通知调用停止方法
// MainApp
- (void)stopBroadcaster {
    CFNotificationName notificationName = CFNotificationName(TScreenShareHostRequestStopNotification);
    CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), notificationName, nil, nil, true);
}

// ExtensionApp
// 监听通知
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                    (__bridge const void *)(self),
                                    onHostRequestFinishBroadcast,
                                    (__bridge CFStringRef)ScreenShareHostRequestStopNotification,
                                    NULL,
                                    CFNotificationSuspensionBehaviorDeliverImmediately);

static void onHostRequestFinishBroadcast(CFNotificationCenterRef center,
                                void *observer,
                                CFStringRef name,
                                const void *object,
                                         CFDictionaryRef
                                         userInfo) {
    ScreenShareSampleHandler *self = (__bridge ScreenShareSampleHandler *)(observer);
    NSError *error = [NSError errorWithDomain:NSStringFromClass(self.class)
                                         code:0
                                     userInfo:@{
                                         NSLocalizedFailureReasonErrorKey:NSLocalizedString(@"您已停止屏幕共享。", nil)
                                     }];
    [self finishBroadcastWithError:error];
}
ExtensionApp事件回调
- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
    // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
    CFNotificationName notificationName = CFNotificationName(ScreenShareBroadcastStartedNotification);
    CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), notificationName, nil, nil, true);
}

- (void)broadcastPaused {
    // User has requested to pause the broadcast. Samples will stop being delivered.
    CFNotificationName notificationName = CFNotificationName(ScreenShareBroadcastPausedNotification);
    CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), notificationName, nil, nil, true);
}

- (void)broadcastResumed {
    // User has requested to resume the broadcast. Samples delivery will resume.
    CFNotificationName notificationName = CFNotificationName(ScreenShareBroadcastResumedNotification);
    CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), notificationName, nil, nil, true);
}

- (void)broadcastFinished {
    // User has requested to finish the broadcast.
    CFNotificationName notificationName = CFNotificationName(ScreenShareBroadcastFinishedNotification);
    CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), notificationName, nil, nil, true);
}

- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
    //使用NSUserDefault
}
MainApp监听屏幕共享事件通知
// 屏幕共享开始
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                (__bridge const void *)(self),
                                onBroadcastStarted,
                                (__bridge CFStringRef)TScreenShareBroadcastStartedNotification,
                                NULL,
                                CFNotificationSuspensionBehaviorDeliverImmediately);
// 屏幕共享完成
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                (__bridge const void *)(self),
                                onBroadcastFinished,
                                (__bridge CFStringRef)TScreenShareBroadcastFinishedNotification,
                                NULL,
                                CFNotificationSuspensionBehaviorDeliverImmediately);
// 屏幕共享暂停
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                (__bridge const void *)(self),
                                onBroadcastPaused,
                                (__bridge CFStringRef)TScreenShareBroadcastPausedNotification,
                                NULL,
                                CFNotificationSuspensionBehaviorDeliverImmediately);
// 屏幕共享暂停
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                (__bridge const void *)(self),
                                onBroadcastResumed,
                                (__bridge CFStringRef)TScreenShareBroadcastResumedNotification,
                                NULL,
                                CFNotificationSuspensionBehaviorDeliverImmediately);
// 实现方法
static void onBroadcastStarted(CFNotificationCenterRef center,
                               void *observer,
                               CFStringRef name,
                               const void *object,
                               CFDictionaryRef
                               userInfo) {
}

static void onBroadcastFinished(CFNotificationCenterRef center,
                                void *observer,
                                CFStringRef name,
                                const void *object,
                                CFDictionaryRef
                                userInfo) {
}

static void onBroadcastPaused(CFNotificationCenterRef center,
                              void *observer,
                              CFStringRef name,
                              const void *object,
                              CFDictionaryRef
                              userInfo) {
}

static void onBroadcastResumed(CFNotificationCenterRef center,
                               void *observer,
                               CFStringRef name,
                               const void *object,
                               CFDictionaryRef
                               userInfo) {
}

流媒体服务:

这里大多都是使用第三方的服务,并且每家SDK的方法都大同小异;RTC屏幕共享方法有开始屏幕共享,停止屏幕共享,以及推流等方法;基本上集成上都有傻瓜式教程,就不在这里展开叙述;

总结:

iOS系统实现屏幕共享的功能太曲折了,就ReplayKit整体而言,对开发者并不是很友好。iOS版本之间兼容也是很头疼的,实现屏幕共享的细节还有很多,在屏幕共享采集后的处理也尤为关键,比如对视频帧和音频帧的处理;还有就是屏幕共享扩展的内存限制50M,这也是需要开发者特别注意的地方;

相关文章

  • iOS屏幕共享

    前言:由于最近项目中需要使用到屏幕共享,所以对iOS屏幕共享进行了一番调研,在这里也分享下踩坑之路。 屏幕共享简介...

  • iOS ReplayKit 屏幕共享,屏幕直播实现

    使用replayKit iOS12 之后相关 api 完成系统/app 内 屏幕采集直播视频数据, 采用 sock...

  • iOS端屏幕录制开发指南

    一、 概述实现直播过程中共享屏幕分为两个步骤:屏幕数据采集和流媒体数据推送。前对于 iOS 来说,屏幕采集需要系统...

  • ios 实现屏幕共享功能

    ios webRTC 实现屏幕共享功能https://xie.infoq.cn/article/35f6be94d...

  • ios 利用 socket 传输 replykit 屏幕共享数据

    ios 利用 socket 传输 replykit 屏幕共享数据到主 app 先上 demo 我这里只讲代码,文章...

  • iOS12+ 的屏幕共享及遇到的某些坑

    最近在弄屏幕共享事宜,看了很多文章了解了从iOS9一直到iOS12 苹果在直播录像方面做的改变。如果你对直播实现的...

  • iOS端屏幕录制(replaykit)调研

    最近项目中需要完成一个屏幕录制并推流的需求,对iOS端这个功能进行了调研,分享一下结果: 一、 概述 屏幕共享是将...

  • 屏幕共享 ReplayKit

    一、概述 屏幕共享是将屏幕上的内容分享,从而实现信息共享的一种技术。对于手机端,用户可以将自己手机屏幕上的内容共享...

  • macOS 如何屏幕共享?mac屏幕共享教程

    macOS 如何屏幕共享?肯定有很多小伙伴不知道,今天就让小编为大家带来macOS 如何屏幕共享的教程,需要的小伙...

  • 屏幕旋转

    屏幕旋转 推荐文档 了解UIWindow——UIWindow实践 iOS屏幕旋转问题总结 IOS:屏幕旋转与变换 ...

网友评论

    本文标题:iOS屏幕共享

    本文链接:https://www.haomeiwen.com/subject/ldrsyltx.html