Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial DASH Support #8

Merged
merged 35 commits into from
Dec 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f2daf93
Working commit for dash support
gesinger Sep 29, 2017
ec36b88
Working commit including some cleanup of dash support
gesinger Oct 2, 2017
3d3f28d
set up demo page for testing
Oct 12, 2017
c02746d
use installed parser
Oct 12, 2017
afd4166
pass along source uri to resolve segments
Oct 12, 2017
d975099
pass along source type
Oct 12, 2017
e0d51bb
FIGURE OUT: check source type in monitor buffer tick
Oct 12, 2017
6052320
Remove unused variable and unnecessary new from dash playlist loader
gesinger Oct 20, 2017
416af97
Pass source type to segment loader
gesinger Oct 20, 2017
8f6d062
Add mpd-parser to package.json
gesinger Oct 20, 2017
7b2c72f
Fix media groups tests by adding source type for initialization
gesinger Oct 20, 2017
56aa7fc
Detect and configure EME for DASH
gesinger Oct 20, 2017
d6a9342
Tell segment loader whether content is demuxed so it can wait on sour…
gesinger Oct 23, 2017
4d5d73a
In the case of demuxed audio and video, wait for both source buffers …
gesinger Oct 25, 2017
2a52a3e
Add tests for canHandleSource and canPlayType DASH support
gesinger Oct 26, 2017
7943f57
Add tests for simpleTypeFromSourceType
gesinger Oct 26, 2017
341b5c3
Add test for proper PlaylistLoader creation on MasterPlaylistLoader c…
gesinger Oct 26, 2017
6895fe3
Add test for dash mime types
gesinger Oct 27, 2017
2409edc
Add tests for intializing AUDIO and SUBTITLES groups with HLS/DASH pl…
gesinger Oct 27, 2017
3a611aa
Add tests for configuring mime types on segment loaders with and with…
gesinger Oct 30, 2017
84e5e6a
Add tests for source buffer emitter async updates in source updater
gesinger Oct 30, 2017
e029754
Add tests for DashPlaylistLoader and fix phony URIs
gesinger Nov 2, 2017
f8fdfdd
Use codecs specified in default alternate audio playlist if not speci…
gesinger Nov 14, 2017
796cf8b
Require overrideNative to play DASH on browsers that support it natively
gesinger Nov 14, 2017
9f1140f
Add videojs-contrib-eme as a dev dependency and include it on test pages
gesinger Nov 16, 2017
2d13405
Rework videojs-contrib-eme options based on pending branch
gesinger Nov 16, 2017
7fc599a
Trigger loadedmetadata async from playlist loader to mimic HLS behavior
gesinger Nov 16, 2017
4857242
Fix a couple DASH playlist loader test failures
gesinger Nov 17, 2017
6c24a61
Resolve async calls before leaving media group tests
gesinger Nov 17, 2017
7e93a0e
Update header of README
gesinger Nov 20, 2017
396d065
Move setup of videojs-contrib-eme options to its own function
gesinger Nov 20, 2017
8bec7d1
Add player.vhs reference to HlsHandler instance
gesinger Nov 20, 2017
edc6a27
Add player.dash reference to HlsHandler instance
gesinger Nov 28, 2017
7ca2f10
Disable native audio and video tracks on test page when overrideNativ…
gesinger Nov 28, 2017
e7f8422
Update mpd-parser to 0.2.0 and videojs-contrib-eme to 3.0.0
gesinger Dec 13, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
# video.js HLS Source Handler
# videojs-http-streaming (VHS)

[![Build Status][travis-icon]][travis-link]
[![Slack Status][slack-icon]][slack-link]

Play HLS, DASH, and future HTTP streaming protocols with video.js, even where they're not
natively supported.

Play back HLS with video.js, even where it's not natively supported.

