diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2a4b777e..21456120 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -24,13 +24,7 @@ services: env_file: config.env environment: - REDIS_HOST=superstreamer-redis - - REDIS_PORT=6379 - DATABASE_URI=postgresql://postgres:sprs@superstreamer-postgres/sprs - - S3_ENDPOINT=${S3_ENDPOINT} - - S3_REGION=${S3_REGION} - - S3_ACCESS_KEY=${S3_ACCESS_KEY} - - S3_SECRET_KEY=${S3_SECRET_KEY} - - S3_BUCKET=${S3_BUCKET} superstreamer-stitcher: image: "superstreamerapp/stitcher:alpha" @@ -42,14 +36,8 @@ services: env_file: config.env environment: - REDIS_HOST=superstreamer-redis - - REDIS_PORT=6379 - PUBLIC_API_ENDPOINT=http://localhost:52001 - PUBLIC_STITCHER_ENDPOINT=http://localhost:52002 - - S3_ENDPOINT=${S3_ENDPOINT} - - S3_REGION=${S3_REGION} - - S3_ACCESS_KEY=${S3_ACCESS_KEY} - - S3_SECRET_KEY=${S3_SECRET_KEY} - - S3_BUCKET=${S3_BUCKET} superstreamer-artisan: image: "superstreamerapp/artisan:alpha" @@ -59,12 +47,6 @@ services: env_file: config.env environment: - REDIS_HOST=superstreamer-redis - - REDIS_PORT=6379 - - S3_ENDPOINT=${S3_ENDPOINT} - - S3_REGION=${S3_REGION} - - S3_ACCESS_KEY=${S3_ACCESS_KEY} - - S3_SECRET_KEY=${S3_SECRET_KEY} - - S3_BUCKET=${S3_BUCKET} superstreamer-redis: image: redis/redis-stack-server:7.2.0-v6 diff --git a/docker/minio/minio.env b/docker/integrations/minio/config.env.example similarity index 54% rename from docker/minio/minio.env rename to docker/integrations/minio/config.env.example index 8bb9ce23..271b9c73 100644 --- a/docker/minio/minio.env +++ b/docker/integrations/minio/config.env.example @@ -1,6 +1,6 @@ # MinIO Configuration S3_ENDPOINT=http://superstreamer-minio:9000 S3_REGION=us-east-1 -S3_ACCESS_KEY=minioadmin -S3_SECRET_KEY=minioadmin -S3_BUCKET=superstreamer \ No newline at end of file +S3_ACCESS_KEY=sprs +S3_SECRET_KEY=sprs +S3_BUCKET=sprs \ No newline at end of file diff --git a/docker/minio/docker-compose.minio.yml b/docker/integrations/minio/docker-compose.yml similarity index 66% rename from docker/minio/docker-compose.minio.yml rename to docker/integrations/minio/docker-compose.yml index 716e90ad..3c0f0910 100644 --- a/docker/minio/docker-compose.minio.yml +++ b/docker/integrations/minio/docker-compose.yml @@ -7,12 +7,12 @@ services: superstreamer-minio: image: quay.io/minio/minio environment: - MINIO_ROOT_USER: minioadmin - MINIO_ROOT_PASSWORD: minioadmin + MINIO_ROOT_USER: sprs + MINIO_ROOT_PASSWORD: sprs command: server /data --console-address ":9001" ports: - - "9000:9000" # API - - "9001:9001" # Console + - "9000:9000" + - "9001:9001" volumes: - superstreamer_minio_data:/data healthcheck: @@ -21,14 +21,14 @@ services: timeout: 5s retries: 5 - createbuckets: + superstreamer-minio-bootstrap: image: minio/mc depends_on: superstreamer-minio: condition: service_healthy entrypoint: > /bin/sh -c " - mc alias set myminio http://superstreamer-minio:9000 minioadmin minioadmin; - mc mb myminio/superstreamer; + mc alias set myminio http://superstreamer-minio:9000 sprs sprs; + mc mb myminio/sprs; exit 0; " diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 1db6258f..a0bd967a 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -39,7 +39,7 @@ export default defineConfig({ provider: "algolia", options: { appId: "4JIX5YPW7R", - apiKey: "004a506c5ecd75d2a16d12feefc7af58", + apiKey: "97be940a67cc7ae000c625203b1c51f1", indexName: "matvp91io", }, }, @@ -108,35 +108,46 @@ function sidebarGuide() { text: "Getting Started", link: "getting-started", }, - { - text: "Packages", - link: "packages", - }, - { - text: "Learning", - link: "learning", - }, ], }, { - text: "Features", + text: "Project", items: [ { - text: "Video processing", - link: "video-processing", + text: "What's included", + link: "whats-included", }, { - text: "Playlist manipulation", - link: "playlist-manipulation", + text: "Building blocks", + link: "building-blocks", + items: [ + { + text: "Video processing", + link: "video-processing", + }, + { + text: "Video personalization", + link: "video-personalization", + }, + { + text: "Player", + link: "player", + }, + ], }, { - text: "Player", - link: "player", - }, - { - text: "Dashboard", - link: "dashboard", - }, + text: "Tutorials", + items: [ + { + text: "Play adaptive video", + link: "tutorials/play-adaptive-video" + }, + { + text: "Add bumper like Netflix", + link: "tutorials/add-bumper-like-netflix" + } + ] + } ], }, { @@ -147,8 +158,8 @@ function sidebarGuide() { link: "contribute", }, { - text: "Credits", - link: "credits", + text: "Thank you", + link: "thank-you", }, ], }, diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index e9943221..cdfe6980 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -4,9 +4,17 @@ --vp-c-brand-3: #ff7400; --vp-home-hero-name-color: transparent; - --vp-home-hero-name-background: -webkit-linear-gradient(120deg, #ff4d00 30%, #ff9a00); - - --vp-home-hero-image-background-image: linear-gradient(-45deg, rgba(255, 77, 0, .2) 40%, rgba(255, 154, 0, .2) 40%); + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #ff4d00 30%, + #ff9a00 + ); + + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + rgba(255, 77, 0, 0.2) 40%, + rgba(255, 154, 0, 0.2) 40% + ); --vp-home-hero-image-filter: blur(44px); } @@ -35,10 +43,12 @@ max-width: 160px; } -.video { - border-radius: 12px; - overflow: hidden; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +.schema { + margin: 4em 0; +} + +.framed { + border-radius: 1rem; } .package { @@ -65,20 +75,39 @@ border-left: 1px solid var(--vp-c-divider); } -.schema { - margin: 3rem auto; +.package div { + margin-left: 2rem; +} + +.video { + border-radius: 1rem; width: 100%; } -.schema-stitcher { - max-width: 400px; +.number { + width: 1.5rem; + height: 1.5rem; + font-size: 0.85rem; + color: #ffffff; + background-color: #000000; + border-radius: 100%; + display: inline-flex; + align-items: center; + justify-content: center; + margin-right: 0.5rem; + text-align: center; + font-weight: bold; } -.schema-dashboard { - max-width: 300px; +h1, +h2, +h3 { + display: flex; + align-items: center; } -.details.minimal { - background-color: transparent; - padding: 0; -} \ No newline at end of file +.image-rounded { + border-radius: 1rem; + width: 100%; + border: 2px solid #f1f1f1; +} diff --git a/docs/guide/building-blocks.md b/docs/guide/building-blocks.md new file mode 100644 index 00000000..bb25fa62 --- /dev/null +++ b/docs/guide/building-blocks.md @@ -0,0 +1,5 @@ +# Building blocks + +Getting the bigger picture from a high-level perspective can be challenging, but we'll do our best to guide you through how the different building blocks come together. + +Ideally, Superstreamer provides tools that cover everything from input source files, like an MP4, all the way to playback, such as a player embedded in a page. However, you're free to choose the tools that best fit your use case and extend them with Superstreamer's features as needed. \ No newline at end of file diff --git a/docs/guide/contribute.md b/docs/guide/contribute.md index 8b6d3ee1..9fb12ea6 100644 --- a/docs/guide/contribute.md +++ b/docs/guide/contribute.md @@ -2,41 +2,6 @@ This is a living document about notes from contributors. If you'd like to contribute yourself, feel free to read through this info first. -## Project structure - -Superstreamer consists of multiple packages, each serve their own purpose. This is particularly handy when you want to scale. You could easily run a package on multiple machines, such as Artisan. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PackageDescription
AppA front-end that uses the API and visualizes running jobs.
APIUsed to interact with the system. Start transcode jobs, get a list of pending jobs, and much more.
ArtisanThe main job runner, can be run on as many machines as you like.
StitcherAn HLS playlist proxy for on the fly manipulation, such as interstitials insertion and resolution filtering.
PlayerAn easy to use HLS.js wrapper and UI built with React.
- ## Running jobs We rely on [BullMQ](https://docs.bullmq.io/), a queue job runner, to define transcode, package and ffmpeg jobs. Eg; take the `/transcode` endpoint, this will push a job to the Redis queue. Artisan constantly monitors the Redis queue for pending work, and when there is a pending job available, it'll start working on that. When finished, it'll update the job that it finished. diff --git a/docs/guide/credits.md b/docs/guide/credits.md deleted file mode 100644 index 47811b60..00000000 --- a/docs/guide/credits.md +++ /dev/null @@ -1,9 +0,0 @@ -# Credits - -Superstreamer wouldn't be here without the awesome help from others! There are some killer projects out there innovating the video industry step by step. Big thanks for all your amazing work β€” we definitely couldn't have pulled this off without you and a ton of coffee! - -- The people behind [HLS.js](https://github.com/video-dev/hls.js/), past and present contributors. -- The people behind [FFmpeg](https://www.ffmpeg.org/), without y'all, video today would be as real as unicorns. -- The [Shaka Project](https://github.com/shaka-project). -- A special shout to Robert Walch for answering so many of my (stupid) questions. --
for the most epic CDN in the world.
\ No newline at end of file diff --git a/docs/guide/dashboard.md b/docs/guide/dashboard.md deleted file mode 100644 index eaec11a2..00000000 --- a/docs/guide/dashboard.md +++ /dev/null @@ -1,19 +0,0 @@ -# Dashboard - -The dashboard is a single page application where you can manage a wide variety of functionalities. Typically, you'd want to build the dashboard once and upload it to S3 to serve it as a static site. The dashboard is an SPA and requires no separate server / backend to function. - -Verify you've set the correct variables in `config.env`, each env key prefixed with `PUBLIC_` will be available in the dashboard and will be included in the final Javascript bundle. - -Show pending, running, completed and failed jobs. You can easily inspect a job, with logs, sub processes and other metrics. - - - -We've got a neat storage tab that lets you explore the configured S3 bucket, as if it was your local hard disk. There's a preview function that lets you inspect readable files such as playlists (m3u8) and more. - - - -A playground for the Stitcher, where you can throw any configuration at it and it'll create a session for you. We've got our player embedded on the right for instant preview. - - - -But wait, there's more! We have a page dedicated to the API and the Sticher API. You can read through the Swagger documentation and fire API calls right away. The dashboard is all you need to get started quickly. \ No newline at end of file diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 65f7a160..d13aaeb8 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -1,16 +1,14 @@ ---- -outline: [1,2] ---- - # Getting Started -We're happy to have you here! Feel free to reach out with any questions on our [Discord](https://discord.gg/4hXgz9EsF4). +Setting up your video streaming platform has never been easier. With just a few simple steps, you'll be up and running, delivering seamless video experiences. + +Let's dive in and get you started! ## Docker Compose If you're familiar with [Docker](https://docs.docker.com/engine/install/), we suggest you use our hosted Docker images. -Create a new folder with a fresh `docker-compose.yml` file and copy it from below. The original file can be found on [GitHub](https://github.com/matvp91/superstreamer/tree/main/docker/docker-compose.yml). +Create a new folder with a fresh `docker-compose.yml` file and copy the contents from below. ::: code-group @@ -19,13 +17,16 @@ version: "3" volumes: superstreamer_redis_data: + superstreamer_postgres_data: services: superstreamer-app: image: "superstreamerapp/app:latest" ports: - 52000:52000 - env_file: config.env + environment: + - PUBLIC_API_ENDPOINT=http://localhost:52001 + - PUBLIC_STITCHER_ENDPOINT=http://localhost:52002 superstreamer-api: image: "superstreamerapp/api:latest" @@ -33,11 +34,12 @@ services: ports: - 52001:52001 depends_on: + - superstreamer-postgres - superstreamer-redis env_file: config.env environment: - REDIS_HOST=superstreamer-redis - - REDIS_PORT=6379 + - DATABASE_URI=postgresql://postgres:sprs@superstreamer-postgres/sprs superstreamer-stitcher: image: "superstreamerapp/stitcher:latest" @@ -49,7 +51,8 @@ services: env_file: config.env environment: - REDIS_HOST=superstreamer-redis - - REDIS_PORT=6379 + - PUBLIC_API_ENDPOINT=http://localhost:52001 + - PUBLIC_STITCHER_ENDPOINT=http://localhost:52002 superstreamer-artisan: image: "superstreamerapp/artisan:latest" @@ -59,7 +62,6 @@ services: env_file: config.env environment: - REDIS_HOST=superstreamer-redis - - REDIS_PORT=6379 superstreamer-redis: image: redis/redis-stack-server:7.2.0-v6 @@ -69,11 +71,24 @@ services: test: ["CMD", "redis-cli", "--raw", "incr", "ping"] volumes: - superstreamer_redis_data:/data + + superstreamer-postgres: + image: "postgres:latest" + restart: always + stop_signal: SIGINT + ports: + - "5432:5432" + volumes: + - superstreamer_postgres_data:/var/lib/postgresql/data + environment: + - POSTGRES_INITDB_ARGS=--data-checksums + - POSTGRES_DB=sprs + - POSTGRES_PASSWORD=sprs ``` ::: -Create a `config.env` file in the same folder. The original example can be found on [GitHub](https://github.com/matvp91/superstreamer/blob/main/config.env.example). +Create a `config.env` file in the same folder. ::: code-group @@ -83,15 +98,12 @@ S3_REGION=us-east-1 S3_ACCESS_KEY= S3_SECRET_KEY= S3_BUCKET=superstreamer -PUBLIC_API_ENDPOINT=http://localhost:52001 -PUBLIC_STITCHER_ENDPOINT=http://localhost:52002 PUBLIC_S3_ENDPOINT=https://s3.us-east-1.amazonaws.com/superstreamer +SUPER_SECRET=somethingsupersecret ``` ::: -If you'd like to change the port of each service individually, provide the `PORT` environment variable. - Start the necessary services with [Docker Compose](https://docs.docker.com/compose/). ::: code-group @@ -105,9 +117,31 @@ $ docker compose up -d By default, we host the app on port `52000`. Open `http://127.0.0.1:52000` in your browser, and you're all set! ::: info + In a scalable architecture, you probably do not want to run the ffmpeg and transcode workers on the same machine as your api or the stitcher. + ::: +::: tip + +If you'd like to change the port of each service individually, provide the `PORT` environment variable for each service individually. + +-Β [AWS S3](https://aws.amazon.com/s3/) +- [Cloudflare R2](https://www.cloudflare.com/developer-platform/products/r2/) + +::: + +### Use S3 + +Superstreamer supports any S3 compliant service. Just add the appropriate credentials to your `config.env` file, and you're all set. + +### Use local + +If you'd like to emulate or provide S3-like object storage environments locally, we'd suggest you pick one of the following projects: + +- [MinIO](https://min.io/) +- [LocalStack](https://www.localstack.cloud/) + ## Local builds One of our main goals is to help you get up and running locally with minimal hassle. Superstreamer is organized as a monorepo, and each service or app comes with its own `build` script. You can build the entire project and all its packages with just a single command. The backend services use Bun, while the frontend app and player are built with Vite. Superstreamer relies on a unified environment variable setup, the `config.env` file at the root of the project. @@ -116,7 +150,6 @@ One of our main goals is to help you get up and running locally with minimal has - Redis, we suggest [Redis Stack](https://redis.io/docs/latest/operate/oss_and_stack/install/install-stack/). - [Bun](https://bun.sh/) v1.1.30 or above. -- [pnpm](https://pnpm.io/installation) as package manager. ### Install dependencies @@ -125,17 +158,15 @@ First, we're going to install a couple of dependencies. Run the following comman ::: code-group ```sh [Terminal] -# Install node dependencies -$ pnpm install +# Install dependencies +$ bun install # Install binary dependencies, such as ffmpeg -$ pnpm install-bin +$ bun run install-bin ``` ::: -Each package now contains a `node_modules` folder, and optionally a `bin` folder when necessary. - ### Build packages Before we build, we're going to configure a few environment variables first. @@ -156,7 +187,7 @@ Next up, we're going to build the different packages into their single Javascrip ::: code-group ```sh [Terminal] -$ pnpm build +$ bun run build ``` ::: @@ -169,13 +200,13 @@ Now that we have each package build, let's run them locally. ```sh [Terminal] # Run the api, default port is 52001 -$ bun packages/api/dist/index.js +$ bun run packages/api/dist/index.js # Run artisan, the job runner -$ bun packages/artisan/dist/consumer/index.js +$ bun run packages/artisan/dist/index.js # Run the stitcher, default port is 52002 -$ bun packages/stitcher/dist/index.js +$ bun run packages/stitcher/dist/index.js ``` ::: @@ -185,7 +216,7 @@ If you'd like to interact with the API, or with Stitcher, run the app. It's a si ::: code-group ```sh [Terminal] -$ pnpm --filter="@superstreamer/app" dev +$ bun run --filter=\"@superstreamer/app\" dev ``` ::: @@ -194,12 +225,12 @@ If you'd like to host the app elsewhere, all files can be found in `packages/app ### Development -We've already covered how to build Superstreamer locally, and we've also made it easy to start developing on the project. Just run `pnpm dev` from the root of the project, and it will launch all the services, including the app. Head over to `http://localhost:52000`, and you'll be welcomed by the app! +We've already covered how to build Superstreamer locally, and we've also made it easy to start developing on the project. Just run `bun run dev` from the root of the project, and it will launch all the services, including the app. Head over to `http://localhost:52000`, and you'll be welcomed by the app! ::: code-group ```sh [Terminal] -$ pnpm dev +$ bun run dev ``` ::: \ No newline at end of file diff --git a/docs/guide/learning.md b/docs/guide/learning.md deleted file mode 100644 index 5022500f..00000000 --- a/docs/guide/learning.md +++ /dev/null @@ -1,33 +0,0 @@ -# Learning - -Here, we delve into the essential concepts and techniques behind transcoding, media pipelines, and all things related to video delivery. Let's explore the fascinating world of video technology together! - -The people at Mux did a great job creating a site about video engineering, read through [howvideo.works](https://howvideo.works/), they cover every aspect starting from transcode all the way to playback. - -We'll quickly go over the terminology commonly found in the documentation to help you better understand it. - -- **Transcoding** - - Convert video to different formats or quality levels for compatibility and optimization. - -- **Packaging** - - Prepare video content for delivery by formatting it into segments and manifest files for streaming protocols (like HLS). - -- **Stitching** - - Modifying playlist files to control video playback, such as switching streams or adding content dynamically (like ads, bumpers, ...). - -- **Interstitials** - - Insert other content into an HLS stream by dynamically updating the playlist during video playback. - -You might be wondering how these terms connect to the various packages in Superstreamer. Here's an overview to clarify: - -- **Artisan** - - Takes care of transcoding (ffmpeg) and packaging (shaka-packager). - -- **Stitcher** - - Does all of the stitching, modifies playlists based on your needs, for each viewer individually if needed. When additional playlists (such as ads) need to be inserted, it'll insert the proper HLS interstitials tags. \ No newline at end of file diff --git a/docs/guide/packages.md b/docs/guide/packages.md deleted file mode 100644 index f3f7d852..00000000 --- a/docs/guide/packages.md +++ /dev/null @@ -1,119 +0,0 @@ -# Packages - -Superstreamer is a monorepo, with multiple smaller projects inside. - -## What's included - -We'll provide a quick overview of them below to give you a sense of what each one does. - -
-

