oddysee-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +356 -0
- package/dist/assets/index-COcDBgFa.css +1 -0
- package/dist/assets/index-DzSVBDbV.js +9 -0
- package/dist/assets/react-CHdo91hT.svg +1 -0
- package/dist/index.cjs +199 -0
- package/dist/index.d.cts +49 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.html +14 -0
- package/dist/index.js +174 -0
- package/dist/vite.svg +1 -0
- package/eslint.config.js +23 -0
- package/package.json +46 -0
- package/public/vite.svg +1 -0
- package/src/index.ts +2 -0
- package/src/use-hls-audio-player.ts +241 -0
- package/tsconfig.app.json +28 -0
- package/tsconfig.json +7 -0
- package/tsconfig.lib.json +16 -0
- package/tsconfig.node.json +26 -0
- package/vite.config.ts +7 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
HLSAudioPlayer,
|
|
4
|
+
type HLSAudioPlayerInterface,
|
|
5
|
+
type PlayerConfig,
|
|
6
|
+
type SourceOptions,
|
|
7
|
+
type PlayerEvent,
|
|
8
|
+
type PlayerError,
|
|
9
|
+
type Track,
|
|
10
|
+
type QualityLevel,
|
|
11
|
+
type PlayerState
|
|
12
|
+
} from '@hls-audio-player/core'
|
|
13
|
+
|
|
14
|
+
// Local mirror of the core's PlayerEventMap so we don't depend on it being exported
|
|
15
|
+
export type PlayerEventMap = {
|
|
16
|
+
play: void
|
|
17
|
+
pause: void
|
|
18
|
+
'track-end': Track | null
|
|
19
|
+
error: PlayerError
|
|
20
|
+
'quality-change': QualityLevel
|
|
21
|
+
'playlist-ready': void
|
|
22
|
+
loadedmetadata: Track | null
|
|
23
|
+
timeupdate: { currentTime: number; duration: number | null }
|
|
24
|
+
loading: void
|
|
25
|
+
canplay: void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface UseHlsAudioPlayerOptions {
|
|
29
|
+
config?: PlayerConfig
|
|
30
|
+
src?: { url: string; options?: SourceOptions }
|
|
31
|
+
autoPlay?: boolean
|
|
32
|
+
on?: Partial<{
|
|
33
|
+
[K in PlayerEvent]: (data: PlayerEventMap[K]) => void
|
|
34
|
+
}>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface UseHlsAudioPlayerResult {
|
|
38
|
+
player: HLSAudioPlayerInterface | null
|
|
39
|
+
state: PlayerState
|
|
40
|
+
isPlaying: boolean
|
|
41
|
+
duration: number
|
|
42
|
+
isLoading: boolean
|
|
43
|
+
loading: boolean
|
|
44
|
+
error: PlayerError | null
|
|
45
|
+
readyState: number
|
|
46
|
+
controls: {
|
|
47
|
+
setSource: (
|
|
48
|
+
url: string,
|
|
49
|
+
options?: SourceOptions,
|
|
50
|
+
) => Promise<HLSAudioPlayerInterface | null>
|
|
51
|
+
play: () => void
|
|
52
|
+
playAsync: () => Promise<HLSAudioPlayerInterface | null>
|
|
53
|
+
pause: () => void
|
|
54
|
+
setVolume: (volume: number) => void
|
|
55
|
+
setCurrentTime: (time: number) => void
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const defaultState: PlayerState = {
|
|
60
|
+
track: null,
|
|
61
|
+
currentTime: 0,
|
|
62
|
+
duration: null,
|
|
63
|
+
volume: 1,
|
|
64
|
+
loading: false,
|
|
65
|
+
error: null,
|
|
66
|
+
readyState: 0,
|
|
67
|
+
isPlaying: false,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function useHlsAudioPlayer(
|
|
71
|
+
options: UseHlsAudioPlayerOptions = {},
|
|
72
|
+
): UseHlsAudioPlayerResult {
|
|
73
|
+
const { config, src, autoPlay, on } = options
|
|
74
|
+
|
|
75
|
+
const player = useMemo(() => {
|
|
76
|
+
return new HLSAudioPlayer(config)
|
|
77
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
78
|
+
}, [])
|
|
79
|
+
|
|
80
|
+
const [state, setState] = useState<PlayerState>(
|
|
81
|
+
() => player.getState() ?? defaultState,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const [loading, setLoading] = useState<boolean>(player.loading ?? false)
|
|
85
|
+
const [error, setError] = useState<PlayerError | null>(player.error ?? null)
|
|
86
|
+
const [readyState, setReadyState] = useState<number>(player.readyState ?? 0)
|
|
87
|
+
const [isPlaying, setIsPlaying] = useState<boolean>(player.isPlaying ?? false)
|
|
88
|
+
const [duration, setDuration] = useState<number>(player.getState()?.duration ?? 0)
|
|
89
|
+
const [isLoading, setIsLoading] = useState<boolean>(player.loading ?? false)
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
const handleStateChange = () => {
|
|
93
|
+
const next = player.getState()
|
|
94
|
+
setState(next)
|
|
95
|
+
setLoading(next.loading)
|
|
96
|
+
setError(next.error)
|
|
97
|
+
setReadyState(next.readyState)
|
|
98
|
+
setIsPlaying(next.isPlaying)
|
|
99
|
+
setDuration(next.duration ?? 0)
|
|
100
|
+
setIsLoading(next.loading)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const listeners: { [K in PlayerEvent]?: (data: PlayerEventMap[K]) => void } = {
|
|
104
|
+
play: data => {
|
|
105
|
+
handleStateChange()
|
|
106
|
+
on?.play?.(data as PlayerEventMap['play'])
|
|
107
|
+
},
|
|
108
|
+
pause: data => {
|
|
109
|
+
handleStateChange()
|
|
110
|
+
on?.pause?.(data as PlayerEventMap['pause'])
|
|
111
|
+
},
|
|
112
|
+
'track-end': data => {
|
|
113
|
+
handleStateChange()
|
|
114
|
+
on?.['track-end']?.(data as PlayerEventMap['track-end'])
|
|
115
|
+
},
|
|
116
|
+
error: data => {
|
|
117
|
+
handleStateChange()
|
|
118
|
+
on?.error?.(data as PlayerEventMap['error'])
|
|
119
|
+
},
|
|
120
|
+
'quality-change': data => {
|
|
121
|
+
handleStateChange()
|
|
122
|
+
on?.['quality-change']?.(data as PlayerEventMap['quality-change'])
|
|
123
|
+
},
|
|
124
|
+
'playlist-ready': data => {
|
|
125
|
+
handleStateChange()
|
|
126
|
+
on?.['playlist-ready']?.(data as PlayerEventMap['playlist-ready'])
|
|
127
|
+
},
|
|
128
|
+
loadedmetadata: data => {
|
|
129
|
+
handleStateChange()
|
|
130
|
+
on?.loadedmetadata?.(data as PlayerEventMap['loadedmetadata'])
|
|
131
|
+
},
|
|
132
|
+
timeupdate: data => {
|
|
133
|
+
handleStateChange()
|
|
134
|
+
on?.timeupdate?.(data as PlayerEventMap['timeupdate'])
|
|
135
|
+
},
|
|
136
|
+
loading: data => {
|
|
137
|
+
handleStateChange()
|
|
138
|
+
on?.loading?.(data as PlayerEventMap['loading'])
|
|
139
|
+
},
|
|
140
|
+
canplay: data => {
|
|
141
|
+
handleStateChange()
|
|
142
|
+
on?.canplay?.(data as PlayerEventMap['canplay'])
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
;(Object.keys(listeners) as PlayerEvent[]).forEach(event => {
|
|
147
|
+
const handler = listeners[event]!
|
|
148
|
+
player.on(event, handler as any)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
return () => {
|
|
152
|
+
;(Object.keys(listeners) as PlayerEvent[]).forEach(event => {
|
|
153
|
+
const handler = listeners[event]
|
|
154
|
+
if (handler) {
|
|
155
|
+
player.off(event, handler as any)
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
}, [player, on])
|
|
160
|
+
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
let cancelled = false
|
|
163
|
+
|
|
164
|
+
if (!src) return
|
|
165
|
+
|
|
166
|
+
player
|
|
167
|
+
.setSource(src.url, src.options)
|
|
168
|
+
.then((p: HLSAudioPlayerInterface) => {
|
|
169
|
+
if (cancelled) return
|
|
170
|
+
if (autoPlay) {
|
|
171
|
+
p.play()
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
.catch((err: unknown) => {
|
|
175
|
+
if (cancelled) return
|
|
176
|
+
console.error('Failed to set HLS source', err)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
return () => {
|
|
180
|
+
cancelled = true
|
|
181
|
+
}
|
|
182
|
+
}, [player, src?.url, src?.options, autoPlay])
|
|
183
|
+
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
return () => {
|
|
186
|
+
player.destroy()
|
|
187
|
+
}
|
|
188
|
+
}, [player])
|
|
189
|
+
|
|
190
|
+
const controls = useMemo(
|
|
191
|
+
() => ({
|
|
192
|
+
setSource: async (url: string, options?: SourceOptions) => {
|
|
193
|
+
try {
|
|
194
|
+
const p = await player.setSource(url, options)
|
|
195
|
+
if (autoPlay) {
|
|
196
|
+
p.play()
|
|
197
|
+
}
|
|
198
|
+
return p
|
|
199
|
+
} catch {
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
play: () => {
|
|
204
|
+
player.play()
|
|
205
|
+
},
|
|
206
|
+
playAsync: async () => {
|
|
207
|
+
try {
|
|
208
|
+
const p = await player.playAsync()
|
|
209
|
+
return p
|
|
210
|
+
} catch {
|
|
211
|
+
return null
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
pause: () => {
|
|
215
|
+
player.pause()
|
|
216
|
+
},
|
|
217
|
+
setVolume: (volume: number) => {
|
|
218
|
+
player.setVolume(volume)
|
|
219
|
+
const next = player.getState()
|
|
220
|
+
setState(next)
|
|
221
|
+
},
|
|
222
|
+
setCurrentTime: (time: number) => {
|
|
223
|
+
const audioElement = player.getAudioElement()
|
|
224
|
+
audioElement.currentTime = time
|
|
225
|
+
},
|
|
226
|
+
}),
|
|
227
|
+
[player, autoPlay],
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
player,
|
|
232
|
+
state,
|
|
233
|
+
isPlaying,
|
|
234
|
+
duration,
|
|
235
|
+
isLoading,
|
|
236
|
+
loading,
|
|
237
|
+
error,
|
|
238
|
+
readyState,
|
|
239
|
+
controls,
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"types": ["vite/client"],
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
|
|
11
|
+
/* Bundler mode */
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"moduleDetection": "force",
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"jsx": "react-jsx",
|
|
18
|
+
|
|
19
|
+
/* Linting */
|
|
20
|
+
"strict": true,
|
|
21
|
+
"noUnusedLocals": true,
|
|
22
|
+
"noUnusedParameters": true,
|
|
23
|
+
"erasableSyntaxOnly": true,
|
|
24
|
+
"noFallthroughCasesInSwitch": true,
|
|
25
|
+
"noUncheckedSideEffectImports": true
|
|
26
|
+
},
|
|
27
|
+
"include": ["src"]
|
|
28
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationMap": true,
|
|
12
|
+
"jsx": "react-jsx"
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/index.ts", "src/use-hls-audio-player.ts"],
|
|
15
|
+
"exclude": ["dist", "node_modules"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"erasableSyntaxOnly": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedSideEffectImports": true
|
|
24
|
+
},
|
|
25
|
+
"include": ["vite.config.ts"]
|
|
26
|
+
}
|