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.
8 Polymer('track-list', {
10 * Initializes an element. This method is called automatically when the
14 this.tracksObserver_ = new ArrayObserver(
16 this.tracksValueChanged_.bind(this));
18 window.addEventListener('resize', this.onWindowResize_.bind(this));
22 * Registers handlers for changing of external variables
25 'model.shuffle': 'onShuffleChanged',
29 * Model object of the Audio Player.
30 * @type {AudioPlayerModel}
36 * @type {Array.<AudioPlayer.TrackInfo>}
41 * Play order of the tracks. Each value is the index of 'this.tracks'.
42 * @type {Array.<number>}
47 * Track index of the current track.
48 * If the tracks property is empty, it should be -1. Otherwise, be a valid
53 currentTrackIndex: -1,
56 * Invoked when 'shuffle' property is changed.
57 * @param {boolean} oldValue Old value.
58 * @param {boolean} newValue New value.
60 onShuffleChanged: function(oldValue, newValue) {
61 this.generatePlayOrder(true /* keep the current track */);
65 * Invoked when the current track index is changed.
66 * @param {number} oldValue old value.
67 * @param {number} newValue new value.
69 currentTrackIndexChanged: function(oldValue, newValue) {
70 if (oldValue === newValue)
73 if (!isNaN(oldValue) && 0 <= oldValue && oldValue < this.tracks.length)
74 this.tracks[oldValue].active = false;
76 if (0 <= newValue && newValue < this.tracks.length) {
77 var currentPlayOrder = this.playOrder.indexOf(newValue);
78 if (currentPlayOrder !== -1) {
80 this.tracks[newValue].active = true;
82 this.ensureTrackInViewport_(newValue /* trackIndex */);
88 if (this.tracks.length === 0)
89 this.currentTrackIndex = -1;
91 this.generatePlayOrder(false /* no need to keep the current track */);
95 * Invoked when 'tracks' property is changed.
96 * @param {Array.<TrackInfo>} oldValue Old value.
97 * @param {Array.<TrackInfo>} newValue New value.
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.
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));
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;
115 // Reset play order and current index.
116 this.generatePlayOrder(false /* no need to keep the current track */);
119 this.currentTrackIndex = -1;
124 * Invoked when the value in the 'tracks' is changed.
125 * @param {Array.<Object>} splices The detail of the change.
127 tracksValueChanged_: function(splices) {
128 if (this.tracks.length === 0)
129 this.currentTrackIndex = -1;
131 this.tracks[this.currentTrackIndex].active = true;
135 * Invoked when the track element is clicked.
136 * @param {Event} event Click event.
138 trackClicked: function(event) {
139 var index = ~~event.currentTarget.getAttribute('index');
140 var track = this.tracks[index];
142 this.selectTrack(track);
146 * Invoked when the window is resized.
149 onWindowResize_: function() {
150 this.ensureTrackInViewport_(this.currentTrackIndex);
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.
158 ensureTrackInViewport_: function(trackIndex) {
159 var trackSelector = '.track[index="' + trackIndex + '"]';
160 var trackElement = this.querySelector(trackSelector);
162 var viewTop = this.scrollTop;
163 var viewHeight = this.clientHeight;
164 var elementTop = trackElement.offsetTop;
165 var elementHeight = trackElement.offsetHeight;
167 if (elementTop < viewTop) {
169 this.scrollTop = elementTop;
170 } else if (elementTop + elementHeight <= viewTop + viewHeight) {
171 // The entire element is in the viewport. Do nothing.
173 // Adjust the bottoms.
174 this.scrollTop = Math.max(0,
175 (elementTop + elementHeight - viewHeight));
181 * Invoked when the track element is clicked.
182 * @param {boolean} keepCurrentTrack Keep the current track or not.
184 generatePlayOrder: function(keepCurrentTrack) {
185 console.assert((keepCurrentTrack !== undefined),
186 'The argument "forward" is undefined');
188 if (this.tracks.length === 0) {
193 // Creates sequenced array.
196 map(function(unused, index) { return index; });
198 if (this.model && this.model.shuffle) {
199 // Randomizes the play order array (Schwarzian-transform algorithm).
203 return {weight: Math.random(), index: a};
205 sort(function(a, b) { return a.weight - b.weight }).
206 map(function(a) { return a.index });
208 if (keepCurrentTrack) {
209 // Puts the current track at the beginning of the play order.
211 this.playOrder.filter(function(value) {
212 return this.currentTrackIndex !== value;
214 this.playOrder.splice(0, 0, this.currentTrackIndex);
218 if (!keepCurrentTrack)
219 this.currentTrackIndex = this.playOrder[0];
223 * Sets the current track.
224 * @param {AudioPlayer.TrackInfo} track TrackInfo to be set as the current
227 selectTrack: function(track) {
229 for (var i = 0; i < this.tracks.length; i++) {
230 if (this.tracks[i].url === track.url) {
236 // TODO(yoshiki): Clean up the flow and the code around here.
237 if (this.currentTrackIndex == index)
238 this.replayCurrentTrack();
240 this.currentTrackIndex = index;
245 * Request to replay the current music.
247 replayCurrentTrack: function() {
252 * Returns the current track.
253 * @param {AudioPlayer.TrackInfo} track TrackInfo of the current track.
255 getCurrentTrack: function() {
256 if (this.tracks.length === 0)
259 return this.tracks[this.currentTrackIndex];
263 * Returns the next (or previous) track in the track list. If there is no
264 * next track, returns -1.
266 * @param {boolean} forward Specify direction: forward or previous mode.
267 * True: forward mode, false: previous mode.
268 * @param {boolean} cyclic Specify if cyclically or not: It true, the first
269 * track is succeeding to the last track, otherwise no track after the
271 * @return {number} The next track index.
273 getNextTrackIndex: function(forward, cyclic) {
274 if (this.tracks.length === 0)
277 var defaultTrackIndex =
278 forward ? this.playOrder[0] : this.playOrder[this.tracks.length - 1];
280 var currentPlayOrder = this.playOrder.indexOf(this.currentTrackIndex);
282 (0 <= currentPlayOrder && currentPlayOrder < this.tracks.length),
283 'Insufficient TrackList.playOrder. The current track is not on the ' +
286 var newPlayOrder = currentPlayOrder + (forward ? +1 : -1);
287 if (newPlayOrder === -1 || newPlayOrder === this.tracks.length)
288 return cyclic ? defaultTrackIndex : -1;
290 var newTrackIndex = this.playOrder[newPlayOrder];
292 (0 <= newTrackIndex && newTrackIndex < this.tracks.length),
293 'Insufficient TrackList.playOrder. New Play Order: ' + newPlayOrder);
295 return newTrackIndex;
297 }); // Polymer('track-list') block
298 })(); // Anonymous closure