update,
This commit is contained in:
95
task1/_poc/useAudioPlayer-main/src/HowlInstanceManager.ts
Normal file
95
task1/_poc/useAudioPlayer-main/src/HowlInstanceManager.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { Howl, HowlOptions } from "howler"
|
||||
|
||||
import { AudioLoadOptions } from "./types"
|
||||
import { Action, ActionTypes } from "./audioPlayerState"
|
||||
|
||||
export type AudioActionCallback = (action: Action) => void
|
||||
|
||||
export class HowlInstanceManager {
|
||||
private callbacks: Map<string, AudioActionCallback> = new Map()
|
||||
private howl: Howl | undefined = undefined
|
||||
private options: AudioLoadOptions = {}
|
||||
private subscriptionIndex = 0
|
||||
|
||||
public subscribe(cb: AudioActionCallback): string {
|
||||
const id = (this.subscriptionIndex++).toString()
|
||||
this.callbacks.set(id, cb)
|
||||
return id
|
||||
}
|
||||
|
||||
public unsubscribe(subscriptionId: string) {
|
||||
this.callbacks.delete(subscriptionId)
|
||||
}
|
||||
|
||||
public getHowl() {
|
||||
return this.howl
|
||||
}
|
||||
|
||||
public getNumberOfConnections() {
|
||||
return this.callbacks.size
|
||||
}
|
||||
|
||||
public createHowl(options: { src: string } & AudioLoadOptions) {
|
||||
this.destroyHowl()
|
||||
|
||||
this.options = options
|
||||
const {
|
||||
initialVolume,
|
||||
initialRate,
|
||||
initialMute,
|
||||
...rest
|
||||
} = this.options
|
||||
const newHowl = new Howl({
|
||||
mute: initialMute,
|
||||
volume: initialVolume,
|
||||
rate: initialRate,
|
||||
...rest
|
||||
} as HowlOptions)
|
||||
|
||||
this.callbacks.forEach(cb =>
|
||||
cb({ type: ActionTypes.START_LOAD, howl: newHowl })
|
||||
)
|
||||
this.howl = newHowl
|
||||
return newHowl
|
||||
}
|
||||
|
||||
public destroyHowl() {
|
||||
if (this.options.onload) {
|
||||
this.howl?.off("load", this.options.onload)
|
||||
}
|
||||
|
||||
if (this.options.onend) {
|
||||
this.howl?.off("end", this.options.onend)
|
||||
}
|
||||
|
||||
if (this.options.onplay) {
|
||||
this.howl?.off("play", this.options.onplay)
|
||||
}
|
||||
|
||||
if (this.options.onpause) {
|
||||
this.howl?.off("pause", this.options.onpause)
|
||||
}
|
||||
|
||||
if (this.options.onstop) {
|
||||
this.howl?.off("stop", this.options.onstop)
|
||||
}
|
||||
|
||||
this.howl?.unload()
|
||||
}
|
||||
|
||||
public broadcast(action: Action) {
|
||||
this.callbacks.forEach(cb => cb(action))
|
||||
}
|
||||
}
|
||||
|
||||
export class HowlInstanceManagerSingleton {
|
||||
private static instance: HowlInstanceManager
|
||||
|
||||
public static getInstance() {
|
||||
if (this.instance === undefined) {
|
||||
HowlInstanceManagerSingleton.instance = new HowlInstanceManager()
|
||||
}
|
||||
|
||||
return HowlInstanceManagerSingleton.instance
|
||||
}
|
||||
}
|
170
task1/_poc/useAudioPlayer-main/src/audioPlayerState.ts
Normal file
170
task1/_poc/useAudioPlayer-main/src/audioPlayerState.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { Howl } from "howler"
|
||||
|
||||
export enum ActionTypes {
|
||||
START_LOAD = "START_LOAD",
|
||||
ON_LOAD = "ON_LOAD",
|
||||
ON_ERROR = "ON_ERROR",
|
||||
ON_PLAY = "ON_PLAY",
|
||||
ON_PAUSE = "ON_PAUSE",
|
||||
ON_STOP = "ON_STOP",
|
||||
ON_END = "ON_END",
|
||||
ON_RATE = "ON_RATE",
|
||||
ON_MUTE = "ON_MUTE",
|
||||
ON_VOLUME = "ON_VOLUME",
|
||||
ON_LOOP = "ON_LOOP"
|
||||
}
|
||||
|
||||
export type StartLoadAction = {
|
||||
type: ActionTypes.START_LOAD
|
||||
linkMediaSession?: boolean
|
||||
howl: Howl
|
||||
}
|
||||
|
||||
// TODO: the main state reducer should be decoupled from Howler
|
||||
// to accomplish this, each action should describe the type of change using an abstraction rather than passing in the howl
|
||||
export type AudioEventAction = {
|
||||
type: Exclude<ActionTypes, ActionTypes.START_LOAD | ActionTypes.ON_ERROR>
|
||||
howl: Howl
|
||||
toggleValue?: boolean
|
||||
debugId?: string
|
||||
}
|
||||
|
||||
export type ErrorEvent = {
|
||||
type: ActionTypes.ON_ERROR
|
||||
message: string
|
||||
}
|
||||
|
||||
export type Action = StartLoadAction | AudioEventAction | ErrorEvent
|
||||
|
||||
export interface AudioPlayerState {
|
||||
src: string | null
|
||||
looping: boolean
|
||||
isReady: boolean
|
||||
isLoading: boolean
|
||||
paused: boolean
|
||||
stopped: boolean
|
||||
playing: boolean
|
||||
duration: number
|
||||
muted: boolean
|
||||
rate: number
|
||||
volume: number
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export function initStateFromHowl(howl?: Howl): AudioPlayerState {
|
||||
if (howl === undefined) {
|
||||
return {
|
||||
src: null,
|
||||
isReady: false,
|
||||
isLoading: false,
|
||||
looping: false,
|
||||
duration: 0,
|
||||
rate: 1,
|
||||
volume: 1,
|
||||
muted: false,
|
||||
playing: false,
|
||||
paused: false,
|
||||
stopped: false,
|
||||
error: null
|
||||
}
|
||||
}
|
||||
|
||||
const position = howl.seek()
|
||||
const playing = howl.playing()
|
||||
|
||||
return {
|
||||
isReady: howl.state() === "loaded",
|
||||
isLoading: howl.state() === "loading",
|
||||
// @ts-ignore _src exists
|
||||
src: howl._src,
|
||||
looping: howl.loop(),
|
||||
duration: howl.duration(),
|
||||
rate: howl.rate(),
|
||||
volume: howl.volume(),
|
||||
muted: howl.mute(),
|
||||
playing,
|
||||
paused: !playing,
|
||||
stopped: !playing && position === 0,
|
||||
error: null
|
||||
}
|
||||
}
|
||||
|
||||
export function reducer(state: AudioPlayerState, action: Action) {
|
||||
switch (action.type) {
|
||||
case ActionTypes.START_LOAD:
|
||||
return {
|
||||
// when called without a Howl object it will return an empty/init state object
|
||||
...initStateFromHowl(),
|
||||
isLoading: true
|
||||
}
|
||||
case ActionTypes.ON_LOAD:
|
||||
// in React 18 there is a weird race condition where ON_LOAD receives a Howl object that has been unloaded
|
||||
// if we detect this case just return the existing state to wait for another action
|
||||
if (action.howl.state() === "unloaded") {
|
||||
return state
|
||||
}
|
||||
return initStateFromHowl(action.howl)
|
||||
case ActionTypes.ON_ERROR:
|
||||
return {
|
||||
// this essentially resets state when called with undefined
|
||||
...initStateFromHowl(),
|
||||
error: action.message
|
||||
}
|
||||
case ActionTypes.ON_PLAY:
|
||||
return {
|
||||
...state,
|
||||
playing: true,
|
||||
paused: false,
|
||||
stopped: false
|
||||
}
|
||||
case ActionTypes.ON_PAUSE:
|
||||
return {
|
||||
...state,
|
||||
playing: false,
|
||||
paused: true
|
||||
}
|
||||
case ActionTypes.ON_STOP: {
|
||||
return {
|
||||
...state,
|
||||
playing: false,
|
||||
paused: false,
|
||||
stopped: true
|
||||
}
|
||||
}
|
||||
case ActionTypes.ON_END: {
|
||||
return {
|
||||
...state,
|
||||
playing: state.looping,
|
||||
stopped: !state.looping
|
||||
}
|
||||
}
|
||||
case ActionTypes.ON_MUTE: {
|
||||
return {
|
||||
...state,
|
||||
muted: action.howl.mute() ?? false
|
||||
}
|
||||
}
|
||||
case ActionTypes.ON_RATE: {
|
||||
return {
|
||||
...state,
|
||||
rate: action.howl?.rate() ?? 1.0
|
||||
}
|
||||
}
|
||||
case ActionTypes.ON_VOLUME: {
|
||||
return {
|
||||
...state,
|
||||
volume: action.howl?.volume() ?? 1.0
|
||||
}
|
||||
}
|
||||
case ActionTypes.ON_LOOP: {
|
||||
const { toggleValue = false, howl } = action
|
||||
howl.loop(toggleValue)
|
||||
return {
|
||||
...state,
|
||||
looping: toggleValue
|
||||
}
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
3
task1/_poc/useAudioPlayer-main/src/index.ts
Normal file
3
task1/_poc/useAudioPlayer-main/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./useAudioPlayer"
|
||||
export * from "./useGlobalAudioPlayer"
|
||||
export * from "./types"
|
36
task1/_poc/useAudioPlayer-main/src/types.ts
Normal file
36
task1/_poc/useAudioPlayer-main/src/types.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { AudioPlayerState } from "./audioPlayerState"
|
||||
|
||||
export interface AudioPlayer extends AudioPlayerState {
|
||||
play: () => void
|
||||
pause: () => void
|
||||
togglePlayPause: () => void
|
||||
stop: () => void
|
||||
setVolume: (volume: number) => void
|
||||
fade: (from: number, to: number, duration: number) => void
|
||||
setRate: (speed: number) => void
|
||||
seek: (seconds: number) => void
|
||||
mute: (muteOnOff: boolean) => void
|
||||
loop: (loopOnOff: boolean) => void
|
||||
getPosition: () => number
|
||||
load: (...args: LoadArguments) => void
|
||||
}
|
||||
|
||||
export interface UserListeners {
|
||||
onstop?: () => void | undefined
|
||||
onpause?: () => void | undefined
|
||||
onload?: () => void | undefined
|
||||
onend?: () => void | undefined
|
||||
onplay?: () => void | undefined
|
||||
}
|
||||
|
||||
export interface AudioLoadOptions extends UserListeners {
|
||||
loop?: boolean
|
||||
autoplay?: boolean
|
||||
initialVolume?: number
|
||||
initialMute?: boolean
|
||||
initialRate?: number
|
||||
format?: string
|
||||
html5?: boolean
|
||||
}
|
||||
|
||||
export type LoadArguments = [src: string, options?: AudioLoadOptions]
|
170
task1/_poc/useAudioPlayer-main/src/useAudioPlayer.ts
Normal file
170
task1/_poc/useAudioPlayer-main/src/useAudioPlayer.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { useCallback, useReducer, useRef } from "react"
|
||||
import {
|
||||
ActionTypes,
|
||||
initStateFromHowl,
|
||||
reducer as audioStateReducer
|
||||
} from "./audioPlayerState"
|
||||
import { useHowlEventSync } from "./useHowlEventSync"
|
||||
import { HowlInstanceManager } from "./HowlInstanceManager"
|
||||
import { AudioPlayer, LoadArguments } from "./types"
|
||||
|
||||
export const useAudioPlayer = (): AudioPlayer & {
|
||||
cleanup: VoidFunction
|
||||
} => {
|
||||
const howlManager = useRef<HowlInstanceManager | null>(null)
|
||||
function getHowlManager() {
|
||||
if (howlManager.current !== null) {
|
||||
return howlManager.current
|
||||
}
|
||||
|
||||
const manager = new HowlInstanceManager()
|
||||
howlManager.current = manager
|
||||
return manager
|
||||
}
|
||||
|
||||
const [state, dispatch] = useHowlEventSync(
|
||||
getHowlManager(),
|
||||
useReducer(
|
||||
audioStateReducer,
|
||||
getHowlManager().getHowl(),
|
||||
initStateFromHowl
|
||||
)
|
||||
)
|
||||
|
||||
const load = useCallback((...[src, options = {}]: LoadArguments) => {
|
||||
// TODO investigate: if we try to avoid loading the same sound (existing howl & same src in call)
|
||||
// then there are some bugs like in the MultipleSounds demo, the "play" button will not switch to "pause"
|
||||
const howl = getHowlManager().createHowl({
|
||||
src,
|
||||
...options
|
||||
})
|
||||
|
||||
dispatch({ type: ActionTypes.START_LOAD, howl })
|
||||
}, [])
|
||||
|
||||
const seek = useCallback((seconds: number) => {
|
||||
const howl = getHowlManager().getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.seek(seconds)
|
||||
}, [])
|
||||
|
||||
const getPosition = useCallback(() => {
|
||||
const howl = getHowlManager().getHowl()
|
||||
if (howl === undefined) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return howl.seek() ?? 0
|
||||
}, [])
|
||||
|
||||
const play = useCallback(() => {
|
||||
const howl = getHowlManager().getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.play()
|
||||
}, [])
|
||||
|
||||
const pause = useCallback(() => {
|
||||
const howl = getHowlManager().getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.pause()
|
||||
}, [])
|
||||
|
||||
const togglePlayPause = useCallback(() => {
|
||||
const howl = getHowlManager().getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (state.playing) {
|
||||
howl.pause()
|
||||
} else {
|
||||
howl.play()
|
||||
}
|
||||
}, [state])
|
||||
|
||||
const stop = useCallback(() => {
|
||||
const howl = getHowlManager().getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.stop()
|
||||
}, [])
|
||||
|
||||
const fade = useCallback((from: number, to: number, duration: number) => {
|
||||
const howl = getHowlManager().getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.fade(from, to, duration)
|
||||
}, [])
|
||||
|
||||
const setRate = useCallback((speed: number) => {
|
||||
const howl = getHowlManager().getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.rate(speed)
|
||||
}, [])
|
||||
|
||||
const setVolume = useCallback((vol: number) => {
|
||||
const howl = getHowlManager().getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.volume(vol)
|
||||
}, [])
|
||||
|
||||
const mute = useCallback((muteOnOff: boolean) => {
|
||||
const howl = getHowlManager().getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.mute(muteOnOff)
|
||||
}, [])
|
||||
|
||||
const loop = useCallback((loopOnOff: boolean) => {
|
||||
const howl = getHowlManager().getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
// this differs from the implementation in useGlobalAudioPlayer which needs to broadcast the action to itself and all other instances of the hook
|
||||
// maybe these two behaviors could be abstracted with one interface in the future
|
||||
dispatch({ type: ActionTypes.ON_LOOP, howl, toggleValue: loopOnOff })
|
||||
}, [])
|
||||
|
||||
const cleanup = useCallback(() => {
|
||||
getHowlManager()?.destroyHowl()
|
||||
}, [])
|
||||
|
||||
return {
|
||||
...state,
|
||||
load,
|
||||
seek,
|
||||
getPosition,
|
||||
play,
|
||||
pause,
|
||||
togglePlayPause,
|
||||
stop,
|
||||
mute,
|
||||
fade,
|
||||
setRate,
|
||||
setVolume,
|
||||
loop,
|
||||
cleanup
|
||||
}
|
||||
}
|
174
task1/_poc/useAudioPlayer-main/src/useGlobalAudioPlayer.ts
Normal file
174
task1/_poc/useAudioPlayer-main/src/useGlobalAudioPlayer.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { useCallback, useEffect, useReducer, useRef } from "react"
|
||||
import {
|
||||
Action,
|
||||
ActionTypes,
|
||||
initStateFromHowl,
|
||||
reducer as audioStateReducer
|
||||
} from "./audioPlayerState"
|
||||
import { useHowlEventSync } from "./useHowlEventSync"
|
||||
import { HowlInstanceManagerSingleton } from "./HowlInstanceManager"
|
||||
import { AudioPlayer, LoadArguments } from "./types"
|
||||
|
||||
export function useGlobalAudioPlayer(): AudioPlayer {
|
||||
const howlManager = useRef(HowlInstanceManagerSingleton.getInstance())
|
||||
|
||||
const [state, dispatch] = useHowlEventSync(
|
||||
howlManager.current,
|
||||
useReducer(
|
||||
audioStateReducer,
|
||||
howlManager.current.getHowl(),
|
||||
initStateFromHowl
|
||||
)
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const howlOnMount = howlManager.current.getHowl()
|
||||
if (howlOnMount !== undefined) {
|
||||
dispatch({ type: ActionTypes.START_LOAD, howl: howlOnMount })
|
||||
if (howlOnMount.state() === "loaded") {
|
||||
dispatch({ type: ActionTypes.ON_LOAD, howl: howlOnMount })
|
||||
}
|
||||
}
|
||||
|
||||
function sync(action: Action) {
|
||||
dispatch(action)
|
||||
}
|
||||
|
||||
const subscriptionId = howlManager.current.subscribe(sync)
|
||||
|
||||
return () => {
|
||||
howlManager.current.unsubscribe(subscriptionId)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const load = useCallback((...[src, options = {}]: LoadArguments) => {
|
||||
// the HowlInstanceManager will intercept this newly created howl and broadcast it to registered hooks
|
||||
howlManager.current.createHowl({
|
||||
src,
|
||||
...options
|
||||
})
|
||||
}, [])
|
||||
|
||||
const seek = useCallback((seconds: number) => {
|
||||
const howl = howlManager.current.getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.seek(seconds)
|
||||
}, [])
|
||||
|
||||
const getPosition = useCallback(() => {
|
||||
const howl = howlManager.current.getHowl()
|
||||
if (howl === undefined) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return howl.seek() ?? 0
|
||||
}, [])
|
||||
|
||||
const play = useCallback(() => {
|
||||
const howl = howlManager.current.getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.play()
|
||||
}, [])
|
||||
|
||||
const pause = useCallback(() => {
|
||||
const howl = howlManager.current.getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.pause()
|
||||
}, [])
|
||||
|
||||
const togglePlayPause = useCallback(() => {
|
||||
const howl = howlManager.current.getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (state.playing) {
|
||||
howl.pause()
|
||||
} else {
|
||||
howl.play()
|
||||
}
|
||||
}, [state])
|
||||
|
||||
const stop = useCallback(() => {
|
||||
const howl = howlManager.current.getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.stop()
|
||||
}, [])
|
||||
|
||||
const fade = useCallback((from: number, to: number, duration: number) => {
|
||||
const howl = howlManager.current.getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.fade(from, to, duration)
|
||||
}, [])
|
||||
|
||||
const setRate = useCallback((speed: number) => {
|
||||
const howl = howlManager.current.getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.rate(speed)
|
||||
}, [])
|
||||
|
||||
const setVolume = useCallback((vol: number) => {
|
||||
const howl = howlManager.current.getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.volume(vol)
|
||||
}, [])
|
||||
|
||||
const mute = useCallback((muteOnOff: boolean) => {
|
||||
const howl = howlManager.current.getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howl.mute(muteOnOff)
|
||||
}, [])
|
||||
|
||||
const loop = useCallback((loopOnOff: boolean) => {
|
||||
const howl = howlManager.current.getHowl()
|
||||
if (howl === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
howlManager.current.broadcast({
|
||||
type: ActionTypes.ON_LOOP,
|
||||
howl,
|
||||
toggleValue: loopOnOff
|
||||
})
|
||||
}, [])
|
||||
|
||||
return {
|
||||
...state,
|
||||
load,
|
||||
seek,
|
||||
getPosition,
|
||||
play,
|
||||
pause,
|
||||
togglePlayPause,
|
||||
stop,
|
||||
mute,
|
||||
fade,
|
||||
setRate,
|
||||
setVolume,
|
||||
loop
|
||||
}
|
||||
}
|
119
task1/_poc/useAudioPlayer-main/src/useHowlEventSync.ts
Normal file
119
task1/_poc/useAudioPlayer-main/src/useHowlEventSync.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
Dispatch,
|
||||
ReducerAction,
|
||||
ReducerState,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef
|
||||
} from "react"
|
||||
import {
|
||||
Action,
|
||||
ActionTypes,
|
||||
AudioPlayerState,
|
||||
reducer
|
||||
} from "./audioPlayerState"
|
||||
import { HowlInstanceManager } from "./HowlInstanceManager"
|
||||
import { HowlErrorCallback } from "howler"
|
||||
|
||||
export function useHowlEventSync(
|
||||
howlManager: HowlInstanceManager,
|
||||
[state, dispatch]: [AudioPlayerState, Dispatch<Action>]
|
||||
): [ReducerState<typeof reducer>, Dispatch<ReducerAction<typeof reducer>>] {
|
||||
const onLoad = useCallback(() => {
|
||||
const howl = howlManager.getHowl()
|
||||
if (howl === undefined) return
|
||||
dispatch({ type: ActionTypes.ON_LOAD, howl })
|
||||
}, [dispatch, howlManager])
|
||||
|
||||
const onError: HowlErrorCallback = useCallback(
|
||||
(_: number, errorCode: unknown) => {
|
||||
dispatch({
|
||||
type: ActionTypes.ON_ERROR,
|
||||
message: errorCode as string
|
||||
})
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const onPlay = useCallback(() => {
|
||||
const howl = howlManager.getHowl()
|
||||
// TODO since this is the sync layer i should really extract the info from the howl here and pass that in with the action payload
|
||||
if (howl === undefined) return
|
||||
dispatch({ type: ActionTypes.ON_PLAY, howl })
|
||||
}, [dispatch, howlManager])
|
||||
|
||||
const onPause = useCallback(() => {
|
||||
const howl = howlManager.getHowl()
|
||||
if (howl === undefined) return
|
||||
dispatch({ type: ActionTypes.ON_PAUSE, howl })
|
||||
}, [dispatch, howlManager])
|
||||
|
||||
const onEnd = useCallback(() => {
|
||||
const howl = howlManager.getHowl()
|
||||
if (howl === undefined) return
|
||||
dispatch({ type: ActionTypes.ON_END, howl })
|
||||
}, [dispatch, howlManager])
|
||||
|
||||
const onStop = useCallback(() => {
|
||||
const howl = howlManager.getHowl()
|
||||
if (howl === undefined) return
|
||||
dispatch({ type: ActionTypes.ON_STOP, howl })
|
||||
}, [dispatch, howlManager])
|
||||
|
||||
const onMute = useCallback(() => {
|
||||
const howl = howlManager.getHowl()
|
||||
if (howl === undefined) return
|
||||
dispatch({ type: ActionTypes.ON_MUTE, howl })
|
||||
}, [dispatch, howlManager])
|
||||
|
||||
const onVolume = useCallback(() => {
|
||||
const howl = howlManager.getHowl()
|
||||
if (howl === undefined) return
|
||||
dispatch({ type: ActionTypes.ON_VOLUME, howl })
|
||||
}, [dispatch, howlManager])
|
||||
|
||||
const onRate = useCallback(() => {
|
||||
const howl = howlManager.getHowl()
|
||||
if (howl === undefined) return
|
||||
dispatch({ type: ActionTypes.ON_RATE, howl })
|
||||
}, [dispatch, howlManager])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
const howl = howlManager.getHowl()
|
||||
// howl?.off("load", onLoad)
|
||||
howl?.off("loaderror", onError)
|
||||
howl?.off("playerror", onError)
|
||||
howl?.off("play", onPlay)
|
||||
howl?.off("pause", onPause)
|
||||
howl?.off("end", onEnd)
|
||||
howl?.off("stop", onStop)
|
||||
howl?.off("mute", onMute)
|
||||
howl?.off("volume", onVolume)
|
||||
howl?.off("rate", onRate)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// using ref bc we don't want identity of dispatch function to change
|
||||
// see talk: https://youtu.be/nUzLlHFVXx0?t=1558
|
||||
const wrappedDispatch = useRef((action: Action) => {
|
||||
if (action.type === ActionTypes.START_LOAD) {
|
||||
const { howl } = action
|
||||
// set up event listening
|
||||
howl.once("load", onLoad)
|
||||
howl.on("loaderror", onError)
|
||||
howl.on("playerror", onError)
|
||||
howl.on("play", onPlay)
|
||||
howl.on("pause", onPause)
|
||||
howl.on("end", onEnd)
|
||||
howl.on("stop", onStop)
|
||||
howl.on("mute", onMute)
|
||||
howl.on("volume", onVolume)
|
||||
howl.on("rate", onRate)
|
||||
}
|
||||
|
||||
dispatch(action)
|
||||
})
|
||||
|
||||
return [state, wrappedDispatch.current]
|
||||
}
|
Reference in New Issue
Block a user