Skip to content

Commit

Permalink
Group Streams Before Downloading
Browse files Browse the repository at this point in the history
To make it easier to understand, we collect all the streams within a
period using a map. This ensures that we will have a set of streams.

We then download each stream in the map. Since the map is indexed by
stream id, we can easily connect the variants and streams.

Issue #1248

Change-Id: I65ae01733b5d9dbf8b6d73c37f802320fffa03d1
  • Loading branch information
vaage authored and joeyparrish committed May 9, 2018
1 parent b7cd984 commit 7a60119
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 63 deletions.
139 changes: 82 additions & 57 deletions lib/offline/storage.js
Expand Up @@ -34,6 +34,7 @@ goog.require('shaka.util.Functional');
goog.require('shaka.util.IDestroyable');
goog.require('shaka.util.LanguageUtils');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.MapUtils');
goog.require('shaka.util.StreamUtils');


Expand Down Expand Up @@ -838,67 +839,66 @@ shaka.offline.Storage.prototype.createPeriod_ = function(
'Multiple tracks of the same type/kind/language given.');
}

let streams = [];

for (let i = 0; i < chosenTracks.length; i++) {
let variant = StreamUtils.findVariantForTrack(period, chosenTracks[i]);
if (variant) {
// Make a rough estimation of the streams' bandwidth so the download
// manager can track the progress of the download.
let bandwidthEstimation;
if (variant.audio) {
// If the audio stream has already been added to the DB as part of
// another variant, add the ID to the list. Otherwise, add it to the DB.
let stream = streams.filter(function(s) {
return s.id == variant.audio.id;
})[0];
if (stream) {
stream.variantIds.push(variant.id);
} else {
// If the variant has both audio and video, roughly estimate them
// both to be 1/2 of the variant's bandwidth.
// If the variant only has one stream, that stream's bandwidth equals
// the bandwidth of the variant.
bandwidthEstimation =
variant.video ? variant.bandwidth / 2 : variant.bandwidth;
streams.push(this.createStream_(
manifest,
period,
variant.audio,
bandwidthEstimation,
variant.id));
}
}
if (variant.video) {
let stream = streams.filter(function(s) {
return s.id == variant.video.id;
})[0];
if (stream) {
stream.variantIds.push(variant.id);
} else {
bandwidthEstimation =
variant.audio ? variant.bandwidth / 2 : variant.bandwidth;
streams.push(this.createStream_(
manifest,
period,
variant.video,
bandwidthEstimation,
variant.id));
}
}
} else {
let textStream =
StreamUtils.findTextStreamForTrack(period, chosenTracks[i]);
goog.asserts.assert(
textStream, 'Could not find track with id ' + chosenTracks[i].id);
streams.push(this.createStream_(
manifest, period, textStream, 0 /* estimatedStreamBandwidth */));
let bandwidth = {};
chosenTracks.forEach((track) => {
if (track.type == 'text') {
// We assume that text has no bandwidth.
bandwidth[track.id] = 0;
}
}

if (track.type == 'variant') {
let variant = StreamUtils.findVariantForTrack(period, track);
let streams = [variant.audio, variant.video].filter((stream) => !!stream);

let bandwidthPerStream = variant.bandwidth / streams.length;
streams.forEach((stream) => {
bandwidth[stream.id] = bandwidth[stream.id] || bandwidthPerStream;
});
}
});

// Need a way to look up which streams should be downloaded. Use a map so
// that we can easily lookup if a stream should be downloaded just by
// checking if its id is in the map.
let idMap = {};
chosenTracks.forEach((track) => {
if (track.type == 'variant' && track.audioId != null) {
idMap[track.audioId] = true;
}
if (track.type == 'variant' && track.videoId != null) {
idMap[track.videoId] = true;
}
if (track.type == 'text') {
idMap[track.id] = true;
}
});

// Find the streams we want to download and create a stream db instance
// for each of them.
let streamDBs = {};
shaka.offline.Storage.getStreamSet_(manifest)
.filter((stream) => !!idMap[stream.id])
.forEach((stream) => {
streamDBs[stream.id] = this.createStream_(
manifest,
period,
stream,
bandwidth[stream.id]);
});

// Connect streams and variants together.
chosenTracks.forEach((track) => {
if (track.type == 'variant' && track.audioId != null) {
streamDBs[track.audioId].variantIds.push(track.id);
}
if (track.type == 'variant' && track.videoId != null) {
streamDBs[track.videoId].variantIds.push(track.id);
}
});

return {
startTime: period.startTime,
streams: streams
streams: shaka.util.MapUtils.values(streamDBs)
};
};

