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