I am implementing Media Player in iOS Platform. I have a problem with UI Freezing, when streaming the videos from the internet using AVPlayer. Note: I'm not using AVAudioPlayer, AVQueuePlayer. Here following code for playing the media: UI Freeze is occurring only start Streaming.
if(_player.rate!=0.0)
{
[_player pause];
[ad.player.playerLayer removeFromSuperlayer];
[_player replaceCurrentItemWithPlayerItem:[ AVPlayerItem playerItemWithURL:_tempURL]];
}
else
{
_player = [_player initWithURL:mediaURL];
ad.player.playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
ad.player.playerLayer.frame=ad.player.bounds;
ad.player.playerLayer.frame=CGRectMake(0, 0, 320, 150);
[ad.player.playerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
_player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
ad.player.playerLayer.needsDisplayOnBoundsChange = YES;
[ad.player.layer addSublayer:ad.player.playerLayer];
[_player play];
}
I referred the following Link:
AVPlayer "freezes" the app at the start of buffering an audio stream
But that link suggested for AVQueuePlayer. But my Requirement is to do in AVPlayer
When you start playing the video it hasn't downloaded any data yet, AVPlayer class has a method called prerollAtRate:completionHandler which loads data starting at the item’s current playback time, which then calls a completionHandler whens its finishes the load attempt.
__weak typeof(self) weakSelf = self;
[self prerollAtRate:0.0 completionHandler:^(BOOL finished) {
NSLog(#"ready: %d", finished);
// if ready call the play method
if (finished) {
dispatch_async(dispatch_get_main_queue(), ^{
// call UI on main thread
[weakSelf.player play];
});
}
}];
Related
Hi in my application I have avplayerViewController. I just launch the application and click on start play button - which will launch avplayerviewcontroller. There i just drag the seek bar and content is buffering before content start play i just close the playerViewcontroller. After few seconds content is start playing (audio is coming). It's very serious issue please guide me how to resolve this issue. As of now when ever i close the player i am doing below operations.
#try{
[self.player replaceCurrentItemWithPlayerItem:nil];
[self.player setRate:0.0];
[self.player seekToTime:CMTimeMake(0, 1)];
[self.player pause];
dispatch_async(dispatch_get_main_queue(), ^{
[self.contentOverlayView sendSubviewToBack:self->playerActivityIndicator];
[self->playerActivityIndicator stopAnimating];
});
// [[self.player currentItem] removeObserver:self forKeyPath:#"status"];
[[self.player currentItem] removeObserver:self forKeyPath:#"playbackBufferEmpty"];
[self.player removeTimeObserver:self->playerPeriodicTimeObserver];
}#catch(id anException){
//do nothing, obviously it wasn't attached because an exception was thrown
NSLog(#"No observer for a player in hide placeholder");
}
I am working on this iOS app that plays music from URLs. Currently it has no issues playing through a playlist while the app is foregrounded, backgrounded, or even when the screen is locked ONLY IF the app was in the foreground prior to locking.
If the app is backgrounded THEN has the screen locked, the next song will load, but it will not play.
My question is: How can I get the AVPlayer to play the next song in a list while the app is in the background and the screen is locked?
Below is the code I am using to load and play the songs...
- (void)loadNextSong {
if (self.playlistIndex < self.playlist.count-1) { //check to see if the next track is in bounds of the playlist array
self.playlistIndex++;
[self.player pause];
[self serviceDidFetchSong:self.playlist[self.playlistIndex]];
[self.player play];
} else { //play the first song if we reach the end of the playlist
self.playlistIndex = 0;
[self.player pause];
[self serviceDidFetchSong:self.playlist[self.playlistIndex]];
[self.player play];
}
}
- (void)serviceDidFetchSong:(Song *)song {
if (song.audioFileURL != nil) {
if (![self.song.audioFileURL isEqual:song.audioFileURL]) {
if (self.timeIntervalObserver) {
[self.player removeTimeObserver:self.timeIntervalObserver];
}
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:song.audioFileURL options:0];
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:asset];
self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
//this is just for a visual of the song progress
__block __weak SongPlayerViewController *blockSelf = self;
self.timeIntervalObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds((1.0f/5.0f), NSEC_PER_SEC)
queue:NULL
usingBlock:^(CMTime time){
[blockSelf updateProgressBar];
}];
}
self.song = song;
}
}
After trying to debug with some console logs and break points, I have verified that [self.player play] is definitely being called and that the correct URL is loaded each time.
My info.plist file has audio and fetch enabled for the required background modes.
The next song just doesn't seem to play at all when the app has been backgrounded and the screen is locked.
Any insight or suggestions would be much appreciated. Thank you.
EDIT
This seems to be an intermittent issue which appears much less frequently since the release of iOS 9.3 which leads me to believe this may have been related to the OS...?
It works when you play it twice.
player?.play()
player?.play()
I am implementing a player with AVPlayer, which will play videos from a URL. This URL can be for "fixed-length" videos (that have a predetermined duration) and live videos (whose duration is unknown/not defined).
The fixed-length URLs I am using link to .mp4 files (using H264 codec), while the live videos have a .m3u8 extensions (but also H264).
I can play a number of .m3u8 URLs, but when I try to play the .mp4, it won't load.
However, if I start the app and play a .mp4 URL first, I can play other .mp4 URLs, but I can't switch to the .m3u8.
Both formats play fine, but there some issue when I switch from one to another.
These are the methods I use to instantiate and "clean" the player:
- (void)prepareAndPlayAutomatically:(BOOL)playAutomatically {
currentItem = [AVPlayerItem playerItemWithURL:videoURL];
[[self player] replaceCurrentItemWithPlayerItem:currentItem];
[self.layer addSublayer:[self playerLayer]];
[[self player] addObserver:self forKeyPath:#"rate" options:0 context:nil];
[[self player] seekToTime:kCMTimeZero];
if (playAutomatically) {
[[self player] play];
}
}
- (void)clean {
[[self player] pause];
[[self player] seekToTime:kCMTimeZero];
[[self player] replaceCurrentItemWithPlayerItem:nil];
[[self player] removeObserver:self forKeyPath:#"rate"];
[self removeFromSuperview];
}
[self player] will return a singleton AVPlayer instance;
[self playerLayer] will also return a singleton AVPlayerLayer instance;
- (AVPlayer *)player {
static dispatch_once_t once;
static AVPlayer *player;
dispatch_once(&once, ^{
player = [AVPlayer playerWithPlayerItem:currentItem];
});
return player;
}
- (AVPlayerLayer *)playerLayer {
static dispatch_once_t once;
static AVPlayerLayer *playerLayer;
dispatch_once(&once, ^{
playerLayer = [AVPlayerLayer playerLayerWithPlayer:[self player]];
});
return playerLayer;
}
I've narrowed down the problem to these singletons. If I remove the singleton pattern, no problem will occur, and I am able to play both kinds of videos.
Is there anything else I am supposed to do when I "clean" the player?
It seems that AVPlayer class is not intended for reuse for items with different video compositions.
From the docs on [AVPlayer replaceCurrentItemWithPlayerItem:] method:
Special Considerations
The new item must have the same compositor as the item it replaces, or
have no compositor.
I too ran into same problem recently. I solved it by maintaining 2 instances of the AVQueuePlayer - One to play Mp4 and other for M3u8.
And switched between these players by hiding one player view and showing the other based on the video format (Mp4/M3u8).
I am starting to get convinced that iOS is just pure magic. I looked through numerous posts on AVAudioPlayer for solutions, but none worked.
When the application starts I have a movie starting off from MPMoviePlayerController and I have AVAudioPlayer kicking off with a sound as follows:
-(void)createAndPlayMovieFromURL:(NSURL *)movieURL sourceType:(MPMovieSourceType)sourceType{
/* Create a new movie player object. */
player = [[MPMoviePlayerController alloc] initWithContentURL:movieURL];
player.controlStyle = MPMovieControlModeDefault;
if (player)
{
/* Save the movie object. */
[self setMoviePlayerController:player];
/* Specify the URL that points to the movie file. */
[player setContentURL:movieURL];
/* If you specify the movie type before playing the movie it can result
in faster load times. */
[player setMovieSourceType:sourceType];
CGRect frame = CGRectMake(0, 0, 480, 320);
/* Inset the movie frame in the parent view frame. */
[[player view] setFrame:frame];
[player view].backgroundColor = [UIColor blackColor];
/* To present a movie in your application, incorporate the view contained
in a movie player’s view property into your application’s view hierarchy.
Be sure to size the frame correctly. */
[self.view addSubview: [player view]];
}
[[self moviePlayerController] play];
}
and for sound:
- (void)setupAudioPlayBack:(NSString *) path : (NSString *) extension{
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:path
ofType:extension]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc]
initWithContentsOfURL:url
error:&error];
if (error)
{
NSLog(#"Error in audioPlayer: %#",
[error localizedDescription]);
} else {
audioPlayer.delegate = self;
[audioPlayer prepareToPlay];
}
}
And now lets talk magic. When the first movie plays and the sound, the sound is not responding to either the volume controls on the phone nor to Silent Mode On switch.
But then immediately a second movie plays with its own sound and everything works fine - there is no sound with Silent Mode On, if Silent Mode Off it responds to Volume Controls. 3rd movie - plays perfectly too.
I tried using the player.useApplicationAudioSession = NO for MPMoviePlayerController - fixed the problem with the first time sound not responding to Silent Mode or Volume Controls, but now when the second movie starts to play it freezes instantly and the sound played is cut from the original size to something around 2 seconds.
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategorySoloAmbient error:nil]; didn't help either wherever I set it.
So my question is - how to make the MPMoviePlayerController interact with AVAudioPlayer without any problems...
Ok I actually found a solution (once again, magic).
When you start your ViewController you set it up as follows:
- (void)viewDidLoad
{
[self setupAudioPlayBack:#"sound1" :#"caf"];
[self playMovieFile:[self localMovieURL:#"mov1" :#"mov"]];
audioTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(playAudio) userInfo:nil repeats:NO];
[super viewDidLoad];
}
Where the playAudio method is simply:
- (void)playAudio{
[audioPlayer play];
}
Just 0.1 millisecond timer is enough to make everything work (0.0 doesn't work) and make sure a new video doesn't start before the audio finishes or it will destroy the settings so you will have to invalidate the timer and redo the timer again.
I just want a continuously looping video. I set up the player like this:
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:someURL];
self.moviePlayer.controlStyle = MPMovieControlStyleNone;
self.moviePlayer.shouldAutoplay = YES;
self.moviePlayer.repeatMode = MPMovieRepeatModeOne;
self.moviePlayer.view.frame = self.container.frame;
[self.container addSubview:self.moviePlayer.view];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(moviePlayBackDidFinish:) name: MPMoviePlayerPlaybackStateDidChangeNotification
object: self.moviePlayer];
- (void) moviePlayBackDidFinish:(NSNotification*)notification {
NSLog( #"myMovieFinishedCallback: %#", notification );
MPMoviePlayerController *movieController = notification.object;
NSLog( #"player.playbackState = %d", movieController.playbackState );
}
The notification method is simply a hack that someone suggested here: Smooth video looping in iOS
I have two problems. The video looping still is not seamless. There is a very noticeable paused between loops. Second, the video stops looping after an arbitrary number of loops. Typically varies between 2-4 loops. This is obviously a huge problem for my app. Is the player really this buggy or am I doing something wrong?
I have created a complete seamless looping solution for video here seamless-video-looping-on-ios. Feel free to download the example xcode app and try it out for yourself to see my approach in action. I found that MPMoviePlayerController and AVPlayer both fail to work for this type of thing.
I was also unable to get gapless looping using MPMoviePlayerController -- there was always at least 0.5s of black, and the occasional flash of the QuickTime logo.
However, I am able to obtain gapless looping using AVPlayer -- it takes a couple of conditions to achieve, though:
Something about the encoding of my test video clip means that seeking to the beginning always causes a pause of about 0.5s at the start of each loop. Seeking to 1s into the clip with a kCMTimeZero tolerance makes it seamless. Without the explicit zero seek-tolerance, the effect is the same as seeking to the beginning of the clip.
Seeking while not playing is erratic; it causes a hang on my iPhone 4, but not my iPad 3. The two alternative fixes (shown #if'ed below), are to:
wait for the seek to finish before calling play again, or
wait until a specific time (before the end of the clip), then restart playback at the beginning.
The following code implements those two conditions:
self.player = [AVPlayer playerWithURL:url];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.frame = self.view.bounds;
[self.view.layer addSublayer:self.playerLayer];
[self.player seekToTime:CMTimeMakeWithSeconds(1, 1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
[self.player play];
#if 1
[[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self.player seekToTime:CMTimeMakeWithSeconds(1, 1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {
[self.player play];
}];
}];
#endif
#if 0
NSArray *times = [NSArray arrayWithObject:[NSValue valueWithCMTime:CMTimeMake(5, 1)]];
[self.player addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
[self.player seekToTime:CMTimeMakeWithSeconds(1, 1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
}];
#endif