Merge branch 'master' into websocket
This commit is contained in:
commit
15634a1913
96
.ci/azure-pipelines-compat.yml
Normal file
96
.ci/azure-pipelines-compat.yml
Normal 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)
|
93
.ci/azure-pipelines-main.yml
Normal file
93
.ci/azure-pipelines-main.yml
Normal 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"
|
89
.ci/azure-pipelines-test.yml
Normal file
89
.ci/azure-pipelines-test.yml
Normal 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
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
name: $(Date:yyyyMMdd)$(Rev:.r)
|
||||
|
||||
variables:
|
||||
- name: TestProjects
|
||||
value: 'tests/**/*Tests.csproj'
|
||||
- name: RestoreBuildProjects
|
||||
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
|
||||
- name: TestProjects
|
||||
value: "tests/**/*Tests.csproj"
|
||||
- name: RestoreBuildProjects
|
||||
value: "Jellyfin.Server/Jellyfin.Server.csproj"
|
||||
- name: DotNetSdkVersion
|
||||
value: 3.1.100
|
||||
|
||||
pr:
|
||||
autoCancel: true
|
||||
|
@ -13,234 +15,21 @@ trigger:
|
|||
batch: true
|
||||
|
||||
jobs:
|
||||
- job: main_build
|
||||
displayName: Main Build
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
Release:
|
||||
BuildConfiguration: Release
|
||||
Debug:
|
||||
BuildConfiguration: Debug
|
||||
maxParallel: 2
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: true
|
||||
- template: azure-pipelines-main.yml
|
||||
parameters:
|
||||
LinuxImage: "ubuntu-latest"
|
||||
RestoreBuildProjects: $(RestoreBuildProjects)
|
||||
|
||||
- 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')) ,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'
|
||||
- template: azure-pipelines-test.yml
|
||||
parameters:
|
||||
ImageNames:
|
||||
Linux: "ubuntu-latest"
|
||||
Windows: "windows-latest"
|
||||
macOS: "macos-latest"
|
||||
|
||||
- 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: 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:
|
||||
- template: azure-pipelines-compat.yml
|
||||
parameters:
|
||||
Packages:
|
||||
Naming:
|
||||
NugetPackageName: Jellyfin.Naming
|
||||
AssemblyFileName: Emby.Naming.dll
|
||||
|
@ -253,74 +42,4 @@ jobs:
|
|||
Common:
|
||||
NugetPackageName: Jellyfin.Common
|
||||
AssemblyFileName: MediaBrowser.Common.dll
|
||||
maxParallel: 2
|
||||
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
|
||||
LinuxImage: "ubuntu-latest"
|
||||
|
|
|
@ -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'
|
|
@ -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'
|
|
@ -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
1
.copr/Makefile
Symbolic link
|
@ -0,0 +1 @@
|
|||
../fedora/Makefile
|
3
.github/CODEOWNERS
vendored
Normal file
3
.github/CODEOWNERS
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Joshua must review all changes to deployment and build.sh
|
||||
deployment/* @joshuaboniface
|
||||
build.sh @joshuaboniface
|
20
.github/ISSUE_TEMPLATE/bug_report.md
vendored
20
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -10,6 +10,19 @@ assignees: ''
|
|||
**Describe the bug**
|
||||
<!-- 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**
|
||||
<!-- Steps to reproduce the behavior: -->
|
||||
1. Go to '...'
|
||||
|
@ -26,12 +39,5 @@ assignees: ''
|
|||
**Screenshots**
|
||||
<!-- 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**
|
||||
<!-- Add any other context about the problem here. -->
|
||||
|
|
13
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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.
|
6
.github/ISSUE_TEMPLATE/media_playback.md
vendored
6
.github/ISSUE_TEMPLATE/media_playback.md
vendored
|
@ -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. -->
|
||||
|
||||
**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**
|
||||
<!-- 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]
|
||||
- Browser (if Web client): [e.g. Firefox, Chrome, Safari]
|
||||
- Client and Browser Version: [e.g. 10.3.4 and 68.0]
|
||||
|
||||
|
|
20
.gitignore
vendored
20
.gitignore
vendored
|
@ -39,6 +39,7 @@ ProgramData*/
|
|||
CorePlugins*/
|
||||
ProgramData-Server*/
|
||||
ProgramData-UI*/
|
||||
MediaBrowser.WebDashboard/jellyfin-web/**
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
|
@ -244,14 +245,14 @@ pip-log.txt
|
|||
#########################
|
||||
|
||||
# Artifacts for debian-x64
|
||||
deployment/debian-package-x64/pkg-src/.debhelper/
|
||||
deployment/debian-package-x64/pkg-src/*.debhelper
|
||||
deployment/debian-package-x64/pkg-src/debhelper-build-stamp
|
||||
deployment/debian-package-x64/pkg-src/files
|
||||
deployment/debian-package-x64/pkg-src/jellyfin.substvars
|
||||
deployment/debian-package-x64/pkg-src/jellyfin/
|
||||
debian/.debhelper/
|
||||
debian/*.debhelper
|
||||
debian/debhelper-build-stamp
|
||||
debian/files
|
||||
debian/jellyfin.substvars
|
||||
debian/jellyfin/
|
||||
# Don't ignore the debian/bin folder
|
||||
!deployment/debian-package-x64/pkg-src/bin/
|
||||
!debian/bin/
|
||||
|
||||
deployment/**/dist/
|
||||
deployment/**/pkg-dist/
|
||||
|
@ -271,3 +272,8 @@ dist
|
|||
|
||||
# BenchmarkDotNet artifacts
|
||||
BenchmarkDotNet.Artifacts
|
||||
|
||||
# Ignore web artifacts from native builds
|
||||
web/
|
||||
web-src.*
|
||||
MediaBrowser.WebDashboard/jellyfin-web/
|
||||
|
|
14
.vscode/extensions.json
vendored
Normal file
14
.vscode/extensions.json
vendored
Normal 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": [
|
||||
|
||||
]
|
||||
}
|
150
CONTRIBUTORS.md
150
CONTRIBUTORS.md
|
@ -1,37 +1,135 @@
|
|||
# Jellyfin Contributors
|
||||
|
||||
- [JoshuaBoniface](https://github.com/joshuaboniface)
|
||||
- [nvllsvm](https://github.com/nvllsvm)
|
||||
- [JustAMan](https://github.com/JustAMan)
|
||||
- [dcrdev](https://github.com/dcrdev)
|
||||
- [EraYaN](https://github.com/EraYaN)
|
||||
- [flemse](https://github.com/flemse)
|
||||
- [97carmine](https://github.com/97carmine)
|
||||
- [Abbe98](https://github.com/Abbe98)
|
||||
- [agrenott](https://github.com/agrenott)
|
||||
- [AndreCarvalho](https://github.com/AndreCarvalho)
|
||||
- [anthonylavado](https://github.com/anthonylavado)
|
||||
- [Artiume](https://github.com/Artiume)
|
||||
- [AThomsen](https://github.com/AThomsen)
|
||||
- [bilde2910](https://github.com/bilde2910)
|
||||
- [bfayers](https://github.com/bfayers)
|
||||
- [Bond_009](https://github.com/Bond-009)
|
||||
- [AnthonyLavado](https://github.com/anthonylavado)
|
||||
- [sparky8251](https://github.com/sparky8251)
|
||||
- [LeoVerto](https://github.com/LeoVerto)
|
||||
- [grafixeyehero](https://github.com/grafixeyehero)
|
||||
- [BnMcG](https://github.com/BnMcG)
|
||||
- [Bond-009](https://github.com/Bond-009)
|
||||
- [brianjmurrell](https://github.com/brianjmurrell)
|
||||
- [bugfixin](https://github.com/bugfixin)
|
||||
- [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)
|
||||
- [wtayl0r](https://github.com/wtayl0r)
|
||||
- [TtheCreator](https://github.com/Tthecreator)
|
||||
- [dannymichel](https://github.com/dannymichel)
|
||||
- [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)
|
||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy/)
|
||||
- [RazeLighter777](https://github.com/RazeLighter777)
|
||||
- [WillWill56](https://github.com/WillWill56)
|
||||
- [Liggy](https://github.com/Liggy)
|
||||
- [fruhnow](https://github.com/fruhnow)
|
||||
- [Lynxy](https://github.com/Lynxy)
|
||||
- [dlahoti](https://github.com/dlahoti)
|
||||
- [dmitrylyzo](https://github.com/dmitrylyzo)
|
||||
- [DMouse10462](https://github.com/DMouse10462)
|
||||
- [DrPandemic](https://github.com/DrPandemic)
|
||||
- [EraYaN](https://github.com/EraYaN)
|
||||
- [escabe](https://github.com/escabe)
|
||||
- [excelite](https://github.com/excelite)
|
||||
- [fasheng](https://github.com/fasheng)
|
||||
- [ploughpuff](https://github.com/ploughpuff)
|
||||
- [pjeanjean](https://github.com/pjeanjean)
|
||||
- [DrPandemic](https://github.com/drpandemic)
|
||||
- [joern-h](https://github.com/joern-h)
|
||||
- [Khinenw](https://github.com/HelloWorld017)
|
||||
- [ferferga](https://github.com/ferferga)
|
||||
- [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)
|
||||
- [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
|
||||
|
||||
|
|
44
Dockerfile
44
Dockerfile
|
@ -1,48 +1,60 @@
|
|||
ARG DOTNET_VERSION=3.1
|
||||
ARG FFMPEG_VERSION=latest
|
||||
|
||||
FROM node:alpine as web-builder
|
||||
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 - \
|
||||
&& cd jellyfin-web-* \
|
||||
&& yarn install \
|
||||
&& yarn build \
|
||||
&& mv dist /dist
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
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
|
||||
|
||||
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=web-builder /dist /jellyfin/jellyfin-web
|
||||
# Install dependencies:
|
||||
# libfontconfig1: needed for Skia
|
||||
# libgomp1: needed for ffmpeg
|
||||
# libva-drm2: needed for ffmpeg
|
||||
# mesa-va-drivers: needed for VAAPI
|
||||
# mesa-va-drivers: needed for AMD VAAPI
|
||||
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 \
|
||||
libfontconfig1 libgomp1 libva-drm2 mesa-va-drivers openssl \
|
||||
&& apt-get clean autoclean \
|
||||
&& apt-get autoremove \
|
||||
mesa-va-drivers \
|
||||
jellyfin-ffmpeg \
|
||||
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/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media \
|
||||
&& ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \
|
||||
&& ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin
|
||||
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US:en
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
"--ffmpeg", "/usr/local/bin/ffmpeg"]
|
||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# DESIGNED FOR BUILDING ON AMD64 ONLY
|
||||
#####################################
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=3.1
|
||||
|
@ -5,11 +7,10 @@ ARG DOTNET_VERSION=3.1
|
|||
|
||||
FROM node:alpine as web-builder
|
||||
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 - \
|
||||
&& cd jellyfin-web-* \
|
||||
&& yarn install \
|
||||
&& yarn build \
|
||||
&& 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 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
|
||||
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/* \
|
||||
&& 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=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
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
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
"--ffmpeg", "/usr/bin/ffmpeg"]
|
||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# DESIGNED FOR BUILDING ON AMD64 ONLY
|
||||
#####################################
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=3.1
|
||||
|
@ -5,11 +7,10 @@ ARG DOTNET_VERSION=3.1
|
|||
|
||||
FROM node:alpine as web-builder
|
||||
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 - \
|
||||
&& cd jellyfin-web-* \
|
||||
&& yarn install \
|
||||
&& yarn build \
|
||||
&& mv dist /dist
|
||||
|
||||
|
||||
|
@ -22,19 +23,40 @@ RUN find . -type d -name obj | xargs -r rm -r
|
|||
# Build
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||
|
||||
|
||||
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
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
ffmpeg \
|
||||
libssl-dev \
|
||||
ca-certificates \
|
||||
libfontconfig1 \
|
||||
libfreetype6 \
|
||||
libomxil-bellagio0 \
|
||||
libomxil-bellagio-bin \
|
||||
locales \
|
||||
&& apt-get clean autoclean -y \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
&& 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=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
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
|
||||
VOLUME /cache /config /media
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
|
||||
namespace DvdLib
|
||||
|
@ -12,19 +12,12 @@ namespace DvdLib
|
|||
|
||||
public override ushort ReadUInt16()
|
||||
{
|
||||
return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0);
|
||||
return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2));
|
||||
}
|
||||
|
||||
public override uint ReadUInt32()
|
||||
{
|
||||
return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0);
|
||||
}
|
||||
|
||||
private byte[] ReadAndReverseBytes(int count)
|
||||
{
|
||||
byte[] val = base.ReadBytes(count);
|
||||
Array.Reverse(val, 0, count);
|
||||
return val;
|
||||
return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
<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>
|
||||
<Compile Include="..\SharedVersion.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
|
@ -13,13 +12,10 @@ namespace DvdLib.Ifo
|
|||
|
||||
private ushort _titleCount;
|
||||
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public Dvd(string path, IFileSystem fileSystem)
|
||||
public Dvd(string path)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
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)) ??
|
||||
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
|
||||
|
@ -33,7 +29,7 @@ namespace DvdLib.Ifo
|
|||
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))
|
||||
{
|
||||
ReadVTS(ifoNumber, ifo.FullName);
|
||||
|
@ -42,7 +38,7 @@ namespace DvdLib.Ifo
|
|||
}
|
||||
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))
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
|
@ -95,7 +91,7 @@ namespace DvdLib.Ifo
|
|||
{
|
||||
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))
|
||||
{
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
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)));
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||
public object Get(GetContentDirectory request)
|
||||
{
|
||||
var xml = ContentDirectory.GetServiceXml();
|
||||
|
@ -156,6 +160,7 @@ namespace Emby.Dlna.Api
|
|||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||
public object Get(GetMediaReceiverRegistrar request)
|
||||
{
|
||||
var xml = MediaReceiverRegistrar.GetServiceXml();
|
||||
|
@ -163,6 +168,7 @@ namespace Emby.Dlna.Api
|
|||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||
public object Get(GetConnnectionManager request)
|
||||
{
|
||||
var xml = ConnectionManager.GetServiceXml();
|
||||
|
@ -170,32 +176,32 @@ namespace Emby.Dlna.Api
|
|||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private ControlResponse PostAsync(Stream requestStream, IUpnpService service)
|
||||
private Task<ControlResponse> PostAsync(Stream requestStream, IUpnpService service)
|
||||
{
|
||||
var id = GetPathValue(2).ToString();
|
||||
|
||||
return service.ProcessControlRequest(new ControlRequest
|
||||
return service.ProcessControlRequestAsync(new ControlRequest
|
||||
{
|
||||
Headers = Request.Headers,
|
||||
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));
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||
public object Subscribe(ProcessContentDirectoryEventRequest request)
|
||||
{
|
||||
return ProcessEventRequest(ContentDirectory);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||
public object Subscribe(ProcessConnectionManagerEventRequest request)
|
||||
{
|
||||
return ProcessEventRequest(ConnectionManager);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||
public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request)
|
||||
{
|
||||
return ProcessEventRequest(MediaReceiverRegistrar);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||
public object Unsubscribe(ProcessContentDirectoryEventRequest request)
|
||||
{
|
||||
return ProcessEventRequest(ContentDirectory);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||
public object Unsubscribe(ProcessConnectionManagerEventRequest request)
|
||||
{
|
||||
return ProcessEventRequest(ConnectionManager);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||
public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request)
|
||||
{
|
||||
return ProcessEventRequest(MediaReceiverRegistrar);
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Net;
|
||||
|
@ -50,6 +53,7 @@ namespace Emby.Dlna.Api
|
|||
_dlnaManager = dlnaManager;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||
public object Get(GetProfileInfos request)
|
||||
{
|
||||
return _dlnaManager.GetProfileInfos().ToArray();
|
||||
|
@ -60,6 +64,7 @@ namespace Emby.Dlna.Api
|
|||
return _dlnaManager.GetProfile(request.Id);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||
public object Get(GetDefaultProfile request)
|
||||
{
|
||||
return _dlnaManager.GetDefaultProfile();
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
namespace Emby.Dlna.Common
|
||||
{
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace Emby.Dlna.Common
|
||||
{
|
||||
|
@ -13,9 +16,14 @@ namespace Emby.Dlna.Common
|
|||
|
||||
public string Depth { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}x{1}", Height, Width);
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}x{1}",
|
||||
Height,
|
||||
Width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
namespace Emby.Dlna.Common
|
||||
{
|
||||
|
@ -13,9 +14,8 @@ namespace Emby.Dlna.Common
|
|||
|
||||
public string EventSubUrl { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}", ServiceId);
|
||||
}
|
||||
=> ServiceId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Dlna.Common
|
||||
{
|
||||
public class ServiceAction
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<Argument> ArgumentList { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public ServiceAction()
|
||||
{
|
||||
ArgumentList = new List<Argument>();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<Argument> ArgumentList { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace Emby.Dlna.Common
|
||||
{
|
||||
public class StateVariable
|
||||
{
|
||||
public StateVariable()
|
||||
{
|
||||
AllowedValues = Array.Empty<string>();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string DataType { get; set; }
|
||||
|
@ -12,14 +19,8 @@ namespace Emby.Dlna.Common
|
|||
|
||||
public string[] AllowedValues { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public StateVariable()
|
||||
{
|
||||
AllowedValues = Array.Empty<string>();
|
||||
}
|
||||
=> Name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,9 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
namespace Emby.Dlna.Configuration
|
||||
{
|
||||
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()
|
||||
{
|
||||
EnablePlayTo = true;
|
||||
|
@ -21,5 +13,21 @@ namespace Emby.Dlna.Configuration
|
|||
ClientDiscoveryIntervalSeconds = 60;
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#nullable enable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Configuration;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -12,7 +15,11 @@ namespace Emby.Dlna.ConnectionManager
|
|||
private readonly ILogger _logger;
|
||||
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)
|
||||
{
|
||||
_dlna = dlna;
|
||||
|
@ -20,17 +27,19 @@ namespace Emby.Dlna.ConnectionManager
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return new ConnectionManagerXmlBuilder().GetXml();
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
/// <inheritdoc />
|
||||
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||
{
|
||||
var profile = _dlna.GetProfile(request.Headers) ??
|
||||
_dlna.GetDefaultProfile();
|
||||
|
||||
return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request);
|
||||
return new ControlHandler(_config, _logger, profile).ProcessControlRequestAsync(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Common;
|
||||
using Emby.Dlna.Service;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -12,29 +15,28 @@ namespace Emby.Dlna.ConnectionManager
|
|||
{
|
||||
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)
|
||||
: base(config, logger)
|
||||
{
|
||||
_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Common;
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -34,7 +37,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
ILibraryManager libraryManager,
|
||||
IServerConfigurationManager config,
|
||||
IUserManager userManager,
|
||||
ILogger logger,
|
||||
ILogger<ContentDirectory> logger,
|
||||
IHttpClient httpClient,
|
||||
ILocalizationManager localization,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
|
@ -66,12 +69,14 @@ namespace Emby.Dlna.ContentDirectory
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return new ContentDirectoryXmlBuilder().GetXml();
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
/// <inheritdoc />
|
||||
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||
{
|
||||
var profile = _dlna.GetProfile(request.Headers) ??
|
||||
_dlna.GetDefaultProfile();
|
||||
|
@ -96,7 +101,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
_userViewManager,
|
||||
_mediaEncoder,
|
||||
_tvSeriesManager)
|
||||
.ProcessControlRequest(request);
|
||||
.ProcessControlRequestAsync(request);
|
||||
}
|
||||
|
||||
private User GetUser(DeviceProfile profile)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Common;
|
||||
using Emby.Dlna.Service;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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 readonly int _systemUpdateId;
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
private readonly DidlBuilder _didlBuilder;
|
||||
|
||||
|
@ -58,7 +59,8 @@ namespace Emby.Dlna.ContentDirectory
|
|||
string accessToken,
|
||||
IImageProcessor imageProcessor,
|
||||
IUserDataManager userDataManager,
|
||||
User user, int systemUpdateId,
|
||||
User user,
|
||||
int systemUpdateId,
|
||||
IServerConfigurationManager config,
|
||||
ILocalizationManager localization,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
|
@ -76,117 +78,143 @@ namespace Emby.Dlna.ContentDirectory
|
|||
_profile = profile;
|
||||
_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";
|
||||
|
||||
var user = _user;
|
||||
const string DeviceId = "test";
|
||||
|
||||
if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase))
|
||||
return HandleGetSearchCapabilities();
|
||||
{
|
||||
HandleGetSearchCapabilities(xmlWriter);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(methodName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase))
|
||||
return HandleGetSortCapabilities();
|
||||
{
|
||||
HandleGetSortCapabilities(xmlWriter);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(methodName, "GetSortExtensionCapabilities", StringComparison.OrdinalIgnoreCase))
|
||||
return HandleGetSortExtensionCapabilities();
|
||||
{
|
||||
HandleGetSortExtensionCapabilities(xmlWriter);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(methodName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase))
|
||||
return HandleGetSystemUpdateID();
|
||||
{
|
||||
HandleGetSystemUpdateID(xmlWriter);
|
||||
return;
|
||||
}
|
||||
|
||||
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))
|
||||
return HandleXGetFeatureList();
|
||||
{
|
||||
HandleXGetFeatureList(xmlWriter);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(methodName, "GetFeatureList", StringComparison.OrdinalIgnoreCase))
|
||||
return HandleGetFeatureList();
|
||||
{
|
||||
HandleGetFeatureList(xmlWriter);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
|
||||
return HandleXSetBookmark(methodParams, user);
|
||||
{
|
||||
HandleXSetBookmark(methodParams);
|
||||
return;
|
||||
}
|
||||
|
||||
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))
|
||||
return HandleX_BrowseByLetter(methodParams, user, deviceId);
|
||||
{
|
||||
HandleXBrowseByLetter(xmlWriter, methodParams, DeviceId);
|
||||
return;
|
||||
}
|
||||
|
||||
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 serverItem = GetItemFromObjectId(id, user);
|
||||
var serverItem = GetItemFromObjectId(id);
|
||||
|
||||
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;
|
||||
|
||||
_userDataManager.SaveUserData(user, item, userdata, UserDataSaveReason.TogglePlayed,
|
||||
_userDataManager.SaveUserData(_user, item, userdata, UserDataSaveReason.TogglePlayed,
|
||||
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)
|
||||
{
|
||||
{ "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" }
|
||||
};
|
||||
xmlWriter.WriteElementString(
|
||||
"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");
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, string>> HandleGetSortCapabilities()
|
||||
private void HandleGetSortCapabilities(XmlWriter xmlWriter)
|
||||
{
|
||||
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "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" }
|
||||
};
|
||||
xmlWriter.WriteElementString(
|
||||
"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");
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, string>> HandleGetSortExtensionCapabilities()
|
||||
private void HandleGetSortExtensionCapabilities(XmlWriter xmlWriter)
|
||||
{
|
||||
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "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" }
|
||||
};
|
||||
xmlWriter.WriteElementString(
|
||||
"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");
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, string>> HandleGetSystemUpdateID()
|
||||
private void HandleGetSystemUpdateID(XmlWriter xmlWriter)
|
||||
{
|
||||
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
headers.Add("Id", _systemUpdateId.ToString(_usCulture));
|
||||
return headers;
|
||||
xmlWriter.WriteElementString("Id", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, string>> HandleGetFeatureList()
|
||||
private void HandleGetFeatureList(XmlWriter xmlWriter)
|
||||
{
|
||||
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "FeatureList", GetFeatureListXml() }
|
||||
};
|
||||
xmlWriter.WriteElementString("FeatureList", WriteFeatureListXml());
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, string>> HandleXGetFeatureList()
|
||||
{
|
||||
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "FeatureList", GetFeatureListXml() }
|
||||
};
|
||||
}
|
||||
private void HandleXGetFeatureList(XmlWriter xmlWriter)
|
||||
=> HandleGetFeatureList(xmlWriter);
|
||||
|
||||
private string GetFeatureListXml()
|
||||
private string WriteFeatureListXml()
|
||||
{
|
||||
// TODO: clean this up
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
|
@ -213,7 +241,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
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 flag = sparams["BrowseFlag"];
|
||||
|
@ -237,101 +265,95 @@ namespace Emby.Dlna.ContentDirectory
|
|||
start = startVal;
|
||||
}
|
||||
|
||||
var settings = new XmlWriterSettings
|
||||
{
|
||||
Encoding = Encoding.UTF8,
|
||||
CloseOutput = false,
|
||||
OmitXmlDeclaration = true,
|
||||
ConformanceLevel = ConformanceLevel.Fragment
|
||||
};
|
||||
|
||||
StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
|
||||
|
||||
int totalCount;
|
||||
|
||||
var dlnaOptions = _config.GetDlnaConfiguration();
|
||||
|
||||
using (var writer = XmlWriter.Create(builder, settings))
|
||||
using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
|
||||
{
|
||||
//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(id, user);
|
||||
var item = serverItem.Item;
|
||||
|
||||
if (string.Equals(flag, "BrowseMetadata"))
|
||||
var settings = new XmlWriterSettings()
|
||||
{
|
||||
totalCount = 1;
|
||||
Encoding = Encoding.UTF8,
|
||||
CloseOutput = false,
|
||||
OmitXmlDeclaration = true,
|
||||
ConformanceLevel = ConformanceLevel.Fragment
|
||||
};
|
||||
|
||||
if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
|
||||
{
|
||||
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
|
||||
using (var writer = XmlWriter.Create(builder, settings))
|
||||
{
|
||||
var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount);
|
||||
totalCount = childrenResult.TotalRecordCount;
|
||||
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
|
||||
|
||||
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;
|
||||
var displayStubType = i.StubType;
|
||||
totalCount = 1;
|
||||
|
||||
if (childItem.IsDisplayedAsFolder || displayStubType.HasValue)
|
||||
if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
|
||||
{
|
||||
var childCount = (GetUserItems(childItem, displayStubType, user, sortCriteria, null, 0))
|
||||
.TotalRecordCount;
|
||||
var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);
|
||||
|
||||
_didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter);
|
||||
_didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
|
||||
}
|
||||
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();
|
||||
//writer.WriteEndDocument();
|
||||
xmlWriter.WriteElementString("Result", builder.ToString());
|
||||
}
|
||||
|
||||
var resXML = builder.ToString();
|
||||
|
||||
return new[]
|
||||
{
|
||||
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))
|
||||
};
|
||||
xmlWriter.WriteElementString("NumberReturned", provided.ToString(CultureInfo.InvariantCulture));
|
||||
xmlWriter.WriteElementString("TotalMatches", totalCount.ToString(CultureInfo.InvariantCulture));
|
||||
xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
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
|
||||
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 sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
|
||||
|
@ -354,99 +376,86 @@ namespace Emby.Dlna.ContentDirectory
|
|||
start = startVal;
|
||||
}
|
||||
|
||||
var settings = new XmlWriterSettings
|
||||
QueryResult<BaseItem> childrenResult;
|
||||
|
||||
using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
|
||||
{
|
||||
Encoding = Encoding.UTF8,
|
||||
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)
|
||||
var settings = new XmlWriterSettings()
|
||||
{
|
||||
if (i.IsDisplayedAsFolder)
|
||||
{
|
||||
var childCount = (GetChildrenSorted(i, user, searchCriteria, sortCriteria, null, 0))
|
||||
.TotalRecordCount;
|
||||
Encoding = Encoding.UTF8,
|
||||
CloseOutput = false,
|
||||
OmitXmlDeclaration = true,
|
||||
ConformanceLevel = ConformanceLevel.Fragment
|
||||
};
|
||||
|
||||
_didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter);
|
||||
}
|
||||
else
|
||||
using (var writer = XmlWriter.Create(builder, settings))
|
||||
{
|
||||
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();
|
||||
//writer.WriteEndDocument();
|
||||
xmlWriter.WriteElementString("Result", builder.ToString());
|
||||
}
|
||||
|
||||
var resXML = builder.ToString();
|
||||
|
||||
return new List<KeyValuePair<string, string>>
|
||||
{
|
||||
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))
|
||||
};
|
||||
xmlWriter.WriteElementString("NumberReturned", childrenResult.Items.Count.ToString(CultureInfo.InvariantCulture));
|
||||
xmlWriter.WriteElementString("TotalMatches", childrenResult.TotalRecordCount.ToString(CultureInfo.InvariantCulture));
|
||||
xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
private QueryResult<BaseItem> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit)
|
||||
{
|
||||
var folder = (Folder)item;
|
||||
|
||||
var sortOrders = new List<(string, SortOrder)>();
|
||||
if (!folder.IsPreSorted)
|
||||
{
|
||||
sortOrders.Add((ItemSortBy.SortName, sort.SortOrder));
|
||||
}
|
||||
var sortOrders = folder.IsPreSorted
|
||||
? Array.Empty<(string, SortOrder)>()
|
||||
: new[] { (ItemSortBy.SortName, sort.SortOrder) };
|
||||
|
||||
var mediaTypes = new List<string>();
|
||||
string[] mediaTypes = Array.Empty<string>();
|
||||
bool? isFolder = null;
|
||||
|
||||
if (search.SearchType == SearchType.Audio)
|
||||
{
|
||||
mediaTypes.Add(MediaType.Audio);
|
||||
mediaTypes = new[] { MediaType.Audio };
|
||||
isFolder = false;
|
||||
}
|
||||
else if (search.SearchType == SearchType.Video)
|
||||
{
|
||||
mediaTypes.Add(MediaType.Video);
|
||||
mediaTypes = new[] { MediaType.Video };
|
||||
isFolder = false;
|
||||
}
|
||||
else if (search.SearchType == SearchType.Image)
|
||||
{
|
||||
mediaTypes.Add(MediaType.Photo);
|
||||
mediaTypes = new[] { MediaType.Photo };
|
||||
isFolder = false;
|
||||
}
|
||||
else if (search.SearchType == SearchType.Playlist)
|
||||
|
@ -470,7 +479,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
IsMissing = false,
|
||||
ExcludeItemTypes = new[] { typeof(Book).Name },
|
||||
IsFolder = isFolder,
|
||||
MediaTypes = mediaTypes.ToArray(),
|
||||
MediaTypes = mediaTypes,
|
||||
DtoOptions = GetDtoOptions()
|
||||
});
|
||||
}
|
||||
|
@ -514,11 +523,11 @@ namespace Emby.Dlna.ContentDirectory
|
|||
}
|
||||
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))
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -581,7 +590,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
|
||||
if (stubType.HasValue && stubType.Value == StubType.Playlists)
|
||||
{
|
||||
return GetMusicPlaylists(item, user, query);
|
||||
return GetMusicPlaylists(user, query);
|
||||
}
|
||||
|
||||
if (stubType.HasValue && stubType.Value == StubType.Albums)
|
||||
|
@ -709,7 +718,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
|
||||
if (stubType.HasValue && stubType.Value == StubType.Collections)
|
||||
{
|
||||
return GetMovieCollections(item, user, query);
|
||||
return GetMovieCollections(user, query);
|
||||
}
|
||||
|
||||
if (stubType.HasValue && stubType.Value == StubType.Favorites)
|
||||
|
@ -722,46 +731,42 @@ namespace Emby.Dlna.ContentDirectory
|
|||
return GetGenres(item, user, query);
|
||||
}
|
||||
|
||||
var list = new List<ServerItem>();
|
||||
|
||||
list.Add(new ServerItem(item)
|
||||
var array = new ServerItem[]
|
||||
{
|
||||
StubType = StubType.ContinueWatching
|
||||
});
|
||||
|
||||
list.Add(new ServerItem(item)
|
||||
{
|
||||
StubType = StubType.Latest
|
||||
});
|
||||
|
||||
list.Add(new ServerItem(item)
|
||||
{
|
||||
StubType = StubType.Movies
|
||||
});
|
||||
|
||||
list.Add(new ServerItem(item)
|
||||
{
|
||||
StubType = StubType.Collections
|
||||
});
|
||||
|
||||
list.Add(new ServerItem(item)
|
||||
{
|
||||
StubType = StubType.Favorites
|
||||
});
|
||||
|
||||
list.Add(new ServerItem(item)
|
||||
{
|
||||
StubType = StubType.Genres
|
||||
});
|
||||
new ServerItem(item)
|
||||
{
|
||||
StubType = StubType.ContinueWatching
|
||||
},
|
||||
new ServerItem(item)
|
||||
{
|
||||
StubType = StubType.Latest
|
||||
},
|
||||
new ServerItem(item)
|
||||
{
|
||||
StubType = StubType.Movies
|
||||
},
|
||||
new ServerItem(item)
|
||||
{
|
||||
StubType = StubType.Collections
|
||||
},
|
||||
new ServerItem(item)
|
||||
{
|
||||
StubType = StubType.Favorites
|
||||
},
|
||||
new ServerItem(item)
|
||||
{
|
||||
StubType = StubType.Genres
|
||||
}
|
||||
};
|
||||
|
||||
return new QueryResult<ServerItem>
|
||||
{
|
||||
Items = list,
|
||||
TotalRecordCount = list.Count
|
||||
Items = array,
|
||||
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)
|
||||
.OrderBy(i => i.SortName)
|
||||
|
@ -771,11 +776,11 @@ namespace Emby.Dlna.ContentDirectory
|
|||
})
|
||||
.ToArray();
|
||||
|
||||
return new QueryResult<ServerItem>
|
||||
return ApplyPaging(new QueryResult<ServerItem>
|
||||
{
|
||||
Items = folders,
|
||||
TotalRecordCount = folders.Length
|
||||
};
|
||||
}, startIndex, 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)
|
||||
{
|
||||
return GetNextUp(item, user, query);
|
||||
return GetNextUp(item, query);
|
||||
}
|
||||
|
||||
if (stubType.HasValue && stubType.Value == StubType.Latest)
|
||||
|
@ -912,7 +917,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
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.Parent = parent;
|
||||
|
@ -1107,7 +1112,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
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.IncludeItemTypes = new[] { typeof(Playlist).Name };
|
||||
|
@ -1136,7 +1141,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||
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)>();
|
||||
|
||||
|
@ -1291,24 +1296,24 @@ namespace Emby.Dlna.ContentDirectory
|
|||
return result;
|
||||
}
|
||||
|
||||
private ServerItem GetItemFromObjectId(string id, User user)
|
||||
private ServerItem GetItemFromObjectId(string id)
|
||||
{
|
||||
return DidlBuilder.IsIdRoot(id)
|
||||
|
||||
? new ServerItem(_libraryManager.GetUserRootFolder())
|
||||
: ParseItemId(id, user);
|
||||
: ParseItemId(id);
|
||||
}
|
||||
|
||||
private ServerItem ParseItemId(string id, User user)
|
||||
private ServerItem ParseItemId(string id)
|
||||
{
|
||||
StubType? stubType = null;
|
||||
|
||||
// After using PlayTo, MediaMonkey sends a request to the server trying to get item info
|
||||
const string paramsSrch = "Params=";
|
||||
var paramsIndex = id.IndexOf(paramsSrch, StringComparison.OrdinalIgnoreCase);
|
||||
const string ParamsSrch = "Params=";
|
||||
var paramsIndex = id.IndexOf(ParamsSrch, StringComparison.OrdinalIgnoreCase);
|
||||
if (paramsIndex != -1)
|
||||
{
|
||||
id = id.Substring(paramsIndex + paramsSrch.Length);
|
||||
id = id.Substring(paramsIndex + ParamsSrch.Length);
|
||||
|
||||
var parts = id.Split(';');
|
||||
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());
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Common;
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public class ControlResponse
|
||||
{
|
||||
public ControlResponse()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public IDictionary<string, string> Headers { get; set; }
|
||||
|
||||
public string Xml { get; set; }
|
||||
|
||||
public bool IsSuccessful { get; set; }
|
||||
|
||||
public ControlResponse()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
@ -18,7 +20,6 @@ using MediaBrowser.Controller.Playlists;
|
|||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -44,6 +45,7 @@ namespace Emby.Dlna.Didl
|
|||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public DidlBuilder(
|
||||
DeviceProfile profile,
|
||||
|
@ -55,7 +57,8 @@ namespace Emby.Dlna.Didl
|
|||
ILocalizationManager localization,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
ILogger logger,
|
||||
IMediaEncoder mediaEncoder)
|
||||
IMediaEncoder mediaEncoder,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_profile = profile;
|
||||
_user = user;
|
||||
|
@ -67,6 +70,7 @@ namespace Emby.Dlna.Didl
|
|||
_mediaSourceManager = mediaSourceManager;
|
||||
_logger = logger;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
public static string NormalizeDlnaMediaUrl(string url)
|
||||
|
@ -74,7 +78,7 @@ namespace Emby.Dlna.Didl
|
|||
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
|
||||
{
|
||||
|
@ -99,7 +103,7 @@ namespace Emby.Dlna.Didl
|
|||
|
||||
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.WriteEndDocument();
|
||||
|
@ -126,7 +130,6 @@ namespace Emby.Dlna.Didl
|
|||
}
|
||||
|
||||
public void WriteItemElement(
|
||||
DlnaOptions options,
|
||||
XmlWriter writer,
|
||||
BaseItem item,
|
||||
User user,
|
||||
|
@ -163,25 +166,23 @@ namespace Emby.Dlna.Didl
|
|||
// refID?
|
||||
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
|
||||
|
||||
var hasMediaSources = item as IHasMediaSources;
|
||||
|
||||
if (hasMediaSources != null)
|
||||
if (item is IHasMediaSources)
|
||||
{
|
||||
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))
|
||||
{
|
||||
AddVideoResource(options, writer, item, deviceId, filter, streamInfo);
|
||||
AddVideoResource(writer, item, deviceId, filter, streamInfo);
|
||||
}
|
||||
}
|
||||
|
||||
AddCover(item, context, null, writer);
|
||||
AddCover(item, null, writer);
|
||||
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)
|
||||
{
|
||||
|
@ -225,7 +226,7 @@ namespace Emby.Dlna.Didl
|
|||
|
||||
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);
|
||||
|
@ -282,7 +283,10 @@ namespace Emby.Dlna.Didl
|
|||
else
|
||||
{
|
||||
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.WriteString(info.Url);
|
||||
|
@ -292,7 +296,7 @@ namespace Emby.Dlna.Didl
|
|||
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);
|
||||
|
||||
|
@ -334,7 +338,13 @@ namespace Emby.Dlna.Didl
|
|||
{
|
||||
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.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)
|
||||
? MimeTypes.GetMimeType(filename)
|
||||
: mediaProfile.MimeType;
|
||||
|
||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
||||
"http-get:*:{0}:{1}",
|
||||
mimeType,
|
||||
contentFeatures
|
||||
));
|
||||
writer.WriteAttributeString(
|
||||
"protocolInfo",
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"http-get:*:{0}:{1}",
|
||||
mimeType,
|
||||
contentFeatures));
|
||||
|
||||
writer.WriteString(url);
|
||||
|
||||
|
@ -391,54 +403,122 @@ namespace Emby.Dlna.Didl
|
|||
{
|
||||
switch (itemStubType.Value)
|
||||
{
|
||||
case StubType.Latest: return _localization.GetLocalizedString("Latest");
|
||||
case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
|
||||
case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
|
||||
case StubType.Albums: return _localization.GetLocalizedString("Albums");
|
||||
case StubType.Artists: return _localization.GetLocalizedString("Artists");
|
||||
case StubType.Songs: return _localization.GetLocalizedString("Songs");
|
||||
case StubType.Genres: return _localization.GetLocalizedString("Genres");
|
||||
case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
|
||||
case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
|
||||
case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
|
||||
case StubType.Latest: return _localization.GetLocalizedString("Latest");
|
||||
case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
|
||||
case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
|
||||
case StubType.Albums: return _localization.GetLocalizedString("Albums");
|
||||
case StubType.Artists: return _localization.GetLocalizedString("Artists");
|
||||
case StubType.Songs: return _localization.GetLocalizedString("Songs");
|
||||
case StubType.Genres: return _localization.GetLocalizedString("Genres");
|
||||
case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
|
||||
case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
|
||||
case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
|
||||
case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching");
|
||||
case StubType.Movies: return _localization.GetLocalizedString("Movies");
|
||||
case StubType.Collections: return _localization.GetLocalizedString("Collections");
|
||||
case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
|
||||
case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
|
||||
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
|
||||
case StubType.Movies: return _localization.GetLocalizedString("Movies");
|
||||
case StubType.Collections: return _localization.GetLocalizedString("Collections");
|
||||
case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
|
||||
case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
|
||||
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
|
||||
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
|
||||
case StubType.Series: return _localization.GetLocalizedString("Shows");
|
||||
case StubType.Series: return _localization.GetLocalizedString("Shows");
|
||||
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
|
||||
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
|
||||
if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.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);
|
||||
|
||||
if (episode.IndexNumberEnd.HasValue)
|
||||
{
|
||||
number += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return number + " - " + item.Name;
|
||||
name += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -504,7 +584,7 @@ namespace Emby.Dlna.Didl
|
|||
targetSampleRate,
|
||||
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)
|
||||
? MimeTypes.GetMimeType(filename)
|
||||
|
@ -520,11 +600,13 @@ namespace Emby.Dlna.Didl
|
|||
streamInfo.RunTimeTicks ?? 0,
|
||||
streamInfo.TranscodeSeekInfo);
|
||||
|
||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
||||
"http-get:*:{0}:{1}",
|
||||
mimeType,
|
||||
contentFeatures
|
||||
));
|
||||
writer.WriteAttributeString(
|
||||
"protocolInfo",
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"http-get:*:{0}:{1}",
|
||||
mimeType,
|
||||
contentFeatures));
|
||||
|
||||
writer.WriteString(url);
|
||||
|
||||
|
@ -547,7 +629,7 @@ namespace Emby.Dlna.Didl
|
|||
|
||||
var clientId = GetClientId(folder, stubType);
|
||||
|
||||
if (string.Equals(requestedId, "0"))
|
||||
if (string.Equals(requestedId, "0", StringComparison.Ordinal))
|
||||
{
|
||||
writer.WriteAttributeString("id", "0");
|
||||
writer.WriteAttributeString("parentID", "-1");
|
||||
|
@ -576,7 +658,7 @@ namespace Emby.Dlna.Didl
|
|||
|
||||
AddGeneralProperties(folder, stubType, context, writer, filter);
|
||||
|
||||
AddCover(folder, context, stubType, writer);
|
||||
AddCover(folder, stubType, writer);
|
||||
|
||||
writer.WriteFullEndElement();
|
||||
}
|
||||
|
@ -609,7 +691,10 @@ namespace Emby.Dlna.Didl
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -632,7 +717,7 @@ namespace Emby.Dlna.Didl
|
|||
{
|
||||
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)
|
||||
{
|
||||
//var types = new[]
|
||||
//{
|
||||
// PersonType.Director,
|
||||
// PersonType.Writer,
|
||||
// PersonType.Producer,
|
||||
// PersonType.Composer,
|
||||
// "Creator"
|
||||
//};
|
||||
if (!item.SupportsPeople)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//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
|
||||
//// The actual issue might just be due to processing a more metadata than it can handle
|
||||
//var limit = 6;
|
||||
foreach (var actor in people)
|
||||
{
|
||||
var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
|
||||
?? PersonType.Actor;
|
||||
|
||||
//foreach (var actor in people)
|
||||
//{
|
||||
// var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
|
||||
// ?? PersonType.Actor;
|
||||
|
||||
// AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
|
||||
|
||||
// index++;
|
||||
|
||||
// if (index >= limit)
|
||||
// {
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -914,17 +998,8 @@ namespace Emby.Dlna.Didl
|
|||
|
||||
}
|
||||
|
||||
private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
|
||||
{
|
||||
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,
|
||||
private void AddImageResElement(
|
||||
BaseItem item,
|
||||
XmlWriter writer,
|
||||
int maxWidth,
|
||||
int maxHeight,
|
||||
|
@ -950,13 +1025,17 @@ namespace Emby.Dlna.Didl
|
|||
var contentFeatures = new ContentFeatureBuilder(_profile)
|
||||
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
|
||||
|
||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
||||
"http-get:*:{0}:{1}",
|
||||
MimeTypes.GetMimeType("file." + format),
|
||||
contentFeatures
|
||||
));
|
||||
writer.WriteAttributeString(
|
||||
"protocolInfo",
|
||||
string.Format(
|
||||
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);
|
||||
|
||||
|
@ -981,19 +1060,58 @@ namespace Emby.Dlna.Didl
|
|||
}
|
||||
}
|
||||
|
||||
item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
|
||||
|
||||
if (item != null)
|
||||
// For audio tracks without art use album art if available.
|
||||
if (item is Audio audioItem)
|
||||
{
|
||||
if (item.HasImage(ImageType.Primary))
|
||||
{
|
||||
return GetImageInfo(item, ImageType.Primary);
|
||||
}
|
||||
var album = audioItem.AlbumEntity;
|
||||
return album != null && album.HasImage(ImageType.Primary)
|
||||
? GetImageInfo(album, ImageType.Primary)
|
||||
: null;
|
||||
}
|
||||
|
||||
// Don't look beyond album/playlist level. Metadata service may assign an image from a different album/show to the parent folder.
|
||||
if (item 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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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,
|
||||
info.ItemId.ToString("N", CultureInfo.InvariantCulture),
|
||||
info.Type,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
|
||||
namespace Emby.Dlna.Didl
|
||||
{
|
||||
|
@ -16,7 +17,7 @@ namespace Emby.Dlna.Didl
|
|||
|
||||
public Filter(string filter)
|
||||
{
|
||||
_all = StringHelper.EqualsIgnoreCase(filter, "*");
|
||||
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
_fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
@ -51,6 +53,6 @@ namespace Emby.Dlna.Didl
|
|||
_encoding = encoding;
|
||||
}
|
||||
|
||||
public override Encoding Encoding => (null == _encoding) ? base.Encoding : _encoding;
|
||||
public override Encoding Encoding => _encoding ?? base.Encoding;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -385,7 +387,7 @@ namespace Emby.Dlna
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
<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>
|
||||
<Compile Include="..\SharedVersion.cs" />
|
||||
</ItemGroup>
|
||||
|
@ -15,6 +20,19 @@
|
|||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<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>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public class EventSubscriptionResponse
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public Dictionary<string, string> Headers { get; set; }
|
||||
|
||||
public EventSubscriptionResponse()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public string Content { get; set; }
|
||||
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public Dictionary<string, string> Headers { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
|
@ -164,7 +167,7 @@ namespace Emby.Dlna.Eventing
|
|||
|
||||
try
|
||||
{
|
||||
using (await _httpClient.SendAsync(options, "NOTIFY").ConfigureAwait(false))
|
||||
using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace Emby.Dlna.Eventing
|
||||
|
@ -13,6 +15,8 @@ namespace Emby.Dlna.Eventing
|
|||
|
||||
public long TriggerCount { get; set; }
|
||||
|
||||
public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
|
||||
|
||||
public void IncrementTriggerCount()
|
||||
{
|
||||
if (TriggerCount == long.MaxValue)
|
||||
|
@ -22,7 +26,5 @@ namespace Emby.Dlna.Eventing
|
|||
|
||||
TriggerCount++;
|
||||
}
|
||||
|
||||
public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public interface IUpnpService
|
||||
|
@ -13,6 +17,6 @@ namespace Emby.Dlna
|
|||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>ControlResponse.</returns>
|
||||
ControlResponse ProcessControlRequest(ControlRequest request);
|
||||
Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Globalization;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.PlayTo;
|
||||
|
@ -24,7 +26,7 @@ using MediaBrowser.Model.System;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Rssdp;
|
||||
using Rssdp.Infrastructure;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
||||
|
||||
namespace Emby.Dlna.Main
|
||||
{
|
||||
|
@ -56,7 +58,9 @@ namespace Emby.Dlna.Main
|
|||
private ISsdpCommunicationsServer _communicationsServer;
|
||||
|
||||
internal IContentDirectory ContentDirectory { get; private set; }
|
||||
|
||||
internal IConnectionManager ConnectionManager { get; private set; }
|
||||
|
||||
internal IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
|
||||
|
||||
public static DlnaEntryPoint Current;
|
||||
|
@ -104,7 +108,7 @@ namespace Emby.Dlna.Main
|
|||
libraryManager,
|
||||
config,
|
||||
userManager,
|
||||
_logger,
|
||||
loggerFactory.CreateLogger<ContentDirectory.ContentDirectory>(),
|
||||
httpClient,
|
||||
localizationManager,
|
||||
mediaSourceManager,
|
||||
|
@ -112,9 +116,16 @@ namespace Emby.Dlna.Main
|
|||
mediaEncoder,
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -251,8 +262,8 @@ namespace Emby.Dlna.Main
|
|||
{
|
||||
if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
// Not support IPv6 right now
|
||||
continue;
|
||||
// Not supporting IPv6 right now
|
||||
continue;
|
||||
}
|
||||
|
||||
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -9,35 +12,33 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
|||
{
|
||||
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)
|
||||
: 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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -9,23 +12,28 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
|||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config)
|
||||
public MediaReceiverRegistrar(
|
||||
ILogger<MediaReceiverRegistrar> logger,
|
||||
IHttpClient httpClient,
|
||||
IServerConfigurationManager config)
|
||||
: base(logger, httpClient)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return new MediaReceiverRegistrarXmlBuilder().GetXml();
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
/// <inheritdoc />
|
||||
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||
{
|
||||
return new ControlHandler(
|
||||
_config,
|
||||
Logger)
|
||||
.ProcessControlRequest(request);
|
||||
.ProcessControlRequestAsync(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Common;
|
||||
using Emby.Dlna.Service;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Common;
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -221,7 +223,7 @@ namespace Emby.Dlna.PlayTo
|
|||
_logger.LogDebug("Setting mute");
|
||||
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);
|
||||
|
||||
IsMuted = mute;
|
||||
|
@ -251,7 +253,7 @@ namespace Emby.Dlna.PlayTo
|
|||
// Remote control will perform better
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -270,7 +272,7 @@ namespace Emby.Dlna.PlayTo
|
|||
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);
|
||||
|
||||
RestartTimer(true);
|
||||
|
@ -302,7 +304,7 @@ namespace Emby.Dlna.PlayTo
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
|
@ -344,7 +346,12 @@ namespace Emby.Dlna.PlayTo
|
|||
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)
|
||||
|
@ -368,7 +375,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
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);
|
||||
|
||||
RestartTimer(true);
|
||||
|
@ -386,7 +393,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
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);
|
||||
|
||||
TransportState = TRANSPORTSTATE.PAUSED;
|
||||
|
@ -513,8 +520,12 @@ namespace Emby.Dlna.PlayTo
|
|||
return;
|
||||
}
|
||||
|
||||
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
|
||||
.ConfigureAwait(false);
|
||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
command.Name,
|
||||
rendererCommands.BuildPost(command, service.ServiceType),
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (result == null || result.Document == null)
|
||||
{
|
||||
|
@ -559,8 +570,12 @@ namespace Emby.Dlna.PlayTo
|
|||
return;
|
||||
}
|
||||
|
||||
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
|
||||
.ConfigureAwait(false);
|
||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
command.Name,
|
||||
rendererCommands.BuildPost(command, service.ServiceType),
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (result == null || result.Document == null)
|
||||
return;
|
||||
|
@ -586,8 +601,12 @@ namespace Emby.Dlna.PlayTo
|
|||
return null;
|
||||
}
|
||||
|
||||
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false)
|
||||
.ConfigureAwait(false);
|
||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
command.Name,
|
||||
avCommands.BuildPost(command, service.ServiceType),
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (result == null || result.Document == null)
|
||||
{
|
||||
|
@ -597,7 +616,7 @@ namespace Emby.Dlna.PlayTo
|
|||
var transportState =
|
||||
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
|
||||
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
|
||||
|
@ -624,8 +643,12 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
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)
|
||||
.ConfigureAwait(false);
|
||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
command.Name,
|
||||
rendererCommands.BuildPost(command, service.ServiceType),
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (result == null || result.Document == null)
|
||||
{
|
||||
|
@ -687,8 +710,12 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
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)
|
||||
.ConfigureAwait(false);
|
||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
command.Name,
|
||||
rendererCommands.BuildPost(command, service.ServiceType),
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (result == null || result.Document == null)
|
||||
{
|
||||
|
@ -868,7 +895,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
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);
|
||||
|
||||
|
@ -896,7 +923,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
||||
|
||||
var httpClient = new SsdpHttpClient(_httpClient, _config);
|
||||
var httpClient = new SsdpHttpClient(_httpClient);
|
||||
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
||||
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)
|
||||
{
|
||||
var ssdpHttpClient = new SsdpHttpClient(httpClient, config);
|
||||
var ssdpHttpClient = new SsdpHttpClient(httpClient);
|
||||
|
||||
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Common;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
@ -25,6 +27,8 @@ namespace Emby.Dlna.PlayTo
|
|||
{
|
||||
public class PlayToController : ISessionController, IDisposable
|
||||
{
|
||||
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
||||
|
||||
private Device _device;
|
||||
private readonly SessionInfo _session;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
|
@ -43,9 +47,10 @@ namespace Emby.Dlna.PlayTo
|
|||
private readonly string _serverAddress;
|
||||
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(
|
||||
SessionInfo session,
|
||||
|
@ -81,18 +86,22 @@ namespace Emby.Dlna.PlayTo
|
|||
_mediaEncoder = mediaEncoder;
|
||||
}
|
||||
|
||||
public bool IsSessionActive => !_disposed && _device != null;
|
||||
|
||||
public bool SupportsMediaControl => IsSessionActive;
|
||||
|
||||
public void Init(Device device)
|
||||
{
|
||||
_device = device;
|
||||
_device.OnDeviceUnavailable = OnDeviceUnavailable;
|
||||
_device.PlaybackStart += _device_PlaybackStart;
|
||||
_device.PlaybackProgress += _device_PlaybackProgress;
|
||||
_device.PlaybackStopped += _device_PlaybackStopped;
|
||||
_device.MediaChanged += _device_MediaChanged;
|
||||
_device.PlaybackStart += OnDevicePlaybackStart;
|
||||
_device.PlaybackProgress += OnDevicePlaybackProgress;
|
||||
_device.PlaybackStopped += OnDevicePlaybackStopped;
|
||||
_device.MediaChanged += OnDeviceMediaChanged;
|
||||
|
||||
_device.Start();
|
||||
|
||||
_deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
|
||||
_deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -135,15 +144,15 @@ namespace Emby.Dlna.PlayTo
|
|||
var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager);
|
||||
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);
|
||||
if (streamInfo.Item == null) return;
|
||||
|
||||
var newItemProgress = GetProgressInfo(e.NewMediaInfo, streamInfo);
|
||||
var newItemProgress = GetProgressInfo(streamInfo);
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -166,9 +175,9 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
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);
|
||||
|
||||
|
@ -192,7 +201,7 @@ namespace Emby.Dlna.PlayTo
|
|||
}
|
||||
else
|
||||
{
|
||||
Playlist.Clear();
|
||||
_playlist.Clear();
|
||||
}
|
||||
}
|
||||
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
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -233,7 +242,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
if (info.Item != null)
|
||||
{
|
||||
var progress = GetProgressInfo(e.MediaInfo, info);
|
||||
var progress = GetProgressInfo(info);
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -264,7 +273,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
if (info.Item != null)
|
||||
{
|
||||
var progress = GetProgressInfo(e.MediaInfo, info);
|
||||
var progress = GetProgressInfo(info);
|
||||
|
||||
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;
|
||||
|
||||
|
@ -287,13 +296,13 @@ namespace Emby.Dlna.PlayTo
|
|||
return ticks;
|
||||
}
|
||||
|
||||
private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo, StreamParams info)
|
||||
private PlaybackStartInfo GetProgressInfo(StreamParams info)
|
||||
{
|
||||
return new PlaybackStartInfo
|
||||
{
|
||||
ItemId = info.ItemId,
|
||||
SessionId = _session.Id,
|
||||
PositionTicks = GetProgressPositionTicks(mediaInfo, info),
|
||||
PositionTicks = GetProgressPositionTicks(info),
|
||||
IsMuted = _device.IsMuted,
|
||||
IsPaused = _device.IsPaused,
|
||||
MediaSourceId = info.MediaSourceId,
|
||||
|
@ -308,9 +317,7 @@ namespace Emby.Dlna.PlayTo
|
|||
};
|
||||
}
|
||||
|
||||
#region SendCommands
|
||||
|
||||
public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
|
||||
public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
|
||||
|
||||
|
@ -348,11 +355,12 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
if (command.PlayCommand == PlayCommand.PlayLast)
|
||||
{
|
||||
Playlist.AddRange(playlist);
|
||||
_playlist.AddRange(playlist);
|
||||
}
|
||||
|
||||
if (command.PlayCommand == PlayCommand.PlayNext)
|
||||
{
|
||||
Playlist.AddRange(playlist);
|
||||
_playlist.AddRange(playlist);
|
||||
}
|
||||
|
||||
if (!command.ControllingUserId.Equals(Guid.Empty))
|
||||
|
@ -361,7 +369,7 @@ namespace Emby.Dlna.PlayTo
|
|||
_session.DeviceName, _session.RemoteEndPoint, user);
|
||||
}
|
||||
|
||||
await PlayItems(playlist).ConfigureAwait(false);
|
||||
return PlayItems(playlist, cancellationToken);
|
||||
}
|
||||
|
||||
private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
|
||||
|
@ -369,7 +377,7 @@ namespace Emby.Dlna.PlayTo
|
|||
switch (command.Command)
|
||||
{
|
||||
case PlaystateCommand.Stop:
|
||||
Playlist.Clear();
|
||||
_playlist.Clear();
|
||||
return _device.SetStop(CancellationToken.None);
|
||||
|
||||
case PlaystateCommand.Pause:
|
||||
|
@ -385,10 +393,10 @@ namespace Emby.Dlna.PlayTo
|
|||
return Seek(command.SeekPositionTicks ?? 0);
|
||||
|
||||
case PlaystateCommand.NextTrack:
|
||||
return SetPlaylistIndex(_currentPlaylistIndex + 1);
|
||||
return SetPlaylistIndex(_currentPlaylistIndex + 1, cancellationToken);
|
||||
|
||||
case PlaystateCommand.PreviousTrack:
|
||||
return SetPlaylistIndex(_currentPlaylistIndex - 1);
|
||||
return SetPlaylistIndex(_currentPlaylistIndex - 1, cancellationToken);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
@ -424,14 +432,6 @@ namespace Emby.Dlna.PlayTo
|
|||
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)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
|
@ -449,7 +449,7 @@ namespace Emby.Dlna.PlayTo
|
|||
_dlnaManager.GetDefaultProfile();
|
||||
|
||||
var mediaSources = item is IHasMediaSources
|
||||
? (_mediaSourceManager.GetStaticMediaSources(item, true, user))
|
||||
? _mediaSourceManager.GetStaticMediaSources(item, true, user)
|
||||
: new List<MediaSourceInfo>();
|
||||
|
||||
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));
|
||||
|
||||
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _mediaEncoder)
|
||||
.GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
|
||||
var itemXml = new DidlBuilder(
|
||||
profile,
|
||||
user,
|
||||
_imageProcessor,
|
||||
_serverAddress,
|
||||
_accessToken,
|
||||
_userDataManager,
|
||||
_localization,
|
||||
_mediaSourceManager,
|
||||
_logger,
|
||||
_mediaEncoder,
|
||||
_libraryManager)
|
||||
.GetItemDidl(item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
|
||||
|
||||
playlistItem.Didl = itemXml;
|
||||
|
||||
|
@ -568,30 +579,31 @@ namespace Emby.Dlna.PlayTo
|
|||
/// Plays the items.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns><c>true</c> on success.</returns>
|
||||
private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Playlist.Clear();
|
||||
Playlist.AddRange(items);
|
||||
_logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, Playlist.Count);
|
||||
_playlist.Clear();
|
||||
_playlist.AddRange(items);
|
||||
_logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, _playlist.Count);
|
||||
|
||||
await SetPlaylistIndex(0).ConfigureAwait(false);
|
||||
await SetPlaylistIndex(0, cancellationToken).ConfigureAwait(false);
|
||||
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();
|
||||
await _device.SetStop(CancellationToken.None);
|
||||
_playlist.Clear();
|
||||
await _device.SetStop(cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_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;
|
||||
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
|
||||
|
@ -600,10 +612,7 @@ namespace Emby.Dlna.PlayTo
|
|||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
|
@ -622,19 +631,17 @@ namespace Emby.Dlna.PlayTo
|
|||
_device.Dispose();
|
||||
}
|
||||
|
||||
_device.PlaybackStart -= _device_PlaybackStart;
|
||||
_device.PlaybackProgress -= _device_PlaybackProgress;
|
||||
_device.PlaybackStopped -= _device_PlaybackStopped;
|
||||
_device.MediaChanged -= _device_MediaChanged;
|
||||
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
||||
_device.PlaybackStart -= OnDevicePlaybackStart;
|
||||
_device.PlaybackProgress -= OnDevicePlaybackProgress;
|
||||
_device.PlaybackStopped -= OnDevicePlaybackStopped;
|
||||
_device.MediaChanged -= OnDeviceMediaChanged;
|
||||
_deviceDiscovery.DeviceLeft -= OnDeviceDiscoveryDeviceLeft;
|
||||
_device.OnDeviceUnavailable = null;
|
||||
_device = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
||||
|
||||
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType))
|
||||
|
@ -711,7 +718,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
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 newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex);
|
||||
|
@ -736,7 +743,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
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 newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex);
|
||||
|
@ -850,8 +857,11 @@ namespace Emby.Dlna.PlayTo
|
|||
return request;
|
||||
}
|
||||
|
||||
var index = url.IndexOf('?');
|
||||
if (index == -1) return request;
|
||||
var index = url.IndexOf('?', StringComparison.Ordinal);
|
||||
if (index == -1)
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
var query = url.Substring(index + 1);
|
||||
Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
@ -21,7 +23,7 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
public class PlayToManager : IDisposable
|
||||
public sealed class PlayToManager : IDisposable
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
|
@ -64,10 +66,10 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
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)
|
||||
{
|
||||
|
@ -229,9 +231,10 @@ namespace Emby.Dlna.PlayTo
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
|
||||
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -242,6 +245,9 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
}
|
||||
|
||||
_sessionLock.Dispose();
|
||||
_disposeCancellationTokenSource.Dispose();
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Globalization;
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Emby.Dlna.Common;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
|
@ -19,12 +21,10 @@ namespace Emby.Dlna.PlayTo
|
|||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
public SsdpHttpClient(IHttpClient httpClient, IServerConfigurationManager config)
|
||||
public SsdpHttpClient(IHttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public async Task<XDocument> SendCommandAsync(
|
||||
|
@ -32,18 +32,15 @@ namespace Emby.Dlna.PlayTo
|
|||
DeviceService service,
|
||||
string command,
|
||||
string postData,
|
||||
bool logRequest = true,
|
||||
string header = null)
|
||||
string header = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var cancellationToken = CancellationToken.None;
|
||||
|
||||
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
|
||||
using (var response = await PostSoapDataAsync(
|
||||
url,
|
||||
$"\"{service.ServiceType}#{command}\"",
|
||||
postData,
|
||||
header,
|
||||
logRequest,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
using (var stream = response.Content)
|
||||
|
@ -63,8 +60,10 @@ namespace Emby.Dlna.PlayTo
|
|||
return serviceUrl;
|
||||
}
|
||||
|
||||
if (!serviceUrl.StartsWith("/"))
|
||||
if (!serviceUrl.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
serviceUrl = "/" + serviceUrl;
|
||||
}
|
||||
|
||||
return baseUrl + serviceUrl;
|
||||
}
|
||||
|
@ -90,7 +89,7 @@ namespace Emby.Dlna.PlayTo
|
|||
options.RequestHeaders["NT"] = "upnp:event";
|
||||
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;
|
||||
|
||||
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 reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
|
@ -125,7 +124,6 @@ namespace Emby.Dlna.PlayTo
|
|||
string soapAction,
|
||||
string postData,
|
||||
string header,
|
||||
bool logRequest,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (soapAction[0] != '\"')
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
public enum TRANSPORTSTATE
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Emby.Dlna.Ssdp;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
|
@ -10,7 +12,7 @@ namespace Emby.Dlna.Profiles
|
|||
{
|
||||
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";
|
||||
ModelDescription = "UPnP/AV 1.0 Compliant Media Server";
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Dlna.Profiles
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user