HTML5 Video Player Enhance

To enhance the functional features of html5 player (h5player) supporting all websites,shortcuts similar to Potplayer - Adjustment of 亮度(brightness),飽和度(saturate),對比度(contrast),速度(playback speed); frame , hue , blur

目前為 2019-09-28 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name HTML5 Video Player Enhance
  3. // @version 2.6.2(tw)
  4. // @description To enhance the functional features of html5 player (h5player) supporting all websites,shortcuts similar to Potplayer - Adjustment of 亮度(brightness),飽和度(saturate),對比度(contrast),速度(playback speed); frame , hue , blur
  5. // @author CY Fung
  6. // @match http://*/*
  7. // @match https://*/*
  8. // @run-at document-start
  9. // @grant GM_addStyle
  10. // @namespace https://gf.qytechs.cn/users/371179
  11. // ==/UserScript==
  12. (function () {
  13. 'use strict';
  14. // Your code here...
  15.  
  16. let requestAnimationFrame=window.requestAnimationFrame||function(fn) { setTimeout(fn, 3); };
  17.  
  18. let gbl = {};
  19. var UA={};
  20. var tool={};
  21.  
  22.  
  23. Object.assign(gbl, {
  24. checkFullScreen: function checkFullScreen(doc) {
  25. if (typeof doc.fullScreen == 'boolean') return doc.fullScreen;
  26. if (typeof doc.webkitIsFullScreen == 'boolean') return doc.webkitIsFullScreen;
  27. if (typeof doc.mozFullScreen == 'boolean') return doc.mozFullScreen;
  28. return null;
  29. },
  30. gv001: {
  31. isFull: false,
  32. isIframe: false,
  33. autoCheckCount: 0
  34. },
  35. h5player_specific_id_rules :{
  36. 'www.acfun.cn': ['.player-container .player'],
  37. 'www.bilibili.com': ['#bilibiliPlayer'],
  38. 'www.douyu.com': ['#js-player-video-case'],
  39. 'www.huya.com': ['#videoContainer'],
  40. 'www.twitch.tv': ['.player'],
  41. 'www.youtube.com': ['#movie_player'],
  42. 'www.yy.com': ['#player'],
  43. 'www.viu.com': ['#viu-player']
  44. },
  45. h5player_general_id_rules : ['.dplayer', '.video-js', '.jwplayer'],
  46. });
  47. var gv = gbl.gv001;
  48.  
  49. if (window.top !== window.self && window.top && window.self) {
  50. gv.isIframe = true;
  51. }
  52. if (navigator.language.toLocaleLowerCase() == 'zh-cn') {
  53. gv.btnText = {
  54. tip: 'Iframe内视频,请用鼠标点击视频后重试'
  55. };
  56. } else {
  57. gv.btnText = {
  58. tip: 'Iframe video. Please click on the video and try again'
  59. };
  60. }
  61.  
  62.  
  63.  
  64. function consoleLog(){
  65.  
  66. if(gv.isIframe) postMsgA('consoleLog',arguments);else console.log.apply(console,arguments);
  67. }
  68.  
  69. function postMsg(){
  70. var arg = Array.prototype.slice.call(arguments)
  71. var tag = arg.shift();
  72. if(typeof tag == 'string'){
  73. var data = arg;
  74. window.parent.postMessage({tag,data},'*')
  75. }
  76. }
  77.  
  78.  
  79. function postMsgA(){
  80. var tag = arguments[0];
  81. if(typeof tag == 'string'){
  82. var data = Array.from(arguments[1]);
  83. window.parent.postMessage({tag,data},'*')
  84. }
  85. }
  86.  
  87.  
  88.  
  89.  
  90. var domTool = {
  91. getRect: function (element) {
  92. var rect = element.getBoundingClientRect();
  93. var scroll = domTool.getScroll();
  94. return {
  95. pageX: rect.left + scroll.left,
  96. pageY: rect.top + scroll.top,
  97. screenX: rect.left,
  98. screenY: rect.top
  99. };
  100. },
  101. isHalfFullClient: function (element) {
  102. var client = domTool.getClient();
  103. var rect = domTool.getRect(element);
  104. if ((Math.abs(client.width - element.offsetWidth) < 21 && rect.screenX < 20) || (Math.abs(client.height - element.offsetHeight) < 21 && rect.screenY < 10)) {
  105. if (Math.abs(element.offsetWidth / 2 + rect.screenX - client.width / 2) < 10 && Math.abs(element.offsetHeight / 2 + rect.screenY - client.height / 2) < 10) {
  106. return true;
  107. } else {
  108. return false;
  109. }
  110. } else {
  111. return false;
  112. }
  113. },
  114. isAllFullClient: function (element) {
  115. var client = domTool.getClient();
  116. var rect = domTool.getRect(element);
  117. if ((Math.abs(client.width - element.offsetWidth) < 21 && rect.screenX < 20) && (Math.abs(client.height - element.offsetHeight) < 21 && rect.screenY < 10)) {
  118. return true;
  119. } else {
  120. return false;
  121. }
  122. },
  123. getScroll: function () {
  124. return {
  125. left: document.documentElement.scrollLeft || document.body.scrollLeft,
  126. top: document.documentElement.scrollTop || document.body.scrollTop
  127. };
  128. },
  129. getClient: function () {
  130. return {
  131. width: document.compatMode == 'CSS1Compat' ? document.documentElement.clientWidth : document.body.clientWidth,
  132. height: document.compatMode == 'CSS1Compat' ? document.documentElement.clientHeight : document.body.clientHeight
  133. };
  134. },
  135. addStyle: GM_addStyle,
  136. /*function (css) {
  137. var style = document.createElement('style');
  138. style.type = 'text/css';
  139. var node = document.createTextNode(css);
  140. style.appendChild(node);
  141. document.head.appendChild(style);
  142. console.log(document.head,style,'add style')
  143. return style;
  144. },*/
  145. matchRule: function (str, rule) {
  146. return new RegExp("^" + rule.split("*").join(".*") + "$").test(str);
  147. },
  148. addTip: async function (str) {
  149. if (!document.getElementById('catTip')) {
  150. var tip = document.createElement('tbdiv');
  151. tip.id = 'catTip';
  152. tip.innerHTML = str;
  153. tip.style.cssText = 'transition: all 0.8s ease-out;background: none repeat scroll 0 0 #27a9d8;color: #FFFFFF;font: 1.1em "微软雅黑";margin-left: -250px;overflow: hidden;padding: 10px;position: fixed;text-align: center;bottom: 100px;z-index: 300;';
  154. document.body.appendChild(tip);
  155.  
  156.  
  157. tip.style.right = -tip.offsetWidth - 5 + 'px';
  158. await new Promise(resolve => {
  159. tip.style.display = 'block';
  160. setTimeout(() => {
  161. tip.style.right = '25px';
  162. resolve('OK');
  163. }, 300);
  164. });
  165. await new Promise(resolve => {
  166. setTimeout(() => {
  167. tip.style.right = -tip.offsetWidth - 5 + 'px';
  168. resolve('OK');
  169. }, 3500);
  170. });
  171. await new Promise(resolve => {
  172. setTimeout(() => {
  173. document.body.removeChild(tip);
  174. resolve('OK');
  175. }, 1000);
  176. });
  177. }
  178. },
  179.  
  180. eachParentNode:function (dom, fn) {
  181. let parent = dom.parentNode
  182. while (parent) {
  183. let isEnd = fn(parent, dom)
  184. parent = parent.parentNode
  185. if (isEnd) {
  186. break
  187. }
  188. }
  189. },
  190.  
  191. hideDom:function hideDom(selector) {
  192. var dom = document.querySelector(selector)
  193. if (dom) {
  194. requestAnimationFrame(function () {
  195. dom.style.opacity = 0;
  196. dom.style.transform='translate(-9999px)';
  197. dom=null;
  198. })
  199. }
  200. }
  201. };
  202.  
  203. var handle = {
  204. video_onload:function () {
  205. consoleLog('video size', this.videoWidth + ' x ' + this.videoHeight);
  206. },
  207. player_mouseEnter:function (e) {
  208. h5Player._isFoucs = true
  209. },
  210. player_mouseLeave: function (e) {
  211. h5Player._isFoucs = false
  212.  
  213. },
  214. doc_focusout:function (e) {
  215. let doc=this;
  216. h5Player.focusFxLock=true;
  217. requestAnimationFrame(function () {
  218. h5Player.focusFxLock=false;
  219. if (!doc.hasFocus()&&h5Player.player() && !h5Player.isLostFocus) {
  220. h5Player.isLostFocus=true;
  221. consoleLog('doc.focusout')
  222. h5Player.tips('focus is lost', -1);
  223. }
  224. });
  225. },
  226. doc_focusin:function (e) {
  227. let doc=this;
  228. if(h5Player.focusFxLock)return;
  229. requestAnimationFrame(function () {
  230.  
  231. if(h5Player.focusFxLock)return;
  232. if (doc.hasFocus()&&h5Player.player() && h5Player.isLostFocus) {
  233. h5Player.isLostFocus=false;
  234. consoleLog('doc.focusin')
  235. h5Player.tips(false);
  236.  
  237. }
  238. });
  239. },
  240. /*
  241. doc_focusout:function (e) {
  242. let doc=this;
  243. h5Player.focusFxLock=true;
  244. setTimeout(function () {
  245. h5Player.focusFxLock=false;
  246. if (!doc.hasFocus()&&h5Player.player() && !h5Player.isLostFocus) {
  247. h5Player.isLostFocus=true;
  248. consoleLog('doc.focusout')
  249. requestAnimationFrame(()=>{
  250. h5Player.tips('focus is lost', -1);
  251. });
  252. h5Player.filter.setup({grey:1});
  253. }
  254. },450);
  255. },
  256. doc_focusin:function (e) {
  257. let doc=this;
  258. if(h5Player.focusFxLock)return;
  259. setTimeout(function () {
  260.  
  261. if(h5Player.focusFxLock)return;
  262. if (doc.hasFocus()&&h5Player.player() && h5Player.isLostFocus) {
  263. h5Player.isLostFocus=false;
  264. consoleLog('doc.focusin')
  265. h5Player.tips(false);
  266. h5Player.filter.setup({grey:0})
  267.  
  268. }
  269. },450);
  270. },
  271. */
  272.  
  273. selectPlayer_mouseenter: function (event) {
  274. h5Player.playerInstance = event.target
  275. h5Player.initPlayerInstance(false)
  276. },
  277.  
  278. player_syncPlayback:function (event) {
  279. h5Player.playerInstance = event.target
  280. h5Player.initPlayerInstance(false)
  281. /* 同步之前設定的播放速度 */
  282. h5Player.setPlaybackRate()
  283. },
  284.  
  285.  
  286.  
  287. mouseover_getPlayer: function (e) {
  288. if (gv.isFull) {
  289. return;
  290. }
  291. gv.mouseoverEl = e.target;
  292. var hostname = document.location.hostname;
  293. var players = [];
  294. for (var i in gbl.h5player_specific_id_rules) {
  295. if (domTool.matchRule(hostname, i)) {
  296. for (let v of gbl.h5player_specific_id_rules[i]) {
  297. players = document.querySelectorAll(v);
  298. if (players.length > 0) {
  299. break;
  300. }
  301. }
  302. break;
  303. }
  304. }
  305. if (players.length == 0) {
  306. for (let v of gbl.h5player_general_id_rules) {
  307. players = document.querySelectorAll(v);
  308. if (players.length > 0) {
  309. break;
  310. }
  311. }
  312. }
  313. if (players.length == 0 && e.target.nodeName != 'VIDEO' && document.querySelectorAll('video').length > 0) {
  314. var videos = document.querySelectorAll('video');
  315. for (let v of videos) {
  316. var vRect = v.getBoundingClientRect();
  317. if (e.clientX >= vRect.x - 2 && e.clientX <= vRect.x + vRect.width + 2 && e.clientY >= vRect.y - 2 && e.clientY <= vRect.y + vRect.height + 2 && v.offsetWidth > 399 && v.offsetHeight > 220) {
  318. players = [];
  319. players[0] = handle.autoCheck(v);
  320. gv.autoCheckCount = 1;
  321. break;
  322. }
  323. }
  324. }
  325. if (players.length > 0) {
  326. var path = e.path || e.composedPath();
  327. for (let v of players) {
  328. if (path.indexOf(v) > -1) {
  329. gv.player = v;
  330. return;
  331. }
  332. }
  333. }
  334. switch (e.target.nodeName) {
  335. case 'VIDEO':
  336. case 'OBJECT':
  337. case 'EMBED':
  338. if (e.target.offsetWidth > 399 && e.target.offsetHeight > 220) {
  339. gv.player = e.target;
  340.  
  341. }
  342. break;
  343. default:
  344. 0;
  345. }
  346. },
  347. autoCheck: function (v) {
  348. var tempPlayer, el = v;
  349. gv.playerChilds = [];
  350. gv.playerChilds.push(v);
  351. while (el = el.parentNode) {
  352. if (Math.abs(v.offsetWidth - el.offsetWidth) < 15 && Math.abs(v.offsetHeight - el.offsetHeight) < 15) {
  353. tempPlayer = el;
  354. gv.playerChilds.push(el);
  355. } else {
  356. break;
  357. }
  358. }
  359. return tempPlayer;
  360. },
  361. win_receiveMsg: async function (e) {
  362. var tag=e.data;
  363. if(typeof e.data == 'object' && typeof e.data.tag =='string'){
  364. tag=e.data.tag;
  365. }
  366. switch (tag) {
  367. case 'consoleLog':
  368.  
  369. console.log.apply(console,e.data.data)
  370. break;
  371. case 'iframePicInPic':
  372. console.log('messege:iframePicInPic');
  373. if (!document.pictureInPictureElement) {
  374. await document.querySelector('video').requestPictureInPicture().catch(error => {
  375. domTool.addTip(gv.btnText.tip);
  376. });
  377. } else {
  378. await document.exitPictureInPicture();
  379. }
  380. break;
  381. case 'parentFull':
  382. console.log('messege:parentFull');
  383. gv.player = gv.mouseoverEl;
  384. if (gv.isIframe) {
  385. window.parent.postMessage('parentFull', '*');
  386. }
  387. Maximize.setPlayerParents();
  388. Maximize.fullWin();
  389. if (getComputedStyle(gv.player).left != '0px') {
  390. domTool.addStyle('#htmlToothbrush #bodyToothbrush .playerToothbrush {left:0px !important;width:100vw !important;}');
  391. }
  392. gv.isFull = true;
  393. break;
  394. case 'parentSmall':
  395. console.log('messege:parentSmall');
  396. if (gv.isIframe) {
  397. window.parent.postMessage('parentSmall', '*');
  398. }
  399. Maximize.smallWin();
  400. break;
  401. case 'innerFull':
  402. console.log('messege:innerFull');
  403. if (gv.player.nodeName == 'IFRAME') {
  404. gv.player.contentWindow.postMessage('innerFull', '*');
  405. }
  406. Maximize.setPlayerParents();
  407. Maximize.fullWin();
  408. break;
  409. case 'innerSmall':
  410. console.log('messege:innerSmall');
  411. if (gv.player.nodeName == 'IFRAME') {
  412. gv.player.contentWindow.postMessage('innerSmall', '*');
  413. }
  414. Maximize.smallWin();
  415. break;
  416. }
  417. }
  418. };
  419. var Maximize = {
  420. playerControl: function () {
  421. if (!gv.player) {
  422. return;
  423. }
  424. this.setPlayerParents();
  425. if (!gv.isFull) {
  426. if (gv.isIframe) {
  427. window.parent.postMessage('parentFull', '*');
  428. }
  429. if (gv.player.nodeName == 'IFRAME') {
  430. gv.player.contentWindow.postMessage('innerFull', '*');
  431. }
  432. this.fullWin();
  433. if (gv.autoCheckCount > 0 && !domTool.isHalfFullClient(gv.playerChilds[0])) {
  434. if (gv.autoCheckCount > 10) {
  435. for (var v of gv.playerChilds) {
  436. v.classList.add('videoToothbrush');
  437. }
  438. return;
  439. }
  440. var tempPlayer = handle.autoCheck(gv.playerChilds[0]);
  441. gv.autoCheckCount++;
  442. Maximize.playerControl();
  443. gv.player = tempPlayer;
  444. Maximize.playerControl();
  445. } else {
  446. gv.autoCheckCount = 0;
  447. }
  448. } else {
  449. if (gv.isIframe) {
  450. window.parent.postMessage('parentSmall', '*');
  451. }
  452. if (gv.player.nodeName == 'IFRAME') {
  453. gv.player.contentWindow.postMessage('innerSmall', '*');
  454. }
  455. this.smallWin();
  456. }
  457. },
  458. setPlayerParents: function () {
  459. if (gv.isFull) {
  460. return;
  461. }
  462. gv.playerParents = [];
  463. var full = gv.player;
  464. while (full = full.parentNode) {
  465. if (full.nodeName == 'BODY' || full.nodeName == 'HEAD' || full.nodeName == 'HTML') {
  466. break;
  467. }
  468. if (full.getAttribute) {
  469. gv.playerParents.push(full);
  470. }
  471. }
  472. },
  473. fullWin: function () {
  474. if (!gv.isFull) {
  475. document.removeEventListener('mouseover', handle.mouseover_getPlayer, false);
  476. gv.backHtmlId = document.body.parentNode.id;
  477. gv.backBodyId = document.body.id;
  478. if (document.location.hostname == 'www.youtube.com' && document.querySelector('#movie_player .ytp-size-button .ytp-svg-shadow').getBoundingClientRect().width == 20) {
  479. document.querySelector('.ytp-size-button').click();
  480. gv.ytbStageChange = true;
  481. }
  482. document.body.parentNode.id = 'htmlToothbrush';
  483. document.body.id = 'bodyToothbrush';
  484. for (var v of gv.playerParents) {
  485. v.classList.add('parentToothbrush');
  486. //父元素position:fixed会造成层级错乱
  487. if (getComputedStyle(v).position == 'fixed') {
  488. v.classList.add('absoluteToothbrush');
  489. }
  490. }
  491. gv.player.classList.add('playerToothbrush');
  492. if (gv.player.nodeName == 'VIDEO') {
  493. gv.backControls = gv.player.controls;
  494. gv.player.controls = true;
  495. }
  496. window.dispatchEvent(new Event('resize'));
  497. }
  498. gv.isFull = true;
  499. },
  500. smallWin: function () {
  501. document.body.parentNode.id = gv.backHtmlId;
  502. document.body.id = gv.backBodyId;
  503. for (var v of gv.playerParents) {
  504. v.classList.remove('parentToothbrush');
  505. v.classList.remove('absoluteToothbrush');
  506. }
  507. gv.player.classList.remove('playerToothbrush');
  508. if (document.location.hostname == 'www.youtube.com' && gv.ytbStageChange) {
  509. document.querySelector('.ytp-size-button').click();
  510. gv.ytbStageChange = false;
  511. }
  512. if (gv.player.nodeName == 'VIDEO') {
  513. gv.player.controls = gv.backControls;
  514. }
  515. document.addEventListener('mouseover', handle.mouseover_getPlayer, false);
  516. window.dispatchEvent(new Event('resize'));
  517. gv.isFull = false;
  518. }
  519. };
  520. var pictureInPicture = function (videoElm) {
  521. if (document.pictureInPictureElement) document.exitPictureInPicture();
  522. else {
  523. ('requestPictureInPicture' in videoElm)?videoElm.requestPictureInPicture():gbl.h5Player.tips('PIP is not supported.');
  524. }
  525.  
  526. }
  527.  
  528. const TCC_items=[
  529.  
  530.  
  531. {
  532. 'fullScreen': '.fullscreen-btn',
  533. 'exitFullScreen': '.exit-fullscreen-btn',
  534. 'webFullScreen': function () {},
  535. 'exitWebFullScreen': '.exit-fullscreen-btn',
  536. 'autoPlay': '.player-start-btn',
  537. 'pause': '.player-pause',
  538. 'play': '.player-play',
  539. 'switchPlayStatus': '.player-play',
  540. 'playbackRate': function () {},
  541. 'currentTime': function () {},
  542. 'addCurrentTime': '.add-currenttime',
  543. 'subtractCurrentTime': '.subtract-currenttime',
  544. // 自定義快捷鍵的執行方式,如果是組合鍵,必須是 ctrl-->shift-->alt 這樣的順序,沒有可以忽略,鍵名必須全小寫
  545. 'shortcuts': {
  546. /* 註冊要執行自定義回調操作的快捷鍵 */
  547. register: ['ctrl+shift+alt+c', 'ctrl+shift+c', 'ctrl+alt+c', 'ctrl+c', 'c'],
  548. /* 自定義快捷鍵的回調操作 */
  549. callback: function (h5Player, taskConf, data) {
  550. let {
  551. event, player
  552. } = data
  553. console.log(event, player)
  554. }
  555. },
  556. 'include':['||demo.demo^'],
  557. 'exclude':[],
  558. },
  559. {
  560. // 'webFullScreen': 'button.ytp-size-button',
  561. 'fullScreen': 'button.ytp-fullscreen-button',
  562. 'include':['||youtube.com^'],
  563. },
  564. {
  565. 'fullScreen': 'button.button-nfplayerFullscreen',
  566. 'addCurrentTime': 'button.button-nfplayerFastForward',
  567. 'subtractCurrentTime': 'button.button-nfplayerBackTen',
  568. 'include':['||netflix.com^'],
  569. },
  570. {
  571. 'fullScreen': '[data-text="进入全屏"],[data-text="進入全屏"]',
  572. 'webFullScreen': '[data-text="退出全屏",[data-text="退出全屏]"]',
  573. 'autoPlay': '.bilibili-player-video-btn-start',
  574. 'switchPlayStatus': '.bilibili-player-video-btn-start',
  575. 'include':['||bilibili.com^'],
  576. },
  577. {
  578. 'fullScreen': '.bilibili-live-player-video-controller-fullscreen-btn button',
  579. 'webFullScreen': '.bilibili-live-player-video-controller-web-fullscreen-btn button',
  580. 'switchPlayStatus': '.bilibili-live-player-video-controller-start-btn button',
  581. 'include':['||live.bilibili.com^']
  582. },
  583. {
  584. 'fullScreen': '.iqp-btn-fullscreen',
  585. 'webFullScreen': '.iqp-btn-webscreen',
  586. 'init': function (h5Player, taskConf) {
  587. // 隱藏水印
  588. domTool.hideDom('.iqp-logo-box')
  589. // 移除暫停廣告
  590. domTool.addStyle('div[templatetype="common_pause"]{ display:none }')
  591. },
  592. 'include':['||iqiyi.com^']
  593. },
  594. {
  595. 'fullScreen': '.control-fullscreen-icon',
  596. 'init': function (h5Player, taskConf) {
  597. // 隱藏水印
  598. domTool.hideDom('.youku-layer-logo')
  599. },
  600. 'include':['||youku.com^']
  601. },
  602. {
  603. 'fullScreen': 'button.Fullscreen',
  604. 'include':['||ted.com^']
  605. },
  606. {
  607. 'pause': '.container_inner .txp-shadow-mod]',
  608. 'play': '.container_inner .txp-shadow-mod',
  609. 'shortcuts': {
  610. register: ['c', 'x', 'z'],
  611. callback: function (h5Player, taskConf, data) {
  612. let {
  613. event
  614. } = data
  615. let key = event.key.toLowerCase()
  616. let speedItems = document.querySelectorAll('.container_inner txpdiv[data-role="txp-button-speed-list"] .txp_menuitem')
  617. /* 利用sessionStorage下的playbackRate進行設置 */
  618. if (window.sessionStorage.playbackRate && /(c|x|z)/.test(key)) {
  619. let curSpeed = Number(window.sessionStorage.playbackRate)
  620. let perSpeed = curSpeed - 0.1 >= 0 ? curSpeed - 0.1 : 0.1
  621. let nextSpeed = curSpeed + 0.1 <= 4 ? curSpeed + 0.1 : 4
  622. let targetSpeed = curSpeed
  623. switch (key) {
  624. case 'z':
  625. targetSpeed = 1
  626. break
  627. case 'c':
  628. targetSpeed = nextSpeed
  629. break
  630. case 'x':
  631. targetSpeed = perSpeed
  632. break
  633. }
  634. window.sessionStorage.playbackRate = targetSpeed
  635. h5Player.setCurrentTime(0.1, true)
  636. h5Player.setPlaybackRate(targetSpeed, true)
  637. return true
  638. }
  639. /* 模擬點擊觸發 */
  640. if (speedItems.length >= 3 && /(c|x|z)/.test(key)) {
  641. let curIndex = 1
  642. speedItems.forEach((item, index) => {
  643. if (item.classList.contains('txp_current')) {
  644. curIndex = index
  645. }
  646. })
  647. let perIndex = curIndex - 1 >= 0 ? curIndex - 1 : 0
  648. let nextIndex = curIndex + 1 < speedItems.length ? curIndex + 1 : speedItems.length - 1
  649. let target = speedItems[1]
  650. switch (key) {
  651. case 'z':
  652. target = speedItems[1]
  653. break
  654. case 'c':
  655. target = speedItems[nextIndex]
  656. break
  657. case 'x':
  658. target = speedItems[perIndex]
  659. break
  660. }
  661. target.click()
  662. let speedNum = Number(target.innerHTML.replace('x'))
  663. h5Player.setPlaybackRate(speedNum)
  664. }
  665. }
  666. },
  667. 'init': function (h5Player, taskConf) {
  668. // 隱藏水印
  669. domTool.hideDom('.txp-watermark')
  670. },
  671. 'include':['||v.qq.com^']
  672. },
  673. {
  674. 'fullScreen': function (h5Player, taskConf) {
  675. h5Player.playerInstance.parentNode.querySelector('.vjs-fullscreen-control').click()
  676. },
  677. 'include':['||pan.baidu.com^']
  678. }
  679.  
  680. ];
  681.  
  682. ;var uCheck=(function(){
  683. function seperator(x) {
  684. return x == '' || /^[^\w\d\-\.\%\_]$/.test(x)
  685. }
  686.  
  687. return function uCheck(rule, url) {
  688.  
  689. var url_s01 = url.split('\:\/\/');
  690. let protocol = url_s01[0]
  691. var url_s01r = url.substring(protocol.length + 3);
  692. var rule_m2 = null;
  693.  
  694. if (rule_m2 = /^\|(.+)\|$/.exec(rule)) {
  695. return url == rule_m2[1];
  696. }
  697.  
  698. var rule_m1 = rule.match(/^(\|\|)?([^\^]+)(\^)?$/)
  699. var w = '';
  700. let o = {}
  701. if (!rule_m1) return null;
  702. //console.log(110, rule_m1)
  703. if (rule_m1[3]) o.last = true
  704. if (rule_m1[1]) o.domain = true
  705. if (rule_m1[2]) w = rule_m1[2];
  706. var wq = w.replace(/\//g, '\\/').replace(/\./g, '\\.').replace(/\|/g, '\\|').replace(/\*/g, '.*');
  707. var rq = new RegExp('^(.*)(' + wq + ")(.?)", 'i')
  708. var exec1 = rq.exec(url_s01r);
  709. //console.log(120, rq, url_s01r, exec1)
  710.  
  711.  
  712. if (!exec1) return false;
  713. if (o.domain) {
  714. exec1[1]=exec1[1]||'';
  715. if (exec1[1]) {
  716. if (exec1[1].indexOf(':\/\/') >= 0) return false
  717. if (!/\./.test(exec1[1].substr(-1))) return false
  718.  
  719. }
  720. }
  721. if (o.last) {
  722. if(!seperator(exec1[3]))return false;
  723. }
  724. return true;
  725. }
  726.  
  727. })();
  728.  
  729.  
  730. const TCC = {
  731. /**
  732. * 任務配置中心 Task Control Center
  733. * 用於配置所有無法進行通用處理的任務,如不同網站的FULLSCREEN方式不一樣,必須調用網站本身的FULLSCREEN邏輯,才能確保字幕、彈幕等正常工作
  734. * */
  735.  
  736.  
  737. /**
  738. * 獲取任務配置,只能獲取到當前域名下的任務配置信息
  739. * @param taskName {string} -可選 指定具體任務,默認返回所有類型的任務配置
  740. */
  741. getTaskConfig: function () {
  742. let t = this
  743.  
  744. if(t._getTaskConfig) return t._getTaskConfig;
  745.  
  746. //only just once
  747.  
  748. let matchAllDomain=false;
  749. /**
  750. * 格式化配置任務
  751. * @param matchAllDomain { boolean } -可選 默認只格式當前域名或host下的配置任務,傳入true則將所有域名下的任務配置都進行格式化
  752. */
  753.  
  754.  
  755. var formatTCC=(function () {
  756.  
  757. let result = []
  758. let url=location+'';
  759. TCC_items.forEach((item) =>{
  760. if(!tool.isObj(item))return;
  761. if(item.exclude){
  762. if(item.exclude.some( rule=>uCheck(rule,url) )){
  763. item.isExcluded=true;
  764. return;
  765. }
  766. }
  767.  
  768. if(item.include){
  769. if(item.include.some( rule=>uCheck(rule,url) )){
  770. item.isIncluded=true;
  771. }
  772.  
  773. }
  774.  
  775. if(item.isIncluded){
  776. result.push( item)
  777. item.isMatch=true;
  778. }
  779.  
  780. })
  781. return result
  782. })();
  783.  
  784.  
  785. console.log('formatTCC',formatTCC)
  786.  
  787. let taskConf = formatTCC[0]
  788. if(!taskConf) return t._getTaskConfig={};
  789. let isMatch = taskConf.isMatch; /* 判斷所提供的配置任務是否適用於當前URL */
  790.  
  791.  
  792. if (isMatch) return t._getTaskConfig=taskConf;
  793. return t._getTaskConfig={};
  794. },
  795. _getTaskConfig:null,
  796. /**
  797. * 執行當前頁面下的相應任務
  798. * @param taskName {object|string} -必選,可直接傳入任務配置對象,也可用是任務名稱的字符串信息,自己去查找是否有任務需要執行
  799. * @param data {object} -可選,傳給回調函數的數據
  800. */
  801. doTask: function (taskName, data) {
  802. var isDo = false
  803. if (!taskName) return isDo
  804. let taskConf = this.getTaskConfig()
  805. if (!tool.isObj(taskConf) ) return isDo
  806. let task = taskConf[taskName]
  807.  
  808. if (!task ) return isDo
  809. console.log('h5player-dotask',taskName)
  810. var clickDOM = null;
  811. if (taskName === 'shortcuts') {
  812. if (tool.isObj(task) && tool.getType(task.callback) === 'function') {
  813. task.callback(h5Player, taskConf, data)
  814. isDo = true
  815. }
  816. } else if (tool.getType(task) === 'function') {
  817. task(h5Player, taskConf, data)
  818. isDo = true
  819. } else {
  820. let wrapDom = h5Player.getPlayerWrapDom()
  821. /* 觸發選擇器上的點擊事件 */
  822. if (wrapDom && wrapDom.querySelector(task)) {
  823. // 在video的父元素裡查找,是為了盡可能相容多實例下的邏輯
  824. clickDOM=wrapDom.querySelector(task)
  825. } else if (document.querySelector(task)) {
  826. clickDOM=document.querySelector(task)
  827. }
  828. if(clickDOM){
  829. requestAnimationFrame(function(){
  830. //prevent keydown and click same time
  831. clickDOM.click()
  832. });
  833. isDo = true
  834. }
  835. }
  836. return isDo
  837. }
  838. }
  839.  
  840.  
  841. function ready(selector, fn, shadowRoot) {
  842. //only for video elements
  843.  
  844.  
  845. if(!wVideo._readyCount && !shadowRoot){
  846. document.addEventListener('mouseover', handle.mouseover_getPlayer, false);
  847. window.addEventListener('message', handle.win_receiveMsg, false);
  848. }
  849.  
  850. /**
  851. * 元素監聽器
  852. * @param selector -必選
  853. * @param fn -必選,元素存在時的回調
  854. * @param shadowRoot -可選 指定監聽某個shadowRoot下面的DOM元素
  855. * 參考:https://javascript.ruanyifeng.com/dom/mutationobserver.html
  856. */
  857. ready.listeners = ready.listeners||[]
  858. let listeners = ready.listeners
  859. let win = window
  860. let doc = shadowRoot || win.document
  861. let MutationObserver = win.MutationObserver || win.WebKitMutationObserver
  862. let observer
  863.  
  864. var addedVideoNodes=[]
  865.  
  866. function check() {
  867.  
  868.  
  869. var mutations=arguments[0]||null
  870.  
  871.  
  872. var requireChecking=true;
  873. var mi=0;
  874. if(mutations){
  875. mutations.forEach(mutation=>mutation.addedNodes.forEach( node=>{if(node.tagName=='VIDEO')addedVideoNodes[mi++]=node;} ))
  876. if(mi==0) requireChecking = false;
  877. }
  878.  
  879. if(requireChecking){
  880.  
  881. var addedVideoNodes2
  882. if(mi>0){
  883. addedVideoNodes2=addedVideoNodes.slice(0,mi)
  884. }
  885.  
  886. //mutationsAddedOnly=mutations.filter(mutation=>mutation.addedNodes.length>0)
  887.  
  888. for (let i = 0; i < listeners.length; i++) {
  889. let listener = listeners[i]
  890. let elements=mutations?addedVideoNodes2.filter( elm=>elm.matches(listener.selector) ):Array.from(doc.querySelectorAll('video'))
  891.  
  892. consoleLog(elements.length,30)
  893. for (let j = 0; j < elements.length; j++) {
  894. var element = elements[j]
  895. if (!element._isMutationReady_) {
  896. element._isMutationReady_ = true
  897. element.setAttribute('h5play_observed','1');
  898. listener.fn.call(element, element)
  899. }
  900. }
  901. }
  902.  
  903. }
  904. }
  905.  
  906. // 儲存選擇器和回調函數
  907. listeners.push({
  908. selector: selector,
  909. fn: fn
  910. })
  911.  
  912. if(listeners.length==1){
  913. observer = new MutationObserver(check)
  914. consoleLog('q5q',location.href)
  915. observer.observe(shadowRoot || doc.documentElement, {
  916. childList: true,
  917. subtree: true
  918. })
  919. }
  920. check()
  921. }
  922.  
  923. /**
  924. * 某些網頁用了attachShadow closed mode,需要open才能獲取video標籤,例如百度雲盤
  925. * 解決參考:
  926. * https://developers.google.com/web/fundamentals/web-components/shadowdom?hl=zh-cn#closed
  927. * https://stackoverflow.com/questions/54954383/override-element-prototype-attachshadow-using-chrome-extension
  928. */
  929.  
  930.  
  931. function hackAttachShadow() {
  932. if (window._hasHackAttachShadow_) return
  933. window._hasHackAttachShadow_ = true
  934. var err = null;
  935.  
  936. function errFunc(_err) {
  937. console.error('hackAttachShadow error by h5player plug-in', {
  938. errCode: err = _err
  939. })
  940. return
  941. }
  942. window._shadowDomList_ = []
  943. if (!window.Element) return errFunc(0x1E01);
  944. if (!window.Element.prototype) return (0x1E02);
  945. window.Element.prototype._attachShadow = window.Element.prototype.attachShadow
  946.  
  947. window.Element.prototype.attachShadow = function () {
  948.  
  949. let arg = arguments
  950. try {
  951.  
  952. if (arg[0] && arg[0]['mode']) {
  953. // 強制使用 open mode
  954. arg[0]['mode'] = 'open'
  955. }
  956. } catch (e) {
  957. return errFunc(0x3001);
  958. }
  959. let shadowRoot = this._attachShadow.apply(this, arg)
  960.  
  961. // 存一份shadowDomListf
  962. if(window._shadowDomList_ && window._shadowDomList_.push) window._shadowDomList_.push(shadowRoot)
  963.  
  964.  
  965. try {
  966. // 在document下面添加 addShadowRoot 自定義事件
  967. let shadowEvent = new window.CustomEvent('addShadowRoot', {
  968. shadowRoot,
  969. detail: {
  970. shadowRoot,
  971. message: 'addShadowRoot',
  972. time: new Date()
  973. },
  974. bubbles: true,
  975. cancelable: true
  976. })
  977. document.dispatchEvent(shadowEvent)
  978.  
  979. } catch (e) {
  980. return errFunc(0x3000);
  981. }
  982. return shadowRoot
  983. }
  984.  
  985. }
  986.  
  987.  
  988. /* 事件偵聽hack */
  989. function hackEventListener() {
  990. if(!window.EventTarget)return
  991. const EVENT = window.EventTarget.prototype
  992. if (EVENT._addEventListener) return
  993.  
  994. function Listeners(dom,type) { this._dom=dom;this._type=type;this.listenersCount=0;this.hashList={}; };
  995. Listeners.prototype = new Object;
  996. Listeners.prototype.__defineGetter__("baseFunc",function(){
  997. if(this._dom && this._type){
  998. return this._dom['on'+this._type];
  999. }
  1000. });
  1001. Listeners.prototype.__defineGetter__("funcCount",function(){
  1002. if(this._dom && this._type){
  1003. return (typeof this.baseFunc=='function')*1+(this.listenersCount||0)
  1004. }
  1005. });
  1006.  
  1007. EVENT._baseLogTime=(+new Date().getTime())*100-1200000000000;
  1008.  
  1009. EVENT._addEventListener = EVENT.addEventListener
  1010. EVENT._removeEventListener = EVENT.removeEventListener
  1011. // hack addEventListener
  1012. EVENT._evtCount=0;
  1013. EVENT.addEventListener = function () {
  1014. let arg = arguments
  1015. let type = arg[0]
  1016. let listener = arg[1]
  1017. this._addEventListener.apply(this, arg)
  1018. this._listeners = this._listeners || {}
  1019. this._listeners[type] = this._listeners[type] || new Listeners(this,type)
  1020. var addedTime=+new Date().getTime();
  1021. var uid = 100000+(++EVENT._evtCount);
  1022. let listenerObj = {
  1023. //target: this,
  1024. //type,
  1025. listener,
  1026. options: arg[2],
  1027. uid: uid,
  1028. //addedTime:addedTime
  1029. }
  1030. this._listeners[type].hashList[uid+'']=listenerObj;
  1031. this._listeners[type].listenersCount++;
  1032. }
  1033. // hack removeEventListener
  1034. EVENT.removeEventListener = function () {
  1035. let arg = arguments
  1036. let type = arg[0]
  1037. let listener = arg[1]
  1038. this._removeEventListener.apply(this, arg)
  1039. if(this._listeners&&this._listeners[type]&&this._listeners[type].hashList){
  1040. var hashList=this._listeners[type].hashList
  1041. for(var k in hashList){
  1042. if(hashList[k].listener===listener){
  1043. delete hashList[k];
  1044. this._listeners[type].listenersCount--;
  1045. break;
  1046. }
  1047. }
  1048. }
  1049. }
  1050. }
  1051.  
  1052.  
  1053.  
  1054. Object.assign(tool,{
  1055. quickSort : function (arr) {
  1056.  
  1057. /**
  1058. * 向上查找操作
  1059. * @param dom {Element} -必選 初始dom元素
  1060. * @param fn {function} -必選 每一級ParentNode的回調操作
  1061. * 如果函數返回true則表示停止向上查找動作
  1062. */
  1063. function _quickSort(arr){
  1064.  
  1065. if (arr.length <= 1) {
  1066. return arr
  1067. }
  1068. var pivotIndex = Math.floor(arr.length / 2)
  1069. var pivot = arr.splice(pivotIndex, 1)[0]
  1070. var left = []
  1071. var right = []
  1072. for (var i = 0; i < arr.length; i++) {
  1073. if (arr[i] < pivot) {
  1074. left.push(arr[i])
  1075. } else {
  1076. right.push(arr[i])
  1077. }
  1078. }
  1079. return _quickSort(left).concat([pivot], _quickSort(right))
  1080. }
  1081.  
  1082. return _quickSort(arr)
  1083. },
  1084.  
  1085.  
  1086. getType:function (obj) {
  1087.  
  1088. /**
  1089. * 準確地獲取對象的具體類型
  1090. * @param obj { all } -必選 要判斷的對象
  1091. * @returns {*} 返回判斷的具體類型
  1092. */
  1093. if (obj == null) {
  1094. return String(obj)
  1095. }
  1096. return typeof obj === 'object' || typeof obj === 'function' ? (obj.constructor && obj.constructor.name && obj.constructor.name.toLowerCase()) || /function\s(.+?)\(/.exec(obj.constructor)[1].toLowerCase() : typeof obj
  1097. },
  1098.  
  1099. isObj:function isObj(obj) {
  1100. return tool.getType(obj) === 'object'
  1101. }
  1102. });
  1103.  
  1104.  
  1105. Object.assign(UA,{
  1106. fakeUA:function fakeUA(ua) {
  1107. Object.defineProperty(navigator, 'userAgent', {
  1108. value: ua,
  1109. writable: false,
  1110. configurable: false,
  1111. enumerable: true
  1112. })
  1113. },
  1114. userAgentMap: {
  1115. android: {
  1116. chrome: 'Mozilla/5.0 (Linux; Android 9; SM-G960F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.157 Mobile Safari/537.36',
  1117. firefox: 'Mozilla/5.0 (Android 7.0; Mobile; rv:57.0) Gecko/57.0 Firefox/57.0'
  1118. },
  1119. iPhone: {
  1120. safari: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Mobile/15E148 Safari/604.1',
  1121. chrome: 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/74.0.3729.121 Mobile/15E148 Safari/605.1'
  1122. },
  1123. iPad: {
  1124. safari: 'Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1 Mobile/15E148 Safari/604.1',
  1125. chrome: 'Mozilla/5.0 (iPad; CPU OS 12_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/74.0.3729.155 Mobile/15E148 Safari/605.1'
  1126. }
  1127. }
  1128.  
  1129. });
  1130.  
  1131. Object.assign(UA,{
  1132. fakeConfig : {
  1133. // 'tv.cctv.com': userAgentMap.iPhone.chrome,
  1134. // 'v.qq.com': userAgentMap.iPad.chrome,
  1135. 'open.163.com': UA.userAgentMap.iPhone.chrome,
  1136. 'm.open.163.com': UA.userAgentMap.iPhone.chrome
  1137. }
  1138. });
  1139.  
  1140. function debugMsg() {
  1141. let arg = Array.from(arguments)
  1142. arg.unshift('h5player debug message :')
  1143. console.info.apply(console, arg)
  1144. }
  1145. let h5Player = {
  1146. /* 提示文本的字號 */
  1147. fontSize: 16,
  1148. enable: true,
  1149. globalMode: true,
  1150. playerInstance: null,
  1151. scale: 1,
  1152. translate: {
  1153. x: 0,
  1154. y: 0
  1155. },
  1156. playbackRate: 1,
  1157. /* 快進快退步長 */
  1158. skipStep: 5,
  1159. /* 獲取當前播放器的實例 */
  1160. player: function () {
  1161. let t = this
  1162. return t.playerInstance || t.getPlayerList()[0]
  1163. },
  1164. /* 每個網頁可能存在的多個video播放器 */
  1165. getPlayerList: function () {
  1166. let list = []
  1167.  
  1168. function findPlayer(context) {
  1169. context.querySelectorAll('video').forEach(function (player) {
  1170. list.push(player)
  1171. })
  1172. }
  1173. findPlayer(document)
  1174. // 被封裝在 shadow dom 裡面的video
  1175. if (window._shadowDomList_) {
  1176. window._shadowDomList_.forEach(function (shadowRoot) {
  1177. findPlayer(shadowRoot)
  1178. })
  1179. }
  1180. return list
  1181. },
  1182. getPlayerWrapDom: function () {
  1183. let t = this
  1184. let player = t.player()
  1185. if (!player) return
  1186. let wrapDom = null
  1187. let playerBox = player.getBoundingClientRect()
  1188. domTool.eachParentNode(player, function (parent) {
  1189. if (parent === document || !parent.getBoundingClientRect) return
  1190. let parentBox = parent.getBoundingClientRect()
  1191. if (parentBox.width && parentBox.height) {
  1192. if (parentBox.width === playerBox.width && parentBox.height === playerBox.height) {
  1193. wrapDom = parent
  1194. }
  1195. }
  1196. })
  1197. return wrapDom
  1198. },
  1199. fireGlobalInit:function(){
  1200.  
  1201. let t = this
  1202. /* 綁定鍵盤事件 */
  1203. if (t._hasBindEvent_) return
  1204. t._hasBindEvent_ = true
  1205. h5Player.isLostFocus=null;
  1206. consoleLog('keydown bind')
  1207. document.removeEventListener('keydown', t.keydownEvent)
  1208. document.addEventListener('keydown', t.keydownEvent, true)
  1209. /* 相容iframe操作 */
  1210. async function bindTopWindow(){
  1211. //iframe may not be able to control top window
  1212. //error; just ignore with asycn
  1213. let topDoc=window.top&& window.top.document?window.top.document:null
  1214. if(topDoc){
  1215. topDoc.addEventListener('focusout', handle.doc_focusout, true)
  1216. topDoc.addEventListener('focusin', handle.doc_focusin, true)
  1217. //iframe may not be able to control top window
  1218. if (window.top !== window) {
  1219. topDoc.removeEventListener('keydown', t.keydownEvent)
  1220. topDoc.addEventListener('keydown', t.keydownEvent, true)
  1221. }
  1222. }
  1223. }
  1224. bindTopWindow()
  1225. consoleLog('bindTopWindow passed')
  1226.  
  1227.  
  1228. let host = window.location.host
  1229. if (UA.fakeConfig[host]) {
  1230. t.setFakeUA(UA.fakeConfig[host])
  1231. }
  1232.  
  1233. },
  1234. initPlayerInstance: function (isSingle) {
  1235.  
  1236. /**
  1237. * 初始化播放器實例
  1238. * @param isSingle 是否為單實例video標籤
  1239. */
  1240. let t = this
  1241. if (!t.playerInstance) return
  1242. let player = t.playerInstance
  1243. if(!t.init_count) {
  1244. t.init_count=(t.init_count||0)+1;
  1245. if(t.init_count===1) t.fireGlobalInit();
  1246. }
  1247.  
  1248. if (!player.hasAttribute('tabindex')) player.setAttribute('tabindex', '-1');
  1249. if (!player.hasAttribute('playsinline')) player.setAttribute('playsinline', 'playsinline');
  1250. if (!player.hasAttribute('x-webkit-airplay')) player.setAttribute('x-webkit-airplay', 'deny');
  1251. if (!player.hasAttribute('preload')) player.setAttribute('preload', 'auto');
  1252. player.style['image-rendering'] = '-webkit-optimize-contrast';
  1253. player.style['image-rendering'] = 'crisp-edges';
  1254. t.filter.reset()
  1255. t.initTips()
  1256. t.initPlaybackRate()
  1257. t.isFoucs()
  1258. player.addEventListener('playing',function(){
  1259. consoleLog('playing')
  1260. requestAnimationFrame(()=>h5Player.tips('Playback resumed',undefined,500))
  1261.  
  1262. })
  1263.  
  1264. player.addEventListener('pause',function(){
  1265. consoleLog('pause')
  1266. requestAnimationFrame(()=>h5Player.tips('Playback paused',undefined,500))
  1267.  
  1268. })
  1269.  
  1270. /* 增加通用FULLSCREEN,網頁FULLSCREEN-api */
  1271. if (!player._hasCanplayEvent_) {
  1272. player.addEventListener('canplay', function (event) {
  1273. h5Player.initAutoPlay(this)
  1274. })
  1275. player._hasCanplayEvent_ = true
  1276. }
  1277. /* 播放的時候進行相關同步操作 */
  1278. if (!player._hasPlayingInitEvent_) {
  1279. player._setPlaybackRateOnPlayingCount=0;
  1280. player.addEventListener('playing', function (event) {
  1281. let t = h5Player;
  1282. if (this._setPlaybackRateOnPlayingCount === 0) {
  1283. /* 同步之前設定的播放速度 */
  1284. t.setPlaybackRate()
  1285. if (isSingle === true) {
  1286. /* 恢復播放進度和進行進度記錄 */
  1287. t.setPlayProgress(this)
  1288. t.playProgressRecorder(player)
  1289. }
  1290. } else {
  1291. t.setPlaybackRate(null, true)
  1292. }
  1293. this._setPlaybackRateOnPlayingCount = 1; // += 1
  1294. })
  1295. player._hasPlayingInitEvent_ = true
  1296. }
  1297. /* 進行自定義初始化操作 */
  1298. let taskConf = TCC.getTaskConfig()
  1299. if (taskConf.init) {
  1300. TCC.doTask('init', player)
  1301. }
  1302. },
  1303. initPlaybackRate: function () {
  1304. let t = this
  1305. t.playbackRate = t.getPlaybackRate()
  1306. },
  1307. getPlaybackRate: function () {
  1308. let t = this
  1309. let playbackRate = window.localStorage.getItem('_h5_player_playback_rate_') || t.playbackRate
  1310. return Number(Number(playbackRate).toFixed(1))
  1311. },
  1312. change_playerBox: function (tips) {
  1313. let t = this;
  1314. let player = t.player()
  1315. var playerBox = player.parentNode
  1316. while (playerBox && playerBox.offsetHeight == 0) playerBox = playerBox.parentNode;
  1317. while (playerBox && playerBox.offsetHeight < player.offsetHeight) playerBox = playerBox.parentNode;
  1318. playerBox = playerBox || player.parentNode
  1319. if (playerBox) {
  1320. if (!tips.parentNode || tips.parentNode !== playerBox) {
  1321. playerBox.insertBefore(tips, playerBox.firstChild);
  1322. }
  1323. }
  1324. },
  1325. callFullScreenBtn7: function () {
  1326. let t = this;
  1327. let player = t.player()
  1328. if (gv.player && gv.player.contains(player)) {} else {
  1329. gv.player = player;
  1330. }
  1331. Maximize.playerControl();
  1332. },
  1333. callFullScreenBtn8: function () {
  1334. let t = this;
  1335. let player = t.player()
  1336. var doubleClickEvent = document.createEvent('MouseEvents');
  1337. doubleClickEvent.initEvent('dblclick', true, true);
  1338. player.dispatchEvent(doubleClickEvent); // inside method
  1339. },
  1340. callFullScreenBtn0: function () {
  1341. return this.callFullScreenBtn7();
  1342. },
  1343. callFullScreenBtn5: function (playerBox) {
  1344. return this.callFullScreenBtn7();
  1345. let t = this;
  1346. let player = t.player()
  1347.  
  1348. function get_gPlayer() {
  1349. // parent of same size as video player
  1350. var q1 = JSON.stringify(player.getClientRects()[0])
  1351. var pPlayer = player
  1352. var qPlayer = playerBox
  1353. for (var q3 = 0; q3 < 4; q3++) {
  1354. if (!qPlayer) break;
  1355. var q2 = JSON.stringify(qPlayer.getClientRects()[0])
  1356. if (q1 == q2) {
  1357. pPlayer = qPlayer
  1358. qPlayer = qPlayer.parentNode
  1359. } else {
  1360. break;
  1361. }
  1362. }
  1363. var gPlayer = pPlayer
  1364. return gPlayer;
  1365. }
  1366. var gPlayer = get_gPlayer();
  1367. console.log('gPlayer', gPlayer)
  1368. consoleLog('DOM fullscreen')
  1369. gPlayer.requestFullscreen()
  1370. },
  1371. callFullScreenBtn: function () {
  1372. //return this.callFullScreenBtn7();
  1373. //return this.callFullScreenBtn8();
  1374. let t = this;
  1375. let player = t.player()
  1376. if (!player || !player.ownerDocument) return this.callFullScreenBtn0();
  1377. let tcn = player.getAttribute('_h5player_tips') || (t.tipsClassName);
  1378. consoleLog('tcn', tcn)
  1379. let playerBox = player.ownerDocument.querySelector('.' + tcn)
  1380. if (!playerBox) return this.callFullScreenBtn0();
  1381. let chFull = gbl.checkFullScreen(player.ownerDocument);
  1382. if (chFull === null) return (console.log('chFull', 'null'), this.callFullScreenBtn0());
  1383. if (chFull === true) {
  1384. consoleLog('chFull', 'true')
  1385. player.ownerDocument.exitFullscreen();
  1386. // var el = [t.player_focus_input]; if (el) {el = el[0].click()}
  1387. } else {
  1388. consoleLog('chFull', 'false')
  1389. var pPlayer = player
  1390. var qPlayer = playerBox
  1391. var clicked = false;
  1392. var _gs1_tmp={};
  1393. var _gs1_filter=function (elq) {
  1394. return _gs1_tmp.elm == elq.elm ? false : _gs1_tmp.elm.contains(elq.elm)
  1395. };
  1396. var _gs1_contains=function (elp) {
  1397. if (elp.childElementCount === 0) return false;
  1398. _gs1_tmp.elm = elp.elm;
  1399. elp.contains = gs1.filter(_gs1_filter).length > 0;
  1400. }
  1401. // try to find the fullscreen button
  1402. for (var q3 = 0; q3 < 4; q3++) { //max 4 layers
  1403. if (!qPlayer) break;
  1404. var fs1 = qPlayer.querySelectorAll('[class*="fullscreen"]')
  1405. if (fs1.length > 0) {
  1406. // -- indiv-elm --
  1407. var gs1 = Array.prototype.map.call(fs1, function (elm) {
  1408. return {
  1409. elm: elm,
  1410. visible: null,
  1411. click: null,
  1412. childElementCount: null,
  1413. contains: null
  1414. }
  1415. });
  1416. if (('_listeners' in document)) {
  1417. gs1.forEach(function (elp) {
  1418. var elm = elp.elm;
  1419. elp.click = elm._listeners && elm._listeners.click && elm._listeners.click.funcCount > 0
  1420. })
  1421. }
  1422. if ('childElementCount' in player) {
  1423. gs1.forEach(function (elp) {
  1424. var elm = elp.elm;
  1425. elp.childElementCount = elm.childElementCount;
  1426. })
  1427. }
  1428. if ('getBoundingClientRect' in player) {
  1429. gs1.forEach(function (elp) {
  1430. var elm = elp.elm;
  1431. var rect = elm.getBoundingClientRect();
  1432. elp.visible = rect.height * rect.width > 0
  1433. })
  1434. }
  1435. gs1 = gs1.filter(function (elp) {
  1436. return elp.click
  1437. })
  1438. //console.log('gs1',gs1)
  1439. // -- inter-elm --
  1440. if ('contains' in player) {
  1441. gs1.forEach(_gs1_contains)
  1442. }
  1443. var gs2 = gs1.filter(function (elp) {
  1444. return !elp.contains && elp.visible
  1445. })
  1446. console.log('fullscreen btn',gs2)
  1447. //console.log('gs2',gs2)
  1448. if (gs2.length >= 1) {
  1449. var gs2_a = gs2.map(elp => elp.elm.className.length)
  1450. var gs2_b = Math.min.apply(Math, gs2_a)
  1451. var gs2_c = gs2_a.lastIndexOf(gs2_b)
  1452. // pick the last btn if there is more than one
  1453. gs2[gs2_c].elm.click();
  1454. clicked = true;
  1455. consoleLog('original fullscreen')
  1456. break;
  1457. }
  1458. }
  1459. pPlayer = qPlayer
  1460. qPlayer = qPlayer.parentNode
  1461. }
  1462. if (!clicked) {
  1463. //cannot find -> default
  1464. this.callFullScreenBtn5(playerBox);
  1465. }
  1466. }
  1467. },
  1468. /* 設置播放速度 */
  1469. setPlaybackRate: function (num, notips) {
  1470. let taskConf = TCC.getTaskConfig()
  1471. if (taskConf.playbackRate) {
  1472. TCC.doTask('playbackRate')
  1473. return
  1474. }
  1475. let t = this
  1476. let player = t.player()
  1477. let curPlaybackRate
  1478. if (num) {
  1479. num = Number(num)
  1480. if (Number.isNaN(num)) {
  1481. console.error('h5player: 播放速度轉換出錯')
  1482. return false
  1483. }
  1484. if (num <= 0) {
  1485. num = 0.1
  1486. }
  1487. num = Number(num.toFixed(1))
  1488. curPlaybackRate = num
  1489. } else {
  1490. curPlaybackRate = t.getPlaybackRate()
  1491. }
  1492. /* 記錄播放速度的信息 */
  1493. window.localStorage.setItem('_h5_player_playback_rate_', curPlaybackRate)
  1494. t.playbackRate = curPlaybackRate
  1495. player.playbackRate = curPlaybackRate
  1496. /* 本身處於1被播放速度的時候不再提示 */
  1497. if (!num && curPlaybackRate === 1) return;
  1498. !notips && t.tips('Playback speed: ' + player.playbackRate + 'x')
  1499. },
  1500. /**
  1501. * 初始化自動播放邏輯
  1502. * 必須是配置了自動播放按鈕選擇器得的才會進行自動播放
  1503. */
  1504. initAutoPlay: function (p) {
  1505. let t = this
  1506. let player = p || t.player()
  1507. // 在輪詢重試的時候,如果實例變了,或處於隱藏頁面中則不進行自動播放操作
  1508. if (!player || (p && p !== t.player()) || document.hidden) return
  1509. let taskConf = TCC.getTaskConfig()
  1510. if (player && taskConf.autoPlay && player.paused) {
  1511. TCC.doTask('autoPlay')
  1512. if (player.paused) {
  1513. // 輪詢重試
  1514. if (!player._initAutoPlayCount_) {
  1515. player._initAutoPlayCount_ = 1
  1516. }
  1517. player._initAutoPlayCount_ += 1
  1518. if (player._initAutoPlayCount_ >= 10) {
  1519. return false
  1520. }
  1521. setTimeout(function () {
  1522. t.initAutoPlay(player)
  1523. }, 200)
  1524. }
  1525. }
  1526. },
  1527. setWebFullScreen: function () {
  1528. return this.callFullScreenBtn();
  1529. },
  1530. setCurrentTime: function (num, notips) {
  1531. if (!num) return
  1532. num = Number(num);
  1533. let _num = Math.abs(Number(num.toFixed(1)));
  1534. let t = this;
  1535. let player = t.player();
  1536. let taskConf = TCC.getTaskConfig();
  1537. if (taskConf.currentTime) {
  1538. TCC.doTask('currentTime');
  1539. return
  1540. }
  1541. if (num > 0) {
  1542. if (taskConf.addCurrentTime) {
  1543. TCC.doTask('addCurrentTime')
  1544. } else {
  1545. player.currentTime += _num;
  1546. !notips && t.tips(_num + ' Sec. Forward')
  1547. }
  1548. } else {
  1549. if (taskConf.subtractCurrentTime) {
  1550. TCC.doTask('subtractCurrentTime')
  1551. } else {
  1552. player.currentTime -= _num;
  1553. !notips && t.tips(_num + ' Sec. Backward')
  1554. }
  1555. }
  1556. },
  1557. setVolume: function (num) {
  1558. if (!num) return
  1559. num = Number(num)
  1560. let _num = Math.abs(Number(num.toFixed(2)))
  1561. let t = this
  1562. let player = t.player()
  1563. if (num > 0) {
  1564. if (player.volume < 1) {
  1565. player.volume += _num
  1566. }
  1567. } else {
  1568. if (player.volume > 0) {
  1569. player.volume -= _num
  1570. }
  1571. }
  1572. t.tips('Volume: ' + parseInt(player.volume * 100) + '%')
  1573. },
  1574. setFakeUA(ua) {
  1575. ua = ua || UA.userAgentMap.iPhone.safari
  1576. /* 記錄設定的ua信息 */
  1577. window.localStorage.setItem('_h5_player_user_agent_', ua)
  1578. UA.fakeUA(ua)
  1579. },
  1580. /* ua偽裝切換開關 */
  1581. switchFakeUA(ua) {
  1582. let customUA = window.localStorage.getItem('_h5_player_user_agent_')
  1583. if (customUA) {
  1584. window.localStorage.removeItem('_h5_player_user_agent_')
  1585. } else {
  1586. this.setFakeUA(ua)
  1587. }
  1588. debugMsg('ua', navigator.userAgent)
  1589. },
  1590. switchPlayStatus: function () {
  1591. let t = this
  1592. let player = t.player()
  1593. let taskConf = TCC.getTaskConfig()
  1594. if (taskConf.switchPlayStatus) {
  1595. TCC.doTask('switchPlayStatus')
  1596. return
  1597. }
  1598. if (player.paused) {
  1599. if (taskConf.play) {
  1600. TCC.doTask('play')
  1601. } else {
  1602. player.play()
  1603. }
  1604. } else {
  1605. if (taskConf.pause) {
  1606. TCC.doTask('pause')
  1607. } else {
  1608. player.pause()
  1609.  
  1610. }
  1611. }
  1612. },
  1613. tipsClassName: 'html_player_enhance_tips',
  1614. tips: function (str, duration, order) {
  1615. let t = h5Player
  1616. let player = t.player()
  1617. if (!player) {
  1618. console.log('h5Player Tips:', str)
  1619. return true
  1620. }
  1621. let parentNode = player.parentNode
  1622. let tipsSelector = '.' + (player.getAttribute('_h5player_tips') || t.tipsClassName)
  1623. let tipsDom = player.ownerDocument.querySelector(tipsSelector) || (t.initTips(), player.ownerDocument.querySelector(tipsSelector))
  1624. if (!tipsDom) {
  1625. console.log('init h5player tips dom error...')
  1626. return false
  1627. }
  1628. t.change_playerBox(tipsDom);
  1629. let style = tipsDom.style
  1630.  
  1631. if (this.on_off[2]) clearTimeout(this.on_off[2]);
  1632. this.on_off[2] = 0;
  1633.  
  1634. if (str === false) {
  1635. tipsDom.innerText = '';
  1636. tipsDom.setAttribute('_potTips_','0')
  1637. } else {
  1638. order=order||1000
  1639. tipsDom.tipsOrder=tipsDom.tipsOrder||0;
  1640.  
  1641. var shallDisplay=true
  1642. if(order<tipsDom.tipsOrder && getComputedStyle(tipsDom).opacity>0)shallDisplay=false
  1643.  
  1644. if(shallDisplay){
  1645.  
  1646. if (duration === undefined) duration = 2000
  1647. tipsDom.innerText = str
  1648.  
  1649. tipsDom.setAttribute('_potTips_','2')
  1650.  
  1651.  
  1652.  
  1653. if (duration > 0) {
  1654.  
  1655.  
  1656. requestAnimationFrame(function(){
  1657. tipsDom.setAttribute('_potTips_','1')
  1658. })
  1659.  
  1660. }else{
  1661. order=-1;
  1662. }
  1663.  
  1664. tipsDom.tipsOrder=order
  1665.  
  1666. }
  1667.  
  1668.  
  1669.  
  1670. }
  1671. },
  1672. initTips: function () {
  1673. /* 設置提示DOM的樣式 */
  1674. let t = this
  1675. let player = t.player()
  1676. let parentNode = player.parentNode
  1677. var tcn = player.getAttribute('_h5player_tips') || (t.tipsClassName + '_' + (+new Date));
  1678. player.setAttribute('_h5player_tips', tcn)
  1679. if (player.ownerDocument.querySelector('.' + tcn)) return
  1680.  
  1681.  
  1682.  
  1683. domTool.addStyle(`
  1684. [_potTips_="1"]{
  1685. animation: 2s linear 0s normal forwards 1 delayHide;
  1686. }
  1687. [_potTips_="0"]{
  1688. opacity:0; transform:translate(-9999px);
  1689. }
  1690. [_potTips_="2"]{
  1691. opacity:1; transform: translate(-50%,-50%);
  1692. }
  1693.  
  1694. @keyframes delayHide{
  1695. 0% { opacity:1; transform: translate(-50%,-50%); }
  1696. 99% { opacity:1; transform: translate(-50%,-50%); }
  1697. 100% { opacity:0; transform:translate(-9999px); }
  1698. }
  1699. `+`
  1700. [_potTips_]{
  1701. position: absolute !important;
  1702. z-index: 999 !important;
  1703. font-size: ${t.fontSize || 16}px !important;
  1704. padding: 10px !important;
  1705. background: rgba(0,0,0,0.4) !important;
  1706. color:white !important;
  1707. top: 50%;
  1708. left: 50%;max-width:500px;max-height:50px;
  1709. border-radius:3px;
  1710. -webkit-font-smoothing: subpixel-antialiased;
  1711. -moz-font-smoothing: subpixel-antialiased;
  1712. -ms-font-smoothing: subpixel-antialiased;
  1713. font-smoothing: subpixel-antialiased;
  1714. font-family: 'microsoft yahei', Verdana, Geneva, sans-serif;
  1715. -webkit-user-select: none;
  1716. -moz-user-select: none;
  1717. -ms-user-select: none;
  1718. user-select: none;
  1719. -webkit-touch-callout: none;
  1720. -webkit-user-select: none;
  1721. -khtml-user-drag: none;
  1722. -khtml-user-select: none;
  1723. -moz-user-select: none;
  1724. -moz-user-select: -moz-none;
  1725. -ms-user-select: none;
  1726. pointer-events: none;
  1727. user-select: none;
  1728. }
  1729. `.replace(/\r\n/g, ''))
  1730.  
  1731. let tips = document.createElement('div')
  1732. tips.setAttribute('class', tcn)
  1733. tips.setAttribute('unselectable', 'on')
  1734. tips.setAttribute('_potTips_','0')
  1735. t.change_playerBox(tips);
  1736. },
  1737. on_off: new Array(3),
  1738. rotate: 0,
  1739. fps: 30,
  1740. /* 濾鏡效果 */
  1741. filter: {
  1742. key: {},
  1743. view_units: {
  1744. 'hue-rotate': 'deg',
  1745. 'blur': 'px'
  1746. },
  1747. setup: function (options) {
  1748. var view = ''
  1749. for (var view_key in this.key) {
  1750. var view_unit = this.view_units[view_key] || ''
  1751. view += view_key + '(' + (+this.key[view_key] || 0).toFixed(3) + view_unit + ') '
  1752. this.key[view_key] = Number(+this.key[view_key] || 0)
  1753. }
  1754. view += 'url("#unsharpen1")'
  1755. if(options&&options.grey) view+=' url("#grey1")'
  1756. h5Player.player().style.WebkitFilter = view
  1757. },
  1758. reset: function () {
  1759. this.key['brightness'] = 1
  1760. this.key['contrast'] = 1
  1761. this.key['saturate'] = 1
  1762. this.key['hue-rotate'] = 0
  1763. this.key['blur'] = 0
  1764. this.setup()
  1765. }
  1766. },
  1767. _isFoucs: false,
  1768. /* 播放器的聚焦事件 */
  1769. isFoucs: function () {
  1770. let t = h5Player
  1771. let player = t.player()
  1772. if (!player) return
  1773. player.addEventListener('mouseenter', handle.player_mouseEnter)
  1774. player.addEventListener('mouseleave', handle.player_mouseLeave)
  1775. },
  1776. keyMap: {
  1777. 'enter': 13,
  1778. 'shift': 16,
  1779. 'ctrl': 17,
  1780. 'alt': 18,
  1781. 'esc': 27,
  1782. 'space': 32,
  1783. 'LEFT': 37,
  1784. 'UP': 38,
  1785. 'RIGHT': 39,
  1786. 'DOWN': 40,
  1787. '1': 49,
  1788. '2': 50,
  1789. '3': 51,
  1790. '4': 52,
  1791. 'c': 67,
  1792. 'd': 68,
  1793. 'e': 69,
  1794. 'f': 70,
  1795. 'i': 73,
  1796. 'j': 74,
  1797. 'k': 75,
  1798. 'n': 78,
  1799. 'o': 79,
  1800. 'p': 80,
  1801. 'q': 81,
  1802. 'r': 82,
  1803. 's': 83,
  1804. 't': 84,
  1805. 'u': 85,
  1806. 'w': 87,
  1807. 'x': 88,
  1808. 'y': 89,
  1809. 'z': 90,
  1810. 'pad1': 97,
  1811. 'pad2': 98,
  1812. 'pad3': 99,
  1813. 'pad4': 100,
  1814. '\\': 220,
  1815. },
  1816. trigger_allowKeys: ['x', 'c', 'z', 'arrowright', 'arrowleft', 'arrowup', 'arrowdown'],
  1817. /* 播放器事件響應器 */
  1818. playerTrigger: function (player, event) {
  1819. if (!player || !event) return
  1820. let t = h5Player
  1821. let keyCode = event.keyCode
  1822. if (event.code == "Space" && keyCode > 128) keyCode = 32;
  1823.  
  1824. //shift + key
  1825. if (event.shiftKey && !event.ctrlKey && !event.altKey) {
  1826. let key = event.key.toLowerCase()
  1827. // 網頁FULLSCREEN
  1828. if (key === 'enter') {
  1829. t.callFullScreenBtn()
  1830. }
  1831. // 進入或退出畫中畫模式
  1832. else if (key === 'p') {
  1833. pictureInPicture(player)
  1834. }
  1835. // 視頻畫面縮放相關事件
  1836. else if (t.trigger_allowKeys.includes(key)) {
  1837. t.scale = Number(t.scale)
  1838. switch (key) {
  1839. // shift+X:視頻縮小 -0.1
  1840. case 'x':
  1841. t.scale -= 0.1
  1842. break
  1843. // shift+C:視頻放大 +0.1
  1844. case 'c':
  1845. t.scale += 0.1
  1846. break
  1847. // shift+Z:視頻恢復正常大小
  1848. case 'z':
  1849. t.scale = 1
  1850. t.translate = {
  1851. x: 0,
  1852. y: 0
  1853. }
  1854. break
  1855. case 'arrowright':
  1856. t.translate.x += 10
  1857. break
  1858. case 'arrowleft':
  1859. t.translate.x -= 10
  1860. break
  1861. case 'arrowup':
  1862. t.translate.y -= 10
  1863. break
  1864. case 'arrowdown':
  1865. t.translate.y += 10
  1866. break
  1867. }
  1868. let scale = t.scale = Number(t.scale).toFixed(1)
  1869. player.style.transform = `scale(${scale}) translate(${t.translate.x}px, ${t.translate.y}px)`
  1870. let tipsMsg = `視頻縮放率:${scale * 100}%`
  1871. if (t.translate.x) {
  1872. tipsMsg += `,水平位移:${t.translate.x}px`
  1873. }
  1874. if (t.translate.y) {
  1875. tipsMsg += `,垂直位移:${t.translate.y}px`
  1876. }
  1877. t.tips(tipsMsg)
  1878. // 阻止事件冒泡
  1879. event.stopPropagation()
  1880. event.preventDefault()
  1881. return true
  1882. }
  1883. }
  1884. // 防止其它無關組合鍵衝突
  1885. if (!event.altKey && !event.ctrlKey && !event.shiftKey) {
  1886. var kControl = null
  1887. consoleLog('keycode', keyCode)
  1888. switch (keyCode) {
  1889. // 方向鍵右→:快進3秒
  1890. case 39:
  1891. t.setCurrentTime(t.skipStep)
  1892. break;
  1893. // 方向鍵左←:後退3秒
  1894. case 37:
  1895. t.setCurrentTime(-t.skipStep)
  1896. break;
  1897. // 方向鍵上↑:音量升高 1%
  1898. case 38:
  1899. t.setVolume(0.01)
  1900. break;
  1901. // 方向鍵下↓:音量降低 1%
  1902. case 40:
  1903. t.setVolume(-0.01)
  1904. break;
  1905. // 空格鍵:暫停/播放
  1906. case h5Player.keyMap.space:
  1907. t.switchPlayStatus()
  1908. break;
  1909. // 按鍵X:減速播放 -0.1
  1910. case h5Player.keyMap.x:
  1911. if (player.playbackRate > 0) {
  1912. t.setPlaybackRate(player.playbackRate - 0.1)
  1913. }
  1914. break;
  1915. // 按鍵C:加速播放 +0.1
  1916. case h5Player.keyMap.c:
  1917. if (player.playbackRate < 16) {
  1918. t.setPlaybackRate(player.playbackRate + 0.1)
  1919. }
  1920. break;
  1921. // 按鍵Z:正常速度播放
  1922. case h5Player.keyMap.z:
  1923. player.playbackRate = 1
  1924. t.setPlaybackRate(player.playbackRate)
  1925. break;
  1926. // 按鍵F:下一幀
  1927. case h5Player.keyMap.f:
  1928. if (window.location.hostname === 'www.netflix.com') return /* netflix 的F鍵是FULLSCREEN的意思 */
  1929. if (!player.paused) player.pause()
  1930. player.currentTime += Number(1 / t.fps)
  1931. t.tips('Jump to: Next frame')
  1932. break;
  1933. // 按鍵D:上一幀
  1934. case h5Player.keyMap.d:
  1935. if (!player.paused) player.pause()
  1936. player.currentTime -= Number(1 / t.fps)
  1937. t.tips('Jump to: Previous frame')
  1938. break;
  1939. // 按鍵E:亮度增加%
  1940. case h5Player.keyMap.e:
  1941. kControl = 'brightness'
  1942. t.filter.key[kControl] += 0.1
  1943. t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
  1944. t.filter.setup()
  1945. t.tips('Brightness: ' + parseInt(t.filter.key[kControl] * 100) + '%')
  1946. break;
  1947. // 按鍵W:亮度減少%
  1948. case h5Player.keyMap.w:
  1949. kControl = 'brightness'
  1950. if (t.filter.key[kControl] > 0) {
  1951. t.filter.key[kControl] -= 0.1
  1952. t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
  1953. t.filter.setup()
  1954. }
  1955. t.tips('Brightness: ' + parseInt(t.filter.key[kControl] * 100) + '%')
  1956. break;
  1957. // 按鍵T:對比度增加%
  1958. case h5Player.keyMap.t:
  1959. kControl = 'contrast'
  1960. t.filter.key[kControl] += 0.1
  1961. t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
  1962. t.filter.setup()
  1963. t.tips('Contrast: ' + parseInt(t.filter.key[kControl] * 100) + '%')
  1964. break;
  1965. // 按鍵R:對比度減少%
  1966. case h5Player.keyMap.r:
  1967. kControl = 'contrast'
  1968. if (t.filter.key[kControl] > 0) {
  1969. t.filter.key[kControl] -= 0.1
  1970. t.filter.key[kControl] = t.filter.key[1].toFixed(2)
  1971. t.filter.setup()
  1972. }
  1973. t.tips('Contrast: ' + parseInt(t.filter.key[kControl] * 100) + '%')
  1974. break;
  1975. // 按鍵U:飽和度增加%
  1976. case h5Player.keyMap.u:
  1977. kControl = 'saturate'
  1978. t.filter.key[kControl] += 0.1
  1979. t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
  1980. t.filter.setup()
  1981. t.tips('Saturate: ' + parseInt(t.filter.key[kControl] * 100) + '%')
  1982. break;
  1983. // 按鍵Y:飽和度減少%
  1984. case h5Player.keyMap.y:
  1985. kControl = 'saturate'
  1986. if (t.filter.key[kControl] > 0) {
  1987. t.filter.key[kControl] -= 0.1
  1988. t.filter.key[kControl] = t.filter.key[kControl].toFixed(2)
  1989. t.filter.setup()
  1990. }
  1991. t.tips('Saturate: ' + parseInt(t.filter.key[kControl] * 100) + '%')
  1992. break;
  1993. // 按鍵O:色相增加 1 度
  1994. case h5Player.keyMap.o:
  1995. kControl = 'hue-rotate'
  1996. t.filter.key['hue-rotate'] += 1
  1997. t.filter.setup()
  1998. t.tips('Hue: ' + t.filter.key[kControl] + ' deg')
  1999. break;
  2000. // 按鍵I:色相減少 1 度
  2001. case h5Player.keyMap.i:
  2002. kControl = 'hue-rotate'
  2003. t.filter.key['hue-rotate'] -= 1
  2004. t.filter.setup()
  2005. t.tips('Hue: ' + t.filter.key[kControl] + ' deg')
  2006. break;
  2007. // 按鍵K:模糊增加 0.1 px
  2008. case h5Player.keyMap.k:
  2009. kControl = 'blur'
  2010. t.filter.key[kControl] += 0.1
  2011. t.filter.key[kControl] = (+t.filter.key[kControl] || 0).toFixed(1)
  2012. t.filter.setup()
  2013. t.tips('Blur: ' + t.filter.key[kControl] + ' px')
  2014. break;
  2015. // 按鍵J:模糊減少 0.1 px
  2016. case h5Player.keyMap.j:
  2017. kControl = 'blur'
  2018. if (t.filter.key[kControl] > 0) {
  2019. t.filter.key[kControl] -= 0.1
  2020. t.filter.key[kControl] = (+t.filter.key[kControl] || 0).toFixed(1)
  2021. t.filter.setup()
  2022. }
  2023. t.tips('Blur: ' + t.filter.key[kControl] + ' px')
  2024. break;
  2025. // 按鍵Q:圖像復位
  2026. case h5Player.keyMap.q:
  2027. t.filter.reset()
  2028. t.tips('Video Filter Reset')
  2029. break;
  2030. // 按鍵S:畫面旋轉 90 度
  2031. case h5Player.keyMap.s:
  2032. t.rotate += 90
  2033. if (t.rotate % 360 === 0) t.rotate = 0;
  2034. player.style.transform = 'rotate(' + t.rotate + 'deg)'
  2035. t.tips('Rotation:' + t.rotate + ' deg')
  2036. break;
  2037. // 按鍵迴車,進入FULLSCREEN
  2038. case h5Player.keyMap.enter:
  2039. t.callFullScreenBtn();
  2040. break;
  2041. case h5Player.keyMap.n:
  2042. pictureInPicture(player);
  2043. break;
  2044. default:
  2045. // 按1-4設置播放速度 49-52;97-100
  2046. if ((keyCode >= 49 && keyCode <= 52) || (keyCode >= 97 && keyCode <= 100)) {
  2047. player.playbackRate = Number(event.key)
  2048. t.setPlaybackRate(player.playbackRate)
  2049. }
  2050. }
  2051. // 阻止事件冒泡
  2052. event.stopPropagation()
  2053. event.preventDefault()
  2054. return true
  2055. }
  2056. },
  2057. isRegister:function isRegister(event, registerList,key) {
  2058. let list = registerList
  2059. /* 當前觸發的組合鍵 */
  2060. let combineKey = []
  2061. if (event.ctrlKey) {
  2062. combineKey.push('ctrl')
  2063. }
  2064. if (event.shiftKey) {
  2065. combineKey.push('shift')
  2066. }
  2067. if (event.altKey) {
  2068. combineKey.push('alt')
  2069. }
  2070. combineKey.push(key)
  2071. /* 通過循環判斷當前觸發的組合鍵和已註冊的組合鍵是否完全一致 */
  2072. let hasRegArr = list.filter((shortcut) => {
  2073. let regKey = shortcut.split('+');
  2074. if (combineKey.length === regKey.length) {
  2075. let allMatch = regKey.every(key=>combineKey.includes(key));
  2076. if (allMatch) {
  2077. return true;
  2078. }
  2079. }
  2080. return false;
  2081. })
  2082. return hasRegArr.length==1
  2083. },
  2084.  
  2085. /* 運行自定義的快捷鍵操作,如果運行了會返回true */
  2086. runCustomShortcuts: function (player, event) {
  2087. if (!player || !event) return
  2088.  
  2089. let t = h5Player;
  2090.  
  2091. let taskConf = TCC.getTaskConfig()
  2092. let confIsCorrect = tool.isObj(taskConf.shortcuts) && Array.isArray(taskConf.shortcuts.register) && taskConf.shortcuts.callback instanceof Function
  2093. /* 判斷當前觸發的快捷鍵是否已被註冊 */
  2094.  
  2095. let key = event.key.toLowerCase()
  2096. if (confIsCorrect && t.isRegister(event, taskConf.shortcuts.register, key)) {
  2097. // 執行自定義快捷鍵操作
  2098. TCC.doTask('shortcuts', {
  2099. event,
  2100. player,
  2101. h5Player
  2102. })
  2103. return true
  2104. } else {
  2105. return false
  2106. }
  2107. },
  2108. /* 判斷焦點是否處於可編輯元素 */
  2109. isEditableTarget: function (target) {
  2110. let isEditable = target.getAttribute && target.getAttribute('contenteditable') === 'true';
  2111. let isInputDom = /INPUT|TEXTAREA|SELECT/.test(target.nodeName);
  2112. return isEditable || isInputDom;
  2113. },
  2114. /* 按鍵響應方法 */
  2115. keydownEvent: function (event) {
  2116. //console.log('-full', gbl.checkFullScreen(document))
  2117. let t = h5Player
  2118. let keyCode = event.keyCode
  2119. let key = event.key.toLowerCase()
  2120. let player = t.player()
  2121. if (event.code == "Space" && keyCode > 128) keyCode = 32;
  2122. /* 處於可編輯元素中不執行任何快捷鍵 */
  2123. if (t.isEditableTarget(event.target)) return
  2124. /* shift+f 切換UA偽裝 */
  2125. if (event.shiftKey && keyCode === 70) {
  2126. t.switchFakeUA()
  2127. }
  2128. /* 未用到的按鍵不進行任何事件監聽 */
  2129. //console.log('-keycode-', keyCode)
  2130. t.keyCodeList=t.keyCodeList||Object.values(t.keyMap);
  2131. t.keyList=t.keyList||Object.keys(t.keyMap).concat(['enter', 'shift', 'control', 'alt', 'escape', ' ', 'space', 'arrowleft', 'arrowright', 'arrowright', 'arrowup', 'arrowdown','|']);
  2132. let isInUseCode = t.keyCodeList.includes(keyCode) || t.keyList.includes(key)
  2133. if (!isInUseCode) return
  2134. if (!player) {
  2135. // console.log('無可用的播放,不執行相關操作')
  2136. return
  2137. }
  2138. /* 切換插件的可用狀態 */
  2139. if (event.ctrlKey && keyCode === 32) {
  2140. t.enable = !t.enable;
  2141. if (t.enable) {
  2142. t.tips('啟用h5Player插件')
  2143. } else {
  2144. t.tips('禁用h5Player插件')
  2145. }
  2146. }
  2147. if (!t.enable) {
  2148. consoleLog('h5Player 已禁用~')
  2149. return false
  2150. }
  2151. // 按ctrl+\ 鍵進入聚焦或取消聚焦狀態,用於視頻標籤被遮擋的場景
  2152. if (event.ctrlKey && keyCode === 220) {
  2153. t.globalMode = !t.globalMode
  2154. if (t.globalMode) {
  2155. t.tips('全局模式')
  2156. } else {
  2157. t.tips('禁用全局模式')
  2158. }
  2159. }
  2160. /* 非全局模式下,不聚焦則不執行快捷鍵的操作 */
  2161. if (!t.globalMode && !t._isFoucs) return
  2162. /* 判斷是否執行了自定義快捷鍵操作,如果是則不再響應後面默認定義操作 */
  2163. if (t.runCustomShortcuts(player, event) === true) return
  2164. /* 響應播放器相關操作 */
  2165. t.playerTrigger(player, event)
  2166. },
  2167. /**
  2168. * 獲取播放進度
  2169. * @param player -可選 對應的h5 播放器對象, 如果不傳,則獲取到的是整個播放進度表,傳則獲取當前播放器的播放進度
  2170. */
  2171. getPlayProgress: function (player) {
  2172. let progressMap = window.localStorage.getItem('_h5_player_play_progress_')
  2173. if (!progressMap) {
  2174. progressMap = {}
  2175. } else {
  2176. progressMap = JSON.parse(progressMap)
  2177. }
  2178. if (!player) {
  2179. return progressMap
  2180. } else {
  2181. let keyName = window.location.href || player.src
  2182. if (progressMap[keyName]) {
  2183. return progressMap[keyName].progress
  2184. } else {
  2185. return player.currentTime
  2186. }
  2187. }
  2188. },
  2189. /* 播放進度記錄器 */
  2190. playProgressRecorder: function (player) {
  2191. let t = h5Player
  2192. clearTimeout(player._playProgressTimer_)
  2193. var _player = player;
  2194. var recorder_handle = {
  2195.  
  2196. _timerFunc:function () {
  2197. var player = _player
  2198. let progressMap = t.getPlayProgress()
  2199. let keyName = window.location.href || player.src
  2200. let list = Object.keys(progressMap)
  2201. /* 只保存最近10個視頻的播放進度 */
  2202. if (list.length > 10) {
  2203. /* 根據更新的時間戳,取出最早添加播放進度的記錄項 */
  2204. let timeList = []
  2205. list.forEach(function (keyName) {
  2206. progressMap[keyName] && progressMap[keyName].t && timeList.push(progressMap[keyName].t)
  2207. })
  2208. timeList = tool.quickSort(timeList)
  2209. let timestamp = timeList[0]
  2210. /* 刪除最早添加的記錄項 */
  2211. list.forEach(function (keyName) {
  2212. if (progressMap[keyName].t === timestamp) {
  2213. delete progressMap[keyName]
  2214. }
  2215. })
  2216. }
  2217. /* 記錄當前播放進度 */
  2218. progressMap[keyName] = {
  2219. progress: player.currentTime,
  2220. t: new Date().getTime()
  2221. }
  2222. /* 存儲播放進度表 */
  2223. window.localStorage.setItem('_h5_player_play_progress_', JSON.stringify(progressMap))
  2224. /* 循環偵聽 */
  2225. recorder_handle._recorder()
  2226. },_recorder : function () {
  2227. var player = _player
  2228. player._playProgressTimer_ = setTimeout(recorder_handle._timerFunc, 1000 * 2)
  2229. }
  2230.  
  2231.  
  2232. };
  2233. recorder_handle._recorder()
  2234. },
  2235. /* 設置播放進度 */
  2236. setPlayProgress: function (player, time) {
  2237. if (!player) return
  2238. let t = h5Player
  2239. let curTime = Number(t.getPlayProgress(player))
  2240. if (!curTime || Number.isNaN(curTime)) return
  2241. player.currentTime = curTime || player.currentTime
  2242. if (curTime > 3) {
  2243. t.tips('- Playback Progress is restored -')
  2244. }
  2245. },
  2246. /**
  2247. * 檢測h5播放器是否存在
  2248. * @param callback
  2249. */
  2250. detecH5Player: function () {
  2251. let t = this
  2252. let playerList = t.getPlayerList()
  2253. if (playerList.length) {
  2254. consoleLog(' - HTML5 Video is detected -', `Number of Videos: ${playerList.length}`)
  2255. /* 單video實例標籤的情況 */
  2256. if (playerList.length === 1) {
  2257. t.playerInstance = playerList[0]
  2258. t.initPlayerInstance(true)
  2259. } else {
  2260. /* 多video實例標籤的情況 */
  2261. playerList.forEach(function (player) {
  2262. /* 鼠標移到其上面的時候重新指定實例 */
  2263. if (!player._hasMouseRedirectEvent_) {
  2264. player.addEventListener('mouseenter', handle.selectPlayer_mouseenter)
  2265. player._hasMouseRedirectEvent_ = true
  2266. }
  2267. /* 播放器開始播放的時候重新指向實例 */
  2268. if (!player._hasPlayingRedirectEvent_) {
  2269. player.addEventListener('playing', handle.player_syncPlayback)
  2270. player._hasPlayingRedirectEvent_ = true
  2271. }
  2272. })
  2273. }
  2274. }
  2275. },
  2276. load: false
  2277. }
  2278.  
  2279.  
  2280.  
  2281. hackAttachShadow()
  2282. hackEventListener()
  2283. gbl.h5Player = h5Player;
  2284.  
  2285. function wVideo(video) {
  2286. consoleLog('wVideo')
  2287.  
  2288. wVideo._readyCount++;
  2289. /* 檢測是否存在H5播放器 */
  2290. h5Player.detecH5Player()
  2291.  
  2292. var pdd = document.createElement('div')
  2293. pdd.id = 'mysvg'
  2294. pdd.innerHTML = `
  2295. <svg id='image' version="1.1" xmlns="http://www.w3.org/2000/svg">
  2296. <defs>
  2297. <filter id="sharpen1">
  2298. <feConvolveMatrix filterRes="100 100" style="color-interpolation-filters:linearrgb" order="3" kernelMatrix="` + `
  2299. -0.3 -0.3 -0.3
  2300. -0.3 3.4 -0.3
  2301. -0.3 -0.3 -0.3`.replace(/[\n\r]+/g, ' ').trim() + `" preserveAlpha="true"/>
  2302. </filter>
  2303. <filter id="unsharpen1">
  2304. <feConvolveMatrix style="color-interpolation-filters:sRGB;color-rendering: optimizeQuality;color-interpolation: sRGB;" order="5" kernelMatrix="` + ` -0.00391 -0.01563 -0.02344 -0.01563 -0.00391
  2305. -0.01563 -0.06250 -0.09375 -0.06250 -0.01563
  2306. -0.02344 -0.09375 1.85980 -0.09375 -0.02344
  2307. -0.01563 -0.06250 -0.09375 -0.06250 -0.01563
  2308. -0.00391 -0.01563 -0.02344 -0.01563 -0.00391`.replace(/[\n\r]+/g, ' ').trim() + `" preserveAlpha="true"/>
  2309. </filter>
  2310. <filter id="grey1">
  2311. <feColorMatrix values="0.3333 0.3333 0.3333 0 0
  2312. 0.3333 0.3333 0.3333 0 0
  2313. 0.3333 0.3333 0.3333 0 0
  2314. 0 0 0 1 0"/>
  2315. <feColorMatrix type="saturate" values="0" />
  2316. </filter>
  2317. </defs>
  2318. </svg>
  2319. `;
  2320. video = video||document.querySelector('video');
  2321.  
  2322. if (video) {
  2323. if(video.parentNode) video.parentNode.appendChild(pdd);
  2324. video.addEventListener('loadedmetadata', handle.video_onload, true);
  2325. }
  2326. }
  2327.  
  2328.  
  2329. wVideo._readyCount=0;
  2330.  
  2331. try {
  2332.  
  2333. /* 檢測到有視頻標籤就進行初始化 */
  2334. ready('video', function (elm) {
  2335. wVideo(elm);
  2336. })
  2337. /* 檢測shadow dom 下面的video */
  2338. document.addEventListener('addShadowRoot', function (e) {
  2339. ready('video', function (elm) {
  2340. wVideo(elm);
  2341. }, e.detail.shadowRoot)
  2342. })
  2343. } catch (e) {
  2344. console.error('h5player:', e)
  2345. }
  2346.  
  2347. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址