Youtube floating player

Pins the video player in its original position while you scroll the page.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        Youtube floating player
// @description Pins the video player in its original position while you scroll the page.
// @version     3.2
// @author      REVerdi
// @namespace   https://openuserjs.org/users/REVerdi
// @copyright   2014+, REVerdi (https://openuserjs.org/users/REVerdi)
// @license     (CC) Attribution Non-Commercial Share Alike; http://creativecommons.org/licenses/by-nc-sa/3.0/
// Por causa do SPF (Structured Page Fragments), não posso usar  // @include   http*://www.youtube.com/watch?*
// porque se o 1° link no YouTube não for do tipo acima, esse script nunca será executado.
// @include     http*://www.youtube.com/*
// @grant       none
// ==/UserScript==


/*
TESTADO APENAS NO FIREFOX

ONLY TESTED ON FIREFOX
*/


/*
O YouTube "quase" exatamente como eu queria!
Eu não sou programador, então não joguem tomates podres em mim :)

YouTube "almost" exactly as I wanted!
I'm not a programmer, so don't throw rotten tomatoes at me :)
*/


// Based on the ideia of drhouse (http://userscripts.org/scripts/show/186872)
// and contains source code written by tforbus:
// http://www.tristinforbus.com/
// https://github.com/tforbus/youtube-fixed-video-bookmarklet
// http://www.whattheforbus.com/youtube-bookmarklet [removed]
// https://chrome.google.com/webstore/detail/video-pinner/egfhbaheiflmihggjcfmnmchkijkcdpl [removed]


