I'm working an application displaying "n" number of videos on full screen using AVPlayer & AVPlayerLayer classes on UICollectionView Cell with horizontal scroll. I was set paging enabled on UIScrollView. Because I need to play only current position player otherwise the "Previous & next" cell players should be in "pause" status.
I'm getting videos in url format. So initially I need to download and after that it will start play. So each and every no need to download videos from URL. So I decided to cache that downloaded video on local. So I down load and store it on local file. Then I load video from local If it exist on local file.
Issues :
(a). "Play & pause" were not working properly on their related index. Initially "0" the index was played. When I scroll to next Index at this moment current index player is should be playing, the next index player should be in "Pause" status. It's not syncing correctly.
(b). I can see the previous player video clip until the next player start to play.
(c). All viewed audio playing in background simultaneously as well as when release the player also. (I mean, I can hearing after dismiss some views or viewControllers)
(d) Need to maintain memory leak out problems.
Here below What I tried the way of source,
- (void)setVideoLoading:(NSString *)videoUrl{
NSString *urlString = videoUrl;
SHAREDATA.videoUrl = [NSURL URLWithString:urlString];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:SHAREDATA.videoUrl options:nil];
NSArray *requestedKeys = @[@"playable"];
//Tells the asset to load the values of any of the specified keys that are not already loaded.
[asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset: asset];
if (self.avPlayer)
{
/* Remove existing player item key value observers and notifications. */
[self.avPlayer removeObserver:self forKeyPath:@"status" context:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.avPlayer.currentItem];
}
self.avPlayer = [AVPlayer playerWithPlayerItem:item];
self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
if (_typeOfContentMode == UIViewContentModeScaleAspectFit) {
self.avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
}
else if (_typeOfContentMode == UIViewContentModeScaleAspectFill)
{
self.avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
}
self.avPlayerLayer.frame = CGRectMake(0, 0, _videoBackgroundView.frame.size.width, _videoBackgroundView.frame.size.height);
[_videoBackgroundView.layer addSublayer:self.avPlayerLayer];
self.avPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
self.avPlayer.muted = NO;
[self.avPlayer addObserver:self
forKeyPath:@"status"
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:nil];
});
}];
Here below the KVO method.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (object == self.avPlayer && [keyPath isEqualToString:@"status"]) {
if (self.avPlayer.status == AVPlayerStatusReadyToPlay) {
[_videoProgressView setProgress:0 animated:NO];
[self setVideoPlayEnabled];
[self setAutoUpdateTimer];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:[self.avPlayer currentItem]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.avPlayer currentItem]];
}
else if (self.avPlayer.status == AVPlayerStatusFailed ||
self.avPlayer.status == AVPlayerStatusUnknown) {
[SVProgressHUD dismiss];
if (self.avPlayer.rate>0 && !self.avPlayer.error)
{
[self.avPlayer setRate:0.0];
[self.avPlayer pause];
}
}
}
}
- (void)playerItemDidReachEnd:(NSNotification *)notification {
[SVProgressHUD dismiss];
AVPlayerItem *p = [notification object];
[p seekToTime:kCMTimeZero completionHandler:^(BOOL finished)
{
[self.avPlayer play];
}];
}
I pass url from "cellForItemAtIndexPath", I pause current player on "didEndDisplayingCell".
Then In "scrollViewDidEndDecelerating" I just checked additional global instance of my customCell class(Declared on global variable) and pause that cell current player.
But main purpose on scrollViewDidEndDecelerating, I'm getting current visible cell.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if (self.lastPlayingCell) {
[self.lastPlayingCell.avPlayer pause];
}
// Play/Pause Video
CGRect visibleRect = (CGRect){.origin = self.videoCollectionView.contentOffset, .size = self.videoCollectionView.bounds.size};
CGPoint visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
_visibleIndexPath = [self.videoCollectionView indexPathForItemAtPoint:visiblePoint];
DetailsCollectionCell *cell = (DetailsCollectionCell *)[self.videoCollectionView cellForItemAtIndexPath:_visibleIndexPath];
if (![cell isEqual: self.lastPlayingCell]) {
[self.avPlayer play];
self.lastPlayingCell = cell;//assigned to current visible cell to lastplayingcell instance.
}
}
One thing I informing here, I just display AVPlayer as a subview of UIView class. When I click on video thumb image (list of published videos info on UITableView on it's previous view)I mean UITable didselect function, I just display the UIView with moving it's "Y" position value (like a present a view controller).
So I can moving that view "Top & down" direction like in "Facebook messanger app camera access view".
So When I dismiss the view, I need to release the AVPlayer, stop audio on background of all viewed videos and remove the registered observer classes like below.
- (void)setRemoveRegisteredObserversAndPlayerRelatedElements
{
[self.avPlayer removeObserver:self forKeyPath:@"status" context:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:[self.avPlayer currentItem]];
[self.avPlayerLayer.player pause];
[self.avPlayer pause];
self.avPlayer = [AVPlayer playerWithURL:[NSURL URLWithString:@""]];
[self.avPlayerLayer removeFromSuperlayer];
self.avPlayerLayer.player = nil;
self.avPlayerLayer = nil;
self.avPlayer = nil;
}
Any Idea to solve those issues and playing video smoothly on their current index, stop the audio and handle memory leaks.
Any one share or give some suggestion to achieve this one.
cellForItemAtIndexPath
- (UICollectionViewCell *) collectionView: (UICollectionView *) collectionView cellForItemAtIndexPath: (NSIndexPath *) indexPath
{
videoDetailsCollectionCell *videoDetailCell = [collectionView dequeueReusableCellWithReuseIdentifier:kVideoDetailsCollectionCell forIndexPath:indexPath];
NSDictionary *publishedVideoObject = [self.videoDetailArray objectAtIndex:indexPath.row];
[videoDetailCell loadVideoURL:publishedVideoObject];
return videoDetailCell;
}
I have added all of the class files from ZOWVideoPlayer library file.
Latest Edit:
#pragma mark - Load video url for their current index cell
- (videoDetailsCollectionCell *)videoCellFor:(UICollectionView *)collectionView withIndex:(NSIndexPath *)indexPath
{
videoDetailsCollectionCell * videoCell = [collectionView dequeueReusableCellWithReuseIdentifier:kCustomVideoCell forIndexPath:indexPath];
//[videoCell.videoView.videoPlayer resume];
return videoCell;
}
**In CellForItemAtIndexPath method **
NSDictionary *publicationObject = [videoDetailArray objectAtIndex:indexPath.row];
videoDetailsCollectionCell * cell = [self videoCellFor:collectionView withIndex:indexPath];
cell.mediaUrl = [NSString replaceEmptyStringInsteadOfNull:[NSString stringWithFormat:@"%@",publicationObject.video]];
return cell;
Then In scrollViewDidScroll I was Pause last played videos as you mentioned before.
Then In scrollViewDidEndDecelerating I followed like below answer part except using UICollectionView Instead of IndexedCollectionView class for retrieving page count info.
And finally I was stopped all played videos like below,
- (void)setPauseTheLastViewedPlayerAudio {
// Pause last played videos
if (self.lastPlayedVideo) {
[self.lastPlayedVideo mute];
[self.lastPlayedVideo stopVideoPlay];
}
_videoDetailArray = nil;
[_videoCollectionView reloadData];
}
Because I should dismiss the player view moving with finger from Top to down direction using UIPanGestureRecognizer.