支持更多音视频格式,添加点播测试页面
This commit is contained in:
@@ -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];
|
||||
|
||||
18
Example/SellyCloudSDK/Controllers/VOD/AVVodItemCell.h
Normal file
18
Example/SellyCloudSDK/Controllers/VOD/AVVodItemCell.h
Normal 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
|
||||
124
Example/SellyCloudSDK/Controllers/VOD/AVVodItemCell.m
Normal file
124
Example/SellyCloudSDK/Controllers/VOD/AVVodItemCell.m
Normal 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
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// AVVodListViewController.h
|
||||
// SellyCloudSDK_Example
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AVVodListViewController : UIViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
139
Example/SellyCloudSDK/Controllers/VOD/AVVodListViewController.m
Normal file
139
Example/SellyCloudSDK/Controllers/VOD/AVVodListViewController.m
Normal 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
|
||||
24
Example/SellyCloudSDK/Play/AVVodItemModel.h
Normal file
24
Example/SellyCloudSDK/Play/AVVodItemModel.h
Normal 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
|
||||
22
Example/SellyCloudSDK/Play/AVVodItemModel.m
Normal file
22
Example/SellyCloudSDK/Play/AVVodItemModel.m
Normal 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
|
||||
@@ -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];
|
||||
|
||||
@@ -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",
|
||||
|
||||
BIN
Example/SellyCloudSDK/test_flv.flv
Normal file
BIN
Example/SellyCloudSDK/test_flv.flv
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user