需求设计
做一个小学生教育辅导视频播放器。
参考小猿搜题视频播放器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L0GsyFSt-1675164972791)(https://tva1.sinaimg.cn/large/008vxvgGgy1h9xk4fm5xfj31sx0u0mz0.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XZ7e0Z3Z-1675164972792)(https://tva1.sinaimg.cn/large/008vxvgGgy1h9xk4fm5xfj31sx0u0mz0.jpg)]
主要功能:
- 非VIP用户免费播放开头部分;
- 截屏;
- 倍速播放;
- 进度条快进快退;
实现原理
- 公开属性
#import "WMPlayer.h"
@property (nonatomic, retain) WMPlayerModel *playerModel;
@property (nonatomic, strong) WMPlayer *wmPlayer;
- 实例化播放器
//self.wmPlayer = [[WMPlayer alloc] initWithFrame:CGRectMake(0, [WMPlayer IsiPhoneX]?34:0, self.view.frame.size.width, self.view.frame.size.width*(9.0/16))];
self.wmPlayer = [[WMPlayer alloc] initWithFrame:CGRectMake(0, 44, self.view.frame.size.width, self.view.frame.size.height-88)];
self.wmPlayer.delegate = self;
self.wmPlayer.playerModel = self.playerModel;
[self.view addSubview:self.wmPlayer];
[self.wmPlayer play];
//旋转屏幕通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onDeviceOrientationChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
- VC生命周期
#pragma mark - Life Cycle
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:NO];
self.view.frame = UIScreen.mainScreen.bounds;
self.wmPlayer.delegate = self;
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidAppear:animated];
[self.navigationController setNavigationBarHidden:NO animated:NO];
}
- (void)dealloc{
[self.wmPlayer pause];
[self.wmPlayer removeFromSuperview];
self.wmPlayer = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
NSLog(@"DetailViewController dealloc");
}
- 播放器代理
#pragma mark - WMPlayerDelegate
///播放器CloseButton
-(void)wmplayer:(WMPlayer *)wmplayer clickedCloseButton:(UIButton *)closeBtn{
if (wmplayer.isFullscreen) {
[self exitFullScreen];
}else{
if (self.presentingViewController) {
[self dismissViewControllerAnimated:YES completion:^{
}];
}else{
[self.navigationController popViewControllerAnimated:YES];
}
}
}
///全屏按钮
-(void)wmplayer:(WMPlayer *)wmplayer clickedFullScreenButton:(UIButton *)fullScreenBtn{
if (self.wmPlayer.viewState == PlayerViewStateSmall) {
[self enterFullScreen];
}
}
-(void)enterFullScreen{
if (self.wmPlayer.viewState != PlayerViewStateSmall) {
return;
}
LandscapeRightViewController *rightVC = [[LandscapeRightViewController alloc] init];
[self presentToVC:rightVC];
}
-(void)exitFullScreen{
if (self.wmPlayer.viewState!=PlayerViewStateFullScreen) {
return;
}
self.wmPlayer.isFullscreen = NO;
self.wmPlayer.viewState = PlayerViewStateAnimating;
[self dismissViewControllerAnimated:YES completion:^{
self.wmPlayer.viewState = PlayerViewStateSmall;
}];
}
- 旋转屏幕通知
#pragma mark - NSNotificationCenter
/**
* 旋转屏幕通知
*/
- (void)onDeviceOrientationChange:(NSNotification *)notification{
if (self.wmPlayer.viewState!=PlayerViewStateSmall) {
return;
}
if (self.wmPlayer.isLockScreen){
return;
}
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
UIInterfaceOrientation interfaceOrientation = (UIInterfaceOrientation)orientation;
switch (interfaceOrientation) {
case UIInterfaceOrientationPortraitUpsideDown:{
}
break;
case UIInterfaceOrientationPortrait:{
}
break;
case UIInterfaceOrientationLandscapeLeft:{
[self presentToVC:[LandscapeLeftViewController new]];
}
break;
case UIInterfaceOrientationLandscapeRight:{
[self presentToVC:[LandscapeRightViewController new]];
}
break;
default:
break;
}
}
-(void)presentToVC:(FullScreenHelperViewController *)aHelperVC{
self.wmPlayer.viewState = PlayerViewStateAnimating;
self.wmPlayer.beforeBounds = self.wmPlayer.bounds;
self.wmPlayer.beforeCenter = self.wmPlayer.center;
self.wmPlayer.parentView = self.wmPlayer.superview;
self.wmPlayer.isFullscreen = YES;
aHelperVC.wmPlayer = self.wmPlayer;
aHelperVC.modalPresentationStyle = UIModalPresentationFullScreen;
aHelperVC.transitioningDelegate = self;
[self presentViewController:aHelperVC animated:YES completion:^{
self.wmPlayer.viewState = PlayerViewStateFullScreen;
}];
}
- 调用播放器
VideoDataModel *videoModel = self.videoDataAry[indexPath.row];
WMPlayerModel *playerModel = [WMPlayerModel new];
playerModel.videoURL = [NSURL URLWithString:videoModel.video_url];
//playerModel.videoURL = [NSURL URLWithString:@"http://static.tripbe.com/videofiles/20121214/9533522808.f4v.mp4"];
//playerModel.videoURL = [NSURL URLWithString:@"http://img.zhuoqi.tech/test_h264_level30_480_360.mp4"];
playerModel.title = videoModel.nickname;
DetailViewController *detailVC = [DetailViewController new];
detailVC.playerModel = playerModel;
[self.navigationController pushViewController:detailVC animated:YES];
基本概念
一个在线视频能够播放,大致是经过了如下步骤:
HLS(Http Live Streaming)
HLS是苹果推出,实现的基于HTTP的流媒体传输协议:
优点:
1、通过m3u8索引文件可实现针对当前浏览设备的智能选择播放源
2、通过m3u8索引文件可实现添加备份索引文件,防止服务器崩溃视频播放失败
3、和http视频一样不需要太多服务器额外配置
缺点:
1、并非真正实时视频,30s左右时间差
2、需要视频处理
3、因为需要请求索引文件(ts视频文件)请求次数相对较多,对服务器负载较大
AVPlayer支持哪些视频格式
苹果设备支持音视频格式并不是就代表AVPlayer也支持那么多格式,确定AVPlayer的支持格式,我们可以查看AVKit中的一个API:
//展示当前支持的音视频格式
let asset = AVURLAsset.audiovisualTypes()
//打印asset可以得到(已经转过展示格式)
asset type (
"audio/aacp",
"video/3gpp2",
"audio/mpeg3",
"audio/mp3",
"audio/x-caf",
"audio/mpeg",
"video/quicktime",
"audio/x-mpeg3",
"video/mp4",
"audio/wav",
"video/avi",
"audio/scpls",
"audio/mp4",
"audio/x-mpg",
"video/x-m4v",
"audio/x-wav",
"audio/x-aiff",
"application/vnd.apple.mpegurl",
"video/3gpp",
"text/vtt",
"audio/x-mpeg",
"audio/wave",
"audio/x-m4r",
"audio/x-mp3",
"audio/AMR",
"audio/aiff",
"audio/3gpp2",
"audio/aac",
"audio/mpg",
"audio/mpegurl",
"audio/x-m4b",
"application/mp4",
"audio/x-m4p",
"audio/x-scpls",
"audio/x-mpegurl",
"audio/x-aac",
"audio/3gpp",
"audio/basic",
"audio/x-m4a",
"application/x-mpegurl"
)
AVPlayer支持的
视频编码格式:H.264、HEVC(iPhone7及以后设备)、MPEG-4。
视频封装格式:.mp4、.mov、.m4v、.3gp、.avi等。
如果想支持更多的视频格式,可以使用使用第三方的框架,常用的视频编码和解码框架有VLC和ffmpeg。
AVPlayerItem的控制
AVPlayerItem
作为资源管理对象,它控制着视频从创建到销毁的诸多状态。
播放状态 status
typedef NS_ENUM(NSInteger,AVPlayerItemStatus) {
AVPlayerItemStatusUnknown,//未知
AVPlayerItemStatusReadyToPlay,//准备播放
AVPlayerItemStatusFailed//播放失败
};
我们使用KVO监测playItem.status,可以获取播放状态的变化
[self.playerItem addObserver:selfforKeyPath:@"status"options:NSKeyValueObservingOptionNewcontext:nil];
在监听回调中:
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context{
if([object isKindOfClass:[AVPlayerItemclass]]) {
if([keyPath isEqualToString:@"status"]) {
switch(_playerItem.status) {
caseAVPlayerItemStatusReadyToPlay://推荐将视频播放放这里
[self play];
break;
caseAVPlayerItemStatusUnknown:
NSLog(@"AVPlayerItemStatusUnknown");
break;
caseAVPlayerItemStatusFailed:
NSLog(@"AVPlayerItemStatusFailed");
break;
default:
break;
}
}
}
}
虽然设置完播放配置我们可以直接调用[self.player play];
进行播放,但是更稳妥的方法是在回调收到AVPlayerItemStatusReadyToPlay
时进行播放。