| 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 |