簡概:
- 本次文章分別講述兩種視頻列表全屏滑動切換播放的視方式。
- Demo 中使用的播放器原本是 Bilibili/ijkplayer 后續為了方便集成,改為了ksvc/KSYMediaPlayer_iOS
- 如果你有問題,或者對下述文字有任何意見與建議,除了在文章最后留言,還可以在微博@茅坑里的石頭-_-上給我留言,或者聯系我的郵箱hu-yangyang@qq.com,以及訪問我的Github。
- 文章某尾會給到Demo(請在真機上調試)。
Demo滑動效果
解決方案一
- UIScrollView + KSYMediaPlayer 這里是用UIScrollView 和 三張背景圖片做無限輪播,播放器鋪在上方。
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
self.contentSize = CGSizeMake(0, frame.size.height * 3);
self.contentOffset = CGPointMake(0, frame.size.height);
self.pagingEnabled = YES;
self.opaque = YES;
self.backgroundColor = [UIColor clearColor];
self.showsHorizontalScrollIndicator = NO;
self.showsVerticalScrollIndicator = NO;
self.delegate = self;
self.upperImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
self.middleImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, frame.size.height, frame.size.width, frame.size.height)];
self.downImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, frame.size.height*2, frame.size.width, frame.size.height)];
[self addSubview:self.upperImageView];
[self addSubview:self.middleImageView];
[self addSubview:self.downImageView];
}
return self;
}
-(void) initPlayer
{
KSYMoviePlayerController *_player;
_player = [[KSYMoviePlayerController alloc] initWithContentURL: [NSURL URLWithString:self.live.VideoAddress]];
_player.view.backgroundColor = [UIColor clearColor];
[self.player setBufferSizeMax:1];
_player.view.autoresizesSubviews = true;
[_player.view setFrame: self.view.bounds]; // player's frame must match parent's
_player.view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
_player.shouldAutoplay = TRUE;
_player.shouldLoop = YES;
_player.scalingMode = MPMovieScalingModeAspectFit;
[_player prepareToPlay];
self.player = _player;
self.player.view.frame = CGRectMake(0, SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
self.player.shouldAutoplay = YES;
self.view.autoresizesSubviews = YES;
[self.playerScrollView addSubview:self.player.view];
[[NSNotificationCenter defaultCenter]addObserver:self
selector:@selector(handlePlayerNotify:)
name:(MPMoviePlayerFirstVideoFrameRenderedNotification)
object:nil];
}
- 當scrollview 滑動時,當整頁翻過時,則將scrollview迅速復位,并完成圖片替換,以及上下圖的預加載。
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self switchPlayer:scrollView];
}
- (void)switchPlayer:(UIScrollView*)scrollView
{
CGFloat offset = scrollView.contentOffset.y;
if (self.lives.count) {
if (offset >= 2*self.frame.size.height)
{
// slides to the down player
scrollView.contentOffset = CGPointMake(0, self.frame.size.height);
_currentIndex++;
self.upperImageView.image = self.middleImageView.image;
self.middleImageView.image = self.downImageView.image;
if (_currentIndex == self.lives.count - 1)
{
_downLive = [self.lives firstObject];
} else if (_currentIndex == self.lives.count)
{
_downLive = self.lives[1];
_currentIndex = 0;
} else
{
_downLive = self.lives[_currentIndex+1];
}
[self prepareForImageView:self.downImageView withLive:_downLive];
if (_previousIndex == _currentIndex) {
return;
}
if ([self.playerDelegate respondsToSelector:@selector(playerScrollView:currentPlayerIndex:)]) {
[self.playerDelegate playerScrollView:self currentPlayerIndex:_currentIndex];
_previousIndex = _currentIndex;
NSLog(@"current index in delegate: %ld -%s",_currentIndex,__FUNCTION__);
}
}
else if (offset <= 0)
{
// slides to the upper player
scrollView.contentOffset = CGPointMake(0, self.frame.size.height);
_currentIndex--;
self.downImageView.image = self.middleImageView.image;
self.middleImageView.image = self.upperImageView.image;
if (_currentIndex == 0)
{
_upperLive = [self.lives lastObject];
} else if (_currentIndex == -1)
{
_upperLive = self.lives[self.lives.count - 2];
_currentIndex = self.lives.count-1;
} else
{
_upperLive = self.lives[_currentIndex - 1];
}
[self prepareForImageView:self.upperImageView withLive:_upperLive];
if (_previousIndex == _currentIndex) {
return;
}
if ([self.playerDelegate respondsToSelector:@selector(playerScrollView:currentPlayerIndex:)]) {
[self.playerDelegate playerScrollView:self currentPlayerIndex:_currentIndex];
_previousIndex = _currentIndex;
NSLog(@"current index in delegate: %ld -%s",_currentIndex,__FUNCTION__);
}
}
}
}
- (void) prepareForImageView: (UIImageView *)imageView withLive:(VideoInfoModel *)live
{
[imageView sd_setImageWithURL:[NSURL URLWithString:live.coverImageAddress]];
}
- 上述步驟完成后通知播放器重新加載視頻
#pragma mark DYPlayerScrollViewDelegate
- (void)playerScrollView:(DYPlayerScrollView *)playerScrollView currentPlayerIndex:(NSInteger)index
{
NSLog(@"current index from delegate:%ld %s",(long)index,__FUNCTION__);
if (self.index == index) {
return;
} else {
[self reloadPlayerWithLive:self.dataList[index]];
self.index = index;
}
}
- (void)reloadPlayerWithLive:(VideoInfoModel *)live
{
[self.player reset:false];
[self.player.view setHidden:true];
[self.player setUrl:[NSURL URLWithString:live.VideoAddress]];
[self.player setShouldAutoplay:YES];
[self.player setBufferSizeMax:1];
[self.player setShouldLoop:YES];
self.player.view.backgroundColor = [UIColor clearColor];
[self.player prepareToPlay];
}
- 方案一總結:demo 實現效果會發現,每次視頻滑動切換的時候,重新加載視頻直到視頻首幀出現都需要時間,這樣的播放體驗并不是很流暢。我們不得不思考是否有更優的實現方式。
解決方案二
在方案一種 我們成功的實現的視頻列表滑動切換這一效果,但是視頻每次重新加載直到首幀出現這個過程必定是耗時的(首幀加載速度可以通過cdn 優化,以及播放器優化),那我們是否可以嘗試像方案一中圖片處理的方式一樣。有兩個背景播放器去緩沖對應視頻list 的上一個視頻以及下一個視頻。順著這樣的思路,我們實現了像如開篇效果演示的gif 的流暢體驗。(因為方案二是基于方案一的,所以以下就簡敘下思路。)
與方案一不同的是這里我們是用的是三個播放器,兩個用作背景緩沖。
@property (nonatomic, strong) KSYMoviePlayerController *upPerPlayer, *middlePlayer, *downPlayer;
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
// 在DYPlayerScrollView init方法中分別初始化 upperImageView middleImageView downImageView
以及 upPerPlayer middlePlayer downPlayer
}
}
- 當scrollview 滑動時,當整頁翻過時,則將scrollview迅速復位,除了完成圖片的操作,這里還要完成播放器的操作。
- (void)switchPlayer:(UIScrollView*)scrollView
{
CGFloat offset = scrollView.contentOffset.y;
if (self.lives.count) {
if (offset >= 2*self.frame.size.height)
{
// slides to the down player
scrollView.contentOffset = CGPointMake(0, self.frame.size.height);
_currentIndex++;
self.upperImageView.image = self.middleImageView.image;
self.middleImageView.image = self.downImageView.image;
if (self.upPerPlayer.view.frame.origin.y == 0) {
self.upPerPlayer.view.frame = CGRectMake(0, SCREEN_HEIGHT * 2, SCREEN_WIDTH, SCREEN_HEIGHT);
}else{
self.upPerPlayer.view.frame = CGRectMake(0, self.upPerPlayer.view.frame.origin.y - SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
}
if (self.middlePlayer.view.frame.origin.y == 0) {
self.middlePlayer.view.frame = CGRectMake(0, SCREEN_HEIGHT * 2, SCREEN_WIDTH, SCREEN_HEIGHT);
}else{
self.middlePlayer.view.frame = CGRectMake(0, self.middlePlayer.view.frame.origin.y - SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
}
if (_currentIndex == self.lives.count - 1)
{
_downLive = [self.lives firstObject];
} else if (_currentIndex == self.lives.count)
{
_downLive = self.lives[1];
_currentIndex = 0;
} else
{
_downLive = self.lives[_currentIndex+1];
}
[self prepareForImageView:self.downImageView withLive:_downLive];
if (self.downPlayer.view.frame.origin.y == 0) {
self.downPlayer.view.frame = CGRectMake(0, SCREEN_HEIGHT * 2, SCREEN_WIDTH, SCREEN_HEIGHT);
}else{
self.downPlayer.view.frame = CGRectMake(0, self.downPlayer.view.frame.origin.y - SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
}
if (self.upPerPlayer.view.frame.origin.y == SCREEN_HEIGHT * 2) {
[self prepareForVideo:self.upPerPlayer withLive:_downLive];
}
if (self.middlePlayer.view.frame.origin.y == SCREEN_HEIGHT * 2) {
[self prepareForVideo:self.middlePlayer withLive:_downLive];
}
if (self.downPlayer.view.frame.origin.y == SCREEN_HEIGHT * 2) {
[self prepareForVideo:self.downPlayer withLive:_downLive];
}
if (_previousIndex == _currentIndex) {
return;
}
if ([self.playerDelegate respondsToSelector:@selector(playerScrollView:currentPlayerIndex:)]) {
[self.playerDelegate playerScrollView:self currentPlayerIndex:_currentIndex];
_previousIndex = _currentIndex;
NSLog(@"current index in delegate: %ld -%s",_currentIndex,__FUNCTION__);
}
}
else if (offset <= 0)
{
// slides to the upper player
scrollView.contentOffset = CGPointMake(0, self.frame.size.height);
_currentIndex--;
self.downImageView.image = self.middleImageView.image;
if (self.downPlayer.view.frame.origin.y == 2 * SCREEN_HEIGHT) {
self.downPlayer.view.frame = CGRectMake(0, SCREEN_HEIGHT * 0, SCREEN_WIDTH, SCREEN_HEIGHT);
}else{
self.downPlayer.view.frame = CGRectMake(0, self.downPlayer.view.frame.origin.y + SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
}
self.middleImageView.image = self.upperImageView.image;
if (self.middlePlayer.view.frame.origin.y == 2 * SCREEN_HEIGHT) {
self.middlePlayer.view.frame = CGRectMake(0, SCREEN_HEIGHT * 0, SCREEN_WIDTH, SCREEN_HEIGHT);
}else{
self.middlePlayer.view.frame = CGRectMake(0, self.middlePlayer.view.frame.origin.y + SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
}
if (_currentIndex == 0)
{
_upperLive = [self.lives lastObject];
} else if (_currentIndex == -1)
{
_upperLive = self.lives[self.lives.count - 2];
_currentIndex = self.lives.count-1;
} else
{
_upperLive = self.lives[_currentIndex - 1];
}
[self prepareForImageView:self.upperImageView withLive:_upperLive];
if (self.upPerPlayer.view.frame.origin.y == 2 * SCREEN_HEIGHT) {
self.upPerPlayer.view.frame = CGRectMake(0, SCREEN_HEIGHT * 0, SCREEN_WIDTH, SCREEN_HEIGHT);
}else{
self.upPerPlayer.view.frame = CGRectMake(0, self.upPerPlayer.view.frame.origin.y + SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
}
if (self.upPerPlayer.view.frame.origin.y == 0 ) {
[self prepareForVideo:self.upPerPlayer withLive:_upperLive];
}
if (self.middlePlayer.view.frame.origin.y == 0 ) {
[self prepareForVideo:self.middlePlayer withLive:_upperLive];
}
if (self.downPlayer.view.frame.origin.y == 0 ) {
[self prepareForVideo:self.downPlayer withLive:_upperLive];
}
if (_previousIndex == _currentIndex) {
return;
}
if ([self.playerDelegate respondsToSelector:@selector(playerScrollView:currentPlayerIndex:)]) {
[self.playerDelegate playerScrollView:self currentPlayerIndex:_currentIndex];
_previousIndex = _currentIndex;
NSLog(@"current index in delegate: %ld -%s",_currentIndex,__FUNCTION__);
}
}
}
}
- 監聽視頻準備播放,以及第一幀出現通知,做合適的邏輯處理。
[[NSNotificationCenter defaultCenter]addObserver:self
selector:@selector(handlePlayerNotify:)
name:(MPMoviePlayerFirstVideoFrameRenderedNotification)
object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self
selector:@selector(handlePlayerPreparedToPlayNotify:)
name:(MPMediaPlaybackIsPreparedToPlayDidChangeNotification)
object:nil];
- 方案二總結:從demo的運行效果來看,流暢不卡頓。這應該是現在主流的視頻列表滑動播放比較好的實現方式了