Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa1ccbe458 | ||
|
|
702a5abdc5 | ||
|
|
cdab2d76a7 | ||
|
|
9202ab62ed | ||
|
|
1b23e203ce | ||
|
|
611fb52d76 | ||
|
|
3336bac3fb | ||
|
|
462c8a7c7b |
@@ -0,0 +1,170 @@
|
|||||||
|
name: 'Latest Release'
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- '**/*.md'
|
||||||
|
- 'manifest.json'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
latest-release:
|
||||||
|
runs-on: linux/amd64
|
||||||
|
container:
|
||||||
|
image: gitea.tourolle.paris/dtourolle/srfplay-builder:latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: build-${{ github.run_id }}
|
||||||
|
|
||||||
|
- name: Restore dependencies
|
||||||
|
working-directory: build-${{ github.run_id }}
|
||||||
|
run: dotnet restore Jellyfin.Plugin.SRFPlay.sln
|
||||||
|
|
||||||
|
- name: Build solution
|
||||||
|
working-directory: build-${{ github.run_id }}
|
||||||
|
run: dotnet build Jellyfin.Plugin.SRFPlay.sln --configuration Release --no-restore --no-self-contained /m:1
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
working-directory: build-${{ github.run_id }}
|
||||||
|
run: dotnet test Jellyfin.Plugin.SRFPlay.sln --no-build --configuration Release --verbosity normal
|
||||||
|
|
||||||
|
- name: Build Jellyfin Plugin
|
||||||
|
id: jprm
|
||||||
|
working-directory: build-${{ github.run_id }}
|
||||||
|
run: |
|
||||||
|
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 latest release
|
||||||
|
working-directory: build-${{ github.run_id }}
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
REPO_OWNER="${{ github.repository_owner }}"
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
GITEA_URL="${{ github.server_url }}"
|
||||||
|
TAG="latest"
|
||||||
|
|
||||||
|
EXISTING=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${TAG}")
|
||||||
|
|
||||||
|
if [ "$EXISTING" = "200" ]; then
|
||||||
|
RELEASE_ID=$(curl -s \
|
||||||
|
-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})..."
|
||||||
|
curl -s -X DELETE \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl -s -X DELETE \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/tags/${TAG}" || true
|
||||||
|
|
||||||
|
- name: Create latest release
|
||||||
|
working-directory: build-${{ github.run_id }}
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
REPO_OWNER="${{ github.repository_owner }}"
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
GITEA_URL="${{ github.server_url }}"
|
||||||
|
|
||||||
|
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}')")
|
||||||
|
|
||||||
|
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||||
|
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||||
|
RELEASE_ID=$(echo "$BODY" | jq -r '.id')
|
||||||
|
echo "Created release with ID: ${RELEASE_ID}"
|
||||||
|
else
|
||||||
|
echo "Failed to create release. HTTP ${HTTP_CODE}"
|
||||||
|
echo "$BODY"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload plugin artifact
|
||||||
|
echo "Uploading plugin artifact..."
|
||||||
|
curl -f -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/zip" \
|
||||||
|
--data-binary "@${{ steps.jprm.outputs.artifact }}" \
|
||||||
|
"${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases/${RELEASE_ID}/assets?name=${{ steps.jprm.outputs.artifact_name }}"
|
||||||
|
|
||||||
|
# Upload build.yaml
|
||||||
|
echo "Uploading build.yaml..."
|
||||||
|
curl -f -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/x-yaml" \
|
||||||
|
--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!"
|
||||||
|
|
||||||
|
- name: Update manifest.json
|
||||||
|
working-directory: build-${{ github.run_id }}
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
REPO_OWNER="${{ github.repository_owner }}"
|
||||||
|
REPO_NAME="${{ github.event.repository.name }}"
|
||||||
|
GITEA_URL="${{ github.server_url }}"
|
||||||
|
CHECKSUM="${{ steps.checksum.outputs.checksum }}"
|
||||||
|
ARTIFACT_NAME="${{ steps.jprm.outputs.artifact_name }}"
|
||||||
|
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)
|
||||||
|
|
||||||
|
git config user.name "Gitea Actions"
|
||||||
|
git config user.email "actions@gitea.tourolle.paris"
|
||||||
|
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",
|
||||||
|
"targetAbi": "10.9.0.0",
|
||||||
|
"sourceUrl": "${DOWNLOAD_URL}",
|
||||||
|
"checksum": "${CHECKSUM}",
|
||||||
|
"timestamp": "${TIMESTAMP}"
|
||||||
|
}
|
||||||
|
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"
|
||||||
|
git push origin master
|
||||||
|
|
||||||
|
- name: Cleanup
|
||||||
|
if: always()
|
||||||
|
run: rm -rf build-${{ github.run_id }}
|
||||||
@@ -160,7 +160,7 @@ public class RecordingService : IRecordingService, IDisposable
|
|||||||
|
|
||||||
// Filter to only future/current livestreams that aren't blocked
|
// Filter to only future/current livestreams that aren't blocked
|
||||||
return livestreams
|
return livestreams
|
||||||
.Where(ls => ls.Blocked != true && (ls.ValidTo == null || ls.ValidTo > DateTime.UtcNow))
|
.Where(ls => ls.Blocked != true && (ls.ValidTo == null || ls.ValidTo.Value.ToUniversalTime() > DateTime.UtcNow))
|
||||||
.OrderBy(ls => ls.ValidFrom)
|
.OrderBy(ls => ls.ValidFrom)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
@@ -308,7 +308,7 @@ public class RecordingService : IRecordingService, IDisposable
|
|||||||
public async Task ProcessRecordingsAsync(CancellationToken cancellationToken)
|
public async Task ProcessRecordingsAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Prevent overlapping scheduler runs from spawning duplicate ffmpeg processes
|
// Prevent overlapping scheduler runs from spawning duplicate ffmpeg processes
|
||||||
if (!await _processLock.WaitAsync(0).ConfigureAwait(false))
|
if (!await _processLock.WaitAsync(0, CancellationToken.None).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("ProcessRecordingsAsync already running, skipping");
|
_logger.LogDebug("ProcessRecordingsAsync already running, skipping");
|
||||||
return;
|
return;
|
||||||
@@ -333,13 +333,23 @@ public class RecordingService : IRecordingService, IDisposable
|
|||||||
|
|
||||||
foreach (var entry in _recordings.ToList())
|
foreach (var entry in _recordings.ToList())
|
||||||
{
|
{
|
||||||
|
// Normalize ValidFrom/ValidTo to UTC for correct comparison
|
||||||
|
var validFromUtc = entry.ValidFrom.HasValue ? entry.ValidFrom.Value.ToUniversalTime() : (DateTime?)null;
|
||||||
|
var validToUtc = entry.ValidTo.HasValue ? entry.ValidTo.Value.ToUniversalTime() : (DateTime?)null;
|
||||||
|
|
||||||
switch (entry.State)
|
switch (entry.State)
|
||||||
{
|
{
|
||||||
case RecordingState.Scheduled:
|
case RecordingState.Scheduled:
|
||||||
case RecordingState.WaitingForStream:
|
case RecordingState.WaitingForStream:
|
||||||
// Check if it's time to start recording
|
// Check if it's time to start recording
|
||||||
if (entry.ValidFrom.HasValue && entry.ValidFrom.Value <= now.AddMinutes(2))
|
if (validFromUtc.HasValue && validFromUtc.Value <= now.AddMinutes(2))
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Time to start recording '{Title}': ValidFrom={ValidFrom} (UTC: {ValidFromUtc}), Now={Now}",
|
||||||
|
entry.Title,
|
||||||
|
entry.ValidFrom,
|
||||||
|
validFromUtc,
|
||||||
|
now);
|
||||||
changed |= await TryStartRecordingAsync(entry, cancellationToken).ConfigureAwait(false);
|
changed |= await TryStartRecordingAsync(entry, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +357,7 @@ public class RecordingService : IRecordingService, IDisposable
|
|||||||
|
|
||||||
case RecordingState.Recording:
|
case RecordingState.Recording:
|
||||||
// Check if recording should stop (ValidTo reached or process died)
|
// Check if recording should stop (ValidTo reached or process died)
|
||||||
if (entry.ValidTo.HasValue && entry.ValidTo.Value <= now)
|
if (validToUtc.HasValue && validToUtc.Value <= now)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Recording '{Title}' reached ValidTo, stopping", entry.Title);
|
_logger.LogInformation("Recording '{Title}' reached ValidTo, stopping", entry.Title);
|
||||||
StopFfmpeg(entry.Id);
|
StopFfmpeg(entry.Id);
|
||||||
|
|||||||
@@ -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": "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",
|
"version": "1.0.25",
|
||||||
"changelog": "Release 1.0.25",
|
"changelog": "Release 1.0.25",
|
||||||
|
|||||||
Reference in New Issue
Block a user