Merge pull request #3691 from crobibero/api-video
move VideoService.cs to Jellyfin.Api
This commit is contained in:
commit
cb31aba5dd
|
@ -35,13 +35,12 @@ namespace Jellyfin.Api.Controllers
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IStreamHelper _streamHelper;
|
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly ISubtitleEncoder _subtitleEncoder;
|
private readonly ISubtitleEncoder _subtitleEncoder;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly IDeviceManager _deviceManager;
|
private readonly IDeviceManager _deviceManager;
|
||||||
private readonly TranscodingJobHelper _transcodingJobHelper;
|
private readonly TranscodingJobHelper _transcodingJobHelper;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Progressive;
|
private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Progressive;
|
||||||
|
|
||||||
|
@ -55,13 +54,12 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
||||||
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||||
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
||||||
/// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
|
|
||||||
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
||||||
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
|
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
|
||||||
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
|
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
|
||||||
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
|
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
|
||||||
/// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
|
/// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
|
||||||
/// <param name="httpClient">Instance of the <see cref="HttpClient"/>.</param>
|
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
||||||
public AudioController(
|
public AudioController(
|
||||||
IDlnaManager dlnaManager,
|
IDlnaManager dlnaManager,
|
||||||
IUserManager userManger,
|
IUserManager userManger,
|
||||||
|
@ -70,13 +68,12 @@ namespace Jellyfin.Api.Controllers
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
IServerConfigurationManager serverConfigurationManager,
|
IServerConfigurationManager serverConfigurationManager,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IStreamHelper streamHelper,
|
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
ISubtitleEncoder subtitleEncoder,
|
ISubtitleEncoder subtitleEncoder,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
IDeviceManager deviceManager,
|
IDeviceManager deviceManager,
|
||||||
TranscodingJobHelper transcodingJobHelper,
|
TranscodingJobHelper transcodingJobHelper,
|
||||||
HttpClient httpClient)
|
IHttpClientFactory httpClientFactory)
|
||||||
{
|
{
|
||||||
_dlnaManager = dlnaManager;
|
_dlnaManager = dlnaManager;
|
||||||
_authContext = authorizationContext;
|
_authContext = authorizationContext;
|
||||||
|
@ -85,13 +82,12 @@ namespace Jellyfin.Api.Controllers
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_serverConfigurationManager = serverConfigurationManager;
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_streamHelper = streamHelper;
|
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_subtitleEncoder = subtitleEncoder;
|
_subtitleEncoder = subtitleEncoder;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_deviceManager = deviceManager;
|
_deviceManager = deviceManager;
|
||||||
_transcodingJobHelper = transcodingJobHelper;
|
_transcodingJobHelper = transcodingJobHelper;
|
||||||
_httpClient = httpClient;
|
_httpClientFactory = httpClientFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -146,6 +142,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
|
/// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
|
||||||
/// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
|
/// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
|
||||||
/// <param name="streamOptions">Optional. The streaming options.</param>
|
/// <param name="streamOptions">Optional. The streaming options.</param>
|
||||||
|
/// <response code="200">Audio stream returned.</response>
|
||||||
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
|
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
|
||||||
[HttpGet("{itemId}/{stream=stream}.{container?}")]
|
[HttpGet("{itemId}/{stream=stream}.{container?}")]
|
||||||
[HttpGet("{itemId}/stream")]
|
[HttpGet("{itemId}/stream")]
|
||||||
|
@ -211,7 +208,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static.HasValue ? @static.Value : true,
|
Static = @static ?? true,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
|
@ -222,10 +219,10 @@ namespace Jellyfin.Api.Controllers
|
||||||
MediaSourceId = mediaSourceId,
|
MediaSourceId = mediaSourceId,
|
||||||
DeviceId = deviceId,
|
DeviceId = deviceId,
|
||||||
AudioCodec = audioCodec,
|
AudioCodec = audioCodec,
|
||||||
EnableAutoStreamCopy = enableAutoStreamCopy.HasValue ? enableAutoStreamCopy.Value : true,
|
EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
|
||||||
AllowAudioStreamCopy = allowAudioStreamCopy.HasValue ? allowAudioStreamCopy.Value : true,
|
AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
|
||||||
AllowVideoStreamCopy = allowVideoStreamCopy.HasValue ? allowVideoStreamCopy.Value : true,
|
AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
|
||||||
BreakOnNonKeyFrames = breakOnNonKeyFrames.HasValue ? breakOnNonKeyFrames.Value : false,
|
BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
|
||||||
AudioSampleRate = audioSampleRate,
|
AudioSampleRate = audioSampleRate,
|
||||||
MaxAudioChannels = maxAudioChannels,
|
MaxAudioChannels = maxAudioChannels,
|
||||||
AudioBitRate = audioBitRate,
|
AudioBitRate = audioBitRate,
|
||||||
|
@ -235,7 +232,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps.HasValue ? copyTimestamps.Value : true,
|
CopyTimestamps = copyTimestamps ?? true,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
|
@ -244,13 +241,13 @@ namespace Jellyfin.Api.Controllers
|
||||||
SubtitleMethod = subtitleMethod,
|
SubtitleMethod = subtitleMethod,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc.HasValue ? requireAvc.Value : true,
|
RequireAvc = requireAvc ?? true,
|
||||||
DeInterlace = deInterlace.HasValue ? deInterlace.Value : true,
|
DeInterlace = deInterlace ?? true,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic.HasValue ? requireNonAnamorphic.Value : true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode.HasValue ? enableMpegtsM2TsMode.Value : true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodingReasons,
|
TranscodeReasons = transcodingReasons,
|
||||||
|
@ -283,8 +280,11 @@ namespace Jellyfin.Api.Controllers
|
||||||
{
|
{
|
||||||
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
|
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
|
||||||
|
|
||||||
// TODO AllowEndOfFile = false
|
await new ProgressiveFileCopier(state.DirectStreamProvider, null, _transcodingJobHelper, CancellationToken.None)
|
||||||
await new ProgressiveFileCopier(_streamHelper, state.DirectStreamProvider).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false);
|
{
|
||||||
|
AllowEndOfFile = false
|
||||||
|
}.WriteToAsync(Response.Body, CancellationToken.None)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
// TODO (moved from MediaBrowser.Api): Don't hardcode contentType
|
// TODO (moved from MediaBrowser.Api): Don't hardcode contentType
|
||||||
return File(Response.Body, MimeTypes.GetMimeType("file.ts")!);
|
return File(Response.Body, MimeTypes.GetMimeType("file.ts")!);
|
||||||
|
@ -295,7 +295,8 @@ namespace Jellyfin.Api.Controllers
|
||||||
{
|
{
|
||||||
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
|
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
|
||||||
|
|
||||||
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, _httpClient).ConfigureAwait(false);
|
using var httpClient = _httpClientFactory.CreateClient();
|
||||||
|
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, httpClient).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (@static.HasValue && @static.Value && state.InputProtocol != MediaProtocol.File)
|
if (@static.HasValue && @static.Value && state.InputProtocol != MediaProtocol.File)
|
||||||
|
@ -318,8 +319,11 @@ namespace Jellyfin.Api.Controllers
|
||||||
|
|
||||||
if (state.MediaSource.IsInfiniteStream)
|
if (state.MediaSource.IsInfiniteStream)
|
||||||
{
|
{
|
||||||
// TODO AllowEndOfFile = false
|
await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None)
|
||||||
await new ProgressiveFileCopier(_streamHelper, state.MediaPath).WriteToAsync(Response.Body, CancellationToken.None).ConfigureAwait(false);
|
{
|
||||||
|
AllowEndOfFile = false
|
||||||
|
}.WriteToAsync(Response.Body, CancellationToken.None)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
return File(Response.Body, contentType);
|
return File(Response.Body, contentType);
|
||||||
}
|
}
|
||||||
|
@ -338,7 +342,6 @@ namespace Jellyfin.Api.Controllers
|
||||||
return await FileStreamResponseHelpers.GetTranscodedFile(
|
return await FileStreamResponseHelpers.GetTranscodedFile(
|
||||||
state,
|
state,
|
||||||
isHeadRequest,
|
isHeadRequest,
|
||||||
_streamHelper,
|
|
||||||
this,
|
this,
|
||||||
_transcodingJobHelper,
|
_transcodingJobHelper,
|
||||||
ffmpegCommandLineArguments,
|
ffmpegCommandLineArguments,
|
||||||
|
|
|
@ -24,7 +24,6 @@ using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
|
@ -45,9 +44,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IDtoService _dtoService;
|
private readonly IDtoService _dtoService;
|
||||||
private readonly ISessionContext _sessionContext;
|
private readonly ISessionContext _sessionContext;
|
||||||
private readonly IStreamHelper _streamHelper;
|
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IConfigurationManager _configurationManager;
|
private readonly IConfigurationManager _configurationManager;
|
||||||
|
private readonly TranscodingJobHelper _transcodingJobHelper;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LiveTvController"/> class.
|
/// Initializes a new instance of the <see cref="LiveTvController"/> class.
|
||||||
|
@ -58,9 +57,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||||
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
|
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
|
||||||
/// <param name="sessionContext">Instance of the <see cref="ISessionContext"/> interface.</param>
|
/// <param name="sessionContext">Instance of the <see cref="ISessionContext"/> interface.</param>
|
||||||
/// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
|
|
||||||
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
||||||
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
|
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
|
||||||
|
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param>
|
||||||
public LiveTvController(
|
public LiveTvController(
|
||||||
ILiveTvManager liveTvManager,
|
ILiveTvManager liveTvManager,
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
|
@ -68,9 +67,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
IDtoService dtoService,
|
IDtoService dtoService,
|
||||||
ISessionContext sessionContext,
|
ISessionContext sessionContext,
|
||||||
IStreamHelper streamHelper,
|
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
IConfigurationManager configurationManager)
|
IConfigurationManager configurationManager,
|
||||||
|
TranscodingJobHelper transcodingJobHelper)
|
||||||
{
|
{
|
||||||
_liveTvManager = liveTvManager;
|
_liveTvManager = liveTvManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
@ -78,9 +77,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
_sessionContext = sessionContext;
|
_sessionContext = sessionContext;
|
||||||
_streamHelper = streamHelper;
|
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_configurationManager = configurationManager;
|
_configurationManager = configurationManager;
|
||||||
|
_transcodingJobHelper = transcodingJobHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1187,7 +1186,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var memoryStream = new MemoryStream();
|
await using var memoryStream = new MemoryStream();
|
||||||
await new ProgressiveFileCopier(_streamHelper, path).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false);
|
await new ProgressiveFileCopier(path, null, _transcodingJobHelper, CancellationToken.None)
|
||||||
|
.WriteToAsync(memoryStream, CancellationToken.None)
|
||||||
|
.ConfigureAwait(false);
|
||||||
return File(memoryStream, MimeTypes.GetMimeType(path));
|
return File(memoryStream, MimeTypes.GetMimeType(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1214,7 +1215,9 @@ namespace Jellyfin.Api.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var memoryStream = new MemoryStream();
|
await using var memoryStream = new MemoryStream();
|
||||||
await new ProgressiveFileCopier(_streamHelper, liveStreamInfo).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false);
|
await new ProgressiveFileCopier(liveStreamInfo, null, _transcodingJobHelper, CancellationToken.None)
|
||||||
|
.WriteToAsync(memoryStream, CancellationToken.None)
|
||||||
|
.ConfigureAwait(false);
|
||||||
return File(memoryStream, MimeTypes.GetMimeType("file." + container));
|
return File(memoryStream, MimeTypes.GetMimeType("file." + container));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Api.Constants;
|
using Jellyfin.Api.Constants;
|
||||||
|
using Jellyfin.Api.Models.VideoDtos;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
@ -126,7 +127,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] string? mediaSourceId,
|
[FromQuery] string? mediaSourceId,
|
||||||
[FromQuery] string? liveStreamId,
|
[FromQuery] string? liveStreamId,
|
||||||
[FromQuery] DeviceProfile? deviceProfile,
|
[FromBody] DeviceProfileDto? deviceProfile,
|
||||||
[FromQuery] bool autoOpenLiveStream = false,
|
[FromQuery] bool autoOpenLiveStream = false,
|
||||||
[FromQuery] bool enableDirectPlay = true,
|
[FromQuery] bool enableDirectPlay = true,
|
||||||
[FromQuery] bool enableDirectStream = true,
|
[FromQuery] bool enableDirectStream = true,
|
||||||
|
@ -136,7 +137,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
{
|
{
|
||||||
var authInfo = _authContext.GetAuthorizationInfo(Request);
|
var authInfo = _authContext.GetAuthorizationInfo(Request);
|
||||||
|
|
||||||
var profile = deviceProfile;
|
var profile = deviceProfile?.DeviceProfile;
|
||||||
|
|
||||||
_logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile);
|
_logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile);
|
||||||
|
|
||||||
|
@ -190,7 +191,7 @@ namespace Jellyfin.Api.Controllers
|
||||||
var openStreamResult = await OpenMediaSource(new LiveStreamRequest
|
var openStreamResult = await OpenMediaSource(new LiveStreamRequest
|
||||||
{
|
{
|
||||||
AudioStreamIndex = audioStreamIndex,
|
AudioStreamIndex = audioStreamIndex,
|
||||||
DeviceProfile = deviceProfile,
|
DeviceProfile = deviceProfile?.DeviceProfile,
|
||||||
EnableDirectPlay = enableDirectPlay,
|
EnableDirectPlay = enableDirectPlay,
|
||||||
EnableDirectStream = enableDirectStream,
|
EnableDirectStream = enableDirectStream,
|
||||||
ItemId = itemId,
|
ItemId = itemId,
|
||||||
|
|
|
@ -1,19 +1,34 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Api.Constants;
|
using Jellyfin.Api.Constants;
|
||||||
using Jellyfin.Api.Extensions;
|
using Jellyfin.Api.Extensions;
|
||||||
using Jellyfin.Api.Helpers;
|
using Jellyfin.Api.Helpers;
|
||||||
|
using Jellyfin.Api.Models.StreamingDtos;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Controller.Devices;
|
||||||
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
namespace Jellyfin.Api.Controllers
|
namespace Jellyfin.Api.Controllers
|
||||||
{
|
{
|
||||||
|
@ -26,6 +41,19 @@ namespace Jellyfin.Api.Controllers
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IDtoService _dtoService;
|
private readonly IDtoService _dtoService;
|
||||||
|
private readonly IDlnaManager _dlnaManager;
|
||||||
|
private readonly IAuthorizationContext _authContext;
|
||||||
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
|
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||||
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly ISubtitleEncoder _subtitleEncoder;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
private readonly IDeviceManager _deviceManager;
|
||||||
|
private readonly TranscodingJobHelper _transcodingJobHelper;
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
|
private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Progressive;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="VideosController"/> class.
|
/// Initializes a new instance of the <see cref="VideosController"/> class.
|
||||||
|
@ -33,14 +61,47 @@ namespace Jellyfin.Api.Controllers
|
||||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||||
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
|
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
|
||||||
|
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
|
||||||
|
/// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
|
||||||
|
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
|
||||||
|
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||||
|
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
||||||
|
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
||||||
|
/// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
|
||||||
|
/// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
|
||||||
|
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
|
||||||
|
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param>
|
||||||
|
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
||||||
public VideosController(
|
public VideosController(
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
IDtoService dtoService)
|
IDtoService dtoService,
|
||||||
|
IDlnaManager dlnaManager,
|
||||||
|
IAuthorizationContext authContext,
|
||||||
|
IMediaSourceManager mediaSourceManager,
|
||||||
|
IServerConfigurationManager serverConfigurationManager,
|
||||||
|
IMediaEncoder mediaEncoder,
|
||||||
|
IFileSystem fileSystem,
|
||||||
|
ISubtitleEncoder subtitleEncoder,
|
||||||
|
IConfiguration configuration,
|
||||||
|
IDeviceManager deviceManager,
|
||||||
|
TranscodingJobHelper transcodingJobHelper,
|
||||||
|
IHttpClientFactory httpClientFactory)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
|
_dlnaManager = dlnaManager;
|
||||||
|
_authContext = authContext;
|
||||||
|
_mediaSourceManager = mediaSourceManager;
|
||||||
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
|
_mediaEncoder = mediaEncoder;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
_subtitleEncoder = subtitleEncoder;
|
||||||
|
_configuration = configuration;
|
||||||
|
_deviceManager = deviceManager;
|
||||||
|
_transcodingJobHelper = transcodingJobHelper;
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -200,5 +261,263 @@ namespace Jellyfin.Api.Controllers
|
||||||
primaryVersion.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
|
primaryVersion.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a video stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="itemId">The item id.</param>
|
||||||
|
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
|
||||||
|
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
|
||||||
|
/// <param name="params">The streaming parameters.</param>
|
||||||
|
/// <param name="tag">The tag.</param>
|
||||||
|
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
|
||||||
|
/// <param name="playSessionId">The play session id.</param>
|
||||||
|
/// <param name="segmentContainer">The segment container.</param>
|
||||||
|
/// <param name="segmentLength">The segment lenght.</param>
|
||||||
|
/// <param name="minSegments">The minimum number of segments.</param>
|
||||||
|
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
|
||||||
|
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
|
||||||
|
/// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
|
||||||
|
/// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
|
||||||
|
/// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
|
||||||
|
/// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
|
||||||
|
/// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
|
||||||
|
/// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
|
||||||
|
/// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
|
||||||
|
/// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
|
||||||
|
/// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
|
||||||
|
/// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
|
||||||
|
/// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
|
||||||
|
/// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
|
||||||
|
/// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
|
||||||
|
/// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
|
||||||
|
/// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
|
||||||
|
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
|
||||||
|
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
|
||||||
|
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
|
||||||
|
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
|
||||||
|
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
|
||||||
|
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
|
||||||
|
/// <param name="maxRefFrames">Optional.</param>
|
||||||
|
/// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
|
||||||
|
/// <param name="requireAvc">Optional. Whether to require avc.</param>
|
||||||
|
/// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
|
||||||
|
/// <param name="requireNonAnamorphic">Optional. Whether to require a non anamporphic stream.</param>
|
||||||
|
/// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
|
||||||
|
/// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
|
||||||
|
/// <param name="liveStreamId">The live stream id.</param>
|
||||||
|
/// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
|
||||||
|
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.</param>
|
||||||
|
/// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
|
||||||
|
/// <param name="transcodingReasons">Optional. The transcoding reason.</param>
|
||||||
|
/// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
|
||||||
|
/// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
|
||||||
|
/// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
|
||||||
|
/// <param name="streamOptions">Optional. The streaming options.</param>
|
||||||
|
/// <response code="200">Video stream returned.</response>
|
||||||
|
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
|
||||||
|
[HttpGet("{itemId}/{stream=stream}.{container?}")]
|
||||||
|
[HttpGet("{itemId}/stream")]
|
||||||
|
[HttpHead("{itemId}/{stream=stream}.{container?}")]
|
||||||
|
[HttpHead("{itemId}/stream")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
public async Task<ActionResult> GetVideoStream(
|
||||||
|
[FromRoute] Guid itemId,
|
||||||
|
[FromRoute] string? container,
|
||||||
|
[FromQuery] bool? @static,
|
||||||
|
[FromQuery] string? @params,
|
||||||
|
[FromQuery] string? tag,
|
||||||
|
[FromQuery] string? deviceProfileId,
|
||||||
|
[FromQuery] string? playSessionId,
|
||||||
|
[FromQuery] string? segmentContainer,
|
||||||
|
[FromQuery] int? segmentLength,
|
||||||
|
[FromQuery] int? minSegments,
|
||||||
|
[FromQuery] string? mediaSourceId,
|
||||||
|
[FromQuery] string? deviceId,
|
||||||
|
[FromQuery] string? audioCodec,
|
||||||
|
[FromQuery] bool? enableAutoStreamCopy,
|
||||||
|
[FromQuery] bool? allowVideoStreamCopy,
|
||||||
|
[FromQuery] bool? allowAudioStreamCopy,
|
||||||
|
[FromQuery] bool? breakOnNonKeyFrames,
|
||||||
|
[FromQuery] int? audioSampleRate,
|
||||||
|
[FromQuery] int? maxAudioBitDepth,
|
||||||
|
[FromQuery] int? audioBitRate,
|
||||||
|
[FromQuery] int? audioChannels,
|
||||||
|
[FromQuery] int? maxAudioChannels,
|
||||||
|
[FromQuery] string? profile,
|
||||||
|
[FromQuery] string? level,
|
||||||
|
[FromQuery] float? framerate,
|
||||||
|
[FromQuery] float? maxFramerate,
|
||||||
|
[FromQuery] bool? copyTimestamps,
|
||||||
|
[FromQuery] long? startTimeTicks,
|
||||||
|
[FromQuery] int? width,
|
||||||
|
[FromQuery] int? height,
|
||||||
|
[FromQuery] int? videoBitRate,
|
||||||
|
[FromQuery] int? subtitleStreamIndex,
|
||||||
|
[FromQuery] SubtitleDeliveryMethod subtitleMethod,
|
||||||
|
[FromQuery] int? maxRefFrames,
|
||||||
|
[FromQuery] int? maxVideoBitDepth,
|
||||||
|
[FromQuery] bool? requireAvc,
|
||||||
|
[FromQuery] bool? deInterlace,
|
||||||
|
[FromQuery] bool? requireNonAnamorphic,
|
||||||
|
[FromQuery] int? transcodingMaxAudioChannels,
|
||||||
|
[FromQuery] int? cpuCoreLimit,
|
||||||
|
[FromQuery] string? liveStreamId,
|
||||||
|
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||||
|
[FromQuery] string? videoCodec,
|
||||||
|
[FromQuery] string? subtitleCodec,
|
||||||
|
[FromQuery] string? transcodingReasons,
|
||||||
|
[FromQuery] int? audioStreamIndex,
|
||||||
|
[FromQuery] int? videoStreamIndex,
|
||||||
|
[FromQuery] EncodingContext context,
|
||||||
|
[FromQuery] Dictionary<string, string> streamOptions)
|
||||||
|
{
|
||||||
|
var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
|
||||||
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
var streamingRequest = new VideoRequestDto
|
||||||
|
{
|
||||||
|
Id = itemId,
|
||||||
|
Container = container,
|
||||||
|
Static = @static ?? true,
|
||||||
|
Params = @params,
|
||||||
|
Tag = tag,
|
||||||
|
DeviceProfileId = deviceProfileId,
|
||||||
|
PlaySessionId = playSessionId,
|
||||||
|
SegmentContainer = segmentContainer,
|
||||||
|
SegmentLength = segmentLength,
|
||||||
|
MinSegments = minSegments,
|
||||||
|
MediaSourceId = mediaSourceId,
|
||||||
|
DeviceId = deviceId,
|
||||||
|
AudioCodec = audioCodec,
|
||||||
|
EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
|
||||||
|
AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
|
||||||
|
AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
|
||||||
|
BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
|
||||||
|
AudioSampleRate = audioSampleRate,
|
||||||
|
MaxAudioChannels = maxAudioChannels,
|
||||||
|
AudioBitRate = audioBitRate,
|
||||||
|
MaxAudioBitDepth = maxAudioBitDepth,
|
||||||
|
AudioChannels = audioChannels,
|
||||||
|
Profile = profile,
|
||||||
|
Level = level,
|
||||||
|
Framerate = framerate,
|
||||||
|
MaxFramerate = maxFramerate,
|
||||||
|
CopyTimestamps = copyTimestamps ?? true,
|
||||||
|
StartTimeTicks = startTimeTicks,
|
||||||
|
Width = width,
|
||||||
|
Height = height,
|
||||||
|
VideoBitRate = videoBitRate,
|
||||||
|
SubtitleStreamIndex = subtitleStreamIndex,
|
||||||
|
SubtitleMethod = subtitleMethod,
|
||||||
|
MaxRefFrames = maxRefFrames,
|
||||||
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
|
RequireAvc = requireAvc ?? true,
|
||||||
|
DeInterlace = deInterlace ?? true,
|
||||||
|
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
||||||
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
|
LiveStreamId = liveStreamId,
|
||||||
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
||||||
|
VideoCodec = videoCodec,
|
||||||
|
SubtitleCodec = subtitleCodec,
|
||||||
|
TranscodeReasons = transcodingReasons,
|
||||||
|
AudioStreamIndex = audioStreamIndex,
|
||||||
|
VideoStreamIndex = videoStreamIndex,
|
||||||
|
Context = context,
|
||||||
|
StreamOptions = streamOptions
|
||||||
|
};
|
||||||
|
|
||||||
|
using var state = await StreamingHelpers.GetStreamingState(
|
||||||
|
streamingRequest,
|
||||||
|
Request,
|
||||||
|
_authContext,
|
||||||
|
_mediaSourceManager,
|
||||||
|
_userManager,
|
||||||
|
_libraryManager,
|
||||||
|
_serverConfigurationManager,
|
||||||
|
_mediaEncoder,
|
||||||
|
_fileSystem,
|
||||||
|
_subtitleEncoder,
|
||||||
|
_configuration,
|
||||||
|
_dlnaManager,
|
||||||
|
_deviceManager,
|
||||||
|
_transcodingJobHelper,
|
||||||
|
_transcodingJobType,
|
||||||
|
cancellationTokenSource.Token)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (@static.HasValue && @static.Value && state.DirectStreamProvider != null)
|
||||||
|
{
|
||||||
|
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
|
||||||
|
|
||||||
|
await new ProgressiveFileCopier(state.DirectStreamProvider, null, _transcodingJobHelper, CancellationToken.None)
|
||||||
|
{
|
||||||
|
AllowEndOfFile = false
|
||||||
|
}.WriteToAsync(Response.Body, CancellationToken.None)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
// TODO (moved from MediaBrowser.Api): Don't hardcode contentType
|
||||||
|
return File(Response.Body, MimeTypes.GetMimeType("file.ts")!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static remote stream
|
||||||
|
if (@static.HasValue && @static.Value && state.InputProtocol == MediaProtocol.Http)
|
||||||
|
{
|
||||||
|
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
|
||||||
|
|
||||||
|
using var httpClient = _httpClientFactory.CreateClient();
|
||||||
|
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, httpClient).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@static.HasValue && @static.Value && state.InputProtocol != MediaProtocol.File)
|
||||||
|
{
|
||||||
|
return BadRequest($"Input protocol {state.InputProtocol} cannot be streamed statically");
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputPath = state.OutputFilePath;
|
||||||
|
var outputPathExists = System.IO.File.Exists(outputPath);
|
||||||
|
|
||||||
|
var transcodingJob = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
|
||||||
|
var isTranscodeCached = outputPathExists && transcodingJob != null;
|
||||||
|
|
||||||
|
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, (@static.HasValue && @static.Value) || isTranscodeCached, startTimeTicks, Request, _dlnaManager);
|
||||||
|
|
||||||
|
// Static stream
|
||||||
|
if (@static.HasValue && @static.Value)
|
||||||
|
{
|
||||||
|
var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath);
|
||||||
|
|
||||||
|
if (state.MediaSource.IsInfiniteStream)
|
||||||
|
{
|
||||||
|
await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None)
|
||||||
|
{
|
||||||
|
AllowEndOfFile = false
|
||||||
|
}.WriteToAsync(Response.Body, CancellationToken.None)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return File(Response.Body, contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return FileStreamResponseHelpers.GetStaticFileResult(
|
||||||
|
state.MediaPath,
|
||||||
|
contentType,
|
||||||
|
isHeadRequest,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to start ffmpeg (because media can't be returned directly)
|
||||||
|
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
|
||||||
|
var encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration);
|
||||||
|
var ffmpegCommandLineArguments = encodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, "superfast");
|
||||||
|
return await FileStreamResponseHelpers.GetTranscodedFile(
|
||||||
|
state,
|
||||||
|
isHeadRequest,
|
||||||
|
this,
|
||||||
|
_transcodingJobHelper,
|
||||||
|
ffmpegCommandLineArguments,
|
||||||
|
Request,
|
||||||
|
_transcodingJobType,
|
||||||
|
cancellationTokenSource).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@ using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Api.Models.PlaybackDtos;
|
||||||
using Jellyfin.Api.Models.StreamingDtos;
|
using Jellyfin.Api.Models.StreamingDtos;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
@ -71,8 +71,7 @@ namespace Jellyfin.Api.Helpers
|
||||||
return controller.NoContent();
|
return controller.NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
return controller.PhysicalFile(path, contentType);
|
||||||
return controller.File(stream, contentType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -80,7 +79,6 @@ namespace Jellyfin.Api.Helpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="state">The current <see cref="StreamState"/>.</param>
|
/// <param name="state">The current <see cref="StreamState"/>.</param>
|
||||||
/// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param>
|
/// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param>
|
||||||
/// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
|
|
||||||
/// <param name="controller">The <see cref="ControllerBase"/> managing the response.</param>
|
/// <param name="controller">The <see cref="ControllerBase"/> managing the response.</param>
|
||||||
/// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
|
/// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
|
||||||
/// <param name="ffmpegCommandLineArguments">The command line arguments to start ffmpeg.</param>
|
/// <param name="ffmpegCommandLineArguments">The command line arguments to start ffmpeg.</param>
|
||||||
|
@ -91,7 +89,6 @@ namespace Jellyfin.Api.Helpers
|
||||||
public static async Task<ActionResult> GetTranscodedFile(
|
public static async Task<ActionResult> GetTranscodedFile(
|
||||||
StreamState state,
|
StreamState state,
|
||||||
bool isHeadRequest,
|
bool isHeadRequest,
|
||||||
IStreamHelper streamHelper,
|
|
||||||
ControllerBase controller,
|
ControllerBase controller,
|
||||||
TranscodingJobHelper transcodingJobHelper,
|
TranscodingJobHelper transcodingJobHelper,
|
||||||
string ffmpegCommandLineArguments,
|
string ffmpegCommandLineArguments,
|
||||||
|
@ -116,18 +113,20 @@ namespace Jellyfin.Api.Helpers
|
||||||
await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
TranscodingJobDto? job;
|
||||||
if (!File.Exists(outputPath))
|
if (!File.Exists(outputPath))
|
||||||
{
|
{
|
||||||
await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false);
|
job = await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
|
job = transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
|
||||||
state.Dispose();
|
state.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var memoryStream = new MemoryStream();
|
var memoryStream = new MemoryStream();
|
||||||
await new ProgressiveFileCopier(streamHelper, outputPath).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false);
|
await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
memoryStream.Position = 0;
|
||||||
return controller.File(memoryStream, contentType);
|
return controller.File(memoryStream, contentType);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Api.Models.PlaybackDtos;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
|
@ -12,34 +15,53 @@ namespace Jellyfin.Api.Helpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProgressiveFileCopier
|
public class ProgressiveFileCopier
|
||||||
{
|
{
|
||||||
|
private readonly TranscodingJobDto? _job;
|
||||||
private readonly string? _path;
|
private readonly string? _path;
|
||||||
|
private readonly CancellationToken _cancellationToken;
|
||||||
private readonly IDirectStreamProvider? _directStreamProvider;
|
private readonly IDirectStreamProvider? _directStreamProvider;
|
||||||
private readonly IStreamHelper _streamHelper;
|
private readonly TranscodingJobHelper _transcodingJobHelper;
|
||||||
|
private long _bytesWritten;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
|
/// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
|
/// <param name="path">The path to copy from.</param>
|
||||||
/// <param name="path">Filepath to stream from.</param>
|
/// <param name="job">The transcoding job.</param>
|
||||||
public ProgressiveFileCopier(IStreamHelper streamHelper, string path)
|
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/>.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
public ProgressiveFileCopier(string path, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_path = path;
|
_path = path;
|
||||||
_streamHelper = streamHelper;
|
_job = job;
|
||||||
_directStreamProvider = null;
|
_cancellationToken = cancellationToken;
|
||||||
|
_transcodingJobHelper = transcodingJobHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
|
/// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="streamHelper">Instance of the <see cref="IStreamHelper"/> interface.</param>
|
|
||||||
/// <param name="directStreamProvider">Instance of the <see cref="IDirectStreamProvider"/> interface.</param>
|
/// <param name="directStreamProvider">Instance of the <see cref="IDirectStreamProvider"/> interface.</param>
|
||||||
public ProgressiveFileCopier(IStreamHelper streamHelper, IDirectStreamProvider directStreamProvider)
|
/// <param name="job">The transcoding job.</param>
|
||||||
|
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/>.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, TranscodingJobDto? job, TranscodingJobHelper transcodingJobHelper, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_directStreamProvider = directStreamProvider;
|
_directStreamProvider = directStreamProvider;
|
||||||
_streamHelper = streamHelper;
|
_job = job;
|
||||||
_path = null;
|
_cancellationToken = cancellationToken;
|
||||||
|
_transcodingJobHelper = transcodingJobHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether allow read end of file.
|
||||||
|
/// </summary>
|
||||||
|
public bool AllowEndOfFile { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets copy start position.
|
||||||
|
/// </summary>
|
||||||
|
public long StartPosition { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write source stream to output.
|
/// Write source stream to output.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -47,6 +69,10 @@ namespace Jellyfin.Api.Helpers
|
||||||
/// <param name="cancellationToken">Cancellation token.</param>
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
/// <returns>A <see cref="Task"/>.</returns>
|
/// <returns>A <see cref="Task"/>.</returns>
|
||||||
public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
|
public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (_directStreamProvider != null)
|
if (_directStreamProvider != null)
|
||||||
{
|
{
|
||||||
|
@ -55,23 +81,35 @@ namespace Jellyfin.Api.Helpers
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileOptions = FileOptions.SequentialScan;
|
var fileOptions = FileOptions.SequentialScan;
|
||||||
|
var allowAsyncFileRead = false;
|
||||||
|
|
||||||
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||||
if (Environment.OSVersion.Platform != PlatformID.Win32NT)
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
fileOptions |= FileOptions.Asynchronous;
|
fileOptions |= FileOptions.Asynchronous;
|
||||||
|
allowAsyncFileRead = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, fileOptions);
|
await using var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions);
|
||||||
const int emptyReadLimit = 100;
|
|
||||||
var eofCount = 0;
|
var eofCount = 0;
|
||||||
while (eofCount < emptyReadLimit)
|
const int EmptyReadLimit = 20;
|
||||||
|
if (StartPosition > 0)
|
||||||
{
|
{
|
||||||
var bytesRead = await _streamHelper.CopyToAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false);
|
inputStream.Position = StartPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (eofCount < EmptyReadLimit || !AllowEndOfFile)
|
||||||
|
{
|
||||||
|
var bytesRead = await CopyToInternalAsync(inputStream, outputStream, allowAsyncFileRead, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (bytesRead == 0)
|
if (bytesRead == 0)
|
||||||
|
{
|
||||||
|
if (_job == null || _job.HasExited)
|
||||||
{
|
{
|
||||||
eofCount++;
|
eofCount++;
|
||||||
|
}
|
||||||
|
|
||||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -80,5 +118,58 @@ namespace Jellyfin.Api.Helpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_job != null)
|
||||||
|
{
|
||||||
|
_transcodingJobHelper.OnTranscodeEndRequest(_job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> CopyToInternalAsync(Stream source, Stream destination, bool readAsync, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var array = ArrayPool<byte>.Shared.Rent(IODefaults.CopyToBufferSize);
|
||||||
|
int bytesRead;
|
||||||
|
int totalBytesRead = 0;
|
||||||
|
|
||||||
|
if (readAsync)
|
||||||
|
{
|
||||||
|
bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bytesRead = source.Read(array, 0, array.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (bytesRead != 0)
|
||||||
|
{
|
||||||
|
var bytesToWrite = bytesRead;
|
||||||
|
|
||||||
|
if (bytesToWrite > 0)
|
||||||
|
{
|
||||||
|
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
_bytesWritten += bytesRead;
|
||||||
|
totalBytesRead += bytesRead;
|
||||||
|
|
||||||
|
if (_job != null)
|
||||||
|
{
|
||||||
|
_job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readAsync)
|
||||||
|
{
|
||||||
|
bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bytesRead = source.Read(array, 0, array.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalBytesRead;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -680,6 +680,20 @@ namespace Jellyfin.Api.Helpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when [transcode end].
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="job">The transcode job.</param>
|
||||||
|
public void OnTranscodeEndRequest(TranscodingJobDto job)
|
||||||
|
{
|
||||||
|
job.ActiveRequestCount--;
|
||||||
|
_logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={ActiveRequestCount}", job.ActiveRequestCount);
|
||||||
|
if (job.ActiveRequestCount <= 0)
|
||||||
|
{
|
||||||
|
PingTimer(job, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The progressive
|
/// The progressive
|
||||||
|
@ -712,20 +726,6 @@ namespace Jellyfin.Api.Helpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Transcoding video finished. Decrement the active request counter.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="job">The <see cref="TranscodingJobDto"/> which ended.</param>
|
|
||||||
public void OnTranscodeEndRequest(TranscodingJobDto job)
|
|
||||||
{
|
|
||||||
job.ActiveRequestCount--;
|
|
||||||
_logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount);
|
|
||||||
if (job.ActiveRequestCount <= 0)
|
|
||||||
{
|
|
||||||
PingTimer(job, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the exited.
|
/// Processes the exited.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.3.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.3.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
15
Jellyfin.Api/Models/VideoDtos/DeviceProfileDto.cs
Normal file
15
Jellyfin.Api/Models/VideoDtos/DeviceProfileDto.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Models.VideoDtos
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Device profile dto.
|
||||||
|
/// </summary>
|
||||||
|
public class DeviceProfileDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets device profile.
|
||||||
|
/// </summary>
|
||||||
|
public DeviceProfile? DeviceProfile { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Net.Http;
|
||||||
using Jellyfin.Server.Extensions;
|
using Jellyfin.Server.Extensions;
|
||||||
using Jellyfin.Server.Middleware;
|
using Jellyfin.Server.Middleware;
|
||||||
using Jellyfin.Server.Models;
|
using Jellyfin.Server.Models;
|
||||||
|
@ -43,6 +44,7 @@ namespace Jellyfin.Server
|
||||||
services.AddCustomAuthentication();
|
services.AddCustomAuthentication();
|
||||||
|
|
||||||
services.AddJellyfinApiAuthorization();
|
services.AddJellyfinApiAuthorization();
|
||||||
|
services.AddHttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -14,49 +14,6 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback.Progressive
|
namespace MediaBrowser.Api.Playback.Progressive
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Class GetVideoStream.
|
|
||||||
/// </summary>
|
|
||||||
[Route("/Videos/{Id}/stream.mpegts", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.ts", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.webm", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.asf", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.wmv", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.ogv", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.mp4", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.m4v", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.mkv", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.mpeg", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.mpg", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.avi", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.m2ts", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.3gp", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.wmv", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.wtv", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.mov", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.iso", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.flv", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.rm", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream", "GET")]
|
|
||||||
[Route("/Videos/{Id}/stream.ts", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.webm", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.asf", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.wmv", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.ogv", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.mp4", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.m4v", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.mkv", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.mpeg", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.mpg", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.avi", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.3gp", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.wmv", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.wtv", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.m2ts", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.mov", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.iso", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream.flv", "HEAD")]
|
|
||||||
[Route("/Videos/{Id}/stream", "HEAD")]
|
|
||||||
public class GetVideoStream : VideoStreamRequest
|
public class GetVideoStream : VideoStreamRequest
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user