1- import { useState } from 'react' ;
1+ import { useState , useRef , useEffect } from 'react' ;
22import {
33 Text ,
44 View ,
@@ -11,14 +11,15 @@ import {
1111} from 'react-native' ;
1212
1313import { pick , types } from '@react-native-documents/picker' ;
14+ import Video from 'react-native-video' ;
1415import {
1516 getRawPcmData ,
1617 getWaveformDataByPoints ,
1718 getWaveformData ,
1819 type WaveformMethod ,
1920} from 'react-native-audio-data' ;
2021
21- const WaveformView = ( { data } : { data : number [ ] } ) => {
22+ const WaveformView = ( { data, progress } : { data : number [ ] , progress : number } ) => {
2223 if ( ! data || data . length === 0 ) {
2324 return (
2425 < View style = { [ styles . waveformContainer , styles . emptyContainer ] } >
@@ -45,14 +46,20 @@ const WaveformView = ({ data }: { data: number[] }) => {
4546 { data . map ( ( value , index ) => {
4647 let heightPercent = value * scale * 100 ;
4748 heightPercent = Math . max ( heightPercent , 2 ) ;
49+
50+ // Determine if this bar is "played"
51+ // progress is 0..1. The index corresponding to progress is floor(progress * data.length)
52+ const isPlayed = index / data . length < progress ;
53+
4854 return (
4955 < View
5056 key = { index }
5157 style = { [
5258 styles . bar ,
5359 {
5460 height : `${ heightPercent } %` ,
55- opacity : 0.5 + value * scale * 0.5 ,
61+ opacity : isPlayed ? 1.0 : ( 0.5 + value * scale * 0.5 ) ,
62+ backgroundColor : isPlayed ? '#00ff00' : '#00e5ff' ,
5663 } ,
5764 ] }
5865 />
@@ -97,15 +104,75 @@ export default function App() {
97104 const [ method , setMethod ] = useState < WaveformMethod > ( 'RMS' ) ;
98105 const [ mode , setMode ] = useState < 'points' | 'ms' > ( 'points' ) ;
99106
107+ // Video Player State
108+ const [ paused , setPaused ] = useState ( true ) ;
109+ const [ progress , setProgress ] = useState ( 0 ) ;
110+ const [ duration , setDuration ] = useState ( 0 ) ;
111+
112+ // Animation Refs
113+ const lastUpdate = useRef < { time : number ; timestamp : number } | null > ( null ) ;
114+ const animationFrame = useRef < number | null > ( null ) ;
115+
100116 const methods : WaveformMethod [ ] = [ 'RMS' , 'LUFS' , 'AbsMean' ] ;
101117
118+ const startAnimationLoop = ( ) => {
119+ if ( animationFrame . current ) cancelAnimationFrame ( animationFrame . current ) ;
120+
121+ const loop = ( ) => {
122+ if ( lastUpdate . current && duration > 0 ) {
123+ const now = Date . now ( ) ;
124+ const elapsed = ( now - lastUpdate . current . timestamp ) / 1000 ;
125+ const estimatedTime = lastUpdate . current . time + elapsed ;
126+ const newProgress = Math . min ( estimatedTime / duration , 1.0 ) ;
127+ setProgress ( newProgress ) ;
128+ }
129+ animationFrame . current = requestAnimationFrame ( loop ) ;
130+ } ;
131+ animationFrame . current = requestAnimationFrame ( loop ) ;
132+ } ;
133+
134+ const stopAnimationLoop = ( ) => {
135+ if ( animationFrame . current ) {
136+ cancelAnimationFrame ( animationFrame . current ) ;
137+ animationFrame . current = null ;
138+ }
139+ } ;
140+
141+ // Cleanup on unmount
142+ useEffect ( ( ) => {
143+ return ( ) => stopAnimationLoop ( ) ;
144+ } , [ ] ) ;
145+
146+ const togglePlayback = ( ) => {
147+ const nextPaused = ! paused ;
148+ setPaused ( ( prev ) => ! prev ) ;
149+
150+ if ( nextPaused ) {
151+ stopAnimationLoop ( ) ;
152+ } else {
153+ // Reset last update if starting from scratch or re-playing
154+ if ( progress >= 1 ) {
155+ lastUpdate . current = { time : 0 , timestamp : Date . now ( ) } ;
156+ setProgress ( 0 ) ;
157+ }
158+ startAnimationLoop ( ) ;
159+ }
160+ } ;
161+
102162 const handlePickAndProcess = async ( ) => {
103163 try {
104164 setLoading ( true ) ;
105165 setLog ( 'Picking file...' ) ;
106166 setSelectedPath ( null ) ;
107167 setWaveformData ( [ ] ) ;
108168
169+ // Reset player state
170+ setPaused ( true ) ;
171+ setProgress ( 0 ) ;
172+ setDuration ( 0 ) ;
173+ stopAnimationLoop ( ) ;
174+ lastUpdate . current = null ;
175+
109176 const results = await pick ( {
110177 type : [ types . audio ] ,
111178 allowMultiSelection : false ,
@@ -186,7 +253,47 @@ export default function App() {
186253 </ View >
187254 ) }
188255
189- < WaveformView data = { waveformData } />
256+ < WaveformView data = { waveformData } progress = { progress } />
257+
258+ { selectedPath && (
259+ < View style = { styles . playerContainer } >
260+ < Video
261+ source = { { uri : selectedPath } }
262+ paused = { paused }
263+ onLoad = { ( data ) => {
264+ setDuration ( data . duration ) ;
265+ console . log ( 'Video loaded' , data . duration ) ;
266+ } }
267+ onProgress = { ( data ) => {
268+ // Sync authoritative time
269+ lastUpdate . current = { time : data . currentTime , timestamp : Date . now ( ) } ;
270+
271+ // If paused, we want exact sync. If playing, the loop handles it.
272+ if ( paused && duration > 0 ) {
273+ setProgress ( data . currentTime / duration ) ;
274+ }
275+ } }
276+ onEnd = { ( ) => {
277+ setPaused ( true ) ;
278+ setProgress ( 1 ) ;
279+ stopAnimationLoop ( ) ;
280+ console . log ( 'Video ended' ) ;
281+ } }
282+ onError = { ( e ) => console . log ( 'Video error' , e ) }
283+ audioOnly = { true }
284+ repeat = { false }
285+ ignoreSilentSwitch = "ignore"
286+ progressUpdateInterval = { 250 }
287+ />
288+ < Button
289+ title = { paused ? "Play Audio" : "Pause" }
290+ onPress = { togglePlayback }
291+ />
292+ < Text style = { styles . progressText } >
293+ { ( progress * duration ) . toFixed ( 1 ) } s / { duration . toFixed ( 1 ) } s
294+ </ Text >
295+ </ View >
296+ ) }
190297
191298 < View style = { styles . settingsContainer } >
192299 < View style = { styles . settingRow } >
@@ -260,7 +367,7 @@ const styles = StyleSheet.create({
260367 alignItems : 'stretch' ,
261368 } ,
262369 header : {
263- fontSize : 24 ,
370+ fontSize : 20 ,
264371 fontWeight : 'bold' ,
265372 textAlign : 'center' ,
266373 marginBottom : 20 ,
@@ -427,4 +534,20 @@ const styles = StyleSheet.create({
427534 fontFamily : 'monospace' ,
428535 fontSize : 12 ,
429536 } ,
537+ playerContainer : {
538+ backgroundColor : '#fff' ,
539+ borderRadius : 8 ,
540+ padding : 10 ,
541+ marginBottom : 15 ,
542+ flexDirection : 'row' ,
543+ alignItems : 'center' ,
544+ justifyContent : 'space-between' ,
545+ borderWidth : 1 ,
546+ borderColor : '#ddd' ,
547+ } ,
548+ progressText : {
549+ fontSize : 14 ,
550+ color : '#555' ,
551+ fontWeight : 'bold' ,
552+ } ,
430553} ) ;
0 commit comments