1 // Copyright (c) 2012 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.
8 * @param {HTMLElement} container Container element.
11 function AudioPlayer(container) {
12 this.container_ = container;
13 this.volumeManager_ = new VolumeManagerWrapper(
14 VolumeManagerWrapper.DriveEnabledStatus.DRIVE_ENABLED);
15 this.metadataCache_ = MetadataCache.createFull(this.volumeManager_);
16 this.selectedEntry_ = null;
18 this.model_ = new AudioPlayerModel();
19 var observer = new PathObserver(this.model_, 'expanded');
20 observer.open(function(newValue, oldValue) {
21 // Inverse arguments intentionally to match the Polymer way.
22 this.onModelExpandedChanged(oldValue, newValue);
26 this.currentTrackIndex_ = -1;
27 this.playlistGeneration_ = 0;
30 * Whether if the playlist is expanded or not. This value is changed by
31 * this.syncExpanded().
32 * True: expanded, false: collapsed, null: unset.
37 this.isExpanded_ = null; // Initial value is null. It'll be set in load().
39 this.player_ = document.querySelector('audio-player');
40 // TODO(yoshiki): Move tracks into the model.
41 this.player_.tracks = [];
42 this.player_.model = this.model_;
43 Platform.performMicrotaskCheckpoint();
45 this.errorString_ = '';
46 this.offlineString_ = '';
47 chrome.fileManagerPrivate.getStrings(function(strings) {
48 container.ownerDocument.title = strings['AUDIO_PLAYER_TITLE'];
49 this.errorString_ = strings['AUDIO_ERROR'];
50 this.offlineString_ = strings['AUDIO_OFFLINE'];
51 AudioPlayer.TrackInfo.DEFAULT_ARTIST =
52 strings['AUDIO_PLAYER_DEFAULT_ARTIST'];
55 this.volumeManager_.addEventListener('externally-unmounted',
56 this.onExternallyUnmounted_.bind(this));
58 window.addEventListener('resize', this.onResize_.bind(this));
60 // Show the window after DOM is processed.
61 var currentWindow = chrome.app.window.current();
63 setTimeout(currentWindow.show.bind(currentWindow), 0);
67 * Initial load method (static).
69 AudioPlayer.load = function() {
70 document.ondragstart = function(e) { e.preventDefault() };
72 AudioPlayer.instance =
73 new AudioPlayer(document.querySelector('.audio-player'));
82 if (AudioPlayer.instance)
83 AudioPlayer.instance.onUnload();
90 AudioPlayer.instance.load(window.appState);
94 * Loads a new playlist.
95 * @param {Playlist} playlist Playlist object passed via mediaPlayerPrivate.
97 AudioPlayer.prototype.load = function(playlist) {
98 this.playlistGeneration_++;
99 this.currentTrackIndex_ = -1;
101 // Save the app state, in case of restart. Make a copy of the object, so the
102 // playlist member is not changed after entries are resolved.
103 window.appState = JSON.parse(JSON.stringify(playlist)); // cloning
106 this.isExpanded_ = this.model_.expanded;
108 // Resolving entries has to be done after the volume manager is initialized.
109 this.volumeManager_.ensureInitialized(function() {
110 util.URLsToEntries(playlist.items, function(entries) {
111 this.entries_ = entries;
113 var position = playlist.position || 0;
114 var time = playlist.time || 0;
116 if (this.entries_.length == 0)
120 var currentTracks = this.player_.tracks;
121 var unchanged = (currentTracks.length === this.entries_.length);
123 for (var i = 0; i != this.entries_.length; i++) {
124 var entry = this.entries_[i];
125 var onClick = this.select_.bind(this, i);
126 newTracks.push(new AudioPlayer.TrackInfo(entry, onClick));
128 if (unchanged && entry.toURL() !== currentTracks[i].url)
133 this.player_.tracks = newTracks;
135 // Makes it sure that the handler of the track list is called, before
136 // the handler of the track index.
137 Platform.performMicrotaskCheckpoint();
140 this.select_(position, !!time);
142 // Load the selected track metadata first, then load the rest.
143 this.loadMetadata_(position);
144 for (i = 0; i != this.entries_.length; i++) {
146 this.loadMetadata_(i);
153 * Loads metadata for a track.
154 * @param {number} track Track number.
157 AudioPlayer.prototype.loadMetadata_ = function(track) {
159 this.entries_[track], this.displayMetadata_.bind(this, track));
163 * Displays track's metadata.
164 * @param {number} track Track number.
165 * @param {Object} metadata Metadata object.
166 * @param {string=} opt_error Error message.
169 AudioPlayer.prototype.displayMetadata_ = function(track, metadata, opt_error) {
170 this.player_.tracks[track].setMetadata(metadata, opt_error);
174 * Closes audio player when a volume containing the selected item is unmounted.
175 * @param {Event} event The unmount event.
178 AudioPlayer.prototype.onExternallyUnmounted_ = function(event) {
179 if (!this.selectedEntry_)
182 if (this.volumeManager_.getVolumeInfo(this.selectedEntry_) ===
188 * Called on window is being unloaded.
190 AudioPlayer.prototype.onUnload = function() {
192 this.player_.onPageUnload();
194 if (this.volumeManager_)
195 this.volumeManager_.dispose();
199 * Selects a new track to play.
200 * @param {number} newTrack New track number.
201 * @param {number} time New playback position (in second).
204 AudioPlayer.prototype.select_ = function(newTrack, time) {
205 if (this.currentTrackIndex_ == newTrack) return;
207 this.currentTrackIndex_ = newTrack;
208 this.player_.currentTrackIndex = this.currentTrackIndex_;
209 this.player_.audioController.time = time;
210 Platform.performMicrotaskCheckpoint();
212 if (!window.appReopen)
213 this.player_.audioElement.play();
215 window.appState.position = this.currentTrackIndex_;
216 window.appState.time = 0;
219 var entry = this.entries_[this.currentTrackIndex_];
221 this.fetchMetadata_(entry, function(metadata) {
222 if (this.currentTrackIndex_ != newTrack)
225 this.selectedEntry_ = entry;
230 * @param {FileEntry} entry Track file entry.
231 * @param {function(object)} callback Callback.
234 AudioPlayer.prototype.fetchMetadata_ = function(entry, callback) {
235 this.metadataCache_.getOne(entry, 'thumbnail|media|external',
236 function(generation, metadata) {
237 // Do nothing if another load happened since the metadata request.
238 if (this.playlistGeneration_ == generation)
240 }.bind(this, this.playlistGeneration_));
244 * Media error handler.
247 AudioPlayer.prototype.onError_ = function() {
248 var track = this.currentTrackIndex_;
250 this.invalidTracks_[track] = true;
253 this.entries_[track],
255 var error = (!navigator.onLine && !metadata.external.present) ?
256 this.offlineString_ : this.errorString_;
257 this.displayMetadata_(track, metadata, error);
258 this.scheduleAutoAdvance_();
263 * Toggles the expanded mode when resizing.
265 * @param {Event} event Resize event.
268 AudioPlayer.prototype.onResize_ = function(event) {
269 if (!this.isExpanded_ &&
270 window.innerHeight >= AudioPlayer.EXPANDED_MODE_MIN_HEIGHT) {
271 this.isExpanded_ = true;
272 this.model_.expanded = true;
273 } else if (this.isExpanded_ &&
274 window.innerHeight < AudioPlayer.EXPANDED_MODE_MIN_HEIGHT) {
275 this.isExpanded_ = false;
276 this.model_.expanded = false;
280 /* Keep the below constants in sync with the CSS. */
283 * Window header size in pixels.
287 AudioPlayer.HEADER_HEIGHT = 33; // 32px + border 1px
290 * Track height in pixels.
294 AudioPlayer.TRACK_HEIGHT = 44;
297 * Controls bar height in pixels.
301 AudioPlayer.CONTROLS_HEIGHT = 73; // 72px + border 1px
304 * Default number of items in the expanded mode.
308 AudioPlayer.DEFAULT_EXPANDED_ITEMS = 5;
311 * Minimum size of the window in the expanded mode in pixels.
315 AudioPlayer.EXPANDED_MODE_MIN_HEIGHT = AudioPlayer.CONTROLS_HEIGHT +
316 AudioPlayer.TRACK_HEIGHT * 2;
319 * Invoked when the 'expanded' property in the model is changed.
320 * @param {boolean} oldValue Old value.
321 * @param {boolean} newValue New value.
323 AudioPlayer.prototype.onModelExpandedChanged = function(oldValue, newValue) {
324 if (this.isExpanded_ !== null &&
325 this.isExpanded_ === newValue)
328 if (this.isExpanded_ && !newValue)
329 this.lastExpandedHeight_ = window.innerHeight;
331 if (this.isExpanded_ !== newValue) {
332 this.isExpanded_ = newValue;
336 window.appState.expanded = newValue;
344 AudioPlayer.prototype.syncHeight_ = function() {
347 if (this.model_.expanded) {
349 if (!this.lastExpandedHeight_ ||
350 this.lastExpandedHeight_ < AudioPlayer.EXPANDED_MODE_MIN_HEIGHT) {
351 var expandedListHeight =
352 Math.min(this.entries_.length, AudioPlayer.DEFAULT_EXPANDED_ITEMS) *
353 AudioPlayer.TRACK_HEIGHT;
354 targetHeight = AudioPlayer.CONTROLS_HEIGHT + expandedListHeight;
355 this.lastExpandedHeight_ = targetHeight;
357 targetHeight = this.lastExpandedHeight_;
361 targetHeight = AudioPlayer.CONTROLS_HEIGHT + AudioPlayer.TRACK_HEIGHT;
364 window.resizeTo(window.innerWidth, targetHeight + AudioPlayer.HEADER_HEIGHT);
368 * Create a TrackInfo object encapsulating the information about one track.
370 * @param {fileEntry} entry FileEntry to be retrieved the track info from.
371 * @param {function} onClick Click handler.
374 AudioPlayer.TrackInfo = function(entry, onClick) {
375 this.url = entry.toURL();
376 this.title = this.getDefaultTitle();
377 this.artist = this.getDefaultArtist();
379 // TODO(yoshiki): implement artwork.
385 * @return {HTMLDivElement} The wrapper element for the track.
387 AudioPlayer.TrackInfo.prototype.getBox = function() { return this.box_ };
390 * @return {string} Default track title (file name extracted from the url).
392 AudioPlayer.TrackInfo.prototype.getDefaultTitle = function() {
393 var title = this.url.split('/').pop();
394 var dotIndex = title.lastIndexOf('.');
395 if (dotIndex >= 0) title = title.substr(0, dotIndex);
396 title = decodeURIComponent(title);
401 * TODO(kaznacheev): Localize.
403 AudioPlayer.TrackInfo.DEFAULT_ARTIST = 'Unknown Artist';
406 * @return {string} 'Unknown artist' string.
408 AudioPlayer.TrackInfo.prototype.getDefaultArtist = function() {
409 return AudioPlayer.TrackInfo.DEFAULT_ARTIST;
413 * @param {Object} metadata The metadata object.
414 * @param {string} error Error string.
416 AudioPlayer.TrackInfo.prototype.setMetadata = function(
418 // TODO(yoshiki): Handle error in better way.
419 // TODO(yoshiki): implement artwork (metadata.thumbnail)
420 this.title = (metadata.media && metadata.media.title) ||
421 this.getDefaultTitle();
422 this.artist = error ||
423 (metadata.media && metadata.media.artist) || this.getDefaultArtist();
426 // Starts loading the audio player.
427 window.addEventListener('polymer-ready', function(e) {