HTML5 Video Screenshot with Button

Capture screenshot from HTML5 video player using button or Ctrl+Shift+S

  1. // ==UserScript==
  2. // @name HTML5 Video Screenshot with Button
  3. // @version 1.0
  4. // @description Capture screenshot from HTML5 video player using button or Ctrl+Shift+S
  5. // @match *://*/*
  6. // @grant none
  7. // @license MIT
  8. // @namespace https://greasyfork.org/users/1349839
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. function captureScreenshot(video) {
  15. const canvas = document.createElement('canvas');
  16. canvas.width = video.videoWidth;
  17. canvas.height = video.videoHeight;
  18. canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
  19.  
  20. canvas.toBlob((blob) => {
  21. navigator.clipboard.write([
  22. new ClipboardItem({ 'image/png': blob })
  23. ]).then(() => {
  24. showTip('Screenshot copied to clipboard');
  25. }).catch((error) => {
  26. showTip('Failed to copy screenshot', true);
  27. console.error('Failed to copy screenshot: ', error);
  28. });
  29. }, 'image/png');
  30. }
  31.  
  32. function showTip(message, isError = false) {
  33. const tip = document.createElement('div');
  34. tip.textContent = message;
  35. tip.style.position = 'fixed';
  36. tip.style.bottom = '20px';
  37. tip.style.right = '20px';
  38. tip.style.padding = '10px';
  39. tip.style.backgroundColor = isError ? 'rgba(255, 0, 0, 0.8)' : 'rgba(0, 0, 0, 0.8)';
  40. tip.style.color = 'white';
  41. tip.style.borderRadius = '5px';
  42. tip.style.zIndex = '9999999';
  43. document.body.appendChild(tip);
  44. setTimeout(() => tip.remove(), 3000);
  45. }
  46.  
  47. function createButton(text, onClick) {
  48. const button = document.createElement('button');
  49. button.textContent = text;
  50. button.style.padding = '5px 10px';
  51. button.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
  52. button.style.color = 'white';
  53. button.style.border = 'none';
  54. button.style.borderRadius = '5px';
  55. button.style.cursor = 'pointer';
  56. button.style.fontSize = '16px';
  57. button.style.transition = 'opacity 0.3s';
  58. button.style.opacity = '0';
  59. button.onclick = onClick;
  60. return button;
  61. }
  62.  
  63. function addControlsToVideo(video) {
  64. const wrapper = document.createElement('div');
  65. wrapper.style.position = 'absolute';
  66. wrapper.style.top = '10px';
  67. wrapper.style.right = '10px';
  68. wrapper.style.zIndex = '9999998';
  69.  
  70. const screenshotButton = createButton('📷', (e) => {
  71. e.preventDefault();
  72. e.stopPropagation();
  73. captureScreenshot(video);
  74. });
  75.  
  76. wrapper.appendChild(screenshotButton);
  77.  
  78. let hideTimeout;
  79. let isMouseOverControls = false;
  80.  
  81. function showControls() {
  82. screenshotButton.style.opacity = '1';
  83. clearTimeout(hideTimeout);
  84. if (!isMouseOverControls) {
  85. hideTimeout = setTimeout(() => {
  86. if (!isMouseOverControls) {
  87. screenshotButton.style.opacity = '0';
  88. }
  89. }, 2000);
  90. }
  91. }
  92.  
  93. // Show controls on mouse move and hide after 2 seconds of inactivity
  94. video.parentElement.addEventListener('mousemove', showControls);
  95. video.parentElement.addEventListener('mouseenter', showControls);
  96.  
  97. // Clear timeout when leaving the video area
  98. video.parentElement.addEventListener('mouseleave', () => {
  99. if (!isMouseOverControls) {
  100. screenshotButton.style.opacity = '0';
  101. clearTimeout(hideTimeout);
  102. }
  103. });
  104.  
  105. // Keep controls visible when mouse is over them
  106. wrapper.addEventListener('mouseenter', () => {
  107. isMouseOverControls = true;
  108. clearTimeout(hideTimeout);
  109. screenshotButton.style.opacity = '1';
  110. });
  111.  
  112. wrapper.addEventListener('mouseleave', () => {
  113. isMouseOverControls = false;
  114. showControls();
  115. });
  116.  
  117. // Create a wrapper for the video if it doesn't exist
  118. let videoWrapper = video.parentElement;
  119. if (!videoWrapper.style.position || videoWrapper.style.position === 'static') {
  120. videoWrapper = document.createElement('div');
  121. videoWrapper.style.position = 'relative';
  122. videoWrapper.style.display = 'inline-block';
  123. video.parentNode.insertBefore(videoWrapper, video);
  124. videoWrapper.appendChild(video);
  125. }
  126.  
  127. videoWrapper.appendChild(wrapper);
  128.  
  129. // Add keyboard shortcut functionality to the button
  130. screenshotButton.addEventListener('keydown', function(e) {
  131. if (e.ctrlKey && e.shiftKey && e.key === 'S') {
  132. e.preventDefault();
  133. captureScreenshot(video);
  134. }
  135. });
  136. }
  137.  
  138. // Listen for the shortcut key (Ctrl+Shift+S)
  139. document.addEventListener('keydown', function(e) {
  140. if (e.ctrlKey && e.shiftKey && e.key === 'S') {
  141. e.preventDefault();
  142. const video = document.querySelector('video');
  143. if (video) {
  144. captureScreenshot(video);
  145. } else {
  146. showTip('No video element found on the page', true);
  147. }
  148. }
  149. });
  150.  
  151. // Add controls to video players
  152. function addControlsToVideos() {
  153. const videos = document.querySelectorAll('video');
  154. videos.forEach(addControlsToVideo);
  155. }
  156.  
  157. // Initial addition of controls
  158. addControlsToVideos();
  159.  
  160. // Use a MutationObserver to add controls to dynamically loaded videos
  161. const observer = new MutationObserver((mutations) => {
  162. mutations.forEach((mutation) => {
  163. if (mutation.addedNodes) {
  164. mutation.addedNodes.forEach((node) => {
  165. if (node.nodeName === 'VIDEO') {
  166. addControlsToVideo(node);
  167. } else if (node.querySelector) {
  168. const videos = node.querySelectorAll('video');
  169. videos.forEach(addControlsToVideo);
  170. }
  171. });
  172. }
  173. });
  174. });
  175.  
  176. observer.observe(document.body, {
  177. childList: true,
  178. subtree: true
  179. });
  180. })();