adding playback controls for audio and video

This commit is contained in:
balzack 2025-01-05 13:14:04 -08:00
parent b3dc1459ca
commit ee8169f168
5 changed files with 131 additions and 17 deletions

View File

@ -1,5 +1,5 @@
import {StyleSheet} from 'react-native';
import {Colors} from '../constants/Colors';
import {Colors} from '../../constants/Colors';
export const styles = StyleSheet.create({
modal: {
@ -9,6 +9,12 @@ export const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
},
failed: {
color: Colors.offsync,
},
control: {
backgroundColor: 'transparent',
},
button: {
position: 'absolute',
borderRadius: 4,
@ -44,12 +50,30 @@ export const styles = StyleSheet.create({
width: '100%',
height: '100%',
},
close: {
info: {
fontSize: 12,
position: 'absolute',
top: 0,
right: 0,
},
label: {
flexGrow: 1,
fontSize: 32,
paddingLeft: 16,
minWidth: 0,
textOverflow: 'ellipsis',
flexShrink: 1,
},
close: {
display: 'flex',
flexDirection: 'row',
position: 'absolute',
alignItems: 'center',
top: 0,
minWidth: 0,
width: '100%',
},
closeIcon: {
flexShrink: 0,
backgroundColor: 'transparent',
},
progress: {

View File

@ -1,17 +1,20 @@
import React, { useState, useEffect, useRef } from 'react';
import { SafeAreaView, Modal, Pressable, View, Image, Animated, useAnimatedValue } from 'react-native'
import { Icon, ProgressBar, IconButton } from 'react-native-paper'
import { Icon, Text, ProgressBar, IconButton } from 'react-native-paper'
import { useAudioAsset } from './useAudioAsset.hook';
import { MediaAsset } from '../../conversation/Conversation';
import { styles } from './AudioAsset.styled'
import {BlurView} from '@react-native-community/blur';
import Video from 'react-native-video'
import Video, { VideoRef } from 'react-native-video'
import thumb from '../../images/audio.png';
import {Colors} from '../../constants/Colors';
export function AudioAsset({ topicId, asset, loaded, show }: { topicId: string, asset: MediaAsset, loaded: ()=>void, show: boolean }) {
const { state, actions } = useAudioAsset(topicId, asset);
const [modal, setModal] = useState(false);
const opacity = useAnimatedValue(0);
const videoRef = useRef<VideoRef>(null as null | VideoRef);
const [status, setStatus] = useState('loading');
useEffect(() => {
if (show) {
@ -33,6 +36,30 @@ export function AudioAsset({ topicId, asset, loaded, show }: { topicId: string,
actions.cancelLoad();
}
const play = () => {
videoRef.current.resume();
}
const pause = () => {
videoRef.current.pause();
}
const error = () => {
setStatus('failed');
}
const end = () => {
videoRef.current.seek(0);
}
const playbackRateChange = (e) => {
if (e.playbackRate === 0) {
setStatus('paused');
} else {
setStatus('playing');
}
}
return (
<View style={styles.audio}>
<Pressable onPress={showAudio}>
@ -48,6 +75,7 @@ export function AudioAsset({ topicId, asset, loaded, show }: { topicId: string,
<View style={styles.button}>
<Icon size={28} source="play-box-outline" />
</View>
<Text style={styles.info} numberOfLines={1}>{ asset.audio?.label || asset.encrypted?.label }</Text>
</Animated.View>
</Pressable>
<Modal animationType="fade" transparent={true} supportedOrientations={['portrait', 'landscape']} visible={modal} onRequestClose={hideAudio}>
@ -59,8 +87,18 @@ export function AudioAsset({ topicId, asset, loaded, show }: { topicId: string,
source={thumb}
/>
{ state.dataUrl && (
<Video source={{ uri: state.dataUrl }} style={styles.full} paused={false}
onLoad={(e)=>console.log(e)} onError={(e)=>console.log(e)} controls={false} resizeMode="contain" />
<Video source={{ uri: state.dataUrl }} style={styles.full} paused={false} ref={videoRef}
onPlaybackRateChange={playbackRateChange} onEnd={end} onError={error}
controls={false} resizeMode="contain" />
)}
{ status === 'failed' && (
<Icon color={Colors.offsync} size={64} source="fire" />
)}
{ status === 'playing' && (
<IconButton style={styles.control} size={64} icon="pause" onPress={pause} />
)}
{ status === 'paused' && (
<IconButton style={styles.control} size={64} icon="play" onPress={play} />
)}
{ state.loading && (
<View style={styles.progress}>
@ -68,6 +106,7 @@ export function AudioAsset({ topicId, asset, loaded, show }: { topicId: string,
</View>
)}
<SafeAreaView style={styles.close}>
<Text style={styles.label} adjustsFontSizeToFit={true} numberOfLines={1}>{ asset.audio?.label || asset.encrypted?.label }</Text>
<IconButton style={styles.closeIcon} icon="close" compact="true" mode="contained" size={28} onPress={hideAudio} />
</SafeAreaView>
</View>

View File

@ -31,15 +31,13 @@ export function useAudioAsset(topicId: string, asset: MediaAsset) {
updateState({ loading: true, loadPercent: 0 });
try {
const dataUrl = await focus.getTopicAssetUrl(topicId, assetId, (loadPercent: number)=>{ updateState({ loadPercent }); return !cancelled.current });
console.log("AUDIO", dataUrl);
updateState({ dataUrl });
} catch (err) {
console.log(err);
}
updateState({ loading: false });
}
}
},
}
return { state, actions }

View File

@ -1,5 +1,5 @@
import {StyleSheet} from 'react-native';
import {Colors} from '../constants/Colors';
import {Colors} from '../../constants/Colors';
export const styles = StyleSheet.create({
modal: {
@ -9,6 +9,13 @@ export const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
},
failed: {
color: Colors.offsync,
},
control: {
position: 'absolute',
backgroundColor: 'transparent',
},
button: {
position: 'absolute',
borderRadius: 13,

View File

@ -5,13 +5,17 @@ import { useVideoAsset } from './useVideoAsset.hook';
import { MediaAsset } from '../../conversation/Conversation';
import { styles } from './VideoAsset.styled'
import {BlurView} from '@react-native-community/blur';
import Video from 'react-native-video'
import Video, { VideoRef } from 'react-native-video'
export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string, asset: MediaAsset, loaded: ()=>void, show: boolean }) {
const { state, actions } = useVideoAsset(topicId, asset);
const [modal, setModal] = useState(false);
const opacity = useAnimatedValue(0);
const videoRef = useRef<VideoRef>(null as null | VideoRef);
const [status, setStatus] = useState('loading');
const [showControl, setShowControl] = useState(false);
const clear = useRef();
useEffect(() => {
if (state.loaded && show) {
Animated.timing(opacity, {
@ -35,6 +39,38 @@ export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string,
actions.cancelLoad();
}
const controls = () => {
clearTimeout(clear.current);
setShowControl(true);
clear.current = setTimeout(() => {
setShowControl(false);
}, 3000);
}
const play = () => {
videoRef.current.resume();
}
const pause = () => {
videoRef.current.pause();
}
const error = () => {
setStatus('failed');
}
const end = () => {
videoRef.current.seek(0);
}
const playbackRateChange = (e) => {
if (e.playbackRate === 0) {
setStatus('paused');
} else {
setStatus('playing');
}
}
return (
<View style={styles.video}>
{ state.thumbUrl && (
@ -54,7 +90,7 @@ export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string,
</Pressable>
)}
<Modal animationType="fade" transparent={true} supportedOrientations={['portrait', 'landscape']} visible={modal} onRequestClose={hideVideo}>
<View style={styles.modal}>
<Pressable style={styles.modal} onPress={controls}>
<BlurView style={styles.blur} blurType="dark" blurAmount={16} reducedTransparencyFallbackColor="dark" />
<Image
style={styles.full}
@ -62,8 +98,18 @@ export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string,
source={{ uri: state.thumbUrl }}
/>
{ state.dataUrl && (
<Video source={{ uri: state.dataUrl }} style={styles.full}
controls={false} resizeMode="contain" />
<Video source={{ uri: state.dataUrl }} style={styles.full} ref={videoRef}
onPlaybackRateChange={playbackRateChange} onEnd={end} onError={error}
controls={false} resizeMode="contain" />
)}
{ status === 'failed' && (
<Icon color={Colors.offsync} size={64} source="fire" />
)}
{ status === 'playing' && showControl && (
<IconButton style={styles.control} size={64} icon="pause" onPress={pause} />
)}
{ status === 'paused' && showControl && (
<IconButton style={styles.control} size={64} icon="play" onPress={play} />
)}
{ state.loading && (
<View style={styles.progress}>
@ -73,7 +119,7 @@ export function VideoAsset({ topicId, asset, loaded, show }: { topicId: string,
<SafeAreaView style={styles.close}>
<IconButton style={styles.closeIcon} icon="close" compact="true" mode="contained" size={28} onPress={hideVideo} />
</SafeAreaView>
</View>
</Pressable>
</Modal>
</View>
);