initial commit

This commit is contained in:
Caleb
2026-03-01 15:59:27 +08:00
commit a9e97d56cb
1426 changed files with 172367 additions and 0 deletions

View File

@@ -0,0 +1,331 @@
//
// 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> //
#import "SellyCallControlView.h"
#import <Masonry/Masonry.h>
@interface SellyVideoCallConferenceController ()<SellyRTCSessionDelegate, SellyCallControlViewDelegate>
// WebRTC
@property (nonatomic, strong) SellyRTCSession *session;
@property (nonatomic, assign) BOOL localVideoEnable;
@property (nonatomic, assign) BOOL localAudioEnable;
@property (nonatomic, assign) BOOL speakerEnabled;
@property (nonatomic, assign) BOOL screenShareEnabled;
@property (weak, nonatomic) IBOutlet SLSVideoGridView *grid;
@property (weak, nonatomic) IBOutlet UILabel *duration;
@property (nonatomic, strong) RPSystemBroadcastPickerView *systemBroadcastPicker;
@property (nonatomic, strong) SellyCallControlView *controlView;
@end
@implementation SellyVideoCallConferenceController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"音视频会议";
[UIApplication sharedApplication].idleTimerDisabled = YES;
self.session.delegate = self;
if (self.videoConfig == nil) {
self.videoConfig = SellyRTCVideoConfiguration.defaultConfig;
}
self.session.videoConfig = self.videoConfig;
NSString *token = [TokenGenerator generateRTCCallTokenWithUserId:SellyCloudManager.sharedInstance.userId callId:self.channelId];
[self.session startWithChannelId:self.channelId token:token];
//
[self onCameraClick:nil];
//
{
//使receiverspeaker
// [self.session setAudioOutput:AVAudioSessionPortOverrideSpeaker];
}
{
//使receiverspeaker
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];
[self setupControlView];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//
[self.navigationController setNavigationBarHidden:YES animated:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
//
[self.navigationController setNavigationBarHidden:NO animated:animated];
}
- (void)setupControlView {
//
self.controlView = [[SellyCallControlView alloc] init];
self.controlView.delegate = self;
//
self.controlView.showScreenShareButton = YES;
[self.view addSubview:self.controlView];
// 使 Masonry
[self.controlView mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.equalTo(self.view);
make.bottom.equalTo(self.view).offset(-20);
make.height.mas_equalTo(200);
}];
//
self.speakerEnabled = YES;
[self.controlView updateSpeakerEnabled:self.speakerEnabled];
[self.controlView updateVideoEnabled:self.localVideoEnable];
[self.controlView updateMuteEnabled:!self.localAudioEnable];
[self.controlView updateScreenShareEnabled:self.screenShareEnabled];
}
- (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];
[UIApplication sharedApplication].idleTimerDisabled = NO;
}
- (IBAction)onSpeakerClick:(id)sender {
//
AVAudioSessionPort currentPort = AVAudioSession.sharedInstance.currentRoute.outputs.firstObject.portType;
if ([currentPort isEqualToString:AVAudioSessionPortBuiltInSpeaker]) {
[self.session setAudioOutput:AVAudioSessionPortOverrideNone];
self.speakerEnabled = NO;
}
else {
[self.session setAudioOutput:AVAudioSessionPortOverrideSpeaker];
self.speakerEnabled = YES;
}
[self.controlView updateSpeakerEnabled:self.speakerEnabled];
}
- (IBAction)onCameraClick:(id)sender {
[self.session enableLocalVideo:!self.localVideoEnable];
self.localVideoEnable = !self.localVideoEnable;
[self.controlView updateVideoEnabled:self.localVideoEnable];
}
- (IBAction)onSwitchClick:(id)sender {
[self.session switchCamera];
}
- (IBAction)onMuteClick:(id)sender {
[self.session enableLocalAudio:!self.localAudioEnable];
self.localAudioEnable = !self.localAudioEnable;
[self.controlView updateMuteEnabled:!self.localAudioEnable];
}
#pragma mark - SellyCallControlViewDelegate
- (void)callControlView:(SellyCallControlView *)controlView didTapAction:(SellyCallControlAction)action {
switch (action) {
case SellyCallControlActionSpeaker:
[self onSpeakerClick:nil];
break;
case SellyCallControlActionVideo:
[self onCameraClick:nil];
break;
case SellyCallControlActionSwitchCamera:
[self onSwitchClick:nil];
break;
case SellyCallControlActionMute:
[self onMuteClick:nil];
break;
case SellyCallControlActionScreenShare:
[self startScreenCapture:nil];
break;
case SellyCallControlActionHangup:
[self.navigationController popViewControllerAnimated:YES];
break;
default:
break;
}
}
- (void)attachLocalStream {
NSString *uid = SellyCloudManager.sharedInstance.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:(SellyRTCStats *)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 generateRTCCallTokenWithUserId:SellyCloudManager.sharedInstance.userId callId:self.channelId];
[session renewToken:newToken];
}
- (void)rtcSession:(SellyRTCSession *)session tokenExpired:(NSString *)token {
NSString *newToken = [TokenGenerator generateRTCCallTokenWithUserId:SellyCloudManager.sharedInstance.userId callId:self.channelId];
[session renewToken:newToken];
}
- (void)rtcSession:(SellyRTCSession *)session onScreenShareStatusChanged:(SellyScreenShareState)state {
if (state == SellyScreenShareStateStarted) {
self.screenShareEnabled = YES;
self.localVideoEnable = false;
[self.session startScreenCapture];
[self.controlView updateScreenShareEnabled:self.screenShareEnabled];
[self.controlView updateVideoEnabled:self.localVideoEnable];
}
else if (state == SellyScreenShareStateStopped) {
self.screenShareEnabled = NO;
self.localVideoEnable = true;
[self.session enableLocalVideo:true];
[self.controlView updateScreenShareEnabled:self.screenShareEnabled];
[self.controlView updateVideoEnabled:self.localVideoEnable];
}
}
- (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