1- import React , { useCallback , useEffect , useRef } from "react" ;
1+ import React , {
2+ useCallback ,
3+ useEffect ,
4+ useMemo ,
5+ useRef ,
6+ useState ,
7+ } from "react" ;
28import { useSyncExternalStore } from "use-sync-external-store/shim/index.js" ;
9+ import { computePercentage } from "../utils/computePercentage" ;
310
411type CreateAudioElementOptions = {
512 initialVolume ?: number ;
@@ -69,8 +76,88 @@ function useCreateAudioElement(options?: CreateAudioElementOptions) {
6976 return audioElementRef ;
7077}
7178
79+ function useProgressBar ( audio : ReturnType < typeof useCreateAudioElement > ) {
80+ const progressBarRef = useRef < HTMLDivElement > ( null ) ;
81+ const [ barState , setBarState ] = useState ( {
82+ percentage : 0 ,
83+ isThumbDown : false ,
84+ } ) ;
85+
86+ const seek = useCallback (
87+ ( second : number ) => {
88+ if ( audio . current && ! Number . isNaN ( second ) ) {
89+ audio . current . currentTime = second ;
90+ }
91+ } ,
92+ [ audio ]
93+ ) ;
94+
95+ const handlePercentage = useCallback ( ( percentage : number ) => {
96+ if ( ! Number . isNaN ( percentage ) ) {
97+ setBarState ( ( prev ) => ( { ...prev , percentage : percentage } ) ) ;
98+ }
99+ } , [ ] ) ;
100+
101+ const handleIsThumbDown = useCallback ( ( is : boolean ) => {
102+ setBarState ( ( prev ) => ( { ...prev , isThumbDown : is } ) ) ;
103+ } , [ ] ) ;
104+
105+ const thumbMove = useCallback (
106+ ( e : MouseEvent ) => {
107+ if ( ! progressBarRef . current || ! audio . current ) return ;
108+
109+ const percentage = computePercentage ( e , progressBarRef ) ;
110+ handlePercentage ( percentage ) ;
111+ } ,
112+ [ audio , handlePercentage ]
113+ ) ;
114+
115+ const thumbUp = useCallback (
116+ ( e : MouseEvent ) => {
117+ if ( ! progressBarRef . current || ! audio . current ) return ;
118+
119+ document . removeEventListener ( "mouseup" , thumbUp ) ;
120+ document . removeEventListener ( "mousemove" , thumbMove ) ;
121+ const percentage = computePercentage ( e , progressBarRef ) ;
122+ const currentTime = audio . current . duration * percentage ;
123+
124+ handlePercentage ( percentage ) ;
125+ handleIsThumbDown ( false ) ;
126+ seek ( currentTime ) ;
127+ } ,
128+ [ audio , handleIsThumbDown , handlePercentage , seek , thumbMove ]
129+ ) ;
130+
131+ useEffect ( ( ) => {
132+ const ref = progressBarRef . current ;
133+ if ( ref ) {
134+ ref . addEventListener ( "mousedown" , ( e ) => {
135+ handleIsThumbDown ( true ) ;
136+ const percentage = computePercentage ( e , progressBarRef ) ;
137+ handlePercentage ( percentage ) ;
138+
139+ document . addEventListener ( "mousemove" , thumbMove ) ;
140+ document . addEventListener ( "mouseup" , thumbUp ) ;
141+ } ) ;
142+ }
143+
144+ return ( ) => {
145+ if ( ref ) {
146+ ref . removeEventListener ( "mousedown" , ( ) => {
147+ document . addEventListener ( "mousemove" , thumbMove ) ;
148+ document . addEventListener ( "mouseup" , thumbUp ) ;
149+ } ) ;
150+ }
151+ } ;
152+ // eslint-disable-next-line react-hooks/exhaustive-deps
153+ } , [ ] ) ;
154+
155+ return { progressBarRef, barState } ;
156+ }
157+
72158export function useAudioControl ( options : CreateAudioElementOptions ) {
73159 const audioElementRef = useCreateAudioElement ( options ) ;
160+ const { progressBarRef, barState } = useProgressBar ( audioElementRef ) ;
74161
75162 const playAudio = useCallback (
76163 async ( src : string ) => {
@@ -293,6 +380,12 @@ export function useAudioControl(options: CreateAudioElementOptions) {
293380 ( ) => undefined
294381 ) ;
295382
383+ const playedPercentage = useMemo ( ( ) => {
384+ if ( barState . isThumbDown ) return barState . percentage ;
385+ if ( ! currentTime || ! duration ) return 0 ;
386+ return currentTime / duration ;
387+ } , [ barState . isThumbDown , barState . percentage , currentTime , duration ] ) ;
388+
296389 return {
297390 volume,
298391 setVolume,
@@ -306,5 +399,7 @@ export function useAudioControl(options: CreateAudioElementOptions) {
306399 togglePlay,
307400 seek,
308401 isLoading,
402+ progressBarRef,
403+ playedPercentage,
309404 } ;
310405}
0 commit comments