AIGC标识 折腾笔记[63]-电视自动播放软件

摘要

本文记录如何从零造一个"插上就能放电视"的软件 ThreeStarTV,解决长辈不会操作网络电视的问题:Windows 电脑开机后自动全屏、带声音播放 CCTV-1 高清直播,全程零操作。软件基于 .NET 8 WinForms + WebView2,内嵌 hls.js 播放 m3u8 直播流,直播流连续失败时自动回退央视网网页播放器并自动点击"全屏",最终由 .NET Framework 4.8 启动器内嵌 .NET 8 运行时与主程序,分发为单个便携式 exe,完全离线安装运行。

声明

本文人类为第一作者, 龙虾为通讯作者. 本文有AI生成内容.

工程仓库

简介

本软件脱胎于一个相机 HMI 查看器项目(多路 WebView2 网格、主题切换、中英文、配置持久化等基础设施直接复用),将每一路的"相机画面"换成了"电视直播"。核心诉求只有一个:长辈按一下开机键,电视自己响起来

m3u8 简介

m3u8 是 HLS(HTTP Live Streaming,Apple 提出的自适应码率流媒体协议)使用的播放列表文件格式,本质是一个 UTF-8 文本清单:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:175835760
#EXTINF:2.000,
/live/cctv1/20260920/175835760_0000.ts
#EXTINF:2.000,
/live/cctv1/20260920/20260920_0001.ts
...

播放器不断轮询 m3u8 清单、按序下载 .ts 视频分片并拼接播放,即可实现低延迟直播。央视各频道的直播源本质上就是一个个 m3u8 地址,例如本文使用的 CCTV-1 高清流:

https://ldncctvwbcdbd.a.bdydns.com/ldncctvwbcd/cdrmldcctv1_1/index.m3u8

m3u8 的痛点在于浏览器兼容性:Safari 和 iOS 全系原生支持,但 Windows 上的 Chromium(Chrome / Edge / WebView2)不支持原生播放 HLS——<video> 标签直接指向 m3u8 只会报错。这正是需要 hls.js 的原因。

hls.js 简介

