Dealing With Interruptions on iOS Devices

We all deal with inter- What? Not now, I'm in the middle of something. . . Sorry, let's try that again; we all deal with interruptions. From chatty co-workers to adorable cat video distractions, something always manages to turn that five-minute task into an all day endeavor. When an incoming phone call cuts in the middle of your Red5 Pro live stream, well that's just outright disruptive. Fortunately, the following steps show you how to deal with these interruptions on your iPhone.

Now I know what you're thinking; "That's all well and good, but what about Android?". No worries, we will deal with Android in a future blog post. So bear with us for a bit and we will have more instructions for Android users as well.

For those trying to quickly get back to that "five-minute" task, basically you can use the R5RecordTypeAppend publish type for the R5Stream:publish:type API while providing the stream name used prior to the interruption.

Example of Pausing and Resuming Recording on iOS Interruptions

In this example, we will show how to resume a broadcast from an iOS Interruption, such as a Phone Call or Siri activation.

Feature

We can explain our feature(s) of resumable interrupt recording as such:

Scenario 1: App Interrupt pauses current broadcast session

*Given* I have started a broadcast session
*When* I receive an interrupt in my App (e.g., Phone Call, Siri Activation)
*Then* My recording session is paused

Scenario 2: Return from Interrupt resumes broadcast session

*Given* I have started a broadcast session
*And* I receive an interrupt in my App (e.g., Phone Call, Siri Activation)
*When* I return from the interrupt (e.g., Decline of Phone Call)
*Then* My recording is resumed from the point of interruption

Scenario 3: Return from Background does not resume broadcast session

*Given* I have started a broadcast session
*And* I receive an interrupt in my App (e.g., Phone Call, Siri Activation)
*When* I enter my App into the background (e.g., Accept of Phone Call)
*And* I re-launch the App
*Then* A new broadcast session is started

Implementation

In order to implement the expectations of our feature specification, we need to first establish the Givens in of our scenarios which describe the creation of a broadcast session.

Base

We will use an R5VideoViewController from the Red5 Pro SDK as the base for our broadcast session and logic for maintaining our interrupt and background recording feature specifications.

For additional information on creating a ViewController for streaming on iOS, visit the documentation at https://www.red5pro.com/docs/streaming/ios/.

View controller

PublishViewController.h

@interface PublishViewController : R5VideoViewController<R5StreamDelegate>

- (void)start:(NSString *)theStreamName;
- (void)stop;
- (void)resume:(NSString *)theStreamName;

@end

PublishViewController.m


#import "PublishViewController.h"
#import <R5Streaming/R5Streaming.h>

@interface PublishViewController () {
    R5Configuration *config;
    R5Stream *stream;
    NSString *streamName;
    BOOL isResumable;
}
@end

@implementation PublishViewController {

- (void)start:(NSString *)streamName {
}

- (void)stop {
}

- (void)resume:(NSString *)streamName {
}

}
@end

This serves as the initial class template for a ViewContoller that will handle the logic of the feature specifications, with the internal property declarations to manage the configuration and stream.

Configuration

We'll create a method to generate a class-local configuration that can be used throughout the recording sessions on the PublishViewController:

PublishViewController.m

