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