How to disconnect AVPlayer from AVPlayerItem? - ios

I want to reuse an AVPlayerItem but keep getting this error:
An AVPlayerItem cannot be associated with more than one instance of AVPlayer
Before trying to reuse it, I destroy the previous AVPlayer like this:
[self.player pause];
[self.player replaceCurrentItemWithPlayerItem:nil];
self.player = nil;
why is the AVPlayerItem still associated and how can I disconnect it?
Here is a Gist with a full reproduction of the problem (only 50 lines btw): https://gist.github.com/sbiermanlytle/14a6faab515f7691b810789086ae9e50
You can run it by creating a new Single View app, and supplanting the ViewController implementation with that code.

You can't disconnect AVPlayerItems. I guess the private property that points to the player is not weak, so dereferencing the item by setting current player item to NULL does not automatically set the item's player property to NULL..
Simply create a new one. Either with the URL, in which case the cache system will return an AVAsset instantly ( Just another guess... ), or, better, with the asset of the PlayerItem you want to 'disconnect'.
AVPlayerItem* newPlayerItem = [AVPlayerItem playerItemWithAsset:playerItem.asset];
There is no performance loss doing this. The item is just a 'handle' to the asset, which contains the data for real. So don't be afraid to trash and create new items on the fly.

Related

Associate AVPlayerItem with new AVPlayer in Swift?

How can I reassociate an instance of AVPlayerItem with a new AVPlayer in Swift? When used in an initializer. Every time I try to reassign an AVPlayerItem to a different AVPlayer even after I've set all references to the AVPlayer to nil (so it should get garbage collected), it complains that an AVPlayerItem cannot be assigned to more than 1 AVPlayer object. I understand a few ways to get around it but I want to know why this way doesn't work.
let player: AVPlayer = AVPlayer(playerItem: somePlayerItem)
I don't know what happens during this declaration but something somewhere gets set to allow somePlayerItem know it has an associated player. Does this have some observer set somewhere or property set that would note this?
Does anyone know a way to reassign this AVPlayerItem to a different AVPlayer after the original AVPlayer's references have been destroyed? I know I can just make a new AVPlayerItem using the URL but I want to know if I can keep the same object.
You can't reuse an item, but you can reuse an asset.
So create your asset once:
let asset=AVAsset(URL: your_url)
Then reuse it when needed by creating a new item:
let item=AVPlayerItem(asset: asset)
let player=AVPlayer(playerItem: item)

Streaming multiple music with AVPlayer

