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

Side by Side Diff: ui/file_manager/file_manager/audio_player/js/audio_player.js

Issue 641283002: Separate the audio player app from Files.app Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Clean up Created 6 years, 2 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
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 this.selectedEntry_ = null;
17
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);
23 }.bind(this));
24
25 this.entries_ = [];
26 this.currentTrackIndex_ = -1;
27 this.playlistGeneration_ = 0;
28
29 /**
30 * Whether if the playlist is expanded or not. This value is changed by
31 * this.syncExpanded().
32 * True: expanded, false: collapsed, null: unset.
33 *
34 * @type {?boolean}
35 * @private
36 */
37 this.isExpanded_ = null; // Initial value is null. It'll be set in load().
38
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();
44
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'];
53 }.bind(this));
54
55 this.volumeManager_.addEventListener('externally-unmounted',
56 this.onExternallyUnmounted_.bind(this));
57
58 window.addEventListener('resize', this.onResize_.bind(this));
59
60 // Show the window after DOM is processed.
61 var currentWindow = chrome.app.window.current();
62 if (currentWindow)
63 setTimeout(currentWindow.show.bind(currentWindow), 0);
64 }
65
66 /**
67 * Initial load method (static).
68 */
69 AudioPlayer.load = function() {
70 document.ondragstart = function(e) { e.preventDefault() };
71
72 AudioPlayer.instance =
73 new AudioPlayer(document.querySelector('.audio-player'));
74
75 reload();
76 };
77
78 /**
79 * Unloads the player.
80 */
81 function unload() {
82 if (AudioPlayer.instance)
83 AudioPlayer.instance.onUnload();
84 }
85
86 /**
87 * Reloads the player.
88 */
89 function reload() {
90 AudioPlayer.instance.load(window.appState);
91 }
92
93 /**
94 * Loads a new playlist.
95 * @param {Playlist} playlist Playlist object passed via mediaPlayerPrivate.
96 */
97 AudioPlayer.prototype.load = function(playlist) {
98 this.playlistGeneration_++;
99 this.currentTrackIndex_ = -1;
100
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
104 util.saveAppState();
105
106 this.isExpanded_ = this.model_.expanded;
107
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;
112
113 var position = playlist.position || 0;
114 var time = playlist.time || 0;
115
116 if (this.entries_.length == 0)
117 return;
118
119 var newTracks = [];
120 var currentTracks = this.player_.tracks;
121 var unchanged = (currentTracks.length === this.entries_.length);
122
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));
127
128 if (unchanged && entry.toURL() !== currentTracks[i].url)
129 unchanged = false;
130 }
131
132 if (!unchanged) {
133 this.player_.tracks = newTracks;
134
135 // Makes it sure that the handler of the track list is called, before
136 // the handler of the track index.
137 Platform.performMicrotaskCheckpoint();
138 }
139
140 this.select_(position, !!time);
141
142 // Load the selected track metadata first, then load the rest.
143 this.loadMetadata_(position);
144 for (i = 0; i != this.entries_.length; i++) {
145 if (i != position)
146 this.loadMetadata_(i);
147 }
148 }.bind(this));
149 }.bind(this));
150 };
151
152 /**
153 * Loads metadata for a track.
154 * @param {number} track Track number.
155 * @private
156 */
157 AudioPlayer.prototype.loadMetadata_ = function(track) {
158 this.fetchMetadata_(
159 this.entries_[track], this.displayMetadata_.bind(this, track));
160 };
161
162 /**
163 * Displays track's metadata.
164 * @param {number} track Track number.
165 * @param {Object} metadata Metadata object.
166 * @param {string=} opt_error Error message.
167 * @private
168 */
169 AudioPlayer.prototype.displayMetadata_ = function(track, metadata, opt_error) {
170 this.player_.tracks[track].setMetadata(metadata, opt_error);
171 };
172
173 /**
174 * Closes audio player when a volume containing the selected item is unmounted.
175 * @param {Event} event The unmount event.
176 * @private
177 */
178 AudioPlayer.prototype.onExternallyUnmounted_ = function(event) {
179 if (!this.selectedEntry_)
180 return;
181
182 if (this.volumeManager_.getVolumeInfo(this.selectedEntry_) ===
183 event.volumeInfo)
184 window.close();
185 };
186
187 /**
188 * Called on window is being unloaded.
189 */
190 AudioPlayer.prototype.onUnload = function() {
191 if (this.player_)
192 this.player_.onPageUnload();
193
194 if (this.volumeManager_)
195 this.volumeManager_.dispose();
196 };
197
198 /**
199 * Selects a new track to play.
200 * @param {number} newTrack New track number.
201 * @param {number} time New playback position (in second).
202 * @private
203 */
204 AudioPlayer.prototype.select_ = function(newTrack, time) {
205 if (this.currentTrackIndex_ == newTrack) return;
206
207 this.currentTrackIndex_ = newTrack;
208 this.player_.currentTrackIndex = this.currentTrackIndex_;
209 this.player_.audioController.time = time;
210 Platform.performMicrotaskCheckpoint();
211
212 if (!window.appReopen)
213 this.player_.audioElement.play();
214
215 window.appState.position = this.currentTrackIndex_;
216 window.appState.time = 0;
217 util.saveAppState();
218
219 var entry = this.entries_[this.currentTrackIndex_];
220
221 this.fetchMetadata_(entry, function(metadata) {
222 if (this.currentTrackIndex_ != newTrack)
223 return;
224
225 this.selectedEntry_ = entry;
226 }.bind(this));
227 };
228
229 /**
230 * @param {FileEntry} entry Track file entry.
231 * @param {function(object)} callback Callback.
232 * @private
233 */
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)
239 callback(metadata);
240 }.bind(this, this.playlistGeneration_));
241 };
242
243 /**
244 * Media error handler.
245 * @private
246 */
247 AudioPlayer.prototype.onError_ = function() {
248 var track = this.currentTrackIndex_;
249
250 this.invalidTracks_[track] = true;
251
252 this.fetchMetadata_(
253 this.entries_[track],
254 function(metadata) {
255 var error = (!navigator.onLine && !metadata.external.present) ?
256 this.offlineString_ : this.errorString_;
257 this.displayMetadata_(track, metadata, error);
258 this.scheduleAutoAdvance_();
259 }.bind(this));
260 };
261
262 /**
263 * Toggles the expanded mode when resizing.
264 *
265 * @param {Event} event Resize event.
266 * @private
267 */
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;
277 }
278 };
279
280 /* Keep the below constants in sync with the CSS. */
281
282 /**
283 * Window header size in pixels.
284 * @type {number}
285 * @const
286 */
287 AudioPlayer.HEADER_HEIGHT = 33; // 32px + border 1px
288
289 /**
290 * Track height in pixels.
291 * @type {number}
292 * @const
293 */
294 AudioPlayer.TRACK_HEIGHT = 44;
295
296 /**
297 * Controls bar height in pixels.
298 * @type {number}
299 * @const
300 */
301 AudioPlayer.CONTROLS_HEIGHT = 73; // 72px + border 1px
302
303 /**
304 * Default number of items in the expanded mode.
305 * @type {number}
306 * @const
307 */
308 AudioPlayer.DEFAULT_EXPANDED_ITEMS = 5;
309
310 /**
311 * Minimum size of the window in the expanded mode in pixels.
312 * @type {number}
313 * @const
314 */
315 AudioPlayer.EXPANDED_MODE_MIN_HEIGHT = AudioPlayer.CONTROLS_HEIGHT +
316 AudioPlayer.TRACK_HEIGHT * 2;
317
318 /**
319 * Invoked when the 'expanded' property in the model is changed.
320 * @param {boolean} oldValue Old value.
321 * @param {boolean} newValue New value.
322 */
323 AudioPlayer.prototype.onModelExpandedChanged = function(oldValue, newValue) {
324 if (this.isExpanded_ !== null &&
325 this.isExpanded_ === newValue)
326 return;
327
328 if (this.isExpanded_ && !newValue)
329 this.lastExpandedHeight_ = window.innerHeight;
330
331 if (this.isExpanded_ !== newValue) {
332 this.isExpanded_ = newValue;
333 this.syncHeight_();
334
335 // Saves new state.
336 window.appState.expanded = newValue;
337 util.saveAppState();
338 }
339 };
340
341 /**
342 * @private
343 */
344 AudioPlayer.prototype.syncHeight_ = function() {
345 var targetHeight;
346
347 if (this.model_.expanded) {
348 // 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;
356 } else {
357 targetHeight = this.lastExpandedHeight_;
358 }
359 } else {
360 // Not expanded.
361 targetHeight = AudioPlayer.CONTROLS_HEIGHT + AudioPlayer.TRACK_HEIGHT;
362 }
363
364 window.resizeTo(window.innerWidth, targetHeight + AudioPlayer.HEADER_HEIGHT);
365 };
366
367 /**
368 * Create a TrackInfo object encapsulating the information about one track.
369 *
370 * @param {fileEntry} entry FileEntry to be retrieved the track info from.
371 * @param {function} onClick Click handler.
372 * @constructor
373 */
374 AudioPlayer.TrackInfo = function(entry, onClick) {
375 this.url = entry.toURL();
376 this.title = this.getDefaultTitle();
377 this.artist = this.getDefaultArtist();
378
379 // TODO(yoshiki): implement artwork.
380 this.artwork = null;
381 this.active = false;
382 };
383
384 /**
385 * @return {HTMLDivElement} The wrapper element for the track.
386 */
387 AudioPlayer.TrackInfo.prototype.getBox = function() { return this.box_ };
388
389 /**
390 * @return {string} Default track title (file name extracted from the url).
391 */
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);
397 return title;
398 };
399
400 /**
401 * TODO(kaznacheev): Localize.
402 */
403 AudioPlayer.TrackInfo.DEFAULT_ARTIST = 'Unknown Artist';
404
405 /**
406 * @return {string} 'Unknown artist' string.
407 */
408 AudioPlayer.TrackInfo.prototype.getDefaultArtist = function() {
409 return AudioPlayer.TrackInfo.DEFAULT_ARTIST;
410 };
411
412 /**
413 * @param {Object} metadata The metadata object.
414 * @param {string} error Error string.
415 */
416 AudioPlayer.TrackInfo.prototype.setMetadata = function(
417 metadata, error) {
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();
424 };
425
426 // Starts loading the audio player.
427 window.addEventListener('polymer-ready', function(e) {
428 AudioPlayer.load();
429 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698