@flowplayer/spins 0.6.0 → 0.8.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 +317 -61
- package/dist/index.js +18 -18
- package/dist/types.d.ts +2 -0
- 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,299 @@ 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
|
+
- `SEEKED` - Seeking completed
|
|
211
|
+
- `ENDED` - Playback ended
|
|
212
|
+
|
|
213
|
+
All media events include:
|
|
214
|
+
- `detail.spinIndex`: Index of the spin
|
|
215
|
+
- `detail.currentTime`: Current playback time in seconds
|
|
216
|
+
- `detail.duration`: Video duration in seconds
|
|
217
|
+
- `detail.mediaId`: Media ID (if available)
|
|
218
|
+
- `detail.url`: Full URL of the media
|
|
219
|
+
|
|
220
|
+
#### Ad Events (`adEvents`)
|
|
221
|
+
|
|
222
|
+
- `AD_STARTED` - Ad playback started
|
|
223
|
+
- `AD_COMPLETED` - Ad finished playing
|
|
224
|
+
- `AD_PROGRESS` - Ad playback progress
|
|
225
|
+
- `AD_PAUSED` - Ad paused
|
|
226
|
+
- `AD_RESUMED` - Ad resumed
|
|
227
|
+
- `AD_SKIPPED` - Ad skipped by user
|
|
228
|
+
- `AD_ERROR` - Ad error occurred
|
|
229
|
+
- `AD_REQUEST_ERROR` - Ad request failed
|
|
230
|
+
- `AD_PLAYBACK_ERROR` - Ad playback error
|
|
231
|
+
- `AD_BREAK_COMPLETED` - Ad break finished
|
|
232
|
+
- `AD_TEARDOWN` - Ad cleanup
|
|
233
|
+
- `AD_MUTED` - Ad muted
|
|
234
|
+
- `AD_VOLUME_CHANGED` - Ad volume changed
|
|
235
|
+
- `AD_PREROLL_FINSIHED` - Preroll ad finished
|
|
236
|
+
|
|
237
|
+
All ad events include:
|
|
238
|
+
- `detail.spinIndex`: Index of the spin where the ad is shown
|
|
106
239
|
|
|
107
240
|
## TypeScript Support
|
|
108
241
|
|
|
109
|
-
The library
|
|
242
|
+
The library is fully typed with TypeScript. Import types for full type safety:
|
|
110
243
|
|
|
111
244
|
```typescript
|
|
112
|
-
import {
|
|
245
|
+
import {
|
|
246
|
+
createSpins,
|
|
247
|
+
createAdPlayer,
|
|
248
|
+
spinEvents,
|
|
249
|
+
mediaEvents,
|
|
250
|
+
adEvents,
|
|
251
|
+
type SpinsConfig,
|
|
252
|
+
type SpinItem,
|
|
253
|
+
type SpinsContainer,
|
|
254
|
+
type SpinsAdPlayer,
|
|
255
|
+
type SpinsAdPlayerConfig,
|
|
256
|
+
type SpinEvent,
|
|
257
|
+
type MediaEvent,
|
|
258
|
+
type AdEvent,
|
|
259
|
+
type PlayerEvent,
|
|
260
|
+
type SpinEventData,
|
|
261
|
+
type MediaEventData,
|
|
262
|
+
type AdEventData,
|
|
263
|
+
type PlayerEventsDetailMap,
|
|
264
|
+
type AdEventsDetailMap,
|
|
265
|
+
} from "@flowplayer/spins";
|
|
113
266
|
```
|
|
114
267
|
|
|
115
|
-
###
|
|
268
|
+
### Key Type Definitions
|
|
116
269
|
|
|
117
|
-
|
|
270
|
+
#### `SpinsConfig`
|
|
271
|
+
|
|
272
|
+
Main configuration for creating a spins container:
|
|
118
273
|
|
|
119
274
|
```typescript
|
|
120
275
|
interface SpinsConfig {
|
|
121
276
|
playlist: string | Array<SpinItem>;
|
|
122
|
-
token
|
|
123
|
-
lang
|
|
277
|
+
token?: string;
|
|
278
|
+
lang?: string;
|
|
279
|
+
ui?: number;
|
|
280
|
+
ima?: PlayerIMAConfig;
|
|
281
|
+
adsFrequency?: number;
|
|
282
|
+
share?: ShareConfig;
|
|
283
|
+
consent?: number;
|
|
284
|
+
plugins?: string[];
|
|
124
285
|
}
|
|
125
286
|
```
|
|
126
287
|
|
|
127
|
-
|
|
288
|
+
#### `SpinItem`
|
|
128
289
|
|
|
129
|
-
Individual
|
|
290
|
+
Individual video configuration:
|
|
130
291
|
|
|
131
292
|
```typescript
|
|
132
293
|
interface SpinItem {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
294
|
+
url: string | { src: string; type: string };
|
|
295
|
+
title?: string;
|
|
296
|
+
poster?: string;
|
|
297
|
+
description?: string;
|
|
298
|
+
subtitles?: SubtitlesConfig;
|
|
137
299
|
}
|
|
138
300
|
```
|
|
139
301
|
|
|
140
|
-
|
|
302
|
+
#### `SpinsContainer`
|
|
141
303
|
|
|
142
|
-
The
|
|
143
|
-
You can override styles by targeting the spins container and its children:
|
|
304
|
+
The container element with event handling:
|
|
144
305
|
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
|
|
306
|
+
```typescript
|
|
307
|
+
interface SpinsContainer extends HTMLElement {
|
|
308
|
+
on(event: SpinEvent, handler: (e: CustomEvent<SpinEventData>) => void): void;
|
|
309
|
+
off(event: SpinEvent, handler: (e: CustomEvent<SpinEventData>) => void): void;
|
|
310
|
+
onPlayerEvent<K extends PlayerEvent>(
|
|
311
|
+
event: K,
|
|
312
|
+
handler: (e: CustomEvent<PlayerEventsDetailMap[K]>) => void
|
|
313
|
+
): void;
|
|
314
|
+
offPlayerEvent<K extends PlayerEvent>(
|
|
315
|
+
event: K,
|
|
316
|
+
handler: (e: CustomEvent<PlayerEventsDetailMap[K]>) => void
|
|
317
|
+
): void;
|
|
318
|
+
addSpins(spins: SpinItem[]): void;
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### Event Data Types
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
interface SpinEventData {
|
|
326
|
+
config: SpinItem;
|
|
327
|
+
spin: HTMLElement;
|
|
328
|
+
index: number;
|
|
148
329
|
}
|
|
149
330
|
|
|
150
|
-
|
|
151
|
-
|
|
331
|
+
interface MediaEventData {
|
|
332
|
+
spinIndex: number;
|
|
333
|
+
duration?: number;
|
|
334
|
+
currentTime: number;
|
|
335
|
+
mediaId?: string;
|
|
336
|
+
url: string;
|
|
152
337
|
}
|
|
338
|
+
|
|
339
|
+
type AdEventData<K extends AdEvent> = {
|
|
340
|
+
spinIndex: number;
|
|
341
|
+
} & AdEventsDetailMap[K];
|
|
153
342
|
```
|
|
154
343
|
|
|
155
|
-
##
|
|
344
|
+
## Advanced Usage
|
|
156
345
|
|
|
157
|
-
###
|
|
346
|
+
### Listening to All Events
|
|
158
347
|
|
|
159
|
-
|
|
160
|
-
|
|
348
|
+
```typescript
|
|
349
|
+
import {
|
|
350
|
+
createSpins,
|
|
351
|
+
spinEvents,
|
|
352
|
+
mediaEvents,
|
|
353
|
+
adEvents,
|
|
354
|
+
type SpinEventData,
|
|
355
|
+
type MediaEventData
|
|
356
|
+
} from "@flowplayer/spins";
|
|
357
|
+
|
|
358
|
+
const container = createSpins({
|
|
359
|
+
playlist: "your-playlist-id",
|
|
360
|
+
token: "your-token",
|
|
361
|
+
lang: "en",
|
|
362
|
+
adsFrequency: 5 // Show ads every 5th spin
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Spin lifecycle events
|
|
366
|
+
container.on(spinEvents.SPIN_CREATED, (e) => {
|
|
367
|
+
console.log(`Spin ${e.detail.index} created:`, e.detail.config);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
container.on(spinEvents.SPIN_IN_VIEWPORT, (e) => {
|
|
371
|
+
console.log(`Spin ${e.detail.index} is now visible`);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// Media playback events
|
|
375
|
+
container.onPlayerEvent(mediaEvents.PLAY, (e) => {
|
|
376
|
+
console.log(`Playing spin ${e.detail.spinIndex}`);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
container.onPlayerEvent(mediaEvents.TIME_UPDATE, (e) => {
|
|
380
|
+
console.log(`Progress: ${e.detail.currentTime}/${e.detail.duration}`);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
container.onPlayerEvent(mediaEvents.ENDED, (e) => {
|
|
384
|
+
console.log(`Spin ${e.detail.spinIndex} finished`);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Ad events
|
|
388
|
+
container.onPlayerEvent(adEvents.AD_STARTED, (e) => {
|
|
389
|
+
console.log(`Ad started at spin ${e.detail.spinIndex}`);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
container.onPlayerEvent(adEvents.AD_COMPLETED, (e) => {
|
|
393
|
+
console.log(`Ad completed at spin ${e.detail.spinIndex}`);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
document.body.appendChild(container);
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Advertising Configuration
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
import { createSpins } from "@flowplayer/spins";
|
|
403
|
+
|
|
404
|
+
const container = createSpins({
|
|
405
|
+
playlist: "your-playlist-id",
|
|
406
|
+
token: "your-token",
|
|
407
|
+
lang: "en",
|
|
408
|
+
adsFrequency: 3, // Show ads every 3rd spin
|
|
409
|
+
ima: {
|
|
410
|
+
ads: [{
|
|
411
|
+
adTag: "https://pubads.g.doubleclick.net/gampad/ads?...",
|
|
412
|
+
time: 0
|
|
413
|
+
}]}
|
|
414
|
+
});
|
|
415
|
+
```
|
|
161
416
|
|
|
162
417
|
## Contributing
|
|
163
418
|
|
|
@@ -167,6 +422,7 @@ This project uses:
|
|
|
167
422
|
- **Vitest** with **Playwright** for browser testing
|
|
168
423
|
- **Storybook** for component development and documentation
|
|
169
424
|
- **Lit** for custom web components
|
|
425
|
+
- **Zod** for runtime schema validation
|
|
170
426
|
|
|
171
427
|
## License
|
|
172
428
|
|