SellyCloudSDK_demo/Example/SellyCloudSDK/Live/SCLivePusherViewController.m

325 lines
9.3 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// SCRtmpLiveViewController.m
// SellyCloudSDK_Example
//
// Created by Caleb on 8/7/25.
// Copyright © 2025 Caleb. All rights reserved.
//
#import "SCLivePusherViewController.h"
#import <SellyCloudSDK/SellyCloudManager.h>
#import "SCLiveItemContainerView.h"
#import "FUManager.h"
#import <Photos/Photos.h>
#import "SCLiveStatsView.h"
#import "UIView+SellyCloud.h"
@interface SCLivePusherViewController ()<SellyPusherManagerDelegate>
@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