How LunaTV Handles Playback History and Favorites Synchronization

LunaTV synchronizes playback history and favorites across devices using a stateless HTTP API with pluggable storage backends (Redis, Upstash, Kvrocks, or local storage), composite key identification, and signed-cookie authentication to ensure real-time data consistency.

LunaTV, an open-source streaming platform by MoonTechLab, implements a storage-agnostic architecture for persisting user-specific data. The system manages playback history (play records) and favorite items through a unified abstraction layer that supports both local and distributed storage engines. This design enables seamless cross-device synchronization while maintaining strict data integrity through composite key generation and automated migration handling.

Architecture Overview

Pluggable Storage Backends

According to the LunaTV source code, the platform selects its storage implementation at runtime via the NEXT_PUBLIC_STORAGE_TYPE environment variable. Supported backends include local storage, Redis, Upstash, and Kvrocks. The DbManager class in src/lib/db.ts abstracts all storage operations, ensuring consistent behavior regardless of the underlying engine.

All database access flows through the ensureMigrated method, which guarantees that any pending data migrations complete before executing read or write operations. This prevents synchronization errors during schema updates or backend switches.

Authentication and User Validation

Every request to the synchronization endpoints first extracts user identity via getAuthInfoFromCookie from src/lib/auth.ts. For non-owner users, the system validates against UserConfig.Users in src/lib/config.ts, checking both existence and ban status before permitting data access. This guard logic is shared identically between the playback history and favorites APIs.

Playback History Synchronization

The playback history system tracks user viewing progress through the /api/playrecords endpoint defined in src/app/api/playrecords/route.ts.

API Operations and Storage Methods

The endpoint supports five distinct operations mapped to DbManager methods:

  • Read all records: GET /api/playrecords calls db.getAllPlayRecords(username), which delegates to IStorage.getAllPlayRecords and returns an object mapping composite keys to PlayRecord objects.
  • Read specific record: Internally uses db.getPlayRecord(username, source, id) → IStorage.getPlayRecord.
  • Save or update: POST /api/playrecords accepts a body with {key, record}, parses the composite key into source and id, populates missing save_time fields, and invokes db.savePlayRecord(username, source, id, finalRecord) → IStorage.setPlayRecord.
  • Delete single: DELETE /api/playrecords?key=source+id triggers db.deletePlayRecord(username, source, id) → IStorage.deletePlayRecord.
  • Delete all: DELETE /api/playrecords (without key parameter) executes db.deleteAllPlayRecords(username) → IStorage.deleteAllPlayRecords.

Data Persistence Flow

When saving a play record, the server normalizes the input by ensuring the save_time timestamp is present. The composite key format <source>+<id> (e.g., douyin+12345) uniquely identifies media items across different platforms, preventing collisions between content from separate sources.

Favorites Synchronization

The favorites system mirrors the playback history architecture through the /api/favorites endpoint in src/app/api/favorites/route.ts.

CRUD Operations

Favorites implement identical CRUD patterns using the DbManager abstraction:

  1. List favorites: GET /api/favorites calls db.getAllFavorites(username) → IStorage.getAllFavorites, returning {[key]: Favorite}.
  2. Get specific: GET /api/favorites?key=source+id uses db.getFavorite(username, source, id) → IStorage.getFavorite.
  3. Create/update: POST /api/favorites validates required fields, constructs finalFavorite with save_time, and calls db.saveFavorite(username, source, id, finalFavorite) → IStorage.setFavorite.
  4. Remove single: DELETE /api/favorites?key=source+id invokes db.deleteFavorite(username, source, id) → IStorage.deleteFavorite.
  5. Clear all: DELETE /api/favorites executes db.deleteAllFavorites(username) → IStorage.deleteAllFavorites.

Key Generation Strategy

Both systems rely on the generateStorageKey helper function in src/lib/db.ts to construct composite keys. This ensures that a YouTube video with ID abcde generates the key youtube+abcde, creating a consistent namespace across all user data operations.

Cross-Device Synchronization Mechanics

LunaTV achieves real-time synchronization through three core mechanisms:

  • Stateless HTTP API: Clients (web or mobile) issue authenticated GET/POST/DELETE requests for every interaction. No local state assumptions exist between the client and server.
  • External Storage as Source of Truth: By persisting data in Redis, Upstash, or Kvrocks rather than browser-local storage, the server maintains a central repository accessible from any authenticated device.
  • Immediate Consistency: Each write operation updates the backend storage directly. Subsequent reads from any device instantly reflect the latest playback positions or favorite additions, eliminating synchronization lag.

Implementation Examples

Fetching Playback History

// Retrieve all play records for authenticated user
const response = await fetch('/api/playrecords', {
  credentials: 'include',
});
const playRecords = await response.json(); 
// Returns: { "douyin+12345": { title: "...", index: 1 }, ... }

Recording Playback Progress

// Save or update a play record
await fetch('/api/playrecords', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    key: 'douyin+12345',
    record: { 
      title: 'Example Video', 
      source_name: 'Douyin', 
      index: 1 
    }
  })
});

Managing Favorites

// Add a favorite
await fetch('/api/favorites', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    key: 'youtube+abcde',
    favorite: { 
      title: 'Cool Clip', 
      source_name: 'YouTube' 
    }
  })
});

// Remove a specific favorite
await fetch('/api/favorites?key=youtube+abcde', {
  method: 'DELETE',
  credentials: 'include'
});

Summary

  • Pluggable Architecture: LunaTV supports Redis, Upstash, Kvrocks, and local storage via runtime configuration in src/lib/db.ts.
  • Composite Keys: Media items are uniquely identified using the <source>+<id> format generated by generateStorageKey.
  • Unified API Pattern: Both playback history and favorites use identical CRUD patterns through DbManager methods with ensureMigrated safety checks.
  • Real-Time Sync: Stateless HTTP endpoints with external storage backends ensure immediate cross-device synchronization for all user data.
  • Secure Access: All endpoints validate users via signed cookies and check against UserConfig.Users before data access.

Frequently Asked Questions

What storage backends does LunaTV support for synchronization?

LunaTV supports four storage backends selectable via the NEXT_PUBLIC_STORAGE_TYPE environment variable: local storage for single-instance deployments, Redis for high-performance caching, Upstash for serverless Redis-compatible storage, and Kvrocks for disk-persistent key-value storage. The DbManager class abstracts all implementations behind the IStorage interface.

How does LunaTV authenticate requests for playback history and favorites?

Every synchronization request extracts user identity from signed HTTP cookies using getAuthInfoFromCookie in src/lib/auth.ts. For non-administrative users, the system validates the username against the UserConfig.Users registry and checks ban status before permitting access to personal data endpoints.

Can LunaTV migrate data between different storage backends?

Yes. The DbManager class implements an ensureMigrated method that runs before any storage operation. This ensures pending data migrations complete before accessing the backend, allowing administrators to switch from local storage to Redis (or vice versa) without data loss or corruption.

How does LunaTV handle simultaneous updates from multiple devices?

Since the storage backend (Redis, Upstash, etc.) serves as the single source of truth, the last-write-wins model applies. Each device sends stateless HTTP requests that immediately persist to the shared storage, ensuring that the most recent operation from any device becomes the current state visible to all subsequent reads.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →