| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 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 (function() { | |
| 6 'use strict'; | |
| 7 | |
| 8 Polymer('track-list', { | |
| 9 /** | |
| 10 * Initializes an element. This method is called automatically when the | |
| 11 * element is ready. | |
| 12 */ | |
| 13 ready: function() { | |
| 14 this.tracksObserver_ = new ArrayObserver( | |
| 15 this.tracks, | |
| 16 this.tracksValueChanged_.bind(this)); | |
| 17 | |
| 18 window.addEventListener('resize', this.onWindowResize_.bind(this)); | |
| 19 }, | |
| 20 | |
| 21 /** | |
| 22 * Registers handlers for changing of external variables | |
| 23 */ | |
| 24 observe: { | |
| 25 'model.shuffle': 'onShuffleChanged', | |
| 26 }, | |
| 27 | |
| 28 /** | |
| 29 * Model object of the Audio Player. | |
| 30 * @type {AudioPlayerModel} | |
| 31 */ | |
| 32 model: null, | |
| 33 | |
| 34 /** | |
| 35 * List of tracks. | |
| 36 * @type {Array.<AudioPlayer.TrackInfo>} | |
| 37 */ | |
| 38 tracks: [], | |
| 39 | |
| 40 /** | |
| 41 * Play order of the tracks. Each value is the index of 'this.tracks'. | |
| 42 * @type {Array.<number>} | |
| 43 */ | |
| 44 playOrder: [], | |
| 45 | |
| 46 /** | |
| 47 * Track index of the current track. | |
| 48 * If the tracks property is empty, it should be -1. Otherwise, be a valid | |
| 49 * track number. | |
| 50 * | |
| 51 * @type {number} | |
| 52 */ | |
| 53 currentTrackIndex: -1, | |
| 54 | |
| 55 /** | |
| 56 * Invoked when 'shuffle' property is changed. | |
| 57 * @param {boolean} oldValue Old value. | |
| 58 * @param {boolean} newValue New value. | |
| 59 */ | |
| 60 onShuffleChanged: function(oldValue, newValue) { | |
| 61 this.generatePlayOrder(true /* keep the current track */); | |
| 62 }, | |
| 63 | |
| 64 /** | |
| 65 * Invoked when the current track index is changed. | |
| 66 * @param {number} oldValue old value. | |
| 67 * @param {number} newValue new value. | |
| 68 */ | |
| 69 currentTrackIndexChanged: function(oldValue, newValue) { | |
| 70 if (oldValue === newValue) | |
| 71 return; | |
| 72 | |
| 73 if (!isNaN(oldValue) && 0 <= oldValue && oldValue < this.tracks.length) | |
| 74 this.tracks[oldValue].active = false; | |
| 75 | |
| 76 if (0 <= newValue && newValue < this.tracks.length) { | |
| 77 var currentPlayOrder = this.playOrder.indexOf(newValue); | |
| 78 if (currentPlayOrder !== -1) { | |
| 79 // Success | |
| 80 this.tracks[newValue].active = true; | |
| 81 | |
| 82 this.ensureTrackInViewport_(newValue /* trackIndex */); | |
| 83 return; | |
| 84 } | |
| 85 } | |
| 86 | |
| 87 // Invalid index | |
| 88 if (this.tracks.length === 0) | |
| 89 this.currentTrackIndex = -1; | |
| 90 else | |
| 91 this.generatePlayOrder(false /* no need to keep the current track */); | |
| 92 }, | |
| 93 | |
| 94 /** | |
| 95 * Invoked when 'tracks' property is changed. | |
| 96 * @param {Array.<TrackInfo>} oldValue Old value. | |
| 97 * @param {Array.<TrackInfo>} newValue New value. | |
| 98 */ | |
| 99 tracksChanged: function(oldValue, newValue) { | |
| 100 // Note: Sometimes both oldValue and newValue are null though the actual | |
| 101 // values are not null. Maybe it's a bug of Polymer. | |
| 102 | |
| 103 // Re-register the observer of 'this.tracks'. | |
| 104 this.tracksObserver_.close(); | |
| 105 this.tracksObserver_ = new ArrayObserver(this.tracks); | |
| 106 this.tracksObserver_.open(this.tracksValueChanged_.bind(this)); | |
| 107 | |
| 108 if (this.tracks.length !== 0) { | |
| 109 // Restore the active track. | |
| 110 if (this.currentTrackIndex !== -1 && | |
| 111 this.currentTrackIndex < this.tracks.length) { | |
| 112 this.tracks[this.currentTrackIndex].active = true; | |
| 113 } | |
| 114 | |
| 115 // Reset play order and current index. | |
| 116 this.generatePlayOrder(false /* no need to keep the current track */); | |
| 117 } else { | |
| 118 this.playOrder = []; | |
| 119 this.currentTrackIndex = -1; | |
| 120 } | |
| 121 }, | |
| 122 | |
| 123 /** | |
| 124 * Invoked when the value in the 'tracks' is changed. | |
| 125 * @param {Array.<Object>} splices The detail of the change. | |
| 126 */ | |
| 127 tracksValueChanged_: function(splices) { | |
| 128 if (this.tracks.length === 0) | |
| 129 this.currentTrackIndex = -1; | |
| 130 else | |
| 131 this.tracks[this.currentTrackIndex].active = true; | |
| 132 }, | |
| 133 | |
| 134 /** | |
| 135 * Invoked when the track element is clicked. | |
| 136 * @param {Event} event Click event. | |
| 137 */ | |
| 138 trackClicked: function(event) { | |
| 139 var index = ~~event.currentTarget.getAttribute('index'); | |
| 140 var track = this.tracks[index]; | |
| 141 if (track) | |
| 142 this.selectTrack(track); | |
| 143 }, | |
| 144 | |
| 145 /** | |
| 146 * Invoked when the window is resized. | |
| 147 * @private | |
| 148 */ | |
| 149 onWindowResize_: function() { | |
| 150 this.ensureTrackInViewport_(this.currentTrackIndex); | |
| 151 }, | |
| 152 | |
| 153 /** | |
| 154 * Scrolls the track list to ensure the given track in the viewport. | |
| 155 * @param {number} trackIndex The index of the track to be in the viewport. | |
| 156 * @private | |
| 157 */ | |
| 158 ensureTrackInViewport_: function(trackIndex) { | |
| 159 var trackSelector = '::shadow .track[index="' + trackIndex + '"]'; | |
| 160 var trackElement = this.querySelector(trackSelector); | |
| 161 if (trackElement) { | |
| 162 var viewTop = this.scrollTop; | |
| 163 var viewHeight = this.clientHeight; | |
| 164 var elementTop = trackElement.offsetTop; | |
| 165 var elementHeight = trackElement.offsetHeight; | |
| 166 | |
| 167 if (elementTop < viewTop) { | |
| 168 // Adjust the tops. | |
| 169 this.scrollTop = elementTop; | |
| 170 } else if (elementTop + elementHeight <= viewTop + viewHeight) { | |
| 171 // The entire element is in the viewport. Do nothing. | |
| 172 } else { | |
| 173 // Adjust the bottoms. | |
| 174 this.scrollTop = Math.max(0, | |
| 175 (elementTop + elementHeight - viewHeight)); | |
| 176 } | |
| 177 } | |
| 178 }, | |
| 179 | |
| 180 /** | |
| 181 * Invoked when the track element is clicked. | |
| 182 * @param {boolean} keepCurrentTrack Keep the current track or not. | |
| 183 */ | |
| 184 generatePlayOrder: function(keepCurrentTrack) { | |
| 185 console.assert((keepCurrentTrack !== undefined), | |
| 186 'The argument "forward" is undefined'); | |
| 187 | |
| 188 if (this.tracks.length === 0) { | |
| 189 this.playOrder = []; | |
| 190 return; | |
| 191 } | |
| 192 | |
| 193 // Creates sequenced array. | |
| 194 this.playOrder = | |
| 195 this.tracks. | |
| 196 map(function(unused, index) { return index; }); | |
| 197 | |
| 198 if (this.model && this.model.shuffle) { | |
| 199 // Randomizes the play order array (Schwarzian-transform algorithm). | |
| 200 this.playOrder = this.playOrder | |
| 201 .map(function(a) { | |
| 202 return {weight: Math.random(), index: a}; | |
| 203 }) | |
| 204 .sort(function(a, b) { return a.weight - b.weight }) | |
| 205 .map(function(a) { return a.index }); | |
| 206 | |
| 207 if (keepCurrentTrack) { | |
| 208 // Puts the current track at the beginning of the play order. | |
| 209 this.playOrder = this.playOrder | |
| 210 .filter(function(value) { | |
| 211 return this.currentTrackIndex !== value; | |
| 212 }, this); | |
| 213 this.playOrder.splice(0, 0, this.currentTrackIndex); | |
| 214 } | |
| 215 } | |
| 216 | |
| 217 if (!keepCurrentTrack) | |
| 218 this.currentTrackIndex = this.playOrder[0]; | |
| 219 }, | |
| 220 | |
| 221 /** | |
| 222 * Sets the current track. | |
| 223 * @param {AudioPlayer.TrackInfo} track TrackInfo to be set as the current | |
| 224 * track. | |
| 225 */ | |
| 226 selectTrack: function(track) { | |
| 227 var index = -1; | |
| 228 for (var i = 0; i < this.tracks.length; i++) { | |
| 229 if (this.tracks[i].url === track.url) { | |
| 230 index = i; | |
| 231 break; | |
| 232 } | |
| 233 } | |
| 234 if (index >= 0) { | |
| 235 // TODO(yoshiki): Clean up the flow and the code around here. | |
| 236 if (this.currentTrackIndex == index) | |
| 237 this.replayCurrentTrack(); | |
| 238 else | |
| 239 this.currentTrackIndex = index; | |
| 240 } | |
| 241 }, | |
| 242 | |
| 243 /** | |
| 244 * Request to replay the current music. | |
| 245 */ | |
| 246 replayCurrentTrack: function() { | |
| 247 this.fire('replay'); | |
| 248 }, | |
| 249 | |
| 250 /** | |
| 251 * Returns the current track. | |
| 252 * @param {AudioPlayer.TrackInfo} track TrackInfo of the current track. | |
| 253 */ | |
| 254 getCurrentTrack: function() { | |
| 255 if (this.tracks.length === 0) | |
| 256 return null; | |
| 257 | |
| 258 return this.tracks[this.currentTrackIndex]; | |
| 259 }, | |
| 260 | |
| 261 /** | |
| 262 * Returns the next (or previous) track in the track list. If there is no | |
| 263 * next track, returns -1. | |
| 264 * | |
| 265 * @param {boolean} forward Specify direction: forward or previous mode. | |
| 266 * True: forward mode, false: previous mode. | |
| 267 * @param {boolean} cyclic Specify if cyclically or not: It true, the first | |
| 268 * track is succeeding to the last track, otherwise no track after the | |
| 269 * last. | |
| 270 * @return {number} The next track index. | |
| 271 */ | |
| 272 getNextTrackIndex: function(forward, cyclic) { | |
| 273 if (this.tracks.length === 0) | |
| 274 return -1; | |
| 275 | |
| 276 var defaultTrackIndex = | |
| 277 forward ? this.playOrder[0] : this.playOrder[this.tracks.length - 1]; | |
| 278 | |
| 279 var currentPlayOrder = this.playOrder.indexOf(this.currentTrackIndex); | |
| 280 console.assert( | |
| 281 (0 <= currentPlayOrder && currentPlayOrder < this.tracks.length), | |
| 282 'Insufficient TrackList.playOrder. The current track is not on the ' + | |
| 283 'track list.'); | |
| 284 | |
| 285 var newPlayOrder = currentPlayOrder + (forward ? +1 : -1); | |
| 286 if (newPlayOrder === -1 || newPlayOrder === this.tracks.length) | |
| 287 return cyclic ? defaultTrackIndex : -1; | |
| 288 | |
| 289 var newTrackIndex = this.playOrder[newPlayOrder]; | |
| 290 console.assert( | |
| 291 (0 <= newTrackIndex && newTrackIndex < this.tracks.length), | |
| 292 'Insufficient TrackList.playOrder. New Play Order: ' + newPlayOrder); | |
| 293 | |
| 294 return newTrackIndex; | |
| 295 }, | |
| 296 }); // Polymer('track-list') block | |
| 297 })(); // Anonymous closure | |
| OLD | NEW |