支持更多音视频格式,添加点播测试页面

This commit is contained in:
Caleb
2026-03-11 10:56:04 +08:00
parent 3d64acb766
commit fd433e4337
18 changed files with 411 additions and 48 deletions

View File

@@ -6,6 +6,7 @@
#import "AVTabBarController.h"
#import "AVHomeViewController.h"
#import "AVCallViewController.h"
#import "AVVodListViewController.h"
#import "AVSettingsViewController.h"
#import "AVLoginViewController.h"
#import "AVUserManager.h"
@@ -25,6 +26,13 @@
image:[UIImage systemImageNamed:@"house"]
selectedImage:[UIImage systemImageNamed:@"house.fill"]];
// Create VOD tab
AVVodListViewController *vodVC = [[AVVodListViewController alloc] init];
UINavigationController *vodNav = [[UINavigationController alloc] initWithRootViewController:vodVC];
vodNav.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"点播"
image:[UIImage systemImageNamed:@"play.rectangle"]
selectedImage:[UIImage systemImageNamed:@"play.rectangle.fill"]];
// Create Call tab
AVCallViewController *callVC = [[AVCallViewController alloc] init];
UINavigationController *callNav = [[UINavigationController alloc] initWithRootViewController:callVC];
@@ -40,7 +48,7 @@
selectedImage:[UIImage systemImageNamed:@"gearshape.fill"]];
// Set view controllers
self.viewControllers = @[homeNav, callNav, settingsNav];
self.viewControllers = @[homeNav, vodNav, callNav, settingsNav];
// Customize tab bar appearance
self.tabBar.tintColor = [UIColor systemBlueColor];

View File

@@ -0,0 +1,18 @@
//
// AVVodItemCell.h
// SellyCloudSDK_Example
//
#import <UIKit/UIKit.h>
@class AVVodItemModel;
NS_ASSUME_NONNULL_BEGIN
@interface AVVodItemCell : UICollectionViewCell
- (void)configureWithModel:(AVVodItemModel *)model;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,124 @@
//
// AVVodItemCell.m
// SellyCloudSDK_Example
//
#import "AVVodItemCell.h"
#import "AVVodItemModel.h"
#import <Masonry/Masonry.h>
#import <SDWebImage/SDWebImage.h>
@interface AVVodItemCell ()
@property (nonatomic, strong) UIImageView *thumbnailView;
@property (nonatomic, strong) UIView *overlayView;
@property (nonatomic, strong) UIImageView *playIcon;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *typeLabel;
@end
@implementation AVVodItemCell
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setupUI];
}
return self;
}
- (void)setupUI {
self.contentView.backgroundColor = [UIColor secondarySystemBackgroundColor];
self.contentView.layer.cornerRadius = 8;
self.contentView.clipsToBounds = YES;
// Thumbnail
_thumbnailView = [[UIImageView alloc] init];
_thumbnailView.backgroundColor = [UIColor systemGray4Color];
_thumbnailView.contentMode = UIViewContentModeScaleAspectFill;
_thumbnailView.clipsToBounds = YES;
[self.contentView addSubview:_thumbnailView];
// Overlay
_overlayView = [[UIView alloc] init];
_overlayView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.2];
[_thumbnailView addSubview:_overlayView];
// Play icon
_playIcon = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"play.circle.fill"]];
_playIcon.tintColor = [UIColor whiteColor];
_playIcon.contentMode = UIViewContentModeScaleAspectFit;
_playIcon.layer.shadowColor = [UIColor blackColor].CGColor;
_playIcon.layer.shadowOffset = CGSizeMake(0, 2);
_playIcon.layer.shadowOpacity = 0.3;
_playIcon.layer.shadowRadius = 4;
[_thumbnailView addSubview:_playIcon];
// Type label (badge on thumbnail)
_typeLabel = [[UILabel alloc] init];
_typeLabel.font = [UIFont systemFontOfSize:10 weight:UIFontWeightMedium];
_typeLabel.textColor = [UIColor whiteColor];
_typeLabel.backgroundColor = [UIColor systemBlueColor];
_typeLabel.textAlignment = NSTextAlignmentCenter;
_typeLabel.layer.cornerRadius = 3;
_typeLabel.clipsToBounds = YES;
[_thumbnailView addSubview:_typeLabel];
// Title label
_titleLabel = [[UILabel alloc] init];
_titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightSemibold];
_titleLabel.textColor = [UIColor labelColor];
_titleLabel.numberOfLines = 2;
[self.contentView addSubview:_titleLabel];
// Layout
[_thumbnailView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.equalTo(self.contentView);
make.height.equalTo(_thumbnailView.mas_width).multipliedBy(9.0 / 16.0).priority(750);
}];
[_overlayView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(_thumbnailView);
}];
[_playIcon mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(_thumbnailView);
make.width.height.equalTo(@44);
}];
[_typeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_thumbnailView).offset(8);
make.left.equalTo(_thumbnailView).offset(8);
make.height.equalTo(@18);
make.width.greaterThanOrEqualTo(@36);
}];
[_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(_thumbnailView.mas_bottom).offset(8);
make.left.equalTo(self.contentView).offset(8);
make.right.equalTo(self.contentView).offset(-8);
make.bottom.lessThanOrEqualTo(self.contentView).offset(-8).priority(750);
}];
}
- (void)configureWithModel:(AVVodItemModel *)model {
_titleLabel.text = model.title;
_typeLabel.text = [NSString stringWithFormat:@" %@ ", model.type.uppercaseString];
if (model.cover.length > 0) {
NSURL *imageURL = [NSURL URLWithString:model.cover];
[_thumbnailView sd_setImageWithURL:imageURL];
} else {
_thumbnailView.image = nil;
}
}
- (void)prepareForReuse {
[super prepareForReuse];
[_thumbnailView sd_cancelCurrentImageLoad];
_thumbnailView.image = nil;
_titleLabel.text = @"";
_typeLabel.text = @"";
}
@end

