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

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: Make the script/css files flattened. Created 6 years, 10 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 * Unloads the player.
64 */
65 function unload() {
66 if (AudioPlayer.instance)
67 AudioPlayer.instance.onUnload();
68 }
69
70 /**
71 * Reloads 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 * Loads 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 * Loads 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 * Displays 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.player_)
164 this.player_.onPageUnload();
165
166 if (this.volumeManager_)
167 this.volumeManager_.dispose();
168 };
169
170 /**
171 * Selects a new track to play.
172 * @param {number} newTrack New track number.
173 * @param {boolean=} opt_restoreState True if restoring the play state from URL.
174 * @private
175 */
176 AudioPlayer.prototype.select_ = function(newTrack, opt_restoreState) {
177 if (this.currentTrackIndex_ == newTrack) return;
178
179 this.currentTrackIndex_ = newTrack;
180 this.player_.currentTrackIndex = this.currentTrackIndex_;
181
182 if (window.appState) {
183 window.appState.position = this.currentTrackIndex_;
184 window.appState.time = 0;
185 util.saveAppState();
186 } else {
187 util.platform.setPreference(AudioPlayer.TRACK_KEY, this.currentTrackIndex_);
188 }
189
190 var entry = this.entries_[this.currentTrackIndex_];
191
192 this.fetchMetadata_(entry, function(metadata) {
193 if (this.currentTrackIndex_ != newTrack)
194 return;
195
196 // Resolve real filesystem path of the current audio file.
197 this.selectedItemFilesystemPath_ = null;
198 this.selectedItemFilesystemPath_ = entry.fullPath;
199 }.bind(this));
200 };
201
202 /**
203 * @param {FileEntry} entry Track file entry.
204 * @param {function(object)} callback Callback.
205 * @private
206 */
207 AudioPlayer.prototype.fetchMetadata_ = function(entry, callback) {
208 this.metadataCache_.get(entry, 'thumbnail|media|streaming',
209 function(generation, metadata) {
210 // Do nothing if another load happened since the metadata request.
211 if (this.playlistGeneration_ == generation)
212 callback(metadata);
213 }.bind(this, this.playlistGeneration_));
214 };
215
216 /**
217 * @return {boolean} True if the player is be displayed in compact mode.
218 * @private
219 */
220 AudioPlayer.prototype.isExpanded_ = function() {
221 return this.player_.isExpanded();
222 };
223
224 /**
225 * Media error handler.
226 * @private
227 */
228 AudioPlayer.prototype.onError_ = function() {
229 var track = this.currentTrackIndex_;
230
231 this.invalidTracks_[track] = true;
232
233 this.fetchMetadata_(
234 this.entries_[track],
235 function(metadata) {
236 var error = (!navigator.onLine && metadata.streaming) ?
237 this.offlineString_ : this.errorString_;
238 this.displayMetadata_(track, metadata, error);
239 this.scheduleAutoAdvance_();
240 }.bind(this));
241 };
242
243 /**
244 * Expands/collapses button click handler. Toggles the mode and updates the
245 * height of the window.
246 *
247 * @private
248 */
249 AudioPlayer.prototype.onExpandCollapse_ = function() {
250 if (this.isExpanded_) {
251 this.player_.expand(false);
252 } else {
253 this.player_.expand(true);
254 }
255 this.syncHeight_();
256 };
257
258 /**
259 * Toggles the expanded mode when resizing.
260 *
261 * @param {Event} event Resize event.
262 * @private
263 */
264 AudioPlayer.prototype.onResize_ = function(event) {
265 if (!this.isExpanded_ &&
266 window.innerHeight >= AudioPlayer.EXPANDED_MODE_MIN_HEIGHT) {
267 this.isExpanded_ = true;
268 this.player_.expand(true);
269 } else if (this.isExpanded_ &&
270 window.innerHeight < AudioPlayer.EXPANDED_MODE_MIN_HEIGHT) {
271 this.isExpanded_ = false;
272 this.player_.expand(false);
273 }
274 };
275
276 /* Keep the below constants in sync with the CSS. */
277
278 /**
279 * Window header size in pixels.
280 * @type {number}
281 * @const
282 */
283 AudioPlayer.HEADER_HEIGHT = 28;
284
285 /**
286 * Track height in pixels.
287 * @type {number}
288 * @const
289 */
290 AudioPlayer.TRACK_HEIGHT = 44;
291
292 /**
293 * Controls bar height in pixels.
294 * @type {number}
295 * @const
296 */
297 AudioPlayer.CONTROLS_HEIGHT = 72;
298
299 /**
300 * Default number of items in the expanded mode.
301 * @type {number}
302 * @const
303 */
304 AudioPlayer.DEFAULT_EXPANDED_ITEMS = 5;
305
306 /**
307 * Minimum size of the window in the expanded mode in pixels.
308 * @type {number}
309 * @const
310 */
311 AudioPlayer.EXPANDED_MODE_MIN_HEIGHT = AudioPlayer.CONTROLS_HEIGHT +
312 AudioPlayer.TRACK_HEIGHT * 2;
313
314 /**
315 * Sets the correct player window height.
316 */
317 AudioPlayer.prototype.syncExpanded = function() {
318 if (this.isExpanded_ == this.player_.isExpanded())
319 return;
320
321 if (this.isExpanded_ && !this.player_.isExpanded())
322 this.lastExpandedHeight_ = window.innerHeight;
323
324 this.isExpanded_ = this.player_.isExpanded();
325 this.syncHeight_();
326 };
327
328 /**
329 * @private
330 */
331 AudioPlayer.prototype.syncHeight_ = function() {
332 var targetHeight;
333
334 if (this.isExpanded_) {
335 // Expanded.
336 if (!this.lastExpandedHeight_ ||
337 this.lastExpandedHeight_ < AudioPlayer.EXPANDED_MODE_MIN_HEIGHT) {
338 var expandedListHeight =
339 Math.min(this.entries_.length, AudioPlayer.DEFAULT_EXPANDED_ITEMS) *
340 AudioPlayer.TRACK_HEIGHT;
341 targetHeight = AudioPlayer.CONTROLS_HEIGHT + expandedListHeight;
342 } else {
343 targetHeight = this.lastExpandedHeight_;
344 }
345 } else {
346 // Not expaned.
347 targetHeight = AudioPlayer.CONTROLS_HEIGHT + AudioPlayer.TRACK_HEIGHT;
348 }
349
350 window.resizeTo(window.innerWidth, targetHeight + AudioPlayer.HEADER_HEIGHT);
351 };
352
353 /**
354 * Create a TrackInfo object encapsulating the information about one track.
355 *
356 * @param {fileEntry} entry FileEntry to be retrieved the track info from.
357 * @param {function} onClick Click handler.
358 * @constructor
359 */
360 AudioPlayer.TrackInfo = function(entry, onClick) {
361 this.url = entry.toURL();
362 this.title = entry.name;
363 this.artist = this.getDefaultArtist();
364
365 // TODO(yoshiki): implement artwork.
366 this.artwork = null;
367 this.active = false;
368 };
369
370 /**
371 * @return {HTMLDivElement} The wrapper element for the track.
372 */
373 AudioPlayer.TrackInfo.prototype.getBox = function() { return this.box_ };
374
375 /**
376 * @return {string} Default track title (file name extracted from the url).
377 */
378 AudioPlayer.TrackInfo.prototype.getDefaultTitle = function() {
379 var title = this.url.split('/').pop();
380 var dotIndex = title.lastIndexOf('.');
381 if (dotIndex >= 0) title = title.substr(0, dotIndex);
382 title = decodeURIComponent(title);
383 return title;
384 };
385
386 /**
387 * TODO(kaznacheev): Localize.
388 */
389 AudioPlayer.TrackInfo.DEFAULT_ARTIST = 'Unknown Artist';
390
391 /**
392 * @return {string} 'Unknown artist' string.
393 */
394 AudioPlayer.TrackInfo.prototype.getDefaultArtist = function() {
395 return AudioPlayer.TrackInfo.DEFAULT_ARTIST;
396 };
397
398 /**
399 * @param {Object} metadata The metadata object.
400 * @param {string} error Error string.
401 */
402 AudioPlayer.TrackInfo.prototype.setMetadata = function(
403 metadata, error) {
404 if (error) {
405 // TODO(yoshiki): Handle error in better way.
406 this.title = entry.name;
407 this.artist = this.getDefaultArtist();
408 } else if (metadata.thumbnail && metadata.thumbnail.url) {
409 // TODO(yoshiki): implement artwork.
410 }
411 this.title = (metadata.media && metadata.media.title) ||
412 this.getDefaultTitle();
413 this.artist = error ||
414 (metadata.media && metadata.media.artist) || this.getDefaultArtist();
415 };
416
417 // Starts loading the audio player.
418 window.addEventListener('WebComponentsReady', function(e) {
419 AudioPlayer.load();
420 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698