Skip to content

Commit

Permalink
[expo-av] Improve error-messages on iOS (expo#9618)
Browse files Browse the repository at this point in the history
* [expo-av] Improve error-messages on iOS

* [expo-av] Update changelog

* [expo-av] Improve error messages
  • Loading branch information
IjzerenHein authored Aug 17, 2020
1 parent 1a534cc commit 63508eb
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 71 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Fix blank Video after unlocking screen. ([#9586](https://github.com/expo/expo/pull/9586) by [@IjzerenHein](https://github.com/IjzerenHein))
- Fix exception on Android when loading invalid Video source. ([#9596](https://github.com/expo/expo/pull/9596) by [@IjzerenHein](https://github.com/IjzerenHein))
- Fix Audio prepareToRecordAsync after it failed once on iOS. ([#9612](https://github.com/expo/expo/pull/9612) by [@IjzerenHein](https://github.com/IjzerenHein))
- Improve error-messages on iOS. ([#9618](https://github.com/expo/expo/pull/9618) by [@IjzerenHein](https://github.com/IjzerenHein))

## 8.4.1 — 2020-07-29

Expand Down
55 changes: 27 additions & 28 deletions ios/EXAV/EXAV.m
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ - (instancetype)init
- (NSDictionary *)constantsToExport
{
return @{
@"Qualities": @{
@"Low": AVAudioTimePitchAlgorithmLowQualityZeroLatency,
@"Medium": AVAudioTimePitchAlgorithmTimeDomain,
@"High": AVAudioTimePitchAlgorithmSpectral
}
};
@"Qualities": @{
@"Low": AVAudioTimePitchAlgorithmLowQualityZeroLatency,
@"Medium": AVAudioTimePitchAlgorithmTimeDomain,
@"High": AVAudioTimePitchAlgorithmSpectral
}
};
}

#pragma mark - Expo experience lifecycle
Expand Down Expand Up @@ -137,7 +137,7 @@ - (void)onAppBackgrounded
_isBackgrounded = YES;
if (!_staysActiveInBackground) {
[self _deactivateAudioSession]; // This will pause all players and stop all recordings

[self _runBlockForAllAVObjects:^(NSObject<EXAVObject> *exAVObject) {
[exAVObject appDidBackground];
}];
Expand Down Expand Up @@ -240,11 +240,11 @@ - (NSError *)_updateAudioSessionCategoryForAudioSessionMode:(EXAVAudioSessionMod
break;
}
}

if ([[_kernelAudioSessionManagerDelegate activeCategory] isEqual:requiredAudioCategory] && [_kernelAudioSessionManagerDelegate activeCategoryOptions] == requiredAudioCategoryOptions) {
return nil;
}

return [_kernelAudioSessionManagerDelegate setCategory:requiredAudioCategory withOptions:requiredAudioCategoryOptions forModule:self];
}

Expand Down Expand Up @@ -286,12 +286,12 @@ - (NSError *)promoteAudioSessionIfNecessary
}

NSError *error;

error = [self _updateAudioSessionCategoryForAudioSessionMode:audioSessionModeRequired];
if (error) {
return error;
}

error = [_kernelAudioSessionManagerDelegate setActive:YES forModule:self];
if (error) {
return error;
Expand All @@ -316,7 +316,7 @@ - (NSError *)_deactivateAudioSession
}

NSError *error = [_kernelAudioSessionManagerDelegate setActive:NO forModule:self];

if (!error) {
_currentAudioSessionMode = EXAVAudioSessionModeInactive;
}
Expand All @@ -342,7 +342,7 @@ - (NSError *)demoteAudioSessionIfPossible
}
return error;
}

// We require the session to be inactive and it is active, let's deactivate it!
return [self _deactivateAudioSession];
}
Expand Down Expand Up @@ -387,7 +387,7 @@ - (void)_runBlock:(void (^)(EXAVPlayerData *data))block
if (data) {
block(data);
} else {
reject(@"E_AUDIO_NOPLAYER", nil, UMErrorWithMessage(@"Player does not exist."));
reject(@"E_AUDIO_NOPLAYER", @"Sound object not loaded. Did you unload it using Audio.unloadAsync?", nil);
}
}

Expand All @@ -413,8 +413,7 @@ - (void)_runBlock:(void (^)(EXVideoView *view))block
if ([view isKindOfClass:[EXVideoView class]]) {
block(view);
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Invalid view returned from registry, expecting EXVideo, got: %@", view];
reject(@"E_VIDEO_TAGINCORRECT", errorMessage, UMErrorWithMessage(errorMessage));
reject(@"E_VIDEO_TAGINCORRECT", [NSString stringWithFormat:@"Invalid view returned from registry, expecting EXVideo, got: %@", view], nil);
}
} forView:reactTag ofClass:[EXVideoView class]];
}
Expand Down Expand Up @@ -480,13 +479,13 @@ - (void)_setNewAudioRecorderFilenameAndSettings:(NSDictionary *)optionsFromJS
}
}
recorderSettings[AVEncoderBitRateStrategyKey] = bitRateStrategy;

