Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(127)

Side by Side Diff: ui/file_manager/video_player/js/media_controls.js

Issue 1415953006: Fix accessibility issues in AudioPlayer and VideoPlayer. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix query for repeat button. Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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
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
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 };
OLDNEW
« no previous file with comments | « ui/file_manager/video_player/css/media_controls.css ('k') | ui/file_manager/video_player/js/video_player.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698