mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-06 19:37:55 +02:00
Merge branch 'master' into Plugins
This commit is contained in:
commit
8439860b2a
1097 changed files with 42305 additions and 43436 deletions
|
@ -12,10 +12,12 @@ parameters:
|
||||||
jobs:
|
jobs:
|
||||||
- job: CompatibilityCheck
|
- job: CompatibilityCheck
|
||||||
displayName: Compatibility Check
|
displayName: Compatibility Check
|
||||||
|
dependsOn: Build
|
||||||
|
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
|
||||||
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: "${{ parameters.LinuxImage }}"
|
vmImage: "${{ parameters.LinuxImage }}"
|
||||||
# only execute for pull requests
|
|
||||||
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
${{ each Package in parameters.Packages }}:
|
${{ each Package in parameters.Packages }}:
|
||||||
|
@ -23,7 +25,7 @@ jobs:
|
||||||
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
||||||
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
||||||
maxParallel: 2
|
maxParallel: 2
|
||||||
dependsOn: Build
|
|
||||||
steps:
|
steps:
|
||||||
- checkout: none
|
- checkout: none
|
||||||
|
|
||||||
|
@ -34,32 +36,32 @@ jobs:
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2
|
||||||
displayName: 'Install ABI CompatibilityChecker tool'
|
displayName: 'Install ABI CompatibilityChecker Tool'
|
||||||
inputs:
|
inputs:
|
||||||
command: custom
|
command: custom
|
||||||
custom: tool
|
custom: tool
|
||||||
arguments: 'update compatibilitychecker -g'
|
arguments: 'update compatibilitychecker -g'
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: "Download New Assembly Build Artifact"
|
displayName: 'Download New Assembly Build Artifact'
|
||||||
inputs:
|
inputs:
|
||||||
source: "current"
|
source: 'current'
|
||||||
artifact: "$(NugetPackageName)"
|
artifact: "$(NugetPackageName)"
|
||||||
path: "$(System.ArtifactsDirectory)/new-artifacts"
|
path: "$(System.ArtifactsDirectory)/new-artifacts"
|
||||||
runVersion: "latest"
|
runVersion: "latest"
|
||||||
|
|
||||||
- task: CopyFiles@2
|
- task: CopyFiles@2
|
||||||
displayName: "Copy New Assembly Build Artifact"
|
displayName: 'Copy New Assembly Build Artifact'
|
||||||
inputs:
|
inputs:
|
||||||
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
|
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
|
||||||
contents: "**/*.dll"
|
contents: '**/*.dll'
|
||||||
targetFolder: $(System.ArtifactsDirectory)/new-release
|
targetFolder: $(System.ArtifactsDirectory)/new-release
|
||||||
cleanTargetFolder: true
|
cleanTargetFolder: true
|
||||||
overWrite: true
|
overWrite: true
|
||||||
flattenFolders: true
|
flattenFolders: true
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: "Download Reference Assembly Build Artifact"
|
displayName: 'Download Reference Assembly Build Artifact'
|
||||||
inputs:
|
inputs:
|
||||||
source: "specific"
|
source: "specific"
|
||||||
artifact: "$(NugetPackageName)"
|
artifact: "$(NugetPackageName)"
|
||||||
|
@ -70,16 +72,15 @@ jobs:
|
||||||
runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
|
runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
|
||||||
|
|
||||||
- task: CopyFiles@2
|
- task: CopyFiles@2
|
||||||
displayName: "Copy Reference Assembly Build Artifact"
|
displayName: 'Copy Reference Assembly Build Artifact'
|
||||||
inputs:
|
inputs:
|
||||||
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
|
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
|
||||||
contents: "**/*.dll"
|
contents: '**/*.dll'
|
||||||
targetFolder: $(System.ArtifactsDirectory)/current-release
|
targetFolder: $(System.ArtifactsDirectory)/current-release
|
||||||
cleanTargetFolder: true
|
cleanTargetFolder: true
|
||||||
overWrite: true
|
overWrite: true
|
||||||
flattenFolders: true
|
flattenFolders: true
|
||||||
|
|
||||||
# The `--warnings-only` switch will swallow the return code and not emit any errors.
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2
|
||||||
displayName: 'Execute ABI Compatibility Check Tool'
|
displayName: 'Execute ABI Compatibility Check Tool'
|
||||||
inputs:
|
inputs:
|
||||||
|
|
|
@ -64,28 +64,28 @@ jobs:
|
||||||
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
|
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
|
||||||
zipAfterPublish: false
|
zipAfterPublish: false
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@1
|
||||||
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@1
|
||||||
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@1
|
||||||
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@1
|
||||||
displayName: 'Publish Artifact Common'
|
displayName: 'Publish Artifact Common'
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
|
|
214
.ci/azure-pipelines-package.yml
Normal file
214
.ci/azure-pipelines-package.yml
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
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/v')
|
||||||
|
|
||||||
|
- 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/v')
|
||||||
|
|
||||||
|
- 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/v')
|
||||||
|
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
|
||||||
|
timeoutInMinutes: 20
|
||||||
|
displayName: 'Collect Artifacts'
|
||||||
|
continueOnError: true
|
||||||
|
dependsOn:
|
||||||
|
- BuildPackage
|
||||||
|
- BuildDocker
|
||||||
|
condition: and(succeeded('BuildPackage'), succeeded('BuildDocker'))
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: SSH@0
|
||||||
|
displayName: 'Update Unstable Repository'
|
||||||
|
continueOnError: true
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: repository
|
||||||
|
runOptions: 'commands'
|
||||||
|
commands: sudo nohup -n /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable &
|
||||||
|
|
||||||
|
- task: SSH@0
|
||||||
|
displayName: 'Update Stable Repository'
|
||||||
|
continueOnError: true
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
|
inputs:
|
||||||
|
sshEndpoint: repository
|
||||||
|
runOptions: 'commands'
|
||||||
|
commands: sudo nohup -n /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) &
|
||||||
|
|
||||||
|
- job: PublishNuget
|
||||||
|
displayName: 'Publish NuGet packages'
|
||||||
|
dependsOn:
|
||||||
|
- BuildPackage
|
||||||
|
condition: succeeded('BuildPackage')
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: DotNetCoreCLI@2
|
||||||
|
displayName: 'Build Stable Nuget packages'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
|
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'
|
||||||
|
versioningScheme: 'off'
|
||||||
|
|
||||||
|
- task: DotNetCoreCLI@2
|
||||||
|
displayName: 'Build Unstable Nuget packages'
|
||||||
|
inputs:
|
||||||
|
command: 'custom'
|
||||||
|
projects: |
|
||||||
|
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
|
||||||
|
custom: 'pack'
|
||||||
|
arguments: '--version-suffix $(Build.BuildNumber) -o $(Build.ArtifactStagingDirectory) -p:Stability=Unstable'
|
||||||
|
|
||||||
|
- task: PublishBuildArtifacts@1
|
||||||
|
displayName: 'Publish Nuget packages'
|
||||||
|
inputs:
|
||||||
|
pathToPublish: $(Build.ArtifactStagingDirectory)
|
||||||
|
artifactName: Jellyfin Nuget Packages
|
||||||
|
|
||||||
|
- task: NuGetAuthenticate@0
|
||||||
|
displayName: 'Authenticate to stable Nuget feed'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
|
inputs:
|
||||||
|
nuGetServiceConnections: 'NugetOrg'
|
||||||
|
|
||||||
|
- task: NuGetCommand@2
|
||||||
|
displayName: 'Push Nuget packages to stable feed'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
|
inputs:
|
||||||
|
command: 'push'
|
||||||
|
packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg;$(Build.ArtifactStagingDirectory)/**/*.snupkg'
|
||||||
|
nuGetFeedType: 'external'
|
||||||
|
publishFeedCredentials: 'NugetOrg'
|
||||||
|
allowPackageConflicts: true # This ignores an error if the version already exists
|
||||||
|
|
||||||
|
- task: NuGetAuthenticate@0
|
||||||
|
displayName: 'Authenticate to unstable Nuget feed'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
|
|
||||||
|
- task: NuGetCommand@2
|
||||||
|
displayName: 'Push Nuget packages to unstable feed'
|
||||||
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
|
inputs:
|
||||||
|
command: 'push'
|
||||||
|
packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg' # No symbols since Azure Artifact does not support it
|
||||||
|
nuGetFeedType: 'internal'
|
||||||
|
publishVstsFeed: '7cce6c46-d610-45e3-9fb7-65a6bfd1b671/a5746b79-f369-42db-93ff-59cd066f9327'
|
||||||
|
allowPackageConflicts: true # This ignores an error if the version already exists
|
|
@ -74,7 +74,6 @@ jobs:
|
||||||
- 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: 'Run ReportGenerator'
|
||||||
enabled: false
|
|
||||||
inputs:
|
inputs:
|
||||||
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
||||||
targetdir: "$(Agent.TempDirectory)/merged/"
|
targetdir: "$(Agent.TempDirectory)/merged/"
|
||||||
|
@ -84,10 +83,16 @@ jobs:
|
||||||
- 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
|
||||||
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
||||||
pathToSources: $(Build.SourcesDirectory)
|
pathToSources: $(Build.SourcesDirectory)
|
||||||
failIfCoverageEmpty: true
|
failIfCoverageEmpty: true
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@1
|
||||||
|
displayName: 'Publish OpenAPI Artifact'
|
||||||
|
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||||
|
inputs:
|
||||||
|
targetPath: "tests/Jellyfin.Api.Tests/bin/Release/netcoreapp3.1/openapi.json"
|
||||||
|
artifactName: 'OpenAPI Spec'
|
||||||
|
|
|
@ -13,13 +13,21 @@ pr:
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
batch: true
|
batch: true
|
||||||
|
branches:
|
||||||
|
include:
|
||||||
|
- '*'
|
||||||
|
tags:
|
||||||
|
include:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
- ${{ if not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')) }}:
|
||||||
- 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/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
|
||||||
- template: azure-pipelines-test.yml
|
- template: azure-pipelines-test.yml
|
||||||
parameters:
|
parameters:
|
||||||
ImageNames:
|
ImageNames:
|
||||||
|
@ -27,6 +35,7 @@ jobs:
|
||||||
Windows: 'windows-latest'
|
Windows: 'windows-latest'
|
||||||
macOS: 'macos-latest'
|
macOS: 'macos-latest'
|
||||||
|
|
||||||
|
- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
|
||||||
- template: azure-pipelines-abi.yml
|
- template: azure-pipelines-abi.yml
|
||||||
parameters:
|
parameters:
|
||||||
Packages:
|
Packages:
|
||||||
|
@ -43,3 +52,6 @@ jobs:
|
||||||
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/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
|
||||||
|
- template: azure-pipelines-package.yml
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -39,7 +39,6 @@ ProgramData*/
|
||||||
CorePlugins*/
|
CorePlugins*/
|
||||||
ProgramData-Server*/
|
ProgramData-Server*/
|
||||||
ProgramData-UI*/
|
ProgramData-UI*/
|
||||||
MediaBrowser.WebDashboard/jellyfin-web/**
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
## Visual Studio
|
## Visual Studio
|
||||||
|
@ -276,4 +275,4 @@ BenchmarkDotNet.Artifacts
|
||||||
# Ignore web artifacts from native builds
|
# Ignore web artifacts from native builds
|
||||||
web/
|
web/
|
||||||
web-src.*
|
web-src.*
|
||||||
MediaBrowser.WebDashboard/jellyfin-web/
|
MediaBrowser.WebDashboard/jellyfin-web
|
||||||
|
|
22
.vscode/launch.json
vendored
22
.vscode/launch.json
vendored
|
@ -1,7 +1,4 @@
|
||||||
{
|
{
|
||||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
|
||||||
// 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",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
|
@ -9,11 +6,21 @@
|
||||||
"type": "coreclr",
|
"type": "coreclr",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"preLaunchTask": "build",
|
"preLaunchTask": "build",
|
||||||
// If you have changed target frameworks, make sure to update the program path.
|
|
||||||
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/netcoreapp3.1/jellyfin.dll",
|
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/netcoreapp3.1/jellyfin.dll",
|
||||||
"args": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}/Jellyfin.Server",
|
"cwd": "${workspaceFolder}/Jellyfin.Server",
|
||||||
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
|
"console": "internalConsole",
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"internalConsoleOptions": "openOnSessionStart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Launch (nowebclient)",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build",
|
||||||
|
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/netcoreapp3.1/jellyfin.dll",
|
||||||
|
"args": ["--nowebclient"],
|
||||||
|
"cwd": "${workspaceFolder}/Jellyfin.Server",
|
||||||
"console": "internalConsole",
|
"console": "internalConsole",
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"internalConsoleOptions": "openOnSessionStart"
|
"internalConsoleOptions": "openOnSessionStart"
|
||||||
|
@ -24,5 +31,8 @@
|
||||||
"request": "attach",
|
"request": "attach",
|
||||||
"processId": "${command:pickProcess}"
|
"processId": "${command:pickProcess}"
|
||||||
}
|
}
|
||||||
,]
|
],
|
||||||
|
"env": {
|
||||||
|
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
9
.vscode/tasks.json
vendored
9
.vscode/tasks.json
vendored
|
@ -17,9 +17,14 @@
|
||||||
"type": "process",
|
"type": "process",
|
||||||
"args": [
|
"args": [
|
||||||
"test",
|
"test",
|
||||||
"${workspaceFolder}/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj"
|
"${workspaceFolder}/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj"
|
||||||
],
|
],
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"options": {
|
||||||
|
"env": {
|
||||||
|
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
- [bugfixin](https://github.com/bugfixin)
|
- [bugfixin](https://github.com/bugfixin)
|
||||||
- [chaosinnovator](https://github.com/chaosinnovator)
|
- [chaosinnovator](https://github.com/chaosinnovator)
|
||||||
- [ckcr4lyf](https://github.com/ckcr4lyf)
|
- [ckcr4lyf](https://github.com/ckcr4lyf)
|
||||||
|
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
|
||||||
- [crankdoofus](https://github.com/crankdoofus)
|
- [crankdoofus](https://github.com/crankdoofus)
|
||||||
- [crobibero](https://github.com/crobibero)
|
- [crobibero](https://github.com/crobibero)
|
||||||
- [cromefire](https://github.com/cromefire)
|
- [cromefire](https://github.com/cromefire)
|
||||||
|
@ -56,6 +57,7 @@
|
||||||
- [Larvitar](https://github.com/Larvitar)
|
- [Larvitar](https://github.com/Larvitar)
|
||||||
- [LeoVerto](https://github.com/LeoVerto)
|
- [LeoVerto](https://github.com/LeoVerto)
|
||||||
- [Liggy](https://github.com/Liggy)
|
- [Liggy](https://github.com/Liggy)
|
||||||
|
- [lmaonator](https://github.com/lmaonator)
|
||||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy)
|
- [LogicalPhallacy](https://github.com/LogicalPhallacy)
|
||||||
- [loli10K](https://github.com/loli10K)
|
- [loli10K](https://github.com/loli10K)
|
||||||
- [lostmypillow](https://github.com/lostmypillow)
|
- [lostmypillow](https://github.com/lostmypillow)
|
||||||
|
@ -77,6 +79,7 @@
|
||||||
- [nvllsvm](https://github.com/nvllsvm)
|
- [nvllsvm](https://github.com/nvllsvm)
|
||||||
- [nyanmisaka](https://github.com/nyanmisaka)
|
- [nyanmisaka](https://github.com/nyanmisaka)
|
||||||
- [oddstr13](https://github.com/oddstr13)
|
- [oddstr13](https://github.com/oddstr13)
|
||||||
|
- [orryverducci](https://github.com/orryverducci)
|
||||||
- [petermcneil](https://github.com/petermcneil)
|
- [petermcneil](https://github.com/petermcneil)
|
||||||
- [Phlogi](https://github.com/Phlogi)
|
- [Phlogi](https://github.com/Phlogi)
|
||||||
- [pjeanjean](https://github.com/pjeanjean)
|
- [pjeanjean](https://github.com/pjeanjean)
|
||||||
|
|
|
@ -14,7 +14,7 @@ COPY . .
|
||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# 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:DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
FROM debian:buster-slim
|
FROM debian:buster-slim
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# Discard objs - may cause failures if exists
|
# Discard objs - may cause failures if exists
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
RUN find . -type d -name obj | xargs -r rm -r
|
||||||
# Build
|
# Build
|
||||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||||
|
|
|
@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# Discard objs - may cause failures if exists
|
# Discard objs - may cause failures if exists
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
RUN find . -type d -name obj | xargs -r rm -r
|
||||||
# Build
|
# Build
|
||||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||||
FROM arm64v8/debian:buster-slim
|
FROM arm64v8/debian:buster-slim
|
||||||
|
|
|
@ -1,383 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Emby.Dlna.Main;
|
|
||||||
using MediaBrowser.Common.Extensions;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.Dlna;
|
|
||||||
using MediaBrowser.Controller.Net;
|
|
||||||
using MediaBrowser.Model.Services;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.Api
|
|
||||||
{
|
|
||||||
[Route("/Dlna/{UuId}/description.xml", "GET", Summary = "Gets dlna server info")]
|
|
||||||
[Route("/Dlna/{UuId}/description", "GET", Summary = "Gets dlna server info")]
|
|
||||||
public class GetDescriptionXml
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string UuId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/{UuId}/contentdirectory/contentdirectory.xml", "GET", Summary = "Gets dlna content directory xml")]
|
|
||||||
[Route("/Dlna/{UuId}/contentdirectory/contentdirectory", "GET", Summary = "Gets dlna content directory xml")]
|
|
||||||
public class GetContentDirectory
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string UuId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/{UuId}/connectionmanager/connectionmanager.xml", "GET", Summary = "Gets dlna connection manager xml")]
|
|
||||||
[Route("/Dlna/{UuId}/connectionmanager/connectionmanager", "GET", Summary = "Gets dlna connection manager xml")]
|
|
||||||
public class GetConnnectionManager
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string UuId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/{UuId}/mediareceiverregistrar/mediareceiverregistrar.xml", "GET", Summary = "Gets dlna mediareceiverregistrar xml")]
|
|
||||||
[Route("/Dlna/{UuId}/mediareceiverregistrar/mediareceiverregistrar", "GET", Summary = "Gets dlna mediareceiverregistrar xml")]
|
|
||||||
public class GetMediaReceiverRegistrar
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string UuId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/{UuId}/contentdirectory/control", "POST", Summary = "Processes a control request")]
|
|
||||||
public class ProcessContentDirectoryControlRequest : IRequiresRequestStream
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string UuId { get; set; }
|
|
||||||
|
|
||||||
public Stream RequestStream { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/{UuId}/connectionmanager/control", "POST", Summary = "Processes a control request")]
|
|
||||||
public class ProcessConnectionManagerControlRequest : IRequiresRequestStream
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string UuId { get; set; }
|
|
||||||
|
|
||||||
public Stream RequestStream { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/{UuId}/mediareceiverregistrar/control", "POST", Summary = "Processes a control request")]
|
|
||||||
public class ProcessMediaReceiverRegistrarControlRequest : IRequiresRequestStream
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string UuId { get; set; }
|
|
||||||
|
|
||||||
public Stream RequestStream { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/{UuId}/mediareceiverregistrar/events", "SUBSCRIBE", Summary = "Processes an event subscription request")]
|
|
||||||
[Route("/Dlna/{UuId}/mediareceiverregistrar/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")]
|
|
||||||
public class ProcessMediaReceiverRegistrarEventRequest
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")]
|
|
||||||
public string UuId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/{UuId}/contentdirectory/events", "SUBSCRIBE", Summary = "Processes an event subscription request")]
|
|
||||||
[Route("/Dlna/{UuId}/contentdirectory/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")]
|
|
||||||
public class ProcessContentDirectoryEventRequest
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")]
|
|
||||||
public string UuId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/{UuId}/connectionmanager/events", "SUBSCRIBE", Summary = "Processes an event subscription request")]
|
|
||||||
[Route("/Dlna/{UuId}/connectionmanager/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")]
|
|
||||||
public class ProcessConnectionManagerEventRequest
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")]
|
|
||||||
public string UuId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/{UuId}/icons/{Filename}", "GET", Summary = "Gets a server icon")]
|
|
||||||
[Route("/Dlna/icons/{Filename}", "GET", Summary = "Gets a server icon")]
|
|
||||||
public class GetIcon
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string UuId { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Filename", Description = "The icon filename", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Filename { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DlnaServerService : IService, IRequiresRequest
|
|
||||||
{
|
|
||||||
private const string XMLContentType = "text/xml; charset=UTF-8";
|
|
||||||
|
|
||||||
private readonly IDlnaManager _dlnaManager;
|
|
||||||
private readonly IHttpResultFactory _resultFactory;
|
|
||||||
private readonly IServerConfigurationManager _configurationManager;
|
|
||||||
|
|
||||||
public IRequest Request { get; set; }
|
|
||||||
|
|
||||||
private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
|
|
||||||
|
|
||||||
private IConnectionManager ConnectionManager => DlnaEntryPoint.Current.ConnectionManager;
|
|
||||||
|
|
||||||
private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
|
|
||||||
|
|
||||||
public DlnaServerService(
|
|
||||||
IDlnaManager dlnaManager,
|
|
||||||
IHttpResultFactory httpResultFactory,
|
|
||||||
IServerConfigurationManager configurationManager)
|
|
||||||
{
|
|
||||||
_dlnaManager = dlnaManager;
|
|
||||||
_resultFactory = httpResultFactory;
|
|
||||||
_configurationManager = configurationManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetHeader(string name)
|
|
||||||
{
|
|
||||||
return Request.Headers[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetDescriptionXml request)
|
|
||||||
{
|
|
||||||
var url = Request.AbsoluteUri;
|
|
||||||
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
|
|
||||||
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress);
|
|
||||||
|
|
||||||
var cacheLength = TimeSpan.FromDays(1);
|
|
||||||
var cacheKey = Request.RawUrl.GetMD5();
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(xml);
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
var xml = ContentDirectory.GetServiceXml();
|
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Get(GetMediaReceiverRegistrar request)
|
|
||||||
{
|
|
||||||
var xml = MediaReceiverRegistrar.GetServiceXml();
|
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Get(GetConnnectionManager request)
|
|
||||||
{
|
|
||||||
var xml = ConnectionManager.GetServiceXml();
|
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<object> Post(ProcessMediaReceiverRegistrarControlRequest request)
|
|
||||||
{
|
|
||||||
var response = await PostAsync(request.RequestStream, MediaReceiverRegistrar).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<object> Post(ProcessContentDirectoryControlRequest request)
|
|
||||||
{
|
|
||||||
var response = await PostAsync(request.RequestStream, ContentDirectory).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<object> Post(ProcessConnectionManagerControlRequest request)
|
|
||||||
{
|
|
||||||
var response = await PostAsync(request.RequestStream, ConnectionManager).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task<ControlResponse> PostAsync(Stream requestStream, IUpnpService service)
|
|
||||||
{
|
|
||||||
var id = GetPathValue(2).ToString();
|
|
||||||
|
|
||||||
return service.ProcessControlRequestAsync(new ControlRequest
|
|
||||||
{
|
|
||||||
Headers = Request.Headers,
|
|
||||||
InputXml = requestStream,
|
|
||||||
TargetServerUuId = id,
|
|
||||||
RequestedUrl = Request.AbsoluteUri
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from MediaBrowser.Api/BaseApiService.cs
|
|
||||||
// TODO: Remove code duplication
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the path segment at the specified index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index of the path segment.</param>
|
|
||||||
/// <returns>The path segment at the specified index.</returns>
|
|
||||||
/// <exception cref="IndexOutOfRangeException" >Path doesn't contain enough segments.</exception>
|
|
||||||
/// <exception cref="InvalidDataException" >Path doesn't start with the base url.</exception>
|
|
||||||
protected internal ReadOnlySpan<char> GetPathValue(int index)
|
|
||||||
{
|
|
||||||
static void ThrowIndexOutOfRangeException()
|
|
||||||
=> throw new IndexOutOfRangeException("Path doesn't contain enough segments.");
|
|
||||||
|
|
||||||
static void ThrowInvalidDataException()
|
|
||||||
=> throw new InvalidDataException("Path doesn't start with the base url.");
|
|
||||||
|
|
||||||
ReadOnlySpan<char> path = Request.PathInfo;
|
|
||||||
|
|
||||||
// Remove the protocol part from the url
|
|
||||||
int pos = path.LastIndexOf("://");
|
|
||||||
if (pos != -1)
|
|
||||||
{
|
|
||||||
path = path.Slice(pos + 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the query string
|
|
||||||
pos = path.LastIndexOf('?');
|
|
||||||
if (pos != -1)
|
|
||||||
{
|
|
||||||
path = path.Slice(0, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the domain
|
|
||||||
pos = path.IndexOf('/');
|
|
||||||
if (pos != -1)
|
|
||||||
{
|
|
||||||
path = path.Slice(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove base url
|
|
||||||
string baseUrl = _configurationManager.Configuration.BaseUrl;
|
|
||||||
int baseUrlLen = baseUrl.Length;
|
|
||||||
if (baseUrlLen != 0)
|
|
||||||
{
|
|
||||||
if (path.StartsWith(baseUrl, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
path = path.Slice(baseUrlLen);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// The path doesn't start with the base url,
|
|
||||||
// how did we get here?
|
|
||||||
ThrowInvalidDataException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove leading /
|
|
||||||
path = path.Slice(1);
|
|
||||||
|
|
||||||
// Backwards compatibility
|
|
||||||
const string Emby = "emby/";
|
|
||||||
if (path.StartsWith(Emby, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
path = path.Slice(Emby.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
const string MediaBrowser = "mediabrowser/";
|
|
||||||
if (path.StartsWith(MediaBrowser, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
path = path.Slice(MediaBrowser.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip segments until we are at the right index
|
|
||||||
for (int i = 0; i < index; i++)
|
|
||||||
{
|
|
||||||
pos = path.IndexOf('/');
|
|
||||||
if (pos == -1)
|
|
||||||
{
|
|
||||||
ThrowIndexOutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
path = path.Slice(pos + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the rest
|
|
||||||
pos = path.IndexOf('/');
|
|
||||||
if (pos != -1)
|
|
||||||
{
|
|
||||||
path = path.Slice(0, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetIcon request)
|
|
||||||
{
|
|
||||||
var contentType = "image/" + Path.GetExtension(request.Filename)
|
|
||||||
.TrimStart('.')
|
|
||||||
.ToLowerInvariant();
|
|
||||||
|
|
||||||
var cacheLength = TimeSpan.FromDays(365);
|
|
||||||
var cacheKey = Request.RawUrl.GetMD5();
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
return ProcessEventRequest(ContentDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Subscribe(ProcessConnectionManagerEventRequest request)
|
|
||||||
{
|
|
||||||
return ProcessEventRequest(ConnectionManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request)
|
|
||||||
{
|
|
||||||
return ProcessEventRequest(MediaReceiverRegistrar);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Unsubscribe(ProcessContentDirectoryEventRequest request)
|
|
||||||
{
|
|
||||||
return ProcessEventRequest(ContentDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Unsubscribe(ProcessConnectionManagerEventRequest request)
|
|
||||||
{
|
|
||||||
return ProcessEventRequest(ConnectionManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request)
|
|
||||||
{
|
|
||||||
return ProcessEventRequest(MediaReceiverRegistrar);
|
|
||||||
}
|
|
||||||
|
|
||||||
private object ProcessEventRequest(IEventManager eventManager)
|
|
||||||
{
|
|
||||||
var subscriptionId = GetHeader("SID");
|
|
||||||
|
|
||||||
if (string.Equals(Request.Verb, "SUBSCRIBE", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
var notificationType = GetHeader("NT");
|
|
||||||
|
|
||||||
var callback = GetHeader("CALLBACK");
|
|
||||||
var timeoutString = GetHeader("TIMEOUT");
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(notificationType))
|
|
||||||
{
|
|
||||||
return GetSubscriptionResponse(eventManager.RenewEventSubscription(subscriptionId, notificationType, timeoutString, callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetSubscriptionResponse(eventManager.CreateEventSubscription(notificationType, timeoutString, callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetSubscriptionResponse(eventManager.CancelEventSubscription(subscriptionId));
|
|
||||||
}
|
|
||||||
|
|
||||||
private object GetSubscriptionResponse(EventSubscriptionResponse response)
|
|
||||||
{
|
|
||||||
return _resultFactory.GetResult(Request, response.Content, response.ContentType, response.Headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using MediaBrowser.Controller.Dlna;
|
|
||||||
using MediaBrowser.Controller.Net;
|
|
||||||
using MediaBrowser.Model.Dlna;
|
|
||||||
using MediaBrowser.Model.Services;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.Api
|
|
||||||
{
|
|
||||||
[Route("/Dlna/ProfileInfos", "GET", Summary = "Gets a list of profiles")]
|
|
||||||
public class GetProfileInfos : IReturn<DeviceProfileInfo[]>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/Profiles/{Id}", "DELETE", Summary = "Deletes a profile")]
|
|
||||||
public class DeleteProfile : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/Profiles/Default", "GET", Summary = "Gets the default profile")]
|
|
||||||
public class GetDefaultProfile : IReturn<DeviceProfile>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/Profiles/{Id}", "GET", Summary = "Gets a single profile")]
|
|
||||||
public class GetProfile : IReturn<DeviceProfile>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Id", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Id { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/Profiles/{Id}", "POST", Summary = "Updates a profile")]
|
|
||||||
public class UpdateProfile : DeviceProfile, IReturnVoid
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Dlna/Profiles", "POST", Summary = "Creates a profile")]
|
|
||||||
public class CreateProfile : DeviceProfile, IReturnVoid
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authenticated(Roles = "Admin")]
|
|
||||||
public class DlnaService : IService
|
|
||||||
{
|
|
||||||
private readonly IDlnaManager _dlnaManager;
|
|
||||||
|
|
||||||
public DlnaService(IDlnaManager dlnaManager)
|
|
||||||
{
|
|
||||||
_dlnaManager = dlnaManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Get(GetProfileInfos request)
|
|
||||||
{
|
|
||||||
return _dlnaManager.GetProfileInfos().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public object Get(GetProfile request)
|
|
||||||
{
|
|
||||||
return _dlnaManager.GetProfile(request.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Get(GetDefaultProfile request)
|
|
||||||
{
|
|
||||||
return _dlnaManager.GetDefaultProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(DeleteProfile request)
|
|
||||||
{
|
|
||||||
_dlnaManager.DeleteProfile(request.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(UpdateProfile request)
|
|
||||||
{
|
|
||||||
_dlnaManager.UpdateProfile(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Post(CreateProfile request)
|
|
||||||
{
|
|
||||||
_dlnaManager.CreateProfile(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,7 +13,7 @@ namespace Emby.Dlna.Common
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
public List<Argument> ArgumentList { get; set; }
|
public List<Argument> ArgumentList { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Emby.Dlna.Common
|
namespace Emby.Dlna.Common
|
||||||
{
|
{
|
||||||
|
@ -17,7 +18,7 @@ namespace Emby.Dlna.Common
|
||||||
|
|
||||||
public bool SendsEvents { get; set; }
|
public bool SendsEvents { get; set; }
|
||||||
|
|
||||||
public string[] AllowedValues { get; set; }
|
public IReadOnlyList<string> AllowedValues { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#nullable enable
|
#nullable enable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Emby.Dlna.Configuration;
|
using Emby.Dlna.Configuration;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
|
||||||
|
@ -14,19 +13,4 @@ namespace Emby.Dlna
|
||||||
return manager.GetConfiguration<DlnaOptions>("dlna");
|
return manager.GetConfiguration<DlnaOptions>("dlna");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DlnaConfigurationFactory : IConfigurationFactory
|
|
||||||
{
|
|
||||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
|
||||||
{
|
|
||||||
return new ConfigurationStore[]
|
|
||||||
{
|
|
||||||
new ConfigurationStore
|
|
||||||
{
|
|
||||||
Key = "dlna",
|
|
||||||
ConfigurationType = typeof (DlnaOptions)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,28 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.ConnectionManager
|
namespace Emby.Dlna.ConnectionManager
|
||||||
{
|
{
|
||||||
public class ConnectionManager : BaseService, IConnectionManager
|
public class ConnectionManagerService : BaseService, IConnectionManager
|
||||||
{
|
{
|
||||||
private readonly IDlnaManager _dlna;
|
private readonly IDlnaManager _dlna;
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
public ConnectionManager(
|
public ConnectionManagerService(
|
||||||
IDlnaManager dlna,
|
IDlnaManager dlna,
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
ILogger<ConnectionManager> logger,
|
ILogger<ConnectionManagerService> logger,
|
||||||
IHttpClient httpClient)
|
IHttpClientFactory httpClientFactory)
|
||||||
: base(logger, httpClient)
|
: base(logger, httpClientFactory)
|
||||||
{
|
{
|
||||||
_dlna = dlna;
|
_dlna = dlna;
|
||||||
_config = config;
|
_config = config;
|
||||||
_logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -39,7 +37,7 @@ namespace Emby.Dlna.ConnectionManager
|
||||||
var profile = _dlna.GetProfile(request.Headers) ??
|
var profile = _dlna.GetProfile(request.Headers) ??
|
||||||
_dlna.GetDefaultProfile();
|
_dlna.GetDefaultProfile();
|
||||||
|
|
||||||
return new ControlHandler(_config, _logger, profile).ProcessControlRequestAsync(request);
|
return new ControlHandler(_config, Logger, profile).ProcessControlRequestAsync(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -44,7 +44,7 @@ namespace Emby.Dlna.ConnectionManager
|
||||||
DataType = "string",
|
DataType = "string",
|
||||||
SendsEvents = false,
|
SendsEvents = false,
|
||||||
|
|
||||||
AllowedValues = new string[]
|
AllowedValues = new[]
|
||||||
{
|
{
|
||||||
"OK",
|
"OK",
|
||||||
"ContentFormatMismatch",
|
"ContentFormatMismatch",
|
||||||
|
@ -67,7 +67,7 @@ namespace Emby.Dlna.ConnectionManager
|
||||||
DataType = "string",
|
DataType = "string",
|
||||||
SendsEvents = false,
|
SendsEvents = false,
|
||||||
|
|
||||||
AllowedValues = new string[]
|
AllowedValues = new[]
|
||||||
{
|
{
|
||||||
"Output",
|
"Output",
|
||||||
"Input"
|
"Input"
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
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;
|
||||||
|
@ -19,7 +19,7 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.ContentDirectory
|
namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
public class ContentDirectory : BaseService, IContentDirectory
|
public class ContentDirectoryService : BaseService, IContentDirectory
|
||||||
{
|
{
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
@ -33,15 +33,15 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly ITVSeriesManager _tvSeriesManager;
|
private readonly ITVSeriesManager _tvSeriesManager;
|
||||||
|
|
||||||
public ContentDirectory(
|
public ContentDirectoryService(
|
||||||
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<ContentDirectoryService> logger,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClient,
|
||||||
ILocalizationManager localization,
|
ILocalizationManager localization,
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
IUserViewManager userViewManager,
|
IUserViewManager userViewManager,
|
|
@ -10,7 +10,8 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
public string GetXml()
|
public string GetXml()
|
||||||
{
|
{
|
||||||
return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(),
|
return new ServiceXmlBuilder().GetXml(
|
||||||
|
new ServiceActionListBuilder().GetActions(),
|
||||||
GetStateVariables());
|
GetStateVariables());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
DataType = "string",
|
DataType = "string",
|
||||||
SendsEvents = false,
|
SendsEvents = false,
|
||||||
|
|
||||||
AllowedValues = new string[]
|
AllowedValues = new[]
|
||||||
{
|
{
|
||||||
"BrowseMetadata",
|
"BrowseMetadata",
|
||||||
"BrowseDirectChildren"
|
"BrowseDirectChildren"
|
||||||
|
|
|
@ -11,6 +11,7 @@ using System.Xml;
|
||||||
using Emby.Dlna.Didl;
|
using Emby.Dlna.Didl;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
|
@ -39,6 +40,11 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
public class ControlHandler : BaseControlHandler
|
public class ControlHandler : BaseControlHandler
|
||||||
{
|
{
|
||||||
|
private const string NsDc = "http://purl.org/dc/elements/1.1/";
|
||||||
|
private const string NsDidl = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
|
||||||
|
private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/";
|
||||||
|
private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
||||||
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
@ -46,11 +52,6 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
private readonly IUserViewManager _userViewManager;
|
private readonly IUserViewManager _userViewManager;
|
||||||
private readonly ITVSeriesManager _tvSeriesManager;
|
private readonly ITVSeriesManager _tvSeriesManager;
|
||||||
|
|
||||||
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
|
|
||||||
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
|
|
||||||
private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
|
|
||||||
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
|
||||||
|
|
||||||
private readonly int _systemUpdateId;
|
private readonly int _systemUpdateId;
|
||||||
|
|
||||||
private readonly DidlBuilder _didlBuilder;
|
private readonly DidlBuilder _didlBuilder;
|
||||||
|
@ -180,7 +181,11 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
|
|
||||||
userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
|
userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
|
||||||
|
|
||||||
_userDataManager.SaveUserData(_user, item, userdata, UserDataSaveReason.TogglePlayed,
|
_userDataManager.SaveUserData(
|
||||||
|
_user,
|
||||||
|
item,
|
||||||
|
userdata,
|
||||||
|
UserDataSaveReason.TogglePlayed,
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +257,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
var id = sparams["ObjectID"];
|
var id = sparams["ObjectID"];
|
||||||
var flag = sparams["BrowseFlag"];
|
var flag = sparams["BrowseFlag"];
|
||||||
var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
|
var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
|
||||||
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
|
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty));
|
||||||
|
|
||||||
var provided = 0;
|
var provided = 0;
|
||||||
|
|
||||||
|
@ -285,18 +290,17 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
|
|
||||||
using (var writer = XmlWriter.Create(builder, settings))
|
using (var writer = XmlWriter.Create(builder, settings))
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
|
||||||
|
|
||||||
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
|
writer.WriteAttributeString("xmlns", "dc", null, NsDc);
|
||||||
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
|
writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
|
||||||
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
|
writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
|
||||||
|
|
||||||
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
|
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
|
||||||
|
|
||||||
var serverItem = GetItemFromObjectId(id);
|
var serverItem = GetItemFromObjectId(id);
|
||||||
var item = serverItem.Item;
|
var item = serverItem.Item;
|
||||||
|
|
||||||
|
|
||||||
if (string.Equals(flag, "BrowseMetadata", StringComparison.Ordinal))
|
if (string.Equals(flag, "BrowseMetadata", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
totalCount = 1;
|
totalCount = 1;
|
||||||
|
@ -361,8 +365,8 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
|
|
||||||
private void HandleSearch(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
|
private void HandleSearch(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
|
||||||
{
|
{
|
||||||
var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", ""));
|
var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", string.Empty));
|
||||||
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
|
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", string.Empty));
|
||||||
var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
|
var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
|
||||||
|
|
||||||
// sort example: dc:title, dc:date
|
// sort example: dc:title, dc:date
|
||||||
|
@ -396,11 +400,11 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
|
|
||||||
using (var writer = XmlWriter.Create(builder, settings))
|
using (var writer = XmlWriter.Create(builder, settings))
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
|
||||||
|
|
||||||
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
|
writer.WriteAttributeString("xmlns", "dc", null, NsDc);
|
||||||
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
|
writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
|
||||||
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
|
writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
|
||||||
|
|
||||||
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
|
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
|
||||||
|
|
||||||
|
@ -782,11 +786,14 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return ApplyPaging(new QueryResult<ServerItem>
|
return ApplyPaging(
|
||||||
|
new QueryResult<ServerItem>
|
||||||
{
|
{
|
||||||
Items = folders,
|
Items = folders,
|
||||||
TotalRecordCount = folders.Length
|
TotalRecordCount = folders.Length
|
||||||
}, startIndex, limit);
|
},
|
||||||
|
startIndex,
|
||||||
|
limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
|
private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
|
||||||
|
@ -1134,14 +1141,16 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
||||||
|
|
||||||
var items = _userViewManager.GetLatestItems(new LatestItemsQuery
|
var items = _userViewManager.GetLatestItems(
|
||||||
|
new LatestItemsQuery
|
||||||
{
|
{
|
||||||
UserId = user.Id,
|
UserId = user.Id,
|
||||||
Limit = 50,
|
Limit = 50,
|
||||||
IncludeItemTypes = new[] { nameof(Audio) },
|
IncludeItemTypes = new[] { nameof(Audio) },
|
||||||
ParentId = parent?.Id ?? Guid.Empty,
|
ParentId = parent?.Id ?? Guid.Empty,
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -1150,12 +1159,15 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
||||||
|
|
||||||
var result = _tvSeriesManager.GetNextUp(new NextUpQuery
|
var result = _tvSeriesManager.GetNextUp(
|
||||||
|
new NextUpQuery
|
||||||
{
|
{
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -1164,14 +1176,16 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
||||||
|
|
||||||
var items = _userViewManager.GetLatestItems(new LatestItemsQuery
|
var items = _userViewManager.GetLatestItems(
|
||||||
|
new LatestItemsQuery
|
||||||
{
|
{
|
||||||
UserId = user.Id,
|
UserId = user.Id,
|
||||||
Limit = 50,
|
Limit = 50,
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -1188,7 +1202,8 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
IncludeItemTypes = new[] { nameof(Movie) },
|
IncludeItemTypes = new[] { nameof(Movie) },
|
||||||
ParentId = parent?.Id ?? Guid.Empty,
|
ParentId = parent?.Id ?? Guid.Empty,
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
@ -1348,49 +1363,9 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
|
Logger.LogError("Error parsing item Id: {Id}. Returning user root folder.", id);
|
||||||
|
|
||||||
return new ServerItem(_libraryManager.GetUserRootFolder());
|
return new ServerItem(_libraryManager.GetUserRootFolder());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ServerItem
|
|
||||||
{
|
|
||||||
public BaseItem Item { get; set; }
|
|
||||||
|
|
||||||
public StubType? StubType { get; set; }
|
|
||||||
|
|
||||||
public ServerItem(BaseItem item)
|
|
||||||
{
|
|
||||||
Item = item;
|
|
||||||
|
|
||||||
if (item is IItemByName && !(item is Folder))
|
|
||||||
{
|
|
||||||
StubType = Dlna.ContentDirectory.StubType.Folder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum StubType
|
|
||||||
{
|
|
||||||
Folder = 0,
|
|
||||||
Latest = 2,
|
|
||||||
Playlists = 3,
|
|
||||||
Albums = 4,
|
|
||||||
AlbumArtists = 5,
|
|
||||||
Artists = 6,
|
|
||||||
Songs = 7,
|
|
||||||
Genres = 8,
|
|
||||||
FavoriteSongs = 9,
|
|
||||||
FavoriteArtists = 10,
|
|
||||||
FavoriteAlbums = 11,
|
|
||||||
ContinueWatching = 12,
|
|
||||||
Movies = 13,
|
|
||||||
Collections = 14,
|
|
||||||
Favorites = 15,
|
|
||||||
NextUp = 16,
|
|
||||||
Series = 17,
|
|
||||||
FavoriteSeries = 18,
|
|
||||||
FavoriteEpisodes = 19
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
23
Emby.Dlna/ContentDirectory/ServerItem.cs
Normal file
23
Emby.Dlna/ContentDirectory/ServerItem.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
|
||||||
|
namespace Emby.Dlna.ContentDirectory
|
||||||
|
{
|
||||||
|
internal class ServerItem
|
||||||
|
{
|
||||||
|
public ServerItem(BaseItem item)
|
||||||
|
{
|
||||||
|
Item = item;
|
||||||
|
|
||||||
|
if (item is IItemByName && !(item is Folder))
|
||||||
|
{
|
||||||
|
StubType = Dlna.ContentDirectory.StubType.Folder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseItem Item { get; set; }
|
||||||
|
|
||||||
|
public StubType? StubType { get; set; }
|
||||||
|
}
|
||||||
|
}
|
28
Emby.Dlna/ContentDirectory/StubType.cs
Normal file
28
Emby.Dlna/ContentDirectory/StubType.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
#pragma warning disable SA1602
|
||||||
|
|
||||||
|
namespace Emby.Dlna.ContentDirectory
|
||||||
|
{
|
||||||
|
public enum StubType
|
||||||
|
{
|
||||||
|
Folder = 0,
|
||||||
|
Latest = 2,
|
||||||
|
Playlists = 3,
|
||||||
|
Albums = 4,
|
||||||
|
AlbumArtists = 5,
|
||||||
|
Artists = 6,
|
||||||
|
Songs = 7,
|
||||||
|
Genres = 8,
|
||||||
|
FavoriteSongs = 9,
|
||||||
|
FavoriteArtists = 10,
|
||||||
|
FavoriteAlbums = 11,
|
||||||
|
ContinueWatching = 12,
|
||||||
|
Movies = 13,
|
||||||
|
Collections = 14,
|
||||||
|
Favorites = 15,
|
||||||
|
NextUp = 16,
|
||||||
|
Series = 17,
|
||||||
|
FavoriteSeries = 18,
|
||||||
|
FavoriteEpisodes = 19
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,17 +7,17 @@ namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public class ControlRequest
|
public class ControlRequest
|
||||||
{
|
{
|
||||||
public IHeaderDictionary Headers { get; set; }
|
public ControlRequest(IHeaderDictionary headers)
|
||||||
|
{
|
||||||
|
Headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IHeaderDictionary Headers { get; }
|
||||||
|
|
||||||
public Stream InputXml { get; set; }
|
public Stream InputXml { get; set; }
|
||||||
|
|
||||||
public string TargetServerUuId { get; set; }
|
public string TargetServerUuId { get; set; }
|
||||||
|
|
||||||
public string RequestedUrl { get; set; }
|
public string RequestedUrl { get; set; }
|
||||||
|
|
||||||
public ControlRequest()
|
|
||||||
{
|
|
||||||
Headers = new HeaderDictionary();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,16 @@ namespace Emby.Dlna
|
||||||
Headers = new Dictionary<string, string>();
|
Headers = new Dictionary<string, string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDictionary<string, string> Headers { get; set; }
|
public IDictionary<string, string> Headers { get; }
|
||||||
|
|
||||||
public string Xml { get; set; }
|
public string Xml { get; set; }
|
||||||
|
|
||||||
public bool IsSuccessful { get; set; }
|
public bool IsSuccessful { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Xml;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,12 +34,12 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
public class DidlBuilder
|
public class DidlBuilder
|
||||||
{
|
{
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
private const string NsDidl = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
|
||||||
|
private const string NsDc = "http://purl.org/dc/elements/1.1/";
|
||||||
|
private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
||||||
|
private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/";
|
||||||
|
|
||||||
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
|
|
||||||
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
|
||||||
private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
|
|
||||||
|
|
||||||
private readonly DeviceProfile _profile;
|
private readonly DeviceProfile _profile;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
@ -100,11 +100,11 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
// writer.WriteStartDocument();
|
// writer.WriteStartDocument();
|
||||||
|
|
||||||
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
|
||||||
|
|
||||||
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
|
writer.WriteAttributeString("xmlns", "dc", null, NsDc);
|
||||||
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
|
writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
|
||||||
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
|
writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
|
||||||
// didl.SetAttribute("xmlns:sec", NS_SEC);
|
// didl.SetAttribute("xmlns:sec", NS_SEC);
|
||||||
|
|
||||||
WriteXmlRootAttributes(_profile, writer);
|
WriteXmlRootAttributes(_profile, writer);
|
||||||
|
@ -147,7 +147,7 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
var clientId = GetClientId(item, null);
|
var clientId = GetClientId(item, null);
|
||||||
|
|
||||||
writer.WriteStartElement(string.Empty, "item", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "item", NsDidl);
|
||||||
|
|
||||||
writer.WriteAttributeString("restricted", "1");
|
writer.WriteAttributeString("restricted", "1");
|
||||||
writer.WriteAttributeString("id", clientId);
|
writer.WriteAttributeString("id", clientId);
|
||||||
|
@ -207,7 +207,8 @@ namespace Emby.Dlna.Didl
|
||||||
var targetWidth = streamInfo.TargetWidth;
|
var targetWidth = streamInfo.TargetWidth;
|
||||||
var targetHeight = streamInfo.TargetHeight;
|
var targetHeight = streamInfo.TargetHeight;
|
||||||
|
|
||||||
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container,
|
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(
|
||||||
|
streamInfo.Container,
|
||||||
streamInfo.TargetVideoCodec.FirstOrDefault(),
|
streamInfo.TargetVideoCodec.FirstOrDefault(),
|
||||||
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
||||||
targetWidth,
|
targetWidth,
|
||||||
|
@ -279,7 +280,7 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NsDidl);
|
||||||
|
|
||||||
writer.WriteAttributeString("protocolInfo", "http-get:*:smi/caption:*");
|
writer.WriteAttributeString("protocolInfo", "http-get:*:smi/caption:*");
|
||||||
|
|
||||||
|
@ -288,7 +289,7 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NsDidl);
|
||||||
var protocolInfo = string.Format(
|
var protocolInfo = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"http-get:*:text/{0}:*",
|
"http-get:*:text/{0}:*",
|
||||||
|
@ -304,7 +305,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo)
|
private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo)
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NsDidl);
|
||||||
|
|
||||||
var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken));
|
var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken));
|
||||||
|
|
||||||
|
@ -364,7 +365,8 @@ namespace Emby.Dlna.Didl
|
||||||
writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(_usCulture));
|
writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(_usCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
|
var mediaProfile = _profile.GetVideoMediaProfile(
|
||||||
|
streamInfo.Container,
|
||||||
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
||||||
streamInfo.TargetVideoCodec.FirstOrDefault(),
|
streamInfo.TargetVideoCodec.FirstOrDefault(),
|
||||||
streamInfo.TargetAudioBitrate,
|
streamInfo.TargetAudioBitrate,
|
||||||
|
@ -525,7 +527,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
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", NsDidl);
|
||||||
|
|
||||||
if (streamInfo == null)
|
if (streamInfo == null)
|
||||||
{
|
{
|
||||||
|
@ -582,7 +584,8 @@ namespace Emby.Dlna.Didl
|
||||||
writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
|
writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container,
|
var mediaProfile = _profile.GetAudioMediaProfile(
|
||||||
|
streamInfo.Container,
|
||||||
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
||||||
targetChannels,
|
targetChannels,
|
||||||
targetAudioBitrate,
|
targetAudioBitrate,
|
||||||
|
@ -595,7 +598,8 @@ namespace Emby.Dlna.Didl
|
||||||
? MimeTypes.GetMimeType(filename)
|
? MimeTypes.GetMimeType(filename)
|
||||||
: mediaProfile.MimeType;
|
: mediaProfile.MimeType;
|
||||||
|
|
||||||
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
|
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(
|
||||||
|
streamInfo.Container,
|
||||||
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
||||||
targetAudioBitrate,
|
targetAudioBitrate,
|
||||||
targetSampleRate,
|
targetSampleRate,
|
||||||
|
@ -626,7 +630,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
|
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "container", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "container", NsDidl);
|
||||||
|
|
||||||
writer.WriteAttributeString("restricted", "1");
|
writer.WriteAttributeString("restricted", "1");
|
||||||
writer.WriteAttributeString("searchable", "1");
|
writer.WriteAttributeString("searchable", "1");
|
||||||
|
@ -713,7 +717,7 @@ namespace Emby.Dlna.Didl
|
||||||
// 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), NsDc);
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteObjectClass(writer, item, itemStubType);
|
WriteObjectClass(writer, item, itemStubType);
|
||||||
|
@ -722,7 +726,7 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
if (item.PremiereDate.HasValue)
|
if (item.PremiereDate.HasValue)
|
||||||
{
|
{
|
||||||
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NS_DC);
|
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NsDc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -730,13 +734,13 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
foreach (var genre in item.Genres)
|
foreach (var genre in item.Genres)
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "genre", genre, NS_UPNP);
|
AddValue(writer, "upnp", "genre", genre, NsUpnp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var studio in item.Studios)
|
foreach (var studio in item.Studios)
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "publisher", studio, NS_UPNP);
|
AddValue(writer, "upnp", "publisher", studio, NsUpnp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(item is Folder))
|
if (!(item is Folder))
|
||||||
|
@ -747,28 +751,29 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(desc))
|
if (!string.IsNullOrWhiteSpace(desc))
|
||||||
{
|
{
|
||||||
AddValue(writer, "dc", "description", desc, NS_DC);
|
AddValue(writer, "dc", "description", desc, NsDc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (filter.Contains("upnp:longDescription"))
|
// if (filter.Contains("upnp:longDescription"))
|
||||||
//{
|
// {
|
||||||
// if (!string.IsNullOrWhiteSpace(item.Overview))
|
// if (!string.IsNullOrWhiteSpace(item.Overview))
|
||||||
// {
|
// {
|
||||||
// AddValue(writer, "upnp", "longDescription", item.Overview, NS_UPNP);
|
// AddValue(writer, "upnp", "longDescription", item.Overview, NsUpnp);
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(item.OfficialRating))
|
if (!string.IsNullOrEmpty(item.OfficialRating))
|
||||||
{
|
{
|
||||||
if (filter.Contains("dc:rating"))
|
if (filter.Contains("dc:rating"))
|
||||||
{
|
{
|
||||||
AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
|
AddValue(writer, "dc", "rating", item.OfficialRating, NsDc);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.Contains("upnp:rating"))
|
if (filter.Contains("upnp:rating"))
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
|
AddValue(writer, "upnp", "rating", item.OfficialRating, NsUpnp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -780,7 +785,7 @@ namespace Emby.Dlna.Didl
|
||||||
// More types here
|
// More types here
|
||||||
// http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs
|
// http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs
|
||||||
|
|
||||||
writer.WriteStartElement("upnp", "class", NS_UPNP);
|
writer.WriteStartElement("upnp", "class", NsUpnp);
|
||||||
|
|
||||||
if (item.IsDisplayedAsFolder || stubType.HasValue)
|
if (item.IsDisplayedAsFolder || stubType.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -881,7 +886,7 @@ namespace Emby.Dlna.Didl
|
||||||
var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
|
var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
|
||||||
?? PersonType.Actor;
|
?? PersonType.Actor;
|
||||||
|
|
||||||
AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
|
AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NsUpnp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -895,8 +900,8 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
foreach (var artist in hasArtists.Artists)
|
foreach (var artist in hasArtists.Artists)
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "artist", artist, NS_UPNP);
|
AddValue(writer, "upnp", "artist", artist, NsUpnp);
|
||||||
AddValue(writer, "dc", "creator", artist, NS_DC);
|
AddValue(writer, "dc", "creator", artist, NsDc);
|
||||||
|
|
||||||
// If it doesn't support album artists (musicvideo), then tag as both
|
// If it doesn't support album artists (musicvideo), then tag as both
|
||||||
if (hasAlbumArtists == null)
|
if (hasAlbumArtists == null)
|
||||||
|
@ -916,16 +921,16 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(item.Album))
|
if (!string.IsNullOrWhiteSpace(item.Album))
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "album", item.Album, NS_UPNP);
|
AddValue(writer, "upnp", "album", item.Album, NsUpnp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.IndexNumber.HasValue)
|
if (item.IndexNumber.HasValue)
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
|
AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp);
|
||||||
|
|
||||||
if (item is Episode)
|
if (item is Episode)
|
||||||
{
|
{
|
||||||
AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
|
AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -934,7 +939,7 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
writer.WriteStartElement("upnp", "artist", NS_UPNP);
|
writer.WriteStartElement("upnp", "artist", NsUpnp);
|
||||||
writer.WriteAttributeString("role", "AlbumArtist");
|
writer.WriteAttributeString("role", "AlbumArtist");
|
||||||
|
|
||||||
writer.WriteString(name);
|
writer.WriteString(name);
|
||||||
|
@ -943,7 +948,7 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
catch (XmlException ex)
|
catch (XmlException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error adding xml value: {value}", name);
|
_logger.LogError(ex, "Error adding xml value: {Value}", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -955,7 +960,7 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
catch (XmlException ex)
|
catch (XmlException ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error adding xml value: {value}", value);
|
_logger.LogError(ex, "Error adding xml value: {Value}", value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -970,14 +975,14 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
|
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
|
||||||
|
|
||||||
writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
|
writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
|
||||||
writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
|
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
|
||||||
writer.WriteString(albumartUrlInfo.Url);
|
writer.WriteString(albumartUrlInfo.url);
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
|
|
||||||
// TOOD: Remove these default values
|
// TOOD: Remove these default values
|
||||||
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
|
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
|
||||||
writer.WriteElementString("upnp", "icon", NS_UPNP, iconUrlInfo.Url);
|
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
|
||||||
|
|
||||||
if (!_profile.EnableAlbumArtInDidl)
|
if (!_profile.EnableAlbumArtInDidl)
|
||||||
{
|
{
|
||||||
|
@ -1020,12 +1025,12 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format);
|
var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format);
|
||||||
|
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NsDidl);
|
||||||
|
|
||||||
// Images must have a reported size or many clients (Bubble upnp), will only use the first thumbnail
|
// Images must have a reported size or many clients (Bubble upnp), will only use the first thumbnail
|
||||||
// rather than using a larger one when available
|
// rather than using a larger one when available
|
||||||
var width = albumartUrlInfo.Width ?? maxWidth;
|
var width = albumartUrlInfo.width ?? maxWidth;
|
||||||
var height = albumartUrlInfo.Height ?? maxHeight;
|
var height = albumartUrlInfo.height ?? maxHeight;
|
||||||
|
|
||||||
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);
|
||||||
|
@ -1042,7 +1047,7 @@ namespace Emby.Dlna.Didl
|
||||||
"resolution",
|
"resolution",
|
||||||
string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
|
string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
|
||||||
|
|
||||||
writer.WriteString(albumartUrlInfo.Url);
|
writer.WriteString(albumartUrlInfo.url);
|
||||||
|
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
}
|
}
|
||||||
|
@ -1138,7 +1143,6 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
if (width == 0 || height == 0)
|
if (width == 0 || height == 0)
|
||||||
{
|
{
|
||||||
// _imageProcessor.GetImageSize(item, imageInfo);
|
|
||||||
width = null;
|
width = null;
|
||||||
height = null;
|
height = null;
|
||||||
}
|
}
|
||||||
|
@ -1148,18 +1152,6 @@ namespace Emby.Dlna.Didl
|
||||||
height = null;
|
height = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try
|
|
||||||
//{
|
|
||||||
// var size = _imageProcessor.GetImageSize(imageInfo);
|
|
||||||
|
|
||||||
// width = size.Width;
|
|
||||||
// height = size.Height;
|
|
||||||
//}
|
|
||||||
// catch
|
|
||||||
//{
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
||||||
var inputFormat = (Path.GetExtension(imageInfo.Path) ?? string.Empty)
|
var inputFormat = (Path.GetExtension(imageInfo.Path) ?? string.Empty)
|
||||||
.TrimStart('.')
|
.TrimStart('.')
|
||||||
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
|
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
|
||||||
|
@ -1176,30 +1168,6 @@ namespace Emby.Dlna.Didl
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ImageDownloadInfo
|
|
||||||
{
|
|
||||||
internal Guid ItemId;
|
|
||||||
internal string ImageTag;
|
|
||||||
internal ImageType Type;
|
|
||||||
|
|
||||||
internal int? Width;
|
|
||||||
internal int? Height;
|
|
||||||
|
|
||||||
internal bool IsDirectStream;
|
|
||||||
|
|
||||||
internal string Format;
|
|
||||||
|
|
||||||
internal ItemImageInfo ItemImageInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ImageUrlInfo
|
|
||||||
{
|
|
||||||
internal string Url;
|
|
||||||
|
|
||||||
internal int? Width;
|
|
||||||
internal int? Height;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetClientId(BaseItem item, StubType? stubType)
|
public static string GetClientId(BaseItem item, StubType? stubType)
|
||||||
{
|
{
|
||||||
return GetClientId(item.Id, stubType);
|
return GetClientId(item.Id, stubType);
|
||||||
|
@ -1217,7 +1185,7 @@ namespace Emby.Dlna.Didl
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
|
private (string url, int? width, int? height) GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
|
||||||
{
|
{
|
||||||
var url = string.Format(
|
var url = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
|
@ -1255,12 +1223,26 @@ namespace Emby.Dlna.Didl
|
||||||
// just lie
|
// just lie
|
||||||
info.IsDirectStream = true;
|
info.IsDirectStream = true;
|
||||||
|
|
||||||
return new ImageUrlInfo
|
return (url, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ImageDownloadInfo
|
||||||
{
|
{
|
||||||
Url = url,
|
internal Guid ItemId { get; set; }
|
||||||
Width = width,
|
|
||||||
Height = height
|
internal string ImageTag { get; set; }
|
||||||
};
|
|
||||||
|
internal ImageType Type { get; set; }
|
||||||
|
|
||||||
|
internal int? Width { get; set; }
|
||||||
|
|
||||||
|
internal int? Height { get; set; }
|
||||||
|
|
||||||
|
internal bool IsDirectStream { get; set; }
|
||||||
|
|
||||||
|
internal string Format { get; set; }
|
||||||
|
|
||||||
|
internal ItemImageInfo ItemImageInfo { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
public bool Contains(string field)
|
public bool Contains(string field)
|
||||||
{
|
{
|
||||||
// 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 _all || Array.Exists(_fields, x => x.Equals(field, StringComparison.OrdinalIgnoreCase));
|
||||||
return true;
|
|
||||||
// return _all || ListHelper.ContainsIgnoreCase(_fields, field);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
#pragma warning disable CA1305
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -29,7 +30,6 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public StringWriterWithEncoding(Encoding encoding)
|
public StringWriterWithEncoding(Encoding encoding)
|
||||||
{
|
{
|
||||||
_encoding = encoding;
|
_encoding = encoding;
|
||||||
|
|
24
Emby.Dlna/DlnaConfigurationFactory.cs
Normal file
24
Emby.Dlna/DlnaConfigurationFactory.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#nullable enable
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Emby.Dlna.Configuration;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
|
||||||
|
namespace Emby.Dlna
|
||||||
|
{
|
||||||
|
public class DlnaConfigurationFactory : IConfigurationFactory
|
||||||
|
{
|
||||||
|
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
new ConfigurationStore
|
||||||
|
{
|
||||||
|
Key = "dlna",
|
||||||
|
ConfigurationType = typeof(DlnaOptions)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,11 +54,15 @@ namespace Emby.Dlna
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string UserProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user");
|
||||||
|
|
||||||
|
private string SystemProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system");
|
||||||
|
|
||||||
public async Task InitProfilesAsync()
|
public async Task InitProfilesAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ExtractSystemProfilesAsync();
|
await ExtractSystemProfilesAsync().ConfigureAwait(false);
|
||||||
LoadProfiles();
|
LoadProfiles();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -122,15 +126,15 @@ namespace Emby.Dlna
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
builder.AppendLine("No matching device profile found. The default will need to be used.");
|
builder.AppendLine("No matching device profile found. The default will need to be used.");
|
||||||
builder.AppendLine(string.Format("DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty));
|
builder.AppendFormat(CultureInfo.InvariantCulture, "DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty).AppendLine();
|
||||||
builder.AppendLine(string.Format("FriendlyName:{0}", profile.FriendlyName ?? string.Empty));
|
builder.AppendFormat(CultureInfo.InvariantCulture, "FriendlyName:{0}", profile.FriendlyName ?? string.Empty).AppendLine();
|
||||||
builder.AppendLine(string.Format("Manufacturer:{0}", profile.Manufacturer ?? string.Empty));
|
builder.AppendFormat(CultureInfo.InvariantCulture, "Manufacturer:{0}", profile.Manufacturer ?? string.Empty).AppendLine();
|
||||||
builder.AppendLine(string.Format("ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty));
|
builder.AppendFormat(CultureInfo.InvariantCulture, "ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty).AppendLine();
|
||||||
builder.AppendLine(string.Format("ModelDescription:{0}", profile.ModelDescription ?? string.Empty));
|
builder.AppendFormat(CultureInfo.InvariantCulture, "ModelDescription:{0}", profile.ModelDescription ?? string.Empty).AppendLine();
|
||||||
builder.AppendLine(string.Format("ModelName:{0}", profile.ModelName ?? string.Empty));
|
builder.AppendFormat(CultureInfo.InvariantCulture, "ModelName:{0}", profile.ModelName ?? string.Empty).AppendLine();
|
||||||
builder.AppendLine(string.Format("ModelNumber:{0}", profile.ModelNumber ?? string.Empty));
|
builder.AppendFormat(CultureInfo.InvariantCulture, "ModelNumber:{0}", profile.ModelNumber ?? string.Empty).AppendLine();
|
||||||
builder.AppendLine(string.Format("ModelUrl:{0}", profile.ModelUrl ?? string.Empty));
|
builder.AppendFormat(CultureInfo.InvariantCulture, "ModelUrl:{0}", profile.ModelUrl ?? string.Empty).AppendLine();
|
||||||
builder.AppendLine(string.Format("SerialNumber:{0}", profile.SerialNumber ?? string.Empty));
|
builder.AppendFormat(CultureInfo.InvariantCulture, "SerialNumber:{0}", profile.SerialNumber ?? string.Empty).AppendLine();
|
||||||
|
|
||||||
_logger.LogInformation(builder.ToString());
|
_logger.LogInformation(builder.ToString());
|
||||||
}
|
}
|
||||||
|
@ -240,7 +244,7 @@ namespace Emby.Dlna
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var headerString = string.Join(", ", headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
|
var headerString = string.Join(", ", headers.Select(i => string.Format(CultureInfo.InvariantCulture, "{0}={1}", i.Key, i.Value)));
|
||||||
_logger.LogDebug("No matching device profile found. {0}", headerString);
|
_logger.LogDebug("No matching device profile found. {0}", headerString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,10 +284,6 @@ namespace Emby.Dlna
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string UserProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user");
|
|
||||||
|
|
||||||
private string SystemProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system");
|
|
||||||
|
|
||||||
private IEnumerable<DeviceProfile> GetProfiles(string path, DeviceProfileType type)
|
private IEnumerable<DeviceProfile> GetProfiles(string path, DeviceProfileType type)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -387,7 +387,7 @@ namespace Emby.Dlna
|
||||||
|
|
||||||
foreach (var name in _assembly.GetManifestResourceNames())
|
foreach (var name in _assembly.GetManifestResourceNames())
|
||||||
{
|
{
|
||||||
if (!name.StartsWith(namespaceName))
|
if (!name.StartsWith(namespaceName, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -406,7 +406,7 @@ namespace Emby.Dlna
|
||||||
|
|
||||||
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
{
|
{
|
||||||
await stream.CopyToAsync(fileStream);
|
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -495,8 +495,8 @@ namespace Emby.Dlna
|
||||||
/// 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">The device profile.</param>
|
||||||
/// <returns></returns>
|
/// <returns>The reserialized device profile.</returns>
|
||||||
private DeviceProfile ReserializeProfile(DeviceProfile profile)
|
private DeviceProfile ReserializeProfile(DeviceProfile profile)
|
||||||
{
|
{
|
||||||
if (profile.GetType() == typeof(DeviceProfile))
|
if (profile.GetType() == typeof(DeviceProfile))
|
||||||
|
@ -509,13 +509,6 @@ namespace Emby.Dlna
|
||||||
return _jsonSerializer.DeserializeFromString<DeviceProfile>(json);
|
return _jsonSerializer.DeserializeFromString<DeviceProfile>(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
class InternalProfileInfo
|
|
||||||
{
|
|
||||||
internal DeviceProfileInfo Info { get; set; }
|
|
||||||
|
|
||||||
internal string Path { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
|
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
|
||||||
{
|
{
|
||||||
var profile = GetProfile(headers) ??
|
var profile = GetProfile(headers) ??
|
||||||
|
@ -540,7 +533,15 @@ namespace Emby.Dlna
|
||||||
Stream = _assembly.GetManifestResourceStream(resource)
|
Stream = _assembly.GetManifestResourceStream(resource)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class InternalProfileInfo
|
||||||
|
{
|
||||||
|
internal DeviceProfileInfo Info { get; set; }
|
||||||
|
|
||||||
|
internal string Path { get; set; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
class DlnaProfileEntryPoint : IServerEntryPoint
|
class DlnaProfileEntryPoint : IServerEntryPoint
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
|
@ -80,6 +80,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -15,6 +15,6 @@ namespace Emby.Dlna
|
||||||
|
|
||||||
public string ContentType { get; set; }
|
public string ContentType { get; set; }
|
||||||
|
|
||||||
public Dictionary<string, string> Headers { get; set; }
|
public Dictionary<string, string> Headers { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Mime;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
|
@ -14,17 +15,19 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.Eventing
|
namespace Emby.Dlna.Eventing
|
||||||
{
|
{
|
||||||
public class EventManager : IEventManager
|
public class DlnaEventManager : IDlnaEventManager
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions =
|
private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions =
|
||||||
new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
|
new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
public EventManager(ILogger logger, IHttpClient httpClient)
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
|
public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +61,8 @@ namespace Emby.Dlna.Eventing
|
||||||
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
|
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
|
||||||
var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
_logger.LogDebug("Creating event subscription for {0} with timeout of {1} to {2}",
|
_logger.LogDebug(
|
||||||
|
"Creating event subscription for {0} with timeout of {1} to {2}",
|
||||||
notificationType,
|
notificationType,
|
||||||
timeout,
|
timeout,
|
||||||
callbackUrl);
|
callbackUrl);
|
||||||
|
@ -94,7 +98,7 @@ namespace Emby.Dlna.Eventing
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Cancelling event subscription {0}", subscriptionId);
|
_logger.LogDebug("Cancelling event subscription {0}", subscriptionId);
|
||||||
|
|
||||||
_subscriptions.TryRemove(subscriptionId, out EventSubscription sub);
|
_subscriptions.TryRemove(subscriptionId, out _);
|
||||||
|
|
||||||
return new EventSubscriptionResponse
|
return new EventSubscriptionResponse
|
||||||
{
|
{
|
||||||
|
@ -103,7 +107,6 @@ namespace Emby.Dlna.Eventing
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
|
||||||
private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
|
private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
|
||||||
{
|
{
|
||||||
var response = new EventSubscriptionResponse
|
var response = new EventSubscriptionResponse
|
||||||
|
@ -152,33 +155,30 @@ namespace Emby.Dlna.Eventing
|
||||||
builder.Append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">");
|
builder.Append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">");
|
||||||
foreach (var key in stateVariables.Keys)
|
foreach (var key in stateVariables.Keys)
|
||||||
{
|
{
|
||||||
builder.Append("<e:property>");
|
builder.Append("<e:property>")
|
||||||
builder.Append("<" + key + ">");
|
.Append('<')
|
||||||
builder.Append(stateVariables[key]);
|
.Append(key)
|
||||||
builder.Append("</" + key + ">");
|
.Append('>')
|
||||||
builder.Append("</e:property>");
|
.Append(stateVariables[key])
|
||||||
|
.Append("</")
|
||||||
|
.Append(key)
|
||||||
|
.Append('>')
|
||||||
|
.Append("</e:property>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append("</e:propertyset>");
|
builder.Append("</e:propertyset>");
|
||||||
|
|
||||||
var options = new HttpRequestOptions
|
using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl);
|
||||||
{
|
options.Content = new StringContent(builder.ToString(), Encoding.UTF8, MediaTypeNames.Text.Xml);
|
||||||
RequestContent = builder.ToString(),
|
options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType);
|
||||||
RequestContentType = "text/xml",
|
options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange");
|
||||||
Url = subscription.CallbackUrl,
|
options.Headers.TryAddWithoutValidation("SID", subscription.Id);
|
||||||
BufferContent = false
|
options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(_usCulture));
|
||||||
};
|
|
||||||
|
|
||||||
options.RequestHeaders.Add("NT", subscription.NotificationType);
|
|
||||||
options.RequestHeaders.Add("NTS", "upnp:propchange");
|
|
||||||
options.RequestHeaders.Add("SID", subscription.Id);
|
|
||||||
options.RequestHeaders.Add("SEQ", subscription.TriggerCount.ToString(_usCulture));
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
{
|
.SendAsync(options, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public interface IConnectionManager : IEventManager, IUpnpService
|
public interface IConnectionManager : IDlnaEventManager, IUpnpService
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public interface IContentDirectory : IEventManager, IUpnpService
|
public interface IContentDirectory : IDlnaEventManager, IUpnpService
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,22 +2,32 @@
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public interface IEventManager
|
public interface IDlnaEventManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cancels the event subscription.
|
/// Cancels the event subscription.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="subscriptionId">The subscription identifier.</param>
|
/// <param name="subscriptionId">The subscription identifier.</param>
|
||||||
|
/// <returns>The response.</returns>
|
||||||
EventSubscriptionResponse CancelEventSubscription(string subscriptionId);
|
EventSubscriptionResponse CancelEventSubscription(string subscriptionId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Renews the event subscription.
|
/// Renews the event subscription.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="subscriptionId">The subscription identifier.</param>
|
||||||
|
/// <param name="notificationType">The notification type.</param>
|
||||||
|
/// <param name="requestedTimeoutString">The requested timeout as a sting.</param>
|
||||||
|
/// <param name="callbackUrl">The callback url.</param>
|
||||||
|
/// <returns>The response.</returns>
|
||||||
EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl);
|
EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the event subscription.
|
/// Creates the event subscription.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="notificationType">The notification type.</param>
|
||||||
|
/// <param name="requestedTimeoutString">The requested timeout as a sting.</param>
|
||||||
|
/// <param name="callbackUrl">The callback url.</param>
|
||||||
|
/// <returns>The response.</returns>
|
||||||
EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl);
|
EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public interface IMediaReceiverRegistrar : IEventManager, IUpnpService
|
public interface IMediaReceiverRegistrar : IDlnaEventManager, IUpnpService
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -30,15 +31,13 @@ using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||||
|
|
||||||
namespace Emby.Dlna.Main
|
namespace Emby.Dlna.Main
|
||||||
{
|
{
|
||||||
public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
|
public sealed class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
|
||||||
{
|
{
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly ILogger<DlnaEntryPoint> _logger;
|
private readonly ILogger<DlnaEntryPoint> _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 IHttpClientFactory _httpClientFactory;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IDlnaManager _dlnaManager;
|
private readonly IDlnaManager _dlnaManager;
|
||||||
|
@ -47,30 +46,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; }
|
private bool _disposed;
|
||||||
|
|
||||||
internal IConnectionManager ConnectionManager { get; private set; }
|
|
||||||
|
|
||||||
internal IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
|
|
||||||
|
|
||||||
public static DlnaEntryPoint Current;
|
|
||||||
|
|
||||||
public DlnaEntryPoint(
|
public DlnaEntryPoint(
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IServerApplicationHost appHost,
|
IServerApplicationHost appHost,
|
||||||
ISessionManager sessionManager,
|
ISessionManager sessionManager,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClientFactory,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
IDlnaManager dlnaManager,
|
IDlnaManager dlnaManager,
|
||||||
|
@ -88,7 +80,7 @@ namespace Emby.Dlna.Main
|
||||||
_config = config;
|
_config = config;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_dlnaManager = dlnaManager;
|
_dlnaManager = dlnaManager;
|
||||||
|
@ -102,34 +94,42 @@ namespace Emby.Dlna.Main
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
_logger = loggerFactory.CreateLogger<DlnaEntryPoint>();
|
_logger = loggerFactory.CreateLogger<DlnaEntryPoint>();
|
||||||
|
|
||||||
ContentDirectory = new ContentDirectory.ContentDirectory(
|
ContentDirectory = new ContentDirectory.ContentDirectoryService(
|
||||||
dlnaManager,
|
dlnaManager,
|
||||||
userDataManager,
|
userDataManager,
|
||||||
imageProcessor,
|
imageProcessor,
|
||||||
libraryManager,
|
libraryManager,
|
||||||
config,
|
config,
|
||||||
userManager,
|
userManager,
|
||||||
loggerFactory.CreateLogger<ContentDirectory.ContentDirectory>(),
|
loggerFactory.CreateLogger<ContentDirectory.ContentDirectoryService>(),
|
||||||
httpClient,
|
httpClientFactory,
|
||||||
localizationManager,
|
localizationManager,
|
||||||
mediaSourceManager,
|
mediaSourceManager,
|
||||||
userViewManager,
|
userViewManager,
|
||||||
mediaEncoder,
|
mediaEncoder,
|
||||||
tvSeriesManager);
|
tvSeriesManager);
|
||||||
|
|
||||||
ConnectionManager = new ConnectionManager.ConnectionManager(
|
ConnectionManager = new ConnectionManager.ConnectionManagerService(
|
||||||
dlnaManager,
|
dlnaManager,
|
||||||
config,
|
config,
|
||||||
loggerFactory.CreateLogger<ConnectionManager.ConnectionManager>(),
|
loggerFactory.CreateLogger<ConnectionManager.ConnectionManagerService>(),
|
||||||
httpClient);
|
httpClientFactory);
|
||||||
|
|
||||||
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(
|
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService(
|
||||||
loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrar>(),
|
loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrarService>(),
|
||||||
httpClient,
|
httpClientFactory,
|
||||||
config);
|
config);
|
||||||
Current = this;
|
Current = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static DlnaEntryPoint Current { get; private set; }
|
||||||
|
|
||||||
|
public IContentDirectory ContentDirectory { get; private set; }
|
||||||
|
|
||||||
|
public IConnectionManager ConnectionManager { get; private set; }
|
||||||
|
|
||||||
|
public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
|
||||||
|
|
||||||
public async Task RunAsync()
|
public async Task RunAsync()
|
||||||
{
|
{
|
||||||
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
|
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
|
||||||
|
@ -181,7 +181,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(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
_communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
||||||
{
|
{
|
||||||
IsShared = true
|
IsShared = true
|
||||||
};
|
};
|
||||||
|
@ -232,20 +232,22 @@ 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;
|
{
|
||||||
_Publisher.SupportPnpRootDevice = false;
|
LogFunction = LogMessage,
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
|
@ -267,6 +269,12 @@ namespace Emby.Dlna.Main
|
||||||
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";
|
||||||
|
|
||||||
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
|
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
|
||||||
|
@ -288,13 +296,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)
|
||||||
|
@ -326,7 +334,7 @@ namespace Emby.Dlna.Main
|
||||||
|
|
||||||
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
||||||
{
|
{
|
||||||
var service = fullDeviceType.Replace("urn:", string.Empty).Replace(":1", string.Empty);
|
var service = fullDeviceType.Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase).Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var serviceParts = service.Split(':');
|
var serviceParts = service.Split(':');
|
||||||
|
|
||||||
|
@ -337,7 +345,6 @@ 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)
|
||||||
|
@ -358,7 +365,7 @@ namespace Emby.Dlna.Main
|
||||||
_appHost,
|
_appHost,
|
||||||
_imageProcessor,
|
_imageProcessor,
|
||||||
_deviceDiscovery,
|
_deviceDiscovery,
|
||||||
_httpClient,
|
_httpClientFactory,
|
||||||
_config,
|
_config,
|
||||||
_userDataManager,
|
_userDataManager,
|
||||||
_localization,
|
_localization,
|
||||||
|
@ -395,8 +402,24 @@ namespace Emby.Dlna.Main
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DisposeDevicePublisher()
|
||||||
|
{
|
||||||
|
if (_publisher != null)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Disposing SsdpDevicePublisher");
|
||||||
|
_publisher.Dispose();
|
||||||
|
_publisher = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DisposeDevicePublisher();
|
DisposeDevicePublisher();
|
||||||
DisposePlayToManager();
|
DisposePlayToManager();
|
||||||
DisposeDeviceDiscovery();
|
DisposeDeviceDiscovery();
|
||||||
|
@ -412,16 +435,8 @@ namespace Emby.Dlna.Main
|
||||||
ConnectionManager = null;
|
ConnectionManager = null;
|
||||||
MediaReceiverRegistrar = null;
|
MediaReceiverRegistrar = null;
|
||||||
Current = null;
|
Current = null;
|
||||||
}
|
|
||||||
|
|
||||||
public void DisposeDevicePublisher()
|
_disposed = true;
|
||||||
{
|
|
||||||
if (_Publisher != null)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Disposing SsdpDevicePublisher");
|
|
||||||
_Publisher.Dispose();
|
|
||||||
_Publisher = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.MediaReceiverRegistrar
|
namespace Emby.Dlna.MediaReceiverRegistrar
|
||||||
{
|
{
|
||||||
public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar
|
public class MediaReceiverRegistrarService : BaseService, IMediaReceiverRegistrar
|
||||||
{
|
{
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
public MediaReceiverRegistrar(
|
public MediaReceiverRegistrarService(
|
||||||
ILogger<MediaReceiverRegistrar> logger,
|
ILogger<MediaReceiverRegistrarService> logger,
|
||||||
IHttpClient httpClient,
|
IHttpClientFactory httpClientFactory,
|
||||||
IServerConfigurationManager config)
|
IServerConfigurationManager config)
|
||||||
: base(logger, httpClient)
|
: base(logger, httpClientFactory)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
|
@ -10,7 +10,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
||||||
{
|
{
|
||||||
public string GetXml()
|
public string GetXml()
|
||||||
{
|
{
|
||||||
return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(),
|
return new ServiceXmlBuilder().GetXml(
|
||||||
|
new ServiceActionListBuilder().GetActions(),
|
||||||
GetStateVariables());
|
GetStateVariables());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,13 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Security;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using Emby.Dlna.Server;
|
|
||||||
using Emby.Dlna.Ssdp;
|
using Emby.Dlna.Ssdp;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
@ -19,15 +20,40 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class Device : IDisposable
|
public class Device : IDisposable
|
||||||
{
|
{
|
||||||
|
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
private readonly object _timerLock = new object();
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
|
private int _muteVol;
|
||||||
|
private int _volume;
|
||||||
|
private DateTime _lastVolumeRefresh;
|
||||||
|
private bool _volumeRefreshActive;
|
||||||
|
private int _connectFailureCount;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
public Device(DeviceInfo deviceProperties, IHttpClientFactory httpClientFactory, ILogger logger)
|
||||||
|
{
|
||||||
|
Properties = deviceProperties;
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<PlaybackStartEventArgs> PlaybackStart;
|
||||||
|
|
||||||
|
public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress;
|
||||||
|
|
||||||
|
public event EventHandler<PlaybackStoppedEventArgs> PlaybackStopped;
|
||||||
|
|
||||||
|
public event EventHandler<MediaChangedEventArgs> MediaChanged;
|
||||||
|
|
||||||
public DeviceInfo Properties { get; set; }
|
public DeviceInfo Properties { get; set; }
|
||||||
|
|
||||||
private int _muteVol;
|
|
||||||
public bool IsMuted { get; set; }
|
public bool IsMuted { get; set; }
|
||||||
|
|
||||||
private int _volume;
|
|
||||||
|
|
||||||
public int Volume
|
public int Volume
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -43,29 +69,21 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0);
|
public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0);
|
||||||
|
|
||||||
public TRANSPORTSTATE TransportState { get; private set; }
|
public TransportState TransportState { get; private set; }
|
||||||
|
|
||||||
public bool IsPlaying => TransportState == TRANSPORTSTATE.PLAYING;
|
public bool IsPlaying => TransportState == TransportState.Playing;
|
||||||
|
|
||||||
public bool IsPaused => TransportState == TRANSPORTSTATE.PAUSED || TransportState == TRANSPORTSTATE.PAUSED_PLAYBACK;
|
public bool IsPaused => TransportState == TransportState.Paused || TransportState == TransportState.PausedPlayback;
|
||||||
|
|
||||||
public bool IsStopped => TransportState == TRANSPORTSTATE.STOPPED;
|
public bool IsStopped => TransportState == TransportState.Stopped;
|
||||||
|
|
||||||
private readonly IHttpClient _httpClient;
|
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
private readonly IServerConfigurationManager _config;
|
|
||||||
|
|
||||||
public Action OnDeviceUnavailable { get; set; }
|
public Action OnDeviceUnavailable { get; set; }
|
||||||
|
|
||||||
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
|
private TransportCommands AvCommands { get; set; }
|
||||||
{
|
|
||||||
Properties = deviceProperties;
|
private TransportCommands RendererCommands { get; set; }
|
||||||
_httpClient = httpClient;
|
|
||||||
_logger = logger;
|
public UBaseObject CurrentMediaInfo { get; private set; }
|
||||||
_config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
|
@ -73,8 +91,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
_timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite);
|
_timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DateTime _lastVolumeRefresh;
|
|
||||||
private bool _volumeRefreshActive;
|
|
||||||
private Task RefreshVolumeIfNeeded()
|
private Task RefreshVolumeIfNeeded()
|
||||||
{
|
{
|
||||||
if (_volumeRefreshActive
|
if (_volumeRefreshActive
|
||||||
|
@ -105,7 +121,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly object _timerLock = new object();
|
|
||||||
private void RestartTimer(bool immediate = false)
|
private void RestartTimer(bool immediate = false)
|
||||||
{
|
{
|
||||||
lock (_timerLock)
|
lock (_timerLock)
|
||||||
|
@ -222,7 +237,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
_logger.LogDebug("Setting mute");
|
_logger.LogDebug("Setting mute");
|
||||||
var value = mute ? 1 : 0;
|
var value = mute ? 1 : 0;
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
|
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
IsMuted = mute;
|
IsMuted = mute;
|
||||||
|
@ -233,6 +248,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets volume on a scale of 0-100.
|
/// Sets volume on a scale of 0-100.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="value">The volume on a scale of 0-100.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||||
public async Task SetVolume(int value, CancellationToken cancellationToken)
|
public async Task SetVolume(int value, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -254,7 +272,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
// Remote control will perform better
|
// Remote control will perform better
|
||||||
Volume = value;
|
Volume = value;
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
|
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +293,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
throw new InvalidOperationException("Unable to find service");
|
throw new InvalidOperationException("Unable to find service");
|
||||||
}
|
}
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
|
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
RestartTimer(true);
|
RestartTimer(true);
|
||||||
|
@ -285,7 +303,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
url = url.Replace("&", "&");
|
url = url.Replace("&", "&", StringComparison.Ordinal);
|
||||||
|
|
||||||
_logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
|
_logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
|
||||||
|
|
||||||
|
@ -297,8 +315,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var dictionary = new Dictionary<string, string>
|
var dictionary = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{"CurrentURI", url},
|
{ "CurrentURI", url },
|
||||||
{"CurrentURIMetaData", CreateDidlMeta(metaData)}
|
{ "CurrentURIMetaData", CreateDidlMeta(metaData) }
|
||||||
};
|
};
|
||||||
|
|
||||||
var service = GetAvTransportService();
|
var service = GetAvTransportService();
|
||||||
|
@ -309,7 +327,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
|
|
||||||
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
|
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
|
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
await Task.Delay(50).ConfigureAwait(false);
|
await Task.Delay(50).ConfigureAwait(false);
|
||||||
|
@ -334,7 +352,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
return DescriptionXmlBuilder.Escape(value);
|
return SecurityElement.Escape(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken)
|
private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken)
|
||||||
|
@ -351,7 +369,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(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
command.Name,
|
command.Name,
|
||||||
|
@ -380,7 +398,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var service = GetAvTransportService();
|
var service = GetAvTransportService();
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
RestartTimer(true);
|
RestartTimer(true);
|
||||||
|
@ -398,16 +416,14 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var service = GetAvTransportService();
|
var service = GetAvTransportService();
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
TransportState = TRANSPORTSTATE.PAUSED;
|
TransportState = TransportState.Paused;
|
||||||
|
|
||||||
RestartTimer(true);
|
RestartTimer(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int _connectFailureCount;
|
|
||||||
|
|
||||||
private async void TimerCallback(object sender)
|
private async void TimerCallback(object sender)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
@ -436,7 +452,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
if (transportState.HasValue)
|
if (transportState.HasValue)
|
||||||
{
|
{
|
||||||
// If we're not playing anything no need to get additional data
|
// If we're not playing anything no need to get additional data
|
||||||
if (transportState.Value == TRANSPORTSTATE.STOPPED)
|
if (transportState.Value == TransportState.Stopped)
|
||||||
{
|
{
|
||||||
UpdateMediaInfo(null, transportState.Value);
|
UpdateMediaInfo(null, transportState.Value);
|
||||||
}
|
}
|
||||||
|
@ -465,7 +481,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
{
|
{
|
||||||
RestartTimerInactive();
|
RestartTimerInactive();
|
||||||
}
|
}
|
||||||
|
@ -527,7 +543,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
command.Name,
|
command.Name,
|
||||||
|
@ -539,7 +555,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
|
var volume = result.Document.Descendants(UPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
|
||||||
var volumeValue = volume?.Value;
|
var volumeValue = volume?.Value;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(volumeValue))
|
if (string.IsNullOrWhiteSpace(volumeValue))
|
||||||
|
@ -577,7 +593,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
command.Name,
|
command.Name,
|
||||||
|
@ -589,14 +605,14 @@ namespace Emby.Dlna.PlayTo
|
||||||
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"))
|
||||||
.FirstOrDefault(i => i != null);
|
.FirstOrDefault(i => i != null);
|
||||||
|
|
||||||
IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase);
|
IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<TRANSPORTSTATE?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
private async Task<TransportState?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
|
@ -610,7 +626,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
command.Name,
|
command.Name,
|
||||||
|
@ -623,12 +639,12 @@ 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?.Value;
|
||||||
|
|
||||||
if (transportStateValue != null
|
if (transportStateValue != null
|
||||||
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
|
&& Enum.TryParse(transportStateValue, true, out TransportState state))
|
||||||
{
|
{
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -636,7 +652,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<uBaseObject> GetMediaInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
private async Task<UBaseObject> GetMediaInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
|
@ -652,7 +668,7 @@ 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(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
command.Name,
|
command.Name,
|
||||||
|
@ -671,7 +687,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var e = track.Element(uPnpNamespaces.items) ?? track;
|
var e = track.Element(UPnpNamespaces.Items) ?? track;
|
||||||
|
|
||||||
var elementString = (string)e;
|
var elementString = (string)e;
|
||||||
|
|
||||||
|
@ -687,13 +703,13 @@ namespace Emby.Dlna.PlayTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
e = track.Element(uPnpNamespaces.items) ?? track;
|
e = track.Element(UPnpNamespaces.Items) ?? track;
|
||||||
|
|
||||||
elementString = (string)e;
|
elementString = (string)e;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(elementString))
|
if (!string.IsNullOrWhiteSpace(elementString))
|
||||||
{
|
{
|
||||||
return new uBaseObject
|
return new UBaseObject
|
||||||
{
|
{
|
||||||
Url = elementString
|
Url = elementString
|
||||||
};
|
};
|
||||||
|
@ -702,7 +718,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(bool, uBaseObject)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
private async Task<(bool, UBaseObject)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
|
@ -719,7 +735,7 @@ 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(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
command.Name,
|
command.Name,
|
||||||
|
@ -731,11 +747,11 @@ namespace Emby.Dlna.PlayTo
|
||||||
return (false, null);
|
return (false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null);
|
var trackUriElem = result.Document.Descendants(UPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null);
|
||||||
var trackUri = trackUriElem == null ? null : trackUriElem.Value;
|
var trackUri = trackUriElem?.Value;
|
||||||
|
|
||||||
var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
|
var durationElem = result.Document.Descendants(UPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
|
||||||
var duration = durationElem == null ? null : durationElem.Value;
|
var duration = durationElem?.Value;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(duration)
|
if (!string.IsNullOrWhiteSpace(duration)
|
||||||
&& !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
&& !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
||||||
|
@ -747,8 +763,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
Duration = null;
|
Duration = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
|
var positionElem = result.Document.Descendants(UPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
|
||||||
var position = positionElem == null ? null : positionElem.Value;
|
var position = positionElem?.Value;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
@ -787,7 +803,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return (true, null);
|
return (true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var e = uPnpResponse.Element(uPnpNamespaces.items);
|
var e = uPnpResponse.Element(UPnpNamespaces.Items);
|
||||||
|
|
||||||
var uTrack = CreateUBaseObject(e, trackUri);
|
var uTrack = CreateUBaseObject(e, trackUri);
|
||||||
|
|
||||||
|
@ -819,7 +835,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
// some devices send back invalid xml
|
// some devices send back invalid xml
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return XElement.Parse(xml.Replace("&", "&"));
|
return XElement.Parse(xml.Replace("&", "&", StringComparison.Ordinal));
|
||||||
}
|
}
|
||||||
catch (XmlException)
|
catch (XmlException)
|
||||||
{
|
{
|
||||||
|
@ -828,27 +844,27 @@ namespace Emby.Dlna.PlayTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
|
private static UBaseObject CreateUBaseObject(XElement container, string trackUri)
|
||||||
{
|
{
|
||||||
if (container == null)
|
if (container == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(container));
|
throw new ArgumentNullException(nameof(container));
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = container.GetValue(uPnpNamespaces.Res);
|
var url = container.GetValue(UPnpNamespaces.Res);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(url))
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
{
|
{
|
||||||
url = trackUri;
|
url = trackUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new uBaseObject
|
return new UBaseObject
|
||||||
{
|
{
|
||||||
Id = container.GetAttributeValue(uPnpNamespaces.Id),
|
Id = container.GetAttributeValue(UPnpNamespaces.Id),
|
||||||
ParentId = container.GetAttributeValue(uPnpNamespaces.ParentId),
|
ParentId = container.GetAttributeValue(UPnpNamespaces.ParentId),
|
||||||
Title = container.GetValue(uPnpNamespaces.title),
|
Title = container.GetValue(UPnpNamespaces.Title),
|
||||||
IconUrl = container.GetValue(uPnpNamespaces.Artwork),
|
IconUrl = container.GetValue(UPnpNamespaces.Artwork),
|
||||||
SecondText = "",
|
SecondText = string.Empty,
|
||||||
Url = url,
|
Url = url,
|
||||||
ProtocolInfo = GetProtocolInfo(container),
|
ProtocolInfo = GetProtocolInfo(container),
|
||||||
MetaData = container.ToString()
|
MetaData = container.ToString()
|
||||||
|
@ -862,11 +878,11 @@ namespace Emby.Dlna.PlayTo
|
||||||
throw new ArgumentNullException(nameof(container));
|
throw new ArgumentNullException(nameof(container));
|
||||||
}
|
}
|
||||||
|
|
||||||
var resElement = container.Element(uPnpNamespaces.Res);
|
var resElement = container.Element(UPnpNamespaces.Res);
|
||||||
|
|
||||||
if (resElement != null)
|
if (resElement != null)
|
||||||
{
|
{
|
||||||
var info = resElement.Attribute(uPnpNamespaces.ProtocolInfo);
|
var info = resElement.Attribute(UPnpNamespaces.ProtocolInfo);
|
||||||
|
|
||||||
if (info != null && !string.IsNullOrWhiteSpace(info.Value))
|
if (info != null && !string.IsNullOrWhiteSpace(info.Value))
|
||||||
{
|
{
|
||||||
|
@ -897,7 +913,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
||||||
|
|
||||||
var httpClient = new SsdpHttpClient(_httpClient);
|
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
||||||
|
|
||||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -925,7 +941,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
||||||
|
|
||||||
var httpClient = new SsdpHttpClient(_httpClient);
|
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
||||||
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
||||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -941,12 +957,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url.Contains("/"))
|
if (!url.Contains('/', StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
url = "/dmr/" + url;
|
url = "/dmr/" + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url.StartsWith("/"))
|
if (!url.StartsWith("/", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
url = "/" + url;
|
url = "/" + url;
|
||||||
}
|
}
|
||||||
|
@ -954,25 +970,21 @@ namespace Emby.Dlna.PlayTo
|
||||||
return baseUrl + url;
|
return baseUrl + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransportCommands AvCommands { get; set; }
|
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken)
|
||||||
|
|
||||||
private TransportCommands RendererCommands { get; set; }
|
|
||||||
|
|
||||||
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
var ssdpHttpClient = new SsdpHttpClient(httpClient);
|
var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
|
||||||
|
|
||||||
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var friendlyNames = new List<string>();
|
var friendlyNames = new List<string>();
|
||||||
|
|
||||||
var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault();
|
var name = document.Descendants(UPnpNamespaces.Ud.GetName("friendlyName")).FirstOrDefault();
|
||||||
if (name != null && !string.IsNullOrWhiteSpace(name.Value))
|
if (name != null && !string.IsNullOrWhiteSpace(name.Value))
|
||||||
{
|
{
|
||||||
friendlyNames.Add(name.Value);
|
friendlyNames.Add(name.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var room = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault();
|
var room = document.Descendants(UPnpNamespaces.Ud.GetName("roomName")).FirstOrDefault();
|
||||||
if (room != null && !string.IsNullOrWhiteSpace(room.Value))
|
if (room != null && !string.IsNullOrWhiteSpace(room.Value))
|
||||||
{
|
{
|
||||||
friendlyNames.Add(room.Value);
|
friendlyNames.Add(room.Value);
|
||||||
|
@ -981,77 +993,77 @@ namespace Emby.Dlna.PlayTo
|
||||||
var deviceProperties = new DeviceInfo()
|
var deviceProperties = new DeviceInfo()
|
||||||
{
|
{
|
||||||
Name = string.Join(" ", friendlyNames),
|
Name = string.Join(" ", friendlyNames),
|
||||||
BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port)
|
BaseUrl = string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", url.Host, url.Port)
|
||||||
};
|
};
|
||||||
|
|
||||||
var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
|
var model = document.Descendants(UPnpNamespaces.Ud.GetName("modelName")).FirstOrDefault();
|
||||||
if (model != null)
|
if (model != null)
|
||||||
{
|
{
|
||||||
deviceProperties.ModelName = model.Value;
|
deviceProperties.ModelName = model.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault();
|
var modelNumber = document.Descendants(UPnpNamespaces.Ud.GetName("modelNumber")).FirstOrDefault();
|
||||||
if (modelNumber != null)
|
if (modelNumber != null)
|
||||||
{
|
{
|
||||||
deviceProperties.ModelNumber = modelNumber.Value;
|
deviceProperties.ModelNumber = modelNumber.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault();
|
var uuid = document.Descendants(UPnpNamespaces.Ud.GetName("UDN")).FirstOrDefault();
|
||||||
if (uuid != null)
|
if (uuid != null)
|
||||||
{
|
{
|
||||||
deviceProperties.UUID = uuid.Value;
|
deviceProperties.UUID = uuid.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault();
|
var manufacturer = document.Descendants(UPnpNamespaces.Ud.GetName("manufacturer")).FirstOrDefault();
|
||||||
if (manufacturer != null)
|
if (manufacturer != null)
|
||||||
{
|
{
|
||||||
deviceProperties.Manufacturer = manufacturer.Value;
|
deviceProperties.Manufacturer = manufacturer.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault();
|
var manufacturerUrl = document.Descendants(UPnpNamespaces.Ud.GetName("manufacturerURL")).FirstOrDefault();
|
||||||
if (manufacturerUrl != null)
|
if (manufacturerUrl != null)
|
||||||
{
|
{
|
||||||
deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
|
deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault();
|
var presentationUrl = document.Descendants(UPnpNamespaces.Ud.GetName("presentationURL")).FirstOrDefault();
|
||||||
if (presentationUrl != null)
|
if (presentationUrl != null)
|
||||||
{
|
{
|
||||||
deviceProperties.PresentationUrl = presentationUrl.Value;
|
deviceProperties.PresentationUrl = presentationUrl.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelUrl = document.Descendants(uPnpNamespaces.ud.GetName("modelURL")).FirstOrDefault();
|
var modelUrl = document.Descendants(UPnpNamespaces.Ud.GetName("modelURL")).FirstOrDefault();
|
||||||
if (modelUrl != null)
|
if (modelUrl != null)
|
||||||
{
|
{
|
||||||
deviceProperties.ModelUrl = modelUrl.Value;
|
deviceProperties.ModelUrl = modelUrl.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var serialNumber = document.Descendants(uPnpNamespaces.ud.GetName("serialNumber")).FirstOrDefault();
|
var serialNumber = document.Descendants(UPnpNamespaces.Ud.GetName("serialNumber")).FirstOrDefault();
|
||||||
if (serialNumber != null)
|
if (serialNumber != null)
|
||||||
{
|
{
|
||||||
deviceProperties.SerialNumber = serialNumber.Value;
|
deviceProperties.SerialNumber = serialNumber.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelDescription = document.Descendants(uPnpNamespaces.ud.GetName("modelDescription")).FirstOrDefault();
|
var modelDescription = document.Descendants(UPnpNamespaces.Ud.GetName("modelDescription")).FirstOrDefault();
|
||||||
if (modelDescription != null)
|
if (modelDescription != null)
|
||||||
{
|
{
|
||||||
deviceProperties.ModelDescription = modelDescription.Value;
|
deviceProperties.ModelDescription = modelDescription.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault();
|
var icon = document.Descendants(UPnpNamespaces.Ud.GetName("icon")).FirstOrDefault();
|
||||||
if (icon != null)
|
if (icon != null)
|
||||||
{
|
{
|
||||||
deviceProperties.Icon = CreateIcon(icon);
|
deviceProperties.Icon = CreateIcon(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList")))
|
foreach (var services in document.Descendants(UPnpNamespaces.Ud.GetName("serviceList")))
|
||||||
{
|
{
|
||||||
if (services == null)
|
if (services == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service"));
|
var servicesList = services.Descendants(UPnpNamespaces.Ud.GetName("service"));
|
||||||
if (servicesList == null)
|
if (servicesList == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
@ -1068,10 +1080,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Device(deviceProperties, httpClient, logger, config);
|
return new Device(deviceProperties, httpClientFactory, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
|
||||||
private static DeviceIcon CreateIcon(XElement element)
|
private static DeviceIcon CreateIcon(XElement element)
|
||||||
{
|
{
|
||||||
if (element == null)
|
if (element == null)
|
||||||
|
@ -1079,11 +1090,11 @@ namespace Emby.Dlna.PlayTo
|
||||||
throw new ArgumentNullException(nameof(element));
|
throw new ArgumentNullException(nameof(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
var mimeType = element.GetDescendantValue(uPnpNamespaces.ud.GetName("mimetype"));
|
var mimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype"));
|
||||||
var width = element.GetDescendantValue(uPnpNamespaces.ud.GetName("width"));
|
var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width"));
|
||||||
var height = element.GetDescendantValue(uPnpNamespaces.ud.GetName("height"));
|
var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height"));
|
||||||
var depth = element.GetDescendantValue(uPnpNamespaces.ud.GetName("depth"));
|
var depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth"));
|
||||||
var url = element.GetDescendantValue(uPnpNamespaces.ud.GetName("url"));
|
var url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url"));
|
||||||
|
|
||||||
var widthValue = int.Parse(width, NumberStyles.Integer, UsCulture);
|
var widthValue = int.Parse(width, NumberStyles.Integer, UsCulture);
|
||||||
var heightValue = int.Parse(height, NumberStyles.Integer, UsCulture);
|
var heightValue = int.Parse(height, NumberStyles.Integer, UsCulture);
|
||||||
|
@ -1100,11 +1111,11 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
private static DeviceService Create(XElement element)
|
private static DeviceService Create(XElement element)
|
||||||
{
|
{
|
||||||
var type = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceType"));
|
var type = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType"));
|
||||||
var id = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceId"));
|
var id = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId"));
|
||||||
var scpdUrl = element.GetDescendantValue(uPnpNamespaces.ud.GetName("SCPDURL"));
|
var scpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL"));
|
||||||
var controlURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("controlURL"));
|
var controlURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL"));
|
||||||
var eventSubURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("eventSubURL"));
|
var eventSubURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL"));
|
||||||
|
|
||||||
return new DeviceService
|
return new DeviceService
|
||||||
{
|
{
|
||||||
|
@ -1116,14 +1127,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<PlaybackStartEventArgs> PlaybackStart;
|
private void UpdateMediaInfo(UBaseObject mediaInfo, TransportState state)
|
||||||
public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress;
|
|
||||||
public event EventHandler<PlaybackStoppedEventArgs> PlaybackStopped;
|
|
||||||
public event EventHandler<MediaChangedEventArgs> MediaChanged;
|
|
||||||
|
|
||||||
public uBaseObject CurrentMediaInfo { get; private set; }
|
|
||||||
|
|
||||||
private void UpdateMediaInfo(uBaseObject mediaInfo, TRANSPORTSTATE state)
|
|
||||||
{
|
{
|
||||||
TransportState = state;
|
TransportState = state;
|
||||||
|
|
||||||
|
@ -1132,7 +1136,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (previousMediaInfo == null && mediaInfo != null)
|
if (previousMediaInfo == null && mediaInfo != null)
|
||||||
{
|
{
|
||||||
if (state != TRANSPORTSTATE.STOPPED)
|
if (state != TransportState.Stopped)
|
||||||
{
|
{
|
||||||
OnPlaybackStart(mediaInfo);
|
OnPlaybackStart(mediaInfo);
|
||||||
}
|
}
|
||||||
|
@ -1151,7 +1155,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlaybackStart(uBaseObject mediaInfo)
|
private void OnPlaybackStart(UBaseObject mediaInfo)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(mediaInfo.Url))
|
if (string.IsNullOrWhiteSpace(mediaInfo.Url))
|
||||||
{
|
{
|
||||||
|
@ -1164,7 +1168,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlaybackProgress(uBaseObject mediaInfo)
|
private void OnPlaybackProgress(UBaseObject mediaInfo)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(mediaInfo.Url))
|
if (string.IsNullOrWhiteSpace(mediaInfo.Url))
|
||||||
{
|
{
|
||||||
|
@ -1177,7 +1181,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlaybackStop(uBaseObject mediaInfo)
|
private void OnPlaybackStop(UBaseObject mediaInfo)
|
||||||
{
|
{
|
||||||
PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs
|
PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs
|
||||||
{
|
{
|
||||||
|
@ -1185,7 +1189,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMediaChanged(uBaseObject old, uBaseObject newMedia)
|
private void OnMediaChanged(UBaseObject old, UBaseObject newMedia)
|
||||||
{
|
{
|
||||||
MediaChanged?.Invoke(this, new MediaChangedEventArgs
|
MediaChanged?.Invoke(this, new MediaChangedEventArgs
|
||||||
{
|
{
|
||||||
|
@ -1194,14 +1198,17 @@ namespace Emby.Dlna.PlayTo
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _disposed;
|
/// <inheritdoc />
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases unmanaged and optionally managed resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
@ -1220,9 +1227,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
|
return string.Format(CultureInfo.InvariantCulture, "{0} - {1}", Properties.Name, Properties.BaseUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class DeviceInfo
|
public class DeviceInfo
|
||||||
{
|
{
|
||||||
|
private readonly List<DeviceService> _services = new List<DeviceService>();
|
||||||
|
private string _baseUrl = string.Empty;
|
||||||
|
|
||||||
public DeviceInfo()
|
public DeviceInfo()
|
||||||
{
|
{
|
||||||
Name = "Generic Device";
|
Name = "Generic Device";
|
||||||
|
@ -33,7 +36,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
public string PresentationUrl { get; set; }
|
public string PresentationUrl { get; set; }
|
||||||
|
|
||||||
private string _baseUrl = string.Empty;
|
|
||||||
public string BaseUrl
|
public string BaseUrl
|
||||||
{
|
{
|
||||||
get => _baseUrl;
|
get => _baseUrl;
|
||||||
|
@ -42,7 +44,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
public DeviceIcon Icon { get; set; }
|
public DeviceIcon Icon { get; set; }
|
||||||
|
|
||||||
private readonly List<DeviceService> _services = new List<DeviceService>();
|
|
||||||
public List<DeviceService> Services => _services;
|
public List<DeviceService> Services => _services;
|
||||||
|
|
||||||
public DeviceIdentification ToDeviceIdentification()
|
public DeviceIdentification ToDeviceIdentification()
|
||||||
|
|
13
Emby.Dlna/PlayTo/MediaChangedEventArgs.cs
Normal file
13
Emby.Dlna/PlayTo/MediaChangedEventArgs.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Emby.Dlna.PlayTo
|
||||||
|
{
|
||||||
|
public class MediaChangedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public UBaseObject OldMediaInfo { get; set; }
|
||||||
|
|
||||||
|
public UBaseObject NewMediaInfo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Didl;
|
using Emby.Dlna.Didl;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
|
using Jellyfin.Data.Events;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
|
@ -18,7 +19,6 @@ using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Events;
|
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
|
@ -31,7 +31,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
||||||
|
|
||||||
private Device _device;
|
|
||||||
private readonly SessionInfo _session;
|
private readonly SessionInfo _session;
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
@ -50,6 +49,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
private readonly string _accessToken;
|
private readonly string _accessToken;
|
||||||
|
|
||||||
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
|
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
|
||||||
|
private Device _device;
|
||||||
private int _currentPlaylistIndex;
|
private int _currentPlaylistIndex;
|
||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
@ -372,8 +372,13 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (!command.ControllingUserId.Equals(Guid.Empty))
|
if (!command.ControllingUserId.Equals(Guid.Empty))
|
||||||
{
|
{
|
||||||
_sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId,
|
_sessionManager.LogSessionActivity(
|
||||||
_session.DeviceName, _session.RemoteEndPoint, user);
|
_session.Client,
|
||||||
|
_session.ApplicationVersion,
|
||||||
|
_session.DeviceId,
|
||||||
|
_session.DeviceName,
|
||||||
|
_session.RemoteEndPoint,
|
||||||
|
user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PlayItems(playlist, cancellationToken);
|
return PlayItems(playlist, cancellationToken);
|
||||||
|
@ -498,7 +503,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
if (streamInfo.MediaType == DlnaProfileType.Audio)
|
if (streamInfo.MediaType == DlnaProfileType.Audio)
|
||||||
{
|
{
|
||||||
return new ContentFeatureBuilder(profile)
|
return new ContentFeatureBuilder(profile)
|
||||||
.BuildAudioHeader(streamInfo.Container,
|
.BuildAudioHeader(
|
||||||
|
streamInfo.Container,
|
||||||
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
||||||
streamInfo.TargetAudioBitrate,
|
streamInfo.TargetAudioBitrate,
|
||||||
streamInfo.TargetAudioSampleRate,
|
streamInfo.TargetAudioSampleRate,
|
||||||
|
@ -512,7 +518,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
if (streamInfo.MediaType == DlnaProfileType.Video)
|
if (streamInfo.MediaType == DlnaProfileType.Video)
|
||||||
{
|
{
|
||||||
var list = new ContentFeatureBuilder(profile)
|
var list = new ContentFeatureBuilder(profile)
|
||||||
.BuildVideoHeader(streamInfo.Container,
|
.BuildVideoHeader(
|
||||||
|
streamInfo.Container,
|
||||||
streamInfo.TargetVideoCodec.FirstOrDefault(),
|
streamInfo.TargetVideoCodec.FirstOrDefault(),
|
||||||
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
streamInfo.TargetAudioCodec.FirstOrDefault(),
|
||||||
streamInfo.TargetWidth,
|
streamInfo.TargetWidth,
|
||||||
|
@ -633,6 +640,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases unmanaged and optionally managed resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
@ -673,10 +684,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
case GeneralCommandType.ToggleMute:
|
case GeneralCommandType.ToggleMute:
|
||||||
return _device.ToggleMute(cancellationToken);
|
return _device.ToggleMute(cancellationToken);
|
||||||
case GeneralCommandType.SetAudioStreamIndex:
|
case GeneralCommandType.SetAudioStreamIndex:
|
||||||
|
if (command.Arguments.TryGetValue("Index", out string index))
|
||||||
{
|
{
|
||||||
if (command.Arguments.TryGetValue("Index", out string arg))
|
if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
|
||||||
{
|
|
||||||
if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out var val))
|
|
||||||
{
|
{
|
||||||
return SetAudioStreamIndex(val);
|
return SetAudioStreamIndex(val);
|
||||||
}
|
}
|
||||||
|
@ -685,12 +695,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
|
throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
|
||||||
}
|
|
||||||
case GeneralCommandType.SetSubtitleStreamIndex:
|
case GeneralCommandType.SetSubtitleStreamIndex:
|
||||||
|
if (command.Arguments.TryGetValue("Index", out index))
|
||||||
{
|
{
|
||||||
if (command.Arguments.TryGetValue("Index", out string arg))
|
if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val))
|
||||||
{
|
|
||||||
if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out var val))
|
|
||||||
{
|
{
|
||||||
return SetSubtitleStreamIndex(val);
|
return SetSubtitleStreamIndex(val);
|
||||||
}
|
}
|
||||||
|
@ -699,12 +707,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
|
throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
|
||||||
}
|
|
||||||
case GeneralCommandType.SetVolume:
|
case GeneralCommandType.SetVolume:
|
||||||
|
if (command.Arguments.TryGetValue("Volume", out string vol))
|
||||||
{
|
{
|
||||||
if (command.Arguments.TryGetValue("Volume", out string arg))
|
if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume))
|
||||||
{
|
|
||||||
if (int.TryParse(arg, NumberStyles.Integer, _usCulture, out var volume))
|
|
||||||
{
|
{
|
||||||
return _device.SetVolume(volume, cancellationToken);
|
return _device.SetVolume(volume, cancellationToken);
|
||||||
}
|
}
|
||||||
|
@ -713,8 +719,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;
|
||||||
}
|
}
|
||||||
|
@ -778,7 +782,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
const int maxWait = 15000000;
|
const int maxWait = 15000000;
|
||||||
const int interval = 500;
|
const int interval = 500;
|
||||||
var currentWait = 0;
|
var currentWait = 0;
|
||||||
while (_device.TransportState != TRANSPORTSTATE.PLAYING && currentWait < maxWait)
|
while (_device.TransportState != TransportState.Playing && currentWait < maxWait)
|
||||||
{
|
{
|
||||||
await Task.Delay(interval).ConfigureAwait(false);
|
await Task.Delay(interval).ConfigureAwait(false);
|
||||||
currentWait += interval;
|
currentWait += interval;
|
||||||
|
@ -787,8 +791,67 @@ namespace Emby.Dlna.PlayTo
|
||||||
await _device.Seek(TimeSpan.FromTicks(positionTicks), cancellationToken).ConfigureAwait(false);
|
await _device.Seek(TimeSpan.FromTicks(positionTicks), cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int? GetIntValue(IReadOnlyDictionary<string, string> values, string name)
|
||||||
|
{
|
||||||
|
var value = values.GetValueOrDefault(name);
|
||||||
|
|
||||||
|
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long GetLongValue(IReadOnlyDictionary<string, string> values, string name)
|
||||||
|
{
|
||||||
|
var value = values.GetValueOrDefault(name);
|
||||||
|
|
||||||
|
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(GetType().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_device == null)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return SendPlayCommand(data as PlayRequest, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not supported or needed right now
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
private class StreamParams
|
private class StreamParams
|
||||||
{
|
{
|
||||||
|
private MediaSourceInfo mediaSource;
|
||||||
|
private IMediaSourceManager _mediaSourceManager;
|
||||||
|
|
||||||
public Guid ItemId { get; set; }
|
public Guid ItemId { get; set; }
|
||||||
|
|
||||||
public bool IsDirectStream { get; set; }
|
public bool IsDirectStream { get; set; }
|
||||||
|
@ -809,15 +872,11 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
public BaseItem Item { get; set; }
|
public BaseItem Item { get; set; }
|
||||||
|
|
||||||
private MediaSourceInfo MediaSource;
|
|
||||||
|
|
||||||
private IMediaSourceManager _mediaSourceManager;
|
|
||||||
|
|
||||||
public async Task<MediaSourceInfo> GetMediaSource(CancellationToken cancellationToken)
|
public async Task<MediaSourceInfo> GetMediaSource(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (MediaSource != null)
|
if (mediaSource != null)
|
||||||
{
|
{
|
||||||
return MediaSource;
|
return mediaSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasMediaSources = Item as IHasMediaSources;
|
var hasMediaSources = Item as IHasMediaSources;
|
||||||
|
@ -827,9 +886,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
|
mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return MediaSource;
|
return mediaSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Guid GetItemId(string url)
|
private static Guid GetItemId(string url)
|
||||||
|
@ -901,61 +960,5 @@ namespace Emby.Dlna.PlayTo
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int? GetIntValue(IReadOnlyDictionary<string, string> values, string name)
|
|
||||||
{
|
|
||||||
var value = values.GetValueOrDefault(name);
|
|
||||||
|
|
||||||
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long GetLongValue(IReadOnlyDictionary<string, string> values, string name)
|
|
||||||
{
|
|
||||||
var value = values.GetValueOrDefault(name);
|
|
||||||
|
|
||||||
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
throw new ObjectDisposedException(GetType().Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_device == null)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return SendPlayCommand(data as PlayRequest, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not supported or needed right now
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,10 @@ using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Events;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
|
@ -16,7 +18,6 @@ using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Events;
|
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
@ -33,7 +34,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
private readonly IDlnaManager _dlnaManager;
|
private readonly IDlnaManager _dlnaManager;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
|
@ -46,7 +47,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
||||||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
|
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
|
@ -56,7 +57,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_imageProcessor = imageProcessor;
|
_imageProcessor = imageProcessor;
|
||||||
_deviceDiscovery = deviceDiscovery;
|
_deviceDiscovery = deviceDiscovery;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
_config = config;
|
_config = config;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
|
@ -174,7 +175,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (controller == null)
|
if (controller == null)
|
||||||
{
|
{
|
||||||
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, cancellationToken).ConfigureAwait(false);
|
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
string deviceName = device.Properties.Name;
|
string deviceName = device.Properties.Name;
|
||||||
|
|
||||||
|
@ -218,7 +219,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
PlayableMediaTypes = profile.GetSupportedMediaTypes(),
|
PlayableMediaTypes = profile.GetSupportedMediaTypes(),
|
||||||
|
|
||||||
SupportedCommands = new string[]
|
SupportedCommands = new[]
|
||||||
{
|
{
|
||||||
GeneralCommandType.VolumeDown.ToString(),
|
GeneralCommandType.VolumeDown.ToString(),
|
||||||
GeneralCommandType.VolumeUp.ToString(),
|
GeneralCommandType.VolumeUp.ToString(),
|
||||||
|
@ -247,8 +248,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
_disposeCancellationTokenSource.Cancel();
|
_disposeCancellationTokenSource.Cancel();
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.LogDebug(ex, "Error while disposing PlayToManager");
|
||||||
}
|
}
|
||||||
|
|
||||||
_sessionLock.Dispose();
|
_sessionLock.Dispose();
|
||||||
|
|
|
@ -6,6 +6,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class PlaybackProgressEventArgs : EventArgs
|
public class PlaybackProgressEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public uBaseObject MediaInfo { get; set; }
|
public UBaseObject MediaInfo { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class PlaybackStartEventArgs : EventArgs
|
public class PlaybackStartEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public uBaseObject MediaInfo { get; set; }
|
public UBaseObject MediaInfo { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class PlaybackStoppedEventArgs : EventArgs
|
public class PlaybackStoppedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public uBaseObject MediaInfo { get; set; }
|
public UBaseObject MediaInfo { get; set; }
|
||||||
}
|
|
||||||
|
|
||||||
public class MediaChangedEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
public uBaseObject OldMediaInfo { get; set; }
|
|
||||||
|
|
||||||
public uBaseObject NewMediaInfo { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Mime;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -20,11 +22,11 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
public SsdpHttpClient(IHttpClient httpClient)
|
public SsdpHttpClient(IHttpClientFactory httpClientFactory)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<XDocument> SendCommandAsync(
|
public async Task<XDocument> SendCommandAsync(
|
||||||
|
@ -36,21 +38,19 @@ namespace Emby.Dlna.PlayTo
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
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,
|
||||||
cancellationToken)
|
cancellationToken)
|
||||||
.ConfigureAwait(false))
|
.ConfigureAwait(false);
|
||||||
using (var stream = response.Content)
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||||
{
|
|
||||||
return XDocument.Parse(
|
return XDocument.Parse(
|
||||||
await reader.ReadToEndAsync().ConfigureAwait(false),
|
await reader.ReadToEndAsync().ConfigureAwait(false),
|
||||||
LoadOptions.PreserveWhitespace);
|
LoadOptions.PreserveWhitespace);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
|
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
|
||||||
{
|
{
|
||||||
|
@ -76,49 +76,32 @@ namespace Emby.Dlna.PlayTo
|
||||||
int eventport,
|
int eventport,
|
||||||
int timeOut = 3600)
|
int timeOut = 3600)
|
||||||
{
|
{
|
||||||
var options = new HttpRequestOptions
|
using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url);
|
||||||
{
|
options.Headers.UserAgent.ParseAdd(USERAGENT);
|
||||||
Url = url,
|
options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(_usCulture));
|
||||||
UserAgent = USERAGENT,
|
options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(_usCulture) + ">");
|
||||||
LogErrorResponseBody = true,
|
options.Headers.TryAddWithoutValidation("NT", "upnp:event");
|
||||||
BufferContent = false,
|
options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(_usCulture));
|
||||||
};
|
|
||||||
|
|
||||||
options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture);
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport.ToString(_usCulture) + ">";
|
.SendAsync(options, HttpCompletionOption.ResponseHeadersRead)
|
||||||
options.RequestHeaders["NT"] = "upnp:event";
|
.ConfigureAwait(false);
|
||||||
options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture);
|
|
||||||
|
|
||||||
using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken)
|
public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var options = new HttpRequestOptions
|
using var options = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
{
|
options.Headers.UserAgent.ParseAdd(USERAGENT);
|
||||||
Url = url,
|
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
|
||||||
UserAgent = USERAGENT,
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
LogErrorResponseBody = true,
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
BufferContent = false,
|
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||||
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
|
|
||||||
|
|
||||||
using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
|
|
||||||
using (var stream = response.Content)
|
|
||||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
|
||||||
{
|
|
||||||
return XDocument.Parse(
|
return XDocument.Parse(
|
||||||
await reader.ReadToEndAsync().ConfigureAwait(false),
|
await reader.ReadToEndAsync().ConfigureAwait(false),
|
||||||
LoadOptions.PreserveWhitespace);
|
LoadOptions.PreserveWhitespace);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private Task<HttpResponseInfo> PostSoapDataAsync(
|
private Task<HttpResponseMessage> PostSoapDataAsync(
|
||||||
string url,
|
string url,
|
||||||
string soapAction,
|
string soapAction,
|
||||||
string postData,
|
string postData,
|
||||||
|
@ -130,29 +113,20 @@ namespace Emby.Dlna.PlayTo
|
||||||
soapAction = $"\"{soapAction}\"";
|
soapAction = $"\"{soapAction}\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = new HttpRequestOptions
|
using var options = new HttpRequestMessage(HttpMethod.Post, url);
|
||||||
{
|
options.Headers.UserAgent.ParseAdd(USERAGENT);
|
||||||
Url = url,
|
options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction);
|
||||||
UserAgent = USERAGENT,
|
options.Headers.TryAddWithoutValidation("Pragma", "no-cache");
|
||||||
LogErrorResponseBody = true,
|
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
|
||||||
BufferContent = false,
|
|
||||||
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
options.RequestHeaders["SOAPAction"] = soapAction;
|
|
||||||
options.RequestHeaders["Pragma"] = "no-cache";
|
|
||||||
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(header))
|
if (!string.IsNullOrEmpty(header))
|
||||||
{
|
{
|
||||||
options.RequestHeaders["contentFeatures.dlna.org"] = header;
|
options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
|
||||||
}
|
}
|
||||||
|
|
||||||
options.RequestContentType = "text/xml";
|
options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml);
|
||||||
options.RequestContent = postData;
|
|
||||||
|
|
||||||
return _httpClient.Post(options);
|
return _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
|
||||||
{
|
|
||||||
public enum TRANSPORTSTATE
|
|
||||||
{
|
|
||||||
STOPPED,
|
|
||||||
PLAYING,
|
|
||||||
TRANSITIONING,
|
|
||||||
PAUSED_PLAYBACK,
|
|
||||||
PAUSED
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
|
@ -11,36 +12,30 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class TransportCommands
|
public class TransportCommands
|
||||||
{
|
{
|
||||||
|
private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
|
||||||
private List<StateVariable> _stateVariables = new List<StateVariable>();
|
private List<StateVariable> _stateVariables = new List<StateVariable>();
|
||||||
public List<StateVariable> StateVariables
|
|
||||||
{
|
|
||||||
get => _stateVariables;
|
|
||||||
set => _stateVariables = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ServiceAction> _serviceActions = new List<ServiceAction>();
|
private List<ServiceAction> _serviceActions = new List<ServiceAction>();
|
||||||
public List<ServiceAction> ServiceActions
|
|
||||||
{
|
public List<StateVariable> StateVariables => _stateVariables;
|
||||||
get => _serviceActions;
|
|
||||||
set => _serviceActions = value;
|
public List<ServiceAction> ServiceActions => _serviceActions;
|
||||||
}
|
|
||||||
|
|
||||||
public static TransportCommands Create(XDocument document)
|
public static TransportCommands Create(XDocument document)
|
||||||
{
|
{
|
||||||
var command = new TransportCommands();
|
var command = new TransportCommands();
|
||||||
|
|
||||||
var actionList = document.Descendants(uPnpNamespaces.svc + "actionList");
|
var actionList = document.Descendants(UPnpNamespaces.Svc + "actionList");
|
||||||
|
|
||||||
foreach (var container in actionList.Descendants(uPnpNamespaces.svc + "action"))
|
foreach (var container in actionList.Descendants(UPnpNamespaces.Svc + "action"))
|
||||||
{
|
{
|
||||||
command.ServiceActions.Add(ServiceActionFromXml(container));
|
command.ServiceActions.Add(ServiceActionFromXml(container));
|
||||||
}
|
}
|
||||||
|
|
||||||
var stateValues = document.Descendants(uPnpNamespaces.ServiceStateTable).FirstOrDefault();
|
var stateValues = document.Descendants(UPnpNamespaces.ServiceStateTable).FirstOrDefault();
|
||||||
|
|
||||||
if (stateValues != null)
|
if (stateValues != null)
|
||||||
{
|
{
|
||||||
foreach (var container in stateValues.Elements(uPnpNamespaces.svc + "stateVariable"))
|
foreach (var container in stateValues.Elements(UPnpNamespaces.Svc + "stateVariable"))
|
||||||
{
|
{
|
||||||
command.StateVariables.Add(FromXml(container));
|
command.StateVariables.Add(FromXml(container));
|
||||||
}
|
}
|
||||||
|
@ -51,19 +46,19 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
private static ServiceAction ServiceActionFromXml(XElement container)
|
private static ServiceAction ServiceActionFromXml(XElement container)
|
||||||
{
|
{
|
||||||
var argumentList = new List<Argument>();
|
var serviceAction = new ServiceAction
|
||||||
|
{
|
||||||
|
Name = container.GetValue(UPnpNamespaces.Svc + "name"),
|
||||||
|
};
|
||||||
|
|
||||||
foreach (var arg in container.Descendants(uPnpNamespaces.svc + "argument"))
|
var argumentList = serviceAction.ArgumentList;
|
||||||
|
|
||||||
|
foreach (var arg in container.Descendants(UPnpNamespaces.Svc + "argument"))
|
||||||
{
|
{
|
||||||
argumentList.Add(ArgumentFromXml(arg));
|
argumentList.Add(ArgumentFromXml(arg));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ServiceAction
|
return serviceAction;
|
||||||
{
|
|
||||||
Name = container.GetValue(uPnpNamespaces.svc + "name"),
|
|
||||||
|
|
||||||
ArgumentList = argumentList
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Argument ArgumentFromXml(XElement container)
|
private static Argument ArgumentFromXml(XElement container)
|
||||||
|
@ -75,29 +70,29 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
return new Argument
|
return new Argument
|
||||||
{
|
{
|
||||||
Name = container.GetValue(uPnpNamespaces.svc + "name"),
|
Name = container.GetValue(UPnpNamespaces.Svc + "name"),
|
||||||
Direction = container.GetValue(uPnpNamespaces.svc + "direction"),
|
Direction = container.GetValue(UPnpNamespaces.Svc + "direction"),
|
||||||
RelatedStateVariable = container.GetValue(uPnpNamespaces.svc + "relatedStateVariable")
|
RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static StateVariable FromXml(XElement container)
|
private static StateVariable FromXml(XElement container)
|
||||||
{
|
{
|
||||||
var allowedValues = new List<string>();
|
var allowedValues = new List<string>();
|
||||||
var element = container.Descendants(uPnpNamespaces.svc + "allowedValueList")
|
var element = container.Descendants(UPnpNamespaces.Svc + "allowedValueList")
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
if (element != null)
|
if (element != null)
|
||||||
{
|
{
|
||||||
var values = element.Descendants(uPnpNamespaces.svc + "allowedValue");
|
var values = element.Descendants(UPnpNamespaces.Svc + "allowedValue");
|
||||||
|
|
||||||
allowedValues.AddRange(values.Select(child => child.Value));
|
allowedValues.AddRange(values.Select(child => child.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new StateVariable
|
return new StateVariable
|
||||||
{
|
{
|
||||||
Name = container.GetValue(uPnpNamespaces.svc + "name"),
|
Name = container.GetValue(UPnpNamespaces.Svc + "name"),
|
||||||
DataType = container.GetValue(uPnpNamespaces.svc + "dataType"),
|
DataType = container.GetValue(UPnpNamespaces.Svc + "dataType"),
|
||||||
AllowedValues = allowedValues.ToArray()
|
AllowedValues = allowedValues.ToArray()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -123,7 +118,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Format(CommandBase, action.Name, xmlNamespace, stateString);
|
return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string BuildPost(ServiceAction action, string xmlNamesapce, object value, string commandParameter = "")
|
public string BuildPost(ServiceAction action, string xmlNamesapce, object value, string commandParameter = "")
|
||||||
|
@ -147,7 +142,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
|
return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string BuildPost(ServiceAction action, string xmlNamesapce, object value, Dictionary<string, string> dictionary)
|
public string BuildPost(ServiceAction action, string xmlNamesapce, object value, Dictionary<string, string> dictionary)
|
||||||
|
@ -170,7 +165,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
|
return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildArgumentXml(Argument argument, string value, string commandParameter = "")
|
private string BuildArgumentXml(Argument argument, string value, string commandParameter = "")
|
||||||
|
@ -180,15 +175,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
if (state != null)
|
if (state != null)
|
||||||
{
|
{
|
||||||
var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ??
|
var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ??
|
||||||
state.AllowedValues.FirstOrDefault() ??
|
(state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value);
|
||||||
value;
|
|
||||||
|
|
||||||
return string.Format("<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType ?? "string", sendValue);
|
return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType ?? "string", sendValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Format("<{0}>{1}</{0}>", argument.Name, value);
|
return string.Format(CultureInfo.InvariantCulture, "<{0}>{1}</{0}>", argument.Name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
Emby.Dlna/PlayTo/TransportState.cs
Normal file
14
Emby.Dlna/PlayTo/TransportState.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
#pragma warning disable SA1602
|
||||||
|
|
||||||
|
namespace Emby.Dlna.PlayTo
|
||||||
|
{
|
||||||
|
public enum TransportState
|
||||||
|
{
|
||||||
|
Stopped,
|
||||||
|
Playing,
|
||||||
|
Transitioning,
|
||||||
|
PausedPlayback,
|
||||||
|
Paused
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,22 +6,22 @@ using Emby.Dlna.Ssdp;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class UpnpContainer : uBaseObject
|
public class UpnpContainer : UBaseObject
|
||||||
{
|
{
|
||||||
public static uBaseObject Create(XElement container)
|
public static UBaseObject Create(XElement container)
|
||||||
{
|
{
|
||||||
if (container == null)
|
if (container == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(container));
|
throw new ArgumentNullException(nameof(container));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new uBaseObject
|
return new UBaseObject
|
||||||
{
|
{
|
||||||
Id = container.GetAttributeValue(uPnpNamespaces.Id),
|
Id = container.GetAttributeValue(UPnpNamespaces.Id),
|
||||||
ParentId = container.GetAttributeValue(uPnpNamespaces.ParentId),
|
ParentId = container.GetAttributeValue(UPnpNamespaces.ParentId),
|
||||||
Title = container.GetValue(uPnpNamespaces.title),
|
Title = container.GetValue(UPnpNamespaces.Title),
|
||||||
IconUrl = container.GetValue(uPnpNamespaces.Artwork),
|
IconUrl = container.GetValue(UPnpNamespaces.Artwork),
|
||||||
UpnpClass = container.GetValue(uPnpNamespaces.uClass)
|
UpnpClass = container.GetValue(UPnpNamespaces.Class)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class uBaseObject
|
public class UBaseObject
|
||||||
{
|
{
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
@ -20,20 +21,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
|
|
||||||
public string[] ProtocolInfo { get; set; }
|
public IReadOnlyList<string> ProtocolInfo { get; set; }
|
||||||
|
|
||||||
public string UpnpClass { get; set; }
|
public string UpnpClass { get; set; }
|
||||||
|
|
||||||
public bool Equals(uBaseObject obj)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(obj));
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Equals(Id, obj.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string MediaType
|
public string MediaType
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -58,5 +49,15 @@ namespace Emby.Dlna.PlayTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Equals(UBaseObject obj)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Equals(Id, obj.Id, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,38 +4,64 @@ using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class uPnpNamespaces
|
public static class UPnpNamespaces
|
||||||
{
|
{
|
||||||
public static XNamespace dc = "http://purl.org/dc/elements/1.1/";
|
public static XNamespace Dc { get; } = "http://purl.org/dc/elements/1.1/";
|
||||||
public static XNamespace ns = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
|
|
||||||
public static XNamespace svc = "urn:schemas-upnp-org:service-1-0";
|
|
||||||
public static XNamespace ud = "urn:schemas-upnp-org:device-1-0";
|
|
||||||
public static XNamespace upnp = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
|
||||||
public static XNamespace RenderingControl = "urn:schemas-upnp-org:service:RenderingControl:1";
|
|
||||||
public static XNamespace AvTransport = "urn:schemas-upnp-org:service:AVTransport:1";
|
|
||||||
public static XNamespace ContentDirectory = "urn:schemas-upnp-org:service:ContentDirectory:1";
|
|
||||||
|
|
||||||
public static XName containers = ns + "container";
|
public static XNamespace Ns { get; } = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
|
||||||
public static XName items = ns + "item";
|
|
||||||
public static XName title = dc + "title";
|
|
||||||
public static XName creator = dc + "creator";
|
|
||||||
public static XName artist = upnp + "artist";
|
|
||||||
public static XName Id = "id";
|
|
||||||
public static XName ParentId = "parentID";
|
|
||||||
public static XName uClass = upnp + "class";
|
|
||||||
public static XName Artwork = upnp + "albumArtURI";
|
|
||||||
public static XName Description = dc + "description";
|
|
||||||
public static XName LongDescription = upnp + "longDescription";
|
|
||||||
public static XName Album = upnp + "album";
|
|
||||||
public static XName Author = upnp + "author";
|
|
||||||
public static XName Director = upnp + "director";
|
|
||||||
public static XName PlayCount = upnp + "playbackCount";
|
|
||||||
public static XName Tracknumber = upnp + "originalTrackNumber";
|
|
||||||
public static XName Res = ns + "res";
|
|
||||||
public static XName Duration = "duration";
|
|
||||||
public static XName ProtocolInfo = "protocolInfo";
|
|
||||||
|
|
||||||
public static XName ServiceStateTable = svc + "serviceStateTable";
|
public static XNamespace Svc { get; } = "urn:schemas-upnp-org:service-1-0";
|
||||||
public static XName StateVariable = svc + "stateVariable";
|
|
||||||
|
public static XNamespace Ud { get; } = "urn:schemas-upnp-org:device-1-0";
|
||||||
|
|
||||||
|
public static XNamespace UPnp { get; } = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
||||||
|
|
||||||
|
public static XNamespace RenderingControl { get; } = "urn:schemas-upnp-org:service:RenderingControl:1";
|
||||||
|
|
||||||
|
public static XNamespace AvTransport { get; } = "urn:schemas-upnp-org:service:AVTransport:1";
|
||||||
|
|
||||||
|
public static XNamespace ContentDirectory { get; } = "urn:schemas-upnp-org:service:ContentDirectory:1";
|
||||||
|
|
||||||
|
public static XName Containers { get; } = Ns + "container";
|
||||||
|
|
||||||
|
public static XName Items { get; } = Ns + "item";
|
||||||
|
|
||||||
|
public static XName Title { get; } = Dc + "title";
|
||||||
|
|
||||||
|
public static XName Creator { get; } = Dc + "creator";
|
||||||
|
|
||||||
|
public static XName Artist { get; } = UPnp + "artist";
|
||||||
|
|
||||||
|
public static XName Id { get; } = "id";
|
||||||
|
|
||||||
|
public static XName ParentId { get; } = "parentID";
|
||||||
|
|
||||||
|
public static XName Class { get; } = UPnp + "class";
|
||||||
|
|
||||||
|
public static XName Artwork { get; } = UPnp + "albumArtURI";
|
||||||
|
|
||||||
|
public static XName Description { get; } = Dc + "description";
|
||||||
|
|
||||||
|
public static XName LongDescription { get; } = UPnp + "longDescription";
|
||||||
|
|
||||||
|
public static XName Album { get; } = UPnp + "album";
|
||||||
|
|
||||||
|
public static XName Author { get; } = UPnp + "author";
|
||||||
|
|
||||||
|
public static XName Director { get; } = UPnp + "director";
|
||||||
|
|
||||||
|
public static XName PlayCount { get; } = UPnp + "playbackCount";
|
||||||
|
|
||||||
|
public static XName Tracknumber { get; } = UPnp + "originalTrackNumber";
|
||||||
|
|
||||||
|
public static XName Res { get; } = Ns + "res";
|
||||||
|
|
||||||
|
public static XName Duration { get; } = "duration";
|
||||||
|
|
||||||
|
public static XName ProtocolInfo { get; } = "protocolInfo";
|
||||||
|
|
||||||
|
public static XName ServiceStateTable { get; } = Svc + "serviceStateTable";
|
||||||
|
|
||||||
|
public static XName StateVariable { get; } = Svc + "stateVariable";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,14 +64,14 @@ namespace Emby.Dlna.Profiles
|
||||||
new DirectPlayProfile
|
new DirectPlayProfile
|
||||||
{
|
{
|
||||||
// play all
|
// play all
|
||||||
Container = "",
|
Container = string.Empty,
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new DirectPlayProfile
|
new DirectPlayProfile
|
||||||
{
|
{
|
||||||
// play all
|
// play all
|
||||||
Container = "",
|
Container = string.Empty,
|
||||||
Type = DlnaProfileType.Audio
|
Type = DlnaProfileType.Audio
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles
|
||||||
|
|
||||||
public void AddXmlRootAttribute(string name, string value)
|
public void AddXmlRootAttribute(string name, string value)
|
||||||
{
|
{
|
||||||
var atts = XmlRootAttributes ?? new XmlAttribute[] { };
|
var atts = XmlRootAttributes ?? System.Array.Empty<XmlAttribute>();
|
||||||
var list = atts.ToList();
|
var list = atts.ToList();
|
||||||
|
|
||||||
list.Add(new XmlAttribute
|
list.Add(new XmlAttribute
|
||||||
|
|
|
@ -28,7 +28,7 @@ namespace Emby.Dlna.Profiles
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = System.Array.Empty<ResponseProfile>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = System.Array.Empty<ResponseProfile>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Match = HeaderMatchType.Substring,
|
Match = HeaderMatchType.Substring,
|
||||||
Name = "User-Agent",
|
Name = "User-Agent",
|
||||||
Value ="Zip_"
|
Value = "Zip_"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -81,7 +81,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "h264",
|
Codec = "h264",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -124,7 +124,7 @@ namespace Emby.Dlna.Profiles
|
||||||
new CodecProfile
|
new CodecProfile
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -161,7 +161,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "ac3,he-aac",
|
Codec = "ac3,he-aac",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -177,7 +177,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "aac",
|
Codec = "aac",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -192,7 +192,7 @@ namespace Emby.Dlna.Profiles
|
||||||
new CodecProfile
|
new CodecProfile
|
||||||
{
|
{
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
// The device does not have any audio switching capabilities
|
// The device does not have any audio switching capabilities
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
|
|
|
@ -72,7 +72,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = System.Array.Empty<ResponseProfile>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -191,7 +191,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[]
|
ResponseProfiles = new[]
|
||||||
{
|
{
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
|
|
|
@ -32,7 +32,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[]
|
ResponseProfiles = new[]
|
||||||
{
|
{
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace Emby.Dlna.Profiles
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = System.Array.Empty<ResponseProfile>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
|
||||||
namespace Emby.Dlna.Profiles
|
namespace Emby.Dlna.Profiles
|
||||||
|
@ -37,7 +38,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = Array.Empty<ResponseProfile>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,7 +138,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
|
|
@ -93,8 +93,8 @@ namespace Emby.Dlna.Profiles
|
||||||
new CodecProfile
|
new CodecProfile
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec="h264",
|
Codec = "h264",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, "baseline|constrained baseline"),
|
new ProfileCondition(ProfileConditionType.EqualsAny, ProfileConditionValue.VideoProfile, "baseline|constrained baseline"),
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
|
@ -122,7 +122,7 @@ namespace Emby.Dlna.Profiles
|
||||||
new CodecProfile
|
new CodecProfile
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -150,7 +150,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "aac",
|
Codec = "aac",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -166,7 +166,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Audio,
|
Type = CodecType.Audio,
|
||||||
Codec = "aac",
|
Codec = "aac",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -182,7 +182,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Audio,
|
Type = CodecType.Audio,
|
||||||
Codec = "mp3",
|
Codec = "mp3",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -202,7 +202,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[]
|
ResponseProfiles = new[]
|
||||||
{
|
{
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
|
|
|
@ -139,7 +139,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
|
||||||
namespace Emby.Dlna.Profiles
|
namespace Emby.Dlna.Profiles
|
||||||
|
@ -149,7 +150,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "h264",
|
Codec = "h264",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -177,7 +178,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "ac3",
|
Codec = "ac3",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -196,7 +197,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -223,7 +224,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = Array.Empty<ResponseProfile>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
|
||||||
namespace Emby.Dlna.Profiles
|
namespace Emby.Dlna.Profiles
|
||||||
|
@ -149,7 +150,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "h264",
|
Codec = "h264",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -177,7 +178,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "ac3",
|
Codec = "ac3",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -196,7 +197,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -223,7 +224,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = Array.Empty<ResponseProfile>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
|
||||||
namespace Emby.Dlna.Profiles
|
namespace Emby.Dlna.Profiles
|
||||||
|
@ -137,7 +138,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "h264",
|
Codec = "h264",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -165,7 +166,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "ac3",
|
Codec = "ac3",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -184,7 +185,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -211,7 +212,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = Array.Empty<ResponseProfile>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
|
||||||
namespace Emby.Dlna.Profiles
|
namespace Emby.Dlna.Profiles
|
||||||
|
@ -137,7 +138,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "h264",
|
Codec = "h264",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -165,7 +166,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "ac3",
|
Codec = "ac3",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -184,7 +185,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -211,7 +212,7 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = Array.Empty<ResponseProfile>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "h264",
|
Codec = "h264",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -156,7 +156,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "ac3",
|
Codec = "ac3",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -172,7 +172,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "aac",
|
Codec = "aac",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -191,7 +191,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -217,7 +217,7 @@ namespace Emby.Dlna.Profiles
|
||||||
VideoCodec = "h264,mpeg4,vc1",
|
VideoCodec = "h264,mpeg4,vc1",
|
||||||
AudioCodec = "ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
|
OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -102,13 +102,13 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
|
OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -128,13 +128,13 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/mpeg",
|
MimeType = "video/mpeg",
|
||||||
OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
|
OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -148,28 +148,28 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
|
OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="mpeg2video",
|
VideoCodec = "mpeg2video",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
|
OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "mpeg",
|
Container = "mpeg",
|
||||||
VideoCodec="mpeg1video,mpeg2video",
|
VideoCodec = "mpeg1video,mpeg2video",
|
||||||
MimeType = "video/mpeg",
|
MimeType = "video/mpeg",
|
||||||
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL",
|
OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -180,7 +180,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -204,7 +204,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "h264",
|
Codec = "h264",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -243,7 +243,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "mpeg2video",
|
Codec = "mpeg2video",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -275,7 +275,7 @@ namespace Emby.Dlna.Profiles
|
||||||
new CodecProfile
|
new CodecProfile
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -303,7 +303,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "ac3",
|
Codec = "ac3",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -319,7 +319,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "aac",
|
Codec = "aac",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -341,7 +341,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "mp3,mp2",
|
Codec = "mp3,mp2",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
|
|
@ -120,7 +120,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -143,13 +143,13 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
|
OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -169,13 +169,13 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/mpeg",
|
MimeType = "video/mpeg",
|
||||||
OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
|
OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -189,28 +189,28 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
|
OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="mpeg2video",
|
VideoCodec = "mpeg2video",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
|
OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "mpeg",
|
Container = "mpeg",
|
||||||
VideoCodec="mpeg1video,mpeg2video",
|
VideoCodec = "mpeg1video,mpeg2video",
|
||||||
MimeType = "video/mpeg",
|
MimeType = "video/mpeg",
|
||||||
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL",
|
OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
|
@ -227,7 +227,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "h264",
|
Codec = "h264",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -266,7 +266,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "mpeg2video",
|
Codec = "mpeg2video",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -298,7 +298,7 @@ namespace Emby.Dlna.Profiles
|
||||||
new CodecProfile
|
new CodecProfile
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -326,7 +326,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "ac3",
|
Codec = "ac3",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -364,7 +364,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "mp3,mp2",
|
Codec = "mp3,mp2",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
|
|
@ -131,13 +131,13 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
|
OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -157,13 +157,13 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/mpeg",
|
MimeType = "video/mpeg",
|
||||||
OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
|
OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -177,28 +177,28 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
|
OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="mpeg2video",
|
VideoCodec = "mpeg2video",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
|
OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "mpeg",
|
Container = "mpeg",
|
||||||
VideoCodec="mpeg1video,mpeg2video",
|
VideoCodec = "mpeg1video,mpeg2video",
|
||||||
MimeType = "video/mpeg",
|
MimeType = "video/mpeg",
|
||||||
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL",
|
OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
|
@ -215,7 +215,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -282,7 +282,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "mp3,mp2",
|
Codec = "mp3,mp2",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
|
|
@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -187,13 +187,13 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
|
OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -213,13 +213,13 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/mpeg",
|
MimeType = "video/mpeg",
|
||||||
OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
|
OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -233,28 +233,28 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
|
OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="mpeg2video",
|
VideoCodec = "mpeg2video",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
|
OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "mpeg",
|
Container = "mpeg",
|
||||||
VideoCodec="mpeg1video,mpeg2video",
|
VideoCodec = "mpeg1video,mpeg2video",
|
||||||
MimeType = "video/mpeg",
|
MimeType = "video/mpeg",
|
||||||
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL",
|
OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
|
@ -265,14 +265,13 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
CodecProfiles = new[]
|
CodecProfiles = new[]
|
||||||
{
|
{
|
||||||
new CodecProfile
|
new CodecProfile
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -300,7 +299,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "mp3,mp2",
|
Codec = "mp3,mp2",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
|
|
@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -187,13 +187,13 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
|
OrgPn = "AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -213,13 +213,13 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/mpeg",
|
MimeType = "video/mpeg",
|
||||||
OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
|
OrgPn = "AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -233,28 +233,28 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="h264",
|
VideoCodec = "h264",
|
||||||
AudioCodec="ac3,aac,mp3",
|
AudioCodec = "ac3,aac,mp3",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
|
OrgPn = "AVC_TS_HD_24_AC3,AVC_TS_HD_50_AC3,AVC_TS_HD_60_AC3,AVC_TS_HD_EU",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "ts,mpegts",
|
Container = "ts,mpegts",
|
||||||
VideoCodec="mpeg2video",
|
VideoCodec = "mpeg2video",
|
||||||
MimeType = "video/vnd.dlna.mpeg-tts",
|
MimeType = "video/vnd.dlna.mpeg-tts",
|
||||||
OrgPn="MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
|
OrgPn = "MPEG_TS_SD_EU,MPEG_TS_SD_NA,MPEG_TS_SD_KO",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "mpeg",
|
Container = "mpeg",
|
||||||
VideoCodec="mpeg1video,mpeg2video",
|
VideoCodec = "mpeg1video,mpeg2video",
|
||||||
MimeType = "video/mpeg",
|
MimeType = "video/mpeg",
|
||||||
OrgPn="MPEG_PS_NTSC,MPEG_PS_PAL",
|
OrgPn = "MPEG_PS_NTSC,MPEG_PS_PAL",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
|
@ -265,14 +265,13 @@ namespace Emby.Dlna.Profiles
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
CodecProfiles = new[]
|
CodecProfiles = new[]
|
||||||
{
|
{
|
||||||
new CodecProfile
|
new CodecProfile
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -300,7 +299,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "mp3,mp2",
|
Codec = "mp3,mp2",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
|
|
@ -108,7 +108,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -133,7 +133,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "h264",
|
Codec = "h264",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -176,7 +176,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "ac3",
|
Codec = "ac3",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -201,7 +201,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "wmapro",
|
Codec = "wmapro",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -217,7 +217,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "aac",
|
Codec = "aac",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -235,7 +235,7 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "mp4,mov",
|
Container = "mp4,mov",
|
||||||
AudioCodec="aac",
|
AudioCodec = "aac",
|
||||||
MimeType = "video/mp4",
|
MimeType = "video/mp4",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
@ -244,7 +244,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Container = "avi",
|
Container = "avi",
|
||||||
MimeType = "video/divx",
|
MimeType = "video/divx",
|
||||||
OrgPn="AVI",
|
OrgPn = "AVI",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -135,7 +135,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "h264",
|
Codec = "h264",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -178,7 +178,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "ac3",
|
Codec = "ac3",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -203,7 +203,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "wmapro",
|
Codec = "wmapro",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -219,7 +219,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "aac",
|
Codec = "aac",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -237,7 +237,7 @@ namespace Emby.Dlna.Profiles
|
||||||
new ResponseProfile
|
new ResponseProfile
|
||||||
{
|
{
|
||||||
Container = "mp4,mov",
|
Container = "mp4,mov",
|
||||||
AudioCodec="aac",
|
AudioCodec = "aac",
|
||||||
MimeType = "video/mp4",
|
MimeType = "video/mp4",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
@ -246,7 +246,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Container = "avi",
|
Container = "avi",
|
||||||
MimeType = "video/divx",
|
MimeType = "video/divx",
|
||||||
OrgPn="AVI",
|
OrgPn = "AVI",
|
||||||
Type = DlnaProfileType.Video
|
Type = DlnaProfileType.Video
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace Emby.Dlna.Profiles
|
||||||
|
|
||||||
Headers = new[]
|
Headers = new[]
|
||||||
{
|
{
|
||||||
new HttpHeaderInfo {Name = "User-Agent", Value = "alphanetworks", Match = HeaderMatchType.Substring},
|
new HttpHeaderInfo { Name = "User-Agent", Value = "alphanetworks", Match = HeaderMatchType.Substring },
|
||||||
new HttpHeaderInfo
|
new HttpHeaderInfo
|
||||||
{
|
{
|
||||||
Name = "User-Agent",
|
Name = "User-Agent",
|
||||||
|
@ -168,7 +168,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = DlnaProfileType.Photo,
|
Type = DlnaProfileType.Photo,
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -193,7 +193,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "h264",
|
Codec = "h264",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -221,7 +221,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "aac",
|
Codec = "aac",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
|
|
@ -119,7 +119,7 @@ namespace Emby.Dlna.Profiles
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
Container = "mp4,mov",
|
Container = "mp4,mov",
|
||||||
|
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -138,7 +138,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "mpeg4",
|
Codec = "mpeg4",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -187,7 +187,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "h264",
|
Codec = "h264",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -236,7 +236,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Codec = "wmv2,wmv3,vc1",
|
Codec = "wmv2,wmv3,vc1",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -284,7 +284,7 @@ namespace Emby.Dlna.Profiles
|
||||||
new CodecProfile
|
new CodecProfile
|
||||||
{
|
{
|
||||||
Type = CodecType.Video,
|
Type = CodecType.Video,
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -307,7 +307,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "ac3,wmav2,wmapro",
|
Codec = "ac3,wmav2,wmapro",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
@ -323,7 +323,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Type = CodecType.VideoAudio,
|
Type = CodecType.VideoAudio,
|
||||||
Codec = "aac",
|
Codec = "aac",
|
||||||
Conditions = new []
|
Conditions = new[]
|
||||||
{
|
{
|
||||||
new ProfileCondition
|
new ProfileCondition
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
@ -64,10 +65,10 @@ namespace Emby.Dlna.Server
|
||||||
|
|
||||||
foreach (var att in attributes)
|
foreach (var att in attributes)
|
||||||
{
|
{
|
||||||
builder.AppendFormat(" {0}=\"{1}\"", att.Name, att.Value);
|
builder.AppendFormat(CultureInfo.InvariantCulture, " {0}=\"{1}\"", att.Name, att.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append(">");
|
builder.Append('>');
|
||||||
|
|
||||||
builder.Append("<specVersion>");
|
builder.Append("<specVersion>");
|
||||||
builder.Append("<major>1</major>");
|
builder.Append("<major>1</major>");
|
||||||
|
@ -76,7 +77,9 @@ namespace Emby.Dlna.Server
|
||||||
|
|
||||||
if (!EnableAbsoluteUrls)
|
if (!EnableAbsoluteUrls)
|
||||||
{
|
{
|
||||||
builder.Append("<URLBase>" + Escape(_serverAddress) + "</URLBase>");
|
builder.Append("<URLBase>")
|
||||||
|
.Append(SecurityElement.Escape(_serverAddress))
|
||||||
|
.Append("</URLBase>");
|
||||||
}
|
}
|
||||||
|
|
||||||
AppendDeviceInfo(builder);
|
AppendDeviceInfo(builder);
|
||||||
|
@ -93,91 +96,14 @@ namespace Emby.Dlna.Server
|
||||||
|
|
||||||
AppendIconList(builder);
|
AppendIconList(builder);
|
||||||
|
|
||||||
builder.Append("<presentationURL>" + Escape(_serverAddress) + "/web/index.html</presentationURL>");
|
builder.Append("<presentationURL>")
|
||||||
|
.Append(SecurityElement.Escape(_serverAddress))
|
||||||
|
.Append("/web/index.html</presentationURL>");
|
||||||
|
|
||||||
AppendServiceList(builder);
|
AppendServiceList(builder);
|
||||||
builder.Append("</device>");
|
builder.Append("</device>");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly char[] s_escapeChars = new char[]
|
|
||||||
{
|
|
||||||
'<',
|
|
||||||
'>',
|
|
||||||
'"',
|
|
||||||
'\'',
|
|
||||||
'&'
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly string[] s_escapeStringPairs = new[]
|
|
||||||
{
|
|
||||||
"<",
|
|
||||||
"<",
|
|
||||||
">",
|
|
||||||
">",
|
|
||||||
"\"",
|
|
||||||
""",
|
|
||||||
"'",
|
|
||||||
"'",
|
|
||||||
"&",
|
|
||||||
"&"
|
|
||||||
};
|
|
||||||
|
|
||||||
private static string GetEscapeSequence(char c)
|
|
||||||
{
|
|
||||||
int num = s_escapeStringPairs.Length;
|
|
||||||
for (int i = 0; i < num; i += 2)
|
|
||||||
{
|
|
||||||
string text = s_escapeStringPairs[i];
|
|
||||||
string result = s_escapeStringPairs[i + 1];
|
|
||||||
if (text[0] == c)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.ToString(CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Replaces invalid XML characters in a string with their valid XML equivalent.</summary>
|
|
||||||
/// <returns>The input string with invalid characters replaced.</returns>
|
|
||||||
/// <param name="str">The string within which to escape invalid characters. </param>
|
|
||||||
public static string Escape(string str)
|
|
||||||
{
|
|
||||||
if (str == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder stringBuilder = null;
|
|
||||||
int length = str.Length;
|
|
||||||
int num = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
int num2 = str.IndexOfAny(s_escapeChars, num);
|
|
||||||
if (num2 == -1)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stringBuilder == null)
|
|
||||||
{
|
|
||||||
stringBuilder = new StringBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
stringBuilder.Append(str, num, num2 - num);
|
|
||||||
stringBuilder.Append(GetEscapeSequence(str[num2]));
|
|
||||||
num = num2 + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stringBuilder == null)
|
|
||||||
{
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
stringBuilder.Append(str, num, length - num);
|
|
||||||
return stringBuilder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AppendDeviceProperties(StringBuilder builder)
|
private void AppendDeviceProperties(StringBuilder builder)
|
||||||
{
|
{
|
||||||
builder.Append("<dlna:X_DLNACAP/>");
|
builder.Append("<dlna:X_DLNACAP/>");
|
||||||
|
@ -187,32 +113,54 @@ namespace Emby.Dlna.Server
|
||||||
|
|
||||||
builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>");
|
builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>");
|
||||||
|
|
||||||
builder.Append("<friendlyName>" + Escape(GetFriendlyName()) + "</friendlyName>");
|
builder.Append("<friendlyName>")
|
||||||
builder.Append("<manufacturer>" + Escape(_profile.Manufacturer ?? string.Empty) + "</manufacturer>");
|
.Append(SecurityElement.Escape(GetFriendlyName()))
|
||||||
builder.Append("<manufacturerURL>" + Escape(_profile.ManufacturerUrl ?? string.Empty) + "</manufacturerURL>");
|
.Append("</friendlyName>");
|
||||||
|
builder.Append("<manufacturer>")
|
||||||
|
.Append(SecurityElement.Escape(_profile.Manufacturer ?? string.Empty))
|
||||||
|
.Append("</manufacturer>");
|
||||||
|
builder.Append("<manufacturerURL>")
|
||||||
|
.Append(SecurityElement.Escape(_profile.ManufacturerUrl ?? string.Empty))
|
||||||
|
.Append("</manufacturerURL>");
|
||||||
|
|
||||||
builder.Append("<modelDescription>" + Escape(_profile.ModelDescription ?? string.Empty) + "</modelDescription>");
|
builder.Append("<modelDescription>")
|
||||||
builder.Append("<modelName>" + Escape(_profile.ModelName ?? string.Empty) + "</modelName>");
|
.Append(SecurityElement.Escape(_profile.ModelDescription ?? string.Empty))
|
||||||
|
.Append("</modelDescription>");
|
||||||
|
builder.Append("<modelName>")
|
||||||
|
.Append(SecurityElement.Escape(_profile.ModelName ?? string.Empty))
|
||||||
|
.Append("</modelName>");
|
||||||
|
|
||||||
builder.Append("<modelNumber>" + Escape(_profile.ModelNumber ?? string.Empty) + "</modelNumber>");
|
builder.Append("<modelNumber>")
|
||||||
builder.Append("<modelURL>" + Escape(_profile.ModelUrl ?? string.Empty) + "</modelURL>");
|
.Append(SecurityElement.Escape(_profile.ModelNumber ?? string.Empty))
|
||||||
|
.Append("</modelNumber>");
|
||||||
|
builder.Append("<modelURL>")
|
||||||
|
.Append(SecurityElement.Escape(_profile.ModelUrl ?? string.Empty))
|
||||||
|
.Append("</modelURL>");
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_profile.SerialNumber))
|
if (string.IsNullOrEmpty(_profile.SerialNumber))
|
||||||
{
|
{
|
||||||
builder.Append("<serialNumber>" + Escape(_serverId) + "</serialNumber>");
|
builder.Append("<serialNumber>")
|
||||||
|
.Append(SecurityElement.Escape(_serverId))
|
||||||
|
.Append("</serialNumber>");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.Append("<serialNumber>" + Escape(_profile.SerialNumber) + "</serialNumber>");
|
builder.Append("<serialNumber>")
|
||||||
|
.Append(SecurityElement.Escape(_profile.SerialNumber))
|
||||||
|
.Append("</serialNumber>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append("<UPC/>");
|
builder.Append("<UPC/>");
|
||||||
|
|
||||||
builder.Append("<UDN>uuid:" + Escape(_serverUdn) + "</UDN>");
|
builder.Append("<UDN>uuid:")
|
||||||
|
.Append(SecurityElement.Escape(_serverUdn))
|
||||||
|
.Append("</UDN>");
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_profile.SonyAggregationFlags))
|
if (!string.IsNullOrEmpty(_profile.SonyAggregationFlags))
|
||||||
{
|
{
|
||||||
builder.Append("<av:aggregationFlags xmlns:av=\"urn:schemas-sony-com:av\">" + Escape(_profile.SonyAggregationFlags) + "</av:aggregationFlags>");
|
builder.Append("<av:aggregationFlags xmlns:av=\"urn:schemas-sony-com:av\">")
|
||||||
|
.Append(SecurityElement.Escape(_profile.SonyAggregationFlags))
|
||||||
|
.Append("</av:aggregationFlags>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,11 +198,21 @@ namespace Emby.Dlna.Server
|
||||||
{
|
{
|
||||||
builder.Append("<icon>");
|
builder.Append("<icon>");
|
||||||
|
|
||||||
builder.Append("<mimetype>" + Escape(icon.MimeType ?? string.Empty) + "</mimetype>");
|
builder.Append("<mimetype>")
|
||||||
builder.Append("<width>" + Escape(icon.Width.ToString(_usCulture)) + "</width>");
|
.Append(SecurityElement.Escape(icon.MimeType ?? string.Empty))
|
||||||
builder.Append("<height>" + Escape(icon.Height.ToString(_usCulture)) + "</height>");
|
.Append("</mimetype>");
|
||||||
builder.Append("<depth>" + Escape(icon.Depth ?? string.Empty) + "</depth>");
|
builder.Append("<width>")
|
||||||
builder.Append("<url>" + BuildUrl(icon.Url) + "</url>");
|
.Append(SecurityElement.Escape(icon.Width.ToString(_usCulture)))
|
||||||
|
.Append("</width>");
|
||||||
|
builder.Append("<height>")
|
||||||
|
.Append(SecurityElement.Escape(icon.Height.ToString(_usCulture)))
|
||||||
|
.Append("</height>");
|
||||||
|
builder.Append("<depth>")
|
||||||
|
.Append(SecurityElement.Escape(icon.Depth ?? string.Empty))
|
||||||
|
.Append("</depth>");
|
||||||
|
builder.Append("<url>")
|
||||||
|
.Append(BuildUrl(icon.Url))
|
||||||
|
.Append("</url>");
|
||||||
|
|
||||||
builder.Append("</icon>");
|
builder.Append("</icon>");
|
||||||
}
|
}
|
||||||
|
@ -270,11 +228,21 @@ namespace Emby.Dlna.Server
|
||||||
{
|
{
|
||||||
builder.Append("<service>");
|
builder.Append("<service>");
|
||||||
|
|
||||||
builder.Append("<serviceType>" + Escape(service.ServiceType ?? string.Empty) + "</serviceType>");
|
builder.Append("<serviceType>")
|
||||||
builder.Append("<serviceId>" + Escape(service.ServiceId ?? string.Empty) + "</serviceId>");
|
.Append(SecurityElement.Escape(service.ServiceType ?? string.Empty))
|
||||||
builder.Append("<SCPDURL>" + BuildUrl(service.ScpdUrl) + "</SCPDURL>");
|
.Append("</serviceType>");
|
||||||
builder.Append("<controlURL>" + BuildUrl(service.ControlUrl) + "</controlURL>");
|
builder.Append("<serviceId>")
|
||||||
builder.Append("<eventSubURL>" + BuildUrl(service.EventSubUrl) + "</eventSubURL>");
|
.Append(SecurityElement.Escape(service.ServiceId ?? string.Empty))
|
||||||
|
.Append("</serviceId>");
|
||||||
|
builder.Append("<SCPDURL>")
|
||||||
|
.Append(BuildUrl(service.ScpdUrl))
|
||||||
|
.Append("</SCPDURL>");
|
||||||
|
builder.Append("<controlURL>")
|
||||||
|
.Append(BuildUrl(service.ControlUrl))
|
||||||
|
.Append("</controlURL>");
|
||||||
|
builder.Append("<eventSubURL>")
|
||||||
|
.Append(BuildUrl(service.EventSubUrl))
|
||||||
|
.Append("</eventSubURL>");
|
||||||
|
|
||||||
builder.Append("</service>");
|
builder.Append("</service>");
|
||||||
}
|
}
|
||||||
|
@ -298,7 +266,7 @@ namespace Emby.Dlna.Server
|
||||||
url = _serverAddress.TrimEnd('/') + url;
|
url = _serverAddress.TrimEnd('/') + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Escape(url);
|
return SecurityElement.Escape(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<DeviceIcon> GetIcons()
|
private IEnumerable<DeviceIcon> GetIcons()
|
||||||
|
|
|
@ -15,11 +15,7 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
public abstract class BaseControlHandler
|
public abstract class BaseControlHandler
|
||||||
{
|
{
|
||||||
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/";
|
||||||
|
|
||||||
protected IServerConfigurationManager Config { get; }
|
|
||||||
|
|
||||||
protected ILogger Logger { get; }
|
|
||||||
|
|
||||||
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
|
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
|
||||||
{
|
{
|
||||||
|
@ -27,6 +23,10 @@ namespace Emby.Dlna.Service
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected IServerConfigurationManager Config { get; }
|
||||||
|
|
||||||
|
protected ILogger Logger { get; }
|
||||||
|
|
||||||
public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -80,10 +80,10 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
writer.WriteStartDocument(true);
|
writer.WriteStartDocument(true);
|
||||||
|
|
||||||
writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV);
|
writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv);
|
||||||
writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/");
|
writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/");
|
||||||
|
|
||||||
writer.WriteStartElement("SOAP-ENV", "Body", NS_SOAPENV);
|
writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv);
|
||||||
writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
|
writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
|
||||||
|
|
||||||
WriteResult(requestInfo.LocalName, requestInfo.Headers, writer);
|
WriteResult(requestInfo.LocalName, requestInfo.Headers, writer);
|
||||||
|
@ -210,15 +210,6 @@ namespace Emby.Dlna.Service
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ControlRequestInfo
|
|
||||||
{
|
|
||||||
public string LocalName { get; set; }
|
|
||||||
|
|
||||||
public string NamespaceURI { get; set; }
|
|
||||||
|
|
||||||
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter);
|
protected abstract void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter);
|
||||||
|
|
||||||
private void LogRequest(ControlRequest request)
|
private void LogRequest(ControlRequest request)
|
||||||
|
@ -240,5 +231,14 @@ namespace Emby.Dlna.Service
|
||||||
|
|
||||||
Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
|
Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ControlRequestInfo
|
||||||
|
{
|
||||||
|
public string LocalName { get; set; }
|
||||||
|
|
||||||
|
public string NamespaceURI { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,23 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.Net.Http;
|
||||||
using Emby.Dlna.Eventing;
|
using Emby.Dlna.Eventing;
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.Service
|
namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
public class BaseService : IEventManager
|
public class BaseService : IDlnaEventManager
|
||||||
{
|
{
|
||||||
protected IEventManager EventManager;
|
protected BaseService(ILogger<BaseService> logger, IHttpClientFactory httpClientFactory)
|
||||||
protected IHttpClient HttpClient;
|
|
||||||
protected ILogger Logger;
|
|
||||||
|
|
||||||
protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient)
|
|
||||||
{
|
{
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
HttpClient = httpClient;
|
EventManager = new DlnaEventManager(logger, httpClientFactory);
|
||||||
|
|
||||||
EventManager = new EventManager(logger, HttpClient);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected IDlnaEventManager EventManager { get; }
|
||||||
|
|
||||||
|
protected ILogger Logger { get; }
|
||||||
|
|
||||||
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
|
public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
|
||||||
{
|
{
|
||||||
return EventManager.CancelEventSubscription(subscriptionId);
|
return EventManager.CancelEventSubscription(subscriptionId);
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
public static class ControlErrorHandler
|
public static class ControlErrorHandler
|
||||||
{
|
{
|
||||||
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
private const string NsSoapEnv = "http://schemas.xmlsoap.org/soap/envelope/";
|
||||||
|
|
||||||
public static ControlResponse GetResponse(Exception ex)
|
public static ControlResponse GetResponse(Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -26,11 +26,11 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
writer.WriteStartDocument(true);
|
writer.WriteStartDocument(true);
|
||||||
|
|
||||||
writer.WriteStartElement("SOAP-ENV", "Envelope", NS_SOAPENV);
|
writer.WriteStartElement("SOAP-ENV", "Envelope", NsSoapEnv);
|
||||||
writer.WriteAttributeString(string.Empty, "encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/");
|
writer.WriteAttributeString(string.Empty, "encodingStyle", NsSoapEnv, "http://schemas.xmlsoap.org/soap/encoding/");
|
||||||
|
|
||||||
writer.WriteStartElement("SOAP-ENV", "Body", NS_SOAPENV);
|
writer.WriteStartElement("SOAP-ENV", "Body", NsSoapEnv);
|
||||||
writer.WriteStartElement("SOAP-ENV", "Fault", NS_SOAPENV);
|
writer.WriteStartElement("SOAP-ENV", "Fault", NsSoapEnv);
|
||||||
|
|
||||||
writer.WriteElementString("faultcode", "500");
|
writer.WriteElementString("faultcode", "500");
|
||||||
writer.WriteElementString("faultstring", ex.Message);
|
writer.WriteElementString("faultstring", ex.Message);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using Emby.Dlna.Server;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.Service
|
namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,9 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
builder.Append("<action>");
|
builder.Append("<action>");
|
||||||
|
|
||||||
builder.Append("<name>" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "</name>");
|
builder.Append("<name>")
|
||||||
|
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
|
||||||
|
.Append("</name>");
|
||||||
|
|
||||||
builder.Append("<argumentList>");
|
builder.Append("<argumentList>");
|
||||||
|
|
||||||
|
@ -45,9 +47,15 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
builder.Append("<argument>");
|
builder.Append("<argument>");
|
||||||
|
|
||||||
builder.Append("<name>" + DescriptionXmlBuilder.Escape(argument.Name ?? string.Empty) + "</name>");
|
builder.Append("<name>")
|
||||||
builder.Append("<direction>" + DescriptionXmlBuilder.Escape(argument.Direction ?? string.Empty) + "</direction>");
|
.Append(SecurityElement.Escape(argument.Name ?? string.Empty))
|
||||||
builder.Append("<relatedStateVariable>" + DescriptionXmlBuilder.Escape(argument.RelatedStateVariable ?? string.Empty) + "</relatedStateVariable>");
|
.Append("</name>");
|
||||||
|
builder.Append("<direction>")
|
||||||
|
.Append(SecurityElement.Escape(argument.Direction ?? string.Empty))
|
||||||
|
.Append("</direction>");
|
||||||
|
builder.Append("<relatedStateVariable>")
|
||||||
|
.Append(SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty))
|
||||||
|
.Append("</relatedStateVariable>");
|
||||||
|
|
||||||
builder.Append("</argument>");
|
builder.Append("</argument>");
|
||||||
}
|
}
|
||||||
|
@ -68,17 +76,25 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
var sendEvents = item.SendsEvents ? "yes" : "no";
|
var sendEvents = item.SendsEvents ? "yes" : "no";
|
||||||
|
|
||||||
builder.Append("<stateVariable sendEvents=\"" + sendEvents + "\">");
|
builder.Append("<stateVariable sendEvents=\"")
|
||||||
|
.Append(sendEvents)
|
||||||
|
.Append("\">");
|
||||||
|
|
||||||
builder.Append("<name>" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "</name>");
|
builder.Append("<name>")
|
||||||
builder.Append("<dataType>" + DescriptionXmlBuilder.Escape(item.DataType ?? string.Empty) + "</dataType>");
|
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
|
||||||
|
.Append("</name>");
|
||||||
|
builder.Append("<dataType>")
|
||||||
|
.Append(SecurityElement.Escape(item.DataType ?? string.Empty))
|
||||||
|
.Append("</dataType>");
|
||||||
|
|
||||||
if (item.AllowedValues.Length > 0)
|
if (item.AllowedValues.Count > 0)
|
||||||
{
|
{
|
||||||
builder.Append("<allowedValueList>");
|
builder.Append("<allowedValueList>");
|
||||||
foreach (var allowedValue in item.AllowedValues)
|
foreach (var allowedValue in item.AllowedValues)
|
||||||
{
|
{
|
||||||
builder.Append("<allowedValue>" + DescriptionXmlBuilder.Escape(allowedValue) + "</allowedValue>");
|
builder.Append("<allowedValue>")
|
||||||
|
.Append(SecurityElement.Escape(allowedValue))
|
||||||
|
.Append("</allowedValue>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append("</allowedValueList>");
|
builder.Append("</allowedValueList>");
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Jellyfin.Data.Events;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Events;
|
|
||||||
using Rssdp;
|
using Rssdp;
|
||||||
using Rssdp.Infrastructure;
|
using Rssdp.Infrastructure;
|
||||||
|
|
||||||
|
@ -17,9 +17,17 @@ namespace Emby.Dlna.Ssdp
|
||||||
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
|
private SsdpDeviceLocator _deviceLocator;
|
||||||
|
private ISsdpCommunicationsServer _commsServer;
|
||||||
|
|
||||||
private int _listenerCount;
|
private int _listenerCount;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
|
public DeviceDiscovery(IServerConfigurationManager config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
|
private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -49,15 +57,6 @@ namespace Emby.Dlna.Ssdp
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
|
||||||
|
|
||||||
private SsdpDeviceLocator _deviceLocator;
|
|
||||||
|
|
||||||
private ISsdpCommunicationsServer _commsServer;
|
|
||||||
|
|
||||||
public DeviceDiscovery(IServerConfigurationManager config)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this method from somewhere in your code to start the search.
|
// Call this method from somewhere in your code to start the search.
|
||||||
public void Start(ISsdpCommunicationsServer communicationsServer)
|
public void Start(ISsdpCommunicationsServer communicationsServer)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@ using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Emby.Dlna.Ssdp
|
namespace Emby.Dlna.Ssdp
|
||||||
{
|
{
|
||||||
public static class Extensions
|
public static class SsdpExtensions
|
||||||
{
|
{
|
||||||
public static string GetValue(this XElement container, XName name)
|
public static string GetValue(this XElement container, XName name)
|
||||||
{
|
{
|
|
@ -448,21 +448,21 @@ namespace Emby.Drawing
|
||||||
/// or
|
/// or
|
||||||
/// filename.
|
/// filename.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
public string GetCachePath(string path, string filename)
|
public string GetCachePath(ReadOnlySpan<char> path, ReadOnlySpan<char> filename)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (path.IsEmpty)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentException("Path can't be empty.", nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(filename))
|
if (path.IsEmpty)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(filename));
|
throw new ArgumentException("Filename can't be empty.", nameof(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefix = filename.Substring(0, 1);
|
var prefix = filename.Slice(0, 1);
|
||||||
|
|
||||||
return Path.Combine(path, prefix, filename);
|
return Path.Join(path, prefix, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
|
@ -136,8 +136,8 @@ namespace Emby.Naming.Common
|
||||||
|
|
||||||
CleanDateTimes = new[]
|
CleanDateTimes = new[]
|
||||||
{
|
{
|
||||||
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*",
|
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*",
|
||||||
@"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
|
@"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
|
||||||
};
|
};
|
||||||
|
|
||||||
CleanStrings = new[]
|
CleanStrings = new[]
|
||||||
|
@ -277,7 +277,7 @@ namespace Emby.Naming.Common
|
||||||
// This isn't a Kodi naming rule, but the expression below causes false positives,
|
// This isn't a Kodi naming rule, but the expression below causes false positives,
|
||||||
// so we make sure this one gets tested first.
|
// so we make sure this one gets tested first.
|
||||||
// "Foo Bar 889"
|
// "Foo Bar 889"
|
||||||
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/x]*$")
|
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/x]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
@ -300,32 +300,32 @@ namespace Emby.Naming.Common
|
||||||
// *** End Kodi Standard Naming
|
// *** End Kodi Standard Naming
|
||||||
|
|
||||||
// [bar] Foo - 1 [baz]
|
// [bar] Foo - 1 [baz]
|
||||||
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>\d+).*$")
|
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>[0-9]+).*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d+)[xX](?<epnumber>\d+)[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]+)[xX](?<epnumber>[0-9]+)[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d+)[x,X]?[eE](?<epnumber>\d+)[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>[0-9]+)[x,X]?[eE](?<epnumber>[0-9]+)[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d+))[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]+))[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d+)[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]+)[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// "01.avi"
|
// "01.avi"
|
||||||
new EpisodeExpression(@".*[\\\/](?<epnumber>\d+)(-(?<endingepnumber>\d+))*\.\w+$")
|
new EpisodeExpression(@".*[\\\/](?<epnumber>[0-9]+)(-(?<endingepnumber>[0-9]+))*\.\w+$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
|
@ -335,34 +335,34 @@ namespace Emby.Naming.Common
|
||||||
new EpisodeExpression(@"([0-9]+)-([0-9]+)"),
|
new EpisodeExpression(@"([0-9]+)-([0-9]+)"),
|
||||||
|
|
||||||
// "01 - blah.avi", "01-blah.avi"
|
// "01 - blah.avi", "01-blah.avi"
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\s?-\s?[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\s?-\s?[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// "01.blah.avi"
|
// "01.blah.avi"
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.[^\\\/]+$")
|
new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\.[^\\\/]+$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah"
|
// "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah"
|
||||||
new EpisodeExpression(@".*[\\\/][^\\\/]* - (?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$")
|
new EpisodeExpression(@".*[\\\/][^\\\/]* - (?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// "01 episode title.avi"
|
// "01 episode title.avi"
|
||||||
new EpisodeExpression(@"[Ss]eason[\._ ](?<seasonnumber>[0-9]+)[\\\/](?<epnumber>\d{1,3})([^\\\/]*)$")
|
new EpisodeExpression(@"[Ss]eason[\._ ](?<seasonnumber>[0-9]+)[\\\/](?<epnumber>[0-9]{1,3})([^\\\/]*)$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
// "Episode 16", "Episode 16 - Title"
|
// "Episode 16", "Episode 16 - Title"
|
||||||
new EpisodeExpression(@".*[\\\/][^\\\/]* (?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$")
|
new EpisodeExpression(@".*[\\\/][^\\\/]* (?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
|
@ -625,17 +625,17 @@ namespace Emby.Naming.Common
|
||||||
AudioBookPartsExpressions = new[]
|
AudioBookPartsExpressions = new[]
|
||||||
{
|
{
|
||||||
// Detect specified chapters, like CH 01
|
// Detect specified chapters, like CH 01
|
||||||
@"ch(?:apter)?[\s_-]?(?<chapter>\d+)",
|
@"ch(?:apter)?[\s_-]?(?<chapter>[0-9]+)",
|
||||||
// Detect specified parts, like Part 02
|
// Detect specified parts, like Part 02
|
||||||
@"p(?:ar)?t[\s_-]?(?<part>\d+)",
|
@"p(?:ar)?t[\s_-]?(?<part>[0-9]+)",
|
||||||
// Chapter is often beginning of filename
|
// Chapter is often beginning of filename
|
||||||
@"^(?<chapter>\d+)",
|
"^(?<chapter>[0-9]+)",
|
||||||
// Part if often ending of filename
|
// Part if often ending of filename
|
||||||
@"(?<part>\d+)$",
|
"(?<part>[0-9]+)$",
|
||||||
// Sometimes named as 0001_005 (chapter_part)
|
// Sometimes named as 0001_005 (chapter_part)
|
||||||
@"(?<chapter>\d+)_(?<part>\d+)",
|
"(?<chapter>[0-9]+)_(?<part>[0-9]+)",
|
||||||
// Some audiobooks are ripped from cd's, and will be named by disk number.
|
// Some audiobooks are ripped from cd's, and will be named by disk number.
|
||||||
@"dis(?:c|k)[\s_-]?(?<chapter>\d+)"
|
@"dis(?:c|k)[\s_-]?(?<chapter>[0-9]+)"
|
||||||
};
|
};
|
||||||
|
|
||||||
var extensions = VideoFileExtensions.ToList();
|
var extensions = VideoFileExtensions.ToList();
|
||||||
|
@ -675,16 +675,16 @@ namespace Emby.Naming.Common
|
||||||
|
|
||||||
MultipleEpisodeExpressions = new string[]
|
MultipleEpisodeExpressions = new string[]
|
||||||
{
|
{
|
||||||
@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[eExX](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[xX][eE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})(-[xE]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})(-[xE]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )[0-9]{1,4}[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )[0-9]{1,4}[xX][eE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))(-[xX]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]{1,3})((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$"
|
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]{1,3})(-[xX]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$"
|
||||||
}.Select(i => new EpisodeExpression(i)
|
}.Select(i => new EpisodeExpression(i)
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
|
|
|
@ -10,6 +10,15 @@
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
|
||||||
|
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
|
||||||
|
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -23,10 +32,15 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Naming</PackageId>
|
<PackageId>Jellyfin.Naming</PackageId>
|
||||||
<PackageLicenseUrl>https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt</PackageLicenseUrl>
|
<VersionPrefix>10.7.0</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<!-- TODO: <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> -->
|
<!-- TODO: <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> -->
|
||||||
|
|
|
@ -77,7 +77,7 @@ namespace Emby.Naming.TV
|
||||||
|
|
||||||
if (filename.StartsWith("s", StringComparison.OrdinalIgnoreCase))
|
if (filename.StartsWith("s", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var testFilename = filename.Substring(1);
|
var testFilename = filename.AsSpan().Slice(1);
|
||||||
|
|
||||||
if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,191 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1402
|
|
||||||
#pragma warning disable SA1649
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Jellyfin.Data.Enums;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.Net;
|
|
||||||
using MediaBrowser.Controller.Notifications;
|
|
||||||
using MediaBrowser.Model.Dto;
|
|
||||||
using MediaBrowser.Model.Notifications;
|
|
||||||
using MediaBrowser.Model.Services;
|
|
||||||
|
|
||||||
namespace Emby.Notifications.Api
|
|
||||||
{
|
|
||||||
[Route("/Notifications/{UserId}", "GET", Summary = "Gets notifications")]
|
|
||||||
public class GetNotifications : IReturn<NotificationResult>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string UserId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ApiMember(Name = "IsRead", Description = "An optional filter by IsRead", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
|
||||||
public bool? IsRead { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
|
||||||
public int? StartIndex { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
|
||||||
public int? Limit { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Notification
|
|
||||||
{
|
|
||||||
public string Id { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string UserId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public DateTime Date { get; set; }
|
|
||||||
|
|
||||||
public bool IsRead { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string Description { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string Url { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public NotificationLevel Level { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NotificationResult
|
|
||||||
{
|
|
||||||
public IReadOnlyList<Notification> Notifications { get; set; } = Array.Empty<Notification>();
|
|
||||||
|
|
||||||
public int TotalRecordCount { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class NotificationsSummary
|
|
||||||
{
|
|
||||||
public int UnreadCount { get; set; }
|
|
||||||
|
|
||||||
public NotificationLevel MaxUnreadNotificationLevel { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Notifications/{UserId}/Summary", "GET", Summary = "Gets a notification summary for a user")]
|
|
||||||
public class GetNotificationsSummary : IReturn<NotificationsSummary>
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string UserId { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Notifications/Types", "GET", Summary = "Gets notification types")]
|
|
||||||
public class GetNotificationTypes : IReturn<List<NotificationTypeInfo>>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Notifications/Services", "GET", Summary = "Gets notification types")]
|
|
||||||
public class GetNotificationServices : IReturn<List<NameIdPair>>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Notifications/Admin", "POST", Summary = "Sends a notification to all admin users")]
|
|
||||||
public class AddAdminNotification : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "Name", Description = "The notification's name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ApiMember(Name = "Description", Description = "The notification's description", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string Description { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ApiMember(Name = "ImageUrl", Description = "The notification's image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string? ImageUrl { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Url", Description = "The notification's info url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public string? Url { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Level", Description = "The notification level", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
|
||||||
public NotificationLevel Level { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Notifications/{UserId}/Read", "POST", Summary = "Marks notifications as read")]
|
|
||||||
public class MarkRead : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
|
||||||
public string UserId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
|
||||||
public string Ids { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Route("/Notifications/{UserId}/Unread", "POST", Summary = "Marks notifications as unread")]
|
|
||||||
public class MarkUnread : IReturnVoid
|
|
||||||
{
|
|
||||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
|
||||||
public string UserId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
|
||||||
public string Ids { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Authenticated]
|
|
||||||
public class NotificationsService : IService
|
|
||||||
{
|
|
||||||
private readonly INotificationManager _notificationManager;
|
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
|
|
||||||
public NotificationsService(INotificationManager notificationManager, IUserManager userManager)
|
|
||||||
{
|
|
||||||
_notificationManager = notificationManager;
|
|
||||||
_userManager = userManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Get(GetNotificationTypes request)
|
|
||||||
{
|
|
||||||
return _notificationManager.GetNotificationTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Get(GetNotificationServices request)
|
|
||||||
{
|
|
||||||
return _notificationManager.GetNotificationServices().ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Get(GetNotificationsSummary request)
|
|
||||||
{
|
|
||||||
return new NotificationsSummary();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Post(AddAdminNotification request)
|
|
||||||
{
|
|
||||||
// This endpoint really just exists as post of a real with sickbeard
|
|
||||||
var notification = new NotificationRequest
|
|
||||||
{
|
|
||||||
Date = DateTime.UtcNow,
|
|
||||||
Description = request.Description,
|
|
||||||
Level = request.Level,
|
|
||||||
Name = request.Name,
|
|
||||||
Url = request.Url,
|
|
||||||
UserIds = _userManager.Users
|
|
||||||
.Where(user => user.HasPermission(PermissionKind.IsAdministrator))
|
|
||||||
.Select(user => user.Id)
|
|
||||||
.ToArray()
|
|
||||||
};
|
|
||||||
|
|
||||||
return _notificationManager.SendNotification(notification, CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public void Post(MarkRead request)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public void Post(MarkUnread request)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
|
||||||
public object Get(GetNotifications request)
|
|
||||||
{
|
|
||||||
return new NotificationResult();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,6 +4,7 @@ using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Events;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
@ -13,7 +14,6 @@ using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Notifications;
|
using MediaBrowser.Controller.Notifications;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
using MediaBrowser.Model.Events;
|
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Notifications;
|
using MediaBrowser.Model.Notifications;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
|
@ -1,590 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Jellyfin.Data.Entities;
|
|
||||||
using MediaBrowser.Common.Plugins;
|
|
||||||
using MediaBrowser.Common.Updates;
|
|
||||||
using MediaBrowser.Controller.Authentication;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.Plugins;
|
|
||||||
using MediaBrowser.Controller.Session;
|
|
||||||
using MediaBrowser.Controller.Subtitles;
|
|
||||||
using MediaBrowser.Model.Activity;
|
|
||||||
using MediaBrowser.Model.Dto;
|
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using MediaBrowser.Model.Events;
|
|
||||||
using MediaBrowser.Model.Globalization;
|
|
||||||
using MediaBrowser.Model.Notifications;
|
|
||||||
using MediaBrowser.Model.Tasks;
|
|
||||||
using MediaBrowser.Model.Updates;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Activity
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Entry point for the activity logger.
|
|
||||||
/// </summary>
|
|
||||||
public sealed class ActivityLogEntryPoint : IServerEntryPoint
|
|
||||||
{
|
|
||||||
private readonly ILogger<ActivityLogEntryPoint> _logger;
|
|
||||||
private readonly IInstallationManager _installationManager;
|
|
||||||
private readonly ISessionManager _sessionManager;
|
|
||||||
private readonly ITaskManager _taskManager;
|
|
||||||
private readonly IActivityManager _activityManager;
|
|
||||||
private readonly ILocalizationManager _localization;
|
|
||||||
private readonly ISubtitleManager _subManager;
|
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="logger">The logger.</param>
|
|
||||||
/// <param name="sessionManager">The session manager.</param>
|
|
||||||
/// <param name="taskManager">The task manager.</param>
|
|
||||||
/// <param name="activityManager">The activity manager.</param>
|
|
||||||
/// <param name="localization">The localization manager.</param>
|
|
||||||
/// <param name="installationManager">The installation manager.</param>
|
|
||||||
/// <param name="subManager">The subtitle manager.</param>
|
|
||||||
/// <param name="userManager">The user manager.</param>
|
|
||||||
public ActivityLogEntryPoint(
|
|
||||||
ILogger<ActivityLogEntryPoint> logger,
|
|
||||||
ISessionManager sessionManager,
|
|
||||||
ITaskManager taskManager,
|
|
||||||
IActivityManager activityManager,
|
|
||||||
ILocalizationManager localization,
|
|
||||||
IInstallationManager installationManager,
|
|
||||||
ISubtitleManager subManager,
|
|
||||||
IUserManager userManager)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_sessionManager = sessionManager;
|
|
||||||
_taskManager = taskManager;
|
|
||||||
_activityManager = activityManager;
|
|
||||||
_localization = localization;
|
|
||||||
_installationManager = installationManager;
|
|
||||||
_subManager = subManager;
|
|
||||||
_userManager = userManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task RunAsync()
|
|
||||||
{
|
|
||||||
_taskManager.TaskCompleted += OnTaskCompleted;
|
|
||||||
|
|
||||||
_installationManager.PluginInstalled += OnPluginInstalled;
|
|
||||||
_installationManager.PluginUninstalled += OnPluginUninstalled;
|
|
||||||
_installationManager.PluginUpdated += OnPluginUpdated;
|
|
||||||
_installationManager.PackageInstallationFailed += OnPackageInstallationFailed;
|
|
||||||
|
|
||||||
_sessionManager.SessionStarted += OnSessionStarted;
|
|
||||||
_sessionManager.AuthenticationFailed += OnAuthenticationFailed;
|
|
||||||
_sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded;
|
|
||||||
_sessionManager.SessionEnded += OnSessionEnded;
|
|
||||||
_sessionManager.PlaybackStart += OnPlaybackStart;
|
|
||||||
_sessionManager.PlaybackStopped += OnPlaybackStopped;
|
|
||||||
|
|
||||||
_subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
|
|
||||||
|
|
||||||
_userManager.OnUserCreated += OnUserCreated;
|
|
||||||
_userManager.OnUserPasswordChanged += OnUserPasswordChanged;
|
|
||||||
_userManager.OnUserDeleted += OnUserDeleted;
|
|
||||||
_userManager.OnUserLockedOut += OnUserLockedOut;
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnUserLockedOut(object sender, GenericEventArgs<User> e)
|
|
||||||
{
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserLockedOutWithName"),
|
|
||||||
e.Argument.Username),
|
|
||||||
NotificationType.UserLockedOut.ToString(),
|
|
||||||
e.Argument.Id)
|
|
||||||
{
|
|
||||||
LogSeverity = LogLevel.Error
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
|
||||||
{
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
|
|
||||||
e.Provider,
|
|
||||||
Notifications.NotificationEntryPoint.GetItemName(e.Item)),
|
|
||||||
"SubtitleDownloadFailure",
|
|
||||||
Guid.Empty)
|
|
||||||
{
|
|
||||||
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
|
|
||||||
ShortOverview = e.Exception.Message
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
|
|
||||||
{
|
|
||||||
var item = e.MediaInfo;
|
|
||||||
|
|
||||||
if (item == null)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("PlaybackStopped reported with null media info.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.Item != null && e.Item.IsThemeMedia)
|
|
||||||
{
|
|
||||||
// Don't report theme song or local trailer playback
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.Users.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = e.Users[0];
|
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
|
|
||||||
user.Username,
|
|
||||||
GetItemName(item),
|
|
||||||
e.DeviceName),
|
|
||||||
GetPlaybackStoppedNotificationType(item.MediaType),
|
|
||||||
user.Id))
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
|
|
||||||
{
|
|
||||||
var item = e.MediaInfo;
|
|
||||||
|
|
||||||
if (item == null)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("PlaybackStart reported with null media info.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.Item != null && e.Item.IsThemeMedia)
|
|
||||||
{
|
|
||||||
// Don't report theme song or local trailer playback
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.Users.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = e.Users.First();
|
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
|
|
||||||
user.Username,
|
|
||||||
GetItemName(item),
|
|
||||||
e.DeviceName),
|
|
||||||
GetPlaybackNotificationType(item.MediaType),
|
|
||||||
user.Id))
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetItemName(BaseItemDto item)
|
|
||||||
{
|
|
||||||
var name = item.Name;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(item.SeriesName))
|
|
||||||
{
|
|
||||||
name = item.SeriesName + " - " + name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.Artists != null && item.Artists.Count > 0)
|
|
||||||
{
|
|
||||||
name = item.Artists[0] + " - " + name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetPlaybackNotificationType(string mediaType)
|
|
||||||
{
|
|
||||||
if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return NotificationType.AudioPlayback.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return NotificationType.VideoPlayback.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetPlaybackStoppedNotificationType(string mediaType)
|
|
||||||
{
|
|
||||||
if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return NotificationType.AudioPlaybackStopped.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return NotificationType.VideoPlaybackStopped.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnSessionEnded(object sender, SessionEventArgs e)
|
|
||||||
{
|
|
||||||
var session = e.SessionInfo;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(session.UserName))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserOfflineFromDevice"),
|
|
||||||
session.UserName,
|
|
||||||
session.DeviceName),
|
|
||||||
"SessionEnded",
|
|
||||||
session.UserId)
|
|
||||||
{
|
|
||||||
ShortOverview = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("LabelIpAddressValue"),
|
|
||||||
session.RemoteEndPoint),
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
|
||||||
{
|
|
||||||
var user = e.Argument.User;
|
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
|
|
||||||
user.Name),
|
|
||||||
"AuthenticationSucceeded",
|
|
||||||
user.Id)
|
|
||||||
{
|
|
||||||
ShortOverview = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("LabelIpAddressValue"),
|
|
||||||
e.Argument.SessionInfo.RemoteEndPoint),
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
|
||||||
{
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
|
|
||||||
e.Argument.Username),
|
|
||||||
"AuthenticationFailed",
|
|
||||||
Guid.Empty)
|
|
||||||
{
|
|
||||||
LogSeverity = LogLevel.Error,
|
|
||||||
ShortOverview = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("LabelIpAddressValue"),
|
|
||||||
e.Argument.RemoteEndPoint),
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
|
||||||
{
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserDeletedWithName"),
|
|
||||||
e.Argument.Username),
|
|
||||||
"UserDeleted",
|
|
||||||
Guid.Empty))
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
|
|
||||||
{
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserPasswordChangedWithName"),
|
|
||||||
e.Argument.Username),
|
|
||||||
"UserPasswordChanged",
|
|
||||||
e.Argument.Id))
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnUserCreated(object sender, GenericEventArgs<User> e)
|
|
||||||
{
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserCreatedWithName"),
|
|
||||||
e.Argument.Username),
|
|
||||||
"UserCreated",
|
|
||||||
e.Argument.Id))
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnSessionStarted(object sender, SessionEventArgs e)
|
|
||||||
{
|
|
||||||
var session = e.SessionInfo;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(session.UserName))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserOnlineFromDevice"),
|
|
||||||
session.UserName,
|
|
||||||
session.DeviceName),
|
|
||||||
"SessionStarted",
|
|
||||||
session.UserId)
|
|
||||||
{
|
|
||||||
ShortOverview = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("LabelIpAddressValue"),
|
|
||||||
session.RemoteEndPoint)
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnPluginUpdated(object sender, InstallationInfo e)
|
|
||||||
{
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("PluginUpdatedWithName"),
|
|
||||||
e.Name),
|
|
||||||
NotificationType.PluginUpdateInstalled.ToString(),
|
|
||||||
Guid.Empty)
|
|
||||||
{
|
|
||||||
ShortOverview = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
|
||||||
e.Version),
|
|
||||||
Overview = e.Changelog
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnPluginUninstalled(object sender, IPlugin e)
|
|
||||||
{
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("PluginUninstalledWithName"),
|
|
||||||
e.Name),
|
|
||||||
NotificationType.PluginUninstalled.ToString(),
|
|
||||||
Guid.Empty))
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnPluginInstalled(object sender, InstallationInfo e)
|
|
||||||
{
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("PluginInstalledWithName"),
|
|
||||||
e.Name),
|
|
||||||
NotificationType.PluginInstalled.ToString(),
|
|
||||||
Guid.Empty)
|
|
||||||
{
|
|
||||||
ShortOverview = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
|
||||||
e.Version)
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
|
||||||
{
|
|
||||||
var installationInfo = e.InstallationInfo;
|
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("NameInstallFailed"),
|
|
||||||
installationInfo.Name),
|
|
||||||
NotificationType.InstallationFailed.ToString(),
|
|
||||||
Guid.Empty)
|
|
||||||
{
|
|
||||||
ShortOverview = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
|
||||||
installationInfo.Version),
|
|
||||||
Overview = e.Exception.Message
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
|
|
||||||
{
|
|
||||||
var result = e.Result;
|
|
||||||
var task = e.Task;
|
|
||||||
|
|
||||||
if (task.ScheduledTask is IConfigurableScheduledTask activityTask
|
|
||||||
&& !activityTask.IsLogged)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var time = result.EndTimeUtc - result.StartTimeUtc;
|
|
||||||
var runningTime = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("LabelRunningTimeValue"),
|
|
||||||
ToUserFriendlyString(time));
|
|
||||||
|
|
||||||
if (result.Status == TaskCompletionStatus.Failed)
|
|
||||||
{
|
|
||||||
var vals = new List<string>();
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(e.Result.ErrorMessage))
|
|
||||||
{
|
|
||||||
vals.Add(e.Result.ErrorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(e.Result.LongErrorMessage))
|
|
||||||
{
|
|
||||||
vals.Add(e.Result.LongErrorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
await CreateLogEntry(new ActivityLog(
|
|
||||||
string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
|
|
||||||
NotificationType.TaskFailed.ToString(),
|
|
||||||
Guid.Empty)
|
|
||||||
{
|
|
||||||
LogSeverity = LogLevel.Error,
|
|
||||||
Overview = string.Join(Environment.NewLine, vals),
|
|
||||||
ShortOverview = runningTime
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CreateLogEntry(ActivityLog entry)
|
|
||||||
=> await _activityManager.CreateAsync(entry).ConfigureAwait(false);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_taskManager.TaskCompleted -= OnTaskCompleted;
|
|
||||||
|
|
||||||
_installationManager.PluginInstalled -= OnPluginInstalled;
|
|
||||||
_installationManager.PluginUninstalled -= OnPluginUninstalled;
|
|
||||||
_installationManager.PluginUpdated -= OnPluginUpdated;
|
|
||||||
_installationManager.PackageInstallationFailed -= OnPackageInstallationFailed;
|
|
||||||
|
|
||||||
_sessionManager.SessionStarted -= OnSessionStarted;
|
|
||||||
_sessionManager.AuthenticationFailed -= OnAuthenticationFailed;
|
|
||||||
_sessionManager.AuthenticationSucceeded -= OnAuthenticationSucceeded;
|
|
||||||
_sessionManager.SessionEnded -= OnSessionEnded;
|
|
||||||
|
|
||||||
_sessionManager.PlaybackStart -= OnPlaybackStart;
|
|
||||||
_sessionManager.PlaybackStopped -= OnPlaybackStopped;
|
|
||||||
|
|
||||||
_subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
|
|
||||||
|
|
||||||
_userManager.OnUserCreated -= OnUserCreated;
|
|
||||||
_userManager.OnUserPasswordChanged -= OnUserPasswordChanged;
|
|
||||||
_userManager.OnUserDeleted -= OnUserDeleted;
|
|
||||||
_userManager.OnUserLockedOut -= OnUserLockedOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructs a user-friendly string for this TimeSpan instance.
|
|
||||||
/// </summary>
|
|
||||||
private static string ToUserFriendlyString(TimeSpan span)
|
|
||||||
{
|
|
||||||
const int DaysInYear = 365;
|
|
||||||
const int DaysInMonth = 30;
|
|
||||||
|
|
||||||
// Get each non-zero value from TimeSpan component
|
|
||||||
var values = new List<string>();
|
|
||||||
|
|
||||||
// Number of years
|
|
||||||
int days = span.Days;
|
|
||||||
if (days >= DaysInYear)
|
|
||||||
{
|
|
||||||
int years = days / DaysInYear;
|
|
||||||
values.Add(CreateValueString(years, "year"));
|
|
||||||
days %= DaysInYear;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of months
|
|
||||||
if (days >= DaysInMonth)
|
|
||||||
{
|
|
||||||
int months = days / DaysInMonth;
|
|
||||||
values.Add(CreateValueString(months, "month"));
|
|
||||||
days = days % DaysInMonth;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of days
|
|
||||||
if (days >= 1)
|
|
||||||
{
|
|
||||||
values.Add(CreateValueString(days, "day"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of hours
|
|
||||||
if (span.Hours >= 1)
|
|
||||||
{
|
|
||||||
values.Add(CreateValueString(span.Hours, "hour"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of minutes
|
|
||||||
if (span.Minutes >= 1)
|
|
||||||
{
|
|
||||||
values.Add(CreateValueString(span.Minutes, "minute"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of seconds (include when 0 if no other components included)
|
|
||||||
if (span.Seconds >= 1 || values.Count == 0)
|
|
||||||
{
|
|
||||||
values.Add(CreateValueString(span.Seconds, "second"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combine values into string
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
for (int i = 0; i < values.Count; i++)
|
|
||||||
{
|
|
||||||
if (builder.Length > 0)
|
|
||||||
{
|
|
||||||
builder.Append(i == values.Count - 1 ? " and " : ", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Append(values[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return result
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructs a string description of a time-span value.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The value of this item.</param>
|
|
||||||
/// <param name="description">The name of this item (singular form).</param>
|
|
||||||
private static string CreateValueString(int value, string description)
|
|
||||||
{
|
|
||||||
return string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"{0:#,##0} {1}",
|
|
||||||
value,
|
|
||||||
value == 1 ? description : string.Format(CultureInfo.InvariantCulture, "{0}s", description));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -308,7 +308,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error loading configuration file: {path}", path);
|
Logger.LogError(ex, "Error loading configuration file: {Path}", path);
|
||||||
|
|
||||||
return Activator.CreateInstance(configurationType);
|
return Activator.CreateInstance(configurationType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -22,7 +24,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
{
|
{
|
||||||
object configuration;
|
object configuration;
|
||||||
|
|
||||||
byte[] buffer = null;
|
byte[]? buffer = null;
|
||||||
|
|
||||||
// Use try/catch to avoid the extra file system lookup using File.Exists
|
// Use try/catch to avoid the extra file system lookup using File.Exists
|
||||||
try
|
try
|
||||||
|
@ -36,19 +38,23 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
configuration = Activator.CreateInstance(type);
|
configuration = Activator.CreateInstance(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
using var stream = new MemoryStream();
|
using var stream = new MemoryStream(buffer?.Length ?? 0);
|
||||||
xmlSerializer.SerializeToStream(configuration, stream);
|
xmlSerializer.SerializeToStream(configuration, stream);
|
||||||
|
|
||||||
// Take the object we just got and serialize it back to bytes
|
// Take the object we just got and serialize it back to bytes
|
||||||
var newBytes = stream.ToArray();
|
byte[] newBytes = stream.GetBuffer();
|
||||||
|
int newBytesLen = (int)stream.Length;
|
||||||
|
|
||||||
// If the file didn't exist before, or if something has changed, re-save
|
// If the file didn't exist before, or if something has changed, re-save
|
||||||
if (buffer == null || !buffer.SequenceEqual(newBytes))
|
if (buffer == null || !newBytes.AsSpan(0, newBytesLen).SequenceEqual(buffer))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
// Save it after load in case we got new items
|
// Save it after load in case we got new items
|
||||||
File.WriteAllBytes(path, newBytes);
|
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
|
{
|
||||||
|
fs.Write(newBytes, 0, newBytesLen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return configuration;
|
return configuration;
|
||||||
|
|
|
@ -4,7 +4,6 @@ using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
@ -38,23 +37,23 @@ using Emby.Server.Implementations.LiveTv;
|
||||||
using Emby.Server.Implementations.Localization;
|
using Emby.Server.Implementations.Localization;
|
||||||
using Emby.Server.Implementations.Net;
|
using Emby.Server.Implementations.Net;
|
||||||
using Emby.Server.Implementations.Playlists;
|
using Emby.Server.Implementations.Playlists;
|
||||||
|
using Emby.Server.Implementations.QuickConnect;
|
||||||
using Emby.Server.Implementations.ScheduledTasks;
|
using Emby.Server.Implementations.ScheduledTasks;
|
||||||
using Emby.Server.Implementations.Security;
|
using Emby.Server.Implementations.Security;
|
||||||
using Emby.Server.Implementations.Serialization;
|
using Emby.Server.Implementations.Serialization;
|
||||||
using Emby.Server.Implementations.Services;
|
|
||||||
using Emby.Server.Implementations.Session;
|
using Emby.Server.Implementations.Session;
|
||||||
|
using Emby.Server.Implementations.SyncPlay;
|
||||||
using Emby.Server.Implementations.TV;
|
using Emby.Server.Implementations.TV;
|
||||||
using Emby.Server.Implementations.Updates;
|
using Emby.Server.Implementations.Updates;
|
||||||
using Emby.Server.Implementations.SyncPlay;
|
using Jellyfin.Api.Helpers;
|
||||||
using MediaBrowser.Api;
|
|
||||||
using MediaBrowser.Common;
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Events;
|
using MediaBrowser.Common.Events;
|
||||||
|
using MediaBrowser.Common.Json;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
using MediaBrowser.Common.Updates;
|
using MediaBrowser.Common.Updates;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Authentication;
|
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Chapters;
|
using MediaBrowser.Controller.Chapters;
|
||||||
using MediaBrowser.Controller.Collections;
|
using MediaBrowser.Controller.Collections;
|
||||||
|
@ -73,13 +72,14 @@ using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Playlists;
|
using MediaBrowser.Controller.Playlists;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Controller.QuickConnect;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Controller.Security;
|
using MediaBrowser.Controller.Security;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Controller.Sorting;
|
using MediaBrowser.Controller.Sorting;
|
||||||
using MediaBrowser.Controller.Subtitles;
|
using MediaBrowser.Controller.Subtitles;
|
||||||
using MediaBrowser.Controller.TV;
|
|
||||||
using MediaBrowser.Controller.SyncPlay;
|
using MediaBrowser.Controller.SyncPlay;
|
||||||
|
using MediaBrowser.Controller.TV;
|
||||||
using MediaBrowser.LocalMetadata.Savers;
|
using MediaBrowser.LocalMetadata.Savers;
|
||||||
using MediaBrowser.MediaEncoding.BdInfo;
|
using MediaBrowser.MediaEncoding.BdInfo;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
|
@ -90,20 +90,19 @@ using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using MediaBrowser.Model.Services;
|
|
||||||
using MediaBrowser.Model.System;
|
using MediaBrowser.Model.System;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using MediaBrowser.Providers.Chapters;
|
using MediaBrowser.Providers.Chapters;
|
||||||
using MediaBrowser.Providers.Manager;
|
using MediaBrowser.Providers.Manager;
|
||||||
using MediaBrowser.Providers.Plugins.TheTvdb;
|
using MediaBrowser.Providers.Plugins.TheTvdb;
|
||||||
using MediaBrowser.Providers.Subtitles;
|
using MediaBrowser.Providers.Subtitles;
|
||||||
using MediaBrowser.WebDashboard.Api;
|
|
||||||
using MediaBrowser.XbmcMetadata.Providers;
|
using MediaBrowser.XbmcMetadata.Providers;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Prometheus.DotNetRuntime;
|
using Prometheus.DotNetRuntime;
|
||||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||||
|
using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations
|
namespace Emby.Server.Implementations
|
||||||
{
|
{
|
||||||
|
@ -124,14 +123,18 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
private IMediaEncoder _mediaEncoder;
|
private IMediaEncoder _mediaEncoder;
|
||||||
private ISessionManager _sessionManager;
|
private ISessionManager _sessionManager;
|
||||||
private IHttpServer _httpServer;
|
private IHttpClientFactory _httpClientFactory;
|
||||||
private IHttpClient _httpClient;
|
private IWebSocketManager _webSocketManager;
|
||||||
|
|
||||||
|
private string[] _urlPrefixes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this instance can self restart.
|
/// Gets a value indicating whether this instance can self restart.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanSelfRestart => _startupOptions.RestartPath != null;
|
public bool CanSelfRestart => _startupOptions.RestartPath != null;
|
||||||
|
|
||||||
|
public bool CoreStartupHasCompleted { get; private set; }
|
||||||
|
|
||||||
public virtual bool CanLaunchWebBrowser
|
public virtual bool CanLaunchWebBrowser
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -175,6 +178,8 @@ namespace Emby.Server.Implementations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected ILogger<ApplicationHost> Logger { get; }
|
protected ILogger<ApplicationHost> Logger { get; }
|
||||||
|
|
||||||
|
protected IServiceCollection ServiceCollection { get; }
|
||||||
|
|
||||||
private IPlugin[] _plugins;
|
private IPlugin[] _plugins;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -192,7 +197,7 @@ namespace Emby.Server.Implementations
|
||||||
/// Gets or sets the application paths.
|
/// Gets or sets the application paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application paths.</value>
|
/// <value>The application paths.</value>
|
||||||
protected ServerApplicationPaths ApplicationPaths { get; set; }
|
protected IServerApplicationPaths ApplicationPaths { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets all concrete types.
|
/// Gets or sets all concrete types.
|
||||||
|
@ -236,13 +241,15 @@ namespace Emby.Server.Implementations
|
||||||
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
|
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ApplicationHost(
|
public ApplicationHost(
|
||||||
ServerApplicationPaths applicationPaths,
|
IServerApplicationPaths applicationPaths,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IStartupOptions options,
|
IStartupOptions options,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
INetworkManager networkManager)
|
INetworkManager networkManager,
|
||||||
|
IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
_xmlSerializer = new MyXmlSerializer();
|
_xmlSerializer = new MyXmlSerializer();
|
||||||
|
ServiceCollection = serviceCollection;
|
||||||
|
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
|
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
|
||||||
|
@ -273,6 +280,10 @@ namespace Emby.Server.Implementations
|
||||||
Password = ServerConfigurationManager.Configuration.CertificatePassword
|
Password = ServerConfigurationManager.Configuration.CertificatePassword
|
||||||
};
|
};
|
||||||
Certificate = GetCertificate(CertificateInfo);
|
Certificate = GetCertificate(CertificateInfo);
|
||||||
|
|
||||||
|
ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
|
||||||
|
ApplicationVersionString = ApplicationVersion.ToString(3);
|
||||||
|
ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ExpandVirtualPath(string path)
|
public string ExpandVirtualPath(string path)
|
||||||
|
@ -302,16 +313,16 @@ namespace Emby.Server.Implementations
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Version ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version;
|
public Version ApplicationVersion { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string ApplicationVersionString { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
|
public string ApplicationVersionString { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current application user agent.
|
/// Gets the current application user agent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application user agent.</value>
|
/// <value>The application user agent.</value>
|
||||||
public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersionString;
|
public string ApplicationUserAgent { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the email address for use within a comment section of a user agent field.
|
/// Gets the email address for use within a comment section of a user agent field.
|
||||||
|
@ -442,8 +453,7 @@ namespace Emby.Server.Implementations
|
||||||
Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
|
Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
|
||||||
|
|
||||||
Logger.LogInformation("Core startup complete");
|
Logger.LogInformation("Core startup complete");
|
||||||
_httpServer.GlobalResponse = null;
|
CoreStartupHasCompleted = true;
|
||||||
|
|
||||||
stopWatch.Restart();
|
stopWatch.Restart();
|
||||||
await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false);
|
await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false);
|
||||||
Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
|
Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
|
||||||
|
@ -466,7 +476,7 @@ namespace Emby.Server.Implementations
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Init(IServiceCollection serviceCollection)
|
public void Init()
|
||||||
{
|
{
|
||||||
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
|
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
|
||||||
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
|
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
|
||||||
|
@ -484,12 +494,10 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
foreach (var plugin in Plugins)
|
foreach (var plugin in Plugins)
|
||||||
{
|
{
|
||||||
pluginBuilder.AppendLine(
|
pluginBuilder.Append(plugin.Name)
|
||||||
string.Format(
|
.Append(' ')
|
||||||
CultureInfo.InvariantCulture,
|
.Append(plugin.Version)
|
||||||
"{0} {1}",
|
.AppendLine();
|
||||||
plugin.Name,
|
|
||||||
plugin.Version));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString());
|
Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString());
|
||||||
|
@ -497,147 +505,141 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
DiscoverTypes();
|
DiscoverTypes();
|
||||||
|
|
||||||
RegisterServices(serviceCollection);
|
RegisterServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
|
|
||||||
=> _httpServer.RequestHandler(context);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers services/resources with the service collection that will be available via DI.
|
/// Registers services/resources with the service collection that will be available via DI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void RegisterServices(IServiceCollection serviceCollection)
|
protected virtual void RegisterServices()
|
||||||
{
|
{
|
||||||
serviceCollection.AddSingleton(_startupOptions);
|
ServiceCollection.AddSingleton(_startupOptions);
|
||||||
|
|
||||||
serviceCollection.AddMemoryCache();
|
ServiceCollection.AddMemoryCache();
|
||||||
|
|
||||||
serviceCollection.AddSingleton(ConfigurationManager);
|
ServiceCollection.AddSingleton(ConfigurationManager);
|
||||||
serviceCollection.AddSingleton<IApplicationHost>(this);
|
ServiceCollection.AddSingleton<IApplicationHost>(this);
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
ServiceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IJsonSerializer, JsonSerializer>();
|
ServiceCollection.AddSingleton<IJsonSerializer, JsonSerializer>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton(_fileSystemManager);
|
ServiceCollection.AddSingleton(_fileSystemManager);
|
||||||
serviceCollection.AddSingleton<TvdbClientManager>();
|
ServiceCollection.AddSingleton<TvdbClientManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IHttpClient, HttpClientManager.HttpClientManager>();
|
ServiceCollection.AddSingleton(_networkManager);
|
||||||
|
|
||||||
serviceCollection.AddSingleton(_networkManager);
|
ServiceCollection.AddSingleton<IIsoManager, IsoManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IIsoManager, IsoManager>();
|
ServiceCollection.AddSingleton<ITaskManager, TaskManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ITaskManager, TaskManager>();
|
ServiceCollection.AddSingleton(_xmlSerializer);
|
||||||
|
|
||||||
serviceCollection.AddSingleton(_xmlSerializer);
|
ServiceCollection.AddSingleton<IStreamHelper, StreamHelper>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
|
ServiceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
|
ServiceCollection.AddSingleton<ISocketFactory, SocketFactory>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ISocketFactory, SocketFactory>();
|
ServiceCollection.AddSingleton<IInstallationManager, InstallationManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IInstallationManager, InstallationManager>();
|
ServiceCollection.AddSingleton<IZipClient, ZipClient>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IZipClient, ZipClient>();
|
ServiceCollection.AddSingleton<IServerApplicationHost>(this);
|
||||||
|
ServiceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IHttpResultFactory, HttpResultFactory>();
|
ServiceCollection.AddSingleton(ServerConfigurationManager);
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
ServiceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
|
||||||
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton(ServerConfigurationManager);
|
ServiceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
|
ServiceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
|
||||||
|
ServiceCollection.AddSingleton<IUserDataManager, UserDataManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
|
ServiceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
|
ServiceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
|
||||||
serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IDisplayPreferencesRepository, SqliteDisplayPreferencesRepository>();
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
|
|
||||||
|
|
||||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||||
serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
|
ServiceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
|
||||||
|
|
||||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||||
// TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
|
ServiceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>));
|
||||||
serviceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>));
|
ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
||||||
serviceCollection.AddSingleton<IMediaEncoder>(provider =>
|
|
||||||
ActivatorUtilities.CreateInstance<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(provider, _startupOptions.FFmpegPath ?? string.Empty));
|
|
||||||
|
|
||||||
// TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
|
// TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
|
||||||
serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
|
ServiceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
|
||||||
serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
|
ServiceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
|
||||||
serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
|
ServiceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
|
||||||
serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
|
ServiceCollection.AddSingleton<ILibraryManager, LibraryManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IMusicManager, MusicManager>();
|
ServiceCollection.AddSingleton<IMusicManager, MusicManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
|
ServiceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ISearchEngine, SearchEngine>();
|
ServiceCollection.AddSingleton<ISearchEngine, SearchEngine>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ServiceController>();
|
ServiceCollection.AddSingleton<IWebSocketManager, WebSocketManager>();
|
||||||
serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>();
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
|
ServiceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
|
ServiceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IDeviceManager, DeviceManager>();
|
ServiceCollection.AddSingleton<IDeviceManager, DeviceManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
|
ServiceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
|
ServiceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IProviderManager, ProviderManager>();
|
ServiceCollection.AddSingleton<IProviderManager, ProviderManager>();
|
||||||
|
|
||||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||||
serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
|
ServiceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
|
||||||
serviceCollection.AddSingleton<IDtoService, DtoService>();
|
ServiceCollection.AddSingleton<IDtoService, DtoService>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IChannelManager, ChannelManager>();
|
ServiceCollection.AddSingleton<IChannelManager, ChannelManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ISessionManager, SessionManager>();
|
ServiceCollection.AddSingleton<ISessionManager, SessionManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IDlnaManager, DlnaManager>();
|
ServiceCollection.AddSingleton<IDlnaManager, DlnaManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
|
ServiceCollection.AddSingleton<ICollectionManager, CollectionManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
|
ServiceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
|
ServiceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<LiveTvDtoService>();
|
ServiceCollection.AddSingleton<LiveTvDtoService>();
|
||||||
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
|
ServiceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
|
ServiceCollection.AddSingleton<IUserViewManager, UserViewManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<INotificationManager, NotificationManager>();
|
ServiceCollection.AddSingleton<INotificationManager, NotificationManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
|
ServiceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
|
ServiceCollection.AddSingleton<IChapterManager, ChapterManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
|
ServiceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
|
ServiceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
|
||||||
serviceCollection.AddSingleton<ISessionContext, SessionContext>();
|
ServiceCollection.AddSingleton<ISessionContext, SessionContext>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IAuthService, AuthService>();
|
ServiceCollection.AddSingleton<IAuthService, AuthService>();
|
||||||
|
ServiceCollection.AddSingleton<IQuickConnect, QuickConnectManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
|
ServiceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IResourceFileManager, ResourceFileManager>();
|
ServiceCollection.AddSingleton<IResourceFileManager, ResourceFileManager>();
|
||||||
serviceCollection.AddSingleton<EncodingHelper>();
|
ServiceCollection.AddSingleton<EncodingHelper>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
|
ServiceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
|
||||||
|
|
||||||
|
ServiceCollection.AddSingleton<TranscodingJobHelper>();
|
||||||
|
ServiceCollection.AddScoped<MediaInfoHelper>();
|
||||||
|
ServiceCollection.AddScoped<AudioHelper>();
|
||||||
|
ServiceCollection.AddScoped<DynamicHlsHelper>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -651,10 +653,9 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
_mediaEncoder = Resolve<IMediaEncoder>();
|
_mediaEncoder = Resolve<IMediaEncoder>();
|
||||||
_sessionManager = Resolve<ISessionManager>();
|
_sessionManager = Resolve<ISessionManager>();
|
||||||
_httpServer = Resolve<IHttpServer>();
|
_httpClientFactory = Resolve<IHttpClientFactory>();
|
||||||
_httpClient = Resolve<IHttpClient>();
|
_webSocketManager = Resolve<IWebSocketManager>();
|
||||||
|
|
||||||
((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
|
|
||||||
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
|
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
|
||||||
|
|
||||||
SetStaticProperties();
|
SetStaticProperties();
|
||||||
|
@ -754,7 +755,6 @@ namespace Emby.Server.Implementations
|
||||||
CollectionFolder.XmlSerializer = _xmlSerializer;
|
CollectionFolder.XmlSerializer = _xmlSerializer;
|
||||||
CollectionFolder.JsonSerializer = Resolve<IJsonSerializer>();
|
CollectionFolder.JsonSerializer = Resolve<IJsonSerializer>();
|
||||||
CollectionFolder.ApplicationHost = this;
|
CollectionFolder.ApplicationHost = this;
|
||||||
AuthenticatedAttribute.AuthService = Resolve<IAuthService>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -774,7 +774,8 @@ namespace Emby.Server.Implementations
|
||||||
.Where(i => i != null)
|
.Where(i => i != null)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
_httpServer.Init(GetExportTypes<IService>(), GetExports<IWebSocketListener>(), GetUrlPrefixes());
|
_urlPrefixes = GetUrlPrefixes().ToArray();
|
||||||
|
_webSocketManager.Init(GetExports<IWebSocketListener>());
|
||||||
|
|
||||||
Resolve<ILibraryManager>().AddParts(
|
Resolve<ILibraryManager>().AddParts(
|
||||||
GetExports<IResolverIgnoreRule>(),
|
GetExports<IResolverIgnoreRule>(),
|
||||||
|
@ -799,7 +800,6 @@ namespace Emby.Server.Implementations
|
||||||
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
|
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
|
||||||
|
|
||||||
Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
|
Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
|
||||||
Resolve<IUserManager>().AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
|
|
||||||
|
|
||||||
Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>());
|
Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>());
|
||||||
}
|
}
|
||||||
|
@ -839,6 +839,8 @@ namespace Emby.Server.Implementations
|
||||||
{
|
{
|
||||||
hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s));
|
hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plugin.RegisterServices(ServiceCollection);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -873,6 +875,11 @@ namespace Emby.Server.Implementations
|
||||||
Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName);
|
Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
catch (TypeLoadException ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Error loading types from {Assembly}.", ass.FullName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Type type in exportedTypes)
|
foreach (Type type in exportedTypes)
|
||||||
{
|
{
|
||||||
|
@ -934,7 +941,7 @@ namespace Emby.Server.Implementations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_httpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase))
|
if (!_urlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
requiresRestart = true;
|
requiresRestart = true;
|
||||||
}
|
}
|
||||||
|
@ -1111,12 +1118,6 @@ namespace Emby.Server.Implementations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include composable parts in the Api assembly
|
|
||||||
yield return typeof(ApiEntryPoint).Assembly;
|
|
||||||
|
|
||||||
// Include composable parts in the Dashboard assembly
|
|
||||||
yield return typeof(DashboardService).Assembly;
|
|
||||||
|
|
||||||
// Include composable parts in the Model assembly
|
// Include composable parts in the Model assembly
|
||||||
yield return typeof(SystemInfo).Assembly;
|
yield return typeof(SystemInfo).Assembly;
|
||||||
|
|
||||||
|
@ -1232,7 +1233,7 @@ namespace Emby.Server.Implementations
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetLocalApiUrl(addresses.First());
|
return GetLocalApiUrl(addresses[0]);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -1305,13 +1306,13 @@ namespace Emby.Server.Implementations
|
||||||
var addresses = ServerConfigurationManager
|
var addresses = ServerConfigurationManager
|
||||||
.Configuration
|
.Configuration
|
||||||
.LocalNetworkAddresses
|
.LocalNetworkAddresses
|
||||||
.Select(NormalizeConfiguredLocalAddress)
|
.Select(x => NormalizeConfiguredLocalAddress(x))
|
||||||
.Where(i => i != null)
|
.Where(i => i != null)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (addresses.Count == 0)
|
if (addresses.Count == 0)
|
||||||
{
|
{
|
||||||
addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
|
addresses.AddRange(_networkManager.GetLocalIpAddresses());
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultList = new List<IPAddress>();
|
var resultList = new List<IPAddress>();
|
||||||
|
@ -1326,8 +1327,7 @@ namespace Emby.Server.Implementations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var valid = await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false);
|
if (await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false))
|
||||||
if (valid)
|
|
||||||
{
|
{
|
||||||
resultList.Add(address);
|
resultList.Add(address);
|
||||||
|
|
||||||
|
@ -1341,13 +1341,12 @@ namespace Emby.Server.Implementations
|
||||||
return resultList;
|
return resultList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPAddress NormalizeConfiguredLocalAddress(string address)
|
public IPAddress NormalizeConfiguredLocalAddress(ReadOnlySpan<char> address)
|
||||||
{
|
{
|
||||||
var index = address.Trim('/').IndexOf('/');
|
var index = address.Trim('/').IndexOf('/');
|
||||||
|
|
||||||
if (index != -1)
|
if (index != -1)
|
||||||
{
|
{
|
||||||
address = address.Substring(index + 1);
|
address = address.Slice(index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IPAddress.TryParse(address.Trim('/'), out IPAddress result))
|
if (IPAddress.TryParse(address.Trim('/'), out IPAddress result))
|
||||||
|
@ -1377,26 +1376,18 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var response = await _httpClient.SendAsync(
|
using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
|
||||||
new HttpRequestOptions
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
{
|
.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
Url = apiUrl,
|
|
||||||
LogErrorResponseBody = false,
|
await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||||
BufferContent = false,
|
var result = await System.Text.Json.JsonSerializer.DeserializeAsync<string>(stream, JsonDefaults.GetOptions(), cancellationToken).ConfigureAwait(false);
|
||||||
CancellationToken = cancellationToken
|
|
||||||
}, HttpMethod.Post).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
using (var reader = new StreamReader(response.Content))
|
|
||||||
{
|
|
||||||
var result = await reader.ReadToEndAsync().ConfigureAwait(false);
|
|
||||||
var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
|
var valid = string.Equals(Name, result, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
_validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
|
_validAddressResults.AddOrUpdate(apiUrl, valid, (k, v) => valid);
|
||||||
Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid);
|
Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, valid);
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, "Cancelled");
|
Logger.LogDebug("Ping test result to {0}. Success: {1}", apiUrl, "Cancelled");
|
||||||
|
@ -1473,6 +1464,20 @@ namespace Emby.Server.Implementations
|
||||||
_plugins = list.ToArray();
|
_plugins = list.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Assembly> GetApiPluginAssemblies()
|
||||||
|
{
|
||||||
|
var assemblies = _allConcreteTypes
|
||||||
|
.Where(i => typeof(ControllerBase).IsAssignableFrom(i))
|
||||||
|
.Select(i => i.Assembly)
|
||||||
|
.Distinct();
|
||||||
|
|
||||||
|
foreach (var assembly in assemblies)
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Found API endpoints in plugin {Name}", assembly.FullName);
|
||||||
|
yield return assembly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void LaunchUrl(string url)
|
public virtual void LaunchUrl(string url)
|
||||||
{
|
{
|
||||||
if (!CanLaunchWebBrowser)
|
if (!CanLaunchWebBrowser)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Browser
|
namespace Emby.Server.Implementations.Browser
|
||||||
|
@ -24,7 +26,7 @@ namespace Emby.Server.Implementations.Browser
|
||||||
/// <param name="appHost">The app host.</param>
|
/// <param name="appHost">The app host.</param>
|
||||||
public static void OpenSwaggerPage(IServerApplicationHost appHost)
|
public static void OpenSwaggerPage(IServerApplicationHost appHost)
|
||||||
{
|
{
|
||||||
TryOpenUrl(appHost, "/swagger/index.html");
|
TryOpenUrl(appHost, "/api-docs/swagger");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue