I have a music player which streams audio from server, using AVPlayer, it plays a audio well, but after completion of an audio , when i click play button for new audio it takes time to start new audio almost 35-40 sec. Can anyone help me?
Use observer to know the player state.
Using KVO, it's possible to be notified for changes of the player status:
playButton.enabled = NO;
player = [AVPlayer playerWithURL:fileURL];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
This method will be called when the status changes:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (object == player && [keyPath isEqualToString:#"status"]) {
if (player.status == AVPlayerStatusReadyToPlay) {
playButton.enabled = YES;
} else if (player.status == AVPlayerStatusFailed) {
// something went wrong. player.error should contain some information
}
}
}
Related
I am trying to use the native player (AVPlayer) to reproduce a live stream on iOS. However, I have trouble resuming the playback. When I stop the playback and resume it after few seconds, the playback starts from the moment I paused instead of reproducing the current (last) sample of the live stream.
Is there a way to get the last sample, o configure AVPlayer to reproduce from last sample when tapping on Play Button?
My solution is based on denying user to keep the player paused. This is, destroying the player each time playback is resumed. And creating a new instance each time, playback is intended to be resumed.
According to Apple recommendation, the only solution to know if the AVPlayer was stopped is to add KVO. This is:
- (void)setupPlayer {
AVPlayer *player = [AVPlayer playerWithURL:streamURL];
AVPlayerViewController *playerViewController = [[AVPlayerViewController alloc] init];
playerViewController.player = player;
self.playerViewController = playerViewController;
[self configureConstraintsForView:self.playerViewController.view]; //Add Player to View
[self setupObservers];
[player play];
}
- (void)setupObservers {
[self.playerViewController.player addObserver:self
forKeyPath:#"rate"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
if ([keyPath isEqualToString:#"rate"] && self.playerViewController.player.rate == CGPointZero.x) {
[self.playerViewController.view removeFromSuperview];
[self.playerViewController.player removeObserver:self forKeyPath:#"rate"];
[self.playerViewController.player pause];
self.playerViewController = nil;
}
}
Then, when user wants to re-engage the player, just call -(void)setupPlayer which start the playback from the last live sample.
I use AVPlayer for streaming mp3 file from the internet and it works really slow. Using profiler I found out, that it downloads entire file at first, and then starts playing. Is there any workaround for this?
Right now, I'm using this code
if let player = player {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
let item = AVPlayerItem(url: url)
player.replaceCurrentItem(with: item)
} else {
player = AVPlayer(url: url)
}
player?.play()
Things I've tried:
move player?.play() to an observer, attached to status property of the item
play around with properties preferredForwardBufferDuration and preferredPeakBitRate
All the time the result is downloading a whole audio file and only then start of playing.
Please note, the issue is - player starts to play ONLY after the whole file was downloaded, while I want it to stream mp3.
To play immediately you can try to set
player.automaticallyWaitsToMinimizeStalling = false
You can add an observer to when the AVPlayer gets empty buffer:
[[self.tracksPlayer currentItem] addObserver:self
forKeyPath:#"playbackBufferEmpty"
options:NSKeyValueObservingOptionNew
context:nil];
And an observer so you can know when the AVPlayer buffered enough to keep up:
[[self.tracksPlayer currentItem] addObserver:self
forKeyPath:#"playbackLikelyToKeepUp"
options:NSKeyValueObservingOptionNew
context:nil];
Then just check for that in your KVO callback:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (object == [self.tracksPlayer currentItem] &&
[keyPath isEqualToString:#"playbackBufferEmpty"]) {
if ([self.tracksPlayer currentItem].playbackBufferEmpty) {
NSLog(#"Buffer Empty");
}
} else if (object == [self.tracksPlayer currentItem] &&
[keyPath isEqualToString:#"playbackLikelyToKeepUp"]) {
if ([self.tracksPlayer currentItem].playbackLikelyToKeepUp) {
NSLog(#"LikelyToKeepUp");
}
}
}
In iOS9, the MPMoviePlayer classes have all been deprecated in favor of AVPlayer. I have an existing app using MPMoviePlayerPlaybackDidFinishReasonUserInfoKey to determine how to log events on how the video player ended. How do I do the same with AVPlayer?
The following are the ending reason keys:
MPMovieFinishReasonPlaybackEnded
MPMovieFinishReasonPlaybackError
MPMovieFinishReasonUserExited
There is no equivalent to MPMoviePlayerPlaybackDidFinishReasonUserInfoKey and MPMoviePlayerPlaybackDidFinishNotification in AVKit. To accomplish the same functionality in AVKit, you must listen to three notifications separately, rather than one notification with different possible reasons.
MPMovieFinishReasonPlaybackEnded >>> AVPlayerItemDidPlayToEndTimeNotification
MPMovieFinishReasonPlaybackError >>> AVPlayerItemFailedToPlayToEndTimeNotification
MPMovieFinishReasonUserExited. No conversion. There are multiple ways to detect that the user has killed the player. One is detecting a modal has closed.
If you want to know if the video is playing or not, you can do a KVO:
[self.player addObserver:self forKeyPath:#"rate" options:0 context:nil];
Then add this method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"rate"]) {
if ([self.player rate]) {
[self changeToPause]; // This changes the button to Pause
}
else {
[self changeToPlay]; // This changes the button to Play
}
}
}
Try, in viewDidLoad:
AVPlayerItem* playerItem = [AVPlayerItem playerItemWithURL:[NSURL URLWithString:#"yoururl"]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
AVPlayer* player = [[[AVPlayer alloc] initWithPlayerItem:playerItem] autorelease];
[player play]
And
-(void)itemDidFinishPlaying:(NSNotification *) notification {
// Will be called when AVPlayer finishes playing playerItem
}
Every time, when I try to playing a megabyte video using AVPlayer, it initially shows a white screen for a second and then starts the video.
Why is this happening if the video is already cached? Is there a way to stop this from happening, so that it goes straight to the video without displaying a white screen?
I tried using AVPlayer's isReady to check the status of AVPlayer and play video only when it's ready, but it still displays the white screen.
Also every time when I try to get the video duration of the video that's about to play through AVPlayer I keep getting 0.0 seconds initially, so I am not able to add a timer to the video either because I can't get the video duration because it keeps displaying a white screen for a second.
Firstly, AVPlayer doesn't show any white screen, its your background which is white. So, basically your AVPlayer is starting late. I guess you press a UIButton and then it loads the file in AVPlayer and immediately start playing it. Thats where the problem is. It may take some time for the AVPlayer to buffer enough data and be ready to play the file. Using KVO, it is possible to be notified for changes of the player status.
So first you need to disable the play button, load the AVPlayer and add an observer:
play.enabled = NO;
player = [AVPlayer playerWithURL:URL];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
Then enable it after checking AVPlayerStatusReadyToPlay:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (object == player && [keyPath isEqualToString:#"status"]) {
if (player.status == AVPlayerStatusReadyToPlay) {
play.enabled = YES;
}
}
}
I know this is an old question, but I get the same issue even when properly detecting when the AVPlayer is ready to play.
I wanted it to play over an image so that there was a smooth transition between an initial static image, and then moving video.
The trick for me was to set a clear background with:
AVPlayerViewController *controller = [[AVPlayerViewController alloc] init];
[controller.view setBackgroundColor:[UIColor clearColor]];
This way, if I toggle the visibility of the player when it's ready to play, I never see a black or white screen, because the player has a clear background, making for a smooth transition!
Blancos is right. AVPlayer is taking time to achieve state AVPlayerStatusReadyToPlay.
So, initialize the player with url and play it only when it is AVPlayerStatusReadyToPlay.
player = [AVPlayer playerWithURL:URL];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if (object == player && [keyPath isEqualToString:#"status"]) {
if (player.status == AVPlayerStatusReadyToPlay) {
player.play()
}
}
}
I had the same problem. To avoid adding a KVO, I just set the AVPlayer up when the url is set like so...
var urlToUse: NSURL?
{
didSet
{
guard let urlToUse = urlToUse else { return }
replayPlayer = AVPlayer(URL: urlToUse)
}
}
That way the AVPlayer status will be ready when needed.
I developed an app for radio and it's working.
I want to put an activity indicator view so that when you touch the play it starts the activity indicator view and when the audio starts, the activity indicator stops.
This will add the activity view to the center of the view. Add this code in the "IBAction" where u handle the play button.
UIActivityIndicatorView *activityView=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityView.center=self.view.center;
[activityView startAnimating];
[self.view addSubview:activityView];
[self.view userInteractionEnabled:NO]; //to avoid touch events when activity is on
And to stop the activity indicator use
[activityView stopAnimating];
[activityView removeFromSuperview];
[self.view userInteractionEnabled:YES];
This is from this link about AV Foundation. and this answer ->
Using KVO, it's possible to be notified for changes of the player status:
player = [AVPlayer playerWithURL:fileURL];
[player addObserver:self forKeyPath:#"status" options:0 context:nil];
In the PLAY button's event add the code to start the UIActivityIndicator
This method will be called when the status changes:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (object == player && [keyPath isEqualToString:#"status"]) {
if (player.status == AVPlayerStatusReadyToPlay) {
//DISABLE THE UIACTIVITY INDICATOR HERE
} else if (player.status == AVPlayerStatusFailed) {
// something went wrong. player.error should contain some information
}
}
}