Avplayer Shows Black Screen When Playing Multiple Videos in Swift3 iOS

iOS 10 - AVPlayer shows black screen when playing video

Thanks for your replies! I just found that, for some reason in iOS 10 the viewDidLayoutSubviews did not get called as it was in iOS 9. Actually I was setting the player layer's frame in the viewDidLayoutSubviews method. Since it didn't get called, I was not able to see the player on screen. What I did was, set the player layer's frame in the loadPlayer method above.And it works fine. Thanks! Hope this helps someone.

EDIT

This is the line of code that I moved from viewDidLayoutSubviews to my loadPlayer method and it worked:

self.playerLayer.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);

AVPlayer on Swift app shows a black screen but plays audio when playing video

The problem is with the video file itself / the encoding.

Deceptively, the Quicktime ".mov" format is not a video codec, but rather audio/video container format that can contain video and audio (and some other things) in any number of compression codecs (h.264, mpeg2, ProRes, MJPEG, AAC, mp3, etc.) … Your files don't work because they include video compressed with a codec which iOS does not support.

From @alexkent in this SO answer on another post.

I don't think a .mov is necessarily not going to work for you, but you'd have to make sure it was encoded in a way that is iOS-compatible. So as you mentioned in the comments, using .mp4 ensures the video is encoded in an iOS-compatible fashion.

Multiple videos with AVPlayer

So the project is now live in the App Store and it is time to come back to this thread and share my findings and reveal what I ended up doing.

What did not work

The first option where I used on big AVComposition with all the videos in it was not good enough since there was no way to jump to a specific time in the composition without having a small scrubbing glitch. Further I had a problem with pausing the video exactly between two videos in the composition since the API could not provide frame guarantee for pausing.

The third with having two AVPlayers and let them take turns worked great in practice. Exspecially on iPad 4 or iPhone 5. Devices with lower amount of RAM was a problem though since having several videos in memory at the same time consumed too much memory. Especially since I had to deal with videos of very high resolution.

What I ended up doing

Well, left was option number 2. Creating an AVPlayerItem for a video when needed and feeding it to the AVPlayer. The good thing about this solution was the memory consumption. By lazy creating the AVPlayerItems and throwing them away the moment they were not longer needed in could keep memory consumption to a minimum, which was very important in order to support older devices with limited RAM. The problem with this solution was that when going from one video to the next there a blank screen for at quick moment while the next video was loaded into memory. My idea of fixing this was to put an image behind the AVPlayer that would show when the player was buffering. I knew I needed images that we exactly pixel to pixel perfect with the video, so I captured images that were exact copies of the last and first frame of the videos. This solution worked great in practice.

The problem with this solution

I had the issue though that the position of the image inside the UIImageView was not the same as the position of the video inside the AVPlayer if the video/image was not its native size or a module 4 scaling of that. Said other words, I had a problem with how half pixels were handled with a UIImageView and a AVPlayer. It did not seem to be the same way.

How I fixed it

I tried a lot of stuff since I my application was using the videos in interactive way where shown in different sizes. I tried changed the magnificationfilter and minificationFilter of AVPlayerLayer and CALayer to use the same algorithm but did not really change anything. In the end I ended up creating an iPad app that automatically could take screenshots of the videos in all the sizes I needed and then use the right image when the video was scaled to a certain size. This gave images that were pixel perfect in all of the sizes I was showing a specific video. Not a perfect toolchain, but the result was perfect.

Final reflection

The main reason why this position problem was very visible for me (and therefore very important to solve), was because the video content my app is playing is drawn animations, where a lot the content is in a fixed position and only a part of the picture is moving. If all the content is moved just one pixel it gives a very visible and ugly glitch. At WWDC this year I discussed this problem with an Apple engineer that is an expert in AVFoundation. When I introduced the problem to him his suggestion basically was to go with option 3, but I explained to him that that was not possible because memory consumption and that I already tried that solution out. In that light he said that I choose the right solution and asked me to file a bug report for the UIImage/AVPlayer positioning when video is scaled.

iOS Play video black screen?

Create property for MPMoviePlayerController, because you retain view after adding it as subview, but not retain controller.

@property (strong, nonatomic) MPMoviePlayerController *player;

...

@synthesize player = _player;

...

