@oxymoron.13/tizentube-s95b 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +42 -49
  2. package/S95B-CHANGES.txt +3 -9
  3. package/dist/userScript.js +2 -2
  4. package/package.json +5 -11
  5. package/UPSTREAM-README.md +0 -42
  6. package/mods/config.js +0 -117
  7. package/mods/domrect-polyfill.js +0 -104
  8. package/mods/features/adblock.js +0 -584
  9. package/mods/features/autoFrameRate.js +0 -42
  10. package/mods/features/enableFeatures.js +0 -19
  11. package/mods/features/moreSubtitles.js +0 -378
  12. package/mods/features/pictureInPicture.js +0 -198
  13. package/mods/features/preferredVideoQuality.js +0 -101
  14. package/mods/features/s95bCodec.js +0 -45
  15. package/mods/features/s95bI18n.js +0 -36
  16. package/mods/features/s95bMenu.js +0 -246
  17. package/mods/features/sponsorblock.js +0 -437
  18. package/mods/features/standaloneUserscript.js +0 -107
  19. package/mods/features/updater.js +0 -119
  20. package/mods/features/userAgentSpoofing.js +0 -73
  21. package/mods/features/videoQueuing.js +0 -64
  22. package/mods/package-lock.json +0 -2321
  23. package/mods/package.json +0 -33
  24. package/mods/resolveCommand.js +0 -376
  25. package/mods/rollup.config.js +0 -41
  26. package/mods/spatial-navigation-polyfill.js +0 -1756
  27. package/mods/tiny-sha256.js +0 -99
  28. package/mods/translations/i18nResources.js +0 -11
  29. package/mods/translations/index.js +0 -18
  30. package/mods/translations/language-names.js +0 -981
  31. package/mods/translations/resources/ar.json +0 -308
  32. package/mods/translations/resources/az.json +0 -308
  33. package/mods/translations/resources/bs.json +0 -308
  34. package/mods/translations/resources/cs.json +0 -308
  35. package/mods/translations/resources/de.json +0 -308
  36. package/mods/translations/resources/dk.json +0 -308
  37. package/mods/translations/resources/el.json +0 -308
  38. package/mods/translations/resources/en.json +0 -308
  39. package/mods/translations/resources/es-419.json +0 -308
  40. package/mods/translations/resources/es.json +0 -308
  41. package/mods/translations/resources/fi-FI.json +0 -308
  42. package/mods/translations/resources/fr.json +0 -311
  43. package/mods/translations/resources/hr.json +0 -308
  44. package/mods/translations/resources/hu.json +0 -308
  45. package/mods/translations/resources/id.json +0 -308
  46. package/mods/translations/resources/it.json +0 -308
  47. package/mods/translations/resources/ja.json +0 -308
  48. package/mods/translations/resources/lt.json +0 -308
  49. package/mods/translations/resources/lv.json +0 -308
  50. package/mods/translations/resources/pl.json +0 -308
  51. package/mods/translations/resources/pt-BR.json +0 -308
  52. package/mods/translations/resources/pt-PT.json +0 -308
  53. package/mods/translations/resources/ro.json +0 -310
  54. package/mods/translations/resources/ru.json +0 -308
  55. package/mods/translations/resources/sr-Cyrl.json +0 -308
  56. package/mods/translations/resources/sr-Latn.json +0 -308
  57. package/mods/translations/resources/tlh.json +0 -308
  58. package/mods/translations/resources/tr.json +0 -308
  59. package/mods/translations/resources/uk.json +0 -308
  60. package/mods/translations/resources/vi.json +0 -309
  61. package/mods/translations/resources/zh-CN.json +0 -308
  62. package/mods/translations/resources/zh-TW.json +0 -308
  63. package/mods/translations/update-translations.js +0 -55
  64. package/mods/ui/chapters.js +0 -137
  65. package/mods/ui/clock.js +0 -71
  66. package/mods/ui/customCommandExecution.js +0 -49
  67. package/mods/ui/customGuideAction.js +0 -119
  68. package/mods/ui/customUI.js +0 -192
  69. package/mods/ui/customYTSettings.js +0 -29
  70. package/mods/ui/disableWhosWatching.js +0 -48
  71. package/mods/ui/settings.js +0 -1024
  72. package/mods/ui/sidebarModification.js +0 -179
  73. package/mods/ui/speedUI.js +0 -119
  74. package/mods/ui/theme.js +0 -26
  75. package/mods/ui/ui.css +0 -157
  76. package/mods/ui/ui.js +0 -261
  77. package/mods/ui/ytUI.js +0 -510
  78. package/mods/userScript.js +0 -28
  79. package/mods/utils/ASTParser.js +0 -67
  80. package/mods/utils/innerTubeCalls.js +0 -138
  81. package/service/package-lock.json +0 -4193
  82. package/service/package.json +0 -29
  83. package/service/rollup.config.js +0 -47
  84. package/service/service.js +0 -118
