Merge branch 'master' into websocket

This commit is contained in:
Bond_009 2020-05-02 00:54:04 +02:00
commit 15634a1913
1246 changed files with 23673 additions and 14062 deletions

View File

@ -0,0 +1,96 @@
parameters:
- name: Packages
type: object
default: {}
- name: LinuxImage
type: string
default: "ubuntu-latest"
- name: DotNetSdkVersion
type: string
default: 3.1.100
jobs:
- job: CompatibilityCheck
displayName: Compatibility Check
pool:
vmImage: "${{ parameters.LinuxImage }}"
# only execute for pull requests
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
strategy:
matrix:
${{ each Package in parameters.Packages }}:
${{ Package.key }}:
NugetPackageName: ${{ Package.value.NugetPackageName }}
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
maxParallel: 2
dependsOn: Build
steps:
- checkout: none
- task: UseDotNet@2
displayName: "Update DotNet"
inputs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
- task: DownloadPipelineArtifact@2
displayName: "Download New Assembly Build Artifact"
inputs:
source: "current"
artifact: "$(NugetPackageName)"
path: "$(System.ArtifactsDirectory)/new-artifacts"
runVersion: "latest"
- task: CopyFiles@2
displayName: "Copy New Assembly Build Artifact"
inputs:
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
contents: "**/*.dll"
targetFolder: $(System.ArtifactsDirectory)/new-release
cleanTargetFolder: true
overWrite: true
flattenFolders: true
- task: DownloadPipelineArtifact@2
displayName: "Download Reference Assembly Build Artifact"
inputs:
source: "specific"
artifact: "$(NugetPackageName)"
path: "$(System.ArtifactsDirectory)/current-artifacts"
project: "$(System.TeamProjectId)"
pipeline: "$(System.DefinitionId)"
runVersion: "latestFromBranch"
runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
- task: CopyFiles@2
displayName: "Copy Reference Assembly Build Artifact"
inputs:
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
contents: "**/*.dll"
targetFolder: $(System.ArtifactsDirectory)/current-release
cleanTargetFolder: true
overWrite: true
flattenFolders: true
- task: DownloadGitHubRelease@0
displayName: "Download ABI Compatibility Check Tool"
inputs:
connection: Jellyfin Release Download
userRepository: EraYaN/dotnet-compatibility
defaultVersionType: "latest"
itemPattern: "**-ci.zip"
downloadPath: "$(System.ArtifactsDirectory)"
- task: ExtractFiles@1
displayName: "Extract ABI Compatibility Check Tool"
inputs:
archiveFilePatterns: "$(System.ArtifactsDirectory)/*-ci.zip"
destinationFolder: $(System.ArtifactsDirectory)/tools
cleanDestinationFolder: true
# The `--warnings-only` switch will swallow the return code and not emit any errors.
- task: CmdLine@2
displayName: "Execute ABI Compatibility Check Tool"
inputs:
script: "dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only"
workingDirectory: $(System.ArtifactsDirectory)

View File

@ -0,0 +1,93 @@
parameters:
LinuxImage: "ubuntu-latest"
RestoreBuildProjects: "Jellyfin.Server/Jellyfin.Server.csproj"
DotNetSdkVersion: 3.1.100
jobs:
- job: Build
displayName: Build
strategy:
matrix:
Release:
BuildConfiguration: Release
Debug:
BuildConfiguration: Debug
pool:
vmImage: "${{ parameters.LinuxImage }}"
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: true
- task: DownloadPipelineArtifact@2
displayName: "Download Web Branch"
condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
inputs:
path: '$(Agent.TempDirectory)'
artifact: 'jellyfin-web-production'
source: 'specific'
project: 'jellyfin'
pipeline: 'Jellyfin Web'
runBranch: variables['Build.SourceBranch']
- task: DownloadPipelineArtifact@2
displayName: "Download Web Target"
condition: eq(variables['Build.Reason'], 'PullRequest')
inputs:
path: '$(Agent.TempDirectory)'
artifact: 'jellyfin-web-production'
source: 'specific'
project: 'jellyfin'
pipeline: 'Jellyfin Web'
runBranch: variables['System.PullRequest.TargetBranch']
- task: ExtractFiles@1
displayName: "Extract Web Client"
inputs:
archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
cleanDestinationFolder: false
- task: UseDotNet@2
displayName: "Update DotNet"
inputs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
- task: DotNetCoreCLI@2
displayName: "Publish Server"
inputs:
command: publish
publishWebProjects: false
projects: "${{ parameters.RestoreBuildProjects }}"
arguments: "--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)"
zipAfterPublish: false
- task: PublishPipelineArtifact@0
displayName: "Publish Artifact Naming"
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll"
artifactName: "Jellyfin.Naming"
- task: PublishPipelineArtifact@0
displayName: "Publish Artifact Controller"
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
artifactName: "Jellyfin.Controller"
- task: PublishPipelineArtifact@0
displayName: "Publish Artifact Model"
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
artifactName: "Jellyfin.Model"
- task: PublishPipelineArtifact@0
displayName: "Publish Artifact Common"
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
inputs:
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
artifactName: "Jellyfin.Common"

View File

@ -0,0 +1,89 @@
parameters:
- name: ImageNames
type: object
default:
Linux: "ubuntu-latest"
Windows: "windows-latest"
macOS: "macos-latest"
- name: TestProjects
type: string
default: "tests/**/*Tests.csproj"
- name: DotNetSdkVersion
type: string
default: 3.1.100
jobs:
- job: Test
displayName: Test
strategy:
matrix:
${{ each imageName in parameters.ImageNames }}:
${{ imageName.key }}:
ImageName: ${{ imageName.value }}
pool:
vmImage: "$(ImageName)"
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: false
# This is required for the SonarCloud analyzer
- task: UseDotNet@2
displayName: "Install .NET Core SDK 2.1"
condition: eq(variables['ImageName'], 'ubuntu-latest')
inputs:
packageType: sdk
version: '2.1.805'
- task: UseDotNet@2
displayName: "Update DotNet"
inputs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
- task: SonarCloudPrepare@1
displayName: 'Prepare analysis on SonarCloud'
condition: eq(variables['ImageName'], 'ubuntu-latest')
inputs:
SonarCloud: 'Sonarcloud for Jellyfin'
organization: 'jellyfin'
projectKey: 'jellyfin_jellyfin'
- task: DotNetCoreCLI@2
displayName: 'Run CLI Tests'
inputs:
command: "test"
projects: ${{ parameters.TestProjects }}
arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal "-p:GenerateDocumentationFile=False"'
publishTestResults: true
testRunTitle: $(Agent.JobName)
workingDirectory: "$(Build.SourcesDirectory)"
- task: SonarCloudAnalyze@1
displayName: 'Run Code Analysis'
condition: eq(variables['ImageName'], 'ubuntu-latest')
- task: SonarCloudPublish@1
displayName: 'Publish Quality Gate Result'
condition: eq(variables['ImageName'], 'ubuntu-latest')
- 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
displayName: 'Run ReportGenerator'
inputs:
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
targetdir: "$(Agent.TempDirectory)/merged/"
reporttypes: "Cobertura"
## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
- task: PublishCodeCoverageResults@1
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
displayName: 'Publish Code Coverage'
inputs:
codeCoverageTool: "cobertura"
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
pathToSources: $(Build.SourcesDirectory)
failIfCoverageEmpty: true

View File

@ -1,10 +1,12 @@
name: $(Date:yyyyMMdd)$(Rev:.r) name: $(Date:yyyyMMdd)$(Rev:.r)
variables: variables:
- name: TestProjects - name: TestProjects
value: 'tests/**/*Tests.csproj' value: "tests/**/*Tests.csproj"
- name: RestoreBuildProjects - name: RestoreBuildProjects
value: 'Jellyfin.Server/Jellyfin.Server.csproj' value: "Jellyfin.Server/Jellyfin.Server.csproj"
- name: DotNetSdkVersion
value: 3.1.100
pr: pr:
autoCancel: true autoCancel: true
@ -13,234 +15,21 @@ trigger:
batch: true batch: true
jobs: jobs:
- job: main_build - template: azure-pipelines-main.yml
displayName: Main Build parameters:
pool: LinuxImage: "ubuntu-latest"
vmImage: ubuntu-latest RestoreBuildProjects: $(RestoreBuildProjects)
strategy:
matrix:
Release:
BuildConfiguration: Release
Debug:
BuildConfiguration: Debug
maxParallel: 2
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: true
- task: CmdLine@2 - template: azure-pipelines-test.yml
displayName: "Clone Web Client (Master, Release, or Tag)" parameters:
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')) ImageNames:
inputs: Linux: "ubuntu-latest"
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web' Windows: "windows-latest"
macOS: "macos-latest"
- task: CmdLine@2 - template: azure-pipelines-compat.yml
displayName: "Clone Web Client (PR)" parameters:
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest')) Packages:
inputs:
script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
- task: NodeTool@0
displayName: 'Install Node'
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
versionSpec: '10.x'
- task: CmdLine@2
displayName: "Build Web Client"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: yarn install
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
- task: CopyFiles@2
displayName: 'Copy Web Client'
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
contents: '**'
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: false # Optional
- task: UseDotNet@2
displayName: 'Update DotNet'
inputs:
packageType: sdk
version: 3.1.100
- task: DotNetCoreCLI@2
displayName: 'Publish Server'
inputs:
command: publish
publishWebProjects: false
projects: '$(RestoreBuildProjects)'
arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)'
zipAfterPublish: false
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Naming'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
artifactName: 'Jellyfin.Naming'
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Controller'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
artifactName: 'Jellyfin.Controller'
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Model'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
artifactName: 'Jellyfin.Model'
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Common'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
artifactName: 'Jellyfin.Common'
- job: main_test
displayName: Main Test
pool:
vmImage: windows-latest
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: false
- task: DotNetCoreCLI@2
displayName: Build
inputs:
command: build
publishWebProjects: false
projects: '$(TestProjects)'
arguments: '--configuration $(BuildConfiguration)'
zipAfterPublish: false
- task: VisualStudioTestPlatformInstaller@1
inputs:
packageFeedSelector: 'nugetOrg' # Options: nugetOrg, customFeed, netShare
versionSelector: 'latestPreRelease' # Required when packageFeedSelector == NugetOrg || PackageFeedSelector == CustomFeed# Options: latestPreRelease, latestStable, specificVersion
- task: VSTest@2
inputs:
testSelector: 'testAssemblies' # Options: testAssemblies, testPlan, testRun
testAssemblyVer2: | # Required when testSelector == TestAssemblies
**\bin\$(BuildConfiguration)\**\*tests.dll
**\bin\$(BuildConfiguration)\**\*test.dll
!**\obj\**
!**\xunit.runner.visualstudio.testadapter.dll
!**\xunit.runner.visualstudio.dotnetcore.testadapter.dll
searchFolder: '$(System.DefaultWorkingDirectory)'
runInParallel: True # Optional
runTestsInIsolation: True # Optional
codeCoverageEnabled: True # Optional
configuration: 'Debug' # Optional
publishRunAttachments: true # Optional
testRunTitle: $(Agent.JobName)
otherConsoleOptions: '/platform:x64 /Framework:.NETCoreApp,Version=v3.1 /logger:console;verbosity="normal"'
- job: main_build_win
displayName: Publish Windows
pool:
vmImage: windows-latest
strategy:
matrix:
Release:
BuildConfiguration: Release
maxParallel: 2
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: true
- task: CmdLine@2
displayName: "Clone Web Client (Master, Release, or Tag)"
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
- task: CmdLine@2
displayName: "Clone Web Client (PR)"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
inputs:
script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
- task: NodeTool@0
displayName: 'Install Node'
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
versionSpec: '10.x'
- task: CmdLine@2
displayName: "Build Web Client"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: yarn install
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
- task: CopyFiles@2
displayName: 'Copy Web Client'
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
contents: '**'
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: false # Optional
- task: CmdLine@2
displayName: 'Clone UX Repository'
inputs:
script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
- task: PowerShell@2
displayName: 'Build NSIS Installer'
inputs:
targetType: 'filePath' # Optional. Options: filePath, inline
filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
workingDirectory: $(Build.SourcesDirectory) # Optional
- task: CopyFiles@2
displayName: 'Copy NSIS Installer'
inputs:
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ # Optional
contents: 'jellyfin*.exe'
targetFolder: $(System.ArtifactsDirectory)/setup
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: true # Optional
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Setup'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
targetPath: '$(build.artifactstagingdirectory)/setup'
artifactName: 'Jellyfin Server Setup'
- job: dotnet_compat
displayName: Compatibility Check
pool:
vmImage: ubuntu-latest
dependsOn: main_build
# only execute for pull requests
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
strategy:
matrix:
Naming: Naming:
NugetPackageName: Jellyfin.Naming NugetPackageName: Jellyfin.Naming
AssemblyFileName: Emby.Naming.dll AssemblyFileName: Emby.Naming.dll
@ -253,74 +42,4 @@ jobs:
Common: Common:
NugetPackageName: Jellyfin.Common NugetPackageName: Jellyfin.Common
AssemblyFileName: MediaBrowser.Common.dll AssemblyFileName: MediaBrowser.Common.dll
maxParallel: 2 LinuxImage: "ubuntu-latest"
steps:
- checkout: none
- task: UseDotNet@2
displayName: 'Update DotNet'
inputs:
packageType: sdk
version: 3.1.100
- task: DownloadPipelineArtifact@2
displayName: 'Download New Assembly Build Artifact'
inputs:
source: 'current' # Options: current, specific
artifact: '$(NugetPackageName)' # Optional
path: '$(System.ArtifactsDirectory)/new-artifacts'
runVersion: 'latest' # Required when source == Specific. Options: latest, latestFromBranch, specific
- task: CopyFiles@2
displayName: 'Copy New Assembly Build Artifact'
inputs:
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
contents: '**/*.dll'
targetFolder: $(System.ArtifactsDirectory)/new-release
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: true # Optional
- task: DownloadPipelineArtifact@2
displayName: 'Download Reference Assembly Build Artifact'
inputs:
source: 'specific' # Options: current, specific
artifact: '$(NugetPackageName)' # Optional
path: '$(System.ArtifactsDirectory)/current-artifacts'
project: '$(System.TeamProjectId)' # Required when source == Specific
pipeline: '$(System.DefinitionId)' # Required when source == Specific
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
runBranch: 'refs/heads/$(System.PullRequest.TargetBranch)' # Required when source == Specific && runVersion == LatestFromBranch
- task: CopyFiles@2
displayName: 'Copy Reference Assembly Build Artifact'
inputs:
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
contents: '**/*.dll'
targetFolder: $(System.ArtifactsDirectory)/current-release
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: true # Optional
- task: DownloadGitHubRelease@0
displayName: 'Download ABI Compatibility Check Tool'
inputs:
connection: Jellyfin Release Download
userRepository: EraYaN/dotnet-compatibility
defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag
itemPattern: '**-ci.zip' # Optional
downloadPath: '$(System.ArtifactsDirectory)'
- task: ExtractFiles@1
displayName: 'Extract ABI Compatibility Check Tool'
inputs:
archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip'
destinationFolder: $(System.ArtifactsDirectory)/tools
cleanDestinationFolder: true
# The `--warnings-only` switch will swallow the return code and not emit any errors.
- task: CmdLine@2
displayName: 'Execute ABI Compatibility Check Tool'
inputs:
script: 'dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only'
workingDirectory: $(System.ArtifactsDirectory) # Optional

