SellyCloudSDK_demo/Example/SellyCloudSDK/VideoCall/SellyVideoCallViewController.m

258 lines
9.7 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.

//
// SellyVideoCallViewController.m
// SellyCloudSDK_Example
//
// Created by Caleb on 3/11/25.
// Copyright © 2025 Caleb. All rights reserved.
//
#import "SellyVideoCallViewController.h"
#import <SellyCloudSDK/SellyCloudManager.h>
#import "FUManager.h"
#import "UIView+SellyCloud.h"
#import "SellyCallPiPManager.h"
#import "TokenGenerator.h"
@interface SellyVideoCallViewController ()<SellyRTCSessionDelegate>
@property (weak, nonatomic) IBOutlet UIView *localView;
@property (weak, nonatomic) IBOutlet UIView *remoteView;
@property (nonatomic, strong)SellyRTCEngine *engine;
@property (nonatomic, strong)SellyRTCSession *session;
@property (nonatomic, assign)BOOL localVideoEnable;
@property (nonatomic, assign)BOOL localAudioEnable;
@property (nonatomic, strong)AVAudioPlayer *player;
@property (weak, nonatomic) IBOutlet UILabel *bitrate;
@property (weak, nonatomic) IBOutlet UILabel *videoFps;
@property (weak, nonatomic) IBOutlet UILabel *rtt;
@property (weak, nonatomic) IBOutlet UILabel *codec;
@property (weak, nonatomic) IBOutlet UILabel *duration;
@property (weak, nonatomic) IBOutlet UILabel *videoSize;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *remoteWConstraint;
@property (nonatomic, strong) SellyCallPiPManager *pipManager;
@end
@implementation SellyVideoCallViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"音视频单聊";
// Do any additional setup after loading the view from its nib.
SellyRtcVideoCanvas *localCanvas = SellyRtcVideoCanvas.new;
localCanvas.view = self.localView;
localCanvas.userId = SellyRTCEngine.sharedEngine.userId;
[self.session setLocalCanvas:localCanvas];
self.session.delegate = self;
self.session.delegate = self;
if (self.videoConfig == nil) {
self.videoConfig = SellyRTCVideoConfiguration.defaultConfig;
}
self.session.videoConfig = self.videoConfig;
//模拟先本地预览响铃5s后接通的流程
[self.session startPreview];
//开启视频
[self onCameraClick:nil];
//模拟等待接听铃声
[self playSourceName:nil numberOfLoops:100];
//设置扬声器播放
{
//使用这种方案通话接通前无法在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];
}
self.remoteWConstraint.constant = 200*self.session.videoConfig.resolution.height/self.session.videoConfig.resolution.width;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//模拟3s后接通
NSString *token = [TokenGenerator generateTokenWithUserId:SellyRTCEngine.sharedEngine.userId callId:self.channelId];
[self.session startWithChannelId:self.channelId token:token];
});
//默认语音开启
self.localAudioEnable = true;
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.session end];
[self QCM_stopRing];
//正常情况下退出通话要保持,这里只做演示
if (@available(iOS 15.0, *)) {
if (self.pipManager.pipActive) {
[self.pipManager togglePiP]; // 关掉 PiP
}
}
}
- (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;
}
- (IBAction)onActionPIP:(id)sender {
if (@available(iOS 15.0, *)) {
if (self.pipManager.pipPossible) {
[self.pipManager togglePiP];
} else {
[self.view showToast:@"当前设备不支持画中画"];
}
} else {
[self.view showToast:@"iOS 15 以上才支持自定义 PiP"];
}
}
- (void)playSourceName:(NSString *)source numberOfLoops:(NSInteger)numberOfLoops {
NSString *url = [NSBundle.mainBundle pathForResource:@"call" ofType:@"caf"];
_player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:url] error:nil];
_player.numberOfLoops = numberOfLoops;
[_player play];
}
- (void)QCM_stopRing {
if (_player && _player.isPlaying) {
[_player stop];
}
}
#pragma marks SellyRTCSessionDelegate
- (void)rtcSession:(SellyRTCSession *)session didReceiveMessage:(NSString *)message userId:(NSString *)userId {
NSLog(@"userId == %@ didReceiveMessage == %@",userId,message);
}
- (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 connectionStateChanged:(SellyRTCConnectState)state userId:(nullable NSString *)userId {
NSLog(@"ice.connectionStateChanged == %ld",state);
// 初始化 PiP Manager
if (state == SellyRTCConnectStateConnected && !self.pipManager) {
self.pipManager = [[SellyCallPiPManager alloc] initWithRenderView:self.remoteView];
[self.pipManager setupIfNeeded];
}
}
- (void)rtcSession:(SellyRTCSession *)session onRoomConnectionStateChanged:(SellyRoomConnectionState)state {
NSLog(@"onSocketStateChanged == %ld",(long)state);
}
- (CVPixelBufferRef)rtcSession:(SellyRTCSession *)session onCaptureVideoFrame:(CVPixelBufferRef)pixelBuffer {
CVPixelBufferRef afterBuffer = [FUManager.shareManager renderItemsToPixelBuffer:pixelBuffer];
return afterBuffer;
}
//返回false sdk内部将不会默认渲染改帧
- (BOOL)rtcSession:(SellyRTCSession *)session
onRenderVideoFrame:(SellyRTCVideoFrame *)videoFrame
userId:(NSString *)userId {
// 1. SDK 继续默认渲染到你设置的 canvaslocalView/remoteView
// 2. 同时,我们把远端的帧喂给 PiP layer。假设你想 PiP 显示远端 userId
[self.pipManager feedVideoFrame:videoFrame];
return false; // 仍然让 SDK 内部按 canvas 渲染
}
- (void)rtcSession:(SellyRTCSession *)session onError:(NSError *)error {
NSLog(@"rtcSession.error == %@",error);
[self.session end];
[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 onStats:(SellyRTCP2pStats *)stats userId:(nullable NSString *)userId {
self.bitrate.text = [NSString stringWithFormat:@"%ld/%ld",(NSInteger)stats.txKbps,(NSInteger)stats.rxKbps];
self.videoFps.text = [NSString stringWithFormat:@"%ld/%ld",(NSInteger)stats.sentFps,(NSInteger)stats.recvFps];
self.rtt.text = [NSString stringWithFormat:@"%ld",(NSInteger)stats.transportRttMs];
self.codec.text = [NSString stringWithFormat:@"%@/%@",stats.videoCodec,stats.audioCodec];
self.videoSize.text = [NSString stringWithFormat:@"%ldx%ld",stats.recvWidth,stats.recvHeight];
}
- (void)rtcSession:(SellyRTCSession *)session onDuration:(NSInteger)duration {
self.duration.text = [NSString stringWithFormat:@"%02ld:%02ld",duration/60,duration%60];
}
- (void)rtcSession:(SellyRTCSession *)session onUserJoined:(NSString *)userId {
NSLog(@"###onUserJoined == %@",userId);
SellyRtcVideoCanvas *remoteCanvas = SellyRtcVideoCanvas.new;
remoteCanvas.view = self.remoteView;
remoteCanvas.userId = userId;
[self.session setRemoteCanvas:remoteCanvas];
[self QCM_stopRing];
}
- (void)rtcSession:(SellyRTCSession *)session onUserLeave:(NSString *)userId {
NSLog(@"####onUserLeave == %@",userId);
[self.navigationController popViewControllerAnimated:true];
}
- (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];
}
- (SellyRTCEngine *)engine {
return SellyRTCEngine.sharedEngine;
}
- (SellyRTCSession *)session {
if (!_session) {
_session = [[SellyRTCSession alloc] initWithType:true];
}
return _session;
}
- (void)dealloc
{
[self.session setAudioOutput:AVAudioSessionPortOverrideNone];
NSError *error;
[AVAudioSession.sharedInstance setCategory:AVAudioSessionCategoryPlayback error:&error];
}
@end