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

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

Powered by Google App Engine
This is Rietveld 408576698