Skip to content

Commit

Permalink
Moved Legacy Variant Ids Resolution into DBEngine
Browse files Browse the repository at this point in the history
Before we added variants, there was no variant ids field on
StreamDB. To support legacy downloads, we created variants
for streams with no variantIds.

This change moves that conversion into DBEngine so that outside
DBEngine, streams will always have variantIds.

Change-Id: I85d050dac0b5f82fe00947380587dd7af4401dee
  • Loading branch information
vaage committed Dec 6, 2017
1 parent 0e43b62 commit 2f47e6d
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 119 deletions.
4 changes: 2 additions & 2 deletions externs/shaka/offline.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ shakaExtern.PeriodDB;
* encrypted: boolean,
* keyId: ?string,
* segments: !Array.<shakaExtern.SegmentDB>,
* variantIds: ?Array.<number>
* variantIds: !Array.<number>
* }}
*
* @property {number} id
Expand Down Expand Up @@ -197,7 +197,7 @@ shakaExtern.PeriodDB;
* The key ID this stream is encrypted with.
* @property {!Array.<shakaExtern.SegmentDB>} segments
* An array of segments that make up the stream
* @property {?Array.<number>} variantIds
* @property {!Array.<number>} variantIds
* An array of ids of variants the stream is a part of.
*/
shakaExtern.StreamDB;
Expand Down
108 changes: 102 additions & 6 deletions lib/offline/db_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,17 +188,23 @@ shaka.offline.DBEngine.prototype.destroy = function() {

/** @override */
shaka.offline.DBEngine.prototype.getManifest = function(key) {
return this.get_(
shaka.offline.DBEngine.Store_.MANIFEST,
key);
/** @const */
var Store = shaka.offline.DBEngine.Store_;

return this.get_(Store.MANIFEST, key).then(function(manifest) {
return manifest ?
shaka.offline.DBEngine.sanitizeManifest_(manifest) :
null;
});
};


/** @override */
shaka.offline.DBEngine.prototype.forEachManifest = function(each) {
return this.forEach_(
shaka.offline.DBEngine.Store_.MANIFEST,
each);
/** @const */
var Store = shaka.offline.DBEngine.Store_;

return this.forEach_(Store.MANIFEST, each);
};


Expand Down Expand Up @@ -565,3 +571,93 @@ shaka.offline.DBEngine.getLastId_ = function(store, onId) {
}
};
};


