├── .gitignore ├── LICENSE ├── README.md ├── Twitch-Auto-Max-Quality.user.js └── Twitch-Mobile-Web-Source-Quality.user.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .eslintrc 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Nomo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twitch-Auto-Max-Quality 2 | 3 | 7 | 8 | > **Important: As a Korean developer, I no longer plan to actively maintain this script as Twitch has shut down operations in South Korea. Check [this article](https://blog.twitch.tv/en/2023/12/05/an-update-on-twitch-in-korea/) for more information.** 9 | 10 | - Always start playing live video with the quality you want on twitch.tv. 11 | - Prevent automatic change of video quality when tab is disabled. 12 | 13 | ## Install 14 | 15 | ### STEP 1. ScriptManager 16 | 17 | - Firefox - [Tampermonkey](https://addons.mozilla.org/ko/firefox/addon/tampermonkey/) 18 | - Chrome - [Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) 19 | - Opera - [Tampermonkey](https://addons.opera.com/extensions/details/tampermonkey-beta/) 20 | - Safari - [Tampermonkey](https://safari.tampermonkey.net/tampermonkey.safariextz) 21 | - Edge - [Tampermonkey](https://microsoftedge.microsoft.com/addons/detail/tampermonkey/iikmkjmpaadaobahmlepeloendndfphd) 22 | 23 | This script may not work properly on script managers other than Tampermonkey. 24 | 25 | ### STEP 2. UserScript 26 | 27 | - [Install](https://raw.githubusercontent.com/nomomo/Twitch-Auto-Max-Quality/master/Twitch-Auto-Max-Quality.user.js) from [https://raw.githubusercontent.com/nomomo/Twitch-Auto-Max-Quality/master/Twitch-Auto-Max-Quality.user.js](https://raw.githubusercontent.com/nomomo/Twitch-Auto-Max-Quality/master/Twitch-Auto-Max-Quality.user.js) 28 | 29 | ## Bug report 30 | 31 | nomotg@gmail.com 32 | 33 | ## Change log 34 | 35 | ### 0.4.1b (2024-03-01) 36 | 37 | - I have set the "Only the quality you want method" to default. New users installing the script will now only see the maximum quality. Existing users will not be affected. 38 | 39 | ### 0.4.1 (2023-03-23) 40 | 41 | - Fixed an issue where 160p was exposed with source quality in certain environments when using the "Only the quality you want method" (Thanks Taskmant!) 42 | 43 | ### 0.4.0 (2023-02-19) 44 | 45 | - The script version update was missing, only the version was updated. 46 | 47 | ### 0.4.0 (2023-02-11) 48 | 49 | - "Only source quality method" is renewed as "Only the quality you want method" 50 | - You can now remove all but the quality you want, not just the source quality. (set it in the settings menu) 51 | - Now again "Simulate settings button method" works fine. 52 | - The script was modified according to some changes in the Twitch player. 53 | - Known bug for "Simulate settings button method": When browsing VOD and CLIP on twitch.tv in some environments, it automatically scrolls down after setting the quality. 54 | 55 | ### 0.3.2 (2022-07-31) 56 | 57 | - Add legacy option to "Only source quality method" 58 | - Type legacy : Removes all other selectable video quality except source quality & show (source) text. 59 | - Fixed an issue where the "Only source quality method" did not work on the VOD page. 60 | 61 | ### 0.3.1 (2022-07-30) 62 | 63 | - Now, when using the "Only source quality method", the text "(source)" is no longer displayed in the quality name. Of course the video quality set by the script is the source quality, even if "(source)" is not displayed. 64 | - Add options to "Only source quality method" 65 | - Type 0 : Removes all other selectable video quality except best quality 66 | - Type 1 : Overwrite all selectable video quality with best quality 67 | 68 | ### 0.2.2 (2022-03-23) 69 | 70 | - "Disable power saving for inactive tabs" feature no longer works with embed Twitch clips to avoid conflicts with ad blocking extensions. 71 | 72 | ### 0.2.1 (2022-03-09) 73 | 74 | - New TAMQ Labs feature: "Disable power saving for inactive tabs (Disable JavaScript Timer Throttling)" 75 | - If you often have problems playing videos in inactive tabs, try using this feature. 76 | - By enabling this option, you can disable some of the power saving features (Javascript Timer Throttling) for inactive tabs supported by Chrome-based browsers. 77 | - This feature may conflict with certain ad filters in the ad blocking extension. 78 | 79 | ### 0.2.0 (2022-03-04) 80 | 81 | - Now "Only source quality method" works on the VOD page as well. 82 | - New feature added: "Apply \'Only source quality method\' to the clip page" 83 | - Removed "Use the Twitch EMBED API in an Iframe" and "Use for live video where url starts with blob" options. 84 | - Korean language is supported in the settings menu. You can select English and Korean(한국어) from the setting menu. 85 | 86 | ### 0.1.2 (2021-09-15) 87 | 88 | - Fixed an issue related to the timeline of VOD (Thanks MonkeyDMax92) 89 | 90 | ### 0.1.1 (2021-09-14) 91 | 92 | - Fixed the script not working properly in Violentmonkey. 93 | - Added exception handling. 94 | 95 | ### 0.1.0 (2021-08-04) 96 | 97 | - New feature added: "Only source quality method" 98 | - Removes all other selectable video quality except source quality. So even if the Twitch player sets the video quality to "Auto", the only selectable "Source quality" is set. 99 | - When this option is enabled, Localstorage modify method and Simulate settings button method are automatically disabled. 100 | - Caution: Enabling this option may conflict with other scripts (eg TwitchAdSolution.), causing problems with video playback or the scripts not working properly. Of course, it might work just fine. If you run into problems, setting the \"Position\" to 1 or the last one in \"Settings\" tab of the script settings menu may solve the problem. If that doesn't work, turn this option off. 101 | 102 | ### 0.0.9 (2021-06-07) 103 | 104 | - Fixes an issue where the script unintentionally works on some clip pages. 105 | 106 | ### 0.0.8 (2021-06-05) 107 | 108 | - Now again "Simulate settings button method" works fine. 109 | - You can now set your preferred video quality. (set it in the settings menu) 110 | - Removed "legacy mode" of "Simulate settings button method" 111 | 112 | ### 0.0.7 (2021-03-10) 113 | 114 | - Removed 'Automatic restart on error' that did not work properly. 115 | - Fixed jquery https related problem. (Thanks sowind) 116 | - Added 'Set the volume when stream starts'. (enable it in the settings menu) 117 | 118 | ### 0.0.6 (2020-10-29) 119 | 120 | - It works better for cases where there is only one video quality. 121 | - User can set initial delay for automatic quality change. If the problem occurs because the script changes the video quality too quickly, increase the delay. 122 | 123 | ### 0.0.5 (2020-05-31) 124 | 125 | - In May 2020, the structure of the Twitch player inserted as an iframe seems to have changed. So I modified the code so that the script works properly again in the Twitch player inserted as an iframe. However, there may be users who still use the old Twitch Player(idk). If 'Simulate settings button method' does not work properly, try turning on "legacy mode" in the settings menu. 126 | 127 | ### 0.0.4 (2019-11-15) 128 | 129 | - Occasionally, video quality is fixed at 720p when watching live with the embed player. In this case, the script will automatically pause the video and try to play again quickly. 130 | 131 | ### 0.0.3 (2019-10-23) 132 | 133 | - This script will be disabled when users watch squad streaming. (This script does not currently support squad streaming page) 134 | - Initial delay (500 ms) is applied when the video quality setting menu is clicked virtually. This will prevent the problem caused by trying to change the video quality too quickly. 135 | 136 | ### 0.0.2 (2019-10-17) 137 | 138 | - Modified to apply to the new layout of the Twitch. 139 | 140 | ### 0.0.1 (2019-10-07) 141 | 142 | - Initial commit 143 | 144 | ## Happy?? 145 | 146 | Buy Me A Coffee 147 | -------------------------------------------------------------------------------- /Twitch-Auto-Max-Quality.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Twitch-Auto-Max-Quality 3 | // @namespace Twitch-Auto-Max-Quality 4 | // @version 0.4.1 5 | // @author Nomo 6 | // @description Always start playing live video with source quality on twitch.tv 7 | // @supportURL https://github.com/nomomo/Twitch-Auto-Max-Quality/issues 8 | // @homepage https://github.com/nomomo/Twitch-Auto-Max-Quality/ 9 | // @downloadURL https://raw.githubusercontent.com/nomomo/Twitch-Auto-Max-Quality/master/Twitch-Auto-Max-Quality.user.js 10 | // @updateURL https://raw.githubusercontent.com/nomomo/Twitch-Auto-Max-Quality/master/Twitch-Auto-Max-Quality.user.js 11 | // @include *://*.twitch.tv/* 12 | // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js 13 | // @require https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js 14 | // @run-at document-start 15 | // @grant GM.addStyle 16 | // @grant GM_addStyle 17 | // @grant GM.getValue 18 | // @grant GM_getValue 19 | // @grant GM.setValue 20 | // @grant GM_setValue 21 | // @grant GM.deleteValue 22 | // @grant GM_deleteValue 23 | // @grant GM.listValues 24 | // @grant GM_listValues 25 | // @grant GM.info 26 | // @grant GM_info 27 | // @grant GM.xmlHttpRequest 28 | // @grant GM_xmlhttpRequest 29 | // @grant GM.registerMenuCommand 30 | // @grant GM_registerMenuCommand 31 | // @grant GM_getResourceText 32 | // @grant GM.notification 33 | // @grant GM_notification 34 | // @grant GM.addValueChangeListener 35 | // @grant GM_addValueChangeListener 36 | // @grant GM.removeValueChangeListener 37 | // @grant GM_removeValueChangeListener 38 | // @grant unsafeWindow 39 | // ==/UserScript== 40 | // @icon https://raw.githubusercontent.com/nomomo/Twitch-Auto-Max-Quality/master/images/logo.png 41 | /* eslint-disable no-undef */ 42 | "use strict"; 43 | (async () => { 44 | console.log("[TAMQ] RUNNING TWITCH AUTO MAX QUALITY", document.location.href); 45 | var DEBUG = await GM.getValue("DEBUG", false); 46 | 47 | //////////////////////////////////////////////////////////////////////////////////// 48 | // libs 49 | //////////////////////////////////////////////////////////////////////////////////// 50 | var NOMO_DEBUG = function ( /**/ ) { 51 | if (!DEBUG) return; 52 | var args = arguments, args_length = args.length, args_copy = args; 53 | for (let i = args_length; i > 0; i--) args[i] = args_copy[i - 1]; 54 | args[0] = "[TAMQ] "; 55 | args.length = args_length + 1; 56 | console.log.apply(console, args); 57 | }; 58 | 59 | /* arrive.js 60 | * v2.4.1 61 | * https://github.com/uzairfarooq/arrive 62 | * MIT licensed 63 | * Copyright (c) 2014-2017 Uzair Farooq 64 | */ 65 | // eslint-disable-next-line no-cond-assign, no-unused-vars, no-prototype-builtins 66 | const Arrive = function(e,t,n){"use strict";function r(e,t,n){l.addMethod(t,n,e.unbindEvent),l.addMethod(t,n,e.unbindEventWithSelectorOrCallback),l.addMethod(t,n,e.unbindEventWithSelectorAndCallback);}function i(e){e.arrive=f.bindEvent,r(f,e,"unbindArrive"),e.leave=d.bindEvent,r(d,e,"unbindLeave");}if(e.MutationObserver&&"undefined"!=typeof HTMLElement){var o=0,l=function(){var t=HTMLElement.prototype.matches||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector;return{matchesSelector:function(e,n){return e instanceof HTMLElement&&t.call(e,n);},addMethod:function(e,t,r){var i=e[t];e[t]=function(){return r.length==arguments.length?r.apply(this,arguments):"function"==typeof i?i.apply(this,arguments):n;};},callCallbacks:function(e,t){t&&t.options.onceOnly&&1==t.firedElems.length&&(e=[e[0]]);for(var n,r=0;n=e[r];r++)n&&n.callback&&n.callback.call(n.elem,n.elem);t&&t.options.onceOnly&&1==t.firedElems.length&&t.me.unbindEventWithSelectorAndCallback.call(t.target,t.selector,t.callback);},checkChildNodesRecursively:function(e,t,n,r){for(var i,o=0;i=e[o];o++)n(i,t,r)&&r.push({callback:t.callback,elem:i}),i.childNodes.length>0&&l.checkChildNodesRecursively(i.childNodes,t,n,r);},mergeArrays:function(e,t){var n,r={};for(n in e)e.hasOwnProperty(n)&&(r[n]=e[n]);for(n in t)t.hasOwnProperty(n)&&(r[n]=t[n]);return r;},toElementsArray:function(t){return n===t||"number"==typeof t.length&&t!==e||(t=[t]),t;}};}(),c=function(){var e=function(){this._eventsBucket=[],this._beforeAdding=null,this._beforeRemoving=null;};return e.prototype.addEvent=function(e,t,n,r){var i={target:e,selector:t,options:n,callback:r,firedElems:[]};return this._beforeAdding&&this._beforeAdding(i),this._eventsBucket.push(i),i;},e.prototype.removeEvent=function(e){for(var t,n=this._eventsBucket.length-1;t=this._eventsBucket[n];n--)if(e(t)){this._beforeRemoving&&this._beforeRemoving(t);var r=this._eventsBucket.splice(n,1);r&&r.length&&(r[0].callback=null);}},e.prototype.beforeAdding=function(e){this._beforeAdding=e;},e.prototype.beforeRemoving=function(e){this._beforeRemoving=e;},e;}(),a=function(t,r){var i=new c,o=this,a={fireOnAttributesModification:!1};return i.beforeAdding(function(n){var i,l=n.target;(l===e.document||l===e)&&(l=document.getElementsByTagName("html")[0]),i=new MutationObserver(function(e){r.call(this,e,n);});var c=t(n.options);i.observe(l,c),n.observer=i,n.me=o;}),i.beforeRemoving(function(e){e.observer.disconnect();}),this.bindEvent=function(e,t,n){t=l.mergeArrays(a,t);for(var r=l.toElementsArray(this),o=0;o0?l.checkChildNodesRecursively(n,t,r,o):"attributes"===e.type&&r(i,t,o)&&o.push({callback:t.callback,elem:i}),l.callCallbacks(o,t);});}function r(e,t){return l.matchesSelector(e,t.selector)&&(e._id===n&&(e._id=o++),-1==t.firedElems.indexOf(e._id))?(t.firedElems.push(e._id),!0):!1;}var i={fireOnAttributesModification:!1,onceOnly:!1,existing:!1};f=new a(e,t);var c=f.bindEvent;return f.bindEvent=function(e,t,r){n===r?(r=t,t=i):t=l.mergeArrays(i,t);var o=l.toElementsArray(this);if(t.existing){for(var a=[],s=0;s0&&l.checkChildNodesRecursively(n,t,r,i),l.callCallbacks(i,t);});}function r(e,t){return l.matchesSelector(e,t.selector);}var i={};d=new a(e,t);var o=d.bindEvent;return d.bindEvent=function(e,t,r){n===r?(r=t,t=i):t=l.mergeArrays(i,t),o.call(this,e,t,r);},d;},f=new s,d=new u;t&&i(t.fn),i(HTMLElement.prototype),i(NodeList.prototype),i(HTMLCollection.prototype),i(HTMLDocument.prototype),i(Window.prototype);var h={};return r(f,h,"unbindAllArrive"),r(d,h,"unbindAllLeave"),h;}}(window,"undefined"==typeof jQuery?null:jQuery,void 0); 67 | 68 | /* HackTimer.js by turuslan 69 | * v1.1.0 70 | * https://github.com/turuslan/HackTimer 71 | * MIT licensed 72 | */ 73 | // eslint-disable-next-line no-cond-assign 74 | var HackTimerWorker_min_js, HackTimerWorker_min_js_blob; 75 | const disableJavascriptTimer = function() { 76 | if(unsafeWindow["TAMQuseHackTimer"] !== undefined){ 77 | return; 78 | } 79 | unsafeWindow["TAMQuseHackTimer"] = true; 80 | HackTimerWorker_min_js = `var f={},p=postMessage,r='hasOwnProperty';onmessage=function(e){var d=e.data,i=d.i,t=d[r]('t')?d.t:0;switch(d.n){case'a':f[i]=setInterval(function(){p(i)},t);break;case'b':if(f[r](i)){clearInterval(f[i]);delete f[i]}break;case'c':f[i]=setTimeout(function(){p(i);if(f[r](i))delete f[i]},t);break;case'd':if(f[r](i)){clearTimeout(f[i]);delete f[i]}break}}`; 81 | HackTimerWorker_min_js_blob = new Blob([HackTimerWorker_min_js], {type: 'application/javascript'}); 82 | 83 | // eslint-disable-next-line no-cond-assign 84 | // eslint-disable-next-line no-empty, no-unused-vars 85 | (function(s){var w,f={},o=unsafeWindow,l=console,m=Math,z='postMessage',p=0,r='hasOwnProperty',y=[].slice,x='fail',v=o.Worker;function d(){do{p=0x7FFFFFFF>p?p+1:0;}while(f[r](p));return p;}if(!/MSIE 10/i.test(navigator.userAgent)){try{s=o.URL.createObjectURL(new Blob(["var f={},p=postMessage,r='hasOwnProperty';onmessage=function(e){var d=e.data,i=d.i,t=d[r]('t')?d.t:0;switch(d.n){case'a':f[i]=setInterval(function(){p(i)},t);break;case'b':if(f[r](i)){clearInterval(f[i]);delete f[i]}break;case'c':f[i]=setTimeout(function(){p(i);if(f[r](i))delete f[i]},t);break;case'd':if(f[r](i)){clearTimeout(f[i]);delete f[i]}break}}"]));}catch(e){}}if(typeof(v)!=='undefined'){try{w=new v(s);o.setInterval=function(c,t){var i=d();f[i]={c:c,p:y.call(arguments,2)};w[z]({n:'a',i:i,t:t});return i;};o.clearInterval=function(i){if(f[r](i))delete f[i],w[z]({n:'b',i:i});};o.setTimeout=function(c,t){var i=d();f[i]={c:c,p:y.call(arguments,2),t:!0};w[z]({n:'c',i:i,t:t});return i;};o.clearTimeout=function(i){if(f[r](i))delete f[i],w[z]({n:'d',i:i});};w.onmessage=function(e){var i=e.data,c,n;if(f[r](i)){n=f[i];c=n.c;if(n[r]('t'))delete f[i];}if(typeof(c)=='string')try{c=new Function(c);}catch(k){}if(typeof(c)=='function')c.apply(o,n.p);};}catch(e){l.log(x);}}else l.log(x);})(HackTimerWorker_min_js_blob);//('HackTimerWorker.min.js'); 86 | }; 87 | 88 | /* GM_setting.js 89 | * Version: May. 19, 2022 90 | * MIT licensed 91 | * https://github.com/nomomo/ 92 | * nomotg@gmail.com 93 | * Copyright (c) 2017-2022 NOMO 94 | */ 95 | // eslint-disable-next-line 96 | var GM_setting=function(e,t,n){var i,a=void 0,s="",o=[],r={},l={},_={},d={},c=!1,p=function(){if(c){for(var e=arguments,t=e.length,n=e,i=t;i>0;i--)e[i]=n[i-1];e[0]="+[GM_SETTINGS] ",e.length=t+1,console.log.apply(console,e)}},g=(navigator.language||navigator.userLanguage).toLowerCase().substring(0,2),u=g,v="ko",f=!1;const h={en:{title_settings:"Settings",title_reset:"Reset",donate:"Donate",buymeacoffee:"Buy me a coffee",buymeacoffeeDesc:"Support my projects by buying me a coffee! ☕",toonation:"Toonation",button_reset_settings:"Reset Settings",confirm_reset_settings:"Are you sure you want to reset the settings?",complete_reset_settings:"Settings reset complete!",button_reset_settings_all:"Script reset (refresh is required)",confirm_reset_settings_all:"Do you really want to reset script?",complete_reset_settings_all:"Script initialization complete!",auto_saved:"Autosaved: ",err_val_req:"A value must be entered.",err_num_req:"Only numbers can be entered.",err_num_over:"The input value must be a number greater than or equal to : ",err_num_not_more_than:"The input value must be a number less than or equal to: ",err_valid_array_string:"Only English letters, numbers, commas (,) and underscores (_) can be entered.",err_value_empty:"Something for which no value exists, such as an empty value.",err_value_dup:"Duplicate value exists: ",err_value_blank:"There is an item of a space in the string: "},ko:{title_settings:"Settings",title_reset:"Reset",donate:"후원하기",buymeacoffee:"Buy me a coffee 로 커피 한 잔 사주기",buymeacoffeeDesc:"커피 한 잔☕ 으로 프로젝트를 지원해주세요~",toonation:"Toonation 으로 후원하기",button_reset_settings:"Reset Settings",confirm_reset_settings:"진짜 설정을 초기화 할까요?",complete_reset_settings:"설정 초기화 완료!",button_reset_settings_all:"전체 초기화(새로고침 필요)",confirm_reset_settings_all:"진짜 스크립트를 모두 초기화 할까요?",complete_reset_settings_all:"스크립트 초기화 완료!",auto_saved:"자동 저장 됨: ",err_val_req:"반드시 값이 입력되어야 합니다.",err_num_req:"숫자만 입력 가능합니다.",err_num_over:"입력 값은 다음 값 이상의 숫자이어야 합니다. : ",err_num_not_more_than:"입력 값은 다음 값 이하의 숫자이어야 합니다. : ",err_valid_array_string:"영문, 숫자, 콤마(,), 언더바(_) 만 입력 가능합니다.",err_value_empty:"공백 값 등 값이 존재하지 않는 항목이 존재합니다.",err_value_dup:"중복된 값이 존재합니다: ",err_value_blank:"문자열 내 공백이 존재하는 항목이 있습니다: "}};var G=function(e){var t="";if("object"==typeof e){var n=Object.keys(e);if(0===n.length)return t;t=void 0!==e[u]?e[u]:void 0!==e[v]?e[u]:e[n[0]]}else t=e;return t},M=function(e){return void 0!==h[u]?h[u][e]:void 0!==h[v]?h[v][e]:""},m=async function(){""!==s&&await GM.setValue(s,_),t[s]=_,e.each(o,function(e,t){void 0!==l[t]&&void 0!==l[t].change&&l[t].change(_[t])}),o=[]},x=async function(){p("load_"),""!==s&&(_=await GM.getValue(s,_)),_.Lang=await y(),t[s]=_},y=async function(){return u=await GM.getValue("GM_SETTING_LANG",g),p("loadLang_",u),u},b=function(t){d={};var n=e(t);i=n,0!==n.find("#GM_setting_container").length&&n.empty();var s=e("
"),o=e(`\n
\n
Settings
\n
\n \n
\n \n
\n
\n
`);void 0!==GM.info&&null!==GM.info&&void 0!==GM.info.script&&null!==GM.info.script&&void 0!==GM.info.script.homepage&&null!==GM.info.script.homepage&&""!==GM.info.script.homepage?o.find("#GM_homepage_link").show():o.find("#GM_homepage_link").hide();var r=o.find("#GM_multilang");if(f){r.show();var c=r.find("#GM_multilang_select");c.val(u),c.on("change",async function(t){var n=u;e("option:selected",this),this.value;u=this.value,p(`LANG VALUE CHANGED FROM ${n} TO ${u}`),await async function(e){null==e?(await GM.setValue("GM_SETTING_LANG",u),p("saveLang_",u)):(await GM.setValue("GM_SETTING_LANG",e),p("saveLang_",e))}(),null!=a?(e(a).empty(),b(a)):p("NO CREATED LAYOUT")})}else r.hide();var g=e(""),v=void 0;for(var h in n.append(s),s.append(o).append(g),l){var m,x,y=l[h].category,w=l[h].depth,$=l[h].type,S=G(l[h].title),L=G(l[h].desc),z=G(l[h].category_name),N=l[h].radio_enable_value,O=e("
"),E="tag"===$||"textarea"===$;if("radio"===$){var C=l[h].radio;for(var q in m=e("
"),C){var V=e("");e("").attr({value:C[q].value,type:"set"===$?"text"===$:"tag"===$?"textarea":$,GM_setting_type:$,GM_setting_key:h,GM_setting_category:void 0===y?"default":y,GM_setting_radio_enable_value:void 0===N?"none":N}).prependTo(V),m.append(V)}}else if("combobox"===$){var U=l[h].options;for(var j in m=e(``).attr({GM_setting_type:$,GM_setting_key:h,GM_setting_category:void 0===y?"default":y,GM_setting_radio_enable_value:void 0===N?"none":N}),U){var I=e(``);m.append(I)}}else m=e(`<${E?"textarea ":"input "} class='form-control' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' ${"checkbox"===$?"onfocus='this.blur()'":""}${E?">":" />"}`).attr({type:"set"===$?"text"===$:"tag"===$?"textarea":$,GM_setting_type:$,GM_setting_key:h,GM_setting_category:void 0===y?"default":y,GM_setting_radio_enable_value:void 0===N?"none":N});x=e(void 0!==z?`
${z}
`:"
");var B=e("
"),R=e(`${S}`),F=e(`${L}`),H=e(`
  • `);if(g.append(H),B.append(R).append(F),"checkbox"===$)e('').prepend(m).appendTo(O),m.on("change",function(){e(this).is(":checked")?e(this).closest("label").addClass("active"):e(this).closest("label").removeClass("active"),e(this).is(":disabled")?e(this).closest("label").addClass("disable").prop("disabled",!0):e(this).closest("label").removeClass("disable").prop("disabled",!1)});else O.append(m);H.append(x).append(B).append(O),d[h]=m,void 0!==l[h].append&&O.append(l[h].append),v=l[h]}T(),A(n),n.find("input[type='checkbox']").on("click",function(){A(n)}),n.find("input[type='radio']").on("click",function(){A(n)}),n.find("select").on("change",function(){p("GM_setting - select change"),D(e(this),n,d)}),n.find("input, textarea").on("input",function(){p("GM_setting - text change"),D(e(this),n,d)}),g.append(`
  • \n
    ${M("title_reset")}
    \n
    \n \n ${M("button_reset_settings")}\n \x3c!--button_reset_settings_all--\x3e\n \n \n
    \n
    \n
    \n
  • `),g.find(".GM_setting_reset").on("click",async function(){confirm(M("confirm_reset_settings"))&&(await GM_setting.reset(),GM_setting.createlayout(i),k(M("complete_reset_settings")+(new Date).toLocaleTimeString(),i))}),g.find(".GM_setting_reset_all").on("click",async function(){if(confirm(M("confirm_reset_settings_all"))){for(var e=await GM.listValues(),t=0;t\n
    ${M("donate")}
    \n
    \n \n ${M("buymeacoffee")}\n \n \n ${M("buymeacoffeeDesc")}\n \n
    \n
    \n Buy Me A Coffee\n
    \n \n`),g.after(`\n \n `)},w=void 0,k=function(t,n){if(void 0!==n){var i="GM_setting_autosaved";n.find("."+i).animate({bottom:"+=40px"},{duration:300,queue:!1}),e("
    "+t+"
    ").appendTo(n).fadeIn("fast").animate({opacity:1},6e3,function(){e(this).fadeOut("fast").delay(600).remove()}).animate({left:"+=30px"},{duration:300,queue:!1})}},$=async function(){for(var t in p("read_"),d){var n=d[t],i=S(n);"tag"===l[t].type&&(1===(i=i.split(",")).length&&""===i[0]&&(i=[]),e.each(i,function(e,t){i[e]=t.replace(/^\s*|\s*$/g,"")})),_[t]!==i&&-1===o.indexOf(t)&&o.push(t),_[t]=i}},T=async function(){for(var e in p("write_"),d){var t=d[e];L(t,_[e])}},S=function(e){var t;switch(e.attr("GM_setting_type")){case"checkbox":t=e.prop("checked");break;case"set":case"text":case"tag":case"textarea":t=e.val();break;case"radio":t=e.find("input:checked").val();break;case"combobox":t=e.find("option:selected").val();break;default:t=void 0}return t},L=function(e,t){switch(e.attr("GM_setting_type")){case"checkbox":e.prop("checked",t).trigger("change");break;case"set":case"text":e.val(t);break;case"tag":case"textarea":e.val(t),e.height("auto"),e.height(e.prop("scrollHeight")+"px");break;case"radio":e.find("input[value="+t+"]").prop("checked",!0);break;case"combobox":e.find("option[value="+t+"]").prop("selected",!0)}},A=async function(t){var n=t.find("li");n.removeClass("GM_setting_item_disable"),n.find("input, textarea, select").prop("disabled",!1),n.find("input[type='checkbox']").trigger("change");for(var i,a,s=[!0,!0],o=1e3,r=0;r=d&&(a=void 0,o=1e3),g==d&&g>0)void 0!==a&&(s[g-1]=a==p);else if(gn?(o=!1,r+=M("err_num_over")+l[t].min_value):void 0!==l[t].max_value&&l[t].max_value"+o.message+"")),clearTimeout(w),w=setTimeout(function(){var t=!0;e.each(i,function(e,n){if(!z(e,S(n)).valid)return t=!1,!1}),t&&($(),m(),k(M("auto_saved")+(new Date).toLocaleTimeString(),n))},1e3)},N=function(e,t){var n=Object.keys(e).sort(),i=Object.keys(t).sort();return JSON.stringify(n)===JSON.stringify(i)};return{init:async function(t,i){s=t,await async function(e){for(var t in p("init_",l),e&&(e.DEBUG&&p("GM_setting - DEBUG",c=!0),e.CONSOLE_MSG&&(p=e.CONSOLE_MSG),e.SETTINGS&&(l=e.SETTINGS),e.MULTILANG&&(f=!0,e.LANG_DEFAULT&&(v=e.LANG_DEFAULT))),l)r[t]=l[t].value;if(r.Lang="",await x(),!N(r,_)){for(t in r)void 0===_[t]&&(_[t]=r[t]);for(t in _)void 0===r[t]&&delete _[t];await m()}}(i),await async function(){"function"==typeof GM.addValueChangeListener&&(p("설정에 대한 addValueChangeListener 바인드"),GM.addValueChangeListener(s,async function(t,n,i,a){a&&(p("다른 창에서 설정 변경됨. val_name, old_value, new_value:",t,n,i),await x(),e.each(n,function(e,t){void 0!==l[e]&&void 0!==l[e].change&&n[e]!==i[e]&&l[e].change(_[e])}),o=[])})),e(n).on("input","input[gm_setting_key='under_dev']",function(){p("실험실 기능 온오프 이벤트"),e(this).is(":checked")?e(".GM_setting_under_dev").css("opacity",0).slideDown("fast").animate({opacity:1},{queue:!1,duration:"fast"}):e(".GM_setting_under_dev").css("opacity",1).slideUp("fast").animate({opacity:0},{queue:!1,duration:"fast"})})}(),GM.addStyle('\n#GM_setting .btn {font-size:12px;}\n.GM_setting_autosaved.btn {\n max-width:100%;\n font-size:12px;\n white-space:pre-wrap;\n user-select:text;\n}\n#GM_setting .btn-xxs {\n cursor: pointer;\n padding: 4px 4px;\n}\n#GM_setting label.btn-xxs {\n box-sizing: content-box;\n width:11px;\n height:11px;\n}\n#GM_setting a{\n color: #428bca;\n text-decoration: none;\n}\n#GM_setting a:hover, #GM_setting a:focus {\n color: #2a6496;\n text-decoration: underline;\n}\n#GM_setting {clear:both;margin-left:auto; margin-right:auto; padding:0;font-size:13px;max-width:1400px; min-width:750px; box-sizing:content-box;}\n#GM_setting_head{margin-left:auto; margin-right:auto; padding:20px 0px 10px 10px;font-size:18px;font-weight:800;max-width:1400px; min-width:750px; box-sizing:content-box;}\n#GM_setting li {list-style:none;margin:0px;padding:8px;border-top:1px solid #eee;}\n\n#GM_setting .GM_setting_depth1.GM_setting_category {border-top: 2px solid #999;margin-top:30px;padding-top:10px;}\n#GM_setting li[GM_setting_key=\'version_check\'] {margin-top:0px !important}\n\n#GM_setting .GM_setting_category_name{display:table-cell;width:110px;padding:0 0 0 0px;font-weight:700;vertical-align:top;}\n#GM_setting .GM_setting_category_blank{display:table-cell;width:110px;padding:0 0 0 0px;vertical-align:top;}\n\n#GM_setting .GM_setting_list_head{display:table-cell;box-sizing:content-box;vertical-align:top;}\n#GM_setting .GM_setting_depth1 .GM_setting_list_head {padding-left:0px;width:300px;}\n#GM_setting .GM_setting_depth2 .GM_setting_list_head {padding-left:30px;width:270px;}\n#GM_setting .GM_setting_depth3 .GM_setting_list_head {padding-left:60px;width:240px;}\n#GM_setting .GM_setting_depth4 .GM_setting_list_head {padding-left:90px;width:210px;}\n#GM_setting .GM_setting_depth5 .GM_setting_list_head {padding-left:120px;width:180px;}\n\n#GM_setting .GM_setting_title{display:block;font-weight:700;}\n#GM_setting .GM_setting_desc{display:block;font-size:11px;}\n\n#GM_setting .GM_setting_input_container {display:table-cell;padding:0 0 0 30px;vertical-align:top;}\n#GM_setting .GM_setting_input_container span{vertical-align:top;}\n#GM_setting .GM_setting_input_container span.btn{margin:0 0 0 10px;}\n#GM_setting input{display:inline}\n#GM_setting input[type="text"]{ width: 100px; height: 30px; padding: 5px 5px; font-size:12px; }\n#GM_setting textarea{ width: 250px; height: 30px; padding: 5px 5px; font-size:12px; }\n#GM_setting input[type="checkbox"] { display:none; width: 20px;height:20px; padding: 0; margin:0; }\n#GM_setting input[type="radio"] {width: 20px;height:20px; padding: 0; margin:0; }\n\n#GM_setting .radio-inline{ padding-left:0; padding-right:10px; }\n#GM_setting .radio-inline input{ margin:0 5px 0 0; }\n\n#GM_setting .GM_setting_item_disable, #GM_setting .GM_setting_item_disable .GM_setting_title, #GM_setting .GM_setting_item_disable .GM_setting_desc{color:#ccc !important}\n#GM_setting .invalid input, #GM_setting .invalid textarea{border-color:#dc3545;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;color:#dc3545;}\n#GM_setting .invalid input:focus, #GM_setting .invalid textarea:focus{border-color:#dc3545;box-shadow:0 0 0 0.2rem rgba(220,53,69,.25);outline:0;color:#dc3545;}\n#GM_setting .invalid {color:#dc3545}\n#GM_setting .invalid_text {font-size:12px;padding:5px 0 0 5px;}\n\n#GM_setting .GM_setting_under_dev .GM_setting_title{color:#6441a5;font-style:italic}\n\n#GM_setting .btn-xxs {cursor:pointer;padding:4px 4px;} /*padding: 1px 2px;font-size: 9px;line-height: 1.0;border-radius: 3px;margin:0 2px 2px 0;*/\n#GM_setting .btn-xxs .glyphicon{}\n#GM_setting .btn-xxs span.glyphicon {font-size:11px; opacity: 0.1;}\n#GM_setting .btn-xxs.active span.glyphicon {opacity: 0.9;}\n#GM_setting .btn-xxs.disable {opacity: 0.3;cursor:not-allowed;}\n\n#GM_setting_footer { padding: 30px 0 30px 0; margin: 30px 0 0 0; border-top: 1px solid #ccc; text-align: center; font-size:13px; letter-spacing:0.2px; }\n#GM_setting_footer .footer_divider { margin: 0 5px; display: inline-block; width: 1px; height: 13px; background-color: #ebebeb; }\n')},load:async function(){p("GM_setting - load"),await x()},save:async function(){p("GM_setting - save"),await m()},save_overwrite:async function(){var n=_,i=t[s];ADD_DEBUG("_settings",l),e.each(n,function(e,t){void 0!==l[e]&&void 0!==l[e].change&&n[e]!==i[e]&&l[e].change(i[e])}),_=t[s],p("GM_setting - save_overwrite"),await m()},reset:async function(){await GM.setValue(s,r),await x()},createlayout:function(e){a=e,b(e)},getType:function(e){return void 0!==l[e]?l[e].type:void 0},message:function(e,t){k(e,t)}}}(jQuery,window,document); 97 | 98 | const _settings = { 99 | disable_visibilitychange: { 100 | category: "general", category_name: "General", depth: 1, type: "checkbox", value: true, 101 | title: {en:"Prevent automatic change of video quality when tab is disabled", ko:"탭이 비활성된 경우 화질 변경을 방지"}, 102 | desc: "" 103 | }, 104 | max_quality_start: { 105 | category: "general", depth: 1, type: "checkbox", value: true, 106 | title: {en:"Start live stream with user-preferred quality", ko:"라이브 스트림을 선호하는 화질로 시작"}, 107 | desc: {en:"This script provides several methods for selecting video quality. Choose your preferred method below.", ko:"이 스크립트는 비디오 품질을 선택하기 위한 여러 방법을 제공합니다. 알잘딱으로 적용됩니다. 적용하길 원하는 방법들을 아래에서 선택하세요."} 108 | }, 109 | only_source_quality: { 110 | category: "general", depth: 2, type: "checkbox", value: true, 111 | title: {en:"[1. Only the quality you want method]", ko:"[1. \"오로지 당신이 원하는 화질만\" 방식]"}, 112 | desc: { 113 | en:"Removes all other selectable video quality except \"the quality you want\". So even if the Twitch player sets the video quality to \"Auto\", the only selectable quality you want is set. This option is especially recommended for users who always want to watch videos in the best quality.
    Caution: Enabling this option may conflict with other scripts (eg TwitchAdSolution.), causing problems with video playback or the scripts not working properly. In this case, turn this option off. If you change this option, you must refresh the web page.", 114 | ko:"\"원하는 화질\"을 제외한 모든 비디오 품질을 제거합니다. 따라서 Twitch 플레이어가 화질을 \"자동\"으로 설정하더라도, 선택가능한 화질은 하나밖에 없으므로 자연스럽게 원하는 화질로 재생됩니다. 이 옵션은 특히 언제나 최고 품질로 비디오를 보고 싶은 유저에게 권장됩니다.
    주의: 이 옵션을 활성화하면 다른 스크립트(예: TwitchAdSolution)와 충돌하여 여러 문제가 발생할 수도 있습니다. 이 경우 이 옵션을 끄고 다른 방법을 사용하세요. 옵션 수정 후 새로 고침 필요." 115 | } 116 | }, 117 | only_source_quality_type: { 118 | category:"etc", 119 | depth: 3, 120 | type: "combobox", 121 | value: "0", 122 | title:"Options", 123 | desc: { 124 | en:"Type 0: Removes all other selectable video quality except the quality you want.
    Type 1: Overwrite all selectable video quality with the quality you want.
    Legacy: Removes all other selectable video quality except the quality you want & show \"(source)\" text if selected quality is source.", 125 | ko:`Type 0: 원하는 화질을 제외한 나머지를 모두 제거
    Type 1: 모든 화질을 원하는 화질로 덮어쓰기
    Legacy: 최고 화질을 제외한 나머지를 모두 제거 & 선택된 화질이 원본일 경우 "(원본)" 글자를 표시` 126 | }, 127 | options:{ 128 | "0":{title:"0"}, 129 | "1":{title:"1"}, 130 | "1000":{title:"Legacy"} 131 | } 132 | }, 133 | only_source_quality_prefer_1st : { 134 | category:"general", depth:3, type: "text", value: "best", 135 | title:{en:"1st preferred quality for only the quality you want method", ko:"가장 선호하는 비디오 품질 (오로지 당신이 원하는 화질만 방법)"}, 136 | desc:{ 137 | en:`(Choose one of the following: best, worst, 1080p, 720p, 720p60, 720p30, 480p, 360p, 160p, ...)`, 138 | ko:`(다음 중 하나를 선택하여 입력하세요: best, worst, 1080p, 720p, 720p60, 720p30, 480p, 360p, 160p, ...)` 139 | } 140 | }, 141 | only_source_quality_prefer_2nd : { 142 | category:"general", depth:3, type: "text", value: "720p", 143 | title:{en:"2nd preferred quality for only the quality you want method", ko:"두 번째로 선호하는 비디오 품질 (오로지 당신이 원본 화질만 방법)"}, 144 | desc:{ 145 | en:`(Choose one of the following: 1080p, 720p, 720p60, 720p30, 480p, 360p, 160p, ...)`, 146 | ko:`(다음 중 하나를 선택하여 입력하세요: 1080p, 720p, 720p60, 720p30, 480p, 360p, 160p, ...)` 147 | } 148 | }, 149 | only_source_quality_clip: { 150 | category: "general", depth: 3, type: "checkbox", value: true, 151 | title: {en:"Apply \"Only the quality you want method\" to the clip page", ko:"오로지 당신이 원하는 화질만 방법을 클립 페이지에도 적용"}, 152 | desc: { 153 | en:"Removes all selectable quality except \"the quality you want\" when playing clips on Twitch.tv. This feature is not yet supported for clips inserted in an iframe method on an external page other than Twitch.", 154 | ko:"Twitch.tv 에서 클립을 재생할 때 \"원하는 화질\"을 제외한 모든 비디오 품질을 제거합니다. Twitch 가 아닌 외부 페이지에 iframe 방식으로 삽입된 클립에 대해서는 아직 이 기능을 지원하지 않습니다." 155 | } 156 | }, 157 | max_quality_menu_trigger: { 158 | category: "general", depth: 2, type: "checkbox", value: true, 159 | title: {en:"[2. Simulate settings button method]", ko:"[2. 설정 버튼 매크로 방식]"}, 160 | desc: { 161 | en:"Try to fix to user-preferred video quality by virtually clicking the quality setting button & menu. This method is like a kind of Macro.
    The script looks for text that corresponds to the user's preferred quality in the quality settings menu of Twitch Player. The script will first try to set the \"most preferred video quality\". If the script cannot find the \"most preferred video quality\", the script will look for a \"second preferred video quality\". If the second preferred video quality does not exist, video quality will be selected as the source quality.
    If the player's setting button does not work properly, turn this feature off. If you change this option, you must refresh the web page.", 162 | ko:"화질 설정 버튼과 메뉴를 가상으로 클릭하여 사용자가 원하는 영상 화질로 설정합니다. 이 방법은 일종의 매크로 입니다.
    스크립트는 Twitch Player 의 화질 설정 메뉴에서 사용자가 선호하는 화질에 해당하는 텍스트를 찾아 설정을 시도합니다. 텍스트를 찾는 것에 실패한 경우 비디오 품질이 \"원본\" 품질로 선택됩니다.
    플레이어의 설정 버튼이 제대로 작동하지 않으면 이 기능을 끄십시오. 옵션 수정 후 새로 고침 필요." 163 | } 164 | }, 165 | max_quality_menu_trigger_prefer_1st : { 166 | category:"general", depth:3, type: "text", value: "best", 167 | title:{en:"1st preferred quality for simulate settings button method", ko:"가장 선호하는 비디오 품질 (설정 버튼 매크로 방법)"}, 168 | desc:{ 169 | en:`(Choose one of the following: best, worst, 1080p, 720p, 720p60, 720p30, 480p, 360p, 160p, ...)`, 170 | ko:`(다음 중 하나를 선택하여 입력하세요: best, worst, 1080p, 720p, 720p60, 720p30, 480p, 360p, 160p, ...)` 171 | } 172 | }, 173 | max_quality_menu_trigger_prefer_2nd : { 174 | category:"general", depth:3, type: "text", value: "720p", 175 | title:{en:"2nd preferred quality for simulate settings button method", ko:"두 번째로 선호하는 비디오 품질 (설정 버튼 매크로 방법)"}, 176 | desc:{ 177 | en:`(Choose one of the following: 1080p, 720p, 720p60, 720p30, 480p, 360p, 160p, ...)`, 178 | ko:`(다음 중 하나를 선택하여 입력하세요: 1080p, 720p, 720p60, 720p30, 480p, 360p, 160p, ...)` 179 | } 180 | }, 181 | max_quality_menu_trigger_each_delay : { 182 | category:"general", depth:3, type: "text", value: 50, valid:"number", min_value:0, max_value:100000, 183 | title:{en:"Delay on each click.", ko:"각 클릭 시의 딜레이 시간"}, 184 | desc:{en:"(Unit:ms, Default: 50, Range: 0~100000)", ko:"(단위:ms, 기본값: 50, 범위: 0~100000)"} 185 | }, 186 | max_quality_menu_trigger_clip: { 187 | category: "general", depth: 3, type: "checkbox", value: true, 188 | title: { 189 | en:"Apply \"Simulate settings button method\" to the clip page on twitch.tv", 190 | ko:"설정 버튼 매크로 방법을 Twitch.tv 의 클립 페이지에도 적용" 191 | }, 192 | desc: "", 193 | }, 194 | max_quality_localstorage: { 195 | category: "general", depth: 2, type: "checkbox", value: true, 196 | title: {en:"[3. Localstorage modify method]", ko:"[3. Localstorage 수정 방식]"}, 197 | desc: { 198 | en:`Modify local storage related to video quality.
    This feature directly modifies the quality settings stored in Twitch Player, and the settings will be overwritten when Twitch Player loads. It is recommended to turn on these two options together if you want to set the source quality.
    This function does not work properly if the script does not have permission to access Localstorage, such as Incognito mode in Google Chrome."`, 199 | ko:`동영상 품질과 관련된 Localstorage 값을 수정합니다.
    이 기능은 Twitch Player에 저장된 화질 설정을 직접 수정하며, Twitch Player가 로드될 때 설정을 덮어씁니다. 일반적으로 원본 화질로 설정하길 원하는 경우 이 두 옵션을 함께 켜는 것을 권장합니다.
    Google Chrome 의 시크릿 창에서는 Localstorage 에 대한 액세스 권한이 없어 이 기능이 제대로 작동하지 않습니다.` 200 | } 201 | }, 202 | max_quality_localstorage_prefer_1st : { 203 | category:"general", depth:3, type: "text", value: "best", 204 | title: {en:"Video quality for localstorage modify method.", ko:"Localstorage 수정 시 설정할 비디오 품질"}, 205 | desc: { 206 | en:`(Choose one of the following: best(=chunked), 720p60, 720p30, 480p30, 360p30, 160p30)
    If you enter an incorrect value or your preferred video quality is not supported, Twitch Player may choose "Auto".
    Anything other than best(chunked) may not work. In this case, use the Simulate settings button method.`, 207 | ko:`(다음 중 하나를 선택하여 입력하세요: best(=chunked), 720p60, 720p30, 480p30, 360p30, 160p30)
    잘못된 값을 입력하거나 원하는 비디오 품질이 지원되지 않는 경우 Twitch Player는 아마도 "자동"을 선택할 것입니다.
    best(chunked) 이외의 것을 입력했을 때 적절히 작동하지 않을 수도 있으며, 이 경우 설정 버튼 매크로 방법을 대신 사용하세요.` 208 | } 209 | }, 210 | set_volume_when_stream_starts: { 211 | category: "general", depth: 2, type: "checkbox", value: false, 212 | title: {en:"Set the volume when stream starts", ko:"스트림 시작 시 특정 사운드 볼륨(Volume)으로 시작"}, 213 | desc: "" 214 | }, 215 | target_start_volume : { 216 | category:"general", depth:3, type: "text", value: 1.0, valid:"number", min_value:0.0, max_value:1.0, 217 | title:{en:"Volume", ko:"Volume"}, 218 | desc:{en:"(Max Volume: 1.0, Mute: 0.0, Range: 0.0 ~ 1.0)", ko:"(Max Volume: 1.0, 음소거: 0.0, 범위: 0.0 ~ 1.0)"} }, 219 | disable_javascript_timer: { 220 | category: "TAMQLabs", category_name: "TAMQ Labs", depth: 1, type: "checkbox", value: false, 221 | title: {en:"Disable power saving for inactive tabs (Disable JavaScript Timer Throttling)", ko:"비활성 탭의 절전 기능 비활성화 (Disable JavaScript Timer Throttling)"}, 222 | desc: {en:"If you often have problems playing videos in inactive tabs, try using this feature.
    By enabling this option, you can disable some of the power saving features (Javascript Timer Throttling) for inactive tabs supported by Chrome-based browsers. This feature may conflict with certain ad filters in the ad blocking extension. You must refresh the page after changing this option.", 223 | ko:"만약 Twitch.tv 탭을 비활성 상태로 사용할 때 동영상 재생 중 문제가 발생하는 경우가 많다고 느꼈다면 이 옵션을 켜보세요.
    이 옵션은 크롬 계열 브라우저에서 지원하는 비활성 탭에 대한 power saving 기능(Javascript Timer Throttling)의 일부를 비활성화 하여, 비활성 탭에 있는 동영상을 재생 중일 때 발생할 수 있는 문제를 개선할 수 있습니다.
    이 기능은 광고 차단 확장기능의 특정 광고 필터와 충돌할 수 있습니다. 이 실험실 기능은 알 수 없는 문제를 발생시킬 수도 있습니다. 옵션 변경 후 새로고침 필요."} 224 | }, 225 | disble_WebRTC: { 226 | under_dev: true, 227 | category: "TAMQLabs", depth: 1, type: "checkbox", value: false, 228 | title: {en:"Disable WebRTC", ko:"WebRTC 비활성화"}, 229 | desc: {en:"", 230 | ko:""} 231 | }, 232 | disable_P2P: { 233 | under_dev: true, 234 | category: "TAMQLabs", depth: 1, type: "checkbox", value: false, 235 | title: {en:"Disable P2P", ko:"P2P 비활성화"}, 236 | desc: {en:"", 237 | ko:""} 238 | } 239 | }; 240 | window.GM_setting = GM_setting; 241 | await GM_setting.init("GM_SETTINGS", {"DEBUG":DEBUG, "SETTINGS":_settings, "CONSOLE_MSG":NOMO_DEBUG, "MULTILANG":true}); 242 | 243 | //////////////////////////////////////////////////////////////////////////////////// 244 | // Initialize 245 | //////////////////////////////////////////////////////////////////////////////////// 246 | var first_url = document.location.href.toLowerCase(); 247 | //var isPlayerTwitchTv = (first_url.indexOf("//player.twitch.tv") !== -1 ? true : false); 248 | var isClipsTwitchTv = (first_url.indexOf("//clips.twitch.tv") !== -1 ? true : false); 249 | var is_clip_embed = (first_url.indexOf("//clips.twitch.tv/embed?") !== -1 ? true : false); 250 | var date_n = Number(new Date()); 251 | 252 | GM.addStyle(/*css*/` 253 | #nomo_settings { color: #000; overflow-y: scroll; } 254 | #nomo_settings::-webkit-scrollbar { width: 8px; height: 8px; background: #ccc; } 255 | #nomo_settings::-webkit-scrollbar-thumb { background: #772ce8; } 256 | #GM_setting_head { color: #000; } 257 | #GM_setting .btn{ background-color: #6441a4; border-color: #6441a4; color: #fff;} 258 | #GM_setting .btn:hover{ background-color: #7d5bbe; border-color: #7d5bbe; color: #fff; } 259 | #GM_setting .bth:active{ background-color: #6441a4; border-color: #7d5bbe; box-shadow: 0 0 6px 0 #7d5bbe; color: #fff; } 260 | #GM_setting .btn:focus{ background-color: #7d5bbe; border-color: #9a7fcc; box-shadow: 0 0 6px 0 #7d5bbe; color: #fff; } 261 | #GM_setting .GM_setting_desc{ font-size: 13px !important; word-break: keep-all;} 262 | #GM_setting .GM_setting_category_name, #GM_setting .GM_setting_title{ font-size: 14px !important; } 263 | 264 | body.pl-menu-hide div.pl-menu, 265 | body.pl-menu-hide .pl-settings-icon, 266 | body.pl-menu-hide button[data-a-target='player-settings-button'], 267 | body.pl-menu-hide div[data-a-target='player-settings-menu'], 268 | body.pl-menu-hide div.settings-menu-button-component, 269 | body.pl-menu-hide div[aria-labelledby="active-settings-menu-header"] 270 | { display:none; opacity:0; } 271 | 272 | #GM_setting .GM_setting_depth1 .GM_setting_list_head{ width: 500px !important; } 273 | #GM_setting .GM_setting_depth2 .GM_setting_list_head{ width: 470px !important; } 274 | #GM_setting .GM_setting_depth3 .GM_setting_list_head{ width: 440px !important; } 275 | 276 | .smoothScroll{ 277 | scroll-behavior:smooth; 278 | } 279 | `); 280 | 281 | //////////////////////////////////////////////////////////////////////////////////// 282 | // disable_javascript_timer 283 | if(!is_clip_embed && GM_SETTINGS.disable_javascript_timer){ 284 | disableJavascriptTimer(); 285 | } 286 | 287 | // disble_WebRTC 288 | if(GM_SETTINGS.disble_WebRTC){ 289 | unsafeWindow.navigator.getUserMedia = undefined; 290 | unsafeWindow.MediaStreamTrack = undefined; 291 | unsafeWindow.RTCPeerConnection = undefined; 292 | unsafeWindow.RTCSessionDescription = undefined; 293 | unsafeWindow.navigator.mozGetUserMedia = undefined; 294 | unsafeWindow.mozMediaStreamTrack = undefined; 295 | unsafeWindow.mozRTCPeerConnection = undefined; 296 | unsafeWindow.mozRTCSessionDescription = undefined; 297 | unsafeWindow.navigator.webkitGetUserMedia = undefined; 298 | unsafeWindow.webkitMediaStreamTrack = undefined; 299 | unsafeWindow.webkitRTCPeerConnection = undefined; 300 | unsafeWindow.webkitRTCSessionDescription = undefined; 301 | } 302 | 303 | var debugHackTimer = false; 304 | var startDate = new Date(); 305 | var countSettimeout = 0, countSetInterval = 0; 306 | if(debugHackTimer){ 307 | var setTimeoutDebug = function(){ 308 | setTimeout(function(){ 309 | countSettimeout += 1; 310 | setTimeoutDebug(); 311 | if(countSettimeout % 10 == 0){ 312 | var IDEALCOUNT = (Number(new Date()) - Number(startDate)) / 1000; 313 | console.log(`setTimeoutDebug::::: IDEAL COUNT = ${IDEALCOUNT.toFixed(2)}\tCURRENT COUNT = ${countSettimeout}\tERROR = ${(IDEALCOUNT - countSettimeout).toFixed(2)}\tdocument.hidden = ${document.hidden}\tdocument.visibilityState = ${document.visibilityState}`); 314 | } 315 | },1000); 316 | }; 317 | setTimeoutDebug(); 318 | (function(){ 319 | setInterval(function(){ 320 | countSetInterval += 1; 321 | if(countSetInterval % 10 == 0){ 322 | var IDEALCOUNT = (Number(new Date()) - Number(startDate)) / 1000; 323 | console.log(`setIntervalDebug:::: IDEAL COUNT = ${IDEALCOUNT.toFixed(2)}\tCURRENT COUNT = ${countSetInterval}\tERROR = ${(IDEALCOUNT - countSetInterval).toFixed(2)}\tdocument.hidden = ${document.hidden}\tdocument.visibilityState = ${document.visibilityState}`); 324 | } 325 | },1000); 326 | })(); 327 | } 328 | 329 | //////////////////////////////////////////////////////////////////////////////////// 330 | // set_volume_when_stream_starts 331 | var is_volume_changed = false; 332 | if(GM_SETTINGS.set_volume_when_stream_starts){ 333 | localStorage.setItem('volume', GM_SETTINGS.target_start_volume); 334 | 335 | if(GM_SETTINGS.target_start_volume !== 0){ 336 | localStorage.setItem('video-muted', {default:false}); 337 | } 338 | } 339 | 340 | 341 | //////////////////////////////////////////////////////////////////////////////////// 342 | // only_source_quality method 343 | if((GM_SETTINGS.max_quality_start && GM_SETTINGS.only_source_quality) || GM_SETTINGS.disable_P2P){ 344 | 345 | // only_source_quality method for live stream and vod 346 | var realWorker = unsafeWindow.Worker; 347 | unsafeWindow.Worker = function (input) { 348 | var newInput = String(input); 349 | 350 | // 19-09-18 wasmworker version: 2.14.0 351 | var myBlob = "importScripts('https://static.twitchcdn.net/assets/amazon-ivs-wasmworker.min-7da53ec1e6fb32a92d1d.js');"; 352 | 353 | var req = new XMLHttpRequest(); 354 | req.open('GET', newInput, false); 355 | req.send(); 356 | var resText = req.responseText; 357 | if(req.status == 200 || req.status == 201){ 358 | myBlob = resText; 359 | } 360 | 361 | // rewrite blob 362 | var workerBlob = new Blob( 363 | [ /*javascript*/ ` 364 | // ${myBlob}; 365 | var DEBUG_WORKER2 = ${DEBUG}; 366 | var NOMO_DEBUG = function ( /**/ ) { 367 | if (!DEBUG_WORKER2) return; 368 | var args = arguments, args_length = args.length, args_copy = args; 369 | for (let i = args_length; i > 0; i--) args[i] = args_copy[i - 1]; 370 | args[0] = "[TAMQ][WORKER] "; 371 | args.length = args_length + 1; 372 | console.log.apply(console, args); 373 | }; 374 | 375 | // Worker fetch 376 | var originalFetch2 = self.fetch; 377 | self.fetch = async function(input, init){ 378 | if(input.toLowerCase().indexOf(".m3u8") !== -1 379 | && (input.toLowerCase().indexOf('usher.ttvnw.net/api/channel/hls') !== -1 || input.toLowerCase().indexOf('usher.ttvnw.net/vod/') !== -1)){ 380 | var m3u8_fetch = await originalFetch2.apply(this, arguments); 381 | var m3u8_text = await m3u8_fetch.text(); 382 | NOMO_DEBUG("\\n", input, "\\n", (new Date()), "\\n", m3u8_text); 383 | 384 | /////////////////////////////////////////////////// 385 | // disable_P2P 386 | if(DEBUG_WORKER2){ 387 | if(m3u8_text.indexOf(",P2P=1") !== -1){ 388 | NOMO_DEBUG("P2P ON"); 389 | } 390 | else{ 391 | NOMO_DEBUG("P2P OFF"); 392 | } 393 | } 394 | if(${GM_SETTINGS.disable_P2P}){ 395 | NOMO_DEBUG("disable_P2P"); 396 | m3u8_text = m3u8_text.replace(",P2P=1",""); 397 | } 398 | 399 | // only disable_P2P 400 | var osq_enabled = Boolean(${String((GM_SETTINGS.max_quality_start && GM_SETTINGS.only_source_quality))}); 401 | NOMO_DEBUG("osq_enabled", osq_enabled); 402 | if(!osq_enabled){ 403 | NOMO_DEBUG("CONVERTED m3u8_text", m3u8_text); 404 | var m3u8_blob = new Blob([m3u8_text], {type: 'text/plain'}); 405 | var m3u8_blob_url = URL.createObjectURL(m3u8_blob); 406 | var new_arg = arguments; 407 | new_arg[0] = m3u8_blob_url; 408 | 409 | // REVOKE after 10s 410 | setTimeout(function(){URL.revokeObjectURL(m3u8_blob_url);},10000); 411 | return originalFetch2.apply(this, new_arg); 412 | } 413 | 414 | /////////////////////////////////////////////////// 415 | // only_source_quality 416 | // find target quality index 417 | var type = ${GM_SETTINGS.only_source_quality_type}; 418 | var remove_chunked = true; 419 | if(type == 1000){ 420 | type = 0; 421 | remove_chunked = false; 422 | } 423 | var found = false; 424 | 425 | var regex = /(\\n#EXT-X-MEDIA:.+\\n.+\\n.+\\.m3u8)/gi; 426 | var mat = m3u8_text.match(regex); 427 | NOMO_DEBUG("mat", mat); 428 | found = mat !== null; 429 | if(mat == null) mat = []; // error case 430 | 431 | var q1st = ${"\""+GM_SETTINGS.only_source_quality_prefer_1st.toLowerCase()+"\""}; 432 | var q2nd = ${"\""+GM_SETTINGS.only_source_quality_prefer_2nd.toLowerCase()+"\""}; 433 | NOMO_DEBUG("TARGET Q 1st: " + q1st + ", 2nd:" + q2nd); 434 | 435 | var qualityText = []; 436 | var targetIndex = -1; 437 | var targetIndex1st = -1; 438 | var targetIndex2nd = -1; 439 | 440 | for(let i=0;i