I have an app that I want to make which requires streaming audio files from web server. I use AVPlayer as the player. The problem is, some responses that I am receiving from the server has two audio files on it. And this makes the streaming hard. My audio player UI by the way is like this:
I have a slider for the streamed time ranges (the black one) and another slider for the AVPlayer.currentTime. I have two audio music streamed and their music durations are added together which is now 8:46. My first music has 6 minutes duration and my second music has 1:46. As you can see in the above photo, my streamed time ranges slider indicates that AVAsset has completely streamed the first music. My problem is, I can't continue streaming and playing the next music when the first one has reached it's end. It just stop and the slider value gets back to 0.
What I want to accomplish is that when the first item has reached its end, AVPlayer would load another player item and that would be the second music. Will continue to play and slider will continue to move.
Is this possible? What are your suggestions? Thanks experts.
To load audio files one after the other, you can use AVQueuePlayer.
NSURL *song1 = [NSURL URLWithString:#"audio url1"];
NSURL *song2 = [NSURL URLWithString:#"audio url2"];
AVPlayerItem *playerItem1 = [[AVPlayerItem alloc] initWithURL:song1];
AVPlayerItem *playerItem2 = [[AVPlayerItem alloc] initWithURL:song2];
NSArray *songs = #[playerItem1, playerItem2];
self.queuePlayer = [[AVQueuePlayer alloc] initWithItems:songs];
[self.queuePlayer play];
Hope this might help you.

Why doesn't AVPlayer stop loading data?

When I watch my mac's network connectivity I can tell that a movie is still buffering even after I remove the view which had the AVPlayer on it.
Does anyone know how to force AVPlayer to stop loading data?
Maybe it's late to answer this question, but I've just solved the same one.
Just save here in case anyone need...
[self.player.currentItem cancelPendingSeeks];
[self.player.currentItem.asset cancelLoading];
There's no cancel method in AVPlayer class, but in AVPlayerItem and AVAsset class, there are!
PS: player here is AVPlayer class, not AVQueuePlayer class.
An AVPlayer is not a view. You may have "removed the view" but that does not mean you have removed or stopped the AVPlayer. I would guess that the way to do what you want is to destroy the AVPlayer itself.
I figured it out.
When you want to stop an AVPlayerItem from loading, use AVQueuePlayer's removeAllItems and then re-initialize it.
[self.avPlayer removeAllItems];
self.avPlayer = [AVQueuePlayer playerWithURL:[NSURL URLWithString:#""]];
self.avPlayer = nil;
This will stop the current item from loading -- it is the only way I found to accomplish this.

IOS: AVAudioPlayer

In my app I have 52 mp3 and when I load my view controller I alloc all 52 mp3 in this way:
NSString *pathFrase1 = [NSString stringWithFormat:#"%#/%#",[[NSBundle mainBundle] resourcePath],[NSString stringWithFormat:#"a%d_1a.mp3",set]];
NSURL *filePath1 = [NSURL fileURLWithPath:pathFrase1 isDirectory:NO];
f1 = [[AVAudioPlayer alloc] initWithContentsOfURL:filePath1 error:nil];
[f1 prepareToPlay];
but it's very slow when I open viewcontroller, then is there a way to alloc mp3 when I use it? and release its AVAudioPlayer??
It's a little more complicated to handle, but instead of using AVAudioPlayer, use AVPlayer. AVPlayer is designed to play AVAssets, which can be preloaded files. Specifically, you'll want to use a subclass of AVAsset called AVURLAssets, which can load up your URL: Loading AVAsset. You can then use AVAsset to create an AVPlayerItem. An AVPlayerItem is a lightweight wrapper that AVPlayer uses to keep track of play state for an AVAsset. The nice thing about using AVPlayer is that it can play an AVMutableComposition, which itself can contain multiple AVAssets. AVPlayer can also play a queue of AVAssets and provide you with information on when it is beginning to play a new AVAsset, and which one. If you load your MP3's into a bunch of AVURlAssets you can load them and keep them around, creating AVPlayerItem & AVPlayer only when you want to play one (or more) of the MP3's.
AVAudioPlayer is designed to play single files, but it uses AVAssets (and probably AVPlayer) behind the scenes. It's nice for simple situations, but anything more complex and you really want to use AVPlayer.
I should also point out that AVPlayerItem & AVPlayer are light weight objects. They don't take long at all to instantiate. It's loading the AVAsset that takes all the time. So you can feel free to create and destroy AVPlayerItem & AVPlayer objects as you need.
Finally, AVAsset and AVPlayer sometimes rely on blocks for notifications. So, for example, you may need to use c-blocks when loading up AVURLAsset to get notification on when an Asset if fully loaded. Just be aware that those blocks aren't called on the main thread. So if you try to update any UI elements or do any animations from that block it won't work right. You need to dispatch another block on to the main thread to do this, for example: dispatch_async(dispatch_get_main_queue(),^{....update UI element code....});. For more information about about dispatching blocks see Apple's Concurrency Programming Guide and Block Programming Guide.
Sure, but there will be a delay as the AVAudioPlayer is allocated and prepared for playing. Can you predict what will play and when? If so, maybe you can load a couple of seconds before you need a particular mp3 to play.
An alternative, which may not work depending on your timing requirements, is the following:
- (void)prepareAudioPlayer {
static int index = -1;
index = index + 1;
if (index < [[self FileNames] count]) {
NSError *err = nil;
NSString *audioFilePath = #""; // Figure out how to get each file name here
NSURL *audioFileURL = [NSURL fileURLWithPath:audioFilePath];
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFileURL error:&err];
if (err) {
NSLog(#"Audio Player Error: %#", [err description]);
}
[player prepareToPlay];
// Add this player to the "AudioPlayers" array
[[self AudioPlayers] addObject:player];
// Recurse until all players are loaded
[self prepareAudioPlayer];
}
}
This solution requires properties of FileNames and AudioPlayers.
Once this is set up, you could do something like the following (probably in viewDidLoad):
// Make the file name array
[self setFileNames:[NSMutableArray array]];
// Initiate the audio player loading
[self performSelectorInBackground:#selector(prepareAudioPlayer) withObject:nil];
Later, when you need to play a particular file, you can find the index of the file name in the FileNames array and the call play on the AVAudioPlayer for that index in the AudioPlayers array.
This seems like maybe not the best way to do things, but it might work if you require it this way.
Here is the using method , If the sound is playing, current Time is the offset of the current playback position, measured in seconds from the start of the sound. If the sound is not playing, current Time is the offset of where playing starts upon calling the play method, measured in seconds from the start of the sound.
By setting this property you can seek to a specific point in a sound file or implement audio fast-forward and rewind functions.
The value of this property increases monotonically while an audio player is playing or paused.
If more than one audio player is connected to the audio output device, device time continues incrementing as long as at least one of the players is playing or paused.
If the audio output device has no connected audio players that are either playing or paused, device time reverts to 0.
Use this property to indicate “now” when calling the play AtTime: instance method. By configuring multiple audio players to play at a specified offset from deviceCurrent Time, you can perform precise synchronization—as described in the discussion for that method.To learn more visit..enter link description here

Controlling the pre-buffering of items in the avqueueplayer

Please can someone tell me if there is a way to selectively pre-buffer the avplayeritems in the AVQueuePlayer array rather than leaving it down to the AVQueuePlayer automatic way of only loading the next item in as the first item finishes playing. 
I'm loading a sequence of 4 short movie clips and I'd like to pre-cache them before telling the AVQueuePlayer to play the array. Is there actually a way of getting under the bonet of avqueueplayer and controlling the pre-buffering as desired?
Right now with its default lazy-loading behaviour, I'm getting some chugging in the playback, with the clips not even playing-out properly because the AVQueuePlayer is trying to loading-in the next clip while it's playing. I'm doing this on iPad deployed to the actual device and not with the simulator.
You can do this with the mpmovieplayer by calling [player prepareToPlay]; which basically manually initiates the loading of each video file you want and then you can check for the completeion of loading by watching for the mpmovieplayerLoadstateDidChange notification and testing the loadState value to see if it has fully loaded ,then telling the mpmovieplayer to play. How can you effectively do a similar thing with AVQueuePlayer?
Is this even possible or have I discovered one of the major drawbacks of the AVQueuePlayer?
Nice suggestion with the playerObserver Stephen, but what is needed is something like you need to be able to explicitly get individual items to load into memory and then tell the AVQueuePlayer 'do not play the first item in the array until ALL items in the array are loaded into memory' There currently seems to be no way to start even the second item in the array loading until the first one is coming to an end!
As a slightly separate issue, I've also noticed some weirdness in the AVQueuePlayer where, if you load two of the same source video file into the array (both referenced as two completely separate AVPlayerItems as you should do) when you play the video clips in the array all the way through, the first time the clip plays through ok, but when it comes to playing that same clip again (as a separate AVPlayerItem) it plays-through very quickly until a certain point in the video then finally starts playing at normal speed from there.
Has anyone else noticed this behaviour?
Apple Developer Support just confirmed to me that AVQueuePlayer does not buffer video items.
I have the same question. I wish AV Foundation has something like asset fully loaded notification.
Following code may partially solve the problem:
Float64 durationSeconds = CMTimeGetSeconds([<#An asset#> duration]);
CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 1);
NSArray *times = [NSArray arrayWithObjects:[NSValue valueWithCMTime:secondThird], nil];
self.playerObserver = [<#A player#> addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
}];

Resources