View File

@ -1,46 +0,0 @@
name: Nightly-$(date:yyyyMMdd).$(rev:r)
variables:
- name: Version
value: '1.0.0'
trigger: none
pr: none
jobs:
- job: publish_artifacts_nightly
displayName: Publish Artifacts Nightly
pool:
vmImage: ubuntu-latest
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download the Windows Setup Artifact
inputs:
source: 'specific' # Options: current, specific
artifact: 'Jellyfin Server Setup' # Optional
path: '$(System.ArtifactsDirectory)/win-installer'
project: '$(System.TeamProjectId)' # Required when source == Specific
pipelineId: 1 # Required when source == Specific
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
- task: SSH@0
displayName: 'Create Drop directory'
inputs:
sshEndpoint: 'Jellyfin Build Server'
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_nightly_azure_upload'
- task: CopyFilesOverSSH@0
displayName: 'Copy the Windows Setup to the Repo'
inputs:
sshEndpoint: 'Jellyfin Build Server'
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
contents: 'jellyfin_*.exe'
targetFolder: '/srv/incoming/jellyfin_nightly_azure_upload/win-installer'
- task: SSH@0
displayName: 'Clean up SCP symlink'
inputs:
sshEndpoint: 'Jellyfin Build Server'
commands: 'rm -f /srv/incoming/jellyfin_nightly_azure_upload'

View File

@ -1,48 +0,0 @@
name: Release-$(Version)-$(date:yyyyMMdd).$(rev:r)
variables:
- name: Version
value: '1.0.0'
- name: UsedRunId
value: 0
trigger: none
pr: none
jobs:
- job: publish_artifacts_release
displayName: Publish Artifacts Release
pool:
vmImage: ubuntu-latest
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download the Windows Setup Artifact
inputs:
source: 'specific' # Options: current, specific
artifact: 'Jellyfin Server Setup' # Optional
path: '$(System.ArtifactsDirectory)/win-installer'
project: '$(System.TeamProjectId)' # Required when source == Specific
pipelineId: 1 # Required when source == Specific
runVersion: 'specific' # Required when source == Specific. Options: latest, latestFromBranch, specific
runId: $(UsedRunId)
- task: SSH@0
displayName: 'Create Drop directory'
inputs:
sshEndpoint: 'Jellyfin Build Server'
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_release_azure_upload'
- task: CopyFilesOverSSH@0
displayName: 'Copy the Windows Setup to the Repo'
inputs:
sshEndpoint: 'Jellyfin Build Server'
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
contents: 'jellyfin_*.exe'
targetFolder: '/srv/incoming/jellyfin_release_azure_upload/win-installer'
- task: SSH@0
displayName: 'Clean up SCP symlink'
inputs:
sshEndpoint: 'Jellyfin Build Server'
commands: 'rm -f /srv/incoming/jellyfin_release_azure_upload'

View File

@ -1,59 +0,0 @@
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
deployment/fedora-package-x64/pkg-src/jellyfin.spec)
deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
|| curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
cd deployment/fedora-package-x64; \
SOURCE_DIR=../.. \
WORKDIR="$${PWD}"; \
package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
pkg_src_dir="$${WORKDIR}/pkg-src"; \
GNU_TAR=1; \
tar \
--transform "s,^\.,jellyfin-$(VERSION)," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='**/.vs' \
--exclude='**/.vscode' \
--exclude='deployment' \
--exclude='**/bin' \
--exclude='**/obj' \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-czf "pkg-src/jellyfin-$(VERSION).tar.gz" \
-C $${SOURCE_DIR} ./ || GNU_TAR=0; \
if [ $${GNU_TAR} -eq 0 ]; then \
package_temporary_dir="$$(mktemp -d)"; \
mkdir -p "$${package_temporary_dir}/jellyfin"; \
tar \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='**/.vs' \
--exclude='**/.vscode' \
--exclude='deployment' \
--exclude='**/bin' \
--exclude='**/obj' \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
-C $${SOURCE_DIR} ./; \
mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
-C "$${package_temporary_dir}/jellyfin-$(VERSION); \
rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
-C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
rm -rf $${package_temporary_dir}; \
fi; \
rpmbuild -bs pkg-src/jellyfin.spec \
--define "_sourcedir $$PWD/pkg-src/" \
--define "_srcrpmdir $(outdir)"

1
.copr/Makefile Symbolic link
View File

@ -0,0 +1 @@
../fedora/Makefile

3
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,3 @@
# Joshua must review all changes to deployment and build.sh
deployment/* @joshuaboniface
build.sh @joshuaboniface

View File

@ -10,6 +10,19 @@ assignees: ''
**Describe the bug** **Describe the bug**
<!-- A clear and concise description of what the bug is. --> <!-- A clear and concise description of what the bug is. -->
**System (please complete the following information):**
- OS: [e.g. Debian, Windows]
- Virtualization: [e.g. Docker, KVM, LXC]
- Clients: [Browser, Android, Fire Stick, etc.]
- Browser: [e.g. Firefox 72, Chrome 80, Safari 13]
- Jellyfin Version: [e.g. 10.4.3, nightly 20191231]
- Playback: [Direct Play, Remux, Direct Stream, Transcode]
- Installed Plugins: [e.g. none, Fanart, Anime, etc.]
- Reverse Proxy: [e.g. none, nginx, apache, etc.]
- Base URL: [e.g. none, yes: /example]
- Networking: [e.g. Host, Bridge/NAT]
- Storage: [e.g. local, NFS, cloud]
**To Reproduce** **To Reproduce**
<!-- Steps to reproduce the behavior: --> <!-- Steps to reproduce the behavior: -->
1. Go to '...' 1. Go to '...'
@ -26,12 +39,5 @@ assignees: ''
**Screenshots** **Screenshots**
<!-- If applicable, add screenshots to help explain your problem. --> <!-- If applicable, add screenshots to help explain your problem. -->
**System (please complete the following information):**
- OS: [e.g. Docker, Debian, Windows]
- Browser: [e.g. Firefox, Chrome, Safari]
- Jellyfin Version: [e.g. 10.0.1]
- Installed Plugins: [e.g. none, Fanart, Anime, etc.]
- Reverse proxy: [e.g. no, nginx, apache, etc.]
**Additional context** **Additional context**
<!-- Add any other context about the problem here. --> <!-- Add any other context about the problem here. -->

View File

@ -0,0 +1,13 @@
---
name: Feature Request
about: Request a new feature
title: ''
labels: feature-request
assignees: ''
---
**PLEASE DO NOT OPEN FEATURE REQUEST ISSUES ON GITHUB**
**Feature requests should be opened on our dedicated [feature request](https://features.jellyfin.org/) hub so they can be appropriately discussed and prioritized.**
However, if you are willing to contribute to the project by adding a new feature yourself, then please ensure that you first review our [documentation](https://docs.jellyfin.org/general/contributing/development.html) on contributing code. Once you have reviewed the documentation, feel free to come back here and open an issue here outlining your proposed approach so that it can be documented, tracked, and discussed by other team members.

View File

@ -11,7 +11,10 @@ assignees: ''
<!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. --> <!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. -->
**Logs** **Logs**
<!-- Please paste any log message from during the playback issue, for example the ffmpeg command line can be very useful. --> <!-- Please paste any log messages from during the playback issue. -->
**FFmpeg Logs**
<!-- Please paste any FFmpeg logs if remuxing or transcoding appears to be part of the issue. -->
**Stats for Nerds Screenshots** **Stats for Nerds Screenshots**
<!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. --> <!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. -->
@ -29,4 +32,3 @@ assignees: ''
- Client: [e.g. Web/Browser, webOS, Android, Android TV, Electron] - Client: [e.g. Web/Browser, webOS, Android, Android TV, Electron]
- Browser (if Web client): [e.g. Firefox, Chrome, Safari] - Browser (if Web client): [e.g. Firefox, Chrome, Safari]
- Client and Browser Version: [e.g. 10.3.4 and 68.0] - Client and Browser Version: [e.g. 10.3.4 and 68.0]

20
.gitignore vendored
View File

@ -39,6 +39,7 @@ ProgramData*/
CorePlugins*/ CorePlugins*/
ProgramData-Server*/ ProgramData-Server*/
ProgramData-UI*/ ProgramData-UI*/
MediaBrowser.WebDashboard/jellyfin-web/**
################# #################
## Visual Studio ## Visual Studio
@ -244,14 +245,14 @@ pip-log.txt
######################### #########################
# Artifacts for debian-x64 # Artifacts for debian-x64
deployment/debian-package-x64/pkg-src/.debhelper/ debian/.debhelper/
deployment/debian-package-x64/pkg-src/*.debhelper debian/*.debhelper
deployment/debian-package-x64/pkg-src/debhelper-build-stamp debian/debhelper-build-stamp
deployment/debian-package-x64/pkg-src/files debian/files
deployment/debian-package-x64/pkg-src/jellyfin.substvars debian/jellyfin.substvars
deployment/debian-package-x64/pkg-src/jellyfin/ debian/jellyfin/
# Don't ignore the debian/bin folder # Don't ignore the debian/bin folder
!deployment/debian-package-x64/pkg-src/bin/ !debian/bin/
deployment/**/dist/ deployment/**/dist/
deployment/**/pkg-dist/ deployment/**/pkg-dist/
@ -271,3 +272,8 @@ dist
# BenchmarkDotNet artifacts # BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts BenchmarkDotNet.Artifacts
# Ignore web artifacts from native builds
web/
web-src.*
MediaBrowser.WebDashboard/jellyfin-web/

14
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"ms-dotnettools.csharp",
"editorconfig.editorconfig"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]
}

View File

