1package webkit
2
3import (
4 "strings"
5)
6
7// GenerateCodecControlScript creates JavaScript code to control codec preferences
8// This script overrides MediaCapabilities API and platform-specific player configurations
9func GenerateCodecControlScript(prefs CodecPreferencesConfig) string {
10 // Build blocked codecs regex pattern
11 var blockedCodecs []string
12 for _, codec := range prefs.BlockedCodecs {
13 switch strings.ToLower(codec) {
14 case "vp9":
15 blockedCodecs = append(blockedCodecs, "vp9", "vp09")
16 case "vp8":
17 blockedCodecs = append(blockedCodecs, "vp8", "vp08")
18 case "h264":
19 blockedCodecs = append(blockedCodecs, "h264", "avc1")
20 case "h265", "hevc":
21 blockedCodecs = append(blockedCodecs, "h265", "hevc", "hev1", "hvc1")
22 default:
23 blockedCodecs = append(blockedCodecs, strings.ToLower(codec))
24 }
25 }
26
27 // Build preferred codecs list
28 var preferredCodecs []string
29 for _, codec := range prefs.PreferredCodecs {
30 switch strings.ToLower(codec) {
31 case "av1":
32 preferredCodecs = append(preferredCodecs, "av01", "av1")
33 case "h264":
34 preferredCodecs = append(preferredCodecs, "h264", "avc1")
35 case "vp9":
36 preferredCodecs = append(preferredCodecs, "vp9", "vp09")
37 case "vp8":
38 preferredCodecs = append(preferredCodecs, "vp8", "vp08")
39 default:
40 preferredCodecs = append(preferredCodecs, strings.ToLower(codec))
41 }
42 }
43
44 blockedPattern := strings.Join(blockedCodecs, "|")
45 hasBlockedCodecs := len(blockedCodecs) > 0
46 hasPreferredCodecs := len(preferredCodecs) > 0
47
48 // Convert preferredCodecs slice to proper JavaScript array syntax
49 preferredCodecsJS := "[" + strings.Join(func() []string {
50 var quoted []string
51 for _, codec := range preferredCodecs {
52 quoted = append(quoted, `"`+codec+`"`)
53 }
54 return quoted
55 }(), ", ") + "]"
56
57 // Build JavaScript directly without format specifiers
58 var js strings.Builder
59
60 js.WriteString(`(() => {
61 'use strict';
62
63 const isMainFrame = (window.self === window.top);
64 function log(msg) {
65 if (isMainFrame) console.log('[dumber-codec] ' + msg);
66 }
67
68 log('Initializing codec control');
69
70 // Smart fullscreen handling that works with WebKit's native behavior
71 const videoFullscreenStates = new WeakMap();
72 let fullscreenTransitionInProgress = false;
73
74 // Minimal state tracking - only what's essential
75 function trackVideoForFullscreen(video) {
76 if (!video || video.tagName !== 'VIDEO') return;
77
78 // Only track if video has meaningful content
79 if (video.readyState < 2 || !video.duration) return;
80
81 const state = {
82 wasPlaying: !video.paused && !video.ended,
83 currentTime: video.currentTime,
84 playbackRate: video.playbackRate
85 };
86
87 videoFullscreenStates.set(video, state);
88 log('Tracking video for fullscreen - playing: ' + state.wasPlaying + ', time: ' + state.currentTime);
89 }
90
91 // Gentle recovery without forcing reloads
92 function recoverVideoAfterFullscreen(video) {
93 const state = videoFullscreenStates.get(video);
94 if (!state) return;
95
96 log('Recovering video after fullscreen transition');
97
98 // Only intervene if video is stuck in a loading state
99 const isStuck = video.readyState < 2 && video.networkState === 2; // NETWORK_LOADING but not ready
100 const hasLostTime = Math.abs(video.currentTime - state.currentTime) > 2;
101
102 if (isStuck || hasLostTime) {
103 log('Video appears stuck, applying gentle recovery');
104
105 // Gentle nudge - just try to resume if it was playing
106 if (state.wasPlaying && video.paused) {
107 setTimeout(() => {
108 if (video.readyState >= 2) {
109 video.play().catch(e => log('Play failed: ' + e));
110 }
111 }, 200);
112 }
113 }
114
115 videoFullscreenStates.delete(video);
116 }
117
118 // WebKit-aware fullscreen change detection
119 function handleWebKitFullscreenTransition() {
120 if (fullscreenTransitionInProgress) return;
121 fullscreenTransitionInProgress = true;
122
123 log('WebKit fullscreen transition detected');
124
125 // Skip fullscreen recovery on Twitch to prevent theater/fullscreen freezing
126 if (location.hostname.includes('twitch.tv')) {
127 log('Skipping fullscreen recovery on Twitch for stability');
128 fullscreenTransitionInProgress = false;
129 return;
130 }
131
132 const videos = document.querySelectorAll('video');
133 const isEntering = !!(document.fullscreenElement || document.webkitFullscreenElement);
134
135 videos.forEach(video => {
136 if (isEntering) {
137 trackVideoForFullscreen(video);
138 } else {
139 // Give WebKit time to complete its native fullscreen handling
140 setTimeout(() => recoverVideoAfterFullscreen(video), 500);
141 }
142 });
143
144 setTimeout(() => {
145 fullscreenTransitionInProgress = false;
146 }, 1000);
147 }
148
149 // Use passive listeners to avoid interfering with WebKit's native handling
150 document.addEventListener('fullscreenchange', handleWebKitFullscreenTransition, { passive: true });
151 document.addEventListener('webkitfullscreenchange', handleWebKitFullscreenTransition, { passive: true });
152
153 // Override canPlayType for HTMLMediaElement (primary codec detection)
154 const originalCanPlayType = HTMLMediaElement.prototype.canPlayType;
155 HTMLMediaElement.prototype.canPlayType = function(type) {
156 log('canPlayType called with: ' + type);
157
158 // Block unwanted codecs`)
159
160 if hasBlockedCodecs {
161 js.WriteString(`
162 const blockedRegex = /` + blockedPattern + `/i;
163 if (blockedRegex.test(type)) {
164 log('Blocking codec via canPlayType: ' + type);
165 return '';
166 }`)
167 }
168
169 if hasPreferredCodecs {
170 js.WriteString(`
171
172 // Boost preferred codecs
173 const preferredCodecs = ` + preferredCodecsJS + `;
174 for (const preferred of preferredCodecs) {
175 if (type.toLowerCase().includes(preferred)) {
176 log('Boosting preferred codec via canPlayType: ' + type);
177 return 'probably';
178 }
179 }`)
180 }
181
182 js.WriteString(`
183
184 // Call original for other codecs
185 return originalCanPlayType.call(this, type);
186 };
187
188 // Override MediaSource.isTypeSupported for more aggressive codec blocking
189 if (window.MediaSource && MediaSource.isTypeSupported) {
190 const originalIsTypeSupported = MediaSource.isTypeSupported.bind(MediaSource);
191 MediaSource.isTypeSupported = function(type) {
192 log('MediaSource.isTypeSupported called with: ' + type);`)
193
194 if hasBlockedCodecs {
195 js.WriteString(`
196
197 // Block unwanted codecs
198 const blockedRegex = /` + blockedPattern + `/i;
199 if (blockedRegex.test(type)) {
200 log('Blocking codec via MediaSource.isTypeSupported: ' + type);
201 return false;
202 }`)
203 }
204
205 if hasPreferredCodecs {
206 js.WriteString(`
207
208 // Boost preferred codecs
209 const preferredCodecs = ` + preferredCodecsJS + `;
210 for (const preferred of preferredCodecs) {
211 if (type.toLowerCase().includes(preferred)) {
212 log('Boosting preferred codec via MediaSource.isTypeSupported: ' + type);
213 return true;
214 }
215 }`)
216 }
217
218 js.WriteString(`
219
220 // Call original for other codecs
221 return originalIsTypeSupported(type);
222 };
223 }
224
225 // Override MediaCapabilities API
226 if (navigator.mediaCapabilities && navigator.mediaCapabilities.decodingInfo) {
227 const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo.bind(navigator.mediaCapabilities);
228
229 navigator.mediaCapabilities.decodingInfo = async function(config) {
230 const contentType = config.video?.contentType || config.type || '';
231
232 // Parse resolution from video config for resolution-aware decisions
233 let width = 0, height = 0;
234 if (config.video) {
235 width = config.video.width || 0;
236 height = config.video.height || 0;
237 }
238
239 // Determine if this is a high resolution request
240 const isHighRes = height >= 1440 || width >= 2560; // 1440p/1080p ultrawide+
241 const is4K = height >= 2160 || width >= 3840;
242 const is1080p60 = (height >= 1080 || width >= 1920) &&
243 (config.video?.framerate >= 50 || config.video?.frameRate >= 50);
244
245 log('MediaCapabilities query - contentType: ' + contentType +
246 ', resolution: ' + width + 'x' + height + ', highRes: ' + isHighRes + ', 4K: ' + is4K + ', 1080p60: ' + is1080p60);
247
248 // Block unwanted codecs`)
249
250 if hasBlockedCodecs {
251 js.WriteString(`
252 const blockedRegex = /` + blockedPattern + `/i;
253 if (blockedRegex.test(contentType)) {
254 log('Blocking codec: ' + contentType);
255 return {
256 supported: false,
257 smooth: false,
258 powerEfficient: false
259 };
260 }`)
261 }
262
263 js.WriteString(`
264
265 // Resolution-aware preferred codec handling`)
266
267 if hasPreferredCodecs {
268 js.WriteString(`
269 const preferredCodecs = ` + preferredCodecsJS + `;
270 for (const preferred of preferredCodecs) {
271 if (contentType.toLowerCase().includes(preferred)) {
272 log('Boosting preferred codec: ' + contentType);
273
274 // Resolution-aware capability reporting for AV1
275 if (preferred === 'av01' || preferred === 'av1') {
276 // AV1 handling based on resolution
277 if (is4K) {
278 // 4K AV1: supported but not smooth (encourage VP9 fallback)
279 return {
280 supported: true,
281 smooth: false,
282 powerEfficient: false
283 };
284 } else if (is1080p60) {
285 // 1080p60 AV1: supported but not power efficient
286 return {
287 supported: true,
288 smooth: true,
289 powerEfficient: false
290 };
291 } else if (isHighRes) {
292 // 1440p AV1: supported but not optimal
293 return {
294 supported: true,
295 smooth: false,
296 powerEfficient: true
297 };
298 } else {
299 // <= 1080p30 AV1: fully supported
300 return {
301 supported: true,
302 smooth: true,
303 powerEfficient: true
304 };
305 }
306 } else {
307 // Other preferred codecs (VP9, H264) - always report as capable
308 return {
309 supported: true,
310 smooth: true,
311 powerEfficient: true
312 };
313 }
314 }
315 }`)
316 }
317
318 js.WriteString(`
319
320 // Call original for other codecs
321 try {
322 return await originalDecodingInfo(config);
323 } catch (e) {
324 console.warn('[dumber-codec] MediaCapabilities error:', e);
325 return {
326 supported: false,
327 smooth: false,
328 powerEfficient: false
329 };
330 }
331 };
332 }
333
334 // YouTube-specific codec forcing
335 if (location.hostname.includes('youtube.com')) {
336 log('Applying YouTube codec preferences');
337
338 // Set YouTube's localStorage preferences for smart AV1 usage
339 // 2048 = prefer AV1 for lower resolutions, fallback to VP9 for higher res
340 // This allows YouTube to make smarter codec decisions based on our MediaCapabilities
341 try {
342 localStorage.setItem('yt-player-av1-pref', '2048');
343 Object.defineProperty(localStorage, 'yt-player-av1-pref', {
344 value: '2048',
345 writable: false,
346 configurable: false
347 });
348 log('YouTube: Set smart AV1 localStorage preference (2048)');
349 } catch (e) {
350 console.warn('[dumber-codec] YouTube: Failed to set localStorage:', e);
351 }
352
353 // Override ytInitialPlayerResponse parsing
354 Object.defineProperty(window, 'ytplayer', {
355 configurable: true,
356 get() {
357 return this._ytplayer || null;
358 },
359 set(player) {
360 if (player && player.config && player.config.args) {`)
361
362 if prefs.ForceAV1 {
363 js.WriteString(`
364 // Force AV1 if enabled
365 player.config.args.preferred_codecs = 'av01';
366 log('YouTube: Forced AV1 codec');`)
367 }
368
369 if hasPreferredCodecs {
370 js.WriteString(`
371 // Set codec preference order
372 const preferredCodecs = ` + preferredCodecsJS + `;
373 const codecOrder = preferredCodecs.map(c => {
374 switch(c.toLowerCase()) {
375 case 'av1': case 'av01': return 'av01';
376 case 'h264': case 'avc1': return 'avc1';
377 case 'vp9': case 'vp09': return 'vp9';
378 case 'vp8': case 'vp08': return 'vp8';
379 default: return c;
380 }
381 }).join(',');
382 player.config.args.preferred_codecs = codecOrder;
383 log('YouTube: Set codec order: ' + codecOrder);`)
384 }
385
386 js.WriteString(`
387 }
388 this._ytplayer = player;
389 }
390 });
391
392 // Intercept YouTube's format selection
393 const originalFetch = window.fetch;
394 window.fetch = function(input, init) {
395 if (typeof input === 'string' && input.includes('/youtubei/v1/player')) {
396 log('YouTube: Intercepting player request');`)
397
398 if hasPreferredCodecs {
399 js.WriteString(`
400
401 // Modify request to prefer our codecs
402 if (init && init.body) {
403 try {
404 const body = JSON.parse(init.body);
405 if (body.videoId) {
406 // Add codec preferences to the request
407 body.codecPreferences = ` + preferredCodecsJS + `;
408 init.body = JSON.stringify(body);
409 }
410 } catch (e) {
411 // Non-JSON body, ignore
412 }
413 }`)
414 }
415
416 js.WriteString(`
417 }
418
419 // Process YouTube player API responses for format manipulation
420 if (typeof input === 'string' && input.includes('/youtubei/v1/player')) {
421 return originalFetch.call(this, input, init).then(response => {
422 if (!response.ok) return response;
423
424 return response.text().then(text => {
425 try {
426 const data = JSON.parse(text);
427
428 // Manipulate streaming data if present
429 if (data.streamingData && data.streamingData.formats) {
430 log('YouTube: Processing format manifest');
431
432 // Sort formats to prioritize AV1 for lower resolutions, VP9 for higher
433 data.streamingData.formats.sort((a, b) => {
434 const aHeight = a.height || 0;
435 const bHeight = b.height || 0;
436 const aIsAV1 = a.mimeType && a.mimeType.includes('av01');
437 const bIsAV1 = b.mimeType && b.mimeType.includes('av01');
438 const aIsVP9 = a.mimeType && a.mimeType.includes('vp9');
439 const bIsVP9 = b.mimeType && b.mimeType.includes('vp9');
440
441 // For resolutions <= 1080p, prioritize AV1
442 if (Math.max(aHeight, bHeight) <= 1080) {
443 if (aIsAV1 && !bIsAV1) return -1;
444 if (!aIsAV1 && bIsAV1) return 1;
445 }
446 // For resolutions > 1080p, prioritize VP9
447 else {
448 if (aIsVP9 && !bIsVP9) return -1;
449 if (!aIsVP9 && bIsVP9) return 1;
450 }
451
452 return 0;
453 });
454
455 log('YouTube: Reordered formats for optimal codec selection');
456 }
457
458 return new Response(JSON.stringify(data), {
459 status: response.status,
460 statusText: response.statusText,
461 headers: response.headers
462 });
463 } catch (e) {
464 console.warn('[dumber-codec] YouTube: Failed to process response:', e);
465 return new Response(text, {
466 status: response.status,
467 statusText: response.statusText,
468 headers: response.headers
469 });
470 }
471 });
472 });
473 }
474
475 return originalFetch.call(this, input, init);
476 };
477 }
478
479 // Twitch: No codec interference - let Twitch handle codec selection natively
480 if (location.hostname.includes('twitch.tv')) {
481 log('Detected Twitch domain - no codec interference for stability');
482 // Twitch codec control completely removed to prevent theater/fullscreen freezing
483 }
484
485 // Passive video monitoring without interfering with element creation
486 const monitorExistingVideos = () => {
487 const videos = document.querySelectorAll('video');
488 videos.forEach(video => {
489 if (!video.__codecMonitored) {
490 video.__codecMonitored = true;
491 log('Found video element, adding passive monitoring');
492
493 // Passive codec detection only
494 video.addEventListener('canplay', function() {
495 log('Video can play - resolution: ' + this.videoWidth + 'x' + this.videoHeight);
496 }, { passive: true, once: true });
497 }
498 });
499 };
500
501 // Monitor for video elements periodically without overriding createElement
502 const videoObserver = new MutationObserver(monitorExistingVideos);
503
504 // Wait for document.body to be available before observing
505 function startVideoObserver() {
506 if (document.body) {
507 videoObserver.observe(document.body, { childList: true, subtree: true });
508 monitorExistingVideos(); // Check existing videos
509 log('Video observer started');
510 } else {
511 // Retry after a short delay if body not ready
512 setTimeout(startVideoObserver, 50);
513 }
514 }
515 startVideoObserver();
516
517 log('Codec control initialization complete');
518})();`)
519
520 return js.String()
521}