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 'use strict'; | 5 'use strict'; |
6 | 6 |
7 /** | 7 /** |
8 * @fileoverview MediaControls class implements media playback controls | 8 * @fileoverview MediaControls class implements media playback controls |
9 * that exist outside of the audio/video HTML element. | 9 * that exist outside of the audio/video HTML element. |
10 */ | 10 */ |
11 | 11 |
12 /** | 12 /** |
13 * @param {HTMLElement} containerElement The container for the controls. | 13 * @param {HTMLElement} containerElement The container for the controls. |
14 * @param {function} onMediaError Function to display an error message. | 14 * @param {function} onMediaError Function to display an error message. |
15 * @constructor | 15 * @constructor |
16 */ | 16 */ |
17 function MediaControls(containerElement, onMediaError) { | 17 function MediaControls(containerElement, onMediaError) { |
18 this.container_ = containerElement; | 18 this.container_ = containerElement; |
19 this.document_ = this.container_.ownerDocument; | 19 this.document_ = this.container_.ownerDocument; |
20 this.media_ = null; | 20 this.media_ = null; |
21 | 21 |
22 this.onMediaPlayBound_ = this.onMediaPlay_.bind(this, true); | 22 this.onMediaPlayBound_ = this.onMediaPlay_.bind(this, true); |
23 this.onMediaPauseBound_ = this.onMediaPlay_.bind(this, false); | 23 this.onMediaPauseBound_ = this.onMediaPlay_.bind(this, false); |
24 this.onMediaDurationBound_ = this.onMediaDuration_.bind(this); | 24 this.onMediaDurationBound_ = this.onMediaDuration_.bind(this); |
25 this.onMediaProgressBound_ = this.onMediaProgress_.bind(this); | 25 this.onMediaProgressBound_ = this.onMediaProgress_.bind(this); |
26 this.onMediaError_ = onMediaError || function() {}; | 26 this.onMediaError_ = onMediaError || function() {}; |
| 27 |
| 28 this.savedVolume_ = 1; // 100% volume. |
27 } | 29 } |
28 | 30 |
29 /** | 31 /** |
30 * Button's state types. Values are used as CSS class names. | 32 * Button's state types. Values are used as CSS class names. |
31 * @enum {string} | 33 * @enum {string} |
32 */ | 34 */ |
33 MediaControls.ButtonStateType = { | 35 MediaControls.ButtonStateType = { |
34 DEFAULT: 'default', | 36 DEFAULT: 'default', |
35 PLAYING: 'playing', | 37 PLAYING: 'playing', |
36 ENDED: 'ended' | 38 ENDED: 'ended' |
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
126 }; | 128 }; |
127 | 129 |
128 /* | 130 /* |
129 * Playback control. | 131 * Playback control. |
130 */ | 132 */ |
131 | 133 |
132 /** | 134 /** |
133 * Play the media. | 135 * Play the media. |
134 */ | 136 */ |
135 MediaControls.prototype.play = function() { | 137 MediaControls.prototype.play = function() { |
| 138 if (!this.media_) |
| 139 return; // Media is detached. |
| 140 |
136 this.media_.play(); | 141 this.media_.play(); |
137 }; | 142 }; |
138 | 143 |
139 /** | 144 /** |
140 * Pause the media. | 145 * Pause the media. |
141 */ | 146 */ |
142 MediaControls.prototype.pause = function() { | 147 MediaControls.prototype.pause = function() { |
| 148 if (!this.media_) |
| 149 return; // Media is detached. |
| 150 |
143 this.media_.pause(); | 151 this.media_.pause(); |
144 }; | 152 }; |
145 | 153 |
146 /** | 154 /** |
147 * @return {boolean} True if the media is currently playing. | 155 * @return {boolean} True if the media is currently playing. |
148 */ | 156 */ |
149 MediaControls.prototype.isPlaying = function() { | 157 MediaControls.prototype.isPlaying = function() { |
150 return this.media_ && !this.media_.paused && !this.media_.ended; | 158 return this.media_ && !this.media_.paused && !this.media_.ended; |
151 }; | 159 }; |
152 | 160 |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
223 var ratio = current / duration; | 231 var ratio = current / duration; |
224 this.progressSlider_.setValue(ratio); | 232 this.progressSlider_.setValue(ratio); |
225 this.currentTime_.textContent = MediaControls.formatTime_(current); | 233 this.currentTime_.textContent = MediaControls.formatTime_(current); |
226 }; | 234 }; |
227 | 235 |
228 /** | 236 /** |
229 * @param {number} value Progress [0..1]. | 237 * @param {number} value Progress [0..1]. |
230 * @private | 238 * @private |
231 */ | 239 */ |
232 MediaControls.prototype.onProgressChange_ = function(value) { | 240 MediaControls.prototype.onProgressChange_ = function(value) { |
| 241 if (!this.media_) |
| 242 return; // Media is detached. |
| 243 |
233 if (!this.media_.seekable || !this.media_.duration) { | 244 if (!this.media_.seekable || !this.media_.duration) { |
234 console.error('Inconsistent media state'); | 245 console.error('Inconsistent media state'); |
235 return; | 246 return; |
236 } | 247 } |
237 | 248 |
238 var current = this.media_.duration * value; | 249 var current = this.media_.duration * value; |
239 this.media_.currentTime = current; | 250 this.media_.currentTime = current; |
240 this.currentTime_.textContent = MediaControls.formatTime_(current); | 251 this.currentTime_.textContent = MediaControls.formatTime_(current); |
241 }; | 252 }; |
242 | 253 |
243 /** | 254 /** |
244 * @param {boolean} on True if dragging. | 255 * @param {boolean} on True if dragging. |
245 * @private | 256 * @private |
246 */ | 257 */ |
247 MediaControls.prototype.onProgressDrag_ = function(on) { | 258 MediaControls.prototype.onProgressDrag_ = function(on) { |
248 if (!this.media_) | 259 if (!this.media_) |
249 return; | 260 return; // Media is detached. |
250 | 261 |
251 if (on) { | 262 if (on) { |
252 this.resumeAfterDrag_ = this.isPlaying(); | 263 this.resumeAfterDrag_ = this.isPlaying(); |
253 this.media_.pause(); | 264 this.media_.pause(); |
254 } else { | 265 } else { |
255 if (this.resumeAfterDrag_) { | 266 if (this.resumeAfterDrag_) { |
256 if (this.media_.ended) | 267 if (this.media_.ended) |
257 this.onMediaPlay_(false); | 268 this.onMediaPlay_(false); |
258 else | 269 else |
259 this.media_.play(); | 270 this.media_.play(); |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
308 if (value <= 1 / 3) return 1; | 319 if (value <= 1 / 3) return 1; |
309 if (value <= 2 / 3) return 2; | 320 if (value <= 2 / 3) return 2; |
310 return 3; | 321 return 3; |
311 }; | 322 }; |
312 | 323 |
313 /** | 324 /** |
314 * @param {number} value Volume [0..1]. | 325 * @param {number} value Volume [0..1]. |
315 * @private | 326 * @private |
316 */ | 327 */ |
317 MediaControls.prototype.onVolumeChange_ = function(value) { | 328 MediaControls.prototype.onVolumeChange_ = function(value) { |
| 329 if (!this.media_) |
| 330 return; // Media is detached. |
| 331 |
318 this.media_.volume = value; | 332 this.media_.volume = value; |
319 this.soundButton_.setAttribute('level', MediaControls.getVolumeLevel_(value)); | 333 this.soundButton_.setAttribute('level', MediaControls.getVolumeLevel_(value)); |
320 }; | 334 }; |
321 | 335 |
322 /** | 336 /** |
323 * @param {boolean} on True if dragging is in progress. | 337 * @param {boolean} on True if dragging is in progress. |
324 * @private | 338 * @private |
325 */ | 339 */ |
326 MediaControls.prototype.onVolumeDrag_ = function(on) { | 340 MediaControls.prototype.onVolumeDrag_ = function(on) { |
327 if (on && (this.media_.volume != 0)) { | 341 if (on && (this.media_.volume != 0)) { |
(...skipping 18 matching lines...) Expand all Loading... |
346 this.media_.addEventListener('durationchange', this.onMediaDurationBound_); | 360 this.media_.addEventListener('durationchange', this.onMediaDurationBound_); |
347 this.media_.addEventListener('timeupdate', this.onMediaProgressBound_); | 361 this.media_.addEventListener('timeupdate', this.onMediaProgressBound_); |
348 this.media_.addEventListener('error', this.onMediaError_); | 362 this.media_.addEventListener('error', this.onMediaError_); |
349 | 363 |
350 // Reflect the media state in the UI. | 364 // Reflect the media state in the UI. |
351 this.onMediaDuration_(); | 365 this.onMediaDuration_(); |
352 this.onMediaPlay_(this.isPlaying()); | 366 this.onMediaPlay_(this.isPlaying()); |
353 this.onMediaProgress_(); | 367 this.onMediaProgress_(); |
354 if (this.volume_) { | 368 if (this.volume_) { |
355 /* Copy the user selected volume to the new media element. */ | 369 /* Copy the user selected volume to the new media element. */ |
356 this.media_.volume = this.volume_.getValue(); | 370 this.savedVolume_ = this.media_.volume = this.volume_.getValue(); |
357 } | 371 } |
358 }; | 372 }; |
359 | 373 |
360 /** | 374 /** |
361 * Detach media event handlers. | 375 * Detach media event handlers. |
362 */ | 376 */ |
363 MediaControls.prototype.detachMedia = function() { | 377 MediaControls.prototype.detachMedia = function() { |
364 if (!this.media_) | 378 if (!this.media_) |
365 return; | 379 return; |
366 | 380 |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
412 | 426 |
413 this.enableControls_('.media-control', true); | 427 this.enableControls_('.media-control', true); |
414 | 428 |
415 var sliderContainer = this.progressSlider_.getContainer(); | 429 var sliderContainer = this.progressSlider_.getContainer(); |
416 if (this.media_.seekable) | 430 if (this.media_.seekable) |
417 sliderContainer.classList.remove('readonly'); | 431 sliderContainer.classList.remove('readonly'); |
418 else | 432 else |
419 sliderContainer.classList.add('readonly'); | 433 sliderContainer.classList.add('readonly'); |
420 | 434 |
421 var valueToString = function(value) { | 435 var valueToString = function(value) { |
| 436 var duration = this.media_ ? this.media_.duration : 0; |
422 return MediaControls.formatTime_(this.media_.duration * value); | 437 return MediaControls.formatTime_(this.media_.duration * value); |
423 }.bind(this); | 438 }.bind(this); |
424 | 439 |
425 this.duration_.textContent = valueToString(1); | 440 this.duration_.textContent = valueToString(1); |
426 | 441 |
427 if (this.progressSlider_.setValueToStringFunction) | 442 if (this.progressSlider_.setValueToStringFunction) |
428 this.progressSlider_.setValueToStringFunction(valueToString); | 443 this.progressSlider_.setValueToStringFunction(valueToString); |
429 | 444 |
430 if (this.media_.seekable) | 445 if (this.media_.seekable) |
431 this.restorePlayState(); | 446 this.restorePlayState(); |
(...skipping 780 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1212 AudioControls.prototype.onAdvanceClick_ = function(forward) { | 1227 AudioControls.prototype.onAdvanceClick_ = function(forward) { |
1213 if (!forward && | 1228 if (!forward && |
1214 (this.getMedia().currentTime > AudioControls.TRACK_RESTART_THRESHOLD)) { | 1229 (this.getMedia().currentTime > AudioControls.TRACK_RESTART_THRESHOLD)) { |
1215 // We are far enough from the beginning of the current track. | 1230 // We are far enough from the beginning of the current track. |
1216 // Restart it instead of than skipping to the previous one. | 1231 // Restart it instead of than skipping to the previous one. |
1217 this.getMedia().currentTime = 0; | 1232 this.getMedia().currentTime = 0; |
1218 } else { | 1233 } else { |
1219 this.advanceTrack_(forward); | 1234 this.advanceTrack_(forward); |
1220 } | 1235 } |
1221 }; | 1236 }; |
OLD | NEW |