@ -1,37 +1,135 @@
# Jellyfin Contributors # Jellyfin Contributors
- [JoshuaBoniface](https://github.com/joshuaboniface) - [97carmine](https://github.com/97carmine)
- [nvllsvm](https://github.com/nvllsvm) - [Abbe98](https://github.com/Abbe98)
- [JustAMan](https://github.com/JustAMan) - [agrenott](https://github.com/agrenott)
- [dcrdev](https://github.com/dcrdev) - [AndreCarvalho](https://github.com/AndreCarvalho)
- [EraYaN](https://github.com/EraYaN) - [anthonylavado](https://github.com/anthonylavado)
- [flemse](https://github.com/flemse) - [Artiume](https://github.com/Artiume)
- [AThomsen](https://github.com/AThomsen)
- [bilde2910](https://github.com/bilde2910)
- [bfayers](https://github.com/bfayers) - [bfayers](https://github.com/bfayers)
- [Bond_009](https://github.com/Bond-009) - [BnMcG](https://github.com/BnMcG)
- [AnthonyLavado](https://github.com/anthonylavado) - [Bond-009](https://github.com/Bond-009)
- [sparky8251](https://github.com/sparky8251) - [brianjmurrell](https://github.com/brianjmurrell)
- [LeoVerto](https://github.com/LeoVerto) - [bugfixin](https://github.com/bugfixin)
- [grafixeyehero](https://github.com/grafixeyehero) - [chaosinnovator](https://github.com/chaosinnovator)
- [ckcr4lyf](https://github.com/ckcr4lyf)
- [crankdoofus](https://github.com/crankdoofus)
- [crobibero](https://github.com/crobibero)
- [cromefire](https://github.com/cromefire)
- [cryptobank](https://github.com/cryptobank)
- [cvium](https://github.com/cvium) - [cvium](https://github.com/cvium)
- [wtayl0r](https://github.com/wtayl0r) - [dannymichel](https://github.com/dannymichel)
- [TtheCreator](https://github.com/Tthecreator) - [DaveChild](https://github.com/DaveChild)
- [Delgan](https://github.com/Delgan)
- [dcrdev](https://github.com/dcrdev)
- [dhartung](https://github.com/dhartung)
- [dinki](https://github.com/dinki)
- [dkanada](https://github.com/dkanada) - [dkanada](https://github.com/dkanada)
- [LogicalPhallacy](https://github.com/LogicalPhallacy/) - [dlahoti](https://github.com/dlahoti)
- [RazeLighter777](https://github.com/RazeLighter777) - [dmitrylyzo](https://github.com/dmitrylyzo)
- [WillWill56](https://github.com/WillWill56) - [DMouse10462](https://github.com/DMouse10462)
- [Liggy](https://github.com/Liggy) - [DrPandemic](https://github.com/DrPandemic)
- [fruhnow](https://github.com/fruhnow) - [EraYaN](https://github.com/EraYaN)
- [Lynxy](https://github.com/Lynxy) - [escabe](https://github.com/escabe)
- [excelite](https://github.com/excelite)
- [fasheng](https://github.com/fasheng) - [fasheng](https://github.com/fasheng)
- [ploughpuff](https://github.com/ploughpuff) - [ferferga](https://github.com/ferferga)
- [pjeanjean](https://github.com/pjeanjean)
- [DrPandemic](https://github.com/drpandemic)
- [joern-h](https://github.com/joern-h)
- [Khinenw](https://github.com/HelloWorld017)
- [fhriley](https://github.com/fhriley) - [fhriley](https://github.com/fhriley)
- [nevado](https://github.com/nevado) - [flemse](https://github.com/flemse)
- [Froghut](https://github.com/Froghut)
- [fruhnow](https://github.com/fruhnow)
- [geilername](https://github.com/geilername)
- [gnattu](https://github.com/gnattu)
- [grafixeyehero](https://github.com/grafixeyehero)
- [h1nk](https://github.com/h1nk)
- [hawken93](https://github.com/hawken93)
- [HelloWorld017](https://github.com/HelloWorld017)
- [jftuga](https://github.com/jftuga)
- [joern-h](https://github.com/joern-h)
- [joshuaboniface](https://github.com/joshuaboniface)
- [JustAMan](https://github.com/JustAMan)
- [justinfenn](https://github.com/justinfenn)
- [KerryRJ](https://github.com/KerryRJ)
- [Larvitar](https://github.com/Larvitar)
- [LeoVerto](https://github.com/LeoVerto)
- [Liggy](https://github.com/Liggy)
- [LogicalPhallacy](https://github.com/LogicalPhallacy)
- [loli10K](https://github.com/loli10K)
- [lostmypillow](https://github.com/lostmypillow)
- [Lynxy](https://github.com/Lynxy)
- [ManfredRichthofen](https://github.com/ManfredRichthofen)
- [Marenz](https://github.com/Marenz)
- [marius-luca-87](https://github.com/marius-luca-87)
- [mark-monteiro](https://github.com/mark-monteiro) - [mark-monteiro](https://github.com/mark-monteiro)
- [ullmie02](https://github.com/ullmie02) - [Matt07211](https://github.com/Matt07211)
- [mcarlton00](https://github.com/mcarlton00)
- [mitchfizz05](https://github.com/mitchfizz05)
- [MrTimscampi](https://github.com/MrTimscampi)
- [n8225](https://github.com/n8225)
- [Narfinger](https://github.com/Narfinger)
- [NathanPickard](https://github.com/NathanPickard)
- [neilsb](https://github.com/neilsb)
- [nevado](https://github.com/nevado)
- [Nickbert7](https://github.com/Nickbert7)
- [nvllsvm](https://github.com/nvllsvm)
- [nyanmisaka](https://github.com/nyanmisaka)
- [oddstr13](https://github.com/oddstr13)
- [petermcneil](https://github.com/petermcneil)
- [Phlogi](https://github.com/Phlogi)
- [pjeanjean](https://github.com/pjeanjean)
- [ploughpuff](https://github.com/ploughpuff)
- [pR0Ps](https://github.com/pR0Ps)
- [PrplHaz4](https://github.com/PrplHaz4)
- [RazeLighter777](https://github.com/RazeLighter777)
- [redSpoutnik](https://github.com/redSpoutnik)
- [ringmatter](https://github.com/ringmatter)
- [ryan-hartzell](https://github.com/ryan-hartzell)
- [s0urcelab](https://github.com/s0urcelab)
- [sachk](https://github.com/sachk)
- [sammyrc34](https://github.com/sammyrc34)
- [samuel9554](https://github.com/samuel9554)
- [scheidleon](https://github.com/scheidleon)
- [sebPomme](https://github.com/sebPomme)
- [SegiH](https://github.com/SegiH)
- [SenorSmartyPants](https://github.com/SenorSmartyPants)
- [shemanaev](https://github.com/shemanaev)
- [skaro13](https://github.com/skaro13)
- [sl1288](https://github.com/sl1288)
- [sorinyo2004](https://github.com/sorinyo2004)
- [sparky8251](https://github.com/sparky8251)
- [stanionascu](https://github.com/stanionascu)
- [stevehayles](https://github.com/stevehayles)
- [SuperSandro2000](https://github.com/SuperSandro2000)
- [tbraeutigam](https://github.com/tbraeutigam)
- [teacupx](https://github.com/teacupx)
- [Terror-Gene](https://github.com/Terror-Gene)
- [ThatNerdyPikachu](https://github.com/ThatNerdyPikachu)
- [ThibaultNocchi](https://github.com/ThibaultNocchi)
- [thornbill](https://github.com/thornbill)
- [ThreeFive-O](https://github.com/ThreeFive-O)
- [TrisMcC](https://github.com/TrisMcC)
- [trumblejoe](https://github.com/trumblejoe)
- [TtheCreator](https://github.com/TtheCreator)
- [twinkybot](https://github.com/twinkybot)
- [Ullmie02](https://github.com/Ullmie02)
- [Unhelpful](https://github.com/Unhelpful)
- [viaregio](https://github.com/viaregio)
- [vitorsemeano](https://github.com/vitorsemeano)
- [voodoos](https://github.com/voodoos)
- [whooo](https://github.com/whooo)
- [WiiPlayer2](https://github.com/WiiPlayer2)
- [WillWill56](https://github.com/WillWill56)
- [wtayl0r](https://github.com/wtayl0r)
- [Wuerfelbecher](https://github.com/Wuerfelbecher)
- [Wunax](https://github.com/Wunax)
- [WWWesten](https://github.com/WWWesten)
- [WX9yMOXWId](https://github.com/WX9yMOXWId)
- [xosdy](https://github.com/xosdy)
- [XVicarious](https://github.com/XVicarious)
- [YouKnowBlom](https://github.com/YouKnowBlom)
- [KristupasSavickas](https://github.com/KristupasSavickas)
# Emby Contributors # Emby Contributors

View File

@ -1,48 +1,60 @@
ARG DOTNET_VERSION=3.1 ARG DOTNET_VERSION=3.1
ARG FFMPEG_VERSION=latest
FROM node:alpine as web-builder FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl \ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \ && cd jellyfin-web-* \
&& yarn install \ && yarn install \
&& yarn build \
&& mv dist /dist && mv dist /dist
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder
WORKDIR /repo WORKDIR /repo
COPY . . COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" # because of changes in docker and systemd we need to not build in parallel at the moment
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
FROM debian:buster-slim FROM debian:buster-slim
COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
# http://stackoverflow.com/questions/48162574/ddg#49462622
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
COPY --from=builder /jellyfin /jellyfin COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web COPY --from=web-builder /dist /jellyfin/jellyfin-web
# Install dependencies: # Install dependencies:
# libfontconfig1: needed for Skia # mesa-va-drivers: needed for AMD VAAPI
# libgomp1: needed for ffmpeg
# libva-drm2: needed for ffmpeg
# mesa-va-drivers: needed for VAAPI
RUN apt-get update \ RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https \
&& wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
&& echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
&& apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \ && apt-get install --no-install-recommends --no-install-suggests -y \
libfontconfig1 libgomp1 libva-drm2 mesa-va-drivers openssl \ mesa-va-drivers \
&& apt-get clean autoclean \ jellyfin-ffmpeg \
&& apt-get autoremove \ openssl \
locales \
&& apt-get remove gnupg wget apt-transport-https -y \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \ && mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media \ && chmod 777 /cache /config /media \
&& ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
&& ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
EXPOSE 8096 EXPOSE 8096
VOLUME /cache /config /media VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \ ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \ "--datadir", "/config", \
"--cachedir", "/cache", \ "--cachedir", "/cache", \
"--ffmpeg", "/usr/local/bin/ffmpeg"] "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]

View File

@ -1,3 +1,5 @@
# DESIGNED FOR BUILDING ON AMD64 ONLY
#####################################
# Requires binfm_misc registration # Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register # https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=3.1 ARG DOTNET_VERSION=3.1
@ -5,11 +7,10 @@ ARG DOTNET_VERSION=3.1
FROM node:alpine as web-builder FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl \ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \ && cd jellyfin-web-* \
&& yarn install \ && yarn install \
&& yarn build \
&& mv dist /dist && mv dist /dist
@ -24,21 +25,53 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin"
FROM multiarch/qemu-user-static:x86_64-arm as qemu FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM debian:stretch-slim-arm32v7 FROM arm32v7/debian:buster-slim
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
# http://stackoverflow.com/questions/48162574/ddg#49462622
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
RUN apt-get update \ RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \
curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \
curl -s https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \
echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \
echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \
apt-get update && \
apt-get install --no-install-recommends --no-install-suggests -y \
jellyfin-ffmpeg \
libssl-dev \
libfontconfig1 \
libfreetype6 \
libomxil-bellagio0 \
libomxil-bellagio-bin \
libraspberrypi0 \
vainfo \
libva2 \
locales \
&& apt-get remove curl gnupg -y \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \ && mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media && chmod 777 /cache /config /media \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
COPY --from=builder /jellyfin /jellyfin COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
EXPOSE 8096 EXPOSE 8096
VOLUME /cache /config /media VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \ ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \ "--datadir", "/config", \
"--cachedir", "/cache", \ "--cachedir", "/cache", \
"--ffmpeg", "/usr/bin/ffmpeg"] "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]

View File

@ -1,3 +1,5 @@
# DESIGNED FOR BUILDING ON AMD64 ONLY
#####################################
# Requires binfm_misc registration # Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register # https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=3.1 ARG DOTNET_VERSION=3.1
@ -5,11 +7,10 @@ ARG DOTNET_VERSION=3.1
FROM node:alpine as web-builder FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl \ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \ && cd jellyfin-web-* \
&& yarn install \ && yarn install \
&& yarn build \
&& mv dist /dist && mv dist /dist
@ -22,19 +23,40 @@ 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:GenerateDocumentationFile=false;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 debian:stretch-slim-arm64v8 FROM arm64v8/debian:buster-slim
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive"
# http://stackoverflow.com/questions/48162574/ddg#49462622
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
RUN apt-get update \ RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ ffmpeg \
libssl-dev \
ca-certificates \
libfontconfig1 \
libfreetype6 \
libomxil-bellagio0 \
libomxil-bellagio-bin \
locales \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \ && mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media && chmod 777 /cache /config /media \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
COPY --from=builder /jellyfin /jellyfin COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
EXPOSE 8096 EXPOSE 8096
VOLUME /cache /config /media VOLUME /cache /config /media

View File

@ -1,4 +1,4 @@
using System; using System.Buffers.Binary;
using System.IO; using System.IO;
namespace DvdLib namespace DvdLib
@ -12,19 +12,12 @@ namespace DvdLib
public override ushort ReadUInt16() public override ushort ReadUInt16()
{ {
return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0); return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2));
} }
public override uint ReadUInt32() public override uint ReadUInt32()
{ {
return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0); return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4));
}
private byte[] ReadAndReverseBytes(int count)
{
byte[] val = base.ReadBytes(count);
Array.Reverse(val, 0, count);
return val;
} }
} }
} }

View File

@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{713F42B5-878E-499D-A878-E4C652B1D5E8}</ProjectGuid>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\SharedVersion.cs" /> <Compile Include="..\SharedVersion.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Model.IO;
namespace DvdLib.Ifo namespace DvdLib.Ifo
{ {
@ -13,13 +12,10 @@ namespace DvdLib.Ifo
private ushort _titleCount; private ushort _titleCount;
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>(); public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
private readonly IFileSystem _fileSystem; public Dvd(string path)
public Dvd(string path, IFileSystem fileSystem)
{ {
_fileSystem = fileSystem;
Titles = new List<Title>(); Titles = new List<Title>();
var allFiles = _fileSystem.GetFiles(path, true).ToList(); var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories);
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ?? var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase)); allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
@ -33,7 +29,7 @@ namespace DvdLib.Ifo
continue; continue;
} }
var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries); var nums = ifo.Name.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber)) if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
{ {
ReadVTS(ifoNumber, ifo.FullName); ReadVTS(ifoNumber, ifo.FullName);
@ -42,7 +38,7 @@ namespace DvdLib.Ifo
} }
else else
{ {
using (var vmgFs = _fileSystem.GetFileStream(vmgPath.FullName, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read)) using (var vmgFs = new FileStream(vmgPath.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
{ {
using (var vmgRead = new BigEndianBinaryReader(vmgFs)) using (var vmgRead = new BigEndianBinaryReader(vmgFs))
{ {
@ -76,7 +72,7 @@ namespace DvdLib.Ifo
} }
} }
private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles) private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> allFiles)
{ {
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum); var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
@ -95,7 +91,7 @@ namespace DvdLib.Ifo
{ {
VTSPaths[vtsNum] = vtsPath; VTSPaths[vtsNum] = vtsPath;
using (var vtsFs = _fileSystem.GetFileStream(vtsPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read)) using (var vtsFs = new FileStream(vtsPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{ {
using (var vtsRead = new BigEndianBinaryReader(vtsFs)) using (var vtsRead = new BigEndianBinaryReader(vtsFs))
{ {

View File

@ -1,4 +1,7 @@
#pragma warning disable CS1591
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -149,6 +152,7 @@ namespace Emby.Dlna.Api
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult<Stream>(new MemoryStream(bytes))); return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult<Stream>(new MemoryStream(bytes)));
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetContentDirectory request) public object Get(GetContentDirectory request)
{ {
var xml = ContentDirectory.GetServiceXml(); var xml = ContentDirectory.GetServiceXml();
@ -156,6 +160,7 @@ namespace Emby.Dlna.Api
return _resultFactory.GetResult(Request, xml, XMLContentType); return _resultFactory.GetResult(Request, xml, XMLContentType);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetMediaReceiverRegistrar request) public object Get(GetMediaReceiverRegistrar request)
{ {
var xml = MediaReceiverRegistrar.GetServiceXml(); var xml = MediaReceiverRegistrar.GetServiceXml();
@ -163,6 +168,7 @@ namespace Emby.Dlna.Api
return _resultFactory.GetResult(Request, xml, XMLContentType); return _resultFactory.GetResult(Request, xml, XMLContentType);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetConnnectionManager request) public object Get(GetConnnectionManager request)
{ {
var xml = ConnectionManager.GetServiceXml(); var xml = ConnectionManager.GetServiceXml();
@ -170,32 +176,32 @@ namespace Emby.Dlna.Api
return _resultFactory.GetResult(Request, xml, XMLContentType); return _resultFactory.GetResult(Request, xml, XMLContentType);
} }
public object Post(ProcessMediaReceiverRegistrarControlRequest request) public async Task<object> Post(ProcessMediaReceiverRegistrarControlRequest request)
{ {
var response = PostAsync(request.RequestStream, MediaReceiverRegistrar); var response = await PostAsync(request.RequestStream, MediaReceiverRegistrar).ConfigureAwait(false);
return _resultFactory.GetResult(Request, response.Xml, XMLContentType); return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
} }
public object Post(ProcessContentDirectoryControlRequest request) public async Task<object> Post(ProcessContentDirectoryControlRequest request)
{ {
var response = PostAsync(request.RequestStream, ContentDirectory); var response = await PostAsync(request.RequestStream, ContentDirectory).ConfigureAwait(false);
return _resultFactory.GetResult(Request, response.Xml, XMLContentType); return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
} }
public object Post(ProcessConnectionManagerControlRequest request) public async Task<object> Post(ProcessConnectionManagerControlRequest request)
{ {
var response = PostAsync(request.RequestStream, ConnectionManager); var response = await PostAsync(request.RequestStream, ConnectionManager).ConfigureAwait(false);
return _resultFactory.GetResult(Request, response.Xml, XMLContentType); return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
} }
private ControlResponse PostAsync(Stream requestStream, IUpnpService service) private Task<ControlResponse> PostAsync(Stream requestStream, IUpnpService service)
{ {
var id = GetPathValue(2).ToString(); var id = GetPathValue(2).ToString();
return service.ProcessControlRequest(new ControlRequest return service.ProcessControlRequestAsync(new ControlRequest
{ {
Headers = Request.Headers, Headers = Request.Headers,
InputXml = requestStream, InputXml = requestStream,
@ -311,31 +317,37 @@ namespace Emby.Dlna.Api
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream)); return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream));
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Subscribe(ProcessContentDirectoryEventRequest request) public object Subscribe(ProcessContentDirectoryEventRequest request)
{ {
return ProcessEventRequest(ContentDirectory); return ProcessEventRequest(ContentDirectory);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Subscribe(ProcessConnectionManagerEventRequest request) public object Subscribe(ProcessConnectionManagerEventRequest request)
{ {
return ProcessEventRequest(ConnectionManager); return ProcessEventRequest(ConnectionManager);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request) public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request)
{ {
return ProcessEventRequest(MediaReceiverRegistrar); return ProcessEventRequest(MediaReceiverRegistrar);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Unsubscribe(ProcessContentDirectoryEventRequest request) public object Unsubscribe(ProcessContentDirectoryEventRequest request)
{ {
return ProcessEventRequest(ContentDirectory); return ProcessEventRequest(ContentDirectory);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Unsubscribe(ProcessConnectionManagerEventRequest request) public object Unsubscribe(ProcessConnectionManagerEventRequest request)
{ {
return ProcessEventRequest(ConnectionManager); return ProcessEventRequest(ConnectionManager);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request) public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request)
{ {
return ProcessEventRequest(MediaReceiverRegistrar); return ProcessEventRequest(MediaReceiverRegistrar);

View File

@ -1,3 +1,6 @@
#pragma warning disable CS1591
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
@ -50,6 +53,7 @@ namespace Emby.Dlna.Api
_dlnaManager = dlnaManager; _dlnaManager = dlnaManager;
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetProfileInfos request) public object Get(GetProfileInfos request)
{ {
return _dlnaManager.GetProfileInfos().ToArray(); return _dlnaManager.GetProfileInfos().ToArray();
@ -60,6 +64,7 @@ namespace Emby.Dlna.Api
return _dlnaManager.GetProfile(request.Id); return _dlnaManager.GetProfile(request.Id);
} }
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetDefaultProfile request) public object Get(GetDefaultProfile request)
{ {
return _dlnaManager.GetDefaultProfile(); return _dlnaManager.GetDefaultProfile();

View File

@ -1,3 +1,4 @@
#pragma warning disable CS1591
namespace Emby.Dlna.Common namespace Emby.Dlna.Common
{ {

View File

@ -1,3 +1,6 @@
#pragma warning disable CS1591
using System.Globalization;
namespace Emby.Dlna.Common namespace Emby.Dlna.Common
{ {
@ -13,9 +16,14 @@ namespace Emby.Dlna.Common
public string Depth { get; set; } public string Depth { get; set; }
/// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return string.Format("{0}x{1}", Height, Width); return string.Format(
CultureInfo.InvariantCulture,
"{0}x{1}",
Height,
Width);
} }
} }
} }

View File

@ -1,3 +1,4 @@
#pragma warning disable CS1591
namespace Emby.Dlna.Common namespace Emby.Dlna.Common
{ {
@ -13,9 +14,8 @@ namespace Emby.Dlna.Common
public string EventSubUrl { get; set; } public string EventSubUrl { get; set; }
/// <inheritdoc />
public override string ToString() public override string ToString()
{ => ServiceId;
return string.Format("{0}", ServiceId);
}
} }
} }

View File

@ -1,21 +1,24 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
namespace Emby.Dlna.Common namespace Emby.Dlna.Common
{ {
public class ServiceAction public class ServiceAction
{ {
public string Name { get; set; }
public List<Argument> ArgumentList { get; set; }
public override string ToString()
{
return Name;
}
public ServiceAction() public ServiceAction()
{ {
ArgumentList = new List<Argument>(); ArgumentList = new List<Argument>();
} }
public string Name { get; set; }
public List<Argument> ArgumentList { get; set; }
/// <inheritdoc />
public override string ToString()
{
return Name;
}
} }
} }

View File

@ -1,9 +1,16 @@
#pragma warning disable CS1591
using System; using System;
namespace Emby.Dlna.Common namespace Emby.Dlna.Common
{ {
public class StateVariable public class StateVariable
{ {
public StateVariable()
{
AllowedValues = Array.Empty<string>();
}
public string Name { get; set; } public string Name { get; set; }
public string DataType { get; set; } public string DataType { get; set; }
@ -12,14 +19,8 @@ namespace Emby.Dlna.Common
public string[] AllowedValues { get; set; } public string[] AllowedValues { get; set; }
/// <inheritdoc />
public override string ToString() public override string ToString()
{ => Name;
return Name;
}
public StateVariable()
{
AllowedValues = Array.Empty<string>();
}
} }
} }

View File

@ -1,17 +1,9 @@
#pragma warning disable CS1591
namespace Emby.Dlna.Configuration namespace Emby.Dlna.Configuration
{ {
public class DlnaOptions public class DlnaOptions
{ {
public bool EnablePlayTo { get; set; }
public bool EnableServer { get; set; }
public bool EnableDebugLog { get; set; }
public bool BlastAliveMessages { get; set; }
public bool SendOnlyMatchedHost { get; set; }
public int ClientDiscoveryIntervalSeconds { get; set; }
public int BlastAliveMessageIntervalSeconds { get; set; }
public string DefaultUserId { get; set; }
public DlnaOptions() public DlnaOptions()
{ {
EnablePlayTo = true; EnablePlayTo = true;
@ -21,5 +13,21 @@ namespace Emby.Dlna.Configuration
ClientDiscoveryIntervalSeconds = 60; ClientDiscoveryIntervalSeconds = 60;
BlastAliveMessageIntervalSeconds = 1800; BlastAliveMessageIntervalSeconds = 1800;
} }
public bool EnablePlayTo { get; set; }
public bool EnableServer { get; set; }
public bool EnableDebugLog { get; set; }
public bool BlastAliveMessages { get; set; }
public bool SendOnlyMatchedHost { get; set; }
public int ClientDiscoveryIntervalSeconds { get; set; }
public int BlastAliveMessageIntervalSeconds { get; set; }
public string DefaultUserId { get; set; }
} }
} }

View File

@ -1,3 +1,6 @@
#nullable enable
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Configuration; using Emby.Dlna.Configuration;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;

View File

@ -1,3 +1,6 @@
#pragma warning disable CS1591
using System.Threading.Tasks;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -12,7 +15,11 @@ namespace Emby.Dlna.ConnectionManager
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient) public ConnectionManager(
IDlnaManager dlna,
IServerConfigurationManager config,
ILogger<ConnectionManager> logger,
IHttpClient httpClient)
: base(logger, httpClient) : base(logger, httpClient)
{ {
_dlna = dlna; _dlna = dlna;
@ -20,17 +27,19 @@ namespace Emby.Dlna.ConnectionManager
_logger = logger; _logger = logger;
} }
/// <inheritdoc />
public string GetServiceXml() public string GetServiceXml()
{ {
return new ConnectionManagerXmlBuilder().GetXml(); return new ConnectionManagerXmlBuilder().GetXml();
} }
public ControlResponse ProcessControlRequest(ControlRequest request) /// <inheritdoc />
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{ {
var profile = _dlna.GetProfile(request.Headers) ?? var profile = _dlna.GetProfile(request.Headers) ??
_dlna.GetDefaultProfile(); _dlna.GetDefaultProfile();
return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request); return new ControlHandler(_config, _logger, profile).ProcessControlRequestAsync(request);
} }
} }
} }

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using Emby.Dlna.Service; using Emby.Dlna.Service;

View File

@ -1,5 +1,8 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -12,29 +15,28 @@ namespace Emby.Dlna.ConnectionManager
{ {
private readonly DeviceProfile _profile; private readonly DeviceProfile _profile;
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
{
if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
{
return HandleGetProtocolInfo();
}
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
private IEnumerable<KeyValuePair<string, string>> HandleGetProtocolInfo()
{
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "Source", _profile.ProtocolInfo },
{ "Sink", "" }
};
}
public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile) public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
: base(config, logger) : base(config, logger)
{ {
_profile = profile; _profile = profile;
} }
/// <inheritdoc />
protected override void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter)
{
if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
{
HandleGetProtocolInfo(xmlWriter);
return;
}
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
private void HandleGetProtocolInfo(XmlWriter xmlWriter)
{
xmlWriter.WriteElementString("Source", _profile.ProtocolInfo);
xmlWriter.WriteElementString("Sink", string.Empty);
}
} }
} }

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;

View File

@ -1,4 +1,7 @@
#pragma warning disable CS1591
using System; using System;
using System.Threading.Tasks;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -34,7 +37,7 @@ namespace Emby.Dlna.ContentDirectory
ILibraryManager libraryManager, ILibraryManager libraryManager,
IServerConfigurationManager config, IServerConfigurationManager config,
IUserManager userManager, IUserManager userManager,
ILogger logger, ILogger<ContentDirectory> logger,
IHttpClient httpClient, IHttpClient httpClient,
ILocalizationManager localization, ILocalizationManager localization,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
@ -66,12 +69,14 @@ namespace Emby.Dlna.ContentDirectory
} }
} }
/// <inheritdoc />
public string GetServiceXml() public string GetServiceXml()
{ {
return new ContentDirectoryXmlBuilder().GetXml(); return new ContentDirectoryXmlBuilder().GetXml();
} }
public ControlResponse ProcessControlRequest(ControlRequest request) /// <inheritdoc />
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{ {
var profile = _dlna.GetProfile(request.Headers) ?? var profile = _dlna.GetProfile(request.Headers) ??
_dlna.GetDefaultProfile(); _dlna.GetDefaultProfile();
@ -96,7 +101,7 @@ namespace Emby.Dlna.ContentDirectory
_userViewManager, _userViewManager,
_mediaEncoder, _mediaEncoder,
_tvSeriesManager) _tvSeriesManager)
.ProcessControlRequest(request); .ProcessControlRequestAsync(request);
} }
private User GetUser(DeviceProfile profile) private User GetUser(DeviceProfile profile)

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using Emby.Dlna.Service; using Emby.Dlna.Service;

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -44,7 +46,6 @@ namespace Emby.Dlna.ContentDirectory
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/"; private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private readonly int _systemUpdateId; private readonly int _systemUpdateId;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly DidlBuilder _didlBuilder; private readonly DidlBuilder _didlBuilder;
@ -58,7 +59,8 @@ namespace Emby.Dlna.ContentDirectory
string accessToken, string accessToken,
IImageProcessor imageProcessor, IImageProcessor imageProcessor,
IUserDataManager userDataManager, IUserDataManager userDataManager,
User user, int systemUpdateId, User user,
int systemUpdateId,
IServerConfigurationManager config, IServerConfigurationManager config,
ILocalizationManager localization, ILocalizationManager localization,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
@ -76,117 +78,143 @@ namespace Emby.Dlna.ContentDirectory
_profile = profile; _profile = profile;
_config = config; _config = config;
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, mediaEncoder); _didlBuilder = new DidlBuilder(
profile,
user,
imageProcessor,
serverAddress,
accessToken,
userDataManager,
localization,
mediaSourceManager,
Logger,
mediaEncoder,
libraryManager);
} }
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams) /// <inheritdoc />
protected override void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter)
{ {
var deviceId = "test"; const string DeviceId = "test";
var user = _user;
if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase)) if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase))
return HandleGetSearchCapabilities(); {
HandleGetSearchCapabilities(xmlWriter);
return;
}
if (string.Equals(methodName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase)) if (string.Equals(methodName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase))
return HandleGetSortCapabilities(); {
HandleGetSortCapabilities(xmlWriter);
return;
}
if (string.Equals(methodName, "GetSortExtensionCapabilities", StringComparison.OrdinalIgnoreCase)) if (string.Equals(methodName, "GetSortExtensionCapabilities", StringComparison.OrdinalIgnoreCase))
return HandleGetSortExtensionCapabilities(); {
HandleGetSortExtensionCapabilities(xmlWriter);
return;
}
if (string.Equals(methodName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase)) if (string.Equals(methodName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase))
return HandleGetSystemUpdateID(); {
HandleGetSystemUpdateID(xmlWriter);
return;
}
if (string.Equals(methodName, "Browse", StringComparison.OrdinalIgnoreCase)) if (string.Equals(methodName, "Browse", StringComparison.OrdinalIgnoreCase))
return HandleBrowse(methodParams, user, deviceId); {
HandleBrowse(xmlWriter, methodParams, DeviceId);
return;
}
if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase)) if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase))
return HandleXGetFeatureList(); {
HandleXGetFeatureList(xmlWriter);
return;
}
if (string.Equals(methodName, "GetFeatureList", StringComparison.OrdinalIgnoreCase)) if (string.Equals(methodName, "GetFeatureList", StringComparison.OrdinalIgnoreCase))
return HandleGetFeatureList(); {
HandleGetFeatureList(xmlWriter);
return;
}
if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase)) if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
return HandleXSetBookmark(methodParams, user); {
HandleXSetBookmark(methodParams);
return;
}
if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase)) if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase))
return HandleSearch(methodParams, user, deviceId); {
HandleSearch(xmlWriter, methodParams, DeviceId);
return;
}
if (string.Equals(methodName, "X_BrowseByLetter", StringComparison.OrdinalIgnoreCase)) if (string.Equals(methodName, "X_BrowseByLetter", StringComparison.OrdinalIgnoreCase))
return HandleX_BrowseByLetter(methodParams, user, deviceId); {
HandleXBrowseByLetter(xmlWriter, methodParams, DeviceId);
return;
}
throw new ResourceNotFoundException("Unexpected control request name: " + methodName); throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
} }
private IEnumerable<KeyValuePair<string, string>> HandleXSetBookmark(IDictionary<string, string> sparams, User user) private void HandleXSetBookmark(IDictionary<string, string> sparams)
{ {
var id = sparams["ObjectID"]; var id = sparams["ObjectID"];
var serverItem = GetItemFromObjectId(id, user); var serverItem = GetItemFromObjectId(id);
var item = serverItem.Item; var item = serverItem.Item;
var newbookmark = int.Parse(sparams["PosSecond"], _usCulture); var newbookmark = int.Parse(sparams["PosSecond"], CultureInfo.InvariantCulture);
var userdata = _userDataManager.GetUserData(user, item); var userdata = _userDataManager.GetUserData(_user, item);
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);
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
} }
private IEnumerable<KeyValuePair<string, string>> HandleGetSearchCapabilities() private void HandleGetSearchCapabilities(XmlWriter xmlWriter)
{ {
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) xmlWriter.WriteElementString(
{ "SearchCaps",
{ "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" } "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords");
};
} }
private IEnumerable<KeyValuePair<string, string>> HandleGetSortCapabilities() private void HandleGetSortCapabilities(XmlWriter xmlWriter)
{ {
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) xmlWriter.WriteElementString(
{ "SortCaps",
{ "SortCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" } "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating");
};
} }
private IEnumerable<KeyValuePair<string, string>> HandleGetSortExtensionCapabilities() private void HandleGetSortExtensionCapabilities(XmlWriter xmlWriter)
{ {
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) xmlWriter.WriteElementString(
{ "SortExtensionCaps",
{ "SortExtensionCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" } "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating");
};
} }
private IEnumerable<KeyValuePair<string, string>> HandleGetSystemUpdateID() private void HandleGetSystemUpdateID(XmlWriter xmlWriter)
{ {
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); xmlWriter.WriteElementString("Id", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
headers.Add("Id", _systemUpdateId.ToString(_usCulture));
return headers;
} }
private IEnumerable<KeyValuePair<string, string>> HandleGetFeatureList() private void HandleGetFeatureList(XmlWriter xmlWriter)
{ {
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) xmlWriter.WriteElementString("FeatureList", WriteFeatureListXml());
{
{ "FeatureList", GetFeatureListXml() }
};
} }
private IEnumerable<KeyValuePair<string, string>> HandleXGetFeatureList() private void HandleXGetFeatureList(XmlWriter xmlWriter)
{ => HandleGetFeatureList(xmlWriter);
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "FeatureList", GetFeatureListXml() }
};
}
private string GetFeatureListXml() private string WriteFeatureListXml()
{ {
// TODO: clean this up
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
@ -213,7 +241,7 @@ namespace Emby.Dlna.ContentDirectory
return defaultValue; return defaultValue;
} }
private IEnumerable<KeyValuePair<string, string>> HandleBrowse(IDictionary<string, string> sparams, User user, string deviceId) private void HandleBrowse(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
{ {
var id = sparams["ObjectID"]; var id = sparams["ObjectID"];
var flag = sparams["BrowseFlag"]; var flag = sparams["BrowseFlag"];
@ -237,101 +265,95 @@ namespace Emby.Dlna.ContentDirectory
start = startVal; start = startVal;
} }
var settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
CloseOutput = false,
OmitXmlDeclaration = true,
ConformanceLevel = ConformanceLevel.Fragment
};
StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
int totalCount; int totalCount;
var dlnaOptions = _config.GetDlnaConfiguration(); using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
using (var writer = XmlWriter.Create(builder, settings))
{ {
//writer.WriteStartDocument(); var settings = new XmlWriterSettings()
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
//didl.SetAttribute("xmlns:sec", NS_SEC);
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
var serverItem = GetItemFromObjectId(id, user);
var item = serverItem.Item;
if (string.Equals(flag, "BrowseMetadata"))
{ {
totalCount = 1; Encoding = Encoding.UTF8,
CloseOutput = false,
OmitXmlDeclaration = true,
ConformanceLevel = ConformanceLevel.Fragment
};
if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue) using (var writer = XmlWriter.Create(builder, settings))
{
var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount);
_didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
}
else
{
_didlBuilder.WriteItemElement(dlnaOptions, writer, item, user, null, null, deviceId, filter);
}
provided++;
}
else
{ {
var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount); writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
totalCount = childrenResult.TotalRecordCount;
provided = childrenResult.Items.Count; writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
foreach (var i in childrenResult.Items) DidlBuilder.WriteXmlRootAttributes(_profile, writer);
var serverItem = GetItemFromObjectId(id);
var item = serverItem.Item;
if (string.Equals(flag, "BrowseMetadata", StringComparison.Ordinal))
{ {
var childItem = i.Item; totalCount = 1;
var displayStubType = i.StubType;
if (childItem.IsDisplayedAsFolder || displayStubType.HasValue) if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
{ {
var childCount = (GetUserItems(childItem, displayStubType, user, sortCriteria, null, 0)) var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);
.TotalRecordCount;
_didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter); _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
} }
else else
{ {
_didlBuilder.WriteItemElement(dlnaOptions, writer, childItem, user, item, serverItem.StubType, deviceId, filter); var dlnaOptions = _config.GetDlnaConfiguration();
_didlBuilder.WriteItemElement(writer, item, _user, null, null, deviceId, filter);
}
provided++;
}
else
{
var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);
totalCount = childrenResult.TotalRecordCount;
provided = childrenResult.Items.Count;
var dlnaOptions = _config.GetDlnaConfiguration();
foreach (var i in childrenResult.Items)
{
var childItem = i.Item;
var displayStubType = i.StubType;
if (childItem.IsDisplayedAsFolder || displayStubType.HasValue)
{
var childCount = GetUserItems(childItem, displayStubType, _user, sortCriteria, null, 0)
.TotalRecordCount;
_didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter);
}
else
{
_didlBuilder.WriteItemElement(writer, childItem, _user, item, serverItem.StubType, deviceId, filter);
}
} }
} }
writer.WriteFullEndElement();
} }
writer.WriteFullEndElement(); xmlWriter.WriteElementString("Result", builder.ToString());
//writer.WriteEndDocument();
} }
var resXML = builder.ToString(); xmlWriter.WriteElementString("NumberReturned", provided.ToString(CultureInfo.InvariantCulture));
xmlWriter.WriteElementString("TotalMatches", totalCount.ToString(CultureInfo.InvariantCulture));
return new[] xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
{
new KeyValuePair<string,string>("Result", resXML),
new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture))
};
} }
private IEnumerable<KeyValuePair<string, string>> HandleX_BrowseByLetter(IDictionary<string, string> sparams, User user, string deviceId) private void HandleXBrowseByLetter(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
{ {
// TODO: Implement this method // TODO: Implement this method
return HandleSearch(sparams, user, deviceId); HandleSearch(xmlWriter, sparams, deviceId);
} }
private IEnumerable<KeyValuePair<string, string>> HandleSearch(IDictionary<string, string> sparams, User user, 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", ""));
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", "")); var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
@ -354,99 +376,86 @@ namespace Emby.Dlna.ContentDirectory
start = startVal; start = startVal;
} }
var settings = new XmlWriterSettings QueryResult<BaseItem> childrenResult;
using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
{ {
Encoding = Encoding.UTF8, var settings = new XmlWriterSettings()
CloseOutput = false,
OmitXmlDeclaration = true,
ConformanceLevel = ConformanceLevel.Fragment
};
StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
int totalCount = 0;
int provided = 0;
using (var writer = XmlWriter.Create(builder, settings))
{
//writer.WriteStartDocument();
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
//didl.SetAttribute("xmlns:sec", NS_SEC);
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
var serverItem = GetItemFromObjectId(sparams["ContainerID"], user);
var item = serverItem.Item;
var childrenResult = (GetChildrenSorted(item, user, searchCriteria, sortCriteria, start, requestedCount));
totalCount = childrenResult.TotalRecordCount;
provided = childrenResult.Items.Count;
var dlnaOptions = _config.GetDlnaConfiguration();
foreach (var i in childrenResult.Items)
{ {
if (i.IsDisplayedAsFolder) Encoding = Encoding.UTF8,
{ CloseOutput = false,
var childCount = (GetChildrenSorted(i, user, searchCriteria, sortCriteria, null, 0)) OmitXmlDeclaration = true,
.TotalRecordCount; ConformanceLevel = ConformanceLevel.Fragment
};
_didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter); using (var writer = XmlWriter.Create(builder, settings))
} {
else writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
var serverItem = GetItemFromObjectId(sparams["ContainerID"]);
var item = serverItem.Item;
childrenResult = GetChildrenSorted(item, _user, searchCriteria, sortCriteria, start, requestedCount);
var dlnaOptions = _config.GetDlnaConfiguration();
foreach (var i in childrenResult.Items)
{ {
_didlBuilder.WriteItemElement(dlnaOptions, writer, i, user, item, serverItem.StubType, deviceId, filter); if (i.IsDisplayedAsFolder)
{
var childCount = GetChildrenSorted(i, _user, searchCriteria, sortCriteria, null, 0)
.TotalRecordCount;
_didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter);
}
else
{
_didlBuilder.WriteItemElement(writer, i, _user, item, serverItem.StubType, deviceId, filter);
}
} }
writer.WriteFullEndElement();
} }
writer.WriteFullEndElement(); xmlWriter.WriteElementString("Result", builder.ToString());
//writer.WriteEndDocument();
} }
var resXML = builder.ToString(); xmlWriter.WriteElementString("NumberReturned", childrenResult.Items.Count.ToString(CultureInfo.InvariantCulture));
xmlWriter.WriteElementString("TotalMatches", childrenResult.TotalRecordCount.ToString(CultureInfo.InvariantCulture));
return new List<KeyValuePair<string, string>> xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
{
new KeyValuePair<string,string>("Result", resXML),
new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture))
};
} }
private QueryResult<BaseItem> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) private QueryResult<BaseItem> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit)
{ {
var folder = (Folder)item; var folder = (Folder)item;
var sortOrders = new List<(string, SortOrder)>(); var sortOrders = folder.IsPreSorted
if (!folder.IsPreSorted) ? Array.Empty<(string, SortOrder)>()
{ : new[] { (ItemSortBy.SortName, sort.SortOrder) };
sortOrders.Add((ItemSortBy.SortName, sort.SortOrder));
}
var mediaTypes = new List<string>(); string[] mediaTypes = Array.Empty<string>();
bool? isFolder = null; bool? isFolder = null;
if (search.SearchType == SearchType.Audio) if (search.SearchType == SearchType.Audio)
{ {
mediaTypes.Add(MediaType.Audio); mediaTypes = new[] { MediaType.Audio };
isFolder = false; isFolder = false;
} }
else if (search.SearchType == SearchType.Video) else if (search.SearchType == SearchType.Video)
{ {
mediaTypes.Add(MediaType.Video); mediaTypes = new[] { MediaType.Video };
isFolder = false; isFolder = false;
} }
else if (search.SearchType == SearchType.Image) else if (search.SearchType == SearchType.Image)
{ {
mediaTypes.Add(MediaType.Photo); mediaTypes = new[] { MediaType.Photo };
isFolder = false; isFolder = false;
} }
else if (search.SearchType == SearchType.Playlist) else if (search.SearchType == SearchType.Playlist)
@ -470,7 +479,7 @@ namespace Emby.Dlna.ContentDirectory
IsMissing = false, IsMissing = false,
ExcludeItemTypes = new[] { typeof(Book).Name }, ExcludeItemTypes = new[] { typeof(Book).Name },
IsFolder = isFolder, IsFolder = isFolder,
MediaTypes = mediaTypes.ToArray(), MediaTypes = mediaTypes,
DtoOptions = GetDtoOptions() DtoOptions = GetDtoOptions()
}); });
} }
@ -514,11 +523,11 @@ namespace Emby.Dlna.ContentDirectory
} }
else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{ {
return GetFolders(item, user, stubType, sort, startIndex, limit); return GetFolders(user, startIndex, limit);
} }
else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{ {
return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit); return GetLiveTvChannels(user, sort, startIndex, limit);
} }
} }
@ -549,7 +558,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(queryResult); return ToResult(queryResult);
} }
private QueryResult<ServerItem> GetLiveTvChannels(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) private QueryResult<ServerItem> GetLiveTvChannels(User user, SortCriteria sort, int? startIndex, int? limit)
{ {
var query = new InternalItemsQuery(user) var query = new InternalItemsQuery(user)
{ {
@ -581,7 +590,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.Playlists) if (stubType.HasValue && stubType.Value == StubType.Playlists)
{ {
return GetMusicPlaylists(item, user, query); return GetMusicPlaylists(user, query);
} }
if (stubType.HasValue && stubType.Value == StubType.Albums) if (stubType.HasValue && stubType.Value == StubType.Albums)
@ -709,7 +718,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.Collections) if (stubType.HasValue && stubType.Value == StubType.Collections)
{ {
return GetMovieCollections(item, user, query); return GetMovieCollections(user, query);
} }
if (stubType.HasValue && stubType.Value == StubType.Favorites) if (stubType.HasValue && stubType.Value == StubType.Favorites)
@ -722,46 +731,42 @@ namespace Emby.Dlna.ContentDirectory
return GetGenres(item, user, query); return GetGenres(item, user, query);
} }
var list = new List<ServerItem>(); var array = new ServerItem[]
list.Add(new ServerItem(item)
{ {
StubType = StubType.ContinueWatching new ServerItem(item)
}); {
StubType = StubType.ContinueWatching
list.Add(new ServerItem(item) },
{ new ServerItem(item)
StubType = StubType.Latest {
}); StubType = StubType.Latest
},
list.Add(new ServerItem(item) new ServerItem(item)
{ {
StubType = StubType.Movies StubType = StubType.Movies
}); },
new ServerItem(item)
list.Add(new ServerItem(item) {
{ StubType = StubType.Collections
StubType = StubType.Collections },
}); new ServerItem(item)
{
list.Add(new ServerItem(item) StubType = StubType.Favorites
{ },
StubType = StubType.Favorites new ServerItem(item)
}); {
StubType = StubType.Genres
list.Add(new ServerItem(item) }
{ };
StubType = StubType.Genres
});
return new QueryResult<ServerItem> return new QueryResult<ServerItem>
{ {
Items = list, Items = array,
TotalRecordCount = list.Count TotalRecordCount = array.Length
}; };
} }
private QueryResult<ServerItem> GetFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) private QueryResult<ServerItem> GetFolders(User user, int? startIndex, int? limit)
{ {
var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true) var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true)
.OrderBy(i => i.SortName) .OrderBy(i => i.SortName)
@ -771,11 +776,11 @@ namespace Emby.Dlna.ContentDirectory
}) })
.ToArray(); .ToArray();
return new QueryResult<ServerItem> return ApplyPaging(new QueryResult<ServerItem>
{ {
Items = folders, Items = folders,
TotalRecordCount = folders.Length TotalRecordCount = folders.Length
}; }, 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)
@ -794,7 +799,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.NextUp) if (stubType.HasValue && stubType.Value == StubType.NextUp)
{ {
return GetNextUp(item, user, query); return GetNextUp(item, query);
} }
if (stubType.HasValue && stubType.Value == StubType.Latest) if (stubType.HasValue && stubType.Value == StubType.Latest)
@ -912,7 +917,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result); return ToResult(result);
} }
private QueryResult<ServerItem> GetMovieCollections(BaseItem parent, User user, InternalItemsQuery query) private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query)
{ {
query.Recursive = true; query.Recursive = true;
//query.Parent = parent; //query.Parent = parent;
@ -1107,7 +1112,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result); return ToResult(result);
} }
private QueryResult<ServerItem> GetMusicPlaylists(BaseItem parent, User user, InternalItemsQuery query) private QueryResult<ServerItem> GetMusicPlaylists(User user, InternalItemsQuery query)
{ {
query.Parent = null; query.Parent = null;
query.IncludeItemTypes = new[] { typeof(Playlist).Name }; query.IncludeItemTypes = new[] { typeof(Playlist).Name };
@ -1136,7 +1141,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(items); return ToResult(items);
} }
private QueryResult<ServerItem> GetNextUp(BaseItem parent, User user, InternalItemsQuery query) private QueryResult<ServerItem> GetNextUp(BaseItem parent, InternalItemsQuery query)
{ {
query.OrderBy = Array.Empty<(string, SortOrder)>(); query.OrderBy = Array.Empty<(string, SortOrder)>();
@ -1291,24 +1296,24 @@ namespace Emby.Dlna.ContentDirectory
return result; return result;
} }
private ServerItem GetItemFromObjectId(string id, User user) private ServerItem GetItemFromObjectId(string id)
{ {
return DidlBuilder.IsIdRoot(id) return DidlBuilder.IsIdRoot(id)
? new ServerItem(_libraryManager.GetUserRootFolder()) ? new ServerItem(_libraryManager.GetUserRootFolder())
: ParseItemId(id, user); : ParseItemId(id);
} }
private ServerItem ParseItemId(string id, User user) private ServerItem ParseItemId(string id)
{ {
StubType? stubType = null; StubType? stubType = null;
// After using PlayTo, MediaMonkey sends a request to the server trying to get item info // After using PlayTo, MediaMonkey sends a request to the server trying to get item info
const string paramsSrch = "Params="; const string ParamsSrch = "Params=";
var paramsIndex = id.IndexOf(paramsSrch, StringComparison.OrdinalIgnoreCase); var paramsIndex = id.IndexOf(ParamsSrch, StringComparison.OrdinalIgnoreCase);
if (paramsIndex != -1) if (paramsIndex != -1)
{ {
id = id.Substring(paramsIndex + paramsSrch.Length); id = id.Substring(paramsIndex + ParamsSrch.Length);
var parts = id.Split(';'); var parts = id.Split(';');
id = parts[23]; id = parts[23];
@ -1336,7 +1341,7 @@ 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());
} }

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.IO; using System.IO;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;

View File

@ -1,18 +1,20 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
namespace Emby.Dlna namespace Emby.Dlna
{ {
public class ControlResponse public class ControlResponse
{ {
public ControlResponse()
{
Headers = new Dictionary<string, string>();
}
public IDictionary<string, string> Headers { get; set; } public IDictionary<string, string> Headers { get; set; }
public string Xml { get; set; } public string Xml { get; set; }
public bool IsSuccessful { get; set; } public bool IsSuccessful { get; set; }
public ControlResponse()
{
Headers = new Dictionary<string, string>();
}
} }
} }

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -18,7 +20,6 @@ using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -44,6 +45,7 @@ namespace Emby.Dlna.Didl
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ILibraryManager _libraryManager;
public DidlBuilder( public DidlBuilder(
DeviceProfile profile, DeviceProfile profile,
@ -55,7 +57,8 @@ namespace Emby.Dlna.Didl
ILocalizationManager localization, ILocalizationManager localization,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
ILogger logger, ILogger logger,
IMediaEncoder mediaEncoder) IMediaEncoder mediaEncoder,
ILibraryManager libraryManager)
{ {
_profile = profile; _profile = profile;
_user = user; _user = user;
@ -67,6 +70,7 @@ namespace Emby.Dlna.Didl
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_logger = logger; _logger = logger;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_libraryManager = libraryManager;
} }
public static string NormalizeDlnaMediaUrl(string url) public static string NormalizeDlnaMediaUrl(string url)
@ -74,7 +78,7 @@ namespace Emby.Dlna.Didl
return url + "&dlnaheaders=true"; return url + "&dlnaheaders=true";
} }
public string GetItemDidl(DlnaOptions options, BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo) public string GetItemDidl(BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
{ {
var settings = new XmlWriterSettings var settings = new XmlWriterSettings
{ {
@ -99,7 +103,7 @@ namespace Emby.Dlna.Didl
WriteXmlRootAttributes(_profile, writer); WriteXmlRootAttributes(_profile, writer);
WriteItemElement(options, writer, item, user, context, null, deviceId, filter, streamInfo); WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo);
writer.WriteFullEndElement(); writer.WriteFullEndElement();
//writer.WriteEndDocument(); //writer.WriteEndDocument();
@ -126,7 +130,6 @@ namespace Emby.Dlna.Didl
} }
public void WriteItemElement( public void WriteItemElement(
DlnaOptions options,
XmlWriter writer, XmlWriter writer,
BaseItem item, BaseItem item,
User user, User user,
@ -163,25 +166,23 @@ namespace Emby.Dlna.Didl
// refID? // refID?
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false); // storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
var hasMediaSources = item as IHasMediaSources; if (item is IHasMediaSources)
if (hasMediaSources != null)
{ {
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{ {
AddAudioResource(options, writer, item, deviceId, filter, streamInfo); AddAudioResource(writer, item, deviceId, filter, streamInfo);
} }
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{ {
AddVideoResource(options, writer, item, deviceId, filter, streamInfo); AddVideoResource(writer, item, deviceId, filter, streamInfo);
} }
} }
AddCover(item, context, null, writer); AddCover(item, null, writer);
writer.WriteFullEndElement(); writer.WriteFullEndElement();
} }
private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null) private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
{ {
if (streamInfo == null) if (streamInfo == null)
{ {
@ -225,7 +226,7 @@ namespace Emby.Dlna.Didl
foreach (var contentFeature in contentFeatureList) foreach (var contentFeature in contentFeatureList)
{ {
AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo); AddVideoResource(writer, filter, contentFeature, streamInfo);
} }
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken); var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken);
@ -282,7 +283,10 @@ namespace Emby.Dlna.Didl
else else
{ {
writer.WriteStartElement(string.Empty, "res", NS_DIDL); writer.WriteStartElement(string.Empty, "res", NS_DIDL);
var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant()); var protocolInfo = string.Format(
CultureInfo.InvariantCulture,
"http-get:*:text/{0}:*",
info.Format.ToLowerInvariant());
writer.WriteAttributeString("protocolInfo", protocolInfo); writer.WriteAttributeString("protocolInfo", protocolInfo);
writer.WriteString(info.Url); writer.WriteString(info.Url);
@ -292,7 +296,7 @@ namespace Emby.Dlna.Didl
return true; return true;
} }
private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, 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", NS_DIDL);
@ -334,7 +338,13 @@ namespace Emby.Dlna.Didl
{ {
if (targetWidth.HasValue && targetHeight.HasValue) if (targetWidth.HasValue && targetHeight.HasValue)
{ {
writer.WriteAttributeString("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value)); writer.WriteAttributeString(
"resolution",
string.Format(
CultureInfo.InvariantCulture,
"{0}x{1}",
targetWidth.Value,
targetHeight.Value));
} }
} }
@ -368,17 +378,19 @@ namespace Emby.Dlna.Didl
streamInfo.TargetVideoCodecTag, streamInfo.TargetVideoCodecTag,
streamInfo.IsTargetAVC); streamInfo.IsTargetAVC);
var filename = url.Substring(0, url.IndexOf('?')); var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType) var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename) ? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType; : mediaProfile.MimeType;
writer.WriteAttributeString("protocolInfo", string.Format( writer.WriteAttributeString(
"http-get:*:{0}:{1}", "protocolInfo",
mimeType, string.Format(
contentFeatures CultureInfo.InvariantCulture,
)); "http-get:*:{0}:{1}",
mimeType,
contentFeatures));
writer.WriteString(url); writer.WriteString(url);
@ -391,54 +403,122 @@ namespace Emby.Dlna.Didl
{ {
switch (itemStubType.Value) switch (itemStubType.Value)
{ {
case StubType.Latest: return _localization.GetLocalizedString("Latest"); case StubType.Latest: return _localization.GetLocalizedString("Latest");
case StubType.Playlists: return _localization.GetLocalizedString("Playlists"); case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists"); case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
case StubType.Albums: return _localization.GetLocalizedString("Albums"); case StubType.Albums: return _localization.GetLocalizedString("Albums");
case StubType.Artists: return _localization.GetLocalizedString("Artists"); case StubType.Artists: return _localization.GetLocalizedString("Artists");
case StubType.Songs: return _localization.GetLocalizedString("Songs"); case StubType.Songs: return _localization.GetLocalizedString("Songs");
case StubType.Genres: return _localization.GetLocalizedString("Genres"); case StubType.Genres: return _localization.GetLocalizedString("Genres");
case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums"); case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists"); case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs"); case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching"); case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching");
case StubType.Movies: return _localization.GetLocalizedString("Movies"); case StubType.Movies: return _localization.GetLocalizedString("Movies");
case StubType.Collections: return _localization.GetLocalizedString("Collections"); case StubType.Collections: return _localization.GetLocalizedString("Collections");
case StubType.Favorites: return _localization.GetLocalizedString("Favorites"); case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp"); case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
case StubType.Series: return _localization.GetLocalizedString("Shows"); case StubType.Series: return _localization.GetLocalizedString("Shows");
default: break; default: break;
} }
} }
if (item is Episode episode && context is Season season) return item is Episode episode
? GetEpisodeDisplayName(episode, context)
: item.Name;
}
/// <summary>
/// Gets episode display name appropriate for the given context.
/// </summary>
/// <remarks>
/// If context is a season, this will return a string containing just episode number and name.
/// Otherwise the result will include series nams and season number.
/// </remarks>
/// <param name="episode">The episode.</param>
/// <param name="context">Current context.</param>
/// <returns>Formatted name of the episode.</returns>
private string GetEpisodeDisplayName(Episode episode, BaseItem context)
{
string[] components;
if (context is Season season)
{ {
// This is a special embedded within a season // This is a special embedded within a season
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0 if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0) && season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
{ {
return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name); return string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("ValueSpecialEpisodeName"),
episode.Name);
} }
if (item.IndexNumber.HasValue) // inside a season use simple format (ex. '12 - Episode Name')
var epNumberName = GetEpisodeIndexFullName(episode);
components = new[] { epNumberName, episode.Name };
}
else
{
// outside a season include series and season details (ex. 'TV Show - S05E11 - Episode Name')
var epNumberName = GetEpisodeNumberDisplayName(episode);
components = new[] { episode.SeriesName, epNumberName, episode.Name };
}
return string.Join(" - ", components.Where(NotNullOrWhiteSpace));
}
/// <summary>
/// Gets complete episode number.
/// </summary>
/// <param name="episode">The episode.</param>
/// <returns>For single episodes returns just the number. For double episodes - current and ending numbers.</returns>
private string GetEpisodeIndexFullName(Episode episode)
{
var name = string.Empty;
if (episode.IndexNumber.HasValue)
{
name += episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
if (episode.IndexNumberEnd.HasValue)
{ {
var number = item.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); name += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
if (episode.IndexNumberEnd.HasValue)
{
number += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
}
return number + " - " + item.Name;
} }
} }
return item.Name; return name;
} }
private void AddAudioResource(DlnaOptions options, XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null) /// <summary>
/// Gets episode number formatted as 'S##E##'.
/// </summary>
/// <param name="episode">The episode.</param>
/// <returns>Formatted episode number.</returns>
private string GetEpisodeNumberDisplayName(Episode episode)
{
var name = string.Empty;
var seasonNumber = episode.Season?.IndexNumber;
if (seasonNumber.HasValue)
{
name = "S" + seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture);
}
var indexName = GetEpisodeIndexFullName(episode);
if (!string.IsNullOrWhiteSpace(indexName))
{
name += "E" + indexName;
}
return name;
}
private bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s);
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
{ {
writer.WriteStartElement(string.Empty, "res", NS_DIDL); writer.WriteStartElement(string.Empty, "res", NS_DIDL);
@ -504,7 +584,7 @@ namespace Emby.Dlna.Didl
targetSampleRate, targetSampleRate,
targetAudioBitDepth); targetAudioBitDepth);
var filename = url.Substring(0, url.IndexOf('?')); var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType) var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename) ? MimeTypes.GetMimeType(filename)
@ -520,11 +600,13 @@ namespace Emby.Dlna.Didl
streamInfo.RunTimeTicks ?? 0, streamInfo.RunTimeTicks ?? 0,
streamInfo.TranscodeSeekInfo); streamInfo.TranscodeSeekInfo);
writer.WriteAttributeString("protocolInfo", string.Format( writer.WriteAttributeString(
"http-get:*:{0}:{1}", "protocolInfo",
mimeType, string.Format(
contentFeatures CultureInfo.InvariantCulture,
)); "http-get:*:{0}:{1}",
mimeType,
contentFeatures));
writer.WriteString(url); writer.WriteString(url);
@ -547,7 +629,7 @@ namespace Emby.Dlna.Didl
var clientId = GetClientId(folder, stubType); var clientId = GetClientId(folder, stubType);
if (string.Equals(requestedId, "0")) if (string.Equals(requestedId, "0", StringComparison.Ordinal))
{ {
writer.WriteAttributeString("id", "0"); writer.WriteAttributeString("id", "0");
writer.WriteAttributeString("parentID", "-1"); writer.WriteAttributeString("parentID", "-1");
@ -576,7 +658,7 @@ namespace Emby.Dlna.Didl
AddGeneralProperties(folder, stubType, context, writer, filter); AddGeneralProperties(folder, stubType, context, writer, filter);
AddCover(folder, context, stubType, writer); AddCover(folder, stubType, writer);
writer.WriteFullEndElement(); writer.WriteFullEndElement();
} }
@ -609,7 +691,10 @@ namespace Emby.Dlna.Didl
if (playbackPositionTicks > 0) if (playbackPositionTicks > 0)
{ {
var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds).ToString(_usCulture)); var elementValue = string.Format(
CultureInfo.InvariantCulture,
"BM={0}",
Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds));
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value); AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
} }
} }
@ -632,7 +717,7 @@ namespace Emby.Dlna.Didl
{ {
if (item.PremiereDate.HasValue) if (item.PremiereDate.HasValue)
{ {
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC); AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NS_DC);
} }
} }
@ -762,37 +847,36 @@ namespace Emby.Dlna.Didl
private void AddPeople(BaseItem item, XmlWriter writer) private void AddPeople(BaseItem item, XmlWriter writer)
{ {
//var types = new[] if (!item.SupportsPeople)
//{ {
// PersonType.Director, return;
// PersonType.Writer, }
// PersonType.Producer,
// PersonType.Composer,
// "Creator"
//};
//var people = _libraryManager.GetPeople(item); var types = new[]
{
PersonType.Director,
PersonType.Writer,
PersonType.Producer,
PersonType.Composer,
"creator"
};
//var index = 0; // Seeing some LG models locking up due content with large lists of people
// The actual issue might just be due to processing a more metadata than it can handle
var people = _libraryManager.GetPeople(
new InternalPeopleQuery
{
ItemId = item.Id,
Limit = 6
});
//// Seeing some LG models locking up due content with large lists of people foreach (var actor in people)
//// The actual issue might just be due to processing a more metadata than it can handle {
//var limit = 6; var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
?? PersonType.Actor;
//foreach (var actor in people) AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
//{ }
// var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
// ?? PersonType.Actor;
// AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
// index++;
// if (index >= limit)
// {
// break;
// }
//}
} }
private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter) private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
@ -869,7 +953,7 @@ namespace Emby.Dlna.Didl
} }
} }
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer) private void AddCover(BaseItem item, StubType? stubType, XmlWriter writer)
{ {
ImageDownloadInfo imageInfo = GetImageInfo(item); ImageDownloadInfo imageInfo = GetImageInfo(item);
@ -914,17 +998,8 @@ namespace Emby.Dlna.Didl
} }
private void AddEmbeddedImageAsCover(string name, XmlWriter writer) private void AddImageResElement(
{ BaseItem item,
writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
writer.WriteString(_serverAddress + "/Dlna/icons/people480.jpg");
writer.WriteFullEndElement();
writer.WriteElementString("upnp", "icon", NS_UPNP, _serverAddress + "/Dlna/icons/people48.jpg");
}
private void AddImageResElement(BaseItem item,
XmlWriter writer, XmlWriter writer,
int maxWidth, int maxWidth,
int maxHeight, int maxHeight,
@ -950,13 +1025,17 @@ namespace Emby.Dlna.Didl
var contentFeatures = new ContentFeatureBuilder(_profile) var contentFeatures = new ContentFeatureBuilder(_profile)
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn); .BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
writer.WriteAttributeString("protocolInfo", string.Format( writer.WriteAttributeString(
"http-get:*:{0}:{1}", "protocolInfo",
MimeTypes.GetMimeType("file." + format), string.Format(
contentFeatures CultureInfo.InvariantCulture,
)); "http-get:*:{0}:{1}",
MimeTypes.GetMimeType("file." + format),
contentFeatures));
writer.WriteAttributeString("resolution", string.Format("{0}x{1}", width, height)); writer.WriteAttributeString(
"resolution",
string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
writer.WriteString(albumartUrlInfo.Url); writer.WriteString(albumartUrlInfo.Url);
@ -981,19 +1060,58 @@ namespace Emby.Dlna.Didl
} }
} }
item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary)); // For audio tracks without art use album art if available.
if (item is Audio audioItem)
if (item != null)
{ {
if (item.HasImage(ImageType.Primary)) var album = audioItem.AlbumEntity;
{ return album != null && album.HasImage(ImageType.Primary)
return GetImageInfo(item, ImageType.Primary); ? GetImageInfo(album, ImageType.Primary)
} : null;
}
// Don't look beyond album/playlist level. Metadata service may assign an image from a different album/show to the parent folder.
if (item is MusicAlbum || item is Playlist)
{
return null;
}
// For other item types check parents, but be aware that image retrieved from a parent may be not suitable for this media item.
var parentWithImage = GetFirstParentWithImageBelowUserRoot(item);
if (parentWithImage != null)
{
return GetImageInfo(parentWithImage, ImageType.Primary);
} }
return null; return null;
} }
private BaseItem GetFirstParentWithImageBelowUserRoot(BaseItem item)
{
if (item == null)
{
return null;
}
if (item.HasImage(ImageType.Primary))
{
return item;
}
var parent = item.GetParent();
if (parent is UserRootFolder)
{
return null;
}
// terminate in case we went past user root folder (unlikely?)
if (parent is Folder folder && folder.IsRoot)
{
return null;
}
return GetFirstParentWithImageBelowUserRoot(parent);
}
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type) private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
{ {
var imageInfo = item.GetImageInfo(type, 0); var imageInfo = item.GetImageInfo(type, 0);
@ -1095,7 +1213,9 @@ namespace Emby.Dlna.Didl
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format) private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
{ {
var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0", var url = string.Format(
CultureInfo.InvariantCulture,
"{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
_serverAddress, _serverAddress,
info.ItemId.ToString("N", CultureInfo.InvariantCulture), info.ItemId.ToString("N", CultureInfo.InvariantCulture),
info.Type, info.Type,

View File

@ -1,5 +1,6 @@
#pragma warning disable CS1591
using System; using System;
using MediaBrowser.Model.Extensions;
namespace Emby.Dlna.Didl namespace Emby.Dlna.Didl
{ {
@ -16,7 +17,7 @@ namespace Emby.Dlna.Didl
public Filter(string filter) public Filter(string filter)
{ {
_all = StringHelper.EqualsIgnoreCase(filter, "*"); _all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
_fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); _fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
} }

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
@ -51,6 +53,6 @@ namespace Emby.Dlna.Didl
_encoding = encoding; _encoding = encoding;
} }
public override Encoding Encoding => (null == _encoding) ? base.Encoding : _encoding; public override Encoding Encoding => _encoding ?? base.Encoding;
} }
} }

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -385,7 +387,7 @@ namespace Emby.Dlna
{ {
Directory.CreateDirectory(systemProfilesPath); Directory.CreateDirectory(systemProfilesPath);
using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
{ {
await stream.CopyToAsync(fileStream); await stream.CopyToAsync(fileStream);
} }

View File

@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{805844AB-E92F-45E6-9D99-4F6D48D129A5}</ProjectGuid>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\SharedVersion.cs" /> <Compile Include="..\SharedVersion.cs" />
</ItemGroup> </ItemGroup>
@ -15,6 +20,19 @@
<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>
</PropertyGroup>
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,17 +1,20 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
namespace Emby.Dlna namespace Emby.Dlna
{ {
public class EventSubscriptionResponse public class EventSubscriptionResponse
{ {
public string Content { get; set; }
public string ContentType { get; set; }
public Dictionary<string, string> Headers { get; set; }
public EventSubscriptionResponse() public EventSubscriptionResponse()
{ {
Headers = new Dictionary<string, string>(); Headers = new Dictionary<string, string>();
} }
public string Content { get; set; }
public string ContentType { get; set; }
public Dictionary<string, string> Headers { get; set; }
} }
} }

View File

@ -1,8 +1,11 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
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.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
@ -164,7 +167,7 @@ namespace Emby.Dlna.Eventing
try try
{ {
using (await _httpClient.SendAsync(options, "NOTIFY").ConfigureAwait(false)) using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
{ {
} }

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
namespace Emby.Dlna.Eventing namespace Emby.Dlna.Eventing
@ -13,6 +15,8 @@ namespace Emby.Dlna.Eventing
public long TriggerCount { get; set; } public long TriggerCount { get; set; }
public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
public void IncrementTriggerCount() public void IncrementTriggerCount()
{ {
if (TriggerCount == long.MaxValue) if (TriggerCount == long.MaxValue)
@ -22,7 +26,5 @@ namespace Emby.Dlna.Eventing
TriggerCount++; TriggerCount++;
} }
public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
} }
} }

View File

@ -1,3 +1,4 @@
#pragma warning disable CS1591
namespace Emby.Dlna namespace Emby.Dlna
{ {

View File

@ -1,3 +1,4 @@
#pragma warning disable CS1591
namespace Emby.Dlna namespace Emby.Dlna
{ {

View File

@ -1,3 +1,4 @@
#pragma warning disable CS1591
namespace Emby.Dlna namespace Emby.Dlna
{ {

View File

@ -1,3 +1,4 @@
#pragma warning disable CS1591
namespace Emby.Dlna namespace Emby.Dlna
{ {

View File

@ -1,3 +1,7 @@
#pragma warning disable CS1591
using System.Threading.Tasks;
namespace Emby.Dlna namespace Emby.Dlna
{ {
public interface IUpnpService public interface IUpnpService
@ -13,6 +17,6 @@ namespace Emby.Dlna
/// </summary> /// </summary>
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
/// <returns>ControlResponse.</returns> /// <returns>ControlResponse.</returns>
ControlResponse ProcessControlRequest(ControlRequest request); Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request);
} }
} }

View File

@ -1,6 +1,8 @@
#pragma warning disable CS1591
using System; using System;
using System.Net.Sockets;
using System.Globalization; using System.Globalization;
using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.PlayTo; using Emby.Dlna.PlayTo;
@ -24,7 +26,7 @@ using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Rssdp; using Rssdp;
using Rssdp.Infrastructure; using Rssdp.Infrastructure;
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Dlna.Main namespace Emby.Dlna.Main
{ {
@ -56,7 +58,9 @@ namespace Emby.Dlna.Main
private ISsdpCommunicationsServer _communicationsServer; private ISsdpCommunicationsServer _communicationsServer;
internal IContentDirectory ContentDirectory { get; private set; } internal IContentDirectory ContentDirectory { get; private set; }
internal IConnectionManager ConnectionManager { get; private set; } internal IConnectionManager ConnectionManager { get; private set; }
internal IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; } internal IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
public static DlnaEntryPoint Current; public static DlnaEntryPoint Current;
@ -104,7 +108,7 @@ namespace Emby.Dlna.Main
libraryManager, libraryManager,
config, config,
userManager, userManager,
_logger, loggerFactory.CreateLogger<ContentDirectory.ContentDirectory>(),
httpClient, httpClient,
localizationManager, localizationManager,
mediaSourceManager, mediaSourceManager,
@ -112,9 +116,16 @@ namespace Emby.Dlna.Main
mediaEncoder, mediaEncoder,
tvSeriesManager); tvSeriesManager);
ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient); ConnectionManager = new ConnectionManager.ConnectionManager(
dlnaManager,
config,
loggerFactory.CreateLogger<ConnectionManager.ConnectionManager>(),
httpClient);
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config); MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(
loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrar>(),
httpClient,
config);
Current = this; Current = this;
} }
@ -251,8 +262,8 @@ namespace Emby.Dlna.Main
{ {
if (address.AddressFamily == AddressFamily.InterNetworkV6) if (address.AddressFamily == AddressFamily.InterNetworkV6)
{ {
// Not support IPv6 right now // Not supporting IPv6 right now
continue; continue;
} }
var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; var fullService = "urn:schemas-upnp-org:device:MediaServer:1";

View File

@ -1,5 +1,8 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -9,35 +12,33 @@ namespace Emby.Dlna.MediaReceiverRegistrar
{ {
public class ControlHandler : BaseControlHandler public class ControlHandler : BaseControlHandler
{ {
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
{
if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase))
return HandleIsAuthorized();
if (string.Equals(methodName, "IsValidated", StringComparison.OrdinalIgnoreCase))
return HandleIsValidated();
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
private static IEnumerable<KeyValuePair<string, string>> HandleIsAuthorized()
{
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "Result", "1" }
};
}
private static IEnumerable<KeyValuePair<string, string>> HandleIsValidated()
{
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "Result", "1" }
};
}
public ControlHandler(IServerConfigurationManager config, ILogger logger) public ControlHandler(IServerConfigurationManager config, ILogger logger)
: base(config, logger) : base(config, logger)
{ {
} }
/// <inheritdoc />
protected override void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter)
{
if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase))
{
HandleIsAuthorized(xmlWriter);
return;
}
if (string.Equals(methodName, "IsValidated", StringComparison.OrdinalIgnoreCase))
{
HandleIsValidated(xmlWriter);
return;
}
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
private static void HandleIsAuthorized(XmlWriter xmlWriter)
=> xmlWriter.WriteElementString("Result", "1");
private static void HandleIsValidated(XmlWriter xmlWriter)
=> xmlWriter.WriteElementString("Result", "1");
} }
} }

View File

@ -1,3 +1,6 @@
#pragma warning disable CS1591
using System.Threading.Tasks;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -9,23 +12,28 @@ namespace Emby.Dlna.MediaReceiverRegistrar
{ {
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config) public MediaReceiverRegistrar(
ILogger<MediaReceiverRegistrar> logger,
IHttpClient httpClient,
IServerConfigurationManager config)
: base(logger, httpClient) : base(logger, httpClient)
{ {
_config = config; _config = config;
} }
/// <inheritdoc />
public string GetServiceXml() public string GetServiceXml()
{ {
return new MediaReceiverRegistrarXmlBuilder().GetXml(); return new MediaReceiverRegistrarXmlBuilder().GetXml();
} }
public ControlResponse ProcessControlRequest(ControlRequest request) /// <inheritdoc />
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{ {
return new ControlHandler( return new ControlHandler(
_config, _config,
Logger) Logger)
.ProcessControlRequest(request); .ProcessControlRequestAsync(request);
} }
} }
} }

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using Emby.Dlna.Service; using Emby.Dlna.Service;

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -221,7 +223,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, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
.ConfigureAwait(false); .ConfigureAwait(false);
IsMuted = mute; IsMuted = mute;
@ -251,7 +253,7 @@ namespace Emby.Dlna.PlayTo
// Remote control will perform better // Remote control will perform better
Volume = value; Volume = value;
await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
@ -270,7 +272,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service"); throw new InvalidOperationException("Unable to find service");
} }
await new SsdpHttpClient(_httpClient, _config).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(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
.ConfigureAwait(false); .ConfigureAwait(false);
RestartTimer(true); RestartTimer(true);
@ -302,7 +304,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, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header) await new SsdpHttpClient(_httpClient).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);
@ -344,7 +346,12 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service"); throw new InvalidOperationException("Unable to find service");
} }
return new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)); return new SsdpHttpClient(_httpClient).SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
avCommands.BuildPost(command, service.ServiceType, 1),
cancellationToken: cancellationToken);
} }
public async Task SetPlay(CancellationToken cancellationToken) public async Task SetPlay(CancellationToken cancellationToken)
@ -368,7 +375,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService(); var service = GetAvTransportService();
await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
.ConfigureAwait(false); .ConfigureAwait(false);
RestartTimer(true); RestartTimer(true);
@ -386,7 +393,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService(); var service = GetAvTransportService();
await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
.ConfigureAwait(false); .ConfigureAwait(false);
TransportState = TRANSPORTSTATE.PAUSED; TransportState = TRANSPORTSTATE.PAUSED;
@ -513,8 +520,12 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true) var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
.ConfigureAwait(false); Properties.BaseUrl,
service,
command.Name,
rendererCommands.BuildPost(command, service.ServiceType),
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{ {
@ -559,8 +570,12 @@ namespace Emby.Dlna.PlayTo
return; return;
} }
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true) var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
.ConfigureAwait(false); Properties.BaseUrl,
service,
command.Name,
rendererCommands.BuildPost(command, service.ServiceType),
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
return; return;
@ -586,8 +601,12 @@ namespace Emby.Dlna.PlayTo
return null; return null;
} }
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false) var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
.ConfigureAwait(false); Properties.BaseUrl,
service,
command.Name,
avCommands.BuildPost(command, service.ServiceType),
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{ {
@ -597,7 +616,7 @@ namespace Emby.Dlna.PlayTo
var transportState = var transportState =
result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null); result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
var transportStateValue = transportState == null ? null : 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))
@ -624,8 +643,12 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false) var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
.ConfigureAwait(false); Properties.BaseUrl,
service,
command.Name,
rendererCommands.BuildPost(command, service.ServiceType),
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{ {
@ -687,8 +710,12 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false); var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false) var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
.ConfigureAwait(false); Properties.BaseUrl,
service,
command.Name,
rendererCommands.BuildPost(command, service.ServiceType),
cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null) if (result == null || result.Document == null)
{ {
@ -868,7 +895,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
var httpClient = new SsdpHttpClient(_httpClient, _config); var httpClient = new SsdpHttpClient(_httpClient);
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
@ -896,7 +923,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl); string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
var httpClient = new SsdpHttpClient(_httpClient, _config); var httpClient = new SsdpHttpClient(_httpClient);
_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);
@ -931,7 +958,7 @@ namespace Emby.Dlna.PlayTo
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, CancellationToken cancellationToken) public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, CancellationToken cancellationToken)
{ {
var ssdpHttpClient = new SsdpHttpClient(httpClient, config); var ssdpHttpClient = new SsdpHttpClient(httpClient);
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false); var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -25,6 +27,8 @@ namespace Emby.Dlna.PlayTo
{ {
public class PlayToController : ISessionController, IDisposable public class PlayToController : ISessionController, IDisposable
{ {
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
private Device _device; private Device _device;
private readonly SessionInfo _session; private readonly SessionInfo _session;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
@ -43,9 +47,10 @@ namespace Emby.Dlna.PlayTo
private readonly string _serverAddress; private readonly string _serverAddress;
private readonly string _accessToken; private readonly string _accessToken;
public bool IsSessionActive => !_disposed && _device != null; private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
private int _currentPlaylistIndex;
public bool SupportsMediaControl => IsSessionActive; private bool _disposed;
public PlayToController( public PlayToController(
SessionInfo session, SessionInfo session,
@ -81,18 +86,22 @@ namespace Emby.Dlna.PlayTo
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
} }
public bool IsSessionActive => !_disposed && _device != null;
public bool SupportsMediaControl => IsSessionActive;
public void Init(Device device) public void Init(Device device)
{ {
_device = device; _device = device;
_device.OnDeviceUnavailable = OnDeviceUnavailable; _device.OnDeviceUnavailable = OnDeviceUnavailable;
_device.PlaybackStart += _device_PlaybackStart; _device.PlaybackStart += OnDevicePlaybackStart;
_device.PlaybackProgress += _device_PlaybackProgress; _device.PlaybackProgress += OnDevicePlaybackProgress;
_device.PlaybackStopped += _device_PlaybackStopped; _device.PlaybackStopped += OnDevicePlaybackStopped;
_device.MediaChanged += _device_MediaChanged; _device.MediaChanged += OnDeviceMediaChanged;
_device.Start(); _device.Start();
_deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft; _deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft;
} }
private void OnDeviceUnavailable() private void OnDeviceUnavailable()
@ -108,7 +117,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
void _deviceDiscovery_DeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e) private void OnDeviceDiscoveryDeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{ {
var info = e.Argument; var info = e.Argument;
@ -123,7 +132,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
async void _device_MediaChanged(object sender, MediaChangedEventArgs e) private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e)
{ {
if (_disposed) if (_disposed)
{ {
@ -135,15 +144,15 @@ namespace Emby.Dlna.PlayTo
var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager); var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager);
if (streamInfo.Item != null) if (streamInfo.Item != null)
{ {
var positionTicks = GetProgressPositionTicks(e.OldMediaInfo, streamInfo); var positionTicks = GetProgressPositionTicks(streamInfo);
ReportPlaybackStopped(e.OldMediaInfo, streamInfo, positionTicks); ReportPlaybackStopped(streamInfo, positionTicks);
} }
streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager); streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager);
if (streamInfo.Item == null) return; if (streamInfo.Item == null) return;
var newItemProgress = GetProgressInfo(e.NewMediaInfo, streamInfo); var newItemProgress = GetProgressInfo(streamInfo);
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false); await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
} }
@ -153,7 +162,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
async void _device_PlaybackStopped(object sender, PlaybackStoppedEventArgs e) private async void OnDevicePlaybackStopped(object sender, PlaybackStoppedEventArgs e)
{ {
if (_disposed) if (_disposed)
{ {
@ -166,9 +175,9 @@ namespace Emby.Dlna.PlayTo
if (streamInfo.Item == null) return; if (streamInfo.Item == null) return;
var positionTicks = GetProgressPositionTicks(e.MediaInfo, streamInfo); var positionTicks = GetProgressPositionTicks(streamInfo);
ReportPlaybackStopped(e.MediaInfo, streamInfo, positionTicks); ReportPlaybackStopped(streamInfo, positionTicks);
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false); var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
@ -192,7 +201,7 @@ namespace Emby.Dlna.PlayTo
} }
else else
{ {
Playlist.Clear(); _playlist.Clear();
} }
} }
catch (Exception ex) catch (Exception ex)
@ -201,7 +210,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
private async void ReportPlaybackStopped(uBaseObject mediaInfo, StreamParams streamInfo, long? positionTicks) private async void ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks)
{ {
try try
{ {
@ -220,7 +229,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
async void _device_PlaybackStart(object sender, PlaybackStartEventArgs e) private async void OnDevicePlaybackStart(object sender, PlaybackStartEventArgs e)
{ {
if (_disposed) if (_disposed)
{ {
@ -233,7 +242,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null) if (info.Item != null)
{ {
var progress = GetProgressInfo(e.MediaInfo, info); var progress = GetProgressInfo(info);
await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false); await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false);
} }
@ -244,7 +253,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
async void _device_PlaybackProgress(object sender, PlaybackProgressEventArgs e) private async void OnDevicePlaybackProgress(object sender, PlaybackProgressEventArgs e)
{ {
if (_disposed) if (_disposed)
{ {
@ -264,7 +273,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null) if (info.Item != null)
{ {
var progress = GetProgressInfo(e.MediaInfo, info); var progress = GetProgressInfo(info);
await _sessionManager.OnPlaybackProgress(progress).ConfigureAwait(false); await _sessionManager.OnPlaybackProgress(progress).ConfigureAwait(false);
} }
@ -275,7 +284,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
private long? GetProgressPositionTicks(uBaseObject mediaInfo, StreamParams info) private long? GetProgressPositionTicks(StreamParams info)
{ {
var ticks = _device.Position.Ticks; var ticks = _device.Position.Ticks;
@ -287,13 +296,13 @@ namespace Emby.Dlna.PlayTo
return ticks; return ticks;
} }
private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo, StreamParams info) private PlaybackStartInfo GetProgressInfo(StreamParams info)
{ {
return new PlaybackStartInfo return new PlaybackStartInfo
{ {
ItemId = info.ItemId, ItemId = info.ItemId,
SessionId = _session.Id, SessionId = _session.Id,
PositionTicks = GetProgressPositionTicks(mediaInfo, info), PositionTicks = GetProgressPositionTicks(info),
IsMuted = _device.IsMuted, IsMuted = _device.IsMuted,
IsPaused = _device.IsPaused, IsPaused = _device.IsPaused,
MediaSourceId = info.MediaSourceId, MediaSourceId = info.MediaSourceId,
@ -308,9 +317,7 @@ namespace Emby.Dlna.PlayTo
}; };
} }
#region SendCommands public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
{ {
_logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand); _logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
@ -348,11 +355,12 @@ namespace Emby.Dlna.PlayTo
if (command.PlayCommand == PlayCommand.PlayLast) if (command.PlayCommand == PlayCommand.PlayLast)
{ {
Playlist.AddRange(playlist); _playlist.AddRange(playlist);
} }
if (command.PlayCommand == PlayCommand.PlayNext) if (command.PlayCommand == PlayCommand.PlayNext)
{ {
Playlist.AddRange(playlist); _playlist.AddRange(playlist);
} }
if (!command.ControllingUserId.Equals(Guid.Empty)) if (!command.ControllingUserId.Equals(Guid.Empty))
@ -361,7 +369,7 @@ namespace Emby.Dlna.PlayTo
_session.DeviceName, _session.RemoteEndPoint, user); _session.DeviceName, _session.RemoteEndPoint, user);
} }
await PlayItems(playlist).ConfigureAwait(false); return PlayItems(playlist, cancellationToken);
} }
private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken) private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
@ -369,7 +377,7 @@ namespace Emby.Dlna.PlayTo
switch (command.Command) switch (command.Command)
{ {
case PlaystateCommand.Stop: case PlaystateCommand.Stop:
Playlist.Clear(); _playlist.Clear();
return _device.SetStop(CancellationToken.None); return _device.SetStop(CancellationToken.None);
case PlaystateCommand.Pause: case PlaystateCommand.Pause:
@ -385,10 +393,10 @@ namespace Emby.Dlna.PlayTo
return Seek(command.SeekPositionTicks ?? 0); return Seek(command.SeekPositionTicks ?? 0);
case PlaystateCommand.NextTrack: case PlaystateCommand.NextTrack:
return SetPlaylistIndex(_currentPlaylistIndex + 1); return SetPlaylistIndex(_currentPlaylistIndex + 1, cancellationToken);
case PlaystateCommand.PreviousTrack: case PlaystateCommand.PreviousTrack:
return SetPlaylistIndex(_currentPlaylistIndex - 1); return SetPlaylistIndex(_currentPlaylistIndex - 1, cancellationToken);
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -424,14 +432,6 @@ namespace Emby.Dlna.PlayTo
return info.IsDirectStream; return info.IsDirectStream;
} }
#endregion
#region Playlist
private int _currentPlaylistIndex;
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
private List<PlaylistItem> Playlist => _playlist;
private void AddItemFromId(Guid id, List<BaseItem> list) private void AddItemFromId(Guid id, List<BaseItem> list)
{ {
var item = _libraryManager.GetItemById(id); var item = _libraryManager.GetItemById(id);
@ -449,7 +449,7 @@ namespace Emby.Dlna.PlayTo
_dlnaManager.GetDefaultProfile(); _dlnaManager.GetDefaultProfile();
var mediaSources = item is IHasMediaSources var mediaSources = item is IHasMediaSources
? (_mediaSourceManager.GetStaticMediaSources(item, true, user)) ? _mediaSourceManager.GetStaticMediaSources(item, true, user)
: new List<MediaSourceInfo>(); : new List<MediaSourceInfo>();
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex); var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
@ -457,8 +457,19 @@ namespace Emby.Dlna.PlayTo
playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken)); playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken));
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _mediaEncoder) var itemXml = new DidlBuilder(
.GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo); profile,
user,
_imageProcessor,
_serverAddress,
_accessToken,
_userDataManager,
_localization,
_mediaSourceManager,
_logger,
_mediaEncoder,
_libraryManager)
.GetItemDidl(item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
playlistItem.Didl = itemXml; playlistItem.Didl = itemXml;
@ -568,30 +579,31 @@ namespace Emby.Dlna.PlayTo
/// Plays the items. /// Plays the items.
/// </summary> /// </summary>
/// <param name="items">The items.</param> /// <param name="items">The items.</param>
/// <returns></returns> /// <param name="cancellationToken">The cancellation token.</param>
private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items) /// <returns><c>true</c> on success.</returns>
private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items, CancellationToken cancellationToken = default)
{ {
Playlist.Clear(); _playlist.Clear();
Playlist.AddRange(items); _playlist.AddRange(items);
_logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, Playlist.Count); _logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, _playlist.Count);
await SetPlaylistIndex(0).ConfigureAwait(false); await SetPlaylistIndex(0, cancellationToken).ConfigureAwait(false);
return true; return true;
} }
private async Task SetPlaylistIndex(int index) private async Task SetPlaylistIndex(int index, CancellationToken cancellationToken = default)
{ {
if (index < 0 || index >= Playlist.Count) if (index < 0 || index >= _playlist.Count)
{ {
Playlist.Clear(); _playlist.Clear();
await _device.SetStop(CancellationToken.None); await _device.SetStop(cancellationToken).ConfigureAwait(false);
return; return;
} }
_currentPlaylistIndex = index; _currentPlaylistIndex = index;
var currentitem = Playlist[index]; var currentitem = _playlist[index];
await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, CancellationToken.None); await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false);
var streamInfo = currentitem.StreamInfo; var streamInfo = currentitem.StreamInfo;
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo)) if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
@ -600,10 +612,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
#endregion /// <inheritdoc />
private bool _disposed;
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
@ -622,19 +631,17 @@ namespace Emby.Dlna.PlayTo
_device.Dispose(); _device.Dispose();
} }
_device.PlaybackStart -= _device_PlaybackStart; _device.PlaybackStart -= OnDevicePlaybackStart;
_device.PlaybackProgress -= _device_PlaybackProgress; _device.PlaybackProgress -= OnDevicePlaybackProgress;
_device.PlaybackStopped -= _device_PlaybackStopped; _device.PlaybackStopped -= OnDevicePlaybackStopped;
_device.MediaChanged -= _device_MediaChanged; _device.MediaChanged -= OnDeviceMediaChanged;
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft; _deviceDiscovery.DeviceLeft -= OnDeviceDiscoveryDeviceLeft;
_device.OnDeviceUnavailable = null; _device.OnDeviceUnavailable = null;
_device = null; _device = null;
_disposed = true; _disposed = true;
} }
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
{ {
if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType)) if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType))
@ -711,7 +718,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null) if (info.Item != null)
{ {
var newPosition = GetProgressPositionTicks(media, info) ?? 0; var newPosition = GetProgressPositionTicks(info) ?? 0;
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null; var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex); var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex);
@ -736,7 +743,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null) if (info.Item != null)
{ {
var newPosition = GetProgressPositionTicks(media, info) ?? 0; var newPosition = GetProgressPositionTicks(info) ?? 0;
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null; var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex); var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex);
@ -850,8 +857,11 @@ namespace Emby.Dlna.PlayTo
return request; return request;
} }
var index = url.IndexOf('?'); var index = url.IndexOf('?', StringComparison.Ordinal);
if (index == -1) return request; if (index == -1)
{
return request;
}
var query = url.Substring(index + 1); var query = url.Substring(index + 1);
Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString()); Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@ -21,7 +23,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo
{ {
public class PlayToManager : IDisposable public sealed class PlayToManager : IDisposable
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
@ -64,10 +66,10 @@ namespace Emby.Dlna.PlayTo
public void Start() public void Start()
{ {
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; _deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
} }
async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e) private async void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{ {
if (_disposed) if (_disposed)
{ {
@ -229,9 +231,10 @@ namespace Emby.Dlna.PlayTo
} }
} }
/// <inheritdoc />
public void Dispose() public void Dispose()
{ {
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered; _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
try try
{ {
@ -242,6 +245,9 @@ namespace Emby.Dlna.PlayTo
} }
_sessionLock.Dispose();
_disposeCancellationTokenSource.Dispose();
_disposed = true; _disposed = true;
} }
} }

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo

