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.
10 * @extends {PolymerElement}
12 var TrackListElement = function() {};
14 TrackListElement
.prototype = {
20 * @type {Array<AudioPlayer.TrackInfo>}
25 observer
: 'tracksChanged'
29 * Track index of the current track.
30 * If the tracks property is empty, it should be -1. Otherwise, be a valid
36 observer
: 'currentTrackIndexChanged',
41 * Whether shuffling play order is enabled or not.
46 observer
: 'shuffleChanged'
51 * Initializes an element. This method is called automatically when the
55 this.observeTrackList();
57 window
.addEventListener('resize', this.onWindowResize_
.bind(this));
60 observeTrackList: function() {
61 // Unobserve the previous track list.
62 if (this.unobserveTrackList_
)
63 this.unobserveTrackList_();
65 // Observe the new track list.
66 var observer
= this.tracksValueChanged_
.bind(this);
67 Array
.observe(this.tracks
, observer
);
69 // Set the function to unobserve it.
70 this.unobserveTrackList_ = function(tracks
, observer
) {
71 Array
.unobserve(tracks
, observer
);
72 }.bind(null, this.tracks
, observer
);
76 * Play order of the tracks. Each value is the index of 'this.tracks'.
77 * @type {Array<number>}
82 * Invoked when 'shuffle' property is changed.
83 * @param {boolean} newValue New value.
84 * @param {boolean} oldValue Old value.
86 shuffleChanged: function(newValue
, oldValue
) {
87 this.generatePlayOrder(true /* keep the current track */);
91 * Invoked when the current track index is changed.
92 * @param {number} newValue new value.
93 * @param {number} oldValue old value.
95 currentTrackIndexChanged: function(newValue
, oldValue
) {
96 if (oldValue
=== newValue
)
99 if (!isNaN(oldValue
) && 0 <= oldValue
&& oldValue
< this.tracks
.length
)
100 this.set('tracks.' + oldValue
+ '.active', false);
102 if (0 <= newValue
&& newValue
< this.tracks
.length
) {
103 var currentPlayOrder
= this.playOrder
.indexOf(newValue
);
104 if (currentPlayOrder
!== -1) {
106 this.set('tracks.' + newValue
+ '.active', true);
108 this.ensureTrackInViewport_(newValue
/* trackIndex */);
114 if (this.tracks
.length
=== 0)
115 this.currentTrackIndex
= -1;
117 this.generatePlayOrder(false /* no need to keep the current track */);
121 * Invoked when 'tracks' property is changed.
122 * @param {Array<AudioPlayer.TrackInfo>} newValue New value.
123 * @param {Array<AudioPlayer.TrackInfo>} oldValue Old value.
125 tracksChanged: function(newValue
, oldValue
) {
126 // Note: Sometimes both oldValue and newValue are null though the actual
127 // values are not null. Maybe it's a bug of Polymer.
129 // Re-register the observer of 'this.tracks'.
130 this.observeTrackList();
132 if (this.tracks
.length
!== 0) {
133 // Restore the active track.
134 if (this.currentTrackIndex
!== -1 &&
135 this.currentTrackIndex
< this.tracks
.length
) {
136 this.set('tracks.' + this.currentTrackIndex
+ '.active', true);
139 // Reset play order and current index.
140 this.generatePlayOrder(false /* no need to keep the current track */);
143 this.currentTrackIndex
= -1;
148 * Invoked when the value in the 'tracks' is changed.
149 * @param {Array<Object>} changes The detail of the change.
151 tracksValueChanged_: function(changes
) {
152 if (this.tracks
.length
=== 0)
153 this.currentTrackIndex
= -1;
155 this.set('tracks.' + this.currentTrackIndex
+ '.active', true);
159 * Invoked when the track element is clicked.
160 * @param {Event} event Click event.
162 trackClicked: function(event
) {
163 var index
= ~~event
.currentTarget
.getAttribute('index');
164 var track
= this.tracks
[index
];
166 this.selectTrack(track
);
170 * Invoked when the window is resized.
173 onWindowResize_: function() {
174 this.ensureTrackInViewport_(this.currentTrackIndex
);
178 * Scrolls the track list to ensure the given track in the viewport.
179 * @param {number} trackIndex The index of the track to be in the viewport.
182 ensureTrackInViewport_: function(trackIndex
) {
183 var trackSelector
= '::shadow .track[index="' + trackIndex
+ '"]';
184 var trackElement
= this.querySelector(trackSelector
);
186 var viewTop
= this.scrollTop
;
187 var viewHeight
= this.clientHeight
;
188 var elementTop
= trackElement
.offsetTop
;
189 var elementHeight
= trackElement
.offsetHeight
;
191 if (elementTop
< viewTop
) {
193 this.scrollTop
= elementTop
;
194 } else if (elementTop
+ elementHeight
<= viewTop
+ viewHeight
) {
195 // The entire element is in the viewport. Do nothing.
197 // Adjust the bottoms.
198 this.scrollTop
= Math
.max(0,
199 (elementTop
+ elementHeight
- viewHeight
));
205 * Invoked when the track element is clicked.
206 * @param {boolean} keepCurrentTrack Keep the current track or not.
208 generatePlayOrder: function(keepCurrentTrack
) {
209 console
.assert((keepCurrentTrack
!== undefined),
210 'The argument "forward" is undefined');
212 if (this.tracks
.length
=== 0) {
217 // Creates sequenced array.
220 map(function(unused
, index
) { return index
; });
223 // Randomizes the play order array (Schwarzian-transform algorithm).
224 this.playOrder
= this.playOrder
226 return {weight
: Math
.random(), index
: a
};
228 .sort(function(a
, b
) { return a
.weight
- b
.weight
})
229 .map(function(a
) { return a
.index
});
231 if (keepCurrentTrack
) {
232 // Puts the current track at the beginning of the play order.
233 this.playOrder
= this.playOrder
234 .filter(function(value
) {
235 return this.currentTrackIndex
!== value
;
237 this.playOrder
.splice(0, 0, this.currentTrackIndex
);
241 if (!keepCurrentTrack
)
242 this.currentTrackIndex
= this.playOrder
[0];
246 * Sets the current track.
247 * @param {AudioPlayer.TrackInfo} track TrackInfo to be set as the current
250 selectTrack: function(track
) {
252 for (var i
= 0; i
< this.tracks
.length
; i
++) {
253 if (this.tracks
[i
].url
=== track
.url
) {
259 // TODO(yoshiki): Clean up the flow and the code around here.
260 if (this.currentTrackIndex
== index
)
261 this.replayCurrentTrack();
263 this.currentTrackIndex
= index
;
268 * Request to replay the current music.
270 replayCurrentTrack: function() {
275 * Returns the current track.
276 * @return {AudioPlayer.TrackInfo} track TrackInfo of the current track.
278 getCurrentTrack: function() {
279 if (this.tracks
.length
=== 0)
282 return this.tracks
[this.currentTrackIndex
];
286 * Returns the next (or previous) track in the track list. If there is no
287 * next track, returns -1.
289 * @param {boolean} forward Specify direction: forward or previous mode.
290 * True: forward mode, false: previous mode.
291 * @param {boolean} cyclic Specify if cyclically or not: It true, the first
292 * track is succeeding to the last track, otherwise no track after the
294 * @return {number} The next track index.
296 getNextTrackIndex: function(forward
, cyclic
) {
297 if (this.tracks
.length
=== 0)
300 var defaultTrackIndex
=
301 forward
? this.playOrder
[0] : this.playOrder
[this.tracks
.length
- 1];
303 var currentPlayOrder
= this.playOrder
.indexOf(this.currentTrackIndex
);
305 (0 <= currentPlayOrder
&& currentPlayOrder
< this.tracks
.length
),
306 'Insufficient TrackList.playOrder. The current track is not on the ' +
309 var newPlayOrder
= currentPlayOrder
+ (forward
? +1 : -1);
310 if (newPlayOrder
=== -1 || newPlayOrder
=== this.tracks
.length
)
311 return cyclic
? defaultTrackIndex
: -1;
313 var newTrackIndex
= this.playOrder
[newPlayOrder
];
315 (0 <= newTrackIndex
&& newTrackIndex
< this.tracks
.length
),
316 'Insufficient TrackList.playOrder. New Play Order: ' + newPlayOrder
);
318 return newTrackIndex
;
320 }; // TrackListElement.prototype for 'track-list'
322 Polymer(TrackListElement
.prototype);
323 })(); // Anonymous closure