# 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
; } ```