Upstream version 7.35.139.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / 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 propertye 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 = '.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 =
201             this.playOrder.
202             map(function(a) {
203               return {weight: Math.random(), index: a};
204             }).
205             sort(function(a, b) { return a.weight - b.weight }).
206             map(function(a) { return a.index });
207
208         if (keepCurrentTrack) {
209           // Puts the current track at the beginning of the play order.
210           this.playOrder =
211               this.playOrder.filter(function(value) {
212                 return this.currentTrackIndex !== value;
213               }, this);
214           this.playOrder.splice(0, 0, this.currentTrackIndex);
215         }
216       }
217
218       if (!keepCurrentTrack)
219         this.currentTrackIndex = this.playOrder[0];
220     },
221
222     /**
223      * Sets the current track.
224      * @param {AudioPlayer.TrackInfo} track TrackInfo to be set as the current
225      *     track.
226      */
227     selectTrack: function(track) {
228       var index = -1;
229       for (var i = 0; i < this.tracks.length; i++) {
230         if (this.tracks[i].url === track.url) {
231           index = i;
232           break;
233         }
234       }
235       if (index >= 0) {
236         // TODO(yoshiki): Clean up the flow and the code around here.
237         if (this.currentTrackIndex == index)
238           this.replayCurrentTrack();
239         else
240           this.currentTrackIndex = index;
241       }
242     },
243
244     /**
245      * Request to replay the current music.
246      */
247     replayCurrentTrack: function() {
248       this.fire('replay');
249     },
250
251     /**
252      * Returns the current track.
253      * @param {AudioPlayer.TrackInfo} track TrackInfo of the current track.
254      */
255     getCurrentTrack: function() {
256       if (this.tracks.length === 0)
257         return null;
258
259       return this.tracks[this.currentTrackIndex];
260     },
261
262     /**
263      * Returns the next (or previous) track in the track list. If there is no
264      * next track, returns -1.
265      *
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
270      *     last.
271      * @return {number} The next track index.
272      */
273     getNextTrackIndex: function(forward, cyclic)  {
274       if (this.tracks.length === 0)
275         return -1;
276
277       var defaultTrackIndex =
278           forward ? this.playOrder[0] : this.playOrder[this.tracks.length - 1];
279
280       var currentPlayOrder = this.playOrder.indexOf(this.currentTrackIndex);
281       console.assert(
282           (0 <= currentPlayOrder && currentPlayOrder < this.tracks.length),
283           'Insufficient TrackList.playOrder. The current track is not on the ' +
284             'track list.');
285
286       var newPlayOrder = currentPlayOrder + (forward ? +1 : -1);
287       if (newPlayOrder === -1 || newPlayOrder === this.tracks.length)
288         return cyclic ? defaultTrackIndex : -1;
289
290       var newTrackIndex = this.playOrder[newPlayOrder];
291       console.assert(
292           (0 <= newTrackIndex && newTrackIndex < this.tracks.length),
293           'Insufficient TrackList.playOrder. New Play Order: ' + newPlayOrder);
294
295       return newTrackIndex;
296     },
297   });  // Polymer('track-list') block
298 })();  // Anonymous closure