@livepeer-frameworks/player-react 0.0.4 → 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.
@@ -55,6 +55,8 @@ interface PlayerControlsProps {
55
55
  isLoopEnabled?: boolean;
56
56
  /** Toggle loop callback */
57
57
  onToggleLoop?: () => void;
58
+ /** Jump to live edge callback */
59
+ onJumpToLive?: () => void;
58
60
  }
59
61
  declare const PlayerControls: React.FC<PlayerControlsProps>;
60
62
  export default PlayerControls;
@@ -1,22 +1,10 @@
1
1
  import React from "react";
2
- import { type ContentMetadata, type PlaybackQuality } from "@livepeer-frameworks/player-core";
3
- interface StreamStateInfo {
4
- status?: string;
5
- viewers?: number;
6
- tracks?: Array<{
7
- type: string;
8
- codec: string;
9
- width?: number;
10
- height?: number;
11
- bps?: number;
12
- channels?: number;
13
- }>;
14
- }
2
+ import { type ContentMetadata, type PlaybackQuality, type StreamState } from "@livepeer-frameworks/player-core";
15
3
  interface StatsPanelProps {
16
4
  isOpen: boolean;
17
5
  onClose: () => void;
18
6
  metadata?: ContentMetadata | null;
19
- streamState?: StreamStateInfo | null;
7
+ streamState?: StreamState | null;
20
8
  quality?: PlaybackQuality | null;
21
9
  videoElement?: HTMLVideoElement | null;
22
10
  protocol?: string;
@@ -33,7 +33,7 @@ export interface UseMetaTrackReturn {
33
33
  * ```tsx
34
34
  * const { isConnected, subscribe } = useMetaTrack({
35
35
  * mistBaseUrl: 'https://mist.example.com',
36
- * streamName: 'my-stream',
36
+ * streamName: 'pk_...', // playbackId (view key)
37
37
  * enabled: true,
38
38
  * });
39
39
  *
@@ -117,6 +117,8 @@ export interface UsePlayerControllerReturn {
117
117
  seek: (time: number) => void;
118
118
  /** Seek by delta */
119
119
  seekBy: (delta: number) => void;
120
+ /** Jump to live edge (for live streams) */
121
+ jumpToLive: () => void;
120
122
  /** Set volume */
121
123
  setVolume: (volume: number) => void;
122
124
  /** Toggle mute */
@@ -10,7 +10,7 @@ import type { UseStreamStateOptions, StreamState } from '../types';
10
10
  * ```tsx
11
11
  * const { status, isOnline, message } = useStreamState({
12
12
  * mistBaseUrl: 'https://mist.example.com',
13
- * streamName: 'my-stream',
13
+ * streamName: 'pk_...', // playbackId (view key)
14
14
  * pollInterval: 3000,
15
15
  * });
16
16
  * ```
@@ -33,7 +33,7 @@ export interface UseTelemetryOptions extends TelemetryOptions {
33
33
  * endpoint: '/api/telemetry',
34
34
  * interval: 5000,
35
35
  * videoElement,
36
- * contentId: 'my-stream',
36
+ * contentId: 'pk_...', // playbackId (view key)
37
37
  * contentType: 'live',
38
38
  * playerType: 'hlsjs',
39
39
  * protocol: 'HLS',
@@ -2,11 +2,11 @@ import type { ContentType } from '@livepeer-frameworks/player-core';
2
2
  import type { ContentEndpoints } from '../types';
3
3
  interface Params {
4
4
  gatewayUrl: string;
5
- contentType: ContentType;
6
5
  contentId: string;
6
+ contentType?: ContentType;
7
7
  authToken?: string;
8
8
  }
9
- export declare function useViewerEndpoints({ gatewayUrl, contentType, contentId, authToken }: Params): {
9
+ export declare function useViewerEndpoints({ gatewayUrl, contentType: _contentType, contentId, authToken }: Params): {
10
10
  endpoints: ContentEndpoints | null;
11
11
  status: "error" | "ready" | "idle" | "loading";
12
12
  error: string | null;
@@ -7,7 +7,7 @@ export interface PlayerProps {
7
7
  /** Content identifier or stream name */
8
8
  contentId: string;
9
9
  /** Content type */
10
- contentType: ContentType;
10
+ contentType?: ContentType;
11
11
  /** Pre-resolved endpoints/capabilities from Gateway/Foghorn */
12
12
  endpoints?: ContentEndpoints;
13
13
  /** Optional thumbnail/poster image */
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  declare const buttonVariants: (props?: ({
4
4
  variant?: "link" | "default" | "secondary" | "ghost" | "outline" | "destructive" | "subtle" | null | undefined;
5
- size?: "default" | "sm" | "lg" | "icon" | null | undefined;
5
+ size?: "default" | "icon" | "sm" | "lg" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
8
8
  asChild?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livepeer-frameworks/player-react",
3
- "version": "0.0.4",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "description": "React components for FrameWorks streaming player",
6
6
  "main": "dist/cjs/index.js",
@@ -76,7 +76,7 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
76
76
  videoElement,
77
77
  protocol,
78
78
  nodeId,
79
- isVisible = true,
79
+ isVisible: _isVisible = true,
80
80
  isOpen: controlledIsOpen,
81
81
  onOpenChange,
82
82
  }) => {
@@ -91,7 +91,7 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
91
91
  }
92
92
  }, [onOpenChange]);
93
93
  const [activeTab, setActiveTab] = useState<"config" | "stats">("config");
94
- const [currentComboIndex, setCurrentComboIndex] = useState(0);
94
+ const [, setCurrentComboIndex] = useState(0);
95
95
  const [hoveredComboIndex, setHoveredComboIndex] = useState<number | null>(null);
96
96
  const [tooltipAbove, setTooltipAbove] = useState(false);
97
97
  const [showDisabledPlayers, setShowDisabledPlayers] = useState(false);
@@ -147,7 +147,7 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
147
147
  setPlayerStats(stats);
148
148
  }
149
149
  }
150
- } catch (e) {
150
+ } catch {
151
151
  // Ignore errors
152
152
  }
153
153
  };
@@ -274,31 +274,9 @@ const DevModePanel: React.FC<DevModePanelProps> = ({
274
274
  return () => clearInterval(interval);
275
275
  }, [isOpen, activeTab, videoElement]);
276
276
 
277
- // Toggle button (when closed)
277
+ // Panel is only rendered when open (no floating toggle button)
278
278
  if (!isOpen) {
279
- return (
280
- <button
281
- type="button"
282
- onClick={() => setIsOpen(true)}
283
- className={cn(
284
- "fw-dev-toggle",
285
- !isVisible && "fw-dev-toggle--hidden"
286
- )}
287
- title="Advanced Settings"
288
- aria-label="Open advanced settings panel"
289
- >
290
- <svg
291
- width="16"
292
- height="16"
293
- viewBox="0 0 24 24"
294
- fill="none"
295
- stroke="currentColor"
296
- strokeWidth="2"
297
- >
298
- <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
299
- </svg>
300
- </button>
301
- );
279
+ return null;
302
280
  }
303
281
 
304
282
  return (
@@ -210,20 +210,6 @@ interface StatusOverlayProps {
210
210
  onRetry?: () => void;
211
211
  }
212
212
 
213
- function getStatusLabel(status?: StreamStatus): string {
214
- switch (status) {
215
- case 'ONLINE': return 'ONLINE';
216
- case 'OFFLINE': return 'OFFLINE';
217
- case 'INITIALIZING': return 'STARTING';
218
- case 'BOOTING': return 'STARTING';
219
- case 'WAITING_FOR_DATA': return 'WAITING';
220
- case 'SHUTTING_DOWN': return 'ENDING';
221
- case 'ERROR': return 'ERROR';
222
- case 'INVALID': return 'ERROR';
223
- default: return 'CONNECTING';
224
- }
225
- }
226
-
227
213
  function StatusIcon({ status }: { status?: StreamStatus }) {
228
214
  const iconClass = "w-5 h-5";
229
215
 
@@ -297,7 +297,7 @@ const LoadingScreen: React.FC<LoadingScreenProps> = ({ message = "Waiting for so
297
297
  // Fallback to synthetic sound if data URL fails
298
298
  createSyntheticHitmarkerSound();
299
299
  });
300
- } catch (error) {
300
+ } catch {
301
301
  // Fallback to synthetic sound
302
302
  createSyntheticHitmarkerSound();
303
303
  }
@@ -372,7 +372,7 @@ const LoadingScreen: React.FC<LoadingScreenProps> = ({ message = "Waiting for so
372
372
  oscillator2.stop(stopTime);
373
373
  noiseSource.stop(startTime + 0.02);
374
374
 
375
- } catch (error) {
375
+ } catch {
376
376
  console.log('Audio context not available');
377
377
  }
378
378
  };
@@ -1,6 +1,5 @@
1
- import React, { useState, useCallback, useMemo } from "react";
1
+ import React, { useState, useCallback } from "react";
2
2
  import IdleScreen from "./IdleScreen";
3
- import ThumbnailOverlay from "./ThumbnailOverlay";
4
3
  import TitleOverlay from "./TitleOverlay";
5
4
  import StatsPanel from "./StatsPanel";
6
5
  import PlayerControls from "./PlayerControls";
@@ -134,9 +133,6 @@ const PlayerInner: React.FC<PlayerProps> = ({
134
133
  const showBufferingSpinner = !state.shouldShowIdleScreen &&
135
134
  state.isBuffering && !state.error && state.hasPlaybackStarted;
136
135
 
137
- // Click-to-play overlay support
138
- const supportsOverlay = false;
139
-
140
136
  // ============================================================================
141
137
  // Waiting for Endpoint (shown as overlay, not early return)
142
138
  // ============================================================================
@@ -182,19 +178,7 @@ const PlayerInner: React.FC<PlayerProps> = ({
182
178
  isOpen={isStatsOpen}
183
179
  onClose={handleStatsToggle}
184
180
  metadata={state.metadata}
185
- streamState={state.streamState?.isOnline ? {
186
- status: state.streamState.status,
187
- viewers: state.metadata?.viewers,
188
- tracks: state.streamState.streamInfo?.meta?.tracks
189
- ? Object.values(state.streamState.streamInfo.meta.tracks).map(t => ({
190
- type: t.type,
191
- codec: t.codec,
192
- width: t.width,
193
- height: t.height,
194
- bps: t.bps,
195
- }))
196
- : [],
197
- } : null}
181
+ streamState={state.streamState}
198
182
  quality={state.playbackQuality}
199
183
  videoElement={state.videoElement}
200
184
  protocol={primaryEndpoint?.protocol}
@@ -4,7 +4,6 @@ import {
4
4
  cn,
5
5
  // Seeking utilities from core
6
6
  SPEED_PRESETS,
7
- getLatencyTier,
8
7
  isMediaStreamSource,
9
8
  supportsPlaybackRate as coreSupportsPlaybackRate,
10
9
  calculateSeekableRange,
@@ -13,20 +12,16 @@ import {
13
12
  calculateIsNearLive,
14
13
  isLiveContent,
15
14
  // Time formatting from core
16
- formatTime,
17
15
  formatTimeDisplay,
18
16
  } from "@livepeer-frameworks/player-core";
19
17
  import { Slider } from "../ui/slider";
20
18
  import SeekBar from "./SeekBar";
21
19
  import {
22
- ClosedCaptionsIcon,
23
20
  FullscreenToggleIcon,
24
- LiveIcon,
25
21
  PlayPauseIcon,
26
22
  SeekToLiveIcon,
27
23
  SkipBackIcon,
28
24
  SkipForwardIcon,
29
- StatsIcon,
30
25
  VolumeIcon,
31
26
  SettingsIcon
32
27
  } from "./Icons";
@@ -107,8 +102,8 @@ const PlayerControls: React.FC<PlayerControlsProps> = ({
107
102
  onTogglePlay,
108
103
  onToggleFullscreen,
109
104
  isFullscreen: propIsFullscreen,
110
- isLoopEnabled: propIsLoopEnabled,
111
- onToggleLoop,
105
+ isLoopEnabled: _propIsLoopEnabled,
106
+ onToggleLoop: _onToggleLoop,
112
107
  onJumpToLive,
113
108
  }) => {
114
109
  // Context fallback - prefer props passed from parent over context
@@ -145,7 +145,7 @@ const SeekBar: React.FC<SeekBarProps> = ({
145
145
 
146
146
  // Fallback: If we have liveEdge, use it even if not marked as live
147
147
  // This handles cases where duration is Infinity but we have valid seekable data
148
- if (Number.isFinite(liveEdge) && liveEdge > 0) {
148
+ if (liveEdge !== undefined && Number.isFinite(liveEdge) && liveEdge > 0) {
149
149
  const start = Number.isFinite(seekableStart) ? seekableStart : 0;
150
150
  const window = liveEdge - start;
151
151
  if (window > 0) {
@@ -1,24 +1,16 @@
1
1
  import React from "react";
2
- import { cn, type ContentMetadata, type PlaybackQuality } from "@livepeer-frameworks/player-core";
3
-
4
- interface StreamStateInfo {
5
- status?: string;
6
- viewers?: number;
7
- tracks?: Array<{
8
- type: string;
9
- codec: string;
10
- width?: number;
11
- height?: number;
12
- bps?: number;
13
- channels?: number;
14
- }>;
15
- }
2
+ import {
3
+ cn,
4
+ type ContentMetadata,
5
+ type PlaybackQuality,
6
+ type StreamState,
7
+ } from "@livepeer-frameworks/player-core";
16
8
 
17
9
  interface StatsPanelProps {
18
10
  isOpen: boolean;
19
11
  onClose: () => void;
20
12
  metadata?: ContentMetadata | null;
21
- streamState?: StreamStateInfo | null;
13
+ streamState?: StreamState | null;
22
14
  quality?: PlaybackQuality | null;
23
15
  videoElement?: HTMLVideoElement | null;
24
16
  protocol?: string;
@@ -61,20 +53,46 @@ const StatsPanel: React.FC<StatsPanelProps> = ({
61
53
  const latency = quality?.latency ? `${Math.round(quality.latency)} ms` : "—";
62
54
 
63
55
  // Stream state stats
64
- const viewers = streamState?.viewers ?? metadata?.viewers ?? "—";
56
+ const viewers = metadata?.viewers ?? "—";
65
57
  const streamStatus = streamState?.status ?? metadata?.status ?? "—";
66
58
 
59
+ const mistInfo = metadata?.mist ?? streamState?.streamInfo;
60
+
61
+ const deriveTracksFromMist = () => {
62
+ const mistTracks = mistInfo?.meta?.tracks;
63
+ if (!mistTracks) return undefined;
64
+ return Object.values(mistTracks).map(t => ({
65
+ type: t.type,
66
+ codec: t.codec,
67
+ width: t.width,
68
+ height: t.height,
69
+ bitrate: typeof t.bps === "number" ? Math.round(t.bps) : undefined,
70
+ fps: typeof t.fpks === "number" ? t.fpks / 1000 : undefined,
71
+ channels: t.channels,
72
+ sampleRate: t.rate,
73
+ }));
74
+ };
75
+
67
76
  // Format track info from metadata
68
77
  const formatTracks = () => {
69
- if (!streamState?.tracks?.length) return "—";
70
- return streamState.tracks.map(t => {
78
+ const tracks = metadata?.tracks ?? deriveTracksFromMist();
79
+ if (!tracks?.length) return "—";
80
+ return tracks.map(t => {
71
81
  if (t.type === "video") {
72
- return `${t.codec} ${t.width}x${t.height}@${t.bps ? Math.round(t.bps / 1000) + "kbps" : "?"}`;
82
+ const resolution = t.width && t.height ? `${t.width}x${t.height}` : "?";
83
+ const bitrate = t.bitrate ? `${Math.round(t.bitrate / 1000)}kbps` : "?";
84
+ return `${t.codec ?? "?"} ${resolution}@${bitrate}`;
73
85
  }
74
- return `${t.codec} ${t.channels}ch`;
86
+ const channels = t.channels ? `${t.channels}ch` : "?";
87
+ return `${t.codec ?? "?"} ${channels}`;
75
88
  }).join(", ");
76
89
  };
77
90
 
91
+ const mistType = mistInfo?.type ?? "—";
92
+ const mistBufferWindow = mistInfo?.meta?.buffer_window;
93
+ const mistLastMs = mistInfo?.lastms;
94
+ const mistUnixOffset = mistInfo?.unixoffset;
95
+
78
96
  const stats = [
79
97
  { label: "Resolution", value: currentRes },
80
98
  { label: "Buffer", value: `${buffered}s` },
@@ -90,6 +108,10 @@ const StatsPanel: React.FC<StatsPanelProps> = ({
90
108
  { label: "Viewers", value: String(viewers) },
91
109
  { label: "Status", value: streamStatus },
92
110
  { label: "Tracks", value: formatTracks() },
111
+ { label: "Mist Type", value: mistType },
112
+ { label: "Mist Buffer Window", value: mistBufferWindow != null ? String(mistBufferWindow) : "—" },
113
+ { label: "Mist Lastms", value: mistLastMs != null ? String(mistLastMs) : "—" },
114
+ { label: "Mist Unixoffset", value: mistUnixOffset != null ? String(mistUnixOffset) : "—" },
93
115
  ];
94
116
 
95
117
  // Add metadata fields if available
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState, useRef, useCallback } from 'react';
1
+ import React, { useEffect, useState, useRef } from 'react';
2
2
  import type { SubtitleCue, MetaTrackEvent } from '../types';
3
3
 
4
4
  export interface SubtitleRendererProps {
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useState, useRef, useCallback } from 'react';
2
- import { MetaTrackManager, type MetaTrackSubscription, type MetaTrackEvent } from '@livepeer-frameworks/player-core';
2
+ import { MetaTrackManager, type MetaTrackEvent } from '@livepeer-frameworks/player-core';
3
3
  import type { UseMetaTrackOptions } from '../types';
4
4
 
5
5
  export interface UseMetaTrackReturn {
@@ -36,7 +36,7 @@ export interface UseMetaTrackReturn {
36
36
  * ```tsx
37
37
  * const { isConnected, subscribe } = useMetaTrack({
38
38
  * mistBaseUrl: 'https://mist.example.com',
39
- * streamName: 'my-stream',
39
+ * streamName: 'pk_...', // playbackId (view key)
40
40
  * enabled: true,
41
41
  * });
42
42
  *
@@ -5,19 +5,16 @@
5
5
  * Manages the complete player lifecycle and provides reactive state.
6
6
  */
7
7
 
8
- import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
8
+ import { useState, useEffect, useRef, useCallback } from 'react';
9
9
  import {
10
10
  PlayerController,
11
11
  type PlayerControllerConfig,
12
- type PlayerControllerEvents,
13
12
  type PlayerState,
14
13
  type StreamState,
15
- type StreamSource,
16
14
  type StreamInfo,
17
15
  type PlaybackQuality,
18
16
  type ContentEndpoints,
19
17
  type ContentMetadata,
20
- type MistStreamInfo,
21
18
  } from '@livepeer-frameworks/player-core';
22
19
 
23
20
  // ============================================================================
@@ -273,6 +270,7 @@ export function usePlayerController(
273
270
  setState(prev => ({
274
271
  ...prev,
275
272
  streamState,
273
+ metadata: controller.getMetadata(),
276
274
  isEffectivelyLive: controller.isEffectivelyLive(),
277
275
  shouldShowIdleScreen: controller.shouldShowIdleScreen(),
278
276
  }));
@@ -329,7 +327,7 @@ export function usePlayerController(
329
327
  });
330
328
  }));
