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.
@@ -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,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -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
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ })