diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 39f98e063e..b0684c0d4c 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -259,7 +259,7 @@ jobs:
publishFeedCredentials: 'NugetOrg'
allowPackageConflicts: true # This ignores an error if the version already exists
- - task: NuGetAuthenticate@0
+ - task: NuGetAuthenticate@1
displayName: 'Authenticate to unstable Nuget feed'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index c03564f970..81fe5add42 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
- "version": "8.0.0",
+ "version": "8.0.1",
"commands": [
"dotnet-ef"
]
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000..063901c800
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,28 @@
+{
+ "name": "Development Jellyfin Server",
+ "image":"mcr.microsoft.com/devcontainers/dotnet:8.0-jammy",
+ // restores nuget packages, installs the dotnet workloads and installs the dev https certificate
+ "postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust",
+ // reads the extensions list and installs them
+ "postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
+ "features": {
+ "ghcr.io/devcontainers/features/dotnet:2": {
+ "version": "none",
+ "dotnetRuntimeVersions": "8.0",
+ "aspNetCoreRuntimeVersions": "8.0"
+ },
+ "ghcr.io/devcontainers-contrib/features/apt-packages:1": {
+ "preserve_apt_list": false,
+ "packages": ["libfontconfig1"]
+ },
+ "ghcr.io/devcontainers/features/docker-in-docker:2": {
+ "dockerDashComposeVersion": "v2"
+ },
+ "ghcr.io/devcontainers/features/github-cli:1": {},
+ "ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {}
+ },
+ "hostRequirements": {
+ "memory": "8gb",
+ "cpus": 4
+ }
+}
diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml
index b525966453..d8c550e704 100644
--- a/.github/workflows/ci-codeql-analysis.yml
+++ b/.github/workflows/ci-codeql-analysis.yml
@@ -27,11 +27,11 @@ jobs:
dotnet-version: '8.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
+ uses: github/codeql-action/init@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
+ uses: github/codeql-action/autobuild@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
+ uses: github/codeql-action/analyze@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1
diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml
index 7d6667794e..e43160562f 100644
--- a/.github/workflows/ci-openapi.yml
+++ b/.github/workflows/ci-openapi.yml
@@ -25,7 +25,7 @@ jobs:
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
+ uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
with:
name: openapi-head
retention-days: 14
@@ -59,7 +59,7 @@ jobs:
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
+ uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
with:
name: openapi-base
retention-days: 14
@@ -78,12 +78,12 @@ jobs:
- openapi-base
steps:
- name: Download openapi-head
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
+ uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
with:
name: openapi-head
path: openapi-head
- name: Download openapi-base
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
+ uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
with:
name: openapi-base
path: openapi-base
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 5a0125f5fa..0dacbc5c61 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -19,9 +19,9 @@ jobs:
runs-on: "${{ matrix.os }}"
steps:
- - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
+ - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- - uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4
+ - uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with:
dotnet-version: ${{ env.SDK_VERSION }}
@@ -34,7 +34,7 @@ jobs:
--verbosity minimal
- name: Merge code coverage results
- uses: danielpalme/ReportGenerator-GitHub-Action@4d510cbed8a05af5aefea46c7fd6e05b95844c89 # 5
+ uses: danielpalme/ReportGenerator-GitHub-Action@4d510cbed8a05af5aefea46c7fd6e05b95844c89 # 5.2.0
with:
reports: "**/coverage.cobertura.xml"
targetdir: "merged/"
@@ -42,9 +42,3 @@ jobs:
# TODO - which action / tool to use to publish code coverage results?
# - name: Publish code coverage results
-
- - name: Publish OpenAPI Artifact
- uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3
- with:
- name: "OpenAPI Spec"
- path: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net*/openapi.json"
diff --git a/.github/workflows/issue-stale.yml b/.github/workflows/issue-stale.yml
index 926a7fbfb0..5a1ca9f7a2 100644
--- a/.github/workflows/issue-stale.yml
+++ b/.github/workflows/issue-stale.yml
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }}
steps:
- - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
+ - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
ascending: true
diff --git a/.github/workflows/project-automation.yml b/.github/workflows/project-automation.yml
index 3637eb16ad..d62f655b30 100644
--- a/.github/workflows/project-automation.yml
+++ b/.github/workflows/project-automation.yml
@@ -15,7 +15,7 @@ jobs:
if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps:
- name: Remove from 'Current Release' project
- uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
+ uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
continue-on-error: true
with:
@@ -24,7 +24,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Release Next' project
- uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
+ uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
continue-on-error: true
with:
@@ -33,7 +33,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Current Release' project
- uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
+ uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
continue-on-error: true
with:
@@ -47,7 +47,7 @@ jobs:
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
- name: Move issue to needs triage
- uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
+ uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
continue-on-error: true
with:
@@ -56,7 +56,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add issue to triage project
- uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
+ uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: github.event.issue.pull_request == '' && github.event.action == 'opened'
continue-on-error: true
with:
diff --git a/.github/workflows/pull-request-stale.yaml b/.github/workflows/pull-request-stale.yaml
index de093a9887..d01b3f4a1f 100644
--- a/.github/workflows/pull-request-stale.yaml
+++ b/.github/workflows/pull-request-stale.yaml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }}
steps:
- - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
+ - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
ascending: true
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 59d9452fed..d738e9fba4 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,13 +1,11 @@
{
- // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
- // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
-
- // List of extensions which should be recommended for users of this workspace.
"recommendations": [
"ms-dotnettools.csharp",
- "editorconfig.editorconfig"
+ "editorconfig.editorconfig",
+ "GitHub.vscode-github-actions",
+ "ms-dotnettools.vscode-dotnet-runtime",
+ "ms-dotnettools.csdevkit"
],
- // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index d208879d17..457f59e0f6 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -81,6 +81,7 @@
- [Maxr1998](https://github.com/Maxr1998)
- [mcarlton00](https://github.com/mcarlton00)
- [mitchfizz05](https://github.com/mitchfizz05)
+ - [mohd-akram](https://github.com/mohd-akram)
- [MrTimscampi](https://github.com/MrTimscampi)
- [n8225](https://github.com/n8225)
- [Nalsai](https://github.com/Nalsai)
@@ -171,9 +172,10 @@
- [tallbl0nde](https://github.com/tallbl0nde)
- [sleepycatcoding](https://github.com/sleepycatcoding)
- [scampower3](https://github.com/scampower3)
- - [Chris-Codes-It] (https://github.com/Chris-Codes-It)
+ - [Chris-Codes-It](https://github.com/Chris-Codes-It)
- [Pithaya](https://github.com/Pithaya)
- [Çağrı Sakaoğlu](https://github.com/ilovepilav)
+ - [Gauvino](https://github.com/Gauvino)
# Emby Contributors
diff --git a/Directory.Packages.props b/Directory.Packages.props
index ff76252f85..dcf1834949 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -12,52 +12,52 @@
-
+
-
+
-
+
-
-
+
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
@@ -66,25 +66,25 @@
-
-
-
+
+
+
-
-
+
+
-
+
-
+
-
+
diff --git a/Dockerfile b/Dockerfile
index d3f10cd12e..550c3203d7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,72 +8,68 @@ FROM node:20-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
+ && apk del curl \
&& cd jellyfin-web-* \
&& npm ci --no-audit --unsafe-perm \
&& npm run build:production \
&& mv dist /dist
-FROM debian:stable-slim as app
+FROM debian:bookworm-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
# http://stackoverflow.com/questions/48162574/ddg#49462622
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
+ENV NVIDIA_VISIBLE_DEVICES="all"
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
-# https://github.com/intel/compute-runtime/releases
-ARG GMMLIB_VERSION=22.0.2
-ARG IGC_VERSION=1.0.10395
-ARG NEO_VERSION=22.08.22549
-ARG LEVEL_ZERO_VERSION=1.3.22549
+ENV JELLYFIN_DATA_DIR=/config
+ENV JELLYFIN_CACHE_DIR=/cache
+
+# https://github.com/intel/compute-runtime/releases
+ARG GMMLIB_VERSION=22.3.11.ci17757293
+ARG IGC_VERSION=1.0.15136.22
+ARG NEO_VERSION=23.39.27427.23
+ARG LEVEL_ZERO_VERSION=1.3.27427.23
-# Install dependencies:
-# mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding.
-# curl: healthcheck
RUN apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget curl \
- && wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
+ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \
+ && curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \
&& echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
&& apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y \
- mesa-va-drivers \
- jellyfin-ffmpeg5 \
- openssl \
- locales \
+ && apt-get install --no-install-recommends --no-install-suggests -y mesa-va-drivers jellyfin-ffmpeg6 openssl locales \
# Intel VAAPI Tone mapping dependencies:
# Prefer NEO to Beignet since the latter one doesn't support Comet Lake or newer for now.
# Do not use the intel-opencl-icd package from repo since they will not build with RELEASE_WITH_REGKEYS enabled.
&& mkdir intel-compute-runtime \
&& cd intel-compute-runtime \
- && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-gmmlib_${GMMLIB_VERSION}_amd64.deb \
- && wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \
- && wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \
- && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \
- && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \
+ && curl -LO https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \
+ -LO https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \
+ -LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \
+ -LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \
+ -LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/libigdgmm12_${GMMLIB_VERSION}_amd64.deb \
&& dpkg -i *.deb \
&& cd .. \
&& rm -rf intel-compute-runtime \
- && apt-get remove gnupg wget -y \
+ && apt-get remove gnupg -y \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
- && mkdir -p /cache /config /media \
- && chmod 777 /cache /config /media \
+ && mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
+ && chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
-# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
-ENV LC_ALL en_US.UTF-8
-ENV LANG en_US.UTF-8
-ENV LANGUAGE en_US:en
+ENV LC_ALL=en_US.UTF-8
+ENV LANG=en_US.UTF-8
+ENV LANGUAGE=en_US:en
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# because of changes in docker and systemd we need to not build in parallel at the moment
-# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
+
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
FROM app
@@ -83,11 +79,9 @@ COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096
-VOLUME /cache /config
-ENTRYPOINT ["./jellyfin/jellyfin", \
- "--datadir", "/config", \
- "--cachedir", "/cache", \
- "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
+VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR}
+ENTRYPOINT [ "./jellyfin/jellyfin", \
+ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ]
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
- CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
+ CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
diff --git a/Dockerfile.arm b/Dockerfile.arm
index db1acc838a..07039e43b5 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -4,64 +4,58 @@
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=8.0
-
FROM node:20-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
+ && apk del curl \
&& cd jellyfin-web-* \
&& npm ci --no-audit --unsafe-perm \
&& npm run build:production \
&& mv dist /dist
FROM multiarch/qemu-user-static:x86_64-arm as qemu
-FROM arm32v7/debian:stable-slim as app
+FROM arm32v7/debian:bookworm-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
# http://stackoverflow.com/questions/48162574/ddg#49462622
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
+ENV NVIDIA_VISIBLE_DEVICES="all"
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
+ENV JELLYFIN_DATA_DIR=/config
+ENV JELLYFIN_CACHE_DIR=/cache
+
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
-# curl: setup & healthcheck
RUN apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \
- curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \
- curl -ks https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \
- echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \
- echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \
- apt-get update && \
- apt-get install --no-install-recommends --no-install-suggests -y \
- jellyfin-ffmpeg \
- libssl-dev \
- libfontconfig1 \
- libfreetype6 \
- vainfo \
- libva2 \
- locales \
+ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \
+ && curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \
+ && curl -fsSL https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | gpg --dearmor -o /etc/apt/trusted.gpg.d/ubuntu-jellyfin.gpg \
+ && echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
+ && apt-get update \
+ && apt-get install --no-install-recommends --no-install-suggests -y \
+ jellyfin-ffmpeg6 libssl-dev libfontconfig1 \
+ libfreetype6 vainfo libva2 locales \
&& apt-get remove gnupg -y \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
- && mkdir -p /cache /config /media \
- && chmod 777 /cache /config /media \
+ && mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
+ && chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
-# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
-ENV LC_ALL en_US.UTF-8
-ENV LANG en_US.UTF-8
-ENV LANGUAGE en_US:en
+ENV LC_ALL=en_US.UTF-8
+ENV LANG=en_US.UTF-8
+ENV LANGUAGE=en_US:en
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# Discard objs - may cause failures if exists
-RUN find . -type d -name obj | xargs -r rm -r
-# Build
+
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none
FROM app
@@ -72,11 +66,9 @@ COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096
-VOLUME /cache /config
-ENTRYPOINT ["./jellyfin/jellyfin", \
- "--datadir", "/config", \
- "--cachedir", "/cache", \
- "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
+VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR}
+ENTRYPOINT [ "/jellyfin/jellyfin", \
+ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ]
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
- CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
+ CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
index 3eb5f45fc4..54023794fc 100644
--- a/Dockerfile.arm64
+++ b/Dockerfile.arm64
@@ -4,58 +4,58 @@
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=8.0
-
FROM node:20-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
+ && apk del curl \
&& cd jellyfin-web-* \
&& npm ci --no-audit --unsafe-perm \
&& npm run build:production \
&& mv dist /dist
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
-FROM arm64v8/debian:stable-slim as app
+FROM arm64v8/debian:bookworm-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
# http://stackoverflow.com/questions/48162574/ddg#49462622
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
+ENV NVIDIA_VISIBLE_DEVICES="all"
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
+ENV JELLYFIN_DATA_DIR=/config
+ENV JELLYFIN_CACHE_DIR=/cache
+
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
-# curl: healcheck
-RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
- ffmpeg \
- libssl-dev \
- ca-certificates \
- libfontconfig1 \
- libfreetype6 \
- libomxil-bellagio0 \
- libomxil-bellagio-bin \
- locales \
- curl \
+RUN apt-get update \
+ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \
+ && curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \
+ && curl -fsSL https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | gpg --dearmor -o /etc/apt/trusted.gpg.d/ubuntu-jellyfin.gpg \
+ && echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
+ && apt-get update \
+ && apt-get install --no-install-recommends --no-install-suggests -y \
+ jellyfin-ffmpeg6 locales libssl-dev libfontconfig1 \
+ libfreetype6 libomxil-bellagio0 libomxil-bellagio-bin \
+ && apt-get remove gnupg -y \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
- && mkdir -p /cache /config /media \
- && chmod 777 /cache /config /media \
+ && mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
+ && chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
-# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
-ENV LC_ALL en_US.UTF-8
-ENV LANG en_US.UTF-8
-ENV LANGUAGE en_US:en
+ENV LC_ALL=en_US.UTF-8
+ENV LANG=en_US.UTF-8
+ENV LANGUAGE=en_US:en
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# Discard objs - may cause failures if exists
-RUN find . -type d -name obj | xargs -r rm -r
-# Build
+
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none
FROM app
@@ -66,11 +66,9 @@ COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096
-VOLUME /cache /config
-ENTRYPOINT ["./jellyfin/jellyfin", \
- "--datadir", "/config", \
- "--cachedir", "/cache", \
- "--ffmpeg", "/usr/bin/ffmpeg"]
+VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR}
+ENTRYPOINT [ "/jellyfin/jellyfin", \
+ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ]
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
- CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
+ CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index dce56e0a4f..5870fed761 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -15,7 +15,6 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Emby.Naming.Common;
using Emby.Photos;
-using Emby.Server.Implementations.Channels;
using Emby.Server.Implementations.Collections;
using Emby.Server.Implementations.Configuration;
using Emby.Server.Implementations.Cryptography;
@@ -25,7 +24,6 @@ using Emby.Server.Implementations.Dto;
using Emby.Server.Implementations.HttpServer.Security;
using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.Library;
-using Emby.Server.Implementations.LiveTv;
using Emby.Server.Implementations.Localization;
using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.Plugins;
@@ -76,6 +74,7 @@ using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo;
using MediaBrowser.MediaEncoding.Subtitles;
+using MediaBrowser.MediaEncoding.Transcoding;
using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
@@ -503,8 +502,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(_xmlSerializer);
- serviceCollection.AddSingleton();
-
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
@@ -556,8 +553,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService));
serviceCollection.AddSingleton();
- serviceCollection.AddSingleton();
-
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
@@ -566,9 +561,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton();
- serviceCollection.AddSingleton();
- serviceCollection.AddSingleton();
-
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
@@ -583,7 +575,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton();
- serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
serviceCollection.AddScoped();
serviceCollection.AddScoped();
serviceCollection.AddScoped();
@@ -703,7 +695,7 @@ namespace Emby.Server.Implementations
GetExports(),
GetExports());
- Resolve().AddParts(GetExports(), GetExports(), GetExports());
+ Resolve().AddParts(GetExports(), GetExports());
Resolve().AddParts(GetExports());
}
diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs
index 01b5fdaeea..25ef57d271 100644
--- a/Emby.Server.Implementations/Data/SqliteExtensions.cs
+++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs
@@ -104,6 +104,13 @@ namespace Emby.Server.Implementations.Data
if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal, out var dateTimeResult))
{
+ // If the resulting DateTimeKind is Unspecified it is actually Utc.
+ // This is required downstream for the Json serializer.
+ if (dateTimeResult.Kind == DateTimeKind.Unspecified)
+ {
+ dateTimeResult = DateTime.SpecifyKind(dateTimeResult, DateTimeKind.Utc);
+ }
+
result = dateTimeResult;
return true;
}
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index d0772654ce..a6336f1451 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -699,7 +699,7 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBindNull("@EndDate");
}
- saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(default) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture));
+ saveItemStatement.TryBind("@ChannelId", item.ChannelId.IsEmpty() ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture));
if (item is IHasProgramAttributes hasProgramAttributes)
{
@@ -729,7 +729,7 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBind("@ProductionYear", item.ProductionYear);
var parentId = item.ParentId;
- if (parentId.Equals(default))
+ if (parentId.IsEmpty())
{
saveItemStatement.TryBindNull("@ParentId");
}
@@ -925,7 +925,7 @@ namespace Emby.Server.Implementations.Data
{
saveItemStatement.TryBind("@SeasonName", episode.SeasonName);
- var nullableSeasonId = episode.SeasonId.Equals(default) ? (Guid?)null : episode.SeasonId;
+ var nullableSeasonId = episode.SeasonId.IsEmpty() ? (Guid?)null : episode.SeasonId;
saveItemStatement.TryBind("@SeasonId", nullableSeasonId);
}
@@ -937,7 +937,7 @@ namespace Emby.Server.Implementations.Data
if (item is IHasSeries hasSeries)
{
- var nullableSeriesId = hasSeries.SeriesId.Equals(default) ? (Guid?)null : hasSeries.SeriesId;
+ var nullableSeriesId = hasSeries.SeriesId.IsEmpty() ? (Guid?)null : hasSeries.SeriesId;
saveItemStatement.TryBind("@SeriesId", nullableSeriesId);
saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey);
@@ -1010,7 +1010,7 @@ namespace Emby.Server.Implementations.Data
}
Guid ownerId = item.OwnerId;
- if (ownerId.Equals(default))
+ if (ownerId.IsEmpty())
{
saveItemStatement.TryBindNull("@OwnerId");
}
@@ -1266,7 +1266,7 @@ namespace Emby.Server.Implementations.Data
/// is .
public BaseItem RetrieveItem(Guid id)
{
- if (id.Equals(default))
+ if (id.IsEmpty())
{
throw new ArgumentException("Guid can't be empty", nameof(id));
}
@@ -1970,7 +1970,7 @@ namespace Emby.Server.Implementations.Data
{
CheckDisposed();
- if (id.Equals(default))
+ if (id.IsEmpty())
{
throw new ArgumentNullException(nameof(id));
}
@@ -3230,7 +3230,7 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add($"ChannelId in ({inClause})");
}
- if (!query.ParentId.Equals(default))
+ if (!query.ParentId.IsEmpty())
{
whereClauses.Add("ParentId=@ParentId");
statement?.TryBind("@ParentId", query.ParentId);
@@ -4452,7 +4452,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
public void DeleteItem(Guid id)
{
- if (id.Equals(default))
+ if (id.IsEmpty())
{
throw new ArgumentNullException(nameof(id));
}
@@ -4583,13 +4583,13 @@ AND Type = @InternalPersonType)");
statement?.TryBind("@UserId", query.User.InternalId);
}
- if (!query.ItemId.Equals(default))
+ if (!query.ItemId.IsEmpty())
{
whereClauses.Add("ItemId=@ItemId");
statement?.TryBind("@ItemId", query.ItemId);
}
- if (!query.AppearsInItemId.Equals(default))
+ if (!query.AppearsInItemId.IsEmpty())
{
whereClauses.Add("p.Name in (Select Name from People where ItemId=@AppearsInItemId)");
statement?.TryBind("@AppearsInItemId", query.AppearsInItemId);
@@ -4640,7 +4640,7 @@ AND Type = @InternalPersonType)");
private void UpdateAncestors(Guid itemId, List ancestorIds, SqliteConnection db, SqliteCommand deleteAncestorsStatement)
{
- if (itemId.Equals(default))
+ if (itemId.IsEmpty())
{
throw new ArgumentNullException(nameof(itemId));
}
@@ -5156,7 +5156,7 @@ AND Type = @InternalPersonType)");
private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, SqliteConnection db)
{
- if (itemId.Equals(default))
+ if (itemId.IsEmpty())
{
throw new ArgumentNullException(nameof(itemId));
}
@@ -5228,7 +5228,7 @@ AND Type = @InternalPersonType)");
public void UpdatePeople(Guid itemId, List people)
{
- if (itemId.Equals(default))
+ if (itemId.IsEmpty())
{
throw new ArgumentNullException(nameof(itemId));
}
@@ -5378,7 +5378,7 @@ AND Type = @InternalPersonType)");
{
CheckDisposed();
- if (id.Equals(default))
+ if (id.IsEmpty())
{
throw new ArgumentNullException(nameof(id));
}
@@ -5758,7 +5758,7 @@ AND Type = @InternalPersonType)");
CancellationToken cancellationToken)
{
CheckDisposed();
- if (id.Equals(default))
+ if (id.IsEmpty())
{
throw new ArgumentException("Guid can't be empty.", nameof(id));
}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 44b97e8b83..d0d5bb81c1 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -418,15 +418,6 @@ namespace Emby.Server.Implementations.Dto
{
dto.PlayAccess = item.GetPlayAccess(user);
}
-
- if (options.ContainsField(ItemFields.BasicSyncInfo))
- {
- var userCanSync = user is not null && user.HasPermission(PermissionKind.EnableContentDownloading);
- if (userCanSync && item.SupportsExternalTransfer)
- {
- dto.SupportsSync = true;
- }
- }
}
private static int GetChildCount(Folder folder, User user)
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index b3344bb9ff..34276355a7 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -22,7 +22,6 @@
-
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index a83d7a4105..83e7b230df 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Events;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -241,7 +242,7 @@ public sealed class LibraryChangedNotifier : IServerEntryPoint
{
var userIds = _sessionManager.Sessions
.Select(i => i.UserId)
- .Where(i => !i.Equals(default))
+ .Where(i => !i.IsEmpty())
.Distinct()
.ToArray();
diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
index 6e8f77977e..34c722e41d 100644
--- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
+++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
@@ -32,26 +32,26 @@ namespace Emby.Server.Implementations.Images
switch (viewType)
{
- case CollectionType.Movies:
+ case CollectionType.movies:
includeItemTypes = new[] { BaseItemKind.Movie };
break;
- case CollectionType.TvShows:
+ case CollectionType.tvshows:
includeItemTypes = new[] { BaseItemKind.Series };
break;
- case CollectionType.Music:
+ case CollectionType.music:
includeItemTypes = new[] { BaseItemKind.MusicAlbum };
break;
- case CollectionType.MusicVideos:
+ case CollectionType.musicvideos:
includeItemTypes = new[] { BaseItemKind.MusicVideo };
break;
- case CollectionType.Books:
+ case CollectionType.books:
includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook };
break;
- case CollectionType.BoxSets:
+ case CollectionType.boxsets:
includeItemTypes = new[] { BaseItemKind.BoxSet };
break;
- case CollectionType.HomeVideos:
- case CollectionType.Photos:
+ case CollectionType.homevideos:
+ case CollectionType.photos:
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo };
break;
default:
@@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Images
break;
}
- var recursive = viewType != CollectionType.Playlists;
+ var recursive = viewType != CollectionType.playlists;
return view.GetItemList(new InternalItemsQuery
{
diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs
index 5de53df739..6b2ae23b3c 100644
--- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs
@@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Images
var view = (UserView)item;
var isUsingCollectionStrip = IsUsingCollectionStrip(view);
- var recursive = isUsingCollectionStrip && view?.ViewType is not null && view.ViewType != CollectionType.BoxSets && view.ViewType != CollectionType.Playlists;
+ var recursive = isUsingCollectionStrip && view?.ViewType is not null && view.ViewType != CollectionType.boxsets && view.ViewType != CollectionType.playlists;
var result = view.GetItemList(new InternalItemsQuery
{
@@ -114,9 +114,9 @@ namespace Emby.Server.Implementations.Images
{
CollectionType[] collectionStripViewTypes =
{
- CollectionType.Movies,
- CollectionType.TvShows,
- CollectionType.Playlists
+ CollectionType.movies,
+ CollectionType.tvshows,
+ CollectionType.playlists
};
return view?.ViewType is not null && collectionStripViewTypes.Contains(view.ViewType.Value);
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index f40177fa77..8ae913dad8 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -732,7 +732,7 @@ namespace Emby.Server.Implementations.Library
Path = path
};
- if (folder.Id.Equals(default))
+ if (folder.Id.IsEmpty())
{
if (string.IsNullOrEmpty(folder.Path))
{
@@ -1219,7 +1219,7 @@ namespace Emby.Server.Implementations.Library
/// is null.
public BaseItem GetItemById(Guid id)
{
- if (id.Equals(default))
+ if (id.IsEmpty())
{
throw new ArgumentException("Guid can't be empty", nameof(id));
}
@@ -1241,7 +1241,7 @@ namespace Emby.Server.Implementations.Library
public List GetItemList(InternalItemsQuery query, bool allowExternalContent)
{
- if (query.Recursive && !query.ParentId.Equals(default))
+ if (query.Recursive && !query.ParentId.IsEmpty())
{
var parent = GetItemById(query.ParentId);
if (parent is not null)
@@ -1272,7 +1272,7 @@ namespace Emby.Server.Implementations.Library
public int GetCount(InternalItemsQuery query)
{
- if (query.Recursive && !query.ParentId.Equals(default))
+ if (query.Recursive && !query.ParentId.IsEmpty())
{
var parent = GetItemById(query.ParentId);
if (parent is not null)
@@ -1430,7 +1430,7 @@ namespace Emby.Server.Implementations.Library
public QueryResult GetItemsResult(InternalItemsQuery query)
{
- if (query.Recursive && !query.ParentId.Equals(default))
+ if (query.Recursive && !query.ParentId.IsEmpty())
{
var parent = GetItemById(query.ParentId);
if (parent is not null)
@@ -1486,7 +1486,7 @@ namespace Emby.Server.Implementations.Library
private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true)
{
if (query.AncestorIds.Length == 0 &&
- query.ParentId.Equals(default) &&
+ query.ParentId.IsEmpty() &&
query.ChannelIds.Count == 0 &&
query.TopParentIds.Length == 0 &&
string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) &&
@@ -1514,13 +1514,13 @@ namespace Emby.Server.Implementations.Library
{
if (item is UserView view)
{
- if (view.ViewType == CollectionType.LiveTv)
+ if (view.ViewType == CollectionType.livetv)
{
return new[] { view.Id };
}
// Translate view into folders
- if (!view.DisplayParentId.Equals(default))
+ if (!view.DisplayParentId.IsEmpty())
{
var displayParent = GetItemById(view.DisplayParentId);
if (displayParent is not null)
@@ -1531,7 +1531,7 @@ namespace Emby.Server.Implementations.Library
return Array.Empty();
}
- if (!view.ParentId.Equals(default))
+ if (!view.ParentId.IsEmpty())
{
var displayParent = GetItemById(view.ParentId);
if (displayParent is not null)
@@ -1543,7 +1543,7 @@ namespace Emby.Server.Implementations.Library
}
// Handle grouping
- if (user is not null && view.ViewType != CollectionType.Unknown && UserView.IsEligibleForGrouping(view.ViewType)
+ if (user is not null && view.ViewType != CollectionType.unknown && UserView.IsEligibleForGrouping(view.ViewType)
&& user.GetPreference(PreferenceKind.GroupedFolders).Length > 0)
{
return GetUserRootFolder()
@@ -2137,7 +2137,7 @@ namespace Emby.Server.Implementations.Library
return null;
}
- while (!item.ParentId.Equals(default))
+ while (!item.ParentId.IsEmpty())
{
var parent = item.GetParent();
if (parent is null || parent is AggregateFolder)
@@ -2215,7 +2215,7 @@ namespace Emby.Server.Implementations.Library
CollectionType? viewType,
string sortName)
{
- var parentIdString = parentId.Equals(default)
+ var parentIdString = parentId.IsEmpty()
? null
: parentId.ToString("N", CultureInfo.InvariantCulture);
var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty);
@@ -2251,7 +2251,7 @@ namespace Emby.Server.Implementations.Library
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
- if (!refresh && !item.DisplayParentId.Equals(default))
+ if (!refresh && !item.DisplayParentId.IsEmpty())
{
var displayParent = GetItemById(item.DisplayParentId);
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
@@ -2315,7 +2315,7 @@ namespace Emby.Server.Implementations.Library
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
- if (!refresh && !item.DisplayParentId.Equals(default))
+ if (!refresh && !item.DisplayParentId.IsEmpty())
{
var displayParent = GetItemById(item.DisplayParentId);
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
@@ -2345,7 +2345,7 @@ namespace Emby.Server.Implementations.Library
{
ArgumentException.ThrowIfNullOrEmpty(name);
- var parentIdString = parentId.Equals(default)
+ var parentIdString = parentId.IsEmpty()
? null
: parentId.ToString("N", CultureInfo.InvariantCulture);
var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty);
@@ -2391,7 +2391,7 @@ namespace Emby.Server.Implementations.Library
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
- if (!refresh && !item.DisplayParentId.Equals(default))
+ if (!refresh && !item.DisplayParentId.IsEmpty())
{
var displayParent = GetItemById(item.DisplayParentId);
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
@@ -2419,7 +2419,7 @@ namespace Emby.Server.Implementations.Library
return GetItemById(parentId.Value);
}
- if (userId.HasValue && !userId.Equals(default))
+ if (!userId.IsNullOrEmpty())
{
return GetUserRootFolder();
}
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 96fad9bca8..c38f1af912 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -11,14 +11,16 @@ using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using EasyCaching.Core.Configurations;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
@@ -37,6 +39,7 @@ namespace Emby.Server.Implementations.Library
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
private const char LiveStreamIdDelimeter = '_';
+ private readonly IServerApplicationHost _appHost;
private readonly IItemRepository _itemRepo;
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
@@ -55,6 +58,7 @@ namespace Emby.Server.Implementations.Library
private IMediaSourceProvider[] _providers;
public MediaSourceManager(
+ IServerApplicationHost appHost,
IItemRepository itemRepo,
IApplicationPaths applicationPaths,
ILocalizationManager localizationManager,
@@ -66,6 +70,7 @@ namespace Emby.Server.Implementations.Library
IMediaEncoder mediaEncoder,
IDirectoryService directoryService)
{
+ _appHost = appHost;
_itemRepo = itemRepo;
_userManager = userManager;
_libraryManager = libraryManager;
@@ -520,10 +525,10 @@ namespace Emby.Server.Implementations.Library
_logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource);
var clone = JsonSerializer.Deserialize(json, _jsonOptions);
- if (!request.UserId.Equals(default))
+ if (!request.UserId.IsEmpty())
{
var user = _userManager.GetUserById(request.UserId);
- var item = request.ItemId.Equals(default)
+ var item = request.ItemId.IsEmpty()
? null
: _libraryManager.GetItemById(request.ItemId);
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);
@@ -799,6 +804,35 @@ namespace Emby.Server.Implementations.Library
return result.Item1;
}
+ public async Task> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken)
+ {
+ var stream = new MediaSourceInfo
+ {
+ EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
+ EncoderProtocol = MediaProtocol.Http,
+ Path = info.Path,
+ Protocol = MediaProtocol.File,
+ Id = info.Id,
+ SupportsDirectPlay = false,
+ SupportsDirectStream = true,
+ SupportsTranscoding = true,
+ IsInfiniteStream = true,
+ RequiresOpening = false,
+ RequiresClosing = false,
+ BufferMs = 0,
+ IgnoreDts = true,
+ IgnoreIndex = true
+ };
+
+ await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
+ .AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
+
+ return new List
+ {
+ stream
+ };
+ }
+
public async Task CloseLiveStream(string id)
{
ArgumentException.ThrowIfNullOrEmpty(id);
diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs
index b2439a87e1..078f4ad219 100644
--- a/Emby.Server.Implementations/Library/MusicManager.cs
+++ b/Emby.Server.Implementations/Library/MusicManager.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Library
{
return Guid.Empty;
}
- }).Where(i => !i.Equals(default)).ToArray();
+ }).Where(i => !i.IsEmpty()).ToArray();
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
index ac423ed091..dbf05c1dbb 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
@@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
List files,
CollectionType? collectionType)
{
- if (collectionType == CollectionType.Books)
+ if (collectionType == CollectionType.books)
{
return ResolveMultipleAudio(parent, files, true);
}
@@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var collectionType = args.GetCollectionType();
- var isBooksCollectionType = collectionType == CollectionType.Books;
+ var isBooksCollectionType = collectionType == CollectionType.books;
if (args.IsDirectory)
{
@@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
MediaBrowser.Controller.Entities.Audio.Audio item = null;
- var isMusicCollectionType = collectionType == CollectionType.Music;
+ var isMusicCollectionType = collectionType == CollectionType.music;
// Use regular audio type for mixed libraries, owned items and music
if (isMixedCollectionType ||
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
index 06e292f4cf..0bfb7fbe6a 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
@@ -55,7 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
protected override MusicAlbum Resolve(ItemResolveArgs args)
{
var collectionType = args.GetCollectionType();
- var isMusicMediaFolder = collectionType == CollectionType.Music;
+ var isMusicMediaFolder = collectionType == CollectionType.music;
// If there's a collection type and it's not music, don't allow it.
if (!isMusicMediaFolder)
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index 7d6f97b121..1bdae7f62b 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var collectionType = args.GetCollectionType();
- var isMusicMediaFolder = collectionType == CollectionType.Music;
+ var isMusicMediaFolder = collectionType == CollectionType.music;
// If there's a collection type and it's not music, it can't be a music artist
if (!isMusicMediaFolder)
diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
index b76bfe4274..464a548ab9 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
@@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
var collectionType = args.GetCollectionType();
// Only process items that are in a collection folder containing books
- if (collectionType != CollectionType.Books)
+ if (collectionType != CollectionType.books)
{
return null;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 50fd8b8779..1a210e3cc8 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -31,11 +31,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private static readonly CollectionType[] _validCollectionTypes = new[]
{
- CollectionType.Movies,
- CollectionType.HomeVideos,
- CollectionType.MusicVideos,
- CollectionType.TvShows,
- CollectionType.Photos
+ CollectionType.movies,
+ CollectionType.homevideos,
+ CollectionType.musicvideos,
+ CollectionType.tvshows,
+ CollectionType.photos
};
///
@@ -100,12 +100,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
Video movie = null;
var files = args.GetActualFileSystemChildren().ToList();
- if (collectionType == CollectionType.MusicVideos)
+ if (collectionType == CollectionType.musicvideos)
{
movie = FindMovie(args, args.Path, args.Parent, files, DirectoryService, collectionType, false);
}
- if (collectionType == CollectionType.HomeVideos)
+ if (collectionType == CollectionType.homevideos)
{
movie = FindMovie
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index aa7be9109e..7d5f22545d 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -5,7 +5,9 @@ using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
using Jellyfin.Api.Middleware;
+using Jellyfin.LiveTv.Extensions;
using Jellyfin.MediaEncoding.Hls.Extensions;
+using Jellyfin.Networking;
using Jellyfin.Networking.HappyEyeballs;
using Jellyfin.Server.Extensions;
using Jellyfin.Server.HealthChecks;
@@ -13,7 +15,6 @@ using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Extensions;
using Jellyfin.Server.Infrastructure;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
using Microsoft.AspNetCore.Builder;
@@ -121,6 +122,9 @@ namespace Jellyfin.Server
.AddCheck>(nameof(JellyfinDbContext));
services.AddHlsPlaylistGenerator();
+ services.AddLiveTvServices();
+
+ services.AddHostedService();
}
///
diff --git a/Jellyfin.sln b/Jellyfin.sln
index 4385ac2417..30eab6cc21 100644
--- a/Jellyfin.sln
+++ b/Jellyfin.sln
@@ -87,6 +87,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Hls.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Keyframes.Tests", "tests\Jellyfin.MediaEncoding.Keyframes.Tests\Jellyfin.MediaEncoding.Keyframes.Tests.csproj", "{24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.LiveTv.Tests", "tests\Jellyfin.LiveTv.Tests\Jellyfin.LiveTv.Tests.csproj", "{C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.LiveTv", "src\Jellyfin.LiveTv\Jellyfin.LiveTv.csproj", "{8C6B2B13-58A4-4506-9DAB-1F882A093FE0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -233,6 +237,14 @@ Global
{24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24960660-DE6C-47BF-AEEF-CEE8F19FE6C2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8C6B2B13-58A4-4506-9DAB-1F882A093FE0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -259,6 +271,8 @@ Global
{08FFF49B-F175-4807-A2B5-73B0EBD9F716} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
{154872D9-6C12-4007-96E3-8F70A58386CE} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
{0A3FCC4D-C714-4072-B90F-E374A15F9FF9} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
+ {C4F71272-C6BE-4C30-BE0D-4E6ED651D6D3} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+ {8C6B2B13-58A4-4506-9DAB-1F882A093FE0} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}
diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs
index 8eb27888ab..c8b432ecb2 100644
--- a/MediaBrowser.Controller/Channels/IChannelManager.cs
+++ b/MediaBrowser.Controller/Channels/IChannelManager.cs
@@ -95,12 +95,5 @@ namespace MediaBrowser.Controller.Channels
/// The cancellation token.
/// The item media sources.
IEnumerable GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken);
-
- ///
- /// Whether the item supports media probe.
- ///
- /// The item.
- /// Whether media probe should be enabled.
- bool EnableMediaProbe(BaseItem item);
}
}
diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs
index 8362db1a71..eb181dcc4c 100644
--- a/MediaBrowser.Controller/Devices/IDeviceManager.cs
+++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs
@@ -59,9 +59,8 @@ namespace MediaBrowser.Controller.Devices
/// Gets the devices.
///
/// The user's id, or null.
- /// A value indicating whether the device supports sync, or null.
/// IEnumerable<DeviceInfo>.
- Task> GetDevicesForUser(Guid? userId, bool? supportsSync);
+ Task> GetDevicesForUser(Guid? userId);
Task DeleteDevice(Device device);
diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs
index d789033f16..b225f22df0 100644
--- a/MediaBrowser.Controller/Entities/AggregateFolder.cs
+++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs
@@ -9,6 +9,7 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -184,7 +185,7 @@ namespace MediaBrowser.Controller.Entities
/// The id is empty.
public BaseItem FindVirtualChild(Guid id)
{
- if (id.Equals(default))
+ if (id.IsEmpty())
{
throw new ArgumentNullException(nameof(id));
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 18d948a62f..11cdf84445 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Entities.Audio
public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess, IHasLookupInfo
{
[JsonIgnore]
- public bool IsAccessedByName => ParentId.Equals(default);
+ public bool IsAccessedByName => ParentId.IsEmpty();
[JsonIgnore]
public override bool IsFolder => !IsAccessedByName;
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 98485f9a80..ddcc994a0b 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -240,7 +240,7 @@ namespace MediaBrowser.Controller.Entities
{
get
{
- if (!ChannelId.Equals(default))
+ if (!ChannelId.IsEmpty())
{
return SourceType.Channel;
}
@@ -530,7 +530,7 @@ namespace MediaBrowser.Controller.Entities
get
{
var id = DisplayParentId;
- if (id.Equals(default))
+ if (id.IsEmpty())
{
return null;
}
@@ -724,7 +724,7 @@ namespace MediaBrowser.Controller.Entities
if (this is IHasCollectionType view)
{
- if (view.CollectionType == CollectionType.LiveTv)
+ if (view.CollectionType == CollectionType.livetv)
{
return true;
}
@@ -746,7 +746,7 @@ namespace MediaBrowser.Controller.Entities
public virtual bool StopRefreshIfLocalMetadataFound => true;
[JsonIgnore]
- protected virtual bool SupportsOwnedItems => !ParentId.Equals(default) && IsFileProtocol;
+ protected virtual bool SupportsOwnedItems => !ParentId.IsEmpty() && IsFileProtocol;
[JsonIgnore]
public virtual bool SupportsPeople => false;
@@ -773,8 +773,6 @@ namespace MediaBrowser.Controller.Entities
/// The remote trailers.
public IReadOnlyList RemoteTrailers { get; set; }
- public virtual bool SupportsExternalTransfer => false;
-
public virtual double GetDefaultPrimaryImageAspectRatio()
{
return 0;
@@ -825,7 +823,7 @@ namespace MediaBrowser.Controller.Entities
public BaseItem GetOwner()
{
var ownerId = OwnerId;
- return ownerId.Equals(default) ? null : LibraryManager.GetItemById(ownerId);
+ return ownerId.IsEmpty() ? null : LibraryManager.GetItemById(ownerId);
}
public bool CanDelete(User user, List allCollectionFolders)
@@ -970,7 +968,7 @@ namespace MediaBrowser.Controller.Entities
public BaseItem GetParent()
{
var parentId = ParentId;
- if (parentId.Equals(default))
+ if (parentId.IsEmpty())
{
return null;
}
@@ -1363,7 +1361,7 @@ namespace MediaBrowser.Controller.Entities
var tasks = extras.Select(i =>
{
var subOptions = new MetadataRefreshOptions(options);
- if (!i.OwnerId.Equals(ownerId) || !i.ParentId.Equals(default))
+ if (!i.OwnerId.Equals(ownerId) || !i.ParentId.IsEmpty())
{
i.OwnerId = ownerId;
i.ParentId = Guid.Empty;
@@ -1675,7 +1673,7 @@ namespace MediaBrowser.Controller.Entities
// First get using the cached Id
if (info.ItemId.HasValue)
{
- if (info.ItemId.Value.Equals(default))
+ if (info.ItemId.Value.IsEmpty())
{
return null;
}
@@ -2441,7 +2439,7 @@ namespace MediaBrowser.Controller.Entities
return Task.FromResult(true);
}
- if (video.OwnerId.Equals(default))
+ if (video.OwnerId.IsEmpty())
{
video.OwnerId = this.Id;
}
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index e707eedbf6..74eb089de3 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -12,6 +12,7 @@ using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Collections;
@@ -198,7 +199,7 @@ namespace MediaBrowser.Controller.Entities
{
item.SetParent(this);
- if (item.Id.Equals(default))
+ if (item.Id.IsEmpty())
{
item.Id = LibraryManager.GetNewItemId(item.Path, item.GetType());
}
@@ -697,7 +698,7 @@ namespace MediaBrowser.Controller.Entities
if (this is not UserRootFolder
&& this is not AggregateFolder
- && query.ParentId.Equals(default))
+ && query.ParentId.IsEmpty())
{
query.Parent = this;
}
@@ -840,7 +841,7 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
+ if (!query.AdjacentTo.IsNullOrEmpty())
{
Logger.LogDebug("Query requires post-filtering due to AdjacentTo");
return true;
@@ -987,7 +988,7 @@ namespace MediaBrowser.Controller.Entities
#pragma warning restore CA1309
// This must be the last filter
- if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
+ if (!query.AdjacentTo.IsNullOrEmpty())
{
items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index bf31508c1d..37e2414142 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -8,6 +8,7 @@ using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
@@ -74,12 +75,12 @@ namespace MediaBrowser.Controller.Entities.TV
get
{
var seriesId = SeriesId;
- if (seriesId.Equals(default))
+ if (seriesId.IsEmpty())
{
seriesId = FindSeriesId();
}
- return seriesId.Equals(default) ? null : (LibraryManager.GetItemById(seriesId) as Series);
+ return seriesId.IsEmpty() ? null : (LibraryManager.GetItemById(seriesId) as Series);
}
}
@@ -89,12 +90,12 @@ namespace MediaBrowser.Controller.Entities.TV
get
{
var seasonId = SeasonId;
- if (seasonId.Equals(default))
+ if (seasonId.IsEmpty())
{
seasonId = FindSeasonId();
}
- return seasonId.Equals(default) ? null : (LibraryManager.GetItemById(seasonId) as Season);
+ return seasonId.IsEmpty() ? null : (LibraryManager.GetItemById(seasonId) as Season);
}
}
@@ -271,7 +272,7 @@ namespace MediaBrowser.Controller.Entities.TV
var seasonId = SeasonId;
- if (!seasonId.Equals(default) && !list.Contains(seasonId))
+ if (!seasonId.IsEmpty() && !list.Contains(seasonId))
{
list.Add(seasonId);
}
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 0a040a3c28..c29cefc15e 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -9,6 +9,7 @@ using System.Linq;
using System.Text.Json.Serialization;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Querying;
@@ -48,12 +49,12 @@ namespace MediaBrowser.Controller.Entities.TV
get
{
var seriesId = SeriesId;
- if (seriesId.Equals(default))
+ if (seriesId.IsEmpty())
{
seriesId = FindSeriesId();
}
- return seriesId.Equals(default) ? null : (LibraryManager.GetItemById(seriesId) as Series);
+ return seriesId.IsEmpty() ? null : (LibraryManager.GetItemById(seriesId) as Series);
}
}
diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs
index 1f94cf767d..c93488a85e 100644
--- a/MediaBrowser.Controller/Entities/UserView.cs
+++ b/MediaBrowser.Controller/Entities/UserView.cs
@@ -19,19 +19,19 @@ namespace MediaBrowser.Controller.Entities
{
private static readonly CollectionType?[] _viewTypesEligibleForGrouping =
{
- Jellyfin.Data.Enums.CollectionType.Movies,
- Jellyfin.Data.Enums.CollectionType.TvShows,
+ Jellyfin.Data.Enums.CollectionType.movies,
+ Jellyfin.Data.Enums.CollectionType.tvshows,
null
};
private static readonly CollectionType?[] _originalFolderViewTypes =
{
- Jellyfin.Data.Enums.CollectionType.Books,
- Jellyfin.Data.Enums.CollectionType.MusicVideos,
- Jellyfin.Data.Enums.CollectionType.HomeVideos,
- Jellyfin.Data.Enums.CollectionType.Photos,
- Jellyfin.Data.Enums.CollectionType.Music,
- Jellyfin.Data.Enums.CollectionType.BoxSets
+ Jellyfin.Data.Enums.CollectionType.books,
+ Jellyfin.Data.Enums.CollectionType.musicvideos,
+ Jellyfin.Data.Enums.CollectionType.homevideos,
+ Jellyfin.Data.Enums.CollectionType.photos,
+ Jellyfin.Data.Enums.CollectionType.music,
+ Jellyfin.Data.Enums.CollectionType.boxsets
};
public static ITVSeriesManager TVSeriesManager { get; set; }
@@ -70,11 +70,11 @@ namespace MediaBrowser.Controller.Entities
///
public override IEnumerable GetIdsForAncestorQuery()
{
- if (!DisplayParentId.Equals(default))
+ if (!DisplayParentId.IsEmpty())
{
yield return DisplayParentId;
}
- else if (!ParentId.Equals(default))
+ else if (!ParentId.IsEmpty())
{
yield return ParentId;
}
@@ -95,11 +95,11 @@ namespace MediaBrowser.Controller.Entities
{
var parent = this as Folder;
- if (!DisplayParentId.Equals(default))
+ if (!DisplayParentId.IsEmpty())
{
parent = LibraryManager.GetItemById(DisplayParentId) as Folder ?? parent;
}
- else if (!ParentId.Equals(default))
+ else if (!ParentId.IsEmpty())
{
parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent;
}
@@ -161,7 +161,7 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- return collectionFolder.CollectionType == Jellyfin.Data.Enums.CollectionType.Playlists;
+ return collectionFolder.CollectionType == Jellyfin.Data.Enums.CollectionType.playlists;
}
public static bool IsEligibleForGrouping(Folder folder)
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index 42431c8326..4af000557e 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -58,58 +58,58 @@ namespace MediaBrowser.Controller.Entities
switch (viewType)
{
- case CollectionType.Folders:
+ case CollectionType.folders:
return GetResult(_libraryManager.GetUserRootFolder().GetChildren(user, true), query);
- case CollectionType.TvShows:
+ case CollectionType.tvshows:
return GetTvView(queryParent, user, query);
- case CollectionType.Movies:
+ case CollectionType.movies:
return GetMovieFolders(queryParent, user, query);
- case CollectionType.TvShowSeries:
+ case CollectionType.tvshowseries:
return GetTvSeries(queryParent, user, query);
- case CollectionType.TvGenres:
+ case CollectionType.tvgenres:
return GetTvGenres(queryParent, user, query);
- case CollectionType.TvGenre:
+ case CollectionType.tvgenre:
return GetTvGenreItems(queryParent, displayParent, user, query);
- case CollectionType.TvResume:
+ case CollectionType.tvresume:
return GetTvResume(queryParent, user, query);
- case CollectionType.TvNextUp:
+ case CollectionType.tvnextup:
return GetTvNextUp(queryParent, query);
- case CollectionType.TvLatest:
+ case CollectionType.tvlatest:
return GetTvLatest(queryParent, user, query);
- case CollectionType.MovieFavorites:
+ case CollectionType.moviefavorites:
return GetFavoriteMovies(queryParent, user, query);
- case CollectionType.MovieLatest:
+ case CollectionType.movielatest:
return GetMovieLatest(queryParent, user, query);
- case CollectionType.MovieGenres:
+ case CollectionType.moviegenres:
return GetMovieGenres(queryParent, user, query);
- case CollectionType.MovieGenre:
+ case CollectionType.moviegenre:
return GetMovieGenreItems(queryParent, displayParent, user, query);
- case CollectionType.MovieResume:
+ case CollectionType.movieresume:
return GetMovieResume(queryParent, user, query);
- case CollectionType.MovieMovies:
+ case CollectionType.moviemovies:
return GetMovieMovies(queryParent, user, query);
- case CollectionType.MovieCollections:
+ case CollectionType.moviecollection:
return GetMovieCollections(user, query);
- case CollectionType.TvFavoriteEpisodes:
+ case CollectionType.tvfavoriteepisodes:
return GetFavoriteEpisodes(queryParent, user, query);
- case CollectionType.TvFavoriteSeries:
+ case CollectionType.tvfavoriteseries:
return GetFavoriteSeries(queryParent, user, query);
default:
@@ -146,12 +146,12 @@ namespace MediaBrowser.Controller.Entities
var list = new List
{
- GetUserView(CollectionType.MovieResume, "HeaderContinueWatching", "0", parent),
- GetUserView(CollectionType.MovieLatest, "Latest", "1", parent),
- GetUserView(CollectionType.MovieMovies, "Movies", "2", parent),
- GetUserView(CollectionType.MovieCollections, "Collections", "3", parent),
- GetUserView(CollectionType.MovieFavorites, "Favorites", "4", parent),
- GetUserView(CollectionType.MovieGenres, "Genres", "5", parent)
+ GetUserView(CollectionType.movieresume, "HeaderContinueWatching", "0", parent),
+ GetUserView(CollectionType.movielatest, "Latest", "1", parent),
+ GetUserView(CollectionType.moviemovies, "Movies", "2", parent),
+ GetUserView(CollectionType.moviecollection, "Collections", "3", parent),
+ GetUserView(CollectionType.moviefavorites, "Favorites", "4", parent),
+ GetUserView(CollectionType.moviegenres, "Genres", "5", parent)
};
return GetResult(list, query);
@@ -264,7 +264,7 @@ namespace MediaBrowser.Controller.Entities
}
})
.Where(i => i is not null)
- .Select(i => GetUserViewWithName(CollectionType.MovieGenre, i.SortName, parent));
+ .Select(i => GetUserViewWithName(CollectionType.moviegenre, i.SortName, parent));
return GetResult(genres, query);
}
@@ -303,13 +303,13 @@ namespace MediaBrowser.Controller.Entities
var list = new List
{
- GetUserView(CollectionType.TvResume, "HeaderContinueWatching", "0", parent),
- GetUserView(CollectionType.TvNextUp, "HeaderNextUp", "1", parent),
- GetUserView(CollectionType.TvLatest, "Latest", "2", parent),
- GetUserView(CollectionType.TvShowSeries, "Shows", "3", parent),
- GetUserView(CollectionType.TvFavoriteSeries, "HeaderFavoriteShows", "4", parent),
- GetUserView(CollectionType.TvFavoriteEpisodes, "HeaderFavoriteEpisodes", "5", parent),
- GetUserView(CollectionType.TvGenres, "Genres", "6", parent)
+ GetUserView(CollectionType.tvresume, "HeaderContinueWatching", "0", parent),
+ GetUserView(CollectionType.tvnextup, "HeaderNextUp", "1", parent),
+ GetUserView(CollectionType.tvlatest, "Latest", "2", parent),
+ GetUserView(CollectionType.tvshowseries, "Shows", "3", parent),
+ GetUserView(CollectionType.tvfavoriteseries, "HeaderFavoriteShows", "4", parent),
+ GetUserView(CollectionType.tvfavoriteepisodes, "HeaderFavoriteEpisodes", "5", parent),
+ GetUserView(CollectionType.tvgenres, "Genres", "6", parent)
};
return GetResult(list, query);
@@ -330,7 +330,7 @@ namespace MediaBrowser.Controller.Entities
private QueryResult GetTvNextUp(Folder parent, InternalItemsQuery query)
{
- var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.TvShows });
+ var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.tvshows });
var result = _tvSeriesManager.GetNextUp(
new NextUpQuery
@@ -392,7 +392,7 @@ namespace MediaBrowser.Controller.Entities
}
})
.Where(i => i is not null)
- .Select(i => GetUserViewWithName(CollectionType.TvGenre, i.SortName, parent));
+ .Select(i => GetUserViewWithName(CollectionType.tvgenre, i.SortName, parent));
return GetResult(genres, query);
}
@@ -433,7 +433,7 @@ namespace MediaBrowser.Controller.Entities
var user = query.User;
// This must be the last filter
- if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
+ if (!query.AdjacentTo.IsNullOrEmpty())
{
items = FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
}
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index be2eb4d288..5adadec390 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -456,7 +456,7 @@ namespace MediaBrowser.Controller.Entities
foreach (var child in LinkedAlternateVersions)
{
// Reset the cached value
- if (child.ItemId.HasValue && child.ItemId.Value.Equals(default))
+ if (child.ItemId.IsNullOrEmpty())
{
child.ItemId = null;
}
diff --git a/MediaBrowser.Controller/Library/ILiveStream.cs b/MediaBrowser.Controller/Library/ILiveStream.cs
index 4c44a17fdd..bf64aca0f0 100644
--- a/MediaBrowser.Controller/Library/ILiveStream.cs
+++ b/MediaBrowser.Controller/Library/ILiveStream.cs
@@ -2,6 +2,7 @@
#pragma warning disable CA1711, CS1591
+using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -9,7 +10,7 @@ using MediaBrowser.Model.Dto;
namespace MediaBrowser.Controller.Library
{
- public interface ILiveStream
+ public interface ILiveStream : IDisposable
{
int ConsumerCount { get; set; }
diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs
index f1758a9d80..bace703ada 100644
--- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs
+++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs
@@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -116,6 +117,14 @@ namespace MediaBrowser.Controller.Library
/// An instance of .
public ILiveStream GetLiveStreamInfoByUniqueId(string uniqueId);
+ ///
+ /// Gets the media sources for an active recording.
+ ///
+ /// The .
+ /// The .
+ /// A task containing the 's for the recording.
+ Task> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken);
+
///
/// Closes the media source.
///
diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs
index 034c405910..43cccfc65c 100644
--- a/MediaBrowser.Controller/Library/IUserDataManager.cs
+++ b/MediaBrowser.Controller/Library/IUserDataManager.cs
@@ -35,6 +35,15 @@ namespace MediaBrowser.Controller.Library
void SaveUserData(User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken);
+ ///
+ /// Save the provided user data for the given user.
+ ///
+ /// The user.
+ /// The item.
+ /// The reason for updating the user data.
+ /// The reason.
+ void SaveUserData(User user, BaseItem item, UpdateUserItemDataDto userDataDto, UserDataSaveReason reason);
+
UserItemData GetUserData(User user, BaseItem item);
UserItemData GetUserData(Guid userId, BaseItem item);
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index 3b6a16dee3..26f9fe42d3 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -36,7 +36,7 @@ namespace MediaBrowser.Controller.LiveTv
/// The services.
IReadOnlyList Services { get; }
- IListingsProvider[] ListingProviders { get; }
+ IReadOnlyList ListingProviders { get; }
///
/// Gets the new timer defaults asynchronous.
@@ -71,9 +71,8 @@ namespace MediaBrowser.Controller.LiveTv
/// Adds the parts.
///
/// The services.
- /// The tuner hosts.
/// The listing providers.
- void AddParts(IEnumerable services, IEnumerable tunerHosts, IEnumerable listingProviders);
+ void AddParts(IEnumerable services, IEnumerable listingProviders);
///
/// Gets the timer.
@@ -253,14 +252,6 @@ namespace MediaBrowser.Controller.LiveTv
/// Task.
Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList fields, User user = null);
- ///
- /// Saves the tuner host.
- ///
- /// Turner host to save.
- /// Option to specify that data source has changed.
- /// Tuner host information wrapped in a task.
- Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
-
///
/// Saves the listing provider.
///
@@ -298,10 +289,6 @@ namespace MediaBrowser.Controller.LiveTv
Task> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
- List GetTunerHostTypes();
-
- Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken);
-
string GetEmbyTvActiveRecordingPath(string id);
ActiveRecordingInfo GetActiveRecordingInfo(string path);
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
index ce34954e3f..52fb156481 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
@@ -140,14 +140,6 @@ namespace MediaBrowser.Controller.LiveTv
/// Task.
Task CloseLiveStream(string id, CancellationToken cancellationToken);
- ///
- /// Records the live stream.
- ///
- /// The identifier.
- /// The cancellation token.
- /// Task.
- Task RecordLiveStream(string id, CancellationToken cancellationToken);
-
///
/// Resets the tuner.
///
@@ -180,9 +172,4 @@ namespace MediaBrowser.Controller.LiveTv
{
Task GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, List currentLiveStreams, CancellationToken cancellationToken);
}
-
- public interface ISupportsUpdatingDefaults
- {
- Task UpdateTimerDefaults(SeriesTimerInfo info, CancellationToken cancellationToken);
- }
}
diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
index 24820abb90..3689a2adf6 100644
--- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs
+++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
@@ -35,13 +35,6 @@ namespace MediaBrowser.Controller.LiveTv
/// Task<IEnumerable<ChannelInfo>>.
Task> GetChannels(bool enableCache, CancellationToken cancellationToken);
- ///
- /// Gets the tuner infos.
- ///
- /// The cancellation token.
- /// Task<List<LiveTvTunerInfo>>.
- Task> GetTunerInfos(CancellationToken cancellationToken);
-
///
/// Gets the channel stream.
///
@@ -50,7 +43,7 @@ namespace MediaBrowser.Controller.LiveTv
/// The current live streams.
/// The cancellation token to cancel operation.
/// Live stream wrapped in a task.
- Task GetChannelStream(string channelId, string streamId, List currentLiveStreams, CancellationToken cancellationToken);
+ Task GetChannelStream(string channelId, string streamId, IList currentLiveStreams, CancellationToken cancellationToken);
///
/// Gets the channel stream media sources.
diff --git a/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs
new file mode 100644
index 0000000000..3df6066f66
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/ITunerHostManager.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.LiveTv;
+
+namespace MediaBrowser.Controller.LiveTv;
+
+///
+/// Service responsible for managing the s.
+///
+public interface ITunerHostManager
+{
+ ///
+ /// Gets the available s.
+ ///
+ IReadOnlyList TunerHosts { get; }
+
+ ///
+ /// Gets the s for the available s.
+ ///
+ /// The s.
+ IEnumerable GetTunerHostTypes();
+
+ ///
+ /// Saves the tuner host.
+ ///
+ /// Turner host to save.
+ /// Option to specify that data source has changed.
+ /// Tuner host information wrapped in a task.
+ Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
+
+ ///
+ /// Discovers the available tuners.
+ ///
+ /// A value indicating whether to only return new devices.
+ /// The s.
+ IAsyncEnumerable DiscoverTuners(bool newDevicesOnly);
+
+ ///
+ /// Scans for tuner devices that have changed URLs.
+ ///
+ /// The to use.
+ /// A task that represents the scanning operation.
+ Task ScanForTunerDeviceChanges(CancellationToken cancellationToken);
+}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs
deleted file mode 100644
index eb3babc180..0000000000
--- a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Model.LiveTv;
-
-namespace MediaBrowser.Controller.LiveTv
-{
- public class LiveTvServiceStatusInfo
- {
- public LiveTvServiceStatusInfo()
- {
- Tuners = new List();
- IsVisible = true;
- }
-
- ///
- /// Gets or sets the status.
- ///
- /// The status.
- public LiveTvServiceStatus Status { get; set; }
-
- ///
- /// Gets or sets the status message.
- ///
- /// The status message.
- public string StatusMessage { get; set; }
-
- ///
- /// Gets or sets the version.
- ///
- /// The version.
- public string Version { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance has update available.
- ///
- /// true if this instance has update available; otherwise, false.
- public bool HasUpdateAvailable { get; set; }
-
- ///
- /// Gets or sets the tuners.
- ///
- /// The tuners.
- public List Tuners { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance is visible.
- ///
- /// true if this instance is visible; otherwise, false.
- public bool IsVisible { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs
deleted file mode 100644
index aa5eb59d16..0000000000
--- a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Model.LiveTv;
-
-namespace MediaBrowser.Controller.LiveTv
-{
- public class LiveTvTunerInfo
- {
- public LiveTvTunerInfo()
- {
- Clients = new List();
- }
-
- ///
- /// Gets or sets the type of the source.
- ///
- /// The type of the source.
- public string SourceType { get; set; }
-
- ///
- /// Gets or sets the name.
- ///
- /// The name.
- public string Name { get; set; }
-
- ///
- /// Gets or sets the identifier.
- ///
- /// The identifier.
- public string Id { get; set; }
-
- ///
- /// Gets or sets the URL.
- ///
- /// The URL.
- public string Url { get; set; }
-
- ///
- /// Gets or sets the status.
- ///
- /// The status.
- public LiveTvTunerStatus Status { get; set; }
-
- ///
- /// Gets or sets the channel identifier.
- ///
- /// The channel identifier.
- public string ChannelId { get; set; }
-
- ///
- /// Gets or sets the recording identifier.
- ///
- /// The recording identifier.
- public string RecordingId { get; set; }
-
- ///
- /// Gets or sets the name of the program.
- ///
- /// The name of the program.
- public string ProgramName { get; set; }
-
- ///
- /// Gets or sets the clients.
- ///
- /// The clients.
- public List Clients { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance can reset.
- ///
- /// true if this instance can reset; otherwise, false.
- public bool CanReset { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
deleted file mode 100644
index 1dcf7a58fe..0000000000
--- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
+++ /dev/null
@@ -1,210 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using MediaBrowser.Model.LiveTv;
-
-namespace MediaBrowser.Controller.LiveTv
-{
- public class RecordingInfo
- {
- public RecordingInfo()
- {
- Genres = new List();
- }
-
- ///
- /// Gets or sets the id of the recording.
- ///
- public string Id { get; set; }
-
- ///
- /// Gets or sets the series timer identifier.
- ///
- /// The series timer identifier.
- public string SeriesTimerId { get; set; }
-
- ///
- /// Gets or sets the timer identifier.
- ///
- /// The timer identifier.
- public string TimerId { get; set; }
-
- ///
- /// Gets or sets the channelId of the recording.
- ///
- public string ChannelId { get; set; }
-
- ///
- /// Gets or sets the type of the channel.
- ///
- /// The type of the channel.
- public ChannelType ChannelType { get; set; }
-
- ///
- /// Gets or sets the name of the recording.
- ///
- public string Name { get; set; }
-
- ///
- /// Gets or sets the path.
- ///
- /// The path.
- public string Path { get; set; }
-
- ///
- /// Gets or sets the URL.
- ///
- /// The URL.
- public string Url { get; set; }
-
- ///
- /// Gets or sets the overview.
- ///
- /// The overview.
- public string Overview { get; set; }
-
- ///
- /// Gets or sets the start date of the recording, in UTC.
- ///
- public DateTime StartDate { get; set; }
-
- ///
- /// Gets or sets the end date of the recording, in UTC.
- ///
- public DateTime EndDate { get; set; }
-
- ///
- /// Gets or sets the program identifier.
- ///
- /// The program identifier.
- public string ProgramId { get; set; }
-
- ///
- /// Gets or sets the status.
- ///
- /// The status.
- public RecordingStatus Status { get; set; }
-
- ///
- /// Gets or sets the genre of the program.
- ///
- public List Genres { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance is repeat.
- ///
- /// true if this instance is repeat; otherwise, false.
- public bool IsRepeat { get; set; }
-
- ///
- /// Gets or sets the episode title.
- ///
- /// The episode title.
- public string EpisodeTitle { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance is hd.
- ///
- /// true if this instance is hd; otherwise, false.
- public bool? IsHD { get; set; }
-
- ///
- /// Gets or sets the audio.
- ///
- /// The audio.
- public ProgramAudio? Audio { get; set; }
-
- ///
- /// Gets or sets the original air date.
- ///
- /// The original air date.
- public DateTime? OriginalAirDate { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance is movie.
- ///
- /// true if this instance is movie; otherwise, false.
- public bool IsMovie { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance is sports.
- ///
- /// true if this instance is sports; otherwise, false.
- public bool IsSports { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance is series.
- ///
- /// true if this instance is series; otherwise, false.
- public bool IsSeries { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance is live.
- ///
- /// true if this instance is live; otherwise, false.
- public bool IsLive { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance is news.
- ///
- /// true if this instance is news; otherwise, false.
- public bool IsNews { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance is kids.
- ///
- /// true if this instance is kids; otherwise, false.
- public bool IsKids { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance is premiere.
- ///
- /// true if this instance is premiere; otherwise, false.
- public bool IsPremiere { get; set; }
-
- ///
- /// Gets or sets the official rating.
- ///
- /// The official rating.
- public string OfficialRating { get; set; }
-
- ///
- /// Gets or sets the community rating.
- ///
- /// The community rating.
- public float? CommunityRating { get; set; }
-
- ///
- /// Gets or sets the image path if it can be accessed directly from the file system.
- ///
- /// The image path.
- public string ImagePath { get; set; }
-
- ///
- /// Gets or sets the image url if it can be downloaded.
- ///
- /// The image URL.
- public string ImageUrl { get; set; }
-
- ///
- /// Gets or sets a value indicating whether this instance has image.
- ///
- /// null if [has image] contains no value, true if [has image]; otherwise, false.
- public bool? HasImage { get; set; }
-
- ///
- /// Gets or sets the show identifier.
- ///
- /// The show identifier.
- public string ShowId { get; set; }
-
- ///
- /// Gets or sets the date last updated.
- ///
- /// The date last updated.
- public DateTime DateLastUpdated { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs b/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs
deleted file mode 100644
index 0b943c9396..0000000000
--- a/MediaBrowser.Controller/LiveTv/RecordingStatusChangedEventArgs.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using MediaBrowser.Model.LiveTv;
-
-namespace MediaBrowser.Controller.LiveTv
-{
- public class RecordingStatusChangedEventArgs : EventArgs
- {
- public string RecordingId { get; set; }
-
- public RecordingStatus NewStatus { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 46fd1ae478..400e7f40fb 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -1068,7 +1068,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// hw transpose filters should be added manually.
- args.Append(" -autorotate 0");
+ args.Append(" -noautorotate");
return args.ToString().Trim();
}
@@ -1159,7 +1159,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
if (!isSwDecoder && _mediaEncoder.EncoderVersion >= new Version(4, 4))
{
- arg.Append(" -autoscale 0");
+ arg.Append(" -noautoscale");
}
return arg.ToString();
@@ -3343,7 +3343,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// [0:s]scale=s=1280x720
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
- overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
+ overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
return (mainFilters, subFilters, overlayFilters);
@@ -3520,7 +3520,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
subFilters.Add("hwupload=derive_device=cuda");
- overlayFilters.Add("overlay_cuda=eof_action=endall:shortest=1:repeatlast=0");
+ overlayFilters.Add("overlay_cuda=eof_action=pass:repeatlast=0");
}
}
else
@@ -3529,7 +3529,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
- overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
+ overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
}
@@ -3718,7 +3718,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
subFilters.Add("hwupload=derive_device=opencl");
- overlayFilters.Add("overlay_opencl=eof_action=endall:shortest=1:repeatlast=0");
+ overlayFilters.Add("overlay_opencl=eof_action=pass:repeatlast=0");
overlayFilters.Add("hwmap=derive_device=d3d11va:reverse=1");
overlayFilters.Add("format=d3d11");
}
@@ -3729,7 +3729,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
- overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
+ overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
}
@@ -3964,7 +3964,7 @@ namespace MediaBrowser.Controller.MediaEncoding
: string.Empty;
var overlayQsvFilter = string.Format(
CultureInfo.InvariantCulture,
- "overlay_qsv=eof_action=endall:shortest=1:repeatlast=0{0}",
+ "overlay_qsv=eof_action=pass:repeatlast=0{0}",
overlaySize);
overlayFilters.Add(overlayQsvFilter);
}
@@ -3975,7 +3975,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
- overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
+ overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
}
@@ -4180,7 +4180,7 @@ namespace MediaBrowser.Controller.MediaEncoding
: string.Empty;
var overlayQsvFilter = string.Format(
CultureInfo.InvariantCulture,
- "overlay_qsv=eof_action=endall:shortest=1:repeatlast=0{0}",
+ "overlay_qsv=eof_action=pass:repeatlast=0{0}",
overlaySize);
overlayFilters.Add(overlayQsvFilter);
}
@@ -4191,7 +4191,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
- overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
+ overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
}
}
@@ -4445,7 +4445,7 @@ namespace MediaBrowser.Controller.MediaEncoding
: string.Empty;
var overlayVaapiFilter = string.Format(
CultureInfo.InvariantCulture,
- "overlay_vaapi=eof_action=endall:shortest=1:repeatlast=0{0}",
+ "overlay_vaapi=eof_action=pass:repeatlast=0{0}",
overlaySize);
overlayFilters.Add(overlayVaapiFilter);
}
@@ -4456,7 +4456,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
- overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
+ overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
if (isVaapiEncoder)
{
@@ -4616,7 +4616,7 @@ namespace MediaBrowser.Controller.MediaEncoding
subFilters.Add("hwupload=derive_device=vulkan");
subFilters.Add("format=vulkan");
- overlayFilters.Add("overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0");
+ overlayFilters.Add("overlay_vulkan=eof_action=pass:repeatlast=0");
if (isSwEncoder)
{
@@ -4817,7 +4817,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
- overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
+ overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
if (isVaapiEncoder)
{
diff --git a/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs b/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs
new file mode 100644
index 0000000000..c19a12ae7a
--- /dev/null
+++ b/MediaBrowser.Controller/MediaEncoding/ITranscodeManager.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Streaming;
+
+namespace MediaBrowser.Controller.MediaEncoding;
+
+///
+/// A service for managing media transcoding.
+///
+public interface ITranscodeManager
+{
+ ///
+ /// Get transcoding job.
+ ///
+ /// Playback session id.
+ /// The transcoding job.
+ public TranscodingJob? GetTranscodingJob(string playSessionId);
+
+ ///
+ /// Get transcoding job.
+ ///
+ /// Path to the transcoding file.
+ /// The .
+ /// The transcoding job.
+ public TranscodingJob? GetTranscodingJob(string path, TranscodingJobType type);
+
+ ///
+ /// Ping transcoding job.
+ ///
+ /// Play session id.
+ /// Is user paused.
+ /// Play session id is null.
+ public void PingTranscodingJob(string playSessionId, bool? isUserPaused);
+
+ ///
+ /// Kills the single transcoding job.
+ ///
+ /// The device id.
+ /// The play session identifier.
+ /// The delete files.
+ /// Task.
+ public Task KillTranscodingJobs(string deviceId, string? playSessionId, Func deleteFiles);
+
+ ///
+ /// Report the transcoding progress to the session manager.
+ ///
+ /// The of which the progress will be reported.
+ /// The of the current transcoding job.
+ /// The current transcoding position.
+ /// The framerate of the transcoding job.
+ /// The completion percentage of the transcode.
+ /// The number of bytes transcoded.
+ /// The bitrate of the transcoding job.
+ public void ReportTranscodingProgress(
+ TranscodingJob job,
+ StreamState state,
+ TimeSpan? transcodingPosition,
+ float? framerate,
+ double? percentComplete,
+ long? bytesTranscoded,
+ int? bitRate);
+
+ ///
+ /// Starts FFMpeg.
+ ///
+ /// The state.
+ /// The output path.
+ /// The command line arguments for FFmpeg.
+ /// The user id.
+ /// The .
+ /// The cancellation token source.
+ /// The working directory.
+ /// Task.
+ public Task StartFfMpeg(
+ StreamState state,
+ string outputPath,
+ string commandLineArguments,
+ Guid userId,
+ TranscodingJobType transcodingJobType,
+ CancellationTokenSource cancellationTokenSource,
+ string? workingDirectory = null);
+
+ ///
+ /// Called when [transcode begin request].
+ ///
+ /// The path.
+ /// The type.
+ /// The .
+ public TranscodingJob? OnTranscodeBeginRequest(string path, TranscodingJobType type);
+
+ ///
+ /// Called when [transcode end].
+ ///
+ /// The transcode job.
+ public void OnTranscodeEndRequest(TranscodingJob job);
+
+ ///
+ /// Gets the transcoding lock.
+ ///
+ /// The output path of the transcoded file.
+ /// A .
+ public SemaphoreSlim GetTranscodingLock(string outputPath);
+}
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs b/MediaBrowser.Controller/MediaEncoding/TranscodingJob.cs
similarity index 64%
rename from Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
rename to MediaBrowser.Controller/MediaEncoding/TranscodingJob.cs
index 480ddab098..1e6d5933c8 100644
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
+++ b/MediaBrowser.Controller/MediaEncoding/TranscodingJob.cs
@@ -1,49 +1,39 @@
using System;
using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
using System.Threading;
-using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto;
using Microsoft.Extensions.Logging;
-namespace Jellyfin.Api.Models.PlaybackDtos;
+namespace MediaBrowser.Controller.MediaEncoding;
///
/// Class TranscodingJob.
///
-public class TranscodingJobDto : IDisposable
+public sealed class TranscodingJob : IDisposable
{
- ///
- /// The process lock.
- ///
- [SuppressMessage("Microsoft.Performance", "CA1051:NoVisibleInstanceFields", MessageId = "ProcessLock", Justification = "Imported from ServiceStack")]
- [SuppressMessage("Microsoft.Performance", "SA1401:PrivateField", MessageId = "ProcessLock", Justification = "Imported from ServiceStack")]
- public readonly object ProcessLock = new object();
+ private readonly ILogger _logger;
+ private readonly object _processLock = new();
+ private readonly object _timerLock = new();
+
+ private Timer? _killTimer;
///
- /// Timer lock.
- ///
- private readonly object _timerLock = new object();
-
- ///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// Instance of the interface.
- public TranscodingJobDto(ILogger logger)
+ public TranscodingJob(ILogger logger)
{
- Logger = logger;
+ _logger = logger;
}
///
/// Gets or sets the play session identifier.
///
- /// The play session identifier.
public string? PlaySessionId { get; set; }
///
/// Gets or sets the live stream identifier.
///
- /// The live stream identifier.
public string? LiveStreamId { get; set; }
///
@@ -54,7 +44,6 @@ public class TranscodingJobDto : IDisposable
///
/// Gets or sets the path.
///
- /// The path.
public MediaSourceInfo? MediaSource { get; set; }
///
@@ -65,32 +54,18 @@ public class TranscodingJobDto : IDisposable
///
/// Gets or sets the type.
///
- /// The type.
public TranscodingJobType Type { get; set; }
///
/// Gets or sets the process.
///
- /// The process.
public Process? Process { get; set; }
- ///
- /// Gets logger.
- ///
- public ILogger Logger { get; private set; }
-
///
/// Gets or sets the active request count.
///
- /// The active request count.
public int ActiveRequestCount { get; set; }
- ///
- /// Gets or sets the kill timer.
- ///
- /// The kill timer.
- private Timer? KillTimer { get; set; }
-
///
/// Gets or sets device id.
///
@@ -178,7 +153,7 @@ public class TranscodingJobDto : IDisposable
{
lock (_timerLock)
{
- KillTimer?.Change(Timeout.Infinite, Timeout.Infinite);
+ _killTimer?.Change(Timeout.Infinite, Timeout.Infinite);
}
}
@@ -189,10 +164,10 @@ public class TranscodingJobDto : IDisposable
{
lock (_timerLock)
{
- if (KillTimer is not null)
+ if (_killTimer is not null)
{
- KillTimer.Dispose();
- KillTimer = null;
+ _killTimer.Dispose();
+ _killTimer = null;
}
}
}
@@ -220,15 +195,15 @@ public class TranscodingJobDto : IDisposable
lock (_timerLock)
{
- if (KillTimer is null)
+ if (_killTimer is null)
{
- Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
- KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
+ _logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ _killTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
}
else
{
- Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
- KillTimer.Change(intervalMs, Timeout.Infinite);
+ _logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ _killTimer.Change(intervalMs, Timeout.Infinite);
}
}
}
@@ -245,39 +220,61 @@ public class TranscodingJobDto : IDisposable
lock (_timerLock)
{
- if (KillTimer is not null)
+ if (_killTimer is not null)
{
var intervalMs = PingTimeout;
- Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
- KillTimer.Change(intervalMs, Timeout.Infinite);
+ _logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ _killTimer.Change(intervalMs, Timeout.Infinite);
}
}
}
+ ///
+ /// Stops the transcoding job.
+ ///
+ public void Stop()
+ {
+ lock (_processLock)
+ {
+#pragma warning disable CA1849 // Can't await in lock block
+ TranscodingThrottler?.Stop().GetAwaiter().GetResult();
+
+ var process = Process;
+
+ if (!HasExited)
+ {
+ try
+ {
+ _logger.LogInformation("Stopping ffmpeg process with q command for {Path}", Path);
+
+ process!.StandardInput.WriteLine("q");
+
+ // Need to wait because killing is asynchronous.
+ if (!process.WaitForExit(5000))
+ {
+ _logger.LogInformation("Killing FFmpeg process for {Path}", Path);
+ process.Kill();
+ }
+ }
+ catch (InvalidOperationException)
+ {
+ }
+ }
+#pragma warning restore CA1849
+ }
+ }
+
///
public void Dispose()
{
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Dispose all resources.
- ///
- /// Whether to dispose all resources.
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- Process?.Dispose();
- Process = null;
- KillTimer?.Dispose();
- KillTimer = null;
- CancellationTokenSource?.Dispose();
- CancellationTokenSource = null;
- TranscodingThrottler?.Dispose();
- TranscodingThrottler = null;
- }
+ Process?.Dispose();
+ Process = null;
+ _killTimer?.Dispose();
+ _killTimer = null;
+ CancellationTokenSource?.Dispose();
+ CancellationTokenSource = null;
+ TranscodingThrottler?.Dispose();
+ TranscodingThrottler = null;
}
}
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/MediaBrowser.Controller/MediaEncoding/TranscodingThrottler.cs
similarity index 94%
rename from Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
rename to MediaBrowser.Controller/MediaEncoding/TranscodingThrottler.cs
index b577c4ea6a..813f13eaef 100644
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs
+++ b/MediaBrowser.Controller/MediaEncoding/TranscodingThrottler.cs
@@ -2,19 +2,18 @@
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
-namespace Jellyfin.Api.Models.PlaybackDtos;
+namespace MediaBrowser.Controller.MediaEncoding;
///
/// Transcoding throttler.
///
public class TranscodingThrottler : IDisposable
{
- private readonly TranscodingJobDto _job;
+ private readonly TranscodingJob _job;
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
private readonly IFileSystem _fileSystem;
@@ -30,7 +29,7 @@ public class TranscodingThrottler : IDisposable
/// Instance of the interface.
/// Instance of the interface.
/// Instance of the interface.
- public TranscodingThrottler(TranscodingJobDto job, ILogger logger, IConfigurationManager config, IFileSystem fileSystem, IMediaEncoder mediaEncoder)
+ public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config, IFileSystem fileSystem, IMediaEncoder mediaEncoder)
{
_job = job;
_logger = logger;
@@ -146,7 +145,7 @@ public class TranscodingThrottler : IDisposable
}
}
- private bool IsThrottleAllowed(TranscodingJobDto job, int thresholdSeconds)
+ private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds)
{
var bytesDownloaded = job.BytesDownloaded;
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs
index 53df7133b5..5a47236f92 100644
--- a/MediaBrowser.Controller/Session/ISessionManager.cs
+++ b/MediaBrowser.Controller/Session/ISessionManager.cs
@@ -111,7 +111,8 @@ namespace MediaBrowser.Controller.Session
/// Reports the session ended.
///
/// The session identifier.
- void ReportSessionEnded(string sessionId);
+ /// Task.
+ ValueTask ReportSessionEnded(string sessionId);
///
/// Sends the general command.
diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs
index 3e30c8dc45..3a12a56f1e 100644
--- a/MediaBrowser.Controller/Session/SessionInfo.cs
+++ b/MediaBrowser.Controller/Session/SessionInfo.cs
@@ -19,7 +19,7 @@ namespace MediaBrowser.Controller.Session
///
/// Class SessionInfo.
///
- public sealed class SessionInfo : IAsyncDisposable, IDisposable
+ public sealed class SessionInfo : IAsyncDisposable
{
// 1 second
private const long ProgressIncrement = 10000000;
@@ -374,8 +374,7 @@ namespace MediaBrowser.Controller.Session
}
}
- ///
- public void Dispose()
+ public async ValueTask DisposeAsync()
{
_disposed = true;
@@ -386,30 +385,17 @@ namespace MediaBrowser.Controller.Session
foreach (var controller in controllers)
{
- if (controller is IDisposable disposable)
+ if (controller is IAsyncDisposable disposableAsync)
+ {
+ _logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name);
+ await disposableAsync.DisposeAsync().ConfigureAwait(false);
+ }
+ else if (controller is IDisposable disposable)
{
_logger.LogDebug("Disposing session controller synchronously {TypeName}", disposable.GetType().Name);
disposable.Dispose();
}
}
}
-
- public async ValueTask DisposeAsync()
- {
- _disposed = true;
-
- StopAutomaticProgress();
-
- var controllers = SessionControllers.ToList();
-
- foreach (var controller in controllers)
- {
- if (controller is IAsyncDisposable disposableAsync)
- {
- _logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name);
- await disposableAsync.DisposeAsync().ConfigureAwait(false);
- }
- }
- }
}
}
diff --git a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs b/MediaBrowser.Controller/Streaming/ProgressiveFileStream.cs
similarity index 90%
rename from Jellyfin.Api/Helpers/ProgressiveFileStream.cs
rename to MediaBrowser.Controller/Streaming/ProgressiveFileStream.cs
index d7b1c9f8bb..f44dc92d71 100644
--- a/Jellyfin.Api/Helpers/ProgressiveFileStream.cs
+++ b/MediaBrowser.Controller/Streaming/ProgressiveFileStream.cs
@@ -3,10 +3,10 @@ using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Api.Models.PlaybackDtos;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.IO;
-namespace Jellyfin.Api.Helpers;
+namespace MediaBrowser.Controller.Streaming;
///
/// A progressive file stream for transferring transcoded files as they are written to.
@@ -14,8 +14,8 @@ namespace Jellyfin.Api.Helpers;
public class ProgressiveFileStream : Stream
{
private readonly Stream _stream;
- private readonly TranscodingJobDto? _job;
- private readonly TranscodingJobHelper? _transcodingJobHelper;
+ private readonly TranscodingJob? _job;
+ private readonly ITranscodeManager? _transcodeManager;
private readonly int _timeoutMs;
private bool _disposed;
@@ -24,12 +24,12 @@ public class ProgressiveFileStream : Stream
///
/// The path to the transcoded file.
/// The transcoding job information.
- /// The transcoding job helper.
+ /// The transcode manager.
/// The timeout duration in milliseconds.
- public ProgressiveFileStream(string filePath, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, int timeoutMs = 30000)
+ public ProgressiveFileStream(string filePath, TranscodingJob? job, ITranscodeManager transcodeManager, int timeoutMs = 30000)
{
_job = job;
- _transcodingJobHelper = transcodingJobHelper;
+ _transcodeManager = transcodeManager;
_timeoutMs = timeoutMs;
_stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan);
@@ -43,7 +43,7 @@ public class ProgressiveFileStream : Stream
public ProgressiveFileStream(Stream stream, int timeoutMs = 30000)
{
_job = null;
- _transcodingJobHelper = null;
+ _transcodeManager = null;
_timeoutMs = timeoutMs;
_stream = stream;
}
@@ -153,7 +153,7 @@ public class ProgressiveFileStream : Stream
if (_job is not null)
{
- _transcodingJobHelper?.OnTranscodeEndRequest(_job);
+ _transcodeManager?.OnTranscodeEndRequest(_job);
}
}
}
diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/MediaBrowser.Controller/Streaming/StreamState.cs
similarity index 88%
rename from Jellyfin.Api/Models/StreamingDtos/StreamState.cs
rename to MediaBrowser.Controller/Streaming/StreamState.cs
index cc1f9163e5..b5dbe29ec7 100644
--- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs
+++ b/MediaBrowser.Controller/Streaming/StreamState.cs
@@ -1,11 +1,9 @@
using System;
-using Jellyfin.Api.Helpers;
-using Jellyfin.Api.Models.PlaybackDtos;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
-namespace Jellyfin.Api.Models.StreamingDtos;
+namespace MediaBrowser.Controller.Streaming;
///
/// The stream state dto.
@@ -13,7 +11,7 @@ namespace Jellyfin.Api.Models.StreamingDtos;
public class StreamState : EncodingJobInfo, IDisposable
{
private readonly IMediaSourceManager _mediaSourceManager;
- private readonly TranscodingJobHelper _transcodingJobHelper;
+ private readonly ITranscodeManager _transcodeManager;
private bool _disposed;
///
@@ -21,12 +19,12 @@ public class StreamState : EncodingJobInfo, IDisposable
///
/// Instance of the interface.
/// The .
- /// The singleton.
- public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, TranscodingJobHelper transcodingJobHelper)
+ /// The singleton.
+ public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, ITranscodeManager transcodeManager)
: base(transcodingType)
{
_mediaSourceManager = mediaSourceManager;
- _transcodingJobHelper = transcodingJobHelper;
+ _transcodeManager = transcodeManager;
}
///
@@ -141,7 +139,7 @@ public class StreamState : EncodingJobInfo, IDisposable
///
/// Gets or sets the transcoding job.
///
- public TranscodingJobDto? TranscodingJob { get; set; }
+ public TranscodingJob? TranscodingJob { get; set; }
///
public void Dispose()
@@ -153,7 +151,7 @@ public class StreamState : EncodingJobInfo, IDisposable
///
public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{
- _transcodingJobHelper.ReportTranscodingProgress(TranscodingJob!, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
+ _transcodeManager.ReportTranscodingProgress(TranscodingJob!, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
}
///
diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs b/MediaBrowser.Controller/Streaming/StreamingRequestDto.cs
similarity index 96%
rename from Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs
rename to MediaBrowser.Controller/Streaming/StreamingRequestDto.cs
index a357498d4c..e47ef65f06 100644
--- a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs
+++ b/MediaBrowser.Controller/Streaming/StreamingRequestDto.cs
@@ -1,6 +1,6 @@
using MediaBrowser.Controller.MediaEncoding;
-namespace Jellyfin.Api.Models.StreamingDtos;
+namespace MediaBrowser.Controller.Streaming;
///
/// The audio streaming request dto.
diff --git a/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs b/MediaBrowser.Controller/Streaming/VideoRequestDto.cs
similarity index 94%
rename from Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs
rename to MediaBrowser.Controller/Streaming/VideoRequestDto.cs
index 8548fec1a1..44dc831fdc 100644
--- a/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs
+++ b/MediaBrowser.Controller/Streaming/VideoRequestDto.cs
@@ -1,4 +1,4 @@
-namespace Jellyfin.Api.Models.StreamingDtos;
+namespace MediaBrowser.Controller.Streaming;
///
/// The video request dto.
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
similarity index 66%
rename from Jellyfin.Api/Helpers/TranscodingJobHelper.cs
rename to MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
index 77d3edbd65..d79e4441aa 100644
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
@@ -8,10 +8,8 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Models.PlaybackDtos;
-using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
@@ -19,94 +17,78 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Session;
+using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
-using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
-namespace Jellyfin.Api.Helpers;
+namespace MediaBrowser.MediaEncoding.Transcoding;
-///
-/// Transcoding job helpers.
-///
-public class TranscodingJobHelper : IDisposable
+///
+public sealed class TranscodeManager : ITranscodeManager, IDisposable
{
- ///
- /// The active transcoding jobs.
- ///
- private static readonly List _activeTranscodingJobs = new List();
-
- ///
- /// The transcoding locks.
- ///
- private static readonly Dictionary _transcodingLocks = new Dictionary();
-
- private readonly IAttachmentExtractor _attachmentExtractor;
- private readonly IApplicationPaths _appPaths;
- private readonly EncodingHelper _encodingHelper;
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
- private readonly ILogger _logger;
+ private readonly IApplicationPaths _appPaths;
+ private readonly IServerConfigurationManager _serverConfigurationManager;
+ private readonly IUserManager _userManager;
+ private readonly ISessionManager _sessionManager;
+ private readonly EncodingHelper _encodingHelper;
private readonly IMediaEncoder _mediaEncoder;
private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IServerConfigurationManager _serverConfigurationManager;
- private readonly ISessionManager _sessionManager;
- private readonly ILoggerFactory _loggerFactory;
- private readonly IUserManager _userManager;
+ private readonly IAttachmentExtractor _attachmentExtractor;
+
+ private readonly List _activeTranscodingJobs = new();
+ private readonly Dictionary _transcodingLocks = new();
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of .
- /// Instance of the interface.
- /// Instance of the interface.
- public TranscodingJobHelper(
- IAttachmentExtractor attachmentExtractor,
- IApplicationPaths appPaths,
- ILogger logger,
- IMediaSourceManager mediaSourceManager,
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ public TranscodeManager(
+ ILoggerFactory loggerFactory,
IFileSystem fileSystem,
- IMediaEncoder mediaEncoder,
+ IApplicationPaths appPaths,
IServerConfigurationManager serverConfigurationManager,
+ IUserManager userManager,
ISessionManager sessionManager,
EncodingHelper encodingHelper,
- ILoggerFactory loggerFactory,
- IUserManager userManager)
+ IMediaEncoder mediaEncoder,
+ IMediaSourceManager mediaSourceManager,
+ IAttachmentExtractor attachmentExtractor)
{
- _attachmentExtractor = attachmentExtractor;
- _appPaths = appPaths;
- _logger = logger;
- _mediaSourceManager = mediaSourceManager;
+ _loggerFactory = loggerFactory;
_fileSystem = fileSystem;
- _mediaEncoder = mediaEncoder;
+ _appPaths = appPaths;
_serverConfigurationManager = serverConfigurationManager;
+ _userManager = userManager;
_sessionManager = sessionManager;
_encodingHelper = encodingHelper;
- _loggerFactory = loggerFactory;
- _userManager = userManager;
+ _mediaEncoder = mediaEncoder;
+ _mediaSourceManager = mediaSourceManager;
+ _attachmentExtractor = attachmentExtractor;
+ _logger = loggerFactory.CreateLogger();
DeleteEncodedMediaCache();
-
- sessionManager.PlaybackProgress += OnPlaybackProgress;
- sessionManager.PlaybackStart += OnPlaybackProgress;
+ _sessionManager.PlaybackProgress += OnPlaybackProgress;
+ _sessionManager.PlaybackStart += OnPlaybackProgress;
}
- ///
- /// Get transcoding job.
- ///
- /// Playback session id.
- /// The transcoding job.
- public TranscodingJobDto? GetTranscodingJob(string playSessionId)
+ ///
+ public TranscodingJob? GetTranscodingJob(string playSessionId)
{
lock (_activeTranscodingJobs)
{
@@ -114,13 +96,8 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- /// Get transcoding job.
- ///
- /// Path to the transcoding file.
- /// The .
- /// The transcoding job.
- public TranscodingJobDto? GetTranscodingJob(string path, TranscodingJobType type)
+ ///
+ public TranscodingJob? GetTranscodingJob(string path, TranscodingJobType type)
{
lock (_activeTranscodingJobs)
{
@@ -128,19 +105,14 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- /// Ping transcoding job.
- ///
- /// Play session id.
- /// Is user paused.
- /// Play session id is null.
+ ///
public void PingTranscodingJob(string playSessionId, bool? isUserPaused)
{
ArgumentException.ThrowIfNullOrEmpty(playSessionId);
_logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
- List jobs;
+ List jobs;
lock (_activeTranscodingJobs)
{
@@ -161,7 +133,7 @@ public class TranscodingJobHelper : IDisposable
}
}
- private void PingTimer(TranscodingJobDto job, bool isProgressCheckIn)
+ private void PingTimer(TranscodingJob job, bool isProgressCheckIn)
{
if (job.HasExited)
{
@@ -190,13 +162,9 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- /// Called when [transcode kill timer stopped].
- ///
- /// The state.
private async void OnTranscodeKillTimerStopped(object? state)
{
- var job = state as TranscodingJobDto ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJobDto)}", nameof(state));
+ var job = state as TranscodingJob ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJob)}", nameof(state));
if (!job.HasExited && job.Type != TranscodingJobType.Progressive)
{
var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds;
@@ -213,43 +181,21 @@ public class TranscodingJobHelper : IDisposable
await KillTranscodingJob(job, true, path => true).ConfigureAwait(false);
}
- ///
- /// Kills the single transcoding job.
- ///
- /// The device id.
- /// The play session identifier.
- /// The delete files.
- /// Task.
+ ///
public Task KillTranscodingJobs(string deviceId, string? playSessionId, Func deleteFiles)
{
- return KillTranscodingJobs(
- j => string.IsNullOrWhiteSpace(playSessionId)
- ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
- : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase),
- deleteFiles);
- }
-
- ///
- /// Kills the transcoding jobs.
- ///
- /// The kill job.
- /// The delete files.
- /// Task.
- private Task KillTranscodingJobs(Func killJob, Func deleteFiles)
- {
- var jobs = new List();
+ var jobs = new List();
lock (_activeTranscodingJobs)
{
// This is really only needed for HLS.
// Progressive streams can stop on their own reliably.
- jobs.AddRange(_activeTranscodingJobs.Where(killJob));
+ jobs.AddRange(_activeTranscodingJobs.Where(j => string.IsNullOrWhiteSpace(playSessionId)
+ ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
+ : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase)));
}
- if (jobs.Count == 0)
- {
- return Task.CompletedTask;
- }
+ return Task.WhenAll(GetKillJobs());
IEnumerable GetKillJobs()
{
@@ -258,17 +204,9 @@ public class TranscodingJobHelper : IDisposable
yield return KillTranscodingJob(job, false, deleteFiles);
}
}
-
- return Task.WhenAll(GetKillJobs());
}
- ///
- /// Kills the transcoding job.
- ///
- /// The job.
- /// if set to true [close live stream].
- /// The delete.
- private async Task KillTranscodingJob(TranscodingJobDto job, bool closeLiveStream, Func delete)
+ private async Task KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func delete)
{
job.DisposeKillTimer();
@@ -282,6 +220,7 @@ public class TranscodingJobHelper : IDisposable
{
#pragma warning disable CA1849 // Can't await in lock block
job.CancellationTokenSource.Cancel();
+#pragma warning restore CA1849
}
}
@@ -290,35 +229,7 @@ public class TranscodingJobHelper : IDisposable
_transcodingLocks.Remove(job.Path!);
}
- lock (job.ProcessLock!)
- {
- job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
-
- var process = job.Process;
-
- var hasExited = job.HasExited;
-
- if (!hasExited)
- {
- try
- {
- _logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path);
-
- process!.StandardInput.WriteLine("q");
-
- // Need to wait because killing is asynchronous.
- if (!process.WaitForExit(5000))
- {
- _logger.LogInformation("Killing FFmpeg process for {Path}", job.Path);
- process.Kill();
- }
- }
- catch (InvalidOperationException)
- {
- }
- }
-#pragma warning restore CA1849
- }
+ job.Stop();
if (delete(job.Path!))
{
@@ -381,10 +292,6 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- /// Deletes the progressive partial stream files.
- ///
- /// The output file path.
private void DeleteProgressivePartialStreamFiles(string outputFilePath)
{
if (File.Exists(outputFilePath))
@@ -393,10 +300,6 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- /// Deletes the HLS partial stream files.
- ///
- /// The output file path.
private void DeleteHlsPartialStreamFiles(string outputFilePath)
{
var directory = Path.GetDirectoryName(outputFilePath)
@@ -428,18 +331,9 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- /// Report the transcoding progress to the session manager.
- ///
- /// The of which the progress will be reported.
- /// The of the current transcoding job.
- /// The current transcoding position.
- /// The framerate of the transcoding job.
- /// The completion percentage of the transcode.
- /// The number of bytes transcoded.
- /// The bitrate of the transcoding job.
+ ///
public void ReportTranscodingProgress(
- TranscodingJobDto job,
+ TranscodingJob job,
StreamState state,
TimeSpan? transcodingPosition,
float? framerate,
@@ -490,22 +384,12 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- /// Starts FFmpeg.
- ///
- /// The state.
- /// The output path.
- /// The command line arguments for FFmpeg.
- /// The .
- /// The .
- /// The cancellation token source.
- /// The working directory.
- /// Task.
- public async Task StartFfMpeg(
+ ///
+ public async Task StartFfMpeg(
StreamState state,
string outputPath,
string commandLineArguments,
- HttpRequest request,
+ Guid userId,
TranscodingJobType transcodingJobType,
CancellationTokenSource cancellationTokenSource,
string? workingDirectory = null)
@@ -517,8 +401,7 @@ public class TranscodingJobHelper : IDisposable
if (state.VideoRequest is not null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
{
- var userId = request.HttpContext.User.GetUserId();
- var user = userId.Equals(default) ? null : _userManager.GetUserById(userId);
+ var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
{
this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
@@ -595,13 +478,26 @@ public class TranscodingJobHelper : IDisposable
$"{logFilePrefix}{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{state.Request.MediaSourceId}_{Guid.NewGuid().ToString()[..8]}.log");
// FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
- Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
+ Stream logStream = new FileStream(
+ logFilePath,
+ FileMode.Create,
+ FileAccess.Write,
+ FileShare.Read,
+ IODefaults.FileStreamBufferSize,
+ FileOptions.Asynchronous);
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
- var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
+ var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(
+ JsonSerializer.Serialize(state.MediaSource)
+ + Environment.NewLine
+ + Environment.NewLine
+ + commandLineLogMessage
+ + Environment.NewLine
+ + Environment.NewLine);
+
await logStream.WriteAsync(commandLineLogMessageBytes, cancellationTokenSource.Token).ConfigureAwait(false);
- process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
+ process.Exited += (_, _) => OnFfMpegProcessExited(process, transcodingJob, state);
try
{
@@ -610,7 +506,6 @@ public class TranscodingJobHelper : IDisposable
catch (Exception ex)
{
_logger.LogError(ex, "Error starting FFmpeg");
-
this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
throw;
@@ -656,7 +551,7 @@ public class TranscodingJobHelper : IDisposable
return transcodingJob;
}
- private void StartThrottler(StreamState state, TranscodingJobDto transcodingJob)
+ private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
{
if (EnableThrottling(state))
{
@@ -665,31 +560,14 @@ public class TranscodingJobHelper : IDisposable
}
}
- private bool EnableThrottling(StreamState state)
- {
- var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
+ private static bool EnableThrottling(StreamState state)
+ => state.InputProtocol == MediaProtocol.File
+ && state.RunTimeTicks.HasValue
+ && state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks
+ && state.IsInputVideo
+ && state.VideoType == VideoType.VideoFile;
- return state.InputProtocol == MediaProtocol.File &&
- state.RunTimeTicks.HasValue &&
- state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
- state.IsInputVideo &&
- state.VideoType == VideoType.VideoFile;
- }
-
- ///
- /// Called when [transcode beginning].
- ///
- /// The path.
- /// The play session identifier.
- /// The live stream identifier.
- /// The transcoding job identifier.
- /// The type.
- /// The process.
- /// The device id.
- /// The state.
- /// The cancellation token source.
- /// TranscodingJob.
- public TranscodingJobDto OnTranscodeBeginning(
+ private TranscodingJob OnTranscodeBeginning(
string path,
string? playSessionId,
string? liveStreamId,
@@ -702,7 +580,7 @@ public class TranscodingJobHelper : IDisposable
{
lock (_activeTranscodingJobs)
{
- var job = new TranscodingJobDto(_loggerFactory.CreateLogger())
+ var job = new TranscodingJob(_loggerFactory.CreateLogger())
{
Type = type,
Path = path,
@@ -724,11 +602,8 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- /// Called when [transcode end].
- ///
- /// The transcode job.
- public void OnTranscodeEndRequest(TranscodingJobDto job)
+ ///
+ public void OnTranscodeEndRequest(TranscodingJob job)
{
job.ActiveRequestCount--;
_logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={ActiveRequestCount}", job.ActiveRequestCount);
@@ -738,16 +613,7 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- ///
- /// The progressive
- ///
- /// Called when [transcode failed to start].
- ///
- /// The path.
- /// The type.
- /// The state.
- public void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state)
+ private void OnTranscodeFailedToStart(string path, TranscodingJobType type, StreamState state)
{
lock (_activeTranscodingJobs)
{
@@ -770,13 +636,7 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- /// Processes the exited.
- ///
- /// The process.
- /// The job.
- /// The state.
- private void OnFfMpegProcessExited(Process process, TranscodingJobDto job, StreamState state)
+ private void OnFfMpegProcessExited(Process process, TranscodingJob job, StreamState state)
{
job.HasExited = true;
job.ExitCode = process.ExitCode;
@@ -822,44 +682,30 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- /// Called when [transcode begin request].
- ///
- /// The path.
- /// The type.
- /// The .
- public TranscodingJobDto? OnTranscodeBeginRequest(string path, TranscodingJobType type)
+ ///
+ public TranscodingJob? OnTranscodeBeginRequest(string path, TranscodingJobType type)
{
lock (_activeTranscodingJobs)
{
- var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
+ var job = _activeTranscodingJobs
+ .FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
if (job is null)
{
return null;
}
- OnTranscodeBeginRequest(job);
+ job.ActiveRequestCount++;
+ if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive)
+ {
+ job.StopKillTimer();
+ }
return job;
}
}
- private void OnTranscodeBeginRequest(TranscodingJobDto job)
- {
- job.ActiveRequestCount++;
-
- if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive)
- {
- job.StopKillTimer();
- }
- }
-
- ///
- /// Gets the transcoding lock.
- ///
- /// The output path of the transcoded file.
- /// A .
+ ///
public SemaphoreSlim GetTranscodingLock(string outputPath)
{
lock (_transcodingLocks)
@@ -882,9 +728,6 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- /// Deletes the encoded media cache.
- ///
private void DeleteEncodedMediaCache()
{
var path = _serverConfigurationManager.GetTranscodePath();
@@ -899,26 +742,10 @@ public class TranscodingJobHelper : IDisposable
}
}
- ///
- /// Dispose transcoding job helper.
- ///
+ ///
public void Dispose()
{
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Dispose throttler.
- ///
- /// Disposing.
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- _loggerFactory.Dispose();
- _sessionManager.PlaybackProgress -= OnPlaybackProgress;
- _sessionManager.PlaybackStart -= OnPlaybackProgress;
- }
+ _sessionManager.PlaybackProgress -= OnPlaybackProgress;
+ _sessionManager.PlaybackStart -= OnPlaybackProgress;
}
}
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index fbad29143b..1c071067df 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -31,6 +31,8 @@ namespace MediaBrowser.Model.Configuration
public bool EnableLUFSScan { get; set; }
+ public bool UseReplayGainTags { get; set; }
+
public bool EnableChapterImageExtraction { get; set; }
public bool ExtractChapterImagesDuringLibraryScan { get; set; }
diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs
index 7a1c7a7382..4962992a0a 100644
--- a/MediaBrowser.Model/Devices/DeviceInfo.cs
+++ b/MediaBrowser.Model/Devices/DeviceInfo.cs
@@ -15,6 +15,8 @@ namespace MediaBrowser.Model.Devices
public string Name { get; set; }
+ public string CustomName { get; set; }
+
///
/// Gets or sets the access token.
///
diff --git a/MediaBrowser.Model/Dlna/DeviceIdentification.cs b/MediaBrowser.Model/Dlna/DeviceIdentification.cs
deleted file mode 100644
index 6625b79814..0000000000
--- a/MediaBrowser.Model/Dlna/DeviceIdentification.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-
-namespace MediaBrowser.Model.Dlna
-{
- public class DeviceIdentification
- {
- ///
- /// Gets or sets the name of the friendly.
- ///
- /// The name of the friendly.
- public string FriendlyName { get; set; } = string.Empty;
-
- ///
- /// Gets or sets the model number.
- ///
- /// The model number.
- public string ModelNumber { get; set; } = string.Empty;
-
- ///
- /// Gets or sets the serial number.
- ///
- /// The serial number.
- public string SerialNumber { get; set; } = string.Empty;
-
- ///
- /// Gets or sets the name of the model.
- ///
- /// The name of the model.
- public string ModelName { get; set; } = string.Empty;
-
- ///
- /// Gets or sets the model description.
- ///
- /// The model description.
- public string ModelDescription { get; set; } = string.Empty;
-
- ///
- /// Gets or sets the model URL.
- ///
- /// The model URL.
- public string ModelUrl { get; set; } = string.Empty;
-
- ///
- /// Gets or sets the manufacturer.
- ///
- /// The manufacturer.
- public string Manufacturer { get; set; } = string.Empty;
-
- ///
- /// Gets or sets the manufacturer URL.
- ///
- /// The manufacturer URL.
- public string ManufacturerUrl { get; set; } = string.Empty;
-
- ///
- /// Gets or sets the headers.
- ///
- /// The headers.
- public HttpHeaderInfo[] Headers { get; set; } = Array.Empty();
- }
-}
diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs
index 71d0896a70..2addebbfca 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs
@@ -1,11 +1,7 @@
#pragma warning disable CA1819 // Properties should not return arrays
+
using System;
-using System.ComponentModel;
-using System.Linq;
using System.Xml.Serialization;
-using Jellyfin.Data.Enums;
-using Jellyfin.Extensions;
-using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Model.Dlna
{
@@ -17,7 +13,6 @@ namespace MediaBrowser.Model.Dlna
/// the device is able to direct play (without transcoding or remuxing),
/// as well as which containers/codecs to transcode to in case it isn't.
///
- [XmlRoot("Profile")]
public class DeviceProfile
{
///
@@ -31,104 +26,6 @@ namespace MediaBrowser.Model.Dlna
[XmlIgnore]
public string? Id { get; set; }
- ///
- /// Gets or sets the Identification.
- ///
- public DeviceIdentification? Identification { get; set; }
-
- ///
- /// Gets or sets the friendly name of the device profile, which can be shown to users.
- ///
- public string? FriendlyName { get; set; }
-
- ///
- /// Gets or sets the manufacturer of the device which this profile represents.
- ///
- public string? Manufacturer { get; set; }
-
- ///
- /// Gets or sets an url for the manufacturer of the device which this profile represents.
- ///
- public string? ManufacturerUrl { get; set; }
-
- ///
- /// Gets or sets the model name of the device which this profile represents.
- ///
- public string? ModelName { get; set; }
-
- ///
- /// Gets or sets the model description of the device which this profile represents.
- ///
- public string? ModelDescription { get; set; }
-
- ///
- /// Gets or sets the model number of the device which this profile represents.
- ///
- public string? ModelNumber { get; set; }
-
- ///
- /// Gets or sets the ModelUrl.
- ///
- public string? ModelUrl { get; set; }
-
- ///
- /// Gets or sets the serial number of the device which this profile represents.
- ///
- public string? SerialNumber { get; set; }
-
- ///
- /// Gets or sets a value indicating whether EnableAlbumArtInDidl.
- ///
- [DefaultValue(false)]
- public bool EnableAlbumArtInDidl { get; set; }
-
- ///
- /// Gets or sets a value indicating whether EnableSingleAlbumArtLimit.
- ///
- [DefaultValue(false)]
- public bool EnableSingleAlbumArtLimit { get; set; }
-
- ///
- /// Gets or sets a value indicating whether EnableSingleSubtitleLimit.
- ///
- [DefaultValue(false)]
- public bool EnableSingleSubtitleLimit { get; set; }
-
- ///
- /// Gets or sets the SupportedMediaTypes.
- ///
- public string SupportedMediaTypes { get; set; } = "Audio,Photo,Video";
-
- ///
- /// Gets or sets the UserId.
- ///
- public string? UserId { get; set; }
-
- ///
- /// Gets or sets the AlbumArtPn.
- ///
- public string? AlbumArtPn { get; set; }
-
- ///
- /// Gets or sets the MaxAlbumArtWidth.
- ///
- public int? MaxAlbumArtWidth { get; set; }
-
- ///
- /// Gets or sets the MaxAlbumArtHeight.
- ///
- public int? MaxAlbumArtHeight { get; set; }
-
- ///
- /// Gets or sets the maximum allowed width of embedded icons.
- ///
- public int? MaxIconWidth { get; set; }
-
- ///
- /// Gets or sets the maximum allowed height of embedded icons.
- ///
- public int? MaxIconHeight { get; set; }
-
///
/// Gets or sets the maximum allowed bitrate for all streamed content.
///
@@ -149,51 +46,6 @@ namespace MediaBrowser.Model.Dlna
///
public int? MaxStaticMusicBitrate { get; set; } = 8000000;
- ///
- /// Gets or sets the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace.
- ///
- public string? SonyAggregationFlags { get; set; }
-
- ///
- /// Gets or sets the ProtocolInfo.
- ///
- public string? ProtocolInfo { get; set; }
-
- ///
- /// Gets or sets the TimelineOffsetSeconds.
- ///
- [DefaultValue(0)]
- public int TimelineOffsetSeconds { get; set; }
-
- ///
- /// Gets or sets a value indicating whether RequiresPlainVideoItems.
- ///
- [DefaultValue(false)]
- public bool RequiresPlainVideoItems { get; set; }
-
- ///
- /// Gets or sets a value indicating whether RequiresPlainFolders.
- ///
- [DefaultValue(false)]
- public bool RequiresPlainFolders { get; set; }
-
- ///
- /// Gets or sets a value indicating whether EnableMSMediaReceiverRegistrar.
- ///
- [DefaultValue(false)]
- public bool EnableMSMediaReceiverRegistrar { get; set; }
-
- ///
- /// Gets or sets a value indicating whether IgnoreTranscodeByteRangeRequests.
- ///
- [DefaultValue(false)]
- public bool IgnoreTranscodeByteRangeRequests { get; set; }
-
- ///
- /// Gets or sets the XmlRootAttributes.
- ///
- public XmlAttribute[] XmlRootAttributes { get; set; } = Array.Empty();
-
///
/// Gets or sets the direct play profiles.
///
@@ -214,298 +66,9 @@ namespace MediaBrowser.Model.Dlna
///
public CodecProfile[] CodecProfiles { get; set; } = Array.Empty();
- ///
- /// Gets or sets the ResponseProfiles.
- ///
- public ResponseProfile[] ResponseProfiles { get; set; } = Array.Empty();
-
///
/// Gets or sets the subtitle profiles.
///
public SubtitleProfile[] SubtitleProfiles { get; set; } = Array.Empty();
-
- ///
- /// The GetSupportedMediaTypes.
- ///
- /// The .
- public MediaType[] GetSupportedMediaTypes()
- {
- return ContainerProfile.SplitValue(SupportedMediaTypes)
- .Select(m => Enum.TryParse(m, out var parsed) ? parsed : MediaType.Unknown)
- .Where(m => m != MediaType.Unknown)
- .ToArray();
- }
-
- ///
- /// Gets the audio transcoding profile.
- ///
- /// The container.
- /// The audio Codec.
- /// A .
- public TranscodingProfile? GetAudioTranscodingProfile(string? container, string? audioCodec)
- {
- container = (container ?? string.Empty).TrimStart('.');
-
- foreach (var i in TranscodingProfiles)
- {
- if (i.Type != DlnaProfileType.Audio)
- {
- continue;
- }
-
- if (!string.Equals(container, i.Container, StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- return i;
- }
-
- return null;
- }
-
- ///
- /// Gets the video transcoding profile.
- ///
- /// The container.
- /// The audio Codec.
- /// The video Codec.
- /// The .
- public TranscodingProfile? GetVideoTranscodingProfile(string? container, string? audioCodec, string? videoCodec)
- {
- container = (container ?? string.Empty).TrimStart('.');
-
- foreach (var i in TranscodingProfiles)
- {
- if (i.Type != DlnaProfileType.Video)
- {
- continue;
- }
-
- if (!string.Equals(container, i.Container, StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- if (!string.Equals(videoCodec, i.VideoCodec, StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- return i;
- }
-
- return null;
- }
-
- ///
- /// Gets the audio media profile.
- ///
- /// The container.
- /// The audio codec.
- /// The audio channels.
- /// The audio bitrate.
- /// The audio sample rate.
- /// The audio bit depth.
- /// The .
- public ResponseProfile? GetAudioMediaProfile(string? container, string? audioCodec, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth)
- {
- foreach (var i in ResponseProfiles)
- {
- if (i.Type != DlnaProfileType.Audio)
- {
- continue;
- }
-
- if (!ContainerProfile.ContainsContainer(i.GetContainers(), container))
- {
- continue;
- }
-
- var audioCodecs = i.GetAudioCodecs();
- if (audioCodecs.Length > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- var anyOff = false;
- foreach (ProfileCondition c in i.Conditions)
- {
- if (!ConditionProcessor.IsAudioConditionSatisfied(GetModelProfileCondition(c), audioChannels, audioBitrate, audioSampleRate, audioBitDepth))
- {
- anyOff = true;
- break;
- }
- }
-
- if (anyOff)
- {
- continue;
- }
-
- return i;
- }
-
- return null;
- }
-
- ///
- /// Gets the model profile condition.
- ///
- /// The c.
- /// The .
- private ProfileCondition GetModelProfileCondition(ProfileCondition c)
- {
- return new ProfileCondition
- {
- Condition = c.Condition,
- IsRequired = c.IsRequired,
- Property = c.Property,
- Value = c.Value
- };
- }
-
- ///
- /// Gets the image media profile.
- ///
- /// The container.
- /// The width.
- /// The height.
- /// The .
- public ResponseProfile? GetImageMediaProfile(string container, int? width, int? height)
- {
- foreach (var i in ResponseProfiles)
- {
- if (i.Type != DlnaProfileType.Photo)
- {
- continue;
- }
-
- if (!ContainerProfile.ContainsContainer(i.GetContainers(), container))
- {
- continue;
- }
-
- var anyOff = false;
- foreach (var c in i.Conditions)
- {
- if (!ConditionProcessor.IsImageConditionSatisfied(GetModelProfileCondition(c), width, height))
- {
- anyOff = true;
- break;
- }
- }
-
- if (anyOff)
- {
- continue;
- }
-
- return i;
- }
-
- return null;
- }
-
- ///
- /// Gets the video media profile.
- ///
- /// The container.
- /// The audio codec.
- /// The video codec.
- /// The width.
- /// The height.
- /// The bit depth.
- /// The video bitrate.
- /// The video profile.
- /// The video range type.
- /// The video level.
- /// The video framerate.
- /// The packet length.
- /// The timestamp.
- /// True if anamorphic.
- /// True if interlaced.
- /// The ref frames.
- /// The number of video streams.
- /// The number of audio streams.
- /// The video Codec tag.
- /// True if Avc.
- /// The .
- public ResponseProfile? GetVideoMediaProfile(
- string? container,
- string? audioCodec,
- string? videoCodec,
- int? width,
- int? height,
- int? bitDepth,
- int? videoBitrate,
- string? videoProfile,
- VideoRangeType videoRangeType,
- double? videoLevel,
- float? videoFramerate,
- int? packetLength,
- TransportStreamTimestamp timestamp,
- bool? isAnamorphic,
- bool? isInterlaced,
- int? refFrames,
- int? numVideoStreams,
- int? numAudioStreams,
- string? videoCodecTag,
- bool? isAvc)
- {
- foreach (var i in ResponseProfiles)
- {
- if (i.Type != DlnaProfileType.Video)
- {
- continue;
- }
-
- if (!ContainerProfile.ContainsContainer(i.GetContainers(), container))
- {
- continue;
- }
-
- var audioCodecs = i.GetAudioCodecs();
- if (audioCodecs.Length > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- var videoCodecs = i.GetVideoCodecs();
- if (videoCodecs.Length > 0 && !videoCodecs.Contains(videoCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- var anyOff = false;
- foreach (ProfileCondition c in i.Conditions)
- {
- if (!ConditionProcessor.IsVideoConditionSatisfied(GetModelProfileCondition(c), width, height, bitDepth, videoBitrate, videoProfile, videoRangeType, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc))
- {
- anyOff = true;
- break;
- }
- }
-
- if (anyOff)
- {
- continue;
- }
-
- return i;
- }
-
- return null;
- }
}
}
diff --git a/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs b/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs
deleted file mode 100644
index 74c32c523e..0000000000
--- a/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.Dlna
-{
- public class DeviceProfileInfo
- {
- ///
- /// Gets or sets the identifier.
- ///
- /// The identifier.
- public string Id { get; set; }
-
- ///
- /// Gets or sets the name.
- ///
- /// The name.
- public string Name { get; set; }
-
- ///
- /// Gets or sets the type.
- ///
- /// The type.
- public DeviceProfileType Type { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Dlna/DeviceProfileType.cs b/MediaBrowser.Model/Dlna/DeviceProfileType.cs
deleted file mode 100644
index 46062abd09..0000000000
--- a/MediaBrowser.Model/Dlna/DeviceProfileType.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.Dlna
-{
- public enum DeviceProfileType
- {
- System = 0,
- User = 1
- }
-}
diff --git a/MediaBrowser.Model/Dlna/HeaderMatchType.cs b/MediaBrowser.Model/Dlna/HeaderMatchType.cs
deleted file mode 100644
index 2a9abb20eb..0000000000
--- a/MediaBrowser.Model/Dlna/HeaderMatchType.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.Dlna
-{
- public enum HeaderMatchType
- {
- Equals = 0,
- Regex = 1,
- Substring = 2
- }
-}
diff --git a/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs b/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs
deleted file mode 100644
index 17c4dffcc0..0000000000
--- a/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-using System.Xml.Serialization;
-
-namespace MediaBrowser.Model.Dlna
-{
- public class HttpHeaderInfo
- {
- [XmlAttribute("name")]
- public string Name { get; set; }
-
- [XmlAttribute("value")]
- public string Value { get; set; }
-
- [XmlAttribute("match")]
- public HeaderMatchType Match { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Dlna/ResponseProfile.cs b/MediaBrowser.Model/Dlna/ResponseProfile.cs
deleted file mode 100644
index bf9661f7f3..0000000000
--- a/MediaBrowser.Model/Dlna/ResponseProfile.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-using System;
-using System.Xml.Serialization;
-
-namespace MediaBrowser.Model.Dlna
-{
- public class ResponseProfile
- {
- public ResponseProfile()
- {
- Conditions = Array.Empty();
- }
-
- [XmlAttribute("container")]
- public string Container { get; set; }
-
- [XmlAttribute("audioCodec")]
- public string AudioCodec { get; set; }
-
- [XmlAttribute("videoCodec")]
- public string VideoCodec { get; set; }
-
- [XmlAttribute("type")]
- public DlnaProfileType Type { get; set; }
-
- [XmlAttribute("orgPn")]
- public string OrgPn { get; set; }
-
- [XmlAttribute("mimeType")]
- public string MimeType { get; set; }
-
- public ProfileCondition[] Conditions { get; set; }
-
- public string[] GetContainers()
- {
- return ContainerProfile.SplitValue(Container);
- }
-
- public string[] GetAudioCodecs()
- {
- return ContainerProfile.SplitValue(AudioCodec);
- }
-
- public string[] GetVideoCodecs()
- {
- return ContainerProfile.SplitValue(VideoCodec);
- }
- }
-}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index bf18d46dcd..da683a17e6 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
@@ -1536,7 +1537,7 @@ namespace MediaBrowser.Model.Dlna
private static void ValidateMediaOptions(MediaOptions options, bool isMediaSource)
{
- if (options.ItemId.Equals(default))
+ if (options.ItemId.IsEmpty())
{
ArgumentException.ThrowIfNullOrEmpty(options.DeviceId);
}
diff --git a/MediaBrowser.Model/Dlna/XmlAttribute.cs b/MediaBrowser.Model/Dlna/XmlAttribute.cs
deleted file mode 100644
index 03bb2e4b11..0000000000
--- a/MediaBrowser.Model/Dlna/XmlAttribute.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-using System.Xml.Serialization;
-
-namespace MediaBrowser.Model.Dlna
-{
- ///
- /// Defines the .
- ///
- public class XmlAttribute
- {
- ///
- /// Gets or sets the name of the attribute.
- ///
- [XmlAttribute("name")]
- public string Name { get; set; }
-
- ///
- /// Gets or sets the value of the attribute.
- ///
- [XmlAttribute("value")]
- public string Value { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index d257eab920..cfff717db2 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -85,11 +85,6 @@ namespace MediaBrowser.Model.Dto
public string PreferredMetadataCountryCode { get; set; }
- ///
- /// Gets or sets a value indicating whether [supports synchronize].
- ///
- public bool? SupportsSync { get; set; }
-
public string Container { get; set; }
///
diff --git a/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs b/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs
new file mode 100644
index 0000000000..7bfedf9735
--- /dev/null
+++ b/MediaBrowser.Model/Dto/UpdateUserItemDataDto.cs
@@ -0,0 +1,76 @@
+using System;
+
+namespace MediaBrowser.Model.Dto
+{
+ ///
+ /// This is used by the api to get information about a item user data.
+ ///
+ public class UpdateUserItemDataDto
+ {
+ ///
+ /// Gets or sets the rating.
+ ///
+ /// The rating.
+ public double? Rating { get; set; }
+
+ ///
+ /// Gets or sets the played percentage.
+ ///
+ /// The played percentage.
+ public double? PlayedPercentage { get; set; }
+
+ ///
+ /// Gets or sets the unplayed item count.
+ ///
+ /// The unplayed item count.
+ public int? UnplayedItemCount { get; set; }
+
+ ///
+ /// Gets or sets the playback position ticks.
+ ///
+ /// The playback position ticks.
+ public long? PlaybackPositionTicks { get; set; }
+
+ ///
+ /// Gets or sets the play count.
+ ///
+ /// The play count.
+ public int? PlayCount { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this instance is favorite.
+ ///
+ /// true if this instance is favorite; otherwise, false.
+ public bool? IsFavorite { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this is likes.
+ ///
+ /// null if [likes] contains no value, true if [likes]; otherwise, false.
+ public bool? Likes { get; set; }
+
+ ///
+ /// Gets or sets the last played date.
+ ///
+ /// The last played date.
+ public DateTime? LastPlayedDate { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this is played.
+ ///
+ /// true if played; otherwise, false.
+ public bool? Played { get; set; }
+
+ ///
+ /// Gets or sets the key.
+ ///
+ /// The key.
+ public string? Key { get; set; }
+
+ ///
+ /// Gets or sets the item identifier.
+ ///
+ /// The item identifier.
+ public string? ItemId { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index 34642b83aa..ae4a008bb3 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -214,6 +215,27 @@ namespace MediaBrowser.Model.Entities
}
}
+ ///
+ /// Gets the audio spatial format.
+ ///
+ /// The audio spatial format.
+ [DefaultValue(AudioSpatialFormat.None)]
+ public AudioSpatialFormat AudioSpatialFormat
+ {
+ get
+ {
+ if (Type != MediaStreamType.Audio || string.IsNullOrEmpty(Profile))
+ {
+ return AudioSpatialFormat.None;
+ }
+
+ return
+ Profile.Contains("Dolby Atmos", StringComparison.OrdinalIgnoreCase) ? AudioSpatialFormat.DolbyAtmos :
+ Profile.Contains("DTS:X", StringComparison.OrdinalIgnoreCase) ? AudioSpatialFormat.DTSX :
+ AudioSpatialFormat.None;
+ }
+ }
+
public string LocalizedUndefined { get; set; }
public string LocalizedDefault { get; set; }
diff --git a/MediaBrowser.Model/Entities/UserDataSaveReason.cs b/MediaBrowser.Model/Entities/UserDataSaveReason.cs
index 20404e6f4d..b8e73a98cd 100644
--- a/MediaBrowser.Model/Entities/UserDataSaveReason.cs
+++ b/MediaBrowser.Model/Entities/UserDataSaveReason.cs
@@ -33,6 +33,11 @@ namespace MediaBrowser.Model.Entities
///
/// The import.
///
- Import = 6
+ Import = 6,
+
+ ///
+ /// API call updated item user data.
+ ///
+ UpdateUserData = 7,
}
}
diff --git a/MediaBrowser.Model/IO/IStreamHelper.cs b/MediaBrowser.Model/IO/IStreamHelper.cs
index f900da5567..034a6bf8bf 100644
--- a/MediaBrowser.Model/IO/IStreamHelper.cs
+++ b/MediaBrowser.Model/IO/IStreamHelper.cs
@@ -13,8 +13,6 @@ namespace MediaBrowser.Model.IO
Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken);
- Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken);
-
Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs b/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs
deleted file mode 100644
index 80a6461957..0000000000
--- a/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.LiveTv
-{
- public enum LiveTvTunerStatus
- {
- Available = 0,
- Disabled = 1,
- RecordingTv = 2,
- LiveTv = 3
- }
-}
diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs
index 242a1c6e99..49d7c0bcb0 100644
--- a/MediaBrowser.Model/Querying/ItemFields.cs
+++ b/MediaBrowser.Model/Querying/ItemFields.cs
@@ -175,13 +175,6 @@ namespace MediaBrowser.Model.Querying
///
Studios,
- BasicSyncInfo,
-
- ///
- /// The synchronize information.
- ///
- SyncInfo,
-
///
/// The taglines of the item.
///
diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs
index 7fefce9cd5..597845fc17 100644
--- a/MediaBrowser.Model/Session/ClientCapabilities.cs
+++ b/MediaBrowser.Model/Session/ClientCapabilities.cs
@@ -23,14 +23,8 @@ namespace MediaBrowser.Model.Session
public bool SupportsMediaControl { get; set; }
- public bool SupportsContentUploading { get; set; }
-
- public string MessageCallbackUrl { get; set; }
-
public bool SupportsPersistentIdentifier { get; set; }
- public bool SupportsSync { get; set; }
-
public DeviceProfile DeviceProfile { get; set; }
public string AppStoreUrl { get; set; }
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 4ba8844182..b530b9de3f 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -706,7 +706,7 @@ namespace MediaBrowser.Providers.Manager
{
BaseItem? referenceItem = null;
- if (!searchInfo.ItemId.Equals(default))
+ if (!searchInfo.ItemId.IsEmpty())
{
referenceItem = _libraryManager.GetItemById(searchInfo.ItemId);
}
@@ -944,7 +944,7 @@ namespace MediaBrowser.Providers.Manager
public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
{
ArgumentNullException.ThrowIfNull(itemId);
- if (itemId.Equals(default))
+ if (itemId.IsEmpty())
{
throw new ArgumentException("Guid can't be empty", nameof(itemId));
}
diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
index 5d41542e2c..f68faab042 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
@@ -61,6 +61,9 @@ namespace MediaBrowser.Providers.MediaInfo
[GeneratedRegex(@"I:\s+(.*?)\s+LUFS")]
private static partial Regex LUFSRegex();
+ [GeneratedRegex(@"REPLAYGAIN_TRACK_GAIN:\s+-?([0-9.]+)\s+dB")]
+ private static partial Regex ReplayGainTagRegex();
+
///
/// Probes the specified item for metadata.
///
@@ -104,8 +107,50 @@ namespace MediaBrowser.Providers.MediaInfo
}
var libraryOptions = _libraryManager.GetLibraryOptions(item);
+ bool foundLUFSValue = false;
- if (libraryOptions.EnableLUFSScan)
+ if (libraryOptions.UseReplayGainTags)
+ {
+ using (var process = new Process()
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = _mediaEncoder.ProbePath,
+ Arguments = $"-hide_banner -i \"{path}\"",
+ RedirectStandardOutput = false,
+ RedirectStandardError = true
+ },
+ })
+ {
+ try
+ {
+ process.Start();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error starting ffmpeg");
+
+ throw;
+ }
+
+ using var reader = process.StandardError;
+ var output = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
+ Match split = ReplayGainTagRegex().Match(output);
+
+ if (split.Success)
+ {
+ item.LUFS = DefaultLUFSValue - float.Parse(split.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
+ foundLUFSValue = true;
+ }
+ else
+ {
+ item.LUFS = DefaultLUFSValue;
+ }
+ }
+ }
+
+ if (libraryOptions.EnableLUFSScan && !foundLUFSValue)
{
using (var process = new Process()
{
@@ -144,7 +189,8 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
}
- else
+
+ if (!libraryOptions.EnableLUFSScan && !libraryOptions.UseReplayGainTags)
{
item.LUFS = DefaultLUFSValue;
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
index c2018d820e..c76c65591f 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
@@ -75,12 +75,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
var collections = new RemoteSearchResult[collectionSearchResults.Count];
for (var i = 0; i < collectionSearchResults.Count; i++)
{
+ var result = collectionSearchResults[i];
var collection = new RemoteSearchResult
{
- Name = collectionSearchResults[i].Name,
- SearchProviderName = Name
+ Name = result.Name,
+ SearchProviderName = Name,
+ ImageUrl = _tmdbClientManager.GetPosterUrl(result.PosterPath)
};
- collection.SetProviderId(MetadataProvider.Tmdb, collectionSearchResults[i].Id.ToString(CultureInfo.InvariantCulture));
+ collection.SetProviderId(MetadataProvider.Tmdb, result.Id.ToString(CultureInfo.InvariantCulture));
collections[i] = collection;
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs
index 03aaf380b5..99b759ae29 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/PluginConfiguration.cs
@@ -7,6 +7,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
///
public class PluginConfiguration : BasePluginConfiguration
{
+ ///
+ /// Gets or sets a value to use as the API key for accessing TMDb. This is intentionally excluded from the
+ /// settings page as the API key should not need to be changed by most users.
+ ///
+ public string TmdbApiKey { get; set; } = string.Empty;
+
///
/// Gets or sets a value indicating whether include adult content when searching with TMDb.
///
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html
index cd21516f95..f3c24e7b42 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html
@@ -64,9 +64,18 @@
var clientConfig, pluginConfig;
var configureImageScaling = function() {
- if (clientConfig === null || pluginConfig === null) {
+ if (clientConfig === undefined || pluginConfig === undefined) {
return;
}
+ if (Object.keys(clientConfig).length === 0) {
+ clientConfig = {
+ PosterSizes: [pluginConfig.PosterSize],
+ BackdropSizes: [pluginConfig.BackdropSize],
+ LogoSizes: [pluginConfig.LogoSize],
+ ProfileSizes: [pluginConfig.ProfileSize],
+ StillSizes: [pluginConfig.StillSize]
+ };
+ }
var sizeOptionsGenerator = function (size) {
return '';
@@ -104,6 +113,15 @@
ApiClient.fetch(request).then(function (config) {
clientConfig = config;
configureImageScaling();
+ }, function (error) {
+ error.text().then(function (contents) {
+ Dashboard.alert({
+ title: error.statusText,
+ message: contents
+ });
+ clientConfig = {};
+ configureImageScaling();
+ });
});
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index 2f62e117eb..dac7a74ed8 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -299,7 +299,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
if (!string.IsNullOrWhiteSpace(person.ProfilePath))
{
- personInfo.ImageUrl = _tmdbClientManager.GetPosterUrl(person.ProfilePath);
+ personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(person.ProfilePath);
}
if (person.Id > 0)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
index 72e59c9ac4..82f2c54f16 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
@@ -36,7 +36,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
public TmdbClientManager(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
- _tmDbClient = new TMDbClient(TmdbUtils.ApiKey);
+
+ var apiKey = Plugin.Instance.Configuration.TmdbApiKey;
+ apiKey = string.IsNullOrEmpty(apiKey) ? TmdbUtils.ApiKey : apiKey;
+ _tmDbClient = new TMDbClient(apiKey);
+
// Not really interested in NotFoundException
_tmDbClient.ThrowApiExceptions = false;
}
diff --git a/README.md b/README.md
index 15dd0ae679..62ef21334d 100644
--- a/README.md
+++ b/README.md
@@ -142,6 +142,17 @@ cd Jellyfin.Server/bin/Debug/net8.0 # Change into the build output directory
2. Execute the build output. On Linux, Mac, etc. use `./jellyfin` and on Windows use `jellyfin.exe`.
+### Running from GH-Codespaces
+
+As Jellyfin will run on a container on a github hosted server, JF needs to handle some things differently.
+**NOTE:** If you want to access the JF instance from outside, like with a WebClient on another PC, remember to set the "ports" in the lower VsCode window to public.
+
+#### FFmpeg installation.
+Because sometimes you need FFMPEG to test certain cases, follow the instructions from the wiki on the dev enviorment:
+https://jellyfin.org/docs/general/installation/linux/#ffmpeg-installation
+
+**NOTE:** When first opening the server instance with any WebUI, you will be send to the login instead of the setup page. Refresh the login page once and you should be redirected to the Setup.
+
### Running The Tests
This repository also includes unit tests that are used to validate functionality as part of a CI pipeline on Azure. There are several ways to run these tests.
diff --git a/bump_version b/bump_version
index 41d27f5c8a..dd55e62c79 100755
--- a/bump_version
+++ b/bump_version
@@ -21,7 +21,7 @@ fi
shared_version_file="./SharedVersion.cs"
build_file="./build.yaml"
# csproj files for nuget packages
-jellyfin_subprojects=(
+jellyfin_subprojects=(
MediaBrowser.Common/MediaBrowser.Common.csproj
Jellyfin.Data/Jellyfin.Data.csproj
MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -97,7 +97,7 @@ cat ${debian_changelog_file} >> ${debian_changelog_temp}
# Move into place
mv ${debian_changelog_temp} ${debian_changelog_file}
-# Write out a temporary Yum changelog with our new stuff prepended and some templated formatting
+# Write out a temporary Dnf changelog with our new stuff prepended and some templated formatting
fedora_spec_file="fedora/jellyfin.spec"
fedora_changelog_temp="$( mktemp )"
fedora_spec_temp_dir="$( mktemp -d )"
diff --git a/debian/rules b/debian/rules
index 069d48aad1..79cd55a15d 100755
--- a/debian/rules
+++ b/debian/rules
@@ -7,27 +7,27 @@ HOST_ARCH := $(shell arch)
BUILD_ARCH := ${DEB_HOST_MULTIARCH}
ifeq ($(HOST_ARCH),x86_64)
# Building AMD64
- DOTNETRUNTIME := debian-x64
+ DOTNETRUNTIME := linux-x64
ifeq ($(BUILD_ARCH),arm-linux-gnueabihf)
# Cross-building ARM on AMD64
- DOTNETRUNTIME := debian-arm
+ DOTNETRUNTIME := linux-arm
endif
ifeq ($(BUILD_ARCH),aarch64-linux-gnu)
# Cross-building ARM on AMD64
- DOTNETRUNTIME := debian-arm64
+ DOTNETRUNTIME := linux-arm64
endif
endif
ifeq ($(HOST_ARCH),armv7l)
# Building ARM
- DOTNETRUNTIME := debian-arm
+ DOTNETRUNTIME := linux-arm
endif
ifeq ($(HOST_ARCH),arm64)
# Building ARM
- DOTNETRUNTIME := debian-arm64
+ DOTNETRUNTIME := linux-arm64
endif
ifeq ($(HOST_ARCH),aarch64)
# Building ARM
- DOTNETRUNTIME := debian-arm64
+ DOTNETRUNTIME := linux-arm64
endif
export DH_VERBOSE=1
diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64
index 7c9bbf39e9..af309b0831 100644
--- a/deployment/Dockerfile.centos.amd64
+++ b/deployment/Dockerfile.centos.amd64
@@ -1,29 +1,36 @@
-FROM centos:7
+FROM quay.io/centos/centos:stream9
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV IS_DOCKER=YES
# Prepare CentOS environment
-RUN yum update -yq \
- && yum install -yq epel-release \
- && yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
+RUN dnf update -yq \
+ && dnf install -yq \
+ @buildsys-build rpmdevtools git \
+ dnf-plugins-core libcurl-devel fontconfig-devel \
+ freetype-devel openssl-devel glibc-devel \
+ libicu-devel systemd wget make \
+ && dnf clean all \
+ && rm -rf /var/cache/dnf
# Install DotNET SDK
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
- && mkdir -p dotnet-sdk \
- && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
- && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
+ && mkdir -p dotnet-sdk \
+ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
+ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
# Create symlinks and directories
RUN ln -sf ${SOURCE_DIR}/deployment/build.centos.amd64 /build.sh \
- && mkdir -p ${SOURCE_DIR}/SPECS \
- && ln -s ${SOURCE_DIR}/fedora/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \
- && mkdir -p ${SOURCE_DIR}/SOURCES \
- && ln -s ${SOURCE_DIR}/fedora ${SOURCE_DIR}/SOURCES
+ && mkdir -p ${SOURCE_DIR}/SPECS \
+ && ln -s ${SOURCE_DIR}/fedora/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \
+ && mkdir -p ${SOURCE_DIR}/SOURCES \
+ && ln -s ${SOURCE_DIR}/fedora ${SOURCE_DIR}/SOURCES
VOLUME ${SOURCE_DIR}/
diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64
index d344c59646..da0c9dabd3 100644
--- a/deployment/Dockerfile.debian.amd64
+++ b/deployment/Dockerfile.debian.amd64
@@ -1,7 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -10,11 +14,14 @@ ENV ARCH=amd64
ENV IS_DOCKER=YES
# Prepare Debian build environment
-RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+RUN apt-get update -yq \
+ && apt-get install --no-install-recommends -yq \
debhelper gnupg devscripts build-essential mmv \
- libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev \
- libssl1.1 liblttng-ust0
+ libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev \
+ libssl-dev libssl3 liblttng-ust1 \
+ && apt-get clean autoclean -yq \
+ && apt-get autoremove -yq \
+ && rm -rf /var/lib/apt/lists/*
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.amd64 /build.sh
diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64
index 8a5411f059..6c4cb816f5 100644
--- a/deployment/Dockerfile.debian.arm64
+++ b/deployment/Dockerfile.debian.arm64
@@ -1,7 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -11,23 +15,26 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg devscripts build-essential mmv
# Prepare the cross-toolchain
RUN dpkg --add-architecture arm64 \
- && apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends cross-gcc-dev \
- && TARGET_LIST="arm64" cross-gcc-gensource 9 \
- && cd cross-gcc-packages-amd64/cross-gcc-9-arm64 \
- && apt-get install -yqq --no-install-recommends \
- gcc-9-source libstdc++-9-dev-arm64-cross \
+ && apt-get update -yqq \
+ && apt-get install --no-install-recommends -yqq cross-gcc-dev \
+ && TARGET_LIST="arm64" cross-gcc-gensource 12 \
+ && cd cross-gcc-packages-amd64/cross-gcc-12-arm64 \
+ && apt-get install --no-install-recommends -yqq \
+ gcc-12-source libstdc++-12-dev-arm64-cross \
binutils-aarch64-linux-gnu bison flex libtool \
gdb sharutils netbase libmpc-dev libmpfr-dev libgmp-dev \
systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip \
libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 \
libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 \
- libfreetype6-dev:arm64 libssl-dev:arm64 liblttng-ust0:arm64 libstdc++-9-dev:arm64
+ libfreetype6-dev:arm64 libssl-dev:arm64 liblttng-ust1:arm64 libstdc++-12-dev:arm64 \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.arm64 /build.sh
diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf
index e95ba16962..b1fa6cee52 100644
--- a/deployment/Dockerfile.debian.armhf
+++ b/deployment/Dockerfile.debian.armhf
@@ -1,7 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -11,24 +15,27 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg devscripts build-essential mmv
# Prepare the cross-toolchain
RUN dpkg --add-architecture armhf \
- && apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends cross-gcc-dev \
- && TARGET_LIST="armhf" cross-gcc-gensource 9 \
- && cd cross-gcc-packages-amd64/cross-gcc-9-armhf \
- && apt-get install -yqq --no-install-recommends\
- gcc-9-source libstdc++-9-dev-armhf-cross \
+ && apt-get update -yqq \
+ && apt-get install --no-install-recommends -yqq cross-gcc-dev \
+ && TARGET_LIST="armhf" cross-gcc-gensource 12 \
+ && cd cross-gcc-packages-amd64/cross-gcc-12-armhf \
+ && apt-get install --no-install-recommends -yqq \
+ gcc-12-source libstdc++-12-dev-armhf-cross \
binutils-aarch64-linux-gnu bison flex libtool gdb \
sharutils netbase libmpc-dev libmpfr-dev libgmp-dev \
systemtap-sdt-dev autogen expect chrpath zlib1g-dev \
zip binutils-arm-linux-gnueabihf libc6-dev:armhf \
linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf \
libfontconfig1-dev:armhf libfreetype6-dev:armhf libssl-dev:armhf \
- liblttng-ust0:armhf libstdc++-9-dev:armhf
+ liblttng-ust1:armhf libstdc++-12-dev:armhf \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.armhf /build.sh
diff --git a/deployment/Dockerfile.docker.amd64 b/deployment/Dockerfile.docker.amd64
index 1749ca563c..ca16a08fbc 100644
--- a/deployment/Dockerfile.docker.amd64
+++ b/deployment/Dockerfile.docker.amd64
@@ -1,13 +1,12 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
ARG SOURCE_DIR=/src
ARG ARTIFACT_DIR=/jellyfin
WORKDIR ${SOURCE_DIR}
COPY . .
-
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# because of changes in docker and systemd we need to not build in parallel at the moment
-# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
+RUN dotnet publish Jellyfin.Server --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
diff --git a/deployment/Dockerfile.docker.arm64 b/deployment/Dockerfile.docker.arm64
index bbddb61e4d..6e0f7d18e9 100644
--- a/deployment/Dockerfile.docker.arm64
+++ b/deployment/Dockerfile.docker.arm64
@@ -1,13 +1,12 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
ARG SOURCE_DIR=/src
ARG ARTIFACT_DIR=/jellyfin
WORKDIR ${SOURCE_DIR}
COPY . .
-
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# because of changes in docker and systemd we need to not build in parallel at the moment
-# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none
+RUN dotnet publish Jellyfin.Server --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none
diff --git a/deployment/Dockerfile.docker.armhf b/deployment/Dockerfile.docker.armhf
index 3de1d68878..44fb705e6d 100644
--- a/deployment/Dockerfile.docker.armhf
+++ b/deployment/Dockerfile.docker.armhf
@@ -1,13 +1,12 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
ARG SOURCE_DIR=/src
ARG ARTIFACT_DIR=/jellyfin
WORKDIR ${SOURCE_DIR}
COPY . .
-
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-# because of changes in docker and systemd we need to not build in parallel at the moment
-# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none
+RUN dotnet publish Jellyfin.Server --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none
diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64
index 66ead37d7d..75a6d1e649 100644
--- a/deployment/Dockerfile.fedora.amd64
+++ b/deployment/Dockerfile.fedora.amd64
@@ -1,7 +1,9 @@
FROM fedora:39
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -9,21 +11,26 @@ ENV IS_DOCKER=YES
# Prepare Fedora environment
RUN dnf update -yq \
- && dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget make
+ && dnf install -yq \
+ @buildsys-build rpmdevtools git \
+ dnf-plugins-core libcurl-devel fontconfig-devel \
+ freetype-devel openssl-devel glibc-devel \
+ libicu-devel systemd wget make \
+ && dnf clean all \
+ && rm -rf /var/cache/dnf
# Install DotNET SDK
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
- && mkdir -p dotnet-sdk \
- && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
- && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-
+ && mkdir -p dotnet-sdk \
+ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
+ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
# Create symlinks and directories
RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora.amd64 /build.sh \
- && mkdir -p ${SOURCE_DIR}/SPECS \
- && ln -s ${SOURCE_DIR}/fedora/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \
- && mkdir -p ${SOURCE_DIR}/SOURCES \
- && ln -s ${SOURCE_DIR}/fedora ${SOURCE_DIR}/SOURCES
+ && mkdir -p ${SOURCE_DIR}/SPECS \
+ && ln -s ${SOURCE_DIR}/fedora/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \
+ && mkdir -p ${SOURCE_DIR}/SOURCES \
+ && ln -s ${SOURCE_DIR}/fedora ${SOURCE_DIR}/SOURCES
VOLUME ${SOURCE_DIR}/
diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64
index 386f7cefe0..6b8de3773f 100644
--- a/deployment/Dockerfile.linux.amd64
+++ b/deployment/Dockerfile.linux.amd64
@@ -1,7 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -11,10 +15,13 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg devscripts unzip \
mmv libcurl4-openssl-dev libfontconfig1-dev \
- libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
+ libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to docker-build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.amd64 /build.sh
diff --git a/deployment/Dockerfile.linux.amd64-musl b/deployment/Dockerfile.linux.amd64-musl
index 56c8773332..49d98da2ac 100644
--- a/deployment/Dockerfile.linux.amd64-musl
+++ b/deployment/Dockerfile.linux.amd64-musl
@@ -1,7 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -11,10 +15,13 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg devscripts unzip \
mmv libcurl4-openssl-dev libfontconfig1-dev \
- libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
+ libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to docker-build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.amd64-musl /build.sh
diff --git a/deployment/Dockerfile.linux.arm64 b/deployment/Dockerfile.linux.arm64
index c9692c440a..aba33c8b23 100644
--- a/deployment/Dockerfile.linux.arm64
+++ b/deployment/Dockerfile.linux.arm64
@@ -1,7 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -11,10 +15,13 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg devscripts unzip \
mmv libcurl4-openssl-dev libfontconfig1-dev \
- libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
+ libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to docker-build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.arm64 /build.sh
diff --git a/deployment/Dockerfile.linux.armhf b/deployment/Dockerfile.linux.armhf
index 2304615560..247f756150 100644
--- a/deployment/Dockerfile.linux.armhf
+++ b/deployment/Dockerfile.linux.armhf
@@ -1,7 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -11,10 +15,13 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg devscripts unzip \
mmv libcurl4-openssl-dev libfontconfig1-dev \
- libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
+ libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to docker-build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.armhf /build.sh
diff --git a/deployment/Dockerfile.linux.musl-linux-arm64 b/deployment/Dockerfile.linux.musl-linux-arm64
index 240d091869..a6e1ba217e 100644
--- a/deployment/Dockerfile.linux.musl-linux-arm64
+++ b/deployment/Dockerfile.linux.musl-linux-arm64
@@ -1,7 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -11,10 +15,13 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
- apt-transport-https debhelper gnupg devscripts unzip \
+ && apt-get install --no-install-recommends -yqq \
+ debhelper gnupg devscripts unzip \
mmv libcurl4-openssl-dev libfontconfig1-dev \
- libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
+ libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to docker-build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.musl-linux-arm64 /build.sh
diff --git a/deployment/Dockerfile.macos.amd64 b/deployment/Dockerfile.macos.amd64
index 1b054dfc47..45980c363e 100644
--- a/deployment/Dockerfile.macos.amd64
+++ b/deployment/Dockerfile.macos.amd64
@@ -1,7 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -11,10 +15,13 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg devscripts \
mmv libcurl4-openssl-dev libfontconfig1-dev \
- libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
+ libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to docker-build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.macos.amd64 /build.sh
diff --git a/deployment/Dockerfile.macos.arm64 b/deployment/Dockerfile.macos.arm64
index 07e18da55a..ee3a813dde 100644
--- a/deployment/Dockerfile.macos.arm64
+++ b/deployment/Dockerfile.macos.arm64
@@ -1,7 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -11,10 +15,13 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg devscripts \
mmv libcurl4-openssl-dev libfontconfig1-dev \
- libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
+ libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to docker-build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.macos.arm64 /build.sh
diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable
index 36135f7a6e..0ab1b19147 100644
--- a/deployment/Dockerfile.portable
+++ b/deployment/Dockerfile.portable
@@ -1,7 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -10,10 +14,13 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg devscripts \
mmv libcurl4-openssl-dev libfontconfig1-dev \
- libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
+ libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to docker-build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.portable /build.sh
diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64
index 84fa2028e5..2326d3e852 100644
--- a/deployment/Dockerfile.ubuntu.amd64
+++ b/deployment/Dockerfile.ubuntu.amd64
@@ -1,7 +1,11 @@
-FROM ubuntu:bionic
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-jammy
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -11,16 +15,13 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg wget ca-certificates devscripts \
mmv build-essential libcurl4-openssl-dev libfontconfig1-dev \
- libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
-
-# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
- && mkdir -p dotnet-sdk \
- && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
- && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
+ libfreetype6-dev libssl-dev libssl3 liblttng-ust1 \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.ubuntu.amd64 /build.sh
diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64
index ca3aa35085..461a287a18 100644
--- a/deployment/Dockerfile.ubuntu.arm64
+++ b/deployment/Dockerfile.ubuntu.arm64
@@ -1,7 +1,11 @@
-FROM ubuntu:bionic
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-jammy
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -11,39 +15,36 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg wget ca-certificates devscripts \
mmv build-essential lsb-release
-# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
- && mkdir -p dotnet-sdk \
- && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
- && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-
# Prepare the cross-toolchain
RUN rm /etc/apt/sources.list \
- && export CODENAME="$( lsb_release -c -s )" \
- && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
- && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
- && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
- && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
- && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \
- && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \
- && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \
- && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \
- && dpkg --add-architecture arm64 \
- && apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends cross-gcc-dev \
- && TARGET_LIST="arm64" cross-gcc-gensource 6 \
- && cd cross-gcc-packages-amd64/cross-gcc-6-arm64 \
- && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \
- && apt-get install -yqq --no-install-recommends \
- gcc-6-source libstdc++6-arm64-cross binutils-aarch64-linux-gnu \
- bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev \
+ && export CODENAME="$( lsb_release -c -s )" \
+ && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
+ && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
+ && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
+ && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
+ && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \
+ && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \
+ && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \
+ && echo "deb [arch=arm64] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/arm64.list \
+ && dpkg --add-architecture arm64 \
+ && apt-get update -yqq \
+ && apt-get install --no-install-recommends -yqq cross-gcc-dev \
+ && TARGET_LIST="arm64" cross-gcc-gensource 12 \
+ && cd cross-gcc-packages-amd64/cross-gcc-12-arm64 \
+ && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \
+ && apt-get install --no-install-recommends -yqq \
+ gcc-12-source libstdc++6-arm64-cross binutils-aarch64-linux-gnu \
+ bison flex libtool gdb sharutils netbase libmpc-dev \
libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev \
zip libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 libcurl4-openssl-dev:arm64 \
- libfontconfig1-dev:arm64 libfreetype6-dev:arm64 liblttng-ust0:arm64 libstdc++6:arm64 libssl-dev:arm64
+ libfontconfig1-dev:arm64 libfreetype6-dev:arm64 liblttng-ust1:arm64 libstdc++6:arm64 libssl-dev:arm64 \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.ubuntu.arm64 /build.sh
diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf
index e52b7fba35..83fe32acf8 100644
--- a/deployment/Dockerfile.ubuntu.armhf
+++ b/deployment/Dockerfile.ubuntu.armhf
@@ -1,7 +1,11 @@
-FROM ubuntu:bionic
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-jammy
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -11,39 +15,36 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg wget ca-certificates devscripts \
mmv build-essential lsb-release
-# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5226a5fa-8c0b-474f-b79a-8984ad7c5beb/3113ccbf789c9fd29972835f0f334b7a/dotnet-sdk-8.0.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
- && mkdir -p dotnet-sdk \
- && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
- && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-
# Prepare the cross-toolchain
RUN rm /etc/apt/sources.list \
- && export CODENAME="$( lsb_release -c -s )" \
- && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
- && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
- && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
- && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
- && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \
- && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \
- && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \
- && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \
- && dpkg --add-architecture armhf \
- && apt-get update -yqq \
- && apt-get install -yqq cross-gcc-dev \
- && TARGET_LIST="armhf" cross-gcc-gensource 6 \
- && cd cross-gcc-packages-amd64/cross-gcc-6-armhf \
- && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \
- && apt-get install -yqq --no-install-recommends \
- gcc-6-source libstdc++6-armhf-cross binutils-arm-linux-gnueabihf \
- bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev \
+ && export CODENAME="$( lsb_release -c -s )" \
+ && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
+ && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
+ && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
+ && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
+ && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \
+ && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \
+ && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \
+ && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \
+ && dpkg --add-architecture armhf \
+ && apt-get update -yqq \
+ && apt-get install --no-install-recommends -yqq cross-gcc-dev \
+ && TARGET_LIST="armhf" cross-gcc-gensource 12 \
+ && cd cross-gcc-packages-amd64/cross-gcc-12-armhf \
+ && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \
+ && apt-get install --no-install-recommends -yqq \
+ gcc-12-source libstdc++6-armhf-cross binutils-arm-linux-gnueabihf \
+ bison flex libtool gdb sharutils netbase libmpc-dev \
libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev \
zip libc6-dev:armhf linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf \
- libfontconfig1-dev:armhf libfreetype6-dev:armhf liblttng-ust0:armhf libstdc++6:armhf libssl-dev:armhf
+ libfontconfig1-dev:armhf libfreetype6-dev:armhf liblttng-ust1:armhf libstdc++6:armhf libssl-dev:armhf \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.armhf /build.sh
diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64
index 08587aa7eb..358fb620ac 100644
--- a/deployment/Dockerfile.windows.amd64
+++ b/deployment/Dockerfile.windows.amd64
@@ -1,7 +1,11 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim
+ARG DOTNET_VERSION=8.0
+
+FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-bookworm-slim
+
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
+
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -10,10 +14,13 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update -yqq \
- && apt-get install -yqq --no-install-recommends \
+ && apt-get install --no-install-recommends -yqq \
debhelper gnupg devscripts unzip \
mmv libcurl4-openssl-dev libfontconfig1-dev \
- libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 zip
+ libfreetype6-dev libssl-dev libssl3 liblttng-ust1 zip \
+ && apt-get clean autoclean -yqq \
+ && apt-get autoremove -yqq \
+ && rm -rf /var/lib/apt/lists/*
# Link to docker-build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.windows.amd64 /build.sh
diff --git a/deployment/build.centos.amd64 b/deployment/build.centos.amd64
index 0374624d80..26be377f10 100755
--- a/deployment/build.centos.amd64
+++ b/deployment/build.centos.amd64
@@ -1,16 +1,16 @@
#!/bin/bash
-#= CentOS/RHEL 7+ amd64 .rpm
+#= CentOS/RHEL 9+ amd64 .rpm
set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
+pushd "${SOURCE_DIR}"
if [[ ${IS_DOCKER} == YES ]]; then
# Remove BuildRequires for dotnet, since it's installed manually
- pushd fedora
+ pushd centos
cp -a jellyfin.spec /tmp/spec.orig
sed -i 's/BuildRequires: dotnet/# BuildRequires: dotnet/' jellyfin.spec
@@ -20,7 +20,7 @@ fi
# Modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
- pushd fedora
+ pushd centos
PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
@@ -35,23 +35,23 @@ EOF
fi
# Build RPM
-make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
+make -f centos/Makefile srpm outdir=/root/rpmbuild/SRPMS
rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
# Move the artifacts out
-mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/
+mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
-rm -f fedora/jellyfin*.tar.gz
+rm -f centos/jellyfin*.tar.gz
if [[ ${IS_DOCKER} == YES ]]; then
- pushd fedora
+ pushd centos
cp -a /tmp/spec.orig jellyfin.spec
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
popd
fi
diff --git a/deployment/build.debian.amd64 b/deployment/build.debian.amd64
index 7e968192bb..350b22a854 100755
--- a/deployment/build.debian.amd64
+++ b/deployment/build.debian.amd64
@@ -1,18 +1,12 @@
#!/bin/bash
-#= Debian 10+ amd64 .deb
+#= Debian 12+ amd64 .deb
set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
-
-if [[ ${IS_DOCKER} == YES ]]; then
- # Remove build-dep for dotnet-sdk-8.0, since it's installed manually
- cp -a debian/control /tmp/control.orig
- sed -i '/dotnet-sdk-8.0,/d' debian/control
-fi
+pushd "${SOURCE_DIR}"
# Modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -32,12 +26,12 @@ fi
# Build DEB
dpkg-buildpackage -us -uc --pre-clean --post-clean
-mkdir -p ${ARTIFACT_DIR}/
-mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
cp -a /tmp/control.orig debian/control
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.debian.arm64 b/deployment/build.debian.arm64
index 7b7b603d63..0dfca0ab49 100755
--- a/deployment/build.debian.arm64
+++ b/deployment/build.debian.arm64
@@ -1,18 +1,12 @@
#!/bin/bash
-#= Debian 10+ arm64 .deb
+#= Debian 12+ arm64 .deb
set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
-
-if [[ ${IS_DOCKER} == YES ]]; then
- # Remove build-dep for dotnet-sdk-8.0, since it's installed manually
- cp -a debian/control /tmp/control.orig
- sed -i '/dotnet-sdk-8.0,/d' debian/control
-fi
+pushd "${SOURCE_DIR}"
# Modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -33,12 +27,12 @@ fi
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
dpkg-buildpackage -us -uc -a arm64 --pre-clean --post-clean
-mkdir -p ${ARTIFACT_DIR}/
-mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
cp -a /tmp/control.orig debian/control
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.debian.armhf b/deployment/build.debian.armhf
index 3d894ba20e..0ab9e2f9a4 100755
--- a/deployment/build.debian.armhf
+++ b/deployment/build.debian.armhf
@@ -1,18 +1,12 @@
#!/bin/bash
-#= Debian 10+ arm64 .deb
+#= Debian 12+ arm64 .deb
set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
-
-if [[ ${IS_DOCKER} == YES ]]; then
- # Remove build-dep for dotnet-sdk-8.0, since it's installed manually
- cp -a debian/control /tmp/control.orig
- sed -i '/dotnet-sdk-8.0,/d' debian/control
-fi
+pushd "${SOURCE_DIR}"
# Modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -33,12 +27,12 @@ fi
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
dpkg-buildpackage -us -uc -a armhf --pre-clean --post-clean
-mkdir -p ${ARTIFACT_DIR}/
-mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
cp -a /tmp/control.orig debian/control
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.fedora.amd64 b/deployment/build.fedora.amd64
index 1b629289f0..2b4ec2a9c5 100755
--- a/deployment/build.fedora.amd64
+++ b/deployment/build.fedora.amd64
@@ -1,12 +1,12 @@
#!/bin/bash
-#= Fedora 29+ amd64 .rpm
+#= Fedora 39+ amd64 .rpm
set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
+pushd "${SOURCE_DIR}"
if [[ ${IS_DOCKER} == YES ]]; then
# Remove BuildRequires for dotnet, since it's installed manually
@@ -39,10 +39,10 @@ make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
# Move the artifacts out
-mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/
+mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
rm -f fedora/jellyfin*.tar.gz
@@ -51,7 +51,7 @@ if [[ ${IS_DOCKER} == YES ]]; then
pushd fedora
cp -a /tmp/spec.orig jellyfin.spec
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
popd
fi
diff --git a/deployment/build.linux.amd64 b/deployment/build.linux.amd64
index 05059e4edf..2998d2f9e3 100755
--- a/deployment/build.linux.amd64
+++ b/deployment/build.linux.amd64
@@ -6,7 +6,7 @@ set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
+pushd "${SOURCE_DIR}"
# Get version
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -16,16 +16,16 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
-tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
-rm -rf dist/jellyfin-server_${version}
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
+tar -czf jellyfin-server_"${version}"_linux-amd64.tar.gz -C dist jellyfin-server_"${version}"
+rm -rf dist/jellyfin-server_"${version}"
# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.linux.amd64-musl b/deployment/build.linux.amd64-musl
index 0ee4b05fb3..0fa1764650 100755
--- a/deployment/build.linux.amd64-musl
+++ b/deployment/build.linux.amd64-musl
@@ -6,7 +6,7 @@ set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
+pushd "${SOURCE_DIR}"
# Get version
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -16,16 +16,16 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
-tar -czf jellyfin-server_${version}_linux-amd64-musl.tar.gz -C dist jellyfin-server_${version}
-rm -rf dist/jellyfin-server_${version}
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-x64 --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
+tar -czf jellyfin-server_"${version}"_linux-amd64-musl.tar.gz -C dist jellyfin-server_"${version}"
+rm -rf dist/jellyfin-server_"${version}"
# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.linux.arm64 b/deployment/build.linux.arm64
index 6e36db0eb8..dc44ca330d 100755
--- a/deployment/build.linux.arm64
+++ b/deployment/build.linux.arm64
@@ -6,7 +6,7 @@ set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
+pushd "${SOURCE_DIR}"
# Get version
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -16,16 +16,16 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
-tar -czf jellyfin-server_${version}_linux-arm64.tar.gz -C dist jellyfin-server_${version}
-rm -rf dist/jellyfin-server_${version}
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm64 --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
+tar -czf jellyfin-server_"${version}"_linux-arm64.tar.gz -C dist jellyfin-server_"${version}"
+rm -rf dist/jellyfin-server_"${version}"
# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.linux.armhf b/deployment/build.linux.armhf
index f83eeebf1c..f9de9ff0a3 100755
--- a/deployment/build.linux.armhf
+++ b/deployment/build.linux.armhf
@@ -6,7 +6,7 @@ set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
+pushd "${SOURCE_DIR}"
# Get version
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -16,16 +16,16 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
-tar -czf jellyfin-server_${version}_linux-armhf.tar.gz -C dist jellyfin-server_${version}
-rm -rf dist/jellyfin-server_${version}
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
+tar -czf jellyfin-server_"${version}"_linux-armhf.tar.gz -C dist jellyfin-server_"${version}"
+rm -rf dist/jellyfin-server_"${version}"
# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.linux.musl-linux-arm64 b/deployment/build.linux.musl-linux-arm64
index 38826ae7fc..ae9ab010f5 100755
--- a/deployment/build.linux.musl-linux-arm64
+++ b/deployment/build.linux.musl-linux-arm64
@@ -6,7 +6,7 @@ set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
+pushd "${SOURCE_DIR}"
# Get version
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -16,16 +16,16 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-arm64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
-tar -czf jellyfin-server_${version}_linux-arm64-musl.tar.gz -C dist jellyfin-server_${version}
-rm -rf dist/jellyfin-server_${version}
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-arm64 --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
+tar -czf jellyfin-server_"${version}"_linux-arm64-musl.tar.gz -C dist jellyfin-server_"${version}"
+rm -rf dist/jellyfin-server_"${version}"
# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.macos.amd64 b/deployment/build.macos.amd64
index eac3538771..81e0f43f62 100755
--- a/deployment/build.macos.amd64
+++ b/deployment/build.macos.amd64
@@ -6,7 +6,7 @@ set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
+pushd "${SOURCE_DIR}"
# Get version
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -16,16 +16,16 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
-tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version}
-rm -rf dist/jellyfin-server_${version}
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
+tar -czf jellyfin-server_"${version}"_macos-amd64.tar.gz -C dist jellyfin-server_"${version}"
+rm -rf dist/jellyfin-server_"${version}"
# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.macos.arm64 b/deployment/build.macos.arm64
index 42da07e2f6..0a6f37edea 100755
--- a/deployment/build.macos.arm64
+++ b/deployment/build.macos.arm64
@@ -6,7 +6,7 @@ set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
+pushd "${SOURCE_DIR}"
# Get version
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -16,16 +16,16 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-arm64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
-tar -czf jellyfin-server_${version}_macos-arm64.tar.gz -C dist jellyfin-server_${version}
-rm -rf dist/jellyfin-server_${version}
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-arm64 --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
+tar -czf jellyfin-server_"${version}"_macos-arm64.tar.gz -C dist jellyfin-server_"${version}"
+rm -rf dist/jellyfin-server_"${version}"
# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.portable b/deployment/build.portable
index 27e5e987fe..fad14fccfc 100755
--- a/deployment/build.portable
+++ b/deployment/build.portable
@@ -6,7 +6,7 @@ set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
+pushd "${SOURCE_DIR}"
# Get version
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -16,16 +16,16 @@ else
fi
# Build archives
-dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=false
-tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
-rm -rf dist/jellyfin-server_${version}
+dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_"${version}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=false
+tar -czf jellyfin-server_"${version}"_portable.tar.gz -C dist jellyfin-server_"${version}"
+rm -rf dist/jellyfin-server_"${version}"
# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv jellyfin[-_]*.tar.gz "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.ubuntu.amd64 b/deployment/build.ubuntu.amd64
index 5f25cb610f..6fd87a3aec 100755
--- a/deployment/build.ubuntu.amd64
+++ b/deployment/build.ubuntu.amd64
@@ -1,18 +1,12 @@
#!/bin/bash
-#= Ubuntu 18.04+ amd64 .deb
+#= Ubuntu 22.04+ amd64 .deb
set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
-
-if [[ ${IS_DOCKER} == YES ]]; then
- # Remove build-dep for dotnet-sdk-8.0, since it's installed manually
- cp -a debian/control /tmp/control.orig
- sed -i '/dotnet-sdk-8.0,/d' debian/control
-fi
+pushd "${SOURCE_DIR}"
# Modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -32,12 +26,12 @@ fi
# Build DEB
dpkg-buildpackage -us -uc --pre-clean --post-clean
-mkdir -p ${ARTIFACT_DIR}/
-mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
cp -a /tmp/control.orig debian/control
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.ubuntu.arm64 b/deployment/build.ubuntu.arm64
index 334ced9970..f783941c73 100755
--- a/deployment/build.ubuntu.arm64
+++ b/deployment/build.ubuntu.arm64
@@ -1,18 +1,12 @@
#!/bin/bash
-#= Ubuntu 18.04+ arm64 .deb
+#= Ubuntu 22.04+ arm64 .deb
set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
-
-if [[ ${IS_DOCKER} == YES ]]; then
- # Remove build-dep for dotnet-sdk-8.0, since it's installed manually
- cp -a debian/control /tmp/control.orig
- sed -i '/dotnet-sdk-8.0,/d' debian/control
-fi
+pushd "${SOURCE_DIR}"
# Modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -33,12 +27,12 @@ fi
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
dpkg-buildpackage -us -uc -a arm64 --pre-clean --post-clean
-mkdir -p ${ARTIFACT_DIR}/
-mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
cp -a /tmp/control.orig debian/control
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.ubuntu.armhf b/deployment/build.ubuntu.armhf
index 77e33c3071..cde6708c5b 100755
--- a/deployment/build.ubuntu.armhf
+++ b/deployment/build.ubuntu.armhf
@@ -1,18 +1,12 @@
#!/bin/bash
-#= Ubuntu 18.04+ arm64 .deb
+#= Ubuntu 22.04+ arm64 .deb
set -o errexit
set -o xtrace
# Move to source directory
-pushd ${SOURCE_DIR}
-
-if [[ ${IS_DOCKER} == YES ]]; then
- # Remove build-dep for dotnet-sdk-8.0, since it's installed manually
- cp -a debian/control /tmp/control.orig
- sed -i '/dotnet-sdk-8.0,/d' debian/control
-fi
+pushd "${SOURCE_DIR}"
# Modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -33,12 +27,12 @@ fi
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
dpkg-buildpackage -us -uc -a armhf --pre-clean --post-clean
-mkdir -p ${ARTIFACT_DIR}/
-mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
cp -a /tmp/control.orig debian/control
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64
index 0786358bd5..cd07f4e0b2 100755
--- a/deployment/build.windows.amd64
+++ b/deployment/build.windows.amd64
@@ -11,7 +11,7 @@ NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip"
FFMPEG_URL="https://repo.jellyfin.org/releases/server/windows/ffmpeg/jellyfin-ffmpeg-portable_win64.zip";
# Move to source directory
-pushd ${SOURCE_DIR}
+pushd "${SOURCE_DIR}"
# Get version
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
@@ -23,30 +23,30 @@ fi
output_dir="dist/jellyfin-server_${version}"
# Build binary
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output "${output_dir}"/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
# Prepare addins
addin_build_dir="$( mktemp -d )"
-wget ${NSSM_URL} -O ${addin_build_dir}/nssm.zip
-wget ${FFMPEG_URL} -O ${addin_build_dir}/jellyfin-ffmpeg.zip
-unzip ${addin_build_dir}/nssm.zip -d ${addin_build_dir}
-cp ${addin_build_dir}/${NSSM_VERSION}/win64/nssm.exe ${output_dir}/nssm.exe
-unzip ${addin_build_dir}/jellyfin-ffmpeg.zip -d ${addin_build_dir}/jellyfin-ffmpeg
-cp ${addin_build_dir}/jellyfin-ffmpeg/* ${output_dir}
-rm -rf ${addin_build_dir}
+wget ${NSSM_URL} -O "${addin_build_dir}"/nssm.zip
+wget ${FFMPEG_URL} -O "${addin_build_dir}"/jellyfin-ffmpeg.zip
+unzip "${addin_build_dir}"/nssm.zip -d "${addin_build_dir}"
+cp "${addin_build_dir}"/${NSSM_VERSION}/win64/nssm.exe "${output_dir}"/nssm.exe
+unzip "${addin_build_dir}"/jellyfin-ffmpeg.zip -d "${addin_build_dir}"/jellyfin-ffmpeg
+cp "${addin_build_dir}"/jellyfin-ffmpeg/* "${output_dir}"
+rm -rf "${addin_build_dir}"
# Create zip package
pushd dist
-zip -qr jellyfin-server_${version}.portable.zip jellyfin-server_${version}
+zip -qr jellyfin-server_"${version}".portable.zip jellyfin-server_"${version}"
popd
-rm -rf ${output_dir}
+rm -rf "${output_dir}"
# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv dist/jellyfin[-_]*.zip ${ARTIFACT_DIR}/
+mkdir -p "${ARTIFACT_DIR}/"
+mv dist/jellyfin[-_]*.zip "${ARTIFACT_DIR}/"
if [[ ${IS_DOCKER} == YES ]]; then
- chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+ chown -Rc "$(stat -c %u:%g "${ARTIFACT_DIR}")" "${ARTIFACT_DIR}"
fi
popd
diff --git a/fedora/README.md b/fedora/README.md
index d449b51c16..6ea87740f2 100644
--- a/fedora/README.md
+++ b/fedora/README.md
@@ -14,8 +14,10 @@ The RPM package for Fedora/CentOS requires some additional repositories as ffmpe
# ffmpeg from RPMfusion free
# Fedora
$ sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
-# CentOS 7
-$ sudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm
+# CentOS 8
+$ sudo dnf localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm
+# CentOS 9
+$ sudo dnf localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-9.noarch.rpm
```
## Building with dotnet
@@ -26,8 +28,10 @@ Jellyfin is build with `--self-contained` so no dotnet required for runtime.
# dotnet required for building the RPM
# Fedora
$ sudo dnf copr enable @dotnet-sig/dotnet
-# CentOS
-$ sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm
+# CentOS 8
+$ sudo rpm -Uvh https://packages.microsoft.com/config/rhel/8/packages-microsoft-prod.rpm
+# CentOS 9
+$ sudo rpm -Uvh https://packages.microsoft.com/config/rhel/9/packages-microsoft-prod.rpm
```
## TODO
diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec
index fb9fb2f7da..5327495ad3 100644
--- a/fedora/jellyfin.spec
+++ b/fedora/jellyfin.spec
@@ -1,10 +1,4 @@
%global debug_package %{nil}
-# Set the dotnet runtime
-%if 0%{?fedora}
-%global dotnet_runtime fedora.%{fedora}-x64
-%else
-%global dotnet_runtime centos-x64
-%endif
Name: jellyfin
Version: 10.9.0
@@ -29,12 +23,6 @@ BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel,
BuildRequires: dotnet-runtime-8.0, dotnet-sdk-8.0
Requires: %{name}-server = %{version}-%{release}, %{name}-web = %{version}-%{release}
-# Temporary (hopefully?) fix for https://github.com/jellyfin/jellyfin/issues/7471
-%if 0%{?fedora} >= 36
-%global __requires_exclude ^liblttng-ust\\.so\\.0.*$
-%endif
-
-
%description
Jellyfin is a free software media system that puts you in control of managing and streaming your media.
@@ -66,14 +54,14 @@ the Jellyfin server to bind to ports 80 and/or 443 for example.
export DOTNET_CLI_TELEMETRY_OPTOUT=1
export PATH=$PATH:/usr/local/bin
# cannot use --output due to https://github.com/dotnet/sdk/issues/22220
-dotnet publish --configuration Release --self-contained --runtime %{dotnet_runtime} \
+dotnet publish --configuration Release --self-contained --runtime linux-x64 \
-p:DebugSymbols=false -p:DebugType=none Jellyfin.Server
%install
# Jellyfin files
%{__mkdir} -p %{buildroot}%{_libdir}/jellyfin %{buildroot}%{_bindir}
-%{__cp} -r Jellyfin.Server/bin/Release/net8.0/%{dotnet_runtime}/publish/* %{buildroot}%{_libdir}/jellyfin
+%{__cp} -r Jellyfin.Server/bin/Release/net8.0/linux-x64/publish/* %{buildroot}%{_libdir}/jellyfin
%{__install} -D %{SOURCE10} %{buildroot}%{_bindir}/jellyfin
sed -i -e 's|/usr/lib64|%{_libdir}|g' %{buildroot}%{_bindir}/jellyfin
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 0000000000..54e660f9c9
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Jellyfin.Extensions/GuidExtensions.cs b/src/Jellyfin.Extensions/GuidExtensions.cs
new file mode 100644
index 0000000000..95c591a82e
--- /dev/null
+++ b/src/Jellyfin.Extensions/GuidExtensions.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Jellyfin.Extensions;
+
+///
+/// Guid specific extensions.
+///
+public static class GuidExtensions
+{
+ ///
+ /// Determine whether the guid is default.
+ ///
+ /// The guid.
+ /// Whether the guid is the default value.
+ public static bool IsEmpty(this Guid guid)
+ => guid.Equals(default);
+
+ ///
+ /// Determine whether the guid is null or default.
+ ///
+ /// The guid.
+ /// Whether the guid is null or the default valueF.
+ public static bool IsNullOrEmpty([NotNullWhen(false)] this Guid? guid)
+ => guid is null || guid.Value.IsEmpty();
+}
diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs
index 656e3c3dab..0a50b5c3b9 100644
--- a/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs
+++ b/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs
@@ -18,7 +18,7 @@ namespace Jellyfin.Extensions.Json.Converters
{
// null got handled higher up the call stack
var val = value!.Value;
- if (val.Equals(default))
+ if (val.IsEmpty())
{
writer.WriteNullValue();
}
diff --git a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/src/Jellyfin.LiveTv/Channels/ChannelDynamicMediaSourceProvider.cs
similarity index 96%
rename from Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
rename to src/Jellyfin.LiveTv/Channels/ChannelDynamicMediaSourceProvider.cs
index 3e149cc82c..839549ed67 100644
--- a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
+++ b/src/Jellyfin.LiveTv/Channels/ChannelDynamicMediaSourceProvider.cs
@@ -8,7 +8,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
-namespace Emby.Server.Implementations.Channels
+namespace Jellyfin.LiveTv.Channels
{
///
/// A media source provider for channels.
diff --git a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs b/src/Jellyfin.LiveTv/Channels/ChannelImageProvider.cs
similarity index 97%
rename from Emby.Server.Implementations/Channels/ChannelImageProvider.cs
rename to src/Jellyfin.LiveTv/Channels/ChannelImageProvider.cs
index 25cbfcf146..32e224550f 100644
--- a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
+++ b/src/Jellyfin.LiveTv/Channels/ChannelImageProvider.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-namespace Emby.Server.Implementations.Channels
+namespace Jellyfin.LiveTv.Channels
{
///
/// An image provider for channels.
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs
similarity index 94%
rename from Emby.Server.Implementations/Channels/ChannelManager.cs
rename to src/Jellyfin.LiveTv/Channels/ChannelManager.cs
index 8279acb058..bc968f8ee1 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/src/Jellyfin.LiveTv/Channels/ChannelManager.cs
@@ -34,7 +34,7 @@ using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Season = MediaBrowser.Controller.Entities.TV.Season;
using Series = MediaBrowser.Controller.Entities.TV.Series;
-namespace Emby.Server.Implementations.Channels
+namespace Jellyfin.LiveTv.Channels
{
///
/// The LiveTV channel manager.
@@ -113,15 +113,6 @@ namespace Emby.Server.Implementations.Channels
return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
}
- ///
- public bool EnableMediaProbe(BaseItem item)
- {
- var internalChannel = _libraryManager.GetItemById(item.ChannelId);
- var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
-
- return channel is ISupportsMediaProbe;
- }
-
///
public Task DeleteItem(BaseItem item)
{
@@ -159,7 +150,7 @@ namespace Emby.Server.Implementations.Channels
///
public async Task> GetChannelsInternalAsync(ChannelQuery query)
{
- var user = query.UserId.Equals(default)
+ var user = query.UserId.IsEmpty()
? null
: _userManager.GetUserById(query.UserId);
@@ -272,7 +263,7 @@ namespace Emby.Server.Implementations.Channels
///
public async Task> GetChannelsAsync(ChannelQuery query)
{
- var user = query.UserId.Equals(default)
+ var user = query.UserId.IsEmpty()
? null
: _userManager.GetUserById(query.UserId);
@@ -562,18 +553,6 @@ namespace Emby.Server.Implementations.Channels
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
}
- ///
- /// Checks whether the provided Guid supports external transfer.
- ///
- /// The Guid.
- /// Whether or not the provided Guid supports external transfer.
- public bool SupportsExternalTransfer(Guid channelId)
- {
- var channelProvider = GetChannelProvider(channelId);
-
- return channelProvider.GetChannelFeatures().SupportsContentDownloading;
- }
-
///
/// Gets the provided channel's supported features.
///
@@ -716,7 +695,7 @@ namespace Emby.Server.Implementations.Channels
// Find the corresponding channel provider plugin
var channelProvider = GetChannelProvider(channel);
- var parentItem = query.ParentId.Equals(default)
+ var parentItem = query.ParentId.IsEmpty()
? channel
: _libraryManager.GetItemById(query.ParentId);
@@ -729,7 +708,7 @@ namespace Emby.Server.Implementations.Channels
cancellationToken)
.ConfigureAwait(false);
- if (query.ParentId.Equals(default))
+ if (query.ParentId.IsEmpty())
{
query.Parent = channel;
}
@@ -812,11 +791,16 @@ namespace Emby.Server.Implementations.Channels
{
if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
{
- await using FileStream jsonStream = AsyncFile.OpenRead(cachePath);
- var cachedResult = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- if (cachedResult is not null)
+ var jsonStream = AsyncFile.OpenRead(cachePath);
+ await using (jsonStream.ConfigureAwait(false))
{
- return null;
+ var cachedResult = await JsonSerializer
+ .DeserializeAsync(jsonStream, _jsonOptions, cancellationToken)
+ .ConfigureAwait(false);
+ if (cachedResult is not null)
+ {
+ return null;
+ }
}
}
}
@@ -835,11 +819,16 @@ namespace Emby.Server.Implementations.Channels
{
if (_fileSystem.GetLastWriteTimeUtc(cachePath).Add(cacheLength) > DateTime.UtcNow)
{
- await using FileStream jsonStream = AsyncFile.OpenRead(cachePath);
- var cachedResult = await JsonSerializer.DeserializeAsync(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- if (cachedResult is not null)
+ var jsonStream = AsyncFile.OpenRead(cachePath);
+ await using (jsonStream.ConfigureAwait(false))
{
- return null;
+ var cachedResult = await JsonSerializer
+ .DeserializeAsync(jsonStream, _jsonOptions, cancellationToken)
+ .ConfigureAwait(false);
+ if (cachedResult is not null)
+ {
+ return null;
+ }
}
}
}
@@ -867,7 +856,7 @@ namespace Emby.Server.Implementations.Channels
throw new InvalidOperationException("Channel returned a null result from GetChannelItems");
}
- await CacheResponse(result, cachePath);
+ await CacheResponse(result, cachePath).ConfigureAwait(false);
return result;
}
@@ -883,8 +872,11 @@ namespace Emby.Server.Implementations.Channels
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
- await using FileStream createStream = File.Create(path);
- await JsonSerializer.SerializeAsync(createStream, result, _jsonOptions).ConfigureAwait(false);
+ var createStream = File.Create(path);
+ await using (createStream.ConfigureAwait(false))
+ {
+ await JsonSerializer.SerializeAsync(createStream, result, _jsonOptions).ConfigureAwait(false);
+ }
}
catch (Exception ex)
{
@@ -1202,19 +1194,6 @@ namespace Emby.Server.Implementations.Channels
return result;
}
- internal IChannel GetChannelProvider(Guid internalChannelId)
- {
- var result = GetAllChannels()
- .FirstOrDefault(i => internalChannelId.Equals(GetInternalChannelId(i.Name)));
-
- if (result is null)
- {
- throw new ResourceNotFoundException("No channel provider found for channel id " + internalChannelId);
- }
-
- return result;
- }
-
///
public void Dispose()
{
diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/src/Jellyfin.LiveTv/Channels/ChannelPostScanTask.cs
similarity index 98%
rename from Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
rename to src/Jellyfin.LiveTv/Channels/ChannelPostScanTask.cs
index b358ba4d55..b4f6cf731e 100644
--- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
+++ b/src/Jellyfin.LiveTv/Channels/ChannelPostScanTask.cs
@@ -8,7 +8,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.Channels
+namespace Jellyfin.LiveTv.Channels
{
///
/// A task to remove all non-installed channels from the database.
diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/src/Jellyfin.LiveTv/Channels/RefreshChannelsScheduledTask.cs
similarity index 98%
rename from Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
rename to src/Jellyfin.LiveTv/Channels/RefreshChannelsScheduledTask.cs
index cfd08e6535..556e052d4e 100644
--- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
+++ b/src/Jellyfin.LiveTv/Channels/RefreshChannelsScheduledTask.cs
@@ -9,7 +9,7 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.Channels
+namespace Jellyfin.LiveTv.Channels
{
///
/// The "Refresh Channels" scheduled task.
diff --git a/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs b/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs
new file mode 100644
index 0000000000..67d0e5295b
--- /dev/null
+++ b/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationExtensions.cs
@@ -0,0 +1,18 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.LiveTv;
+
+namespace Jellyfin.LiveTv.Configuration;
+
+///
+/// extensions for Live TV.
+///
+public static class LiveTvConfigurationExtensions
+{
+ ///
+ /// Gets the .
+ ///
+ /// The .
+ /// The .
+ public static LiveTvOptions GetLiveTvConfiguration(this IConfigurationManager configurationManager)
+ => configurationManager.GetConfiguration("livetv");
+}
diff --git a/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationFactory.cs b/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationFactory.cs
new file mode 100644
index 0000000000..258afbb056
--- /dev/null
+++ b/src/Jellyfin.LiveTv/Configuration/LiveTvConfigurationFactory.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.LiveTv;
+
+namespace Jellyfin.LiveTv.Configuration;
+
+///
+/// implementation for .
+///
+public class LiveTvConfigurationFactory : IConfigurationFactory
+{
+ ///
+ public IEnumerable GetConfigurations()
+ {
+ return new[]
+ {
+ new ConfigurationStore
+ {
+ ConfigurationType = typeof(LiveTvOptions),
+ Key = "livetv"
+ }
+ };
+ }
+}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/src/Jellyfin.LiveTv/EmbyTV/DirectRecorder.cs
similarity index 68%
rename from Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
rename to src/Jellyfin.LiveTv/EmbyTV/DirectRecorder.cs
index 49833de737..2a25218b63 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/src/Jellyfin.LiveTv/EmbyTV/DirectRecorder.cs
@@ -5,16 +5,16 @@ using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv.EmbyTV
{
- public class DirectRecorder : IRecorder
+ public sealed class DirectRecorder : IRecorder
{
private readonly ILogger _logger;
private readonly IHttpClientFactory _httpClientFactory;
@@ -46,7 +46,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
- await using (var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
+ var output = new FileStream(
+ targetFile,
+ FileMode.CreateNew,
+ FileAccess.Write,
+ FileShare.Read,
+ IODefaults.FileStreamBufferSize,
+ FileOptions.Asynchronous);
+
+ await using (output.ConfigureAwait(false))
{
onStarted();
@@ -80,24 +88,31 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
- await using var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.CopyToBufferSize, FileOptions.Asynchronous);
+ var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.CopyToBufferSize, FileOptions.Asynchronous);
+ await using (output.ConfigureAwait(false))
+ {
+ onStarted();
- onStarted();
+ _logger.LogInformation("Copying recording stream to file {0}", targetFile);
- _logger.LogInformation("Copying recording stream to file {0}", targetFile);
+ // The media source if infinite so we need to handle stopping ourselves
+ using var durationToken = new CancellationTokenSource(duration);
+ using var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
+ cancellationToken = linkedCancellationToken.Token;
- // The media source if infinite so we need to handle stopping ourselves
- using var durationToken = new CancellationTokenSource(duration);
- using var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
- cancellationToken = linkedCancellationToken.Token;
+ await _streamHelper.CopyUntilCancelled(
+ await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false),
+ output,
+ IODefaults.CopyToBufferSize,
+ cancellationToken).ConfigureAwait(false);
- await _streamHelper.CopyUntilCancelled(
- await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false),
- output,
- IODefaults.CopyToBufferSize,
- cancellationToken).ConfigureAwait(false);
+ _logger.LogInformation("Recording completed to file {0}", targetFile);
+ }
+ }
- _logger.LogInformation("Recording completed to file {0}", targetFile);
+ ///
+ public void Dispose()
+ {
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs
similarity index 93%
rename from Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
rename to src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs
index 74b62ca3f2..e7e927b2d0 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/src/Jellyfin.LiveTv/EmbyTV/EmbyTV.cs
@@ -14,14 +14,13 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
-using Emby.Server.Implementations.Library;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
using Jellyfin.Extensions;
+using Jellyfin.LiveTv.Configuration;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -37,18 +36,14 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv.EmbyTV
{
- public class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable
+ public sealed class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable
{
public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
- private const int TunerDiscoveryDurationMs = 3000;
-
- private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerConfigurationManager _config;
@@ -57,6 +52,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly TimerManager _timerProvider;
private readonly LiveTvManager _liveTvManager;
+ private readonly ITunerHostManager _tunerHostManager;
private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _libraryMonitor;
@@ -74,16 +70,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1);
- private bool _disposed = false;
+ private bool _disposed;
public EmbyTV(
- IServerApplicationHost appHost,
IStreamHelper streamHelper,
IMediaSourceManager mediaSourceManager,
ILogger logger,
IHttpClientFactory httpClientFactory,
IServerConfigurationManager config,
ILiveTvManager liveTvManager,
+ ITunerHostManager tunerHostManager,
IFileSystem fileSystem,
ILibraryManager libraryManager,
ILibraryMonitor libraryMonitor,
@@ -92,7 +88,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Current = this;
- _appHost = appHost;
_logger = logger;
_httpClientFactory = httpClientFactory;
_config = config;
@@ -102,6 +97,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_providerManager = providerManager;
_mediaEncoder = mediaEncoder;
_liveTvManager = (LiveTvManager)liveTvManager;
+ _tunerHostManager = tunerHostManager;
_mediaSourceManager = mediaSourceManager;
_streamHelper = streamHelper;
@@ -132,7 +128,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
get
{
- var path = GetConfiguration().RecordingPath;
+ var path = _config.GetLiveTvConfiguration().RecordingPath;
return string.IsNullOrWhiteSpace(path)
? DefaultRecordingPath
@@ -195,7 +191,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
pathsAdded.AddRange(pathsToCreate);
}
- var config = GetConfiguration();
+ var config = _config.GetLiveTvConfiguration();
var pathsToRemove = config.MediaLocationsCreated
.Except(recordingFolders.SelectMany(i => i.Locations))
@@ -315,7 +311,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var list = new List();
- foreach (var hostInstance in _liveTvManager.TunerHosts)
+ foreach (var hostInstance in _tunerHostManager.TunerHosts)
{
try
{
@@ -515,7 +511,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var list = new List();
- foreach (var hostInstance in _liveTvManager.TunerHosts)
+ foreach (var hostInstance in _tunerHostManager.TunerHosts)
{
try
{
@@ -837,7 +833,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public Task GetNewTimerDefaultsAsync(CancellationToken cancellationToken, ProgramInfo program = null)
{
- var config = GetConfiguration();
+ var config = _config.GetLiveTvConfiguration();
var defaults = new SeriesTimerInfo()
{
@@ -938,7 +934,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private List> GetListingProviders()
{
- return GetConfiguration().ListingProviders
+ return _config.GetLiveTvConfiguration().ListingProviders
.Select(i =>
{
var provider = _liveTvManager.ListingProviders.FirstOrDefault(l => string.Equals(l.Type, i.Type, StringComparison.OrdinalIgnoreCase));
@@ -971,7 +967,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return result;
}
- foreach (var hostInstance in _liveTvManager.TunerHosts)
+ foreach (var hostInstance in _tunerHostManager.TunerHosts)
{
try
{
@@ -1003,7 +999,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new ArgumentNullException(nameof(channelId));
}
- foreach (var hostInstance in _liveTvManager.TunerHosts)
+ foreach (var hostInstance in _tunerHostManager.TunerHosts)
{
try
{
@@ -1022,45 +1018,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new NotImplementedException();
}
- public async Task> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken)
- {
- var stream = new MediaSourceInfo
- {
- EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
- EncoderProtocol = MediaProtocol.Http,
- Path = info.Path,
- Protocol = MediaProtocol.File,
- Id = info.Id,
- SupportsDirectPlay = false,
- SupportsDirectStream = true,
- SupportsTranscoding = true,
- IsInfiniteStream = true,
- RequiresOpening = false,
- RequiresClosing = false,
- BufferMs = 0,
- IgnoreDts = true,
- IgnoreIndex = true
- };
-
- await new LiveStreamHelper(_mediaEncoder, _logger, _config.CommonApplicationPaths)
- .AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
-
- return new List
- {
- stream
- };
- }
-
public Task CloseLiveStream(string id, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
- public Task RecordLiveStream(string id, CancellationToken cancellationToken)
- {
- return Task.CompletedTask;
- }
-
public Task ResetTuner(string id, CancellationToken cancellationToken)
{
return Task.CompletedTask;
@@ -1111,7 +1073,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private string GetRecordingPath(TimerInfo timer, RemoteSearchResult metadata, out string seriesPath)
{
var recordPath = RecordingPath;
- var config = GetConfiguration();
+ var config = _config.GetLiveTvConfiguration();
seriesPath = null;
if (timer.IsProgramSeries)
@@ -1270,7 +1232,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
directStreamProvider = liveStreamResponse.Item2;
}
- var recorder = GetRecorder(mediaStreamInfo);
+ using var recorder = GetRecorder(mediaStreamInfo);
recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath);
recordPath = EnsureFileUnique(recordPath, timer.Id);
@@ -1631,7 +1593,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private void PostProcessRecording(TimerInfo timer, string path)
{
- var options = GetConfiguration();
+ var options = _config.GetLiveTvConfiguration();
if (string.IsNullOrWhiteSpace(options.RecordingPostProcessor))
{
return;
@@ -1812,7 +1774,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
program.AddGenre("News");
}
- var config = GetConfiguration();
+ var config = _config.GetLiveTvConfiguration();
if (config.SaveRecordingNFO)
{
@@ -2030,7 +1992,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
await writer.WriteElementStringAsync(null, "genre", null, genre).ConfigureAwait(false);
}
- var people = item.Id.Equals(default) ? new List() : _libraryManager.GetPeople(item);
+ var people = item.Id.IsEmpty() ? new List() : _libraryManager.GetPeople(item);
var directors = people
.Where(i => i.IsType(PersonKind.Director))
@@ -2163,11 +2125,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return _libraryManager.GetItemList(query).Cast().FirstOrDefault();
}
- private LiveTvOptions GetConfiguration()
- {
- return _config.GetConfiguration("livetv");
- }
-
private bool ShouldCancelTimerForSeriesTimer(SeriesTimerInfo seriesTimer, TimerInfo timer)
{
if (timer.IsManual)
@@ -2360,7 +2317,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
string channelId = seriesTimer.RecordAnyChannel ? null : seriesTimer.ChannelId;
- if (string.IsNullOrWhiteSpace(channelId) && !parent.ChannelId.Equals(default))
+ if (string.IsNullOrWhiteSpace(channelId) && !parent.ChannelId.IsEmpty())
{
if (!tempChannelCache.TryGetValue(parent.ChannelId, out LiveTvChannel channel))
{
@@ -2419,7 +2376,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
string channelId = null;
- if (!programInfo.ChannelId.Equals(default))
+ if (!programInfo.ChannelId.IsEmpty())
{
if (!tempChannelCache.TryGetValue(programInfo.ChannelId, out LiveTvChannel channel))
{
@@ -2524,22 +2481,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
///
public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
- if (disposing)
- {
- _recordingDeleteSemaphore.Dispose();
- }
+ _recordingDeleteSemaphore.Dispose();
foreach (var pair in _activeRecordings.ToList())
{
@@ -2563,7 +2511,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
};
}
- var customPath = GetConfiguration().MovieRecordingPath;
+ var customPath = _config.GetLiveTvConfiguration().MovieRecordingPath;
if (!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase) && Directory.Exists(customPath))
{
yield return new VirtualFolderInfo
@@ -2574,7 +2522,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
};
}
- customPath = GetConfiguration().SeriesRecordingPath;
+ customPath = _config.GetLiveTvConfiguration().SeriesRecordingPath;
if (!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase) && Directory.Exists(customPath))
{
yield return new VirtualFolderInfo
@@ -2585,81 +2533,5 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
};
}
}
-
- public async Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
- {
- var list = new List();
-
- var configuredDeviceIds = GetConfiguration().TunerHosts
- .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId))
- .Select(i => i.DeviceId)
- .ToList();
-
- foreach (var host in _liveTvManager.TunerHosts)
- {
- var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
-
- if (newDevicesOnly)
- {
- discoveredDevices = discoveredDevices.Where(d => !configuredDeviceIds.Contains(d.DeviceId, StringComparison.OrdinalIgnoreCase))
- .ToList();
- }
-
- list.AddRange(discoveredDevices);
- }
-
- return list;
- }
-
- public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken)
- {
- foreach (var host in _liveTvManager.TunerHosts)
- {
- await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false);
- }
- }
-
- private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken)
- {
- var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
-
- var configuredDevices = GetConfiguration().TunerHosts
- .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase))
- .ToList();
-
- foreach (var device in discoveredDevices)
- {
- var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase));
-
- if (configuredDevice is not null && !string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase))
- {
- _logger.LogInformation("Tuner url has changed from {PreviousUrl} to {NewUrl}", configuredDevice.Url, device.Url);
-
- configuredDevice.Url = device.Url;
- await _liveTvManager.SaveTunerHost(configuredDevice).ConfigureAwait(false);
- }
- }
- }
-
- private async Task> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken)
- {
- try
- {
- var discoveredDevices = await host.DiscoverDevices(discoveryDurationMs, cancellationToken).ConfigureAwait(false);
-
- foreach (var device in discoveredDevices)
- {
- _logger.LogInformation("Discovered tuner device {0} at {1}", host.Name, device.Url);
- }
-
- return discoveredDevices;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error discovering tuner devices");
-
- return new List();
- }
- }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/src/Jellyfin.LiveTv/EmbyTV/EncodedRecorder.cs
similarity index 97%
rename from Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
rename to src/Jellyfin.LiveTv/EmbyTV/EncodedRecorder.cs
index 5369c9b3d1..132a5fc516 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/src/Jellyfin.LiveTv/EmbyTV/EncodedRecorder.cs
@@ -23,9 +23,9 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv.EmbyTV
{
- public class EncodedRecorder : IRecorder, IDisposable
+ public class EncodedRecorder : IRecorder
{
private readonly ILogger _logger;
private readonly IMediaEncoder _mediaEncoder;
@@ -34,10 +34,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private bool _hasExited;
- private Stream _logFileStream;
+ private FileStream _logFileStream;
private string _targetPath;
private Process _process;
- private bool _disposed = false;
+ private bool _disposed;
public EncodedRecorder(
ILogger logger,
@@ -308,7 +308,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- private async Task StartStreamingLog(Stream source, Stream target)
+ private async Task StartStreamingLog(Stream source, FileStream target)
{
try
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs b/src/Jellyfin.LiveTv/EmbyTV/EntryPoint.cs
similarity index 87%
rename from Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs
rename to src/Jellyfin.LiveTv/EmbyTV/EntryPoint.cs
index a2ec2df375..e750c05ac8 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs
+++ b/src/Jellyfin.LiveTv/EmbyTV/EntryPoint.cs
@@ -3,7 +3,7 @@
using System.Threading.Tasks;
using MediaBrowser.Controller.Plugins;
-namespace Emby.Server.Implementations.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv.EmbyTV
{
public sealed class EntryPoint : IServerEntryPoint
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/src/Jellyfin.LiveTv/EmbyTV/EpgChannelData.cs
similarity index 97%
rename from Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
rename to src/Jellyfin.LiveTv/EmbyTV/EpgChannelData.cs
index 20a8213a77..43d308c434 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
+++ b/src/Jellyfin.LiveTv/EmbyTV/EpgChannelData.cs
@@ -4,7 +4,7 @@ using System;
using System.Collections.Generic;
using MediaBrowser.Controller.LiveTv;
-namespace Emby.Server.Implementations.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv.EmbyTV
{
internal class EpgChannelData
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/src/Jellyfin.LiveTv/EmbyTV/IRecorder.cs
similarity index 92%
rename from Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
rename to src/Jellyfin.LiveTv/EmbyTV/IRecorder.cs
index 7705132da2..7ed42e2634 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
+++ b/src/Jellyfin.LiveTv/EmbyTV/IRecorder.cs
@@ -6,9 +6,9 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
-namespace Emby.Server.Implementations.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv.EmbyTV
{
- public interface IRecorder
+ public interface IRecorder : IDisposable
{
///
/// Records the specified media source.
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/src/Jellyfin.LiveTv/EmbyTV/ItemDataProvider.cs
similarity index 98%
rename from Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
rename to src/Jellyfin.LiveTv/EmbyTV/ItemDataProvider.cs
index d5a6feb470..547ffeb668 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/src/Jellyfin.LiveTv/EmbyTV/ItemDataProvider.cs
@@ -9,7 +9,7 @@ using System.Text.Json;
using Jellyfin.Extensions.Json;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv.EmbyTV
{
public class ItemDataProvider
where T : class
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/NfoConfigurationExtensions.cs b/src/Jellyfin.LiveTv/EmbyTV/NfoConfigurationExtensions.cs
similarity index 92%
rename from Emby.Server.Implementations/LiveTv/EmbyTV/NfoConfigurationExtensions.cs
rename to src/Jellyfin.LiveTv/EmbyTV/NfoConfigurationExtensions.cs
index 83f5e84131..e8570f0e0d 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/NfoConfigurationExtensions.cs
+++ b/src/Jellyfin.LiveTv/EmbyTV/NfoConfigurationExtensions.cs
@@ -1,7 +1,7 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Configuration;
-namespace Emby.Server.Implementations.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv.EmbyTV
{
///
/// Class containing extension methods for working with the nfo configuration.
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/src/Jellyfin.LiveTv/EmbyTV/RecordingHelper.cs
similarity index 98%
rename from Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
rename to src/Jellyfin.LiveTv/EmbyTV/RecordingHelper.cs
index 7bbeae866a..6bda231b24 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/src/Jellyfin.LiveTv/EmbyTV/RecordingHelper.cs
@@ -5,7 +5,7 @@ using System.Globalization;
using System.Text;
using MediaBrowser.Controller.LiveTv;
-namespace Emby.Server.Implementations.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv.EmbyTV
{
internal static class RecordingHelper
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/src/Jellyfin.LiveTv/EmbyTV/SeriesTimerManager.cs
similarity index 92%
rename from Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
rename to src/Jellyfin.LiveTv/EmbyTV/SeriesTimerManager.cs
index bf28f3b67d..2ebe60b296 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
+++ b/src/Jellyfin.LiveTv/EmbyTV/SeriesTimerManager.cs
@@ -4,7 +4,7 @@ using System;
using MediaBrowser.Controller.LiveTv;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv.EmbyTV
{
public class SeriesTimerManager : ItemDataProvider
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/src/Jellyfin.LiveTv/EmbyTV/TimerManager.cs
similarity index 99%
rename from Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
rename to src/Jellyfin.LiveTv/EmbyTV/TimerManager.cs
index 9f8441fa48..37b1fa14cc 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/src/Jellyfin.LiveTv/EmbyTV/TimerManager.cs
@@ -10,7 +10,7 @@ using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.EmbyTV
+namespace Jellyfin.LiveTv.EmbyTV
{
public class TimerManager : ItemDataProvider
{
diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/src/Jellyfin.LiveTv/ExclusiveLiveStream.cs
similarity index 86%
rename from Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
rename to src/Jellyfin.LiveTv/ExclusiveLiveStream.cs
index 868071a992..9d442e20cc 100644
--- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
+++ b/src/Jellyfin.LiveTv/ExclusiveLiveStream.cs
@@ -1,5 +1,6 @@
#nullable disable
+#pragma warning disable CA1711
#pragma warning disable CS1591
using System;
@@ -10,9 +11,9 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
-namespace Emby.Server.Implementations.Library
+namespace Jellyfin.LiveTv
{
- public class ExclusiveLiveStream : ILiveStream
+ public sealed class ExclusiveLiveStream : ILiveStream
{
private readonly Func _closeFn;
@@ -51,5 +52,10 @@ namespace Emby.Server.Implementations.Library
{
return Task.CompletedTask;
}
+
+ ///
+ public void Dispose()
+ {
+ }
}
}
diff --git a/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs
new file mode 100644
index 0000000000..5490547ec3
--- /dev/null
+++ b/src/Jellyfin.LiveTv/Extensions/LiveTvServiceCollectionExtensions.cs
@@ -0,0 +1,31 @@
+using Jellyfin.LiveTv.Channels;
+using Jellyfin.LiveTv.TunerHosts;
+using Jellyfin.LiveTv.TunerHosts.HdHomerun;
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.IO;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Jellyfin.LiveTv.Extensions;
+
+///
+/// Live TV extensions for .
+///
+public static class LiveTvServiceCollectionExtensions
+{
+ ///
+ /// Adds Live TV services to the .
+ ///
+ /// The to add services to.
+ public static void AddLiveTvServices(this IServiceCollection services)
+ {
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+
+ services.AddSingleton();
+ services.AddSingleton();
+ }
+}
diff --git a/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj b/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj
new file mode 100644
index 0000000000..5a826a1da0
--- /dev/null
+++ b/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj
@@ -0,0 +1,23 @@
+
+
+ net8.0
+ true
+
+
+
+
+ <_Parameter1>Jellyfin.LiveTv.Tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs
similarity index 98%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs
index 6b0520ad0f..3b20cd160b 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs
@@ -16,9 +16,9 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
+using Jellyfin.LiveTv.Listings.SchedulesDirectDtos;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.LiveTv;
@@ -27,7 +27,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.Listings
+namespace Jellyfin.LiveTv.Listings
{
public class SchedulesDirect : IListingsProvider, IDisposable
{
@@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
IsMovie = IsMovie(details),
Etag = programInfo.Md5,
IsLive = string.Equals(programInfo.LiveTapeDelay, "live", StringComparison.OrdinalIgnoreCase),
- IsPremiere = programInfo.Premiere || (programInfo.IsPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1
+ IsPremiere = programInfo.Premiere || (programInfo.IsPremiereOrFinale ?? string.Empty).Contains("premiere", StringComparison.OrdinalIgnoreCase)
};
var showId = programId;
@@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return null;
}
- if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
+ if (uri.Contains("http", StringComparison.OrdinalIgnoreCase))
{
return uri;
}
@@ -613,6 +613,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
// Response is automatically disposed in the calling function,
// so dispose manually if not returning.
+#pragma warning disable IDISP016, IDISP017
response.Dispose();
if (!enableRetry || (int)response.StatusCode >= 500)
{
@@ -621,6 +622,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
null,
response.StatusCode);
}
+#pragma warning restore IDISP016, IDISP017
_tokens.Clear();
options.Headers.TryAddWithoutValidation("token", await GetToken(providerInfo, cancellationToken).ConfigureAwait(false));
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs
similarity index 91%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs
index 95ac996e01..c1a502fd5a 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Broadcaster dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs
similarity index 86%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs
index f6251b9ad8..0cc39f3bb5 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Caption dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/CastDto.cs
similarity index 93%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/CastDto.cs
index 0b7a2c63ad..bdcf87fdac 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/CastDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Cast dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs
similarity index 91%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs
index 87c327ed82..4e0d740785 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Channel dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs
similarity index 86%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs
index c19cd2e48d..5c624c2885 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Content rating dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs
similarity index 92%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs
index f00c9accdd..6d3c79c185 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Crew dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/DayDto.cs
similarity index 91%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/DayDto.cs
index 1a371965cf..094f9a3194 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/DayDto.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Day dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs
similarity index 87%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs
index ca6ae7fb13..0063f4cc34 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Description 1_000 dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs
similarity index 87%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs
index 1577219ed2..1d9a18cc7f 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Description 100 dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs
similarity index 90%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs
index eaf4a340bd..75e91547bb 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Descriptions program dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs
similarity index 81%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs
index fbdfb1f716..28abe094ed 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Event details dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs
similarity index 86%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs
index 6852d89d71..6eefc1744b 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Gracenote dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs
similarity index 92%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs
index b9844562f3..a62ae61f91 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Headends dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs
similarity index 95%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs
index a1ae3ca6d4..21b595f244 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Image data dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs
similarity index 94%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs
index 3dc64e5d8a..856b7a89b0 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// The lineup dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs
similarity index 92%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs
index f190817813..99f80ce8a1 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Lineups dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs
similarity index 91%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs
index fecc55e037..d7836384e2 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Logo dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MapDto.cs
similarity index 95%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MapDto.cs
index ffd02d474b..ea583a1cea 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MapDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Map dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs
similarity index 89%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs
index 40faa493c5..cafc8e2738 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Metadata dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs
similarity index 82%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs
index 43f2901566..243ccff5c0 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Metadata programs dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs
similarity index 93%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs
index 04560ab55d..1c5c5333ce 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs
@@ -1,7 +1,7 @@
using System;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Metadata schedule dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs
similarity index 91%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs
index 31bef423b2..aea7408336 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Movie dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs
similarity index 86%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs
index e8b15dc07c..328cefadc3 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Multipart dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs
similarity index 98%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs
index 84c48f67f3..8c3906f863 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Program details dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs
similarity index 97%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs
index 60389b45bf..527a6f8a19 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Program dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs
similarity index 93%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs
index c5ddcf7c51..61496155ac 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Quality rating dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs
similarity index 85%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs
index e04b619a4d..287cd4ed5c 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Rating dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs
similarity index 86%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs
index c8f79fd1c2..d380ec7aee 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Recommendation dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs
similarity index 89%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs
index 0cd05709b3..6fc695a393 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Request schedule for channel dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs
similarity index 88%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs
index 84e224b71e..523900a96a 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Show image dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/StationDto.cs
similarity index 96%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/StationDto.cs
index d797fd49b1..dbde1e117d 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/StationDto.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Station dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs
similarity index 80%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs
index 61cd4a9b00..146124f987 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// Title dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs
similarity index 94%
rename from Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs
rename to src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs
index afb9994869..b3bc618376 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs
+++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs
@@ -1,7 +1,7 @@
using System;
using System.Text.Json.Serialization;
-namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
+namespace Jellyfin.LiveTv.Listings.SchedulesDirectDtos
{
///
/// The token dto.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs
similarity index 85%
rename from Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
rename to src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs
index 066afb956b..cecc363f07 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs
@@ -23,7 +23,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.Listings
+namespace Jellyfin.LiveTv.Listings
{
public class XmlTvListingsProvider : IListingsProvider
{
@@ -84,38 +84,53 @@ namespace Emby.Server.Implementations.LiveTv.Listings
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(info.Path, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
+ var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ await using (stream.ConfigureAwait(false))
+ {
+ return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
+ }
}
else
{
- await using var stream = AsyncFile.OpenRead(info.Path);
- return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
+ var stream = AsyncFile.OpenRead(info.Path);
+ await using (stream.ConfigureAwait(false))
+ {
+ return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
+ }
}
}
private async Task UnzipIfNeededAndCopy(string originalUrl, Stream stream, string file, CancellationToken cancellationToken)
{
- await using var fileStream = new FileStream(file, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
+ var fileStream = new FileStream(
+ file,
+ FileMode.CreateNew,
+ FileAccess.Write,
+ FileShare.None,
+ IODefaults.FileStreamBufferSize,
+ FileOptions.Asynchronous);
- if (Path.GetExtension(originalUrl.AsSpan().LeftPart('?')).Equals(".gz", StringComparison.OrdinalIgnoreCase))
+ await using (fileStream.ConfigureAwait(false))
{
- try
+ if (Path.GetExtension(originalUrl.AsSpan().LeftPart('?')).Equals(".gz", StringComparison.OrdinalIgnoreCase))
{
- using var reader = new GZipStream(stream, CompressionMode.Decompress);
- await reader.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
+ try
+ {
+ using var reader = new GZipStream(stream, CompressionMode.Decompress);
+ await reader.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error extracting from gz file {File}", originalUrl);
+ }
}
- catch (Exception ex)
+ else
{
- _logger.LogError(ex, "Error extracting from gz file {File}", originalUrl);
+ await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
- }
- else
- {
- await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
- }
- return file;
+ return file;
+ }
}
public async Task> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/src/Jellyfin.LiveTv/LiveTvDtoService.cs
similarity index 98%
rename from Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
rename to src/Jellyfin.LiveTv/LiveTvDtoService.cs
index 9326fbd5c7..55b056d3d8 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/src/Jellyfin.LiveTv/LiveTvDtoService.cs
@@ -8,6 +8,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Drawing;
@@ -20,7 +21,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv
+namespace Jellyfin.LiveTv
{
public class LiveTvDtoService
{
@@ -456,7 +457,7 @@ namespace Emby.Server.Implementations.LiveTv
info.Id = timer.ExternalId;
}
- if (!dto.ChannelId.Equals(default) && string.IsNullOrEmpty(info.ChannelId))
+ if (!dto.ChannelId.IsEmpty() && string.IsNullOrEmpty(info.ChannelId))
{
var channel = _libraryManager.GetItemById(dto.ChannelId);
@@ -522,7 +523,7 @@ namespace Emby.Server.Implementations.LiveTv
info.Id = timer.ExternalId;
}
- if (!dto.ChannelId.Equals(default) && string.IsNullOrEmpty(info.ChannelId))
+ if (!dto.ChannelId.IsEmpty() && string.IsNullOrEmpty(info.ChannelId))
{
var channel = _libraryManager.GetItemById(dto.ChannelId);
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/src/Jellyfin.LiveTv/LiveTvManager.cs
similarity index 95%
rename from Emby.Server.Implementations/LiveTv/LiveTvManager.cs
rename to src/Jellyfin.LiveTv/LiveTvManager.cs
index dd427c7368..bada4249aa 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/src/Jellyfin.LiveTv/LiveTvManager.cs
@@ -9,11 +9,11 @@ using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using Emby.Server.Implementations.Library;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Data.Events;
-using MediaBrowser.Common.Configuration;
+using Jellyfin.Extensions;
+using Jellyfin.LiveTv.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
@@ -34,7 +34,7 @@ using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv
+namespace Jellyfin.LiveTv
{
///
/// Class LiveTvManager.
@@ -58,9 +58,9 @@ namespace Emby.Server.Implementations.LiveTv
private readonly IFileSystem _fileSystem;
private readonly IChannelManager _channelManager;
private readonly LiveTvDtoService _tvDtoService;
+ private readonly ITunerHostManager _tunerHostManager;
private ILiveTvService[] _services = Array.Empty();
- private ITunerHost[] _tunerHosts = Array.Empty();
private IListingsProvider[] _listingProviders = Array.Empty();
public LiveTvManager(
@@ -75,7 +75,8 @@ namespace Emby.Server.Implementations.LiveTv
ILocalizationManager localization,
IFileSystem fileSystem,
IChannelManager channelManager,
- LiveTvDtoService liveTvDtoService)
+ LiveTvDtoService liveTvDtoService,
+ ITunerHostManager tunerHostManager)
{
_config = config;
_logger = logger;
@@ -89,6 +90,7 @@ namespace Emby.Server.Implementations.LiveTv
_userDataManager = userDataManager;
_channelManager = channelManager;
_tvDtoService = liveTvDtoService;
+ _tunerHostManager = tunerHostManager;
}
public event EventHandler> SeriesTimerCancelled;
@@ -105,30 +107,17 @@ namespace Emby.Server.Implementations.LiveTv
/// The services.
public IReadOnlyList Services => _services;
- public ITunerHost[] TunerHosts => _tunerHosts;
-
- public IListingsProvider[] ListingProviders => _listingProviders;
-
- private LiveTvOptions GetConfiguration()
- {
- return _config.GetConfiguration("livetv");
- }
+ public IReadOnlyList ListingProviders => _listingProviders;
public string GetEmbyTvActiveRecordingPath(string id)
{
return EmbyTV.EmbyTV.Current.GetActiveRecordingPath(id);
}
- ///
- /// Adds the parts.
- ///
- /// The services.
- /// The tuner hosts.
- /// The listing providers.
- public void AddParts(IEnumerable services, IEnumerable tunerHosts, IEnumerable listingProviders)
+ ///
+ public void AddParts(IEnumerable services, IEnumerable listingProviders)
{
_services = services.ToArray();
- _tunerHosts = tunerHosts.Where(i => i.IsSupported).ToArray();
_listingProviders = listingProviders.ToArray();
@@ -160,23 +149,9 @@ namespace Emby.Server.Implementations.LiveTv
}));
}
- public List GetTunerHostTypes()
- {
- return _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair
- {
- Name = i.Name,
- Id = i.Type
- }).ToList();
- }
-
- public Task> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken)
- {
- return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken);
- }
-
public QueryResult GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
{
- var user = query.UserId.Equals(default)
+ var user = query.UserId.IsEmpty()
? null
: _userManager.GetUserById(query.UserId);
@@ -231,7 +206,9 @@ namespace Emby.Server.Implementations.LiveTv
_logger.LogInformation("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
MediaSourceInfo info;
+#pragma warning disable CA1859 // TODO: Analyzer bug?
ILiveStream liveStream;
+#pragma warning restore CA1859
if (service is ISupportsDirectStreamProvider supportsManagedStream)
{
liveStream = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, mediaSourceId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
@@ -1033,7 +1010,7 @@ namespace Emby.Server.Implementations.LiveTv
{
await EmbyTV.EmbyTV.Current.CreateRecordingFolders().ConfigureAwait(false);
- await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
+ await _tunerHostManager.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
var numComplete = 0;
double progressPerService = _services.Length == 0
@@ -1095,13 +1072,12 @@ namespace Emby.Server.Implementations.LiveTv
// Load these now which will prefetch metadata
var dtoOptions = new DtoOptions();
var fields = dtoOptions.Fields.ToList();
- fields.Remove(ItemFields.BasicSyncInfo);
dtoOptions.Fields = fields.ToArray();
progress.Report(100);
}
- private async Task, List>> RefreshChannelsInternal(ILiveTvService service, IProgress progress, CancellationToken cancellationToken)
+ private async Task, List>> RefreshChannelsInternal(ILiveTvService service, ActionableProgress progress, CancellationToken cancellationToken)
{
progress.Report(10);
@@ -1270,7 +1246,7 @@ namespace Emby.Server.Implementations.LiveTv
{
cancellationToken.ThrowIfCancellationRequested();
- if (itemId.Equals(default))
+ if (itemId.IsEmpty())
{
// Somehow some invalid data got into the db. It probably predates the boundary checking
continue;
@@ -1302,7 +1278,7 @@ namespace Emby.Server.Implementations.LiveTv
private double GetGuideDays()
{
- var config = GetConfiguration();
+ var config = _config.GetLiveTvConfiguration();
if (config.GuideDays.HasValue)
{
@@ -1529,7 +1505,7 @@ namespace Emby.Server.Implementations.LiveTv
public async Task> GetRecordingsAsync(RecordingQuery query, DtoOptions options)
{
- var user = query.UserId.Equals(default)
+ var user = query.UserId.IsEmpty()
? null
: _userManager.GetUserById(query.UserId);
@@ -2125,7 +2101,7 @@ namespace Emby.Server.Implementations.LiveTv
private bool IsLiveTvEnabled(User user)
{
- return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0);
+ return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || _config.GetLiveTvConfiguration().TunerHosts.Length > 0);
}
public IEnumerable GetEnabledUsers()
@@ -2168,49 +2144,7 @@ namespace Emby.Server.Implementations.LiveTv
public Folder GetInternalLiveTvFolder(CancellationToken cancellationToken)
{
var name = _localization.GetLocalizedString("HeaderLiveTV");
- return _libraryManager.GetNamedView(name, CollectionType.LiveTv, name);
- }
-
- public async Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
- {
- info = JsonSerializer.Deserialize(JsonSerializer.SerializeToUtf8Bytes(info));
-
- var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
-
- if (provider is null)
- {
- throw new ResourceNotFoundException();
- }
-
- if (provider is IConfigurableTunerHost configurable)
- {
- await configurable.Validate(info).ConfigureAwait(false);
- }
-
- var config = GetConfiguration();
-
- var list = config.TunerHosts.ToList();
- var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
-
- if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
- {
- info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
- list.Add(info);
- config.TunerHosts = list.ToArray();
- }
- else
- {
- config.TunerHosts[index] = info;
- }
-
- _config.SaveConfiguration("livetv", config);
-
- if (dataSourceChanged)
- {
- _taskManager.CancelIfRunningAndQueue();
- }
-
- return info;
+ return _libraryManager.GetNamedView(name, CollectionType.livetv, name);
}
public async Task SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings)
@@ -2232,7 +2166,7 @@ namespace Emby.Server.Implementations.LiveTv
await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false);
- LiveTvOptions config = GetConfiguration();
+ var config = _config.GetLiveTvConfiguration();
var list = config.ListingProviders.ToList();
int index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
@@ -2257,7 +2191,7 @@ namespace Emby.Server.Implementations.LiveTv
public void DeleteListingsProvider(string id)
{
- var config = GetConfiguration();
+ var config = _config.GetLiveTvConfiguration();
config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray();
@@ -2267,7 +2201,7 @@ namespace Emby.Server.Implementations.LiveTv
public async Task SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber)
{
- var config = GetConfiguration();
+ var config = _config.GetLiveTvConfiguration();
var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray();
@@ -2327,7 +2261,7 @@ namespace Emby.Server.Implementations.LiveTv
public Task> GetLineups(string providerType, string providerId, string country, string location)
{
- var config = GetConfiguration();
+ var config = _config.GetLiveTvConfiguration();
if (string.IsNullOrWhiteSpace(providerId))
{
@@ -2357,13 +2291,13 @@ namespace Emby.Server.Implementations.LiveTv
public Task> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken)
{
- var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
+ var info = _config.GetLiveTvConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
return EmbyTV.EmbyTV.Current.GetChannelsForListingsProvider(info, cancellationToken);
}
public Task> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken)
{
- var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
+ var info = _config.GetLiveTvConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
var provider = _listingProviders.First(i => string.Equals(i.Type, info.Type, StringComparison.OrdinalIgnoreCase));
return provider.GetChannels(info, cancellationToken);
}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/src/Jellyfin.LiveTv/LiveTvMediaSourceProvider.cs
similarity index 96%
rename from Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
rename to src/Jellyfin.LiveTv/LiveTvMediaSourceProvider.cs
index 6a92fc5997..ce9361089c 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/src/Jellyfin.LiveTv/LiveTvMediaSourceProvider.cs
@@ -16,7 +16,7 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv
+namespace Jellyfin.LiveTv
{
public class LiveTvMediaSourceProvider : IMediaSourceProvider
{
@@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.LiveTv
{
if (activeRecordingInfo is not null)
{
- sources = await EmbyTV.EmbyTV.Current.GetRecordingStreamMediaSources(activeRecordingInfo, cancellationToken)
+ sources = await _mediaSourceManager.GetRecordingStreamMediaSources(activeRecordingInfo, cancellationToken)
.ConfigureAwait(false);
}
else
diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/src/Jellyfin.LiveTv/RecordingNotifier.cs
similarity index 98%
rename from Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
rename to src/Jellyfin.LiveTv/RecordingNotifier.cs
index e0ca02d986..2923948ebf 100644
--- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
+++ b/src/Jellyfin.LiveTv/RecordingNotifier.cs
@@ -15,7 +15,7 @@ using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.EntryPoints
+namespace Jellyfin.LiveTv
{
public sealed class RecordingNotifier : IServerEntryPoint
{
diff --git a/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs b/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs
similarity index 88%
rename from Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs
rename to src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs
index 72bbdd14a6..18bd61d999 100644
--- a/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs
+++ b/src/Jellyfin.LiveTv/RefreshGuideScheduledTask.cs
@@ -2,12 +2,12 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.LiveTv.Configuration;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Tasks;
-namespace Emby.Server.Implementations.LiveTv
+namespace Jellyfin.LiveTv
{
///
/// The "Refresh Guide" scheduled task.
@@ -38,7 +38,7 @@ namespace Emby.Server.Implementations.LiveTv
public string Category => "Live TV";
///
- public bool IsHidden => _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Length == 0;
+ public bool IsHidden => _liveTvManager.Services.Count == 1 && _config.GetLiveTvConfiguration().TunerHosts.Length == 0;
///
public bool IsEnabled => true;
@@ -66,10 +66,5 @@ namespace Emby.Server.Implementations.LiveTv
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }
};
}
-
- private LiveTvOptions GetConfiguration()
- {
- return _config.GetConfiguration("livetv");
- }
}
}
diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/src/Jellyfin.LiveTv/StreamHelper.cs
similarity index 79%
rename from Emby.Server.Implementations/IO/StreamHelper.cs
rename to src/Jellyfin.LiveTv/StreamHelper.cs
index 6eaf22ce48..e9644e95e7 100644
--- a/Emby.Server.Implementations/IO/StreamHelper.cs
+++ b/src/Jellyfin.LiveTv/StreamHelper.cs
@@ -7,7 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
-namespace Emby.Server.Implementations.IO
+namespace Jellyfin.LiveTv
{
public class StreamHelper : IStreamHelper
{
@@ -81,36 +81,6 @@ namespace Emby.Server.Implementations.IO
}
}
- public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
- {
- byte[] buffer = ArrayPool.Shared.Rent(IODefaults.CopyToBufferSize);
- try
- {
- int bytesRead;
-
- while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
- {
- var bytesToWrite = Math.Min(bytesRead, copyLength);
-
- if (bytesToWrite > 0)
- {
- await destination.WriteAsync(buffer.AsMemory(0, Convert.ToInt32(bytesToWrite)), cancellationToken).ConfigureAwait(false);
- }
-
- copyLength -= bytesToWrite;
-
- if (copyLength <= 0)
- {
- break;
- }
- }
- }
- finally
- {
- ArrayPool.Shared.Return(buffer);
- }
- }
-
public async Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken)
{
byte[] buffer = ArrayPool.Shared.Rent(bufferSize);
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs
similarity index 84%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
rename to src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs
index ff25ee5854..afc2e4f9ce 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/BaseTunerHost.cs
@@ -10,7 +10,7 @@ using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
+using Jellyfin.LiveTv.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
@@ -19,7 +19,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.TunerHosts
+namespace Jellyfin.LiveTv.TunerHosts
{
public abstract class BaseTunerHost
{
@@ -67,9 +67,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return list;
}
- protected virtual List GetTunerHosts()
+ protected virtual IList GetTunerHosts()
{
- return GetConfiguration().TunerHosts
+ return Config.GetLiveTvConfiguration().TunerHosts
.Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
.ToList();
}
@@ -96,8 +96,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
try
{
Directory.CreateDirectory(Path.GetDirectoryName(channelCacheFile));
- await using var writeStream = AsyncFile.OpenWrite(channelCacheFile);
- await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellationToken).ConfigureAwait(false);
+ var writeStream = AsyncFile.OpenWrite(channelCacheFile);
+ await using (writeStream.ConfigureAwait(false))
+ {
+ await JsonSerializer.SerializeAsync(writeStream, channels, cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
}
catch (IOException)
{
@@ -112,10 +115,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
try
{
- await using var readStream = AsyncFile.OpenRead(channelCacheFile);
- var channels = await JsonSerializer.DeserializeAsync>(readStream, cancellationToken: cancellationToken)
- .ConfigureAwait(false);
- list.AddRange(channels);
+ var readStream = AsyncFile.OpenRead(channelCacheFile);
+ await using (readStream.ConfigureAwait(false))
+ {
+ var channels = await JsonSerializer
+ .DeserializeAsync>(readStream, cancellationToken: cancellationToken)
+ .ConfigureAwait(false);
+ list.AddRange(channels);
+ }
}
catch (IOException)
{
@@ -159,9 +166,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return new List();
}
- protected abstract Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List currentLiveStreams, CancellationToken cancellationToken);
+ protected abstract Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList currentLiveStreams, CancellationToken cancellationToken);
- public async Task GetChannelStream(string channelId, string streamId, List currentLiveStreams, CancellationToken cancellationToken)
+ public async Task GetChannelStream(string channelId, string streamId, IList currentLiveStreams, CancellationToken cancellationToken)
{
ArgumentException.ThrowIfNullOrEmpty(channelId);
@@ -221,10 +228,5 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
-
- protected LiveTvOptions GetConfiguration()
- {
- return Config.GetConfiguration("livetv");
- }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/Channels.cs
similarity index 86%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs
rename to src/Jellyfin.LiveTv/TunerHosts/HdHomerun/Channels.cs
index 0f04531896..311a71d137 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/Channels.cs
@@ -1,6 +1,6 @@
#nullable disable
-namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
+namespace Jellyfin.LiveTv.TunerHosts.HdHomerun
{
internal class Channels
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs
similarity index 83%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs
rename to src/Jellyfin.LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs
index 42068cd340..3ece181f25 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs
@@ -2,7 +2,7 @@
using System;
-namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
+namespace Jellyfin.LiveTv.TunerHosts.HdHomerun
{
internal class DiscoverResponse
{
@@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var model = ModelNumber ?? string.Empty;
- if (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)
+ if (model.Contains("hdtc", StringComparison.OrdinalIgnoreCase))
{
return true;
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs
similarity index 93%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs
rename to src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs
index aae33503fa..50a887826a 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs
@@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
-namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
+namespace Jellyfin.LiveTv.TunerHosts.HdHomerun
{
public class HdHomerunChannelCommands : IHdHomerunChannelCommands
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
similarity index 73%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
rename to src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 8cd0c4ffb7..fef84dd000 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -30,7 +29,7 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
+namespace Jellyfin.LiveTv.TunerHosts.HdHomerun
{
public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
@@ -143,7 +142,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (!throwAllExceptions && ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
{
const string DefaultValue = "HDHR";
- var response = new DiscoverResponse
+ var discoverResponse = new DiscoverResponse
{
ModelNumber = DefaultValue
};
@@ -152,163 +151,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// HDHR4 doesn't have this api
lock (_modelCache)
{
- _modelCache[cacheKey] = response;
+ _modelCache[cacheKey] = discoverResponse;
}
}
- return response;
+ return discoverResponse;
}
throw;
}
}
- private async Task> GetTunerInfosHttp(TunerHostInfo info, CancellationToken cancellationToken)
- {
- var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
-
- using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
- .GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken)
- .ConfigureAwait(false);
- var tuners = new List();
- var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- await using (stream.ConfigureAwait(false))
- {
- using var sr = new StreamReader(stream, System.Text.Encoding.UTF8);
- await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false))
- {
- string stripedLine = StripXML(line);
- if (stripedLine.Contains("Channel", StringComparison.Ordinal))
- {
- LiveTvTunerStatus status;
- var index = stripedLine.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
- var name = stripedLine.Substring(0, index - 1);
- var currentChannel = stripedLine.Substring(index + 7);
- if (string.Equals(currentChannel, "none", StringComparison.Ordinal))
- {
- status = LiveTvTunerStatus.LiveTv;
- }
- else
- {
- status = LiveTvTunerStatus.Available;
- }
-
- tuners.Add(new LiveTvTunerInfo
- {
- Name = name,
- SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
- ProgramName = currentChannel,
- Status = status
- });
- }
- }
- }
-
- return tuners;
- }
-
- private static string StripXML(string source)
- {
- if (string.IsNullOrEmpty(source))
- {
- return string.Empty;
- }
-
- char[] buffer = new char[source.Length];
- int bufferIndex = 0;
- bool inside = false;
-
- for (int i = 0; i < source.Length; i++)
- {
- char let = source[i];
- if (let == '<')
- {
- inside = true;
- continue;
- }
-
- if (let == '>')
- {
- inside = false;
- continue;
- }
-
- if (!inside)
- {
- buffer[bufferIndex++] = let;
- }
- }
-
- return new string(buffer, 0, bufferIndex);
- }
-
- private async Task> GetTunerInfosUdp(TunerHostInfo info, CancellationToken cancellationToken)
- {
- var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
-
- var tuners = new List(model.TunerCount);
-
- var uri = new Uri(GetApiUrl(info));
-
- using (var manager = new HdHomerunManager())
- {
- // Legacy HdHomeruns are IPv4 only
- var ipInfo = IPAddress.Parse(uri.Host);
-
- for (int i = 0; i < model.TunerCount; i++)
- {
- var name = string.Format(CultureInfo.InvariantCulture, "Tuner {0}", i + 1);
- var currentChannel = "none"; // TODO: Get current channel and map back to Station Id
- var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false);
- var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv;
- tuners.Add(new LiveTvTunerInfo
- {
- Name = name,
- SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
- ProgramName = currentChannel,
- Status = status
- });
- }
- }
-
- return tuners;
- }
-
- public async Task> GetTunerInfos(CancellationToken cancellationToken)
- {
- var list = new List();
-
- foreach (var host in GetConfiguration().TunerHosts
- .Where(i => string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)))
- {
- try
- {
- list.AddRange(await GetTunerInfos(host, cancellationToken).ConfigureAwait(false));
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error getting tuner info");
- }
- }
-
- return list;
- }
-
- public async Task> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
- {
- // TODO Need faster way to determine UDP vs HTTP
- var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false);
-
- var hdHomerunChannelInfo = channels.FirstOrDefault() as HdHomerunChannelInfo;
-
- if (hdHomerunChannelInfo is null || hdHomerunChannelInfo.IsLegacyTuner)
- {
- return await GetTunerInfosUdp(info, cancellationToken).ConfigureAwait(false);
- }
-
- return await GetTunerInfosHttp(info, cancellationToken).ConfigureAwait(false);
- }
-
private static string GetApiUrl(TunerHostInfo info)
{
var url = info.Url;
@@ -527,7 +380,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return list;
}
- protected override async Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List currentLiveStreams, CancellationToken cancellationToken)
+ protected override async Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList currentLiveStreams, CancellationToken cancellationToken)
{
var tunerCount = tunerHost.TunerCount;
@@ -574,40 +427,24 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_streamHelper);
}
- var enableHttpStream = true;
- if (enableHttpStream)
+ mediaSource.Protocol = MediaProtocol.Http;
+
+ var httpUrl = channel.Path;
+
+ // If raw was used, the tuner doesn't support params
+ if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
{
- mediaSource.Protocol = MediaProtocol.Http;
-
- var httpUrl = channel.Path;
-
- // If raw was used, the tuner doesn't support params
- if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
- {
- httpUrl += "?transcode=" + profile;
- }
-
- mediaSource.Path = httpUrl;
-
- return new SharedHttpStream(
- mediaSource,
- tunerHost,
- streamId,
- FileSystem,
- _httpClientFactory,
- Logger,
- Config,
- _appHost,
- _streamHelper);
+ httpUrl += "?transcode=" + profile;
}
- return new HdHomerunUdpStream(
+ mediaSource.Path = httpUrl;
+
+ return new SharedHttpStream(
mediaSource,
tunerHost,
streamId,
- new HdHomerunChannelCommands(hdhomerunChannel.Number, profile),
- modelInfo.TunerCount,
FileSystem,
+ _httpClientFactory,
Logger,
Config,
_appHost,
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
similarity index 99%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
rename to src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index 68383a5547..8613387270 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -14,7 +14,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Controller.LiveTv;
-namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
+namespace Jellyfin.LiveTv.TunerHosts.HdHomerun
{
public sealed class HdHomerunManager : IDisposable
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
similarity index 99%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
rename to src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index 6195c7648f..6c8cde62c9 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -1,5 +1,6 @@
#nullable disable
+#pragma warning disable CA1711
#pragma warning disable CS1591
using System;
@@ -19,7 +20,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
+namespace Jellyfin.LiveTv.TunerHosts.HdHomerun
{
public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs
similarity index 75%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs
rename to src/Jellyfin.LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs
index 11bd40ab10..9fcf386f9c 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
-namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
+namespace Jellyfin.LiveTv.TunerHosts.HdHomerun
{
public interface IHdHomerunChannelCommands
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs
similarity index 94%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs
rename to src/Jellyfin.LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs
index 654474e971..6dc9c885f6 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs
@@ -3,7 +3,7 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
-namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
+namespace Jellyfin.LiveTv.TunerHosts.HdHomerun
{
public partial class LegacyHdHomerunChannelCommands : IHdHomerunChannelCommands
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/src/Jellyfin.LiveTv/TunerHosts/LiveStream.cs
similarity index 90%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
rename to src/Jellyfin.LiveTv/TunerHosts/LiveStream.cs
index 767b941366..70d8afc5d0 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/LiveStream.cs
@@ -1,5 +1,6 @@
#nullable disable
+#pragma warning disable CA1711
#pragma warning disable CS1591
using System;
@@ -14,7 +15,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.TunerHosts
+namespace Jellyfin.LiveTv.TunerHosts
{
public class LiveStream : ILiveStream
{
@@ -112,6 +113,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return stream;
}
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool dispose)
+ {
+ if (dispose)
+ {
+ LiveStreamCancellationTokenSource?.Dispose();
+ }
+ }
+
protected async Task DeleteTempFiles(string path, int retryCount = 0)
{
if (retryCount == 0)
@@ -134,7 +150,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
- private void TrySeek(Stream stream, long offset)
+ private void TrySeek(FileStream stream, long offset)
{
if (!stream.CanSeek)
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs
similarity index 92%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
rename to src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs
index db5e81df5f..3666d342ed 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/M3UTunerHost.cs
@@ -24,7 +24,7 @@ using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
-namespace Emby.Server.Implementations.LiveTv.TunerHosts
+namespace Jellyfin.LiveTv.TunerHosts
{
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
@@ -80,23 +80,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
.ConfigureAwait(false);
}
- public Task> GetTunerInfos(CancellationToken cancellationToken)
- {
- var list = GetTunerHosts()
- .Select(i => new LiveTvTunerInfo()
- {
- Name = Name,
- SourceType = Type,
- Status = LiveTvTunerStatus.Available,
- Id = i.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture),
- Url = i.Url
- })
- .ToList();
-
- return Task.FromResult(list);
- }
-
- protected override async Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List currentLiveStreams, CancellationToken cancellationToken)
+ protected override async Task GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, IList currentLiveStreams, CancellationToken cancellationToken)
{
var tunerCount = tunerHost.TunerCount;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs
similarity index 99%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
rename to src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs
index 341782d9d3..5900d1c5be 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/M3uParser.cs
@@ -18,7 +18,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.TunerHosts
+namespace Jellyfin.LiveTv.TunerHosts
{
public partial class M3uParser
{
@@ -66,7 +66,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
.ConfigureAwait(false);
response.EnsureSuccessStatusCode();
- return await response.Content.ReadAsStreamAsync(cancellationToken);
+ return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
}
private async Task> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/src/Jellyfin.LiveTv/TunerHosts/SharedHttpStream.cs
similarity index 78%
rename from Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
rename to src/Jellyfin.LiveTv/TunerHosts/SharedHttpStream.cs
index 51f46f4dac..5ef04ad9e9 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/src/Jellyfin.LiveTv/TunerHosts/SharedHttpStream.cs
@@ -1,3 +1,4 @@
+#pragma warning disable CA1711
#pragma warning disable CS1591
using System;
@@ -16,7 +17,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.LiveTv.TunerHosts
+namespace Jellyfin.LiveTv.TunerHosts
{
public class SharedHttpStream : LiveStream, IDirectStreamProvider
{
@@ -83,14 +84,27 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Logger.LogInformation("Beginning {StreamType} stream to {FilePath}", GetType().Name, TempFilePath);
using (response)
{
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
- await StreamHelper.CopyToAsync(
- stream,
- fileStream,
- IODefaults.CopyToBufferSize,
- () => Resolve(openTaskCompletionSource),
- cancellationToken).ConfigureAwait(false);
+ var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ await using (stream.ConfigureAwait(false))
+ {
+ var fileStream = new FileStream(
+ TempFilePath,
+ FileMode.Create,
+ FileAccess.Write,
+ FileShare.Read,
+ IODefaults.FileStreamBufferSize,
+ FileOptions.Asynchronous);
+
+ await using (fileStream.ConfigureAwait(false))
+ {
+ await StreamHelper.CopyToAsync(
+ stream,
+ fileStream,
+ IODefaults.CopyToBufferSize,
+ () => Resolve(openTaskCompletionSource),
+ cancellationToken).ConfigureAwait(false);
+ }
+ }
}
}
catch (OperationCanceledException ex)
diff --git a/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs
new file mode 100644
index 0000000000..3e4b0e13fc
--- /dev/null
+++ b/src/Jellyfin.LiveTv/TunerHosts/TunerHostManager.cs
@@ -0,0 +1,174 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.LiveTv.Configuration;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.LiveTv.TunerHosts;
+
+///
+public class TunerHostManager : ITunerHostManager
+{
+ private const int TunerDiscoveryDurationMs = 3000;
+
+ private readonly ILogger _logger;
+ private readonly IConfigurationManager _config;
+ private readonly ITaskManager _taskManager;
+ private readonly ITunerHost[] _tunerHosts;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ public TunerHostManager(
+ ILogger logger,
+ IConfigurationManager config,
+ ITaskManager taskManager,
+ IEnumerable tunerHosts)
+ {
+ _logger = logger;
+ _config = config;
+ _taskManager = taskManager;
+ _tunerHosts = tunerHosts.Where(t => t.IsSupported).ToArray();
+ }
+
+ ///
+ public IReadOnlyList TunerHosts => _tunerHosts;
+
+ ///
+ public IEnumerable GetTunerHostTypes()
+ => _tunerHosts.OrderBy(i => i.Name).Select(i => new NameIdPair
+ {
+ Name = i.Name,
+ Id = i.Type
+ });
+
+ ///
+ public async Task SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
+ {
+ info = JsonSerializer.Deserialize(JsonSerializer.SerializeToUtf8Bytes(info))!;
+
+ var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
+
+ if (provider is null)
+ {
+ throw new ResourceNotFoundException();
+ }
+
+ if (provider is IConfigurableTunerHost configurable)
+ {
+ await configurable.Validate(info).ConfigureAwait(false);
+ }
+
+ var config = _config.GetLiveTvConfiguration();
+
+ var list = config.TunerHosts.ToList();
+ var index = list.FindIndex(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase));
+
+ if (index == -1 || string.IsNullOrWhiteSpace(info.Id))
+ {
+ info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
+ list.Add(info);
+ config.TunerHosts = list.ToArray();
+ }
+ else
+ {
+ config.TunerHosts[index] = info;
+ }
+
+ _config.SaveConfiguration("livetv", config);
+
+ if (dataSourceChanged)
+ {
+ _taskManager.CancelIfRunningAndQueue();
+ }
+
+ return info;
+ }
+
+ ///
+ public async IAsyncEnumerable DiscoverTuners(bool newDevicesOnly)
+ {
+ var configuredDeviceIds = _config.GetLiveTvConfiguration().TunerHosts
+ .Where(i => !string.IsNullOrWhiteSpace(i.DeviceId))
+ .Select(i => i.DeviceId)
+ .ToList();
+
+ foreach (var host in _tunerHosts)
+ {
+ var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, CancellationToken.None).ConfigureAwait(false);
+ foreach (var tuner in discoveredDevices)
+ {
+ if (!newDevicesOnly || !configuredDeviceIds.Contains(tuner.DeviceId, StringComparer.OrdinalIgnoreCase))
+ {
+ yield return tuner;
+ }
+ }
+ }
+ }
+
+ ///
+ public async Task ScanForTunerDeviceChanges(CancellationToken cancellationToken)
+ {
+ foreach (var host in _tunerHosts)
+ {
+ await ScanForTunerDeviceChanges(host, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task ScanForTunerDeviceChanges(ITunerHost host, CancellationToken cancellationToken)
+ {
+ var discoveredDevices = await DiscoverDevices(host, TunerDiscoveryDurationMs, cancellationToken).ConfigureAwait(false);
+
+ var configuredDevices = _config.GetLiveTvConfiguration().TunerHosts
+ .Where(i => string.Equals(i.Type, host.Type, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+
+ foreach (var device in discoveredDevices)
+ {
+ var configuredDevice = configuredDevices.FirstOrDefault(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase));
+
+ if (configuredDevice is not null && !string.Equals(device.Url, configuredDevice.Url, StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.LogInformation("Tuner url has changed from {PreviousUrl} to {NewUrl}", configuredDevice.Url, device.Url);
+
+ configuredDevice.Url = device.Url;
+ await SaveTunerHost(configuredDevice).ConfigureAwait(false);
+ }
+ }
+ }
+
+ private async Task> DiscoverDevices(ITunerHost host, int discoveryDurationMs, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var discoveredDevices = await host.DiscoverDevices(discoveryDurationMs, cancellationToken).ConfigureAwait(false);
+
+ foreach (var device in discoveredDevices)
+ {
+ _logger.LogInformation("Discovered tuner device {0} at {1}", host.Name, device.Url);
+ }
+
+ return discoveredDevices;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error discovering tuner devices");
+
+ return Array.Empty();
+ }
+ }
+}
diff --git a/src/Jellyfin.Networking/AutoDiscoveryHost.cs b/src/Jellyfin.Networking/AutoDiscoveryHost.cs
new file mode 100644
index 0000000000..5624c4ed13
--- /dev/null
+++ b/src/Jellyfin.Networking/AutoDiscoveryHost.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
+using MediaBrowser.Model.ApiClient;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Networking;
+
+///
+/// responsible for responding to auto-discovery messages.
+///
+public sealed class AutoDiscoveryHost : BackgroundService
+{
+ ///
+ /// The port to listen on for auto-discovery messages.
+ ///
+ private const int PortNumber = 7359;
+
+ private readonly ILogger _logger;
+ private readonly IServerApplicationHost _appHost;
+ private readonly IConfigurationManager _configurationManager;
+ private readonly INetworkManager _networkManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ public AutoDiscoveryHost(
+ ILogger logger,
+ IServerApplicationHost appHost,
+ IConfigurationManager configurationManager,
+ INetworkManager networkManager)
+ {
+ _logger = logger;
+ _appHost = appHost;
+ _configurationManager = configurationManager;
+ _networkManager = networkManager;
+ }
+
+ ///
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ var networkConfig = _configurationManager.GetNetworkConfiguration();
+ if (!networkConfig.AutoDiscovery)
+ {
+ return;
+ }
+
+ var udpServers = new List();
+ // Linux needs to bind to the broadcast addresses to receive broadcast traffic
+ if (OperatingSystem.IsLinux() && networkConfig.EnableIPv4)
+ {
+ udpServers.Add(ListenForAutoDiscoveryMessage(IPAddress.Broadcast, stoppingToken));
+ }
+
+ udpServers.AddRange(_networkManager.GetInternalBindAddresses()
+ .Select(intf => ListenForAutoDiscoveryMessage(
+ OperatingSystem.IsLinux() && intf.AddressFamily == AddressFamily.InterNetwork
+ ? NetworkUtils.GetBroadcastAddress(intf.Subnet)
+ : intf.Address,
+ stoppingToken)));
+
+ await Task.WhenAll(udpServers).ConfigureAwait(false);
+ }
+
+ private async Task ListenForAutoDiscoveryMessage(IPAddress address, CancellationToken cancellationToken)
+ {
+ using var udpClient = new UdpClient(new IPEndPoint(address, PortNumber));
+ udpClient.MulticastLoopback = false;
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ var result = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
+ var text = Encoding.UTF8.GetString(result.Buffer);
+ if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
+ {
+ await RespondToV2Message(udpClient, result.RemoteEndPoint, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ catch (SocketException ex)
+ {
+ _logger.LogError(ex, "Failed to receive data from socket");
+ }
+ catch (OperationCanceledException)
+ {
+ _logger.LogDebug("Broadcast socket operation cancelled");
+ }
+ }
+ }
+
+ private async Task RespondToV2Message(UdpClient udpClient, IPEndPoint endpoint, CancellationToken cancellationToken)
+ {
+ var localUrl = _appHost.GetSmartApiUrl(endpoint.Address);
+ if (string.IsNullOrEmpty(localUrl))
+ {
+ _logger.LogWarning("Unable to respond to server discovery request because the local ip address could not be determined.");
+ return;
+ }
+
+ var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName);
+
+ try
+ {
+ _logger.LogDebug("Sending AutoDiscovery response");
+ await udpClient
+ .SendAsync(JsonSerializer.SerializeToUtf8Bytes(response).AsMemory(), endpoint, cancellationToken)
+ .ConfigureAwait(false);
+ }
+ catch (SocketException ex)
+ {
+ _logger.LogError(ex, "Error sending response message");
+ }
+ }
+}
diff --git a/src/Jellyfin.Networking/Udp/UdpServer.cs b/src/Jellyfin.Networking/Udp/UdpServer.cs
deleted file mode 100644
index b130a5a5ff..0000000000
--- a/src/Jellyfin.Networking/Udp/UdpServer.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-using System;
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller;
-using MediaBrowser.Model.ApiClient;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
-
-namespace Jellyfin.Networking.Udp;
-
-///
-/// Provides a Udp Server.
-///
-public sealed class UdpServer : IDisposable
-{
- ///
- /// The _logger.
- ///
- private readonly ILogger _logger;
- private readonly IServerApplicationHost _appHost;
- private readonly IConfiguration _config;
-
- private readonly byte[] _receiveBuffer = new byte[8192];
-
- private readonly Socket _udpSocket;
- private readonly IPEndPoint _endpoint;
- private bool _disposed;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The logger.
- /// The application host.
- /// The configuration manager.
- /// The bind address.
- /// The port.
- public UdpServer(
- ILogger logger,
- IServerApplicationHost appHost,
- IConfiguration configuration,
- IPAddress bindAddress,
- int port)
- {
- _logger = logger;
- _appHost = appHost;
- _config = configuration;
-
- _endpoint = new IPEndPoint(bindAddress, port);
-
- _udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
- {
- MulticastLoopback = false,
- };
- _udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
- }
-
- private async Task RespondToV2Message(EndPoint endpoint, CancellationToken cancellationToken)
- {
- string? localUrl = _config[AddressOverrideKey];
- if (string.IsNullOrEmpty(localUrl))
- {
- localUrl = _appHost.GetSmartApiUrl(((IPEndPoint)endpoint).Address);
- }
-
- if (string.IsNullOrEmpty(localUrl))
- {
- _logger.LogWarning("Unable to respond to server discovery request because the local ip address could not be determined.");
- return;
- }
-
- var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName);
-
- try
- {
- _logger.LogDebug("Sending AutoDiscovery response");
- await _udpSocket.SendToAsync(JsonSerializer.SerializeToUtf8Bytes(response), SocketFlags.None, endpoint, cancellationToken).ConfigureAwait(false);
- }
- catch (SocketException ex)
- {
- _logger.LogError(ex, "Error sending response message");
- }
- }
-
- ///
- /// Starts the specified port.
- ///
- /// The cancellation token to cancel operation.
- public void Start(CancellationToken cancellationToken)
- {
- _udpSocket.Bind(_endpoint);
-
- _ = Task.Run(async () => await BeginReceiveAsync(cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
- }
-
- private async Task BeginReceiveAsync(CancellationToken cancellationToken)
- {
- while (!cancellationToken.IsCancellationRequested)
- {
- try
- {
- var endpoint = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
- var result = await _udpSocket.ReceiveFromAsync(_receiveBuffer, endpoint, cancellationToken).ConfigureAwait(false);
- var text = Encoding.UTF8.GetString(_receiveBuffer, 0, result.ReceivedBytes);
- if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
- {
- await RespondToV2Message(result.RemoteEndPoint, cancellationToken).ConfigureAwait(false);
- }
- }
- catch (SocketException ex)
- {
- _logger.LogError(ex, "Failed to receive data from socket");
- }
- catch (OperationCanceledException)
- {
- _logger.LogDebug("Broadcast socket operation cancelled");
- }
- }
- }
-
- ///
- public void Dispose()
- {
- if (_disposed)
- {
- return;
- }
-
- _udpSocket.Dispose();
- _disposed = true;
- }
-}
diff --git a/src/Jellyfin.Networking/UdpServerEntryPoint.cs b/src/Jellyfin.Networking/UdpServerEntryPoint.cs
deleted file mode 100644
index 61180c3c0f..0000000000
--- a/src/Jellyfin.Networking/UdpServerEntryPoint.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Sockets;
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Networking.Udp;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Plugins;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using IConfigurationManager = MediaBrowser.Common.Configuration.IConfigurationManager;
-
-namespace Jellyfin.Networking;
-
-///
-/// Class responsible for registering all UDP broadcast endpoints and their handlers.
-///
-public sealed class UdpServerEntryPoint : IServerEntryPoint
-{
- ///
- /// The port of the UDP server.
- ///
- public const int PortNumber = 7359;
-
- ///
- /// The logger.
- ///
- private readonly ILogger _logger;
- private readonly IServerApplicationHost _appHost;
- private readonly IConfiguration _config;
- private readonly IConfigurationManager _configurationManager;
- private readonly INetworkManager _networkManager;
-
- ///
- /// The UDP server.
- ///
- private readonly List _udpServers;
- private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
- private bool _disposed;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- /// Instance of the interface.
- public UdpServerEntryPoint(
- ILogger logger,
- IServerApplicationHost appHost,
- IConfiguration configuration,
- IConfigurationManager configurationManager,
- INetworkManager networkManager)
- {
- _logger = logger;
- _appHost = appHost;
- _config = configuration;
- _configurationManager = configurationManager;
- _networkManager = networkManager;
- _udpServers = new List();
- }
-
- ///
- public Task RunAsync()
- {
- ObjectDisposedException.ThrowIf(_disposed, this);
-
- if (!_configurationManager.GetNetworkConfiguration().AutoDiscovery)
- {
- return Task.CompletedTask;
- }
-
- try
- {
- // Linux needs to bind to the broadcast addresses to get broadcast traffic
- // Windows receives broadcast fine when binding to just the interface, it is unable to bind to broadcast addresses
- if (OperatingSystem.IsLinux())
- {
- // Add global broadcast listener
- var server = new UdpServer(_logger, _appHost, _config, IPAddress.Broadcast, PortNumber);
- server.Start(_cancellationTokenSource.Token);
- _udpServers.Add(server);
-
- // Add bind address specific broadcast listeners
- // IPv6 is currently unsupported
- var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
- foreach (var intf in validInterfaces)
- {
- var broadcastAddress = NetworkUtils.GetBroadcastAddress(intf.Subnet);
- _logger.LogDebug("Binding UDP server to {Address} on port {PortNumber}", broadcastAddress, PortNumber);
-
- server = new UdpServer(_logger, _appHost, _config, broadcastAddress, PortNumber);
- server.Start(_cancellationTokenSource.Token);
- _udpServers.Add(server);
- }
- }
- else
- {
- // Add bind address specific broadcast listeners
- // IPv6 is currently unsupported
- var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
- foreach (var intf in validInterfaces)
- {
- var intfAddress = intf.Address;
- _logger.LogDebug("Binding UDP server to {Address} on port {PortNumber}", intfAddress, PortNumber);
-
- var server = new UdpServer(_logger, _appHost, _config, intfAddress, PortNumber);
- server.Start(_cancellationTokenSource.Token);
- _udpServers.Add(server);
- }
- }
- }
- catch (SocketException ex)
- {
- _logger.LogWarning(ex, "Unable to start AutoDiscovery listener on UDP port {PortNumber}", PortNumber);
- }
-
- return Task.CompletedTask;
- }
-
- ///
- public void Dispose()
- {
- if (_disposed)
- {
- return;
- }
-
- _cancellationTokenSource.Cancel();
- _cancellationTokenSource.Dispose();
- foreach (var server in _udpServers)
- {
- server.Dispose();
- }
-
- _udpServers.Clear();
- _disposed = true;
- }
-}
diff --git a/tests/Jellyfin.Api.Tests/Controllers/ImageControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/ImageControllerTests.cs
index 0254a1ec6e..5034ad3c71 100644
--- a/tests/Jellyfin.Api.Tests/Controllers/ImageControllerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Controllers/ImageControllerTests.cs
@@ -27,7 +27,7 @@ public static class ImageControllerTests
[InlineData(null)]
[InlineData("")]
[InlineData("text/html")]
- public static void TryGetImageExtensionFromContentType_InValid_False(string contentType)
+ public static void TryGetImageExtensionFromContentType_InValid_False(string? contentType)
{
Assert.False(ImageController.TryGetImageExtensionFromContentType(contentType, out var ex));
Assert.Null(ex);
diff --git a/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs b/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs
index d46beedd99..95f9a5fcfb 100644
--- a/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs
@@ -8,20 +8,18 @@ namespace Jellyfin.Extensions.Tests
{
public static TheoryData, IList, int, IList> CopyTo_Valid_Correct_TestData()
{
- var data = new TheoryData, IList, int, IList