iqiyi player switch

iqiyi player switch between flash and html5

目前为 2017-04-06 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name iqiyi player switch
  3. // @namespace https://github.com/gooyie/userscript-iqiyi-player-switch
  4. // @homepageURL https://github.com/gooyie/userscript-iqiyi-player-switch
  5. // @supportURL https://github.com/gooyie/userscript-iqiyi-player-switch/issues
  6. // @version 1.1.3
  7. // @description iqiyi player switch between flash and html5
  8. // @author gooyie
  9. // @license MIT License
  10. //
  11. // @include *://www.iqiyi.com/v_*
  12. // @include *://www.iqiyi.com/w_*
  13. // @include *://www.iqiyi.com/dongman/*/*
  14. // @include *://www.iqiyi.com/yinyue/*/*
  15. // @grant GM_registerMenuCommand
  16. // @grant GM_unregisterMenuCommand
  17. // @grant GM_log
  18. // @require https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.7.0/js/md5.min.js
  19. // @run-at document-start
  20.  
  21. // ==/UserScript==
  22.  
  23. (function() {
  24. 'use strict';
  25.  
  26. const PLAYER_TYPE = {
  27. Html5VOD: "h5_VOD",
  28. FlashVOD: "flash_VOD"
  29. };
  30.  
  31. class DocCookies {
  32. static get(key) {
  33. let value;
  34. if (new RegExp('^[^\\x00-\\x20\\x7f\\(\\)<>@,;:\\\\\\"\\[\\]\\?=\\{\\}\\/\\u0080-\\uffff]+$').test(key)) {
  35. let re = new RegExp("(^| )" + key + "=([^;]*)(;|$)");
  36. let rs = re.exec(document.cookie);
  37. value = rs ? rs[2] : '';
  38. }
  39. return value ? decodeURIComponent(value) : '';
  40. }
  41.  
  42. static set(k, v, o={}) {
  43. let n = o.expires;
  44. if ("number" == typeof o.expires) {
  45. n = new Date();
  46. n.setTime(n.getTime() + o.expires);
  47. }
  48. let key = k;
  49. let value = encodeURIComponent(v);
  50. let path = o.path ? '; path=' + o.path : '';
  51. let expires = n ? '; expires=' + n.toGMTString() : '';
  52. let domain = o.domain ? '; domain=' + o.domain : '';
  53. document.cookie = `${key}=${value}${path}${expires}${domain}`;
  54. }
  55.  
  56. static remove(k, o={}) {
  57. o.expires = new Date(0);
  58. this.set(k, '', o);
  59. }
  60. }
  61.  
  62. class Detector {
  63.  
  64. static isSupportHtml5() {
  65. let v = document.createElement('video');
  66. return !!(
  67. v.canPlayType('audio/mp4; codecs="mp4a.40.2"') &&
  68. v.canPlayType('video/mp4; codecs="avc1.640029"') &&
  69. v.canPlayType('video/mp4; codecs="avc1.640029, mp4a.40.2"')
  70. );
  71. }
  72.  
  73. static isSupportVms() {
  74. return !!(
  75. window.MediaSource && window.URL && window.WebSocket && window.ReadableStream &&
  76. (window.RTCSessionDescription || window.webkitRTCSessionDescription) &&
  77. (window.RTCPeerConnection || window.webkitRTCPeerConnection) &&
  78. (window.RTCIceCandidate || window.webkitRTCIceCandidate)
  79. );
  80. }
  81.  
  82. static isSupportM3u8() {
  83. let v = document.createElement('video');
  84. return !!(
  85. v.canPlayType('application/x-mpegurl') &&
  86. v.canPlayType('application/vnd.apple.mpegurl')
  87. );
  88. }
  89.  
  90. }
  91.  
  92. class Hooker {
  93.  
  94. static hookCall(cb = () => {}) {
  95.  
  96. const call = Function.prototype.call;
  97. Function.prototype.call = function(...args) {
  98. let ret = call.bind(this)(...args);
  99. if (args) cb(...args);
  100. return ret;
  101. };
  102.  
  103. Function.prototype.call.toString = Function.prototype.call.toLocaleString = function() {
  104. return 'function call() { [native code] }';
  105. };
  106.  
  107. }
  108.  
  109. static _isFactoryCall(args) {
  110. return args.length === 4 && 'object' === typeof args[1] && args[1].hasOwnProperty('exports');
  111. }
  112.  
  113. static hookFactoryCall(cb = () => {}) {
  114. this.hookCall((...args) => {if (this._isFactoryCall(args)) cb(...args);});
  115. }
  116.  
  117. static _isJqueryFactoryCall(exports) {
  118. return exports.hasOwnProperty('fn') && exports.fn.hasOwnProperty('jquery');
  119. }
  120.  
  121. static hookJquery(cb = () => {}) { // module.exports, module, module.exports, require
  122. this.hookFactoryCall((...args) => {if (this._isJqueryFactoryCall(args[1].exports)) cb(...args);});
  123. }
  124.  
  125. static hookJqueryAjax(cb = () => {}) {
  126. this.hookJquery((...args) => {
  127. let exports = args[1].exports;
  128.  
  129. const ajax = exports.ajax.bind(exports);
  130.  
  131. exports.ajax = function(url, options = {}) {
  132. if (typeof url === 'object') {
  133. [url, options] = [url.url, url];
  134. }
  135.  
  136. let isHijacked = cb(url, options);
  137. if (isHijacked) return;
  138.  
  139. ajax(url, options);
  140. };
  141. });
  142. }
  143.  
  144. static _isHttpFactoryCall(exports) {
  145. return exports.hasOwnProperty('jsonp') && exports.hasOwnProperty('ajax');
  146. }
  147.  
  148. static hookHttp(cb = () => {}) {
  149. this.hookFactoryCall((...args) => {if (this._isHttpFactoryCall(args[1].exports)) cb(...args);});
  150. }
  151.  
  152. static hookHttpJsonp(cb = () => {}) {
  153. this.hookHttp((...args) => {
  154. let exports = args[1].exports;
  155.  
  156. const jsonp = exports.jsonp.bind(exports);
  157.  
  158. exports.jsonp = function(options) {
  159. let isHijacked = cb(options);
  160. if (isHijacked) return;
  161. jsonp(options);
  162. };
  163. });
  164. }
  165.  
  166. }
  167.  
  168. class Faker {
  169.  
  170. static fakeMacPlatform() {
  171. const PLAFORM_MAC = 'mac';
  172. Object.defineProperty(navigator, 'platform', {get: () => PLAFORM_MAC});
  173. }
  174.  
  175. static fakeSafary() {
  176. const UA_SAFARY = 'safari';
  177. Object.defineProperty(navigator, 'userAgent', {get: () => UA_SAFARY});
  178. }
  179.  
  180. static fakeChrome() {
  181. const UA_CHROME = 'chrome';
  182. Object.defineProperty(navigator, 'userAgent', {get: () => UA_CHROME});
  183. }
  184.  
  185. static _calcSign(authcookie) {
  186. const RESPONSE_KEY = '-0J1d9d^ESd)9jSsja';
  187. return md5(authcookie.substring(5, 39).split("").reverse().join("") + "<1<" + RESPONSE_KEY);
  188. }
  189.  
  190. static fakeVipRes(authcookie) {
  191. let json = {
  192. code: "A00000",
  193. data: {
  194. sign: this._calcSign(authcookie)
  195. }
  196. };
  197. return json;
  198. }
  199.  
  200. static fakeAdRes() {
  201. let json = {};
  202. return json;
  203. }
  204.  
  205. }
  206.  
  207. class Mocker {
  208.  
  209. static mock() {
  210. let currType = DocCookies.get('player_forcedType');
  211. if (currType !== PLAYER_TYPE.Html5VOD) return;
  212.  
  213. this.mockForBestDefintion();
  214. this.mockAd();
  215. this.mockVip();
  216. }
  217.  
  218. static mockToUseVms() {
  219. Faker.fakeMacPlatform();
  220. Faker.fakeChrome();
  221. }
  222.  
  223. static mockToUseM3u8() {
  224. Faker.fakeMacPlatform();
  225. Faker.fakeSafary();
  226. }
  227.  
  228. static mockForBestDefintion() {
  229. if (Detector.isSupportVms()) {
  230. this.mockToUseVms(); // vms, 1080p or higher
  231. } else if (Detector.isSupportM3u8()) {
  232. this.mockToUseM3u8(); // tmts m3u8
  233. }
  234. // tmts mp4 ...
  235. }
  236.  
  237. static _isAdReq(url) {
  238. const AD_URL = 'http://t7z.cupid.iqiyi.com/show2';
  239. return url.indexOf(AD_URL) === 0;
  240. }
  241.  
  242. static mockAd() {
  243. Hooker.hookJqueryAjax((url, options) => {
  244. GM_log('[jquery ajax]: %s', url);
  245.  
  246. if (this._isAdReq(url)) {
  247. let res = Faker.fakeAdRes();
  248. options.complete({responseJSON: res}, 'success');
  249. return true;
  250. }
  251. });
  252. }
  253.  
  254. static _isCheckVipReq(url) {
  255. const CHECK_VIP_URL = 'https://cmonitor.iqiyi.com/apis/user/check_vip.action';
  256. return url === CHECK_VIP_URL;
  257. }
  258.  
  259. static mockVip() {
  260. Hooker.hookHttpJsonp((options) => {
  261. let url = options.url;
  262. GM_log('[http jsonp]: %s', url);
  263.  
  264. if (this._isCheckVipReq(url)) {
  265. let res = Faker.fakeVipRes(options.params.authcookie);
  266. options.success(res);
  267. return true;
  268. }
  269. });
  270. }
  271.  
  272. }
  273.  
  274. class Switcher {
  275.  
  276. static switchTo(toType) {
  277. GM_log('switching to %s ...', toType);
  278.  
  279. if (toType === PLAYER_TYPE.Html5VOD && !Detector.isSupportHtml5()) {
  280. alert('╮(╯▽╰)╭ 你的浏览器播放不了html5视频~~~~');
  281. return;
  282. }
  283. // cookie 有效时间为一年
  284. let date = new Date();
  285. date.setFullYear(date.getFullYear() + 1);
  286.  
  287. DocCookies.set('player_forcedType', toType, {expires: date});
  288. document.location.reload();
  289. }
  290.  
  291. }
  292.  
  293. function registerMenu() {
  294. const MENU_NAME = {
  295. HTML5: 'HTML5播放器',
  296. FLASH: 'Flash播放器'
  297. };
  298.  
  299. let currType = DocCookies.get('player_forcedType');
  300. let [toType, name] = currType === PLAYER_TYPE.Html5VOD ? [PLAYER_TYPE.FlashVOD, MENU_NAME.FLASH] : [PLAYER_TYPE.Html5VOD, MENU_NAME.HTML5];
  301. GM_registerMenuCommand(name, () => Switcher.switchTo(toType), null);
  302. }
  303.  
  304.  
  305. registerMenu();
  306. Mocker.mock();
  307.  
  308. })();