โ† Presentations
PRD ยท Hot Video Priority

Hot Video Priority & Trending Feed

๐Ÿ“„ Type: PRD ๐ŸŸข Status: Approved ๐Ÿ“… Date: 2026-02-28 ๐Ÿ‘ค Owner: nguyenhuuca ๐Ÿท๏ธ Version: 1.0

I Overview

A trending signal that surfaces popular videos and pre-warms them for faster playback.

๐ŸŽฏ What & Why

๐ŸŽฏ
Give viewers a curated "Trending Now" experience instead of a flat, unordered video list โ€” improving discovery, engagement, and viral spread.

This PRD introduces a hot-score ranking, a dedicated trending endpoint, a sort option on the existing feed, a scheduled recompute job, cache pre-warming for hot videos, and frontend surfacing (trending section + ๐Ÿ”ฅ badge).

๐Ÿ“‹ Metadata

FieldValue
StatusApproved
Authornguyenhuuca
Date2026-02-28
Version1.0
Beads IssueN/A
PR-FAQN/A
StakeholdersBackend Team, Frontend Team, Product
๐Ÿ”Ž Next Steps & Related Artifacts

After PRD approval

  • [x] Architect Review: Technical feasibility โ€” Output: ADR-0011
  • [ ] UI/UX Designer: Wireframes for Trending Now section + Hot badge (/ui-ux-designer) โ€” Output: artifacts/design_spec_hot-video.md
  • [x] Engineering Estimate: Effort estimation & decomposition โ€” Output: plan-hot-video-priority
  • [ ] Create Beads Issues: bd create "Hot Video Priority" -t feature

Related Artifacts

  • ADR: ADR-0011 โ€” Hot Video Priority Scoring
  • Implementation Plan: plan-hot-video-priority
  • Cache Strategy: ADR-0003
  • LRU Cache: ADR-0005

Approval

RoleNameDateStatus
Productnguyenhuuca2026-02-28Approved
Engineeringnguyenhuuca2026-02-28Approved
DesignN/AN/AN/A

Version History

VersionDateAuthorChanges
1.02026-02-28nguyenhuucaInitial draft
1.12026-05-31nguyenhuucaRestructured to official PRD template; removed non-existent upCount field; fixed entity name YouTubeVideo โ†’ VideoSource; fixed doc paths

II Problem Statement

No trending signal exists โ€” discovery is 100% manual browsing.

โ“ The Problem

All videos are displayed in a flat, unordered list. Users have no signal about which videos are trending or popular โ€” leading to poor content discovery and lower engagement. Users who want to watch something good must manually browse without any curation.

๐Ÿ“Š Quantitative Evidence

  • No trending signal exists โ€” 100% of content discovery is manual browsing
  • VideoAccessStats table exists and tracks hitCount + lastAccessedAt, but recordAccess() is commented out โ€” zero access data is being written
  • ShareLink table tracks share events but is not used for ranking
  • VideoCacheImpl already promotes videos with โ‰ฅ5 cache hits โ€” evidence that popularity signals are valuable

๐Ÿ’ฌ Qualitative Evidence

  • Users who want to watch "something good" have no starting point other than scrolling
  • Hot content buried in a flat list reduces the chance of viral spread
  • Pre-caching popular videos is already partially designed in VideoCacheImpl โ€” the infrastructure anticipates this need

III Goals & Success Metrics

Discovery, consumption, latency, and trending engagement.

๐Ÿ“ˆ Goals & Targets

GoalMetricTarget
Improve content discoveryAverage session duration+15%
Increase video consumptionVideos watched per session+20%
Reduce hot video latencyTime-to-first-frame for hot videos< 500ms (from ~2s via pre-cache)
Drive trending engagementUser click-through on hot badges> 30%

IV User Stories

Viewer-facing experience and API consumer integration.

Viewer Trending Now section

As a viewer, I want to see a "Trending Now" section at the top of the home page so that I can quickly find popular content without browsing.

Acceptance: A "๐Ÿ”ฅ Trending Now" section shows top 5 hot videos above the main feed.

