rtmp、rtc推拉流支持加密

This commit is contained in:
caleb
2026-04-07 16:35:04 +08:00
parent bc56b7851d
commit 88800334ec
19 changed files with 370 additions and 147 deletions

View File

@@ -4,15 +4,11 @@
//
#import "AVUserManager.h"
#import <AFNetworking/AFNetworking.h>
#import "AVApiService.h"
static NSString * const kUserLoggedInKey = @"AVUserLoggedIn";
static NSString * const kUserUsernameKey = @"AVUserUsername";
@interface AVUserManager ()
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
@end
@implementation AVUserManager
+ (instancetype)sharedManager {
@@ -27,15 +23,8 @@ static NSString * const kUserUsernameKey = @"AVUserUsername";
- (instancetype)init {
self = [super init];
if (self) {
// UserDefaults
_isLoggedIn = [[NSUserDefaults standardUserDefaults] boolForKey:kUserLoggedInKey];
_currentUsername = [[NSUserDefaults standardUserDefaults] stringForKey:kUserUsernameKey];
//
self.sessionManager = [AFHTTPSessionManager manager];
self.sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];
self.sessionManager.requestSerializer = [AFJSONRequestSerializer serializer];
self.sessionManager.requestSerializer.timeoutInterval = 10;
}
return self;
}
@@ -43,89 +32,24 @@ static NSString * const kUserUsernameKey = @"AVUserUsername";
- (void)loginWithUsername:(NSString *)username
password:(NSString *)password
completion:(void (^)(BOOL success, NSString * _Nullable errorMessage))completion {
//
if (username.length == 0) {
if (completion) {
completion(NO, @"用户名不能为空");
}
if (completion) completion(NO, @"用户名不能为空");
return;
}
if (password.length == 0) {
if (completion) {
completion(NO, @"密码不能为空");
}
if (completion) completion(NO, @"密码不能为空");
return;
}
// API
NSString *loginURL = @"http://rtmp.sellycloud.io:8089/live/sdk/demo/login";
//
NSDictionary *parameters = @{
@"username": username,
@"password": password
};
NSLog(@"🚀 开始登录请求: %@", username);
// POST
[self.sessionManager POST:loginURL parameters:parameters headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
//
NSLog(@"✅ 登录成功,服务器响应: %@", responseObject);
//
[self saveLoginStateWithUsername:username];
// tokenuserId
//
// if (responseObject[@"token"]) {
// [[NSUserDefaults standardUserDefaults] setObject:responseObject[@"token"] forKey:@"AVUserToken"];
// }
//
if (completion) {
completion(YES, nil);
[[AVApiService shared] loginWithUsername:username password:password
success:^(id responseObject) {
[self saveLoginStateWithUsername:username];
if (completion) completion(YES, nil);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
//
NSLog(@"❌ 登录失败: %@", error.localizedDescription);
NSString *errorMessage = @"登录失败,请检查用户名和密码";
// HTTP
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
if (httpResponse) {
NSLog(@"❌ HTTP 状态码: %ld", (long)httpResponse.statusCode);
}
//
if (error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]) {
NSData *errorData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
NSDictionary *errorDict = [NSJSONSerialization JSONObjectWithData:errorData options:0 error:nil];
NSLog(@"❌ 服务器错误响应: %@", errorDict);
//
if (errorDict[@"message"]) {
errorMessage = errorDict[@"message"];
} else if (errorDict[@"error"]) {
errorMessage = errorDict[@"error"];
} else if (errorDict[@"msg"]) {
errorMessage = errorDict[@"msg"];
}
} else {
//
errorMessage = [NSString stringWithFormat:@"网络请求失败: %@", error.localizedDescription];
}
//
if (completion) {
completion(NO, errorMessage);
}
}];
failure:^(NSError *error, NSString *serverMessage) {
NSString *msg = serverMessage ?: [NSString stringWithFormat:@"网络请求失败: %@", error.localizedDescription];
if (completion) completion(NO, msg);
}];
}
- (void)saveLoginStateWithUsername:(NSString *)username {

View File

@@ -13,9 +13,7 @@
#import "AVLiveStreamModel.h"
#import "AVLiveStreamCell.h"
#import "SCPlayerConfigView.h"
#import <AFNetworking/AFNetworking.h>
#import <AFNetworking/UIImageView+AFNetworking.h>
#import <YYModel/YYModel.h>
#import "AVApiService.h"
@interface AVHomeViewController () <UICollectionViewDelegate, UICollectionViewDataSource>
@property (nonatomic, strong) UIView *headerView;
@@ -23,13 +21,11 @@
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSArray<AVLiveStreamModel *> *liveStreams;
@property (nonatomic, strong) UIRefreshControl *refreshControl;
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
@end
@implementation AVHomeViewController
static NSString * const kLiveStreamCellIdentifier = @"LiveStreamCell";
static NSString * const kLiveListAPIURL = @"http://rtmp.sellycloud.io:8089/live/sdk/alive-list";
- (void)viewDidLoad {
[super viewDidLoad];
@@ -53,11 +49,6 @@ static NSString * const kLiveListAPIURL = @"http://rtmp.sellycloud.io:8089/live/
//
self.liveStreams = [NSMutableArray array];
// AFNetworking
self.sessionManager = [AFHTTPSessionManager manager];
self.sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];
self.sessionManager.requestSerializer.timeoutInterval = 10;
[self setupHeaderButtons];
[self setupCollectionView];
@@ -191,30 +182,20 @@ static NSString * const kLiveListAPIURL = @"http://rtmp.sellycloud.io:8089/live/
- (void)fetchLiveStreams {
__weak typeof(self) weakSelf = self;
[self.sessionManager GET:kLiveListAPIURL parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, NSDictionary *responseObject) {
[[AVApiService shared] fetchLiveStreams:^(NSArray<AVLiveStreamModel *> *streams) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
NSLog(@"%@",responseObject);
[strongSelf.refreshControl endRefreshing];
//
strongSelf.liveStreams = [NSArray yy_modelArrayWithClass:AVLiveStreamModel.class json:responseObject[@"list"]];
NSLog(@"✅ 成功获取 %ld 个直播流", (long)strongSelf.liveStreams.count);
// UI
strongSelf.liveStreams = streams;
NSLog(@"✅ 成功获取 %ld 个直播流", (long)streams.count);
[strongSelf.collectionView reloadData];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
} failure:^(NSError *error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;
[strongSelf.refreshControl endRefreshing];
NSLog(@"❌ 网络请求失败: %@", error.localizedDescription);
//
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"加载失败"
message:[NSString stringWithFormat:@"无法获取直播列表: %@", error.localizedDescription]
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
[strongSelf presentViewController:alert animated:YES completion:nil];
@@ -349,6 +330,7 @@ static NSString * const kLiveListAPIURL = @"http://rtmp.sellycloud.io:8089/live/
liveStream.preview_image = @"";
liveStream.duration = 0;
liveStream.startTime = [[NSDate date] timeIntervalSince1970];
liveStream.xorKey = config.xorKey;
// playProtocol
switch (config.protocol) {

View File

@@ -17,6 +17,7 @@
@property (nonatomic, strong) UILabel *nameLabel;
@property (nonatomic, strong) UILabel *liveLabel;
@property (nonatomic, strong) UILabel *protocolLabel;
@property (nonatomic, strong) UIImageView *encryptIcon;
@end
@@ -141,6 +142,19 @@
make.centerY.equalTo(_liveLabel);
make.height.equalTo(@16);
make.width.greaterThanOrEqualTo(@40);
}];
//
_encryptIcon = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"lock.shield.fill"]];
_encryptIcon.tintColor = [UIColor systemOrangeColor];
_encryptIcon.contentMode = UIViewContentModeScaleAspectFit;
_encryptIcon.hidden = YES;
[infoContainerView addSubview:_encryptIcon];
[_encryptIcon mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_protocolLabel.mas_right).offset(6);
make.centerY.equalTo(_liveLabel);
make.width.height.equalTo(@20);
make.right.lessThanOrEqualTo(infoContainerView);
}];
}
@@ -154,6 +168,9 @@
//
_protocolLabel.text = model.play_protocol.uppercaseString;
//
_encryptIcon.hidden = (model.xorKey.length == 0);
// 使 SDWebImage
if (model.preview_image.length > 0) {
@@ -177,6 +194,7 @@
_nameLabel.text = @"";
_durationLabel.text = @"";
_protocolLabel.text = @"";
_encryptIcon.hidden = YES;
}
@end