@oxymoron.13/tizentube-s95b 0.2.2 → 0.3.0

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 (87) hide show
  1. package/README.md +51 -42
  2. package/S95B-CHANGES.txt +12 -4
  3. package/UPSTREAM-README.md +42 -0
  4. package/dist/service.js +3 -0
  5. package/dist/userScript.js +2 -2
  6. package/mods/config.js +117 -0
  7. package/mods/domrect-polyfill.js +104 -0
  8. package/mods/features/adblock.js +584 -0
  9. package/mods/features/autoFrameRate.js +42 -0
  10. package/mods/features/enableFeatures.js +19 -0
  11. package/mods/features/moreSubtitles.js +378 -0
  12. package/mods/features/pictureInPicture.js +198 -0
  13. package/mods/features/preferredVideoQuality.js +101 -0
  14. package/mods/features/s95bCodec.js +45 -0
  15. package/mods/features/s95bI18n.js +28 -0
  16. package/mods/features/s95bMenu.js +137 -0
  17. package/mods/features/s95bOptions.js +30 -0
  18. package/mods/features/s95bPlayback.js +29 -0
  19. package/mods/features/sponsorblock.js +437 -0
  20. package/mods/features/standaloneUserscript.js +107 -0
  21. package/mods/features/updater.js +119 -0
  22. package/mods/features/userAgentSpoofing.js +73 -0
  23. package/mods/features/videoQueuing.js +64 -0
  24. package/mods/package-lock.json +2321 -0
  25. package/mods/package.json +33 -0
  26. package/mods/resolveCommand.js +381 -0
  27. package/mods/rollup.config.js +41 -0
  28. package/mods/spatial-navigation-polyfill.js +1756 -0
  29. package/mods/tiny-sha256.js +99 -0
  30. package/mods/translations/i18nResources.js +11 -0
  31. package/mods/translations/index.js +18 -0
  32. package/mods/translations/language-names.js +981 -0
  33. package/mods/translations/resources/ar.json +308 -0
  34. package/mods/translations/resources/az.json +308 -0
  35. package/mods/translations/resources/bs.json +308 -0
  36. package/mods/translations/resources/cs.json +308 -0
  37. package/mods/translations/resources/de.json +308 -0
  38. package/mods/translations/resources/dk.json +308 -0
  39. package/mods/translations/resources/el.json +308 -0
  40. package/mods/translations/resources/en.json +308 -0
  41. package/mods/translations/resources/es-419.json +308 -0
  42. package/mods/translations/resources/es.json +308 -0
  43. package/mods/translations/resources/fi-FI.json +308 -0
  44. package/mods/translations/resources/fr.json +311 -0
  45. package/mods/translations/resources/hr.json +308 -0
  46. package/mods/translations/resources/hu.json +308 -0
  47. package/mods/translations/resources/id.json +308 -0
  48. package/mods/translations/resources/it.json +308 -0
  49. package/mods/translations/resources/ja.json +308 -0
  50. package/mods/translations/resources/lt.json +308 -0
  51. package/mods/translations/resources/lv.json +308 -0
  52. package/mods/translations/resources/pl.json +308 -0
  53. package/mods/translations/resources/pt-BR.json +308 -0
  54. package/mods/translations/resources/pt-PT.json +308 -0
  55. package/mods/translations/resources/ro.json +310 -0
  56. package/mods/translations/resources/ru.json +308 -0
  57. package/mods/translations/resources/sr-Cyrl.json +308 -0
  58. package/mods/translations/resources/sr-Latn.json +308 -0
  59. package/mods/translations/resources/tlh.json +308 -0
  60. package/mods/translations/resources/tr.json +308 -0
  61. package/mods/translations/resources/uk.json +308 -0
  62. package/mods/translations/resources/vi.json +309 -0
  63. package/mods/translations/resources/zh-CN.json +308 -0
  64. package/mods/translations/resources/zh-TW.json +308 -0
  65. package/mods/translations/update-translations.js +55 -0
  66. package/mods/ui/chapters.js +137 -0
  67. package/mods/ui/clock.js +71 -0
  68. package/mods/ui/customCommandExecution.js +49 -0
  69. package/mods/ui/customGuideAction.js +119 -0
  70. package/mods/ui/customUI.js +192 -0
  71. package/mods/ui/customYTSettings.js +29 -0
  72. package/mods/ui/disableWhosWatching.js +48 -0
  73. package/mods/ui/settings.js +1024 -0
  74. package/mods/ui/sidebarModification.js +179 -0
  75. package/mods/ui/speedUI.js +119 -0
  76. package/mods/ui/theme.js +26 -0
  77. package/mods/ui/ui.css +157 -0
  78. package/mods/ui/ui.js +261 -0
  79. package/mods/ui/ytUI.js +510 -0
  80. package/mods/userScript.js +28 -0
  81. package/mods/utils/ASTParser.js +67 -0
  82. package/mods/utils/innerTubeCalls.js +138 -0
  83. package/package.json +11 -5
  84. package/service/package-lock.json +4193 -0
  85. package/service/package.json +29 -0
  86. package/service/rollup.config.js +47 -0
  87. package/service/service.js +121 -0
