Skip to content

feat: clip creation from live streams #371

@davedumto

Description

@davedumto

Overview

Let viewers and streamers create short clips (30–60s) from a live stream by clicking a ✂️ Clip button. Clips are the primary viral mechanic on streaming platforms — a great moment gets clipped, shared, and drives new viewers back.

How it works

  1. Viewer/streamer clicks ✂️ Clip button on the player during a live stream
  2. Selects clip length: 30s or 60s
  3. POST /api/streams/clips is called:
    • Calls Mux to create an asset from the last N seconds of the DVR stream
    • Inserts a stream_recordings row with status: 'processing', clip: true
  4. Mux video.asset.ready webhook fires → updates status to ready
  5. User is notified (links to notification issue feat: real-time notifications system #365) with a share link

Mux DVR requirement

Clipping requires live DVR to be enabled when the stream is created. Update POST /api/streams/create:

{
  latency_mode: "standard",  // required for DVR
  use_slate_for_standard_latency: false,
  reconnect_window: 60,
}

Clip creation (Mux API)

// POST /api/streams/clips
const asset = await mux.video.assets.create({
  input: [{
    url: `https://stream.mux.com/${playbackId}.m3u8`,
    start_time: Math.floor(Date.now() / 1000) - durationSeconds,
    end_time: Math.floor(Date.now() / 1000),
  }],
  playback_policy: ["public"],
  mp4_support: "standard",
});

// Insert into DB
await sql`
  INSERT INTO stream_recordings
    (id, user_id, playback_id, mux_asset_id, status, clip, clipped_by, source_stream_id)
  VALUES
    (${uuid}, ${streamOwnerId}, ${asset.playback_ids[0].id},
     ${asset.id}, 'processing', true, ${clippedByUsername}, ${streamId})
`;

DB changes

ALTER TABLE stream_recordings ADD COLUMN IF NOT EXISTS clip BOOLEAN DEFAULT false;
ALTER TABLE stream_recordings ADD COLUMN IF NOT EXISTS clipped_by TEXT;
ALTER TABLE stream_recordings ADD COLUMN IF NOT EXISTS source_stream_id TEXT;

API route

POST /api/streams/clips

  • Auth required (session cookie)
  • Rate limited: max 5 clips per user per stream
  • Request: { playback_id, duration_seconds: 30 | 60, stream_owner_username }
  • Response: { clip_id, status: "processing" }

UI

Clip button on player (view-stream.tsx)

  • ✂️ icon in player controls overlay (bottom-right, near fullscreen)
  • Only visible when stream is live and user is logged in
  • Opens a small popover: "30 second clip" / "60 second clip" → Confirm
  • "Creating clip..." loading state → "Clip ready! Share →" toast with link

Clips page (/[username]/clips)

  • Clips show a ✂️ badge on the thumbnail
  • "Clipped by @username" shown if clipped by a viewer (not the owner)
  • Owner can delete clips from their page (already implemented)

Share

  • On clip ready: clipboard copy of /[username]/clips/[id]
  • OG metadata already works for clip pages (already implemented)

Acceptance criteria

  • Mux DVR enabled on stream creation
  • ✂️ Clip button visible on live stream player for logged-in users
  • 30s/60s length selection
  • POST /api/streams/clips creates Mux asset from DVR stream
  • stream_recordings row inserted with status: processing
  • Mux video.asset.ready webhook updates status to ready
  • Clip visible on /[username]/clips with ✂️ badge
  • Share link with working OG metadata
  • Rate limit: max 5 clips per user per stream
  • Notification sent when clip is ready

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions