# Spins
The Spins package in Wowza Flowplayer is a lightweight, TypeScript-based package designed to create short-form video experiences using Wowza Video playlists or inline arrays of video items. It provides a vertical video player component, optimized for formats like TikTok, Instagram Reels, and YouTube Shorts. This package automatically handles playlist loading and provides the structure needed for smooth, mobile-optimized video playback.
info
Spins is currently in beta. Features, APIs, and behaviors may change as development continues. We encourage testing in staging environments and welcome your feedback.
## Before you start
Before you start working with the Spins package, we make the following assumptions and recommendations:
- You're familiar with the ECMAScript Module (ESM) approach of handling JavaScript modules.
- You have Node.js and Node Package Manager (npm) installed on your machine.
- Review the [Token configuration](/docs/wowza-flowplayer/player/configure/#token-configuration) section. To authorize the player, you need to configure it with your token.
- Ensure you have a Wowza Video subscription if you want to configure playlists in Wowza Video and use the playlist ID to configure the Spins package. For more, see get started with a Wowza Video trial.
## Install Spins
The [@flowplayer/spins](https://www.npmjs.com/package/@flowplayer/spins) package is available for download via npm (recommended), yarn, or as an ES (ECMAScript) module. It automatically includes all minimum player components and CSS to work with the Wowza Flowplayer.
```js
// Install the spins package from npm
npm install @flowplayer/spins
// Import the package for use
import { createSpins, spinEvents } from "@flowplayer/spins";
```
```yarn
yarn add @flowplayer/spins
```
```html
```
## Configure Spins
To use the Spins package, configure it with the `createSpins()` method. There are two ways to provide video content to Spins:
- Using a Wowza Video playlist ID – Use the playlist ID of an existing playlist from your Wowza Video account, and Spins will automatically load and display the content. For more information on how to create a playlist and get a playlist ID, see Overview of playlists and Playlist details page in Wowza Video.
- Using an array of `SpinItem` objects – Use this method to manually define each video clip, including source URL, title, poster image, description, and plugins.
| Property | Description |
| --- | --- |
| `playlist`*string* | `SpinItem[]` | Defines a Wowza Video playlist ID (composite media ID) or an array of `SpinItem` objects. |
| `lang`*string* *Optional* | Sets the language of the player UI and messages. For example, `en` for English. |
| `token`*string* *Optional* | Adds a Wowza Flowplayer token required to license and use the player. |
| `ui`*Optional* | Sets [UI configuration](https://developer.wowza.com/docs/wowza-flowplayer/player/configure/#user-interface-configuration) for customizing player appearance and behavior. |
| `ima`*Optional* | Configures the Ads plugin for use with the spins container. For configuration details, see the [Ads plugin](https://developer.wowza.com/docs/wowza-flowplayer/plugins/advertising/#ima-object-configuration). |
| `share`*Optional* | Configures the Share plugin for use with the spins container. For configuration details, see the [Share plugin](https://developer.wowza.com/docs/wowza-flowplayer/plugins/sharing/#configure-the-plugin). |
| `consent`*Optional* | Configures the Consent plugin for use with the spins container. For configuration details, see the [Consent plugin](https://developer.wowza.com/docs/wowza-flowplayer/plugins/consent/#configuration). |
| `plugins`*Optional* | Sets plugins to include with the player. For example:`["subtitles", "vtsel", "ads", "share", "thumbnails", "asel"]`. |
| `adsFrequency`*Optional* | Displays ads at the specified spin interval. For example, a value of 3 displays ads on the 3rd, 6th, 9th, and subsequent spins. The default frequency is 10, meaning ads display every 10th spin. This property requires you to set the `ima` property. If `ima` is not set, ads are not displayed. If `ima` is set but `adsFrequency` is not, ads default to every 10th spin. |
#### SpinItem properties
Each `SpinItem` represents a single short-form video and includes properties such as the video URL, title, poster image, and subtitles.
| Property | Description |
| --- | --- |
| `url` *string* | Defines the video source URL or media ID from Wowza Video for the video. |
| `title` *string* *Optional* | Defines a title for the video. |
| `poster` *string* *Optional* | Sets a poster image shown before the video plays. |
| `description` *string* *Optional* | Defines a short description of the video content. |
| `subtitles` *Optional* | Adds [subtitles](https://developer.wowza.com/docs/wowza-flowplayer/plugins/html-subtitles/#configure-the-plugin) to the video. |
#### Example
The following examples use the properties from the previous table to import the Spins package from npm and to configure it with a Wowza Video playlist ID or with an array of customized spin items. You can also see how [event listeners](/docs/wowza-flowplayer/player/spins/#listen-to-events) are used to manage the spins container.
```typescript
import { createSpins, spinEvents } from "@flowplayer/spins";
// Use a playlist ID from Wowza Video
const playlistID = "c30850bc-eeeb-421d-9ed3-158339d0851a";
// Create Spins using playlist ID
const container = createSpins({
token: "[your-player-token]",
playlist: playlistID,
plugins: ["subtitles"]
});
// Listen for the SPIN_IN_VIEWPORT event
container.on(spinEvents.SPIN_IN_VIEWPORT, (ev) => {
const { config, spin, index } = ev.detail;
console.log("spin_in_viewport_data: ", config, spin, index);
// Add your code here
});
// Append the container to the DOM
document.body.append(container);
```
```typescript
import { createSpins, spinEvents } from "@flowplayer/spins";
// Manually define an array of spin items
const spinItemArray = [
{
// Add media ID for a Wowza Video asset
url: "38a31588-07a0-49bf-ba55-fa0310e13f15"
},
{
url: "https://cf23f0bd0.lwcdn.com/hls/9eda7730-23e9-4462-9ad4-c53e8004c3e9/playlist.m3u8",
title: "A title",
description: "A description",
poster: "//cf23f0bd0.lwcdn.com/i/v-i-9eda7730-23e9-4462-9ad4-c53e8004c3e9-1.jpg"
},
{
url: "https://stdlwcdn.lwcdn.com/hls/2cd21de6-1586-428b-bf7f-acc3fdcfd697/playlist.m3u8",
poster: "//stdlwcdn.lwcdn.com/i/v-i-67059b8d-ce1a-457e-9af1-d927ff945826-1.jpg",
title: "Test - missing mp4 01/09/2025 14:57"
},
{
url: "https://cdn.flowplayer.com/d9cd469f-14fc-4b7b-a7f6-ccbfa755dcb8/hls/383f752a-cbd1-4691-a73f-a4e583391b3d/playlist.m3u8?t=1",
subtitles: {
tracks: [
{
src: "https://builds.flowplayer.com/samples/en.vtt",
lang: "en",
label: "English"
},
{
src: "https://builds.flowplayer.com/samples/pt.vtt",
lang: "pt",
label: "Portuguese"
}
]
}
}
];
// Create Spins using spin item array
const container = createSpins({
playlist: spinItemArray,
plugins: ["subtitles"]
});
// Listen for the SPIN_IN_VIEWPORT event
container.on(spinEvents.SPIN_IN_VIEWPORT, (ev) => {
const { config, spin, index } = ev.detail;
console.log("spin_in_viewport_data: ", config, spin, index);
// Add your code here
});
// Append the container to the DOM
document.body.append(container);
```
## Listen to events
This section describes the events you can capture to control and manage your spins container.
### Spin events
| Event | Description |
| --- | --- |
| `SPIN_CREATED` | Emitted when a new spin is created |
| `SPIN_IN_VIEWPORT` | Emitted when a spin becomes visible in the view |
### Media events
| Event | Description |
| --- | --- |
| `CAN_PLAY` | Emitted when the browser can start playing the media without stopping for buffering. |
| `TIME_UPDATE` | Emitted when the playback position changes (e.g., during playback or scrubbing). |
| `PLAYING` | Emitted when playback is actively progressing after being paused or buffering. |
| `WAITING` | Emitted when playback is delayed while the next frame is being loaded (buffering). |
| `PAUSE` | Emitted when playback has been paused by the user or programmatically. |
| `PLAY` | Emitted when playback has been started or resumed. |
| `SEEKING` | Emitted when the user begins seeking to a new playback position. |
| `SEEKED` | Emitted when a seek operation completed. |
| `ENDED` | Emitted when playback has reached the end of the media resource. |
### Ad events
| Event | Description |
| --- | --- |
| `AD_TEARDOWN` | Emitted when the ad teardown/cleanup process occurs. |
| `AD_COMPLETED` | Emitted when a single ad has finished playing. |
| `AD_REQUEST_ERROR` | Emitted when an ad request fails due to a network or server error. |
| `AD_STARTED` | Emitted when an ad starts playing. |
| `AD_ERROR` | Internal or general ad error event. |
| `AD_PLAYBACK_ERROR` | Emitted when an ad request succeeds but the ad fails to play. |
| `AD_BREAK_COMPLETED` | Emitted when an ad break (group of ads) has completed. |
| `AD_PROGRESS` | Emitted periodically as the ad progresses during playback. |
| `AD_PAUSED` | Emitted when an ad is paused. |
| `AD_RESUMED` | Emitted when an ad resumes after being paused. |
| `AD_PREROLL_FINISHED` | Emitted when pre-roll or post-roll playback is finished. |
| `AD_SKIPPED` | Emitted when the skip button is clicked and the ad is skipped. |
| `AD_MUTED` | Emitted when the ad has been muted. |
| `AD_VOLUME_CHANGED` | Emitted when the ad volume changes during playback. |
#### SpinsContainer methods
| Method | Description |
| --- | --- |
| `on(event: string, handler: (ev: CustomEvent) => void)` | Subscribes to an event (e.g., `SPIN_CREATED`, `SPIN_IN_VIEWPORT`). |
| `off(event: string, handler: (ev: CustomEvent) => void)` | Removes a previously added event listener. |
| `addSpins(spinItems: SpinItem[])` | Adds new spins to the container dynamically. Accepts an array of `SpinItem` objects. |
| `onPlayerEvent(event: string, handler: (ev: CustomEvent) => void)` | Adds a listener for any player event, including mediaEvents and adEvents. |
| `offPlayerEvent(event: string, handler: (ev: CustomEvent) => void)` | Removes a listener for any player event added with onPlayerEvent(). |
#### Example
This code initializes a Spins player instance using the `createSpins` function with a specified playlist ID, access token, and language. It also sets up a few event listeners:
```typescript
import {
createSpins,
spinEvents,
mediaEvents,
adEvents
} from "@flowplayer/spins";
const container = createSpins({
playlist: "[your-playlist-id]",
token: "[your-player-token]",
lang: "en",
adsFrequency: 5,
ima: "your ads configuration"
});
// Spin lifecycle events
container.on(spinEvents.SPIN_CREATED, (ev) => {
const { config, spin, index } = ev.detail;
console.log("New spin created:", spin);
});
container.on(spinEvents.SPIN_IN_VIEWPORT, (ev) => {
const { config, spin, index } = ev.detail;
console.log("Spin is now in viewport:", spin);
});
// Media events
container.onPlayerEvent(mediaEvents.TIME_UPDATE, (ev) => {
const { spinIndex, duration, currentTime } = ev.detail;
console.log(`Progress: ${currentTime}/${duration}`);
});
// Ad events
container.onPlayerEvent(adEvents.AD_STARTED, (ev) => {
console.log(`Ad started at spin ${ev.detail.spinIndex}`);
});
document.body.appendChild(container);
```
## Control CSS styles
The package includes default CSS styling optimized for short-form content. You can override or extend these styles using standard CSS or CSS modules.
```css
.fp-spins-container {
max-height: 100vh;
overflow: hidden;
background: #000;
}
flowplayer-spin {
border-radius: 10px;
}
```
## Advanced usage
### Configure with React
Spins also works seamlessly with React. Here's how you can embed it inside a React component:
```js
import { createSpins } from "@flowplayer/spins";
import { useEffect, useRef } from "react";
function SpinsPlayer({ playlistId }: { playlistId: string }) {
const containerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
const spinsContainer = createSpins({
playlist: playlistId,
token: "[your-player-token]",
lang: "en"
});
containerRef.current.appendChild(spinsContainer);
return () => {
if (containerRef.current.contains(spinsContainer)) {
containerRef.current.removeChild(spinsContainer);
}
};
}, [playlistId]);
return ;
}
```