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-27 提交的版本,查看 最新版本

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