View File

@@ -0,0 +1,14 @@
//
// AVVodListViewController.h
// SellyCloudSDK_Example
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AVVodListViewController : UIViewController
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,139 @@
//
// AVVodListViewController.m
// SellyCloudSDK_Example
//
#import "AVVodListViewController.h"
#import "AVVodItemModel.h"
#import "AVVodItemCell.h"
#import "AVLiveStreamModel.h"
#import "SCVodVideoPlayerViewController.h"
#import "AVConstants.h"
#import <Masonry/Masonry.h>
@interface AVVodListViewController () <UICollectionViewDelegate, UICollectionViewDataSource>
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSArray<AVVodItemModel *> *vodItems;
@end
@implementation AVVodListViewController
static NSString * const kVodItemCellIdentifier = @"VodItemCell";
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor systemBackgroundColor];
self.title = @"点播测试";
self.vodItems = @[];
[self setupCollectionView];
[self fetchVodList];
}
#pragma mark - Network
- (void)fetchVodList {
NSURL *url = [NSURL URLWithString:@"http://rtmp.sellycloud.io:8089/live/sdk/demo/vodlist"];
NSURLSessionDataTask *task = [NSURLSession.sharedSession dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error || !data) {
NSLog(@"fetchVodList failed: %@", error);
return;
}
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if (![json isKindOfClass:NSDictionary.class]) return;
NSMutableArray<AVVodItemModel *> *items = [NSMutableArray array];
[json enumerateKeysAndObjectsUsingBlock:^(NSString *type, NSString *vodUrl, BOOL *stop) {
AVVodItemModel *item = [AVVodItemModel modelWithUrl:vodUrl
cover:@""
title:[NSString stringWithFormat:@"%@ 测试", type.uppercaseString]
type:type];
[items addObject:item];
}];
dispatch_async(dispatch_get_main_queue(), ^{
self.vodItems = items.copy;
[self.collectionView reloadData];
});
}];
[task resume];
}
#pragma mark - Setup UI
- (void)setupCollectionView {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.minimumInteritemSpacing = 12;
layout.minimumLineSpacing = 16;
layout.sectionInset = UIEdgeInsetsMake(16, 16, 16, 16);
self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
self.collectionView.backgroundColor = [UIColor systemBackgroundColor];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
self.collectionView.alwaysBounceVertical = YES;
[self.collectionView registerClass:[AVVodItemCell class] forCellWithReuseIdentifier:kVodItemCellIdentifier];
[self.view addSubview:self.collectionView];
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop);
make.left.right.bottom.equalTo(self.view);
}];
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.vodItems.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
AVVodItemCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kVodItemCellIdentifier forIndexPath:indexPath];
[cell configureWithModel:self.vodItems[indexPath.item]];
return cell;
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGFloat totalHorizontalPadding = 16 * 2 + 12;
CGFloat availableWidth = collectionView.bounds.size.width - totalHorizontalPadding;
CGFloat itemWidth = availableWidth / 2.0;
CGFloat thumbnailHeight = itemWidth * (9.0 / 16.0);
CGFloat itemHeight = thumbnailHeight + 6 + 18 + 6; // thumbnail + spacing + title (1 line) + bottom
return CGSizeMake(itemWidth, itemHeight);
}
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[collectionView deselectItemAtIndexPath:indexPath animated:YES];
AVVodItemModel *item = self.vodItems[indexPath.item];
// Convert to AVLiveStreamModel for the player
AVLiveStreamModel *stream = [[AVLiveStreamModel alloc] init];
stream.url = item.url;
SCVodVideoPlayerViewController *vc = [[SCVodVideoPlayerViewController alloc] initWithLiveStream:stream];
vc.hidesBottomBarWhenPushed = YES;
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self.navigationController pushViewController:vc animated:YES];
}
#pragma mark - Orientation Support
- (BOOL)shouldAutorotate {
return YES;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationPortrait;
}
@end

View File

@@ -0,0 +1,24 @@
//
// AVVodItemModel.h
// SellyCloudSDK_Example
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface AVVodItemModel : NSObject
@property (nonatomic, copy) NSString *url;
@property (nonatomic, copy) NSString *cover;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *type; // mp4, hls, flv, etc.
+ (instancetype)modelWithUrl:(NSString *)url
cover:(NSString *)cover
title:(NSString *)title
type:(NSString *)type;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,22 @@
//
// AVVodItemModel.m
// SellyCloudSDK_Example
//
#import "AVVodItemModel.h"
@implementation AVVodItemModel
+ (instancetype)modelWithUrl:(NSString *)url
cover:(NSString *)cover
title:(NSString *)title
type:(NSString *)type {
AVVodItemModel *model = [[AVVodItemModel alloc] init];
model.url = url;
model.cover = cover;
model.title = title;
model.type = type;
return model;
}
@end

View File

@@ -230,7 +230,7 @@
}
- (void)rtcSession:(SellyRTCSession *)session connectionStateChanged:(SellyRTCConnectState)state userId:(nullable NSString *)userId {
NSLog(@"ice.connectionStateChanged == %ld",state);
NSLog(@"ice.connectionStateChanged userId == %@ statu == %ld",userId,state);
// PiP Manager
if (state == SellyRTCConnectStateConnected && !self.pipManager) {
self.pipManager = [[SellyCallPiPManager alloc] initWithRenderView:self.remoteView];

View File

@@ -18,7 +18,7 @@
{
//
long signTime = (long)[[NSDate date] timeIntervalSince1970];
long exprTime = signTime + 60; // 10
long exprTime = signTime + 600; // 10
// payload
NSString *payload = [NSString stringWithFormat:@"%@%@%@%ld%ld",

Binary file not shown.

Binary file not shown.

Binary file not shown.