6

I am working on iOS app which will be compatible with iOS 6/7 and stream audio .mp3 files from a website.

I have already gotten this to work using the following code:

-(NSString*)documentsFolder
{
    NSString* dataPath =  [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
    if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath])
        [[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:NULL];
    return dataPath;
}

-(NSString*)createURLFile:(NSString*)songURL
{
   NSString* M3U_FILE = @"song.m3u";
   NSString* path = [NSString stringWithFormat:@"%@",[[self documentsFolder] stringByAppendingPathComponent:M3U_FILE]];
    if([[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil])
    {
      NSFileHandle* outFile = [NSFileHandle fileHandleForWritingAtPath:path];
        if(outFile != nil)
      {
         NSData* buffer = [songURL dataUsingEncoding:NSUTF8StringEncoding];
         [outFile writeData:buffer];
         return path;
      }
   }
    return nil;
}


- (void)createStreamer
{
    // Remove any previous references.
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    // Create a new player.
    NSString* fileURL = [self createURLFile:self.aSong.songpath];
    self.songPlayer = [[AVPlayer alloc]initWithURL:[NSURL fileURLWithPath:fileURL]];
    NSAssert(self.songPlayer != nil, @"NIL AVPlayer Created!!!");
    // Observer for when the song ends...
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(playerItemDidReachEnd:)
                                                 name:AVPlayerItemDidPlayToEndTimeNotification
                                               object:[self.songPlayer currentItem]];
    [[UIApplication sharedApplication] setIdleTimerDisabled: YES];
}

I store the url for the .mp3 file in a local .m3u file and use that to load up the AVPlayer. In earlier versions of iOS, I was told that the AVPlayer would load the song first and then play it, not stream it immediately. While this does not appear to be true in iOS 6/7 (the song starts streaming almost immediately), the .m3u file was being created in case there were any problems created by not having it done this way.

With this, a loop is monitoring the status of the AVPlayer and after a few seconds, the audio starts to play out the phone without a problem.

For testing purposes, I set up an MPVolumeView on the page which plays songs:

MPVolumeView *volumeView = [[[MPVolumeView alloc] initWithFrame:CGRectMake(0, 0, 310, 20)] autorelease];
volumeView.center = CGPointMake(160,62);
[volumeView sizeToFit];
[self.view addSubview:volumeView];

The reason for this is that the volume slider will also show an indicator if the bluetooth is connected as an audio output source and allow me to change the audio route between the phone and the bluetooth device. So far, so good.

I connected my phone to my Jawbox Jambone via bluetooth, start the AVPlayer on a song, and the song comes out of the Jawbox as expected. The volume control has the small "rectangle with arrow" indicating that I can switch the audio output and indeed, while the song is playing, I can switch between the phone and the Jawbox. Happiness.

The problem occurs when I try to connect it to a car. I have two experiences with this:

  1. The car is already paired with the phone for making/receiving calls. The phone even indicates it is paired when I get into the car. But when I use the same code to play the same audio files, they only come out of the phone. The volume slider does not show the "bluetooth route" indicator at all (like it does not recognize the car as a audio output route).
  2. In another car, the audio was streaming from another app (some radio streaming app). The other app was stopped and this one started. The audio started playing for the same song tested above, but stopped after a second or two. Again, there was no indicator on the volume slider that the bluetooth was connected at this point.

Can somebody explain to me why the audio could stream fine out to one bluetooth device but not to another?

Have I missed something (an entitlement?) in the profile for my app that will allow it to stream audio via bluetooth to a car?

FuzzyBunnySlippers
  • 3,387
  • 2
  • 18
  • 28

4 Answers4

3

There is this project at GIT. Play iOS project is a streaming client for Play that runs on your iPhone/iPad. It supports background audio as well as the media keys when backgrounded. It supports:

  • Streams shoutcast stream
  • Displays currently playing track
  • Background audio
  • Lock screen album art & play controls
  • AirPlay (along with Bluetooth) streaming. Supports sending metadata and album art

You can download the project here. I have not tested this on CAR bluetooth audio player though. Hope it may be of any help to you.

Aditya
  • 2,876
  • 4
  • 34
  • 30
2

In the first example, your car may simply be a remote player. You would need to register for remote events like this (consider using an AVAudioPlayer instead of an AVPlayer also)

Setup the AudioSession to recognize a bluetooth audio route:

