From 63508eb5abe2bc7d33027387e04538b8639715a9 Mon Sep 17 00:00:00 2001 From: Hein Rutjes Date: Mon, 17 Aug 2020 11:19:24 +0200 Subject: [PATCH] [expo-av] Improve error-messages on iOS (#9618) * [expo-av] Improve error-messages on iOS * [expo-av] Update changelog * [expo-av] Improve error messages --- CHANGELOG.md | 1 + ios/EXAV/EXAV.m | 55 ++++++++++++------------- ios/EXAV/Video/EXVideoView.m | 79 ++++++++++++++++-------------------- 3 files changed, 64 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b6eb5cababed..c583a9aac8754 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/ios/EXAV/EXAV.m b/ios/EXAV/EXAV.m index f573f0d4b3cd5..88b30a34e9be8 100644 --- a/ios/EXAV/EXAV.m +++ b/ios/EXAV/EXAV.m @@ -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 @@ -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 appDidBackground]; }]; @@ -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]; } @@ -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; @@ -316,7 +316,7 @@ - (NSError *)_deactivateAudioSession } NSError *error = [_kernelAudioSessionManagerDelegate setActive:NO forModule:self]; - + if (!error) { _currentAudioSessionMode = EXAVAudioSessionModeInactive; } @@ -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]; } @@ -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); } } @@ -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]]; } @@ -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; @@ -495,7 +494,7 @@ - (void)_setNewAudioRecorderFilenameAndSettings:(NSDictionary *)optionsFromJS - (NSError *)_createNewAudioRecorder { if (_audioRecorder) { - return UMErrorWithMessage(@"Recorder already exists."); + return UMErrorWithMessage(@"Recorder is already prepared."); } id fileSystem = [_moduleRegistry getModuleImplementingProtocol:@protocol(UMFileSystemInterface)]; @@ -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; } @@ -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]; @@ -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); } } @@ -819,7 +818,7 @@ - (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]; @@ -827,10 +826,10 @@ - (void)sendEventWithName:(NSString *)eventName body:(NSDictionary *)body 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]); diff --git a/ios/EXAV/Video/EXVideoView.m b/ios/EXAV/Video/EXVideoView.m index dddf050331e61..889121facfcbf 100644 --- a/ios/EXAV/Video/EXVideoView.m +++ b/ios/EXAV/Video/EXVideoView.m @@ -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); @@ -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 @@ -287,7 +286,7 @@ - (void)setSource:(NSDictionary *)source [_statusToSet addEntriesFromDictionary:[_data getStatus]]; [self _removeData]; } - + [self _removePlayer]; if (initialStatus) { @@ -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]; @@ -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; @@ -401,14 +398,14 @@ - (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; @@ -416,10 +413,10 @@ - (void)setFullscreen:(BOOL)value controller = presentedController; presentedController = controller.presentedViewController; } - + _presentingViewController = controller; [self _callFullscreenCallbackForUpdate:EXVideoFullscreenUpdatePlayerWillPresent]; - + dispatch_async(dispatch_get_main_queue(), ^{ UM_ENSURE_STRONGIFY(self); self.fullscreenPlayerViewController.showsPlaybackControls = YES; @@ -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:^{ @@ -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]); } } @@ -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