教程

开发 TikTok LIVE 直播叠加层

打造一个兼容 OBS 的实时浏览器源叠加层,显示任意 TikTok LIVE 直播的实时弹幕、礼物提醒和观众人数。

你将做出什么

一个基于浏览器的直播叠加层,适用于 OBS、Streamlabs 或任何支持浏览器源的软件。它会显示:

  • 实时弹幕
  • 带钻石价值的礼物动画提醒
  • 当前观众人数
  • 带用户头像的滚动弹幕流

准备工作

  • 已安装 Node.js 18+
  • 一个 TikTools API Key(7 天评估,无需信用卡) - 点这里获取
  • OBS Studio(或任何支持浏览器源的直播软件)

第 1 步:搭建项目

mkdir tiktok-overlay && cd tiktok-overlay
npm init -y
npm install @tiktool/live express

第 2 步:创建服务端

创建 server.mjs,它会连接 TikTok,并通过 Server-Sent Events(SSE)把事件广播给浏览器叠加层:

import express from 'express'
import { TikTokLive } from '@tiktool/live'

const app = express()
const clients = new Set()

// Serve the overlay HTML
app.get('/', (req, res) => {
  res.sendFile('overlay.html', { root: '.' })
})

// SSE endpoint for real-time events
app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    Connection: 'keep-alive',
  })
  clients.add(res)
  req.on('close', () => clients.delete(res))
})

function broadcast(event, data) {
  for (const client of clients) {
    client.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`)
  }
}

// Connect to TikTok LIVE
const tiktok = new TikTokLive({
  uniqueId: process.env.STREAMER || 'tv_asahi_news',
  apiKey: process.env.TIKTOOL_API_KEY
})

tiktok.on('chat', (e) => broadcast('chat', {
  user: e.nickname, comment: e.comment, avatar: e.profilePictureUrl
}))

tiktok.on('gift', (e) => broadcast('gift', {
  user: e.nickname, gift: e.giftName, diamonds: e.diamondCount
}))

tiktok.on('viewer_count', (e) => broadcast('viewers', {
  count: e.viewerCount
}))

await tiktok.connect()
app.listen(3333, () => console.log('Overlay: http://localhost:3333'))

第 3 步:创建叠加层 HTML

创建 overlay.html,这就是 OBS 作为浏览器源渲染的页面:

<!DOCTYPE html>
<html>
<head>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: 'Inter', sans-serif;
      background: transparent;
      overflow: hidden;
    }

    .chat-feed {
      position: fixed; bottom: 20px; left: 20px;
      width: 360px; max-height: 400px;
      display: flex; flex-direction: column-reverse;
      gap: 6px; overflow: hidden;
    }

    .chat-msg {
      background: rgba(0, 0, 0, 0.6);
      border-radius: 8px; padding: 8px 12px;
      color: #fff; font-size: 14px;
      animation: slideIn 0.3s ease;
    }

    .chat-msg .user { color: #fff; font-weight: 600; }

    .gift-alert {
      position: fixed; top: 50%; left: 50%;
      transform: translate(-50%, -50%);
      background: rgba(0, 0, 0, 0.8);
      border: 2px solid var(--border-primary);
      border-radius: 16px; padding: 24px 40px;
      text-align: center; color: #fff;
      animation: popIn 0.5s ease;
      display: none;
    }

    .gift-alert.show { display: block; }
    .gift-alert .gift-name { font-size: 28px; font-weight: 800; }
    .gift-alert .gift-user { font-size: 16px; color: #94a3b8; }
    .gift-alert .gift-diamonds { font-size: 18px; color: #fff; }

    .viewer-count {
      position: fixed; top: 20px; right: 20px;
      background: rgba(0, 0, 0, 0.6);
      border-radius: 8px; padding: 8px 16px;
      color: #fff; font-size: 14px; font-weight: 600;
    }

    @keyframes slideIn {
      from { opacity: 0; transform: translateY(10px); }
      to { opacity: 1; transform: translateY(0); }
    }

    @keyframes popIn {
      0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5); }
      100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    }
  </style>
</head>
<body>
  <div class="chat-feed" id="chat"></div>
  <div class="gift-alert" id="gift">
    <div class="gift-name" id="gift-name"></div>
    <div class="gift-user" id="gift-user"></div>
    <div class="gift-diamonds" id="gift-diamonds"></div>
  </div>
  <div class="viewer-count" id="viewers">👁 0</div>

  <script>
    const chat = document.getElementById('chat')
    const gift = document.getElementById('gift')
    const src = new EventSource('/events')

    src.addEventListener('chat', (e) => {
      const d = JSON.parse(e.data)
      const el = document.createElement('div')
      el.className = 'chat-msg'
      el.innerHTML = `<span class="user">${d.user}</span> ${d.comment}`
      chat.prepend(el)
      if (chat.children.length > 50) chat.lastChild.remove()
    })

    src.addEventListener('gift', (e) => {
      const d = JSON.parse(e.data)
      document.getElementById('gift-name').textContent = `🎁 ${d.gift}`
      document.getElementById('gift-user').textContent = `from ${d.user}`
      document.getElementById('gift-diamonds').textContent = `${d.diamonds} 💎`
      gift.classList.add('show')
      setTimeout(() => gift.classList.remove('show'), 4000)
    })

    src.addEventListener('viewers', (e) => {
      const d = JSON.parse(e.data)
      document.getElementById('viewers').textContent = `👁 ${d.count.toLocaleString()}`
    })
  </script>
</body>
</html>

第 4 步:运行并添加到 OBS

TIKTOOL_API_KEY=your_key STREAMER=target_username node server.mjs

然后在 OBS 中:

  • 添加一个新的浏览器源
  • 把 URL 设为 http://localhost:3333
  • 宽度设为 1920,高度设为 1080
  • 勾选「不可见时关闭源」

自定义灵感

  • 自定义主题 - 修改颜色、字体和布局,匹配你的直播风格
  • 音效 - 用 Web Audio API 在收到礼物时播放音效
  • GIF 动画 - 为特定礼物显示 GIF 动画
  • 目标进度条 - 添加打赏/礼物目标进度条
  • 头部送礼用户领奖台 - 实时显示前 3 名送礼用户