> ## Documentation Index
> Fetch the complete documentation index at: https://cloudinary.com/documentation/llms.txt
> Use this file to discover all available pages before exploring further.

# Adaptive bitrate streaming


Adaptive bitrate streaming is a video delivery technique that adjusts the quality of a video stream in real time according to detected bandwidth and CPU capacity. This enables videos to start quicker, with fewer buffering interruptions, and at the best possible quality for the current device and network connection, to maximize user experience.

Cloudinary can automatically generate and deliver HLS or MPEG-DASH adaptive bitrate streaming videos at the required quality levels, including generating all of the required files from a single original video asset.

## Adaptive bitrate streaming in Next.js video tutorial

Watch this video to learn how to add adaptive bitrate streaming to a Next.js application using the CldVideoPlayer component from Next Cloudinary.

  This video is brought to you by Cloudinary's video player - embed your own!Use the controls to set the playback speed, navigate to chapters of interest and select subtitles in your preferred language.

### Tutorial contents This tutorial presents the following topics. Click a timestamp to jump to that part of the video.### Introduction to Adaptive Streaming
{table:class=tutorial-bullets}|  | 
| --- | --- |
|{videotime:id=media :min=0 :sec=00 :player=cld} | [Adaptive bitrate streaming](adaptive_bitrate_streaming) (ABR) automatically adjusts video quality based on the viewer's network speed and CPU capacity. This prevents buffering and pausing, keeping playback smooth even on slower connections. |

### How ABR Works
{table:class=tutorial-bullets}|  | 
| --- | --- |
|{videotime:id=media :min=0 :sec=36 :player=cld} | ABR works by switching between different renditions based on real-time network and device conditions. Instead of buffering at one fixed quality, playback adapts to maintain a smoother viewer experience. |

### Setting Up in Next.js
{table:class=tutorial-bullets}|  | 
| --- | --- |
|{videotime:id=media :min=1 :sec=08 :player=cld} | Starting with a basic Next.js application using the [Next.js SDK](nextjs_integration) and the [CldVideoPlayer](nextjs_video_transformations) component, you can add ABR support with just a few props. |

