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));
20 * Registers handlers for changing of external variables
23 'model.shuffle': 'onShuffleChanged',
27 * Model object of the Audio Player.
28 * @type {AudioPlayerModel}
34 * @type {Array.<AudioPlayer.TrackInfo>}
39 * Play order of the tracks. Each value is the index of 'this.tracks'.
40 * @type {Array.<number>}
45 * Track index of the current track.
46 * If the tracks propertye is empty, it should be -1. Otherwise, be a valid
51 currentTrackIndex: -1,
54 * Invoked when 'shuffle' property is changed.
55 * @param {boolean} oldValue Old value.
56 * @param {boolean} newValue New value.
58 onShuffleChanged: function(oldValue, newValue) {
59 this.generatePlayOrder(true /* keep the current track */);
63 * Invoked when the current track index is changed.
64 * @param {number} oldValue old value.
65 * @param {number} newValue new value.
67 currentTrackIndexChanged: function(oldValue, newValue) {
68 if (oldValue === newValue)
71 if (!isNaN(oldValue) && oldValue !== -1)
72 this.tracks[oldValue].active = false;
74 if (0 <= newValue && newValue < this.tracks.length) {
75 var currentPlayOrder = this.playOrder.indexOf(newValue);
76 if (currentPlayOrder !== -1) {
78 this.tracks[newValue].active = true;
80 var trackSelector = '.track[index="' + newValue + '"]';
81 var trackElement = this.impl.querySelector(trackSelector);
83 this.scrollTop = Math.max(
85 (trackElement.offsetTop + trackElement.offsetHeight -
93 if (this.tracks.length === 0)
94 this.currentTrackIndex = -1;
96 this.generatePlayOrder(false /* no need to keep the current track */);
100 * Invoked when 'tracks' property is clicked.
101 * @param {Event} event Click event.
103 tracksChanged: function(oldValue, newValue) {
104 // Note: Sometimes both oldValue and newValue are null though the actual
105 // values are not null. Maybe it's a bug of Polymer.
107 // Re-register the observer of 'this.tracks'.
108 this.tracksObserver_.close();
109 this.tracksObserver_ = new ArrayObserver(
111 this.tracksValueChanged_.bind(this));
113 // Reset play order and current index.
114 if (this.tracks.length !== 0)
115 this.generatePlayOrder(false /* no need to keep the current track */);
117 if (this.tracks.length === 0) {
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 track = event.target.templateInstance.model;
140 this.selectTrack(track);
144 * Invoked when the track element is clicked.
145 * @param {boolean} keepCurrentTrack Keep the current track or not.
147 generatePlayOrder: function(keepCurrentTrack) {
148 console.assert((keepCurrentTrack !== undefined),
149 'The argument "forward" is undefined');
151 if (this.tracks.length === 0) {
156 // Creates sequenced array.
159 map(function(unused, index) { return index; });
161 if (this.model && this.model.shuffle) {
162 // Randomizes the play order array (Schwarzian-transform algorithm).
166 return {weight: Math.random(), index: a};
168 sort(function(a, b) { return a.weight - b.weight }).
169 map(function(a) { return a.index });
171 if (keepCurrentTrack) {
172 // Puts the current track at the beginning of the play order.
174 this.playOrder.filter(function(value) {
175 return this.currentTrackIndex !== value;
177 this.playOrder.splice(0, 0, this.currentTrackIndex);
181 if (!keepCurrentTrack)
182 this.currentTrackIndex = this.playOrder[0];
186 * Sets the current track.
187 * @param {AudioPlayer.TrackInfo} track TrackInfo to be set as the current
190 selectTrack: function(track) {
192 for (var i = 0; i < this.tracks.length; i++) {
193 if (this.tracks[i].url === track.url) {
199 this.currentTrackIndex = index;
203 * Returns the current track.
204 * @param {AudioPlayer.TrackInfo} track TrackInfo of the current track.
206 getCurrentTrack: function() {
207 if (this.tracks.length === 0)
210 return this.tracks[this.currentTrackIndex];
214 * Returns the next (or previous) track in the track list. If there is no
215 * next track, returns -1.
217 * @param {boolean} forward Specify direction: forward or previous mode.
218 * True: forward mode, false: previous mode.
219 * @param {boolean} cyclic Specify if cyclically or not: It true, the first
220 * track is succeeding to the last track, otherwise no track after the
222 * @return {number} The next track index.
224 getNextTrackIndex: function(forward, cyclic) {
225 if (this.tracks.length === 0)
228 var defaultTrackIndex =
229 forward ? this.playOrder[0] : this.playOrder[this.tracks.length - 1];
231 var currentPlayOrder = this.playOrder.indexOf(this.currentTrackIndex);
233 (0 <= currentPlayOrder && currentPlayOrder < this.tracks.length),
234 'Insufficient TrackList.playOrder. The current track is not on the ' +
237 var newPlayOrder = currentPlayOrder + (forward ? +1 : -1);
238 if (newPlayOrder === -1 || newPlayOrder === this.tracks.length)
239 return cyclic ? defaultTrackIndex : -1;
241 var newTrackIndex = this.playOrder[newPlayOrder];
243 (0 <= newTrackIndex && newTrackIndex < this.tracks.length),
244 'Insufficient TrackList.playOrder. New Play Order: ' + newPlayOrder);
246 return newTrackIndex;
248 }); // Polymer('track-list') block
249 })(); // Anonymous closure