VideoHelper

看片小助肘 - 支持片头跳过设置,倍速播放记忆,自动全屏设置,音量记忆、截屏功能和自动播放功能

  1. // ==UserScript==
  2. // @name VideoHelper
  3. // @namespace https://github.com/Cocowwy/Tampermonkey-Tools
  4. // @version 1.2
  5. // @description 看片小助肘 - 支持片头跳过设置,倍速播放记忆,自动全屏设置,音量记忆、截屏功能和自动播放功能
  6. // @author Cocowwy
  7. // @match *://*/*
  8. // @updateUrl https://github.com/Cocowwy/Tampermonkey-Tools/blob/master/VideoHelper.js
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. const STORAGE_KEY = 'video_auto_jump_time';
  16. const SPEED_STORAGE_KEY = 'video_playback_speed';
  17. const AUTO_FULLSCREEN_KEY = 'video_auto_fullscreen';
  18. const VOLUME_STORAGE_KEY = 'video_volume';
  19. const AUTO_PLAY_KEY = 'video_auto_play';
  20. let isPanelVisible = false;
  21. let isDragging = false;
  22. let startX, startY, initialX, initialY;
  23.  
  24. // 创建折叠按钮
  25. const toggleBtn = document.createElement('div');
  26. toggleBtn.innerHTML = '📽️';
  27. toggleBtn.style.cssText = `
  28. position: fixed;
  29. left: 10px;
  30. top: 50%;
  31. transform: translateY(-50%);
  32. cursor: pointer;
  33. z-index: 9999;
  34. opacity: 0.7;
  35. transition: opacity 0.3s;
  36. font-size: 20px;
  37. border-radius: 50%;
  38. width: 40px;
  39. height: 40px;
  40. display: flex;
  41. align-items: center;
  42. justify-content: center;
  43. color: white;
  44. background: rgba(0, 0, 0, 0.85);
  45. `;
  46. toggleBtn.addEventListener('mouseenter', () => toggleBtn.style.opacity = '1');
  47. toggleBtn.addEventListener('mouseleave', () => toggleBtn.style.opacity = '0.7');
  48. document.body.appendChild(toggleBtn);
  49.  
  50. // 创建配置面板
  51. const panel = document.createElement('div');
  52. panel.style.cssText = `
  53. position: fixed;
  54. left: -300px; /* 初始隐藏位置 */
  55. top: 50%;
  56. transform: translateY(-50%);
  57. background: rgba(0, 0, 0, 0.85);
  58. padding: 20px;
  59. border-radius: 8px;
  60. box-shadow: 0 0 15px rgba(0,0,0,0.3);
  61. transition: all 0.3s ease;
  62. color: white;
  63. width: 260px;
  64. z-index: 9998;
  65. backdrop-filter: blur(5px);
  66. `;
  67.  
  68. // 拖动处理函数
  69. const handleMouseDown = (e) => {
  70. isDragging = true;
  71. startX = e.clientX;
  72. startY = e.clientY;
  73. initialX = panel.offsetLeft;
  74. initialY = panel.offsetTop;
  75. panel.style.transition = 'none';
  76. };
  77.  
  78. const handleMouseMove = (e) => {
  79. if (!isDragging) return;
  80. const dx = e.clientX - startX;
  81. const dy = e.clientY - startY;
  82. panel.style.left = `${initialX + dx}px`;
  83. panel.style.top = `${initialY + dy}px`;
  84. };
  85.  
  86. const handleMouseUp = () => {
  87. isDragging = false;
  88. panel.style.transition = 'all 0.3s ease';
  89. };
  90.  
  91. // 面板内容
  92. panel.innerHTML = `
  93. <div style="margin-bottom: 15px; font-weight: bold; font-size: 16px;">
  94. 🎬 视频小助肘
  95. <div style="float: right; cursor: pointer; opacity: 0.7;" id="closeBtn">×</div>
  96. </div>
  97. <div style="margin-bottom: 15px;">
  98. <label style="display: block; margin-bottom: 8px; font-size: 14px;">
  99. 跳过片头时间(秒):
  100. </label>
  101. <input type="number"
  102. id="timeInput"
  103. style="width: 95%;
  104. padding: 8px;
  105. border: 1px solid rgba(255,255,255,0.2);
  106. border-radius: 4px;
  107. background: rgba(255,255,255,0.1);
  108. color: white;
  109. margin-bottom: 15px;">
  110. </input>
  111. <label style="display: block; margin-bottom: 8px; font-size: 14px;">
  112. 播放倍速:
  113. </label>
  114. <input type="number"
  115. id="speedInput"
  116. style="width: 95%;
  117. padding: 8px;
  118. border: 1px solid rgba(255,255,255,0.2);
  119. border-radius: 4px;
  120. background: rgba(255,255,255,0.1);
  121. color: white;
  122. margin-bottom: 15px;">
  123. </input>
  124. <label style="display: block; margin-bottom: 8px; font-size: 14px;">
  125. 音量 (0 - 100):
  126. </label>
  127. <input type="number"
  128. id="volumeInput"
  129. min="0"
  130. max="100"
  131. style="width: 95%;
  132. padding: 8px;
  133. border: 1px solid rgba(255,255,255,0.2);
  134. border-radius: 4px;
  135. background: rgba(255,255,255,0.1);
  136. color: white;
  137. margin-bottom: 15px;">
  138. </input>
  139. <label style="display: block; margin-bottom: 8px; font-size: 14px;">
  140. 自动全屏:
  141. </label>
  142. <input type="checkbox" id="autoFullscreenCheckbox">
  143. <label style="display: block; margin-bottom: 8px; font-size: 14px;">
  144. 自动播放:
  145. </label>
  146. <input type="checkbox" id="autoPlayCheckbox">
  147. <button id="saveBtn"
  148. style="width: 100%;
  149. padding: 8px;
  150. background: #2196F3;
  151. border: none;
  152. border-radius: 4px;
  153. color: white;
  154. cursor: pointer;
  155. transition: background 0.3s;">
  156. 保存
  157. </button>
  158. <button id="screenshotBtn"
  159. style="width: 100%;
  160. padding: 8px;
  161. background: #4CAF50;
  162. border: none;
  163. border-radius: 4px;
  164. color: white;
  165. cursor: pointer;
  166. transition: background 0.3s;
  167. margin-top: 10px;">
  168. 视频截屏
  169. </button>
  170. </div>
  171. `;
  172.  
  173. // 元素引用
  174. const timeInput = panel.querySelector('#timeInput');
  175. const speedInput = panel.querySelector('#speedInput');
  176. const volumeInput = panel.querySelector('#volumeInput');
  177. const autoFullscreenCheckbox = panel.querySelector('#autoFullscreenCheckbox');
  178. const autoPlayCheckbox = panel.querySelector('#autoPlayCheckbox');
  179. const saveBtn = panel.querySelector('#saveBtn');
  180. const closeBtn = panel.querySelector('#closeBtn');
  181. const screenshotBtn = panel.querySelector('#screenshotBtn');
  182.  
  183. // 事件监听
  184. saveBtn.addEventListener('mouseenter', () => saveBtn.style.background = '#1976D2');
  185. saveBtn.addEventListener('mouseleave', () => saveBtn.style.background = '#2196F3');
  186. closeBtn.addEventListener('click', () => togglePanel(false));
  187. panel.addEventListener('mousedown', handleMouseDown);
  188. document.addEventListener('mousemove', handleMouseMove);
  189. document.addEventListener('mouseup', handleMouseUp);
  190.  
  191. // 功能函数
  192. function togglePanel(show) {
  193. isPanelVisible = show;
  194. panel.style.left = show ? '60px' : '-300px'; // 展开时距离左侧60px
  195. toggleBtn.innerHTML = show ? 'X' : '📽️';
  196. }
  197.  
  198. toggleBtn.addEventListener('click', () => togglePanel(!isPanelVisible));
  199.  
  200. // 初始化存储值
  201. timeInput.value = localStorage.getItem(STORAGE_KEY) || 0;
  202. speedInput.value = localStorage.getItem(SPEED_STORAGE_KEY) || 1;
  203. volumeInput.value = localStorage.getItem(VOLUME_STORAGE_KEY) || 100;
  204. autoFullscreenCheckbox.checked = localStorage.getItem(AUTO_FULLSCREEN_KEY) === 'true';
  205. autoPlayCheckbox.checked = localStorage.getItem(AUTO_PLAY_KEY) === 'true';
  206.  
  207. saveBtn.addEventListener('click', () => {
  208. const jumpTime = parseInt(timeInput.value, 10);
  209. const playbackSpeed = parseFloat(speedInput.value);
  210. const volume = parseInt(volumeInput.value, 10);
  211. const autoFullscreen = autoFullscreenCheckbox.checked;
  212. const autoPlay = autoPlayCheckbox.checked;
  213.  
  214. if (!isNaN(jumpTime) && !isNaN(playbackSpeed) && !isNaN(volume) && volume >= 0 && volume <= 100) {
  215. localStorage.setItem(STORAGE_KEY, jumpTime);
  216. localStorage.setItem(SPEED_STORAGE_KEY, playbackSpeed);
  217. localStorage.setItem(VOLUME_STORAGE_KEY, volume);
  218. localStorage.setItem(AUTO_FULLSCREEN_KEY, autoFullscreen);
  219. localStorage.setItem(AUTO_PLAY_KEY, autoPlay);
  220.  
  221. const video = document.querySelector('video');
  222. if (video) {
  223. video.currentTime = jumpTime;
  224. video.playbackRate = playbackSpeed;
  225. video.volume = volume / 100;
  226. if (autoPlay) {
  227. video.play().catch(() => {
  228. showToast('⚠️ 自动播放被浏览器阻止,请手动播放');
  229. });
  230. }
  231. }
  232.  
  233. showToast('✅ 设置已保存');
  234. } else {
  235. showToast('⚠️ 请输入有效数字,音量范围为 0 - 100');
  236. }
  237. });
  238.  
  239. // 视频处理
  240. const video = document.querySelector('video');
  241. if (video) {
  242. video.addEventListener('loadedmetadata', () => {
  243. const jumpTime = parseInt(localStorage.getItem(STORAGE_KEY), 10);
  244. const playbackSpeed = parseFloat(localStorage.getItem(SPEED_STORAGE_KEY));
  245. const volume = parseFloat(localStorage.getItem(VOLUME_STORAGE_KEY));
  246. const autoFullscreen = localStorage.getItem(AUTO_FULLSCREEN_KEY) === 'true';
  247.  
  248. if (!isNaN(jumpTime) && jumpTime <= video.duration) {
  249. video.currentTime = jumpTime;
  250. }
  251. if (!isNaN(playbackSpeed)) {
  252. video.playbackRate = playbackSpeed;
  253. }
  254. if (!isNaN(volume) && volume >= 0 && volume <= 100) {
  255. video.volume = volume / 100;
  256. }
  257. });
  258.  
  259. video.addEventListener('play', () => {
  260. const autoFullscreen = localStorage.getItem(AUTO_FULLSCREEN_KEY) === 'true';
  261. if (autoFullscreen) {
  262. if (video.requestFullscreen) {
  263. video.requestFullscreen();
  264. } else if (video.webkitRequestFullscreen) {
  265. video.webkitRequestFullscreen();
  266. } else if (video.msRequestFullscreen) {
  267. video.msRequestFullscreen();
  268. } else if (video.mozRequestFullscreen) {
  269. video.mozRequestFullscreen();
  270. } else {
  271. showToast('⚠️ 当前浏览器不支持全屏功能');
  272. }
  273. }
  274. });
  275. }
  276.  
  277. // 截屏功能
  278. screenshotBtn.addEventListener('click', () => {
  279. const video = document.querySelector('video');
  280. if (video) {
  281. const canvas = document.createElement('canvas');
  282. canvas.width = video.videoWidth;
  283. canvas.height = video.videoHeight;
  284. const ctx = canvas.getContext('2d');
  285. ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
  286.  
  287. const link = document.createElement('a');
  288. link.href = canvas.toDataURL('image/png');
  289. link.download = 'video_screenshot.png';
  290. link.click();
  291.  
  292. showToast('📸 截屏已保存');
  293. } else {
  294. showToast('⚠️ 未找到视频元素');
  295. }
  296. });
  297.  
  298. // 提示信息函数
  299. function showToast(message) {
  300. const toast = document.createElement('div');
  301. toast.textContent = message;
  302. toast.style.cssText = `
  303. position: fixed;
  304. bottom: 20px;
  305. left: 50%;
  306. transform: translateX(-50%);
  307. background: rgba(0,0,0,0.8);
  308. color: white;
  309. padding: 12px 24px;
  310. border-radius: 4px;
  311. font-size: 14px;
  312. z-index: 10000;
  313. animation: fadeInOut 2.5s;
  314. `;
  315. document.body.appendChild(toast);
  316. setTimeout(() => toast.remove(), 2500);
  317. }
  318.  
  319. // 注入CSS动画
  320. const style = document.createElement('style');
  321. style.textContent = `
  322. @keyframes fadeInOut {
  323. 0% { opacity: 0; transform: translate(-50%, 10px); }
  324. 15% { opacity: 1; transform: translate(-50%, 0); }
  325. 85% { opacity: 1; transform: translate(-50%, 0); }
  326. 100% { opacity: 0; transform: translate(-50%, -10px); }
  327. }
  328. `;
  329. document.head.appendChild(style);
  330.  
  331. document.body.appendChild(panel);
  332. })();