(function(){


"use strict";


var _window;
if (typeof unsafeWindow !== undefined){
    _window = unsafeWindow;
}
else {
    _window = window;
}


/*
 * constants
 */
const        BODY_ID = 'body';

const      PLAYER_ID = 'player';                                                //why not 'movie_player'?
const     THEATER_ID = 'theater-background';
const    PLAYLIST_ID = 'watch-appbar-playlist';
const     CONTENT_ID = 'watch7-content';
const     SIDEBAR_ID = 'watch7-sidebar';
const PLACEHOLDER_ID = 'placeholder-player';                                    //to detect if the user is or is not on a video page

const   BUTTON_CLASS = 'ytp-size-button ytp-button';                            //player view toggle button class

/*
 * flow control variables
 */
var       eventListenersAdded = 0;
var bodyMutationObserverAdded = 0;

/*
 * user is playing a...
 */
const SINGLE_VIDEO = 1;
const     PLAYLIST = 2;

/*
 * player view mode
 */
var playerViewMode;
const NOT_DETECTED = 0;
const DEFAULT_VIEW = 1;
const THEATER_MODE = 2;


var playerViewToggleButton;                                                     //tem que ser pública para que se possa rodar o removeEventListener


//http://www.w3schools.com/colors/colors_names.asp
//if (document.getElementById(        PLAYER_ID)) document.getElementById(        PLAYER_ID).style.border = "thick solid cyan"   ;
//if (document.getElementById(       THEATER_ID)) document.getElementById(       THEATER_ID).style.border = "thick solid red"    ;
//if (document.getElementById(      PLAYLIST_ID)) document.getElementById(      PLAYLIST_ID).style.border = "thick solid pink"   ;
//if (document.getElementById(       SIDEBAR_ID)) document.getElementById(       SIDEBAR_ID).style.border = "thick solid yellow" ;
//if (document.getElementById(   PLACEHOLDER_ID)) document.getElementById(   PLACEHOLDER_ID).style.border = "thick solid green"  ;
//if (document.getElementById(   'movie_player')) document.getElementById(   'movie_player').style.border = "thick solid blue"   ;
//if (document.getElementById('player-playlist')) document.getElementById('player-playlist').style.border = "thick solid fuchsia";


function userIsOnAVideoPage() {                                                 //user is where?
    var playerPlaceHolder = document.getElementById(PLACEHOLDER_ID);
    if( playerPlaceHolder ) return 1;                                           //user IS     on a video page
    else                    return 0;                                           //user is NOT on a video page
}


function userIsPlayingA() {
    if( /list/.test(document.location) === false ) return SINGLE_VIDEO;
    else                                           return PLAYLIST;
}


function getPlayerViewToggleButton() {
    var player  = document.getElementById(PLAYER_ID);
    var buttons = player.getElementsByTagName('BUTTON');
    for( var b = 0; b < buttons.length; b++ ) {
        if( buttons[b].className == BUTTON_CLASS) {
            var buttonTitle = buttons[b].getAttribute('title');
            if( buttonTitle != 'null' ) return buttons[b];
        }
    }
}




function resizePlayer() {
    var  playerRect;
    var theaterRect;
    var sidebarRect;
    
    var playlist;
    
    var  player = document.getElementById( PLAYER_ID);
    var theater = document.getElementById(THEATER_ID);
    var sidebar = document.getElementById(SIDEBAR_ID);
    
    var content = document.getElementById(CONTENT_ID);
    var contentRect = content.getBoundingClientRect();
    
    var playerPlaceHolder = document.getElementById(PLACEHOLDER_ID);
    playerPlaceHolder.firstElementChild.style.backgroundColor = 'transparent';

     player.style.top      = '60px';                                            //determinado por tentativa e erro :S
     player.style.position = 'fixed';
    theater.style.position = 'fixed';
    sidebar.style.position = 'inherit';                                         //static|absolute|fixed|relative|initial|inherit
    
    switch( playerViewMode ) {
        case DEFAULT_VIEW:
//          var content = document.getElementById(CONTENT_ID);
//          var contentRect = content.getBoundingClientRect();                  //bottom, height, left, right, top, width, x, y
            player.style.left = contentRect.left + 'px';
            switch( userIsPlayingA() ) {
                case SINGLE_VIDEO:
                     player.style.zIndex = 998;
                    sidebar.style.zIndex = 999;
                    break;
                    
                case PLAYLIST:
                     player.style.zIndex = 999;
                    sidebar.style.zIndex = 998;
                    
                    playlist = document.getElementById(PLAYLIST_ID);            //para desfazer o que foi feito para playlist em modo teatro
                    playlist.style.top      = '';
                    playlist.style.position = '';
                    playlist.style.width    = '';
                    playlist.style.left     = '';
                    
                     playerRect =  player.getBoundingClientRect();                    //to limit the
                    sidebarRect = sidebar.getBoundingClientRect();                    //width of
                     player.style.width = sidebarRect.right - playerRect.left + 'px'; //the playlist
                     
                    break;
            }
            break;
        case THEATER_MODE:
             player.style.zIndex = 999;
            sidebar.style.zIndex = 998;
            
             playerRect =  player.getBoundingClientRect();
            theaterRect = theater.getBoundingClientRect();
             player.style.left = (theaterRect.width / 2) - (playerRect.width / 2)  + 'px';
             
            switch( userIsPlayingA() ) {
                case SINGLE_VIDEO:
                    //nothing to do
                    break;
                    
                case PLAYLIST:
                    sidebarRect = sidebar.getBoundingClientRect();
                    
                    playlist = document.getElementById(PLAYLIST_ID);
                    playlist.style.top      = '170px';                          //determinado por tentativa e erro :S
                    playlist.style.position = 'fixed';
                    
                    playlist.style.width = sidebarRect.right - contentRect.right + 'px';
                    playlist.style.left  = contentRect.right + 'px';
                    //or
                  //playlist.style.width = sidebarRect.width + 'px';
                  //playlist.style.left  = sidebarRect.left  + 'px';
                    
                    break;
            }
            break;
    }
}




function pageResize() {
    resizePlayer();
}




function playerViewToggleButtonClick() {
    switch( playerViewMode ) {
        case DEFAULT_VIEW:
            playerViewMode = THEATER_MODE;
            break;
        case THEATER_MODE:
            playerViewMode = DEFAULT_VIEW;
            break;
    }
    resizePlayer();
}




function getPlayerViewMode() {
    var playerViewToggleButtonTitle = playerViewToggleButton.getAttribute('title');
    if( playerViewToggleButtonTitle != 'null' ) {
        playerViewToggleButtonTitle = playerViewToggleButtonTitle.toLowerCase();
        switch( playerViewToggleButtonTitle ) {                                 //detecta o modo de visualização pelo título do botão (eu sei, depende do idioma :S)
            case 'theater mode':                                                //'Theater mode'
            case 'modo teatro':                                                 //'Modo Teatro'
                playerViewMode = DEFAULT_VIEW;
                break;
            case 'default view':                                                //'Default view'
            case 'visualização padrão':                                         //'Visualização padrão'
                playerViewMode = THEATER_MODE;
                break;
        }
    }
  //else playerViewMode = NOT_DETECTED;                                         //isso ocorre, mas daí é mantido o último valor válido detectado
}



/*
function initElements() {                                                       //merged into resizePlayer()
    var  player = document.getElementById( PLAYER_ID);
    var theater = document.getElementById(THEATER_ID);
    var sidebar = document.getElementById(SIDEBAR_ID);
    
    var playerPlaceHolder = document.getElementById(PLACEHOLDER_ID);
    playerPlaceHolder.firstElementChild.style.backgroundColor = 'transparent';
    
     player.style.top      = '60px';                                            //determinado por tentativa e erro :S
     player.style.position = 'fixed';
    theater.style.position = 'fixed';
    sidebar.style.position = 'inherit';                                         //static|absolute|fixed|relative|initial|inherit
}
*/



//https://developer.mozilla.org/pt-BR/docs/Web/API/MutationObserver
//http://www.w3schools.com/jsref/met_element_addeventlistener.asp
var bodyMutationObserver = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        if( userIsOnAVideoPage() ) {
            if( eventListenersAdded === 0 ) {                                   //só adiciona uma vez
                playerViewToggleButton = getPlayerViewToggleButton();
                playerViewToggleButton.addEventListener('click', playerViewToggleButtonClick, false);
                 _window.addEventListener('resize', pageResize, false);
                
                eventListenersAdded = 1;
                
                getPlayerViewMode();
              //initElements();
              //resizePlayer();                                                 //aqui não funciona 100%, portanto...
            }
            resizePlayer();                                                     //...tem que ficar aqui e ser chamada várias vezes :S
        }
        else {                                                                  //user is NOT on a video page
            if( eventListenersAdded ) {                                         //só remove uma vez
                var player = document.getElementById(PLAYER_ID);
                player.style.top      = '';
                player.style.position = '';
                player.style.zIndex   = '';
                player.style.left     = '';
                player.style.width    = '';
                
                playerViewToggleButton.removeEventListener('click', playerViewToggleButtonClick, false);
                 _window.removeEventListener('resize', pageResize, false);
                
                eventListenersAdded = 0;
            }
        }
    });
});
/*
function remBodyMutationObserver();
    if( bodyMutationObserverAdded == 1 ) {                                      //( bodyMutationObserverAdded == 1 ) ou apenas ( bodyMutationObserverAdded )
        bodyMutationObserver.disconnect();
        bodyMutationObserverAdded = 0;
    }
}
*/
function addBodyMutationObserver() {
    if( bodyMutationObserverAdded === 0 ) {                                     //( bodyMutationObserverAdded === 0 ) ou apenas ( !bodyMutationObserverAdded )
        var config = { attributes: true, characterData: true, childList: true };
        var target = document.getElementById(BODY_ID);                          //tem que ser 'body', porque 'page' só funciona quando a 1ª página NÃO for uma de vídeo
        if( target !== null ) {                                                 //( target !== null ) ou apenas ( target )
            bodyMutationObserver.observe(target, config);
            bodyMutationObserverAdded = 1;
        }
    }
}

addBodyMutationObserver();                                                      //initScript == entryPoint


})();