@dialtribe/react-sdk 0.1.0-alpha.8 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
package/README.md
CHANGED
|
@@ -1,535 +1,535 @@
|
|
|
1
|
-
# @dialtribe/react-sdk
|
|
2
|
-
|
|
3
|
-
Official DialTribe SDK for React
|
|
4
|
-
|
|
5
|
-
## Status
|
|
6
|
-
|
|
7
|
-
**Alpha 5** (v0.1.0-alpha.5) - 🏆 **A+ Production Quality** - Session token architecture, WCAG 2.1 AA accessible, keyboard navigation, professional error handling.
|
|
8
|
-
|
|
9
|
-
**Code Quality:** **A+** ⭐⭐⭐⭐⭐
|
|
10
|
-
|
|
11
|
-
**Recent Updates (December 2025):**
|
|
12
|
-
- ✅ Refactored to API-first architecture with session tokens
|
|
13
|
-
- ✅ New `BroadcastPlayer` component for inline usage
|
|
14
|
-
- ✅ Simplified `BroadcastPlayerModal` to thin wrapper
|
|
15
|
-
- ✅ Added `DialTribeProvider` context for token management
|
|
16
|
-
- ✅ All API calls through `DialTribeClient`
|
|
17
|
-
- ✅ **WCAG 2.1 Level AA accessible** - Full keyboard navigation
|
|
18
|
-
- ✅ **Production-safe** - No console logs, secure endpoints
|
|
19
|
-
- ✅ **Professional UX** - Specific error messages, retry mechanism
|
|
20
|
-
- ✅ **Mobile-first** - Fully responsive design
|
|
21
|
-
|
|
22
|
-
**Breaking Changes from alpha.4** - See [Migration Guide](#migration-guide-alpha4--alpha5) below.
|
|
23
|
-
|
|
24
|
-
## Installation
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npm install @dialtribe/react-sdk
|
|
28
|
-
# or
|
|
29
|
-
yarn add @dialtribe/react-sdk
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Styling Setup
|
|
33
|
-
|
|
34
|
-
The SDK components use Tailwind CSS classes. Choose the option that fits your project:
|
|
35
|
-
|
|
36
|
-
#### Option 1: No Tailwind in Your Project (Simple)
|
|
37
|
-
|
|
38
|
-
Just import the pre-built CSS file once in your app:
|
|
39
|
-
|
|
40
|
-
```tsx
|
|
41
|
-
// In your main app file (e.g., App.tsx, _app.tsx, or index.tsx)
|
|
42
|
-
import '@dialtribe/react-sdk/styles.css';
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
**That's it!** The SDK includes all necessary styles (17KB minified).
|
|
46
|
-
|
|
47
|
-
#### Option 2: You Already Use Tailwind (Recommended)
|
|
48
|
-
|
|
49
|
-
If your project already has Tailwind CSS configured, you **don't need to import the CSS file**. Instead, configure Tailwind to scan the SDK files:
|
|
50
|
-
|
|
51
|
-
```js
|
|
52
|
-
// tailwind.config.js
|
|
53
|
-
module.exports = {
|
|
54
|
-
content: [
|
|
55
|
-
'./src/**/*.{js,jsx,ts,tsx}',
|
|
56
|
-
'./node_modules/@dialtribe/react-sdk/dist/**/*.{js,mjs}', // Add this line
|
|
57
|
-
],
|
|
58
|
-
theme: {
|
|
59
|
-
extend: {},
|
|
60
|
-
},
|
|
61
|
-
plugins: [],
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
This way, Tailwind will automatically generate only the classes the SDK needs, keeping your bundle optimized.
|
|
66
|
-
|
|
67
|
-
## Quick Start
|
|
68
|
-
|
|
69
|
-
### Import Options
|
|
70
|
-
|
|
71
|
-
You can import components in two ways:
|
|
72
|
-
|
|
73
|
-
**Option 1: Barrel import (imports everything)**
|
|
74
|
-
```tsx
|
|
75
|
-
import { HelloWorld } from '@dialtribe/react-sdk';
|
|
76
|
-
|
|
77
|
-
function App() {
|
|
78
|
-
return <HelloWorld name="Developer" />;
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
**Option 2: Subpath import (tree-shakeable, smaller bundle)**
|
|
83
|
-
```tsx
|
|
84
|
-
import { HelloWorld } from '@dialtribe/react-sdk/hello-world';
|
|
85
|
-
|
|
86
|
-
function App() {
|
|
87
|
-
return <HelloWorld name="Developer" />;
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
For optimal bundle size, we recommend using subpath imports when importing individual components.
|
|
92
|
-
|
|
93
|
-
### Bundle Sizes
|
|
94
|
-
|
|
95
|
-
Current bundle sizes (alpha.5, December 2025 - A+ Quality):
|
|
96
|
-
|
|
97
|
-
| Import Method | Minified | Gzipped | Notes |
|
|
98
|
-
|--------------|----------|---------|-------|
|
|
99
|
-
| `@dialtribe/react-sdk/hello-world` | 756 B | ~448 B | Simple test component |
|
|
100
|
-
| `@dialtribe/react-sdk/
|
|
101
|
-
| `@dialtribe/react-sdk` (barrel) | 91.70 KB | ~17.5 KB | All components |
|
|
102
|
-
| `@dialtribe/react-sdk/styles.css` | 17 KB | 3.8 KB | Required styles (if no Tailwind) |
|
|
103
|
-
|
|
104
|
-
**Total for BroadcastPlayer:** ~20.8 KB gzipped (17 KB JS + 3.8 KB CSS) ✅
|
|
105
|
-
|
|
106
|
-
**What's included in
|
|
107
|
-
- BroadcastPlayer (inline player)
|
|
108
|
-
- BroadcastPlayerModal (modal wrapper)
|
|
109
|
-
- DialTribeProvider (context)
|
|
110
|
-
- DialTribeClient (API client)
|
|
111
|
-
- useDialTribe hook
|
|
112
|
-
- **WCAG 2.1 AA accessibility** (ARIA labels, keyboard nav)
|
|
113
|
-
- **Professional error handling** (specific messages, retry)
|
|
114
|
-
- **Production safety** (debug logging, secure endpoints)
|
|
115
|
-
- All utilities (formatTime, buildBroadcastCdnUrl, etc.)
|
|
116
|
-
|
|
117
|
-
**Why subpath imports matter:** The barrel import (`@dialtribe/react-sdk`) includes all components. Using subpath imports like `/
|
|
118
|
-
|
|
119
|
-
## Components
|
|
120
|
-
|
|
121
|
-
### BroadcastPlayer
|
|
122
|
-
|
|
123
|
-
Full-featured media player for DialTribe broadcasts with support for:
|
|
124
|
-
- **Live HLS streaming** - Real-time playback with CloudFront CDN
|
|
125
|
-
- **MP4 video playback** - Recorded video with presigned S3 URLs
|
|
126
|
-
- **MP3 audio playback** - Recorded audio with animated waveform visualization
|
|
127
|
-
- **Transcripts** - Word-level timestamps with clickable seeking
|
|
128
|
-
- **Clip creation** - Optional clip creator integration (via render prop)
|
|
129
|
-
- **Audience tracking** - Session management and playback analytics
|
|
130
|
-
- **Auto URL refresh** - Proactive presigned URL renewal before expiration
|
|
131
|
-
- **Session token authentication** - Secure, revocable access tokens
|
|
132
|
-
|
|
133
|
-
#### Architecture (alpha.5+)
|
|
134
|
-
|
|
135
|
-
The SDK uses an **API-first architecture** with session token authentication:
|
|
136
|
-
|
|
137
|
-
1. **Your Backend** - Stores your `DIALTRIBE_SECRET_KEY` securely
|
|
138
|
-
2. **Session Token** - Your backend requests a session token from DialTribe API
|
|
139
|
-
3. **DialTribeProvider** - Wrap your app with the provider (passes token to all components)
|
|
140
|
-
4. **BroadcastPlayer** - Inline player component (you control the layout)
|
|
141
|
-
5. **BroadcastPlayerModal** - Pre-built modal wrapper (ready to use)
|
|
142
|
-
|
|
143
|
-
**Important:** Make sure you've completed the [Styling Setup](#styling-setup) above.
|
|
144
|
-
|
|
145
|
-
#### Step 1: Get Session Token from Your Backend
|
|
146
|
-
|
|
147
|
-
First, create an API endpoint in your backend to generate session tokens:
|
|
148
|
-
|
|
149
|
-
```javascript
|
|
150
|
-
// Backend: Express.js endpoint example
|
|
151
|
-
app.post('/api/dialtribe/session', async (req, res) => {
|
|
152
|
-
// Verify user is authenticated in your system
|
|
153
|
-
if (!req.user) {
|
|
154
|
-
return res.status(401).json({ error: 'Unauthorized' });
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Request session token from DialTribe
|
|
158
|
-
const response = await fetch('https://dialtribe.com/api/public/v1/sessions', {
|
|
159
|
-
method: 'POST',
|
|
160
|
-
headers: {
|
|
161
|
-
'Content-Type': 'application/json',
|
|
162
|
-
'Authorization': `Bearer ${process.env.DIALTRIBE_SECRET_KEY}`, // Keep this secret!
|
|
163
|
-
},
|
|
164
|
-
body: JSON.stringify({
|
|
165
|
-
externalUserId: req.user.id,
|
|
166
|
-
expiresIn: 21600, // 6 hours
|
|
167
|
-
}),
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
const { session_token } = await response.json();
|
|
171
|
-
res.json({ sessionToken: session_token });
|
|
172
|
-
});
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
#### Step 2: Wrap Your App with DialTribeProvider
|
|
176
|
-
|
|
177
|
-
```tsx
|
|
178
|
-
import { DialTribeProvider } from '@dialtribe/react-sdk/
|
|
179
|
-
import '@dialtribe/react-sdk/styles.css';
|
|
180
|
-
import { useState, useEffect } from 'react';
|
|
181
|
-
|
|
182
|
-
function Root() {
|
|
183
|
-
const [sessionToken, setSessionToken] = useState<string | null>(null);
|
|
184
|
-
|
|
185
|
-
// Get session token from YOUR backend
|
|
186
|
-
useEffect(() => {
|
|
187
|
-
fetch('/api/dialtribe/session', { credentials: 'include' })
|
|
188
|
-
.then(res => res.json())
|
|
189
|
-
.then(data => setSessionToken(data.sessionToken))
|
|
190
|
-
.catch(err => console.error('Failed to get session token:', err));
|
|
191
|
-
}, []);
|
|
192
|
-
|
|
193
|
-
if (!sessionToken) return <div>Loading...</div>;
|
|
194
|
-
|
|
195
|
-
return (
|
|
196
|
-
<DialTribeProvider
|
|
197
|
-
sessionToken={sessionToken}
|
|
198
|
-
onTokenRefresh={(newToken, expiresAt) => {
|
|
199
|
-
console.log('Token refreshed, expires at:', expiresAt);
|
|
200
|
-
setSessionToken(newToken);
|
|
201
|
-
}}
|
|
202
|
-
onTokenExpired={() => {
|
|
203
|
-
console.error('Token expired, redirecting to login...');
|
|
204
|
-
window.location.href = '/login';
|
|
205
|
-
}}
|
|
206
|
-
>
|
|
207
|
-
<App />
|
|
208
|
-
</DialTribeProvider>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
#### Step 3: Fetch Broadcasts from DialTribe API
|
|
214
|
-
|
|
215
|
-
```tsx
|
|
216
|
-
import { useDialTribe } from '@dialtribe/react-sdk/
|
|
217
|
-
import { useState, useEffect } from 'react';
|
|
218
|
-
|
|
219
|
-
function App() {
|
|
220
|
-
const { sessionToken } = useDialTribe();
|
|
221
|
-
const [broadcasts, setBroadcasts] = useState([]);
|
|
222
|
-
|
|
223
|
-
useEffect(() => {
|
|
224
|
-
// Fetch broadcasts from DialTribe API
|
|
225
|
-
fetch('https://dialtribe.com/api/public/v1/broadcasts', {
|
|
226
|
-
headers: { 'Authorization': `Bearer ${sessionToken}` }
|
|
227
|
-
})
|
|
228
|
-
.then(res => res.json())
|
|
229
|
-
.then(data => setBroadcasts(data.broadcasts))
|
|
230
|
-
.catch(err => console.error('Failed to fetch broadcasts:', err));
|
|
231
|
-
}, [sessionToken]);
|
|
232
|
-
|
|
233
|
-
return (
|
|
234
|
-
<div>
|
|
235
|
-
<h1>My Broadcasts</h1>
|
|
236
|
-
{broadcasts.map(broadcast => (
|
|
237
|
-
<BroadcastCard key={broadcast.id} broadcast={broadcast} />
|
|
238
|
-
))}
|
|
239
|
-
</div>
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
#### Step 4: Use the Player Component
|
|
245
|
-
|
|
246
|
-
**Option A: Built-in Modal (Recommended for Quick Setup)**
|
|
247
|
-
|
|
248
|
-
```tsx
|
|
249
|
-
import { BroadcastPlayerModal } from '@dialtribe/react-sdk/
|
|
250
|
-
import { useState } from 'react';
|
|
251
|
-
|
|
252
|
-
function BroadcastCard({ broadcast }) {
|
|
253
|
-
const [showModal, setShowModal] = useState(false);
|
|
254
|
-
|
|
255
|
-
return (
|
|
256
|
-
<>
|
|
257
|
-
<button onClick={() => setShowModal(true)}>
|
|
258
|
-
Watch: {broadcast.streamKeyRecord?.foreignName}
|
|
259
|
-
</button>
|
|
260
|
-
|
|
261
|
-
<BroadcastPlayerModal
|
|
262
|
-
broadcast={broadcast}
|
|
263
|
-
isOpen={showModal}
|
|
264
|
-
onClose={() => setShowModal(false)}
|
|
265
|
-
appId="your-app-id"
|
|
266
|
-
contentId={broadcast.id}
|
|
267
|
-
/>
|
|
268
|
-
</>
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
**Option B: Inline Player (Custom Layout)**
|
|
274
|
-
|
|
275
|
-
```tsx
|
|
276
|
-
import { BroadcastPlayer } from '@dialtribe/react-sdk/
|
|
277
|
-
|
|
278
|
-
function CustomVideoPage({ broadcast }) {
|
|
279
|
-
return (
|
|
280
|
-
<div className="custom-layout">
|
|
281
|
-
<header>
|
|
282
|
-
<h1>{broadcast.streamKeyRecord?.foreignName}</h1>
|
|
283
|
-
<p>Your custom header</p>
|
|
284
|
-
</header>
|
|
285
|
-
|
|
286
|
-
<BroadcastPlayer
|
|
287
|
-
broadcast={broadcast}
|
|
288
|
-
appId="your-app-id"
|
|
289
|
-
contentId={broadcast.id}
|
|
290
|
-
/>
|
|
291
|
-
|
|
292
|
-
<footer>
|
|
293
|
-
<p>Your custom footer</p>
|
|
294
|
-
</footer>
|
|
295
|
-
</div>
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
#### Why Session Tokens?
|
|
301
|
-
|
|
302
|
-
**Benefits of the Session Token Architecture:**
|
|
303
|
-
- ✅ **Secure** - Secret keys never exposed to frontend
|
|
304
|
-
- ✅ **Scoped** - Users can only watch content, not manage it
|
|
305
|
-
- ✅ **Automatic** - Tokens refresh transparently during playback
|
|
306
|
-
- ✅ **Revocable** - Invalidate access instantly if needed
|
|
307
|
-
- ✅ **Trackable** - Know who's watching what
|
|
308
|
-
|
|
309
|
-
**All API calls now use fixed DialTribe endpoints** - no custom endpoint configuration needed.
|
|
310
|
-
|
|
311
|
-
#### Clip Creator Integration
|
|
312
|
-
|
|
313
|
-
You can provide your own clip creator component via render prop:
|
|
314
|
-
|
|
315
|
-
```tsx
|
|
316
|
-
<BroadcastPlayerModal
|
|
317
|
-
broadcast={broadcast}
|
|
318
|
-
isOpen={showModal}
|
|
319
|
-
onClose={() => setShowModal(false)}
|
|
320
|
-
appId="your-app-id"
|
|
321
|
-
contentId={broadcast.id}
|
|
322
|
-
renderClipCreator={({ isOpen, onClose, sourceVideoUrl, sourceDuration, onPauseParent }) => (
|
|
323
|
-
<YourClipCreator
|
|
324
|
-
isOpen={isOpen}
|
|
325
|
-
onClose={onClose}
|
|
326
|
-
videoUrl={sourceVideoUrl}
|
|
327
|
-
duration={sourceDuration}
|
|
328
|
-
onPause={onPauseParent}
|
|
329
|
-
/>
|
|
330
|
-
)}
|
|
331
|
-
/>
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
#### Keyboard Shortcuts
|
|
335
|
-
|
|
336
|
-
The player supports keyboard shortcuts when `enableKeyboardShortcuts={true}` is passed:
|
|
337
|
-
|
|
338
|
-
```tsx
|
|
339
|
-
<BroadcastPlayer
|
|
340
|
-
broadcast={broadcast}
|
|
341
|
-
enableKeyboardShortcuts={true}
|
|
342
|
-
/>
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
**Available Shortcuts:**
|
|
346
|
-
|
|
347
|
-
| Key | Action |
|
|
348
|
-
|-----|--------|
|
|
349
|
-
| `Space` or `K` | Play/Pause |
|
|
350
|
-
| `←` (Left Arrow) | Seek backward 5 seconds |
|
|
351
|
-
| `→` (Right Arrow) | Seek forward 5 seconds |
|
|
352
|
-
| `↑` (Up Arrow) | Increase volume 10% |
|
|
353
|
-
| `↓` (Down Arrow) | Decrease volume 10% |
|
|
354
|
-
| `M` | Toggle mute |
|
|
355
|
-
| `F` | Toggle fullscreen (video only) |
|
|
356
|
-
| `0` or `Home` | Jump to beginning |
|
|
357
|
-
| `End` | Jump to end |
|
|
358
|
-
|
|
359
|
-
**Important Notes:**
|
|
360
|
-
- Keyboard shortcuts are **disabled by default** (`enableKeyboardShortcuts={false}`)
|
|
361
|
-
- Shortcuts don't interfere with typing in input fields or textareas
|
|
362
|
-
- Shortcuts are ignored when modifier keys (Ctrl, Cmd, Alt) are pressed
|
|
363
|
-
- The player is WCAG 2.1 Level AA accessible with or without keyboard shortcuts enabled
|
|
364
|
-
|
|
365
|
-
#### Props
|
|
366
|
-
|
|
367
|
-
**BroadcastPlayerModal** (Modal Wrapper)
|
|
368
|
-
|
|
369
|
-
| Prop | Type | Required | Description |
|
|
370
|
-
|------|------|----------|-------------|
|
|
371
|
-
| `broadcast` | `Broadcast` | Yes | Broadcast data from DialTribe API |
|
|
372
|
-
| `isOpen` | `boolean` | Yes | Whether modal is visible |
|
|
373
|
-
| `onClose` | `() => void` | Yes | Callback when modal should close |
|
|
374
|
-
| `appId` | `string` | No | App ID for clip creation and tracking |
|
|
375
|
-
| `contentId` | `number` | No | Content ID for clip creation and tracking |
|
|
376
|
-
| `foreignId` | `string` | No | Partner system user ID for analytics |
|
|
377
|
-
| `foreignTier` | `string` | No | User tier (e.g., 'guest', 'member', 'subscriber_gold') |
|
|
378
|
-
| `renderClipCreator` | `(props) => ReactNode` | No | Optional clip creator render prop |
|
|
379
|
-
| `className` | `string` | No | Custom CSS classes for the player |
|
|
380
|
-
| `enableKeyboardShortcuts` | `boolean` | No | Enable keyboard shortcuts (default: false) |
|
|
381
|
-
|
|
382
|
-
**BroadcastPlayer** (Inline Player)
|
|
383
|
-
|
|
384
|
-
| Prop | Type | Required | Description |
|
|
385
|
-
|------|------|----------|-------------|
|
|
386
|
-
| `broadcast` | `Broadcast` | Yes | Broadcast data from DialTribe API |
|
|
387
|
-
| `appId` | `string` | No | App ID for clip creation and tracking |
|
|
388
|
-
| `contentId` | `number` | No | Content ID for clip creation and tracking |
|
|
389
|
-
| `foreignId` | `string` | No | Partner system user ID for analytics |
|
|
390
|
-
| `foreignTier` | `string` | No | User tier (e.g., 'guest', 'member', 'subscriber_gold') |
|
|
391
|
-
| `renderClipCreator` | `(props) => ReactNode` | No | Optional clip creator render prop |
|
|
392
|
-
| `onError` | `(error: Error) => void` | No | Called when player encounters error |
|
|
393
|
-
| `className` | `string` | No | Custom CSS classes for the player |
|
|
394
|
-
| `enableKeyboardShortcuts` | `boolean` | No | Enable keyboard shortcuts (default: false) |
|
|
395
|
-
|
|
396
|
-
#### Utilities
|
|
397
|
-
|
|
398
|
-
The
|
|
399
|
-
|
|
400
|
-
```tsx
|
|
401
|
-
import {
|
|
402
|
-
// Context & Client
|
|
403
|
-
DialTribeProvider,
|
|
404
|
-
useDialTribe,
|
|
405
|
-
DialTribeClient,
|
|
406
|
-
|
|
407
|
-
// Components
|
|
408
|
-
BroadcastPlayer,
|
|
409
|
-
BroadcastPlayerModal,
|
|
410
|
-
|
|
411
|
-
// Utilities
|
|
412
|
-
formatTime,
|
|
413
|
-
buildBroadcastCdnUrl,
|
|
414
|
-
HTTP_STATUS,
|
|
415
|
-
|
|
416
|
-
// Types
|
|
417
|
-
type Broadcast,
|
|
418
|
-
type DialTribeContextValue,
|
|
419
|
-
type ApiClientConfig,
|
|
420
|
-
} from '@dialtribe/react-sdk/
|
|
421
|
-
|
|
422
|
-
// Format seconds to HH:MM:SS or MM:SS
|
|
423
|
-
const timeString = formatTime(3665); // "1:01:05"
|
|
424
|
-
|
|
425
|
-
// Build CloudFront URL for HLS segments
|
|
426
|
-
const hlsUrl = buildBroadcastCdnUrl(appHash, broadcastHash, 'index.m3u8');
|
|
427
|
-
|
|
428
|
-
// HTTP status codes
|
|
429
|
-
if (error.status === HTTP_STATUS.FORBIDDEN) {
|
|
430
|
-
// Handle 403 error
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Use DialTribeClient directly for API calls
|
|
434
|
-
const { sessionToken } = useDialTribe();
|
|
435
|
-
const client = new DialTribeClient({
|
|
436
|
-
sessionToken,
|
|
437
|
-
onTokenRefresh: (newToken, expiresAt) => console.log('Token refreshed'),
|
|
438
|
-
onTokenExpired: () => console.log('Token expired'),
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
const broadcasts = await client.getBroadcasts({ limit: 10 });
|
|
442
|
-
const broadcast = await client.getBroadcast(123);
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
#### Migration Guide (alpha.4 → alpha.5)
|
|
446
|
-
|
|
447
|
-
**Breaking Changes:**
|
|
448
|
-
|
|
449
|
-
1. **Session Token Required** - All SDK usage now requires wrapping your app with `DialTribeProvider`
|
|
450
|
-
2. **No More `apiConfig` Prop** - API endpoints are now fixed to `https://dialtribe.com/api/public/v1`
|
|
451
|
-
3. **Added `isOpen` Prop** - `BroadcastPlayerModal` now requires `isOpen` boolean prop
|
|
452
|
-
4. **Removed Types** - `BroadcastPlayerApiConfig` and `DEFAULT_API_CONFIG` are no longer exported
|
|
453
|
-
5. **Removed Hooks** - `useSessionToken` and `useAuthenticatedFetch` are deprecated
|
|
454
|
-
|
|
455
|
-
**Before (alpha.4):**
|
|
456
|
-
```tsx
|
|
457
|
-
import { BroadcastPlayerModal } from '@dialtribe/react-sdk/
|
|
458
|
-
|
|
459
|
-
<BroadcastPlayerModal
|
|
460
|
-
broadcast={broadcast}
|
|
461
|
-
onClose={() => setShow(false)}
|
|
462
|
-
apiConfig={{
|
|
463
|
-
sessionToken,
|
|
464
|
-
playbackEndpoint: '/api/play',
|
|
465
|
-
onSessionTokenRefresh: (token) => setToken(token),
|
|
466
|
-
}}
|
|
467
|
-
/>
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
**After (alpha.5):**
|
|
471
|
-
```tsx
|
|
472
|
-
import {
|
|
473
|
-
DialTribeProvider,
|
|
474
|
-
BroadcastPlayerModal,
|
|
475
|
-
} from '@dialtribe/react-sdk/
|
|
476
|
-
|
|
477
|
-
// 1. Wrap app with provider (once, at root)
|
|
478
|
-
<DialTribeProvider
|
|
479
|
-
sessionToken={sessionToken}
|
|
480
|
-
onTokenRefresh={(token) => setToken(token)}
|
|
481
|
-
onTokenExpired={() => handleExpired()}
|
|
482
|
-
>
|
|
483
|
-
<App />
|
|
484
|
-
</DialTribeProvider>
|
|
485
|
-
|
|
486
|
-
// 2. Use modal anywhere in your app
|
|
487
|
-
<BroadcastPlayerModal
|
|
488
|
-
broadcast={broadcast}
|
|
489
|
-
isOpen={showModal}
|
|
490
|
-
onClose={() => setShowModal(false)}
|
|
491
|
-
/>
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
**New Features in alpha.5:**
|
|
495
|
-
- ✅ `BroadcastPlayer` - Inline player component (no modal)
|
|
496
|
-
- ✅ `useDialTribe()` - Access session token from any component
|
|
497
|
-
- ✅ `DialTribeClient` - Direct API client for custom integrations
|
|
498
|
-
- ✅ Fixed API endpoints - No configuration needed, works out of the box
|
|
499
|
-
|
|
500
|
-
## Development
|
|
501
|
-
|
|
502
|
-
### Build
|
|
503
|
-
|
|
504
|
-
```bash
|
|
505
|
-
yarn build
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
### Watch mode
|
|
509
|
-
|
|
510
|
-
```bash
|
|
511
|
-
yarn dev
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
### Type checking
|
|
515
|
-
|
|
516
|
-
```bash
|
|
517
|
-
yarn typecheck
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
## Roadmap
|
|
521
|
-
|
|
522
|
-
This SDK is being built to provide:
|
|
523
|
-
|
|
524
|
-
- Live broadcast playback
|
|
525
|
-
- Clip creation and playback
|
|
526
|
-
- File uploads (direct and URL-based)
|
|
527
|
-
- API key authentication
|
|
528
|
-
- Media player components
|
|
529
|
-
- Upload management UI
|
|
530
|
-
|
|
531
|
-
See [NPM_SDK.md](../dialtribe/docs/_TODO/NPM_SDK.md) for the complete implementation plan.
|
|
532
|
-
|
|
533
|
-
## License
|
|
534
|
-
|
|
535
|
-
MIT
|
|
1
|
+
# @dialtribe/react-sdk
|
|
2
|
+
|
|
3
|
+
Official DialTribe SDK for React
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
**Alpha 5** (v0.1.0-alpha.5) - 🏆 **A+ Production Quality** - Session token architecture, WCAG 2.1 AA accessible, keyboard navigation, professional error handling.
|
|
8
|
+
|
|
9
|
+
**Code Quality:** **A+** ⭐⭐⭐⭐⭐
|
|
10
|
+
|
|
11
|
+
**Recent Updates (December 2025):**
|
|
12
|
+
- ✅ Refactored to API-first architecture with session tokens
|
|
13
|
+
- ✅ New `BroadcastPlayer` component for inline usage
|
|
14
|
+
- ✅ Simplified `BroadcastPlayerModal` to thin wrapper
|
|
15
|
+
- ✅ Added `DialTribeProvider` context for token management
|
|
16
|
+
- ✅ All API calls through `DialTribeClient`
|
|
17
|
+
- ✅ **WCAG 2.1 Level AA accessible** - Full keyboard navigation
|
|
18
|
+
- ✅ **Production-safe** - No console logs, secure endpoints
|
|
19
|
+
- ✅ **Professional UX** - Specific error messages, retry mechanism
|
|
20
|
+
- ✅ **Mobile-first** - Fully responsive design
|
|
21
|
+
|
|
22
|
+
**Breaking Changes from alpha.4** - See [Migration Guide](#migration-guide-alpha4--alpha5) below.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @dialtribe/react-sdk
|
|
28
|
+
# or
|
|
29
|
+
yarn add @dialtribe/react-sdk
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Styling Setup
|
|
33
|
+
|
|
34
|
+
The SDK components use Tailwind CSS classes. Choose the option that fits your project:
|
|
35
|
+
|
|
36
|
+
#### Option 1: No Tailwind in Your Project (Simple)
|
|
37
|
+
|
|
38
|
+
Just import the pre-built CSS file once in your app:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// In your main app file (e.g., App.tsx, _app.tsx, or index.tsx)
|
|
42
|
+
import '@dialtribe/react-sdk/styles.css';
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**That's it!** The SDK includes all necessary styles (17KB minified).
|
|
46
|
+
|
|
47
|
+
#### Option 2: You Already Use Tailwind (Recommended)
|
|
48
|
+
|
|
49
|
+
If your project already has Tailwind CSS configured, you **don't need to import the CSS file**. Instead, configure Tailwind to scan the SDK files:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
// tailwind.config.js
|
|
53
|
+
module.exports = {
|
|
54
|
+
content: [
|
|
55
|
+
'./src/**/*.{js,jsx,ts,tsx}',
|
|
56
|
+
'./node_modules/@dialtribe/react-sdk/dist/**/*.{js,mjs}', // Add this line
|
|
57
|
+
],
|
|
58
|
+
theme: {
|
|
59
|
+
extend: {},
|
|
60
|
+
},
|
|
61
|
+
plugins: [],
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This way, Tailwind will automatically generate only the classes the SDK needs, keeping your bundle optimized.
|
|
66
|
+
|
|
67
|
+
## Quick Start
|
|
68
|
+
|
|
69
|
+
### Import Options
|
|
70
|
+
|
|
71
|
+
You can import components in two ways:
|
|
72
|
+
|
|
73
|
+
**Option 1: Barrel import (imports everything)**
|
|
74
|
+
```tsx
|
|
75
|
+
import { HelloWorld } from '@dialtribe/react-sdk';
|
|
76
|
+
|
|
77
|
+
function App() {
|
|
78
|
+
return <HelloWorld name="Developer" />;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Option 2: Subpath import (tree-shakeable, smaller bundle)**
|
|
83
|
+
```tsx
|
|
84
|
+
import { HelloWorld } from '@dialtribe/react-sdk/hello-world';
|
|
85
|
+
|
|
86
|
+
function App() {
|
|
87
|
+
return <HelloWorld name="Developer" />;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
For optimal bundle size, we recommend using subpath imports when importing individual components.
|
|
92
|
+
|
|
93
|
+
### Bundle Sizes
|
|
94
|
+
|
|
95
|
+
Current bundle sizes (alpha.5, December 2025 - A+ Quality):
|
|
96
|
+
|
|
97
|
+
| Import Method | Minified | Gzipped | Notes |
|
|
98
|
+
|--------------|----------|---------|-------|
|
|
99
|
+
| `@dialtribe/react-sdk/hello-world` | 756 B | ~448 B | Simple test component |
|
|
100
|
+
| `@dialtribe/react-sdk/dialtribe-player` | 91.16 KB | ~17 KB | Full A+ player + accessibility |
|
|
101
|
+
| `@dialtribe/react-sdk` (barrel) | 91.70 KB | ~17.5 KB | All components |
|
|
102
|
+
| `@dialtribe/react-sdk/styles.css` | 17 KB | 3.8 KB | Required styles (if no Tailwind) |
|
|
103
|
+
|
|
104
|
+
**Total for BroadcastPlayer:** ~20.8 KB gzipped (17 KB JS + 3.8 KB CSS) ✅
|
|
105
|
+
|
|
106
|
+
**What's included in dialtribe-player:**
|
|
107
|
+
- BroadcastPlayer (inline player)
|
|
108
|
+
- BroadcastPlayerModal (modal wrapper)
|
|
109
|
+
- DialTribeProvider (context)
|
|
110
|
+
- DialTribeClient (API client)
|
|
111
|
+
- useDialTribe hook
|
|
112
|
+
- **WCAG 2.1 AA accessibility** (ARIA labels, keyboard nav)
|
|
113
|
+
- **Professional error handling** (specific messages, retry)
|
|
114
|
+
- **Production safety** (debug logging, secure endpoints)
|
|
115
|
+
- All utilities (formatTime, buildBroadcastCdnUrl, etc.)
|
|
116
|
+
|
|
117
|
+
**Why subpath imports matter:** The barrel import (`@dialtribe/react-sdk`) includes all components. Using subpath imports like `/dialtribe-player` ensures you only bundle what you need.
|
|
118
|
+
|
|
119
|
+
## Components
|
|
120
|
+
|
|
121
|
+
### BroadcastPlayer
|
|
122
|
+
|
|
123
|
+
Full-featured media player for DialTribe broadcasts with support for:
|
|
124
|
+
- **Live HLS streaming** - Real-time playback with CloudFront CDN
|
|
125
|
+
- **MP4 video playback** - Recorded video with presigned S3 URLs
|
|
126
|
+
- **MP3 audio playback** - Recorded audio with animated waveform visualization
|
|
127
|
+
- **Transcripts** - Word-level timestamps with clickable seeking
|
|
128
|
+
- **Clip creation** - Optional clip creator integration (via render prop)
|
|
129
|
+
- **Audience tracking** - Session management and playback analytics
|
|
130
|
+
- **Auto URL refresh** - Proactive presigned URL renewal before expiration
|
|
131
|
+
- **Session token authentication** - Secure, revocable access tokens
|
|
132
|
+
|
|
133
|
+
#### Architecture (alpha.5+)
|
|
134
|
+
|
|
135
|
+
The SDK uses an **API-first architecture** with session token authentication:
|
|
136
|
+
|
|
137
|
+
1. **Your Backend** - Stores your `DIALTRIBE_SECRET_KEY` securely
|
|
138
|
+
2. **Session Token** - Your backend requests a session token from DialTribe API
|
|
139
|
+
3. **DialTribeProvider** - Wrap your app with the provider (passes token to all components)
|
|
140
|
+
4. **BroadcastPlayer** - Inline player component (you control the layout)
|
|
141
|
+
5. **BroadcastPlayerModal** - Pre-built modal wrapper (ready to use)
|
|
142
|
+
|
|
143
|
+
**Important:** Make sure you've completed the [Styling Setup](#styling-setup) above.
|
|
144
|
+
|
|
145
|
+
#### Step 1: Get Session Token from Your Backend
|
|
146
|
+
|
|
147
|
+
First, create an API endpoint in your backend to generate session tokens:
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
// Backend: Express.js endpoint example
|
|
151
|
+
app.post('/api/dialtribe/session', async (req, res) => {
|
|
152
|
+
// Verify user is authenticated in your system
|
|
153
|
+
if (!req.user) {
|
|
154
|
+
return res.status(401).json({ error: 'Unauthorized' });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Request session token from DialTribe
|
|
158
|
+
const response = await fetch('https://dialtribe.com/api/public/v1/sessions', {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
headers: {
|
|
161
|
+
'Content-Type': 'application/json',
|
|
162
|
+
'Authorization': `Bearer ${process.env.DIALTRIBE_SECRET_KEY}`, // Keep this secret!
|
|
163
|
+
},
|
|
164
|
+
body: JSON.stringify({
|
|
165
|
+
externalUserId: req.user.id,
|
|
166
|
+
expiresIn: 21600, // 6 hours
|
|
167
|
+
}),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const { session_token } = await response.json();
|
|
171
|
+
res.json({ sessionToken: session_token });
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### Step 2: Wrap Your App with DialTribeProvider
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import { DialTribeProvider } from '@dialtribe/react-sdk/dialtribe-player';
|
|
179
|
+
import '@dialtribe/react-sdk/styles.css';
|
|
180
|
+
import { useState, useEffect } from 'react';
|
|
181
|
+
|
|
182
|
+
function Root() {
|
|
183
|
+
const [sessionToken, setSessionToken] = useState<string | null>(null);
|
|
184
|
+
|
|
185
|
+
// Get session token from YOUR backend
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
fetch('/api/dialtribe/session', { credentials: 'include' })
|
|
188
|
+
.then(res => res.json())
|
|
189
|
+
.then(data => setSessionToken(data.sessionToken))
|
|
190
|
+
.catch(err => console.error('Failed to get session token:', err));
|
|
191
|
+
}, []);
|
|
192
|
+
|
|
193
|
+
if (!sessionToken) return <div>Loading...</div>;
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<DialTribeProvider
|
|
197
|
+
sessionToken={sessionToken}
|
|
198
|
+
onTokenRefresh={(newToken, expiresAt) => {
|
|
199
|
+
console.log('Token refreshed, expires at:', expiresAt);
|
|
200
|
+
setSessionToken(newToken);
|
|
201
|
+
}}
|
|
202
|
+
onTokenExpired={() => {
|
|
203
|
+
console.error('Token expired, redirecting to login...');
|
|
204
|
+
window.location.href = '/login';
|
|
205
|
+
}}
|
|
206
|
+
>
|
|
207
|
+
<App />
|
|
208
|
+
</DialTribeProvider>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### Step 3: Fetch Broadcasts from DialTribe API
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
import { useDialTribe } from '@dialtribe/react-sdk/dialtribe-player';
|
|
217
|
+
import { useState, useEffect } from 'react';
|
|
218
|
+
|
|
219
|
+
function App() {
|
|
220
|
+
const { sessionToken } = useDialTribe();
|
|
221
|
+
const [broadcasts, setBroadcasts] = useState([]);
|
|
222
|
+
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
// Fetch broadcasts from DialTribe API
|
|
225
|
+
fetch('https://dialtribe.com/api/public/v1/broadcasts', {
|
|
226
|
+
headers: { 'Authorization': `Bearer ${sessionToken}` }
|
|
227
|
+
})
|
|
228
|
+
.then(res => res.json())
|
|
229
|
+
.then(data => setBroadcasts(data.broadcasts))
|
|
230
|
+
.catch(err => console.error('Failed to fetch broadcasts:', err));
|
|
231
|
+
}, [sessionToken]);
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<div>
|
|
235
|
+
<h1>My Broadcasts</h1>
|
|
236
|
+
{broadcasts.map(broadcast => (
|
|
237
|
+
<BroadcastCard key={broadcast.id} broadcast={broadcast} />
|
|
238
|
+
))}
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### Step 4: Use the Player Component
|
|
245
|
+
|
|
246
|
+
**Option A: Built-in Modal (Recommended for Quick Setup)**
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
import { BroadcastPlayerModal } from '@dialtribe/react-sdk/dialtribe-player';
|
|
250
|
+
import { useState } from 'react';
|
|
251
|
+
|
|
252
|
+
function BroadcastCard({ broadcast }) {
|
|
253
|
+
const [showModal, setShowModal] = useState(false);
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<>
|
|
257
|
+
<button onClick={() => setShowModal(true)}>
|
|
258
|
+
Watch: {broadcast.streamKeyRecord?.foreignName}
|
|
259
|
+
</button>
|
|
260
|
+
|
|
261
|
+
<BroadcastPlayerModal
|
|
262
|
+
broadcast={broadcast}
|
|
263
|
+
isOpen={showModal}
|
|
264
|
+
onClose={() => setShowModal(false)}
|
|
265
|
+
appId="your-app-id"
|
|
266
|
+
contentId={broadcast.id}
|
|
267
|
+
/>
|
|
268
|
+
</>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Option B: Inline Player (Custom Layout)**
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
import { BroadcastPlayer } from '@dialtribe/react-sdk/dialtribe-player';
|
|
277
|
+
|
|
278
|
+
function CustomVideoPage({ broadcast }) {
|
|
279
|
+
return (
|
|
280
|
+
<div className="custom-layout">
|
|
281
|
+
<header>
|
|
282
|
+
<h1>{broadcast.streamKeyRecord?.foreignName}</h1>
|
|
283
|
+
<p>Your custom header</p>
|
|
284
|
+
</header>
|
|
285
|
+
|
|
286
|
+
<BroadcastPlayer
|
|
287
|
+
broadcast={broadcast}
|
|
288
|
+
appId="your-app-id"
|
|
289
|
+
contentId={broadcast.id}
|
|
290
|
+
/>
|
|
291
|
+
|
|
292
|
+
<footer>
|
|
293
|
+
<p>Your custom footer</p>
|
|
294
|
+
</footer>
|
|
295
|
+
</div>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### Why Session Tokens?
|
|
301
|
+
|
|
302
|
+
**Benefits of the Session Token Architecture:**
|
|
303
|
+
- ✅ **Secure** - Secret keys never exposed to frontend
|
|
304
|
+
- ✅ **Scoped** - Users can only watch content, not manage it
|
|
305
|
+
- ✅ **Automatic** - Tokens refresh transparently during playback
|
|
306
|
+
- ✅ **Revocable** - Invalidate access instantly if needed
|
|
307
|
+
- ✅ **Trackable** - Know who's watching what
|
|
308
|
+
|
|
309
|
+
**All API calls now use fixed DialTribe endpoints** - no custom endpoint configuration needed.
|
|
310
|
+
|
|
311
|
+
#### Clip Creator Integration
|
|
312
|
+
|
|
313
|
+
You can provide your own clip creator component via render prop:
|
|
314
|
+
|
|
315
|
+
```tsx
|
|
316
|
+
<BroadcastPlayerModal
|
|
317
|
+
broadcast={broadcast}
|
|
318
|
+
isOpen={showModal}
|
|
319
|
+
onClose={() => setShowModal(false)}
|
|
320
|
+
appId="your-app-id"
|
|
321
|
+
contentId={broadcast.id}
|
|
322
|
+
renderClipCreator={({ isOpen, onClose, sourceVideoUrl, sourceDuration, onPauseParent }) => (
|
|
323
|
+
<YourClipCreator
|
|
324
|
+
isOpen={isOpen}
|
|
325
|
+
onClose={onClose}
|
|
326
|
+
videoUrl={sourceVideoUrl}
|
|
327
|
+
duration={sourceDuration}
|
|
328
|
+
onPause={onPauseParent}
|
|
329
|
+
/>
|
|
330
|
+
)}
|
|
331
|
+
/>
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### Keyboard Shortcuts
|
|
335
|
+
|
|
336
|
+
The player supports keyboard shortcuts when `enableKeyboardShortcuts={true}` is passed:
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
<BroadcastPlayer
|
|
340
|
+
broadcast={broadcast}
|
|
341
|
+
enableKeyboardShortcuts={true}
|
|
342
|
+
/>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Available Shortcuts:**
|
|
346
|
+
|
|
347
|
+
| Key | Action |
|
|
348
|
+
|-----|--------|
|
|
349
|
+
| `Space` or `K` | Play/Pause |
|
|
350
|
+
| `←` (Left Arrow) | Seek backward 5 seconds |
|
|
351
|
+
| `→` (Right Arrow) | Seek forward 5 seconds |
|
|
352
|
+
| `↑` (Up Arrow) | Increase volume 10% |
|
|
353
|
+
| `↓` (Down Arrow) | Decrease volume 10% |
|
|
354
|
+
| `M` | Toggle mute |
|
|
355
|
+
| `F` | Toggle fullscreen (video only) |
|
|
356
|
+
| `0` or `Home` | Jump to beginning |
|
|
357
|
+
| `End` | Jump to end |
|
|
358
|
+
|
|
359
|
+
**Important Notes:**
|
|
360
|
+
- Keyboard shortcuts are **disabled by default** (`enableKeyboardShortcuts={false}`)
|
|
361
|
+
- Shortcuts don't interfere with typing in input fields or textareas
|
|
362
|
+
- Shortcuts are ignored when modifier keys (Ctrl, Cmd, Alt) are pressed
|
|
363
|
+
- The player is WCAG 2.1 Level AA accessible with or without keyboard shortcuts enabled
|
|
364
|
+
|
|
365
|
+
#### Props
|
|
366
|
+
|
|
367
|
+
**BroadcastPlayerModal** (Modal Wrapper)
|
|
368
|
+
|
|
369
|
+
| Prop | Type | Required | Description |
|
|
370
|
+
|------|------|----------|-------------|
|
|
371
|
+
| `broadcast` | `Broadcast` | Yes | Broadcast data from DialTribe API |
|
|
372
|
+
| `isOpen` | `boolean` | Yes | Whether modal is visible |
|
|
373
|
+
| `onClose` | `() => void` | Yes | Callback when modal should close |
|
|
374
|
+
| `appId` | `string` | No | App ID for clip creation and tracking |
|
|
375
|
+
| `contentId` | `number` | No | Content ID for clip creation and tracking |
|
|
376
|
+
| `foreignId` | `string` | No | Partner system user ID for analytics |
|
|
377
|
+
| `foreignTier` | `string` | No | User tier (e.g., 'guest', 'member', 'subscriber_gold') |
|
|
378
|
+
| `renderClipCreator` | `(props) => ReactNode` | No | Optional clip creator render prop |
|
|
379
|
+
| `className` | `string` | No | Custom CSS classes for the player |
|
|
380
|
+
| `enableKeyboardShortcuts` | `boolean` | No | Enable keyboard shortcuts (default: false) |
|
|
381
|
+
|
|
382
|
+
**BroadcastPlayer** (Inline Player)
|
|
383
|
+
|
|
384
|
+
| Prop | Type | Required | Description |
|
|
385
|
+
|------|------|----------|-------------|
|
|
386
|
+
| `broadcast` | `Broadcast` | Yes | Broadcast data from DialTribe API |
|
|
387
|
+
| `appId` | `string` | No | App ID for clip creation and tracking |
|
|
388
|
+
| `contentId` | `number` | No | Content ID for clip creation and tracking |
|
|
389
|
+
| `foreignId` | `string` | No | Partner system user ID for analytics |
|
|
390
|
+
| `foreignTier` | `string` | No | User tier (e.g., 'guest', 'member', 'subscriber_gold') |
|
|
391
|
+
| `renderClipCreator` | `(props) => ReactNode` | No | Optional clip creator render prop |
|
|
392
|
+
| `onError` | `(error: Error) => void` | No | Called when player encounters error |
|
|
393
|
+
| `className` | `string` | No | Custom CSS classes for the player |
|
|
394
|
+
| `enableKeyboardShortcuts` | `boolean` | No | Enable keyboard shortcuts (default: false) |
|
|
395
|
+
|
|
396
|
+
#### Utilities
|
|
397
|
+
|
|
398
|
+
The dialtribe-player module also exports useful utilities:
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
import {
|
|
402
|
+
// Context & Client
|
|
403
|
+
DialTribeProvider,
|
|
404
|
+
useDialTribe,
|
|
405
|
+
DialTribeClient,
|
|
406
|
+
|
|
407
|
+
// Components
|
|
408
|
+
BroadcastPlayer,
|
|
409
|
+
BroadcastPlayerModal,
|
|
410
|
+
|
|
411
|
+
// Utilities
|
|
412
|
+
formatTime,
|
|
413
|
+
buildBroadcastCdnUrl,
|
|
414
|
+
HTTP_STATUS,
|
|
415
|
+
|
|
416
|
+
// Types
|
|
417
|
+
type Broadcast,
|
|
418
|
+
type DialTribeContextValue,
|
|
419
|
+
type ApiClientConfig,
|
|
420
|
+
} from '@dialtribe/react-sdk/dialtribe-player';
|
|
421
|
+
|
|
422
|
+
// Format seconds to HH:MM:SS or MM:SS
|
|
423
|
+
const timeString = formatTime(3665); // "1:01:05"
|
|
424
|
+
|
|
425
|
+
// Build CloudFront URL for HLS segments
|
|
426
|
+
const hlsUrl = buildBroadcastCdnUrl(appHash, broadcastHash, 'index.m3u8');
|
|
427
|
+
|
|
428
|
+
// HTTP status codes
|
|
429
|
+
if (error.status === HTTP_STATUS.FORBIDDEN) {
|
|
430
|
+
// Handle 403 error
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Use DialTribeClient directly for API calls
|
|
434
|
+
const { sessionToken } = useDialTribe();
|
|
435
|
+
const client = new DialTribeClient({
|
|
436
|
+
sessionToken,
|
|
437
|
+
onTokenRefresh: (newToken, expiresAt) => console.log('Token refreshed'),
|
|
438
|
+
onTokenExpired: () => console.log('Token expired'),
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const broadcasts = await client.getBroadcasts({ limit: 10 });
|
|
442
|
+
const broadcast = await client.getBroadcast(123);
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
#### Migration Guide (alpha.4 → alpha.5)
|
|
446
|
+
|
|
447
|
+
**Breaking Changes:**
|
|
448
|
+
|
|
449
|
+
1. **Session Token Required** - All SDK usage now requires wrapping your app with `DialTribeProvider`
|
|
450
|
+
2. **No More `apiConfig` Prop** - API endpoints are now fixed to `https://dialtribe.com/api/public/v1`
|
|
451
|
+
3. **Added `isOpen` Prop** - `BroadcastPlayerModal` now requires `isOpen` boolean prop
|
|
452
|
+
4. **Removed Types** - `BroadcastPlayerApiConfig` and `DEFAULT_API_CONFIG` are no longer exported
|
|
453
|
+
5. **Removed Hooks** - `useSessionToken` and `useAuthenticatedFetch` are deprecated
|
|
454
|
+
|
|
455
|
+
**Before (alpha.4):**
|
|
456
|
+
```tsx
|
|
457
|
+
import { BroadcastPlayerModal } from '@dialtribe/react-sdk/dialtribe-player';
|
|
458
|
+
|
|
459
|
+
<BroadcastPlayerModal
|
|
460
|
+
broadcast={broadcast}
|
|
461
|
+
onClose={() => setShow(false)}
|
|
462
|
+
apiConfig={{
|
|
463
|
+
sessionToken,
|
|
464
|
+
playbackEndpoint: '/api/play',
|
|
465
|
+
onSessionTokenRefresh: (token) => setToken(token),
|
|
466
|
+
}}
|
|
467
|
+
/>
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**After (alpha.5):**
|
|
471
|
+
```tsx
|
|
472
|
+
import {
|
|
473
|
+
DialTribeProvider,
|
|
474
|
+
BroadcastPlayerModal,
|
|
475
|
+
} from '@dialtribe/react-sdk/dialtribe-player';
|
|
476
|
+
|
|
477
|
+
// 1. Wrap app with provider (once, at root)
|
|
478
|
+
<DialTribeProvider
|
|
479
|
+
sessionToken={sessionToken}
|
|
480
|
+
onTokenRefresh={(token) => setToken(token)}
|
|
481
|
+
onTokenExpired={() => handleExpired()}
|
|
482
|
+
>
|
|
483
|
+
<App />
|
|
484
|
+
</DialTribeProvider>
|
|
485
|
+
|
|
486
|
+
// 2. Use modal anywhere in your app
|
|
487
|
+
<BroadcastPlayerModal
|
|
488
|
+
broadcast={broadcast}
|
|
489
|
+
isOpen={showModal}
|
|
490
|
+
onClose={() => setShowModal(false)}
|
|
491
|
+
/>
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**New Features in alpha.5:**
|
|
495
|
+
- ✅ `BroadcastPlayer` - Inline player component (no modal)
|
|
496
|
+
- ✅ `useDialTribe()` - Access session token from any component
|
|
497
|
+
- ✅ `DialTribeClient` - Direct API client for custom integrations
|
|
498
|
+
- ✅ Fixed API endpoints - No configuration needed, works out of the box
|
|
499
|
+
|
|
500
|
+
## Development
|
|
501
|
+
|
|
502
|
+
### Build
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
yarn build
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Watch mode
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
yarn dev
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Type checking
|
|
515
|
+
|
|
516
|
+
```bash
|
|
517
|
+
yarn typecheck
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
## Roadmap
|
|
521
|
+
|
|
522
|
+
This SDK is being built to provide:
|
|
523
|
+
|
|
524
|
+
- Live broadcast playback
|
|
525
|
+
- Clip creation and playback
|
|
526
|
+
- File uploads (direct and URL-based)
|
|
527
|
+
- API key authentication
|
|
528
|
+
- Media player components
|
|
529
|
+
- Upload management UI
|
|
530
|
+
|
|
531
|
+
See [NPM_SDK.md](../dialtribe/docs/_TODO/NPM_SDK.md) for the complete implementation plan.
|
|
532
|
+
|
|
533
|
+
## License
|
|
534
|
+
|
|
535
|
+
MIT
|