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