Lead Maintainer: Jon-Carlos Rivera [@imbcmdth](https://github.com/imbcmdth)
Lead Maintainers:
- Jon-Carlos Rivera [@imbcmdth](https://github.com/imbcmdth)
- Joe Forbes [@forbesjo](https://github.com/forbesjo)
- Matthew Neil [@mjneil](https://github.com/mjneil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

- Oshin Karamian [@OshinKaramian](https://github.com/OshinKaramian)
- Garrett Singer [@gesinger](https://github.com/gesinger)
- Chuck Wilson [@squarebracket](https://github.com/squarebracket)

Maintenance Status: Stable

Expand Down
14 changes: 13 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@
<label>
Video URL:
<input id=url type=url value="https://d2zihajmogu5jn.cloudfront.net/bipbop-advanced/bipbop_16x9_variant.m3u8">
</label><br/>
<label>
MimeType:
<select id=mimetype>
<option>application/x-mpegURL</option>
<option>application/dash+xml</option>
</select>
</label>
<button type=submit>Load</button>
</form>
Expand All @@ -47,19 +54,24 @@
</ul>

<script src="/node_modules/video.js/dist/video.js"></script>
<script src="/node_modules/videojs-contrib-eme/dist/videojs-contrib-eme.js"></script>
<script src="/dist/videojs-http-streaming.js"></script>
<script>
(function(window, videojs) {
var player = window.player = videojs('videojs-http-streaming-player');

// configure videojs-contrib-eme
player.eme();

// hook up the video switcher
var loadUrl = document.getElementById('load-url');
var url = document.getElementById('url');
var mimeType = document.getElementById('mimetype');
loadUrl.addEventListener('submit', function(event) {
event.preventDefault();
player.src({
src: url.value,
type: 'application/x-mpegURL'
type: mimeType.options[mimeType.selectedIndex].innerText
});
return false;
});
Expand Down
66 changes: 66 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"global": "^4.3.0",
"m3u8-parser": "2.1.0",
"mux.js": "4.3.2",
"mpd-parser": "0.2.0",
"url-toolkit": "1.0.9",
"video.js": "^5.19.1 || ^6.2.0",
"videojs-contrib-media-sources": "4.6.2",
Expand Down Expand Up @@ -134,6 +135,7 @@
"shelljs": "^0.5.3",
"sinon": "1.10.2",
"uglify-js": "^2.5.0",
"videojs-contrib-eme": "^3.0.0",
"videojs-contrib-quality-levels": "^2.0.2",
"videojs-flash": "^2.0.0",
"videojs-standard": "^4.0.3",
Expand Down
4 changes: 2 additions & 2 deletions scripts/manifest-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ var build = function() {
var file = path.resolve(manifestDir, files.shift());
var extname = path.extname(file);

if (extname === '.m3u8') {
if (extname === '.m3u8' || extname === '.mpd') {
// translate this manifest
manifests += ' \'' + path.basename(file, '.m3u8') + '\': ';
manifests += ' \'' + path.basename(file, extname) + '\': ';
manifests += fs.readFileSync(file, 'utf8')
.split(/\r\n|\n/)
// quote and concatenate
Expand Down
181 changes: 181 additions & 0 deletions src/dash-playlist-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { EventTarget } from 'video.js';
import mpdParser from 'mpd-parser';
import {
setupMediaPlaylists,
resolveMediaGroupUris
} from './playlist-loader';

export default class DashPlaylistLoader extends EventTarget {
// DashPlaylistLoader must accept either a src url or a playlist because subsequent
// playlist loader setups from media groups will expect to be able to pass a playlist
// (since there aren't external URLs to media playlists with DASH)
constructor(srcUrlOrPlaylist, hls, withCredentials) {
super();

this.hls_ = hls;
this.withCredentials = withCredentials;

if (!srcUrlOrPlaylist) {
throw new Error('A non-empty playlist URL or playlist is required');
}

// initialize the loader state
if (typeof srcUrlOrPlaylist === 'string') {
this.srcUrl = srcUrlOrPlaylist;
this.state = 'HAVE_NOTHING';
return;
}

this.state = 'HAVE_METADATA';
this.started = true;
// we only should have one, so select it
this.media(srcUrlOrPlaylist);
// trigger async to mimic behavior of HLS, where it must request a playlist
setTimeout(() => {
this.trigger('loadedmetadata');
}, 0);
}

dispose() {
this.stopRequest();
}

stopRequest() {
if (this.request) {
const oldRequest = this.request;

this.request = null;
oldRequest.onreadystatechange = null;
oldRequest.abort();
}
}

media(playlist) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to get function comments here? Mostly I'm asking as this has a few pieces of nuance (the playlist can be a string or object, and thus will be handled differently, as well as the fact that it is both a getter and a setter).

// getter
if (!playlist) {
return this.media_;
}

// setter
if (this.state === 'HAVE_NOTHING') {
throw new Error('Cannot switch media playlist from ' + this.state);
}

// find the playlist object if the target playlist has been specified by URI
if (typeof playlist === 'string') {
if (!this.master.playlists[playlist]) {
throw new Error('Unknown playlist URI: ' + playlist);
}
playlist = this.master.playlists[playlist];
}

const mediaChange = !this.media_ || playlist.uri !== this.media_.uri;

this.state = 'HAVE_METADATA';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these states doc'ed anywhere? Might be nice to have them doc'ed in some form, as it might make debugging easier (particularly if there is a sequence to these events).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah nice, this is what I was looking for.

this.media_ = playlist;

// trigger media change if the active media has been updated
if (mediaChange) {
this.trigger('mediachanging');
// since every playlist is technically loaded, trigger that we loaded it
this.trigger('loadedplaylist');
this.trigger('mediachange');
}
return;
}

pause() {
this.stopRequest();
if (this.state === 'HAVE_NOTHING') {
// If we pause the loader before any data has been retrieved, its as if we never
// started, so reset to an unstarted state.
this.started = false;
}
}

load() {
// because the playlists are internal to the manifest, load should either load the
// main manifest, or do nothing but trigger an event
if (!this.started) {
this.start();
return;
}

this.trigger('loadedplaylist');
}

start() {
this.started = true;

// request the specified URL
this.request = this.hls_.xhr({
uri: this.srcUrl,
withCredentials: this.withCredentials
}, (error, req) => {
// disposed
if (!this.request) {
return;
}

// clear the loader's request reference
this.request = null;

if (error) {
this.error = {
status: req.status,
message: 'DASH playlist request error at URL: ' + this.srcUrl,
responseText: req.responseText,
// MEDIA_ERR_NETWORK
code: 2
};
if (this.state === 'HAVE_NOTHING') {
this.started = false;
}
return this.trigger('error');
}

this.master = mpdParser.parse(req.responseText, this.srcUrl);
this.master.uri = this.srcUrl;

this.state = 'HAVE_MASTER';

// TODO mediaSequence will be added in mpd-parser
this.master.playlists.forEach((playlist) => {
playlist.mediaSequence = 0;
});
for (let groupKey in this.master.mediaGroups.AUDIO) {
for (let labelKey in this.master.mediaGroups.AUDIO[groupKey]) {
this.master.mediaGroups.AUDIO[groupKey][labelKey].playlists.forEach(
(playlist) => {
playlist.mediaSequence = 0;
});
}
}

// set up phony URIs for the playlists since we won't have external URIs for DASH
// but reference playlists by their URI throughout the project
for (let i = 0; i < this.master.playlists.length; i++) {
const phonyUri = `placeholder-uri-${i}`;

this.master.playlists[i].uri = phonyUri;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should also add resolvedUri here, quite a few places outside of this class use resolvedUri instead of uri

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe not quite a few since media-groups has source type checking now, but there is a spot in MPC and external plugins may look for it

Copy link
Contributor Author

@gesinger gesinger Nov 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot, it actually gets added in setupMediaPlaylists

// set up by URI references
this.master.playlists[phonyUri] = this.master.playlists[i];
}

setupMediaPlaylists(this.master);
resolveMediaGroupUris(this.master);

this.trigger('loadedplaylist');
if (!this.request) {
// no media playlist was specifically selected so start
// from the first listed one
this.media(this.master.playlists[0]);
}
// trigger loadedmetadata to resolve setup of media groups
// trigger async to mimic behavior of HLS, where it must request a playlist
setTimeout(() => {
this.trigger('loadedmetadata');
}, 0);
});
}
}
Loading