[https://github.com/video-dev/hls.js]

hls.js 是流传最广的 HLS 纯 JS 播放器:用 JavaScript 实现完整的 m3u8 解析、分片下载、TS 解封装与 MSE(Media Source Extensions)喂帧,把"浏览器不会播的 HLS"变成"浏览器原生会播的 MP4 流"。它不依赖任何浏览器插件,一条 <script> 引入即可工作:

if (Hls.isSupported()) {
  var hls = new Hls();
  hls.loadSource('https://example.com/live/index.m3u8');
  hls.attachMedia(videoElement);
}

对本项目还有两个关键性质:

  • 可内嵌分发:hls.js 是单文件 UMD 库(约 600KB),可以作为嵌入资源打进 exe,运行时 NavigateToString 注入播放器页面,不依赖任何 CDN,离线也能放;
  • 错误事件完备Hls.Events.ERROR 区分网络错误/媒体错误/其他致命错误,data.fatal 标记是否不可恢复,为"失败自动回退"提供了钩子。

WebView2 简介

WebView2 是微软基于 Chromium(Edge 同款内核)提供的 Windows 原生控件,可在 WinForms/WPF 应用中嵌入一个完整的现代浏览器。本项目所有播放能力都建立在 WebView2 之上:

  • 播放 m3u8:加载内嵌 hls.js 的播放器 HTML 页;
  • 回退播放:直接导航央视网直播网页(tv.cctv.com/live/cctv1);
  • 自动全屏、自动取消静音、加载计时:全部通过 WebView2 的脚本注入与 chrome.webview 消息通道完成。

它的两个特性对本项目至关重要:

  • 启动参数可定制--autoplay-policy=no-user-gesture-required 解除 Chromium 的自动播放限制,实现"开机即有声音";
  • 用户数据目录可持久化:所有格子共享一个环境实例后,央视网页面的 JS/CSS 等静态依赖第二次启动起直接走本地 HTTP 缓存,显著加快出画面速度。

央视直播网址

央视网官方直播页规律非常整齐,tv.cctv.com/live/cctvX/ 即 CCTV-X 的网页播放器:

频道 网页播放器 说明
CCTV-1 综合 https://tv.cctv.com/live/cctv1/ 本文默认频道
CCTV-2 财经 https://tv.cctv.com/live/cctv2/ 依次类推至 CCTV-13
CCTV-13 新闻 https://tv.cctv.com/live/cctv13/ 网址收藏中已内置 1~13

软件内置的"网址收藏"默认包含 CCTV-1 的 m3u8 推流地址与 CCTV-1~13 的网页地址,双击可复制到每一路播放格。

工程

1. 内嵌 hls.js 播放器页(m3u8 → 画面)

CameraCell.Navigate() 检测到地址以 .m3u8 结尾时,不再直接导航(Chromium 播不了),而是 NavigateToString 一个内嵌播放器页;hls.js 在编译期作为嵌入资源烘焙进 exe,运行时读取注入:

ThreeStarTV/CameraCell.cs(播放器页构造,节选)

private static string LoadEmbeddedScript(string suffix)
{
    var asm = System.Reflection.Assembly.GetExecutingAssembly();
    foreach (var name in asm.GetManifestResourceNames())
    {
        if (name.EndsWith(suffix))
        {
            using var s = asm.GetManifestResourceStream(name);
            using var r = new System.IO.StreamReader(s, System.Text.Encoding.UTF8);
            return r.ReadToEnd();
        }
    }
    return "";
}

// WebView2(Chromium) 不支持原生播放 HLS,用内嵌的 hls.js 包装 m3u8 地址(无需外网加载播放器)
private static string BuildHlsPlayerHtml(string streamUrl)
{
    var jsonUrl = System.Text.Json.JsonSerializer.Serialize(streamUrl);
    var jsonFallback = System.Text.Json.JsonSerializer.Serialize(ConfigService.FallbackWebUrl);
    var mutedAttr = ConfigService.App.autoUnmute ? "" : " muted";
    var unmuteJs = ConfigService.App.autoUnmute
        ? "v.addEventListener('canplay', function(){ v.muted = false; });"
        : "";
    return @"<!DOCTYPE html><html><head><meta charset=""utf-8"">
<style>html,body{margin:0;height:100%;background:#000;overflow:hidden}video{width:100%;height:100%}</style>
<script>" + LoadHlsJsScript() + @"</script>
</head><body><video id=""v"" autoplay playsinline" + mutedAttr + @"></video><script>
var url = " + jsonUrl + @";
var fallbackUrl = " + jsonFallback + @";
var retries = 0;
var v = document.getElementById('v');
function fail(){
  retries++;
  if (retries >= 3) { location.replace(fallbackUrl); return; } // 连续失败则回退到央视网网页
  setTimeout(function(){ location.reload(); }, 3000);
}
function start(){
  if (window.Hls && Hls.isSupported()) {
    var h = new Hls({ liveSyncDurationCount: 3 });
    h.loadSource(url); h.attachMedia(v);
    h.on(Hls.Events.ERROR, function(e, data){
      if (data && data.fatal) fail();
    });
  } else if (v.canPlayType('application/vnd.apple.mpegurl')) {
    v.src = url;
  } else {
    fail();
  }
}
" + unmuteJs + @"
v.addEventListener('error', fail);
v.addEventListener('playing', function(){ try { chrome.webview.postMessage({__player:'playing'}); } catch(e){} });
start();
</script></body></html>";
}

几个关键点:

  • 零 CDN 依赖:hls.js 内嵌后,播放器页完全自包含,弱网/离线(有流地址可达的前提下)都能播;
  • 自动取消静音:Chromium 要求自动播放必须静音,因此 <video>muted 起播,监听 canplay 后解除静音;配合 WebView2 启动参数 --autoplay-policy=no-user-gesture-required,实现开机即有声;
  • 直播低延迟liveSyncDurationCount: 3 让播放点只落后直播边缘 3 个分片;
  • 起播通知playing 事件通过 chrome.webview.postMessage 通知 C# 侧隐藏加载遮罩。

2. 失败自动回退央视网网页播放器

直播 CDN 地址不是永久的,可能挂掉或地域不可用。播放器页内置重试与回退逻辑:致命错误/视频错误重试 2 次(间隔 3 秒),第 3 次仍失败则 location.replace 跳转到央视网 CCTV-1 网页播放器——网页播放器是官方页面,稳定性远高于裸流地址:

function fail(){
  retries++;
  if (retries >= 3) { location.replace(fallbackUrl); return; } // 连续失败则回退到央视网网页
  setTimeout(function(){ location.reload(); }, 3000);
}

回退页加载完成后,还需要帮长辈点掉"全屏"按钮——见下一节。

3. 央视网回退页自动全屏

央视网网页播放器的全屏按钮要等播放器渲染出来才存在,且不同期页面 class 名有变化。注入脚本每秒扫描一次 DOM,找到 title/class/id 含"全屏"或"full"的可见元素即点击一次,然后自我注销:

ThreeStarTV/CameraCell.csAutoFullscreenScriptAddScriptToExecuteOnDocumentCreated 注入,仅 cctv.com 域名生效)

(function(){
  if (/cctv\.com$|\.cctv\.com$/.test(location.hostname)) {
    var timer = setInterval(function(){
      try {
        var els = document.querySelectorAll('[title*="全屏"], [class*="full"], [id*="full"]');
        for (var i = 0; i < els.length; i++) {
          var b = els[i], t = (b.title || '') + ' ' + (b.className || '') + ' ' + (b.id || '');
          if (/全屏|full/i.test(t) && b.offsetParent !== null) { b.click(); clearInterval(timer); return; }
        }
      } catch (e) {}
    }, 1000);
  }
})();

offsetParent !== null 用于过滤隐藏元素,避免点到页面里同名的不可见占位节点。

4. 开机启动与启动设置(零操作的核心)

四个启动设置全部持久化到 AppConfig.json:自动取消静音(默认开)、开机启动、窗口置顶、启动时最大化。开机启动通过在 shell:startup 目录维护快捷方式实现,只创建/只删除指向本程序的快捷方式,绝不碰用户自己的其他快捷方式:

ThreeStarTV/AutoStartService.cs(节选)

public static void Enable()
{
    if (IsEnabled()) return; // 已存在且指向本程序,不重复创建
    CreateShortcut(ShortcutPath, Application.ExecutablePath);
}

public static void Disable()
{
    if (!File.Exists(ShortcutPath)) return;
    // 只删除指向本程序的快捷方式,用户自建的其他快捷方式不动
    if (string.Equals(GetShortcutTarget(ShortcutPath),
        Application.ExecutablePath, StringComparison.OrdinalIgnoreCase))
    {
        File.Delete(ShortcutPath);
    }
}

首次启动时若无配置文件,自动生成"1 路、CCTV-1 m3u8、无加载延迟"的默认配置(ConfigService.Load()),即装即播;App.Main() 启动时调用 AutoStartService.SyncWithConfig() 自动修复快捷方式与配置不一致的情况。

5. 启动提速:共享 WebView2 环境 + 预热预取 + 加载遮罩

长辈的耐心以秒计,启动速度是硬指标。三层优化叠加:

共享环境:全部播放格共享一个 CoreWebView2Environment,用户数据目录默认持久化——央视网页面的 JS/CSS 等静态依赖第二次启动起直接走本地 HTTP 缓存,不再重复下载:

private static async Task<CoreWebView2Environment> GetSharedEnvAsync()
{
    if (sharedEnv == null)
    {
        var options = new CoreWebView2EnvironmentOptions(
            additionalBrowserArguments: "--autoplay-policy=no-user-gesture-required");
        sharedEnv = await CoreWebView2Environment.CreateAsync(null, null, options);
    }
    return sharedEnv;
}

启动预热App.Main() 后台提前请求直播流清单与央视网回退页,完成 DNS 解析与 TLS 握手(系统 DNS 缓存 Chromium 可复用),首个画面的等待显著缩短:

private static void WarmupPrefetch()
{
    Task.Run(async () =>
    {
        using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
        foreach (var url in new[] { ConfigService.DefaultStreamUrl, ConfigService.FallbackWebUrl })
        {
            using var req = new HttpRequestMessage(HttpMethod.Get, url);
            req.Headers.UserAgent.ParseAdd("Mozilla/5.0");
            using var resp = await client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
            _ = await resp.Content.ReadAsByteArrayAsync(); // 完整读取以复用/关闭连接
        }
    });
}

加载遮罩:暗色遮罩 + 进度条 + "加载中 xxx ms" 实时计时(100ms 刷新,伪进度条按用时推进,封顶 90%);m3u8 页等到播放器 playing 消息、普通网页导航完成即隐藏,60 秒超时兜底,避免永久遮挡。

6. 单文件分发:.NET 4.8 启动器内嵌运行时与主程序

长辈的电脑上不可能预装 .NET 8。解决方案是一个 .NET Framework 4.8(Win10/11 系统自带)写的启动器外壳:检测 Microsoft.WindowsDesktop.App 8.x 运行时 → 缺失则静默安装内嵌的运行时安装包(/install /quiet /norestart,完全离线)→ 将内嵌的主程序 exe 释放到 %LOCALAPPDATA%\ThreeStarTV\App\ 并启动。主程序与运行时安装包都以嵌入资源形式打进启动器,最终产物只有一个 exe:

ThreeStarTV.Launcher/Program.cs(流程骨架)

private const string RuntimeResource = "DotNet8Runtime.exe";
private const string AppResource = "ThreeStarTV.exe";

// 1. 运行时检测:dotnet --list-runtimes 输出包含 Microsoft.WindowsDesktop.App 8.
// 2. 缺失则静默安装内嵌安装包(10 分钟超时兜底)
int code = await InstallRuntimeAsync(form);

// 3. 释放并启动主程序
string appDir = Path.Combine(
    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
    "ThreeStarTV", "App");
ExtractResource(AppResource, Path.Combine(appDir, "ThreeStarTV.exe"));
Process.Start(new ProcessStartInfo { FileName = appPath, WorkingDirectory = appDir, UseShellExecute = true });

释放主程序前若文件被占用(上次崩溃的残留进程),先结束 ThreeStarTV 进程再重试删除,最多 4 次——这是实践踩出的坑:崩溃后重启曾提示"访问被拒绝"。

7. 构建

需要 .NET SDK 8+ 与 MSBuild(Visual Studio 或 .NET Framework 4.8 自带):

bash build-with-timestamp.sh

build-with-timestamp.sh(流程)

# [1/3] 发布主程序(net8.0-windows,win-x64 单文件,框架依赖)
dotnet publish ThreeStarTV/ThreeStarTV.csproj -c Release -r win-x64 --self-contained false \
  -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o publish/app

# [2/3] 编译启动器(内嵌运行时安装包与主程序两个资源)
MSBuild.exe ThreeStarTV.Launcher/ThreeStarTV.Launcher.csproj //p:Configuration=Release

# [3/3] 输出便携式单文件 dist/ThreeStarTV_<yyyyMMddHHmm>.exe

产物 dist/ThreeStarTV_<时间戳>.exe 直接拷贝到目标机器即可运行。

常见问题

Q: 首次运行黑屏/提示需要安装 WebView2?

A: 主程序依赖 WebView2 运行时。Win10 2004+ / Win11 一般已预装;精简版系统可能缺失。首次运行需联网下载,或另行部署 WebView2 离线包(Evergreen Standalone Installer)。

Q: 直播流地址失效了怎么办?

A: 播放器页连续 3 次失败会自动回退央视网网页播放器(见第 2 节),通常无感知。若长期播放央视网页,可在 设置 → 网址收藏 里找到新的 m3u8 地址替换,或在每一路格子直接改地址;CCTV-1~13 的网页地址收藏中均已内置。

Q: 回退到央视网页后没有自动全屏?

A: 自动全屏脚本依赖页面 DOM 中"全屏"按钮的实际渲染(约需几秒到十几秒),期间画面已是正常播放状态,只是带网页边框。若央视改版导致按钮特征变化,脚本的匹配规则(title/class/id 含"全屏/full")需要同步更新。

Q: 开机后没声音 / 没有最大化?

A: 检查 设置 → 软件设置 → 启动设置 中的"自动取消静音"与"启动时最大化"是否开启(默认开)。无声的另一个常见原因是系统音量合成器里 WebView2 被单独静音。

Q: 杀毒软件报毒?

A: 启动器会静默安装运行时、写 %LOCALAPPDATA% 并拉起子进程,部分杀软会拦截。添加信任即可;运行时安装包为微软官方原版。

Q: 如何改成默认播放其他频道?

A: 首次启动前删除配置目录(便携模式为 exe 旁 Configs/,否则为 %LOCALAPPDATA%\ThreeStarTV\)下的 CameraConfig.json,或直接在格子中把地址改为目标频道的 m3u8/网页地址并锁定。

运行效果图

实拍环境:Windows 虚拟机,软件启动后回退至央视网网页播放器自动全屏播放 CCTV-1。

运行截图1 运行截图2
screenshot1 screenshot2
posted @ 2026-09-20 20:54  qsBye  阅读(12)  评论(0)    收藏  举报