Video Popout and No scroll on Click timestamps

Adds a button for watching YouTube videos in picture in picture mode while viewing comments and prevents the page from scrolling up if the user clicks a timestamp in the comments

  1. // ==UserScript==
  2. // @name Video Popout and No scroll on Click timestamps
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2
  5. // @description Adds a button for watching YouTube videos in picture in picture mode while viewing comments and prevents the page from scrolling up if the user clicks a timestamp in the comments
  6. // @author TetteDev
  7. // @license GPLv3
  8. // @match *://*.youtube.com/watch*
  9. // @icon https://cdn-icons-png.flaticon.com/512/1412/1412577.png
  10. // @grant GM_info
  11. // @grant GM_addElement
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. var seconds=0;
  18. function timestampToSeconds(t){
  19. let parts = t.split(':').reverse();
  20. if (parts.length<2){ return false; }
  21. seconds = 0;
  22. for(let i=0; i<parts.length; i++){
  23. switch (i) {
  24. case 0: seconds += (+parts[i]); break;
  25. case 1: seconds += (+parts[i])*60; break;
  26. case 2: seconds += (+parts[i])*60*60; break;
  27. case 3: seconds += (+parts[i])*60*60*24; break;
  28. }
  29. }
  30. return Number.isInteger(seconds);
  31. }
  32. document.addEventListener("click", function(e){
  33. if(e.target.tagName=="A"){
  34. if(timestampToSeconds(e.target.innerText)){
  35. e.preventDefault();
  36. e.stopPropagation();
  37. e.stopImmediatePropagation();
  38. movie_player.seekTo(seconds);
  39. return;
  40. }
  41. } else if(e.target.closest("a#endpoint")){/*chapters*/
  42. if(timestampToSeconds(e.target.closest("a#endpoint").querySelector("#details #time").innerText)){
  43. e.preventDefault();
  44. e.stopPropagation();
  45. e.stopImmediatePropagation();
  46. movie_player.seekTo(seconds);
  47. return;
  48. }
  49. }
  50. }, {capture: true} );
  51.  
  52. if ('pictureInPictureEnabled' in document) {
  53. const videoElement = document.getElementsByClassName("video-stream")[0];
  54. const pipButtonElement = GM_addElement(document.body, "button", {
  55. textContent: "Pip Mode",
  56. });
  57.  
  58. pipButtonElement.style.position = "fixed";
  59. pipButtonElement.style.right = "2rem";
  60. pipButtonElement.style.bottom = "2rem";
  61. pipButtonElement.style.visibility = "hidden";
  62. pipButtonElement.style.width = "10rem";
  63. pipButtonElement.style.height = "3.5rem";
  64. pipButtonElement.style.borderRadius = "10px";
  65. pipButtonElement.style.backgroundColor = "#ff0000";
  66. pipButtonElement.style.border = `1px solid ${pipButtonElement.style.backgroundColor}`;
  67. pipButtonElement.style.fontFamily = "Roboto,Arial,sans-serif";
  68. pipButtonElement.style.color = "#f1f1f1";
  69. pipButtonElement.style.cursor = "pointer";
  70.  
  71. const isVideoVisible = (element) => {
  72. const rect = element.getBoundingClientRect();
  73.  
  74. if (rect.top + rect.height > 0 && rect.top < window.innerHeight) {
  75. return true;
  76. }
  77. return false;
  78. };
  79. function urlIsYoutubeVideo(currentUrl) {
  80. const regex = /(youtu.*be.*)\/(watch\?v=|embed\/|v|shorts|)(.*?((?=[&#?])|$))/;
  81. return currentUrl.match(regex);
  82. }
  83.  
  84. // Shows the "Pip" button if the main video is scrolled out of view but the user closed the PiP instance
  85. videoElement.addEventListener("leavepictureinpicture", (event) => {
  86. if (!urlIsYoutubeVideo(window.location.href) && pipButtonElement.style.visibility !== "hidden") { pipButtonElement.style.visibility = "hidden"; }
  87.  
  88. if (!isVideoVisible(videoElement)) {
  89. pipButtonElement.style.visibility = "visible";
  90. pipButtonElement.click = null;
  91. pipButtonElement.addEventListener("click", async () => {
  92. pipButtonElement.disabled = true;
  93.  
  94. try {
  95. await videoElement.requestPictureInPicture();
  96. pipButtonElement.style.visibility = "hidden";
  97. } catch (err) {
  98. console.log(err)
  99. } finally {
  100. pipButtonElement.disabled = false;
  101. }
  102. });
  103. }
  104. });
  105.  
  106. function shouldNotIntercept(navigationEvent) {
  107. return (
  108. !navigationEvent.canIntercept ||
  109. // If this is just a hashChange,
  110. // just let the browser handle scrolling to the content.
  111. navigationEvent.hashChange ||
  112. // If this is a download,
  113. // let the browser perform the download.
  114. navigationEvent.downloadRequest ||
  115. // If this is a form submission,
  116. // let that go to the server.
  117. navigationEvent.formData
  118. );
  119. }
  120. navigation.addEventListener("navigate", async e => {
  121. // Needed only when .intercept in called on 'e' ??
  122. if (shouldNotIntercept(e)) return;
  123.  
  124. if (!urlIsYoutubeVideo(e.destination.url) && pipButtonElement.style.visibility !== "hidden") { pipButtonElement.style.visibility = "hidden"; }
  125. if (urlIsYoutubeVideo(e.destination.url) && isVideoVisible(videoElement) && pipButtonElement.style.visibility !== "hidden") {
  126. pipButtonElement.style.visibility = "hidden";
  127. }
  128.  
  129. if (videoElement === document.pictureInPictureElement && !urlIsYoutubeVideo(e.destination.url)) {
  130. await document.exitPictureInPicture();
  131. }
  132. });
  133.  
  134. document.addEventListener("scroll", async () => {
  135. if (isVideoVisible(videoElement)) {
  136. pipButtonElement.style.visibility = "hidden";
  137. try {
  138. if (videoElement === document.pictureInPictureElement) {
  139. await document.exitPictureInPicture();
  140. }
  141. } catch (err) {
  142. console.log(err)
  143. }
  144. } else {
  145. if (videoElement !== document.pictureInPictureElement) {
  146. pipButtonElement.style.visibility = "visible";
  147. pipButtonElement.addEventListener("click", async () => {
  148. pipButtonElement.disabled = true;
  149.  
  150. try {
  151. await videoElement.requestPictureInPicture();
  152. pipButtonElement.style.visibility = "hidden";
  153. } catch (err) {
  154. console.log(err)
  155. } finally {
  156. pipButtonElement.disabled = false;
  157. }
  158. });
  159. }
  160. }
  161. });
  162. }
  163. else {
  164. alert(`No support of PIP in Browser, disable the userscript named '${GM_info.script.name}'`);
  165. }
  166. })();