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");
}
}
}
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 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
}
}
}
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've looked around but I can't find a delegate protocol for the AVPlayer class. What gives?
I'm using its subclass, AVQueuePlayer, to play an array of AVPlayerItems, each loaded from a URL. Is there any way I can call a method when a song finishes playing? Notably at the end of the queue?
And if that's not possible, is there any way I could call a method when the song STARTS playing, after buffering? I'm trying to get a loading icon in there but it turns the icon off before the music actually begins, even though it's after the [audioPlayer play] action.
Yes, the AVPlayer class does not have a delegate protocol like the AVAudioPlayer. You need to subscribe to notifications on an AVPlayerItem. You can create an AVPlayerItem using the same URL that you would otherwise pass to -initWithURL: on AVPlayer.
-(void)startPlaybackForItemWithURL:(NSURL*)url {
// First create an AVPlayerItem
AVPlayerItem* playerItem = [AVPlayerItem playerItemWithURL:url];
// Subscribe to the AVPlayerItem's DidPlayToEndTime notification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem];
// Pass the AVPlayerItem to a new player
AVPlayer* player = [[[AVPlayer alloc] initWithPlayerItem:playerItem] autorelease];
// Begin playback
[player play]
}
-(void)itemDidFinishPlaying:(NSNotification *) notification {
// Will be called when AVPlayer finishes playing playerItem
}
Yes. Add a KVO observer to the player's status or rate:
- (IBAction)go {
self.player = .....
self.player.actionAtItemEnd = AVPlayerActionStop;
[self.player addObserver:self forKeyPath:#"rate" options:0 context:0];
}
- (void)stopped {
...
[self.player removeObserver:self]; //assumes we are the only observer
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == 0) {
if(player.rate==0.0) //stopped
[self stopped];
}
else
[super observeVal...];
}
So basically, that's it.
Disclaimer: I wrote that in here so I didn't check if the code was good. ALSO I never used AVPlayer before but it should be about right.
Swift 3 - I add an observer to AVPlayerItem every time I add a video to the player:
func playVideo(url: URL) {
let playerItem = AVPlayerItem(asset: AVURLAsset(url: someVideoUrl))
NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidPlayToEndTime), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
self.player.replaceCurrentItem(with: playerItem)
self.player.play()
}
func playerItemDidPlayToEndTime() {
// load next video or something
}
There is a lot of information in the Apple docs AVFoundation Programming Guide (look for the monitoring playback section). It appears to be mainly through KVO so you may wish to brush up on that if you are not too familiar (there is a guide for that to Key Value Observing ProgrammingGuide.
I'm using this one, and it works:
_player = [[AVPlayer alloc]initWithURL:[NSURL URLWithString:_playingAudio.url]];
CMTime endTime = CMTimeMakeWithSeconds(_playingAudio.duration, 1);
timeObserver = [_player addBoundaryTimeObserverForTimes:[NSArray arrayWithObject:[NSValue valueWithCMTime:endTime]] queue:NULL usingBlock:^(void) {
[_player removeTimeObserver:timeObserver];
timeObserver = nil;
//TODO play next sound
}];
[self play];
where _playingAudio is my custom class with some properties and timeObserver is id ivar.