Resize YT To Window Size

Moves the video to the top of the website and resizes it to the screen size.

当前为 2014-07-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Resize YT To Window Size
  3. // @description Moves the video to the top of the website and resizes it to the screen size.
  4. // @author Chris H (Zren / Shade)
  5. // @icon http://youtube.com/favicon.ico
  6. // @homepageURL https://github.com/Zren/ResizeYoutubePlayerToWindowSize/
  7. // @namespace http://xshade.ca
  8. // @version 1.39
  9. // @include http*://*.youtube.com/*
  10. // @include http*://youtube.com/*
  11. // @include http*://*.youtu.be/*
  12. // @include http*://youtu.be/*
  13. // ==/UserScript==
  14.  
  15. // Github: https://github.com/Zren/ResizeYoutubePlayerToWindowSize
  16. // GreasyFork: https://greasyfork.org/scripts/811-resize-yt-to-window-size
  17. // OpenUserJS.org: https://openuserjs.org/scripts/zren/httpxshade.ca/Resize_YT_To_Window_Size
  18. // Userscripts.org: http://userscripts.org:8080/scripts/show/153699
  19.  
  20. (function (window) {
  21. "use strict";
  22.  
  23. //--- Imported Globals
  24. var yt = window.yt;
  25.  
  26. //--- Utils
  27. function isStringType(obj) { return typeof obj === 'string'; }
  28. function isArrayType(obj) { return obj instanceof Array; }
  29. function isObjectType(obj) { return typeof obj === 'object'; }
  30. function isUndefined(obj) { return typeof obj === 'undefined'; }
  31. function buildVenderPropertyDict(propertyNames, value) {
  32. var d = {};
  33. for (var i in propertyNames)
  34. d[propertyNames[i]] = value;
  35. return d;
  36. }
  37.  
  38. //--- jQuery
  39. // Based on jQuery
  40. // https://github.com/jquery/jquery/blob/master/src/manipulation.js
  41. var core_rnotwhite = /\S+/g;
  42. var rclass = /[\t\r\n\f]/g;
  43. var rtrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
  44. var jQuery = {
  45. trim: function( text ) {
  46. return (text || "").replace( rtrim, "" );
  47. },
  48. addClass: function( elem, value ) {
  49. var classes, cur, clazz, j,
  50. proceed = typeof value === "string" && value;
  51. if ( proceed ) {
  52. // The disjunction here is for better compressibility (see removeClass)
  53. classes = ( value || "" ).match( core_rnotwhite ) || [];
  54. cur = elem.nodeType === 1 && ( elem.className ?
  55. ( " " + elem.className + " " ).replace( rclass, " " ) :
  56. " "
  57. );
  58. if ( cur ) {
  59. j = 0;
  60. while ( (clazz = classes[j++]) ) {
  61. if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
  62. cur += clazz + " ";
  63. }
  64. }
  65. elem.className = jQuery.trim( cur );
  66. }
  67. }
  68. },
  69. removeClass: function( elem, value ) {
  70. var classes, cur, clazz, j,
  71. proceed = arguments.length === 0 || typeof value === "string" && value;
  72. if ( proceed ) {
  73. classes = ( value || "" ).match( core_rnotwhite ) || [];
  74. // This expression is here for better compressibility (see addClass)
  75. cur = elem.nodeType === 1 && ( elem.className ?
  76. ( " " + elem.className + " " ).replace( rclass, " " ) :
  77. ""
  78. );
  79. if ( cur ) {
  80. j = 0;
  81. while ( (clazz = classes[j++]) ) {
  82. // Remove *all* instances
  83. while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
  84. cur = cur.replace( " " + clazz + " ", " " );
  85. }
  86. }
  87. elem.className = value ? jQuery.trim( cur ) : "";
  88. }
  89. }
  90. }
  91. };
  92.  
  93. //--- Stylesheet
  94. var JSStyleSheet = function(id) {
  95. this.id = id;
  96. this.stylesheet = '';
  97. };
  98.  
  99. JSStyleSheet.prototype.buildRule = function(selector, styles) {
  100. var s = "";
  101. for (var key in styles) {
  102. s += "\t" + key + ": " + styles[key] + ";\n";
  103. }
  104. return selector + " {\n" + s + "}\n";
  105. };
  106.  
  107. JSStyleSheet.prototype.appendRule = function(selector, k, v) {
  108. if (isArrayType(selector))
  109. selector = selector.join(',\n');
  110. var newStyle;
  111. if (!isUndefined(k) && !isUndefined(v) && isStringType(k)) { // v can be any type (as we stringify it).
  112. // appendRule('#blarg', 'display', 'none');
  113. var d = {};
  114. d[k] = v;
  115. newStyle = this.buildRule(selector, d);
  116. } else if (!isUndefined(k) && isUndefined(v) && isObjectType(k)) {
  117. // appendRule('#blarg', {'display': 'none'});
  118. newStyle = this.buildRule(selector, k);
  119. } else {
  120. // Invalid Arguments
  121. console.log('Illegal arguments', arguments);
  122. return;
  123. }
  124. this.stylesheet += newStyle;
  125. };
  126.  
  127. JSStyleSheet.injectIntoHeader = function(injectedStyleId, stylesheet) {
  128. var styleElement = document.getElementById(injectedStyleId);
  129. if (!styleElement) {
  130. styleElement = document.createElement('style');
  131. styleElement.type = 'text/css';
  132. styleElement.id = injectedStyleId;
  133. document.getElementsByTagName('head')[0].appendChild(styleElement);
  134. }
  135. styleElement.appendChild(document.createTextNode(stylesheet));
  136. };
  137.  
  138. JSStyleSheet.prototype.injectIntoHeader = function(injectedStyleId, stylesheet) {
  139. JSStyleSheet.injectIntoHeader(this.id, this.stylesheet);
  140. };
  141.  
  142. //--- Constants
  143. var scriptShortName = 'ytwp'; // YT Window Player
  144. var scriptStyleId = scriptShortName + '-style'; // ytwp-style
  145. var scriptBodyClassId = scriptShortName + '-window-player'; // .ytwp-window-player
  146. var viewingVideoClassId = scriptShortName + '-viewing-video'; // .ytwp-viewing-video
  147. var topOfPageClassId = scriptShortName + '-scrolltop'; // .ytwp-scrolltop
  148. var scriptBodyClassSelector = 'body.' + scriptBodyClassId; // body.ytwp-window-player
  149. var videoContainerId = 'player';
  150. var videoContainerPlacemarkerId = scriptShortName + '-placemarker'; // ytwp-placemarker
  151. var transitionProperties = ["transition", "-ms-transition", "-moz-transition", "-webkit-transition", "-o-transition"];
  152. //--- YTWP
  153. var ytwp = window.ytwp = {
  154. scriptShortName: scriptShortName, // YT Window Player
  155. log_: function(logger, args) { logger.apply(console, ['[' + this.scriptShortName + '] '].concat(Array.prototype.slice.call(args))); return 1; },
  156. log: function() { return this.log_(console.log, arguments); },
  157. error: function() { return this.log_(console.error, arguments); },
  158.  
  159. initialized: false,
  160. pageReady: false,
  161. watchPage: false,
  162. };
  163.  
  164. ytwp.util = {
  165. isWatchUrl: function (url) {
  166. if (!url)
  167. url = window.location.href;
  168. return url.match(/https?:\/\/(www\.)?youtube.com\/watch\?/);
  169. }
  170. };
  171.  
  172. ytwp.event = {
  173. init: function() {
  174. ytwp.log('init');
  175. if (ytwp.initialized) return;
  176.  
  177. ytwp.isWatchPage = ytwp.util.isWatchUrl();
  178. if (!ytwp.isWatchPage) return;
  179.  
  180. ytwp.event.initStyle();
  181. ytwp.event.initScroller();
  182. ytwp.initialized = true;
  183. ytwp.pageReady = false;
  184. },
  185. initScroller: function() {
  186. // Register listener & Call it now.
  187. unsafeWindow.addEventListener('scroll', ytwp.event.onScroll, false);
  188. unsafeWindow.addEventListener('resize', ytwp.event.onScroll, false);
  189. ytwp.event.onScroll();
  190. },
  191. onScroll: function() {
  192. var viewportHeight = document.documentElement.clientHeight;
  193. // topOfPageClassId
  194. if (unsafeWindow.scrollY == 0) {
  195. jQuery.addClass(document.body, topOfPageClassId);
  196. } else {
  197. jQuery.removeClass(document.body, topOfPageClassId);
  198. }
  199.  
  200. // viewingVideoClassId
  201. if (unsafeWindow.scrollY <= viewportHeight) {
  202. jQuery.addClass(document.body, viewingVideoClassId);
  203. } else {
  204. jQuery.removeClass(document.body, viewingVideoClassId);
  205. }
  206. },
  207. initStyle: function() {
  208. ytwp.log('initStyle');
  209. ytwp.style = new JSStyleSheet(scriptStyleId);
  210. ytwp.event.buildStylesheet();
  211. ytwp.style.injectIntoHeader();
  212. },
  213. buildStylesheet: function() {
  214. ytwp.log('buildStylesheet');
  215. //--- Video Player
  216. //
  217. var d;
  218. d = buildVenderPropertyDict(transitionProperties, 'left 0s linear, padding-left 0s linear');
  219. d['padding'] = '0 !important';
  220. d['margin'] = '0 !important';
  221. ytwp.style.appendRule([
  222. scriptBodyClassSelector + ' #player',
  223. scriptBodyClassSelector + '.ytcenter-site-center.ytcenter-non-resize.ytcenter-guide-visible #player',
  224. scriptBodyClassSelector + '.ltr.ytcenter-site-center.ytcenter-non-resize.ytcenter-guide-visible.guide-collapsed #player',
  225. scriptBodyClassSelector + '.ltr.ytcenter-site-center.ytcenter-non-resize.ytcenter-guide-visible.guide-collapsed #player-legacy',
  226. scriptBodyClassSelector + '.ltr.ytcenter-site-center.ytcenter-non-resize.ytcenter-guide-visible.guide-collapsed #watch7-main-container',
  227. ], d);
  228. //
  229. d = buildVenderPropertyDict(transitionProperties, 'width 0s linear, left 0s linear');
  230.  
  231. // Bugfix for Firefox
  232. // Parts of the header (search box) are hidden under the player.
  233. // Firefox doesn't seem to be using the fixed header+guide yet.
  234. d['float'] = 'initial';
  235.  
  236. ytwp.style.appendRule(scriptBodyClassSelector + ' #player-api', d);
  237.  
  238. // !important is mainly for simplicity, but is needed to override the !important styling when the Guide is open due to:
  239. // .sidebar-collapsed #watch7-video, .sidebar-collapsed #watch7-main, .sidebar-collapsed .watch7-playlist { width: 945px!important; }
  240. // Also, Youtube Center resizes #player at element level.
  241. ytwp.style.appendRule(
  242. [
  243. scriptBodyClassSelector + ' #player',
  244. scriptBodyClassSelector + ' #movie_player',
  245. scriptBodyClassSelector + ' #player-mole-container',
  246. ],
  247. {
  248. 'width': '100% !important',
  249. 'height': '100% !important',
  250. 'min-width': '100% !important',
  251. 'max-width': '100% !important',
  252. 'min-height': '100% !important',
  253. 'max-height': '100% !important'
  254. }
  255. );
  256.  
  257. // Resize #player-unavailable, #player-api
  258. // Using min/max width/height will keep
  259. ytwp.style.appendRule(scriptBodyClassSelector + ' #player .player-width', 'width', '100% !important');
  260. ytwp.style.appendRule(scriptBodyClassSelector + ' #player .player-height', 'height', '100% !important');
  261. //--- Sidebar
  262. // Remove the transition delay as you can see it moving on page load.
  263. d = buildVenderPropertyDict(transitionProperties, 'margin-top 0s linear, padding-top 0s linear');
  264. d['margin-top'] = '0 !important';
  265. d['top'] = '0 !important';
  266. ytwp.style.appendRule(scriptBodyClassSelector + ' #watch7-sidebar', d);
  267.  
  268. ytwp.style.appendRule(scriptBodyClassSelector + '.cardified-page #watch7-sidebar-contents', 'padding-top', '0');
  269. //--- Absolutely position the fixed header.
  270. // Masthead
  271. ytwp.style.appendRule(scriptBodyClassSelector + '.' + viewingVideoClassId + ' #masthead-positioner', {
  272. 'position': 'absolute',
  273. 'top': '100% !important'
  274. });
  275. // Guide
  276. // When watching the video, we need to line it up with the masthead.
  277. ytwp.style.appendRule(scriptBodyClassSelector + '.' + viewingVideoClassId + ' #appbar-guide-menu', {
  278. 'display': 'initial',
  279. 'position': 'absolute',
  280. 'top': '100% !important' // Masthead height
  281. });
  282. ytwp.style.appendRule(scriptBodyClassSelector + '.' + viewingVideoClassId + ' #page.watch #guide', {
  283. 'display': 'initial',
  284. 'margin': '0',
  285. 'position': 'initial'
  286. });
  287.  
  288. //---
  289. // Hide Scrollbars
  290. ytwp.style.appendRule(scriptBodyClassSelector + '.' + topOfPageClassId, 'overflow-x', 'hidden');
  291.  
  292. //--- Fix Other Possible Style Issues
  293.  
  294. //--- Whitespace Leftover From Moving The Video
  295. ytwp.style.appendRule(scriptBodyClassSelector + ' #page.watch', 'padding-top', '0');
  296. ytwp.style.appendRule(scriptBodyClassSelector + ' .player-branded-banner', 'height', '0');
  297. //--- Playlist Bar
  298. //ytwp.style.appendRule(scriptBodyClassSelector + ' #watch7-playlist-tray-container', "margin", "-15px -10px 20px -10px");
  299. ytwp.style.appendRule(scriptBodyClassSelector + ' .watch7-playlist-bar-left', 'width', '640px !important'); // Same width as .watch-content
  300. ytwp.style.appendRule([
  301. scriptBodyClassSelector + ' .playlist',
  302. scriptBodyClassSelector + ' .playlist .watch7-playlist-bar',
  303. ], 'max-width', '1040px'); // Same width as .watch-content (640px) + .watch-sidebar (300-400px).
  304. ytwp.style.appendRule(scriptBodyClassSelector + ' #watch7-playlist-tray-container', {
  305. "margin-top": "-15px",
  306. "height": "287px !important", // 65 (playlist tile) * 4 + 27 (trim on bottom)
  307. "margin-bottom": "15px"
  308. });
  309. ytwp.style.appendRule([
  310. scriptBodyClassSelector + '.cardified-page #watch7-playlist-tray-container + #watch7-sidebar-contents', // Pre Oct 26
  311. scriptBodyClassSelector + '.cardified-page #watch-appbar-playlist + #watch7-sidebar-contents', // Post Oct 26
  312. ], 'padding-top', '15px');
  313. // YT Center
  314. ytwp.style.appendRule(scriptBodyClassSelector + ' #player', 'margin-bottom', '0 !important');
  315. ytwp.style.appendRule(scriptBodyClassSelector + ' #watch7-playlist-tray-container', {
  316. 'left': 'initial !important',
  317. 'width': 'initial !important'
  318. });
  319. ytwp.style.appendRule(scriptBodyClassSelector + ' .watch7-playlist-bar-right', 'width', '363px !important');
  320. },
  321. onWatchInit: function() {
  322. ytwp.log('onWatchInit');
  323. if (!ytwp.initialized) return;
  324. if (ytwp.pageReady) return;
  325.  
  326. ytwp.event.moveVideoContainer();
  327. ytwp.event.movePlaylist();
  328. ytwp.event.addBodyClass();
  329. ytwp.pageReady = true;
  330. },
  331. onWatchDispose: function() {
  332. ytwp.log('onWatchDispose');
  333. if (ytwp.isWatchPage) {
  334. if (ytwp.util.isWatchUrl()) {
  335. ytwp.event.onWatchDisposeToWatch();
  336. } else {
  337. ytwp.event.onWatchDisposeToElsewhere();
  338. }
  339. }
  340. },
  341. onDispose: function() {
  342. ytwp.initialized = false;
  343. ytwp.pageReady = false;
  344. ytwp.isWatchPage = false;
  345. },
  346. onWatchDisposeToWatch: function() {
  347. ytwp.log('onWatchDisposeToWatch');
  348. },
  349. onWatchDisposeToElsewhere: function() {
  350. ytwp.log('onWatchDisposeToElsewhere');
  351. // Delete the Video player (as it's not where it normally is).
  352. // var videoContainer = document.getElementById(videoContainerId);
  353. // if (videoContainer)
  354. // videoContainer.remove();
  355. },
  356. moveVideoContainer: function() {
  357. // https://developers.google.com/youtube/js_api_reference#getPlayerState
  358. var PLAYING = 1;
  359. var BUFFERING = 3;
  360. var autoPlay = false;
  361. try {
  362. var playerState = document.getElementById('movie_player').getPlayerState();
  363. autoPlay = playerState == PLAYING || playerState == BUFFERING;
  364. } catch (e) {}
  365.  
  366. ytwp.log('moveVideoContainer');
  367. var videoContainer = document.getElementById(videoContainerId);
  368. var body = document.body;
  369. body.insertBefore(videoContainer, body.firstChild);
  370.  
  371. // Moving the player seems to pause the video for some reason.
  372. try {
  373. if (autoPlay) {
  374. document.getElementById('movie_player').playVideo();
  375. ytwp.log('autoplaying');
  376. }
  377. } catch(e) {
  378. // Videos in a playlist will cause this error, but will play fine.
  379. //ytwp.error('Error calling playVideo(). Might not autoplay video.');
  380. }
  381. },
  382. removeVideoContainer: function() {
  383. ytwp.log('removeVideoContainer');
  384. var videoContainer = document.getElementById(videoContainerId);
  385. if (videoContainer)
  386. videoContainer.parentNode.removeChild(videoContainer);
  387. },
  388. movePlaylist: function() {
  389. // --- Old Playlist bar (still in firefox).
  390.  
  391. // Move the bar to the top of the main container.
  392. var mainContainer = document.getElementById('watch7-main-container');
  393. var bar = document.getElementById('playlist');
  394. if (mainContainer && bar) {
  395. mainContainer.insertBefore(bar, mainContainer.firstChild);
  396. ytwp.log('Moved #playlist');
  397. }
  398. // Move the tray to inside the sidebar
  399. var tray = document.getElementById('watch7-playlist-tray-container');
  400. var sidebar = document.getElementById('watch7-sidebar');
  401. if (tray && sidebar) {
  402. sidebar.insertBefore(tray, sidebar.firstChild);
  403. ytwp.log('Moved #watch7-playlist-tray-container');
  404. }
  405. },
  406. addBodyClass: function() {
  407. // Insert CSS Into the body so people can style around the effects of this script.
  408. jQuery.addClass(document.body, scriptBodyClassId);
  409. ytwp.log('Applied ' + scriptBodyClassSelector);
  410. },
  411. html5PlayerSeekFix: function() {
  412. var videoContainer = document.getElementById(videoContainerId);
  413. if (videoContainer) {
  414. var watchClasses = [
  415. 'watch-small',
  416. 'watch-medium',
  417. 'watch-medium-540',
  418. 'watch-large'
  419. ];
  420. for (var i = watchClasses.length - 1; i >= 0; i--) {
  421. jQuery.removeClass(videoContainer, watchClasses[i]);
  422. }
  423. }
  424. }
  425. };
  426.  
  427. ytwp.pubsubListeners = {
  428. 'init': function() { // Not always called
  429. ytwp.event.init();
  430. ytwp.event.onWatchInit();
  431. },
  432. 'init-watch': function() { // Not always called
  433. ytwp.event.init();
  434. ytwp.event.onWatchInit();
  435. },
  436. 'player-added': function() { // Not always called
  437. // Usually called after init-watch, however this is called before init when going from channel -> watch page.
  438. // The init event is when the body element resets all it's classes.
  439. ytwp.event.init();
  440. ytwp.event.onWatchInit();
  441. ytwp.event.html5PlayerSeekFix();
  442. },
  443. 'player-resize': function() {
  444. ytwp.event.html5PlayerSeekFix();
  445. },
  446. 'player-playback-start': function() {
  447. ytwp.event.html5PlayerSeekFix();
  448. },
  449. 'appbar-guide-delay-load': function() {
  450. // Listen to a later event that is always called in case the others are missed.
  451. ytwp.event.init();
  452. ytwp.event.onWatchInit();
  453. // Channel -> /watch
  454. if (ytwp.util.isWatchUrl())
  455. ytwp.event.addBodyClass();
  456. },
  457. 'dispose-watch': function() {
  458. ytwp.event.onWatchDispose();
  459. },
  460. 'dispose': function() {
  461. ytwp.event.onDispose();
  462. }
  463. };
  464.  
  465. ytwp.initLogging = function() {
  466. };
  467.  
  468. ytwp.registerYoutubeListeners = function() {
  469. // ytwp.registerYoutubePlayerApiListeners();
  470. ytwp.registerYoutubePubSubListeners();
  471. };
  472.  
  473. ytwp.registerYoutubePlayerApiListeners = function() {
  474. var onYouTubePlayerReady_old = onYouTubePlayerReady;
  475. onYouTubePlayerReady = function() {
  476. onYouTubePlayerReady_old.apply(this, arguments);
  477. };
  478. };
  479.  
  480. ytwp.registerYoutubePubSubListeners = function() {
  481. // Debugging Youtubes Events
  482. // var yt_pubsub_publish = yt.pubsub.instance_.publish;
  483. // yt.pubsub.instance_.publish = function(){
  484. // // ytwp.log(arguments);
  485. // ytwp.log('[pubsub]', arguments[0]);
  486. // yt_pubsub_publish.apply(this, arguments);
  487. // };
  488.  
  489. // Subscribe
  490. for (var eventName in ytwp.pubsubListeners) {
  491. var eventListener = ytwp.pubsubListeners[eventName];
  492. yt.pubsub.instance_.subscribe(eventName, eventListener);
  493. }
  494. };
  495.  
  496. ytwp.main = function() {
  497. ytwp.initLogging();
  498. try {
  499. ytwp.registerYoutubeListeners();
  500. } catch(e) {
  501. ytwp.error("Could not hook yt.pubsub");
  502. }
  503. };
  504.  
  505. ytwp.main();
  506. })(unsafeWindow);