Expand Down Expand Up @@ -1108,4 +1108,29 @@ shaka.offline.Storage.lookForSimilarTracks_ = function(tracks) {
};


/**
* Get a collection of streams that are in the manifest. This collection will
* only have one instance of each stream (similar to a set).
*
* @param {shakaExtern.Manifest} manifest
* @return {!Array.<shakaExtern.Stream>}
* @private
*/
shaka.offline.Storage.getStreamSet_ = function(manifest) {
// Use a map so that we don't store duplicates. Since a stream's id should
// be unique within the manifest, we can use that as the key.
let map = {};

manifest.periods.forEach((period) => {
period.textStreams.forEach((text) => { map[text.id] = text; });
period.variants.forEach((variant) => {
if (variant.audio) { map[variant.audio.id] = variant.audio; }
if (variant.video) { map[variant.video.id] = variant.video; }
});
});

return shaka.util.MapUtils.values(map);
};


shaka.Player.registerSupportPlugin('offline', shaka.offline.Storage.support);
12 changes: 6 additions & 6 deletions test/offline/storage_unit.js
Expand Up @@ -584,19 +584,19 @@ describe('Storage', function() {
return fakeStorageEngine.getManifest(0);
})
.then(function(manifest) {
let stream1 = manifest.periods[0].streams[0];
let stream1 = manifest.periods[0].streams[1];
expect(stream1.initSegmentKey).toBe(null);
expect(stream1.segments.length).toBe(5);
expect(stream1.segments).toContain(makeSegment(0, 1, id1));
expect(stream1.segments).toContain(makeSegment(0, 1, id2));
expect(stream1.segments).toContain(makeSegment(1, 2, id3));
expect(stream1.segments).toContain(makeSegment(2, 3, id4));
expect(stream1.segments).toContain(makeSegment(3, 4, id5));
expect(stream1.segments).toContain(makeSegment(4, 5, id6));

let stream2 = manifest.periods[0].streams[1];
let stream2 = manifest.periods[0].streams[0];
expect(stream2.initSegmentKey).toBe(null);
expect(stream2.segments.length).toBe(1);
expect(stream2.segments).toContain(makeSegment(0, 1, id2));
expect(stream2.segments).toContain(makeSegment(0, 1, id1));

return fakeStorageEngine.getSegment(id4);
})
Expand Down Expand Up @@ -660,7 +660,7 @@ describe('Storage', function() {
expect(netEngine.request.calls.count()).toBe(1);
return fakeStorageEngine.getManifest(0);
}).then((manifest) => {
let stream = manifest.periods[0].streams[0];
let stream = manifest.periods[0].streams[1];
expect(stream.contentType).toBe('audio');
expect(stream.segments.length).toBe(0);
expect(stream.initSegmentKey).toBe(0);
Expand Down Expand Up @@ -692,7 +692,7 @@ describe('Storage', function() {
return fakeStorageEngine.getManifest(0);
})
.then(function(manifest) {
let stream = manifest.periods[0].streams[0];
let stream = manifest.periods[0].streams[1];
expect(stream.segments.length).toBe(3);
})
.catch(fail)
Expand Down

0 comments on commit 7a60119

Please sign in to comment.