Files
shaka-player/lib/util/content_database.js
T
Jacob Trimble 899c322995 Added support for multiple BaseURL elements.
Added a new class called FailoverUri which handles multiple URLs and
switches to others when one fails.  This is also handles the request
itself.  This replaces SegmentMetadata as well as a number of manual
requests.

The MPD parser now produces arrays of URIs rather than just one.  The
MPD Processor then converts it to a FailoverUri inside the manifest.

Added unit tests to test the new functionality and updated the old ones
to the new changes.

This does not support failover on subtitles since subtitles are handled
by the browser.

Closes #68

Change-Id: I5410104827b9e4102b243444b1b5a3f01dcaf10d
2015-08-10 16:01:06 -07:00

323 lines
8.4 KiB
JavaScript

/**
* Copyright 2014 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 Stores and manages content streams in a database.
*/
goog.provide('shaka.util.ContentDatabase');
goog.require('shaka.asserts');
goog.require('shaka.util.FakeEventTarget');
goog.require('shaka.util.PublicPromise');
goog.require('shaka.util.TypedBind');
/**
* Creates a new ContentDatabase, which manages a database for reading and
* writing streams to and from persistent storage.
*
* @param {string} mode The I/O mode, which must be either 'readonly' or
* 'readwrite'.
* @param {shaka.util.FakeEventTarget} parent
*
* @constructor
* @struct
* @extends {shaka.util.FakeEventTarget}
*/
shaka.util.ContentDatabase = function(mode, parent) {
shaka.asserts.assert(mode == 'readonly' || mode == 'readwrite');
shaka.util.FakeEventTarget.call(this, parent);
/** @private {IDBDatabase} */
this.db_ = null;
/** @private {string} */
this.mode_ = mode;
};
goog.inherits(shaka.util.ContentDatabase, shaka.util.FakeEventTarget);
/**
* The name of the IndexedDb instance.
*
* @const {string}
*/
shaka.util.ContentDatabase.DB_NAME = 'content_database';
/**
* The current version of the database.
*
* @private {number}
* @const
*/
shaka.util.ContentDatabase.DB_VERSION_ = 1;
/**
* The name of the group store in the IndexedDb instance.
*
* @private {string}
* @const
*/
shaka.util.ContentDatabase.GROUP_STORE_ = 'group_store';
/**
* The name of the index store in the IndexedDb instance.
*
* @private {string}
* @const
*/
shaka.util.ContentDatabase.INDEX_STORE_ = 'stream_index_store';
/**
* The name of the content store in the IndexedDb instance.
*
* @private {string}
* @const
*/
shaka.util.ContentDatabase.CONTENT_STORE_ = 'content_store';
/**
* @typedef {{
* group_id: number,
* stream_ids: !Array.<number>,
* session_ids: !Array.<string>,
* duration: ?number,
* key_system: string
* }}
*/
shaka.util.ContentDatabase.GroupInformation;
/**
* @typedef {{
* stream_id: number,
* mime_type: string,
* codecs: string,
* init_segment: ArrayBuffer,
* references: !Array.<shaka.util.ContentDatabase.SegmentInformation>
* }}
*/
shaka.util.ContentDatabase.StreamIndex;
/**
* @typedef {{
* index: number,
* start_time: number,
* end_time: number,
* start_byte: number,
* url: string
* }}
*/
shaka.util.ContentDatabase.SegmentInformation;
/**
* Opens a connection to the database and sets up the database if required. If
* a new version number is given the onupgradeneeded event will be fired. Must
* be run before any operations can be performed on the database.
* The database will have the structure:
* Group Store: {
* group_id: number
* stream_ids: Array.<number>
* session_ids: Array.<string>
* }
* Index Store: {
* stream_id: number,
* mime_type: string,
* references: [{shaka.util.ContentDatabase.SegmentInformation}]
* }
* Content Store: {
* stream_id: number,
* segment_id: number,
* content: ArrayBuffer
* }
* @return {!Promise}
*/
shaka.util.ContentDatabase.prototype.setUpDatabase = function() {
if (!window.indexedDB) {
var error = new Error('Persistant storage requires IndexedDB support.');
return Promise.reject(error);
}
if (this.db_) {
var error = new Error('A database connection is already open.');
return Promise.reject(error);
}
var p = new shaka.util.PublicPromise();
var indexedDB = window.indexedDB;
var request = indexedDB.open(shaka.util.ContentDatabase.DB_NAME,
shaka.util.ContentDatabase.DB_VERSION_);
request.onupgradeneeded = shaka.util.TypedBind(this,
/** @param {!Event} e */
function(e) {
this.db_ = e.target.result;
this.createStore_(
shaka.util.ContentDatabase.GROUP_STORE_, {keyPath: 'group_id'});
this.createStore_(
shaka.util.ContentDatabase.INDEX_STORE_, {keyPath: 'stream_id'});
var contentStore = this.createStore_(
shaka.util.ContentDatabase.CONTENT_STORE_, {autoIncrement: 'true'});
contentStore.createIndex('segment',
['stream_id', 'segment_id'],
{unique: true});
contentStore.createIndex('stream',
'stream_id',
{unique: false});
});
request.onsuccess = shaka.util.TypedBind(this,
/** @param {!Event} e */
function(e) {
this.db_ = e.target.result;
p.resolve();
});
request.onerror = function(e) { p.reject(request.error); };
return p;
};
/**
* Closes the connection to the database.
*/
shaka.util.ContentDatabase.prototype.closeDatabaseConnection = function() {
if (this.db_) {
this.db_.close();
this.db_ = null;
}
};
/**
* Closes the connection to the database if required and then deletes the
* database. The database can only be deleted if there are no other connections
* to the database.
* @return {!Promise}
*/
shaka.util.ContentDatabase.prototype.deleteDatabase = function() {
var p = new shaka.util.PublicPromise();
this.closeDatabaseConnection();
var deleteRequest = window.indexedDB.deleteDatabase(
shaka.util.ContentDatabase.DB_NAME);
deleteRequest.onsuccess = function(e) {
shaka.asserts.assert(e.newVersion == null);
p.resolve();
};
deleteRequest.onerror = function(e) { p.reject(deleteRequest.error); };
return p;
};
/**
* Creates an object store in the database. It will replace any previous object
* store with the same name in this database.
* @param {string} name The unique name of the object store.
* @param {Object} options The options for this object store including
* keyPath and autoIncrement, other options will be ignored.
* @return {!IDBObjectStore}
* @private
*/
shaka.util.ContentDatabase.prototype.createStore_ = function(name, options) {
if (this.db_.objectStoreNames.contains(name)) {
this.db_.deleteObjectStore(name);
}
return this.db_.createObjectStore(name, options);
};
/**
* Opens a reference to the content store.
* @return {!IDBObjectStore} A reference to the content store.
* @protected
*/
shaka.util.ContentDatabase.prototype.getContentStore = function() {
return this.getStore_(shaka.util.ContentDatabase.CONTENT_STORE_);
};
/**
* Opens a reference to the index store.
* @return {!IDBObjectStore} A reference to the index store.
* @protected
*/
shaka.util.ContentDatabase.prototype.getIndexStore = function() {
return this.getStore_(shaka.util.ContentDatabase.INDEX_STORE_);
};
/**
* Opens a reference to the group store.
* @return {!IDBObjectStore} A reference to the group store.
* @protected
*/
shaka.util.ContentDatabase.prototype.getGroupStore = function() {
return this.getStore_(shaka.util.ContentDatabase.GROUP_STORE_);
};
/**
* Opens a reference to a store.
* @param {string} storeName The name of a store in the database.
* @return {!IDBObjectStore} A reference to a store.
* @private
*/
shaka.util.ContentDatabase.prototype.getStore_ = function(storeName) {
shaka.asserts.assert(this.db_, 'A database connection should be open.');
var trans = this.db_.transaction([storeName], this.mode_);
return trans.objectStore(storeName);
};
/**
* Retrieves an item from a store in the database.
* @param {!IDBObjectStore|!IDBIndex} store The store to request an item from.
* @param {number|!Array} id The unique id(s) of item in the store.
* @return {!Promise}
* @protected
*/
shaka.util.ContentDatabase.prototype.retrieveItem = function(
store, id) {
var p = new shaka.util.PublicPromise();
var request = store.get(id);
request.onerror = function(e) { p.reject(request.error); };
request.onsuccess = function() {
if (request.result) {
p.resolve(request.result);
} else {
var error = new Error('Item not found.');
error.type = 'storage';
p.reject(error);
}
};
return p;
};