チュートリアル

TikTok LIVEの 配信オーバーレイを作る

どのTikTok LIVE配信からも、ライブチャット、ギフトアラート、視聴者数を表示する、OBS対応のリアルタイムなブラウザソースオーバーレイを作成します。

作るもの

OBS、Streamlabsなど、ブラウザソースに対応したソフトで使えるブラウザベースの配信オーバーレイです。表示する内容:

  • リアルタイムのライブチャットメッセージ
  • ダイヤモンド価値付きのアニメーションギフトアラート
  • 現在の視聴者数
  • ユーザーのアバター付きでスクロールするチャットフィード

必要なもの

  • Node.js 18+がインストール済み
  • TikToolsの無料APIキー - こちらから取得
  • 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人のギフターをリアルタイムで表示