Skip to content

Latest commit

 

History

History
245 lines (194 loc) · 5.9 KB

File metadata and controls

245 lines (194 loc) · 5.9 KB

Active Stream Detection - Prevent Duplicates ✅

Problem

When a broadcaster already has an active stream and visits the stream-obs page, a new stream was being created, leading to:

  • Multiple duplicate streams
  • Lost connection to the original stream
  • Confusion about which stream is active

Solution

Implemented automatic detection and loading of existing active streams.


How It Works

1. New Convex Query: getActiveStream

Location: /convex/streams.ts

export const getActiveStream = query({
  args: { userId: v.string() },
  handler: async (ctx, args) => {
    // Get last 5 streams
    const streams = await ctx.db
      .query("streams")
      .withIndex("by_host", (q) => q.eq("hostUserId", args.userId))
      .order("desc")
      .take(5);

    // Return first stream that's either:
    // - Currently live (isLive: true)
    // - Created within last hour and not ended
    return streams.find(stream => {
      const oneHourAgo = Date.now() - (60 * 60 * 1000);
      return stream.isLive || (stream.createdAt > oneHourAgo && !stream.endedAt);
    }) || null;
  },
});

Logic:

  • Checks user's last 5 streams
  • Returns stream if:
    • Live: isLive === true
    • Recent: Created within last hour AND not ended
  • Returns null if no active stream found

2. Auto-Load on Page Mount

Location: /app/stream-obs/page.tsx

// Query for active stream
const activeStream = useQuery(
  api.streams.getActiveStream,
  isWalletConnected && userId ? { userId } : "skip"
);

// Auto-load effect
useEffect(() => {
  if (!activeStream || streamSessionId) return;
  
  // Found existing stream, load it
  console.log("Found existing active stream:", activeStream._id);
  
  // Load stream data
  setStreamSessionId(activeStream._id);
  setMuxPlaybackId(activeStream.muxPlaybackId);
  setStreamMetadata({
    title: activeStream.title,
    category: activeStream.category,
    tags: activeStream.tags,
  });
  
  // Restore live state if streaming
  if (activeStream.isLive) {
    setIsStreaming(true);
  }
  
  // Fetch RTMP credentials
  fetchStreamKey(activeStream._id);
}, [activeStream, streamSessionId, userId]);

3. UI Feedback

Loading State:

{!streamSessionId ? (
  activeStream ? (
    // Loading existing stream
    <>
      <p>Loading your active stream...</p>
      <div className="spinner"></div>
    </>
  ) : (
    // No active stream, allow creation
    <button onClick={createStreamSession}>
      Create Stream
    </button>
  )
) : (
  // Stream loaded, show RTMP instructions
)}

User Flow

Scenario 1: Fresh Start (No Active Stream)

  1. User clicks "Go Live"
  2. Fills out stream details
  3. Chooses OBS setup
  4. Arrives at stream-obs page
  5. getActiveStream returns null
  6. Shows "Create Stream" button
  7. Click → Creates new stream ✅

Scenario 2: Returning to Active Stream

  1. User has stream already running
  2. Navigates away (e.g., checks dashboard)
  3. Clicks "Go Live" or visits /stream-obs
  4. getActiveStream finds active stream ✅
  5. Automatically loads:
    • Stream ID
    • RTMP URL & Stream Key
    • Stream metadata (title, tags)
    • Live status
  6. Shows existing stream controls
  7. No duplicate created

Scenario 3: Stream Ended (>1 hour ago)

  1. User ended stream 2 hours ago
  2. Visits /stream-obs
  3. getActiveStream returns null (too old)
  4. Allows creating new stream ✅

What Gets Restored

When loading an existing active stream:

Stream ID - Database record ✅ RTMP URL - Server endpoint ✅ Stream Key - OBS credential ✅ Mux Playback ID - For viewer access ✅ Stream Metadata - Title, tags, category ✅ Live Status - If already streaming ✅ Chat History - Messages from live stream ✅ Stats - Viewers, earnings, gifts


Benefits

  1. No Duplicates - Only one active stream per user
  2. Seamless Resume - Return to streaming without recreating
  3. Persistent State - Stream state maintained across page reloads
  4. Data Integrity - All stats and history preserved
  5. Better UX - Users don't lose their stream setup

Edge Cases Handled

Multiple Tabs

  • Both tabs connect to same stream
  • Shared RTMP credentials
  • Real-time chat updates

Page Refresh

  • Stream automatically reloaded
  • Live status preserved
  • OBS stays connected

Browser Close/Reopen

  • Within 1 hour: Stream restored
  • After 1 hour: Treated as ended

Manual "Create Stream" Click

  • Only works when no active stream
  • Prevented if stream already exists
  • Clear feedback to user

Testing

Test 1: Create New Stream

  1. Ensure no active streams
  2. Visit /stream-obs
  3. Should show "Create Stream" button ✅

Test 2: Return to Active Stream

  1. Create stream and start streaming
  2. Navigate to /dashboard
  3. Click "Go Live" again
  4. Should auto-load existing stream ✅
  5. RTMP credentials shown
  6. Live badge visible if streaming

Test 3: Old Stream Ignored

  1. Create stream
  2. Wait 2 hours (or manually set old timestamp)
  3. Visit /stream-obs
  4. Should allow creating new stream ✅

Technical Details

Query Performance

  • Only checks last 5 streams
  • Uses indexed query (by_host)
  • Sorted descending (newest first)
  • Efficient filter logic

State Management

  • Minimal re-renders
  • Dependency array properly set
  • No infinite loops
  • Guards against race conditions

Error Handling

  • Graceful fallback if query fails
  • Console logging for debugging
  • User-friendly error messages

Summary

Single Stream Policy - One active stream per user ✅ Auto-Detection - Finds existing streams automatically ✅ Seamless Loading - Restores all stream state ✅ 1-Hour Window - Recent streams auto-loaded ✅ No Duplicates - Prevents multiple stream creation ✅ Better UX - Return to streaming easily

Your stream state is now persistent and protected! 🎉