diff --git a/awesome/init.fnl b/awesome/init.fnl index f4bac86..264d1a0 100644 --- a/awesome/init.fnl +++ b/awesome/init.fnl @@ -393,7 +393,8 @@ (not c.size_hints.program_position)) ;; Prevent clients from being unreachable after screen count changes. (awful.placement.no_offscreen c)) - (awful.client.focus.byidx 1))) + (awful.client.focus.byidx 1) + (: c :activate []))) (client.connect_signal "focus" (fn [c] (set c.border_color beautiful.border_focus))) diff --git a/awesome/rules.fnl b/awesome/rules.fnl index 2cf8caa..415390e 100644 --- a/awesome/rules.fnl +++ b/awesome/rules.fnl @@ -1,4 +1,5 @@ (local awful (require "awful")) +(local gears (require "gears")) (local beautiful (require "beautiful")) (local keybindings (require "keybindings")) @@ -15,6 +16,7 @@ ;; :buttons clientbuttons :screen awful.screen.preferred :placement (+ awful.placement.no_overlap awful.placement.no_offscreen) + ;; :shape gears.shape.rounded_rect } } @@ -29,10 +31,20 @@ :floating true :raise true :height 900 - :screen 2 + :screen (screen.count) :placement (+ awful.placement.no_offscreen awful.placement.centered) } } + { + :rule_any { + :class [ + "qutebrowser" + ] + } + :properties { + :screen (screen.count) + } + } { :rule_any { :class [ diff --git a/picom.conf b/picom.conf index 8261f22..d6eb046 100644 --- a/picom.conf +++ b/picom.conf @@ -149,11 +149,11 @@ fade-exclude = [ # Opacity of inactive windows. (0.1 - 1.0, defaults to 1.0) # inactive-opacity = 1 -inactive-opacity = 0.8; +inactive-opacity = 0.7; # Opacity of window titlebars and borders. (0.1 - 1.0, disabled by default) # frame-opacity = 1.0 -frame-opacity = 0.7; +frame-opacity = 0.8; # Default opacity for dropdown menus and popup menus. (0.0 - 1.0, defaults to 1.0) # menu-opacity = 1.0 diff --git a/qutebrowser/autoconfig.yml b/qutebrowser/autoconfig.yml index ac827d3..81980e0 100644 --- a/qutebrowser/autoconfig.yml +++ b/qutebrowser/autoconfig.yml @@ -6,5 +6,5 @@ config_version: 2 settings: - content.notifications: - https://www.reddit.com: false + colors.webpage.darkmode.enabled: + global: true diff --git a/qutebrowser/bookmarks/urls b/qutebrowser/bookmarks/urls index 9accb47..fa77546 100644 --- a/qutebrowser/bookmarks/urls +++ b/qutebrowser/bookmarks/urls @@ -1 +1,2 @@ https://yewtu.be/feed/subscriptions Subscriptions - YewTube +https://portal.azure.com/#@tfcconnection.org/dashboard/private/e5f3455d-3737-47d8-852b-1847a3b4801b Dashboard - Microsoft Azure diff --git a/qutebrowser/config.py b/qutebrowser/config.py index f8aac67..d8c7784 100644 --- a/qutebrowser/config.py +++ b/qutebrowser/config.py @@ -28,20 +28,6 @@ ## Type: Bool c.auto_save.session = True -## Backend to use to display websites. qutebrowser supports two different -## web rendering engines / backends, QtWebKit and QtWebEngine. QtWebKit -## was discontinued by the Qt project with Qt 5.6, but picked up as a -## well maintained fork: https://github.com/annulen/webkit/wiki - -## qutebrowser only supports the fork. QtWebEngine is Qt's official -## successor to QtWebKit. It's slightly more resource hungry than -## QtWebKit and has a couple of missing features in qutebrowser, but is -## generally the preferred choice. -## Type: String -## Valid values: -## - webengine: Use QtWebEngine (based on Chromium). -## - webkit: Use QtWebKit (based on WebKit, similar to Safari). -# c.backend = 'webengine' - ## This setting can be used to map keys to other keys. When the key used ## as dictionary-key is pressed, the binding for the key used as ## dictionary-value is invoked instead. This is useful for global @@ -766,7 +752,7 @@ c.colors.hints.match.fg = base02 ## `colors.webpage.darkmode.algorithm` is set to `lightness-hsl` or ## `brightness-rgb`. ## Type: Float -# c.colors.webpage.darkmode.contrast = 0.0 +c.colors.webpage.darkmode.contrast = 0.7 ## Render all web contents using a dark theme. Example configurations ## from Chromium's `chrome://flags`: - "With simple HSL/CIELAB/RGB-based @@ -778,7 +764,7 @@ c.colors.hints.match.fg = base02 ## `colors.webpage.darkmode.threshold.background` to 205. - "With ## selective inversion of everything": Combines the two variants above. ## Type: Bool -c.colors.webpage.darkmode.enabled = False +c.colors.webpage.darkmode.enabled = True ## Render all colors as grayscale. This only has an effect when ## `colors.webpage.darkmode.algorithm` is set to `lightness-hsl` or @@ -801,14 +787,14 @@ c.colors.webpage.darkmode.enabled = False ## - always: Apply dark mode filter to all images. ## - never: Never apply dark mode filter to any images. ## - smart: Apply dark mode based on image content. -# c.colors.webpage.darkmode.policy.images = 'never' +c.colors.webpage.darkmode.policy.images = 'smart' ## Which pages to apply dark mode to. ## Type: String ## Valid values: ## - always: Apply dark mode filter to all frames, regardless of content. ## - smart: Apply dark mode filter to frames based on background color. -# c.colors.webpage.darkmode.policy.page = 'smart' +c.colors.webpage.darkmode.policy.page = 'smart' ## Threshold for inverting background elements with dark mode. Background ## elements with brightness above this threshold will be inverted, and @@ -2089,7 +2075,7 @@ c.tabs.title.alignment = 'center' ## the search engine name to the search term, e.g. `:open google ## qutebrowser`. ## Type: Dict -c.url.searchengines = {'DEFAULT': 'https://duckduckgo.com/?q={}', 'yt': 'https://yewtu.be/search?q={}', 'mel': 'https://melpa.org/#/?q={}'} +c.url.searchengines = {'DEFAULT': 'https://duckduckgo.com/?q={}', 'yt': 'https://yewtu.be/search?q={}', 'mel': 'https://melpa.org/#/?q={}', 'y': 'https://www.youtube.com/results?search_query={}'} ## Page(s) to open at the start. ## Type: List of FuzzyUrl, or FuzzyUrl @@ -2314,6 +2300,7 @@ config.bind('J', 'tab-prev') # config.bind('{{', 'navigate prev -t') # config.bind('}}', 'navigate next -t') config.bind('v', 'hint links spawn --detach mpv --force-window yes {hint-url}') +config.bind('gv', 'spawn --detach mpv --force-window yes {url}') ## Bindings for caret mode # config.bind('$', 'move-to-end-of-line', mode='caret') diff --git a/qutebrowser/greasemonkey b/qutebrowser/greasemonkey new file mode 120000 index 0000000..bbf8c0e --- /dev/null +++ b/qutebrowser/greasemonkey @@ -0,0 +1 @@ +/home/chris/.local/share/qutebrowser/greasemonkey \ No newline at end of file diff --git a/qutebrowser/greasemonkey/HTML5视频播放器增强脚本.user.js b/qutebrowser/greasemonkey/HTML5视频播放器增强脚本.user.js deleted file mode 100644 index 7b37b14..0000000 --- a/qutebrowser/greasemonkey/HTML5视频播放器增强脚本.user.js +++ /dev/null @@ -1,3313 +0,0 @@ -// ==UserScript== -// @name HTML5视频播放器增强脚本 -// @name:en HTML5 video player enhanced script -// @name:zh HTML5视频播放器增强脚本 -// @name:zh-CN HTML5视频播放器增强脚本 -// @name:zh-TW HTML5視頻播放器增強腳本 -// @name:ja HTML5ビデオプレーヤーの拡張スクリプト -// @name:ko HTML5 비디오 플레이어 고급 스크립트 -// @name:ru HTML5 видео плеер улучшенный скрипт -// @name:de HTML5 Video Player erweitertes Skript -// @namespace https://github.com/xxxily/h5player -// @homepage https://github.com/xxxily/h5player -// @version 3.3.0 -// @description HTML5视频播放增强脚本,支持所有H5视频播放网站,全程快捷键控制,支持:倍速播放/加速播放、视频画面截图、画中画、网页全屏、调节亮度、饱和度、对比度、自定义配置功能增强等功能。 -// @description:en HTML5 video playback enhanced script, supports all H5 video playback websites, full-length shortcut key control, supports: double-speed playback / accelerated playback, video screenshots, picture-in-picture, full-page webpage, brightness, saturation, contrast, custom configuration enhancement And other functions. -// @description:zh HTML5视频播放增强脚本,支持所有H5视频播放网站,全程快捷键控制,支持:倍速播放/加速播放、视频画面截图、画中画、网页全屏、调节亮度、饱和度、对比度、自定义配置功能增强等功能。 -// @description:zh-CN HTML5视频播放增强脚本,支持所有H5视频播放网站,全程快捷键控制,支持:倍速播放/加速播放、视频画面截图、画中画、网页全屏、调节亮度、饱和度、对比度、自定义配置功能增强等功能。 -// @description:zh-TW HTML5視頻播放增強腳本,支持所有H5視頻播放網站,全程快捷鍵控制,支持:倍速播放/加速播放、視頻畫面截圖、畫中畫、網頁全屏、調節亮度、飽和度、對比度、自定義配置功能增強等功能。 -// @description:ja HTML5ビデオ再生拡張スクリプト、すべてのH5ビデオ再生Webサイト、フルレングスのショートカットキーコントロールをサポート、サポート:倍速再生/加速再生、ビデオスクリーンショット、ピクチャーインピクチャー、フルページWebページ、明るさ、彩度、コントラスト、カスタム構成拡張 そして他の機能。 -// @description:ko HTML5 비디오 재생 고급 스크립트, 모든 H5 비디오 재생 웹 사이트 지원, 전체 길이 바로 가기 키 제어 지원 : 2 배속 재생 / 가속 재생, 비디오 스크린 샷, PIP (picture-in-picture), 전체 페이지 웹 페이지, 밝기, 채도, 대비, 사용자 정의 구성 향상 그리고 다른 기능들. -// @description:ru HTML5 улучшенный сценарий воспроизведения видео, поддерживает все веб-сайты воспроизведения видео H5, полноразмерное управление с помощью сочетания клавиш, поддерживает: двухскоростное воспроизведение / ускоренное воспроизведение, скриншоты видео, картинка в картинке, полностраничную веб-страницу, яркость, насыщенность, контрастность, улучшение пользовательской конфигурации И другие функции. -// @description:de Verbessertes Skript für die HTML5-Videowiedergabe, unterstützt alle H5-Videowiedergabewebsites, Tastenkombination in voller Länge, unterstützt: Wiedergabe mit doppelter Geschwindigkeit / beschleunigte Wiedergabe, Video-Screenshots, Bild-in-Bild, ganzseitige Webseite, Helligkeit, Sättigung, Kontrast, benutzerdefinierte Konfigurationsverbesserung Und andere Funktionen. -// @author ankvps -// @icon https://raw.githubusercontent.com/xxxily/h5player/master/logo.png -// @match http://*/* -// @match https://*/* -// @grant unsafeWindow -// @grant GM_addStyle -// @grant GM_setValue -// @grant GM_getValue -// @grant GM_deleteValue -// @grant GM_listValues -// @grant GM_addValueChangeListener -// @grant GM_removeValueChangeListener -// @grant GM_registerMenuCommand -// @grant GM_unregisterMenuCommand -// @grant GM_getTab -// @grant GM_saveTab -// @grant GM_getTabs -// @grant GM_openInTab -// @grant GM_download -// @grant GM_xmlhttpRequest -// @run-at document-start -// @require https://unpkg.com/vue@2.6.11/dist/vue.min.js -// @require https://unpkg.com/element-ui@2.13.0/lib/index.js -// @resource elementUiCss https://unpkg.com/element-ui@2.13.0/lib/theme-chalk/index.css -// @connect 127.0.0.1 -// @license GPL -// ==/UserScript== -(function (w) { if (w) { w.name = 'h5player'; } })(); - -/*! - * @name utils.js - * @description 数据类型相关的方法 - * @version 0.0.1 - * @author Blaze - * @date 22/03/2019 22:46 - * @github https://github.com/xxxily - */ - -/** - * 准确地获取对象的具体类型 参见:https://www.talkingcoder.com/article/6333557442705696719 - * @param obj { all } -必选 要判断的对象 - * @returns {*} 返回判断的具体类型 - */ -function getType (obj) { - if (obj == null) { - return String(obj) - } - return typeof obj === 'object' || typeof obj === 'function' - ? (obj.constructor && obj.constructor.name && obj.constructor.name.toLowerCase()) || - /function\s(.+?)\(/.exec(obj.constructor)[1].toLowerCase() - : typeof obj -} - -const isType = (obj, typeName) => getType(obj) === typeName; -const isObj = obj => isType(obj, 'object'); - -/** - * 任务配置中心 Task Control Center - * 用于配置所有无法进行通用处理的任务,如不同网站的全屏方式不一样,必须调用网站本身的全屏逻辑,才能确保字幕、弹幕等正常工作 - * */ - -class TCC { - constructor (taskConf, doTaskFunc) { - this.conf = taskConf || { - /** - * 配置示例 - * 父级键名对应的是一级域名, - * 子级键名对应的相关功能名称,键值对应的该功能要触发的点击选择器或者要调用的相关函数 - * 所有子级的键值都支持使用选择器触发或函数调用 - * 配置了子级的则使用子级配置逻辑进行操作,否则使用默认逻辑 - * 注意:include,exclude这两个子级键名除外,这两个是用来进行url范围匹配的 - * */ - 'demo.demo': { - fullScreen: '.fullscreen-btn', - exitFullScreen: '.exit-fullscreen-btn', - webFullScreen: function () {}, - exitWebFullScreen: '.exit-fullscreen-btn', - autoPlay: '.player-start-btn', - pause: '.player-pause', - play: '.player-play', - switchPlayStatus: '.player-play', - playbackRate: function () {}, - currentTime: function () {}, - addCurrentTime: '.add-currenttime', - subtractCurrentTime: '.subtract-currenttime', - // 自定义快捷键的执行方式,如果是组合键,必须是 ctrl-->shift-->alt 这样的顺序,没有可以忽略,键名必须全小写 - shortcuts: { - /* 注册要执行自定义回调操作的快捷键 */ - register: [ - 'ctrl+shift+alt+c', - 'ctrl+shift+c', - 'ctrl+alt+c', - 'ctrl+c', - 'c' - ], - /* 自定义快捷键的回调操作 */ - callback: function (h5Player, taskConf, data) { - const { event, player } = data; - console.log(event, player); - } - }, - /* 当前域名下需包含的路径信息,默认整个域名下所有路径可用 必须是正则 */ - include: /^.*/, - /* 当前域名下需排除的路径信息,默认不排除任何路径 必须是正则 */ - exclude: /\t/ - } - }; - - // 通过doTaskFunc回调定义配置该如何执行任务 - this.doTaskFunc = doTaskFunc instanceof Function ? doTaskFunc : function () {}; - } - - /** - * 获取域名 , 目前实现方式不好,需改造,对地区性域名(如com.cn)、三级及以上域名支持不好 - * */ - getDomain () { - const host = window.location.host; - let domain = host; - const tmpArr = host.split('.'); - if (tmpArr.length > 2) { - tmpArr.shift(); - domain = tmpArr.join('.'); - } - return domain - } - - /** - * 格式化配置任务 - * @param isAll { boolean } -可选 默认只格式当前域名或host下的配置任务,传入true则将所有域名下的任务配置都进行格式化 - */ - formatTCC (isAll) { - const t = this; - const keys = Object.keys(t.conf); - const domain = t.getDomain(); - const host = window.location.host; - - function formatter (item) { - const defObj = { - include: /^.*/, - exclude: /\t/ - }; - item.include = item.include || defObj.include; - item.exclude = item.exclude || defObj.exclude; - return item - } - - const result = {}; - keys.forEach(function (key) { - let item = t[key]; - if (isObj(item)) { - if (isAll) { - item = formatter(item); - result[key] = item; - } else { - if (key === host || key === domain) { - item = formatter(item); - result[key] = item; - } - } - } - }); - return result - } - - /* 判断所提供的配置任务是否适用于当前URL */ - isMatch (taskConf) { - const url = window.location.href; - let isMatch = false; - if (!taskConf.include && !taskConf.exclude) { - isMatch = true; - } else { - if (taskConf.include && taskConf.include.test(url)) { - isMatch = true; - } - if (taskConf.exclude && taskConf.exclude.test(url)) { - isMatch = false; - } - } - return isMatch - } - - /** - * 获取任务配置,只能获取到当前域名下的任务配置信息 - * @param taskName {string} -可选 指定具体任务,默认返回所有类型的任务配置 - */ - getTaskConfig () { - const t = this; - if (!t._hasFormatTCC_) { - t.formatTCC(); - t._hasFormatTCC_ = true; - } - const domain = t.getDomain(); - const taskConf = t.conf[window.location.host] || t.conf[domain]; - - if (taskConf && t.isMatch(taskConf)) { - return taskConf - } - - return {} - } - - /** - * 执行当前页面下的相应任务 - * @param taskName {object|string} -必选,可直接传入任务配置对象,也可用是任务名称的字符串信息,自己去查找是否有任务需要执行 - * @param data {object} -可选,传给回调函数的数据 - */ - doTask (taskName, data) { - const t = this; - let isDo = false; - if (!taskName) return isDo - const taskConf = isObj(taskName) ? taskName : t.getTaskConfig(); - - if (!isObj(taskConf) || !taskConf[taskName]) return isDo - - const task = taskConf[taskName]; - - if (task) { - isDo = t.doTaskFunc(taskName, taskConf, data); - } - - return isDo - } -} - -/** - * 元素监听器 - * @param selector -必选 - * @param fn -必选,元素存在时的回调 - * @param shadowRoot -可选 指定监听某个shadowRoot下面的DOM元素 - * 参考:https://javascript.ruanyifeng.com/dom/mutationobserver.html - */ -function ready (selector, fn, shadowRoot) { - const listeners = []; - const win = window; - const doc = shadowRoot || win.document; - const MutationObserver = win.MutationObserver || win.WebKitMutationObserver; - let observer; - - function $ready (selector, fn) { - // 储存选择器和回调函数 - listeners.push({ - selector: selector, - fn: fn - }); - if (!observer) { - // 监听document变化 - observer = new MutationObserver(check); - observer.observe(shadowRoot || doc.documentElement, { - childList: true, - subtree: true - }); - } - // 检查该节点是否已经在DOM中 - check(); - } - - function check () { - for (let i = 0; i < listeners.length; i++) { - var listener = listeners[i]; - var elements = doc.querySelectorAll(listener.selector); - for (let j = 0; j < elements.length; j++) { - var element = elements[j]; - if (!element._isMutationReady_) { - element._isMutationReady_ = true; - listener.fn.call(element, element); - } - } - } - } - - $ready(selector, fn); -} - -/** - * 某些网页用了attachShadow closed mode,需要open才能获取video标签,例如百度云盘 - * 解决参考: - * https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=zh-cn#closed - * https://stackoverflow.com/questions/54954383/override-element-prototype-attachshadow-using-chrome-extension - */ -function hackAttachShadow () { - if (window._hasHackAttachShadow_) return - try { - window._shadowDomList_ = []; - window.Element.prototype._attachShadow = window.Element.prototype.attachShadow; - window.Element.prototype.attachShadow = function () { - const arg = arguments; - if (arg[0] && arg[0].mode) { - // 强制使用 open mode - arg[0].mode = 'open'; - } - const shadowRoot = this._attachShadow.apply(this, arg); - // 存一份shadowDomList - window._shadowDomList_.push(shadowRoot); - - // 在document下面添加 addShadowRoot 自定义事件 - const shadowEvent = new window.CustomEvent('addShadowRoot', { - shadowRoot, - detail: { - shadowRoot, - message: 'addShadowRoot', - time: new Date() - }, - bubbles: true, - cancelable: true - }); - document.dispatchEvent(shadowEvent); - - return shadowRoot - }; - window._hasHackAttachShadow_ = true; - } catch (e) { - console.error('hackAttachShadow error by h5player plug-in'); - } -} - -/** - * 事件侦听hack - * @param config.debug {Boolean} -可选 开启调试模式,调试模式下会把所有注册的事件都挂载到 window._listenerList_ 对象下,用于调试分析 - * @param config.proxyNodeType {String|Array} -可选 对某些类型的dom标签的事件进行代理处理 - * 请不要对一些非常常见的标签进行事件代理,过多的代理会造成严重的性能消耗 - */ -function hackEventListener (config) { - config = config || { - debug: false, - proxyAll: false, - proxyNodeType: [] - }; - - /* 对proxyNodeType数据进行预处理,将里面的字符变成大写 */ - let proxyNodeType = Array.isArray(config.proxyNodeType) ? config.proxyNodeType : [config.proxyNodeType]; - const tmpArr = []; - proxyNodeType.forEach(type => { - if (typeof type === 'string') { - tmpArr.push(type.toUpperCase()); - } - }); - proxyNodeType = tmpArr; - - const EVENT = window.EventTarget.prototype; - if (EVENT._addEventListener) return - EVENT._addEventListener = EVENT.addEventListener; - EVENT._removeEventListener = EVENT.removeEventListener; - // 挂载到全局用于调试 - window._listenerList_ = window._listenerList_ || {}; - - // hack addEventListener - EVENT.addEventListener = function () { - const t = this; - const arg = arguments; - const type = arg[0]; - const listener = arg[1]; - - if (!listener) { - return false - } - - /* 把sourceopen事件干掉,则好多网站视频都将播放不了 */ - // if (/sourceopen/gi.test(type)) { - // console.log('------------------------------') - // console.log(type, listener) - // return false - // } - - /** - * 使用了Symbol之后,某些页面下会和 raven-js发生冲突,所以必须进行 try catch - * TODO 如何解决该问题待研究,测试页面:https://xueqiu.com/S/SZ300498 - */ - try { - /** - * 对监听函数进行代理 - * 为了降低对性能的影响,此处只对特定的标签的事件进行代理 - */ - const listenerSymbol = Symbol.for(listener); - let listenerProxy = null; - if (config.proxyAll || proxyNodeType.includes(t.nodeName)) { - try { - listenerProxy = new Proxy(listener, { - apply (target, ctx, args) { - // const event = args[0] - // console.log(event.type, event, target) - - /* 让外部通过 _listenerProxyApplyHandler_ 控制事件的执行 */ - if (t._listenerProxyApplyHandler_ instanceof Function) { - const handlerResult = t._listenerProxyApplyHandler_(target, ctx, args, arg); - if (handlerResult !== undefined) { - return handlerResult - } - } - - return target.apply(ctx, args) - } - }); - - /* 挂载listenerProxy到自身,方便快速查找 */ - listener[listenerSymbol] = listenerProxy; - - /* 使用listenerProxy替代本来应该进行侦听的listener */ - arg[1] = listenerProxy; - } catch (e) { - // console.error('listenerProxy error:', e) - } - } - t._addEventListener.apply(t, arg); - t._listeners = t._listeners || {}; - t._listeners[type] = t._listeners[type] || []; - const listenerObj = { - target: t, - type, - listener, - listenerProxy, - options: arg[2], - addTime: new Date().getTime() - }; - t._listeners[type].push(listenerObj); - - /* 挂载到全局对象用于观测调试 */ - if (config.debug) { - window._listenerList_[type] = window._listenerList_[type] || []; - window._listenerList_[type].push(listenerObj); - } - } catch (e) { - t._addEventListener.apply(t, arg); - // console.error(e) - } - }; - - // hack removeEventListener - EVENT.removeEventListener = function () { - const arg = arguments; - const type = arg[0]; - const listener = arg[1]; - - if (!listener) { - return false - } - - try { - /* 对arg[1]重新赋值,以便正确卸载对应的监听函数 */ - const listenerSymbol = Symbol.for(listener); - arg[1] = listener[listenerSymbol] || listener; - - this._removeEventListener.apply(this, arg); - this._listeners = this._listeners || {}; - this._listeners[type] = this._listeners[type] || []; - - const result = []; - this._listeners[type].forEach(listenerObj => { - if (listenerObj.listener !== listener) { - result.push(listenerObj); - } - }); - this._listeners[type] = result; - - /* 从全局列表中移除 */ - if (config.debug) { - const result = []; - const listenerTypeList = window._listenerList_[type] || []; - listenerTypeList.forEach(listenerObj => { - if (listenerObj.listener !== listener) { - result.push(listenerObj); - } - }); - window._listenerList_[type] = result; - } - } catch (e) { - this._removeEventListener.apply(this, arg); - console.error(e); - } - }; - - /* 对document下的事件侦听方法进行hack */ - try { - if (document.addEventListener !== EVENT.addEventListener) { - document.addEventListener = EVENT.addEventListener; - } - if (document.removeEventListener !== EVENT.removeEventListener) { - document.removeEventListener = EVENT.removeEventListener; - } - - // if (window.addEventListener !== EVENT.addEventListener) { - // window.addEventListener = EVENT.addEventListener - // } - // if (window.removeEventListener !== EVENT.removeEventListener) { - // window.removeEventListener = EVENT.removeEventListener - // } - } catch (e) { - console.error(e); - } -} - -const quickSort = function (arr) { - if (arr.length <= 1) { return arr } - var pivotIndex = Math.floor(arr.length / 2); - var pivot = arr.splice(pivotIndex, 1)[0]; - var left = []; - var right = []; - for (var i = 0; i < arr.length; i++) { - if (arr[i] < pivot) { - left.push(arr[i]); - } else { - right.push(arr[i]); - } - } - return quickSort(left).concat([pivot], quickSort(right)) -}; - -function hideDom (selector, delay) { - setTimeout(function () { - const dom = document.querySelector(selector); - if (dom) { - dom.style.opacity = 0; - } - }, delay || 1000 * 5); -} - -/** - * 向上查找操作 - * @param dom {Element} -必选 初始dom元素 - * @param fn {function} -必选 每一级ParentNode的回调操作 - * 如果函数返回true则表示停止向上查找动作 - */ -function eachParentNode (dom, fn) { - let parent = dom.parentNode; - while (parent) { - const isEnd = fn(parent, dom); - parent = parent.parentNode; - if (isEnd) { - break - } - } -} - -/** - * 判断当前元素是否为可编辑元素 - * @param target - * @returns Boolean - */ -function isEditableTarget (target) { - const isEditable = target.getAttribute && target.getAttribute('contenteditable') === 'true'; - const isInputDom = /INPUT|TEXTAREA|SELECT/.test(target.nodeName); - return isEditable || isInputDom -} - -/* ua信息伪装 */ -function fakeUA (ua) { - Object.defineProperty(navigator, 'userAgent', { - value: ua, - writable: false, - configurable: false, - enumerable: true - }); -} - -/* ua信息来源:https://developers.whatismybrowser.com */ -const userAgentMap = { - android: { - chrome: 'Mozilla/5.0 (Linux; Android 9; SM-G960F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.157 Mobile Safari/537.36', - firefox: 'Mozilla/5.0 (Android 7.0; Mobile; rv:57.0) Gecko/57.0 Firefox/57.0' - }, - iPhone: { - safari: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Mobile/15E148 Safari/604.1', - chrome: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/74.0.3729.121 Mobile/15E148 Safari/605.1' - }, - iPad: { - safari: 'Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Mobile/15E148 Safari/604.1', - chrome: 'Mozilla/5.0 (iPad; CPU OS 12_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/74.0.3729.155 Mobile/15E148 Safari/605.1' - } -}; - -/** - * 判断是否处于Iframe中 - * @returns {boolean} - */ -function isInIframe () { - return window !== window.top -} - -/** - * 判断是否处于跨域限制的Iframe中 - * @returns {boolean} - */ -function isInCrossOriginFrame () { - let result = true; - try { - if (window.top.localStorage || window.top.location.href) { - result = false; - } - } catch (e) { - result = true; - } - return result -} - -/** - * 简单的节流函数 - * @param fn - * @param interval - * @returns {Function} - */ -function throttle (fn, interval = 80) { - let timeout = null; - return function () { - if (timeout) return false - timeout = setTimeout(() => { - timeout = null; - }, interval); - fn.apply(this, arguments); - } -} - -/** - * 任务配置中心 Task Control Center - * 用于配置所有无法进行通用处理的任务,如不同网站的全屏方式不一样,必须调用网站本身的全屏逻辑,才能确保字幕、弹幕等正常工作 - * */ - -const taskConf = { - /** - * 配置示例 - * 父级键名对应的是一级域名, - * 子级键名对应的相关功能名称,键值对应的该功能要触发的点击选择器或者要调用的相关函数 - * 所有子级的键值都支持使用选择器触发或函数调用 - * 配置了子级的则使用子级配置逻辑进行操作,否则使用默认逻辑 - * 注意:include,exclude这两个子级键名除外,这两个是用来进行url范围匹配的 - * */ - 'demo.demo': { - fullScreen: '.fullscreen-btn', - exitFullScreen: '.exit-fullscreen-btn', - webFullScreen: function () {}, - exitWebFullScreen: '.exit-fullscreen-btn', - autoPlay: '.player-start-btn', - pause: '.player-pause', - play: '.player-play', - switchPlayStatus: '.player-play', - playbackRate: function () {}, - currentTime: function () {}, - addCurrentTime: '.add-currenttime', - subtractCurrentTime: '.subtract-currenttime', - // 自定义快捷键的执行方式,如果是组合键,必须是 ctrl-->shift-->alt 这样的顺序,没有可以忽略,键名必须全小写 - shortcuts: { - /* 注册要执行自定义回调操作的快捷键 */ - register: [ - 'ctrl+shift+alt+c', - 'ctrl+shift+c', - 'ctrl+alt+c', - 'ctrl+c', - 'c' - ], - /* 自定义快捷键的回调操作 */ - callback: function (h5Player, taskConf, data) { - const { event, player } = data; - console.log(event, player); - } - }, - /* 当前域名下需包含的路径信息,默认整个域名下所有路径可用 必须是正则 */ - include: /^.*/, - /* 当前域名下需排除的路径信息,默认不排除任何路径 必须是正则 */ - exclude: /\t/ - }, - 'youtube.com': { - webFullScreen: 'button.ytp-size-button', - fullScreen: 'button.ytp-fullscreen-button', - next: '.ytp-next-button', - shortcuts: { - register: [ - 'escape' - ], - callback: function (h5Player, taskConf, data) { - const { event } = data; - if (event.keyCode === 27) { - /* 取消播放下一个推荐的视频 */ - if (document.querySelector('.ytp-upnext').style.display !== 'none') { - document.querySelector('.ytp-upnext-cancel-button').click(); - } - } - } - } - }, - 'netflix.com': { - fullScreen: 'button.button-nfplayerFullscreen', - addCurrentTime: 'button.button-nfplayerFastForward', - subtractCurrentTime: 'button.button-nfplayerBackTen' - }, - 'bilibili.com': { - // fullScreen: '[data-text="进入全屏"]', - // webFullScreen: '[data-text="网页全屏"]', - fullScreen: '.bilibili-player-video-btn-fullscreen', - webFullScreen: function () { - const webFullscreen = document.querySelector('.bilibili-player-video-web-fullscreen'); - if (webFullscreen) { - webFullscreen.click(); - - /* 取消弹幕框聚焦,干扰了快捷键的操作 */ - setTimeout(function () { - document.querySelector('.bilibili-player-video-danmaku-input').blur(); - }, 1000 * 0.1); - - return true - } - }, - // autoPlay: '.bilibili-player-video-btn-start', - switchPlayStatus: '.bilibili-player-video-btn-start', - next: '.bilibili-player-video-btn-next', - init: function (h5Player, taskConf) {}, - shortcuts: { - register: [ - 'escape' - ], - callback: function (h5Player, taskConf, data) { - const { event } = data; - if (event.keyCode === 27) { - /* 退出网页全屏 */ - const webFullscreen = document.querySelector('.bilibili-player-video-web-fullscreen'); - if (webFullscreen.classList.contains('closed')) { - webFullscreen.click(); - } - } - } - } - }, - 't.bilibili.com': { - fullScreen: 'button[name="fullscreen-button"]' - }, - 'live.bilibili.com': { - fullScreen: '.bilibili-live-player-video-controller-fullscreen-btn button', - webFullScreen: '.bilibili-live-player-video-controller-web-fullscreen-btn button', - switchPlayStatus: '.bilibili-live-player-video-controller-start-btn button' - }, - 'acfun.cn': { - fullScreen: '[data-bind-key="screenTip"]', - webFullScreen: '[data-bind-key="webTip"]', - switchPlayStatus: function (h5player) { - /* 无法抢得控制权,只好延迟判断要不要干预 */ - const player = h5player.player(); - const status = player.paused; - setTimeout(function () { - if (status === player.paused) { - if (player.paused) { - player.play(); - } else { - player.pause(); - } - } - }, 200); - } - }, - 'iqiyi.com': { - fullScreen: '.iqp-btn-fullscreen', - webFullScreen: '.iqp-btn-webscreen', - next: '.iqp-btn-next', - init: function (h5Player, taskConf) { - // 隐藏水印 - hideDom('.iqp-logo-box'); - // 移除暂停广告 - window.GM_addStyle(` - div[templatetype="common_pause"]{ display:none } - .iqp-logo-box{ display:none !important } - `); - } - }, - 'youku.com': { - fullScreen: '.control-fullscreen-icon', - next: '.control-next-video', - init: function (h5Player, taskConf) { - // 隐藏水印 - hideDom('.youku-layer-logo'); - } - }, - 'ted.com': { - fullScreen: 'button.Fullscreen' - }, - 'qq.com': { - pause: '.container_inner .txp-shadow-mod', - play: '.container_inner .txp-shadow-mod', - shortcuts: { - register: ['c', 'x', 'z', '1', '2', '3', '4'], - callback: function (h5Player, taskConf, data) { - const { event } = data; - const key = event.key.toLowerCase(); - const speedItems = document.querySelectorAll('.container_inner txpdiv[data-role="txp-button-speed-list"] .txp_menuitem'); - - /* 利用sessionStorage下的playbackRate进行设置 */ - if (window.sessionStorage.playbackRate && /(c|x|z|1|2|3|4)/.test(key)) { - const curSpeed = Number(window.sessionStorage.playbackRate); - const perSpeed = curSpeed - 0.1 >= 0 ? curSpeed - 0.1 : 0.1; - const nextSpeed = curSpeed + 0.1 <= 4 ? curSpeed + 0.1 : 4; - let targetSpeed = curSpeed; - switch (key) { - case 'z' : - targetSpeed = 1; - break - case 'c' : - targetSpeed = nextSpeed; - break - case 'x' : - targetSpeed = perSpeed; - break - default : - targetSpeed = Number(key); - break - } - - window.sessionStorage.playbackRate = targetSpeed; - h5Player.setCurrentTime(0.01, true); - h5Player.setPlaybackRate(targetSpeed, true); - return true - } - - /* 模拟点击触发 */ - if (speedItems.length >= 3 && /(c|x|z)/.test(key)) { - let curIndex = 1; - speedItems.forEach((item, index) => { - if (item.classList.contains('txp_current')) { - curIndex = index; - } - }); - const perIndex = curIndex - 1 >= 0 ? curIndex - 1 : 0; - const nextIndex = curIndex + 1 < speedItems.length ? curIndex + 1 : speedItems.length - 1; - - let target = speedItems[1]; - switch (key) { - case 'z' : - target = speedItems[1]; - break - case 'c' : - target = speedItems[nextIndex]; - break - case 'x' : - target = speedItems[perIndex]; - break - } - - target.click(); - const speedNum = Number(target.innerHTML.replace('x')); - h5Player.setPlaybackRate(speedNum); - return true - } - } - }, - fullScreen: 'txpdiv[data-report="window-fullscreen"]', - webFullScreen: 'txpdiv[data-report="browser-fullscreen"]', - next: 'txpdiv[data-report="play-next"]', - init: function (h5Player, taskConf) { - // 隐藏水印 - hideDom('.txp-watermark'); - hideDom('.txp-watermark-action'); - }, - include: /(v.qq|sports.qq)/ - }, - 'pan.baidu.com': { - fullScreen: function (h5Player, taskConf) { - h5Player.player().parentNode.querySelector('.vjs-fullscreen-control').click(); - } - }, - 'facebook.com': { - fullScreen: function (h5Player, taskConf) { - const actionBtn = h5Player.player().parentNode.querySelectorAll('button'); - if (actionBtn && actionBtn.length > 3) { - /* 模拟点击倒数第二个按钮 */ - actionBtn[actionBtn.length - 2].click(); - return true - } - }, - webFullScreen: function (h5Player, taskConf) { - const actionBtn = h5Player.player().parentNode.querySelectorAll('button'); - if (actionBtn && actionBtn.length > 3) { - /* 模拟点击倒数第二个按钮 */ - actionBtn[actionBtn.length - 2].click(); - return true - } - }, - shortcuts: { - /* 在视频模式下按esc键,自动返回上一层界面 */ - register: [ - 'escape' - ], - /* 自定义快捷键的回调操作 */ - callback: function (h5Player, taskConf, data) { - eachParentNode(h5Player.player(), function (parentNode) { - if (parentNode.getAttribute('data-fullscreen-container') === 'true') { - const goBackBtn = parentNode.parentNode.querySelector('div>a>i>u'); - if (goBackBtn) { - goBackBtn.parentNode.parentNode.click(); - } - return true - } - }); - } - } - }, - 'douyu.com': { - fullScreen: function (h5Player, taskConf) { - const player = h5Player.player(); - const container = player._fullScreen_.getContainer(); - if (player._isFullScreen_) { - container.querySelector('div[title="退出窗口全屏"]').click(); - } else { - container.querySelector('div[title="窗口全屏"]').click(); - } - player._isFullScreen_ = !player._isFullScreen_; - return true - }, - webFullScreen: function (h5Player, taskConf) { - const player = h5Player.player(); - const container = player._fullScreen_.getContainer(); - if (player._isWebFullScreen_) { - container.querySelector('div[title="退出网页全屏"]').click(); - } else { - container.querySelector('div[title="网页全屏"]').click(); - } - player._isWebFullScreen_ = !player._isWebFullScreen_; - return true - } - }, - 'open.163.com': { - init: function (h5Player, taskConf) { - const player = h5Player.player(); - /** - * 不设置CORS标识,这样才能跨域截图 - * https://developer.mozilla.org/zh-CN/docs/Web/HTML/CORS_enabled_image - * https://developer.mozilla.org/zh-CN/docs/Web/HTML/CORS_settings_attributes - */ - player.setAttribute('crossOrigin', 'anonymous'); - } - }, - 'agefans.tv': { - init: function (h5Player, taskConf) { - h5Player.player().setAttribute('crossOrigin', 'anonymous'); - } - }, - 'chaoxing.com': { - fullScreen: '.vjs-fullscreen-control' - }, - 'yixi.tv': { - init: function (h5Player, taskConf) { - h5Player.player().setAttribute('crossOrigin', 'anonymous'); - } - } -}; - -function h5PlayerTccInit (h5Player) { - return new TCC(taskConf, function (taskName, taskConf, data) { - const task = taskConf[taskName]; - const wrapDom = h5Player.getPlayerWrapDom(); - - if (taskName === 'shortcuts') { - if (isObj(task) && task.callback instanceof Function) { - return task.callback(h5Player, taskConf, data) - } - } else if (task instanceof Function) { - try { - return task(h5Player, taskConf, data) - } catch (e) { - console.error('TCC自定义函数任务执行失败:', h5Player, taskConf, data); - return false - } - } else { - /* 触发选择器上的点击事件 */ - if (wrapDom && wrapDom.querySelector(task)) { - // 在video的父元素里查找,是为了尽可能兼容多实例下的逻辑 - wrapDom.querySelector(task).click(); - return true - } else if (document.querySelector(task)) { - document.querySelector(task).click(); - return true - } - } - }) -} - -/* ua伪装配置 */ -const fakeConfig = { - // 'tv.cctv.com': userAgentMap.iPhone.chrome, - // 'v.qq.com': userAgentMap.iPad.chrome, - 'open.163.com': userAgentMap.iPhone.chrome, - 'm.open.163.com': userAgentMap.iPhone.chrome -}; - -/** - * 元素全屏API,同时兼容网页全屏 - */ - -class FullScreen { - constructor (dom, pageMode) { - this.dom = dom; - // 默认全屏模式,如果传入pageMode则表示进行的是页面全屏操作 - this.pageMode = pageMode || false; - const fullPageStyle = ` - ._webfullscreen_ { - display: block !important; - position: fixed !important; - width: 100% !important; - height: 100% !important; - top: 0 !important; - left: 0 !important; - background: #000 !important; - z-index: 999999 !important; - } - ._webfullscreen_zindex_ { - z-index: 999999 !important; - } - `; - if (!window._hasInitFullPageStyle_) { - window.GM_addStyle(fullPageStyle); - window._hasInitFullPageStyle_ = true; - } - - window.addEventListener('keyup', (event) => { - const key = event.key.toLowerCase(); - if (key === 'escape') { - if (this.isFull()) { - this.exit(); - } else if (this.isFullScreen()) { - this.exitFullScreen(); - } - } - }, true); - - this.getContainer(); - } - - eachParentNode (dom, fn) { - let parent = dom.parentNode; - while (parent && parent.classList) { - const isEnd = fn(parent, dom); - parent = parent.parentNode; - if (isEnd) { - break - } - } - } - - getContainer () { - const t = this; - if (t._container_) return t._container_ - - const d = t.dom; - const domBox = d.getBoundingClientRect(); - let container = d; - t.eachParentNode(d, function (parentNode) { - const noParentNode = !parentNode || !parentNode.getBoundingClientRect; - if (noParentNode || parentNode.getAttribute('data-fullscreen-container')) { - container = parentNode; - return true - } - - const parentBox = parentNode.getBoundingClientRect(); - const isInsideTheBox = parentBox.width <= domBox.width && parentBox.height <= domBox.height; - if (isInsideTheBox) { - container = parentNode; - } else { - return true - } - }); - - container.setAttribute('data-fullscreen-container', 'true'); - t._container_ = container; - return container - } - - isFull () { - return this.dom.classList.contains('_webfullscreen_') - } - - isFullScreen () { - const d = document; - return !!(d.fullscreen || d.webkitIsFullScreen || d.mozFullScreen || - d.fullscreenElement || d.webkitFullscreenElement || d.mozFullScreenElement) - } - - enterFullScreen () { - const c = this.getContainer(); - const enterFn = c.requestFullscreen || c.webkitRequestFullScreen || c.mozRequestFullScreen || c.msRequestFullScreen; - enterFn && enterFn.call(c); - } - - enter () { - const t = this; - if (t.isFull()) return - const container = t.getContainer(); - let needSetIndex = false; - if (t.dom === container) { - needSetIndex = true; - } - this.eachParentNode(t.dom, function (parentNode) { - parentNode.classList.add('_webfullscreen_'); - if (container === parentNode || needSetIndex) { - needSetIndex = true; - parentNode.classList.add('_webfullscreen_zindex_'); - } - }); - t.dom.classList.add('_webfullscreen_'); - const fullScreenMode = !t.pageMode; - if (fullScreenMode) { - t.enterFullScreen(); - } - } - - exitFullScreen () { - const d = document; - const exitFn = d.exitFullscreen || d.webkitExitFullscreen || d.mozCancelFullScreen || d.msExitFullscreen; - exitFn && exitFn.call(d); - } - - exit () { - const t = this; - t.dom.classList.remove('_webfullscreen_'); - this.eachParentNode(t.dom, function (parentNode) { - parentNode.classList.remove('_webfullscreen_'); - parentNode.classList.remove('_webfullscreen_zindex_'); - }); - const fullScreenMode = !t.pageMode; - if (fullScreenMode || t.isFullScreen()) { - t.exitFullScreen(); - } - } - - toggle () { - this.isFull() ? this.exit() : this.enter(); - } -} - -/*! - * @name videoCapturer.js - * @version 0.0.1 - * @author Blaze - * @date 2019/9/21 12:03 - * @github https://github.com/xxxily - */ - -var videoCapturer = { - /** - * 进行截图操作 - * @param video {dom} -必选 video dom 标签 - * @returns {boolean} - */ - capture (video, download, title) { - if (!video) return false - const t = this; - const currentTime = `${Math.floor(video.currentTime / 60)}'${(video.currentTime % 60).toFixed(3)}''`; - const captureTitle = title || `${document.title}_${currentTime}`; - - /* 截图核心逻辑 */ - video.setAttribute('crossorigin', 'anonymous'); - const canvas = document.createElement('canvas'); - canvas.width = video.videoWidth; - canvas.height = video.videoHeight; - const context = canvas.getContext('2d'); - context.drawImage(video, 0, 0, canvas.width, canvas.height); - - if (download) { - t.download(canvas, captureTitle, video); - } else { - t.previe(canvas, captureTitle); - } - - return canvas - }, - /** - * 预览截取到的画面内容 - * @param canvas - */ - previe (canvas, title) { - canvas.style = 'max-width:100%'; - const previewPage = window.open('', '_blank'); - previewPage.document.title = `capture previe - ${title || 'Untitled'}`; - previewPage.document.body.style.textAlign = 'center'; - previewPage.document.body.style.background = '#000'; - previewPage.document.body.appendChild(canvas); - }, - /** - * canvas 下载截取到的内容 - * @param canvas - */ - download (canvas, title, video) { - title = title || 'videoCapturer_' + Date.now(); - try { - canvas.toBlob(function (blob) { - const el = document.createElement('a'); - el.download = `${title}.jpg`; - el.href = URL.createObjectURL(blob); - el.click(); - }, 'image/jpeg', 0.99); - } catch (e) { - window.alert('视频源受CORS标识限制,无法下载截图,\n您可向作者反馈信息,以便完善网站兼容逻辑'); - console.log('video object:', video); - console.error('video crossorigin:', video.getAttribute('crossorigin')); - console.error(e); - } - } -}; - -/** - * 鼠标事件观测对象 - * 用于实现鼠标事件的穿透响应,有别于pointer-events:none - * pointer-events:none是设置当前层允许穿透 - * 而MouseObserver是:即使不知道target上面存在多少层遮挡,一样可以响应鼠标事件 - */ - -class MouseObserver { - constructor (observeOpt) { - // eslint-disable-next-line no-undef - this.observer = new IntersectionObserver((infoList) => { - infoList.forEach((info) => { - info.target.IntersectionObserverEntry = info; - }); - }, observeOpt || {}); - - this.observeList = []; - } - - _observe (target) { - let hasObserve = false; - for (let i = 0; i < this.observeList.length; i++) { - const el = this.observeList[i]; - if (target === el) { - hasObserve = true; - break - } - } - - if (!hasObserve) { - this.observer.observe(target); - this.observeList.push(target); - } - } - - _unobserve (target) { - this.observer.unobserve(target); - const newObserveList = []; - this.observeList.forEach((el) => { - if (el !== target) { - newObserveList.push(el); - } - }); - this.observeList = newObserveList; - } - - /** - * 增加事件绑定 - * @param target {element} -必选 要绑定事件的dom对象 - * @param type {string} -必选 要绑定的事件,只支持鼠标事件 - * @param listener {function} -必选 符合触发条件时的响应函数 - */ - on (target, type, listener, options) { - const t = this; - t._observe(target); - - if (!target.MouseObserverEvent) { - target.MouseObserverEvent = {}; - } - target.MouseObserverEvent[type] = true; - - if (!t._mouseObserver_) { - t._mouseObserver_ = {}; - } - - if (!t._mouseObserver_[type]) { - t._mouseObserver_[type] = []; - - window.addEventListener(type, (event) => { - t.observeList.forEach((target) => { - const isVisibility = target.IntersectionObserverEntry && target.IntersectionObserverEntry.intersectionRatio > 0; - const isReg = target.MouseObserverEvent[event.type] === true; - if (isVisibility && isReg) { - /* 判断是否符合触发侦听器事件条件 */ - const bound = target.getBoundingClientRect(); - const offsetX = event.x - bound.x; - const offsetY = event.y - bound.y; - const isNeedTap = offsetX <= bound.width && offsetX >= 0 && offsetY <= bound.height && offsetY >= 0; - - if (isNeedTap) { - /* 执行监听回调 */ - const listenerList = t._mouseObserver_[type]; - listenerList.forEach((listener) => { - if (listener instanceof Function) { - listener.call(t, event, { - x: offsetX, - y: offsetY - }, target); - } - }); - } - } - }); - }, options); - } - - /* 将监听回调加入到事件队列 */ - if (listener instanceof Function) { - t._mouseObserver_[type].push(listener); - } - } - - /** - * 解除事件绑定 - * @param target {element} -必选 要解除事件的dom对象 - * @param type {string} -必选 要解除的事件,只支持鼠标事件 - * @param listener {function} -必选 绑定事件时的响应函数 - * @returns {boolean} - */ - off (target, type, listener) { - const t = this; - if (!target || !type || !listener || !t._mouseObserver_ || !t._mouseObserver_[type] || !target.MouseObserverEvent || !target.MouseObserverEvent[type]) return false - - const newListenerList = []; - const listenerList = t._mouseObserver_[type]; - let isMatch = false; - listenerList.forEach((listenerItem) => { - if (listenerItem === listener) { - isMatch = true; - } else { - newListenerList.push(listenerItem); - } - }); - - if (isMatch) { - t._mouseObserver_[type] = newListenerList; - - /* 侦听器已被完全移除 */ - if (newListenerList.length === 0) { - delete target.MouseObserverEvent[type]; - } - - /* 当MouseObserverEvent为空对象时移除观测对象 */ - if (JSON.stringify(target.MouseObserverEvent[type]) === '{}') { - t._unobserve(target); - } - } - } -} - -/** - * 简单的i18n库 - */ - -class I18n { - constructor (config) { - this._languages = {}; - this._locale = this.getClientLang(); - this._defaultLanguage = ''; - this.init(config); - } - - init (config) { - if (!config) return false - - const t = this; - t._locale = config.locale || t._locale; - /* 指定当前要是使用的语言环境,默认无需指定,会自动读取 */ - t._languages = config.languages || t._languages; - t._defaultLanguage = config.defaultLanguage || t._defaultLanguage; - } - - use () {} - - t (path) { - const t = this; - let result = t.getValByPath(t._languages[t._locale] || {}, path); - - /* 版本回退 */ - if (!result && t._locale !== t._defaultLanguage) { - result = t.getValByPath(t._languages[t._defaultLanguage] || {}, path); - } - - return result || '' - } - - /* 当前语言值 */ - language () { - return this._locale - } - - languages () { - return this._languages - } - - changeLanguage (locale) { - if (this._languages[locale]) { - this._languages = locale; - return locale - } else { - return false - } - } - - /** - * 根据文本路径获取对象里面的值 - * @param obj {Object} -必选 要操作的对象 - * @param path {String} -必选 路径信息 - * @returns {*} - */ - getValByPath (obj, path) { - path = path || ''; - const pathArr = path.split('.'); - let result = obj; - - /* 递归提取结果值 */ - for (let i = 0; i < pathArr.length; i++) { - if (!result) break - result = result[pathArr[i]]; - } - - return result - } - - /* 获取客户端当前的语言环境 */ - getClientLang () { - return navigator.languages ? navigator.languages[0] : navigator.language - } -} - -/* 用于获取全局唯一的id */ -function getId () { - let gID = window.GM_getValue('_global_id_'); - if (!gID) gID = 0; - gID = Number(gID) + 1; - window.GM_setValue('_global_id_', gID); - return gID -} - -let curTabId = null; - -/** - * 获取当前TAB标签的Id号,可用于iframe确定自己是否处于同一TAB标签下 - * @returns {Promise} - */ -function getTabId () { - return new Promise((resolve, reject) => { - window.GM_getTab(function (obj) { - if (!obj.tabId) { - obj.tabId = getId(); - window.GM_saveTab(obj); - } - /* 每次获取都更新当前Tab的id号 */ - curTabId = obj.tabId; - resolve(obj.tabId); - }); - }) -} - -/* 一开始就初始化好curTabId,这样后续就不需要异步获取Tabid,部分场景下需要用到 */ -getTabId(); - -/*! - * @name menuCommand.js - * @version 0.0.1 - * @author Blaze - * @date 2019/9/21 14:22 - */ - -const monkeyMenu = { - on (title, fn, accessKey) { - return window.GM_registerMenuCommand && window.GM_registerMenuCommand(title, fn, accessKey) - }, - off (id) { - return window.GM_unregisterMenuCommand && window.GM_unregisterMenuCommand(id) - }, - /* 切换类型的菜单功能 */ - switch (title, fn, defVal) { - const t = this; - t.on(title, fn); - } -}; - -/*! - * @name monkeyMsg.js - * @version 0.0.1 - * @author Blaze - * @date 2019/9/21 14:22 - */ - -/** - * 将对象数据里面可存储到GM_setValue里面的值提取出来 - * @param obj {objcet} -必选 打算要存储的对象数据 - * @param deep {number} -可选 如果对象层级非常深,则须限定递归的层级,默认最高不能超过3级 - * @returns {{}} - */ -function extractDatafromOb (obj, deep) { - deep = deep || 1; - if (deep > 3) return {} - - const result = {}; - if (typeof obj === 'object') { - for (const key in obj) { - const val = obj[key]; - const valType = typeof val; - if (valType === 'number' || valType === 'string' || valType === 'boolean') { - Object.defineProperty(result, key, { - value: val, - writable: true, - configurable: true, - enumerable: true - }); - } else if (valType === 'object' && Object.prototype.propertyIsEnumerable.call(obj, key)) { - /* 进行递归提取 */ - result[key] = extractDatafromOb(val, deep + 1); - } else if (valType === 'array') { - result[key] = val; - } - } - } - return result -} - -const monkeyMsg = { - /** - * 发送消息,除了正常发送信息外,还会补充各类必要的信息 - * @param name {string} -必选 要发送给那个字段,接收时要一致才能监听的正确 - * @param data {Any} -必选 要发送的数据 - * @param throttleInterval -可选,因为会出现莫名奇妙的重复发送情况,为了消除重复发送带来的副作用,所以引入节流限制逻辑,即限制某个时间间隔内只能发送一次,多余的次数自动抛弃掉,默认80ms - * @returns {Promise} - */ - send (name, data, throttleInterval = 80) { - /* 阻止频繁发送修改事件 */ - const oldMsg = window.GM_getValue(name); - if (oldMsg && oldMsg.updateTime) { - const interval = Math.abs(Date.now() - oldMsg.updateTime); - if (interval < throttleInterval) { - return false - } - } - - const msg = { - /* 发送过来的数据 */ - data, - /* 补充标签ID,用于判断是否同处一个tab标签下 */ - tabId: curTabId || 'undefined', - /* 补充消息的页面来源的标题信息 */ - title: document.title, - /* 补充消息的页面来源信息 */ - referrer: extractDatafromOb(window.location), - /* 最近一次更新该数据的时间 */ - updateTime: Date.now() - }; - if (typeof data === 'object') { - msg.data = extractDatafromOb(data); - } - window.GM_setValue(name, msg); - }, - set: (name, data) => monkeyMsg.send(name, data), - get: (name) => window.GM_getValue(name), - on: (name, fn) => window.GM_addValueChangeListener(name, fn), - off: (listenerId) => window.GM_removeValueChangeListener(listenerId) -}; - -class Debug { - constructor (msg) { - const t = this; - msg = msg || 'debug message:'; - t.log = t.createDebugMethod('log', null, msg); - t.error = t.createDebugMethod('error', null, msg); - t.info = t.createDebugMethod('info', null, msg); - } - - create (msg) { - return new Debug(msg) - } - - createDebugMethod (name, color, tipsMsg) { - name = name || 'info'; - - const bgColorMap = { - info: '#2274A5', - log: '#95B46A', - error: '#D33F49' - }; - - return function () { - if (!window._debugMode_) { - return false - } - - const curTime = new Date(); - const H = curTime.getHours(); - const M = curTime.getMinutes(); - const S = curTime.getSeconds(); - const msg = tipsMsg || 'debug message:'; - - const arg = Array.from(arguments); - arg.unshift(`color: white; background-color: ${color || bgColorMap[name] || '#95B46A'}`); - arg.unshift(`%c [${H}:${M}:${S}] ${msg} `); - window.console[name].apply(window.console, arg); - } - } - - isDebugMode () { - return Boolean(window._debugMode_) - } -} - -var Debug$1 = new Debug(); - -var debug = Debug$1.create('h5player message:'); - -/* 当前用到的快捷键 */ -const hasUseKey = { - keyCodeList: [13, 16, 17, 18, 27, 32, 37, 38, 39, 40, 49, 50, 51, 52, 67, 68, 69, 70, 73, 74, 75, 78, 79, 80, 81, 82, 83, 84, 85, 87, 88, 89, 90, 97, 98, 99, 100, 220], - keyList: ['enter', 'shift', 'control', 'alt', 'escape', ' ', 'arrowleft', 'arrowright', 'arrowright', 'arrowup', 'arrowdown', '1', '2', '3', '4', 'c', 'd', 'e', 'f', 'i', 'j', 'k', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'w', 'x', 'y', 'z', '\\', '|'], - keyMap: { - enter: 13, - shift: 16, - ctrl: 17, - alt: 18, - esc: 27, - space: 32, - '←': 37, - '↑': 38, - '→': 39, - '↓': 40, - 1: 49, - 2: 50, - 3: 51, - 4: 52, - c: 67, - d: 68, - e: 69, - f: 70, - i: 73, - j: 74, - k: 75, - n: 78, - o: 79, - p: 80, - q: 81, - r: 82, - s: 83, - t: 84, - u: 85, - w: 87, - x: 88, - y: 89, - z: 90, - pad1: 97, - pad2: 98, - pad3: 99, - pad4: 100, - '\\': 220 - } -}; - -/** - * 判断当前按键是否注册为需要用的按键 - * 用于减少对其它键位的干扰 - */ -function isRegisterKey (event) { - const keyCode = event.keyCode; - const key = event.key.toLowerCase(); - return hasUseKey.keyCodeList.includes(keyCode) || - hasUseKey.keyList.includes(key) -} - -/** - * 由于tampermonkey对window对象进行了封装,我们实际访问到的window并非页面真实的window - * 这就导致了如果我们需要将某些对象挂载到页面的window进行调试的时候就无法挂载了 - * 所以必须使用特殊手段才能访问到页面真实的window对象,于是就有了下面这个函数 - * @returns {Promise} - */ -async function getPageWindow () { - return new Promise(function (resolve, reject) { - if (window._pageWindow) { - return resolve(window._pageWindow) - } - - const listenEventList = ['load', 'mousemove', 'scroll', 'get-page-window-event']; - - function getWin (event) { - window._pageWindow = this; - // debug.log('getPageWindow succeed', event) - listenEventList.forEach(eventType => { - window.removeEventListener(eventType, getWin, true); - }); - resolve(window._pageWindow); - } - - listenEventList.forEach(eventType => { - window.addEventListener(eventType, getWin, true); - }); - - /* 自行派发事件以便用最短的时候获得pageWindow对象 */ - window.dispatchEvent(new window.Event('get-page-window-event')); - }) -} -getPageWindow(); - -/*! - * @name crossTabCtl.js - * @description 跨Tab控制脚本逻辑 - * @version 0.0.1 - * @author Blaze - * @date 2019/11/21 上午11:56 - * @github https://github.com/xxxily - */ - -const crossTabCtl = { - /* 由于没有专门的监控方法,所以只能通过轮询来更新画中画信息 */ - updatePictureInPictureInfo () { - setInterval(function () { - if (document.pictureInPictureElement) { - monkeyMsg.send('globalPictureInPictureInfo', { - hasGlobalPictureInPictureElement: true - }); - } - }, 1000 * 1.5); - }, - /* 判断当前是否开启了画中画功能 */ - hasOpenPictureInPicture () { - const data = monkeyMsg.get('globalPictureInPictureInfo'); - /* 画中画的全局信息更新时间差在3s内,才认为当前开启了画中画模式 */ - return data && Math.abs(Date.now() - data.updateTime) < 1000 * 3 - }, - /** - * 判断是否需要发送跨Tab控制按键信息 - */ - isNeedSendCrossTabCtlEvent () { - const t = crossTabCtl; - if (t.hasOpenPictureInPicture()) { - /* 画中画开启后,判断不在同一个Tab才发送事件 */ - const data = monkeyMsg.get('globalPictureInPictureInfo'); - if (data.tabId !== curTabId) { - return true - } - } - }, - crossTabKeydownEvent (event) { - const t = crossTabCtl; - /* 处于可编辑元素中不执行任何快捷键 */ - if (isEditableTarget(event.target)) return - if (t.isNeedSendCrossTabCtlEvent() && isRegisterKey(event)) { - // 阻止事件冒泡和默认事件 - event.stopPropagation(); - event.preventDefault(); - - /* 广播按键消息,进行跨Tab控制 */ - monkeyMsg.send('globalKeydownEvent', event); - debug.log('已发送跨Tab按键控制信息:', event); - return true - } - }, - bindCrossTabEvent () { - const t = crossTabCtl; - if (t._hasBindEvent_) return - document.removeEventListener('keydown', t.crossTabKeydownEvent); - document.addEventListener('keydown', t.crossTabKeydownEvent, true); - t._hasBindEvent_ = true; - }, - init () { - const t = crossTabCtl; - t.updatePictureInPictureInfo(); - t.bindCrossTabEvent(); - } -}; - -var zhCN = { - about: '关于', - issues: '反馈', - setting: '设置', - tipsMsg: { - playspeed: '播放速度:', - forward: '前进:', - backward: '后退:', - seconds: '秒', - volume: '音量:', - nextframe: '定位:下一帧', - previousframe: '定位:上一帧', - stopframe: '定格帧画面:', - play: '播放', - pause: '暂停', - arpl: '允许自动恢复播放进度', - drpl: '禁止自动恢复播放进度', - brightness: '图像亮度:', - contrast: '图像对比度:', - saturation: '图像饱和度:', - hue: '图像色相:', - blur: '图像模糊度:', - imgattrreset: '图像属性:复位', - imgrotate: '画面旋转:', - onplugin: '启用h5Player插件', - offplugin: '禁用h5Player插件', - globalmode: '全局模式:', - playbackrestored: '为你恢复上次播放进度', - playbackrestoreoff: '恢复播放进度功能已禁用,按 SHIFT+R 可开启该功能', - horizontal: '水平位移:', - vertical: '垂直位移:', - videozoom: '视频缩放率:' - } -}; - -var enUS = { - about: 'about', - issues: 'issues', - setting: 'setting', - tipsMsg: { - playspeed: 'Speed: ', - forward: 'Forward: ', - backward: 'Backward: ', - seconds: 'sec', - volume: 'Volume: ', - nextframe: 'Next frame', - previousframe: 'Previous frame', - stopframe: 'Stopframe: ', - play: 'Play', - pause: 'Pause', - arpl: 'Allow auto resume playback progress', - drpl: 'Disable auto resume playback progress', - brightness: 'Brightness: ', - contrast: 'Contrast: ', - saturation: 'Saturation: ', - hue: 'HUE: ', - blur: 'Blur: ', - imgattrreset: 'Attributes: reset', - imgrotate: 'Picture rotation: ', - onplugin: 'ON h5Player plugin', - offplugin: 'OFF h5Player plugin', - globalmode: 'Global mode: ', - playbackrestored: 'Restored the last playback progress for you', - playbackrestoreoff: 'The function of restoring the playback progress is disabled. Press SHIFT+R to turn on the function', - horizontal: 'Horizontal displacement: ', - vertical: 'Vertical displacement: ', - videozoom: 'Video zoom: ' - }, - demo: 'demo-test' -}; - -var ru = { - about: 'около', - issues: 'обратная связь', - setting: 'установка', - tipsMsg: { - playspeed: 'Скорость: ', - forward: 'Вперёд: ', - backward: 'Назад: ', - seconds: ' сек', - volume: 'Громкость: ', - nextframe: 'Следующий кадр', - previousframe: 'Предыдущий кадр', - stopframe: 'Стоп-кадр: ', - play: 'Запуск', - pause: 'Пауза', - arpl: 'Разрешить автоматическое возобновление прогресса воспроизведения', - drpl: 'Запретить автоматическое возобновление прогресса воспроизведения', - brightness: 'Яркость: ', - contrast: 'Контраст: ', - saturation: 'Насыщенность: ', - hue: 'Оттенок: ', - blur: 'Размытие: ', - imgattrreset: 'Атрибуты: сброс', - imgrotate: 'Поворот изображения: ', - onplugin: 'ВКЛ: плагин воспроизведения', - offplugin: 'ВЫКЛ: плагин воспроизведения', - globalmode: 'Глобальный режим:', - playbackrestored: 'Восстановлен последний прогресс воспроизведения', - playbackrestoreoff: 'Функция восстановления прогресса воспроизведения отключена. Нажмите SHIFT + R, чтобы включить функцию', - horizontal: 'Горизонтальное смещение: ', - vertical: 'Вертикальное смещение: ', - videozoom: 'Увеличить видео: ' - } -}; - -var zhTW = { - about: '關於', - issues: '反饋', - setting: '設置', - tipsMsg: { - playspeed: '播放速度:', - forward: '向前:', - backward: '向後:', - seconds: '秒', - volume: '音量:', - nextframe: '定位:下一幀', - previousframe: '定位:上一幀', - stopframe: '定格幀畫面:', - play: '播放', - pause: '暫停', - arpl: '允許自動恢復播放進度', - drpl: '禁止自動恢復播放進度', - brightness: '圖像亮度:', - contrast: '圖像對比度:', - saturation: '圖像飽和度:', - hue: '圖像色相:', - blur: '圖像模糊度:', - imgattrreset: '圖像屬性:復位', - imgrotate: '畫面旋轉:', - onplugin: '啟用h5Player插件', - offplugin: '禁用h5Player插件', - globalmode: '全局模式:', - playbackrestored: '為你恢復上次播放進度', - playbackrestoreoff: '恢復播放進度功能已禁用,按 SHIFT+R 可開啟該功能', - horizontal: '水平位移:', - vertical: '垂直位移:', - videozoom: '視頻縮放率:' - } -}; - -const messages = { - 'zh-CN': zhCN, - zh: zhCN, - 'zh-HK': zhTW, - 'zh-TW': zhTW, - 'en-US': enUS, - en: enUS, - ru: ru -}; - -(async function () { - debug.log('h5Player init'); - - const i18n = new I18n({ - defaultLanguage: 'en', - /* 指定当前要是使用的语言环境,默认无需指定,会自动读取 */ - // locale: 'zh-TW', - languages: messages - }); - - const mouseObserver = new MouseObserver(); - - // monkeyMenu.on('i18n.t('setting')', function () { - // window.alert('功能开发中,敬请期待...') - // }) - monkeyMenu.on(i18n.t('about'), function () { - window.GM_openInTab('https://github.com/xxxily/h5player', { - active: true, - insert: true, - setParent: true - }); - }); - monkeyMenu.on(i18n.t('issues'), function () { - window.GM_openInTab('https://github.com/xxxily/h5player/issues', { - active: true, - insert: true, - setParent: true - }); - }); - - hackAttachShadow(); - hackEventListener({ - // proxyAll: true, - proxyNodeType: ['video'], - debug: debug.isDebugMode() - }); - - let TCC = null; - const h5Player = { - /* 提示文本的字号 */ - fontSize: 12, - enable: true, - globalMode: true, - playerInstance: null, - scale: 1, - translate: { - x: 0, - y: 0 - }, - playbackRate: 1, - lastPlaybackRate: 1, - /* 快进快退步长 */ - skipStep: 5, - /* 获取当前播放器的实例 */ - player: function () { - const t = this; - return t.playerInstance || t.getPlayerList()[0] - }, - /* 每个网页可能存在的多个video播放器 */ - getPlayerList: function () { - const list = []; - function findPlayer (context) { - context.querySelectorAll('video').forEach(function (player) { - list.push(player); - }); - } - findPlayer(document); - - // 被封装在 shadow dom 里面的video - if (window._shadowDomList_) { - window._shadowDomList_.forEach(function (shadowRoot) { - findPlayer(shadowRoot); - }); - } - - return list - }, - getPlayerWrapDom: function () { - const t = this; - const player = t.player(); - if (!player) return - - let wrapDom = null; - const playerBox = player.getBoundingClientRect(); - eachParentNode(player, function (parent) { - if (parent === document || !parent.getBoundingClientRect) return - const parentBox = parent.getBoundingClientRect(); - if (parentBox.width && parentBox.height) { - if (parentBox.width === playerBox.width && parentBox.height === playerBox.height) { - wrapDom = parent; - } - } - }); - return wrapDom - }, - - /* 挂载到页面上的window对象,用于调试 */ - async mountToGlobal () { - try { - const pageWindow = await getPageWindow(); - if (pageWindow) { - pageWindow._h5Player = h5Player || 'null'; - if (window.top !== window) { - pageWindow._h5PlayerInFrame = h5Player || 'null'; - } - pageWindow._window = window || ''; - debug.log('h5Player对象已成功挂载到全局'); - } - } catch (e) { - debug.error(e); - } - }, - - /** - * 初始化播放器实例 - * @param isSingle 是否为单实例video标签 - */ - initPlayerInstance (isSingle) { - const t = this; - if (!t.playerInstance) return - - const player = t.playerInstance; - t.initPlaybackRate(); - t.isFoucs(); - t.proxyPlayerInstance(player); - - // player.addEventListener('durationchange', () => { - // debug.log('当前视频长度:', player.duration) - // }) - // player.setAttribute('preload', 'auto') - - /* 增加通用全屏,网页全屏api */ - player._fullScreen_ = new FullScreen(player); - player._fullPageScreen_ = new FullScreen(player, true); - - /* 注册播放器的事件代理处理器 */ - player._listenerProxyApplyHandler_ = t.playerEventHandler; - - if (!player._hasCanplayEvent_) { - player.addEventListener('canplay', function (event) { - t.initAutoPlay(player); - }); - player._hasCanplayEvent_ = true; - } - - /* 播放的时候进行相关同步操作 */ - if (!player._hasPlayingInitEvent_) { - let setPlaybackRateOnPlayingCount = 0; - player.addEventListener('playing', function (event) { - if (setPlaybackRateOnPlayingCount === 0) { - /* 同步之前设定的播放速度 */ - t.setPlaybackRate(); - - if (isSingle === true) { - /* 恢复播放进度和进行进度记录 */ - t.setPlayProgress(player); - setTimeout(function () { - t.playProgressRecorder(player); - }, 1000 * 3); - } - } else { - t.setPlaybackRate(null, true); - } - setPlaybackRateOnPlayingCount += 1; - }); - player._hasPlayingInitEvent_ = true; - } - - /* 进行自定义初始化操作 */ - const taskConf = TCC.getTaskConfig(); - if (taskConf.init) { - TCC.doTask('init', player); - } - - /* 注册鼠标响应事件 */ - mouseObserver.on(player, 'click', function (event, offset, target) { - // debug.log('捕捉到鼠标点击事件:', event, offset, target) - }); - - debug.isDebugMode() && t.mountToGlobal(); - }, - - /** - * 对播放器实例的方法或属性进行代理 - * @param player - */ - proxyPlayerInstance (player) { - if (!player) return - - /* 要代理的方法或属性列表 */ - const proxyList = [ - 'play', - 'pause' - ]; - - proxyList.forEach(key => { - const originKey = 'origin_' + key; - if (Reflect.has(player, key) && !Reflect.has(player, originKey)) { - player[originKey] = player[key]; - const proxy = new Proxy(player[key], { - apply (target, ctx, args) { - debug.log(key + '被调用'); - - /* 处理挂起逻辑 */ - const hangUpInfo = player._hangUpInfo_ || {}; - const hangUpDetail = hangUpInfo[key] || hangUpInfo['hangUp_' + key]; - const needHangUp = hangUpDetail && hangUpDetail.timeout >= Date.now(); - if (needHangUp) { - debug.log(key + '已被挂起,本次调用将被忽略'); - return false - } - - return target.apply(ctx || player, args) - } - }); - - player[key] = proxy; - } - }); - - if (!player._hangUp_) { - player._hangUpInfo_ = {}; - /** - * 挂起player某个函数的调用 - * @param name {String} -必选 player方法或属性名,名字写对外,还须要该方法或属性被代理了才能进行挂起,否则这将是个无效的调用 - * @param timeout {Number} -可选 挂起多长时间,默认200ms - * @private - */ - player._hangUp_ = function (name, timeout) { - timeout = Number(timeout) || 200; - debug.log('_hangUp_', name, timeout); - player._hangUpInfo_[name] = { - timeout: Date.now() + timeout - }; - }; - } - }, - initPlaybackRate () { - const t = this; - t.playbackRate = t.getPlaybackRate(); - }, - getPlaybackRate () { - const t = this; - let playbackRate = t.playbackRate; - if (!isInCrossOriginFrame()) { - playbackRate = window.localStorage.getItem('_h5_player_playback_rate_') || t.playbackRate; - } - return Number(Number(playbackRate).toFixed(1)) - }, - /* 设置播放速度 */ - setPlaybackRate: function (num, notips) { - const taskConf = TCC.getTaskConfig(); - if (taskConf.playbackRate) { - TCC.doTask('playbackRate'); - return - } - - const t = this; - const player = t.player(); - let curPlaybackRate; - if (num) { - num = Number(num); - if (Number.isNaN(num)) { - debug.error('h5player: 播放速度转换出错'); - return false - } - - if (num <= 0) { - num = 0.1; - } else if (num > 16) { - num = 16; - } - - num = Number(num.toFixed(1)); - curPlaybackRate = num; - } else { - curPlaybackRate = t.getPlaybackRate(); - } - - /* 记录播放速度的信息 */ - !isInCrossOriginFrame() && window.localStorage.setItem('_h5_player_playback_rate_', curPlaybackRate); - - t.playbackRate = curPlaybackRate; - player.playbackRate = curPlaybackRate; - - /* 本身处于1倍播放速度的时候不再提示 */ - if (!num && curPlaybackRate === 1) return - !notips && t.tips(i18n.t('tipsMsg.playspeed') + player.playbackRate); - }, - /* 恢复播放速度,还原到1倍速度、或恢复到上次的倍速 */ - resetPlaybackRate: function (player) { - const t = this; - player = player || t.player(); - - const oldPlaybackRate = Number(player.playbackRate); - const playbackRate = oldPlaybackRate === 1 ? t.lastPlaybackRate : 1; - if (oldPlaybackRate !== 1) { - t.lastPlaybackRate = oldPlaybackRate; - } - - player.playbackRate = playbackRate; - t.setPlaybackRate(player.playbackRate); - }, - /** - * 初始化自动播放逻辑 - * 必须是配置了自动播放按钮选择器得的才会进行自动播放 - */ - initAutoPlay: function (p) { - const t = this; - const player = p || t.player(); - - // 在轮询重试的时候,如果实例变了,或处于隐藏页面中则不进行自动播放操作 - if (!player || (p && p !== t.player()) || document.hidden) return - - const taskConf = TCC.getTaskConfig(); - if (player && taskConf.autoPlay && player.paused) { - TCC.doTask('autoPlay'); - if (player.paused) { - // 轮询重试 - if (!player._initAutoPlayCount_) { - player._initAutoPlayCount_ = 1; - } - player._initAutoPlayCount_ += 1; - if (player._initAutoPlayCount_ >= 10) { - return false - } - setTimeout(function () { - t.initAutoPlay(player); - }, 200); - } - } - }, - setWebFullScreen: function () { - const t = this; - const player = t.player(); - const isDo = TCC.doTask('webFullScreen'); - if (!isDo && player && player._fullPageScreen_) { - player._fullPageScreen_.toggle(); - } - }, - /* 设置播放进度 */ - setCurrentTime: function (num, notips) { - if (!num) return - num = Number(num); - const _num = Math.abs(Number(num.toFixed(1))); - - const t = this; - const player = t.player(); - const taskConf = TCC.getTaskConfig(); - if (taskConf.currentTime) { - TCC.doTask('currentTime'); - return - } - - if (num > 0) { - if (taskConf.addCurrentTime) { - TCC.doTask('addCurrentTime'); - } else { - player.currentTime += _num; - !notips && t.tips(i18n.t('tipsMsg.forward') + _num + i18n.t('tipsMsg.seconds')); - } - } else { - if (taskConf.subtractCurrentTime) { - TCC.doTask('subtractCurrentTime'); - } else { - player.currentTime -= _num; - !notips && t.tips(i18n.t('tipsMsg.backward') + _num + i18n.t('tipsMsg.seconds')); - } - } - }, - /* 设置声音大小 */ - setVolume: function (num) { - if (!num) return - const t = this; - const player = t.player(); - - num = Number(num); - const _num = Math.abs(Number(num.toFixed(2))); - const curVol = player.volume; - let newVol = curVol; - - if (num > 0) { - newVol += _num; - if (newVol > 1) { - newVol = 1; - } - } else { - newVol -= _num; - if (newVol < 0) { - newVol = 0; - } - } - - player.volume = newVol; - - /* 条件音量的时候顺便把静音模式关闭 */ - player.muted = false; - - t.tips(i18n.t('tipsMsg.volume') + parseInt(player.volume * 100) + '%'); - }, - - /* 设置视频画面的缩放与位移 */ - setTransform (scale, translate) { - const t = this; - const player = t.player(); - scale = t.scale = typeof scale === 'undefined' ? t.scale : Number(scale).toFixed(1); - translate = t.translate = translate || t.translate; - player.style.transform = `scale(${scale}) translate(${translate.x}px, ${translate.y}px) rotate(${t.rotate}deg)`; - let tipsMsg = i18n.t('tipsMsg.videozoom') + `${scale * 100}%`; - if (translate.x) { - tipsMsg += ` ${i18n.t('tipsMsg.horizontal')}${t.translate.x}px`; - } - if (translate.y) { - tipsMsg += ` ${i18n.t('tipsMsg.vertical')}${t.translate.y}px`; - } - t.tips(tipsMsg); - }, - - /** - * 定格帧画面 - * @param perFps {Number} -可选 默认 1,即定格到下一帧,如果是-1则为定格到上一帧 - */ - freezeFrame (perFps) { - perFps = perFps || 1; - const t = this; - const player = t.player(); - - /* 跳帧 */ - player.currentTime += Number(perFps / t.fps); - - /* 定格画面 */ - if (!player.paused) player.pause(); - - /* 有些播放器发现画面所在位置变了会自动进行播放,所以此时需要对播放操作进行挂起 */ - player._hangUp_ && player._hangUp_('play', 400); - - if (perFps === 1) { - t.tips(i18n.t('tipsMsg.nextframe')); - } else if (perFps === -1) { - t.tips(i18n.t('tipsMsg.previousframe')); - } else { - t.tips(i18n.t('tipsMsg.stopframe') + perFps); - } - }, - - /* 播放下一个视频,默认是没有这个功能的,只有在TCC里配置了next字段才会有该功能 */ - setNextVideo () { - const isDo = TCC.doTask('next'); - if (!isDo) { - debug.log('当前网页不支持一键播放下个视频功能~'); - } - }, - - setFakeUA (ua) { - ua = ua || userAgentMap.iPhone.safari; - - /* 记录设定的ua信息 */ - !isInCrossOriginFrame() && window.localStorage.setItem('_h5_player_user_agent_', ua); - fakeUA(ua); - }, - - /* ua伪装切换开关 */ - switchFakeUA (ua) { - const customUA = isInCrossOriginFrame() ? null : window.localStorage.getItem('_h5_player_user_agent_'); - if (customUA) { - !isInCrossOriginFrame() && window.localStorage.removeItem('_h5_player_user_agent_'); - } else { - this.setFakeUA(ua); - } - - debug.log('ua', navigator.userAgent); - }, - - /* 切换播放状态 */ - switchPlayStatus () { - const t = this; - const player = t.player(); - const taskConf = TCC.getTaskConfig(); - if (taskConf.switchPlayStatus) { - TCC.doTask('switchPlayStatus'); - return - } - - if (player.paused) { - if (taskConf.play) { - TCC.doTask('play'); - } else { - player.play(); - t.tips(i18n.t('tipsMsg.play')); - } - } else { - if (taskConf.pause) { - TCC.doTask('pause'); - } else { - player.pause(); - t.tips(i18n.t('tipsMsg.pause')); - } - } - }, - - isAllowRestorePlayProgress: function () { - const keyName = '_allowRestorePlayProgress_' + window.location.host; - const allowRestorePlayProgressVal = window.GM_getValue(keyName); - return !allowRestorePlayProgressVal || allowRestorePlayProgressVal === 'true' - }, - /* 切换自动恢复播放进度的状态 */ - switchRestorePlayProgressStatus: function () { - const t = h5Player; - let isAllowRestorePlayProgress = t.isAllowRestorePlayProgress(); - /* 进行值反转 */ - isAllowRestorePlayProgress = !isAllowRestorePlayProgress; - const keyName = '_allowRestorePlayProgress_' + window.location.host; - window.GM_setValue(keyName, String(isAllowRestorePlayProgress)); - - /* 操作提示 */ - if (isAllowRestorePlayProgress) { - t.tips(i18n.t('tipsMsg.arpl')); - t.setPlayProgress(t.player()); - } else { - t.tips(i18n.t('tipsMsg.drpl')); - } - }, - tipsClassName: 'html_player_enhance_tips', - getTipsContainer: function () { - const t = h5Player; - const player = t.player(); - // 使用getContainer获取到的父节点弊端太多,暂时弃用 - // const _tispContainer_ = player._tispContainer_ || getContainer(player); - - let tispContainer = player._tispContainer_ || player.parentNode; - /* 如果父节点为无长宽的元素,则再往上查找一级 */ - const containerBox = tispContainer.getBoundingClientRect(); - if ((!containerBox.width || !containerBox.height) && tispContainer.parentNode) { - tispContainer = tispContainer.parentNode; - } - - if (!player._tispContainer_) { player._tispContainer_ = tispContainer; } - return tispContainer - }, - tips: function (str) { - const t = h5Player; - const player = t.player(); - if (!player) { - debug.log('h5Player Tips:', str); - return true - } - - const parentNode = t.getTipsContainer(); - - // 修复部分提示按钮位置异常问题 - const defStyle = parentNode.getAttribute('style') || ''; - let backupStyle = parentNode.getAttribute('style-backup') || ''; - if (!backupStyle) { - parentNode.setAttribute('style-backup', defStyle || 'style-backup:none'); - backupStyle = defStyle; - } - - const newStyleArr = backupStyle.split(';'); - - const oldPosition = parentNode.getAttribute('def-position') || window.getComputedStyle(parentNode).position; - if (parentNode.getAttribute('def-position') === null) { - parentNode.setAttribute('def-position', oldPosition || ''); - } - if (['static', 'inherit', 'initial', 'unset', ''].includes(oldPosition)) { - newStyleArr.push('position: relative'); - } - - const playerBox = player.getBoundingClientRect(); - const parentNodeBox = parentNode.getBoundingClientRect(); - /* 不存在高宽时,给包裹节点一个最小高宽,才能保证提示能正常显示 */ - if (!parentNodeBox.width || !parentNodeBox.height) { - newStyleArr.push('min-width:' + playerBox.width + 'px'); - newStyleArr.push('min-height:' + playerBox.height + 'px'); - } - - parentNode.setAttribute('style', newStyleArr.join(';')); - - const tipsSelector = '.' + t.tipsClassName; - let tipsDom = parentNode.querySelector(tipsSelector); - - /* 提示dom未初始化的,则进行初始化 */ - if (!tipsDom) { - t.initTips(); - tipsDom = parentNode.querySelector(tipsSelector); - if (!tipsDom) { - debug.log('init h5player tips dom error...'); - return false - } - } - - const style = tipsDom.style; - tipsDom.innerText = str; - - for (var i = 0; i < 3; i++) { - if (this.on_off[i]) clearTimeout(this.on_off[i]); - } - - function showTips () { - style.display = 'block'; - t.on_off[0] = setTimeout(function () { - style.opacity = 1; - }, 50); - t.on_off[1] = setTimeout(function () { - // 隐藏提示框和还原样式 - style.opacity = 0; - style.display = 'none'; - if (backupStyle && backupStyle !== 'style-backup:none') { - parentNode.setAttribute('style', backupStyle); - } - }, 2000); - } - - if (style.display === 'block') { - style.display = 'none'; - clearTimeout(this.on_off[3]); - t.on_off[2] = setTimeout(function () { - showTips(); - }, 100); - } else { - showTips(); - } - }, - /* 设置提示DOM的样式 */ - initTips: function () { - const t = h5Player; - const parentNode = t.getTipsContainer(); - if (parentNode.querySelector('.' + t.tipsClassName)) return - - // top: 50%; - // left: 50%; - // transform: translate(-50%,-50%); - const tipsStyle = ` - position: absolute; - z-index: 999999; - font-size: ${t.fontSize || 16}px; - padding: 5px 10px; - background: rgba(0,0,0,0.4); - color:white; - top: 0; - left: 0; - transition: all 500ms ease; - opacity: 0; - border-bottom-right-radius: 5px; - display: none; - -webkit-font-smoothing: subpixel-antialiased; - font-family: 'microsoft yahei', Verdana, Geneva, sans-serif; - -webkit-user-select: none; - `; - const tips = document.createElement('div'); - tips.setAttribute('style', tipsStyle); - tips.setAttribute('class', t.tipsClassName); - parentNode.appendChild(tips); - }, - on_off: new Array(3), - rotate: 0, - fps: 30, - /* 滤镜效果 */ - filter: { - key: [1, 1, 1, 0, 0], - setup: function () { - var view = 'brightness({0}) contrast({1}) saturate({2}) hue-rotate({3}deg) blur({4}px)'; - for (var i = 0; i < 5; i++) { - view = view.replace('{' + i + '}', String(this.key[i])); - this.key[i] = Number(this.key[i]); - } - h5Player.player().style.filter = view; - }, - reset: function () { - this.key[0] = 1; - this.key[1] = 1; - this.key[2] = 1; - this.key[3] = 0; - this.key[4] = 0; - this.setup(); - } - }, - _isFoucs: false, - - /* 播放器的聚焦事件 */ - isFoucs: function () { - const t = h5Player; - const player = t.player(); - if (!player) return - - player.onmouseenter = function (e) { - h5Player._isFoucs = true; - }; - player.onmouseleave = function (e) { - h5Player._isFoucs = false; - }; - }, - /* 播放器事件响应器 */ - palyerTrigger: function (player, event) { - if (!player || !event) return - const t = h5Player; - const keyCode = event.keyCode; - const key = event.key.toLowerCase(); - - if (event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) { - // 网页全屏 - if (key === 'enter') { - t.setWebFullScreen(); - } - - // 进入或退出画中画模式 - if (key === 'p') { - if (window._isPictureInPicture_) { - document.exitPictureInPicture().then(() => { - window._isPictureInPicture_ = null; - }).catch(() => { - window._isPictureInPicture_ = null; - }); - } else { - player.requestPictureInPicture && player.requestPictureInPicture().then(() => { - window._isPictureInPicture_ = true; - }).catch(() => { - window._isPictureInPicture_ = null; - }); - } - } - - // 截图并下载保存 - if (key === 's') { - videoCapturer.capture(player, true); - } - - if (key === 'r') { - t.switchRestorePlayProgressStatus(); - } - - // 视频画面缩放相关事件 - const allowKeys = ['x', 'c', 'z', 'arrowright', 'arrowleft', 'arrowup', 'arrowdown']; - if (!allowKeys.includes(key)) return - - t.scale = Number(t.scale); - switch (key) { - // shift+X:视频缩小 -0.1 - case 'x' : - t.scale -= 0.1; - break - // shift+C:视频放大 +0.1 - case 'c' : - t.scale += 0.1; - break - // shift+Z:视频恢复正常大小 - case 'z' : - t.scale = 1; - t.translate = { x: 0, y: 0 }; - break - case 'arrowright' : - t.translate.x += 10; - break - case 'arrowleft' : - t.translate.x -= 10; - break - case 'arrowup' : - t.translate.y -= 10; - break - case 'arrowdown' : - t.translate.y += 10; - break - } - t.setTransform(t.scale, t.translate); - - // 阻止事件冒泡 - event.stopPropagation(); - event.preventDefault(); - return true - } - - // ctrl+方向键右→:快进30秒 - if (event.ctrlKey && keyCode === 39) { - t.setCurrentTime(t.skipStep * 6); - } - // ctrl+方向键左←:后退30秒 - if (event.ctrlKey && keyCode === 37) { - t.setCurrentTime(-t.skipStep * 6); - } - - // ctrl+方向键上↑:音量升高 20% - if (event.ctrlKey && keyCode === 38) { - t.setVolume(0.2); - } - // 方向键下↓:音量降低 20% - if (event.ctrlKey && keyCode === 40) { - t.setVolume(-0.2); - } - - // 防止其它无关组合键冲突 - if (event.altKey || event.ctrlKey || event.shiftKey || event.metaKey) return - - // 方向键右→:快进5秒 - if (keyCode === 39) { - t.setCurrentTime(t.skipStep); - } - // 方向键左←:后退5秒 - if (keyCode === 37) { - t.setCurrentTime(-t.skipStep); - } - - // 方向键上↑:音量升高 10% - if (keyCode === 38) { - t.setVolume(0.1); - } - // 方向键下↓:音量降低 10% - if (keyCode === 40) { - t.setVolume(-0.1); - } - - // 空格键:暂停/播放 - if (keyCode === 32) { - t.switchPlayStatus(); - } - - // 按键X:减速播放 -0.1 - if (keyCode === 88) { - t.setPlaybackRate(player.playbackRate - 0.1); - } - // 按键C:加速播放 +0.1 - if (keyCode === 67) { - t.setPlaybackRate(player.playbackRate + 0.1); - } - // 按键Z:正常速度播放 - if (keyCode === 90) { - t.resetPlaybackRate(); - } - - // 按1-4设置播放速度 49-52;97-100 - if ((keyCode >= 49 && keyCode <= 52) || (keyCode >= 97 && keyCode <= 100)) { - t.setPlaybackRate(event.key); - } - - // 按键F:下一帧 - if (keyCode === 70) { - if (window.location.hostname === 'www.netflix.com') { - /* netflix 的F键是全屏的意思 */ - return - } - t.freezeFrame(1); - } - // 按键D:上一帧 - if (keyCode === 68) { - t.freezeFrame(-1); - } - - // 按键E:亮度增加% - if (keyCode === 69) { - t.filter.key[0] += 0.1; - t.filter.key[0] = t.filter.key[0].toFixed(2); - t.filter.setup(); - t.tips(i18n.t('tipsMsg.brightness') + parseInt(t.filter.key[0] * 100) + '%'); - } - // 按键W:亮度减少% - if (keyCode === 87) { - if (t.filter.key[0] > 0) { - t.filter.key[0] -= 0.1; - t.filter.key[0] = t.filter.key[0].toFixed(2); - t.filter.setup(); - } - t.tips(i18n.t('tipsMsg.brightness') + parseInt(t.filter.key[0] * 100) + '%'); - } - - // 按键T:对比度增加% - if (keyCode === 84) { - t.filter.key[1] += 0.1; - t.filter.key[1] = t.filter.key[1].toFixed(2); - t.filter.setup(); - t.tips(i18n.t('tipsMsg.contrast') + parseInt(t.filter.key[1] * 100) + '%'); - } - // 按键R:对比度减少% - if (keyCode === 82) { - if (t.filter.key[1] > 0) { - t.filter.key[1] -= 0.1; - t.filter.key[1] = t.filter.key[1].toFixed(2); - t.filter.setup(); - } - t.tips(i18n.t('tipsMsg.contrast') + parseInt(t.filter.key[1] * 100) + '%'); - } - - // 按键U:饱和度增加% - if (keyCode === 85) { - t.filter.key[2] += 0.1; - t.filter.key[2] = t.filter.key[2].toFixed(2); - t.filter.setup(); - t.tips(i18n.t('tipsMsg.saturation') + parseInt(t.filter.key[2] * 100) + '%'); - } - // 按键Y:饱和度减少% - if (keyCode === 89) { - if (t.filter.key[2] > 0) { - t.filter.key[2] -= 0.1; - t.filter.key[2] = t.filter.key[2].toFixed(2); - t.filter.setup(); - } - t.tips(i18n.t('tipsMsg.saturation') + parseInt(t.filter.key[2] * 100) + '%'); - } - - // 按键O:色相增加 1 度 - if (keyCode === 79) { - t.filter.key[3] += 1; - t.filter.setup(); - t.tips(i18n.t('tipsMsg.hue') + t.filter.key[3] + '度'); - } - // 按键I:色相减少 1 度 - if (keyCode === 73) { - t.filter.key[3] -= 1; - t.filter.setup(); - t.tips(i18n.t('tipsMsg.hue') + t.filter.key[3] + '度'); - } - - // 按键K:模糊增加 1 px - if (keyCode === 75) { - t.filter.key[4] += 1; - t.filter.setup(); - t.tips(i18n.t('tipsMsg.blur') + t.filter.key[4] + 'PX'); - } - // 按键J:模糊减少 1 px - if (keyCode === 74) { - if (t.filter.key[4] > 0) { - t.filter.key[4] -= 1; - t.filter.setup(); - } - t.tips(i18n.t('tipsMsg.blur') + t.filter.key[4] + 'PX'); - } - - // 按键Q:图像复位 - if (keyCode === 81) { - t.filter.reset(); - t.tips(i18n.t('tipsMsg.imgattrreset')); - } - - // 按键S:画面旋转 90 度 - if (keyCode === 83) { - t.rotate += 90; - if (t.rotate % 360 === 0) t.rotate = 0; - player.style.transform = `scale(${t.scale}) translate(${t.translate.x}px, ${t.translate.y}px) rotate( ${t.rotate}deg)`; - t.tips(i18n.t('tipsMsg.imgrotate') + t.rotate + '°'); - } - - // 按键回车,进入全屏 - if (keyCode === 13) { - const isDo = TCC.doTask('fullScreen'); - if (!isDo && player._fullScreen_) { - player._fullScreen_.toggle(); - } - } - - if (key === 'n') { - t.setNextVideo(); - } - - // 阻止事件冒泡 - event.stopPropagation(); - event.preventDefault(); - return true - }, - - /* 运行自定义的快捷键操作,如果运行了会返回true */ - runCustomShortcuts: function (player, event) { - if (!player || !event) return - const key = event.key.toLowerCase(); - const taskConf = TCC.getTaskConfig(); - const confIsCorrect = isObj(taskConf.shortcuts) && - Array.isArray(taskConf.shortcuts.register) && - taskConf.shortcuts.callback instanceof Function; - - /* 判断当前触发的快捷键是否已被注册 */ - function isRegister () { - const list = taskConf.shortcuts.register; - - /* 当前触发的组合键 */ - const combineKey = []; - if (event.ctrlKey) { - combineKey.push('ctrl'); - } - if (event.shiftKey) { - combineKey.push('shift'); - } - if (event.altKey) { - combineKey.push('alt'); - } - if (event.metaKey) { - combineKey.push('command'); - } - - combineKey.push(key); - - /* 通过循环判断当前触发的组合键和已注册的组合键是否完全一致 */ - let hasReg = false; - list.forEach((shortcut) => { - const regKey = shortcut.split('+'); - if (combineKey.length === regKey.length) { - let allMatch = true; - regKey.forEach((key) => { - if (!combineKey.includes(key)) { - allMatch = false; - } - }); - if (allMatch) { - hasReg = true; - } - } - }); - - return hasReg - } - - if (confIsCorrect && isRegister()) { - // 执行自定义快捷键操作 - const isDo = TCC.doTask('shortcuts', { - event, - player, - h5Player - }); - - if (isDo) { - event.stopPropagation(); - event.preventDefault(); - } - - return isDo - } else { - return false - } - }, - - /* 按键响应方法 */ - keydownEvent: function (event) { - const t = h5Player; - const keyCode = event.keyCode; - // const key = event.key.toLowerCase() - const player = t.player(); - - /* 处于可编辑元素中不执行任何快捷键 */ - if (isEditableTarget(event.target)) return - - /* shift+f 切换UA伪装 */ - if (event.shiftKey && keyCode === 70) { - t.switchFakeUA(); - } - - /* 未用到的按键不进行任何事件监听 */ - if (!isRegisterKey(event)) return - - /* 广播按键消息,进行跨域控制 */ - monkeyMsg.send('globalKeydownEvent', event); - - if (!player) { - // debug.log('无可用的播放,不执行相关操作') - return - } - - /* 切换插件的可用状态 */ - if (event.ctrlKey && keyCode === 32) { - t.enable = !t.enable; - if (t.enable) { - t.tips(i18n.t('tipsMsg.onplugin')); - } else { - t.tips(i18n.t('tipsMsg.offplugin')); - } - } - - if (!t.enable) { - debug.log('h5Player 已禁用~'); - return false - } - - // 按ctrl+\ 键进入聚焦或取消聚焦状态,用于视频标签被遮挡的场景 - if (event.ctrlKey && keyCode === 220) { - t.globalMode = !t.globalMode; - if (t.globalMode) { - t.tips(i18n.t('tipsMsg.globalmode') + ' ON'); - } else { - t.tips(i18n.t('tipsMsg.globalmode') + ' OFF'); - } - } - - /* 非全局模式下,不聚焦则不执行快捷键的操作 */ - if (!t.globalMode && !t._isFoucs) return - - /* 判断是否执行了自定义快捷键操作,如果是则不再响应后面默认定义操作 */ - if (t.runCustomShortcuts(player, event) === true) return - - /* 响应播放器相关操作 */ - t.palyerTrigger(player, event); - }, - - /** - * 获取播放进度 - * @param player -可选 对应的h5 播放器对象, 如果不传,则获取到的是整个播放进度表,传则获取当前播放器的播放进度 - */ - getPlayProgress: function (player) { - let progressMap = isInCrossOriginFrame() ? null : window.localStorage.getItem('_h5_player_play_progress_'); - if (!progressMap) { - progressMap = {}; - } else { - progressMap = JSON.parse(progressMap); - } - if (!player) { - return progressMap - } else { - let keyName = window.location.href || player.src; - keyName += player.duration; - if (progressMap[keyName]) { - return progressMap[keyName].progress - } else { - return player.currentTime - } - } - }, - /* 播放进度记录器 */ - playProgressRecorder: function (player) { - const t = h5Player; - clearTimeout(player._playProgressTimer_); - function recorder (player) { - player._playProgressTimer_ = setTimeout(function () { - if (!t.isAllowRestorePlayProgress()) { - recorder(player); - return true - } - - const progressMap = t.getPlayProgress() || {}; - const list = Object.keys(progressMap); - - let keyName = window.location.href || player.src; - keyName += player.duration; - - /* 只保存最近10个视频的播放进度 */ - if (list.length > 10) { - /* 根据更新的时间戳,取出最早添加播放进度的记录项 */ - let timeList = []; - list.forEach(function (keyName) { - progressMap[keyName] && progressMap[keyName].t && timeList.push(progressMap[keyName].t); - }); - timeList = quickSort(timeList); - const timestamp = timeList[0]; - - /* 删除最早添加的记录项 */ - list.forEach(function (keyName) { - if (progressMap[keyName].t === timestamp) { - delete progressMap[keyName]; - } - }); - } - - /* 记录当前播放进度 */ - progressMap[keyName] = { - progress: player.currentTime, - t: new Date().getTime() - }; - - /* 存储播放进度表 */ - !isInCrossOriginFrame() && window.localStorage.setItem('_h5_player_play_progress_', JSON.stringify(progressMap)); - - /* 循环侦听 */ - recorder(player); - }, 1000 * 2); - } - recorder(player); - }, - /* 设置播放进度 */ - setPlayProgress: function (player, time) { - const t = h5Player; - if (!player) return - - const curTime = Number(t.getPlayProgress(player)); - if (!curTime || Number.isNaN(curTime)) return - - if (t.isAllowRestorePlayProgress()) { - player.currentTime = curTime || player.currentTime; - if (curTime > 3) { - t.tips(i18n.t('tipsMsg.playbackrestored')); - } - } else { - t.tips(i18n.t('tipsMsg.playbackrestoreoff')); - } - }, - /** - * 检测h5播放器是否存在 - * @param callback - */ - detecH5Player: function () { - const t = this; - const playerList = t.getPlayerList(); - - if (playerList.length) { - debug.log('检测到HTML5视频!'); - - /* 单video实例标签的情况 */ - if (playerList.length === 1) { - t.playerInstance = playerList[0]; - t.initPlayerInstance(true); - } else { - /* 多video实例标签的情况 */ - playerList.forEach(function (player) { - /* 鼠标移到其上面的时候重新指定实例 */ - if (player._hasMouseRedirectEvent_) return - player.addEventListener('mouseenter', function (event) { - t.playerInstance = event.target; - t.initPlayerInstance(false); - }); - player._hasMouseRedirectEvent_ = true; - - /* 播放器开始播放的时候重新指向实例 */ - if (player._hasPlayingRedirectEvent_) return - player.addEventListener('playing', function (event) { - t.playerInstance = event.target; - t.initPlayerInstance(false); - - /* 同步之前设定的播放速度 */ - t.setPlaybackRate(); - }); - player._hasPlayingRedirectEvent_ = true; - }); - } - } - }, - /* 指定取消响应某些事件的列表 */ - _hangUpPlayerEventList_: [], - /** - * 挂起播放器的某些事件,注意:挂起时间过长容易出现较多副作用 - * @param eventType {String|Array} -必选 要挂起的事件类型,可以是单个事件也可以是多个事件 - * @param timeout {Number} -可选 调用挂起事件函数后,多久后失效,恢复正常事件响应,默认200ms - */ - hangUpPlayerEvent (eventType, timeout) { - const t = h5Player; - t._hangUpPlayerEventList_ = t._hangUpPlayerEventList_ || []; - eventType = Array.isArray(eventType) ? eventType : [eventType]; - timeout = timeout || 200; - - eventType.forEach(type => { - if (!t._hangUpPlayerEventList_.includes(type)) { - t._hangUpPlayerEventList_.push(type); - } - }); - - clearTimeout(t._hangUpPlayerEventTimer_); - t._hangUpPlayerEventTimer_ = setTimeout(function () { - const newList = []; - t._hangUpPlayerEventList_.forEach(cancelType => { - if (!eventType.includes(cancelType)) { - newList.push(cancelType); - } - }); - t._hangUpPlayerEventList_ = newList; - }, timeout); - }, - /** - * 播放器里的所有事件代理处理器 - * @param target - * @param ctx - * @param args - * @param listenerArgs - */ - playerEventHandler (target, ctx, args, listenerArgs) { - const t = h5Player; - const eventType = listenerArgs[0]; - - /* 取消对某些事件的响应 */ - if (t._hangUpPlayerEventList_.includes(eventType) || t._hangUpPlayerEventList_.includes('all')) { - debug.log(`播放器[${eventType}]事件被取消`); - return false - } - }, - /* 绑定相关事件 */ - bindEvent: function () { - const t = this; - if (t._hasBindEvent_) return - - document.removeEventListener('keydown', t.keydownEvent); - document.addEventListener('keydown', t.keydownEvent, true); - - /* 兼容iframe操作 */ - if (isInIframe() && !isInCrossOriginFrame()) { - window.top.document.removeEventListener('keydown', t.keydownEvent); - window.top.document.addEventListener('keydown', t.keydownEvent, true); - } - - /* 响应来自按键消息的广播 */ - monkeyMsg.on('globalKeydownEvent', async (name, oldVal, newVal, remote) => { - const tabId = await getTabId(); - const triggerFakeEvent = throttle(function () { - /* 模拟触发快捷键事件,实现跨域、跨Tab控制 */ - const player = t.player(); - if (player) { - const fakeEvent = newVal.data; - fakeEvent.stopPropagation = () => {}; - fakeEvent.preventDefault = () => {}; - t.palyerTrigger(player, fakeEvent); - debug.log('模拟触发操作成功'); - } - }, 80); - - if (remote) { - if (isInCrossOriginFrame()) { - /** - * 同处跨域受限页面,且都处于可见状态,大概率处于同一个Tab标签里,但不是100% - * tabId一致则100%为同一标签下 - */ - if (newVal.tabId === tabId && document.visibilityState === 'visible') { - triggerFakeEvent(); - } - } else if (crossTabCtl.hasOpenPictureInPicture() && document.pictureInPictureElement) { - /* 跨Tab控制画中画里面的视频播放 */ - if (tabId !== newVal.tabId) { - triggerFakeEvent(); - debug.log('已接收到跨Tab按键控制信息:', newVal); - } - } - } - }); - - t._hasBindEvent_ = true; - }, - init: function (global) { - var t = this; - if (global) { - /* 绑定键盘事件 */ - t.bindEvent(); - - /** - * 判断是否需要进行ua伪装 - * 下面方案暂时不可用 - * 由于部分网站跳转至移动端后域名不一致,形成跨域问题 - * 导致无法同步伪装配置而不断死循环跳转 - * eg. open.163.com - * */ - // let customUA = window.localStorage.getItem('_h5_player_user_agent_') - // debug.log(customUA, window.location.href, window.navigator.userAgent, document.referrer) - // if (customUA) { - // t.setFakeUA(customUA) - // alert(customUA) - // } else { - // alert('ua false') - // } - - /* 对配置了ua伪装的域名进行伪装 */ - const host = window.location.host; - if (fakeConfig[host]) { - t.setFakeUA(fakeConfig[host]); - } - } else { - /* 检测是否存在H5播放器 */ - t.detecH5Player(); - } - }, - load: false - }; - - /* 初始化任务配置中心 */ - TCC = h5PlayerTccInit(h5Player); - - try { - /* 初始化全局所需的相关方法 */ - h5Player.init(true); - - /* 检测到有视频标签就进行初始化 */ - ready('video', function () { - h5Player.init(); - }); - - /* 检测shadow dom 下面的video */ - document.addEventListener('addShadowRoot', function (e) { - const shadowRoot = e.detail.shadowRoot; - ready('video', function (element) { - h5Player.init(); - }, shadowRoot); - }); - - if (isInCrossOriginFrame()) { - debug.log('当前处于跨域受限的Iframe中,h5Player相关功能可能无法正常开启'); - } - - /* 初始化跨Tab控制逻辑 */ - crossTabCtl.init(); - } catch (e) { - debug.error(e); - } - - // h5playerUi.init() - - // debugCode.init(h5Player) - - // document.addEventListener('visibilitychange', function () { - // if (!document.hidden) { - // h5Player.initAutoPlay() - // } - // }) -})(); diff --git a/qutebrowser/quickmarks b/qutebrowser/quickmarks index 6f83d90..4645e15 100644 --- a/qutebrowser/quickmarks +++ b/qutebrowser/quickmarks @@ -1 +1,5 @@ yt https://yewtu.be/feed/subscriptions +sp https://tfcconnection.sharepoint.com/sites/TeensForChrist/Shared%20Documents/Forms/AllItems.aspx +ama https://smile.amazon.com/ +azu https://portal.azure.com/#@tfcconnection.org/dashboard/private/e5f3455d-3737-47d8-852b-1847a3b4801b +ac https://azure-costs.com/app/teams/personal/dashboards?active=e47b9bb7-1701-400e-9cbd-195c9cfda2c2&cycle=monthly diff --git a/qutebrowser/userscripts b/qutebrowser/userscripts new file mode 120000 index 0000000..9134726 --- /dev/null +++ b/qutebrowser/userscripts @@ -0,0 +1 @@ +/home/chris/.local/share/qutebrowser/userscripts \ No newline at end of file diff --git a/scripts/9538f34566af5ed38eba46446a1249a00126e899-9538f34566af5ed38eba46446a1249a00126e899.unknown_video.part b/scripts/9538f34566af5ed38eba46446a1249a00126e899-9538f34566af5ed38eba46446a1249a00126e899.unknown_video.part deleted file mode 100644 index 91c0823..0000000 Binary files a/scripts/9538f34566af5ed38eba46446a1249a00126e899-9538f34566af5ed38eba46446a1249a00126e899.unknown_video.part and /dev/null differ diff --git a/scripts/fehbg.sh b/scripts/fehbg.sh new file mode 100755 index 0000000..eba0373 --- /dev/null +++ b/scripts/fehbg.sh @@ -0,0 +1,2 @@ +#!/bin/sh +feh --no-fehbg --bg-fill '/home/chris/Pictures/wallpapers/RoyalKing.png' diff --git a/scripts/fileusage.sh b/scripts/fileusage.sh new file mode 100755 index 0000000..69a579e --- /dev/null +++ b/scripts/fileusage.sh @@ -0,0 +1,2 @@ +#!/usr/bin/sh +btrfs fi usage / | rg Free | awk '{print $3}'