if (
iosOptionsFromJS[EXAudioRecordingOptionOutputFormatKey] &&
[iosOptionsFromJS[EXAudioRecordingOptionOutputFormatKey] isKindOfClass:[NSString class]]
) {
recorderSettings[AVFormatIDKey] =
@([self _getFormatIDFromString:iosOptionsFromJS[EXAudioRecordingOptionOutputFormatKey]]);
@([self _getFormatIDFromString:iosOptionsFromJS[EXAudioRecordingOptionOutputFormatKey]]);
}

_audioRecorderSettings = recorderSettings;
Expand All @@ -495,7 +494,7 @@ - (void)_setNewAudioRecorderFilenameAndSettings:(NSDictionary *)optionsFromJS
- (NSError *)_createNewAudioRecorder
{
if (_audioRecorder) {
return UMErrorWithMessage(@"Recorder already exists.");
return UMErrorWithMessage(@"Recorder is already prepared.");
}

id<UMFileSystemInterface> fileSystem = [_moduleRegistry getModuleImplementingProtocol:@protocol(UMFileSystemInterface)];
Expand Down Expand Up @@ -541,7 +540,7 @@ - (NSDictionary *)_getAudioRecorderStatus
- (BOOL)_checkAudioRecorderExistsOrReject:(UMPromiseRejectBlock)reject
{
if (_audioRecorder == nil) {
reject(@"E_AUDIO_NORECORDER", nil, UMErrorWithMessage(@"Recorder does not exist."));
reject(@"E_AUDIO_NORECORDER", @"Recorder does not exist. Prepare it first using Audio.prepareToRecordAsync.", nil);
}
return _audioRecorder != nil;
}
Expand Down Expand Up @@ -780,10 +779,10 @@ - (void)sendEventWithName:(NSString *)eventName body:(NSDictionary *)body
return;
}
if (!_allowsAudioRecording) {
reject(@"E_AUDIO_AUDIOMODE", nil, UMErrorWithMessage(@"Recording not allowed on iOS. Enable with Audio.setAudioModeAsync"));
reject(@"E_AUDIO_AUDIOMODE", @"Recording not allowed on iOS. Enable with Audio.setAudioModeAsync.", nil);
return;
}

[self _setNewAudioRecorderFilenameAndSettings:options];
NSError *error = [self _createNewAudioRecorder];

Expand All @@ -793,19 +792,19 @@ - (void)sendEventWithName:(NSString *)eventName body:(NSDictionary *)body
if (error) {
_audioRecorderIsPreparing = false;
[self _removeAudioRecorder:YES];
reject(@"E_AUDIO_RECORDERNOTCREATED", @"Prepare encountered an error: audio session not activated!", error);
reject(@"E_AUDIO_RECORDERNOTCREATED", [NSString stringWithFormat:@"Prepare encountered an error: %@", error.description], error);
return;
} else if ([_audioRecorder prepareToRecord]) {
resolve(@{@"uri": [[_audioRecorder url] absoluteString],
@"status": [self _getAudioRecorderStatus]});
} else {
[self _removeAudioRecorder:YES];
reject(@"E_AUDIO_RECORDERNOTCREATED", nil, UMErrorWithMessage(@"Prepare encountered an error: recorder not prepared."));
reject(@"E_AUDIO_RECORDERNOTCREATED", @"Prepare encountered an error: recorder not prepared.", nil);
}
_audioRecorderIsPreparing = false;
[self demoteAudioSessionIfPossible];
} else {
reject(@"E_AUDIO_RECORDERNOTCREATED", @"Prepare encountered an error: recorder not created.", error);
reject(@"E_AUDIO_RECORDERNOTCREATED", [NSString stringWithFormat:@"Prepare encountered an error: %@", error.description], error);
}
}

