8

Well, this is my very first post ever -- between this website, personal blogs, and iPhoneDevSDK forums, I have always been able to figure things out on my own. I've been developing for iOS for about five months and have never asked for help outside of Google. But this time, I'm stuck.

I have successfully implemented Matt Galagher's AudioStream class (had to remove link because SO assumes my post is spam) into my app, and I'm happy to report that it works beautifully. I'm using it in one view controller, which is actually a child to a parent view in a TabBar app. In fact, my implementation isn't much different than that in Matt's example -- I only changed some UI elements.

Up to this point, I'd been using viewDidDisappear to stop the streamer when the user switches to another tab. But recently I decided it would be slick to let the audio stream play as long as the app is running, no matter what view is on top. That was a very simple change, and now my audio stream continues to play no matter where I am in the app.

Here's how the view controller works:

  1. I have a Play button and a Stop button laying in the same spot, with the Stop button hidden.
  2. When the user presses the Play button, the Play button hides, revealing a UIActivityIndicatorView (`[streamer isWaiting]`)
  3. When the stream begins playing (`[streamer isPlaying]`), the `UIActivityIndicatorView` is hidden, and a Stop button appears
  4. Also, while `[streamer isPlaying]`, I'm displaying an elapsed time (mm:ss) inside the Navigation Prompt, which updates each second

All of the above work perfectly every time. If I leave the view (either by popping to the parent view, or by navigating to another tab), the audio will stay playing, which is exactly what I want. But when I return to the view, the UI appears as though I haven't started it yet (Play button visible, Navigation Prompt is hidden). If I press the play button, then I hear a second stream playing simultaneously with the first.

The updateProgress seems to have stopped (as self.navigationItem.prompt is no longer visible), and I'm guessing the playbackStateChanged is "dead" (not sure how to describe it).

I've spent hours sifting through the AudioStreamer class trying to figure out how to maintain control of the stream, but I'm exhausted. I'm hoping someone might be able to tell me what I'm missing.

As I said earlier, my view controller is nearly identical to the example (see hyperlink above, as SO still assumes I'm a spammer), with only a few UI-related changes.

I guess the short question would be this: has anyone been able to implement the AudioSTreamer class, pop its view, then come back and be able to view elapsed time or stop the stream?

EDIT: below is my view controller implementing AudioStreamer

.h

#import <UIKit/UIKit.h>
#import <QuartzCore/CoreAnimation.h>
#import <MediaPlayer/MediaPlayer.h>
#import <CFNetwork/CFNetwork.h>

@class AudioStreamer;


@interface RadioViewController : UIViewController
{
    IBOutlet UIButton *playButton;
    IBOutlet UIButton *stopButton;
    IBOutlet UIActivityIndicatorView *waitIndicator;

    AudioStreamer *streamer;
    NSTimer *progressUpdateTimer;

    BOOL shouldAutoStop;
}

- (IBAction)play;
- (IBAction)stop;

- (void)createStreamer;
- (void)destroyStreamer;
- (void)updateProgress:(NSTimer *)aNotification;
- (void)checkWiFi;

@end

.m

#import "AudioStreamer.h"
#import "ServerCheck.h"
#import "RadioViewController.h"

@implementation RadioViewController


- (void)viewDidLoad
{
    [[self navigationItem] setTitle:@"WXK33 162.550"];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkWiFi) name:UIApplicationDidBecomeActiveNotification object:nil];

    [super viewDidLoad];
}

- (void)viewDidAppear:(BOOL)animated
{   
    [self performSelector:@selector(checkWiFi)];

    [super viewDidAppear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
    //[self performSelector:@selector(stop)];

    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];

    [super viewDidDisappear:animated];
}


- (void)createStreamer
{
    if ([ServerCheck serverReachable:@"audiostream.wunderground.com"])
    {
        if (streamer)
        {
            return;
        }

        [self destroyStreamer];

        NSURL *url = [NSURL URLWithString:@"http://audiostream.wunderground.com/MaffooClock/San_Angelo.mp3"];
        streamer = [[AudioStreamer alloc] initWithURL:url];

        progressUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateProgress:) userInfo:nil repeats:YES];

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackStateChanged:) name:ASStatusChangedNotification object:streamer];

    }
    else
    {
        [[self navigationController] popViewControllerAnimated:YES];
    }
}

- (IBAction)play
{
    [self createStreamer];
    [waitIndicator startAnimating];
    [streamer start];

    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}

- (IBAction)stop
{
    self.navigationItem.prompt = nil;
    [streamer stop];

    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}


- (void)playbackStateChanged:(NSNotification *)aNotification
{
    if ([streamer isWaiting])
    {
        [playButton setHidden:YES];
        [stopButton setHidden:YES];
        [waitIndicator startAnimating];
    }
    else if ([streamer isPlaying])
    {   
        [playButton setHidden:YES];
        [stopButton setHidden:NO];
        [waitIndicator stopAnimating];

        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
    }
    else if ([streamer isIdle])
    {
        [self destroyStreamer];

        [playButton setHidden:NO];
        [stopButton setHidden:YES];
        [waitIndicator stopAnimating];
    }
}


- (void)updateProgress:(NSTimer *)updatedTimer
{
    if (streamer.bitRate > 0.0)
    {
        double progress = streamer.progress;

        double minutes = floor(progress/60);
        double seconds = trunc(progress - (minutes * 60));

        self.navigationItem.prompt = [NSString stringWithFormat:@"Elapsed: %02.0f:%02.0f",minutes,seconds];;

        if (shouldAutoStop && progress > 600)
            [self performSelector:@selector(stop)];
    }
    else
    {
        self.navigationItem.prompt = nil;
    }
}

- (void) checkWiFi
{
    if (![ServerCheck wifiAvailable])
    {
        LogInfo(@"No Wi-Fi");

        NSString * messageTitle = @"Notice";
        NSString * messageText = @"It appears that you do not have an active Wi-Fi connection.  Listening to streaming audio via cellular data may incurr additional data charges.  Streaming will automatically stop after 10 minutes.";

        UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:messageTitle message:messageText delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];

        [errorAlert show];
        [errorAlert release];

        shouldAutoStop = YES;
    }
    else
        shouldAutoStop = NO;    
}


- (void)destroyStreamer
{
    if (streamer)
    {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:ASStatusChangedNotification object:streamer];
        [progressUpdateTimer invalidate];
        progressUpdateTimer = nil;

        [streamer stop];
        [streamer release], streamer = nil;
    }
}


- (void)dealloc
{
    [self destroyStreamer];
    if (progressUpdateTimer)
    {
        [progressUpdateTimer invalidate], progressUpdateTimer = nil;
    }
    [super dealloc];
}

@end
Matthew Clark
  • 1,885
  • 21
  • 32
  • We'd need to see more of your actual code (you can just cut and paste the relevant code snips here and format them as code). For instance, it sounds to me like you are pushing a new instance of this view on top of the old one? Is that the case? – Jason Coco Jan 20 '11 at 04:27
  • Yeah, I had my code in the post while I was writing it, but it's so similar to the example, that I thought it would be sufficient to just mention that. I'll post it anyway, though... – Matthew Clark Jan 20 '11 at 04:33
  • Right, this is the code that runs it, but when somebody presses on that tab, do you do something like RadioViewController* rvc = [[RadioViewController alloc] initWithNibName:nil bundle:nil]; [[self navigationController] pushNewViewController:rvc animated:YES]; [rvc release]; ?? – Jason Coco Jan 20 '11 at 04:50
  • The tab shows a navigation controller, with a table view being used as a menu. When a cell is tapped, I use a switch to decide which view controller to push onto the stack. I've created a `UIViewController * viewController;` in the .h, and then inside the switch I assign whichever view controller I want, and after the switch, I call `[self.navigationController pushViewController:viewController animated:YES];` I just noticed I never release `viewController`, but that's not an issue here. – Matthew Clark Jan 20 '11 at 14:19
  • I think I'm onto something... as a test, I alloc'd the view controller in the viewDidLoad of the parent view, and now when I pop from the child view (streamer) and then push back, everything is still there! Looks like I need to re-evaluate how I'm allocating view controllers. – Matthew Clark Jan 20 '11 at 14:51

1 Answers1

0

Turns out that I just needed to re-think how I'm allocating and pushing views. The "More" tab displays a table view, and each cell represents a child view that will be pushed when that cell is selected. I changed this view to alloc all child views inside viewDidLoad, then simply perform the push inside didSelectRowAtIndexPath, and release the child views in dealloc.

This solves my problem perfectly. Thanks to Jason Coco for getting the ball rolling.

Matthew Clark
  • 1,885
  • 21
  • 32