Compare commits

..

14 Commits

Author SHA1 Message Date
dkanada da34bd940e Merge pull request #2478 from JustAMan/fix-search-order
Fix ordering of search results

(cherry picked from commit 0d9787dfb4)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-03-22 15:25:11 -04:00
Joshua M. Boniface 54efde4073 Merge pull request #2642 from mark-monteiro/fix-extras
Add missing null check when retrieving extras

(cherry picked from commit 425bd2b01b)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-03-22 15:24:54 -04:00
Joshua M. Boniface 02cfa15582 Quickly fix failing curl in ARM Dockerfile 2020-03-22 15:02:03 -04:00
Joshua M. Boniface 0e171d794c Bump version to 10.5.2 2020-03-22 12:13:59 -04:00
dkanada d5f2384375 Merge pull request #2617 from Shawmon/wasm-mimetype
add wasm mimetype

(cherry picked from commit af5d3e8eae)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-03-22 11:48:18 -04:00
dkanada 816b3f5c2e Merge pull request #2622 from Artiume/patch-5
Fix Release 10.5.z
2020-03-21 16:26:16 +09:00
artiume a234388552 Fix arm64 2020-03-17 10:34:25 -04:00
artiume 30d38bd5c6 Update armhf 2020-03-17 10:33:06 -04:00
artiume 9f49fe2e99 Fix Release 10.5.z 2020-03-17 09:38:22 -04:00
Joshua M. Boniface f720a0fca2 Fix mangled Dockerfiles 2020-03-16 09:30:01 -04:00
Joshua M. Boniface aadff77531 Merge pull request #2607 from joshuaboniface/fix-fedora
Correct BuildRequires and NodeJS for Fedora/CentOS

(cherry picked from commit ef4dfd4461)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-03-15 23:06:22 -04:00
dkanada 89fc5aa11a Merge pull request #2582 from Bond-009/subs
Fix subtitles

(cherry picked from commit 6960f0af67)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-03-15 23:05:41 -04:00
dkanada cb91595a24 Merge pull request #2541 from joshuaboniface/fix-docker-arm
Fix curl for Jellyfin GPG key

