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 /** | 5 /** |
6 * @fileoverview MediaControls class implements media playback controls | 6 * @fileoverview MediaControls class implements media playback controls |
7 * that exist outside of the audio/video HTML element. | 7 * that exist outside of the audio/video HTML element. |
8 */ | 8 */ |
9 | 9 |
10 /** | 10 /** |
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
113 if (seconds < 10) result += '0'; | 113 if (seconds < 10) result += '0'; |
114 result += seconds; | 114 result += seconds; |
115 return result; | 115 return result; |
116 }; | 116 }; |
117 | 117 |
118 /** | 118 /** |
119 * Create a custom control. | 119 * Create a custom control. |
120 * | 120 * |
121 * @param {string} className Class name. | 121 * @param {string} className Class name. |
122 * @param {HTMLElement=} opt_parent Parent element or container if undefined. | 122 * @param {HTMLElement=} opt_parent Parent element or container if undefined. |
123 * @param {string=} opt_tagName Tag name of the control. 'div' if undefined. | |
124 * @return {!HTMLElement} The new control element. | 123 * @return {!HTMLElement} The new control element. |
125 */ | 124 */ |
126 MediaControls.prototype.createControl = | 125 MediaControls.prototype.createControl = function(className, opt_parent) { |
127 function(className, opt_parent, opt_tagName) { | |
128 var parent = opt_parent || this.container_; | 126 var parent = opt_parent || this.container_; |
129 var control = /** @type {!HTMLElement} */ | 127 var control = assertInstanceof(this.document_.createElement('div'), |
130 (this.document_.createElement(opt_tagName || 'div')); | 128 HTMLDivElement); |
131 control.className = className; | 129 control.className = className; |
132 parent.appendChild(control); | 130 parent.appendChild(control); |
133 return control; | 131 return control; |
134 }; | 132 }; |
135 | 133 |
136 /** | 134 /** |
137 * Create a custom button. | 135 * Create a custom button. |
138 * | 136 * |
139 * @param {string} className Class name. | 137 * @param {string} className Class name. |
140 * @param {function(Event)=} opt_handler Click handler. | 138 * @param {function(Event)=} opt_handler Click handler. |
141 * @param {HTMLElement=} opt_parent Parent element or container if undefined. | 139 * @param {HTMLElement=} opt_parent Parent element or container if undefined. |
142 * @param {number=} opt_numStates Number of states, default: 1. | 140 * @param {number=} opt_numStates Number of states, default: 1. |
143 * @return {!HTMLElement} The new button element. | 141 * @return {!HTMLElement} The new button element. |
144 */ | 142 */ |
145 MediaControls.prototype.createButton = function( | 143 MediaControls.prototype.createButton = function( |
146 className, opt_handler, opt_parent, opt_numStates) { | 144 className, opt_handler, opt_parent, opt_numStates) { |
147 opt_numStates = opt_numStates || 1; | 145 opt_numStates = opt_numStates || 1; |
148 | 146 |
149 var button = this.createControl(className, opt_parent, 'files-icon-button'); | 147 var button = this.createControl(className, opt_parent); |
150 button.classList.add('media-button'); | 148 button.classList.add('media-button'); |
151 | 149 |
152 button.setAttribute('state', MediaControls.ButtonStateType.DEFAULT); | 150 button.setAttribute('state', MediaControls.ButtonStateType.DEFAULT); |
153 | 151 |
154 if (opt_handler) | 152 if (opt_handler) |
155 button.addEventListener('click', opt_handler); | 153 button.addEventListener('click', opt_handler); |
156 | 154 |
157 return button; | 155 return button; |
158 }; | 156 }; |
159 | 157 |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
225 MediaControls.prototype.onPlayButtonClicked = function(event) { | 223 MediaControls.prototype.onPlayButtonClicked = function(event) { |
226 this.togglePlayState(); | 224 this.togglePlayState(); |
227 }; | 225 }; |
228 | 226 |
229 /** | 227 /** |
230 * @param {HTMLElement=} opt_parent Parent container. | 228 * @param {HTMLElement=} opt_parent Parent container. |
231 */ | 229 */ |
232 MediaControls.prototype.initPlayButton = function(opt_parent) { | 230 MediaControls.prototype.initPlayButton = function(opt_parent) { |
233 this.playButton_ = this.createButton('play media-control', | 231 this.playButton_ = this.createButton('play media-control', |
234 this.onPlayButtonClicked.bind(this), opt_parent, 3 /* States. */); | 232 this.onPlayButtonClicked.bind(this), opt_parent, 3 /* States. */); |
235 this.playButton_.setAttribute('aria-label', | |
236 str('MEDIA_PLAYER_PLAY_BUTTON_LABEL')); | |
237 }; | 233 }; |
238 | 234 |
239 /* | 235 /* |
240 * Time controls | 236 * Time controls |
241 */ | 237 */ |
242 | 238 |
243 /** | 239 /** |
244 * The default range of 100 is too coarse for the media progress slider. | 240 * The default range of 100 is too coarse for the media progress slider. |
245 */ | 241 */ |
246 MediaControls.PROGRESS_RANGE = 5000; | 242 MediaControls.PROGRESS_RANGE = 5000; |
247 | 243 |
248 /** | 244 /** |
249 * @param {HTMLElement=} opt_parent Parent container. | 245 * @param {HTMLElement=} opt_parent Parent container. |
250 */ | 246 */ |
251 MediaControls.prototype.initTimeControls = function(opt_parent) { | 247 MediaControls.prototype.initTimeControls = function(opt_parent) { |
252 var timeControls = this.createControl('time-controls', opt_parent); | 248 var timeControls = this.createControl('time-controls', opt_parent); |
253 | 249 |
254 var timeBox = this.createControl('time media-control', timeControls); | 250 var timeBox = this.createControl('time media-control', timeControls); |
255 | 251 |
256 this.currentTimeSpacer_ = this.createControl('spacer', timeBox); | 252 this.currentTimeSpacer_ = this.createControl('spacer', timeBox); |
257 this.currentTime_ = this.createControl('current', timeBox); | 253 this.currentTime_ = this.createControl('current', timeBox); |
258 // Set the initial width to the minimum to reduce the flicker. | 254 // Set the initial width to the minimum to reduce the flicker. |
259 this.updateTimeLabel_(0, 0); | 255 this.updateTimeLabel_(0, 0); |
260 | 256 |
261 this.progressSlider_ = /** @type {!PaperSliderElement} */ ( | 257 this.progressSlider_ = /** @type {!PaperSliderElement} */ ( |
262 document.createElement('paper-slider')); | 258 document.createElement('paper-slider')); |
263 this.progressSlider_.classList.add('progress', 'media-control'); | 259 this.progressSlider_.classList.add('progress', 'media-control'); |
264 this.progressSlider_.max = MediaControls.PROGRESS_RANGE; | 260 this.progressSlider_.max = MediaControls.PROGRESS_RANGE; |
265 this.progressSlider_.setAttribute('aria-label', | |
266 str('MEDIA_PLAYER_SEEK_SLIDER_LABEL')); | |
267 this.progressSlider_.addEventListener('change', function(event) { | 261 this.progressSlider_.addEventListener('change', function(event) { |
268 this.onProgressChange_(this.progressSlider_.ratio); | 262 this.onProgressChange_(this.progressSlider_.ratio); |
269 }.bind(this)); | 263 }.bind(this)); |
270 this.progressSlider_.addEventListener( | 264 this.progressSlider_.addEventListener( |
271 'immediate-value-change', | 265 'immediate-value-change', |
272 function(event) { | 266 function(event) { |
273 this.onProgressDrag_(); | 267 this.onProgressDrag_(); |
274 }.bind(this)); | 268 }.bind(this)); |
275 timeControls.appendChild(this.progressSlider_); | 269 timeControls.appendChild(this.progressSlider_); |
276 }; | 270 }; |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
384 | 378 |
385 /** | 379 /** |
386 * @param {HTMLElement=} opt_parent Parent element for the controls. | 380 * @param {HTMLElement=} opt_parent Parent element for the controls. |
387 */ | 381 */ |
388 MediaControls.prototype.initVolumeControls = function(opt_parent) { | 382 MediaControls.prototype.initVolumeControls = function(opt_parent) { |
389 var volumeControls = this.createControl('volume-controls', opt_parent); | 383 var volumeControls = this.createControl('volume-controls', opt_parent); |
390 | 384 |
391 this.soundButton_ = this.createButton('sound media-control', | 385 this.soundButton_ = this.createButton('sound media-control', |
392 this.onSoundButtonClick_.bind(this), volumeControls); | 386 this.onSoundButtonClick_.bind(this), volumeControls); |
393 this.soundButton_.setAttribute('level', 3); // max level. | 387 this.soundButton_.setAttribute('level', 3); // max level. |
394 this.soundButton_.setAttribute('aria-label', | |
395 str('MEDIA_PLAYER_MUTE_BUTTON_LABEL')); | |
396 | 388 |
397 this.volume_ = /** @type {!PaperSliderElement} */ ( | 389 this.volume_ = /** @type {!PaperSliderElement} */ ( |
398 document.createElement('paper-slider')); | 390 document.createElement('paper-slider')); |
399 this.volume_.classList.add('volume', 'media-control'); | 391 this.volume_.classList.add('volume', 'media-control'); |
400 this.volume_.setAttribute('aria-label', | |
401 str('MEDIA_PLAYER_VOLUME_SLIDER_LABEL')); | |
402 this.volume_.addEventListener('change', function(event) { | 392 this.volume_.addEventListener('change', function(event) { |
403 this.onVolumeChange_(this.volume_.ratio); | 393 this.onVolumeChange_(this.volume_.ratio); |
404 }.bind(this)); | 394 }.bind(this)); |
405 this.volume_.addEventListener('immediate-value-change', function(event) { | 395 this.volume_.addEventListener('immediate-value-change', function(event) { |
406 this.onVolumeDrag_(); | 396 this.onVolumeDrag_(); |
407 }.bind(this)); | 397 }.bind(this)); |
408 this.volume_.value = this.volume_.max; | 398 this.volume_.value = this.volume_.max; |
409 volumeControls.appendChild(this.volume_); | 399 volumeControls.appendChild(this.volume_); |
410 }; | 400 }; |
411 | 401 |
412 /** | 402 /** |
413 * Click handler for the sound level button. | 403 * Click handler for the sound level button. |
414 * @private | 404 * @private |
415 */ | 405 */ |
416 MediaControls.prototype.onSoundButtonClick_ = function() { | 406 MediaControls.prototype.onSoundButtonClick_ = function() { |
417 if (this.media_.volume == 0) { | 407 if (this.media_.volume == 0) { |
418 this.volume_.value = (this.savedVolume_ || 1) * this.volume_.max; | 408 this.volume_.value = (this.savedVolume_ || 1) * this.volume_.max; |
419 this.soundButton_.setAttribute('aria-label', | |
420 str('MEDIA_PLAYER_MUTE_BUTTON_LABEL')); | |
421 } else { | 409 } else { |
422 this.savedVolume_ = this.media_.volume; | 410 this.savedVolume_ = this.media_.volume; |
423 this.volume_.value = 0; | 411 this.volume_.value = 0; |
424 this.soundButton_.setAttribute('aria-label', | |
425 str('MEDIA_PLAYER_UNMUTE_BUTTON_LABEL')); | |
426 } | 412 } |
427 this.onVolumeChange_(this.volume_.ratio); | 413 this.onVolumeChange_(this.volume_.ratio); |
428 }; | 414 }; |
429 | 415 |
430 /** | 416 /** |
431 * @param {number} value Volume [0..1]. | 417 * @param {number} value Volume [0..1]. |
432 * @return {number} The rough level [0..3] used to pick an icon. | 418 * @return {number} The rough level [0..3] used to pick an icon. |
433 * @private | 419 * @private |
434 */ | 420 */ |
435 MediaControls.getVolumeLevel_ = function(value) { | 421 MediaControls.getVolumeLevel_ = function(value) { |
436 if (value == 0) return 0; | 422 if (value == 0) return 0; |
437 if (value <= 1 / 3) return 1; | 423 if (value <= 1 / 3) return 1; |
438 if (value <= 2 / 3) return 2; | 424 if (value <= 2 / 3) return 2; |
439 return 3; | 425 return 3; |
440 }; | 426 }; |
441 | 427 |
442 /** | 428 /** |
443 * @param {number} value Volume [0..1]. | 429 * @param {number} value Volume [0..1]. |
444 * @private | 430 * @private |
445 */ | 431 */ |
446 MediaControls.prototype.onVolumeChange_ = function(value) { | 432 MediaControls.prototype.onVolumeChange_ = function(value) { |
447 if (!this.media_) | 433 if (!this.media_) |
448 return; // Media is detached. | 434 return; // Media is detached. |
449 | 435 |
450 this.media_.volume = value; | 436 this.media_.volume = value; |
451 this.soundButton_.setAttribute('level', MediaControls.getVolumeLevel_(value)); | 437 this.soundButton_.setAttribute('level', MediaControls.getVolumeLevel_(value)); |
452 this.soundButton_.setAttribute('aria-label', | |
453 value === 0 ? str('MEDIA_PLAYER_UNMUTE_BUTTON_LABEL') | |
454 : str('MEDIA_PLAYER_MUTE_BUTTON_LABEL')); | |
455 }; | 438 }; |
456 | 439 |
457 /** | 440 /** |
458 * @private | 441 * @private |
459 */ | 442 */ |
460 MediaControls.prototype.onVolumeDrag_ = function() { | 443 MediaControls.prototype.onVolumeDrag_ = function() { |
461 if (this.media_.volume !== 0) { | 444 if (this.media_.volume !== 0) { |
462 this.savedVolume_ = this.media_.volume; | 445 this.savedVolume_ = this.media_.volume; |
463 } | 446 } |
464 }; | 447 }; |
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
599 /** | 582 /** |
600 * Updates the play button state. | 583 * Updates the play button state. |
601 * @param {boolean} playing If the video is playing. | 584 * @param {boolean} playing If the video is playing. |
602 * @private | 585 * @private |
603 */ | 586 */ |
604 MediaControls.prototype.updatePlayButtonState_ = function(playing) { | 587 MediaControls.prototype.updatePlayButtonState_ = function(playing) { |
605 if (this.media_.ended && | 588 if (this.media_.ended && |
606 this.progressSlider_.value === this.progressSlider_.max) { | 589 this.progressSlider_.value === this.progressSlider_.max) { |
607 this.playButton_.setAttribute('state', | 590 this.playButton_.setAttribute('state', |
608 MediaControls.ButtonStateType.ENDED); | 591 MediaControls.ButtonStateType.ENDED); |
609 this.playButton_.setAttribute('aria-label', | |
610 str('MEDIA_PLAYER_PLAY_BUTTON_LABEL')); | |
611 } else if (playing) { | 592 } else if (playing) { |
612 this.playButton_.setAttribute('state', | 593 this.playButton_.setAttribute('state', |
613 MediaControls.ButtonStateType.PLAYING); | 594 MediaControls.ButtonStateType.PLAYING); |
614 this.playButton_.setAttribute('aria-label', | |
615 str('MEDIA_PLAYER_PAUSE_BUTTON_LABEL')); | |
616 } else { | 595 } else { |
617 this.playButton_.setAttribute('state', | 596 this.playButton_.setAttribute('state', |
618 MediaControls.ButtonStateType.DEFAULT); | 597 MediaControls.ButtonStateType.DEFAULT); |
619 this.playButton_.setAttribute('aria-label', | |
620 str('MEDIA_PLAYER_PLAY_BUTTON_LABEL')); | |
621 } | 598 } |
622 }; | 599 }; |
623 | 600 |
624 /** | 601 /** |
625 * Restore play state. Base implementation is empty. | 602 * Restore play state. Base implementation is empty. |
626 */ | 603 */ |
627 MediaControls.prototype.restorePlayState = function() {}; | 604 MediaControls.prototype.restorePlayState = function() {}; |
628 | 605 |
629 /** | 606 /** |
630 * Encode current state into the page URL or the app state. | 607 * Encode current state into the page URL or the app state. |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
665 delete window.appState.time; | 642 delete window.appState.time; |
666 util.saveAppState(); | 643 util.saveAppState(); |
667 return; | 644 return; |
668 }; | 645 }; |
669 | 646 |
670 /** | 647 /** |
671 * Create video controls. | 648 * Create video controls. |
672 * | 649 * |
673 * @param {!HTMLElement} containerElement The container for the controls. | 650 * @param {!HTMLElement} containerElement The container for the controls. |
674 * @param {function(Event)} onMediaError Function to display an error message. | 651 * @param {function(Event)} onMediaError Function to display an error message. |
| 652 * @param {function(string):string} stringFunction Function providing localized |
| 653 * strings. |
675 * @param {function(Event)=} opt_fullScreenToggle Function to toggle fullscreen | 654 * @param {function(Event)=} opt_fullScreenToggle Function to toggle fullscreen |
676 * mode. | 655 * mode. |
677 * @param {HTMLElement=} opt_stateIconParent The parent for the icon that | 656 * @param {HTMLElement=} opt_stateIconParent The parent for the icon that |
678 * gives visual feedback when the playback state changes. | 657 * gives visual feedback when the playback state changes. |
679 * @constructor | 658 * @constructor |
680 * @struct | 659 * @struct |
681 * @extends {MediaControls} | 660 * @extends {MediaControls} |
682 */ | 661 */ |
683 function VideoControls( | 662 function VideoControls(containerElement, onMediaError, stringFunction, |
684 containerElement, onMediaError, opt_fullScreenToggle, opt_stateIconParent) { | 663 opt_fullScreenToggle, opt_stateIconParent) { |
685 MediaControls.call(this, containerElement, onMediaError); | 664 MediaControls.call(this, containerElement, onMediaError); |
| 665 this.stringFunction_ = stringFunction; |
686 | 666 |
687 this.container_.classList.add('video-controls'); | 667 this.container_.classList.add('video-controls'); |
688 this.initPlayButton(); | 668 this.initPlayButton(); |
689 this.initTimeControls(); | 669 this.initTimeControls(); |
690 this.initVolumeControls(); | 670 this.initVolumeControls(); |
691 | 671 |
692 // Create the cast button. | 672 // Create the cast button. |
693 // We need to use <button> since cr.ui.MenuButton.decorate modifies prototype | 673 this.castButton_ = this.createButton('cast menubutton'); |
694 // chain, by which <files-icon-button> will not work correctly. | |
695 // TODO(fukino): Find a way to use files-icon-button consistently. | |
696 this.castButton_ = this.createControl( | |
697 'cast media-button', undefined, 'button'); | |
698 this.castButton_.setAttribute('menu', '#cast-menu'); | 674 this.castButton_.setAttribute('menu', '#cast-menu'); |
699 this.castButton_.setAttribute('aria-label', str('VIDEO_PLAYER_PLAY_ON')); | 675 this.castButton_.setAttribute( |
700 this.castButton_.setAttribute('state', MediaControls.ButtonStateType.DEFAULT); | 676 'label', this.stringFunction_('VIDEO_PLAYER_PLAY_ON')); |
701 this.castButton_.appendChild(document.createElement('files-ripple')); | |
702 cr.ui.decorate(this.castButton_, cr.ui.MenuButton); | 677 cr.ui.decorate(this.castButton_, cr.ui.MenuButton); |
703 | 678 |
704 if (opt_fullScreenToggle) { | 679 if (opt_fullScreenToggle) { |
705 this.fullscreenButton_ = | 680 this.fullscreenButton_ = |
706 this.createButton('fullscreen', opt_fullScreenToggle); | 681 this.createButton('fullscreen', opt_fullScreenToggle); |
707 this.fullscreenButton_.setAttribute('aria-label', | |
708 str('VIDEO_PLAYER_FULL_SCREEN_BUTTON_LABEL')); | |
709 } | 682 } |
710 | 683 |
711 if (opt_stateIconParent) { | 684 if (opt_stateIconParent) { |
712 this.stateIcon_ = this.createControl( | 685 this.stateIcon_ = this.createControl( |
713 'playback-state-icon', opt_stateIconParent); | 686 'playback-state-icon', opt_stateIconParent); |
714 this.textBanner_ = this.createControl('text-banner', opt_stateIconParent); | 687 this.textBanner_ = this.createControl('text-banner', opt_stateIconParent); |
715 } | 688 } |
716 | 689 |
717 // Disables all controls at first. | 690 // Disables all controls at first. |
718 this.enableControls_(false); | 691 this.enableControls_(false); |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
764 }; | 737 }; |
765 | 738 |
766 /** | 739 /** |
767 * Shows a text banner. | 740 * Shows a text banner. |
768 * | 741 * |
769 * @param {string} identifier String identifier. | 742 * @param {string} identifier String identifier. |
770 * @private | 743 * @private |
771 */ | 744 */ |
772 VideoControls.prototype.showTextBanner_ = function(identifier) { | 745 VideoControls.prototype.showTextBanner_ = function(identifier) { |
773 this.textBanner_.removeAttribute('visible'); | 746 this.textBanner_.removeAttribute('visible'); |
774 this.textBanner_.textContent = str(identifier); | 747 this.textBanner_.textContent = this.stringFunction_(identifier); |
775 | 748 |
776 setTimeout(function() { | 749 setTimeout(function() { |
777 var onAnimationEnd = function(event) { | 750 var onAnimationEnd = function(event) { |
778 this.textBanner_.removeEventListener( | 751 this.textBanner_.removeEventListener( |
779 'webkitAnimationEnd', onAnimationEnd); | 752 'webkitAnimationEnd', onAnimationEnd); |
780 this.textBanner_.removeAttribute('visible'); | 753 this.textBanner_.removeAttribute('visible'); |
781 }.bind(this); | 754 }.bind(this); |
782 this.textBanner_.addEventListener('webkitAnimationEnd', onAnimationEnd); | 755 this.textBanner_.addEventListener('webkitAnimationEnd', onAnimationEnd); |
783 | 756 |
784 this.textBanner_.setAttribute('visible', 'true'); | 757 this.textBanner_.setAttribute('visible', 'true'); |
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
909 var hideBelow = function(selector, limit) { | 882 var hideBelow = function(selector, limit) { |
910 this.container_.querySelector(selector).style.display = | 883 this.container_.querySelector(selector).style.display = |
911 width < limit ? 'none' : '-webkit-box'; | 884 width < limit ? 'none' : '-webkit-box'; |
912 }.bind(this); | 885 }.bind(this); |
913 | 886 |
914 hideBelow('.time', 350); | 887 hideBelow('.time', 350); |
915 hideBelow('.volume', 275); | 888 hideBelow('.volume', 275); |
916 hideBelow('.volume-controls', 210); | 889 hideBelow('.volume-controls', 210); |
917 hideBelow('.fullscreen', 150); | 890 hideBelow('.fullscreen', 150); |
918 }; | 891 }; |
919 | |
920 /** | |
921 * Updates video control when the window is fullscreened or restored. | |
922 * @param {boolean} fullscreen True if the window gets fullscreened. | |
923 */ | |
924 VideoControls.prototype.onFullScreenChanged = function(fullscreen) { | |
925 if (fullscreen) { | |
926 this.container_.setAttribute('fullscreen', ''); | |
927 } else { | |
928 this.container_.removeAttribute('fullscreen'); | |
929 } | |
930 | |
931 if (this.fullscreenButton_) { | |
932 this.fullscreenButton_.setAttribute('aria-label', | |
933 fullscreen ? str('VIDEO_PLAYER_EXIT_FULL_SCREEN_BUTTON_LABEL') | |
934 : str('VIDEO_PLAYER_FULL_SCREEN_BUTTON_LABEL'));; | |
935 } | |
936 }; | |
OLD | NEW |