Upstream version 10.39.225.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   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     var currentTrackIndex = this.currentTrackIndex;
265
266     var timerId = setTimeout(
267         function() {
268           // If the other timer is scheduled, do nothing.
269           if (this.autoAdvanceTimer_ !== timerId)
270             return;
271
272           this.autoAdvanceTimer_ = null;
273
274           // If the track has been changed since the advance was scheduled, do
275           // nothing.
276           if (this.currentTrackIndex !== currentTrackIndex)
277             return;
278
279           // We are advancing only if the next track is not known to be invalid.
280           // This prevents an endless auto-advancing in the case when all tracks
281           // are invalid (we will only visit each track once).
282           this.advance_(forward, repeat, true /* only if valid */);
283         }.bind(this),
284         3000);
285
286     this.autoAdvanceTimer_ = timerId;
287   },
288
289   /**
290    * Cancels the scheduled auto advance.
291    * @private
292    */
293   cancelAutoAdvance_: function() {
294     if (this.autoAdvanceTimer_) {
295       clearTimeout(this.autoAdvanceTimer_);
296       this.autoAdvanceTimer_ = null;
297     }
298   },
299
300   /**
301    * The index of the current track.
302    * If the list has no tracks, the value must be -1.
303    *
304    * @type {number}
305    */
306   get currentTrackIndex() {
307     return this.trackList.currentTrackIndex;
308   },
309   set currentTrackIndex(value) {
310     this.trackList.currentTrackIndex = value;
311   },
312
313   /**
314    * The list of the tracks in the playlist.
315    *
316    * When it changed, current operation including playback is stopped and
317    * restarts playback with new tracks if necessary.
318    *
319    * @type {Array.<AudioPlayer.TrackInfo>}
320    */
321   get tracks() {
322     return this.trackList ? this.trackList.tracks : null;
323   },
324   set tracks(tracks) {
325     if (this.trackList.tracks === tracks)
326       return;
327
328     this.cancelAutoAdvance_();
329
330     this.trackList.tracks = tracks;
331     var currentTrack = this.trackList.getCurrentTrack();
332     if (currentTrack && currentTrack.url != this.audioElement.src) {
333       this.audioElement.src = currentTrack.url;
334       this.audioElement.play();
335     }
336   },
337
338   /**
339    * Invoked when the audio player is being unloaded.
340    */
341   onPageUnload: function() {
342     this.audioElement.src = '';  // Hack to prevent crashing.
343   },
344
345   /**
346    * Invoked when the 'keydown' event is fired.
347    * @param {Event} event The event object.
348    */
349   onKeyDown_: function(event) {
350     switch (event.keyIdentifier) {
351       case 'Up':
352         if (this.audioController.volumeSliderShown && this.model.volume < 100)
353           this.model.volume += 1;
354         break;
355       case 'Down':
356         if (this.audioController.volumeSliderShown && this.model.volume > 0)
357           this.model.volume -= 1;
358         break;
359       case 'PageUp':
360         if (this.audioController.volumeSliderShown && this.model.volume < 91)
361           this.model.volume += 10;
362         break;
363       case 'PageDown':
364         if (this.audioController.volumeSliderShown && this.model.volume > 9)
365           this.model.volume -= 10;
366         break;
367       case 'MediaNextTrack':
368         this.onControllerNextClicked();
369         break;
370       case 'MediaPlayPause':
371         var playing = this.audioController.playing;
372         this.onControllerPlayingChanged(playing, !playing);
373         break;
374       case 'MediaPreviousTrack':
375         this.onControllerPreviousClicked();
376         break;
377       case 'MediaStop':
378         // TODO: Define "Stop" behavior.
379         break;
380     }
381   },
382 });