OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 document.addEventListener('DOMContentLoaded', function() { | 5 document.addEventListener('DOMContentLoaded', function() { |
6 // Test harness sets the search string to prevent the automatic load. | 6 // Test harness sets the search string to prevent the automatic load. |
7 // It calls AudioPlayer.load() explicitly after initializing | 7 // It calls AudioPlayer.load() explicitly after initializing |
8 // the |chrome| variable with an appropriate mock object. | 8 // the |chrome| variable with an appropriate mock object. |
9 if (!document.location.search) { | 9 if (!document.location.search) { |
10 AudioPlayer.load(); | 10 AudioPlayer.load(); |
(...skipping 25 matching lines...) Expand all Loading... |
36 // but it would make keeping the list scroll position very tricky. | 36 // but it would make keeping the list scroll position very tricky. |
37 this.trackList_ = createChild('track-list'); | 37 this.trackList_ = createChild('track-list'); |
38 this.trackStack_ = createChild('track-stack'); | 38 this.trackStack_ = createChild('track-stack'); |
39 | 39 |
40 createChild('title-button close').addEventListener( | 40 createChild('title-button close').addEventListener( |
41 'click', function() { chrome.mediaPlayerPrivate.closeWindow() }); | 41 'click', function() { chrome.mediaPlayerPrivate.closeWindow() }); |
42 | 42 |
43 createChild('title-button collapse').addEventListener( | 43 createChild('title-button collapse').addEventListener( |
44 'click', this.onExpandCollapse_.bind(this)); | 44 'click', this.onExpandCollapse_.bind(this)); |
45 | 45 |
46 this.audioControls_ = new AudioControls( | 46 this.audioControls_ = new FullWindowAudioControls( |
47 createChild(), this.advance_.bind(this), this.onError_.bind(this)); | 47 createChild(), this.advance_.bind(this), this.onError_.bind(this)); |
48 | 48 |
49 this.audioControls_.attachMedia(createChild('', 'audio')); | 49 this.audioControls_.attachMedia(createChild('', 'audio')); |
50 | 50 |
51 chrome.fileBrowserPrivate.getStrings(function(strings) { | 51 chrome.fileBrowserPrivate.getStrings(function(strings) { |
52 container.ownerDocument.title = strings['AUDIO_PLAYER_TITLE']; | 52 container.ownerDocument.title = strings['AUDIO_PLAYER_TITLE']; |
53 this.errorString_ = strings['AUDIO_ERROR']; | 53 this.errorString_ = strings['AUDIO_ERROR']; |
54 this.offlineString_ = strings['AUDIO_OFFLINE']; | 54 this.offlineString_ = strings['AUDIO_OFFLINE']; |
55 AudioPlayer.TrackInfo.DEFAULT_ARTIST = | 55 AudioPlayer.TrackInfo.DEFAULT_ARTIST = |
56 strings['AUDIO_PLAYER_DEFAULT_ARTIST']; | 56 strings['AUDIO_PLAYER_DEFAULT_ARTIST']; |
57 }.bind(this)); | 57 }.bind(this)); |
58 } | 58 } |
59 | 59 |
60 /** | 60 /** |
| 61 * Key in the local storage for the list of track urls. |
| 62 */ |
| 63 AudioPlayer.PLAYLIST_KEY = 'audioPlaylist'; |
| 64 |
| 65 /** |
| 66 * Key in the local storage for the number of the current track. |
| 67 */ |
| 68 AudioPlayer.TRACK_KEY = 'audioTrack'; |
| 69 |
| 70 /** |
61 * Initial load method (static). | 71 * Initial load method (static). |
62 */ | 72 */ |
63 AudioPlayer.load = function() { | 73 AudioPlayer.load = function() { |
64 document.ondragstart = function(e) { e.preventDefault() }; | 74 document.ondragstart = function(e) { e.preventDefault() }; |
65 document.oncontextmenu = function(e) { e.preventDefault(); }; | 75 document.oncontextmenu = function(e) { e.preventDefault(); }; |
66 | 76 |
67 // If the audio player is starting before the first instance of the File | 77 // If the audio player is starting before the first instance of the File |
68 // Manager then it does not have access to filesystem URLs. Request it now. | 78 // Manager then it does not have access to filesystem URLs. Request it now. |
69 chrome.fileBrowserPrivate.requestLocalFileSystem(function() { | 79 chrome.fileBrowserPrivate.requestLocalFileSystem(function() { |
70 var player = new AudioPlayer(document.querySelector('.audio-player')); | 80 var player = new AudioPlayer(document.querySelector('.audio-player')); |
71 function getPlaylist() { | 81 function getPlaylist() { |
72 chrome.mediaPlayerPrivate.getPlaylist(player.load.bind(player)); | 82 chrome.mediaPlayerPrivate.getPlaylist(player.load.bind(player)); |
73 } | 83 } |
74 getPlaylist(); | 84 if (document.location.hash) // The window is reloading, restore the state. |
| 85 player.load(null); |
| 86 else |
| 87 getPlaylist(); |
75 chrome.mediaPlayerPrivate.onPlaylistChanged.addListener(getPlaylist); | 88 chrome.mediaPlayerPrivate.onPlaylistChanged.addListener(getPlaylist); |
76 }); | 89 }); |
77 }; | 90 }; |
78 | 91 |
79 /** | 92 /** |
80 * Load a new playlist. | 93 * Load a new playlist. |
81 * @param {Playlist} playlist Playlist object passed via mediaPlayerPrivate. | 94 * @param {Playlist} playlist Playlist object passed via mediaPlayerPrivate. |
82 */ | 95 */ |
83 AudioPlayer.prototype.load = function(playlist) { | 96 AudioPlayer.prototype.load = function(playlist) { |
| 97 if (!playlist || !playlist.items.length) { |
| 98 // playlist is null if the window is being reloaded. |
| 99 // playlist is empty if ChromeOS has restarted with the Audio Player open. |
| 100 // Restore the playlist from the local storage. Restore the player state |
| 101 // encoded in the page location. |
| 102 try { |
| 103 playlist = { |
| 104 items: JSON.parse(localStorage[AudioPlayer.PLAYLIST_KEY]), |
| 105 position: Number(localStorage[AudioPlayer.TRACK_KEY]), |
| 106 restore: true |
| 107 }; |
| 108 } catch (ignore) {} |
| 109 } else { |
| 110 // Remember the playlist for the restart. |
| 111 localStorage[AudioPlayer.PLAYLIST_KEY] = JSON.stringify(playlist.items); |
| 112 localStorage[AudioPlayer.TRACK_KEY] = playlist.position; |
| 113 } |
| 114 |
84 this.playlistGeneration_++; | 115 this.playlistGeneration_++; |
85 | 116 |
86 this.audioControls_.pause(); | 117 this.audioControls_.pause(); |
87 | 118 |
88 this.currentTrack_ = -1; | 119 this.currentTrack_ = -1; |
89 | 120 |
90 this.urls_ = playlist.items; | 121 this.urls_ = playlist.items; |
91 | 122 |
92 this.invalidTracks_ = {}; | 123 this.invalidTracks_ = {}; |
93 this.cancelAutoAdvance_(); | 124 this.cancelAutoAdvance_(); |
94 | 125 |
95 if (this.urls_.length == 1) | 126 if (this.urls_.length <= 1) |
96 this.container_.classList.add('single-track'); | 127 this.container_.classList.add('single-track'); |
97 else | 128 else |
98 this.container_.classList.remove('single-track'); | 129 this.container_.classList.remove('single-track'); |
99 | 130 |
100 this.syncHeight_(); | 131 this.syncHeight_(); |
101 | 132 |
102 this.trackList_.textContent = ''; | 133 this.trackList_.textContent = ''; |
103 this.trackStack_.textContent = ''; | 134 this.trackStack_.textContent = ''; |
104 | 135 |
105 this.trackListItems_ = []; | 136 this.trackListItems_ = []; |
106 this.trackStackItems_ = []; | 137 this.trackStackItems_ = []; |
107 | 138 |
| 139 if (this.urls_.length == 0) |
| 140 return; |
| 141 |
108 for (var i = 0; i != this.urls_.length; i++) { | 142 for (var i = 0; i != this.urls_.length; i++) { |
109 var url = this.urls_[i]; | 143 var url = this.urls_[i]; |
110 var onClick = this.select_.bind(this, i); | 144 var onClick = this.select_.bind(this, i); |
111 this.trackListItems_.push( | 145 this.trackListItems_.push( |
112 new AudioPlayer.TrackInfo(this.trackList_, url, onClick)); | 146 new AudioPlayer.TrackInfo(this.trackList_, url, onClick)); |
113 this.trackStackItems_.push( | 147 this.trackStackItems_.push( |
114 new AudioPlayer.TrackInfo(this.trackStack_, url, onClick)); | 148 new AudioPlayer.TrackInfo(this.trackStack_, url, onClick)); |
115 } | 149 } |
116 | 150 |
117 this.select_(playlist.position); | 151 this.select_(playlist.position, playlist.restore); |
118 | 152 |
119 // This class will be removed if at least one track has art. | 153 // This class will be removed if at least one track has art. |
120 this.container_.classList.add('noart'); | 154 this.container_.classList.add('noart'); |
121 | 155 |
122 // Load the selected track metadata first, then load the rest. | 156 // Load the selected track metadata first, then load the rest. |
123 this.loadMetadata_(playlist.position); | 157 this.loadMetadata_(playlist.position); |
124 for (i = 0; i != this.urls_.length; i++) { | 158 for (i = 0; i != this.urls_.length; i++) { |
125 if (i != playlist.position) | 159 if (i != playlist.position) |
126 this.loadMetadata_(i); | 160 this.loadMetadata_(i); |
127 } | 161 } |
(...skipping 19 matching lines...) Expand all Loading... |
147 AudioPlayer.prototype.displayMetadata_ = function(track, metadata, opt_error) { | 181 AudioPlayer.prototype.displayMetadata_ = function(track, metadata, opt_error) { |
148 this.trackListItems_[track]. | 182 this.trackListItems_[track]. |
149 setMetadata(metadata, this.container_, opt_error); | 183 setMetadata(metadata, this.container_, opt_error); |
150 this.trackStackItems_[track]. | 184 this.trackStackItems_[track]. |
151 setMetadata(metadata, this.container_, opt_error); | 185 setMetadata(metadata, this.container_, opt_error); |
152 }; | 186 }; |
153 | 187 |
154 /** | 188 /** |
155 * Select a new track to play. | 189 * Select a new track to play. |
156 * @param {number} newTrack New track number. | 190 * @param {number} newTrack New track number. |
| 191 * @param {boolean} opt_restoreState True if restoring the play state from URL. |
157 * @private | 192 * @private |
158 */ | 193 */ |
159 AudioPlayer.prototype.select_ = function(newTrack) { | 194 AudioPlayer.prototype.select_ = function(newTrack, opt_restoreState) { |
160 if (this.currentTrack_ == newTrack) return; | 195 if (this.currentTrack_ == newTrack) return; |
161 | 196 |
162 this.changeSelectionInList_(this.currentTrack_, newTrack); | 197 this.changeSelectionInList_(this.currentTrack_, newTrack); |
163 this.changeSelectionInStack_(this.currentTrack_, newTrack); | 198 this.changeSelectionInStack_(this.currentTrack_, newTrack); |
164 | 199 |
165 this.currentTrack_ = newTrack; | 200 this.currentTrack_ = newTrack; |
| 201 localStorage[AudioPlayer.TRACK_KEY] = this.currentTrack_; |
| 202 |
166 this.scrollToCurrent_(false); | 203 this.scrollToCurrent_(false); |
167 | 204 |
168 var url = this.urls_[this.currentTrack_]; | 205 var url = this.urls_[this.currentTrack_]; |
169 this.fetchMetadata_(url, function(metadata) { | 206 this.fetchMetadata_(url, function(metadata) { |
170 var media = this.audioControls_.getMedia(); | |
171 // Do not try no stream when offline. | 207 // Do not try no stream when offline. |
172 media.src = | 208 var src = |
173 (navigator.onLine && metadata.streaming && metadata.streaming.url) || | 209 (navigator.onLine && metadata.streaming && metadata.streaming.url) || |
174 url; | 210 url; |
175 media.load(); | 211 this.audioControls_.load(src, opt_restoreState); |
176 this.audioControls_.play(); | |
177 }.bind(this)); | 212 }.bind(this)); |
178 }; | 213 }; |
179 | 214 |
180 /** | 215 /** |
181 * @param {string} url Track file url. | 216 * @param {string} url Track file url. |
182 * @param {function(object)} callback Callback. | 217 * @param {function(object)} callback Callback. |
183 * @private | 218 * @private |
184 */ | 219 */ |
185 AudioPlayer.prototype.fetchMetadata_ = function(url, callback) { | 220 AudioPlayer.prototype.fetchMetadata_ = function(url, callback) { |
186 this.metadataCache_.get(url, 'thumbnail|media|streaming', | 221 this.metadataCache_.get(url, 'thumbnail|media|streaming', |
(...skipping 254 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
441 // Only display the image if the thumbnail loaded successfully. | 476 // Only display the image if the thumbnail loaded successfully. |
442 this.art_.classList.remove('blank'); | 477 this.art_.classList.remove('blank'); |
443 container.classList.remove('noart'); | 478 container.classList.remove('noart'); |
444 }.bind(this); | 479 }.bind(this); |
445 this.img_.src = metadata.thumbnail.url; | 480 this.img_.src = metadata.thumbnail.url; |
446 } | 481 } |
447 this.title_.textContent = metadata.media.title || this.getDefaultTitle(); | 482 this.title_.textContent = metadata.media.title || this.getDefaultTitle(); |
448 this.artist_.textContent = | 483 this.artist_.textContent = |
449 error || metadata.media.artist || this.getDefaultArtist(); | 484 error || metadata.media.artist || this.getDefaultArtist(); |
450 }; | 485 }; |
| 486 |
| 487 /** |
| 488 * Audio controls specific for the Audio Player. |
| 489 * |
| 490 * @param {HTMLElement} container Parent container. |
| 491 * @param {function(boolean)} advanceTrack Parameter: true=forward. |
| 492 * @param {function} onError Error handler. |
| 493 * @constructor |
| 494 */ |
| 495 function FullWindowAudioControls(container, advanceTrack, onError) { |
| 496 AudioControls.apply(this, arguments); |
| 497 } |
| 498 |
| 499 FullWindowAudioControls.prototype = { __proto__: AudioControls.prototype }; |
| 500 |
| 501 /** |
| 502 * Enable play state restore from the location hash. |
| 503 * @param {string} src Source URL. |
| 504 * @param {boolean} restore True if need to restore the play state. |
| 505 */ |
| 506 FullWindowAudioControls.prototype.load = function(src, restore) { |
| 507 this.media_.src = src; |
| 508 this.media_.load(); |
| 509 this.restoreWhenLoaded_ = restore; |
| 510 }; |
| 511 |
| 512 /** |
| 513 * Save the current play state in the location hash so that it survives |
| 514 * the page reload. |
| 515 */ |
| 516 FullWindowAudioControls.prototype.onPlayStateChanged = function() { |
| 517 this.encodeStateIntoLocation(); |
| 518 }; |
| 519 |
| 520 /** |
| 521 * Restore the Save the play state from the location hash. |
| 522 */ |
| 523 FullWindowAudioControls.prototype.restorePlayState = function() { |
| 524 if (this.restoreWhenLoaded_) { |
| 525 this.restoreWhenLoaded_ = false; // This should only work once. |
| 526 if (this.decodeStateFromLocation()) |
| 527 return; |
| 528 } |
| 529 this.play(); |
| 530 }; |
OLD | NEW |