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 ADDED
@@ -0,0 +1,356 @@
1
+ # @use-hls-player/react
2
+
3
+ A React hook that provides a simple, intuitive interface for HLS audio streaming using the core HLS audio player.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @use-hls-player/react
9
+ # or
10
+ yarn add @use-hls-player/react
11
+ # or
12
+ pnpm add @use-hls-player/react
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```tsx
18
+ import { useHlsAudioPlayer } from '@use-hls-player/react';
19
+
20
+ function AudioPlayer() {
21
+ const { state, controls, isLoading, isPlaying } = useHlsAudioPlayer({
22
+ src: { url: 'https://example.com/playlist.m3u8' },
23
+ autoPlay: false,
24
+ });
25
+
26
+ return (
27
+ <div>
28
+ <button onClick={() => isPlaying ? controls.pause() : controls.play()}>
29
+ {isPlaying ? 'Pause' : 'Play'}
30
+ </button>
31
+ <div>
32
+ {isLoading ? 'Loading...' : `Duration: ${state.duration}s`}
33
+ </div>
34
+ {state.error && <div>Error: {state.error.message}</div>}
35
+ </div>
36
+ );
37
+ }
38
+ ```
39
+
40
+ ## API Reference
41
+
42
+ ### `useHlsAudioPlayer(options)`
43
+
44
+ #### Options
45
+
46
+ ```typescript
47
+ interface UseHlsAudioPlayerOptions {
48
+ config?: PlayerConfig;
49
+ src?: { url: string; options?: SourceOptions };
50
+ autoPlay?: boolean;
51
+ on?: Partial<{
52
+ [K in PlayerEvent]: (data: PlayerEventMap[K]) => void
53
+ }>;
54
+ }
55
+ ```
56
+
57
+ - **config**: Global player configuration (see PlayerConfig below)
58
+ - **src**: Initial HLS source to load
59
+ - **autoPlay**: Automatically start playback when source is ready
60
+ - **on**: Event callbacks for player events
61
+
62
+ #### Return Value
63
+
64
+ ```typescript
65
+ interface UseHlsAudioPlayerResult {
66
+ player: HLSAudioPlayerInterface | null;
67
+ state: PlayerState;
68
+ isPlaying: boolean;
69
+ duration: number;
70
+ isLoading: boolean;
71
+ loading: boolean;
72
+ error: PlayerError | null;
73
+ readyState: number;
74
+ controls: {
75
+ setSource: (url: string, options?: SourceOptions) => Promise<void>;
76
+ play: () => Promise<void>;
77
+ pause: () => void;
78
+ setVolume: (volume: number) => void;
79
+ setCurrentTime: (time: number) => void;
80
+ };
81
+ }
82
+ ```
83
+
84
+ ### Player State
85
+
86
+ ```typescript
87
+ interface PlayerState {
88
+ track: Track | null;
89
+ currentTime: number;
90
+ duration: number | null;
91
+ volume: number;
92
+ loading: boolean;
93
+ error: PlayerError | null;
94
+ readyState: number;
95
+ isPlaying: boolean;
96
+ }
97
+ ```
98
+
99
+ ### Configuration Types
100
+
101
+ ```typescript
102
+ interface PlayerConfig {
103
+ network?: {
104
+ headers?: Record<string, string>;
105
+ timeout?: number;
106
+ retryCount?: number;
107
+ };
108
+ audio?: {
109
+ crossfade?: boolean;
110
+ normalization?: boolean;
111
+ preload?: boolean;
112
+ volume?: number;
113
+ };
114
+ playback?: {
115
+ autoPlay?: boolean;
116
+ startTime?: number;
117
+ };
118
+ }
119
+
120
+ interface SourceOptions {
121
+ headers?: Record<string, string>;
122
+ startTime?: number;
123
+ [key: string]: any;
124
+ }
125
+ ```
126
+
127
+ ## Examples
128
+
129
+ ### Basic Audio Player
130
+
131
+ ```tsx
132
+ import { useHlsAudioPlayer } from '@use-hls-player/react';
133
+
134
+ export default function BasicPlayer() {
135
+ const { state, controls, isLoading, isPlaying } = useHlsAudioPlayer({
136
+ src: { url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8' },
137
+ });
138
+
139
+ const togglePlay = () => {
140
+ if (isPlaying) {
141
+ controls.pause();
142
+ } else {
143
+ controls.play();
144
+ }
145
+ };
146
+
147
+ return (
148
+ <div className="player">
149
+ <button onClick={togglePlay} disabled={isLoading}>
150
+ {isLoading ? 'Loading...' : isPlaying ? 'Pause' : 'Play'}
151
+ </button>
152
+
153
+ <div className="progress">
154
+ <input
155
+ type="range"
156
+ min={0}
157
+ max={state.duration ?? 0}
158
+ value={state.currentTime}
159
+ onChange={(e) => controls.setCurrentTime(Number(e.target.value))}
160
+ />
161
+ <span>{state.currentTime.toFixed(0)}s / {state.duration?.toFixed(0) ?? '--'}s</span>
162
+ </div>
163
+
164
+ <div className="volume">
165
+ <input
166
+ type="range"
167
+ min={0}
168
+ max={1}
169
+ step={0.01}
170
+ value={state.volume}
171
+ onChange={(e) => controls.setVolume(parseFloat(e.target.value))}
172
+ />
173
+ <span>Volume: {Math.round(state.volume * 100)}%</span>
174
+ </div>
175
+
176
+ {state.error && (
177
+ <div className="error">Error: {state.error.message}</div>
178
+ )}
179
+ </div>
180
+ );
181
+ }
182
+ ```
183
+
184
+ ### Playlist Player
185
+
186
+ ```tsx
187
+ import { useState } from 'react';
188
+ import { useHlsAudioPlayer } from '@use-hls-player/react';
189
+
190
+ const playlist = [
191
+ { id: 1, url: 'https://pl.streamingvideoprovider.com/mp3-playlist/playlist.m3u8', title: 'Music Playlist' },
192
+ { id: 2, url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', title: 'Test Stream' },
193
+ { id: 3, url: 'https://cph-p2p-msl.akamaized.net/hls/live/2000341/test/master.m3u8', title: 'Live Stream' },
194
+ ];
195
+
196
+ export default function PlaylistPlayer() {
197
+ const [currentTrackIndex, setCurrentTrackIndex] = useState(0);
198
+ const currentTrack = playlist[currentTrackIndex];
199
+
200
+ const { state, controls, isLoading, isPlaying } = useHlsAudioPlayer({
201
+ src: { url: currentTrack.url },
202
+ autoPlay: true,
203
+ });
204
+
205
+ const playTrack = (index: number) => {
206
+ setCurrentTrackIndex(index);
207
+ };
208
+
209
+ const playNext = () => {
210
+ const nextIndex = (currentTrackIndex + 1) % playlist.length;
211
+ setCurrentTrackIndex(nextIndex);
212
+ };
213
+
214
+ const playPrevious = () => {
215
+ const prevIndex = currentTrackIndex === 0 ? playlist.length - 1 : currentTrackIndex - 1;
216
+ setCurrentTrackIndex(prevIndex);
217
+ };
218
+
219
+ return (
220
+ <div className="playlist-player">
221
+ <div className="current-track">
222
+ <h3>{currentTrack.title}</h3>
223
+ <p>{isLoading ? 'Loading...' : isPlaying ? 'Playing' : 'Paused'}</p>
224
+ </div>
225
+
226
+ <div className="controls">
227
+ <button onClick={playPrevious}>Previous</button>
228
+ <button onClick={() => isPlaying ? controls.pause() : controls.play()}>
229
+ {isPlaying ? 'Pause' : 'Play'}
230
+ </button>
231
+ <button onClick={playNext}>Next</button>
232
+ </div>
233
+
234
+ <div className="playlist">
235
+ {playlist.map((track, index) => (
236
+ <button
237
+ key={track.id}
238
+ onClick={() => playTrack(index)}
239
+ className={index === currentTrackIndex ? 'active' : ''}
240
+ >
241
+ {track.title}
242
+ </button>
243
+ ))}
244
+ </div>
245
+
246
+ {state.error && (
247
+ <div className="error">Error: {state.error.message}</div>
248
+ )}
249
+ </div>
250
+ );
251
+ }
252
+ ```
253
+
254
+ ### Advanced Configuration with Headers
255
+
256
+ ```tsx
257
+ import { useHlsAudioPlayer } from '@use-hls-player/react';
258
+
259
+ export default function AuthenticatedPlayer() {
260
+ const { state, controls, isLoading } = useHlsAudioPlayer({
261
+ src: {
262
+ url: 'https://protected-stream.example.com/playlist.m3u8',
263
+ options: {
264
+ headers: {
265
+ 'Authorization': 'Bearer your-token-here',
266
+ 'User-Agent': 'MyAudioPlayer/1.0'
267
+ }
268
+ }
269
+ },
270
+ config: {
271
+ network: {
272
+ timeout: 10000,
273
+ retryCount: 3
274
+ },
275
+ audio: {
276
+ volume: 0.8,
277
+ preload: true
278
+ }
279
+ },
280
+ on: {
281
+ error: (error) => console.error('Player error:', error),
282
+ loading: () => console.log('Loading started'),
283
+ canplay: () => console.log('Can play')
284
+ }
285
+ });
286
+
287
+ return (
288
+ <div>
289
+ <button onClick={() => controls.play()} disabled={isLoading}>
290
+ {isLoading ? 'Loading...' : 'Play'}
291
+ </button>
292
+ {state.error && <div>Authentication failed: {state.error.message}</div>}
293
+ </div>
294
+ );
295
+ }
296
+ ```
297
+
298
+ ### Event Handling
299
+
300
+ ```tsx
301
+ import { useState } from 'react';
302
+ import { useHlsAudioPlayer } from '@use-hls-player/react';
303
+
304
+ export default function EventHandlingPlayer() {
305
+ const [events, setEvents] = useState<string[]>([]);
306
+
307
+ const { state, controls } = useHlsAudioPlayer({
308
+ src: { url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8' },
309
+ on: {
310
+ play: () => setEvents(prev => [...prev, 'Playback started']),
311
+ pause: () => setEvents(prev => [...prev, 'Playback paused']),
312
+ loading: () => setEvents(prev => [...prev, 'Loading...']),
313
+ canplay: () => setEvents(prev => [...prev, 'Ready to play']),
314
+ error: (error) => setEvents(prev => [...prev, `Error: ${error.message}`]),
315
+ timeupdate: ({ currentTime, duration }) =>
316
+ setEvents(prev => [...prev, `Time: ${currentTime.toFixed(1)}s / ${duration?.toFixed(1) ?? '--'}s`]),
317
+ }
318
+ });
319
+
320
+ return (
321
+ <div>
322
+ <div className="controls">
323
+ <button onClick={() => controls.play()}>Play</button>
324
+ <button onClick={() => controls.pause()}>Pause</button>
325
+ </div>
326
+
327
+ <div className="events">
328
+ <h4>Events:</h4>
329
+ {events.slice(-5).map((event, index) => (
330
+ <div key={index}>{event}</div>
331
+ ))}
332
+ </div>
333
+ </div>
334
+ );
335
+ }
336
+ ```
337
+
338
+ ## Features
339
+
340
+ - **Simple Hook Interface**: Just call `useHlsAudioPlayer()` and get everything you need
341
+ - **Reactive State**: All player state is automatically synchronized with React
342
+ - **Event Handling**: Built-in support for all player events
343
+ - **TypeScript Support**: Full type safety and IntelliSense
344
+ - **Custom Headers**: Support for authenticated streams
345
+ - **Playlist Management**: Easy switching between multiple streams
346
+ - **Error Handling**: Comprehensive error reporting
347
+ - **Performance Optimized**: Efficient re-renders and cleanup
348
+
349
+ ## Dependencies
350
+
351
+ - React 18+
352
+ - @hls-audio-player/core (peer dependency)
353
+
354
+ ## License
355
+
356
+ Apache-2.0
@@ -0,0 +1 @@
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}