How We Added Video Playlists to SendRec

Published: (February 23, 2026 at 05:29 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Overview

Folders group videos by project and tags cross‑label them, but neither solves the common request: “I want to send someone five videos in a specific order and have them play through automatically.”

In SendRec v1.57.0 we added playlists. Users can create a playlist, add videos, drag them into the desired order, and share a link. Viewers receive a dark‑themed watch page with a sidebar, auto‑advance, and watched badges.


Database Migration

Two new tables are introduced.

-- playlists table
CREATE TABLE playlists (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    title TEXT NOT NULL,
    description TEXT,
    share_token TEXT UNIQUE,
    is_shared BOOLEAN NOT NULL DEFAULT false,
    share_password TEXT,
    require_email BOOLEAN NOT NULL DEFAULT false,
    position INTEGER NOT NULL DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- playlist_videos junction table
CREATE TABLE playlist_videos (
    playlist_id UUID NOT NULL REFERENCES playlists(id) ON DELETE CASCADE,
    video_id UUID NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
    position INTEGER NOT NULL DEFAULT 0,
    PRIMARY KEY (playlist_id, video_id)
);
  • The playlists table mirrors the sharing pattern used for videos (public share_token, optional share_password, optional email gating).
  • The playlist_videos table stores the ordering of videos. A composite primary key prevents duplicate entries, and ON DELETE CASCADE ensures that removing a playlist or a video cleans up related rows automatically.

JSON Aggregation for Video‑Playlist Membership

Each video now returns the playlists it belongs to using a single JSON aggregate subquery, avoiding N+1 queries:

COALESCE(
    (SELECT json_agg(
        json_build_object('id', p.id, 'title', p.title)
        ORDER BY p.title
    )
    FROM playlist_videos pv
    JOIN playlists p ON p.id = pv.playlist_id
    WHERE pv.video_id = v.id),
    '[]'::json
) AS playlists_json

The COALESCE guarantees an empty array ([]) when a video isn’t part of any playlist.


Server‑Rendered Watch Page

Shared playlists are served at:

/watch/playlist/{shareToken}

The page is a Go HTML template with embedded CSS and vanilla JavaScript—no React, no build step, no client‑side routing.

Layout

  • Two‑panel design – a dark sidebar on the left lists videos (thumbnails, titles, durations, position numbers) and the player on the right.
  • The currently playing video is highlighted.
  • A checkmark appears next to videos the viewer has watched (≥ 80 % of duration). Watched state is persisted in localStorage keyed by the playlist’s share token.

Auto‑Advance Countdown

When a video ends, a 5‑second countdown overlay appears:

function startCountdown(nextIndex) {
    var nextVideo = videos[nextIndex];
    nextTitleEl.textContent = nextVideo.title;
    overlay.classList.remove('hidden');
    var remaining = 5000;
    countdownTimer = setInterval(function() {
        remaining -= 50;
        progressEl.style.width = Math.max(0, (remaining / 5000) * 100) + '%';
        if (remaining <= 0) {
            cancelCountdown();
            switchVideo(nextIndex);
        }
    }, 50);
}
  • Viewers can click “Play Now” to skip the countdown or “Cancel” to stay on the current video.
  • The progress bar updates every 50 ms for smooth animation.

Security & Gating

  • Content Security Policy with nonce‑based style-src forces all visibility toggling to use CSS classes (.hidden, .gate-error.visible) and classList manipulation.
  • Password protection – a password form appears before any video loads. On success, a signed HMAC cookie is set.
  • Email gate – viewers submit an email; it is stored in a signed cookie for subsequent visits.

Both gates reuse the same cookie‑signing and verification logic as the individual video watch page. The only difference is the API endpoint:

  • /api/watch/playlist/{shareToken}/verify (playlist)
  • /api/watch/{shareToken}/verify (single video)

Playlist Management UI

Video Detail View

In the Organization section, each video lists its playlist memberships alongside folders and tags. Users can:

  • Toggle a video’s inclusion in any playlist with a single click.
  • Search the playlist list when more than five playlists exist.

Playlist Detail Page

  • Add Videos modal includes a live search input that filters available videos by title.
  • Reordering is performed with up/down arrow buttons that send position updates to the API (no drag‑and‑drop UI).

Limitations

  • No playlist‑level analytics (individual video analytics still work).
  • No collaborative playlists across users.
  • No embed support for playlists.

Despite these constraints, the core use case—sending a curated, ordered set of videos—works as intended.


Release Information

  • Playlists are live at app.sendrec.eu in v1.57.0.
  • Free‑tier users can create up to 3 playlists.
  • Self‑hosted installations receive the feature automatically; migration 000039 runs on startup.
0 views
Back to Blog

Related posts

Read more »