@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.
- package/LICENSE +20 -20
- package/README.md +535 -535
- package/dist/{broadcast-player.d.mts → dialtribe-player-CNriUtNi.d.mts} +97 -85
- package/dist/{broadcast-player.d.ts → dialtribe-player-CNriUtNi.d.ts} +97 -85
- package/dist/dialtribe-player.d.mts +3 -0
- package/dist/dialtribe-player.d.ts +3 -0
- package/dist/{broadcast-player.js → dialtribe-player.js} +1103 -548
- package/dist/dialtribe-player.js.map +1 -0
- package/dist/{broadcast-player.mjs → dialtribe-player.mjs} +1099 -544
- package/dist/dialtribe-player.mjs.map +1 -0
- package/dist/dialtribe-streamer-C16mRqha.d.ts +567 -0
- package/dist/dialtribe-streamer-FjMRCQfA.d.mts +567 -0
- package/dist/dialtribe-streamer.d.mts +4 -0
- package/dist/dialtribe-streamer.d.ts +4 -0
- package/dist/dialtribe-streamer.js +4836 -0
- package/dist/dialtribe-streamer.js.map +1 -0
- package/dist/dialtribe-streamer.mjs +4799 -0
- package/dist/dialtribe-streamer.mjs.map +1 -0
- package/dist/hello-world.js.map +1 -1
- package/dist/hello-world.mjs.map +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3306 -609
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3222 -541
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +3 -3
- package/package.json +91 -86
- package/dist/broadcast-player.js.map +0 -1
- package/dist/broadcast-player.mjs.map +0 -1
|
@@ -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/
|
|
12
|
-
var
|
|
13
|
-
function
|
|
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(
|
|
49
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DialtribeContext.Provider, { value, children });
|
|
50
50
|
}
|
|
51
|
-
function
|
|
52
|
-
const context = react.useContext(
|
|
51
|
+
function useDialtribe() {
|
|
52
|
+
const context = react.useContext(DialtribeContext);
|
|
53
53
|
if (!context) {
|
|
54
54
|
throw new Error(
|
|
55
|
-
'
|
|
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/
|
|
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
|
-
|
|
76
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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 } =
|
|
763
|
+
const { sessionToken, setSessionToken, markExpired, apiBaseUrl } = useDialtribe();
|
|
763
764
|
const clientRef = react.useRef(null);
|
|
764
765
|
if (!clientRef.current && sessionToken) {
|
|
765
|
-
clientRef.current = new
|
|
766
|
+
clientRef.current = new DialtribeClient({
|
|
766
767
|
sessionToken,
|
|
767
768
|
apiBaseUrl,
|
|
768
769
|
onTokenRefresh: (newToken, expiresAt) => {
|
|
769
|
-
debug.log(`[
|
|
770
|
+
debug.log(`[DialtribeClient] Token refreshed, expires at ${expiresAt}`);
|
|
770
771
|
setSessionToken(newToken, expiresAt);
|
|
771
772
|
},
|
|
772
773
|
onTokenExpired: () => {
|
|
773
|
-
debug.error("[
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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)
|
|
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(
|
|
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
|
-
}, [
|
|
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(
|
|
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 {
|
|
967
|
+
return {
|
|
968
|
+
url: buildPlaybackUrl(broadcast.id, broadcast.hash),
|
|
969
|
+
type: "mp4"
|
|
970
|
+
};
|
|
936
971
|
}
|
|
937
972
|
if (broadcast.recordingMp3Url && broadcast.hash) {
|
|
938
|
-
return {
|
|
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(
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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(
|
|
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("[
|
|
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 = [
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
}, [
|
|
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
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
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
|
-
|
|
1549
|
-
|
|
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
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
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
|
-
|
|
1571
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
1572
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1573
|
-
"
|
|
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
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
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: "
|
|
1595
|
-
/* @__PURE__ */ jsxRuntime.
|
|
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
|
-
"
|
|
1784
|
+
"input",
|
|
1615
1785
|
{
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
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:
|
|
1630
|
-
step:
|
|
1631
|
-
value:
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
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":
|
|
1637
|
-
"aria-valuenow":
|
|
1638
|
-
"aria-valuetext":
|
|
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
|
|
1705
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center
|
|
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
|
-
"
|
|
2213
|
+
"svg",
|
|
1711
2214
|
{
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
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
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
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
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
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
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
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
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
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
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
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
|
|
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.
|
|
2129
|
-
exports.
|
|
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.
|
|
2137
|
-
//# sourceMappingURL=
|
|
2138
|
-
//# sourceMappingURL=
|
|
2691
|
+
exports.useDialtribe = useDialtribe;
|
|
2692
|
+
//# sourceMappingURL=dialtribe-player.js.map
|
|
2693
|
+
//# sourceMappingURL=dialtribe-player.js.map
|