Fix search results being clipped in app list.
[chromium-blink-merge.git] / ui / file_manager / audio_player / elements / audio_player.js
blob990513f2f2c04c422e111bca8a68983a5ab106e0
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.
5 /**
6  * @constructor
7  * @extends {PolymerElement}
8  */
9 var AudioPlayerElement = function() {};
11 AudioPlayerElement.prototype = {
12   // Child Elements
13   audioController: null,
14   audioElement: null,
15   trackList: null,
17   // Published values
18   playing: true,
19   currenttrackurl: '',
20   playcount: 0,
22   // Attributes of the element (lower characters only).
23   // These values must be used only to data binding and shouldn't be assigned
24   // any value nowhere except in the handler.
25   publish: {
26     playing: {
27       value: true,
28       reflect: true
29     },
30     currenttrackurl: {
31       value: '',
32       reflect: true
33     },
34     playcount: {
35       value: 0,
36       reflect: true
37     }
38   },
40   /**
41    * Model object of the Audio Player.
42    * @type {AudioPlayerModel}
43    */
44   model: null,
46   /**
47    * Initializes an element. This method is called automatically when the
48    * element is ready.
49    */
50   ready: function() {
51     this.audioController = this.$.audioController;
52     this.audioElement = this.$.audio;
53     this.trackList = this.$.trackList;
55     this.addEventListener('keydown', this.onKeyDown_.bind(this));
57     this.audioElement.volume = 0;  // Temporary initial volume.
58     this.audioElement.addEventListener('ended', this.onAudioEnded.bind(this));
59     this.audioElement.addEventListener('error', this.onAudioError.bind(this));
61     var onAudioStatusUpdatedBound = this.onAudioStatusUpdate_.bind(this);
62     this.audioElement.addEventListener('timeupdate', onAudioStatusUpdatedBound);
63     this.audioElement.addEventListener('ended', onAudioStatusUpdatedBound);
64     this.audioElement.addEventListener('play', onAudioStatusUpdatedBound);
65     this.audioElement.addEventListener('pause', onAudioStatusUpdatedBound);
66     this.audioElement.addEventListener('suspend', onAudioStatusUpdatedBound);
67     this.audioElement.addEventListener('abort', onAudioStatusUpdatedBound);
68     this.audioElement.addEventListener('error', onAudioStatusUpdatedBound);
69     this.audioElement.addEventListener('emptied', onAudioStatusUpdatedBound);
70     this.audioElement.addEventListener('stalled', onAudioStatusUpdatedBound);
71   },
73   /**
74    * Registers handlers for changing of external variables
75    */
76   observe: {
77     'trackList.currentTrackIndex': 'onCurrentTrackIndexChanged',
78     'audioController.playing': 'onControllerPlayingChanged',
79     'audioController.time': 'onControllerTimeChanged',
80     'model.volume': 'onVolumeChanged',
81   },
83   /**
84    * Invoked when trackList.currentTrackIndex is changed.
85    * @param {number} oldValue old value.
86    * @param {number} newValue new value.
87    */
88   onCurrentTrackIndexChanged: function(oldValue, newValue) {
89     var currentTrackUrl = '';
91     if (oldValue != newValue) {
92       var currentTrack = this.trackList.getCurrentTrack();
93       if (currentTrack && currentTrack.url != this.audioElement.src) {
94         this.audioElement.src = currentTrack.url;
95         currentTrackUrl = this.audioElement.src;
96         if (this.audioController.playing)
97           this.audioElement.play();
98       }
99     }
101     // The attributes may be being watched, so we change it at the last.
102     this.currenttrackurl = currentTrackUrl;
103   },
105   /**
106    * Invoked when audioController.playing is changed.
107    * @param {boolean} oldValue old value.
108    * @param {boolean} newValue new value.
109    */
110   onControllerPlayingChanged: function(oldValue, newValue) {
111     this.playing = newValue;
113     if (newValue) {
114       if (!this.audioElement.src) {
115         var currentTrack = this.trackList.getCurrentTrack();
116         if (currentTrack && currentTrack.url != this.audioElement.src) {
117           this.audioElement.src = currentTrack.url;
118         }
119       }
121       if (this.audioElement.src) {
122         this.currenttrackurl = this.audioElement.src;
123         this.audioElement.play();
124         return;
125       }
126     }
128     // When the new status is "stopped".
129     this.cancelAutoAdvance_();
130     this.audioElement.pause();
131     this.currenttrackurl = '';
132     this.lastAudioUpdateTime_ = null;
133   },
135   /**
136    * Invoked when audioController.volume is changed.
137    * @param {number} oldValue old value.
138    * @param {number} newValue new value.
139    */
140   onVolumeChanged: function(oldValue, newValue) {
141     this.audioElement.volume = newValue / 100;
142   },
144   /**
145    * Invoked when the model changed.
146    * @param {AudioPlayerModel} oldValue Old Value.
147    * @param {AudioPlayerModel} newValue New Value.
148    */
149   modelChanged: function(oldValue, newValue) {
150     this.trackList.model = newValue;
151     this.audioController.model = newValue;
153     // Invoke the handler manually.
154     this.onVolumeChanged(0, newValue.volume);
155   },
157   /**
158    * Invoked when audioController.time is changed.
159    * @param {number} oldValue old time (in ms).
160    * @param {number} newValue new time (in ms).
161    */
162   onControllerTimeChanged: function(oldValue, newValue) {
163     // Ignores updates from the audio element.
164     if (this.lastAudioUpdateTime_ === newValue)
165       return;
167     if (this.audioElement.readyState !== 0)
168       this.audioElement.currentTime = this.audioController.time / 1000;
169   },
171   /**
172    * Invoked when the next button in the controller is clicked.
173    * This handler is registered in the 'on-click' attribute of the element.
174    */
175   onControllerNextClicked: function() {
176     this.advance_(true /* forward */, true /* repeat */);
177   },
179   /**
180    * Invoked when the previous button in the controller is clicked.
181    * This handler is registered in the 'on-click' attribute of the element.
182    */
183   onControllerPreviousClicked: function() {
184     this.advance_(false /* forward */, true /* repeat */);
185   },
187   /**
188    * Invoked when the playback in the audio element is ended.
189    * This handler is registered in this.ready().
190    */
191   onAudioEnded: function() {
192     this.playcount++;
193     this.advance_(true /* forward */, this.model.repeat);
194   },
196   /**
197    * Invoked when the playback in the audio element gets error.
198    * This handler is registered in this.ready().
199    */
200   onAudioError: function() {
201     this.scheduleAutoAdvance_(true /* forward */, this.model.repeat);
202   },
204   /**
205    * Invoked when the time of playback in the audio element is updated.
206    * This handler is registered in this.ready().
207    * @private
208    */
209   onAudioStatusUpdate_: function() {
210     this.audioController.time =
211         (this.lastAudioUpdateTime_ = this.audioElement.currentTime * 1000);
212     this.audioController.duration = this.audioElement.duration * 1000;
213     this.audioController.playing = !this.audioElement.paused;
214   },
216   /**
217    * Invoked when receiving a request to replay the current music from the track
218    * list element.
219    */
220   onReplayCurrentTrack: function() {
221     // Changes the current time back to the beginning, regardless of the current
222     // status (playing or paused).
223     this.audioElement.currentTime = 0;
224     this.audioController.time = 0;
225   },
227   /**
228    * Goes to the previous or the next track.
229    * @param {boolean} forward True if next, false if previous.
230    * @param {boolean} repeat True if repeat-mode is enabled. False otherwise.
231    * @private
232    */
233   advance_: function(forward, repeat) {
234     this.cancelAutoAdvance_();
236     var nextTrackIndex = this.trackList.getNextTrackIndex(forward, true);
237     var isNextTrackAvailable =
238         (this.trackList.getNextTrackIndex(forward, repeat) !== -1);
240     this.audioController.playing = isNextTrackAvailable;
242     // If there is only a single file in the list, 'currentTrackInde' is not
243     // changed and the handler is not invoked. Instead, plays here.
244     // TODO(yoshiki): clean up the code around here.
245     if (isNextTrackAvailable &&
246         this.trackList.currentTrackIndex == nextTrackIndex) {
247       this.audioElement.play();
248     }
250     this.trackList.currentTrackIndex = nextTrackIndex;
251   },
253   /**
254    * Timeout ID of auto advance. Used internally in scheduleAutoAdvance_() and
255    *     cancelAutoAdvance_().
256    * @type {number?}
257    * @private
258    */
259   autoAdvanceTimer_: null,
261   /**
262    * Schedules automatic advance to the next track after a timeout.
263    * @param {boolean} forward True if next, false if previous.
264    * @param {boolean} repeat True if repeat-mode is enabled. False otherwise.
265    * @private
266    */
267   scheduleAutoAdvance_: function(forward, repeat) {
268     this.cancelAutoAdvance_();
269     var currentTrackIndex = this.currentTrackIndex;
271     var timerId = setTimeout(
272         function() {
273           // If the other timer is scheduled, do nothing.
274           if (this.autoAdvanceTimer_ !== timerId)
275             return;
277           this.autoAdvanceTimer_ = null;
279           // If the track has been changed since the advance was scheduled, do
280           // nothing.
281           if (this.currentTrackIndex !== currentTrackIndex)
282             return;
284           // We are advancing only if the next track is not known to be invalid.
285           // This prevents an endless auto-advancing in the case when all tracks
286           // are invalid (we will only visit each track once).
287           this.advance_(forward, repeat, true /* only if valid */);
288         }.bind(this),
289         3000);
291     this.autoAdvanceTimer_ = timerId;
292   },
294   /**
295    * Cancels the scheduled auto advance.
296    * @private
297    */
298   cancelAutoAdvance_: function() {
299     if (this.autoAdvanceTimer_) {
300       clearTimeout(this.autoAdvanceTimer_);
301       this.autoAdvanceTimer_ = null;
302     }
303   },
305   /**
306    * The index of the current track.
307    * If the list has no tracks, the value must be -1.
308    *
309    * @type {number}
310    */
311   get currentTrackIndex() {
312     return this.trackList.currentTrackIndex;
313   },
314   set currentTrackIndex(value) {
315     this.trackList.currentTrackIndex = value;
316   },
318   /**
319    * The list of the tracks in the playlist.
320    *
321    * When it changed, current operation including playback is stopped and
322    * restarts playback with new tracks if necessary.
323    *
324    * @type {Array.<AudioPlayer.TrackInfo>}
325    */
326   get tracks() {
327     return this.trackList ? this.trackList.tracks : null;
328   },
329   set tracks(tracks) {
330     if (this.trackList.tracks === tracks)
331       return;
333     this.cancelAutoAdvance_();
335     this.trackList.tracks = tracks;
336     var currentTrack = this.trackList.getCurrentTrack();
337     if (currentTrack && currentTrack.url != this.audioElement.src) {
338       this.audioElement.src = currentTrack.url;
339       this.audioElement.play();
340     }
341   },
343   /**
344    * Invoked when the audio player is being unloaded.
345    */
346   onPageUnload: function() {
347     this.audioElement.src = '';  // Hack to prevent crashing.
348   },
350   /**
351    * Invoked when the 'keydown' event is fired.
352    * @param {Event} event The event object.
353    */
354   onKeyDown_: function(event) {
355     switch (event.keyIdentifier) {
356       case 'Up':
357         if (this.audioController.volumeSliderShown && this.model.volume < 100)
358           this.model.volume += 1;
359         break;
360       case 'Down':
361         if (this.audioController.volumeSliderShown && this.model.volume > 0)
362           this.model.volume -= 1;
363         break;
364       case 'PageUp':
365         if (this.audioController.volumeSliderShown && this.model.volume < 91)
366           this.model.volume += 10;
367         break;
368       case 'PageDown':
369         if (this.audioController.volumeSliderShown && this.model.volume > 9)
370           this.model.volume -= 10;
371         break;
372       case 'MediaNextTrack':
373         this.onControllerNextClicked();
374         break;
375       case 'MediaPlayPause':
376         var playing = this.audioController.playing;
377         this.onControllerPlayingChanged(playing, !playing);
378         break;
379       case 'MediaPreviousTrack':
380         this.onControllerPreviousClicked();
381         break;
382       case 'MediaStop':
383         // TODO: Define "Stop" behavior.
384         break;
385     }
386   },
389 Polymer('audio-player', AudioPlayerElement.prototype);