- // ==UserScript==
- // @name Better Theater Mode for YouTube
- // @name:zh-TW 更佳 YouTube 劇場模式
- // @name:zh-CN 更佳 YouTube 剧场模式
- // @name:ja より良いYouTubeシアターモード
- // @icon https://www.youtube.com/img/favicon_48.png
- // @author ElectroKnight22
- // @namespace electroknight22_youtube_better_theater_mode_namespace
- // @version 1.11.4
- // @match *://www.youtube.com/*
- // @match *://www.youtube-nocookie.com/*
- // @grant GM.getValue
- // @grant GM.setValue
- // @grant GM.deleteValue
- // @grant GM.listValues
- // @grant GM.registerMenuCommand
- // @grant GM.unregisterMenuCommand
- // @grant GM.notification
- // @noframes
- // @license MIT
- // @description Improves YouTube's theater mode with a Twitch.tv-like design, enhancing video and chat layouts while maintaining performance and compatibility. Also adds an optional, customized floating chat for fullscreen mode, seamlessly integrated with YouTube's design.
- // @description:zh-TW 改善 YouTube 劇場模式,參考 Twitch.tv 的設計,增強影片與聊天室佈局,同時維持效能與相容性。另新增可選的、自製風格的浮動聊天室功能(僅限全螢幕模式),與 YouTube 原有的設計語言相融合。
- // @description:zh-CN 改进 YouTube 剧场模式,参考 Twitch.tv 的设计,增强视频与聊天室布局,同时保持性能与兼容性。同时新增可选的、自制风格的浮动聊天室功能(仅限全屏模式),融入了 YouTube 原有的设计语言。
- // @description:ja YouTubeのシアターモードを改善し、Twitch.tvのデザインを参考にして、動画とチャットのレイアウトを強化しつつ、パフォーマンスと互換性を維持します。また、全画面モード専用のオプションとして、カスタマイズ済みフローティングチャット機能を、YouTubeのデザイン言語に沿って統合しています。
- // ==/UserScript==
-
- /*jshint esversion: 11 */
- (function () {
- "use strict";
-
- // UI Constants
- const MIN_CHAT_SIZE = { width: '300px', height: '355px' };
- const DRAG_BAR_HEIGHT = '35px';
-
- // CONFIG
- const CONFIG = {
- DEFAULT_SETTINGS: {
- isSimpleMode: true,
- enableOnlyForLiveStreams: false,
- modifyVideoPlayer: true,
- modifyChat: true,
- setLowHeadmast: false,
- useCustomPlayerHeight: false,
- playerHeightPx: 600,
- floatingChat: false,
- chatStyle: {
- left: '0px',
- top: '-500px',
- width: MIN_CHAT_SIZE.width,
- height: MIN_CHAT_SIZE.height,
- opacity: '0.95',
- },
- debug: false
- },
- DEFAULT_BLACKLIST: [],
- REQUIRED_VERSIONS: {
- Tampermonkey: '5.4.624'
- }
- };
-
- // TRANSLATIONS
- const BROWSER_LANGUAGE = navigator.language || navigator.userLanguage;
-
- function getPreferredLanguage() {
- if (BROWSER_LANGUAGE.startsWith('zh') && BROWSER_LANGUAGE !== 'zh-TW') {
- return 'zh-CN';
- }
- // Check if language is supported, otherwise fall back to English
- return ['en-US', 'zh-TW', 'zh-CN', 'ja'].includes(BROWSER_LANGUAGE)
- ? BROWSER_LANGUAGE
- : 'en-US';
- }
-
- const TRANSLATIONS = {
- 'en-US': {
- tampermonkeyOutdatedAlert: "It looks like you're using an older version of Tampermonkey that might cause menu issues. For the best experience, please update to version 5.4.6224 or later.",
- turnOn: 'Turn On',
- turnOff: 'Turn Off',
- livestreamOnlyMode: 'Livestream Only Mode',
- applyChatStyles: 'Apply Chat Styles',
- applyVideoPlayerStyles: 'Apply Video Player Styles',
- moveHeadmastBelowVideoPlayer: 'Move Headmast Below Video Player',
- useCustomPlayerHeight: 'Use Custom Player Height',
- playerHeightText: 'Player Height',
- floatingChat: 'Floating Chat',
- blacklistVideo: 'Blacklist Video',
- unblacklistVideo: 'Unblacklist Video',
- simpleMode: 'Simple Mode',
- advancedMode: 'Advanced Mode',
- debug: 'DEBUG'
- },
- 'zh-TW': {
- tampermonkeyOutdatedAlert: "看起來您正在使用較舊版本的篡改猴,可能會導致選單問題。為了獲得最佳體驗,請更新至 5.4.6224 或更高版本。",
- turnOn: '開啟',
- turnOff: '關閉',
- livestreamOnlyMode: '僅限直播模式',
- applyChatStyles: '套用聊天樣式',
- applyVideoPlayerStyles: '套用影片播放器樣式',
- moveHeadmastBelowVideoPlayer: '將頁首橫幅移到影片播放器下方',
- useCustomPlayerHeight: '使用自訂播放器高度',
- playerHeightText: '播放器高度',
- floatingChat: '浮動聊天室',
- blacklistVideo: '將影片加入黑名單',
- unblacklistVideo: '從黑名單中移除影片',
- simpleMode: '簡易模式',
- advancedMode: '進階模式',
- debug: '偵錯'
- },
- 'zh-CN': {
- tampermonkeyOutdatedAlert: "看起来您正在使用旧版本的篡改猴,这可能会导致菜单问题。为了获得最佳体验,请更新到 5.4.6224 或更高版本。",
- turnOn: '开启',
- turnOff: '关闭',
- livestreamOnlyMode: '仅限直播模式',
- applyChatStyles: '应用聊天样式',
- applyVideoPlayerStyles: '应用视频播放器样式',
- moveHeadmastBelowVideoPlayer: '将页首横幅移动到视频播放器下方',
- useCustomPlayerHeight: '使用自定义播放器高度',
- playerHeightText: '播放器高度',
- floatingChat: '浮动聊天室',
- blacklistVideo: '将视频加入黑名单',
- unblacklistVideo: '从黑名单中移除视频',
- simpleMode: '简易模式',
- advancedMode: '高级模式',
- debug: '调试'
- },
- 'ja': {
- tampermonkeyOutdatedAlert: "ご利用のTampermonkeyのバージョンが古いため、メニューに問題が発生する可能性があります。より良い体験のため、バージョン5.4.6224以上に更新してください。",
- turnOn: "オンにする",
- turnOff: "オフにする",
- livestreamOnlyMode: "ライブ配信専用モード",
- applyChatStyles: "チャットスタイルを適用",
- applyVideoPlayerStyles: "ビデオプレイヤースタイルを適用",
- moveHeadmastBelowVideoPlayer: "ヘッドマストをビデオプレイヤーの下に移動",
- useCustomPlayerHeight: "カスタムプレイヤーの高さを使用",
- playerHeightText: "プレイヤーの高さ",
- floatingChat: "フローティングチャット",
- blacklistVideo: "動画をブラックリストに追加",
- unblacklistVideo: "ブラックリストから動画を解除",
- simpleMode: "シンプルモード",
- advancedMode: "高度モード",
- debug: "デバッグ"
- }
- };
-
- function getLocalizedText() {
- return TRANSLATIONS[getPreferredLanguage()] || TRANSLATIONS['en-US'];
- }
-
- // STATE VARIABLES
- const state = {
- userSettings: { ...CONFIG.DEFAULT_SETTINGS },
- advancedSettingsBackup: null,
- blacklist: new Set(),
- useCompatibilityMode: false,
- menuItems: new Set(),
- activeStyles: new Map(),
- resizeObserver: null,
- moviePlayer: null,
- videoId: null,
- chatFrame: null,
- currentPageType: '',
- isFullscreen: false,
- isTheaterMode: false,
- chatCollapsed: true,
- isLiveStream: false,
- chatWidth: 0,
- moviePlayerHeight: 0,
- isOldTampermonkey: false,
- isScriptRecentlyUpdated: false
- };
-
- // GM API COMPATIBILITY
- const GM = {
- registerMenuCommand: state.useCompatibilityMode ? GM_registerMenuCommand : window.GM?.registerMenuCommand,
- unregisterMenuCommand: state.useCompatibilityMode ? GM_unregisterMenuCommand : window.GM?.unregisterMenuCommand,
- getValue: state.useCompatibilityMode ? GM_getValue : window.GM?.getValue,
- setValue: state.useCompatibilityMode ? GM_setValue : window.GM?.setValue,
- listValues: state.useCompatibilityMode ? GM_listValues : window.GM?.listValues,
- deleteValue: state.useCompatibilityMode ? GM_deleteValue : window.GM?.deleteValue,
- notification: state.useCompatibilityMode ? GM_notification : window.GM?.notification
- };
-
- // STYLE DEFINITIONS
- const styleRules = {
- chatStyle: {
- id: "betterTheater-chatStyle",
- getRule: () => `
- ytd-live-chat-frame[theater-watch-while][rounded-container] {
- border-radius: 0 !important;
- border-top: 0 !important;
- }
- ytd-watch-flexy[fixed-panels] #chat.ytd-watch-flexy {
- top: 0 !important;
- border-top: 0 !important;
- border-bottom: 0 !important;
- }
- `,
- },
-
- videoPlayerStyle: {
- id: "betterTheater-videoPlayerStyle",
- getRule: () => {
- if (state.userSettings.useCustomPlayerHeight) {
- return `
- ytd-watch-flexy[full-bleed-player] #full-bleed-container.ytd-watch-flexy {
- min-height: 0px !important;
- height: ${state.userSettings.playerHeightPx}px !important;
- }
- `;
- } else {
- return `
- ytd-watch-flexy[full-bleed-player] #full-bleed-container.ytd-watch-flexy {
- max-height: calc(100vh - var(--ytd-watch-flexy-masthead-height)) !important;
- }
- `;
- }
- }
- },
-
- headmastStyle: {
- id: "betterTheater-headmastStyle",
- getRule: () => `
- #masthead-container.ytd-app {
- max-width: calc(100% - ${state.chatWidth}px) !important;
- }
- `,
- },
-
- lowHeadmastStyle: {
- id: "betterTheater-lowHeadmastStyle",
- getRule: () => `
- #page-manager.ytd-app {
- margin-top: 0 !important;
- top: calc(-1 * var(--ytd-toolbar-offset)) !important;
- position: relative !important;
- }
- ytd-watch-flexy[flexy]:not([full-bleed-player][full-bleed-no-max-width-columns]) #columns.ytd-watch-flexy {
- margin-top: var(--ytd-toolbar-offset) !important;
- }
- ${state.userSettings.modifyVideoPlayer ? `
- ytd-watch-flexy[full-bleed-player] #full-bleed-container.ytd-watch-flexy {
- max-height: 100vh !important;
- }
- ` : ''}
- #masthead-container.ytd-app {
- z-index: 599 !important;
- top: ${state.moviePlayerHeight}px !important;
- position: relative !important;
- }
- `,
- },
-
- videoPlayerFixStyle: {
- id: "betterTheater-videoPlayerFixStyle",
- getRule: () => `
- .html5-video-container {
- top: -1px !important;
- }
- #skip-navigation.ytd-masthead {
- left: -500px;
- }
- `,
- },
-
- chatFrameFixStyle: {
- id: "betterTheater-chatFrameFixStyle",
- getRule: () => {
- const chatInputContainer = document.querySelector(
- "tp-yt-iron-pages#panel-pages.style-scope.yt-live-chat-renderer"
- );
- const shouldHideChatInputContainerTopBorder =
- chatInputContainer?.clientHeight === 0;
- const borderTopStyle = shouldHideChatInputContainerTopBorder
- ? 'border-top: 0 !important;'
- : '';
-
- return `
- #panel-pages.yt-live-chat-renderer {
- ${borderTopStyle}
- border-bottom: 0 !important;
- }
- `;
- },
- },
-
- chatRendererFixStyle: {
- id: "betterTheater-chatRendererFixStyle",
- getRule: () => `
- ytd-live-chat-frame[theater-watch-while][rounded-container] {
- border-bottom: 0 !important;
- }
- `,
- },
-
- floatingChatStyle: {
- id: "betterTheater-floatingChatStyle",
- getRule: () => `
- #chat-container {
- min-width: ${MIN_CHAT_SIZE.width} !important;
- max-width: 100vw !important;
- max-height: 100vh !important;
- position: absolute;
- border-radius: 0 0 12px 12px !important;
- }
- #chat {
- top: ${DRAG_BAR_HEIGHT} !important;
- height: calc(100% - ${DRAG_BAR_HEIGHT}) !important;
- width: inherit !important;
- min-width: inherit !important;
- max-width: inherit !important;
- min-height: ${parseInt(MIN_CHAT_SIZE.height) - parseInt(DRAG_BAR_HEIGHT)}px !important;
- max-height: 100vh !important;
- }
- `,
- },
-
- floatingChatStyleExpanded: {
- id: "betterTheater-floatingChatStyleExpanded",
- getRule: () => `
- #chat-container {
- min-height: ${MIN_CHAT_SIZE.height} !important;
- }
- ytd-live-chat-frame:not([theater-watch-while])[rounded-container] {
- border-top-left-radius: 0 !important;
- border-top-right-radius: 0 !important;
- border-top: 0 !important;
- }
- ytd-live-chat-frame:not([theater-watch-while])[rounded-container] iframe.ytd-live-chat-frame {
- border-top-left-radius: 0 !important;
- border-top-right-radius: 0 !important;
- }
- `,
- },
-
- floatingChatStyleCollapsed: {
- id: "betterTheater-floatingChatStyleCollapsed",
- getRule: () => `
- ytd-live-chat-frame[round-background] #show-hide-button.ytd-live-chat-frame>ytd-toggle-button-renderer.ytd-live-chat-frame,
- ytd-live-chat-frame[round-background] #show-hide-button.ytd-live-chat-frame>ytd-button-renderer.ytd-live-chat-frame {
- margin: 0 !important;
- border-radius: 0 0 12px 12px !important;
- border-left: 1px solid var(--yt-spec-10-percent-layer) !important;
- border-right: 1px solid var(--yt-spec-10-percent-layer) !important;
- border-bottom: 1px solid var(--yt-spec-10-percent-layer) !important;
- background-clip: padding-box !important;
- }
- ytd-live-chat-frame[modern-buttons][collapsed] {
- border-radius: 0 0 12px 12px !important;
- }
- button.yt-spec-button-shape-next.yt-spec-button-shape-next--outline.yt-spec-button-shape-next--mono.yt-spec-button-shape-next--size-m {
- border-radius: 0 0 12px 12px !important;
- border: none !important;
- }
- .chat-resize-handle {
- visibility: hidden !important;
- }
- `,
- },
-
- debugResizeHandleStyle: {
- id: "betterTheater-debugResizeHandleStyle",
- getRule: () => `
- /* Default state for resize handles */
- #chat-container .chat-resize-handle {
- background: transparent;
- opacity: 0;
- }
- /* Debug state for resize handles when #chat-container has [debug] attribute */
- #chat-container[debug] .chat-resize-handle {
- opacity: 0.5;
- }
- #chat-container[debug] .chat-resize-handle.rs-right {
- background: rgba(255, 0, 0, 0.5);
- }
- #chat-container[debug] .chat-resize-handle.rs-left {
- background: rgba(0, 255, 0, 0.5);
- }
- #chat-container[debug] .chat-resize-handle.rs-bottom {
- background: rgba(0, 0, 255, 0.5);
- }
- #chat-container[debug] .chat-resize-handle.rs-top {
- background: rgba(255, 255, 0, 0.5);
- }
- #chat-container[debug] .chat-resize-handle.rs-bottom-left {
- background: rgba(0, 255, 255, 0.5);
- }
- #chat-container[debug] .chat-resize-handle.rs-top-left {
- background: rgba(255, 255, 0, 0.5);
- }
- #chat-container[debug] .chat-resize-handle.rs-top-right {
- background: rgba(255, 0, 0, 0.5);
- }
- #chat-container[debug] .chat-resize-handle.rs-bottom-right {
- background: rgba(255, 0, 255, 0.5);
- }
- `,
- },
-
- chatSliderStyle: {
- id: "betterTheater-chatSliderStyle",
- getRule: () => `
- .chat-drag-bar input[type=range] {
- -webkit-appearance: none;
- width: 100px;
- height: 4px;
- background: var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color));
- border-radius: 2px;
- outline: none;
- }
- .chat-drag-bar input[type=range]::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- width: 14px;
- height: 14px;
- border-radius: 50%;
- background: var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color));
- cursor: pointer;
- }
- .chat-drag-bar input[type=range]::-moz-range-thumb {
- width: 14px;
- height: 14px;
- border-radius: 50%;
- background: var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color));
- cursor: pointer;
- }
- .chat-drag-bar input[type=range]::-moz-range-track {
- background: var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color));
- height: 4px;
- border-radius: 2px;
- }
- .chat-drag-bar input[type=range]::-ms-thumb {
- background: var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color));
- }
- .chat-drag-bar input[type=range]::-ms-track {
- background: var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color));
- height: 4px;
- border-radius: 2px;
- }
- `
- }
- };
-
- // STYLE MANAGEMENT
- function applyStyle(style, setPersistent = false) {
- if (typeof style.getRule !== 'function') return;
- if (state.activeStyles.has(style.id)) {
- removeStyle(style);
- }
-
- const styleElement = document.createElement('style');
- styleElement.id = style.id;
- styleElement.type = 'text/css';
- styleElement.textContent = style.getRule();
- (document.head || document.documentElement).appendChild(styleElement);
- state.activeStyles.set(style.id, {
- element: styleElement,
- persistent: setPersistent
- });
- }
-
- function removeStyle(style) {
- if (!state.activeStyles.has(style.id)) return;
-
- const { element: styleElement } = state.activeStyles.get(style.id);
- if (styleElement && styleElement.parentNode) {
- styleElement.parentNode.removeChild(styleElement);
- }
-
- state.activeStyles.delete(style.id);
- }
-
- function removeAllStyles() {
- state.activeStyles.forEach((styleData, styleId) => {
- if (!styleData.persistent) {
- removeStyle({ id: styleId });
- }
- });
- }
-
- function setStyleState(style, on = true) {
- on ? applyStyle(style) : removeStyle(style);
- }
-
- function addResizeHandles(chatContainer) {
- const handleConfigs = {
- right: {
- width: "6px", top: "0", right: "0", bottom: "0",
- cursor: "ew-resize", horizontal: true, vertical: false
- },
- left: {
- width: "6px", top: "0", left: "0", bottom: "0",
- cursor: "ew-resize", horizontal: true, vertical: false
- },
- bottom: {
- height: "6px", left: "0", bottom: "0", right: "0",
- cursor: "ns-resize", horizontal: false, vertical: true
- },
- top: {
- height: "6px", left: "0", top: "0", right: "0",
- cursor: "ns-resize", horizontal: false, vertical: true
- },
- bottomLeft: {
- width: "12px", height: "12px", left: "0", bottom: "0",
- cursor: "nesw-resize", horizontal: true, vertical: true
- },
- topLeft: {
- width: "12px", height: "12px", left: "0", top: "0",
- cursor: "nwse-resize", horizontal: true, vertical: true
- },
- topRight: {
- width: "12px", height: "12px", right: "0", top: "0",
- cursor: "nesw-resize", horizontal: true, vertical: true
- },
- bottomRight: {
- width: "12px", height: "12px", right: "0", bottom: "0",
- cursor: "nwse-resize", horizontal: true, vertical: true
- }
- };
-
- const handles = {};
- for (const [position, config] of Object.entries(handleConfigs)) {
- const handle = document.createElement("div");
- handle.className = `chat-resize-handle rs-${position}`;
- handle.style.position = "absolute";
- handle.style.zIndex = "10001";
- Object.assign(handle.style, config);
- chatContainer.appendChild(handle);
- handles[position] = handle;
- initResizeHandler(handle, config);
- }
- return handles;
-
- function initResizeHandler(handle, config) {
- let startX, startY, startWidth, startHeight, startLeft, startTop;
- async function saveChatStyle() {
- Object.assign(state.userSettings.chatStyle ??= {}, {
- width: chatContainer.style.width,
- height: chatContainer.style.height,
- left: chatContainer.style.left,
- top: chatContainer.style.top
- });
- await updateSetting('chatStyle', state.userSettings.chatStyle);
- }
-
- handle.addEventListener("pointerdown", function (e) {
- if (e.pointerType === "mouse" && e.button !== 0) return;
- e.preventDefault();
- startX = e.clientX;
- startY = e.clientY;
- startWidth = chatContainer.offsetWidth;
- startHeight = chatContainer.offsetHeight;
- startLeft = parseFloat(getComputedStyle(chatContainer).left) || 0;
- startTop = parseFloat(getComputedStyle(chatContainer).top) || 0;
-
- handle.setPointerCapture(e.pointerId);
- });
-
- handle.addEventListener("pointermove", function (e) {
- if (!handle.hasPointerCapture(e.pointerId)) return;
- e.preventDefault();
- const movieRect = state.moviePlayer.getBoundingClientRect();
- const chatParentRect = chatContainer.parentElement.getBoundingClientRect();
- const chatRect = chatContainer.getBoundingClientRect();
- const minWidth = parseInt(MIN_CHAT_SIZE.width);
- const minHeight = parseInt(MIN_CHAT_SIZE.height);
- let dx = e.clientX - startX;
- let dy = e.clientY - startY;
- let newLeft = startLeft;
- let newTop = startTop;
- let newWidth = startWidth;
- let newHeight = startHeight;
- dx = Math.max(-startX, Math.min(dx, movieRect.right - startX));
- dy = Math.max(-startY, Math.min(dy, movieRect.bottom - startY));
-
- if (config.horizontal) {
- const isRightSide = handle.className.toLowerCase().includes('right');
- const isLeftSide = handle.className.toLowerCase().includes('left');
- if (isRightSide) {
- newWidth += dx;
- } else if (isLeftSide) {
- newWidth -= dx;
- newLeft += Math.min(dx, startWidth - minWidth);
- }
- }
-
- if (config.vertical) {
- const isBottomSide = handle.className.toLowerCase().includes('bottom');
- const isTopSide = handle.className.toLowerCase().includes('top');
- if (isBottomSide) {
- newHeight += dy;
- } else if (isTopSide) {
- newHeight -= dy;
- newTop += Math.min(dy, startHeight - minHeight);
- }
- }
-
- // constrain to movie rect
- const correctedTopBound = movieRect.top - chatParentRect.top;
- const correctedLeftBound = movieRect.left - chatParentRect.left;
- newWidth = Math.min(Math.max(minWidth, newWidth), movieRect.right - chatRect.left);
- newHeight = Math.min(Math.max(minHeight, newHeight), movieRect.bottom - chatRect.top);
- newTop = Math.max(newTop, correctedTopBound);
- newLeft = Math.max(newLeft, correctedLeftBound);
- Object.assign(chatContainer.style, {
- left: newLeft + "px",
- top: newTop + "px",
- width: newWidth + "px",
- height: newHeight + "px"
- });
- });
-
- handle.addEventListener("pointerup", function (e) {
- handle.releasePointerCapture(e.pointerId);
- saveChatStyle();
- });
- }
- }
-
- // Removes all resize handles from a container
- function removeResizeHandles(chatContainer) {
- if (!chatContainer) return;
- const handles = chatContainer.querySelectorAll(".chat-resize-handle");
- handles.forEach(handle => handle.remove());
- }
-
- // DRAG BAR & CHAT CONTROLS
- function addDragBarWithOpacitySlider(chatContainer) {
- let existingBar = chatContainer.querySelector('.chat-drag-bar');
- if (existingBar) return existingBar;
-
- applyStyle(styleRules.chatSliderStyle, true);
-
- const dragBar = document.createElement("div");
- dragBar.className = "chat-drag-bar";
- dragBar.style.position = "absolute";
- dragBar.style.top = "0";
- dragBar.style.left = "0";
- dragBar.style.right = "0";
- dragBar.style.height = "15px";
- dragBar.style.background = "var(--yt-live-chat-background-color)";
- dragBar.style.color = "var(--yt-live-chat-header-text-color, var(--yt-live-chat-primary-text-color))";
- dragBar.style.border = "1px solid var(--yt-spec-10-percent-layer)";
- dragBar.style.backgroundClip = "padding-box";
- dragBar.style.display = "flex";
- dragBar.style.alignItems = "center";
- dragBar.style.justifyContent = "space-between";
- dragBar.style.padding = (parseInt(DRAG_BAR_HEIGHT) - 15) / 2 + "px";
- dragBar.style.zIndex = "10000";
- dragBar.style.borderRadius = "12px 12px 0 0";
-
- const dragLabel = document.createElement("div");
- dragLabel.innerText = "⋮⋮";
- dragLabel.style.fontSize = "var(--yt-live-chat-header-font-size, 18px)";
- dragLabel.style.userSelect = "none";
-
- const opacitySlider = document.createElement("input");
- opacitySlider.type = "range";
- opacitySlider.min = "20";
- opacitySlider.max = "100";
- opacitySlider.value = Math.round(parseFloat(state.userSettings.chatStyle.opacity) * 100).toString();
- opacitySlider.style.marginLeft = "10px";
-
- opacitySlider.addEventListener("input", () => {
- const newOpacity = opacitySlider.value / 100;
- chatContainer.style.opacity = newOpacity;
- });
-
- opacitySlider.addEventListener("mouseup", () => {
- Object.assign(state.userSettings.chatStyle ??= {}, {
- opacity: chatContainer.style.opacity
- });
- updateSetting('chatStyle', state.userSettings.chatStyle);
- });
-
- ["pointerdown", "pointermove", "pointerup"].forEach(eventType => {
- opacitySlider.addEventListener(eventType, (e) => {
- e.stopPropagation();
- });
- });
-
- dragBar.appendChild(dragLabel);
- dragBar.appendChild(opacitySlider);
- chatContainer.insertBefore(dragBar, chatContainer.firstChild);
-
- setupDragBehavior(dragBar, chatContainer);
-
- return dragBar;
- }
-
- function setupDragBehavior(dragBar, chatContainer) {
- let startX = 0, startY = 0;
- let startLeft = 0, startTop = 0;
-
- async function saveChatPosition() {
- Object.assign(state.userSettings.chatStyle ??= {}, {
- left: chatContainer.style.left,
- top: chatContainer.style.top
- });
- await updateSetting('chatStyle', state.userSettings.chatStyle);
- }
-
- dragBar.addEventListener("pointerdown", function (e) {
- if (e.pointerType === "mouse" && e.button !== 0) return;
-
- startX = e.clientX;
- startY = e.clientY;
- startLeft = parseFloat(getComputedStyle(chatContainer).left) || 0;
- startTop = parseFloat(getComputedStyle(chatContainer).top) || 0;
-
- dragBar.setPointerCapture(e.pointerId);
- e.preventDefault();
- });
-
- // Handle pointer move event
- dragBar.addEventListener("pointermove", function (e) {
- if (!dragBar.hasPointerCapture(e.pointerId)) return;
-
- let dx = e.clientX - startX;
- let dy = e.clientY - startY;
- let newLeft = startLeft + dx;
- let newTop = startTop + dy;
-
- const movieRect = state.moviePlayer.getBoundingClientRect();
- const chatParentRect = chatContainer.parentElement.getBoundingClientRect();
- const correctedTopBound = movieRect.top - chatParentRect.top;
- const correctedLeftBound = movieRect.left - chatParentRect.left;
- const correctedLowerBound = movieRect.bottom - chatParentRect.top - (
- state.chatCollapsed
- ? parseInt(DRAG_BAR_HEIGHT) + chatContainer.querySelector('#show-hide-button').offsetHeight
- : chatContainer.offsetHeight
- );
- const correctedRightBound = movieRect.right - chatParentRect.left - chatContainer.offsetWidth;
-
- newTop = Math.min(Math.max(newTop, correctedTopBound), correctedLowerBound);
- newLeft = Math.min(Math.max(newLeft, correctedLeftBound), correctedRightBound);
- Object.assign(chatContainer.style, {
- left: newLeft + "px",
- top: newTop + "px",
- });
- e.preventDefault();
- });
-
- dragBar.addEventListener("pointerup", function (e) {
- dragBar.releasePointerCapture(e.pointerId);
- saveChatPosition();
- });
- }
-
- function removeDragBarWithOpacitySlider(chatContainer) {
- if (!chatContainer) return;
- const dragBar = chatContainer.querySelector('.chat-drag-bar');
- if (dragBar) dragBar.remove();
- }
-
- function removeAllChatStyles(chatContainer) {
- removeStyle(styleRules.floatingChatStyleCollapsed);
- removeStyle(styleRules.floatingChatStyleExpanded);
- removeStyle(styleRules.floatingChatStyle);
-
- if (chatContainer) chatContainer.style = '';
- }
-
- function applySavedChatStyle(chatContainer) {
- if (!chatContainer) return;
-
- const movieRect = state.moviePlayer.getBoundingClientRect();
- const chatParentRect = chatContainer.parentElement.getBoundingClientRect();
- const chatRect = chatContainer.getBoundingClientRect();
- const minWidth = parseInt(MIN_CHAT_SIZE.width);
- const minHeight = parseInt(MIN_CHAT_SIZE.height);
-
- let width = parseFloat(state.userSettings.chatStyle.width);
- let height = parseFloat(state.userSettings.chatStyle.height);
- let top = parseFloat(state.userSettings.chatStyle.top);
- let left = parseFloat(state.userSettings.chatStyle.left);
- const correctedTopBound = movieRect.top - chatParentRect.top;
- const correctedLeftBound = movieRect.left - chatParentRect.left;
- width = Math.min(Math.max(minWidth, width), movieRect.width);
- height = Math.min(Math.max(minHeight, height), movieRect.height);
- top = Math.max(top, correctedTopBound) - (
- state.chatCollapsed ?
- 0 :
- chatRect.bottom - movieRect.bottom
- );
- left = Math.max(left, correctedLeftBound);
- console.log('width', width, 'height', height);
- Object.assign(chatContainer.style, {
- left: left + "px",
- top: top + "px",
- width: width + "px",
- height: height + "px",
- opacity: parseFloat(state.userSettings.chatStyle.opacity)
- });
- }
-
- // STYLE UPDATE FUNCTIONS
- function updateStyles() {
- try {
- if (state.userSettings.useCustomPlayerHeight) {
- state.userSettings.modifyVideoPlayer = true;
- }
-
- const shouldNotActivate =
- (state.blacklist && state.blacklist.has(state.videoId)) ||
- (state.userSettings.enableOnlyForLiveStreams && !state.isLiveStream);
-
- if (shouldNotActivate) {
- removeAllStyles();
- if (state.moviePlayer && state.moviePlayer.setCenterCrop) {
- state.moviePlayer.setCenterCrop();
- }
- return;
- }
-
- setStyleState(styleRules.chatStyle, state.userSettings.modifyChat);
- setStyleState(styleRules.videoPlayerStyle, state.userSettings.modifyVideoPlayer);
- updateHeadmastStyle();
- updateFullscreenFloatingChatStyle();
- if (state.moviePlayer && state.moviePlayer.setCenterCrop) {
- state.moviePlayer.setCenterCrop();
- }
- } catch (error) {
- logDebug(`Error when updating styles: ${error}`, 'error');
- }
- }
-
- function updateHeadmastStyle() {
- updateLowHeadmastStyle();
-
- const shouldShrinkHeadmast =
- state.isTheaterMode &&
- state.chatFrame?.getAttribute('theater-watch-while') === '' &&
- (state.userSettings.setLowHeadmast || state.userSettings.modifyChat);
-
- // Update chat width for style calculation
- state.chatWidth = state.chatFrame?.offsetWidth || 0;
-
- // Apply or remove headmast style
- setStyleState(styleRules.headmastStyle, shouldShrinkHeadmast);
- }
-
- function updateLowHeadmastStyle() {
- if (!state.moviePlayer) return;
-
- const shouldApplyLowHeadmast =
- state.userSettings.setLowHeadmast &&
- state.isTheaterMode &&
- !state.isFullscreen &&
- state.currentPageType === 'watch';
-
- setStyleState(styleRules.lowHeadmastStyle, shouldApplyLowHeadmast);
- }
-
- function updateFullscreenFloatingChatStyle() {
- try {
- const chatContainer = document.querySelector('#chat-container');
- setStyleState(styleRules.floatingChatStyleCollapsed,
- state.chatCollapsed && state.isFullscreen);
- setStyleState(styleRules.floatingChatStyleExpanded,
- !state.chatCollapsed && state.isFullscreen);
- setStyleState(styleRules.floatingChatStyle, state.isFullscreen);
-
- if (state.userSettings.floatingChat &&
- chatContainer.querySelector('#chat') &&
- chatContainer &&
- state.isFullscreen) {
-
- applySavedChatStyle(chatContainer);
- removeDragBarWithOpacitySlider(chatContainer);
- addDragBarWithOpacitySlider(chatContainer);
- addResizeHandles(chatContainer);
- } else if (chatContainer) {
- removeAllChatStyles(chatContainer);
- removeDragBarWithOpacitySlider(chatContainer);
- removeResizeHandles(chatContainer);
- }
- } catch (error) {
- logDebug(`Error when updating fullscreen chat styles: ${error}`, 'error');
- }
- }
-
- function updateDebugStyles() {
- const chatContainer = document.querySelector('#chat-container');
- if (chatContainer) {
- if (state.userSettings.debug) {
- chatContainer.setAttribute("debug", "");
- } else {
- chatContainer.removeAttribute("debug");
- }
- }
- }
-
- // EVENT HANDLERS
- function updateFullscreenStatus() {
- state.isFullscreen = !!document.fullscreenElement;
- updateStyles();
- }
-
- function updateTheaterStatus(event) {
- state.isTheaterMode = !!event?.detail?.enabled;
- updateStyles();
- }
-
- function updateChatStatus(event) {
- state.chatFrame = event.target;
- state.chatCollapsed = event.detail !== false;
-
- // Wait for player API to be ready before updating styles
- window.addEventListener('player-api-ready', updateStyles, { once: true });
- }
-
- function updateMoviePlayer() {
- const newMoviePlayer = document.querySelector('#movie_player');
-
- // Create resize observer if needed
- if (!state.resizeObserver) {
- state.resizeObserver = new ResizeObserver(() => {
- state.moviePlayerHeight = state.moviePlayer?.offsetHeight || 0;
- updateStyles();
- });
- }
-
- // Stop observing old player
- if (state.moviePlayer) {
- state.resizeObserver.unobserve(state.moviePlayer);
- }
-
- // Start observing new player
- state.moviePlayer = newMoviePlayer;
- if (state.moviePlayer) {
- state.resizeObserver.observe(state.moviePlayer);
- }
- }
-
- function updateVideoStatus(event) {
- try {
- state.currentPageType = event.detail.pageData.page;
- state.videoId = event.detail.pageData.playerResponse.videoDetails.videoId;
- state.isLiveStream = event.detail.pageData.playerResponse.videoDetails.isLiveContent;
-
- updateMoviePlayer();
- refreshMenuOptions();
- } catch (error) {
- logDebug(`Failed to update video status: ${error}`, 'error');
- }
- }
-
- // SETTINGS MANAGEMENT
- async function updateSetting(key, value) {
- try {
- let currentSettings = await GM.getValue('settings', CONFIG.DEFAULT_SETTINGS);
- currentSettings[key] = value;
- await GM.setValue('settings', currentSettings);
- state.userSettings[key] = value;
- } catch (error) {
- logDebug(`Error updating setting: ${error}`, 'error');
- }
- }
-
- async function loadUserSettings() {
- try {
- const storedSettings = await GM.getValue('settings', CONFIG.DEFAULT_SETTINGS);
- const newSettings = {};
- let needsSave = false;
-
- // Use stored settings or defaults
- for (const key in CONFIG.DEFAULT_SETTINGS) {
- if (key in storedSettings) {
- newSettings[key] = storedSettings[key];
- } else {
- newSettings[key] = CONFIG.DEFAULT_SETTINGS[key];
- needsSave = true;
- }
- }
-
- // Check for obsolete settings
- for (const key in storedSettings) {
- if (!(key in CONFIG.DEFAULT_SETTINGS)) {
- needsSave = true;
- }
- }
-
- // Save settings if needed
- state.userSettings = newSettings;
- if (needsSave) {
- await GM.setValue('settings', state.userSettings);
- }
-
- updateMode();
- } catch (error) {
- logDebug(`Error loading user settings: ${error}`, 'error');
- throw new Error(`Error loading user settings: ${error}. Aborting script.`);
- }
- }
-
- function updateMode() {
- if (state.userSettings.isSimpleMode === true) {
- // Backup advanced settings before switching to simple mode
- state.advancedSettingsBackup = {
- ...state.userSettings,
- isSimpleMode: false
- };
-
- // Apply simple mode settings
- state.userSettings = {
- ...CONFIG.DEFAULT_SETTINGS,
- isSimpleMode: true
- };
-
- logDebug('Using simple mode');
- } else if (state.advancedSettingsBackup) {
- // Restore advanced settings
- state.userSettings = {
- ...state.advancedSettingsBackup,
- isSimpleMode: false
- };
-
- logDebug('Using advanced mode');
- logDebug('Advanced settings backup:', state.advancedSettingsBackup);
- }
-
- logDebug(`Loaded settings: ${JSON.stringify(state.userSettings)}`);
- }
-
- async function loadBlacklist() {
- try {
- let storedBlacklist = await GM.getValue('blacklist', CONFIG.DEFAULT_BLACKLIST);
- state.blacklist = new Set(
- Array.isArray(storedBlacklist) ? storedBlacklist : []
- );
-
- logDebug(`Loaded blacklist: ${JSON.stringify(Array.from(state.blacklist))}`);
- } catch (error) {
- logDebug(`Error loading blacklist: ${error}`, 'error');
- throw new Error(`Error loading blacklist: ${error}. Aborting script.`);
- }
- }
-
- async function updateBlacklist() {
- try {
- await GM.setValue('blacklist', Array.from(state.blacklist));
- } catch (error) {
- logDebug(`Error updating blacklist: ${error}`, 'error');
- }
- }
-
- async function updateScriptInfo() {
- try {
- const oldScriptInfo = await GM.getValue('scriptInfo', null);
- const newScriptInfo = {
- version: getScriptVersionFromMeta(),
- };
-
- await GM.setValue('scriptInfo', newScriptInfo);
-
- // Check if script was updated
- if (!oldScriptInfo || compareVersions(newScriptInfo.version, oldScriptInfo?.version) !== 0) {
- state.isScriptRecentlyUpdated = true;
- }
-
- logDebug(`Previous script info: ${JSON.stringify(oldScriptInfo)}`);
- logDebug(`Updated script info: ${JSON.stringify(newScriptInfo)}`);
- } catch (error) {
- logDebug(`Error updating script info: ${error}`, 'error');
- }
- }
-
- async function cleanupOldStorage() {
- try {
- const allowedKeys = ['settings', 'scriptInfo', 'blacklist'];
- const keys = await GM.listValues();
-
- for (const key of keys) {
- if (!allowedKeys.includes(key)) {
- await GM.deleteValue(key);
- logDebug(`Deleted leftover key: ${key}`);
- }
- }
- } catch (error) {
- logDebug(`Error cleaning up old storage keys: ${error}`, 'error');
- }
- }
-
- // MENU MANAGEMENT
- function removeMenuOptions() {
- state.menuItems.forEach((menuItem) => {
- GM.unregisterMenuCommand(menuItem);
- });
- state.menuItems.clear();
- }
-
- async function refreshMenuOptions() {
- const shouldAutoClose = state.isOldTampermonkey;
- removeMenuOptions();
-
- // Advanced mode menu options
- const advancedMenuOptions = state.userSettings.isSimpleMode ? {} : {
- toggleOnlyLiveStreamMode: {
- alwaysShow: true,
- label: () => `${state.userSettings.enableOnlyForLiveStreams ? "✅" : "❌"} ${getLocalizedText().livestreamOnlyMode}`,
- menuId: "toggleOnlyLiveStreamMode",
- handleClick: async function () {
- state.userSettings.enableOnlyForLiveStreams = !state.userSettings.enableOnlyForLiveStreams;
- await updateSetting('enableOnlyForLiveStreams', state.userSettings.enableOnlyForLiveStreams);
- updateStyles();
- refreshMenuOptions();
- },
- },
- toggleChatStyle: {
- alwaysShow: true,
- label: () => `${state.userSettings.modifyChat ? "✅" : "❌"} ${getLocalizedText().applyChatStyles}`,
- menuId: "toggleChatStyle",
- handleClick: async function () {
- state.userSettings.modifyChat = !state.userSettings.modifyChat;
- await updateSetting('modifyChat', state.userSettings.modifyChat);
- updateStyles();
- refreshMenuOptions();
- },
- },
- ...(!state.userSettings.useCustomPlayerHeight ? {
- toggleVideoPlayerStyle: {
- alwaysShow: true,
- label: () => `${state.userSettings.modifyVideoPlayer ? "✅" : "❌"} ${getLocalizedText().applyVideoPlayerStyles}`,
- menuId: "toggleVideoPlayerStyle",
- handleClick: async function () {
- state.userSettings.modifyVideoPlayer = !state.userSettings.modifyVideoPlayer;
- await updateSetting('modifyVideoPlayer', state.userSettings.modifyVideoPlayer);
- updateStyles();
- refreshMenuOptions();
- },
- },
- } : {}),
- toggleLowHeadmast: {
- alwaysShow: true,
- label: () => `${state.userSettings.setLowHeadmast ? "✅" : "❌"} ${getLocalizedText().moveHeadmastBelowVideoPlayer}`,
- menuId: "toggleLowHeadmast",
- handleClick: async function () {
- state.userSettings.setLowHeadmast = !state.userSettings.setLowHeadmast;
- await updateSetting('setLowHeadmast', state.userSettings.setLowHeadmast);
- updateStyles();
- refreshMenuOptions();
- },
- },
- toggleCustomPlayerHeight: {
- alwaysShow: true,
- label: () => `${state.userSettings.useCustomPlayerHeight ? "✅" : "❌"} ${getLocalizedText().useCustomPlayerHeight}`,
- menuId: "toggleCustomPlayerHeight",
- handleClick: async function () {
- state.userSettings.useCustomPlayerHeight = !state.userSettings.useCustomPlayerHeight;
- await updateSetting('useCustomPlayerHeight', state.userSettings.useCustomPlayerHeight);
- updateStyles();
- refreshMenuOptions();
- },
- },
- ...(state.userSettings.useCustomPlayerHeight ? {
- customHeightInputSelector: {
- alwaysShow: true,
- label: () => `🔢 ${getLocalizedText().playerHeightText} (${state.userSettings.playerHeightPx}px)`,
- menuId: "customHeightInputSelector",
- handleClick: async function () {
- const playerHeightInputValue = await promptForNumber();
- if (playerHeightInputValue === null) return;
-
- state.userSettings.playerHeightPx = playerHeightInputValue;
- await updateSetting('playerHeightPx', playerHeightInputValue);
- updateStyles();
- refreshMenuOptions();
- },
- },
- } : {}),
- toggleFloatingChat: {
- alwaysShow: true,
- label: () => `${state.userSettings.floatingChat ? "✅" : "❌"} ${getLocalizedText().floatingChat}`,
- menuId: "toggleFloatingChat",
- handleClick: async function () {
- state.userSettings.floatingChat = !state.userSettings.floatingChat;
- await updateSetting('floatingChat', state.userSettings.floatingChat);
- refreshMenuOptions();
- },
- },
- toggleDebug: {
- alwaysShow: true,
- label: () => `${state.userSettings.debug ? "✅" : "❌"} ${getLocalizedText().debug}`,
- menuId: "toggleDebug",
- handleClick: async function () {
- state.userSettings.debug = !state.userSettings.debug;
- await updateSetting('debug', state.userSettings.debug);
- updateDebugStyles();
- refreshMenuOptions();
- }
- }
- };
-
- // Common menu options for both simple and advanced modes
- const commonMenuOptions = {
- addVideoToBlacklist: {
- alwaysShow: true,
- label: () => `🚫 ${state.blacklist.has(state.videoId) ? getLocalizedText().unblacklistVideo : getLocalizedText().blacklistVideo} [id: ${state.videoId}]`,
- menuId: "addVideoToBlacklist",
- handleClick: async function () {
- if (state.blacklist.has(state.videoId)) {
- state.blacklist.delete(state.videoId);
- } else {
- state.blacklist.add(state.videoId);
- }
- await updateBlacklist();
- updateStyles();
- refreshMenuOptions();
- },
- },
- toggleSimpleMode: {
- alwaysShow: true,
- label: () => `${state.userSettings.isSimpleMode ? "🚀 " + getLocalizedText().simpleMode : "🔧 " + getLocalizedText().advancedMode}`,
- menuId: "toggleSimpleMode",
- handleClick: async function () {
- state.userSettings.isSimpleMode = !state.userSettings.isSimpleMode;
- await updateSetting('isSimpleMode', state.userSettings.isSimpleMode);
- updateMode();
- updateStyles();
- refreshMenuOptions();
- },
- },
- };
-
- // Combine menu options
- const menuOptions = {
- ...commonMenuOptions,
- ...advancedMenuOptions
- };
-
- // Process and register all menu options
- for (const [_, item] of Object.entries(menuOptions)) {
- if (!item.alwaysShow && !state.userSettings.expandMenu) continue;
-
- const menuId = GM.registerMenuCommand(item.label(), item.handleClick, {
- id: item.menuId,
- autoClose: shouldAutoClose,
- });
-
- state.menuItems.add(item.menuId);
- }
- }
-
- async function promptForNumber(message = "Enter a number:", validator = null) {
- while (true) {
- const input = prompt(message);
-
- if (input === null) return null;
-
- const value = Number(input.trim());
- const isValidNumber = input.trim() !== "" && !isNaN(value);
- const passesCustomValidator = typeof validator === "function" ? validator(value) : true;
-
- if (isValidNumber && passesCustomValidator) {
- return value;
- } else {
- alert("⚠️ Please enter a valid number.");
- }
- }
- }
-
- // UTILITY FUNCTIONS
- function logDebug(message, level = 'log', data) {
- if (!state.userSettings.debug) return;
-
- const consoleMethod = console[level] || console.log;
-
- if (data !== undefined) {
- consoleMethod('[Better Theater] ' + message, data);
- } else {
- consoleMethod('[Better Theater] ' + message);
- }
- }
-
- function compareVersions(v1, v2) {
- if (!v1 || !v2) return 0;
-
- const parts1 = v1.split('.').map(Number);
- const parts2 = v2.split('.').map(Number);
- const len = Math.max(parts1.length, parts2.length);
-
- for (let i = 0; i < len; i++) {
- const num1 = parts1[i] || 0;
- const num2 = parts2[i] || 0;
-
- if (num1 > num2) return 1;
- if (num1 < num2) return -1;
- }
-
- return 0;
- }
-
- function getScriptVersionFromMeta() {
- const versionMatch = GM_info.scriptMetaStr.match(/@version\s+([^\r\n]+)/);
- return versionMatch ? versionMatch[1].trim() : null;
- }
-
- function detectGreasemonkeyAPI() {
- if (typeof GM !== 'undefined') return true;
- if (typeof GM_info !== 'undefined') {
- state.useCompatibilityMode = true;
- logDebug("Running in compatibility mode", 'warn');
- return true;
- }
-
- return false;
- }
-
- function checkTampermonkeyVersion() {
- if (GM_info.scriptHandler === "Tampermonkey" &&
- compareVersions(GM_info.version, CONFIG.REQUIRED_VERSIONS.Tampermonkey) !== 1) {
- state.isOldTampermonkey = true;
- if (state.isScriptRecentlyUpdated) {
- GM.notification({
- text: getLocalizedText().tampermonkeyOutdatedAlert,
- timeout: 15000
- });
- }
- }
- }
-
- function isLiveChatIFrame() {
- return /^https?:\/\/.*youtube\.com\/live_chat.*$/.test(window.location.href);
- }
-
- // EVENT LISTENERS
- function attachEventListeners() {
- window.addEventListener('yt-set-theater-mode-enabled', updateTheaterStatus, true);
- window.addEventListener('yt-chat-collapsed-changed', updateChatStatus, true);
- window.addEventListener('yt-page-data-fetched', updateVideoStatus, true);
- window.addEventListener('yt-page-data-updated', updateStyles, true);
- window.addEventListener('fullscreenchange', updateFullscreenStatus, true);
- window.addEventListener('yt-navigate-finish', updateDebugStyles, { once: true });
- }
-
- // INITIALIZATION
- async function initialize() {
- try {
- if (!detectGreasemonkeyAPI()) {
- throw new Error("Did not detect valid Greasemonkey API");
- }
- applyStyle(styleRules.debugResizeHandleStyle, true);
- await cleanupOldStorage();
- await loadUserSettings();
- await loadBlacklist();
- await updateScriptInfo();
- checkTampermonkeyVersion();
- if (isLiveChatIFrame()) {
- applyStyle(styleRules.chatFrameFixStyle, true);
- return;
- }
- applyStyle(styleRules.chatRendererFixStyle, true);
- applyStyle(styleRules.videoPlayerFixStyle, true);
- updateStyles();
- attachEventListeners();
- refreshMenuOptions();
-
- } catch (error) {
- logDebug(`Error when initializing script: ${error}. Aborting script.`, 'error');
- }
- }
-
- initialize();
- })();