@@ -0,0 +1,45 @@
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
+ }
@@ -0,0 +1,28 @@
1
+ /* S95B Performance Edition – GPL-3.0-only */
2
+ export const STRINGS = Object.freeze({
3
+ de: Object.freeze({
4
+ title: 'S95B · Performance', language: 'Sprache', codec: 'Videocodec', quality: 'Standardqualität (alle Videos)',
5
+ automatic: 'Automatisch', light: 'Ressourcenschonender Modus',
6
+ lightHint: 'Schaltet Vorschauen und DeArrow ab; vorherige Werte werden beim Ausschalten wiederhergestellt.',
7
+ 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. Gilt ab dem nächsten Video.',
8
+ qualityHint: 'Gilt für alle Videos (YouTubes eigene Zeile „Qualität“ ändert nur das laufende Video). Eine feste Qualität gilt sofort. „Automatisch“ wirkt eventuell erst nach einem Neustart von YouTube.',
9
+ languageHint: 'Gilt für dieses Menü, beim nächsten Öffnen.',
10
+ statVideo: 'Video', statFrames: 'Verlorene Bilder', statCodec: 'Codec-Wunsch',
11
+ statUnavailable: 'nicht verfügbar', statWait: 'wartet auf Video',
12
+ languageDE: 'Deutsch', languageEN: 'English',
13
+ }),
14
+ en: Object.freeze({
15
+ title: 'S95B · Performance', language: 'Language', codec: 'Video codec', quality: 'Default quality (all videos)',
16
+ automatic: 'Automatic', light: 'Low-overhead mode',
17
+ lightHint: 'Disables previews and DeArrow; the previous values are restored when switched off.',
18
+ 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. Applies from the next video.',
19
+ qualityHint: 'Applies to all videos (YouTube’s own “Quality” row only changes the current video). A fixed quality applies immediately. “Automatic” may only take effect after restarting YouTube.',
20
+ languageHint: 'Applies to this menu, the next time it is opened.',
21
+ statVideo: 'Video', statFrames: 'Dropped frames', statCodec: 'Codec preference',
22
+ statUnavailable: 'not available', statWait: 'waiting for video',
23
+ languageDE: 'Deutsch', languageEN: 'English',
24
+ })
25
+ });
26
+ export function translate(lang, key) {
27
+ return (STRINGS[lang] || STRINGS.de)[key] || STRINGS.en[key] || key;
28
+ }
@@ -0,0 +1,137 @@
1
+ /* S95B Performance Edition – GPL-3.0-only
2
+ * The yellow remote key opens a menu that YouTube itself draws (TizenTube's own optionShow),
3
+ * the same mechanism as the green (settings) and blue (speed) menus. No DOM overlay is added to the page.
4
+ * Uses only existing TizenTube configRead/configWrite APIs. No firmware calls, network requests or timers.
5
+ */
6
+ import { configRead, configWrite, configChangeEmitter } from '../config.js';
7
+ import { optionShow } from '../ui/settings.js';
8
+ import { translate } from './s95bI18n.js';
9
+ import { codecMenu, qualityMenu, codecLabel, currentCodec } from './s95bOptions.js';
10
+
11
+ const STATE_KEY = 's95b-perf-edition-v1';
12
+ const EDITED = ['enablePreviews', 'enableDeArrow', 'enableDeArrowThumbnails'];
13
+ const defaultState = { lang: 'de', light: false, previous: null };
14
+ let state = { ...defaultState };
15
+
16
+ function readState() {
17
+ try {
18
+ const data = JSON.parse(localStorage.getItem(STATE_KEY) || 'null');
19
+ if (data && typeof data === 'object') {
20
+ state = { ...defaultState,
21
+ lang: data.lang === 'en' ? 'en' : 'de', light: data.light === true,
22
+ previous: data.previous && typeof data.previous === 'object' ? data.previous : null };
23
+ }
24
+ } catch (_) { state = { ...defaultState }; }
25
+ }
26
+ function save() {
27
+ try { localStorage.setItem(STATE_KEY, JSON.stringify(state)); } catch (_) { /* no persistent storage */ }
28
+ }
29
+ function t(key) { return translate(state.lang, key); }
30
+
31
+
32
+ // Low-overhead mode: switches off a few TizenTube features and remembers the previous values.
33
+ function turnLight(enable) {
34
+ if (enable === state.light) return;
35
+ if (enable) {
36
+ const values = {};
37
+ EDITED.forEach(key => { values[key] = configRead(key); });
38
+ state.previous = values;
39
+ EDITED.forEach(key => configWrite(key, false));
40
+ state.light = true;
41
+ } else {
42
+ if (state.previous) EDITED.forEach(key => {
43
+ if (Object.prototype.hasOwnProperty.call(state.previous, key)) {
44
+ configWrite(key, state.previous[key]);
45
+ }
46
+ });
47
+ state.previous = null;
48
+ state.light = false;
49
+ }
50
+ save();
51
+ }
52
+
53
+ // Source frame rate as YouTube's own stats report it ("3840x2160@24 / ..."), same source as upstream autoFrameRate.js.
54
+ function frameRate() {
55
+ try {
56
+ const player = document.querySelector('.html5-video-player');
57
+ if (!player || typeof player.getStatsForNerds !== 'function') return '';
58
+ const match = String(player.getStatsForNerds().resolution || '').match(/@([\d.]+)/);
59
+ return match ? ' @' + match[1] : '';
60
+ } catch (_) { return ''; }
61
+ }
62
+
63
+ // One snapshot of what the web player reports, taken each time the menu opens.
64
+ function statsLine() {
65
+ const video = document.querySelector('video');
66
+ let size = t('statWait');
67
+ let dropped = t('statUnavailable');
68
+ if (video) {
69
+ const width = video.videoWidth || 0;
70
+ const height = video.videoHeight || 0;
71
+ size = width > 0 && height > 0 ? width + ' × ' + height + frameRate() : t('statUnavailable');
72
+ if (typeof video.getVideoPlaybackQuality === 'function') {
73
+ try {
74
+ const q = video.getVideoPlaybackQuality();
75
+ if (q && Number.isFinite(q.droppedVideoFrames)) dropped = String(q.droppedVideoFrames);
76
+ } catch (_) { /* diagnostics must never interrupt playback */ }
77
+ }
78
+ }
79
+ return t('statVideo') + ': ' + size + ' | ' + t('statFrames') + ': ' + dropped +
80
+ ' | ' + t('statCodec') + ': ' + codecLabel(t, currentCodec());
81
+ }
82
+
83
+ function buildOptions() {
84
+ return [
85
+ { name: t('language'), value: null, menuId: 's95b-language',
86
+ menuHeader: { title: t('language'), subtitle: t('languageHint') },
87
+ options: [
88
+ { name: t('languageDE'), key: 's95bLanguage', value: 'de' },
89
+ { name: t('languageEN'), key: 's95bLanguage', value: 'en' }
90
+ ] },
91
+ codecMenu(t),
92
+ qualityMenu(t),
93
+ { name: t('light'), subtitle: t('lightHint'), value: 's95bLowOverhead' }
94
+ ];
95
+ }
96
+
97
+ function openMenu() {
98
+ try {
99
+ optionShow({
100
+ menuId: 's95b-menu',
101
+ menuHeader: { title: t('title'), subtitle: statsLine() },
102
+ selectedIndex: 0,
103
+ options: buildOptions()
104
+ }, false);
105
+ } catch (e) { console.warn('[S95B] menu could not be opened:', e); }
106
+ }
107
+
108
+ function onKeys(e) {
109
+ const code = e.keyCode || e.which;
110
+ // Samsung ColorF2Yellow: 405 per Tizen docs, 170 as the alternative code TizenTube mentions, F2 on a keyboard.
111
+ // Red (403), green (404) and blue (406) are used by TizenTube itself. Nothing else is touched.
112
+ if (code === 405 || code === 170 || e.key === 'ColorF2Yellow' || e.key === 'F2') {
113
+ e.preventDefault(); e.stopImmediatePropagation();
114
+ if (e.type === undefined || e.type === 'keydown') openMenu();
115
+ }
116
+ }
117
+
118
+ function onConfigChange(event) {
119
+ const detail = event && event.detail;
120
+ if (!detail) return;
121
+ if (detail.key === 's95bLowOverhead') turnLight(detail.value === true);
122
+ else if (detail.key === 's95bLanguage') { state.lang = detail.value === 'en' ? 'en' : 'de'; save(); }
123
+ }
124
+
125
+ function init() {
126
+ // Menu entries read their current value from the TizenTube config, so mirror our stored state into it first.
127
+ if (configRead('s95bLowOverhead') !== state.light) configWrite('s95bLowOverhead', state.light);
128
+ if (configRead('s95bLanguage') !== state.lang) configWrite('s95bLanguage', state.lang);
129
+ configChangeEmitter.addEventListener('configChange', onConfigChange);
130
+ // Capture phase on window: only the yellow key is consumed.
131
+ window.addEventListener('keydown', onKeys, true);
132
+ window.addEventListener('keyup', onKeys, true);
133
+ window.addEventListener('keypress', onKeys, true);
134
+ }
135
+
136
+ readState();
137
+ init();
@@ -0,0 +1,30 @@
1
+ /* S95B Performance Edition – GPL-3.0-only
2
+ * Codec and default-quality choices, shared by the yellow menu and the playback settings popup.
3
+ * Both write TizenTube's own config keys (preferredVideoCodec, preferredVideoQuality).
4
+ */
5
+ import { configRead } from '../config.js';
6
+
7
+ export const CODECS = ['any', 'vp9', 'av01', 'avc1'];
8
+ export const QUALITIES = ['auto', '2160p', '1440p', '1080p', '720p'];
9
+
10
+ export function codecLabel(t, value) {
11
+ return value === 'any' ? t('automatic') : value === 'vp9' ? 'VP9' : value === 'av01' ? 'AV1' : 'H.264';
12
+ }
13
+ export function qualityLabel(t, value) {
14
+ return value === 'auto' ? t('automatic') : value === '2160p' ? '2160p (4K)' : String(value);
15
+ }
16
+ export function currentCodec() {
17
+ const value = configRead('preferredVideoCodec');
18
+ return CODECS.indexOf(value) >= 0 ? value : 'any';
19
+ }
20
+
21
+ export function codecMenu(t) {
22
+ return { name: t('codec'), icon: 'VIDEO_QUALITY', value: null, menuId: 's95b-codec',
23
+ menuHeader: { title: t('codec'), subtitle: t('codecHint') },
24
+ options: CODECS.map(codec => ({ name: codecLabel(t, codec), key: 'preferredVideoCodec', value: codec })) };
25
+ }
26
+ export function qualityMenu(t) {
27
+ return { name: t('quality'), icon: 'VIDEO_QUALITY', value: null, menuId: 's95b-quality',
28
+ menuHeader: { title: t('quality'), subtitle: t('qualityHint') },
29
+ options: QUALITIES.map(quality => ({ name: qualityLabel(t, quality), key: 'preferredVideoQuality', value: quality })) };
30
+ }
@@ -0,0 +1,29 @@
1
+ /* S95B Performance Edition – GPL-3.0-only
2
+ * Two entries for YouTube's playback settings popup (opened while a video plays): video codec and
3
+ * default quality for all videos. They open the same YouTube-drawn choice lists as the yellow menu.
4
+ * buttonItem is handed in by resolveCommand.js, which already imports it.
5
+ */
6
+ import { configRead } from '../config.js';
7
+ import { translate } from './s95bI18n.js';
8
+ import { codecMenu, qualityMenu, codecLabel, qualityLabel, currentCodec } from './s95bOptions.js';
9
+
10
+ export function s95bPlaybackItems(buttonItem) {
11
+ const lang = configRead('s95bLanguage') === 'en' ? 'en' : 'de';
12
+ const t = key => translate(lang, key);
13
+ const entry = (menu, subtitle) => buttonItem({ title: menu.name, subtitle }, { icon: menu.icon }, [{
14
+ customAction: {
15
+ action: 'OPTIONS_SHOW',
16
+ parameters: {
17
+ options: menu.options,
18
+ selectedIndex: Math.max(0, menu.options.findIndex(o => o.value === configRead(o.key))),
19
+ update: false,
20
+ menuId: menu.menuId,
21
+ menuHeader: menu.menuHeader
22
+ }
23
+ }
24
+ }]);
25
+ return [
26
+ entry(codecMenu(t), codecLabel(t, currentCodec())),
27
+ entry(qualityMenu(t), qualityLabel(t, configRead('preferredVideoQuality')))
28
+ ];
29
+ }