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.
10 * Flag whether the audio is playing or paused. True if playing, or false
15 observer
: 'playingChanged',
16 reflectToAttribute
: true
20 * Current elapsed time in the current music in millisecond.
24 observer
: 'timeChanged'
28 * Whether the shuffle button is ON.
32 observer
: 'shuffleChanged'
36 * Whether the repeat button is ON.
40 observer
: 'repeatChanged'
44 * The audio volume. 0 is silent, and 100 is maximum loud.
48 observer
: 'volumeChanged'
52 * Whether the expanded button is ON.
56 observer
: 'expandedChanged'
60 * Track index of the current track.
64 observer
: 'currentTrackIndexChanged'
68 * Model object of the Audio Player.
69 * @type {AudioPlayerModel}
74 observer
: 'modelChanged'
78 * URL of the current track. (exposed publicly for tests)
83 reflectToAttribute
: true
87 * The number of played tracks. (exposed publicly for tests)
92 reflectToAttribute
: true
97 * Handles change event for shuffle mode.
98 * @param {boolean} shuffle
100 shuffleChanged: function(shuffle
) {
102 this.model
.shuffle
= shuffle
;
106 * Handles change event for repeat mode.
107 * @param {boolean} repeat
109 repeatChanged: function(repeat
) {
111 this.model
.repeat
= repeat
;
115 * Handles change event for audio volume.
116 * @param {number} volume
118 volumeChanged: function(volume
) {
120 this.model
.volume
= volume
;
124 * Handles change event for expanded state of track list.
126 expandedChanged: function(expanded
) {
128 this.model
.expanded
= expanded
;
132 * Initializes an element. This method is called automatically when the
136 this.addEventListener('keydown', this.onKeyDown_
.bind(this));
138 this.$.audio
.volume
= 0; // Temporary initial volume.
139 this.$.audio
.addEventListener('ended', this.onAudioEnded
.bind(this));
140 this.$.audio
.addEventListener('error', this.onAudioError
.bind(this));
142 var onAudioStatusUpdatedBound
= this.onAudioStatusUpdate_
.bind(this);
143 this.$.audio
.addEventListener('timeupdate', onAudioStatusUpdatedBound
);
144 this.$.audio
.addEventListener('ended', onAudioStatusUpdatedBound
);
145 this.$.audio
.addEventListener('play', onAudioStatusUpdatedBound
);
146 this.$.audio
.addEventListener('pause', onAudioStatusUpdatedBound
);
147 this.$.audio
.addEventListener('suspend', onAudioStatusUpdatedBound
);
148 this.$.audio
.addEventListener('abort', onAudioStatusUpdatedBound
);
149 this.$.audio
.addEventListener('error', onAudioStatusUpdatedBound
);
150 this.$.audio
.addEventListener('emptied', onAudioStatusUpdatedBound
);
151 this.$.audio
.addEventListener('stalled', onAudioStatusUpdatedBound
);
155 * Invoked when trackList.currentTrackIndex is changed.
156 * @param {number} newValue new value.
157 * @param {number} oldValue old value.
159 currentTrackIndexChanged: function(newValue
, oldValue
) {
160 var currentTrackUrl
= '';
162 if (oldValue
!= newValue
) {
163 var currentTrack
= this.$.trackList
.getCurrentTrack();
164 if (currentTrack
&& currentTrack
.url
!= this.$.audio
.src
) {
165 this.$.audio
.src
= currentTrack
.url
;
166 currentTrackUrl
= this.$.audio
.src
;
172 // The attributes may be being watched, so we change it at the last.
173 this.currenttrackurl
= currentTrackUrl
;
177 * Invoked when playing is changed.
178 * @param {boolean} newValue new value.
179 * @param {boolean} oldValue old value.
181 playingChanged: function(newValue
, oldValue
) {
183 if (!this.$.audio
.src
) {
184 var currentTrack
= this.$.trackList
.getCurrentTrack();
185 if (currentTrack
&& currentTrack
.url
!= this.$.audio
.src
) {
186 this.$.audio
.src
= currentTrack
.url
;
190 if (this.$.audio
.src
) {
191 this.currenttrackurl
= this.$.audio
.src
;
197 // When the new status is "stopped".
198 this.cancelAutoAdvance_();
199 this.$.audio
.pause();
200 this.currenttrackurl
= '';
201 this.lastAudioUpdateTime_
= null;
205 * Invoked when the model changed.
206 * @param {AudioPlayerModel} newModel New model.
207 * @param {AudioPlayerModel} oldModel Old model.
209 modelChanged: function(newModel
, oldModel
) {
211 if (newModel
!== oldModel
&& newModel
) {
212 this.shuffle
= newModel
.shuffle
;
213 this.repeat
= newModel
.repeat
;
214 this.volume
= newModel
.volume
;
215 this.expanded
= newModel
.expanded
;
220 * Invoked when time is changed.
221 * @param {number} newValue new time (in ms).
222 * @param {number} oldValue old time (in ms).
224 timeChanged: function(newValue
, oldValue
) {
225 // Ignores updates from the audio element.
226 if (this.lastAudioUpdateTime_
=== newValue
)
229 if (this.$.audio
.readyState
!== 0)
230 this.$.audio
.currentTime
= this.time
/ 1000;
234 * Invoked when the next button in the controller is clicked.
235 * This handler is registered in the 'on-click' attribute of the element.
237 onControllerNextClicked: function() {
238 this.advance_(true /* forward */, true /* repeat */);
242 * Invoked when the previous button in the controller is clicked.
243 * This handler is registered in the 'on-click' attribute of the element.
245 onControllerPreviousClicked: function() {
246 this.advance_(false /* forward */, true /* repeat */);
250 * Invoked when the playback in the audio element is ended.
251 * This handler is registered in this.ready().
253 onAudioEnded: function() {
255 this.advance_(true /* forward */, this.repeat
);
259 * Invoked when the playback in the audio element gets error.
260 * This handler is registered in this.ready().
262 onAudioError: function() {
263 this.scheduleAutoAdvance_(true /* forward */, this.repeat
);
267 * Invoked when the time of playback in the audio element is updated.
268 * This handler is registered in this.ready().
271 onAudioStatusUpdate_: function() {
272 this.time
= (this.lastAudioUpdateTime_
= this.$.audio
.currentTime
* 1000);
273 this.duration
= this.$.audio
.duration
* 1000;
274 this.playing
= !this.$.audio
.paused
;
278 * Invoked when receiving a request to replay the current music from the track
281 onReplayCurrentTrack: function() {
282 // Changes the current time back to the beginning, regardless of the current
283 // status (playing or paused).
284 this.$.audio
.currentTime
= 0;
289 * Goes to the previous or the next track.
290 * @param {boolean} forward True if next, false if previous.
291 * @param {boolean} repeat True if repeat-mode is enabled. False otherwise.
294 advance_: function(forward
, repeat
) {
295 this.cancelAutoAdvance_();
297 var nextTrackIndex
= this.$.trackList
.getNextTrackIndex(forward
, true);
298 var isNextTrackAvailable
=
299 (this.$.trackList
.getNextTrackIndex(forward
, repeat
) !== -1);
301 this.playing
= isNextTrackAvailable
;
303 // If there is only a single file in the list, 'currentTrackInde' is not
304 // changed and the handler is not invoked. Instead, plays here.
305 // TODO(yoshiki): clean up the code around here.
306 if (isNextTrackAvailable
&&
307 this.$.trackList
.currentTrackIndex
== nextTrackIndex
) {
311 this.$.trackList
.currentTrackIndex
= nextTrackIndex
;
315 * Timeout ID of auto advance. Used internally in scheduleAutoAdvance_() and
316 * cancelAutoAdvance_().
320 autoAdvanceTimer_
: null,
323 * Schedules automatic advance to the next track after a timeout.
324 * @param {boolean} forward True if next, false if previous.
325 * @param {boolean} repeat True if repeat-mode is enabled. False otherwise.
328 scheduleAutoAdvance_: function(forward
, repeat
) {
329 this.cancelAutoAdvance_();
330 var currentTrackIndex
= this.currentTrackIndex
;
332 var timerId
= setTimeout(
334 // If the other timer is scheduled, do nothing.
335 if (this.autoAdvanceTimer_
!== timerId
)
338 this.autoAdvanceTimer_
= null;
340 // If the track has been changed since the advance was scheduled, do
342 if (this.currentTrackIndex
!== currentTrackIndex
)
345 // We are advancing only if the next track is not known to be invalid.
346 // This prevents an endless auto-advancing in the case when all tracks
347 // are invalid (we will only visit each track once).
348 this.advance_(forward
, repeat
);
352 this.autoAdvanceTimer_
= timerId
;
356 * Cancels the scheduled auto advance.
359 cancelAutoAdvance_: function() {
360 if (this.autoAdvanceTimer_
) {
361 clearTimeout(this.autoAdvanceTimer_
);
362 this.autoAdvanceTimer_
= null;
367 * The list of the tracks in the playlist.
369 * When it changed, current operation including playback is stopped and
370 * restarts playback with new tracks if necessary.
372 * @type {Array<TrackInfo>}
375 return this.$.trackList
? this.$.trackList
.tracks
: null;
378 if (this.$.trackList
.tracks
=== tracks
)
381 this.cancelAutoAdvance_();
383 this.$.trackList
.tracks
= tracks
;
384 var currentTrack
= this.$.trackList
.getCurrentTrack();
385 if (currentTrack
&& currentTrack
.url
!= this.$.audio
.src
) {
386 this.$.audio
.src
= currentTrack
.url
;
392 * Invoked when the audio player is being unloaded.
394 onPageUnload: function() {
395 this.$.audio
.src
= ''; // Hack to prevent crashing.
399 * Invoked when the 'keydown' event is fired.
400 * @param {Event} event The event object.
402 onKeyDown_: function(event
) {
403 switch (event
.keyIdentifier
) {
405 if (this.$.audioController
.volumeSliderShown
&& this.model
.volume
< 100)
406 this.model
.volume
+= 1;
409 if (this.$.audioController
.volumeSliderShown
&& this.model
.volume
> 0)
410 this.model
.volume
-= 1;
413 if (this.$.audioController
.volumeSliderShown
&& this.model
.volume
< 91)
414 this.model
.volume
+= 10;
417 if (this.$.audioController
.volumeSliderShown
&& this.model
.volume
> 9)
418 this.model
.volume
-= 10;
420 case 'MediaNextTrack':
421 this.onControllerNextClicked();
423 case 'MediaPlayPause':
424 this.playing
= !this.playing
;
426 case 'MediaPreviousTrack':
427 this.onControllerPreviousClicked();
430 // TODO: Define "Stop" behavior.
436 * Computes volume value for audio element. (should be in [0.0, 1.0])
437 * @param {number} volume Volume which is set in the UI. ([0, 100])
440 computeAudioVolume_: function(volume
) {