shaka-player 4.0.2 → 4.1.1
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/AUTHORS +1 -0
- package/CHANGELOG.md +36 -16
- package/CONTRIBUTORS +2 -0
- package/build/test.py +1 -1
- package/demo/common/assets.js +1 -1
- package/demo/common/message_ids.js +1 -0
- package/demo/config.js +3 -1
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/demo/main.js +3 -3
- package/dist/demo.compiled.debug.js +7 -7
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +7 -7
- package/dist/demo.compiled.map +2 -2
- package/dist/deps.js +2 -2
- package/dist/locales.js +30 -30
- package/dist/receiver.compiled.debug.js +3 -3
- package/dist/receiver.compiled.debug.map +2 -2
- package/dist/receiver.compiled.js +3 -3
- package/dist/receiver.compiled.map +2 -2
- package/dist/shaka-player.compiled.d.ts +14 -8
- package/dist/shaka-player.compiled.debug.d.ts +14 -8
- package/dist/shaka-player.compiled.debug.externs.js +13 -4
- package/dist/shaka-player.compiled.debug.js +296 -282
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +13 -4
- package/dist/shaka-player.compiled.js +781 -773
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +15 -9
- package/dist/shaka-player.ui.debug.d.ts +15 -9
- package/dist/shaka-player.ui.debug.externs.js +13 -4
- package/dist/shaka-player.ui.debug.js +345 -326
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +13 -4
- package/dist/shaka-player.ui.js +955 -949
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/chromecast.md +3 -1
- package/docs/tutorials/drm-config.md +16 -0
- package/docs/tutorials/ui.md +26 -0
- package/externs/chromecast.js +9 -2
- package/externs/shaka/manifest.js +7 -0
- package/externs/shaka/player.js +25 -4
- package/externs/shaka/text.js +7 -0
- package/lib/cast/cast_proxy.js +19 -8
- package/lib/cast/cast_receiver.js +3 -3
- package/lib/cast/cast_sender.js +12 -2
- package/lib/hls/hls_parser.js +189 -69
- package/lib/media/drm_engine.js +17 -8
- package/lib/media/gap_jumping_controller.js +23 -1
- package/lib/media/playhead.js +47 -4
- package/lib/media/segment_index.js +26 -1
- package/lib/media/segment_reference.js +10 -3
- package/lib/media/stall_detector.js +19 -2
- package/lib/media/streaming_engine.js +9 -2
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/player.js +175 -69
- package/lib/text/cue.js +6 -0
- package/lib/text/ui_text_displayer.js +1 -0
- package/lib/text/vtt_text_parser.js +4 -0
- package/lib/util/error.js +6 -0
- package/lib/util/fake_event.js +39 -0
- package/lib/util/player_configuration.js +2 -0
- package/lib/util/stats.js +28 -1
- package/lib/util/stream_utils.js +8 -0
- package/package.json +2 -2
- package/test/cast/cast_proxy_unit.js +3 -1
- package/test/cast/cast_sender_unit.js +21 -6
- package/test/demo/demo_unit.js +2 -1
- package/test/hls/hls_parser_unit.js +102 -8
- package/test/media/adaptation_set_unit.js +1 -0
- package/test/media/playhead_unit.js +114 -25
- package/test/media/stall_detector_unit.js +20 -2
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +1 -0
- package/test/player_integration.js +3 -0
- package/test/player_unit.js +142 -1
- package/test/test/util/manifest_generator.js +2 -0
- package/test/test/util/simple_fakes.js +14 -0
- package/test/test/util/streaming_engine_util.js +1 -0
- package/test/text/vtt_text_parser_unit.js +4 -0
- package/test/ui/ui_unit.js +18 -5
- package/test/util/stream_utils_unit.js +108 -0
- package/ui/controls.js +12 -8
- package/ui/externs/ui.js +10 -1
- package/ui/statistics_button.js +10 -0
- package/ui/ui.js +12 -1
package/lib/text/cue.js
CHANGED
|
@@ -411,6 +411,7 @@ shaka.text.UITextDisplayer = class {
|
|
|
411
411
|
style.paddingRight =
|
|
412
412
|
shaka.text.UITextDisplayer.convertLengthValue_(
|
|
413
413
|
cue.linePadding, cue, this.videoContainer_);
|
|
414
|
+
style.textShadow = cue.textShadow;
|
|
414
415
|
|
|
415
416
|
if (cue.backgroundImage) {
|
|
416
417
|
style.backgroundImage = 'url(\'' + cue.backgroundImage + '\')';
|
|
@@ -293,6 +293,10 @@ shaka.text.VttTextParser = class {
|
|
|
293
293
|
validStyle = true;
|
|
294
294
|
cue.opacity = parseFloat(value);
|
|
295
295
|
break;
|
|
296
|
+
case 'text-shadow':
|
|
297
|
+
validStyle = true;
|
|
298
|
+
cue.textShadow = value;
|
|
299
|
+
break;
|
|
296
300
|
case 'white-space':
|
|
297
301
|
validStyle = true;
|
|
298
302
|
cue.wrapLine = value != 'noWrap';
|
package/lib/util/error.js
CHANGED
|
@@ -267,6 +267,12 @@ shaka.util.Error.Code = {
|
|
|
267
267
|
*/
|
|
268
268
|
'ATTEMPTS_EXHAUSTED': 1010,
|
|
269
269
|
|
|
270
|
+
/**
|
|
271
|
+
* The segment is missing.
|
|
272
|
+
* <br> error.data[0] is the URI.
|
|
273
|
+
*/
|
|
274
|
+
'SEGMENT_MISSING': 1011,
|
|
275
|
+
|
|
270
276
|
|
|
271
277
|
/** The text parser failed to parse a text stream due to an invalid header. */
|
|
272
278
|
'INVALID_TEXT_HEADER': 2000,
|
package/lib/util/fake_event.js
CHANGED
|
@@ -144,3 +144,42 @@ shaka.util.FakeEvent = class {
|
|
|
144
144
|
*/
|
|
145
145
|
stopPropagation() {}
|
|
146
146
|
};
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* An internal enum that contains the string values of all of the player events.
|
|
151
|
+
* This exists primarily to act as an implicit list of events, for tests.
|
|
152
|
+
*
|
|
153
|
+
* @enum {string}
|
|
154
|
+
*/
|
|
155
|
+
shaka.util.FakeEvent.EventName = {
|
|
156
|
+
AbrStatusChanged: 'abrstatuschanged',
|
|
157
|
+
Adaptation: 'adaptation',
|
|
158
|
+
Buffering: 'buffering',
|
|
159
|
+
DownloadFailed: 'downloadfailed',
|
|
160
|
+
DownloadHeadersReceived: 'downloadheadersreceived',
|
|
161
|
+
DrmSessionUpdate: 'drmsessionupdate',
|
|
162
|
+
Emsg: 'emsg',
|
|
163
|
+
Error: 'error',
|
|
164
|
+
ExpirationUpdated: 'expirationupdated',
|
|
165
|
+
GapJumped: 'gapjumped',
|
|
166
|
+
Loaded: 'loaded',
|
|
167
|
+
Loading: 'loading',
|
|
168
|
+
ManifestParsed: 'manifestparsed',
|
|
169
|
+
MediaQualityChanged: 'mediaqualitychanged',
|
|
170
|
+
Metadata: 'metadata',
|
|
171
|
+
OnStateChange: 'onstatechange',
|
|
172
|
+
OnStateIdle: 'onstateidle',
|
|
173
|
+
RateChange: 'ratechange',
|
|
174
|
+
SessionDataEvent: 'sessiondata',
|
|
175
|
+
StallDetected: 'stalldetected',
|
|
176
|
+
Streaming: 'streaming',
|
|
177
|
+
TextChanged: 'textchanged',
|
|
178
|
+
TextTrackVisibility: 'texttrackvisibility',
|
|
179
|
+
TimelineRegionAdded: 'timelineregionadded',
|
|
180
|
+
TimelineRegionEnter: 'timelineregionenter',
|
|
181
|
+
TimelineRegionExit: 'timelineregionexit',
|
|
182
|
+
TracksChanged: 'trackschanged',
|
|
183
|
+
Unloading: 'unloading',
|
|
184
|
+
VariantChanged: 'variantchanged',
|
|
185
|
+
};
|
|
@@ -74,6 +74,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
74
74
|
logLicenseExchange: false,
|
|
75
75
|
updateExpirationTime: 1,
|
|
76
76
|
preferredKeySystems: [],
|
|
77
|
+
keySystemsMapping: {},
|
|
77
78
|
};
|
|
78
79
|
|
|
79
80
|
const manifest = {
|
|
@@ -169,6 +170,7 @@ shaka.util.PlayerConfiguration = class {
|
|
|
169
170
|
updateIntervalSeconds: 1,
|
|
170
171
|
dispatchAllEmsgBoxes: false,
|
|
171
172
|
observeQualityChanges: false,
|
|
173
|
+
maxDisabledTime: 30,
|
|
172
174
|
};
|
|
173
175
|
|
|
174
176
|
// Some browsers will stop earlier than others before a gap (e.g., Edge
|
package/lib/util/stats.js
CHANGED
|
@@ -31,6 +31,11 @@ shaka.util.Stats = class {
|
|
|
31
31
|
/** @private {number} */
|
|
32
32
|
this.totalCorruptedFrames_ = NaN;
|
|
33
33
|
|
|
34
|
+
/** @private {number} */
|
|
35
|
+
this.totalStallsDetected_ = NaN;
|
|
36
|
+
/** @private {number} */
|
|
37
|
+
this.totalGapsJumped_ = NaN;
|
|
38
|
+
|
|
34
39
|
/** @private {number} */
|
|
35
40
|
this.completionPercent_ = NaN;
|
|
36
41
|
|
|
@@ -76,7 +81,6 @@ shaka.util.Stats = class {
|
|
|
76
81
|
this.totalDecodedFrames_ = decoded;
|
|
77
82
|
}
|
|
78
83
|
|
|
79
|
-
|
|
80
84
|
/**
|
|
81
85
|
* Update corrupted frames. This will replace the previous values.
|
|
82
86
|
*
|
|
@@ -86,6 +90,25 @@ shaka.util.Stats = class {
|
|
|
86
90
|
this.totalCorruptedFrames_ = corrupted;
|
|
87
91
|
}
|
|
88
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Update number of stalls detected. This will replace the previous value.
|
|
95
|
+
*
|
|
96
|
+
* @param {number} stallsDetected
|
|
97
|
+
*/
|
|
98
|
+
setStallsDetected(stallsDetected) {
|
|
99
|
+
this.totalStallsDetected_ = stallsDetected;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Update number of playback gaps jumped over. This will replace the previous
|
|
104
|
+
* value.
|
|
105
|
+
*
|
|
106
|
+
* @param {number} gapsJumped
|
|
107
|
+
*/
|
|
108
|
+
setGapsJumped(gapsJumped) {
|
|
109
|
+
this.totalGapsJumped_ = gapsJumped;
|
|
110
|
+
}
|
|
111
|
+
|
|
89
112
|
/**
|
|
90
113
|
* Set the width and height of the video we are currently playing.
|
|
91
114
|
*
|
|
@@ -208,6 +231,8 @@ shaka.util.Stats = class {
|
|
|
208
231
|
decodedFrames: this.totalDecodedFrames_,
|
|
209
232
|
droppedFrames: this.totalDroppedFrames_,
|
|
210
233
|
corruptedFrames: this.totalCorruptedFrames_,
|
|
234
|
+
stallsDetected: this.totalStallsDetected_,
|
|
235
|
+
gapsJumped: this.totalGapsJumped_,
|
|
211
236
|
estimatedBandwidth: this.bandwidthEstimate_,
|
|
212
237
|
completionPercent: this.completionPercent_,
|
|
213
238
|
loadLatency: this.loadLatencySeconds_,
|
|
@@ -238,6 +263,8 @@ shaka.util.Stats = class {
|
|
|
238
263
|
decodedFrames: NaN,
|
|
239
264
|
droppedFrames: NaN,
|
|
240
265
|
corruptedFrames: NaN,
|
|
266
|
+
stallsDetected: NaN,
|
|
267
|
+
gapsJumped: NaN,
|
|
241
268
|
estimatedBandwidth: NaN,
|
|
242
269
|
completionPercent: NaN,
|
|
243
270
|
loadLatency: NaN,
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -329,6 +329,13 @@ shaka.util.StreamUtils = class {
|
|
|
329
329
|
|
|
330
330
|
const video = variant.video;
|
|
331
331
|
|
|
332
|
+
if (variant.disabledUntilTime != 0) {
|
|
333
|
+
if (variant.disabledUntilTime > Date.now() / 1000) {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
variant.disabledUntilTime = 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
332
339
|
// |video.width| and |video.height| can be undefined, which breaks
|
|
333
340
|
// the math, so make sure they are there first.
|
|
334
341
|
if (video && video.width && video.height) {
|
|
@@ -1008,6 +1015,7 @@ shaka.util.StreamUtils = class {
|
|
|
1008
1015
|
track.channelsCount = audio.channelsCount;
|
|
1009
1016
|
track.audioSamplingRate = audio.audioSamplingRate;
|
|
1010
1017
|
track.audioBandwidth = audio.bandwidth || null;
|
|
1018
|
+
track.spatialAudio = audio.spatialAudio;
|
|
1011
1019
|
track.label = audio.label;
|
|
1012
1020
|
track.audioRoles = audio.roles;
|
|
1013
1021
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shaka-player",
|
|
3
3
|
"description": "DASH/EME video player library",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.1.1",
|
|
5
5
|
"homepage": "https://github.com/shaka-project/shaka-player",
|
|
6
6
|
"author": "Google",
|
|
7
7
|
"maintainers": [
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"prepublishOnly": "python build/checkversion.py && python build/all.py --force"
|
|
97
97
|
},
|
|
98
98
|
"dependencies": {
|
|
99
|
-
"eme-encryption-scheme-polyfill": "^2.0.
|
|
99
|
+
"eme-encryption-scheme-polyfill": "^2.0.5"
|
|
100
100
|
},
|
|
101
101
|
"engines": {
|
|
102
102
|
"node": ">=14"
|
|
@@ -11,6 +11,7 @@ describe('CastProxy', () => {
|
|
|
11
11
|
|
|
12
12
|
const originalCastSender = shaka.cast.CastSender;
|
|
13
13
|
const fakeAppId = 'fake app ID';
|
|
14
|
+
const fakeAndroidReceiverCompatible = false;
|
|
14
15
|
|
|
15
16
|
let mockPlayer;
|
|
16
17
|
let mockSender;
|
|
@@ -31,7 +32,8 @@ describe('CastProxy', () => {
|
|
|
31
32
|
mockPlayer = createMockPlayer();
|
|
32
33
|
mockSender = null;
|
|
33
34
|
|
|
34
|
-
proxy = new CastProxy(mockVideo, mockPlayer, fakeAppId
|
|
35
|
+
proxy = new CastProxy(mockVideo, mockPlayer, fakeAppId,
|
|
36
|
+
fakeAndroidReceiverCompatible);
|
|
35
37
|
});
|
|
36
38
|
|
|
37
39
|
afterEach(async () => {
|
|
@@ -13,6 +13,7 @@ describe('CastSender', () => {
|
|
|
13
13
|
const originalStatusDelay = shaka.cast.CastSender.STATUS_DELAY;
|
|
14
14
|
|
|
15
15
|
const fakeAppId = 'asdf';
|
|
16
|
+
const fakeAndroidReceiverCompatible = false;
|
|
16
17
|
const fakeInitState = {
|
|
17
18
|
manifest: null,
|
|
18
19
|
player: null,
|
|
@@ -52,7 +53,8 @@ describe('CastSender', () => {
|
|
|
52
53
|
sender = new CastSender(
|
|
53
54
|
fakeAppId, Util.spyFunc(onStatusChanged),
|
|
54
55
|
Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
|
|
55
|
-
Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired)
|
|
56
|
+
Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired),
|
|
57
|
+
fakeAndroidReceiverCompatible);
|
|
56
58
|
});
|
|
57
59
|
|
|
58
60
|
afterEach(async () => {
|
|
@@ -87,7 +89,12 @@ describe('CastSender', () => {
|
|
|
87
89
|
expect(sender.apiReady()).toBe(true);
|
|
88
90
|
expect(sender.hasReceivers()).toBe(false);
|
|
89
91
|
expect(onStatusChanged).toHaveBeenCalled();
|
|
90
|
-
expect(mockCastApi.SessionRequest).toHaveBeenCalledWith(
|
|
92
|
+
expect(mockCastApi.SessionRequest).toHaveBeenCalledWith(
|
|
93
|
+
fakeAppId,
|
|
94
|
+
/* capabilities= */ [],
|
|
95
|
+
/* timeout= */ null,
|
|
96
|
+
fakeAndroidReceiverCompatible,
|
|
97
|
+
/* credentialsData= */ null);
|
|
91
98
|
expect(mockCastApi.initialize).toHaveBeenCalled();
|
|
92
99
|
});
|
|
93
100
|
|
|
@@ -97,7 +104,12 @@ describe('CastSender', () => {
|
|
|
97
104
|
expect(sender.apiReady()).toBe(true);
|
|
98
105
|
expect(sender.hasReceivers()).toBe(false);
|
|
99
106
|
expect(onStatusChanged).toHaveBeenCalled();
|
|
100
|
-
expect(mockCastApi.SessionRequest).toHaveBeenCalledWith(
|
|
107
|
+
expect(mockCastApi.SessionRequest).toHaveBeenCalledWith(
|
|
108
|
+
fakeAppId,
|
|
109
|
+
/* capabilities= */ [],
|
|
110
|
+
/* timeout= */ null,
|
|
111
|
+
fakeAndroidReceiverCompatible,
|
|
112
|
+
/* credentialsData= */ null);
|
|
101
113
|
expect(mockCastApi.initialize).toHaveBeenCalled();
|
|
102
114
|
});
|
|
103
115
|
});
|
|
@@ -122,7 +134,8 @@ describe('CastSender', () => {
|
|
|
122
134
|
sender = new CastSender(
|
|
123
135
|
fakeAppId, Util.spyFunc(onStatusChanged),
|
|
124
136
|
Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
|
|
125
|
-
Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired)
|
|
137
|
+
Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired),
|
|
138
|
+
/* androidReceiverCompatible= */ false);
|
|
126
139
|
sender.init();
|
|
127
140
|
// You get an initial call to onStatusChanged when it initializes.
|
|
128
141
|
expect(onStatusChanged).toHaveBeenCalledTimes(3);
|
|
@@ -256,7 +269,8 @@ describe('CastSender', () => {
|
|
|
256
269
|
sender = new CastSender(
|
|
257
270
|
fakeAppId, Util.spyFunc(onStatusChanged),
|
|
258
271
|
Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
|
|
259
|
-
Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired)
|
|
272
|
+
Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired),
|
|
273
|
+
/* androidReceiverCompatible= */ false);
|
|
260
274
|
sender.init();
|
|
261
275
|
|
|
262
276
|
// The sender should automatically rejoin the session, without needing
|
|
@@ -288,7 +302,8 @@ describe('CastSender', () => {
|
|
|
288
302
|
sender = new CastSender(
|
|
289
303
|
fakeAppId, Util.spyFunc(onStatusChanged),
|
|
290
304
|
Util.spyFunc(onFirstCastStateUpdate), Util.spyFunc(onRemoteEvent),
|
|
291
|
-
Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired)
|
|
305
|
+
Util.spyFunc(onResumeLocal), Util.spyFunc(onInitStateRequired),
|
|
306
|
+
/* androidReceiverCompatible= */ false);
|
|
292
307
|
sender.init();
|
|
293
308
|
|
|
294
309
|
expect(sender.isCasting()).toBe(false);
|
package/test/demo/demo_unit.js
CHANGED
|
@@ -94,7 +94,8 @@ describe('Demo', () => {
|
|
|
94
94
|
.add('playRangeStart')
|
|
95
95
|
.add('playRangeEnd')
|
|
96
96
|
.add('manifest.dash.keySystemsByURI')
|
|
97
|
-
.add('manifest.hls.mediaPlaylistFullMimeType')
|
|
97
|
+
.add('manifest.hls.mediaPlaylistFullMimeType')
|
|
98
|
+
.add('drm.keySystemsMapping');
|
|
98
99
|
|
|
99
100
|
/**
|
|
100
101
|
* @param {!Object} section
|
|
@@ -2021,6 +2021,10 @@ describe('HlsParser', () => {
|
|
|
2021
2021
|
});
|
|
2022
2022
|
|
|
2023
2023
|
describe('produces syncTime', () => {
|
|
2024
|
+
// Corresponds to "2000-01-01T00:00:00.00Z".
|
|
2025
|
+
// All the PROGRAM-DATE-TIME values in the tests below are at or after this.
|
|
2026
|
+
const syncTimeBase = 946684800;
|
|
2027
|
+
|
|
2024
2028
|
/**
|
|
2025
2029
|
* @param {number} startTime
|
|
2026
2030
|
* @param {number} endTime
|
|
@@ -2039,9 +2043,10 @@ describe('HlsParser', () => {
|
|
|
2039
2043
|
/**
|
|
2040
2044
|
* @param {string} media
|
|
2041
2045
|
* @param {!Array.<number>} startTimes
|
|
2046
|
+
* @param {number} syncTimeOffset
|
|
2042
2047
|
* @param {(function(!shaka.media.SegmentReference))=} modifyFn
|
|
2043
2048
|
*/
|
|
2044
|
-
async function test(media, startTimes, modifyFn) {
|
|
2049
|
+
async function test(media, startTimes, syncTimeOffset, modifyFn) {
|
|
2045
2050
|
const master = [
|
|
2046
2051
|
'#EXTM3U\n',
|
|
2047
2052
|
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,vtt",',
|
|
@@ -2053,7 +2058,8 @@ describe('HlsParser', () => {
|
|
|
2053
2058
|
for (let i = 0; i < startTimes.length - 1; i++) {
|
|
2054
2059
|
const startTime = startTimes[i];
|
|
2055
2060
|
const endTime = startTimes[i + 1];
|
|
2056
|
-
const reference = makeReference(
|
|
2061
|
+
const reference = makeReference(
|
|
2062
|
+
startTime, endTime, syncTimeOffset + startTime);
|
|
2057
2063
|
if (modifyFn) {
|
|
2058
2064
|
modifyFn(reference);
|
|
2059
2065
|
}
|
|
@@ -2100,7 +2106,7 @@ describe('HlsParser', () => {
|
|
|
2100
2106
|
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
|
|
2101
2107
|
'#EXTINF:5,\n',
|
|
2102
2108
|
'main.mp4',
|
|
2103
|
-
].join(''), [0, 5, 10, 15, 20, 25]);
|
|
2109
|
+
].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5);
|
|
2104
2110
|
});
|
|
2105
2111
|
|
|
2106
2112
|
it('when some EXT-X-PROGRAM-DATE-TIME values are missing', async () => {
|
|
@@ -2122,7 +2128,7 @@ describe('HlsParser', () => {
|
|
|
2122
2128
|
'main.mp4\n',
|
|
2123
2129
|
'#EXTINF:4,\n',
|
|
2124
2130
|
'main.mp4',
|
|
2125
|
-
].join(''), [0, 2, 7, 12, 17, 19, 23]);
|
|
2131
|
+
].join(''), [0, 2, 7, 12, 17, 19, 23], syncTimeBase + 8);
|
|
2126
2132
|
});
|
|
2127
2133
|
|
|
2128
2134
|
it('except when ignoreManifestProgramDateTime is set', async () => {
|
|
@@ -2148,7 +2154,7 @@ describe('HlsParser', () => {
|
|
|
2148
2154
|
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
|
|
2149
2155
|
'#EXTINF:5,\n',
|
|
2150
2156
|
'main.mp4',
|
|
2151
|
-
].join(''), [0, 5, 10, 15, 20, 25], (reference) => {
|
|
2157
|
+
].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5, (reference) => {
|
|
2152
2158
|
reference.syncTime = null;
|
|
2153
2159
|
});
|
|
2154
2160
|
});
|
|
@@ -2176,11 +2182,11 @@ describe('HlsParser', () => {
|
|
|
2176
2182
|
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
|
|
2177
2183
|
'#EXTINF:5,\n',
|
|
2178
2184
|
'main.mp4',
|
|
2179
|
-
].join(''), [0, 5, 10, 15, 20, 25], (reference) => {
|
|
2185
|
+
].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5, (reference) => {
|
|
2180
2186
|
if (reference.startTime == 10) {
|
|
2181
2187
|
reference.partialReferences = [
|
|
2182
|
-
makeReference(10, 12.5,
|
|
2183
|
-
makeReference(12.5, 15,
|
|
2188
|
+
makeReference(10, 12.5, syncTimeBase + 15),
|
|
2189
|
+
makeReference(12.5, 15, syncTimeBase + 17.5),
|
|
2184
2190
|
];
|
|
2185
2191
|
}
|
|
2186
2192
|
});
|
|
@@ -3555,4 +3561,92 @@ describe('HlsParser', () => {
|
|
|
3555
3561
|
|
|
3556
3562
|
await testHlsParser(media, '', manifest);
|
|
3557
3563
|
});
|
|
3564
|
+
|
|
3565
|
+
it('syncs on sequence with ignoreManifestProgramDateTime', async () => {
|
|
3566
|
+
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
3567
|
+
config.hls.ignoreManifestProgramDateTime = true;
|
|
3568
|
+
parser.configure(config);
|
|
3569
|
+
|
|
3570
|
+
const master = [
|
|
3571
|
+
'#EXTM3U\n',
|
|
3572
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",URI="audio"\n',
|
|
3573
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
|
3574
|
+
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
|
3575
|
+
'video\n',
|
|
3576
|
+
].join('');
|
|
3577
|
+
|
|
3578
|
+
const video = [
|
|
3579
|
+
'#EXTM3U\n',
|
|
3580
|
+
'#EXT-X-TARGETDURATION:5\n',
|
|
3581
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
3582
|
+
'#EXT-X-MEDIA-SEQUENCE:1\n',
|
|
3583
|
+
'#EXTINF:5,\n',
|
|
3584
|
+
'video1.mp4\n',
|
|
3585
|
+
'#EXTINF:5,\n',
|
|
3586
|
+
'video2.mp4\n',
|
|
3587
|
+
'#EXTINF:5,\n',
|
|
3588
|
+
'video3.mp4\n',
|
|
3589
|
+
].join('');
|
|
3590
|
+
|
|
3591
|
+
const audio = [
|
|
3592
|
+
'#EXTM3U\n',
|
|
3593
|
+
'#EXT-X-TARGETDURATION:5\n',
|
|
3594
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
3595
|
+
'#EXT-X-MEDIA-SEQUENCE:3\n',
|
|
3596
|
+
'#EXTINF:5,\n',
|
|
3597
|
+
'audio3.mp4\n',
|
|
3598
|
+
'#EXTINF:5,\n',
|
|
3599
|
+
'audio4.mp4\n',
|
|
3600
|
+
].join('');
|
|
3601
|
+
|
|
3602
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
3603
|
+
manifest.sequenceMode = true;
|
|
3604
|
+
manifest.anyTimeline();
|
|
3605
|
+
manifest.addPartialVariant((variant) => {
|
|
3606
|
+
variant.language = 'en';
|
|
3607
|
+
variant.bandwidth = 200;
|
|
3608
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
3609
|
+
stream.mime('video/mp4', 'avc1');
|
|
3610
|
+
stream.size(960, 540);
|
|
3611
|
+
stream.frameRate = 60;
|
|
3612
|
+
});
|
|
3613
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
3614
|
+
stream.language = 'en';
|
|
3615
|
+
stream.mime('audio/mp4', 'mp4a');
|
|
3616
|
+
});
|
|
3617
|
+
});
|
|
3618
|
+
});
|
|
3619
|
+
|
|
3620
|
+
fakeNetEngine
|
|
3621
|
+
.setResponseText('test:/master', master)
|
|
3622
|
+
.setResponseText('test:/audio', audio)
|
|
3623
|
+
.setResponseText('test:/video', video)
|
|
3624
|
+
.setResponseValue('test:/init.mp4', initSegmentData);
|
|
3625
|
+
|
|
3626
|
+
const actualManifest = await parser.start('test:/master', playerInterface);
|
|
3627
|
+
expect(actualManifest).toEqual(manifest);
|
|
3628
|
+
|
|
3629
|
+
const actualVideo = actualManifest.variants[0].video;
|
|
3630
|
+
await actualVideo.createSegmentIndex();
|
|
3631
|
+
goog.asserts.assert(actualVideo.segmentIndex != null, 'Null segmentIndex!');
|
|
3632
|
+
|
|
3633
|
+
const actualAudio = actualManifest.variants[0].audio;
|
|
3634
|
+
await actualAudio.createSegmentIndex();
|
|
3635
|
+
goog.asserts.assert(actualAudio.segmentIndex != null, 'Null segmentIndex!');
|
|
3636
|
+
|
|
3637
|
+
// Verify that the references are aligned on sequence number.
|
|
3638
|
+
const videoSegments = Array.from(actualVideo.segmentIndex);
|
|
3639
|
+
const audioSegments = Array.from(actualAudio.segmentIndex);
|
|
3640
|
+
|
|
3641
|
+
// The first two were dropped to align with the audio.
|
|
3642
|
+
expect(videoSegments.map((ref) => ref.getUris()[0])).toEqual([
|
|
3643
|
+
'test:/video3.mp4',
|
|
3644
|
+
]);
|
|
3645
|
+
// Audio has a 4th segment that video doesn't, but that doesn't get clipped
|
|
3646
|
+
// or used as the base. Alignment is truly based on media sequence number.
|
|
3647
|
+
expect(audioSegments.map((ref) => ref.getUris()[0])).toEqual([
|
|
3648
|
+
'test:/audio3.mp4',
|
|
3649
|
+
'test:/audio4.mp4',
|
|
3650
|
+
]);
|
|
3651
|
+
});
|
|
3558
3652
|
});
|