From 2ea6d34fd1b4c8b6178ab455d09e85e041ac61e1 Mon Sep 17 00:00:00 2001 From: Djorkaeff Alexandre Date: Thu, 30 Apr 2020 15:54:27 -0300 Subject: [PATCH] [FIX] Recorded audio on Android doesn't play on iOS (#2073) * react-native-video -> expo-av * remove react-native-video * Add audio mode * update mocks * [FIX] Loading bigger than play/pause Co-authored-by: Diego Mello --- __mocks__/expo-av.js | 14 + .../__snapshots__/Storyshots.test.js.snap | 4 - app/containers/MessageBox/Recording.js | 3 +- app/containers/message/Audio.js | 135 +- ios/Podfile.lock | 14 +- .../SocketRocket/NSRunLoop+SRWebSocket.h | 1 + .../NSRunLoop+SRWebSocketPrivate.h | 1 + .../SocketRocket/NSURLRequest+SRWebSocket.h | 1 + .../NSURLRequest+SRWebSocketPrivate.h | 1 + .../Private/SocketRocket/SRConstants.h | 1 + .../SocketRocket/SRDelegateController.h | 1 + .../Headers/Private/SocketRocket/SRError.h | 1 + .../SocketRocket/SRHTTPConnectMessage.h | 1 + .../Headers/Private/SocketRocket/SRHash.h | 1 + .../Private/SocketRocket/SRIOConsumer.h | 1 + .../Private/SocketRocket/SRIOConsumerPool.h | 1 + ios/Pods/Headers/Private/SocketRocket/SRLog.h | 1 + .../Headers/Private/SocketRocket/SRMutex.h | 1 + .../SocketRocket/SRPinningSecurityPolicy.h | 1 + .../Private/SocketRocket/SRProxyConnect.h | 1 + .../Headers/Private/SocketRocket/SRRandom.h | 1 + .../Private/SocketRocket/SRRunLoopThread.h | 1 + .../Private/SocketRocket/SRSIMDHelpers.h | 1 + .../Private/SocketRocket/SRSecurityPolicy.h | 1 + .../Private/SocketRocket/SRURLUtilities.h | 1 + .../Private/SocketRocket/SRWebSocket.h | 1 + .../Private/SocketRocket/SocketRocket.h | 1 + .../Private/react-native-video/RCTVideo.h | 1 - .../react-native-video/RCTVideoManager.h | 1 - .../RCTVideoPlayerViewController.h | 1 - .../RCTVideoPlayerViewControllerDelegate.h | 1 - .../UIView+FindUIViewController.h | 1 - .../SocketRocket/NSRunLoop+SRWebSocket.h | 1 + .../SocketRocket/NSURLRequest+SRWebSocket.h | 1 + .../Public/SocketRocket/SRSecurityPolicy.h | 1 + .../Headers/Public/SocketRocket/SRWebSocket.h | 1 + .../Public/SocketRocket/SocketRocket.h | 1 + .../Public/react-native-video/RCTVideo.h | 1 - .../react-native-video/RCTVideoManager.h | 1 - .../RCTVideoPlayerViewController.h | 1 - .../RCTVideoPlayerViewControllerDelegate.h | 1 - .../UIView+FindUIViewController.h | 1 - ...actNativeKeyboardTrackingView.podspec.json | 24 + ios/Pods/Manifest.lock | 14 +- ios/Pods/Pods.xcodeproj/project.pbxproj | 16923 ++++++++-------- ...ods-RocketChatRN-acknowledgements.markdown | 58 +- .../Pods-RocketChatRN-acknowledgements.plist | 70 +- .../Pods-RocketChatRN.debug.xcconfig | 6 +- .../Pods-RocketChatRN.release.xcconfig | 6 +- ...hareRocketChatRN-acknowledgements.markdown | 58 +- ...s-ShareRocketChatRN-acknowledgements.plist | 70 +- .../Pods-ShareRocketChatRN.debug.xcconfig | 6 +- .../Pods-ShareRocketChatRN.release.xcconfig | 6 +- .../SocketRocket/SocketRocket-dummy.m | 5 + .../SocketRocket-prefix.pch} | 0 .../SocketRocket/SocketRocket.xcconfig | 11 + .../react-native-video-dummy.m | 5 - .../react-native-video.xcconfig | 11 - package.json | 1 - yarn.lock | 19 - 60 files changed, 8898 insertions(+), 8602 deletions(-) create mode 100644 __mocks__/expo-av.js create mode 120000 ios/Pods/Headers/Private/SocketRocket/NSRunLoop+SRWebSocket.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/NSRunLoop+SRWebSocketPrivate.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/NSURLRequest+SRWebSocket.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/NSURLRequest+SRWebSocketPrivate.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRConstants.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRDelegateController.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRError.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRHTTPConnectMessage.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRHash.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRIOConsumer.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRIOConsumerPool.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRLog.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRMutex.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRPinningSecurityPolicy.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRProxyConnect.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRRandom.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRRunLoopThread.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRSIMDHelpers.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRSecurityPolicy.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRURLUtilities.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SRWebSocket.h create mode 120000 ios/Pods/Headers/Private/SocketRocket/SocketRocket.h delete mode 120000 ios/Pods/Headers/Private/react-native-video/RCTVideo.h delete mode 120000 ios/Pods/Headers/Private/react-native-video/RCTVideoManager.h delete mode 120000 ios/Pods/Headers/Private/react-native-video/RCTVideoPlayerViewController.h delete mode 120000 ios/Pods/Headers/Private/react-native-video/RCTVideoPlayerViewControllerDelegate.h delete mode 120000 ios/Pods/Headers/Private/react-native-video/UIView+FindUIViewController.h create mode 120000 ios/Pods/Headers/Public/SocketRocket/NSRunLoop+SRWebSocket.h create mode 120000 ios/Pods/Headers/Public/SocketRocket/NSURLRequest+SRWebSocket.h create mode 120000 ios/Pods/Headers/Public/SocketRocket/SRSecurityPolicy.h create mode 120000 ios/Pods/Headers/Public/SocketRocket/SRWebSocket.h create mode 120000 ios/Pods/Headers/Public/SocketRocket/SocketRocket.h delete mode 120000 ios/Pods/Headers/Public/react-native-video/RCTVideo.h delete mode 120000 ios/Pods/Headers/Public/react-native-video/RCTVideoManager.h delete mode 120000 ios/Pods/Headers/Public/react-native-video/RCTVideoPlayerViewController.h delete mode 120000 ios/Pods/Headers/Public/react-native-video/RCTVideoPlayerViewControllerDelegate.h delete mode 120000 ios/Pods/Headers/Public/react-native-video/UIView+FindUIViewController.h create mode 100644 ios/Pods/Local Podspecs/ReactNativeKeyboardTrackingView.podspec.json create mode 100644 ios/Pods/Target Support Files/SocketRocket/SocketRocket-dummy.m rename ios/Pods/Target Support Files/{react-native-video/react-native-video-prefix.pch => SocketRocket/SocketRocket-prefix.pch} (100%) create mode 100644 ios/Pods/Target Support Files/SocketRocket/SocketRocket.xcconfig delete mode 100644 ios/Pods/Target Support Files/react-native-video/react-native-video-dummy.m delete mode 100644 ios/Pods/Target Support Files/react-native-video/react-native-video.xcconfig diff --git a/__mocks__/expo-av.js b/__mocks__/expo-av.js new file mode 100644 index 00000000..e20feac6 --- /dev/null +++ b/__mocks__/expo-av.js @@ -0,0 +1,14 @@ +export class Sound { + loadAsync = () => {}; + + playAsync = () => {}; + + pauseAsync = () => {}; + + stopAsync = () => {}; + + setOnPlaybackStatusUpdate = () => {}; + + setPositionAsync = () => {}; +} +export const Audio = { Sound }; diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index 2fa6d106..b9950e32 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -16315,7 +16315,6 @@ exports[`Storyshots Message list message 1`] = ` ] } > - View - View - View - View { diff --git a/app/containers/message/Audio.js b/app/containers/message/Audio.js index 6ca5ce14..83ac45ea 100644 --- a/app/containers/message/Audio.js +++ b/app/containers/message/Audio.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { View, StyleSheet, Text, Easing, Dimensions } from 'react-native'; -import Video from 'react-native-video'; +import { Audio } from 'expo-av'; import Slider from '@react-native-community/slider'; import moment from 'moment'; import equal from 'deep-equal'; @@ -15,6 +15,17 @@ import sharedStyles from '../../views/Styles'; import { themes } from '../../constants/colors'; import { isAndroid, isIOS } from '../../utils/deviceInfo'; import { withSplit } from '../../split'; +import ActivityIndicator from '../ActivityIndicator'; + +const mode = { + allowsRecordingIOS: false, + playsInSilentModeIOS: true, + staysActiveInBackground: false, + shouldDuckAndroid: true, + playThroughEarpieceAndroid: false, + interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX, + interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX +}; const styles = StyleSheet.create({ audioContainer: { @@ -31,6 +42,9 @@ const styles = StyleSheet.create({ alignItems: 'center', backgroundColor: 'transparent' }, + audioLoading: { + marginHorizontal: 8 + }, slider: { flex: 1 }, @@ -51,25 +65,32 @@ const sliderAnimationConfig = { delay: 0 }; -const Button = React.memo(({ paused, onPress, theme }) => ( +const Button = React.memo(({ + loading, paused, onPress, theme +}) => ( - + { + loading + ? + : + } )); Button.propTypes = { + loading: PropTypes.bool, paused: PropTypes.bool, theme: PropTypes.string, onPress: PropTypes.func }; Button.displayName = 'MessageAudioButton'; -class Audio extends React.Component { +class MessageAudio extends React.Component { static propTypes = { file: PropTypes.object.isRequired, baseUrl: PropTypes.string.isRequired, @@ -83,16 +104,33 @@ class Audio extends React.Component { super(props); const { baseUrl, file, user } = props; this.state = { + loading: false, currentTime: 0, duration: 0, paused: true, uri: `${ baseUrl }${ file.audio_url }?rc_uid=${ user.id }&rc_token=${ user.token }` }; + + this.sound = new Audio.Sound(); + this.sound.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate); + } + + async componentDidMount() { + const { uri } = this.state; + + this.setState({ loading: true }); + try { + await Audio.setAudioModeAsync(mode); + await this.sound.loadAsync({ uri }); + } catch { + // Do nothing + } + this.setState({ loading: false }); } shouldComponentUpdate(nextProps, nextState) { const { - currentTime, duration, paused, uri + currentTime, duration, paused, uri, loading } = this.state; const { file, split, theme } = this.props; if (nextProps.theme !== theme) { @@ -116,44 +154,87 @@ class Audio extends React.Component { if (nextProps.split !== split) { return true; } + if (nextState.loading !== loading) { + return true; + } return false; } + async componentWillUnmount() { + try { + await this.sound.stopAsync(); + } catch { + // Do nothing + } + } + + onPlaybackStatusUpdate = (status) => { + if (status) { + this.onLoad(status); + this.onProgress(status); + this.onEnd(status); + } + } + onLoad = (data) => { - this.setState({ duration: data.duration > 0 ? data.duration : 0 }); + const duration = data.durationMillis / 1000; + this.setState({ duration: duration > 0 ? duration : 0 }); } onProgress = (data) => { const { duration } = this.state; - if (data.currentTime <= duration) { - this.setState({ currentTime: data.currentTime }); + const currentTime = data.positionMillis / 1000; + if (currentTime <= duration) { + this.setState({ currentTime }); } } - onEnd = () => { - this.setState({ paused: true, currentTime: 0 }); - requestAnimationFrame(() => { - this.player.seek(0); - }); + onEnd = async(data) => { + if (data.didJustFinish) { + try { + await this.sound.stopAsync(); + this.setState({ paused: true, currentTime: 0 }); + } catch { + // do nothing + } + } } get duration() { - const { duration } = this.state; - return formatTime(duration); + const { currentTime, duration } = this.state; + return formatTime(currentTime || duration); } - setRef = ref => this.player = ref; - togglePlayPause = () => { const { paused } = this.state; - this.setState({ paused: !paused }); + this.setState({ paused: !paused }, this.playPause); } - onValueChange = value => this.setState({ currentTime: value }); + playPause = async() => { + const { paused } = this.state; + try { + if (paused) { + await this.sound.pauseAsync(); + } else { + await this.sound.playAsync(); + } + } catch { + // Do nothing + } + } + + onValueChange = async(value) => { + try { + this.setState({ currentTime: value }); + await this.sound.setPositionAsync(value * 1000); + } catch { + // Do nothing + } + } render() { const { - uri, paused, currentTime, duration + loading, paused, currentTime, duration } = this.state; const { user, baseUrl, file, getCustomEmoji, split, theme @@ -173,17 +254,7 @@ class Audio extends React.Component { split && sharedStyles.tabletContent ]} > -