UNPKG

13.2 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6
7function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
8
9var _config = require('./config');
10
11var _config2 = _interopRequireDefault(_config);
12
13var _playlist = require('./playlist');
14
15var _playlist2 = _interopRequireDefault(_playlist);
16
17// Utilities
18
19/**
20 * Returns the CSS value for the specified property on an element
21 * using `getComputedStyle`. Firefox has a long-standing issue where
22 * getComputedStyle() may return null when running in an iframe with
23 * `display: none`.
24 *
25 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
26 * @param {HTMLElement} el the htmlelement to work on
27 * @param {string} the proprety to get the style for
28 */
29var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
30 var result = undefined;
31
32 if (!el) {
33 return '';
34 }
35
36 result = window.getComputedStyle(el);
37 if (!result) {
38 return '';
39 }
40
41 return result[property];
42};
43
44/**
45 * Resuable stable sort function
46 *
47 * @param {Playlists} array
48 * @param {Function} sortFn Different comparators
49 * @function stableSort
50 */
51var stableSort = function stableSort(array, sortFn) {
52 var newArray = array.slice();
53
54 array.sort(function (left, right) {
55 var cmp = sortFn(left, right);
56
57 if (cmp === 0) {
58 return newArray.indexOf(left) - newArray.indexOf(right);
59 }
60 return cmp;
61 });
62};
63
64/**
65 * A comparator function to sort two playlist object by bandwidth.
66 *
67 * @param {Object} left a media playlist object
68 * @param {Object} right a media playlist object
69 * @return {Number} Greater than zero if the bandwidth attribute of
70 * left is greater than the corresponding attribute of right. Less
71 * than zero if the bandwidth of right is greater than left and
72 * exactly zero if the two are equal.
73 */
74var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) {
75 var leftBandwidth = undefined;
76 var rightBandwidth = undefined;
77
78 if (left.attributes && left.attributes.BANDWIDTH) {
79 leftBandwidth = left.attributes.BANDWIDTH;
80 }
81 leftBandwidth = leftBandwidth || window.Number.MAX_VALUE;
82 if (right.attributes && right.attributes.BANDWIDTH) {
83 rightBandwidth = right.attributes.BANDWIDTH;
84 }
85 rightBandwidth = rightBandwidth || window.Number.MAX_VALUE;
86
87 return leftBandwidth - rightBandwidth;
88};
89
90exports.comparePlaylistBandwidth = comparePlaylistBandwidth;
91/**
92 * A comparator function to sort two playlist object by resolution (width).
93 * @param {Object} left a media playlist object
94 * @param {Object} right a media playlist object
95 * @return {Number} Greater than zero if the resolution.width attribute of
96 * left is greater than the corresponding attribute of right. Less
97 * than zero if the resolution.width of right is greater than left and
98 * exactly zero if the two are equal.
99 */
100var comparePlaylistResolution = function comparePlaylistResolution(left, right) {
101 var leftWidth = undefined;
102 var rightWidth = undefined;
103
104 if (left.attributes && left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
105 leftWidth = left.attributes.RESOLUTION.width;
106 }
107
108 leftWidth = leftWidth || window.Number.MAX_VALUE;
109
110 if (right.attributes && right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
111 rightWidth = right.attributes.RESOLUTION.width;
112 }
113
114 rightWidth = rightWidth || window.Number.MAX_VALUE;
115
116 // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
117 // have the same media dimensions/ resolution
118 if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
119 return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
120 }
121 return leftWidth - rightWidth;
122};
123
124exports.comparePlaylistResolution = comparePlaylistResolution;
125/**
126 * Chooses the appropriate media playlist based on bandwidth and player size
127 *
128 * @param {Object} master
129 * Object representation of the master manifest
130 * @param {Number} playerBandwidth
131 * Current calculated bandwidth of the player
132 * @param {Number} playerWidth
133 * Current width of the player element
134 * @param {Number} playerHeight
135 * Current height of the player element
136 * @return {Playlist} the highest bitrate playlist less than the
137 * currently detected bandwidth, accounting for some amount of
138 * bandwidth variance
139 */
140var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight) {
141 // convert the playlists to an intermediary representation to make comparisons easier
142 var sortedPlaylistReps = master.playlists.map(function (playlist) {
143 var width = undefined;
144 var height = undefined;
145 var bandwidth = undefined;
146
147 if (playlist.attributes) {
148 width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width;
149 height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height;
150 bandwidth = playlist.attributes.BANDWIDTH;
151 }
152
153 bandwidth = bandwidth || window.Number.MAX_VALUE;
154
155 return {
156 bandwidth: bandwidth,
157 width: width,
158 height: height,
159 playlist: playlist
160 };
161 });
162
163 stableSort(sortedPlaylistReps, function (left, right) {
164 return left.bandwidth - right.bandwidth;
165 });
166
167 // filter out any playlists that have been excluded due to
168 // incompatible configurations or playback errors
169 sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) {
170 return _playlist2['default'].isEnabled(rep.playlist);
171 });
172
173 // filter out any variant that has greater effective bitrate
174 // than the current estimated bandwidth
175 var bandwidthPlaylistReps = sortedPlaylistReps.filter(function (rep) {
176 return rep.bandwidth * _config2['default'].BANDWIDTH_VARIANCE < playerBandwidth;
177 });
178
179 var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1];
180
181 // get all of the renditions with the same (highest) bandwidth
182 // and then taking the very first element
183 var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) {
184 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
185 })[0];
186
187 // filter out playlists without resolution information
188 var haveResolution = bandwidthPlaylistReps.filter(function (rep) {
189 return rep.width && rep.height;
190 });
191
192 // sort variants by resolution
193 stableSort(haveResolution, function (left, right) {
194 return left.width - right.width;
195 });
196
197 // if we have the exact resolution as the player use it
198 var resolutionBestRepList = haveResolution.filter(function (rep) {
199 return rep.width === playerWidth && rep.height === playerHeight;
200 });
201
202 highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1];
203 // ensure that we pick the highest bandwidth variant that have exact resolution
204 var resolutionBestRep = resolutionBestRepList.filter(function (rep) {
205 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
206 })[0];
207
208 var resolutionPlusOneList = undefined;
209 var resolutionPlusOneSmallest = undefined;
210 var resolutionPlusOneRep = undefined;
211
212 // find the smallest variant that is larger than the player
213 // if there is no match of exact resolution
214 if (!resolutionBestRep) {
215 resolutionPlusOneList = haveResolution.filter(function (rep) {
216 return rep.width > playerWidth || rep.height > playerHeight;
217 });
218
219 // find all the variants have the same smallest resolution
220 resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) {
221 return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height;
222 });
223
224 // ensure that we also pick the highest bandwidth variant that
225 // is just-larger-than the video player
226 highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1];
227 resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) {
228 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth;
229 })[0];
230 }
231
232 // fallback chain of variants
233 return (resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || sortedPlaylistReps[0]).playlist;
234};
235
236// Playlist Selectors
237
238/**
239 * Chooses the appropriate media playlist based on the most recent
240 * bandwidth estimate and the player size.
241 *
242 * Expects to be called within the context of an instance of HlsHandler
243 *
244 * @return {Playlist} the highest bitrate playlist less than the
245 * currently detected bandwidth, accounting for some amount of
246 * bandwidth variance
247 */
248var lastBandwidthSelector = function lastBandwidthSelector() {
249 return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10));
250};
251
252exports.lastBandwidthSelector = lastBandwidthSelector;
253/**
254 * Chooses the appropriate media playlist based on an
255 * exponential-weighted moving average of the bandwidth after
256 * filtering for player size.
257 *
258 * Expects to be called within the context of an instance of HlsHandler
259 *
260 * @param {Number} decay - a number between 0 and 1. Higher values of
261 * this parameter will cause previous bandwidth estimates to lose
262 * significance more quickly.
263 * @return {Function} a function which can be invoked to create a new
264 * playlist selector function.
265 * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
266 */
267var movingAverageBandwidthSelector = function movingAverageBandwidthSelector(decay) {
268 var average = -1;
269
270 if (decay < 0 || decay > 1) {
271 throw new Error('Moving average bandwidth decay must be between 0 and 1.');
272 }
273
274 return function () {
275 if (average < 0) {
276 average = this.systemBandwidth;
277 }
278
279 average = decay * this.systemBandwidth + (1 - decay) * average;
280 return simpleSelector(this.playlists.master, average, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10));
281 };
282};
283
284exports.movingAverageBandwidthSelector = movingAverageBandwidthSelector;
285/**
286 * Chooses the appropriate media playlist based on the potential to rebuffer
287 *
288 * @param {Object} settings
289 * Object of information required to use this selector
290 * @param {Object} settings.master
291 * Object representation of the master manifest
292 * @param {Number} settings.currentTime
293 * The current time of the player
294 * @param {Number} settings.bandwidth
295 * Current measured bandwidth
296 * @param {Number} settings.duration
297 * Duration of the media
298 * @param {Number} settings.segmentDuration
299 * Segment duration to be used in round trip time calculations
300 * @param {Number} settings.timeUntilRebuffer
301 * Time left in seconds until the player has to rebuffer
302 * @param {Number} settings.currentTimeline
303 * The current timeline segments are being loaded from
304 * @param {SyncController} settings.syncController
305 * SyncController for determining if we have a sync point for a given playlist
306 * @return {Object|null}
307 * {Object} return.playlist
308 * The highest bandwidth playlist with the least amount of rebuffering
309 * {Number} return.rebufferingImpact
310 * The amount of time in seconds switching to this playlist will rebuffer. A
311 * negative value means that switching will cause zero rebuffering.
312 */
313var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) {
314 var master = settings.master;
315 var currentTime = settings.currentTime;
316 var bandwidth = settings.bandwidth;
317 var duration = settings.duration;
318 var segmentDuration = settings.segmentDuration;
319 var timeUntilRebuffer = settings.timeUntilRebuffer;
320 var currentTimeline = settings.currentTimeline;
321 var syncController = settings.syncController;
322
323 var bandwidthPlaylists = master.playlists.filter(_playlist2['default'].hasAttribute.bind(null, 'BANDWIDTH'));
324
325 var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) {
326 var syncPoint = syncController.getSyncPoint(playlist, duration, currentTimeline, currentTime);
327 // If there is no sync point for this playlist, switching to it will require a
328 // sync request first. This will double the request time
329 var numRequests = syncPoint ? 1 : 2;
330 var requestTimeEstimate = _playlist2['default'].estimateSegmentRequestTime(segmentDuration, bandwidth, playlist);
331 var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer;
332
333 return {
334 playlist: playlist,
335 rebufferingImpact: rebufferingImpact
336 };
337 });
338
339 var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) {
340 return estimate.rebufferingImpact <= 0;
341 });
342
343 // Sort by bandwidth DESC
344 stableSort(noRebufferingPlaylists, function (a, b) {
345 return comparePlaylistBandwidth(b.playlist, a.playlist);
346 });
347
348 if (noRebufferingPlaylists.length) {
349 return noRebufferingPlaylists[0];
350 }
351
352 stableSort(rebufferingEstimates, function (a, b) {
353 return a.rebufferingImpact - b.rebufferingImpact;
354 });
355
356 return rebufferingEstimates[0] || null;
357};
358exports.minRebufferMaxBandwidthSelector = minRebufferMaxBandwidthSelector;
\No newline at end of file