- (IBAction)playVideo:(id)sender
{
NSURL *videoUrl = [[DataStore singletonInstance] getVideoUrl:self withUuid:self.eventDetailVC.event.uuid];
if ([videoUrl checkResourceIsReachableAndReturnError:nil] == NO)
{
NSLog(@"Video doesn't not exist.");
return;
}
self.player = [[MPMoviePlayerController alloc] initWithContentURL:videoUrl];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(moviePlayBackDidFinish:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
[previewView addSubview:_player.view];
_player.view.frame = previewView.bounds;
_player.controlStyle = MPMovieControlStyleDefault;
[_player play];
}

- (void)moviePlayBackDidFinish:(NSNotification*)notification
{
NSLog(@"moviePlayBackDidFinish: called");
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
// Checking for errors
NSDictionary *notiUserInfo = [notification userInfo];
if (notiUserInfo != nil)
{
NSError *errorInfo = [notiUserInfo objectForKey:@"error"];
if ([[errorInfo domain] isEqualToString:@"MediaPlayerErrorDomain"])
{
UIAlertView *notice = [[UIAlertView alloc] initWithTitle:@"Error"
message:[errorInfo localizedDescription]
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[notice show];
return;
}
}
// Remove from view
[_player.view removeFromSuperview];
[_player stop];
self.player = nil;
}

Display multiple videos using AVPlayer and AVPlayerLayer on UICollectionView

I was struggling with the same issue some time ago.
My approach to solving this issue is little different than yours.

let me explain in steps

  • I have declared a global instance of the player in the view controller that hold the current video cell player instance.

    @property (nonatomic, strong) InstagramVideoView *sharedVideoPlayer; // Share video player instance
  • The video player objects are created for each cell & they also hold the video URL for each video player.

    - (CommonVideoCollectionViewCell *)videoCellFor:(UICollectionView *)collectionView withIndex:(NSIndexPath *)indexPath {
    CommonVideoCollectionViewCell *videoCell = [collectionView dequeueReusableCellWithReuseIdentifier:VideoCollectionViewCellIdentifier forIndexPath:indexPath];
    return videoCell;
    }
    -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    {
    CommonVideoCollectionViewCell *cell = [self videoCellFor:collectionView withIndex:indexPath];
    cell.mediaUrl = tempMedia.media;
    return cell;
    }
  • Each time user tries to scroll the collection cell pause the video from the global instance of the video player. this solves the flickering issue for the cell.

    // Pause last played videos
    if (self.sharedVideoPlayer) {
    [self.sharedVideoPlayer pause];
    [self.sharedVideoPlayer mute];
    }

  • Last step is to find the current video cell in the center of horizontal collection view & play the video from URL we saved in current video cell.

    -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    if ([scrollView isKindOfClass:[UITableView class]]) {
    return;
    }

    IndexedCollectionView *pageContentCollectionView = (IndexedCollectionView *)scrollView;
    CGPoint centerPoint = CGPointMake(pageContentCollectionView.center.x + pageContentCollectionView.contentOffset.x,
    pageContentCollectionView.center.y + pageContentCollectionView.contentOffset.y);

    NSIndexPath *centerCellIndexPath = [pageContentCollectionView indexPathForItemAtPoint:centerPoint];
    NSArray *visibleCells = [pageContentCollectionView visibleCells];
    // Get cell for index & pause video playback
    UICollectionViewCell *cell = [pageContentCollectionView cellForItemAtIndexPath:centerCellIndexPath];

    for (UICollectionViewCell *tempCell in visibleCells) {
    if ([tempCell isKindOfClass:[CommonVideoCollectionViewCell class]]) {
    CommonVideoCollectionViewCell *videoCell = (CommonVideoCollectionViewCell *)tempCell;
    InstagramVideoView *videoPlayer = [videoCell videoView];
    // Pause & mute all the video player instance
    [videoCell.videoView setHidden:YES];
    [videoCell.placeholderImageView setHidden:NO];
    [videoPlayer pause];
    [videoPlayer mute];
    // Check if the central cell is present
    if (tempCell == cell) {
    self.sharedVideoPlayer = videoPlayer;// Save played video player instance
    [self.sharedVideoPlayer playVideoWithURL:[NSURL URLWithString:[(CommonVideoCollectionViewCell *)tempCell mediaUrl]]];
    [videoCell.videoView setHidden:NO];
    [videoCell.placeholderImageView setHidden:YES];
    [videoPlayer resume]; // resume video playback
    [videoPlayer mute];
    }
    }
    }
    }

    CommonVideoCollectionViewCell

    #import<UIKit/UIKit.h>
    #import "InstagramVideoView.h"
    @interface CommonVideoCollectionViewCell : UICollectionViewCell
    @property (weak, nonatomic) IBOutlet InstagramVideoView *videoView;
    @property (weak, nonatomic) IBOutlet UIImageView *placeholderImageView;
    // Set media url
    @property (strong, nonatomic) NSString *mediaUrl;
    @end

End result -
enter image description here

Note - For smooth video playing, the player cache the video in temp memory. let me know if you have issue in any of the following steps.



Related Topics



Leave a reply



Submit