@dialtribe/react-sdk 0.1.0-alpha.5 → 0.1.0-alpha.7
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/dist/broadcast-player.d.mts +49 -18
- package/dist/broadcast-player.d.ts +49 -18
- package/dist/broadcast-player.js +47 -34
- package/dist/broadcast-player.js.map +1 -1
- package/dist/broadcast-player.mjs +47 -34
- package/dist/broadcast-player.mjs.map +1 -1
- package/dist/index.js +47 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +47 -34
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -4
|
@@ -14,6 +14,8 @@ interface DialTribeContextValue {
|
|
|
14
14
|
isExpired: boolean;
|
|
15
15
|
/** Mark token as expired */
|
|
16
16
|
markExpired: () => void;
|
|
17
|
+
/** Optional API base URL override */
|
|
18
|
+
apiBaseUrl?: string;
|
|
17
19
|
}
|
|
18
20
|
interface DialTribeProviderProps {
|
|
19
21
|
/** Session token from your backend (required) */
|
|
@@ -22,6 +24,12 @@ interface DialTribeProviderProps {
|
|
|
22
24
|
onTokenRefresh?: (newToken: string, expiresAt: string) => void;
|
|
23
25
|
/** Called when the session token expires or becomes invalid */
|
|
24
26
|
onTokenExpired?: () => void;
|
|
27
|
+
/**
|
|
28
|
+
* Optional: Override API base URL (for local development)
|
|
29
|
+
* If not provided, uses NEXT_PUBLIC_DIALTRIBE_API_URL environment variable or production default
|
|
30
|
+
* @example "http://localhost:3001/api/public/v1"
|
|
31
|
+
*/
|
|
32
|
+
apiBaseUrl?: string;
|
|
25
33
|
/** Your React app */
|
|
26
34
|
children: React$1.ReactNode;
|
|
27
35
|
}
|
|
@@ -32,22 +40,32 @@ interface DialTribeProviderProps {
|
|
|
32
40
|
*
|
|
33
41
|
* @example
|
|
34
42
|
* ```tsx
|
|
43
|
+
* // Production (default)
|
|
44
|
+
* <DialTribeProvider
|
|
45
|
+
* sessionToken="sess_xxx..."
|
|
46
|
+
* onTokenRefresh={(newToken) => setToken(newToken)}
|
|
47
|
+
* onTokenExpired={() => router.push('/login')}
|
|
48
|
+
* >
|
|
49
|
+
* <App />
|
|
50
|
+
* </DialTribeProvider>
|
|
51
|
+
*
|
|
52
|
+
* // Local development (explicit)
|
|
35
53
|
* <DialTribeProvider
|
|
36
54
|
* sessionToken="sess_xxx..."
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* setToken(newToken);
|
|
40
|
-
* }}
|
|
41
|
-
* onTokenExpired={() => {
|
|
42
|
-
* // Redirect to login or refresh
|
|
43
|
-
* router.push('/login');
|
|
44
|
-
* }}
|
|
55
|
+
* apiBaseUrl="http://localhost:3001/api/public/v1"
|
|
56
|
+
* onTokenRefresh={(newToken) => setToken(newToken)}
|
|
45
57
|
* >
|
|
46
58
|
* <App />
|
|
47
59
|
* </DialTribeProvider>
|
|
60
|
+
*
|
|
61
|
+
* // Local development (via env var - recommended)
|
|
62
|
+
* // Set NEXT_PUBLIC_DIALTRIBE_API_URL=http://localhost:3001/api/public/v1 in .env
|
|
63
|
+
* <DialTribeProvider sessionToken="sess_xxx...">
|
|
64
|
+
* <App />
|
|
65
|
+
* </DialTribeProvider>
|
|
48
66
|
* ```
|
|
49
67
|
*/
|
|
50
|
-
declare function DialTribeProvider({ sessionToken: initialToken, onTokenRefresh, onTokenExpired, children, }: DialTribeProviderProps): react_jsx_runtime.JSX.Element;
|
|
68
|
+
declare function DialTribeProvider({ sessionToken: initialToken, onTokenRefresh, onTokenExpired, apiBaseUrl, children, }: DialTribeProviderProps): react_jsx_runtime.JSX.Element;
|
|
51
69
|
/**
|
|
52
70
|
* Hook to access DialTribe context
|
|
53
71
|
*
|
|
@@ -63,19 +81,25 @@ declare function useDialTribe(): DialTribeContextValue;
|
|
|
63
81
|
/**
|
|
64
82
|
* DialTribe API Client
|
|
65
83
|
*
|
|
66
|
-
* Handles all API communication with
|
|
84
|
+
* Handles all API communication with DialTribe endpoints.
|
|
67
85
|
* Automatically manages session token refresh via X-Session-Token headers.
|
|
86
|
+
*
|
|
87
|
+
* Supports configurable API base URL for development:
|
|
88
|
+
* 1. Via environment variable: NEXT_PUBLIC_DIALTRIBE_API_URL
|
|
89
|
+
* 2. Via apiBaseUrl prop on DialTribeProvider (takes precedence)
|
|
90
|
+
*
|
|
91
|
+
* Defaults to production URL if neither is specified.
|
|
68
92
|
*/
|
|
69
|
-
/**
|
|
70
|
-
declare const DIALTRIBE_API_BASE
|
|
71
|
-
/** API endpoints (
|
|
93
|
+
/** Default API base URL (production or from environment) */
|
|
94
|
+
declare const DIALTRIBE_API_BASE: string;
|
|
95
|
+
/** Default API endpoints (uses default base URL) */
|
|
72
96
|
declare const ENDPOINTS: {
|
|
73
|
-
readonly broadcasts:
|
|
97
|
+
readonly broadcasts: `${string}/broadcasts`;
|
|
74
98
|
readonly broadcast: (id: number) => string;
|
|
75
|
-
readonly contentPlay:
|
|
76
|
-
readonly presignedUrl:
|
|
77
|
-
readonly sessionStart:
|
|
78
|
-
readonly sessionPing:
|
|
99
|
+
readonly contentPlay: `${string}/content/play`;
|
|
100
|
+
readonly presignedUrl: `${string}/media/presigned-url`;
|
|
101
|
+
readonly sessionStart: `${string}/session/start`;
|
|
102
|
+
readonly sessionPing: `${string}/session/ping`;
|
|
79
103
|
};
|
|
80
104
|
interface ApiClientConfig {
|
|
81
105
|
/** Session token for authentication */
|
|
@@ -84,6 +108,12 @@ interface ApiClientConfig {
|
|
|
84
108
|
onTokenRefresh?: (newToken: string, expiresAt: string) => void;
|
|
85
109
|
/** Called when token expires or becomes invalid */
|
|
86
110
|
onTokenExpired?: () => void;
|
|
111
|
+
/**
|
|
112
|
+
* Optional: Override API base URL (e.g., for local development)
|
|
113
|
+
* If not provided, uses environment variable or production default
|
|
114
|
+
* @example "http://localhost:3001/api/public/v1"
|
|
115
|
+
*/
|
|
116
|
+
apiBaseUrl?: string;
|
|
87
117
|
}
|
|
88
118
|
/**
|
|
89
119
|
* DialTribe API Client
|
|
@@ -93,6 +123,7 @@ interface ApiClientConfig {
|
|
|
93
123
|
*/
|
|
94
124
|
declare class DialTribeClient {
|
|
95
125
|
private config;
|
|
126
|
+
private endpoints;
|
|
96
127
|
constructor(config: ApiClientConfig);
|
|
97
128
|
/**
|
|
98
129
|
* Make an authenticated request to DialTribe API
|
|
@@ -14,6 +14,8 @@ interface DialTribeContextValue {
|
|
|
14
14
|
isExpired: boolean;
|
|
15
15
|
/** Mark token as expired */
|
|
16
16
|
markExpired: () => void;
|
|
17
|
+
/** Optional API base URL override */
|
|
18
|
+
apiBaseUrl?: string;
|
|
17
19
|
}
|
|
18
20
|
interface DialTribeProviderProps {
|
|
19
21
|
/** Session token from your backend (required) */
|
|
@@ -22,6 +24,12 @@ interface DialTribeProviderProps {
|
|
|
22
24
|
onTokenRefresh?: (newToken: string, expiresAt: string) => void;
|
|
23
25
|
/** Called when the session token expires or becomes invalid */
|
|
24
26
|
onTokenExpired?: () => void;
|
|
27
|
+
/**
|
|
28
|
+
* Optional: Override API base URL (for local development)
|
|
29
|
+
* If not provided, uses NEXT_PUBLIC_DIALTRIBE_API_URL environment variable or production default
|
|
30
|
+
* @example "http://localhost:3001/api/public/v1"
|
|
31
|
+
*/
|
|
32
|
+
apiBaseUrl?: string;
|
|
25
33
|
/** Your React app */
|
|
26
34
|
children: React$1.ReactNode;
|
|
27
35
|
}
|
|
@@ -32,22 +40,32 @@ interface DialTribeProviderProps {
|
|
|
32
40
|
*
|
|
33
41
|
* @example
|
|
34
42
|
* ```tsx
|
|
43
|
+
* // Production (default)
|
|
44
|
+
* <DialTribeProvider
|
|
45
|
+
* sessionToken="sess_xxx..."
|
|
46
|
+
* onTokenRefresh={(newToken) => setToken(newToken)}
|
|
47
|
+
* onTokenExpired={() => router.push('/login')}
|
|
48
|
+
* >
|
|
49
|
+
* <App />
|
|
50
|
+
* </DialTribeProvider>
|
|
51
|
+
*
|
|
52
|
+
* // Local development (explicit)
|
|
35
53
|
* <DialTribeProvider
|
|
36
54
|
* sessionToken="sess_xxx..."
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* setToken(newToken);
|
|
40
|
-
* }}
|
|
41
|
-
* onTokenExpired={() => {
|
|
42
|
-
* // Redirect to login or refresh
|
|
43
|
-
* router.push('/login');
|
|
44
|
-
* }}
|
|
55
|
+
* apiBaseUrl="http://localhost:3001/api/public/v1"
|
|
56
|
+
* onTokenRefresh={(newToken) => setToken(newToken)}
|
|
45
57
|
* >
|
|
46
58
|
* <App />
|
|
47
59
|
* </DialTribeProvider>
|
|
60
|
+
*
|
|
61
|
+
* // Local development (via env var - recommended)
|
|
62
|
+
* // Set NEXT_PUBLIC_DIALTRIBE_API_URL=http://localhost:3001/api/public/v1 in .env
|
|
63
|
+
* <DialTribeProvider sessionToken="sess_xxx...">
|
|
64
|
+
* <App />
|
|
65
|
+
* </DialTribeProvider>
|
|
48
66
|
* ```
|
|
49
67
|
*/
|
|
50
|
-
declare function DialTribeProvider({ sessionToken: initialToken, onTokenRefresh, onTokenExpired, children, }: DialTribeProviderProps): react_jsx_runtime.JSX.Element;
|
|
68
|
+
declare function DialTribeProvider({ sessionToken: initialToken, onTokenRefresh, onTokenExpired, apiBaseUrl, children, }: DialTribeProviderProps): react_jsx_runtime.JSX.Element;
|
|
51
69
|
/**
|
|
52
70
|
* Hook to access DialTribe context
|
|
53
71
|
*
|
|
@@ -63,19 +81,25 @@ declare function useDialTribe(): DialTribeContextValue;
|
|
|
63
81
|
/**
|
|
64
82
|
* DialTribe API Client
|
|
65
83
|
*
|
|
66
|
-
* Handles all API communication with
|
|
84
|
+
* Handles all API communication with DialTribe endpoints.
|
|
67
85
|
* Automatically manages session token refresh via X-Session-Token headers.
|
|
86
|
+
*
|
|
87
|
+
* Supports configurable API base URL for development:
|
|
88
|
+
* 1. Via environment variable: NEXT_PUBLIC_DIALTRIBE_API_URL
|
|
89
|
+
* 2. Via apiBaseUrl prop on DialTribeProvider (takes precedence)
|
|
90
|
+
*
|
|
91
|
+
* Defaults to production URL if neither is specified.
|
|
68
92
|
*/
|
|
69
|
-
/**
|
|
70
|
-
declare const DIALTRIBE_API_BASE
|
|
71
|
-
/** API endpoints (
|
|
93
|
+
/** Default API base URL (production or from environment) */
|
|
94
|
+
declare const DIALTRIBE_API_BASE: string;
|
|
95
|
+
/** Default API endpoints (uses default base URL) */
|
|
72
96
|
declare const ENDPOINTS: {
|
|
73
|
-
readonly broadcasts:
|
|
97
|
+
readonly broadcasts: `${string}/broadcasts`;
|
|
74
98
|
readonly broadcast: (id: number) => string;
|
|
75
|
-
readonly contentPlay:
|
|
76
|
-
readonly presignedUrl:
|
|
77
|
-
readonly sessionStart:
|
|
78
|
-
readonly sessionPing:
|
|
99
|
+
readonly contentPlay: `${string}/content/play`;
|
|
100
|
+
readonly presignedUrl: `${string}/media/presigned-url`;
|
|
101
|
+
readonly sessionStart: `${string}/session/start`;
|
|
102
|
+
readonly sessionPing: `${string}/session/ping`;
|
|
79
103
|
};
|
|
80
104
|
interface ApiClientConfig {
|
|
81
105
|
/** Session token for authentication */
|
|
@@ -84,6 +108,12 @@ interface ApiClientConfig {
|
|
|
84
108
|
onTokenRefresh?: (newToken: string, expiresAt: string) => void;
|
|
85
109
|
/** Called when token expires or becomes invalid */
|
|
86
110
|
onTokenExpired?: () => void;
|
|
111
|
+
/**
|
|
112
|
+
* Optional: Override API base URL (e.g., for local development)
|
|
113
|
+
* If not provided, uses environment variable or production default
|
|
114
|
+
* @example "http://localhost:3001/api/public/v1"
|
|
115
|
+
*/
|
|
116
|
+
apiBaseUrl?: string;
|
|
87
117
|
}
|
|
88
118
|
/**
|
|
89
119
|
* DialTribe API Client
|
|
@@ -93,6 +123,7 @@ interface ApiClientConfig {
|
|
|
93
123
|
*/
|
|
94
124
|
declare class DialTribeClient {
|
|
95
125
|
private config;
|
|
126
|
+
private endpoints;
|
|
96
127
|
constructor(config: ApiClientConfig);
|
|
97
128
|
/**
|
|
98
129
|
* Make an authenticated request to DialTribe API
|
package/dist/broadcast-player.js
CHANGED
|
@@ -14,6 +14,7 @@ function DialTribeProvider({
|
|
|
14
14
|
sessionToken: initialToken,
|
|
15
15
|
onTokenRefresh,
|
|
16
16
|
onTokenExpired,
|
|
17
|
+
apiBaseUrl,
|
|
17
18
|
children
|
|
18
19
|
}) {
|
|
19
20
|
const [sessionToken, setSessionTokenState] = react.useState(initialToken);
|
|
@@ -42,7 +43,8 @@ function DialTribeProvider({
|
|
|
42
43
|
sessionToken,
|
|
43
44
|
setSessionToken,
|
|
44
45
|
isExpired,
|
|
45
|
-
markExpired
|
|
46
|
+
markExpired,
|
|
47
|
+
apiBaseUrl
|
|
46
48
|
};
|
|
47
49
|
return /* @__PURE__ */ jsxRuntime.jsx(DialTribeContext.Provider, { value, children });
|
|
48
50
|
}
|
|
@@ -57,18 +59,28 @@ function useDialTribe() {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
// src/client/DialTribeClient.ts
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
function getDefaultApiBaseUrl() {
|
|
63
|
+
if (typeof process !== "undefined" && process.env?.NEXT_PUBLIC_DIALTRIBE_API_URL) {
|
|
64
|
+
return process.env.NEXT_PUBLIC_DIALTRIBE_API_URL;
|
|
65
|
+
}
|
|
66
|
+
return "https://dialtribe.com/api/public/v1";
|
|
67
|
+
}
|
|
68
|
+
var DIALTRIBE_API_BASE = getDefaultApiBaseUrl();
|
|
69
|
+
function getEndpoints(baseUrl = DIALTRIBE_API_BASE) {
|
|
70
|
+
return {
|
|
71
|
+
broadcasts: `${baseUrl}/broadcasts`,
|
|
72
|
+
broadcast: (id) => `${baseUrl}/broadcasts/${id}`,
|
|
73
|
+
contentPlay: `${baseUrl}/content/play`,
|
|
74
|
+
presignedUrl: `${baseUrl}/media/presigned-url`,
|
|
75
|
+
sessionStart: `${baseUrl}/session/start`,
|
|
76
|
+
sessionPing: `${baseUrl}/session/ping`
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
var ENDPOINTS = getEndpoints();
|
|
69
80
|
var DialTribeClient = class {
|
|
70
81
|
constructor(config) {
|
|
71
82
|
this.config = config;
|
|
83
|
+
this.endpoints = config.apiBaseUrl ? getEndpoints(config.apiBaseUrl) : ENDPOINTS;
|
|
72
84
|
}
|
|
73
85
|
/**
|
|
74
86
|
* Make an authenticated request to DialTribe API
|
|
@@ -115,7 +127,7 @@ var DialTribeClient = class {
|
|
|
115
127
|
if (params?.broadcastStatus) searchParams.set("broadcastStatus", params.broadcastStatus.toString());
|
|
116
128
|
if (params?.search) searchParams.set("search", params.search);
|
|
117
129
|
if (params?.includeDeleted) searchParams.set("includeDeleted", "true");
|
|
118
|
-
const url = `${
|
|
130
|
+
const url = `${this.endpoints.broadcasts}${searchParams.toString() ? `?${searchParams}` : ""}`;
|
|
119
131
|
const response = await this.fetch(url);
|
|
120
132
|
if (!response.ok) {
|
|
121
133
|
throw new Error(`Failed to fetch broadcasts: ${response.status} ${response.statusText}`);
|
|
@@ -126,7 +138,7 @@ var DialTribeClient = class {
|
|
|
126
138
|
* Get a single broadcast by ID
|
|
127
139
|
*/
|
|
128
140
|
async getBroadcast(id) {
|
|
129
|
-
const response = await this.fetch(
|
|
141
|
+
const response = await this.fetch(this.endpoints.broadcast(id));
|
|
130
142
|
if (!response.ok) {
|
|
131
143
|
if (response.status === 404) {
|
|
132
144
|
throw new Error("Broadcast not found");
|
|
@@ -148,7 +160,7 @@ var DialTribeClient = class {
|
|
|
148
160
|
});
|
|
149
161
|
if (params.hash) searchParams.set("hash", params.hash);
|
|
150
162
|
if (params.action) searchParams.set("action", params.action);
|
|
151
|
-
const url = `${
|
|
163
|
+
const url = `${this.endpoints.contentPlay}?${searchParams}`;
|
|
152
164
|
const response = await this.fetch(url, {
|
|
153
165
|
redirect: "manual"
|
|
154
166
|
// Don't follow redirect, we want the URL
|
|
@@ -172,7 +184,7 @@ var DialTribeClient = class {
|
|
|
172
184
|
hash: params.hash,
|
|
173
185
|
fileType: params.fileType
|
|
174
186
|
});
|
|
175
|
-
const url = `${
|
|
187
|
+
const url = `${this.endpoints.presignedUrl}?${searchParams}`;
|
|
176
188
|
const response = await this.fetch(url);
|
|
177
189
|
if (!response.ok) {
|
|
178
190
|
throw new Error(`Failed to refresh URL: ${response.status} ${response.statusText}`);
|
|
@@ -185,7 +197,7 @@ var DialTribeClient = class {
|
|
|
185
197
|
* @returns audienceId and optional resumePosition
|
|
186
198
|
*/
|
|
187
199
|
async startSession(params) {
|
|
188
|
-
const response = await this.fetch(
|
|
200
|
+
const response = await this.fetch(this.endpoints.sessionStart, {
|
|
189
201
|
method: "POST",
|
|
190
202
|
body: JSON.stringify(params)
|
|
191
203
|
});
|
|
@@ -204,7 +216,7 @@ var DialTribeClient = class {
|
|
|
204
216
|
* - 3: UNMOUNT
|
|
205
217
|
*/
|
|
206
218
|
async sendSessionPing(params) {
|
|
207
|
-
const response = await this.fetch(
|
|
219
|
+
const response = await this.fetch(this.endpoints.sessionPing, {
|
|
208
220
|
method: "POST",
|
|
209
221
|
body: JSON.stringify(params)
|
|
210
222
|
});
|
|
@@ -747,11 +759,12 @@ function BroadcastPlayer({
|
|
|
747
759
|
className = "",
|
|
748
760
|
enableKeyboardShortcuts = false
|
|
749
761
|
}) {
|
|
750
|
-
const { sessionToken, setSessionToken, markExpired } = useDialTribe();
|
|
762
|
+
const { sessionToken, setSessionToken, markExpired, apiBaseUrl } = useDialTribe();
|
|
751
763
|
const clientRef = react.useRef(null);
|
|
752
764
|
if (!clientRef.current && sessionToken) {
|
|
753
765
|
clientRef.current = new DialTribeClient({
|
|
754
766
|
sessionToken,
|
|
767
|
+
apiBaseUrl,
|
|
755
768
|
onTokenRefresh: (newToken, expiresAt) => {
|
|
756
769
|
debug.log(`[DialTribeClient] Token refreshed, expires at ${expiresAt}`);
|
|
757
770
|
setSessionToken(newToken, expiresAt);
|
|
@@ -1410,8 +1423,8 @@ function BroadcastPlayer({
|
|
|
1410
1423
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center p-8", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner, { variant: "white", text: "Loading..." }) });
|
|
1411
1424
|
}
|
|
1412
1425
|
const hasTranscript = broadcast.transcriptStatus === 2 && transcriptData && (transcriptData.segments && transcriptData.segments.some((s) => s.words && s.words.length > 0) || transcriptData.words && transcriptData.words.length > 0);
|
|
1413
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `bg-black rounded-lg shadow-2xl w-full h-full flex flex-col ${className}`, children: [
|
|
1414
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-zinc-900/50 backdrop-blur-sm border-b border-zinc-800 px-4 md:px-6 py-3 md:py-4 flex justify-between items-center rounded-t-lg shrink-0", children: [
|
|
1426
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `bg-black rounded-lg shadow-2xl w-full max-h-full flex flex-col overflow-hidden ${className}`, children: [
|
|
1427
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-zinc-900/50 backdrop-blur-sm border-b border-zinc-800 px-3 sm:px-4 md:px-6 py-2 sm:py-3 md:py-4 flex justify-between items-center rounded-t-lg shrink-0", children: [
|
|
1415
1428
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1416
1429
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-white", children: broadcast.streamKeyRecord?.foreignName || "Broadcast" }),
|
|
1417
1430
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-400", children: [
|
|
@@ -1439,8 +1452,8 @@ function BroadcastPlayer({
|
|
|
1439
1452
|
}
|
|
1440
1453
|
) })
|
|
1441
1454
|
] }),
|
|
1442
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
|
|
1443
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col overflow-hidden", children: [
|
|
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: [
|
|
1444
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: [
|
|
1445
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: [
|
|
1446
1459
|
/* @__PURE__ */ jsxRuntime.jsx(AudioWaveform, { audioElement, isPlaying: isLiveStream ? true : playing, isLive: isLiveStream }),
|
|
@@ -1722,7 +1735,7 @@ function BroadcastPlayer({
|
|
|
1722
1735
|
] })
|
|
1723
1736
|
] }))
|
|
1724
1737
|
] }),
|
|
1725
|
-
showTranscript && hasTranscript && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full md:w-96 bg-zinc-900 border-l border-zinc-800 flex flex-col overflow-hidden", children: [
|
|
1738
|
+
showTranscript && hasTranscript && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 md:flex-none min-h-0 w-full md:w-96 bg-zinc-900 border-t md:border-t-0 border-l border-zinc-800 flex flex-col overflow-hidden", children: [
|
|
1726
1739
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-3 border-b border-zinc-800 bg-zinc-900/50 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1727
1740
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1728
1741
|
/* @__PURE__ */ jsxRuntime.jsx("svg", { className: "w-5 h-5 text-green-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) }),
|
|
@@ -1751,7 +1764,7 @@ function BroadcastPlayer({
|
|
|
1751
1764
|
]
|
|
1752
1765
|
}
|
|
1753
1766
|
) }),
|
|
1754
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: transcriptContainerRef, className: "flex-1 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: (() => {
|
|
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: (() => {
|
|
1755
1768
|
const filteredSegments = transcriptData.segments.filter((s) => s.words && s.words.length > 0);
|
|
1756
1769
|
let globalWordIndex = 0;
|
|
1757
1770
|
const wordMap = /* @__PURE__ */ new Map();
|
|
@@ -1911,14 +1924,12 @@ function BroadcastPlayerModal({
|
|
|
1911
1924
|
}) {
|
|
1912
1925
|
const closeButtonRef = react.useRef(null);
|
|
1913
1926
|
const previousActiveElement = react.useRef(null);
|
|
1914
|
-
if (!isOpen) return null;
|
|
1915
1927
|
react.useEffect(() => {
|
|
1916
|
-
if (isOpen)
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
}
|
|
1928
|
+
if (!isOpen) return;
|
|
1929
|
+
previousActiveElement.current = document.activeElement;
|
|
1930
|
+
setTimeout(() => {
|
|
1931
|
+
closeButtonRef.current?.focus();
|
|
1932
|
+
}, 100);
|
|
1922
1933
|
return () => {
|
|
1923
1934
|
if (previousActiveElement.current) {
|
|
1924
1935
|
previousActiveElement.current.focus();
|
|
@@ -1926,6 +1937,7 @@ function BroadcastPlayerModal({
|
|
|
1926
1937
|
};
|
|
1927
1938
|
}, [isOpen]);
|
|
1928
1939
|
react.useEffect(() => {
|
|
1940
|
+
if (!isOpen) return;
|
|
1929
1941
|
const handleKeyDown = (e) => {
|
|
1930
1942
|
if (e.key === "Escape") {
|
|
1931
1943
|
onClose();
|
|
@@ -1933,7 +1945,8 @@ function BroadcastPlayerModal({
|
|
|
1933
1945
|
};
|
|
1934
1946
|
document.addEventListener("keydown", handleKeyDown);
|
|
1935
1947
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
1936
|
-
}, [onClose]);
|
|
1948
|
+
}, [isOpen, onClose]);
|
|
1949
|
+
if (!isOpen) return null;
|
|
1937
1950
|
const handleBackdropClick = (e) => {
|
|
1938
1951
|
if (e.target === e.currentTarget) {
|
|
1939
1952
|
onClose();
|
|
@@ -1942,18 +1955,18 @@ function BroadcastPlayerModal({
|
|
|
1942
1955
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1943
1956
|
"div",
|
|
1944
1957
|
{
|
|
1945
|
-
className: "fixed inset-0 bg-black/70 backdrop-blur-xl flex items-center justify-center z-50 p-4",
|
|
1958
|
+
className: "fixed inset-0 bg-black/70 backdrop-blur-xl flex items-center justify-center z-50 p-2 sm:p-4",
|
|
1946
1959
|
onClick: handleBackdropClick,
|
|
1947
1960
|
role: "dialog",
|
|
1948
1961
|
"aria-modal": "true",
|
|
1949
1962
|
"aria-label": "Broadcast player",
|
|
1950
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full
|
|
1963
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full max-w-7xl max-h-[95vh] sm:max-h-[90vh] overflow-hidden", children: [
|
|
1951
1964
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1952
1965
|
"button",
|
|
1953
1966
|
{
|
|
1954
1967
|
ref: closeButtonRef,
|
|
1955
1968
|
onClick: onClose,
|
|
1956
|
-
className: "absolute top-4 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",
|
|
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",
|
|
1957
1970
|
title: "Close (ESC)",
|
|
1958
1971
|
"aria-label": "Close player",
|
|
1959
1972
|
children: "\xD7"
|