网络拓扑图

网络接口配置
- 进入网络-接口-设备 配置, 对 br-lan 进行编辑, 去掉网桥端口 lan3 (因为要和 wan 进行桥接)
- 新建设备 my-br, 选择设备 wan 和 lan 3
- 新建接口 iptv, 设备选择刚才创建的 my-br, 协议选择静态IP 地址, 输入一个和现在内网网段不冲突的网段地址,我这里是 192.168.67.1/24
udpxy 组播转单播
openwrt 安装 luci-app-udpxy, 配置如下:
- Bind IP/Interface: 0.0.0.0
- 端口: 4022
- Source IP/Interface: 192.168.67.1
- 已启用: 勾选
确认服务成功运行可以访问 host:port/status 查看状态
抓包
opkg 安装 tcpdump, 从机顶盒开机开始抓包,抓包命令
| 1
 | tcpdump -i iptv -s 0 -w /tmp/iptv.pcap
 | 
将文件下载到本地, 参考这篇文章的教程提取出 frameset_builder.jsp 文件 (注意中文编码的转换)
https://post.smzdm.com/p/a785k2z9/  
(frameset_builder.jsp 里面有 rtsp 的单播地址,可以打开直接观看,只要是电信网络环境就可以,只不过参数很长,还有一些身份和认证的参数,不好公开分享)  
再使用下面的网页将组播地址批量替换成单播地址
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 
 | <!DOCTYPE html><html>
 <head>
 <meta charset="UTF-8">
 <title>FrameSet Builder 分析工具</title>
 </head>
 <body>
 <h2>上传 frameset_builder.jsp 文件</h2>
 <input type="file" id="fileInput" accept=".jsp">
 
 <h3>可选参数</h3>
 <label for="localIp">本地 IP: </label>
 <input type="text" id="localIp" placeholder="例如 192.168.11.1">
 <br>
 <label for="portInput">端口号: </label>
 <input type="text" id="portInput" placeholder="例如 8888">
 
 <br><br>
 <button id="processButton">分析文件</button>
 
 <h3>分析结果</h3>
 <pre id="output"></pre>
 
 <h3>下载选项</h3>
 <label>
 <input type="radio" name="downloadFormat" value="txt" checked> iptv.txt
 </label>
 <label>
 <input type="radio" name="downloadFormat" value="m3u"> iptv.m3u
 </label>
 <br><br>
 <button id="downloadButton">下载文件</button>
 
 <script>
 
 let records = [];
 
 document.getElementById('processButton').addEventListener('click', function() {
 const fileInput = document.getElementById('fileInput');
 if (!fileInput.files || fileInput.files.length === 0) {
 alert('请先选择一个文件');
 return;
 }
 const file = fileInput.files[0];
 const reader = new FileReader();
 
 reader.onload = function(e) {
 try {
 const content = e.target.result;
 
 records = [];
 
 const regex = /ChannelName="([^"]+)"\s*,\s*UserChannelID="([^"]+)"\s*,\s*ChannelURL="([^"]+)"/g;
 let match;
 
 
 const localIp = document.getElementById('localIp').value.trim();
 const port = document.getElementById('portInput').value.trim();
 
 
 const ipRegex = /^(25[0-5]|2[0-4]\d|[01]?\d?\d)(\.(25[0-5]|2[0-4]\d|[01]?\d?\d)){3}$/;
 const ipValid = localIp && ipRegex.test(localIp);
 let portValid = false;
 if (port && /^\d+$/.test(port)) {
 const portNum = parseInt(port, 10);
 if (portNum >= 1 && portNum <= 65535) {
 portValid = true;
 }
 }
 
 const transform = ipValid && portValid;
 
 while ((match = regex.exec(content)) !== null) {
 let channelName = match[1];
 let channelUrl = match[3];
 if (transform && channelUrl.startsWith("igmp://")) {
 
 const rest = channelUrl.substring("igmp://".length);
 channelUrl = "http://" + localIp + ":" + port + "/udp/" + rest;
 }
 records.push({ channelName, channelUrl });
 }
 
 
 
 
 records.sort((a, b) => {
 const getRank = name => {
 const upper = name.toUpperCase();
 return (upper.includes("HD") || upper.includes("4K")) ? 0 : 1;
 };
 const rankA = getRank(a.channelName);
 const rankB = getRank(b.channelName);
 if (rankA !== rankB) {
 return rankA - rankB;
 }
 return b.channelName.localeCompare(a.channelName);
 });
 
 let outputStr = "";
 if (records.length === 0) {
 outputStr = "未找到匹配的记录。";
 } else {
 records.forEach(record => {
 outputStr += record.channelName + "," + record.channelUrl + "\n";
 });
 }
 document.getElementById('output').textContent = outputStr;
 } catch (error) {
 console.error("解析文件时出错:", error);
 document.getElementById('output').textContent = "解析文件时出错。";
 }
 };
 
 reader.onerror = function(e) {
 alert("读取文件出错");
 };
 
 
 reader.readAsText(file, "UTF-8");
 });
 
 document.getElementById('downloadButton').addEventListener('click', function() {
 if (records.length === 0) {
 alert("没有可下载的内容,请先分析文件。");
 return;
 }
 const format = document.querySelector('input[name="downloadFormat"]:checked').value;
 let content = "";
 let filename = "";
 
 if (format === "txt") {
 
 records.forEach(record => {
 content += record.channelName + "," + record.channelUrl + "\n";
 });
 filename = "iptv.txt";
 } else if (format === "m3u") {
 
 content = "#EXTM3U\n";
 records.forEach(record => {
 content += "#EXTINF:-1," + record.channelName + "\n" + record.channelUrl + "\n";
 });
 filename = "playlist.m3u";
 }
 
 const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
 const url = URL.createObjectURL(blob);
 const a = document.createElement("a");
 a.href = url;
 a.download = filename;
 document.body.appendChild(a);
 a.click();
 document.body.removeChild(a);
 URL.revokeObjectURL(url);
 });
 </script>
 </body>
 </html>
 
 | 
本地 IP 和 本地端口填写 udpxy 的配置
m3u文件
最后整理成 m3u 文件 playlist.m3u
然后就可以到电视端或者手机端导入观看了
总结
关于组播
- 路由设置好组播放功能,直接用播放器播放此格式源 rtp://225.1.4.73:1102 就能本地网能任意观看
- 路由设置好组播放功能,用播放器播放此格式源http://192.168.1.1:2222/rtp/225.1.4.73:1102 就能本地网能任意观看。
- 路由设置好组播放功能并有公网IP或区域网名,可通花生壳等工具做成外网,即就可以在有网络下,在哪都能观看,不受限与本地网络(家)
(https://www.right.com.cn/forum/thread-8261285-1-1.html)