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… Continue reading Dealing With Interruptions on iOS Devices
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:
- User has declined an interrupt
- 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.