cgilly2fast

plugin adaptive bitrate videos

by cgilly2fast
Media & images
v3
^3.43.0
MIT

Overview

Summary generated from the README

Processes videos uploaded to selected Payload upload collections, transcodes them into multiple resolutions and generates HLS segment playlists plus a master manifest. Work runs through the Payload job queue with a configurable concurrency limit, and the manifest location is saved back to the source collection. Requires Payload 3 with a server URL set and ffmpeg available on the server.

Features

  • Automatic video processing on upload with HLS master manifest generation
  • Default resolutions from 144p to 2160p with custom sizes and bitrates
  • No upscaling: only resolutions at or below the video's short side are produced
  • Configurable segment length and option to keep the original file
  • Job queue with configurable queue name and maximum concurrent jobs
  • taskOverride option to offload processing to external compute

Use cases

  • Stream uploaded videos with adaptive quality to viewers on varying connections
  • Store HLS segments in cloud storage alongside the Payload cloud storage adapters
  • Move CPU-heavy transcoding to containers or dedicated servers via a custom task

From the author

Payload plugin for automatic adaptive bitrate process of videos.

Installation

npm i plugin-adaptive-bitrate-videos

21

Stars

3

Forks

2

Open Issues

5 months ago

Last Updated

0

Weekly Downloads

v3.1.1

npm Version

117.5 KB

Unpacked Size

3

Dependencies

GitHub stars

Trends appear after 7 days of data. Daily snapshots have not started yet.

npm weekly downloads

Trends appear after 7 days of data. Daily snapshots have not started yet.

Each point is npm's rolling 7-day download count.

Health Score

50/100Good

Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.

Badge for your README

