-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathmediasource.js
280 lines (250 loc) · 10.7 KB
/
mediasource.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.polyfill.MediaSource');
goog.require('shaka.log');
goog.require('shaka.polyfill');
goog.require('shaka.util.MimeUtils');
goog.require('shaka.util.Platform');
/**
* @summary A polyfill to patch MSE bugs.
* @export
*/
shaka.polyfill.MediaSource = class {
/**
* Install the polyfill if needed.
* @export
*/
static install() {
shaka.log.debug('MediaSource.install');
// MediaSource bugs are difficult to detect without checking for the
// affected platform. SourceBuffer is not always exposed on window, for
// example, and instances are only accessible after setting up MediaSource
// on a video element. Because of this, we use UA detection and other
// platform detection tricks to decide which patches to install.
const safariVersion = shaka.util.Platform.safariVersion();
if (!window.MediaSource && !window.ManagedMediaSource) {
shaka.log.info('No MSE implementation available.');
} else if (safariVersion && window.MediaSource) {
// NOTE: shaka.Player.isBrowserSupported() has its own restrictions on
// Safari version.
if (safariVersion <= 10) {
// Safari 8 does not implement appendWindowEnd.
// Safari 9 & 10 do not correctly implement abort() on SourceBuffer.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=160316
// Blacklist these very outdated versions.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
// Safari 10 fires spurious 'updateend' events after endOfStream().
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165336
shaka.log.info('Blacklisting MSE on Safari <= 10.');
shaka.polyfill.MediaSource.blacklist_();
} else if (safariVersion <= 12) {
shaka.log.info('Patching Safari 11 & 12 MSE bugs.');
// Safari 11 & 12 do not correctly implement abort() on SourceBuffer.
// Calling abort() before appending a segment causes that segment to be
// incomplete in the buffer.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
shaka.polyfill.MediaSource.stubAbort_();
// If you remove up to a keyframe, Safari 11 & 12 incorrectly will also
// remove that keyframe and the content up to the next.
// Offsetting the end of the removal range seems to help.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
shaka.polyfill.MediaSource.patchRemovalRange_();
} else if (safariVersion <= 15) {
shaka.log.info('Patching Safari 13 & 14 & 15 MSE bugs.');
// Safari 13 does not correctly implement abort() on SourceBuffer.
// Calling abort() before appending a segment causes that segment to be
// incomplete in the buffer.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
shaka.polyfill.MediaSource.stubAbort_();
}
} else if (shaka.util.Platform.isZenterio()) {
// Zenterio uses WPE based on Webkit 607.x.x which do not correctly
// implement abort() on SourceBuffer.
// Calling abort() before appending a segment causes that segment to be
// incomplete in the buffer.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
shaka.polyfill.MediaSource.stubAbort_();
// If you remove up to a keyframe, Webkit 607.x.x incorrectly will also
// remove that keyframe and the content up to the next.
// Offsetting the end of the removal range seems to help.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
shaka.polyfill.MediaSource.patchRemovalRange_();
} else if (shaka.util.Platform.isTizen2() ||
shaka.util.Platform.isTizen3() ||
shaka.util.Platform.isTizen4()) {
shaka.log.info('Rejecting Opus.');
// Tizen's implementation of MSE does not work well with opus. To prevent
// the player from trying to play opus on Tizen, we will override media
// source to always reject opus content.
shaka.polyfill.MediaSource.rejectCodec_('opus');
} else if (shaka.util.Platform.isComcastX1()) {
// XOne look to be like safari 8 which do not correctly
// implement abort() on SourceBuffer.
// Calling abort() before appending a segment causes that segment to be
// incomplete in the buffer.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
shaka.polyfill.MediaSource.stubAbort_();
// If you remove up to a keyframe, Webkit 601.x.x incorrectly will also
// remove that keyframe and the content up to the next.
// Offsetting the end of the removal range seems to help.
// Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
shaka.polyfill.MediaSource.patchRemovalRange_();
} else {
shaka.log.info('Using native MSE as-is.');
}
if (window.MediaSource || window.ManagedMediaSource) {
// TS content is broken on all browsers in general.
// See https://github.com/shaka-project/shaka-player/issues/4955
// See https://github.com/shaka-project/shaka-player/issues/5278
// See https://github.com/shaka-project/shaka-player/issues/6334
shaka.polyfill.MediaSource.rejectContainer_('mp2t');
}
if (window.MediaSource &&
MediaSource.isTypeSupported('video/webm; codecs="vp9"') &&
!MediaSource.isTypeSupported('video/webm; codecs="vp09.00.10.08"')) {
shaka.log.info('Patching vp09 support queries.');
// Only the old, deprecated style of VP9 codec strings is supported.
// This occurs on older smart TVs.
// Patch isTypeSupported to translate the new strings into the old one.
shaka.polyfill.MediaSource.patchVp09_();
}
}
/**
* Blacklist the current browser by making
* MediaSourceEngine.isBrowserSupported fail later.
*
* @private
*/
static blacklist_() {
window['MediaSource'] = null;
}
/**
* Stub out abort(). On some buggy MSE implementations, calling abort()
* causes various problems.
*
* @private
*/
static stubAbort_() {
/* eslint-disable no-restricted-syntax */
const addSourceBuffer = MediaSource.prototype.addSourceBuffer;
MediaSource.prototype.addSourceBuffer = function(...varArgs) {
const sourceBuffer = addSourceBuffer.apply(this, varArgs);
sourceBuffer.abort = function() {}; // Stub out for buggy implementations.
return sourceBuffer;
};
/* eslint-enable no-restricted-syntax */
}
/**
* Patch remove(). On Safari 11, if you call remove() to remove the content
* up to a keyframe, Safari will also remove the keyframe and all of the data
* up to the next one. For example, if the keyframes are at 0s, 5s, and 10s,
* and you tried to remove 0s-5s, it would instead remove 0s-10s.
*
* Offsetting the end of the range seems to be a usable workaround.
*
* @private
*/
static patchRemovalRange_() {
// eslint-disable-next-line no-restricted-syntax
const originalRemove = SourceBuffer.prototype.remove;
// eslint-disable-next-line no-restricted-syntax
SourceBuffer.prototype.remove = function(startTime, endTime) {
// eslint-disable-next-line no-restricted-syntax
return originalRemove.call(this, startTime, endTime - 0.001);
};
}
/**
* Patch |MediaSource.isTypeSupported| to always reject |container|. This is
* used when we know that we are on a platform that does not work well with
* a given container.
*
* @param {string} container
* @private
*/
static rejectContainer_(container) {
if (window.MediaSource) {
const isTypeSupported =
// eslint-disable-next-line no-restricted-syntax
MediaSource.isTypeSupported.bind(MediaSource);
MediaSource.isTypeSupported = (mimeType) => {
const actualContainer = shaka.util.MimeUtils.getContainerType(mimeType);
return actualContainer != container && isTypeSupported(mimeType);
};
}
if (window.ManagedMediaSource) {
const isTypeSupportedManaged =
// eslint-disable-next-line no-restricted-syntax
ManagedMediaSource.isTypeSupported.bind(ManagedMediaSource);
window.ManagedMediaSource.isTypeSupported = (mimeType) => {
const actualContainer = shaka.util.MimeUtils.getContainerType(mimeType);
return actualContainer != container && isTypeSupportedManaged(mimeType);
};
}
}
/**
* Patch |MediaSource.isTypeSupported| to always reject |codec|. This is used
* when we know that we are on a platform that does not work well with a given
* codec.
*
* @param {string} codec
* @private
*/
static rejectCodec_(codec) {
const isTypeSupported =
// eslint-disable-next-line no-restricted-syntax
MediaSource.isTypeSupported.bind(MediaSource);
MediaSource.isTypeSupported = (mimeType) => {
const actualCodec = shaka.util.MimeUtils.getCodecBase(mimeType);
return actualCodec != codec && isTypeSupported(mimeType);
};
if (window.ManagedMediaSource) {
const isTypeSupportedManaged =
// eslint-disable-next-line no-restricted-syntax
ManagedMediaSource.isTypeSupported.bind(ManagedMediaSource);
window.ManagedMediaSource.isTypeSupported = (mimeType) => {
const actualCodec = shaka.util.MimeUtils.getCodecBase(mimeType);
return actualCodec != codec && isTypeSupportedManaged(mimeType);
};
}
}
/**
* Patch isTypeSupported() to translate vp09 codec strings into vp9, to allow
* such content to play on older smart TVs.
*
* @private
*/
static patchVp09_() {
const originalIsTypeSupported = MediaSource.isTypeSupported;
if (shaka.util.Platform.isWebOS()) {
// Don't do this on LG webOS as otherwise it is unable
// to play vp09 at all.
return;
}
MediaSource.isTypeSupported = (mimeType) => {
// Split the MIME type into its various parameters.
const pieces = mimeType.split(/ *; */);
const codecsIndex =
pieces.findIndex((piece) => piece.startsWith('codecs='));
if (codecsIndex < 0) {
// No codec? Call the original without modifying the MIME type.
return originalIsTypeSupported(mimeType);
}
const codecsParam = pieces[codecsIndex];
const codecs = codecsParam
.replace('codecs=', '').replace(/"/g, '').split(/\s*,\s*/);
const vp09Index = codecs.findIndex(
(codecName) => codecName.startsWith('vp09'));
if (vp09Index >= 0) {
// vp09? Replace it with vp9.
codecs[vp09Index] = 'vp9';
pieces[codecsIndex] = 'codecs="' + codecs.join(',') + '"';
mimeType = pieces.join('; ');
}
return originalIsTypeSupported(mimeType);
};
}
};
shaka.polyfill.register(shaka.polyfill.MediaSource.install);