moved the player from messageAudio to audioPlayer

This commit is contained in:
Reinaldo Neto 2023-10-03 17:23:52 -03:00
parent 182af31fb2
commit 8b053f630e
7 changed files with 237 additions and 207 deletions

View File

@ -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);

View File

@ -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';

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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
}

View File

@ -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} />
</>
);
};