331
329
 
332
- unsubs.push(controller.on('playerSelected', ({ player, source }) => {
330
+ unsubs.push(controller.on('playerSelected', ({ player: _player, source }) => {
333
331
  setState(prev => ({
334
332
  ...prev,
335
333
  currentPlayerInfo: controller.getCurrentPlayerInfo(),
@@ -70,7 +70,7 @@ const initialState: StreamState = {
70
70
  * ```tsx
71
71
  * const { status, isOnline, message } = useStreamState({
72
72
  * mistBaseUrl: 'https://mist.example.com',
73
- * streamName: 'my-stream',
73
+ * streamName: 'pk_...', // playbackId (view key)
74
74
  * pollInterval: 3000,
75
75
  * });
76
76
  * ```
@@ -264,7 +264,7 @@ export function useStreamState(options: UseStreamStateOptions): UseStreamStateRe
264
264
  }
265
265
  };
266
266
 
267
- ws.onerror = (event) => {
267
+ ws.onerror = (_event) => {
268
268
  console.warn('[useStreamState] WebSocket error, falling back to HTTP polling');
269
269
  if (wsTimeoutRef.current) {
270
270
  clearTimeout(wsTimeoutRef.current);
@@ -36,7 +36,7 @@ export interface UseTelemetryOptions extends TelemetryOptions {
36
36
  * endpoint: '/api/telemetry',
37
37
  * interval: 5000,
38
38
  * videoElement,
39
- * contentId: 'my-stream',
39
+ * contentId: 'pk_...', // playbackId (view key)
40
40
  * contentType: 'live',
41
41
  * playerType: 'hlsjs',
42
42
  * protocol: 'HLS',
@@ -7,8 +7,8 @@ const INITIAL_DELAY_MS = 500;
7
7
 
8
8
  interface Params {
9
9
  gatewayUrl: string;
10
- contentType: ContentType;
11
10
  contentId: string;
11
+ contentType?: ContentType;
12
12
  authToken?: string;
13
13
  }
14
14
 
@@ -44,14 +44,14 @@ async function fetchWithRetry(
44
44
  throw lastError ?? new Error('Gateway unreachable after retries');
45
45
  }
46
46
 
47
- export function useViewerEndpoints({ gatewayUrl, contentType, contentId, authToken }: Params) {
47
+ export function useViewerEndpoints({ gatewayUrl, contentType: _contentType, contentId, authToken }: Params) {
48
48
  const [endpoints, setEndpoints] = useState<ContentEndpoints | null>(null);
49
49
  const [status, setStatus] = useState<'idle' | 'loading' | 'ready' | 'error'>('idle');
50
50
  const [error, setError] = useState<string | null>(null);
51
51
  const abortRef = useRef<AbortController | null>(null);
52
52
 
53
53
  useEffect(() => {
54
- if (!gatewayUrl || !contentType || !contentId) return;
54
+ if (!gatewayUrl || !contentId) return;
55
55
  setStatus('loading');
56
56
  setError(null);
57
57
  abortRef.current?.abort();
@@ -62,8 +62,8 @@ export function useViewerEndpoints({ gatewayUrl, contentType, contentId, authTok
62
62
  try {
63
63
  const graphqlEndpoint = gatewayUrl.replace(/\/$/, '');
64
64
  const query = `
65
- query ResolveViewer($contentType: String!, $contentId: String!) {
66
- resolveViewerEndpoint(contentType: $contentType, contentId: $contentId) {
65
+ query ResolveViewer($contentId: String!) {
66
+ resolveViewerEndpoint(contentId: $contentId) {
67
67
  primary { nodeId baseUrl protocol url geoDistance loadScore outputs }
68
68
  fallbacks { nodeId baseUrl protocol url geoDistance loadScore outputs }
69
69
  metadata { contentType contentId title description durationSeconds status isLive viewers recordingSizeBytes clipSource createdAt }
@@ -76,7 +76,7 @@ export function useViewerEndpoints({ gatewayUrl, contentType, contentId, authTok
76
76
  'Content-Type': 'application/json',
77
77
  ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
78
78
  },
79
- body: JSON.stringify({ query, variables: { contentType, contentId } }),
79
+ body: JSON.stringify({ query, variables: { contentId } }),
80
80
  signal: ac.signal,
81
81
  });
82
82
  if (!res.ok) throw new Error(`Gateway GQL error ${res.status}`);
@@ -111,10 +111,9 @@ export function useViewerEndpoints({ gatewayUrl, contentType, contentId, authTok
111
111
  })();
112
112
 
113
113
  return () => ac.abort();
114
- }, [gatewayUrl, contentType, contentId, authToken]);
114
+ }, [gatewayUrl, contentId, authToken]);
115
115
 
116
116
  return { endpoints, status, error };
117
117
  }
118
118
 
119
119
  export default useViewerEndpoints;
120
-
package/src/types.ts CHANGED
@@ -17,7 +17,7 @@ export interface PlayerProps {
17
17
  /** Content identifier or stream name */
18
18
  contentId: string;
19
19
  /** Content type */
20
- contentType: ContentType;
20
+ contentType?: ContentType;
21
21
  /** Pre-resolved endpoints/capabilities from Gateway/Foghorn */
22
22
  endpoints?: ContentEndpoints;
23
23
  /** Optional thumbnail/poster image */
package/src/ui/slider.tsx CHANGED
@@ -13,7 +13,7 @@ export interface SliderProps extends React.ComponentPropsWithoutRef<typeof Slide
13
13
  }
14
14
 
15
15
  const Slider = React.forwardRef<React.ElementRef<typeof SliderPrimitive.Root>, SliderProps>(
16
- ({ className, trackClassName, thumbClassName, showTrack = true, hoverThumb = false, accentColor = false, orientation = "horizontal", ...props }, ref) => {
16
+ ({ className, trackClassName, thumbClassName, showTrack = true, hoverThumb: _hoverThumb = false, accentColor = false, orientation = "horizontal", ...props }, ref) => {
17
17
  // Colors based on accentColor prop
18
18
  const rangeColorClass = accentColor ? "bg-[hsl(var(--tn-cyan,195_100%_50%))]" : "bg-white/90";
19
19
  const thumbColorClass = accentColor ? "bg-[hsl(var(--tn-cyan,195_100%_50%))]" : "bg-white";