@@ -1,45 +0,0 @@
1
- /* S95B Performance Edition – GPL-3.0-only.
2
- * Pure, conservative format selector. Does not touch Samsung/Tizen APIs.
3
- */
4
- export function selectCodecFormats(response, codec) {
5
- if (!response || !response.streamingData || !Array.isArray(response.streamingData.adaptiveFormats)) {
6
- return { changed: false, reason: 'no-formats' };
7
- }
8
- if (codec !== 'vp9' && codec !== 'av01' && codec !== 'avc1') {
9
- return { changed: false, reason: 'automatic' };
10
- }
11
-
12
- const formats = response.streamingData.adaptiveFormats;
13
- const video = formats.filter(item => item && typeof item.mimeType === 'string' && /^video\//i.test(item.mimeType));
14
- const isMatch = item => {
15
- const mime = item.mimeType.toLowerCase();
16
- if (codec === 'vp9') return /(?:vp9|vp09)/.test(mime);
17
- if (codec === 'av01') return /av01/.test(mime);
18
- return /avc1/.test(mime);
19
- };
20
- const selected = video.filter(isMatch);
21
- if (!selected.length) return { changed: false, reason: 'codec-unavailable' };
22
- if (selected.length === video.length) return { changed: false, reason: 'already-selected' };
23
-
24
- // Conservative quality parity: preserve original formats if filtering could
25
- // remove the best available height/fps combination. Metadata absent => no filter.
26
- const valid = item => Number.isFinite(item.height) && item.height > 0 &&
27
- Number.isFinite(item.fps) && item.fps > 0;
28
- if (!video.every(valid) || !selected.every(valid)) {
29
- return { changed: false, reason: 'quality-unknown' };
30
- }
31
- const maxHeight = Math.max(...video.map(item => item.height));
32
- const maxFpsAtHeight = Math.max(...video.filter(item => item.height === maxHeight).map(item => item.fps));
33
- const bestMatch = selected.some(item => item.height >= maxHeight && item.fps >= maxFpsAtHeight);
34
- if (!bestMatch) return { changed: false, reason: 'quality-fallback' };
35
-
36
- // Keep audio and unrecognized entries untouched. No media decoding or resampling.
37
- const next = formats.filter(item => !item || typeof item.mimeType !== 'string' ||
38
- !/^video\//i.test(item.mimeType) || isMatch(item));
39
- if (!next.some(item => item && /^audio\//i.test(item.mimeType || ''))) {
40
- // Do not make a pre-existing audio problem worse by filtering streams.
41
- return { changed: false, reason: 'missing-audio' };
42
- }
43
- response.streamingData.adaptiveFormats = next;
44
- return { changed: true, reason: 'selected' };
45
- }
@@ -1,36 +0,0 @@
1
- /* S95B Performance Edition – GPL-3.0-only */
2
- export const STRINGS = Object.freeze({
3
- de: Object.freeze({
4
- title: 'S95B · Performance', subtitle: 'TizenTube Performance Edition 0.2.1',
5
- language: 'Sprache', codec: 'Videocodec', quality: 'Bevorzugte Qualität', automatic: 'Automatisch',
6
- light: 'Ressourcenschonender Modus', diag: 'Diagnoseanzeige',
7
- reset: 'Änderungen zurücksetzen', close: 'Schließen', on: 'Ein', off: 'Aus',
8
- hint: 'Gelbe Taste: Einstellungen · Pfeile: Navigieren · OK: Ändern · Zurück: Schließen',
9
- restart: 'Codec-Änderungen gelten ab dem nächsten Video. Eine feste Qualität gilt sofort; „Automatisch“ wirkt eventuell erst nach einem Neustart von YouTube.',
10
- safe: 'Kein Eingriff in Firmware, Bildrate oder Decoder.',
11
- statVideo: 'Video', statFrames: 'Verlorene Bilder', statCodec: 'Codec-Wunsch',
12
- statUnavailable: 'Vom Web-Player nicht verfügbar', statWait: 'Warte auf Video',
13
- codecHint: 'H.264 liefert bei YouTube in der Regel höchstens 1080p; 4K gibt es nur als VP9 oder AV1. Ein Codec wird nur erzwungen, wenn dadurch keine Qualität verloren geht.',
14
- lightHint: 'Schaltet Vorschauen und DeArrow ab; vorherige Werte werden gesichert.',
15
- restoreHint: 'Stellt nur die von diesem Menü geänderten Werte wieder her.',
16
- languageDE: 'Deutsch', languageEN: 'English',
17
- }),
18
- en: Object.freeze({
19
- title: 'S95B · Performance', subtitle: 'TizenTube Performance Edition 0.2.1',
20
- language: 'Language', codec: 'Video codec', quality: 'Preferred quality', automatic: 'Automatic',
21
- light: 'Low-overhead mode', diag: 'Diagnostic overlay',
22
- reset: 'Restore changed settings', close: 'Close', on: 'On', off: 'Off',
23
- hint: 'Yellow button: settings · Arrows: navigate · OK: change · Back: close',
24
- restart: 'Codec changes apply from the next video. A fixed quality applies immediately; “Automatic” may only take effect after restarting YouTube.',
25
- safe: 'No firmware, display-rate or decoder changes.',
26
- statVideo: 'Video', statFrames: 'Dropped frames', statCodec: 'Codec preference',
27
- statUnavailable: 'Not available from web player', statWait: 'Waiting for video',
28
- codecHint: 'On YouTube, H.264 usually tops out at 1080p; 4K is only available as VP9 or AV1. A codec is only forced if no quality is lost.',
29
- lightHint: 'Disables previews and DeArrow; original values are backed up.',
30
- restoreHint: 'Restores only values changed from this menu.',
31
- languageDE: 'Deutsch', languageEN: 'English',
32
- })
33
- });
34
- export function translate(lang, key) {
35
- return (STRINGS[lang] || STRINGS.de)[key] || STRINGS.en[key] || key;
36
- }
@@ -1,246 +0,0 @@
1
- /* S95B Performance Edition – GPL-3.0-only
2
- * No global player hooks, firmware calls, network requests, or external scripts.
3
- * UI runs only when the TizenTube userscript loads, keyboard events only when
4
- * explicitly invoked. Uses existing TizenTube configRead/configWrite APIs.
5
- */
6
- import { configRead, configWrite } from '../config.js';
7
- import { translate } from './s95bI18n.js';
8
-
9
- const STATE_KEY = 's95b-perf-edition-v1';
10
- const EDITED = ['enablePreviews', 'enableDeArrow', 'enableDeArrowThumbnails'];
11
- const defaultState = { lang: 'de', light: false, diagnostics: false, previous: null, codecPrevious: null, qualityPrevious: null };
12
- let state = { ...defaultState };
13
- let overlay = null;
14
- let diagnostic = null;
15
- let diagTimer = null;
16
- let menuButtons = [];
17
- let selected = 0;
18
-
19
- function readState() {
20
- try {
21
- const data = JSON.parse(localStorage.getItem(STATE_KEY) || 'null');
22
- if (data && typeof data === 'object') {
23
- state = { ...defaultState,
24
- lang: data.lang === 'en' ? 'en' : 'de', light: data.light === true,
25
- diagnostics: data.diagnostics === true,
26
- previous: data.previous && typeof data.previous === 'object' ? data.previous : null,
27
- codecPrevious: typeof data.codecPrevious === 'string' ? data.codecPrevious : null,
28
- qualityPrevious: typeof data.qualityPrevious === 'string' ? data.qualityPrevious : null };
29
- }
30
- } catch (_) { state = { ...defaultState }; }
31
- }
32
- function save() {
33
- try { localStorage.setItem(STATE_KEY, JSON.stringify(state)); } catch (_) { /* no persistent storage */ }
34
- }
35
- function currentCodec() {
36
- const value = configRead('preferredVideoCodec');
37
- return value === 'vp9' || value === 'av01' || value === 'avc1' ? value : 'any';
38
- }
39
- function codecLabel(value) {
40
- return value === 'any' ? translate(state.lang, 'automatic') : value === 'vp9' ? 'VP9' : value === 'av01' ? 'AV1' : 'H.264';
41
- }
42
- const CODECS = ['any', 'vp9', 'av01', 'avc1'];
43
- function changeCodec() {
44
- if (state.codecPrevious === null) state.codecPrevious = String(configRead('preferredVideoCodec') || 'any');
45
- configWrite('preferredVideoCodec', CODECS[(CODECS.indexOf(currentCodec()) + 1) % CODECS.length]);
46
- save();
47
- }
48
- // Uses TizenTube's own preferredVideoQuality setting and handler (same values as its settings menu).
49
- const QUALITIES = ['auto', '2160p', '1440p', '1080p', '720p'];
50
- function currentQuality() {
51
- const value = String(configRead('preferredVideoQuality') || 'auto');
52
- return QUALITIES.indexOf(value) >= 0 ? value : 'auto';
53
- }
54
- function qualityLabel(value) {
55
- return value === 'auto' ? translate(state.lang, 'automatic') : value === '2160p' ? '2160p (4K)' : value;
56
- }
57
- function changeQuality() {
58
- if (state.qualityPrevious === null) state.qualityPrevious = String(configRead('preferredVideoQuality') || 'auto');
59
- configWrite('preferredVideoQuality', QUALITIES[(QUALITIES.indexOf(currentQuality()) + 1) % QUALITIES.length]);
60
- save();
61
- }
62
- function turnLight(enable) {
63
- if (enable === state.light) return;
64
- if (enable) {
65
- const values = {};
66
- EDITED.forEach(key => { values[key] = configRead(key); });
67
- state.previous = values;
68
- EDITED.forEach(key => configWrite(key, false));
69
- state.light = true;
70
- } else {
71
- if (state.previous) EDITED.forEach(key => {
72
- if (Object.prototype.hasOwnProperty.call(state.previous, key)) {
73
- configWrite(key, state.previous[key]);
74
- }
75
- });
76
- state.previous = null;
77
- state.light = false;
78
- }
79
- save();
80
- }
81
- function reset() {
82
- turnLight(false);
83
- if (state.codecPrevious !== null) {
84
- configWrite('preferredVideoCodec', state.codecPrevious);
85
- state.codecPrevious = null;
86
- }
87
- if (state.qualityPrevious !== null) {
88
- configWrite('preferredVideoQuality', state.qualityPrevious);
89
- state.qualityPrevious = null;
90
- }
91
- state.diagnostics = false;
92
- stopDiagnostics();
93
- save();
94
- }
95
- function create(tag, attrs, value) {
96
- const n = document.createElement(tag);
97
- if (attrs) Object.keys(attrs).forEach(key => {
98
- if (key === 'className') n.className = attrs[key];
99
- else n.setAttribute(key, attrs[key]);
100
- });
101
- if (typeof value === 'string') n.textContent = value;
102
- return n;
103
- }
104
- function drawMenu() {
105
- if (!overlay) return;
106
- overlay.textContent = '';
107
- const t = key => translate(state.lang, key);
108
- const card = create('section', { className: 's95b-card', role: 'dialog', 'aria-modal': 'true', 'aria-label': t('title') });
109
- const eyebrow = create('div', { className: 's95b-eyebrow' }, t('subtitle'));
110
- const title = create('h2', null, t('title'));
111
- card.append(eyebrow, title);
112
- const fields = [
113
- [t('language'), state.lang === 'de' ? t('languageDE') : t('languageEN'), () => { state.lang = state.lang === 'de' ? 'en' : 'de'; save(); }],
114
- [t('codec'), codecLabel(currentCodec()), changeCodec],
115
- [t('quality'), qualityLabel(currentQuality()), changeQuality],
116
- [t('light'), state.light ? t('on') : t('off'), () => turnLight(!state.light)],
117
- [t('diag'), state.diagnostics ? t('on') : t('off'), () => { state.diagnostics = !state.diagnostics; save(); if (state.diagnostics) startDiagnostics(); else stopDiagnostics(); }],
118
- [t('reset'), '', reset],
119
- [t('close'), '', hideMenu]
120
- ];
121
- menuButtons = fields.map((field, index) => {
122
- const button = create('button', { type: 'button', className: 's95b-option', 'data-index': String(index) });
123
- button.append(create('span', null, field[0]), create('strong', null, field[1]));
124
- button.addEventListener('click', () => { selected = index; field[2](); if (overlay) drawMenu(); });
125
- card.appendChild(button);
126
- return button;
127
- });
128
- card.append(create('p', { className: 's95b-explanation' }, t('codecHint')));
129
- card.append(create('p', { className: 's95b-explanation' }, t('lightHint')));
130
- card.append(create('p', { className: 's95b-note' }, t('restart')));
131
- card.append(create('p', { className: 's95b-note' }, t('restoreHint')));
132
- card.append(create('p', { className: 's95b-footer' }, t('safe')));
133
- card.append(create('p', { className: 's95b-footer' }, t('hint')));
134
- overlay.appendChild(card);
135
- selected = Math.max(0, Math.min(selected, menuButtons.length - 1));
136
- highlight();
137
- }
138
- function highlight() {
139
- menuButtons.forEach((b, i) => {
140
- b.classList.toggle('s95b-active', i === selected);
141
- b.setAttribute('tabindex', i === selected ? '0' : '-1');
142
- });
143
- if (menuButtons[selected]) menuButtons[selected].focus();
144
- }
145
- function openMenu() {
146
- if (!document.body) return;
147
- if (overlay) { hideMenu(); return; }
148
- overlay = create('div', { id: 's95b-overlay' });
149
- document.body.appendChild(overlay);
150
- selected = 0;
151
- drawMenu();
152
- }
153
- function hideMenu() {
154
- if (overlay) overlay.remove();
155
- overlay = null;
156
- menuButtons = [];
157
- }
158
- function fmtVideo(v) {
159
- const t = key => translate(state.lang, key);
160
- if (!v) return t('statWait');
161
- const width = v.videoWidth || 0;
162
- const height = v.videoHeight || 0;
163
- return width > 0 && height > 0 ? `${width} × ${height}` : t('statUnavailable');
164
- }
165
- function updateDiagnostics() {
166
- if (!diagnostic) return;
167
- const t = key => translate(state.lang, key);
168
- const video = document.querySelector('video');
169
- const codec = codecLabel(currentCodec());
170
- let dropped = t('statUnavailable');
171
- if (video && typeof video.getVideoPlaybackQuality === 'function') {
172
- try {
173
- const q = video.getVideoPlaybackQuality();
174
- if (q && Number.isFinite(q.droppedVideoFrames)) dropped = String(q.droppedVideoFrames);
175
- } catch (_) { /* diagnostics should never interrupt playback */ }
176
- }
177
- diagnostic.textContent = [t('statVideo') + ': ' + fmtVideo(video), t('statFrames') + ': ' + dropped,
178
- t('statCodec') + ': ' + codec].join(' | ');
179
- }
180
- function startDiagnostics() {
181
- if (!document.body || diagnostic) return;
182
- diagnostic = create('div', { id: 's95b-diagnostics', role: 'status' });
183
- document.body.appendChild(diagnostic);
184
- updateDiagnostics();
185
- // Interval is deliberately disabled unless explicitly enabled by the user.
186
- diagTimer = setInterval(updateDiagnostics, 2000);
187
- }
188
- function stopDiagnostics() {
189
- if (diagTimer !== null) clearInterval(diagTimer);
190
- diagTimer = null;
191
- if (diagnostic) diagnostic.remove();
192
- diagnostic = null;
193
- }
194
- function onKeys(e) {
195
- const code = e.keyCode || e.which;
196
- const isKeydown = e.type === undefined || e.type === 'keydown';
197
- // Samsung ColorF2Yellow: 405 per Tizen docs; 170 is the alternative code TizenTube
198
- // documents for yellow (it handles 172/191 for green/blue the same way). F2 = keyboard.
199
- // Red (403), green (404) and blue (406) are already used by TizenTube itself.
200
- if (code === 405 || code === 170 || e.key === 'ColorF2Yellow' || e.key === 'F2') {
201
- e.preventDefault(); e.stopImmediatePropagation();
202
- if (isKeydown) openMenu();
203
- return;
204
- }
205
- if (!overlay) return; // Do not interfere with normal YouTube key navigation.
206
- const back = code === 10009 || code === 8 || code === 27 || e.key === 'Escape' || e.key === 'Backspace';
207
- const up = code === 38 || e.key === 'ArrowUp';
208
- const down = code === 40 || e.key === 'ArrowDown';
209
- const enter = code === 13 || e.key === 'Enter';
210
- if (!(back || up || down || enter)) return;
211
- // keyup/keypress of handled keys are swallowed too, so the YouTube UI below never sees half a key press.
212
- e.preventDefault(); e.stopImmediatePropagation();
213
- if (!isKeydown) return;
214
- if (back) hideMenu();
215
- else if (up || down) { selected = (selected + (up ? -1 : 1) + menuButtons.length) % menuButtons.length; highlight(); }
216
- else if (menuButtons[selected]) menuButtons[selected].click();
217
- }
218
- function init() {
219
- if (!document.body || document.getElementById('s95b-style')) return;
220
- const css = `
221
- #s95b-overlay {position:fixed;top:0;left:0;right:0;bottom:0;z-index:2147483646;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.78);font-family:Arial,Helvetica,sans-serif;color:#f7fafc;}
222
- #s95b-overlay * {box-sizing:border-box;}
223
- #s95b-overlay .s95b-card {width:88vw;max-width:930px;max-height:90vh;overflow:auto;background:#111a27;border:2px solid #34506c;border-radius:18px;padding:28px 34px;box-shadow:0 20px 70px rgba(0,0,0,.55);}
224
- #s95b-overlay .s95b-eyebrow {font-size:19px;color:#a8cce0;}
225
- #s95b-overlay h2 {font-size:38px;line-height:1.2;margin:6px 0 18px;}
226
- #s95b-overlay button {width:100%;display:flex;align-items:center;justify-content:space-between;font-size:24px;line-height:1.2;color:#fff;text-align:left;padding:14px 18px;margin:6px 0;border:2px solid #26374b;border-radius:10px;background:#192638;cursor:pointer;}
227
- #s95b-overlay button strong {color:#bcecff;font-size:22px;margin-left:24px;}
228
- #s95b-overlay button.s95b-active,#s95b-overlay button:focus {background:#176687;border-color:#bcecff;outline:none;}
229
- #s95b-overlay p {font-size:17px;line-height:1.45;}
230
- #s95b-overlay .s95b-explanation {color:#bcd9df;margin:17px 0 4px;}
231
- #s95b-overlay .s95b-note {color:#d7dce5;margin:7px 0;}
232
- #s95b-overlay .s95b-footer {color:#91a0b3;margin:10px 0 0;font-size:15px;}
233
- #s95b-diagnostics {pointer-events:none;position:fixed;bottom:2vh;left:2vw;z-index:2147483645;padding:10px 16px;color:white;background:rgba(0,0,0,.7);font:15px Arial,Helvetica,sans-serif;border-radius:8px;}
234
- `;
235
- const style = create('style', { id: 's95b-style' });
236
- style.textContent = css;
237
- document.head.appendChild(style);
238
- // Register our listener in the capture phase; only known menu keys are consumed.
239
- window.addEventListener('keydown', onKeys, true);
240
- window.addEventListener('keyup', onKeys, true);
241
- window.addEventListener('keypress', onKeys, true);
242
- if (state.diagnostics) startDiagnostics();
243
- }
244
- readState();
245
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init, { once: true });
246
- else init();