// // SCRtmpLiveViewController.m // SellyCloudSDK_Example // // Created by Caleb on 8/7/25. // Copyright © 2025 Caleb. All rights reserved. // #import "SCLivePusherViewController.h" #import #import "SCLiveItemContainerView.h" #import "FUManager.h" #import #import "SCLiveStatsView.h" #import "UIView+SellyCloud.h" @interface SCLivePusherViewController () @property (nonatomic, strong)UIView *liveView; @property (nonatomic, strong)SellyVideoPusher *livePusher; @property (nonatomic, strong)SCLiveItemContainerView *itemContainer; @property (nonatomic, strong)SCLiveStatsView *statsView; @end @implementation SCLivePusherViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = UIColor.whiteColor; [self.view addSubview:self.liveView]; [self.liveView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.view); }]; // Do any additional setup after loading the view. [self startLive]; // 禁止息屏 [UIApplication sharedApplication].idleTimerDisabled = YES; // [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(appDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; // [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(appWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil]; [self.view addSubview:self.statsView]; [self.statsView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.offset(10); make.top.offset(90); make.width.offset(210); make.height.offset(220); }]; } - (void)appDidEnterBackground { [self _sendPicture]; } - (void)appWillEnterForeground { [self _stopSendPicture]; } - (void)startLive { if (self.streamInfo) { self.livePusher = [[SellyVideoPusher alloc] initWithStreamInfo:self.streamInfo]; } else { self.livePusher = [[SellyVideoPusher alloc] initWithUrl:self.url]; } self.livePusher.preview = self.liveView; self.livePusher.delegate = self; self.livePusher.enableCustomVideoProcess = true; self.livePusher.scaleMode = SellyPlayerScalingModeAspectFit; SellyLiveVideoConfiguration *videoConfig = self.videoConfig; #warning RTC直播中支持自动切换横竖屏,RTMP不支持 { //横屏直播 // videoConfig.videoSize = CGSizeMake(1280, 720); // videoConfig.outputImageOrientation = UIInterfaceOrientationLandscapeRight; } { //竖屏直播 // videoConfig.videoSize = CGSizeMake(720, 1280); // videoConfig.outputImageOrientation = UIInterfaceOrientationPortrait; } if (self.audioOnly) { //纯语音直播 [self.livePusher startRunningAudio:nil]; } else { //音视频直播 [self.livePusher startRunning:AVCaptureDevicePositionBack videoConfig:videoConfig audioConfig:nil]; } NSError *error = [self.livePusher startLive]; if (error) { NSLog(@"###startLive failed. error == %@",error.localizedDescription); } __weak typeof(self) weakSelf = self; NSMutableArray *items = NSMutableArray.new; { SCLiveItemModel *model = SCLiveItemModel.new; model.type = 1; model.title = @"前/后"; model.clickCallback = ^{ [weakSelf switchDevicePosition]; }; [items addObject:model]; } { SCLiveItemModel *model = SCLiveItemModel.new; model.type = 2; model.title = @"mute"; model.clickCallback = ^{ [weakSelf muteClick]; }; [items addObject:model]; } { SCLiveItemModel *model = SCLiveItemModel.new; model.type = 3; model.title = @"camera"; model.clickCallback = ^{ [weakSelf cameraClick]; }; [items addObject:model]; } { SCLiveItemModel *model = SCLiveItemModel.new; model.type = 4; model.title = @"镜像"; model.clickCallback = ^{ [weakSelf mirrorClick]; }; [items addObject:model]; } { SCLiveItemModel *model = SCLiveItemModel.new; model.type = 5; model.title = @"截图"; model.clickCallback = ^{ [weakSelf captureImageClick]; }; [items addObject:model]; } { SCLiveItemModel *model = SCLiveItemModel.new; model.type = 6; model.title = @"静图"; model.clickCallback = ^{ [weakSelf sendStaticImage]; }; [items addObject:model]; } self.itemContainer.models = items; [self.view addSubview:self.itemContainer]; [self.itemContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.offset(0); make.bottom.offset(-48); make.height.offset(100); }]; } - (void)switchDevicePosition { [self.livePusher switchCameraPosition:nil]; if (self.livePusher.captureDevicePosition == AVCaptureDevicePositionFront) { self.livePusher.mirror = true; } else { self.livePusher.mirror = false; } } - (void)muteClick { if (self.livePusher.isMute) { [self.livePusher startMicrophone]; } else { [self.livePusher stopMicrophone]; } } - (void)cameraClick { if (self.livePusher.isCameraEnable) { [self.livePusher stopCamera]; [self _sendPicture]; } else { [self _stopSendPicture]; [self.livePusher startCamera]; } } - (void)mirrorClick { self.livePusher.mirror = !self.livePusher.mirror; } - (void)captureImageClick { [self saveCurrentFrameToPhotoAlbum:[self.livePusher getCurrentImage]]; } - (void)sendStaticImage { if (self.livePusher.isCameraEnable) { //推送静态图片 [self.livePusher stopCamera]; [self _sendPicture]; } else { //推送相机采集流 [self _stopSendPicture]; [self.livePusher startCamera]; } } - (void)_sendPicture { [self.livePusher pushStaticImage:[UIImage imageNamed:@"test.jpg"]]; } - (void)_stopSendPicture { [self.livePusher stopPushImage]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self.livePusher stopLive:^(NSError * _Nonnull error) { }]; } // 假设 self.session 是你的 LFLiveSession 实例 - (void)saveCurrentFrameToPhotoAlbum:(UIImage *)image { if (!image) { NSLog(@"当前没有图像可保存"); return; } // 检查权限 PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; if (status == PHAuthorizationStatusDenied || status == PHAuthorizationStatusRestricted) { NSLog(@"无权限访问相册,请到设置中开启权限"); return; } if (status == PHAuthorizationStatusNotDetermined) { [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus newStatus) { if (newStatus == PHAuthorizationStatusAuthorized) { [self saveImage:image]; } }]; } else { [self saveImage:image]; } } - (void)saveImage:(UIImage *)image { UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), NULL); } // 保存完成回调 - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { if (error) { NSLog(@"保存失败:%@", error.localizedDescription); } else { NSLog(@"已保存当前视频帧至相册"); } } - (void)dealloc { // 禁止息屏 [UIApplication sharedApplication].idleTimerDisabled = NO; [NSNotificationCenter.defaultCenter removeObserver:self]; NSLog(@"###%@ dealloc",NSStringFromClass(self.class)); } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ #pragma marks SellyCloudRTMPDelegate - (void)pusher:(SellyVideoPusher *)pusher liveStatusDidChanged:(SellyLiveState)status { } - (void)pusher:(SellyVideoPusher *)pusher onStatisticsUpdate:(SellyLivePusherStats *)stats { NSLog(@"##stats == %@",stats); self.statsView.stats = stats; } - (void)pusher:(SellyVideoPusher *)pusher onError:(NSError *)error { //推流报错,直播结束 NSLog(@"###onPusherError == %@",error); [self.navigationController popViewControllerAnimated:true]; [self.view showToast:error.localizedDescription]; } - (CVPixelBufferRef)pusher:(SellyVideoPusher *)pusher onCaptureVideoFrame:(CVPixelBufferRef)pixelBuffer { CVPixelBufferRef afterBuffer = [FUManager.shareManager renderItemsToPixelBuffer:pixelBuffer]; return afterBuffer; } - (SCLiveItemContainerView *)itemContainer { if (!_itemContainer) { _itemContainer = [[SCLiveItemContainerView alloc] init]; } return _itemContainer; } - (UIView *)liveView { if (!_liveView) { _liveView = UIView.new; } return _liveView; } - (SCLiveStatsView *)statsView { if (!_statsView) { _statsView = [[SCLiveStatsView alloc] init]; } return _statsView; } @end