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

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