@oxymoron.13/tizentube-s95b 0.2.0 → 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 -34
  16. package/mods/features/s95bMenu.js +0 -223
  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,378 +0,0 @@
1
- // TizenTube Subtitle Localization Mod
2
- // Automatically adds user's local language to subtitle auto-translate menu if not present
3
-
4
- import { configRead } from "../config.js";
5
- import languages from "../translations/language-names.js";
6
-
7
- const LANGUAGE_CODES = [
8
- "af", "sq", "am", "ar", "hy", "as", "az", "eu", "be", "bn", "bs", "bg",
9
- "my", "ca", "zh-CN", "zh-TW", "zh-HK", "hr", "cs", "da", "nl", "en", "et",
10
- "fil", "fi", "fr", "gl", "ka", "de", "el", "gu", "he", "hi", "hu", "is",
11
- "id", "ga", "it", "ja", "kn", "kk", "km", "ko", "ky", "lo", "lv", "lt",
12
- "mk", "ms", "ml", "mt", "mr", "mn", "ne", "no", "or", "fa", "pl", "pt",
13
- "pa", "ro", "ru", "sr", "si", "sk", "sl", "es", "sw", "sv", "ta", "te",
14
- "th", "tr", "uk", "ur", "uz", "vi", "cy", "yi", "yo", "zu"
15
- ];
16
-
17
- // Return an object mapping language code -> localized language name.
18
- export function getComprehensiveLanguageList() {
19
- try {
20
- const map = {};
21
- LANGUAGE_CODES.forEach((code) => {
22
- if (code.includes("-")) {
23
- const [lang, region] = code.split("-");
24
- const languageName = languages.language.standard.long[lang] || code;
25
- const regionName = languages.region.long[region] || region;
26
- map[code] = `${languageName} (${regionName})`;
27
- } else {
28
- const name = languages.language.standard.long[code] || code;
29
- map[code] = name;
30
- }
31
- });
32
- return map;
33
- } catch (e) {
34
- const fallback = {};
35
- LANGUAGE_CODES.forEach((c) => (fallback[c] = c));
36
- return fallback;
37
- }
38
- }
39
-
40
- // Infer the most likely language for a given ISO 3166-1 alpha-2 country code using Intl.Locale.
41
- // Returns { code, name } or null if unknown.
42
- export function getCountryLanguage(countryCode) {
43
- if (!countryCode) return null;
44
- try {
45
- const region = String(countryCode).toUpperCase();
46
-
47
- const zhRegionMap = { CN: "zh-CN", TW: "zh-TW", HK: "zh-HK", SG: "zh-CN" };
48
- if (zhRegionMap[region]) {
49
- const code = zhRegionMap[region];
50
- const name = languages.language.standard.long[code] || code;
51
- return { code, name };
52
- }
53
-
54
- const base = new Intl.Locale("und", { region });
55
- const maximized = base.maximize ? base.maximize() : base;
56
- const lang = maximized.language || "en";
57
-
58
- const name = languages.language.standard.long[lang] || lang;
59
-
60
- return { code: lang, name };
61
- } catch (e) {
62
- console.warn("TizenTube Subtitle Localization: Could not infer language for country", countryCode, e);
63
- return null;
64
- }
65
- }
66
-
67
- let isPatched = false;
68
-
69
- // Function to get user's country code
70
- function getUserCountryCode() {
71
- try {
72
- // Always use window.yt.config_.GL as primary source
73
- if (window.yt && window.yt.config_ && window.yt.config_.GL) {
74
- return window.yt.config_.GL;
75
- }
76
-
77
- console.warn(
78
- "TizenTube Subtitle Localization: Could not determine user country code"
79
- );
80
- return null;
81
- } catch (error) {
82
- console.error(
83
- "TizenTube Subtitle Localization: Error getting country code:",
84
- error
85
- );
86
- return null;
87
- }
88
- }
89
-
90
- // Function to check if language already exists in the menu
91
- function languageExistsInMenu(items, languageCode, languageName) {
92
- return items.some((item) => {
93
- if (
94
- item.compactLinkRenderer &&
95
- item.compactLinkRenderer.serviceEndpoint
96
- ) {
97
- const commands =
98
- item.compactLinkRenderer.serviceEndpoint.commandExecutorCommand
99
- ?.commands;
100
- if (
101
- commands &&
102
- commands[0] &&
103
- commands[0].selectSubtitlesTrackCommand
104
- ) {
105
- const translationLang =
106
- commands[0].selectSubtitlesTrackCommand.translationLanguage;
107
- return (
108
- translationLang &&
109
- (translationLang.languageCode === languageCode ||
110
- translationLang.languageName === languageName)
111
- );
112
- }
113
- }
114
- return false;
115
- });
116
- }
117
-
118
- // Function to create a language option
119
- function createLanguageOption(languageCode, languageName) {
120
- return {
121
- compactLinkRenderer: {
122
- title: { simpleText: languageName },
123
- serviceEndpoint: {
124
- commandExecutorCommand: {
125
- commands: [
126
- {
127
- selectSubtitlesTrackCommand: {
128
- translationLanguage: {
129
- languageCode,
130
- languageName,
131
- },
132
- },
133
- },
134
- {
135
- openClientOverlayAction: {
136
- type: "CLIENT_OVERLAY_TYPE_CAPTIONS_LANGUAGE",
137
- updateAction: true,
138
- },
139
- },
140
- {
141
- signalAction: { signal: "POPUP_BACK" },
142
- },
143
- ],
144
- },
145
- },
146
- secondaryIcon: { iconType: "RADIO_BUTTON_UNCHECKED" },
147
- },
148
- };
149
- }
150
-
151
- // Function to get languages already present in menu
152
- function getExistingLanguages(items) {
153
- const existingLanguages = new Set();
154
-
155
- items.forEach((item) => {
156
- if (
157
- item.compactLinkRenderer &&
158
- item.compactLinkRenderer.serviceEndpoint
159
- ) {
160
- const commands =
161
- item.compactLinkRenderer.serviceEndpoint.commandExecutorCommand
162
- ?.commands;
163
- if (
164
- commands &&
165
- commands[0] &&
166
- commands[0].selectSubtitlesTrackCommand
167
- ) {
168
- const translationLang =
169
- commands[0].selectSubtitlesTrackCommand.translationLanguage;
170
- if (translationLang) {
171
- existingLanguages.add(translationLang.languageCode);
172
- existingLanguages.add(translationLang.languageName);
173
- }
174
- }
175
- }
176
- });
177
-
178
- return existingLanguages;
179
- }
180
-
181
- // Function to create section title
182
- function createSectionTitle(title) {
183
- return {
184
- overlayMessageRenderer: {
185
- title: { simpleText: "" },
186
- subtitle: { simpleText: title },
187
- style: "OVERLAY_MESSAGE_STYLE_SUBSECTION_TITLE",
188
- },
189
- };
190
- }
191
-
192
- // Main function to patch the subtitle menu
193
- function patchSubtitleMenu() {
194
- if (isPatched) return;
195
-
196
- const player = document.querySelector('.html5-video-player');
197
- if (!player) return setTimeout(patchSubtitleMenu, 250);
198
-
199
- // Always patch if possible - settings will be checked dynamically
200
- if (!window._yttv) return setTimeout(patchSubtitleMenu, 250);
201
- const yttvInstance = Object.values(window._yttv).find(
202
- (obj) =>
203
- obj &&
204
- obj.instance &&
205
- typeof obj.instance.resolveCommand === "function"
206
- );
207
-
208
- if (
209
- !yttvInstance ||
210
- yttvInstance.instance.resolveCommand.isPatchedBySubtitleLocalization
211
- ) {
212
- if (!yttvInstance) {
213
- console.error(
214
- "TizenTube Subtitle Localization: Could not find resolveCommand instance."
215
- );
216
- } else {
217
- console.log("TizenTube Subtitle Localization: Already patched.");
218
- }
219
- return;
220
- }
221
-
222
- const originalResolveCommand = yttvInstance.instance.resolveCommand;
223
-
224
- yttvInstance.instance.resolveCommand = function (cmd, _) {
225
- // Identify the correct command using its uniqueId
226
- if (
227
- cmd?.openPopupAction?.uniqueId ===
228
- "CLIENT_OVERLAY_TYPE_CAPTIONS_AUTO_TRANSLATE"
229
- ) {
230
- // Check current settings dynamically each time menu opens
231
- const showUserLanguage = configRead("enableShowUserLanguage");
232
- const showOtherLanguages = configRead("enableShowOtherLanguages");
233
-
234
- // If neither feature is enabled, don't modify the menu
235
- if (!showUserLanguage && !showOtherLanguages) {
236
- return originalResolveCommand.apply(this, arguments);
237
- }
238
-
239
- const items =
240
- cmd.openPopupAction.popup.overlaySectionRenderer.overlay
241
- .overlayTwoPanelRenderer.actionPanel.overlayPanelRenderer
242
- .content.overlayPanelItemListRenderer.items;
243
-
244
- // Get existing languages
245
- const existingLanguages = getExistingLanguages(items);
246
-
247
- // Add user's local language if enabled
248
- if (showUserLanguage) {
249
- const userCountryCode = getUserCountryCode();
250
- const userLanguage = getCountryLanguage(userCountryCode);
251
-
252
- if (userLanguage) {
253
- // Check if the user's language already exists
254
- if (
255
- !languageExistsInMenu(items, userLanguage.code, userLanguage.name)
256
- ) {
257
- console.log(
258
- `%c[TizenTube Subtitle Localization] Adding user's local language: ${userLanguage.name} (${userLanguage.code})`,
259
- "background: #2196F3; color: #ffffff; font-size: 14px; font-weight: bold;"
260
- );
261
-
262
- const userLanguageOption = createLanguageOption(
263
- userLanguage.code,
264
- userLanguage.name
265
- );
266
-
267
- // Find the "Recommended languages" section and insert after it
268
- const recommendedIndex = items.findIndex(
269
- (item) =>
270
- item.overlayMessageRenderer?.subtitle
271
- ?.simpleText === "Recommended languages"
272
- );
273
-
274
- if (recommendedIndex > -1) {
275
- // Insert user's language as the first recommendation
276
- items.splice(
277
- recommendedIndex + 1,
278
- 0,
279
- userLanguageOption
280
- );
281
- // Update existing languages set
282
- existingLanguages.add(userLanguage.code);
283
- existingLanguages.add(userLanguage.name);
284
- } else {
285
- // Find "Other languages" section and insert before it
286
- const otherLanguagesIndex = items.findIndex(
287
- (item) =>
288
- item.overlayMessageRenderer?.subtitle
289
- ?.simpleText === "Other languages"
290
- );
291
-
292
- if (otherLanguagesIndex > -1) {
293
- items.splice(
294
- otherLanguagesIndex,
295
- 0,
296
- userLanguageOption
297
- );
298
- } else {
299
- // As a fallback, add it at the beginning
300
- items.unshift(userLanguageOption);
301
- }
302
- // Update existing languages set
303
- existingLanguages.add(userLanguage.code);
304
- existingLanguages.add(userLanguage.name);
305
- }
306
- } else {
307
- console.log(
308
- `%c[TizenTube Subtitle Localization] User's language ${userLanguage.name} already exists in menu`,
309
- "background: #4CAF50; color: #ffffff; font-size: 12px;"
310
- );
311
- }
312
- } else {
313
- console.warn(
314
- `TizenTube Subtitle Localization: No language mapping found for country code: ${userCountryCode}`
315
- );
316
- }
317
- }
318
-
319
- // Create "Tizen Languages" section with all missing languages if enabled
320
- if (showOtherLanguages) {
321
- const missingLanguages = Object.entries(getComprehensiveLanguageList())
322
- .filter(([code, name]) => !existingLanguages.has(code) && !existingLanguages.has(name))
323
- .sort(([, a], [, b]) => a.localeCompare(b));
324
-
325
- if (missingLanguages.length > 0) {
326
- console.log(
327
- `%c[TizenTube Subtitle Localization] Adding "Tizen Languages" section with ${missingLanguages.length} additional languages`,
328
- "background: #FF9800; color: #ffffff; font-size: 12px;"
329
- );
330
-
331
- // Add section title
332
- items.push(createSectionTitle("Other Languages"));
333
-
334
- // Add all missing languages
335
- missingLanguages.forEach(([code, name]) => {
336
- items.push(createLanguageOption(code, name));
337
- });
338
-
339
- console.log(
340
- `%c[TizenTube Subtitle Localization] Added "Tizen Languages" section`,
341
- "background: #FF9800; color: #ffffff; font-size: 12px;"
342
- );
343
- } else {
344
- console.log(
345
- `%c[TizenTube Subtitle Localization] All languages already present in menu`,
346
- "background: #4CAF50; color: #ffffff; font-size: 12px;"
347
- );
348
- }
349
- }
350
- }
351
-
352
- // Let the original function run with our modified 'cmd' object
353
- return originalResolveCommand.apply(this, arguments);
354
- };
355
-
356
- yttvInstance.instance.resolveCommand.isPatchedBySubtitleLocalization = true;
357
- console.log("TizenTube Subtitle Localization: Patch successful!");
358
- isPatched = true;
359
- }
360
-
361
- // Wait for the YouTube TV app to be ready
362
- const interval = setInterval(() => {
363
- if (window._yttv && Object.keys(window._yttv).length > 0) {
364
- patchSubtitleMenu();
365
- clearInterval(interval);
366
- }
367
- }, 1000);
368
-
369
- // Also try to patch when DOM is loaded
370
- if (document.readyState === "loading") {
371
- document.addEventListener("DOMContentLoaded", patchSubtitleMenu);
372
- } else {
373
- patchSubtitleMenu();
374
- }
375
-
376
- console.log(
377
- "TizenTube Subtitle Localization: Module loaded, waiting for YouTube TV..."
378
- );
@@ -1,198 +0,0 @@
1
- // Picture in Picture Mode for TizenTube
2
-
3
- import resolveCommand from "../resolveCommand.js";
4
-
5
- window.isPipPlaying = false;
6
- let PlayerService = null;
7
-
8
- function pipLoad() {
9
- const mappings = Object.values(window._yttv).find(a => a && a.mappings);
10
- PlayerService = mappings.get('PlayerService');
11
- const PlaybackPreviewService = mappings.get('PlaybackPreviewService');
12
- const PlaybackPreviewServiceStart = PlaybackPreviewService.start;
13
- const PlaybackPreviewServiceStop = PlaybackPreviewService.stop;
14
-
15
-
16
- PlaybackPreviewService.start = function (...args) {
17
- if (window.isPipPlaying) return;
18
- return PlaybackPreviewServiceStart.apply(this, args);
19
- }
20
-
21
- PlaybackPreviewService.stop = function (...args) {
22
- if (window.isPipPlaying) return;
23
- return PlaybackPreviewServiceStop.apply(this, args);
24
- }
25
- }
26
-
27
- if (document.readyState === 'complete') {
28
- pipLoad();
29
- } else window.addEventListener('load', pipLoad);
30
-
31
- function enablePip() {
32
- if (!PlayerService) return;
33
- const timestamp = Math.floor(document.querySelector('video').currentTime);
34
- const videoElement = document.querySelector('video');
35
-
36
- const ytlrPlayer = document.querySelector('ytlr-player');
37
- const ytlrPlayerContainer = document.querySelector('ytlr-player-container');
38
-
39
- const observer = new MutationObserver((mutations) => {
40
- mutations.forEach((mutation) => {
41
- if (mutation.attributeName === 'class') {
42
- if (!ytlrPlayer.classList.contains('ytLrPlayerEnabled')) {
43
- function setStyles() {
44
- ytlrPlayerContainer.style.zIndex = '10';
45
- ytlrPlayer.style.display = 'block';
46
- ytlrPlayer.style.backgroundColor = 'rgba(0,0,0,0)';
47
- }
48
-
49
- setStyles();
50
- setTimeout(setStyles, 500);
51
-
52
- function onPipEnter() {
53
- videoElement.style.removeProperty('inset');
54
- const pipWidth = window.innerWidth / 3.5;
55
- const pipHeight = window.innerHeight / 3.5;
56
- videoElement.style.width = `${pipWidth}px`;
57
- videoElement.style.height = `${pipHeight}px`;
58
- videoElement.style.top = '68vh';
59
- videoElement.style.left = '68vw';
60
-
61
- window.isPipPlaying = true;
62
- videoElement.removeEventListener('play', onPipEnter);
63
- }
64
-
65
- videoElement.addEventListener('play', onPipEnter);
66
- observer.disconnect();
67
-
68
- setTimeout(() => {
69
- PlayerService.loadedPlaybackConfig.watchEndpoint.startTimeSeconds = timestamp;
70
- PlayerService.loadVideo(PlayerService.loadedPlaybackConfig);
71
- }, 1000);
72
- }
73
- }
74
- });
75
- });
76
-
77
- observer.observe(ytlrPlayer, { attributes: true });
78
-
79
- // Exit from the current video player
80
- resolveCommand({
81
- signalAction: {
82
- signal: "HISTORY_BACK"
83
- }
84
- });
85
- }
86
-
87
- function pipToFullscreen() {
88
- const { clickTrackingParams, commandMetadata, watchEndpoint } = PlayerService.loadedPlaybackConfig;
89
- watchEndpoint.startTimeSeconds = Math.floor(document.querySelector('video').currentTime);
90
- const command = {
91
- clickTrackingParams,
92
- commandMetadata,
93
- watchEndpoint
94
- };
95
- resolveCommand(command);
96
- window.isPipPlaying = false;
97
- };
98
-
99
- const originalClasses = {
100
- ytlrSearchVoice: {
101
- length: 0,
102
- classes: []
103
- },
104
- ytlrSearchVoiceMicButton: {
105
- length: 0,
106
- classes: []
107
- }
108
- }
109
-
110
- const observerPipEnter = new MutationObserver(() => {
111
- if (!window.isPipPlaying) return;
112
- const searchBar = document.querySelector('ytlr-search-bar');
113
- if (searchBar) {
114
- const pipButtonExists = document.querySelector('#tt-pip-button');
115
- if (!pipButtonExists) {
116
- const voiceButton = searchBar.querySelector('ytlr-search-voice');
117
- if (voiceButton) {
118
- const iconClassNames = Object.values(window._yttv).find(a => a instanceof Map && a.has("CLEAR_COOKIES"));
119
- const iconClassToBeRemoved = iconClassNames.get('MICROPHONE_ON');
120
- const iconClearCookiesClass = iconClassNames.get('CLEAR_COOKIES');
121
- const pipButton = document.createElement('ytlr-search-voice');
122
- for (let i = 0; i < voiceButton.classList.length; i++) {
123
- if (originalClasses.ytlrSearchVoice.length === 0) {
124
- originalClasses.ytlrSearchVoice.length = voiceButton.classList.length;
125
- }
126
-
127
- if (originalClasses.ytlrSearchVoice.length !== voiceButton.classList.length) {
128
- for (const className of originalClasses.ytlrSearchVoice.classes) {
129
- pipButton.classList.add(className);
130
- }
131
- break;
132
- }
133
-
134
- if (!originalClasses.ytlrSearchVoice.classes.includes(voiceButton.classList[i]))
135
- originalClasses.ytlrSearchVoice.classes.push(voiceButton.classList[i]);
136
-
137
- pipButton.classList.add(voiceButton.classList[i]);
138
-
139
- }
140
- pipButton.style.left = '10.25em';
141
- pipButton.id = 'tt-pip-button';
142
- const pipButtonMicButton = document.createElement('ytlr-search-voice-mic-button');
143
- for (let i = 0; i < voiceButton.children[0].classList.length; i++) {
144
- if (originalClasses.ytlrSearchVoiceMicButton.length === 0) {
145
- originalClasses.ytlrSearchVoiceMicButton.length = voiceButton.children[0].classList.length;
146
- }
147
-
148
- if (originalClasses.ytlrSearchVoiceMicButton.length !== voiceButton.children[0].classList.length) {
149
- for (const className of originalClasses.ytlrSearchVoiceMicButton.classes) {
150
- pipButtonMicButton.classList.add(className);
151
- }
152
- break;
153
- }
154
-
155
- if (!originalClasses.ytlrSearchVoiceMicButton.classes.includes(voiceButton.children[0].classList[i]))
156
- originalClasses.ytlrSearchVoiceMicButton.classes.push(voiceButton.children[0].classList[i]);
157
-
158
- pipButtonMicButton.classList.add(voiceButton.children[0].classList[i]);
159
- }
160
- const pipIcon = document.createElement('yt-icon');
161
- for (let i = 0; i < voiceButton.children[0].children[0].classList.length; i++) {
162
- pipIcon.classList.add(voiceButton.children[0].children[0].classList[i]);
163
- }
164
- pipIcon.classList.remove(iconClassToBeRemoved);
165
- pipIcon.classList.add(iconClearCookiesClass);
166
-
167
- pipButtonMicButton.appendChild(pipIcon);
168
- pipButton.appendChild(pipButtonMicButton);
169
- searchBar.appendChild(pipButton);
170
- } else {
171
- const pipButton = document.createElement('ytlr-search-voice');
172
- pipButton.style.left = '10.25em';
173
- pipButton.id = 'tt-pip-button';
174
- pipButton.setAttribute('idomkey', 'ytLrSearchBarSearchVoice');
175
- pipButton.setAttribute('tabindex', '0');
176
- pipButton.classList.add('ytLrSearchVoiceHost', 'ytLrSearchBarSearchVoice');
177
- const pipButtonMicButton = document.createElement('ytlr-search-voice-mic-button');
178
- pipButtonMicButton.setAttribute('hybridnavfocusable', 'true');
179
- pipButtonMicButton.setAttribute('tabindex', '-1');
180
- pipButtonMicButton.classList.add('ytLrSearchVoiceMicButtonHost', 'zylon-ve');
181
- const pipIcon = document.createElement('yt-icon');
182
- pipIcon.setAttribute('tabindex', '-1');
183
- pipIcon.classList.add('ytContribIconTvArrowLeft', 'ytContribIconHost', 'ytLrSearchVoiceMicButtonIcon');
184
-
185
- pipButtonMicButton.appendChild(pipIcon);
186
- pipButton.appendChild(pipButtonMicButton);
187
- searchBar.appendChild(pipButton);
188
- }
189
- }
190
- }
191
- });
192
-
193
- observerPipEnter.observe(document.body, { childList: true, subtree: true });
194
-
195
- export {
196
- enablePip,
197
- pipToFullscreen
198
- }
@@ -1,101 +0,0 @@
1
- import { configRead, configChangeEmitter } from "../config.js";
2
-
3
- const SELECTORS = {
4
- PLAYER: '.html5-video-player',
5
- };
6
-
7
- const EVENTS = {
8
- YT_STATE_CHANGE: 'onStateChange',
9
- CONFIG_CHANGE: 'configChange',
10
- };
11
-
12
- const CONFIG_KEYS = {
13
- QUALITY: 'preferredVideoQuality',
14
- };
15
-
16
- class PreferredQualityHandler {
17
- #player = null;
18
- #attachTimeout = null;
19
- #lastVideoId = null;
20
- #hasAppliedQuality = false;
21
-
22
- constructor() {
23
- this.init();
24
- }
25
-
26
- init() {
27
- this.#pollForPlayer();
28
- this.#setupConfigListener();
29
- }
30
-
31
- #pollForPlayer() {
32
- clearTimeout(this.#attachTimeout);
33
-
34
- const playerElement = document.querySelector(SELECTORS.PLAYER);
35
-
36
- if (!playerElement) {
37
- this.#attachTimeout = setTimeout(() => this.#pollForPlayer(), 100);
38
- return;
39
- }
40
-
41
- this.#player = playerElement;
42
-
43
- this.#player.addEventListener(EVENTS.YT_STATE_CHANGE, this.#handleStateChange);
44
-
45
- this.#handleStateChange();
46
- }
47
-
48
- #setupConfigListener() {
49
- configChangeEmitter.addEventListener(EVENTS.CONFIG_CHANGE, (ev) => {
50
- if (ev.detail?.key === CONFIG_KEYS.QUALITY) {
51
- this.#applyQuality();
52
- }
53
- });
54
- }
55
-
56
- #handleStateChange = () => {
57
- const state = this.#player?.getPlayerStateObject?.();
58
- const videoData = this.#player?.getVideoData?.();
59
- const videoId = videoData?.video_id;
60
-
61
- if (videoId !== this.#lastVideoId) {
62
- this.#lastVideoId = videoId;
63
- this.#hasAppliedQuality = false;
64
- }
65
-
66
- const isShorts = Object.values(this.#player.getVideoStats()).find(a => a && a === 'shortspage');
67
- if (state?.isPlaying && !this.#hasAppliedQuality && !isShorts) {
68
- this.#applyQuality();
69
- this.#hasAppliedQuality = true;
70
- }
71
- };
72
-
73
- #applyQuality() {
74
- const preferredQuality = configRead(CONFIG_KEYS.QUALITY);
75
- if (!preferredQuality || preferredQuality === 'auto' || !this.#player) return;
76
-
77
- try {
78
- const quality = this.#determineQuality(preferredQuality);
79
-
80
- if (quality) {
81
- this.#player.setPlaybackQualityRange(quality, quality)
82
- }
83
- } catch (e) {
84
- console.warn('[PreferredQuality] Failed to apply quality:', e);
85
- }
86
- }
87
-
88
- #determineQuality(preference) {
89
- const availableQualities = this.#player.getAvailableQualityData();
90
- if (!availableQualities?.length) return 'highres';
91
-
92
- const getQualityValue = (label) => parseInt(label, 10) || 0;
93
- const targetValue = getQualityValue(preference);
94
-
95
- const match = availableQualities.find(q => getQualityValue(q.qualityLabel) === targetValue);
96
-
97
- return match ? match.quality : 'highres';
98
- }
99
- }
100
-
101
- window.preferredVideoQualityHandler = new PreferredQualityHandler();