import React , { useState , useCallback , useEffect , useRef , useReducer , useMemo } from "react" ; import { Howl , HowlOptions } from "howler" ; import { initialState , reducer , Actions } from "./audioPlayerState" ; import { playerContext , positionContext } from "./context" ; import { AudioPlayerContext } from "./types" ; export interface AudioPlayerProviderProps { children : React . ReactNode ; value ? : AudioPlayerContext ; } export function AudioPlayerProvider ( { children , value } ) { const [ player , setPlayer ] = useState < Howl | null > ( null ) ; const [ { loading , error , playing , stopped , duration , ready , ended } , dispatch ] = useReducer ( reducer , initialState ) ; const playerRef = useRef < Howl > ( ) ; const prevPlayer = useRef < Howl > ( ) ; const [ position , setPosition ] = useState ( 0 ) ; const positionContextValue = useMemo ( ( ) => ( { position , setPosition } ) , [ position , setPosition ] ) ; const constructHowl = useCallback ( ( audioProps ) : Howl => { return new Howl ( audioProps ) ; } , [ ] ) ; const load = useCallback ( ( { src , autoplay = false , html5 = false , ... rest } ) => { let wasPlaying = false ; if ( playerRef . current ) { const { _src } = playerRef . current ; const prevSrc = Array . isArray ( _src ) ? _src [ 0 ] : _src ; if ( prevSrc === src ) return ; if ( loading ) { prevPlayer . current = playerRef . current ; prevPlayer . current . once ( "load" , ( ) => { prevPlayer . current ?. unload ( ) ; } ) ; } wasPlaying = playerRef . current . playing ( ) ; if ( wasPlaying ) { playerRef . current . stop ( ) ; playerRef . current . off ( ) ; playerRef . current = undefined ; } } dispatch ( { type : Actions . START_LOAD } ) ; const howl = constructHowl ( { src , autoplay : wasPlaying || autoplay , html5 , ... rest } ) ; if ( howl . _state === "loaded" ) { dispatch ( { type : Actions . ON_LOAD , duration : howl . duration ( ) } ) ; } howl . on ( "load" , ( ) => void dispatch ( { type : Actions . ON_LOAD , duration : howl . duration ( ) } ) ) ; howl . on ( "play" , ( ) => void dispatch ( { type : Actions . ON_PLAY } ) ) ; howl . on ( "end" , ( ) => void dispatch ( { type : Actions . ON_END } ) ) ; howl . on ( "pause" , ( ) => void dispatch ( { type : Actions . ON_PAUSE } ) ) ; howl . on ( "stop" , ( ) => void dispatch ( { type : Actions . ON_STOP } ) ) ; howl . on ( "playerror" , ( _id , error ) => { dispatch ( { type : Actions . ON_PLAY_ERROR , error : new Error ( "[Play error] " + error ) } ) ; } ) ; howl . on ( "loaderror" , ( _id , error ) => { dispatch ( { type : Actions . ON_LOAD_ERROR , error : new Error ( "[Load error] " + error ) } ) ; } ) ; setPlayer ( howl ) ; playerRef . current = howl ; } , [ constructHowl , loading ] ) ; useEffect ( ( ) => { return ( ) => { if ( playerRef . current ) playerRef . current . unload ( ) ; } ; } , [ ] ) ; const contextValue = useMemo ( ( ) => { return value ? value : { player , load , error , loading , playing , stopped , ready , duration , ended } ; } , [ loading , error , playing , stopped , load , value , player , ready , duration , ended ] ) ; return ( < playerContext . Provider value = { contextValue } > < positionContext . Provider value = { positionContextValue } > { children } < /positionContext.Provider> < /playerContext.Provider>); }