moved the player from messageAudio to audioPlayer
This commit is contained in:
parent
182af31fb2
commit
8b053f630e
|
@ -1,8 +1,8 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
|
||||
|
||||
import { CustomIcon } from '../../../CustomIcon';
|
||||
import { useTheme } from '../../../../theme';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import { useTheme } from '../../theme';
|
||||
|
||||
const Loading = () => {
|
||||
const rotation = useSharedValue(0);
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
|
||||
import Touchable from '../../Touchable';
|
||||
import { CustomIcon } from '../../../CustomIcon';
|
||||
import { useTheme } from '../../../../theme';
|
||||
import Touchable from '../message/Touchable';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import { useTheme } from '../../theme';
|
||||
import styles from './styles';
|
||||
import Loading from './Loading';
|
||||
|
|
@ -2,10 +2,10 @@ import React from 'react';
|
|||
import { Text } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
import { useTheme } from '../../../../theme';
|
||||
import Touchable from '../../Touchable';
|
||||
import { useTheme } from '../../theme';
|
||||
import Touchable from '../message/Touchable';
|
||||
|
||||
const AudioRate = ({
|
||||
const PlaybackSpeed = ({
|
||||
onChange,
|
||||
loaded = false,
|
||||
rate = 1
|
||||
|
@ -25,11 +25,11 @@ const AudioRate = ({
|
|||
<Touchable
|
||||
disabled={!loaded}
|
||||
onPress={onPress}
|
||||
style={[styles.containerAudioRate, { backgroundColor: colors.buttonBackgroundSecondaryDefault }]}
|
||||
style={[styles.containerPlaybackSpeed, { backgroundColor: colors.buttonBackgroundSecondaryDefault }]}
|
||||
>
|
||||
<Text style={[styles.audioRateText, { color: colors.buttonFontOnSecondary }]}>{rate}x</Text>
|
||||
<Text style={[styles.playbackSpeedText, { color: colors.buttonFontOnSecondary }]}>{rate}x</Text>
|
||||
</Touchable>
|
||||
);
|
||||
};
|
||||
|
||||
export default AudioRate;
|
||||
export default PlaybackSpeed;
|
|
@ -12,11 +12,11 @@ import Animated, {
|
|||
} from 'react-native-reanimated';
|
||||
|
||||
import styles from './styles';
|
||||
import { useTheme } from '../../../../theme';
|
||||
import { useTheme } from '../../theme';
|
||||
|
||||
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
|
||||
|
||||
interface ISlider {
|
||||
interface ISeek {
|
||||
duration: SharedValue<number>;
|
||||
currentTime: SharedValue<number>;
|
||||
loaded: boolean;
|
||||
|
@ -30,7 +30,7 @@ const BUTTON_HIT_SLOP = {
|
|||
left: 8
|
||||
};
|
||||
|
||||
const Slider = ({ currentTime, duration, loaded = false, onChangeTime }: ISlider) => {
|
||||
const Seek = ({ currentTime, duration, loaded = false, onChangeTime }: ISeek) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
const maxWidth = useSharedValue(1);
|
||||
|
@ -90,6 +90,7 @@ const Slider = ({ currentTime, duration, loaded = false, onChangeTime }: ISlider
|
|||
}
|
||||
});
|
||||
|
||||
// https://docs.swmansion.com/react-native-reanimated/docs/2.x/fundamentals/worklets/
|
||||
const formatTime = (ms: number) => {
|
||||
'worklet';
|
||||
|
||||
|
@ -129,22 +130,22 @@ const Slider = ({ currentTime, duration, loaded = false, onChangeTime }: ISlider
|
|||
const thumbColor = loaded ? colors.buttonBackgroundPrimaryDefault : colors.tintDisabled;
|
||||
|
||||
return (
|
||||
<View style={styles.sliderContainer}>
|
||||
<View style={styles.seekContainer}>
|
||||
<AnimatedTextInput
|
||||
defaultValue={'00:00'}
|
||||
editable={false}
|
||||
style={[styles.duration, { color: colors.fontDefault }]}
|
||||
animatedProps={getCurrentTime}
|
||||
/>
|
||||
<View style={styles.slider} onLayout={onLayout}>
|
||||
<View style={styles.seek} onLayout={onLayout}>
|
||||
<View style={[styles.line, { backgroundColor: colors.strokeLight }]} />
|
||||
<Animated.View style={[styles.line, styleLine, { backgroundColor: colors.buttonBackgroundPrimaryDefault }]} />
|
||||
<PanGestureHandler enabled={loaded} onGestureEvent={gestureHandler}>
|
||||
<Animated.View hitSlop={BUTTON_HIT_SLOP} style={[styles.thumbSlider, { backgroundColor: thumbColor }, styleThumb]} />
|
||||
<Animated.View hitSlop={BUTTON_HIT_SLOP} style={[styles.thumbSeek, { backgroundColor: thumbColor }, styleThumb]} />
|
||||
</PanGestureHandler>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Slider;
|
||||
export default Seek;
|
|
@ -0,0 +1,208 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { StyleProp, TextStyle, View } from 'react-native';
|
||||
import { AVPlaybackStatus } from 'expo-av';
|
||||
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
|
||||
import { useSharedValue } from 'react-native-reanimated';
|
||||
|
||||
import { IAttachment, IUserMessage } from '../../definitions';
|
||||
import { useTheme } from '../../theme';
|
||||
import { downloadMediaFile, getMediaCache } from '../../lib/methods/handleMediaDownload';
|
||||
import { fetchAutoDownloadEnabled } from '../../lib/methods/autoDownloadPreference';
|
||||
import styles from './styles';
|
||||
import Seek from './Seek';
|
||||
import PlaybackSpeed from './PlaybackSpeed';
|
||||
import PlayButton from './PlayButton';
|
||||
import audioPlayer from '../../lib/methods/audioPlayer';
|
||||
|
||||
interface IAudioPlayerProps {
|
||||
file: IAttachment;
|
||||
isReply?: boolean;
|
||||
style?: StyleProp<TextStyle>[];
|
||||
author?: IUserMessage;
|
||||
msg?: string;
|
||||
baseUrl: string;
|
||||
user: any;
|
||||
}
|
||||
|
||||
const AudioPlayer = ({ file, author, isReply = false, baseUrl, user }: IAudioPlayerProps) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [paused, setPaused] = useState(true);
|
||||
const [cached, setCached] = useState(false);
|
||||
const [rate, setRate] = useState(1);
|
||||
|
||||
const duration = useSharedValue(0);
|
||||
const currentTime = useSharedValue(0);
|
||||
|
||||
const { colors } = useTheme();
|
||||
|
||||
const audioUri = useRef<string>('');
|
||||
|
||||
const onPlaybackStatusUpdate = (status: AVPlaybackStatus) => {
|
||||
if (status) {
|
||||
onPlaying(status);
|
||||
handlePlaybackStatusUpdate(status);
|
||||
onEnd(status);
|
||||
}
|
||||
};
|
||||
|
||||
const loadAudio = async (audio: string) => {
|
||||
await audioPlayer.loadAudio(audio);
|
||||
audioUri.current = audio;
|
||||
audioPlayer.setOnPlaybackStatusUpdate(audio, onPlaybackStatusUpdate);
|
||||
};
|
||||
|
||||
const onPlaying = (data: AVPlaybackStatus) => {
|
||||
if (data.isLoaded && data.isPlaying) {
|
||||
setPaused(false);
|
||||
} else {
|
||||
setPaused(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePlaybackStatusUpdate = (data: AVPlaybackStatus) => {
|
||||
if (data.isLoaded && data.durationMillis) {
|
||||
const durationSeconds = data.durationMillis / 1000;
|
||||
duration.value = durationSeconds > 0 ? durationSeconds : 0;
|
||||
const currentSecond = data.positionMillis / 1000;
|
||||
if (currentSecond <= durationSeconds) {
|
||||
currentTime.value = currentSecond;
|
||||
}
|
||||
setRate(data.rate);
|
||||
}
|
||||
};
|
||||
|
||||
const onEnd = (data: AVPlaybackStatus) => {
|
||||
if (data.isLoaded) {
|
||||
if (data.didJustFinish) {
|
||||
try {
|
||||
setPaused(true);
|
||||
currentTime.value = 0;
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setPosition = async (time: number) => {
|
||||
await audioPlayer.setPositionAsync(audioUri.current, time);
|
||||
};
|
||||
|
||||
const getUrl = () => {
|
||||
let url = file.audio_url;
|
||||
if (url && !url.startsWith('http')) {
|
||||
url = `${baseUrl}${file.audio_url}`;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
const togglePlayPause = async () => {
|
||||
try {
|
||||
if (!paused) {
|
||||
await audioPlayer.pauseAudio(audioUri.current);
|
||||
} else {
|
||||
await audioPlayer.playAudio(audioUri.current);
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeRate = async (value = 1.0) => {
|
||||
await audioPlayer.setRateAsync(audioUri.current, value);
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const url = getUrl();
|
||||
if (url) {
|
||||
const audio = await downloadMediaFile({
|
||||
downloadUrl: `${url}?rc_uid=${user.id}&rc_token=${user.token}`,
|
||||
type: 'audio',
|
||||
mimeType: file.audio_type
|
||||
});
|
||||
await loadAudio(audio);
|
||||
setLoading(false);
|
||||
setCached(true);
|
||||
}
|
||||
} catch {
|
||||
setLoading(false);
|
||||
setCached(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAutoDownload = async () => {
|
||||
const url = getUrl();
|
||||
try {
|
||||
if (url) {
|
||||
const isCurrentUserAuthor = author?._id === user.id;
|
||||
const isAutoDownloadEnabled = fetchAutoDownloadEnabled('audioPreferenceDownload');
|
||||
if (isAutoDownloadEnabled || isCurrentUserAuthor) {
|
||||
await handleDownload();
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
setCached(false);
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
|
||||
const onPress = () => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
if (cached) {
|
||||
togglePlayPause();
|
||||
return;
|
||||
}
|
||||
handleDownload();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleCache = async () => {
|
||||
const cachedAudioResult = await getMediaCache({
|
||||
type: 'audio',
|
||||
mimeType: file.audio_type,
|
||||
urlToCache: getUrl()
|
||||
});
|
||||
if (cachedAudioResult?.exists) {
|
||||
await loadAudio(cachedAudioResult.uri);
|
||||
setLoading(false);
|
||||
setCached(true);
|
||||
return;
|
||||
}
|
||||
if (isReply) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
await handleAutoDownload();
|
||||
};
|
||||
handleCache();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (paused) {
|
||||
deactivateKeepAwake();
|
||||
} else {
|
||||
activateKeepAwake();
|
||||
}
|
||||
}, [paused]);
|
||||
|
||||
if (!baseUrl) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<View style={[styles.audioContainer, { backgroundColor: colors.surfaceTint, borderColor: colors.strokeExtraLight }]}>
|
||||
<PlayButton disabled={isReply} loading={loading} paused={paused} cached={cached} onPress={onPress} />
|
||||
<Seek currentTime={currentTime} duration={duration} loaded={!isReply && cached} onChangeTime={setPosition} />
|
||||
<PlaybackSpeed onChange={onChangeRate} loaded={!isReply && cached} rate={rate} />
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AudioPlayer;
|
|
@ -1,6 +1,6 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import sharedStyles from '../../../../views/Styles';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
audioContainer: {
|
||||
|
@ -20,13 +20,13 @@ const styles = StyleSheet.create({
|
|||
borderRadius: 4,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
sliderContainer: {
|
||||
seekContainer: {
|
||||
flexDirection: 'row',
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
height: '100%'
|
||||
},
|
||||
slider: {
|
||||
seek: {
|
||||
marginRight: 12,
|
||||
height: '100%',
|
||||
justifyContent: 'center',
|
||||
|
@ -45,13 +45,13 @@ const styles = StyleSheet.create({
|
|||
fontSize: 14,
|
||||
...sharedStyles.textRegular
|
||||
},
|
||||
thumbSlider: {
|
||||
thumbSeek: {
|
||||
height: 12,
|
||||
width: 12,
|
||||
borderRadius: 6,
|
||||
zIndex: 3
|
||||
},
|
||||
containerAudioRate: {
|
||||
containerPlaybackSpeed: {
|
||||
width: 36,
|
||||
height: 24,
|
||||
borderRadius: 4,
|
||||
|
@ -60,7 +60,7 @@ const styles = StyleSheet.create({
|
|||
alignItems: 'center',
|
||||
overflow: 'hidden'
|
||||
},
|
||||
audioRateText: {
|
||||
playbackSpeedText: {
|
||||
fontSize: 14,
|
||||
...sharedStyles.textBold
|
||||
}
|
|
@ -1,21 +1,11 @@
|
|||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { StyleProp, TextStyle, View } from 'react-native';
|
||||
import { AVPlaybackStatus } from 'expo-av';
|
||||
import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake';
|
||||
import { useSharedValue } from 'react-native-reanimated';
|
||||
import React, { useContext } from 'react';
|
||||
import { StyleProp, TextStyle } from 'react-native';
|
||||
|
||||
import Markdown from '../../../markdown';
|
||||
import MessageContext from '../../Context';
|
||||
import { TGetCustomEmoji } from '../../../../definitions/IEmoji';
|
||||
import { IAttachment, IUserMessage } from '../../../../definitions';
|
||||
import { useTheme } from '../../../../theme';
|
||||
import { downloadMediaFile, getMediaCache } from '../../../../lib/methods/handleMediaDownload';
|
||||
import { fetchAutoDownloadEnabled } from '../../../../lib/methods/autoDownloadPreference';
|
||||
import styles from './styles';
|
||||
import Slider from './Slider';
|
||||
import AudioRate from './AudioRate';
|
||||
import PlayButton from './PlayButton';
|
||||
import audioPlayer from '../../../../lib/methods/audioPlayer';
|
||||
import AudioPlayer from '../../../AudioPlayer';
|
||||
|
||||
interface IMessageAudioProps {
|
||||
file: IAttachment;
|
||||
|
@ -27,172 +17,7 @@ interface IMessageAudioProps {
|
|||
}
|
||||
|
||||
const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMessageAudioProps) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [paused, setPaused] = useState(true);
|
||||
const [cached, setCached] = useState(false);
|
||||
const [rate, setRate] = useState(1);
|
||||
|
||||
const duration = useSharedValue(0);
|
||||
const currentTime = useSharedValue(0);
|
||||
|
||||
const { baseUrl, user } = useContext(MessageContext);
|
||||
const { colors } = useTheme();
|
||||
|
||||
const audioUri = useRef<string>('');
|
||||
|
||||
const onPlaybackStatusUpdate = (status: AVPlaybackStatus) => {
|
||||
if (status) {
|
||||
onPlaying(status);
|
||||
handlePlaybackStatusUpdate(status);
|
||||
onEnd(status);
|
||||
}
|
||||
};
|
||||
|
||||
const loadAudio = async (audio: string) => {
|
||||
await audioPlayer.loadAudio(audio);
|
||||
audioUri.current = audio;
|
||||
audioPlayer.setOnPlaybackStatusUpdate(audio, onPlaybackStatusUpdate);
|
||||
};
|
||||
|
||||
const onPlaying = (data: AVPlaybackStatus) => {
|
||||
if (data.isLoaded && data.isPlaying) {
|
||||
setPaused(false);
|
||||
} else {
|
||||
setPaused(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePlaybackStatusUpdate = (data: AVPlaybackStatus) => {
|
||||
if (data.isLoaded && data.durationMillis) {
|
||||
const durationSeconds = data.durationMillis / 1000;
|
||||
duration.value = durationSeconds > 0 ? durationSeconds : 0;
|
||||
const currentSecond = data.positionMillis / 1000;
|
||||
if (currentSecond <= durationSeconds) {
|
||||
currentTime.value = currentSecond;
|
||||
}
|
||||
setRate(data.rate);
|
||||
}
|
||||
};
|
||||
|
||||
const onEnd = (data: AVPlaybackStatus) => {
|
||||
if (data.isLoaded) {
|
||||
if (data.didJustFinish) {
|
||||
try {
|
||||
setPaused(true);
|
||||
currentTime.value = 0;
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setPosition = async (time: number) => {
|
||||
await audioPlayer.setPositionAsync(audioUri.current, time);
|
||||
};
|
||||
|
||||
const getUrl = () => {
|
||||
let url = file.audio_url;
|
||||
if (url && !url.startsWith('http')) {
|
||||
url = `${baseUrl}${file.audio_url}`;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
const togglePlayPause = async () => {
|
||||
try {
|
||||
if (!paused) {
|
||||
await audioPlayer.pauseAudio(audioUri.current);
|
||||
} else {
|
||||
await audioPlayer.playAudio(audioUri.current);
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeRate = async (value = 1.0) => {
|
||||
await audioPlayer.setRateAsync(audioUri.current, value);
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const url = getUrl();
|
||||
if (url) {
|
||||
const audio = await downloadMediaFile({
|
||||
downloadUrl: `${url}?rc_uid=${user.id}&rc_token=${user.token}`,
|
||||
type: 'audio',
|
||||
mimeType: file.audio_type
|
||||
});
|
||||
await loadAudio(audio);
|
||||
setLoading(false);
|
||||
setCached(true);
|
||||
}
|
||||
} catch {
|
||||
setLoading(false);
|
||||
setCached(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAutoDownload = async () => {
|
||||
const url = getUrl();
|
||||
try {
|
||||
if (url) {
|
||||
const isCurrentUserAuthor = author?._id === user.id;
|
||||
const isAutoDownloadEnabled = fetchAutoDownloadEnabled('audioPreferenceDownload');
|
||||
if (isAutoDownloadEnabled || isCurrentUserAuthor) {
|
||||
await handleDownload();
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
setCached(false);
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
|
||||
const onPress = () => {
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
if (cached) {
|
||||
togglePlayPause();
|
||||
return;
|
||||
}
|
||||
handleDownload();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleCache = async () => {
|
||||
const cachedAudioResult = await getMediaCache({
|
||||
type: 'audio',
|
||||
mimeType: file.audio_type,
|
||||
urlToCache: getUrl()
|
||||
});
|
||||
if (cachedAudioResult?.exists) {
|
||||
await loadAudio(cachedAudioResult.uri);
|
||||
setLoading(false);
|
||||
setCached(true);
|
||||
return;
|
||||
}
|
||||
if (isReply) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
await handleAutoDownload();
|
||||
};
|
||||
handleCache();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (paused) {
|
||||
deactivateKeepAwake();
|
||||
} else {
|
||||
activateKeepAwake();
|
||||
}
|
||||
}, [paused]);
|
||||
|
||||
if (!baseUrl) {
|
||||
return null;
|
||||
|
@ -200,11 +25,7 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMe
|
|||
return (
|
||||
<>
|
||||
<Markdown msg={msg} style={[isReply && style]} username={user.username} getCustomEmoji={getCustomEmoji} />
|
||||
<View style={[styles.audioContainer, { backgroundColor: colors.surfaceTint, borderColor: colors.strokeExtraLight }]}>
|
||||
<PlayButton disabled={isReply} loading={loading} paused={paused} cached={cached} onPress={onPress} />
|
||||
<Slider currentTime={currentTime} duration={duration} loaded={!isReply && cached} onChangeTime={setPosition} />
|
||||
<AudioRate onChange={onChangeRate} loaded={!isReply && cached} rate={rate} />
|
||||
</View>
|
||||
<AudioPlayer file={file} baseUrl={baseUrl} user={user} author={author} isReply={isReply} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue