iOS - get playlist from didPickMediaItems - ios

I want to know when a user selects a song using media picker, what playlist they picked the song from. For some reason, the playlist doesn't seem to exist.
Basically when a user goes to a playlist in media picker let's call it "gym songs" and they select a song, mediaPicker:didPickMediaItems: function returns the song details but doesn't say which playlist the user selected the sone from.
So what I want to do is play that song and continue playing the other songs in that playlist. But without the playlist info, it starts playing that song and goes on playing the rest of the songs in "all songs" list.
This is my code:
(void) mediaPicker: (MPMediaPickerController *) mediaPicker didPickMediaItems: (MPMediaItemCollection *) mediaItemCollection
{
MPMediaItem *selectedSong = [[mediaItemCollection items] objectAtIndex:0];
MPMediaQuery *songQuery = [MPMediaQuery songsQuery];
[musicPlayer setQueueWithQuery:songQuery];
for (MPMediaItem *oneSong in [songQuery items]) {
if ([selectedSong isEqual:oneSong]) {
musicPlayer.nowPlayingItem = oneSong;
[musicPlayer play];
// Exit the loop
break;
}
}
[self dismissModalViewControllerAnimated: YES]; }

This data is not made available. Your only option is to do all this yourself in code. Put up a tableview and populate it with all the playlists in the library. When the user chooses a playlist put up another tableview with all the tracks in that playlist and allow the user to choose one.

Related

Avplayer item pre buffering while scrolling on UITableView

