支持业务层直接推送视频帧,支持屏幕共享推流

This commit is contained in:
Caleb 2025-12-11 16:52:45 +08:00
parent 14431c1570
commit b85cee5d0d
17 changed files with 209 additions and 182 deletions

View File

@ -12,6 +12,7 @@
#import "UIView+SellyCloud.h" #import "UIView+SellyCloud.h"
#import "SLSVideoGridView.h" #import "SLSVideoGridView.h"
#import "TokenGenerator.h" #import "TokenGenerator.h"
#import <ReplayKit/ReplayKit.h> //
@interface SellyVideoCallConferenceController ()<SellyRTCSessionDelegate> @interface SellyVideoCallConferenceController ()<SellyRTCSessionDelegate>
@ -23,7 +24,7 @@
@property (weak, nonatomic) IBOutlet SLSVideoGridView *grid; @property (weak, nonatomic) IBOutlet SLSVideoGridView *grid;
@property (weak, nonatomic) IBOutlet UILabel *duration; @property (weak, nonatomic) IBOutlet UILabel *duration;
@property (nonatomic, strong)RPSystemBroadcastPickerView *systemBroadcastPicker;
@end @end
@implementation SellyVideoCallConferenceController @implementation SellyVideoCallConferenceController
@ -60,6 +61,29 @@
//view //view
[self attachLocalStream]; [self attachLocalStream];
self.localAudioEnable = true; 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 { - (void)viewDidDisappear:(BOOL)animated {
@ -187,6 +211,17 @@
[session renewToken:newToken]; [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 { - (SellyRTCSession *)session {
if (!_session) { if (!_session) {
_session = [[SellyRTCSession alloc] initWithType:false]; _session = [[SellyRTCSession alloc] initWithType:false];

314
README.md
View File

@ -1,42 +1,42 @@
# SellyCloudSDK # SellyRTC iOS SDK 接入文档
# 📘 SellyRTC iOS SDK 接入文档 本文档介绍如何使用 **SellyRTCSession** 快速集成一对一或多人音视频通话功能,包括:
本文档介绍如何使用 **SellyRTCSession** 快速集成一对一或多人音视频通话功能,包括基本接入、音视频控制、数据处理、事件回调、统计与 Token 更新机制。 - Session 创建与基础通话流程
- 本地 / 远端画面渲染
- 音视频控制与通话路由
- 屏幕分享与外部视频源推流
- 自定义消息、统计与 Token 机制
- 连接状态与事件回调
--- ---
## 📑 目录 ## 📑 目录
1. [准备工作](#准备工作)
1. [准备工作](#准备工作)
2. [快速开始](#快速开始) 2. [快速开始](#快速开始)
- 创建 Session
- 设置本地/远端画面
- 配置视频参数
- 加入频道
- 结束通话
3. [常用功能](#常用功能) 3. [常用功能](#常用功能)
- 开关音视频 4. [屏幕分享与外部视频源](#屏幕分享与外部视频源)
- 切换摄像头 5. [视频帧前后处理](#视频帧前后处理)
- 静音远端 6. [事件回调Delegate](#事件回调delegate)
- 音频输出控制 7. [通话统计](#通话统计)
- 发送自定义消息 8. [Token 过期机制](#token-过期机制)
- Token 更新 9. [连接状态说明](#连接状态说明)
4. [视频/音频帧处理](#视频音频帧处理) 10. [常见问题](#常见问题)
5. [事件回调 (Delegate)](#事件回调-delegate)
6. [通话统计](#通话统计)
7. [Token 过期机制](#token-过期机制)
8. [常见问题](#常见问题)
--- ---
# 🔧 准备工作 # 🔧 准备工作
确保已正确集成 `SellyRTC.framework` 并导入头文件: 1. 集成 SellyRTC.framework
2. 引入头文件:
```objc ```objc
#import <SellyRTC/SellyRTC.h> #import <SellyRTC/SellyRTC.h>
``` ```
3. 在真机运行(摄像头/麦克风需要硬件支持)
--- ---
# 🚀 快速开始 # 🚀 快速开始
@ -44,39 +44,34 @@
## 1. 创建 Session ## 1. 创建 Session
```objc ```objc
// YES = 单聊P2P NO = 多人会议 /// @param isP2p YES 表示一对一通话P2P 直连NO 表示多人通话(房间 / 会议模式)
///
/// P2P 模式YES
/// - 点对点直连,端到端延迟更低
/// - 适合普通一对一音视频通话
self.session = [[SellyRTCSession alloc] initWithType:YES]; self.session = [[SellyRTCSession alloc] initWithType:YES];
self.session.delegate = self; self.session.delegate = self;
``` ```
⚠️ `init``new` 已禁用,必须使用 `initWithType:`
--- ---
## 2. 设置本地 & 远端画布 ## 2. 设置本地远端画布
```objc ```objc
// 本地视频
SellyRtcVideoCanvas *local = [SellyRtcVideoCanvas new]; SellyRtcVideoCanvas *local = [SellyRtcVideoCanvas new];
local.view = self.localView; local.view = self.localView;
[self.session setLocalCanvas:local]; [self.session setLocalCanvas:local];
// 远端视频(多人时 userId 必填)
SellyRtcVideoCanvas *remote = [SellyRtcVideoCanvas new]; SellyRtcVideoCanvas *remote = [SellyRtcVideoCanvas new];
remote.view = self.remoteView; remote.view = self.remoteView;
remote.userId = @"remoteUser"; remote.userId = @"remoteUserId";
[self.session setRemoteCanvas:remote]; [self.session setRemoteCanvas:remote];
``` ```
> 多人会议:可多次调用 `setRemoteCanvas:`
> SDK 根据 `canvas.userId` 将对应远端视频渲染到指定 view。
--- ---
## 3. 配置视频参数(可选) ## 3. 配置视频参数(可选)
必须在加入频道前配置:
```objc ```objc
SellyRTCVideoConfiguration *config = [SellyRTCVideoConfiguration defaultConfig]; SellyRTCVideoConfiguration *config = [SellyRTCVideoConfiguration defaultConfig];
config.width = 720; config.width = 720;
@ -88,15 +83,29 @@ self.session.videoConfig = config;
--- ---
## 4. 加入频道 / 发起通话 ## 4. 打开本地预览
```objc ```objc
[self.session startWithChannelId:@"room123" token:@"xxxx-token"]; [self.session startPreview];
```
关闭:
```objc
[self.session stopPreview];
``` ```
--- ---
## 5. 结束通话 ## 5. 加入频道(开始通话)
```objc
[self.session startWithChannelId:@"room_123" token:@"your-token"];
```
---
## 6. 结束通话
```objc ```objc
[self.session end]; [self.session end];
@ -106,48 +115,42 @@ self.session.videoConfig = config;
# 🎛 常用功能 # 🎛 常用功能
## 开/关本地视频 ## 开关本地视频
```objc ```objc
[self.session enableLocalVideo:NO];
[self.session enableLocalVideo:YES]; [self.session enableLocalVideo:YES];
[self.session enableLocalVideo:NO];
``` ```
## 开/关本地音频采集 ## 开关本地音频
```objc ```objc
[self.session enableLocalAudio:NO]; [self.session enableLocalAudio:NO];
[self.session enableLocalAudio:YES]; [self.session enableLocalAudio:YES];
``` ```
## 切换前后摄像头 ## 切换摄像头
```objc ```objc
[self.session switchCamera]; [self.session switchCamera];
``` ```
## 静音远端音频 ## 静音远端
```objc ```objc
// P2P 模式 userId 可传 nil [self.session muteRemoteAudioStream:@"remoteUser" mute:YES];
[self.session muteRemoteAudioStream:nil mute:YES];
``` ```
## 控制音频输出(扬声器/听筒 ## 音频输出(听筒/扬声器)
```objc ```objc
[self.session setAudioOutput:AVAudioSessionPortOverrideSpeaker]; [self.session setAudioOutput:AVAudioSessionPortOverrideSpeaker];
``` ```
如有耳机 / 蓝牙设备,建议外部使用 `AVRoutePickerView`
## 发送自定义消息 ## 发送自定义消息
```objc ```objc
[self.session sendMessage:@"Hello World" [self.session sendMessage:@"hello" completion:^(NSError *error) {}];
completion:^(NSError *error) {
if (error) NSLog(@"发送失败: %@", error);
}];
``` ```
## 更新 Token ## 更新 Token
@ -158,203 +161,154 @@ self.session.videoConfig = config;
--- ---
# 🎥 视频 / 音频帧处理 # 🖥 屏幕分享与外部视频源
## 视频采集前处理(美颜、滤镜、自定义 GPU ## 开启屏幕分享
```objc ```objc
- (CVPixelBufferRef)rtcSession:(SellyRTCSession *)session [self.session startScreenCapture];
onCaptureVideoFrame:(CVPixelBufferRef)pixelBuffer { ```
// 1. 执行美颜、贴纸、旋转等处理 屏幕分享状态回调:
// 2. 返回处理后的 pixelBuffer
```objc
- (void)rtcSession:(SellyRTCSession *)session onScreenShareStatusChanged:(SellyScreenShareState)state {}
```
---
## 外部推流(自定义采集)
```objc
[self.session enableLocalVideo:YES];
[self.session pushExternalVideoFrame:pixelBuffer];
```
常用于游戏画面/ReplayKit 输出/第三方渲染等。
---
# 🎥 视频帧前后处理
## 本地采集前处理(美颜/滤镜等)
```objc
- (CVPixelBufferRef)rtcSession:(SellyRTCSession *)session onCaptureVideoFrame:(CVPixelBufferRef)pixelBuffer {
return pixelBuffer; return pixelBuffer;
} }
``` ```
## 远端视频解码后回调AI 分析、自定义渲染) ## 远端渲染前处理(做 AI/贴纸/录像
```objc ```objc
- (BOOL)rtcSession:(SellyRTCSession *)session - (BOOL)rtcSession:(SellyRTCSession *)session onRenderVideoFrame:(SellyRTCVideoFrame *)videoFrame userId:(NSString *)userId {
onRenderVideoFrame:(SellyRTCVideoFrame *)videoFrame
userId:(NSString *)userId {
// 返回 YES继续渲染
// 返回 NO丢弃该帧
return YES; return YES;
} }
``` ```
--- ---
# 📡 事件回调 (Delegate) # 📡 事件回调Delegate
## 通话严重错误
```objc ```objc
#pragma mark - SellyRTCSessionDelegate - (void)rtcSession:(SellyRTCSession *)session onError:(NSError *)error {}
``` ```
## 通话错误(不可恢复) ## 远端音视频开关
```objc ```objc
- (void)rtcSession:(SellyRTCSession *)session onError:(NSError *)error; - (void)rtcSession:(SellyRTCSession *)s videoEnabled:(BOOL)enabled userId:(NSString *)userId {}
``` ```
## 远端启用/关闭音视频 ## 自定义消息
```objc ```objc
- (void)rtcSession:(SellyRTCSession *)session - (void)rtcSession:(SellyRTCSession *)s didReceiveMessage:(NSString *)msg userId:(NSString *)uid {}
videoEnabled:(BOOL)enabled
userId:(NSString *)userId;
- (void)rtcSession:(SellyRTCSession *)session
audioEnabled:(BOOL)enabled
userId:(NSString *)userId;
``` ```
## 收到消息 ## 流级连接状态
```objc ```objc
- (void)rtcSession:(SellyRTCSession *)session - (void)rtcSession:(SellyRTCSession *)s connectionStateChanged:(SellyRTCConnectState)state userId:(NSString *)uid {}
didReceiveMessage:(NSString *)message
userId:(NSString *)userId;
``` ```
## 连接状态变化 ## 会话级连接状态
```objc ```objc
- (void)rtcSession:(SellyRTCSession *)session - (void)rtcSession:(SellyRTCSession *)s onRoomConnectionStateChanged:(SellyRoomConnectionState)state {}
connectionStateChanged:(SellyRTCConnectState)state
userId:(NSString *)userId;
```
连接状态包括:
```
Disconnected
Connecting
Connected
Reconnecting
Failed
``` ```
## 用户加入/离开 ## 用户加入/离开
```objc ```objc
- (void)rtcSession:(SellyRTCSession *)session onUserJoined:(NSString *)userId; - (void)rtcSession:(SellyRTCSession *)s onUserJoined:(NSString *)uid {}
- (void)rtcSession:(SellyRTCSession *)s onUserLeave:(NSString *)uid {}
- (void)rtcSession:(SellyRTCSession *)session onUserLeave:(NSString *)userId;
``` ```
## 通话时长 ## 通话时长
```objc ```objc
- (void)rtcSession:(SellyRTCSession *)session onDuration:(NSInteger)duration; - (void)rtcSession:(SellyRTCSession *)s onDuration:(NSInteger)duration {}
```
## Token 回调
```objc
- (void)rtcSession:(SellyRTCSession *)s tokenWillExpire:(NSString *)token {}
- (void)rtcSession:(SellyRTCSession *)s tokenExpired:(NSString *)token {}
```
## 屏幕分享状态
```objc
- (void)rtcSession:(SellyRTCSession *)s onScreenShareStatusChanged:(SellyScreenShareState)state {}
``` ```
--- ---
# 📊 通话统计信息 # 📊 通话统计
```objc ```objc
- (void)rtcSession:(SellyRTCSession *)session - (void)rtcSession:(SellyRTCSession *)s onStats:(SellyRTCP2pStats *)stats userId:(NSString *)uid {}
onStats:(SellyRTCP2pStats *)stats
userId:(NSString *)userId;
``` ```
可获取:
- 上下行码率
- 帧率
- 丢包率
- RTT
- 解码帧数等
--- ---
# 🔑 Token 过期机制 # 🔑 Token 过期机制
## Token 即将过期 - Token 将在 **60 秒前** 触发 `tokenWillExpire`
- Token 过期后通话继续,但重连会失败
```objc - 请及时调用 `renewToken:`
- (void)rtcSession:(SellyRTCSession *)session tokenWillExpire:(NSString *)token;
```
业务应立即向服务器请求新 Token
```objc
[self.session renewToken:newToken];
```
## Token 已过期(通话仍继续)
```objc
- (void)rtcSession:(SellyRTCSession *)session tokenExpired:(NSString *)token;
```
⚠️ 过期后通话不受影响,但**断网重连会失败**,务必及时更新 Token。
--- ---
# ❓ 常见问题 # 🌐 连接状态说明
### Q1多人远端画面如何渲染 `connectionState` 可读取当前房间状态:
重复调用: - Disconnected
- Connecting
```objc - Connected
SellyRtcVideoCanvas *canvas = [SellyRtcVideoCanvas new]; - Reconnecting
canvas.view = remoteViewX;
canvas.userId = @"userX";
[self.session setRemoteCanvas:canvas];
```
SDK 会根据 `userId` 做路由。
--- ---
### Q2远端画面不显示 # ❓ 常见问题 FAQ
### Q远端画面不显示怎么办
检查: 检查:
- remoteCanvas.view 是否正确添加 1. 是否设置了正确的 `remoteCanvas.userId`
- remoteCanvas.userId 是否一致 2. 是否 addSubview 到界面
- 是否重复 addSubview 覆盖 3. 是否在 onUserJoined 后调用 setRemoteCanvas
- 是否调用了 `setRemoteCanvas:`
### Q如何实现摄像头与屏幕分享切换
1. 开启摄像头 → enableLocalVideo:YES
2. 开启屏幕分享 → startScreenCapture
3. 收到屏幕分享结束 → 恢复摄像头
--- ---
### Q3如何做画中画 (PiP)
将本地 + 远端视频放入一个 containerView
```text
[container addSubview:remoteView]
[container addSubview:localView] // 小窗
```
并使用:
```objc
AVPictureInPictureControllerContentSource *source =
[[AVPictureInPictureControllerContentSource alloc] initWithActiveVideoCallSource:sampleBufferLayer
contentView:container];
```
---
# 🎉 完成
您已完成 SellyRTC 的全部集成内容。
如需更多帮助,可继续咨询:""
## Author
Caleb, liaoqiang1123@gmail.com
## License
SellyCloudSDK is available under the MIT license. See the LICENSE file for more info.

View File

@ -19,6 +19,7 @@
#import "SellyRTCSession.h" #import "SellyRTCSession.h"
#import "SellyRTCVideoConfiguration.h" #import "SellyRTCVideoConfiguration.h"
#import "SellyRTCSessionDelegate.h" #import "SellyRTCSessionDelegate.h"
#import "SellyRTCReplayKitHandler.h"
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN

View File

@ -1,6 +1,6 @@
#if 0 #if 0
#elif defined(__arm64__) && __arm64__ #elif defined(__arm64__) && __arm64__
// Generated by Apple Swift version 6.2 effective-5.10 (swiftlang-6.2.0.19.9 clang-1700.3.19.1) // Generated by Apple Swift version 6.2.1 effective-5.10 (swiftlang-6.2.1.4.8 clang-1700.4.4.1)
#ifndef SELLYCLOUDSDK_SWIFT_H #ifndef SELLYCLOUDSDK_SWIFT_H
#define SELLYCLOUDSDK_SWIFT_H #define SELLYCLOUDSDK_SWIFT_H
#pragma clang diagnostic push #pragma clang diagnostic push

View File

@ -19,6 +19,7 @@
#import "SellyPusherManagerDelegate.h" #import "SellyPusherManagerDelegate.h"
#import "SellyRTCEngine.h" #import "SellyRTCEngine.h"
#import "SellyRTCP2pStats.h" #import "SellyRTCP2pStats.h"
#import "SellyRTCReplayKitHandler.h"
#import "SellyRTCSession.h" #import "SellyRTCSession.h"
#import "SellyRTCSessionDelegate.h" #import "SellyRTCSessionDelegate.h"
#import "SellyRtcVideoCanvas.h" #import "SellyRtcVideoCanvas.h"

View File

@ -61,4 +61,11 @@ typedef NS_ENUM(NSInteger, SellyRoomConnectionState) {
SellyRoomStateFailed, SellyRoomStateFailed,
}; };
//屏幕分享状态
typedef NS_ENUM(uint8_t, SellyScreenShareState) {
SellyScreenShareStateStarted = 1, // 开始
SellyScreenShareStatePaused = 2, // 暂停
SellyScreenShareStateResumed = 3, // 恢复
SellyScreenShareStateStopped = 4, // 结束
};
#endif /* SellyPublicDefinition_h */ #endif /* SellyPublicDefinition_h */

View File

@ -0,0 +1,17 @@
//
// SellyRTCReplayKitHandler.h
// SellyCloudSDK
//
// Created by Caleb on 12/10/25.
//
#import <Foundation/Foundation.h>
#import <ReplayKit/ReplayKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface SellyRTCReplayKitHandler : RPBroadcastSampleHandler
@end
NS_ASSUME_NONNULL_END

View File

@ -79,6 +79,12 @@ NS_ASSUME_NONNULL_BEGIN
//更新token //更新token
- (void)renewToken:(NSString * _Nonnull)token; - (void)renewToken:(NSString * _Nonnull)token;
//Starts screen sharing.
- (void)startScreenCapture;
//外部推流不使用sdk默认的视频采集
- (void)pushExternalVideoFrame:(CVPixelBufferRef)pixelBuffer;
// //
@property (nonatomic, weak)id<SellyRTCSessionDelegate> delegate; @property (nonatomic, weak)id<SellyRTCSessionDelegate> delegate;

View File

@ -8,6 +8,7 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "SellyRTCP2pStats.h" #import "SellyRTCP2pStats.h"
#import "SellyRTCVideoFrame.h" #import "SellyRTCVideoFrame.h"
@class SellyRTCSession; @class SellyRTCSession;
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@ -113,6 +114,11 @@ token过期后依然可以正常通话但是断网重连会失败
*/ */
- (void)rtcSession:(SellyRTCSession * _Nonnull)session tokenExpired:(NSString *)token; - (void)rtcSession:(SellyRTCSession * _Nonnull)session tokenExpired:(NSString *)token;
/**
*/
- (void)rtcSession:(SellyRTCSession * _Nonnull)session onScreenShareStatusChanged:(SellyScreenShareState)state;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END