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.
6 * Overrided metadata worker's path.
10 ContentProvider.WORKER_SCRIPT = '/js/metadata_worker.js';
13 * @param {HTMLElement} container Container element.
16 function AudioPlayer(container) {
17 this.container_ = container;
18 this.volumeManager_ = new VolumeManagerWrapper(
19 VolumeManagerWrapper.DriveEnabledStatus.DRIVE_ENABLED);
20 this.metadataCache_ = MetadataCache.createFull(this.volumeManager_);
21 this.selectedEntry_ = null;
23 this.model_ = new AudioPlayerModel();
24 var observer = new PathObserver(this.model_, 'expanded');
25 observer.open(function(newValue, oldValue) {
26 // Inverse arguments intentionally to match the Polymer way.
27 this.onModelExpandedChanged(oldValue, newValue);
31 this.currentTrackIndex_ = -1;
32 this.playlistGeneration_ = 0;
35 * Whether if the playlist is expanded or not. This value is changed by
36 * this.syncExpanded().
37 * True: expanded, false: collapsed, null: unset.
42 this.isExpanded_ = null; // Initial value is null. It'll be set in load().
44 this.player_ = document.querySelector('audio-player');
45 // TODO(yoshiki): Move tracks into the model.
46 this.player_.tracks = [];
47 this.player_.model = this.model_;
48 Platform.performMicrotaskCheckpoint();
50 this.errorString_ = '';
51 this.offlineString_ = '';
52 chrome.fileManagerPrivate.getStrings(function(strings) {
53 container.ownerDocument.title = strings['AUDIO_PLAYER_TITLE'];
54 this.errorString_ = strings['AUDIO_ERROR'];
55 this.offlineString_ = strings['AUDIO_OFFLINE'];
56 AudioPlayer.TrackInfo.DEFAULT_ARTIST =
57 strings['AUDIO_PLAYER_DEFAULT_ARTIST'];
60 this.volumeManager_.addEventListener('externally-unmounted',
61 this.onExternallyUnmounted_.bind(this));
63 window.addEventListener('resize', this.onResize_.bind(this));
65 // Show the window after DOM is processed.
66 var currentWindow = chrome.app.window.current();
68 setTimeout(currentWindow.show.bind(currentWindow), 0);
72 * Initial load method (static).
74 AudioPlayer.load = function() {
75 document.ondragstart = function(e) { e.preventDefault() };
77 AudioPlayer.instance =
78 new AudioPlayer(document.querySelector('.audio-player'));
87 if (AudioPlayer.instance)
88 AudioPlayer.instance.onUnload();
95 AudioPlayer.instance.load(window.appState);
99 * Loads a new playlist.
100 * @param {Playlist} playlist Playlist object passed via mediaPlayerPrivate.
102 AudioPlayer.prototype.load = function(playlist) {
103 this.playlistGeneration_++;
104 this.currentTrackIndex_ = -1;
106 // Save the app state, in case of restart. Make a copy of the object, so the
107 // playlist member is not changed after entries are resolved.
108 window.appState = JSON.parse(JSON.stringify(playlist)); // cloning
111 this.isExpanded_ = this.model_.expanded;
113 // Resolving entries has to be done after the volume manager is initialized.
114 this.volumeManager_.ensureInitialized(function() {
115 util.URLsToEntries(playlist.items, function(entries) {
116 this.entries_ = entries;
118 var position = playlist.position || 0;
119 var time = playlist.time || 0;
121 if (this.entries_.length == 0)
125 var currentTracks = this.player_.tracks;
126 var unchanged = (currentTracks.length === this.entries_.length);
128 for (var i = 0; i != this.entries_.length; i++) {
129 var entry = this.entries_[i];
130 var onClick = this.select_.bind(this, i);
131 newTracks.push(new AudioPlayer.TrackInfo(entry, onClick));
133 if (unchanged && entry.toURL() !== currentTracks[i].url)
138 this.player_.tracks = newTracks;
140 // Makes it sure that the handler of the track list is called, before
141 // the handler of the track index.
142 Platform.performMicrotaskCheckpoint();
145 this.select_(position, !!time);
147 // Load the selected track metadata first, then load the rest.
148 this.loadMetadata_(position);
149 for (i = 0; i != this.entries_.length; i++) {
151 this.loadMetadata_(i);
158 * Loads metadata for a track.
159 * @param {number} track Track number.
162 AudioPlayer.prototype.loadMetadata_ = function(track) {
164 this.entries_[track], this.displayMetadata_.bind(this, track));
168 * Displays track's metadata.
169 * @param {number} track Track number.
170 * @param {Object} metadata Metadata object.
171 * @param {string=} opt_error Error message.
174 AudioPlayer.prototype.displayMetadata_ = function(track, metadata, opt_error) {
175 this.player_.tracks[track].setMetadata(metadata, opt_error);
179 * Closes audio player when a volume containing the selected item is unmounted.
180 * @param {Event} event The unmount event.
183 AudioPlayer.prototype.onExternallyUnmounted_ = function(event) {
184 if (!this.selectedEntry_)
187 if (this.volumeManager_.getVolumeInfo(this.selectedEntry_) ===
193 * Called on window is being unloaded.
195 AudioPlayer.prototype.onUnload = function() {
197 this.player_.onPageUnload();
199 if (this.volumeManager_)
200 this.volumeManager_.dispose();
204 * Selects a new track to play.
205 * @param {number} newTrack New track number.
206 * @param {number} time New playback position (in second).
209 AudioPlayer.prototype.select_ = function(newTrack, time) {
210 if (this.currentTrackIndex_ == newTrack) return;
212 this.currentTrackIndex_ = newTrack;
213 this.player_.currentTrackIndex = this.currentTrackIndex_;
214 this.player_.audioController.time = time;
215 Platform.performMicrotaskCheckpoint();
217 if (!window.appReopen)
218 this.player_.audioElement.play();
220 window.appState.position = this.currentTrackIndex_;
221 window.appState.time = 0;
224 var entry = this.entries_[this.currentTrackIndex_];
226 this.fetchMetadata_(entry, function(metadata) {
227 if (this.currentTrackIndex_ != newTrack)
230 this.selectedEntry_ = entry;
235 * @param {FileEntry} entry Track file entry.
236 * @param {function(object)} callback Callback.
239 AudioPlayer.prototype.fetchMetadata_ = function(entry, callback) {
240 this.metadataCache_.getOne(entry, 'thumbnail|media|external',
241 function(generation, metadata) {
242 // Do nothing if another load happened since the metadata request.
243 if (this.playlistGeneration_ == generation)
245 }.bind(this, this.playlistGeneration_));
249 * Media error handler.
252 AudioPlayer.prototype.onError_ = function() {
253 var track = this.currentTrackIndex_;
255 this.invalidTracks_[track] = true;
258 this.entries_[track],
260 var error = (!navigator.onLine && !metadata.external.present) ?
261 this.offlineString_ : this.errorString_;
262 this.displayMetadata_(track, metadata, error);
263 this.scheduleAutoAdvance_();
268 * Toggles the expanded mode when resizing.
270 * @param {Event} event Resize event.
273 AudioPlayer.prototype.onResize_ = function(event) {
274 if (!this.isExpanded_ &&
275 window.innerHeight >= AudioPlayer.EXPANDED_MODE_MIN_HEIGHT) {
276 this.isExpanded_ = true;
277 this.model_.expanded = true;
278 } else if (this.isExpanded_ &&
279 window.innerHeight < AudioPlayer.EXPANDED_MODE_MIN_HEIGHT) {
280 this.isExpanded_ = false;
281 this.model_.expanded = false;
285 /* Keep the below constants in sync with the CSS. */
288 * Window header size in pixels.
292 AudioPlayer.HEADER_HEIGHT = 33; // 32px + border 1px
295 * Track height in pixels.
299 AudioPlayer.TRACK_HEIGHT = 44;
302 * Controls bar height in pixels.
306 AudioPlayer.CONTROLS_HEIGHT = 73; // 72px + border 1px
309 * Default number of items in the expanded mode.
313 AudioPlayer.DEFAULT_EXPANDED_ITEMS = 5;
316 * Minimum size of the window in the expanded mode in pixels.
320 AudioPlayer.EXPANDED_MODE_MIN_HEIGHT = AudioPlayer.CONTROLS_HEIGHT +
321 AudioPlayer.TRACK_HEIGHT * 2;
324 * Invoked when the 'expanded' property in the model is changed.
325 * @param {boolean} oldValue Old value.
326 * @param {boolean} newValue New value.
328 AudioPlayer.prototype.onModelExpandedChanged = function(oldValue, newValue) {
329 if (this.isExpanded_ !== null &&
330 this.isExpanded_ === newValue)
333 if (this.isExpanded_ && !newValue)
334 this.lastExpandedHeight_ = window.innerHeight;
336 if (this.isExpanded_ !== newValue) {
337 this.isExpanded_ = newValue;
341 window.appState.expanded = newValue;
349 AudioPlayer.prototype.syncHeight_ = function() {
352 if (this.model_.expanded) {
354 if (!this.lastExpandedHeight_ ||
355 this.lastExpandedHeight_ < AudioPlayer.EXPANDED_MODE_MIN_HEIGHT) {
356 var expandedListHeight =
357 Math.min(this.entries_.length, AudioPlayer.DEFAULT_EXPANDED_ITEMS) *
358 AudioPlayer.TRACK_HEIGHT;
359 targetHeight = AudioPlayer.CONTROLS_HEIGHT + expandedListHeight;
360 this.lastExpandedHeight_ = targetHeight;
362 targetHeight = this.lastExpandedHeight_;
366 targetHeight = AudioPlayer.CONTROLS_HEIGHT + AudioPlayer.TRACK_HEIGHT;
369 window.resizeTo(window.innerWidth, targetHeight + AudioPlayer.HEADER_HEIGHT);
373 * Create a TrackInfo object encapsulating the information about one track.
375 * @param {fileEntry} entry FileEntry to be retrieved the track info from.
376 * @param {function} onClick Click handler.
379 AudioPlayer.TrackInfo = function(entry, onClick) {
380 this.url = entry.toURL();
381 this.title = this.getDefaultTitle();
382 this.artist = this.getDefaultArtist();
384 // TODO(yoshiki): implement artwork.
390 * @return {HTMLDivElement} The wrapper element for the track.
392 AudioPlayer.TrackInfo.prototype.getBox = function() { return this.box_ };
395 * @return {string} Default track title (file name extracted from the url).
397 AudioPlayer.TrackInfo.prototype.getDefaultTitle = function() {
398 var title = this.url.split('/').pop();
399 var dotIndex = title.lastIndexOf('.');
400 if (dotIndex >= 0) title = title.substr(0, dotIndex);
401 title = decodeURIComponent(title);
406 * TODO(kaznacheev): Localize.
408 AudioPlayer.TrackInfo.DEFAULT_ARTIST = 'Unknown Artist';
411 * @return {string} 'Unknown artist' string.
413 AudioPlayer.TrackInfo.prototype.getDefaultArtist = function() {
414 return AudioPlayer.TrackInfo.DEFAULT_ARTIST;
418 * @param {Object} metadata The metadata object.
419 * @param {string} error Error string.
421 AudioPlayer.TrackInfo.prototype.setMetadata = function(
423 // TODO(yoshiki): Handle error in better way.
424 // TODO(yoshiki): implement artwork (metadata.thumbnail)
425 this.title = (metadata.media && metadata.media.title) ||
426 this.getDefaultTitle();
427 this.artist = error ||
428 (metadata.media && metadata.media.artist) || this.getDefaultArtist();
431 // Starts loading the audio player.
432 window.addEventListener('polymer-ready', function(e) {