Expand All @@ -819,18 +818,18 @@ - (void)sendEventWithName:(NSString *)eventName body:(NSDictionary *)body
}
if ([self _checkAudioRecorderExistsOrReject:reject]) {
if (!_allowsAudioRecording) {
reject(@"E_AUDIO_AUDIOMODE", nil, UMErrorWithMessage(@"Recording not allowed on iOS. Enable with Audio.setAudioModeAsync"));
reject(@"E_AUDIO_AUDIOMODE", @"Recording not allowed on iOS. Enable with Audio.setAudioModeAsync.", nil);
} else if (!_audioRecorder.recording) {
_audioRecorderShouldBeginRecording = true;
NSError *error = [self promoteAudioSessionIfNecessary];
if (!error) {
if ([_audioRecorder record]) {
resolve([self _getAudioRecorderStatus]);
} else {
reject(@"E_AUDIO_RECORDING", nil, UMErrorWithMessage(@"Start encountered an error: recording not started."));
reject(@"E_AUDIO_RECORDING", @"Start encountered an error: recording not started.", nil);
}
} else {
reject(@"E_AUDIO_RECORDING", @"Start encountered an error: audio session not activated.", error);
reject(@"E_AUDIO_RECORDING", [NSString stringWithFormat:@"Start encountered an error: %@", error.description], error);
}
} else {
resolve([self _getAudioRecorderStatus]);
Expand Down
79 changes: 36 additions & 43 deletions ios/EXAV/Video/EXVideoView.m
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,12 @@ - (void)_removeData
- (void)_removePlayer
{
if (_requestedFullscreenChangeRejecter) {
NSString *errorMessage = @"Player is being removed, cancelling fullscreen change request.";
_requestedFullscreenChangeRejecter(@"E_VIDEO_FULLSCREEN", errorMessage, UMErrorWithMessage(errorMessage));
_requestedFullscreenChangeRejecter(@"E_VIDEO_FULLSCREEN", @"Player is being removed, cancelling fullscreen change request.", nil);
_requestedFullscreenChangeResolver = nil;
_requestedFullscreenChangeRejecter = nil;
_requestedFullscreenChange = NO;
}

// Any ViewController/layer updates need to be
// executed on the main UI thread.
UM_WEAKIFY(self);
Expand Down Expand Up @@ -248,9 +247,9 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
@"height": @(presentationSize.height),
@"orientation": @"landscape"};
}

_onReadyForDisplay(@{@"naturalSize": naturalSize,
@"status": [_data getStatus]});
@"status": [_data getStatus]});
}

// On iOS 11 or lower, use video-bounds monitoring to detect changes in the full-screen
Expand Down Expand Up @@ -287,7 +286,7 @@ - (void)setSource:(NSDictionary *)source
[_statusToSet addEntriesFromDictionary:[_data getStatus]];
[self _removeData];
}

[self _removePlayer];

if (initialStatus) {
Expand Down Expand Up @@ -324,21 +323,21 @@ - (void)setSource:(NSDictionary *)source
withSource:source
withStatus:statusToInitiallySet
withLoadFinishBlock:^(BOOL success, NSDictionary *successStatus, NSString *error) {
UM_ENSURE_STRONGIFY(self);
if (success) {
[self _updateForNewPlayer];
if (resolve) {
resolve(successStatus);
}
} else {
[self _removeData];
[self _removePlayer];
if (reject) {
reject(@"E_VIDEO_NOTCREATED", error, UMErrorWithMessage(error));
}
[self _callErrorCallback:error];
}
}];
UM_ENSURE_STRONGIFY(self);
if (success) {
[self _updateForNewPlayer];
if (resolve) {
resolve(successStatus);
}
} else {
[self _removeData];
[self _removePlayer];
if (reject) {
reject(@"E_VIDEO_NOTCREATED", error, nil);
}
[self _callErrorCallback:error];
}
}];
[_data setStatusUpdateCallback:statusUpdateCallback];
[_data setErrorCallback:errorCallback];

