HTML5 Video Player Enhance

To enhance the functionality of HTML5 Video Player (h5player) supporting all websites using shortcut keys similar to PotPlayer.

当前为 2021-06-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name HTML5 Video Player Enhance
  3. // @version 2.9.2a2
  4. // @description To enhance the functionality of HTML5 Video Player (h5player) supporting all websites using shortcut keys similar to PotPlayer.
  5. // @author CY Fung
  6. // @match http://*/*
  7. // @match https://*/*
  8. // @run-at document-start
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/js-sha256/0.9.0/sha256.min.js
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/mathjs/9.3.2/math.js
  11. // @namespace https://greasyfork.org/users/371179
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // ==/UserScript==
  15.  
  16.  
  17. /**
  18. * Remarks
  19. * This script support modern browser only with ES6+.
  20. * fullscreen and pointerLock buggy in shadowRoot
  21. * Space Pause not success
  22. * shift F key issue
  23. **/
  24. (function $$() {
  25. 'use strict';
  26.  
  27. if (!document || !document.documentElement) return window.requestAnimationFrame($$);
  28.  
  29. let _debug_h5p_logging_ = false;
  30.  
  31. try {
  32. _debug_h5p_logging_ = +window.localStorage.getItem('_h5_player_sLogging_') > 0
  33. } catch (e) {}
  34.  
  35.  
  36.  
  37. const SHIFT = 1;
  38. const CTRL = 2;
  39. const ALT = 4;
  40. const TERMINATE = 0x842;
  41. const _sVersion_ = 1817;
  42. const str_postMsgData = '__postMsgData__'
  43. const DOM_ACTIVE_FOUND = 1;
  44. const DOM_ACTIVE_SRC_LOADED = 2;
  45. const DOM_ACTIVE_ONCE_PLAYED = 4;
  46. const DOM_ACTIVE_MOUSE_CLICK = 8;
  47. const DOM_ACTIVE_MOUSE_IN = 16;
  48. const DOM_ACTIVE_DELAYED_PAUSED = 32;
  49. const DOM_ACTIVE_INVALID_PARENT = 2048;
  50.  
  51. let _endlessloop = null;
  52. const isIframe = (window.top !== window.self && window.top && window.self);
  53. const shadowRoots = [];
  54.  
  55. const getRoot = (elm) => elm.getRootNode instanceof Function ? elm.getRootNode() : (elm.ownerDocument || null);
  56.  
  57. const isShadowRoot = (elm) => (elm && ('host' in elm)) ? elm.nodeType == 11 && !!elm.host && elm.host.nodeType == 1 : null; //instanceof ShadowRoot
  58.  
  59. const playerConfs = {}
  60.  
  61. const hanlderResizeVideo=(entries) => {
  62. const detected_changes={};
  63. for (let entry of entries) {
  64. const player = entry.target.nodeName=="VIDEO"?entry.target:entry.target.querySelector("VIDEO[_h5ppid]");
  65. if(!player)continue;
  66. const vpid = player.getAttribute('_h5ppid');
  67. if(!vpid)continue;
  68. if(vpid in detected_changes) continue;
  69. detected_changes[vpid]=true;
  70. const wPlayer = $hs.getPlayerBlockElement(player)
  71. if(!wPlayer)continue;
  72. const layoutBox = wPlayer.parentNode
  73. if(!layoutBox)continue;
  74. const tipsDom=layoutBox.querySelector('[_potTips_]');
  75. if(!tipsDom)continue;
  76.  
  77. $hs.fixNonBoxingVideoTipsPosition(tipsDom, player);
  78. window.requestAnimationFrame(()=>$hs.fixNonBoxingVideoTipsPosition(tipsDom, player))
  79.  
  80. }
  81. };
  82.  
  83. const $mb={
  84.  
  85.  
  86. nightly_isSupportQueueMicrotask:function(){
  87.  
  88. if('_isSupportQueueMicrotask' in $mb)return $mb._isSupportQueueMicrotask;
  89.  
  90. $mb._isSupportQueueMicrotask= false;
  91. $mb.queueMicrotask=window.queueMicrotask;
  92. if(typeof $mb.queueMicrotask=='function'){
  93. $mb._isSupportQueueMicrotask= true;
  94. }
  95.  
  96. return $mb._isSupportQueueMicrotask;
  97.  
  98. },
  99.  
  100. stable_isSupportAdvancedEventListener:function(){
  101.  
  102. if ('_isSupportAdvancedEventListener' in $mb) return $mb._isSupportAdvancedEventListener
  103. let prop = 0;
  104. document.createAttribute('z').addEventListener('', null, {
  105. get passive() {
  106. prop++;
  107. },
  108. get once() {
  109. prop++;
  110. }
  111. });
  112. return ($mb._isSupportAdvancedEventListener = (prop == 2));
  113. }
  114.  
  115. }
  116.  
  117. const $ws = {
  118. requestAnimationFrame,
  119. cancelAnimationFrame,
  120. MutationObserver
  121. }
  122. //throw Error if your browser is too outdated. (eg ES6 script, no such window object)
  123.  
  124. Element.prototype.__matches__ = (Element.prototype.matches || Element.prototype.matchesSelector ||
  125. Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector ||
  126. Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector ||
  127. Element.prototype.matches()); // throw Error if not supported
  128.  
  129.  
  130. Element.prototype.__requestPointerLock__ = (Element.prototype.requestPointerLock ||
  131. Element.prototype.mozRequestPointerLock || Element.prototype.webkitRequestPointerLock || function() {});
  132.  
  133. // Ask the browser to release the pointer
  134. Document.prototype.__exitPointerLock__ = (Document.prototype.exitPointerLock ||
  135. Document.prototype.mozExitPointerLock || Document.prototype.webkitExitPointerLock || function() {});
  136.  
  137. // built-in hash - https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
  138. async function digestMessage(message) {
  139. return window.sha256(message)
  140. }
  141.  
  142. const dround = (x) => ~~(x + .5);
  143.  
  144. const jsonStringify_replacer = function(key, val) {
  145. if (val && (val instanceof Element || val instanceof Document)) return val.toString();
  146. return val; // return as is
  147. };
  148.  
  149. const jsonParse = function() {
  150. try {
  151. return JSON.parse.apply(this, arguments)
  152. } catch (e) {}
  153. return null;
  154. }
  155. const jsonStringify = function(obj) {
  156. try {
  157. return JSON.stringify.call(this, obj, jsonStringify_replacer)
  158. } catch (e) {}
  159. return null;
  160. }
  161.  
  162. function _postMsg() {
  163. //async is needed. or error handling for postMessage
  164. const [win, tag, ...data] = arguments;
  165. if (typeof tag == 'string') {
  166. let postMsgObj = {
  167. tag,
  168. passing: true,
  169. winOrder: _postMsg.a
  170. }
  171. try {
  172. let k = 'msg-' + (+new Date)
  173. win.document[str_postMsgData] = win.document[str_postMsgData] || {}
  174. win.document[str_postMsgData][k] = data; //direct
  175. postMsgObj.str = k;
  176. postMsgObj.stype = 1;
  177. } catch (e) {}
  178. if (!postMsgObj.stype) {
  179. postMsgObj.str = jsonStringify({
  180. d: data
  181. })
  182. if (postMsgObj.str && postMsgObj.str.length) postMsgObj.stype = 2;
  183. }
  184. if (!postMsgObj.stype) {
  185. postMsgObj.str = "" + data;
  186. postMsgObj.stype = 0;
  187. }
  188. win.postMessage(postMsgObj, '*');
  189. }
  190.  
  191. }
  192.  
  193. function postMsg() {
  194. let win = window;
  195. let a = 0;
  196. while (win = win.parent) {
  197. _postMsg.a = ++a;
  198. _postMsg(win, ...arguments)
  199. if (win == top) break;
  200. }
  201. }
  202.  
  203.  
  204. function crossBrowserTransition(type) {
  205. if (crossBrowserTransition['_result_' + type]) return crossBrowserTransition['_result_' + type]
  206. let el = document.createElement("fakeelement");
  207.  
  208. const capital = (x) => x[0].toUpperCase() + x.substr(1);
  209. const capitalType = capital(type);
  210.  
  211. const transitions = {
  212. [type]: `${type}end`,
  213. [`O${capitalType}`]: `o${capitalType}End`,
  214. [`Moz${capitalType}`]: `${type}end`,
  215. [`Webkit${capitalType}`]: `webkit${capitalType}End`,
  216. [`MS${capitalType}`]: `MS${capitalType}End`
  217. }
  218.  
  219. for (let styleProp in transitions) {
  220. if (el.style[styleProp] !== undefined) {
  221. return (crossBrowserTransition['_result_' + type] = transitions[styleProp]);
  222. }
  223. }
  224. }
  225.  
  226. function isInOperation(elm) {
  227. let elmInFocus = elm || document.activeElement;
  228. if (!elmInFocus) return false;
  229. let res1 = elmInFocus.__matches__(
  230. 'a[href],link[href],button,input:not([type="hidden"]),select,textarea,iframe,frame,menuitem,[draggable],[contenteditable]'
  231. );
  232. return res1;
  233. }
  234.  
  235. const fn_toString = (f, n = 50) => {
  236. let s = (f + "");
  237. if (s.length > 2 * n + 5) {
  238. s = s.substr(0, n) + ' ... ' + s.substr(-n);
  239. }
  240. return s
  241. };
  242.  
  243. function consoleLog() {
  244. if (!_debug_h5p_logging_) return;
  245. if (isIframe) postMsg('consoleLog', ...arguments);
  246. else console.log.apply(console, arguments);
  247. }
  248.  
  249. function consoleLogF() {
  250. if (isIframe) postMsg('consoleLog', ...arguments);
  251. else console.log.apply(console, arguments);
  252. }
  253.  
  254. class AFLooperArray extends Array {
  255. constructor() {
  256. super();
  257. this.activeLoopsCount = 0;
  258. this.cid = 0;
  259. this.loopingFrame = this.loopingFrame.bind(this);
  260. }
  261.  
  262. loopingFrame() {
  263. if (!this.cid) return; //cancelled
  264. for (const opt of this) {
  265. if (opt.isFunctionLooping) opt.qn(opt.fn);
  266. }
  267. this.cid = $ws.requestAnimationFrame(this.loopingFrame);
  268. }
  269.  
  270. get isArrayLooping() {
  271. return this.cid > 0;
  272. }
  273.  
  274. loopStart() {
  275. this.cid = $ws.requestAnimationFrame(this.loopingFrame);
  276. }
  277. loopStop() {
  278. if (this.cid) $ws.cancelAnimationFrame(this.cid);
  279. this.cid = 0;
  280. }
  281. appendLoop(fn) {
  282. if (typeof fn != 'function' || !this) return;
  283. const opt = new AFLooperFunc(fn, this);
  284. super.push(opt);
  285. return opt;
  286. }
  287. }
  288.  
  289. class AFLooperFunc {
  290. constructor(fn, bind) {
  291. this._looping = false;
  292. this.fn = fn.bind(this);
  293. if($mb.nightly_isSupportQueueMicrotask()) this.qn=$mb.queueMicrotask; else this.qn=this.fn; //qn(fn) = qn() = fn()
  294. this.bind = bind;
  295. }
  296. get isFunctionLooping() {
  297. return this._looping;
  298. }
  299. loopingStart() {
  300. if (this._looping === false) {
  301. this._looping = true;
  302. if (++this.bind.activeLoopsCount == 1) this.bind.loopStart();
  303. }
  304. }
  305. loopingStop() {
  306. if (this._looping === true) {
  307. this._looping = false;
  308. if (--this.bind.activeLoopsCount == 0) this.bind.loopStop();
  309. }
  310. }
  311. }
  312.  
  313. function decimalEqual(a, b) {
  314. return Math.round(a * 100000000) == Math.round(b * 100000000)
  315. }
  316.  
  317. function nonZeroNum(a) {
  318. return a > 0 || a < 0;
  319. }
  320.  
  321. class PlayerConf {
  322.  
  323. get scaleFactor() {
  324. return this.mFactor * this.vFactor;
  325. }
  326.  
  327. cssTransform() {
  328.  
  329. const playerConf = this;
  330. const player = playerConf.domElement;
  331. if (!player) return;
  332. const videoScale = playerConf.scaleFactor;
  333. let {x,y}=playerConf.translate;
  334.  
  335. let [_x,_y]=((playerConf.rotate % 180) == 90)?[y,x]:[x,y];
  336.  
  337. if((playerConf.rotate % 360) == 270)_x = -_x;
  338. if((playerConf.rotate % 360) == 90)_y = -_y;
  339.  
  340. var s = [
  341. playerConf.rotate > 0 ? 'rotate(' + playerConf.rotate + 'deg)' : '',
  342. !decimalEqual(videoScale, 1.0) ? 'scale(' + videoScale + ')' : '',
  343. (nonZeroNum(_x) || nonZeroNum(_y)) ? `translate(${_x}px, ${_y}px)` : '',
  344. ];
  345.  
  346. player.style.transform = s.join(' ').trim()
  347.  
  348. }
  349.  
  350. constructor() {
  351.  
  352. this.translate = {
  353. x: 0,
  354. y: 0
  355. };
  356. this.rotate = 0;
  357. this.mFactor = 1.0;
  358. this.vFactor = 1.0;
  359. this.fps = 30;
  360. this.filter_key = {};
  361. this.filter_view_units = {
  362. 'hue-rotate': 'deg',
  363. 'blur': 'px'
  364. };
  365. this.filterReset();
  366.  
  367. }
  368.  
  369. setFilter(prop, f) {
  370.  
  371. let oldValue = this.filter_key[prop];
  372. if (typeof oldValue != 'number') return;
  373. let newValue = f(oldValue)
  374. if (oldValue != newValue) {
  375.  
  376. newValue = +newValue.toFixed(6); //javascript bug
  377.  
  378. }
  379.  
  380. this.filter_key[prop] = newValue
  381. this.filterSetup();
  382.  
  383. return newValue;
  384.  
  385.  
  386.  
  387. }
  388.  
  389. filterSetup(options) {
  390.  
  391. let ums = GM_getValue("unsharpen_mask")
  392. if (!ums) ums = ""
  393.  
  394. let view = []
  395. let playerElm = $hs.player();
  396. if (!playerElm) return;
  397. for (let view_key in this.filter_key) {
  398. let filter_value = +((+this.filter_key[view_key] || 0).toFixed(3))
  399. let addTo = true;
  400. switch(view_key){
  401. case 'brightness':
  402. /* fall through */
  403. case 'contrast':
  404. /* fall through */
  405. case 'saturate':
  406. if(decimalEqual(filter_value,1.0))addTo=false;
  407. break;
  408. case 'hue-rotate':
  409. /* fall through */
  410. case 'blur':
  411. if(decimalEqual(filter_value,0.0))addTo=false;
  412. break;
  413. }
  414. let view_unit = this.filter_view_units[view_key] || ''
  415. if(addTo) view.push(`${view_key}(${filter_value}${view_unit})`)
  416. this.filter_key[view_key] = Number(+this.filter_key[view_key] || 0)
  417. }
  418. if (ums) view .push(`url("#_h5p_${ums}")`);
  419. if (options && options.grey) view .push('url("#grey1")');
  420. playerElm.style.filter = view.join(' ').trim(); //performance in firefox is bad
  421. }
  422.  
  423. filterReset() {
  424. this.filter_key['brightness'] = 1.0
  425. this.filter_key['contrast'] = 1.0
  426. this.filter_key['saturate'] = 1.0
  427. this.filter_key['hue-rotate'] = 0.0
  428. this.filter_key['blur'] = 0.0
  429. this.filterSetup()
  430. }
  431.  
  432. }
  433.  
  434. const Store = {
  435. prefix: '_h5_player',
  436. save: function(k, v) {
  437. if (!Store.available()) return false;
  438. if (typeof v != 'string') return false;
  439. Store.LS.setItem(Store.prefix + k, v)
  440. let sk = fn_toString(k + "", 30);
  441. let sv = fn_toString(v + "", 30);
  442. consoleLog(`localStorage Saved "${sk}" = "${sv}"`)
  443. return true;
  444.  
  445. },
  446. read: function(k) {
  447. if (!Store.available()) return false;
  448. let v = Store.LS.getItem(Store.prefix + k)
  449. let sk = fn_toString(k + "", 30);
  450. let sv = fn_toString(v + "", 30);
  451. consoleLog(`localStorage Read "${sk}" = "${sv}"`);
  452. return v;
  453.  
  454. },
  455. remove: function(k) {
  456.  
  457. if (!Store.available()) return false;
  458. Store.LS.removeItem(Store.prefix + k)
  459. let sk = fn_toString(k + "", 30);
  460. consoleLog(`localStorage Removed "${sk}"`)
  461. return true;
  462. },
  463. clearInvalid: function(sVersion) {
  464. if (!Store.available()) return false;
  465.  
  466. //let sVersion=1814;
  467. if (+Store.read('_sVersion_') < sVersion) {
  468. Store._keys()
  469. .filter(s => s.indexOf(Store.prefix) === 0)
  470. .forEach(key => window.localStorage.removeItem(key))
  471. Store.save('_sVersion_', sVersion + '')
  472. return 2;
  473. }
  474. return 1;
  475.  
  476. },
  477. available: function() {
  478. if (Store.LS) return true;
  479. if (!window) return false;
  480. const localStorage = window.localStorage;
  481. if (!localStorage) return false;
  482. if (typeof localStorage != 'object') return false;
  483. if (!('getItem' in localStorage)) return false;
  484. if (!('setItem' in localStorage)) return false;
  485. Store.LS = localStorage;
  486. return true;
  487.  
  488. },
  489. _keys: function() {
  490. return Object.keys(localStorage);
  491. },
  492. _setItem: function(key, value) {
  493. return localStorage.setItem(key, value)
  494. },
  495. _getItem: function(key) {
  496. return localStorage.getItem(key)
  497. },
  498. _removeItem: function(key) {
  499. return localStorage.removeItem(key)
  500. }
  501.  
  502. }
  503.  
  504. const domTool = {
  505. nopx: (x) => +x.replace('px', ''),
  506. cssWH: function(m, r) {
  507. if (!r) r = getComputedStyle(m, null);
  508. let c = (x) => +x.replace('px', '');
  509. return {
  510. w: m.offsetWidth || c(r.width),
  511. h: m.offsetHeight || c(r.height)
  512. }
  513. },
  514. _isActionBox_1: function(vEl, pEl) {
  515.  
  516. let vElCSS = domTool.cssWH(vEl);
  517. let vElCSSw = vElCSS.w;
  518. let vElCSSh = vElCSS.h;
  519.  
  520. let vElx = vEl;
  521. let res = [];
  522. let mLevel = 0;
  523. if (vEl && pEl && vEl != pEl && pEl.contains(vEl)) {
  524. while (vElx && vElx != pEl) {
  525. vElx = vElx.parentNode;
  526. let vElx_css = null;
  527. if (isShadowRoot(vElx)) {} else {
  528. vElx_css = getComputedStyle(vElx, null);
  529. let vElx_wp = domTool.nopx(vElx_css.paddingLeft) + domTool.nopx(vElx_css.paddingRight)
  530. vElCSSw += vElx_wp
  531. let vElx_hp = domTool.nopx(vElx_css.paddingTop) + domTool.nopx(vElx_css.paddingBottom)
  532. vElCSSh += vElx_hp
  533. }
  534. res.push({
  535. level: ++mLevel,
  536. padW: vElCSSw,
  537. padH: vElCSSh,
  538. elm: vElx,
  539. css: vElx_css
  540. })
  541.  
  542. }
  543. }
  544.  
  545. // in the array, each item is the parent of video player
  546. res.vEl_cssWH = vElCSS
  547.  
  548. return res;
  549.  
  550. },
  551. _isActionBox: function(vEl, walkRes, pEl_idx) {
  552.  
  553. function absDiff(w1, w2, h1, h2) {
  554. let w = (w1 - w2),
  555. h = h1 - h2;
  556. return [(w > 0 ? w : -w), (h > 0 ? h : -h)]
  557. }
  558.  
  559. function midPoint(rect) {
  560. return {
  561. x: (rect.left + rect.right) / 2,
  562. y: (rect.top + rect.bottom) / 2
  563. }
  564. }
  565.  
  566. let parentCount = walkRes.length;
  567. if (pEl_idx >= 0 && pEl_idx < parentCount) {} else {
  568. return;
  569. }
  570. let pElr = walkRes[pEl_idx]
  571. if (!pElr.css) {
  572. //shadowRoot
  573. return true;
  574. }
  575.  
  576. let pEl = pElr.elm;
  577.  
  578. //prevent activeElement==body
  579. let pElCSS = domTool.cssWH(pEl, pElr.css);
  580.  
  581. //check prediction of parent dimension
  582. let d1v = absDiff(pElCSS.w, pElr.padW, pElCSS.h, pElr.padH)
  583.  
  584. let d1x = d1v[0] < 10
  585. let d1y = d1v[1] < 10;
  586.  
  587. if (d1x && d1y) return true; //both edge along the container - fit size
  588. if (!d1x && !d1y) return false; //no edge along the container - body contain the video element, fixed width&height
  589.  
  590. //case: youtube video fullscreen
  591.  
  592. //check centre point
  593.  
  594. let pEl_rect = pEl.getBoundingClientRect()
  595. let vEl_rect = vEl.getBoundingClientRect()
  596.  
  597. let pEl_center = midPoint(pEl_rect)
  598. let vEl_center = midPoint(vEl_rect)
  599.  
  600. let d2v = absDiff(pEl_center.x, vEl_center.x, pEl_center.y, vEl_center.y);
  601.  
  602. let d2x = d2v[0] < 10;
  603. let d2y = d2v[1] < 10;
  604.  
  605. return (d2x && d2y);
  606.  
  607. },
  608. getRect: function(element) {
  609. let rect = element.getBoundingClientRect();
  610. let scroll = domTool.getScroll();
  611. return {
  612. pageX: rect.left + scroll.left,
  613. pageY: rect.top + scroll.top,
  614. screenX: rect.left,
  615. screenY: rect.top
  616. };
  617. },
  618. getScroll: function() {
  619. return {
  620. left: document.documentElement.scrollLeft || document.body.scrollLeft,
  621. top: document.documentElement.scrollTop || document.body.scrollTop
  622. };
  623. },
  624. getClient: function() {
  625. return {
  626. width: document.compatMode == 'CSS1Compat' ? document.documentElement.clientWidth : document.body.clientWidth,
  627. height: document.compatMode == 'CSS1Compat' ? document.documentElement.clientHeight : document.body.clientHeight
  628. };
  629. },
  630. addStyle: //GM_addStyle,
  631. function(css, head) {
  632. if (!head) {
  633. let _doc = document.documentElement;
  634. head = _doc.querySelector('head') || _doc.querySelector('html') || _doc;
  635. }
  636. let doc = head.ownerDocument;
  637. let style = doc.createElement('style');
  638. style.type = 'text/css';
  639. style.textContent = css;
  640. head.appendChild(style);
  641. //console.log(document.head,style,'add style')
  642. return style;
  643. },
  644. eachParentNode: function(dom, fn) {
  645. let parent = dom.parentNode
  646. while (parent) {
  647. let isEnd = fn(parent, dom)
  648. parent = parent.parentNode
  649. if (isEnd) {
  650. break
  651. }
  652. }
  653. },
  654.  
  655. hideDom: function hideDom(selector) {
  656. let dom = document.querySelector(selector)
  657. if (dom) {
  658. $ws.requestAnimationFrame(function() {
  659. dom.style.opacity = 0;
  660. dom.style.transform = 'translate(-9999px)';
  661. dom = null;
  662. })
  663. }
  664. }
  665. };
  666.  
  667. const handle = {
  668.  
  669.  
  670. afPlaybackRecording: async function() {
  671. const opts = this;
  672.  
  673. let qTime = +new Date;
  674. if (qTime >= opts.pTime) {
  675. opts.pTime = qTime + opts.timeDelta; //prediction of next Interval
  676. opts.savePlaybackProgress()
  677. }
  678.  
  679. },
  680. savePlaybackProgress: function() {
  681.  
  682. //this refer to endless's opts
  683. let player = this.player;
  684.  
  685. let _uid = this.player_uid; //_h5p_uid_encrypted
  686. if (!_uid) return;
  687.  
  688. let shallSave = true;
  689. let currentTimeToSave = ~~player.currentTime;
  690.  
  691. if (this._lastSave == currentTimeToSave) shallSave = false;
  692.  
  693. if (shallSave) {
  694.  
  695. this._lastSave = currentTimeToSave
  696.  
  697. //console.log('aasas',this.player_uid, shallSave, '_play_progress_'+_uid, currentTimeToSave)
  698.  
  699. Store.save('_play_progress_' + _uid, jsonStringify({
  700. 't': currentTimeToSave
  701. }))
  702.  
  703. }
  704.  
  705. },
  706. playingWithRecording: function() {
  707. let player = this.player;
  708. if (!player.paused && !this.isFunctionLooping){
  709. let player = this.player;
  710. let _uid = player.getAttribute('_h5p_uid_encrypted') || ''
  711. if (_uid) {
  712. this.player_uid = _uid;
  713. this.pTime = 0;
  714. this.loopingStart();
  715. }
  716. }
  717. }
  718.  
  719. };
  720.  
  721. class Momentary extends Map {
  722. act(uniqueId, fn_start, fn_end, delay) {
  723. if (!uniqueId) return;
  724. uniqueId = uniqueId + "";
  725. const last_cid = this.get(uniqueId);
  726. if (last_cid > 0) clearTimeout(last_cid);
  727. fn_start();
  728. const new_cid = setTimeout(fn_end, delay)
  729. this.set(uniqueId, new_cid)
  730. }
  731. }
  732.  
  733. const momentary = new Momentary();
  734.  
  735. const $hs = {
  736.  
  737. /* 提示文本的字號 */
  738. fontSize: 16,
  739. enable: true,
  740. playerInstance: null,
  741. playbackRate: 1,
  742. /* 快進快退步長 */
  743. skipStep: 5,
  744. trigger_actionBoxes: [],
  745.  
  746. /* 獲取當前播放器的實例 */
  747. player: function() {
  748. let res = $hs.playerInstance || null;
  749. if (res && res.parentNode == null) {
  750. $hs.playerInstance = null;
  751. res = null;
  752. }
  753.  
  754. if (res == null) {
  755. for (let k in playerConfs) {
  756. let playerConf = playerConfs[k];
  757. if (playerConf && playerConf.domElement && playerConf.domElement.parentNode) return playerConf.domElement;
  758. }
  759. }
  760. return res;
  761. },
  762.  
  763. pictureInPicture: function(videoElm) {
  764. if (document.pictureInPictureElement) {
  765. document.exitPictureInPicture();
  766. } else if ('requestPictureInPicture' in videoElm) {
  767. videoElm.requestPictureInPicture()
  768. } else {
  769. $hs.tips('PIP is not supported.');
  770. }
  771. },
  772.  
  773. getPlayerConf: function(video) {
  774.  
  775. if (!video) return null;
  776. let vpid = video.getAttribute('_h5ppid') || null;
  777. if (!vpid) return null;
  778. return playerConfs[vpid] || null;
  779.  
  780. },
  781.  
  782. handlerVideoPlaying: function(evt) {
  783. const videoElm = evt.target || this || null;
  784. const playerConf = $hs.getPlayerConf(videoElm)
  785.  
  786. $hs._actionBoxSet(videoElm);
  787. if (playerConf) {
  788. if (playerConf.timeout_pause > 0) playerConf.timeout_pause = clearTimeout(playerConf.timeout_pause);
  789. playerConf.lastPauseAt = 0
  790. playerConf.domActive |= DOM_ACTIVE_ONCE_PLAYED;
  791. playerConf.domActive &= ~DOM_ACTIVE_DELAYED_PAUSED;
  792. }
  793.  
  794. $hs.swtichPlayerInstance();
  795.  
  796.  
  797.  
  798. $hs.onVideoTriggering();
  799.  
  800. if (!$hs.enable) return $hs.tips(false);
  801.  
  802. if (videoElm._isThisPausedBefore_) consoleLog('resumed')
  803. let _pausedbefore_ = videoElm._isThisPausedBefore_
  804.  
  805. if (videoElm.playpause_cid) {
  806. clearTimeout(videoElm.playpause_cid);
  807. videoElm.playpause_cid = 0;
  808. }
  809. let _last_paused = videoElm._last_paused
  810. videoElm._last_paused = videoElm.paused
  811. if (_last_paused === !videoElm.paused) {
  812. videoElm.playpause_cid = setTimeout(() => {
  813. if (videoElm.paused === !_last_paused && !videoElm.paused && _pausedbefore_) {
  814. $hs.tips('Playback resumed', undefined, 2500)
  815. }
  816. }, 90)
  817. }
  818.  
  819. /* 播放的時候進行相關同步操作 */
  820.  
  821. if (!videoElm._record_continuous) {
  822.  
  823. /* 同步之前設定的播放速度 */
  824. $hs.setPlaybackRate()
  825.  
  826. if (!_endlessloop) _endlessloop = new AFLooperArray();
  827.  
  828. videoElm._record_continuous = _endlessloop.appendLoop(handle.afPlaybackRecording);
  829. videoElm._record_continuous._lastSave = -999;
  830.  
  831. videoElm._record_continuous.timeDelta = 2000;
  832. videoElm._record_continuous.player = videoElm
  833. videoElm._record_continuous.savePlaybackProgress = handle.savePlaybackProgress;
  834. videoElm._record_continuous.playingWithRecording = handle.playingWithRecording;
  835. }
  836.  
  837. videoElm._record_continuous.playingWithRecording(videoElm); //try to start recording
  838.  
  839. videoElm._isThisPausedBefore_ = false;
  840.  
  841. },
  842. handlerVideoPause: function(evt) {
  843.  
  844. const videoElm = evt.target || this || null;
  845.  
  846. const playerConf = $hs.getPlayerConf(videoElm)
  847. if (playerConf) {
  848. playerConf.lastPauseAt = +new Date;
  849. playerConf.timeout_pause = setTimeout(() => {
  850. if (playerConf.lastPauseAt > 0) playerConf.domActive |= DOM_ACTIVE_DELAYED_PAUSED;
  851. }, 600)
  852. }
  853.  
  854. if (!$hs.enable) return $hs.tips(false);
  855. consoleLog('pause')
  856. videoElm._isThisPausedBefore_ = true;
  857.  
  858. let _last_paused = videoElm._last_paused
  859. videoElm._last_paused = videoElm.paused
  860. if (videoElm.playpause_cid) {
  861. clearTimeout(videoElm.playpause_cid);
  862. videoElm.playpause_cid = 0;
  863. }
  864. if (_last_paused === !videoElm.paused) {
  865. videoElm.playpause_cid = setTimeout(() => {
  866. if (videoElm.paused === !_last_paused && videoElm.paused) {
  867. $hs._tips(videoElm, 'Playback paused', undefined, 2500)
  868. }
  869. }, 90)
  870. }
  871.  
  872.  
  873. if (videoElm._record_continuous && videoElm._record_continuous.isFunctionLooping) {
  874. videoElm._record_continuous.savePlaybackProgress(); //savePlaybackProgress once before stopping //handle.savePlaybackProgress;
  875. videoElm._record_continuous.loopingStop();
  876. }
  877.  
  878.  
  879. },
  880. handlerVideoVolumeChange: function(evt) {
  881.  
  882. const videoElm = evt.target || this || null;
  883.  
  884. if (videoElm.volume >= 0) {} else {
  885. return;
  886. }
  887. let cVol = videoElm.volume;
  888. let cMuted = videoElm.muted;
  889.  
  890. if (cVol === videoElm._volume_p && cMuted === videoElm._muted_p) {
  891. // nothing changed
  892. } else if (cVol === videoElm._volume_p && cMuted !== videoElm._muted_p) {
  893. // muted changed
  894. } else { // cVol != pVol
  895.  
  896. // only volume changed
  897.  
  898. let shallShowTips = videoElm._volume >= 0; //prevent initialization
  899.  
  900. if (!cVol) {
  901. videoElm.muted = true;
  902. } else if (cMuted) {
  903. videoElm.muted = false;
  904. videoElm._volume = cVol;
  905. } else if (!cMuted) {
  906. videoElm._volume = cVol;
  907. }
  908. consoleLog('volume changed')
  909.  
  910. if (shallShowTips)
  911. $hs._tips(videoElm, 'Volume: ' + dround(videoElm.volume * 100) + '%', undefined, 3000)
  912.  
  913. }
  914.  
  915. videoElm._volume_p = cVol
  916. videoElm._muted_p = cMuted
  917.  
  918. },
  919. handlerVideoLoadedMetaData: function(evt) {
  920. const videoElm = evt.target || this || null;
  921. consoleLog('video size', this.videoWidth + ' x ' + this.videoHeight);
  922.  
  923. let vpid = videoElm.getAttribute('_h5ppid') || null;
  924. if (!vpid || !videoElm.currentSrc) return;
  925.  
  926. if ($hs.varSrcList[vpid] != videoElm.currentSrc) {
  927. $hs.varSrcList[vpid] = videoElm.currentSrc;
  928. $hs.videoSrcFound(videoElm);
  929. $hs._actionBoxSet(videoElm);
  930. }
  931. if (!videoElm._onceVideoLoaded) {
  932. videoElm._onceVideoLoaded = true;
  933. playerConfs[vpid].domActive |= DOM_ACTIVE_SRC_LOADED;
  934. }
  935.  
  936. },
  937. handlerElementMouseEnter: function(evt) {
  938. if ($hs.intVideoInitCount > 0) {} else {
  939. return;
  940. }
  941.  
  942. const actionBox = $hs.trigger_actionBoxes.filter(actionBox => (evt.target === actionBox || actionBox.contains(evt.target)))[0]
  943. if (!actionBox) return;
  944. const vpid = actionBox.getAttribute('_h5p_actionbox_');
  945. const videoElm = getRoot(actionBox).querySelector(`VIDEO[_h5ppid="${vpid}"]`);
  946. if (!videoElm) return;
  947.  
  948. $hs._actionBoxSet(videoElm);
  949.  
  950. const playerConf = $hs.getPlayerConf(videoElm)
  951. if (playerConf) {
  952. momentary.act("actionBoxMouseEnter",
  953. () => {
  954. playerConf.domActive |= DOM_ACTIVE_MOUSE_IN;
  955. },
  956. () => {
  957. playerConf.domActive &= ~DOM_ACTIVE_MOUSE_IN;
  958. },
  959. 300)
  960. }
  961.  
  962. },
  963. handlerElementMouseDown: function(evt) {
  964.  
  965. if ($hs.intVideoInitCount > 0) {} else {
  966. return;
  967. }
  968.  
  969. const actionBox = $hs.trigger_actionBoxes.filter(actionBox => (evt.target === actionBox || actionBox.contains(evt.target)))[0]
  970. if (!actionBox) return;
  971. const vpid = actionBox.getAttribute('_h5p_actionbox_');
  972. const videoElm = getRoot(actionBox).querySelector(`VIDEO[_h5ppid="${vpid}"]`);
  973. if (!videoElm) return;
  974.  
  975. $hs._actionBoxSet(videoElm);
  976.  
  977. const playerConf = $hs.getPlayerConf(videoElm)
  978. if (playerConf) {
  979. momentary.act("actionBoxClicking",
  980. () => {
  981. playerConf.domActive |= DOM_ACTIVE_MOUSE_CLICK;
  982. },
  983. () => {
  984. playerConf.domActive &= ~DOM_ACTIVE_MOUSE_CLICK;
  985. },
  986. 300)
  987. }
  988. $hs.swtichPlayerInstance();
  989.  
  990. },
  991. handlerElementWheelTuneVolume: function(evt) { //shift + wheel
  992.  
  993. if ($hs.intVideoInitCount > 0) {} else {
  994. return;
  995. }
  996.  
  997. const actionBox = $hs.trigger_actionBoxes.filter(actionBox => (evt.target === actionBox || actionBox.contains(evt.target)))[0]
  998. if (!actionBox) return;
  999. const vpid = actionBox.getAttribute('_h5p_actionbox_');
  1000. const videoElm = getRoot(actionBox).querySelector(`VIDEO[_h5ppid="${vpid}"]`);
  1001. if (!videoElm) return;
  1002.  
  1003. if (!evt.shiftKey) return;
  1004. if (evt.deltaY) {
  1005. let player = $hs.player();
  1006. if (!player || player != videoElm) return;
  1007.  
  1008. if (evt.deltaY > 0) {
  1009.  
  1010. if ((player.muted && player.volume === 0) && player._volume > 0) {
  1011.  
  1012. player.muted = false;
  1013. player.volume = player._volume;
  1014. } else if (player.muted && (player.volume > 0 || !player._volume)) {
  1015. player.muted = false;
  1016. }
  1017. $hs.tuneVolume(-0.05)
  1018.  
  1019. evt.stopPropagation()
  1020. evt.preventDefault()
  1021. return false
  1022.  
  1023. } else if (evt.deltaY < 0) {
  1024.  
  1025. if ((player.muted && player.volume === 0) && player._volume > 0) {
  1026. player.muted = false;
  1027. player.volume = player._volume;
  1028. } else if (player.muted && (player.volume > 0 || !player._volume)) {
  1029. player.muted = false;
  1030. }
  1031. $hs.tuneVolume(+0.05)
  1032.  
  1033. evt.stopPropagation()
  1034. evt.preventDefault()
  1035. return false
  1036.  
  1037. }
  1038. }
  1039. },
  1040. debug01: function(evt, videoActive) {
  1041.  
  1042. if (!$hs.eventHooks) {
  1043. document.__h5p_eventhooks = ($hs.eventHooks = {
  1044. _debug_: []
  1045. });
  1046. }
  1047. $hs.eventHooks._debug_.push([videoActive, evt.type]);
  1048. // console.log('h5p eventhooks = document.__h5p_eventhooks')
  1049. },
  1050.  
  1051. swtichPlayerInstance: function() {
  1052.  
  1053. let newPlayerInstance = null;
  1054. const ONLY_PLAYING_NONE = 0x4A00;
  1055. const ONLY_PLAYING_MORE_THAN_ONE = 0x5A00;
  1056. let onlyPlayingInstance = ONLY_PLAYING_NONE;
  1057. for (let k in playerConfs) {
  1058. let playerConf = playerConfs[k] || {};
  1059. let {
  1060. domElement,
  1061. domActive
  1062. } = playerConf;
  1063. if (domElement) {
  1064. if (domActive & DOM_ACTIVE_INVALID_PARENT) continue;
  1065. if (!domElement.parentNode) {
  1066. playerConf.domActive |= DOM_ACTIVE_INVALID_PARENT;
  1067. continue;
  1068. }
  1069. if (domActive & DOM_ACTIVE_MOUSE_CLICK) {
  1070. newPlayerInstance = domElement
  1071. break;
  1072. }
  1073. if (domActive & DOM_ACTIVE_ONCE_PLAYED && (domActive & DOM_ACTIVE_DELAYED_PAUSED) == 0) {
  1074. if (onlyPlayingInstance == ONLY_PLAYING_NONE) onlyPlayingInstance = domElement;
  1075. else onlyPlayingInstance = ONLY_PLAYING_MORE_THAN_ONE;
  1076. }
  1077. }
  1078. }
  1079. if (newPlayerInstance == null && onlyPlayingInstance.nodeType == 1) {
  1080. newPlayerInstance = onlyPlayingInstance;
  1081. }
  1082.  
  1083. $hs.playerInstance = newPlayerInstance
  1084.  
  1085.  
  1086. },
  1087. handlerElementDblClick: function(evt) {
  1088.  
  1089. if ($hs.intVideoInitCount > 0) {} else {
  1090. return;
  1091. }
  1092.  
  1093. if (document.readyState != "complete") return;
  1094.  
  1095. const actionBox = $hs.trigger_actionBoxes.filter(actionBox => (evt.target === actionBox || actionBox.contains(evt.target)))[0]
  1096. if (!actionBox) return;
  1097. const vpid = actionBox.getAttribute('_h5p_actionbox_');
  1098. const videoElm = getRoot(actionBox).querySelector(`VIDEO[_h5ppid="${vpid}"]`);
  1099. if (!videoElm) return;
  1100.  
  1101. $hs._actionBoxSet(videoElm);
  1102.  
  1103. const playerConf = $hs.getPlayerConf(videoElm)
  1104. if (playerConf) {
  1105. momentary.act("actionBoxClicking",
  1106. () => {
  1107. playerConf.domActive |= DOM_ACTIVE_MOUSE_CLICK;
  1108. },
  1109. () => {
  1110. playerConf.domActive &= ~DOM_ACTIVE_MOUSE_CLICK;
  1111. },
  1112. 600)
  1113. }
  1114. $hs.swtichPlayerInstance()
  1115.  
  1116. $hs.onVideoTriggering()
  1117.  
  1118. $hs.callFullScreenBtn();
  1119.  
  1120. evt.stopPropagation()
  1121. evt.preventDefault()
  1122. return false
  1123.  
  1124. },
  1125. handlerDocFocusOut: function(e) {
  1126. let doc = this;
  1127. $hs.focusFxLock = true;
  1128. $ws.requestAnimationFrame(function() {
  1129. $hs.focusFxLock = false;
  1130. if (!$hs.enable) $hs.tips(false);
  1131. else
  1132. if (!doc.hasFocus() && $hs.player() && !$hs.isLostFocus) {
  1133. $hs.isLostFocus = true;
  1134. consoleLog('doc.focusout')
  1135. //$hs.tips('focus is lost', -1);
  1136. }
  1137. });
  1138. },
  1139. handlerDocFocusIn: function(e) {
  1140. let doc = this;
  1141. if ($hs.focusFxLock) return;
  1142. $ws.requestAnimationFrame(function() {
  1143.  
  1144. if ($hs.focusFxLock) return;
  1145. if (!$hs.enable) $hs.tips(false);
  1146. else
  1147. if (doc.hasFocus() && $hs.player() && $hs.isLostFocus) {
  1148. $hs.isLostFocus = false;
  1149. consoleLog('doc.focusin')
  1150. $hs.tips(false);
  1151.  
  1152. }
  1153. });
  1154. },
  1155.  
  1156. handlerWinMessage: async function(e) {
  1157. let tag, ed;
  1158. if (typeof e.data == 'object' && typeof e.data.tag == 'string') {
  1159. tag = e.data.tag;
  1160. ed = e.data
  1161. } else {
  1162. return;
  1163. }
  1164. let msg = null,
  1165. success = 0;
  1166. switch (tag) {
  1167. case 'consoleLog':
  1168. let msg_str = ed.str;
  1169. let msg_stype = ed.stype;
  1170. if (msg_stype === 1) {
  1171. msg = (document[str_postMsgData] || {})[msg_str] || [];
  1172. success = 1;
  1173. } else if (msg_stype === 2) {
  1174. msg = jsonParse(msg_str);
  1175. if (msg && msg.d) {
  1176. success = 2;
  1177. msg = msg.d;
  1178. }
  1179. } else {
  1180. msg = msg_str
  1181. }
  1182. let p = (ed.passing && ed.winOrder) ? [' | from win-' + ed.winOrder] : [];
  1183. if (success) {
  1184. console.log(...msg, ...p)
  1185. //document[ed.data]=null; // also delete the information
  1186. } else {
  1187. console.log('msg--', msg, ...p, ed);
  1188. }
  1189. break;
  1190.  
  1191. }
  1192. },
  1193.  
  1194. isInActiveMode: function(activeElm, player) {
  1195.  
  1196. if (activeElm == player) {
  1197. return true;
  1198. }
  1199.  
  1200. if ($hs.trigger_actionBoxes[0]) {
  1201. let actionBox = $hs.trigger_actionBoxes[0];
  1202. if (activeElm == actionBox || actionBox.contains(activeElm)) {
  1203. return true;
  1204. }
  1205.  
  1206. }
  1207.  
  1208. let _checkingPass = false;
  1209.  
  1210. if (!player) return;
  1211. let layoutBox = $hs.getPlayerBlockElement(player).parentNode;
  1212. if (layoutBox && layoutBox.parentNode && layoutBox.contains(activeElm)) {
  1213. let rpid = player.getAttribute('_h5ppid') || "NULL";
  1214. let actionBox = layoutBox.parentNode.querySelector(`[_h5p_actionbox_="${rpid}"]`); //the box can be layoutBox
  1215. if (actionBox && actionBox.contains(activeElm)) _checkingPass = true;
  1216. }
  1217.  
  1218. return _checkingPass
  1219. },
  1220.  
  1221.  
  1222. toolCheckFullScreen: function(doc) {
  1223. if (typeof doc.fullScreen == 'boolean') return doc.fullScreen;
  1224. if (typeof doc.webkitIsFullScreen == 'boolean') return doc.webkitIsFullScreen;
  1225. if (typeof doc.mozFullScreen == 'boolean') return doc.mozFullScreen;
  1226. return null;
  1227. },
  1228.  
  1229. toolFormatCT: function(u) {
  1230.  
  1231. let w = Math.round(u, 0)
  1232. let a = w % 60
  1233. w = (w - a) / 60
  1234. let b = w % 60
  1235. w = (w - b) / 60
  1236. let str = ("0" + b).substr(-2) + ":" + ("0" + a).substr(-2);
  1237. if (w) str = w + ":" + str
  1238.  
  1239. return str
  1240.  
  1241. },
  1242.  
  1243. makeFocus: function(player, evt) {
  1244. setTimeout(function() {
  1245. let rpid = player.getAttribute('_h5ppid');
  1246. let actionBox = getRoot(player).querySelector(`[_h5p_actionbox_="${rpid}"]`);
  1247.  
  1248. //console.log('p',rpid, player,actionBox,document.activeElement)
  1249. if (actionBox && actionBox != document.activeElement && !actionBox.contains(document.activeElement)) {
  1250. consoleLog('make focus on', actionBox)
  1251. actionBox.focus();
  1252.  
  1253. }
  1254.  
  1255. }, 300)
  1256. },
  1257.  
  1258. getActionBlockElement: function(player, layoutBox) {
  1259.  
  1260. if (!player || !layoutBox) return null;
  1261.  
  1262.  
  1263. let walkRes = domTool._isActionBox_1(player, layoutBox);
  1264. let parentCount = walkRes.length;
  1265. let actionBox = null;
  1266. if (parentCount - 1 >= 0 && domTool._isActionBox(player, walkRes, parentCount - 1)) {
  1267. actionBox = walkRes[parentCount - 1].elm;
  1268. } else if (parentCount - 2 >= 0 && domTool._isActionBox(player, walkRes, parentCount - 2)) {
  1269. actionBox = walkRes[parentCount - 2].elm;
  1270. } else {
  1271. actionBox = player;
  1272. }
  1273.  
  1274.  
  1275. let search = $hs.videoFullscreenBtns(actionBox, 4);
  1276. if (search && search.elements.length > 0) actionBox = search.container;
  1277.  
  1278. return actionBox;
  1279.  
  1280.  
  1281. },
  1282.  
  1283. _actionBoxSet: function(player) {
  1284.  
  1285. if (!player) return null;
  1286. let vpid = player.getAttribute('_h5ppid');
  1287. if (!vpid) return null;
  1288.  
  1289. let layoutBox = $hs.getPlayerBlockElement(player).parentNode;
  1290. let actionBox = $hs.getActionBlockElement(player, layoutBox);
  1291.  
  1292.  
  1293. //console.log('actionbox search:', actionBox)
  1294.  
  1295. if (actionBox && actionBox.getAttribute('_h5p_actionbox_') != vpid) {
  1296. consoleLog('D-1', actionBox);
  1297. for (const previousActionboxes of getRoot(player).querySelectorAll(`[_h5p_actionbox_="${vpid}"]`)) {
  1298. //change of parentNode
  1299. previousActionboxes.removeAttribute('_h5p_actionbox_');
  1300. }
  1301. actionBox.setAttribute('_h5p_actionbox_', vpid);
  1302. $hs.trigger_actionBoxes = [actionBox];
  1303.  
  1304. if (!actionBox.hasAttribute('tabindex')) actionBox.setAttribute('tabindex', '-1');
  1305.  
  1306.  
  1307.  
  1308. }
  1309. return actionBox;
  1310. },
  1311.  
  1312. videoSrcFound: function(player) {
  1313.  
  1314. // src loaded
  1315.  
  1316. if (!player) return;
  1317. let vpid = player.getAttribute('_h5ppid') || null;
  1318. if (!vpid || !player.currentSrc) return;
  1319.  
  1320. player._isThisPausedBefore_ = false;
  1321.  
  1322. player.removeAttribute('_h5p_uid_encrypted');
  1323.  
  1324. if (player._record_continuous) player._record_continuous._lastSave = -999; //first time must save
  1325.  
  1326. let uid_A = location.pathname.replace(/[^\d+]/g, '') + '.' + location.search.replace(/[^\d+]/g, '');
  1327. let _uid = location.hostname.replace('www.', '').toLowerCase() + '!' + location.pathname.toLowerCase() + 'A' + uid_A + 'W' + player.videoWidth + 'H' + player.videoHeight + 'L' + (player.duration << 0);
  1328.  
  1329. digestMessage(_uid).then(function(_uid_encrypted) {
  1330.  
  1331. let d = +new Date;
  1332.  
  1333. let recordedTime = null;
  1334.  
  1335. ;
  1336. (function() {
  1337. //read the last record only;
  1338.  
  1339. let k1 = '_h5_player_play_progress_';
  1340. let k1n = '_play_progress_';
  1341. let k2 = _uid_encrypted;
  1342. let k3 = k1 + k2;
  1343. let k3n = k1n + k2;
  1344. let m2 = Store._keys().filter(key => key.substr(0, k3.length) == k3); //all progress records for this video
  1345. let m2v = m2.map(keyName => +(keyName.split('+')[1] || '0'))
  1346. let m2vMax = Math.max(0, ...m2v)
  1347. if (!m2vMax) recordedTime = null;
  1348. else {
  1349. let _json_recordedTime = null;
  1350. _json_recordedTime = Store.read(k3n + '+' + m2vMax);
  1351. if (!_json_recordedTime) _json_recordedTime = {};
  1352. else _json_recordedTime = jsonParse(_json_recordedTime);
  1353. if (typeof _json_recordedTime == 'object') recordedTime = _json_recordedTime;
  1354. else recordedTime = null;
  1355. recordedTime = typeof recordedTime == 'object' ? recordedTime.t : recordedTime;
  1356. if (typeof recordedTime == 'number' && (+recordedTime >= 0 || +recordedTime <= 0)) {
  1357.  
  1358. } else if (typeof recordedTime == 'string' && recordedTime.length > 0 && (+recordedTime >= 0 || +recordedTime <= 0)) {
  1359. recordedTime = +recordedTime
  1360. } else {
  1361. recordedTime = null
  1362. }
  1363. }
  1364. if (recordedTime !== null) {
  1365. player._h5player_lastrecord_ = recordedTime;
  1366. } else {
  1367. player._h5player_lastrecord_ = null;
  1368. }
  1369. if (player._h5player_lastrecord_ > 5) {
  1370. consoleLog('last record playing', player._h5player_lastrecord_);
  1371. setTimeout(function() {
  1372. $hs._tips(player, `Press Shift-R to restore Last Playback: ${$hs.toolFormatCT(player._h5player_lastrecord_)}`, 5000, 4000)
  1373. }, 1000)
  1374. }
  1375.  
  1376. })();
  1377. // delay the recording by 5.4s => prevent ads or mis operation
  1378. setTimeout(function() {
  1379.  
  1380. let k1 = '_h5_player_play_progress_';
  1381. let k1n = '_play_progress_';
  1382. let k2 = _uid_encrypted;
  1383. let k3 = k1 + k2;
  1384. let k3n = k1n + k2;
  1385.  
  1386. //re-read all the localStorage keys
  1387. let m1 = Store._keys().filter(key => key.substr(0, k1.length) == k1); //all progress records in this site
  1388. let p = m1.length + 1;
  1389.  
  1390. for (const key of m1) { //all progress records for this video
  1391. if (key.substr(0, k3.length) == k3) {
  1392. Store._removeItem(key); //remove previous record for the current video
  1393. p--;
  1394. }
  1395. }
  1396.  
  1397. if (recordedTime !== null) {
  1398. Store.save(k3n + '+' + d, jsonStringify({
  1399. 't': recordedTime
  1400. })) //prevent loss of last record
  1401. }
  1402.  
  1403. const _record_max_ = 48;
  1404. const _record_keep_ = 26;
  1405.  
  1406. if (p > _record_max_) {
  1407. //exisiting 48 records for one site;
  1408. //keep only 26 records
  1409.  
  1410. const comparator = (a, b) => (a.t < b.t ? -1 : a.t > b.t ? 1 : 0);
  1411.  
  1412. m1
  1413. .map(keyName => ({
  1414. keyName,
  1415. t: +(keyName.split('+')[1] || '0')
  1416. }))
  1417. .sort(comparator)
  1418. .slice(0, -_record_keep_)
  1419. .forEach((item) => localStorage.removeItem(item.keyName));
  1420.  
  1421. consoleLog(`stored progress: reduced to ${_record_keep_}`)
  1422. }
  1423.  
  1424. player.setAttribute('_h5p_uid_encrypted', _uid_encrypted + '+' + d);
  1425.  
  1426. //try to start recording
  1427. if (player._record_continuous) player._record_continuous.playingWithRecording();
  1428.  
  1429. }, 5400);
  1430.  
  1431. })
  1432.  
  1433. },
  1434. bindDocEvents: function(rootNode) {
  1435. if (!rootNode._onceBindedDocEvents) {
  1436.  
  1437. rootNode._onceBindedDocEvents = true;
  1438. rootNode.addEventListener('keydown', $hs.handlerRootKeyDownEvent, true)
  1439. document._debug_rootNode_ = rootNode;
  1440.  
  1441. rootNode.addEventListener('mouseenter', $hs.handlerElementMouseEnter, true)
  1442. rootNode.addEventListener('mousedown', $hs.handlerElementMouseDown, true)
  1443. rootNode.addEventListener('dblclick', $hs.handlerElementDblClick, true)
  1444. rootNode.addEventListener('wheel', $hs.handlerElementWheelTuneVolume, {
  1445. passive: false
  1446. });
  1447. // wheel - bubble events to keep it simple (i.e. it must be passive:false & capture:false)
  1448.  
  1449. }
  1450. },
  1451. fireGlobalInit: function() {
  1452. if ($hs.intVideoInitCount != 1) return;
  1453. if (!$hs.varSrcList) $hs.varSrcList = {};
  1454. $hs.isLostFocus = null;
  1455. try {
  1456. //iframe may not be able to control top window
  1457. //error; just ignore with async
  1458. let topDoc = window.top && window.top.document ? window.top.document : null;
  1459. if (topDoc) {
  1460. topDoc.addEventListener('focusout', $hs.handlerDocFocusOut, true)
  1461. topDoc.addEventListener('focusin', $hs.handlerDocFocusIn, true)
  1462. }
  1463.  
  1464. } catch (e) {}
  1465. Store.clearInvalid(_sVersion_)
  1466. },
  1467. onVideoTriggering: function() {
  1468.  
  1469.  
  1470. // initialize a single video player - h5Player.playerInstance
  1471.  
  1472. /**
  1473. * 初始化播放器實例
  1474. */
  1475. let player = $hs.playerInstance
  1476. if (!player) return
  1477.  
  1478. let vpid = player.getAttribute('_h5ppid');
  1479.  
  1480. if (!vpid) return;
  1481.  
  1482. let firstTime = !!$hs.initTips()
  1483. if (firstTime) {
  1484. // first time to trigger this player
  1485. if (!player.hasAttribute('playsinline')) player.setAttribute('playsinline', 'playsinline');
  1486. if (!player.hasAttribute('x-webkit-airplay')) player.setAttribute('x-webkit-airplay', 'deny');
  1487. if (!player.hasAttribute('preload')) player.setAttribute('preload', 'auto');
  1488. //player.style['image-rendering'] = 'crisp-edges';
  1489. $hs.playbackRate = $hs.getPlaybackRate()
  1490. }
  1491.  
  1492. },
  1493. getPlaybackRate: function() {
  1494. let playbackRate = Store.read('_playback_rate_') || $hs.playbackRate
  1495. return Number(Number(playbackRate).toFixed(1))
  1496. },
  1497. getPlayerBlockElement: function(player) {
  1498. //without checkActiveBox, just a DOM for you to append tipsDom
  1499.  
  1500. let layoutBox = null,
  1501. wPlayer = null
  1502.  
  1503. if (!player || !player.offsetHeight || !player.offsetWidth || !player.parentNode) {
  1504. return null;
  1505. }
  1506.  
  1507. function search_nodes() {
  1508.  
  1509. wPlayer = player; // NOT NULL
  1510. layoutBox = wPlayer.parentNode; // NOT NULL
  1511.  
  1512. while (layoutBox.parentNode && layoutBox.nodeType == 1 && layoutBox.offsetHeight == 0) {
  1513. wPlayer = layoutBox; // NOT NULL
  1514. layoutBox = layoutBox.parentNode; // NOT NULL
  1515. }
  1516. //container must be with offsetHeight
  1517.  
  1518. while (layoutBox.parentNode && layoutBox.nodeType == 1 && layoutBox.offsetHeight < player.offsetHeight) {
  1519. wPlayer = layoutBox; // NOT NULL
  1520. layoutBox = layoutBox.parentNode; // NOT NULL
  1521. }
  1522. //container must have height >= player height
  1523.  
  1524. }
  1525.  
  1526. search_nodes();
  1527.  
  1528. if (layoutBox.nodeType == 11) {
  1529.  
  1530.  
  1531. //shadowRoot without html and body
  1532. let shadowChild, shadowElm_container, shadowElm_head, shadowElm_html;
  1533. let rootNode = getRoot(player);
  1534. if (rootNode.querySelectorAll('html,body').length < 2) {
  1535.  
  1536. shadowElm_container = player.ownerDocument.createElement('BODY')
  1537. rootNode.insertBefore(shadowElm_container, rootNode.firstChild)
  1538.  
  1539. while (shadowChild = shadowElm_container.nextSibling) shadowElm_container.appendChild(shadowChild);
  1540.  
  1541. shadowElm_head = rootNode.insertBefore(player.ownerDocument.createElement('HEAD'), shadowElm_container)
  1542.  
  1543. shadowElm_html = rootNode.insertBefore(player.ownerDocument.createElement('HTML'), shadowElm_head)
  1544.  
  1545. shadowElm_container.setAttribute('style', 'padding:0;margin:0;border:0; box-sizing: border-box;')
  1546. shadowElm_html.setAttribute('style', 'padding:0;margin:0;border:0; box-sizing: border-box;')
  1547.  
  1548. shadowElm_html.appendChild(shadowElm_head)
  1549. shadowElm_html.appendChild(shadowElm_container)
  1550.  
  1551. }
  1552.  
  1553. search_nodes();
  1554.  
  1555. }
  1556.  
  1557. //condition:
  1558. //!layoutBox.parentNode || layoutBox.nodeType != 1 || layoutBox.offsetHeight > player.offsetHeight
  1559.  
  1560. // layoutBox is a node contains <video> and offsetHeight>=video.offsetHeight
  1561. // wPlayer is a HTML Element (nodeType==1)
  1562. // you can insert the DOM element into the layoutBox
  1563.  
  1564. if (layoutBox && wPlayer && layoutBox.nodeType === 1 && wPlayer.parentNode == layoutBox) return wPlayer;
  1565. throw 'unknown error';
  1566.  
  1567. },
  1568. getCommonContainer: function(elm1, elm2) {
  1569.  
  1570. let box1 = elm1;
  1571. let box2 = elm2;
  1572.  
  1573. while (box1 && box2) {
  1574. if (box1.contains(box2) || box2.contains(box1)) {
  1575. break;
  1576. }
  1577. box1 = box1.parentNode;
  1578. box2 = box2.parentNode;
  1579. }
  1580.  
  1581. let layoutBox = null;
  1582.  
  1583. box1 = (box1 && box1.contains(elm2)) ? box1 : null;
  1584. box2 = (box2 && box2.contains(elm1)) ? box2 : null;
  1585.  
  1586. if (box1 && box2) layoutBox = box1.contains(box2) ? box2 : box1;
  1587. else layoutBox = box1 || box2 || null;
  1588.  
  1589. return layoutBox
  1590.  
  1591. },
  1592. change_layoutBox: function(tipsDom) {
  1593. let player = $hs.player()
  1594. if (!player) return;
  1595. let wPlayer = $hs.getPlayerBlockElement(player);
  1596. let layoutBox = wPlayer.parentNode;
  1597.  
  1598. if ((layoutBox && layoutBox.nodeType == 1) && (!tipsDom.parentNode || tipsDom.parentNode !== layoutBox)) {
  1599.  
  1600. consoleLog('changed_layoutBox')
  1601. layoutBox.insertBefore(tipsDom, wPlayer);
  1602.  
  1603. }
  1604. },
  1605.  
  1606. queryFullscreenBtnsIndependant: function(parentNode, returnElementsOnly) {
  1607.  
  1608. let btns = [];
  1609. for (const elm of parentNode.querySelectorAll('[class*="full"][class*="screen"]')) {
  1610. let className = (elm.getAttribute('class') || "");
  1611. if (/\b(fullscreen|full-screen)\b/i.test(className.replace(/([A-Z][a-z]+)/g, '-$1-').replace(/[\_\-]+/g, '-'))) {
  1612.  
  1613. let hasClickListeners = null,
  1614. childElementCount = null,
  1615. isVisible = null;
  1616.  
  1617.  
  1618. if ('_listeners' in elm) {
  1619.  
  1620. hasClickListeners = elm._listeners && elm._listeners.click && elm._listeners.click.funcCount > 0
  1621.  
  1622. }
  1623.  
  1624. if ('childElementCount' in elm) {
  1625.  
  1626. childElementCount = elm.childElementCount;
  1627.  
  1628. }
  1629. if ('offsetParent' in elm) {
  1630. isVisible = !!elm.offsetParent; //works with parent/self display none; not work with visiblity hidden / opacity0
  1631.  
  1632. }
  1633.  
  1634. if (hasClickListeners) {
  1635. let btn = {
  1636. elm,
  1637. isVisible,
  1638. hasClickListeners,
  1639. childElementCount,
  1640. isContained: null
  1641. };
  1642.  
  1643.  
  1644. btns.push(returnElementsOnly ? elm : btn)
  1645.  
  1646. }
  1647.  
  1648. }
  1649. }
  1650. return btns;
  1651.  
  1652. },
  1653. exclusiveElements: function(elms) {
  1654.  
  1655. //not containing others
  1656. let res = [];
  1657.  
  1658. for (const roleElm of elms) {
  1659.  
  1660. let isContained = false;
  1661. for (const testElm of elms) {
  1662. if (testElm != roleElm && roleElm.contains(testElm)) {
  1663. isContained = true;
  1664. break;
  1665. }
  1666. }
  1667. if (!isContained) res.push(roleElm)
  1668. }
  1669. return res;
  1670.  
  1671. },
  1672. videoFullscreenBtns: function(startPoint, searchCount) {
  1673.  
  1674.  
  1675. let c = 0,
  1676. p = startPoint,
  1677. q = null;
  1678. while (p && (++c <= searchCount)) {
  1679.  
  1680. if (p.querySelectorAll('video').length !== 1) break;
  1681.  
  1682. let elmFullscreenBtns = $hs.queryFullscreenBtnsIndependant(p, true);
  1683. if (elmFullscreenBtns.length > 0) {
  1684.  
  1685. let exclusiveFullscreenBtns = $hs.exclusiveElements(elmFullscreenBtns)
  1686. if (exclusiveFullscreenBtns.length > 0) {
  1687. return {
  1688. container: p,
  1689. elements: exclusiveFullscreenBtns
  1690. };
  1691.  
  1692. }
  1693. }
  1694.  
  1695.  
  1696. q = p;
  1697. p = p.parentNode;
  1698. }
  1699.  
  1700. return null;
  1701.  
  1702.  
  1703.  
  1704.  
  1705. },
  1706. callFullScreenBtn: function() {
  1707.  
  1708. let player = $hs.player()
  1709. if (!player || !player.ownerDocument || !('exitFullscreen' in player.ownerDocument)) return;
  1710.  
  1711. let btnElement = null;
  1712.  
  1713. let vpid = player.getAttribute('_h5ppid') || null;
  1714.  
  1715. if (!vpid) return;
  1716.  
  1717.  
  1718. const chFull = $hs.toolCheckFullScreen(player.ownerDocument);
  1719.  
  1720.  
  1721.  
  1722.  
  1723. if (chFull === true) {
  1724. player.ownerDocument.exitFullscreen();
  1725. } else if (chFull === false) {
  1726.  
  1727.  
  1728. let actionBox = getRoot(player).querySelector(`[_h5p_actionbox_="${vpid}"]`);
  1729.  
  1730. if (actionBox) {
  1731.  
  1732. let exclusiveFullscreenBtns = $hs.videoFullscreenBtns(actionBox, 1);
  1733. let btnElements = exclusiveFullscreenBtns ? exclusiveFullscreenBtns.elements : null;
  1734. if (btnElements && btnElements.length > 0) {
  1735.  
  1736. //consoleLog('fullscreen btn', btnElements)
  1737.  
  1738. const lens_elemsClassName = btnElements.map(elm => elm.className.length)
  1739. const j_elemsClassName = Math.min(...lens_elemsClassName)
  1740. let btnElement_idx = lens_elemsClassName.lastIndexOf(j_elemsClassName)
  1741. // pick the last btn if there is more than one
  1742. btnElement = btnElements[btnElement_idx];
  1743. window.requestAnimationFrame(() => btnElement.click());
  1744. //consoleLog('original fullscreen')
  1745.  
  1746. }
  1747. }
  1748.  
  1749.  
  1750. }
  1751.  
  1752. if (chFull === null || (chFull === false && !btnElement)) {
  1753.  
  1754. let layoutBox = $hs.getPlayerBlockElement(player).parentNode;
  1755.  
  1756. let gPlayer = null;
  1757. if (!layoutBox || !layoutBox.parentNode) {
  1758. consoleLog('the container for DOM fullscreen cannot be found')
  1759. return;
  1760. }
  1761. gPlayer = layoutBox.parentNode.querySelector(`[_h5p_actionbox_="${vpid}"]`); //the box can be layoutBox
  1762.  
  1763. consoleLog('DOM fullscreen', gPlayer)
  1764. try {
  1765. gPlayer.requestFullscreen() //known bugs : TypeError: fullscreen error
  1766. } catch (e) {
  1767. consoleLogF('fullscreen error', e)
  1768. }
  1769.  
  1770.  
  1771. }
  1772.  
  1773.  
  1774.  
  1775.  
  1776. },
  1777. /* 設置播放速度 */
  1778. setPlaybackRate: function(num, flagTips) {
  1779. let player = $hs.player()
  1780. let curPlaybackRate
  1781. if (num) {
  1782. num = +num
  1783. if (num > 0) { // also checking the type of variable
  1784. curPlaybackRate = num < 0.1 ? 0.1 : +(num.toFixed(1))
  1785. } else {
  1786. console.error('h5player: 播放速度轉換出錯')
  1787. return false
  1788. }
  1789. } else {
  1790. curPlaybackRate = $hs.getPlaybackRate()
  1791. }
  1792. /* 記錄播放速度的信息 */
  1793.  
  1794. let changed = curPlaybackRate !== player.playbackRate;
  1795.  
  1796. if (curPlaybackRate !== player.playbackRate) {
  1797.  
  1798. Store.save('_playback_rate_', curPlaybackRate + '')
  1799. $hs.playbackRate = curPlaybackRate
  1800. player.playbackRate = curPlaybackRate
  1801. /* 本身處於1被播放速度的時候不再提示 */
  1802. //if (!num && curPlaybackRate === 1) return;
  1803.  
  1804. }
  1805.  
  1806. flagTips = (flagTips < 0) ? false : (flagTips > 0) ? true : changed;
  1807. if (flagTips) $hs.tips('Playback speed: ' + player.playbackRate + 'x')
  1808. },
  1809. tuneCurrentTime: function(amount, flagTips) {
  1810. let _amount = +(+amount).toFixed(1);
  1811. let player = $hs.player();
  1812. if (_amount >= 0 || _amount < 0) {} else {
  1813. return;
  1814. }
  1815.  
  1816. let newCurrentTime = player.currentTime + _amount;
  1817. if (newCurrentTime < 0) newCurrentTime = 0;
  1818. if (newCurrentTime > player.duration) newCurrentTime = player.duration;
  1819.  
  1820. let changed = newCurrentTime != player.currentTime && newCurrentTime >= 0 && newCurrentTime <= player.duration;
  1821.  
  1822. if (changed) {
  1823. //player.currentTime = newCurrentTime;
  1824. player.pause();
  1825. let t_ch = (Math.random() / 5 + .75);
  1826. player.currentTime = newCurrentTime * t_ch + player.currentTime * (1.0 - t_ch);
  1827. setTimeout(() => {
  1828. player.play();
  1829. player.currentTime = newCurrentTime;
  1830. }, 33);
  1831. flagTips = (flagTips < 0) ? false : (flagTips > 0) ? true : changed;
  1832. $hs.tips(false);
  1833. if (flagTips) {
  1834. if (_amount > 0) $hs.tips(_amount + ' Sec. Forward', undefined, 3000);
  1835. else $hs.tips(-_amount + ' Sec. Backward', undefined, 3000)
  1836. }
  1837. }
  1838.  
  1839. },
  1840. tuneVolume: function(amount) {
  1841. let _amount = +(+amount).toFixed(2);
  1842.  
  1843. let player = $hs.player()
  1844.  
  1845. let newVol = player.volume + _amount;
  1846. if (newVol < 0) newVol = 0;
  1847. if (newVol > 1) newVol = 1;
  1848. let chVol = player.volume !== newVol && newVol >= 0 && newVol <= 1;
  1849.  
  1850. if (chVol) {
  1851.  
  1852. if (_amount > 0 && player.volume < 1) {
  1853. player.volume = newVol // positive
  1854. } else if (_amount < 0 && player.volume > 0) {
  1855. player.volume = newVol // negative
  1856. }
  1857. $hs.tips(false);
  1858. $hs.tips('Volume: ' + dround(player.volume * 100) + '%', undefined)
  1859. }
  1860. },
  1861. switchPlayStatus: function() {
  1862. let player = $hs.player()
  1863. if (player.paused) {
  1864. player.play()
  1865. if (player._isThisPausedBefore_) {
  1866. $hs.tips(false);
  1867. $hs.tips('Playback resumed', undefined, 2500)
  1868. }
  1869. } else {
  1870. player.pause()
  1871. $hs.tips(false);
  1872. $hs.tips('Playback paused', undefined, 2500)
  1873. }
  1874. },
  1875. tipsClassName: 'html_player_enhance_tips',
  1876. _tips: function(player, str, duration, order) {
  1877.  
  1878.  
  1879. if (!player.getAttribute('_h5player_tips')) $hs.initTips();
  1880.  
  1881. let tipsSelector = '#' + (player.getAttribute('_h5player_tips') || $hs.tipsClassName) //if this attribute still doesnt exist, set it to the base cls name
  1882. let tipsDom = getRoot(player).querySelector(tipsSelector)
  1883. if (!tipsDom) {
  1884. consoleLog('init h5player tips dom error...')
  1885. return false
  1886. }
  1887. $hs.change_layoutBox(tipsDom);
  1888.  
  1889.  
  1890. if (str === false) {
  1891. tipsDom.textContent = '';
  1892. tipsDom.setAttribute('_potTips_', '0')
  1893. } else {
  1894. order = order || 1000
  1895. tipsDom.tipsOrder = tipsDom.tipsOrder || 0;
  1896.  
  1897. let shallDisplay = true
  1898. if (order < tipsDom.tipsOrder && getComputedStyle(tipsDom).opacity > 0) shallDisplay = false
  1899.  
  1900. if (shallDisplay) {
  1901. tipsDom._playerElement = player;
  1902. tipsDom._playerVPID = player.getAttribute('_h5ppid');
  1903. tipsDom._playerBlockElm = $hs.getPlayerBlockElement(player)
  1904.  
  1905. if (duration === undefined) duration = 2000
  1906. tipsDom.textContent = str
  1907. tipsDom.setAttribute('_potTips_', '2')
  1908.  
  1909. if (duration > 0) {
  1910. $ws.requestAnimationFrame(() => tipsDom.setAttribute('_potTips_', '1'))
  1911. } else {
  1912. order = -1;
  1913. }
  1914.  
  1915. tipsDom.tipsOrder = order
  1916.  
  1917. }
  1918.  
  1919. }
  1920.  
  1921. if(window.ResizeObserver){
  1922. //observe not fire twice for the same element.
  1923. if(!$hs.observer_resizeVideos) $hs.observer_resizeVideos=new ResizeObserver(hanlderResizeVideo)
  1924. $hs.observer_resizeVideos.observe(tipsDom._playerBlockElm.parentNode)
  1925. $hs.observer_resizeVideos.observe(tipsDom._playerBlockElm)
  1926. $hs.observer_resizeVideos.observe(player)
  1927. }
  1928. //ensure function called
  1929. window.requestAnimationFrame(()=>$hs.fixNonBoxingVideoTipsPosition(tipsDom, player))
  1930.  
  1931. },
  1932. tips: function(str, duration, order) {
  1933. let player = $hs.player()
  1934. if (!player) {
  1935. consoleLog('h5Player Tips:', str)
  1936. return true
  1937. }
  1938.  
  1939. return $hs._tips(player, str, duration, order)
  1940.  
  1941. },
  1942. listOfTipsDom: [],
  1943. getPotTips: function(arr) {
  1944. const res = [];
  1945. for (const tipsDom of $hs.listOfTipsDom) {
  1946. if (tipsDom && tipsDom.nodeType > 0 && tipsDom.parentNode && tipsDom.ownerDocument) {
  1947. if (tipsDom._playerBlockElm && tipsDom._playerBlockElm.parentNode) {
  1948. const potTipsAttr = tipsDom.getAttribute('_potTips_')
  1949. if (arr.indexOf(potTipsAttr) >= 0) res.push(tipsDom)
  1950. }
  1951. }
  1952. }
  1953. return res;
  1954. },
  1955. initTips: function() {
  1956. /* 設置提示DOM的樣式 */
  1957. let player = $hs.player()
  1958. let shadowRoot = getRoot(player);
  1959. let doc = player.ownerDocument;
  1960. //console.log((document.documentElement.qq=player),shadowRoot,'xax')
  1961. let parentNode = player.parentNode
  1962. let tcn = player.getAttribute('_h5player_tips') || ($hs.tipsClassName + '_' + (+new Date));
  1963. player.setAttribute('_h5player_tips', tcn)
  1964. if (shadowRoot.querySelector('#' + tcn)) return false;
  1965.  
  1966. if (!shadowRoot._onceAddedCSS) {
  1967. shadowRoot._onceAddedCSS = true;
  1968.  
  1969. let cssStyle = `
  1970. [_potTips_="1"]{
  1971. animation: 2s linear 0s normal forwards 1 delayHide;
  1972. }
  1973. [_potTips_="0"]{
  1974. opacity:0; transform:translate(-9999px);
  1975. }
  1976. [_potTips_="2"]{
  1977. opacity:.95; transform: translate(0,0);
  1978. }
  1979.  
  1980. @keyframes delayHide{
  1981. 0%, 99% { opacity:0.95; transform: translate(0,0); }
  1982. 100% { opacity:0; transform:translate(-9999px); }
  1983. }
  1984. ` + `
  1985. [_potTips_]{
  1986. font-weight: bold !important;
  1987. position: absolute !important;
  1988. z-index: 999 !important;
  1989. font-size: ${$hs.fontSize || 16}px !important;
  1990. padding: 0px !important;
  1991. border:none !important;
  1992. background: rgba(0,0,0,0) !important;
  1993. color:#738CE6 !important;
  1994. text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
  1995. top: 50%;
  1996. left: 50%;
  1997. max-width:500px;max-height:50px;
  1998. border-radius:3px;
  1999. font-family: 'microsoft yahei', Verdana, Geneva, sans-serif;
  2000. pointer-events: none;
  2001. }
  2002. body div[_potTips_]{
  2003. -webkit-user-select: none !important;
  2004. -moz-user-select: none !important;
  2005. -ms-user-select: none !important;
  2006. user-select: none !important;
  2007. -webkit-touch-callout: none !important;
  2008. -webkit-user-select: none !important;
  2009. -khtml-user-drag: none !important;
  2010. -khtml-user-select: none !important;
  2011. -moz-user-select: none !important;
  2012. -moz-user-select: -moz-none !important;
  2013. -ms-user-select: none !important;
  2014. user-select: none !important;
  2015. }
  2016. `.replace(/\r\n/g, '');
  2017.  
  2018. let cssContainer = (shadowRoot.querySelector('head') || shadowRoot.querySelector('html') || document.documentElement);
  2019.  
  2020. domTool.addStyle(cssStyle, cssContainer);
  2021.  
  2022. }
  2023.  
  2024. let tipsDom = doc.createElement('div')
  2025.  
  2026. tipsDom.addEventListener(crossBrowserTransition('animation'), function(e) {
  2027. if (this.getAttribute('_potTips_') == '1') {
  2028. this.setAttribute('_potTips_', '0')
  2029. }
  2030. })
  2031.  
  2032. tipsDom.id = tcn;
  2033. tipsDom.setAttribute('_potTips_', '0');
  2034. $hs.listOfTipsDom.push(tipsDom);
  2035. $hs.change_layoutBox(tipsDom);
  2036.  
  2037. return true;
  2038. },
  2039.  
  2040. responsiveSizing: function(container, elm) {
  2041.  
  2042. let gcssP = getComputedStyle(container);
  2043.  
  2044. let gcssE = getComputedStyle(elm);
  2045.  
  2046. //console.log(gcssE.left,gcssP.width)
  2047. let elmBound = {
  2048. left: parseFloat(gcssE.left) / parseFloat(gcssP.width),
  2049. width: parseFloat(gcssE.width) / parseFloat(gcssP.width),
  2050. top: parseFloat(gcssE.top) / parseFloat(gcssP.height),
  2051. height: parseFloat(gcssE.height) / parseFloat(gcssP.height)
  2052. };
  2053.  
  2054. let elm00 = [elmBound.left, elmBound.top];
  2055. let elm01 = [elmBound.left + elmBound.width, elmBound.top];
  2056. let elm10 = [elmBound.left, elmBound.top + elmBound.height];
  2057. let elm11 = [elmBound.left + elmBound.width, elmBound.top + elmBound.height];
  2058.  
  2059. return {
  2060. elm00,
  2061. elm01,
  2062. elm10,
  2063. elm11,
  2064. plw: elmBound.width,
  2065. plh: elmBound.height
  2066. };
  2067.  
  2068. },
  2069.  
  2070. fixNonBoxingVideoTipsPosition: function(tipsDom, player) {
  2071.  
  2072. if (!tipsDom || !player) return;
  2073.  
  2074. let ct = $hs.getCommonContainer(tipsDom, player)
  2075.  
  2076. if (!ct) return;
  2077.  
  2078. //relative
  2079.  
  2080. let elm00 = $hs.responsiveSizing(ct, player).elm00;
  2081.  
  2082. if (isNaN(elm00[0]) || isNaN(elm00[1])) {
  2083.  
  2084. [tipsDom.style.left, tipsDom.style.top] = [player.style.left, player.style.top];
  2085. //eg auto
  2086. } else {
  2087.  
  2088. let rlm00 = elm00.map(t => (t * 100).toFixed(2) + '%');
  2089. [tipsDom.style.left, tipsDom.style.top] = rlm00;
  2090.  
  2091. }
  2092.  
  2093. // absolute
  2094.  
  2095. let _offset = {
  2096. left: 10,
  2097. top: 15
  2098. };
  2099.  
  2100. let customOffset = {
  2101. left: _offset.left,
  2102. top: _offset.top
  2103. };
  2104. let p = tipsDom.getBoundingClientRect();
  2105. let q = player.getBoundingClientRect();
  2106. let currentPos = [p.left, p.top];
  2107.  
  2108. let targetPos = [q.left + player.offsetWidth * 0 + customOffset.left, q.top + player.offsetHeight * 0 + customOffset.top];
  2109.  
  2110. let mL = +tipsDom.style.marginLeft.replace('px', '') || 0;
  2111. if (isNaN(mL)) mL = 0;
  2112. let mT = +tipsDom.style.marginTop.replace('px', '') || 0;
  2113. if (isNaN(mT)) mT = 0;
  2114.  
  2115. let z1 = -(currentPos[0] - targetPos[0]);
  2116. let z2 = -(currentPos[1] - targetPos[1]);
  2117.  
  2118. if (z1 || z2) {
  2119.  
  2120. let y1 = z1 + mL;
  2121. let y2 = z2 + mT;
  2122.  
  2123. tipsDom.style.marginLeft = y1 + 'px';
  2124. tipsDom.style.marginTop = y2 + 'px';
  2125.  
  2126. }
  2127. },
  2128.  
  2129. playerTrigger: function(player, event) {
  2130. if (!player || !event) return
  2131. const pCode = event.code;
  2132. let keyAsm = (event.shiftKey ? SHIFT : 0) | ((event.ctrlKey || event.metaKey) ? CTRL : 0) | (event.altKey ? ALT : 0);
  2133.  
  2134.  
  2135. let vpid = player.getAttribute('_h5ppid') || null;
  2136. if (!vpid) return;
  2137. let playerConf = playerConfs[vpid]
  2138. if (!playerConf) return;
  2139.  
  2140. //shift + key
  2141. if (keyAsm == SHIFT) {
  2142. // 網頁FULLSCREEN
  2143. if (pCode === 'Enter') {
  2144. $hs.callFullScreenBtn()
  2145. return TERMINATE
  2146. } else if (pCode == 'KeyF') {
  2147. //change unsharpen filter
  2148.  
  2149. let resList = ["unsharpen3_05", "unsharpen3_10", "unsharpen5_05", "unsharpen5_10", "unsharpen9_05", "unsharpen9_10"]
  2150. let res = (prompt("Enter the unsharpen mask\n(" + resList.map(x => '"' + x + '"').join(', ') + ")", "unsharpen9_05") || "").toLowerCase();
  2151. if (resList.indexOf(res) < 0) res = ""
  2152. GM_setValue("unsharpen_mask", res)
  2153. for (const el of document.querySelectorAll('video[_h5p_uid_encrypted]')) {
  2154. if (el.style.filter == "" || el.style.filter) {
  2155. let filterStr1 = el.style.filter.replace(/\s*url\(\"#_h5p_unsharpen[\d\_]+\"\)/, '');
  2156. let filterStr2 = (res.length > 0 ? ' url("#_h5p_' + res + '")' : '')
  2157. el.style.filter = filterStr1 + filterStr2;
  2158. }
  2159. }
  2160. return TERMINATE
  2161.  
  2162. }
  2163. // 進入或退出畫中畫模式
  2164. else if (pCode == 'KeyP') {
  2165. $hs.pictureInPicture(player)
  2166.  
  2167. return TERMINATE
  2168. } else if (pCode == 'KeyR') {
  2169. if (player._h5player_lastrecord_ !== null && (player._h5player_lastrecord_ >= 0 || player._h5player_lastrecord_ <= 0)) {
  2170. $hs.setPlayProgress(player, player._h5player_lastrecord_)
  2171.  
  2172. return TERMINATE
  2173. }
  2174.  
  2175. } else if (pCode == 'KeyO') {
  2176. let _debug_h5p_logging_ch = false;
  2177. try {
  2178. Store._setItem('_h5_player_sLogging_', 1 - Store._getItem('_h5_player_sLogging_'))
  2179. _debug_h5p_logging_ = +Store._getItem('_h5_player_sLogging_') > 0;
  2180. _debug_h5p_logging_ch = true;
  2181. } catch (e) {
  2182.  
  2183. }
  2184. consoleLogF('_debug_h5p_logging_', !!_debug_h5p_logging_, 'changed', _debug_h5p_logging_ch)
  2185.  
  2186. if (_debug_h5p_logging_ch) {
  2187.  
  2188. return TERMINATE
  2189. }
  2190. } else if (pCode == 'KeyT') {
  2191. if (/^blob/i.test(player.currentSrc)) {
  2192. alert(`The current video is ${player.currentSrc}\nSorry, it cannot be opened in PotPlayer.`);
  2193. } else {
  2194. let confirm_res = confirm(`The current video is ${player.currentSrc}\nDo you want to open it in PotPlayer?`);
  2195. if (confirm_res) window.open('potplayer://' + player.currentSrc, '_blank');
  2196. }
  2197. return TERMINATE
  2198. }
  2199.  
  2200.  
  2201.  
  2202. let videoScale = playerConf.vFactor;
  2203.  
  2204. function tipsForVideoScaling() {
  2205.  
  2206. playerConf.vFactor = +videoScale.toFixed(1);
  2207.  
  2208. playerConf.cssTransform();
  2209. let tipsMsg = `視頻縮放率:${ +(videoScale * 100).toFixed(2) }%`
  2210. if (playerConf.translate.x) {
  2211. tipsMsg += `,水平位移:${playerConf.translate.x}px`
  2212. }
  2213. if (playerConf.translate.y) {
  2214. tipsMsg += `,垂直位移:${playerConf.translate.y}px`
  2215. }
  2216. $hs.tips(false);
  2217. $hs.tips(tipsMsg)
  2218.  
  2219.  
  2220. }
  2221.  
  2222. // 視頻畫面縮放相關事件
  2223.  
  2224. switch (pCode) {
  2225. // shift+X:視頻縮小 -0.1
  2226. case 'KeyX':
  2227. videoScale -= 0.1
  2228. if (videoScale < 0.1) videoScale = 0.1;
  2229. tipsForVideoScaling();
  2230. return TERMINATE
  2231. break
  2232. // shift+C:視頻放大 +0.1
  2233. case 'KeyC':
  2234. videoScale += 0.1
  2235. if (videoScale > 16) videoScale = 16;
  2236. tipsForVideoScaling();
  2237. return TERMINATE
  2238. break
  2239. // shift+Z:視頻恢復正常大小
  2240. case 'KeyZ':
  2241. videoScale = 1.0
  2242. playerConf.translate.x=0;
  2243. playerConf.translate.y=0;
  2244. tipsForVideoScaling();
  2245. return TERMINATE
  2246. break
  2247. case 'ArrowRight':
  2248. playerConf.translate.x += 10
  2249. tipsForVideoScaling();
  2250. return TERMINATE
  2251. break
  2252. case 'ArrowLeft':
  2253. playerConf.translate.x -= 10
  2254. tipsForVideoScaling();
  2255. return TERMINATE
  2256. break
  2257. case 'ArrowUp':
  2258. playerConf.translate.y -= 10
  2259. tipsForVideoScaling();
  2260. return TERMINATE
  2261. break
  2262. case 'ArrowDown':
  2263. playerConf.translate.y += 10
  2264. tipsForVideoScaling();
  2265. return TERMINATE
  2266. break
  2267.  
  2268. }
  2269.  
  2270. }
  2271. // 防止其它無關組合鍵衝突
  2272. if (!keyAsm) {
  2273. let kControl = null
  2274. let newPBR, oldPBR, nv;
  2275. switch (pCode) {
  2276. // 方向鍵右→:快進3秒
  2277. case 'ArrowRight':
  2278. $hs.tuneCurrentTime($hs.skipStep);
  2279. return TERMINATE;
  2280. break;
  2281. // 方向鍵左←:後退3秒
  2282. case 'ArrowLeft':
  2283. $hs.tuneCurrentTime(-$hs.skipStep);
  2284. return TERMINATE;
  2285. break;
  2286. // 方向鍵上↑:音量升高 1%
  2287. case 'ArrowUp':
  2288. if ((player.muted && player.volume === 0) && player._volume > 0) {
  2289.  
  2290. player.muted = false;
  2291. player.volume = player._volume;
  2292. } else if (player.muted && (player.volume > 0 || !player._volume)) {
  2293. player.muted = false;
  2294. }
  2295. $hs.tuneVolume(0.01);
  2296. return TERMINATE;
  2297. break;
  2298. // 方向鍵下↓:音量降低 1%
  2299. case 'ArrowDown':
  2300.  
  2301. if ((player.muted && player.volume === 0) && player._volume > 0) {
  2302.  
  2303. player.muted = false;
  2304. player.volume = player._volume;
  2305. } else if (player.muted && (player.volume > 0 || !player._volume)) {
  2306. player.muted = false;
  2307. }
  2308. $hs.tuneVolume(-0.01);
  2309. return TERMINATE;
  2310. break;
  2311. // 空格鍵:暫停/播放
  2312. case 'Space':
  2313. $hs.switchPlayStatus();
  2314. return TERMINATE;
  2315. break;
  2316. // 按鍵X:減速播放 -0.1
  2317. case 'KeyX':
  2318. if (player.playbackRate > 0) {
  2319. $hs.tips(false);
  2320. $hs.setPlaybackRate(player.playbackRate - 0.1);
  2321. return TERMINATE
  2322. }
  2323. break;
  2324. // 按鍵C:加速播放 +0.1
  2325. case 'KeyC':
  2326. if (player.playbackRate < 16) {
  2327. $hs.tips(false);
  2328. $hs.setPlaybackRate(player.playbackRate + 0.1);
  2329. return TERMINATE
  2330. }
  2331.  
  2332. break;
  2333. // 按鍵Z:正常速度播放
  2334. case 'KeyZ':
  2335. $hs.tips(false);
  2336. oldPBR = player.playbackRate;
  2337. if (oldPBR != 1.0) {
  2338. player._playbackRate_z = oldPBR;
  2339. newPBR = 1.0;
  2340. } else if (player._playbackRate_z != 1.0) {
  2341. newPBR = player._playbackRate_z || 1.0;
  2342. player._playbackRate_z = 1.0;
  2343. } else {
  2344. newPBR = 1.0
  2345. player._playbackRate_z = 1.0;
  2346. }
  2347. $hs.setPlaybackRate(newPBR, 1)
  2348. return TERMINATE
  2349. break;
  2350. // 按鍵F:下一幀
  2351. case 'KeyF':
  2352. if (window.location.hostname === 'www.netflix.com') return /* netflix 的F鍵是FULLSCREEN的意思 */
  2353. $hs.tips(false);
  2354. if (!player.paused) player.pause()
  2355. player.currentTime += +(1 / playerConf.fps)
  2356. $hs.tips('Jump to: Next frame')
  2357. return TERMINATE
  2358. break;
  2359. // 按鍵D:上一幀
  2360. case 'KeyD':
  2361. $hs.tips(false);
  2362. if (!player.paused) player.pause()
  2363. player.currentTime -= +(1 / playerConf.fps)
  2364. $hs.tips('Jump to: Previous frame')
  2365. return TERMINATE
  2366. break;
  2367. // 按鍵E:亮度增加%
  2368. case 'KeyE':
  2369. $hs.tips(false);
  2370. nv = playerConf.setFilter('brightness', (v) => v + 0.1);
  2371. $hs.tips('Brightness: ' + dround(nv * 100) + '%')
  2372. return TERMINATE
  2373. break;
  2374. // 按鍵W:亮度減少%
  2375. case 'KeyW':
  2376. $hs.tips(false);
  2377. nv = playerConf.setFilter('brightness', (v) => v > 0.1 ? v - 0.1 : 0);
  2378. $hs.tips('Brightness: ' + dround(nv * 100) + '%')
  2379. return TERMINATE
  2380. break;
  2381. // 按鍵T:對比度增加%
  2382. case 'KeyT':
  2383. $hs.tips(false);
  2384. nv = playerConf.setFilter('contrast', (v) => v + 0.1);
  2385. $hs.tips('Contrast: ' + dround(nv * 100) + '%')
  2386. return TERMINATE
  2387. break;
  2388. // 按鍵R:對比度減少%
  2389. case 'KeyR':
  2390. $hs.tips(false);
  2391. nv = playerConf.setFilter('contrast', (v) => v > 0.1 ? v - 0.1 : 0);
  2392. $hs.tips('Contrast: ' + dround(nv * 100) + '%')
  2393. return TERMINATE
  2394. break;
  2395. // 按鍵U:飽和度增加%
  2396. case 'KeyU':
  2397. $hs.tips(false);
  2398. nv = playerConf.setFilter('saturate', (v) => v + 0.1);
  2399. $hs.tips('Saturate: ' + dround(nv * 100) + '%')
  2400. return TERMINATE
  2401. break;
  2402. // 按鍵Y:飽和度減少%
  2403. case 'KeyY':
  2404. $hs.tips(false);
  2405. nv = playerConf.setFilter('saturate', (v) => v > 0.1 ? v - 0.1 : 0);
  2406. $hs.tips('Saturate: ' + dround(nv * 100) + '%')
  2407. return TERMINATE
  2408. break;
  2409. // 按鍵O:色相增加 1 度
  2410. case 'KeyO':
  2411. $hs.tips(false);
  2412. nv = playerConf.setFilter('hue-rotate', (v) => v + 1);
  2413. $hs.tips('Hue: ' + nv + ' deg')
  2414. return TERMINATE
  2415. break;
  2416. // 按鍵I:色相減少 1 度
  2417. case 'KeyI':
  2418. $hs.tips(false);
  2419. nv = playerConf.setFilter('hue-rotate', (v) => v - 1);
  2420. $hs.tips('Hue: ' + nv + ' deg')
  2421. return TERMINATE
  2422. break;
  2423. // 按鍵K:模糊增加 0.1 px
  2424. case 'KeyK':
  2425. $hs.tips(false);
  2426. nv = playerConf.setFilter('blur', (v) => v + 0.1);
  2427. $hs.tips('Blur: ' + nv + ' px')
  2428. return TERMINATE
  2429. break;
  2430. // 按鍵J:模糊減少 0.1 px
  2431. case 'KeyJ':
  2432. $hs.tips(false);
  2433. nv = playerConf.setFilter('blur', (v) => v > 0.1 ? v - 0.1 : 0);
  2434. $hs.tips('Blur: ' + nv + ' px')
  2435. return TERMINATE
  2436. break;
  2437. // 按鍵Q:圖像復位
  2438. case 'KeyQ':
  2439. $hs.tips(false);
  2440. playerConf.filterReset();
  2441. $hs.tips('Video Filter Reset')
  2442. return TERMINATE
  2443. break;
  2444. // 按鍵S:畫面旋轉 90 度
  2445. case 'KeyS':
  2446. $hs.tips(false);
  2447. playerConf.rotate += 90
  2448. if (playerConf.rotate % 360 === 0) playerConf.rotate = 0;
  2449. if(!playerConf.videoHeight||!playerConf.videoWidth){
  2450. playerConf.videoWidth = playerConf.domElement.videoWidth;
  2451. playerConf.videoHeight = playerConf.domElement.videoHeight;
  2452. }
  2453. if (playerConf.videoWidth>0 && playerConf.videoHeight>0) {
  2454.  
  2455.  
  2456. if ((playerConf.rotate % 180) == 90) {
  2457. playerConf.mFactor = playerConf.videoHeight / playerConf.videoWidth;
  2458. } else {
  2459. playerConf.mFactor = 1.0;
  2460. }
  2461.  
  2462.  
  2463. playerConf.cssTransform();
  2464.  
  2465. $hs.tips('Rotation:' + playerConf.rotate + ' deg')
  2466.  
  2467. }
  2468.  
  2469. return TERMINATE
  2470. break;
  2471. // 按鍵迴車,進入FULLSCREEN
  2472. case 'Enter':
  2473. //t.callFullScreenBtn();
  2474. break;
  2475. case 'KeyN':
  2476. $hs.pictureInPicture(player);
  2477. return TERMINATE
  2478. break;
  2479. case 'KeyM':
  2480. //console.log('m!', player.volume,player._volume)
  2481.  
  2482. if (player.volume >= 0) {
  2483.  
  2484. if (!player.volume || player.muted) {
  2485.  
  2486. let newVol = player.volume || player._volume || 0.5;
  2487. if (player.volume !== newVol) {
  2488. player.volume = newVol;
  2489. }
  2490. player.muted = false;
  2491. $hs.tips(false);
  2492. $hs.tips('Mute: Off', undefined);
  2493.  
  2494. } else {
  2495.  
  2496. player._volume = player.volume;
  2497. player._volume_p = player.volume;
  2498. //player.volume = 0;
  2499. player.muted = true;
  2500. $hs.tips(false);
  2501. $hs.tips('Mute: On', undefined);
  2502.  
  2503. }
  2504.  
  2505. }
  2506.  
  2507. return TERMINATE
  2508. break;
  2509. default:
  2510. // 按1-4設置播放速度 49-52;97-100
  2511. let numKey = +(event.key)
  2512.  
  2513. if (numKey >= 1 && numKey <= 4) {
  2514. $hs.tips(false);
  2515. $hs.setPlaybackRate(numKey, 1)
  2516. return TERMINATE
  2517. }
  2518. }
  2519.  
  2520. }
  2521. },
  2522.  
  2523. handlerPlayerMouseMove: function(e) {
  2524. let player = $hs.player();
  2525. let rootNode = getRoot(player);
  2526.  
  2527. if (rootNode.pointerLockElement != player) {
  2528. player.removeEventListener('mousemove', $hs.handlerPlayerMouseMove)
  2529. return;
  2530. }
  2531.  
  2532. let movementX = e.movementX || e.mozMovementX || e.webkitMovementX || 0,
  2533. movementY = e.movementY || e.mozMovementY || e.webkitMovementY || 0;
  2534.  
  2535. player.__xyOffset.x += movementX
  2536. player.__xyOffset.y += movementY
  2537. let ld = Math.sqrt(screen.width * screen.width + screen.height * screen.height) * .1
  2538. let md = Math.sqrt(player.__xyOffset.x * player.__xyOffset.x + player.__xyOffset.y * player.__xyOffset.y);
  2539. if (md > ld) $hs.playerActionLeave();
  2540.  
  2541. },
  2542.  
  2543. playerActionEnter: function() {
  2544. let player = $hs.player();
  2545.  
  2546. if (player) {
  2547. player.__requestPointerLock__();
  2548. player.__xyOffset = {
  2549. x: 0,
  2550. y: 0
  2551. };
  2552. player.addEventListener('mousemove', $hs.handlerPlayerMouseMove)
  2553. }
  2554. },
  2555.  
  2556. playerActionLeave: function() {
  2557. let player = $hs.player();
  2558. if (player) player.removeEventListener('mousemove', $hs.handlerPlayerMouseMove)
  2559. document.__exitPointerLock__();
  2560. },
  2561.  
  2562. /* 按鍵響應方法 */
  2563. handlerRootKeyDownEvent: function(event) {
  2564. if ($hs.intVideoInitCount > 0) {} else {
  2565. return;
  2566. }
  2567. // DOM Standard - either .key or .code
  2568. // Here we adopt .code (physical layout)
  2569.  
  2570. let pCode = event.code;
  2571. if (typeof pCode != 'string') return;
  2572.  
  2573. let player = $hs.player()
  2574.  
  2575. if (!player) return; // no video tag
  2576.  
  2577. let rootNode = getRoot(player);
  2578.  
  2579. let keyAsm = (event.shiftKey ? SHIFT : 0) | ((event.ctrlKey || event.metaKey) ? CTRL : 0) | (event.altKey ? ALT : 0);
  2580.  
  2581. if (!keyAsm && pCode == 'Escape' && (document.fullscreenElement || rootNode.pointerLockElement)) {
  2582. setTimeout(() => {
  2583. if (document.fullscreenElement) {
  2584. document.exitFullscreen();
  2585. } else if (document.pointerLockElement) {
  2586. $hs.playerActionLeave();
  2587. }
  2588. }, 700);
  2589. return;
  2590. }
  2591.  
  2592. if (isInOperation(event.target)) return;
  2593.  
  2594. //console.log('K01')
  2595.  
  2596. /* 切換插件的可用狀態 */
  2597. // Shift-`
  2598. if (keyAsm == SHIFT && pCode == 'Backquote') {
  2599. $hs.enable = !$hs.enable;
  2600. $hs.tips(false);
  2601. if ($hs.enable) {
  2602. $hs.tips('啟用h5Player插件')
  2603. } else {
  2604. $hs.tips('禁用h5Player插件')
  2605. }
  2606. // 阻止事件冒泡
  2607. event.stopPropagation()
  2608. event.preventDefault()
  2609. return false
  2610. }
  2611. if (!$hs.enable) {
  2612. consoleLog('h5Player 已禁用~')
  2613. return false
  2614. }
  2615.  
  2616. /* 非全局模式下,不聚焦則不執行快捷鍵的操作 */
  2617.  
  2618. if (!keyAsm && pCode == 'Enter' && !isInOperation()) { //not NumberpadEnter
  2619. if (!rootNode.pointerLockElement && !document.fullscreenElement) {
  2620. if (rootNode.pointerLockElement != player) {
  2621. $hs.playerActionEnter();
  2622.  
  2623. // 阻止事件冒泡
  2624. event.stopPropagation()
  2625. event.preventDefault()
  2626. return false
  2627. }
  2628. } else if (rootNode.pointerLockElement && !document.fullscreenElement) {
  2629. $hs.playerActionLeave();
  2630.  
  2631. // 阻止事件冒泡
  2632. event.stopPropagation()
  2633. event.preventDefault()
  2634. return false
  2635. } else if (document.fullscreenElement) {
  2636. document.exitFullscreen();
  2637.  
  2638. // 阻止事件冒泡
  2639. event.stopPropagation()
  2640. event.preventDefault()
  2641. return false
  2642. }
  2643. }
  2644.  
  2645. let hv = (elm) => (elm && (elm == player || elm.contains(player)) ? elm : null);
  2646.  
  2647. let _checkingPass;
  2648.  
  2649. let plm = null;
  2650. if (rootNode.activeElement && !rootNode.pointerLockElement && !document.fullscreenElement) {
  2651. // the active element may or may not contains the player
  2652. // but the player box (player->parent->parent->...) shall contains the active element (and the player)
  2653. // so if the active element is inside the layoutbox, okay!
  2654. // ps. layoutbox may be much larger than the activeelement, then it is not overlapping case.
  2655.  
  2656. plm = rootNode.activeElement
  2657.  
  2658. //console.log('activeElement', plm)
  2659. _checkingPass = $hs.isInActiveMode(rootNode.activeElement, player);
  2660. } else {
  2661. plm = hv(rootNode.pointerLockElement) || hv(document.fullscreenElement)
  2662. _checkingPass = !!plm
  2663. }
  2664.  
  2665.  
  2666. if (_checkingPass) {
  2667.  
  2668. if (isInOperation()) return;
  2669.  
  2670.  
  2671. let res = $hs.playerTrigger(player, event)
  2672. if (res == TERMINATE) {
  2673. event.stopPropagation()
  2674. event.preventDefault()
  2675. return false
  2676. }
  2677.  
  2678. }
  2679. },
  2680. /* 設置播放進度 */
  2681. setPlayProgress: function(player, curTime) {
  2682. if (!player) return
  2683. if (!curTime || Number.isNaN(curTime)) return
  2684. player.currentTime = curTime
  2685. if (curTime > 3) {
  2686. $hs.tips(false);
  2687. $hs.tips(`Playback Jumps to ${$hs.toolFormatCT(curTime)}`)
  2688. if (player.paused) player.play();
  2689. }
  2690. }
  2691. }
  2692.  
  2693. function makeFilter(arr, k) {
  2694. let res = ""
  2695. for (const e of arr) {
  2696. for (const d of e) {
  2697. res += " " + (1.0 * d * k).toFixed(9)
  2698. }
  2699. }
  2700. return res.trim()
  2701. }
  2702.  
  2703. function _add_filter(rootElm) {
  2704. let rootView = null;
  2705. if (rootElm && rootElm.nodeType > 0) {
  2706. while (rootElm.parentNode && rootElm.parentNode.nodeType === 1) rootElm = rootElm.parentNode;
  2707. rootView = rootElm.querySelector('body') || rootElm;
  2708. } else {
  2709. return;
  2710. }
  2711.  
  2712. if (rootView && rootView.querySelector && !rootView.querySelector('#_h5player_section_')) {
  2713.  
  2714. let svgFilterElm = document.createElement('section')
  2715. svgFilterElm.style.position = 'fixed';
  2716. svgFilterElm.style.left = '-999px';
  2717. svgFilterElm.style.width = '1px';
  2718. svgFilterElm.style.top = '-999px';
  2719. svgFilterElm.style.height = '1px';
  2720. svgFilterElm.id = '_h5player_section_'
  2721. let svgXML = `
  2722. <svg id='_h5p_image' version="1.1" xmlns="http://www.w3.org/2000/svg">
  2723. <defs>
  2724. <filter id="_h5p_sharpen1">
  2725. <feConvolveMatrix filterRes="100 100" style="color-interpolation-filters:sRGB" order="3" kernelMatrix="` + `
  2726. -0.3 -0.3 -0.3
  2727. -0.3 3.4 -0.3
  2728. -0.3 -0.3 -0.3`.replace(/[\n\r]+/g, ' ').trim() + `" preserveAlpha="true"/>
  2729. </filter>
  2730. <filter id="_h5p_unsharpen1">
  2731. <feConvolveMatrix style="color-interpolation-filters:sRGB;color-interpolation: sRGB;" order="5" kernelMatrix="` +
  2732. makeFilter([
  2733. [1, 4, 6, 4, 1],
  2734. [4, 16, 24, 16, 4],
  2735. [6, 24, -476, 24, 6],
  2736. [4, 16, 24, 16, 4],
  2737. [1, 4, 6, 4, 1]
  2738. ], -1 / 256) + `" preserveAlpha="false"/>
  2739. </filter>
  2740. <filter id="_h5p_unsharpen3_05">
  2741. <feConvolveMatrix style="color-interpolation-filters:sRGB;color-interpolation: sRGB;" order="3" kernelMatrix="` +
  2742. makeFilter(
  2743. [
  2744. [0.025, 0.05, 0.025],
  2745. [0.05, -1.1, 0.05],
  2746. [0.025, 0.05, 0.025]
  2747. ], -1 / .8) + `" preserveAlpha="false"/>
  2748. </filter>
  2749. <filter id="_h5p_unsharpen3_10">
  2750. <feConvolveMatrix style="color-interpolation-filters:sRGB;color-interpolation: sRGB;" order="3" kernelMatrix="` +
  2751. makeFilter(
  2752. [
  2753. [0.05, 0.1, 0.05],
  2754. [0.1, -1.4, 0.1],
  2755. [0.05, 0.1, 0.05]
  2756. ], -1 / .8) + `" preserveAlpha="false"/>
  2757. </filter>
  2758. <filter id="_h5p_unsharpen5_05">
  2759. <feConvolveMatrix style="color-interpolation-filters:sRGB;color-interpolation: sRGB;" order="5" kernelMatrix="` +
  2760. makeFilter(
  2761. [
  2762. [0.025, 0.1, 0.15, 0.1, 0.025],
  2763. [0.1, 0.4, 0.6, 0.4, 0.1],
  2764. [0.15, 0.6, -18.3, 0.6, 0.15],
  2765. [0.1, 0.4, 0.6, 0.4, 0.1],
  2766. [0.025, 0.1, 0.15, 0.1, 0.025]
  2767. ], -1 / 12.8) + `" preserveAlpha="false"/>
  2768. </filter>
  2769. <filter id="_h5p_unsharpen5_10">
  2770. <feConvolveMatrix style="color-interpolation-filters:sRGB;color-interpolation: sRGB;" order="5" kernelMatrix="` +
  2771. makeFilter(
  2772. [
  2773. [0.05, 0.2, 0.3, 0.2, 0.05],
  2774. [0.2, 0.8, 1.2, 0.8, 0.2],
  2775. [0.3, 1.2, -23.8, 1.2, 0.3],
  2776. [0.2, 0.8, 1.2, 0.8, 0.2],
  2777. [0.05, 0.2, 0.3, 0.2, 0.05]
  2778. ], -1 / 12.8) + `" preserveAlpha="false"/>
  2779. </filter>
  2780. <filter id="_h5p_unsharpen9_05">
  2781. <feConvolveMatrix style="color-interpolation-filters:sRGB;color-interpolation: sRGB;" order="9" kernelMatrix="` +
  2782. makeFilter(
  2783. [
  2784. [0.025, 0.2, 0.7, 1.4, 1.75, 1.4, 0.7, 0.2, 0.025],
  2785. [0.2, 1.6, 5.6, 11.2, 14, 11.2, 5.6, 1.6, 0.2],
  2786. [0.7, 5.6, 19.6, 39.2, 49, 39.2, 19.6, 5.6, 0.7],
  2787. [1.4, 11.2, 39.2, 78.4, 98, 78.4, 39.2, 11.2, 1.4],
  2788. [1.75, 14, 49, 98, -4792.7, 98, 49, 14, 1.75],
  2789. [1.4, 11.2, 39.2, 78.4, 98, 78.4, 39.2, 11.2, 1.4],
  2790. [0.7, 5.6, 19.6, 39.2, 49, 39.2, 19.6, 5.6, 0.7],
  2791. [0.2, 1.6, 5.6, 11.2, 14, 11.2, 5.6, 1.6, 0.2],
  2792. [0.025, 0.2, 0.7, 1.4, 1.75, 1.4, 0.7, 0.2, 0.025]
  2793. ], -1 / 3276.8) + `" preserveAlpha="false"/>
  2794. </filter>
  2795. <filter id="_h5p_unsharpen9_10">
  2796. <feConvolveMatrix style="color-interpolation-filters:sRGB;color-interpolation: sRGB;" order="9" kernelMatrix="` +
  2797. makeFilter(
  2798. [
  2799. [0.05, 0.4, 1.4, 2.8, 3.5, 2.8, 1.4, 0.4, 0.05],
  2800. [0.4, 3.2, 11.2, 22.4, 28, 22.4, 11.2, 3.2, 0.4],
  2801. [1.4, 11.2, 39.2, 78.4, 98, 78.4, 39.2, 11.2, 1.4],
  2802. [2.8, 22.4, 78.4, 156.8, 196, 156.8, 78.4, 22.4, 2.8],
  2803. [3.5, 28, 98, 196, -6308.6, 196, 98, 28, 3.5],
  2804. [2.8, 22.4, 78.4, 156.8, 196, 156.8, 78.4, 22.4, 2.8],
  2805. [1.4, 11.2, 39.2, 78.4, 98, 78.4, 39.2, 11.2, 1.4],
  2806. [0.4, 3.2, 11.2, 22.4, 28, 22.4, 11.2, 3.2, 0.4],
  2807. [0.05, 0.4, 1.4, 2.8, 3.5, 2.8, 1.4, 0.4, 0.05]
  2808. ], -1 / 3276.8) + `" preserveAlpha="false"/>
  2809. </filter>
  2810. <filter id="_h5p_grey1">
  2811. <feColorMatrix values="0.3333 0.3333 0.3333 0 0
  2812. 0.3333 0.3333 0.3333 0 0
  2813. 0.3333 0.3333 0.3333 0 0
  2814. 0 0 0 1 0"/>
  2815. <feColorMatrix type="saturate" values="0" />
  2816. </filter>
  2817. </defs>
  2818. </svg>
  2819. `;
  2820.  
  2821. svgFilterElm.innerHTML = svgXML.replace(/[\r\n\s]+/g, ' ').trim();
  2822.  
  2823. rootView.appendChild(svgFilterElm);
  2824. }
  2825.  
  2826. }
  2827.  
  2828. /**
  2829. * 某些網頁用了attachShadow closed mode,需要open才能獲取video標籤,例如百度雲盤
  2830. * 解決參考:
  2831. * https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=zh-cn#closed
  2832. * https://stackoverflow.com/questions/54954383/override-element-prototype-attachshadow-using-chrome-extension
  2833. */
  2834.  
  2835. const initForShadowRoot = async (shadowRoot) => {
  2836. try {
  2837. if (shadowRoot && shadowRoot.nodeType > 0 && 'querySelectorAll' in shadowRoot) {
  2838. $hs.bindDocEvents(shadowRoot);
  2839.  
  2840. captureVideoEvents(shadowRoot);
  2841. shadowRoots.push(shadowRoot)
  2842. }
  2843. } catch (e) {
  2844. console.log('h5Player: initForShadowRoot failed')
  2845. }
  2846. }
  2847.  
  2848. function hackAttachShadow() { // attachShadow - DOM Standard
  2849.  
  2850. let _prototype_ = window && window.HTMLElement ? window.HTMLElement.prototype : null;
  2851. if (_prototype_ && typeof _prototype_.attachShadow == 'function') {
  2852.  
  2853. let _attachShadow = _prototype_.attachShadow
  2854.  
  2855. hackAttachShadow = null
  2856. _prototype_.attachShadow = function() {
  2857. let arg = [...arguments];
  2858. if (arg[0] && arg[0].mode) arg[0].mode = 'open';
  2859. let shadowRoot = _attachShadow.apply(this, arg);
  2860. initForShadowRoot(shadowRoot);
  2861. return shadowRoot
  2862. };
  2863.  
  2864. _prototype_.attachShadow.toString = () => _attachShadow.toString();
  2865.  
  2866. }
  2867.  
  2868. }
  2869.  
  2870. function hackCreateShadowRoot() { // createShadowRoot - Deprecated
  2871.  
  2872. let _prototype_ = window && window.HTMLElement ? window.HTMLElement.prototype : null;
  2873. if (_prototype_ && typeof _prototype_.createShadowRoot == 'function') {
  2874.  
  2875. let _createShadowRoot = _prototype_.createShadowRoot;
  2876.  
  2877. hackCreateShadowRoot = null
  2878. _prototype_.createShadowRoot = function() {
  2879. const shadowRoot = _createShadowRoot.apply(this, arguments);
  2880. initForShadowRoot(shadowRoot);
  2881. return shadowRoot;
  2882. };
  2883. _prototype_.createShadowRoot.toString = () => _createShadowRoot.toString();
  2884.  
  2885. }
  2886. }
  2887.  
  2888. /* 事件偵聽hack */
  2889. function hackEventListener() {
  2890. if (!window.EventTarget) return;
  2891. const eventTargetPrototype = window.EventTarget.prototype;
  2892. let _addEventListener = eventTargetPrototype.addEventListener;
  2893. let _removeEventListener = eventTargetPrototype.removeEventListener;
  2894. if (typeof _addEventListener == 'function' && typeof _removeEventListener == 'function') {} else return;
  2895. hackEventListener = null;
  2896.  
  2897. class Listeners {
  2898.  
  2899. constructor(dom, type) {
  2900. this._dom = dom;
  2901. this._type = type;
  2902. this.listenersCount = 0;
  2903. this.hashList = {};
  2904. }
  2905. get baseFunc() {
  2906. if (this._dom && this._type) {
  2907. return this._dom['on' + this._type];
  2908. }
  2909. }
  2910. get funcCount() {
  2911. if (this._dom && this._type) {
  2912. return (typeof this.baseFunc == 'function') * 1 + (this.listenersCount || 0)
  2913. }
  2914. }
  2915.  
  2916.  
  2917. }
  2918.  
  2919.  
  2920.  
  2921. let hackedEvtCount = 0;
  2922.  
  2923.  
  2924. let watchList = ['click'];
  2925.  
  2926. eventTargetPrototype.addEventListener = function() {
  2927.  
  2928. let arg = arguments
  2929. let type = arg[0]
  2930. let listener = arg[1]
  2931.  
  2932. if (!this || !(this instanceof EventTarget) || typeof type != 'string' || typeof listener != 'function') {
  2933. return _addEventListener.apply(this, arguments)
  2934. //unknown bug?
  2935. }
  2936.  
  2937. if (watchList.indexOf(type) < 0) return _addEventListener.apply(this, arguments);
  2938.  
  2939.  
  2940. let res;
  2941. res = _addEventListener.apply(this, arg)
  2942.  
  2943.  
  2944. let boolCapture = (arg[2] && typeof arg[2] == 'object') ? (arg[2].capture === true) : (arg[2] === true)
  2945.  
  2946. this._listeners = this._listeners || {}
  2947. this._listeners[type] = this._listeners[type] || new Listeners(this, type)
  2948. let uid = 100000 + (++hackedEvtCount);
  2949. let listenerObj = {
  2950. listener,
  2951. options: arg[2],
  2952. uid: uid,
  2953. useCapture: boolCapture
  2954. }
  2955. this._listeners[type].hashList[uid + ''] = listenerObj;
  2956. this._listeners[type].listenersCount++;
  2957. return res;
  2958. }
  2959. // hack removeEventListener
  2960. eventTargetPrototype.removeEventListener = function() {
  2961.  
  2962. let arg = arguments
  2963. let type = arg[0]
  2964. let listener = arg[1]
  2965.  
  2966. if (!this || !(this instanceof EventTarget) || typeof type != 'string' || typeof listener != 'function') {
  2967. return _removeEventListener.apply(this, arguments)
  2968. //unknown bug?
  2969. }
  2970.  
  2971. if (watchList.indexOf(type) < 0) return _removeEventListener.apply(this, arguments);
  2972.  
  2973. let boolCapture = (arg[2] && typeof arg[2] == 'object') ? (arg[2].capture === true) : (arg[2] === true)
  2974.  
  2975. let defaultRemoval = true;
  2976. if (this._listeners && this._listeners[type] && this._listeners[type].hashList) {
  2977. let hashList = this._listeners[type].hashList
  2978. for (let k in hashList) {
  2979. if (hashList[k].listener === listener && hashList[k].useCapture == boolCapture) {
  2980. delete hashList[k];
  2981. this._listeners[type].listenersCount--;
  2982. break;
  2983. }
  2984. }
  2985. }
  2986. if (defaultRemoval) return _removeEventListener.apply(this, arg);
  2987. }
  2988. eventTargetPrototype.addEventListener.toString = () => _addEventListener.toString();
  2989. eventTargetPrototype.removeEventListener.toString = () => _removeEventListener.toString();
  2990.  
  2991.  
  2992. }
  2993. function captureVideoEvents(rootDoc){
  2994.  
  2995. var g=function(evt){
  2996.  
  2997. var domElement = evt.target || this || null
  2998. if(domElement && domElement.nodeType==1 && domElement.nodeName=="VIDEO"){
  2999. var video=domElement
  3000. if (!domElement.getAttribute('_h5ppid')) handlerVideoFound(video);
  3001. if(domElement.getAttribute('_h5ppid')){
  3002. switch(evt.type){
  3003. case 'loadedmetadata':
  3004. return $hs.handlerVideoLoadedMetaData.call(video, evt);
  3005. // case 'playing':
  3006. // return $hs.handlerVideoPlaying.call(video, evt);
  3007. // case 'pause':
  3008. // return $hs.handlerVideoPause.call(video, evt);
  3009. // case 'volumechange':
  3010. // return $hs.handlerVideoVolumeChange.call(video, evt);
  3011. }
  3012. }
  3013. }
  3014.  
  3015.  
  3016. }
  3017.  
  3018. // using capture phase
  3019. rootDoc.addEventListener('loadedmetadata',g,true);
  3020.  
  3021. }
  3022.  
  3023. function handlerVideoFound(video) {
  3024.  
  3025. if (!video) return;
  3026. if (video.getAttribute('_h5ppid')) return;
  3027. let alabel = video.getAttribute('aria-label')
  3028. if (alabel && typeof alabel == "string" && alabel.toUpperCase() == "GIF") return;
  3029.  
  3030.  
  3031. consoleLog('handlerVideoFound', video)
  3032.  
  3033. $hs.intVideoInitCount = ($hs.intVideoInitCount || 0) + 1;
  3034. let vpid = 'h5p-' + $hs.intVideoInitCount
  3035. consoleLog(' - HTML5 Video is detected -', `Number of Videos: ${$hs.intVideoInitCount}`)
  3036. if ($hs.intVideoInitCount === 1) $hs.fireGlobalInit();
  3037. video.setAttribute('_h5ppid', vpid)
  3038.  
  3039.  
  3040. playerConfs[vpid] = new PlayerConf();
  3041. playerConfs[vpid].domElement = video;
  3042. playerConfs[vpid].domActive = DOM_ACTIVE_FOUND;
  3043.  
  3044. let rootNode = getRoot(video);
  3045.  
  3046. if (rootNode.host) $hs.getPlayerBlockElement(video); // shadowing
  3047. let rootElm = rootNode.querySelector('head') || rootNode.querySelector('html') || document.documentElement //48763
  3048. _add_filter(rootElm) // either main document or shadow node
  3049.  
  3050.  
  3051.  
  3052. video.addEventListener('playing',$hs.handlerVideoPlaying,true);
  3053. video.addEventListener('pause',$hs.handlerVideoPause,true);
  3054. video.addEventListener('volumechange',$hs.handlerVideoVolumeChange,true);
  3055.  
  3056.  
  3057.  
  3058. }
  3059.  
  3060.  
  3061.  
  3062. hackAttachShadow()
  3063. hackCreateShadowRoot()
  3064. hackEventListener()
  3065.  
  3066.  
  3067. window.addEventListener('message', $hs.handlerWinMessage, false);
  3068. $hs.bindDocEvents(document);
  3069. captureVideoEvents(document);
  3070.  
  3071. let windowsLD = (function() {
  3072. let ls_res = [];
  3073. try {
  3074. ls_res = [!!window.localStorage, !!window.top.localStorage];
  3075. } catch (e) {}
  3076. try {
  3077. let winp = window;
  3078. let winc = 0;
  3079. while (winp !== window.top && winp && ++winc) winp = winp.parentNode;
  3080. ls_res.push(winc);
  3081. } catch (e) {}
  3082. return ls_res;
  3083. })();
  3084.  
  3085. consoleLogF('- h5Player Plugin Loaded -', ...windowsLD)
  3086.  
  3087. function isInCrossOriginFrame() {
  3088. let result = true;
  3089. try {
  3090. if (window.top.localStorage || window.top.location.href) result = false;
  3091. } catch (e) {}
  3092. return result
  3093. }
  3094.  
  3095. if (isInCrossOriginFrame()) consoleLog('cross origin frame detected');
  3096.  
  3097.  
  3098. })();