DASH Manifest Transform Extension Guide (Advanced)
This guide explains how to extend or modify DASH manifest transformations in Amazon Vinyl.
Overview
DASH manifest transformers sort and modify DASH manifests before playback. The
system uses a functional architecture where transformers are composable
functions over ObservableValue<Promise<DashManifestData>>.
Quality filtering (MIME type, key system, sample rate) is handled separately by the media timeline transformer, not the manifest transformer. The manifest transformer focuses on structural sorting and representation-level filtering.
Default Implementation
createDefaultDashManifestTransformer() sorts:
- Adaptation sets by
selectionPriority(descending) - Representations by
qualityRanking(ascending), thenbandwidth(descending)
The result is mapped over the manifest controller observable.
Creating Custom Filters
Representation Filters
Filter representations based on properties like bandwidth:
import { filterDashRepresentations, mapManifestTransform } from '@amazon/vinyl'
import { MediaUnsupportedError } from '@amazon/vinyl-util'
function filterHighBitrate(manifest: DashManifest) {
return filterDashRepresentations(
(representation) => representation.bandwidth <= 1_000_000,
() => {
throw new MediaUnsupportedError(
'No representations within bitrate range',
'bitrate_filter'
)
},
manifest
)
}
Adaptation Set Filters
Filter entire adaptation sets:
import { filterDashAdaptationSets } from '@amazon/vinyl'
import { MediaUnsupportedError } from '@amazon/vinyl-util'
function filterVideoOnly(manifest: DashManifest) {
return filterDashAdaptationSets(
(adaptationSet) =>
adaptationSet.mimeType?.startsWith('video/') ?? false,
() => {
throw new MediaUnsupportedError(
'No video adaptation sets found',
'video_only'
)
},
manifest
)
}
Creating Custom Transformers
Transform Function
Use flowAsync to compose multiple filter/sort steps, and
mapManifestTransform to apply them over a manifest observable:
import {
filterDashRepresentations,
sortDashRepresentations,
mapManifestTransform,
} from '@amazon/vinyl'
import { flowAsync } from '@amazon/vinyl-util'
import type { ObservableValue } from '@amazon/vinyl-observable'
import type { DashManifestData } from '@amazon/vinyl'
function createCustomTransformer(
manifestController: ObservableValue<Promise<DashManifestData>>
) {
const transformManifest = flowAsync(
(m) => filterDashRepresentations(customPredicate, throwCustomError, m),
(m) => sortDashRepresentations(customComparator, m)
)
return mapManifestTransform(manifestController, transformManifest)
}
Filter Factory
Transformers are factory functions with the signature
(ObservableValue<Promise<DashManifestData>>) => ObservableValue<Promise<DashManifestData>>.
This allows composition with flow:
import { mapManifestTransform, filterDashRepresentations } from '@amazon/vinyl'
function createCustomFilter(deps: CustomDeps) {
return (
manifestController: ObservableValue<Promise<DashManifestData>>
): ObservableValue<Promise<DashManifestData>> =>
mapManifestTransform(manifestController, (manifest) =>
filterDashRepresentations(
(rep) => customPredicate(deps, rep),
throwCustomError,
manifest
)
)
}
For filters that depend on additional reactive inputs, use combineData:
import { combineData } from '@amazon/vinyl-observable'
import { filterDashRepresentations } from '@amazon/vinyl'
function createConfigFilter(deps: {
readonly configProvider: ObservableValue<FilterConfig>
}): (
manifestAndPath: ObservableValue<Promise<DashManifestData>>
) => ObservableValue<Promise<DashManifestData>> {
return (manifestAndPath) =>
combineData({
manifestAndPath,
config: deps.configProvider,
}).map(async ({ manifestAndPath, config }) => {
const { manifest, baseUrl } = await manifestAndPath
return {
baseUrl,
manifest: filterDashRepresentations(
(rep) => rep.bandwidth <= config.maxBitrate,
throwConfigFilterError,
manifest
),
}
})
}
Composing Transformers with flow
import { flow } from '@amazon/vinyl-util'
function createMyManifestTransformer(
deps: MyTransformerDeps
): ObservableValue<Promise<DashManifestData>> {
return flow(
createCustomFilter(deps),
createSortTransformer()
)(createDefaultDashManifestTransformer(deps))
}
Integration with createVinylPlayer
Override the manifestTransformed factory to inject custom transformations
after the default sorting:
import {
createVinylPlayer,
createDefaultDashManifestTransformer,
createDashFactories,
filterDashRepresentations,
mapManifestTransform,
type DashManifestTransformerDeps,
} from '@amazon/vinyl'
import { flowAsync } from '@amazon/vinyl-util'
import { MediaUnsupportedError } from '@amazon/vinyl-util'
function filterStereoOnly(manifest: DashManifest) {
return filterDashRepresentations(
(rep) =>
!rep.AudioChannelConfiguration ||
Number(rep.AudioChannelConfiguration.value) <= 2,
() => {
throw new MediaUnsupportedError(
'Only stereo audio supported',
'stereo_only'
)
},
manifest
)
}
const player = createVinylPlayer(
{ media: new Audio() },
{
createDashFactories: (options) => (deps) => (loadOptions) => ({
...createDashFactories(options)(deps)(loadOptions),
manifestTransformed: (
transformDeps: DashManifestTransformerDeps
) => {
const transformManifest = flowAsync(filterStereoOnly)
return mapManifestTransform(
createDefaultDashManifestTransformer(transformDeps),
transformManifest
)
},
}),
}
)
Utility Functions
filterDashAdaptationSets()- Filter adaptation sets with error handlingfilterDashRepresentations()- Filter representations with error handlingsortDashAdaptationSets()- Sort adaptation sets by comparatorsortDashRepresentations()- Sort representations by comparatormanifestIsPlayable()- Check if manifest has playable contentmapManifestTransform()- Apply a(DashManifest) => MaybePromise<DashManifest>over a manifest observable, preserving baseUrl
Best Practices
- Error Handling: Always provide meaningful error codes and messages
- Performance: Use async filters only for expensive operations (e.g. DRM)
- Composition: Prefer small, focused filter factories composed with
flow - Immutability: Filter utilities clone manifests internally