@amazon/vinyl 3.2.2 → 3.2.3-next.20261001224604
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.cjs +62 -21
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +65 -22
- package/dist/index.js.map +3 -3
- package/dist/test/benchmark.es5.js +7 -6
- package/dist/test/benchmark.js +35 -30
- package/dist/test/benchmark.js.map +3 -3
- package/dist/test/integ.es5.js +184 -171
- package/dist/test/integ.js +450 -165
- package/dist/test/integ.js.map +4 -4
- package/dist/types/current/ad/AdControllerImpl.d.ts.map +1 -1
- package/dist/types/current/ad/AdControllerImpl.js +15 -1
- package/dist/types/current/ad/discoverHlsInterstitials.js +13 -1
- package/dist/types/current/drm/commonEme/MsCommonEme.d.ts.map +1 -1
- package/dist/types/current/drm/commonEme/MsCommonEme.js +2 -2
- package/dist/types/current/drm/commonEme/StandardCommonEme.d.ts.map +1 -1
- package/dist/types/current/drm/commonEme/StandardCommonEme.js +3 -2
- package/dist/types/current/drm/commonEme/WebKitCommonEme.d.ts.map +1 -1
- package/dist/types/current/drm/commonEme/WebKitCommonEme.js +2 -2
- package/dist/types/current/drm/dom/MSMediaKeys.d.ts +1 -1
- package/dist/types/current/drm/dom/MSMediaKeys.d.ts.map +1 -1
- package/dist/types/current/drm/dom/WebKitMediaKeys.d.ts +1 -1
- package/dist/types/current/drm/dom/WebKitMediaKeys.d.ts.map +1 -1
- package/dist/types/current/drm/util/extractContentId.js +2 -2
- package/dist/types/current/playback/PlaybackControllerImpl.d.ts +1 -1
- package/dist/types/current/streaming/MediaQualityMetadata.d.ts +3 -1
- package/dist/types/current/streaming/MediaQualityMetadata.d.ts.map +1 -1
- package/dist/types/current/streaming/SegmentControllerImpl.d.ts +1 -1
- package/dist/types/current/streaming/buffering/BufferingController.d.ts +1 -1
- package/dist/types/current/streaming/buffering/BufferingController.d.ts.map +1 -1
- package/dist/types/current/streaming/buffering/BufferingController.js +14 -5
- package/dist/types/current/streaming/buffering/MediaSourceController.d.ts.map +1 -1
- package/dist/types/current/streaming/buffering/MediaSourceController.js +4 -0
- package/dist/types/current/text/discoverDashTextTracks.d.ts +2 -2
- package/dist/types/current/text/discoverDashTextTracks.js +4 -3
- package/dist/types/current/text/parseWebVtt.d.ts.map +1 -1
- package/dist/types/current/text/parseWebVtt.js +3 -2
- package/dist/types/current/track/TrackController.d.ts.map +1 -1
- package/dist/types/current/track/TrackController.js +7 -0
- package/dist/types/current/track/hls/HlsManifestData.d.ts +14 -1
- package/dist/types/current/track/hls/HlsManifestData.d.ts.map +1 -1
- package/dist/types/current/track/hls/buildHlsMediaTimeline.d.ts.map +1 -1
- package/dist/types/current/track/hls/buildHlsMediaTimeline.js +10 -2
- package/dist/types/current/track/hls/createUrlHlsManifestProvider.d.ts +3 -2
- package/dist/types/current/track/hls/createUrlHlsManifestProvider.d.ts.map +1 -1
- package/dist/types/current/track/hls/createUrlHlsManifestProvider.js +8 -3
- package/dist/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ts3.7/playback/PlaybackControllerImpl.d.ts +1 -1
- package/dist/types/ts3.7/streaming/MediaQualityMetadata.d.ts +3 -1
- package/dist/types/ts3.7/streaming/SegmentControllerImpl.d.ts +1 -1
- package/dist/types/ts3.7/streaming/buffering/BufferingController.d.ts +1 -1
- package/dist/types/ts3.7/text/discoverDashTextTracks.d.ts +2 -2
- package/dist/types/ts3.7/track/hls/HlsManifestData.d.ts +14 -1
- package/dist/types/ts3.7/track/hls/createUrlHlsManifestProvider.d.ts +3 -2
- package/dist/types/ts4.1/playback/PlaybackControllerImpl.d.ts +1 -1
- package/dist/types/ts4.1/streaming/MediaQualityMetadata.d.ts +3 -1
- package/dist/types/ts4.1/streaming/SegmentControllerImpl.d.ts +1 -1
- package/dist/types/ts4.1/streaming/buffering/BufferingController.d.ts +1 -1
- package/dist/types/ts4.1/text/discoverDashTextTracks.d.ts +2 -2
- package/dist/types/ts4.1/track/hls/HlsManifestData.d.ts +14 -1
- package/dist/types/ts4.1/track/hls/createUrlHlsManifestProvider.d.ts +3 -2
- package/dist/types/ts4.7/playback/PlaybackControllerImpl.d.ts +1 -1
- package/dist/types/ts4.7/streaming/MediaQualityMetadata.d.ts +3 -1
- package/dist/types/ts4.7/streaming/SegmentControllerImpl.d.ts +1 -1
- package/dist/types/ts4.7/streaming/buffering/BufferingController.d.ts +1 -1
- package/dist/types/ts4.7/text/discoverDashTextTracks.d.ts +2 -2
- package/dist/types/ts4.7/track/hls/HlsManifestData.d.ts +14 -1
- package/dist/types/ts4.7/track/hls/createUrlHlsManifestProvider.d.ts +3 -2
- package/dist/unit/types/current/ad/AdControllerImpl.test.js +66 -0
- package/dist/unit/types/current/ad/discoverHlsInterstitials.test.js +29 -2
- package/dist/unit/types/current/drm/commonEme/StandardCommonEme.test.js +12 -4
- package/dist/unit/types/current/drm/util/extractContentId.test.js +7 -0
- package/dist/unit/types/current/index.d.ts +1 -0
- package/dist/unit/types/current/index.d.ts.map +1 -1
- package/dist/unit/types/current/index.js +1 -0
- package/dist/unit/types/current/streaming/MediaQualityMetadata.test.js +8 -0
- package/dist/unit/types/current/streaming/buffering/BufferingController.test.js +26 -0
- package/dist/unit/types/current/streaming/buffering/MediaSourceController.test.js +29 -0
- package/dist/unit/types/current/text/TextTrack.test.d.ts +2 -0
- package/dist/unit/types/current/text/TextTrack.test.d.ts.map +1 -0
- package/dist/unit/types/current/text/TextTrack.test.js +12 -0
- package/dist/unit/types/current/text/discoverDashTextTracks.test.js +17 -0
- package/dist/unit/types/current/text/parseWebVtt.test.js +26 -0
- package/dist/unit/types/current/track/TrackController.test.js +15 -60
- package/dist/unit/types/current/track/hls/buildHlsMediaTimeline.test.js +22 -0
- package/dist/unit/types/current/track/hls/createUrlHlsManifestProvider.test.js +23 -0
- package/dist/unit/types/current/tsconfig.tsbuildinfo +1 -1
- package/dist/vinylTestUtil/types/current/tsconfig.tsbuildinfo +1 -1
- package/docs/CUSTOMIZATION_GUIDE.md +310 -0
- package/package.json +10 -10
- package/docs/DASH_MANIFEST_TRANSFORM_GUIDE.md +0 -236
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Customization Guide (Advanced)
|
|
2
|
+
|
|
3
|
+
Amazon Vinyl exposes several extension points that let an application filter,
|
|
4
|
+
sort, or otherwise reshape playback without forking the player. The extension
|
|
5
|
+
points are stacked from the most general to the most specific:
|
|
6
|
+
|
|
7
|
+
1. **Media timeline transform** — protocol-agnostic filtering and shaping of the
|
|
8
|
+
qualities the ABR selector may pick from.
|
|
9
|
+
2. **DASH manifest transform** — DASH-specific filtering, sorting, and
|
|
10
|
+
restructuring of the parsed manifest.
|
|
11
|
+
3. **HLS manifest transform** — HLS-specific filtering and sorting of the parsed
|
|
12
|
+
master playlist.
|
|
13
|
+
4. **Parser rule extensions** — parse additional XML elements/attributes on top
|
|
14
|
+
of the default DASH parser rules (or the HLS parser hooks), for custom
|
|
15
|
+
`ContentProtection` children, supplemental descriptors, etc.
|
|
16
|
+
|
|
17
|
+
Start at the highest layer that solves your problem. Timeline-level filtering is
|
|
18
|
+
protocol-agnostic and applies to both DASH and HLS.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Media timeline transform (protocol-agnostic)
|
|
23
|
+
|
|
24
|
+
The `MediaTimeline` is the last layer before the ABR selector sees the stream: a
|
|
25
|
+
list of `MediaPeriod`s, each with a set of `MediaQualityData` entries carrying
|
|
26
|
+
`MediaQualityMetadata` (mime type, codecs, bandwidth, resolution, language,
|
|
27
|
+
content protections, characteristics, …). Filtering here is the recommended
|
|
28
|
+
place for anything that doesn't need to look at the raw manifest, because it
|
|
29
|
+
works identically for DASH and HLS.
|
|
30
|
+
|
|
31
|
+
Vinyl ships `createDefaultMediaTimelineTransformer`, which chains codec/mime
|
|
32
|
+
support, DRM key-system support, sample-rate support, audio-description gating,
|
|
33
|
+
and language preference. Applications can stack their own filters over the same
|
|
34
|
+
primitives.
|
|
35
|
+
|
|
36
|
+
### Filter timeline qualities
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import {
|
|
40
|
+
createVinylPlayer,
|
|
41
|
+
filterTimelineQualities,
|
|
42
|
+
createDefaultMediaTimelineTransformer,
|
|
43
|
+
type MediaTimeline,
|
|
44
|
+
type MediaQualityMetadata,
|
|
45
|
+
} from '@amazon/vinyl'
|
|
46
|
+
import { MediaUnsupportedError } from '@amazon/vinyl-util'
|
|
47
|
+
|
|
48
|
+
/** Drop everything over 2 Mbps. */
|
|
49
|
+
function dropHighBitrate(timeline: MediaTimeline): MediaTimeline {
|
|
50
|
+
return filterTimelineQualities(
|
|
51
|
+
(quality: MediaQualityMetadata) => quality.bandwidth <= 2_000_000,
|
|
52
|
+
() => {
|
|
53
|
+
throw new MediaUnsupportedError(
|
|
54
|
+
'No qualities within bitrate cap',
|
|
55
|
+
'bitrate-cap'
|
|
56
|
+
)
|
|
57
|
+
},
|
|
58
|
+
timeline
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const player = createVinylPlayer(
|
|
63
|
+
{ media: new Audio() },
|
|
64
|
+
{
|
|
65
|
+
// Override the timeline transformer factory to chain the extra filter
|
|
66
|
+
// on top of the default one.
|
|
67
|
+
createMediaTimelineTransformer: (deps) => {
|
|
68
|
+
const defaults = createDefaultMediaTimelineTransformer(deps)
|
|
69
|
+
return defaults.map(async (t) => dropHighBitrate(await t))
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
> **Note:** A plain bitrate cap already has a built-in configuration value —
|
|
76
|
+
> `player.configure({ abr: { maxBandwidth: 2_000_000 } })` — and resolution caps
|
|
77
|
+
> map to `abr.maxHeight` / `abr.maxWidth`. The filter above is shown only to
|
|
78
|
+
> illustrate the mechanism; reach for a custom timeline filter when you need
|
|
79
|
+
> logic the built-in ABR restrictions don't cover.
|
|
80
|
+
|
|
81
|
+
`filterTimelineQualitiesAsync` is available for filters that need to await (e.g.
|
|
82
|
+
an EME key-system probe). Both throw via the callback when a period ends up
|
|
83
|
+
empty, so callers can surface a specific error.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 2. DASH manifest transform
|
|
88
|
+
|
|
89
|
+
Applications that need to look at raw DASH elements (adaptation-set switching
|
|
90
|
+
groups, per-representation descriptors, essential/supplemental properties) work
|
|
91
|
+
at this layer. The manifest transformer is a factory over
|
|
92
|
+
`ObservableValue<Promise<DashManifestData>>`, so multiple transformers compose.
|
|
93
|
+
|
|
94
|
+
Vinyl's default (`createDefaultDashManifestTransformer`) sorts adaptation sets
|
|
95
|
+
by descending `selectionPriority` and representations by ascending
|
|
96
|
+
`qualityRanking` then descending `bandwidth`. Filtering utilities
|
|
97
|
+
(`filterDashRepresentations`, `filterDashAdaptationSets`,
|
|
98
|
+
`sortDashRepresentations`, `sortDashAdaptationSets`) return cloned manifests, so
|
|
99
|
+
composition is safe.
|
|
100
|
+
|
|
101
|
+
### Add a representation filter
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import {
|
|
105
|
+
createDefaultDashManifestTransformer,
|
|
106
|
+
createVinylPlayer,
|
|
107
|
+
filterDashRepresentations,
|
|
108
|
+
mapManifestTransform,
|
|
109
|
+
type DashManifest,
|
|
110
|
+
} from '@amazon/vinyl'
|
|
111
|
+
import { MediaUnsupportedError } from '@amazon/vinyl-util'
|
|
112
|
+
|
|
113
|
+
/** Drop stereo-plus channel counts (headphones-only build). */
|
|
114
|
+
function stereoOnly(manifest: DashManifest): DashManifest {
|
|
115
|
+
return filterDashRepresentations(
|
|
116
|
+
(rep) =>
|
|
117
|
+
!rep.AudioChannelConfiguration ||
|
|
118
|
+
Number(rep.AudioChannelConfiguration.value) <= 2,
|
|
119
|
+
() => {
|
|
120
|
+
throw new MediaUnsupportedError(
|
|
121
|
+
'Only stereo audio supported',
|
|
122
|
+
'stereo-only'
|
|
123
|
+
)
|
|
124
|
+
},
|
|
125
|
+
manifest
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const player = createVinylPlayer(
|
|
130
|
+
{ media: new Audio() },
|
|
131
|
+
{
|
|
132
|
+
createDashFactories: (options) => (deps) => (loadOptions) => {
|
|
133
|
+
const base = deps.createDashFactories(options)(deps)(loadOptions)
|
|
134
|
+
return {
|
|
135
|
+
...base,
|
|
136
|
+
manifestTransformed: (transformDeps) =>
|
|
137
|
+
mapManifestTransform(
|
|
138
|
+
createDefaultDashManifestTransformer(transformDeps),
|
|
139
|
+
stereoOnly
|
|
140
|
+
),
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Filters can pull in additional reactive inputs (a config observable, a network
|
|
148
|
+
class, etc.) by mapping over `combineData({ manifestController, config })`
|
|
149
|
+
instead of `mapManifestTransform`.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 3. HLS manifest transform
|
|
154
|
+
|
|
155
|
+
The HLS layer is smaller than DASH — one master playlist with a list of
|
|
156
|
+
variants, media playlists loaded lazily by the selected variant.
|
|
157
|
+
`createDefaultHlsManifestTransformer` sorts variants by descending bandwidth.
|
|
158
|
+
Extend it the same way as DASH by overriding the `manifestTransformed` slot on
|
|
159
|
+
the HLS factories.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import {
|
|
163
|
+
createDefaultHlsManifestTransformer,
|
|
164
|
+
createVinylPlayer,
|
|
165
|
+
type HlsManifestData,
|
|
166
|
+
} from '@amazon/vinyl'
|
|
167
|
+
|
|
168
|
+
/** Drop every variant over 720p. */
|
|
169
|
+
function cap720p(data: HlsManifestData): HlsManifestData {
|
|
170
|
+
const variants = data.mainPlaylist.variants.filter(
|
|
171
|
+
(v) => !v.resolution || v.resolution.height <= 720
|
|
172
|
+
)
|
|
173
|
+
return {
|
|
174
|
+
...data,
|
|
175
|
+
mainPlaylist: { ...data.mainPlaylist, variants },
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const player = createVinylPlayer(
|
|
180
|
+
{ media: new Audio() },
|
|
181
|
+
{
|
|
182
|
+
createHlsFactories: (options) => (deps) => (loadOptions) => {
|
|
183
|
+
const base = deps.createHlsFactories(options)(deps)(loadOptions)
|
|
184
|
+
return {
|
|
185
|
+
...base,
|
|
186
|
+
manifestTransformed: (transformDeps) =>
|
|
187
|
+
createDefaultHlsManifestTransformer(transformDeps).map(
|
|
188
|
+
async (v) => cap720p(await v)
|
|
189
|
+
),
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
> **Note:** A resolution cap like the one above also has a built-in
|
|
197
|
+
> configuration value — `player.configure({ abr: { maxHeight: 720 } })`. The
|
|
198
|
+
> filter is shown only to illustrate the mechanism.
|
|
199
|
+
|
|
200
|
+
Cross-cutting quality filtering (bandwidth caps, codec support, language
|
|
201
|
+
preference) belongs at the timeline layer above; this layer is for HLS-specific
|
|
202
|
+
shape (e.g. dropping variants by an HLS-only attribute the ABR restrictions
|
|
203
|
+
don't model).
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 4. Parser rule extensions
|
|
208
|
+
|
|
209
|
+
Vinyl's DASH parser is rule-based. The `dashDrmRules` ruleset (default DASH
|
|
210
|
+
manifest rules + cenc + PlayReady content-protection extensions) is exported so
|
|
211
|
+
consumers can layer additional element/attribute rules on top of it without
|
|
212
|
+
reimplementing anything.
|
|
213
|
+
|
|
214
|
+
### Parse a custom `ContentProtection` child
|
|
215
|
+
|
|
216
|
+
Consider a manifest that carries a vendor-specific `<LicenseUrl>` element inside
|
|
217
|
+
each `ContentProtection`, under its own namespace:
|
|
218
|
+
|
|
219
|
+
```xml
|
|
220
|
+
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
|
|
221
|
+
xmlns:ext="urn:example:drm:2020">
|
|
222
|
+
<cenc:pssh>...</cenc:pssh>
|
|
223
|
+
<ext:LicenseUrl>https://example.com/getLicense</ext:LicenseUrl>
|
|
224
|
+
</ContentProtection>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Define a rule for the new element, merge it in, and build a parser:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import {
|
|
231
|
+
charactersString,
|
|
232
|
+
createDashProtectionXmlRules,
|
|
233
|
+
dashDrmRules,
|
|
234
|
+
element,
|
|
235
|
+
mapXmlRules,
|
|
236
|
+
mergeXmlRules,
|
|
237
|
+
parseXml,
|
|
238
|
+
ParseXmlHandlerImpl,
|
|
239
|
+
type XmlRules,
|
|
240
|
+
} from '@amazon/vinyl-mpd-parser'
|
|
241
|
+
|
|
242
|
+
const NS = 'urn:example:drm:2020'
|
|
243
|
+
|
|
244
|
+
interface LicenseUrlProtection {
|
|
245
|
+
readonly LicenseUrl?: { readonly _content: string }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const licenseUrlRules: XmlRules<LicenseUrlProtection> = {
|
|
249
|
+
LicenseUrl: element({ _content: charactersString }, { namespaceUri: NS }),
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const rules = mapXmlRules(
|
|
253
|
+
mergeXmlRules(dashDrmRules, createDashProtectionXmlRules(licenseUrlRules))
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
export function parseDashManifestWithLicenseUrl(xml: string) {
|
|
257
|
+
return parseXml(xml, new ParseXmlHandlerImpl(rules))
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Feed the extended parser into a track via a custom `manifestProvider`:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
player.load({
|
|
265
|
+
type: 'dash',
|
|
266
|
+
uri: manifestUrl,
|
|
267
|
+
manifestProvider: async () => {
|
|
268
|
+
const response = await fetch(manifestUrl)
|
|
269
|
+
return {
|
|
270
|
+
manifest: parseDashManifestWithLicenseUrl(await response.text()),
|
|
271
|
+
baseUrl: manifestUrl,
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
// …bind license-server providers that read the parsed LicenseUrl from the
|
|
275
|
+
// matching ContentProtection element.
|
|
276
|
+
})
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`createDashProtectionXmlRules` wires a set of `ContentProtection` child rules
|
|
280
|
+
into every level
|
|
281
|
+
(`MPD > Period > AdaptationSet > Representation > SubRepresentation`), so the
|
|
282
|
+
same rule automatically applies wherever `ContentProtection` may appear.
|
|
283
|
+
|
|
284
|
+
### Utility reference
|
|
285
|
+
|
|
286
|
+
- `filterTimelineQualities` / `filterTimelineQualitiesAsync` — timeline-level
|
|
287
|
+
quality filter with a throw-on-empty callback.
|
|
288
|
+
- `filterDashRepresentations` / `filterDashAdaptationSets` — DASH element
|
|
289
|
+
filters with a throw-on-empty callback.
|
|
290
|
+
- `sortDashRepresentations` / `sortDashAdaptationSets` — DASH sorters.
|
|
291
|
+
- `mapManifestTransform` — apply a
|
|
292
|
+
`(DashManifest) => MaybePromise<DashManifest>` over a manifest observable,
|
|
293
|
+
preserving `baseUrl`.
|
|
294
|
+
- `manifestIsPlayable` — check whether a filtered DASH manifest still has
|
|
295
|
+
playable representations.
|
|
296
|
+
- `dashDrmRules` (from `@amazon/vinyl-mpd-parser`) — default DASH manifest +
|
|
297
|
+
cenc + PlayReady rules, ready to merge additional rules onto.
|
|
298
|
+
- `createDashProtectionXmlRules` — lifts a `ContentProtection` child rule to
|
|
299
|
+
every place `ContentProtection` may appear.
|
|
300
|
+
|
|
301
|
+
## Guidelines
|
|
302
|
+
|
|
303
|
+
1. **Pick the highest layer that fits.** Timeline filters are DASH/HLS-neutral
|
|
304
|
+
and cheapest to write.
|
|
305
|
+
2. **Give every filter a distinct error code** so surfaced errors are
|
|
306
|
+
attributable.
|
|
307
|
+
3. **Sync filters are preferred**; only use async filters for genuinely
|
|
308
|
+
asynchronous checks (e.g. EME probes).
|
|
309
|
+
4. **Filter utilities clone.** You can chain them freely without worrying about
|
|
310
|
+
mutating shared state.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amazon/vinyl",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.3-next.20261001224604",
|
|
4
4
|
"description": "An HTML5 streaming media player",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -90,18 +90,18 @@
|
|
|
90
90
|
"oxlint:write": "oxlint --type-aware --fix"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
|
-
"@amazon/vinyl-browserstack": "
|
|
94
|
-
"@amazon/vinyl-build-utils": "
|
|
93
|
+
"@amazon/vinyl-browserstack": "3.2.3-next.20261001224604",
|
|
94
|
+
"@amazon/vinyl-build-utils": "3.2.3-next.20261001224604",
|
|
95
95
|
"commander": "^15.0.0"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@amazon/vinyl-di": "
|
|
99
|
-
"@amazon/vinyl-hls-parser": "
|
|
100
|
-
"@amazon/vinyl-mpd-parser": "
|
|
101
|
-
"@amazon/vinyl-observable": "
|
|
102
|
-
"@amazon/vinyl-transmux": "
|
|
103
|
-
"@amazon/vinyl-util": "
|
|
104
|
-
"@amazon/vinyl-validation": "
|
|
98
|
+
"@amazon/vinyl-di": "3.2.3-next.20261001224604",
|
|
99
|
+
"@amazon/vinyl-hls-parser": "3.2.3-next.20261001224604",
|
|
100
|
+
"@amazon/vinyl-mpd-parser": "3.2.3-next.20261001224604",
|
|
101
|
+
"@amazon/vinyl-observable": "3.2.3-next.20261001224604",
|
|
102
|
+
"@amazon/vinyl-transmux": "3.2.3-next.20261001224604",
|
|
103
|
+
"@amazon/vinyl-util": "3.2.3-next.20261001224604",
|
|
104
|
+
"@amazon/vinyl-validation": "3.2.3-next.20261001224604"
|
|
105
105
|
},
|
|
106
106
|
"publishConfig": {
|
|
107
107
|
"access": "public"
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
# DASH Manifest Transform Extension Guide (Advanced)
|
|
2
|
-
|
|
3
|
-
This guide explains how to extend or modify DASH manifest transformations in
|
|
4
|
-
Amazon Vinyl.
|
|
5
|
-
|
|
6
|
-
## Overview
|
|
7
|
-
|
|
8
|
-
DASH manifest transformers sort and modify DASH manifests before playback. The
|
|
9
|
-
system uses a functional architecture where transformers are composable
|
|
10
|
-
functions over `ObservableValue<Promise<DashManifestData>>`.
|
|
11
|
-
|
|
12
|
-
Quality filtering (MIME type, key system, sample rate) is handled separately by
|
|
13
|
-
the media timeline transformer, not the manifest transformer. The manifest
|
|
14
|
-
transformer focuses on structural sorting and representation-level filtering.
|
|
15
|
-
|
|
16
|
-
## Default Implementation
|
|
17
|
-
|
|
18
|
-
`createDefaultDashManifestTransformer()` sorts:
|
|
19
|
-
|
|
20
|
-
- Adaptation sets by `selectionPriority` (descending)
|
|
21
|
-
- Representations by `qualityRanking` (ascending), then `bandwidth` (descending)
|
|
22
|
-
|
|
23
|
-
The result is mapped over the manifest controller observable.
|
|
24
|
-
|
|
25
|
-
## Creating Custom Filters
|
|
26
|
-
|
|
27
|
-
### Representation Filters
|
|
28
|
-
|
|
29
|
-
Filter representations based on properties like bandwidth:
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
import { filterDashRepresentations, mapManifestTransform } from '@amazon/vinyl'
|
|
33
|
-
import { MediaUnsupportedError } from '@amazon/vinyl-util'
|
|
34
|
-
|
|
35
|
-
function filterHighBitrate(manifest: DashManifest) {
|
|
36
|
-
return filterDashRepresentations(
|
|
37
|
-
(representation) => representation.bandwidth <= 1_000_000,
|
|
38
|
-
() => {
|
|
39
|
-
throw new MediaUnsupportedError(
|
|
40
|
-
'No representations within bitrate range',
|
|
41
|
-
'bitrate_filter'
|
|
42
|
-
)
|
|
43
|
-
},
|
|
44
|
-
manifest
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Adaptation Set Filters
|
|
50
|
-
|
|
51
|
-
Filter entire adaptation sets:
|
|
52
|
-
|
|
53
|
-
```typescript
|
|
54
|
-
import { filterDashAdaptationSets } from '@amazon/vinyl'
|
|
55
|
-
import { MediaUnsupportedError } from '@amazon/vinyl-util'
|
|
56
|
-
|
|
57
|
-
function filterVideoOnly(manifest: DashManifest) {
|
|
58
|
-
return filterDashAdaptationSets(
|
|
59
|
-
(adaptationSet) =>
|
|
60
|
-
adaptationSet.mimeType?.startsWith('video/') ?? false,
|
|
61
|
-
() => {
|
|
62
|
-
throw new MediaUnsupportedError(
|
|
63
|
-
'No video adaptation sets found',
|
|
64
|
-
'video_only'
|
|
65
|
-
)
|
|
66
|
-
},
|
|
67
|
-
manifest
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Creating Custom Transformers
|
|
73
|
-
|
|
74
|
-
### Transform Function
|
|
75
|
-
|
|
76
|
-
Use `flowAsync` to compose multiple filter/sort steps, and
|
|
77
|
-
`mapManifestTransform` to apply them over a manifest observable:
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
import {
|
|
81
|
-
filterDashRepresentations,
|
|
82
|
-
sortDashRepresentations,
|
|
83
|
-
mapManifestTransform,
|
|
84
|
-
} from '@amazon/vinyl'
|
|
85
|
-
import { flowAsync } from '@amazon/vinyl-util'
|
|
86
|
-
import type { ObservableValue } from '@amazon/vinyl-observable'
|
|
87
|
-
import type { DashManifestData } from '@amazon/vinyl'
|
|
88
|
-
|
|
89
|
-
function createCustomTransformer(
|
|
90
|
-
manifestController: ObservableValue<Promise<DashManifestData>>
|
|
91
|
-
) {
|
|
92
|
-
const transformManifest = flowAsync(
|
|
93
|
-
(m) => filterDashRepresentations(customPredicate, throwCustomError, m),
|
|
94
|
-
(m) => sortDashRepresentations(customComparator, m)
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
return mapManifestTransform(manifestController, transformManifest)
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Filter Factory
|
|
102
|
-
|
|
103
|
-
Transformers are factory functions with the signature
|
|
104
|
-
`(ObservableValue<Promise<DashManifestData>>) => ObservableValue<Promise<DashManifestData>>`.
|
|
105
|
-
This allows composition with `flow`:
|
|
106
|
-
|
|
107
|
-
```typescript
|
|
108
|
-
import { mapManifestTransform, filterDashRepresentations } from '@amazon/vinyl'
|
|
109
|
-
|
|
110
|
-
function createCustomFilter(deps: CustomDeps) {
|
|
111
|
-
return (
|
|
112
|
-
manifestController: ObservableValue<Promise<DashManifestData>>
|
|
113
|
-
): ObservableValue<Promise<DashManifestData>> =>
|
|
114
|
-
mapManifestTransform(manifestController, (manifest) =>
|
|
115
|
-
filterDashRepresentations(
|
|
116
|
-
(rep) => customPredicate(deps, rep),
|
|
117
|
-
throwCustomError,
|
|
118
|
-
manifest
|
|
119
|
-
)
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
For filters that depend on additional reactive inputs, use `combineData`:
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
import { combineData } from '@amazon/vinyl-observable'
|
|
128
|
-
import { filterDashRepresentations } from '@amazon/vinyl'
|
|
129
|
-
|
|
130
|
-
function createConfigFilter(deps: {
|
|
131
|
-
readonly configProvider: ObservableValue<FilterConfig>
|
|
132
|
-
}): (
|
|
133
|
-
manifestAndPath: ObservableValue<Promise<DashManifestData>>
|
|
134
|
-
) => ObservableValue<Promise<DashManifestData>> {
|
|
135
|
-
return (manifestAndPath) =>
|
|
136
|
-
combineData({
|
|
137
|
-
manifestAndPath,
|
|
138
|
-
config: deps.configProvider,
|
|
139
|
-
}).map(async ({ manifestAndPath, config }) => {
|
|
140
|
-
const { manifest, baseUrl } = await manifestAndPath
|
|
141
|
-
return {
|
|
142
|
-
baseUrl,
|
|
143
|
-
manifest: filterDashRepresentations(
|
|
144
|
-
(rep) => rep.bandwidth <= config.maxBitrate,
|
|
145
|
-
throwConfigFilterError,
|
|
146
|
-
manifest
|
|
147
|
-
),
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### Composing Transformers with flow
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
import { flow } from '@amazon/vinyl-util'
|
|
157
|
-
|
|
158
|
-
function createMyManifestTransformer(
|
|
159
|
-
deps: MyTransformerDeps
|
|
160
|
-
): ObservableValue<Promise<DashManifestData>> {
|
|
161
|
-
return flow(
|
|
162
|
-
createCustomFilter(deps),
|
|
163
|
-
createSortTransformer()
|
|
164
|
-
)(createDefaultDashManifestTransformer(deps))
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## Integration with createVinylPlayer
|
|
169
|
-
|
|
170
|
-
Override the `manifestTransformed` factory to inject custom transformations
|
|
171
|
-
after the default sorting:
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
import {
|
|
175
|
-
createVinylPlayer,
|
|
176
|
-
createDefaultDashManifestTransformer,
|
|
177
|
-
createDashFactories,
|
|
178
|
-
filterDashRepresentations,
|
|
179
|
-
mapManifestTransform,
|
|
180
|
-
type DashManifestTransformerDeps,
|
|
181
|
-
} from '@amazon/vinyl'
|
|
182
|
-
import { flowAsync } from '@amazon/vinyl-util'
|
|
183
|
-
import { MediaUnsupportedError } from '@amazon/vinyl-util'
|
|
184
|
-
|
|
185
|
-
function filterStereoOnly(manifest: DashManifest) {
|
|
186
|
-
return filterDashRepresentations(
|
|
187
|
-
(rep) =>
|
|
188
|
-
!rep.AudioChannelConfiguration ||
|
|
189
|
-
Number(rep.AudioChannelConfiguration.value) <= 2,
|
|
190
|
-
() => {
|
|
191
|
-
throw new MediaUnsupportedError(
|
|
192
|
-
'Only stereo audio supported',
|
|
193
|
-
'stereo_only'
|
|
194
|
-
)
|
|
195
|
-
},
|
|
196
|
-
manifest
|
|
197
|
-
)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const player = createVinylPlayer(
|
|
201
|
-
{ media: new Audio() },
|
|
202
|
-
{
|
|
203
|
-
createDashFactories: (options) => (deps) => (loadOptions) => ({
|
|
204
|
-
...createDashFactories(options)(deps)(loadOptions),
|
|
205
|
-
manifestTransformed: (
|
|
206
|
-
transformDeps: DashManifestTransformerDeps
|
|
207
|
-
) => {
|
|
208
|
-
const transformManifest = flowAsync(filterStereoOnly)
|
|
209
|
-
|
|
210
|
-
return mapManifestTransform(
|
|
211
|
-
createDefaultDashManifestTransformer(transformDeps),
|
|
212
|
-
transformManifest
|
|
213
|
-
)
|
|
214
|
-
},
|
|
215
|
-
}),
|
|
216
|
-
}
|
|
217
|
-
)
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
## Utility Functions
|
|
221
|
-
|
|
222
|
-
- `filterDashAdaptationSets()` - Filter adaptation sets with error handling
|
|
223
|
-
- `filterDashRepresentations()` - Filter representations with error handling
|
|
224
|
-
- `sortDashAdaptationSets()` - Sort adaptation sets by comparator
|
|
225
|
-
- `sortDashRepresentations()` - Sort representations by comparator
|
|
226
|
-
- `manifestIsPlayable()` - Check if manifest has playable content
|
|
227
|
-
- `mapManifestTransform()` - Apply a
|
|
228
|
-
`(DashManifest) => MaybePromise<DashManifest>` over a manifest observable,
|
|
229
|
-
preserving baseUrl
|
|
230
|
-
|
|
231
|
-
## Best Practices
|
|
232
|
-
|
|
233
|
-
1. **Error Handling**: Always provide meaningful error codes and messages
|
|
234
|
-
2. **Performance**: Use async filters only for expensive operations (e.g. DRM)
|
|
235
|
-
3. **Composition**: Prefer small, focused filter factories composed with `flow`
|
|
236
|
-
4. **Immutability**: Filter utilities clone manifests internally
|