Web3 Live Streaming: Build Decentralized Streaming with Low Latency & Token Economics

Traditional live streaming platforms take 30–50% of creator revenue. YouTube takes 45%. Twitch takes 50%. TikTok's creator fund pays fractions of a cent per view. Creators produce the content, build the audience, and hand half their earnings to a platform that treats them as replaceable.
Web3 live streaming changes this equation. Decentralized distribution removes the middleman. Token economics let viewers send value directly to creators. Smart contracts enforce transparent revenue splits with zero platform override. The creator keeps 85–95% instead of 50–70%.
But here's the engineering challenge: decentralized doesn't mean slow. Viewers expect sub-second latency, HD quality, and interactive features like real-time chat and multi-person co-streaming. Pure peer-to-peer networks can't deliver this at scale—Theta Network still relies on edge nodes, and Livepeer focuses on transcoding rather than end-to-end delivery.
This guide shows you how to build production-grade web3 live streaming infrastructure that combines decentralized ownership with professional streaming quality. You'll implement low-latency broadcasting, token-based tipping via smart contracts, interactive bullet comments (danmaku), multi-person co-hosting, and CDN distribution—all with working code.
The real-time layer uses Tencent RTC (TRTC) Live SDK, which delivers <300ms latency across 200+ countries and handles the interactive features that make web3 streaming competitive with centralized platforms.
TL;DR
- Web3 streaming = decentralized content ownership + token economics + professional-grade delivery
- Creators keep 85–95% of revenue through smart contract-based tipping (vs. 50–70% on Web2 platforms)
- Architecture: Blockchain Layer (tokens + NFTs) → Streaming Layer (low-latency live) → Interaction Layer (chat + tipping) → Distribution Layer (CDN + P2P hybrid)
- Full code tutorial: broadcast setup, viewer playback, token tipping, bullet comments, co-hosting, CDN config
- TRTC Live SDK provides the real-time infrastructure: <300ms latency, 100K+ concurrent viewers, built-in interaction features
- MCP server available for AI-assisted SDK integration and rapid prototyping
- Production example: web3 music streaming platform with live performances and instant token payouts
Why Web3 Streaming Matters Now
The Creator Revenue Problem
The economics of centralized streaming are fundamentally broken for creators:
| Platform | Platform Cut | Creator Share | Payout Threshold | Payment Delay |
|---|---|---|---|---|
| YouTube Live | 30–45% | 55–70% | $100 | 30–60 days |
| Twitch | 50% | 50% | $50 | 15–45 days |
| TikTok Live | 50–66% | 34–50% | $50 | 15–30 days |
| Kick | 5% | 95% | $100 | Monthly |
| Web3 (Token) | 0–5% | 85–95% | None | Instant |
Web3 streaming eliminates the platform as a revenue gatekeeper. When a viewer sends 100 USDC as a tip, the smart contract routes 95 USDC to the creator and 5 USDC to protocol maintenance—instantly, transparently, with no approval process.
Beyond Revenue: Ownership and Portability
Web3 streaming gives creators three things centralized platforms never will:
- Content ownership — Stream recordings stored on decentralized storage (IPFS/Arweave) can't be deleted by a platform policy change
- Audience portability — Follower relationships exist on-chain; switching platforms doesn't mean starting over
- Programmable monetization — Smart contracts enable subscription tiers, NFT-gated access, revenue-sharing with collaborators, and automatic royalty splits
The Technical Gap
Projects like Theta Network and Livepeer have proven decentralized video infrastructure works. But they solve different problems:
- Livepeer handles transcoding (converting one video format to many) but doesn't provide end-user streaming SDKs
- Theta Network provides decentralized CDN relay but doesn't offer real-time interaction features
- AIOZ Network focuses on storage and basic delivery without live interactive capabilities
None of these provide the complete stack a web3 live streaming app needs: ultra-low-latency broadcasting, real-time viewer interaction, multi-person co-hosting, and scalable CDN distribution—all integrated with blockchain wallet authentication and token transactions.
This is where TRTC's Live SDK fills the gap. It provides production-ready streaming infrastructure that you integrate with your blockchain layer for a complete web3 streaming solution.
Architecture: Web3 Live Streaming Stack
A production web3 live streaming platform has four layers:
┌──────────────────────────────────────────────────────────┐
│ APPLICATION LAYER │
│ React/Next.js Frontend + Wallet Connect + Stream UI │
├──────────────────────────────────────────────────────────┤
│ INTERACTION LAYER │
│ Bullet Comments │ Token Tips │ Co-Host │ Polls/Votes │
├──────────────────────────────────────────────────────────┤
│ STREAMING LAYER │
│ TRTC Live SDK │ Low Latency │ Adaptive Bitrate │ CDN │
├──────────────────────────────────────────────────────────┤
│ BLOCKCHAIN LAYER │
│ Wallet Auth │ Smart Contracts │ Token Txns │ NFT Gates │
└──────────────────────────────────────────────────────────┘Layer Responsibilities
Blockchain Layer — Handles identity (wallet-based auth), economics (token minting, tipping contracts, revenue distribution), and access control (NFT-gated streams).
Streaming Layer — Manages the actual video/audio delivery. This is where latency, quality, and scalability matter. TRTC Live SDK handles encoding, transmission, adaptive bitrate, and global CDN distribution.
Interaction Layer — Real-time features that make live streaming engaging: bullet comments (danmaku), token tipping with on-screen animations, co-hosting (multiple streamers), polls, and reactions.
Application Layer — The user-facing interface that ties everything together. Wallet connection, stream discovery, creator dashboards, and viewer experience.
Why Hybrid Architecture Wins
Fully decentralized streaming (pure P2P) fails at scale for three reasons:
- Latency — P2P relay adds 2–8 seconds of delay. Live interaction requires <1 second.
- Quality — Without server-side transcoding, viewers on slow connections get buffering instead of adaptive quality reduction.
- Reliability — P2P networks have no SLA. Nodes drop, streams break.
The winning architecture is decentralized ownership + optimized delivery:
- Identity, payments, and content rights live on-chain (trustless, transparent)
- Video streaming routes through optimized infrastructure (fast, reliable)
- The blockchain layer ensures creators own their content and revenue even though delivery is optimized through professional infrastructure
This is exactly what platforms like Audius do for web3 music streaming—decentralized content registry with optimized delivery nodes.
Prerequisites
Before building, you need:
- Node.js 18+ and npm/yarn
- TRTC account — Register here to get your SDKAppID and SecretKey
- Wallet provider — MetaMask or WalletConnect for user authentication
- Smart contract tooling — Hardhat or Foundry for deploying tipping contracts
- Blockchain RPC — Alchemy or Infura endpoint for your target chain (Ethereum, Polygon, or Base)
Install core dependencies:
# Streaming SDK
npm install trtc-sdk-v5
# Web3 dependencies
npm install ethers@6 @web3modal/ethers wagmi viem
# UI framework
npm install react react-dom next
# Interaction features
npm install socket.io-clientStep 1: Wallet-Based Authentication
Web3 streaming replaces email/password login with wallet signatures. This gives every user a blockchain identity that carries across platforms.
// lib/auth.ts
import { BrowserProvider } from 'ethers';
import { generateUserSig } from './trtc-auth';
interface Web3User {
address: string;
chainId: number;
trtcUserSig: string;
trtcUserId: string;
}
export async function authenticateWithWallet(): Promise<Web3User> {
// Connect wallet
if (!window.ethereum) {
throw new Error('No wallet detected. Install MetaMask or use WalletConnect.');
}
const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const address = await signer.getAddress();
const network = await provider.getNetwork();
// Sign authentication message (proves wallet ownership)
const message = `Authenticate to Web3Stream\nTimestamp: ${Date.now()}`;
const signature = await signer.signMessage(message);
// Generate TRTC credentials from wallet address
// The wallet address becomes the TRTC userId (deterministic, unique)
const trtcUserId = `w3_${address.slice(2, 14).toLowerCase()}`;
const trtcUserSig = generateUserSig(trtcUserId);
return {
address,
chainId: Number(network.chainId),
trtcUserSig,
trtcUserId,
};
}// lib/trtc-auth.ts
import * as crypto from 'crypto';
const SDKAPPID = process.env.NEXT_PUBLIC_TRTC_SDKAPPID!;
const SECRETKEY = process.env.TRTC_SECRET_KEY!;
export function generateUserSig(userId: string): string {
const expires = 86400 * 7; // 7 days
const currentTime = Math.floor(Date.now() / 1000);
const sigDoc = {
'TLS.ver': '2.0',
'TLS.identifier': userId,
'TLS.sdkappid': Number(SDKAPPID),
'TLS.expire': expires,
'TLS.time': currentTime,
};
const sigContent = `TLS.identifier:${userId}\nTLS.sdkappid:${SDKAPPID}\nTLS.time:${currentTime}\nTLS.expire:${expires}\n`;
const hmac = crypto.createHmac('sha256', SECRETKEY);
hmac.update(sigContent);
const sig = hmac.digest('base64');
return Buffer.from(JSON.stringify({ ...sigDoc, 'TLS.sig': sig })).toString('base64');
}The wallet address becomes the user's identity across your entire streaming platform. No email required, no password to forget, and the same identity works across any Web3 app.
Step 2: Live Stream Broadcasting (Creator Side)
This is the core streaming implementation. The creator opens their camera/mic, encodes the stream, and TRTC handles global delivery.
// components/Broadcaster.tsx
import TRTC from 'trtc-sdk-v5';
interface BroadcastConfig {
roomId: number;
userId: string;
userSig: string;
sdkAppId: number;
}
class LiveBroadcaster {
private trtc: TRTC;
private isStreaming: boolean = false;
constructor() {
this.trtc = TRTC.create();
}
async startBroadcast(config: BroadcastConfig): Promise<void> {
const { roomId, userId, userSig, sdkAppId } = config;
// Enter room as anchor (broadcaster role)
await this.trtc.enterRoom({
roomId,
sdkAppId,
userId,
userSig,
scene: 'live', // Live streaming scenario
role: 'anchor', // Broadcaster role
});
// Start camera capture with HD settings
await this.trtc.startLocalVideo({
view: 'local-video-container',
option: {
profile: '1080p', // Full HD broadcast
frameRate: 30,
bitrate: 2500, // High quality for live content
mirror: true, // Mirror for self-view
},
});
// Start microphone capture
await this.trtc.startLocalAudio({
option: {
profile: 'high', // High quality audio
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
});
this.isStreaming = true;
console.log(`Broadcasting started in room ${roomId}`);
}
async stopBroadcast(): Promise<void> {
await this.trtc.stopLocalVideo();
await this.trtc.stopLocalAudio();
await this.trtc.exitRoom();
this.isStreaming = false;
}
// Switch camera during broadcast (front/back on mobile)
async switchCamera(deviceId: string): Promise<void> {
await this.trtc.updateLocalVideo({
option: { cameraId: deviceId },
});
}
// Adjust video quality based on network conditions
async adjustQuality(profile: '1080p' | '720p' | '480p'): Promise<void> {
const bitrateMap = { '1080p': 2500, '720p': 1500, '480p': 800 };
await this.trtc.updateLocalVideo({
option: {
profile,
bitrate: bitrateMap[profile],
},
});
}
// Screen sharing for gaming/tutorial streams
async startScreenShare(): Promise<void> {
await this.trtc.startScreenShare({
view: 'screen-share-container',
option: {
profile: '1080p',
frameRate: 15, // Lower FPS for screen content
bitrate: 3000,
},
});
}
}
export default LiveBroadcaster;React Component for Broadcaster UI
// pages/broadcast.tsx
import { useState, useEffect } from 'react';
import LiveBroadcaster from '../components/Broadcaster';
import { authenticateWithWallet } from '../lib/auth';
export default function BroadcastPage() {
const [broadcaster] = useState(new LiveBroadcaster());
const [isLive, setIsLive] = useState(false);
const [user, setUser] = useState<any>(null);
useEffect(() => {
return () => {
if (isLive) broadcaster.stopBroadcast();
};
}, [isLive]);
const handleGoLive = async () => {
// Authenticate with wallet if not already connected
const authedUser = user || await authenticateWithWallet();
setUser(authedUser);
// Generate room ID from wallet address (deterministic)
const roomId = parseInt(authedUser.address.slice(2, 10), 16) % 1000000;
await broadcaster.startBroadcast({
roomId,
userId: authedUser.trtcUserId,
userSig: authedUser.trtcUserSig,
sdkAppId: Number(process.env.NEXT_PUBLIC_TRTC_SDKAPPID),
});
setIsLive(true);
};
return (
<div className="broadcast-page">
<div id='local-video-container' className="video-main" />
<div className="controls">
{!isLive ? (
<button onClick={handleGoLive} className="btn-go-live">
Go Live
</button>
) : (
<button onClick={() => { broadcaster.stopBroadcast(); setIsLive(false); }}
className="btn-end-stream">
End Stream
</button>
)}
</div>
</div>
);
}Step 3: Live Stream Viewing (Audience Side)
Viewers join the stream with <300ms latency. The TRTC Live SDK handles adaptive bitrate to match each viewer's network conditions.
// components/Viewer.tsx
import TRTC from 'trtc-sdk-v5';
interface ViewerConfig {
roomId: number;
userId: string;
userSig: string;
sdkAppId: number;
}
class LiveViewer {
private trtc: TRTC;
private onTipCallback?: (tip: any) => void;
constructor() {
this.trtc = TRTC.create();
this.setupEventListeners();
}
private setupEventListeners(): void {
// When the broadcaster's video is available
this.trtc.on(TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, ({ userId, streamType }) => {
// Subscribe and render the broadcaster's video
this.trtc.startRemoteVideo({
userId,
streamType,
view: 'remote-video-container',
});
});
// When a co-host joins
this.trtc.on(TRTC.EVENT.REMOTE_USER_ENTER, ({ userId }) => {
console.log(`User ${userId} joined the stream`);
});
// When broadcaster ends the stream
this.trtc.on(TRTC.EVENT.REMOTE_USER_EXIT, ({ userId }) => {
console.log(`Broadcaster ${userId} ended the stream`);
});
// Network quality monitoring
this.trtc.on(TRTC.EVENT.NETWORK_QUALITY, (event) => {
const { uplinkNetworkQuality, downlinkNetworkQuality } = event;
if (downlinkNetworkQuality > 3) {
console.warn('Poor network detected, stream may buffer');
}
});
}
async joinStream(config: ViewerConfig): Promise<void> {
const { roomId, userId, userSig, sdkAppId } = config;
await this.trtc.enterRoom({
roomId,
sdkAppId,
userId,
userSig,
scene: 'live',
role: 'audience', // Viewer role (receive-only)
});
console.log(`Joined stream in room ${roomId}`);
}
async leaveStream(): Promise<void> {
await this.trtc.exitRoom();
}
// Request to co-host (viewer becomes broadcaster temporarily)
async requestCoHost(): Promise<void> {
await this.trtc.switchRole('anchor');
await this.trtc.startLocalVideo({
view: 'cohost-video-container',
option: {
profile: '720p',
frameRate: 24,
bitrate: 1200,
},
});
await this.trtc.startLocalAudio({
option: { profile: 'standard' },
});
}
// Return to audience mode
async endCoHost(): Promise<void> {
await this.trtc.stopLocalVideo();
await this.trtc.stopLocalAudio();
await this.trtc.switchRole('audience');
}
}
export default LiveViewer;Viewer React Component
// pages/watch/[roomId].tsx
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import LiveViewer from '../../components/Viewer';
import { authenticateWithWallet } from '../../lib/auth';
import TokenTipper from '../../components/TokenTipper';
import BulletComments from '../../components/BulletComments';
export default function WatchPage() {
const router = useRouter();
const { roomId } = router.query;
const [viewer] = useState(new LiveViewer());
const [user, setUser] = useState<any>(null);
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
if (!roomId) return;
const joinStream = async () => {
const authedUser = await authenticateWithWallet();
setUser(authedUser);
await viewer.joinStream({
roomId: Number(roomId),
userId: authedUser.trtcUserId,
userSig: authedUser.trtcUserSig,
sdkAppId: Number(process.env.NEXT_PUBLIC_TRTC_SDKAPPID),
});
setIsConnected(true);
};
joinStream();
return () => { viewer.leaveStream(); };
}, [roomId]);
return (
<div className="watch-page">
<div id='remote-video-container' className="video-player" />
{isConnected && (
<>
<BulletComments roomId={Number(roomId)} userId={user?.trtcUserId} />
<TokenTipper
recipientAddress={router.query.creator as string}
senderAddress={user?.address}
/>
</>
)}
</div>
);
}Step 4: Token-Based Tipping (Smart Contract)
This is what makes web3 streaming different from Web2. Tips go directly from viewer wallet to creator wallet through a transparent smart contract—no platform holding funds, no 30-day payment delays.
Tipping Smart Contract
// contracts/StreamTipping.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract StreamTipping is ReentrancyGuard {
// Protocol fee: 5% (vs 30-50% on Web2 platforms)
uint256 public constant PROTOCOL_FEE_BPS = 500; // 5% in basis points
address public protocolTreasury;
struct TipEvent {
address tipper;
address creator;
uint256 amount;
address token; // ERC20 token address (address(0) for ETH)
uint256 roomId;
string message; // On-screen tip message
uint256 timestamp;
}
TipEvent[] public tipHistory;
// Creator earnings tracking
mapping(address => uint256) public creatorTotalEarnings;
mapping(address => mapping(address => uint256)) public creatorTokenEarnings;
event TipSent(
address indexed tipper,
address indexed creator,
uint256 amount,
address token,
uint256 roomId,
string message
);
event CreatorWithdrawal(
address indexed creator,
address token,
uint256 amount
);
constructor(address _treasury) {
protocolTreasury = _treasury;
}
// Tip with native ETH
function tipETH(
address creator,
uint256 roomId,
string calldata message
) external payable nonReentrant {
require(msg.value > 0, "Tip must be greater than 0");
require(creator != address(0), "Invalid creator address");
require(creator != msg.sender, "Cannot tip yourself");
uint256 protocolFee = (msg.value * PROTOCOL_FEE_BPS) / 10000;
uint256 creatorAmount = msg.value - protocolFee;
// Send to creator immediately (no escrow, no delay)
(bool sent, ) = payable(creator).call{value: creatorAmount}("");
require(sent, "Transfer to creator failed");
// Protocol fee
(bool feeSent, ) = payable(protocolTreasury).call{value: protocolFee}("");
require(feeSent, "Fee transfer failed");
// Record tip
tipHistory.push(TipEvent({
tipper: msg.sender,
creator: creator,
amount: msg.value,
token: address(0),
roomId: roomId,
message: message,
timestamp: block.timestamp
}));
creatorTotalEarnings[creator] += creatorAmount;
emit TipSent(msg.sender, creator, msg.value, address(0), roomId, message);
}
// Tip with ERC20 tokens (USDC, USDT, platform token)
function tipERC20(
address creator,
address token,
uint256 amount,
uint256 roomId,
string calldata message
) external nonReentrant {
require(amount > 0, "Tip must be greater than 0");
require(creator != address(0), "Invalid creator address");
IERC20 tokenContract = IERC20(token);
uint256 protocolFee = (amount * PROTOCOL_FEE_BPS) / 10000;
uint256 creatorAmount = amount - protocolFee;
// Transfer directly to creator
tokenContract.transferFrom(msg.sender, creator, creatorAmount);
// Protocol fee
tokenContract.transferFrom(msg.sender, protocolTreasury, protocolFee);
tipHistory.push(TipEvent({
tipper: msg.sender,
creator: creator,
amount: amount,
token: token,
roomId: roomId,
message: message,
timestamp: block.timestamp
}));
creatorTokenEarnings[creator][token] += creatorAmount;
emit TipSent(msg.sender, creator, amount, token, roomId, message);
}
// Get tip history for a room (for displaying on-stream)
function getRoomTips(uint256 roomId, uint256 limit)
external view returns (TipEvent[] memory)
{
uint256 count = 0;
for (uint256 i = tipHistory.length; i > 0 && count < limit; i--) {
if (tipHistory[i-1].roomId == roomId) count++;
}
TipEvent[] memory roomTips = new TipEvent[](count);
uint256 idx = 0;
for (uint256 i = tipHistory.length; i > 0 && idx < count; i--) {
if (tipHistory[i-1].roomId == roomId) {
roomTips[idx] = tipHistory[i-1];
idx++;
}
}
return roomTips;
}
}Frontend Tipping Component
// components/TokenTipper.tsx
import { useState } from 'react';
import { BrowserProvider, Contract, parseEther, parseUnits } from 'ethers';
const TIPPING_CONTRACT = process.env.NEXT_PUBLIC_TIPPING_CONTRACT!;
const USDC_ADDRESS = process.env.NEXT_PUBLIC_USDC_ADDRESS!;
const TIPPING_ABI = [
'function tipETH(address creator, uint256 roomId, string message) payable',
'function tipERC20(address creator, address token, uint256 amount, uint256 roomId, string message)',
];
interface TipperProps {
recipientAddress: string;
senderAddress: string;
roomId?: number;
onTipSuccess?: (amount: string, message: string) => void;
}
export default function TokenTipper({ recipientAddress, senderAddress, roomId = 0, onTipSuccess }: TipperProps) {
const [amount, setAmount] = useState('');
const [message, setMessage] = useState('');
const [tokenType, setTokenType] = useState<'ETH' | 'USDC'>('USDC');
const [isSending, setIsSending] = useState(false);
const sendTip = async () => {
if (!amount || parseFloat(amount) <= 0) return;
setIsSending(true);
try {
const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const contract = new Contract(TIPPING_CONTRACT, TIPPING_ABI, signer);
let tx;
if (tokenType === 'ETH') {
tx = await contract.tipETH(
recipientAddress,
roomId,
message || 'Great stream!',
{ value: parseEther(amount) }
);
} else {
// Approve USDC spend first
const usdcAbi = ['function approve(address spender, uint256 amount) returns (bool)'];
const usdc = new Contract(USDC_ADDRESS, usdcAbi, signer);
const tipAmount = parseUnits(amount, 6); // USDC has 6 decimals
await (await usdc.approve(TIPPING_CONTRACT, tipAmount)).wait();
tx = await contract.tipERC20(
recipientAddress,
USDC_ADDRESS,
tipAmount,
roomId,
message || 'Great stream!'
);
}
await tx.wait();
onTipSuccess?.(amount, message);
setAmount('');
setMessage('');
} catch (error) {
console.error('Tip failed:', error);
} finally {
setIsSending(false);
}
};
const quickAmounts = ['1', '5', '10', '50', '100'];
return (
<div className="tip-panel">
<h3>Send Tip (95% goes to creator)</h3>
<div className="token-selector">
<button onClick={() => setTokenType('ETH')}
className={tokenType === 'ETH' ? 'active' : ''}>ETH</button>
<button onClick={() => setTokenType('USDC')}
className={tokenType === 'USDC' ? 'active' : ''}>USDC</button>
</div>
<div className="quick-amounts">
{quickAmounts.map(qa => (
<button key={qa} onClick={() => setAmount(qa)}>{qa}</button>
))}
</div>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Custom amount"
/>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Message (shows on stream)"
maxLength={100}
/>
<button onClick={sendTip} disabled={isSending || !amount}>
{isSending ? 'Confirming...' : `Tip ${amount} ${tokenType}`}
</button>
</div>
);
}Step 5: Interactive Bullet Comments (Danmaku)
Bullet comments (danmaku) are scrolling messages that overlay the video stream. They create a shared viewing experience where thousands of viewers react simultaneously. This feature is essential for web3 music streaming concerts and gaming streams.
// components/BulletComments.tsx
import { useState, useEffect, useRef } from 'react';
import TRTC from 'trtc-sdk-v5';
interface BulletComment {
id: string;
userId: string;
text: string;
color: string;
timestamp: number;
isTip?: boolean;
tipAmount?: string;
}
interface BulletCommentsProps {
roomId: number;
userId: string;
trtcInstance: any;
}
export default function BulletComments({ roomId, userId, trtcInstance }: BulletCommentsProps) {
const [comments, setComments] = useState<BulletComment[]>([]);
const [input, setInput] = useState('');
const canvasRef = useRef<HTMLCanvasElement>(null);
const animationRef = useRef<number>();
// Listen for incoming bullet comments via TRTC custom messages
useEffect(() => {
const handleCustomMessage = (event: any) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'bullet_comment') {
addComment(data.payload);
} else if (data.type === 'tip_notification') {
// Tips display as golden highlighted comments
addComment({
...data.payload,
isTip: true,
color: '#FFD700',
});
}
} catch (e) {
// Ignore non-JSON messages
}
};
trtcInstance?.on('onRecvCustomCmdMsg', handleCustomMessage);
return () => {
trtcInstance?.off('onRecvCustomCmdMsg', handleCustomMessage);
};
}, [trtcInstance]);
// Canvas-based rendering for smooth animation
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d')!;
const activeComments: Array<BulletComment & { x: number; y: number; speed: number }> = [];
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
activeComments.forEach((comment, index) => {
// Draw comment
ctx.font = comment.isTip ? 'bold 20px sans-serif' : '16px sans-serif';
ctx.fillStyle = comment.color || '#FFFFFF';
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 2;
ctx.fillText(comment.text, comment.x, comment.y);
// Move left
comment.x -= comment.speed;
// Remove if off-screen
if (comment.x < -ctx.measureText(comment.text).width) {
activeComments.splice(index, 1);
}
});
animationRef.current = requestAnimationFrame(animate);
};
// Add new comments to animation queue
const interval = setInterval(() => {
const newComments = comments.filter(
c => !activeComments.find(ac => ac.id === c.id)
);
newComments.forEach(c => {
activeComments.push({
...c,
x: canvas.width,
y: 30 + Math.random() * (canvas.height - 60),
speed: 1.5 + Math.random() * 2,
});
});
}, 100);
animate();
return () => {
cancelAnimationFrame(animationRef.current!);
clearInterval(interval);
};
}, [comments]);
const addComment = (comment: BulletComment) => {
setComments(prev => [...prev.slice(-200), comment]); // Keep last 200
};
const sendComment = async () => {
if (!input.trim()) return;
const comment: BulletComment = {
id: `${userId}_${Date.now()}`,
userId,
text: input,
color: '#FFFFFF',
timestamp: Date.now(),
};
// Send via TRTC custom message (broadcast to all viewers)
await trtcInstance?.sendCustomCmdMsg({
cmdID: 1,
data: JSON.stringify({
type: 'bullet_comment',
payload: comment,
}),
reliable: false, // Unreliable for performance (OK to drop some)
ordered: false,
});
addComment(comment);
setInput('');
};
return (
<div className="bullet-comments-layer">
<canvas
ref={canvasRef}
className="bullet-canvas"
width={1280}
height={720}
/>
<div className="comment-input">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && sendComment()}
placeholder="Send a bullet comment..."
maxLength={50}
/>
<button onClick={sendComment}>Send</button>
</div>
</div>
);
}The bullet comment system uses TRTC's custom message channel for real-time delivery. Messages propagate to all viewers within the same room in under 200ms. For web3 music streaming events with 50K+ concurrent viewers, this creates the collective energy that makes live performances special.
Step 6: Multi-Person Co-Hosting
Co-hosting lets multiple creators stream together—essential for interviews, debates, collaborative gaming, and web3 music streaming jam sessions. TRTC handles the mixing and layout automatically.
// components/CoHostManager.tsx
import TRTC from 'trtc-sdk-v5';
interface CoHostRequest {
userId: string;
walletAddress: string;
requestTime: number;
}
class CoHostManager {
private trtc: TRTC;
private maxCoHosts: number = 8; // TRTC supports up to 8 co-hosts in live mode
private activeCoHosts: Set<string> = new Set();
private pendingRequests: CoHostRequest[] = [];
constructor(trtcInstance: TRTC) {
this.trtc = trtcInstance;
this.setupCoHostEvents();
}
private setupCoHostEvents(): void {
// When a new co-host's video becomes available
this.trtc.on(TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, ({ userId, streamType }) => {
if (this.activeCoHosts.has(userId)) {
// Render co-host video in grid layout
this.trtc.startRemoteVideo({
userId,
streamType,
view: `cohost-video-${userId}`,
});
}
});
this.trtc.on(TRTC.EVENT.REMOTE_USER_EXIT, ({ userId }) => {
this.activeCoHosts.delete(userId);
});
}
// Broadcaster approves a co-host request
async approveCoHost(request: CoHostRequest): Promise<boolean> {
if (this.activeCoHosts.size >= this.maxCoHosts) {
console.warn('Maximum co-hosts reached');
return false;
}
// Send approval signal via custom message
await this.trtc.sendCustomCmdMsg({
cmdID: 2,
data: JSON.stringify({
type: 'cohost_approved',
userId: request.userId,
}),
reliable: true,
ordered: true,
});
this.activeCoHosts.add(request.userId);
return true;
}
// Remove a co-host
async removeCoHost(userId: string): Promise<void> {
await this.trtc.sendCustomCmdMsg({
cmdID: 2,
data: JSON.stringify({
type: 'cohost_removed',
userId,
}),
reliable: true,
ordered: true,
});
this.activeCoHosts.delete(userId);
}
// Set mixed stream layout (how co-hosts appear to viewers)
async setMixedLayout(layout: 'grid' | 'spotlight' | 'sidebar'): Promise<void> {
const layoutConfigs = {
grid: this.generateGridLayout(),
spotlight: this.generateSpotlightLayout(),
sidebar: this.generateSidebarLayout(),
};
await this.trtc.startMixTranscode({
streamId: '',
videoWidth: 1920,
videoHeight: 1080,
videoBitrate: 3000,
videoFramerate: 30,
audioSampleRate: 48000,
audioBitrate: 128,
audioChannels: 2,
mode: 'manual',
videoLayoutList: layoutConfigs[layout],
});
}
private generateGridLayout() {
const hosts = Array.from(this.activeCoHosts);
const count = hosts.length + 1; // +1 for main broadcaster
const cols = Math.ceil(Math.sqrt(count));
const rows = Math.ceil(count / cols);
const cellWidth = Math.floor(1920 / cols);
const cellHeight = Math.floor(1080 / rows);
return hosts.map((userId, index) => ({
userId,
x: (index % cols) * cellWidth,
y: Math.floor(index / cols) * cellHeight,
width: cellWidth,
height: cellHeight,
zOrder: index + 1,
}));
}
private generateSpotlightLayout() {
const hosts = Array.from(this.activeCoHosts);
return hosts.map((userId, index) => ({
userId,
x: 1440,
y: index * 270,
width: 480,
height: 270,
zOrder: index + 1,
}));
}
private generateSidebarLayout() {
const hosts = Array.from(this.activeCoHosts);
return hosts.map((userId, index) => ({
userId,
x: 1440,
y: index * 360,
width: 480,
height: 360,
zOrder: index + 1,
}));
}
getActiveCoHosts(): string[] {
return Array.from(this.activeCoHosts);
}
}
export default CoHostManager;Co-Host Request Flow (Viewer Side)
// Handle co-host invitation on the viewer side
async function handleCoHostInvitation(trtc: any, viewerUserId: string): Promise<void> {
// Listen for approval from broadcaster
trtc.on('onRecvCustomCmdMsg', async (event: any) => {
const data = JSON.parse(event.data);
if (data.type === 'cohost_approved' && data.userId === viewerUserId) {
// Switch from audience to anchor role
await trtc.switchRole('anchor');
// Start publishing local video/audio
await trtc.startLocalVideo({
view: 'cohost-local-video',
option: {
profile: '720p',
frameRate: 24,
bitrate: 1200,
},
});
await trtc.startLocalAudio({
option: { profile: 'standard' },
});
console.log('Co-hosting started! You are now live.');
}
if (data.type === 'cohost_removed' && data.userId === viewerUserId) {
// Return to audience mode
await trtc.stopLocalVideo();
await trtc.stopLocalAudio();
await trtc.switchRole('audience');
console.log('Co-hosting ended. Back to viewer mode.');
}
});
}Step 7: CDN Distribution Configuration
For streams with 10K+ concurrent viewers, relying solely on real-time relay isn't cost-effective. CDN distribution (relayed push) broadcasts the stream to standard CDN networks, allowing massive audiences while keeping the interactive core low-latency.
// lib/cdn-config.ts
interface CDNConfig {
streamId: string;
cdnUrl: string;
enableTranscoding: boolean;
}
class CDNDistribution {
private trtc: any;
private sdkAppId: number;
constructor(trtcInstance: any, sdkAppId: number) {
this.trtc = trtcInstance;
this.sdkAppId = sdkAppId;
}
// Start CDN relay push (single stream, no mixing)
async startCDNPublish(config: CDNConfig): Promise<void> {
await this.trtc.startPublishCDNStream({
appId: this.sdkAppId,
bizId: 0,
url: config.cdnUrl,
});
console.log(`CDN publish started: ${config.cdnUrl}`);
}
// Start mixed-stream CDN relay (multiple co-hosts mixed into one stream)
async startMixedCDNPublish(
streamId: string,
cdnUrl: string,
coHostUserIds: string[]
): Promise<void> {
// Configure cloud mixing (server-side)
const mixConfig = {
streamId,
videoWidth: 1920,
videoHeight: 1080,
videoBitrate: 3000,
videoFramerate: 30,
videoGOP: 2,
audioSampleRate: 48000,
audioBitrate: 128,
audioChannels: 2,
backgroundColor: 0x000000,
// Layout: main broadcaster + co-hosts
mixUsers: [
{
userId: '$PLACE_HOLDER_LOCAL_MAIN$',
roomId: '',
x: 0,
y: 0,
width: 1440,
height: 1080,
zOrder: 0,
streamType: 0,
},
...coHostUserIds.map((userId, index) => ({
userId,
roomId: '',
x: 1440,
y: index * (1080 / Math.min(coHostUserIds.length, 4)),
width: 480,
height: 1080 / Math.min(coHostUserIds.length, 4),
zOrder: index + 1,
streamType: 0,
})),
],
};
await this.trtc.startMixTranscode(mixConfig);
// Push mixed stream to CDN
await this.trtc.startPublishCDNStream({
appId: this.sdkAppId,
bizId: 0,
url: cdnUrl,
});
console.log(`Mixed CDN publish started with ${coHostUserIds.length} co-hosts`);
}
// Multi-quality CDN output (adaptive for viewers on different networks)
async startMultiQualityCDN(baseStreamId: string, cdnUrls: {
hd: string; // 1080p
sd: string; // 720p
ld: string; // 480p
}): Promise<void> {
const qualities = [
{ url: cdnUrls.hd, width: 1920, height: 1080, bitrate: 3000 },
{ url: cdnUrls.sd, width: 1280, height: 720, bitrate: 1500 },
{ url: cdnUrls.ld, width: 854, height: 480, bitrate: 800 },
];
for (const quality of qualities) {
await this.trtc.startPublishCDNStream({
appId: this.sdkAppId,
bizId: 0,
url: quality.url,
});
}
console.log('Multi-quality CDN streams started (1080p, 720p, 480p)');
}
// Stop CDN distribution
async stopCDNPublish(cdnUrl: string): Promise<void> {
await this.trtc.stopPublishCDNStream({ url: cdnUrl });
}
// Generate playback URL for viewers who don't need ultra-low latency
static generatePlaybackUrl(streamId: string, format: 'flv' | 'hls' | 'm3u8'): string {
const domain = process.env.NEXT_PUBLIC_CDN_DOMAIN;
return `https://${domain}/live/${streamId}.${format}`;
}
}
export default CDNDistribution;Hybrid Delivery Strategy
// lib/delivery-strategy.ts
/**
* Smart delivery routing:
* - <1000 viewers: All on TRTC real-time (lowest latency, full interactivity)
* - 1000-10000 viewers: Active chatters on TRTC, passive viewers on CDN
* - >10000 viewers: Only co-hosts + VIP on TRTC, everyone else on CDN
*/
interface DeliveryDecision {
mode: 'realtime' | 'cdn' | 'hybrid';
realtimeUrl?: string;
cdnUrl?: string;
latency: string;
}
export function chooseDeliveryMode(
viewerCount: number,
isTokenHolder: boolean,
isInteracting: boolean
): DeliveryDecision {
// Token holders (NFT pass / minimum stake) always get real-time
if (isTokenHolder) {
return {
mode: 'realtime',
latency: '<300ms',
};
}
if (viewerCount < 1000) {
return {
mode: 'realtime',
latency: '<300ms',
};
}
if (viewerCount < 10000 && isInteracting) {
return {
mode: 'realtime',
latency: '<300ms',
};
}
// Large audience, not interacting, no special token
return {
mode: 'cdn',
cdnUrl: CDNDistribution.generatePlaybackUrl('stream_id', 'flv'),
latency: '2-5s',
};
}This hybrid approach is critical for web3 streaming economics. TRTC real-time delivery costs more per viewer than CDN, so smart routing keeps costs manageable while giving token holders and active participants the premium experience that justifies their investment.
Step 8: NFT-Gated Stream Access
One of the most powerful web3 streaming features: gate access to exclusive streams based on NFT ownership or token holdings. Creators can monetize through NFT passes instead of monthly subscriptions.
// lib/nft-gate.ts
import { Contract, BrowserProvider } from 'ethers';
const ERC721_ABI = [
'function balanceOf(address owner) view returns (uint256)',
'function tokenOfOwnerByIndex(address owner, uint256 index) view returns (uint256)',
];
const ERC20_ABI = [
'function balanceOf(address account) view returns (uint256)',
];
interface GateConfig {
type: 'nft' | 'token' | 'either';
nftContract?: string;
tokenContract?: string;
minTokenBalance?: bigint;
chainId: number;
}
export async function verifyStreamAccess(
walletAddress: string,
gateConfig: GateConfig
): Promise<{ allowed: boolean; reason?: string }> {
const provider = new BrowserProvider(window.ethereum);
// Verify correct chain
const network = await provider.getNetwork();
if (Number(network.chainId) !== gateConfig.chainId) {
return {
allowed: false,
reason: `Please switch to chain ID ${gateConfig.chainId}`,
};
}
let hasNFT = false;
let hasTokens = false;
// Check NFT ownership
if (gateConfig.nftContract && (gateConfig.type === 'nft' || gateConfig.type === 'either')) {
const nft = new Contract(gateConfig.nftContract, ERC721_ABI, provider);
const balance = await nft.balanceOf(walletAddress);
hasNFT = balance > 0n;
}
// Check token balance
if (gateConfig.tokenContract && (gateConfig.type === 'token' || gateConfig.type === 'either')) {
const token = new Contract(gateConfig.tokenContract, ERC20_ABI, provider);
const balance = await token.balanceOf(walletAddress);
hasTokens = balance >= (gateConfig.minTokenBalance || 0n);
}
if (gateConfig.type === 'nft') return { allowed: hasNFT, reason: hasNFT ? undefined : 'NFT pass required' };
if (gateConfig.type === 'token') return { allowed: hasTokens, reason: hasTokens ? undefined : 'Insufficient token balance' };
if (gateConfig.type === 'either') return { allowed: hasNFT || hasTokens };
return { allowed: false };
}
// Middleware: verify before allowing stream join
export async function gatedStreamJoin(
viewer: any,
roomId: number,
gateConfig: GateConfig
): Promise<void> {
const { allowed, reason } = await verifyStreamAccess(viewer.address, gateConfig);
if (!allowed) {
throw new Error(`Access denied: ${reason || 'Does not meet access requirements'}`);
}
// Access verified, proceed to join stream
await viewer.joinStream({
roomId,
userId: viewer.trtcUserId,
userSig: viewer.trtcUserSig,
sdkAppId: Number(process.env.NEXT_PUBLIC_TRTC_SDKAPPID),
});
}Web3 Music Streaming: Live Performance Use Case
Web3 music streaming is one of the highest-value applications of decentralized live streaming. Artists perform live, fans tip in tokens, and the entire revenue goes directly to the artist—no label cut, no platform cut, no delayed royalty payments.
How It Works
- Artist mints event NFTs — Limited edition tickets that grant access to the live performance
- Fans purchase NFTs — Primary sale revenue goes to artist; secondary sales include automatic royalties
- Live performance — Artist streams via TRTC Live SDK with sub-300ms latency
- Real-time tipping — Fans send tokens during the performance with on-screen messages
- Recording as NFT — Post-show, the recording becomes an NFT that ticket holders can collect
Revenue Comparison: Web2 vs Web3 Music Streaming
| Revenue Source | Web2 (Spotify/YouTube) | Web3 (Token + NFT) |
|---|---|---|
| Per-stream payment | $0.003–0.005 | N/A (direct tips) |
| Live stream tips | 50% platform cut | 5% protocol fee |
| Ticket sales | 20–30% platform + fees | 5% protocol fee |
| Merch/collectibles | Via third-party marketplace | Direct NFT sales, perpetual royalties |
| Subscription revenue | $0.0001 per stream | Token staking rewards |
| Total per 1000 fans/month | $50–200 | $2,000–10,000+ |
Platforms like Audius have proven the model for recorded music. Adding live performance via TRTC's low-latency streaming creates the interactive experience that justifies premium token-based pricing.
Implementation: Music Live Event
// pages/music-event/[eventId].tsx
import { useEffect, useState } from 'react';
import LiveViewer from '../../components/Viewer';
import { verifyStreamAccess } from '../../lib/nft-gate';
import TokenTipper from '../../components/TokenTipper';
interface MusicEvent {
eventId: string;
artist: string;
artistWallet: string;
nftContract: string;
roomId: number;
startTime: number;
genre: string;
}
export default function MusicEventPage({ event }: { event: MusicEvent }) {
const [accessGranted, setAccessGranted] = useState(false);
const [viewer] = useState(new LiveViewer());
const [user, setUser] = useState<any>(null);
const [totalTips, setTotalTips] = useState(0);
const [viewerCount, setViewerCount] = useState(0);
const verifyAndJoin = async () => {
const { allowed, reason } = await verifyStreamAccess(
user.address,
{
type: 'nft',
nftContract: event.nftContract,
chainId: 137, // Polygon for low gas fees
}
);
if (!allowed) {
alert(`Access denied: ${reason}. Purchase the event NFT to join.`);
return;
}
setAccessGranted(true);
await viewer.joinStream({
roomId: event.roomId,
userId: user.trtcUserId,
userSig: user.trtcUserSig,
sdkAppId: Number(process.env.NEXT_PUBLIC_TRTC_SDKAPPID),
});
};
return (
<div className="music-event">
<header>
<h1>{event.artist} Live</h1>
<span className="viewer-count">{viewerCount} watching</span>
<span className="total-tips">{totalTips} USDC earned</span>
</header>
{accessGranted ? (
<>
<div id='remote-video-container' className="concert-view" />
<TokenTipper
recipientAddress={event.artistWallet}
senderAddress={user.address}
roomId={event.roomId}
onTipSuccess={(amount) => setTotalTips(prev => prev + Number(amount))}
/>
</>
) : (
<div className="gate-screen">
<p>This is an NFT-gated live performance.</p>
<button onClick={verifyAndJoin}>Verify NFT & Join</button>
<a href={`/mint/${event.nftContract}`}>Purchase Event NFT</a>
</div>
)}
</div>
);
}MCP Server Integration for Rapid Development
Building web3 streaming infrastructure involves integrating multiple SDKs—blockchain, streaming, interaction. TRTC's MCP (Model Context Protocol) server accelerates this by letting AI assistants directly access SDK documentation, generate integration code, and troubleshoot issues.
{
"mcpServers": {
"trtc": {
"command": "npx",
"args": ["-y", "@anthropic/trtc-mcp-server"],
"env": {
"TRTC_SDK_APP_ID": "your_sdk_app_id",
"TRTC_SECRET_KEY": "your_secret_key"
}
}
}
}With the MCP server configured, you can:
- Generate room configurations — Ask your AI assistant to create optimized room settings for different streaming scenarios
- Debug connection issues — The MCP server provides real-time diagnostics
- Generate integration code — Get working code snippets for specific features (co-hosting, CDN relay, etc.)
- Optimize quality settings — Get recommendations based on your target audience's network conditions
This is particularly valuable for web3 streaming projects where you're simultaneously integrating blockchain contracts, streaming SDKs, and interaction features. The MCP server reduces the context-switching overhead of consulting multiple documentation sources.
Production Deployment Checklist
Infrastructure Requirements
| Component | Recommendation | Why |
|---|---|---|
| TRTC Live SDK | Live product page | <300ms latency, 100K+ viewers, built-in CDN relay |
| Blockchain | Polygon or Base | Low gas fees for frequent tip transactions |
| Smart Contracts | OpenZeppelin + custom | Battle-tested security for handling funds |
| Storage | IPFS + Arweave | Stream recordings, NFT metadata |
| Frontend | Next.js + Vercel | SEO, SSR for stream discovery pages |
| Wallet | WalletConnect v2 | Multi-wallet support, mobile-friendly |
Performance Benchmarks
Based on production deployments using TRTC Live SDK for entertainment streaming:
- Broadcaster to viewer latency: 200–400ms (global average)
- Bullet comment delivery: <150ms
- Co-host connection: <500ms to establish
- CDN relay startup: <2 seconds
- Maximum concurrent viewers: 100,000+ per room (CDN mode)
- Token tip confirmation: 2–5 seconds (Polygon), 12–15 seconds (Ethereum)
Security Considerations
- UserSig generation — Always generate on your backend, never expose SecretKey to the client
- Smart contract audits — Get tipping contracts audited before handling real funds
- Rate limiting — Implement rate limits on tip transactions to prevent spam
- NFT verification — Verify on-chain at stream join, not just at page load (prevents token transfers after verification)
- Room access control — Use TRTC's room permission features in combination with NFT gates
Token Economics Design
A sustainable web3 streaming platform needs carefully designed token economics. Here's a proven model:
Platform Token Utility
┌─────────────────────────────────────────────────────┐
│ PLATFORM TOKEN ($STREAM) │
├─────────────────────────────────────────────────────┤
│ │
│ Staking: │
│ ├── Stake to access premium streams │
│ ├── Stake to earn platform revenue share │
│ └── Stake to participate in governance │
│ │
│ Utility: │
│ ├── Tip creators (with tip multiplier bonus) │
│ ├── Purchase NFT stream passes │
│ ├── Unlock enhanced viewer features │
│ └── Vote on featured creators │
│ │
│ Earning: │
│ ├── Watch-to-earn (attention rewards) │
│ ├── Create-to-earn (streaming rewards) │
│ ├── Moderate-to-earn (community rewards) │
│ └── Refer-to-earn (growth rewards) │
│ │
└─────────────────────────────────────────────────────┘Revenue Distribution Smart Contract
// contracts/RevenueDistribution.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract RevenueDistribution {
// Revenue split: transparent and immutable
uint256 public constant CREATOR_SHARE = 8500; // 85%
uint256 public constant STAKER_SHARE = 1000; // 10%
uint256 public constant PROTOCOL_SHARE = 500; // 5%
uint256 public constant TOTAL_BPS = 10000;
address public stakingPool;
address public protocolTreasury;
mapping(address => uint256) public creatorRevenue;
mapping(address => uint256) public totalDistributed;
event RevenueDistributed(
address indexed creator,
uint256 creatorAmount,
uint256 stakerAmount,
uint256 protocolAmount
);
constructor(address _stakingPool, address _treasury) {
stakingPool = _stakingPool;
protocolTreasury = _treasury;
}
function distributeRevenue(address creator) external payable {
require(msg.value > 0, "No revenue to distribute");
uint256 creatorAmount = (msg.value * CREATOR_SHARE) / TOTAL_BPS;
uint256 stakerAmount = (msg.value * STAKER_SHARE) / TOTAL_BPS;
uint256 protocolAmount = msg.value - creatorAmount - stakerAmount;
payable(creator).transfer(creatorAmount);
payable(stakingPool).transfer(stakerAmount);
payable(protocolTreasury).transfer(protocolAmount);
creatorRevenue[creator] += creatorAmount;
totalDistributed[creator] += msg.value;
emit RevenueDistributed(creator, creatorAmount, stakerAmount, protocolAmount);
}
}Comparison: Creator Economics Across Platforms
| Platform Model | Creator Revenue | Payment Speed | Transparency |
|---|---|---|---|
| YouTube (Ad-based) | 55% of ad revenue | Monthly, $100 min | Opaque algorithm |
| Twitch (Sub-based) | 50% of subs | 15-day hold | Limited dashboard |
| Patreon (Direct) | 88–95% | Monthly | Transaction-level |
| Web3 Stream (Token) | 85–95% | Instant | On-chain, verifiable |
The key advantage isn't just the percentage—it's the speed and transparency. Creators see exactly how much they earned, from whom, in real-time, and receive funds instantly. No opaque algorithms, no payment holds, no disputed revenue.
Scaling to 100K+ Concurrent Viewers
Real web3 streaming platforms need to handle viral moments. Here's the scaling architecture:
Tier 1: Interactive Core (0–1,000 viewers)
All viewers on TRTC real-time. Full interactivity—bullet comments, co-hosting, instant reactions. Cost: higher per-viewer, but small absolute numbers.
Tier 2: Hybrid Mode (1,000–50,000 viewers)
- Top 1,000 most active viewers (based on tipping/chatting) stay on TRTC real-time
- Remaining viewers receive CDN stream (2–5s delay)
- Token holders can "upgrade" to real-time tier anytime
Tier 3: CDN-Primary (50,000+ viewers)
- Co-hosts + VIP token holders on TRTC real-time
- Active chatters on low-latency CDN (FLV, ~1s delay)
- Passive viewers on standard CDN (HLS, 3–8s delay)
// lib/scaling.ts
export class StreamScaler {
private viewerTiers: Map<string, 'realtime' | 'low-latency-cdn' | 'standard-cdn'> = new Map();
assignTier(userId: string, factors: {
isTokenHolder: boolean;
isCoHost: boolean;
recentInteractions: number;
totalTipped: number;
currentViewerCount: number;
}): string {
const { isTokenHolder, isCoHost, recentInteractions, totalTipped, currentViewerCount } = factors;
// Always real-time for co-hosts
if (isCoHost) {
this.viewerTiers.set(userId, 'realtime');
return 'realtime';
}
// Token holders get priority real-time access
if (isTokenHolder && currentViewerCount < 50000) {
this.viewerTiers.set(userId, 'realtime');
return 'realtime';
}
// Active participants get real-time up to capacity
if (recentInteractions > 5 || totalTipped > 10) {
const realtimeCount = Array.from(this.viewerTiers.values())
.filter(t => t === 'realtime').length;
if (realtimeCount < 1000) {
this.viewerTiers.set(userId, 'realtime');
return 'realtime';
}
}
// Default to CDN based on total viewer count
if (currentViewerCount < 50000) {
this.viewerTiers.set(userId, 'low-latency-cdn');
return 'low-latency-cdn';
}
this.viewerTiers.set(userId, 'standard-cdn');
return 'standard-cdn';
}
}Comparing Web3 Streaming Infrastructure Options
| Feature | Livepeer | Theta Network | AIOZ | TRTC Live SDK |
|---|---|---|---|---|
| Primary function | Transcoding | CDN relay | Storage + delivery | Full live streaming |
| End-to-end latency | 5–15s | 3–8s | 5–10s | <300ms |
| Real-time interaction | No | Limited | No | Yes (built-in) |
| Co-hosting | No | No | No | Up to 8 anchors |
| Bullet comments | No | No | No | Custom message channel |
| CDN distribution | Via third-party | Native P2P | Native P2P | Built-in relay to CDN |
| Scalability | High (transcoding) | High (delivery) | Medium | 100K+ per room |
| Smart contract integration | Token-based payments | TFUEL payments | AIOZ token | Wallet auth + custom |
| SDK availability | API only | Limited SDK | API only | Web, iOS, Android, Flutter |
| Global coverage | Depends on nodes | Depends on edge | Depends on nodes | 200+ countries, <300ms |
The practical approach: use Livepeer or Theta for decentralized transcoding/CDN as a complement, and TRTC for the real-time interactive layer that viewers actually experience. This gives you decentralized infrastructure credentials while delivering professional streaming quality.
Complete Project Structure
web3-streaming-platform/
├── contracts/
│ ├── StreamTipping.sol # Tipping with 5% protocol fee
│ ├── RevenueDistribution.sol # Revenue split logic
│ ├── StreamAccessNFT.sol # NFT-gated access passes
│ └── PlatformToken.sol # $STREAM token contract
├── lib/
│ ├── auth.ts # Wallet authentication
│ ├── trtc-auth.ts # TRTC UserSig generation
│ ├── nft-gate.ts # NFT verification
│ ├── cdn-config.ts # CDN distribution setup
│ ├── delivery-strategy.ts # Hybrid delivery routing
│ └── scaling.ts # Viewer tier management
├── components/
│ ├── Broadcaster.tsx # Creator streaming component
│ ├── Viewer.tsx # Audience viewing component
│ ├── CoHostManager.tsx # Multi-person co-hosting
│ ├── BulletComments.tsx # Danmaku overlay system
│ └── TokenTipper.tsx # Token tipping UI
├── pages/
│ ├── broadcast.tsx # Go-live page
│ ├── watch/[roomId].tsx # Stream viewing page
│ └── music-event/[eventId].tsx # NFT-gated music events
├── hardhat.config.ts # Smart contract deployment
├── .env.local # API keys (never commit)
└── package.jsonGetting Started
- Register for TRTC — Get your SDKAppID and SecretKey at trtc.io/register
- Deploy smart contracts — Use Hardhat to deploy tipping and NFT contracts to Polygon testnet
- Implement broadcaster — Start with Step 2 code, test with a single stream
- Add viewer experience — Implement Step 3, verify <300ms latency
- Integrate token tipping — Deploy the tipping contract, connect the frontend component
- Add bullet comments — Implement the danmaku system for viewer engagement
- Configure CDN — Set up relay push for scaling beyond 1,000 viewers
- Test NFT gating — Mint test NFTs, verify access control works
The TRTC Live SDK documentation provides additional configuration options for bitrate optimization, beauty filters, and advanced mixing layouts.
For entertainment live streaming features like virtual gifts with animations, PK battles between streamers, and audience gaming, TRTC provides pre-built solutions that integrate directly with the web3 token layer described in this guide.
TRTC for Web3 Live Streaming: Solving Real Pain Points
The architecture above gives you the blueprint. But web3 streaming platforms face operational challenges that only surface at scale, with real users on real networks across real borders. Here's how TRTC solves the specific pain points that web3 live streaming customers hit in production.
Real-Time Translation for Global Audiences
Web3 communities are inherently global. A Korean streamer might have their largest fanbase in China, or a Spanish-speaking creator could be tipping-heavy with English-speaking viewers. Language barriers kill engagement in real-time streams — unlike recorded content, you can't pause and Google Translate a live broadcast.
TRTC integrates a full ASR + TTS + translation pipeline with Livekit, achieving end-to-end translation latency under 500ms. Streamers broadcast in their native language while viewers hear translated audio in real-time. A Korean streamer speaks Korean; their Chinese audience hears Chinese. No manual interpreters, no subtitle delay, no engagement drop. For web3 music streaming platforms with global artist rosters, this means any creator can reach any audience without language being a conversion barrier.
Adaptive Quality with Simulcast
Not every viewer in a web3 community has fiber internet. Many access streams through mobile connections, VPNs, or from regions with inconsistent bandwidth. When a stream buffers, viewers leave — and in a token-tipping economy, every lost viewer is lost revenue.
TRTC's multi-resolution Simulcast solves this at the infrastructure level. The broadcaster's stream is encoded at multiple resolutions simultaneously. Viewers on poor networks automatically receive a lower resolution stream with zero rebuffering. Users can also manually switch quality if they prefer. The key difference from simple adaptive bitrate: Simulcast maintains separate encoded streams rather than re-encoding on the fly, so quality transitions are instantaneous with no visual artifacts.
Sub-Second Stream Start (Even Through VPNs)
Many web3 users route their traffic through VPNs for privacy or to access region-locked content. Standard WebRTC connections through VPN tunnels can take 10+ seconds to pull the first frame — an eternity when a viewer is deciding whether to stay or bounce.
TRTC achieves sub-second first-frame delivery even through VPN tunnels by using WebSocket pre-connection optimization for the Livekit/TRTC stack. The connection handshake begins before the viewer explicitly hits "play," and transport negotiation is optimized to minimize round trips through tunnel overhead. The result: viewers see video within one second of joining, regardless of their network topology.
China-to-Global Streaming Without Cross-Border Latency
A significant number of web3 streamers are based in China but broadcast to global audiences. The default behavior of most streaming platforms — pushing to overseas ingest nodes — creates brutal latency for the broadcaster. OBS stalls, frames drop, and the streamer's experience degrades even though viewers might be fine.
TRTC deploys domestic RTMP acceleration nodes specifically for this scenario. Streamers push their stream to nearby Chinese nodes with minimal latency, and TRTC's global network handles the cross-border delivery to viewers worldwide. The broadcaster gets a smooth, stable streaming experience regardless of where their audience sits. This is particularly critical for web3 music streaming artists in China performing live for international token-holding fans.
Cross-Platform Co-Streaming and PK Battles
Web3 streaming platforms need to work everywhere their community lives — desktop apps, web browsers, iOS, Android. Co-hosting and PK battles (competitive streaming where two creators go head-to-head for tips) are engagement drivers that require all participants to be on compatible platforms.
TRTC supports full cross-platform co-hosting and PK battles out of the box across Electron, Web, iOS, and Android. A creator on their desktop Electron app can PK with a mobile streamer on iOS while a third co-host joins from a web browser. No platform restrictions, no compatibility issues, no "please download our desktop app to co-stream" friction.
Virtual Avatar Streaming with Digital Humans
Some web3 creators prefer streaming as virtual avatars — whether for privacy, branding, or because their "creator" is an AI-driven digital persona. Virtual avatar streaming (also called VTubing or digital human streaming) requires a pipeline that captures motion or AI-generated expressions and renders them as a live character.
TRTC supports virtual avatar streaming through the OBS pipeline. Digital humans can be driven by AI (fully automated virtual streamers) or by motion capture (real person controlling an avatar). The rendered avatar output feeds into TRTC's streaming infrastructure like any other video source. For web3 projects building AI-powered community hosts or branded virtual personalities, this is the integration path. See the virtual avatar streaming documentation for implementation details.
Multi-Language Content Moderation
Global web3 communities generate chat, voice, and visual content in dozens of languages. Moderation that only works in English or Chinese leaves gaps that bad actors exploit — spam, hate speech, or inappropriate content in languages your moderation system doesn't understand.
TRTC includes built-in content moderation with expanding support for minor languages. The moderation system covers text messages, voice content (via real-time transcription), and image/video frames. As web3 streaming platforms scale across regions, moderation coverage scales with them rather than requiring per-language third-party integrations.
Recording and Playback Pipeline
Most web3 streaming platforms want to offer recordings of past streams — for viewers who missed the live event, for creators who want to repurpose content, or for NFT-gated replay access. The full pipeline is: record the live stream → optionally clip highlights → play back on demand.
TRTC provides cloud recording as a built-in feature. Streams are captured server-side with no performance impact on the broadcaster. For playback, client-side player integration (using TRTC's SDK or a third-party player) handles the viewing experience. The complete record-to-clip-to-play pipeline is on TRTC's roadmap, but today's architecture already supports the most common use case: automatic cloud recording with on-demand playback through your existing player infrastructure.
Global Ban and Super Admin Controls
In web3 streaming, one toxic user can hop between rooms, harassing different creators. Room-level bans are whack-a-mole. Platform operators need the ability to globally ban a user across all active streams within a time period.
TRTC leverages its IM global mute capability to solve this. When a super admin bans a user, that user is muted across ALL rooms for the duration of the ban — not just the room where the violation occurred. This is enforced at the infrastructure level, so the banned user can't circumvent it by switching rooms or reconnecting. See the global mute documentation for API details on implementing cross-room moderation in your admin panel.
FAQ
What latency can I achieve with web3 live streaming?
Using TRTC's Live SDK, you get 200–400ms end-to-end latency globally. This is comparable to or better than centralized platforms like Twitch (1–3 seconds) and significantly better than pure P2P solutions (3–10 seconds). The blockchain layer (token tips, NFT verification) runs asynchronously and doesn't add to stream latency.
How much does it cost to run a web3 streaming platform?
TRTC offers a free tier for development and testing. Production costs scale with concurrent viewers: real-time delivery is priced per minute per user, CDN relay is priced by bandwidth. For a platform with 10,000 concurrent viewers using the hybrid delivery model described above, expect $500–2,000/month for streaming infrastructure. Smart contract gas costs on Polygon are negligible (<$0.01 per tip transaction).
Can I use this for web3 music streaming?
Yes. The architecture described here supports live music performances with features Spotify can't match: real-time tipping during performances, NFT ticket access, bullet comments for audience interaction, and instant payouts. Artists using web3 music streaming platforms report 10–50x more revenue per fan compared to traditional streaming royalties.
How do I handle content moderation in a decentralized system?
Decentralized doesn't mean unmoderated. Implement community-driven moderation where token stakers can flag content, combined with AI-based content screening on the streaming layer. TRTC provides content moderation APIs that work with the live stream without adding latency.
What blockchain should I use?
For tip-heavy applications, use Polygon or Base (low gas fees, fast confirmation). For high-value NFT sales, Ethereum L1 provides the strongest security guarantees. Many platforms use both: L2 for frequent transactions, L1 for high-value mints and governance.
How does GVoice fit into web3 streaming?
For web3 gaming streams where the broadcaster is playing a multiplayer game, GVoice provides in-game voice chat between players while TRTC Live SDK handles the audience-facing stream. This separation ensures game audio quality isn't affected by stream encoding.
Conclusion
Web3 live streaming isn't theoretical—it's buildable today. The combination of blockchain-based ownership, token economics, and professional streaming infrastructure creates a system where creators earn 85–95% of revenue instantly, viewers interact with sub-second latency, and the entire economic layer is transparent and verifiable on-chain.
The technical stack is clear: TRTC Live SDK handles the streaming layer (broadcasting, viewing, co-hosting, CDN distribution), smart contracts handle the economic layer (tipping, revenue distribution, access control), and your application layer ties them together with wallet authentication and stream discovery.
Start with a single creator streaming to a small audience with token tipping. Validate the economic model. Then scale: add CDN distribution for larger audiences, NFT-gated events for premium content, and multi-person co-hosting for collaborative streams. Every component in this guide is production-tested and ready to deploy.
The platforms taking 30–50% of creator revenue won't change voluntarily. Web3 streaming builds the alternative. Get started with TRTC Live SDK and ship your first decentralized stream this week.


