oddysee-react 0.1.0 → 0.2.0-canary.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 +14 -12
- package/__tests__/retry.test.ts +148 -0
- package/__tests__/seek.test.ts +184 -0
- package/dist/index.cjs +33399 -2
- package/dist/index.d.cts +184 -1
- package/dist/index.d.ts +184 -1
- package/dist/index.js +33404 -3
- package/package.json +10 -3
- package/src/use-hls-audio-player.ts +141 -3
- package/dist/assets/index-COcDBgFa.css +0 -1
- package/dist/assets/index-DzSVBDbV.js +0 -9
- package/dist/assets/react-CHdo91hT.svg +0 -1
- package/dist/index.html +0 -14
- package/dist/vite.svg +0 -1
- package/public/vite.svg +0 -1
package/package.json
CHANGED
|
@@ -4,11 +4,16 @@
|
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.2.0-canary.0",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"author": "Karelle Hofler",
|
|
10
10
|
"main": "dist/index.js",
|
|
11
11
|
"types": "dist/index.d.ts",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/itskarelleh/oddysee-hls-audio-player.git"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
12
17
|
"exports": {
|
|
13
18
|
".": {
|
|
14
19
|
"import": "./dist/index.js",
|
|
@@ -19,12 +24,13 @@
|
|
|
19
24
|
"build": "tsup src/index.ts --format esm,cjs --dts --tsconfig tsconfig.lib.json",
|
|
20
25
|
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
21
26
|
"lint": "eslint .",
|
|
27
|
+
"test": "vitest",
|
|
22
28
|
"preview": "vite preview",
|
|
23
29
|
"pub": "npm run build && npm publish",
|
|
24
30
|
"pub:test": "npm run build && npm publish --tag test"
|
|
25
31
|
},
|
|
26
32
|
"dependencies": {
|
|
27
|
-
"
|
|
33
|
+
"oddysee-typescript": "^0.2.0-canary.0",
|
|
28
34
|
"react": "^19.2.0",
|
|
29
35
|
"react-dom": "^19.2.0"
|
|
30
36
|
},
|
|
@@ -41,6 +47,7 @@
|
|
|
41
47
|
"tsup": "7.2.0",
|
|
42
48
|
"typescript": "~5.9.3",
|
|
43
49
|
"typescript-eslint": "^8.46.4",
|
|
44
|
-
"vite": "^7.2.4"
|
|
50
|
+
"vite": "^7.2.4",
|
|
51
|
+
"vitest": "^4.0.16"
|
|
45
52
|
}
|
|
46
53
|
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type ChangeEvent,
|
|
8
|
+
type PointerEvent,
|
|
9
|
+
type MouseEvent,
|
|
10
|
+
type TouchEvent,
|
|
11
|
+
} from 'react'
|
|
2
12
|
import {
|
|
3
13
|
HLSAudioPlayer,
|
|
4
14
|
type HLSAudioPlayerInterface,
|
|
@@ -9,7 +19,8 @@ import {
|
|
|
9
19
|
type Track,
|
|
10
20
|
type QualityLevel,
|
|
11
21
|
type PlayerState
|
|
12
|
-
}
|
|
22
|
+
}
|
|
23
|
+
from 'oddysee-typescript'
|
|
13
24
|
|
|
14
25
|
// Local mirror of the core's PlayerEventMap so we don't depend on it being exported
|
|
15
26
|
export type PlayerEventMap = {
|
|
@@ -43,6 +54,27 @@ export interface UseHlsAudioPlayerResult {
|
|
|
43
54
|
loading: boolean
|
|
44
55
|
error: PlayerError | null
|
|
45
56
|
readyState: number
|
|
57
|
+
scrub: {
|
|
58
|
+
isScrubbing: boolean
|
|
59
|
+
displayTime: number
|
|
60
|
+
begin: () => void
|
|
61
|
+
update: (time: number) => void
|
|
62
|
+
commit: (time?: number) => void
|
|
63
|
+
}
|
|
64
|
+
seekBar: {
|
|
65
|
+
isScrubbing: boolean
|
|
66
|
+
displayTime: number
|
|
67
|
+
onChange: (event: ChangeEvent<HTMLInputElement>) => void
|
|
68
|
+
onPointerDown: () => void
|
|
69
|
+
onPointerUp: (event: PointerEvent<HTMLInputElement>) => void
|
|
70
|
+
onPointerCancel: () => void
|
|
71
|
+
onMouseDown: () => void
|
|
72
|
+
onMouseUp: (event: MouseEvent<HTMLInputElement>) => void
|
|
73
|
+
onTouchStart: () => void
|
|
74
|
+
onTouchEnd: (event: TouchEvent<HTMLInputElement>) => void
|
|
75
|
+
onFocus: () => void
|
|
76
|
+
onBlur: () => void
|
|
77
|
+
}
|
|
46
78
|
controls: {
|
|
47
79
|
setSource: (
|
|
48
80
|
url: string,
|
|
@@ -53,6 +85,10 @@ export interface UseHlsAudioPlayerResult {
|
|
|
53
85
|
pause: () => void
|
|
54
86
|
setVolume: (volume: number) => void
|
|
55
87
|
setCurrentTime: (time: number) => void
|
|
88
|
+
beginSeek: () => void
|
|
89
|
+
updateSeek: (time: number) => void
|
|
90
|
+
commitSeek: () => void
|
|
91
|
+
retry: (count?: number, interval?: number) => void
|
|
56
92
|
}
|
|
57
93
|
}
|
|
58
94
|
|
|
@@ -87,6 +123,9 @@ export function useHlsAudioPlayer(
|
|
|
87
123
|
const [isPlaying, setIsPlaying] = useState<boolean>(player.isPlaying ?? false)
|
|
88
124
|
const [duration, setDuration] = useState<number>(player.getState()?.duration ?? 0)
|
|
89
125
|
const [isLoading, setIsLoading] = useState<boolean>(player.loading ?? false)
|
|
126
|
+
const [isScrubbing, setIsScrubbing] = useState(false)
|
|
127
|
+
const [scrubTime, setScrubTime] = useState(0)
|
|
128
|
+
const scrubTimeRef = useRef(0)
|
|
90
129
|
|
|
91
130
|
useEffect(() => {
|
|
92
131
|
const handleStateChange = () => {
|
|
@@ -223,10 +262,56 @@ export function useHlsAudioPlayer(
|
|
|
223
262
|
const audioElement = player.getAudioElement()
|
|
224
263
|
audioElement.currentTime = time
|
|
225
264
|
},
|
|
265
|
+
beginSeek:() =>
|
|
266
|
+
player.beginSeek(),
|
|
267
|
+
updateSeek: (time: number) =>
|
|
268
|
+
player.updateSeek(time),
|
|
269
|
+
commitSeek: () =>
|
|
270
|
+
player.commitSeek(),
|
|
271
|
+
retry: (count?: number, interval?: number) => {
|
|
272
|
+
player.retry(count, interval)
|
|
273
|
+
},
|
|
226
274
|
}),
|
|
227
275
|
[player, autoPlay],
|
|
228
276
|
)
|
|
229
277
|
|
|
278
|
+
const beginScrub = useCallback(() => {
|
|
279
|
+
if (!state.duration) return
|
|
280
|
+
setIsScrubbing(true)
|
|
281
|
+
scrubTimeRef.current = state.currentTime
|
|
282
|
+
setScrubTime(state.currentTime)
|
|
283
|
+
player.beginSeek()
|
|
284
|
+
}, [player, state.currentTime, state.duration])
|
|
285
|
+
|
|
286
|
+
const updateScrub = useCallback((time: number) => {
|
|
287
|
+
scrubTimeRef.current = time
|
|
288
|
+
setScrubTime(time)
|
|
289
|
+
}, [])
|
|
290
|
+
|
|
291
|
+
const commitScrub = useCallback(
|
|
292
|
+
(time?: number) => {
|
|
293
|
+
if (!isScrubbing) return
|
|
294
|
+
if (typeof time === 'number' && !Number.isNaN(time)) {
|
|
295
|
+
scrubTimeRef.current = time
|
|
296
|
+
setScrubTime(time)
|
|
297
|
+
}
|
|
298
|
+
setIsScrubbing(false)
|
|
299
|
+
player.updateSeek(scrubTimeRef.current)
|
|
300
|
+
player.commitSeek()
|
|
301
|
+
},
|
|
302
|
+
[player, isScrubbing],
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
const commitScrubFromElement = useCallback(
|
|
306
|
+
(element: HTMLInputElement) => {
|
|
307
|
+
requestAnimationFrame(() => {
|
|
308
|
+
const nextTime = Number(element.value)
|
|
309
|
+
commitScrub(nextTime)
|
|
310
|
+
})
|
|
311
|
+
},
|
|
312
|
+
[commitScrub],
|
|
313
|
+
)
|
|
314
|
+
|
|
230
315
|
return {
|
|
231
316
|
player,
|
|
232
317
|
state,
|
|
@@ -236,6 +321,59 @@ export function useHlsAudioPlayer(
|
|
|
236
321
|
loading,
|
|
237
322
|
error,
|
|
238
323
|
readyState,
|
|
239
|
-
|
|
324
|
+
seekBar: {
|
|
325
|
+
isScrubbing,
|
|
326
|
+
displayTime: isScrubbing ? scrubTime : state.currentTime,
|
|
327
|
+
onChange: (event: ChangeEvent<HTMLInputElement>) => {
|
|
328
|
+
const nextTime = Number(event.target.value)
|
|
329
|
+
if (!isScrubbing) {
|
|
330
|
+
beginScrub()
|
|
331
|
+
}
|
|
332
|
+
updateScrub(nextTime)
|
|
333
|
+
},
|
|
334
|
+
onPointerDown: () => {
|
|
335
|
+
if (!isScrubbing) {
|
|
336
|
+
beginScrub()
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
onPointerUp: (event: PointerEvent<HTMLInputElement>) => {
|
|
340
|
+
commitScrubFromElement(event.currentTarget)
|
|
341
|
+
},
|
|
342
|
+
onPointerCancel: () => {
|
|
343
|
+
commitScrub()
|
|
344
|
+
},
|
|
345
|
+
onMouseDown: () => {
|
|
346
|
+
if (!isScrubbing) {
|
|
347
|
+
beginScrub()
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
onMouseUp: (event: MouseEvent<HTMLInputElement>) => {
|
|
351
|
+
commitScrubFromElement(event.currentTarget)
|
|
352
|
+
},
|
|
353
|
+
onTouchStart: () => {
|
|
354
|
+
if (!isScrubbing) {
|
|
355
|
+
beginScrub()
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
onTouchEnd: (event: TouchEvent<HTMLInputElement>) => {
|
|
359
|
+
commitScrubFromElement(event.currentTarget)
|
|
360
|
+
},
|
|
361
|
+
onFocus: () => {
|
|
362
|
+
if (!isScrubbing) {
|
|
363
|
+
beginScrub()
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
onBlur: () => {
|
|
367
|
+
commitScrub()
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
scrub: {
|
|
371
|
+
isScrubbing,
|
|
372
|
+
displayTime: isScrubbing ? scrubTime : state.currentTime,
|
|
373
|
+
begin: beginScrub,
|
|
374
|
+
update: updateScrub,
|
|
375
|
+
commit: commitScrub,
|
|
376
|
+
},
|
|
377
|
+
controls
|
|
240
378
|
}
|
|
241
379
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#535bf2}body{margin:0;display:flex;place-items:center;min-width:320px;min-height:100vh}h1{font-size:3.2em;line-height:1.1}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#1a1a1a;cursor:pointer;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}@media(prefers-color-scheme:light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}}#root{max-width:1280px;margin:0 auto;padding:2rem;text-align:center}.logo{height:6em;padding:1.5em;will-change:filter;transition:filter .3s}.logo:hover{filter:drop-shadow(0 0 2em #646cffaa)}.logo.react:hover{filter:drop-shadow(0 0 2em #61dafbaa)}@keyframes logo-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media(prefers-reduced-motion:no-preference){a:nth-of-type(2) .logo{animation:logo-spin infinite 20s linear}}.card{padding:2em}.read-the-docs{color:#888}
|