- (BOOL)prepareAudioSession {

    // deactivate existing session
NSError *setCategoryError = nil;
NSError *activationError = nil;

BOOL success = [[AVAudioSession sharedInstance] setActive:NO error: nil];
if (!success) {
    NSLog(@"deactivationError");
}

    // set audio session category AVAudioSessionCategoryPlayAndRecord options AVAudioSessionCategoryOptionAllowBluetooth
success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:&setCategoryError];
if (!success)
{
    NSLog(@"setCategoryError %@",setCategoryError);
}

    // activate audio session
success = [[AVAudioSession sharedInstance] setActive:YES error: &activationError];
if (!success) {
    NSLog(@"activationError");
}

return success;

}

You can check the routes:

 AVAudioSessionRouteDescription *mAVASRD = audioSession.currentRoute;
NSLog(@"the array is %@",mAVASRD.outputs);

for (int ctr = 0; ctr < [mAVASRD.outputs count]; ctr++)
{
    AVAudioSessionPortDescription *myPortDescription = [mAVASRD.outputs objectAtIndex:ctr];
    NSLog(@"the type is %@",myPortDescription.portType);
    NSLog(@"the name is %@",myPortDescription.portName);
    NSLog(@"the UID is %@",myPortDescription.UID);
    NSLog(@"the data sources are %@",myPortDescription.dataSources);
}

Then initialize your AVAudioPlayer and turn on RemoteControlEvents (you can use the console in your car to send play/pause/etc)

[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

then implement something like the delegate method for AVAudioPlayer in this stack overflow question to capture the received events and react accordingly in your code:

AVAudioPlayer on Lock Screen

In the scenario 2, when you moved one app to the background (the radio streaming app) and started your app, the likely culprit for the issue is the same cause - your app has to recognize the bluetooth route for audio.

By the way for phone calls and Siri, the iOS uses a different Bluetooth channel that the default for remote control (which is the one I am describing for your car).

When you setup this route and remote control events, you also get a bonus byproduct - your app will be controllable from the lock screen. Check out this technical note from Apple to configure your app to play in the background as well if that is also something you need to do when the screen locks: Technical QA document QA1668

Finally, for added integration via your bluetooth route, look at MPNowPlayingInfoCenter - put the title artist artwork and other good stuff on the lock screen and on most bluetooth screens in the car that are displaying that information.

Community
  • 1
  • 1
thebdog
  • 63
  • 6
  • I also suggest you research the AV requirement to handle route changes as well as route notifications. Your app will thank you. – thebdog Jul 24 '14 at 12:28
0

I'm pretty sure MPVolumeView can only address Bluetooth devices which conform to the newer low power consumption Bluetooth spec... (Bluetooth Low Energy or BLE)...

I know the phone app doesn't use MPVolumeView, probably this other audio player doesn't either.. You may need to look into CoreBluetooth and implement your own :( good luck. There may be a solution on github

Jef
  • 4,728
  • 2
  • 25
  • 33
0

A bluetooth speaker designed as a speaker will be no problem.

However, a car will usually be a "phone" bluetooth speaker and will only accept a "phone" type of communications.

My guess would be that you would have to trick it by setting up a "phone audio" connection and having the incoming audio transfer into the void, and the outgoing music stream as a phone signal.

Mind you, signal quality might degrade and there probaly won't be a fix for that.

Tschallacka
  • 27,901
  • 14
  • 88
  • 133
  • I have seen solutions like this mentioned, always with the caveat of degraded audio. I don't think that would be a good solution...but thanks for the feedback. – FuzzyBunnySlippers Jan 28 '14 at 00:07
  • Yes, but remember you have to work with the limitations of the car. Most cars will have bluetooth set up to go through the cars speakers, even muting the music playing whilst conversation is going, howerever, most cars are not designed to work with audio signals but with phone signals. You would have to target an audience which has bought a car radio with bluetooth play capabilities. – Tschallacka Jan 28 '14 at 07:57
  • 1
    The problem is that they hear it with high quality audio when they listen on their phone. So doing it this way gives them "good audio" on the phone and "poor audio" in their car via bluetooth. When they can plug in an mp3 player and get good audio (possibly from their iPhone). So if I have to choose between no bluetooth audio and bad audio, which they will compare to "good audio", I'd rather it be "no bluetooth audio" than a stream of "audio quality bad" complaints. – FuzzyBunnySlippers Jan 28 '14 at 12:33