@flowplayer/spins 0.5.1 → 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 +305 -79
- package/dist/index.js +28 -31
- package/dist/types.d.ts +176 -22
- 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,106 +120,297 @@ 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
|
-
- `on(event, handler)` - Subscribe to events
|
|
99
|
-
- `off(event, handler)` - Unsubscribe from events
|
|
141
|
+
A `SpinsContainer` instance (extends `HTMLElement`) with the following methods:
|
|
100
142
|
|
|
101
|
-
|
|
143
|
+
##### Event Handling
|
|
102
144
|
|
|
103
|
-
|
|
104
|
-
|
|
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
|
+
});
|
|
105
150
|
|
|
106
|
-
|
|
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
|
|
175
|
+
|
|
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
|
+
```
|
|
107
186
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
187
|
+
### Events
|
|
188
|
+
|
|
189
|
+
#### Spin Events (`spinEvents`)
|
|
190
|
+
|
|
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
|
+
|
|
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
|
|
112
238
|
|
|
113
239
|
## TypeScript Support
|
|
114
240
|
|
|
115
|
-
The library
|
|
241
|
+
The library is fully typed with TypeScript. Import types for full type safety:
|
|
116
242
|
|
|
117
243
|
```typescript
|
|
118
|
-
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";
|
|
119
265
|
```
|
|
120
266
|
|
|
121
|
-
###
|
|
267
|
+
### Key Type Definitions
|
|
268
|
+
|
|
269
|
+
#### `SpinsConfig`
|
|
122
270
|
|
|
123
|
-
|
|
271
|
+
Main configuration for creating a spins container:
|
|
124
272
|
|
|
125
273
|
```typescript
|
|
126
274
|
interface SpinsConfig {
|
|
127
275
|
playlist: string | Array<SpinItem>;
|
|
128
|
-
token
|
|
129
|
-
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[];
|
|
130
284
|
}
|
|
131
285
|
```
|
|
132
286
|
|
|
133
|
-
|
|
287
|
+
#### `SpinItem`
|
|
134
288
|
|
|
135
|
-
Individual
|
|
289
|
+
Individual video configuration:
|
|
136
290
|
|
|
137
291
|
```typescript
|
|
138
292
|
interface SpinItem {
|
|
139
|
-
src:
|
|
293
|
+
url: string | { src: string; type: string };
|
|
140
294
|
title?: string;
|
|
141
295
|
poster?: string;
|
|
142
296
|
description?: string;
|
|
143
|
-
subtitles?: SubtitlesConfig
|
|
297
|
+
subtitles?: SubtitlesConfig;
|
|
144
298
|
}
|
|
145
299
|
```
|
|
146
300
|
|
|
147
|
-
|
|
301
|
+
#### `SpinsContainer`
|
|
148
302
|
|
|
149
|
-
The
|
|
150
|
-
You can override styles by targeting the spins container and its children:
|
|
303
|
+
The container element with event handling:
|
|
151
304
|
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
|
|
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;
|
|
155
328
|
}
|
|
156
329
|
|
|
157
|
-
|
|
158
|
-
|
|
330
|
+
interface MediaEventData {
|
|
331
|
+
spinIndex: number;
|
|
332
|
+
duration?: number;
|
|
333
|
+
currentTime: number;
|
|
334
|
+
mediaId?: string;
|
|
335
|
+
url: string;
|
|
159
336
|
}
|
|
337
|
+
|
|
338
|
+
type AdEventData<K extends AdEvent> = {
|
|
339
|
+
spinIndex: number;
|
|
340
|
+
} & AdEventsDetailMap[K];
|
|
160
341
|
```
|
|
161
342
|
|
|
162
|
-
##
|
|
343
|
+
## Advanced Usage
|
|
163
344
|
|
|
164
|
-
###
|
|
345
|
+
### Listening to All Events
|
|
165
346
|
|
|
166
|
-
|
|
167
|
-
|
|
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
|
+
});
|
|
168
363
|
|
|
169
|
-
|
|
364
|
+
// Spin lifecycle events
|
|
365
|
+
container.on(spinEvents.SPIN_CREATED, (e) => {
|
|
366
|
+
console.log(`Spin ${e.detail.index} created:`, e.detail.config);
|
|
367
|
+
});
|
|
170
368
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
369
|
+
container.on(spinEvents.SPIN_IN_VIEWPORT, (e) => {
|
|
370
|
+
console.log(`Spin ${e.detail.index} is now visible`);
|
|
371
|
+
});
|
|
174
372
|
|
|
175
|
-
|
|
176
|
-
|
|
373
|
+
// Media playback events
|
|
374
|
+
container.onPlayerEvent(mediaEvents.PLAY, (e) => {
|
|
375
|
+
console.log(`Playing spin ${e.detail.spinIndex}`);
|
|
376
|
+
});
|
|
177
377
|
|
|
178
|
-
|
|
179
|
-
|
|
378
|
+
container.onPlayerEvent(mediaEvents.TIME_UPDATE, (e) => {
|
|
379
|
+
console.log(`Progress: ${e.detail.currentTime}/${e.detail.duration}`);
|
|
380
|
+
});
|
|
180
381
|
|
|
181
|
-
|
|
182
|
-
|
|
382
|
+
container.onPlayerEvent(mediaEvents.ENDED, (e) => {
|
|
383
|
+
console.log(`Spin ${e.detail.spinIndex} finished`);
|
|
384
|
+
});
|
|
183
385
|
|
|
184
|
-
|
|
185
|
-
|
|
386
|
+
// Ad events
|
|
387
|
+
container.onPlayerEvent(adEvents.AD_STARTED, (e) => {
|
|
388
|
+
console.log(`Ad started at spin ${e.detail.spinIndex}`);
|
|
389
|
+
});
|
|
186
390
|
|
|
187
|
-
|
|
188
|
-
|
|
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
|
+
});
|
|
189
414
|
```
|
|
190
415
|
|
|
191
416
|
## Contributing
|
|
@@ -196,6 +421,7 @@ This project uses:
|
|
|
196
421
|
- **Vitest** with **Playwright** for browser testing
|
|
197
422
|
- **Storybook** for component development and documentation
|
|
198
423
|
- **Lit** for custom web components
|
|
424
|
+
- **Zod** for runtime schema validation
|
|
199
425
|
|
|
200
426
|
## License
|
|
201
427
|
|