// ==UserScript==
// @name GDC Vault Player Extended
// @namespace Level3Manatee
// @match https://gdcvault.com/play/*
// @match https://www.gdcvault.com/play/*
// @match https://gdcvault.blazestreaming.com/*
// @grant none
// @version 1.1.0
// @author Level3Manatee
// @license MIT
// @description Extends GDC Vault player functionality: keyboard controls (see ? button), dark mode, stereo / mono toggle, ... and makes it bigger. Saves & restores playback position, subtitle settings and dark mode preference (LocalStorage)
// @supportURL https://github.com/Level3Manatee/GDCVaultPlayerExtended
// ==/UserScript==
/** Notes
* Player is video.js;
API https://docs.videojs.com/player
and https://videojs.readthedocs.io/en/latest/
* The videoplayer is hosted by a third party and in an iframe,
so all actions need to be forwarded as a message,
and executed inside the iframe context.
*/
const isIframe = window.location.href.match(/gdcvault\.com/) === null;
const iframeEl = document.querySelector("#player iframe");
const isVideoContent = isIframe || iframeEl.src.match(/gdcvault\.blazestreaming\.com/) !== null;
const frameDuration = 1 / 30; // for frame-by-frame seeking (there is no good way to determine framerate programatically)
/** Key binds
* keys: { KeyboardEvent.key: preventDefault }
* */
const Actions = {
Pause: {
name: "Play / pause",
keys: {
" ": true,
"k": false
},
execute: Pause,
osd: function () { return player.paused() ? "Pause" : "Play"; }
},
Mute: {
name: "Mute / unmute",
keys: {
"m": false
},
execute: Mute,
osd: function () { return player.muted() ? "Muted" : `Vol ${player.volume().toFixed(1)}`; }
},
SeekBack: {
name: "Seek backward 5 seconds",
keys: {
"ArrowLeft": false
},
execute: function () { return Promise.resolve( Seek(-5.0) ); },
osd: function () { osdTimeDisplay.updateTextNode_(player.currentTime()); return osdTimeDisplay.formattedTime_; }
},
SeekForward: {
name: "Seek forward 10 seconds",
keys: {
"ArrowRight": false
},
execute: function () { return Promise.resolve( Seek(10.0) ); },
osd: function () { osdTimeDisplay.updateTextNode_(player.currentTime()); return osdTimeDisplay.formattedTime_; }
},
VolumeDown: {
name: "Decrease Volume",
keys: {
"ArrowDown": true
},
execute: function () { return Promise.resolve( Volume(-0.1) ); },
osd: function () { return `Vol ${player.volume().toFixed(1)}`; }
},
VolumeUp: {
name: "Increase Volume",
keys: {
"ArrowUp": true
},
execute: function () { return Promise.resolve( Volume(0.1) ); },
osd: function () { return `Vol ${player.volume().toFixed(1)}`; }
},
CycleMonoStereo: {
name: "Cycle Stereo / Mono (L) / Mono (R)",
keys: {
"a": false
},
execute: CycleMonoStereo,
osd: function () { switch (audioChannelMode) { case 0: return "Stereo"; case 1: return "Mono (left)"; case 2: return "Mono (right)"; } }
},
Fullscreen: {
name: "Fullscreen",
keys: {
"f": false
},
execute: Fullscreen
},
PrevFrame: {
name: "Previous frame",
keys: {
",": false
},
execute: function () { return Promise.resolve( SeekFrame(-1) ); }
},
NextFrame: {
name: "Next frame",
keys: {
".": false
},
execute: function () { return Promise.resolve( SeekFrame(1) ); }
},
PlaybackSlower: {
name: "Decrease playback speed",
keys: {
"<": false,
"[": false
},
execute: function () { return Promise.resolve( PlaybackRate(-1) ); },
osd: function () { return `${playbackRate}x`; }
},
PlaybackFaster: {
name: "Increase playback speed",
keys: {
">": false,
"]": false
},
execute: function () { return Promise.resolve( PlaybackRate(1) ); },
osd: function () { return `${playbackRate}x`; }
},
PlaybackDefault: {
name: "Default playback speed (1.0x)",
keys: {
"Backspace": false
},
execute: function () { return Promise.resolve( PlaybackRate(0) ); },
osd: function () { return `${playbackRate}x`; }
},
ToggleSubtitles: {
name: "Toggle subtitles on / off",
keys: {
"v": false,
"s": false
},
execute: ToggleSubtitles,
osd: function () { return SubsAreVisible() ? "Subtitles on" : "Subtitles off"; }
},
CycleSubtitles: {
name: "Cycle through subtitle tracks",
keys: {
"j": false,
"S": false
},
execute: CycleSubtitles,
osd: function () { return subtitles[currentSubtitle] !== undefined ? subtitles[currentSubtitle].language : "No subtitles available"; }
},
ToggleDarkMode: {
name: "Toggle Dark Mode",
keys: {
"d": false
},
execute: ToggleDarkMode,
osd: function () { return isDarkMode ? "Dark Mode on" : "Dark mode off"; }
},
OpenModal: {
name: "Open/close help dialog",
keys: {
"h": false,
"Escape": false
},
execute: ToggleModal
}
}
// export for player button event handlers
window['playerActionShurtcuts'] = { actions: Actions };
let BindingsKeys = [];
for (let [actionName, action] of Object.entries(Actions)) {
for (let [key, preventDefault] of Object.entries(action.keys))
BindingsKeys[key] = {action: Actions[actionName], preventDefault: preventDefault}
}
let dispatchTimeout = -1;
window.addEventListener("keydown", evt => {
// don't dispatch actions when input (e.g. search bar) has focus
if (evt.target.nodeName.toLowerCase() === 'input')
return;
if (BindingsKeys[evt.key] === undefined) return;
if (BindingsKeys[evt.key].preventDefault === true && isVideoContent)
evt.preventDefault();
clearTimeout(dispatchTimeout);
dispatchTimeout = setTimeout(DispatchAction, 0, evt.key);
// DispatchAction(evt.key);
});
/** Listen for forwarded event messages */
window.addEventListener("message", evt => {
if (evt.origin !== "https://gdcvault.com"
&& evt.origin !== "https://www.gdcvault.com"
&& evt.origin !== "https://gdcvault.blazestreaming.com")
return;
DispatchAction(evt.data);
}, false);
function DispatchAction (binding) {
if (isIframe) {
if (BindingsKeys[binding] === undefined)
return;
BindingsKeys[binding].action.execute().then(()=> {
if (BindingsKeys[binding].action.osd !== undefined)
OSD(BindingsKeys[binding].action.osd);
});
} else {
switch (binding) {
case "ToggleDarkMode":
ToggleDarkMode();
break;
case "Fullscreen":
// Fullscreen needs to be applied to the iframe (container)
Fullscreen();
break;
default:
DispatchToIframe(binding);
}
}
}
function DispatchToIframe (binding) {
if (isVideoContent)
iframeEl.contentWindow.postMessage(binding, "https://gdcvault.blazestreaming.com/");
}
function DispatchToParent (binding) {
if (self === top) // return if parent is this (i.e. not in iframe or iframe content without parent)
return false;
parent.postMessage(binding, "https://gdcvault.com");
parent.postMessage(binding, "https://www.gdcvault.com");
return true;
}
/*
* Action functions
*/
let playFailed = false;
let initialPlay = true;
function Pause () {
initialPlay = false;
return new Promise((resolve, reject) => {
if (player.paused()) {
player.play().then(() => {
playFailed = false;
//OSD("Play");
resolve();
}).catch((error) => {
// play failed, likely due to autoplay restrictions (hi chrome)
playFailed = true;
ResetPlayerHasStarted();
reject();
});
} else {
player.pause();
//OSD("Pause");
resolve();
}
});
}
function Mute () {
return new Promise((resolve, reject) => {
player.muted(!player.muted());
resolve();
});
}
function Seek (offset) {
return new Promise((resolve, reject) => {
player.currentTime(player.currentTime() + offset);
resolve();
});
}
function Volume (offset) {
return new Promise((resolve, reject) => {
if (player.muted())
Mute();
player.volume(player.volume() + offset);
volume = player.volume();
localStorage.setItem(`${videoId}-volume`, volume);
resolve();
});
}
let audioChannelMode = 0;
function CycleMonoStereo () {
return new Promise((resolve, reject) => {
audioChannelMode++;
if (audioChannelMode > 2)
audioChannelMode = 0;
if (!usingAudioContext)
InitializeAudioContext();
if (audioContext.state !== "running")
audioContext.resume();
try {
audioSplitter.disconnect(audioMerger);
} catch (e) {}
switch (audioChannelMode) {
case 0: // stereo
audioSplitter.connect(audioMerger, 0, 0);
audioSplitter.connect(audioMerger, 1, 1);
resolve();
break;
case 1: // mono, left channel
audioSplitter.connect(audioMerger, 0, 0);
audioSplitter.connect(audioMerger, 0, 1);
resolve();
break;
case 2: // mono, right channel
audioSplitter.connect(audioMerger, 1, 0);
audioSplitter.connect(audioMerger, 1, 1);
resolve();
break;
}
});
}
function Fullscreen () {
return new Promise((resolve, reject) => {
if (isIframe) {
if (player.isFullscreen()) {
player.exitFullscreen();
resolve();
return;
}
if (DispatchToParent("Fullscreen")) {
resolve();
return;
}
player.isFullscreen() ? player.exitFullscreen() : player.requestFullscreen();
}
else {
iframeIsFullscreen ? document.exitFullscreen() : iframeEl.requestFullscreen();
}
resolve();
});
}
function PlaybackRate (direction) {
return new Promise((resolve, reject) => {
if (direction === 0) {
playbackRate = 1.0;
player.playbackRate(1);
resolve();
return;
}
const rates = player.playbackRates();
const newIndex = Math.min(rates.length-1, Math.max(0, rates.indexOf(player.playbackRate())+direction));
playbackRate = rates[newIndex];
player.playbackRate(playbackRate);
localStorage.setItem(`${videoId}-playbackRate`, player.playbackRate());
resolve();
});
}
function SeekFrame (frameOffset) {
return new Promise((resolve, reject) => {
player.pause();
Seek(frameDuration * frameOffset);
resolve();
});
}
function ToggleSubtitles () {
return new Promise((resolve, reject) => {
if (SubsAreVisible()) {
player.textTrackDisplay.hide();
localStorage.setItem(`${videoId}-showSubtitles`, false);
} else {
player.textTrackDisplay.show();
localStorage.setItem(`${videoId}-showSubtitles`, true);
}
resolve();
});
}
function CycleSubtitles () {
return new Promise((resolve, reject) => {
if (subtitles.length === 0) {
reject();
return;
}
if (subtitles[currentSubtitle] === undefined && subtitles.length <= 1) {
reject();
return;
}
if (!SubsAreVisible())
ToggleSubtitles();
if (subtitles[currentSubtitle] !== undefined)
subtitles[currentSubtitle].mode = 'disabled';
let hangCheck = 0;
while (hangCheck < 100) {
currentSubtitle++;
if (currentSubtitle >= subtitles.length)
currentSubtitle = 0;
hangCheck++;
if (subtitles[currentSubtitle].kind === "subtitles" || subtitles[currentSubtitle].kind === "captions")
break;
}
subtitles[currentSubtitle].mode = 'showing';
localStorage.setItem(`${videoId}-subtitlesTrack`, subtitles[currentSubtitle].language);
resolve();
});
}
function ToggleModal () {
return new Promise((resolve, reject) => {
shortcutsModal.el().classList.contains("vjs-hidden") ? shortcutsModal.open() : shortcutsModal.close();
resolve();
});
}
function ToggleDarkMode () {
return new Promise((resolve, reject) => {
if (isIframe) {
isDarkMode = !isDarkMode;
localStorage.setItem("vpe-darkmode", isDarkMode);
darkModeButton.$('.vjs-icon-placeholder').textContent = isDarkMode ? "\u{263C}" : "\u{2600}";
DispatchToParent("ToggleDarkMode");
} else {
document.documentElement.classList.toggle("dark");
}
resolve();
});
}
function OSD (content) {
switch (typeof content) {
case "string":
DisplayOnOSD(content);
break;
case "function":
DisplayOnOSD(content());
break;
}
}
let osdTimeout = -1;
function DisplayOnOSD (text) {
osdModal.contentEl().textContent = text;
osdModal.show();
clearTimeout(osdTimeout);
osdTimeout = setTimeout(HideOSD, 700);
}
function HideOSD () {
osdModal.hide();
}
/*
* Initialization & utils
*/
// LocalStorage settings
const playbackTime = isIframe ? parseFloat(localStorage.getItem(`${videoId}-time`) ?? 0.0) : null;
let volume = isIframe ? parseFloat(localStorage.getItem(`${videoId}-volume`) ?? -1.0) : -1.0;
let playbackRate = isIframe ? parseFloat(localStorage.getItem(`${videoId}-playbackRate`) ?? 1.0) : null;
let showSubtitles = isIframe ? (localStorage.getItem(`${videoId}-showSubtitles`) === 'true' ? true : false) : null;
let subtitlesTrack = isIframe ? localStorage.getItem(`${videoId}-subtitlesTrack`) : null;
let isDarkMode = isIframe ? (localStorage.getItem("vpe-darkmode") === "true" ? true : false) : null;
let hasRestoredPlaybackTime = false;
if (!isIframe && isDarkMode) {
isDarkMode = false;
ToggleDarkMode();
}
if (isIframe) {
window.addEventListener("load", evt => {
// restore playback position
videojs("my-video").on("loadeddata", function(){
RestorePlaybackTime();
RestoreVolume();
RestorePlaybackRate();
OverrideFullscreenButton();
AddDarkModeButton();
AddShortcutsMenu();
InitSubtitles();
InitOSD();
if (isDarkMode) {
isDarkMode = false;
ToggleDarkMode();
}
});
videojs("my-video").on("play", evt =>{
if (!initialPlay)
return;
Pause();
ResetPlayerHasStarted();
player.muted(false);
});
// Auto-save playback position every second
setInterval(function () {
if (!hasRestoredPlaybackTime)
return;
localStorage.setItem(`${videoId}-time`, player.currentTime());
}, 1000);
});
}
let osdModal = {};
let osdTimeDisplay = {};
function InitOSD () {
videojs.registerComponent("OSDModal", videojs.extend(videojs.getComponent("ModalDialog")));
osdModal = player.addChild("OSDModal", {
label: "OSD",
content: "OSD",
pauseOnOpen: false,
temporary: false,
uncloseable: true,
className: "OSD"
});
videojs.registerComponent("OSDTimeDisplay", videojs.extend(videojs.getComponent("TimeDisplay")));
osdTimeDisplay = player.addChild("OSDTimeDisplay");
}
// API https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API
var audioContext, audioSplitter, audioMerger, audioMediasource;
var usingAudioContext = false;
function InitializeAudioContext () {
audioContext = new AudioContext();
audioSplitter = audioContext.createChannelSplitter(2);
audioMerger = audioContext.createChannelMerger(2);
audioMediasource = audioContext.createMediaElementSource(player.$('video'));
audioMediasource.connect(audioSplitter);
audioSplitter.connect(audioMerger, 0, 0);
audioSplitter.connect(audioMerger, 1, 1);
audioMerger.connect(audioContext.destination);
usingAudioContext = true;
}
function RestorePlaybackTime () {
if (playbackTime === null)
return;
player.currentTime(playbackTime);
hasRestoredPlaybackTime = true;
}
function RestoreVolume () {
if (volume < 0.0)
return;
player.volume(volume);
}
function RestorePlaybackRate () {
if (playbackRate === null) {
playbackRate = 1.0;
return;
}
const rates = player.playbackRates();
const newIndex = Math.min(rates.length-1, Math.max(0, rates.indexOf(playbackRate)));
playbackRate = rates[newIndex];
player.playbackRate(playbackRate);
}
function OverrideFullscreenButton () {
const fullscreenToggle = player.controlBar.getChild("FullscreenToggle");
fullscreenToggle.handleClick = (evt => {
window['playerActionShurtcuts'].actions.Fullscreen();
});
fullscreenToggle.handleKeyDown = ()=>{};
}
let iframeIsFullscreen = false;
if (!isIframe)
iframeEl.addEventListener("fullscreenchange", evt => {
iframeIsFullscreen = document.fullscreenElement !== null;
});
let currentSubtitle = -1;
let subtitles = {};
function InitSubtitles () {
subtitles = player.textTracks();
let savedTrack = -1;
for (let i = 0; i < subtitles.length; i++) {
const track = subtitles[i];
// find saved track
if (subtitlesTrack !== null && subtitlesTrack === track.language)
savedTrack = i;
if (track.mode !== 'showing')
continue;
currentSubtitle = i;
}
if (savedTrack !== -1 && savedTrack !== currentSubtitle) {
if (subtitles[currentSubtitle] !== undefined)
subtitles[currentSubtitle].mode = 'disabled';
subtitles[savedTrack].mode = 'showing';
currentSubtitle = savedTrack;
}
if (!showSubtitles)
player.textTrackDisplay.hide();
else
player.textTrackDisplay.show();
}
function SubsAreVisible () {
return !player.textTrackDisplay.el().classList.contains('vjs-hidden');
}
let darkModeButton = {};
function AddDarkModeButton () {
videojs.registerComponent("DarkModeButton", videojs.extend(videojs.getComponent("Button")));
darkModeButton = player.controlBar.addChild("DarkModeButton", {className:"darkmode"}, 99);
darkModeButton.controlText("Toggle Dark Mode");
darkModeButton.$('.vjs-icon-placeholder').textContent = "\u{2600}";
darkModeButton.handleClick = (evt => {
ToggleDarkMode();
});
darkModeButton.handleKeyDown = () => {};
}
function ResetPlayerHasStarted () {
player.hasStarted(false);
}
let shortcutsMenuButton;
let shortcutsModal = null;
function AddShortcutsMenu () {
videojs.registerComponent("ShortcutsButton", videojs.extend(videojs.getComponent("Button")));
shortcutsMenuButton = player.controlBar.addChild("ShortcutsButton", {className: "shortcuts-help"}, 99);
shortcutsMenuButton.controlText("Shortcuts Help");
shortcutsMenuButton.$('.vjs-icon-placeholder').textContent = "?";
/* Loop through action bindings and generate markup */
let shortcutsElements = document.createElement("div");
Object.keys(Actions).forEach(action => {
let row = document.createElement("div");
row.classList.add("row");
let actionName = document.createElement("div");
actionName.classList.add("action");
actionName.textContent = Actions[action].name;//ActionNames[action];
row.appendChild(actionName);
let binding = document.createElement("div");
binding.classList.add("binding");
const keys = Object.keys(Actions[action].keys);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
let keyEl = document.createElement('span');
keyEl.classList.add('key');
keyEl.textContent = key === ' ' ? 'Space' : key;
binding.appendChild(keyEl);
if (i >= keys.length - 1)
continue;
let orEl = document.createElement('span');
orEl.textContent = "or";
binding.appendChild(orEl);
}
row.appendChild(binding);
shortcutsElements.appendChild(row);
});
let supportLinks = document.createElement("div");
supportLinks.classList.add("support-links");
let versionInfo = "";
try {
versionInfo = GM_info.script.version;
} catch(e) {}
supportLinks.innerHTML = `GDC Vault Player Extended ${versionInfo}
<a href="https://github.com/Level3Manatee/GDCVaultPlayerExtended">GitHub</a> |
<a href="https://greasyfork.org/en/scripts/501309-gdc-vault-player-extended">GreasyFork</a>`;
shortcutsElements.appendChild(supportLinks);
videojs.registerComponent("ShortcutsModal", videojs.extend(videojs.getComponent("ModalDialog")))
shortcutsModal = player.addChild("ShortcutsModal", {
label: "Shortcuts Help",
content: shortcutsElements,
pauseOnOpen: false,
temporary: false,
className: "shortcuts-help"
});
shortcutsModal.el().addEventListener('click', evt => {
shortcutsModal.close();
});
shortcutsMenuButton.handleClick = (evt => {
shortcutsModal.open();
});
// override default handler, since it eats all keyboard input after clicking fullscreen
shortcutsMenuButton.handleKeyDown = () => {};
shortcutsModal.handleKeyDown = () => {};
}
/*
* CSS
*/
function AddStyle () {
const styleEl = document.createElement("style");
const vaultStyle = `
html {
--text-color-grey: hsl(0, 0%, 60%);
}
.text-color-grey {
color: var(--text-color-grey);
}
.wrapper {
max-width: none;
width: min-content;
}
.wrapper > nav,
.wrapper #player {
width: min(90vw, calc(90vh * (16 / 9)));
}
.wrapper #player {
display: flex;
flex-direction: column;
align-items: center;
}
.wrapper #player > :not(.left_column),
.wrapper #player .left_column .overview-section {
width: 100%;
max-width: min(60rem, 100%);
}
.wrapper #player .left_column {
width: 100%;
}
.wrapper #player .left_column .overview-section {
float: none;
margin: 0 auto;
}
@media screen and (min-width: 768px) {
.overview-section dt,
.player-info dt {
width: 20%;
clear: left;
}
}
.wrapper #player .right_column,
#studio-subscription {
margin-left: 0;
}
html.dark {
--dark-bg: hsl(0, 0%, 17%);
--text-color-grey: hsl(0, 0%, 70%);
--color-dim: 0.75;
background-color: hsl(247.7, 38.6%, calc(39.6% * var(--color-dim) * 0.75));
}
html.dark body {
background: none;
}
html.dark body::after {
content: "";
position: absolute;
display: block;
top: 0; right: 0; bottom: 0; left: 0;
z-index: -1;
background: url(https://www.gdcvault.com/img/bg.png);
filter: brightness(calc(var(--color-dim) * 0.75));
}
html.dark #iribbon-container,
html.dark footer {
filter: brightness(var(--color-dim));
}
html.dark header, html.dark .wrapper {
background: var(--dark-bg);
}
html.dark .wrapper {
border-color: var(--dark-bg);
}
.wrapper > nav,
#load_recommended_videos {
filter: brightness(var(--color-dim));
}
html.dark .logo-block {
filter: brightness(1.33);
}
html.dark .site-header #searchForm {
background-color: hsl(58, calc(100% * var(--color-dim)), calc(48% * var(--color-dim)));
}
html.dark .site-header input#vault_keyword_search {
background-color: var(--dark-bg);
color: white;
}
html.dark :is(a, dl:is(.player-info, .overview-section, .video-details) :is(dd, dd strong, dt, dt strong)) {
color: var(--text-color-grey);
}
html.dark #studio-subscription {
border-color: var(--text-color-grey);
color: var(--text-color-grey);
}
html.dark :is(ul#tags, #player #recommended img) {
filter: invert(1) hue-rotate(180deg);
}
html.dark #player #recommended img {
background: none;
}
html.dark #player #recommended #header h3,
html.dark #player #recommended {
color: var(--text-color-grey);
background: var(--dark-bg);
}`;
const iFrameStyle = `
body {
margin: 0;
}
/* hide the unmute thing */
#playerContainer > button {
display: none;
}
.vjs-modal-dialog.OSD {
display: flex;
background: none;
justify-content: center;
align-content: center;
}
.vjs-modal-dialog.OSD .vjs-modal-dialog-content {
position: static;
display: inline-block;
width: auto;
height: auto;
margin: auto;
padding: 0 1em;
border-radius: 0.5em;
background: hsla(0, 0%, 16.9%, 0.8);
color: hsl(0, 0%, 80%);
font-size: 1.5cqi;
font-weight: bold;
text-align: center;
line-height: 3;
}
.video-js .vjs-control-bar {
display: flex;
}
.video-js.vjs-playing:not(.vjs-user-active) .vjs-control-bar {
opacity: 0;
}
.video-js.vjs-has-started .vjs-big-play-button {
opacity: 0;
transition: opacity 0s;
pointer-events: none;
display: block;
}
.video-js.vjs-has-started.vjs-paused .vjs-big-play-button {
display: block;
opacity: 1;
transition: opacity 0.25s 0.75s;
}
.vjs-control-bar button.shortcuts-help,
.vjs-control-bar button.darkmode {
font-size: 1.5em;
cursor: pointer;
width: 2.5em;
}
.vjs-modal-dialog.shortcuts-help {
font-size: min(1rem, ${50 / Object.keys(Actions).length}cqh);
}
.vjs-modal-dialog.shortcuts-help .vjs-modal-dialog-content {
display: flex;
}
.vjs-modal-dialog.shortcuts-help .vjs-modal-dialog-content > div {
margin: auto;
background: black;
padding: 2em;
}
.vjs-modal-dialog.shortcuts-help .row {
display: flex;
gap: 1em;
}
.vjs-modal-dialog.shortcuts-help .action {
width: 30ch;
text-align: right;
align-self: center;
}
.vjs-modal-dialog.shortcuts-help .binding span {
display: inline-block;
height: 2em;
padding: 0.25em;
}
.vjs-modal-dialog.shortcuts-help .binding .key {
margin: 0.25em;
padding: 0.25em 0.75em;
border: 1px solid white;
border-radius: 0.1em;
min-width: 2ch;
}
.shortcuts-help .support-links {
border-top: 1px solid white;
margin-top: 0.5em;
padding-top: 0.5em;
}
.shortcuts-help .support-links a {
color: white;
}`;
if (isIframe)
styleEl.textContent = iFrameStyle;
else
styleEl.textContent = vaultStyle;
document.body.appendChild(styleEl);
}
AddStyle();