Count real listening time instead of full track duration
"Listening time this week" summed the full duration of every track whose
stream ticket was issued, so pressing play and skipping counted the whole
song. Playback is now measured on the client and reported to the API.
Backend:
- ListeningHistory gains PlayedSeconds, LastProgressAt and IsCounted
(migration TrackPlayedSeconds marks existing rows as counted).
- /tracks/{id}/stream returns a listenId; new
PUT /tracks/listens/{listenId}/progress stores the accumulated seconds,
capped by track duration and elapsed wall-clock time, monotonic and
owner-only.
- A listen counts once it reaches min(30s, 50% of the track). Weekly
stats, streak, recent history/albums, mixes and listensCount only use
counted listens; seconds sum PlayedSeconds.
- ListeningHistoryCleanupJob (daily) deletes uncounted listens older
than 8 days.
web-player:
- ListenTracker accumulates played time (seeks are ignored) and flushes
on pause, track change, ended, every 30s, and on pagehide.
- Proxy route /api/listens/[id]/progress; repeat starts a new listen per
loop.
This commit is contained in:
+35
-30
@@ -1,4 +1,4 @@
|
||||
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
@@ -68,7 +68,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("Delivered");
|
||||
|
||||
b.ToTable("InboxState", (string)null);
|
||||
b.ToTable("InboxState");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
|
||||
@@ -159,7 +159,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("InboxMessageId", "InboxConsumerId", "SequenceNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OutboxMessage", (string)null);
|
||||
b.ToTable("OutboxMessage");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxState", b =>
|
||||
@@ -189,7 +189,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("Created");
|
||||
|
||||
b.ToTable("OutboxState", (string)null);
|
||||
b.ToTable("OutboxState");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.Album", b =>
|
||||
@@ -231,7 +231,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("OwnerUserId");
|
||||
|
||||
b.ToTable("Albums", (string)null);
|
||||
b.ToTable("Albums");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.AlbumHasTrack", b =>
|
||||
@@ -259,7 +259,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("AlbumId", "TrackId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("AlbumHasTrack", (string)null);
|
||||
b.ToTable("AlbumHasTrack");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.ListeningHistory", b =>
|
||||
@@ -268,9 +268,18 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("IsCounted")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime?>("LastProgressAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime>("ListenedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<double?>("PlayedSeconds")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<Guid>("TrackId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
@@ -283,7 +292,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("ListeningHistory", (string)null);
|
||||
b.ToTable("ListeningHistory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.Mix", b =>
|
||||
@@ -317,7 +326,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("UserId", "Position");
|
||||
|
||||
b.ToTable("Mixes", (string)null);
|
||||
b.ToTable("Mixes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.MixItem", b =>
|
||||
@@ -341,7 +350,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("MixId", "Position");
|
||||
|
||||
b.ToTable("MixItems", (string)null);
|
||||
b.ToTable("MixItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.PlayList", b =>
|
||||
@@ -385,7 +394,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("PlayLists", (string)null);
|
||||
b.ToTable("PlayLists");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.PlayListHasTrack", b =>
|
||||
@@ -412,7 +421,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("TrackId");
|
||||
|
||||
b.ToTable("PlayListHasTrack", (string)null);
|
||||
b.ToTable("PlayListHasTrack");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.Track", b =>
|
||||
@@ -450,7 +459,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("OwnerUserId");
|
||||
|
||||
b.ToTable("Tracks", (string)null);
|
||||
b.ToTable("Tracks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.TrackLike", b =>
|
||||
@@ -475,7 +484,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("UserId", "TrackId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("TrackLikes", (string)null);
|
||||
b.ToTable("TrackLikes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.TrackTag", b =>
|
||||
@@ -495,7 +504,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("TrackId", "Tag")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("TrackTags", (string)null);
|
||||
b.ToTable("TrackTags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.Upload", b =>
|
||||
@@ -524,7 +533,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Upload", (string)null);
|
||||
b.ToTable("Upload");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.UploadIntent", b =>
|
||||
@@ -575,7 +584,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UploadIntents", (string)null);
|
||||
b.ToTable("UploadIntents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.User", b =>
|
||||
@@ -613,7 +622,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("User", (string)null);
|
||||
b.ToTable("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.UserFollow", b =>
|
||||
@@ -638,7 +647,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
b.HasIndex("FollowerId", "FollowedId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("UserFollows", (string)null);
|
||||
b.ToTable("UserFollows");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Domain.Entities.UserHasTrack", b =>
|
||||
@@ -659,7 +668,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserHasTrack", (string)null);
|
||||
b.ToTable("UserHasTrack");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.AlbumProcessingState", b =>
|
||||
@@ -686,7 +695,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasKey("CorrelationId");
|
||||
|
||||
b.ToTable("AlbumProcessingState", (string)null);
|
||||
b.ToTable("AlbumProcessingState");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.PlayListProcessingState", b =>
|
||||
@@ -713,7 +722,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasKey("CorrelationId");
|
||||
|
||||
b.ToTable("PlayListProcessingState", (string)null);
|
||||
b.ToTable("PlayListProcessingState");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.TrackProcessingState", b =>
|
||||
@@ -749,7 +758,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b.HasKey("CorrelationId");
|
||||
|
||||
b.ToTable("TrackProcessingState", (string)null);
|
||||
b.ToTable("TrackProcessingState");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
|
||||
@@ -803,7 +812,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b1.HasKey("AlbumId");
|
||||
|
||||
b1.ToTable("Albums", (string)null);
|
||||
b1.ToTable("Albums");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AlbumId");
|
||||
@@ -896,32 +905,28 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("LargeName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasColumnName("LargePictureName");
|
||||
|
||||
b1.Property<string>("MediumName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasColumnName("MediumPictureName");
|
||||
|
||||
b1.Property<string>("OriginalName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasColumnName("OriginalPictureName");
|
||||
|
||||
b1.Property<string>("SmallName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasColumnName("SmallPictureName");
|
||||
|
||||
b1.HasKey("PlayListId");
|
||||
|
||||
b1.ToTable("PlayLists", (string)null);
|
||||
b1.ToTable("PlayLists");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("PlayListId");
|
||||
@@ -992,7 +997,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b1.HasKey("TrackId");
|
||||
|
||||
b1.ToTable("Tracks", (string)null);
|
||||
b1.ToTable("Tracks");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("TrackId");
|
||||
@@ -1029,7 +1034,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
|
||||
b1.HasKey("TrackId");
|
||||
|
||||
b1.ToTable("Tracks", (string)null);
|
||||
b1.ToTable("Tracks");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("TrackId");
|
||||
|
||||
Reference in New Issue
Block a user