Payload Directory health badge
Markdown
[![Payload Directory](https://payloaddirectory.dev/badge/846425506-root.svg)](https://payloaddirectory.dev/plugins/846425506-root)
HTML
<a href="https://payloaddirectory.dev/plugins/846425506-root"><img src="https://payloaddirectory.dev/badge/846425506-root.svg" alt="Payload Directory"></a>
Updated May 6, 2026Created Aug 23, 2024Published May 6, 2026

README

Payload Adaptive Bitrate Videos Plugin

This plugin extends Payload CMS to provide Adaptive Bitrate (ABR) streaming capabilities for uploaded video files. It automatically processes video uploads, creates segment playlists for multiple resolution versions, generates HLS manifests, and updates the collection with the master manifest file location.

Updated for Payload 3.0

Introduction Video

Watch the video

Features

  • Automatic video processing on upload
  • Multiple resolution transcoding (144p to 4K, define custom sizes/bitrates)
  • HLS playlist and master manifest generation
  • Flexible storage integration (Local, AWS S3, Google Cloud Storage, Azure Blob Storage)
  • Uses Payload job queue with configurable max running jobs - queue automatically starts processing when new jobs are added (pull worker queue pattern)
  • Offload video processing to external compute resources (AWS Fargate, Docker containers, dedicated servers) via customizable task configuration

Installation

yarn add plugin-adaptive-bitrate-videos  or  npm install plugin-adaptive-bitrate-videos

Usage

Add this package into your dependencies executing this code in your command line:

yarn add plugin-adaptive-bitrate-videos

Now install this plugin within your Payload as follows:

//payload.config.ts
import { buildConfig } from 'payload/config'
import path from 'path'
import { abrVideos } from 'plugin-adaptive-bitrate-videos`'

export default buildConfig({
  serverUrl: 'https://example.com' // Must be set to use pluggin
  plugins: [
    abrVideos({
      collections: {
        'my-collection-slug': {keepOrginal: true}
      }
    })
  ]
  // The rest of your config goes here
})

See Payload config options for documentation on setting serverUrl in Payload config.

Cloud Storage Plugin

This plugin can be used with the Payload Cloud Storage Plugin to store you segments and manifest files. cloudStorage is CollectionOptions object from Payload Cloud Plugin Collection specific options.

//payload.config.ts
import { buildConfig } from 'payload/config'
import path from 'path'
import { abrVideos } from 'plugin-adaptive-bitrate-videos'
import { gcsStorage } from '@payloadcms/storage-gcs'

const adapter = gcsAdapter({
  options: {
    // you can choose any method for authentication, and authorization which is being provided by `@google-cloud/storage`
    keyFilename: './gcs-credentials.json',
    //OR
    credentials: JSON.parse(process.env.GCS_CREDENTIALS || '{}'), // this env variable will have stringify version of your credentials.json file
  },
  bucket: process.env.GCS_BUCKET,
})
// Now you can pass this adapter to the plugin

export default buildConfig({
  serverUrl: 'https://example.com' // Must be set to use plugin
  plugins: [
    abrVideos({
      collections: {
        // The collection users upload source videos to
        'my-collection-slug': {keepOriginal: true}
      }
    }),
    gcsAdapter({ //IMPORTANT: Cloud storage plugin must come after plugin
      options: {
        // you can choose any method for authentication, and authorization which is being provided by `@google-cloud/storage`
        keyFilename: './gcs-credentials.json',
        //OR
        credentials: JSON.parse(process.env.GCS_CREDENTIALS || '{}'), // this env variable will have stringify version of your credentials.json file
      },
      bucket: process.env.GCS_BUCKET,
      collections: {
        // The collection users upload source videos to
        'my-collection-slug': {
          // see docs for the adapter you want to use
          adapter: adapter,
        },
        // the output collection created by the plugin
        'segments': { // Required name 'segments' unless overridden in segmentsOverride
          adapter: adapter,
        },
      },
    })
  ]
  // The rest of your config goes here
})

Custom Sizes And Bitrates

If not resolutions array is provide, the plugin will use the default resolutions and bit rates. size specifies the pixel size of the short side of the video so the aspect ratio of any input video is maintained.

For Example: if you input a 4k 16:9 video (The standard landscape video aspect ratio), the plugin will change the video's height to and allow the width to proportionally change.

For the 1080 output the resulting video will be 1080p x 1920p, the standard 1080p pixel size.

The principle is followed for square and portrait videos.

Note: The plugin will only output segments for resolutions that are less than or equal to the short side of the video. Meaning no upscaling is conducted. Example: a 1080p video will only produce output segments at resolutions that are specifed less than or eqaul to 1080.

//payload.config.ts
import { buildConfig } from 'payload/config'
import path from 'path'
import { abrVideos } from 'plugin-adaptive-bitrate-videos`'

export default buildConfig({
  serverUrl: 'https://example.com' // Must be set to use pluggin
  plugins: [
    abrVideos({
      collections: {
        'my-collection-slug':{
          keepOriginal: true,
          resolutions: [
            {
              size: 1080 // pixel size
              bitrate: 4000 // kilobits per second
            },
            // ...more custom resolutions
          ]
        }
      }
    })
  ]
  // The rest of your config goes here
})

Conditionally Enabling/Disabling

The proper way to conditionally enable/disable this plugin is to use the enabled property.

//payload.config.ts
abrVideos({
  enabled: process.env.MY_CONDITION === 'true',
  collections: {
    'media': {keepOrginal: true}
  }
}),

If the code is included in any way in your config but conditionally disabled in another fashion, you may run into issues such as Webpack Build Error: Can't Resolve 'fs' and 'stream' or similar because the plugin must be run at all times in order to properly extend the webpack config.

Segments Collection Override

Override anything on the segments collection by sending a Payload Collection Config to the segmentsOverrides property.

// payload.config.ts
abrVideos({
  // ...
  segmentOverrides: {
    slug: 'contact-forms',
    access: {
      read: () => true,
      update: () => false,
    },
    fields: [
      {
        name: 'custom-field',
        type: 'text',
      },
    ],
  },
})

Custom Segment Length

Optionally set the length of the segments the source video will be divided into. Default length is 2 seconds. The property takes in an number representing seconds.

// payload.config.ts
abrVideos({
  // ...
  segmentLength: 5, //seconds. Default is 2 seconds.
})

Plugin options

This plugin is configurable to work across many different Payload collections. A * denotes that the property is required.

OptionTypeDescription
collections*Records<string,CollectionOptions>Object with keys set to the slug of collections you want to enable the plugin for, and values set to collection-specific options.
enabledbooleanConditionally enable/disable plugin. Default: true.
segmentsOverridesPayloadCollectionConfigObject that overrides the default collection used to store reference to the output segments. Default: SegmentOverrideDefault
queueNamestringThe name of queue used in payload-jobs Default: 'process-abr-videos-queue'
maxJobsnumberMax number of video processing jobs to allow to be run at once. Default: 1
taskOverrideTaskConfig | ((options: TaskConfigurationOptions) => TaskConfig | Promise<TaskConfig>)Override the default video processing task to run on different compute resources. See taskOverride documentation

Collection-specific options:

OptionTypeDescription
keepOriginal*booleanConditionally set to keep the original source file after processing.
resolutionsArray<Resolution>Set custom resolutions for the plugin to output segment videos to. Default: ResolutionsDefault
segmentLengthnumberSet the output segment length in seconds for each resolution output. Default: 2
SegmentOverrideDefault
const SegmentOverrideDefault = {
  slug: 'segments',
  labels: { singular: 'ABR Segment', plural: 'ABR Segments' },
  access: {
    read: () => true,
    update: () => false,
  },
  upload: true,
  fields: [],
}
ResolutionsDefault
const DefaultResolutions = [
  { size: 144, bitrate: 150 },
  { size: 240, bitrate: 250 },
  { size: 360, bitrate: 500 },
  { size: 480, bitrate: 1000 },
  { size: 720, bitrate: 1500 },
  { size: 1080, bitrate: 4000 },
  { size: 1440, bitrate: 6000 },
  { size: 2160, bitrate: 10000 },
]

Example Front-end Usage

Any video player that can play .m3u8 files can be used. Here is a simple example using the react-hls-video-player. View docs for react-hls-video-player here.

import React, { useRef, useState } from 'react'
import ReactHlsPlayer from 'react-hls-video-player'

const SimpleHlsPlayer = () => {
  const playerRef = useRef(null)
  const [isPlaying, setIsPlaying] = useState(false)

  const handlePlay = () => {
    setIsPlaying(true)
  }

  const handlePause = () => {
    setIsPlaying(false)
  }

  return (
    <div className="w-full max-w-2xl mx-auto">
      <ReactHlsPlayer
        id="videoElement"
        playerRef={playerRef}
        src="https://example.com/path/to/your/manifest.m3u8"
        className="w-full aspect-video"
        autoPlay={false}
        controls={true}
        onPlay={handlePlay}
        onPause={handlePause}
        muted
        playsInline
      />
      <div className="mt-4 text-center">
        <p>Player status: {isPlaying ? 'Playing' : 'Paused'}</p>
      </div>
    </div>
  )
}

export default SimpleHlsPlayer

Development & Testing

This repository includes a Payload 3 development app in dev/ for both manual testing and automated integration testing.

Prerequisites

  • Docker — required to run the local MongoDB instance
  • pnpm

Setup

  1. Install dependencies

    pnpm install
    
  2. Configure environment variables

    Copy the example env file and fill in any values you need:

    cp dev/.env.example dev/.env
    

    The default values in .env.example work out of the box with the Docker MongoDB setup below. The key variables are:

    VariableDescriptionDefault
    DATABASE_URIMongoDB connection stringmongodb://127.0.0.1:27017/payload-plugin-template
    PAYLOAD_SECRETSecret key for Payload CMS(set your own)
    PAYLOAD_PUBLIC_SERVER_URLPublic URL of the dev serverhttp://localhost:3000
  3. Start MongoDB

    A docker-compose.yml is included at the root of the repo. Start MongoDB with:

    docker compose up -d
    

    This runs MongoDB 7 on localhost:27017. Data is persisted in a Docker volume between restarts. To stop it:

    docker compose down
    

Manual Testing

Start the local Payload/Next app:

pnpm dev

Then open the Payload admin at:

http://localhost:3000/admin

To manually verify the plugin, upload a video from dev/mocks/ into one of the configured upload collections (media or videos). The plugin should queue a processing job, create HLS segment files and playlist manifests in the configured segments collection, and create the master .m3u8 manifest back in the source collection.

Integration Tests

Run the integration test suite from the CLI:

NODE_ENV=test pnpm test:int

The integration test setup starts its own MongoDB memory replica set and starts the real Payload/Next dev server automatically. You do not need to run pnpm dev before running the tests.

The tests verify the real upload and processing flow:

  • Payload upload collection receives the source video
  • the plugin queues and runs the video processing job
  • ffmpeg reads the uploaded file through Payload's HTTP file route
  • segment files and HLS playlists are uploaded into the segments collection
  • the master .m3u8 manifest is created in the original source collection
  • deleting a master manifest removes related segment outputs

Build Check

Before publishing, build the package and run the integration suite:

pnpm build
NODE_ENV=test pnpm test:int

Memory & Runtime Considerations

To run the this plugin, you will need to run your Payload server on a machine that can comfortably store 2x the max video upload size.

This is required because the source video needs to be temporally stored and the output segments need to be temporally stored before being saved in your final destination.

See Payload Documentation on setting upload limits here.

Also when deployed to production, videos will process slower than processing locally. If the processVideo function is being run in a cloud function environment ensure cloud function timeouts give enough time for videos to process.

Custom Video Processing with taskOverride

For CPU-intensive video processing, you may want to offload the work to dedicated compute resources. The taskOverride option allows you to customize or replace the default video processing task.

Default Task Input Schema:

  • baseURL (text): Base URL for file access
  • inputPath (text): Path to input video file
  • keepOriginal (checkbox): Whether to keep original file
  • originalID (text): ID of original video record
  • originalData (json): Original video metadata
  • resolutions (array): Target resolutions with size and bitrate
  • segmentDuration (number): Segment length in seconds
  • inputCollectionSlug (text): Source collection slug
  • outputCollectionSlug (text): Target collection slug

Example: Custom Processing Task

// payload.config.ts
abrVideos({
  collections: {
    videos: { keepOriginal: true },
  },
  taskOverride: {
    slug: 'custom-video-processing',
    handler: async ({ input, req }) => {
      // Custom processing logic
      const result = await processVideoHandler(req.payload, input)
      return { output: result }
    },
    retries: 2,
  },
})

Example: External Service Integration

// payload.config.ts
abrVideos({
  collections: {
    videos: { keepOriginal: true },
  },
  taskOverride: (config) => ({
    slug: 'external-video-processing',
    handler: async ({ input, req }) => {
      // Delegate to external processing service
      const response = await fetch(`${process.env.VIDEO_PROCESSING_ENDPOINT}/process`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(input),
      })
      return await response.json()
    },
  }),
})

Questions

Please open an issue on this repo with any questions or issues about using this plugin.

Similar plugins