Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
582c94f5f9 | ||
|
|
dc2dd09e49 | ||
|
|
3cbbd20e71 |
Executable
+62
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build one plugin zip per Jellyfin generation into ./artifacts.
|
||||||
|
#
|
||||||
|
# Jellyfin binds a plugin to the exact API it was compiled against, and 10.11 changed
|
||||||
|
# signatures that still compile but fail at runtime. So each generation gets its own DLL
|
||||||
|
# (see the TargetFrameworks comment in Jellyfin.Plugin.SRFPlay.csproj) and its own
|
||||||
|
# manifest entry. Jellyfin installs the highest version whose targetAbi is <= the server
|
||||||
|
# version, so the flavour code is folded into the version to keep them apart:
|
||||||
|
#
|
||||||
|
# net8.0 -> targetAbi 10.9.0.0 (Jellyfin 10.9 / 10.10) code 9
|
||||||
|
# net9.0 -> targetAbi 10.11.0.0 (Jellyfin 10.11) code 11
|
||||||
|
# net10.0 -> targetAbi 12.0.0.0 (Jellyfin 12) code 12
|
||||||
|
#
|
||||||
|
# Usage: build-plugins.sh <version-format>
|
||||||
|
# <version-format> is a printf format that receives the flavour code, e.g.
|
||||||
|
# "1.2.0.%d" -> 1.2.0.9, 1.2.0.11, 1.2.0.12 (release)
|
||||||
|
# "1.0.20260924.204%02d" -> 1.0.20260924.20409, ...20411, ...20412 (nightly)
|
||||||
|
#
|
||||||
|
# Writes artifacts/builds.tsv with one line per zip: version, targetAbi, zip path, md5.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
FORMAT="$1"
|
||||||
|
|
||||||
|
FLAVOURS=(
|
||||||
|
"net8.0 10.9.0.0 9"
|
||||||
|
"net9.0 10.11.0.0 11"
|
||||||
|
"net10.0 12.0.0.0 12"
|
||||||
|
)
|
||||||
|
|
||||||
|
mkdir -p artifacts
|
||||||
|
: > artifacts/builds.tsv
|
||||||
|
|
||||||
|
# jprm has no targetAbi flag and reads it from build.yaml, so rewrite it per flavour.
|
||||||
|
cp build.yaml build.yaml.orig
|
||||||
|
trap 'mv -f build.yaml.orig build.yaml' EXIT
|
||||||
|
|
||||||
|
for flavour in "${FLAVOURS[@]}"; do
|
||||||
|
read -r framework abi code <<< "$flavour"
|
||||||
|
# shellcheck disable=SC2059 # the format is the argument by design
|
||||||
|
version=$(printf "$FORMAT" "$code")
|
||||||
|
|
||||||
|
cp build.yaml.orig build.yaml
|
||||||
|
sed -i \
|
||||||
|
-e "s/^version:.*/version: \"${version}\"/" \
|
||||||
|
-e "s/^targetAbi:.*/targetAbi: \"${abi}\"/" \
|
||||||
|
-e "s/^framework:.*/framework: \"${framework}\"/" \
|
||||||
|
-e "s/^dotnet_framework:.*/dotnet_framework: \"${framework}\"/" \
|
||||||
|
build.yaml
|
||||||
|
|
||||||
|
echo ">> ${version}: ${framework}, targetAbi ${abi}"
|
||||||
|
jprm --verbosity=debug plugin build . --dotnet-framework "${framework}" --version "${version}"
|
||||||
|
|
||||||
|
zip="artifacts/srfplay_${version}.zip"
|
||||||
|
if [ ! -f "${zip}" ]; then
|
||||||
|
echo "jprm did not produce ${zip}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\t%s\t%s\t%s\n' "${version}" "${abi}" "${zip}" "$(md5sum "${zip}" | awk '{print $1}')" >> artifacts/builds.tsv
|
||||||
|
done
|
||||||
|
|
||||||
|
cat artifacts/builds.tsv
|
||||||
@@ -51,14 +51,10 @@ jobs:
|
|||||||
LABEL="master"
|
LABEL="master"
|
||||||
fi
|
fi
|
||||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
echo "format=${VERSION}%02d" >> $GITHUB_OUTPUT
|
||||||
echo "label=${LABEL}" >> $GITHUB_OUTPUT
|
echo "label=${LABEL}" >> $GITHUB_OUTPUT
|
||||||
echo "Build version: ${VERSION} (${LABEL})"
|
echo "Build version: ${VERSION} (${LABEL})"
|
||||||
|
|
||||||
- name: Set build version
|
|
||||||
working-directory: build-${{ github.run_id }}
|
|
||||||
run: |
|
|
||||||
sed -i "s/^version:.*/version: \"${{ steps.version.outputs.version }}\"/" build.yaml
|
|
||||||
|
|
||||||
- name: Build solution
|
- name: Build solution
|
||||||
working-directory: build-${{ github.run_id }}
|
working-directory: build-${{ github.run_id }}
|
||||||
run: dotnet build Jellyfin.Plugin.SRFPlay.sln --configuration Release --no-restore --no-self-contained /m:1
|
run: dotnet build Jellyfin.Plugin.SRFPlay.sln --configuration Release --no-restore --no-self-contained /m:1
|
||||||
@@ -67,23 +63,16 @@ jobs:
|
|||||||
working-directory: build-${{ github.run_id }}
|
working-directory: build-${{ github.run_id }}
|
||||||
run: dotnet test Jellyfin.Plugin.SRFPlay.sln --no-build --configuration Release --verbosity normal
|
run: dotnet test Jellyfin.Plugin.SRFPlay.sln --no-build --configuration Release --verbosity normal
|
||||||
|
|
||||||
- name: Build Jellyfin Plugin
|
- name: Build Jellyfin Plugins
|
||||||
id: jprm
|
|
||||||
working-directory: build-${{ github.run_id }}
|
working-directory: build-${{ github.run_id }}
|
||||||
run: |
|
# One zip per Jellyfin generation (see .gitea/scripts/build-plugins.sh).
|
||||||
mkdir -p artifacts
|
run: .gitea/scripts/build-plugins.sh "${{ steps.version.outputs.format }}"
|
||||||
jprm --verbosity=debug plugin build .
|
|
||||||
ARTIFACT=$(find . -name "*.zip" -type f -print -quit | sed 's|^\./||')
|
|
||||||
LATEST="artifacts/srfplay_latest.zip"
|
|
||||||
cp "${ARTIFACT}" "${LATEST}"
|
|
||||||
echo "artifact=${LATEST}" >> $GITHUB_OUTPUT
|
|
||||||
echo "Found artifact: ${ARTIFACT} -> ${LATEST}"
|
|
||||||
|
|
||||||
- name: Upload build artifact
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: srfplay-${{ steps.version.outputs.label }}-${{ steps.version.outputs.version }}
|
name: srfplay-${{ steps.version.outputs.label }}-${{ steps.version.outputs.version }}
|
||||||
path: build-${{ github.run_id }}/${{ steps.jprm.outputs.artifact }}
|
path: build-${{ github.run_id }}/artifacts/*.zip
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
|||||||
@@ -27,18 +27,15 @@ jobs:
|
|||||||
# Date-based nightly version: 1.0.<YYYYMMDD>.<run_number>
|
# Date-based nightly version: 1.0.<YYYYMMDD>.<run_number>
|
||||||
# The run_number suffix keeps multiple builds on the same day
|
# The run_number suffix keeps multiple builds on the same day
|
||||||
# monotonically increasing so Jellyfin always offers the newest.
|
# monotonically increasing so Jellyfin always offers the newest.
|
||||||
|
# Each Jellyfin generation gets its own build, so the last segment
|
||||||
|
# is <run_number><2-digit flavour code>, e.g. run 204 -> 20409/20411/20412
|
||||||
|
# (see .gitea/scripts/build-plugins.sh).
|
||||||
DATE=$(date -u +"%Y%m%d")
|
DATE=$(date -u +"%Y%m%d")
|
||||||
VERSION="1.0.${DATE}.${{ github.run_number }}"
|
VERSION="1.0.${DATE}.${{ github.run_number }}"
|
||||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
echo "format=${VERSION}%02d" >> $GITHUB_OUTPUT
|
||||||
echo "Nightly version: ${VERSION}"
|
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
|
- name: Restore dependencies
|
||||||
working-directory: build-${{ github.run_id }}
|
working-directory: build-${{ github.run_id }}
|
||||||
run: dotnet restore Jellyfin.Plugin.SRFPlay.sln
|
run: dotnet restore Jellyfin.Plugin.SRFPlay.sln
|
||||||
@@ -51,25 +48,9 @@ jobs:
|
|||||||
working-directory: build-${{ github.run_id }}
|
working-directory: build-${{ github.run_id }}
|
||||||
run: dotnet test Jellyfin.Plugin.SRFPlay.sln --no-build --configuration Release --verbosity normal
|
run: dotnet test Jellyfin.Plugin.SRFPlay.sln --no-build --configuration Release --verbosity normal
|
||||||
|
|
||||||
- name: Build Jellyfin Plugin
|
- name: Build Jellyfin Plugins
|
||||||
id: jprm
|
|
||||||
working-directory: build-${{ github.run_id }}
|
working-directory: build-${{ github.run_id }}
|
||||||
run: |
|
run: .gitea/scripts/build-plugins.sh "${{ steps.version.outputs.format }}"
|
||||||
mkdir -p artifacts
|
|
||||||
jprm --verbosity=debug plugin build .
|
|
||||||
ARTIFACT=$(find . -name "*.zip" -type f -print -quit | sed 's|^\./||')
|
|
||||||
ARTIFACT_NAME=$(basename "${ARTIFACT}")
|
|
||||||
echo "artifact=${ARTIFACT}" >> $GITHUB_OUTPUT
|
|
||||||
echo "artifact_name=${ARTIFACT_NAME}" >> $GITHUB_OUTPUT
|
|
||||||
echo "Found artifact: ${ARTIFACT}"
|
|
||||||
|
|
||||||
- name: Calculate checksum
|
|
||||||
id: checksum
|
|
||||||
working-directory: build-${{ github.run_id }}
|
|
||||||
run: |
|
|
||||||
CHECKSUM=$(md5sum "${{ steps.jprm.outputs.artifact }}" | awk '{print $1}')
|
|
||||||
echo "checksum=${CHECKSUM}" >> $GITHUB_OUTPUT
|
|
||||||
echo "Checksum: ${CHECKSUM}"
|
|
||||||
|
|
||||||
- name: Delete existing nightly release
|
- name: Delete existing nightly release
|
||||||
working-directory: build-${{ github.run_id }}
|
working-directory: build-${{ github.run_id }}
|
||||||
@@ -129,13 +110,15 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Upload plugin artifact
|
# Upload one plugin zip per Jellyfin generation
|
||||||
echo "Uploading plugin artifact..."
|
while IFS=$'\t' read -r _ _ ZIP _; do
|
||||||
curl -f -X POST \
|
echo "Uploading ${ZIP}..."
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
curl -f -X POST \
|
||||||
-H "Content-Type: application/zip" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
--data-binary "@${{ steps.jprm.outputs.artifact }}" \
|
-H "Content-Type: application/zip" \
|
||||||
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=${{ steps.jprm.outputs.artifact_name }}"
|
--data-binary "@${ZIP}" \
|
||||||
|
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=$(basename "${ZIP}")"
|
||||||
|
done < artifacts/builds.tsv
|
||||||
|
|
||||||
# Upload build.yaml
|
# Upload build.yaml
|
||||||
echo "Uploading build.yaml..."
|
echo "Uploading build.yaml..."
|
||||||
@@ -155,12 +138,10 @@ jobs:
|
|||||||
REPO_OWNER="${{ github.repository_owner }}"
|
REPO_OWNER="${{ github.repository_owner }}"
|
||||||
REPO_NAME="${{ github.event.repository.name }}"
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
GITEA_URL="${{ github.server_url }}"
|
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")
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
DOWNLOAD_URL="${GITEA_URL}/${REPO_OWNER}/${REPO_NAME}/releases/download/latest/${ARTIFACT_NAME}"
|
|
||||||
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||||
|
# builds.tsv is read before the checkout below replaces the tree.
|
||||||
|
BUILDS=$(cat artifacts/builds.tsv)
|
||||||
|
|
||||||
git config user.name "Gitea Actions"
|
git config user.name "Gitea Actions"
|
||||||
git config user.email "actions@gitea.tourolle.paris"
|
git config user.email "actions@gitea.tourolle.paris"
|
||||||
@@ -178,25 +159,24 @@ jobs:
|
|||||||
# edits must be master, not whatever this build started from.
|
# edits must be master, not whatever this build started from.
|
||||||
git checkout -B master origin/master
|
git checkout -B master origin/master
|
||||||
|
|
||||||
NEW_VERSION=$(cat <<EOF
|
# One entry per Jellyfin generation; each server installs the newest
|
||||||
{
|
# entry whose targetAbi it satisfies.
|
||||||
"version": "${VERSION}",
|
NEW_VERSIONS=$(echo "${BUILDS}" | jq -R -s \
|
||||||
"changelog": "Nightly build ${VERSION} (${SHORT_SHA})",
|
--arg url "${GITEA_URL}/${REPO_OWNER}/${REPO_NAME}/releases/download/latest" \
|
||||||
"targetAbi": "10.9.0.0",
|
--arg sha "${SHORT_SHA}" --arg ts "${TIMESTAMP}" \
|
||||||
"sourceUrl": "${DOWNLOAD_URL}",
|
'split("\n") | map(select(length > 0) | split("\t")
|
||||||
"checksum": "${CHECKSUM}",
|
| {version: .[0], changelog: "Nightly build \(.[0]) (\($sha))", targetAbi: .[1],
|
||||||
"timestamp": "${TIMESTAMP}"
|
sourceUrl: "\($url)/\(.[2] | split("/") | last)", checksum: .[3], timestamp: $ts})
|
||||||
}
|
| reverse')
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
# Prepend the new build and keep only the most recent 5 nightlies.
|
# Prepend the new builds and keep only the most recent 5 nightlies
|
||||||
jq --argjson newver "${NEW_VERSION}" \
|
# (3 entries each, one per Jellyfin generation).
|
||||||
'.[0].versions = ([$newver] + .[0].versions)[0:5]' \
|
jq --argjson newvers "${NEW_VERSIONS}" \
|
||||||
|
'.[0].versions = ($newvers + .[0].versions)[0:15]' \
|
||||||
manifest-nightly.json > manifest.tmp && mv manifest.tmp manifest-nightly.json
|
manifest-nightly.json > manifest.tmp && mv manifest.tmp manifest-nightly.json
|
||||||
|
|
||||||
git add manifest-nightly.json
|
git add manifest-nightly.json
|
||||||
git commit -m "Nightly: ${VERSION} (${SHORT_SHA})" || echo "No changes to commit"
|
git commit -m "Nightly: ${{ steps.version.outputs.version }} (${SHORT_SHA})" || echo "No changes to commit"
|
||||||
git push origin master
|
git push origin master
|
||||||
|
|
||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
|
|||||||
@@ -35,13 +35,6 @@ jobs:
|
|||||||
echo "version_number=${VERSION#v}" >> $GITHUB_OUTPUT
|
echo "version_number=${VERSION#v}" >> $GITHUB_OUTPUT
|
||||||
echo "Building version: ${VERSION}"
|
echo "Building version: ${VERSION}"
|
||||||
|
|
||||||
- name: Update build.yaml with version
|
|
||||||
working-directory: release-${{ github.run_id }}
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.get_version.outputs.version_number }}"
|
|
||||||
sed -i "s/^version:.*/version: \"${VERSION}\"/" build.yaml
|
|
||||||
cat build.yaml
|
|
||||||
|
|
||||||
- name: Cache NuGet packages
|
- name: Cache NuGet packages
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
@@ -61,25 +54,11 @@ jobs:
|
|||||||
working-directory: release-${{ github.run_id }}
|
working-directory: release-${{ github.run_id }}
|
||||||
run: dotnet test Jellyfin.Plugin.SRFPlay.sln --no-build --configuration Release --verbosity normal
|
run: dotnet test Jellyfin.Plugin.SRFPlay.sln --no-build --configuration Release --verbosity normal
|
||||||
|
|
||||||
- name: Build Jellyfin Plugin
|
- name: Build Jellyfin Plugins
|
||||||
id: jprm
|
|
||||||
working-directory: release-${{ github.run_id }}
|
working-directory: release-${{ github.run_id }}
|
||||||
run: |
|
# v1.2.0 -> 1.2.0.9 / 1.2.0.11 / 1.2.0.12, one per Jellyfin generation
|
||||||
mkdir -p artifacts
|
# (see .gitea/scripts/build-plugins.sh).
|
||||||
jprm --verbosity=debug plugin build ./
|
run: .gitea/scripts/build-plugins.sh "${{ steps.get_version.outputs.version_number }}.%d"
|
||||||
ARTIFACT=$(find . -name "*.zip" -type f -print -quit | sed 's|^\./||')
|
|
||||||
ARTIFACT_NAME=$(basename "${ARTIFACT}")
|
|
||||||
echo "artifact=${ARTIFACT}" >> $GITHUB_OUTPUT
|
|
||||||
echo "artifact_name=${ARTIFACT_NAME}" >> $GITHUB_OUTPUT
|
|
||||||
echo "Found artifact: ${ARTIFACT}"
|
|
||||||
|
|
||||||
- name: Calculate checksum
|
|
||||||
id: checksum
|
|
||||||
working-directory: release-${{ github.run_id }}
|
|
||||||
run: |
|
|
||||||
CHECKSUM=$(md5sum "${{ steps.jprm.outputs.artifact }}" | awk '{print $1}')
|
|
||||||
echo "checksum=${CHECKSUM}" >> $GITHUB_OUTPUT
|
|
||||||
echo "Checksum: ${CHECKSUM}"
|
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
working-directory: release-${{ github.run_id }}
|
working-directory: release-${{ github.run_id }}
|
||||||
@@ -111,13 +90,15 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Upload plugin artifact
|
# Upload one plugin zip per Jellyfin generation
|
||||||
echo "Uploading plugin artifact..."
|
while IFS=$'\t' read -r _ _ ZIP _; do
|
||||||
curl -f -X POST \
|
echo "Uploading ${ZIP}..."
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
curl -f -X POST \
|
||||||
-H "Content-Type: application/zip" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
--data-binary "@${{ steps.jprm.outputs.artifact }}" \
|
-H "Content-Type: application/zip" \
|
||||||
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=${{ steps.jprm.outputs.artifact_name }}"
|
--data-binary "@${ZIP}" \
|
||||||
|
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=$(basename "${ZIP}")"
|
||||||
|
done < artifacts/builds.tsv
|
||||||
|
|
||||||
# Upload build.yaml
|
# Upload build.yaml
|
||||||
echo "Uploading build.yaml..."
|
echo "Uploading build.yaml..."
|
||||||
@@ -139,29 +120,28 @@ jobs:
|
|||||||
REPO_NAME="${{ github.event.repository.name }}"
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
GITEA_URL="${{ github.server_url }}"
|
GITEA_URL="${{ github.server_url }}"
|
||||||
VERSION="${{ steps.get_version.outputs.version_number }}"
|
VERSION="${{ steps.get_version.outputs.version_number }}"
|
||||||
CHECKSUM="${{ steps.checksum.outputs.checksum }}"
|
|
||||||
ARTIFACT_NAME="${{ steps.jprm.outputs.artifact_name }}"
|
|
||||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
DOWNLOAD_URL="${GITEA_URL}/${REPO_OWNER}/${REPO_NAME}/releases/download/${{ steps.get_version.outputs.version }}/${ARTIFACT_NAME}"
|
# builds.tsv is read before the checkout below replaces the tree.
|
||||||
|
BUILDS=$(cat artifacts/builds.tsv)
|
||||||
|
|
||||||
git config user.name "Gitea Actions"
|
git config user.name "Gitea Actions"
|
||||||
git config user.email "actions@gitea.tourolle.paris"
|
git config user.email "actions@gitea.tourolle.paris"
|
||||||
git fetch origin master
|
git fetch origin master
|
||||||
git checkout master
|
# -B against origin/master for the same reason as the nightly: a bare
|
||||||
|
# `checkout master` edits the manifest on a stale base if master moved.
|
||||||
|
git checkout -B master origin/master
|
||||||
|
|
||||||
NEW_VERSION=$(cat <<EOF
|
# One entry per Jellyfin generation; each server installs the newest
|
||||||
{
|
# entry whose targetAbi it satisfies.
|
||||||
"version": "${VERSION}",
|
NEW_VERSIONS=$(echo "${BUILDS}" | jq -R -s \
|
||||||
"changelog": "Release ${VERSION}",
|
--arg url "${GITEA_URL}/${REPO_OWNER}/${REPO_NAME}/releases/download/${{ steps.get_version.outputs.version }}" \
|
||||||
"targetAbi": "10.9.0.0",
|
--arg release "${VERSION}" --arg ts "${TIMESTAMP}" \
|
||||||
"sourceUrl": "${DOWNLOAD_URL}",
|
'split("\n") | map(select(length > 0) | split("\t")
|
||||||
"checksum": "${CHECKSUM}",
|
| {version: .[0], changelog: "Release \($release)", targetAbi: .[1],
|
||||||
"timestamp": "${TIMESTAMP}"
|
sourceUrl: "\($url)/\(.[2] | split("/") | last)", checksum: .[3], timestamp: $ts})
|
||||||
}
|
| reverse')
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
jq --argjson newver "${NEW_VERSION}" '.[0].versions = [$newver] + .[0].versions' manifest.json > manifest.tmp && mv manifest.tmp manifest.json
|
jq --argjson newvers "${NEW_VERSIONS}" '.[0].versions = $newvers + .[0].versions' manifest.json > manifest.tmp && mv manifest.tmp manifest.json
|
||||||
git add manifest.json
|
git add manifest.json
|
||||||
git commit -m "Update manifest.json for version ${VERSION}"
|
git commit -m "Update manifest.json for version ${VERSION}"
|
||||||
git push origin master
|
git push origin master
|
||||||
|
|||||||
@@ -5,13 +5,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<!-- Mirrors the plugin: jprm publishes the whole solution for one framework at a time. -->
|
||||||
|
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -257,7 +257,7 @@
|
|||||||
|
|
||||||
fetch(ApiClient.serverAddress() + '/Plugins/SRFPlay/Maintenance/ResumePoints/Cleanup?clearAll=' + (clearAll ? 'true' : 'false'), {
|
fetch(ApiClient.serverAddress() + '/Plugins/SRFPlay/Maintenance/ResumePoints/Cleanup?clearAll=' + (clearAll ? 'true' : 'false'), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'X-Emby-Token': ApiClient.accessToken() }
|
headers: { 'Authorization': 'MediaBrowser Token="' + ApiClient.accessToken() + '"' }
|
||||||
})
|
})
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -285,7 +285,7 @@
|
|||||||
|
|
||||||
getHeaders: function() {
|
getHeaders: function() {
|
||||||
return {
|
return {
|
||||||
'X-Emby-Token': ApiClient.accessToken()
|
'Authorization': 'MediaBrowser Token="' + ApiClient.accessToken() + '"'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
verifySession: function(session) {
|
verifySession: function(session) {
|
||||||
var self = this;
|
var self = this;
|
||||||
fetch(this.serverUrl + '/System/Info', {
|
fetch(this.serverUrl + '/System/Info', {
|
||||||
headers: { 'X-Emby-Token': this.token }
|
headers: { 'Authorization': 'MediaBrowser Token="' + this.token + '"' }
|
||||||
}).then(function(r) {
|
}).then(function(r) {
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
document.getElementById('srfrecUserInfo').textContent = '(Server: ' + self.serverUrl + ')';
|
document.getElementById('srfrecUserInfo').textContent = '(Server: ' + self.serverUrl + ')';
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Emby-Authorization': 'MediaBrowser Client="SRF Recordings", Device="Web", DeviceId="srfrec-' + Date.now() + '", Version="1.0.0"'
|
'Authorization': 'MediaBrowser Client="SRF Recordings", Device="Web", DeviceId="srfrec-' + Date.now() + '", Version="1.0.0"'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ Username: user, Pw: pass })
|
body: JSON.stringify({ Username: user, Pw: pass })
|
||||||
})
|
})
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
getHeaders: function() {
|
getHeaders: function() {
|
||||||
return { 'X-Emby-Token': this.token };
|
return { 'Authorization': 'MediaBrowser Token="' + this.token + '"' };
|
||||||
},
|
},
|
||||||
|
|
||||||
formatDate: function(dateStr) {
|
formatDate: function(dateStr) {
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<!--
|
||||||
|
One DLL per Jellyfin generation. The server binds to the Jellyfin API it was built
|
||||||
|
against, and 10.11 changed signatures (User moved, TaskTriggerInfo.Type became an enum,
|
||||||
|
GetItemList return type) that still compile from source but fail at runtime, so a single
|
||||||
|
net8.0 build silently half-works on newer servers. Each TFM pins the oldest package of its
|
||||||
|
line so it loads on every patch release of that line:
|
||||||
|
net8.0 -> Jellyfin 10.9 / 10.10 (targetAbi 10.9.0.0)
|
||||||
|
net9.0 -> Jellyfin 10.11 (targetAbi 10.11.0.0)
|
||||||
|
net10.0 -> Jellyfin 12 (targetAbi 12.0.0.0)
|
||||||
|
-->
|
||||||
|
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||||
<RootNamespace>Jellyfin.Plugin.SRFPlay</RootNamespace>
|
<RootNamespace>Jellyfin.Plugin.SRFPlay</RootNamespace>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
@@ -10,9 +20,30 @@
|
|||||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<PropertyGroup Condition="'$(TargetFramework)' != 'net8.0'">
|
||||||
|
<DefineConstants>$(DefineConstants);JELLYFIN_10_11_OR_GREATER</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net10.0'">
|
||||||
|
<DefineConstants>$(DefineConstants);JELLYFIN_12_OR_GREATER</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||||
<PackageReference Include="Jellyfin.Controller" Version="10.9.11" />
|
<PackageReference Include="Jellyfin.Controller" Version="10.9.11" />
|
||||||
<PackageReference Include="Jellyfin.Model" Version="10.9.11" />
|
<PackageReference Include="Jellyfin.Model" Version="10.9.11" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||||
|
<PackageReference Include="Jellyfin.Controller" Version="10.11.0" />
|
||||||
|
<PackageReference Include="Jellyfin.Model" Version="10.11.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
|
||||||
|
<PackageReference Include="Jellyfin.Controller" Version="12.0.0" />
|
||||||
|
<PackageReference Include="Jellyfin.Model" Version="12.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
<PackageReference Include="Socks5" Version="1.1.0" />
|
<PackageReference Include="Socks5" Version="1.1.0" />
|
||||||
<PackageReference Include="SkiaSharp" Version="2.88.8" />
|
<PackageReference Include="SkiaSharp" Version="2.88.8" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -104,11 +104,7 @@ public class ContentRefreshTask : IScheduledTask
|
|||||||
// Run every X hours as configured
|
// Run every X hours as configured
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
new TaskTriggerInfo
|
TaskTriggers.Interval(TimeSpan.FromHours(intervalHours))
|
||||||
{
|
|
||||||
Type = TaskTriggerInfo.TriggerInterval,
|
|
||||||
IntervalTicks = TimeSpan.FromHours(intervalHours).Ticks
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Utilities;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -87,16 +88,8 @@ public class ExpirationCheckTask : IScheduledTask
|
|||||||
// Run daily at 3 AM or every X hours as configured
|
// Run daily at 3 AM or every X hours as configured
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
new TaskTriggerInfo
|
TaskTriggers.Daily(TimeSpan.FromHours(3)),
|
||||||
{
|
TaskTriggers.Interval(TimeSpan.FromHours(intervalHours))
|
||||||
Type = TaskTriggerInfo.TriggerDaily,
|
|
||||||
TimeOfDayTicks = TimeSpan.FromHours(3).Ticks
|
|
||||||
},
|
|
||||||
new TaskTriggerInfo
|
|
||||||
{
|
|
||||||
Type = TaskTriggerInfo.TriggerInterval,
|
|
||||||
IntervalTicks = TimeSpan.FromHours(intervalHours).Ticks
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Utilities;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -66,11 +67,7 @@ public class RecordingSchedulerTask : IScheduledTask
|
|||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
new TaskTriggerInfo
|
TaskTriggers.Interval(TimeSpan.FromSeconds(30))
|
||||||
{
|
|
||||||
Type = TaskTriggerInfo.TriggerInterval,
|
|
||||||
IntervalTicks = TimeSpan.FromSeconds(30).Ticks
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
||||||
|
using Jellyfin.Plugin.SRFPlay.Utilities;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -77,11 +78,7 @@ public class ResumeCleanupTask : IScheduledTask
|
|||||||
// Runs after the 3 AM expiration check so items deleted there are already gone.
|
// Runs after the 3 AM expiration check so items deleted there are already gone.
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
new TaskTriggerInfo
|
TaskTriggers.Daily(TimeSpan.FromHours(4))
|
||||||
{
|
|
||||||
Type = TaskTriggerInfo.TriggerDaily,
|
|
||||||
TimeOfDayTicks = TimeSpan.FromHours(4).Ticks
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
#if JELLYFIN_10_11_OR_GREATER
|
||||||
|
using Jellyfin.Database.Implementations.Entities;
|
||||||
|
#else
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
|
#endif
|
||||||
using Jellyfin.Plugin.SRFPlay.Api.Models;
|
using Jellyfin.Plugin.SRFPlay.Api.Models;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
#if JELLYFIN_10_11_OR_GREATER
|
||||||
|
using Jellyfin.Database.Implementations.Entities;
|
||||||
|
#else
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
|
#endif
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Plugin.SRFPlay.Api.Models;
|
using Jellyfin.Plugin.SRFPlay.Api.Models;
|
||||||
using Jellyfin.Plugin.SRFPlay.Configuration;
|
using Jellyfin.Plugin.SRFPlay.Configuration;
|
||||||
@@ -71,7 +75,11 @@ public class ResumeCleanupService : IResumeCleanupService
|
|||||||
|
|
||||||
var channelIds = GetPluginChannelIds();
|
var channelIds = GetPluginChannelIds();
|
||||||
|
|
||||||
|
#if JELLYFIN_12_OR_GREATER
|
||||||
|
foreach (var user in _userManager.GetUsers())
|
||||||
|
#else
|
||||||
foreach (var user in _userManager.Users)
|
foreach (var user in _userManager.Users)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
result.UsersChecked++;
|
result.UsersChecked++;
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.SRFPlay.Utilities;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds scheduled task triggers across Jellyfin versions.
|
||||||
|
/// Jellyfin 10.11 replaced the string constants on <see cref="TaskTriggerInfo"/> with the
|
||||||
|
/// <c>TaskTriggerInfoType</c> enum. A task whose GetDefaultTriggers throws is silently given a
|
||||||
|
/// 24h fallback trigger, so this must be built against the matching Jellyfin version.
|
||||||
|
/// </summary>
|
||||||
|
public static class TaskTriggers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a trigger that fires repeatedly at a fixed interval.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="interval">The interval between runs.</param>
|
||||||
|
/// <returns>The trigger.</returns>
|
||||||
|
public static TaskTriggerInfo Interval(TimeSpan interval)
|
||||||
|
{
|
||||||
|
return new TaskTriggerInfo
|
||||||
|
{
|
||||||
|
#if JELLYFIN_10_11_OR_GREATER
|
||||||
|
Type = TaskTriggerInfoType.IntervalTrigger,
|
||||||
|
#else
|
||||||
|
Type = TaskTriggerInfo.TriggerInterval,
|
||||||
|
#endif
|
||||||
|
IntervalTicks = interval.Ticks
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a trigger that fires once a day at the given time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeOfDay">The time of day to run at.</param>
|
||||||
|
/// <returns>The trigger.</returns>
|
||||||
|
public static TaskTriggerInfo Daily(TimeSpan timeOfDay)
|
||||||
|
{
|
||||||
|
return new TaskTriggerInfo
|
||||||
|
{
|
||||||
|
#if JELLYFIN_10_11_OR_GREATER
|
||||||
|
Type = TaskTriggerInfoType.DailyTrigger,
|
||||||
|
#else
|
||||||
|
Type = TaskTriggerInfo.TriggerDaily,
|
||||||
|
#endif
|
||||||
|
TimeOfDayTicks = timeOfDay.Ticks
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,9 +99,20 @@ Examples:
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- .NET 8.0 SDK
|
- .NET 10.0 SDK (builds all three targets below)
|
||||||
- Jellyfin 10.9.11 or later
|
- Jellyfin 10.9.11 or later
|
||||||
|
|
||||||
|
The plugin is built once per Jellyfin generation, because 10.11 changed APIs in ways that
|
||||||
|
compile but fail at runtime. Installing from the repository picks the right one automatically.
|
||||||
|
|
||||||
|
| Jellyfin | Target framework | targetAbi |
|
||||||
|
|---|---|---|
|
||||||
|
| 10.9, 10.10 | net8.0 | 10.9.0.0 |
|
||||||
|
| 10.11 | net9.0 | 10.11.0.0 |
|
||||||
|
| 12 | net10.0 | 12.0.0.0 |
|
||||||
|
|
||||||
|
`.gitea/scripts/build-plugins.sh` builds all three zips the way CI does.
|
||||||
|
|
||||||
### Build Steps
|
### Build Steps
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -111,7 +122,7 @@ dotnet build
|
|||||||
|
|
||||||
### Output
|
### Output
|
||||||
|
|
||||||
The compiled plugin will be in `bin/Debug/net8.0/`
|
The compiled plugins will be in `bin/Debug/net8.0/`, `bin/Debug/net9.0/` and `bin/Debug/net10.0/`
|
||||||
|
|
||||||
## Manual Installation
|
## Manual Installation
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,8 @@
|
|||||||
<Rule Id="CA1308" Action="None" />
|
<Rule Id="CA1308" Action="None" />
|
||||||
<!-- disable warning CA1848: Use the LoggerMessage delegates -->
|
<!-- disable warning CA1848: Use the LoggerMessage delegates -->
|
||||||
<Rule Id="CA1848" Action="None" />
|
<Rule Id="CA1848" Action="None" />
|
||||||
|
<!-- disable warning CA1873: Avoid potentially expensive logging (new in the .NET 10 analyzers; same family as CA1848) -->
|
||||||
|
<Rule Id="CA1873" Action="None" />
|
||||||
<!-- disable warning CA2101: Specify marshaling for P/Invoke string arguments -->
|
<!-- disable warning CA2101: Specify marshaling for P/Invoke string arguments -->
|
||||||
<Rule Id="CA2101" Action="None" />
|
<Rule Id="CA2101" Action="None" />
|
||||||
<!-- disable warning CA2234: Pass System.Uri objects instead of strings -->
|
<!-- disable warning CA2234: Pass System.Uri objects instead of strings -->
|
||||||
|
|||||||
@@ -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.20260908.201",
|
||||||
|
"changelog": "Nightly build 1.0.20260908.201 (d0d5d06)",
|
||||||
|
"targetAbi": "10.9.0.0",
|
||||||
|
"sourceUrl": "https://gitea.tourolle.paris/dtourolle/jellyfin-srfPlay/releases/download/latest/srfplay_1.0.20260908.201.zip",
|
||||||
|
"checksum": "e1c9ed45efbbe7c889f2158fe7c1c537",
|
||||||
|
"timestamp": "2026-09-08T17:28:03Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "1.0.20260903.198",
|
"version": "1.0.20260903.198",
|
||||||
"changelog": "Nightly build 1.0.20260903.198 (699b543)",
|
"changelog": "Nightly build 1.0.20260903.198 (699b543)",
|
||||||
|
|||||||
@@ -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.1.2",
|
||||||
|
"changelog": "Release 1.1.2",
|
||||||
|
"targetAbi": "10.9.0.0",
|
||||||
|
"sourceUrl": "https://gitea.tourolle.paris/dtourolle/jellyfin-srfPlay/releases/download/v1.1.2/srfplay_1.1.2.0.zip",
|
||||||
|
"checksum": "09b96308e9796bf25d681da29d10bfed",
|
||||||
|
"timestamp": "2026-09-08T17:31:00Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"changelog": "Release 1.1.1",
|
"changelog": "Release 1.1.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user