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

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

Powered by Google App Engine
This is Rietveld 408576698