You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CefSharp.WinForms程序最小化后iframe停止播放的解决方法

解决CefSharp.WinForms最小化后iframe停止播放的问题

问题根源

当Windows窗体最小化或远程服务器会话断开时,系统会暂停窗口的UI渲染线程活动,即使开启WindowlessRenderingEnabled,WinForms控件的可见性依赖机制仍会导致浏览器进程被节流,进而触发目标网站的页面可见性检测(如document.hidden),暂停iframe内的媒体播放或交互逻辑。

解决方案组合

1. 切换到OffScreen渲染模式

OffScreen渲染不依赖WinForms控件的可见性,即使窗体隐藏,浏览器进程仍会持续运行。修改InitializeChromium方法:

private void InitializeChromium()
{
    if (!(Cef.IsInitialized ?? false))             
    {
        var settings = new CefSettings
        {
            WindowlessRenderingEnabled = true,
            PersistSessionCookies = true,
            // 启用后台模式,防止系统节流浏览器进程
            BackgroundMode = CefBackgroundMode.Enabled
        };

        userId = txtUsername.Text;
        string cachePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Elearning", "Cache_" + userId);
        settings.RootCachePath = cachePath;

        Cef.Initialize(settings);
    }

    // 创建OffScreen浏览器实例,无需添加到窗体控件
    browser = new ChromiumWebBrowser("http://example.com/login")
    {
        Visible = true // 强制保持浏览器活动状态
    };

    browser.IsBrowserInitializedChanged += OnBrowserInitialized;
    browser.LoadingStateChanged += OnLoadingStateChanged;
    browser.JavascriptMessageReceived += OnJavascriptMessageReceived;
}

2. 覆盖页面可见性检测API

多数网站会通过document.hidden或visibilityState判断页面是否可见,进而暂停内容。在浏览器初始化完成后注入脚本强制页面处于"可见"状态:

private void OnBrowserInitialized(object sender, EventArgs e)
{
    if (browser.IsBrowserInitialized)
    {
        browser.ExecuteScriptAsync(@"
            // 覆盖页面可见性属性
            Object.defineProperty(document, 'hidden', { value: false, writable: false });
            Object.defineProperty(document, 'visibilityState', { value: 'visible', writable: false });
            // 阻止可见性变化事件传播
            document.addEventListener('visibilitychange', e => e.stopImmediatePropagation(), true);
        ");
    }
}

3. 优化后台监控逻辑

移除原代码中后台线程循环调用CheckProgressBar的逻辑,改为在浏览器端注入持续运行的JS脚本,避免线程同步问题:

private void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
{
    if (!e.IsLoading)
    {
        Invoke(new Action(() =>
        {
            if (browser.Address.Contains("scorm/player.php"))
            {
                txtUsername.Visible = false;
                txtPassword.Visible = false;
                btnLogin.Visible = false;
                // 注入一次持续监控脚本
                InjectProgressMonitorScript();
            }
            else
            {
                CheckForLogin();
            }
        }));
    }
}

private void InjectProgressMonitorScript()
{
    string script = @"
        function checkProgressBar() {
            const iframe = document.getElementById('scorm_object');
            if (!iframe) {
                setTimeout(checkProgressBar, 1000);
                return;
            }

            const innerDoc = iframe.contentDocument || iframe.contentWindow.document; 
            const progressLabel = innerDoc.querySelector('.progressbar__label'); 
            const timeLabel = innerDoc.querySelector('.progressbar__label_type_time'); 
            const preloader = innerDoc.querySelector('.preloader'); 

            if (preloader && preloader.style.display !== 'none') {
                window.CefSharp.PostMessage('Session has expired!');
                return;
            }
            
            let totalProgresscheck = 10; 
            let checkInterval = 1000;

            if (progressLabel) {
                const progressValue = progressLabel.getAttribute('aria-label');
                if (progressValue) {
                    const [completedProgress, totalProgress] = progressValue.trim().split(' / '); 

                    if (timeLabel) {
                        const timeValue = timeLabel.getAttribute('aria-label');
                        if (timeValue) {
                            const [completedTime, totalTime] = timeValue.trim().split(' / '); 
                            totalProgresscheck = totalTime; 

                            if (completedProgress !== totalProgress && completedTime === totalTime) {
                                setTimeout(() => {
                                    const nextButton = innerDoc.querySelector('.universal-control-panel__button_next'); 
                                    const playButton = innerDoc.querySelector('.universal-control-panel__button_play-pause');

                                    if (nextButton && playButton.getAttribute('aria-pressed') === 'false') {
                                        nextButton.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
                                        nextButton.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true }));
                                    }
                                }, 1000);
                            } else if (completedProgress == totalProgress && completedTime === totalTime) {
                                  setTimeout(() => window.CefSharp.PostMessage('Next'), 3000);
                            }
                        }
                    }
                }
            }

            // 计算下次检查间隔
            if (totalProgresscheck) {
                let totalSeconds = !isNaN(totalProgresscheck) 
                    ? parseInt(totalProgresscheck, 10) 
                    : totalProgresscheck.split(':').map(Number).reduce((m,s) => m*60 + s);
                checkInterval = (totalSeconds / 10) * 1000; 
            }

            setTimeout(checkProgressBar, checkInterval); 
        }

        checkProgressBar();
    ";

    if (browser != null && !browser.IsDisposed && browser.IsBrowserInitialized)
    {
        browser.ExecuteScriptAsync(script);
    }
}

// 修改消息接收处理逻辑
private void OnJavascriptMessageReceived(object sender, JavascriptMessageReceivedEventArgs e)
{
    string resultMessage = e.Message.ToString();
    Invoke(new Action(() => {
        if (resultMessage.Contains("Session has expired!"))
        {
            MessageBox.Show(this, resultMessage, "Notification", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            Application.Restart();
        }
        else if (resultMessage.Contains("Next"))
        {
            nextSlide();
        }
    }));
}

4. 调整窗体最小化行为

如果仍需保留WinForms窗体,可将最小化时的窗体移至屏幕外,避免系统暂停UI线程:

private void Form1_Resize(object sender, EventArgs e)
{
    if (this.WindowState == FormWindowState.Minimized)
    {
        // 将窗体移至屏幕外,保持可见性但不显示
        this.Location = new Point(-10000, -10000);
        // 强制浏览器保持焦点
        browser.GetBrowser().Host.SetFocus(true);
    }
}

内容的提问来源于stack exchange,提问作者QNJOONG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 09:12:03