HTML5 on CCTV

Replace Flash Player with HTML5 Player on tv.cctv.com

  1. // ==UserScript==
  2. // @name HTML5 on CCTV
  3. // @namespace https://github.com/sffxzzp
  4. // @version 0.19
  5. // @description Replace Flash Player with HTML5 Player on tv.cctv.com
  6. // @author sffxzzp
  7. // @include /^https?://tv.cctv.com/\d*/\d*/\d*/VIDE.*.shtml*/
  8. // @require https://unpkg.com/dplayer/dist/DPlayer.min.js
  9. // @require https://unpkg.com/hls.js/dist/hls.js
  10. // @icon https://tv.cctv.com/favicon.ico
  11. // @connect vdn.apps.cntv.cn
  12. // @connect hls.cntv.myhwcdn.cn
  13. // @connect newcntv.qcloudcdn.com
  14. // @grant GM_xmlhttpRequest
  15. // @grant GM_addStyle
  16. // @grant unsafeWindow
  17. // ==/UserScript==
  18.  
  19. (function() {
  20. var util = (function () {
  21. function util() {}
  22. util.xhr = function (xhrData) {
  23. return new Promise(function(resolve, reject) {
  24. if (!xhrData.xhr) {
  25. GM_xmlhttpRequest({
  26. method: xhrData.method || "get",
  27. url: xhrData.url,
  28. data: xhrData.data,
  29. headers: xhrData.headers || {},
  30. responseType: xhrData.type || "",
  31. timeout: 3e4,
  32. onload: function onload(res) {
  33. return resolve({ response: res, body: res.response });
  34. },
  35. onerror: reject,
  36. ontimeout: reject
  37. });
  38. } else {
  39. var xhr = new XMLHttpRequest();
  40. xhr.open(xhrData.method || "get", xhrData.url, true);
  41. if (xhrData.method === "post") {xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=utf-8");}
  42. if (xhrData.cookie) {xhr.withCredentials = true;}
  43. xhr.responseType = xhrData.responseType || "";
  44. xhr.timeout = 3e4;
  45. if (xhrData.headers) {for (var k in xhrData.headers) {xhr.setRequestHeader(k, xhrData.headers[k]);}}
  46. xhr.onload = function(ev) {
  47. var evt = ev.target;
  48. resolve({ response: evt, body: evt.response });
  49. };
  50. xhr.onerror = reject;
  51. xhr.ontimeout = reject;
  52. xhr.send(xhrData.data);
  53. }
  54. });
  55. };
  56. util.createElement = function (data) {
  57. var node;
  58. if (data.node) {
  59. node = document.createElement(data.node);
  60. if (data.content) {this.setElement({node: node, content: data.content});}
  61. if (data.html) {node.innerHTML = data.html;}
  62. }
  63. return node;
  64. };
  65. util.setElement = function (data) {
  66. if (data.node) {
  67. for (let name in data.content) {data.node.setAttribute(name, data.content[name]);}
  68. if (data.html!=undefined) {data.node.innerHTML = data.html;}
  69. }
  70. };
  71. return util;
  72. })();
  73. var h5onCCTV = (function () {
  74. function h5onCCTV() {};
  75. h5onCCTV.prototype.addPlayer = function (m3u8) {
  76. var h5css = util.createElement({node: 'link', content: {rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/npm/dplayer/dist/DPlayer.min.css'}});
  77. document.head.appendChild(h5css);
  78. var container = document.querySelector('.video_left');
  79. GM_addStyle('.gwA151201_ind01, .retrieve, .buttonVal, #page_body > .column_wrapper, .video, .video .right_but, .cnt_share, [class^=jscroll] {z-index: auto !important;} .nav2 > div > div {z-index: 1 !important;}');
  80. util.setElement({node: container, content: {style: 'height: 100%'}, html: '<div id="dplayer" style="width: 100%; height: 100%;"></div>'});
  81. var pip = document.pictureInPictureEnabled ? [{text: '画中画模式', click: function () {document.querySelector('.dplayer-video').requestPictureInPicture().catch(console.log);}}] : [];
  82. var dp = new DPlayer({
  83. container: container.children[0],
  84. video: {
  85. quality: m3u8,
  86. defaultQuality: m3u8.length-1
  87. },
  88. preload: 'none',
  89. contextmenu: pip
  90. });
  91. unsafeWindow.dp = dp;
  92. var curTime = localStorage.getItem(unsafeWindow.guid);
  93. if (curTime) {
  94. dp.seek(curTime);
  95. var showTime = '';
  96. if (curTime > 3600) {showTime += ' '+parseInt(curTime/3600)+' 时'; curTime = curTime%3600;}
  97. if (curTime > 60) {showTime += ' '+parseInt(curTime/60)+' 分'; curTime = curTime%60;}
  98. showTime += ' '+parseInt(curTime)+' 秒';
  99. dp.notice('已跳转到上次观看进度'+showTime, 2000);
  100. }
  101. dp.on('timeupdate', function () {
  102. if ((dp.video.duration-dp.video.currentTime > 30) && dp.video.currentTime > 30) {
  103. localStorage.setItem(unsafeWindow.guid, dp.video.currentTime);
  104. }
  105. else {
  106. localStorage.removeItem(unsafeWindow.guid);
  107. }
  108. });
  109. }
  110. h5onCCTV.prototype.run = function () {
  111. var _this = this;
  112. util.xhr({
  113. url: `https://vdn.apps.cntv.cn/api/getHttpVideoInfo.do?pid=${unsafeWindow.guid}`,
  114. type: 'json'
  115. }).then(function (res) {
  116. var url = res.body.hls_url.replace('://', '/').split('/');
  117. url = `${url[0]}://newcntv.qcloudcdn.com/${url.slice(2).join('/')}`;
  118. util.xhr({
  119. url: url
  120. }).then(function (res) {
  121. var vlist = res.body.split('\n');
  122. var m3u8 = [];
  123. vlist.forEach(function (v) {
  124. if (v.indexOf('/200.m3u8')>-1) {m3u8.push({name: '流畅', url: 'https://newcntv.qcloudcdn.com'+v, type: 'hls'});}
  125. else if (v.indexOf('/450.m3u8')>-1) {m3u8.push({name: '低清', url: 'https://newcntv.qcloudcdn.com'+v, type: 'hls'});}
  126. else if (v.indexOf('/850.m3u8')>-1) {m3u8.push({name: '标清', url: 'https://newcntv.qcloudcdn.com'+v, type: 'hls'});}
  127. else if (v.indexOf('/1200.m3u8')>-1) {m3u8.push({name: '高清', url: 'https://newcntv.qcloudcdn.com'+v, type: 'hls'});}
  128. else if (v.indexOf('/2000.m3u8')>-1) {m3u8.push({name: '超清', url: 'https://newcntv.qcloudcdn.com'+v, type: 'hls'});}
  129. });
  130. _this.addPlayer(m3u8);
  131. });
  132. });
  133. };
  134. return h5onCCTV;
  135. })();
  136. var program = new h5onCCTV();
  137. program.run();
  138. })();