DEV Community

ahmet gedik
ahmet gedik

Posted on

WebSocket Real-Time Notifications for Video Platforms with Python

Real-Time in a Multi-Region Video Platform

When a viral news clip appears in the UAE or a Danish documentary breaks into Nordic trending charts, TrendVidStream users should see it instantly. A Python WebSocket server bridges the gap between the PHP cron fetcher and the browser.

Architecture

PHP Cron Fetcher ──→ Redis Pub/Sub ──→ Python WS Server ──→ Browser clients
     (8 regions)        (one channel per region)           (filtered by locale)
Enter fullscreen mode Exit fullscreen mode

Python WebSocket Server

pip install websockets redis asyncio
Enter fullscreen mode Exit fullscreen mode
import asyncio
import json
import logging
import urllib.parse
import redis.asyncio as aioredis
import websockets
from websockets.server import WebSocketServerProtocol

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)

region_clients: dict[str, set[WebSocketServerProtocol]] = {}

async def broadcast_to_region(region: str, message: str) -> None:
    clients = region_clients.get(region, set())
    if not clients:
        return
    dead = set()
    results = await asyncio.gather(
        *(ws.send(message) for ws in clients), return_exceptions=True,
    )
    for ws, result in zip(list(clients), results):
        if isinstance(result, Exception):
            dead.add(ws)
    clients.difference_update(dead)

async def handler(websocket: WebSocketServerProtocol) -> None:
    qs = urllib.parse.parse_qs(urllib.parse.urlparse(websocket.path).query)
    region = qs.get('region', ['US'])[0].upper()
    region_clients.setdefault(region, set()).add(websocket)
    try:
        await websocket.wait_closed()
    finally:
        region_clients.get(region, set()).discard(websocket)

async def redis_subscriber() -> None:
    client = aioredis.from_url('redis://localhost:6379', decode_responses=True)
    pubsub = client.pubsub()
    channels = ['tvs:AE', 'tvs:FI', 'tvs:CZ', 'tvs:DK', 'tvs:BE', 'tvs:GB', 'tvs:CH', 'tvs:US']
    await pubsub.subscribe(*channels)

    async for msg in pubsub.listen():
        if msg['type'] != 'message':
            continue
        region = msg['channel'].split(':')[1]
        try:
            payload = json.loads(msg['data'])
            out = json.dumps(payload, ensure_ascii=False)
            await broadcast_to_region(region, out)
        except (json.JSONDecodeError, KeyError) as e:
            logger.warning(f'Bad payload: {e}')

async def main() -> None:
    server = await websockets.serve(handler, '0.0.0.0', 8765)
    await asyncio.gather(server.wait_closed(), redis_subscriber())

if __name__ == '__main__':
    asyncio.run(main())
Enter fullscreen mode Exit fullscreen mode

PHP Publisher with RTL Metadata

<?php
declare(strict_types=1);

class NotificationPublisher
{
    private \Redis $redis;

    public function __construct()
    {
        $this->redis = new \Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }

    public function publish(array $video, string $region): void
    {
        $payload = json_encode([
            'event'     => 'new_trending',
            'region'    => $region,
            'video_id'  => $video['video_id'],
            'title'     => $video['title'],
            'thumbnail' => $video['thumbnail_url'],
            'channel'   => $video['channel_title'],
            'rtl'       => in_array($region, ['AE', 'IL'], true),
            'ts'        => time(),
        ], JSON_UNESCAPED_UNICODE);

        $this->redis->publish("tvs:{$region}", $payload);
    }
}
Enter fullscreen mode Exit fullscreen mode

Browser Client with RTL Toast

class TrendingNotifier {
  constructor(region = 'AE') {
    this.ws = new WebSocket(`wss://ws.trendvidstream.com/?region=${region}`);
    this.ws.onmessage = (e) => {
      const data = JSON.parse(e.data);
      if (data.event === 'new_trending') this.showToast(data);
    };
  }

  showToast(video) {
    const toast = document.createElement('div');
    toast.className = 'trending-toast';
    if (video.rtl) toast.setAttribute('dir', 'rtl');
    toast.innerHTML = `<img src="${video.thumbnail}" alt=""><div><strong>Trending in ${video.region}</strong><span>${video.title}</span></div>`;
    document.body.appendChild(toast);
    setTimeout(() => toast.remove(), 5000);
  }
}

new TrendingNotifier(currentUserRegion);
Enter fullscreen mode Exit fullscreen mode

Multi-Region Considerations

TrendVidStream spans UAE (Arabic RTL), Czech Republic (diacritics: č, ž, š), Finland (ä, ö), and Denmark (ø, å). Key points:

  • Arabic RTL: send rtl: true in the payload so the browser can apply dir="rtl" dynamically.
  • JSON encoding: always JSON_UNESCAPED_UNICODE in PHP and ensure_ascii=False in Python so diacritics and Arabic glyphs transmit unmangled.
  • Connection per region: routing clients into per-region sets means a viral clip in Finland only wakes Finnish clients, not UAE users.

This article is part of the Building TrendVidStream series. Check out TrendVidStream to see these techniques in action.

Top comments (0)