detached 27b965a

fix(twitch): remove codec interference preventing theater mode and fullscreen

commit
27b965a67f45536f3ebc8be3dff179a33d85fdc3
author
bamen · · 2025-09-12 10:39 UTC
parent
cf74e06
signature
unsigned
6 files · +51 -148
M CHANGELOG.md +4 -0
@@ -19,6 +19,10 @@ All notable changes to this project will be documented in this file.
1919 - **Ctrl+Shift+R** / **Cmd+Shift+R**: Hard reload (bypass cache)
2020 - **F5**: Alternative reload key
2121 - WebKit integration with proper `webkit_web_view_reload()` and `webkit_web_view_reload_bypass_cache()` calls
22+- **Twitch theater mode and fullscreen stability fix**:
23+ - **Complete removal of codec interference on Twitch**: Eliminates aggressive codec control that caused theater mode and fullscreen freezing
24+ - **Native Twitch codec selection**: Allows Twitch to handle codec negotiation without browser intervention
25+ - Fixes theater mode freeze and fullscreen black screen issues on Twitch
2226
2327 ### Fixed
2428 - Zoom level toast no longer shows on every page navigation within the same domain:
@@ -19,6 +19,10 @@ All notable changes to this project will be documented in this file.
19 - **Ctrl+Shift+R** / **Cmd+Shift+R**: Hard reload (bypass cache)19 - **Ctrl+Shift+R** / **Cmd+Shift+R**: Hard reload (bypass cache)
20 - **F5**: Alternative reload key20 - **F5**: Alternative reload key
21 - WebKit integration with proper `webkit_web_view_reload()` and `webkit_web_view_reload_bypass_cache()` calls21 - WebKit integration with proper `webkit_web_view_reload()` and `webkit_web_view_reload_bypass_cache()` calls
22+- **Twitch theater mode and fullscreen stability fix**:
23+ - **Complete removal of codec interference on Twitch**: Eliminates aggressive codec control that caused theater mode and fullscreen freezing
24+ - **Native Twitch codec selection**: Allows Twitch to handle codec negotiation without browser intervention
25+ - Fixes theater mode freeze and fullscreen black screen issues on Twitch
22 26
23 ### Fixed27 ### Fixed
24 - Zoom level toast no longer shows on every page navigation within the same domain:28 - Zoom level toast no longer shows on every page navigation within the same domain:
M internal/config/config.go +15 -10
@@ -146,6 +146,9 @@ type CodecConfig struct {
146146
147147 // Maximum resolution for AV1 codec (720p, 1080p, 1440p, 4k, unlimited)
148148 AV1MaxResolution string `mapstructure:"av1_max_resolution" yaml:"av1_max_resolution"`
149+
150+ // Site-specific codec control settings
151+ DisableTwitchCodecControl bool `mapstructure:"disable_twitch_codec_control" yaml:"disable_twitch_codec_control"`
149152 }
150153
151154 // Manager handles configuration loading, watching, and reloading.
@@ -234,16 +237,17 @@ func NewManager() (*Manager, error) {
234237
235238 // Codec preferences environment variable bindings
236239 codecEnvBindings := map[string]string{
237- "codec_preferences.preferred_codecs": "DUMBER_PREFERRED_CODECS",
238- "codec_preferences.force_av1": "DUMBER_FORCE_AV1",
239- "codec_preferences.block_vp9": "DUMBER_BLOCK_VP9",
240- "codec_preferences.block_vp8": "DUMBER_BLOCK_VP8",
241- "codec_preferences.av1_hardware_only": "DUMBER_AV1_HW_ONLY",
242- "codec_preferences.disable_vp9_hardware": "DUMBER_DISABLE_VP9_HW",
243- "codec_preferences.video_buffer_size_mb": "DUMBER_VIDEO_BUFFER_MB",
244- "codec_preferences.queue_buffer_time_sec": "DUMBER_QUEUE_BUFFER_SEC",
245- "codec_preferences.custom_user_agent": "DUMBER_CUSTOM_UA",
246- "codec_preferences.av1_max_resolution": "DUMBER_AV1_MAX_RES",
240+ "codec_preferences.preferred_codecs": "DUMBER_PREFERRED_CODECS",
241+ "codec_preferences.force_av1": "DUMBER_FORCE_AV1",
242+ "codec_preferences.block_vp9": "DUMBER_BLOCK_VP9",
243+ "codec_preferences.block_vp8": "DUMBER_BLOCK_VP8",
244+ "codec_preferences.av1_hardware_only": "DUMBER_AV1_HW_ONLY",
245+ "codec_preferences.disable_vp9_hardware": "DUMBER_DISABLE_VP9_HW",
246+ "codec_preferences.video_buffer_size_mb": "DUMBER_VIDEO_BUFFER_MB",
247+ "codec_preferences.queue_buffer_time_sec": "DUMBER_QUEUE_BUFFER_SEC",
248+ "codec_preferences.custom_user_agent": "DUMBER_CUSTOM_UA",
249+ "codec_preferences.av1_max_resolution": "DUMBER_AV1_MAX_RES",
250+ "codec_preferences.disable_twitch_codec_control": "DUMBER_DISABLE_TWITCH_CODEC",
247251 }
248252
249253 for key, env := range codecEnvBindings {
@@ -493,6 +497,7 @@ func (m *Manager) setDefaults() {
493497 m.viper.SetDefault("codec_preferences.queue_buffer_time_sec", defaults.CodecPreferences.QueueBufferTimeSec)
494498 m.viper.SetDefault("codec_preferences.custom_user_agent", defaults.CodecPreferences.CustomUserAgent)
495499 m.viper.SetDefault("codec_preferences.av1_max_resolution", defaults.CodecPreferences.AV1MaxResolution)
500+ m.viper.SetDefault("codec_preferences.disable_twitch_codec_control", defaults.CodecPreferences.DisableTwitchCodecControl)
496501
497502 // Rendering defaults
498503 m.viper.SetDefault("rendering_mode", string(RenderingModeAuto))
@@ -146,6 +146,9 @@ type CodecConfig struct {
146 146
147 // Maximum resolution for AV1 codec (720p, 1080p, 1440p, 4k, unlimited)147 // Maximum resolution for AV1 codec (720p, 1080p, 1440p, 4k, unlimited)
148 AV1MaxResolution string `mapstructure:"av1_max_resolution" yaml:"av1_max_resolution"`148 AV1MaxResolution string `mapstructure:"av1_max_resolution" yaml:"av1_max_resolution"`
149+
150+ // Site-specific codec control settings
151+ DisableTwitchCodecControl bool `mapstructure:"disable_twitch_codec_control" yaml:"disable_twitch_codec_control"`
149 }152 }
150 153
151 // Manager handles configuration loading, watching, and reloading.154 // Manager handles configuration loading, watching, and reloading.
@@ -234,16 +237,17 @@ func NewManager() (*Manager, error) {
234 237
235 // Codec preferences environment variable bindings238 // Codec preferences environment variable bindings
236 codecEnvBindings := map[string]string{239 codecEnvBindings := map[string]string{
237- "codec_preferences.preferred_codecs": "DUMBER_PREFERRED_CODECS",240+ "codec_preferences.preferred_codecs": "DUMBER_PREFERRED_CODECS",
238- "codec_preferences.force_av1": "DUMBER_FORCE_AV1",241+ "codec_preferences.force_av1": "DUMBER_FORCE_AV1",
239- "codec_preferences.block_vp9": "DUMBER_BLOCK_VP9",242+ "codec_preferences.block_vp9": "DUMBER_BLOCK_VP9",
240- "codec_preferences.block_vp8": "DUMBER_BLOCK_VP8",243+ "codec_preferences.block_vp8": "DUMBER_BLOCK_VP8",
241- "codec_preferences.av1_hardware_only": "DUMBER_AV1_HW_ONLY",244+ "codec_preferences.av1_hardware_only": "DUMBER_AV1_HW_ONLY",
242- "codec_preferences.disable_vp9_hardware": "DUMBER_DISABLE_VP9_HW",245+ "codec_preferences.disable_vp9_hardware": "DUMBER_DISABLE_VP9_HW",
243- "codec_preferences.video_buffer_size_mb": "DUMBER_VIDEO_BUFFER_MB",246+ "codec_preferences.video_buffer_size_mb": "DUMBER_VIDEO_BUFFER_MB",
244- "codec_preferences.queue_buffer_time_sec": "DUMBER_QUEUE_BUFFER_SEC",247+ "codec_preferences.queue_buffer_time_sec": "DUMBER_QUEUE_BUFFER_SEC",
245- "codec_preferences.custom_user_agent": "DUMBER_CUSTOM_UA",248+ "codec_preferences.custom_user_agent": "DUMBER_CUSTOM_UA",
246- "codec_preferences.av1_max_resolution": "DUMBER_AV1_MAX_RES",249+ "codec_preferences.av1_max_resolution": "DUMBER_AV1_MAX_RES",
250+ "codec_preferences.disable_twitch_codec_control": "DUMBER_DISABLE_TWITCH_CODEC",
247 }251 }
248 252
249 for key, env := range codecEnvBindings {253 for key, env := range codecEnvBindings {
@@ -493,6 +497,7 @@ func (m *Manager) setDefaults() {
493 m.viper.SetDefault("codec_preferences.queue_buffer_time_sec", defaults.CodecPreferences.QueueBufferTimeSec)497 m.viper.SetDefault("codec_preferences.queue_buffer_time_sec", defaults.CodecPreferences.QueueBufferTimeSec)
494 m.viper.SetDefault("codec_preferences.custom_user_agent", defaults.CodecPreferences.CustomUserAgent)498 m.viper.SetDefault("codec_preferences.custom_user_agent", defaults.CodecPreferences.CustomUserAgent)
495 m.viper.SetDefault("codec_preferences.av1_max_resolution", defaults.CodecPreferences.AV1MaxResolution)499 m.viper.SetDefault("codec_preferences.av1_max_resolution", defaults.CodecPreferences.AV1MaxResolution)
500+ m.viper.SetDefault("codec_preferences.disable_twitch_codec_control", defaults.CodecPreferences.DisableTwitchCodecControl)
496 501
497 // Rendering defaults502 // Rendering defaults
498 m.viper.SetDefault("rendering_mode", string(RenderingModeAuto))503 m.viper.SetDefault("rendering_mode", string(RenderingModeAuto))
M internal/config/defaults.go +11 -10
@@ -107,16 +107,17 @@ func DefaultConfig() *Config {
107107 LegacyVAAPI: false,
108108 },
109109 CodecPreferences: CodecConfig{
110- PreferredCodecs: "av1,h264", // AV1 first, but allow VP9 fallback for higher resolutions
111- ForceAV1: false, // Use smart AV1 negotiation instead of forcing
112- BlockVP9: false, // Allow VP9 for higher resolutions
113- BlockVP8: true, // Still block VP8 as it's outdated
114- AV1HardwareOnly: false, // Allow software AV1 fallback
115- DisableVP9Hardware: false, // Allow VP9 hardware for high res content
116- VideoBufferSizeMB: 16, // Larger buffer for AV1 streams
117- QueueBufferTimeSec: 10, // More buffering time for smooth playback
118- CustomUserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", // Chrome UA with AV1 support
119- AV1MaxResolution: "1080p", // Optimal AV1 up to 1080p, fallback to VP9 for higher res
110+ PreferredCodecs: "av1,h264", // AV1 first, but allow VP9 fallback for higher resolutions
111+ ForceAV1: false, // Use smart AV1 negotiation instead of forcing
112+ BlockVP9: false, // Allow VP9 for higher resolutions
113+ BlockVP8: true, // Still block VP8 as it's outdated
114+ AV1HardwareOnly: false, // Allow software AV1 fallback
115+ DisableVP9Hardware: false, // Allow VP9 hardware for high res content
116+ VideoBufferSizeMB: 16, // Larger buffer for AV1 streams
117+ QueueBufferTimeSec: 10, // More buffering time for smooth playback
118+ CustomUserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", // Chrome UA with AV1 support
119+ AV1MaxResolution: "1080p", // Optimal AV1 up to 1080p, fallback to VP9 for higher res
120+ DisableTwitchCodecControl: true, // Disable codec control on Twitch by default (prevents theater/fullscreen freezing)
120121 },
121122 RenderingMode: RenderingModeAuto,
122123 }
@@ -107,16 +107,17 @@ func DefaultConfig() *Config {
107 LegacyVAAPI: false,107 LegacyVAAPI: false,
108 },108 },
109 CodecPreferences: CodecConfig{109 CodecPreferences: CodecConfig{
110- PreferredCodecs: "av1,h264", // AV1 first, but allow VP9 fallback for higher resolutions110+ PreferredCodecs: "av1,h264", // AV1 first, but allow VP9 fallback for higher resolutions
111- ForceAV1: false, // Use smart AV1 negotiation instead of forcing111+ ForceAV1: false, // Use smart AV1 negotiation instead of forcing
112- BlockVP9: false, // Allow VP9 for higher resolutions112+ BlockVP9: false, // Allow VP9 for higher resolutions
113- BlockVP8: true, // Still block VP8 as it's outdated113+ BlockVP8: true, // Still block VP8 as it's outdated
114- AV1HardwareOnly: false, // Allow software AV1 fallback114+ AV1HardwareOnly: false, // Allow software AV1 fallback
115- DisableVP9Hardware: false, // Allow VP9 hardware for high res content115+ DisableVP9Hardware: false, // Allow VP9 hardware for high res content
116- VideoBufferSizeMB: 16, // Larger buffer for AV1 streams116+ VideoBufferSizeMB: 16, // Larger buffer for AV1 streams
117- QueueBufferTimeSec: 10, // More buffering time for smooth playback117+ QueueBufferTimeSec: 10, // More buffering time for smooth playback
118- CustomUserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", // Chrome UA with AV1 support118+ CustomUserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", // Chrome UA with AV1 support
119- AV1MaxResolution: "1080p", // Optimal AV1 up to 1080p, fallback to VP9 for higher res119+ AV1MaxResolution: "1080p", // Optimal AV1 up to 1080p, fallback to VP9 for higher res
120+ DisableTwitchCodecControl: true, // Disable codec control on Twitch by default (prevents theater/fullscreen freezing)
120 },121 },
121 RenderingMode: RenderingModeAuto,122 RenderingMode: RenderingModeAuto,
122 }123 }
M main.go +5 -4
@@ -455,10 +455,11 @@ func runBrowser() {
455455 },
456456 Memory: readMemoryConfigFromEnv(),
457457 CodecPreferences: webkit.CodecPreferencesConfig{
458- PreferredCodecs: strings.Split(cfg.CodecPreferences.PreferredCodecs, ","),
459- BlockedCodecs: buildBlockedCodecsList(cfg.CodecPreferences),
460- ForceAV1: cfg.CodecPreferences.ForceAV1,
461- CustomUserAgent: cfg.CodecPreferences.CustomUserAgent,
458+ PreferredCodecs: strings.Split(cfg.CodecPreferences.PreferredCodecs, ","),
459+ BlockedCodecs: buildBlockedCodecsList(cfg.CodecPreferences),
460+ ForceAV1: cfg.CodecPreferences.ForceAV1,
461+ CustomUserAgent: cfg.CodecPreferences.CustomUserAgent,
462+ DisableTwitchCodecControl: cfg.CodecPreferences.DisableTwitchCodecControl,
462463 },
463464 })
464465 if err != nil {
@@ -455,10 +455,11 @@ func runBrowser() {
455 },455 },
456 Memory: readMemoryConfigFromEnv(),456 Memory: readMemoryConfigFromEnv(),
457 CodecPreferences: webkit.CodecPreferencesConfig{457 CodecPreferences: webkit.CodecPreferencesConfig{
458- PreferredCodecs: strings.Split(cfg.CodecPreferences.PreferredCodecs, ","),458+ PreferredCodecs: strings.Split(cfg.CodecPreferences.PreferredCodecs, ","),
459- BlockedCodecs: buildBlockedCodecsList(cfg.CodecPreferences),459+ BlockedCodecs: buildBlockedCodecsList(cfg.CodecPreferences),
460- ForceAV1: cfg.CodecPreferences.ForceAV1,460+ ForceAV1: cfg.CodecPreferences.ForceAV1,
461- CustomUserAgent: cfg.CodecPreferences.CustomUserAgent,461+ CustomUserAgent: cfg.CodecPreferences.CustomUserAgent,
462+ DisableTwitchCodecControl: cfg.CodecPreferences.DisableTwitchCodecControl,
462 },463 },
463 })464 })
464 if err != nil {465 if err != nil {
M pkg/webkit/codec_control.go +11 -120
@@ -44,10 +44,7 @@ func GenerateCodecControlScript(prefs CodecPreferencesConfig) string {
4444 blockedPattern := strings.Join(blockedCodecs, "|")
4545 hasBlockedCodecs := len(blockedCodecs) > 0
4646 hasPreferredCodecs := len(preferredCodecs) > 0
47- firstPreferredCodec := ""
48- if len(preferredCodecs) > 0 {
49- firstPreferredCodec = preferredCodecs[0]
50- }
47+
5148
5249 // Convert preferredCodecs slice to proper JavaScript array syntax
5350 preferredCodecsJS := "[" + strings.Join(func() []string {
@@ -121,6 +118,13 @@ func GenerateCodecControlScript(prefs CodecPreferencesConfig) string {
121118
122119 console.log('[dumber-codec] WebKit fullscreen transition detected');
123120
121+ // Skip fullscreen recovery on Twitch to prevent theater/fullscreen freezing
122+ if (location.hostname.includes('twitch.tv')) {
123+ console.log('[dumber-codec] Skipping fullscreen recovery on Twitch for stability');
124+ fullscreenTransitionInProgress = false;
125+ return;
126+ }
127+
124128 const videos = document.querySelectorAll('video');
125129 const isEntering = !!(document.fullscreenElement || document.webkitFullscreenElement);
126130
@@ -468,114 +472,10 @@ func GenerateCodecControlScript(prefs CodecPreferencesConfig) string {
468472 };
469473 }
470474
471- // Twitch-specific codec control
475+ // Twitch: No codec interference - let Twitch handle codec selection natively
472476 if (location.hostname.includes('twitch.tv')) {
473- console.log('[dumber-codec] Applying Twitch codec preferences');
474-
475- // Intercept Twitch Enhanced Broadcasting API calls
476- const originalFetch = window.fetch;
477- window.fetch = function(input, init) {
478- const url = typeof input === 'string' ? input : input.url;
479-
480- // Intercept Enhanced Broadcasting API calls
481- if (url && url.includes('/api/channel/hls') || url.includes('/enhanced_broadcasting')) {
482- console.log('[dumber-codec] Twitch: Intercepting Enhanced Broadcasting API call');
483-
484- // Modify request to prefer AV1 if available
485- if (init && init.body) {
486- try {
487- const body = JSON.parse(init.body);
488- if (body.supported_codecs) {`)
489-
490- if prefs.ForceAV1 || hasPreferredCodecs {
491- js.WriteString(`
492- // Reorder supported codecs to prioritize AV1
493- const preferredOrder = ['av01', 'avc1', 'vp9', 'vp8'];
494- body.supported_codecs.sort((a, b) => {
495- const aIdx = preferredOrder.indexOf(a.toLowerCase()) !== -1 ?
496- preferredOrder.indexOf(a.toLowerCase()) : 999;
497- const bIdx = preferredOrder.indexOf(b.toLowerCase()) !== -1 ?
498- preferredOrder.indexOf(b.toLowerCase()) : 999;
499- return aIdx - bIdx;
500- });
501- console.log('[dumber-codec] Twitch: Reordered supported codecs:', body.supported_codecs);`)
502- }
503-
504- js.WriteString(`
505- }
506- init.body = JSON.stringify(body);
507- } catch (e) {
508- // Non-JSON body, ignore
509- }
510- }
511- }
512-
513- // Intercept HLS manifest requests for codec modification
514- if (url && (url.includes('.m3u8') || url.includes('/playlist/'))) {
515- return originalFetch.call(this, input, init).then(response => {
516- if (response.ok && response.headers.get('content-type')?.includes('application/vnd.apple.mpegurl')) {
517- return response.text().then(text => {`)
518-
519- if hasBlockedCodecs {
520- js.WriteString(`
521- // Remove blocked codecs from HLS manifest
522- const blockedCodecs = ` + preferredCodecsJS + `.join('|');
523- const blockedRegex = new RegExp('CODECS="[^"]*(' + blockedCodecs + ')[^"]*"', 'gi');
524- text = text.replace(blockedRegex, '');`)
525- }
526-
527- js.WriteString(`
528-
529- console.log('[dumber-codec] Twitch: Modified HLS manifest');
530- return new Response(text, {
531- status: response.status,
532- statusText: response.statusText,
533- headers: response.headers
534- });
535- });
536- }
537- return response;
538- });
539- }
540-
541- return originalFetch.call(this, input, init);
542- };
543-
544- // Override Twitch player codec selection
545- const originalDefineProperty = Object.defineProperty;
546- Object.defineProperty = function(obj, prop, descriptor) {
547- // Intercept video codec settings
548- if (prop === 'videoCodec' && descriptor.value) {`)
549-
550- if prefs.ForceAV1 && hasPreferredCodecs {
551- js.WriteString(`
552- if (true) {
553- descriptor.value = 'av01';
554- console.log('[dumber-codec] Twitch: Forced AV1 codec');
555- }`)
556- } else if hasPreferredCodecs && firstPreferredCodec != "" {
557- twitchCodec := firstPreferredCodec
558- switch strings.ToLower(firstPreferredCodec) {
559- case "av1", "av01":
560- twitchCodec = "av01"
561- case "h264", "avc1":
562- twitchCodec = "avc1"
563- default:
564- twitchCodec = firstPreferredCodec
565- }
566- js.WriteString(`
567- else if (true && "` + firstPreferredCodec + `" !== "") {
568- const twitchCodec = "` + twitchCodec + `";
569- descriptor.value = twitchCodec;
570- console.log('[dumber-codec] Twitch: Set preferred codec:', twitchCodec);
571- }`)
572- }
573-
574- js.WriteString(`
575- }
576-
577- return originalDefineProperty.call(this, obj, prop, descriptor);
578- };
477+ console.log('[dumber-codec] Detected Twitch domain - no codec interference for stability');
478+ // Twitch codec control completely removed to prevent theater/fullscreen freezing
579479 }
580480
581481 // Passive video monitoring without interfering with element creation
@@ -605,12 +505,3 @@ func GenerateCodecControlScript(prefs CodecPreferencesConfig) string {
605505 return js.String()
606506 }
607507
608-// convertConfigCodecsToWebKitCodecs converts config codec preferences to WebKit format
609-func convertConfigCodecsToWebKitCodecs(codecPrefs interface{}) CodecPreferencesConfig {
610- // This function will be called from the main browser service to convert
611- // the config.CodecConfig to webkit.CodecPreferencesConfig
612-
613- // We'll need to import the config package for this conversion
614- // For now, return empty config and implement the conversion later
615- return CodecPreferencesConfig{}
616-}
@@ -44,10 +44,7 @@ func GenerateCodecControlScript(prefs CodecPreferencesConfig) string {
44 blockedPattern := strings.Join(blockedCodecs, "|")44 blockedPattern := strings.Join(blockedCodecs, "|")
45 hasBlockedCodecs := len(blockedCodecs) > 045 hasBlockedCodecs := len(blockedCodecs) > 0
46 hasPreferredCodecs := len(preferredCodecs) > 046 hasPreferredCodecs := len(preferredCodecs) > 0
47- firstPreferredCodec := ""47+
48- if len(preferredCodecs) > 0 {
49- firstPreferredCodec = preferredCodecs[0]
50- }
51 48
52 // Convert preferredCodecs slice to proper JavaScript array syntax49 // Convert preferredCodecs slice to proper JavaScript array syntax
53 preferredCodecsJS := "[" + strings.Join(func() []string {50 preferredCodecsJS := "[" + strings.Join(func() []string {
@@ -121,6 +118,13 @@ func GenerateCodecControlScript(prefs CodecPreferencesConfig) string {
121 118
122 console.log('[dumber-codec] WebKit fullscreen transition detected');119 console.log('[dumber-codec] WebKit fullscreen transition detected');
123 120
121+ // Skip fullscreen recovery on Twitch to prevent theater/fullscreen freezing
122+ if (location.hostname.includes('twitch.tv')) {
123+ console.log('[dumber-codec] Skipping fullscreen recovery on Twitch for stability');
124+ fullscreenTransitionInProgress = false;
125+ return;
126+ }
127+
124 const videos = document.querySelectorAll('video');128 const videos = document.querySelectorAll('video');
125 const isEntering = !!(document.fullscreenElement || document.webkitFullscreenElement);129 const isEntering = !!(document.fullscreenElement || document.webkitFullscreenElement);
126 130
@@ -468,114 +472,10 @@ func GenerateCodecControlScript(prefs CodecPreferencesConfig) string {
468 };472 };
469 }473 }
470 474
471- // Twitch-specific codec control475+ // Twitch: No codec interference - let Twitch handle codec selection natively
472 if (location.hostname.includes('twitch.tv')) {476 if (location.hostname.includes('twitch.tv')) {
473- console.log('[dumber-codec] Applying Twitch codec preferences');477+ console.log('[dumber-codec] Detected Twitch domain - no codec interference for stability');
474- 478+ // Twitch codec control completely removed to prevent theater/fullscreen freezing
475- // Intercept Twitch Enhanced Broadcasting API calls
476- const originalFetch = window.fetch;
477- window.fetch = function(input, init) {
478- const url = typeof input === 'string' ? input : input.url;
479-
480- // Intercept Enhanced Broadcasting API calls
481- if (url && url.includes('/api/channel/hls') || url.includes('/enhanced_broadcasting')) {
482- console.log('[dumber-codec] Twitch: Intercepting Enhanced Broadcasting API call');
483-
484- // Modify request to prefer AV1 if available
485- if (init && init.body) {
486- try {
487- const body = JSON.parse(init.body);
488- if (body.supported_codecs) {`)
489-
490- if prefs.ForceAV1 || hasPreferredCodecs {
491- js.WriteString(`
492- // Reorder supported codecs to prioritize AV1
493- const preferredOrder = ['av01', 'avc1', 'vp9', 'vp8'];
494- body.supported_codecs.sort((a, b) => {
495- const aIdx = preferredOrder.indexOf(a.toLowerCase()) !== -1 ?
496- preferredOrder.indexOf(a.toLowerCase()) : 999;
497- const bIdx = preferredOrder.indexOf(b.toLowerCase()) !== -1 ?
498- preferredOrder.indexOf(b.toLowerCase()) : 999;
499- return aIdx - bIdx;
500- });
501- console.log('[dumber-codec] Twitch: Reordered supported codecs:', body.supported_codecs);`)
502- }
503-
504- js.WriteString(`
505- }
506- init.body = JSON.stringify(body);
507- } catch (e) {
508- // Non-JSON body, ignore
509- }
510- }
511- }
512-
513- // Intercept HLS manifest requests for codec modification
514- if (url && (url.includes('.m3u8') || url.includes('/playlist/'))) {
515- return originalFetch.call(this, input, init).then(response => {
516- if (response.ok && response.headers.get('content-type')?.includes('application/vnd.apple.mpegurl')) {
517- return response.text().then(text => {`)
518-
519- if hasBlockedCodecs {
520- js.WriteString(`
521- // Remove blocked codecs from HLS manifest
522- const blockedCodecs = ` + preferredCodecsJS + `.join('|');
523- const blockedRegex = new RegExp('CODECS="[^"]*(' + blockedCodecs + ')[^"]*"', 'gi');
524- text = text.replace(blockedRegex, '');`)
525- }
526-
527- js.WriteString(`
528-
529- console.log('[dumber-codec] Twitch: Modified HLS manifest');
530- return new Response(text, {
531- status: response.status,
532- statusText: response.statusText,
533- headers: response.headers
534- });
535- });
536- }
537- return response;
538- });
539- }
540-
541- return originalFetch.call(this, input, init);
542- };
543-
544- // Override Twitch player codec selection
545- const originalDefineProperty = Object.defineProperty;
546- Object.defineProperty = function(obj, prop, descriptor) {
547- // Intercept video codec settings
548- if (prop === 'videoCodec' && descriptor.value) {`)
549-
550- if prefs.ForceAV1 && hasPreferredCodecs {
551- js.WriteString(`
552- if (true) {
553- descriptor.value = 'av01';
554- console.log('[dumber-codec] Twitch: Forced AV1 codec');
555- }`)
556- } else if hasPreferredCodecs && firstPreferredCodec != "" {
557- twitchCodec := firstPreferredCodec
558- switch strings.ToLower(firstPreferredCodec) {
559- case "av1", "av01":
560- twitchCodec = "av01"
561- case "h264", "avc1":
562- twitchCodec = "avc1"
563- default:
564- twitchCodec = firstPreferredCodec
565- }
566- js.WriteString(`
567- else if (true && "` + firstPreferredCodec + `" !== "") {
568- const twitchCodec = "` + twitchCodec + `";
569- descriptor.value = twitchCodec;
570- console.log('[dumber-codec] Twitch: Set preferred codec:', twitchCodec);
571- }`)
572- }
573-
574- js.WriteString(`
575- }
576-
577- return originalDefineProperty.call(this, obj, prop, descriptor);
578- };
579 }479 }
580 480
581 // Passive video monitoring without interfering with element creation481 // Passive video monitoring without interfering with element creation
@@ -605,12 +505,3 @@ func GenerateCodecControlScript(prefs CodecPreferencesConfig) string {
605 return js.String()505 return js.String()
606 }506 }
607 507
608-// convertConfigCodecsToWebKitCodecs converts config codec preferences to WebKit format
609-func convertConfigCodecsToWebKitCodecs(codecPrefs interface{}) CodecPreferencesConfig {
610- // This function will be called from the main browser service to convert
611- // the config.CodecConfig to webkit.CodecPreferencesConfig
612-
613- // We'll need to import the config package for this conversion
614- // For now, return empty config and implement the conversion later
615- return CodecPreferencesConfig{}
616-}
M pkg/webkit/settings.go +5 -4
@@ -41,10 +41,11 @@ type Config struct {
4141
4242 // CodecPreferencesConfig holds codec preferences for WebKit media playback
4343 type CodecPreferencesConfig struct {
44- PreferredCodecs []string
45- BlockedCodecs []string
46- ForceAV1 bool
47- CustomUserAgent string
44+ PreferredCodecs []string
45+ BlockedCodecs []string
46+ ForceAV1 bool
47+ CustomUserAgent string
48+ DisableTwitchCodecControl bool
4849 }
4950
5051 // RenderingConfig controls hardware acceleration preferences.
@@ -41,10 +41,11 @@ type Config struct {
41 41
42 // CodecPreferencesConfig holds codec preferences for WebKit media playback42 // CodecPreferencesConfig holds codec preferences for WebKit media playback
43 type CodecPreferencesConfig struct {43 type CodecPreferencesConfig struct {
44- PreferredCodecs []string44+ PreferredCodecs []string
45- BlockedCodecs []string45+ BlockedCodecs []string
46- ForceAV1 bool46+ ForceAV1 bool
47- CustomUserAgent string47+ CustomUserAgent string
48+ DisableTwitchCodecControl bool
48 }49 }
49 50
50 // RenderingConfig controls hardware acceleration preferences.51 // RenderingConfig controls hardware acceleration preferences.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close