View File

@ -1,4 +1,5 @@
using System.Globalization; #pragma warning disable CS1591
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;

View File

@ -1,13 +1,15 @@
#pragma warning disable CS1591
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Net.Http;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo
{ {
@ -19,12 +21,10 @@ 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 IHttpClient _httpClient;
private readonly IServerConfigurationManager _config;
public SsdpHttpClient(IHttpClient httpClient, IServerConfigurationManager config) public SsdpHttpClient(IHttpClient httpClient)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_config = config;
} }
public async Task<XDocument> SendCommandAsync( public async Task<XDocument> SendCommandAsync(
@ -32,18 +32,15 @@ namespace Emby.Dlna.PlayTo
DeviceService service, DeviceService service,
string command, string command,
string postData, string postData,
bool logRequest = true, string header = null,
string header = null) CancellationToken cancellationToken = default)
{ {
var cancellationToken = CancellationToken.None;
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl); var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
using (var response = await PostSoapDataAsync( using (var response = await PostSoapDataAsync(
url, url,
$"\"{service.ServiceType}#{command}\"", $"\"{service.ServiceType}#{command}\"",
postData, postData,
header, header,
logRequest,
cancellationToken) cancellationToken)
.ConfigureAwait(false)) .ConfigureAwait(false))
using (var stream = response.Content) using (var stream = response.Content)
@ -63,8 +60,10 @@ namespace Emby.Dlna.PlayTo
return serviceUrl; return serviceUrl;
} }
if (!serviceUrl.StartsWith("/")) if (!serviceUrl.StartsWith("/", StringComparison.Ordinal))
{
serviceUrl = "/" + serviceUrl; serviceUrl = "/" + serviceUrl;
}
return baseUrl + serviceUrl; return baseUrl + serviceUrl;
} }
@ -90,7 +89,7 @@ namespace Emby.Dlna.PlayTo
options.RequestHeaders["NT"] = "upnp:event"; options.RequestHeaders["NT"] = "upnp:event";
options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture); options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture);
using (await _httpClient.SendAsync(options, "SUBSCRIBE").ConfigureAwait(false)) using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
{ {
} }
@ -110,7 +109,7 @@ namespace Emby.Dlna.PlayTo
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName; options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false)) using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
using (var stream = response.Content) using (var stream = response.Content)
using (var reader = new StreamReader(stream, Encoding.UTF8)) using (var reader = new StreamReader(stream, Encoding.UTF8))
{ {
@ -125,7 +124,6 @@ namespace Emby.Dlna.PlayTo
string soapAction, string soapAction,
string postData, string postData,
string header, string header,
bool logRequest,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
if (soapAction[0] != '\"') if (soapAction[0] != '\"')

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo
{ {
public enum TRANSPORTSTATE public enum TRANSPORTSTATE

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
using System.Xml.Linq; using System.Xml.Linq;
using Emby.Dlna.Ssdp; using Emby.Dlna.Ssdp;

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System; using System;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Xml.Linq; using System.Xml.Linq;
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.Linq; using System.Linq;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@ -10,7 +12,7 @@ namespace Emby.Dlna.Profiles
{ {
Name = "Generic Device"; Name = "Generic Device";
ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*"; ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*";
Manufacturer = "Jellyfin"; Manufacturer = "Jellyfin";
ModelDescription = "UPnP/AV 1.0 Compliant Media Server"; ModelDescription = "UPnP/AV 1.0 Compliant Media Server";

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

View File

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles namespace Emby.Dlna.Profiles

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