mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-26 17:46:26 +03:00
OfflineVideoSource store functionality. Issue #22
Change-Id: Ib55ce40619929f7139a0651f0e0874a0bd4fc054
This commit is contained in:
committed by
Gerrit Code Review
parent
e2f31dd268
commit
18a843eef7
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @fileoverview Implements an offline video source.
|
||||
*/
|
||||
|
||||
goog.provide('shaka.player.OfflineVideoSource');
|
||||
|
||||
goog.require('shaka.asserts');
|
||||
goog.require('shaka.dash.MpdProcessor');
|
||||
goog.require('shaka.dash.MpdRequest');
|
||||
goog.require('shaka.dash.mpd');
|
||||
goog.require('shaka.log');
|
||||
goog.require('shaka.media.EmeManager');
|
||||
goog.require('shaka.media.StreamInfo');
|
||||
goog.require('shaka.player.StreamVideoSource');
|
||||
goog.require('shaka.util.ContentDatabase');
|
||||
goog.require('shaka.util.TypedBind');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates an OfflineVideoSource.
|
||||
* @param {?number} groupId The unique ID of the group of streams
|
||||
* in this source.
|
||||
* @struct
|
||||
* @constructor
|
||||
* @extends {shaka.player.StreamVideoSource}
|
||||
* @export
|
||||
*/
|
||||
shaka.player.OfflineVideoSource = function(groupId) {
|
||||
shaka.player.StreamVideoSource.call(this, null);
|
||||
|
||||
/** @private {?number} */
|
||||
this.groupId_ = groupId;
|
||||
};
|
||||
goog.inherits(shaka.player.OfflineVideoSource, shaka.player.StreamVideoSource);
|
||||
|
||||
|
||||
/**
|
||||
* Stores the content described by the MPD for offline playback.
|
||||
* @param {string} mpdUrl The MPD URL.
|
||||
* @param {string} preferredLanguage The user's preferred language tag.
|
||||
* @param {?shaka.player.DashVideoSource.ContentProtectionCallback}
|
||||
* interpretContentProtection A callback to interpret the ContentProtection
|
||||
* elements in the MPD.
|
||||
* @return {!Promise.<number>} The group ID of the stored content.
|
||||
*/
|
||||
shaka.player.OfflineVideoSource.prototype.store = function(
|
||||
mpdUrl, preferredLanguage, interpretContentProtection) {
|
||||
var mpdRequest = new shaka.dash.MpdRequest(mpdUrl);
|
||||
|
||||
return mpdRequest.send().then(shaka.util.TypedBind(this,
|
||||
/** @param {!shaka.dash.mpd.Mpd} mpd */
|
||||
function(mpd) {
|
||||
var mpdProcessor =
|
||||
new shaka.dash.MpdProcessor(interpretContentProtection);
|
||||
mpdProcessor.process(mpd);
|
||||
|
||||
this.manifestInfo = mpdProcessor.manifestInfo;
|
||||
var baseClassLoad = shaka.player.StreamVideoSource.prototype.load;
|
||||
return baseClassLoad.call(this, preferredLanguage);
|
||||
})
|
||||
).then(shaka.util.TypedBind(this,
|
||||
function() {
|
||||
var fakeVideoElement = /** @type {!HTMLVideoElement} */ (
|
||||
document.createElement('video'));
|
||||
var emeManager =
|
||||
new shaka.media.EmeManager(this, fakeVideoElement, this);
|
||||
return emeManager.initialize();
|
||||
})
|
||||
).then(shaka.util.TypedBind(this,
|
||||
function() {
|
||||
var selectedStreams = [];
|
||||
// TODO (natalieharris) : Add EME support.
|
||||
// TODO(story 1890046): Support multiple periods.
|
||||
var duration = this.manifestInfo.periodInfos[0].duration;
|
||||
if (!duration) {
|
||||
shaka.log.warning('The duration of the stream being stored is null.');
|
||||
}
|
||||
// Choose the first stream set from each type.
|
||||
var streamSetInfos = [];
|
||||
var desiredTypes = ['audio', 'video'];
|
||||
// TODO (natalieharris) : Add text support.
|
||||
for (var i = 0; i < desiredTypes.length; ++i) {
|
||||
var type = desiredTypes[i];
|
||||
if (this.streamSetsByType[type]) {
|
||||
streamSetInfos.push(this.streamSetsByType[type][0]);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < streamSetInfos.length; ++i) {
|
||||
var streamSetInfo = streamSetInfos[i];
|
||||
shaka.asserts.assert(streamSetInfo.streamInfos.length > 0);
|
||||
var streamInfo = this.selectStreamInfo_(streamSetInfo);
|
||||
selectedStreams.push(streamInfo);
|
||||
}
|
||||
return this.insertGroup_(selectedStreams, duration);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Inserts a group of streams into the database.
|
||||
* @param {!Array.<!shaka.media.StreamInfo>} streamInfos The streams to insert.
|
||||
* @param {number} duration The duration of the entire stream.
|
||||
* @return {!Promise.<number>} The unique id assigned to the group.
|
||||
* @private
|
||||
*/
|
||||
shaka.player.OfflineVideoSource.prototype.insertGroup_ =
|
||||
function(streamInfos, duration) {
|
||||
var streamIds = [];
|
||||
var contentDatabase = new shaka.util.ContentDatabase(null);
|
||||
var p = contentDatabase.setUpDatabase();
|
||||
|
||||
// Insert each stream into the database.
|
||||
for (var i = 0; i < streamInfos.length; ++i) {
|
||||
var getSegmentIndex =
|
||||
shaka.media.StreamInfo.prototype.getSegmentIndex.bind(streamInfos[i]);
|
||||
var insertStream = (function(i) {
|
||||
return contentDatabase.insertStream(
|
||||
streamInfos[i].mimeType,
|
||||
streamInfos[i].codecs,
|
||||
duration,
|
||||
streamInfos[i].segmentIndex,
|
||||
streamInfos[i].segmentInitializationData);
|
||||
}).bind(this, i);
|
||||
|
||||
p = p.then(getSegmentIndex).then(insertStream).then(
|
||||
/** @param {number} streamId */
|
||||
function(streamId) {
|
||||
streamIds.push(streamId);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
// Insert information about the group of streams into the database and close
|
||||
// the connection.
|
||||
p = p.then(function() {
|
||||
return contentDatabase.insertGroup(streamIds);
|
||||
}).then(
|
||||
/** @param {number} groupId */
|
||||
function(groupId) {
|
||||
contentDatabase.closeDatabaseConnection();
|
||||
return Promise.resolve(groupId);
|
||||
}
|
||||
).catch(
|
||||
/** @param {Error} e */
|
||||
function(e) {
|
||||
contentDatabase.closeDatabaseConnection();
|
||||
return Promise.reject(e);
|
||||
});
|
||||
return p;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Selects which stream from a stream info set should be stored offline.
|
||||
* @param {!shaka.media.StreamSetInfo} streamSetInfo The stream set to select a
|
||||
* stream from.
|
||||
* @return {!shaka.media.StreamInfo}
|
||||
* @private
|
||||
*/
|
||||
shaka.player.OfflineVideoSource.prototype.selectStreamInfo_ =
|
||||
function(streamSetInfo) {
|
||||
shaka.asserts.assert(streamSetInfo.streamInfos.length > 0);
|
||||
var selected = streamSetInfo.streamInfos[0];
|
||||
|
||||
if (streamSetInfo.contentType == 'video') {
|
||||
streamSetInfo.streamInfos.sort(
|
||||
function(a, b) { return a.height - b.height });
|
||||
selected = streamSetInfo.streamInfos[0];
|
||||
for (var i = 1; i < streamSetInfo.streamInfos.length; ++i) {
|
||||
// Select stream with height closest to, but not exceeding 480.
|
||||
if (streamSetInfo.streamInfos[i].height > 480) {
|
||||
break;
|
||||
} else {
|
||||
selected = streamSetInfo.streamInfos[i];
|
||||
}
|
||||
}
|
||||
} else if (streamSetInfo.contentType == 'audio') {
|
||||
// Choose the middle stream from the available ones. If we have high,
|
||||
// medium, and low quality audio, this is medium. If we only have high
|
||||
// and low, this is high.
|
||||
var index = Math.floor(streamSetInfo.streamInfos.length / 2);
|
||||
selected = streamSetInfo.streamInfos[index];
|
||||
}
|
||||
return selected;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
shaka.player.OfflineVideoSource.prototype.destroy = function() {
|
||||
// TODO (natalieharris)
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
shaka.player.OfflineVideoSource.prototype.load = function(preferredLanguage) {
|
||||
// TODO (natalieharris)
|
||||
};
|
||||
@@ -81,9 +81,9 @@ shaka.player.StreamVideoSource = function(manifestInfo) {
|
||||
|
||||
/**
|
||||
* All usable stream sets. Mutually compatible within each type.
|
||||
* @private {!Object.<string, !Array.<!shaka.media.StreamSetInfo>>}
|
||||
* @protected {!Object.<string, !Array.<!shaka.media.StreamSetInfo>>}
|
||||
*/
|
||||
this.streamSetsByType_ = {};
|
||||
this.streamSetsByType = {};
|
||||
// TODO(story 1890046): Support multiple periods.
|
||||
|
||||
/** @private {!shaka.util.EventManager} */
|
||||
@@ -212,7 +212,7 @@ shaka.player.StreamVideoSource.prototype.load = function(preferredLanguage) {
|
||||
|
||||
/** @override */
|
||||
shaka.player.StreamVideoSource.prototype.getVideoTracks = function() {
|
||||
if (!this.streamSetsByType_['video']) {
|
||||
if (!this.streamSetsByType['video']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -223,8 +223,8 @@ shaka.player.StreamVideoSource.prototype.getVideoTracks = function() {
|
||||
/** @type {!Array.<!shaka.player.VideoTrack>} */
|
||||
var tracks = [];
|
||||
|
||||
for (var i = 0; i < this.streamSetsByType_['video'].length; ++i) {
|
||||
var streamSetInfo = this.streamSetsByType_['video'][i];
|
||||
for (var i = 0; i < this.streamSetsByType['video'].length; ++i) {
|
||||
var streamSetInfo = this.streamSetsByType['video'][i];
|
||||
for (var j = 0; j < streamSetInfo.streamInfos.length; ++j) {
|
||||
var streamInfo = streamSetInfo.streamInfos[j];
|
||||
|
||||
@@ -251,7 +251,7 @@ shaka.player.StreamVideoSource.prototype.getVideoTracks = function() {
|
||||
|
||||
/** @override */
|
||||
shaka.player.StreamVideoSource.prototype.getAudioTracks = function() {
|
||||
if (!this.streamSetsByType_['audio']) {
|
||||
if (!this.streamSetsByType['audio']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -262,8 +262,8 @@ shaka.player.StreamVideoSource.prototype.getAudioTracks = function() {
|
||||
/** @type {!Array.<!shaka.player.AudioTrack>} */
|
||||
var tracks = [];
|
||||
|
||||
for (var i = 0; i < this.streamSetsByType_['audio'].length; ++i) {
|
||||
var streamSetInfo = this.streamSetsByType_['audio'][i];
|
||||
for (var i = 0; i < this.streamSetsByType['audio'].length; ++i) {
|
||||
var streamSetInfo = this.streamSetsByType['audio'][i];
|
||||
var lang = streamSetInfo.lang;
|
||||
|
||||
for (var j = 0; j < streamSetInfo.streamInfos.length; ++j) {
|
||||
@@ -285,7 +285,7 @@ shaka.player.StreamVideoSource.prototype.getAudioTracks = function() {
|
||||
|
||||
/** @override */
|
||||
shaka.player.StreamVideoSource.prototype.getTextTracks = function() {
|
||||
if (!this.streamSetsByType_['text']) {
|
||||
if (!this.streamSetsByType['text']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -296,8 +296,8 @@ shaka.player.StreamVideoSource.prototype.getTextTracks = function() {
|
||||
/** @type {!Array.<!shaka.player.TextTrack>} */
|
||||
var tracks = [];
|
||||
|
||||
for (var i = 0; i < this.streamSetsByType_['text'].length; ++i) {
|
||||
var streamSetInfo = this.streamSetsByType_['text'][i];
|
||||
for (var i = 0; i < this.streamSetsByType['text'].length; ++i) {
|
||||
var streamSetInfo = this.streamSetsByType['text'][i];
|
||||
var lang = streamSetInfo.lang;
|
||||
|
||||
for (var j = 0; j < streamSetInfo.streamInfos.length; ++j) {
|
||||
@@ -349,30 +349,30 @@ shaka.player.StreamVideoSource.prototype.selectConfigurations =
|
||||
}
|
||||
|
||||
// Use the IDs to convert the map of configs into a map of stream sets.
|
||||
this.streamSetsByType_ = {};
|
||||
this.streamSetsByType = {};
|
||||
var types = configs.keys();
|
||||
for (var i = 0; i < types.length; ++i) {
|
||||
var type = types[i];
|
||||
var cfgList = configs.get(type);
|
||||
|
||||
this.streamSetsByType_[type] = [];
|
||||
this.streamSetsByType[type] = [];
|
||||
if (type == 'video') {
|
||||
// We only choose one video stream set.
|
||||
var id = cfgList[0].id;
|
||||
this.streamSetsByType_[type].push(streamSetsById[id]);
|
||||
this.streamSetsByType[type].push(streamSetsById[id]);
|
||||
} else if (type == 'audio') {
|
||||
// We choose mutually compatible stream sets for audio.
|
||||
var basicMimeType = cfgList[0].getBasicMimeType();
|
||||
for (var j = 0; j < cfgList.length; ++j) {
|
||||
var cfg = cfgList[j];
|
||||
if (cfg.getBasicMimeType() != basicMimeType) continue;
|
||||
this.streamSetsByType_[type].push(streamSetsById[cfg.id]);
|
||||
this.streamSetsByType[type].push(streamSetsById[cfg.id]);
|
||||
}
|
||||
} else {
|
||||
// We choose all stream sets otherwise.
|
||||
for (var j = 0; j < cfgList.length; ++j) {
|
||||
var id = cfgList[j].id;
|
||||
this.streamSetsByType_[type].push(streamSetsById[id]);
|
||||
this.streamSetsByType[type].push(streamSetsById[id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -380,7 +380,7 @@ shaka.player.StreamVideoSource.prototype.selectConfigurations =
|
||||
// Assume subs will be needed.
|
||||
this.subsNeeded_ = true;
|
||||
|
||||
var audioSets = this.streamSetsByType_['audio'];
|
||||
var audioSets = this.streamSetsByType['audio'];
|
||||
if (audioSets) {
|
||||
this.sortByLanguage_(audioSets);
|
||||
|
||||
@@ -398,7 +398,7 @@ shaka.player.StreamVideoSource.prototype.selectConfigurations =
|
||||
}
|
||||
}
|
||||
|
||||
var textSets = this.streamSetsByType_['text'];
|
||||
var textSets = this.streamSetsByType['text'];
|
||||
if (textSets) {
|
||||
this.sortByLanguage_(textSets);
|
||||
}
|
||||
@@ -482,13 +482,13 @@ shaka.player.StreamVideoSource.prototype.setRestrictions =
|
||||
*/
|
||||
shaka.player.StreamVideoSource.prototype.selectTrack_ =
|
||||
function(type, id, immediate) {
|
||||
if (!this.streamSetsByType_[type]) {
|
||||
if (!this.streamSetsByType[type]) {
|
||||
return false;
|
||||
}
|
||||
shaka.asserts.assert(this.streamsByType_[type]);
|
||||
|
||||
for (var i = 0; i < this.streamSetsByType_[type].length; ++i) {
|
||||
var streamSetInfo = this.streamSetsByType_[type][i];
|
||||
for (var i = 0; i < this.streamSetsByType[type].length; ++i) {
|
||||
var streamSetInfo = this.streamSetsByType[type][i];
|
||||
for (var j = 0; j < streamSetInfo.streamInfos.length; ++j) {
|
||||
var streamInfo = streamSetInfo.streamInfos[j];
|
||||
if (streamInfo.uniqueId == id) {
|
||||
@@ -565,8 +565,8 @@ shaka.player.StreamVideoSource.prototype.onMediaSourceOpen_ =
|
||||
var desiredTypes = ['audio', 'video', 'text'];
|
||||
for (var i = 0; i < desiredTypes.length; ++i) {
|
||||
var type = desiredTypes[i];
|
||||
if (this.streamSetsByType_[type]) {
|
||||
selectedStreamSetInfos.push(this.streamSetsByType_[type][0]);
|
||||
if (this.streamSetsByType[type]) {
|
||||
selectedStreamSetInfos.push(this.streamSetsByType[type][0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -264,8 +264,8 @@ shaka.util.ContentDatabase.prototype.insertGroup = function(streamIds) {
|
||||
* Inserts a stream into the database.
|
||||
* @param {string} mimeType The stream's mime type.
|
||||
* @param {string} codecs The stream's codecs.
|
||||
* @param {number} duration The stream's entire duration.
|
||||
* @param {!shaka.media.SegmentIndex} segmentIndex The stream's segment index.
|
||||
* @param {?number} duration The stream's entire duration.
|
||||
* @param {shaka.media.SegmentIndex} segmentIndex The stream's segment index.
|
||||
* @param {ArrayBuffer} initSegment The stream's segment of initialization data.
|
||||
* @return {!Promise.<number>} The unique id assigned to the stream.
|
||||
*/
|
||||
@@ -274,7 +274,7 @@ shaka.util.ContentDatabase.prototype.insertStream = function(mimeType,
|
||||
duration,
|
||||
segmentIndex,
|
||||
initSegment) {
|
||||
|
||||
shaka.asserts.assert(segmentIndex);
|
||||
shaka.asserts.assert(segmentIndex.getNumReferences() > 0);
|
||||
|
||||
var p = this.getNextId_(this.getIndexStore_());
|
||||
@@ -315,7 +315,7 @@ shaka.util.ContentDatabase.prototype.getNextId_ = function(store) {
|
||||
* Inserts a stream index into the stream index store.
|
||||
* @param {string} mimeType The stream's mime type.
|
||||
* @param {string} codecs The stream's codecs.
|
||||
* @param {number} duration The stream's entire duration.
|
||||
* @param {?number} duration The stream's entire duration.
|
||||
* @param {ArrayBuffer} initSegment The stream's segment of initialization data.
|
||||
* @param {!shaka.util.ContentDatabase.InsertStreamState} state The stream's
|
||||
* state information.
|
||||
|
||||
Reference in New Issue
Block a user