Switching views without dismissing View - ios

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
}
}

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!

AVAudioPlayer is not working in another viewcontroller

I have passed button tag to another Viewcontroller.
It's passed but when I'm calling any another method like play audio play on that selected button, player not working...
I have tried below code :
It passes id of button clicked to the other view. Where I build player and and based on button id I want to play a poem on other view and control functions like volume control, progressbar, duration, play, push, next, etc...
While I'm giving a method to a particular button id to play poem it is not working...
This below code for play a poem in another view where I build player.
`
- (IBAction)btnpoemclicked:(id)sender {
btnPressed = [NSString stringWithFormat:#"%li", (long)[sender tag]];
NSLog(#"selected poem -%#",btnPressed);
PlayerController *pl=[[PlayerController alloc] init];
pl.btnPressed=btnPressed;
[pl setBtnPressed:pl.btnPressed];
[self performSegueWithIdentifier:#"player" sender:sender];
}
You should pass values in prepareForSegue when using Segues.
Update your code like
- (IBAction)btnPoemClicked:(id)sender {
btnPressed = [NSString stringWithFormat:#"%li", (long)[sender tag]];
[self performSegueWithIdentifier:#"player" sender:self];
}
And then In prepareForSegue pass the values to destinationViewController like this
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"player"]) {
PlayerController *playerController = (PlayerController *)segue.destinationViewController;
playerController.btnPressed = btnPressed;
}
}
Implement your button action like,
- (IBAction)buttonPoemClicked:(id)sender {
[self performSegueWithIdentifier:#"Player" sender:sender];
}
Pass your value by implementing prepareForSegue,
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Player"]) {
PlayerController *playerController = (PlayerController *)segue.destinationViewController;
playerController.pressedButtonTag = [NSString stringWithFormat:#"%li", (long)[sender tag]];
}
}
Additional Note : Try to make your variable/class names little more understandable or try to follow apple recommended naming conventions.

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

How can I alternate view controller and not lose data

I have two UIViewControllers, one is a UIPickerViewController, the Other a UITableViewController. Ideally the Picker should get a request from the user to add x amount of some item to the tableView. The Picker gets user inputs and assigns them to variables val1, val2, val3, where val1 is the number of items (number of rows) and val2 is the name or label for the item.
PickerViewController.m
- (IBAction)add:(id)sender
{
TableViewController *tvc = [[TableViewController alloc] init];
[tvc setValues:self.val1 :self.val2 :self.val3];
[self presentViewController:tvc animated:YES completion:nil];
}
TableViewController.m
-(void)setValues:(NSString *)newVal1 :(NSString *)newVal2 :(NSString *)newVal3
{
self.val1 = newVal1;
self.val2 = newVal2;
self.val3 = newVal3;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"UITableViewCell"];
// This is just a header which holds my "Add" button
UIView *header = self.headerView;
[self.tableView setTableHeaderView:header];
[self addNew:self.val1 :self.val2 :self.val3];
}
- (void)addNew:(NSString *)newVal1 :(NSString *)newVal2 :(NSString *)newVal3
{
if(!self.numberOfRows){
NSLog(#"Initially no of rows = %d", self.numberOfRows);
self.numberOfRows = [self.val1 intValue];
NSLog(#"Then no of rows = %d", self.numberOfRows);
}
else
{
self.numberOfRows = self.numberOfRows + [newVal1 intValue];
NSLog(#"New no rows = %d", self.numberOfRows);
}
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.numberOfRows inSection:0];
// Only run when called again .. not initially
if(self.run != 0){
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPath]withRowAnimation:UITableViewRowAnimationBottom];
self.run ++;
[self.tableView endUpdates];
}
}
// "ADD" button which should go back to the picker and get new items to add to the table
- (IBAction)testAdd:(id)sender
{
PickerViewController *pvc = [[PickerViewController alloc] init];
[self presentViewController:pvc animated:YES completion:nil];
}
Now, I realize every time I call the next view controller I am creating a new instance of it, but I don't know how else to do it, I figure this is the main problem. As of right now, I expect when I leave the tableview for the picker view and return the console should log "New no of rows = x" but that doesn't happen.
I know val3 isn't used and my addNew: may not be the best, but I just need it to handle the basic logging mentioned above and I should be able to take it from there.
Been stuck on this for days
Create a property for TableViewController, and only create it the first time you present it,
- (IBAction)add:(id)sender {
if (! self.tvc) {
self.tvc = [[TableViewController alloc] init];
}
[self.tvc setValues:self.val1 :self.val2 :self.val3];
[self presentViewController:self.tvc animated:YES completion:nil];
}
It's not entirely clear from you question, whether it's this presentation or the one you have in the table view class that you're talking about. It also looks like you're doing something wrong in terms of presentation -- you're presenting the picker view from the table view controller, and also presenting the table view controller from the picker. That's not correct, you should present which ever controller you want to appear second, and that controller should use dismissViewControllerAnimated to go back, not present another controller.
In testAdd you don't need to create a new instance and present it. If you want to go back to the presentingViewController, just use dismissViewControllerAnimated .
And you will go one controller up in the stack.

Passing a NSString to another view with Storyboard

Alright,
I have read some Q&A's on this, but I need to be able to relate it to my code and I am not being successful, I hope someone is able help me:-)
I have a UITableViewController that is populated with a NSMUtableArray.
I would like to link to each entry and then have that URL display in a DetailViewController which has UIWebView in it.
Now I had the idea of passing a string along from one VC to the next, but that does not seem to work.
This is the code I have in the First TVC that is relevant:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int currentindex = [indexPath row];
switch (currentindex) {
case 0:
_urlAddress = #"http://localhost:8888/thePalmsMenu/breakfast_menu.php";
break;
case 1:
_urlAddress = #"http://localhost:8888/thePalmsMenu/lunch_menu.php";
break;
default:
break;
}
webVC *sView = [[webVC alloc] init];
sView.urlAddress = _urlAddress;
[self.navigationController pushViewController:sView animated:YES];
}
And then I have this in the Second View Controller which has the UIWebView in it.
-(void)viewDidLoad
{
_vc.urlAddress = _urlAddress;
NSURL *url = [NSURL URLWithString:_urlAddress];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[_webView loadRequest:requestObj];
}
Now also at the moment the code show I am doing a push navigation, but that is not working - if I embed the first view then I get an error saying I can't push that view twice, so they must be related.
this is the error message for that:
2012-06-08 22:23:28.965 menu_test[68491:f803] nested push animation can result in corrupted navigation bar
2012-06-08 22:23:29.447 menu_test[68491:f803] Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
2012-06-08 22:23:29.449 menu_test[68491:f803] Unbalanced calls to begin/end appearance transitions for .
Now even though these seem like 2 questions, they are sure to be related.
Also I have tried playing with NSUserDefaults, but that is not working as smooth, because the link needs to be set before I can use it, rather than directly passing it on.
Any help would be great:-)
What is happen is that you are really pushing the view controller twice, first time is from the storyboard push sequence, and the second time you do it from code,
You have 2 solutions
I will write down the easiest of them (if you need the second too, please ask)
you will need to pass the data from the prepareForSegue method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"TheIdentifierOfYourDetailView"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
int currentindex = [indexPath row];
switch (currentindex) {
case 0:
_urlAddress = #"http://localhost:8888/thePalmsMenu/breakfast_menu.php";
break;
case 1:
_urlAddress = #"http://localhost:8888/thePalmsMenu/lunch_menu.php";
break;
default:
break;
}
[[segue destinationViewController] setUrlAddress:_urlAddress];
}
}

Resources