### Implementing HLS and DASH
{table:class=tutorial-bullets}|  | 
| --- | --- |
|{videotime:id=media :min=1 :sec=50 :player=cld} | Add the `sourceTypes` property to the `CldVideoPlayer` component with an array containing `hls` (HTTP Live Streaming) and optionally `dash` (MPEG-DASH), then apply a [streaming profile](adaptive_bitrate_streaming#predefined_streaming_profiles) such as `hd` using the `transformation` prop. |

### Testing the Setup
{table:class=tutorial-bullets}|  | 
| --- | --- |
|{videotime:id=media :min=2 :sec=27 :player=cld} | To verify ABR is working, use Chrome DevTools to throttle the network speed (e.g., Slow 3G). The video will load briefly then play at a lower quality instead of buffering. You may need to do an empty cache and hard reload when testing locally. |

## How adaptive bitrate streaming works

When adaptive bitrate streaming is applied, then as a video begins to download, the client can play the first few seconds of the movie at a low quality to get started quickly. When the user's video buffer is full and assuming CPU utilization is low, the client player may switch to a higher quality stream to enhance the viewing experience. If the buffer later drops below certain levels, or CPU utilization jumps above certain thresholds, the client may switch to a lower quality stream.

At the start of the streaming session, the client software downloads a `master playlist file` containing the metadata for the various sub-streams that are available. The client software then decides what to download from the available media files, based on predefined factors such as device type, resolution, current bandwidth, size, etc.

To deliver videos using adaptive streaming, you need multiple copies of your video prepared at different resolutions, qualities, and data rates. These are called video `representations` (also sometimes known as `variants`). You also need index and streaming files for each representation. The  master file references the available representations, provides information required for the client to choose between them, and includes other required metadata, depending on the delivery protocol.

Cloudinary can automatically generate and deliver all of these files from a single original video, transcoded to either or both of the following protocols: 

* HTTP Live Streaming (HLS) 
* Dynamic Adaptive Streaming over HTTP (MPEG-DASH)

See also: [HTTP Live Streaming feature overview](https://cloudinary.com/features/http_live_streaming).

### Delivering adaptive bitrate streaming videos

To deliver videos from Cloudinary using HLS or MPEG-DASH, you can either let Cloudinary [automatically choose the best streaming profile](#automatic_streaming_profile_selection), or [manually select your own](#manual_streaming_profile_selection).

> **NOTE**: There is a difference in how [transformations are counted](transformation_counts#adaptive_bitrate_streaming) for each of these solutions.
 

## Automatic streaming profile selection

The easiest way to deliver videos from Cloudinary using HLS or DASH adaptive bitrate streaming is to let Cloudinary choose the best streaming profile on the fly. This includes support for 4k output by setting the [maximum resolution](#setting_the_maximum_resolution).

Set the `streaming_profile` parameter to `auto` (`sp_auto` in URLs), and specify an extension of either `.m3u8` for HLS or `.mpd` for DASH. Automatic streaming profile selection uses the CMAF format and your video will be processed for streaming using either of the protocols on first request.

Here's an example using HLS:

![Waterfall](https://res.cloudinary.com/demo/video/upload/sp_auto/docs/waterfall.m3u8 "with_image: false")

```nodejs
cloudinary.url("docs/waterfall.m3u8", {streaming_profile: "auto", resource_type: "video"})
```

```react
new CloudinaryVideo("docs/waterfall.m3u8").transcode(streamingProfile("auto"));
```

```vue
new CloudinaryVideo("docs/waterfall.m3u8").transcode(streamingProfile("auto"));
```

```angular
new CloudinaryVideo("docs/waterfall.m3u8").transcode(streamingProfile("auto"));
```

```js
new CloudinaryVideo("docs/waterfall.m3u8").transcode(streamingProfile("auto"));
```

```python
cloudinary.utils.cloudinary_url("docs/waterfall.m3u8", streaming_profile="auto", resource_type="video")
```

```php
(new VideoTag('docs/waterfall.m3u8'))
	->transcode(Transcode::streamingProfile("auto"));
```

```java
cloudinary.url().transformation(new Transformation().streamingProfile("auto")).resourceType("video").generate("docs/waterfall.m3u8")
```

```ruby
cloudinary_url("docs/waterfall.m3u8", streaming_profile: "auto", resource_type: "video")
```

```csharp
cloudinary.Api.UrlVideoUp.Transform(new Transformation().StreamingProfile("auto")).BuildUrl("docs/waterfall.m3u8")
```

```dart
cloudinary.video('docs/waterfall.m3u8').transformation(Transformation()
	.transcode(Transcode.streamingProfile("auto")));
```

```swift
cloudinary.createUrl().setResourceType("video").setTransformation(CLDTransformation().setStreamingProfile("auto")).generate("docs/waterfall.m3u8")
```

```android
MediaManager.get().url().transformation(new Transformation().streamingProfile("auto")).resourceType("video").generate("docs/waterfall.m3u8");
```

```flutter
cloudinary.video('docs/waterfall.m3u8').transformation(Transformation()
	.transcode(Transcode.streamingProfile("auto")));
```

```kotlin
cloudinary.video {
	publicId("docs/waterfall.m3u8")
	 transcode(Transcode.streamingProfile("auto")) 
}.generate()
```

```jquery
$.cloudinary.url("docs/waterfall.m3u8", {streaming_profile: "auto", resource_type: "video"})
```

```react_native
new CloudinaryVideo("docs/waterfall.m3u8").transcode(streamingProfile("auto"));
```

> **NOTES**:
>
> * Depending on your browser, you may not be able to play the streamed video without the use of a video player that supports adaptive bitrate streaming.  The video above is displayed using the [Cloudinary video player](cloudinary_video_player).

> * When requesting a DASH stream, you will receive a 423 response until the video has been processed.

> * Currently, a limited set of video transformations are supported for use together with `sp_auto`. See examples of transformations that you can apply in [Combining transformations with automatic streaming profile selection](#combining_transformations_with_automatic_streaming_profile_selection).

**See full syntax**: [sp_auto](transformation_reference#sp_auto) in the _Transformation Reference_.

### Setting the maximum resolution

To optimize bandwidth usage and give you better expense predictability when using `sp_auto`, you can limit the resolution of the streamed video. 

The default maximum resolution is 1080p, but you can also choose from these other options: 2160p, 1440p, 720p, 540p, and 360p.

For example, setting the maximum resolution to 360p (`sp_auto:maxres_360p`):

![Waterfall](https://res.cloudinary.com/demo/video/upload/sp_auto:maxres_360p/docs/waterfall.mpd "with_image: false")

```nodejs
cloudinary.url("docs/waterfall.mpd", {streaming_profile: "auto:maxres_360p", resource_type: "video"})
```

```react
new CloudinaryVideo("docs/waterfall.mpd").transcode(
  streamingProfile("auto:maxres_360p")
);
```

```vue
new CloudinaryVideo("docs/waterfall.mpd").transcode(
  streamingProfile("auto:maxres_360p")
);
```

```angular
new CloudinaryVideo("docs/waterfall.mpd").transcode(
  streamingProfile("auto:maxres_360p")
);
```

```js
new CloudinaryVideo("docs/waterfall.mpd").transcode(
  streamingProfile("auto:maxres_360p")
);
```

```python
cloudinary.utils.cloudinary_url("docs/waterfall.mpd", streaming_profile="auto:maxres_360p", resource_type="video")
```

```php
(new VideoTag('docs/waterfall.mpd'))
	->transcode(Transcode::streamingProfile("auto:maxres_360p"));
```

```java
cloudinary.url().transformation(new Transformation().streamingProfile("auto:maxres_360p")).resourceType("video").generate("docs/waterfall.mpd")
```

```ruby
cloudinary_url("docs/waterfall.mpd", streaming_profile: "auto:maxres_360p", resource_type: "video")
```

```csharp
cloudinary.Api.UrlVideoUp.Transform(new Transformation().StreamingProfile("auto:maxres_360p")).BuildUrl("docs/waterfall.mpd")
```

```dart
cloudinary.video('docs/waterfall.mpd').transformation(Transformation()
	.transcode(Transcode.streamingProfile("auto:maxres_360p")));
```

```swift
cloudinary.createUrl().setResourceType("video").setTransformation(CLDTransformation().setStreamingProfile("auto:maxres_360p")).generate("docs/waterfall.mpd")
```

```android
MediaManager.get().url().transformation(new Transformation().streamingProfile("auto:maxres_360p")).resourceType("video").generate("docs/waterfall.mpd");
```

```flutter
cloudinary.video('docs/waterfall.mpd').transformation(Transformation()
	.transcode(Transcode.streamingProfile("auto:maxres_360p")));
```

```kotlin
cloudinary.video {
	publicId("docs/waterfall.mpd")
	 transcode(Transcode.streamingProfile("auto:maxres_360p")) 
}.generate()
```

```jquery
$.cloudinary.url("docs/waterfall.mpd", {streaming_profile: "auto:maxres_360p", resource_type: "video"})
```

```react_native
new CloudinaryVideo("docs/waterfall.mpd").transcode(
  streamingProfile("auto:maxres_360p")
);
```

Or, for 4K delivery (`sp_auto:maxres_2160p`):

![Flower bouquet](https://res.cloudinary.com/demo/video/upload/sp_auto:maxres_2160p/docs/flower-bouquet.mpd "with_image: false")

```nodejs
cloudinary.url("docs/flower-bouquet.mpd", {streaming_profile: "auto:maxres_2160p", resource_type: "video"})
```

```react
new CloudinaryVideo("docs/flower-bouquet.mpd").transcode(
  streamingProfile("auto:maxres_2160p")
);
```

```vue
new CloudinaryVideo("docs/flower-bouquet.mpd").transcode(
  streamingProfile("auto:maxres_2160p")
);
```

```angular
new CloudinaryVideo("docs/flower-bouquet.mpd").transcode(
  streamingProfile("auto:maxres_2160p")
);
```

```js
new CloudinaryVideo("docs/flower-bouquet.mpd").transcode(
  streamingProfile("auto:maxres_2160p")
);
```

```python
cloudinary.utils.cloudinary_url("docs/flower-bouquet.mpd", streaming_profile="auto:maxres_2160p", resource_type="video")
```

```php
(new VideoTag('docs/flower-bouquet.mpd'))
	->transcode(Transcode::streamingProfile("auto:maxres_2160p"));
```

```java
cloudinary.url().transformation(new Transformation().streamingProfile("auto:maxres_2160p")).resourceType("video").generate("docs/flower-bouquet.mpd")
```

```ruby
cloudinary_url("docs/flower-bouquet.mpd", streaming_profile: "auto:maxres_2160p", resource_type: "video")
```

```csharp
cloudinary.Api.UrlVideoUp.Transform(new Transformation().StreamingProfile("auto:maxres_2160p")).BuildUrl("docs/flower-bouquet.mpd")
```

```dart
cloudinary.video('docs/flower-bouquet.mpd').transformation(Transformation()
	.transcode(Transcode.streamingProfile("auto:maxres_2160p")));
```

```swift
cloudinary.createUrl().setResourceType("video").setTransformation(CLDTransformation().setStreamingProfile("auto:maxres_2160p")).generate("docs/flower-bouquet.mpd")
```

```android
MediaManager.get().url().transformation(new Transformation().streamingProfile("auto:maxres_2160p")).resourceType("video").generate("docs/flower-bouquet.mpd");
```

```flutter
cloudinary.video('docs/flower-bouquet.mpd').transformation(Transformation()
	.transcode(Transcode.streamingProfile("auto:maxres_2160p")));
```

```kotlin
cloudinary.video {
	publicId("docs/flower-bouquet.mpd")
	 transcode(Transcode.streamingProfile("auto:maxres_2160p")) 
}.generate()
```

```jquery
$.cloudinary.url("docs/flower-bouquet.mpd", {streaming_profile: "auto:maxres_2160p", resource_type: "video"})
```

```react_native
new CloudinaryVideo("docs/flower-bouquet.mpd").transcode(
  streamingProfile("auto:maxres_2160p")
);
```

> **NOTES**:
>
> * The possible values for `maxres` refer to the shorter edge of the video.  For example, 720p is 720 pixels by 1280 pixels, regardless of the orientation of the video (portrait or landscape).

> * When setting `maxres` to `2160p` for 4K output or `1440p` for 2K, the processing of all renditions is asynchronous and you receive a `423` response on initial request.

> * The input resolution also limits the final delivered resolution, no upscaling occurs when the input is lower than the value of `maxres`.

### Defining alternate audio tracks

When using automatic streaming profiles, you can take advantage of defining alternate audio tracks that will be added to your generated manifest file. This allows you to enhance accessibility, support multiple languages, or offer unique audio experiences such as descriptive soundtracks. When delivered using a compatible video player (such as the [Cloudinary Video Player](cloudinary_video_player)), users will be able to select their preferred audio track from the UI.

To define additional audio tracks, use the [audio layer](transformation_reference#l_audio) transformation (`l_audio`) with the alternate flag (`fl_alternate`). This flag takes two additional parameters used to describe the language of the audio track and the name that's displayed in the UI:

* `lang` - the IETF language tag and optional regional tag. For example `lang_en-US`.
* `name` - An optional name for the audio track that will be shown in the player UI. For example `name_Description`. If no name is provided, the name will be inferred from the language.

Here's an example showing a video that now has an alternate instrumental audio track as well as the original vocal track:

![multiple audio track example](https://res.cloudinary.com/demo/video/upload/fl_alternate:lang_en;name_Original,l_audio:outdoors/fl_layer_apply/fl_alternate:lang_en;name_Instrumental,l_audio:docs:instrumental-short/fl_layer_apply/sp_auto/outdoors.m3u8 "with_image: false, with_code: false")

The transformation is broken down as follows:

* `fl_alternate:lang_en;name_Original,l_audio:outdoors` - adds the original audio from the video using the same public ID (`outdoors`). The alternate flag is added with the language set to `en` and the name set to `Original`.
* `fl_alternate:lang_en;name_Instrumental,l_audio:docs:instrumental-short` - adds another audio layer, setting the language again to `en` but this time with the name `Instrumental`.
* `sp_auto` - applies automatic streaming profile selection.

And when delivered using the Cloudinary Video Player, the audio selection menu is visible:

> **NOTES**:
>
> * The `fl_alternate` flag is not currently supported by our SDKs.

> * All audio tracks must be added as a layer, including the original track, otherwise the first audio layer will be used as the default.

> * [Audio transformations](audio_transformations) can be applied to an audio layer as normal, for example to adjust the volume of a particular track (`e_volume`).

### Interactive demo: automatic streaming profile selection

See various videos being streamed using automatic streaming profile selection in this [interactive demo](https://cloudinaryltd.github.io/sp_auto/).

  
    
  

### Combining transformations with automatic streaming profile selection

When combining automatic streaming profile selection (`sp_auto`) with other transformations, you cannot include those transformations in the same component as `sp_auto`, so they need to be [chained](image_transformations#chained_transformations).

Here are some examples of `sp_auto` with chained transformations:

* [Trimming](#trimming)
* [Image overlays](#adding_image_overlays)
* [Subtitles](#adding_subtitles)
* [Audio normalization](#normalizing_audio)

#### Trimming

The following example shows a 21 second video trimmed down to 7 seconds, starting from second 3 (`so_3.0`), and finishing at second 10 (`eo_10.0`):

![Car film](https://res.cloudinary.com/demo/video/upload/sp_auto/so_3.0/eo_10.0/docs/car-film.m3u8 "with_image: false")

```nodejs
cloudinary.url("docs/car-film.m3u8", {resource_type: "video", transformation: [
  {streaming_profile: "auto"},
  {start_offset: "3.0"},
  {end_offset: "10.0"}
  ]})
```

```react
new CloudinaryVideo("docs/car-film.m3u8")
  .transcode(streamingProfile("auto"))
  .videoEdit(trim().startOffset("3.0"))
  .videoEdit(trim().endOffset("10.0"));
```

```vue
new CloudinaryVideo("docs/car-film.m3u8")
  .transcode(streamingProfile("auto"))
  .videoEdit(trim().startOffset("3.0"))
  .videoEdit(trim().endOffset("10.0"));
```

```angular
new CloudinaryVideo("docs/car-film.m3u8")
  .transcode(streamingProfile("auto"))
  .videoEdit(trim().startOffset("3.0"))
  .videoEdit(trim().endOffset("10.0"));
```

```js
new CloudinaryVideo("docs/car-film.m3u8")
  .transcode(streamingProfile("auto"))
  .videoEdit(trim().startOffset("3.0"))
  .videoEdit(trim().endOffset("10.0"));
```

```python
cloudinary.utils.cloudinary_url("docs/car-film.m3u8", resource_type="video", transformation=[
  {'streaming_profile': "auto"},
  {'start_offset': "3.0"},
  {'end_offset': "10.0"}
  ])
```

```php
(new VideoTag('docs/car-film.m3u8'))
	->transcode(Transcode::streamingProfile("auto"))
	->videoEdit(VideoEdit::trim()->startOffset(3.0))
	->videoEdit(VideoEdit::trim()->endOffset(10.0));
```

```java
cloudinary.url().transformation(new Transformation()
  .streamingProfile("auto").chain()
  .startOffset("3.0").chain()
  .endOffset("10.0")).resourceType("video").generate("docs/car-film.m3u8")
```

```ruby
cloudinary_url("docs/car-film.m3u8", resource_type: "video", transformation: [
  {streaming_profile: "auto"},
  {start_offset: "3.0"},
  {end_offset: "10.0"}
  ])
```

```csharp
cloudinary.Api.UrlVideoUp.Transform(new Transformation()
  .StreamingProfile("auto").Chain()
  .StartOffset("3.0").Chain()
  .EndOffset("10.0")).BuildUrl("docs/car-film.m3u8")
```

```dart
cloudinary.video('docs/car-film.m3u8').transformation(Transformation()
	.transcode(Transcode.streamingProfile("auto"))
	.videoEdit(VideoEdit.trim().startOffset('3.0'))
	.videoEdit(VideoEdit.trim().endOffset('10.0')));
```

```swift
cloudinary.createUrl().setResourceType("video").setTransformation(CLDTransformation()
  .setStreamingProfile("auto").chain()
  .setStartOffset("3.0").chain()
  .setEndOffset("10.0")).generate("docs/car-film.m3u8")
```

```android
MediaManager.get().url().transformation(new Transformation()
  .streamingProfile("auto").chain()
  .startOffset("3.0").chain()
  .endOffset("10.0")).resourceType("video").generate("docs/car-film.m3u8");
```

```flutter
cloudinary.video('docs/car-film.m3u8').transformation(Transformation()
	.transcode(Transcode.streamingProfile("auto"))
	.videoEdit(VideoEdit.trim().startOffset('3.0'))
	.videoEdit(VideoEdit.trim().endOffset('10.0')));
```

```kotlin
cloudinary.video {
	publicId("docs/car-film.m3u8")
	 transcode(Transcode.streamingProfile("auto"))
	 videoEdit(VideoEdit.trim() { startOffset(3.0F) })
	 videoEdit(VideoEdit.trim() { endOffset(10.0F) }) 
}.generate()
```

```jquery
$.cloudinary.url("docs/car-film.m3u8", {resource_type: "video", transformation: [
  {streaming_profile: "auto"},
  {start_offset: "3.0"},
  {end_offset: "10.0"}
  ]})
```

```react_native
new CloudinaryVideo("docs/car-film.m3u8")
  .transcode(streamingProfile("auto"))
  .videoEdit(trim().startOffset("3.0"))
  .videoEdit(trim().endOffset("10.0"));
```

#### Adding image overlays

The following example shows the Cloudinary logo overlaid on the video (`l_cloudinary_icon_blue`) in the top-left corner (`fl_layer_apply,g_north_west`):

![Car film](https://res.cloudinary.com/demo/video/upload/sp_auto/l_cloudinary_icon_blue/fl_layer_apply,g_north_west/docs/car-film.m3u8 "with_image: false")

```nodejs
cloudinary.url("docs/car-film.m3u8", {resource_type: "video", transformation: [
  {streaming_profile: "auto"},
  {overlay: "cloudinary_icon_blue"},
  {flags: "layer_apply", gravity: "north_west"}
  ]})
```

```react
new CloudinaryVideo("docs/car-film.m3u8")
  .transcode(streamingProfile("auto"))
  .overlay(
    source(image("cloudinary_icon_blue")).position(
      new Position().gravity(compass("north_west"))
    )
  );
```

```vue
new CloudinaryVideo("docs/car-film.m3u8")
  .transcode(streamingProfile("auto"))
  .overlay(
    source(image("cloudinary_icon_blue")).position(
      new Position().gravity(compass("north_west"))
    )
  );
```

```angular
new CloudinaryVideo("docs/car-film.m3u8")
  .transcode(streamingProfile("auto"))
  .overlay(
    source(image("cloudinary_icon_blue")).position(
      new Position().gravity(compass("north_west"))
    )
  );
```

```js
new CloudinaryVideo("docs/car-film.m3u8")
  .transcode(streamingProfile("auto"))
  .overlay(
    source(image("cloudinary_icon_blue")).position(
      new Position().gravity(compass("north_west"))
    )
  );
```

```python
cloudinary.utils.cloudinary_url("docs/car-film.m3u8", resource_type="video", transformation=[
  {'streaming_profile': "auto"},
  {'overlay': "cloudinary_icon_blue"},
  {'flags': "layer_apply", 'gravity': "north_west"}
  ])
```

```php
(new VideoTag('docs/car-film.m3u8'))
	->transcode(Transcode::streamingProfile("auto"))
	->overlay(Overlay::source(
	Source::image("cloudinary_icon_blue"))
	->position((new Position())
	->gravity(
	Gravity::compass(
	Compass::northWest()))
	)
	);
```

```java
cloudinary.url().transformation(new Transformation()
  .streamingProfile("auto").chain()
  .overlay(new Layer().publicId("cloudinary_icon_blue")).chain()
  .flags("layer_apply").gravity("north_west")).resourceType("video").generate("docs/car-film.m3u8")
```

```ruby
cloudinary_url("docs/car-film.m3u8", resource_type: "video", transformation: [
  {streaming_profile: "auto"},
  {overlay: "cloudinary_icon_blue"},
  {flags: "layer_apply", gravity: "north_west"}
  ])
```

```csharp
cloudinary.Api.UrlVideoUp.Transform(new Transformation()
  .StreamingProfile("auto").Chain()
  .Overlay(new Layer().PublicId("cloudinary_icon_blue")).Chain()
  .Flags("layer_apply").Gravity("north_west")).BuildUrl("docs/car-film.m3u8")
```

```dart
cloudinary.video('docs/car-film.m3u8').transformation(Transformation()
	.transcode(Transcode.streamingProfile("auto"))
	.overlay(Overlay.source(
	Source.image("cloudinary_icon_blue"))
	.position(Position()
	.gravity(
	Gravity.compass(
	Compass.northWest()))
	)
	));
```

```swift
cloudinary.createUrl().setResourceType("video").setTransformation(CLDTransformation()
  .setStreamingProfile("auto").chain()
  .setOverlay("cloudinary_icon_blue").chain()
  .setFlags("layer_apply").setGravity("north_west")).generate("docs/car-film.m3u8")
```

```android
MediaManager.get().url().transformation(new Transformation()
  .streamingProfile("auto").chain()
  .overlay(new Layer().publicId("cloudinary_icon_blue")).chain()
  .flags("layer_apply").gravity("north_west")).resourceType("video").generate("docs/car-film.m3u8");
```

```flutter
cloudinary.video('docs/car-film.m3u8').transformation(Transformation()
	.transcode(Transcode.streamingProfile("auto"))
	.overlay(Overlay.source(
	Source.image("cloudinary_icon_blue"))
	.position(Position()
	.gravity(
	Gravity.compass(
	Compass.northWest()))
	)
	));
```

```kotlin
cloudinary.video {
	publicId("docs/car-film.m3u8")
	 transcode(Transcode.streamingProfile("auto"))
	 overlay(Overlay.source(
	Source.image("cloudinary_icon_blue")) {
	 position(Position() {
	 gravity(
	Gravity.compass(
	Compass.northWest()))
	 })
	 }) 
}.generate()
```

```jquery
$.cloudinary.url("docs/car-film.m3u8", {resource_type: "video", transformation: [
  {streaming_profile: "auto"},
  {overlay: new cloudinary.Layer().publicId("cloudinary_icon_blue")},
  {flags: "layer_apply", gravity: "north_west"}
  ]})
```

```react_native
new CloudinaryVideo("docs/car-film.m3u8")
  .transcode(streamingProfile("auto"))
  .overlay(
    source(image("cloudinary_icon_blue")).position(
      new Position().gravity(compass("north_west"))
    )
  );
```

#### Adding subtitles

You can add subtitles to videos as explained in [Adding subtitles to HLS videos](adaptive_bitrate_streaming#adding_subtitles_to_hls_videos).  For example, the text specified in the file with public ID `docs/narration.vtt` is used for US English subtitles (`sp_auto:subtitles_((code_en-US;file_docs:narration.vtt))`) with this video: 

![Car film with subtitles](https://res.cloudinary.com/demo/video/upload/sp_auto:subtitles_(code_en-US;file_docs:narration.vtt)

```nodejs
cloudinary.image("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt", {resource_type: "video"})
```

```react
new CloudinaryVideo("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt");
```

```vue
new CloudinaryVideo("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt");
```

```angular
new CloudinaryVideo("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt");
```

```js
new CloudinaryVideo("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt");
```

```python
CloudinaryVideo("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt").image()
```

```php
(new VideoTag('sp_auto:subtitles_(code_en-US;file_docs:narration.vtt'));
```

```java
cloudinary.url().transformation(new Transformation().resourceType("video").imageTag("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt");
```

```ruby
cl_image_tag("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt", resource_type: "video")
```

```csharp
cloudinary.Api.UrlVideoUp.BuildImageTag("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt")
```

```dart
cloudinary.video('sp_auto:subtitles_(code_en-US;file_docs:narration.vtt').transformation(Transformation());
```

```swift
cloudinary.createUrl().setResourceType("video").generate("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt")
```

```android
MediaManager.get().url().transformation(new Transformation().resourceType("video").generate("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt");
```

```flutter
cloudinary.video('sp_auto:subtitles_(code_en-US;file_docs:narration.vtt').transformation(Transformation());
```

```kotlin
cloudinary.video {
	publicId("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt") 
}.generate()
```

```jquery
$.cloudinary.image("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt", {resource_type: "video"})
```

```react_native
new CloudinaryVideo("sp_auto:subtitles_(code_en-US;file_docs:narration.vtt");
```/docs/car-film.m3u8 "with_image: false")

#### Normalizing audio

You can normalize the audio in videos with the `e_volume:auto` parameter. This ensures consistent volume levels across different sections of the same video. In the following example of mixed videos with different volume levels, the audio is normalized to reduce the difference in volume between the loud sections and the quiet sections:

![Mixed sounds demo video with normalized audio](https://res.cloudinary.com/demo/video/upload/sp_auto/e_volume:auto/docs/mixed-sounds.m3u8 "with_image: false")

```nodejs
cloudinary.url("docs/mixed-sounds.m3u8", {resource_type: "video", transformation: [
  {streaming_profile: "auto"},
  {effect: "volume:auto"}
  ]})
```

```react
new CloudinaryVideo("docs/mixed-sounds.m3u8")
  .transcode(streamingProfile("auto"))
  .videoEdit(volume(auto()));
```

```vue
new CloudinaryVideo("docs/mixed-sounds.m3u8")
  .transcode(streamingProfile("auto"))
  .videoEdit(volume(auto()));
```

```angular
new CloudinaryVideo("docs/mixed-sounds.m3u8")
  .transcode(streamingProfile("auto"))
  .videoEdit(volume(auto()));
```

```js
new CloudinaryVideo("docs/mixed-sounds.m3u8")
  .transcode(streamingProfile("auto"))
  .videoEdit(volume(auto()));
```

```python
cloudinary.utils.cloudinary_url("docs/mixed-sounds.m3u8", resource_type="video", transformation=[
  {'streaming_profile': "auto"},
  {'effect': "volume:auto"}
  ])
```

```php
(new VideoTag('docs/mixed-sounds.m3u8'))
	->transcode(Transcode::streamingProfile("auto"))
	->videoEdit(VideoEdit::volume(
	Volume::auto()));
```

```java
cloudinary.url().transformation(new Transformation()
  .streamingProfile("auto").chain()
  .effect("volume:auto")).resourceType("video").generate("docs/mixed-sounds.m3u8")
```

```ruby
cloudinary_url("docs/mixed-sounds.m3u8", resource_type: "video", transformation: [
  {streaming_profile: "auto"},
  {effect: "volume:auto"}
  ])
```

```csharp
cloudinary.Api.UrlVideoUp.Transform(new Transformation()
  .StreamingProfile("auto").Chain()
  .Effect("volume:auto")).BuildUrl("docs/mixed-sounds.m3u8")
```

```dart
cloudinary.video('docs/mixed-sounds.m3u8').transformation(Transformation()
	.transcode(Transcode.streamingProfile("auto"))
	.videoEdit(VideoEdit.volume(
	Volume.auto())));
```

```swift
cloudinary.createUrl().setResourceType("video").setTransformation(CLDTransformation()
  .setStreamingProfile("auto").chain()
  .setEffect("volume:auto")).generate("docs/mixed-sounds.m3u8")
```

```android
MediaManager.get().url().transformation(new Transformation()
  .streamingProfile("auto").chain()
  .effect("volume:auto")).resourceType("video").generate("docs/mixed-sounds.m3u8");
```

```flutter
cloudinary.video('docs/mixed-sounds.m3u8').transformation(Transformation()
	.transcode(Transcode.streamingProfile("auto"))
	.videoEdit(VideoEdit.volume(
	Volume.auto())));
```

```kotlin
cloudinary.video {
	publicId("docs/mixed-sounds.m3u8")
	 transcode(Transcode.streamingProfile("auto"))
	 videoEdit(VideoEdit.volume(
	Volume.auto())) 
}.generate()
```

```jquery
$.cloudinary.url("docs/mixed-sounds.m3u8", {resource_type: "video", transformation: [
  {streaming_profile: "auto"},
  {effect: "volume:auto"}
  ]})
```

```react_native
new CloudinaryVideo("docs/mixed-sounds.m3u8")
  .transcode(streamingProfile("auto"))
  .videoEdit(volume(auto()));
```

> **NOTES**:
>
> * `e_volume:auto` is specific to videos delivered with `sp_auto`. 

> * Currently, this is the only [volume option](transformation_reference#e_volume) that you can use with `sp_auto`.

## Manual streaming profile selection

To deliver videos with Cloudinary using HLS or MPEG-DASH adaptive bitrate streaming:

1. [Select a predefined streaming profile](#step_1_select_a_streaming_profile)
2. [Upload your video with an eager transformation](#step_2_upload_your_video_with_an_eager_transformation)
3. [Deliver the video](#step_3_deliver_the_video) 

> **NOTE**: If you have a special use case that does not enable you to use the built-in streaming profiles to automatically generate the master playlist file and all the relevant representations, you can still have Cloudinary create the index and streaming files for each streaming transformation (representation) that you define, and then you can create the master playlist file manually. For details, see [Manually creating representations and master files](#manually_creating_representations_and_master_files).

### Step 1. Select a streaming profile
Cloudinary provides a collection of predefined `streaming profiles`, where each profile defines a set of representations according to suggested best practices. Select a predefined profile based on your performance and codec requirements.

For example, the `4k` profile creates 9 different representations in 16:9 aspect ratio, from extremely high quality to low quality, while the `sd` profile creates only 3 representations, all in 4:3 aspect ratio. Other commonly used profiles include the `hd` and `full_hd` profiles. You can also select a profile that uses a more advanced codec such as `h265` or `vp9`, these profiles create the same representations but with slightly different settings.

Below is the list of predefined streaming profiles for the `h264`, `h265`, `vp9` and `av1` codecs:

|Codec|Profiles|Supported for|
|---|---|---|
|h264| `4k`, `full_hd`, `full_hd_wifi`, `full_hd_lean`, `hd`, `hd_lean`, `sd`| HLS, DASH|
|h265|`4k_h265`, `full_hd_h265`, `full_hd_wifi_h265`, `full_hd_lean_h265`, `hd_h265`, `hd_lean_h265`, `sd_h265`| HLS, DASH|
|vp9| `4k_vp9`, `full_hd_vp9`, `full_hd_wifi_vp9`, `full_hd_lean_vp9`, `hd_vp9`, `hd_lean_vp9`, `sd_vp9`|DASH|
|av1| `4k_av1`, `full_hd_av1`, `full_hd_wifi_av1`, `full_hd_lean_av1`, `hd_av1`, `hd_lean_av1`, `sd_av1`|DASH|

> **NOTE**: AV1 codec profiles require your account plan to use the [video seconds metric](developer_onboarding_faq_video_seconds). If your account plan uses the **video bandwidth metric**, AV1 profiles are not available by default. To discuss options for getting AV1 support, [contact support](https://support.cloudinary.com/hc/en-us/requests/new).

To view a detailed list of settings for each representation, see [Predefined streaming profiles](#predefined_streaming_profiles).

If none of the predefined profiles exactly answers your needs, you can also optionally define custom streaming profiles or even fine-tune the predefined options. You might want a different number of representations, different divisions of quality, different codecs or a different aspect ratio. Or, you might want to apply special transformations for different representations within the profile.

For example, if you want to make use of the more advanced video encoding provided by the `vp9` and `h265` [codecs](video_manipulation_and_delivery#video_codec_settings), or audio encoding provided by the `opus` [codec](audio_transformations#audio_codec_settings), you can create your own streaming profiles (or update the predefined ones). You can use the `h265` codec with both HLS and MPEG-DASH, however the `vp9` and `opus` codecs can only be used with MPEG-DASH. You can [combine codecs](https://cloudinary.com/blog/video_optimization_part_ii_adaptive_bitrate_streaming_of_multiple_codecs) when creating your streaming profiles to ensure the widest support across different browsers and devices. 

Use the [streaming_profiles method of the Admin API](admin_api#streaming_profiles) to create, update, list, delete, or get details of streaming profiles.

### Step 2. Upload your video with an eager transformation

A single streaming profile is comprised of [many derived files](#derived_adaptive_streaming_files), so it can take a while for Cloudinary to generate them all. Therefore, when you upload your video (or later, explicitly), you should include eager, asynchronous transformations with the required streaming profile and video format. 

You can even eagerly prepare your videos for streaming in both formats and you can include other video transformations as well. However, make sure the `streaming_profile` is provided as a separate component of chained transformations.

> **TIP**: You can create an [upload preset](upload_presets) with the `eager` parameter and apply the preset to the upload.

For example, this upload command encodes the `handshake.mp4` video to HLS format using the `full_hd` streaming profile:

```multi
|ruby 
Cloudinary::Uploader.upload("handshake.mp4", resource_type: "video", 
  eager: [{streaming_profile: "full_hd", format: "m3u8"}], 
  eager_async: true,
  eager_notification_url: "https://mysite.example.com/notify_endpoint",
  public_id: "handshake")

|php_2
$cloudinary->uploadApi()->upload("handshake.mp4", [
  "resource_type" => "video", 
  "eager" => ["streaming_profile" => "full_hd", "format" => "m3u8"],
  "eager_async" => true,
  "eager_notification_url" => "https://mysite.example.com/notify_endpoint"
  "public_id" => "handshake"]);

|python
cloudinary.uploader.upload("handshake.mp4", resource_type = "video",
  eager = [
    {"streaming_profile": "full_hd", "format": "m3u8"}]
  eager_async = True,
  eager_notification_url = "https://mysite.example.com/notify_endpoint")
  public_id = "handshake")

|nodejs
var up_options = 
 {resource_type: "video", 
  eager: [
    { streaming_profile: "full_hd", format: "m3u8" }],                                   
  eager_async: true,
  eager_notification_url: "https://mysite.example.com/notify_endpoint",
  public_id: "handshake"};
cloudinary.v2.uploader
.upload("handshake.mp4", up_options)
.then(result=>console.log(result));
  
|java
cloudinary.uploader().upload("handshake.mp4", 
  ObjectUtils.asMap("resource_type", "video",
  "eager", Arrays.asList(
    new EagerTransformation().streamingProfile("full_hd").format("m3u8")),
  "eager_async", true,
  "eager_notification_url", "https://mysite.example.com/notify_endpoint",
  "public_id", "handshake"));

|go
resp, err := cld.Upload.Upload(ctx, "handshake.mp4", uploader.UploadParams{
		ResourceType:         "video",
		Eager:                "sp_full_hd/f_m3u8",
		EagerNotificationURL: "https://mysite.example.com/notify_endpoint",
		PublicID:             "handshake"})

|curl
curl https://api.cloudinary.com/v1_1/demo/video/upload -X POST -F 'file=@/path/to/handshake.mp4' -F 'eager=sp_full_hd,f_m3u8&public_id=handshake' -F 'eager_notification_url=https://mysite.example.com/notify_endpoint' -F 'eager_async=true' -F 'timestamp=173719931' -F 'api_key=436464676' -F 'signature=a781d61f86a6f818af'

|cli
cld uploader upload "handshake.mp4" resource_type="video" eager='[{"streaming_profile": "full_hd", "format": "m3u8"}]' eager_async=true eager_notification_url="https://mysite.example.com/notify_endpoint" public_id="handshake"
```

The [eager transformation response](#eager_response_example) includes the delivery URLs for each requested encoding. 

See more [adaptive streaming upload examples](#examples).

##### Derived adaptive streaming files
When the encoding process is complete for all representations, the derived files will include:

**HLS**

* A fragmented video streaming file (.ts) 1 for each representation
* An index file (.m3u8) of references to the fragments for each representation
* A single master playlist file (.m3u8) containing references to the representation files above and other required metadata

**DASH** 

* A fragmented video streaming file (.mp4dv) for each representation
* An audio stream file (.mp4da) for each representation
* An index file (.mpd) containing references to the fragments of each streaming file as well as a master playlist containing references to the representation files above and other required metadata

1 If working with HLS v.3, a separate video streaming file (.ts) is derived for each fragment of each representation. To work with HLS v.3, you need a private CDN configuration and you need to add a special [hlsv3 flag](#hlsv3) to your video transformations.

### Step 3. Deliver the video

After the eager transformation is complete, deliver your video using the `.m3u8` (HLS) or `.mpd` (MPEG-DASH) file format (extension) and include the `streaming_profile` (`sp_<profilename>`) and other transformations exactly matching those you provided in your eager transformation (as per the URL that was returned in the upload response).

For example, the delivery code for the video that was uploaded in Step 2 above is: 

![Deliver HLS adaptive streaming video](https://res.cloudinary.com/demo/video/upload/sp_full_hd/handshake.m3u8 "url_code: true, with_image: false")

```nodejs
cloudinary.url("handshake.m3u8", {streaming_profile: "full_hd", resource_type: "video"})
```

```react
new CloudinaryVideo("handshake.m3u8").transcode(streamingProfile("full_hd"));
```

```vue
new CloudinaryVideo("handshake.m3u8").transcode(streamingProfile("full_hd"));
```

```angular
new CloudinaryVideo("handshake.m3u8").transcode(streamingProfile("full_hd"));
```

```js
new CloudinaryVideo("handshake.m3u8").transcode(streamingProfile("full_hd"));
```

```python
cloudinary.utils.cloudinary_url("handshake.m3u8", streaming_profile="full_hd", resource_type="video")
```

```php
(new VideoTag('handshake.m3u8'))
	->transcode(Transcode::streamingProfile(
	StreamingProfile::fullHd()));
```

```java
cloudinary.url().transformation(new Transformation().streamingProfile("full_hd")).resourceType("video").generate("handshake.m3u8")
```

```ruby
cloudinary_url("handshake.m3u8", streaming_profile: "full_hd", resource_type: "video")
```

```csharp
cloudinary.Api.UrlVideoUp.Transform(new Transformation().StreamingProfile("full_hd")).BuildUrl("handshake.m3u8")
```

```dart
cloudinary.video('handshake.m3u8').transformation(Transformation()
	.transcode(Transcode.streamingProfile(
	StreamingProfile.fullHd())));
```

```swift
cloudinary.createUrl().setResourceType("video").setTransformation(CLDTransformation().setStreamingProfile("full_hd")).generate("handshake.m3u8")
```

```android
MediaManager.get().url().transformation(new Transformation().streamingProfile("full_hd")).resourceType("video").generate("handshake.m3u8");
```

```flutter
cloudinary.video('handshake.m3u8').transformation(Transformation()
	.transcode(Transcode.streamingProfile(
	StreamingProfile.fullHd())));
```

```kotlin
cloudinary.video {
	publicId("handshake.m3u8")
	 transcode(Transcode.streamingProfile(
	StreamingProfile.fullHd())) 
}.generate()
```

```jquery
$.cloudinary.url("handshake.m3u8", {streaming_profile: "full_hd", resource_type: "video"})
```

```react_native
new CloudinaryVideo("handshake.m3u8").transcode(streamingProfile("full_hd"));
```

**See full syntax**: [sp_\<profile name\>](transformation_reference#sp_profile_name) in the _Transformation Reference_.

> **INFO**: Not all browsers and mobile devices natively support MPEG-DASH and HLS. For example, Chrome does not natively support DASH or HLS. Both HLS and DASH are supported in Microsoft Edge and some SmartTVs. HLS is natively supported on all Apple devices, Safari, and the latest versions of the Chrome for Android browsers. 

To ensure that your encoded video can adaptively stream in all environments, you can embed the [Cloudinary Video Player](cloudinary_video_player) in your application. The Cloudinary video player supports video delivery in both HLS and MPEG-DASH.

#### Delivering HLS version 3

By default, HLS transcoding uses HLS v4  If you need to deliver HLS v3, add the `hlsv3` flag parameter (`fl_hlsv3` in URLs) when setting the video file format (extension) to `.m3u8`. Cloudinary will then automatically create the fragmented video files (all with a .ts extension), each with a duration of 10 seconds, as well as the required `.m3u8` index files for each of the fragmented files. 

> **INFO**: Supporting HLS v3 requires a private CDN configuration. CDN distributions are available for Cloudinary's [Advanced plan](https://cloudinary.com/pricing) and higher, and require a small setup on Cloudinary's side. For details, submit a support request.

### Examples
Step 2 above includes a [simple example](#step_2_upload_your_video_with_an_eager_transformation) for uploading a video and using a streaming profile to prepare the required files for delivering the video in adaptive streaming format. 
This section provides some additional examples. 

#### Request several encodings and formats at once 

```multi
|ruby
Cloudinary::Uploader.upload("handshake.mp4", resource_type: "video", 
  eager: [
     {streaming_profile: "full_hd", format: "m3u8"},
     {streaming_profile: "sd", format: "m3u8"},
     {streaming_profile: "hd", format: "mpd"}], 
  eager_async: true
  eager_notification_url: "https://mysite.example.com/notify_endpoint",
  public_id: "handshake")
  
|php_2
$cloudinary->uploadApi()->upload("handshake.mp4", [
  "resource_type" => "video", 
  "eager" => [
    ["streaming_profile" => "full_hd", "format" => "m3u8"],
    ["streaming_profile" => "sd", "format" => "m3u8"],
    ["streaming_profile" => "hd", "format" => "mpd"]],
  "eager_async" => true,
  "eager_notification_url" => "https://mysite.example.com/notify_endpoint",
  "public_id => "handshake"]);

|python
cloudinary.uploader.upload("handshake.mp4", resource_type = "video",
  eager = [
    {"streaming_profile": "full_hd", "format": "m3u8"},
    {"streaming_profile": "sd", "format": "m3u8"},
    {"streaming_profile": "hd", "format": "mpd"}],
  eager_async = True,
  eager_notification_url = "https://mysite.example.com/notify_endpoint",
  public_id = "handshake")

|nodejs
var up_options = 
{ resource_type: "video", 
  eager: [
    {streaming_profile: "full_hd", format: "m3u8"}, 
    {streaming_profile: "sd", format: "m3u8"}, 
    {streaming_profile: "hd", format: "mpd"} ],                                   
  eager_async: true,
  eager_notification_url: "https://mysite.example.com/notify_endpoint",
  public_id: "handshake" };
cloudinary.v2.uploader
.upload("handshake.mp4", up_options)
.then(result=>console.log(result)); 
  
|java
cloudinary.uploader().upload("handshake.mp4", 
  ObjectUtils.asMap("resource_type", "video",
  "eager", Arrays.asList(
    new EagerTransformation().streamingProfile("full_hd").format("m3u8"),
    new EagerTransformation().streamingProfile("sd").format("m3u8"),
    new EagerTransformation().streamingProfile("hd").format("mpd")),
  "eager_async", true,
  "eager_notification_url", "https://mysite.example.com/notify_endpoint,
  "public_id", "handshake"));

|csharp
var uploadParams = new VideoUploadParams()
{
	File = new FileDescription(@"handshake.mp4"),
  PublicId = "handshake",			
	EagerTransforms = new List<Transformation>()
	{
		new EagerTransformation().SetFormat("m3u8").StreamingProfile("full_hd"),
		new EagerTransformation().SetFormat("m3u8").StreamingProfile("sd"),
		new EagerTransformation().SetFormat("mpd").StreamingProfile("hd"),
	},
	EagerAsync = true,
	EagerNotificationUrl = "https://mysite.example.com/notify_endpoint"
};
var uploadResult = cloudinary.Upload(uploadParams);   

|go
resp, err := cld.Upload.Upload(ctx, "handshake.mp4", uploader.UploadParams{
		ResourceType:         "video",
		Eager:                "sp_full_hd/f_m3u8|sp_sd/f_m3u8|sp_hd/f_mpd",
		EagerAsync:           api.Bool(true),
		EagerNotificationURL: "https://mysite.example.com/notify_endpoint",
		PublicID:             "handshake"})

|curl
curl https://api.cloudinary.com/v1_1/demo/video/upload -X POST -F 'file=@/path/to/handshake.mp4' -F 'eager=f_m3u8,sp_full_hd|f_m3u8,sp_sd|f_m3u8,sp_hd' -F 'public_id=handshake' -F 'eager_notification_url=https://mysite.example.com/notify_endpoint' -F 'eager_async=true' -F 'timestamp=173719931' -F 'api_key=436464676' -F 'signature=a781d61f86a6f818af'

|cli
cld uploader upload "handshake.mp4" resource_type="video" eager='[{"streaming_profile": "full_hd", "format": "m3u8"},{"streaming_profile": "sd", "format": "m3u8"},{"streaming_profile": "hd", "format": "mpd"}]' eager_async=true eager_notification_url="https://mysite.example.com/notify_endpoint" public_id="handshake"
```

#### Use chained transformations with a streaming profile

```multi
|ruby
Cloudinary::Uploader.upload("handshake.mp4", 
  resource_type: "video",
  eager: 
    [{format: "m3u8", transformation: 
      [  
        {crop: "crop", aspect_ratio: "16:9"},
        {streaming_profile: "hd"},
        {overlay: "logowatermark", width: 100}
      ] }],
  eager_async: true, 
  eager_notification_url: "https://mysite.example.com/notify_endpoint",
  public_id: "handshake")

|php_2
$cloudinary->uploadApi()->upload("big_buck_bunny.mp4", [
  "resource_type" => "video", 
  "eager" => [
    "format" => "m3u8",
    "transformation" => [
      ["crop" => "crop", "aspect_ratio" => "16:9"],
      ["streaming_profile" => "hd"],
      ["overlay" => "logowatermark", "width" => 100]]]),
  "eager_async" => true,
  "eager_notification_url" => "https://mysite.example.com/notify_endpoint",
  "public_id => "bb_bunny"]);
 
|python
cloudinary.uploader.upload("handshake.mp4", 
  resource_type = "video",
  eager = [
    {"format": "m3u8", 
    "transformation": [
      {"crop":"crop", "aspect_ratio":"16:9"},
      {"streaming_profile": "hd"},
      {"overlay": "logowatermark", "width": 100}]
     }],
  eager_async = True,
  eager_notification_url = "https://mysite.example.com/notify_endpoint",
  public_id = "handshake")


|nodejs
var up_options = 
{ resource_type: "video", 
  eager: [
    {format: "m3u8", 
     transformation: [
       {crop: "crop", aspect_ratio: "16:9"},
       {streaming_profile: "hd"},
       {overlay: "logowatermark", width: 100}
   ]}, 
  ],                                   
  eager_async: true,
  eager_notification_url: "https://mysite.example.com/notify_endpoint",
  public_id: "handshake" };
cloudinary.v2.uploader
.upload("handshake.mp4", up_options)
.then(result=>console.log(result)); 
  
|java
cloudinary.uploader().upload("handshake.mp4", 
  ObjectUtils.asMap("resource_type", "video",
  "eager", Arrays.asList(
    new EagerTransformation()
      .format("m3u8").chain()
      .crop("crop").aspectRatio("16:9").chain()
      .streamingProfile("hd").chain()
      .overlay("logowatermark").width(100))        
  "eager_async", true,
  "eager_notification_url", "https://mysite.example.com/notify_endpoint,
  "public_id", "handshake"));

|csharp
var uploadParams = new VideoUploadParams()
{
  File = new FileDescription(@"handshake.mp4"),
  PublicId = "handshake",
  EagerTransforms = new List<Transformation>()
  {
			new EagerTransformation().SetFormat("m3u8").StreamingProfile("hd").Chain()
			.Crop("crop").AspectRatio("16:9").Chain()
      .Overlay("logowatermark").Width(100)
  },
  EagerAsync = true, EagerNotificationUrl = "https://mysite.example.com/notify_endpoint"
};
var uploadResult = cloudinary.Upload(uploadParams);   

|go
resp, err := cld.Upload.Upload(ctx, "handshake.mp4", uploader.UploadParams{
		ResourceType:         "video",
		Eager:                "f_m3u8/ar_16:9,c_crop/sp_hd/l_logowatermark/fl_layer_apply/c_scale,w_100",
		EagerAsync:           api.Bool(true),
		EagerNotificationURL: "https://mysite.example.com/notify_endpoint",
		PublicID:             "handshake"})

|curl
curl https://api.cloudinary.com/v1_1/demo/video/upload -X POST -F 'file=@/path/to/handshake.mp4' -F 'eager=f_m3u8,sp_hd/c_crop,ar_16:9/l_logowatermark,w_100' -F 'public_id=handshake' -F 'eager_notification_url=https://mysite.example.com/notify_endpoint' -F 'eager_async=true' -F 'timestamp=173719931' -F 'api_key=436464676' -F 'signature=a781d61f86a6f818af'

|cli
cld uploader upload "handshake.mp4" resource_type="video" eager='[{"format": "m3u8", "transformation": [{"crop": "crop", "aspect_ratio": "16:9"},{"streaming_profile": "hd"},{"overlay": "logowatermark", "width": 100}]}]' eager_async=true eager_notification_url="https://mysite.example.com/notify_endpoint" public_id="handshake"
```

#### Encode an already uploaded video using Explicit
```multi
|ruby
Cloudinary::Uploader.explicit("handshake", 
  resource_type: "video", type: "upload",
  eager: [
    {streaming_profile: "hd", format: "m3u8"}])

|php_2
$cloudinary->uploadApi()->explicit("bb_bunny", [
  "resource_type" => "video", "type" => "upload"
  "eager" => [
    ["streaming_profile" => "hd", "format" => "m3u8"]]]);

|python
cloudinary.uploader.explicit("handshake", 
  resource_type = "video", type = "upload", 
  eager = 
   [
     {"streaming_profile": "hd", "format": "m3u8"},
   ])

|nodejs
var up_options = 
  {resource_type: "video", type: "upload",
   eager: [
    { streaming_profile: "hd", format: "m3u8" }, 
   ] };
cloudinary.v2.uploader
.explicit("handshake", up_options)
.then(result=>console.log(result)); 
  
|java
cloudinary.uploader().explicit("handshake", ObjectUtils.asMap(
  "resource_type", "video", "type", "upload", 
  "eager", Arrays.asList(
    new EagerTransformation().streaming_profile("hd").format("m3u8"),
    )));

|csharp
var explicitParams = new ExplicitParams("handshake")
{
  Type = "upload",
  ResourceType = ResourceType.Video,
  EagerTransforms = new List<Transformation>()
  {
    new EagerTransformation().SetFormat("m3u8").StreamingProfile("hd")
  },
  EagerAsync = true,
  EagerNotificationUrl = "https://mysite.example.com/notify_endpoint"
};
var explicitResult = cloudinary.Explicit(explicitParams);     

|go
resp, err := cld.Upload.Explicit(ctx, uploader.UploadParams{
		PublicID:     "handshake",
		ResourceType: "video",
		Type:         "upload",
		Eager:        "sp_hd/f_m3u8"})

|curl
curl https://api.cloudinary.com/v1_1/demo/video/explicit -X POST --data 'type=upload&public_id=handshake&eager=sp_hd,f_m3u8&eager_notification_url=https://mysite.example.com/notify_endpoint&eager_async=true&timestamp=173719931&api_key=436464676&signature=a781d61f86a6f818af'        

|cli
cld uploader explicit "handshake" resource_type="video" type="upload" eager='[{"streaming_profile": "hd", "format": "m3u8"}]'

```

#### Eager response example

```json
{
  "asset_id": "f8aae290852d38729109f7d92bc39a63",
  "public_id": "handshake",
  "version": 1720616993,
  "version_id": "eda057f54f70b6526c2c5b3ea46db890",
  "signature": "d893e95bd04bd917270cf955112cf46cb424f23b",
  "width": 3840,
  "height": 2160,
  "format": "mp4",
  "resource_type": "video",
  "created_at": "2024-07-10T13:09:53Z",
  "tags": [],
  "bytes": 39123298,
  "type": "upload",
  "placeholder": false,
  "url": "http://res.cloudinary.com/cld-docs/video/upload/v1720616993/handshake.mp4",
  "secure_url": "https://res.cloudinary.com/cld-docs/video/upload/v1720616993/handshake.mp4",
  "playback_url": "https://res.cloudinary.com/cld-docs/video/upload/sp_auto/v1720616993/handshake.m3u8",
  "asset_folder": "",
  "display_name": "handshake",
  "eager": [
      {
          "transformation": "sp_hd/m3u8",
          "bytes": 867,
          "format": "m3u8",
          "url": "http://res.cloudinary.com/cld-docs/video/upload/sp_hd/v1720616993/handshake.m3u8",
          "secure_url": "https://res.cloudinary.com/cld-docs/video/upload/sp_hd/v1720616993/handshake.m3u8"
      }
  ]
}
```

## Notes and guidelines

* For small videos, where all of the derived files combined are less than 60 MB, you can deliver the transformation URL on the fly (instead of as an eager transformation), but this should be used only for demo or debugging purposes. In a production environment, we recommend to always eagerly transform your video representations. 

* The adaptive streaming files for small videos (where all of the derived files combined are less than 60 MB) are derived synchronously if you do not set the async parameter. Larger videos are prepared asynchronously. [Notification](notifications) (webhook) of completion is sent to the `eager_notification_url` parameter.

* If you set the format to an adaptive manifest file (`m3u8` or `mpd`) using the `format` parameter, you must either set the file extension to match, or omit the extension completely. If you provide a different file extension, your video will not play correctly. 

* By default, video sampling is per 2 seconds. You can define a custom sampling rate by specifying it as a chained transformation prior to the streaming profile. For example (in Ruby):

```ruby
   Cloudinary::Uploader.upload("handshake.mp4", resource_type: "video",
     eager: [{format: "mpd", transformation: 
      [{video_sampling: 3}, {streaming_profile: "hd"}] }]
     public_id: "handshake")
```

* All the generated files are considered derived files of the original video. For example, when performing invalidate on a video, the corresponding [derived adaptive streaming files](#derived_adaptive_streaming_files) are also invalidated.

* If you select a predefined profile and it includes any representations with a larger resolution than the original, those representations will not be created. However, at minimum, one representation will be created.

* If the aspect ratio of the original video does not match the aspect ratio of the selected streaming profile, then the `c_limit` cropping transformation is applied to make the video fit the required size. If you don't want to use this cropping method, use a base transformation to crop the video to the relevant aspect ratio.

* If you prepare streaming for a lower-level profile, and then in the future, you want to prepare the same video with a higher-level profile, the new high-quality representations will be created, while any already existing representations that match will be reused.

## Predefined streaming profiles

Each of the predefined streaming profiles will generate a number of different representations. The following sections summarize the representations that are generated for each predefined streaming profile and the settings for each representation.

> **NOTE**: The representations that are generated for each of the streaming profiles have been updated recently for all new customers and most existing customers. If you're still using the older representations, [contact support](https://support.cloudinary.com/hc/en-us/requests/new) for further details.

### Representations generated for each profile

The table below lists the available streaming profiles, the aspect ratio, and which of the representations are generated for each. The IDs in the table map to the IDs defined in the section below.

Profile Name | Aspect Ratio | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|---|
|`4k`|16:9|+|+|+|+|+|+|+|+|
|`full_hd`|16:9|+|+|+|+|+|+|||
|`full_hd_wifi`|16:9|||+||+|+|||
|`full_hd_lean`|16:9|+||+|||+|||
|`hd_lean`|16:9|+||+||+||||
|`hd`|16:9|+|+|+|+|+||||
|`sd`|4:3|+|+|+||||||

> **NOTE**: The profiles in the table above also apply to the codec specific profiles such as `4k_h265`, `4k_vp9` or `4k_av1`.

### Possible representations

|ID|Aspect Ratio|Resolution|Audio|H264 Bitrate (kbps)|H265 Bitrate (kbps)|VP9 Bitrate (kbps)|AV1 Bitrate (kbps)|
|---|--- |--- |--- |--- |--- |--- |---|
|8|16:9|3840x2160|AAC stereo 128kbps|18000|12000 (30fps) 15000 (60fps)|12000 (30fps)15000 (60fps)|7200 (30fps)11000 (60fps)|
|7|16:9|2560x1440|AAC stereo 128kbps|12000|6000 (30fps)9000 (60fps)| 6000 (30fps)9000 (60fps)|4500 (30fps)6600 (60fps)|
|6|16:9|1920x1080|AAC stereo 128kbps|5500|3800|3800|3060|
|5|16:9|1280x720|AAC stereo 128kbps|3300|2300|2300|1930|
|4|16:9|960x540|AAC stereo 96kbps|1500|1100|1100|790|
|3|16:9|640x360|AAC stereo 96kbps|500|350|350|320|
|3(SD)|4:3|640x480|AAC stereo 128kbps|800|550|550|490|
|2|16:9|480x270|AAC stereo 64kbps|270|200|200|190|
|2(SD)|4:3|480x360|AAC stereo 64kbps|400|300|300|260| 
|1|16:9|320x180|AAC stereo 64kbps|140|100|100|90|
|1(SD)|4:3|320x240|AAC stereo 64kbps|200|150|150|140|

## Manually creating representations and master files

If you have a special use case that does not enable you to use the built-in streaming profiles to automatically generate  master playlist file and all the relevant representations, you can still have Cloudinary create the index and streaming files for each streaming transformation you define, and then you can create the master playlist file manually.

### Step 1. Upload with eager transformations per representation

When you upload your video, provide eager transformations for each representation (resolution, quality, data rate) that you want to create. Also make sure to define the `format` for each transformation as `.m3u8` (HLS) or `.mpd` (DASH) in a separate component of the chain. 

When you define the format of your transformation as  `.m3u8` or `.mpd`, Cloudinary automatically generates the following derived files:

* A fragmented video streaming file (`.ts`1 or `.mp4dv`) 
* An index file (`.m3u8` or `.mpd`) of references to the fragments
* For DASH only: an audio stream file (`.mp4da`)  

For example:

```multi
|ruby  
Cloudinary::Uploader.upload("handshake.mp4", 
  resource_type: "video",
  eager: 
   [{format: "m3u8", transformation: 
      [
        {width: 960, height: 540, crop: "limit", video_codec: "h264:main:3.1", bit_rate: "3500k"},
        {overlay: "watermark", width: 100}
      ] 
    }], 
   eager_async: true, 
   eager_notification_url: "https://mysite.example.com/notify_endpoint")
   public_id: "handshake"
     
|php_2
$cloudinary->uploadApi()->upload("handshake.mp4", [
  "resource_type" => "video", 
  "eager" => [
    ["format" => "m3u8", "width" => 960, "height" => 540, "crop" => "limit", "video_codec" => "h264:main:3.1", "bit_rate" =>"3500k"]],
  "eager_async" => true,
  "eager_notification_url" => "https://mysite.example.com/notify_endpoint"
  "public_id => "handshake" ]);
 
|python
cloudinary.uploader.upload("handshake.mp4", 
  resource_type = "video",
  eager = 
    [
      {"format": "m3u8", "width": 960, "height": 540, "crop": "limit", "video_codec": "h264:main:3.1", "bit_rate": "3500k"}
    ],
  eager_async = True,
  eager_notification_url = "https://mysite.example.com/notify_endpoint"
  public_id = "handshake" )


|nodejs
var up_options = 
{ 
  resource_type: "video", 
  eager: 
  [
    { format: "m3u8", width: 960, height: 540, crop: "limit", video_codec: "h264:main:3.1", bit_rate: "3500k" }
  ],                                   
  eager_async: true,
  eager_notification_url: "https://mysite.example.com/notify_endpoint", 
  public_id: "handshake"    
 };
cloudinary.v2.uploader
.upload("handshake.mp4", up_options)
.then(result=>console.log(result)); 
  
|java
cloudinary.uploader().upload("handshake.mp4", 
  ObjectUtils.asMap("resource_type", "video",
  "eager", Arrays.asList(
    new EagerTransformation().format("m3u8").width(960).height(540).crop("limit").videoCodec("h264:main:3.1").bitRate("3500k")),
  "eager_async", true,
  "eager_notification_url", "https://mysite.example.com/notify_endpoint"
  "public_id", "handshake"));

|csharp
var uploadParams = new VideoUploadParams()
{
  File = new FileDescription(@"handshake.mp4"),
  EagerTransforms = new List<Transformation>()
  {
			new EagerTransformation().SetFormat("m3u8").Width(960).Height(540).Crop("limit").VideoCodec("h264:main:3.1").BitRate("3500k")
  },
  EagerAsync = true, EagerNotificationUrl = "https://mysite.example.com/notify_endpoint"
};
var uploadResult = cloudinary.Upload(uploadParams);

|go
resp, err := cld.Upload.Upload(ctx, "handshake.mp4", uploader.UploadParams{
		ResourceType:         "video",
		Eager:                "f_m3u8/c_limit,h_540,w_960/vc_h264:main:3.1/br_3500",
		EagerAsync:           api.Bool(true),
		EagerNotificationURL: "https://mysite.example.com/notify_endpoin",
		PublicID:             "handshake"})

|curl
curl https://api.cloudinary.com/v1_1/demo/video/upload -X POST -F 'file=@/path/to/handshake.mp4' -F 'eager=f_m3u8,w_960,h_540,c_limit,vc_h264:main:3.1,br_3500k' -F 'eager_notification_url=https://mysite.example.com/notify_endpoint' -F 'eager_async=true' -F 'timestamp=173719931' -F 'api_key=436464676' -F 'signature=a781d61f86a6f818af'

|cli
cld uploader upload "handshake.mp4" resource_type="video" eager='[{"format": "m3u8", "width": 960, "height": 540, "crop": "limit", "video_codec": "h264:main:3.1", "bit_rate": "3500k"}]' eager_async=true eager_notification_url="https://mysite.example.com/notify_endpoint" public_id="handshake"
```

> **NOTE**: It is possible to create adaptive streaming transformations on the fly, but recommended to you create all the required transformations [eagerly when uploading the original video file](eager_and_incoming_transformations#eager_transformations) or [explicitly](update_assets), so that there is no delay when first accessed by your users.

1If working with HLS v.3, a separate video streaming file (.ts) is derived for each fragment of each representation. To work with HLS v.3, you need a private CDN configuration and you need to add a special [hlsv3 flag](#hlsv3) to your video transformations. 

### Step 2. Manually create the master playlist file

If you're not using streaming profiles, you need to create your master playlist file manually.

#### HLS Master Playlist (m3u8)
Each item in the `m3u8` master index file points to a Cloudinary dynamic transformation URL, which represents the pair of auto-generated `m3u8` index and `ts` video files created in the preceding step.

For example, the following mobile targeted m3u8 master playlist file contains information on two versions of the `dog.mp4` video, one higher resolution and the other lower resolution for poorer bandwidth conditions:

```
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=480x320,NAME="Nice Dog - hi-res"    https://res.cloudinary.com/demo/video/upload/vc_h264:main:3.1/c_lpad,h_640,w_960/br_5m/vs_2/q_70/dog.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=1500000,RESOLUTION=480x320,NAME="Nice Dog - low-res"    https://res.cloudinary.com/demo/video/upload/vc_h264:baseline:3.0/c_lpad,h_320,w_480/br_1400k/vs_2/q_70/dog.m3u8
```
#### MPEG-DASH Master Playlist

The `.mpd` master file is an XML document. For details on creating the `.mpd` master file, see: [The Structure of an MPEG-DASH MPD](https://www.brendanlong.com/the-structure-of-an-mpeg-dash-mpd.html).

## Adding subtitles to HLS videos

In addition to adding subtitles as a [layer](video_layers#subtitles), you can define subtitles for HLS videos to be referenced from the generated manifest file. This allows for multiple subtitle tracks to be included and will allow users to switch between subtitles from the video player they are using. To add subtitles to HLS videos, use the streaming profile parameter (`sp`) with an additional `subtitles` parameter that defines the vtt files to use for subtitles.

For example:

```
https://res.cloudinary.com/demo/video/upload/sp_sd:subtitles_((code_en-US;file_outdoors.vtt);(code_es;file_outdoors-es.vtt))/outdoors.m3u8
```

The `subtitles` parameter is defined using Cloudinary specific URL syntax that allows you to specify parameters of a JSON-like object in the URL. To convert a JSON object representing the subtitles to a Cloudinary URL syntax string, you can apply the following substitutions:

* `(` - Start of object or array of objects
* `)` - End of object
* `;` - Separator between values object
* `_` - Key/value separator in an object

Taking our above example, the JSON equivalent would be:

```json
"subtitles":[
  {
    "code": "en-US",
    "file": "outdoors.vtt"
  },
  {
    "code": "es",
    "file": "outdoors-es.vtt"
  }
]
```

> **NOTE**: The first language specified will be the default language.

### Reference

#### Syntax

`sp_sd:subtitles_((code_<language code>;file_<subtitles file>))`

You can supply multiple subtitles tracks in the same `subtitles` parameter by appending additional sets of configuration surrounded by parentheses and separated with a semicolon. For example:

`sp_sd:subtitles_((code_<language code>;file_<subtitles file>);(code_<language code2>;file_<subtitles file2>);(code_<language code3>;file_<subtitles file3>))`

#### Parameters

The available parameters you can use with `subtitles` are:

Value | Type | Description
---|---|---
language code | string | The language code for the subtitles track, e.g., `en-US` for English (United States) subtitles.
subtitles file | string | The public ID and file extension for the subtitles file, e.g., `outdoors.vtt`.

> **NOTE**: Subtitles for HLS videos are only supported as part of the URL transformation string. To implement this feature via our SDKs, you can add your subtitles transformation using the `raw_transformation` parameter.

> **Advanced transformations**:
>
> In addition to the commonly used video transformation options described on this page, the following pages describe a variety of additional video transformation options:

> * Apply a video transformation only if a [specified condition](video_conditional_expressions) is met.

> * Use [arithmetic expressions and variables](video_user_defined_variables) to add additional sophistication and flexibility to your transformations.

> * Convert videos to [animated images](videos_to_animated_images).

> * [Stream or control the audio](audio_transformations) of audio or video files.
> The [Transformation URL API Reference](transformation_reference) details all transformation parameters available for both images and videos. Icons indicate which parameters are supported for each asset type.
> The [Video solution](cloudinary_video) section provides a single, comprehensive resource where you can explore all the valuable features Cloudinary offers for your video use cases.
