1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 cr.define('media', function() { 6 7 /** 8 * This class represents a collection of non-intersecting ranges. Ranges 9 * specified by (start, end) can be added and removed at will. It is used to 10 * record which sections of a media file have been cached, e.g. the first and 11 * last few kB plus several MB in the middle. 12 * 13 * Example usage: 14 * someRange.add(0, 100); // Contains 0-100. 15 * someRange.add(150, 200); // Contains 0-100, 150-200. 16 * someRange.remove(25, 75); // Contains 0-24, 76-100, 150-200. 17 * someRange.add(25, 149); // Contains 0-200. 18 */ 19 function DisjointRangeSet() { 20 this.ranges_ = {}; 21 } 22 23 DisjointRangeSet.prototype = { 24 /** 25 * Deletes all ranges intersecting with (start ... end) and returns the 26 * extents of the cleared area. 27 * @param {int} start The start of the range to remove. 28 * @param {int} end The end of the range to remove. 29 * @param {int} sloppiness 0 removes only strictly overlapping ranges, and 30 * 1 removes adjacent ones. 31 * @return {Object} The start and end of the newly cleared range. 32 */ 33 clearRange: function(start, end, sloppiness) { 34 var ranges = this.ranges_; 35 var result = {start: start, end: end}; 36 37 for (var rangeStart in this.ranges_) { 38 rangeEnd = this.ranges_[rangeStart]; 39 // A range intersects another if its start lies within the other range 40 // or vice versa. 41 if ((rangeStart >= start && rangeStart <= (end + sloppiness)) || 42 (start >= rangeStart && start <= (rangeEnd + sloppiness))) { 43 delete ranges[rangeStart]; 44 result.start = Math.min(result.start, rangeStart); 45 result.end = Math.max(result.end, rangeEnd); 46 } 47 } 48 49 return result; 50 }, 51 52 /** 53 * Adds a range to this DisjointRangeSet. 54 * Joins adjacent and overlapping ranges together. 55 * @param {int} start The beginning of the range to add, inclusive. 56 * @param {int} end The end of the range to add, inclusive. 57 */ 58 add: function(start, end) { 59 if (end < start) 60 return; 61 62 // Remove all touching ranges. 63 result = this.clearRange(start, end, 1); 64 // Add back a single contiguous range. 65 this.ranges_[Math.min(start, result.start)] = Math.max(end, result.end); 66 }, 67 68 /** 69 * Combines a DisjointRangeSet with this one. 70 * @param {DisjointRangeSet} ranges A DisjointRangeSet to be squished into 71 * this one. 72 */ 73 merge: function(other) { 74 var ranges = this; 75 other.forEach(function(start, end) { ranges.add(start, end); }); 76 }, 77 78 /** 79 * Removes a range from this DisjointRangeSet. 80 * Will split existing ranges if necessary. 81 * @param {int} start The beginning of the range to remove, inclusive. 82 * @param {int} end The end of the range to remove, inclusive. 83 */ 84 remove: function(start, end) { 85 if (end < start) 86 return; 87 88 // Remove instersecting ranges. 89 result = this.clearRange(start, end, 0); 90 91 // Add back non-overlapping ranges. 92 if (result.start < start) 93 this.ranges_[result.start] = start - 1; 94 if (result.end > end) 95 this.ranges_[end + 1] = result.end; 96 }, 97 98 /** 99 * Iterates over every contiguous range in this DisjointRangeSet, calling a 100 * function for each (start, end). 101 * @param {function(int, int)} iterator The function to call on each range. 102 */ 103 forEach: function(iterator) { 104 for (var start in this.ranges_) 105 iterator(start, this.ranges_[start]); 106 }, 107 108 /** 109 * Maps this DisjointRangeSet to an array by calling a given function on the 110 * start and end of each contiguous range, sorted by start. 111 * @param {function(int, int)} mapper Maps a range to an array element. 112 * @return {Array} An array of each mapper(range). 113 */ 114 map: function(mapper) { 115 var starts = []; 116 for (var start in this.ranges_) 117 starts.push(parseInt(start)); 118 starts.sort(function(a, b) { 119 return a - b; 120 }); 121 122 var ranges = this.ranges_; 123 var results = starts.map(function(s) { 124 return mapper(s, ranges[s]); 125 }); 126 127 return results; 128 }, 129 130 /** 131 * Finds the maximum value present in any of the contained ranges. 132 * @return {int} The maximum value contained by this DisjointRangeSet. 133 */ 134 max: function() { 135 var max = -Infinity; 136 for (var start in this.ranges_) 137 max = Math.max(max, this.ranges_[start]); 138 return max; 139 }, 140 }; 141 142 return { 143 DisjointRangeSet: DisjointRangeSet 144 }; 145 }); 146