99580d5c by brandonocasey

browserify-p5: videojs-contrib-hls and bin-utils conversion

removed stub test and stub src files
updated build scripts to continue working
fixed several linting issues that were previously unnoticed
turn the linter back on
remove init function from stream
added ignore for playlist-loader as it will be finished later
1 parent c694b4b7
......@@ -46,10 +46,7 @@
</ul>
<script src="/node_modules/video.js/dist/video.js"></script>
<script src="/node_modules/videojs-contrib-media-sources/dist/videojs-media-sources.js"></script>
<script src="/src/videojs-contrib-hls.js"></script>
<script src="/dist/videojs-contrib-hls.js"></script>
<script src="/src/bin-utils.js"></script>
<script>
(function(window, videojs) {
var player = window.player = videojs('videojs-contrib-hls-player');
......
......@@ -2,7 +2,7 @@
"name": "videojs-contrib-hls",
"version": "1.3.5",
"description": "Play back HLS with video.js, even where it's not natively supported",
"main": "es5/stub.js",
"main": "es5/videojs-contrib-hls.js",
"engines": {
"node": ">= 0.10.12"
},
......@@ -27,7 +27,7 @@
"docs": "npm-run-all docs:*",
"docs:api": "jsdoc src -r -d docs/api",
"docs:toc": "doctoc README.md",
"lint": "vjsstandard :",
"lint": "vjsstandard",
"prestart": "npm-run-all docs build",
"start": "npm-run-all -p start:* watch:*",
"start:serve": "babel-node scripts/server.js",
......@@ -40,7 +40,7 @@
"preversion": "npm test",
"version": "npm run build",
"watch": "npm-run-all -p watch:*",
"watch:js": "watchify src/stub.js -t babelify -v -o dist/videojs-contrib-hls.js",
"watch:js": "watchify src/videojs-contrib-hls.js -t babelify -v -o dist/videojs-contrib-hls.js",
"watch:test": "npm-run-all -p watch:test:*",
"watch:test:js": "node scripts/watch-test.js",
"watch:test:manifest": "node -e \"var b=require('./scripts/manifest-data.js'); b.watch();\"",
......@@ -72,7 +72,8 @@
"scripts",
"utils",
"test/test-manifests.js",
"test/test-expected.js"
"test/test-expected.js",
"src/playlist-loader.js"
]
},
"files": [
......
......@@ -2,7 +2,7 @@ var browserify = require('browserify');
var fs = require('fs');
var glob = require('glob');
glob('test/{playlist*,decryper,m3u8,stub}.test.js', function(err, files) {
glob('test/**/*.test.js', function(err, files) {
browserify(files)
.transform('babelify')
.bundle()
......
......@@ -3,7 +3,7 @@ var fs = require('fs');
var glob = require('glob');
var watchify = require('watchify');
glob('test/{playlist*,decrypter,m3u8,stub}.test.js', function(err, files) {
glob('test/**/*.test.js', function(err, files) {
var b = browserify(files, {
cache: {},
packageCache: {},
......
(function(window) {
var textRange = function(range, i) {
const textRange = function(range, i) {
return range.start(i) + '-' + range.end(i);
};
var module = {
hexDump: function(data) {
var
bytes = Array.prototype.slice.call(data),
step = 16,
formatHexString = function(e, i) {
var value = e.toString(16);
return "00".substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
},
formatAsciiString = function(e) {
};
const formatHexString = function(e, i) {
let value = e.toString(16);
return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : '');
};
const formatAsciiString = function(e) {
if (e >= 0x20 && e < 0x7e) {
return String.fromCharCode(e);
}
return '.';
},
result = '',
hex,
ascii;
for (var j = 0; j < bytes.length / step; j++) {
};
const utils = {
hexDump(data) {
let bytes = Array.prototype.slice.call(data);
let step = 16;
let result = '';
let hex;
let ascii;
for (let j = 0; j < bytes.length / step; j++) {
hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
result += hex + ' ' + ascii + '\n';
}
return result;
},
tagDump: function(tag) {
return module.hexDump(tag.bytes);
tagDump(tag) {
return utils.hexDump(tag.bytes);
},
textRanges: function(ranges) {
var result = '', i;
textRanges(ranges) {
let result = '';
let i;
for (i = 0; i < ranges.length; i++) {
result += textRange(ranges, i) + ' ';
}
return result;
}
};
};
window.videojs.Hls.utils = module;
})(this);
export default utils;
......
......@@ -89,10 +89,9 @@ const precompute = function() {
decTable[i] = decTable[i].slice(0);
}
return tables;
}
};
let aesTables = null;
/**
* Schedule out an AES key for both encryption and decryption. This
* is a low-level class. Use a cipher mode to do bulk encryption.
......@@ -116,7 +115,7 @@ export default class AES {
*/
// if we have yet to precompute the S-box tables
// do so now
if(!aesTables) {
if (!aesTables) {
aesTables = precompute();
}
// then make a copy of that object for use
......
/**
* A lightweight readable stream implemention that handles event dispatching.
* Objects that inherit from streams should call init in their constructors.
*/
export default class Stream {
constructor() {
this.init();
}
init() {
this.listeners = {};
}
......
import m3u8 from './m3u8';
import Stream from './stream';
import videojs from 'video.js';
import {Decrypter, decrypt, AsyncStream} from './decrypter';
import Playlist from './playlist';
import PlaylistLoader from './playlist-loader';
import xhr from './xhr';
if(typeof window.videojs.Hls === 'undefined') {
videojs.Hls = {};
}
videojs.Hls.Stream = Stream;
videojs.m3u8 = m3u8;
videojs.Hls.decrypt = decrypt;
videojs.Hls.Decrypter = Decrypter;
videojs.Hls.AsyncStream = AsyncStream;
videojs.Hls.xhr = xhr;
videojs.Hls.Playlist = Playlist;
videojs.Hls.PlaylistLoader = PlaylistLoader;
/*
/**
* videojs-hls
* The main file for the HLS project.
* License: https://github.com/videojs/videojs-contrib-hls/blob/master/LICENSE
*/
(function(window, videojs, document, undefined) {
'use strict';
import PlaylistLoader from './playlist-loader';
import Playlist from './playlist';
import xhr from './xhr';
import {Decrypter, AsyncStream, decrypt} from './decrypter';
import utils from './bin-utils';
import {MediaSource, URL} from 'videojs-contrib-media-sources';
import m3u8 from './m3u8';
import videojs from 'video.js';
import resolveUrl from './resolve-url';
const Hls = {
PlaylistLoader,
Playlist,
Decrypter,
AsyncStream,
decrypt,
utils,
xhr
};
// the desired length of video to maintain in the buffer, in seconds
Hls.GOAL_BUFFER_LENGTH = 30;
// HLS is a source handler, not a tech. Make sure attempts to use it
// as one do not cause exceptions.
Hls.canPlaySource = function() {
return videojs.log.warn('HLS is no longer a tech. Please remove it from ' +
'your player\'s techOrder.');
};
// Search for a likely end time for the segment that was just appened
// based on the state of the `buffered` property before and after the
// append.
// If we found only one such uncommon end-point return it.
Hls.findSoleUncommonTimeRangesEnd_ = function(original, update) {
let i;
let start;
let end;
let result = [];
let edges = [];
// In order to qualify as a possible candidate, the end point must:
// 1) Not have already existed in the `original` ranges
// 2) Not result from the shrinking of a range that already existed
// in the `original` ranges
// 3) Not be contained inside of a range that existed in `original`
let overlapsCurrentEnd = function(span) {
return (span[0] <= end && span[1] >= end);
};
if (original) {
// Save all the edges in the `original` TimeRanges object
for (i = 0; i < original.length; i++) {
start = original.start(i);
end = original.end(i);
edges.push([start, end]);
}
}
if (update) {
// Save any end-points in `update` that are not in the `original`
// TimeRanges object
for (i = 0; i < update.length; i++) {
start = update.start(i);
end = update.end(i);
if (edges.some(overlapsCurrentEnd)) {
continue;
}
// at this point it must be a unique non-shrinking end edge
result.push(end);
}
}
// we err on the side of caution and return null if didn't find
// exactly *one* differing end edge in the search above
if (result.length !== 1) {
return null;
}
var
// A fudge factor to apply to advertised playlist bitrates to account for
// temporary flucations in client bandwidth
bandwidthVariance = 1.2,
blacklistDuration = 5 * 60 * 1000, // 5 minute blacklist
TIME_FUDGE_FACTOR = 1 / 30, // Fudge factor to account for TimeRanges rounding
Component = videojs.getComponent('Component'),
return result[0];
};
// The amount of time to wait between checking the state of the buffer
bufferCheckInterval = 500,
/**
* Whether the browser has built-in HLS support.
*/
Hls.supportsNativeHls = function() {
let video = document.createElement('video');
let xMpegUrl;
let vndMpeg;
// native HLS is definitely not supported if HTML5 video isn't
if (!videojs.getComponent('Html5').isSupported()) {
return false;
}
keyFailed,
resolveUrl;
xMpegUrl = video.canPlayType('application/x-mpegURL');
vndMpeg = video.canPlayType('application/vnd.apple.mpegURL');
return (/probably|maybe/).test(xMpegUrl) ||
(/probably|maybe/).test(vndMpeg);
};
// HLS is a source handler, not a tech. Make sure attempts to use it
// as one do not cause exceptions.
Hls.isSupported = function() {
return videojs.log.warn('HLS is no longer a tech. Please remove it from ' +
'your player\'s techOrder.');
};
/**
* A comparator function to sort two playlist object by bandwidth.
* @param left {object} a media playlist object
* @param right {object} a media playlist object
* @return {number} Greater than zero if the bandwidth attribute of
* left is greater than the corresponding attribute of right. Less
* than zero if the bandwidth of right is greater than left and
* exactly zero if the two are equal.
*/
Hls.comparePlaylistBandwidth = function(left, right) {
let leftBandwidth;
let rightBandwidth;
if (left.attributes && left.attributes.BANDWIDTH) {
leftBandwidth = left.attributes.BANDWIDTH;
}
leftBandwidth = leftBandwidth || window.Number.MAX_VALUE;
if (right.attributes && right.attributes.BANDWIDTH) {
rightBandwidth = right.attributes.BANDWIDTH;
}
rightBandwidth = rightBandwidth || window.Number.MAX_VALUE;
return leftBandwidth - rightBandwidth;
};
/**
* A comparator function to sort two playlist object by resolution (width).
* @param left {object} a media playlist object
* @param right {object} a media playlist object
* @return {number} Greater than zero if the resolution.width attribute of
* left is greater than the corresponding attribute of right. Less
* than zero if the resolution.width of right is greater than left and
* exactly zero if the two are equal.
*/
Hls.comparePlaylistResolution = function(left, right) {
let leftWidth;
let rightWidth;
if (left.attributes &&
left.attributes.RESOLUTION &&
left.attributes.RESOLUTION.width) {
leftWidth = left.attributes.RESOLUTION.width;
}
leftWidth = leftWidth || window.Number.MAX_VALUE;
if (right.attributes &&
right.attributes.RESOLUTION &&
right.attributes.RESOLUTION.width) {
rightWidth = right.attributes.RESOLUTION.width;
}
rightWidth = rightWidth || window.Number.MAX_VALUE;
// NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
// have the same media dimensions/ resolution
if (leftWidth === rightWidth &&
left.attributes.BANDWIDTH &&
right.attributes.BANDWIDTH) {
return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
}
return leftWidth - rightWidth;
};
// A fudge factor to apply to advertised playlist bitrates to account for
// temporary flucations in client bandwidth
const bandwidthVariance = 1.2;
// 5 minute blacklist
const blacklistDuration = 5 * 60 * 1000;
// Fudge factor to account for TimeRanges rounding
const TIME_FUDGE_FACTOR = 1 / 30;
const Component = videojs.getComponent('Component');
// The amount of time to wait between checking the state of the buffer
const bufferCheckInterval = 500;
// returns true if a key has failed to download within a certain amount of retries
keyFailed = function(key) {
const keyFailed = function(key) {
return key.retries && key.retries >= 2;
};
videojs.Hls = {};
videojs.HlsHandler = videojs.extend(Component, {
constructor: function(tech, options) {
var self = this, _player;
const parseCodecs = function(codecs) {
let result = {
codecCount: 0,
videoCodec: null,
audioProfile: null
};
result.codecCount = codecs.split(',').length;
result.codecCount = result.codecCount || 2;
// parse the video codec but ignore the version
result.videoCodec = (/(^|\s|,)+(avc1)[^ ,]*/i).exec(codecs);
result.videoCodec = result.videoCodec && result.videoCodec[2];
// parse the last field of the audio codec
result.audioProfile = (/(^|\s|,)+mp4a.\d+\.(\d+)/i).exec(codecs);
result.audioProfile = result.audioProfile && result.audioProfile[2];
return result;
};
const filterBufferedRanges = function(predicate) {
return function(time) {
let i;
let ranges = [];
let tech = this.tech_;
// !!The order of the next two assignments is important!!
// `currentTime` must be equal-to or greater-than the start of the
// buffered range. Flash executes out-of-process so, every value can
// change behind the scenes from line-to-line. By reading `currentTime`
// after `buffered`, we ensure that it is always a current or later
// value during playback.
let buffered = tech.buffered();
if (typeof time === 'undefined') {
time = tech.currentTime();
}
if (buffered && buffered.length) {
// Search for a range containing the play-head
for (i = 0; i < buffered.length; i++) {
if (predicate(buffered.start(i), buffered.end(i), time)) {
ranges.push([buffered.start(i), buffered.end(i)]);
}
}
}
return videojs.createTimeRanges(ranges);
};
};
Component.call(this, tech);
export default class HlsHandler extends Component {
constructor(tech, options) {
super(tech);
let _player;
// tech.player() is deprecated but setup a reference to HLS for
// backwards-compatibility
......@@ -38,9 +259,9 @@ videojs.HlsHandler = videojs.extend(Component, {
_player = videojs(tech.options_.playerId);
if (!_player.hls) {
Object.defineProperty(_player, 'hls', {
get: function() {
get: () => {
videojs.log.warn('player.hls is deprecated. Use player.tech.hls instead.');
return self;
return this;
}
});
}
......@@ -54,7 +275,8 @@ videojs.HlsHandler = videojs.extend(Component, {
// start playlist selection at a reasonable bandwidth for
// broadband internet
this.bandwidth = options.bandwidth || 4194304; // 0.5 Mbps
// 0.5 Mbps
this.bandwidth = options.bandwidth || 4194304;
this.bytesReceived = 0;
// loadingState_ tracks how far along the buffering process we
......@@ -81,71 +303,8 @@ videojs.HlsHandler = videojs.extend(Component, {
this.on(this.tech_, 'play', this.play);
}
});
// HLS is a source handler, not a tech. Make sure attempts to use it
// as one do not cause exceptions.
videojs.Hls.canPlaySource = function() {
return videojs.log.warn('HLS is no longer a tech. Please remove it from ' +
'your player\'s techOrder.');
};
/**
* The Source Handler object, which informs video.js what additional
* MIME types are supported and sets up playback. It is registered
* automatically to the appropriate tech based on the capabilities of
* the browser it is running in. It is not necessary to use or modify
* this object in normal usage.
*/
videojs.HlsSourceHandler = function(mode) {
return {
canHandleSource: function(srcObj) {
return videojs.HlsSourceHandler.canPlayType(srcObj.type);
},
handleSource: function(source, tech) {
if (mode === 'flash') {
// We need to trigger this asynchronously to give others the chance
// to bind to the event when a source is set at player creation
tech.setTimeout(function() {
tech.trigger('loadstart');
}, 1);
}
tech.hls = new videojs.HlsHandler(tech, {
source: source,
mode: mode
});
tech.hls.src(source.src);
return tech.hls;
},
canPlayType: function(type) {
return videojs.HlsSourceHandler.canPlayType(type);
}
};
};
videojs.HlsSourceHandler.canPlayType = function(type) {
var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
// favor native HLS support if it's available
if (videojs.Hls.supportsNativeHls) {
return false;
}
return mpegurlRE.test(type);
};
// register source handlers with the appropriate techs
if (videojs.MediaSource.supportsNativeMediaSources()) {
videojs.getComponent('Html5').registerSourceHandler(videojs.HlsSourceHandler('html5'));
}
if (window.Uint8Array) {
videojs.getComponent('Flash').registerSourceHandler(videojs.HlsSourceHandler('flash'));
}
// the desired length of video to maintain in the buffer, in seconds
videojs.Hls.GOAL_BUFFER_LENGTH = 30;
videojs.HlsHandler.prototype.src = function(src) {
var oldMediaPlaylist;
src(src) {
let oldMediaPlaylist;
// do nothing if the src is falsey
if (!src) {
......@@ -158,16 +317,17 @@ videojs.HlsHandler.prototype.src = function(src) {
this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen.bind(this));
this.options_ = {};
if (this.source_.withCredentials !== undefined) {
if (typeof this.source_.withCredentials !== 'undefined') {
this.options_.withCredentials = this.source_.withCredentials;
} else if (videojs.options.hls) {
this.options_.withCredentials = videojs.options.hls.withCredentials;
}
this.playlists = new videojs.Hls.PlaylistLoader(this.source_.src, this.options_.withCredentials);
this.playlists = new Hls.PlaylistLoader(this.source_.src,
this.options_.withCredentials);
this.tech_.one('canplay', this.setupFirstPlay.bind(this));
this.playlists.on('loadedmetadata', function() {
this.playlists.on('loadedmetadata', () => {
oldMediaPlaylist = this.playlists.media();
// if this isn't a live video and preload permits, start
......@@ -182,14 +342,15 @@ videojs.HlsHandler.prototype.src = function(src) {
this.setupFirstPlay();
this.fillBuffer();
this.tech_.trigger('loadedmetadata');
}.bind(this));
});
this.playlists.on('error', function() {
this.playlists.on('error', () => {
this.blacklistCurrentPlaylist_(this.playlists.error);
}.bind(this));
});
this.playlists.on('loadedplaylist', function() {
var updatedPlaylist = this.playlists.media(), seekable;
this.playlists.on('loadedplaylist', () => {
let updatedPlaylist = this.playlists.media();
let seekable;
if (!updatedPlaylist) {
// select the initial variant
......@@ -207,14 +368,14 @@ videojs.HlsHandler.prototype.src = function(src) {
}
oldMediaPlaylist = updatedPlaylist;
}.bind(this));
});
this.playlists.on('mediachange', function() {
this.playlists.on('mediachange', () => {
this.tech_.trigger({
type: 'mediachange',
bubbles: true
});
}.bind(this));
});
// do nothing if the tech has been disposed already
// this can occur if someone sets the src in player.ready(), for instance
......@@ -223,9 +384,8 @@ videojs.HlsHandler.prototype.src = function(src) {
}
this.tech_.src(videojs.URL.createObjectURL(this.mediaSource));
};
videojs.HlsHandler.prototype.handleSourceOpen = function() {
}
handleSourceOpen() {
// Only attempt to create the source buffer if none already exist.
// handleSourceOpen is also called when we are "re-opening" a source buffer
// after `endOfStream` has been called (in response to a seek for instance)
......@@ -242,83 +402,9 @@ videojs.HlsHandler.prototype.handleSourceOpen = function() {
if (this.tech_.autoplay()) {
this.play();
}
};
// Search for a likely end time for the segment that was just appened
// based on the state of the `buffered` property before and after the
// append.
// If we found only one such uncommon end-point return it.
videojs.Hls.findSoleUncommonTimeRangesEnd_ = function(original, update) {
var
i, start, end,
result = [],
edges = [],
// In order to qualify as a possible candidate, the end point must:
// 1) Not have already existed in the `original` ranges
// 2) Not result from the shrinking of a range that already existed
// in the `original` ranges
// 3) Not be contained inside of a range that existed in `original`
overlapsCurrentEnd = function(span) {
return (span[0] <= end && span[1] >= end);
};
if (original) {
// Save all the edges in the `original` TimeRanges object
for (i = 0; i < original.length; i++) {
start = original.start(i);
end = original.end(i);
edges.push([start, end]);
}
}
if (update) {
// Save any end-points in `update` that are not in the `original`
// TimeRanges object
for (i = 0; i < update.length; i++) {
start = update.start(i);
end = update.end(i);
if (edges.some(overlapsCurrentEnd)) {
continue;
}
// at this point it must be a unique non-shrinking end edge
result.push(end);
}
}
// we err on the side of caution and return null if didn't find
// exactly *one* differing end edge in the search above
if (result.length !== 1) {
return null;
}
return result[0];
};
var parseCodecs = function(codecs) {
var result = {
codecCount: 0,
videoCodec: null,
audioProfile: null
};
result.codecCount = codecs.split(',').length;
result.codecCount = result.codecCount || 2;
// parse the video codec but ignore the version
result.videoCodec = /(^|\s|,)+(avc1)[^ ,]*/i.exec(codecs);
result.videoCodec = result.videoCodec && result.videoCodec[2];
// parse the last field of the audio codec
result.audioProfile = /(^|\s|,)+mp4a.\d+\.(\d+)/i.exec(codecs);
result.audioProfile = result.audioProfile && result.audioProfile[2];
return result;
};
/**
/**
* Blacklist playlists that are known to be codec or
* stream-incompatible with the SourceBuffer configuration. For
* instance, Media Source Extensions would cause the video element to
......@@ -331,13 +417,12 @@ var parseCodecs = function(codecs) {
* will be excluded from the default playlist selection algorithm
* indefinitely.
*/
videojs.HlsHandler.prototype.excludeIncompatibleVariants_ = function(media) {
var
master = this.playlists.master,
codecCount = 2,
videoCodec = null,
audioProfile = null,
codecs;
excludeIncompatibleVariants_(media) {
let master = this.playlists.master;
let codecCount = 2;
let videoCodec = null;
let audioProfile = null;
let codecs;
if (media.attributes && media.attributes.CODECS) {
codecs = parseCodecs(media.attributes.CODECS);
......@@ -346,7 +431,7 @@ videojs.HlsHandler.prototype.excludeIncompatibleVariants_ = function(media) {
codecCount = codecs.codecCount;
}
master.playlists.forEach(function(variant) {
var variantCodecs = {
let variantCodecs = {
codecCount: 2,
videoCodec: null,
audioProfile: null
......@@ -374,10 +459,11 @@ videojs.HlsHandler.prototype.excludeIncompatibleVariants_ = function(media) {
variant.excludeUntil = Infinity;
}
});
};
}
videojs.HlsHandler.prototype.setupSourceBuffer_ = function() {
var media = this.playlists.media(), mimeType;
setupSourceBuffer_() {
let media = this.playlists.media();
let mimeType;
// wait until a media playlist is available and the Media Source is
// attached
......@@ -400,16 +486,15 @@ videojs.HlsHandler.prototype.setupSourceBuffer_ = function() {
// transition the sourcebuffer to the ended state if we've hit the end of
// the playlist
this.sourceBuffer.addEventListener('updateend', this.updateEndHandler_.bind(this));
};
}
/**
/**
* Seek to the latest media position if this is a live video and the
* player and video are loaded and initialized.
*/
videojs.HlsHandler.prototype.setupFirstPlay = function() {
var seekable, media;
media = this.playlists.media();
setupFirstPlay() {
let seekable;
let media = this.playlists.media();
// check that everything is ready to begin buffering
......@@ -439,12 +524,12 @@ videojs.HlsHandler.prototype.setupFirstPlay = function() {
this.tech_.setCurrentTime(seekable.end(0));
}
}
};
}
/**
/**
* Begin playing the video.
*/
videojs.HlsHandler.prototype.play = function() {
play() {
this.loadingState_ = 'segments';
if (this.tech_.ended()) {
......@@ -462,11 +547,10 @@ videojs.HlsHandler.prototype.play = function() {
this.tech_.setCurrentTime(this.seekable().start(0));
}
}
};
}
videojs.HlsHandler.prototype.setCurrentTime = function(currentTime) {
var
buffered = this.findBufferedRange_();
setCurrentTime(currentTime) {
let buffered = this.findBufferedRange_();
if (!(this.playlists && this.playlists.media())) {
// return immediately if the metadata is not ready yet
......@@ -503,18 +587,20 @@ videojs.HlsHandler.prototype.setCurrentTime = function(currentTime) {
// begin filling the buffer at the new position
this.fillBuffer(this.playlists.getMediaIndexForTime_(currentTime));
};
}
duration() {
let playlists = this.playlists;
videojs.HlsHandler.prototype.duration = function() {
var playlists = this.playlists;
if (playlists) {
return videojs.Hls.Playlist.duration(playlists.media());
return Hls.Playlist.duration(playlists.media());
}
return 0;
};
}
videojs.HlsHandler.prototype.seekable = function() {
var media, seekable;
seekable() {
let media;
let seekable;
if (!this.playlists) {
return videojs.createTimeRanges();
......@@ -524,7 +610,7 @@ videojs.HlsHandler.prototype.seekable = function() {
return videojs.createTimeRanges();
}
seekable = videojs.Hls.Playlist.seekable(media);
seekable = Hls.Playlist.seekable(media);
if (seekable.length === 0) {
return seekable;
}
......@@ -534,29 +620,27 @@ videojs.HlsHandler.prototype.seekable = function() {
// fall back to the playlist loader's running estimate of expired
// time
if (seekable.start(0) === 0) {
return videojs.createTimeRanges([[
this.playlists.expired_,
this.playlists.expired_ + seekable.end(0)
]]);
return videojs.createTimeRanges([[this.playlists.expired_,
this.playlists.expired_ + seekable.end(0)]]);
}
// seekable has been calculated based on buffering video data so it
// can be returned directly
return seekable;
};
}
/**
/**
* Update the player duration
*/
videojs.HlsHandler.prototype.updateDuration = function(playlist) {
var oldDuration = this.mediaSource.duration,
newDuration = videojs.Hls.Playlist.duration(playlist),
setDuration = function() {
updateDuration(playlist) {
let oldDuration = this.mediaSource.duration;
let newDuration = Hls.Playlist.duration(playlist);
let setDuration = () => {
this.mediaSource.duration = newDuration;
this.tech_.trigger('durationchange');
this.mediaSource.removeEventListener('sourceopen', setDuration);
}.bind(this);
};
// if the duration has changed, invalidate the cached value
if (oldDuration !== newDuration) {
......@@ -568,31 +652,31 @@ videojs.HlsHandler.prototype.updateDuration = function(playlist) {
this.tech_.trigger('durationchange');
}
}
};
}
/**
/**
* Clear all buffers and reset any state relevant to the current
* source. After this function is called, the tech should be in a
* state suitable for switching to a different video.
*/
videojs.HlsHandler.prototype.resetSrc_ = function() {
resetSrc_() {
this.cancelSegmentXhr();
this.cancelKeyXhr();
if (this.sourceBuffer && this.mediaSource.readyState === 'open') {
this.sourceBuffer.abort();
}
};
}
videojs.HlsHandler.prototype.cancelKeyXhr = function() {
cancelKeyXhr() {
if (this.keyXhr_) {
this.keyXhr_.onreadystatechange = null;
this.keyXhr_.abort();
this.keyXhr_ = null;
}
};
}
videojs.HlsHandler.prototype.cancelSegmentXhr = function() {
cancelSegmentXhr() {
if (this.segmentXhr_) {
// Prevent error handler from running.
this.segmentXhr_.onreadystatechange = null;
......@@ -602,12 +686,12 @@ videojs.HlsHandler.prototype.cancelSegmentXhr = function() {
// clear out the segment being processed
this.pendingSegment_ = null;
};
}
/**
/**
* Abort all outstanding work and cleanup.
*/
videojs.HlsHandler.prototype.dispose = function() {
dispose() {
this.stopCheckingBuffer_();
if (this.playlists) {
......@@ -615,36 +699,35 @@ videojs.HlsHandler.prototype.dispose = function() {
}
this.resetSrc_();
Component.prototype.dispose.call(this);
};
super.dispose();
}
/**
/**
* Chooses the appropriate media playlist based on the current
* bandwidth estimate and the player size.
* @return the highest bitrate playlist less than the currently detected
* bandwidth, accounting for some amount of bandwidth variance
*/
videojs.HlsHandler.prototype.selectPlaylist = function () {
var
effectiveBitrate,
sortedPlaylists = this.playlists.master.playlists.slice(),
bandwidthPlaylists = [],
now = +new Date(),
i,
variant,
bandwidthBestVariant,
resolutionPlusOne,
resolutionBestVariant,
width,
height;
sortedPlaylists.sort(videojs.Hls.comparePlaylistBandwidth);
selectPlaylist() {
let effectiveBitrate;
let sortedPlaylists = this.playlists.master.playlists.slice();
let bandwidthPlaylists = [];
let now = +new Date();
let i;
let variant;
let bandwidthBestVariant;
let resolutionPlusOne;
let resolutionBestVariant;
let width;
let height;
sortedPlaylists.sort(Hls.comparePlaylistBandwidth);
// filter out any playlists that have been excluded due to
// incompatible configurations or playback errors
sortedPlaylists = sortedPlaylists.filter(function(variant) {
if (variant.excludeUntil !== undefined) {
return now >= variant.excludeUntil;
sortedPlaylists = sortedPlaylists.filter((localvariant) => {
if (typeof localvariant.excludeUntil !== 'undefined') {
return now >= localvariant.excludeUntil;
}
return true;
});
......@@ -676,9 +759,10 @@ videojs.HlsHandler.prototype.selectPlaylist = function () {
i = bandwidthPlaylists.length;
// sort variants by resolution
bandwidthPlaylists.sort(videojs.Hls.comparePlaylistResolution);
bandwidthPlaylists.sort(Hls.comparePlaylistResolution);
// forget our old variant from above, or we might choose that in high-bandwidth scenarios
// forget our old variant from above,
// or we might choose that in high-bandwidth scenarios
// (this could be the lowest bitrate rendition as we go through all of them above)
variant = null;
......@@ -712,9 +796,10 @@ videojs.HlsHandler.prototype.selectPlaylist = function () {
// if both dimensions are less than the player use the
// previous (next-largest) variant
break;
} else if (!resolutionPlusOne ||
(variant.attributes.RESOLUTION.width < resolutionPlusOne.attributes.RESOLUTION.width &&
variant.attributes.RESOLUTION.height < resolutionPlusOne.attributes.RESOLUTION.height)) {
} else if (!resolutionPlusOne || (variant.attributes.RESOLUTION.width <
resolutionPlusOne.attributes.RESOLUTION.width &&
variant.attributes.RESOLUTION.height <
resolutionPlusOne.attributes.RESOLUTION.height)) {
// If we still haven't found a good match keep a
// reference to the previous variant for the next loop
// iteration
......@@ -728,13 +813,16 @@ videojs.HlsHandler.prototype.selectPlaylist = function () {
}
// fallback chain of variants
return resolutionPlusOne || resolutionBestVariant || bandwidthBestVariant || sortedPlaylists[0];
};
return resolutionPlusOne ||
resolutionBestVariant ||
bandwidthBestVariant ||
sortedPlaylists[0];
}
/**
/**
* Periodically request new segments and append video data.
*/
videojs.HlsHandler.prototype.checkBuffer_ = function() {
checkBuffer_() {
// calling this method directly resets any outstanding buffer checks
if (this.checkBufferTimeout_) {
window.clearTimeout(this.checkBufferTimeout_);
......@@ -747,101 +835,44 @@ videojs.HlsHandler.prototype.checkBuffer_ = function() {
// wait awhile and try again
this.checkBufferTimeout_ = window.setTimeout((this.checkBuffer_).bind(this),
bufferCheckInterval);
};
}
/**
/**
* Setup a periodic task to request new segments if necessary and
* append bytes into the SourceBuffer.
*/
videojs.HlsHandler.prototype.startCheckingBuffer_ = function() {
startCheckingBuffer_() {
this.checkBuffer_();
};
}
/**
/**
* Stop the periodic task requesting new segments and feeding the
* SourceBuffer.
*/
videojs.HlsHandler.prototype.stopCheckingBuffer_ = function() {
stopCheckingBuffer_() {
if (this.checkBufferTimeout_) {
window.clearTimeout(this.checkBufferTimeout_);
this.checkBufferTimeout_ = null;
}
};
var filterBufferedRanges = function(predicate) {
return function(time) {
var
i,
ranges = [],
tech = this.tech_,
// !!The order of the next two assignments is important!!
// `currentTime` must be equal-to or greater-than the start of the
// buffered range. Flash executes out-of-process so, every value can
// change behind the scenes from line-to-line. By reading `currentTime`
// after `buffered`, we ensure that it is always a current or later
// value during playback.
buffered = tech.buffered();
if (time === undefined) {
time = tech.currentTime();
}
if (buffered && buffered.length) {
// Search for a range containing the play-head
for (i = 0; i < buffered.length; i++) {
if (predicate(buffered.start(i), buffered.end(i), time)) {
ranges.push([buffered.start(i), buffered.end(i)]);
}
}
}
return videojs.createTimeRanges(ranges);
};
};
/**
* Attempts to find the buffered TimeRange that contains the specified
* time, or where playback is currently happening if no specific time
* is specified.
* @param time (optional) {number} the time to filter on. Defaults to
* currentTime.
* @return a new TimeRanges object.
*/
videojs.HlsHandler.prototype.findBufferedRange_ = filterBufferedRanges(function(start, end, time) {
return start - TIME_FUDGE_FACTOR <= time &&
end + TIME_FUDGE_FACTOR >= time;
});
/**
* Returns the TimeRanges that begin at or later than the specified
* time.
* @param time (optional) {number} the time to filter on. Defaults to
* currentTime.
* @return a new TimeRanges object.
*/
videojs.HlsHandler.prototype.findNextBufferedRange_ = filterBufferedRanges(function(start, end, time) {
return start - TIME_FUDGE_FACTOR >= time;
});
/**
/**
* Determines whether there is enough video data currently in the buffer
* and downloads a new segment if the buffered time is less than the goal.
* @param seekToTime (optional) {number} the offset into the downloaded segment
* to seek to, in seconds
*/
videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
var
tech = this.tech_,
currentTime = tech.currentTime(),
hasBufferedContent = (this.tech_.buffered().length !== 0),
currentBuffered = this.findBufferedRange_(),
outsideBufferedRanges = !(currentBuffered && currentBuffered.length),
currentBufferedEnd = 0,
bufferedTime = 0,
segment,
segmentInfo,
segmentTimestampOffset;
fillBuffer(mediaIndex) {
let tech = this.tech_;
let currentTime = tech.currentTime();
let hasBufferedContent = (this.tech_.buffered().length !== 0);
let currentBuffered = this.findBufferedRange_();
let outsideBufferedRanges = !(currentBuffered && currentBuffered.length);
let currentBufferedEnd = 0;
let bufferedTime = 0;
let segment;
let segmentInfo;
let segmentTimestampOffset;
// if preload is set to "none", do not download segments until playback is requested
if (this.loadingState_ !== 'segments') {
......@@ -864,7 +895,7 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
}
// if no segments are available, do nothing
if (this.playlists.state === "HAVE_NOTHING" ||
if (this.playlists.state === 'HAVE_NOTHING' ||
!this.playlists.media() ||
!this.playlists.media().segments) {
return;
......@@ -875,7 +906,7 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
return;
}
if (mediaIndex === undefined) {
if (typeof mediaIndex === 'undefined') {
if (currentBuffered && currentBuffered.length) {
currentBufferedEnd = currentBuffered.end(0);
mediaIndex = this.playlists.getMediaIndexForTime_(currentBufferedEnd);
......@@ -883,7 +914,7 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
// if there is plenty of content in the buffer and we're not
// seeking, relax for awhile
if (bufferedTime >= videojs.Hls.GOAL_BUFFER_LENGTH) {
if (bufferedTime >= Hls.GOAL_BUFFER_LENGTH) {
return;
}
} else {
......@@ -900,7 +931,8 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
// we have entered a state where we are fetching the same segment,
// try to walk forward
if (this.lastSegmentLoaded_ &&
this.playlistUriToUrl(this.lastSegmentLoaded_.uri) === this.playlistUriToUrl(segment.uri) &&
this.playlistUriToUrl(this.lastSegmentLoaded_.uri) ===
this.playlistUriToUrl(segment.uri) &&
this.lastSegmentLoaded_.byterange === segment.byterange) {
return this.fillBuffer(mediaIndex + 1);
}
......@@ -910,12 +942,12 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
// resolve the segment URL relative to the playlist
uri: this.playlistUriToUrl(segment.uri),
// the segment's mediaIndex & mediaSequence at the time it was requested
mediaIndex: mediaIndex,
mediaIndex,
mediaSequence: this.playlists.media().mediaSequence,
// the segment's playlist
playlist: this.playlists.media(),
// The state of the buffer when this segment was requested
currentBufferedEnd: currentBufferedEnd,
currentBufferedEnd,
// unencrypted bytes of the segment
bytes: null,
// when a key is defined for this segment, the encrypted bytes
......@@ -932,7 +964,7 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
};
if (mediaIndex > 0) {
segmentTimestampOffset = videojs.Hls.Playlist.duration(segmentInfo.playlist,
segmentTimestampOffset = Hls.Playlist.duration(segmentInfo.playlist,
segmentInfo.playlist.mediaSequence + mediaIndex) + this.playlists.expired_;
}
......@@ -955,43 +987,50 @@ videojs.HlsHandler.prototype.fillBuffer = function(mediaIndex) {
}
this.loadSegment(segmentInfo);
};
}
playlistUriToUrl(segmentRelativeUrl) {
let playListUrl;
videojs.HlsHandler.prototype.playlistUriToUrl = function(segmentRelativeUrl) {
var playListUrl;
// resolve the segment URL relative to the playlist
if (this.playlists.media().uri === this.source_.src) {
playListUrl = resolveUrl(this.source_.src, segmentRelativeUrl);
} else {
playListUrl = resolveUrl(resolveUrl(this.source_.src, this.playlists.media().uri || ''), segmentRelativeUrl);
playListUrl =
resolveUrl(resolveUrl(this.source_.src, this.playlists.media().uri || ''),
segmentRelativeUrl);
}
return playListUrl;
};
}
/* Turns segment byterange into a string suitable for use in
/*
* Turns segment byterange into a string suitable for use in
* HTTP Range requests
*/
videojs.HlsHandler.prototype.byterangeStr_ = function(byterange) {
var byterangeStart, byterangeEnd;
byterangeStr_(byterange) {
let byterangeStart;
let byterangeEnd;
// `byterangeEnd` is one less than `offset + length` because the HTTP range
// header uses inclusive ranges
byterangeEnd = byterange.offset + byterange.length - 1;
byterangeStart = byterange.offset;
return "bytes=" + byterangeStart + "-" + byterangeEnd;
};
return 'bytes=' + byterangeStart + '-' + byterangeEnd;
}
/* Defines headers for use in the xhr request for a particular segment.
/*
* Defines headers for use in the xhr request for a particular segment.
*/
videojs.HlsHandler.prototype.segmentXhrHeaders_ = function(segment) {
var headers = {};
segmentXhrHeaders_(segment) {
let headers = {};
if ('byterange' in segment) {
headers['Range'] = this.byterangeStr_(segment.byterange);
headers.Range = this.byterangeStr_(segment.byterange);
}
return headers;
};
}
/*
/*
* Sets `bandwidth`, `segmentXhrTime`, and appends to the `bytesReceived.
* Expects an object with:
* * `roundTripTime` - the round trip time for the request we're setting the time for
......@@ -999,22 +1038,23 @@ videojs.HlsHandler.prototype.segmentXhrHeaders_ = function(segment) {
* * `bytesReceived` - amount of bytes downloaded
* `bandwidth` is the only required property.
*/
videojs.HlsHandler.prototype.setBandwidth = function(xhr) {
setBandwidth(localXhr) {
// calculate the download bandwidth
this.segmentXhrTime = xhr.roundTripTime;
this.bandwidth = xhr.bandwidth;
this.bytesReceived += xhr.bytesReceived || 0;
this.segmentXhrTime = localXhr.roundTripTime;
this.bandwidth = localXhr.bandwidth;
this.bytesReceived += localXhr.bytesReceived || 0;
this.tech_.trigger('bandwidthupdate');
};
}
/*
/*
* Blacklists a playlist when an error occurs for a set amount of time
* making it unavailable for selection by the rendition selection algorithm
* and then forces a new playlist (rendition) selection.
*/
videojs.HlsHandler.prototype.blacklistCurrentPlaylist_ = function(error) {
var currentPlaylist, nextPlaylist;
blacklistCurrentPlaylist_(error) {
let currentPlaylist;
let nextPlaylist;
// If the `error` was generated by the playlist loader, it will contain
// the playlist we were trying to load (but failed) and that should be
......@@ -1036,26 +1076,27 @@ videojs.HlsHandler.prototype.blacklistCurrentPlaylist_ = function(error) {
nextPlaylist = this.selectPlaylist();
if (nextPlaylist) {
videojs.log.warn('Problem encountered with the current HLS playlist. Switching to another playlist.');
videojs.log.warn('Problem encountered with the current ' +
'HLS playlist. Switching to another playlist.');
return this.playlists.media(nextPlaylist);
} else {
videojs.log.warn('Problem encountered with the current HLS playlist. No suitable alternatives found.');
}
videojs.log.warn('Problem encountered with the current ' +
'HLS playlist. No suitable alternatives found.');
// We have no more playlists we can select so we must fail
this.error = error;
return this.mediaSource.endOfStream('network');
}
};
videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) {
var
self = this,
segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex],
removeToTime = 0,
seekable = this.seekable();
loadSegment(segmentInfo) {
let segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
let removeToTime = 0;
let seekable = this.seekable();
// Chrome has a hard limit of 150mb of buffer and a very conservative "garbage collector"
// We manually clear out the old buffer to ensure we don't trigger the QuotaExceeded error
// Chrome has a hard limit of 150mb of
// buffer and a very conservative "garbage collector"
// We manually clear out the old buffer to ensure
// we don't trigger the QuotaExceeded error
// on the source buffer during subsequent appends
if (this.sourceBuffer && !this.sourceBuffer.updating) {
// If we have a seekable range use that as the limit for what can be removed safely
......@@ -1077,7 +1118,7 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) {
}
// request the next segment
this.segmentXhr_ = videojs.Hls.xhr({
this.segmentXhr_ = Hls.xhr({
uri: segmentInfo.uri,
responseType: 'arraybuffer',
withCredentials: this.source_.withCredentials,
......@@ -1086,25 +1127,25 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) {
// decrease in network performance or a server issue.
timeout: (segment.duration * 1.5) * 1000,
headers: this.segmentXhrHeaders_(segment)
}, function(error, request) {
}, (error, request) => {
// This is a timeout of a previously aborted segment request
// so simply ignore it
if (!self.segmentXhr_ || request !== self.segmentXhr_) {
if (!this.segmentXhr_ || request !== this.segmentXhr_) {
return;
}
// the segment request is no longer outstanding
self.segmentXhr_ = null;
this.segmentXhr_ = null;
// if a segment request times out, we may have better luck with another playlist
if (request.timedout) {
self.bandwidth = 1;
return self.playlists.media(self.selectPlaylist());
this.bandwidth = 1;
return this.playlists.media(this.selectPlaylist());
}
// otherwise, trigger a network error
if (!request.aborted && error) {
return self.blacklistCurrentPlaylist_({
return this.blacklistCurrentPlaylist_({
status: request.status,
message: 'HLS segment request error at URL: ' + segmentInfo.uri,
code: (request.status >= 500) ? 4 : 2
......@@ -1116,8 +1157,8 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) {
return;
}
self.lastSegmentLoaded_ = segment;
self.setBandwidth(request);
this.lastSegmentLoaded_ = segment;
this.setBandwidth(request);
if (segment.key) {
segmentInfo.encryptedBytes = new Uint8Array(request.response);
......@@ -1125,28 +1166,26 @@ videojs.HlsHandler.prototype.loadSegment = function(segmentInfo) {
segmentInfo.bytes = new Uint8Array(request.response);
}
self.pendingSegment_ = segmentInfo;
this.pendingSegment_ = segmentInfo;
self.tech_.trigger('progress');
self.drainBuffer();
this.tech_.trigger('progress');
this.drainBuffer();
// figure out what stream the next segment should be downloaded from
// with the updated bandwidth information
self.playlists.media(self.selectPlaylist());
this.playlists.media(this.selectPlaylist());
});
};
}
videojs.HlsHandler.prototype.drainBuffer = function() {
var
segmentInfo,
mediaIndex,
playlist,
offset,
bytes,
segment,
decrypter,
segIv;
drainBuffer() {
let segmentInfo;
let mediaIndex;
let playlist;
let bytes;
let segment;
let decrypter;
let segIv;
// if the buffer is empty or the source buffer hasn't been created
// yet, do nothing
......@@ -1169,7 +1208,6 @@ videojs.HlsHandler.prototype.drainBuffer = function() {
segmentInfo = this.pendingSegment_;
mediaIndex = segmentInfo.mediaIndex;
playlist = segmentInfo.playlist;
offset = segmentInfo.offset;
bytes = segmentInfo.bytes;
segment = playlist.segments[mediaIndex];
......@@ -1183,30 +1221,30 @@ videojs.HlsHandler.prototype.drainBuffer = function() {
code: 4
});
} else if (!segment.key.bytes) {
// waiting for the key bytes, try again later
return;
} else if (segmentInfo.decrypter) {
// decryption is in progress, try again later
return;
} else {
}
// if the media sequence is greater than 2^32, the IV will be incorrect
// assuming 10s segments, that would be about 1300 years
segIv = segment.key.iv || new Uint32Array([0, 0, 0, mediaIndex + playlist.mediaSequence]);
segIv = segment.key.iv ||
new Uint32Array([0, 0, 0, mediaIndex + playlist.mediaSequence]);
// create a decrypter to incrementally decrypt the segment
decrypter = new videojs.Hls.Decrypter(segmentInfo.encryptedBytes,
decrypter = new Hls.Decrypter(segmentInfo.encryptedBytes,
segment.key.bytes,
segIv,
function(err, bytes) {
segmentInfo.bytes = bytes;
function(err, localBytes) {
if (err) {
throw new Error(err);
}
segmentInfo.bytes = localBytes;
});
segmentInfo.decrypter = decrypter;
return;
}
}
this.pendingSegment_.buffered = this.tech_.buffered();
......@@ -1216,18 +1254,17 @@ videojs.HlsHandler.prototype.drainBuffer = function() {
// the segment is asynchronously added to the current buffered data
this.sourceBuffer.appendBuffer(bytes);
};
}
videojs.HlsHandler.prototype.updateEndHandler_ = function () {
var
segmentInfo = this.pendingSegment_,
segment,
segments,
playlist,
currentMediaIndex,
currentBuffered,
seekable,
timelineUpdate;
updateEndHandler_() {
let segmentInfo = this.pendingSegment_;
let segment;
let segments;
let playlist;
let currentMediaIndex;
let currentBuffered;
let seekable;
let timelineUpdate;
this.pendingSegment_ = null;
......@@ -1238,7 +1275,8 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () {
playlist = this.playlists.media();
segments = playlist.segments;
currentMediaIndex = segmentInfo.mediaIndex + (segmentInfo.mediaSequence - playlist.mediaSequence);
currentMediaIndex = segmentInfo.mediaIndex +
(segmentInfo.mediaSequence - playlist.mediaSequence);
currentBuffered = this.findBufferedRange_();
// if we switched renditions don't try to add segment timeline
......@@ -1260,16 +1298,17 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () {
currentBuffered.length === 0) {
if (seekable.length &&
this.tech_.currentTime() < seekable.start(0)) {
var next = this.findNextBufferedRange_();
let next = this.findNextBufferedRange_();
if (next.length) {
videojs.log('tried seeking to', this.tech_.currentTime(), 'but that was too early, retrying at', next.start(0));
videojs.log('tried seeking to', this.tech_.currentTime(),
'but that was too early, retrying at', next.start(0));
this.tech_.setCurrentTime(next.start(0) + TIME_FUDGE_FACTOR);
}
}
}
timelineUpdate = videojs.Hls.findSoleUncommonTimeRangesEnd_(segmentInfo.buffered,
timelineUpdate = Hls.findSoleUncommonTimeRangesEnd_(segmentInfo.buffered,
this.tech_.buffered());
if (timelineUpdate && segment) {
......@@ -1299,42 +1338,44 @@ videojs.HlsHandler.prototype.updateEndHandler_ = function () {
// improves subsequent media index calculations.
this.fillBuffer(currentMediaIndex + 1);
return;
};
}
/**
/**
* Attempt to retrieve the key for a particular media segment.
*/
videojs.HlsHandler.prototype.fetchKey_ = function(segment) {
var key, self, settings, receiveKey;
fetchKey_(segment) {
let key;
let settings;
let receiveKey;
// if there is a pending XHR or no segments, don't do anything
if (this.keyXhr_) {
return;
}
self = this;
settings = this.options_;
/**
* Handle a key XHR response.
*/
receiveKey = function(key) {
return function(error, request) {
var view;
self.keyXhr_ = null;
receiveKey = (keyRecieved) => {
return (error, request) => {
let view;
this.keyXhr_ = null;
if (error || !request.response || request.response.byteLength !== 16) {
key.retries = key.retries || 0;
key.retries++;
keyRecieved.retries = keyRecieved.retries || 0;
keyRecieved.retries++;
if (!request.aborted) {
// try fetching again
self.fetchKey_(segment);
this.fetchKey_(segment);
}
return;
}
view = new DataView(request.response);
key.bytes = new Uint32Array([
keyRecieved.bytes = new Uint32Array([
view.getUint32(0),
view.getUint32(4),
view.getUint32(8),
......@@ -1342,7 +1383,7 @@ videojs.HlsHandler.prototype.fetchKey_ = function(segment) {
]);
// check to see if this allows us to make progress buffering now
self.checkBuffer_();
this.checkBuffer_();
};
};
......@@ -1355,135 +1396,105 @@ videojs.HlsHandler.prototype.fetchKey_ = function(segment) {
// request the key if the retry limit hasn't been reached
if (!key.bytes && !keyFailed(key)) {
this.keyXhr_ = videojs.Hls.xhr({
this.keyXhr_ = Hls.xhr({
uri: this.playlistUriToUrl(key.uri),
responseType: 'arraybuffer',
withCredentials: settings.withCredentials
}, receiveKey(key));
return;
}
};
}
}
/**
* Whether the browser has built-in HLS support.
* Attempts to find the buffered TimeRange that contains the specified
* time, or where playback is currently happening if no specific time
* is specified.
* @param time (optional) {number} the time to filter on. Defaults to
* currentTime.
* @return a new TimeRanges object.
*/
videojs.Hls.supportsNativeHls = (function() {
var
video = document.createElement('video'),
xMpegUrl,
vndMpeg;
// native HLS is definitely not supported if HTML5 video isn't
if (!videojs.getComponent('Html5').isSupported()) {
return false;
}
xMpegUrl = video.canPlayType('application/x-mpegURL');
vndMpeg = video.canPlayType('application/vnd.apple.mpegURL');
return (/probably|maybe/).test(xMpegUrl) ||
(/probably|maybe/).test(vndMpeg);
})();
// HLS is a source handler, not a tech. Make sure attempts to use it
// as one do not cause exceptions.
videojs.Hls.isSupported = function() {
return videojs.log.warn('HLS is no longer a tech. Please remove it from ' +
'your player\'s techOrder.');
};
HlsHandler.prototype.findBufferedRange_ =
filterBufferedRanges(function(start, end, time) {
return start - TIME_FUDGE_FACTOR <= time &&
end + TIME_FUDGE_FACTOR >= time;
});
/**
* A comparator function to sort two playlist object by bandwidth.
* @param left {object} a media playlist object
* @param right {object} a media playlist object
* @return {number} Greater than zero if the bandwidth attribute of
* left is greater than the corresponding attribute of right. Less
* than zero if the bandwidth of right is greater than left and
* exactly zero if the two are equal.
* Returns the TimeRanges that begin at or later than the specified
* time.
* @param time (optional) {number} the time to filter on. Defaults to
* currentTime.
* @return a new TimeRanges object.
*/
videojs.Hls.comparePlaylistBandwidth = function(left, right) {
var leftBandwidth, rightBandwidth;
if (left.attributes && left.attributes.BANDWIDTH) {
leftBandwidth = left.attributes.BANDWIDTH;
}
leftBandwidth = leftBandwidth || window.Number.MAX_VALUE;
if (right.attributes && right.attributes.BANDWIDTH) {
rightBandwidth = right.attributes.BANDWIDTH;
}
rightBandwidth = rightBandwidth || window.Number.MAX_VALUE;
return leftBandwidth - rightBandwidth;
};
HlsHandler.prototype.findNextBufferedRange_ =
filterBufferedRanges(function(start, end, time) {
return start - TIME_FUDGE_FACTOR >= time;
});
/**
* A comparator function to sort two playlist object by resolution (width).
* @param left {object} a media playlist object
* @param right {object} a media playlist object
* @return {number} Greater than zero if the resolution.width attribute of
* left is greater than the corresponding attribute of right. Less
* than zero if the resolution.width of right is greater than left and
* exactly zero if the two are equal.
* The Source Handler object, which informs video.js what additional
* MIME types are supported and sets up playback. It is registered
* automatically to the appropriate tech based on the capabilities of
* the browser it is running in. It is not necessary to use or modify
* this object in normal usage.
*/
videojs.Hls.comparePlaylistResolution = function(left, right) {
var leftWidth, rightWidth;
if (left.attributes && left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
leftWidth = left.attributes.RESOLUTION.width;
const HlsSourceHandler = function(mode) {
return {
canHandleSource(srcObj) {
return HlsSourceHandler.canPlayType(srcObj.type);
},
handleSource(source, tech) {
if (mode === 'flash') {
// We need to trigger this asynchronously to give others the chance
// to bind to the event when a source is set at player creation
tech.setTimeout(function() {
tech.trigger('loadstart');
}, 1);
}
leftWidth = leftWidth || window.Number.MAX_VALUE;
if (right.attributes && right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
rightWidth = right.attributes.RESOLUTION.width;
tech.hls = new HlsHandler(tech, {
source,
mode
});
tech.hls.src(source.src);
return tech.hls;
},
canPlayType(type) {
return HlsSourceHandler.canPlayType(type);
}
};
};
rightWidth = rightWidth || window.Number.MAX_VALUE;
HlsSourceHandler.canPlayType = function(type) {
let mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
// NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
// have the same media dimensions/ resolution
if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
} else {
return leftWidth - rightWidth;
// favor native HLS support if it's available
if (Hls.supportsNativeHls()) {
return false;
}
return mpegurlRE.test(type);
};
/**
* Constructs a new URI by interpreting a path relative to another
* URI.
* @param basePath {string} a relative or absolute URI
* @param path {string} a path part to combine with the base
* @return {string} a URI that is equivalent to composing `base`
* with `path`
* @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
*/
resolveUrl = videojs.Hls.resolveUrl = function(basePath, path) {
// use the base element to get the browser to handle URI resolution
var
oldBase = document.querySelector('base'),
docHead = document.querySelector('head'),
a = document.createElement('a'),
base = oldBase,
oldHref,
result;
// prep the document
if (oldBase) {
oldHref = oldBase.href;
} else {
base = docHead.appendChild(document.createElement('base'));
}
if (typeof videojs.MediaSource === 'undefined' ||
typeof videojs.URL === 'undefined') {
videojs.MediaSource = MediaSource;
videojs.URL = URL;
}
// register source handlers with the appropriate techs
if (MediaSource.supportsNativeMediaSources()) {
videojs.getComponent('Html5').registerSourceHandler(HlsSourceHandler('html5'));
}
if (window.Uint8Array) {
videojs.getComponent('Flash').registerSourceHandler(HlsSourceHandler('flash'));
}
base.href = basePath;
a.href = path;
result = a.href;
videojs.HlsHandler = HlsHandler;
videojs.HlsSourceHandler = HlsSourceHandler;
videojs.Hls = Hls;
videojs.m3u8 = m3u8;
// clean up
if (oldBase) {
oldBase.href = oldHref;
} else {
docHead.removeChild(base);
}
return result;
export default {
Hls,
HlsHandler,
HlsSourceHandler
};
})(window, window.videojs, document);
......
......@@ -90,7 +90,6 @@ function() {
});
QUnit.module('Incremental Processing', {
beforeEach() {
this.clock = sinon.useFakeTimers();
......
......@@ -13,13 +13,6 @@
<script src="/node_modules/sinon/pkg/sinon.js"></script>
<script src="/node_modules/qunitjs/qunit/qunit.js"></script>
<script src="/node_modules/video.js/dist/video.js"></script>
<script src="/node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script>
<script src="/src/videojs-contrib-hls.js"></script>
<script src="/dist/videojs-contrib-hls.js"></script>
<script src="/src/bin-utils.js"></script>
<script src="/test/videojs-contrib-hls.test.js"></script>
<script src="/dist-test/videojs-contrib-hls.js"></script>
</body>
......
......@@ -11,29 +11,11 @@ var DEFAULTS = {
'node_modules/video.js/dist/video.js',
'node_modules/video.js/dist/video-js.css',
// REMOVE ME WHEN BROWSERIFIED
'node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js',
// these two stub old functionality
'src/videojs-contrib-hls.js',
'dist/videojs-contrib-hls.js',
'src/bin-utils.js',
'test/stub.test.js',
'test/videojs-contrib-hls.test.js',
'test/m3u8.test.js',
'test/playlist.test.js',
'test/playlist-loader.test.js',
'test/decrypter.test.js',
// END REMOVE ME
// 'test/**/*.js'
'test/**/*.test.js'
],
exclude: [
'test/bundle.js',
// 'test/data/**'
'test/data/**'
],
plugins: [
......@@ -42,7 +24,7 @@ var DEFAULTS = {
],
preprocessors: {
'test/{playlist*,decrypter,stub,m3u8}.test.js': ['browserify']
'test/**/*.test.js': ['browserify']
},
reporters: ['dots'],
......
......@@ -29,9 +29,11 @@ QUnit.test('the environment is sane', function(assert) {
assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists');
assert.strictEqual(typeof sinon, 'object', 'sinon exists');
assert.strictEqual(typeof videojs, 'function', 'videojs exists');
assert.strictEqual(typeof videojs.MediaSource, 'object', 'MediaSource is an object');
assert.strictEqual(typeof videojs.MediaSource, 'function', 'MediaSource is an object');
assert.strictEqual(typeof videojs.URL, 'object', 'URL is an object');
assert.strictEqual(typeof videojs.Hls, 'object', 'Hls is an object');
assert.strictEqual(typeof videojs.HlsSourceHandler,'function', 'HlsSourceHandler is a function');
assert.strictEqual(typeof videojs.HlsSourceHandler,
'function',
'HlsSourceHandler is a function');
assert.strictEqual(typeof videojs.HlsHandler, 'function', 'HlsHandler is a function');
});
......
import manifests from './test-manifests';
import expected from './test-expected';
window.manifests = manifests;
window.expected = expected;
This diff could not be displayed because it is too large.