(cherry picked from commit bf34105af3)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2020-03-15 23:05:13 -04:00
Joshua M. Boniface 6233bae7f0 Bump version to 10.5.1 2020-03-15 23:04:07 -04:00
1564 changed files with 19472 additions and 34225 deletions
@@ -1,13 +1,13 @@
parameters: parameters:
- name: Packages - name: Packages
type: object type: object
default: {} default: {}
- name: LinuxImage - name: LinuxImage
type: string type: string
default: "ubuntu-latest" default: "ubuntu-latest"
- name: DotNetSdkVersion - name: DotNetSdkVersion
type: string type: string
default: 3.1.100 default: 3.1.100
jobs: jobs:
- job: CompatibilityCheck - job: CompatibilityCheck
@@ -23,7 +23,7 @@ jobs:
NugetPackageName: ${{ Package.value.NugetPackageName }} NugetPackageName: ${{ Package.value.NugetPackageName }}
AssemblyFileName: ${{ Package.value.AssemblyFileName }} AssemblyFileName: ${{ Package.value.AssemblyFileName }}
maxParallel: 2 maxParallel: 2
dependsOn: Build dependsOn: MainBuild
steps: steps:
- checkout: none - checkout: none
@@ -33,13 +33,6 @@ jobs:
packageType: sdk packageType: sdk
version: ${{ parameters.DotNetSdkVersion }} version: ${{ parameters.DotNetSdkVersion }}
- task: DotNetCoreCLI@2
displayName: 'Install ABI CompatibilityChecker tool'
inputs:
command: custom
custom: tool
arguments: 'update compatibilitychecker -g'
- task: DownloadPipelineArtifact@2 - task: DownloadPipelineArtifact@2
displayName: "Download New Assembly Build Artifact" displayName: "Download New Assembly Build Artifact"
inputs: inputs:
@@ -79,11 +72,25 @@ jobs:
overWrite: true overWrite: true
flattenFolders: true flattenFolders: true
# The `--warnings-only` switch will swallow the return code and not emit any errors. - task: DownloadGitHubRelease@0
- task: DotNetCoreCLI@2 displayName: "Download ABI Compatibility Check Tool"
displayName: 'Execute ABI Compatibility Check Tool'
inputs: inputs:
command: custom connection: Jellyfin Release Download
custom: compat userRepository: EraYaN/dotnet-compatibility
arguments: 'current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only' defaultVersionType: "latest"
itemPattern: "**-ci.zip"
downloadPath: "$(System.ArtifactsDirectory)"
- task: ExtractFiles@1
displayName: "Extract ABI Compatibility Check Tool"
inputs:
archiveFilePatterns: "$(System.ArtifactsDirectory)/*-ci.zip"
destinationFolder: $(System.ArtifactsDirectory)/tools
cleanDestinationFolder: true
# The `--warnings-only` switch will swallow the return code and not emit any errors.
- task: CmdLine@2
displayName: "Execute ABI Compatibility Check Tool"
inputs:
script: "dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only"
workingDirectory: $(System.ArtifactsDirectory) workingDirectory: $(System.ArtifactsDirectory)
+52 -44
View File
@@ -1,93 +1,101 @@
parameters: parameters:
LinuxImage: 'ubuntu-latest' LinuxImage: "ubuntu-latest"
RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj' RestoreBuildProjects: "Jellyfin.Server/Jellyfin.Server.csproj"
DotNetSdkVersion: 3.1.100 DotNetSdkVersion: 3.1.100
jobs: jobs:
- job: Build - job: MainBuild
displayName: Build displayName: Main Build
strategy: strategy:
matrix: matrix:
Release: Release:
BuildConfiguration: Release BuildConfiguration: Release
Debug: Debug:
BuildConfiguration: Debug BuildConfiguration: Debug
maxParallel: 2
pool: pool:
vmImage: '${{ parameters.LinuxImage }}' vmImage: "${{ parameters.LinuxImage }}"
steps: steps:
- checkout: self - checkout: self
clean: true clean: true
submodules: true submodules: true
persistCredentials: true persistCredentials: true
- task: DownloadPipelineArtifact@2 - task: CmdLine@2
displayName: 'Download Web Branch' displayName: "Clone Web Client (Master, Release, or Tag)"
condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion') condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs: inputs:
path: '$(Agent.TempDirectory)' script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
artifact: 'jellyfin-web-production'
source: 'specific'
project: 'jellyfin'
pipeline: 'Jellyfin Web'
runBranch: variables['Build.SourceBranch']
- task: DownloadPipelineArtifact@2 - task: CmdLine@2
displayName: 'Download Web Target' displayName: "Clone Web Client (PR)"
condition: eq(variables['Build.Reason'], 'PullRequest') condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
inputs: inputs:
path: '$(Agent.TempDirectory)' script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
artifact: 'jellyfin-web-production'
source: 'specific'
project: 'jellyfin'
pipeline: 'Jellyfin Web'
runBranch: variables['System.PullRequest.TargetBranch']
- task: ExtractFiles@1 - task: NodeTool@0
displayName: 'Extract Web Client' displayName: "Install Node"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs: inputs:
archiveFilePatterns: '$(Agent.TempDirectory)/*.zip' versionSpec: "10.x"
destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
cleanDestinationFolder: false - task: CmdLine@2
displayName: "Build Web Client"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: yarn install
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
- task: CopyFiles@2
displayName: "Copy Web Client"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist
contents: "**"
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
cleanTargetFolder: true
overWrite: true
flattenFolders: false
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Update DotNet' displayName: "Update DotNet"
inputs: inputs:
packageType: sdk packageType: sdk
version: ${{ parameters.DotNetSdkVersion }} version: ${{ parameters.DotNetSdkVersion }}
- task: DotNetCoreCLI@2 - task: DotNetCoreCLI@2
displayName: 'Publish Server' displayName: "Publish Server"
inputs: inputs:
command: publish command: publish
publishWebProjects: false publishWebProjects: false
projects: '${{ parameters.RestoreBuildProjects }}' projects: "${{ parameters.RestoreBuildProjects }}"
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)' arguments: "--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)"
zipAfterPublish: false zipAfterPublish: false
- task: PublishPipelineArtifact@0 - task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Naming' displayName: "Publish Artifact Naming"
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs: inputs:
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll' targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll"
artifactName: 'Jellyfin.Naming' artifactName: "Jellyfin.Naming"
- task: PublishPipelineArtifact@0 - task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Controller' displayName: "Publish Artifact Controller"
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs: inputs:
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll' targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
artifactName: 'Jellyfin.Controller' artifactName: "Jellyfin.Controller"
- task: PublishPipelineArtifact@0 - task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Model' displayName: "Publish Artifact Model"
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs: inputs:
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll' targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
artifactName: 'Jellyfin.Model' artifactName: "Jellyfin.Model"
- task: PublishPipelineArtifact@0 - task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Common' displayName: "Publish Artifact Common"
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release')) condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs: inputs:
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll' targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
artifactName: 'Jellyfin.Common' artifactName: "Jellyfin.Common"
-171
View File
@@ -1,171 +0,0 @@
jobs:
- job: BuildPackage
displayName: 'Build Packages'
strategy:
matrix:
CentOS.amd64:
BuildConfiguration: centos.amd64
Fedora.amd64:
BuildConfiguration: fedora.amd64
Debian.amd64:
BuildConfiguration: debian.amd64
Debian.arm64:
BuildConfiguration: debian.arm64
Debian.armhf:
BuildConfiguration: debian.armhf
Ubuntu.amd64:
BuildConfiguration: ubuntu.amd64
Ubuntu.arm64:
BuildConfiguration: ubuntu.arm64
Ubuntu.armhf:
BuildConfiguration: ubuntu.armhf
Linux.amd64:
BuildConfiguration: linux.amd64
Windows.amd64:
BuildConfiguration: windows.amd64
MacOS:
BuildConfiguration: macos
Portable:
BuildConfiguration: portable
pool:
vmImage: 'ubuntu-latest'
steps:
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
displayName: 'Build Dockerfile'
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
displayName: 'Run Dockerfile (unstable)'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="no" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
displayName: 'Run Dockerfile (stable)'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
- task: PublishPipelineArtifact@1
displayName: 'Publish Release'
inputs:
targetPath: '$(Build.SourcesDirectory)/deployment/dist'
artifactName: 'jellyfin-server-$(BuildConfiguration)'
- task: SSH@0
displayName: 'Create target directory on repository server'
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
- task: CopyFilesOverSSH@0
displayName: 'Upload artifacts to repository server'
inputs:
sshEndpoint: repository
sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
contents: '**'
targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
- job: BuildDocker
displayName: 'Build Docker'
strategy:
matrix:
amd64:
BuildConfiguration: amd64
arm64:
BuildConfiguration: arm64
armhf:
BuildConfiguration: armhf
pool:
vmImage: 'ubuntu-latest'
variables:
- name: JellyfinVersion
value: 0.0.0
steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
- task: Docker@2
displayName: 'Push Unstable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
repository: 'jellyfin/jellyfin-server'
command: buildAndPush
buildContext: '.'
Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)'
containerRegistry: Docker Hub
tags: |
unstable-$(Build.BuildNumber)-$(BuildConfiguration)
unstable-$(BuildConfiguration)
- task: Docker@2
displayName: 'Push Stable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
inputs:
repository: 'jellyfin/jellyfin-server'
command: buildAndPush
buildContext: '.'
Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)'
containerRegistry: Docker Hub
tags: |
stable-$(Build.BuildNumber)-$(BuildConfiguration)
$(JellyfinVersion)-$(BuildConfiguration)
- job: CollectArtifacts
displayName: 'Collect Artifacts'
dependsOn:
- BuildPackage
- BuildDocker
condition: and(succeeded('BuildPackage'), succeeded('BuildDocker'))
pool:
vmImage: 'ubuntu-latest'
steps:
- task: SSH@0
displayName: 'Update Unstable Repository'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: |
sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable
rm $0
exit
- task: SSH@0
displayName: 'Update Stable Repository'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: |
sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)
rm $0
exit
- job: PublishNuget
displayName: 'Publish NuGet packages'
dependsOn:
- BuildPackage
condition: and(succeeded('BuildPackage'), startsWith(variables['Build.SourceBranch'], 'refs/tags'))
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NuGetCommand@2
inputs:
command: 'pack'
packagesToPack: Jellyfin.Data/Jellyfin.Data.csproj;MediaBrowser.Common/MediaBrowser.Common.csproj;MediaBrowser.Controller/MediaBrowser.Controller.csproj;MediaBrowser.Model/MediaBrowser.Model.csproj;Emby.Naming/Emby.Naming.csproj
packDestination: '$(Build.ArtifactStagingDirectory)'
- task: NuGetCommand@2
inputs:
command: 'push'
packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
includeNugetOrg: 'true'
+18 -46
View File
@@ -1,25 +1,26 @@
parameters: parameters:
- name: ImageNames - name: ImageNames
type: object type: object
default: default:
Linux: "ubuntu-latest" Linux: "ubuntu-latest"
Windows: "windows-latest" Windows: "windows-latest"
macOS: "macos-latest" macOS: "macos-latest"
- name: TestProjects - name: TestProjects
type: string type: string
default: "tests/**/*Tests.csproj" default: "tests/**/*Tests.csproj"
- name: DotNetSdkVersion - name: DotNetSdkVersion
type: string type: string
default: 3.1.100 default: 3.1.100
jobs: jobs:
- job: Test - job: MainTest
displayName: Test displayName: Main Test
strategy: strategy:
matrix: matrix:
${{ each imageName in parameters.ImageNames }}: ${{ each imageName in parameters.ImageNames }}:
${{ imageName.key }}: ${{ imageName.key }}:
ImageName: ${{ imageName.value }} ImageName: ${{ imageName.value }}
maxParallel: 3
pool: pool:
vmImage: "$(ImageName)" vmImage: "$(ImageName)"
steps: steps:
@@ -28,31 +29,14 @@ jobs:
submodules: true submodules: true
persistCredentials: false persistCredentials: false
# This is required for the SonarCloud analyzer
- task: UseDotNet@2
displayName: "Install .NET Core SDK 2.1"
condition: eq(variables['ImageName'], 'ubuntu-latest')
inputs:
packageType: sdk
version: '2.1.805'
- task: UseDotNet@2 - task: UseDotNet@2
displayName: "Update DotNet" displayName: "Update DotNet"
inputs: inputs:
packageType: sdk packageType: sdk
version: ${{ parameters.DotNetSdkVersion }} version: ${{ parameters.DotNetSdkVersion }}
- task: SonarCloudPrepare@1
displayName: 'Prepare analysis on SonarCloud'
condition: eq(variables['ImageName'], 'ubuntu-latest')
enabled: false
inputs:
SonarCloud: 'Sonarcloud for Jellyfin'
organization: 'jellyfin'
projectKey: 'jellyfin_jellyfin'
- task: DotNetCoreCLI@2 - task: DotNetCoreCLI@2
displayName: 'Run CLI Tests' displayName: Run .NET Core CLI tests
inputs: inputs:
command: "test" command: "test"
projects: ${{ parameters.TestProjects }} projects: ${{ parameters.TestProjects }}
@@ -61,20 +45,9 @@ jobs:
testRunTitle: $(Agent.JobName) testRunTitle: $(Agent.JobName)
workingDirectory: "$(Build.SourcesDirectory)" workingDirectory: "$(Build.SourcesDirectory)"
- task: SonarCloudAnalyze@1
displayName: 'Run Code Analysis'
condition: eq(variables['ImageName'], 'ubuntu-latest')
enabled: false
- task: SonarCloudPublish@1
displayName: 'Publish Quality Gate Result'
condition: eq(variables['ImageName'], 'ubuntu-latest')
enabled: false
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
displayName: 'Run ReportGenerator' displayName: ReportGenerator (merge)
enabled: false
inputs: inputs:
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml" reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
targetdir: "$(Agent.TempDirectory)/merged/" targetdir: "$(Agent.TempDirectory)/merged/"
@@ -83,8 +56,7 @@ jobs:
## V2 is already in the repository but it does not work "wrong number of segments" YAML error. ## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
- task: PublishCodeCoverageResults@1 - task: PublishCodeCoverageResults@1
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
displayName: 'Publish Code Coverage' displayName: Publish Code Coverage
enabled: false
inputs: inputs:
codeCoverageTool: "cobertura" codeCoverageTool: "cobertura"
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2 #summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
+82
View File
@@ -0,0 +1,82 @@
parameters:
WindowsImage: "windows-latest"
TestProjects: "tests/**/*Tests.csproj"
DotNetSdkVersion: 3.1.100
jobs:
- job: PublishWindows
displayName: Publish Windows
pool:
vmImage: ${{ parameters.WindowsImage }}
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: true
- task: CmdLine@2
displayName: "Clone Web Client (Master, Release, or Tag)"
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
- task: CmdLine@2
displayName: "Clone Web Client (PR)"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest'))
inputs:
script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
- task: NodeTool@0
displayName: "Install Node"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
versionSpec: "10.x"
- task: CmdLine@2
displayName: "Build Web Client"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: yarn install
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
- task: CopyFiles@2
displayName: "Copy Web Client"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist
contents: "**"
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
cleanTargetFolder: true
overWrite: true
flattenFolders: false
- task: CmdLine@2
displayName: "Clone UX Repository"
inputs:
script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
- task: PowerShell@2
displayName: "Build NSIS Installer"
inputs:
targetType: "filePath"
filePath: ./deployment/windows/build-jellyfin.ps1
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
errorActionPreference: "stop"
workingDirectory: $(Build.SourcesDirectory)
- task: CopyFiles@2
displayName: "Copy NSIS Installer"
inputs:
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/
contents: "jellyfin*.exe"
targetFolder: $(System.ArtifactsDirectory)/setup
cleanTargetFolder: true
overWrite: true
flattenFolders: true
- task: PublishPipelineArtifact@0
displayName: "Publish Artifact Setup"
condition: succeeded()
inputs:
targetPath: "$(build.artifactstagingdirectory)/setup"
artifactName: "Jellyfin Server Setup"
+17 -18
View File
@@ -1,12 +1,12 @@
name: $(Date:yyyyMMdd)$(Rev:.r) name: $(Date:yyyyMMdd)$(Rev:.r)
variables: variables:
- name: TestProjects - name: TestProjects
value: 'tests/**/*Tests.csproj' value: "tests/**/*Tests.csproj"
- name: RestoreBuildProjects - name: RestoreBuildProjects
value: 'Jellyfin.Server/Jellyfin.Server.csproj' value: "Jellyfin.Server/Jellyfin.Server.csproj"
- name: DotNetSdkVersion - name: DotNetSdkVersion
value: 3.1.100 value: 3.1.100
pr: pr:
autoCancel: true autoCancel: true
@@ -15,22 +15,24 @@ trigger:
batch: true batch: true
jobs: jobs:
- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-main.yml - template: azure-pipelines-main.yml
parameters: parameters:
LinuxImage: 'ubuntu-latest' LinuxImage: "ubuntu-latest"
RestoreBuildProjects: $(RestoreBuildProjects) RestoreBuildProjects: $(RestoreBuildProjects)
- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-test.yml - template: azure-pipelines-test.yml
parameters: parameters:
ImageNames: ImageNames:
Linux: 'ubuntu-latest' Linux: "ubuntu-latest"
Windows: 'windows-latest' Windows: "windows-latest"
macOS: 'macos-latest' macOS: "macos-latest"
- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}: - template: azure-pipelines-windows.yml
- template: azure-pipelines-abi.yml parameters:
WindowsImage: "windows-latest"
TestProjects: $(TestProjects)
- template: azure-pipelines-compat.yml
parameters: parameters:
Packages: Packages:
Naming: Naming:
@@ -45,7 +47,4 @@ jobs:
Common: Common:
NugetPackageName: Jellyfin.Common NugetPackageName: Jellyfin.Common
AssemblyFileName: MediaBrowser.Common.dll AssemblyFileName: MediaBrowser.Common.dll
LinuxImage: 'ubuntu-latest' LinuxImage: "ubuntu-latest"
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- template: azure-pipelines-package.yml
-1
View File
@@ -1 +0,0 @@
../fedora/Makefile
+59
View File
@@ -0,0 +1,59 @@
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
deployment/fedora-package-x64/pkg-src/jellyfin.spec)
deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
|| curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
cd deployment/fedora-package-x64; \
SOURCE_DIR=../.. \
WORKDIR="$${PWD}"; \
package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
pkg_src_dir="$${WORKDIR}/pkg-src"; \
GNU_TAR=1; \
tar \
--transform "s,^\.,jellyfin-$(VERSION)," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='**/.vs' \
--exclude='**/.vscode' \
--exclude='deployment' \
--exclude='**/bin' \
--exclude='**/obj' \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-czf "pkg-src/jellyfin-$(VERSION).tar.gz" \
-C $${SOURCE_DIR} ./ || GNU_TAR=0; \
if [ $${GNU_TAR} -eq 0 ]; then \
package_temporary_dir="$$(mktemp -d)"; \
mkdir -p "$${package_temporary_dir}/jellyfin"; \
tar \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='**/.vs' \
--exclude='**/.vscode' \
--exclude='deployment' \
--exclude='**/bin' \
--exclude='**/obj' \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
-C $${SOURCE_DIR} ./; \
mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
-C "$${package_temporary_dir}/jellyfin-$(VERSION); \
rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
-C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
rm -rf $${package_temporary_dir}; \
fi; \
rpmbuild -bs pkg-src/jellyfin.spec \
--define "_sourcedir $$PWD/pkg-src/" \
--define "_srcrpmdir $(outdir)"
+10 -7
View File
@@ -13,7 +13,7 @@ charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
end_of_line = lf end_of_line = lf
max_line_length = off max_line_length = null
# YAML indentation # YAML indentation
[*.{yml,yaml}] [*.{yml,yaml}]
@@ -22,7 +22,6 @@ indent_size = 2
# XML indentation # XML indentation
[*.{csproj,xml}] [*.{csproj,xml}]
indent_size = 2 indent_size = 2
############################### ###############################
# .NET Coding Conventions # # .NET Coding Conventions #
############################### ###############################
@@ -52,12 +51,11 @@ dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent dotnet_style_prefer_conditional_expression_over_return = true:silent
############################### ###############################
# Naming Conventions # # Naming Conventions #
############################### ###############################
@@ -69,7 +67,7 @@ dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
@@ -161,7 +159,6 @@ csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion csharp_style_inlined_variable_declaration = true:suggestion
############################### ###############################
# C# Formatting Rules # # C# Formatting Rules #
############################### ###############################
@@ -192,3 +189,9 @@ csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences # Wrapping preferences
csharp_preserve_single_line_statements = true csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true csharp_preserve_single_line_blocks = true
###############################
# VB Coding Conventions #
###############################
[*.vb]
# Modifier preferences
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
-3
View File
@@ -1,3 +0,0 @@
# Joshua must review all changes to deployment and build.sh
deployment/* @joshuaboniface
build.sh @joshuaboniface
-13
View File
@@ -1,13 +0,0 @@
---
name: Feature Request
about: Request a new feature
title: ''
labels: feature-request
assignees: ''
---
**PLEASE DO NOT OPEN FEATURE REQUEST ISSUES ON GITHUB**
**Feature requests should be opened on our dedicated [feature request](https://features.jellyfin.org/) hub so they can be appropriately discussed and prioritized.**
However, if you are willing to contribute to the project by adding a new feature yourself, then please ensure that you first review our [documentation](https://docs.jellyfin.org/general/contributing/development.html) on contributing code. Once you have reviewed the documentation, feel free to come back here and open an issue here outlining your proposed approach so that it can be documented, tracked, and discussed by other team members.
-9
View File
@@ -1,9 +0,0 @@
version: 2
updates:
- package-ecosystem: nuget
directory: "/"
schedule:
interval: weekly
time: '12:00'
open-pull-requests-limit: 10
+7 -12
View File
@@ -244,14 +244,14 @@ pip-log.txt
######################### #########################
# Artifacts for debian-x64 # Artifacts for debian-x64
debian/.debhelper/ deployment/debian-package-x64/pkg-src/.debhelper/
debian/*.debhelper deployment/debian-package-x64/pkg-src/*.debhelper
debian/debhelper-build-stamp deployment/debian-package-x64/pkg-src/debhelper-build-stamp
debian/files deployment/debian-package-x64/pkg-src/files
debian/jellyfin.substvars deployment/debian-package-x64/pkg-src/jellyfin.substvars
debian/jellyfin/ deployment/debian-package-x64/pkg-src/jellyfin/
# Don't ignore the debian/bin folder # Don't ignore the debian/bin folder
!debian/bin/ !deployment/debian-package-x64/pkg-src/bin/
deployment/**/dist/ deployment/**/dist/
deployment/**/pkg-dist/ deployment/**/pkg-dist/
@@ -271,8 +271,3 @@ dist
# BenchmarkDotNet artifacts # BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts BenchmarkDotNet.Artifacts
# Ignore web artifacts from native builds
web/
web-src.*
MediaBrowser.WebDashboard/jellyfin-web
-14
View File
@@ -1,14 +0,0 @@
{
// 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"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]
}
+6 -6
View File
@@ -1,6 +1,9 @@
{ {
"version": "0.2.0", // Use IntelliSense to find out which attributes exist for C# debugging
"configurations": [ // Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{ {
"name": ".NET Core Launch (console)", "name": ".NET Core Launch (console)",
"type": "coreclr", "type": "coreclr",
@@ -21,8 +24,5 @@
"request": "attach", "request": "attach",
"processId": "${command:pickProcess}" "processId": "${command:pickProcess}"
} }
], ,]
"env": {
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
}
} }
+1 -16
View File
@@ -10,21 +10,6 @@
"${workspaceFolder}/Jellyfin.Server/Jellyfin.Server.csproj" "${workspaceFolder}/Jellyfin.Server/Jellyfin.Server.csproj"
], ],
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
},
{
"label": "api tests",
"command": "dotnet",
"type": "process",
"args": [
"test",
"${workspaceFolder}/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj"
],
"problemMatcher": "$msCompile"
} }
], ]
"options": {
"env": {
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
}
}
} }
-5
View File
@@ -7,7 +7,6 @@
- [anthonylavado](https://github.com/anthonylavado) - [anthonylavado](https://github.com/anthonylavado)
- [Artiume](https://github.com/Artiume) - [Artiume](https://github.com/Artiume)
- [AThomsen](https://github.com/AThomsen) - [AThomsen](https://github.com/AThomsen)
- [barronpm](https://github.com/barronpm)
- [bilde2910](https://github.com/bilde2910) - [bilde2910](https://github.com/bilde2910)
- [bfayers](https://github.com/bfayers) - [bfayers](https://github.com/bfayers)
- [BnMcG](https://github.com/BnMcG) - [BnMcG](https://github.com/BnMcG)
@@ -23,7 +22,6 @@
- [cvium](https://github.com/cvium) - [cvium](https://github.com/cvium)
- [dannymichel](https://github.com/dannymichel) - [dannymichel](https://github.com/dannymichel)
- [DaveChild](https://github.com/DaveChild) - [DaveChild](https://github.com/DaveChild)
- [Delgan](https://github.com/Delgan)
- [dcrdev](https://github.com/dcrdev) - [dcrdev](https://github.com/dcrdev)
- [dhartung](https://github.com/dhartung) - [dhartung](https://github.com/dhartung)
- [dinki](https://github.com/dinki) - [dinki](https://github.com/dinki)
@@ -93,7 +91,6 @@
- [samuel9554](https://github.com/samuel9554) - [samuel9554](https://github.com/samuel9554)
- [scheidleon](https://github.com/scheidleon) - [scheidleon](https://github.com/scheidleon)
- [sebPomme](https://github.com/sebPomme) - [sebPomme](https://github.com/sebPomme)
- [SegiH](https://github.com/SegiH)
- [SenorSmartyPants](https://github.com/SenorSmartyPants) - [SenorSmartyPants](https://github.com/SenorSmartyPants)
- [shemanaev](https://github.com/shemanaev) - [shemanaev](https://github.com/shemanaev)
- [skaro13](https://github.com/skaro13) - [skaro13](https://github.com/skaro13)
@@ -130,8 +127,6 @@
- [xosdy](https://github.com/xosdy) - [xosdy](https://github.com/xosdy)
- [XVicarious](https://github.com/XVicarious) - [XVicarious](https://github.com/XVicarious)
- [YouKnowBlom](https://github.com/YouKnowBlom) - [YouKnowBlom](https://github.com/YouKnowBlom)
- [KristupasSavickas](https://github.com/KristupasSavickas)
- [Pusta](https://github.com/pusta)
# Emby Contributors # Emby Contributors
+23 -19
View File
@@ -1,11 +1,13 @@
ARG DOTNET_VERSION=3.1 ARG DOTNET_VERSION=3.1
ARG FFMPEG_VERSION=latest
FROM node:alpine as web-builder FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master ARG JELLYFIN_WEB_VERSION=10.5.2
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ RUN apk add curl git \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && git clone --branch release-10.5.z --single-branch https://github.com/jellyfin/jellyfin-web.git \
&& cd jellyfin-web-* \ && cd jellyfin-web \
&& yarn install \ && yarn install \
&& yarn build \
&& mv dist /dist && mv dist /dist
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder
@@ -16,6 +18,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting # 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:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
FROM debian:buster-slim FROM debian:buster-slim
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
@@ -25,36 +28,37 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) # https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg
COPY --from=builder /jellyfin /jellyfin COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web COPY --from=web-builder /dist /jellyfin/jellyfin-web
# Install dependencies: # Install dependencies:
# mesa-va-drivers: needed for AMD VAAPI # libfontconfig1: needed for Skia
# libgomp1: needed for ffmpeg
# libva-drm2: needed for ffmpeg
# mesa-va-drivers: needed for VAAPI
RUN apt-get update \ RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https \
&& wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
&& 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 \ && apt-get install --no-install-recommends --no-install-suggests -y \
libfontconfig1 \
libgomp1 \
libva-drm2 \
mesa-va-drivers \ mesa-va-drivers \
jellyfin-ffmpeg \
openssl \ openssl \
locales \ ca-certificates \
&& apt-get remove gnupg wget apt-transport-https -y \ vainfo \
&& apt-get clean autoclean -y \ i965-va-driver \
&& apt-get autoremove -y \ && apt-get clean autoclean -y\
&& apt-get autoremove -y\
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \ && mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media \ && chmod 777 /cache /config /media \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen && ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \
&& ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
EXPOSE 8096 EXPOSE 8096
VOLUME /cache /config /media VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \ ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \ "--datadir", "/config", \
"--cachedir", "/cache", \ "--cachedir", "/cache", \
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] "--ffmpeg", "/usr/local/bin/ffmpeg"]
+7 -12
View File
@@ -6,11 +6,12 @@ ARG DOTNET_VERSION=3.1
FROM node:alpine as web-builder FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master ARG JELLYFIN_WEB_VERSION=10.5.2
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ RUN apk add curl git \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && git clone --branch release-10.5.z --single-branch https://github.com/jellyfin/jellyfin-web.git \
&& cd jellyfin-web-* \ && cd jellyfin-web \
&& yarn install \ && yarn install \
&& yarn build \
&& mv dist /dist && mv dist /dist
@@ -52,26 +53,20 @@ RUN apt-get update \
libraspberrypi0 \ libraspberrypi0 \
vainfo \ vainfo \
libva2 \ libva2 \
locales \
&& apt-get remove curl gnupg -y \ && apt-get remove curl gnupg -y \
&& apt-get clean autoclean -y \ && apt-get clean autoclean -y \
&& apt-get autoremove -y \ && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \ && mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media \ && chmod 777 /cache /config /media
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
COPY --from=builder /jellyfin /jellyfin COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
EXPOSE 8096 EXPOSE 8096
VOLUME /cache /config /media VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \ ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \ "--datadir", "/config", \
"--cachedir", "/cache", \ "--cachedir", "/cache", \
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"] "--ffmpeg", "/usr/lib/jellyfin-ffmpeg"]
+6 -11
View File
@@ -6,11 +6,12 @@ ARG DOTNET_VERSION=3.1
FROM node:alpine as web-builder FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master ARG JELLYFIN_WEB_VERSION=10.5.2
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ RUN apk add curl git \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && git clone --branch release-10.5.z --single-branch https://github.com/jellyfin/jellyfin-web.git \
&& cd jellyfin-web-* \ && cd jellyfin-web \
&& yarn install \ && yarn install \
&& yarn build \
&& mv dist /dist && mv dist /dist
@@ -42,21 +43,15 @@ RUN apt-get update && apt-get install --no-install-recommends --no-install-sugge
libfreetype6 \ libfreetype6 \
libomxil-bellagio0 \ libomxil-bellagio0 \
libomxil-bellagio-bin \ libomxil-bellagio-bin \
locales \
&& apt-get clean autoclean -y \ && apt-get clean autoclean -y \
&& apt-get autoremove -y \ && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \ && mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media \ && chmod 777 /cache /config /media
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
COPY --from=builder /jellyfin /jellyfin COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
EXPOSE 8096 EXPOSE 8096
VOLUME /cache /config /media VOLUME /cache /config /media
+10 -5
View File
@@ -1,6 +1,4 @@
#pragma warning disable CS1591 using System;
using System.Buffers.Binary;
using System.IO; using System.IO;
namespace DvdLib namespace DvdLib
@@ -14,12 +12,19 @@ namespace DvdLib
public override ushort ReadUInt16() public override ushort ReadUInt16()
{ {
return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2)); return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0);
} }
public override uint ReadUInt32() public override uint ReadUInt32()
{ {
return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4)); return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0);
}
private byte[] ReadAndReverseBytes(int count)
{
byte[] val = base.ReadBytes(count);
Array.Reverse(val, 0, count);
return val;
} }
} }
} }
+4 -6
View File
@@ -1,19 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{713F42B5-878E-499D-A878-E4C652B1D5E8}</ProjectGuid>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\SharedVersion.cs" /> <Compile Include="..\SharedVersion.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
</Project> </Project>
-3
View File
@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.IO; using System.IO;
namespace DvdLib.Ifo namespace DvdLib.Ifo
@@ -7,7 +5,6 @@ namespace DvdLib.Ifo
public class Cell public class Cell
{ {
public CellPlaybackInfo PlaybackInfo { get; private set; } public CellPlaybackInfo PlaybackInfo { get; private set; }
public CellPositionInfo PositionInfo { get; private set; } public CellPositionInfo PositionInfo { get; private set; }
internal void ParsePlayback(BinaryReader br) internal void ParsePlayback(BinaryReader br)
-2
View File
@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.IO; using System.IO;
namespace DvdLib.Ifo namespace DvdLib.Ifo
-2
View File
@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.IO; using System.IO;
namespace DvdLib.Ifo namespace DvdLib.Ifo
-4
View File
@@ -1,13 +1,9 @@
#pragma warning disable CS1591
namespace DvdLib.Ifo namespace DvdLib.Ifo
{ {
public class Chapter public class Chapter
{ {
public ushort ProgramChainNumber { get; private set; } public ushort ProgramChainNumber { get; private set; }
public ushort ProgramNumber { get; private set; } public ushort ProgramNumber { get; private set; }
public uint ChapterNumber { get; private set; } public uint ChapterNumber { get; private set; }
public Chapter(ushort pgcNum, ushort programNum, uint chapterNum) public Chapter(ushort pgcNum, ushort programNum, uint chapterNum)
+11 -19
View File
@@ -1,9 +1,8 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Model.IO;
namespace DvdLib.Ifo namespace DvdLib.Ifo
{ {
@@ -14,10 +13,13 @@ namespace DvdLib.Ifo
private ushort _titleCount; private ushort _titleCount;
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>(); public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
public Dvd(string path) private readonly IFileSystem _fileSystem;
public Dvd(string path, IFileSystem fileSystem)
{ {
_fileSystem = fileSystem;
Titles = new List<Title>(); Titles = new List<Title>();
var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories); var allFiles = _fileSystem.GetFiles(path, true).ToList();
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ?? var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase)); allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
@@ -31,7 +33,7 @@ namespace DvdLib.Ifo
continue; continue;
} }
var nums = ifo.Name.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries); var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries);
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber)) if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
{ {
ReadVTS(ifoNumber, ifo.FullName); ReadVTS(ifoNumber, ifo.FullName);
@@ -74,7 +76,7 @@ namespace DvdLib.Ifo
} }
} }
private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> allFiles) private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
{ {
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum); var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
@@ -117,19 +119,12 @@ namespace DvdLib.Ifo
uint chapNum = 1; uint chapNum = 1;
vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin); vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin);
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1)); var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1));
if (t == null) if (t == null) continue;
{
continue;
}
do do
{ {
t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum)); t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum));
if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1])) if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1])) break;
{
break;
}
chapNum++; chapNum++;
} }
while (vtsFs.Position < (baseAddr + endaddr)); while (vtsFs.Position < (baseAddr + endaddr));
@@ -154,10 +149,7 @@ namespace DvdLib.Ifo
uint vtsPgcOffset = vtsRead.ReadUInt32(); uint vtsPgcOffset = vtsRead.ReadUInt32();
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum)); var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum));
if (t != null) if (t != null) t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
{
t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
}
} }
} }
} }
+2 -10
View File
@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System; using System;
namespace DvdLib.Ifo namespace DvdLib.Ifo
@@ -15,14 +13,8 @@ namespace DvdLib.Ifo
Second = GetBCDValue(data[2]); Second = GetBCDValue(data[2]);
Frames = GetBCDValue((byte)(data[3] & 0x3F)); Frames = GetBCDValue((byte)(data[3] & 0x3F));
if ((data[3] & 0x80) != 0) if ((data[3] & 0x80) != 0) FrameRate = 30;
{ else if ((data[3] & 0x40) != 0) FrameRate = 25;
FrameRate = 30;
}
else if ((data[3] & 0x40) != 0)
{
FrameRate = 25;
}
} }
private static byte GetBCDValue(byte data) private static byte GetBCDValue(byte data)
+1 -3
View File
@@ -1,12 +1,10 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
namespace DvdLib.Ifo namespace DvdLib.Ifo
{ {
public class Program public class Program
{ {
public IReadOnlyList<Cell> Cells { get; } public readonly List<Cell> Cells;
public Program(List<Cell> cells) public Program(List<Cell> cells)
{ {
+2 -15
View File
@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -22,9 +20,7 @@ namespace DvdLib.Ifo
public readonly List<Cell> Cells; public readonly List<Cell> Cells;
public DvdTime PlaybackTime { get; private set; } public DvdTime PlaybackTime { get; private set; }
public UserOperation ProhibitedUserOperations { get; private set; } public UserOperation ProhibitedUserOperations { get; private set; }
public byte[] AudioStreamControl { get; private set; } // 8*2 entries public byte[] AudioStreamControl { get; private set; } // 8*2 entries
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
@@ -35,11 +31,9 @@ namespace DvdLib.Ifo
private ushort _goupProgramNumber; private ushort _goupProgramNumber;
public ProgramPlaybackMode PlaybackMode { get; private set; } public ProgramPlaybackMode PlaybackMode { get; private set; }
public uint ProgramCount { get; private set; } public uint ProgramCount { get; private set; }
public byte StillTime { get; private set; } public byte StillTime { get; private set; }
public byte[] Palette { get; private set; } // 16*4 entries public byte[] Palette { get; private set; } // 16*4 entries
private ushort _commandTableOffset; private ushort _commandTableOffset;
@@ -75,15 +69,8 @@ namespace DvdLib.Ifo
StillTime = br.ReadByte(); StillTime = br.ReadByte();
byte pbMode = br.ReadByte(); byte pbMode = br.ReadByte();
if (pbMode == 0) if (pbMode == 0) PlaybackMode = ProgramPlaybackMode.Sequential;
{ else PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
PlaybackMode = ProgramPlaybackMode.Sequential;
}
else
{
PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
}
ProgramCount = (uint)(pbMode & 0x7F); ProgramCount = (uint)(pbMode & 0x7F);
Palette = br.ReadBytes(64); Palette = br.ReadBytes(64);
+1 -10
View File
@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -8,11 +6,8 @@ namespace DvdLib.Ifo
public class Title public class Title
{ {
public uint TitleNumber { get; private set; } public uint TitleNumber { get; private set; }
public uint AngleCount { get; private set; } public uint AngleCount { get; private set; }
public ushort ChapterCount { get; private set; } public ushort ChapterCount { get; private set; }
public byte VideoTitleSetNumber { get; private set; } public byte VideoTitleSetNumber { get; private set; }
private ushort _parentalManagementMask; private ushort _parentalManagementMask;
@@ -20,7 +15,6 @@ namespace DvdLib.Ifo
private uint _vtsStartSector; // relative to start of entire disk private uint _vtsStartSector; // relative to start of entire disk
public ProgramChain EntryProgramChain { get; private set; } public ProgramChain EntryProgramChain { get; private set; }
public readonly List<ProgramChain> ProgramChains; public readonly List<ProgramChain> ProgramChains;
public readonly List<Chapter> Chapters; public readonly List<Chapter> Chapters;
@@ -59,10 +53,7 @@ namespace DvdLib.Ifo
var pgc = new ProgramChain(pgcNum); var pgc = new ProgramChain(pgcNum);
pgc.ParseHeader(br); pgc.ParseHeader(br);
ProgramChains.Add(pgc); ProgramChains.Add(pgc);
if (entryPgc) if (entryPgc) EntryProgramChain = pgc;
{
EntryProgramChain = pgc;
}
br.BaseStream.Seek(curPos, SeekOrigin.Begin); br.BaseStream.Seek(curPos, SeekOrigin.Begin);
} }
-2
View File
@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System; using System;
namespace DvdLib.Ifo namespace DvdLib.Ifo
+1 -10
View File
@@ -1,7 +1,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -152,7 +152,6 @@ namespace Emby.Dlna.Api
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult<Stream>(new MemoryStream(bytes))); return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult<Stream>(new MemoryStream(bytes)));
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetContentDirectory request) public object Get(GetContentDirectory request)
{ {
var xml = ContentDirectory.GetServiceXml(); var xml = ContentDirectory.GetServiceXml();
@@ -160,7 +159,6 @@ namespace Emby.Dlna.Api
return _resultFactory.GetResult(Request, xml, XMLContentType); return _resultFactory.GetResult(Request, xml, XMLContentType);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetMediaReceiverRegistrar request) public object Get(GetMediaReceiverRegistrar request)
{ {
var xml = MediaReceiverRegistrar.GetServiceXml(); var xml = MediaReceiverRegistrar.GetServiceXml();
@@ -168,7 +166,6 @@ namespace Emby.Dlna.Api
return _resultFactory.GetResult(Request, xml, XMLContentType); return _resultFactory.GetResult(Request, xml, XMLContentType);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetConnnectionManager request) public object Get(GetConnnectionManager request)
{ {
var xml = ConnectionManager.GetServiceXml(); var xml = ConnectionManager.GetServiceXml();
@@ -317,37 +314,31 @@ namespace Emby.Dlna.Api
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream)); return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream));
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Subscribe(ProcessContentDirectoryEventRequest request) public object Subscribe(ProcessContentDirectoryEventRequest request)
{ {
return ProcessEventRequest(ContentDirectory); return ProcessEventRequest(ContentDirectory);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Subscribe(ProcessConnectionManagerEventRequest request) public object Subscribe(ProcessConnectionManagerEventRequest request)
{ {
return ProcessEventRequest(ConnectionManager); return ProcessEventRequest(ConnectionManager);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request) public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request)
{ {
return ProcessEventRequest(MediaReceiverRegistrar); return ProcessEventRequest(MediaReceiverRegistrar);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Unsubscribe(ProcessContentDirectoryEventRequest request) public object Unsubscribe(ProcessContentDirectoryEventRequest request)
{ {
return ProcessEventRequest(ContentDirectory); return ProcessEventRequest(ContentDirectory);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Unsubscribe(ProcessConnectionManagerEventRequest request) public object Unsubscribe(ProcessConnectionManagerEventRequest request)
{ {
return ProcessEventRequest(ConnectionManager); return ProcessEventRequest(ConnectionManager);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request) public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request)
{ {
return ProcessEventRequest(MediaReceiverRegistrar); return ProcessEventRequest(MediaReceiverRegistrar);
+1 -3
View File
@@ -1,6 +1,6 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
@@ -53,7 +53,6 @@ namespace Emby.Dlna.Api
_dlnaManager = dlnaManager; _dlnaManager = dlnaManager;
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetProfileInfos request) public object Get(GetProfileInfos request)
{ {
return _dlnaManager.GetProfileInfos().ToArray(); return _dlnaManager.GetProfileInfos().ToArray();
@@ -64,7 +63,6 @@ namespace Emby.Dlna.Api
return _dlnaManager.GetProfile(request.Id); return _dlnaManager.GetProfile(request.Id);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetDefaultProfile request) public object Get(GetDefaultProfile request)
{ {
return _dlnaManager.GetDefaultProfile(); return _dlnaManager.GetDefaultProfile();
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
namespace Emby.Dlna.Common namespace Emby.Dlna.Common
{ {
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Globalization; using System.Globalization;
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
namespace Emby.Dlna.Common namespace Emby.Dlna.Common
{ {
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic; using System.Collections.Generic;
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
namespace Emby.Dlna.Configuration namespace Emby.Dlna.Configuration
{ {
+1 -1
View File
@@ -1,5 +1,5 @@
#nullable enable
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Configuration; using Emby.Dlna.Configuration;
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.Service; using Emby.Dlna.Service;
@@ -15,11 +16,7 @@ namespace Emby.Dlna.ConnectionManager
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
public ConnectionManager( public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
IDlnaManager dlna,
IServerConfigurationManager config,
ILogger<ConnectionManager> logger,
IHttpClient httpClient)
: base(logger, httpClient) : base(logger, httpClient)
{ {
_dlna = dlna; _dlna = dlna;
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
+11 -8
View File
@@ -1,15 +1,14 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.TV; using MediaBrowser.Controller.TV;
@@ -33,14 +32,13 @@ namespace Emby.Dlna.ContentDirectory
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ITVSeriesManager _tvSeriesManager; private readonly ITVSeriesManager _tvSeriesManager;
public ContentDirectory( public ContentDirectory(IDlnaManager dlna,
IDlnaManager dlna,
IUserDataManager userDataManager, IUserDataManager userDataManager,
IImageProcessor imageProcessor, IImageProcessor imageProcessor,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IServerConfigurationManager config, IServerConfigurationManager config,
IUserManager userManager, IUserManager userManager,
ILogger<ContentDirectory> logger, ILogger logger,
IHttpClient httpClient, IHttpClient httpClient,
ILocalizationManager localization, ILocalizationManager localization,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
@@ -133,13 +131,18 @@ namespace Emby.Dlna.ContentDirectory
foreach (var user in _userManager.Users) foreach (var user in _userManager.Users)
{ {
if (user.HasPermission(PermissionKind.IsAdministrator)) if (user.Policy.IsAdministrator)
{ {
return user; return user;
} }
} }
return _userManager.Users.FirstOrDefault(); foreach (var user in _userManager.Users)
{
return user;
}
return null;
} }
} }
} }
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
+68 -82
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -10,7 +11,6 @@ using System.Threading;
using System.Xml; using System.Xml;
using Emby.Dlna.Didl; using Emby.Dlna.Didl;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
@@ -18,6 +18,7 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
@@ -28,12 +29,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Book = MediaBrowser.Controller.Entities.Book;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Dlna.ContentDirectory namespace Emby.Dlna.ContentDirectory
{ {
@@ -84,18 +79,7 @@ namespace Emby.Dlna.ContentDirectory
_profile = profile; _profile = profile;
_config = config; _config = config;
_didlBuilder = new DidlBuilder( _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, mediaEncoder);
profile,
user,
imageProcessor,
serverAddress,
accessToken,
userDataManager,
localization,
mediaSourceManager,
Logger,
mediaEncoder,
libraryManager);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -170,7 +154,7 @@ namespace Emby.Dlna.ContentDirectory
{ {
var id = sparams["ObjectID"]; var id = sparams["ObjectID"];
var serverItem = GetItemFromObjectId(id); var serverItem = GetItemFromObjectId(id, _user);
var item = serverItem.Item; var item = serverItem.Item;
@@ -293,7 +277,7 @@ namespace Emby.Dlna.ContentDirectory
DidlBuilder.WriteXmlRootAttributes(_profile, writer); DidlBuilder.WriteXmlRootAttributes(_profile, writer);
var serverItem = GetItemFromObjectId(id); var serverItem = GetItemFromObjectId(id, _user);
var item = serverItem.Item; var item = serverItem.Item;
@@ -310,7 +294,7 @@ namespace Emby.Dlna.ContentDirectory
else else
{ {
var dlnaOptions = _config.GetDlnaConfiguration(); var dlnaOptions = _config.GetDlnaConfiguration();
_didlBuilder.WriteItemElement(writer, item, _user, null, null, deviceId, filter); _didlBuilder.WriteItemElement(dlnaOptions, writer, item, _user, null, null, deviceId, filter);
} }
provided++; provided++;
@@ -337,7 +321,7 @@ namespace Emby.Dlna.ContentDirectory
} }
else else
{ {
_didlBuilder.WriteItemElement(writer, childItem, _user, item, serverItem.StubType, deviceId, filter); _didlBuilder.WriteItemElement(dlnaOptions, writer, childItem, _user, item, serverItem.StubType, deviceId, filter);
} }
} }
} }
@@ -404,7 +388,7 @@ namespace Emby.Dlna.ContentDirectory
DidlBuilder.WriteXmlRootAttributes(_profile, writer); DidlBuilder.WriteXmlRootAttributes(_profile, writer);
var serverItem = GetItemFromObjectId(sparams["ContainerID"]); var serverItem = GetItemFromObjectId(sparams["ContainerID"], _user);
var item = serverItem.Item; var item = serverItem.Item;
@@ -423,7 +407,7 @@ namespace Emby.Dlna.ContentDirectory
} }
else else
{ {
_didlBuilder.WriteItemElement(writer, i, _user, item, serverItem.StubType, deviceId, filter); _didlBuilder.WriteItemElement(dlnaOptions, writer, i, _user, item, serverItem.StubType, deviceId, filter);
} }
} }
@@ -466,12 +450,12 @@ namespace Emby.Dlna.ContentDirectory
} }
else if (search.SearchType == SearchType.Playlist) else if (search.SearchType == SearchType.Playlist)
{ {
// items = items.OfType<Playlist>(); //items = items.OfType<Playlist>();
isFolder = true; isFolder = true;
} }
else if (search.SearchType == SearchType.MusicAlbum) else if (search.SearchType == SearchType.MusicAlbum)
{ {
// items = items.OfType<MusicAlbum>(); //items = items.OfType<MusicAlbum>();
isFolder = true; isFolder = true;
} }
@@ -529,11 +513,11 @@ namespace Emby.Dlna.ContentDirectory
} }
else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{ {
return GetFolders(user, startIndex, limit); return GetFolders(item, user, stubType, sort, startIndex, limit);
} }
else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{ {
return GetLiveTvChannels(user, sort, startIndex, limit); return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit);
} }
} }
@@ -564,7 +548,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(queryResult); return ToResult(queryResult);
} }
private QueryResult<ServerItem> GetLiveTvChannels(User user, SortCriteria sort, int? startIndex, int? limit) private QueryResult<ServerItem> GetLiveTvChannels(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
{ {
var query = new InternalItemsQuery(user) var query = new InternalItemsQuery(user)
{ {
@@ -596,7 +580,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.Playlists) if (stubType.HasValue && stubType.Value == StubType.Playlists)
{ {
return GetMusicPlaylists(user, query); return GetMusicPlaylists(item, user, query);
} }
if (stubType.HasValue && stubType.Value == StubType.Albums) if (stubType.HasValue && stubType.Value == StubType.Albums)
@@ -724,7 +708,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.Collections) if (stubType.HasValue && stubType.Value == StubType.Collections)
{ {
return GetMovieCollections(user, query); return GetMovieCollections(item, user, query);
} }
if (stubType.HasValue && stubType.Value == StubType.Favorites) if (stubType.HasValue && stubType.Value == StubType.Favorites)
@@ -737,42 +721,46 @@ namespace Emby.Dlna.ContentDirectory
return GetGenres(item, user, query); return GetGenres(item, user, query);
} }
var array = new[] var list = new List<ServerItem>();
list.Add(new ServerItem(item)
{ {
new ServerItem(item) StubType = StubType.ContinueWatching
{ });
StubType = StubType.ContinueWatching
}, list.Add(new ServerItem(item)
new ServerItem(item) {
{ StubType = StubType.Latest
StubType = StubType.Latest });
},
new ServerItem(item) list.Add(new ServerItem(item)
{ {
StubType = StubType.Movies StubType = StubType.Movies
}, });
new ServerItem(item)
{ list.Add(new ServerItem(item)
StubType = StubType.Collections {
}, StubType = StubType.Collections
new ServerItem(item) });
{
StubType = StubType.Favorites list.Add(new ServerItem(item)
}, {
new ServerItem(item) StubType = StubType.Favorites
{ });
StubType = StubType.Genres
} list.Add(new ServerItem(item)
}; {
StubType = StubType.Genres
});
return new QueryResult<ServerItem> return new QueryResult<ServerItem>
{ {
Items = array, Items = list,
TotalRecordCount = array.Length TotalRecordCount = list.Count
}; };
} }
private QueryResult<ServerItem> GetFolders(User user, int? startIndex, int? limit) private QueryResult<ServerItem> GetFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
{ {
var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true) var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true)
.OrderBy(i => i.SortName) .OrderBy(i => i.SortName)
@@ -805,7 +793,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.NextUp) if (stubType.HasValue && stubType.Value == StubType.NextUp)
{ {
return GetNextUp(item, query); return GetNextUp(item, user, query);
} }
if (stubType.HasValue && stubType.Value == StubType.Latest) if (stubType.HasValue && stubType.Value == StubType.Latest)
@@ -923,10 +911,10 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result); return ToResult(result);
} }
private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query) private QueryResult<ServerItem> GetMovieCollections(BaseItem parent, User user, InternalItemsQuery query)
{ {
query.Recursive = true; query.Recursive = true;
// query.Parent = parent; //query.Parent = parent;
query.SetUser(user); query.SetUser(user);
query.IncludeItemTypes = new[] { typeof(BoxSet).Name }; query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
@@ -1118,10 +1106,10 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result); return ToResult(result);
} }
private QueryResult<ServerItem> GetMusicPlaylists(User user, InternalItemsQuery query) private QueryResult<ServerItem> GetMusicPlaylists(BaseItem parent, User user, InternalItemsQuery query)
{ {
query.Parent = null; query.Parent = null;
query.IncludeItemTypes = new[] { nameof(Playlist) }; query.IncludeItemTypes = new[] { typeof(Playlist).Name };
query.SetUser(user); query.SetUser(user);
query.Recursive = true; query.Recursive = true;
@@ -1138,15 +1126,16 @@ namespace Emby.Dlna.ContentDirectory
{ {
UserId = user.Id, UserId = user.Id,
Limit = 50, Limit = 50,
IncludeItemTypes = new[] { nameof(Audio) }, IncludeItemTypes = new[] { typeof(Audio).Name },
ParentId = parent?.Id ?? Guid.Empty, ParentId = parent == null ? Guid.Empty : parent.Id,
GroupItems = true GroupItems = true
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items); return ToResult(items);
} }
private QueryResult<ServerItem> GetNextUp(BaseItem parent, InternalItemsQuery query) private QueryResult<ServerItem> GetNextUp(BaseItem parent, User user, InternalItemsQuery query)
{ {
query.OrderBy = Array.Empty<(string, SortOrder)>(); query.OrderBy = Array.Empty<(string, SortOrder)>();
@@ -1155,6 +1144,7 @@ namespace Emby.Dlna.ContentDirectory
Limit = query.Limit, Limit = query.Limit,
StartIndex = query.StartIndex, StartIndex = query.StartIndex,
UserId = query.User.Id UserId = query.User.Id
}, new[] { parent }, query.DtoOptions); }, new[] { parent }, query.DtoOptions);
return ToResult(result); return ToResult(result);
@@ -1171,6 +1161,7 @@ namespace Emby.Dlna.ContentDirectory
IncludeItemTypes = new[] { typeof(Episode).Name }, IncludeItemTypes = new[] { typeof(Episode).Name },
ParentId = parent == null ? Guid.Empty : parent.Id, ParentId = parent == null ? Guid.Empty : parent.Id,
GroupItems = false GroupItems = false
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items); return ToResult(items);
@@ -1180,14 +1171,14 @@ namespace Emby.Dlna.ContentDirectory
{ {
query.OrderBy = Array.Empty<(string, SortOrder)>(); query.OrderBy = Array.Empty<(string, SortOrder)>();
var items = _userViewManager.GetLatestItems( var items = _userViewManager.GetLatestItems(new LatestItemsQuery
new LatestItemsQuery
{ {
UserId = user.Id, UserId = user.Id,
Limit = 50, Limit = 50,
IncludeItemTypes = new[] { nameof(Movie) }, IncludeItemTypes = new[] { typeof(Movie).Name },
ParentId = parent?.Id ?? Guid.Empty, ParentId = parent == null ? Guid.Empty : parent.Id,
GroupItems = true GroupItems = true
}, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items); return ToResult(items);
@@ -1220,11 +1211,7 @@ namespace Emby.Dlna.ContentDirectory
Recursive = true, Recursive = true,
ParentId = parentId, ParentId = parentId,
GenreIds = new[] { item.Id }, GenreIds = new[] { item.Id },
IncludeItemTypes = new[] IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name },
{
nameof(Movie),
nameof(Series)
},
Limit = limit, Limit = limit,
StartIndex = startIndex, StartIndex = startIndex,
DtoOptions = GetDtoOptions() DtoOptions = GetDtoOptions()
@@ -1303,15 +1290,15 @@ namespace Emby.Dlna.ContentDirectory
return result; return result;
} }
private ServerItem GetItemFromObjectId(string id) private ServerItem GetItemFromObjectId(string id, User user)
{ {
return DidlBuilder.IsIdRoot(id) return DidlBuilder.IsIdRoot(id)
? new ServerItem(_libraryManager.GetUserRootFolder()) ? new ServerItem(_libraryManager.GetUserRootFolder())
: ParseItemId(id); : ParseItemId(id, user);
} }
private ServerItem ParseItemId(string id) private ServerItem ParseItemId(string id, User user)
{ {
StubType? stubType = null; StubType? stubType = null;
@@ -1357,7 +1344,6 @@ namespace Emby.Dlna.ContentDirectory
internal class ServerItem internal class ServerItem
{ {
public BaseItem Item { get; set; } public BaseItem Item { get; set; }
public StubType? StubType { get; set; } public StubType? StubType { get; set; }
public ServerItem(BaseItem item) public ServerItem(BaseItem item)
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.IO; using System.IO;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic; using System.Collections.Generic;
+128 -252
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Globalization; using System.Globalization;
@@ -6,13 +7,14 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using Emby.Dlna.Configuration;
using Emby.Dlna.ContentDirectory; using Emby.Dlna.ContentDirectory;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Playlists;
@@ -22,13 +24,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Season = MediaBrowser.Controller.Entities.TV.Season;
using Series = MediaBrowser.Controller.Entities.TV.Series;
using XmlAttribute = MediaBrowser.Model.Dlna.XmlAttribute;
namespace Emby.Dlna.Didl namespace Emby.Dlna.Didl
{ {
@@ -51,7 +46,6 @@ namespace Emby.Dlna.Didl
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ILibraryManager _libraryManager;
public DidlBuilder( public DidlBuilder(
DeviceProfile profile, DeviceProfile profile,
@@ -63,8 +57,7 @@ namespace Emby.Dlna.Didl
ILocalizationManager localization, ILocalizationManager localization,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
ILogger logger, ILogger logger,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder)
ILibraryManager libraryManager)
{ {
_profile = profile; _profile = profile;
_user = user; _user = user;
@@ -76,7 +69,6 @@ namespace Emby.Dlna.Didl
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_logger = logger; _logger = logger;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_libraryManager = libraryManager;
} }
public static string NormalizeDlnaMediaUrl(string url) public static string NormalizeDlnaMediaUrl(string url)
@@ -84,7 +76,7 @@ namespace Emby.Dlna.Didl
return url + "&dlnaheaders=true"; return url + "&dlnaheaders=true";
} }
public string GetItemDidl(BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo) public string GetItemDidl(DlnaOptions options, BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
{ {
var settings = new XmlWriterSettings var settings = new XmlWriterSettings
{ {
@@ -98,21 +90,21 @@ namespace Emby.Dlna.Didl
{ {
using (var writer = XmlWriter.Create(builder, settings)) using (var writer = XmlWriter.Create(builder, settings))
{ {
// writer.WriteStartDocument(); //writer.WriteStartDocument();
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL); writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC); writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA); writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP); writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
// didl.SetAttribute("xmlns:sec", NS_SEC); //didl.SetAttribute("xmlns:sec", NS_SEC);
WriteXmlRootAttributes(_profile, writer); WriteXmlRootAttributes(_profile, writer);
WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo); WriteItemElement(options, writer, item, user, context, null, deviceId, filter, streamInfo);
writer.WriteFullEndElement(); writer.WriteFullEndElement();
// writer.WriteEndDocument(); //writer.WriteEndDocument();
} }
return builder.ToString(); return builder.ToString();
@@ -136,6 +128,7 @@ namespace Emby.Dlna.Didl
} }
public void WriteItemElement( public void WriteItemElement(
DlnaOptions options,
XmlWriter writer, XmlWriter writer,
BaseItem item, BaseItem item,
User user, User user,
@@ -172,23 +165,25 @@ namespace Emby.Dlna.Didl
// refID? // refID?
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false); // storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
if (item is IHasMediaSources) var hasMediaSources = item as IHasMediaSources;
if (hasMediaSources != null)
{ {
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{ {
AddAudioResource(writer, item, deviceId, filter, streamInfo); AddAudioResource(options, writer, item, deviceId, filter, streamInfo);
} }
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{ {
AddVideoResource(writer, item, deviceId, filter, streamInfo); AddVideoResource(options, writer, item, deviceId, filter, streamInfo);
} }
} }
AddCover(item, null, writer); AddCover(item, context, null, writer);
writer.WriteFullEndElement(); writer.WriteFullEndElement();
} }
private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null) private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
{ {
if (streamInfo == null) if (streamInfo == null)
{ {
@@ -232,7 +227,7 @@ namespace Emby.Dlna.Didl
foreach (var contentFeature in contentFeatureList) foreach (var contentFeature in contentFeatureList)
{ {
AddVideoResource(writer, filter, contentFeature, streamInfo); AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
} }
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken); var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken);
@@ -289,10 +284,7 @@ namespace Emby.Dlna.Didl
else else
{ {
writer.WriteStartElement(string.Empty, "res", NS_DIDL); writer.WriteStartElement(string.Empty, "res", NS_DIDL);
var protocolInfo = string.Format( var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant());
CultureInfo.InvariantCulture,
"http-get:*:text/{0}:*",
info.Format.ToLowerInvariant());
writer.WriteAttributeString("protocolInfo", protocolInfo); writer.WriteAttributeString("protocolInfo", protocolInfo);
writer.WriteString(info.Url); writer.WriteString(info.Url);
@@ -302,7 +294,7 @@ namespace Emby.Dlna.Didl
return true; return true;
} }
private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo) private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo)
{ {
writer.WriteStartElement(string.Empty, "res", NS_DIDL); writer.WriteStartElement(string.Empty, "res", NS_DIDL);
@@ -344,13 +336,7 @@ namespace Emby.Dlna.Didl
{ {
if (targetWidth.HasValue && targetHeight.HasValue) if (targetWidth.HasValue && targetHeight.HasValue)
{ {
writer.WriteAttributeString( writer.WriteAttributeString("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
"resolution",
string.Format(
CultureInfo.InvariantCulture,
"{0}x{1}",
targetWidth.Value,
targetHeight.Value));
} }
} }
@@ -384,19 +370,17 @@ namespace Emby.Dlna.Didl
streamInfo.TargetVideoCodecTag, streamInfo.TargetVideoCodecTag,
streamInfo.IsTargetAVC); streamInfo.IsTargetAVC);
var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal)); var filename = url.Substring(0, url.IndexOf('?'));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType) var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename) ? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType; : mediaProfile.MimeType;
writer.WriteAttributeString( writer.WriteAttributeString("protocolInfo", string.Format(
"protocolInfo", "http-get:*:{0}:{1}",
string.Format( mimeType,
CultureInfo.InvariantCulture, contentFeatures
"http-get:*:{0}:{1}", ));
mimeType,
contentFeatures));
writer.WriteString(url); writer.WriteString(url);
@@ -409,121 +393,54 @@ namespace Emby.Dlna.Didl
{ {
switch (itemStubType.Value) switch (itemStubType.Value)
{ {
case StubType.Latest: return _localization.GetLocalizedString("Latest"); case StubType.Latest: return _localization.GetLocalizedString("Latest");
case StubType.Playlists: return _localization.GetLocalizedString("Playlists"); case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists"); case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
case StubType.Albums: return _localization.GetLocalizedString("Albums"); case StubType.Albums: return _localization.GetLocalizedString("Albums");
case StubType.Artists: return _localization.GetLocalizedString("Artists"); case StubType.Artists: return _localization.GetLocalizedString("Artists");
case StubType.Songs: return _localization.GetLocalizedString("Songs"); case StubType.Songs: return _localization.GetLocalizedString("Songs");
case StubType.Genres: return _localization.GetLocalizedString("Genres"); case StubType.Genres: return _localization.GetLocalizedString("Genres");
case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums"); case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists"); case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs"); case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching"); case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching");
case StubType.Movies: return _localization.GetLocalizedString("Movies"); case StubType.Movies: return _localization.GetLocalizedString("Movies");
case StubType.Collections: return _localization.GetLocalizedString("Collections"); case StubType.Collections: return _localization.GetLocalizedString("Collections");
case StubType.Favorites: return _localization.GetLocalizedString("Favorites"); case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp"); case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
case StubType.Series: return _localization.GetLocalizedString("Shows"); case StubType.Series: return _localization.GetLocalizedString("Shows");
default: break;
} }
} }
return item is Episode episode if (item is Episode episode && context is Season season)
? GetEpisodeDisplayName(episode, context)
: item.Name;
}
/// <summary>
/// Gets episode display name appropriate for the given context.
/// </summary>
/// <remarks>
/// If context is a season, this will return a string containing just episode number and name.
/// Otherwise the result will include series nams and season number.
/// </remarks>
/// <param name="episode">The episode.</param>
/// <param name="context">Current context.</param>
/// <returns>Formatted name of the episode.</returns>
private string GetEpisodeDisplayName(Episode episode, BaseItem context)
{
string[] components;
if (context is Season season)
{ {
// This is a special embedded within a season // This is a special embedded within a season
if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0 if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0) && season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
{ {
return string.Format( return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("ValueSpecialEpisodeName"),
episode.Name);
} }
// inside a season use simple format (ex. '12 - Episode Name') if (item.IndexNumber.HasValue)
var epNumberName = GetEpisodeIndexFullName(episode);
components = new[] { epNumberName, episode.Name };
}
else
{
// outside a season include series and season details (ex. 'TV Show - S05E11 - Episode Name')
var epNumberName = GetEpisodeNumberDisplayName(episode);
components = new[] { episode.SeriesName, epNumberName, episode.Name };
}
return string.Join(" - ", components.Where(NotNullOrWhiteSpace));
}
/// <summary>
/// Gets complete episode number.
/// </summary>
/// <param name="episode">The episode.</param>
/// <returns>For single episodes returns just the number. For double episodes - current and ending numbers.</returns>
private string GetEpisodeIndexFullName(Episode episode)
{
var name = string.Empty;
if (episode.IndexNumber.HasValue)
{
name += episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
if (episode.IndexNumberEnd.HasValue)
{ {
name += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture); var number = item.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
if (episode.IndexNumberEnd.HasValue)
{
number += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
}
return number + " - " + item.Name;
} }
} }
return name; return item.Name;
} }
/// <summary> private void AddAudioResource(DlnaOptions options, XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
/// Gets episode number formatted as 'S##E##'.
/// </summary>
/// <param name="episode">The episode.</param>
/// <returns>Formatted episode number.</returns>
private string GetEpisodeNumberDisplayName(Episode episode)
{
var name = string.Empty;
var seasonNumber = episode.Season?.IndexNumber;
if (seasonNumber.HasValue)
{
name = "S" + seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture);
}
var indexName = GetEpisodeIndexFullName(episode);
if (!string.IsNullOrWhiteSpace(indexName))
{
name += "E" + indexName;
}
return name;
}
private bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s);
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
{ {
writer.WriteStartElement(string.Empty, "res", NS_DIDL); writer.WriteStartElement(string.Empty, "res", NS_DIDL);
@@ -589,7 +506,7 @@ namespace Emby.Dlna.Didl
targetSampleRate, targetSampleRate,
targetAudioBitDepth); targetAudioBitDepth);
var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal)); var filename = url.Substring(0, url.IndexOf('?'));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType) var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename) ? MimeTypes.GetMimeType(filename)
@@ -605,13 +522,11 @@ namespace Emby.Dlna.Didl
streamInfo.RunTimeTicks ?? 0, streamInfo.RunTimeTicks ?? 0,
streamInfo.TranscodeSeekInfo); streamInfo.TranscodeSeekInfo);
writer.WriteAttributeString( writer.WriteAttributeString("protocolInfo", string.Format(
"protocolInfo", "http-get:*:{0}:{1}",
string.Format( mimeType,
CultureInfo.InvariantCulture, contentFeatures
"http-get:*:{0}:{1}", ));
mimeType,
contentFeatures));
writer.WriteString(url); writer.WriteString(url);
@@ -634,7 +549,7 @@ namespace Emby.Dlna.Didl
var clientId = GetClientId(folder, stubType); var clientId = GetClientId(folder, stubType);
if (string.Equals(requestedId, "0", StringComparison.Ordinal)) if (string.Equals(requestedId, "0"))
{ {
writer.WriteAttributeString("id", "0"); writer.WriteAttributeString("id", "0");
writer.WriteAttributeString("parentID", "-1"); writer.WriteAttributeString("parentID", "-1");
@@ -663,7 +578,7 @@ namespace Emby.Dlna.Didl
AddGeneralProperties(folder, stubType, context, writer, filter); AddGeneralProperties(folder, stubType, context, writer, filter);
AddCover(folder, stubType, writer); AddCover(folder, context, stubType, writer);
writer.WriteFullEndElement(); writer.WriteFullEndElement();
} }
@@ -675,7 +590,7 @@ namespace Emby.Dlna.Didl
return; return;
} }
XmlAttribute secAttribute = null; MediaBrowser.Model.Dlna.XmlAttribute secAttribute = null;
foreach (var attribute in _profile.XmlRootAttributes) foreach (var attribute in _profile.XmlRootAttributes)
{ {
if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase)) if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase))
@@ -696,22 +611,19 @@ namespace Emby.Dlna.Didl
if (playbackPositionTicks > 0) if (playbackPositionTicks > 0)
{ {
var elementValue = string.Format( var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds).ToString(_usCulture));
CultureInfo.InvariantCulture,
"BM={0}",
Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds));
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value); AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
} }
} }
/// <summary> /// <summary>
/// Adds fields used by both items and folders. /// Adds fields used by both items and folders
/// </summary> /// </summary>
private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter) private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
{ {
// Don't filter on dc:title because not all devices will include it in the filter // Don't filter on dc:title because not all devices will include it in the filter
// MediaMonkey for example won't display content without a title // MediaMonkey for example won't display content without a title
// if (filter.Contains("dc:title")) //if (filter.Contains("dc:title"))
{ {
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC); AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
} }
@@ -750,7 +662,7 @@ namespace Emby.Dlna.Didl
AddValue(writer, "dc", "description", desc, NS_DC); AddValue(writer, "dc", "description", desc, NS_DC);
} }
} }
// if (filter.Contains("upnp:longDescription")) //if (filter.Contains("upnp:longDescription"))
//{ //{
// if (!string.IsNullOrWhiteSpace(item.Overview)) // if (!string.IsNullOrWhiteSpace(item.Overview))
// { // {
@@ -765,7 +677,6 @@ namespace Emby.Dlna.Didl
{ {
AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC); AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
} }
if (filter.Contains("upnp:rating")) if (filter.Contains("upnp:rating"))
{ {
AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP); AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
@@ -853,36 +764,37 @@ namespace Emby.Dlna.Didl
private void AddPeople(BaseItem item, XmlWriter writer) private void AddPeople(BaseItem item, XmlWriter writer)
{ {
if (!item.SupportsPeople) //var types = new[]
{ //{
return; // PersonType.Director,
} // PersonType.Writer,
// PersonType.Producer,
// PersonType.Composer,
// "Creator"
//};
var types = new[] //var people = _libraryManager.GetPeople(item);
{
PersonType.Director,
PersonType.Writer,
PersonType.Producer,
PersonType.Composer,
"creator"
};
// Seeing some LG models locking up due content with large lists of people //var index = 0;
// The actual issue might just be due to processing a more metadata than it can handle
var people = _libraryManager.GetPeople(
new InternalPeopleQuery
{
ItemId = item.Id,
Limit = 6
});
foreach (var actor in people) //// Seeing some LG models locking up due content with large lists of people
{ //// The actual issue might just be due to processing a more metadata than it can handle
var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase)) //var limit = 6;
?? PersonType.Actor;
AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP); //foreach (var actor in people)
} //{
// var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
// ?? PersonType.Actor;
// AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
// index++;
// if (index >= limit)
// {
// break;
// }
//}
} }
private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter) private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
@@ -959,7 +871,7 @@ namespace Emby.Dlna.Didl
} }
} }
private void AddCover(BaseItem item, StubType? stubType, XmlWriter writer) private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
{ {
ImageDownloadInfo imageInfo = GetImageInfo(item); ImageDownloadInfo imageInfo = GetImageInfo(item);
@@ -1001,10 +913,20 @@ namespace Emby.Dlna.Didl
} }
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN"); AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
} }
private void AddImageResElement( private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
BaseItem item, {
writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
writer.WriteString(_serverAddress + "/Dlna/icons/people480.jpg");
writer.WriteFullEndElement();
writer.WriteElementString("upnp", "icon", NS_UPNP, _serverAddress + "/Dlna/icons/people48.jpg");
}
private void AddImageResElement(BaseItem item,
XmlWriter writer, XmlWriter writer,
int maxWidth, int maxWidth,
int maxHeight, int maxHeight,
@@ -1030,17 +952,13 @@ namespace Emby.Dlna.Didl
var contentFeatures = new ContentFeatureBuilder(_profile) var contentFeatures = new ContentFeatureBuilder(_profile)
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn); .BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
writer.WriteAttributeString( writer.WriteAttributeString("protocolInfo", string.Format(
"protocolInfo", "http-get:*:{0}:{1}",
string.Format( MimeTypes.GetMimeType("file." + format),
CultureInfo.InvariantCulture, contentFeatures
"http-get:*:{0}:{1}", ));
MimeTypes.GetMimeType("file." + format),
contentFeatures));
writer.WriteAttributeString( writer.WriteAttributeString("resolution", string.Format("{0}x{1}", width, height));
"resolution",
string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
writer.WriteString(albumartUrlInfo.Url); writer.WriteString(albumartUrlInfo.Url);
@@ -1053,12 +971,10 @@ namespace Emby.Dlna.Didl
{ {
return GetImageInfo(item, ImageType.Primary); return GetImageInfo(item, ImageType.Primary);
} }
if (item.HasImage(ImageType.Thumb)) if (item.HasImage(ImageType.Thumb))
{ {
return GetImageInfo(item, ImageType.Thumb); return GetImageInfo(item, ImageType.Thumb);
} }
if (item.HasImage(ImageType.Backdrop)) if (item.HasImage(ImageType.Backdrop))
{ {
if (item is Channel) if (item is Channel)
@@ -1067,58 +983,19 @@ namespace Emby.Dlna.Didl
} }
} }
// For audio tracks without art use album art if available. item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
if (item is Audio audioItem)
{
var album = audioItem.AlbumEntity;
return album != null && album.HasImage(ImageType.Primary)
? GetImageInfo(album, ImageType.Primary)
: null;
}
// Don't look beyond album/playlist level. Metadata service may assign an image from a different album/show to the parent folder. if (item != null)
if (item is MusicAlbum || item is Playlist)
{ {
return null; if (item.HasImage(ImageType.Primary))
} {
return GetImageInfo(item, ImageType.Primary);
// For other item types check parents, but be aware that image retrieved from a parent may be not suitable for this media item. }
var parentWithImage = GetFirstParentWithImageBelowUserRoot(item);
if (parentWithImage != null)
{
return GetImageInfo(parentWithImage, ImageType.Primary);
} }
return null; return null;
} }
private BaseItem GetFirstParentWithImageBelowUserRoot(BaseItem item)
{
if (item == null)
{
return null;
}
if (item.HasImage(ImageType.Primary))
{
return item;
}
var parent = item.GetParent();
if (parent is UserRootFolder)
{
return null;
}
// terminate in case we went past user root folder (unlikely?)
if (parent is Folder folder && folder.IsRoot)
{
return null;
}
return GetFirstParentWithImageBelowUserRoot(parent);
}
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type) private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
{ {
var imageInfo = item.GetImageInfo(type, 0); var imageInfo = item.GetImageInfo(type, 0);
@@ -1138,24 +1015,25 @@ namespace Emby.Dlna.Didl
if (width == 0 || height == 0) if (width == 0 || height == 0)
{ {
// _imageProcessor.GetImageSize(item, imageInfo); //_imageProcessor.GetImageSize(item, imageInfo);
width = null; width = null;
height = null; height = null;
} }
else if (width == -1 || height == -1) else if (width == -1 || height == -1)
{ {
width = null; width = null;
height = null; height = null;
} }
// try //try
//{ //{
// var size = _imageProcessor.GetImageSize(imageInfo); // var size = _imageProcessor.GetImageSize(imageInfo);
// width = size.Width; // width = size.Width;
// height = size.Height; // height = size.Height;
//} //}
// catch //catch
//{ //{
//} //}
@@ -1219,9 +1097,7 @@ namespace Emby.Dlna.Didl
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format) private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
{ {
var url = string.Format( var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
CultureInfo.InvariantCulture,
"{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
_serverAddress, _serverAddress,
info.ItemId.ToString("N", CultureInfo.InvariantCulture), info.ItemId.ToString("N", CultureInfo.InvariantCulture),
info.Type, info.Type,
+4 -1
View File
@@ -1,6 +1,8 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using MediaBrowser.Model.Extensions;
namespace Emby.Dlna.Didl namespace Emby.Dlna.Didl
{ {
@@ -12,6 +14,7 @@ namespace Emby.Dlna.Didl
public Filter() public Filter()
: this("*") : this("*")
{ {
} }
public Filter(string filter) public Filter(string filter)
@@ -25,7 +28,7 @@ namespace Emby.Dlna.Didl
{ {
// Don't bother with this. Some clients (media monkey) use the filter and then don't display very well when very little data comes back. // Don't bother with this. Some clients (media monkey) use the filter and then don't display very well when very little data comes back.
return true; return true;
// return _all || ListHelper.ContainsIgnoreCase(_fields, field); //return _all || ListHelper.ContainsIgnoreCase(_fields, field);
} }
} }
} }
+2 -1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.IO; using System.IO;
@@ -53,6 +54,6 @@ namespace Emby.Dlna.Didl
_encoding = encoding; _encoding = encoding;
} }
public override Encoding Encoding => _encoding ?? base.Encoding; public override Encoding Encoding => (null == _encoding) ? base.Encoding : _encoding;
} }
} }
+9 -28
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -31,7 +32,7 @@ namespace Emby.Dlna
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly IXmlSerializer _xmlSerializer; private readonly IXmlSerializer _xmlSerializer;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILogger<DlnaManager> _logger; private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer; private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
@@ -49,7 +50,7 @@ namespace Emby.Dlna
_xmlSerializer = xmlSerializer; _xmlSerializer = xmlSerializer;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_appPaths = appPaths; _appPaths = appPaths;
_logger = loggerFactory.CreateLogger<DlnaManager>(); _logger = loggerFactory.CreateLogger("Dlna");
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_appHost = appHost; _appHost = appHost;
} }
@@ -88,6 +89,7 @@ namespace Emby.Dlna
.Select(i => i.Item2) .Select(i => i.Item2)
.ToList(); .ToList();
} }
} }
public DeviceProfile GetDefaultProfile() public DeviceProfile GetDefaultProfile()
@@ -140,73 +142,55 @@ namespace Emby.Dlna
if (!string.IsNullOrEmpty(profileInfo.DeviceDescription)) if (!string.IsNullOrEmpty(profileInfo.DeviceDescription))
{ {
if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription)) if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.FriendlyName)) if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
{ {
if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)) if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.Manufacturer)) if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
{ {
if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)) if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl)) if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
{ {
if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)) if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.ModelDescription)) if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
{ {
if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)) if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.ModelName)) if (!string.IsNullOrEmpty(profileInfo.ModelName))
{ {
if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName)) if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.ModelNumber)) if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
{ {
if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)) if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.ModelUrl)) if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
{ {
if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)) if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
{
return false; return false;
}
} }
if (!string.IsNullOrEmpty(profileInfo.SerialNumber)) if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
{ {
if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber)) if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
{
return false; return false;
}
} }
return true; return true;
@@ -268,7 +252,7 @@ namespace Emby.Dlna
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase); return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
case HeaderMatchType.Substring: case HeaderMatchType.Substring:
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
// _logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch); //_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
return isMatch; return isMatch;
case HeaderMatchType.Regex: case HeaderMatchType.Regex:
return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase); return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase);
@@ -456,7 +440,6 @@ namespace Emby.Dlna
{ {
throw new ArgumentException("Profile is missing Id"); throw new ArgumentException("Profile is missing Id");
} }
if (string.IsNullOrEmpty(profile.Name)) if (string.IsNullOrEmpty(profile.Name))
{ {
throw new ArgumentException("Profile is missing Name"); throw new ArgumentException("Profile is missing Name");
@@ -482,7 +465,6 @@ namespace Emby.Dlna
{ {
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile); _profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
} }
SerializeToXml(profile, path); SerializeToXml(profile, path);
} }
@@ -493,7 +475,7 @@ namespace Emby.Dlna
/// <summary> /// <summary>
/// Recreates the object using serialization, to ensure it's not a subclass. /// Recreates the object using serialization, to ensure it's not a subclass.
/// If it's a subclass it may not serlialize properly to xml (different root element tag name). /// If it's a subclass it may not serlialize properly to xml (different root element tag name)
/// </summary> /// </summary>
/// <param name="profile"></param> /// <param name="profile"></param>
/// <returns></returns> /// <returns></returns>
@@ -512,7 +494,6 @@ namespace Emby.Dlna
class InternalProfileInfo class InternalProfileInfo
{ {
internal DeviceProfileInfo Info { get; set; } internal DeviceProfileInfo Info { get; set; }
internal string Path { get; set; } internal string Path { get; set; }
} }
@@ -586,9 +567,9 @@ namespace Emby.Dlna
new Foobar2000Profile(), new Foobar2000Profile(),
new SharpSmartTvProfile(), new SharpSmartTvProfile(),
new MediaMonkeyProfile(), new MediaMonkeyProfile(),
// new Windows81Profile(), //new Windows81Profile(),
// new WindowsMediaCenterProfile(), //new WindowsMediaCenterProfile(),
// new WindowsPhoneProfile(), //new WindowsPhoneProfile(),
new DirectTvProfile(), new DirectTvProfile(),
new DishHopperJoeyProfile(), new DishHopperJoeyProfile(),
new DefaultProfile(), new DefaultProfile(),
-5
View File
@@ -1,10 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{805844AB-E92F-45E6-9D99-4F6D48D129A5}</ProjectGuid>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\SharedVersion.cs" /> <Compile Include="..\SharedVersion.cs" />
</ItemGroup> </ItemGroup>
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic; using System.Collections.Generic;
+11 -18
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@@ -31,26 +32,18 @@ namespace Emby.Dlna.Eventing
public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl) public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl)
{ {
var subscription = GetSubscription(subscriptionId, false); var subscription = GetSubscription(subscriptionId, false);
if (subscription != null)
{
subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
int timeoutSeconds = subscription.TimeoutSeconds;
subscription.SubscriptionTime = DateTime.UtcNow;
_logger.LogDebug( subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
"Renewing event subscription for {0} with timeout of {1} to {2}", int timeoutSeconds = subscription.TimeoutSeconds;
subscription.NotificationType, subscription.SubscriptionTime = DateTime.UtcNow;
timeoutSeconds,
subscription.CallbackUrl);
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds); _logger.LogDebug(
} "Renewing event subscription for {0} with timeout of {1} to {2}",
subscription.NotificationType,
timeoutSeconds,
subscription.CallbackUrl);
return new EventSubscriptionResponse return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
{
Content = string.Empty,
ContentType = "text/plain"
};
} }
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl) public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
@@ -158,7 +151,6 @@ namespace Emby.Dlna.Eventing
builder.Append("</" + key + ">"); builder.Append("</" + key + ">");
builder.Append("</e:property>"); builder.Append("</e:property>");
} }
builder.Append("</e:propertyset>"); builder.Append("</e:propertyset>");
var options = new HttpRequestOptions var options = new HttpRequestOptions
@@ -178,6 +170,7 @@ namespace Emby.Dlna.Eventing
{ {
using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false)) using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
{ {
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
+1 -3
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
@@ -7,13 +8,10 @@ namespace Emby.Dlna.Eventing
public class EventSubscription public class EventSubscription
{ {
public string Id { get; set; } public string Id { get; set; }
public string CallbackUrl { get; set; } public string CallbackUrl { get; set; }
public string NotificationType { get; set; } public string NotificationType { get; set; }
public DateTime SubscriptionTime { get; set; } public DateTime SubscriptionTime { get; set; }
public int TimeoutSeconds { get; set; } public int TimeoutSeconds { get; set; }
public long TriggerCount { get; set; } public long TriggerCount { get; set; }
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
namespace Emby.Dlna namespace Emby.Dlna
{ {
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
namespace Emby.Dlna namespace Emby.Dlna
{ {
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
namespace Emby.Dlna namespace Emby.Dlna
{ {
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
namespace Emby.Dlna namespace Emby.Dlna
{ {
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Threading.Tasks; using System.Threading.Tasks;
+37 -53
View File
@@ -1,8 +1,9 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Globalization;
using System.Net.Sockets; using System.Net.Sockets;
using System.Globalization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.PlayTo; using Emby.Dlna.PlayTo;
@@ -26,15 +27,17 @@ using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Rssdp; using Rssdp;
using Rssdp.Infrastructure; using Rssdp.Infrastructure;
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Dlna.Main namespace Emby.Dlna.Main
{ {
public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
{ {
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ILogger<DlnaEntryPoint> _logger; private readonly ILogger _logger;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private PlayToManager _manager;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
@@ -45,25 +48,23 @@ namespace Emby.Dlna.Main
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IDeviceDiscovery _deviceDiscovery; private readonly IDeviceDiscovery _deviceDiscovery;
private SsdpDevicePublisher _Publisher;
private readonly ISocketFactory _socketFactory; private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
private readonly object _syncLock = new object();
private PlayToManager _manager;
private SsdpDevicePublisher _publisher;
private ISsdpCommunicationsServer _communicationsServer; private ISsdpCommunicationsServer _communicationsServer;
internal IContentDirectory ContentDirectory { get; private set; } internal IContentDirectory ContentDirectory { get; private set; }
internal IConnectionManager ConnectionManager { get; private set; } internal IConnectionManager ConnectionManager { get; private set; }
internal IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; } internal IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
public static DlnaEntryPoint Current; public static DlnaEntryPoint Current;
public DlnaEntryPoint( public DlnaEntryPoint(IServerConfigurationManager config,
IServerConfigurationManager config,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IServerApplicationHost appHost, IServerApplicationHost appHost,
ISessionManager sessionManager, ISessionManager sessionManager,
@@ -97,7 +98,7 @@ namespace Emby.Dlna.Main
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_socketFactory = socketFactory; _socketFactory = socketFactory;
_networkManager = networkManager; _networkManager = networkManager;
_logger = loggerFactory.CreateLogger<DlnaEntryPoint>(); _logger = loggerFactory.CreateLogger("Dlna");
ContentDirectory = new ContentDirectory.ContentDirectory( ContentDirectory = new ContentDirectory.ContentDirectory(
dlnaManager, dlnaManager,
@@ -106,7 +107,7 @@ namespace Emby.Dlna.Main
libraryManager, libraryManager,
config, config,
userManager, userManager,
loggerFactory.CreateLogger<ContentDirectory.ContentDirectory>(), _logger,
httpClient, httpClient,
localizationManager, localizationManager,
mediaSourceManager, mediaSourceManager,
@@ -114,16 +115,9 @@ namespace Emby.Dlna.Main
mediaEncoder, mediaEncoder,
tvSeriesManager); tvSeriesManager);
ConnectionManager = new ConnectionManager.ConnectionManager( ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient);
dlnaManager,
config,
loggerFactory.CreateLogger<ConnectionManager.ConnectionManager>(),
httpClient);
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar( MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config);
loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrar>(),
httpClient,
config);
Current = this; Current = this;
} }
@@ -131,20 +125,20 @@ namespace Emby.Dlna.Main
{ {
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
await ReloadComponents().ConfigureAwait(false); ReloadComponents();
_config.NamedConfigurationUpdated += OnNamedConfigurationUpdated; _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
} }
private async void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
{ {
if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase)) if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
{ {
await ReloadComponents().ConfigureAwait(false); ReloadComponents();
} }
} }
private async Task ReloadComponents() private async void ReloadComponents()
{ {
var options = _config.GetDlnaConfiguration(); var options = _config.GetDlnaConfiguration();
@@ -178,7 +172,7 @@ namespace Emby.Dlna.Main
var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows || var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
OperatingSystem.Id == OperatingSystemId.Linux; OperatingSystem.Id == OperatingSystemId.Linux;
_communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding) _communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding)
{ {
IsShared = true IsShared = true
}; };
@@ -229,22 +223,20 @@ namespace Emby.Dlna.Main
return; return;
} }
if (_publisher != null) if (_Publisher != null)
{ {
return; return;
} }
try try
{ {
_publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost) _Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost);
{ _Publisher.LogFunction = LogMessage;
LogFunction = LogMessage, _Publisher.SupportPnpRootDevice = false;
SupportPnpRootDevice = false
};
await RegisterServerEndpoints().ConfigureAwait(false); await RegisterServerEndpoints().ConfigureAwait(false);
_publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds)); _Publisher.StartBroadcastingAliveMessages(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -262,14 +254,8 @@ namespace Emby.Dlna.Main
{ {
if (address.AddressFamily == AddressFamily.InterNetworkV6) if (address.AddressFamily == AddressFamily.InterNetworkV6)
{ {
// Not supporting IPv6 right now // Not support IPv6 right now
continue; continue;
}
// Limit to LAN addresses only
if (!_networkManager.IsAddressInSubnets(address, true, true))
{
continue;
} }
var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
@@ -281,7 +267,7 @@ namespace Emby.Dlna.Main
var device = new SsdpRootDevice var device = new SsdpRootDevice
{ {
CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info. CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
Location = uri, // Must point to the URL that serves your devices UPnP description document. Location = uri, // Must point to the URL that serves your devices UPnP description document.
Address = address, Address = address,
SubnetMask = _networkManager.GetLocalIpSubnetMask(address), SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
@@ -293,13 +279,13 @@ namespace Emby.Dlna.Main
}; };
SetProperies(device, fullService); SetProperies(device, fullService);
_publisher.AddDevice(device); _Publisher.AddDevice(device);
var embeddedDevices = new[] var embeddedDevices = new[]
{ {
"urn:schemas-upnp-org:service:ContentDirectory:1", "urn:schemas-upnp-org:service:ContentDirectory:1",
"urn:schemas-upnp-org:service:ConnectionManager:1", "urn:schemas-upnp-org:service:ConnectionManager:1",
// "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1" //"urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
}; };
foreach (var subDevice in embeddedDevices) foreach (var subDevice in embeddedDevices)
@@ -325,13 +311,12 @@ namespace Emby.Dlna.Main
{ {
guid = text.GetMD5(); guid = text.GetMD5();
} }
return guid.ToString("N", CultureInfo.InvariantCulture); return guid.ToString("N", CultureInfo.InvariantCulture);
} }
private void SetProperies(SsdpDevice device, string fullDeviceType) private void SetProperies(SsdpDevice device, string fullDeviceType)
{ {
var service = fullDeviceType.Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase).Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase); var service = fullDeviceType.Replace("urn:", string.Empty).Replace(":1", string.Empty);
var serviceParts = service.Split(':'); var serviceParts = service.Split(':');
@@ -342,6 +327,7 @@ namespace Emby.Dlna.Main
device.DeviceType = serviceParts[2]; device.DeviceType = serviceParts[2];
} }
private readonly object _syncLock = new object();
private void StartPlayToManager() private void StartPlayToManager()
{ {
lock (_syncLock) lock (_syncLock)
@@ -353,8 +339,7 @@ namespace Emby.Dlna.Main
try try
{ {
_manager = new PlayToManager( _manager = new PlayToManager(_logger,
_logger,
_sessionManager, _sessionManager,
_libraryManager, _libraryManager,
_userManager, _userManager,
@@ -393,7 +378,6 @@ namespace Emby.Dlna.Main
{ {
_logger.LogError(ex, "Error disposing PlayTo manager"); _logger.LogError(ex, "Error disposing PlayTo manager");
} }
_manager = null; _manager = null;
} }
} }
@@ -420,11 +404,11 @@ namespace Emby.Dlna.Main
public void DisposeDevicePublisher() public void DisposeDevicePublisher()
{ {
if (_publisher != null) if (_Publisher != null)
{ {
_logger.LogInformation("Disposing SsdpDevicePublisher"); _logger.LogInformation("Disposing SsdpDevicePublisher");
_publisher.Dispose(); _Publisher.Dispose();
_publisher = null; _Publisher = null;
} }
} }
} }
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.Service; using Emby.Dlna.Service;
@@ -12,10 +13,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar
{ {
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
public MediaReceiverRegistrar( public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config)
ILogger<MediaReceiverRegistrar> logger,
IHttpClient httpClient,
IServerConfigurationManager config)
: base(logger, httpClient) : base(logger, httpClient)
{ {
_config = config; _config = config;
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
+53 -73
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -19,6 +20,8 @@ namespace Emby.Dlna.PlayTo
{ {
public class Device : IDisposable public class Device : IDisposable
{ {
#region Fields & Properties
private Timer _timer; private Timer _timer;
public DeviceInfo Properties { get; set; } public DeviceInfo Properties { get; set; }
@@ -32,10 +35,9 @@ namespace Emby.Dlna.PlayTo
{ {
get get
{ {
RefreshVolumeIfNeeded().GetAwaiter().GetResult(); RefreshVolumeIfNeeded();
return _volume; return _volume;
} }
set => _volume = value; set => _volume = value;
} }
@@ -51,10 +53,10 @@ namespace Emby.Dlna.PlayTo
public bool IsStopped => TransportState == TRANSPORTSTATE.STOPPED; public bool IsStopped => TransportState == TRANSPORTSTATE.STOPPED;
#endregion
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
public Action OnDeviceUnavailable { get; set; } public Action OnDeviceUnavailable { get; set; }
@@ -75,25 +77,25 @@ namespace Emby.Dlna.PlayTo
private DateTime _lastVolumeRefresh; private DateTime _lastVolumeRefresh;
private bool _volumeRefreshActive; private bool _volumeRefreshActive;
private Task RefreshVolumeIfNeeded() private void RefreshVolumeIfNeeded()
{ {
if (_volumeRefreshActive if (!_volumeRefreshActive)
&& DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
{
_lastVolumeRefresh = DateTime.UtcNow;
return RefreshVolume();
}
return Task.CompletedTask;
}
private async Task RefreshVolume(CancellationToken cancellationToken = default)
{
if (_disposed)
{ {
return; return;
} }
if (DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
{
_lastVolumeRefresh = DateTime.UtcNow;
RefreshVolume(CancellationToken.None);
}
}
private async void RefreshVolume(CancellationToken cancellationToken)
{
if (_disposed)
return;
try try
{ {
await GetVolume(cancellationToken).ConfigureAwait(false); await GetVolume(cancellationToken).ConfigureAwait(false);
@@ -140,6 +142,8 @@ namespace Emby.Dlna.PlayTo
} }
} }
#region Commanding
public Task VolumeDown(CancellationToken cancellationToken) public Task VolumeDown(CancellationToken cancellationToken)
{ {
var sendVolume = Math.Max(Volume - 5, 0); var sendVolume = Math.Max(Volume - 5, 0);
@@ -208,9 +212,7 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute"); var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
if (command == null) if (command == null)
{
return false; return false;
}
var service = GetServiceRenderingControl(); var service = GetServiceRenderingControl();
@@ -231,7 +233,7 @@ namespace Emby.Dlna.PlayTo
} }
/// <summary> /// <summary>
/// Sets volume on a scale of 0-100. /// Sets volume on a scale of 0-100
/// </summary> /// </summary>
public async Task SetVolume(int value, CancellationToken cancellationToken) public async Task SetVolume(int value, CancellationToken cancellationToken)
{ {
@@ -239,9 +241,7 @@ namespace Emby.Dlna.PlayTo
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume"); var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
if (command == null) if (command == null)
{
return; return;
}
var service = GetServiceRenderingControl(); var service = GetServiceRenderingControl();
@@ -264,9 +264,7 @@ namespace Emby.Dlna.PlayTo
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
if (command == null) if (command == null)
{
return; return;
}
var service = GetAvTransportService(); var service = GetAvTransportService();
@@ -291,9 +289,7 @@ namespace Emby.Dlna.PlayTo
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
if (command == null) if (command == null)
{
return; return;
}
var dictionary = new Dictionary<string, string> var dictionary = new Dictionary<string, string>
{ {
@@ -351,12 +347,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service"); throw new InvalidOperationException("Unable to find service");
} }
return new SsdpHttpClient(_httpClient).SendCommandAsync( return new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1));
Properties.BaseUrl,
service,
command.Name,
avCommands.BuildPost(command, service.ServiceType, 1),
cancellationToken: cancellationToken);
} }
public async Task SetPlay(CancellationToken cancellationToken) public async Task SetPlay(CancellationToken cancellationToken)
@@ -406,8 +397,11 @@ namespace Emby.Dlna.PlayTo
RestartTimer(true); RestartTimer(true);
} }
private int _connectFailureCount; #endregion
#region Get data
private int _connectFailureCount;
private async void TimerCallback(object sender) private async void TimerCallback(object sender)
{ {
if (_disposed) if (_disposed)
@@ -460,9 +454,7 @@ namespace Emby.Dlna.PlayTo
_connectFailureCount = 0; _connectFailureCount = 0;
if (_disposed) if (_disposed)
{
return; return;
}
// If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive // If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive
if (transportState.Value == TRANSPORTSTATE.STOPPED) if (transportState.Value == TRANSPORTSTATE.STOPPED)
@@ -482,9 +474,7 @@ namespace Emby.Dlna.PlayTo
catch (Exception ex) catch (Exception ex)
{ {
if (_disposed) if (_disposed)
{
return; return;
}
_logger.LogError(ex, "Error updating device info for {DeviceName}", Properties.Name); _logger.LogError(ex, "Error updating device info for {DeviceName}", Properties.Name);
@@ -500,7 +490,6 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
} }
RestartTimerInactive(); RestartTimerInactive();
} }
} }
@@ -527,12 +516,8 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
Properties.BaseUrl, .ConfigureAwait(false);
service,
command.Name,
rendererCommands.BuildPost(command, service.ServiceType),
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{ {
@@ -577,17 +562,11 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
Properties.BaseUrl, .ConfigureAwait(false);
service,
command.Name,
rendererCommands.BuildPost(command, service.ServiceType),
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{
return; return;
}
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse") var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse")
.Select(i => i.Element("CurrentMute")) .Select(i => i.Element("CurrentMute"))
@@ -610,12 +589,8 @@ namespace Emby.Dlna.PlayTo
return null; return null;
} }
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false)
Properties.BaseUrl, .ConfigureAwait(false);
service,
command.Name,
avCommands.BuildPost(command, service.ServiceType),
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{ {
@@ -625,7 +600,7 @@ namespace Emby.Dlna.PlayTo
var transportState = var transportState =
result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null); result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
var transportStateValue = transportState?.Value; var transportStateValue = transportState == null ? null : transportState.Value;
if (transportStateValue != null if (transportStateValue != null
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state)) && Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
@@ -652,12 +627,8 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false)
Properties.BaseUrl, .ConfigureAwait(false);
service,
command.Name,
rendererCommands.BuildPost(command, service.ServiceType),
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{ {
@@ -719,12 +690,8 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync( var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false)
Properties.BaseUrl, .ConfigureAwait(false);
service,
command.Name,
rendererCommands.BuildPost(command, service.ServiceType),
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{ {
@@ -759,7 +726,7 @@ namespace Emby.Dlna.PlayTo
if (track == null) if (track == null)
{ {
// If track is null, some vendors do this, use GetMediaInfo instead //If track is null, some vendors do this, use GetMediaInfo instead
return (true, null); return (true, null);
} }
@@ -803,6 +770,7 @@ namespace Emby.Dlna.PlayTo
} }
catch (XmlException) catch (XmlException)
{ {
} }
// first try to add a root node with a dlna namesapce // first try to add a root node with a dlna namesapce
@@ -814,6 +782,7 @@ namespace Emby.Dlna.PlayTo
} }
catch (XmlException) catch (XmlException)
{ {
} }
// some devices send back invalid xml // some devices send back invalid xml
@@ -823,6 +792,7 @@ namespace Emby.Dlna.PlayTo
} }
catch (XmlException) catch (XmlException)
{ {
} }
return null; return null;
@@ -877,6 +847,10 @@ namespace Emby.Dlna.PlayTo
return new string[4]; return new string[4];
} }
#endregion
#region From XML
private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken) private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken)
{ {
if (AvCommands != null) if (AvCommands != null)
@@ -1071,6 +1045,8 @@ namespace Emby.Dlna.PlayTo
return new Device(deviceProperties, httpClient, logger, config); return new Device(deviceProperties, httpClient, logger, config);
} }
#endregion
private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private static DeviceIcon CreateIcon(XElement element) private static DeviceIcon CreateIcon(XElement element)
{ {
@@ -1194,6 +1170,8 @@ namespace Emby.Dlna.PlayTo
}); });
} }
#region IDisposable
bool _disposed; bool _disposed;
public void Dispose() public void Dispose()
@@ -1220,6 +1198,8 @@ namespace Emby.Dlna.PlayTo
_disposed = true; _disposed = true;
} }
#endregion
public override string ToString() public override string ToString()
{ {
return string.Format("{0} - {1}", Properties.Name, Properties.BaseUrl); return string.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
+78 -106
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -7,7 +8,6 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.Didl; using Emby.Dlna.Didl;
using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
@@ -23,14 +23,11 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.WebUtilities; using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Photo = MediaBrowser.Controller.Entities.Photo;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo
{ {
public class PlayToController : ISessionController, IDisposable public class PlayToController : ISessionController, IDisposable
{ {
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
private Device _device; private Device _device;
private readonly SessionInfo _session; private readonly SessionInfo _session;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
@@ -49,10 +46,9 @@ namespace Emby.Dlna.PlayTo
private readonly string _serverAddress; private readonly string _serverAddress;
private readonly string _accessToken; private readonly string _accessToken;
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>(); public bool IsSessionActive => !_disposed && _device != null;
private int _currentPlaylistIndex;
private bool _disposed; public bool SupportsMediaControl => IsSessionActive;
public PlayToController( public PlayToController(
SessionInfo session, SessionInfo session,
@@ -88,22 +84,18 @@ namespace Emby.Dlna.PlayTo
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
} }
public bool IsSessionActive => !_disposed && _device != null;
public bool SupportsMediaControl => IsSessionActive;
public void Init(Device device) public void Init(Device device)
{ {
_device = device; _device = device;
_device.OnDeviceUnavailable = OnDeviceUnavailable; _device.OnDeviceUnavailable = OnDeviceUnavailable;
_device.PlaybackStart += OnDevicePlaybackStart; _device.PlaybackStart += _device_PlaybackStart;
_device.PlaybackProgress += OnDevicePlaybackProgress; _device.PlaybackProgress += _device_PlaybackProgress;
_device.PlaybackStopped += OnDevicePlaybackStopped; _device.PlaybackStopped += _device_PlaybackStopped;
_device.MediaChanged += OnDeviceMediaChanged; _device.MediaChanged += _device_MediaChanged;
_device.Start(); _device.Start();
_deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft; _deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
} }
private void OnDeviceUnavailable() private void OnDeviceUnavailable()
@@ -119,7 +111,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
private void OnDeviceDiscoveryDeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e) void _deviceDiscovery_DeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{ {
var info = e.Argument; var info = e.Argument;
@@ -134,7 +126,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e) async void _device_MediaChanged(object sender, MediaChangedEventArgs e)
{ {
if (_disposed) if (_disposed)
{ {
@@ -146,18 +138,15 @@ namespace Emby.Dlna.PlayTo
var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager); var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager);
if (streamInfo.Item != null) if (streamInfo.Item != null)
{ {
var positionTicks = GetProgressPositionTicks(streamInfo); var positionTicks = GetProgressPositionTicks(e.OldMediaInfo, streamInfo);
await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false); ReportPlaybackStopped(e.OldMediaInfo, streamInfo, positionTicks);
} }
streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager); streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager);
if (streamInfo.Item == null) if (streamInfo.Item == null) return;
{
return;
}
var newItemProgress = GetProgressInfo(streamInfo); var newItemProgress = GetProgressInfo(e.NewMediaInfo, streamInfo);
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false); await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
} }
@@ -167,7 +156,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
private async void OnDevicePlaybackStopped(object sender, PlaybackStoppedEventArgs e) async void _device_PlaybackStopped(object sender, PlaybackStoppedEventArgs e)
{ {
if (_disposed) if (_disposed)
{ {
@@ -178,14 +167,11 @@ namespace Emby.Dlna.PlayTo
{ {
var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager); var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager);
if (streamInfo.Item == null) if (streamInfo.Item == null) return;
{
return;
}
var positionTicks = GetProgressPositionTicks(streamInfo); var positionTicks = GetProgressPositionTicks(e.MediaInfo, streamInfo);
await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false); ReportPlaybackStopped(e.MediaInfo, streamInfo, positionTicks);
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false); var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
@@ -193,7 +179,7 @@ namespace Emby.Dlna.PlayTo
(_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) : (_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) :
mediaSource.RunTimeTicks; mediaSource.RunTimeTicks;
var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0; var playedToCompletion = (positionTicks.HasValue && positionTicks.Value == 0);
if (!playedToCompletion && duration.HasValue && positionTicks.HasValue) if (!playedToCompletion && duration.HasValue && positionTicks.HasValue)
{ {
@@ -209,7 +195,7 @@ namespace Emby.Dlna.PlayTo
} }
else else
{ {
_playlist.Clear(); Playlist.Clear();
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -218,7 +204,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
private async Task ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks) private async void ReportPlaybackStopped(uBaseObject mediaInfo, StreamParams streamInfo, long? positionTicks)
{ {
try try
{ {
@@ -228,6 +214,7 @@ namespace Emby.Dlna.PlayTo
SessionId = _session.Id, SessionId = _session.Id,
PositionTicks = positionTicks, PositionTicks = positionTicks,
MediaSourceId = streamInfo.MediaSourceId MediaSourceId = streamInfo.MediaSourceId
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
@@ -236,7 +223,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
private async void OnDevicePlaybackStart(object sender, PlaybackStartEventArgs e) async void _device_PlaybackStart(object sender, PlaybackStartEventArgs e)
{ {
if (_disposed) if (_disposed)
{ {
@@ -249,7 +236,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null) if (info.Item != null)
{ {
var progress = GetProgressInfo(info); var progress = GetProgressInfo(e.MediaInfo, info);
await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false); await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false);
} }
@@ -260,7 +247,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
private async void OnDevicePlaybackProgress(object sender, PlaybackProgressEventArgs e) async void _device_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
{ {
if (_disposed) if (_disposed)
{ {
@@ -280,7 +267,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null) if (info.Item != null)
{ {
var progress = GetProgressInfo(info); var progress = GetProgressInfo(e.MediaInfo, info);
await _sessionManager.OnPlaybackProgress(progress).ConfigureAwait(false); await _sessionManager.OnPlaybackProgress(progress).ConfigureAwait(false);
} }
@@ -291,7 +278,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
private long? GetProgressPositionTicks(StreamParams info) private long? GetProgressPositionTicks(uBaseObject mediaInfo, StreamParams info)
{ {
var ticks = _device.Position.Ticks; var ticks = _device.Position.Ticks;
@@ -303,13 +290,13 @@ namespace Emby.Dlna.PlayTo
return ticks; return ticks;
} }
private PlaybackStartInfo GetProgressInfo(StreamParams info) private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo, StreamParams info)
{ {
return new PlaybackStartInfo return new PlaybackStartInfo
{ {
ItemId = info.ItemId, ItemId = info.ItemId,
SessionId = _session.Id, SessionId = _session.Id,
PositionTicks = GetProgressPositionTicks(info), PositionTicks = GetProgressPositionTicks(mediaInfo, info),
IsMuted = _device.IsMuted, IsMuted = _device.IsMuted,
IsPaused = _device.IsPaused, IsPaused = _device.IsPaused,
MediaSourceId = info.MediaSourceId, MediaSourceId = info.MediaSourceId,
@@ -324,7 +311,9 @@ namespace Emby.Dlna.PlayTo
}; };
} }
public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) #region SendCommands
public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
{ {
_logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand); _logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
@@ -362,12 +351,11 @@ namespace Emby.Dlna.PlayTo
if (command.PlayCommand == PlayCommand.PlayLast) if (command.PlayCommand == PlayCommand.PlayLast)
{ {
_playlist.AddRange(playlist); Playlist.AddRange(playlist);
} }
if (command.PlayCommand == PlayCommand.PlayNext) if (command.PlayCommand == PlayCommand.PlayNext)
{ {
_playlist.AddRange(playlist); Playlist.AddRange(playlist);
} }
if (!command.ControllingUserId.Equals(Guid.Empty)) if (!command.ControllingUserId.Equals(Guid.Empty))
@@ -376,7 +364,7 @@ namespace Emby.Dlna.PlayTo
_session.DeviceName, _session.RemoteEndPoint, user); _session.DeviceName, _session.RemoteEndPoint, user);
} }
return PlayItems(playlist, cancellationToken); await PlayItems(playlist).ConfigureAwait(false);
} }
private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken) private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
@@ -384,7 +372,7 @@ namespace Emby.Dlna.PlayTo
switch (command.Command) switch (command.Command)
{ {
case PlaystateCommand.Stop: case PlaystateCommand.Stop:
_playlist.Clear(); Playlist.Clear();
return _device.SetStop(CancellationToken.None); return _device.SetStop(CancellationToken.None);
case PlaystateCommand.Pause: case PlaystateCommand.Pause:
@@ -400,10 +388,10 @@ namespace Emby.Dlna.PlayTo
return Seek(command.SeekPositionTicks ?? 0); return Seek(command.SeekPositionTicks ?? 0);
case PlaystateCommand.NextTrack: case PlaystateCommand.NextTrack:
return SetPlaylistIndex(_currentPlaylistIndex + 1, cancellationToken); return SetPlaylistIndex(_currentPlaylistIndex + 1);
case PlaystateCommand.PreviousTrack: case PlaystateCommand.PreviousTrack:
return SetPlaylistIndex(_currentPlaylistIndex - 1, cancellationToken); return SetPlaylistIndex(_currentPlaylistIndex - 1);
} }
return Task.CompletedTask; return Task.CompletedTask;
@@ -425,7 +413,6 @@ namespace Emby.Dlna.PlayTo
await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
return; return;
} }
await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
} }
} }
@@ -440,6 +427,14 @@ namespace Emby.Dlna.PlayTo
return info.IsDirectStream; return info.IsDirectStream;
} }
#endregion
#region Playlist
private int _currentPlaylistIndex;
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
private List<PlaylistItem> Playlist => _playlist;
private void AddItemFromId(Guid id, List<BaseItem> list) private void AddItemFromId(Guid id, List<BaseItem> list)
{ {
var item = _libraryManager.GetItemById(id); var item = _libraryManager.GetItemById(id);
@@ -449,13 +444,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
private PlaylistItem CreatePlaylistItem( private PlaylistItem CreatePlaylistItem(BaseItem item, User user, long startPostionTicks, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
BaseItem item,
User user,
long startPostionTicks,
string mediaSourceId,
int? audioStreamIndex,
int? subtitleStreamIndex)
{ {
var deviceInfo = _device.Properties; var deviceInfo = _device.Properties;
@@ -463,7 +452,7 @@ namespace Emby.Dlna.PlayTo
_dlnaManager.GetDefaultProfile(); _dlnaManager.GetDefaultProfile();
var mediaSources = item is IHasMediaSources var mediaSources = item is IHasMediaSources
? _mediaSourceManager.GetStaticMediaSources(item, true, user) ? (_mediaSourceManager.GetStaticMediaSources(item, true, user))
: new List<MediaSourceInfo>(); : new List<MediaSourceInfo>();
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
@@ -471,19 +460,8 @@ namespace Emby.Dlna.PlayTo
playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken)); playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken));
var itemXml = new DidlBuilder( var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _mediaEncoder)
profile, .GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
user,
_imageProcessor,
_serverAddress,
_accessToken,
_userDataManager,
_localization,
_mediaSourceManager,
_logger,
_mediaEncoder,
_libraryManager)
.GetItemDidl(item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
playlistItem.Didl = itemXml; playlistItem.Didl = itemXml;
@@ -593,31 +571,30 @@ namespace Emby.Dlna.PlayTo
/// Plays the items. /// Plays the items.
/// </summary> /// </summary>
/// <param name="items">The items.</param> /// <param name="items">The items.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <returns></returns>
/// <returns><c>true</c> on success.</returns> private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items, CancellationToken cancellationToken = default)
{ {
_playlist.Clear(); Playlist.Clear();
_playlist.AddRange(items); Playlist.AddRange(items);
_logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, _playlist.Count); _logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, Playlist.Count);
await SetPlaylistIndex(0, cancellationToken).ConfigureAwait(false); await SetPlaylistIndex(0).ConfigureAwait(false);
return true; return true;
} }
private async Task SetPlaylistIndex(int index, CancellationToken cancellationToken = default) private async Task SetPlaylistIndex(int index)
{ {
if (index < 0 || index >= _playlist.Count) if (index < 0 || index >= Playlist.Count)
{ {
_playlist.Clear(); Playlist.Clear();
await _device.SetStop(cancellationToken).ConfigureAwait(false); await _device.SetStop(CancellationToken.None);
return; return;
} }
_currentPlaylistIndex = index; _currentPlaylistIndex = index;
var currentitem = _playlist[index]; var currentitem = Playlist[index];
await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false); await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, CancellationToken.None);
var streamInfo = currentitem.StreamInfo; var streamInfo = currentitem.StreamInfo;
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo)) if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
@@ -626,7 +603,10 @@ namespace Emby.Dlna.PlayTo
} }
} }
/// <inheritdoc /> #endregion
private bool _disposed;
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
@@ -645,17 +625,19 @@ namespace Emby.Dlna.PlayTo
_device.Dispose(); _device.Dispose();
} }
_device.PlaybackStart -= OnDevicePlaybackStart; _device.PlaybackStart -= _device_PlaybackStart;
_device.PlaybackProgress -= OnDevicePlaybackProgress; _device.PlaybackProgress -= _device_PlaybackProgress;
_device.PlaybackStopped -= OnDevicePlaybackStopped; _device.PlaybackStopped -= _device_PlaybackStopped;
_device.MediaChanged -= OnDeviceMediaChanged; _device.MediaChanged -= _device_MediaChanged;
_deviceDiscovery.DeviceLeft -= OnDeviceDiscoveryDeviceLeft; _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
_device.OnDeviceUnavailable = null; _device.OnDeviceUnavailable = null;
_device = null; _device = null;
_disposed = true; _disposed = true;
} }
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
{ {
if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType)) if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType))
@@ -714,7 +696,6 @@ namespace Emby.Dlna.PlayTo
throw new ArgumentException("Volume argument cannot be null"); throw new ArgumentException("Volume argument cannot be null");
} }
default: default:
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -733,7 +714,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null) if (info.Item != null)
{ {
var newPosition = GetProgressPositionTicks(info) ?? 0; var newPosition = GetProgressPositionTicks(media, info) ?? 0;
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null; var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex); var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex);
@@ -758,7 +739,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null) if (info.Item != null)
{ {
var newPosition = GetProgressPositionTicks(info) ?? 0; var newPosition = GetProgressPositionTicks(media, info) ?? 0;
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null; var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex); var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex);
@@ -800,15 +781,12 @@ namespace Emby.Dlna.PlayTo
public int? SubtitleStreamIndex { get; set; } public int? SubtitleStreamIndex { get; set; }
public string DeviceProfileId { get; set; } public string DeviceProfileId { get; set; }
public string DeviceId { get; set; } public string DeviceId { get; set; }
public string MediaSourceId { get; set; } public string MediaSourceId { get; set; }
public string LiveStreamId { get; set; } public string LiveStreamId { get; set; }
public BaseItem Item { get; set; } public BaseItem Item { get; set; }
private MediaSourceInfo MediaSource; private MediaSourceInfo MediaSource;
private IMediaSourceManager _mediaSourceManager; private IMediaSourceManager _mediaSourceManager;
@@ -875,11 +853,8 @@ namespace Emby.Dlna.PlayTo
return request; return request;
} }
var index = url.IndexOf('?', StringComparison.Ordinal); var index = url.IndexOf('?');
if (index == -1) if (index == -1) return request;
{
return request;
}
var query = url.Substring(index + 1); var query = url.Substring(index + 1);
Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString()); Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());
@@ -926,8 +901,7 @@ namespace Emby.Dlna.PlayTo
return 0; return 0;
} }
/// <inheritdoc /> public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken)
public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken)
{ {
if (_disposed) if (_disposed)
{ {
@@ -943,12 +917,10 @@ namespace Emby.Dlna.PlayTo
{ {
return SendPlayCommand(data as PlayRequest, cancellationToken); return SendPlayCommand(data as PlayRequest, cancellationToken);
} }
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase)) if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
{ {
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken); return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
} }
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase)) if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
{ {
return SendGeneralCommand(data as GeneralCommand, cancellationToken); return SendGeneralCommand(data as GeneralCommand, cancellationToken);
+8 -17
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Globalization; using System.Globalization;
@@ -23,7 +24,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo
{ {
public sealed class PlayToManager : IDisposable public class PlayToManager : IDisposable
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
@@ -78,15 +79,9 @@ namespace Emby.Dlna.PlayTo
var info = e.Argument; var info = e.Argument;
if (!info.Headers.TryGetValue("USN", out string usn)) if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty;
{
usn = string.Empty;
}
if (!info.Headers.TryGetValue("NT", out string nt)) if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty;
{
nt = string.Empty;
}
string location = info.Location.ToString(); string location = info.Location.ToString();
@@ -94,7 +89,7 @@ namespace Emby.Dlna.PlayTo
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 && if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1) nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1)
{ {
// _logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location); //_logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location);
return; return;
} }
@@ -118,6 +113,7 @@ namespace Emby.Dlna.PlayTo
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -138,7 +134,6 @@ namespace Emby.Dlna.PlayTo
usn = usn.Substring(index); usn = usn.Substring(index);
found = true; found = true;
} }
index = usn.IndexOf("::", StringComparison.OrdinalIgnoreCase); index = usn.IndexOf("::", StringComparison.OrdinalIgnoreCase);
if (index != -1) if (index != -1)
{ {
@@ -190,8 +185,7 @@ namespace Emby.Dlna.PlayTo
serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress); serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress);
} }
controller = new PlayToController( controller = new PlayToController(sessionInfo,
sessionInfo,
_sessionManager, _sessionManager,
_libraryManager, _libraryManager,
_logger, _logger,
@@ -238,7 +232,6 @@ namespace Emby.Dlna.PlayTo
} }
} }
/// <inheritdoc />
public void Dispose() public void Dispose()
{ {
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered; _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
@@ -249,10 +242,8 @@ namespace Emby.Dlna.PlayTo
} }
catch catch
{ {
}
_sessionLock.Dispose(); }
_disposeCancellationTokenSource.Dispose();
_disposed = true; _disposed = true;
} }
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
+1 -1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
@@ -12,7 +13,6 @@ namespace Emby.Dlna.PlayTo
public class MediaChangedEventArgs : EventArgs public class MediaChangedEventArgs : EventArgs
{ {
public uBaseObject OldMediaInfo { get; set; } public uBaseObject OldMediaInfo { get; set; }
public uBaseObject NewMediaInfo { get; set; } public uBaseObject NewMediaInfo { get; set; }
} }
} }
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.IO; using System.IO;
using System.Linq; using System.Linq;
+9 -3
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Globalization; using System.Globalization;
@@ -32,15 +33,18 @@ namespace Emby.Dlna.PlayTo
DeviceService service, DeviceService service,
string command, string command,
string postData, string postData,
string header = null, bool logRequest = true,
CancellationToken cancellationToken = default) string header = null)
{ {
var cancellationToken = CancellationToken.None;
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl); var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
using (var response = await PostSoapDataAsync( using (var response = await PostSoapDataAsync(
url, url,
$"\"{service.ServiceType}#{command}\"", $"\"{service.ServiceType}#{command}\"",
postData, postData,
header, header,
logRequest,
cancellationToken) cancellationToken)
.ConfigureAwait(false)) .ConfigureAwait(false))
using (var stream = response.Content) using (var stream = response.Content)
@@ -60,7 +64,7 @@ namespace Emby.Dlna.PlayTo
return serviceUrl; return serviceUrl;
} }
if (!serviceUrl.StartsWith("/", StringComparison.Ordinal)) if (!serviceUrl.StartsWith("/"))
{ {
serviceUrl = "/" + serviceUrl; serviceUrl = "/" + serviceUrl;
} }
@@ -91,6 +95,7 @@ namespace Emby.Dlna.PlayTo
using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false)) using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
{ {
} }
} }
@@ -123,6 +128,7 @@ namespace Emby.Dlna.PlayTo
string soapAction, string soapAction,
string postData, string postData,
string header, string header,
bool logRequest,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
if (soapAction[0] != '\"') if (soapAction[0] != '\"')
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo
{ {
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
using System.Xml.Linq; using System.Xml.Linq;
+1 -2
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System; using System;
@@ -44,12 +45,10 @@ namespace Emby.Dlna.PlayTo
{ {
return MediaBrowser.Model.Entities.MediaType.Audio; return MediaBrowser.Model.Entities.MediaType.Audio;
} }
if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1) if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1)
{ {
return MediaBrowser.Model.Entities.MediaType.Video; return MediaBrowser.Model.Entities.MediaType.Video;
} }
if (classType.IndexOf("image", StringComparison.Ordinal) != -1) if (classType.IndexOf("image", StringComparison.Ordinal) != -1)
{ {
return MediaBrowser.Model.Entities.MediaType.Photo; return MediaBrowser.Model.Entities.MediaType.Photo;
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Xml.Linq; using System.Xml.Linq;
+3 -2
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System.Linq; using System.Linq;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@@ -12,7 +13,7 @@ namespace Emby.Dlna.Profiles
{ {
Name = "Generic Device"; Name = "Generic Device";
ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*"; ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*";
Manufacturer = "Jellyfin"; Manufacturer = "Jellyfin";
ModelDescription = "UPnP/AV 1.0 Compliant Media Server"; ModelDescription = "UPnP/AV 1.0 Compliant Media Server";
@@ -164,7 +165,7 @@ namespace Emby.Dlna.Profiles
public void AddXmlRootAttribute(string name, string value) public void AddXmlRootAttribute(string name, string value)
{ {
var atts = XmlRootAttributes ?? System.Array.Empty<XmlAttribute>(); var atts = XmlRootAttributes ?? new XmlAttribute[] { };
var list = atts.ToList(); var list = atts.ToList();
list.Add(new XmlAttribute list.Add(new XmlAttribute
+2 -1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@@ -28,7 +29,7 @@ namespace Emby.Dlna.Profiles
}, },
}; };
ResponseProfiles = System.Array.Empty<ResponseProfile>(); ResponseProfiles = new ResponseProfile[] { };
} }
} }
} }
+2 -1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@@ -123,7 +124,7 @@ namespace Emby.Dlna.Profiles
} }
}; };
ResponseProfiles = System.Array.Empty<ResponseProfile>(); ResponseProfiles = new ResponseProfile[] { };
} }
} }
} }
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
+2 -1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@@ -72,7 +73,7 @@ namespace Emby.Dlna.Profiles
} }
}; };
ResponseProfiles = System.Array.Empty<ResponseProfile>(); ResponseProfiles = new ResponseProfile[] { };
} }
} }
} }
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
+2 -1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@@ -37,7 +38,7 @@ namespace Emby.Dlna.Profiles
}, },
}; };
ResponseProfiles = System.Array.Empty<ResponseProfile>(); ResponseProfiles = new ResponseProfile[] { };
} }
} }
} }
+2 -2
View File
@@ -1,6 +1,6 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles
@@ -38,7 +38,7 @@ namespace Emby.Dlna.Profiles
} }
}; };
ResponseProfiles = Array.Empty<ResponseProfile>(); ResponseProfiles = new ResponseProfile[] { };
} }
} }
} }
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
+1
View File
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@@ -1,4 +1,5 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
+2 -2
View File
@@ -1,6 +1,6 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles
@@ -224,7 +224,7 @@ namespace Emby.Dlna.Profiles
} }
}; };
ResponseProfiles = Array.Empty<ResponseProfile>(); ResponseProfiles = new ResponseProfile[] { };
} }
} }
} }
+2 -2
View File
@@ -1,6 +1,6 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles
@@ -224,7 +224,7 @@ namespace Emby.Dlna.Profiles
} }
}; };
ResponseProfiles = Array.Empty<ResponseProfile>(); ResponseProfiles = new ResponseProfile[] { };
} }
} }
} }
+2 -2
View File
@@ -1,6 +1,6 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1600
using System;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles
@@ -212,7 +212,7 @@ namespace Emby.Dlna.Profiles
} }
}; };
ResponseProfiles = Array.Empty<ResponseProfile>(); ResponseProfiles = new ResponseProfile[] { };
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More