/**
* @param {!Object} manifest
* @return {!shakaExtern.ManifestDB}
* @private
*/
shaka.offline.DBEngine.sanitizeManifest_ = function(manifest) {
// Purposely do not use types here as it will allow us to "schrodinger's cat"
// the type until the end where we can conform the type to the final
// ManifestDB type.

/** @const */
var ContentType = shaka.util.ManifestParserUtils.ContentType;

// There are three cases:
// 1. All streams' variant ids are null
// 2. All streams' variant ids are non-null
// 3. Some streams' variant ids are null and other are non-null
// Case 3 is invalid and should never happen in production.

var allStreams = [];
manifest.periods.forEach(function(period) {
allStreams.push.apply(allStreams, period.streams);
});

var audioStreams = allStreams.filter(function(stream) {
return stream.contentType == ContentType.AUDIO;
});

var videoStreams = allStreams.filter(function(stream) {
return stream.contentType == ContentType.VIDEO;
});

var audioVideoStreams = [];
audioVideoStreams.push.apply(audioVideoStreams, audioStreams);
audioVideoStreams.push.apply(audioVideoStreams, videoStreams);

var allVariantIdsNull = allStreams.every(function(stream) {
var ids = stream.variantIds;
return ids == null;
});

var allVariantIdsNonNull = allStreams.every(function(stream) {
var ids = stream.variantIds;
return ids != null && ids != undefined;
});

// Case 3
goog.asserts.assert(
allVariantIdsNull || allVariantIdsNonNull,
'All variant ids should be null or non-null.');

// Convert Case 1 to Case 2
// TODO (vaage) : Move the conversion of case 1 to case 2 to database
// upgrade.
if (allVariantIdsNull) {
// Since all the variant ids are null, we need to first make them into
// valid arrays.
allStreams.forEach(function(stream) {
stream.variantIds = [];
});

/** @type {number} */
var currentVariantId = 0;

// It is not possible in the pre-variant world of shaka to have audio-only
// and video-only content mixed in with audio-video content. So we can
// assume that there is only audio-only or video-only if one group is empty.
if (audioStreams.length == 0 || videoStreams.length == 0) {
// Create all audio only and all video only variants.
audioVideoStreams.forEach(function(stream) {
stream.variantIds.push(currentVariantId);
currentVariantId++;
});
} else {
// Create all audio and video variants.
audioStreams.forEach(function(audio) {
videoStreams.forEach(function(video) {
audio.variantIds.push(currentVariantId);
video.variantIds.push(currentVariantId);

currentVariantId++;
});
});
}
}

return /** @type {shakaExtern.ManifestDB} */ (manifest);
};
58 changes: 3 additions & 55 deletions lib/offline/offline_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,66 +125,14 @@ shaka.offline.OfflineUtils.recreateVariants = function(
var MapUtils = shaka.util.MapUtils;
var OfflineUtils = shaka.offline.OfflineUtils;

// There are three cases:
// 1. All streams' variant ids are null
// 2. All streams' variant ids are non-null
// 3. Some streams' variant ids are null and other are non-null
// Case 3 is invalid and should never happen in production.
// Create a variant for each variant id.
/** @type {!Object.<number, shakaExtern.Variant>} */
var variantMap = {};

/** @type {!Array.<!shakaExtern.StreamDB>} */
var allStreams = [];
allStreams.push.apply(allStreams, audios);
allStreams.push.apply(allStreams, videos);

var allVariantIdsNull =
allStreams.every(function(stream) { return stream.variantIds == null; });

var allVariantIdsNonNull =
allStreams.every(function(stream) { return stream.variantIds != null; });

// Case 3
goog.asserts.assert(
allVariantIdsNull || allVariantIdsNonNull,
'All variant ids should be null or non-null.');

// Convert Case 1 to Case 2
// TODO (vaage) : Move the conversion of case 1 to case 2 to a storage upgrade
// section of code.
if (allVariantIdsNull) {
// Since all the variant ids are null, we need to first make them into
// valid arrays.
allStreams.forEach(function(stream) {
stream.variantIds = [];
});

/** @type {number} */
var currentVariantId = 0;

// It is not possible in the pre-variant world of shaka to have audio-only
// and video-only content mixed in with audio-video content. So we can
// assume that there is only audio-only or video-only if one group is empty.
if (audios.length == 0 || videos.length == 0) {
// Create all audio only and all video only variants.
allStreams.forEach(function(stream) {
stream.variantIds.push(currentVariantId);
currentVariantId++;
});
} else {
// Create all audio and video variants.
audios.forEach(function(audio) {
videos.forEach(function(video) {
audio.variantIds.push(currentVariantId);
video.variantIds.push(currentVariantId);

currentVariantId++;
});
});
}
}

// Create a variant for each variant id.
/** @type {!Object.<number, shakaExtern.Variant>} */
var variantMap = {};
allStreams.forEach(function(stream) {
stream.variantIds.forEach(function(id) {
if (!variantMap[id]) {
Expand Down
89 changes: 89 additions & 0 deletions test/offline/db_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
*/

describe('DBEngine', /** @suppress {accessControls} */ function() {
/** @const */
var ContentType = shaka.util.ManifestParserUtils.ContentType;

/** @const {string} */
var dbName = 'shaka-player-test-db';

Expand Down Expand Up @@ -208,6 +211,64 @@ describe('DBEngine', /** @suppress {accessControls} */ function() {
.then(done).catch(fail);
}));

// TODO : Remove this test once we drop support for DB Engine Version 1
it('fills missing variant ids for old manifests', checkAndRun(function(done) {
/** @type {number} */
var id = db.reserveManifestId();

// Create a manifest with two streams. One video and one audio. When db
// engine recreates the variant ids, it should pair them together into
// a variant.

/** @type {shakaExtern.ManifestDB} */
var originalManifest = createManifest(id);
originalManifest.periods.push({
startTime: 0,
streams: [
createStream(0, ContentType.AUDIO),
createStream(1, ContentType.AUDIO),
createStream(2, ContentType.VIDEO),
createStream(3, ContentType.VIDEO)
]
});

// Remove the variant ids field from all streams.
originalManifest.periods[0].streams.forEach(function(stream) {
delete stream['variantIds'];
expect(stream.variantIds).toBe(undefined);
});

Promise.resolve()
.then(function() {
return db.insertManifest(originalManifest);
})
.then(function() {
return db.getManifest(id);
})
.then(function(manifest) {
expect(manifest.periods.length).toBe(1);

var streams = manifest.periods[0].streams;
expect(streams.length).toBe(4);

// Create a mapping of variants to stream ids.
var variants = {};
streams.forEach(function(stream) {
stream.variantIds.forEach(function(id) {
variants[id] = variants[id] || [];
variants[id].push(stream.id);
});
});

shaka.log.info(variants);

expect(variants[0]).toEqual([0, 2]);
expect(variants[1]).toEqual([0, 3]);
expect(variants[2]).toEqual([1, 2]);
expect(variants[3]).toEqual([1, 3]);
}).then(done).catch(fail);
}));


/**
* Before running the test, check if DBEngine is supported on this platform.
Expand Down Expand Up @@ -244,6 +305,34 @@ describe('DBEngine', /** @suppress {accessControls} */ function() {
}


/**
* @param {number} id
* @param {string} type
* @return {shakaExtern.StreamDB}
*/
function createStream(id, type) {
return {
id: id,
primary: false,
presentationTimeOffset: 0,
contentType: type,
mimeType: '',
codecs: '',
frameRate: undefined,
kind: undefined,
language: '',
label: null,
width: null,
height: null,
initSegmentUri: null,
encrypted: false,
keyId: null,
segments: [],
variantIds: []
};
}


/**
* @param {number} id
* @return {shakaExtern.SegmentDataDB}
Expand Down
Loading

0 comments on commit 2f47e6d

Please sign in to comment.