Move UniversalAudioService to Jellyfin.Api
This commit is contained in:
parent
2cb6eb984b
commit
a3dcca3826
|
@ -197,8 +197,8 @@ namespace Jellyfin.Api.Controllers
|
|||
[FromQuery] string? transcodingReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
[FromQuery] EncodingContext context,
|
||||
[FromQuery] Dictionary<string, string> streamOptions)
|
||||
[FromQuery] EncodingContext? context,
|
||||
[FromQuery] Dictionary<string, string>? streamOptions)
|
||||
{
|
||||
bool isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
|
||||
|
||||
|
@ -253,7 +253,7 @@ namespace Jellyfin.Api.Controllers
|
|||
TranscodeReasons = transcodingReasons,
|
||||
AudioStreamIndex = audioStreamIndex,
|
||||
VideoStreamIndex = videoStreamIndex,
|
||||
Context = context,
|
||||
Context = context ?? EncodingContext.Static,
|
||||
StreamOptions = streamOptions
|
||||
};
|
||||
|
||||
|
|
366
Jellyfin.Api/Controllers/UniversalAudioController.cs
Normal file
366
Jellyfin.Api/Controllers/UniversalAudioController.cs
Normal file
|
@ -0,0 +1,366 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Api.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// The universal audio controller.
|
||||
/// </summary>
|
||||
public class UniversalAudioController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
private readonly IDlnaManager _dlnaManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IAuthorizationContext _authorizationContext;
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly TranscodingJobHelper _transcodingJobHelper;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ISubtitleEncoder _subtitleEncoder;
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
|
||||
public UniversalAudioController(
|
||||
ILoggerFactory loggerFactory,
|
||||
IServerConfigurationManager serverConfigurationManager,
|
||||
IUserManager userManager,
|
||||
ILibraryManager libraryManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IFileSystem fileSystem,
|
||||
IDlnaManager dlnaManager,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IAuthorizationContext authorizationContext,
|
||||
INetworkManager networkManager,
|
||||
TranscodingJobHelper transcodingJobHelper,
|
||||
IConfiguration configuration,
|
||||
ISubtitleEncoder subtitleEncoder,
|
||||
IStreamHelper streamHelper)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_libraryManager = libraryManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_fileSystem = fileSystem;
|
||||
_dlnaManager = dlnaManager;
|
||||
_deviceManager = deviceManager;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_authorizationContext = authorizationContext;
|
||||
_networkManager = networkManager;
|
||||
_loggerFactory = loggerFactory;
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
_transcodingJobHelper = transcodingJobHelper;
|
||||
_configuration = configuration;
|
||||
_subtitleEncoder = subtitleEncoder;
|
||||
_streamHelper = streamHelper;
|
||||
}
|
||||
|
||||
[HttpGet("/Audio/{itemId}/universal")]
|
||||
[HttpGet("/Audio/{itemId}/{universal=universal}.{container?}")]
|
||||
[HttpHead("/Audio/{itemId}/universal")]
|
||||
[HttpHead("/Audio/{itemId}/{universal=universal}.{container?}")]
|
||||
public async Task<ActionResult> GetUniversalAudioStream(
|
||||
[FromRoute] Guid itemId,
|
||||
[FromRoute] string? container,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] string? audioCodec,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] int? transcodingAudioChannels,
|
||||
[FromQuery] long? maxStreamingBitrate,
|
||||
[FromQuery] long? startTimeTicks,
|
||||
[FromQuery] string? transcodingContainer,
|
||||
[FromQuery] string? transcodingProtocol,
|
||||
[FromQuery] int? maxAudioSampleRate,
|
||||
[FromQuery] int? maxAudioBitDepth,
|
||||
[FromQuery] bool? enableRemoteMedia,
|
||||
[FromQuery] bool breakOnNonKeyFrames,
|
||||
[FromQuery] bool enableRedirection = true)
|
||||
{
|
||||
bool isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
|
||||
var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
|
||||
_authorizationContext.GetAuthorizationInfo(Request).DeviceId = deviceId;
|
||||
|
||||
var mediaInfoController = new MediaInfoController(_mediaSourceManager, _deviceManager, _libraryManager, _networkManager, _mediaEncoder, _userManager, _authorizationContext, _loggerFactory.CreateLogger<MediaInfoController>(), _serverConfigurationManager);
|
||||
var playbackInfoResult = await mediaInfoController.GetPlaybackInfo(itemId, userId).ConfigureAwait(false);
|
||||
var mediaSource = playbackInfoResult.Value.MediaSources[0];
|
||||
|
||||
if (mediaSource.SupportsDirectPlay && mediaSource.Protocol == MediaProtocol.Http)
|
||||
{
|
||||
if (enableRedirection)
|
||||
{
|
||||
if (mediaSource.IsRemote && enableRemoteMedia.HasValue && enableRemoteMedia.Value)
|
||||
{
|
||||
return Redirect(mediaSource.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isStatic = mediaSource.SupportsDirectStream;
|
||||
if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// TODO new DynamicHlsController
|
||||
// var dynamicHlsController = new DynamicHlsController();
|
||||
var transcodingProfile = deviceProfile.TranscodingProfiles[0];
|
||||
|
||||
// hls segment container can only be mpegts or fmp4 per ffmpeg documentation
|
||||
// TODO: remove this when we switch back to the segment muxer
|
||||
var supportedHLSContainers = new[] { "mpegts", "fmp4" };
|
||||
|
||||
/*
|
||||
var newRequest = new GetMasterHlsAudioPlaylist
|
||||
{
|
||||
AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)),
|
||||
AudioCodec = transcodingProfile.AudioCodec,
|
||||
Container = ".m3u8",
|
||||
DeviceId = request.DeviceId,
|
||||
Id = request.Id,
|
||||
MaxAudioChannels = request.MaxAudioChannels,
|
||||
MediaSourceId = mediaSource.Id,
|
||||
PlaySessionId = playbackInfoResult.PlaySessionId,
|
||||
StartTimeTicks = request.StartTimeTicks,
|
||||
Static = isStatic,
|
||||
// fallback to mpegts if device reports some weird value unsupported by hls
|
||||
SegmentContainer = Array.Exists(supportedHLSContainers, element => element == request.TranscodingContainer) ? request.TranscodingContainer : "mpegts",
|
||||
AudioSampleRate = request.MaxAudioSampleRate,
|
||||
MaxAudioBitDepth = request.MaxAudioBitDepth,
|
||||
BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames,
|
||||
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray())
|
||||
};
|
||||
|
||||
if (isHeadRequest)
|
||||
{
|
||||
audioController.Request.Method = HttpMethod.Head.Method;
|
||||
return await service.Head(newRequest).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await service.Get(newRequest).ConfigureAwait(false);*/
|
||||
// TODO remove this line
|
||||
return Content(string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
var audioController = new AudioController(
|
||||
_dlnaManager,
|
||||
_userManager,
|
||||
_authorizationContext,
|
||||
_libraryManager,
|
||||
_mediaSourceManager,
|
||||
_serverConfigurationManager,
|
||||
_mediaEncoder,
|
||||
_streamHelper,
|
||||
_fileSystem,
|
||||
_subtitleEncoder,
|
||||
_configuration,
|
||||
_deviceManager,
|
||||
_transcodingJobHelper,
|
||||
// TODO HttpClient
|
||||
new HttpClient());
|
||||
|
||||
if (isHeadRequest)
|
||||
{
|
||||
audioController.Request.Method = HttpMethod.Head.Method;
|
||||
return await audioController.GetAudioStream(
|
||||
itemId,
|
||||
isStatic ? null : ("." + mediaSource.TranscodingContainer),
|
||||
isStatic,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
playbackInfoResult.Value.PlaySessionId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
mediaSource.Id,
|
||||
deviceId,
|
||||
audioCodec,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
breakOnNonKeyFrames,
|
||||
maxAudioSampleRate,
|
||||
maxAudioBitDepth,
|
||||
isStatic ? (int?)null : Convert.ToInt32(Math.Min(maxStreamingBitrate ?? 192000, int.MaxValue)),
|
||||
null,
|
||||
maxAudioChannels,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
startTimeTicks,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
SubtitleDeliveryMethod.Embed,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await audioController.GetAudioStream(
|
||||
itemId,
|
||||
isStatic ? null : ("." + mediaSource.TranscodingContainer),
|
||||
isStatic,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
playbackInfoResult.Value.PlaySessionId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
mediaSource.Id,
|
||||
deviceId,
|
||||
audioCodec,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
breakOnNonKeyFrames,
|
||||
maxAudioSampleRate,
|
||||
maxAudioBitDepth,
|
||||
isStatic ? (int?)null : Convert.ToInt32(Math.Min(maxStreamingBitrate ?? 192000, int.MaxValue)),
|
||||
null,
|
||||
maxAudioChannels,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
startTimeTicks,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
SubtitleDeliveryMethod.Embed,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private DeviceProfile GetDeviceProfile(
|
||||
string? container,
|
||||
string? transcodingContainer,
|
||||
string? audioCodec,
|
||||
string? transcodingProtocol,
|
||||
bool? breakOnNonKeyFrames,
|
||||
int? transcodingAudioChannels,
|
||||
int? maxAudioSampleRate,
|
||||
int? maxAudioBitDepth,
|
||||
int? maxAudioChannels)
|
||||
{
|
||||
var deviceProfile = new DeviceProfile();
|
||||
|
||||
var directPlayProfiles = new List<DirectPlayProfile>();
|
||||
|
||||
var containers = (container ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var cont in containers)
|
||||
{
|
||||
var parts = cont.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var audioCodecs = parts.Length == 1 ? null : string.Join(",", parts.Skip(1).ToArray());
|
||||
|
||||
directPlayProfiles.Add(new DirectPlayProfile { Type = DlnaProfileType.Audio, Container = parts[0], AudioCodec = audioCodecs });
|
||||
}
|
||||
|
||||
deviceProfile.DirectPlayProfiles = directPlayProfiles.ToArray();
|
||||
|
||||
deviceProfile.TranscodingProfiles = new[]
|
||||
{
|
||||
new TranscodingProfile
|
||||
{
|
||||
Type = DlnaProfileType.Audio,
|
||||
Context = EncodingContext.Streaming,
|
||||
Container = transcodingContainer,
|
||||
AudioCodec = audioCodec,
|
||||
Protocol = transcodingProtocol,
|
||||
BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
|
||||
MaxAudioChannels = transcodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
|
||||
}
|
||||
};
|
||||
|
||||
var codecProfiles = new List<CodecProfile>();
|
||||
var conditions = new List<ProfileCondition>();
|
||||
|
||||
if (maxAudioSampleRate.HasValue)
|
||||
{
|
||||
// codec profile
|
||||
conditions.Add(new ProfileCondition { Condition = ProfileConditionType.LessThanEqual, IsRequired = false, Property = ProfileConditionValue.AudioSampleRate, Value = maxAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture) });
|
||||
}
|
||||
|
||||
if (maxAudioBitDepth.HasValue)
|
||||
{
|
||||
// codec profile
|
||||
conditions.Add(new ProfileCondition { Condition = ProfileConditionType.LessThanEqual, IsRequired = false, Property = ProfileConditionValue.AudioBitDepth, Value = maxAudioBitDepth.Value.ToString(CultureInfo.InvariantCulture) });
|
||||
}
|
||||
|
||||
if (maxAudioChannels.HasValue)
|
||||
{
|
||||
// codec profile
|
||||
conditions.Add(new ProfileCondition { Condition = ProfileConditionType.LessThanEqual, IsRequired = false, Property = ProfileConditionValue.AudioChannels, Value = maxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) });
|
||||
}
|
||||
|
||||
if (conditions.Count > 0)
|
||||
{
|
||||
// codec profile
|
||||
codecProfiles.Add(new CodecProfile { Type = CodecType.Audio, Container = container, Conditions = conditions.ToArray() });
|
||||
}
|
||||
|
||||
deviceProfile.CodecProfiles = codecProfiles.ToArray();
|
||||
|
||||
return deviceProfile;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,674 +0,0 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1402
|
||||
#pragma warning disable SA1649
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Services;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
public class GetPlaybackInfo : IReturn<PlaybackInfoResponse>
|
||||
{
|
||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
public Guid UserId { get; set; }
|
||||
}
|
||||
|
||||
public class GetPostedPlaybackInfo : PlaybackInfoRequest, IReturn<PlaybackInfoResponse>
|
||||
{
|
||||
}
|
||||
|
||||
public class OpenMediaSource : LiveStreamRequest, IReturn<LiveStreamResponse>
|
||||
{
|
||||
}
|
||||
|
||||
public class CloseMediaSource : IReturnVoid
|
||||
{
|
||||
[ApiMember(Name = "LiveStreamId", Description = "LiveStreamId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string LiveStreamId { get; set; }
|
||||
}
|
||||
|
||||
public class GetBitrateTestBytes
|
||||
{
|
||||
[ApiMember(Name = "Size", Description = "Size", IsRequired = true, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public int Size { get; set; }
|
||||
|
||||
public GetBitrateTestBytes()
|
||||
{
|
||||
// 100k
|
||||
Size = 102400;
|
||||
}
|
||||
}
|
||||
|
||||
[Authenticated]
|
||||
public class MediaInfoService : BaseApiService
|
||||
{
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly INetworkManager _networkManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IAuthorizationContext _authContext;
|
||||
|
||||
public MediaInfoService(
|
||||
ILogger<MediaInfoService> logger,
|
||||
IServerConfigurationManager serverConfigurationManager,
|
||||
IHttpResultFactory httpResultFactory,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IDeviceManager deviceManager,
|
||||
ILibraryManager libraryManager,
|
||||
INetworkManager networkManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IUserManager userManager,
|
||||
IAuthorizationContext authContext)
|
||||
: base(logger, serverConfigurationManager, httpResultFactory)
|
||||
{
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_deviceManager = deviceManager;
|
||||
_libraryManager = libraryManager;
|
||||
_networkManager = networkManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_userManager = userManager;
|
||||
_authContext = authContext;
|
||||
}
|
||||
|
||||
public object Get(GetBitrateTestBytes request)
|
||||
{
|
||||
const int MaxSize = 10_000_000;
|
||||
|
||||
var size = request.Size;
|
||||
|
||||
if (size <= 0)
|
||||
{
|
||||
throw new ArgumentException($"The requested size ({size}) is equal to or smaller than 0.", nameof(request));
|
||||
}
|
||||
|
||||
if (size > MaxSize)
|
||||
{
|
||||
throw new ArgumentException($"The requested size ({size}) is larger than the max allowed value ({MaxSize}).", nameof(request));
|
||||
}
|
||||
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
|
||||
try
|
||||
{
|
||||
new Random().NextBytes(buffer);
|
||||
return ResultFactory.GetResult(null, buffer, "application/octet-stream");
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<object> Get(GetPlaybackInfo request)
|
||||
{
|
||||
var result = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }).ConfigureAwait(false);
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
public async Task<object> Post(OpenMediaSource request)
|
||||
{
|
||||
var result = await OpenMediaSource(request).ConfigureAwait(false);
|
||||
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
private async Task<LiveStreamResponse> OpenMediaSource(OpenMediaSource request)
|
||||
{
|
||||
var authInfo = _authContext.GetAuthorizationInfo(Request);
|
||||
|
||||
var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var profile = request.DeviceProfile;
|
||||
if (profile == null)
|
||||
{
|
||||
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
|
||||
if (caps != null)
|
||||
{
|
||||
profile = caps.DeviceProfile;
|
||||
}
|
||||
}
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(request.ItemId);
|
||||
|
||||
SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
|
||||
request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
|
||||
request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, request.EnableDirectPlay, true, request.EnableDirectStream, true, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(result.MediaSource.TranscodingUrl))
|
||||
{
|
||||
result.MediaSource.TranscodingUrl += "&LiveStreamId=" + result.MediaSource.LiveStreamId;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.MediaSource != null)
|
||||
{
|
||||
NormalizeMediaSourceContainer(result.MediaSource, profile, DlnaProfileType.Video);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Post(CloseMediaSource request)
|
||||
{
|
||||
_mediaSourceManager.CloseLiveStream(request.LiveStreamId).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<PlaybackInfoResponse> GetPlaybackInfo(GetPostedPlaybackInfo request)
|
||||
{
|
||||
var authInfo = _authContext.GetAuthorizationInfo(Request);
|
||||
|
||||
var profile = request.DeviceProfile;
|
||||
|
||||
Logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile);
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
|
||||
if (caps != null)
|
||||
{
|
||||
profile = caps.DeviceProfile;
|
||||
}
|
||||
}
|
||||
|
||||
var info = await GetPlaybackInfo(request.Id, request.UserId, new[] { MediaType.Audio, MediaType.Video }, request.MediaSourceId, request.LiveStreamId).ConfigureAwait(false);
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
var mediaSourceId = request.MediaSourceId;
|
||||
|
||||
SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, true, request.EnableDirectStream, request.EnableTranscoding, request.AllowVideoStreamCopy, request.AllowAudioStreamCopy);
|
||||
}
|
||||
|
||||
if (request.AutoOpenLiveStream)
|
||||
{
|
||||
var mediaSource = string.IsNullOrWhiteSpace(request.MediaSourceId) ? info.MediaSources.FirstOrDefault() : info.MediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId, StringComparison.Ordinal));
|
||||
|
||||
if (mediaSource != null && mediaSource.RequiresOpening && string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
|
||||
{
|
||||
var openStreamResult = await OpenMediaSource(new OpenMediaSource
|
||||
{
|
||||
AudioStreamIndex = request.AudioStreamIndex,
|
||||
DeviceProfile = request.DeviceProfile,
|
||||
EnableDirectPlay = request.EnableDirectPlay,
|
||||
EnableDirectStream = request.EnableDirectStream,
|
||||
ItemId = request.Id,
|
||||
MaxAudioChannels = request.MaxAudioChannels,
|
||||
MaxStreamingBitrate = request.MaxStreamingBitrate,
|
||||
PlaySessionId = info.PlaySessionId,
|
||||
StartTimeTicks = request.StartTimeTicks,
|
||||
SubtitleStreamIndex = request.SubtitleStreamIndex,
|
||||
UserId = request.UserId,
|
||||
OpenToken = mediaSource.OpenToken
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
info.MediaSources = new[] { openStreamResult.MediaSource };
|
||||
}
|
||||
}
|
||||
|
||||
if (info.MediaSources != null)
|
||||
{
|
||||
foreach (var mediaSource in info.MediaSources)
|
||||
{
|
||||
NormalizeMediaSourceContainer(mediaSource, profile, DlnaProfileType.Video);
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private void NormalizeMediaSourceContainer(MediaSourceInfo mediaSource, DeviceProfile profile, DlnaProfileType type)
|
||||
{
|
||||
mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, mediaSource.Path, profile, type);
|
||||
}
|
||||
|
||||
public async Task<object> Post(GetPostedPlaybackInfo request)
|
||||
{
|
||||
var result = await GetPlaybackInfo(request).ConfigureAwait(false);
|
||||
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
private async Task<PlaybackInfoResponse> GetPlaybackInfo(Guid id, Guid userId, string[] supportedLiveMediaTypes, string mediaSourceId = null, string liveStreamId = null)
|
||||
{
|
||||
var user = _userManager.GetUserById(userId);
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
var result = new PlaybackInfoResponse();
|
||||
|
||||
MediaSourceInfo[] mediaSources;
|
||||
if (string.IsNullOrWhiteSpace(liveStreamId))
|
||||
{
|
||||
|
||||
// TODO handle supportedLiveMediaTypes?
|
||||
var mediaSourcesList = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, true, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mediaSourceId))
|
||||
{
|
||||
mediaSources = mediaSourcesList.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
mediaSources = mediaSourcesList
|
||||
.Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
mediaSources = new[] { mediaSource };
|
||||
}
|
||||
|
||||
if (mediaSources.Length == 0)
|
||||
{
|
||||
result.MediaSources = Array.Empty<MediaSourceInfo>();
|
||||
|
||||
if (!result.ErrorCode.HasValue)
|
||||
{
|
||||
result.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it
|
||||
// Should we move this directly into MediaSourceManager?
|
||||
result.MediaSources = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
|
||||
|
||||
result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetDeviceSpecificData(
|
||||
Guid itemId,
|
||||
PlaybackInfoResponse result,
|
||||
DeviceProfile profile,
|
||||
AuthorizationInfo auth,
|
||||
long? maxBitrate,
|
||||
long startTimeTicks,
|
||||
string mediaSourceId,
|
||||
int? audioStreamIndex,
|
||||
int? subtitleStreamIndex,
|
||||
int? maxAudioChannels,
|
||||
Guid userId,
|
||||
bool enableDirectPlay,
|
||||
bool forceDirectPlayRemoteMediaSource,
|
||||
bool enableDirectStream,
|
||||
bool enableTranscoding,
|
||||
bool allowVideoStreamCopy,
|
||||
bool allowAudioStreamCopy)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(itemId);
|
||||
|
||||
foreach (var mediaSource in result.MediaSources)
|
||||
{
|
||||
SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, maxAudioChannels, result.PlaySessionId, userId, enableDirectPlay, forceDirectPlayRemoteMediaSource, enableDirectStream, enableTranscoding, allowVideoStreamCopy, allowAudioStreamCopy);
|
||||
}
|
||||
|
||||
SortMediaSources(result, maxBitrate);
|
||||
}
|
||||
|
||||
private void SetDeviceSpecificData(
|
||||
BaseItem item,
|
||||
MediaSourceInfo mediaSource,
|
||||
DeviceProfile profile,
|
||||
AuthorizationInfo auth,
|
||||
long? maxBitrate,
|
||||
long startTimeTicks,
|
||||
string mediaSourceId,
|
||||
int? audioStreamIndex,
|
||||
int? subtitleStreamIndex,
|
||||
int? maxAudioChannels,
|
||||
string playSessionId,
|
||||
Guid userId,
|
||||
bool enableDirectPlay,
|
||||
bool forceDirectPlayRemoteMediaSource,
|
||||
bool enableDirectStream,
|
||||
bool enableTranscoding,
|
||||
bool allowVideoStreamCopy,
|
||||
bool allowAudioStreamCopy)
|
||||
{
|
||||
var streamBuilder = new StreamBuilder(_mediaEncoder, Logger);
|
||||
|
||||
var options = new VideoOptions
|
||||
{
|
||||
MediaSources = new[] { mediaSource },
|
||||
Context = EncodingContext.Streaming,
|
||||
DeviceId = auth.DeviceId,
|
||||
ItemId = item.Id,
|
||||
Profile = profile,
|
||||
MaxAudioChannels = maxAudioChannels
|
||||
};
|
||||
|
||||
if (string.Equals(mediaSourceId, mediaSource.Id, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
options.MediaSourceId = mediaSourceId;
|
||||
options.AudioStreamIndex = audioStreamIndex;
|
||||
options.SubtitleStreamIndex = subtitleStreamIndex;
|
||||
}
|
||||
|
||||
var user = _userManager.GetUserById(userId);
|
||||
|
||||
if (!enableDirectPlay)
|
||||
{
|
||||
mediaSource.SupportsDirectPlay = false;
|
||||
}
|
||||
|
||||
if (!enableDirectStream)
|
||||
{
|
||||
mediaSource.SupportsDirectStream = false;
|
||||
}
|
||||
|
||||
if (!enableTranscoding)
|
||||
{
|
||||
mediaSource.SupportsTranscoding = false;
|
||||
}
|
||||
|
||||
if (item is Audio)
|
||||
{
|
||||
Logger.LogInformation(
|
||||
"User policy for {0}. EnableAudioPlaybackTranscoding: {1}",
|
||||
user.Username,
|
||||
user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation("User policy for {0}. EnablePlaybackRemuxing: {1} EnableVideoPlaybackTranscoding: {2} EnableAudioPlaybackTranscoding: {3}",
|
||||
user.Username,
|
||||
user.HasPermission(PermissionKind.EnablePlaybackRemuxing),
|
||||
user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding),
|
||||
user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding));
|
||||
}
|
||||
|
||||
// Beginning of Playback Determination: Attempt DirectPlay first
|
||||
if (mediaSource.SupportsDirectPlay)
|
||||
{
|
||||
if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
|
||||
{
|
||||
mediaSource.SupportsDirectPlay = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var supportsDirectStream = mediaSource.SupportsDirectStream;
|
||||
|
||||
// Dummy this up to fool StreamBuilder
|
||||
mediaSource.SupportsDirectStream = true;
|
||||
options.MaxBitrate = maxBitrate;
|
||||
|
||||
if (item is Audio)
|
||||
{
|
||||
if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
|
||||
{
|
||||
options.ForceDirectPlay = true;
|
||||
}
|
||||
}
|
||||
else if (item is Video)
|
||||
{
|
||||
if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
|
||||
&& !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
|
||||
&& !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
|
||||
{
|
||||
options.ForceDirectPlay = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The MediaSource supports direct stream, now test to see if the client supports it
|
||||
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
|
||||
? streamBuilder.BuildAudioItem(options)
|
||||
: streamBuilder.BuildVideoItem(options);
|
||||
|
||||
if (streamInfo == null || !streamInfo.IsDirectStream)
|
||||
{
|
||||
mediaSource.SupportsDirectPlay = false;
|
||||
}
|
||||
|
||||
// Set this back to what it was
|
||||
mediaSource.SupportsDirectStream = supportsDirectStream;
|
||||
|
||||
if (streamInfo != null)
|
||||
{
|
||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaSource.SupportsDirectStream)
|
||||
{
|
||||
if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
|
||||
{
|
||||
mediaSource.SupportsDirectStream = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
|
||||
|
||||
if (item is Audio)
|
||||
{
|
||||
if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
|
||||
{
|
||||
options.ForceDirectStream = true;
|
||||
}
|
||||
}
|
||||
else if (item is Video)
|
||||
{
|
||||
if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
|
||||
&& !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
|
||||
&& !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
|
||||
{
|
||||
options.ForceDirectStream = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The MediaSource supports direct stream, now test to see if the client supports it
|
||||
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
|
||||
? streamBuilder.BuildAudioItem(options)
|
||||
: streamBuilder.BuildVideoItem(options);
|
||||
|
||||
if (streamInfo == null || !streamInfo.IsDirectStream)
|
||||
{
|
||||
mediaSource.SupportsDirectStream = false;
|
||||
}
|
||||
|
||||
if (streamInfo != null)
|
||||
{
|
||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mediaSource.SupportsTranscoding)
|
||||
{
|
||||
options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
|
||||
|
||||
// The MediaSource supports direct stream, now test to see if the client supports it
|
||||
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
|
||||
? streamBuilder.BuildAudioItem(options)
|
||||
: streamBuilder.BuildVideoItem(options);
|
||||
|
||||
if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding))
|
||||
{
|
||||
if (streamInfo != null)
|
||||
{
|
||||
streamInfo.PlaySessionId = playSessionId;
|
||||
streamInfo.StartPositionTicks = startTimeTicks;
|
||||
mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
|
||||
mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
|
||||
mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
|
||||
mediaSource.TranscodingContainer = streamInfo.Container;
|
||||
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
|
||||
|
||||
// Do this after the above so that StartPositionTicks is set
|
||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (streamInfo != null)
|
||||
{
|
||||
streamInfo.PlaySessionId = playSessionId;
|
||||
|
||||
if (streamInfo.PlayMethod == PlayMethod.Transcode)
|
||||
{
|
||||
streamInfo.StartPositionTicks = startTimeTicks;
|
||||
mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
|
||||
|
||||
if (!allowVideoStreamCopy)
|
||||
{
|
||||
mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
|
||||
}
|
||||
|
||||
if (!allowAudioStreamCopy)
|
||||
{
|
||||
mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
|
||||
}
|
||||
|
||||
mediaSource.TranscodingContainer = streamInfo.Container;
|
||||
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
|
||||
}
|
||||
|
||||
if (!allowAudioStreamCopy)
|
||||
{
|
||||
mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
|
||||
}
|
||||
|
||||
mediaSource.TranscodingContainer = streamInfo.Container;
|
||||
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
|
||||
|
||||
// Do this after the above so that StartPositionTicks is set
|
||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var attachment in mediaSource.MediaAttachments)
|
||||
{
|
||||
attachment.DeliveryUrl = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"/Videos/{0}/{1}/Attachments/{2}",
|
||||
item.Id,
|
||||
mediaSource.Id,
|
||||
attachment.Index);
|
||||
}
|
||||
}
|
||||
|
||||
private long? GetMaxBitrate(long? clientMaxBitrate, Jellyfin.Data.Entities.User user)
|
||||
{
|
||||
var maxBitrate = clientMaxBitrate;
|
||||
var remoteClientMaxBitrate = user?.RemoteClientBitrateLimit ?? 0;
|
||||
|
||||
if (remoteClientMaxBitrate <= 0)
|
||||
{
|
||||
remoteClientMaxBitrate = ServerConfigurationManager.Configuration.RemoteClientBitrateLimit;
|
||||
}
|
||||
|
||||
if (remoteClientMaxBitrate > 0)
|
||||
{
|
||||
var isInLocalNetwork = _networkManager.IsInLocalNetwork(Request.RemoteIp);
|
||||
|
||||
Logger.LogInformation("RemoteClientBitrateLimit: {0}, RemoteIp: {1}, IsInLocalNetwork: {2}", remoteClientMaxBitrate, Request.RemoteIp, isInLocalNetwork);
|
||||
if (!isInLocalNetwork)
|
||||
{
|
||||
maxBitrate = Math.Min(maxBitrate ?? remoteClientMaxBitrate, remoteClientMaxBitrate);
|
||||
}
|
||||
}
|
||||
|
||||
return maxBitrate;
|
||||
}
|
||||
|
||||
private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken)
|
||||
{
|
||||
var profiles = info.GetSubtitleProfiles(_mediaEncoder, false, "-", accessToken);
|
||||
mediaSource.DefaultSubtitleStreamIndex = info.SubtitleStreamIndex;
|
||||
|
||||
mediaSource.TranscodeReasons = info.TranscodeReasons;
|
||||
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
foreach (var stream in mediaSource.MediaStreams)
|
||||
{
|
||||
if (stream.Type == MediaStreamType.Subtitle && stream.Index == profile.Index)
|
||||
{
|
||||
stream.DeliveryMethod = profile.DeliveryMethod;
|
||||
|
||||
if (profile.DeliveryMethod == SubtitleDeliveryMethod.External)
|
||||
{
|
||||
stream.DeliveryUrl = profile.Url.TrimStart('-');
|
||||
stream.IsExternalUrl = profile.IsExternalUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SortMediaSources(PlaybackInfoResponse result, long? maxBitrate)
|
||||
{
|
||||
var originalList = result.MediaSources.ToList();
|
||||
|
||||
result.MediaSources = result.MediaSources.OrderBy(i =>
|
||||
{
|
||||
// Nothing beats direct playing a file
|
||||
if (i.SupportsDirectPlay && i.Protocol == MediaProtocol.File)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}).ThenBy(i =>
|
||||
{
|
||||
// Let's assume direct streaming a file is just as desirable as direct playing a remote url
|
||||
if (i.SupportsDirectPlay || i.SupportsDirectStream)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}).ThenBy(i =>
|
||||
{
|
||||
return i.Protocol switch
|
||||
{
|
||||
MediaProtocol.File => 0,
|
||||
_ => 1,
|
||||
};
|
||||
}).ThenBy(i =>
|
||||
{
|
||||
if (maxBitrate.HasValue && i.Bitrate.HasValue)
|
||||
{
|
||||
return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}).ThenBy(originalList.IndexOf)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Api.Playback.Progressive
|
||||
{
|
||||
/// <summary>
|
||||
/// Class GetAudioStream.
|
||||
/// </summary>
|
||||
public class GetAudioStream : StreamRequest
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class AudioService.
|
||||
/// </summary>
|
||||
// TODO: In order to autheneticate this in the future, Dlna playback will require updating
|
||||
//[Authenticated]
|
||||
public class AudioService : BaseProgressiveStreamingService
|
||||
{
|
||||
public AudioService(
|
||||
ILogger<AudioService> logger,
|
||||
IServerConfigurationManager serverConfigurationManager,
|
||||
IHttpResultFactory httpResultFactory,
|
||||
IHttpClient httpClient,
|
||||
IUserManager userManager,
|
||||
ILibraryManager libraryManager,
|
||||
IIsoManager isoManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IFileSystem fileSystem,
|
||||
IDlnaManager dlnaManager,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext,
|
||||
EncodingHelper encodingHelper)
|
||||
: base(
|
||||
logger,
|
||||
serverConfigurationManager,
|
||||
httpResultFactory,
|
||||
httpClient,
|
||||
userManager,
|
||||
libraryManager,
|
||||
isoManager,
|
||||
mediaEncoder,
|
||||
fileSystem,
|
||||
dlnaManager,
|
||||
deviceManager,
|
||||
mediaSourceManager,
|
||||
jsonSerializer,
|
||||
authorizationContext,
|
||||
encodingHelper)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
public Task<object> Get(GetAudioStream request)
|
||||
{
|
||||
return ProcessRequest(request, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
public Task<object> Head(GetAudioStream request)
|
||||
{
|
||||
return ProcessRequest(request, true);
|
||||
}
|
||||
|
||||
protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
|
||||
{
|
||||
return EncodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,401 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Api.Playback.Hls;
|
||||
using MediaBrowser.Api.Playback.Progressive;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
public class BaseUniversalRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the id.
|
||||
/// </summary>
|
||||
/// <value>The id.</value>
|
||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string MediaSourceId { get; set; }
|
||||
|
||||
[ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
public string AudioCodec { get; set; }
|
||||
|
||||
public string Container { get; set; }
|
||||
|
||||
public int? MaxAudioChannels { get; set; }
|
||||
|
||||
public int? TranscodingAudioChannels { get; set; }
|
||||
|
||||
public long? MaxStreamingBitrate { get; set; }
|
||||
|
||||
[ApiMember(Name = "StartTimeTicks", Description = "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
|
||||
public long? StartTimeTicks { get; set; }
|
||||
|
||||
public string TranscodingContainer { get; set; }
|
||||
|
||||
public string TranscodingProtocol { get; set; }
|
||||
|
||||
public int? MaxAudioSampleRate { get; set; }
|
||||
|
||||
public int? MaxAudioBitDepth { get; set; }
|
||||
|
||||
public bool EnableRedirection { get; set; }
|
||||
|
||||
public bool EnableRemoteMedia { get; set; }
|
||||
|
||||
public bool BreakOnNonKeyFrames { get; set; }
|
||||
|
||||
public BaseUniversalRequest()
|
||||
{
|
||||
EnableRedirection = true;
|
||||
}
|
||||
}
|
||||
|
||||
[Route("/Audio/{Id}/universal.{Container}", "GET", Summary = "Gets an audio stream")]
|
||||
[Route("/Audio/{Id}/universal", "GET", Summary = "Gets an audio stream")]
|
||||
[Route("/Audio/{Id}/universal.{Container}", "HEAD", Summary = "Gets an audio stream")]
|
||||
[Route("/Audio/{Id}/universal", "HEAD", Summary = "Gets an audio stream")]
|
||||
public class GetUniversalAudioStream : BaseUniversalRequest
|
||||
{
|
||||
}
|
||||
|
||||
[Authenticated]
|
||||
public class UniversalAudioService : BaseApiService
|
||||
{
|
||||
private readonly EncodingHelper _encodingHelper;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
public UniversalAudioService(
|
||||
ILogger<UniversalAudioService> logger,
|
||||
ILoggerFactory loggerFactory,
|
||||
IServerConfigurationManager serverConfigurationManager,
|
||||
IHttpResultFactory httpResultFactory,
|
||||
IHttpClient httpClient,
|
||||
IUserManager userManager,
|
||||
ILibraryManager libraryManager,
|
||||
IIsoManager isoManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IFileSystem fileSystem,
|
||||
IDlnaManager dlnaManager,
|
||||
IDeviceManager deviceManager,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IAuthorizationContext authorizationContext,
|
||||
INetworkManager networkManager,
|
||||
EncodingHelper encodingHelper)
|
||||
: base(logger, serverConfigurationManager, httpResultFactory)
|
||||
{
|
||||
HttpClient = httpClient;
|
||||
UserManager = userManager;
|
||||
LibraryManager = libraryManager;
|
||||
IsoManager = isoManager;
|
||||
MediaEncoder = mediaEncoder;
|
||||
FileSystem = fileSystem;
|
||||
DlnaManager = dlnaManager;
|
||||
DeviceManager = deviceManager;
|
||||
MediaSourceManager = mediaSourceManager;
|
||||
JsonSerializer = jsonSerializer;
|
||||
AuthorizationContext = authorizationContext;
|
||||
NetworkManager = networkManager;
|
||||
_encodingHelper = encodingHelper;
|
||||
_loggerFactory = loggerFactory;
|
||||
}
|
||||
|
||||
protected IHttpClient HttpClient { get; private set; }
|
||||
|
||||
protected IUserManager UserManager { get; private set; }
|
||||
|
||||
protected ILibraryManager LibraryManager { get; private set; }
|
||||
|
||||
protected IIsoManager IsoManager { get; private set; }
|
||||
|
||||
protected IMediaEncoder MediaEncoder { get; private set; }
|
||||
|
||||
protected IFileSystem FileSystem { get; private set; }
|
||||
|
||||
protected IDlnaManager DlnaManager { get; private set; }
|
||||
|
||||
protected IDeviceManager DeviceManager { get; private set; }
|
||||
|
||||
protected IMediaSourceManager MediaSourceManager { get; private set; }
|
||||
|
||||
protected IJsonSerializer JsonSerializer { get; private set; }
|
||||
|
||||
protected IAuthorizationContext AuthorizationContext { get; private set; }
|
||||
|
||||
protected INetworkManager NetworkManager { get; private set; }
|
||||
|
||||
public Task<object> Get(GetUniversalAudioStream request)
|
||||
{
|
||||
return GetUniversalStream(request, false);
|
||||
}
|
||||
|
||||
public Task<object> Head(GetUniversalAudioStream request)
|
||||
{
|
||||
return GetUniversalStream(request, true);
|
||||
}
|
||||
|
||||
private DeviceProfile GetDeviceProfile(GetUniversalAudioStream request)
|
||||
{
|
||||
var deviceProfile = new DeviceProfile();
|
||||
|
||||
var directPlayProfiles = new List<DirectPlayProfile>();
|
||||
|
||||
var containers = (request.Container ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var container in containers)
|
||||
{
|
||||
var parts = container.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var audioCodecs = parts.Length == 1 ? null : string.Join(",", parts.Skip(1).ToArray());
|
||||
|
||||
directPlayProfiles.Add(new DirectPlayProfile
|
||||
{
|
||||
Type = DlnaProfileType.Audio,
|
||||
Container = parts[0],
|
||||
AudioCodec = audioCodecs
|
||||
});
|
||||
}
|
||||
|
||||
deviceProfile.DirectPlayProfiles = directPlayProfiles.ToArray();
|
||||
|
||||
deviceProfile.TranscodingProfiles = new[]
|
||||
{
|
||||
new TranscodingProfile
|
||||
{
|
||||
Type = DlnaProfileType.Audio,
|
||||
Context = EncodingContext.Streaming,
|
||||
Container = request.TranscodingContainer,
|
||||
AudioCodec = request.AudioCodec,
|
||||
Protocol = request.TranscodingProtocol,
|
||||
BreakOnNonKeyFrames = request.BreakOnNonKeyFrames,
|
||||
MaxAudioChannels = request.TranscodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
|
||||
}
|
||||
};
|
||||
|
||||
var codecProfiles = new List<CodecProfile>();
|
||||
var conditions = new List<ProfileCondition>();
|
||||
|
||||
if (request.MaxAudioSampleRate.HasValue)
|
||||
{
|
||||
// codec profile
|
||||
conditions.Add(new ProfileCondition
|
||||
{
|
||||
Condition = ProfileConditionType.LessThanEqual,
|
||||
IsRequired = false,
|
||||
Property = ProfileConditionValue.AudioSampleRate,
|
||||
Value = request.MaxAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture)
|
||||
});
|
||||
}
|
||||
|
||||
if (request.MaxAudioBitDepth.HasValue)
|
||||
{
|
||||
// codec profile
|
||||
conditions.Add(new ProfileCondition
|
||||
{
|
||||
Condition = ProfileConditionType.LessThanEqual,
|
||||
IsRequired = false,
|
||||
Property = ProfileConditionValue.AudioBitDepth,
|
||||
Value = request.MaxAudioBitDepth.Value.ToString(CultureInfo.InvariantCulture)
|
||||
});
|
||||
}
|
||||
|
||||
if (request.MaxAudioChannels.HasValue)
|
||||
{
|
||||
// codec profile
|
||||
conditions.Add(new ProfileCondition
|
||||
{
|
||||
Condition = ProfileConditionType.LessThanEqual,
|
||||
IsRequired = false,
|
||||
Property = ProfileConditionValue.AudioChannels,
|
||||
Value = request.MaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture)
|
||||
});
|
||||
}
|
||||
|
||||
if (conditions.Count > 0)
|
||||
{
|
||||
// codec profile
|
||||
codecProfiles.Add(new CodecProfile
|
||||
{
|
||||
Type = CodecType.Audio,
|
||||
Container = request.Container,
|
||||
Conditions = conditions.ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
deviceProfile.CodecProfiles = codecProfiles.ToArray();
|
||||
|
||||
return deviceProfile;
|
||||
}
|
||||
|
||||
private async Task<object> GetUniversalStream(GetUniversalAudioStream request, bool isHeadRequest)
|
||||
{
|
||||
var deviceProfile = GetDeviceProfile(request);
|
||||
|
||||
AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId;
|
||||
|
||||
var mediaInfoService = new MediaInfoService(
|
||||
_loggerFactory.CreateLogger<MediaInfoService>(),
|
||||
ServerConfigurationManager,
|
||||
ResultFactory,
|
||||
MediaSourceManager,
|
||||
DeviceManager,
|
||||
LibraryManager,
|
||||
NetworkManager,
|
||||
MediaEncoder,
|
||||
UserManager,
|
||||
AuthorizationContext)
|
||||
{
|
||||
Request = Request
|
||||
};
|
||||
|
||||
var playbackInfoResult = await mediaInfoService.GetPlaybackInfo(new GetPostedPlaybackInfo
|
||||
{
|
||||
Id = request.Id,
|
||||
MaxAudioChannels = request.MaxAudioChannels,
|
||||
MaxStreamingBitrate = request.MaxStreamingBitrate,
|
||||
StartTimeTicks = request.StartTimeTicks,
|
||||
UserId = request.UserId,
|
||||
DeviceProfile = deviceProfile,
|
||||
MediaSourceId = request.MediaSourceId
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var mediaSource = playbackInfoResult.MediaSources[0];
|
||||
|
||||
if (mediaSource.SupportsDirectPlay && mediaSource.Protocol == MediaProtocol.Http)
|
||||
{
|
||||
if (request.EnableRedirection)
|
||||
{
|
||||
if (mediaSource.IsRemote && request.EnableRemoteMedia)
|
||||
{
|
||||
return ResultFactory.GetRedirectResult(mediaSource.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isStatic = mediaSource.SupportsDirectStream;
|
||||
|
||||
if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var service = new DynamicHlsService(
|
||||
_loggerFactory.CreateLogger<DynamicHlsService>(),
|
||||
ServerConfigurationManager,
|
||||
ResultFactory,
|
||||
UserManager,
|
||||
LibraryManager,
|
||||
IsoManager,
|
||||
MediaEncoder,
|
||||
FileSystem,
|
||||
DlnaManager,
|
||||
DeviceManager,
|
||||
MediaSourceManager,
|
||||
JsonSerializer,
|
||||
AuthorizationContext,
|
||||
NetworkManager,
|
||||
_encodingHelper)
|
||||
{
|
||||
Request = Request
|
||||
};
|
||||
|
||||
var transcodingProfile = deviceProfile.TranscodingProfiles[0];
|
||||
|
||||
// hls segment container can only be mpegts or fmp4 per ffmpeg documentation
|
||||
// TODO: remove this when we switch back to the segment muxer
|
||||
var supportedHLSContainers = new[] { "mpegts", "fmp4" };
|
||||
|
||||
var newRequest = new GetMasterHlsAudioPlaylist
|
||||
{
|
||||
AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)),
|
||||
AudioCodec = transcodingProfile.AudioCodec,
|
||||
Container = ".m3u8",
|
||||
DeviceId = request.DeviceId,
|
||||
Id = request.Id,
|
||||
MaxAudioChannels = request.MaxAudioChannels,
|
||||
MediaSourceId = mediaSource.Id,
|
||||
PlaySessionId = playbackInfoResult.PlaySessionId,
|
||||
StartTimeTicks = request.StartTimeTicks,
|
||||
Static = isStatic,
|
||||
// fallback to mpegts if device reports some weird value unsupported by hls
|
||||
SegmentContainer = Array.Exists(supportedHLSContainers, element => element == request.TranscodingContainer) ? request.TranscodingContainer : "mpegts",
|
||||
AudioSampleRate = request.MaxAudioSampleRate,
|
||||
MaxAudioBitDepth = request.MaxAudioBitDepth,
|
||||
BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames,
|
||||
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray())
|
||||
};
|
||||
|
||||
if (isHeadRequest)
|
||||
{
|
||||
return await service.Head(newRequest).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await service.Get(newRequest).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var service = new AudioService(
|
||||
_loggerFactory.CreateLogger<AudioService>(),
|
||||
ServerConfigurationManager,
|
||||
ResultFactory,
|
||||
HttpClient,
|
||||
UserManager,
|
||||
LibraryManager,
|
||||
IsoManager,
|
||||
MediaEncoder,
|
||||
FileSystem,
|
||||
DlnaManager,
|
||||
DeviceManager,
|
||||
MediaSourceManager,
|
||||
JsonSerializer,
|
||||
AuthorizationContext,
|
||||
_encodingHelper)
|
||||
{
|
||||
Request = Request
|
||||
};
|
||||
|
||||
var newRequest = new GetAudioStream
|
||||
{
|
||||
AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)),
|
||||
AudioCodec = request.AudioCodec,
|
||||
Container = isStatic ? null : ("." + mediaSource.TranscodingContainer),
|
||||
DeviceId = request.DeviceId,
|
||||
Id = request.Id,
|
||||
MaxAudioChannels = request.MaxAudioChannels,
|
||||
MediaSourceId = mediaSource.Id,
|
||||
PlaySessionId = playbackInfoResult.PlaySessionId,
|
||||
StartTimeTicks = request.StartTimeTicks,
|
||||
Static = isStatic,
|
||||
AudioSampleRate = request.MaxAudioSampleRate,
|
||||
MaxAudioBitDepth = request.MaxAudioBitDepth,
|
||||
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray())
|
||||
};
|
||||
|
||||
if (isHeadRequest)
|
||||
{
|
||||
return await service.Head(newRequest).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return await service.Get(newRequest).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user