Expand Down Expand Up @@ -382,15 +381,13 @@ - (void)setFullscreen:(BOOL)value
if (!_data) {
// Tried to set fullscreen for an unloaded component.
if (reject) {
NSString *errorMessage = @"Fullscreen encountered an error: video is not loaded.";
reject(@"E_VIDEO_FULLSCREEN", errorMessage, UMErrorWithMessage(errorMessage));
reject(@"E_VIDEO_FULLSCREEN", @"Fullscreen encountered an error: video is not loaded.", nil);
}
return;
} else if (!_playerHasLoaded) {
// `setUri` has been called, but the video has not yet loaded.
if (_requestedFullscreenChangeRejecter) {
NSString *errorMessage = @"Received newer request, cancelling fullscreen mode change request.";
_requestedFullscreenChangeRejecter(@"E_VIDEO_FULLSCREEN", errorMessage, UMErrorWithMessage(errorMessage));
_requestedFullscreenChangeRejecter(@"E_VIDEO_FULLSCREEN", @"Received newer request, cancelling fullscreen mode change request.", nil);
}

_requestedFullscreenChange = value;
Expand All @@ -401,25 +398,25 @@ - (void)setFullscreen:(BOOL)value
UM_WEAKIFY(self);
if (value && !_fullscreenPlayerPresented && !_fullscreenPlayerViewController) {
_fullscreenPlayerViewController = [self _createNewPlayerViewController];

// Resize mode must be set before layer is added
// to prevent video from being animated when `resizeMode` is `cover`
[self _updateNativeResizeMode];

// Set presentation style to fullscreen
[_fullscreenPlayerViewController setModalPresentationStyle:UIModalPresentationFullScreen];

// Find the nearest view controller
UIViewController *controller = [UIApplication sharedApplication].keyWindow.rootViewController;
UIViewController *presentedController = controller.presentedViewController;
while (presentedController && ![presentedController isBeingDismissed]) {
controller = presentedController;
presentedController = controller.presentedViewController;
}

_presentingViewController = controller;
[self _callFullscreenCallbackForUpdate:EXVideoFullscreenUpdatePlayerWillPresent];

dispatch_async(dispatch_get_main_queue(), ^{
UM_ENSURE_STRONGIFY(self);
self.fullscreenPlayerViewController.showsPlaybackControls = YES;
Expand All @@ -434,7 +431,7 @@ - (void)setFullscreen:(BOOL)value
});
} else if (!value && _fullscreenPlayerPresented && !_fullscreenPlayerIsDismissing) {
[self videoPlayerViewControllerWillDismiss:_fullscreenPlayerViewController];

dispatch_async(dispatch_get_main_queue(), ^{
UM_ENSURE_STRONGIFY(self);
[self.presentingViewController dismissViewControllerAnimated:YES completion:^{
Expand All @@ -447,22 +444,18 @@ - (void)setFullscreen:(BOOL)value
});
} else if (value && !_fullscreenPlayerPresented && _fullscreenPlayerViewController && reject) {
// Fullscreen player should be presented, is being presented, but hasn't been presented yet.
NSString *errorMessage = @"Fullscreen player is already being presented. Await the first change request.";
reject(@"E_VIDEO_FULLSCREEN", errorMessage, UMErrorWithMessage(errorMessage));
reject(@"E_VIDEO_FULLSCREEN", @"Fullscreen player is already being presented. Await the first change request.", nil);
} else if (!value && _fullscreenPlayerIsDismissing && _fullscreenPlayerViewController && reject) {
// Fullscreen player should be dismissing, is already dismissing, but hasn't dismissed yet.
NSString *errorMessage = @"Fullscreen player is already being dismissed. Await the first change request.";
reject(@"E_VIDEO_FULLSCREEN", errorMessage, UMErrorWithMessage(errorMessage));
reject(@"E_VIDEO_FULLSCREEN", @"Fullscreen player is already being dismissed. Await the first change request.", nil);
} else if (!value && !_fullscreenPlayerPresented && _fullscreenPlayerViewController && reject) {
// Fullscreen player is being presented and we receive request to dismiss it.
NSString *errorMessage = @"Fullscreen player is being presented. Await the `present` request and then dismiss the player.";
reject(@"E_VIDEO_FULLSCREEN", errorMessage, UMErrorWithMessage(errorMessage));
reject(@"E_VIDEO_FULLSCREEN", @"Fullscreen player is being presented. Await the `present` request and then dismiss the player.", nil);
} else if (value && _fullscreenPlayerIsDismissing && _fullscreenPlayerViewController && reject) {
// Fullscreen player is being dismissed and we receive request to present it.
NSString *errorMessage = @"Fullscreen player is being dismissed. Await the `dismiss` request and then present the player again.";
reject(@"E_VIDEO_FULLSCREEN", errorMessage, UMErrorWithMessage(errorMessage));
reject(@"E_VIDEO_FULLSCREEN", @"Fullscreen player is being dismissed. Await the `dismiss` request and then present the player again.", nil);
} else if (resolve) {
// Fullscreen is already appropriately set.
// Fullscreen is already appropriately set.
resolve([self getStatus]);
}
}
Expand All @@ -485,9 +478,9 @@ - (void)setSource:(NSDictionary *)source
- (NSDictionary *)source
{
return @{
EXVideoSourceURIKeyPath: (_data != nil && _data.url != nil) ? _data.url.absoluteString : @"",
EXVideoSourceHeadersKeyPath: _data.headers
};
EXVideoSourceURIKeyPath: (_data != nil && _data.url != nil) ? _data.url.absoluteString : @"",
EXVideoSourceHeadersKeyPath: _data.headers
};
}

- (void)setUseNativeControls:(BOOL)useNativeControls
Expand Down

0 comments on commit 63508eb

Please sign in to comment.