Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- "use strict";
- (function() {
- let DOCK_DOCKED = 'docked', DOCK_FLOATING = 'floating';
- let PAGE_CHANNEL = 'channel', PAGE_HOME = 'home', PAGE_SEARCH = 'search', PAGE_SUBSCRIPTIONS = 'subscriptions', PAGE_WATCH = 'watch';
- let SCREEN_PADDING = 16;
- let currentAnimation, currentAnimationCancelled, currentDockMode, currentVideoId, currentPage;
- let queryParams, isNewPage;
- let screen, screenHeader, screenTitle, screenButtonClose, screenButtonMove, screenButtonResizeTopLeft, screenButtonResizeTopRight, screenButtonResizeBottomLeft, screenButtonResizeBottomRight, isScreenMove, isScreenResize;
- let iframe, iframeVideo, moviePlayer, moviePlayerObserver = new MutationObserver(onMoviePlayerObserve), playerAPI, video, videoTitle;
- let intervalCurrentTime, intervalIframeInitialize, intervalVideoInitialize;
- let localStorage = window.localStorage['rjgtav.youtube-mini-player'] ? JSON.parse(window.localStorage['rjgtav.youtube-mini-player']) : {};
- // Thanks to https://www.reddit.com/user/roombascj
- // Workaround: Chrome doesn't fire 'resize' event when it adds the scrollbar
- // Source: https://pastebin.com/tgDxgPza
- let windowResizeListener = document.createElement('iframe');
- windowResizeListener.id = 'rj-resize-listener';
- windowResizeListener.onload = () => windowResizeListener.contentWindow.addEventListener('resize', onWindowResize);
- window.addEventListener('scroll', onWindowScroll);
- document.addEventListener('DOMContentLoaded', onPageLoad);
- document.addEventListener('spfdone', onPageLoad);
- // #YoutubeMaterialDesign
- document.addEventListener('yt-navigate-finish', onPageLoad);
- document.addEventListener('yt-update-title', onPageTitle);
- function initializeIframe () {
- if (iframe != null) return;
- screen = document.createElement('div');
- screen.id = 'rj-miniplayer-screen';
- screen.style.position = 'fixed';
- document.body.appendChild(screen);
- screenHeader = document.createElement('div');
- screenHeader.className = 'rj-miniplayer-header';
- screen.appendChild(screenHeader);
- screenButtonMove = document.createElement('div');
- screenButtonMove.className = 'rj-miniplayer-move';
- screenButtonMove.title = 'Move';
- screenButtonMove.style.backgroundImage = "url('" + chrome.extension.getURL('assets/button/move.png') + "')";
- screenButtonMove.addEventListener('mousedown', onScreenMouseDown);
- screenHeader.appendChild(screenButtonMove);
- screenTitle = document.createElement('div');
- screenTitle.className = 'rj-miniplayer-title';
- screenTitle.addEventListener('click', onScreenTitleClick);
- screenHeader.appendChild(screenTitle);
- screenButtonClose = document.createElement('div');
- screenButtonClose.className = 'rj-miniplayer-close';
- screenButtonClose.title = 'Close';
- screenButtonClose.style.backgroundImage = "url('" + chrome.extension.getURL('assets/button/close.png') + "')";
- screenButtonClose.addEventListener('click', onScreenButtonCloseClick);
- screenHeader.appendChild(screenButtonClose);
- screenButtonResizeBottomRight = document.createElement('div');
- screenButtonResizeBottomRight.className = 'rj-miniplayer-resize bottom-right';
- screenButtonResizeBottomRight.title = 'Resize';
- screenButtonResizeBottomRight.addEventListener('mousedown', onScreenMouseDown);
- screen.appendChild(screenButtonResizeBottomRight);
- screenButtonResizeTopRight = document.createElement('div');
- screenButtonResizeTopRight.className = 'rj-miniplayer-resize top-right';
- screenButtonResizeTopRight.title = 'Resize';
- screenButtonResizeTopRight.addEventListener('mousedown', onScreenMouseDown);
- screenHeader.appendChild(screenButtonResizeTopRight);
- iframe = document.createElement('iframe');
- iframe.setAttribute('allowfullscreen', '1');
- iframe.setAttribute('frameborder', '0');
- iframe.onload = onIframeLoad;
- screen.appendChild(iframe);
- }
- function updateIframeSource () {
- if (iframe == null) return;
- // If it's the same video, we keep playing it
- if (currentVideoId == queryParams.v) return;
- currentVideoId = queryParams.v;
- iframeVideo = null;
- iframe.contentWindow.location.replace('https://www.youtube.com/embed/' + currentVideoId + '?autoplay=1&modestbranding=1&rel=0&showinfo=0');
- // We periodically update the video.currentTime, so the Share button on Youtube works as expected
- // Update: this has a high performance impact :/
- //clearInterval(intervalCurrentTime);
- //intervalCurrentTime = setInterval(() => iframeVideo ? video.currentTime = iframeVideo.currentTime : null, 1000);
- }
- function initializeVideo() {
- if (video != null) {
- video.removeEventListener('playing', onVideoPlay);
- video.removeEventListener('play', onVideoPlay);
- video.removeEventListener('seeking', onVideoSeek);
- video.removeEventListener('seeked', onVideoSeek);
- }
- // Prevent Youtube from auto-playing
- // Workaround: we use intervals so this still runs when the tab isn't focused
- clearInterval(intervalVideoInitialize);
- intervalVideoInitialize = setInterval(() => {
- // Hide moviePlayer to increase performance
- moviePlayer = document.getElementById('movie_player');
- moviePlayerObserver.observe(moviePlayer, { childList: true });
- if (moviePlayer == null) return;
- playerAPI = moviePlayer.parentNode;
- video = playerAPI.getElementsByTagName('video')[0];
- if (video == null) return;
- video.pause();
- video.volume = 0;
- video.addEventListener('playing', onVideoPlay);
- video.addEventListener('play', onVideoPlay);
- video.addEventListener('seeking', onVideoSeek);
- video.addEventListener('seeked', onVideoSeek);
- clearInterval(intervalVideoInitialize);
- }, 25);
- }
- function _updateScreenState(noAnimation, forceAnimation) {
- if (playerAPI == null || screen == null) return;
- let dockMode = (currentPage != PAGE_WATCH || window.scrollY > screen.offsetHeight / 2) ? DOCK_FLOATING : DOCK_DOCKED;
- // Cancel current animation
- if (currentDockMode != dockMode) {
- screen.removeEventListener('transitionend', onScreenTransitionEnd);
- currentAnimationCancelled = currentAnimation != null;
- currentAnimation = null;
- }
- let positionEnd = _loadScreenState(dockMode), positionStart;
- let isAnimating = forceAnimation || currentDockMode != dockMode || currentAnimation != null;
- currentDockMode = dockMode;
- if (isAnimating) {
- // Update animation states
- if (currentAnimation == null) {
- // Note: updating the height/width causes a stutter/redraw on the iframe. Therefore we do it at the beginning, where it is less noticeable
- positionStart = screen.getBoundingClientRect();
- screen.style.height = positionEnd.height + 'px';
- screen.style.width = positionEnd.width + 'px';
- screen.style.transform = 'scale(' + (positionStart.width / positionEnd.width) + ')';
- } else {
- positionStart = currentAnimation.start;
- }
- currentAnimation = { end: positionEnd, start: positionStart, onScreenTransitionEnd: currentAnimation ? currentAnimation.onScreenTransitionEnd : null };
- if (currentDockMode == DOCK_DOCKED) {
- iframe.style.boxShadow = 'initial';
- }
- // Animate
- if (!noAnimation) {
- window.requestAnimationFrame(() => window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
- // Abort animation if cancelled
- if (currentAnimation == null)
- return;
- screen.style.transition = '.2s ease-in-out';
- if (currentAnimationCancelled) {
- screen.style.transform = '';
- onScreenTransitionEnd();
- } else {
- screen.style.transform =
- 'translateX(' + (currentAnimation.end.left - currentAnimation.start.left) + 'px)' +
- 'translateY(' + (currentAnimation.end.top - currentAnimation.start.top) + 'px)';
- }
- // Prevent multiple addEventListener during a single transition (e.g. when scrolling, this method is spammed)
- if (!currentAnimation || !currentAnimation.onScreenTransitionEnd) {
- currentAnimation.onScreenTransitionEnd = onScreenTransitionEnd;
- screen.addEventListener('transitionend', onScreenTransitionEnd);
- }
- })));
- } else {
- onScreenTransitionEnd();
- }
- } else {
- // When the movie is docked at the top of the watch page
- screen.style.transition = '';
- screen.style.height = positionEnd.height + 'px';
- screen.style.width = positionEnd.width + 'px';
- screen.style.top = positionEnd.top + 'px';
- screen.style.left = positionEnd.left + 'px';
- }
- }
- function _updateScreenVisibility(visible) {
- if (visible) {
- if (moviePlayer) moviePlayer.style.display = 'none';
- screen.style.display = 'initial';
- _updateScreenState(true);
- } else {
- if (moviePlayer) moviePlayer.style.display = 'initial';
- screen.style.display = 'none';
- }
- }
- function _calculateDefaultScreenState(dockMode) {
- let state = {};
- state.height = playerAPI.offsetHeight / 2;
- state.width = playerAPI.offsetWidth / 2;
- // #YoutubeMaterialDesign - when switching to the Home page and such
- if (currentPage != PAGE_WATCH && (state.width == 0 || state.height == 0)) {
- let backupState = _loadScreenState(DOCK_DOCKED, PAGE_WATCH);
- state.height = backupState.height / 2;
- state.width = backupState.width / 2;
- }
- if (currentPage == PAGE_CHANNEL || currentPage == PAGE_HOME || currentPage == PAGE_SUBSCRIPTIONS) {
- state.top = (window.innerHeight - state.height - SCREEN_PADDING);
- state.left = (window.innerWidth - state.width - SCREEN_PADDING - SCREEN_PADDING);
- }
- if (currentPage == PAGE_SEARCH) {
- state.top = (window.innerHeight - state.height) / 2;
- state.left = (window.innerWidth - state.width - SCREEN_PADDING - SCREEN_PADDING);
- }
- if (currentPage == PAGE_WATCH) {
- if (dockMode == DOCK_FLOATING) {
- let sidebar = document.getElementById('watch7-sidebar') || document.getElementById('related'); // #YoutubeMaterialDesign
- state.top = (window.innerHeight - state.height) / 2;
- state.left = (sidebar.getBoundingClientRect().left - (state.width - sidebar.offsetWidth) / 2);
- } else {
- state = playerAPI.getBoundingClientRect();
- }
- }
- return _saveScreenState(dockMode, state.height, state.width, state.left, state.top);
- }
- function _loadScreenState(dockMode, page) {
- let state = localStorage.screenState = localStorage.screenState || {};
- state = state[page || currentPage] = state[page || currentPage] || {};
- state = state[dockMode] = state[dockMode] || {};
- // Load default state, if none was previously saved
- if ((currentPage == PAGE_WATCH && dockMode == DOCK_DOCKED) || state.height == null || state.width == null || state.left == null || state.top == null)
- state = _calculateDefaultScreenState(dockMode);
- // In case the screen is outside the screen, move it back inside
- if (dockMode == DOCK_FLOATING) {
- if (state.left + state.width + SCREEN_PADDING > window.innerWidth)
- state.left = window.innerWidth - state.width - SCREEN_PADDING;
- if (state.left < SCREEN_PADDING)
- state.left = SCREEN_PADDING;
- if (state.top + state.height + SCREEN_PADDING > window.innerHeight)
- state.top = window.innerHeight - state.height - SCREEN_PADDING;
- if (state.top < 100)
- state.top = 100;
- }
- return state;
- }
- function _saveScreenState(dockMode, height, width, left, top) {
- let state = localStorage.screenState = localStorage.screenState || {};
- state = state[currentPage] = state[currentPage] || {};
- state = state[dockMode] = state[dockMode] || {};
- // Save to localStorage
- state.height = height;
- state.width = width;
- state.left = left;
- state.top = top;
- window.localStorage['rjgtav.youtube-mini-player'] = JSON.stringify(localStorage);
- return state;
- }
- function _getBoundingDocumentRect(e) {
- var box = e.getBoundingClientRect();
- var body = document.body;
- var docEl = document.documentElement;
- var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
- var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
- var clientTop = docEl.clientTop || body.clientTop || 0;
- var clientLeft = docEl.clientLeft || body.clientLeft || 0;
- var height = e.offsetHeight;
- var width = e.offsetWidth;
- var top = box.top + scrollTop - clientTop;
- var left = box.left + scrollLeft - clientLeft;
- return { height, width, top, left };
- }
- //---------------------------------------------------------------------
- // Event Handlers
- //---------------------------------------------------------------------
- // Thanks to http://stackoverflow.com/questions/34077641/how-to-detect-page-navigation-on-youtube-and-modify-html-before-page-is-rendered
- function onPageLoad() {
- let isFirstTime = iframe == null;
- // Parse query params
- queryParams = {};
- let params = window.location.search.substring(1).split('&');
- params.forEach((param) => {
- let pair = param.split('=');
- queryParams[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
- })
- // Parse current page
- currentPage = null;
- currentPage = currentPage == null && queryParams.v != null ? PAGE_WATCH : currentPage;
- currentPage = currentPage == null && queryParams.search_query != null ? PAGE_SEARCH : currentPage;
- currentPage = currentPage == null && window.location.href.indexOf('/channel/') != -1 ? PAGE_CHANNEL : currentPage;
- currentPage = currentPage == null && window.location.href.indexOf('/feeed/subscriptions') != -1 ? PAGE_SUBSCRIPTIONS : currentPage;
- currentPage = currentPage == null ? PAGE_HOME : currentPage;
- if (currentPage == PAGE_WATCH) {
- initializeIframe();
- initializeVideo();
- onPageTitle();
- updateIframeSource();
- // Make sure the mini player is visible (in case it was hidden by the user)
- _updateScreenVisibility(true);
- }
- document.body.appendChild(windowResizeListener); // Note: we need to append this before the window.onload
- window.requestAnimationFrame(() => {
- _updateScreenState(isFirstTime, true);
- });
- }
- function onPageTitle(event) {
- let index = document.title.indexOf('- YouTube');
- if (index > -1)
- screenTitle.innerText = document.title.substring(0, index);
- }
- function onIframeLoad(event) {
- // Workaround: we use intervals so this still runs when the tab isn't focused
- clearInterval(intervalIframeInitialize);
- intervalIframeInitialize = setInterval(() => {
- iframeVideo = iframe.contentWindow.document.getElementsByTagName('video')[0];
- if (iframeVideo) {
- iframeVideo.currentTime = video.currentTime;
- iframeVideo.onended = onIframeVideoEnded;
- clearInterval(intervalIframeInitialize);
- }
- }, 25);
- }
- function onIframeVideoEnded(event) {
- if (currentPage != PAGE_WATCH) return;
- // In order to trigger Youtube's functionality (e.g. AutoPlay, Playlist), we need to make the original video trigger the 'ended' event
- // Therefore, we seek to the end and play it. See #onVideoSeek() for details
- video.currentTime = video.duration - 0.25;
- _updateScreenVisibility(false);
- }
- function onScreenButtonCloseClick(event) {
- iframeVideo.pause();
- _updateScreenVisibility(false);
- }
- function onScreenMouseDown(event) {
- event.preventDefault();
- isScreenMove = event.target == screenButtonMove;
- isScreenResize = event.target == screenButtonResizeBottomRight || event.target == screenButtonResizeTopRight;
- iframe.style.pointerEvents = 'none';
- screen.classList.add('dragging');
- screenButtonMove.removeEventListener('mousedown', onScreenMouseDown);
- screenButtonResizeBottomRight.removeEventListener('mousedown', onScreenMouseDown);
- screenButtonResizeTopRight.removeEventListener('mousedown', onScreenMouseDown);
- document.body.addEventListener('mouseup', onScreenMouseUp);
- document.body.addEventListener('mousemove', onScreenMouseMove);
- }
- function onScreenMouseUp(event) {
- event.preventDefault();
- if (isScreenMove) {
- // Save
- _saveScreenState(currentDockMode, screen.offsetHeight, screen.offsetWidth, screen.offsetLeft, screen.offsetTop);
- }
- if (isScreenResize) {
- var currentSize = screen.getBoundingClientRect();
- //screen.style.left =(screen.offsetLeft - (currentSize.width - screen.offsetWidth) / 2) + 'px';
- //screen.style.top = (screen.offsetTop - (currentSize.height - screen.offsetHeight) / 2) + 'px';
- screen.style.height = currentSize.height + 'px';
- screen.style.width = currentSize.width + 'px';
- screen.style.transform = '';
- // Save
- _saveScreenState(currentDockMode, currentSize.height, currentSize.width, screen.offsetLeft, screen.offsetTop);
- }
- isScreenMove = isScreenResize = false;
- iframe.style.pointerEvents = 'initial';
- screen.classList.remove('dragging');
- document.body.removeEventListener('mouseup', onScreenMouseUp);
- document.body.removeEventListener('mousemove', onScreenMouseMove);
- screenButtonMove.addEventListener('mousedown', onScreenMouseDown);
- screenButtonResizeBottomRight.addEventListener('mousedown', onScreenMouseDown);
- screenButtonResizeTopRight.addEventListener('mousedown', onScreenMouseDown);
- }
- function onScreenMouseMove(event) {
- event.preventDefault();
- if (isScreenMove) {
- screen.style.left = Math.min(window.innerWidth - screen.offsetWidth - SCREEN_PADDING, Math.max(SCREEN_PADDING, parseInt(screen.style.left) + event.movementX)) + 'px';
- screen.style.top = Math.min(window.innerHeight - screen.offsetHeight - SCREEN_PADDING, Math.max(148, parseInt(screen.style.top) + event.movementY)) + 'px';
- }
- if (isScreenResize) {
- var currentSize = screen.getBoundingClientRect();
- var width = screen.offsetWidth;
- var newWidth = currentSize.width + event.movementX;
- screen.style.transform = 'scaleX(' + newWidth / width + ') scaleY(' + newWidth / width + ')';
- }
- }
- function onScreenTitleClick(event) {
- var videoUrl = window.location.href.split('&t=')[0];
- window.location.href = videoUrl + (iframeVideo ? ('&t=' + parseInt(iframeVideo.currentTime)) : '');
- }
- function onScreenTransitionEnd(event) {
- if (event && event.propertyName != 'transform') return;
- screen.removeEventListener('transitionend', onScreenTransitionEnd);
- if (currentDockMode == DOCK_FLOATING) {
- iframe.style.boxShadow = '0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22)';
- }
- screen.className = currentDockMode;
- screen.style.left = currentAnimation.end.left + 'px';
- screen.style.top = currentAnimation.end.top + 'px';
- screen.style.transform = '';
- screen.style.transition = '';
- currentAnimation = null;
- currentAnimationCancelled = false;
- }
- function onMoviePlayerObserve(mutators) {
- if (mutators[0].addedNodes.length > 0)
- initializeVideo();
- }
- function onVideoPlay(event) {
- if (video == null) return;
- if (!(video.duration > 0 && Math.abs(video.currentTime - video.duration) <= 1)) // See #onIframeVideoEnded() for details
- video.pause();
- }
- function onVideoSeek(event) {
- if (video == null) return;
- if (video.duration > 0 && Math.abs(video.currentTime - video.duration) <= 1) { // See #onIframeVideoEnded() for details
- video.play();
- } else {
- video.pause();
- if (iframeVideo != null)
- iframeVideo.currentTime = video.currentTime;
- // Make sure the mini player is visible (in case it was hidden by the user)
- _updateScreenVisibility(true);
- }
- }
- function onWindowResize() {
- window.requestAnimationFrame(() => {
- _updateScreenState();
- });
- }
- function onWindowScroll(event) {
- _updateScreenState();
- }
- })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement