diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 15b04051f4..1c44024753 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -1399,6 +1399,18 @@ public class DynamicHlsController : BaseJellyfinApiController
TranscodingJobType,
cancellationTokenSource.Token)
.ConfigureAwait(false);
+
+ // Calculate the starting segment index from current transcoding state
+ var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
+ var segmentExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer);
+ var startSegmentIndex = 0;
+
+ var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
+ if (currentTranscodingIndex.HasValue)
+ {
+ startSegmentIndex = currentTranscodingIndex.Value;
+ }
+
var mediaSourceId = state.BaseRequest.MediaSourceId;
var request = new CreateMainPlaylistRequest(
mediaSourceId is null ? null : Guid.Parse(mediaSourceId),
@@ -1408,7 +1420,8 @@ public class DynamicHlsController : BaseJellyfinApiController
state.Request.SegmentContainer ?? string.Empty,
"hls1/main/",
Request.QueryString.ToString(),
- EncodingHelper.IsCopyCodec(state.OutputVideoCodec));
+ EncodingHelper.IsCopyCodec(state.OutputVideoCodec),
+ startSegmentIndex);
var playlist = _dynamicHlsPlaylistGenerator.CreateMainPlaylist(request);
return new FileContentResult(Encoding.UTF8.GetBytes(playlist), MimeTypes.GetMimeType("playlist.m3u8"));
diff --git a/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs b/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs
index f5af500626..ee3d966275 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs
+++ b/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs
@@ -18,7 +18,8 @@ public class CreateMainPlaylistRequest
/// The URI prefix for the relative URL in the playlist.
/// The desired query string to append (must start with ?).
/// Whether the video is being remuxed.
- public CreateMainPlaylistRequest(Guid? mediaSourceId, string filePath, int desiredSegmentLengthMs, long totalRuntimeTicks, string segmentContainer, string endpointPrefix, string queryString, bool isRemuxingVideo)
+ /// The starting segment index for the playlist.
+ public CreateMainPlaylistRequest(Guid? mediaSourceId, string filePath, int desiredSegmentLengthMs, long totalRuntimeTicks, string segmentContainer, string endpointPrefix, string queryString, bool isRemuxingVideo, int startSegmentIndex = 0)
{
MediaSourceId = mediaSourceId;
FilePath = filePath;
@@ -28,6 +29,7 @@ public class CreateMainPlaylistRequest
EndpointPrefix = endpointPrefix;
QueryString = queryString;
IsRemuxingVideo = isRemuxingVideo;
+ StartSegmentIndex = startSegmentIndex;
}
///
@@ -69,4 +71,9 @@ public class CreateMainPlaylistRequest
/// Gets a value indicating whether the video is being remuxed.
///
public bool IsRemuxingVideo { get; }
+
+ ///
+ /// Gets the starting segment index for the playlist.
+ ///
+ public int StartSegmentIndex { get; }
}
diff --git a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs
index fb5027e5b5..63d9ce44fe 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs
+++ b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs
@@ -62,9 +62,9 @@ public class DynamicHlsPlaylistGenerator : IDynamicHlsPlaylistGenerator
.Append("#EXT-X-TARGETDURATION:")
.Append(Math.Ceiling(segments.Count > 0 ? segments.Max() : request.DesiredSegmentLengthMs))
.AppendLine()
- .AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
+ .AppendLine(CultureInfo.InvariantCulture, $"#EXT-X-MEDIA-SEQUENCE:{request.StartSegmentIndex}");
- var index = 0;
+ var index = request.StartSegmentIndex;
if (isHlsInFmp4)
{
diff --git a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
index eab003715c..6e7e36a1d1 100644
--- a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
@@ -11,6 +11,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
+
diff --git a/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs b/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs
index fc969527e8..8b63d96616 100644
--- a/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Playlist/DynamicHlsPlaylistGeneratorTests.cs
@@ -1,6 +1,9 @@
using System;
+using Jellyfin.MediaEncoding.Hls.Extractors;
using Jellyfin.MediaEncoding.Hls.Playlist;
using Jellyfin.MediaEncoding.Keyframes;
+using MediaBrowser.Controller.Configuration;
+using Moq;
using Xunit;
namespace Jellyfin.MediaEncoding.Hls.Tests.Playlist
@@ -96,6 +99,29 @@ namespace Jellyfin.MediaEncoding.Hls.Tests.Playlist
return data;
}
+ [Fact]
+ public void CreateMainPlaylist_WithStartSegmentIndex_GeneratesCorrectSequence()
+ {
+ var configManager = new Mock();
+ var generator = new DynamicHlsPlaylistGenerator(configManager.Object, Array.Empty());
+ var request = new CreateMainPlaylistRequest(
+ null,
+ "/path/to/file.mp4",
+ 6000,
+ 600000000000,
+ "mp4",
+ "hls1/main/",
+ "?params=1",
+ false,
+ startSegmentIndex: 1187);
+
+ var playlist = generator.CreateMainPlaylist(request);
+
+ Assert.Contains("#EXT-X-MEDIA-SEQUENCE:1187", playlist, StringComparison.Ordinal);
+ Assert.Contains("hls1/main/1187.mp4", playlist, StringComparison.Ordinal);
+ Assert.Contains("hls1/main/1188.mp4", playlist, StringComparison.Ordinal);
+ }
+
private static long MsToTicks(int value) => TimeSpan.FromMilliseconds(value).Ticks;
}
}