App

-

- A Single Page Application (SPA) used to interact with the API or start a session on the Stitcher service. -

-
- -
-

API

-

- The API serves as the primary interface for interacting with Superstreamer, such as start tasks like transcoding or packaging jobs. A swagger page is exposed on the /swagger endpoint. -

-
- -
-

Artisan

-

- The actual job runners, these run in the background and consume whatever job API has scheduled next. Artisan instructs `ffmpeg` to run, or packages a previously transcoded asset to an HLS playlist and syncs it all to S3. -

-
- -
-

Stitcher

-

- Also referred to as a "playlist manipulator," Stitcher can create a session for each user and generate a custom HLS playlist tailored to their needs, including resolution filtering and the addition of bumpers or linear ads. The stitcher has its own API. A swagger page is exposed on the /swagger endpoint. -

-
- -
-

Player

-

- The player facade simplifies HLS.js with an intuitive API for building the player. It offers player-friendly methods, supports plugins, and provides React hooks for efficient state management. -

-
- -::: tip -Artisan consumes jobs from a Redis queue. You can run as many Artisan instances as you like in the background, over multiple machines. This scales like a beast. -::: - -## Practical example - -We'll `POST` to the `/transcode` endpoint of the API with a payload that includes the following: the input files (eg; the BigBuckBunny.mp4 file) and a list of streams (the output, eg; we'd like to have a 480p and 720p video stream and an audio stream in English). - -::: details A transcode payload example - -```json -{ - "inputs": [ - { - "path": "s3://source/BigBuckBunny.mp4", - "type": "video" - }, - { - "path": "s3://source/BigBuckBunny.mp4", - "type": "audio", - "language": "eng" - }, - ], - "streams": [ - { - "type": "video", - "codec": "hevc", // Look, a different codec, because we can. - "height": 720, - "bitrate": 4000000, - "framerate": 24 - }, - { - "type": "video", - "codec": "h264", - "height": 480, - "bitrate": 1500000, - "framerate": 24 - }, - { - "type": "audio", - "codec": "aac", - "bitrate": 128000, - "language": "eng", - "channels": 2 - }, - ] -} -``` - -::: - -When an Artisan instance is active, it'll start working on the transcode job. When it finished running ffmpeg in the background, a couple of m4s / m4v files (audio, video streams) are pushed back to S3. You can see the active jobs and their status in the dashboard (App). - -Superstreamer assigns a unique UUID, refered to as an _asset id_, to the job. At this point, we can speak of an asset. - -::: tip - -The dashboard (App) has a neat storage explorer. Search for the _asset id_ in the /transcode folder. - -::: - -Next, we'll package the fresh asset for online streaming. We'll `POST` to the `/package` endpoint of the API with our _asset id_. - -::: details A package payload example - -```json -{ - "assetId": "6775eb41-e62e-45ca-9525-9e14b71cf8f5" -} -``` - -::: - -This'll produce an HLS playlist, located in `/package/6775eb41-e62e-45ca-9525-9e14b71cf8f5/hls/master.m3u8`. You can use the storage explorer to view the different files or take a sneak peak at the contents of the master playlist. - -And tada πŸŽ‰, we now have a valid video streaming file on our S3 bucket! \ No newline at end of file diff --git a/docs/guide/player.md b/docs/guide/player.md index ffe6f5a3..b1d2ede3 100644 --- a/docs/guide/player.md +++ b/docs/guide/player.md @@ -4,29 +4,23 @@ outline: [2,3] # Player -The team behind [HLS.js](https://github.com/video-dev/hls.js/), an open-source HLS streaming library in JavaScript, does an outstanding job maintaining it. While it's an excellent resource for streaming, HLS.js is designed primarily as a streaming library, which is great if you're already familiar with HLS. However, there's a bit of a learning curve if you're looking to build your project on top of it. +Superstreamer comes with a player wrapper around [HLS.js](https://github.com/video-dev/hls.js/). Our goal is to offer a simplified API alongside HLS.js, tailored for developers building a player UI, while preserving access to the powerful features that HLS.js provides. -The goal of our player wrapper is to offer a simplified API alongside HLS.js, tailored for developers building a player UI, while preserving access to the powerful features that HLS.js provides. The wrapper, which we'll refer to as _facade_ from here on, focuses on the following goals: + + +The wrapper focuses on the following goals: - Offer intuitive data structures, events, and methods tailored for developers building a player UI. - Implement a robust state machine. - Provide simplified player-centric methods like `playOrPause`, `setVolume`, and more. - Support spec-compliant plugins, including features like ad signaling. -Beyond the facade, we've also provided useful React hooks for consuming state within components. More details will follow, but the key insight is that building a React UI on top of rapidly changing state can impact performance. Our hooks allow you to efficiently consume player state by creating small, memoized subsets of the specific state needed for each component, ensuring optimal performance. - ::: tip If you're only interested in the React bindings and components, you can skip the facade section. The React integration uses a facade internally, so there's no need for you to provide one yourself. ::: -## Live demo - -What's a player page without a live demo! Have a play with it, you'll love our controls. We've hosted a sample on StackBlitz, which means you can look at the implementation code and make adjustments as you like. - - - ## Installation ::: code-group @@ -61,9 +55,7 @@ We're currently using a beta version of HLS.js, v1.6.0-beta.1. Once the final re ## Facade -We prefer not to call it a wrapper, since it doesn't wrap HLS.js but operates alongside it as an additional way to interact with your video. Just create a new `HlsFacade` instance and pass it your existing `Hls` instance - -Start by consulting the HLS.js [docs](https://github.com/video-dev/hls.js/) first. +Create a new `HlsFacade` instance and pass it your existing `Hls` instance Start by consulting the HLS.js [docs](https://github.com/video-dev/hls.js/) first. ::: code-group @@ -87,7 +79,13 @@ hls.loadSource("https://domain.com/master.m3u8"); ## React -Using React to define your UI declaratively is far more enjoyable than taking an imperative approach. Since the UI serves as a visual representation of the state, creating controls in React is a pleasant experience. However, there are some considerations to keep in mind. Extracting state from the facade and storing it in memory solely to inform React of changes can be resource-intensive. That's why we've dedicated significant effort to ensuring that state updates are as lightweight as possible. +Using React to define your UI declaratively is far more enjoyable than taking an imperative approach. Since the UI serves as a visual representation of the state, creating controls in React is a pleasant experience. + +However, there are some considerations to keep in mind. + +Extracting state from the facade and storing it in memory solely to inform React of changes can be resource-intensive. That's why we've dedicated significant effort to ensuring that state updates are as lightweight as possible. + +Our hooks allow you to efficiently consume player state by creating small, memoized subsets of the specific state needed for each component, ensuring optimal performance. ### Tailwind diff --git a/docs/guide/thank-you.md b/docs/guide/thank-you.md new file mode 100644 index 00000000..29d928a6 --- /dev/null +++ b/docs/guide/thank-you.md @@ -0,0 +1,25 @@ +# Thank you + +Much of the hard work was done before us. We're here to bring everything together seamlessly and make it easy for you to use. Full credit goes to those who laid the foundation. We'd also like to extend our thanks to the individuals and companies supporting us on this journey. + +
+ +![Cloudflare](/logo-cloudflare.jpg){width=100px} + +![Mux](/logo-mux.png){width=80px} + +![Montevideo](/logo-montevideo.png){width=140px} + +![FFmpeg](/logo-ffmpeg.png){width=120px} + +![Qualabs](/logo-qualabs.png){width=120px} + +![Shaka Packager](/logo-shaka-packager.png){width=60px} + +![HLS.js](/logo-hlsjs.png){width=80px} + + + +
+ +- A special shout to Robert Walch for answering so many of my questions. \ No newline at end of file diff --git a/docs/guide/tutorials/add-bumper-like-netflix.md b/docs/guide/tutorials/add-bumper-like-netflix.md new file mode 100644 index 00000000..b1598e18 --- /dev/null +++ b/docs/guide/tutorials/add-bumper-like-netflix.md @@ -0,0 +1,27 @@ +# Add bumper like Netflix + +## 1 Prepare our sources + +We've got the following sources transcoded and packaged: + +- Big Buck Bunny, see [Play adaptive video](/guide/tutorials/play-adaptive-video), + + with UUID `56bfe1d6-ce51-4a28-b688-bc64a89508b6`. + +- A Netflix bumper (for illustrational purpose), + + with UUID `929045c6-13a2-417d-8df0-5a756766172d`. + +Make sure to check [Video personalization](/guide/video-personalization) first. + +## 2 Stitch bumper before content + +We're going to use Stitcher to create us a personalized playlist, with both our videos combined. + + + +## 3 Play our personalized HLS playlist + +We'll use the player to load our new HLS playlist URL. + + \ No newline at end of file diff --git a/docs/guide/tutorials/play-adaptive-video.md b/docs/guide/tutorials/play-adaptive-video.md new file mode 100644 index 00000000..717efc5b --- /dev/null +++ b/docs/guide/tutorials/play-adaptive-video.md @@ -0,0 +1,63 @@ +# Play adaptive video + +Adaptive video, or adaptive bitrate streaming, is a technique that adjusts the quality of a video stream based on the viewer's network conditions and device capabilities. + +Instead of a single video file, adaptive streaming involves multiple versions of the same video at different quality levels. + +In order to do this, we'll have to create an HLS playlist from our video file. + +In this tutorial, we'll use the dashboard as a way to inspect what Superstreamer does. When you use our Docker images, the dashboard is hosted on `http://localhost:52000` by default. + +## 1 Upload our source + +We'll take this Big Buck Bunny video and upload it somewhere. This could either be on the S3 configured in Superstreamer or have it accessible through http(s). + + + +For this tutorial, we'll upload it to the S3 configured in Superstreamer' `config.env` file. We have it available as `s3://input_bbb.mp4`. + +## 2 Transcode to multiple qualities + +We'll transcode our Big Buck Bunny video and produce the following streams: + +- 720 in height. +- 480 in height. +- An audio track with language "eng" (English). + + + +If you want fine grained control, take a look at the API docs in order to specify a different bitrate or framerate. For now, we'll rely on the sane defaults provided by Superstreamer. + +Once the transcode job is finished, a unique UUID is assigned to this asset. In our example, Superstreamer created the new UUID `56bfe1d6-ce51-4a28-b688-bc64a89508b6`. From now on, if we want to do something with this transcode result, we'll simply provide this id. + + + +We'll now see in the Storage tab that Superstreamer produced several video and audio tracks. + +## 3 Package to HLS + +At this stage, these separate tracks don't hold much value for a player. What we need is for video players to switch between different qualities based on the user's bandwidth or preferences. To achieve this, we'll extract segments β€” small chunks of videoβ€”for each stream and package them into an HLS playlist. + + + +Packaging is straightforward: just grab the UUID from the transcode job (`56bfe1d6-ce51-4a28-b688-bc64a89508b6`) and pass it to the package endpoint. This triggers a packaging job behind the scenes. + +## 4 Check HLS playlist in storage + +If we'd like to know what's being produced, we can go back to the Storage tab and inspect the `/package/56bfe1d6-ce51-4a28-b688-bc64a89508b6` folder. + + + +Notice how we transformed a single MP4 video file into a collection of segments, all referenced by an HLS playlist. This playlist is ready to be played by any HLS-compliant player. + +## 5 Play our HLS playlist + + + +Superstreamer comes with a player page where you can easily test your videos. In our example, the HLS playlist is available at: + +```sh + +https://{PUBLIC_S3_ENDPOINT}/package/56bfe1d6-ce51-4a28-b688-bc64a89508b6/master.m3u8 + +``` \ No newline at end of file diff --git a/docs/guide/playlist-manipulation.md b/docs/guide/video-personalization.md similarity index 55% rename from docs/guide/playlist-manipulation.md rename to docs/guide/video-personalization.md index ac2b29a2..52e1bbb4 100644 --- a/docs/guide/playlist-manipulation.md +++ b/docs/guide/video-personalization.md @@ -1,43 +1,68 @@ --- -outline: [1,4] +outline: [2,4] --- -# Playlist manipulation +# Video personalisation -Stitcher is a playlist manipulator that can insert HLS interstitials on-the-fly. +Superstreamer comes with a separate project named Stitcher, an HLS playlist manipulator that can insert interstitials on-the-fly. You can use Stitcher to set individual quality limits for each user or to insert advertisements into your content. + +Make sure you read the [Video processing](/guide/video-processing) page first. In order to stitch playlists together, we must first make sure we have our video files processed and available as HLS playlists. + +## Terminology + +Before we dive in, let's cover some basic terminology. We'll keep this brief, but if you're eager to learn more about video, check out [howvideo.works](https://howvideo.works/). They provide a great explanation of the video delivery process from source to playback. + +- Stitching + + Stitching is the real-time manipulation of a video stream, where multiple HLS playlists are combined and adjusted on the fly. It allows for adding bumpers or ads seamlessly. + +- HLS + + HLS (HTTP Live Streaming) is a protocol that splits video into small chunks and delivers them over HTTP. For Superstreamer, we focus exclusively on using HLS for streaming. + +- Interstitial + + An interstitial is a type of video segment inserted into an HLS playback session, usually serving as a mid-stream interruption for purposes like advertisements, informational messages, or promotional content. ## Use cases -- Insert linear ads at given cue points, derived from a VMAP. +- Insert linear ads at given cue points, derived from a VMAP (a spec compliant format that includes the positions of where ads should be in a video). - Add a bumper playlist at the start of a playlist, like Netflix' intro. You wouldn't want to re-transcode an asset if a bumper changes. - Filter a media playlist to exclude qualities, eg; free users can stream up to 720p. ## Create a session +To personalize a playback session, begin by creating a session for a specific user. This requires a `POST` request, where the body includes parameters for Stitcher to generate a customized playlist in response. + +
+ +
+ Providing input for the stitcher happens in the form of a `uri`. The support uri schemas are: -- `asset://{assetId}` +- **asset://{assetId}** We want to greatly simplify how you stitch playlists together. If you use the asset scheme, Superstreamer will know that you mean a package result from within the platform. -- `http(s)://{url}/master.m3u8` +- **https://{url}/master.m3u8** In case you want to use HLS playlists hosted elsewhere, you can use the http(s) scheme. -```sh [Terminal] -$ curl -X POST https://stitcher-superstreamer.domain.com/session +```sh +curl -X POST + "https://stitcher.domain.com/session" -H "Content-Type: application/json" - -d "{body}" ``` ```json { + // If your asset is known to Superstreamer API, you can refer to it by assetId: "uri": "asset://2d6e6c0e-07d9-48b1-8192-318f32a3b909", - // OR - // If you provide a UUID without scheme, we'll assume it's an assetId: + + // OR - if you provide a UUID without scheme, we'll assume it's an assetId: "uri": "2d6e6c0e-07d9-48b1-8192-318f32a3b909", - // OR - // An external playlist: + + // OR - an external playlist: "uri": "https://my-awesome-external-service/assets/123/master.m3u8" } ``` @@ -46,11 +71,11 @@ The response includes the new playlist URL, and you can pass this on directly to ```json { - "url": "{PUBLIC_STITCHER_ENDPOINT}/session/625f68c2-6c09-44d4-a50f-81873cb7839b/master.m3u8" + "url": "{PUBLIC_STITCHER_ENDPOINT}/out/625f68c2-6c09-44d4-a50f-81873cb7839b/master.m3u8" } ``` -That's nice and all, but all we did was play an asset through the stitcher. That's not very interesting. You're right, but what is interesting is the fact that we can manipulate the playlist for each session individually. +That's nice and all, but all we did was play an asset through the stitcher. That's not very interesting. What is interesting is the fact that we can manipulate the playlist for each session individually. ### Filters @@ -64,13 +89,27 @@ When streaming over networks with limited bandwidth (eg; mobile networks), remov { "uri": "asset://f7e89553-0d3b-4982-ba7b-3ce5499ac689", "filter": { - // Remove all renditions with a height greater than 480 + // Remove all renditions with a height lower or equal than 480. "resolution": "<= 480" } } ``` -### Interstitials +#### Audio language + +To filter by audio language, provide a comma-separated list. + +```json +{ + "uri": "asset://f7e89553-0d3b-4982-ba7b-3ce5499ac689", + "filter": { + // Keep only English and Dutch as audio track. + "audioLanguage": "eng, nld" + } +} +``` + +### Manual interstitials Let's say you transcoded and packaged a new asset, a bumper for example. We'll add it as an interstitial. An HLS interstitials supported player will then switch to the new asset at position 10 and when finished, it'll go back to the primary content. @@ -88,7 +127,7 @@ Let's say you transcoded and packaged a new asset, a bumper for example. We'll a If you'd like to add a bumper, you'd insert an interstitial at position 0. -### Linear ads +### Linear advertisements We solely work with standards. VMAP (Video Multiple Ad Playlist) is an XML-based specification that defines how multiple ad breaks can be inserted into a video stream. Instruct Stitcher to add interstitials based on VMAP definitions. Each VMAP contains one or more AdBreak elements with a position of where the interstitial should be. diff --git a/docs/guide/video-processing.md b/docs/guide/video-processing.md index d3512c82..fe44a115 100644 --- a/docs/guide/video-processing.md +++ b/docs/guide/video-processing.md @@ -1,168 +1,165 @@ # Video processing -To work with video, the first step is to upload your assets into the system. For instance, if you have an MP4 file as your source, we can use the transcode API to process it. This is typically the first step. +We'll begin by processing and preparing our video source file (e.g., MP4, MKV, ...), ensuring it's properly formatted and optimized for the next stages of transcoding and packaging. -## Why transcode? +## Terminology -There's reasons why we would transcode an input file. We'll explain them briefly. +Before we dive in, let's cover some basic terminology. We'll keep this brief, but if you're eager to learn more about video, check out [howvideo.works](https://howvideo.works/). They provide a great explanation of the video delivery process from source to playback. -- **Compatibility** - - Ensure the video format works across various devices and platforms. +- Transcode -- **Quality** + The first step is video transcoding, which converts a video from one format or codec to another, ensuring it works across different devices, browsers, and network conditions. For Superstreamer, this involves transcoding into various tracks, such as 720p or 1080p video, and stereo or surround audio. - Adjust resolution and bitrate for optimal playback quality based on user devices and network conditions. +- Packaging -- **Compression** + After transcoding, we have different video and audio tracks, but they need to be combined into a single format that can be played smoothly. Video packaging organizes these tracks so the player can easily access and switch between them based on the user's device and internet speed. For Superstreamer, we generate HLS playlists. - Decrease file size for easier storage and faster uploads/downloads. +- HLS -- **Streaming** - - Enable adaptive streaming by creating multiple resolutions for different bandwidths. Players can then pick what works for the user based on bandwidth estimations. + HLS (HTTP Live Streaming) is a protocol that splits video into small chunks and delivers them over HTTP. For Superstreamer, we focus exclusively on using HLS for streaming. -## Start a transcode job +## Start a transcode -When you schedule a transcode job with the API, a unique UUID is assigned to each asset. We'll call this the `assetId` from now on. Each asset can be referenced to by providing this id in further steps. +To stream an MP4 file to your audience, the process begins with transcoding it into various qualities, potentially adding multiple audio languages or subtitle tracks. To interact with Superstreamer, simply make an API request to get started. -::: tip + -Video transcoding is the process of converting video, audio and text from one format to another. This involves changing the file's encoding to make it compatible with different devices, reduce its size, or adjust its quality. +We'll begin by sending a `POST` request to the `/transcode` endpoint of our API. In this request, we'll specify the source files (our available inputs) and define the desired outputs β€” specifically, HD and Full HD video tracks, along with English audio. -::: - -::: code-group +```sh +curl -X POST + "https://api.domain.com/transcode" -```sh [Terminal] -$ curl -X POST https://api-superstreamer.domain.com/transcode - -H "Content-Type: application/json" - -d "{body}" ``` -::: - -This is a body payload for the `BigBuckBunny.mp4` file we've uploaded to our S3 bucket, as specified in the config.env file. The source file contains a video and audio track. We've also uploaded a `BigBuckBunnyEng.vtt` file that contains the subtitles. +::: code-group -```json +```json [Request] { - "inputs": [ - // Describe video input + "input": [ { - "path": "s3://source/BigBuckBunny.mp4", - "type": "video" + "type": "video", + "url": "https://domain.com/video.mp4" }, - // Describe audio input { - "path": "s3://source/BigBuckBunny.mp4", "type": "audio", - "language": "eng", - "channels": 2 - }, - // Describe text input - { - "path": "s3://source/BigBuckBunnyEng.vtt", - "type": "text", + "url": "https://domain.com/video.mp4", "language": "eng" } ], "streams": [ - // We'd like to produce a HEVC encoded video stream. { "type": "video", - "codec": "hevc", - "height": 720, - "bitrate": 4000000, - "framerate": 24 + "codec": "h264", + "height": 1080 }, - // And an h264 encoded video stream, at a lower resolution. { "type": "video", "codec": "h264", - "height": 480, - "bitrate": 1500000, - "framerate": 24 + "height": 720 }, - // We'd like to create an audio stream, aac codec with 2 channels. - // If our source was 6 channels (surround), we'd be able to pass 6 here too. { "type": "audio", "codec": "aac", - "bitrate": 128000, - "language": "eng", - "channels": 2 - }, - // We want an "English" text track. - { - "type": "text", "language": "eng" } - ], - // We'll define the segment size upfront, this will be used for packaging purposes. - "segmentSize": 2, + ] } ``` -The result of the transcode job will be an `assetId`, this is a unique UUID that identifies a transcode result. From now on, we'll only work with this id. Eg; if you want to package this transcode result, all we'll have to do is provide this id. +::: -```json +Under the hood, Superstreamer kicks off a transcode job to produce the output streams. Once completed, each job is assigned a unique UUID. For example, in this case, the UUID is `46169885-f274-43ad-ba59-a746d33304fd`. + +This request above will provide a response containing the jobId: + +::: code-group + +```json [Response] { - "assetId": "2d6e6c0e-07d9-48b1-8192-318f32a3b909" + "jobId": "transcode_46169885-f274-43ad-ba59-a746d33304fd" } ``` -Let's quickly go over the steps that the transcode process did: - -- Download, or stream, the input file directly to ffmpeg. -- Store the outcome on hard disk temporarily. -- Upload the transcoded media file to S3. +::: -And it'll do these steps for each output stream separately. If you scale Artisan horizontally, it'll pick up each ffmpeg job individually across different machines and get to work. +This UUID serves as a reference for the asset across all interactions with the Superstreamer API. -That's great and all, but what do we do with the result of a transcode job? A transcode result is not playable by definition. We'll use the transcode result to package our asset into an HLS playlist. +When the job is done, we have separate video tracks in various quality levels and a single audio track in English, exactly the result we were aiming for. -## Start a package job +## Start a package -Now that we have transcoded our input file(s), we'll prepare them for streaming. +Our video and audio tracks are stored as separate files. Let's package them into an HLS playlist, which players will use to determine what to load and at which resolution. -::: tip + -Video packaging refers to the process of preparing a video file for delivery and consumption by users across different devices and platforms. +We'll send a `POST` request to the `/package` endpoint of our API, and all we'll have to do is provide the assetId returned by our transcode job. -::: - -We split the transcoding and packaging processes into two separate steps (API calls). This allows you to generate various packaging outputs from a single transcoding result. For instance, you can create both a DRM-protected and a standard HLS playlist from the same transcoded asset. By separating these steps, we avoid the need to re-transcode our assets, which is usually resource-intensive and can heavily utilize your CPU (and sometimes GPU). In contrast, packaging is a relatively light task. +```sh +curl -X POST + "https://api.domain.com/package" +``` ::: code-group -```sh [Terminal] -$ curl -X POST https://api-superstreamer.domain.com/package - -H "Content-Type: application/json" - -d "{body}" +```json [Request] +{ + "assetId": "46169885-f274-43ad-ba59-a746d33304fd" +} ``` ::: -All we'll have to do is instruct the packager to package a given `assetId`. When no transcode result is found for the given id, an error will be thrown. +We'll check the dashboard app to check the status of our transcode job. -```json -{ - "assetId": "2d6e6c0e-07d9-48b1-8192-318f32a3b909" -} +And that's it. We now have an HLS playlist available on our S3 bucket. + +``` +https://cdn.domain.com/package/46169885-f274-43ad-ba59-a746d33304fd/master.m3u8 ``` -A package job runs the following steps: +## Start a pipeline -- Download all transcode results from S3 to local hard disk. -- Package all different streams into an HLS playlist. -- Upload the playlists, and the segments, back to S3 in the `/package` folder. +In earlier steps, we covered how to transcode and package your video file. With a pipeline job, these two steps can be combined into a single process. -If you've properly configured config.env, your asset is now available for playback at the following URL: +```sh +curl -X POST + "https://api.domain.com/pipeline" ``` -{PUBLIC_S3_ENDPOINT}/package/2d6e6c0e-07d9-48b1-8192-318f32a3b909/hls/master.m3u8 + +```json [Request] +{ + "input": [ + { + "type": "video", + "url": "https://domain.com/video.mp4" + }, + { + "type": "audio", + "url": "https://domain.com/video.mp4", + "language": "eng" + } + ], + "streams": [ + { + "type": "video", + "codec": "h264", + "height": 1080 + }, + { + "type": "video", + "codec": "h264", + "height": 720 + }, + { + "type": "audio", + "codec": "aac", + "language": "eng" + } + ] +} ``` -And tada, you now have a playable asset that can be played with HLS.js! πŸŽ‰ πŸ₯³ -Give it a try and play your asset in the HLS.js demo page: https://hlsjs.video-dev.org/demo/ \ No newline at end of file +The pipeline job generates a unique UUID, and once complete, your asset is instantly available as an HLS playlist β€” just as if you had packaged it manually. \ No newline at end of file diff --git a/docs/guide/what-is-superstreamer.md b/docs/guide/what-is-superstreamer.md index 051d35b1..859386e3 100644 --- a/docs/guide/what-is-superstreamer.md +++ b/docs/guide/what-is-superstreamer.md @@ -1,105 +1,65 @@ # What is Superstreamer? -Superstreamer is here to make video delivery simple. Imagine having everything you need in one platform β€” starting with your raw video, Superstreamer helps you transcode it, package it into HLS playlists, and upload it to S3 with ease. You can even create custom playlists for each viewer, adding bumpers, ads, or filters on the fly. +Superstreamer is an open-source platform designed to simplify video delivery. It offers the tools you need to easily integrate high-quality video streaming into your applications. -When it's time for your audience to watch, Superstreamer's elegant web player ensures your videos are delivered smoothly and look great on any device. It takes the hassle out of streaming, so viewers can enjoy your content without any interruptions. +Our goal is to create a robust, flexible solution that empowers developers, hobbyists, and companies alike to focus on delivering amazing video experiences, similar to how Netflix, YouTube or Vimeo work. -There are plenty of great video tools out there, but we saw a gap β€” a unified platform to bring all those tools together. Our mission with Superstreamer is to make video more accessible for developers, letting them focus on their projects without getting bogged down by the technical details. +[Join our community](https://discord.gg/4hXgz9EsF4) and help us make video streaming more accessible together. -::: info -Can't wait? Head over to our [Getting Started](/guide/getting-started) page and jump right into it! -::: - -::: tip - -Check out these cool screen recordings of the dashboard app! Click here to view them. The link will open in a new tab, so you won't lose your place. - -::: - -## How it works - -Everyone loves schemas, and we're no exception. Let's break down how Superstreamer works and how all the packages are interconnected. - -You've got an epic video file named Frames_Of_Thrones.mp4. It's hefty, and you want to deliver it to your audience with a seamless viewing experience, complete with subtitle options in various languages. Your goal is to provide the best possible experience for your viewers. - -### Transcode - -First, we'll take the source file and create several video streams, ranging from 480p to 1080p β€” like the options you see on YouTube. We'll also point the player to the audio track, which in this case is embedded in the MP4 file. To top it off, we'll include a couple of subtitle files. +## Why? - +Delivering video is complicated because there are so many different ways to process, store, and deliver video content. -
- Steps to take +You have to deal with things like video quality, file sizes, compatibility across devices, and fast delivery speeds. Right now, there are lots of different tools and approaches, but they don't always work well together. - 1. You send a transcode request to the API using your file or s3 URL as the input, along with a few output stream definitions. - 2. The API will push a transcode job to Redis. - 3. One, or multiple (if you're into scale), Artisan instances will grab jobs from Redis, and produce outputs streams locally. - 4. Each Artisan instance will push their output stream to S3. - 5. Finally, the API will assign a unique Asset ID to the process, allowing us to continue working with it. -
+Our goal is to focus on just the important basics and make them work perfectly together, using the standards everyone already uses, so it's easier for everyone to create and watch videos. -### Package +::: details Our technical mantra -So, we've taken our original video, sliced it up into different qualities, bitrates β€” event threw in some subtitles and a surround sound track for good measure. Awesome, right? Well... now we've got a bunch of separate files hanging out on S3. We need to bundle these up so the video player can actually make sense of the mess. +If you're familiar with video engineering, the terms below might sound familiar. We aim to reduce the fragmentation in the field, and we believe we can excel by focusing on a select few aspects and perfecting them. - - -
- Steps to take - - 1. You send a package request to the API with the Asset ID from the transcode process. - 2. The API will push a package job to Redis. - 3. An Artisan instance will download the transcoded files and generate an HLS playlist along with the video segments locally. - 4. The HLS master playlist, media playlists and segments are uploaded to S3 with public permissions. -
- -### Play - -Alright, we've got our HLS playlist sitting in the S3 bucket. You could hit play and call it a day, sure, but where's the fun in that? Like, we've got this NotFlix bumper β€” _tuduuuum_ β€” and we want to slap it right in front of our Frames of Thrones masterpiece. So, what do we do? We transcode and package that bumper, then tell our stitcher to whip up a new playlist on the fly, combining our bumper with the main content. Boom β€” playlist magic! πŸͺ„ +- HLS as a playlist format, with CMAF containered segments. +- Inserting and playing other playlists, like ads, depends completely on HLS interstitials. Watch Apple's [Intertitials Intro](https://developer.apple.com/videos/play/wwdc2024/10114/#:~:text=With%20HLS%20interstitials%2C%20ads%20or,content's%20Program%2DDate%2DTime.) video announced at WWDC24. +- No VAST on the client, ever. 😜 +- Don't reinvent the wheel β€” leverage the fantastic work of others. If that means making open-source contributions, we're all for it. +::: - +## Building blocks -
- Steps to take +The project is made up of several building blocks, each designed to handle specific tasks in the video streaming process. It's important to know that you don't need to use everything β€” this isn't an all-or-nothing solution. We want to avoid vendor lock-in, so you're free to pick and choose the building blocks that best fit your needs. - 1. You send a session request to the Sticher API, with the Asset ID from the transcode (or package, they're the same) process, along with your parameters (eg; ad insertion, bumper, ...) - 2. Stitcher will prepare a unique playlist for this session, which the player downloads. - 3. The player can now play the video, it will grab the rest (such as segments) from S3 directly. -
+For example, you can use our tool for real-time video manipulation (like inserting a bumper or ads), while still transcoding and packaging your video files elsewhere, such as using [Mux](https://www.mux.com/) for those steps. -### Monitor +The flexibility is yours to create the workflow that works best for you. -We'd like to keep an eye on what Superstreamer is up to behind the scenes. Don't worry, we've got a handy little app we call the dashboard. It's like a reality show for your jobs β€” complete with a list that shows all the action, including status updates and how long everything takes. +## Use cases -And if you're feeling ambitious and want to integrate Superstreamer into your own project (which, let's face it, if you're in the video biz, you absolutely should try), interacting with the API from your backend is a walk in the park. +To users, video seems simple – it just plays, right? But delivering a smooth experience for every viewer is actually quite complex. Video isn't as straightforward as it appears. We're here to take care of the technical challenges and make video delivery easy for you. - +- Optimize for speed and quality -## Developer Experience + Need your videos to load faster and look great? This tool makes sure your videos play quickly and at the best quality, no matter the device. If your viewers have slow internet, the [transcode tool](/guide/process#start-a-transcode) can adjust the video quality so it still plays smoothly without buffering. -- **Simplified workflow** +- Convert to different formats - Handling video at scale involves multiple steps: ingesting source files, transcoding them into various formats, packaging for different devices, and ensuring smooth delivery. Superstreamer streamlines these steps into a unified workflow. + Want to play your video on any device? Our transcode tool changes your video to the right format so everyone can watch it, in different qualities depending on the viewer' bandwidth. -- **Scalability** +- Monetize your content - Video platforms often need to serve content to large, diverse audiences, which requires infrastructure that can handle spikes in traffic and support multiple video formats. Superstreamer can be scaled horizontally due to a built-in queue / worker architecture and works great with any S3 compliant storage. + Our ad insertion tool lets you add ads at the perfect moment to earn revenue while keeping the viewer engaged. -- **Customization and Personalization** +- Package for streaming - Modern video platforms often need to deliver personalized content, such as inserting targeted ads, bumpers, or even dynamically altering playlists based on user behavior. Superstreamer is built for these needs and can handle the real-time processing required to customize video streams. + Ready to show your video online? Our [package tool](/guide/process#create-a-package) packages your video so it streams smoothly to your viewers, no matter where they are. -- **Cost Efficiency** +- Create custom playlists - Building and maintaining a full-scale video pipeline can be resource-intensive. Fortunately, Superstreamer isn't tied to a single vendor, allowing you the freedom to choose the most effective and cost-efficient strategies for your media setup. + Want to give your viewers a personalized video experience? Our [stitcher tool](/guide/stitch) lets you change the video order in real-time, so each viewer gets something unique. -## Core Standards +- Client streaming library -We believe in sticking to the tried-and-true standards that make video delivery easier for everyone. By using common formats like H.264, HEVC, AAC, EC-3, ... and streaming methods like HLS, we make sure our platform works smoothly across all devices. When it comes to ads, we stick to IAB VAST for placements, which helps us connect easily with different advertising networks. + Need an easy way to work with [HLS.js](https://github.com/video-dev/hls.js)? Our player's facade makes it super simple to integrate streaming into your project, without dealing with complex video library code. Just focus on your content, and let the player handle the rest. -Video is already a pretty fragmented space. By sticking to our standards, we aim to cut through that confusion and make things more straightforward. When you don't have to tackle everything at once, it's much easier to strive for perfection. That's why we made these thoughtful choices: +- Player -- HLS as a playlist format, with CMAF containered segments. -- Inserting and playing other playlists, like ads, depends completely on [HLS interstitials](https://developer.apple.com/streaming/GettingStartedWithHLSInterstitials.pdf). -- No VAST on the client, ever. 😜 -- Don't reinvent the wheel β€” leverage the fantastic work of others. If that means making open-source contributions, we're all for it. \ No newline at end of file + Want a beautiful, modern video player? Our React component offers an eye-catching design and smooth functionality, giving your viewers an amazing experience. \ No newline at end of file diff --git a/docs/guide/whats-included.md b/docs/guide/whats-included.md new file mode 100644 index 00000000..3fa8ea74 --- /dev/null +++ b/docs/guide/whats-included.md @@ -0,0 +1,53 @@ +# What's included + +Superstreamer is a monorepo, with multiple smaller projects inside. We'll provide a quick overview of the packages below to give you a sense of what each one does. + +
+

App

+

+ A Single Page Application (SPA) used to interact with the API or start a session on the Stitcher service. +

+
+ +
+
+ +
+

API

+

+ The API serves as the primary interface for interacting with Superstreamer, such as start tasks like transcoding or packaging jobs. A swagger page is exposed on the /swagger endpoint. +

+
+ +
+
+ +
+

Artisan

+

+ The actual job runners, these run in the background and consume whatever job API has scheduled next. Artisan instructs ffmpeg to run, or packages a previously transcoded asset to an HLS playlist and syncs it all to S3. +

+
+ +
+
+ +
+

Stitcher

+

+ Also referred to as a "playlist manipulator," Stitcher can create a session for each user and generate a custom HLS playlist tailored to their needs, including resolution filtering and the addition of bumpers or linear ads. The stitcher has its own API. A swagger page is exposed on the /swagger endpoint. +

+
+ +
+
+ +
+

Player

+

+ The player facade simplifies HLS.js with an intuitive API for building the player. It offers player-friendly methods, supports plugins, and provides React hooks for efficient state management. +

+
+ +
+
diff --git a/docs/index.md b/docs/index.md index 88845fe1..d823b9ad 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,8 +4,8 @@ layout: home hero: name: Superstreamer - text: Effortless video - tagline: All-in-one toolkit from ingest to adaptive video playback. + text: Deliver video + tagline: Building blocks for video streaming that's smooth, simple, and scales like a champ! actions: - theme: brand text: Introduction @@ -32,12 +32,12 @@ features: src: /icon-package.svg width: 40 - title: Stitcher - details: Manipulate and craft HLS playlists on the fly, supports HLS interstitials. + details: Personalize and tailor each video on the fly with HLS interstitials, customized for every viewer. icon: src: /icon-stitcher.svg width: 54 - title: Player - details: A unified HLS.js API and React components that integrate seamlessly. + details: A unified API and React components that seamlessly integrate into your player. icon: src: /icon-player.svg width: 37 diff --git a/docs/public/button-dockerhub.webp b/docs/public/button-dockerhub.webp new file mode 100644 index 00000000..0843b3f2 Binary files /dev/null and b/docs/public/button-dockerhub.webp differ diff --git a/docs/public/button-npm.webp b/docs/public/button-npm.webp new file mode 100644 index 00000000..5efa94dd Binary files /dev/null and b/docs/public/button-npm.webp differ diff --git a/docs/public/dashboard-jobs.mp4 b/docs/public/dashboard-jobs.mp4 deleted file mode 100644 index 73b5baf3..00000000 Binary files a/docs/public/dashboard-jobs.mp4 and /dev/null differ diff --git a/docs/public/dashboard-stitcher.mp4 b/docs/public/dashboard-stitcher.mp4 deleted file mode 100644 index c05a0761..00000000 Binary files a/docs/public/dashboard-stitcher.mp4 and /dev/null differ diff --git a/docs/public/dashboard-storage.mp4 b/docs/public/dashboard-storage.mp4 deleted file mode 100644 index be836c87..00000000 Binary files a/docs/public/dashboard-storage.mp4 and /dev/null differ diff --git a/docs/public/company-cloudflare.jpg b/docs/public/logo-cloudflare.jpg similarity index 100% rename from docs/public/company-cloudflare.jpg rename to docs/public/logo-cloudflare.jpg diff --git a/docs/public/logo-ffmpeg.png b/docs/public/logo-ffmpeg.png new file mode 100644 index 00000000..14fbf119 Binary files /dev/null and b/docs/public/logo-ffmpeg.png differ diff --git a/docs/public/logo-hlsjs.png b/docs/public/logo-hlsjs.png new file mode 100644 index 00000000..93e08c85 Binary files /dev/null and b/docs/public/logo-hlsjs.png differ diff --git a/docs/public/logo-montevideo.png b/docs/public/logo-montevideo.png new file mode 100644 index 00000000..b7d782a4 Binary files /dev/null and b/docs/public/logo-montevideo.png differ diff --git a/docs/public/logo-mux.png b/docs/public/logo-mux.png new file mode 100644 index 00000000..c4534767 Binary files /dev/null and b/docs/public/logo-mux.png differ diff --git a/docs/public/logo-qualabs.png b/docs/public/logo-qualabs.png new file mode 100644 index 00000000..70248c98 Binary files /dev/null and b/docs/public/logo-qualabs.png differ diff --git a/docs/public/logo-shaka-packager.png b/docs/public/logo-shaka-packager.png new file mode 100644 index 00000000..67f94407 Binary files /dev/null and b/docs/public/logo-shaka-packager.png differ diff --git a/docs/public/schema-dashboard.png b/docs/public/schema-dashboard.png deleted file mode 100644 index 8f9e9aa6..00000000 Binary files a/docs/public/schema-dashboard.png and /dev/null differ diff --git a/docs/public/tutorials/bumper-play.mp4 b/docs/public/tutorials/bumper-play.mp4 new file mode 100644 index 00000000..60d48aa9 Binary files /dev/null and b/docs/public/tutorials/bumper-play.mp4 differ diff --git a/docs/public/tutorials/bumper-stitcher.mp4 b/docs/public/tutorials/bumper-stitcher.mp4 new file mode 100644 index 00000000..b0b13c4e Binary files /dev/null and b/docs/public/tutorials/bumper-stitcher.mp4 differ diff --git a/docs/public/tutorials/play-adaptive-video-input.mp4 b/docs/public/tutorials/play-adaptive-video-input.mp4 new file mode 100644 index 00000000..cfbd184a Binary files /dev/null and b/docs/public/tutorials/play-adaptive-video-input.mp4 differ diff --git a/docs/public/tutorials/play-adaptive-video-package.mp4 b/docs/public/tutorials/play-adaptive-video-package.mp4 new file mode 100644 index 00000000..adf17d78 Binary files /dev/null and b/docs/public/tutorials/play-adaptive-video-package.mp4 differ diff --git a/docs/public/tutorials/play-adaptive-video-play.mp4 b/docs/public/tutorials/play-adaptive-video-play.mp4 new file mode 100644 index 00000000..2d513a36 Binary files /dev/null and b/docs/public/tutorials/play-adaptive-video-play.mp4 differ diff --git a/docs/public/tutorials/play-adaptive-video-storage-transcode.png b/docs/public/tutorials/play-adaptive-video-storage-transcode.png new file mode 100644 index 00000000..93ce3307 Binary files /dev/null and b/docs/public/tutorials/play-adaptive-video-storage-transcode.png differ diff --git a/docs/public/tutorials/play-adaptive-video-storage.mp4 b/docs/public/tutorials/play-adaptive-video-storage.mp4 new file mode 100644 index 00000000..17061673 Binary files /dev/null and b/docs/public/tutorials/play-adaptive-video-storage.mp4 differ diff --git a/docs/public/tutorials/play-adaptive-video-transcode.mp4 b/docs/public/tutorials/play-adaptive-video-transcode.mp4 new file mode 100644 index 00000000..0fe37b3e Binary files /dev/null and b/docs/public/tutorials/play-adaptive-video-transcode.mp4 differ diff --git a/packages/stitcher/src/interstitials.ts b/packages/stitcher/src/interstitials.ts index ccf6f375..97020d29 100644 --- a/packages/stitcher/src/interstitials.ts +++ b/packages/stitcher/src/interstitials.ts @@ -12,7 +12,7 @@ import type { DateTime } from "luxon"; export type InterstitialType = "ad" | "bumper"; export interface Interstitial { - position: number; + timeOffset: number; url: string; duration?: number; type?: InterstitialType; @@ -38,7 +38,7 @@ export function getStaticDateRanges(startTime: DateTime, session: Session) { if (session.interstitials) { for (const interstitial of session.interstitials) { - group.add(interstitial.position, interstitial.type); + group.add(interstitial.timeOffset, interstitial.type); } } @@ -125,7 +125,7 @@ async function getAssetsFromGroup( const assets: InterstitialAsset[] = []; for (const interstitial of interstitials) { - if (interstitial.position !== timeOffset) { + if (interstitial.timeOffset !== timeOffset) { continue; } diff --git a/packages/stitcher/src/routes/session.ts b/packages/stitcher/src/routes/session.ts index cbab36fb..8cf644df 100644 --- a/packages/stitcher/src/routes/session.ts +++ b/packages/stitcher/src/routes/session.ts @@ -62,7 +62,7 @@ export const sessionRoutes = new Elysia() interstitials: t.Optional( t.Array( t.Object({ - position: t.Number(), + timeOffset: t.Number(), uri: t.String(), duration: t.Optional(t.Number()), type: t.Optional(t.Union([t.Literal("ad"), t.Literal("bumper")])), diff --git a/packages/stitcher/src/session.ts b/packages/stitcher/src/session.ts index 48c7731d..ca2cb1df 100644 --- a/packages/stitcher/src/session.ts +++ b/packages/stitcher/src/session.ts @@ -26,7 +26,7 @@ export async function createSession(params: { url: string; }; interstitials?: { - position: number; + timeOffset: number; uri: string; duration?: number; type?: InterstitialType; @@ -46,7 +46,7 @@ export async function createSession(params: { if (params.interstitials) { session.interstitials = params.interstitials.map((interstitial) => { return { - position: interstitial.position, + timeOffset: interstitial.timeOffset, url: resolveUri(interstitial.uri), duration: interstitial.duration, type: interstitial.type,