Viewer Visual hot marker

As a viewer, I want hot videos to be visually marked so that I know at a glance which content is currently popular.

Acceptance: Video cards with isHot = true display a ๐Ÿ”ฅ badge.

Viewer Faster hot playback

As a viewer, I want hot videos to start playing faster so that I don't wait when choosing trending content.

Acceptance: Top 10 hot videos are pre-warmed in cache; first-frame latency < 500ms.

API consumer Dedicated trending endpoint

As an API consumer, I want a dedicated /api/videos/trending endpoint so that I can build trending feeds in any client.

Acceptance: GET /api/videos/trending?limit=N returns top N videos sorted by hotScore DESC.

API consumer Sort main list by hotness

As an API consumer, I want to sort the main video list by hotness so that I can offer multiple sort options without a separate endpoint.

Acceptance: GET /api/videos?sort=hot returns all videos sorted by hotScore DESC; existing calls without sort are unchanged.

V Requirements

Functional (FR) and non-functional (NFR) requirements.

โœ… Functional Requirements

IDRequirementPriorityNotes
FR-1Activate recordAccess() โ€” async upsert of VideoAccessStats on every streamMust HaveMust not block stream response
FR-2Implement hot score formula: weighted hits (60%) + shares (25%) + recency decay (15%)Must HaveMin-max normalised across active videos
FR-3GET /api/videos/trending?limit=N endpoint (default 10, max 50)Must HaveServed from StatsCacheImpl, TTL 30 min
FR-4Extend GET /api/videos?sort=hot โ€” backward compatibleMust HaveNo change to calls without sort param
FR-5Scheduled job: recompute hot scores every 30 minutesMust HaveRuns in AppScheduler
FR-6Pre-warm VideoCacheImpl for top 10 hot videos after each recomputationShould HaveGoal: first-frame < 500ms
FR-7Frontend: "๐Ÿ”ฅ Trending Now" section at top of home page (top 5 videos)Must Have
FR-8Frontend: "๐Ÿ”ฅ Hot" badge on video cards where isHot = trueMust HaveShow when hotScore โ‰ฅ top 20% threshold

โš™๏ธ Non-Functional Requirements

IDRequirementTarget
NFR-1recordAccess() latency overhead< 5ms (async, fire-and-forget)
NFR-2Trending API response time< 100ms (served from cache)
NFR-3Score recomputation time< 10s for 10,000 videos
NFR-4Additional memory overhead< 50MB
NFR-5Backward compatibilityGET /api/videos without sort param โ€” unchanged behaviour
NFR-6ResilienceDB write failure in recordAccess() must not fail the stream request

VI Scope

What ships in v1 versus what is deferred.

โœ… In Scope

  • Activate VideoAccessServiceImpl.recordAccess() (upsert VideoAccessStats)
  • Hot score algorithm with configurable weights (app.hot-video.* in application.yaml)
  • New GET /api/videos/trending endpoint
  • sort=hot parameter on existing GET /api/videos
  • Liquibase migration: hot_score, is_hot, hot_rank columns on video_sources
  • Scheduled recomputation job every 30 minutes
  • Cache pre-warming for top 10 hot videos
  • Frontend: Trending Now section + ๐Ÿ”ฅ badge on video cards

๐Ÿšซ Out of Scope

  • Personalised recommendations (per-user hot scores) โ€” v2
  • Real-time score updates via WebSocket โ€” v2
  • A/B testing framework for ranking algorithms โ€” v2
  • Machine learning-based scoring โ€” v3
  • Like/dislike counts (not present on VideoSource entity)

VII Dependencies

Most infrastructure already exists; the Liquibase migration is the one gap.

๐Ÿ”— Dependencies

DependencyOwnerStatusRisk
VideoAccessStats entity + repoBackendโœ… ExistsLow โ€” just needs recordAccess() uncommented
ShareLink tableBackendโœ… ExistsLow โ€” read-only query for share count
StatsCacheImplBackendโœ… ExistsLow โ€” already used for stats
VideoCacheImplBackendโœ… ExistsLow โ€” preWarm() needs to be called
AppSchedulerBackendโœ… ExistsLow โ€” add new @Scheduled method
Liquibase migrationBackendโŒ MissingMedium โ€” must land before entity changes
VideoSource entityBackendโœ… Active tableLow โ€” add 3 columns

VIII Risks & Mitigations

Load, score bias, table growth, and staleness โ€” each with a mitigation.

โš ๏ธ Risks & Mitigations

RiskLikelihoodImpactMitigation
recordAccess() creates DB bottleneck under high loadMediumHighKeep @Async + catch all exceptions; add write queue in v2 if needed
Hot score biased toward old high-hit videosMediumMediumApply 24h rolling window + exponential recency decay (e^(-0.1 ร— hoursAgo))
video_access_stats table grows unboundedMediumMediumActivate cleanUpOldVideos() in AppScheduler to purge rows older than window
Scores stale up to 30 minutes after new viral videoLowLowTTL is configurable; acceptable for v1
Cache invalidation on new video addedLowLowTTL-based invalidation (30 min) sufficient for v1

IX Data & API

Appendix โ€” data model, hot score formula, API contract, and new components.

๐Ÿ—„๏ธ Data Model

-- Liquibase migration: 202602280001-add-hot-score.sql
ALTER TABLE video_sources ADD COLUMN hot_score  DECIMAL(5,4) DEFAULT 0;
ALTER TABLE video_sources ADD COLUMN is_hot     BOOLEAN      DEFAULT FALSE;
ALTER TABLE video_sources ADD COLUMN hot_rank   INT          DEFAULT NULL;

CREATE INDEX idx_video_sources_hot_score
    ON video_sources(hot_score DESC) WHERE is_hot = TRUE;

CREATE INDEX IF NOT EXISTS idx_video_access_stats_video_id
    ON video_access_stats(video_id);

๐Ÿงฎ Hot Score Formula

$$hotScore = (0.60 \times normalizedHits) + (0.25 \times normalizedShares) + (0.15 \times recencyBoost)$$ $$recencyBoost = e^{-0.1 \times hoursAgo}$$
All inputs min-max normalised to [0, 1] across active videos.

๐Ÿ”Œ API Contract

GET /api/videos/trending

Query params: limit (int, default=10, max=50)
Response: ResultListInfo<VideoDto>
  data[].videoId     String
  data[].title       String
  data[].embedLink   String
  data[].hotScore    Double
  data[].isHot       Boolean
  data[].rank        Integer  (1 = hottest)

GET /api/videos?sort=hot

Same VideoDto structure, ordered by hotScore DESC

๐Ÿงฉ New Components

service/
โ”œโ”€โ”€ HotVideoService.java
โ””โ”€โ”€ impl/HotVideoServiceImpl.java

jobs/
โ””โ”€โ”€ AppScheduler.java  โ† add recomputeHotScores()
๐Ÿ”Ž Research โ€” existing code references
  • VideoAccessStats entity: api/src/main/java/com/canhlabs/funnyapp/entity/VideoAccessStats.java
  • VideoSource entity: api/src/main/java/com/canhlabs/funnyapp/entity/VideoSource.java
  • VideoCacheImpl: api/src/main/java/com/canhlabs/funnyapp/cache/

X Open Questions

Decisions to resolve before the related requirements are implemented.

๐Ÿงฉ Open Questions

[ ] Should /api/videos/trending be a public endpoint (no auth required)? โ€” Owner: Backend โ€” Due: Before FR-3 implementation
[ ] What threshold defines isHot โ€” top 20% by score or a fixed score cutoff? โ€” Owner: Product โ€” Due: Before FR-2 implementation
[ ] Should shares from private share-links count toward the hot score? โ€” Owner: Backend โ€” Due: Before FR-2 implementation
[ ] Do we need a hot_score_history table for analytics / score trending over time? โ€” Owner: Data โ€” Due: v2 planning