Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / ui / file_manager / file_manager / audio_player / elements / track_list.js
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