Backed out 3 changesets (bug 1580095) for causing mochitests failures in browser_audioScrubber.js. CLOSED TREE

Backed out changeset fa8f9b71e4db (bug 1580095)
Backed out changeset 2d042439b759 (bug 1580095)
Backed out changeset 66a695629d4b (bug 1580095)
This commit is contained in:
Stanca Serban 2023-06-16 18:32:48 +03:00
parent ee3137f15f
commit cf0c7e1d74
9 changed files with 41 additions and 475 deletions

View File

@ -74,18 +74,6 @@ class PictureInPictureVideoWrapper {
setCurrentTime(video, position) {
this.player.seek(position * 1000);
}
setVolume(video, volume) {
this.player.setVolume(volume);
}
getVolume() {
return this.player.getVolume();
}
setMuted(video, shouldMute) {
this.player.setMuted(shouldMute);
}
isMuted() {
return this.player.isMuted();
}
}
this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;

View File

@ -5,19 +5,14 @@
"use strict";
class PictureInPictureVideoWrapper {
constructor(video) {
this.player = video.closest("#movie_player")?.wrappedJSObject;
}
isLive(video) {
return !!document.querySelector(".ytp-live");
}
setMuted(video, shouldMute) {
if (this.player) {
if (shouldMute) {
this.player.mute();
} else {
this.player.unMute();
}
let muteButton = document.querySelector("#player .ytp-mute-button");
if (video.muted !== shouldMute && muteButton) {
muteButton.click();
} else {
video.muted = shouldMute;
}
@ -65,19 +60,6 @@ class PictureInPictureVideoWrapper {
shouldHideToggle(video) {
return !!video.closest(".ytd-video-preview");
}
setVolume(video, volume) {
if (this.player) {
this.player.setVolume(volume * 100);
} else {
video.volume = volume;
}
}
getVolume(video) {
if (this.player) {
return this.player.getVolume() / 100;
}
return video.volume;
}
}
this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;

View File

@ -224,7 +224,6 @@ export class PictureInPictureLauncherChild extends JSWindowActorChild {
webVTTSubtitles: !!video.textTracks?.length,
scrubberPosition,
timestamp,
volume: PictureInPictureChild.videoWrapper.getVolume(video),
});
let args = {
@ -1940,9 +1939,6 @@ export class PictureInPictureChild extends JSWindowActorChild {
} else {
this.sendAsyncMessage("PictureInPicture:Unmuting");
}
this.sendAsyncMessage("PictureInPicture:VolumeChange", {
volume: this.videoWrapper.getVolume(video),
});
break;
}
case "resize": {
@ -2147,12 +2143,6 @@ export class PictureInPictureChild extends JSWindowActorChild {
this.setVideoTime(scrubberPosition, wasPlaying);
break;
}
case "PictureInPicture:SetVolume": {
const { volume } = message.data;
let video = this.getWeakVideo();
this.videoWrapper.setVolume(video, volume);
break;
}
}
}
@ -2575,16 +2565,12 @@ export class PictureInPictureChild extends JSWindowActorChild {
this.closePictureInPicture({ reason: "closePlayerShortcut" });
break;
case "downArrow" /* Volume decrease */:
if (
this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.VOLUME) ||
this.videoWrapper.isMuted(video)
) {
if (this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.VOLUME)) {
return;
}
oldval = this.videoWrapper.getVolume(video);
newval = oldval < 0.1 ? 0 : oldval - 0.1;
this.videoWrapper.setVolume(video, newval);
this.videoWrapper.setMuted(video, newval === 0);
this.videoWrapper.setVolume(video, oldval < 0.1 ? 0 : oldval - 0.1);
this.videoWrapper.setMuted(video, false);
break;
case "upArrow" /* Volume increase */:
if (this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.VOLUME)) {

View File

@ -186,12 +186,6 @@ export class PictureInPictureParent extends JSWindowActorParent {
player.setScrubberPosition(scrubberPosition);
break;
}
case "PictureInPicture:VolumeChange": {
let { volume } = aMessage.data;
let player = PictureInPicture.getWeakPipPlayer(this);
player.setVolume(volume);
break;
}
}
}
}
@ -821,7 +815,6 @@ export var PictureInPicture = {
win.setScrubberPosition(videoData.scrubberPosition);
win.setTimestamp(videoData.timestamp);
win.setVolume(videoData.volume);
Services.prefs.setBoolPref(TOGGLE_HAS_USED_PREF, true);

View File

@ -115,10 +115,6 @@ function setTimestamp(timeString) {
Player.setTimestamp(timeString);
}
function setVolume(volume) {
Player.setVolume(volume);
}
/**
* The Player object handles initializing the player, holds state, and handles
* events for updating state.
@ -240,19 +236,6 @@ let Player = {
this.handleScrubbingDone(event);
});
this.audioScrubber.addEventListener("input", event => {
this.audioScrubbing = true;
this.handleAudioScrubbing(event.target.value);
});
this.audioScrubber.addEventListener("change", event => {
this.audioScrubbing = false;
});
this.audioScrubber.addEventListener("pointerdown", event => {
if (this.isMuted) {
this.audioScrubber.max = 1;
}
});
for (let radio of document.querySelectorAll(
'input[type=radio][name="cc-size"]'
)) {
@ -276,9 +259,6 @@ let Player = {
if (Services.prefs.getBoolPref(AUDIO_TOGGLE_ENABLED_PREF, false)) {
const audioButton = document.getElementById("audio");
audioButton.hidden = false;
const audioScrubber = document.getElementById("audio-scrubber");
audioScrubber.hidden = false;
}
if (Services.prefs.getBoolPref(CAPTIONS_ENABLED_PREF, false)) {
@ -509,8 +489,7 @@ let Player = {
handleScrubbing(event) {
// When using the keyboard to scrub, we get both a keydown and an input
// event. The input event is fired after the keydown and we have already
// handled the keydown event in onKeyDown so we set preventNextInputEvent
// to true in onKeyDown as to not set the current time twice.
// handle the keydown event in onKeyDown and we don't want to handle it twice
if (this.preventNextInputEvent) {
this.preventNextInputEvent = false;
return;
@ -543,36 +522,6 @@ let Player = {
this.scrubbing = false;
},
/**
* Set the volume on the video and unmute if the video was muted.
* If the volume is changed via the keyboard, onKeyDown will set
* this.preventNextInputEvent to true.
* @param {Number} volume A number between 0 and 1 that represents the volume
*/
handleAudioScrubbing(volume) {
// When using the keyboard to adjust the volume, we get both a keydown and
// an input event. The input event is fired after the keydown event and we
// have already handled the keydown event in onKeyDown so we set
// preventNextInputEvent to true in onKeyDown as to not set the volume twice.
if (this.preventNextInputEvent) {
this.preventNextInputEvent = false;
return;
}
if (this.isMuted) {
this.isMuted = false;
this.actor.sendAsyncMessage("PictureInPicture:Unmute");
}
if (volume == 0) {
this.actor.sendAsyncMessage("PictureInPicture:Mute");
}
this.actor.sendAsyncMessage("PictureInPicture:SetVolume", {
volume,
});
},
getScrubberPositionFromEvent(event) {
return event.target.value;
},
@ -600,14 +549,6 @@ let Player = {
this.timestamp.hidden = timestamp === undefined;
},
setVolume(volume) {
if (volume < Number.EPSILON) {
this.actor.sendAsyncMessage("PictureInPicture:Mute");
}
this.audioScrubber.value = volume;
},
closePipWindow(closeData) {
// Set the subtitles font size prefs
Services.prefs.setBoolPref(
@ -636,7 +577,11 @@ let Player = {
onClick(event) {
switch (event.target.id) {
case "audio": {
this.toggleMute();
if (this.isMuted) {
this.actor.sendAsyncMessage("PictureInPicture:Unmute");
} else {
this.actor.sendAsyncMessage("PictureInPicture:Mute");
}
break;
}
@ -772,20 +717,6 @@ let Player = {
}
},
/**
* Toggle the mute state of the video
*/
toggleMute() {
if (this.isMuted) {
// We unmute in handleAudioScrubbing so no need to also do it here
this.audioScrubber.max = 1;
this.handleAudioScrubbing(this.lastVolume ?? 1);
} else {
this.lastVolume = this.audioScrubber.value;
this.actor.sendAsyncMessage("PictureInPicture:Mute");
}
},
resizeToVideo(rect) {
if (this.isFullscreen) {
// We store the size and position because resizing the PiP window
@ -818,7 +749,7 @@ let Player = {
};
// If the up or down arrow is pressed while the scrubber is focused then we
// want to hijack these keydown events to act as left or right arrows
// want to hijack these keydown events to act as left or right arrow
// respectively to correctly seek the video.
if (
event.target.id === "scrubber" &&
@ -832,33 +763,17 @@ let Player = {
eventKeys.keyCode = window.KeyEvent.DOM_VK_LEFT;
}
// If the left or right arrow is pressed while the audio scrubber is focused
// then we want to hijack these keydown events to act as up or down arrows
// respectively to correctly change the volume.
// If the keydown event was one of the arrow keys and the scrubber was
// focused then we will also get an input event that will overwrite the
// keydown event if we dont' prevent the input event.
if (
event.target.id === "audio-scrubber" &&
event.keyCode === window.KeyEvent.DOM_VK_RIGHT
) {
eventKeys.keyCode = window.KeyEvent.DOM_VK_UP;
} else if (
event.target.id === "audio-scrubber" &&
event.keyCode === window.KeyEvent.DOM_VK_LEFT
) {
eventKeys.keyCode = window.KeyEvent.DOM_VK_DOWN;
}
// If the keydown event was one of the arrow keys and the scrubber or the
// audio scrubber was focused then we want to prevent the subsequent input
// event from overwriting the keydown event.
if (
event.target.id === "audio-scrubber" ||
(event.target.id === "scrubber" &&
[
window.KeyEvent.DOM_VK_LEFT,
window.KeyEvent.DOM_VK_RIGHT,
window.KeyEvent.DOM_VK_UP,
window.KeyEvent.DOM_VK_DOWN,
].includes(event.keyCode))
event.target.id === "scrubber" &&
[
window.KeyEvent.DOM_VK_LEFT,
window.KeyEvent.DOM_VK_RIGHT,
window.KeyEvent.DOM_VK_UP,
window.KeyEvent.DOM_VK_DOWN,
].includes(event.keyCode)
) {
this.preventNextInputEvent = true;
}
@ -1160,11 +1075,6 @@ let Player = {
return (this.scrubber = document.getElementById("scrubber"));
},
get audioScrubber() {
delete this.audioScrubber;
return (this.audioScrubber = document.getElementById("audio-scrubber"));
},
get timestamp() {
delete this.timestamp;
return (this.timestamp = document.getElementById("timestamp"));
@ -1233,11 +1143,6 @@ let Player = {
set isMuted(isMuted) {
this._isMuted = isMuted;
if (!isMuted) {
this.audioScrubber.max = 1;
} else if (!this.audioScrubbing) {
this.audioScrubber.max = 0;
}
this.controls.classList.toggle("muted", isMuted);
let strId = isMuted
? `pictureinpicture-unmute-btn`

View File

@ -45,25 +45,25 @@
class="control-item control-button tooltip-under-controls" data-l10n-attrs="tooltip"
#ifdef XP_MACOSX
mac="true"
tabindex="9"
tabindex="8"
#else
tabindex="10"
tabindex="9"
#endif
/>
<button id="unpip"
class="control-item control-button tooltip-under-controls" data-l10n-id="pictureinpicture-unpip-btn" data-l10n-attrs="tooltip"
#ifdef XP_MACOSX
mac="true"
tabindex="10"
#else
tabindex="9"
#else
tabindex="8"
#endif
/>
<div id="controls-bottom-gradient" class="control-item"></div>
<div id="controls-bottom">
<div class="controls-bottom-upper">
<div class="scrubber-no-drag">
<input id="scrubber" class="control-item" min="0" max="1" step=".001" type="range" tabindex="11" hidden="true"/>
<input id="scrubber" class="control-item" min="0" max="1" step=".001" type="range" tabindex="10" hidden="true"/>
</div>
</div>
<div class="controls-bottom-lower">
@ -71,22 +71,21 @@
<div id="timestamp" class="control-item" hidden="true"></div>
</div>
<div class="center-controls">
<button id="seekBackward" class="control-item control-button tooltip-over-controls center-tooltip" hidden="true" data-l10n-id="pictureinpicture-seekbackward-btn" data-l10n-attrs="tooltip" tabindex="12"></button>
<button id="seekBackward" class="control-item control-button tooltip-over-controls center-tooltip" hidden="true" data-l10n-id="pictureinpicture-seekbackward-btn" data-l10n-attrs="tooltip" tabindex="11"></button>
<button id="playpause" class="control-item control-button tooltip-over-controls center-tooltip" tabindex="1"
data-l10n-id="pictureinpicture-pause-btn" data-l10n-attrs="tooltip"/>
<button id="seekForward" class="control-item control-button tooltip-over-controls center-tooltip" hidden="true" data-l10n-id="pictureinpicture-seekforward-btn" data-l10n-attrs="tooltip" tabindex="2"></button>
</div>
<div class="end-controls">
<button id="audio" class="control-item control-button tooltip-over-controls center-tooltip" hidden="true" data-l10n-attrs="tooltip" tabindex="3"/>
<input id="audio-scrubber" class="control-item" min="0" max="1" step=".001" type="range" tabindex="4" hidden="true"/>
<button id="closed-caption" class="control-item control-button tooltip-over-controls center-tooltip closed-caption" hidden="true" disabled="true" data-l10n-id="pictureinpicture-subtitles-btn" data-l10n-attrs="tooltip" tabindex="5"></button>
<button id="closed-caption" class="control-item control-button tooltip-over-controls center-tooltip closed-caption" hidden="true" disabled="true" data-l10n-id="pictureinpicture-subtitles-btn" data-l10n-attrs="tooltip" tabindex="4"></button>
<div id="settings" class="hide panel">
<fieldset class="box panel-fieldset">
<legend class="a11y-only panel-legend" data-l10n-id="pictureinpicture-subtitles-panel-accessible"></legend>
<div class="subtitle-grid">
<label id="subtitles-toggle-label" data-l10n-id="pictureinpicture-subtitles-label" class="bold" for="subtitles-toggle"></label>
<label class="switch">
<input id="subtitles-toggle" type="checkbox" tabindex="6" checked=""/>
<input id="subtitles-toggle" type="checkbox" tabindex="5" checked=""/>
<span class="slider" role="presentation"></span>
</label>
</div>
@ -94,22 +93,22 @@
<fieldset class="font-size-selection panel-fieldset">
<legend data-l10n-id="pictureinpicture-font-size-label" class="bold panel-legend"></legend>
<div id="font-size-selection-radio-small" class="font-size-selection-radio">
<input id="small" type="radio" name="cc-size" tabindex="7"/>
<input id="small" type="radio" name="cc-size" tabindex="6"/>
<label data-l10n-id="pictureinpicture-font-size-small" for="small"></label>
</div>
<div id="font-size-selection-radio-medium" class="font-size-selection-radio">
<input id="medium" type="radio" name="cc-size" tabindex="7"/>
<input id="medium" type="radio" name="cc-size" tabindex="6"/>
<label data-l10n-id="pictureinpicture-font-size-medium" for="medium"></label>
</div>
<div id="font-size-selection-radio-large" class="font-size-selection-radio">
<input id="large" type="radio" name="cc-size" tabindex="7"/>
<input id="large" type="radio" name="cc-size" tabindex="6"/>
<label data-l10n-id="pictureinpicture-font-size-large" for="large"></label>
</div>
</fieldset>
</fieldset>
<div class="arrow"></div>
</div>
<button id="fullscreen" class="control-item control-button tooltip-over-controls inline-end-tooltip" hidden="true" data-l10n-attrs="tooltip" tabindex="8"></button>
<button id="fullscreen" class="control-item control-button tooltip-over-controls inline-end-tooltip" hidden="true" data-l10n-attrs="tooltip" tabindex="7"></button>
</div>
</div>
</div>

View File

@ -44,7 +44,6 @@ prefs =
[browser_aaa_run_first_firstTimePiPToggleEvents.js]
[browser_aaa_telemetry_togglePiP.js]
[browser_audioScrubber.js]
[browser_backgroundTab.js]
[browser_cannotTriggerFromContent.js]
[browser_changePiPSrcInFullscreen.js]

View File

@ -1,243 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const IMPROVED_CONTROLS_ENABLED_PREF =
"media.videocontrols.picture-in-picture.improved-video-controls.enabled";
const videoID = "with-controls";
async function getVideoVolume(browser, videoID) {
return SpecialPowers.spawn(browser, [videoID], async videoID => {
return content.document.getElementById(videoID).volume;
});
}
async function getVideoMuted(browser, videoID) {
return SpecialPowers.spawn(browser, [videoID], async videoID => {
return content.document.getElementById(videoID).muted;
});
}
add_task(async function test_audioScrubber() {
await SpecialPowers.pushPrefEnv({
set: [[IMPROVED_CONTROLS_ENABLED_PREF, true]],
});
await BrowserTestUtils.withNewTab(
{
url: TEST_PAGE,
gBrowser,
},
async browser => {
await ensureVideosReady(browser);
let pipWin = await triggerPictureInPicture(browser, videoID);
ok(pipWin, "Got Picture-in-Picture window.");
// Resize the PiP window so we know the audio scrubber is visible
pipWin.resizeTo(750, 750);
let audioScrubber = pipWin.document.getElementById("audio-scrubber");
audioScrubber.focus();
// Volume should be 1 and video should not be muted when opening this video
let actualVolume = await getVideoVolume(browser, videoID);
let expectedVolume = 1;
let actualMuted = await getVideoMuted(browser, videoID);
isfuzzy(
actualVolume,
expectedVolume,
Number.EPSILON,
`The actual volume ${actualVolume}. The expected volume ${expectedVolume}`
);
ok(!actualMuted, "Video is not muted");
let volumeChangePromise = BrowserTestUtils.waitForContentEvent(
browser,
"volumechange",
true
);
// Test that left arrow key changes volume by -0.1
EventUtils.synthesizeKey("KEY_ArrowLeft", {}, pipWin);
await volumeChangePromise;
actualVolume = await getVideoVolume(browser, videoID);
expectedVolume = 0.9;
isfuzzy(
actualVolume,
expectedVolume,
Number.EPSILON,
`The actual volume ${actualVolume}. The expected volume ${expectedVolume}`
);
// Test that right arrow key changes volume by +0.1 and does not exceed 1
EventUtils.synthesizeKey("KEY_ArrowRight", {}, pipWin);
EventUtils.synthesizeKey("KEY_ArrowRight", {}, pipWin);
EventUtils.synthesizeKey("KEY_ArrowRight", {}, pipWin);
actualVolume = await getVideoVolume(browser, videoID);
expectedVolume = 1;
isfuzzy(
actualVolume,
expectedVolume,
Number.EPSILON,
`The actual volume ${actualVolume}. The expected volume ${expectedVolume}`
);
// Test that the mouse can move the audio scrubber to 0.5
let rect = audioScrubber.getBoundingClientRect();
volumeChangePromise = BrowserTestUtils.waitForContentEvent(
browser,
"volumechange",
true
);
EventUtils.synthesizeMouse(
audioScrubber,
rect.width / 2,
rect.height / 2,
{},
pipWin
);
await volumeChangePromise;
actualVolume = await getVideoVolume(browser, videoID);
expectedVolume = 0.5;
isfuzzy(
actualVolume,
expectedVolume,
Number.EPSILON,
`The actual volume ${actualVolume}. The expected volume ${expectedVolume}`
);
// Test muting and unmuting the video
let muteButton = pipWin.document.getElementById("audio");
muteButton.click();
ok(
await getVideoMuted(browser, videoID),
"The video is muted aftering clicking the mute button"
);
is(
audioScrubber.max,
"0",
"The max of the audio scrubber is 0, so the volume slider appears that the volume is 0"
);
muteButton.click();
ok(
!(await getVideoMuted(browser, videoID)),
"The video is muted aftering clicking the mute button"
);
isfuzzy(
actualVolume,
expectedVolume,
Number.EPSILON,
`The volume is still ${actualVolume}.`
);
volumeChangePromise = BrowserTestUtils.waitForContentEvent(
browser,
"volumechange",
true
);
// Test that the audio scrubber can be dragged from 0.5 to 0 and the video gets muted
EventUtils.synthesizeMouse(
audioScrubber,
rect.width / 2,
rect.height / 2,
{ type: "mousedown" },
pipWin
);
EventUtils.synthesizeMouse(
audioScrubber,
0,
rect.height / 2,
{ type: "mousemove" },
pipWin
);
EventUtils.synthesizeMouse(
audioScrubber,
0,
rect.height / 2,
{ type: "mouseup" },
pipWin
);
await volumeChangePromise;
actualVolume = await getVideoVolume(browser, videoID);
expectedVolume = 0;
isfuzzy(
actualVolume,
expectedVolume,
Number.EPSILON,
`The actual volume ${actualVolume}. The expected volume ${expectedVolume}`
);
ok(
await getVideoMuted(browser, videoID),
"Video is now muted because slider was moved to 0"
);
// Test that the left arrow key does not unmute the video and the volume remains at 0
EventUtils.synthesizeKey("KEY_ArrowLeft", {}, pipWin);
EventUtils.synthesizeKey("KEY_ArrowLeft", {}, pipWin);
actualVolume = await getVideoVolume(browser, videoID);
isfuzzy(
actualVolume,
expectedVolume,
Number.EPSILON,
`The actual volume ${actualVolume}. The expected volume ${expectedVolume}`
);
ok(
await getVideoMuted(browser, videoID),
"Video is now muted because slider is still at 0"
);
volumeChangePromise = BrowserTestUtils.waitForContentEvent(
browser,
"volumechange",
true
);
// Test that the right arrow key will increase the volume by +0.1 and will unmute the video
EventUtils.synthesizeKey("KEY_ArrowRight", {}, pipWin);
await volumeChangePromise;
actualVolume = await getVideoVolume(browser, videoID);
expectedVolume = 0.1;
isfuzzy(
actualVolume,
expectedVolume,
Number.EPSILON,
`The actual volume ${actualVolume}. The expected volume ${expectedVolume}`
);
ok(
!(await getVideoMuted(browser, videoID)),
"Video is no longer muted because we moved the slider"
);
}
);
});

View File

@ -104,8 +104,8 @@ browser {
.end-controls {
display: grid;
grid-template-columns: 1fr 2fr 1fr 1fr;
grid-template-areas: "audio audio-scrubber closedcaption fullscreen";
grid-template-columns: repeat(3, 1fr);
grid-template-areas: "audio closedcaption fullscreen";
justify-self: end;
gap: 8px;
}
@ -253,7 +253,8 @@ body:fullscreen #controls[showing]:hover .control-item:not(:hover),
/* Background gradient is the only control item with a fixed opacity value. */
#controls[keying] .control-item#controls-bottom-gradient,
#controls[showing] .control-item#controls-bottom-gradient,
#controls:hover .control-item#controls-bottom-gradient:hover #controls[donthide] .control-item#controls-bottom-gradient {
#controls:hover .control-item#controls-bottom-gradient:hover
#controls[donthide] .control-item#controls-bottom-gradient {
opacity: 0.8;
}
@ -305,39 +306,6 @@ body:fullscreen #controls[showing]:hover {
grid-area: audio;
}
#audio-scrubber {
grid-area: audio-scrubber;
width: 64px;
background-color: transparent;
padding: 15px 0;
margin: 0;
}
#audio-scrubber::-moz-range-thumb {
border-radius: 8px;
background-color: #FFFFFF;
position: relative;
width: 8px;
height: 8px;
bottom: 24px;
padding: 0;
}
#audio-scrubber::-moz-range-track {
background-color: #969696;
}
#audio-scrubber::-moz-range-progress {
background-color: #FFFFFF;
}
#audio-scrubber,
#audio-scrubber::-moz-range-track,
#audio-scrubber::-moz-range-progress {
height: 2px;
border-radius: 4px;
}
#fullscreen {
grid-area: fullscreen;
}
@ -418,7 +386,7 @@ body:not(:fullscreen) #fullscreen {
font-weight: 590;
}
.box > input[type="radio"] {
.box > input[type="radio"]{
background-color: red;
fill: currentColor;
}
@ -623,17 +591,6 @@ input:checked + .slider::before {
content: unset;
}
@media (width <= 630px) {
#audio-scrubber {
display: none;
}
.end-controls {
grid-template-columns: repeat(3, 1fr);
grid-template-areas: "audio closedcaption fullscreen";
}
}
@media (height <= 325px), (width <= 475px) {
#closed-caption {
display: none;