- (void)createConfiguration {

    config = [R5Configuration new];
    config.host = @"localhost";
    config.port = 8554;
    config.contextName = @"live";

Establishing a Broadcast-able Session

The following method will establish a connection and make a broadcast session eligible for publishing:

PublishViewController.m

- (void)preview {

    NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    AVCaptureDevice *cameraDevice = [cameras lastObject];
    R5Camera *camera = [[R5Camera alloc] initWithDevice:cameraDevice andBitRate:512];

    AVCaptureDevice *audioDevice= [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    R5Microphone *microphone = [[R5Microphone new] initWithDevice:audioDevice];

    R5Connection *connection = [[R5Connection new] initWithConfig:config];

    stream = [[R5Stream new] initWithConnection:connection];
    [stream attachVideo:camera];
    [stream attachAudio:microphone];

    [stream setDelegate:self];
    [self attachStream:stream];
    [self showPreview:YES];

}

In order to start showing a Broadcasters video without publishing to the Red5 Pro server, we will invoke the configuration and preview in the viewDidLoad override of PublishViewController:

PublishViewController.m

- (void)viewDidLoad {

    [super viewDidLoad];
    [self createConfiguration];
    [self preview];

}

Start, Stop and Resume Actions

Now that we have established the references for config and stream, we can think about the specifics of the actions within a broadcast session.

start:streamName

In the start:streamName method, we will use the stream created upon viewDidLoad and start a publishing session with the publish type of R5RecordTypeRecord:

PublishViewController.m

- (void)start:(NSString *)theStreamName {

  streamName = theStreamName;
  [self showPreview:NO];
  [stream publish:streamName type:R5RecordTypeRecord];

}

stop

In the stop method, we will stop the publishing session, deallocate the currently established stream and reset the preview:

PublishViewController.m

- (void)stop {

  [stream stop];
  [stream setDelegate:nil];
  stream = nil;

}

resume:streamName

The resume:streamName method is relatively similar to the start:streamName method, however in resume: we do not assign the class-local streamName (using its value already defined) and specify the R5RecordTypeAppend type:

- (void)resume:(NSString *)theStreamName {

    [self showPreview:NO];
    [self publish:theStreamName type:R5RecordTypeAppend];

}

Listening for Notifications

In order to properly handle interrupts and background entrance of your App, assign NSNotification handlers in the viewDidLoad override of PublishViewController:

PublishViewController.m

- (void)viewDidLoad {

    [super viewDidLoad];
    [self createConfiguration];
    [self preview];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationDidEnterBackgroundNotification:)
                                                 name:UIApplicationDidEnterBackgroundNotification
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationWillResignActiveNotification:)
                                                 name:UIApplicationWillResignActiveNotification
                                               object:nil];

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

}

When your iOS device enters an "interrupt", your application is still active - meaning not in the background - and is waiting for a response to the interrupt. Such interruptions can be a Phone Call or the activation of Siri. During such interruptions, the User action will determine the following state and notification.

If you chose, for instance, to Decline a phone call, the subsequent notification dispatched will be applicationDidBecomeActiveNotification. If you chose to Accept the phone call, the subsequent notification dispatched will be applicationDidEnterBackgroundNotification and your App will be put into the background.

applicationWillResignActiveNotification

The applicationWillResignActiveNotification is notified on the observer when the device enters an "interrupt". In such a case, we want to stop or broadcast session, but retain the possibility of resuming the broadcast if the user declines or dismisses the "interrupt" without the application going into the background. A most common case is the Decline of an incoming Phone Call.

- (void)applicationWillResignActiveNotification:(NSNotification *)notification {

  if (stream != nil && stream.mode == r5_stream_mode_publish) {
    isResumable = YES;
  }
  [self stop];
  [self preview];

}

We first check if we have a stream established and are currently in a publishing session. If so, then we can consider the app in an isResumable state if we were to come back from an "interrupt" without the app going into the background.

applicationDidBecomeActiveNotification

The applicationDidBecomeActiveNotification is notified in two cases:

  1. User has declined an interrupt
  2. The App is launched from the background

In the first case, we have defined the App in an isResumable state in case we return from an "interrupt" without sending the App into the background. In such as case, we want to invoke the resume:streamName method:

- (void)applicationDidBecomeActiveNotification:(NSNotification *)notification {

  if (isResumable) {
    isResumable = NO;
    [self resume:streamName];
  }

}

In the second case, we do not want to consider the broadcast resumable. As such, we need to handle when the App does go into the background following an "interrupt".

applicationDidEnterBackgroundNotification

- (void)applicationDidEnterBackgroundNotification:(NSNotification *)notification {

  isResumable = NO;
  [self stop];

}

Conclusion

In this example, we created a resumable broadcast from an iOS interrupt - such as from a Decline from Phone Call. We used the R5RecordTypeAppend option for R5Stream:publish:type when the App was considered in an isResumable state following an "interrupt" without sending the App into the background.

  • Share: