diff --git a/README.md b/README.md index 0c9e7e2..6d34531 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ # MaximizeVideo Firefox add-on Maximize HTML5/Flash video and fill current tab. + +* Install Firefox add-on: [addons.mozilla.org](https://addons.mozilla.org/zh-TW/firefox/addon/maximize-video/) +* Install Chrome extension: [Chrome Web Store](https://chrome.google.com/webstore/detail/maximize-video/bfpkgjlnboeecjmnbhbknmemmckmpomb) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 93d9dd0..3ea7399 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1,70 +1,86 @@ { - "extName": { - "message": "Maximize Video", - "description": "" - }, - "extDescription": { - "message": "Maximize HTML5/Flash video and fill current tab.", - "description": "" - }, - "optionPageTitle": { - "message": "Maximize Video Preferences:", - "description": "" - }, - "maximizeThisVideo": { - "message": "Maximize this video", - "description": "" - }, + "extName": { + "message": "Maximize Video", + "description": "" + }, + "extDescription": { + "message": "Maximize HTML5/Flash video and fill current tab.", + "description": "" + }, + "optionPageTitle": { + "message": "Maximize Video Preferences:", + "description": "" + }, + "maximizeThisVideo": { + "message": "Maximize this video", + "description": "" + }, "toolbarAction": { - "message": "Toolbar button action:", - "description": "" + "message": "Toolbar button action:", + "description": "" }, - "selectFirstVideo": { - "message": "Auto maximize first video element(If not found any video element in 3 seconds, auto cancel)", - "description": "" + "selectFirstVideo": { + "message": "Auto maximize first video element(If not found any video element in 3 seconds, auto cancel)", + "description": "" }, "dontSelectVideo": { - "message": "Manual selct video element", - "description": "" + "message": "Manual selct video element", + "description": "" }, "supportFlash": { - "message": "Support Flash video", - "description": "" + "message": "Support Flash video", + "description": "" }, "ignoreTinyElement": { - "message": "Ignore tiny element:", - "description": "" - }, - "minWidth": { - "message": "Minimum width", - "description": "" + "message": "Ignore tiny element:", + "description": "" + }, + "minWidth": { + "message": "Minimum width", + "description": "" }, "minHeight": { - "message": "Minimum height", - "description": "" + "message": "Minimum height", + "description": "" }, - "popupWindow": { - "message": "After maximize video, pop-up current tab to standalone window", - "description": "" + "popupWindow": { + "message": "After maximize video, pop-up current tab to standalone window", + "description": "" }, "needOtherAddon": { - "message": "**You need install another add-on 'Popup Window' to enable this function: ", - "description": "" + "message": "**You need install another add-on 'Popup Window' to enable this function: ", + "description": "" }, - "installPopupWindow": { - "message": "https://addons.mozilla.org/firefox/addon/popup-window/", - "description": "" + "installPopupWindow": { + "message": "https://addons.mozilla.org/firefox/addon/popup-window/", + "description": "" + }, + "delayForHideCursor": { + "message": "When maximize video, auto hide mouse cursor after ", + "description": "" + }, + "delayForHideCursor2": { + "message": " seconds.", + "description": "" }, "iconColor": { - "message": "Toolbar button icon color", - "description": "" + "message": "Toolbar button icon color", + "description": "" }, "iconColorBlack": { - "message": "Black", - "description": "" + "message": "Black", + "description": "" }, "iconColorWhite": { - "message": "White", - "description": "" + "message": "White", + "description": "" + }, + "execute": { + "message": "Maximize video", + "description": "" + }, + "youtubeControllers": { + "message": "Keep original video controls on Youtube", + "description": "" } } diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index d3adc45..9e89539 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -1,70 +1,86 @@ { - "extName": { - "message": "Maximize Video", - "description": "" - }, - "extDescription": { - "message": "將 HTML5/Flash 影片放到最大,填滿頁面。", - "description": "" - }, - "optionPageTitle": { - "message": "Maximize Video 設定:", - "description": "" - }, - "maximizeThisVideo": { - "message": "將此影片最大化", - "description": "" - }, + "extName": { + "message": "Maximize Video", + "description": "" + }, + "extDescription": { + "message": "將 HTML5/Flash 影片放到最大,填滿頁面。", + "description": "" + }, + "optionPageTitle": { + "message": "Maximize Video 設定:", + "description": "" + }, + "maximizeThisVideo": { + "message": "將此影片最大化", + "description": "" + }, "toolbarAction": { - "message": "於工具列啟動後:", - "description": "" + "message": "於工具列啟動後:", + "description": "" }, - "selectFirstVideo": { - "message": "自動最大化頁面中第一個找到的影片(如果三秒內沒找到任何影片,將自動取消)", - "description": "" + "selectFirstVideo": { + "message": "自動最大化頁面中第一個找到的影片(如果三秒內沒找到任何影片,將自動取消)", + "description": "" }, "dontSelectVideo": { - "message": "手動選擇要最大化的影片", - "description": "" + "message": "手動選擇要最大化的影片", + "description": "" }, "supportFlash": { - "message": "支援 Flash 影片", - "description": "" + "message": "支援 Flash 影片", + "description": "" }, "ignoreTinyElement": { - "message": "忽略小型元素:", - "description": "" - }, - "minWidth": { - "message": "最小寬度", - "description": "" + "message": "忽略小型元素:", + "description": "" + }, + "minWidth": { + "message": "最小寬度", + "description": "" }, "minHeight": { - "message": "最小高度", - "description": "" + "message": "最小高度", + "description": "" }, - "popupWindow": { - "message": "影片最大化後自動彈出至獨立視窗", - "description": "" + "popupWindow": { + "message": "影片最大化後自動彈出至獨立視窗", + "description": "" }, "needOtherAddon": { - "message": "**要啟用此功能需另外安裝 Popup Window 套件:", - "description": "" + "message": "**要啟用此功能需另外安裝 Popup Window 套件:", + "description": "" }, "installPopupWindow": { - "message": "https://addons.mozilla.org/firefox/addon/popup-window/", - "description": "" + "message": "https://addons.mozilla.org/firefox/addon/popup-window/", + "description": "" + }, + "delayForHideCursor": { + "message": "最大化影片後,於", + "description": "" + }, + "delayForHideCursor2": { + "message": "秒後自動隱藏滑鼠游標。", + "description": "" }, "iconColor": { - "message": "工具列圖示顏色", - "description": "" + "message": "工具列圖示顏色", + "description": "" }, "iconColorBlack": { - "message": "黑", - "description": "" + "message": "黑", + "description": "" }, "iconColorWhite": { - "message": "白", - "description": "" + "message": "白", + "description": "" + }, + "execute": { + "message": "影片最大化", + "description": "" + }, + "youtubeControllers": { + "message": "在 Youtube 網站使用原有的播放器控制列", + "description": "" } } diff --git a/css/maximizeVideo.css b/css/maximizeVideo.css index 08441ff..3d73593 100644 --- a/css/maximizeVideo.css +++ b/css/maximizeVideo.css @@ -12,6 +12,7 @@ [mvclass=show], [mvclass=show-t] { + contain:unset !important; min-width:0px !important; min-height:0px !important; width:0px !important; @@ -19,23 +20,49 @@ transform: none !important; -webkit-transform:none !important; transform-style:flat !important; - -webkit-transformStyle:flat !important; + -webkit-transform-style:flat !important; border-width:0px !important; + will-change:auto !important; + mask-image: unset !important; + -webkit-mask-image: unset !important; + container-type: normal !important; + background: none !important; +} + +video[mvclass=core] { + background-color:black; } .controls[mvclass=core], +.hover-display[mvclass=core], +.pl-controls-bottom[mvclass=core], +.player-streamstatus[mvclass=core], .player-controls-bottom[mvclass=core] { position: fixed !important; z-index: 2147483640; } +.pl-controls-bottom[mvclass=core]:hover, .player-controls-bottom[mvclass=core]:hover { opacity: 1 !important; } +.hover-display[mvclass=core] { + width: 100% !important; +} + +.hover-display[mvclass=core]:hover { + opacity: 1 !important; +} + +.hover-display[mvclass=core] .pl-pinned-panel { + display: none !important; +} + html[mvclass=core], body[mvclass=show], body[mvclass=show-t] { + contain:unset !important; background-color: #000 !important; position: relative !important; overflow: hidden !important; @@ -72,3 +99,7 @@ body[mvclass=show-t] { .mvVideoBlock:hover { opacity: 0.5; } + +[mvclass="show-t"] [data-a-target="player-settings-submenu-advanced-toggle-mini"] { + display:none !important; +} diff --git a/options.css b/css/options.css similarity index 68% rename from options.css rename to css/options.css index 361f04c..2b8eac3 100644 --- a/options.css +++ b/css/options.css @@ -13,8 +13,6 @@ div.col { width: 60px; } -select.itemInput { - display: inline-block; - width: 250px; - margin-left:12px; +.delayForHideCursor { + width: 50px; } diff --git a/css/ytb.css b/css/ytb.css new file mode 100644 index 0000000..260c5cf --- /dev/null +++ b/css/ytb.css @@ -0,0 +1,32 @@ +body.mvytp { + overflow: hidden !important; +} + +body.mvytp .ytp-size-button { + display: none !important; +} + +body.mvytp #movie_player { + position: fixed !important; + /* z-index: 999999999999 !important; */ + z-index: 2100 !important; + bottom: 0px !important; + right: 0px !important; + left: 0px !important; + top: 0px !important; +} + +body.mvytp .html5-video-container { + height: 100% !important; + width: 100% !important; +} + +body.mvytp .html5-video-container .html5-main-video { + height: 100% !important; + width: 100% !important; + bottom: 0px !important; + right: 0px !important; + left: 0px !important; + top: 0px !important; + background: #000 !important; +} diff --git a/icon/icon.svg b/icon/icon.svg index d9168d0..b88cd6b 100644 --- a/icon/icon.svg +++ b/icon/icon.svg @@ -1,10 +1,10 @@ \ No newline at end of file diff --git a/script/background.js b/js/background.js similarity index 58% rename from script/background.js rename to js/background.js index fe84884..560beb5 100644 --- a/script/background.js +++ b/js/background.js @@ -5,11 +5,14 @@ let selectedVideo = null; let defaultPreference = { popupWindow: false, toolbarAction: 0, - supportFlash: true, + // supportFlash: true, minWidth: 100, minHeight: 100, + autoHideCursor: false, + delayForHideCursor: 5, iconColor: 0, - version: 3 + youtubeControllers: false, + version: 8 }; let preferences = {}; @@ -85,29 +88,37 @@ window.addEventListener('DOMContentLoaded', event => { chrome.browserAction.disable(); chrome.browserAction.onClicked.addListener(tab => { - let hashCode = getHashCode(); - chrome.tabs.sendMessage(tab.id, { - action: 'setVideoMask', - toolbarAction: preferences.toolbarAction, - hashCode: hashCode - }); + execBrowserAction(tab); }); +const execBrowserAction = (tab) => { + if(!['about:addons', 'about:blank'].includes(tab.url)) { + let hashCode = getHashCode(); + chrome.tabs.sendMessage(tab.id, { + action: 'setVideoMask', + toolbarAction: preferences.toolbarAction, + hashCode: hashCode + }); + } +}; + chrome.tabs.onUpdated.addListener((tabId, changeInfo, tabInfo) => { - try{ - chrome.tabs.sendMessage(tabId, { - action: 'getReadyStatus' - }, response => { - if(response){ - if(response.readyStatus) { - chrome.browserAction.enable(tabId); - } - else { - chrome.browserAction.disable(tabId); + if(!['about:addons', 'about:blank'].includes(tabInfo.url)) { + try{ + chrome.tabs.sendMessage(tabId, { + action: 'getReadyStatus' + }, response => { + if(response){ + if(response.readyStatus) { + chrome.browserAction.enable(tabId); + } + else { + chrome.browserAction.disable(tabId); + } } - } - }); - } catch(ex){} + }); + } catch(ex){} + } }); chrome.tabs.query({}, tabs => { @@ -129,11 +140,42 @@ chrome.tabs.query({}, tabs => { } }); +chrome.commands.onCommand.addListener(command => { + if (command === "maximizeVideo") { + chrome.tabs.query({active: true, currentWindow: true}, tabs => { + if ((typeof tabs !== 'undefined') && (tabs.length > 0)) { + let tab = tabs[0]; + browser.browserAction.isEnabled({tabId: tab.id}).then(result => { + if(result === true) { + execBrowserAction(tab); + } + }); + } + else { + } + }); + } +}); + const messageHandler = (message, sender, sendResponse) => { // console.log(message); if(message.action === 'tabReady'){ chrome.browserAction.enable(sender.tab.id); } + else if(message.action === 'execContentScript'){ + chrome.tabs.executeScript(sender.tab.Id, { + file: 'js/content-script.js', + frameId: sender.frameId, + runAt: 'document_end' + }, () => { + chrome.tabs.sendMessage(sender.tab.id, { + action: 'scanVideo', + // supportFlash: message.supportFlash !== undefined ? message.supportFlash : preferences.supportFlash, + minWidth: message.minWidth !== undefined ? message.minWidth : preferences.minWidth, + minHeight: message.minHeight !== undefined ? message.minHeight : preferences.minHeight + }, {frameId: sender.frameId}); + }); + } else if(message.action === 'popupWindow'){ if(preferences.popupWindow) { chrome.runtime.sendMessage('PopupWindow@ettoolong', @@ -144,10 +186,14 @@ const messageHandler = (message, sender, sendResponse) => { } } else if(message.action === 'scanVideo'){ + chrome.tabs.executeScript(sender.tab.Id, { + code: '(function(){if(window !== window.top && !window.selfId) chrome.runtime.sendMessage({action: "execContentScript"})})();', + allFrames: true + }); chrome.tabs.sendMessage(sender.tab.id, { action: 'scanVideo', hashCode: message.hashCode, - supportFlash: message.supportFlash !== undefined ? message.supportFlash : preferences.supportFlash, + // supportFlash: message.supportFlash !== undefined ? message.supportFlash : preferences.supportFlash, minWidth: message.minWidth !== undefined ? message.minWidth : preferences.minWidth, minHeight: message.minHeight !== undefined ? message.minHeight : preferences.minHeight }); @@ -158,15 +204,47 @@ const messageHandler = (message, sender, sendResponse) => { }); } else if(message.action === 'maximizeVideo'){ - chrome.tabs.sendMessage(sender.tab.id, { - action: 'maximizeVideo', - hashCode: message.hashCode, - strict: message.strict - }); + const exec = ({youtubeControllers}) => { + chrome.tabs.sendMessage(sender.tab.id, { + action: 'maximizeVideo', + hashCode: message.hashCode, + strict: message.strict, + youtubeControllers + }); + } + + if (preferences.youtubeControllers && message.url.startsWith('https://www.youtube.com/watch')) { + exec({youtubeControllers: true}) + chrome.tabs.sendMessage(sender.tab.id, { + action: 'maximizeVideo-ytb', + }); + } else { + exec({youtubeControllers: false}) + } } else if(message.action === 'cancelMaximaMode'){ + const exec = ({youtubeControllers}) => { + chrome.tabs.sendMessage(sender.tab.id, { + action: 'cancelMaximaMode', + youtubeControllers + }); + } + + if (preferences.youtubeControllers && message.url.startsWith('https://www.youtube.com/watch')) { + exec({youtubeControllers: true}) + chrome.tabs.sendMessage(sender.tab.id, { + action: 'cancelMaximaMode-ytb', + }); + } else { + exec({youtubeControllers: false}) + } + } + else if(message.action === 'videoHotkey'){ chrome.tabs.sendMessage(sender.tab.id, { - action: 'cancelMaximaMode' + action: 'videoHotkey', + keyCode: message.keyCode, + shiftKey: message.shiftKey, + ctrlKey: message.ctrlKey }); } //return true; @@ -185,9 +263,6 @@ const externalMessageHandler = (message, sender, sendResponse) => { if(message.action === 'maximizeVideo' && message.tabId !== undefined) { let toolbarAction = preferences.toolbarAction; - let supportFlash = preferences.supportFlash; - let minWidth = preferences.minWidth; - let minHeight = preferences.minHeight; let hashCode = getHashCode(); if(message.autoSelect !== undefined) { toolbarAction = message.autoSelect === true ? 1 : 0; @@ -197,7 +272,7 @@ const externalMessageHandler = (message, sender, sendResponse) => { action: 'setVideoMask', toolbarAction: toolbarAction, hashCode: hashCode, - supportFlash: message.supportFlash !== undefined ? message.supportFlash : preferences.supportFlash, + // supportFlash: message.supportFlash !== undefined ? message.supportFlash : preferences.supportFlash, minWidth: message.minWidth !== undefined ? message.minWidth : preferences.minWidth, minHeight: message.minHeight !== undefined ? message.minHeight : preferences.minHeight }); diff --git a/script/content-script.js b/js/content-script.js similarity index 55% rename from script/content-script.js rename to js/content-script.js index 31af510..d287825 100644 --- a/script/content-script.js +++ b/js/content-script.js @@ -1,9 +1,216 @@ +let currentPrefs = {}; +let init = false; + +const shortcutFuncs = { + toggleCaptions: function(v){ + const validTracks = []; + for(let i = 0; i < v.textTracks.length; ++i){ + const tt = v.textTracks[i]; + if(tt.mode === 'showing'){ + tt.mode = 'disabled'; + if(v.textTracks.addEventListener){ + // If text track event listeners are supported + // (they are on the most recent Chrome), add + // a marker to remember the old track. Use a + // listener to delete it if a different track + // is selected. + v.cbhtml5vsLastCaptionTrack = tt.label; + function cleanup(e){ + for(let i = 0; i < v.textTracks.length; ++i){ + const ott = v.textTracks[i]; + if(ott.mode === 'showing'){ + delete v.cbhtml5vsLastCaptionTrack; + v.textTracks.removeEventListener('change', cleanup); + return; + } + } + } + v.textTracks.addEventListener('change', cleanup); + } + return; + }else if(tt.mode !== 'hidden'){ + validTracks.push(tt); + } + } + // If we got here, none of the tracks were selected. + if(validTracks.length === 0){ + return true; // Do not prevent default if no UI activated + } + // Find the best one and select it. + validTracks.sort(function(a, b){ + + if(v.cbhtml5vsLastCaptionTrack){ + const lastLabel = v.cbhtml5vsLastCaptionTrack; + + if(a.label === lastLabel && b.label !== lastLabel){ + return -1; + }else if(b.label === lastLabel && a.label !== lastLabel){ + return 1; + } + } + + const aLang = a.language.toLowerCase(), + bLang = b.language.toLowerCase(), + navLang = navigator.language.toLowerCase(); + + if(aLang === navLang && bLang !== navLang){ + return -1; + }else if(bLang === navLang && aLang !== navLang){ + return 1; + } + + const aPre = aLang.split('-')[0], + bPre = bLang.split('-')[0], + navPre = navLang.split('-')[0]; + + if(aPre === navPre && bPre !== navPre){ + return -1; + }else if(bPre === navPre && aPre !== navPre){ + return 1; + } + + return 0; + })[0].mode = 'showing'; + }, + + togglePlay: function(v){ + if(v.paused) + v.play(); + else + v.pause(); + }, + + toStart: function(v){ + v.currentTime = 0; + }, + + toEnd: function(v){ + v.currentTime = v.duration; + }, + + skipLeft: function(v,key,shift,ctrl){ + if(shift) + v.currentTime -= 10; + else if(ctrl) + v.currentTime -= 1; + else + v.currentTime -= 5; + }, + + skipRight: function(v,key,shift,ctrl){ + if(shift) + v.currentTime += 10; + else if(ctrl) + v.currentTime += 1; + else + v.currentTime += 5; + }, + + increaseVol: function(v){ + if(v.volume <= 0.9) v.volume += 0.1; + else v.volume = 1; + }, + + decreaseVol: function(v){ + if(v.volume >= 0.1) v.volume -= 0.1; + else v.volume = 0; + }, + + toggleMute: function(v){ + v.muted = !v.muted; + }, + + toggleFS: function(v){ + v.requestFullscreen(); + }, + + slow: function(v,key,shift){ + if(v.playbackRate >= 0.25) v.playbackRate -= 0.25; + else v.playbackRate = 0.01; + }, + + fast: function(v,key,shift){ + v.playbackRate += 0.25; + }, + + normalSpeed: function(v,key,shift){ + v.playbackRate = v.defaultPlaybackRate; + }, + + toPercentage: function(v,key){ + v.currentTime = v.duration * (key - 48) / 10.0; + }, +}; + +const keyFuncs = { + 32 : shortcutFuncs.togglePlay, // Space + 75 : shortcutFuncs.togglePlay, // K + 35 : shortcutFuncs.toEnd, // End + 48 : shortcutFuncs.toStart, // 0 + 36 : shortcutFuncs.toStart, // Home + 37 : shortcutFuncs.skipLeft, // Left arrow + 74 : shortcutFuncs.skipLeft, // J + 39 : shortcutFuncs.skipRight, // Right arrow + 76 : shortcutFuncs.skipRight, // L + 38 : shortcutFuncs.increaseVol, // Up arrow + 40 : shortcutFuncs.decreaseVol, // Down arrow + 77 : shortcutFuncs.toggleMute, // M + 70 : shortcutFuncs.toggleFS, // F + 67 : shortcutFuncs.toggleCaptions, // C + 188: shortcutFuncs.slow, // Comma + 190: shortcutFuncs.fast, // Period + 191: shortcutFuncs.normalSpeed, // Forward slash + 49 : shortcutFuncs.toPercentage, // 1 + 50 : shortcutFuncs.toPercentage, // 2 + 51 : shortcutFuncs.toPercentage, // 3 + 52 : shortcutFuncs.toPercentage, // 4 + 53 : shortcutFuncs.toPercentage, // 5 + 54 : shortcutFuncs.toPercentage, // 6 + 55 : shortcutFuncs.toPercentage, // 7 + 56 : shortcutFuncs.toPercentage, // 8 + 57 : shortcutFuncs.toPercentage, // 9 +}; + +const srcProxy = { + 'bleacherreport.com': { + play: (node) => { + let elem = node.parentNode.parentNode.querySelector('.amp-interactive'); + elem.click(); + } + } +} + +const setMiniPlayer = (impl, disable) => { + const settingsButton = document.querySelector('[data-a-target="player-settings-button"]'); + try { + settingsButton.click(); + document.querySelector('[data-a-target="player-settings-menu-item-advanced"]').click(); + const menuItem = document.querySelector('[data-a-target="player-settings-submenu-advanced-toggle-mini"]'); + const input = menuItem.querySelector('input'); + if (disable) { + if (input.checked) { + impl.miniPlayer = true; + input.click(); + } + } else { + if (!!impl.miniPlayer && !input.checked) { + input.click(); + } + } + } catch (e) { + } finally { + settingsButton.click(); + } +} + function MVUniversal() {} MVUniversal.prototype={ topTags: [], mvClass: 'show', setCoreNode: function () { }, + restoreCoreNode: function () { + }, getMainNode: function (node) { return node; }, @@ -13,7 +220,7 @@ MVUniversal.prototype={ if(tagName === 'video' || tagName === 'iframe') { let attribute = tagName === 'video' ? 'controls' : 'allowfullscreen'; if(show) { - this.original[attribute] = node.getAttribute(attribute); + this.original[attribute] = node.hasAttribute(attribute) ? node.getAttribute(attribute) : null; node.setAttribute(attribute, 'true'); } let script = document.createElement('script'); @@ -21,7 +228,7 @@ MVUniversal.prototype={ script.textContent = '(function(){Object.defineProperty(document.querySelector("video[mvHashCode='+this.currentHashCode+']"), "'+attribute+'", {configurable: false});document.head.removeChild(document.getElementById("mvScript"));})()'; document.head.appendChild(script); if(!show) { - if(this.original[attribute]) + if(this.original[attribute] !== null) node.setAttribute(attribute, this.original[attribute]); else node.removeAttribute(attribute); @@ -43,6 +250,15 @@ MVUniversal.prototype={ if(this.status === 'maximaVideo') event.stopImmediatePropagation(); }, true); + node.addEventListener('play', event => { + if (!node.src) { + let proxy = srcProxy[window.location.host] + if (proxy) { + event.preventDefault(); + proxy.play(node); + } + } + }, true); } } } @@ -52,10 +268,16 @@ MVTwitch.prototype={ topTags: ['body', 'html'], mvClass: 'show-t', setCoreNode: function () { - document.querySelector('.player-controls-bottom').setAttribute('mvclass', 'core'); + let coreNode = document.querySelector('.player-controls'); + coreNode.parentNode.setAttribute('mvclass', 'core'); + coreNode.setAttribute('mvclass', 'core'); + setMiniPlayer(this, true); + }, + restoreCoreNode: function () { + setMiniPlayer(this, false); }, getMainNode: function (node) { - return document.querySelector('.video-player .video-player__container'); + return document.querySelector('.video-player__container'); }, setControllers: function (show, node) { }, @@ -68,9 +290,17 @@ MVETwitch.prototype={ topTags: ['body', 'html'], mvClass: 'show-t', setCoreNode: function () { - let controlsNode = document.querySelector('.player-controls-bottom'); + let controlsNode = document.querySelector('.pl-controls-bottom'); controlsNode.setAttribute('mvclass', 'core'); controlsNode.parentNode.setAttribute('mvclass', 'core'); + let hoverDisplay = document.querySelector('.hover-display'); + hoverDisplay.setAttribute('mvclass', 'core'); + let playerui = document.querySelector('.player-ui'); + if(playerui) { + playerui.setAttribute('mvclass', 'core'); + } + }, + restoreCoreNode: function () { }, getMainNode: function (node) { return node; @@ -88,6 +318,8 @@ MVNetflix.prototype={ setCoreNode: function () { document.querySelector('.controls').setAttribute('mvclass', 'core'); }, + restoreCoreNode: function () { + }, getMainNode: function (node) { return node; }, @@ -104,25 +336,27 @@ let vnStyle = [ 'position:fixed !important;', 'top:0 !important;', 'left:0 !important;', - 'min-width:0 !important;', - 'min-height:0 !important;', - 'width:100% !important;', - 'height:100% !important;', - 'max-width:100% !important;', - 'max-height:100% !important;', + 'min-width:100vw !important;', + 'min-height:100vh !important;', + 'width:100vw !important;', + 'height:100vh !important;', + 'max-width:100vw !important;', + 'max-height:100vh !important;', 'margin:0 !important;', 'padding:0 !important;', + 'transform:none !important;', 'visibility:visible !important;', 'border-width:0 !important;', 'cursor:default !important;', + 'object-fit:contain !important;', 'z-index: 2147483639 !important;', - 'background:black !important;'].join(''); +].join(''); let vnStyleList = [ 'position', 'top', 'left', 'min-width', 'min-height', 'width', 'height', 'max-width', 'max-height', 'margin', 'padding', 'visibility', 'border-width', - 'cursor', 'background']; + 'cursor']; if(window.location.href.startsWith('https://www.twitch.tv/')) { mvImpl = new MVTwitch(); @@ -153,6 +387,14 @@ function getHashCode(length) { return hashCode; } let selfId = getHashCode(HASHCODE_LENGTH); +window.selfId = selfId; + +function isYoutubeEmbed () { + return window.location.href.startsWith('https://www.youtube.com/embed/'); +} +function isYoutubeWatch () { + return window.location.href.startsWith('https://www.youtube.com/watch'); +} function addToMvCover (elemInfo) { // console.log('[addToMvCover] ' + JSON.stringify(elemInfo, null, 4)); @@ -198,7 +440,7 @@ function addToMvCover (elemInfo) { if(event.button === 0) { event.stopImmediatePropagation(); event.preventDefault(); - let msg = {action: 'maximizeVideo', hashCode: elemInfo.hashCode}; + let msg = {action: 'maximizeVideo', url: window.location.href, hashCode: elemInfo.hashCode}; try{ if(event.shiftKey && event.layerX < 10 && event.layerY < 10 ) msg.strict = true; } catch (ex){} @@ -230,7 +472,7 @@ function addToMvCover (elemInfo) { } if(selected) { mvImpl.toolbarAction = 0; - chrome.runtime.sendMessage({action: 'maximizeVideo', hashCode: selected.getAttribute('mvMaskHash')}); + chrome.runtime.sendMessage({action: 'maximizeVideo', url: window.location.href, hashCode: selected.getAttribute('mvMaskHash')}); } } } @@ -268,31 +510,33 @@ function lockMainNodeStyle(lock) { } function maximizeMainNode() { - let hc = mvImpl.mainNode.getAttribute('mvHashCode'); let originalStyle = mvImpl.originalStyle = (mvImpl.mainNode.getAttribute('style') || ''); + let fixedStyle = vnStyle; + let fixedStyleList = [...vnStyleList] let vnNewStyle = ''; originalStyle = originalStyle.trim().replace(/\r\n/g, '\r').replace(/\n/g, '\r').replace(/\r/g, ''); - if(originalStyle === '') { - vnNewStyle = vnStyle; + if (originalStyle === '') { + vnNewStyle = fixedStyle; } else { let styles = originalStyle.split(';'); let slist = []; - for(let s of styles) { + for (let s of styles) { let t = /([a-zA-Z-]{2,})\s?:\s?(.+)/; if(t.test(s)) { let m = s.split(t); let key = m[1]; let value = m[2]; - if(!vnStyleList.includes(key)) + if (!fixedStyleList.includes(key)) { slist.push(key+':'+value); + } } } - if(slist.length === 0) { - vnNewStyle = vnStyle; + if (slist.length === 0) { + vnNewStyle = fixedStyle; } else { - vnNewStyle = vnStyle + slist.join(';')+';'; + vnNewStyle = fixedStyle + slist.join(';')+';'; } } mvImpl.vnNewStyle = vnNewStyle; @@ -301,7 +545,10 @@ function maximizeMainNode() { }; function restoreVideo() { - mvImpl.setControllers(false); + if (!mvImpl.selectedNode) return; + if (!mvImpl.youtubeControllers || (!isYoutubeEmbed() && !isYoutubeWatch())) { + mvImpl.setControllers(false); + } lockMainNodeStyle(false); mvImpl.mainNode.setAttribute('style', mvImpl.originalStyle); @@ -312,9 +559,17 @@ function restoreVideo() { node.removeAttribute('mvclass'); } } + + if(mvImpl.scrollPosition) { + window.scrollTo(mvImpl.scrollPosition.x, mvImpl.scrollPosition.y); + } }; -function maximizeVideo(selectedNode) { +function maximizeVideo(selectedNode, chain = []) { + const _chain = [...chain] + + mvImpl.scrollPosition = { x: window.scrollX, y: window.scrollY }; + const hideAllSibling = (node) => { if(node === mvImpl.mainNode) { node.setAttribute('mvclass', 'core'); @@ -324,21 +579,32 @@ function maximizeVideo(selectedNode) { } let parent = node.parentNode; - if(parent && parent.nodeType === Node.ELEMENT_NODE) { - if(!mvImpl.topTags.includes(parent.tagName.toLocaleLowerCase())) { - hideAllSibling(parent); + if(parent) { + if (parent.nodeType === Node.ELEMENT_NODE ) { + if(!mvImpl.topTags.includes(parent.tagName.toLocaleLowerCase())) { + hideAllSibling(parent); + } + } else if (parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + if (_chain.length) { + parent = _chain.pop() + hideAllSibling(parent); + } } } }; mvImpl.selectedNode = selectedNode; - mvImpl.setControllers(true); + if (!mvImpl.youtubeControllers || (!isYoutubeEmbed() && !isYoutubeWatch())) { + mvImpl.setControllers(true); + } mvImpl.mainNode = mvImpl.getMainNode(selectedNode); if(window !== window.top) { //this video is in iframe window.parent.postMessage({action: 'getId', senderId: selfId, nextAction: 'setVideoNode'},'*'); } mvImpl.registerEvents(mvImpl.mainNode); - hideAllSibling(mvImpl.mainNode); + if (!mvImpl.youtubeControllers || (!isYoutubeEmbed() && !isYoutubeWatch())) { + hideAllSibling(mvImpl.mainNode); + } maximizeMainNode(); } @@ -416,7 +682,54 @@ window.addEventListener('message', e => { window.addEventListener('keydown', event => { if(event.key === 'Escape' && mvImpl.status === 'selectVideo') { chrome.runtime.sendMessage({action: 'cancelSelectMode'}); + } else if (mvImpl.status === 'maximaVideo') { + if (event.altKey || event.metaKey || event.ctrlKey) { + return true; + } + const func = keyFuncs[event.keyCode]; + if(func){ + //send message to background script ! + //func(mvImpl.mainNode, event.keyCode, event.shiftKey, event.ctrlKey); + if(event.keyCode === 70) {// fullscreen + mvImpl.mainNode.requestFullscreen(); + } + else { + let msg = {action: 'videoHotkey', keyCode: event.keyCode, shiftKey: event.shiftKey, ctrlKey: event.ctrlKey}; + chrome.runtime.sendMessage(msg); + } + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + return false; + } + return true; } +}, true); + +const handleKeyEvent = (event) => { + if (mvImpl.status === 'maximaVideo') { + if(event.altKey || event.metaKey){ + return true; + } + const func = keyFuncs[event.keyCode]; + if(func){ + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + return false; + } + return true; + } +} +window.addEventListener('keypress', handleKeyEvent, true); +window.addEventListener('keyup', handleKeyEvent, true); +window.addEventListener('DOMContentLoaded', event => { + document.addEventListener('fullscreenchange', event => { + if (mvImpl.status === 'maximaVideo' && !mvImpl.youtubeControllers && !mvImpl instanceof MVTwitch && !mvImpl instanceof MVETwitch) { + event.stopPropagation(); + event.stopImmediatePropagation(); + } + }, true); }); function inRect(point, rect) { @@ -447,7 +760,7 @@ function isVisible(elem, elemRect) { function getElemInfo(elem) { let elemRect = elem.getBoundingClientRect(); - if(window.location.href.startsWith('https://www.youtube.com/embed/') && !elem.src) { + if(isYoutubeEmbed() && !elem.src) { let newElemRect = { bottom: elemRect.bottom, height: elemRect.height, @@ -523,8 +836,109 @@ function removeVideoMask() { } } +function clearHideCursorTimer() { + if(mvImpl.hideCursorTimer) { + clearTimeout(mvImpl.hideCursorTimer); + mvImpl.hideCursorTimer = null; + } +} + +function setHideCursorTimer() { + clearHideCursorTimer(); + if(currentPrefs.autoHideCursor) { + mvImpl.hideCursorTimer = setTimeout(()=>{ + mvImpl.vnNewStyle = mvImpl.vnNewStyle.replace('cursor:default','cursor:none'); + mvImpl.mainNode.setAttribute('style', mvImpl.vnNewStyle); + + mvImpl.mainNode.addEventListener('mousemove', e => { + mvImpl.vnNewStyle = mvImpl.vnNewStyle.replace('cursor:none','cursor:default'); + mvImpl.mainNode.setAttribute('style', mvImpl.vnNewStyle); + setHideCursorTimer(); + }, {capture: true, once: true}); // FF50+, Ch55+ + }, currentPrefs.delayForHideCursor*1000); + } +} + + +function findVideoElements(selector) { + const elements = [] + + document.querySelectorAll(selector).forEach(element => { + elements.push(element) + }) + + const shadowRoots = [] + const _findShadowRoots = (root) => { + root.querySelectorAll('*').forEach(element => { + // No shadow root? Continue. + if (!element.shadowRoot) { + return + } + shadowRoots.push(element) + _findShadowRoots(element.shadowRoot) + }) + } + _findShadowRoots(document) + if (shadowRoots.length) { + for(const e of shadowRoots) { + const v = e.shadowRoot.querySelector(selector) + if (v) { + elements.push(v) + } + } + } + return elements +} + +function findVideoElement(selector) { + const videoElem = document.querySelector(selector) + if (videoElem) { + return { elem: videoElem, chain: []} + } + + const _findShadowRoots = (root, chain) => { + let res = { elem: null, chain: chain} + root.querySelectorAll('*').forEach(element => { + // No shadow root? Continue. + if (!element.shadowRoot) { + return + } + if (!element.querySelector('#shadowStyle')) { + const shadowStyle = document.createElement('style'); + shadowStyle.setAttribute('id', 'shadowStyle') + shadowStyle.textContent = ` + :host([mvclass=show]) *:not([mvclass=show]):not([mvclass=core]) { + display:none !important; + opacity:0 !important; + visibility: hidden !important; + } + `; + element.appendChild(shadowStyle); + } + const e = element.shadowRoot.querySelector(selector) + if (e) { + res = {elem: e, chain: [ ...chain, element ]} + } else { + const res2 = _findShadowRoots(element.shadowRoot, [ ...chain, element ]) + if (res2.elem) { + res = res2 + } + } + }) + return res + } + const res = _findShadowRoots(document, []) + return res +} + chrome.runtime.onMessage.addListener( (message, sender, sendResponse) => { - if(message.action === 'maximizeVideo') { + if(message.action === 'videoHotkey') { + if(mvImpl.mainNode.tagName === 'VIDEO') { + const func = keyFuncs[message.keyCode]; + func(mvImpl.mainNode, message.keyCode, message.shiftKey, message.ctrlKey); + } + } + else if(message.action === 'maximizeVideo') { if(mvImpl.status === 'maximaVideo') return; mvImpl.status = 'maximaVideo'; @@ -535,18 +949,25 @@ chrome.runtime.onMessage.addListener( (message, sender, sendResponse) => { v.pause(); } - let elem = document.querySelector('video[mvHashCode="'+message.hashCode+'"],embed[mvHashCode="'+message.hashCode+'"][type="application/x-shockwave-flash"],object[mvHashCode="'+message.hashCode+'"][type="application/x-shockwave-flash"]'); + let { elem, chain } = findVideoElement('video[mvHashCode="'+message.hashCode+'"]') if(elem) { + initPrefs( ()=>{ + setHideCursorTimer(); + }); mvImpl.setCoreNode(); mvImpl.currentHashCode = message.hashCode; - if(window.location.href.startsWith('https://www.youtube.com/embed/') && !elem.src) { + mvImpl.youtubeControllers = message.youtubeControllers; + if(isYoutubeEmbed() && !elem.src) { elem.click(); elem.addEventListener('progress', ()=>{ elem.pause(); },{capture: true, once: true}); } mvImpl.strict = message.strict; - maximizeVideo(elem); + maximizeVideo(elem, chain); + if(mvImpl.mainNode.tagName === 'VIDEO') { + mvImpl.mainNode.focus({preventScroll:true}); + } chrome.runtime.sendMessage({action: 'popupWindow'}); } } @@ -574,7 +995,7 @@ chrome.runtime.onMessage.addListener( (message, sender, sendResponse) => { cover.setAttribute('mvMaskHash', message.hashCode); document.body.appendChild(cover); let msg = {action: 'scanVideo', hashCode: message.hashCode}; - if(message.supportFlash !== undefined) msg.supportFlash = message.supportFlash; + // if(message.supportFlash !== undefined) msg.supportFlash = message.supportFlash; if(message.minWidth !== undefined) msg.minWidth = message.minWidth; if(message.minHeight !== undefined) msg.minHeight = message.minHeight; chrome.runtime.sendMessage(msg); @@ -584,20 +1005,21 @@ chrome.runtime.onMessage.addListener( (message, sender, sendResponse) => { chrome.runtime.sendMessage({action: 'cancelSelectMode'}); } else if(mvImpl.status === 'maximaVideo') { - chrome.runtime.sendMessage({action: 'cancelMaximaMode'}); + clearHideCursorTimer(); + chrome.runtime.sendMessage({action: 'cancelMaximaMode', url: window.location.href}); } } } else if(message.action === 'scanVideo') { mvImpl.status = 'selectVideo'; // console.log('scanVideo'); - let selector = message.supportFlash ? 'video,embed[type="application/x-shockwave-flash"],object[type="application/x-shockwave-flash"]' : 'video'; - let elements = document.querySelectorAll(selector); + const selector = 'video'; + let elements = findVideoElements(selector) const _uploadElemInfo = () => { mvImpl.scanVideoTimer = null; if(mvImpl.status === 'selectVideo'){ uploadElemInfo(elements, message.minWidth, message.minHeight ); - elements = document.querySelectorAll(selector); + elements = findVideoElements(selector); uploadElemInfo(elements, message.minWidth, message.minHeight, true); mvImpl.scanVideoTimer = setTimeout(_uploadElemInfo, 200); } @@ -617,9 +1039,10 @@ chrome.runtime.onMessage.addListener( (message, sender, sendResponse) => { } else if(message.action === 'cancelMaximaMode') { mvImpl.status = 'normal'; + mvImpl.restoreCoreNode(); restoreVideo(); } - return true; + return false; }); if(window === window.top) { @@ -632,3 +1055,36 @@ if(window === window.top) { }, true); } } + +function initPrefs(cb){ + if(!init) { + init = true; + chrome.storage.local.get(results => { + if ((typeof results.length === 'number') && (results.length > 0)) { + results = results[0]; + } + currentPrefs = results; + cb(); + }); + + chrome.storage.onChanged.addListener((changes, area) => { + if(area === 'local') { + let changedItems = Object.keys(changes); + for (let item of changedItems) { + currentPrefs[item] = changes[item].newValue; + switch (item) { + case 'autoHideCursor': + case 'delayForHideCursor': + if(mvImpl.status === 'maximaVideo') { + setHideCursorTimer(); + } + break; + } + } + } + }); + } + else { + cb(); + } +} diff --git a/options.js b/js/options.js similarity index 100% rename from options.js rename to js/options.js diff --git a/js/ytb.js b/js/ytb.js new file mode 100644 index 0000000..47911a9 --- /dev/null +++ b/js/ytb.js @@ -0,0 +1,31 @@ +chrome.runtime.onMessage.addListener((message) => { + if(message.action === 'maximizeVideo-ytb') { + const ytpSizeButton = document.querySelector('.ytp-size-button'); + if (ytpSizeButton) { + document.body.classList.add('mvytp'); + const ytdWatchFlexy = document.querySelector('ytd-watch-flexy'); + let theater_mode = ytdWatchFlexy.hasAttribute('theater'); + if (!theater_mode) { + ytdWatchFlexy.setAttribute('mv', ''); + ytpSizeButton.click(); + } + setTimeout(()=>{ + window.dispatchEvent(new Event('resize')); + }, 10); + } + } + else if(message.action === 'cancelMaximaMode-ytb') { + const ytpSizeButton = document.querySelector('.ytp-size-button'); + if (ytpSizeButton) { + document.body.classList.remove('mvytp'); + const ytdWatchFlexy = document.querySelector('ytd-watch-flexy'); + if (ytdWatchFlexy.hasAttribute('mv')) { + ytdWatchFlexy.removeAttribute('mv'); + window.dispatchEvent(new Event('resize')) + setTimeout(()=>{ + ytpSizeButton.click(); + }, 10); + } + } + } +}) diff --git a/manifest.json b/manifest.json index bc8ce0a..294ec48 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "description": "__MSG_extDescription__", "manifest_version": 2, "name": "__MSG_extName__", - "version": "0.0.1", + "version": "0.0.24", "homepage_url": "https://github.com/ettoolong/MaximizeVideo", "icons": { "16": "icon/icon.svg", @@ -11,10 +11,10 @@ "64": "icon/icon.svg", "96": "icon/icon.svg" }, - "applications": { + "browser_specific_settings": { "gecko": { "id": "MaximizeVideo@ettoolong", - "strict_min_version": "51.0" + "strict_min_version": "66.0" } }, "developer": { @@ -22,16 +22,23 @@ "url": "https://github.com/ettoolong/MaximizeVideo" }, "background": { - "scripts": ["script/background.js"] + "scripts": ["js/background.js"] }, "content_scripts": [ { "matches": ["http://*/*","https://*/*","file:///*"], - "js": ["script/content-script.js"], + "js": ["js/content-script.js"], "css": ["css/maximizeVideo.css"], "all_frames": true, "run_at": "document_start" + }, + { + "matches": ["https://www.youtube.com/*"], + "js": ["js/ytb.js"], + "css": ["css/ytb.css"], + "all_frames": false, + "run_at": "document_start" } ], "browser_action": { @@ -40,14 +47,18 @@ "default_icon": "icon/icon_b.svg" }, "permissions": [ - "storage", - "tabs", - "activeTab" + "http://*/*", "https://*/*", "file:///*", + "storage" ], "default_locale": "en", "options_ui": { "page": "options.html", "open_in_tab": false, "browser_style": true + }, + "commands": { + "maximizeVideo": { + "description": "__MSG_execute__" + } } } diff --git a/options.html b/options.html index 5352786..8b3023c 100644 --- a/options.html +++ b/options.html @@ -2,8 +2,8 @@
- - + +