c694b4b7 by David LaPalomento

Merge pull request #544 from BrandonOCasey/browserify-p4

browserify-p4: playlist*, xhr, and resolve-url
2 parents e3c93f60 4c27b9d1
......@@ -48,10 +48,7 @@
<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="/src/xhr.js"></script>
<script src="/dist/videojs-contrib-hls.js"></script>
<script src="/src/playlist.js"></script>
<script src="/src/playlist-loader.js"></script>
<script src="/src/bin-utils.js"></script>
<script>
(function(window, videojs) {
......
......@@ -2,7 +2,7 @@ var browserify = require('browserify');
var fs = require('fs');
var glob = require('glob');
glob('test/{decryper,m3u8,stub}.test.js', function(err, files) {
glob('test/{playlist*,decryper,m3u8,stub}.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/{decrypter,m3u8,stub}.test.js', function(err, files) {
glob('test/{playlist*,decrypter,m3u8,stub}.test.js', function(err, files) {
var b = browserify(files, {
cache: {},
packageCache: {},
......
......@@ -5,14 +5,13 @@
* M3U8 playlists.
*
*/
(function(window, videojs) {
'use strict';
var
resolveUrl = videojs.Hls.resolveUrl,
xhr = videojs.Hls.xhr,
mergeOptions = videojs.mergeOptions,
import resolveUrl from './resolve-url';
import XhrModule from './xhr';
import {mergeOptions} from 'video.js';
import Stream from './stream';
import m3u8 from './m3u8';
/**
/**
* Returns a new master playlist that is the result of merging an
* updated media playlist into the original version. If the
* updated media playlist does not match any of the playlist
......@@ -23,14 +22,12 @@
* master playlist with the updated media playlist merged in, or
* null if the merge produced no change.
*/
updateMaster = function(master, media) {
var
changed = false,
result = mergeOptions(master, {}),
i,
playlist;
i = master.playlists.length;
const updateMaster = function(master, media) {
let changed = false;
let result = mergeOptions(master, {});
let i = master.playlists.length;
let playlist;
while (i--) {
playlist = result.playlists[i];
if (playlist.uri === media.uri) {
......@@ -51,15 +48,16 @@
if (playlist.segments) {
result.playlists[i].segments = updateSegments(playlist.segments,
media.segments,
media.mediaSequence - playlist.mediaSequence);
media.mediaSequence -
playlist.mediaSequence);
}
changed = true;
}
}
return changed ? result : null;
},
};
/**
/**
* Returns a new array of segments that is the result of merging
* properties from an older list of segments onto an updated
* list. No properties on the updated playlist will be overridden.
......@@ -73,8 +71,11 @@
* playlists.
* @return a list of merged segment objects
*/
updateSegments = function(original, update, offset) {
var result = update.slice(), length, i;
const updateSegments = function(original, update, offset) {
let result = update.slice();
let length;
let i;
offset = offset || 0;
length = Math.min(original.length, update.length + offset);
......@@ -82,18 +83,17 @@
result[i - offset] = mergeOptions(original[i], result[i - offset]);
}
return result;
},
PlaylistLoader = function(srcUrl, withCredentials) {
var
loader = this,
dispose,
mediaUpdateTimeout,
request,
playlistRequestError,
haveMetadata;
PlaylistLoader.prototype.init.call(this);
};
export default class PlaylistLoader extends Stream {
constructor(srcUrl, withCredentials) {
super();
let loader = this;
let dispose;
let mediaUpdateTimeout;
let request;
let playlistRequestError;
let haveMetadata;
// a flag that disables "expired time"-tracking this setting has
// no effect when not playing a live stream
......@@ -127,7 +127,9 @@
// updated playlist.
haveMetadata = function(xhr, url) {
var parser, refreshDelay, update;
let parser;
let refreshDelay;
let update;
loader.setBandwidth(request || xhr);
......@@ -135,7 +137,7 @@
request = null;
loader.state = 'HAVE_METADATA';
parser = new videojs.m3u8.Parser();
parser = new m3u8.Parser();
parser.push(xhr.responseText);
parser.end();
parser.manifest.uri = url;
......@@ -198,7 +200,8 @@
* object to switch to
*/
loader.media = function(playlist) {
var startingState = loader.state, mediaChange;
let startingState = loader.state;
let mediaChange;
// getter
if (!playlist) {
return loader.media_;
......@@ -258,9 +261,9 @@
}
// request the new playlist
request = xhr({
request = XhrModule({
uri: resolveUrl(loader.master.uri, playlist.uri),
withCredentials: withCredentials
withCredentials
}, function(error, request) {
if (error) {
return playlistRequestError(request, playlist.uri, startingState);
......@@ -295,9 +298,9 @@
}
loader.state = 'HAVE_CURRENT_METADATA';
request = xhr({
request = XhrModule({
uri: resolveUrl(loader.master.uri, loader.media().uri),
withCredentials: withCredentials
withCredentials
}, function(error, request) {
if (error) {
return playlistRequestError(request, loader.media().uri);
......@@ -307,11 +310,12 @@
});
// request the specified URL
request = xhr({
request = XhrModule({
uri: srcUrl,
withCredentials: withCredentials
withCredentials
}, function(error, req) {
var parser, i;
let parser;
let i;
// clear the loader's request reference
request = null;
......@@ -321,12 +325,13 @@
status: req.status,
message: 'HLS playlist request error at URL: ' + srcUrl,
responseText: req.responseText,
code: 2 // MEDIA_ERR_NETWORK
// MEDIA_ERR_NETWORK
code: 2
};
return loader.trigger('error');
}
parser = new videojs.m3u8.Parser();
parser = new m3u8.Parser();
parser.push(req.responseText);
parser.end();
......@@ -365,16 +370,16 @@
haveMetadata(req, srcUrl);
return loader.trigger('loadedmetadata');
});
};
PlaylistLoader.prototype = new videojs.Hls.Stream();
}
/**
* Update the PlaylistLoader state to reflect the changes in an
* update to the current media playlist.
* @param update {object} the updated media playlist object
*/
PlaylistLoader.prototype.updateMediaPlaylist_ = function(update) {
var outdated, i, segment;
updateMediaPlaylist_(update) {
let outdated;
let i;
let segment;
outdated = this.media_;
this.media_ = this.master.playlists[update.uri];
......@@ -432,7 +437,7 @@
}
this.expired_ += segment.duration;
}
};
}
/**
* Determine the index of the segment that contains a specified
......@@ -452,17 +457,16 @@
* value will be clamped to the index of the segment containing the
* closest playback position that is currently available.
*/
PlaylistLoader.prototype.getMediaIndexForTime_ = function(time) {
var
i,
segment,
originalTime = time,
numSegments = this.media_.segments.length,
lastSegment = numSegments - 1,
startIndex,
endIndex,
knownStart,
knownEnd;
getMediaIndexForTime_(time) {
let i;
let segment;
let originalTime = time;
let numSegments = this.media_.segments.length;
let lastSegment = numSegments - 1;
let startIndex;
let endIndex;
let knownStart;
let knownEnd;
if (!this.media_) {
return 0;
......@@ -558,7 +562,5 @@
// the one most likely to tell us something about the timeline
return lastSegment;
}
};
videojs.Hls.PlaylistLoader = PlaylistLoader;
})(window, window.videojs);
}
}
......
/**
* Playlist related utilities.
*/
(function(window, videojs) {
'use strict';
import {createTimeRange} from 'video.js';
var duration, intervalDuration, backwardDuration, forwardDuration, seekable;
backwardDuration = function(playlist, endSequence) {
var result = 0, segment, i;
i = endSequence - playlist.mediaSequence;
const backwardDuration = function(playlist, endSequence) {
let result = 0;
let i = endSequence - playlist.mediaSequence;
// if a start time is available for segment immediately following
// the interval, use it
segment = playlist.segments[i];
let segment = playlist.segments[i];
// Walk backward until we find the latest segment with timeline
// information that is earlier than endSequence
if (segment) {
if (segment.start !== undefined) {
if (typeof segment.start !== 'undefined') {
return { result: segment.start, precise: true };
}
if (segment.end !== undefined) {
if (typeof segment.end !== 'undefined') {
return {
result: segment.end - segment.duration,
precise: true
......@@ -28,28 +25,29 @@
}
while (i--) {
segment = playlist.segments[i];
if (segment.end !== undefined) {
if (typeof segment.end !== 'undefined') {
return { result: result + segment.end, precise: true };
}
result += segment.duration;
if (segment.start !== undefined) {
if (typeof segment.start !== 'undefined') {
return { result: result + segment.start, precise: true };
}
}
return { result: result, precise: false };
};
forwardDuration = function(playlist, endSequence) {
var result = 0, segment, i;
return { result, precise: false };
};
i = endSequence - playlist.mediaSequence;
const forwardDuration = function(playlist, endSequence) {
let result = 0;
let segment;
let i = endSequence - playlist.mediaSequence;
// Walk forward until we find the earliest segment with timeline
// information
for (; i < playlist.segments.length; i++) {
segment = playlist.segments[i];
if (segment.start !== undefined) {
if (typeof segment.start !== 'undefined') {
return {
result: segment.start - result,
precise: true
......@@ -58,7 +56,7 @@
result += segment.duration;
if (segment.end !== undefined) {
if (typeof segment.end !== 'undefined') {
return {
result: segment.end - result,
precise: true
......@@ -68,9 +66,9 @@
}
// indicate we didn't find a useful duration estimate
return { result: -1, precise: false };
};
};
/**
/**
* Calculate the media duration from the segments associated with a
* playlist. The duration of a subinterval of the available segments
* may be calculated by specifying an end index.
......@@ -81,10 +79,11 @@
* @return {number} the duration between the first available segment
* and end index.
*/
intervalDuration = function(playlist, endSequence) {
var backward, forward;
const intervalDuration = function(playlist, endSequence) {
let backward;
let forward;
if (endSequence === undefined) {
if (typeof endSequence === 'undefined') {
endSequence = playlist.mediaSequence + playlist.segments.length;
}
......@@ -112,9 +111,9 @@
// return the less-precise, playlist-based duration estimate
return backward.result;
};
};
/**
/**
* Calculates the duration of a playlist. If a start and end index
* are specified, the duration will be for the subset of the media
* timeline between those two indices. The total duration for live
......@@ -129,18 +128,18 @@
* @return {number} the duration between the start index and end
* index.
*/
duration = function(playlist, endSequence, includeTrailingTime) {
export const duration = function(playlist, endSequence, includeTrailingTime) {
if (!playlist) {
return 0;
}
if (includeTrailingTime === undefined) {
if (typeof includeTrailingTime === 'undefined') {
includeTrailingTime = true;
}
// if a slice of the total duration is not requested, use
// playlist-level duration indicators when they're present
if (endSequence === undefined) {
if (typeof endSequence === 'undefined') {
// if present, use the duration specified in the playlist
if (playlist.totalDuration) {
return playlist.totalDuration;
......@@ -156,9 +155,9 @@
return intervalDuration(playlist,
endSequence,
includeTrailingTime);
};
};
/**
/**
* Calculates the interval of time that is currently seekable in a
* playlist. The returned time ranges are relative to the earliest
* moment in the specified playlist that is still available. A full
......@@ -169,16 +168,17 @@
* @return {TimeRanges} the periods of time that are valid targets
* for seeking
*/
seekable = function(playlist) {
var start, end;
export const seekable = function(playlist) {
let start;
let end;
// without segments, there are no seekable ranges
if (!playlist.segments) {
return videojs.createTimeRange();
return createTimeRange();
}
// when the playlist is complete, the entire duration is seekable
if (playlist.endList) {
return videojs.createTimeRange(0, duration(playlist));
return createTimeRange(0, duration(playlist));
}
// live playlists should not expose three segment durations worth
......@@ -186,13 +186,13 @@
// https://tools.ietf.org/html/draft-pantos-http-live-streaming-16#section-6.3.3
start = intervalDuration(playlist, playlist.mediaSequence);
end = intervalDuration(playlist,
playlist.mediaSequence + Math.max(0, playlist.segments.length - 3));
return videojs.createTimeRange(start, end);
};
// exports
videojs.Hls.Playlist = {
duration: duration,
seekable: seekable
};
})(window, window.videojs);
playlist.mediaSequence +
Math.max(0, playlist.segments.length - 3));
return createTimeRange(start, end);
};
// exports
export default {
duration,
seekable
};
......
import document from 'global/document';
/* eslint-disable max-len */
/**
* 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
*/
/* eslint-enable max-len */
const resolveUrl = function(basePath, path) {
// use the base element to get the browser to handle URI resolution
let oldBase = document.querySelector('base');
let docHead = document.querySelector('head');
let a = document.createElement('a');
let base = oldBase;
let oldHref;
let result;
// prep the document
if (oldBase) {
oldHref = oldBase.href;
} else {
base = docHead.appendChild(document.createElement('base'));
}
base.href = basePath;
a.href = path;
result = a.href;
// clean up
if (oldBase) {
oldBase.href = oldHref;
} else {
docHead.removeChild(base);
}
return result;
};
export default resolveUrl;
......@@ -2,6 +2,10 @@ 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 = {};
......@@ -11,3 +15,6 @@ 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;
......
(function(videojs) {
'use strict';
/**
/**
* A wrapper for videojs.xhr that tracks bandwidth.
*/
videojs.Hls.xhr = function(options, callback) {
import {xhr as videojsXHR, mergeOptions} from 'video.js';
const xhr = function(options, callback) {
// Add a default timeout for all hls requests
options = videojs.mergeOptions({
options = mergeOptions({
timeout: 45e3
}, options);
var request = videojs.xhr(options, function(error, response) {
let request = videojsXHR(options, function(error, response) {
if (!error && request.response) {
request.responseTime = (new Date()).getTime();
request.roundTripTime = request.responseTime - request.requestTime;
request.bytesReceived = request.response.byteLength || request.response.length;
if (!request.bandwidth) {
request.bandwidth = Math.floor((request.bytesReceived / request.roundTripTime) * 8 * 1000);
request.bandwidth =
Math.floor((request.bytesReceived / request.roundTripTime) * 8 * 1000);
}
}
// videojs.xhr now uses a specific code on the error object to signal that a request has
// videojs.xhr now uses a specific code
// on the error object to signal that a request has
// timed out errors of setting a boolean on the request object
if (error || request.timedout) {
request.timedout = request.timedout || (error.code === 'ETIMEDOUT');
......@@ -44,5 +44,6 @@
request.requestTime = (new Date()).getTime();
return request;
};
})(window.videojs);
};
export default xhr;
......
......@@ -16,16 +16,11 @@
<script src="/node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js"></script>
<script src="/src/videojs-contrib-hls.js"></script>
<script src="/src/xhr.js"></script>
<script src="/dist/videojs-contrib-hls.js"></script>
<script src="/src/playlist.js"></script>
<script src="/src/playlist-loader.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>
<script src="/test/playlist.test.js"></script>
<script src="/test/playlist-loader.test.js"></script>
</body>
</html>
......
......@@ -16,11 +16,8 @@ var DEFAULTS = {
// these two stub old functionality
'src/videojs-contrib-hls.js',
'src/xhr.js',
'dist/videojs-contrib-hls.js',
'src/playlist.js',
'src/playlist-loader.js',
'src/bin-utils.js',
'test/stub.test.js',
......@@ -45,7 +42,7 @@ var DEFAULTS = {
],
preprocessors: {
'test/{decrypter,stub,m3u8}.test.js': ['browserify']
'test/{playlist*,decrypter,stub,m3u8}.test.js': ['browserify']
},
reporters: ['dots'],
......