SellyCloudSDK_demo/Example/SellyCloudSDK/VideoCall/SellyVideoCallConferenceCon...

243 lines
9.0 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.

//
// SellyVideoCallConferenceController.m
// SellyCloudSDK_Example
//
// Created by Caleb on 11/11/25.
// Copyright © 2025 Caleb. All rights reserved.
//
#import "SellyVideoCallConferenceController.h"
#import <SellyCloudSDK/SellyCloudManager.h>
#import "FUManager.h"
#import "UIView+SellyCloud.h"
#import "SLSVideoGridView.h"
#import "TokenGenerator.h"
#import <ReplayKit/ReplayKit.h> //屏幕采集
@interface SellyVideoCallConferenceController ()<SellyRTCSessionDelegate>
// 你的 WebRTC 会话对象
@property (nonatomic, strong) SellyRTCSession *session;
@property (nonatomic, assign) BOOL localVideoEnable;
@property (nonatomic, assign) BOOL localAudioEnable;
@property (weak, nonatomic) IBOutlet SLSVideoGridView *grid;
@property (weak, nonatomic) IBOutlet UILabel *duration;
@property (nonatomic, strong)RPSystemBroadcastPickerView *systemBroadcastPicker;
@end
@implementation SellyVideoCallConferenceController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"音视频会议";
// Do any additional setup after loading the view.
self.session.delegate = self;
if (self.videoConfig == nil) {
self.videoConfig = SellyRTCVideoConfiguration.defaultConfig;
}
self.session.videoConfig = self.videoConfig;
NSString *token = [TokenGenerator generateTokenWithUserId:SellyRTCEngine.sharedEngine.userId callId:self.channelId];
[self.session startWithChannelId:self.channelId token:token];
//开启视频
[self onCameraClick:nil];
//设置扬声器播放
{
//使用这种方案通话接通前无法在receiver和speaker直接来回切换强制扬声器
// [self.session setAudioOutput:AVAudioSessionPortOverrideSpeaker];
}
{
//使用这种方案通话接通前可以在receiver和speaker直接来回切换
NSError *error;
[AVAudioSession.sharedInstance setCategory:AVAudioSessionCategoryPlayAndRecord mode:AVAudioSessionModeVoiceChat options:AVAudioSessionCategoryOptionDuckOthers|AVAudioSessionCategoryOptionAllowBluetooth|AVAudioSessionCategoryOptionMixWithOthers error:&error];
[self.session setAudioOutput:AVAudioSessionPortOverrideSpeaker];
}
//显示本地view
[self attachLocalStream];
self.localAudioEnable = true;
[self addBroadcastButton];
}
- (void)addBroadcastButton {
if (@available(iOS 12.0, *)) {
self.systemBroadcastPicker = [[RPSystemBroadcastPickerView alloc]
initWithFrame:CGRectMake(UIScreen.mainScreen.bounds.size.width-80, UIScreen.mainScreen.bounds.size.height-180, 60, 60)];
NSString *bundleId = [NSBundle mainBundle].bundleIdentifier;
self.systemBroadcastPicker.preferredExtension = [NSString stringWithFormat:@"%@.ScreenShareUploader",bundleId];// 你的 extension bundle id
self.systemBroadcastPicker.showsMicrophoneButton = false;
}
}
- (IBAction)startScreenCapture:(id)sender {
for (UIView *view in self.systemBroadcastPicker.subviews) {
if ([view isKindOfClass:[UIButton class]]) {
[((UIButton *)view) sendActionsForControlEvents:(UIControlEventAllEvents)];
break;
}
}
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.session end];
}
- (IBAction)onSpeakerClick:(id)sender {
//如果没有外接设备,可以直接调用这个方法在听筒和扬声器直接来回切换
AVAudioSessionPort currentPort = AVAudioSession.sharedInstance.currentRoute.outputs.firstObject.portType;
if ([currentPort isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {
[self.session setAudioOutput:AVAudioSessionPortOverrideNone];
}
else {
[self.session setAudioOutput:AVAudioSessionPortOverrideSpeaker];
}
}
- (IBAction)onCameraClick:(id)sender {
[self.session enableLocalVideo:!self.localVideoEnable];
self.localVideoEnable = !self.localVideoEnable;
}
- (IBAction)onSwitchClick:(id)sender {
[self.session switchCamera];
}
- (IBAction)onMuteClick:(id)sender {
[self.session enableLocalAudio:!self.localAudioEnable];
self.localAudioEnable = !self.localAudioEnable;
}
- (void)attachLocalStream {
NSString *uid = SellyRTCEngine.sharedEngine.userId;
// 1. Grid 布局请求一个格子
SLSVideoTileView *container = [self.grid ensureRenderContainerForUID:uid
displayName:uid];
// 2. 将本地视频绑定到 container
SellyRtcVideoCanvas *canvas = [[SellyRtcVideoCanvas alloc] init];
// 你的接口要求 userId这里用字符串版本地 uid
canvas.userId = uid;
canvas.view = container.contentView;
[self.session setLocalCanvas:canvas];
}
#pragma marks SLSVideoEngineEvents
#pragma mark - SellyRTCSessionDelegate 回调桥接到 SLSVideoEngineEvents
/// 无法恢复的错误
- (void)rtcSession:(SellyRTCSession *)session onError:(NSError *)error {
// 这里看你要不要额外给业务一个错误回调,可以扩展 SLSVideoEngineEvents
NSLog(@"rtc.onerror == %@",error);
[self.view showToast:error.localizedDescription];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:true];
});
}
/// 远端用户加入
- (void)rtcSession:(SellyRTCSession *)session onUserJoined:(NSString *)userId {
SLSVideoTileView *container = [self.grid ensureRenderContainerForUID:userId
displayName:userId];
SellyRtcVideoCanvas *canvas = [[SellyRtcVideoCanvas alloc] init];
canvas.userId = userId;
canvas.view = container.contentView;
[self.session setRemoteCanvas:canvas];
}
/// 远端用户离开
- (void)rtcSession:(SellyRTCSession *)session onUserLeave:(NSString *)userId {
[self.grid detachUID:userId];
}
- (void)rtcSession:(SellyRTCSession *)session audioEnabled:(BOOL)enabled userId:(NSString *)userId {
NSLog(@"userId == %@ audioEnabled == %d",userId,enabled);
}
- (void)rtcSession:(SellyRTCSession *)session videoEnabled:(BOOL)enabled userId:(NSString *)userId {
NSLog(@"userId == %@ videoEnabled == %d",userId,enabled);
}
/// 收到自定义消息
- (void)rtcSession:(SellyRTCSession *)session didReceiveMessage:(NSString *)message userId:(NSString *)userId {
NSLog(@"recv message from %@: %@", userId, message);
}
/// 连接状态变化
- (void)rtcSession:(SellyRTCSession *)session connectionStateChanged:(SellyRTCConnectState)state userId:(nullable NSString *)userId {
NSLog(@"ice.connectionStateChanged == %ld",state);
}
- (void)rtcSession:(SellyRTCSession *)session onRoomConnectionStateChanged:(SellyRoomConnectionState)state {
NSLog(@"####onSocketStateChanged == %ld",(long)state);
}
/// 视频前处理(如果你要做美颜,可以在这里转发给业务;目前和 UI 层无关)
- (CVPixelBufferRef)rtcSession:(SellyRTCSession *)session onCaptureVideoFrame:(CVPixelBufferRef)pixelBuffer {
// 默认不处理,直接返回原始帧
return pixelBuffer;
}
/// 统计信息(如果你的 SellyRTCP2pStats 里有音量相关信息,可以在这里转成 videoEngineVolumeIndication
- (void)rtcSession:(SellyRTCSession *)session onStats:(SellyRTCP2pStats *)stats userId:(nullable NSString *)userId {
// TODO: 如果 stats 里有本地 / 远端音量,可在这里构造一个 dict 调用:
// if ([self.eventsDelegate respondsToSelector:@selector(videoEngineVolumeIndication:)]) { ... }
SLSVideoTileView *view = [self.grid ensureRenderContainerForUID:userId displayName:nil];
view.stats = stats;
}
- (void)rtcSession:(SellyRTCSession *)session onDuration:(NSInteger)duration {
self.duration.text = [NSString stringWithFormat:@"%02ld:%02ld",duration/60,duration%60];
}
- (void)rtcSession:(SellyRTCSession *)session tokenWillExpire:(NSString *)token {
NSString *newToken = [TokenGenerator generateTokenWithUserId:SellyRTCEngine.sharedEngine.userId callId:self.channelId];
[session renewToken:newToken];
}
- (void)rtcSession:(SellyRTCSession *)session tokenExpired:(NSString *)token {
NSString *newToken = [TokenGenerator generateTokenWithUserId:SellyRTCEngine.sharedEngine.userId callId:self.channelId];
[session renewToken:newToken];
}
- (void)rtcSession:(SellyRTCSession *)session onScreenShareStatusChanged:(SellyScreenShareState)state {
if (state == SellyScreenShareStateStarted) {
self.localVideoEnable = false;
[self.session startScreenCapture];
}
else if (state == SellyScreenShareStateStopped) {
self.localVideoEnable = true;
[self.session enableLocalVideo:true];
}
}
- (SellyRTCSession *)session {
if (!_session) {
_session = [[SellyRTCSession alloc] initWithType:false];
}
return _session;
}
/*
#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.
}
*/
@end