Greasy Fork 还支持 简体中文。

Twitch Player Plus

Various tweaks to the Twitch HTML5 player UI

目前為 2015-12-30 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Twitch Player Plus
  3. // @namespace http://ehsankia.com
  4. // @version 1.0
  5. // @description Various tweaks to the Twitch HTML5 player UI
  6. // @match http://www.twitch.tv/*
  7. // @match http://player.twitch.tv/*
  8. // @grant GM_addStyle
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @require http://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
  12. // @copyright 2015+, Ehsan Kia
  13. // ==/UserScript==
  14.  
  15. var html5Player;
  16. var waitForPlayerReadyTimer = setInterval(function() {
  17. html5Player = $('div.player');
  18. if (html5Player.length > 0) {
  19. if (html5Player.attr('data-loading') === "false") {
  20. clearInterval(waitForPlayerReadyTimer);
  21. window.eval("var flashBackend = $('div.player-video object')[0];");
  22. setTimeout(applyFixes, 100);
  23. }
  24. }
  25. }, 100);
  26.  
  27. function applyFixes() {
  28.  
  29. // Move quality options to main bar
  30. $(".js-quality").insertAfter($('.js-quality-display-contain'));
  31.  
  32. // Remove remaining label
  33. $("span:contains('Video Quality:')").remove();
  34.  
  35. // Hide options if there are no transcoders
  36. checkForQualityOptions();
  37. setInterval(checkForQualityOptions, 5000);
  38.  
  39. // Bind F key to toggle fullscreen
  40. $('body').keypress(function(e) {
  41. if (e.which === 102) {
  42. // fallback to event.target just in case
  43. var el = document.activeElement || e.target;
  44. var t = (el && el.tagName.toLowerCase()) || '';
  45.  
  46. // pass through to elements that take keyboard input
  47. if (/(input|textarea|select)/.test(t)) {
  48. return true;
  49. }
  50.  
  51. $('.js-control-fullscreen').click();
  52. return false;
  53. }
  54. });
  55.  
  56. // Add latency status under Live icon
  57. var liveIcon = $('.player-livestatus__online');
  58. liveIcon.append("<div class='lag-status'></div>");
  59. window.eval('flashBackend.startPlaybackStatistics();');
  60. setTimeout(updateLatency, 5000);
  61.  
  62. // Remove old stats button and add new one
  63. $('.player-menu__item--stats').css('display', 'none');
  64. $('.js-control-fullscreen').before(" \
  65. <button type='button' class='player-button js-custom-stats-toggle'> \
  66. <span class='player-tip' data-tip='Video Stats'></span> \
  67. <svg id='icon-stats' viewBox='0 0 1024 1024' style='width: 16px; fill: white; margin: 1px 6px;'> \
  68. <path d='M960 0h-896c-35.328 0-64 28.672-64 64v640c0 35.328 28.672 64 64 64h256l-128 256h32l230.4-256h115.2l230.4 256h32l-128-256h256c35.328 0 64-28.672 64-64v-640c0-35.328-28.672-64-64-64zM960 672c0 17.696-14.304 32-32 32h-832c-17.696 0-32-14.304-32-32v-576c0-17.696 14.304-32 32-32h832c17.696 0 32 14.304 32 32v576zM668.096 500.192l-144.672-372.128-158.016 297.28-88.192-90.72-149.216 92.992 42.112 24.256 95.616-59.584 115.36 118.784 133.6-251.296 147.712 380.128 125.984-265.216 51.328 109.248 56.288-9.44-107.328-228.224-120.576 253.92z'></path> \
  69. </svg> \
  70. </button>");
  71. $('.js-custom-stats-toggle').click(function(){
  72. var prev = $('.js-playback-stats').attr('data-state');
  73. var state = prev === 'on' ? 'off' : 'on';
  74. $('.js-playback-stats').attr('data-state', state);
  75. });
  76.  
  77. // Check if it's a VOD and there isn't a seek argument in the url
  78. var vodID = html5Player.attr('data-video');
  79. var hasSeekParam = document.location.search.search("t=") >= 0;
  80. if (vodID !== undefined && !hasSeekParam) {
  81. //seek to previous position and keep track of the position
  82. var oldTime = GM_getValue("seek_" + vodID);
  83. if (oldTime !== undefined) {
  84. oldTime = parseFloat(oldTime);
  85. window.eval('flashBackend.videoSeek(' + oldTime + ');');
  86. }
  87. setTimeout(function() {
  88. setInterval(trackSeekTime, 15000);
  89. }, 5 * 60 * 1000);
  90. }
  91. }
  92.  
  93. function checkForQualityOptions() {
  94. var numQualityOptions = $(".js-quality > option").length;
  95. if (numQualityOptions > 1) {
  96. $('.js-quality').css('display', 'block');
  97. } else {
  98. $('.js-quality').css('display', 'none');
  99. }
  100. }
  101.  
  102. function updateLatency() {
  103. var lat = $('.js-stat-hls-latency-broadcaster').text();
  104. if (lat === "" || lat === "NaN") {
  105. window.eval('flashBackend.stopPlaybackStatistics();');
  106. window.eval('flashBackend.startPlaybackStatistics();');
  107. setTimeout(updateLatency, 5000);
  108. } else {
  109. $('.lag-status').text(lat + ' sec.');
  110. setTimeout(updateLatency, 1000);
  111. }
  112. }
  113.  
  114. function trackSeekTime() {
  115. var vodID = html5Player.attr('data-video');
  116. var seekTime = window.eval('flashBackend.getVideoTime();');
  117. if (seekTime < 5 * 60) return;
  118. GM_setValue("seek_" + vodID, seekTime);
  119. }
  120.  
  121. GM_addStyle(" \
  122. .js-volume-container { width: 13em; } \
  123. select.js-quality:hover { color: #a991d4 !important; } \
  124. select.js-quality, select.js-quality:focus { \
  125. float: left; \
  126. height: 29px; \
  127. margin: 0 6px 0 4px; \
  128. padding: 0; \
  129. color: white; \
  130. font-weight: bold; \
  131. background: none; \
  132. border: none; \
  133. box-shadow: 0 0 black; \
  134. appearance: none; \
  135. -moz-appearance: none; \
  136. -webkit-appearance: none; \
  137. outline: none; \
  138. cursor: pointer; \
  139. } \
  140. select.js-quality > option { \
  141. color: white; \
  142. background: black; \
  143. padding: 0 5px; \
  144. margin-right: -15px; \
  145. font-weight: bold; \
  146. } \
  147. .lag-status { \
  148. width: 60px; \
  149. margin-left: -20px; \
  150. text-align: center; \
  151. } \
  152. .js-custom-stats-toggle:hover > svg { \
  153. fill: #a991d4 !important; \
  154. }");