Youtube floating player

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

  1. // ==UserScript==
  2. // @name Youtube floating player
  3. // @description Pins the video player in its original position while you scroll the page.
  4. // @version 3.2
  5. // @author REVerdi
  6. // @namespace https://openuserjs.org/users/REVerdi
  7. // @copyright 2014+, REVerdi (https://openuserjs.org/users/REVerdi)
  8. // @license (CC) Attribution Non-Commercial Share Alike; http://creativecommons.org/licenses/by-nc-sa/3.0/
  9. // Por causa do SPF (Structured Page Fragments), não posso usar // @include http*://www.youtube.com/watch?*
  10. // porque se o 1° link no YouTube não for do tipo acima, esse script nunca será executado.
  11. // @include http*://www.youtube.com/*
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15.  
  16. /*
  17. TESTADO APENAS NO FIREFOX
  18.  
  19. ONLY TESTED ON FIREFOX
  20. */
  21.  
  22.  
  23. /*
  24. O YouTube "quase" exatamente como eu queria!
  25. Eu não sou programador, então não joguem tomates podres em mim :)
  26.  
  27. YouTube "almost" exactly as I wanted!
  28. I'm not a programmer, so don't throw rotten tomatoes at me :)
  29. */
  30.  
  31.  
  32. // Based on the ideia of drhouse (http://userscripts.org/scripts/show/186872)
  33. // and contains source code written by tforbus:
  34. // http://www.tristinforbus.com/
  35. // https://github.com/tforbus/youtube-fixed-video-bookmarklet
  36. // http://www.whattheforbus.com/youtube-bookmarklet [removed]
  37. // https://chrome.google.com/webstore/detail/video-pinner/egfhbaheiflmihggjcfmnmchkijkcdpl [removed]
  38.  
  39.  
  40. (function(){
  41.  
  42.  
  43. "use strict";
  44.  
  45.  
  46. var _window;
  47. if (typeof unsafeWindow !== undefined){
  48. _window = unsafeWindow;
  49. }
  50. else {
  51. _window = window;
  52. }
  53.  
  54.  
  55. /*
  56. * constants
  57. */
  58. const BODY_ID = 'body';
  59.  
  60. const PLAYER_ID = 'player'; //why not 'movie_player'?
  61. const THEATER_ID = 'theater-background';
  62. const PLAYLIST_ID = 'watch-appbar-playlist';
  63. const CONTENT_ID = 'watch7-content';
  64. const SIDEBAR_ID = 'watch7-sidebar';
  65. const PLACEHOLDER_ID = 'placeholder-player'; //to detect if the user is or is not on a video page
  66.  
  67. const BUTTON_CLASS = 'ytp-size-button ytp-button'; //player view toggle button class
  68.  
  69. /*
  70. * flow control variables
  71. */
  72. var eventListenersAdded = 0;
  73. var bodyMutationObserverAdded = 0;
  74.  
  75. /*
  76. * user is playing a...
  77. */
  78. const SINGLE_VIDEO = 1;
  79. const PLAYLIST = 2;
  80.  
  81. /*
  82. * player view mode
  83. */
  84. var playerViewMode;
  85. const NOT_DETECTED = 0;
  86. const DEFAULT_VIEW = 1;
  87. const THEATER_MODE = 2;
  88.  
  89.  
  90. var playerViewToggleButton; //tem que ser pública para que se possa rodar o removeEventListener
  91.  
  92.  
  93. //http://www.w3schools.com/colors/colors_names.asp
  94. //if (document.getElementById( PLAYER_ID)) document.getElementById( PLAYER_ID).style.border = "thick solid cyan" ;
  95. //if (document.getElementById( THEATER_ID)) document.getElementById( THEATER_ID).style.border = "thick solid red" ;
  96. //if (document.getElementById( PLAYLIST_ID)) document.getElementById( PLAYLIST_ID).style.border = "thick solid pink" ;
  97. //if (document.getElementById( SIDEBAR_ID)) document.getElementById( SIDEBAR_ID).style.border = "thick solid yellow" ;
  98. //if (document.getElementById( PLACEHOLDER_ID)) document.getElementById( PLACEHOLDER_ID).style.border = "thick solid green" ;
  99. //if (document.getElementById( 'movie_player')) document.getElementById( 'movie_player').style.border = "thick solid blue" ;
  100. //if (document.getElementById('player-playlist')) document.getElementById('player-playlist').style.border = "thick solid fuchsia";
  101.  
  102.  
  103. function userIsOnAVideoPage() { //user is where?
  104. var playerPlaceHolder = document.getElementById(PLACEHOLDER_ID);
  105. if( playerPlaceHolder ) return 1; //user IS on a video page
  106. else return 0; //user is NOT on a video page
  107. }
  108.  
  109.  
  110. function userIsPlayingA() {
  111. if( /list/.test(document.location) === false ) return SINGLE_VIDEO;
  112. else return PLAYLIST;
  113. }
  114.  
  115.  
  116. function getPlayerViewToggleButton() {
  117. var player = document.getElementById(PLAYER_ID);
  118. var buttons = player.getElementsByTagName('BUTTON');
  119. for( var b = 0; b < buttons.length; b++ ) {
  120. if( buttons[b].className == BUTTON_CLASS) {
  121. var buttonTitle = buttons[b].getAttribute('title');
  122. if( buttonTitle != 'null' ) return buttons[b];
  123. }
  124. }
  125. }
  126.  
  127.  
  128.  
  129.  
  130. function resizePlayer() {
  131. var playerRect;
  132. var theaterRect;
  133. var sidebarRect;
  134. var playlist;
  135. var player = document.getElementById( PLAYER_ID);
  136. var theater = document.getElementById(THEATER_ID);
  137. var sidebar = document.getElementById(SIDEBAR_ID);
  138. var content = document.getElementById(CONTENT_ID);
  139. var contentRect = content.getBoundingClientRect();
  140. var playerPlaceHolder = document.getElementById(PLACEHOLDER_ID);
  141. playerPlaceHolder.firstElementChild.style.backgroundColor = 'transparent';
  142.  
  143. player.style.top = '60px'; //determinado por tentativa e erro :S
  144. player.style.position = 'fixed';
  145. theater.style.position = 'fixed';
  146. sidebar.style.position = 'inherit'; //static|absolute|fixed|relative|initial|inherit
  147. switch( playerViewMode ) {
  148. case DEFAULT_VIEW:
  149. // var content = document.getElementById(CONTENT_ID);
  150. // var contentRect = content.getBoundingClientRect(); //bottom, height, left, right, top, width, x, y
  151. player.style.left = contentRect.left + 'px';
  152. switch( userIsPlayingA() ) {
  153. case SINGLE_VIDEO:
  154. player.style.zIndex = 998;
  155. sidebar.style.zIndex = 999;
  156. break;
  157. case PLAYLIST:
  158. player.style.zIndex = 999;
  159. sidebar.style.zIndex = 998;
  160. playlist = document.getElementById(PLAYLIST_ID); //para desfazer o que foi feito para playlist em modo teatro
  161. playlist.style.top = '';
  162. playlist.style.position = '';
  163. playlist.style.width = '';
  164. playlist.style.left = '';
  165. playerRect = player.getBoundingClientRect(); //to limit the
  166. sidebarRect = sidebar.getBoundingClientRect(); //width of
  167. player.style.width = sidebarRect.right - playerRect.left + 'px'; //the playlist
  168. break;
  169. }
  170. break;
  171. case THEATER_MODE:
  172. player.style.zIndex = 999;
  173. sidebar.style.zIndex = 998;
  174. playerRect = player.getBoundingClientRect();
  175. theaterRect = theater.getBoundingClientRect();
  176. player.style.left = (theaterRect.width / 2) - (playerRect.width / 2) + 'px';
  177. switch( userIsPlayingA() ) {
  178. case SINGLE_VIDEO:
  179. //nothing to do
  180. break;
  181. case PLAYLIST:
  182. sidebarRect = sidebar.getBoundingClientRect();
  183. playlist = document.getElementById(PLAYLIST_ID);
  184. playlist.style.top = '170px'; //determinado por tentativa e erro :S
  185. playlist.style.position = 'fixed';
  186. playlist.style.width = sidebarRect.right - contentRect.right + 'px';
  187. playlist.style.left = contentRect.right + 'px';
  188. //or
  189. //playlist.style.width = sidebarRect.width + 'px';
  190. //playlist.style.left = sidebarRect.left + 'px';
  191. break;
  192. }
  193. break;
  194. }
  195. }
  196.  
  197.  
  198.  
  199.  
  200. function pageResize() {
  201. resizePlayer();
  202. }
  203.  
  204.  
  205.  
  206.  
  207. function playerViewToggleButtonClick() {
  208. switch( playerViewMode ) {
  209. case DEFAULT_VIEW:
  210. playerViewMode = THEATER_MODE;
  211. break;
  212. case THEATER_MODE:
  213. playerViewMode = DEFAULT_VIEW;
  214. break;
  215. }
  216. resizePlayer();
  217. }
  218.  
  219.  
  220.  
  221.  
  222. function getPlayerViewMode() {
  223. var playerViewToggleButtonTitle = playerViewToggleButton.getAttribute('title');
  224. if( playerViewToggleButtonTitle != 'null' ) {
  225. playerViewToggleButtonTitle = playerViewToggleButtonTitle.toLowerCase();
  226. switch( playerViewToggleButtonTitle ) { //detecta o modo de visualização pelo título do botão (eu sei, depende do idioma :S)
  227. case 'theater mode': //'Theater mode'
  228. case 'modo teatro': //'Modo Teatro'
  229. playerViewMode = DEFAULT_VIEW;
  230. break;
  231. case 'default view': //'Default view'
  232. case 'visualização padrão': //'Visualização padrão'
  233. playerViewMode = THEATER_MODE;
  234. break;
  235. }
  236. }
  237. //else playerViewMode = NOT_DETECTED; //isso ocorre, mas daí é mantido o último valor válido detectado
  238. }
  239.  
  240.  
  241.  
  242. /*
  243. function initElements() { //merged into resizePlayer()
  244. var player = document.getElementById( PLAYER_ID);
  245. var theater = document.getElementById(THEATER_ID);
  246. var sidebar = document.getElementById(SIDEBAR_ID);
  247. var playerPlaceHolder = document.getElementById(PLACEHOLDER_ID);
  248. playerPlaceHolder.firstElementChild.style.backgroundColor = 'transparent';
  249. player.style.top = '60px'; //determinado por tentativa e erro :S
  250. player.style.position = 'fixed';
  251. theater.style.position = 'fixed';
  252. sidebar.style.position = 'inherit'; //static|absolute|fixed|relative|initial|inherit
  253. }
  254. */
  255.  
  256.  
  257.  
  258. //https://developer.mozilla.org/pt-BR/docs/Web/API/MutationObserver
  259. //http://www.w3schools.com/jsref/met_element_addeventlistener.asp
  260. var bodyMutationObserver = new MutationObserver(function(mutations) {
  261. mutations.forEach(function(mutation) {
  262. if( userIsOnAVideoPage() ) {
  263. if( eventListenersAdded === 0 ) { //só adiciona uma vez
  264. playerViewToggleButton = getPlayerViewToggleButton();
  265. playerViewToggleButton.addEventListener('click', playerViewToggleButtonClick, false);
  266. _window.addEventListener('resize', pageResize, false);
  267. eventListenersAdded = 1;
  268. getPlayerViewMode();
  269. //initElements();
  270. //resizePlayer(); //aqui não funciona 100%, portanto...
  271. }
  272. resizePlayer(); //...tem que ficar aqui e ser chamada várias vezes :S
  273. }
  274. else { //user is NOT on a video page
  275. if( eventListenersAdded ) { //só remove uma vez
  276. var player = document.getElementById(PLAYER_ID);
  277. player.style.top = '';
  278. player.style.position = '';
  279. player.style.zIndex = '';
  280. player.style.left = '';
  281. player.style.width = '';
  282. playerViewToggleButton.removeEventListener('click', playerViewToggleButtonClick, false);
  283. _window.removeEventListener('resize', pageResize, false);
  284. eventListenersAdded = 0;
  285. }
  286. }
  287. });
  288. });
  289. /*
  290. function remBodyMutationObserver();
  291. if( bodyMutationObserverAdded == 1 ) { //( bodyMutationObserverAdded == 1 ) ou apenas ( bodyMutationObserverAdded )
  292. bodyMutationObserver.disconnect();
  293. bodyMutationObserverAdded = 0;
  294. }
  295. }
  296. */
  297. function addBodyMutationObserver() {
  298. if( bodyMutationObserverAdded === 0 ) { //( bodyMutationObserverAdded === 0 ) ou apenas ( !bodyMutationObserverAdded )
  299. var config = { attributes: true, characterData: true, childList: true };
  300. 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
  301. if( target !== null ) { //( target !== null ) ou apenas ( target )
  302. bodyMutationObserver.observe(target, config);
  303. bodyMutationObserverAdded = 1;
  304. }
  305. }
  306. }
  307.  
  308. addBodyMutationObserver(); //initScript == entryPoint
  309.  
  310.  
  311. })();