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 |