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