做一个“频道墙”网页:页面上嵌入官方 Twitch 播放器,上方有一排按钮,点一下就切换到对应的频道。重点讲清楚
parent参数:嵌入用的 JS 会自动带上当前域名,而直接写 iframe 时必须手动写对parent,否则播放器会显示 “This embed is misconfigured”。
先看清成品
我在 box 上用 Chrome(playwright)分别通过
localhost和127.0.0.1打开页面,收到了VIDEO_READY事件,日志显示“播放器就绪:twitchdev”;点“twitchgaming”按钮后,getPlayer().getChannel()返回twitchgaming。播放器里显示 “TwitchDev is offline”,这是测试时频道没有在直播,属于真实状态。直接写 iframe 时,parent=www.example.com返回 302,并显示 “Whoops! This embed is misconfigured.”;parent=localhost返回 200,正常显示。没有测试正在直播的频道是否真的能出画面和声音(box 是无头环境)。
注意:只使用 Twitch 官方 API、EventSub 和官方嵌入播放器,并遵守《Twitch 开发者服务协议》。不要抓取或转录直播流,不要遮挡或去掉播放器上的广告,不要共享账号或 Client Secret,也不要用 VPN 或代理绕过地区限制。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
index.html |
Twitch.Embed + VIDEO_READY 事件 + 按钮切换频道 |
iframe_only.html |
不加载 JS 的纯 iframe 写法 |
e2e.py |
自动打开页面、等待就绪、点击切换并检查结果 |
需要准备哪些环境?
| 项目 | 要求 | 说明 |
|---|---|---|
| 浏览器 | 现代浏览器 | 不需要登录 Twitch |
| 网页托管 | 有自己的域名(正式上线时) | parent 要写这个域名 |
| Python + playwright | 可选 | 只有运行 e2e.py 时才需要 |
官方要点(Embedding Twitch)
| 项目 | 内容 |
|---|---|
| 脚本 | https://embed.twitch.tv/embed/v1.js,然后 new Twitch.Embed(元素id, 选项) |
| parent | 嵌入页面所在的域名;可以有多个,写成数组 |
| layout | video-with-chat(传了 channel 时的默认值,只支持直播)/ video |
| 尺寸 | 视频窗口至少 400×300;width 最小 400,height 最小 300 |
| 事件 | Twitch.Embed.VIDEO_READY(就绪,可以调用 API)、VIDEO_PLAY |
| 编程控制 | embed.getPlayer() 返回播放器对象,可以调用 setChannel、getChannel 等 |
文件结构
35-twitch-C-src/
├── index.html
├── iframe_only.html
└── e2e.py
第 1 步:页面
index.html:
<!doctype html>
<html lang="zh-CN">
<head><meta charset="utf-8"><title>我的 Twitch 频道墙</title>
<style>body{font-family:sans-serif;margin:16px} button{margin:4px} #log{color:#555;font-size:13px}</style></head>
<body>
<h1>Twitch 频道墙</h1>
<div id="buttons"></div>
<div id="twitch-embed"></div>
<p id="log">加载中…</p>
<script src="https://embed.twitch.tv/embed/v1.js"></script>
<script>
// 频道清单自己维护(填登录名)。parent 必须是这个页面所在的域名,否则播放器会拒绝加载。
const CHANNELS = ["twitchdev", "twitchgaming", "twitch"];
const PARENTS = [location.hostname]; // 上线时改成固定的域名列表,例如 ["www.example.com"]
const log = (t) => (document.getElementById("log").textContent = t);
const embed = new Twitch.Embed("twitch-embed", {
width: 854, height: 480, channel: CHANNELS[0],
layout: "video", autoplay: false, muted: true, parent: PARENTS
});
embed.addEventListener(Twitch.Embed.VIDEO_READY, () => {
window.playerReady = true;
log("播放器就绪:" + embed.getPlayer().getChannel());
});
embed.addEventListener(Twitch.Embed.VIDEO_PLAY, () => log("开始播放"));
for (const c of CHANNELS) {
const b = document.createElement("button");
b.textContent = c;
b.onclick = () => { embed.getPlayer().setChannel(c); log("切换到 " + c); };
document.getElementById("buttons").appendChild(b);
}
</script>
</body>
</html>
iframe_only.html:
<!doctype html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>只用 iframe 的嵌入</title></head>
<body>
<!-- 不加载 JS,直接用 iframe。parent 要手动写成页面所在的域名;多个域名就写多个 parent 参数 -->
<iframe id="raw" src="https://player.twitch.tv/?channel=twitchdev&parent=localhost&muted=true&autoplay=false"
height="480" width="854" allowfullscreen></iframe>
</body></html>
本地测试时用 python -m http.server 打开页面,然后访问 http://localhost:端口/。直接双击打开 file:// 页面时没有域名,parent 无法匹配。
第 2 步:自动检查
e2e.py:
"""本地起一个静态服务器(端口 0 = 随机空闲端口),用 Chromium 打开页面,等 VIDEO_READY,再点按钮切换频道。
需要:pip install playwright,并安装 Google Chrome(或改成 playwright install chromium 后去掉 channel 参数)"""
import asyncio, functools, http.server, threading
from pathlib import Path
from playwright.async_api import async_playwright
def serve():
class Quiet(http.server.SimpleHTTPRequestHandler):
def log_message(self, *a): pass
h = functools.partial(Quiet, directory=str(Path(__file__).parent))
s = http.server.ThreadingHTTPServer(("127.0.0.1", 0), h)
threading.Thread(target=s.serve_forever, daemon=True).start()
return s.server_port
async def main(host="localhost", page="index.html"):
port = serve()
async with async_playwright() as p:
b = await p.chromium.launch(channel="chrome"); pg = await b.new_page()
await pg.goto(f"http://{host}:{port}/{page}")
frame_src = await pg.locator("#twitch-embed iframe").get_attribute("src", timeout=20000)
print("iframe:", frame_src)
await pg.wait_for_timeout(6000)
print("iframe 内文字:", (await pg.frames[1].inner_text("body"))[:120].replace("\n", " ") if len(pg.frames) > 1 else "无")
try:
await pg.wait_for_function("window.playerReady === true", timeout=45000)
print(await pg.inner_text("#log"))
await pg.click("text=twitchgaming"); await pg.wait_for_timeout(3000)
print(await pg.inner_text("#log"), "| getChannel =", await pg.evaluate("embed.getPlayer().getChannel()"))
except Exception as e:
print("没有等到 VIDEO_READY:", type(e).__name__)
await b.close()
if __name__ == "__main__":
import sys; asyncio.run(main(*sys.argv[1:]))
$ python e2e.py
iframe: https://embed.twitch.tv?autoplay=false&channel=twitchdev&height=480&layout=video&muted=true&parent=localhost&referrer=...
iframe 内文字: OFFLINE TwitchDev is offline. Check out this Software and Game Development stream from Oct 17, 2024. ...
播放器就绪:twitchdev
切换到 twitchgaming | getChannel = twitchgaming
$ python e2e.py 127.0.0.1
iframe: https://embed.twitch.tv?...&parent=127.0.0.1&...
播放器就绪:twitchdev
切换到 twitchgaming | getChannel = twitchgaming
检查点:iframe 地址里的 parent 和你访问时用的主机名一致,并且 #log 显示了“播放器就绪”。
调试示例:parent 写错了,为什么 JS 版本照样能播?
我故意把 index.html 里的 PARENTS 改成 ["www.example.com"],用 localhost 打开,播放器照样就绪。查看 iframe 地址才发现:
...&parent=www.example.com&parent=localhost&...
v1.js 会自动把当前页面的域名加进去,所以用 JS 嵌入时,这个错误不会表现出来。换成直接写 iframe 的版本,问题就出现了:
parent=localhost -> 200 正常显示播放器
parent=www.example.com -> 302 Whoops! This embed is misconfigured.
结论:用纯 iframe 时,parent 必须和页面所在的域名完全一致(有多个域名就写多个 parent=)。JS 版本虽然能自动补上当前域名,上线时还是应该把真实域名写清楚。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
This embed is misconfigured |
parent 和页面域名对不上 | 把实际域名写进 parent |
| 播放器显示 offline | 频道当时没有在直播 | 属于正常状态;可以改用 video 参数播放回放 |
| 自动播放没有声音 | 浏览器的自动播放策略 | 先静音播放,由用户点击开声音 |
| 播放器很小,或者显示异常 | 尺寸小于最小值 | 宽度至少 400,高度至少 300 |
模板规则
- 只用官方嵌入方式,不要遮挡播放器,也不要去掉播放器上的广告。
parent写清楚真实域名,不要依赖 JS 自动补全。- 频道列表由自己维护;需要显示直播状态时,配合 A 篇在服务器端查询。
项目资源
| 用途 | 地址 |
|---|---|
| Embed Everything | https://dev.twitch.tv/docs/embed/everything/ |
| 视频和剪辑嵌入 | https://dev.twitch.tv/docs/embed/video-and-clips/ |
| Playwright for Python | https://playwright.dev/python/ |
| Twitch 开发者服务协议 | https://www.twitch.tv/p/legal/developer-agreement/ |
| 注册应用(开发者控制台) | https://dev.twitch.tv/console/apps |
嵌入播放器只需要几行代码,parent 却是最容易出问题的地方。JS 版本会帮你自动补上当前域名,纯 iframe 不会。把两种情况都实测一遍,上线后就不会对着 “misconfigured” 发愣。