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.
7 Polymer('audio-player', {
11 audioController: null,
15 // Attributes of the element (little charactor only).
16 // These value must be used only to data binding and shouldn't be assignred
17 // anu value nowhere except in the handler.
22 * Model object of the Audio Player.
23 * @type {AudioPlayerModel}
28 * Initializes an element. This method is called automatically when the
32 this.audioController = this.$.audioController;
33 this.audioElement = this.$.audio;
34 this.trackList = this.$.trackList;
36 this.audioElement.volume = 0; // Temporaly initial volume.
37 this.audioElement.addEventListener('ended', this.onAudioEnded.bind(this));
38 this.audioElement.addEventListener('error', this.onAudioError.bind(this));
40 var onAudioStatusUpdatedBound = this.onAudioStatusUpdate_.bind(this);
41 this.audioElement.addEventListener('timeupdate', onAudioStatusUpdatedBound);
42 this.audioElement.addEventListener('ended', onAudioStatusUpdatedBound);
43 this.audioElement.addEventListener('play', onAudioStatusUpdatedBound);
44 this.audioElement.addEventListener('pause', onAudioStatusUpdatedBound);
45 this.audioElement.addEventListener('suspend', onAudioStatusUpdatedBound);
46 this.audioElement.addEventListener('abort', onAudioStatusUpdatedBound);
47 this.audioElement.addEventListener('error', onAudioStatusUpdatedBound);
48 this.audioElement.addEventListener('emptied', onAudioStatusUpdatedBound);
49 this.audioElement.addEventListener('stalled', onAudioStatusUpdatedBound);
53 * Registers handlers for changing of external variables
56 'trackList.currentTrackIndex': 'onCurrentTrackIndexChanged',
57 'audioController.playing': 'onControllerPlayingChanged',
58 'audioController.time': 'onControllerTimeChanged',
59 'model.volume': 'onVolumeChanged',
63 * Invoked when trackList.currentTrackIndex is changed.
64 * @param {number} oldValue old value.
65 * @param {number} newValue new value.
67 onCurrentTrackIndexChanged: function(oldValue, newValue) {
68 var currentTrackUrl = '';
70 if (oldValue != newValue) {
71 var currentTrack = this.trackList.getCurrentTrack();
72 if (currentTrack && currentTrack.url != this.audioElement.src) {
73 this.audioElement.src = currentTrack.url;
74 currentTrackUrl = this.audioElement.src;
75 if (this.audioController.playing)
76 this.audioElement.play();
80 // The attributes may be being watched, so we change it at the last.
81 this.currenttrackurl = currentTrackUrl;
85 * Invoked when audioController.playing is changed.
86 * @param {boolean} oldValue old value.
87 * @param {boolean} newValue new value.
89 onControllerPlayingChanged: function(oldValue, newValue) {
90 this.playing = newValue;
93 if (!this.audioElement.src) {
94 var currentTrack = this.trackList.getCurrentTrack();
95 if (currentTrack && currentTrack.url != this.audioElement.src) {
96 this.audioElement.src = currentTrack.url;
100 if (this.audioElement.src) {
101 this.currenttrackurl = this.audioElement.src;
102 this.audioElement.play();
107 // When the new status is "stopped".
108 this.cancelAutoAdvance_();
109 this.audioElement.pause();
110 this.currenttrackurl = '';
111 this.lastAudioUpdateTime_ = null;
115 * Invoked when audioController.volume is changed.
116 * @param {number} oldValue old value.
117 * @param {number} newValue new value.
119 onVolumeChanged: function(oldValue, newValue) {
120 this.audioElement.volume = newValue / 100;
124 * Invoked when the model changed.
125 * @param {AudioPlayerModel} oldValue Old Value.
126 * @param {AudioPlayerModel} newValue Nld Value.
128 modelChanged: function(oldValue, newValue) {
129 this.trackList.model = newValue;
130 this.audioController.model = newValue;
132 // Invoke the handler manually.
133 this.onVolumeChanged(0, newValue.volume);
137 * Invoked when audioController.time is changed.
138 * @param {number} oldValue old time (in ms).
139 * @param {number} newValue new time (in ms).
141 onControllerTimeChanged: function(oldValue, newValue) {
142 // Ignores updates from the audio element.
143 if (this.lastAudioUpdateTime_ === newValue)
146 if (this.audioElement.readyState !== 0)
147 this.audioElement.currentTime = this.audioController.time / 1000;
151 * Invoked when the next button in the controller is clicked.
152 * This handler is registered in the 'on-click' attribute of the element.
154 onControllerNextClicked: function() {
155 this.advance_(true /* forward */, true /* repeat */);
159 * Invoked when the previous button in the controller is clicked.
160 * This handler is registered in the 'on-click' attribute of the element.
162 onControllerPreviousClicked: function() {
163 this.advance_(false /* forward */, true /* repeat */);
167 * Invoked when the playback in the audio element is ended.
168 * This handler is registered in this.ready().
170 onAudioEnded: function() {
171 this.advance_(true /* forward */, this.model.repeat);
175 * Invoked when the playback in the audio element gets error.
176 * This handler is registered in this.ready().
178 onAudioError: function() {
179 this.scheduleAutoAdvance_(true /* forward */, this.model.repeat);
183 * Invoked when the time of playback in the audio element is updated.
184 * This handler is registered in this.ready().
187 onAudioStatusUpdate_: function() {
188 this.audioController.time =
189 (this.lastAudioUpdateTime_ = this.audioElement.currentTime * 1000);
190 this.audioController.duration = this.audioElement.duration * 1000;
191 this.audioController.playing = !this.audioElement.paused;
195 * Goes to the previous or the next track.
196 * @param {boolean} forward True if next, false if previous.
197 * @param {boolean} repeat True if repeat-mode is enabled. False otherwise.
200 advance_: function(forward, repeat) {
201 this.cancelAutoAdvance_();
203 var nextTrackIndex = this.trackList.getNextTrackIndex(forward, true);
204 var isNextTrackAvailable =
205 (this.trackList.getNextTrackIndex(forward, repeat) !== -1);
207 this.audioController.playing = isNextTrackAvailable;
208 this.trackList.currentTrackIndex = nextTrackIndex;
210 Platform.performMicrotaskCheckpoint();
214 * Timeout ID of auto advance. Used internally in scheduleAutoAdvance_() and
215 * cancelAutoAdvance_().
219 autoAdvanceTimer_: null,
222 * Schedules automatic advance to the next track after a timeout.
223 * @param {boolean} forward True if next, false if previous.
224 * @param {boolean} repeat True if repeat-mode is enabled. False otherwise.
227 scheduleAutoAdvance_: function(forward, repeat) {
228 this.cancelAutoAdvance_();
229 this.autoAdvanceTimer_ = setTimeout(
231 this.autoAdvanceTimer_ = null;
232 // We are advancing only if the next track is not known to be invalid.
233 // This prevents an endless auto-advancing in the case when all tracks
234 // are invalid (we will only visit each track once).
235 this.advance_(forward, repeat, true /* only if valid */);
241 * Cancels the scheduled auto advance.
244 cancelAutoAdvance_: function() {
245 if (this.autoAdvanceTimer_) {
246 clearTimeout(this.autoAdvanceTimer_);
247 this.autoAdvanceTimer_ = null;
252 * The index of the current track.
253 * If the list has no tracks, the value must be -1.
257 get currentTrackIndex() {
258 return this.trackList.currentTrackIndex;
260 set currentTrackIndex(value) {
261 this.trackList.currentTrackIndex = value;
265 * The list of the tracks in the playlist.
267 * When it changed, current operation including playback is stopped and
268 * restarts playback with new tracks if necessary.
270 * @type {Array.<AudioPlayer.TrackInfo>}
273 return this.trackList ? this.trackList.tracks : null;
276 if (this.trackList.tracks === tracks)
279 this.cancelAutoAdvance_();
281 this.trackList.tracks = tracks;
282 var currentTrack = this.trackList.getCurrentTrack();
283 if (currentTrack && currentTrack.url != this.audioElement.src) {
284 this.audioElement.src = currentTrack.url;
285 this.audioElement.play();
290 * Invoked when the audio player is being unloaded.
292 onPageUnload: function() {
293 this.audioElement.src = ''; // Hack to prevent crashing.