How We Added Video Playlists to SendRec
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, optionalshare_password, optional email gating). - The playlist_videos table stores the ordering of videos. A composite primary key prevents duplicate entries, and
ON DELETE CASCADEensures 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
localStoragekeyed 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-srcforces all visibility toggling to use CSS classes (.hidden,.gate-error.visible) andclassListmanipulation. - 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
000039runs on startup.