Add nightly job
This commit is contained in:
parent
4b5d7e2a7f
commit
cedef6d6aa
@ -1,4 +1,4 @@
|
||||
name: 'Latest Release'
|
||||
name: 'Nightly Build'
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -7,9 +7,10 @@ on:
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- 'manifest.json'
|
||||
- 'manifest-nightly.json'
|
||||
|
||||
jobs:
|
||||
latest-release:
|
||||
nightly-build:
|
||||
runs-on: linux/amd64
|
||||
container:
|
||||
image: gitea.tourolle.paris/dtourolle/srfplay-builder:latest
|
||||
@ -20,6 +21,24 @@ jobs:
|
||||
with:
|
||||
path: build-${{ github.run_id }}
|
||||
|
||||
- name: Compute nightly version
|
||||
id: version
|
||||
run: |
|
||||
# Date-based nightly version: 1.0.<YYYYMMDD>.<run_number>
|
||||
# The run_number suffix keeps multiple builds on the same day
|
||||
# monotonically increasing so Jellyfin always offers the newest.
|
||||
DATE=$(date -u +"%Y%m%d")
|
||||
VERSION="1.0.${DATE}.${{ github.run_number }}"
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "Nightly version: ${VERSION}"
|
||||
|
||||
- name: Set build version
|
||||
working-directory: build-${{ github.run_id }}
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
sed -i "s/^version:.*/version: \"${VERSION}\"/" build.yaml
|
||||
cat build.yaml
|
||||
|
||||
- name: Restore dependencies
|
||||
working-directory: build-${{ github.run_id }}
|
||||
run: dotnet restore Jellyfin.Plugin.SRFPlay.sln
|
||||
@ -52,7 +71,7 @@ jobs:
|
||||
echo "checksum=${CHECKSUM}" >> $GITHUB_OUTPUT
|
||||
echo "Checksum: ${CHECKSUM}"
|
||||
|
||||
- name: Delete existing latest release
|
||||
- name: Delete existing nightly release
|
||||
working-directory: build-${{ github.run_id }}
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@ -71,7 +90,7 @@ jobs:
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${TAG}" | jq -r '.id')
|
||||
|
||||
echo "Deleting existing latest release (ID: ${RELEASE_ID})..."
|
||||
echo "Deleting existing nightly release (ID: ${RELEASE_ID})..."
|
||||
curl -s -X DELETE \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}"
|
||||
@ -81,7 +100,7 @@ jobs:
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/tags/${TAG}" || true
|
||||
|
||||
- name: Create latest release
|
||||
- name: Create nightly release
|
||||
working-directory: build-${{ github.run_id }}
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@ -89,12 +108,14 @@ jobs:
|
||||
REPO_OWNER="${{ github.repository_owner }}"
|
||||
REPO_NAME="${{ github.event.repository.name }}"
|
||||
GITEA_URL="${{ github.server_url }}"
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||
|
||||
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases" \
|
||||
-d "$(jq -n --arg tag "latest" --arg name "Latest Build" --arg body "SRFPlay Jellyfin Plugin latest build from master." '{tag_name: $tag, name: $name, body: $body, target_commitish: "master", draft: false, prerelease: true}')")
|
||||
-d "$(jq -n --arg tag "latest" --arg name "Nightly Build ${VERSION}" --arg body "SRFPlay Jellyfin Plugin nightly build ${VERSION} from master (${SHORT_SHA})." '{tag_name: $tag, name: $name, body: $body, target_commitish: "master", draft: false, prerelease: true}')")
|
||||
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||
@ -124,9 +145,9 @@ jobs:
|
||||
--data-binary "@build.yaml" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=build.yaml"
|
||||
|
||||
echo "Latest release updated successfully!"
|
||||
echo "Nightly release updated successfully!"
|
||||
|
||||
- name: Update manifest.json
|
||||
- name: Update manifest-nightly.json
|
||||
working-directory: build-${{ github.run_id }}
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@ -134,6 +155,7 @@ jobs:
|
||||
REPO_OWNER="${{ github.repository_owner }}"
|
||||
REPO_NAME="${{ github.event.repository.name }}"
|
||||
GITEA_URL="${{ github.server_url }}"
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
CHECKSUM="${{ steps.checksum.outputs.checksum }}"
|
||||
ARTIFACT_NAME="${{ steps.jprm.outputs.artifact_name }}"
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
@ -145,13 +167,10 @@ jobs:
|
||||
git fetch origin master
|
||||
git checkout master
|
||||
|
||||
# Remove existing "latest" entry if present, then prepend new one
|
||||
jq --arg url "$DOWNLOAD_URL" 'if .[0].versions[0].changelog == "Latest Build" then .[0].versions = .[0].versions[1:] else . end' manifest.json > manifest.tmp && mv manifest.tmp manifest.json
|
||||
|
||||
NEW_VERSION=$(cat <<EOF
|
||||
{
|
||||
"version": "0.0.0.0",
|
||||
"changelog": "Latest Build",
|
||||
"version": "${VERSION}",
|
||||
"changelog": "Nightly build ${VERSION} (${SHORT_SHA})",
|
||||
"targetAbi": "10.9.0.0",
|
||||
"sourceUrl": "${DOWNLOAD_URL}",
|
||||
"checksum": "${CHECKSUM}",
|
||||
@ -160,9 +179,13 @@ jobs:
|
||||
EOF
|
||||
)
|
||||
|
||||
jq --argjson newver "${NEW_VERSION}" '.[0].versions = [$newver] + .[0].versions' manifest.json > manifest.tmp && mv manifest.tmp manifest.json
|
||||
git add manifest.json
|
||||
git commit -m "Update manifest.json for latest build (${SHORT_SHA})" || echo "No changes to commit"
|
||||
# Prepend the new build and keep only the most recent 5 nightlies.
|
||||
jq --argjson newver "${NEW_VERSION}" \
|
||||
'.[0].versions = ([$newver] + .[0].versions)[0:5]' \
|
||||
manifest-nightly.json > manifest.tmp && mv manifest.tmp manifest-nightly.json
|
||||
|
||||
git add manifest-nightly.json
|
||||
git commit -m "Nightly: ${VERSION} (${SHORT_SHA})" || echo "No changes to commit"
|
||||
git push origin master
|
||||
|
||||
- name: Cleanup
|
||||
|
||||
@ -20,7 +20,6 @@ namespace Jellyfin.Plugin.SRFPlay.Api;
|
||||
/// </summary>
|
||||
public class SRFApiClient : IDisposable
|
||||
{
|
||||
private static readonly System.Text.CompositeFormat PlayV3UrlFormat = System.Text.CompositeFormat.Parse(ApiEndpoints.PlayV3BaseUrlTemplate);
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly HttpClient _playV3HttpClient;
|
||||
private readonly ILogger _logger;
|
||||
@ -58,6 +57,21 @@ public class SRFApiClient : IDisposable
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the Play v3 API base URL for a business unit. The host is normally
|
||||
/// www.<unit>.ch, but SWI's site lives at www.swissinfo.ch (www.swi.ch is an
|
||||
/// unrelated welding institute), while the API path segment stays the unit code.
|
||||
/// </summary>
|
||||
/// <param name="businessUnit">The lowercase business unit (e.g. "srf", "swi").</param>
|
||||
/// <returns>The Play v3 production base URL ending in a slash.</returns>
|
||||
private static string BuildPlayV3BaseUrl(string businessUnit)
|
||||
{
|
||||
var host = string.Equals(businessUnit, "swi", StringComparison.OrdinalIgnoreCase)
|
||||
? "www.swissinfo.ch"
|
||||
: $"www.{businessUnit}.ch";
|
||||
return $"https://{host}/play/v3/api/{businessUnit}/production/";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads HTTP response content as UTF-8 string.
|
||||
/// </summary>
|
||||
@ -315,7 +329,7 @@ public class SRFApiClient : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
var baseUrl = string.Format(CultureInfo.InvariantCulture, PlayV3UrlFormat, businessUnit);
|
||||
var baseUrl = BuildPlayV3BaseUrl(businessUnit);
|
||||
var url = $"{baseUrl}{endpoint}";
|
||||
_logger.LogInformation("Fetching all {Endpoint} for business unit: {BusinessUnit} from URL: {Url}", endpoint, businessUnit, url);
|
||||
|
||||
@ -352,7 +366,7 @@ public class SRFApiClient : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
var baseUrl = string.Format(CultureInfo.InvariantCulture, PlayV3UrlFormat, businessUnit);
|
||||
var baseUrl = BuildPlayV3BaseUrl(businessUnit);
|
||||
var url = $"{baseUrl}videos-by-show-id?showId={showId}";
|
||||
_logger.LogDebug("Fetching videos for show {ShowId} from business unit: {BusinessUnit}", showId, businessUnit);
|
||||
|
||||
@ -392,7 +406,7 @@ public class SRFApiClient : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
var baseUrl = string.Format(CultureInfo.InvariantCulture, PlayV3UrlFormat, businessUnit);
|
||||
var baseUrl = BuildPlayV3BaseUrl(businessUnit);
|
||||
var url = $"{baseUrl}livestreams?eventType={eventType.ToUpperInvariant()}";
|
||||
_logger.LogInformation("Fetching scheduled livestreams for eventType={EventType} from business unit: {BusinessUnit}", eventType, businessUnit);
|
||||
|
||||
|
||||
@ -175,21 +175,8 @@ public abstract class SrgChannelBase : IChannel, IHasCacheKey
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsUnitEnabled()
|
||||
{
|
||||
var config = Plugin.Instance?.Configuration;
|
||||
return config == null || config.ResolveEnabledUnits().Contains(Unit);
|
||||
}
|
||||
|
||||
private async Task<List<ChannelItemInfo>> GetFolderItemsAsync(string? folderId, CancellationToken cancellationToken)
|
||||
{
|
||||
// If this unit is not enabled in configuration, show nothing (empty tile).
|
||||
if (!IsUnitEnabled())
|
||||
{
|
||||
_logger.LogDebug("Business unit {Unit} is not enabled - returning no items", Unit);
|
||||
return new List<ChannelItemInfo>();
|
||||
}
|
||||
|
||||
// Root level - show folder list
|
||||
if (string.IsNullOrEmpty(folderId))
|
||||
{
|
||||
|
||||
@ -66,7 +66,6 @@ public class PluginConfiguration : BasePluginConfiguration
|
||||
{
|
||||
// Set default options
|
||||
BusinessUnit = BusinessUnit.SRF;
|
||||
EnabledBusinessUnits = new System.Collections.Generic.List<BusinessUnit> { BusinessUnit.SRF };
|
||||
QualityPreference = QualityPreference.Auto;
|
||||
ContentRefreshIntervalHours = 6;
|
||||
ExpirationCheckIntervalHours = 24;
|
||||
@ -79,20 +78,11 @@ public class PluginConfiguration : BasePluginConfiguration
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the legacy single business unit. Retained for backwards compatibility and
|
||||
/// migration into <see cref="EnabledBusinessUnits"/>; new code should use the list instead.
|
||||
/// Gets or sets the legacy single business unit. Retained only for backwards compatibility
|
||||
/// with older configs; every unit now has its own always-on channel.
|
||||
/// </summary>
|
||||
public BusinessUnit BusinessUnit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of business units to expose as channels. One channel tile is shown
|
||||
/// per enabled unit, so polylingual households can browse e.g. SRF (German) and RTS (French)
|
||||
/// at the same time.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Required for configuration serialization")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "Configuration DTO")]
|
||||
public System.Collections.Generic.List<BusinessUnit> EnabledBusinessUnits { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the preferred video quality.
|
||||
/// </summary>
|
||||
@ -172,20 +162,4 @@ public class PluginConfiguration : BasePluginConfiguration
|
||||
/// Gets or sets the output directory for sport livestream recordings.
|
||||
/// </summary>
|
||||
public string RecordingOutputPath { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the effective set of enabled business units, migrating from the legacy single
|
||||
/// <see cref="BusinessUnit"/> value when the list has not been populated yet.
|
||||
/// </summary>
|
||||
/// <returns>The business units that should have channels.</returns>
|
||||
public System.Collections.Generic.IReadOnlyList<BusinessUnit> ResolveEnabledUnits()
|
||||
{
|
||||
if (EnabledBusinessUnits != null && EnabledBusinessUnits.Count > 0)
|
||||
{
|
||||
return EnabledBusinessUnits;
|
||||
}
|
||||
|
||||
// Legacy installs only had a single BusinessUnit; honour it so they keep working.
|
||||
return new System.Collections.Generic.List<BusinessUnit> { BusinessUnit };
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,28 +3,8 @@
|
||||
<div class="content-primary">
|
||||
<form id="SRFPlayConfigForm">
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<h3>Channels (Business Units)</h3>
|
||||
<div class="fieldDescription" style="margin-bottom: 0.5em;">Enable a channel tile for each Swiss broadcaster you want. A separate tile appears per enabled unit, so polylingual households can browse e.g. SRF (German) and RTS (French) at the same time.</div>
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="buSRF" type="checkbox" is="emby-checkbox" data-bu="SRF" />
|
||||
<span>SRF — Schweizer Radio und Fernsehen (German)</span>
|
||||
</label>
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="buRTS" type="checkbox" is="emby-checkbox" data-bu="RTS" />
|
||||
<span>RTS — Radio Télévision Suisse (French)</span>
|
||||
</label>
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="buRSI" type="checkbox" is="emby-checkbox" data-bu="RSI" />
|
||||
<span>RSI — Radiotelevisione svizzera (Italian)</span>
|
||||
</label>
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="buRTR" type="checkbox" is="emby-checkbox" data-bu="RTR" />
|
||||
<span>RTR — Radiotelevisiun Svizra Rumantscha (Romansh)</span>
|
||||
</label>
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="buSWI" type="checkbox" is="emby-checkbox" data-bu="SWI" />
|
||||
<span>SWI — swissinfo.ch (International)</span>
|
||||
</label>
|
||||
<h3>Channels</h3>
|
||||
<div class="fieldDescription">Every Swiss broadcaster has its own channel tile: SRF (German), RTS (French), RSI (Italian), RTR (Romansh) and SWI (swissinfo.ch). They are always available — pin the ones you use from the Jellyfin home screen.</div>
|
||||
</div>
|
||||
<div class="selectContainer">
|
||||
<label class="selectLabel" for="QualityPreference">Quality Preference</label>
|
||||
@ -155,12 +135,6 @@
|
||||
.addEventListener('pageshow', function() {
|
||||
Dashboard.showLoadingMsg();
|
||||
ApiClient.getPluginConfiguration(SRFPlayConfig.pluginUniqueId).then(function (config) {
|
||||
var enabled = (config.EnabledBusinessUnits && config.EnabledBusinessUnits.length)
|
||||
? config.EnabledBusinessUnits
|
||||
: [config.BusinessUnit]; // migrate legacy single value
|
||||
['SRF','RTS','RSI','RTR','SWI'].forEach(function(bu) {
|
||||
document.querySelector('#bu' + bu).checked = enabled.indexOf(bu) !== -1;
|
||||
});
|
||||
document.querySelector('#QualityPreference').value = config.QualityPreference;
|
||||
document.querySelector('#ContentRefreshIntervalHours').value = config.ContentRefreshIntervalHours;
|
||||
document.querySelector('#ExpirationCheckIntervalHours').value = config.ExpirationCheckIntervalHours;
|
||||
@ -186,11 +160,6 @@
|
||||
.addEventListener('submit', function(e) {
|
||||
Dashboard.showLoadingMsg();
|
||||
ApiClient.getPluginConfiguration(SRFPlayConfig.pluginUniqueId).then(function (config) {
|
||||
config.EnabledBusinessUnits = ['SRF','RTS','RSI','RTR','SWI'].filter(function(bu) {
|
||||
return document.querySelector('#bu' + bu).checked;
|
||||
});
|
||||
// Keep legacy field in sync with the first enabled unit for backwards compat.
|
||||
config.BusinessUnit = config.EnabledBusinessUnits[0] || 'SRF';
|
||||
config.QualityPreference = document.querySelector('#QualityPreference').value;
|
||||
config.ContentRefreshIntervalHours = parseInt(document.querySelector('#ContentRefreshIntervalHours').value);
|
||||
config.ExpirationCheckIntervalHours = parseInt(document.querySelector('#ExpirationCheckIntervalHours').value);
|
||||
|
||||
@ -64,8 +64,8 @@ public class ContentRefreshTask : IScheduledTask
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh content for every enabled business unit (one channel per unit).
|
||||
var units = config.ResolveEnabledUnits();
|
||||
// Refresh content for every business unit (one channel per unit).
|
||||
var units = System.Enum.GetValues<Configuration.BusinessUnit>();
|
||||
foreach (var unit in units)
|
||||
{
|
||||
var businessUnit = unit.ToLowerString();
|
||||
|
||||
@ -147,13 +147,12 @@ public class RecordingService : IRecordingService, IDisposable
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<PlayV3TvProgram>> GetUpcomingScheduleAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var config = Plugin.Instance?.Configuration;
|
||||
var units = config?.ResolveEnabledUnits() ?? new[] { Configuration.BusinessUnit.SRF };
|
||||
var units = System.Enum.GetValues<Configuration.BusinessUnit>();
|
||||
|
||||
using var apiClient = _apiClientFactory.CreateClient();
|
||||
|
||||
// Aggregate sport livestreams across every enabled business unit so the recordings
|
||||
// page shows events from all enabled languages.
|
||||
// Aggregate sport livestreams across every business unit so the recordings
|
||||
// page shows events from all languages.
|
||||
var all = new List<PlayV3TvProgram>();
|
||||
foreach (var unit in units)
|
||||
{
|
||||
|
||||
12
manifest-nightly.json
Normal file
12
manifest-nightly.json
Normal file
@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"guid": "a4b12f86-8c3d-4e9a-b7f2-1d5e6c8a9b4f",
|
||||
"name": "SRF Play (Nightly)",
|
||||
"description": "NIGHTLY/UNSTABLE builds of the SRF Play plugin for Jellyfin. Built automatically from the master branch on every push. Expect bugs. For stable releases use the regular SRF Play repository instead.",
|
||||
"overview": "Nightly builds of SRF Play for Jellyfin",
|
||||
"owner": "dtourolle",
|
||||
"category": "Live TV",
|
||||
"imageUrl": "https://gitea.tourolle.paris/dtourolle/jellyfin-srfPlay/raw/branch/master/assests/main%20logo.png",
|
||||
"versions": []
|
||||
}
|
||||
]
|
||||
@ -24,14 +24,6 @@
|
||||
"checksum": "82f53848f2e2b15a35ea731ee69ee402",
|
||||
"timestamp": "2026-06-27T07:16:38Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.0.0",
|
||||
"changelog": "Latest Build",
|
||||
"targetAbi": "10.9.0.0",
|
||||
"sourceUrl": "https://gitea.tourolle.paris/dtourolle/jellyfin-srfPlay/releases/download/latest/srfplay_1.0.0.0.zip",
|
||||
"checksum": "a3848128ff37d68f97493c33538a4d25",
|
||||
"timestamp": "2026-06-27T07:15:26Z"
|
||||
},
|
||||
{
|
||||
"version": "1.0.31",
|
||||
"changelog": "Release 1.0.31",
|
||||
@ -40,14 +32,6 @@
|
||||
"checksum": "4e499de15687e6e328ddca41c6192485",
|
||||
"timestamp": "2026-06-27T06:54:01Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.0.0",
|
||||
"changelog": "Latest Build",
|
||||
"targetAbi": "10.9.0.0",
|
||||
"sourceUrl": "https://gitea.tourolle.paris/dtourolle/jellyfin-srfPlay/releases/download/latest/srfplay_1.0.0.0.zip",
|
||||
"checksum": "1a8b8b018ff6bcdb8da4783b74253fc4",
|
||||
"timestamp": "2026-06-27T06:52:52Z"
|
||||
},
|
||||
{
|
||||
"version": "1.0.30",
|
||||
"changelog": "Release 1.0.30",
|
||||
@ -56,14 +40,6 @@
|
||||
"checksum": "06731df9ba3d2dab53885c9c8ac95fa6",
|
||||
"timestamp": "2026-05-03T16:44:57Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.0.0",
|
||||
"changelog": "Latest Build",
|
||||
"targetAbi": "10.9.0.0",
|
||||
"sourceUrl": "https://gitea.tourolle.paris/dtourolle/jellyfin-srfPlay/releases/download/latest/srfplay_1.0.0.0.zip",
|
||||
"checksum": "834e9f57206eee47ec9607ef6f65d17b",
|
||||
"timestamp": "2026-05-03T16:39:53Z"
|
||||
},
|
||||
{
|
||||
"version": "1.0.29",
|
||||
"changelog": "Release 1.0.29",
|
||||
@ -72,14 +48,6 @@
|
||||
"checksum": "fb745388e64299497262d9ad370d8823",
|
||||
"timestamp": "2026-05-03T16:07:24Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.0.0",
|
||||
"changelog": "Latest Build",
|
||||
"targetAbi": "10.9.0.0",
|
||||
"sourceUrl": "https://gitea.tourolle.paris/dtourolle/jellyfin-srfPlay/releases/download/latest/srfplay_1.0.0.0.zip",
|
||||
"checksum": "7471a63c69deb5b9a31343bb6c49075f",
|
||||
"timestamp": "2026-05-03T16:05:37Z"
|
||||
},
|
||||
{
|
||||
"version": "1.0.27",
|
||||
"changelog": "Release 1.0.27",
|
||||
@ -88,14 +56,6 @@
|
||||
"checksum": "1e15e35452f7b82bf74d8c3560c15949",
|
||||
"timestamp": "2026-03-07T16:40:17Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.0.0",
|
||||
"changelog": "Latest Build",
|
||||
"targetAbi": "10.9.0.0",
|
||||
"sourceUrl": "https://gitea.tourolle.paris/dtourolle/jellyfin-srfPlay/releases/download/latest/srfplay_1.0.0.0.zip",
|
||||
"checksum": "c7e868d23293adcc21d72e735094d9d6",
|
||||
"timestamp": "2026-03-07T16:28:38Z"
|
||||
},
|
||||
{
|
||||
"version": "1.0.25",
|
||||
"changelog": "Release 1.0.25",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user