@dialtribe/react-sdk 0.1.0-alpha.8 → 0.1.2

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.
@@ -8,9 +8,9 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
8
 
9
9
  var ReactPlayer__default = /*#__PURE__*/_interopDefault(ReactPlayer);
10
10
 
11
- // src/context/DialTribeProvider.tsx
12
- var DialTribeContext = react.createContext(null);
13
- function DialTribeProvider({
11
+ // src/context/DialtribeProvider.tsx
12
+ var DialtribeContext = react.createContext(null);
13
+ function DialtribeProvider({
14
14
  sessionToken: initialToken,
15
15
  onTokenRefresh,
16
16
  onTokenExpired,
@@ -46,19 +46,19 @@ function DialTribeProvider({
46
46
  markExpired,
47
47
  apiBaseUrl
48
48
  };
49
- return /* @__PURE__ */ jsxRuntime.jsx(DialTribeContext.Provider, { value, children });
49
+ return /* @__PURE__ */ jsxRuntime.jsx(DialtribeContext.Provider, { value, children });
50
50
  }
51
- function useDialTribe() {
52
- const context = react.useContext(DialTribeContext);
51
+ function useDialtribe() {
52
+ const context = react.useContext(DialtribeContext);
53
53
  if (!context) {
54
54
  throw new Error(
55
- 'useDialTribe must be used within a DialTribeProvider. Wrap your app with <DialTribeProvider sessionToken="sess_xxx">...</DialTribeProvider>'
55
+ 'useDialtribe must be used within a DialtribeProvider. Wrap your app with <DialtribeProvider sessionToken="sess_xxx">...</DialtribeProvider>'
56
56
  );
57
57
  }
58
58
  return context;
59
59
  }
60
60
 
61
- // src/client/DialTribeClient.ts
61
+ // src/client/DialtribeClient.ts
62
62
  function getDefaultApiBaseUrl() {
63
63
  if (typeof process !== "undefined" && process.env?.NEXT_PUBLIC_DIALTRIBE_API_URL) {
64
64
  return process.env.NEXT_PUBLIC_DIALTRIBE_API_URL;
@@ -72,18 +72,19 @@ function getEndpoints(baseUrl = DIALTRIBE_API_BASE) {
72
72
  broadcast: (id) => `${baseUrl}/broadcasts/${id}`,
73
73
  contentPlay: `${baseUrl}/content/play`,
74
74
  presignedUrl: `${baseUrl}/media/presigned-url`,
75
- sessionStart: `${baseUrl}/session/start`,
76
- sessionPing: `${baseUrl}/session/ping`
75
+ audienceStart: `${baseUrl}/audiences/start`,
76
+ audiencePing: `${baseUrl}/audiences/ping`,
77
+ sessionPing: `${baseUrl}/sessions/ping`
77
78
  };
78
79
  }
79
80
  var ENDPOINTS = getEndpoints();
80
- var DialTribeClient = class {
81
+ var DialtribeClient = class {
81
82
  constructor(config) {
82
83
  this.config = config;
83
84
  this.endpoints = config.apiBaseUrl ? getEndpoints(config.apiBaseUrl) : ENDPOINTS;
84
85
  }
85
86
  /**
86
- * Make an authenticated request to DialTribe API
87
+ * Make an authenticated request to Dialtribe API
87
88
  *
88
89
  * Automatically:
89
90
  * - Adds Authorization header with session token
@@ -197,7 +198,7 @@ var DialTribeClient = class {
197
198
  * @returns audienceId and optional resumePosition
198
199
  */
199
200
  async startSession(params) {
200
- const response = await this.fetch(this.endpoints.sessionStart, {
201
+ const response = await this.fetch(this.endpoints.audienceStart, {
201
202
  method: "POST",
202
203
  body: JSON.stringify(params)
203
204
  });
@@ -216,7 +217,7 @@ var DialTribeClient = class {
216
217
  * - 3: UNMOUNT
217
218
  */
218
219
  async sendSessionPing(params) {
219
- const response = await this.fetch(this.endpoints.sessionPing, {
220
+ const response = await this.fetch(this.endpoints.audiencePing, {
220
221
  method: "POST",
221
222
  body: JSON.stringify(params)
222
223
  });
@@ -748,7 +749,7 @@ function getErrorMessage(error) {
748
749
  }
749
750
  return "Unable to play media. Please try refreshing the page or contact support if the problem persists.";
750
751
  }
751
- function BroadcastPlayer({
752
+ function DialtribePlayer({
752
753
  broadcast,
753
754
  appId,
754
755
  contentId,
@@ -759,18 +760,18 @@ function BroadcastPlayer({
759
760
  className = "",
760
761
  enableKeyboardShortcuts = false
761
762
  }) {
762
- const { sessionToken, setSessionToken, markExpired, apiBaseUrl } = useDialTribe();
763
+ const { sessionToken, setSessionToken, markExpired, apiBaseUrl } = useDialtribe();
763
764
  const clientRef = react.useRef(null);
764
765
  if (!clientRef.current && sessionToken) {
765
- clientRef.current = new DialTribeClient({
766
+ clientRef.current = new DialtribeClient({
766
767
  sessionToken,
767
768
  apiBaseUrl,
768
769
  onTokenRefresh: (newToken, expiresAt) => {
769
- debug.log(`[DialTribeClient] Token refreshed, expires at ${expiresAt}`);
770
+ debug.log(`[DialtribeClient] Token refreshed, expires at ${expiresAt}`);
770
771
  setSessionToken(newToken, expiresAt);
771
772
  },
772
773
  onTokenExpired: () => {
773
- debug.error("[DialTribeClient] Token expired");
774
+ debug.error("[DialtribeClient] Token expired");
774
775
  markExpired();
775
776
  }
776
777
  });
@@ -781,7 +782,9 @@ function BroadcastPlayer({
781
782
  const playerRef = react.useRef(null);
782
783
  const transcriptContainerRef = react.useRef(null);
783
784
  const activeWordRef = react.useRef(null);
784
- const [audioElement, setAudioElement] = react.useState(null);
785
+ const [audioElement, setAudioElement] = react.useState(
786
+ null
787
+ );
785
788
  const [playing, setPlaying] = react.useState(false);
786
789
  const [played, setPlayed] = react.useState(0);
787
790
  const [duration, setDuration] = react.useState(0);
@@ -793,7 +796,9 @@ function BroadcastPlayer({
793
796
  const [hasEnded, setHasEnded] = react.useState(false);
794
797
  const [hasStreamEnded, setHasStreamEnded] = react.useState(false);
795
798
  const [showTranscript, setShowTranscript] = react.useState(false);
796
- const [transcriptData, setTranscriptData] = react.useState(null);
799
+ const [transcriptData, setTranscriptData] = react.useState(
800
+ null
801
+ );
797
802
  const [currentTime, setCurrentTime] = react.useState(0);
798
803
  const [isLoadingTranscript, setIsLoadingTranscript] = react.useState(false);
799
804
  const [isLoadingVideo, setIsLoadingVideo] = react.useState(true);
@@ -821,7 +826,9 @@ function BroadcastPlayer({
821
826
  const refreshPresignedUrl = react.useCallback(
822
827
  async (fileType) => {
823
828
  if (!broadcast.hash || isRefreshingUrl.current || !client) {
824
- debug.log("[URL Refresh] Skipping refresh - no hash, already refreshing, or no client");
829
+ debug.log(
830
+ "[URL Refresh] Skipping refresh - no hash, already refreshing, or no client"
831
+ );
825
832
  return false;
826
833
  }
827
834
  if (fileType === "hls") {
@@ -829,14 +836,18 @@ function BroadcastPlayer({
829
836
  return false;
830
837
  }
831
838
  isRefreshingUrl.current = true;
832
- debug.log(`[URL Refresh] Refreshing ${fileType} URL for broadcast ${broadcast.id}`);
839
+ debug.log(
840
+ `[URL Refresh] Refreshing ${fileType} URL for broadcast ${broadcast.id}`
841
+ );
833
842
  try {
834
843
  const data = await client.refreshPresignedUrl({
835
844
  broadcastId: broadcast.id,
836
845
  hash: broadcast.hash,
837
846
  fileType
838
847
  });
839
- debug.log(`[URL Refresh] Successfully refreshed URL, expires at ${data.expiresAt}`);
848
+ debug.log(
849
+ `[URL Refresh] Successfully refreshed URL, expires at ${data.expiresAt}`
850
+ );
840
851
  setCurrentPlaybackInfo({ url: data.url, type: fileType });
841
852
  setUrlExpiresAt(new Date(data.expiresAt));
842
853
  if (errorMessage.includes("URL") || errorMessage.includes("session") || errorMessage.includes("refresh")) {
@@ -851,7 +862,9 @@ function BroadcastPlayer({
851
862
  }
852
863
  debug.error("[URL Refresh] Failed to refresh presigned URL:", error);
853
864
  setHasError(true);
854
- setErrorMessage("Unable to refresh media URL. The session may have expired.");
865
+ setErrorMessage(
866
+ "Unable to refresh media URL. The session may have expired."
867
+ );
855
868
  if (onError && error instanceof Error) {
856
869
  onError(error);
857
870
  }
@@ -871,7 +884,8 @@ function BroadcastPlayer({
871
884
  };
872
885
  const initializeTrackingSession = react.useCallback(async () => {
873
886
  if (!contentId || !appId || !client) return;
874
- if (currentPlaybackInfo?.type === "hls" && broadcast.broadcastStatus === 1) return;
887
+ if (currentPlaybackInfo?.type === "hls" && broadcast.broadcastStatus === 1)
888
+ return;
875
889
  if (hasInitializedSession.current) return;
876
890
  hasInitializedSession.current = true;
877
891
  try {
@@ -894,7 +908,9 @@ function BroadcastPlayer({
894
908
  setAudienceId(data.audienceId);
895
909
  if (data.resumePosition && data.resumePosition > 0 && audioElement) {
896
910
  audioElement.currentTime = data.resumePosition;
897
- debug.log(`[Audience Tracking] Resumed playback at ${data.resumePosition}s`);
911
+ debug.log(
912
+ `[Audience Tracking] Resumed playback at ${data.resumePosition}s`
913
+ );
898
914
  }
899
915
  debug.log("[Audience Tracking] Session initialized:", data.audienceId);
900
916
  } catch (error) {
@@ -903,7 +919,19 @@ function BroadcastPlayer({
903
919
  onError(error);
904
920
  }
905
921
  }
906
- }, [contentId, appId, broadcast.id, broadcast.broadcastStatus, foreignId, foreignTier, sessionId, currentPlaybackInfo?.type, audioElement, client, onError]);
922
+ }, [
923
+ contentId,
924
+ appId,
925
+ broadcast.id,
926
+ broadcast.broadcastStatus,
927
+ foreignId,
928
+ foreignTier,
929
+ sessionId,
930
+ currentPlaybackInfo?.type,
931
+ audioElement,
932
+ client,
933
+ onError
934
+ ]);
907
935
  const sendTrackingPing = react.useCallback(
908
936
  async (eventType) => {
909
937
  if (!audienceId || !sessionId || !client) return;
@@ -927,15 +955,25 @@ function BroadcastPlayer({
927
955
  return { url: broadcast.hlsPlaylistUrl, type: "hls" };
928
956
  }
929
957
  if (broadcast.hash && broadcast.app?.s3Hash) {
930
- const hlsUrl = buildBroadcastCdnUrl(broadcast.app.s3Hash, broadcast.hash, "index.m3u8");
958
+ const hlsUrl = buildBroadcastCdnUrl(
959
+ broadcast.app.s3Hash,
960
+ broadcast.hash,
961
+ "index.m3u8"
962
+ );
931
963
  return { url: hlsUrl, type: "hls" };
932
964
  }
933
965
  }
934
966
  if (broadcast.recordingMp4Url && broadcast.isVideo && broadcast.hash) {
935
- return { url: buildPlaybackUrl(broadcast.id, broadcast.hash), type: "mp4" };
967
+ return {
968
+ url: buildPlaybackUrl(broadcast.id, broadcast.hash),
969
+ type: "mp4"
970
+ };
936
971
  }
937
972
  if (broadcast.recordingMp3Url && broadcast.hash) {
938
- return { url: buildPlaybackUrl(broadcast.id, broadcast.hash), type: "mp3" };
973
+ return {
974
+ url: buildPlaybackUrl(broadcast.id, broadcast.hash),
975
+ type: "mp3"
976
+ };
939
977
  }
940
978
  if (broadcast.hlsPlaylistUrl) {
941
979
  return { url: broadcast.hlsPlaylistUrl, type: "hls" };
@@ -950,7 +988,9 @@ function BroadcastPlayer({
950
988
  if (info && (info.type === "mp4" || info.type === "mp3")) {
951
989
  const expiresAt = new Date(Date.now() + URL_EXPIRATION_MS);
952
990
  setUrlExpiresAt(expiresAt);
953
- debug.log(`[URL Refresh] Initial ${info.type} URL expires at ${expiresAt.toISOString()}`);
991
+ debug.log(
992
+ `[URL Refresh] Initial ${info.type} URL expires at ${expiresAt.toISOString()}`
993
+ );
954
994
  }
955
995
  if (info) {
956
996
  setPlaying(true);
@@ -989,12 +1029,34 @@ function BroadcastPlayer({
989
1029
  setAudioElement(null);
990
1030
  setPlaying(true);
991
1031
  }
992
- }, [broadcast.broadcastStatus, broadcast.recordingMp3Url, broadcast.mp3Size, broadcast.hash, broadcast.id, currentPlaybackInfo]);
1032
+ }, [
1033
+ broadcast.broadcastStatus,
1034
+ broadcast.recordingMp3Url,
1035
+ broadcast.mp3Size,
1036
+ broadcast.hash,
1037
+ broadcast.id,
1038
+ currentPlaybackInfo
1039
+ ]);
993
1040
  const playbackUrl = currentPlaybackInfo?.url || null;
994
1041
  const playbackType = currentPlaybackInfo?.type || null;
995
1042
  const isAudioOnly = playbackType === "mp3" || !broadcast.isVideo && playbackType !== "mp4";
996
1043
  const isLiveStream = broadcast.broadcastStatus === 1 && playbackType === "hls" && !hasStreamEnded;
997
1044
  const wasLiveStream = initialPlaybackTypeRef.current === "hls";
1045
+ const playerConfig = react.useMemo(
1046
+ () => ({
1047
+ file: {
1048
+ forceHLS: playbackType === "hls",
1049
+ hlsOptions: isLiveStream ? {
1050
+ maxLoadingDelay: 10,
1051
+ minAutoBitrate: 0,
1052
+ lowLatencyMode: true,
1053
+ enableWorker: true
1054
+ } : {}
1055
+ }
1056
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1057
+ }),
1058
+ [playbackType, isLiveStream]
1059
+ );
998
1060
  const formatTimestamp = (seconds) => {
999
1061
  if (!seconds || isNaN(seconds) || !isFinite(seconds)) return "00:00:00";
1000
1062
  const hrs = Math.floor(seconds / 3600);
@@ -1065,7 +1127,7 @@ function BroadcastPlayer({
1065
1127
  duration: Math.floor(duration || 0)
1066
1128
  };
1067
1129
  const headers = {
1068
- "Authorization": `Bearer ${sessionToken}`,
1130
+ Authorization: `Bearer ${sessionToken}`,
1069
1131
  "Content-Type": "application/json"
1070
1132
  };
1071
1133
  fetch(ENDPOINTS.sessionPing, {
@@ -1083,7 +1145,9 @@ function BroadcastPlayer({
1083
1145
  setIsLoadingTranscript(true);
1084
1146
  fetch(broadcast.transcriptUrl).then((res) => {
1085
1147
  if (!res.ok) {
1086
- throw new Error(`Failed to fetch transcript: ${res.status} ${res.statusText}`);
1148
+ throw new Error(
1149
+ `Failed to fetch transcript: ${res.status} ${res.statusText}`
1150
+ );
1087
1151
  }
1088
1152
  return res.json();
1089
1153
  }).then((data) => {
@@ -1215,7 +1279,7 @@ function BroadcastPlayer({
1215
1279
  setAudioElement(internalPlayer);
1216
1280
  }
1217
1281
  } catch (error) {
1218
- debug.error("[BroadcastPlayer] Error getting internal player:", error);
1282
+ debug.error("[DialtribePlayer] Error getting internal player:", error);
1219
1283
  }
1220
1284
  };
1221
1285
  react.useEffect(() => {
@@ -1229,7 +1293,17 @@ function BroadcastPlayer({
1229
1293
  return false;
1230
1294
  };
1231
1295
  if (!findAudioElement()) {
1232
- const retryIntervals = [100, 300, 500, 1e3, 1500, 2e3, 3e3, 4e3, 5e3];
1296
+ const retryIntervals = [
1297
+ 100,
1298
+ 300,
1299
+ 500,
1300
+ 1e3,
1301
+ 1500,
1302
+ 2e3,
1303
+ 3e3,
1304
+ 4e3,
1305
+ 5e3
1306
+ ];
1233
1307
  const timeouts = retryIntervals.map(
1234
1308
  (delay) => setTimeout(() => {
1235
1309
  findAudioElement();
@@ -1287,16 +1361,23 @@ function BroadcastPlayer({
1287
1361
  debug.error("Media playback error:", error);
1288
1362
  const isPotentialExpiration = error?.code === HTTP_STATUS.FORBIDDEN || error?.status === HTTP_STATUS.FORBIDDEN || error?.statusCode === HTTP_STATUS.FORBIDDEN || error?.code === HTTP_STATUS.NOT_FOUND || error?.status === HTTP_STATUS.NOT_FOUND || error?.statusCode === HTTP_STATUS.NOT_FOUND || error?.message?.includes("403") || error?.message?.includes("404") || error?.message?.includes("Forbidden") || error?.message?.toLowerCase().includes("network") || error?.type === "network" || error?.message?.includes("MEDIA_ERR_SRC_NOT_SUPPORTED");
1289
1363
  if (isPotentialExpiration && currentPlaybackInfo?.type && !isRefreshingUrl.current) {
1290
- debug.log("[Player Error] Detected potential URL expiration, attempting refresh...");
1364
+ debug.log(
1365
+ "[Player Error] Detected potential URL expiration, attempting refresh..."
1366
+ );
1291
1367
  const currentPosition = audioElement?.currentTime || 0;
1292
1368
  const wasPlaying = playing;
1293
1369
  const fileType = currentPlaybackInfo.type;
1294
1370
  if (fileType !== "mp4" && fileType !== "mp3" && fileType !== "hls") {
1295
- debug.error("[Player Error] Invalid file type, cannot refresh:", fileType);
1371
+ debug.error(
1372
+ "[Player Error] Invalid file type, cannot refresh:",
1373
+ fileType
1374
+ );
1296
1375
  } else {
1297
1376
  const refreshed = await refreshPresignedUrl(fileType);
1298
1377
  if (refreshed) {
1299
- debug.log("[Player Error] URL refreshed successfully, resuming playback");
1378
+ debug.log(
1379
+ "[Player Error] URL refreshed successfully, resuming playback"
1380
+ );
1300
1381
  setTimeout(() => {
1301
1382
  if (audioElement && currentPosition > 0) {
1302
1383
  audioElement.currentTime = currentPosition;
@@ -1344,7 +1425,10 @@ function BroadcastPlayer({
1344
1425
  if (!enableKeyboardShortcuts) return;
1345
1426
  const seekBy = (seconds) => {
1346
1427
  if (!audioElement || duration <= 0) return;
1347
- const newTime = Math.max(0, Math.min(duration, audioElement.currentTime + seconds));
1428
+ const newTime = Math.max(
1429
+ 0,
1430
+ Math.min(duration, audioElement.currentTime + seconds)
1431
+ );
1348
1432
  audioElement.currentTime = newTime;
1349
1433
  setPlayed(newTime / duration);
1350
1434
  };
@@ -1412,7 +1496,17 @@ function BroadcastPlayer({
1412
1496
  };
1413
1497
  window.addEventListener("keydown", handleKeyDown);
1414
1498
  return () => window.removeEventListener("keydown", handleKeyDown);
1415
- }, [enableKeyboardShortcuts, audioElement, duration, playing, muted, isAudioOnly, handlePlayPause, toggleMute, toggleFullscreen]);
1499
+ }, [
1500
+ enableKeyboardShortcuts,
1501
+ audioElement,
1502
+ duration,
1503
+ playing,
1504
+ muted,
1505
+ isAudioOnly,
1506
+ handlePlayPause,
1507
+ toggleMute,
1508
+ toggleFullscreen
1509
+ ]);
1416
1510
  if (currentPlaybackInfo !== null && !playbackUrl) {
1417
1511
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white dark:bg-zinc-900 rounded-lg p-6 max-w-md w-full mx-4 border border-gray-200 dark:border-zinc-800", children: [
1418
1512
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-bold text-black dark:text-white mb-4", children: "Broadcast Unavailable" }),
@@ -1423,452 +1517,914 @@ function BroadcastPlayer({
1423
1517
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center p-8", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner, { variant: "white", text: "Loading..." }) });
1424
1518
  }
1425
1519
  const hasTranscript = broadcast.transcriptStatus === 2 && transcriptData && (transcriptData.segments && transcriptData.segments.some((s) => s.words && s.words.length > 0) || transcriptData.words && transcriptData.words.length > 0);
1426
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `bg-black rounded-lg shadow-2xl w-full max-h-full flex flex-col overflow-hidden ${className}`, children: [
1427
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-zinc-900/50 backdrop-blur-sm border-b border-zinc-800 px-3 sm:px-4 md:px-6 py-2 sm:py-3 md:py-4 flex justify-between items-center rounded-t-lg shrink-0", children: [
1428
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1429
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-white", children: broadcast.streamKeyRecord?.foreignName || "Broadcast" }),
1430
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-400", children: [
1431
- broadcast.isVideo ? "Video" : "Audio",
1432
- " \u2022",
1433
- " ",
1434
- broadcast.broadcastStatus === 1 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500 font-semibold", children: "\u{1F534} LIVE" }) : playbackType === "hls" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500 font-semibold", children: "OFFLINE" }) : formatTime(duration)
1435
- ] })
1436
- ] }),
1437
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-3", children: renderClipCreator && playbackType !== "hls" && appId && contentId && duration > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
1438
- "button",
1439
- {
1440
- onClick: () => setShowClipCreator(true),
1441
- className: "px-3 md:px-4 py-1.5 md:py-2 bg-blue-600 hover:bg-blue-700 text-white text-xs md:text-sm font-medium rounded-lg transition-colors flex items-center gap-1 md:gap-2",
1442
- title: "Create a clip from this broadcast",
1443
- "aria-label": "Create clip from broadcast",
1444
- children: [
1445
- /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "w-3 h-3 md:w-4 md:h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: [
1446
- /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" }),
1447
- /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 12a9 9 0 11-18 0 9 9 0 0118 0z" })
1448
- ] }),
1449
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Create Clip" }),
1450
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sm:hidden", children: "Clip" })
1451
- ]
1452
- }
1453
- ) })
1454
- ] }),
1455
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col md:flex-row flex-1 min-h-0 overflow-hidden", children: [
1456
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 md:shrink md:flex-1 flex flex-col overflow-hidden", children: [
1457
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative ${isAudioOnly ? "bg-linear-to-br from-zinc-900 via-zinc-800 to-zinc-900 flex items-stretch" : "bg-black"}`, children: [
1458
- isAudioOnly ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative cursor-pointer w-full flex flex-col", onClick: handleVideoClick, children: !hasError ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full h-full relative", children: [
1459
- /* @__PURE__ */ jsxRuntime.jsx(AudioWaveform, { audioElement, isPlaying: isLiveStream ? true : playing, isLive: isLiveStream }),
1460
- isLoadingVideo && !hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/90 flex items-center justify-center z-20", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner, { variant: "white" }) }),
1461
- hasEnded && !wasLiveStream && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/50 flex items-center justify-center z-20 pointer-events-auto", children: /* @__PURE__ */ jsxRuntime.jsxs(
1462
- "button",
1463
- {
1464
- onClick: (e) => {
1465
- e.stopPropagation();
1466
- handleRestart();
1467
- },
1468
- className: "bg-white hover:bg-blue-500 text-black hover:text-white font-semibold py-4 px-8 rounded-full transition-all transform hover:scale-105 flex items-center gap-3",
1469
- "aria-label": "Restart playback from beginning",
1470
- children: [
1471
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-6 h-6", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z", clipRule: "evenodd" }) }),
1472
- "Restart"
1473
- ]
1474
- }
1475
- ) })
1476
- ] }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center max-w-md px-4", children: [
1477
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-6xl mb-4", children: "\u26A0\uFE0F" }),
1478
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-semibold text-white mb-3", children: "Playback Error" }),
1479
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-300 text-sm mb-6", children: errorMessage }),
1480
- /* @__PURE__ */ jsxRuntime.jsxs(
1481
- "button",
1482
- {
1483
- onClick: handleRetry,
1484
- className: "px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors inline-flex items-center gap-2",
1485
- "aria-label": "Retry playback",
1486
- children: [
1487
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) }),
1488
- "Retry"
1489
- ]
1490
- }
1491
- )
1492
- ] }) }) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aspect-video relative", children: [
1493
- /* @__PURE__ */ jsxRuntime.jsx("div", { onClick: handleVideoClick, className: "cursor-pointer", children: /* @__PURE__ */ jsxRuntime.jsx(
1494
- ReactPlayer__default.default,
1495
- {
1496
- ref: playerRef,
1497
- src: playbackUrl || void 0,
1498
- playing,
1499
- volume,
1500
- muted,
1501
- width: "100%",
1502
- height: "100%",
1503
- crossOrigin: "anonymous",
1504
- onReady: handlePlayerReady,
1505
- onTimeUpdate: handleTimeUpdate,
1506
- onLoadedMetadata: handleLoadedMetadata,
1507
- onPlay: handlePlay,
1508
- onPause: handlePause,
1509
- onEnded: handleEnded,
1510
- onError: handleError,
1511
- style: { backgroundColor: "#000" }
1512
- },
1513
- playbackUrl || "no-url"
1514
- ) }),
1515
- isLoadingVideo && !hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/90 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner, { variant: "white" }) }),
1516
- hasEnded && !hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/50 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs(
1517
- "button",
1518
- {
1519
- onClick: (e) => {
1520
- e.stopPropagation();
1521
- handleRestart();
1522
- },
1523
- className: "bg-white hover:bg-blue-500 text-black hover:text-white font-semibold py-4 px-8 rounded-full transition-all transform hover:scale-105 flex items-center gap-3",
1524
- children: [
1525
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-6 h-6", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z", clipRule: "evenodd" }) }),
1526
- "Restart"
1527
- ]
1528
- }
1529
- ) }),
1530
- hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/90 flex items-center justify-center p-8", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center max-w-md", children: [
1531
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-6xl mb-4", children: "\u26A0\uFE0F" }),
1532
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-semibold text-white mb-3", children: "Playback Error" }),
1533
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-300 text-sm mb-6", children: errorMessage }),
1534
- /* @__PURE__ */ jsxRuntime.jsxs(
1535
- "button",
1536
- {
1537
- onClick: handleRetry,
1538
- className: "px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors inline-flex items-center gap-2",
1539
- "aria-label": "Retry playback",
1540
- children: [
1541
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) }),
1542
- "Retry"
1543
- ]
1544
- }
1545
- )
1546
- ] }) })
1520
+ const playerContent = /* @__PURE__ */ jsxRuntime.jsxs(
1521
+ "div",
1522
+ {
1523
+ className: `bg-black rounded-lg shadow-2xl w-full max-h-full flex flex-col overflow-hidden ${className}`,
1524
+ children: [
1525
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-zinc-900/50 backdrop-blur-sm border-b border-zinc-800 px-3 sm:px-4 md:px-6 py-2 sm:py-3 md:py-4 flex justify-between items-center rounded-t-lg shrink-0", children: [
1526
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1527
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-white", children: broadcast.streamKeyRecord?.foreignName || "Broadcast" }),
1528
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-400", children: [
1529
+ broadcast.isVideo ? "Video" : "Audio",
1530
+ " \u2022",
1531
+ " ",
1532
+ broadcast.broadcastStatus === 1 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500 font-semibold", children: "\u{1F534} LIVE" }) : playbackType === "hls" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500 font-semibold", children: "OFFLINE" }) : formatTime(duration)
1533
+ ] })
1547
1534
  ] }),
1548
- isAudioOnly && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
1549
- ReactPlayer__default.default,
1535
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-3", children: renderClipCreator && playbackType !== "hls" && appId && contentId && duration > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
1536
+ "button",
1550
1537
  {
1551
- ref: playerRef,
1552
- src: playbackUrl || void 0,
1553
- playing,
1554
- volume,
1555
- muted,
1556
- width: "0",
1557
- height: "0",
1558
- crossOrigin: "anonymous",
1559
- onReady: handlePlayerReady,
1560
- onTimeUpdate: handleTimeUpdate,
1561
- onLoadedMetadata: handleLoadedMetadata,
1562
- onPlay: handlePlay,
1563
- onPause: handlePause,
1564
- onEnded: handleEnded,
1565
- onError: handleError
1566
- },
1567
- playbackUrl || "no-url"
1538
+ onClick: () => setShowClipCreator(true),
1539
+ className: "px-3 md:px-4 py-1.5 md:py-2 bg-blue-600 hover:bg-blue-700 text-white text-xs md:text-sm font-medium rounded-lg transition-colors flex items-center gap-1 md:gap-2",
1540
+ title: "Create a clip from this broadcast",
1541
+ "aria-label": "Create clip from broadcast",
1542
+ children: [
1543
+ /* @__PURE__ */ jsxRuntime.jsxs(
1544
+ "svg",
1545
+ {
1546
+ className: "w-3 h-3 md:w-4 md:h-4",
1547
+ fill: "none",
1548
+ stroke: "currentColor",
1549
+ viewBox: "0 0 24 24",
1550
+ children: [
1551
+ /* @__PURE__ */ jsxRuntime.jsx(
1552
+ "path",
1553
+ {
1554
+ strokeLinecap: "round",
1555
+ strokeLinejoin: "round",
1556
+ strokeWidth: 2,
1557
+ d: "M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
1558
+ }
1559
+ ),
1560
+ /* @__PURE__ */ jsxRuntime.jsx(
1561
+ "path",
1562
+ {
1563
+ strokeLinecap: "round",
1564
+ strokeLinejoin: "round",
1565
+ strokeWidth: 2,
1566
+ d: "M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
1567
+ }
1568
+ )
1569
+ ]
1570
+ }
1571
+ ),
1572
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Create Clip" }),
1573
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sm:hidden", children: "Clip" })
1574
+ ]
1575
+ }
1568
1576
  ) })
1569
1577
  ] }),
1570
- !hasError && !isLiveStream && (wasLiveStream ? parseInt(broadcast.mp3Size || "0") > 0 || parseInt(broadcast.mp4Size || "0") > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-zinc-900/50 backdrop-blur-sm px-4 md:px-6 py-3 md:py-4 rounded-b-lg", children: [
1571
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4", children: [
1572
- /* @__PURE__ */ jsxRuntime.jsx(
1573
- "input",
1578
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col md:flex-row flex-1 min-h-0 overflow-hidden", children: [
1579
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 md:shrink md:flex-1 flex flex-col overflow-hidden", children: [
1580
+ /* @__PURE__ */ jsxRuntime.jsxs(
1581
+ "div",
1574
1582
  {
1575
- type: "range",
1576
- min: 0,
1577
- max: 0.999999,
1578
- step: "any",
1579
- value: played || 0,
1580
- onMouseDown: handleSeekMouseDown,
1581
- onMouseUp: handleSeekMouseUp,
1582
- onTouchStart: handleSeekTouchStart,
1583
- onTouchEnd: handleSeekTouchEnd,
1584
- onChange: handleSeekChange,
1585
- className: "w-full h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer slider",
1586
- "aria-label": "Seek position",
1587
- "aria-valuemin": 0,
1588
- "aria-valuemax": duration,
1589
- "aria-valuenow": played * duration,
1590
- "aria-valuetext": `${formatTime(played * duration)} of ${formatTime(duration)}`,
1591
- role: "slider"
1583
+ className: `relative ${isAudioOnly ? "bg-linear-to-br from-zinc-900 via-zinc-800 to-zinc-900 flex items-stretch" : "bg-black"}`,
1584
+ children: [
1585
+ isAudioOnly ? /* @__PURE__ */ jsxRuntime.jsx(
1586
+ "div",
1587
+ {
1588
+ className: "relative cursor-pointer w-full flex flex-col",
1589
+ onClick: handleVideoClick,
1590
+ children: !hasError ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full h-full relative", children: [
1591
+ /* @__PURE__ */ jsxRuntime.jsx(
1592
+ AudioWaveform,
1593
+ {
1594
+ audioElement,
1595
+ isPlaying: isLiveStream ? true : playing,
1596
+ isLive: isLiveStream
1597
+ }
1598
+ ),
1599
+ isLoadingVideo && !hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/90 flex items-center justify-center z-20", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner, { variant: "white" }) }),
1600
+ hasEnded && !wasLiveStream && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/50 flex items-center justify-center z-20 pointer-events-auto", children: /* @__PURE__ */ jsxRuntime.jsxs(
1601
+ "button",
1602
+ {
1603
+ onClick: (e) => {
1604
+ e.stopPropagation();
1605
+ handleRestart();
1606
+ },
1607
+ className: "bg-white hover:bg-blue-500 text-black hover:text-white font-semibold py-4 px-8 rounded-full transition-all transform hover:scale-105 flex items-center gap-3",
1608
+ "aria-label": "Restart playback from beginning",
1609
+ children: [
1610
+ /* @__PURE__ */ jsxRuntime.jsx(
1611
+ "svg",
1612
+ {
1613
+ className: "w-6 h-6",
1614
+ fill: "currentColor",
1615
+ viewBox: "0 0 20 20",
1616
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1617
+ "path",
1618
+ {
1619
+ fillRule: "evenodd",
1620
+ d: "M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",
1621
+ clipRule: "evenodd"
1622
+ }
1623
+ )
1624
+ }
1625
+ ),
1626
+ "Restart"
1627
+ ]
1628
+ }
1629
+ ) })
1630
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center max-w-md px-4", children: [
1631
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-6xl mb-4", children: "\u26A0\uFE0F" }),
1632
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-semibold text-white mb-3", children: "Playback Error" }),
1633
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-300 text-sm mb-6", children: errorMessage }),
1634
+ /* @__PURE__ */ jsxRuntime.jsxs(
1635
+ "button",
1636
+ {
1637
+ onClick: handleRetry,
1638
+ className: "px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors inline-flex items-center gap-2",
1639
+ "aria-label": "Retry playback",
1640
+ children: [
1641
+ /* @__PURE__ */ jsxRuntime.jsx(
1642
+ "svg",
1643
+ {
1644
+ className: "w-5 h-5",
1645
+ fill: "none",
1646
+ stroke: "currentColor",
1647
+ viewBox: "0 0 24 24",
1648
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1649
+ "path",
1650
+ {
1651
+ strokeLinecap: "round",
1652
+ strokeLinejoin: "round",
1653
+ strokeWidth: 2,
1654
+ d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
1655
+ }
1656
+ )
1657
+ }
1658
+ ),
1659
+ "Retry"
1660
+ ]
1661
+ }
1662
+ )
1663
+ ] }) })
1664
+ }
1665
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "aspect-video relative", children: [
1666
+ /* @__PURE__ */ jsxRuntime.jsx("div", { onClick: handleVideoClick, className: "cursor-pointer", children: /* @__PURE__ */ jsxRuntime.jsx(
1667
+ ReactPlayer__default.default,
1668
+ {
1669
+ ref: playerRef,
1670
+ src: playbackUrl || void 0,
1671
+ playing,
1672
+ volume,
1673
+ muted,
1674
+ width: "100%",
1675
+ height: "100%",
1676
+ crossOrigin: "anonymous",
1677
+ config: playerConfig,
1678
+ onReady: handlePlayerReady,
1679
+ onTimeUpdate: handleTimeUpdate,
1680
+ onLoadedMetadata: handleLoadedMetadata,
1681
+ onPlay: handlePlay,
1682
+ onPause: handlePause,
1683
+ onEnded: handleEnded,
1684
+ onError: handleError,
1685
+ style: { backgroundColor: "#000" }
1686
+ },
1687
+ playbackUrl || "no-url"
1688
+ ) }),
1689
+ isLoadingVideo && !hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/90 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner, { variant: "white" }) }),
1690
+ hasEnded && !hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/50 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs(
1691
+ "button",
1692
+ {
1693
+ onClick: (e) => {
1694
+ e.stopPropagation();
1695
+ handleRestart();
1696
+ },
1697
+ className: "bg-white hover:bg-blue-500 text-black hover:text-white font-semibold py-4 px-8 rounded-full transition-all transform hover:scale-105 flex items-center gap-3",
1698
+ "aria-label": "Restart playback from beginning",
1699
+ children: [
1700
+ /* @__PURE__ */ jsxRuntime.jsx(
1701
+ "svg",
1702
+ {
1703
+ className: "w-6 h-6",
1704
+ fill: "currentColor",
1705
+ viewBox: "0 0 20 20",
1706
+ "aria-hidden": "true",
1707
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1708
+ "path",
1709
+ {
1710
+ fillRule: "evenodd",
1711
+ d: "M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",
1712
+ clipRule: "evenodd"
1713
+ }
1714
+ )
1715
+ }
1716
+ ),
1717
+ "Restart"
1718
+ ]
1719
+ }
1720
+ ) }),
1721
+ hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-black/90 flex items-center justify-center p-8", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center max-w-md", children: [
1722
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-6xl mb-4", children: "\u26A0\uFE0F" }),
1723
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xl font-semibold text-white mb-3", children: "Playback Error" }),
1724
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-300 text-sm mb-6", children: errorMessage }),
1725
+ /* @__PURE__ */ jsxRuntime.jsxs(
1726
+ "button",
1727
+ {
1728
+ onClick: handleRetry,
1729
+ className: "px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors inline-flex items-center gap-2",
1730
+ "aria-label": "Retry playback",
1731
+ children: [
1732
+ /* @__PURE__ */ jsxRuntime.jsx(
1733
+ "svg",
1734
+ {
1735
+ className: "w-5 h-5",
1736
+ fill: "none",
1737
+ stroke: "currentColor",
1738
+ viewBox: "0 0 24 24",
1739
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1740
+ "path",
1741
+ {
1742
+ strokeLinecap: "round",
1743
+ strokeLinejoin: "round",
1744
+ strokeWidth: 2,
1745
+ d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
1746
+ }
1747
+ )
1748
+ }
1749
+ ),
1750
+ "Retry"
1751
+ ]
1752
+ }
1753
+ )
1754
+ ] }) })
1755
+ ] }),
1756
+ isAudioOnly && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
1757
+ ReactPlayer__default.default,
1758
+ {
1759
+ ref: playerRef,
1760
+ src: playbackUrl || void 0,
1761
+ playing,
1762
+ volume,
1763
+ muted,
1764
+ width: "0",
1765
+ height: "0",
1766
+ crossOrigin: "anonymous",
1767
+ config: playerConfig,
1768
+ onReady: handlePlayerReady,
1769
+ onTimeUpdate: handleTimeUpdate,
1770
+ onLoadedMetadata: handleLoadedMetadata,
1771
+ onPlay: handlePlay,
1772
+ onPause: handlePause,
1773
+ onEnded: handleEnded,
1774
+ onError: handleError
1775
+ },
1776
+ playbackUrl || "no-url"
1777
+ ) })
1778
+ ]
1592
1779
  }
1593
1780
  ),
1594
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-xs text-gray-400 mt-1", children: [
1595
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime((played || 0) * duration) }),
1596
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime(duration) })
1597
- ] })
1598
- ] }),
1599
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
1600
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
1601
- /* @__PURE__ */ jsxRuntime.jsx(
1602
- "button",
1603
- {
1604
- onClick: handlePlayPause,
1605
- className: "text-white hover:text-blue-400 transition-colors",
1606
- title: playing ? "Pause" : "Play",
1607
- "aria-label": playing ? "Pause" : "Play",
1608
- "aria-pressed": playing,
1609
- children: playing ? /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-8 h-8", fill: "currentColor", viewBox: "0 0 20 20", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z", clipRule: "evenodd" }) }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-8 h-8", fill: "currentColor", viewBox: "0 0 20 20", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z", clipRule: "evenodd" }) })
1610
- }
1611
- ),
1612
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1781
+ !hasError && !isLiveStream && (wasLiveStream ? parseInt(broadcast.mp3Size || "0") > 0 || parseInt(broadcast.mp4Size || "0") > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-zinc-900/50 backdrop-blur-sm px-4 md:px-6 py-3 md:py-4 rounded-b-lg", children: [
1782
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4", children: [
1613
1783
  /* @__PURE__ */ jsxRuntime.jsx(
1614
- "button",
1784
+ "input",
1615
1785
  {
1616
- onClick: toggleMute,
1617
- className: "text-white hover:text-blue-400 transition-colors",
1618
- title: muted ? "Unmute" : "Mute",
1619
- "aria-label": muted ? "Unmute" : "Mute",
1620
- "aria-pressed": muted,
1621
- children: muted || volume === 0 ? /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM12.293 7.293a1 1 0 011.414 0L15 8.586l1.293-1.293a1 1 0 111.414 1.414L16.414 10l1.293 1.293a1 1 0 01-1.414 1.414L15 11.414l-1.293 1.293a1 1 0 01-1.414-1.414L13.586 10l-1.293-1.293a1 1 0 010-1.414z", clipRule: "evenodd" }) }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z", clipRule: "evenodd" }) })
1786
+ type: "range",
1787
+ min: 0,
1788
+ max: 0.999999,
1789
+ step: "any",
1790
+ value: played || 0,
1791
+ onMouseDown: handleSeekMouseDown,
1792
+ onMouseUp: handleSeekMouseUp,
1793
+ onTouchStart: handleSeekTouchStart,
1794
+ onTouchEnd: handleSeekTouchEnd,
1795
+ onChange: handleSeekChange,
1796
+ className: "w-full h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer slider",
1797
+ "aria-label": "Seek position",
1798
+ "aria-valuemin": 0,
1799
+ "aria-valuemax": duration,
1800
+ "aria-valuenow": played * duration,
1801
+ "aria-valuetext": `${formatTime(
1802
+ played * duration
1803
+ )} of ${formatTime(duration)}`,
1804
+ role: "slider"
1622
1805
  }
1623
1806
  ),
1807
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-xs text-gray-400 mt-1", children: [
1808
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime((played || 0) * duration) }),
1809
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime(duration) })
1810
+ ] })
1811
+ ] }),
1812
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
1813
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
1814
+ /* @__PURE__ */ jsxRuntime.jsx(
1815
+ "button",
1816
+ {
1817
+ onClick: handlePlayPause,
1818
+ className: "text-white hover:text-blue-400 transition-colors",
1819
+ title: playing ? "Pause" : "Play",
1820
+ "aria-label": playing ? "Pause" : "Play",
1821
+ "aria-pressed": playing,
1822
+ children: playing ? /* @__PURE__ */ jsxRuntime.jsx(
1823
+ "svg",
1824
+ {
1825
+ className: "w-8 h-8",
1826
+ fill: "currentColor",
1827
+ viewBox: "0 0 20 20",
1828
+ "aria-hidden": "true",
1829
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1830
+ "path",
1831
+ {
1832
+ fillRule: "evenodd",
1833
+ d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",
1834
+ clipRule: "evenodd"
1835
+ }
1836
+ )
1837
+ }
1838
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1839
+ "svg",
1840
+ {
1841
+ className: "w-8 h-8",
1842
+ fill: "currentColor",
1843
+ viewBox: "0 0 20 20",
1844
+ "aria-hidden": "true",
1845
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1846
+ "path",
1847
+ {
1848
+ fillRule: "evenodd",
1849
+ d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",
1850
+ clipRule: "evenodd"
1851
+ }
1852
+ )
1853
+ }
1854
+ )
1855
+ }
1856
+ ),
1857
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1858
+ /* @__PURE__ */ jsxRuntime.jsx(
1859
+ "button",
1860
+ {
1861
+ onClick: toggleMute,
1862
+ className: "text-white hover:text-blue-400 transition-colors",
1863
+ title: muted ? "Unmute" : "Mute",
1864
+ "aria-label": muted ? "Unmute" : "Mute",
1865
+ "aria-pressed": muted,
1866
+ children: muted || volume === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
1867
+ "svg",
1868
+ {
1869
+ className: "w-5 h-5",
1870
+ fill: "currentColor",
1871
+ viewBox: "0 0 20 20",
1872
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1873
+ "path",
1874
+ {
1875
+ fillRule: "evenodd",
1876
+ d: "M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM12.293 7.293a1 1 0 011.414 0L15 8.586l1.293-1.293a1 1 0 111.414 1.414L16.414 10l1.293 1.293a1 1 0 01-1.414 1.414L15 11.414l-1.293 1.293a1 1 0 01-1.414-1.414L13.586 10l-1.293-1.293a1 1 0 010-1.414z",
1877
+ clipRule: "evenodd"
1878
+ }
1879
+ )
1880
+ }
1881
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1882
+ "svg",
1883
+ {
1884
+ className: "w-5 h-5",
1885
+ fill: "currentColor",
1886
+ viewBox: "0 0 20 20",
1887
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1888
+ "path",
1889
+ {
1890
+ fillRule: "evenodd",
1891
+ d: "M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z",
1892
+ clipRule: "evenodd"
1893
+ }
1894
+ )
1895
+ }
1896
+ )
1897
+ }
1898
+ ),
1899
+ /* @__PURE__ */ jsxRuntime.jsx(
1900
+ "input",
1901
+ {
1902
+ type: "range",
1903
+ min: 0,
1904
+ max: 1,
1905
+ step: 0.01,
1906
+ value: muted ? 0 : volume || 1,
1907
+ onChange: handleVolumeChange,
1908
+ className: "w-20 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer slider",
1909
+ "aria-label": "Volume",
1910
+ "aria-valuemin": 0,
1911
+ "aria-valuemax": 100,
1912
+ "aria-valuenow": muted ? 0 : Math.round(volume * 100),
1913
+ "aria-valuetext": muted ? "Muted" : `${Math.round(volume * 100)}%`,
1914
+ role: "slider"
1915
+ }
1916
+ )
1917
+ ] })
1918
+ ] }),
1919
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
1920
+ !isLiveStream && broadcast.hash && (broadcast.recordingMp4Url || broadcast.recordingMp3Url) && /* @__PURE__ */ jsxRuntime.jsx(
1921
+ "button",
1922
+ {
1923
+ onClick: () => {
1924
+ const downloadUrl = buildPlaybackUrl(
1925
+ broadcast.id,
1926
+ broadcast.hash,
1927
+ "download"
1928
+ );
1929
+ window.open(downloadUrl, "_blank");
1930
+ },
1931
+ className: "text-white hover:text-blue-400 transition-colors",
1932
+ title: "Download Recording",
1933
+ "aria-label": "Download recording",
1934
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1935
+ "svg",
1936
+ {
1937
+ className: "w-5 h-5",
1938
+ fill: "none",
1939
+ stroke: "currentColor",
1940
+ viewBox: "0 0 24 24",
1941
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1942
+ "path",
1943
+ {
1944
+ strokeLinecap: "round",
1945
+ strokeLinejoin: "round",
1946
+ strokeWidth: 2,
1947
+ d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
1948
+ }
1949
+ )
1950
+ }
1951
+ )
1952
+ }
1953
+ ),
1954
+ !isAudioOnly && /* @__PURE__ */ jsxRuntime.jsx(
1955
+ "button",
1956
+ {
1957
+ onClick: toggleFullscreen,
1958
+ className: "text-white hover:text-blue-400 transition-colors",
1959
+ title: "Fullscreen",
1960
+ "aria-label": "Toggle fullscreen",
1961
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1962
+ "svg",
1963
+ {
1964
+ className: "w-5 h-5",
1965
+ fill: "currentColor",
1966
+ viewBox: "0 0 20 20",
1967
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1968
+ "path",
1969
+ {
1970
+ fillRule: "evenodd",
1971
+ d: "M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z",
1972
+ clipRule: "evenodd"
1973
+ }
1974
+ )
1975
+ }
1976
+ )
1977
+ }
1978
+ ),
1979
+ hasTranscript && /* @__PURE__ */ jsxRuntime.jsx(
1980
+ "button",
1981
+ {
1982
+ onClick: () => setShowTranscript(!showTranscript),
1983
+ className: `transition-colors ${showTranscript ? "text-blue-400" : "text-white hover:text-blue-400"}`,
1984
+ title: showTranscript ? "Hide Transcript" : "Show Transcript",
1985
+ "aria-label": showTranscript ? "Hide transcript" : "Show transcript",
1986
+ "aria-pressed": showTranscript,
1987
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1988
+ "svg",
1989
+ {
1990
+ className: "w-5 h-5",
1991
+ fill: "none",
1992
+ stroke: "currentColor",
1993
+ viewBox: "0 0 24 24",
1994
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1995
+ "path",
1996
+ {
1997
+ strokeLinecap: "round",
1998
+ strokeLinejoin: "round",
1999
+ strokeWidth: 2,
2000
+ d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
2001
+ }
2002
+ )
2003
+ }
2004
+ )
2005
+ }
2006
+ )
2007
+ ] })
2008
+ ] })
2009
+ ] }) : null : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-zinc-900/50 backdrop-blur-sm px-4 md:px-6 py-3 md:py-4 rounded-b-lg", children: [
2010
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4", children: [
1624
2011
  /* @__PURE__ */ jsxRuntime.jsx(
1625
2012
  "input",
1626
2013
  {
1627
2014
  type: "range",
1628
2015
  min: 0,
1629
- max: 1,
1630
- step: 0.01,
1631
- value: muted ? 0 : volume || 1,
1632
- onChange: handleVolumeChange,
1633
- className: "w-20 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer slider",
1634
- "aria-label": "Volume",
2016
+ max: 0.999999,
2017
+ step: "any",
2018
+ value: played || 0,
2019
+ onMouseDown: handleSeekMouseDown,
2020
+ onMouseUp: handleSeekMouseUp,
2021
+ onTouchStart: handleSeekTouchStart,
2022
+ onTouchEnd: handleSeekTouchEnd,
2023
+ onChange: handleSeekChange,
2024
+ className: "w-full h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer slider",
2025
+ "aria-label": "Seek position",
1635
2026
  "aria-valuemin": 0,
1636
- "aria-valuemax": 100,
1637
- "aria-valuenow": muted ? 0 : Math.round(volume * 100),
1638
- "aria-valuetext": muted ? "Muted" : `${Math.round(volume * 100)}%`,
2027
+ "aria-valuemax": duration,
2028
+ "aria-valuenow": played * duration,
2029
+ "aria-valuetext": `${formatTime(
2030
+ played * duration
2031
+ )} of ${formatTime(duration)}`,
1639
2032
  role: "slider"
1640
2033
  }
1641
- )
2034
+ ),
2035
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-xs text-gray-400 mt-1", children: [
2036
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime((played || 0) * duration) }),
2037
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime(duration) })
2038
+ ] })
2039
+ ] }),
2040
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2041
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
2042
+ /* @__PURE__ */ jsxRuntime.jsx(
2043
+ "button",
2044
+ {
2045
+ onClick: handlePlayPause,
2046
+ className: "text-white hover:text-blue-400 transition-colors",
2047
+ title: playing ? "Pause" : "Play",
2048
+ "aria-label": playing ? "Pause" : "Play",
2049
+ "aria-pressed": playing,
2050
+ children: playing ? /* @__PURE__ */ jsxRuntime.jsx(
2051
+ "svg",
2052
+ {
2053
+ className: "w-8 h-8",
2054
+ fill: "currentColor",
2055
+ viewBox: "0 0 20 20",
2056
+ "aria-hidden": "true",
2057
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2058
+ "path",
2059
+ {
2060
+ fillRule: "evenodd",
2061
+ d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",
2062
+ clipRule: "evenodd"
2063
+ }
2064
+ )
2065
+ }
2066
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2067
+ "svg",
2068
+ {
2069
+ className: "w-8 h-8",
2070
+ fill: "currentColor",
2071
+ viewBox: "0 0 20 20",
2072
+ "aria-hidden": "true",
2073
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2074
+ "path",
2075
+ {
2076
+ fillRule: "evenodd",
2077
+ d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",
2078
+ clipRule: "evenodd"
2079
+ }
2080
+ )
2081
+ }
2082
+ )
2083
+ }
2084
+ ),
2085
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
2086
+ /* @__PURE__ */ jsxRuntime.jsx(
2087
+ "button",
2088
+ {
2089
+ onClick: toggleMute,
2090
+ className: "text-white hover:text-blue-400 transition-colors",
2091
+ title: muted ? "Unmute" : "Mute",
2092
+ "aria-label": muted ? "Unmute" : "Mute",
2093
+ "aria-pressed": muted,
2094
+ children: muted || volume === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
2095
+ "svg",
2096
+ {
2097
+ className: "w-5 h-5",
2098
+ fill: "currentColor",
2099
+ viewBox: "0 0 20 20",
2100
+ "aria-hidden": "true",
2101
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2102
+ "path",
2103
+ {
2104
+ fillRule: "evenodd",
2105
+ d: "M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM12.293 7.293a1 1 0 011.414 0L15 8.586l1.293-1.293a1 1 0 111.414 1.414L16.414 10l1.293 1.293a1 1 0 01-1.414 1.414L15 11.414l-1.293 1.293a1 1 0 01-1.414-1.414L13.586 10l-1.293-1.293a1 1 0 010-1.414z",
2106
+ clipRule: "evenodd"
2107
+ }
2108
+ )
2109
+ }
2110
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
2111
+ "svg",
2112
+ {
2113
+ className: "w-5 h-5",
2114
+ fill: "currentColor",
2115
+ viewBox: "0 0 20 20",
2116
+ "aria-hidden": "true",
2117
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2118
+ "path",
2119
+ {
2120
+ fillRule: "evenodd",
2121
+ d: "M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z",
2122
+ clipRule: "evenodd"
2123
+ }
2124
+ )
2125
+ }
2126
+ )
2127
+ }
2128
+ ),
2129
+ /* @__PURE__ */ jsxRuntime.jsx(
2130
+ "input",
2131
+ {
2132
+ type: "range",
2133
+ min: 0,
2134
+ max: 1,
2135
+ step: 0.01,
2136
+ value: muted ? 0 : volume || 1,
2137
+ onChange: handleVolumeChange,
2138
+ className: "w-20 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer slider",
2139
+ "aria-label": "Volume",
2140
+ "aria-valuemin": 0,
2141
+ "aria-valuemax": 100,
2142
+ "aria-valuenow": muted ? 0 : Math.round(volume * 100),
2143
+ "aria-valuetext": muted ? "Muted" : `${Math.round(volume * 100)}%`,
2144
+ role: "slider"
2145
+ }
2146
+ )
2147
+ ] })
2148
+ ] }),
2149
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
2150
+ !isAudioOnly && /* @__PURE__ */ jsxRuntime.jsx(
2151
+ "button",
2152
+ {
2153
+ onClick: toggleFullscreen,
2154
+ className: "text-white hover:text-blue-400 transition-colors",
2155
+ title: "Toggle fullscreen",
2156
+ "aria-label": "Toggle fullscreen",
2157
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2158
+ "svg",
2159
+ {
2160
+ className: "w-5 h-5",
2161
+ fill: "currentColor",
2162
+ viewBox: "0 0 20 20",
2163
+ "aria-hidden": "true",
2164
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2165
+ "path",
2166
+ {
2167
+ fillRule: "evenodd",
2168
+ d: "M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z",
2169
+ clipRule: "evenodd"
2170
+ }
2171
+ )
2172
+ }
2173
+ )
2174
+ }
2175
+ ),
2176
+ hasTranscript && /* @__PURE__ */ jsxRuntime.jsx(
2177
+ "button",
2178
+ {
2179
+ onClick: () => setShowTranscript(!showTranscript),
2180
+ className: `transition-colors ${showTranscript ? "text-blue-400" : "text-white hover:text-blue-400"}`,
2181
+ title: showTranscript ? "Hide transcript" : "Show transcript",
2182
+ "aria-label": showTranscript ? "Hide transcript" : "Show transcript",
2183
+ "aria-pressed": showTranscript,
2184
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2185
+ "svg",
2186
+ {
2187
+ className: "w-5 h-5",
2188
+ fill: "none",
2189
+ stroke: "currentColor",
2190
+ viewBox: "0 0 24 24",
2191
+ "aria-hidden": "true",
2192
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2193
+ "path",
2194
+ {
2195
+ strokeLinecap: "round",
2196
+ strokeLinejoin: "round",
2197
+ strokeWidth: 2,
2198
+ d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
2199
+ }
2200
+ )
2201
+ }
2202
+ )
2203
+ }
2204
+ )
2205
+ ] })
1642
2206
  ] })
1643
- ] }),
1644
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
1645
- !isLiveStream && broadcast.hash && (broadcast.recordingMp4Url || broadcast.recordingMp3Url) && /* @__PURE__ */ jsxRuntime.jsx(
1646
- "button",
1647
- {
1648
- onClick: () => {
1649
- const downloadUrl = buildPlaybackUrl(broadcast.id, broadcast.hash, "download");
1650
- window.open(downloadUrl, "_blank");
1651
- },
1652
- className: "text-white hover:text-blue-400 transition-colors",
1653
- title: "Download Recording",
1654
- "aria-label": "Download recording",
1655
- children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" }) })
1656
- }
1657
- ),
1658
- !isAudioOnly && /* @__PURE__ */ jsxRuntime.jsx(
1659
- "button",
1660
- {
1661
- onClick: toggleFullscreen,
1662
- className: "text-white hover:text-blue-400 transition-colors",
1663
- title: "Fullscreen",
1664
- "aria-label": "Toggle fullscreen",
1665
- children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z", clipRule: "evenodd" }) })
1666
- }
1667
- ),
1668
- hasTranscript && /* @__PURE__ */ jsxRuntime.jsx(
1669
- "button",
1670
- {
1671
- onClick: () => setShowTranscript(!showTranscript),
1672
- className: `transition-colors ${showTranscript ? "text-blue-400" : "text-white hover:text-blue-400"}`,
1673
- title: showTranscript ? "Hide Transcript" : "Show Transcript",
1674
- "aria-label": showTranscript ? "Hide transcript" : "Show transcript",
1675
- "aria-pressed": showTranscript,
1676
- children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) })
1677
- }
1678
- )
1679
- ] })
1680
- ] })
1681
- ] }) : null : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-zinc-900/50 backdrop-blur-sm px-4 md:px-6 py-3 md:py-4 rounded-b-lg", children: [
1682
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4", children: [
1683
- /* @__PURE__ */ jsxRuntime.jsx(
1684
- "input",
1685
- {
1686
- type: "range",
1687
- min: 0,
1688
- max: 0.999999,
1689
- step: "any",
1690
- value: played || 0,
1691
- onMouseDown: handleSeekMouseDown,
1692
- onMouseUp: handleSeekMouseUp,
1693
- onTouchStart: handleSeekTouchStart,
1694
- onTouchEnd: handleSeekTouchEnd,
1695
- onChange: handleSeekChange,
1696
- className: "w-full h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer slider"
1697
- }
1698
- ),
1699
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-xs text-gray-400 mt-1", children: [
1700
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime((played || 0) * duration) }),
1701
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatTime(duration) })
1702
- ] })
2207
+ ] }))
1703
2208
  ] }),
1704
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
1705
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
1706
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handlePlayPause, className: "text-white hover:text-blue-400 transition-colors", title: playing ? "Pause" : "Play", children: playing ? /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-8 h-8", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z", clipRule: "evenodd" }) }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-8 h-8", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z", clipRule: "evenodd" }) }) }),
2209
+ showTranscript && hasTranscript && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 md:flex-none min-h-0 w-full md:w-96 bg-zinc-900 border-t md:border-t-0 border-l border-zinc-800 flex flex-col overflow-hidden", children: [
2210
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 border-b border-zinc-800 bg-zinc-900/50 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
1707
2211
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1708
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: toggleMute, className: "text-white hover:text-blue-400 transition-colors", title: muted ? "Unmute" : "Mute", children: muted || volume === 0 ? /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM12.293 7.293a1 1 0 011.414 0L15 8.586l1.293-1.293a1 1 0 111.414 1.414L16.414 10l1.293 1.293a1 1 0 01-1.414 1.414L15 11.414l-1.293 1.293a1 1 0 01-1.414-1.414L13.586 10l-1.293-1.293a1 1 0 010-1.414z", clipRule: "evenodd" }) }) : /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z", clipRule: "evenodd" }) }) }),
1709
2212
  /* @__PURE__ */ jsxRuntime.jsx(
1710
- "input",
2213
+ "svg",
1711
2214
  {
1712
- type: "range",
1713
- min: 0,
1714
- max: 1,
1715
- step: 0.01,
1716
- value: muted ? 0 : volume || 1,
1717
- onChange: handleVolumeChange,
1718
- className: "w-20 h-1 bg-zinc-700 rounded-lg appearance-none cursor-pointer slider"
2215
+ className: "w-5 h-5 text-green-400",
2216
+ fill: "none",
2217
+ stroke: "currentColor",
2218
+ viewBox: "0 0 24 24",
2219
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2220
+ "path",
2221
+ {
2222
+ strokeLinecap: "round",
2223
+ strokeLinejoin: "round",
2224
+ strokeWidth: 2,
2225
+ d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
2226
+ }
2227
+ )
1719
2228
  }
1720
- )
1721
- ] })
1722
- ] }),
1723
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
1724
- !isAudioOnly && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: toggleFullscreen, className: "text-white hover:text-blue-400 transition-colors", title: "Fullscreen", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z", clipRule: "evenodd" }) }) }),
1725
- hasTranscript && /* @__PURE__ */ jsxRuntime.jsx(
1726
- "button",
2229
+ ),
2230
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-white", children: "Transcript" }),
2231
+ broadcast.transcriptLanguage && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 px-2 py-0.5 bg-zinc-800 rounded", children: broadcast.transcriptLanguage.toUpperCase() })
2232
+ ] }),
2233
+ broadcast.transcriptUrl && /* @__PURE__ */ jsxRuntime.jsx(
2234
+ "a",
1727
2235
  {
1728
- onClick: () => setShowTranscript(!showTranscript),
1729
- className: `transition-colors ${showTranscript ? "text-blue-400" : "text-white hover:text-blue-400"}`,
1730
- title: showTranscript ? "Hide Transcript" : "Show Transcript",
1731
- children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) })
2236
+ href: broadcast.transcriptUrl,
2237
+ download: `${broadcast.hash || broadcast.id}-transcript.json`,
2238
+ className: "text-gray-400 hover:text-white transition-colors",
2239
+ title: "Download transcript",
2240
+ "aria-label": "Download transcript as JSON file",
2241
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2242
+ "svg",
2243
+ {
2244
+ className: "w-5 h-5",
2245
+ fill: "none",
2246
+ stroke: "currentColor",
2247
+ viewBox: "0 0 24 24",
2248
+ "aria-hidden": "true",
2249
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2250
+ "path",
2251
+ {
2252
+ strokeLinecap: "round",
2253
+ strokeLinejoin: "round",
2254
+ strokeWidth: 2,
2255
+ d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
2256
+ }
2257
+ )
2258
+ }
2259
+ )
1732
2260
  }
1733
2261
  )
1734
- ] })
1735
- ] })
1736
- ] }))
1737
- ] }),
1738
- showTranscript && hasTranscript && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 md:flex-none min-h-0 w-full md:w-96 bg-zinc-900 border-t md:border-t-0 border-l border-zinc-800 flex flex-col overflow-hidden", children: [
1739
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 border-b border-zinc-800 bg-zinc-900/50 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
1740
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1741
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5 text-green-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) }),
1742
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-white", children: "Transcript" }),
1743
- broadcast.transcriptLanguage && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 px-2 py-0.5 bg-zinc-800 rounded", children: broadcast.transcriptLanguage.toUpperCase() })
1744
- ] }),
1745
- broadcast.transcriptUrl && /* @__PURE__ */ jsxRuntime.jsx(
1746
- "a",
1747
- {
1748
- href: broadcast.transcriptUrl,
1749
- download: `${broadcast.hash || broadcast.id}-transcript.json`,
1750
- className: "text-gray-400 hover:text-white transition-colors",
1751
- title: "Download Transcript",
1752
- children: /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" }) })
1753
- }
1754
- )
1755
- ] }) }),
1756
- !autoScrollEnabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-2 bg-zinc-800/50 border-b border-zinc-700 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
1757
- "button",
1758
- {
1759
- onClick: () => setAutoScrollEnabled(true),
1760
- className: "w-full px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors flex items-center justify-center gap-2 text-sm font-medium",
1761
- children: [
1762
- /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 14l-7 7m0 0l-7-7m7 7V3" }) }),
1763
- "Resume Auto-Scroll"
1764
- ]
1765
- }
1766
- ) }),
1767
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: transcriptContainerRef, className: "flex-1 min-h-0 overflow-y-auto px-4 py-4 text-gray-300 leading-relaxed", children: isLoadingTranscript ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-6 w-6 border-2 border-gray-600 border-t-blue-500 rounded-full animate-spin" }) }) : transcriptData?.segments && transcriptData.segments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: (() => {
1768
- const filteredSegments = transcriptData.segments.filter((s) => s.words && s.words.length > 0);
1769
- let globalWordIndex = 0;
1770
- const wordMap = /* @__PURE__ */ new Map();
1771
- filteredSegments.forEach((segment) => {
1772
- segment.words.forEach((_word, wordIndex) => {
1773
- wordMap.set(`${segment.id}-${wordIndex}`, globalWordIndex++);
1774
- });
1775
- });
1776
- let currentWordIndex = -1;
1777
- filteredSegments.forEach((segment) => {
1778
- segment.words.forEach((word, wordIndex) => {
1779
- const globalIdx = wordMap.get(`${segment.id}-${wordIndex}`) || -1;
1780
- if (currentTime >= word.start && globalIdx > currentWordIndex) {
1781
- currentWordIndex = globalIdx;
2262
+ ] }) }),
2263
+ !autoScrollEnabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-2 bg-zinc-800/50 border-b border-zinc-700 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
2264
+ "button",
2265
+ {
2266
+ onClick: () => setAutoScrollEnabled(true),
2267
+ className: "w-full px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors flex items-center justify-center gap-2 text-sm font-medium",
2268
+ "aria-label": "Resume automatic scrolling of transcript",
2269
+ children: [
2270
+ /* @__PURE__ */ jsxRuntime.jsx(
2271
+ "svg",
2272
+ {
2273
+ className: "w-4 h-4",
2274
+ fill: "none",
2275
+ stroke: "currentColor",
2276
+ viewBox: "0 0 24 24",
2277
+ "aria-hidden": "true",
2278
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2279
+ "path",
2280
+ {
2281
+ strokeLinecap: "round",
2282
+ strokeLinejoin: "round",
2283
+ strokeWidth: 2,
2284
+ d: "M19 14l-7 7m0 0l-7-7m7 7V3"
2285
+ }
2286
+ )
2287
+ }
2288
+ ),
2289
+ "Resume Auto-Scroll"
2290
+ ]
1782
2291
  }
1783
- });
1784
- });
1785
- const previousWordIndex = lastActiveWordIndex.current;
1786
- let minHighlightIndex = -1;
1787
- let maxHighlightIndex = -1;
1788
- if (currentWordIndex >= 0) {
1789
- minHighlightIndex = Math.max(0, currentWordIndex - TRAILING_WORDS);
1790
- maxHighlightIndex = currentWordIndex;
1791
- if (currentWordIndex <= TRAILING_WORDS) {
1792
- minHighlightIndex = 0;
1793
- }
1794
- lastActiveWordIndex.current = currentWordIndex;
1795
- } else if (currentWordIndex === -1) {
1796
- minHighlightIndex = 0;
1797
- maxHighlightIndex = 0;
1798
- } else if (previousWordIndex >= 0) {
1799
- minHighlightIndex = Math.max(0, previousWordIndex - TRAILING_WORDS);
1800
- maxHighlightIndex = previousWordIndex;
1801
- }
1802
- return filteredSegments.map((segment, _segmentIndex) => {
1803
- const isSegmentActive = currentTime >= segment.start && currentTime < segment.end;
1804
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: isSegmentActive ? transcriptContainerRef : null, className: "flex gap-3 items-start leading-relaxed", children: [
1805
- /* @__PURE__ */ jsxRuntime.jsx(
1806
- "button",
1807
- {
1808
- onClick: () => handleWordClick(segment.start),
1809
- className: "text-xs text-gray-500 hover:text-gray-300 transition-colors shrink-0 pt-0.5 font-mono",
1810
- title: `Jump to ${formatTimestamp(segment.start)}`,
1811
- children: formatTimestamp(segment.start)
1812
- }
1813
- ),
1814
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: segment.words.map((word, wordIndex) => {
1815
- const thisGlobalIndex = wordMap.get(`${segment.id}-${wordIndex}`) ?? -1;
1816
- const isTimestampActive = currentTime >= word.start && currentTime < word.end;
1817
- const isInGapFill = minHighlightIndex >= 0 && thisGlobalIndex >= minHighlightIndex && thisGlobalIndex <= maxHighlightIndex;
1818
- const isWordActive = isInGapFill;
1819
- return /* @__PURE__ */ jsxRuntime.jsxs(
1820
- "span",
1821
- {
1822
- ref: isTimestampActive ? activeWordRef : null,
1823
- onClick: () => handleWordClick(word.start),
1824
- className: `cursor-pointer ${isWordActive ? "text-blue-400 font-medium active-word" : isSegmentActive ? "text-gray-200 segment-word" : "text-gray-400 hover:text-gray-200 inactive-word"}`,
1825
- title: `${formatTime(word.start)} - ${formatTime(word.end)}`,
1826
- children: [
1827
- word.word,
1828
- " "
1829
- ]
1830
- },
1831
- `${segment.id}-${wordIndex}`
1832
- );
1833
- }) })
1834
- ] }, segment.id);
1835
- });
1836
- })() }) : transcriptData?.words && transcriptData.words.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1", children: transcriptData.words.map((word, index) => {
1837
- const isActive = currentTime >= word.start && currentTime < word.end;
1838
- return /* @__PURE__ */ jsxRuntime.jsxs(
1839
- "span",
1840
- {
1841
- ref: isActive ? activeWordRef : null,
1842
- onClick: () => handleWordClick(word.start),
1843
- className: `inline-block cursor-pointer transition-all ${isActive ? "text-blue-400 underline decoration-blue-400 decoration-2 font-medium" : "text-gray-400 hover:text-gray-200"}`,
1844
- title: `${formatTime(word.start)} - ${formatTime(word.end)}`,
1845
- children: [
1846
- word.word,
1847
- " "
1848
- ]
1849
- },
1850
- index
1851
- );
1852
- }) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center text-gray-500 py-8", children: [
1853
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2", children: "Transcript data not available" }),
1854
- transcriptData && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-600", children: [
1855
- "Debug: segments=",
1856
- transcriptData.segments ? transcriptData.segments.length : 0,
1857
- ", words=",
1858
- transcriptData.words ? transcriptData.words.length : 0
2292
+ ) }),
2293
+ /* @__PURE__ */ jsxRuntime.jsx(
2294
+ "div",
2295
+ {
2296
+ ref: transcriptContainerRef,
2297
+ className: "flex-1 min-h-0 overflow-y-auto px-4 py-4 text-gray-300 leading-relaxed",
2298
+ children: isLoadingTranscript ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-6 w-6 border-2 border-gray-600 border-t-blue-500 rounded-full animate-spin" }) }) : transcriptData?.segments && transcriptData.segments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: (() => {
2299
+ const filteredSegments = transcriptData.segments.filter(
2300
+ (s) => s.words && s.words.length > 0
2301
+ );
2302
+ let globalWordIndex = 0;
2303
+ const wordMap = /* @__PURE__ */ new Map();
2304
+ filteredSegments.forEach((segment) => {
2305
+ segment.words.forEach((_word, wordIndex) => {
2306
+ wordMap.set(
2307
+ `${segment.id}-${wordIndex}`,
2308
+ globalWordIndex++
2309
+ );
2310
+ });
2311
+ });
2312
+ let currentWordIndex = -1;
2313
+ filteredSegments.forEach((segment) => {
2314
+ segment.words.forEach((word, wordIndex) => {
2315
+ const globalIdx = wordMap.get(`${segment.id}-${wordIndex}`) || -1;
2316
+ if (currentTime >= word.start && globalIdx > currentWordIndex) {
2317
+ currentWordIndex = globalIdx;
2318
+ }
2319
+ });
2320
+ });
2321
+ const previousWordIndex = lastActiveWordIndex.current;
2322
+ let minHighlightIndex = -1;
2323
+ let maxHighlightIndex = -1;
2324
+ if (currentWordIndex >= 0) {
2325
+ minHighlightIndex = Math.max(
2326
+ 0,
2327
+ currentWordIndex - TRAILING_WORDS
2328
+ );
2329
+ maxHighlightIndex = currentWordIndex;
2330
+ if (currentWordIndex <= TRAILING_WORDS) {
2331
+ minHighlightIndex = 0;
2332
+ }
2333
+ lastActiveWordIndex.current = currentWordIndex;
2334
+ } else if (currentWordIndex === -1) {
2335
+ minHighlightIndex = 0;
2336
+ maxHighlightIndex = 0;
2337
+ } else if (previousWordIndex >= 0) {
2338
+ minHighlightIndex = Math.max(
2339
+ 0,
2340
+ previousWordIndex - TRAILING_WORDS
2341
+ );
2342
+ maxHighlightIndex = previousWordIndex;
2343
+ }
2344
+ return filteredSegments.map((segment, _segmentIndex) => {
2345
+ const isSegmentActive = currentTime >= segment.start && currentTime < segment.end;
2346
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2347
+ "div",
2348
+ {
2349
+ ref: isSegmentActive ? transcriptContainerRef : null,
2350
+ className: "flex gap-3 items-start leading-relaxed",
2351
+ children: [
2352
+ /* @__PURE__ */ jsxRuntime.jsx(
2353
+ "button",
2354
+ {
2355
+ onClick: () => handleWordClick(segment.start),
2356
+ className: "text-xs text-gray-500 hover:text-gray-300 transition-colors shrink-0 pt-0.5 font-mono",
2357
+ title: `Jump to ${formatTimestamp(segment.start)}`,
2358
+ children: formatTimestamp(segment.start)
2359
+ }
2360
+ ),
2361
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: segment.words.map((word, wordIndex) => {
2362
+ const thisGlobalIndex = wordMap.get(`${segment.id}-${wordIndex}`) ?? -1;
2363
+ const isTimestampActive = currentTime >= word.start && currentTime < word.end;
2364
+ const isInGapFill = minHighlightIndex >= 0 && thisGlobalIndex >= minHighlightIndex && thisGlobalIndex <= maxHighlightIndex;
2365
+ const isWordActive = isInGapFill;
2366
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2367
+ "span",
2368
+ {
2369
+ ref: isTimestampActive ? activeWordRef : null,
2370
+ onClick: () => handleWordClick(word.start),
2371
+ className: `cursor-pointer ${isWordActive ? "text-blue-400 font-medium active-word" : isSegmentActive ? "text-gray-200 segment-word" : "text-gray-400 hover:text-gray-200 inactive-word"}`,
2372
+ title: `${formatTime(
2373
+ word.start
2374
+ )} - ${formatTime(word.end)}`,
2375
+ children: [
2376
+ word.word,
2377
+ " "
2378
+ ]
2379
+ },
2380
+ `${segment.id}-${wordIndex}`
2381
+ );
2382
+ }) })
2383
+ ]
2384
+ },
2385
+ segment.id
2386
+ );
2387
+ });
2388
+ })() }) : transcriptData?.words && transcriptData.words.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1", children: transcriptData.words.map((word, index) => {
2389
+ const isActive = currentTime >= word.start && currentTime < word.end;
2390
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2391
+ "span",
2392
+ {
2393
+ ref: isActive ? activeWordRef : null,
2394
+ onClick: () => handleWordClick(word.start),
2395
+ className: `inline-block cursor-pointer transition-all ${isActive ? "text-blue-400 underline decoration-blue-400 decoration-2 font-medium" : "text-gray-400 hover:text-gray-200"}`,
2396
+ title: `${formatTime(word.start)} - ${formatTime(
2397
+ word.end
2398
+ )}`,
2399
+ children: [
2400
+ word.word,
2401
+ " "
2402
+ ]
2403
+ },
2404
+ index
2405
+ );
2406
+ }) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center text-gray-500 py-8", children: [
2407
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2", children: "Transcript data not available" }),
2408
+ transcriptData && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-600", children: [
2409
+ "Debug: segments=",
2410
+ transcriptData.segments ? transcriptData.segments.length : 0,
2411
+ ", words=",
2412
+ transcriptData.words ? transcriptData.words.length : 0
2413
+ ] })
2414
+ ] })
2415
+ }
2416
+ )
1859
2417
  ] })
1860
- ] }) })
1861
- ] })
1862
- ] }),
1863
- renderClipCreator && renderClipCreator({
1864
- isOpen: showClipCreator,
1865
- onClose: () => setShowClipCreator(false),
1866
- sourceVideoUrl: playbackType === "mp4" ? playbackUrl || void 0 : void 0,
1867
- sourceAudioUrl: playbackType === "mp3" ? playbackUrl || void 0 : void 0,
1868
- sourceDuration: duration,
1869
- onPauseParent: () => setPlaying(false)
1870
- }),
1871
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
2418
+ ] }),
2419
+ renderClipCreator && renderClipCreator({
2420
+ isOpen: showClipCreator,
2421
+ onClose: () => setShowClipCreator(false),
2422
+ sourceVideoUrl: playbackType === "mp4" ? playbackUrl || void 0 : void 0,
2423
+ sourceAudioUrl: playbackType === "mp3" ? playbackUrl || void 0 : void 0,
2424
+ sourceDuration: duration,
2425
+ onPauseParent: () => setPlaying(false)
2426
+ }),
2427
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
1872
2428
  .slider::-webkit-slider-thumb {
1873
2429
  appearance: none;
1874
2430
  width: 14px;
@@ -1908,91 +2464,12 @@ function BroadcastPlayer({
1908
2464
  transition: color 0.15s ease-in;
1909
2465
  }
1910
2466
  ` })
1911
- ] });
1912
- }
1913
- function BroadcastPlayerModal({
1914
- broadcast,
1915
- isOpen,
1916
- onClose,
1917
- appId,
1918
- contentId,
1919
- foreignId,
1920
- foreignTier,
1921
- renderClipCreator,
1922
- className,
1923
- enableKeyboardShortcuts = false
1924
- }) {
1925
- const closeButtonRef = react.useRef(null);
1926
- const previousActiveElement = react.useRef(null);
1927
- react.useEffect(() => {
1928
- if (!isOpen) return;
1929
- previousActiveElement.current = document.activeElement;
1930
- setTimeout(() => {
1931
- closeButtonRef.current?.focus();
1932
- }, 100);
1933
- return () => {
1934
- if (previousActiveElement.current) {
1935
- previousActiveElement.current.focus();
1936
- }
1937
- };
1938
- }, [isOpen]);
1939
- react.useEffect(() => {
1940
- if (!isOpen) return;
1941
- const handleKeyDown = (e) => {
1942
- if (e.key === "Escape") {
1943
- onClose();
1944
- }
1945
- };
1946
- document.addEventListener("keydown", handleKeyDown);
1947
- return () => document.removeEventListener("keydown", handleKeyDown);
1948
- }, [isOpen, onClose]);
1949
- if (!isOpen) return null;
1950
- const handleBackdropClick = (e) => {
1951
- if (e.target === e.currentTarget) {
1952
- onClose();
1953
- }
1954
- };
1955
- return /* @__PURE__ */ jsxRuntime.jsx(
1956
- "div",
1957
- {
1958
- className: "fixed inset-0 bg-black/70 backdrop-blur-xl flex items-center justify-center z-50 p-2 sm:p-4",
1959
- onClick: handleBackdropClick,
1960
- role: "dialog",
1961
- "aria-modal": "true",
1962
- "aria-label": "Broadcast player",
1963
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full max-w-7xl max-h-[95vh] sm:max-h-[90vh] overflow-hidden", children: [
1964
- /* @__PURE__ */ jsxRuntime.jsx(
1965
- "button",
1966
- {
1967
- ref: closeButtonRef,
1968
- onClick: onClose,
1969
- className: "absolute top-2 right-2 sm:top-4 sm:right-4 z-10 text-gray-400 hover:text-white text-2xl leading-none transition-colors w-8 h-8 flex items-center justify-center bg-black/50 rounded-full",
1970
- title: "Close (ESC)",
1971
- "aria-label": "Close player",
1972
- children: "\xD7"
1973
- }
1974
- ),
1975
- /* @__PURE__ */ jsxRuntime.jsx(
1976
- BroadcastPlayer,
1977
- {
1978
- broadcast,
1979
- appId,
1980
- contentId,
1981
- foreignId,
1982
- foreignTier,
1983
- renderClipCreator,
1984
- className,
1985
- enableKeyboardShortcuts,
1986
- onError: (error) => {
1987
- debug.error("[BroadcastPlayerModal] Player error:", error);
1988
- }
1989
- }
1990
- )
1991
- ] })
2467
+ ]
1992
2468
  }
1993
2469
  );
2470
+ return playerContent;
1994
2471
  }
1995
- var BroadcastPlayerErrorBoundary = class extends react.Component {
2472
+ var DialtribePlayerErrorBoundary = class extends react.Component {
1996
2473
  constructor(props) {
1997
2474
  super(props);
1998
2475
  this.handleReset = () => {
@@ -2118,21 +2595,99 @@ var BroadcastPlayerErrorBoundary = class extends react.Component {
2118
2595
  return this.props.children;
2119
2596
  }
2120
2597
  };
2598
+ var overlayStyles = {
2599
+ modal: {
2600
+ backdrop: "bg-black/70 backdrop-blur-xl p-2 sm:p-4",
2601
+ container: "max-w-7xl max-h-[95vh] sm:max-h-[90vh]"
2602
+ },
2603
+ fullscreen: {
2604
+ backdrop: "bg-black",
2605
+ container: "h-full"
2606
+ }
2607
+ };
2608
+ function DialtribeOverlay({
2609
+ isOpen,
2610
+ onClose,
2611
+ mode = "modal",
2612
+ children,
2613
+ ariaLabel = "Dialog",
2614
+ showCloseButton = true,
2615
+ closeOnBackdropClick = true,
2616
+ closeOnEsc = true
2617
+ }) {
2618
+ const closeButtonRef = react.useRef(null);
2619
+ const previousActiveElement = react.useRef(null);
2620
+ react.useEffect(() => {
2621
+ if (!isOpen) return;
2622
+ previousActiveElement.current = document.activeElement;
2623
+ setTimeout(() => {
2624
+ closeButtonRef.current?.focus();
2625
+ }, 100);
2626
+ return () => {
2627
+ if (previousActiveElement.current) {
2628
+ previousActiveElement.current.focus();
2629
+ }
2630
+ };
2631
+ }, [isOpen]);
2632
+ react.useEffect(() => {
2633
+ if (!isOpen || !closeOnEsc) return;
2634
+ const handleKeyDown = (e) => {
2635
+ if (e.key === "Escape") {
2636
+ onClose();
2637
+ }
2638
+ };
2639
+ document.addEventListener("keydown", handleKeyDown);
2640
+ return () => document.removeEventListener("keydown", handleKeyDown);
2641
+ }, [isOpen, onClose, closeOnEsc]);
2642
+ if (!isOpen) {
2643
+ return null;
2644
+ }
2645
+ const handleBackdropClick = (e) => {
2646
+ if (closeOnBackdropClick && e.target === e.currentTarget) {
2647
+ onClose();
2648
+ }
2649
+ };
2650
+ const styles = overlayStyles[mode];
2651
+ return /* @__PURE__ */ jsxRuntime.jsx(
2652
+ "div",
2653
+ {
2654
+ className: `fixed inset-0 flex items-center justify-center z-50 ${styles.backdrop}`,
2655
+ onClick: handleBackdropClick,
2656
+ role: "dialog",
2657
+ "aria-modal": "true",
2658
+ "aria-label": ariaLabel,
2659
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative w-full overflow-hidden ${styles.container}`, children: [
2660
+ showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(
2661
+ "button",
2662
+ {
2663
+ ref: closeButtonRef,
2664
+ onClick: onClose,
2665
+ className: "absolute top-2 right-2 sm:top-4 sm:right-4 z-10 text-gray-400 hover:text-white text-2xl leading-none transition-colors w-8 h-8 flex items-center justify-center bg-black/50 rounded-full",
2666
+ title: "Close (ESC)",
2667
+ "aria-label": "Close",
2668
+ children: "\xD7"
2669
+ }
2670
+ ),
2671
+ children
2672
+ ] })
2673
+ }
2674
+ );
2675
+ }
2121
2676
 
2122
2677
  exports.AudioWaveform = AudioWaveform;
2123
- exports.BroadcastPlayer = BroadcastPlayer;
2124
- exports.BroadcastPlayerErrorBoundary = BroadcastPlayerErrorBoundary;
2125
- exports.BroadcastPlayerModal = BroadcastPlayerModal;
2126
2678
  exports.CDN_DOMAIN = CDN_DOMAIN;
2127
2679
  exports.DIALTRIBE_API_BASE = DIALTRIBE_API_BASE;
2128
- exports.DialTribeClient = DialTribeClient;
2129
- exports.DialTribeProvider = DialTribeProvider;
2680
+ exports.DialtribeClient = DialtribeClient;
2681
+ exports.DialtribeOverlay = DialtribeOverlay;
2682
+ exports.DialtribePlayer = DialtribePlayer;
2683
+ exports.DialtribePlayerErrorBoundary = DialtribePlayerErrorBoundary;
2684
+ exports.DialtribeProvider = DialtribeProvider;
2130
2685
  exports.ENDPOINTS = ENDPOINTS;
2131
2686
  exports.HTTP_STATUS = HTTP_STATUS;
2132
2687
  exports.LoadingSpinner = LoadingSpinner;
2133
2688
  exports.buildBroadcastCdnUrl = buildBroadcastCdnUrl;
2134
2689
  exports.buildBroadcastS3KeyPrefix = buildBroadcastS3KeyPrefix;
2135
2690
  exports.formatTime = formatTime;
2136
- exports.useDialTribe = useDialTribe;
2137
- //# sourceMappingURL=broadcast-player.js.map
2138
- //# sourceMappingURL=broadcast-player.js.map
2691
+ exports.useDialtribe = useDialtribe;
2692
+ //# sourceMappingURL=dialtribe-player.js.map
2693
+ //# sourceMappingURL=dialtribe-player.js.map