Compare commits

...
3 Commits
Author SHA1 Message Date
dtourolle c1b5dea569 Fixes for Livestreams
🏗️ Build Plugin / build (push) Successful in 41s
🧪 Test Plugin / test (push) Successful in 34s
🚀 Release Plugin / build-and-release (push) Successful in 51s
2026-02-28 13:11:02 +01:00
Gitea Actions 2879ebf4df Update manifest.json for version 1.0.22 2026-02-28 11:36:30 +00:00
dtourolle b0cdcda900 CI fix
🏗️ Build Plugin / build (push) Successful in 44s
🧪 Test Plugin / test (push) Successful in 35s
🚀 Release Plugin / build-and-release (push) Successful in 52s
2026-02-28 12:34:15 +01:00
5 changed files with 40 additions and 14 deletions
+2 -9
View File
@@ -84,20 +84,13 @@ jobs:
REPO_NAME="${{ github.event.repository.name }}" REPO_NAME="${{ github.event.repository.name }}"
GITEA_URL="${{ github.server_url }}" GITEA_URL="${{ github.server_url }}"
# Build JSON payload with jq to handle escaping properly
VERSION="${{ steps.get_version.outputs.version }}"
PAYLOAD=$(jq -n \
--arg tag "${VERSION}" \
--arg name "Release ${VERSION}" \
--arg body "SRFPlay Jellyfin Plugin ${VERSION}\n\nSee attached files for plugin installation." \
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
# Create release using Gitea API # Create release using Gitea API
VERSION="${{ steps.get_version.outputs.version }}"
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \ -H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases" \ "${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases" \
-d "${PAYLOAD}") -d "$(jq -n --arg tag "$VERSION" --arg name "Release $VERSION" --arg body "SRFPlay Jellyfin Plugin. See attached files for plugin installation." '{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1) HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d') BODY=$(echo "$RESPONSE" | sed '$d')
@@ -292,9 +292,23 @@ public class StreamProxyController : ControllerBase
_logger.LogDebug("Streamed segment {SegmentPath} ({ContentType})", segmentPath, contentType); _logger.LogDebug("Streamed segment {SegmentPath} ({ContentType})", segmentPath, contentType);
return new EmptyResult(); return new EmptyResult();
} }
catch (OperationCanceledException)
{
// Client disconnected during streaming (e.g., FFmpeg stopped, player seeked).
// This is expected behavior, not an error.
_logger.LogDebug("Segment streaming canceled - ItemId: {ItemId}, Path: {SegmentPath}", itemId, segmentPath);
return new EmptyResult();
}
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error proxying segment - ItemId: {ItemId}, Path: {SegmentPath}", itemId, segmentPath); _logger.LogError(ex, "Error proxying segment - ItemId: {ItemId}, Path: {SegmentPath}", itemId, segmentPath);
// If we already started streaming data to the client, we can't change the status code
if (Response.HasStarted)
{
return new EmptyResult();
}
return StatusCode(StatusCodes.Status500InternalServerError); return StatusCode(StatusCodes.Status500InternalServerError);
} }
} }
@@ -120,9 +120,10 @@ public class ContentExpirationService : IContentExpirationService
if (mediaComposition?.HasChapters != true) if (mediaComposition?.HasChapters != true)
{ {
// If we can't fetch the content, consider it expired // Don't treat API failures as expired - the content may still be available
_logger.LogWarning("Could not fetch media composition for URN: {Urn}, treating as expired", urn); // and a transient error (network issue, 403, API outage) shouldn't delete library items
return true; _logger.LogWarning("Could not fetch media composition for URN: {Urn}, skipping (not treating as expired)", urn);
return false;
} }
var chapter = mediaComposition.ChapterList[0]; var chapter = mediaComposition.ChapterList[0];
@@ -373,6 +373,15 @@ public class StreamProxyService : IStreamProxyService
return refreshedUrl; return refreshedUrl;
} }
if (streamInfo.IsLiveStream)
{
// For livestreams, keep the mapping and flag for re-authentication
// rather than removing it — the next request will trigger a fresh auth
_logger.LogWarning("Failed to refresh token for livestream {ItemId}, will re-authenticate on next request", itemId);
streamInfo.NeedsAuthentication = true;
return streamInfo.AuthenticatedUrl;
}
_logger.LogWarning("Failed to refresh token for item {ItemId}, removing mapping", itemId); _logger.LogWarning("Failed to refresh token for item {ItemId}, removing mapping", itemId);
_streamMappings.TryRemove(itemId, out _); _streamMappings.TryRemove(itemId, out _);
return null; return null;
@@ -979,8 +988,9 @@ public class StreamProxyService : IStreamProxyService
_logger.LogDebug("Marking item {ItemId} for cleanup (old registration)", kvp.Key); _logger.LogDebug("Marking item {ItemId} for cleanup (old registration)", kvp.Key);
} }
// Remove if token has expired // Remove if token has expired — but skip livestreams since their CDN tokens
if (kvp.Value.TokenExpiresAt.HasValue && kvp.Value.TokenExpiresAt.Value <= now) // expire every ~30s and get refreshed on-demand during playback
if (kvp.Value.TokenExpiresAt.HasValue && kvp.Value.TokenExpiresAt.Value <= now && !kvp.Value.IsLiveStream)
{ {
shouldRemove = true; shouldRemove = true;
_logger.LogDebug("Marking item {ItemId} for cleanup (expired token)", kvp.Key); _logger.LogDebug("Marking item {ItemId} for cleanup (expired token)", kvp.Key);
+8
View File
@@ -8,6 +8,14 @@
"category": "Live TV", "category": "Live TV",
"imageUrl": "https://gitea.tourolle.paris/dtourolle/jellyfin-srfPlay/raw/branch/master/assests/main%20logo.png", "imageUrl": "https://gitea.tourolle.paris/dtourolle/jellyfin-srfPlay/raw/branch/master/assests/main%20logo.png",
"versions": [ "versions": [
{
"version": "1.0.22",
"changelog": "Release 1.0.22",
"targetAbi": "10.9.0.0",
"sourceUrl": "https://gitea.tourolle.paris/dtourolle/jellyfin-srfPlay/releases/download/v1.0.22/srfplay_1.0.22.0.zip",
"checksum": "08177ed8edb4b4cf2441bc808b9860bb",
"timestamp": "2026-02-28T11:36:30Z"
},
{ {
"version": "1.0.16", "version": "1.0.16",
"changelog": "Release 1.0.16", "changelog": "Release 1.0.16",