1.extension项目中,target -extension tool - bitcode - NO
-
Link Binary with Libraries
5.png
日志获取: https://cloud.tencent.com/developer/article/1727299
1.png 2.png 3.png 8.png3.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)两个地方的名字需要一致
网友评论