-
Notifications
You must be signed in to change notification settings - Fork 428
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
Changes from all commits
f2daf93
ec36b88
3d3f28d
c02746d
afd4166
d975099
e0d51bb
6052320
416af97
8f6d062
7b2c72f
56aa7fc
d6a9342
4d5d73a
2a52a3e
7943f57
341b5c3
6895fe3
2409edc
3a611aa
84e5e6a
e029754
f8fdfdd
796cf8b
9f1140f
2d13405
7fc599a
4857242
6c24a61
7e93a0e
396d065
8bec7d1
edc6a27
7ca2f10
e7f8422
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these states mimic the PlaylistLoader states There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should also add There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I forgot, it actually gets added in |
||
// 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); | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice