Commit 822ad0f0 authored by Greg Rutz's avatar Greg Rutz

Merge pull request #486 from cablelabs/manifest-tools

Support for initializing player with a parsed manifest
parents ec7d5614 361a930a
......@@ -55,7 +55,7 @@ Dash.dependencies.DashAdapter = function () {
return null;
},
convertRepresentationToTrackInfo = function(representation) {
convertRepresentationToTrackInfo = function(manifest, representation) {
var trackInfo = new MediaPlayer.vo.TrackInfo(),
a = representation.adaptation.period.mpd.manifest.Period_asArray[representation.adaptation.period.index].AdaptationSet_asArray[representation.adaptation.index],
r = this.manifestExt.getRepresentationFor(representation.index, a);
......@@ -67,12 +67,12 @@ Dash.dependencies.DashAdapter = function () {
trackInfo.fragmentDuration = representation.segmentDuration || (representation.segments && representation.segments.length > 0 ? representation.segments[0].duration : NaN);
trackInfo.MSETimeOffset = representation.MSETimeOffset;
trackInfo.useCalculatedLiveEdgeTime = representation.useCalculatedLiveEdgeTime;
trackInfo.mediaInfo = convertAdaptationToMediaInfo.call(this, representation.adaptation);
trackInfo.mediaInfo = convertAdaptationToMediaInfo.call(this, manifest, representation.adaptation);
return trackInfo;
},
convertAdaptationToMediaInfo = function(adaptation) {
convertAdaptationToMediaInfo = function(manifest, adaptation) {
var mediaInfo = new MediaPlayer.vo.MediaInfo(),
self = this,
a = adaptation.period.mpd.manifest.Period_asArray[adaptation.period.index].AdaptationSet_asArray[adaptation.index];
......@@ -80,7 +80,7 @@ Dash.dependencies.DashAdapter = function () {
mediaInfo.id = adaptation.id;
mediaInfo.index = adaptation.index;
mediaInfo.type = adaptation.type;
mediaInfo.streamInfo = convertPeriodToStreamInfo.call(this, adaptation.period);
mediaInfo.streamInfo = convertPeriodToStreamInfo.call(this, manifest, adaptation.period);
mediaInfo.trackCount = this.manifestExt.getRepresentationCount(a);
mediaInfo.lang = this.manifestExt.getLanguageForAdaptation(a);
mediaInfo.codec = this.manifestExt.getCodec(a);
......@@ -99,7 +99,7 @@ Dash.dependencies.DashAdapter = function () {
return mediaInfo;
},
convertPeriodToStreamInfo = function(period) {
convertPeriodToStreamInfo = function(manifest, period) {
var streamInfo = new MediaPlayer.vo.StreamInfo(),
THRESHOLD = 1;
......@@ -107,15 +107,14 @@ Dash.dependencies.DashAdapter = function () {
streamInfo.index = period.index;
streamInfo.start = period.start;
streamInfo.duration = period.duration;
streamInfo.manifestInfo = convertMpdToManifestInfo.call(this, period.mpd);
streamInfo.manifestInfo = convertMpdToManifestInfo.call(this, manifest, period.mpd);
streamInfo.isLast = Math.abs((streamInfo.start + streamInfo.duration) - streamInfo.manifestInfo.duration) < THRESHOLD;
return streamInfo;
},
convertMpdToManifestInfo = function(mpd) {
var manifestInfo = new MediaPlayer.vo.ManifestInfo(),
manifest = this.manifestModel.getValue();
convertMpdToManifestInfo = function(manifest, mpd) {
var manifestInfo = new MediaPlayer.vo.ManifestInfo();
manifestInfo.DVRWindowSize = mpd.timeShiftBufferDepth;
manifestInfo.loadedTime = mpd.manifest.loadedTime;
......@@ -140,7 +139,7 @@ Dash.dependencies.DashAdapter = function () {
adaptations[periodId] = adaptations[periodId] || this.manifestExt.getAdaptationsForPeriod(manifest, periodInfo);
return convertAdaptationToMediaInfo.call(this, adaptations[periodId][idx]);
return convertAdaptationToMediaInfo.call(this, manifest, adaptations[periodId][idx]);
},
getStreamsInfoFromManifest = function(manifest) {
......@@ -157,7 +156,7 @@ Dash.dependencies.DashAdapter = function () {
ln = periods.length;
for(i = 0; i < ln; i += 1) {
streams.push(convertPeriodToStreamInfo.call(this, periods[i]));
streams.push(convertPeriodToStreamInfo.call(this, manifest, periods[i]));
}
return streams;
......@@ -166,7 +165,7 @@ Dash.dependencies.DashAdapter = function () {
getMpdInfo = function(manifest) {
var mpd = this.manifestExt.getMpd(manifest);
return convertMpdToManifestInfo.call(this, mpd);
return convertMpdToManifestInfo.call(this, manifest, mpd);
},
getInitRequest = function(streamProcessor, quality) {
......@@ -201,11 +200,10 @@ Dash.dependencies.DashAdapter = function () {
return streamProcessor.indexHandler.setCurrentTime(value);
},
updateData = function(streamProcessor) {
updateData = function(manifest, streamProcessor) {
var periodInfo = getPeriodForStreamInfo(streamProcessor.getStreamInfo()),
mediaInfo = streamProcessor.getMediaInfo(),
adaptation = getAdaptationForMediaInfo(mediaInfo),
manifest = this.manifestModel.getValue(),
type = streamProcessor.getType(),
id,
data;
......@@ -216,16 +214,16 @@ Dash.dependencies.DashAdapter = function () {
streamProcessor.trackController.updateData(data, adaptation, type);
},
getTrackInfoForQuality = function(representationController, quality) {
getTrackInfoForQuality = function(manifest, representationController, quality) {
var representation = representationController.getRepresentationForQuality(quality);
return representation ? convertRepresentationToTrackInfo.call(this, representation) : null;
return representation ? convertRepresentationToTrackInfo.call(this, manifest, representation) : null;
},
getCurrentTrackInfo = function(representationController) {
getCurrentTrackInfo = function(manifest, representationController) {
var representation = representationController.getCurrentRepresentation();
return representation ? convertRepresentationToTrackInfo.call(this, representation): null;
return representation ? convertRepresentationToTrackInfo.call(this, manifest, representation): null;
},
getEvent = function(eventBox, eventStreams, startTime) {
......@@ -253,9 +251,8 @@ Dash.dependencies.DashAdapter = function () {
return event;
},
getEventsFor = function(info, streamProcessor) {
var manifest = this.manifestModel.getValue(),
events = [];
getEventsFor = function(manifest, info, streamProcessor) {
var events = [];
if (info instanceof MediaPlayer.vo.StreamInfo) {
events = this.manifestExt.getEventsForPeriod(manifest, getPeriodForStreamInfo(info));
......@@ -271,7 +268,6 @@ Dash.dependencies.DashAdapter = function () {
return {
system : undefined,
manifestExt: undefined,
manifestModel: undefined,
timelineConverter: undefined,
metricsList: {
......
......@@ -108,7 +108,7 @@ MediaPlayer.di.Context = function () {
this.system.mapClass('eventController', MediaPlayer.dependencies.EventController);
this.system.mapClass('textController', MediaPlayer.dependencies.TextController);
this.system.mapClass('bufferController', MediaPlayer.dependencies.BufferController);
this.system.mapSingleton('manifestLoader', MediaPlayer.dependencies.ManifestLoader);
this.system.mapClass('manifestLoader', MediaPlayer.dependencies.ManifestLoader);
this.system.mapSingleton('manifestUpdater', MediaPlayer.dependencies.ManifestUpdater);
this.system.mapClass('fragmentController', MediaPlayer.dependencies.FragmentController);
this.system.mapClass('fragmentLoader', MediaPlayer.dependencies.FragmentLoader);
......
......@@ -33,8 +33,9 @@ MediaPlayer.dependencies.ManifestUpdater = function () {
var refreshDelay = NaN,
refreshTimer = null,
isStopped = false,
isUpdating = false,
isStopped,
isUpdating,
manifestLoader,
clear = function () {
if (refreshTimer !== null) {
......@@ -52,18 +53,19 @@ MediaPlayer.dependencies.ManifestUpdater = function () {
}
},
update = function () {
var self = this,
manifest = self.manifestModel.getValue(),
delay,
update = function (manifest) {
var delay,
timeSinceLastUpdate;
if (manifest !== undefined && manifest !== null) {
delay = self.manifestExt.getRefreshDelay(manifest);
timeSinceLastUpdate = (new Date().getTime() - manifest.loadedTime.getTime()) / 1000;
refreshDelay = Math.max(delay - timeSinceLastUpdate, 0);
start.call(self);
}
this.manifestModel.setValue(manifest);
this.log("Manifest has been refreshed.");
delay = this.manifestExt.getRefreshDelay(manifest);
timeSinceLastUpdate = (new Date().getTime() - manifest.loadedTime.getTime()) / 1000;
refreshDelay = Math.max(delay - timeSinceLastUpdate, 0);
this.notify(MediaPlayer.dependencies.ManifestUpdater.eventList.ENAME_MANIFEST_UPDATED, {manifest:manifest});
},
onRefreshTimer = function () {
......@@ -71,7 +73,7 @@ MediaPlayer.dependencies.ManifestUpdater = function () {
manifest,
url;
if (isUpdating) return;
if (isStopped || isUpdating) return;
isUpdating = true;
manifest = self.manifestModel.getValue();
......@@ -83,27 +85,23 @@ MediaPlayer.dependencies.ManifestUpdater = function () {
//self.log("Refresh manifest @ " + url);
self.manifestLoader.load(url);
manifestLoader.load(url);
},
onManifestLoaded = function(e) {
if (e.error) return;
this.manifestModel.setValue(e.data.manifest);
this.log("Manifest has been refreshed.");
//self.log(manifestResult);
if (isStopped) return;
update.call(this);
if (!e.error) {
update.call(this, e.data.manifest);
}
},
onPlaybackStarted = function(/*e*/) {
this.start();
isStopped = false;
start.call(this);
},
onPlaybackPaused = function(/*e*/) {
this.stop();
isStopped = true;
clear.call(this);
},
onStreamsComposed = function(/*e*/) {
......@@ -114,9 +112,11 @@ MediaPlayer.dependencies.ManifestUpdater = function () {
return {
log: undefined,
system: undefined,
subscribe: undefined,
unsubscribe: undefined,
notify: undefined,
manifestModel: undefined,
manifestExt: undefined,
manifestLoader: undefined,
setup: function () {
// Listen to streamsComposed event to be aware that the streams have been composed
......@@ -126,18 +126,33 @@ MediaPlayer.dependencies.ManifestUpdater = function () {
this[MediaPlayer.dependencies.PlaybackController.eventList.ENAME_PLAYBACK_PAUSED] = onPlaybackPaused;
},
start: function () {
isStopped = false;
update.call(this);
initialize: function (loader) {
isUpdating = false;
isStopped = true;
manifestLoader = loader;
manifestLoader.subscribe(MediaPlayer.dependencies.ManifestLoader.eventList.ENAME_MANIFEST_LOADED, this);
},
setManifest: function (m) {
update.call(this, m);
},
getManifestLoader: function () {
return manifestLoader;
},
stop: function() {
reset: function() {
isStopped = true;
clear.call(this);
manifestLoader.unsubscribe(MediaPlayer.dependencies.ManifestLoader.eventList.ENAME_MANIFEST_LOADED, this);
}
};
};
MediaPlayer.dependencies.ManifestUpdater.prototype = {
constructor: MediaPlayer.dependencies.ManifestUpdater
};
\ No newline at end of file
};
MediaPlayer.dependencies.ManifestUpdater.eventList = {
ENAME_MANIFEST_UPDATED: "manifestUpdated"
};
......@@ -64,14 +64,13 @@ MediaPlayer = function (context) {
*/
var VERSION = "1.4.0",
system,
manifestLoader,
abrController,
element,
source,
protectionData = null,
streamController,
rulesController,
manifestUpdater,
manifest,
metricsExt,
metricsModel,
videoModel,
......@@ -103,15 +102,16 @@ MediaPlayer = function (context) {
playing = true;
this.debug.log("Playback initiated!");
streamController = system.getObject("streamController");
streamController.subscribe(MediaPlayer.dependencies.StreamController.eventList.ENAME_STREAMS_COMPOSED, manifestUpdater);
manifestLoader.subscribe(MediaPlayer.dependencies.ManifestLoader.eventList.ENAME_MANIFEST_LOADED, streamController);
manifestLoader.subscribe(MediaPlayer.dependencies.ManifestLoader.eventList.ENAME_MANIFEST_LOADED, manifestUpdater);
streamController.initialize();
streamController.setVideoModel(videoModel);
streamController.setAutoPlay(autoPlay);
streamController.setProtectionData(protectionData);
DOMStorage.checkInitialBitrate();
streamController.load(source);
if (typeof source === "string") {
streamController.load(source);
} else {
streamController.loadWithManifest(manifest);
}
system.mapValue("scheduleWhilePaused", scheduleWhilePaused);
system.mapOutlet("scheduleWhilePaused", "stream");
system.mapOutlet("scheduleWhilePaused", "scheduleController");
......@@ -221,9 +221,6 @@ MediaPlayer = function (context) {
doReset = function() {
if (playing && streamController) {
streamController.unsubscribe(MediaPlayer.dependencies.StreamController.eventList.ENAME_STREAMS_COMPOSED, manifestUpdater);
manifestLoader.unsubscribe(MediaPlayer.dependencies.ManifestLoader.eventList.ENAME_MANIFEST_LOADED, streamController);
manifestLoader.unsubscribe(MediaPlayer.dependencies.ManifestLoader.eventList.ENAME_MANIFEST_LOADED, manifestUpdater);
streamController.reset();
abrController.reset();
rulesController.reset();
......@@ -264,8 +261,6 @@ MediaPlayer = function (context) {
setup: function() {
metricsExt = system.getObject("metricsExt");
manifestLoader = system.getObject("manifestLoader");
manifestUpdater = system.getObject("manifestUpdater");
abrController = system.getObject("abrController");
rulesController = system.getObject("rulesController");
metricsModel = system.getObject("metricsModel");
......@@ -572,20 +567,26 @@ MediaPlayer = function (context) {
},
/**
* Use this method to set a source URL to a valid MPD manifest file.
* Use this method to set a source URL to a valid MPD manifest file OR
* a previously downloaded and parsed manifest object.
*
* @param {string} url A URL to a valid MPD manifest file.
* @param {string|Object} source A URL to a valid MPD manifest file, or a
* parsed manifest object.
* @throw "MediaPlayer not initialized!"
*
* @memberof MediaPlayer#
*/
attachSource: function (url) {
attachSource: function (urlOrManifest) {
if (!initialized) {
throw "MediaPlayer not initialized!";
}
this.uriQueryFragModel.reset();
source = this.uriQueryFragModel.parseURI(url);
if (typeof urlOrManifest === "string") {
this.uriQueryFragModel.reset();
source = this.uriQueryFragModel.parseURI(urlOrManifest);
} else {
source = urlOrManifest;
}
// TODO : update
......@@ -596,6 +597,15 @@ MediaPlayer = function (context) {
}
},
/**
* Use this method to attach an already parsed manifest
*
* @param m the manifest
*/
attachManifest: function (m) {
manifest = m;
},
/**
* Attach KeySystem-specific data to use for License Acquisition with EME
* @param data and object containing property names corresponding to key
......@@ -614,6 +624,7 @@ MediaPlayer = function (context) {
reset: function() {
this.attachSource(null);
this.attachView(null);
manifest = null;
},
/**
......
......@@ -244,10 +244,9 @@ MediaPlayer.dependencies.Stream = function () {
mediaInfos = {};
mediaSource = null;
manifest = null;
},
initializeMediaForType = function(type, manifest) {
initializeMediaForType = function(type) {
var self = this,
mimeType = null,
codec,
......@@ -318,7 +317,7 @@ MediaPlayer.dependencies.Stream = function () {
processor.initialize(mimeType || type, buffer, self.videoModel, self.fragmentController, self.playbackController, mediaSource, self, eventController);
processor.setMediaInfo(mediaInfo);
self.abrController.updateTopQualityIndex(mediaInfo);
self.adapter.updateData(processor);
self.adapter.updateData(manifest, processor);
if(type === "fragmentedText"){
processor.bufferController.videoModel= self.videoModel;
buffer.initialize(type,processor.bufferController);
......@@ -346,10 +345,10 @@ MediaPlayer.dependencies.Stream = function () {
// Figure out some bits about the stream before building anything.
//self.log("Gathering information for buffers. (1)");
initializeMediaForType.call(self, "video", manifest);
initializeMediaForType.call(self, "audio", manifest);
initializeMediaForType.call(self, "text", manifest);
initializeMediaForType.call(self, "fragmentedText", manifest);
initializeMediaForType.call(self, "video");
initializeMediaForType.call(self, "audio");
initializeMediaForType.call(self, "text");
initializeMediaForType.call(self, "fragmentedText");
//this.log("MediaSource initialized!");
},
......@@ -444,7 +443,7 @@ MediaPlayer.dependencies.Stream = function () {
this.reset();
},
doLoad = function (manifestResult) {
doLoad = function () {
var self = this,
onMediaSourceSetup = function (mediaSourceResult) {
......@@ -468,7 +467,6 @@ MediaPlayer.dependencies.Stream = function () {
//self.log("Stream start loading.");
manifest = manifestResult;
mediaSourceResult = self.mediaSourceExt.createMediaSource();
//self.log("MediaSource created.");
......@@ -524,10 +522,10 @@ MediaPlayer.dependencies.Stream = function () {
i = 0,
mediaInfo,
events,
processor;
processor,
manifest = self.manifestModel.getValue();
updating = true;
manifest = self.manifestModel.getValue();
streamInfo = updatedStreamInfo;
self.log("Manifest updated... set new data on buffers.");
......@@ -583,15 +581,16 @@ MediaPlayer.dependencies.Stream = function () {
keySystem = undefined;
},
load: function(manifest) {
doLoad.call(this, manifest);
load: function() {
doLoad.call(this);
manifest = this.manifestModel.getValue();
},
setVideoModel: function(value) {
this.videoModel = value;
},
initProtection: function(manifest) {
initProtection: function() {
if (this.capabilities.supportsEncryptedMedia()) {
this.protectionModel = this.system.getObject("protectionModel");
this.protectionModel.init(this.getVideoModel());
......@@ -608,6 +607,7 @@ MediaPlayer.dependencies.Stream = function () {
// Look for ContentProtection elements. InitData can be provided by either the
// dash264drm:Pssh ContentProtection format or a DRM-specific format.
var manifest = this.manifestModel.getValue();
var audioInfo = this.adapter.getMediaInfoForType(manifest, streamInfo, "audio");
var videoInfo = this.adapter.getMediaInfoForType(manifest, streamInfo, "video");
var mediaInfo = (videoInfo) ? videoInfo : audioInfo; // We could have audio or video only
......@@ -680,6 +680,7 @@ MediaPlayer.dependencies.Stream = function () {
this.protectionModel = undefined;
}
manifest = null;
tearDownMediaSource.call(this);
this.fragmentController.reset();
......
......@@ -52,6 +52,7 @@ MediaPlayer.dependencies.StreamProcessor = function () {
abrController: undefined,
baseURLExt: undefined,
adapter: undefined,
manifestModel: undefined,
initialize: function (typeValue, buffer, videoModel, fragmentController, playbackController, mediaSource, streamValue, eventControllerValue) {
......@@ -214,11 +215,11 @@ MediaPlayer.dependencies.StreamProcessor = function () {
},
getCurrentTrack: function() {
return this.adapter.getCurrentTrackInfo(this.trackController);
return this.adapter.getCurrentTrackInfo(this.manifestModel.getValue(), this.trackController);
},
getTrackForQuality: function(quality) {
return this.adapter.getTrackInfoForQuality(this.trackController, quality);
return this.adapter.getTrackInfoForQuality(this.manifestModel.getValue(), this.trackController, quality);
},
isBufferingCompleted: function() {
......
......@@ -110,8 +110,9 @@ MediaPlayer.dependencies.BufferController = function () {
startTime = e.data.startTime,
request = this.streamProcessor.getFragmentModel().getRequests({state: MediaPlayer.dependencies.FragmentModel.states.EXECUTED, quality: quality, index: index})[0],
currentTrack = this.streamProcessor.getTrackForQuality(quality),
eventStreamMedia = this.adapter.getEventsFor(currentTrack.mediaInfo, this.streamProcessor),
eventStreamTrack = this.adapter.getEventsFor(currentTrack, this.streamProcessor);
manifest = this.manifestModel.getValue(),
eventStreamMedia = this.adapter.getEventsFor(manifest, currentTrack.mediaInfo, this.streamProcessor),
eventStreamTrack = this.adapter.getEventsFor(manifest, currentTrack, this.streamProcessor);
if(eventStreamMedia.length > 0 || eventStreamTrack.length > 0) {
events = handleInbandEvents.call(this, bytes, request, eventStreamMedia, eventStreamTrack);
......@@ -568,6 +569,7 @@ MediaPlayer.dependencies.BufferController = function () {
sourceBufferExt: undefined,
eventBus: undefined,
bufferMax: undefined,
manifestModel: undefined,
mediaSourceExt: undefined,
metricsModel: undefined,
metricsExt: undefined,
......
......@@ -145,20 +145,19 @@ MediaPlayer.dependencies.EventController = function(){
},
refreshManifest = function () {
var self = this,
manifest = self.manifestModel.getValue(),
var manifest = this.manifestModel.getValue(),
url = manifest.url;
if (manifest.hasOwnProperty("Location")) {
url = manifest.Location;
}
self.log("Refresh manifest @ " + url);
self.manifestLoader.load(url);
this.log("Refresh manifest @ " + url);
this.manifestUpdater.getManifestLoader().load(url);
};
return {
manifestModel: undefined,
manifestLoader:undefined,
manifestUpdater: undefined,
log: undefined,
system: undefined,
addInlineEvents : addInlineEvents,
......
......@@ -124,7 +124,7 @@ MediaPlayer.dependencies.PlaybackController = function () {
onDataUpdateCompleted = function(e) {
if (e.error) return;
var track = this.adapter.convertDataToTrack(e.data.currentRepresentation);
var track = this.adapter.convertDataToTrack(this.manifestModel.getValue(), e.data.currentRepresentation);
streamInfo = track.mediaInfo.streamInfo;
isDynamic = e.sender.streamProcessor.isDynamic();
updateCurrentTime.call(this);
......@@ -286,6 +286,7 @@ MediaPlayer.dependencies.PlaybackController = function () {
uriQueryFragModel: undefined,
metricsModel: undefined,
metricsExt: undefined,
manifestModel: undefined,
notify: undefined,
subscribe: undefined,
unsubscribe: undefined,
......
......@@ -203,7 +203,7 @@ MediaPlayer.dependencies.ScheduleController = function () {
onDataUpdateCompleted = function(e) {
if (e.error) return;
currentTrackInfo = this.adapter.convertDataToTrack(e.data.currentRepresentation);
currentTrackInfo = this.adapter.convertDataToTrack(this.manifestModel.getValue(), e.data.currentRepresentation);
},
onStreamUpdated = function(e) {
......@@ -384,6 +384,7 @@ MediaPlayer.dependencies.ScheduleController = function () {
log: undefined,
system: undefined,
metricsModel: undefined,
manifestModel: undefined,
metricsExt: undefined,
scheduleWhilePaused: undefined,
timelineConverter: undefined,
......
......@@ -291,9 +291,9 @@
stream.setPlaybackController(playbackCtrl);
playbackCtrl.subscribe(MediaPlayer.dependencies.PlaybackController.eventList.ENAME_PLAYBACK_ERROR, stream);
playbackCtrl.subscribe(MediaPlayer.dependencies.PlaybackController.eventList.ENAME_CAN_PLAY, stream);
stream.initProtection(manifest);
stream.initProtection();
stream.setAutoPlay(autoPlay);
stream.load(manifest);
stream.load();
stream.subscribe(MediaPlayer.dependencies.Stream.eventList.ENAME_STREAM_UPDATED, self);
streams.push(stream);
}
......@@ -309,7 +309,7 @@
fireSwitchEvent.call(self, null, activeStream);
}
} catch(e) {
self.errHandler.manifestError(e.message, "nostreamscomposed", self.manifestModel.getValue());
self.errHandler.manifestError(e.message, "nostreamscomposed", manifest);
self.reset();
}
},
......@@ -330,10 +330,8 @@
composeStreams.call(this);
},
onManifestLoaded = function(e) {
onManifestUpdated = function(e) {
if (!e.error) {
this.manifestModel.setValue(e.data.manifest);
this.log("Manifest has loaded.");
//self.log(self.manifestModel.getValue());
......@@ -348,8 +346,8 @@
return {
system: undefined,