Next Song in MediaItemCollection? - ios

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];

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!

Receive NSRangeException error message "objectAtIndex beyond bounds"

In one class called LevelSelectViewController, I have this public property
#property (nonatomic, strong, getter=getLevelNumber) NSNumber *levelNumber;
which stores an int value based on a UIButton* touch selection using the method
- (IBAction)buttonPressedSoWhatNumber:(id)sender
{
UIButton *button = (UIButton *)sender;
int row = button.tag;
_levelNumber = [NSNumber numberWithInt:row];
}
When I put a breakpoint at the end of the method to see if my touch interaction triggers the correct result based on what I coded (when I press button 1, really), _levelNumber reads 0 (which it should). I also have a getter method written out for it.
Now, in this second class called GameViewController, I have a method setUpBoards which (should) obtain that value for *levelNumber. It looks like this:
- (void)setUpBoards {
LevelSelectViewController* level = [[LevelSelectViewController alloc] init];
[level getLevelNumber];
[self createLevelModel:(int)level];
}
In that same class, the method createLevelModel:(int)levelIndex uses that value to be passed to 5 initialization methods that access a Levels.plist file to load data for my game.
Basically, that number represents what level button I pressed and uses that number to load the correct level. In another manner, I have verified that those 5 initialization methods work along with loading data from my Levels.plist file.
Now, between the transition from LevelSelectViewController to GameViewController, I receive the NSRangeException error message:
'NSRangeException', reason: '-[__NSCFArray objectAtIndex:]: index (554166800) beyond bounds (1)'
even when pressing the 1 button (which should work considering I only have Item 0 in my plist typed out.......which, again, I verified worked using another manner).
TO ADD ON TO THIS. Here's another important method:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"cvCell";
CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
NSMutableArray *data = [self.dataArray objectAtIndex:indexPath.section];
NSString *cellData = [data objectAtIndex:indexPath.row];
[cell.buttonClick setTag:indexPath.row];
[cell.buttonClick addTarget:self action:#selector(buttonPressedSoWhatNumber:)
forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:cell.buttonClick];
return cell;
}
Any insight?
Here's the push controller method from LevelSelectViewController to GameViewController:
-(IBAction)buttonPressed:(id)sender {
GameViewController* obj = [[GameViewController alloc] initWithNibName:#"GameViewController" bundle:nil];
[self.navigationController pushViewController:obj animated:YES];
}
buttonPressed: is another method given to the UIButton*
You need a simple int property in LevelSelectViewController that you can use to store the level that has been selected:
#property int levelSelected;
The store the selected value in your button press handler:
- (IBAction)buttonPressedSoWhatNumber:(UIButton *)sender
{
self.levelSelected = sender.tag;
}
Then you can pass this to a corresponding int property on your GameViewController;
-(IBAction)buttonPressed:(id)sender {
GameViewController* obj = [[GameViewController alloc] initWithNibName:#"GameViewController" bundle:nil];
obj.level = self.selectedLevel
[self.navigationController pushViewController:obj animated:YES];
}
The problem is that you are casting a pointer to an int.
#property (nonatomic, strong, getter=getLevelNumber) NSNumber *levelNumber;
Defines a pointer to an object of type NSNumber.
[self createLevelModel:(int)level];
Is casting that NSNumber * to an int.
You also have another bug in that you are setting level as the view controller and calling getLevelNumber but not actually using the returned value. So here is what I would do. Firstly you don't need to define an NSNumber and don't need a custom getter. Just use this:
#property (nonatomic, assign) int levelNumber;
Then this becomes much simple:
- (void)setUpBoards {
LevelSelectViewController* levelSelectViewController = [[LevelSelectViewController alloc] init];
[self createLevelModel: levelSelectViewController.levelNumber]; // Always going to be zero at this point.
}

Switching views without dismissing View

I am developing a small music application in which first view comprises of all the List of songs in Tableview and when user taps on any one row (song) it takes them to Second view & Plays the song their(comprising of play/Pause Buttons). Now I go back in First View from Second View using back button to select another song. And their is one button in First View for just switching views(i.e it takes to current playing song)but it plays that same song from start but not from its currentplaying . Its similar to now Playing button in Stock Music app in iOS.
Im using storyboards & Segues
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
temp = indexPath.row;
[tableView deselectRowAtIndexPath:indexPath animated:NO];
[self performSegueWithIdentifier:#"Player" sender:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([[segue identifier] isEqualToString:#"Player"])
{
MPMediaItem *item = [itemCollections objectAtIndex:temp];
NSString *trackInfo = [[NSString alloc] initWithFormat:#"%#- %#",[item valueForProperty:MPMediaItemPropertyTitle],[item valueForProperty:MPMediaItemPropertyArtist]];
MPMediaItemArtwork *artwork = [item valueForProperty:MPMediaItemPropertyArtwork];
NSURL *tempURL = [item valueForProperty:MPMediaItemPropertyAssetURL];
playerController = [segue destinationViewController];
playerController.fileUrl1 = tempURL;
playerController.globalVar = [NSNumber numberWithInteger:temp];
playerController.labelTitleString = trackInfo;
playerController.img = [artwork imageWithSize:CGSizeMake(100, 100)];
}
}
You could go about this in many ways -- I would suggest implementing a singleton to handle the media playback. Rather than constantly retaining that view, this singleton manager (part of your model) should provide any controllers with the needed information about the current song, album, author, etc.
Ex. In your TableViewController:
- (void)viewDidLoad
{
[self setSong:[[PlaybackManager sharedInstance] currentSong]];
if (self.song) {
//Add navigation item
}
}

issue with unrecognized selector sent to instance

I'm using the master detail template.
Header file MasterViewController.h:
#import <UIKit/UIKit.h>
//(Imported both MasterViewController.h and DetailViewController.h in implementation file of MasterViewController.m)
#class DetailViewController;
#interface MasterViewController : UITableViewController
#property (strong, nonatomic) DetailViewController *detailViewController;
-(void)createFlowerData;
#end
Implementation file HeaderViewController.m:
#interface MasterViewController () {
NSMutableArray *_objects;
NSArray *_flowerData;
NSArray *_flowerSections;
}
#end
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(insertNewObject:)];
self.navigationItem.rightBarButtonItem = addButton;
self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
//invoking the method I implemented to give data to flowerData array
[self createFlowerData];
}
-(void)createFlowerData{
NSMutableArray *redFlowers;
NSMutableArray *blueFlowers;
redFlowers = [[NSMutableArray alloc] init];
blueFlowers = [[NSMutableArray alloc] init];
//create the 2 sections for the flowerSections array
_flowerSections = #[#"Red Flowers", #"Blue Flowers"];
//add the objects to the mutable array
//red flowers
[redFlowers addObject:#{#"name":#"Poppy",#"picture":#"Poppy.png",#"url":#"http://en.wikiepdia.org/wiki/Poppy"}];
[redFlowers addObject:#{#"name":#"Tulip",#"picture":#"Tulip.png",#"url":#"http://en.wikipedia.org/wiki/Tulip"}];
[redFlowers addObject:#{#"name":#"Gerbera",#"picture":#"Gerbera.png",#"url":#"http://en.wikiepdia.org/wiki/Gerbera"}];
//blue flowers
[blueFlowers addObject:#{#"name":#"Phlox",#"picture":#"Phlox.png",#"url":#"http:en.wikipedia.org/wiki/Gerbera"}];
[blueFlowers addObject:#{#"name":#"Pin Cushion Flower",#"picture":#"Pincushion flower.png",#"url":#"http://en.wikipedia.org/wiki/Scabious"}];
[blueFlowers addObject:#{#"name":#"Iris",#"picture":#"Iris.png",#"url":#"http://en.wikipedia.org/wiki/Iris_(plant)"}];
_flowerData = #[redFlowers, blueFlowers];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [_flowerSections count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//find the number of row elements in a given section of the flower Data array
return [_flowerData[section] count];
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
//index 0 is the red flower
//index 1 is the blue flower
return _flowerSections[section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"flowerCell"];
cell.textLabel.text = _flowerData[indexPath.section][indexPath.row][#"name"];
cell.detailTextLabel.text = _flowerData[indexPath.section][indexPath.row][#"url"];
cell.imageView.image = _flowerData[indexPath.section][indexPath.row][#"picture"];
return cell;
}
I then get the following when I build the application on the ios simulator(iPad):
2013-09-01 23:49:40.015 flowerDetail2[2394:c07] -[__NSCFConstantString _isResizable]: unrecognized selector sent to instance 0x6af4
2013-09-01 23:49:40.017 flowerDetail2[2394:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFConstantString _isResizable]: unrecognized selector sent to instance 0x6af4'
*** First throw call stack:
(0x1c95012 0x10d2e7e 0x1d204bd 0x1c84bbc 0x1c8494e 0x4ca7ab 0x121ae9 0x2c1f 0xd18fb 0xd19cf 0xba1bb 0xcab4b 0x672dd 0x10e66b0 0x2291fc0 0x228633c 0x2291eaf 0x1062bd 0x4eb56 0x4d66f 0x4d589 0x4c7e4 0x4c61e 0x4d3d9 0x502d2 0xfa99c 0x47574 0x4776f 0x47905 0x8dceab6 0x50917 0x1496c 0x1594b 0x26cb5 0x27beb 0x19698 0x1bf0df9 0x1bf0ad0 0x1c0abf5 0x1c0a962 0x1c3bbb6 0x1c3af44 0x1c3ae1b 0x1517a 0x16ffc 0x1bed 0x1b15)
libc++abi.dylib: terminate called throwing an exception
(lldb)
(Please note that I did not include everything, just the parts I thought were important)
This has been driving me crazy the whole day, I've checked multiple times, rewritten the whole thing and still the same result, I can't even get the cells to display. I googled, and I found something like it means that I'm sending a message to a method that doesn't know what to do with it, but I'm sure it's right? Could somebody please help me debug this!
cell.imageView.image = _flowerData[indexPath.section][indexPath.row][#"picture"];
this line cell.imageView.image expect UIImage type
[redFlowers addObject:#{#"name":#"Gerbera",#"picture":#"Gerbera.png",#"url":#"http://en.wikiepdia.org/wiki/Gerbera"}];
but you give it a NSString here, which of cause will make runtime error.
So should be like this
cell.imageView.image = [UIImage imageNamed:_flowerData[indexPath.section][indexPath.row][#"picture"]];
If you are NOT using ARC: You need to retain each of your dictionaries. Like this redFlowers = [[[NSMutableArray alloc] init] retain]; do that for each of your arrays. Also, NSLog(#"%#", redFlowers) before the line of code where your app crashes and post the output. Replace redFlowers with whatever the dictionary being called next is.
From what i am getting, what you need to do is first make NSDictionary objects and than add those objects to NSMutableArray.
NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
[dic setValue:#"Poppy" forKey:#"name"];
[dic setValue:#"Poppy.png" forKey:#"picture"];
[dic setValue:#"http://en.wikiepdia.org/wiki/Poppy" forKey:#"url"];
[redFlowers addObject:dic];
[dic release]; // Dont use this in case of ARC
Repeat this for all three or number of objects you need to add. Hope this helps.

iOS - get playlist from didPickMediaItems

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.

Resources