سؤال

I am the author of a Cydia tweak called AirFloat. An app that implements the AirPlay audio protocol (previously known as AirTunes), making it possible to stream audio to your iOS device. AirFloat is originally an App Store app, until it got booted by Apple from the App Store.

I've since that made it available for free in Cydia. Currently the app stands in Cydia exactly as the previous App Store version. As a result of this I get a lot of requests to make it work in the background. But I cannot get it to work.

Basically I am thinking of two approaches.

Note: AirFloat displays the currently playing track on the iOS lock screen.

  1. Create a daemon, which runs the actual AirPlay implementation, and communicates with a UI app using notify. This works. Kind of. It runs and plays the audio, but the MPNowPlayingInfoCenter does not seem to be updatable from a non-UI application. Also when the daemon is running as user mobile.

  2. Second approach is to just have it all work in a UI application. But I am having difficulties not having it be suspended. I've set the "Required Background Modes" to audio and continuous. The server might still be running, but then the Bonjour advertising is brought down, because the run loop is brought to a halt when in background. Secondly the app should be launched automatically with SpringBoard and relaunched on abnormal exit.

Personally I prefer the second approach, because I would avoid doing interprocess communication. And for this approach to work I would need full background execution (including run loops) and have it both launched on SpringBoard launch and relaunched on abnormal exit.

Anyone have any suggestions on how to solve this?

هل كانت مفيدة؟

المحلول

First thank you for your amazing works with AirFlow RAOP is pretty hard thing!

So what you could do is to

1. Create a background task handlers as dispatch_block_t, let's say

     dispatch_block_t myDummyBackgroundTaskBlock = {
        [[UIApplication sharedApplication] endBackgroundTask:myDummyBackgroundTask];
        myDummyBackgroundTask = UIBackgroundTaskInvalid;
        myDummyBackgroundTask = [app beginBackgroundTaskWithExpirationHandler:myDummyBackgroundTask];
    };

2. Define somewhere this background and foreground tasks handler

        // foreground
        -(void)handleTasksForApplicationInForeground {
        if(myDummyBackgroundTask) { // reset that task
           [[UIApplication sharedApplication] endBackgroundTask: myDummyBackgroundTask];
           myDummyBackgroundTask = UIBackgroundTaskInvalid;
         }
        }

         // background
         -(void) handleTasksForApplicationInBackground {
             UIDevice *device = [UIDevice currentDevice];
             BOOL backgroundSupported = NO;
            if ([device respondsToSelector:@selector(isMultitaskingSupported)])
             backgroundSupported = device.multitaskingSupported;
            if(backgroundSupported && backgroundEnabled) { // perform a background task

                myDummyBackgroundTaskBlock = ^{
                    [[UIApplication sharedApplication] endBackgroundTask: myDummyBackgroundTaskBlock];
                    myDummyBackgroundTaskBlock = UIBackgroundTaskInvalid;
               };

               SEL sel = @selector(doDummyBackgroundTask);
               [self doBackgroundTaskAsync:sel];

               [self performSelector:@selector(doBackgroundTaskAsync:) withObject:nil afterDelay:500.0f]; /// LP: this is the funny part since iOS will kill the task after 500 sec.
                 }
               }

3. Now let's handle in the application delegate the background mode (As defined before you can activate background mode with different options in your app .plist):

        -(void)applicationDidEnterBackground:(UIApplication *)application {
           [self handleTasksForApplicationInBackground];
         }

        -(void)applicationWillEnterForeground:(UIApplication *)application {
           [self handleTasksForApplicationInForeground];
         }

4. Let's see what the background async task selector does

            -(void) doBackgroundTaskAsync:(SEL)selector {

            @try {
                 if( [[UIApplication sharedApplication] backgroundTimeRemaining] < 5 ) { 
                   return;
                 }

               if(!myDummyBackgroundTaskBlock) { // need to create again on-the-fly
                    myDummyBackgroundTaskBlock = ^{
                          [[UIApplication sharedApplication] endBackgroundTask:myDummyBackgroundTask];
                         myDummyBackgroundTask = UIBackgroundTaskInvalid;
                    };
                 }

                myDummyBackgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:myDummyBackgroundTaskBlock];
              dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{

               while ([[UIApplication sharedApplication] backgroundTimeRemaining] > 5.0) {
                  int delta = 5.0;
                  [self performSelector: selector ];
                  sleep(delta);
              }
            });
          }
           @catch (...) {
         }
        }

I know that this solution works nice but I know that sometimes it happens that iOS will kill the app background task anyway. Anyway if the users suddenly switches the app between foreground and background it will works indefinitely.

نصائح أخرى

Partial answer. If you add "voip" as Background Mode, it gets started automatically by SpringBoard.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top