shaka-player 4.2.11 → 4.2.13
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/.github/ISSUE_TEMPLATE/bug_report.md +11 -0
- package/.github/workflows/appspot.yaml +39 -0
- package/.github/workflows/build-and-test.yaml +15 -0
- package/.github/workflows/selenium-lab-tests.yaml +3 -2
- package/.github/workflows/update-issues.yaml +1 -0
- package/CHANGELOG.md +42 -0
- package/CONTRIBUTORS +1 -0
- package/demo/custom.js +1 -1
- package/dist/demo.compiled.debug.js +2 -2
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +2 -2
- package/dist/demo.compiled.map +2 -2
- package/dist/deps.js +3 -3
- package/dist/locales.js +33 -33
- package/dist/shaka-player.compiled.d.ts +22 -0
- package/dist/shaka-player.compiled.debug.d.ts +22 -0
- package/dist/shaka-player.compiled.debug.js +233 -232
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.js +738 -738
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +22 -0
- package/dist/shaka-player.ui.debug.d.ts +22 -0
- package/dist/shaka-player.ui.debug.js +259 -258
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.js +917 -916
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/drm-config.md +9 -6
- package/docs/tutorials/selenium-grid-config.md +60 -8
- package/externs/ima.js +34 -0
- package/karma.conf.js +50 -7
- package/lib/abr/simple_abr_manager.js +6 -6
- package/lib/cea/mp4_cea_parser.js +30 -14
- package/lib/dash/dash_parser.js +18 -3
- package/lib/hls/hls_parser.js +13 -2
- package/lib/media/drm_engine.js +14 -8
- package/lib/media/gap_jumping_controller.js +24 -2
- package/lib/media/media_source_engine.js +5 -3
- package/lib/media/playhead.js +11 -0
- package/lib/media/region_observer.js +19 -1
- package/lib/media/streaming_engine.js +7 -3
- package/lib/media/video_wrapper.js +6 -1
- package/lib/player.js +9 -8
- package/lib/polyfill/abort_controller.js +7 -0
- package/lib/polyfill/orientation.js +34 -7
- package/lib/text/vtt_text_parser.js +3 -1
- package/lib/util/manifest_parser_utils.js +5 -3
- package/lib/util/mp4_box_parsers.js +8 -3
- package/lib/util/platform.js +14 -0
- package/lib/util/stream_utils.js +70 -26
- package/package.json +2 -3
- package/test/cast/cast_utils_unit.js +4 -1
- package/test/cea/mp4_cea_parser_unit.js +26 -3
- package/test/dash/dash_parser_manifest_unit.js +120 -0
- package/test/hls/hls_parser_unit.js +222 -0
- package/test/media/drm_engine_integration.js +2 -2
- package/test/media/region_observer_unit.js +2 -1
- package/test/media/segment_index_unit.js +3 -3
- package/test/media/streaming_engine_integration.js +14 -11
- package/test/offline/offline_integration.js +6 -3
- package/test/player_external.js +19 -16
- package/test/player_integration.js +35 -48
- package/test/player_src_equals_integration.js +9 -5
- package/test/test/assets/multiple-trun-init.mp4 +0 -0
- package/test/test/assets/multiple-trun-segment.mp4 +0 -0
- package/test/test/util/fake_demo_main.js +7 -3
- package/test/test/util/layout_tests.js +2 -2
- package/test/test/util/ui_utils.js +9 -2
- package/test/test/util/util.js +6 -27
- package/test/test/util/waiter.js +73 -3
- package/test/ui/ad_ui_unit.js +9 -9
- package/test/util/stream_utils_unit.js +46 -0
- package/ui/range_element.js +5 -0
- package/ui/resolution_selection.js +7 -1
|
@@ -59,15 +59,18 @@ If this is the only `<ContentProtection>` element in the manifest, Shaka will
|
|
|
59
59
|
try all key systems it knows. (Based on keySystemsByURI in
|
|
60
60
|
{@linksource shaka.extern.DashManifestConfiguration}.)
|
|
61
61
|
|
|
62
|
-
Through `player.configure()`, you can
|
|
62
|
+
Through `player.configure()`, you can update the dash key systems mapping by
|
|
63
63
|
scheme URI:
|
|
64
|
+
|
|
64
65
|
```js
|
|
65
66
|
player.configure({
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
manifest: {
|
|
68
|
+
dash: {
|
|
69
|
+
keySystemsByURI: {
|
|
70
|
+
'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready.recommendation',
|
|
71
|
+
'urn:uuid:79f0049a-4098-8642-ab92-e65be0885f95': 'com.microsoft.playready.recommendation',
|
|
72
|
+
}
|
|
73
|
+
}
|
|
71
74
|
}
|
|
72
75
|
});
|
|
73
76
|
```
|
|
@@ -47,17 +47,20 @@ vars:
|
|
|
47
47
|
FirefoxMac:
|
|
48
48
|
browser: firefox
|
|
49
49
|
os: Mac
|
|
50
|
-
|
|
50
|
+
extra_configs:
|
|
51
|
+
- *firefox_config
|
|
51
52
|
|
|
52
53
|
FirefoxWindows:
|
|
53
54
|
browser: firefox
|
|
54
55
|
os: Windows
|
|
55
|
-
|
|
56
|
+
extra_configs:
|
|
57
|
+
- *firefox_config
|
|
56
58
|
|
|
57
59
|
FirefoxLinux:
|
|
58
60
|
browser: firefox
|
|
59
61
|
os: Linux
|
|
60
|
-
|
|
62
|
+
extra_configs:
|
|
63
|
+
- *firefox_config
|
|
61
64
|
```
|
|
62
65
|
|
|
63
66
|
### Browsers
|
|
@@ -75,8 +78,8 @@ browser made available to Karma. Within each of those keys are the following:
|
|
|
75
78
|
and must match the string and case used in the Selenium node config.
|
|
76
79
|
- `disabled` (optional): If true, this browser is disabled and will not be
|
|
77
80
|
used unless explicitly requested.
|
|
78
|
-
- `
|
|
79
|
-
merged with the WebDriver launcher config in Karma.
|
|
81
|
+
- `extra_configs` (optional): An array of dictionaries of extra configs which
|
|
82
|
+
will be merged with the WebDriver launcher config in Karma.
|
|
80
83
|
|
|
81
84
|
Examples of basic desktop browsers definitions:
|
|
82
85
|
|
|
@@ -96,9 +99,9 @@ Safari:
|
|
|
96
99
|
SafariTP:
|
|
97
100
|
browser: safari
|
|
98
101
|
os: Mac
|
|
99
|
-
|
|
100
|
-
safari.options:
|
|
101
|
-
|
|
102
|
+
extra_configs:
|
|
103
|
+
- safari.options:
|
|
104
|
+
technologyPreview: true
|
|
102
105
|
|
|
103
106
|
ChromeWindows:
|
|
104
107
|
browser: chrome
|
|
@@ -120,3 +123,52 @@ FirefoxLinux:
|
|
|
120
123
|
browser: firefox
|
|
121
124
|
os: Linux
|
|
122
125
|
```
|
|
126
|
+
|
|
127
|
+
### Composing configs
|
|
128
|
+
|
|
129
|
+
You can define variables for browser configs that can be composed together in
|
|
130
|
+
`extra_configs`. For example, below you will see some basic definitions for
|
|
131
|
+
Chrome arguments and parameters, and then an additional config with an argument
|
|
132
|
+
that is only needed for some platforms. `karma.conf.js` will merge those
|
|
133
|
+
argument lists intelligently when it loads the YAML config.
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
vars:
|
|
137
|
+
basic_chrome_config: &basic_chrome_config
|
|
138
|
+
goog:chromeOptions:
|
|
139
|
+
args:
|
|
140
|
+
# Normally, Chrome disallows autoplaying videos in many cases. Enable
|
|
141
|
+
# it for testing.
|
|
142
|
+
- "--autoplay-policy=no-user-gesture-required"
|
|
143
|
+
|
|
144
|
+
# Instruct chromedriver not to disable component updater.
|
|
145
|
+
excludeSwitches:
|
|
146
|
+
- "disable-component-update"
|
|
147
|
+
|
|
148
|
+
headless_chrome_config: &headless_chrome_config
|
|
149
|
+
goog:chromeOptions:
|
|
150
|
+
args:
|
|
151
|
+
# Run in headless mode.
|
|
152
|
+
- "--headless"
|
|
153
|
+
|
|
154
|
+
ChromeLinux:
|
|
155
|
+
browser: chrome
|
|
156
|
+
os: Mac
|
|
157
|
+
extra_configs:
|
|
158
|
+
- *basic_chrome_config
|
|
159
|
+
|
|
160
|
+
ChromeWindows:
|
|
161
|
+
browser: chrome
|
|
162
|
+
os: Mac
|
|
163
|
+
extra_configs:
|
|
164
|
+
- *basic_chrome_config
|
|
165
|
+
|
|
166
|
+
ChromeMac:
|
|
167
|
+
browser: chrome
|
|
168
|
+
os: Mac
|
|
169
|
+
# Take the parameters and arguments for basic_chrome_config, then append the
|
|
170
|
+
# arguments for headless_chrome_config.
|
|
171
|
+
extra_configs:
|
|
172
|
+
- *basic_chrome_config
|
|
173
|
+
- *headless_chrome_config
|
|
174
|
+
```
|
package/externs/ima.js
CHANGED
|
@@ -501,6 +501,14 @@ google.ima.dai.api.StreamRequest.prototype.streamActivityMonitorId;
|
|
|
501
501
|
google.ima.dai.api.StreamRequest.prototype.format;
|
|
502
502
|
|
|
503
503
|
|
|
504
|
+
/**
|
|
505
|
+
* @type {Object.<
|
|
506
|
+
* google.ima.dai.api.OmidVerificationVendor,
|
|
507
|
+
* (google.ima.dai.api.OmidAccessMode | undefined)>}
|
|
508
|
+
*/
|
|
509
|
+
google.ima.dai.api.StreamRequest.prototype.omidAccessModeRules;
|
|
510
|
+
|
|
511
|
+
|
|
504
512
|
/**
|
|
505
513
|
* @enum {string}
|
|
506
514
|
*/
|
|
@@ -587,3 +595,29 @@ google.ima.dai.api.StreamEvent.Type = {
|
|
|
587
595
|
SKIPPED: 'skip',
|
|
588
596
|
VIDEO_CLICKED: 'videoClicked',
|
|
589
597
|
};
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* @enum {number}
|
|
602
|
+
*/
|
|
603
|
+
google.ima.dai.api.OmidVerificationVendor = {
|
|
604
|
+
OTHER: 1,
|
|
605
|
+
MOAT: 2,
|
|
606
|
+
DOUBLEVERIFY: 3,
|
|
607
|
+
INTEGRAL_AD_SCIENCE: 4,
|
|
608
|
+
PIXELATE: 5,
|
|
609
|
+
NIELSEN: 6,
|
|
610
|
+
COMSCORE: 7,
|
|
611
|
+
MEETRICS: 8,
|
|
612
|
+
GOOGLE: 9,
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* @enum {string}
|
|
618
|
+
*/
|
|
619
|
+
google.ima.dai.api.OmidAccessMode = {
|
|
620
|
+
FULL: 'full',
|
|
621
|
+
DOMAIN: 'domain',
|
|
622
|
+
LIMITED: 'limited',
|
|
623
|
+
};
|
package/karma.conf.js
CHANGED
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
// Karma configuration
|
|
8
8
|
// Install required modules by running "npm install"
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// lodash is an indirect dependency, depended on by Karma
|
|
11
|
+
const _ = require('lodash');
|
|
11
12
|
const fs = require('fs');
|
|
12
13
|
const glob = require('glob');
|
|
14
|
+
const Jimp = require('jimp');
|
|
13
15
|
const path = require('path');
|
|
14
16
|
const rimraf = require('rimraf');
|
|
15
17
|
const {ssim} = require('ssim.js');
|
|
@@ -17,6 +19,32 @@ const util = require('karma/common/util');
|
|
|
17
19
|
const which = require('which');
|
|
18
20
|
const yaml = require('js-yaml');
|
|
19
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Like Object.assign, but recursive and doesn't clobber objects and arrays.
|
|
24
|
+
* If two arrays are merged, they are concatenated.
|
|
25
|
+
* Ex:
|
|
26
|
+
* mergeConfigs({ foo: 'bar', args: [1, 2, 3] },
|
|
27
|
+
* { baz: 'blah', args: [4, 5, 6] })
|
|
28
|
+
* => { foo: 'bar', baz: 'blah', args: [1, 2, 3, 4, 5, 6] }
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} first
|
|
31
|
+
* @param {Object} second
|
|
32
|
+
* @return {Object}
|
|
33
|
+
*/
|
|
34
|
+
function mergeConfigs(first, second) {
|
|
35
|
+
return _.mergeWith(
|
|
36
|
+
first,
|
|
37
|
+
second,
|
|
38
|
+
(firstValue, secondValue) => {
|
|
39
|
+
// Merge arrays by concatenation.
|
|
40
|
+
if (Array.isArray(firstValue)) {
|
|
41
|
+
return firstValue.concat(secondValue);
|
|
42
|
+
}
|
|
43
|
+
// Use lodash's default merge behavior for everything else.
|
|
44
|
+
return undefined;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
20
48
|
/**
|
|
21
49
|
* @param {Object} config
|
|
22
50
|
*/
|
|
@@ -78,7 +106,7 @@ module.exports = (config) => {
|
|
|
78
106
|
}
|
|
79
107
|
|
|
80
108
|
// Add standard WebDriver configs.
|
|
81
|
-
|
|
109
|
+
mergeConfigs(launcher, {
|
|
82
110
|
base: 'WebDriver',
|
|
83
111
|
config: {hostname: gridHostname, port: gridPort},
|
|
84
112
|
pseudoActivityInterval: 20000,
|
|
@@ -87,8 +115,10 @@ module.exports = (config) => {
|
|
|
87
115
|
version: metadata.version,
|
|
88
116
|
});
|
|
89
117
|
|
|
90
|
-
if (metadata.
|
|
91
|
-
|
|
118
|
+
if (metadata.extra_configs) {
|
|
119
|
+
for (const config of metadata.extra_configs) {
|
|
120
|
+
mergeConfigs(launcher, config);
|
|
121
|
+
}
|
|
92
122
|
}
|
|
93
123
|
}
|
|
94
124
|
|
|
@@ -603,15 +633,27 @@ function WebDriverScreenshotMiddlewareFactory(launcher) {
|
|
|
603
633
|
}
|
|
604
634
|
|
|
605
635
|
/**
|
|
636
|
+
* @param {karma.Launcher.Browser.spec} spec
|
|
606
637
|
* @param {wd.remote} webDriverClient A WebDriver client, an object from the
|
|
607
638
|
* "wd" package, created by "wd.remote()".
|
|
608
639
|
* @return {!Promise.<!Buffer>} A Buffer containing a PNG screenshot
|
|
609
640
|
*/
|
|
610
|
-
function getScreenshot(webDriverClient) {
|
|
641
|
+
function getScreenshot(spec, webDriverClient) {
|
|
611
642
|
return new Promise((resolve, reject) => {
|
|
612
643
|
webDriverClient.takeScreenshot((error, pngBase64) => {
|
|
613
644
|
if (error) {
|
|
614
645
|
reject(error);
|
|
646
|
+
} else if (pngBase64.error) {
|
|
647
|
+
// In some failure cases, pngBase64 is an object with "error",
|
|
648
|
+
// "message", and "stacktrace" fields. This happens, for example,
|
|
649
|
+
// with a timeout from the screenshot command. This is not an
|
|
650
|
+
// expected situation, so log it. The extra newlines keep this from
|
|
651
|
+
// being overwritten on the terminal when running tests against many
|
|
652
|
+
// browsers at once.
|
|
653
|
+
console.log('\n\nUnexpected screenshot failure:\n' +
|
|
654
|
+
` Error: ${JSON.stringify(pngBase64)}\n` +
|
|
655
|
+
` WebDriver spec: ${JSON.stringify(spec)}\n\n\n`);
|
|
656
|
+
reject(pngBase64);
|
|
615
657
|
} else {
|
|
616
658
|
// Convert the screenshot to a binary buffer.
|
|
617
659
|
resolve(Buffer.from(pngBase64, 'base64'));
|
|
@@ -635,7 +677,8 @@ function WebDriverScreenshotMiddlewareFactory(launcher) {
|
|
|
635
677
|
}
|
|
636
678
|
|
|
637
679
|
/** @type {!Buffer} */
|
|
638
|
-
const fullPageScreenshotData =
|
|
680
|
+
const fullPageScreenshotData =
|
|
681
|
+
await getScreenshot(browser.spec, webDriverClient);
|
|
639
682
|
|
|
640
683
|
// Crop the screenshot to the dimensions specified in the test.
|
|
641
684
|
// Jimp is picky about types, so convert these strings to numbers.
|
|
@@ -754,7 +797,7 @@ function WebDriverScreenshotMiddlewareFactory(launcher) {
|
|
|
754
797
|
// The result is cached for the sake of performance.
|
|
755
798
|
if (webDriverClient.canTakeScreenshot === undefined) {
|
|
756
799
|
try {
|
|
757
|
-
await getScreenshot(webDriverClient);
|
|
800
|
+
await getScreenshot(browser.spec, webDriverClient);
|
|
758
801
|
webDriverClient.canTakeScreenshot = true;
|
|
759
802
|
} catch (error) {
|
|
760
803
|
webDriverClient.canTakeScreenshot = false;
|
|
@@ -48,13 +48,16 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
48
48
|
/** @private {shaka.abr.EwmaBandwidthEstimator} */
|
|
49
49
|
this.bandwidthEstimator_ = new shaka.abr.EwmaBandwidthEstimator();
|
|
50
50
|
|
|
51
|
+
/** @private {?function():void} */
|
|
52
|
+
this.onNetworkInformationChange_ = null;
|
|
53
|
+
|
|
51
54
|
// Some browsers implement the Network Information API, which allows
|
|
52
55
|
// retrieving information about a user's network connection. We listen
|
|
53
56
|
// to the change event to be able to make quick changes in case the type
|
|
54
57
|
// of connectivity changes.
|
|
55
58
|
if (navigator.connection && navigator.connection.addEventListener) {
|
|
56
59
|
this.onNetworkInformationChange_ = () => {
|
|
57
|
-
if (this.
|
|
60
|
+
if (this.enabled_ && this.config_.useNetworkInformation) {
|
|
58
61
|
this.bandwidthEstimator_ = new shaka.abr.EwmaBandwidthEstimator();
|
|
59
62
|
if (this.config_) {
|
|
60
63
|
this.bandwidthEstimator_.configure(this.config_.advanced);
|
|
@@ -98,9 +101,6 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
98
101
|
/** @private {ResizeObserver} */
|
|
99
102
|
this.resizeObserver_ = null;
|
|
100
103
|
|
|
101
|
-
/** @private {?function():void} */
|
|
102
|
-
this.onNetworkInformationChange_ = null;
|
|
103
|
-
|
|
104
104
|
/** @private {shaka.util.Timer} */
|
|
105
105
|
this.resizeObserverTimer_ = new shaka.util.Timer(() => {
|
|
106
106
|
if (this.config_.restrictToElementSize) {
|
|
@@ -175,8 +175,8 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
175
175
|
if (this.resizeObserver_ && this.config_.restrictToElementSize) {
|
|
176
176
|
const devicePixelRatio =
|
|
177
177
|
this.config_.ignoreDevicePixelRatio ? 1 : window.devicePixelRatio;
|
|
178
|
-
maxHeight = this.mediaElement_.
|
|
179
|
-
maxWidth = this.mediaElement_.
|
|
178
|
+
maxHeight = this.mediaElement_.clientHeight * devicePixelRatio;
|
|
179
|
+
maxWidth = this.mediaElement_.clientWidth * devicePixelRatio;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
// Get sorted Variants.
|
|
@@ -9,6 +9,7 @@ goog.provide('shaka.cea.Mp4CeaParser');
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
10
|
goog.require('shaka.cea.ICeaParser');
|
|
11
11
|
goog.require('shaka.cea.SeiProcessor');
|
|
12
|
+
goog.require('shaka.log');
|
|
12
13
|
goog.require('shaka.util.DataViewReader');
|
|
13
14
|
goog.require('shaka.util.Error');
|
|
14
15
|
goog.require('shaka.util.Mp4Parser');
|
|
@@ -122,12 +123,19 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
122
123
|
// Fields that are found in MOOF boxes
|
|
123
124
|
let defaultSampleDuration = this.defaultSampleDuration_;
|
|
124
125
|
let defaultSampleSize = this.defaultSampleSize_;
|
|
125
|
-
let
|
|
126
|
+
let moofOffset = 0;
|
|
127
|
+
/** @type {!Array<shaka.util.ParsedTRUNBox>} */
|
|
128
|
+
let parsedTRUNs = [];
|
|
126
129
|
let baseMediaDecodeTime = null;
|
|
127
130
|
let timescale = shaka.cea.ICeaParser.DEFAULT_TIMESCALE_VALUE;
|
|
128
131
|
|
|
129
132
|
new Mp4Parser()
|
|
130
|
-
.box('moof',
|
|
133
|
+
.box('moof', (box) => {
|
|
134
|
+
moofOffset = box.start;
|
|
135
|
+
// trun box parsing is reset on each moof.
|
|
136
|
+
parsedTRUNs = [];
|
|
137
|
+
Mp4Parser.children(box);
|
|
138
|
+
})
|
|
131
139
|
.box('traf', Mp4Parser.children)
|
|
132
140
|
.fullBox('trun', (box) => {
|
|
133
141
|
goog.asserts.assert(
|
|
@@ -136,10 +144,8 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
136
144
|
|
|
137
145
|
const parsedTRUN = shaka.util.Mp4BoxParsers.parseTRUN(
|
|
138
146
|
box.reader, box.version, box.flags);
|
|
139
|
-
|
|
140
|
-
sampleData = parsedTRUN.sampleData;
|
|
147
|
+
parsedTRUNs.push(parsedTRUN);
|
|
141
148
|
})
|
|
142
|
-
|
|
143
149
|
.fullBox('tfhd', (box) => {
|
|
144
150
|
goog.asserts.assert(
|
|
145
151
|
box.flags != null,
|
|
@@ -163,7 +169,6 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
163
169
|
timescale = this.trackIdToTimescale_.get(trackId);
|
|
164
170
|
}
|
|
165
171
|
})
|
|
166
|
-
|
|
167
172
|
.fullBox('tfdt', (box) => {
|
|
168
173
|
goog.asserts.assert(
|
|
169
174
|
box.version != null,
|
|
@@ -176,15 +181,18 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
176
181
|
})
|
|
177
182
|
.box('mdat', (box) => {
|
|
178
183
|
if (baseMediaDecodeTime === null) {
|
|
179
|
-
// This field should have been populated by
|
|
180
|
-
//
|
|
184
|
+
// This field should have been populated by the Base Media Decode
|
|
185
|
+
// Time in the tfdt box.
|
|
186
|
+
shaka.log.alwaysWarn(
|
|
187
|
+
'Unable to find base media decode time for CEA captions!');
|
|
181
188
|
throw new shaka.util.Error(
|
|
182
189
|
shaka.util.Error.Severity.CRITICAL,
|
|
183
190
|
shaka.util.Error.Category.TEXT,
|
|
184
191
|
shaka.util.Error.Code.INVALID_MP4_CEA);
|
|
185
192
|
}
|
|
193
|
+
const offset = moofOffset - box.start - 8;
|
|
186
194
|
this.parseMdat_(box.reader, baseMediaDecodeTime, timescale,
|
|
187
|
-
defaultSampleDuration, defaultSampleSize,
|
|
195
|
+
defaultSampleDuration, defaultSampleSize, offset, parsedTRUNs,
|
|
188
196
|
captionPackets);
|
|
189
197
|
})
|
|
190
198
|
.parse(mediaSegment, /* partialOkay= */ false);
|
|
@@ -199,12 +207,13 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
199
207
|
* @param {number} timescale
|
|
200
208
|
* @param {number} defaultSampleDuration
|
|
201
209
|
* @param {number} defaultSampleSize
|
|
202
|
-
* @param {
|
|
210
|
+
* @param {number} offset
|
|
211
|
+
* @param {!Array<shaka.util.ParsedTRUNBox>} parsedTRUNs
|
|
203
212
|
* @param {!Array<!shaka.cea.ICeaParser.CaptionPacket>} captionPackets
|
|
204
213
|
* @private
|
|
205
214
|
*/
|
|
206
215
|
parseMdat_(reader, time, timescale, defaultSampleDuration,
|
|
207
|
-
defaultSampleSize,
|
|
216
|
+
defaultSampleSize, offset, parsedTRUNs, captionPackets) {
|
|
208
217
|
let sampleIndex = 0;
|
|
209
218
|
|
|
210
219
|
// The fields in each ParsedTRUNSample contained in the sampleData
|
|
@@ -213,17 +222,24 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
213
222
|
// composition time offset, we default to 0.
|
|
214
223
|
let sampleSize = defaultSampleSize;
|
|
215
224
|
|
|
225
|
+
// Combine all sample data. This assumes that the samples described across
|
|
226
|
+
// multiple trun boxes are still continuous in the mdat box.
|
|
227
|
+
const sampleDatas = parsedTRUNs.map((t) => t.sampleData);
|
|
228
|
+
const sampleData = [].concat(...sampleDatas);
|
|
229
|
+
|
|
216
230
|
if (sampleData.length) {
|
|
217
231
|
sampleSize = sampleData[0].sampleSize || defaultSampleSize;
|
|
218
232
|
}
|
|
219
233
|
|
|
234
|
+
reader.skip(offset + parsedTRUNs[0].dataOffset);
|
|
235
|
+
|
|
220
236
|
while (reader.hasMoreData()) {
|
|
221
237
|
const naluSize = reader.readUint32();
|
|
222
238
|
const naluType = reader.readUint8() & 0x1F;
|
|
223
239
|
if (naluType == shaka.cea.ICeaParser.NALU_TYPE_SEI) {
|
|
224
240
|
let timeOffset = 0;
|
|
225
241
|
|
|
226
|
-
if (sampleData.length
|
|
242
|
+
if (sampleIndex < sampleData.length) {
|
|
227
243
|
timeOffset = sampleData[sampleIndex].sampleCompositionTimeOffset || 0;
|
|
228
244
|
}
|
|
229
245
|
|
|
@@ -246,7 +262,7 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
246
262
|
}
|
|
247
263
|
sampleSize -= (naluSize + 4);
|
|
248
264
|
if (sampleSize == 0) {
|
|
249
|
-
if (sampleData.length
|
|
265
|
+
if (sampleIndex < sampleData.length) {
|
|
250
266
|
time += sampleData[sampleIndex].sampleDuration ||
|
|
251
267
|
defaultSampleDuration;
|
|
252
268
|
} else {
|
|
@@ -255,7 +271,7 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
255
271
|
|
|
256
272
|
sampleIndex++;
|
|
257
273
|
|
|
258
|
-
if (sampleData.length
|
|
274
|
+
if (sampleIndex < sampleData.length) {
|
|
259
275
|
sampleSize = sampleData[sampleIndex].sampleSize || defaultSampleSize;
|
|
260
276
|
} else {
|
|
261
277
|
sampleSize = defaultSampleSize;
|
package/lib/dash/dash_parser.js
CHANGED
|
@@ -886,7 +886,19 @@ shaka.dash.DashParser = class {
|
|
|
886
886
|
|
|
887
887
|
// Parallel for HLS VIDEO-RANGE as defined in DASH-IF IOP v4.3 6.2.5.1.
|
|
888
888
|
let videoRange;
|
|
889
|
-
|
|
889
|
+
|
|
890
|
+
// Ref. https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf
|
|
891
|
+
// If signaled, a Supplemental or Essential Property descriptor
|
|
892
|
+
// shall be used, with the schemeIdUri set to
|
|
893
|
+
// urn:mpeg:mpegB:cicp:<Parameter> as defined in
|
|
894
|
+
// ISO/IEC 23001-8 [49] and <Parameter> one of the
|
|
895
|
+
// following: ColourPrimaries, TransferCharacteristics,
|
|
896
|
+
// or MatrixCoefficients.
|
|
897
|
+
const scheme = 'urn:mpeg:mpegB:cicp';
|
|
898
|
+
const transferCharacteristicsScheme = `${scheme}:TransferCharacteristics`;
|
|
899
|
+
const colourPrimariesScheme = `${scheme}:ColourPrimaries`;
|
|
900
|
+
const matrixCoefficientsScheme = `${scheme}:MatrixCoefficients`;
|
|
901
|
+
|
|
890
902
|
const getVideoRangeFromTransferCharacteristicCICP = (cicp) => {
|
|
891
903
|
switch (cicp) {
|
|
892
904
|
case 1:
|
|
@@ -912,10 +924,13 @@ shaka.dash.DashParser = class {
|
|
|
912
924
|
const schemeId = prop.getAttribute('schemeIdUri');
|
|
913
925
|
if (schemeId == 'http://dashif.org/guidelines/trickmode') {
|
|
914
926
|
trickModeFor = prop.getAttribute('value');
|
|
915
|
-
} else if (schemeId ==
|
|
927
|
+
} else if (schemeId == transferCharacteristicsScheme) {
|
|
916
928
|
videoRange = getVideoRangeFromTransferCharacteristicCICP(
|
|
917
929
|
parseInt(prop.getAttribute('value'), 10),
|
|
918
930
|
);
|
|
931
|
+
} else if (schemeId == colourPrimariesScheme ||
|
|
932
|
+
schemeId == matrixCoefficientsScheme) {
|
|
933
|
+
continue;
|
|
919
934
|
} else {
|
|
920
935
|
unrecognizedEssentialProperty = true;
|
|
921
936
|
}
|
|
@@ -925,7 +940,7 @@ shaka.dash.DashParser = class {
|
|
|
925
940
|
XmlUtils.findChildren(elem, 'SupplementalProperty');
|
|
926
941
|
for (const prop of supplementalProperties) {
|
|
927
942
|
const schemeId = prop.getAttribute('schemeIdUri');
|
|
928
|
-
if (schemeId ==
|
|
943
|
+
if (schemeId == transferCharacteristicsScheme) {
|
|
929
944
|
videoRange = getVideoRangeFromTransferCharacteristicCICP(
|
|
930
945
|
parseInt(prop.getAttribute('value'), 10),
|
|
931
946
|
);
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -1713,6 +1713,13 @@ shaka.hls.HlsParser = class {
|
|
|
1713
1713
|
}
|
|
1714
1714
|
}
|
|
1715
1715
|
|
|
1716
|
+
// If there are no roles, and we have defaulted to the subtitle "kind" for
|
|
1717
|
+
// this track, add the implied subtitle role.
|
|
1718
|
+
if (!roles.length &&
|
|
1719
|
+
kind === shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE) {
|
|
1720
|
+
roles.push(shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE);
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1716
1723
|
const serverControlTag = shaka.hls.Utils.getFirstTagWithName(
|
|
1717
1724
|
playlist.tags, 'EXT-X-SERVER-CONTROL');
|
|
1718
1725
|
const canSkipSegments = serverControlTag ?
|
|
@@ -1742,7 +1749,7 @@ shaka.hls.HlsParser = class {
|
|
|
1742
1749
|
width: undefined,
|
|
1743
1750
|
height: undefined,
|
|
1744
1751
|
bandwidth: undefined,
|
|
1745
|
-
roles
|
|
1752
|
+
roles,
|
|
1746
1753
|
forced: forced,
|
|
1747
1754
|
channelsCount,
|
|
1748
1755
|
audioSamplingRate: null,
|
|
@@ -2150,6 +2157,10 @@ shaka.hls.HlsParser = class {
|
|
|
2150
2157
|
// We're interested in the duration part.
|
|
2151
2158
|
const extinfValues = extinfTag.value.split(',');
|
|
2152
2159
|
const duration = Number(extinfValues[0]);
|
|
2160
|
+
// Skip segments without duration
|
|
2161
|
+
if (duration == 0) {
|
|
2162
|
+
return null;
|
|
2163
|
+
}
|
|
2153
2164
|
endTime = startTime + duration;
|
|
2154
2165
|
} else {
|
|
2155
2166
|
endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
|
|
@@ -2341,9 +2352,9 @@ shaka.hls.HlsParser = class {
|
|
|
2341
2352
|
playlist.absoluteUri,
|
|
2342
2353
|
type,
|
|
2343
2354
|
hlsAes128Key);
|
|
2344
|
-
previousReference = reference;
|
|
2345
2355
|
|
|
2346
2356
|
if (reference) {
|
|
2357
|
+
previousReference = reference;
|
|
2347
2358
|
reference.discontinuitySequence = discontinuitySequence;
|
|
2348
2359
|
|
|
2349
2360
|
if (this.config_.hls.ignoreManifestProgramDateTime &&
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -354,18 +354,24 @@ shaka.media.DrmEngine = class {
|
|
|
354
354
|
shaka.media.DrmEngine.replaceDrmInfo_(variants, servers);
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
|
|
357
|
+
/** @type {!Set<shaka.extern.DrmInfo>} */
|
|
358
|
+
const drmInfos = new Set();
|
|
358
359
|
for (const variant of variants) {
|
|
359
|
-
const
|
|
360
|
-
for (const info of
|
|
361
|
-
|
|
362
|
-
info,
|
|
363
|
-
shaka.util.MapUtils.asMap(this.config_.servers),
|
|
364
|
-
shaka.util.MapUtils.asMap(this.config_.advanced || {}),
|
|
365
|
-
this.config_.keySystemsMapping);
|
|
360
|
+
const variantDrmInfos = this.getVariantDrmInfos_(variant);
|
|
361
|
+
for (const info of variantDrmInfos) {
|
|
362
|
+
drmInfos.add(info);
|
|
366
363
|
}
|
|
367
364
|
}
|
|
368
365
|
|
|
366
|
+
for (const info of drmInfos) {
|
|
367
|
+
shaka.media.DrmEngine.fillInDrmInfoDefaults_(
|
|
368
|
+
info,
|
|
369
|
+
shaka.util.MapUtils.asMap(this.config_.servers),
|
|
370
|
+
shaka.util.MapUtils.asMap(this.config_.advanced || {}),
|
|
371
|
+
this.config_.keySystemsMapping);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
|
|
369
375
|
/** @type {!Map.<string, MediaKeySystemConfiguration>} */
|
|
370
376
|
let configsByKeySystem;
|
|
371
377
|
|
|
@@ -58,6 +58,9 @@ shaka.media.GapJumpingController = class {
|
|
|
58
58
|
/** @private {number} */
|
|
59
59
|
this.prevReadyState_ = video.readyState;
|
|
60
60
|
|
|
61
|
+
/** @private {number} */
|
|
62
|
+
this.startTime_ = 0;
|
|
63
|
+
|
|
61
64
|
/** @private {number} */
|
|
62
65
|
this.gapsJumped_ = 0;
|
|
63
66
|
|
|
@@ -123,6 +126,18 @@ shaka.media.GapJumpingController = class {
|
|
|
123
126
|
this.onPollGapJump_();
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Called when playback has started and the video element is
|
|
131
|
+
* listening for seeks.
|
|
132
|
+
*
|
|
133
|
+
* @param {number} startTime
|
|
134
|
+
*/
|
|
135
|
+
onStarted(startTime) {
|
|
136
|
+
if (this.video_.seeking && !this.seekingEventReceived_) {
|
|
137
|
+
this.seekingEventReceived_ = true;
|
|
138
|
+
this.startTime_ = startTime;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
126
141
|
|
|
127
142
|
/** Called when a seek has started. */
|
|
128
143
|
onSeeking() {
|
|
@@ -164,8 +179,8 @@ shaka.media.GapJumpingController = class {
|
|
|
164
179
|
// while paused on a livestream. We make an exception for time 0, since we
|
|
165
180
|
// may be _required_ to seek on startup before play can begin, but only if
|
|
166
181
|
// autoplay is enabled.
|
|
167
|
-
if (this.video_.paused && (this.video_.currentTime !=
|
|
168
|
-
(!this.video_.autoplay && this.video_.currentTime ==
|
|
182
|
+
if (this.video_.paused && (this.video_.currentTime != this.startTime_ ||
|
|
183
|
+
(!this.video_.autoplay && this.video_.currentTime == this.startTime_))) {
|
|
169
184
|
return;
|
|
170
185
|
}
|
|
171
186
|
|
|
@@ -228,6 +243,13 @@ shaka.media.GapJumpingController = class {
|
|
|
228
243
|
}
|
|
229
244
|
|
|
230
245
|
this.video_.currentTime = jumpTo;
|
|
246
|
+
// This accounts for the possibility that we jump a gap at the start
|
|
247
|
+
// position but we jump _into_ another gap. By setting the start
|
|
248
|
+
// position to the new jumpTo we ensure that the check above will
|
|
249
|
+
// pass even though the video is still paused.
|
|
250
|
+
if (currentTime == this.startTime_) {
|
|
251
|
+
this.startTime_ = jumpTo;
|
|
252
|
+
}
|
|
231
253
|
this.gapsJumped_++;
|
|
232
254
|
this.onEvent_(
|
|
233
255
|
new shaka.util.FakeEvent(shaka.util.FakeEvent.EventName.GapJumped));
|
|
@@ -529,11 +529,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
529
529
|
* @param {boolean=} seeked True if we just seeked
|
|
530
530
|
* @param {boolean=} adaptation True if we just automatically switched active
|
|
531
531
|
* variant(s).
|
|
532
|
+
* @param {boolean=} isChunkedData True if we add to the buffer from the
|
|
533
|
+
* partial read of the segment.
|
|
532
534
|
* @return {!Promise}
|
|
533
535
|
*/
|
|
534
536
|
async appendBuffer(
|
|
535
537
|
contentType, data, reference, hasClosedCaptions, seeked = false,
|
|
536
|
-
adaptation = false) {
|
|
538
|
+
adaptation = false, isChunkedData = false) {
|
|
537
539
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
538
540
|
|
|
539
541
|
if (contentType == ContentType.TEXT) {
|
|
@@ -706,13 +708,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
706
708
|
let bufferedBefore = null;
|
|
707
709
|
|
|
708
710
|
await this.enqueueOperation_(contentType, () => {
|
|
709
|
-
if (goog.DEBUG && reference) {
|
|
711
|
+
if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
|
|
710
712
|
bufferedBefore = this.getBuffered_(contentType);
|
|
711
713
|
}
|
|
712
714
|
this.append_(contentType, data);
|
|
713
715
|
});
|
|
714
716
|
|
|
715
|
-
if (goog.DEBUG && reference && !reference.isPreload()) {
|
|
717
|
+
if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
|
|
716
718
|
const bufferedAfter = this.getBuffered_(contentType);
|
|
717
719
|
const newBuffered = shaka.media.TimeRangesUtils.computeAddedRange(
|
|
718
720
|
bufferedBefore, bufferedAfter);
|