Split shorts from regular videos with a vertical shorts feed
- Detect shorts by duration (SHORTS_MAX_DURATION_SECONDS, default 180)
and expose is_short in feed, video and channel-activity DTOs.
- Feed gains type=all|long|short and anchor; lists show a two-mode
'Обычные | Shorts' filter (no 'Все') persisted in the URL.
- Clicking a short opens /shorts/🆔 a vertical scroll-snap feed with
autoplay for the active slide, context-aware endpoints and infinite
loading.
- Keep the sound choice across swipes, syncing with the player's own
mute control and guarding against the widget's stale isMuted() reads.
This commit is contained in:
parent
43adec5224
commit
252f38597d
25 changed files with 1297 additions and 88 deletions
|
|
@ -57,6 +57,21 @@ def test_get_video_by_youtube_id(client, db_session):
|
|||
assert body["channel"]["id"] == channel.id
|
||||
assert body["categories"] == [{"id": category.id, "name": "Linux"}]
|
||||
assert body["local"]["available"] is False
|
||||
# 125s <= default 180s threshold.
|
||||
assert body["is_short"] is True
|
||||
|
||||
|
||||
def test_get_video_is_short_false_for_long_and_unknown_duration(client, db_session, monkeypatch):
|
||||
_seed_single_video(db_session, youtube_video_id="vidLong", duration_seconds=3600)
|
||||
_seed_single_video(db_session, youtube_video_id="vidUnknown")
|
||||
|
||||
assert client.get("/api/videos/vidLong").json()["is_short"] is False
|
||||
assert client.get("/api/videos/vidUnknown").json()["is_short"] is False
|
||||
|
||||
# The threshold is read from settings at serialization time.
|
||||
monkeypatch.setattr("app.config.settings.shorts_max_duration_seconds", 4000)
|
||||
assert client.get("/api/videos/vidLong").json()["is_short"] is True
|
||||
assert client.get("/api/videos/vidUnknown").json()["is_short"] is False
|
||||
|
||||
|
||||
def test_get_video_not_found(client):
|
||||
|
|
@ -64,16 +79,19 @@ def test_get_video_not_found(client):
|
|||
assert resp.status_code == 404
|
||||
|
||||
|
||||
def _seed_single_video(db_session, youtube_video_id="vid1"):
|
||||
channel = Channel(youtube_channel_id="chanA", title="Channel A", subscribed=True)
|
||||
db_session.add(channel)
|
||||
db_session.commit()
|
||||
def _seed_single_video(db_session, youtube_video_id="vid1", duration_seconds=None):
|
||||
channel = db_session.query(Channel).filter(Channel.youtube_channel_id == "chanA").first()
|
||||
if channel is None:
|
||||
channel = Channel(youtube_channel_id="chanA", title="Channel A", subscribed=True)
|
||||
db_session.add(channel)
|
||||
db_session.commit()
|
||||
|
||||
video = Video(
|
||||
youtube_video_id=youtube_video_id,
|
||||
channel_id=channel.id,
|
||||
title="Video One",
|
||||
published_at=datetime(2026, 9, 10, tzinfo=timezone.utc),
|
||||
duration_seconds=duration_seconds,
|
||||
youtube_url=f"https://www.youtube.com/watch?v={youtube_video_id}",
|
||||
)
|
||||
db_session.add(video)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue