Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(423)

Side by Side Diff: chrome/browser/resources/file_manager/audio_player/js/audio_player.js

Issue 144883002: [Files.app] Initial implementation of new audio player (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Addressed comments Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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.
4
5 'use strict';
6
7 /**
8 * @param {HTMLElement} container Container element.
9 * @constructor
10 */
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
17 this.currentTrackIndex_ = -1;
18 this.playlistGeneration_ = 0;
19
20 this.player_ = document.querySelector('audio-player');
21 this.trackListItems_ = [];
22 this.player_.tracks = this.trackListItems_;
23 Platform.performMicrotaskCheckpoint();
24
25 this.errorString_ = '';
26 this.offlineString_ = '';
27 chrome.fileBrowserPrivate.getStrings(function(strings) {
28 container.ownerDocument.title = strings['AUDIO_PLAYER_TITLE'];
29 this.errorString_ = strings['AUDIO_ERROR'];
30 this.offlineString_ = strings['AUDIO_OFFLINE'];
31 AudioPlayer.TrackInfo.DEFAULT_ARTIST =
32 strings['AUDIO_PLAYER_DEFAULT_ARTIST'];
33 }.bind(this));
34
35 this.volumeManager_.addEventListener('externally-unmounted',
36 this.onExternallyUnmounted_.bind(this));
37
38 window.addEventListener('resize', this.onResize_.bind(this));
39
40 // Show the window after DOM is processed.
41 var currentWindow = chrome.app.window.current();
42 if (currentWindow)
43 setTimeout(currentWindow.show.bind(currentWindow), 0);
44 }
45
46 /**
47 * Initial load method (static).
48 */
49 AudioPlayer.load = function() {
50 document.ondragstart = function(e) { e.preventDefault() };
51
52 // TODO(mtomasz): Consider providing an exact size icon, instead of relying
53 // on downsampling by ash.
54 chrome.app.window.current().setIcon(
55 'audio_player/icons/audio-player-64.png');
56 AudioPlayer.instance =
57 new AudioPlayer(document.querySelector('.audio-player'));
58
59 reload();
60 };
61
62 /**
63 * Unload the player.
64 */
65 function unload() {
66 if (AudioPlayer.instance)
67 AudioPlayer.instance.onUnload();
68 }
69
70 /**
71 * Reload the player.
72 */
73 function reload() {
74 if (window.appState) {
75 util.saveAppState();
76 AudioPlayer.instance.load(window.appState);
77 return;
78 }
79 }
80
81 /**
82 * Load a new playlist.
83 * @param {Playlist} playlist Playlist object passed via mediaPlayerPrivate.
84 */
85 AudioPlayer.prototype.load = function(playlist) {
86 this.playlistGeneration_++;
87 this.currentTrackIndex_ = -1;
88
89 // Save the app state, in case of restart.
90 window.appState = playlist;
91 util.saveAppState();
92
93 // Resolving entries has to be done after the volume manager is initialized.
94 this.volumeManager_.ensureInitialized(function() {
95 util.URLsToEntries(playlist.items, function(entries) {
96 this.entries_ = entries;
97
98 var position = playlist.position || 0;
99 var time = playlist.time || 0;
100
101 this.syncHeight_();
102
103 if (this.entries_.length == 0)
104 return;
105
106 this.trackListItems_.splice(0);
107
108 for (var i = 0; i != this.entries_.length; i++) {
109 var entry = this.entries_[i];
110 var onClick = this.select_.bind(this, i, false /* no restore */);
111 this.trackListItems_.push(new AudioPlayer.TrackInfo(entry, onClick));
112 }
113
114 this.select_(position, !!time);
115
116 // Load the selected track metadata first, then load the rest.
117 this.loadMetadata_(position);
118 for (i = 0; i != this.entries_.length; i++) {
119 if (i != position)
120 this.loadMetadata_(i);
121 }
122 }.bind(this));
123 }.bind(this));
124 };
125
126 /**
127 * Load metadata for a track.
128 * @param {number} track Track number.
129 * @private
130 */
131 AudioPlayer.prototype.loadMetadata_ = function(track) {
132 this.fetchMetadata_(
133 this.entries_[track], this.displayMetadata_.bind(this, track));
134 };
135
136 /**
137 * Display track's metadata.
138 * @param {number} track Track number.
139 * @param {Object} metadata Metadata object.
140 * @param {string=} opt_error Error message.
141 * @private
142 */
143 AudioPlayer.prototype.displayMetadata_ = function(track, metadata, opt_error) {
144 this.trackListItems_[track].setMetadata(metadata, opt_error);
145 };
146
147 /**
148 * Closes audio player when a volume containing the selected item is unmounted.
149 * @param {Event} event The unmount event.
150 * @private
151 */
152 AudioPlayer.prototype.onExternallyUnmounted_ = function(event) {
153 if (!this.selectedItemFilesystemPath_)
154 return;
155 if (this.selectedItemFilesystemPath_.indexOf(event.mountPath) == 0)
156 close();
157 };
158
159 /**
160 * Called on window is being unloaded.
161 */
162 AudioPlayer.prototype.onUnload = function() {
163 if (this.volumeManager_)
164 this.volumeManager_.disose();
165 };
166
167 /**
168 * Select a new track to play.
169 * @param {number} newTrack New track number.
170 * @param {boolean=} opt_restoreState True if restoring the play state from URL.
171 * @private
172 */
173 AudioPlayer.prototype.select_ = function(newTrack, opt_restoreState) {
174 if (this.currentTrackIndex_ == newTrack) return;
175
176 this.currentTrackIndex_ = newTrack;
177 this.player_.currentTrackIndex = this.currentTrackIndex_;
178
179 if (window.appState) {
180 window.appState.position = this.currentTrackIndex_;
181 window.appState.time = 0;
182 util.saveAppState();
183 } else {
184 util.platform.setPreference(AudioPlayer.TRACK_KEY, this.currentTrackIndex_);
185 }
186
187 var entry = this.entries_[this.currentTrackIndex_];
188
189 this.fetchMetadata_(entry, function(metadata) {
190 if (this.currentTrackIndex_ != newTrack)
191 return;
192
193 // Resolve real filesystem path of the current audio file.
194 this.selectedItemFilesystemPath_ = null;
195 this.selectedItemFilesystemPath_ = entry.fullPath;
196 }.bind(this));
197 };
198
199 /**
200 * @param {FileEntry} entry Track file entry.
201 * @param {function(object)} callback Callback.
202 * @private
203 */
204 AudioPlayer.prototype.fetchMetadata_ = function(entry, callback) {
205 this.metadataCache_.get(entry, 'thumbnail|media|streaming',
206 function(generation, metadata) {
207 // Do nothing if another load happened since the metadata request.
208 if (this.playlistGeneration_ == generation)
209 callback(metadata);
210 }.bind(this, this.playlistGeneration_));
211 };
212
213 /**
214 * @return {boolean} True if the player is be displayed in compact mode.
215 * @private
216 */
217 AudioPlayer.prototype.isExpanded_ = function() {
218 return this.player_.isExpanded();
219 };
220
221 /**
222 * Media error handler.
223 * @private
224 */
225 AudioPlayer.prototype.onError_ = function() {
226 var track = this.currentTrackIndex_;
227
228 this.invalidTracks_[track] = true;
229
230 this.fetchMetadata_(
231 this.entries_[track],
232 function(metadata) {
233 var error = (!navigator.onLine && metadata.streaming) ?
234 this.offlineString_ : this.errorString_;
235 this.displayMetadata_(track, metadata, error);
236 this.scheduleAutoAdvance_();
237 }.bind(this));
238 };
239
240 /**
241 * Expand/collapse button click handler. Toggles the mode and updates the
242 * height of the window.
243 *
244 * @private
245 */
246 AudioPlayer.prototype.onExpandCollapse_ = function() {
247 if (this.isExpanded_) {
248 this.player_.expand(false);
249 } else {
250 this.player_.expand(true);
251 }
252 this.syncHeight_();
253 };
254
255 /**
256 * Toggles the expanded mode when resizing.
257 *
258 * @param {Event} event Resize event.
259 * @private
260 */
261 AudioPlayer.prototype.onResize_ = function(event) {
262 if (!this.isExpanded_ &&
263 window.innerHeight >= AudioPlayer.EXPANDED_MODE_MIN_HEIGHT) {
264 this.isExpanded_ = true;
265 this.player_.expand(true);
266 } else if (this.isExpanded_ &&
267 window.innerHeight < AudioPlayer.EXPANDED_MODE_MIN_HEIGHT) {
268 this.isExpanded_ = false;
269 this.player_.expand(false);
270 }
271 };
272
273 /* Keep the below constants in sync with the CSS. */
274
275 /**
276 * Window header size in pixels.
277 * @type {number}
278 * @const
279 */
280 AudioPlayer.HEADER_HEIGHT = 28;
281
282 /**
283 * Track height in pixels.
284 * @type {number}
285 * @const
286 */
287 AudioPlayer.TRACK_HEIGHT = 44;
288
289 /**
290 * Controls bar height in pixels.
291 * @type {number}
292 * @const
293 */
294 AudioPlayer.CONTROLS_HEIGHT = 72;
295
296 /**
297 * Default number of items in the expanded mode.
298 * @type {number}
299 * @const
300 */
301 AudioPlayer.DEFAULT_EXPANDED_ITEMS = 5;
302
303 /**
304 * Minimum size of the window in the expanded mode in pixels.
305 * @type {number}
306 * @const
307 */
308 AudioPlayer.EXPANDED_MODE_MIN_HEIGHT = AudioPlayer.CONTROLS_HEIGHT +
309 AudioPlayer.TRACK_HEIGHT * 2;
310
311 /**
312 * Set the correct player window height.
313 */
314 AudioPlayer.prototype.syncExpanded = function() {
315 if (this.isExpanded_ == this.player_.isExpanded())
316 return;
317
318 if (this.isExpanded_ && !this.player_.isExpanded())
319 this.lastExpandedHeight_ = window.innerHeight;
320
321 this.isExpanded_ = this.player_.isExpanded();
322 this.syncHeight_();
323 };
324
325 /**
326 * @private
327 */
328 AudioPlayer.prototype.syncHeight_ = function() {
329 var targetHeight;
330
331 if (this.isExpanded_) {
332 // Expanded.
333 if (!this.lastExpandedHeight_ ||
334 this.lastExpandedHeight_ < AudioPlayer.EXPANDED_MODE_MIN_HEIGHT) {
335 var expandedListHeight =
336 Math.min(this.entries_.length, AudioPlayer.DEFAULT_EXPANDED_ITEMS) *
337 AudioPlayer.TRACK_HEIGHT;
338 targetHeight = AudioPlayer.CONTROLS_HEIGHT + expandedListHeight;
339 } else {
340 targetHeight = this.lastExpandedHeight_;
341 }
342 } else {
343 // Not expaned.
344 targetHeight = AudioPlayer.CONTROLS_HEIGHT + AudioPlayer.TRACK_HEIGHT;
345 }
346
347 window.resizeTo(window.innerWidth, targetHeight + AudioPlayer.HEADER_HEIGHT);
348 };
349
350 /**
351 * Create a TrackInfo object encapsulating the information about one track.
352 *
353 * @param {fileEntry} entry FileEntry to be retrieved the track info from.
354 * @param {function} onClick Click handler.
355 * @constructor
356 */
357 AudioPlayer.TrackInfo = function(entry, onClick) {
358 this.url = entry.toURL();
359 this.title = PathUtil.basename(entry.name);
mtomasz 2014/01/27 01:09:24 PathUtil.basename is a no-op here. Please use entr
yoshiki 2014/01/27 07:12:08 Done.
360 this.artist = this.getDefaultArtist();
361
362 // TODO(yoshiki): implement artwork.
363 this.artwork = null;
364 this.active = false;
365 };
366
367 /**
368 * @return {HTMLDivElement} The wrapper element for the track.
369 */
370 AudioPlayer.TrackInfo.prototype.getBox = function() { return this.box_ };
371
372 /**
373 * @return {string} Default track title (file name extracted from the url).
374 */
375 AudioPlayer.TrackInfo.prototype.getDefaultTitle = function() {
376 var title = this.url.split('/').pop();
377 var dotIndex = title.lastIndexOf('.');
378 if (dotIndex >= 0) title = title.substr(0, dotIndex);
379 title = decodeURIComponent(title);
380 return title;
381 };
382
383 /**
384 * TODO(kaznacheev): Localize.
385 */
386 AudioPlayer.TrackInfo.DEFAULT_ARTIST = 'Unknown Artist';
387
388 /**
389 * @return {string} 'Unknown artist' string.
390 */
391 AudioPlayer.TrackInfo.prototype.getDefaultArtist = function() {
392 return AudioPlayer.TrackInfo.DEFAULT_ARTIST;
393 };
394
395 /**
396 * @param {Object} metadata The metadata object.
397 * @param {string} error Error string.
398 */
399 AudioPlayer.TrackInfo.prototype.setMetadata = function(
400 metadata, error) {
401 if (error) {
402 // TODO(yoshiki): Handle error in better way.
403 this.title = PathUtil.basename(entry.fullPath);
mtomasz 2014/01/27 01:09:24 entry.name can be used instead
yoshiki 2014/01/27 07:12:08 Done.
404 this.artist = this.getDefaultArtist();
405 } else if (metadata.thumbnail && metadata.thumbnail.url) {
406 // TODO(yoshiki): implement artwork.
407 }
408 this.title = (metadata.media && metadata.media.title) ||
409 this.getDefaultTitle();
410 this.artist = error ||
411 (metadata.media && metadata.media.artist) || this.getDefaultArtist();
412 };
413
414 window.addEventListener('WebComponentsReady', function(e) {
415 AudioPlayer.load();
416 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698