video speed

try to take over the world!

当前为 2024-01-12 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name video speed
  3. // @namespace http://tampermonkey.net/
  4. // @version 20240111.7
  5. // @description try to take over the world!
  6. // @author You
  7. // @match *://*/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. class KeepVideoTime {
  12. constructor(props) {
  13. this.key = "_video_cache_";
  14. this.obj = this.get() || {};
  15. this.id = null;
  16. }
  17. set(key, time) {
  18. this.obj[key] = time;
  19. this.store();
  20. }
  21. store() {
  22. localStorage.setItem(this.key, JSON.stringify(this.obj));
  23. }
  24.  
  25. get() {
  26. let data = localStorage.getItem(this.key);
  27. if (!data) {
  28. return null;
  29. }
  30. try {
  31. data = JSON.parse(data)
  32. } catch (e) {
  33. return null;
  34. } finally {
  35. return data;
  36. }
  37. }
  38. keep(getUrlId, video) {
  39. if (!this.id) {
  40. this.id = setInterval(() => {
  41. let vid = getUrlId();
  42. if (vid) {
  43. this.set(vid, video.currentTime);
  44. }
  45. }, 500);
  46. }
  47. }
  48. stop() {
  49. if (this.id) {
  50. clearInterval(this.id);
  51. this.id = null;
  52. }
  53. }
  54. }
  55. let KeepVideoTimeInstance = null;
  56. let onceMap = {};
  57. let clickDomOnce = function (selector, key) {
  58. if (key in onceMap) {
  59. return;
  60. }
  61. onceMap[key] = true;
  62. let id = setInterval(() => {
  63. let d = selector();
  64. if (d.length) {
  65. d[0].click();
  66. clearInterval(id);
  67. delete onceMap[key];
  68. }
  69. }, 500);
  70. }
  71. let DefaultAction = {
  72. "bilibili.com"(video) {
  73. return [
  74. {
  75. group: "屏幕操作",
  76. name: "宽屏",
  77. run: false,
  78. action() {
  79. clickDomOnce(() => document.getElementsByClassName('bpx-player-ctrl-web-enter'), "bpx-player-ctrl-web-enter");
  80. }
  81. },
  82. {
  83. group: "屏幕操作",
  84. name: "全屏",
  85. run: false,
  86. action() {
  87. clickDomOnce(() => document.getElementsByClassName('bpx-player-ctrl-full'), "bpx-player-ctrl-full");
  88. }
  89. },
  90. {
  91. group: "屏幕操作",
  92. name: "undo",
  93. run: true,
  94. action() {
  95. console.log("undo");
  96. }
  97. }
  98. ]
  99. },
  100. 'youtube.com'(videoInstance) {
  101. return [
  102. {
  103. group: "屏幕操作",
  104. name: "宽屏",
  105. run: false,
  106. action() {
  107. let video = videoInstance.getVideo();
  108. if (video.width < window.innerWidth * .8) {
  109. clickDomOnce(() => document.getElementsByClassName('ytp-size-button'), "ytp-size-button");
  110. }
  111. }
  112. },
  113. {
  114. group: "屏幕操作",
  115. name: "全屏",
  116. run: false,
  117. action() {
  118. clickDomOnce(() => document.getElementsByClassName('ytp-fullscreen-button'), "ytp-fullscreen-button");
  119. }
  120. },
  121. {
  122. group: "屏幕操作",
  123. name: "undo",
  124. run: true,
  125. action() {
  126. console.log("undo");
  127. }
  128. },
  129. {
  130. name: "记住最后播放时间",
  131. action(run, cache) {
  132. let video = videoInstance.getVideo();
  133. let getVid = () => {
  134. let vid = location.search.split('?')[1].split('&').map(_=>_.split('=')).filter(_=>_[0]==='v');
  135. if (vid.length === 1) {
  136. vid = vid[0][1];
  137. }
  138. return vid;
  139. }
  140. if (!KeepVideoTimeInstance) {
  141. KeepVideoTimeInstance = new KeepVideoTime();
  142. let vid = getVid();
  143. if (vid && vid in KeepVideoTimeInstance.obj) {
  144. if (video.currentTime < KeepVideoTimeInstance.obj[vid]) {
  145. video.currentTime = KeepVideoTimeInstance.obj[vid];
  146. }
  147. }
  148. }
  149. if (run) {
  150. KeepVideoTimeInstance.keep(getVid, video);
  151. } else {
  152. KeepVideoTimeInstance.stop();
  153. }
  154. },
  155. run: true,
  156. replay: true,
  157. }
  158. ]
  159. }
  160. };
  161. function checkWindow() {
  162. return window === parent.window;
  163. };
  164.  
  165. class DraggableDom {
  166. static Style = {
  167. // width: 100px;
  168. // height: 100px;
  169. backgroundColor: "lightblue",
  170. border: "2px solid #4CAF50",
  171. margin: "10px",
  172. padding: "10px",
  173. cursor: "move",
  174. position: "absolute",
  175. zIndex: 100000,
  176. borderRadius: "5px",
  177. }
  178. constructor(cache = new Cache()) {
  179. this.cache = cache;
  180. this.toggleKey = this.cache.obj.toggleKey;
  181. this.show = false;
  182. this.id = `DD_${new Date().getTime()}`;
  183. this.dom = null;
  184. this.mark = null;
  185. if (this.cache.obj.top > window.innerHeight) {
  186. this.cache.set("height", 0);
  187. }
  188. if (this.cache.obj.left > window.innerWidth) {
  189. this.cache.set("width", 0);
  190. }
  191. this.style = {
  192. ...DraggableDom.Style,
  193. width: `${cache.obj.width}px`,
  194. height: `${cache.obj.height}px`,
  195. top: `${cache.obj.top}px`,
  196. left: `${cache.obj.left}px`,
  197. opacity: cache.obj.opacity ? cache.obj.opacity : 1,
  198. };
  199. this.createDom();
  200. this.dom.id = this.id;
  201. this.bindEvent();
  202. }
  203. getDom() {
  204. if (this.dom) {
  205. let dom = this.dom.getElementsByClassName('dom');
  206. if (dom.length) {
  207. return dom[0];
  208. }
  209. }
  210. return null;
  211. }
  212. createDom() {
  213. // <button accessKey="T" onClick="clickSubmit()">提交按钮</button>
  214. let btn = document.createElement('button');
  215. btn.style.display = 'none';
  216. btn.setAttribute("accessKey", this.toggleKey);
  217. btn.onclick = () => {
  218. if (this.dom) {
  219. this.dom.style.display = this.show ? 'unset' : 'none';
  220. this.show = !this.show;
  221. }
  222. };
  223. this.mark = document.createElement('div');
  224. this.mark.style.width = "100vw";
  225. this.mark.style.height = "100vh";
  226. this.mark.style.position = "fixed";
  227. this.mark.style.left = "0";
  228. this.mark.style.top = "0";
  229. this.mark.style.display = "none";
  230. this.mark.style.zIndex = this.style.zIndex - 1;
  231. this.dom = document.createElement('div');
  232. this.dom .setAttribute('draggable', "true");
  233. this.dom.innerHTML = `<div>按下 &lt;ALT+${this.toggleKey}&gt; 可以[隐藏/显示]当前选项框</div><div class="dom"></div>`
  234. for (let i in this.style) {
  235. this.dom.style[i] = this.style[i];
  236. }
  237. document.body.append(this.dom);
  238. document.body.append(this.mark);
  239. document.body.append(btn);
  240. }
  241. bindEvent() {
  242. // JavaScript 代码,实现拖拽功能
  243. let draggableElement = this.dom;
  244. let $this = this;
  245. draggableElement.ondragstart = function(event) {
  246. event.dataTransfer.setData('text/plain', event.target.id);
  247. $this.mark.style.display = 'block';
  248. }
  249. draggableElement.ondragend = function(event) {
  250. $this.mark.style.display = 'none';
  251. }
  252.  
  253. document.body.ondragover = function(event) {
  254. event.preventDefault();
  255. };
  256.  
  257. document.body.ondrop = function(event) {
  258. event.preventDefault();
  259. var data = event.dataTransfer.getData('text/plain');
  260. if (!data) {
  261. return;
  262. }
  263. var draggedElement = document.getElementById(data);
  264.  
  265. // 获取鼠标位置
  266. var mouseX = event.clientX;
  267. var mouseY = event.clientY;
  268.  
  269. // 设置元素的新位置
  270. draggedElement.style.left = mouseX - draggedElement.offsetWidth / 2 + 'px';
  271. draggedElement.style.top = mouseY - draggedElement.offsetHeight / 2 + 'px';
  272. $this.cache.set("top", parseFloat(draggedElement.style.top));
  273. $this.cache.set("left", parseFloat(draggedElement.style.left));
  274. };
  275. }
  276. };
  277.  
  278. class Cache {
  279. static Reg = {
  280. Float: /^[0-9]+.[0-9]+$/,
  281. Int: /^[0-9]+$/,
  282. }
  283. static Default = {
  284. toggleKey: "X",
  285. top: 0,
  286. left: 0,
  287. width: 'unset',
  288. height: 'unset',
  289. speed: 1,
  290. Action: {},
  291. opacity: 1,
  292. };
  293. constructor(action) {
  294. let $this = this;
  295. this.key = "_video_speed_cache_";
  296. this.objType = {
  297. toggleKey(r) {
  298. r = r.toString();
  299. if (r.length > 1) {
  300. return r[0];
  301. } else if (r.length < 1) {
  302. return "X";
  303. } else {
  304. return r;
  305. }
  306. },
  307. opacity(r) {
  308. r = $this.toNumberFloat(r);
  309. return r <= 0 ? 0.1 : (r > 1 ? 1 : r);
  310. },
  311. top: $this.toNumberFloat,
  312. left: $this.toNumberFloat,
  313. // width: 'unset',
  314. // height: 'unset',
  315. speed(r) {
  316. return $this.toNumberFloat(r, 1)
  317. },
  318. };
  319. this.obj = this.get() || {};
  320. for (let i in Cache.Default) {
  321. if (!(i in this.obj)) {
  322. this.obj[i] = Cache.Default[i];
  323. }
  324. }
  325. }
  326. toNumberFloat(r, dv) {
  327. r = parseFloat(r.toString());
  328. return Cache.Reg.Float.test(r.toFixed(1)) ? r : (dv||0);
  329. }
  330. toNumberInt(r, dv) {
  331. r = parseInt(r.toString());
  332. return Cache.Reg.Int.test(r) ? r : (dv||0);
  333. }
  334. set(key, value) {
  335. if (key in this.objType) {
  336. this.obj[key] = this.objType[key](value);
  337. } else {
  338. this.obj[key] = value;
  339. }
  340. this.store();
  341. return this;
  342. }
  343. store() {
  344. localStorage.setItem(this.key, JSON.stringify(this.obj));
  345. }
  346. checkData(data) {
  347. for (let i in this.objType) {
  348. data[i] = this.objType[i](data[i]);
  349. }
  350. return data;
  351. }
  352. get() {
  353. let data = localStorage.getItem(this.key);
  354. if (!data) {
  355. return null;
  356. }
  357. try {
  358. data = JSON.parse(data)
  359. } catch (e) {
  360. return null;
  361. } finally {
  362. return this.checkData(data);
  363. }
  364. }
  365. clear() {
  366. setTimeout(() => {
  367. localStorage.removeItem(this.key);
  368. }, 1000);
  369. }
  370. }
  371.  
  372. class PlayDom {
  373. constructor(video, videoInstance, actions = []) {
  374. this.video = video;
  375. this.cache = new Cache();
  376. this.draggableDom = new DraggableDom(this.cache);
  377. this.action = {};
  378.  
  379. let ret = this.buildAction(actions);
  380.  
  381. this.createDom(ret.actionListForDom);
  382. ret.promise().then(() => {
  383. let fn = () => {
  384. let v = videoInstance.getVideo();
  385. if (v) {
  386. v.playbackRate = this.cache.obj.speed;
  387. }
  388. }
  389. fn();
  390. setInterval(() => {
  391. fn();
  392. },500);
  393. });
  394. }
  395.  
  396. buildAction(actions) {
  397. let cacheAction = this.cache.obj.Action;
  398. let actionList = [];
  399. let actionListForDom = {};
  400. let actionListForDom_ = [];
  401. actions.forEach(act => {
  402. this.action[act.name] = {
  403. action:()=>{},
  404. replay: false,
  405. }
  406. // act = { name: "", action() {}, group: "", run: false }
  407. if (!act.group) {
  408. act.group = act.name;
  409. }
  410. if (!(act.group in actionListForDom)) {
  411. actionListForDom[act.group] = [];
  412. }
  413. if (!(act.group in cacheAction)) {
  414. cacheAction[act.group] = "";
  415. } else if (cacheAction[act.group] === "") {
  416. cacheAction[act.group] = "none_" + new Date().getTime();
  417. }
  418. if (act.name === cacheAction[act.group]) {
  419. // 该主当前希望运行改方法
  420. actionList.push(act.action);
  421. cacheAction[act.group] = act.name;
  422. act.run = true;
  423. if (act.replay) {
  424. this.action[act.name] = {
  425. action: act.action,
  426. replay: act.replay,
  427. };
  428. }
  429. } else {
  430. if (act.run) {
  431. if (cacheAction[act.group] === "") {
  432. cacheAction[act.group] = act.name;
  433. actionList.push(act.action);
  434. if (act.replay) {
  435. this.action[act.name] = {
  436. action: act.action,
  437. replay: act.replay,
  438. };
  439. }
  440. } else {
  441. act.run = false;
  442. this.action[act.name] = {
  443. action: act.action,
  444. replay: false,
  445. };
  446. }
  447. } else {
  448. this.action[act.name] = {
  449. action: act.action,
  450. replay: false,
  451. };
  452. }
  453. }
  454. if (!act.replay) {
  455. this.action[act.name].replay = true;
  456. }
  457. actionListForDom[act.group].push({
  458. name: act.name,
  459. run: act.run
  460. });
  461. });
  462. for (let i in actionListForDom) {
  463. actionListForDom_.push({
  464. group: i,
  465. actions: actionListForDom[i]
  466. });
  467. }
  468. this.cache.obj.Action = Object.assign(this.cache.obj.Action, cacheAction);
  469. this.cache.store();
  470. return {
  471. promise: (function (actionLis, cache) {
  472. return new Promise(s => s()).then(() => {
  473. actionList.forEach(act => act(true, cache));
  474. return true;
  475. });
  476. }).bind(null,actionList,this.cache.obj),
  477. actionListForDom: actionListForDom_
  478. };
  479. }
  480. createDom(actionListForDom) {
  481. let styleDom = document.createElement('style');
  482. styleDom.innerHTML = `.play_dom_box {
  483. display: flex;
  484. padding: 5px;
  485. }
  486. .play_dom_btn {
  487. background: gainsboro;
  488. padding: 2px;
  489. margin-bottom: 2px;
  490. border-radius: 4px;
  491. width: 40px;
  492. text-align: center;
  493. cursor: pointer;
  494. }
  495. .play_dom_text {
  496. flex: 1;
  497. line-height: 80px;
  498. text-align: center;
  499. }`;
  500. let boxDom = document.createElement('div');
  501. boxDom.innerHTML = `
  502. <div class="play_dom_box">
  503. <div>
  504. <div class="play_dom_btn play_dom_btn_speed" tag="speed">-0.1</div>
  505. <div class="play_dom_btn play_dom_btn_speed" tag="speed">-0.2</div>
  506. <div class="play_dom_btn play_dom_btn_speed" tag="speed">-0.5</div>
  507. </div>
  508. <div class="play_dom_text play_dom_text_speed">${this.cache.obj.speed}</div>
  509. <div>
  510. <div class="play_dom_btn play_dom_btn_speed" tag="speed">+0.1</div>
  511. <div class="play_dom_btn play_dom_btn_speed" tag="speed">+0.2</div>
  512. <div class="play_dom_btn play_dom_btn_speed" tag="speed">+0.5</div>
  513. </div>
  514. </div>
  515. <div style="display: flex;">透明 【<div class="play_dom_text_opacity">${this.cache.obj.opacity}</div>】 <div class="play_dom_btn play_dom_btn_opacity" tag="speed">-0.1</div>
  516. &nbsp;&nbsp;&nbsp;<div class="play_dom_btn play_dom_btn_opacity" tag="speed">+0.1</div></div>
  517. <div>
  518. ${actionListForDom.map(act => {
  519. // {
  520. // group: "",
  521. // actions: [{name: '', run: ''}]
  522. // }
  523. if (act.actions.length > 1) {
  524. let dom = act.actions.map(a => {
  525. return `<input type="radio" tag="${a.name}" name="${act.group}" ${a.run ? 'checked' : ''} class="play_dom_check"/> ${a.name}`;
  526. }).join('');
  527. return `<div>${act.group} | ${dom}</div>`
  528. } else {
  529. let acti = act.actions[0];
  530. return `<div>${acti.name} <input tag="${acti.name}" type="checkbox" name="${act.group}" ${acti.run ? 'checked' : ''} class="play_dom_check"/></div>`
  531. }
  532. }).join(' ')}
  533. </div>`;
  534. let dom = this.draggableDom.getDom();
  535. dom.append(styleDom);
  536. dom.append(boxDom);
  537. let $this = this;
  538. let speedTextDom = boxDom.getElementsByClassName('play_dom_text_speed')[0];
  539. let opacityTextDom = boxDom.getElementsByClassName('play_dom_text_opacity')[0];
  540.  
  541. new Array(...boxDom.getElementsByClassName('play_dom_btn_opacity')).forEach(btn => {
  542. btn.onclick = function () {
  543. let sp = +this.innerText;
  544. $this.cache.set('opacity', (+$this.cache.obj.opacity + sp).toFixed(1));
  545. opacityTextDom.innerText = $this.cache.obj.opacity;
  546. $this.draggableDom.dom.style.opacity = $this.cache.obj.opacity;
  547. };
  548. });
  549. new Array(...boxDom.getElementsByClassName('play_dom_btn_speed')).forEach(btn => {
  550. btn.onclick = function () {
  551. let sp = +this.innerText;
  552. $this.cache.set('speed', (+$this.cache.obj.speed + sp).toFixed(2));
  553. speedTextDom.innerText = $this.cache.obj.speed;
  554. // $this.video.playbackRate = $this.cache.obj.speed;
  555. };
  556. });
  557. new Array(...boxDom.getElementsByClassName('play_dom_check')).forEach(btn => {
  558. btn.onchange = function () {
  559. let group = this.getAttribute('name');
  560. let actionName = this.getAttribute('tag');
  561. let currentAction = $this.action[actionName];
  562. if (this.checked) {
  563. $this.cache.obj.Action[group] = actionName;
  564. if (currentAction.replay) {
  565. currentAction.action(true, $this.cache.obj);
  566. } else {
  567. currentAction.action(true, $this.cache.obj);
  568. $this.action[actionName].action = () => {};
  569. }
  570. } else {
  571. $this.cache.obj.Action[group] = "";
  572. if (currentAction.replay) {
  573. currentAction.action(false, $this.cache.obj);
  574. }
  575. }
  576. $this.cache.store();
  577. };
  578. });
  579. }
  580. }
  581.  
  582. class Video {
  583. static getVideoMethod = {
  584. default() {
  585. let videos = document.getElementsByTagName('video');
  586. let video = false;
  587. if (videos.length) {
  588. if (videos.length === 1) {
  589. video = videos[0];
  590. } else {
  591. let ww = parseInt(window.innerWidth * 0.4);
  592. let v = videos.filter(v => v.width > ww);
  593. if (v.length) {
  594. video = v[0];
  595. }
  596. }
  597. }
  598. return video;
  599. },
  600. 'youtube.com'() {
  601. let videos = document.getElementsByTagName('video');
  602. return videos.length ? videos[0] : false;
  603. },
  604. 'bilibili.com'() {
  605. let bwpVideos = document.getElementsByTagName('bwp-video');
  606. let trVideos = document.getElementsByTagName('video');
  607. return bwpVideos.length ? bwpVideos[0] : (trVideos.length ? trVideos[0] : false);
  608. },
  609. }
  610. constructor() {
  611. this.baseDom = null;
  612. this.host =null;
  613. this.getVideoMethod = () => false;
  614. }
  615. getVideo() {
  616. return this.getVideoMethod();
  617. }
  618. init() {
  619. return new Promise(s => {
  620. setTimeout(() => {
  621. this.host = location.hostname.split('.').reverse().slice(0, 2).reverse().join('.');
  622. if (this.host in Video.getVideoMethod) {
  623. this.getVideoMethod = Video.getVideoMethod[this.host];
  624. }
  625. // 尝试推荐方法2次后使用默认方法不断尝试,超过一分钟后停止所有尝试
  626. let times = 2;
  627. let nexter = () => {
  628. this.getVideoMethod = Video.getVideoMethod["default"];
  629. times = 60;
  630. let id = setInterval(() => {
  631. let video = this.getVideoMethod();
  632. if (video) {
  633. clearInterval(id);
  634. s();
  635. } else {
  636. times--;
  637. if (!times) {
  638. clearInterval(id);
  639. console.log("放弃,没有希望了");
  640. }
  641. }
  642. }, 1000);
  643. };
  644.  
  645. let id = setInterval(() => {
  646. let video = this.getVideoMethod();
  647. if (video) {
  648. clearInterval(id);
  649. s();
  650. } else {
  651. times--;
  652. if (!times) {
  653. clearInterval(id);
  654. nexter();
  655. }
  656. }
  657. }, 500);
  658. s();
  659. }, 1000);
  660. })
  661. }
  662. }
  663. (function() {
  664. 'use strict';
  665. if (checkWindow()) {
  666. let video = new Video();
  667. video.init().then(() => {
  668. let v = video.getVideo();
  669. if (v) {
  670. console.log("play dom");
  671. window.playDom = new PlayDom(v, video, [
  672. ...(DefaultAction[video.host](video) || []),
  673. {
  674. group: "重置",
  675. name: "重置",
  676. run: false,
  677. action() {
  678. new Cache().clear();
  679. }
  680. },
  681. ]);
  682. }
  683. });
  684. }
  685. // Your code here...
  686. })();