Advertisement
Guest User

Untitled

a guest
Dec 27th, 2021
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.25 KB | None | 0 0
  1. "use strict";
  2.  
  3. (function() {
  4.  
  5. let DOCK_DOCKED = 'docked', DOCK_FLOATING = 'floating';
  6. let PAGE_CHANNEL = 'channel', PAGE_HOME = 'home', PAGE_SEARCH = 'search', PAGE_SUBSCRIPTIONS = 'subscriptions', PAGE_WATCH = 'watch';
  7. let SCREEN_PADDING = 16;
  8.  
  9. let currentAnimation, currentAnimationCancelled, currentDockMode, currentVideoId, currentPage;
  10. let queryParams, isNewPage;
  11. let screen, screenHeader, screenTitle, screenButtonClose, screenButtonMove, screenButtonResizeTopLeft, screenButtonResizeTopRight, screenButtonResizeBottomLeft, screenButtonResizeBottomRight, isScreenMove, isScreenResize;
  12. let iframe, iframeVideo, moviePlayer, moviePlayerObserver = new MutationObserver(onMoviePlayerObserve), playerAPI, video, videoTitle;
  13. let intervalCurrentTime, intervalIframeInitialize, intervalVideoInitialize;
  14. let localStorage = window.localStorage['rjgtav.youtube-mini-player'] ? JSON.parse(window.localStorage['rjgtav.youtube-mini-player']) : {};
  15.  
  16. // Thanks to https://www.reddit.com/user/roombascj
  17. // Workaround: Chrome doesn't fire 'resize' event when it adds the scrollbar
  18. // Source: https://pastebin.com/tgDxgPza
  19. let windowResizeListener = document.createElement('iframe');
  20. windowResizeListener.id = 'rj-resize-listener';
  21. windowResizeListener.onload = () => windowResizeListener.contentWindow.addEventListener('resize', onWindowResize);
  22.  
  23. window.addEventListener('scroll', onWindowScroll);
  24. document.addEventListener('DOMContentLoaded', onPageLoad);
  25. document.addEventListener('spfdone', onPageLoad);
  26.  
  27. // #YoutubeMaterialDesign
  28. document.addEventListener('yt-navigate-finish', onPageLoad);
  29. document.addEventListener('yt-update-title', onPageTitle);
  30.  
  31. function initializeIframe () {
  32. if (iframe != null) return;
  33.  
  34. screen = document.createElement('div');
  35. screen.id = 'rj-miniplayer-screen';
  36. screen.style.position = 'fixed';
  37. document.body.appendChild(screen);
  38.  
  39. screenHeader = document.createElement('div');
  40. screenHeader.className = 'rj-miniplayer-header';
  41. screen.appendChild(screenHeader);
  42.  
  43. screenButtonMove = document.createElement('div');
  44. screenButtonMove.className = 'rj-miniplayer-move';
  45. screenButtonMove.title = 'Move';
  46. screenButtonMove.style.backgroundImage = "url('" + chrome.extension.getURL('assets/button/move.png') + "')";
  47. screenButtonMove.addEventListener('mousedown', onScreenMouseDown);
  48. screenHeader.appendChild(screenButtonMove);
  49.  
  50. screenTitle = document.createElement('div');
  51. screenTitle.className = 'rj-miniplayer-title';
  52. screenTitle.addEventListener('click', onScreenTitleClick);
  53. screenHeader.appendChild(screenTitle);
  54.  
  55. screenButtonClose = document.createElement('div');
  56. screenButtonClose.className = 'rj-miniplayer-close';
  57. screenButtonClose.title = 'Close';
  58. screenButtonClose.style.backgroundImage = "url('" + chrome.extension.getURL('assets/button/close.png') + "')";
  59. screenButtonClose.addEventListener('click', onScreenButtonCloseClick);
  60. screenHeader.appendChild(screenButtonClose);
  61.  
  62. screenButtonResizeBottomRight = document.createElement('div');
  63. screenButtonResizeBottomRight.className = 'rj-miniplayer-resize bottom-right';
  64. screenButtonResizeBottomRight.title = 'Resize';
  65. screenButtonResizeBottomRight.addEventListener('mousedown', onScreenMouseDown);
  66. screen.appendChild(screenButtonResizeBottomRight);
  67.  
  68. screenButtonResizeTopRight = document.createElement('div');
  69. screenButtonResizeTopRight.className = 'rj-miniplayer-resize top-right';
  70. screenButtonResizeTopRight.title = 'Resize';
  71. screenButtonResizeTopRight.addEventListener('mousedown', onScreenMouseDown);
  72. screenHeader.appendChild(screenButtonResizeTopRight);
  73.  
  74. iframe = document.createElement('iframe');
  75. iframe.setAttribute('allowfullscreen', '1');
  76. iframe.setAttribute('frameborder', '0');
  77. iframe.onload = onIframeLoad;
  78. screen.appendChild(iframe);
  79. }
  80. function updateIframeSource () {
  81. if (iframe == null) return;
  82.  
  83. // If it's the same video, we keep playing it
  84. if (currentVideoId == queryParams.v) return;
  85. currentVideoId = queryParams.v;
  86.  
  87. iframeVideo = null;
  88. iframe.contentWindow.location.replace('https://www.youtube.com/embed/' + currentVideoId + '?autoplay=1&modestbranding=1&rel=0&showinfo=0');
  89.  
  90. // We periodically update the video.currentTime, so the Share button on Youtube works as expected
  91. // Update: this has a high performance impact :/
  92. //clearInterval(intervalCurrentTime);
  93. //intervalCurrentTime = setInterval(() => iframeVideo ? video.currentTime = iframeVideo.currentTime : null, 1000);
  94. }
  95.  
  96. function initializeVideo() {
  97. if (video != null) {
  98. video.removeEventListener('playing', onVideoPlay);
  99. video.removeEventListener('play', onVideoPlay);
  100. video.removeEventListener('seeking', onVideoSeek);
  101. video.removeEventListener('seeked', onVideoSeek);
  102. }
  103.  
  104. // Prevent Youtube from auto-playing
  105. // Workaround: we use intervals so this still runs when the tab isn't focused
  106. clearInterval(intervalVideoInitialize);
  107. intervalVideoInitialize = setInterval(() => {
  108. // Hide moviePlayer to increase performance
  109. moviePlayer = document.getElementById('movie_player');
  110. moviePlayerObserver.observe(moviePlayer, { childList: true });
  111. if (moviePlayer == null) return;
  112.  
  113. playerAPI = moviePlayer.parentNode;
  114. video = playerAPI.getElementsByTagName('video')[0];
  115. if (video == null) return;
  116.  
  117. video.pause();
  118. video.volume = 0;
  119.  
  120. video.addEventListener('playing', onVideoPlay);
  121. video.addEventListener('play', onVideoPlay);
  122. video.addEventListener('seeking', onVideoSeek);
  123. video.addEventListener('seeked', onVideoSeek);
  124.  
  125. clearInterval(intervalVideoInitialize);
  126. }, 25);
  127. }
  128.  
  129. function _updateScreenState(noAnimation, forceAnimation) {
  130. if (playerAPI == null || screen == null) return;
  131.  
  132. let dockMode = (currentPage != PAGE_WATCH || window.scrollY > screen.offsetHeight / 2) ? DOCK_FLOATING : DOCK_DOCKED;
  133.  
  134. // Cancel current animation
  135. if (currentDockMode != dockMode) {
  136. screen.removeEventListener('transitionend', onScreenTransitionEnd);
  137. currentAnimationCancelled = currentAnimation != null;
  138. currentAnimation = null;
  139. }
  140.  
  141. let positionEnd = _loadScreenState(dockMode), positionStart;
  142. let isAnimating = forceAnimation || currentDockMode != dockMode || currentAnimation != null;
  143. currentDockMode = dockMode;
  144.  
  145. if (isAnimating) {
  146. // Update animation states
  147. if (currentAnimation == null) {
  148. // Note: updating the height/width causes a stutter/redraw on the iframe. Therefore we do it at the beginning, where it is less noticeable
  149. positionStart = screen.getBoundingClientRect();
  150. screen.style.height = positionEnd.height + 'px';
  151. screen.style.width = positionEnd.width + 'px';
  152. screen.style.transform = 'scale(' + (positionStart.width / positionEnd.width) + ')';
  153. } else {
  154. positionStart = currentAnimation.start;
  155. }
  156. currentAnimation = { end: positionEnd, start: positionStart, onScreenTransitionEnd: currentAnimation ? currentAnimation.onScreenTransitionEnd : null };
  157.  
  158. if (currentDockMode == DOCK_DOCKED) {
  159. iframe.style.boxShadow = 'initial';
  160. }
  161.  
  162. // Animate
  163. if (!noAnimation) {
  164. window.requestAnimationFrame(() => window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
  165. // Abort animation if cancelled
  166. if (currentAnimation == null)
  167. return;
  168.  
  169. screen.style.transition = '.2s ease-in-out';
  170. if (currentAnimationCancelled) {
  171. screen.style.transform = '';
  172. onScreenTransitionEnd();
  173. } else {
  174. screen.style.transform =
  175. 'translateX(' + (currentAnimation.end.left - currentAnimation.start.left) + 'px)' +
  176. 'translateY(' + (currentAnimation.end.top - currentAnimation.start.top) + 'px)';
  177. }
  178.  
  179. // Prevent multiple addEventListener during a single transition (e.g. when scrolling, this method is spammed)
  180. if (!currentAnimation || !currentAnimation.onScreenTransitionEnd) {
  181. currentAnimation.onScreenTransitionEnd = onScreenTransitionEnd;
  182. screen.addEventListener('transitionend', onScreenTransitionEnd);
  183. }
  184. })));
  185. } else {
  186. onScreenTransitionEnd();
  187. }
  188. } else {
  189. // When the movie is docked at the top of the watch page
  190. screen.style.transition = '';
  191. screen.style.height = positionEnd.height + 'px';
  192. screen.style.width = positionEnd.width + 'px';
  193. screen.style.top = positionEnd.top + 'px';
  194. screen.style.left = positionEnd.left + 'px';
  195. }
  196. }
  197. function _updateScreenVisibility(visible) {
  198. if (visible) {
  199. if (moviePlayer) moviePlayer.style.display = 'none';
  200. screen.style.display = 'initial';
  201. _updateScreenState(true);
  202. } else {
  203. if (moviePlayer) moviePlayer.style.display = 'initial';
  204. screen.style.display = 'none';
  205. }
  206. }
  207.  
  208. function _calculateDefaultScreenState(dockMode) {
  209. let state = {};
  210. state.height = playerAPI.offsetHeight / 2;
  211. state.width = playerAPI.offsetWidth / 2;
  212.  
  213. // #YoutubeMaterialDesign - when switching to the Home page and such
  214. if (currentPage != PAGE_WATCH && (state.width == 0 || state.height == 0)) {
  215. let backupState = _loadScreenState(DOCK_DOCKED, PAGE_WATCH);
  216. state.height = backupState.height / 2;
  217. state.width = backupState.width / 2;
  218. }
  219.  
  220. if (currentPage == PAGE_CHANNEL || currentPage == PAGE_HOME || currentPage == PAGE_SUBSCRIPTIONS) {
  221. state.top = (window.innerHeight - state.height - SCREEN_PADDING);
  222. state.left = (window.innerWidth - state.width - SCREEN_PADDING - SCREEN_PADDING);
  223. }
  224.  
  225. if (currentPage == PAGE_SEARCH) {
  226. state.top = (window.innerHeight - state.height) / 2;
  227. state.left = (window.innerWidth - state.width - SCREEN_PADDING - SCREEN_PADDING);
  228. }
  229.  
  230. if (currentPage == PAGE_WATCH) {
  231. if (dockMode == DOCK_FLOATING) {
  232. let sidebar = document.getElementById('watch7-sidebar') || document.getElementById('related'); // #YoutubeMaterialDesign
  233. state.top = (window.innerHeight - state.height) / 2;
  234. state.left = (sidebar.getBoundingClientRect().left - (state.width - sidebar.offsetWidth) / 2);
  235. } else {
  236. state = playerAPI.getBoundingClientRect();
  237. }
  238. }
  239.  
  240. return _saveScreenState(dockMode, state.height, state.width, state.left, state.top);
  241. }
  242. function _loadScreenState(dockMode, page) {
  243. let state = localStorage.screenState = localStorage.screenState || {};
  244. state = state[page || currentPage] = state[page || currentPage] || {};
  245. state = state[dockMode] = state[dockMode] || {};
  246.  
  247. // Load default state, if none was previously saved
  248. if ((currentPage == PAGE_WATCH && dockMode == DOCK_DOCKED) || state.height == null || state.width == null || state.left == null || state.top == null)
  249. state = _calculateDefaultScreenState(dockMode);
  250.  
  251. // In case the screen is outside the screen, move it back inside
  252. if (dockMode == DOCK_FLOATING) {
  253. if (state.left + state.width + SCREEN_PADDING > window.innerWidth)
  254. state.left = window.innerWidth - state.width - SCREEN_PADDING;
  255. if (state.left < SCREEN_PADDING)
  256. state.left = SCREEN_PADDING;
  257. if (state.top + state.height + SCREEN_PADDING > window.innerHeight)
  258. state.top = window.innerHeight - state.height - SCREEN_PADDING;
  259. if (state.top < 100)
  260. state.top = 100;
  261. }
  262.  
  263. return state;
  264. }
  265. function _saveScreenState(dockMode, height, width, left, top) {
  266. let state = localStorage.screenState = localStorage.screenState || {};
  267. state = state[currentPage] = state[currentPage] || {};
  268. state = state[dockMode] = state[dockMode] || {};
  269.  
  270. // Save to localStorage
  271. state.height = height;
  272. state.width = width;
  273. state.left = left;
  274. state.top = top;
  275.  
  276. window.localStorage['rjgtav.youtube-mini-player'] = JSON.stringify(localStorage);
  277.  
  278. return state;
  279. }
  280.  
  281. function _getBoundingDocumentRect(e) {
  282. var box = e.getBoundingClientRect();
  283.  
  284. var body = document.body;
  285. var docEl = document.documentElement;
  286.  
  287. var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
  288. var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
  289.  
  290. var clientTop = docEl.clientTop || body.clientTop || 0;
  291. var clientLeft = docEl.clientLeft || body.clientLeft || 0;
  292.  
  293. var height = e.offsetHeight;
  294. var width = e.offsetWidth;
  295. var top = box.top + scrollTop - clientTop;
  296. var left = box.left + scrollLeft - clientLeft;
  297.  
  298. return { height, width, top, left };
  299. }
  300.  
  301. //---------------------------------------------------------------------
  302. // Event Handlers
  303. //---------------------------------------------------------------------
  304. // Thanks to http://stackoverflow.com/questions/34077641/how-to-detect-page-navigation-on-youtube-and-modify-html-before-page-is-rendered
  305. function onPageLoad() {
  306. let isFirstTime = iframe == null;
  307.  
  308. // Parse query params
  309. queryParams = {};
  310. let params = window.location.search.substring(1).split('&');
  311. params.forEach((param) => {
  312. let pair = param.split('=');
  313. queryParams[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
  314. })
  315.  
  316. // Parse current page
  317. currentPage = null;
  318. currentPage = currentPage == null && queryParams.v != null ? PAGE_WATCH : currentPage;
  319. currentPage = currentPage == null && queryParams.search_query != null ? PAGE_SEARCH : currentPage;
  320. currentPage = currentPage == null && window.location.href.indexOf('/channel/') != -1 ? PAGE_CHANNEL : currentPage;
  321. currentPage = currentPage == null && window.location.href.indexOf('/feeed/subscriptions') != -1 ? PAGE_SUBSCRIPTIONS : currentPage;
  322. currentPage = currentPage == null ? PAGE_HOME : currentPage;
  323.  
  324. if (currentPage == PAGE_WATCH) {
  325. initializeIframe();
  326. initializeVideo();
  327. onPageTitle();
  328. updateIframeSource();
  329.  
  330. // Make sure the mini player is visible (in case it was hidden by the user)
  331. _updateScreenVisibility(true);
  332. }
  333.  
  334. document.body.appendChild(windowResizeListener); // Note: we need to append this before the window.onload
  335. window.requestAnimationFrame(() => {
  336. _updateScreenState(isFirstTime, true);
  337. });
  338. }
  339. function onPageTitle(event) {
  340. let index = document.title.indexOf('- YouTube');
  341. if (index > -1)
  342. screenTitle.innerText = document.title.substring(0, index);
  343. }
  344.  
  345. function onIframeLoad(event) {
  346. // Workaround: we use intervals so this still runs when the tab isn't focused
  347. clearInterval(intervalIframeInitialize);
  348. intervalIframeInitialize = setInterval(() => {
  349. iframeVideo = iframe.contentWindow.document.getElementsByTagName('video')[0];
  350. if (iframeVideo) {
  351. iframeVideo.currentTime = video.currentTime;
  352. iframeVideo.onended = onIframeVideoEnded;
  353. clearInterval(intervalIframeInitialize);
  354. }
  355. }, 25);
  356. }
  357. function onIframeVideoEnded(event) {
  358. if (currentPage != PAGE_WATCH) return;
  359.  
  360. // In order to trigger Youtube's functionality (e.g. AutoPlay, Playlist), we need to make the original video trigger the 'ended' event
  361. // Therefore, we seek to the end and play it. See #onVideoSeek() for details
  362. video.currentTime = video.duration - 0.25;
  363. _updateScreenVisibility(false);
  364. }
  365.  
  366. function onScreenButtonCloseClick(event) {
  367. iframeVideo.pause();
  368. _updateScreenVisibility(false);
  369. }
  370.  
  371. function onScreenMouseDown(event) {
  372. event.preventDefault();
  373.  
  374. isScreenMove = event.target == screenButtonMove;
  375. isScreenResize = event.target == screenButtonResizeBottomRight || event.target == screenButtonResizeTopRight;
  376. iframe.style.pointerEvents = 'none';
  377. screen.classList.add('dragging');
  378.  
  379. screenButtonMove.removeEventListener('mousedown', onScreenMouseDown);
  380. screenButtonResizeBottomRight.removeEventListener('mousedown', onScreenMouseDown);
  381. screenButtonResizeTopRight.removeEventListener('mousedown', onScreenMouseDown);
  382.  
  383. document.body.addEventListener('mouseup', onScreenMouseUp);
  384. document.body.addEventListener('mousemove', onScreenMouseMove);
  385. }
  386. function onScreenMouseUp(event) {
  387. event.preventDefault();
  388.  
  389. if (isScreenMove) {
  390. // Save
  391. _saveScreenState(currentDockMode, screen.offsetHeight, screen.offsetWidth, screen.offsetLeft, screen.offsetTop);
  392. }
  393. if (isScreenResize) {
  394. var currentSize = screen.getBoundingClientRect();
  395. //screen.style.left =(screen.offsetLeft - (currentSize.width - screen.offsetWidth) / 2) + 'px';
  396. //screen.style.top = (screen.offsetTop - (currentSize.height - screen.offsetHeight) / 2) + 'px';
  397. screen.style.height = currentSize.height + 'px';
  398. screen.style.width = currentSize.width + 'px';
  399. screen.style.transform = '';
  400.  
  401. // Save
  402. _saveScreenState(currentDockMode, currentSize.height, currentSize.width, screen.offsetLeft, screen.offsetTop);
  403. }
  404.  
  405. isScreenMove = isScreenResize = false;
  406. iframe.style.pointerEvents = 'initial';
  407. screen.classList.remove('dragging');
  408.  
  409. document.body.removeEventListener('mouseup', onScreenMouseUp);
  410. document.body.removeEventListener('mousemove', onScreenMouseMove);
  411.  
  412. screenButtonMove.addEventListener('mousedown', onScreenMouseDown);
  413. screenButtonResizeBottomRight.addEventListener('mousedown', onScreenMouseDown);
  414. screenButtonResizeTopRight.addEventListener('mousedown', onScreenMouseDown);
  415. }
  416. function onScreenMouseMove(event) {
  417. event.preventDefault();
  418.  
  419. if (isScreenMove) {
  420. screen.style.left = Math.min(window.innerWidth - screen.offsetWidth - SCREEN_PADDING, Math.max(SCREEN_PADDING, parseInt(screen.style.left) + event.movementX)) + 'px';
  421. screen.style.top = Math.min(window.innerHeight - screen.offsetHeight - SCREEN_PADDING, Math.max(148, parseInt(screen.style.top) + event.movementY)) + 'px';
  422. }
  423. if (isScreenResize) {
  424. var currentSize = screen.getBoundingClientRect();
  425.  
  426. var width = screen.offsetWidth;
  427. var newWidth = currentSize.width + event.movementX;
  428. screen.style.transform = 'scaleX(' + newWidth / width + ') scaleY(' + newWidth / width + ')';
  429. }
  430. }
  431.  
  432. function onScreenTitleClick(event) {
  433. var videoUrl = window.location.href.split('&t=')[0];
  434. window.location.href = videoUrl + (iframeVideo ? ('&t=' + parseInt(iframeVideo.currentTime)) : '');
  435. }
  436.  
  437. function onScreenTransitionEnd(event) {
  438. if (event && event.propertyName != 'transform') return;
  439. screen.removeEventListener('transitionend', onScreenTransitionEnd);
  440.  
  441. if (currentDockMode == DOCK_FLOATING) {
  442. iframe.style.boxShadow = '0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22)';
  443. }
  444.  
  445. screen.className = currentDockMode;
  446. screen.style.left = currentAnimation.end.left + 'px';
  447. screen.style.top = currentAnimation.end.top + 'px';
  448. screen.style.transform = '';
  449. screen.style.transition = '';
  450.  
  451. currentAnimation = null;
  452. currentAnimationCancelled = false;
  453. }
  454.  
  455. function onMoviePlayerObserve(mutators) {
  456. if (mutators[0].addedNodes.length > 0)
  457. initializeVideo();
  458. }
  459.  
  460. function onVideoPlay(event) {
  461. if (video == null) return;
  462. if (!(video.duration > 0 && Math.abs(video.currentTime - video.duration) <= 1)) // See #onIframeVideoEnded() for details
  463. video.pause();
  464. }
  465. function onVideoSeek(event) {
  466. if (video == null) return;
  467.  
  468. if (video.duration > 0 && Math.abs(video.currentTime - video.duration) <= 1) { // See #onIframeVideoEnded() for details
  469. video.play();
  470. } else {
  471. video.pause();
  472. if (iframeVideo != null)
  473. iframeVideo.currentTime = video.currentTime;
  474.  
  475. // Make sure the mini player is visible (in case it was hidden by the user)
  476. _updateScreenVisibility(true);
  477. }
  478. }
  479.  
  480. function onWindowResize() {
  481. window.requestAnimationFrame(() => {
  482. _updateScreenState();
  483. });
  484. }
  485. function onWindowScroll(event) {
  486. _updateScreenState();
  487. }
  488.  
  489. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement