美文网首页
对接腾讯云录屏直播接入笔记

对接腾讯云录屏直播接入笔记

作者: 数字d | 来源:发表于2021-06-29 11:03 被阅读0次

    1.extension项目中,target -extension tool - bitcode - NO

    1. Link Binary with Libraries


      5.png

    日志获取: https://cloud.tencent.com/developer/article/1727299

    1.png 2.png 3.png 8.png

    3.samplehandler.m中的实现内容

    
    //
    //  SampleHandler.m
    //  Tool
    //
    //  Created by mac on 2021/6/29.
    //
    
    
    #import "SampleHandler.h"
    
    #import <TXLiteAVSDK_Professional/TXLiteAVSDK.h>
    #import <UserNotifications/UNUserNotificationCenter.h>
    #import <UserNotifications/UNNotificationContent.h>
    #import <UserNotifications/UNNotificationSound.h>
    #import <UserNotifications/UNNotificationTrigger.h>
    #import <UserNotifications/UNNotificationRequest.h>
    #import <UserNotifications/NSString+UserNotifications.h>
    
    #define YZ_ThirdParty_tencent_zb_licenceURLL @"" ///<!腾讯云直播licence
    #define YZ_ThirdParty_tencent_zb_licenceKeyyy @"" ///<!腾讯云直播的key
    
    static TXLivePush *s_txLivePublisher;
    static NSString *s_rtmpUrl;
    static dispatch_source_t s_audioTimer;
    static BOOL       s_landScape;   // 1 - 横屏;
    static NSString *s_userid, *s_groupid;
    static id s_lastVideoSampleBuffer;
    static SampleHandler *s_delegate;   // retain delegate
    static BOOL s_headPhoneIn;
    
    
    @interface SampleHandler()<TXLivePushListener, TXLiveBaseDelegate>
    -(void) onPushEvent:(int)EvtID withParam:(NSDictionary*)param;
    -(void) onNetStatus:(NSDictionary*) param;
    @end
    
    @implementation SampleHandler
    
    
    //
    //#pragma mark --------- cycle
    ///**
    // * 常规 init方法
    // * 添加本地通知routeChanged,监听推流地址更新routeChanged,更改之后需要重新初始化推流地址和拉流地址
    // * 添加进程通知CFNotificationCenterAddObserver:如果电竞直播的直播间创建成功,会在YZStartDBViewCtl.m中发出进程通知,SampleHandelr.m会接收到这个进程通知,接收到之后会给self(SampleHandelr.m)发送NSNotification类型的通知,本地的handleReplayKit2PushStartNotification是处理NSNotification类型的通知,handleReplayKit2PushStartNotification方法体内部将会处理接下来的腾讯sdk的各种初始化初和推流信息初始化,至此,app和boardset extension upload 的交互流程完善.
    // */
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                     selector:@selector(routeChanged:)
                                                         name:AVAudioSessionRouteChangeNotification
                                                       object:nil];
            //
            CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                            (__bridge const void *)(self),
                                            onDarwinReplayKit2PushStart,
                                            (CFStringRef)@"kDarvinNotificationNamePushStart",
                                            NULL,
                                            CFNotificationSuspensionBehaviorDeliverImmediately);
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleReplayKit2PushStartNotification:) name:@"Cocoa_ReplayKit_Push_Start" object:nil];
        }
        return self;
    }
    //
    ///**
    // * 录制启动
    // */
    - (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
    
        //发送本地通知,告诉宿主app,现在开始使用tool进行录屏,
        NSLog(@"录制启动");
        [self sendLocalNotificationToHostAppWithTitle:@"Tool已开始录屏" msg:@"录屏已开始,请从这里单击回到app开始直播" userInfo:@{}];
    
    }
    ///**
    // * 录制-暂停
    // */
    - (void)broadcastPaused {
        NSLog(@"录制暂停");
        [self pause];
    }
    ///**
    // * 录制-重启
    // */
    - (void)broadcastResumed {
        NSLog(@"录制重启");
        [self resume];
    }
    ///**
    // * 录制-结束
    // */
    - (void)broadcastFinished {
        NSLog(@"录制结束");
        [self resume];
        [self stop];
    }
    ///**
    // * 录制-系统信息
    // */
    - (void)broadcastAnnotatedWithApplicationInfo:(NSDictionary *)applicationInfo {
        NSLog(@"录制-系统信息:%@",applicationInfo);
    }
    ///**
    // * 录制-结束
    // */
    - (void)finishBroadcastWithError:(NSError *)error {
        NSLog(@"录制-报错 error = %@ ",error);
    
    }
    //
    - (void)dealloc {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    
    
    //
    //
    //#pragma mark --------- process action
    //
    ///**
    // * 发送本地通知的方法
    // */
    - (void)sendLocalNotificationToHostAppWithTitle:(NSString*)title msg:(NSString*)msg userInfo:(NSDictionary*)userInfo {
        UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
        UNMutableNotificationContent * content = [[UNMutableNotificationContent alloc] init];
        content.title = [NSString localizedUserNotificationStringForKey: title arguments:nil];
        content.body = [NSString localizedUserNotificationStringForKey: msg  arguments:nil];
        content.sound = [UNNotificationSound defaultSound];
        content.userInfo = userInfo;
        UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger
                                                      triggerWithTimeInterval:0.1f repeats:NO];
        UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"ReplayKit2Demo"
                                                                              content:content trigger:trigger];
    
        [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
            NSLog(@"本地通知发送 %@",error);
        }];
    }
    
    //
    //
    //#pragma mark --------- notification action
    //
    static void onDarwinReplayKit2PushStart(CFNotificationCenterRef center,
                                            void *observer, CFStringRef name,
                                            const void *object, CFDictionaryRef
                                            userInfo)
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"Cocoa_ReplayKit_Push_Start" object:nil];
    }
    
    
    - (void)routeChanged:(NSNotification *)notification {
        NSInteger routeChangeReason = [notification.userInfo[AVAudioSessionRouteChangeReasonKey] integerValue];
        if (routeChangeReason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
            s_headPhoneIn = NO;
        }
        if (routeChangeReason == AVAudioSessionRouteChangeReasonNewDeviceAvailable) {
            s_headPhoneIn = YES;
        }
        [self tencentInit];
    }
    
    - (void)handleReplayKit2PushStartNotification:(NSNotification*)noti {
        [self tencentInit];
    }
    
    #pragma mark --------- tencent action
    
    
    //
    - (void) initRtmp {
        if (s_txLivePublisher) {
            [s_txLivePublisher stopPush];
        }
    
        TXLivePushConfig* config = [[TXLivePushConfig alloc] init];
        config.customModeType |= CUSTOM_MODE_VIDEO_CAPTURE;
        config.videoBitratePIN   = 1000;
        config.enableAutoBitrate = NO;
        config.videoFPS = 20;
        // 5的手机硬编不稳定
    
        config.autoSampleBufferSize = YES;
                if (s_landScape)
                    config.sampleBufferSize = CGSizeMake(960, 544);
                else
                    config.sampleBufferSize = CGSizeMake(544, 960);
    
        config.customModeType |= CUSTOM_MODE_AUDIO_CAPTURE;
        config.audioSampleRate = AUDIO_SAMPLE_RATE_44100;
        config.audioChannels   = 1;
    
        s_txLivePublisher = [[TXLivePush alloc] initWithConfig:config];
        int value =  [s_txLivePublisher startPush:s_rtmpUrl];
        NSLog(@"init status =  %d,0表示推流成功",value);
        s_delegate = self;
        s_txLivePublisher.delegate = s_delegate;
        [TXLiveBase sharedInstance].delegate = s_delegate;
        [self checkHeadphone];
    }
    
    - (void)pause {
        [s_txLivePublisher setSendAudioSampleBufferMuted:YES];
    }
    //
    - (void)resume {
        [s_txLivePublisher setSendAudioSampleBufferMuted:NO];
    }
    //
    - (void)stop {
        s_rtmpUrl = nil;
        
        if (s_txLivePublisher) {
            [s_txLivePublisher stopPush];
            s_txLivePublisher = nil;
        }
        
        s_lastVideoSampleBuffer = nil;
        s_delegate = nil;
    }
    
    - (void)checkHeadphone {
        AVAudioSession *session = [AVAudioSession sharedInstance];
        for (AVAudioSessionPortDescription *dp in session.currentRoute.outputs) {
            if ([dp.portType isEqualToString:AVAudioSessionPortHeadphones]) {
                s_headPhoneIn = YES;
                return;
            }
        }
        s_headPhoneIn = NO;
    }
    
    
    
    
    
    
    
    
    
    
    
    - (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
        //    NSLog(@"还在录制吗....");
        if (s_txLivePublisher == nil) {
            return;
        }
        
        s_headPhoneIn = YES;
        
        
        switch (sampleBufferType) {
            case RPSampleBufferTypeVideo:{
                           NSLog(@"RPSampleBufferTypeVideo");
                [s_txLivePublisher sendVideoSampleBuffer:sampleBuffer];
                s_lastVideoSampleBuffer = (__bridge id)sampleBuffer;
                return;
            }
                break;
            case RPSampleBufferTypeAudioApp:{
                           NSLog(@"RPSampleBufferTypeAudioApp");
                if (s_headPhoneIn) {
                    if (CMSampleBufferDataIsReady(sampleBuffer) != NO) {
                        [s_txLivePublisher sendAudioSampleBuffer:sampleBuffer withType:sampleBufferType];
                        //                    CMTime  audioCaptureTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
                    } else {
                        NSAssert(0, @"wft");
                    }
                }
                
            }
                break;
            case RPSampleBufferTypeAudioMic:{
                           NSLog(@"RPSampleBufferTypeAudioMic");
                if (CMSampleBufferDataIsReady(sampleBuffer) != NO) {
                    [s_txLivePublisher sendAudioSampleBuffer:sampleBuffer withType:sampleBufferType];
    //                CMTime  audioCaptureTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
                } else {
                    NSAssert(0, @"wft");
                }
                
            }
                break;
                
            default:
                break;
        }
    }
    
    
    -(void)tencentInit {
        
        //    tencent init
        [TXLiveBase setLicenceURL:YZ_ThirdParty_tencent_zb_licenceURLL key:YZ_ThirdParty_tencent_zb_licenceKeyyy];
        
        //    push init
        CGSize sz = [[UIScreen mainScreen] bounds].size;
        NSUserDefaults *defaultsyz = [[NSUserDefaults alloc] initWithSuiteName:@"group.Tool"];
        if (defaultsyz == nil) {
            defaultsyz = [NSUserDefaults standardUserDefaults];
        }
        NSDictionary * dic = [defaultsyz objectForKey:@"yz_extension_groupInfo"];
        //    NSDictionary * dic = YZGETDEFAULTS(@"yz_extension_groupInfo");//!!!!
        NSString *pushflow = [dic valueForKey:@"pushflow"];
        NSString *userId = [dic valueForKey:@"userid"];
        NSString * groupId = [dic valueForKey:@"id"];
        NSLog(@"userdefaults的数据 broadcastStartedWithSetupInfo %@", dic);
        
        if (pushflow.length == 0) {
            NSLog(@"broadcastStartedWithSetupInfo 地址非法,没有获取到电竞直播推流的地址");
            return;
        }
        
        
        
        [self resume];
        [self stop];
        s_userid = userId;
        s_groupid = groupId;
        s_rtmpUrl = pushflow;
        s_landScape = [@(sz.width>sz.height) boolValue];
        [self start];
    }
    
    
    
    
    - (void)start {
        if (s_rtmpUrl == nil) return;
        [self initRtmp];
    }
    
    
    
    
    
    -(void) onPushEvent:(int)EvtID withParam:(NSDictionary*)param {
        NSLog(@"onPushEvent %d", EvtID);
            dispatch_async(dispatch_get_main_queue(), ^{
                if (EvtID == PUSH_ERR_NET_DISCONNECT) {
                    [self initRtmp];
                    if (s_lastVideoSampleBuffer) { // fix bug: pause status no video data come
                        [s_txLivePublisher sendVideoSampleBuffer:(__bridge_retained CMSampleBufferRef)s_lastVideoSampleBuffer];
                    }
                } else if(EvtID == PUSH_WARNING_HW_ACCELERATION_FAIL){
        
                } else if (EvtID == PUSH_EVT_PUSH_BEGIN) {
                    //该事件表示推流成功,可以通知业务server将该流置为上线状态
                    //            [self changeLiveStatus:s_userid status:TCLiveStatus_Online handler:^(int errCode) {
                    //                NSLog(@"changeLiveStatus online code:%d", errCode);
                    //            }];
                    //            start
                }
            });
    }
    
    -(void) onNetStatus:(NSDictionary*) param {
        NSLog(@"net status %@",param);
    }
    
    
    //
    
    - (void)onScreenCapturePaused:(int)reason {
        NSLog(@"on screenCapture Paused");
    }
    
    - (void)onScreenCaptureResumed:(int)reason {
        //   resumed  继续
        NSLog(@"resumed");
    }
    
    - (void)onScreenCaptureStarted {
        // 开始
        NSLog(@"started");
    }
    
    - (void)onScreenCaptureStoped:(int)reason {
        NSLog(@"stop");
    }
    
    //- (void)beginRequestWithExtensionContext:(nonnull NSExtensionContext *)context {
    //    NSLog(@"other context info %@",context);
    //}
    
    @end
    
    
    
    
    

    4.viewcontroller中的核心代码

    //
    //  ViewController.m
    //  app
    //
    //  Created by mac on 2021/6/29.
    //
    
    #import "ViewController.h"
    #import <TXLiteAVSDK_Professional/TXLiteAVSDK.h>
    #import "SampleHandler.h"
    
    
    @interface ViewController ()
    @property (nonatomic, strong) UIButton *editBtn;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        [self.view addSubview:self.editBtn];
        
    }
    
    -(UIButton *)editBtn {
        if (_editBtn == nil) {
            _editBtn = [UIButton buttonWithType:(UIButtonTypeCustom)];
            _editBtn.frame = CGRectMake(50, 100, 100, 50);
            [_editBtn setTitle:@"开播" forState:(UIControlStateNormal)];
            [_editBtn addTarget:self action:@selector(editAction) forControlEvents:(UIControlEventTouchUpInside)];
            _editBtn.backgroundColor = [UIColor blueColor];
        }
        return _editBtn;
    }
    
    -(void)editAction {
        NSLog(@"点击了开播按钮");
        
        if ([self judgeTooForSystemAndCaptureStatus]) {
            NSLog(@"view controller 中存了数据");
            NSMutableDictionary * dicFroGroup = [NSMutableDictionary dictionaryWithCapacity:0];
            [dicFroGroup setValue:@"40" forKey:@"userId"];
            
            
            [dicFroGroup setValue:@"rtmp://135262.livepush.myqcloud.com/live/7920382de5deaa380cd73b0c8b78a0be?txSecret=e1a935710cd47fdebdd7b4cc21476e95&txTime=60DC0D96" forKey:@"pushflow"];
            
            [dicFroGroup setValue:@"1161" forKey:@"groupId"];
            
            [dicFroGroup setValue:@"https://zhongyoujingji.cn/live/7920382de5deaa380cd73b0c8b78a0be.flv" forKey:@"pullflow"];
            
            NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.Tool"];
            if (defaults == nil) {
                defaults = [NSUserDefaults standardUserDefaults];
            }
            [defaults setObject:dicFroGroup forKey:@"yz_extension_groupInfo"];
            [defaults synchronize];
            CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(),(CFStringRef)@"kDarvinNotificationNamePushStart",NULL,nil,YES);
        }
        
    
    
    }
    
    
    /**
     * iOS系统11以上才可以录屏直播
     * 需要先开启屏幕录制功能才能开始电竞直播
     */
    -(BOOL)judgeTooForSystemAndCaptureStatus {
        BOOL infoSys = NO;
        BOOL infoCapture = NO;
       
        if (@available(iOS 11.0,*)) {
            infoSys = YES;
            infoCapture = [UIScreen mainScreen].isCaptured;
            
        }else{
    //        [ToastUtils showMessage:@"录屏直播需要将系统版本更新到11以上" duration:1 position:Toast_Point_Center];
            NSLog(@"录屏直播需要将系统版本更新到11以上");
            return NO;
        }
        
        if (!infoCapture) {
    //        [ToastUtils showMessage:@"请先使用直播工具开始屏幕录制" duration:1 position:Toast_Point_Center];
            NSLog(@"请先使用直播工具开始屏幕录制");
            return NO;
        }
        
        return infoSys && infoCapture;
    }
    
    @end
    
    

    5.项目中有用到了进程间的数据交互,腾讯云对接的时候提供了两种方案,一种是剪切板,一种是userdefaults
    剪切板没试过,只是用了userdefaults,使用之前需要在app和extension中都在targest - signing&capabilities - +Capability + App Groups + 取个名字(group.Tool)两个地方的名字需要一致

    9.png 10.png

    相关文章

      网友评论

          本文标题:对接腾讯云录屏直播接入笔记

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