Add progress and complettion indication
This commit is contained in:
@@ -362,4 +362,116 @@ public class JellypodController : ControllerBase
|
||||
|
||||
return Ok(new { RemovedCount = duplicatesToRemove.Count });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates playback progress for an episode.
|
||||
/// </summary>
|
||||
/// <param name="podcastId">Podcast ID.</param>
|
||||
/// <param name="episodeId">Episode ID.</param>
|
||||
/// <param name="request">Progress update request.</param>
|
||||
/// <returns>No content.</returns>
|
||||
[HttpPost("podcasts/{podcastId}/episodes/{episodeId}/progress")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> UpdatePlaybackProgress(
|
||||
[FromRoute] Guid podcastId,
|
||||
[FromRoute] Guid episodeId,
|
||||
[FromBody] PlaybackProgressRequest request)
|
||||
{
|
||||
var podcast = await _storageService.GetPodcastAsync(podcastId).ConfigureAwait(false);
|
||||
var episode = podcast?.Episodes.FirstOrDefault(e => e.Id == episodeId);
|
||||
|
||||
if (podcast == null || episode == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
episode.PlaybackPositionTicks = request.PositionTicks;
|
||||
episode.LastPlayedDate = DateTime.UtcNow;
|
||||
|
||||
// Mark as played if we've reached near the end (within last 5%)
|
||||
if (episode.Duration.HasValue && request.PositionTicks > 0)
|
||||
{
|
||||
var durationTicks = episode.Duration.Value.Ticks;
|
||||
var percentComplete = (double)request.PositionTicks / durationTicks;
|
||||
if (percentComplete >= 0.95)
|
||||
{
|
||||
episode.IsPlayed = true;
|
||||
episode.PlayCount++;
|
||||
_logger.LogInformation("Episode marked as played: {Title} (played {Count} times)", episode.Title, episode.PlayCount);
|
||||
}
|
||||
}
|
||||
|
||||
await _storageService.UpdatePodcastAsync(podcast).ConfigureAwait(false);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks an episode as played or unplayed.
|
||||
/// </summary>
|
||||
/// <param name="podcastId">Podcast ID.</param>
|
||||
/// <param name="episodeId">Episode ID.</param>
|
||||
/// <param name="played">Whether the episode is played.</param>
|
||||
/// <returns>No content.</returns>
|
||||
[HttpPost("podcasts/{podcastId}/episodes/{episodeId}/played")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> SetPlayedStatus(
|
||||
[FromRoute] Guid podcastId,
|
||||
[FromRoute] Guid episodeId,
|
||||
[FromQuery] bool played = true)
|
||||
{
|
||||
var podcast = await _storageService.GetPodcastAsync(podcastId).ConfigureAwait(false);
|
||||
var episode = podcast?.Episodes.FirstOrDefault(e => e.Id == episodeId);
|
||||
|
||||
if (podcast == null || episode == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
episode.IsPlayed = played;
|
||||
if (played)
|
||||
{
|
||||
episode.LastPlayedDate = DateTime.UtcNow;
|
||||
episode.PlayCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
episode.PlaybackPositionTicks = 0;
|
||||
}
|
||||
|
||||
await _storageService.UpdatePodcastAsync(podcast).ConfigureAwait(false);
|
||||
_logger.LogInformation("Episode {Title} marked as {Status}", episode.Title, played ? "played" : "unplayed");
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets playback progress for an episode.
|
||||
/// </summary>
|
||||
/// <param name="podcastId">Podcast ID.</param>
|
||||
/// <param name="episodeId">Episode ID.</param>
|
||||
/// <returns>Playback progress info.</returns>
|
||||
[HttpGet("podcasts/{podcastId}/episodes/{episodeId}/progress")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<PlaybackProgressResponse>> GetPlaybackProgress(
|
||||
[FromRoute] Guid podcastId,
|
||||
[FromRoute] Guid episodeId)
|
||||
{
|
||||
var podcast = await _storageService.GetPodcastAsync(podcastId).ConfigureAwait(false);
|
||||
var episode = podcast?.Episodes.FirstOrDefault(e => e.Id == episodeId);
|
||||
|
||||
if (podcast == null || episode == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(new PlaybackProgressResponse
|
||||
{
|
||||
PositionTicks = episode.PlaybackPositionTicks,
|
||||
IsPlayed = episode.IsPlayed,
|
||||
LastPlayedDate = episode.LastPlayedDate,
|
||||
PlayCount = episode.PlayCount
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Jellyfin.Plugin.Jellypod.Api.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Request to update playback progress.
|
||||
/// </summary>
|
||||
public class PlaybackProgressRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the playback position in ticks.
|
||||
/// </summary>
|
||||
public long PositionTicks { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace Jellyfin.Plugin.Jellypod.Api.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Response containing playback progress info.
|
||||
/// </summary>
|
||||
public class PlaybackProgressResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the playback position in ticks.
|
||||
/// </summary>
|
||||
public long PositionTicks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the episode has been played.
|
||||
/// </summary>
|
||||
public bool IsPlayed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date the episode was last played.
|
||||
/// </summary>
|
||||
public DateTime? LastPlayedDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of times the episode has been played.
|
||||
/// </summary>
|
||||
public int PlayCount { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user