diff --git a/allstarr/Controllers/JellyfinController.cs b/allstarr/Controllers/JellyfinController.cs
index dc91dd9..4e1c0a3 100644
--- a/allstarr/Controllers/JellyfinController.cs
+++ b/allstarr/Controllers/JellyfinController.cs
@@ -2006,6 +2006,7 @@ public class JellyfinController : ControllerBase
var doc = JsonDocument.Parse(body);
string? itemId = null;
string? itemName = null;
+ long? positionTicks = null;
if (doc.RootElement.TryGetProperty("ItemId", out var itemIdProp))
{
@@ -2016,6 +2017,18 @@ public class JellyfinController : ControllerBase
{
itemName = itemNameProp.GetString();
}
+
+ if (doc.RootElement.TryGetProperty("PositionTicks", out var posProp))
+ {
+ positionTicks = posProp.GetInt64();
+ }
+
+ // Track the playing item for scrobbling on session cleanup
+ var (deviceId, client, device, version) = ExtractDeviceInfo(Request.Headers);
+ if (!string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(itemId))
+ {
+ _sessionManager.UpdatePlayingItem(deviceId, itemId, positionTicks);
+ }
if (!string.IsNullOrEmpty(itemId))
{
@@ -2050,7 +2063,7 @@ public class JellyfinController : ControllerBase
var playbackStart = new
{
ItemId = itemId,
- PositionTicks = doc.RootElement.TryGetProperty("PositionTicks", out var posProp) ? posProp.GetInt64() : 0,
+ PositionTicks = positionTicks ?? 0,
// Let Jellyfin fetch the item details - don't include NowPlayingItem
};
@@ -2064,7 +2077,6 @@ public class JellyfinController : ControllerBase
_logger.LogInformation("✓ Playback start forwarded to Jellyfin ({StatusCode})", statusCode);
// NOW ensure session exists with capabilities (after playback is reported)
- var (deviceId, client, device, version) = ExtractDeviceInfo(Request.Headers);
if (!string.IsNullOrEmpty(deviceId))
{
var sessionCreated = await _sessionManager.EnsureSessionAsync(deviceId, client ?? "Unknown", device ?? "Unknown", version ?? "1.0", Request.Headers);
@@ -2155,6 +2167,12 @@ public class JellyfinController : ControllerBase
positionTicks = posProp.GetInt64();
}
+ // Track the playing item for scrobbling on session cleanup
+ if (!string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(itemId))
+ {
+ _sessionManager.UpdatePlayingItem(deviceId, itemId, positionTicks);
+ }
+
if (!string.IsNullOrEmpty(itemId))
{
var (isExternal, provider, externalId) = _localLibraryService.ParseSongId(itemId);
diff --git a/allstarr/Services/Jellyfin/JellyfinSessionManager.cs b/allstarr/Services/Jellyfin/JellyfinSessionManager.cs
index 382947b..b900e35 100644
--- a/allstarr/Services/Jellyfin/JellyfinSessionManager.cs
+++ b/allstarr/Services/Jellyfin/JellyfinSessionManager.cs
@@ -142,6 +142,21 @@ public class JellyfinSessionManager : IDisposable
_logger.LogDebug("⚠️ SESSION: Cannot update activity - device {DeviceId} not found", deviceId);
}
}
+
+ ///
+ /// Updates the currently playing item for a session (for scrobbling on cleanup).
+ ///
+ public void UpdatePlayingItem(string deviceId, string? itemId, long? positionTicks)
+ {
+ if (_sessions.TryGetValue(deviceId, out var session))
+ {
+ session.LastPlayingItemId = itemId;
+ session.LastPlayingPositionTicks = positionTicks;
+ session.LastActivity = DateTime.UtcNow;
+ _logger.LogDebug("🎵 SESSION: Updated playing item for {DeviceId}: {ItemId} at {Position}",
+ deviceId, itemId, positionTicks);
+ }
+ }
///
/// Marks a session as potentially ended (e.g., after playback stops).
@@ -230,10 +245,19 @@ public class JellyfinSessionManager : IDisposable
try
{
- // Report playback stopped to Jellyfin
- var stopPayload = JsonSerializer.Serialize(new { });
- await _proxyService.PostJsonAsync("Sessions/Playing/Stopped", stopPayload, session.Headers);
- _logger.LogDebug("🛑 SESSION: Reported playback stopped for {DeviceId}", deviceId);
+ // Report playback stopped to Jellyfin if we have a playing item (for scrobbling)
+ if (!string.IsNullOrEmpty(session.LastPlayingItemId))
+ {
+ var stopPayload = new
+ {
+ ItemId = session.LastPlayingItemId,
+ PositionTicks = session.LastPlayingPositionTicks ?? 0
+ };
+ var stopJson = JsonSerializer.Serialize(stopPayload);
+ await _proxyService.PostJsonAsync("Sessions/Playing/Stopped", stopJson, session.Headers);
+ _logger.LogInformation("🛑 SESSION: Reported playback stopped for {DeviceId} (ItemId: {ItemId}, Position: {Position})",
+ deviceId, session.LastPlayingItemId, session.LastPlayingPositionTicks);
+ }
// Notify Jellyfin that the session is ending
await _proxyService.PostJsonAsync("Sessions/Logout", "{}", session.Headers);
@@ -500,6 +524,8 @@ public class JellyfinSessionManager : IDisposable
public DateTime LastActivity { get; set; }
public required IHeaderDictionary Headers { get; init; }
public ClientWebSocket? WebSocket { get; set; }
+ public string? LastPlayingItemId { get; set; }
+ public long? LastPlayingPositionTicks { get; set; }
}
public void Dispose()