@flowplayer/spins 0.6.0 → 0.7.0
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/README.md +316 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A TypeScript library for creating Flowplayer playlist components optimized for short-form video content like Reels and Shorts.
|
|
4
4
|
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
5
7
|
```typescript
|
|
6
8
|
import { createSpins } from "@flowplayer/spins";
|
|
7
9
|
|
|
8
|
-
const spinsContainer = createSpins({
|
|
10
|
+
const spinsContainer = createSpins({
|
|
9
11
|
playlist: "your-playlist-id",
|
|
10
12
|
token: "your-token",
|
|
11
13
|
lang: "en",
|
|
@@ -13,30 +15,6 @@ const spinsContainer = createSpins({
|
|
|
13
15
|
document.body.appendChild(spinsContainer);
|
|
14
16
|
```
|
|
15
17
|
|
|
16
|
-
### React Usage
|
|
17
|
-
|
|
18
|
-
```tsx
|
|
19
|
-
import { createSpins, type SpinsConfig } from "@flowplayer/spins";
|
|
20
|
-
import { useEffect, useRef } from "react";
|
|
21
|
-
|
|
22
|
-
function SpinsPlayer({ playlistId }: { playlistId: string }) {
|
|
23
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
if (containerRef.current) {
|
|
27
|
-
const spinsContainer = createSpins({
|
|
28
|
-
playlist: playlistId,
|
|
29
|
-
token: "your-token",
|
|
30
|
-
lang: "en",
|
|
31
|
-
});
|
|
32
|
-
containerRef.current.appendChild(spinsContainer);
|
|
33
|
-
}
|
|
34
|
-
}, [playlistId]);
|
|
35
|
-
|
|
36
|
-
return <div ref={containerRef} className="spins-container" />;
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
18
|
## Features
|
|
41
19
|
|
|
42
20
|
- 🎯 **Type-safe** - Built with TypeScript and Zod schema validation
|
|
@@ -56,20 +34,76 @@ bun add @flowplayer/spins
|
|
|
56
34
|
|
|
57
35
|
## Usage
|
|
58
36
|
|
|
37
|
+
### Basic Usage
|
|
38
|
+
|
|
59
39
|
```typescript
|
|
60
|
-
import {
|
|
40
|
+
import { createSpins, type SpinsConfig } from "@flowplayer/spins";
|
|
61
41
|
|
|
62
|
-
// Create a playlist component for short-form content
|
|
63
42
|
const config: SpinsConfig = {
|
|
64
43
|
playlist: "your-playlist-id", // or array of SpinItem objects
|
|
65
44
|
token: "your-flowplayer-token",
|
|
66
45
|
lang: "en"
|
|
67
46
|
};
|
|
68
47
|
|
|
69
|
-
const spinsContainer =
|
|
48
|
+
const spinsContainer = createSpins(config);
|
|
70
49
|
document.body.appendChild(spinsContainer);
|
|
71
50
|
```
|
|
72
51
|
|
|
52
|
+
### React Integration
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { createSpins, type SpinsConfig } from "@flowplayer/spins";
|
|
56
|
+
import { useEffect, useRef } from "react";
|
|
57
|
+
|
|
58
|
+
function SpinsPlayer({ playlistId }: { playlistId: string }) {
|
|
59
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!containerRef.current) return;
|
|
63
|
+
|
|
64
|
+
const spinsContainer = createSpins({
|
|
65
|
+
playlist: playlistId,
|
|
66
|
+
token: "your-token",
|
|
67
|
+
lang: "en",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
containerRef.current.appendChild(spinsContainer);
|
|
71
|
+
|
|
72
|
+
return () => {
|
|
73
|
+
spinsContainer.remove();
|
|
74
|
+
};
|
|
75
|
+
}, [playlistId]);
|
|
76
|
+
|
|
77
|
+
return <div ref={containerRef} className="spins-container" />;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### With Custom Playlist
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { createSpins, type SpinItem } from "@flowplayer/spins";
|
|
85
|
+
|
|
86
|
+
const customPlaylist: SpinItem[] = [
|
|
87
|
+
{
|
|
88
|
+
url: "https://example.com/video1.mp4",
|
|
89
|
+
title: "First Video",
|
|
90
|
+
poster: "https://example.com/poster1.jpg",
|
|
91
|
+
description: "Description of first video"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
url: { src: "https://example.com/video2.mp4", type: "video/mp4" },
|
|
95
|
+
title: "Second Video",
|
|
96
|
+
poster: "https://example.com/poster2.jpg"
|
|
97
|
+
}
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
const spinsContainer = createSpins({
|
|
101
|
+
playlist: customPlaylist,
|
|
102
|
+
token: "your-token",
|
|
103
|
+
lang: "en"
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
73
107
|
## Short-Form Video Content
|
|
74
108
|
|
|
75
109
|
This library is specifically designed to handle short-form video playlists, making it easy to create engaging vertical video experiences similar to:
|
|
@@ -86,78 +120,298 @@ The component automatically handles playlist loading and provides the structure
|
|
|
86
120
|
|
|
87
121
|
Creates a Flowplayer playlist component container optimized for short-form video content.
|
|
88
122
|
|
|
89
|
-
####
|
|
123
|
+
#### Configuration Options
|
|
90
124
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
125
|
+
```typescript
|
|
126
|
+
interface SpinsConfig {
|
|
127
|
+
playlist: string | Array<SpinItem>; // Playlist ID or array of SpinItem
|
|
128
|
+
token?: string; // Your Flowplayer token
|
|
129
|
+
lang?: string; // Language code (e.g., "en", "es")
|
|
130
|
+
ui?: number; // UI configuration bitmask
|
|
131
|
+
ima?: PlayerIMAConfig; // IMA ads configuration
|
|
132
|
+
adsFrequency?: number; // Show ads every Nth spin (default: 10)
|
|
133
|
+
share?: ShareConfig; // Share plugin configuration
|
|
134
|
+
consent?: number; // Consent plugin configuration
|
|
135
|
+
plugins?: string[]; // List of plugins to enable
|
|
136
|
+
}
|
|
137
|
+
```
|
|
94
138
|
|
|
95
139
|
#### Returns
|
|
96
140
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
141
|
+
A `SpinsContainer` instance (extends `HTMLElement`) with the following methods:
|
|
142
|
+
|
|
143
|
+
##### Event Handling
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// Listen to spin lifecycle events
|
|
147
|
+
spinsContainer.on(spinEvents.SPIN_CREATED, (e) => {
|
|
148
|
+
console.log('Spin created:', e.detail.config, e.detail.index);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
spinsContainer.on(spinEvents.SPIN_IN_VIEWPORT, (e) => {
|
|
152
|
+
console.log('Spin in viewport:', e.detail.index);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Listen to player events (media events)
|
|
156
|
+
spinsContainer.onPlayerEvent(mediaEvents.PLAY, (e) => {
|
|
157
|
+
console.log('Video playing:', e.detail.spinIndex, e.detail.currentTime);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
spinsContainer.onPlayerEvent(mediaEvents.ENDED, (e) => {
|
|
161
|
+
console.log('Video ended:', e.detail.spinIndex);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Listen to ad events
|
|
165
|
+
spinsContainer.onPlayerEvent(adEvents.AD_STARTED, (e) => {
|
|
166
|
+
console.log('Ad started:', e.detail.spinIndex);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Remove event listeners
|
|
170
|
+
spinsContainer.off(spinEvents.SPIN_CREATED, handler);
|
|
171
|
+
spinsContainer.offPlayerEvent(mediaEvents.PLAY, handler);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
##### Dynamic Content
|
|
101
175
|
|
|
102
|
-
|
|
176
|
+
```typescript
|
|
177
|
+
// Add more spins dynamically
|
|
178
|
+
spinsContainer.addSpins([
|
|
179
|
+
{
|
|
180
|
+
url: "https://example.com/video3.mp4",
|
|
181
|
+
title: "New Video",
|
|
182
|
+
poster: "https://example.com/poster3.jpg"
|
|
183
|
+
}
|
|
184
|
+
]);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Events
|
|
188
|
+
|
|
189
|
+
#### Spin Events (`spinEvents`)
|
|
103
190
|
|
|
104
191
|
- `SPIN_CREATED` - Fired when a new spin is created
|
|
192
|
+
- `detail.config`: SpinItem configuration
|
|
193
|
+
- `detail.spin`: The spin container HTMLElement
|
|
194
|
+
- `detail.index`: Spin index
|
|
195
|
+
|
|
105
196
|
- `SPIN_IN_VIEWPORT` - Fired when a spin enters the viewport
|
|
197
|
+
- `detail.config`: SpinItem configuration
|
|
198
|
+
- `detail.spin`: The spin container HTMLElement
|
|
199
|
+
- `detail.index`: Spin index
|
|
200
|
+
|
|
201
|
+
#### Media Events (`mediaEvents`)
|
|
202
|
+
|
|
203
|
+
- `PLAY` - Playback started/resumed
|
|
204
|
+
- `PAUSE` - Playback paused
|
|
205
|
+
- `PLAYING` - Actively playing
|
|
206
|
+
- `WAITING` - Buffering
|
|
207
|
+
- `CAN_PLAY` - Ready to play
|
|
208
|
+
- `TIME_UPDATE` - Playback position changed
|
|
209
|
+
- `SEEKING` - User seeking
|
|
210
|
+
- `ENDED` - Playback ended
|
|
211
|
+
|
|
212
|
+
All media events include:
|
|
213
|
+
- `detail.spinIndex`: Index of the spin
|
|
214
|
+
- `detail.currentTime`: Current playback time in seconds
|
|
215
|
+
- `detail.duration`: Video duration in seconds
|
|
216
|
+
- `detail.mediaId`: Media ID (if available)
|
|
217
|
+
- `detail.url`: Full URL of the media
|
|
218
|
+
|
|
219
|
+
#### Ad Events (`adEvents`)
|
|
220
|
+
|
|
221
|
+
- `AD_STARTED` - Ad playback started
|
|
222
|
+
- `AD_COMPLETED` - Ad finished playing
|
|
223
|
+
- `AD_PROGRESS` - Ad playback progress
|
|
224
|
+
- `AD_PAUSED` - Ad paused
|
|
225
|
+
- `AD_RESUMED` - Ad resumed
|
|
226
|
+
- `AD_SKIPPED` - Ad skipped by user
|
|
227
|
+
- `AD_ERROR` - Ad error occurred
|
|
228
|
+
- `AD_REQUEST_ERROR` - Ad request failed
|
|
229
|
+
- `AD_PLAYBACK_ERROR` - Ad playback error
|
|
230
|
+
- `AD_BREAK_COMPLETED` - Ad break finished
|
|
231
|
+
- `AD_TEARDOWN` - Ad cleanup
|
|
232
|
+
- `AD_MUTED` - Ad muted
|
|
233
|
+
- `AD_VOLUME_CHANGED` - Ad volume changed
|
|
234
|
+
- `AD_PREROLL_FINSIHED` - Preroll ad finished
|
|
235
|
+
|
|
236
|
+
All ad events include:
|
|
237
|
+
- `detail.spinIndex`: Index of the spin where the ad is shown
|
|
106
238
|
|
|
107
239
|
## TypeScript Support
|
|
108
240
|
|
|
109
|
-
The library
|
|
241
|
+
The library is fully typed with TypeScript. Import types for full type safety:
|
|
110
242
|
|
|
111
243
|
```typescript
|
|
112
|
-
import {
|
|
244
|
+
import {
|
|
245
|
+
createSpins,
|
|
246
|
+
createAdPlayer,
|
|
247
|
+
spinEvents,
|
|
248
|
+
mediaEvents,
|
|
249
|
+
adEvents,
|
|
250
|
+
type SpinsConfig,
|
|
251
|
+
type SpinItem,
|
|
252
|
+
type SpinsContainer,
|
|
253
|
+
type SpinsAdPlayer,
|
|
254
|
+
type SpinsAdPlayerConfig,
|
|
255
|
+
type SpinEvent,
|
|
256
|
+
type MediaEvent,
|
|
257
|
+
type AdEvent,
|
|
258
|
+
type PlayerEvent,
|
|
259
|
+
type SpinEventData,
|
|
260
|
+
type MediaEventData,
|
|
261
|
+
type AdEventData,
|
|
262
|
+
type PlayerEventsDetailMap,
|
|
263
|
+
type AdEventsDetailMap,
|
|
264
|
+
} from "@flowplayer/spins";
|
|
113
265
|
```
|
|
114
266
|
|
|
115
|
-
###
|
|
267
|
+
### Key Type Definitions
|
|
116
268
|
|
|
117
|
-
|
|
269
|
+
#### `SpinsConfig`
|
|
270
|
+
|
|
271
|
+
Main configuration for creating a spins container:
|
|
118
272
|
|
|
119
273
|
```typescript
|
|
120
274
|
interface SpinsConfig {
|
|
121
275
|
playlist: string | Array<SpinItem>;
|
|
122
|
-
token
|
|
123
|
-
lang
|
|
276
|
+
token?: string;
|
|
277
|
+
lang?: string;
|
|
278
|
+
ui?: number;
|
|
279
|
+
ima?: PlayerIMAConfig;
|
|
280
|
+
adsFrequency?: number;
|
|
281
|
+
share?: ShareConfig;
|
|
282
|
+
consent?: number;
|
|
283
|
+
plugins?: string[];
|
|
124
284
|
}
|
|
125
285
|
```
|
|
126
286
|
|
|
127
|
-
|
|
287
|
+
#### `SpinItem`
|
|
128
288
|
|
|
129
|
-
Individual
|
|
289
|
+
Individual video configuration:
|
|
130
290
|
|
|
131
291
|
```typescript
|
|
132
292
|
interface SpinItem {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
293
|
+
url: string | { src: string; type: string };
|
|
294
|
+
title?: string;
|
|
295
|
+
poster?: string;
|
|
296
|
+
description?: string;
|
|
297
|
+
subtitles?: SubtitlesConfig;
|
|
137
298
|
}
|
|
138
299
|
```
|
|
139
300
|
|
|
140
|
-
|
|
301
|
+
#### `SpinsContainer`
|
|
141
302
|
|
|
142
|
-
The
|
|
143
|
-
You can override styles by targeting the spins container and its children:
|
|
303
|
+
The container element with event handling:
|
|
144
304
|
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
|
|
305
|
+
```typescript
|
|
306
|
+
interface SpinsContainer extends HTMLElement {
|
|
307
|
+
on(event: SpinEvent, handler: (e: CustomEvent<SpinEventData>) => void): void;
|
|
308
|
+
off(event: SpinEvent, handler: (e: CustomEvent<SpinEventData>) => void): void;
|
|
309
|
+
onPlayerEvent<K extends PlayerEvent>(
|
|
310
|
+
event: K,
|
|
311
|
+
handler: (e: CustomEvent<PlayerEventsDetailMap[K]>) => void
|
|
312
|
+
): void;
|
|
313
|
+
offPlayerEvent<K extends PlayerEvent>(
|
|
314
|
+
event: K,
|
|
315
|
+
handler: (e: CustomEvent<PlayerEventsDetailMap[K]>) => void
|
|
316
|
+
): void;
|
|
317
|
+
addSpins(spins: SpinItem[]): void;
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### Event Data Types
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
interface SpinEventData {
|
|
325
|
+
config: SpinItem;
|
|
326
|
+
spin: HTMLElement;
|
|
327
|
+
index: number;
|
|
148
328
|
}
|
|
149
329
|
|
|
150
|
-
|
|
151
|
-
|
|
330
|
+
interface MediaEventData {
|
|
331
|
+
spinIndex: number;
|
|
332
|
+
duration?: number;
|
|
333
|
+
currentTime: number;
|
|
334
|
+
mediaId?: string;
|
|
335
|
+
url: string;
|
|
152
336
|
}
|
|
337
|
+
|
|
338
|
+
type AdEventData<K extends AdEvent> = {
|
|
339
|
+
spinIndex: number;
|
|
340
|
+
} & AdEventsDetailMap[K];
|
|
153
341
|
```
|
|
154
342
|
|
|
155
|
-
##
|
|
343
|
+
## Advanced Usage
|
|
156
344
|
|
|
157
|
-
###
|
|
345
|
+
### Listening to All Events
|
|
158
346
|
|
|
159
|
-
|
|
160
|
-
|
|
347
|
+
```typescript
|
|
348
|
+
import {
|
|
349
|
+
createSpins,
|
|
350
|
+
spinEvents,
|
|
351
|
+
mediaEvents,
|
|
352
|
+
adEvents,
|
|
353
|
+
type SpinEventData,
|
|
354
|
+
type MediaEventData
|
|
355
|
+
} from "@flowplayer/spins";
|
|
356
|
+
|
|
357
|
+
const container = createSpins({
|
|
358
|
+
playlist: "your-playlist-id",
|
|
359
|
+
token: "your-token",
|
|
360
|
+
lang: "en",
|
|
361
|
+
adsFrequency: 5 // Show ads every 5th spin
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Spin lifecycle events
|
|
365
|
+
container.on(spinEvents.SPIN_CREATED, (e) => {
|
|
366
|
+
console.log(`Spin ${e.detail.index} created:`, e.detail.config);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
container.on(spinEvents.SPIN_IN_VIEWPORT, (e) => {
|
|
370
|
+
console.log(`Spin ${e.detail.index} is now visible`);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Media playback events
|
|
374
|
+
container.onPlayerEvent(mediaEvents.PLAY, (e) => {
|
|
375
|
+
console.log(`Playing spin ${e.detail.spinIndex}`);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
container.onPlayerEvent(mediaEvents.TIME_UPDATE, (e) => {
|
|
379
|
+
console.log(`Progress: ${e.detail.currentTime}/${e.detail.duration}`);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
container.onPlayerEvent(mediaEvents.ENDED, (e) => {
|
|
383
|
+
console.log(`Spin ${e.detail.spinIndex} finished`);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Ad events
|
|
387
|
+
container.onPlayerEvent(adEvents.AD_STARTED, (e) => {
|
|
388
|
+
console.log(`Ad started at spin ${e.detail.spinIndex}`);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
container.onPlayerEvent(adEvents.AD_COMPLETED, (e) => {
|
|
392
|
+
console.log(`Ad completed at spin ${e.detail.spinIndex}`);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
document.body.appendChild(container);
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Advertising Configuration
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { createSpins } from "@flowplayer/spins";
|
|
402
|
+
|
|
403
|
+
const container = createSpins({
|
|
404
|
+
playlist: "your-playlist-id",
|
|
405
|
+
token: "your-token",
|
|
406
|
+
lang: "en",
|
|
407
|
+
adsFrequency: 3, // Show ads every 3rd spin
|
|
408
|
+
ima: {
|
|
409
|
+
ads: [{
|
|
410
|
+
adTag: "https://pubads.g.doubleclick.net/gampad/ads?...",
|
|
411
|
+
time: 0
|
|
412
|
+
}]}
|
|
413
|
+
});
|
|
414
|
+
```
|
|
161
415
|
|
|
162
416
|
## Contributing
|
|
163
417
|
|
|
@@ -167,6 +421,7 @@ This project uses:
|
|
|
167
421
|
- **Vitest** with **Playwright** for browser testing
|
|
168
422
|
- **Storybook** for component development and documentation
|
|
169
423
|
- **Lit** for custom web components
|
|
424
|
+
- **Zod** for runtime schema validation
|
|
170
425
|
|
|
171
426
|
## License
|
|
172
427
|
|