I have created an UITableView with custom UITableViewCells. UITableView consists of images and videos that load via internet. While user is scrolling in one of the UITableViewCell i load AVPlayer to play a hls video. I set URL in hls format to the avplayer item in order to play the item.
self.playerController = [[AVPlayerViewController alloc] init];
NSURL *videoURL = #"https://playeritemvideourl";
self.player = [AVPlayer playerWithURL:url];
self.playerController.player = self.player;
[self.player play];
The video plays but there is a delay of about 3 to 5 seconds from the moment [self.player play] is triggered. How do i pre buffer the video to the currentitem of avplayer so when i scroll to the specific UITableViewCell the video starts playing instantly? I looked at preferredForwardBufferDuration property on AVPlayerItem but does not seem to make any difference. Any help or suggestions appreciated!
AVPlayer begins streaming m3u8 when it is instantiated. (I noticed this by monitoring the Network graph in the Debug navigator in Xcode. I instantiated an AVPlayer without calling -[play] and the network was under load.) Instead of loading the AVPlayer once the cell becomes visible, instantiate the AVPlayer before the cell is visible and play the content when it becomes visible.
This can be implemented by first having some data structure hold the AVPlayer items. I would recommend a NSMutableDictionary, as we can set the key to the video's URL we want and the object can be the AVPlayer already loaded with the URL. There are two ways to populate the structure. You could load it all at once in a method like -viewDidLoad or load items in dynamically in -scrollViewDidScroll: to determine if we are close to a cell with a video. I would use the former if there is not a lot of content and the user is almost guaranteed to watch all the videos. I would use the latter if we have a lot of content to load or if the user might not watch all the videos. Here is an example with a UITableViewController.
MyTableViewController.h
#import <UIKit/UIKit.h>
#import <AVKit/AVKit.h>
#interface MyTableViewController : UITableViewController
{
NSMutableDictionary *mediaPlayers; // stores the media players, key = NSString with URL, value = AVPlayer
// you don't need mediaKeyIndexPaths or mediaKeyURLs if you are taking the load-all-at-once approach
NSMutableSet *mediaKeyIndexPaths; // set that stores the key NSIndexPaths that trigger loading a media player
NSDictionary *mediaKeyURLs; // dictionary that maps a key NSIndexPath to a URL (NSString), key = NSIndexPath, value = NSString
}
#property (strong, nonatomic) AVPlayerViewController *playerController;
#end
MyTableViewController.m
#import "MyTableViewController.h"
#implementation MyTableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// if you want to load all items at once, you can forget mediaKeyIndexPaths and mediaKeyURLs
// and instead just load all of your media here
mediaPlayers = [[NSMutableDictionary alloc] init];
// if taking the load-all-at-once approach, load mediaPlayers like this
// NSString *videoURLString = #"https://playeritemvideourl";
// AVPlayer *player = [AVPlayer playerWithURL:videoURLString];
// [mediaPlayers setObject:player forKey:#"https://playeritemvideourl"];
// lets say that the cell with the media in it is defined here
NSIndexPath *dummyMediaIndexPath = [NSIndexPath indexPathForRow:40 inSection:0];
// calculate an index path that, when visible, will trigger loading the media at dummyMediaIndexPath
NSIndexPath *dummyKeyIndexPath = [NSIndexPath indexPathForRow:dummyMediaIndexPath.row-10
inSection:dummyMediaIndexPath.section];
// add the key index path to the set of key index paths
mediaKeyIndexPaths = [[NSMutableSet alloc] initWithObjects:dummyKeyIndexPath, nil];
// define mediaKeyURLs mapping the key dummyKeyIndexPath to the value of the URL string we want
mediaKeyURLs = [[NSDictionary alloc] initWithObjectsAndKeys:
#"https://playeritemvideourl", dummyKeyIndexPath,
nil];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 100;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TextCell" forIndexPath:indexPath];
// this is the row of dummyMediaIndexPath defined in -viewDidLoad
if (indexPath.row == 40)
{
self.playerController = [[AVPlayerViewController alloc] init];
NSString *videoURLString = #"https://playeritemvideourl";
AVPlayer *player = [mediaPlayers objectForKey:videoURLString]; // load player with URL
if (!player)
{
player = [AVPlayer playerWithURL:[NSURL URLWithString:videoURLString]];
NSLog(#"Video with URL: %# was not preloaded. Loading now.", videoURLString);
}
self.playerController.player = player;
// [player play];
// present your playerController here
[cell setText:#"Player cell"];
}
else
{
[cell setText:[NSString stringWithFormat:#"Cell #%li", (long)indexPath.row]];
}
return cell;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// get visible rows
NSArray *visibleRows = [self.tableView indexPathsForVisibleRows];
// traverse through rows
for (NSIndexPath *i in visibleRows)
{
// we use an NSSet to quickly determine if i is contained in mediaKeyIndexPaths
if ([mediaKeyIndexPaths containsObject:i])
{
[mediaKeyIndexPaths removeObject:i]; // we only need to load a player once
NSString *videoURLString = [mediaKeyURLs objectForKey:i];
NSLog(#"Preloading URL: %#", videoURLString); // for information purposes only
AVPlayer *player = [AVPlayer playerWithURL:[NSURL URLWithString:videoURLString]];
[mediaPlayers setObject:player forKey:videoURLString];
}
}
}
#end
This is about as specific as I can make with the amount of code and information you provided. Hope this helps!

MediaPicker-based iOS app Not Playing All Songs

I've been looking at this tutorial for information on how to locate songs in a user's music library on their iPhone. Everything is working as it's supposed to, except that songs that are purchased but not on the user's phone do not play.
Does anyone know a way to fix this, or know how to test to see if the song is purchased vs installed and then alert the user?
Below is the code I'm using from the tutorial that selects and plays the songs:
/*
* This method is called when the user presses button. It displays a media picker
* screen to the user configured to show only audio files.
*/
- (IBAction)pickSong:(id)sender
{
MPMediaPickerController *picker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeAnyAudio];
[picker setDelegate:self];
[picker setAllowsPickingMultipleItems: NO];
[self presentViewController:picker animated:YES completion:NULL];
}
#pragma mark - Media Picker Delegate
/*
* This method is called when the user chooses something from the media picker screen. It dismisses the media picker screen
* and plays the selected song.
*/
- (void)mediaPicker:(MPMediaPickerController *) mediaPicker didPickMediaItems:(MPMediaItemCollection *) collection {
// remove the media picker screen
[self dismissViewControllerAnimated:YES completion:NULL];
// grab the first selection (media picker is capable of returning more than one selected item,
// but this app only deals with one song at a time)
MPMediaItem *item = [[collection items] objectAtIndex:0];
// display title of song in a navigation bar
//NSString *title = [item valueForProperty:MPMediaItemPropertyTitle];
//[_navBar.topItem setTitle:title];
// get a URL reference to the selected item
NSURL *url = [item valueForProperty:MPMediaItemPropertyAssetURL];
// pass the URL to playURL:, defined earlier in this file
[self playURL:url];
}

Next Song in MediaItemCollection?

How do I find out the name of the next item to be played in an MPMediaItem collection? I would prefer to store this as an MPMediaItem.
I have a songsViewController that has a tableView of all the songs. This is my code for didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MPMediaItem *selectedItem;
selectedItem = [[songs objectAtIndex:indexPath.row] representativeItem];
MPMusicPlayerController *musicPlayer = [MPMusicPlayerController iPodMusicPlayer];
[musicPlayer setQueueWithItemCollection:[MPMediaItemCollection collectionWithItems:[songsQuery items]]];
[musicPlayer setNowPlayingItem:selectedItem];
nowPlayingViewController *nowPlaying = [[rightSideMenuViewController alloc]
initWithNibName:#"nowPlayingViewController" bundle:nil];
[self presentViewController:nowPlaying animated:YES completion:nil];
[musicPlayer play];
}
I have a UILabel on my nowPlayingViewController which pops up when the user has selected a song. I would like to store the title of the next item in the MediaItemCollection/queue to be in that UILabel - so it is a preview of what the next song is.
Any help would be much appreciated, thanks! :)
Keep your list (since your can't access at musicplayer queue).
#property (nonatomic, strong) NSArray *playlist;
When you do:
[musicPlayer setQueueWithItemCollection:[MPMediaItemCollection collectionWithItems:[songsQuery items]]];
Add:
playlist = [songsQuery items];
To fetch your previous/next:
-(MPMediaItem *)nextItem
{
int currentIndex = [musicPlayer indexOfNowPlayingItem];
MPMediaItem *nextItem = [playlist objectAtIndex:currentIndex+1];
return nextItem
}
-(MPMediaItem *)previousItem
{
int currentIndex = [musicPlayer indexOfNowPlayingItem];
MPMediaItem *previousItem = [playlist objectAtIndex:currentIndex-1];
return previousItem;
}
Important note:
I didn't check if the current item was the first/last (according if you want previous/next) item in playlist. So be careful with bounds of the NSArray, or you'll get a:
NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index Z beyond bounds [X .. Y]
So does previousItem "exists"? Does nextItem "exists"?
You may also have to look at:
#property (nonatomic) MPMusicRepeatMode repeatMode
In case that the nextItem may be the first item, or the previous item the last one.
MPMusicPlayerController provides the following instance methods to quickly skip to your next or previous song:
[musicPlayer skipToNextItem];
[musicPlayer skipToPreviousItem];

In AVAudioplayer multiple songs play at once.how to fix single song play at a time

I am trying to create AVAudioplayer programatically.The audio player successfully playing.But i have some issues.I am displaying 12 audio items in UITableview.If i click first item that should navigate audio player view controller.And i click the play button the song will play.If i click back button the song play continuously.But if i click again same item the Audio view controller the view will display initial state like progress bar should not move and current time and song duration are empty.but song play continuously.
And another issue is there. If i click first item that corresponding song will play.I should not click any pass button.i click back button and click second item.if i click second item the audio view controller will display.I click play button the song display.In background first song play and second song also play.This is my second issue.how to solve these two issues.please help me any body.
-(void)playOrPauseButtonPressed:(id)sender
{
if(playing==NO)
{
[playButton setBackgroundImage:[UIImage imageNamed:#"Pause.png"] forState:UIControlStateNormal];
// Here Pause.png is a image showing Pause Button.
NSError *err=nil;
AVAudioSession *audioSession=[AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
NSLog(#"%# %d",urlsArray,selectedIndex);
NSString *sourcePath=[urlsArray objectAtIndex:selectedIndex];
NSData *objectData=[NSData dataWithContentsOfURL:[NSURL URLWithString:sourcePath]];
audioPlayer = [[AVAudioPlayer alloc] initWithData:objectData error:&err];
audioPlayer.numberOfLoops = 0;
[audioPlayer prepareToPlay];
audioPlayer.delegate=self;
[audioPlayer play];
playing=YES;
}
else if (playing==YES)
{
[playButton setBackgroundImage:[UIImage imageNamed:#"play.png"] forState:UIControlStateNormal];
[audioPlayer pause];
playing=NO;
}
if (self.audioPlayer)
{
[self updateViewForPlayerInfo];
[self updateViewForPlayerState];
[self.audioPlayer setDelegate:self];
}
}
-(void)updateViewForPlayerInfo
{
self.songDuration.text = [NSString stringWithFormat:#"%d:%02d", (int)self.audioPlayer.duration / 60, (int)self.audioPlayer.duration % 60, nil];
NSLog(#"%f", self.audioPlayer.duration);
self.progressBar.maximumValue = self.audioPlayer.duration;
self.volumeSlider.value = self.audioPlayer.volume;
}
-(void)updateViewForPlayerState
{
[self updateCurrentTime];
if (self.updatedTimer)
{
[self.updatedTimer invalidate];
}
if (self.audioPlayer.playing)
{
self.updatedTimer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(updateCurrentTime) userInfo:self.audioPlayer repeats:YES];
}
}
-(void)updateCurrentTime
{
//NSLog(#"self.audioPlayer.currentTime = %f", self.audioPlayer.currentTime);
self.currentTime.text = [NSString stringWithFormat:#"%d:%02d", (int)self.audioPlayer.currentTime / 60, (int)self.audioPlayer.currentTime % 60, nil];
self.progressBar.value = self.audioPlayer.currentTime;
}
-(void)volumeSliderMoved:(UISlider *)sender
{
self.audioPlayer.volume=[sender value];
}
I think the problem is here
audioPlayer = [[AVAudioPlayer alloc] initWithData:objectData error:&err];
Each time you are creating the audioPlayer object. So my suggestion is check for audioPlayer before creating the object like this
if(audioPlayer)
{
audioPlayer.delegate=nil;
audioPlayer=nil;
}
Then create the object.
In short what's happening is the following:
you click on the table cell
you create an audio player view controller
You play the audio - your audio player itself is retained for some reason (Unclear why - are you using ARC?)
You click the back button - the Audio player view controller is released
you click the table cell again - and create a new audio player view controller with it's own player!
Basically - if you want the previous song continue playing even when your exiting the Audio player view controller - and to have only a single player for the audio - I would hold it in a singleton class that will manage the player , or as a property of the app delegate.
This way - when you enter the Audio Player view controller - It will check the following:
If a player doesn't exist - create it and assign it the item - ready to play
If a player exists but it's the same item - just update the controls to show it's current playing status.
If a player exists but it's another item - either don't update the controls - and when the user hits play - replace the item in the player and start updating the controls - or stop the song as you display the Audio player view controller. (Up to you - I don't know the design of your app obviously)
In working with AVAudioPlayer, I use a property to keep up with the player. Based on looking at your code it isn't clear to me that you are keeping the player around, or that you aren't instantiating a new instance every time. Based on your description, it sounds like you ARE instantiating a new instance - don't think you want that.
So in your .h file, something like this:
#property (nonatomic, strong) AVAudioPlayer *myAVAudioPlayer;
and then the corresponding #synthesize in your .m file.
When you want to move from one song to the next, simply stop playing current - then reload a new URL using an init for your self.myAVAudioPlayer property. You MUST then call the prepareToPlay method again for the AVAudioPlayer option.
You probably will make it easier on yourself by not managing the variables regarding playing or not (it looked like you were doing that from your post). Use your property and check by accessing the setter/getter for the property for state of the player:
if(!self.myAVAudioPlayer)
{
// allocate and initialize using the code you have
[self.myAVAudioPlayer prepareToPlay];
} else
if ([self.myAVAudioPlayer isPlaying])
{
//do whatever you need for isPlaying - looks like pause
} else
if (![self.myAVAudioPlayer isPlaying])
{
// do whatever you need to do if the player isn't playing when the button is pressed
// at this point you will likely just re-init with a new URL and reset other items
// don't forget to call -prepareToPlay again
}
Here is a link to another approach which takes advantage of AVPlayerItem --> click https://stackoverflow.com/a/22254588/3563814

How can I use a media picker for settings?

I'm a new developer and I'm getting to grips with audio in iOS.
This will be tricky to explain, but I'll give it a go. I'm using a media picker so that the user can select a song from their library. I don't want there to be a queue or for it to play immediately. Instead, I want it to appear in a tableview and when the user selects that song in the table view, it will play that when, in another view, a button is pressed.
So basically, I want the user to be able to select a song from their library and it is added to list. They can then selected a song from that list and It will play that when a button is pressed.
If it helps, this is the code I'm using for the media picker and to play audio (and they're not actually connected to each other):
Media Picker:
MPMediaPickerController *mediaPicker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeAnyAudio];
[mediaPicker setDelegate:self];
[mediaPicker setAllowsPickingMultipleItems:NO];
mediaPicker.prompt = NSLocalizedString(#"text1", "text2");
[self presentViewController:mediaPicker animated:YES completion:nil];
Playing Audio:
if (self.panicButtonPressed == NO) {
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:#"Alarm 1"
ofType:#"mp3"]];
alarmSound = [[AVAudioPlayer alloc]
initWithContentsOfURL:url
error:nil];
[alarmSound play];
self.panicButtonPressed = YES;
}
Any help would be greatly appreciated.
Once you get the file URL you can use it to access the files metadata (title,artist,etc.). You can then populate your table with this data and the URL.

Resources