Creating a Play/Pause button for AVPlayer in another View Controller - ios

I am creating a Play/Pause button for an AVPlayer, but the actual player is in a UIView and the button is in the UIViewController where I am creating an instance of the player. The video plays if I simply put [self.player play]; in the code for the player, but when I try to control playing and pausing from the button it doesn't.
Here is the code:
-(IBAction)playPauseButtonClicked:(id)sender
{
PlayerView *player = [[PlayerView alloc] init];
if (self.playing == YES)
{
[player.player pause];
self.playing = NO;
NSLog(#"Pause");
}
else
{
[player.player play];
NSLog(#"Play");
self.playing = YES;
}
// UIButton *theButton = (UIButton *)sender;
//
// self.playing = !self.playing;
// [theButton setImage:self.playing ? #"pauseImage.png":#"playImage.png" forState:UIControlStateNormal];
}

You have to study well about Object Oriented Concepts and Object communication.
You are creating the PlayerView class object inside your playPauseButtonClicked again.
Instead, you have to pass the existing PlayerView object into the class that contains playPauseButtonClicked method.
Example:
Class A:
ClassB *classB = [[ClassB alloc] init];
classB.player = self.player;
Class B:
.h file
#property (nonatomic,weak) PlayerView *player;
.m file
-(IBAction)playPauseButtonClicked:(id)sender {
if (self.player.playing == YES)
{
[self.player pause];
self.player.playing = NO;
NSLog(#"Pause");
}
else
{
[self.player play];
NSLog(#"Play");
self.player.playing = YES;
}
}

First You pass your PlayerView Object to next View Controller
And In FirstViewController.h and SecondViewController.h file declare object of PlayerView
Such As
#property (nonatomic,retain) PlayerView *player;
And when navigate from first view controller to second view controller inlude this code:
SecondViewController *obj = [[SecondViewController alloc]init];
obj.player = self.player ;
Now In -(IBAction)playPauseButtonClicked:(id)sender Method of second view controller
Remove The Line from Code : PlayerView *player = [[PlayerView alloc] init];
because a above line create new object of Player View instead of object of previous view controller AVPlayer Object write this updated method
-(IBAction)playPauseButtonClicked:(id)sender {
if (self.playing == YES)
{
[player.player pause];
self.playing = NO;
NSLog(#"Pause");
}
else
{
[player.player play];
NSLog(#"Play");
self.playing = YES;
}
}

Related

I could not get different videoplayers for different cells

I want to play video on many of the cells of my table view.
My movie player object is not new for every cell.
But I can not play the different video for different cells, same video is played for all cells. It hink because of reusing property of the cell, player var is also reused.
I put a condition player==nil then alloc condition in cell.m file, but it is not working.
Only one time I can alloc the cell, afterwords It does not go in condiotns.
here player also reused with cell. That Id od ot want I want to be hold that player with the cell.
But things does not come, which condition should I put or which property I shopuld use in cell.m file.
cell.m file coding
#property (strong, nonatomic) MPMoviePlayerController *player;
NSURL *videoURL=[NSURL URLWithString:[cell.searchDict objectForKey:#"image_path1"]];
// if(player!=nil && [[player.contentURL absoluteString] isEqualToString:videoURL.absoluteString]){
// [player.view removeFromSuperview];
// player=nil;
//
// }
// if(cell.player==nil){
[cell.player.view removeFromSuperview];
cell.player=nil;
cell.player = [[MPMoviePlayerController alloc] initWithContentURL:videoURL];
cell.player.view.frame =CGRectMake(0, 0, SCREEN_WIDTH-14, 407.0);
cell.player.controlStyle = MPMovieControlStyleEmbedded;
cell.player.scalingMode = MPMovieScalingModeFill;
cell.player.allowsAirPlay = NO;
cell.player.view.backgroundColor = [UIColor clearColor];
cell.player.view.userInteractionEnabled = NO;
cell.player.shouldAutoplay = YES;
// self.player.
cell.player.view.tag=indexPath.row;
[cell.viewForImage insertSubview:cell.player.view aboveSubview:cell.card_showimg];
// [[cell.player.view layer] setBorderColor:[UIColor redColor].CGColor];
// [[cell.player.view layer] setBorderWidth:3.0f];
// }
cell._loadNotification = MPMoviePlayerLoadStateDidChangeNotification;
if ([cell.player respondsToSelector:#selector(readyForDisplay)]) {
// iOS 6, listen for this notification instead
cell._loadNotification = MPMoviePlayerReadyForDisplayDidChangeNotification;
}
// tell us when the video has finished playing
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(splashPlaybackStateDidChange:)
name:MPMoviePlayerPlaybackStateDidChangeNotification
object:cell.player];
// cell.player.view.hidden = FALSE;
// cell.backgroundImageView.hidden = FALSE;
// [_player setContentURL:url];
[cell.player prepareToPlay];
[cell.player play];
}
else{
[cell.player.view removeFromSuperview];
cell.player=nil;
}
}
else{
[cell.player.view removeFromSuperview];
cell.player=nil;
}
I am using this coding for reusing cells.
ExploreVCCell *cell = [tableView1 dequeueReusableCellWithIdentifier:#"OfferCell"];

How to toggle a sound on/off

i created a sound in DetailViewController,
soundFileURL = [[NSBundle mainBundle] URLForResource:#"click" withExtension:#"wav"];
s1Player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFileURL error:nil];
s1Player.delegate = self;
s1Player.volume = 2;
[s1Player play];
i want to control the above sound in my ViewController. i created a button in ViewController, and toggle it (sound on / off).
i tried,
DetailViewController.m
ViewController *viewController = [[ViewController alloc] init];
if(viewController.stopSound) {
[s1Player stop];
s1Player.volume = 0;
}
else {
[s1Player play];
s1Player.volume = 2;
}
ViewController.h
#property BOOL stopSound;
in ViewController.m
- (void) setSoundAction {
if(_stopSound){
_stopSound = NO;
}
else{
_stopSound = YES;
}
}
If the above code is not understandable or not cleared please suggest me how to toggle a button i.e, sound on and sound off on DetailViewController. Because Sound placed on DetailViewController.
What you want is just toggle button, then how about this?
-(void)viewDidLoad
{
[super viewDidLoad];
UIButton *toggleButton = [UIButton buttonWithType:UIButtonTypeCustom];
toggleButton.frame = CGRectMake(10.0, 10.0, 40.0, 40.0);
[toggleButton setTitle:#"Toggle" forState:UIControlStateNormal];
[toggleButton addTarget:self action:#selector(toggleSound:) forControlEvents:UIControlEventTouchUpInside]; //execute the method when touched
[self.view addSubview:toggleButton];
}
- (void)toggleSound:(UIButton *)btn
{
static BOOL muted; //remember the current status of AVAudioPlayer object.
muted = !muted; //toggle!!
if(muted) {
s1player.volume = 0.0;
} else {
s2player.volume = 1.0; //max value for volume is 1.0
}
}

How to set UIButton tap sound in whole app?

Is there any way to set specific audio file as tap sound in UIButtons across whole iOS app without writing code in individual view?
You don't want to add any code in subclass . so you can subclass UIApplication like :
#interface PlaySound : UIApplication
#end
#implementation PlaySound
- (BOOL)sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event{
if ([sender isKindOfClass:[UIButton class]]) {
//here, play sound
}
return [super sendAction:action to:target from:sender forEvent:event];
}
#end
and register your own application in main.m like:
int main(int argc, char *argv[])
{
#autoreleasepool {
NSString *appClass = #"PlaySound";
int retVal = UIApplicationMain(argc, argv, appClass, NSStringFromClass([AppDelegate class]));
return retVal;
}
}
then whenever people touches an button control , your custom application will receive this message and play sound. you don't have to write any code in any individual view.
You can achieve this by writing code once in AppDelegate file
In AppDelegate.m write a class method, like given below
+(void)playAlarmSound
{
NSString *sound_file;
if ((sound_file = [[NSBundle mainBundle] pathForResource:#"Output" ofType:#"aif"])){
NSURL *url = [[NSURL alloc] initFileURLWithPath:sound_file];
if (audioPlayer)
{
[audioPlayer release];
audioPlayer= nil;
}
AVAudioPlayer *audioPlayer = [[[AVAudioPlayer alloc] initWithContentsOfURL:url error:NULL]autorelease];
audioPlayer.delegate = self;
[url release];
audioPlayer.numberOfLoops=0;
[audioPlayer prepareToPlay];
[audioPlayer play];
}
}
In AppDelegate.h
+(void)playAlarmSound;
Now in your class where you want to call the above method write the below line
[AppDelegate playAlarmSound];
Note:- Import AVFoundation framework
I would say, create an 'Observer' class that plays the sound for all buttons are connected to. check this example.
creating a category is the way to go. answered by tiguero
.h:
#import <UIKit/UIKit.h>
#class SCLSoundEffect;
typedef enum {
SCLCLICKSOUND = 0,
SCLOTHERSOUND,
}
SCLSoundCategory;
#interface UIButton (soundEffect)
#property (nonatomic, strong) SCLSoundEffect *buttonSoundEffect;
+ (id) buttonWithType:(UIButtonType)buttonType andSound: (SCLSoundCategory)soundCategory;
- (void) playSound;
#end
.m:
#import "UIButton+soundEffect.h"
#import <objc/runtime.h>
#import "SCLSoundEffect.h"
static char const * const kButtonSoundEffectKey = "buttonSoundEffect";
#implementation UIButton (soundEffect)
#dynamic buttonSoundEffect;
+ (id) buttonWithType:(UIButtonType)buttonType andSound:(SCLSoundCategory) soundCategory;
{
UIButton *newButton = [UIButton buttonWithType:buttonType];
NSString *stringToUse = nil;
switch (soundCategory) {
case SCLCLICKSOUND:
stringToUse = #"button_sound.wav";
break;
case SCLOTHERSOUND:
assert(0); // To be defined
default:
break;
}
[newButton setButtonSoundEffect: [[SCLSoundEffect alloc] initWithSoundNamed:stringToUse]];
[newButton addTarget:newButton action:#selector(playSound) forControlEvents:UIControlEventTouchDown];
return newButton;
}
- (void) playSound
{
[self.buttonSoundEffect play];
}
- (SCLSoundEffect *)buttonSoundEffect {
return objc_getAssociatedObject(self, kButtonSoundEffectKey);
}
- (void)setButtonSoundEffect:(SCLSoundEffect *)buttonSoundEffect{
objc_setAssociatedObject(self, kButtonSoundEffectKey, buttonSoundEffect, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void) dealloc
{
[self setButtonSoundEffect:nil];
}
Now each time I create a button that play some sound I just need to use the following method:
UIButton *mySoundButton = [UIButton buttonWithType:UIButtonTypeCustom andSound:SCLCLICKSOUND];

iPhone development mpmovieplayer crashing

I am working on an app that will let me play different videos on the iPad remotely with an iPhone. I have been following along with apples example for a video player but I've been having some troubles. The videos play just fine and I can get it to play from a variety of videos but switching between them a few times it will crash and i get this in the debugger:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An AVPlayerItem cannot be associated with more than one instance of AVPlayer'
*** First throw call stack:
(0x380da8bf 0x37c261e5 0x30acbcb5 0x30abc1f7 0x30ac3bf3 0x30c93d55 0x30c95f7b 0x380ad2dd 0x380304dd 0x380303a5 0x37e07fcd 0x31bb0743 0x25e5 0x257c)
This is the code I am using to create the player:
MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentOfURL:movieURL];
if (player) {
[self setMoviePlayerController:player];
[self installMovieNotificationObservers];
[player setContentURL:movieURL];
[player setMovieSourceType:sourceType];
[self applyUserSettingsToMoviePlayer];
[self.view addSubview:self.backgroundView];
[player.view setFrame:self.view.bounds];
[player.view setBackgroundColor = [UIColor blackColor];
[self.view addSubview:player.view];
}
And when the current movie is stopped I use:
[[self moviePlayerController] stop];
MPMoviePlayerController *player = [self moviePlayerController];
[player.view removeFromSuperview];
[self removeMovieNotificationHandlers];
[self setMoviePlayerController:nil];
Edit:
So Ive now discovered it happens every time i try and switch a video for the 11th time. weird! I'm practically pulling my hair out.
What fixed this problem for me was stopping the MPMoviePlayerController before doing the setContentURL.
MPMoviePlayerController *streamPlayer;
[streamPlayer stop];
[streamPlayer setContentURL:[NSURL URLWithString:selectedStation]];
In the implementation you have above, ARC doesn't know that the MPMoviePlayerController is finished and needs to be released.
Define MPMoviePlayerController in your .h file and make it accessible via a #property (and #synthesize).
#property (strong, nonatomic) MPMoviePlayerController * moviePlayerController;
Then take the result of your alloc & init and assign it to that. I.E.
self.moviePlayerController = [[MPMoviePlayerController alloc] initWithContentOfURL:movieURL];
you should just keep the moviePlayerController and if you want to play another video, just use
[self.moviePlayerController setContentURL:movieURL];
then in your notification callback:
- (void) moviePlayBackDidFinish:(NSNotification*)notification
{
self.moviePlayer = nil;
[self initanothermovieplayerandplay];
}
and please do not remove the notification handler from notification center, only do this in dealloc method of your VC.
now let's add some fade when the movie play is done:
- (void) moviePlayBackDidFinish:(NSNotification*)notification
{
[UIView animateWithDuration:1
delay: 0.0
options: UIViewAnimationOptionCurveEaseIn
animations:^{
// one second to fade out the view
self.moviePlayer.view.alpha = 0.0;
}
completion:^(BOOL finished){
self.moviePlayer = nil;
[self initanothermovieplayerandplay];
}
}
I had exactly the same problem.
Nothing was wrong with my and i guess with your code :)
Just a broken video file was mine problem.
Changing *.mov type to m4a for example fixed it. Maybe one or more of the files you play are corrupted?
Try to find out which files lead to crash and than if u can try to quickly forward backward the play position of one of them while playing - this should lead to crash in few tries.
This is how i found the bad files. By the way all my bad files were movies .mov made with Snapz Pro X :)
Not sure if it is the case here, but we had a lot of problems, because the MPMoviePlayer is a singleton somewhere under the hood.
What we did is, that we implemented our own MoviePlayer wrapper which can be used from UIView (actually we have exactly one subclass of UIView MoviePlayerView to show movies) and assures that only one instance of MPMoviePlayerController exists. The code goes like this (it contains some special stuff, we need to show previews/thumbs the way we want etc. you should clean up as well as some release-statements):
// MoviePlayer.h
#import <Foundation/Foundation.h>
#import <MediaPlayer/MediaPlayer.h>
#import "Logger.h"
#class MoviePlayerView;
#interface MoviePlayer : NSObject
{
#private
MPMoviePlayerController *controller;
MoviePlayerView *currentView;
}
#property (nonatomic, readonly) MPMoviePlayerController *controller;
+(MoviePlayer *) instance;
-(void) playMovie:(NSURL*)movieURL onView:(MoviePlayerView *)view;
-(void) stopMovie;
#end
// MoviePlayer.m
#import "MoviePlayer.h"
#import "MoviePlayerView.h"
#implementation MoviePlayer
#synthesize controller;
static MoviePlayer *player = nil;
#pragma mark Singleton management
+(MoviePlayer *) instance
{
#synchronized([MoviePlayer class])
{
if (player == nil)
{
player = [[super allocWithZone:NULL] init];
player->controller = [[MPMoviePlayerController alloc] init];
player->controller.shouldAutoplay = NO;
player->controller.scalingMode = MPMovieScalingModeAspectFit;
player->currentView = nil;
}
return player;
}
}
+(id) allocWithZone:(NSZone *)zone
{
return [[self instance] retain];
}
-(id) copyWithZone:(NSZone *)zone
{
return self;
}
-(id) retain
{
return self;
}
-(NSUInteger) retainCount
{
return NSUIntegerMax;
}
-(oneway void) release
{
// singleton will never be released
}
-(id) autorelease
{
return self;
}
#pragma mark MoviePlayer implementations
-(void) stopMovie
{
#synchronized(self)
{
if (controller.view.superview)
{
[controller.view removeFromSuperview];
}
if (controller.playbackState != MPMoviePlaybackStateStopped)
{
[controller pause];
[controller stop];
}
if (currentView)
{
NSNotificationCenter *ntfc = [NSNotificationCenter defaultCenter];
[ntfc removeObserver:currentView name:MPMoviePlayerLoadStateDidChangeNotification object:controller];
[ntfc removeObserver:currentView name:MPMoviePlayerPlaybackStateDidChangeNotification object:controller];
currentView = nil;
}
}
}
-(void) playMovie:(NSURL*)movieURL onView:(MoviePlayerView *)view
{
#synchronized(self)
{
[self stopMovie];
currentView = view;
NSNotificationCenter *ntfc = [NSNotificationCenter defaultCenter];
[ntfc addObserver:currentView
selector:#selector(loadStateDidChange:)
name:MPMoviePlayerLoadStateDidChangeNotification
object:controller];
[ntfc addObserver:currentView
selector:#selector(playbackStateDidChange:)
name:MPMoviePlayerPlaybackStateDidChangeNotification
object:controller];
[controller setContentURL:movieURL];
controller.view.frame = view.bounds;
[view addSubview: controller.view];
[controller play];
}
}
#end
// MoviePlayerView.h
#import <UIKit/UIKit.h>
#import "MoviePlayer.h"
#interface MoviePlayerView : MediaView
{
NSURL *movieURL;
NSURL *thumbnailURL;
UIImageView *previewImage;
UIView *iconView;
BOOL hasPreviewImage;
}
-(id) initWithFrame:(CGRect)frame thumbnailURL:(NSURL *)thumbnail movieURL:(NSURL *)movie;
-(void) loadStateDidChange:(NSNotification *)ntf;
-(void) playbackStateDidChange:(NSNotification *)ntf;
#end
// MoviePlayerView.m
#import "MoviePlayerView.h"
#interface MoviePlayerView()
-(void) initView;
-(void) initController;
-(void) playMovie;
-(void) setActivityIcon;
-(void) setMovieIcon:(float)alpha;
-(void) clearIcon;
-(CGPoint) centerPoint;
#end
#implementation MoviePlayerView
-(id) initWithFrame:(CGRect)frame thumbnailURL:(NSURL *)thumbnail movieURL:(NSURL *)movie
{
self = [super initWithFrame:frame];
if (self)
{
movieURL = [movie retain];
thumbnailURL = [thumbnail retain];
[self initView];
[self initController];
hasPreviewImage = NO;
loadingFinished = YES;
}
return self;
}
-(void) dealloc
{
[iconView release];
[previewImage release];
[movieURL release];
[super dealloc];
}
-(void)initView
{
self.backgroundColor = [UIColor blackColor];
// add preview image view and icon view
previewImage = [[UIImageView alloc] initWithFrame:self.bounds];
[previewImage setContentMode:UIViewContentModeScaleAspectFit];
UIImage *img = nil;
if (thumbnailURL)
{
img = [ImageUtils loadImageFromURL:thumbnailURL];
if (img)
{
previewImage.image = img;
hasPreviewImage = YES;
}
}
[self addSubview:previewImage];
[self setMovieIcon:(hasPreviewImage ? 0.8f : 0.3f)];
}
-(void)initController
{
UITapGestureRecognizer *rec = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(playMovie)];
[self addGestureRecognizer:rec];
[rec release];
}
-(void)playMovie
{
[[MoviePlayer instance] playMovie:movieURL onView:self];
[self setActivityIcon];
}
-(void) loadStateDidChange:(NSNotification *)ntf
{
MPMoviePlayerController *controller = [ntf object];
switch (controller.loadState)
{
case MPMovieLoadStatePlayable:
{
[self clearIcon];
[controller setFullscreen:YES animated:YES];
break;
}
case MPMovieLoadStateStalled:
{
[self setActivityIcon];
break;
}
default:
{
break; // nothing to be done
}
}
}
-(void) playbackStateDidChange:(NSNotification *)ntf
{
MPMoviePlayerController *controller = [ntf object];
switch (controller.playbackState)
{
case MPMoviePlaybackStatePlaying:
{
[self clearIcon];
break;
}
case MPMoviePlaybackStateStopped:
{
[self setMovieIcon:(hasPreviewImage ? 0.8f : 0.3f)];
break;
}
case MPMoviePlaybackStatePaused:
{
[self setMovieIcon:0.8f];
break;
}
case MPMoviePlaybackStateInterrupted:
{
[self setActivityIcon];
break;
}
default:
{
break; // nothing to be done
}
}
}
-(void) setActivityIcon
{
[self clearIcon];
iconView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
iconView.center = [self centerPoint];
[self addSubview:iconView];
[iconView performSelector:#selector(startAnimating)];
}
-(void) setMovieIcon:(float)alpha
{
[self clearIcon];
iconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"icon_movie.png"]];
iconView.center = [self centerPoint];
iconView.alpha = alpha;
[self addSubview:iconView];
}
-(void) clearIcon
{
if (iconView)
{
SEL stop = #selector(stopAnimating);
if ([iconView respondsToSelector:stop])
{
[iconView performSelector:stop];
}
[iconView removeFromSuperview];
[iconView release];
iconView = nil;
}
}
-(CGPoint) centerPoint
{
return CGPointMake(roundf(self.bounds.size.width / 2.0f), roundf(self.bounds.size.height / 2.0f));
}
-(void)resize
{
for (UIView *view in [self subviews])
{
if (view == iconView)
{
iconView.center = [self centerPoint];
continue;
}
view.frame = self.bounds;
}
[self addCaptionLabel];
}
-(void) layoutSubviews
{
[super layoutSubviews];
[self resize];
}
#end
...
player = [[MPMoviePlayerController alloc] initWithContentURL: [NSURL URLWithString:...
...
but I didn't gave internet connection to phone (wi-fi) :)
I had the same problem. My solution is using prepareToPlay:
MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentOfURL:movieURL];
if (player) {
[player prepareToPlay];
//...
}
This error seems to be thrown for lots of different reasons, but the reason I found was that the MPMoviePlayerController class freaks out if you call methods in a certain order. From an IRC Channel:
"apparently if you call prepareToPlay WHILE setting source type and
NOT setting the view yet causes this crash"
So I fixed this by just making sure that I called prepareToPlay: LAST (or second to last, with the last being play:).
It is also weird because my original code worked in iOS 5.1, but this problem suddenly manifested when I started using the iOS 6.0 sdk. It is possibly a bug in the MPMoviePlayerController code, so I'm going to be filing a radar report on it, as calling prepareToPlay: before setting the view / setting the sourceFileType should not throw an exception (or at least an exception that seemingly has nothing to do with the actual error)

Switching Videos in AVPlayer Creates Flash When Changing

I am using AVFoundation's AVPlayer to play 2 video clips made from 1 longer video (so the end of the first matches the beginning of the second)
When the first video ends and the user taps, I create a new AVPlayer and assign it to my PlayerView, and start playing the second clip.
This all works, however, there is a prominent screen "flicker".
My assumption is that this is caused by the player view removing the first clip and then showing the second clip.
What I need is for this flicker to no appear, so that going between the two clips is seamless.
Do anyone know if there is a way to stop this flickr, either via the AVPlayer* classes, or a way to "fake" it by doing something to make it so this isn't visible.
Thanks
Below is the code of my load and play method:
- (void)loadAssetFromFile
{
NSURL *fileURL = nil;
switch (playingClip)
{
case 1:
fileURL = [[NSBundle mainBundle] URLForResource:#"wh_3a" withExtension:#"mp4"];
break;
case 2:
fileURL = [[NSBundle mainBundle] URLForResource:#"wh_3b" withExtension:#"mp4"];
break;
case 3:
fileURL = [[NSBundle mainBundle] URLForResource:#"wh_3c" withExtension:#"mp4"];
break;
case 4:
fileURL = [[NSBundle mainBundle] URLForResource:#"wh_3d" withExtension:#"mp4"];
break;
default:
return;
break;
}
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
NSString *tracksKey = #"tracks";
[asset loadValuesAsynchronouslyForKeys:[NSArray arrayWithObject:tracksKey] completionHandler:
^{
// The completion block goes here.
NSError *error = nil;
AVKeyValueStatus status = [asset statusOfValueForKey:tracksKey error:&error];
if (status == AVKeyValueStatusLoaded)
{
self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
self.player = [AVPlayer playerWithPlayerItem:playerItem];
[playerView setPlayer:player];
[self.player seekToTime:kCMTimeZero];
[self play];
}
else {
// Deal with the error appropriately.
NSLog(#"The asset's tracks were not loaded:\n%#", [error localizedDescription]);
}
}];
}
You do not need to re-create AVPlayer for this task. You can just have multiple AVPlayerItems and then switch which one is current via [AVPlayer replaceCurrentItemWithPlayerItem:item].
Also, you can observe for when current item has changed with the code below.
static void* CurrentItemObservationContext = &CurrentItemObservationContext;
...
After creating a player, register the observer:
[player1 addObserver:self
forKeyPath:kCurrentItemKey
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:CurrentItemObservationContext];
...
- (void)observeValueForKeyPath:(NSString*) path
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {
if (context == CurrentItemObservationContext) {
AVPlayerItem *item = [change objectForKey:NSKeyValueChangeNewKey];
if (item != (id)[NSNull null]) {
[player1 play];
}
}
}
There are two workaround that I found. To me both approaches worked and I prefer the second one.
First, as #Alex Kennberg mentioned, create two set of AVPlayerLayer and AVPlayer. and switching them when you switch between videos. Be sure to set background color to clear color.
Second, use UIImageView as the owner view of AVPlayerLayer. Create thumbnail image of a video and set it to the imageview before switching the video. Be sure to set the view mode correctly.
I ran into the same issue with the video "flashing" and solved it this way in Swift 5.
Set my player variable to this
var player = AVPlayer(playerItem: nil)
Then inside my playVideo function, I changed this
self.player.replaceCurrentItem(with: AVPlayerItem(url: fileURL))
to this
player = AVPlayer(url: fileURL)
"fileURL" is the path to video I want to play.
This removed the flash and played the next video seamlessly for me.
You can initialise the PlayerItem and seek to zero some time before you assign it to the player.
Then the flickering disappears
I tried this and it worked for me.
if (layerView1.playerLayer.superlayer) {
[layerView1.playerLayer removeFromSuperlayer];
}
But I am also allocating my own AVPlayerLayer instead of using IB to do it.
After too many tries without success, I finally found a solution, not the best one, but works
My entry code bellow, have a look at the loadVideo method
#import "ViewController.h"
#import <AVKit/AVKit.h>
#interface ViewController ()<UIGestureRecognizerDelegate>
#property (nonatomic, strong) NSArray *videos;
#property (nonatomic, assign) NSInteger videoIndex;
#property (nonatomic, strong) AVPlayer *player;
#property (nonatomic, strong) AVPlayerLayer *playerLayer;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
self.videos = #[#"1.mp4", #"2.mp4", #"3.mp4", #"4.mp4", #"5.mp4", #"6.mp4"];
self.videoIndex = 0;
[self loadVideo];
[self configureGestures:self.view]; //changes video on swipe
}
-(void)prevZoomPanel{
if(self.videoIndex <= 0){
NSLog(#"cant go prev");
return;
}
self.videoIndex -= 1;
[self loadVideo];
}
-(void)nextZoomPanel{
if(self.videoIndex >= self.videos.count - 1){
NSLog(#"cant go next");
return;
}
self.videoIndex += 1;
[self loadVideo];
}
#pragma mark - Load Video
-(void)loadVideo{
NSURL * bundle = [[NSBundle mainBundle] bundleURL];
NSURL * file = [NSURL URLWithString:self.videos[self.videoIndex] relativeToURL:bundle];
NSURL * absoluteFile = [file absoluteURL];
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:absoluteFile];
//*************
//DO NOT USE '[self.player replaceCurrentItemWithPlayerItem:item]', it flashes, instead, initialize the instace again.
//Why is replaceCurrentItemWithPlayerItem flashing but playerWithPlayerItem is NOT?
// if you want to see the diferente, uncomment the code above
self.player = [AVPlayer playerWithPlayerItem:item];
// if (self.player == nil) {
// self.player = [AVPlayer playerWithPlayerItem:item];
// }else{
// [self.player replaceCurrentItemWithPlayerItem:item];
// }
//*************
//create an instance of AVPlayerLayer and add it on self.view
//afraid of this
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
playerLayer.frame = self.view.layer.bounds;
[self.view.layer addSublayer:playerLayer];
//*************
//play the video before remove the old AVPlayerLayer instance, at this time will have 2 sublayers
[self.player play];
NSLog(#"sublayers before: %zd", self.view.layer.sublayers.count);
//*************
//remove all sublayers after 0.09s, to avoid the flash, 0.08 still flashing.
//TODO: tested on iPhone X, need to test on slower iPhones to check if the time is enough.
//Why do I need to wait to remove? Is that safe? What if I swipe a lot too fast, faster than 0.09s ?
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.09 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSArray* sublayers = [NSArray arrayWithArray:self.view.layer.sublayers];
NSInteger idx = 0;
for (CALayer *layer in sublayers) {
if (idx < self.view.layer.sublayers.count && self.view.layer.sublayers.count > 1) {
//to avoid memory crash, need to remove all sublayer but keep the top one.
[layer removeFromSuperlayer];
}
idx += 1;
}
NSLog(#"sublayers after: %zd", self.view.layer.sublayers.count);
});
//*************
//the code bellow is the same of the above, but with no delay
//uncomment the code bellow AND comment the code above to test
// NSArray* sublayers = [NSArray arrayWithArray:self.view.layer.sublayers];
// NSInteger idx = 0;
//
// for (CALayer *layer in sublayers) {
//
// if (idx < self.view.layer.sublayers.count && self.view.layer.sublayers.count > 1) {
//
// //to avoid memory crash, need to remove all sublayer but keep the top one.
// [layer removeFromSuperlayer];
// }
// idx += 1;
// }
//*************
//App's memory usage is about 14MB constantly, didn't increase on videos change.
//TODO: need to test with more than 100 heavy videos.
}
-(void)configureGestures:(UIView *)view{
UISwipeGestureRecognizer *right = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(userDidSwipeScreen:)];
right.direction = UISwipeGestureRecognizerDirectionRight;
right.delegate = self;
[view addGestureRecognizer:right];
UISwipeGestureRecognizer *left = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(userDidSwipeScreen:)];
left.direction = UISwipeGestureRecognizerDirectionLeft;
left.delegate = self;
[view addGestureRecognizer:left];
}
- (void)userDidSwipeScreen:(UISwipeGestureRecognizer *)swipeGestureRecognizer{
switch (swipeGestureRecognizer.direction) {
case UISwipeGestureRecognizerDirectionLeft: [self nextZoomPanel];break;
case UISwipeGestureRecognizerDirectionRight:[self prevZoomPanel];break;
default: break;
}
}
#end
I found a very simple solution (maybe too simple for some people, but for me it worked):
In Interface Builder I set the background color of my view (which gets the video layer attached to) to black. So it's just 'flashing' black now...
As what #blancos says in this answer
Firstly, AVPlayer doesn't show any white screen, its your background
which is white
He's 100% correct because when I set my background to white, the flash was white. But when I set the background to green, the flash was green. So to fix it, I set the background to black
view.backgroundColor = .black
When switching videos, I used player.replaceCurrentItem(...):
playerItem: AVPlayerItem?
func switchVideos(url: URL) {
playerItem = AVPlayerItem(url: url)
player.replaceCurrentItem(with: playerItem!)
// if necessary use the KVO to know when the video is ready to play
}

Resources