mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-05 21:37:10 -04:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
1da36ba59d
@ -1,5 +1,7 @@
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using System;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using ServiceStack;
|
||||
using ServiceStack.Text.Controller;
|
||||
using ServiceStack.Web;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@ -21,9 +23,12 @@ namespace MediaBrowser.Api.Dlna
|
||||
{
|
||||
}
|
||||
|
||||
[Route("/Dlna/control", "POST", Summary = "Processes a control request")]
|
||||
[Route("/Dlna/{UuId}/control", "POST", Summary = "Processes a control request")]
|
||||
public class ProcessControlRequest : IRequiresRequestStream
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UuId { get; set; }
|
||||
|
||||
public Stream RequestStream { get; set; }
|
||||
}
|
||||
|
||||
@ -66,12 +71,16 @@ namespace MediaBrowser.Api.Dlna
|
||||
|
||||
private async Task<ControlResponse> PostAsync(ProcessControlRequest request)
|
||||
{
|
||||
var pathInfo = PathInfo.Parse(Request.PathInfo);
|
||||
var id = pathInfo.GetArgumentValue<string>(1);
|
||||
|
||||
using (var reader = new StreamReader(request.RequestStream))
|
||||
{
|
||||
return _dlnaManager.ProcessControlRequest(new ControlRequest
|
||||
{
|
||||
Headers = GetRequestHeaders(),
|
||||
InputXml = await reader.ReadToEndAsync().ConfigureAwait(false)
|
||||
InputXml = await reader.ReadToEndAsync().ConfigureAwait(false),
|
||||
TargetServerUuId = id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -300,7 +300,7 @@ namespace MediaBrowser.Api.Playback
|
||||
case EncodingQuality.HighSpeed:
|
||||
return 2;
|
||||
case EncodingQuality.HighQuality:
|
||||
return isWebm ? Math.Max((int)((Environment.ProcessorCount -1) / 2) , 2) : 0;
|
||||
return 2;
|
||||
case EncodingQuality.MaxQuality:
|
||||
return isWebm ? Math.Max(Environment.ProcessorCount - 1, 2) : 0;
|
||||
default:
|
||||
@ -373,7 +373,6 @@ namespace MediaBrowser.Api.Playback
|
||||
break;
|
||||
case EncodingQuality.MaxQuality:
|
||||
crf = "4";
|
||||
//profilescore aready set to 0
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unrecognized quality setting");
|
||||
@ -381,7 +380,9 @@ namespace MediaBrowser.Api.Playback
|
||||
|
||||
if (isVc1)
|
||||
{
|
||||
profileScore = 1;
|
||||
profileScore ++;
|
||||
// Max of 2
|
||||
profileScore = Math.Min(profileScore, 2);
|
||||
}
|
||||
|
||||
// http://www.webmproject.org/docs/encoder-parameters/
|
||||
@ -1713,33 +1714,19 @@ namespace MediaBrowser.Api.Playback
|
||||
var extension = GetOutputFileExtension(state);
|
||||
|
||||
// first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none
|
||||
var orgOp = ";DLNA.ORG_OP=";
|
||||
var orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(state.RunTimeTicks.HasValue, isStaticallyStreamed, state.TranscodeSeekInfo);
|
||||
|
||||
if (state.RunTimeTicks.HasValue)
|
||||
{
|
||||
// Time-based seeking currently only possible when transcoding
|
||||
orgOp += isStaticallyStreamed ? "0" : "1";
|
||||
|
||||
// Byte-based seeking only possible when not transcoding
|
||||
orgOp += isStaticallyStreamed || state.TranscodeSeekInfo == TranscodeSeekInfo.Bytes ? "1" : "0";
|
||||
|
||||
if (!isStaticallyStreamed)
|
||||
if (state.RunTimeTicks.HasValue && !isStaticallyStreamed)
|
||||
{
|
||||
AddTimeSeekResponseHeaders(state, responseHeaders);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No seeking is available if we don't know the content runtime
|
||||
orgOp += "00";
|
||||
}
|
||||
|
||||
// 0 = native, 1 = transcoded
|
||||
var orgCi = isStaticallyStreamed ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
|
||||
|
||||
var flagValue = DlnaFlags.DLNA_ORG_FLAG_STREAMING_TRANSFER_MODE |
|
||||
DlnaFlags.DLNA_ORG_FLAG_BACKGROUND_TRANSFERT_MODE |
|
||||
DlnaFlags.DLNA_ORG_FLAG_DLNA_V15;
|
||||
var flagValue = DlnaFlags.StreamingTransferMode |
|
||||
DlnaFlags.BackgroundTransferMode |
|
||||
DlnaFlags.DlnaV15;
|
||||
|
||||
if (isStaticallyStreamed)
|
||||
{
|
||||
@ -1801,23 +1788,6 @@ namespace MediaBrowser.Api.Playback
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum DlnaFlags
|
||||
{
|
||||
DLNA_ORG_FLAG_SENDER_PACED = (1 << 31),
|
||||
DLNA_ORG_FLAG_TIME_BASED_SEEK = (1 << 30),
|
||||
DLNA_ORG_FLAG_BYTE_BASED_SEEK = (1 << 29),
|
||||
DLNA_ORG_FLAG_PLAY_CONTAINER = (1 << 28),
|
||||
DLNA_ORG_FLAG_S0_INCREASE = (1 << 27),
|
||||
DLNA_ORG_FLAG_SN_INCREASE = (1 << 26),
|
||||
DLNA_ORG_FLAG_RTSP_PAUSE = (1 << 25),
|
||||
DLNA_ORG_FLAG_STREAMING_TRANSFER_MODE = (1 << 24),
|
||||
DLNA_ORG_FLAG_INTERACTIVE_TRANSFERT_MODE = (1 << 23),
|
||||
DLNA_ORG_FLAG_BACKGROUND_TRANSFERT_MODE = (1 << 22),
|
||||
DLNA_ORG_FLAG_CONNECTION_STALL = (1 << 21),
|
||||
DLNA_ORG_FLAG_DLNA_V15 = (1 << 20),
|
||||
};
|
||||
|
||||
private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
|
||||
{
|
||||
var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture);
|
||||
|
@ -8,6 +8,8 @@ namespace MediaBrowser.Controller.Dlna
|
||||
|
||||
public string InputXml { get; set; }
|
||||
|
||||
public string TargetServerUuId { get; set; }
|
||||
|
||||
public ControlRequest()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
@ -20,6 +22,8 @@ namespace MediaBrowser.Controller.Dlna
|
||||
|
||||
public string Xml { get; set; }
|
||||
|
||||
public bool IsSuccessful { get; set; }
|
||||
|
||||
public ControlResponse()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
|
@ -12,6 +12,10 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
/// </summary>
|
||||
public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<SongInfo>
|
||||
{
|
||||
public string FormatName { get; set; }
|
||||
public long? Size { get; set; }
|
||||
public string Container { get; set; }
|
||||
|
||||
public Audio()
|
||||
{
|
||||
Artists = new List<string>();
|
||||
|
@ -26,6 +26,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
public List<Guid> AdditionalPartIds { get; set; }
|
||||
public List<Guid> LocalAlternateVersionIds { get; set; }
|
||||
|
||||
public string FormatName { get; set; }
|
||||
public long? Size { get; set; }
|
||||
public string Container { get; set; }
|
||||
|
||||
public Video()
|
||||
{
|
||||
PlayableStreamFileNames = new List<string>();
|
||||
|
@ -3,6 +3,7 @@ using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Dlna.Profiles;
|
||||
using MediaBrowser.Dlna.Server;
|
||||
@ -27,8 +28,11 @@ namespace MediaBrowser.Dlna
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IDtoService _dtoService;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
|
||||
public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger, IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager)
|
||||
public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger, IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager)
|
||||
{
|
||||
_xmlSerializer = xmlSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
@ -37,6 +41,9 @@ namespace MediaBrowser.Dlna
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_userManager = userManager;
|
||||
_libraryManager = libraryManager;
|
||||
_dtoService = dtoService;
|
||||
_imageProcessor = imageProcessor;
|
||||
_userDataManager = userDataManager;
|
||||
|
||||
//DumpProfiles();
|
||||
}
|
||||
@ -502,7 +509,14 @@ namespace MediaBrowser.Dlna
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
{
|
||||
return new ControlHandler(_logger, _userManager, _libraryManager)
|
||||
var profile = GetProfile(request.Headers)
|
||||
?? GetDefaultProfile();
|
||||
|
||||
var device = DlnaServerEntryPoint.Instance.GetServerUpnpDevice(request.TargetServerUuId);
|
||||
|
||||
var serverAddress = device.Descriptor.ToString().Substring(0, device.Descriptor.ToString().IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return new ControlHandler(_logger, _userManager, _libraryManager, profile, serverAddress, _dtoService, _imageProcessor, _userDataManager)
|
||||
.ProcessControlRequest(request);
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,6 @@
|
||||
<Compile Include="Ssdp\SsdpHelper.cs" />
|
||||
<Compile Include="PlayTo\SsdpHttpClient.cs" />
|
||||
<Compile Include="Common\StateVariable.cs" />
|
||||
<Compile Include="PlayTo\StreamHelper.cs" />
|
||||
<Compile Include="PlayTo\TransportCommands.cs" />
|
||||
<Compile Include="PlayTo\TransportStateEventArgs.cs" />
|
||||
<Compile Include="PlayTo\uBaseObject.cs" />
|
||||
|
@ -39,6 +39,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
/// <param name="serverAddress">The server address.</param>
|
||||
/// <param name="streamUrl">The stream URL.</param>
|
||||
/// <param name="streams">The streams.</param>
|
||||
/// <param name="includeImageRes">if set to <c>true</c> [include image resource].</param>
|
||||
/// <returns>System.String.</returns>
|
||||
public static string Build(BaseItem dto, string userId, string serverAddress, string streamUrl, IEnumerable<MediaStream> streams, bool includeImageRes)
|
||||
{
|
||||
|
@ -1,12 +1,14 @@
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Session;
|
||||
@ -32,6 +34,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
private readonly IDlnaManager _dlnaManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IDtoService _dtoService;
|
||||
private bool _playbackStarted;
|
||||
|
||||
private const int UpdateTimerIntervalMs = 1000;
|
||||
@ -52,7 +55,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
}
|
||||
}
|
||||
|
||||
public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost)
|
||||
public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost, IDtoService dtoService)
|
||||
{
|
||||
_session = session;
|
||||
_itemRepository = itemRepository;
|
||||
@ -62,6 +65,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
_dlnaManager = dlnaManager;
|
||||
_userManager = userManager;
|
||||
_appHost = appHost;
|
||||
_dtoService = dtoService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -172,20 +176,23 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
|
||||
if (playlistItem != null)
|
||||
{
|
||||
var streamInfo = playlistItem.StreamInfo;
|
||||
|
||||
if (!_playbackStarted)
|
||||
{
|
||||
await _sessionManager.OnPlaybackStart(new PlaybackStartInfo
|
||||
{
|
||||
ItemId = _currentItem.Id.ToString("N"),
|
||||
SessionId = _session.Id,
|
||||
CanSeek = true,
|
||||
CanSeek = streamInfo.RunTimeTicks.HasValue,
|
||||
QueueableMediaTypes = new List<string> { _currentItem.MediaType },
|
||||
MediaSourceId = playlistItem.MediaSourceId,
|
||||
AudioStreamIndex = playlistItem.AudioStreamIndex,
|
||||
SubtitleStreamIndex = playlistItem.SubtitleStreamIndex,
|
||||
MediaSourceId = streamInfo.MediaSourceId,
|
||||
AudioStreamIndex = streamInfo.AudioStreamIndex,
|
||||
SubtitleStreamIndex = streamInfo.SubtitleStreamIndex,
|
||||
IsMuted = _device.IsMuted,
|
||||
IsPaused = _device.IsPaused,
|
||||
VolumeLevel = _device.Volume
|
||||
VolumeLevel = _device.Volume,
|
||||
PlayMethod = streamInfo.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
@ -196,9 +203,9 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
var ticks = _device.Position.Ticks;
|
||||
|
||||
if (playlistItem.Transcode)
|
||||
if (!streamInfo.IsDirectStream)
|
||||
{
|
||||
ticks += playlistItem.StartPositionTicks;
|
||||
ticks += streamInfo.StartPositionTicks;
|
||||
}
|
||||
|
||||
await _sessionManager.OnPlaybackProgress(new PlaybackProgressInfo
|
||||
@ -208,11 +215,12 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
PositionTicks = ticks,
|
||||
IsMuted = _device.IsMuted,
|
||||
IsPaused = _device.IsPaused,
|
||||
MediaSourceId = playlistItem.MediaSourceId,
|
||||
AudioStreamIndex = playlistItem.AudioStreamIndex,
|
||||
SubtitleStreamIndex = playlistItem.SubtitleStreamIndex,
|
||||
MediaSourceId = streamInfo.MediaSourceId,
|
||||
AudioStreamIndex = streamInfo.AudioStreamIndex,
|
||||
SubtitleStreamIndex = streamInfo.SubtitleStreamIndex,
|
||||
VolumeLevel = _device.Volume,
|
||||
CanSeek = true
|
||||
CanSeek = streamInfo.RunTimeTicks.HasValue,
|
||||
PlayMethod = streamInfo.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
@ -411,46 +419,43 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
|
||||
private PlaylistItem CreatePlaylistItem(BaseItem item, long startPostionTicks, string serverAddress)
|
||||
{
|
||||
var streams = _itemRepository.GetMediaStreams(new MediaStreamQuery
|
||||
{
|
||||
ItemId = item.Id
|
||||
|
||||
}).ToList();
|
||||
|
||||
var deviceInfo = _device.Properties;
|
||||
|
||||
var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
|
||||
_dlnaManager.GetDefaultProfile();
|
||||
|
||||
var playlistItem = GetPlaylistItem(item, streams, profile);
|
||||
playlistItem.StartPositionTicks = startPostionTicks;
|
||||
playlistItem.DeviceProfileId = profile.Id;
|
||||
var mediaSources = item is Audio || item is Video
|
||||
? _dtoService.GetMediaSources(item)
|
||||
: new List<MediaSourceInfo>();
|
||||
|
||||
if (playlistItem.MediaType == DlnaProfileType.Audio)
|
||||
{
|
||||
playlistItem.StreamUrl = StreamHelper.GetAudioUrl(deviceInfo, playlistItem, streams, serverAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress);
|
||||
}
|
||||
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId);
|
||||
playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
|
||||
|
||||
playlistItem.Didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, streams, profile.EnableAlbumArtInDidl);
|
||||
playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress);
|
||||
|
||||
var mediaStreams = mediaSources
|
||||
.Where(i => string.Equals(i.Id, playlistItem.StreamInfo.MediaSourceId))
|
||||
.SelectMany(i => i.MediaStreams)
|
||||
.ToList();
|
||||
|
||||
playlistItem.Didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, mediaStreams, profile.EnableAlbumArtInDidl);
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
|
||||
private string GetDlnaHeaders(PlaylistItem item)
|
||||
{
|
||||
var orgOp = item.Transcode ? ";DLNA.ORG_OP=00" : ";DLNA.ORG_OP=01";
|
||||
var streamInfo = item.StreamInfo;
|
||||
|
||||
var orgCi = item.Transcode ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
|
||||
var orgOp = !streamInfo.IsDirectStream ? ";DLNA.ORG_OP=00" : ";DLNA.ORG_OP=01";
|
||||
|
||||
var orgCi = !streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
|
||||
|
||||
const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000";
|
||||
|
||||
string contentFeatures;
|
||||
|
||||
var container = item.Container.TrimStart('.');
|
||||
var container = streamInfo.Container.TrimStart('.');
|
||||
|
||||
if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@ -488,7 +493,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
|
||||
}
|
||||
else if (item.MediaType == DlnaProfileType.Video)
|
||||
else if (streamInfo.MediaType == DlnaProfileType.Video)
|
||||
{
|
||||
// Default to AVI for video
|
||||
contentFeatures = "DLNA.ORG_PN=AVI";
|
||||
@ -502,20 +507,38 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
|
||||
}
|
||||
|
||||
private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaStream> mediaStreams, DeviceProfile profile)
|
||||
private PlaylistItem GetPlaylistItem(BaseItem item, List<MediaSourceInfo> mediaSources, DeviceProfile profile, string deviceId)
|
||||
{
|
||||
var video = item as Video;
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
return new PlaylistItemFactory().Create(video, mediaStreams, profile);
|
||||
return new PlaylistItem
|
||||
{
|
||||
StreamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
|
||||
{
|
||||
ItemId = item.Id.ToString("N"),
|
||||
MediaSources = mediaSources,
|
||||
Profile = profile,
|
||||
DeviceId = deviceId
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
var audio = item as Audio;
|
||||
|
||||
if (audio != null)
|
||||
{
|
||||
return new PlaylistItemFactory().Create(audio, mediaStreams, profile);
|
||||
return new PlaylistItem
|
||||
{
|
||||
StreamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
|
||||
{
|
||||
ItemId = item.Id.ToString("N"),
|
||||
MediaSources = mediaSources,
|
||||
Profile = profile,
|
||||
DeviceId = deviceId
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
var photo = item as Photo;
|
||||
@ -578,8 +601,9 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
|
||||
await _device.SetAvTransport(nextTrack.StreamUrl, dlnaheaders, nextTrack.Didl);
|
||||
|
||||
if (nextTrack.StartPositionTicks > 0 && !nextTrack.Transcode)
|
||||
await _device.Seek(TimeSpan.FromTicks(nextTrack.StartPositionTicks));
|
||||
var streamInfo = nextTrack.StreamInfo;
|
||||
if (streamInfo.StartPositionTicks > 0 && streamInfo.IsDirectStream)
|
||||
await _device.Seek(TimeSpan.FromTicks(streamInfo.StartPositionTicks));
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -602,7 +626,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
return Task.FromResult(false);
|
||||
|
||||
prevTrack.PlayState = 1;
|
||||
return _device.SetAvTransport(prevTrack.StreamUrl, GetDlnaHeaders(prevTrack), prevTrack.Didl);
|
||||
return _device.SetAvTransport(prevTrack.StreamInfo.ToDlnaUrl(GetServerAddress()), GetDlnaHeaders(prevTrack), prevTrack.Didl);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -2,11 +2,11 @@
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Dlna.Ssdp;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Session;
|
||||
using System;
|
||||
@ -37,8 +37,9 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
private readonly IDlnaManager _dlnaManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IDtoService _dtoService;
|
||||
|
||||
public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost)
|
||||
public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService)
|
||||
{
|
||||
_locations = new ConcurrentDictionary<string, DateTime>();
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
@ -52,6 +53,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
_userManager = userManager;
|
||||
_dlnaManager = dlnaManager;
|
||||
_appHost = appHost;
|
||||
_dtoService = dtoService;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
@ -248,7 +250,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
|
||||
if (controller == null)
|
||||
{
|
||||
sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost);
|
||||
sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost, _dtoService);
|
||||
|
||||
controller.Init(device);
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
@ -24,8 +25,9 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IDlnaManager _dlnaManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IDtoService _dtoService;
|
||||
|
||||
public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost)
|
||||
public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService)
|
||||
{
|
||||
_config = config;
|
||||
_sessionManager = sessionManager;
|
||||
@ -36,6 +38,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
_userManager = userManager;
|
||||
_dlnaManager = dlnaManager;
|
||||
_appHost = appHost;
|
||||
_dtoService = dtoService;
|
||||
_logger = logManager.GetLogger("PlayTo");
|
||||
}
|
||||
|
||||
@ -72,7 +75,18 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
try
|
||||
{
|
||||
_manager = new PlayToManager(_logger, _config, _sessionManager, _httpClient, _itemRepo, _libraryManager, _networkManager, _userManager, _dlnaManager, _appHost);
|
||||
_manager = new PlayToManager(_logger,
|
||||
_config,
|
||||
_sessionManager,
|
||||
_httpClient,
|
||||
_itemRepo,
|
||||
_libraryManager,
|
||||
_networkManager,
|
||||
_userManager,
|
||||
_dlnaManager,
|
||||
_appHost,
|
||||
_dtoService);
|
||||
|
||||
_manager.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -4,45 +4,12 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class PlaylistItem
|
||||
{
|
||||
public string ItemId { get; set; }
|
||||
|
||||
public string MediaSourceId { get; set; }
|
||||
|
||||
public bool Transcode { get; set; }
|
||||
|
||||
public DlnaProfileType MediaType { get; set; }
|
||||
|
||||
public string Container { get; set; }
|
||||
|
||||
public int PlayState { get; set; }
|
||||
|
||||
public string StreamUrl { get; set; }
|
||||
|
||||
public string Didl { get; set; }
|
||||
|
||||
public long StartPositionTicks { get; set; }
|
||||
|
||||
public string VideoCodec { get; set; }
|
||||
|
||||
public string AudioCodec { get; set; }
|
||||
|
||||
public int? AudioStreamIndex { get; set; }
|
||||
|
||||
public int? SubtitleStreamIndex { get; set; }
|
||||
|
||||
public int? MaxAudioChannels { get; set; }
|
||||
|
||||
public int? AudioBitrate { get; set; }
|
||||
|
||||
public int? VideoBitrate { get; set; }
|
||||
|
||||
public int? VideoLevel { get; set; }
|
||||
|
||||
public int? MaxWidth { get; set; }
|
||||
public int? MaxHeight { get; set; }
|
||||
|
||||
public int? MaxFramerate { get; set; }
|
||||
|
||||
public string DeviceProfileId { get; set; }
|
||||
public StreamInfo StreamInfo { get; set; }
|
||||
}
|
||||
}
|
@ -1,10 +1,6 @@
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -15,235 +11,40 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public PlaylistItem Create(Audio item, List<MediaStream> mediaStreams, DeviceProfile profile)
|
||||
{
|
||||
var playlistItem = new PlaylistItem
|
||||
{
|
||||
ItemId = item.Id.ToString("N"),
|
||||
MediaType = DlnaProfileType.Audio
|
||||
};
|
||||
|
||||
var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
|
||||
var directPlay = profile.DirectPlayProfiles
|
||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, audioStream));
|
||||
|
||||
if (directPlay != null)
|
||||
{
|
||||
var audioCodec = audioStream == null ? null : audioStream.Codec;
|
||||
|
||||
// Make sure audio codec profiles are satisfied
|
||||
if (!string.IsNullOrEmpty(audioCodec) && profile.CodecProfiles.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(audioCodec))
|
||||
.All(i => AreConditionsSatisfied(i.Conditions, item.Path, null, audioStream)))
|
||||
{
|
||||
playlistItem.Transcode = false;
|
||||
playlistItem.Container = Path.GetExtension(item.Path);
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
}
|
||||
|
||||
var transcodingProfile = profile.TranscodingProfiles
|
||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item));
|
||||
|
||||
if (transcodingProfile != null)
|
||||
{
|
||||
playlistItem.Transcode = true;
|
||||
playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.');
|
||||
playlistItem.AudioCodec = transcodingProfile.AudioCodec;
|
||||
|
||||
var audioTranscodingConditions = profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(transcodingProfile.AudioCodec))
|
||||
.Take(1)
|
||||
.SelectMany(i => i.Conditions);
|
||||
|
||||
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
|
||||
}
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
|
||||
public PlaylistItem Create(Photo item, DeviceProfile profile)
|
||||
{
|
||||
var playlistItem = new PlaylistItem
|
||||
{
|
||||
StreamInfo = new StreamInfo
|
||||
{
|
||||
ItemId = item.Id.ToString("N"),
|
||||
MediaType = DlnaProfileType.Photo
|
||||
MediaType = DlnaProfileType.Photo,
|
||||
}
|
||||
};
|
||||
|
||||
var directPlay = profile.DirectPlayProfiles
|
||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item));
|
||||
.FirstOrDefault(i => i.Type == DlnaProfileType.Photo && IsSupported(i, item));
|
||||
|
||||
if (directPlay != null)
|
||||
{
|
||||
playlistItem.Transcode = false;
|
||||
playlistItem.Container = Path.GetExtension(item.Path);
|
||||
playlistItem.StreamInfo.IsDirectStream = true;
|
||||
playlistItem.StreamInfo.Container = Path.GetExtension(item.Path);
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
|
||||
var transcodingProfile = profile.TranscodingProfiles
|
||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item));
|
||||
.FirstOrDefault(i => i.Type == DlnaProfileType.Photo);
|
||||
|
||||
if (transcodingProfile != null)
|
||||
{
|
||||
playlistItem.Transcode = true;
|
||||
playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.');
|
||||
playlistItem.StreamInfo.IsDirectStream = true;
|
||||
playlistItem.StreamInfo.Container = "." + transcodingProfile.Container.TrimStart('.');
|
||||
}
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
|
||||
public PlaylistItem Create(Video item, List<MediaStream> mediaStreams, DeviceProfile profile)
|
||||
{
|
||||
var playlistItem = new PlaylistItem
|
||||
{
|
||||
ItemId = item.Id.ToString("N"),
|
||||
MediaType = DlnaProfileType.Video
|
||||
};
|
||||
|
||||
var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
|
||||
|
||||
var directPlay = profile.DirectPlayProfiles
|
||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(i, item, videoStream, audioStream));
|
||||
|
||||
if (directPlay != null)
|
||||
{
|
||||
var videoCodec = videoStream == null ? null : videoStream.Codec;
|
||||
|
||||
// Make sure video codec profiles are satisfied
|
||||
if (!string.IsNullOrEmpty(videoCodec) && profile.CodecProfiles.Where(i => i.Type == CodecType.Video && i.ContainsCodec(videoCodec))
|
||||
.All(i => AreConditionsSatisfied(i.Conditions, item.Path, videoStream, audioStream)))
|
||||
{
|
||||
var audioCodec = audioStream == null ? null : audioStream.Codec;
|
||||
|
||||
// Make sure audio codec profiles are satisfied
|
||||
if (string.IsNullOrEmpty(audioCodec) || profile.CodecProfiles.Where(i => i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec))
|
||||
.All(i => AreConditionsSatisfied(i.Conditions, item.Path, videoStream, audioStream)))
|
||||
{
|
||||
playlistItem.Transcode = false;
|
||||
playlistItem.Container = Path.GetExtension(item.Path);
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var transcodingProfile = profile.TranscodingProfiles
|
||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsSupported(profile, i, item));
|
||||
|
||||
if (transcodingProfile != null)
|
||||
{
|
||||
playlistItem.Transcode = true;
|
||||
playlistItem.Container = "." + transcodingProfile.Container.TrimStart('.');
|
||||
playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',').FirstOrDefault();
|
||||
playlistItem.VideoCodec = transcodingProfile.VideoCodec;
|
||||
|
||||
var videoTranscodingConditions = profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.Video && i.ContainsCodec(transcodingProfile.VideoCodec))
|
||||
.Take(1)
|
||||
.SelectMany(i => i.Conditions);
|
||||
|
||||
ApplyTranscodingConditions(playlistItem, videoTranscodingConditions);
|
||||
|
||||
var audioTranscodingConditions = profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.VideoAudio && i.ContainsCodec(transcodingProfile.AudioCodec))
|
||||
.Take(1)
|
||||
.SelectMany(i => i.Conditions);
|
||||
|
||||
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
|
||||
}
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
|
||||
private void ApplyTranscodingConditions(PlaylistItem item, IEnumerable<ProfileCondition> conditions)
|
||||
{
|
||||
foreach (var condition in conditions
|
||||
.Where(i => !string.IsNullOrEmpty(i.Value)))
|
||||
{
|
||||
var value = condition.Value;
|
||||
|
||||
switch (condition.Property)
|
||||
{
|
||||
case ProfileConditionValue.AudioBitrate:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.AudioBitrate = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.AudioChannels:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.MaxAudioChannels = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.AudioProfile:
|
||||
case ProfileConditionValue.Has64BitOffsets:
|
||||
case ProfileConditionValue.VideoBitDepth:
|
||||
case ProfileConditionValue.VideoProfile:
|
||||
{
|
||||
// Not supported yet
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.Height:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.MaxHeight = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.VideoBitrate:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.VideoBitrate = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.VideoFramerate:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.MaxFramerate = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.VideoLevel:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.VideoLevel = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.Width:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.MaxWidth = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentException("Unrecognized ProfileConditionValue");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSupported(DirectPlayProfile profile, Photo item)
|
||||
{
|
||||
var mediaPath = item.Path;
|
||||
@ -260,223 +61,5 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsSupported(DirectPlayProfile profile, Audio item, MediaStream audioStream)
|
||||
{
|
||||
var mediaPath = item.Path;
|
||||
|
||||
if (profile.Container.Length > 0)
|
||||
{
|
||||
// Check container type
|
||||
var mediaContainer = Path.GetExtension(mediaPath);
|
||||
if (!profile.GetContainers().Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsSupported(DirectPlayProfile profile, Video item, MediaStream videoStream, MediaStream audioStream)
|
||||
{
|
||||
if (item.VideoType != VideoType.VideoFile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var mediaPath = item.Path;
|
||||
|
||||
if (profile.Container.Length > 0)
|
||||
{
|
||||
// Check container type
|
||||
var mediaContainer = Path.GetExtension(mediaPath);
|
||||
if (!profile.GetContainers().Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check video codec
|
||||
var videoCodecs = profile.GetVideoCodecs();
|
||||
if (videoCodecs.Count > 0)
|
||||
{
|
||||
var videoCodec = videoStream == null ? null : videoStream.Codec;
|
||||
if (string.IsNullOrWhiteSpace(videoCodec) || !videoCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var audioCodecs = profile.GetAudioCodecs();
|
||||
if (audioCodecs.Count > 0)
|
||||
{
|
||||
// Check audio codecs
|
||||
var audioCodec = audioStream == null ? null : audioStream.Codec;
|
||||
if (string.IsNullOrWhiteSpace(audioCodec) || !audioCodecs.Contains(audioCodec, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Audio item)
|
||||
{
|
||||
// Placeholder for future conditions
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Photo item)
|
||||
{
|
||||
// Placeholder for future conditions
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsSupported(DeviceProfile profile, TranscodingProfile transcodingProfile, Video item)
|
||||
{
|
||||
// Placeholder for future conditions
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool AreConditionsSatisfied(IEnumerable<ProfileCondition> conditions, string mediaPath, MediaStream videoStream, MediaStream audioStream)
|
||||
{
|
||||
return conditions.All(i => IsConditionSatisfied(i, mediaPath, videoStream, audioStream));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is condition satisfied] [the specified condition].
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition.</param>
|
||||
/// <param name="mediaPath">The media path.</param>
|
||||
/// <param name="videoStream">The video stream.</param>
|
||||
/// <param name="audioStream">The audio stream.</param>
|
||||
/// <returns><c>true</c> if [is condition satisfied] [the specified condition]; otherwise, <c>false</c>.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">Unexpected ProfileConditionType</exception>
|
||||
private bool IsConditionSatisfied(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream)
|
||||
{
|
||||
if (condition.Property == ProfileConditionValue.Has64BitOffsets)
|
||||
{
|
||||
// TODO: Determine how to evaluate this
|
||||
}
|
||||
|
||||
if (condition.Property == ProfileConditionValue.VideoProfile)
|
||||
{
|
||||
var profile = videoStream == null ? null : videoStream.Profile;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(profile))
|
||||
{
|
||||
switch (condition.Condition)
|
||||
{
|
||||
case ProfileConditionType.Equals:
|
||||
return string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase);
|
||||
case ProfileConditionType.NotEquals:
|
||||
return !string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase);
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected ProfileConditionType");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (condition.Property == ProfileConditionValue.AudioProfile)
|
||||
{
|
||||
var profile = audioStream == null ? null : audioStream.Profile;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(profile))
|
||||
{
|
||||
switch (condition.Condition)
|
||||
{
|
||||
case ProfileConditionType.Equals:
|
||||
return string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase);
|
||||
case ProfileConditionType.NotEquals:
|
||||
return !string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase);
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected ProfileConditionType");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var actualValue = GetConditionValue(condition, mediaPath, videoStream, audioStream);
|
||||
|
||||
if (actualValue.HasValue)
|
||||
{
|
||||
long expected;
|
||||
if (long.TryParse(condition.Value, NumberStyles.Any, _usCulture, out expected))
|
||||
{
|
||||
switch (condition.Condition)
|
||||
{
|
||||
case ProfileConditionType.Equals:
|
||||
return actualValue.Value == expected;
|
||||
case ProfileConditionType.GreaterThanEqual:
|
||||
return actualValue.Value >= expected;
|
||||
case ProfileConditionType.LessThanEqual:
|
||||
return actualValue.Value <= expected;
|
||||
case ProfileConditionType.NotEquals:
|
||||
return actualValue.Value != expected;
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected ProfileConditionType");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Value doesn't exist in metadata. Fail it if required.
|
||||
return !condition.IsRequired;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the condition value.
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition.</param>
|
||||
/// <param name="mediaPath">The media path.</param>
|
||||
/// <param name="videoStream">The video stream.</param>
|
||||
/// <param name="audioStream">The audio stream.</param>
|
||||
/// <returns>System.Nullable{System.Int64}.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">Unexpected Property</exception>
|
||||
private long? GetConditionValue(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream)
|
||||
{
|
||||
switch (condition.Property)
|
||||
{
|
||||
case ProfileConditionValue.AudioBitrate:
|
||||
return audioStream == null ? null : audioStream.BitRate;
|
||||
case ProfileConditionValue.AudioChannels:
|
||||
return audioStream == null ? null : audioStream.Channels;
|
||||
case ProfileConditionValue.VideoBitrate:
|
||||
return videoStream == null ? null : videoStream.BitRate;
|
||||
case ProfileConditionValue.VideoFramerate:
|
||||
return videoStream == null ? null : (ConvertToLong(videoStream.AverageFrameRate ?? videoStream.RealFrameRate));
|
||||
case ProfileConditionValue.Height:
|
||||
return videoStream == null ? null : videoStream.Height;
|
||||
case ProfileConditionValue.Width:
|
||||
return videoStream == null ? null : videoStream.Width;
|
||||
case ProfileConditionValue.VideoLevel:
|
||||
return videoStream == null ? null : ConvertToLong(videoStream.Level);
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected Property");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts to long.
|
||||
/// </summary>
|
||||
/// <param name="val">The value.</param>
|
||||
/// <returns>System.Nullable{System.Int64}.</returns>
|
||||
private long? ConvertToLong(float? val)
|
||||
{
|
||||
return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts to long.
|
||||
/// </summary>
|
||||
/// <param name="val">The value.</param>
|
||||
/// <returns>System.Nullable{System.Int64}.</returns>
|
||||
private long? ConvertToLong(double? val)
|
||||
{
|
||||
return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,69 +0,0 @@
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
class StreamHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the audio URL.
|
||||
/// </summary>
|
||||
/// <param name="deviceProperties">The device properties.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="streams">The streams.</param>
|
||||
/// <param name="serverAddress">The server address.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
internal static string GetAudioUrl(DeviceInfo deviceProperties, PlaylistItem item, List<MediaStream> streams, string serverAddress)
|
||||
{
|
||||
var dlnaCommand = BuildDlnaUrl(deviceProperties, item);
|
||||
|
||||
return string.Format("{0}/audio/{1}/stream{2}?{3}", serverAddress, item.ItemId, "." + item.Container.TrimStart('.'), dlnaCommand);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the video URL.
|
||||
/// </summary>
|
||||
/// <param name="deviceProperties">The device properties.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="streams">The streams.</param>
|
||||
/// <param name="serverAddress">The server address.</param>
|
||||
/// <returns>The url to send to the device</returns>
|
||||
internal static string GetVideoUrl(DeviceInfo deviceProperties, PlaylistItem item, List<MediaStream> streams, string serverAddress)
|
||||
{
|
||||
var dlnaCommand = BuildDlnaUrl(deviceProperties, item);
|
||||
|
||||
return string.Format("{0}/Videos/{1}/stream{2}?{3}", serverAddress, item.ItemId, item.Container, dlnaCommand);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the dlna URL.
|
||||
/// </summary>
|
||||
private static string BuildDlnaUrl(DeviceInfo deviceProperties, PlaylistItem item)
|
||||
{
|
||||
var usCulture = new CultureInfo("en-US");
|
||||
|
||||
var list = new List<string>
|
||||
{
|
||||
item.DeviceProfileId ?? string.Empty,
|
||||
deviceProperties.UUID ?? string.Empty,
|
||||
item.MediaSourceId ?? string.Empty,
|
||||
(!item.Transcode).ToString().ToLower(),
|
||||
item.VideoCodec ?? string.Empty,
|
||||
item.AudioCodec ?? string.Empty,
|
||||
item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(usCulture) : string.Empty,
|
||||
item.SubtitleStreamIndex.HasValue ? item.SubtitleStreamIndex.Value.ToString(usCulture) : string.Empty,
|
||||
item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(usCulture) : string.Empty,
|
||||
item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(usCulture) : string.Empty,
|
||||
item.MaxAudioChannels.HasValue ? item.MaxAudioChannels.Value.ToString(usCulture) : string.Empty,
|
||||
item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(usCulture) : string.Empty,
|
||||
item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(usCulture) : string.Empty,
|
||||
item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(usCulture) : string.Empty,
|
||||
item.StartPositionTicks.ToString(usCulture),
|
||||
item.VideoLevel.HasValue ? item.VideoLevel.Value.ToString(usCulture) : string.Empty
|
||||
};
|
||||
|
||||
return string.Format("Params={0}", string.Join(";", list.ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,23 @@
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Xml;
|
||||
|
||||
namespace MediaBrowser.Dlna.Server
|
||||
@ -19,7 +27,12 @@ namespace MediaBrowser.Dlna.Server
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private DeviceProfile _profile;
|
||||
private readonly DeviceProfile _profile;
|
||||
private readonly IDtoService _dtoService;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
|
||||
private readonly string _serverAddress;
|
||||
|
||||
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
|
||||
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
|
||||
@ -31,11 +44,16 @@ namespace MediaBrowser.Dlna.Server
|
||||
private int systemID = 0;
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public ControlHandler(ILogger logger, IUserManager userManager, ILibraryManager libraryManager)
|
||||
public ControlHandler(ILogger logger, IUserManager userManager, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
_libraryManager = libraryManager;
|
||||
_profile = profile;
|
||||
_serverAddress = serverAddress;
|
||||
_dtoService = dtoService;
|
||||
_imageProcessor = imageProcessor;
|
||||
_userDataManager = userDataManager;
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
@ -46,6 +64,8 @@ namespace MediaBrowser.Dlna.Server
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error processing control request", ex);
|
||||
|
||||
return GetErrorResponse(ex);
|
||||
}
|
||||
}
|
||||
@ -120,7 +140,8 @@ namespace MediaBrowser.Dlna.Server
|
||||
|
||||
var controlResponse = new ControlResponse
|
||||
{
|
||||
Xml = env.OuterXml
|
||||
Xml = env.OuterXml,
|
||||
IsSuccessful = true
|
||||
};
|
||||
|
||||
controlResponse.Headers.Add("EXT", string.Empty);
|
||||
@ -153,7 +174,8 @@ namespace MediaBrowser.Dlna.Server
|
||||
|
||||
return new ControlResponse
|
||||
{
|
||||
Xml = env.OuterXml
|
||||
Xml = env.OuterXml,
|
||||
IsSuccessful = false
|
||||
};
|
||||
}
|
||||
|
||||
@ -161,7 +183,16 @@ namespace MediaBrowser.Dlna.Server
|
||||
{
|
||||
var id = sparams["ObjectID"];
|
||||
|
||||
var newbookmark = long.Parse(sparams["PosSecond"]);
|
||||
var item = _libraryManager.GetItemById(new Guid(id));
|
||||
|
||||
var newbookmark = int.Parse(sparams["PosSecond"], _usCulture);
|
||||
|
||||
var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
|
||||
|
||||
userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
|
||||
|
||||
_userDataManager.SaveUserData(user.Id, item, userdata, UserDataSaveReason.TogglePlayed,
|
||||
CancellationToken.None);
|
||||
|
||||
return new Headers();
|
||||
}
|
||||
@ -209,13 +240,13 @@ namespace MediaBrowser.Dlna.Server
|
||||
var id = sparams["ObjectID"];
|
||||
var flag = sparams["BrowseFlag"];
|
||||
|
||||
int requested = 20;
|
||||
var provided = 0;
|
||||
int requested = 0;
|
||||
int start = 0;
|
||||
|
||||
if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requested) && requested <= 0)
|
||||
{
|
||||
requested = 20;
|
||||
requested = 0;
|
||||
}
|
||||
if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out start) && start <= 0)
|
||||
{
|
||||
@ -232,11 +263,11 @@ namespace MediaBrowser.Dlna.Server
|
||||
didl.SetAttribute("xmlns:sec", NS_SEC);
|
||||
result.AppendChild(didl);
|
||||
|
||||
var folder = string.IsNullOrWhiteSpace(id)
|
||||
var folder = string.IsNullOrWhiteSpace(id) || string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
|
||||
? user.RootFolder
|
||||
: (Folder)_libraryManager.GetItemById(new Guid(id));
|
||||
|
||||
var children = folder.GetChildren(user, true).ToList();
|
||||
var children = GetChildrenSorted(folder, user).ToList();
|
||||
|
||||
if (string.Equals(flag, "BrowseMetadata"))
|
||||
{
|
||||
@ -244,41 +275,30 @@ namespace MediaBrowser.Dlna.Server
|
||||
provided++;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var i in children.OfType<Folder>())
|
||||
{
|
||||
if (start > 0)
|
||||
{
|
||||
start--;
|
||||
continue;
|
||||
children = children.Skip(start).ToList();
|
||||
}
|
||||
if (requested > 0)
|
||||
{
|
||||
children = children.Take(requested).ToList();
|
||||
}
|
||||
|
||||
var childCount = i.GetChildren(user, true).Count();
|
||||
provided = children.Count;
|
||||
|
||||
Browse_AddFolder(result, i, childCount);
|
||||
|
||||
if (++provided == requested)
|
||||
foreach (var i in children)
|
||||
{
|
||||
break;
|
||||
if (i.IsFolder)
|
||||
{
|
||||
var f = (Folder)i;
|
||||
var childCount = GetChildrenSorted(f, user).Count();
|
||||
|
||||
Browse_AddFolder(result, f, childCount);
|
||||
}
|
||||
}
|
||||
|
||||
if (provided != requested)
|
||||
else
|
||||
{
|
||||
foreach (var i in children.Where(i => !i.IsFolder))
|
||||
{
|
||||
if (start > 0)
|
||||
{
|
||||
start--;
|
||||
continue;
|
||||
}
|
||||
|
||||
Browse_AddItem(result, i, user);
|
||||
|
||||
if (++provided == requested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -294,10 +314,23 @@ namespace MediaBrowser.Dlna.Server
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<BaseItem> GetChildrenSorted(Folder folder, User user)
|
||||
{
|
||||
var children = folder.GetChildren(user, true).Where(i => i.LocationType != LocationType.Virtual);
|
||||
|
||||
if (folder is Series || folder is Season || folder is BoxSet)
|
||||
{
|
||||
return children;
|
||||
}
|
||||
|
||||
return _libraryManager.Sort(children, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending);
|
||||
}
|
||||
|
||||
private void Browse_AddFolder(XmlDocument result, Folder f, int childCount)
|
||||
{
|
||||
var container = result.CreateElement(string.Empty, "container", NS_DIDL);
|
||||
container.SetAttribute("restricted", "0");
|
||||
container.SetAttribute("searchable", "1");
|
||||
container.SetAttribute("childCount", childCount.ToString(_usCulture));
|
||||
container.SetAttribute("id", f.Id.ToString("N"));
|
||||
|
||||
@ -311,20 +344,28 @@ namespace MediaBrowser.Dlna.Server
|
||||
container.SetAttribute("parentID", parent.Id.ToString("N"));
|
||||
}
|
||||
|
||||
var title = result.CreateElement("dc", "title", NS_DC);
|
||||
title.InnerText = f.Name;
|
||||
container.AppendChild(title);
|
||||
AddCommonFields(f, container);
|
||||
|
||||
var date = result.CreateElement("dc", "date", NS_DC);
|
||||
date.InnerText = f.DateModified.ToString("o");
|
||||
container.AppendChild(date);
|
||||
AddCover(f, container);
|
||||
|
||||
var objectClass = result.CreateElement("upnp", "class", NS_UPNP);
|
||||
objectClass.InnerText = "object.container.storageFolder";
|
||||
container.AppendChild(objectClass);
|
||||
container.AppendChild(CreateObjectClass(result, f));
|
||||
result.DocumentElement.AppendChild(container);
|
||||
}
|
||||
|
||||
private void AddValue(XmlElement elem, string prefix, string name, string value, string namespaceUri)
|
||||
{
|
||||
try
|
||||
{
|
||||
var date = elem.OwnerDocument.CreateElement(prefix, name, namespaceUri);
|
||||
date.InnerText = value;
|
||||
elem.AppendChild(date);
|
||||
}
|
||||
catch (XmlException)
|
||||
{
|
||||
//_logger.Error("Error adding xml value: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
private void Browse_AddItem(XmlDocument result, BaseItem item, User user)
|
||||
{
|
||||
var element = result.CreateElement(string.Empty, "item", NS_DIDL);
|
||||
@ -342,52 +383,222 @@ namespace MediaBrowser.Dlna.Server
|
||||
|
||||
AddGeneralProperties(item, element);
|
||||
|
||||
AddActors(item, element);
|
||||
// refID?
|
||||
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
|
||||
|
||||
var title = result.CreateElement("dc", "title", NS_DC);
|
||||
title.InnerText = item.Name;
|
||||
element.AppendChild(title);
|
||||
var audio = item as Audio;
|
||||
if (audio != null)
|
||||
{
|
||||
AddAudioResource(element, audio);
|
||||
}
|
||||
|
||||
var res = result.CreateElement(string.Empty, "res", NS_DIDL);
|
||||
|
||||
//res.InnerText = String.Format(
|
||||
// "http://{0}:{1}{2}file/{3}",
|
||||
// request.LocalEndPoint.Address,
|
||||
// request.LocalEndPoint.Port,
|
||||
// prefix,
|
||||
// resource.Id
|
||||
// );
|
||||
|
||||
//if (props.TryGetValue("SizeRaw", out prop))
|
||||
//{
|
||||
// res.SetAttribute("size", prop);
|
||||
//}
|
||||
//if (props.TryGetValue("Resolution", out prop))
|
||||
//{
|
||||
// res.SetAttribute("resolution", prop);
|
||||
//}
|
||||
//if (props.TryGetValue("Duration", out prop))
|
||||
//{
|
||||
// res.SetAttribute("duration", prop);
|
||||
//}
|
||||
|
||||
//res.SetAttribute("protocolInfo", String.Format(
|
||||
// "http-get:*:{1}:{0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}",
|
||||
// resource.PN, DlnaMaps.Mime[resource.Type], DlnaMaps.DefaultStreaming
|
||||
// ));
|
||||
|
||||
element.AppendChild(res);
|
||||
var video = item as Video;
|
||||
if (video != null)
|
||||
{
|
||||
AddVideoResource(element, video);
|
||||
}
|
||||
|
||||
AddCover(item, element);
|
||||
|
||||
result.DocumentElement.AppendChild(element);
|
||||
}
|
||||
|
||||
private string GetDeviceId()
|
||||
{
|
||||
return "erer";
|
||||
}
|
||||
|
||||
private void AddVideoResource(XmlElement container, Video video)
|
||||
{
|
||||
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
|
||||
|
||||
var sources = _dtoService.GetMediaSources(video);
|
||||
|
||||
int? maxBitrateSetting = null;
|
||||
|
||||
var streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
|
||||
{
|
||||
ItemId = video.Id.ToString("N"),
|
||||
MediaSources = sources,
|
||||
Profile = _profile,
|
||||
DeviceId = GetDeviceId(),
|
||||
MaxBitrate = maxBitrateSetting
|
||||
});
|
||||
|
||||
var url = streamInfo.ToDlnaUrl(_serverAddress);
|
||||
res.InnerText = url;
|
||||
|
||||
var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId));
|
||||
|
||||
if (mediaSource.RunTimeTicks.HasValue)
|
||||
{
|
||||
res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
|
||||
}
|
||||
|
||||
if (streamInfo.IsDirectStream && mediaSource.Size.HasValue)
|
||||
{
|
||||
res.SetAttribute("size", mediaSource.Size.Value.ToString(_usCulture));
|
||||
}
|
||||
|
||||
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video && !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase));
|
||||
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
|
||||
var targetAudioBitrate = streamInfo.AudioBitrate ?? (audioStream == null ? null : audioStream.BitRate);
|
||||
var targetSampleRate = audioStream == null ? null : audioStream.SampleRate;
|
||||
var targetChannels = streamInfo.MaxAudioChannels ?? (audioStream == null ? null : audioStream.Channels);
|
||||
|
||||
var targetWidth = streamInfo.MaxWidth ?? (videoStream == null ? null : videoStream.Width);
|
||||
var targetHeight = streamInfo.MaxHeight ?? (videoStream == null ? null : videoStream.Height);
|
||||
|
||||
var targetVideoCodec = streamInfo.IsDirectStream
|
||||
? (videoStream == null ? null : videoStream.Codec)
|
||||
: streamInfo.VideoCodec;
|
||||
|
||||
var targetAudioCodec = streamInfo.IsDirectStream
|
||||
? (audioStream == null ? null : audioStream.Codec)
|
||||
: streamInfo.AudioCodec;
|
||||
|
||||
var targetBitrate = maxBitrateSetting ?? mediaSource.Bitrate;
|
||||
|
||||
if (targetChannels.HasValue)
|
||||
{
|
||||
res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
|
||||
}
|
||||
|
||||
if (targetWidth.HasValue && targetHeight.HasValue)
|
||||
{
|
||||
res.SetAttribute("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
|
||||
}
|
||||
|
||||
if (targetSampleRate.HasValue)
|
||||
{
|
||||
res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
|
||||
}
|
||||
|
||||
if (targetAudioBitrate.HasValue)
|
||||
{
|
||||
res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
|
||||
}
|
||||
|
||||
var formatProfile = new MediaFormatProfileResolver().ResolveVideoFormat(streamInfo.Container,
|
||||
targetVideoCodec,
|
||||
targetAudioCodec,
|
||||
targetWidth,
|
||||
targetHeight,
|
||||
targetBitrate,
|
||||
TransportStreamTimestamp.NONE);
|
||||
|
||||
var filename = url.Substring(0, url.IndexOf('?'));
|
||||
|
||||
var orgOpValue = DlnaMaps.GetOrgOpValue(mediaSource.RunTimeTicks.HasValue, streamInfo.IsDirectStream, streamInfo.TranscodeSeekInfo);
|
||||
|
||||
var orgCi = streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
|
||||
|
||||
res.SetAttribute("protocolInfo", String.Format(
|
||||
"http-get:*:{0}:DLNA.ORG_PN={1};DLNA.ORG_OP={2};DLNA.ORG_CI={3};DLNA.ORG_FLAGS={4}",
|
||||
MimeTypes.GetMimeType(filename),
|
||||
formatProfile,
|
||||
orgOpValue,
|
||||
orgCi,
|
||||
DlnaMaps.DefaultStreaming
|
||||
));
|
||||
|
||||
container.AppendChild(res);
|
||||
}
|
||||
|
||||
private void AddAudioResource(XmlElement container, Audio audio)
|
||||
{
|
||||
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
|
||||
|
||||
var sources = _dtoService.GetMediaSources(audio);
|
||||
|
||||
var streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
|
||||
{
|
||||
ItemId = audio.Id.ToString("N"),
|
||||
MediaSources = sources,
|
||||
Profile = _profile,
|
||||
DeviceId = GetDeviceId()
|
||||
});
|
||||
|
||||
var url = streamInfo.ToDlnaUrl(_serverAddress);
|
||||
res.InnerText = url;
|
||||
|
||||
var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId));
|
||||
|
||||
if (mediaSource.RunTimeTicks.HasValue)
|
||||
{
|
||||
res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
|
||||
}
|
||||
|
||||
if (streamInfo.IsDirectStream && mediaSource.Size.HasValue)
|
||||
{
|
||||
res.SetAttribute("size", mediaSource.Size.Value.ToString(_usCulture));
|
||||
}
|
||||
|
||||
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
|
||||
var targetAudioBitrate = streamInfo.AudioBitrate ?? (audioStream == null ? null : audioStream.BitRate);
|
||||
var targetSampleRate = audioStream == null ? null : audioStream.SampleRate;
|
||||
var targetChannels = streamInfo.MaxAudioChannels ?? (audioStream == null ? null : audioStream.Channels);
|
||||
|
||||
if (targetChannels.HasValue)
|
||||
{
|
||||
res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
|
||||
}
|
||||
|
||||
if (targetSampleRate.HasValue)
|
||||
{
|
||||
res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
|
||||
}
|
||||
|
||||
if (targetAudioBitrate.HasValue)
|
||||
{
|
||||
res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
|
||||
}
|
||||
|
||||
var formatProfile = new MediaFormatProfileResolver().ResolveAudioFormat(streamInfo.Container, targetAudioBitrate, targetSampleRate, targetChannels);
|
||||
|
||||
var filename = url.Substring(0, url.IndexOf('?'));
|
||||
|
||||
var orgOpValue = DlnaMaps.GetOrgOpValue(mediaSource.RunTimeTicks.HasValue, streamInfo.IsDirectStream, streamInfo.TranscodeSeekInfo);
|
||||
|
||||
var orgCi = streamInfo.IsDirectStream ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
|
||||
|
||||
res.SetAttribute("protocolInfo", String.Format(
|
||||
"http-get:*:{0}:DLNA.ORG_PN={1};DLNA.ORG_OP={2};DLNA.ORG_CI={3};DLNA.ORG_FLAGS={4}",
|
||||
MimeTypes.GetMimeType(filename),
|
||||
formatProfile,
|
||||
orgOpValue,
|
||||
orgCi,
|
||||
DlnaMaps.DefaultStreaming
|
||||
));
|
||||
|
||||
container.AppendChild(res);
|
||||
}
|
||||
|
||||
private XmlElement CreateObjectClass(XmlDocument result, BaseItem item)
|
||||
{
|
||||
var objectClass = result.CreateElement("upnp", "class", NS_UPNP);
|
||||
|
||||
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
||||
if (item.IsFolder)
|
||||
{
|
||||
string classType = null;
|
||||
|
||||
if (!_profile.RequiresPlainFolders)
|
||||
{
|
||||
if (item is MusicAlbum)
|
||||
{
|
||||
classType = "object.container.musicAlbum";
|
||||
}
|
||||
if (item is MusicArtist)
|
||||
{
|
||||
classType = "object.container.musicArtist";
|
||||
}
|
||||
}
|
||||
|
||||
objectClass.InnerText = classType ?? "object.container.storageFolder";
|
||||
}
|
||||
else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
objectClass.InnerText = "object.item.audioItem.musicTrack";
|
||||
}
|
||||
@ -396,10 +607,17 @@ namespace MediaBrowser.Dlna.Server
|
||||
objectClass.InnerText = "object.item.imageItem.photo";
|
||||
}
|
||||
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!_profile.RequiresPlainVideoItems && item is Movie)
|
||||
{
|
||||
objectClass.InnerText = "object.item.videoItem.movie";
|
||||
}
|
||||
else
|
||||
{
|
||||
objectClass.InnerText = "object.item.videoItem";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
@ -407,152 +625,259 @@ namespace MediaBrowser.Dlna.Server
|
||||
return objectClass;
|
||||
}
|
||||
|
||||
private void AddActors(BaseItem item, XmlElement element)
|
||||
private void AddPeople(BaseItem item, XmlElement element)
|
||||
{
|
||||
foreach (var actor in item.People)
|
||||
{
|
||||
var e = element.OwnerDocument.CreateElement("upnp", "actor", NS_UPNP);
|
||||
e.InnerText = actor.Name;
|
||||
element.AppendChild(e);
|
||||
AddValue(element, "upnp", (actor.Type ?? PersonType.Actor).ToLower(), actor.Name, NS_UPNP);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddBookmarkInfo(BaseItem item, User user, XmlElement element)
|
||||
{
|
||||
//var bookmark = bookmarkable.Bookmark;
|
||||
//if (bookmark.HasValue)
|
||||
//{
|
||||
// var dcmInfo = item.OwnerDocument.CreateElement("sec", "dcmInfo", NS_SEC);
|
||||
// dcmInfo.InnerText = string.Format("BM={0}", bookmark.Value);
|
||||
// item.AppendChild(dcmInfo);
|
||||
//}
|
||||
var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
|
||||
|
||||
if (userdata.PlaybackPositionTicks > 0)
|
||||
{
|
||||
var dcmInfo = element.OwnerDocument.CreateElement("sec", "dcmInfo", NS_SEC);
|
||||
dcmInfo.InnerText = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds).ToString(_usCulture));
|
||||
element.AppendChild(dcmInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds fields used by both items and folders
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="element"></param>
|
||||
private void AddCommonFields(BaseItem item, XmlElement element)
|
||||
{
|
||||
if (item.PremiereDate.HasValue)
|
||||
{
|
||||
AddValue(element, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
|
||||
}
|
||||
|
||||
if (item.Genres.Count > 0)
|
||||
{
|
||||
AddValue(element, "upnp", "genre", item.Genres[0], NS_UPNP);
|
||||
}
|
||||
|
||||
if (item.Studios.Count > 0)
|
||||
{
|
||||
AddValue(element, "upnp", "publisher", item.Studios[0], NS_UPNP);
|
||||
}
|
||||
|
||||
AddValue(element, "dc", "title", item.Name, NS_DC);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.Overview))
|
||||
{
|
||||
AddValue(element, "dc", "description", item.Overview, NS_DC);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(item.OfficialRating))
|
||||
{
|
||||
AddValue(element, "dc", "rating", item.OfficialRating, NS_DC);
|
||||
}
|
||||
|
||||
AddPeople(item, element);
|
||||
}
|
||||
|
||||
private void AddGeneralProperties(BaseItem item, XmlElement element)
|
||||
{
|
||||
//var prop = string.Empty;
|
||||
//if (props.TryGetValue("DateO", out prop))
|
||||
//{
|
||||
// var e = item.OwnerDocument.CreateElement("dc", "date", NS_DC);
|
||||
// e.InnerText = prop;
|
||||
// item.AppendChild(e);
|
||||
//}
|
||||
//if (props.TryGetValue("Genre", out prop))
|
||||
//{
|
||||
// var e = item.OwnerDocument.CreateElement("upnp", "genre", NS_UPNP);
|
||||
// e.InnerText = prop;
|
||||
// item.AppendChild(e);
|
||||
//}
|
||||
AddCommonFields(item, element);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.Overview))
|
||||
var audio = item as Audio;
|
||||
|
||||
if (audio != null)
|
||||
{
|
||||
var e = element.OwnerDocument.CreateElement("dc", "description", NS_DC);
|
||||
e.InnerText = item.Overview;
|
||||
element.AppendChild(e);
|
||||
if (audio.Artists.Count > 0)
|
||||
{
|
||||
AddValue(element, "upnp", "artist", audio.Artists[0], NS_UPNP);
|
||||
}
|
||||
|
||||
//if (props.TryGetValue("Artist", out prop))
|
||||
//{
|
||||
// var e = item.OwnerDocument.CreateElement("upnp", "artist", NS_UPNP);
|
||||
// e.SetAttribute("role", "AlbumArtist");
|
||||
// e.InnerText = prop;
|
||||
// item.AppendChild(e);
|
||||
//}
|
||||
//if (props.TryGetValue("Performer", out prop))
|
||||
//{
|
||||
// var e = item.OwnerDocument.CreateElement("upnp", "artist", NS_UPNP);
|
||||
// e.SetAttribute("role", "Performer");
|
||||
// e.InnerText = prop;
|
||||
// item.AppendChild(e);
|
||||
// e = item.OwnerDocument.CreateElement("dc", "creator", NS_DC);
|
||||
// e.InnerText = prop;
|
||||
// item.AppendChild(e);
|
||||
//}
|
||||
//if (props.TryGetValue("Album", out prop))
|
||||
//{
|
||||
// var e = item.OwnerDocument.CreateElement("upnp", "album", NS_UPNP);
|
||||
// e.InnerText = prop;
|
||||
// item.AppendChild(e);
|
||||
//}
|
||||
//if (props.TryGetValue("Track", out prop))
|
||||
//{
|
||||
// var e = item.OwnerDocument.CreateElement("upnp", "originalTrackNumber", NS_UPNP);
|
||||
// e.InnerText = prop;
|
||||
// item.AppendChild(e);
|
||||
//}
|
||||
//if (props.TryGetValue("Creator", out prop))
|
||||
//{
|
||||
// var e = item.OwnerDocument.CreateElement("dc", "creator", NS_DC);
|
||||
// e.InnerText = prop;
|
||||
// item.AppendChild(e);
|
||||
//}
|
||||
if (!string.IsNullOrEmpty(audio.Album))
|
||||
{
|
||||
AddValue(element, "upnp", "album", audio.Album, NS_UPNP);
|
||||
}
|
||||
|
||||
//if (props.TryGetValue("Director", out prop))
|
||||
//{
|
||||
// var e = item.OwnerDocument.CreateElement("upnp", "director", NS_UPNP);
|
||||
// e.InnerText = prop;
|
||||
// item.AppendChild(e);
|
||||
//}
|
||||
if (!string.IsNullOrEmpty(audio.AlbumArtist))
|
||||
{
|
||||
AddValue(element, "upnp", "albumArtist", audio.AlbumArtist, NS_UPNP);
|
||||
}
|
||||
}
|
||||
|
||||
var album = item as MusicAlbum;
|
||||
|
||||
if (album != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(album.AlbumArtist))
|
||||
{
|
||||
AddValue(element, "upnp", "artist", album.AlbumArtist, NS_UPNP);
|
||||
AddValue(element, "upnp", "albumArtist", album.AlbumArtist, NS_UPNP);
|
||||
}
|
||||
}
|
||||
|
||||
var musicVideo = item as MusicVideo;
|
||||
|
||||
if (musicVideo != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(musicVideo.Artist))
|
||||
{
|
||||
AddValue(element, "upnp", "artist", musicVideo.Artist, NS_UPNP);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(musicVideo.Album))
|
||||
{
|
||||
AddValue(element, "upnp", "album", musicVideo.Album, NS_UPNP);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.IndexNumber.HasValue)
|
||||
{
|
||||
AddValue(element, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCover(BaseItem item, XmlElement element)
|
||||
{
|
||||
//var result = item.OwnerDocument;
|
||||
//var cover = resource as IMediaCover;
|
||||
//if (cover == null)
|
||||
//{
|
||||
// return;
|
||||
//}
|
||||
//try
|
||||
//{
|
||||
// var c = cover.Cover;
|
||||
// var curl = String.Format(
|
||||
// "http://{0}:{1}{2}cover/{3}",
|
||||
// request.LocalEndPoint.Address,
|
||||
// request.LocalEndPoint.Port,
|
||||
// prefix,
|
||||
// resource.Id
|
||||
// );
|
||||
// var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
|
||||
// var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
|
||||
// profile.InnerText = "JPEG_TN";
|
||||
// icon.SetAttributeNode(profile);
|
||||
// icon.InnerText = curl;
|
||||
// item.AppendChild(icon);
|
||||
// icon = result.CreateElement("upnp", "icon", NS_UPNP);
|
||||
// profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
|
||||
// profile.InnerText = "JPEG_TN";
|
||||
// icon.SetAttributeNode(profile);
|
||||
// icon.InnerText = curl;
|
||||
// item.AppendChild(icon);
|
||||
var imageInfo = GetImageInfo(item);
|
||||
|
||||
// var res = result.CreateElement(string.Empty, "res", NS_DIDL);
|
||||
// res.InnerText = curl;
|
||||
if (imageInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// res.SetAttribute("protocolInfo", string.Format(
|
||||
// "http-get:*:{1}:{0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}",
|
||||
// c.PN, DlnaMaps.Mime[c.Type], DlnaMaps.DefaultStreaming
|
||||
// ));
|
||||
// var width = c.MetaWidth;
|
||||
// var height = c.MetaHeight;
|
||||
// if (width.HasValue && height.HasValue)
|
||||
// {
|
||||
// res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// res.SetAttribute("resolution", "200x200");
|
||||
// }
|
||||
// res.SetAttribute("protocolInfo", string.Format(
|
||||
// "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=01;DLNA.ORG_CI=1;DLNA.ORG_FLAGS={0}",
|
||||
// DlnaMaps.DefaultInteractive
|
||||
// ));
|
||||
// item.AppendChild(res);
|
||||
//}
|
||||
//catch (Exception)
|
||||
//{
|
||||
// return;
|
||||
//}
|
||||
var result = element.OwnerDocument;
|
||||
|
||||
var curl = GetImageUrl(imageInfo);
|
||||
|
||||
var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
|
||||
var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
|
||||
profile.InnerText = "JPEG_TN";
|
||||
icon.SetAttributeNode(profile);
|
||||
icon.InnerText = curl;
|
||||
element.AppendChild(icon);
|
||||
|
||||
icon = result.CreateElement("upnp", "icon", NS_UPNP);
|
||||
profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
|
||||
profile.InnerText = "JPEG_TN";
|
||||
icon.SetAttributeNode(profile);
|
||||
icon.InnerText = curl;
|
||||
element.AppendChild(icon);
|
||||
|
||||
if (!_profile.EnableAlbumArtInDidl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var res = result.CreateElement(string.Empty, "res", NS_DIDL);
|
||||
res.InnerText = curl;
|
||||
|
||||
int? width = imageInfo.Width;
|
||||
int? height = imageInfo.Height;
|
||||
|
||||
var mediaProfile = new MediaFormatProfileResolver().ResolveImageFormat("jpg", width, height);
|
||||
|
||||
res.SetAttribute("protocolInfo", string.Format(
|
||||
"http-get:*:{1}DLNA.ORG_PN=:{0};DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}",
|
||||
mediaProfile, "image/jpeg", DlnaMaps.DefaultStreaming
|
||||
));
|
||||
|
||||
if (width.HasValue && height.HasValue)
|
||||
{
|
||||
res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value));
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Devices need to see something here?
|
||||
res.SetAttribute("resolution", "200x200");
|
||||
}
|
||||
|
||||
element.AppendChild(res);
|
||||
}
|
||||
|
||||
private ImageDownloadInfo GetImageInfo(BaseItem item)
|
||||
{
|
||||
if (item.HasImage(ImageType.Primary))
|
||||
{
|
||||
return GetImageInfo(item, ImageType.Primary);
|
||||
}
|
||||
if (item.HasImage(ImageType.Thumb))
|
||||
{
|
||||
return GetImageInfo(item, ImageType.Thumb);
|
||||
}
|
||||
|
||||
if (item is Audio || item is Episode)
|
||||
{
|
||||
item = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary));
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
return GetImageInfo(item, ImageType.Primary);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
|
||||
{
|
||||
var imageInfo = item.GetImageInfo(type, 0);
|
||||
string tag = null;
|
||||
|
||||
try
|
||||
{
|
||||
var guid = _imageProcessor.GetImageCacheTag(item, ImageType.Primary);
|
||||
|
||||
tag = guid.HasValue ? guid.Value.ToString("N") : null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int? width = null;
|
||||
int? height = null;
|
||||
|
||||
try
|
||||
{
|
||||
var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified);
|
||||
|
||||
width = Convert.ToInt32(size.Width);
|
||||
height = Convert.ToInt32(size.Height);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return new ImageDownloadInfo
|
||||
{
|
||||
ItemId = item.Id.ToString("N"),
|
||||
Type = ImageType.Primary,
|
||||
ImageTag = tag,
|
||||
Width = width,
|
||||
Height = height
|
||||
};
|
||||
}
|
||||
|
||||
class ImageDownloadInfo
|
||||
{
|
||||
internal string ItemId;
|
||||
internal string ImageTag;
|
||||
internal ImageType Type;
|
||||
|
||||
internal int? Width;
|
||||
internal int? Height;
|
||||
}
|
||||
|
||||
private string GetImageUrl(ImageDownloadInfo info)
|
||||
{
|
||||
return string.Format("{0}/Items/{1}/Images/{2}?tag={3}&format=jpg",
|
||||
_serverAddress,
|
||||
info.ItemId,
|
||||
info.Type,
|
||||
info.ImageTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ namespace MediaBrowser.Dlna.Server
|
||||
ServiceType = "urn:schemas-upnp-org:service:ContentDirectory:1",
|
||||
ServiceId = "urn:upnp-org:serviceId:ContentDirectory",
|
||||
ScpdUrl = "/mediabrowser/dlna/contentdirectory.xml",
|
||||
ControlUrl = "/mediabrowser/dlna/control"
|
||||
ControlUrl = "/mediabrowser/dlna/" + _serverUdn + "/control"
|
||||
});
|
||||
|
||||
return list;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using MediaBrowser.Common;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@ -18,8 +19,12 @@ namespace MediaBrowser.Dlna.Server
|
||||
private readonly IApplicationHost _appHost;
|
||||
private readonly INetworkManager _network;
|
||||
|
||||
public static DlnaServerEntryPoint Instance;
|
||||
|
||||
public DlnaServerEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost, INetworkManager network)
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
_config = config;
|
||||
_appHost = appHost;
|
||||
_network = network;
|
||||
@ -86,6 +91,11 @@ namespace MediaBrowser.Dlna.Server
|
||||
}
|
||||
}
|
||||
|
||||
public UpnpDevice GetServerUpnpDevice(string uuid)
|
||||
{
|
||||
return _ssdpHandler.Devices.FirstOrDefault(i => string.Equals(uuid, i.Uuid.ToString("N"), StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private void DisposeServer()
|
||||
{
|
||||
lock (_syncLock)
|
||||
|
@ -44,7 +44,7 @@ namespace MediaBrowser.Dlna.Server
|
||||
Start();
|
||||
}
|
||||
|
||||
private IEnumerable<UpnpDevice> Devices
|
||||
public IEnumerable<UpnpDevice> Devices
|
||||
{
|
||||
get
|
||||
{
|
||||
|
@ -224,7 +224,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
case EncodingQuality.HighSpeed:
|
||||
return 2;
|
||||
case EncodingQuality.HighQuality:
|
||||
return isWebm ? Math.Max((int)((Environment.ProcessorCount -1) / 2) , 2) : 0;
|
||||
return 2;
|
||||
case EncodingQuality.MaxQuality:
|
||||
return isWebm ? Math.Max(Environment.ProcessorCount - 1, 2) : 0;
|
||||
default:
|
||||
|
@ -768,7 +768,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
}
|
||||
|
||||
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
|
||||
var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2},thumbnail=40\" -f image2 \"{1}\"", inputPath, "-", vf) :
|
||||
var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2},thumbnail=30\" -f image2 \"{1}\"", inputPath, "-", vf) :
|
||||
string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, "-", vf);
|
||||
|
||||
var probeSize = GetProbeSizeArgument(type);
|
||||
|
@ -119,6 +119,15 @@
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\DirectPlayProfile.cs">
|
||||
<Link>Dlna\DirectPlayProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\DlnaMaps.cs">
|
||||
<Link>Dlna\DlnaMaps.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfile.cs">
|
||||
<Link>Dlna\MediaFormatProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
|
||||
<Link>Dlna\MediaFormatProfileResolver.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\ResponseProfile.cs">
|
||||
<Link>Dlna\ResponseProfile.cs</Link>
|
||||
</Compile>
|
||||
|
@ -106,6 +106,15 @@
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\DirectPlayProfile.cs">
|
||||
<Link>Dlna\DirectPlayProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\DlnaMaps.cs">
|
||||
<Link>Dlna\DlnaMaps.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfile.cs">
|
||||
<Link>Dlna\MediaFormatProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
|
||||
<Link>Dlna\MediaFormatProfileResolver.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\ResponseProfile.cs">
|
||||
<Link>Dlna\ResponseProfile.cs</Link>
|
||||
</Compile>
|
||||
|
62
MediaBrowser.Model/Dlna/DlnaMaps.cs
Normal file
62
MediaBrowser.Model/Dlna/DlnaMaps.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public class DlnaMaps
|
||||
{
|
||||
public static readonly string DefaultStreaming =
|
||||
FlagsToString(DlnaFlags.StreamingTransferMode |
|
||||
DlnaFlags.BackgroundTransferMode |
|
||||
DlnaFlags.ConnectionStall |
|
||||
DlnaFlags.ByteBasedSeek |
|
||||
DlnaFlags.DlnaV15);
|
||||
|
||||
public static readonly string DefaultInteractive =
|
||||
FlagsToString(DlnaFlags.InteractiveTransferMode |
|
||||
DlnaFlags.BackgroundTransferMode |
|
||||
DlnaFlags.ConnectionStall |
|
||||
DlnaFlags.ByteBasedSeek |
|
||||
DlnaFlags.DlnaV15);
|
||||
|
||||
public static string FlagsToString(DlnaFlags flags)
|
||||
{
|
||||
return string.Format("{0:X8}{1:D24}", (ulong)flags, 0);
|
||||
}
|
||||
|
||||
public static string GetOrgOpValue(bool hasKnownRuntime, bool isDirectStream, TranscodeSeekInfo profileTranscodeSeekInfo)
|
||||
{
|
||||
if (hasKnownRuntime)
|
||||
{
|
||||
var orgOp = string.Empty;
|
||||
|
||||
// Time-based seeking currently only possible when transcoding
|
||||
orgOp += isDirectStream ? "0" : "1";
|
||||
|
||||
// Byte-based seeking only possible when not transcoding
|
||||
orgOp += isDirectStream || profileTranscodeSeekInfo == TranscodeSeekInfo.Bytes ? "1" : "0";
|
||||
|
||||
return orgOp;
|
||||
}
|
||||
|
||||
// No seeking is available if we don't know the content runtime
|
||||
return "00";
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum DlnaFlags : ulong
|
||||
{
|
||||
BackgroundTransferMode = (1 << 22),
|
||||
ByteBasedSeek = (1 << 29),
|
||||
ConnectionStall = (1 << 21),
|
||||
DlnaV15 = (1 << 20),
|
||||
InteractiveTransferMode = (1 << 23),
|
||||
PlayContainer = (1 << 28),
|
||||
RtspPause = (1 << 25),
|
||||
S0Increase = (1 << 27),
|
||||
SenderPaced = (1L << 31),
|
||||
SnIncrease = (1 << 26),
|
||||
StreamingTransferMode = (1 << 24),
|
||||
TimeBasedSeek = (1 << 30)
|
||||
}
|
||||
}
|
122
MediaBrowser.Model/Dlna/MediaFormatProfile.cs
Normal file
122
MediaBrowser.Model/Dlna/MediaFormatProfile.cs
Normal file
@ -0,0 +1,122 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public enum MediaFormatProfile
|
||||
{
|
||||
MP3,
|
||||
WMA_BASE,
|
||||
WMA_FULL,
|
||||
LPCM16_44_MONO,
|
||||
LPCM16_44_STEREO,
|
||||
LPCM16_48_MONO,
|
||||
LPCM16_48_STEREO,
|
||||
AAC_ISO,
|
||||
AAC_ISO_320,
|
||||
AAC_ADTS,
|
||||
AAC_ADTS_320,
|
||||
FLAC,
|
||||
OGG,
|
||||
|
||||
JPEG_SM,
|
||||
JPEG_MED,
|
||||
JPEG_LRG,
|
||||
JPEG_TN,
|
||||
PNG_LRG,
|
||||
PNG_TN,
|
||||
GIF_LRG,
|
||||
RAW,
|
||||
|
||||
MPEG1,
|
||||
MPEG_PS_PAL,
|
||||
MPEG_PS_NTSC,
|
||||
MPEG_TS_SD_EU,
|
||||
MPEG_TS_SD_EU_ISO,
|
||||
MPEG_TS_SD_EU_T,
|
||||
MPEG_TS_SD_NA,
|
||||
MPEG_TS_SD_NA_ISO,
|
||||
MPEG_TS_SD_NA_T,
|
||||
MPEG_TS_SD_KO,
|
||||
MPEG_TS_SD_KO_ISO,
|
||||
MPEG_TS_SD_KO_T,
|
||||
MPEG_TS_JP_T,
|
||||
AVI,
|
||||
MATROSKA,
|
||||
FLV,
|
||||
DVR_MS,
|
||||
WTV,
|
||||
OGV,
|
||||
AVC_MP4_MP_SD_AAC_MULT5,
|
||||
AVC_MP4_MP_SD_MPEG1_L3,
|
||||
AVC_MP4_MP_SD_AC3,
|
||||
AVC_MP4_MP_HD_720p_AAC,
|
||||
AVC_MP4_MP_HD_1080i_AAC,
|
||||
AVC_MP4_HP_HD_AAC,
|
||||
AVC_TS_MP_HD_AAC_MULT5,
|
||||
AVC_TS_MP_HD_AAC_MULT5_T,
|
||||
AVC_TS_MP_HD_AAC_MULT5_ISO,
|
||||
AVC_TS_MP_HD_MPEG1_L3,
|
||||
AVC_TS_MP_HD_MPEG1_L3_T,
|
||||
AVC_TS_MP_HD_MPEG1_L3_ISO,
|
||||
AVC_TS_MP_HD_AC3,
|
||||
AVC_TS_MP_HD_AC3_T,
|
||||
AVC_TS_MP_HD_AC3_ISO,
|
||||
AVC_TS_HP_HD_MPEG1_L2_T,
|
||||
AVC_TS_HP_HD_MPEG1_L2_ISO,
|
||||
AVC_TS_MP_SD_AAC_MULT5,
|
||||
AVC_TS_MP_SD_AAC_MULT5_T,
|
||||
AVC_TS_MP_SD_AAC_MULT5_ISO,
|
||||
AVC_TS_MP_SD_MPEG1_L3,
|
||||
AVC_TS_MP_SD_MPEG1_L3_T,
|
||||
AVC_TS_MP_SD_MPEG1_L3_ISO,
|
||||
AVC_TS_HP_SD_MPEG1_L2_T,
|
||||
AVC_TS_HP_SD_MPEG1_L2_ISO,
|
||||
AVC_TS_MP_SD_AC3,
|
||||
AVC_TS_MP_SD_AC3_T,
|
||||
AVC_TS_MP_SD_AC3_ISO,
|
||||
AVC_TS_HD_DTS_T,
|
||||
AVC_TS_HD_DTS_ISO,
|
||||
WMVMED_BASE,
|
||||
WMVMED_FULL,
|
||||
WMVMED_PRO,
|
||||
WMVHIGH_FULL,
|
||||
WMVHIGH_PRO,
|
||||
VC1_ASF_AP_L1_WMA,
|
||||
VC1_ASF_AP_L2_WMA,
|
||||
VC1_ASF_AP_L3_WMA,
|
||||
VC1_TS_AP_L1_AC3_ISO,
|
||||
VC1_TS_AP_L2_AC3_ISO,
|
||||
VC1_TS_HD_DTS_ISO,
|
||||
VC1_TS_HD_DTS_T,
|
||||
MPEG4_P2_MP4_ASP_AAC,
|
||||
MPEG4_P2_MP4_SP_L6_AAC,
|
||||
MPEG4_P2_MP4_NDSD,
|
||||
MPEG4_P2_TS_ASP_AAC,
|
||||
MPEG4_P2_TS_ASP_AAC_T,
|
||||
MPEG4_P2_TS_ASP_AAC_ISO,
|
||||
MPEG4_P2_TS_ASP_MPEG1_L3,
|
||||
MPEG4_P2_TS_ASP_MPEG1_L3_T,
|
||||
MPEG4_P2_TS_ASP_MPEG1_L3_ISO,
|
||||
MPEG4_P2_TS_ASP_MPEG2_L2,
|
||||
MPEG4_P2_TS_ASP_MPEG2_L2_T,
|
||||
MPEG4_P2_TS_ASP_MPEG2_L2_ISO,
|
||||
MPEG4_P2_TS_ASP_AC3,
|
||||
MPEG4_P2_TS_ASP_AC3_T,
|
||||
MPEG4_P2_TS_ASP_AC3_ISO,
|
||||
AVC_TS_HD_50_LPCM_T,
|
||||
AVC_MP4_LPCM,
|
||||
MPEG4_P2_3GPP_SP_L0B_AAC,
|
||||
MPEG4_P2_3GPP_SP_L0B_AMR,
|
||||
AVC_3GPP_BL_QCIF15_AAC,
|
||||
MPEG4_H263_3GPP_P0_L10_AMR,
|
||||
MPEG4_H263_MP4_P0_L10_AAC
|
||||
}
|
||||
|
||||
public enum TransportStreamTimestamp
|
||||
{
|
||||
NONE,
|
||||
ZERO,
|
||||
VALID
|
||||
}
|
||||
}
|
342
MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
Normal file
342
MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
Normal file
@ -0,0 +1,342 @@
|
||||
using System;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public class MediaFormatProfileResolver
|
||||
{
|
||||
public MediaFormatProfile ResolveVideoFormat(string container, string videoCodec, string audioCodec, int? width, int? height, int? bitrate, TransportStreamTimestamp timestampType)
|
||||
{
|
||||
if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
|
||||
return ResolveVideoASFFormat(videoCodec, audioCodec, width, height, bitrate);
|
||||
if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase))
|
||||
return ResolveVideoMP4Format(videoCodec, audioCodec, width, height, bitrate);
|
||||
if (string.Equals(container, "avi", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.AVI;
|
||||
if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.MATROSKA;
|
||||
if (string.Equals(container, "mpeg2ps", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
|
||||
// MediaFormatProfile.MPEG_PS_PAL, MediaFormatProfile.MPEG_PS_NTSC
|
||||
return MediaFormatProfile.MPEG_PS_NTSC;
|
||||
if (string.Equals(container, "mpeg1video", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.MPEG1;
|
||||
if (string.Equals(container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
|
||||
return ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, bitrate, timestampType);
|
||||
if (string.Equals(container, "flv", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.FLV;
|
||||
if (string.Equals(container, "wtv", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.WTV;
|
||||
if (string.Equals(container, "3gp", StringComparison.OrdinalIgnoreCase))
|
||||
return ResolveVideo3GPFormat(videoCodec, audioCodec, width, height, bitrate);
|
||||
if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.OGV;
|
||||
|
||||
throw new ArgumentException("Unsupported container: " + container);
|
||||
}
|
||||
|
||||
private MediaFormatProfile ResolveVideoMPEG2TSFormat(string videoCodec, string audioCodec, int? width, int? height, int? bitrate, TransportStreamTimestamp timestampType)
|
||||
{
|
||||
// String suffix = "";
|
||||
// if (isNoTimestamp(timestampType))
|
||||
// suffix = "_ISO";
|
||||
// else if (timestampType == TransportStreamTimestamp.VALID) {
|
||||
// suffix = "_T";
|
||||
// }
|
||||
|
||||
// String resolution = "S";
|
||||
// if ((width.intValue() > 720) || (height.intValue() > 576)) {
|
||||
// resolution = "H";
|
||||
// }
|
||||
|
||||
// if (videoCodec == VideoCodec.MPEG2)
|
||||
// {
|
||||
// List!(MediaFormatProfile) profiles = Arrays.asList(cast(MediaFormatProfile[])[ MediaFormatProfile.valueOf("MPEG_TS_SD_EU" + suffix), MediaFormatProfile.valueOf("MPEG_TS_SD_NA" + suffix), MediaFormatProfile.valueOf("MPEG_TS_SD_KO" + suffix) ]);
|
||||
|
||||
// if ((timestampType == TransportStreamTimestamp.VALID) && (audioCodec == AudioCodec.AAC)) {
|
||||
// profiles.add(MediaFormatProfile.MPEG_TS_JP_T);
|
||||
// }
|
||||
// return profiles;
|
||||
// }if (videoCodec == VideoCodec.H264)
|
||||
// {
|
||||
// if (audioCodec == AudioCodec.LPCM)
|
||||
// return Collections.singletonList(MediaFormatProfile.AVC_TS_HD_50_LPCM_T);
|
||||
// if (audioCodec == AudioCodec.DTS) {
|
||||
// if (isNoTimestamp(timestampType)) {
|
||||
// return Collections.singletonList(MediaFormatProfile.AVC_TS_HD_DTS_ISO);
|
||||
// }
|
||||
// return Collections.singletonList(MediaFormatProfile.AVC_TS_HD_DTS_T);
|
||||
// }
|
||||
// if (audioCodec == AudioCodec.MP2) {
|
||||
// if (isNoTimestamp(timestampType)) {
|
||||
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_HP_%sD_MPEG1_L2_ISO", cast(Object[])[ resolution ])));
|
||||
// }
|
||||
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_HP_%sD_MPEG1_L2_T", cast(Object[])[ resolution ])));
|
||||
// }
|
||||
|
||||
// if (audioCodec == AudioCodec.AAC)
|
||||
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_MP_%sD_AAC_MULT5%s", cast(Object[])[ resolution, suffix ])));
|
||||
// if (audioCodec == AudioCodec.MP3)
|
||||
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_MP_%sD_MPEG1_L3%s", cast(Object[])[ resolution, suffix ])));
|
||||
// if ((audioCodec is null) || (audioCodec == AudioCodec.AC3)) {
|
||||
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("AVC_TS_MP_%sD_AC3%s", cast(Object[])[ resolution, suffix ])));
|
||||
// }
|
||||
// }
|
||||
// else if (videoCodec == VideoCodec.VC1) {
|
||||
// if ((audioCodec is null) || (audioCodec == AudioCodec.AC3))
|
||||
// {
|
||||
// if ((width.intValue() > 720) || (height.intValue() > 576)) {
|
||||
// return Collections.singletonList(MediaFormatProfile.VC1_TS_AP_L2_AC3_ISO);
|
||||
// }
|
||||
// return Collections.singletonList(MediaFormatProfile.VC1_TS_AP_L1_AC3_ISO);
|
||||
// }
|
||||
// if (audioCodec == AudioCodec.DTS) {
|
||||
// suffix = suffix.equals("_ISO") ? suffix : "_T";
|
||||
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("VC1_TS_HD_DTS%s", cast(Object[])[ suffix ])));
|
||||
// }
|
||||
// } else if ((videoCodec == VideoCodec.MPEG4) || (videoCodec == VideoCodec.MSMPEG4)) {
|
||||
// if (audioCodec == AudioCodec.AAC)
|
||||
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_AAC%s", cast(Object[])[ suffix ])));
|
||||
// if (audioCodec == AudioCodec.MP3)
|
||||
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_MPEG1_L3%s", cast(Object[])[ suffix ])));
|
||||
// if (audioCodec == AudioCodec.MP2)
|
||||
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_MPEG2_L2%s", cast(Object[])[ suffix ])));
|
||||
// if ((audioCodec is null) || (audioCodec == AudioCodec.AC3)) {
|
||||
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_AC3%s", cast(Object[])[ suffix ])));
|
||||
// }
|
||||
// }
|
||||
|
||||
throw new ArgumentException("Mpeg video file does not match any supported DLNA profile");
|
||||
}
|
||||
|
||||
private MediaFormatProfile ResolveVideoMP4Format(string videoCodec, string audioCodec, int? width, int? height, int? bitrate)
|
||||
{
|
||||
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.Equals(audioCodec, "lpcm", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.AVC_MP4_LPCM;
|
||||
if (string.IsNullOrEmpty(audioCodec) ||
|
||||
string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaFormatProfile.AVC_MP4_MP_SD_AC3;
|
||||
}
|
||||
if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaFormatProfile.AVC_MP4_MP_SD_MPEG1_L3;
|
||||
}
|
||||
if (width.HasValue && height.HasValue)
|
||||
{
|
||||
if ((width.Value <= 720) && (height.Value <= 576))
|
||||
{
|
||||
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.AVC_MP4_MP_SD_AAC_MULT5;
|
||||
}
|
||||
else if ((width.Value <= 1280) && (height.Value <= 720))
|
||||
{
|
||||
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.AVC_MP4_MP_HD_720p_AAC;
|
||||
}
|
||||
else if ((width.Value <= 1920) && (height.Value <= 1080))
|
||||
{
|
||||
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaFormatProfile.AVC_MP4_MP_HD_1080i_AAC;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (width.HasValue && height.HasValue && width.Value <= 720 && height.Value <= 576)
|
||||
{
|
||||
if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.MPEG4_P2_MP4_ASP_AAC;
|
||||
if (string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase) || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaFormatProfile.MPEG4_P2_MP4_NDSD;
|
||||
}
|
||||
}
|
||||
else if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaFormatProfile.MPEG4_P2_MP4_SP_L6_AAC;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(videoCodec, "h263", StringComparison.OrdinalIgnoreCase) && string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaFormatProfile.MPEG4_H263_MP4_P0_L10_AAC;
|
||||
}
|
||||
|
||||
throw new ArgumentException("MP4 video file does not match any supported DLNA profile");
|
||||
}
|
||||
|
||||
private MediaFormatProfile ResolveVideo3GPFormat(string videoCodec, string audioCodec, int? width, int? height, int? bitrate)
|
||||
{
|
||||
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.AVC_3GPP_BL_QCIF15_AAC;
|
||||
}
|
||||
else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.MPEG4_P2_3GPP_SP_L0B_AAC;
|
||||
if (string.Equals(audioCodec, "amrnb", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.MPEG4_P2_3GPP_SP_L0B_AMR;
|
||||
}
|
||||
else if (string.Equals(videoCodec, "h263", StringComparison.OrdinalIgnoreCase) && string.Equals(audioCodec, "amrnb", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaFormatProfile.MPEG4_H263_3GPP_P0_L10_AMR;
|
||||
}
|
||||
|
||||
throw new ArgumentException("3GP video file does not match any supported DLNA profile");
|
||||
}
|
||||
private MediaFormatProfile ResolveVideoASFFormat(string videoCodec, string audioCodec, int? width, int? height, int? bitrate)
|
||||
{
|
||||
if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase) &&
|
||||
(string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "wmapro", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
|
||||
if (width.HasValue && height.HasValue)
|
||||
{
|
||||
if ((width.Value <= 720) && (height.Value <= 576))
|
||||
{
|
||||
if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaFormatProfile.WMVMED_FULL;
|
||||
}
|
||||
return MediaFormatProfile.WMVMED_PRO;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaFormatProfile.WMVHIGH_FULL;
|
||||
}
|
||||
return MediaFormatProfile.WMVHIGH_PRO;
|
||||
}
|
||||
|
||||
if (string.Equals(videoCodec, "vc1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (width.HasValue && height.HasValue)
|
||||
{
|
||||
if ((width.Value <= 720) && (height.Value <= 576))
|
||||
return MediaFormatProfile.VC1_ASF_AP_L1_WMA;
|
||||
if ((width.Value <= 1280) && (height.Value <= 720))
|
||||
return MediaFormatProfile.VC1_ASF_AP_L2_WMA;
|
||||
if ((width.Value <= 1920) && (height.Value <= 1080))
|
||||
return MediaFormatProfile.VC1_ASF_AP_L3_WMA;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(videoCodec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return MediaFormatProfile.DVR_MS;
|
||||
}
|
||||
|
||||
throw new ArgumentException("ASF video file does not match any supported DLNA profile");
|
||||
}
|
||||
|
||||
public MediaFormatProfile ResolveAudioFormat(string container, int? bitrate, int? frequency, int? channels)
|
||||
{
|
||||
if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
|
||||
return ResolveAudioASFFormat(bitrate, frequency, channels);
|
||||
if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.MP3;
|
||||
if (string.Equals(container, "lpcm", StringComparison.OrdinalIgnoreCase))
|
||||
return ResolveAudioLPCMFormat(bitrate, frequency, channels);
|
||||
if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase))
|
||||
return ResolveAudioMP4Format(bitrate, frequency, channels);
|
||||
if (string.Equals(container, "adts", StringComparison.OrdinalIgnoreCase))
|
||||
return ResolveAudioADTSFormat(bitrate, frequency, channels);
|
||||
if (string.Equals(container, "flac", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.FLAC;
|
||||
if (string.Equals(container, "oga", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.OGG;
|
||||
throw new ArgumentException("Unsupported container: " + container);
|
||||
}
|
||||
|
||||
private MediaFormatProfile ResolveAudioASFFormat(int? bitrate, int? frequency, int? channels)
|
||||
{
|
||||
if (bitrate.HasValue && bitrate.Value <= 193)
|
||||
{
|
||||
return MediaFormatProfile.WMA_BASE;
|
||||
}
|
||||
return MediaFormatProfile.WMA_FULL;
|
||||
}
|
||||
|
||||
private MediaFormatProfile ResolveAudioLPCMFormat(int? bitrate, int? frequency, int? channels)
|
||||
{
|
||||
if (frequency.HasValue && channels.HasValue)
|
||||
{
|
||||
if (frequency.Value == 44100 && channels.Value == 1)
|
||||
{
|
||||
return MediaFormatProfile.LPCM16_44_MONO;
|
||||
}
|
||||
if (frequency.Value == 44100 && channels.Value == 2)
|
||||
{
|
||||
return MediaFormatProfile.LPCM16_44_STEREO;
|
||||
}
|
||||
if (frequency.Value == 48000 && channels.Value == 1)
|
||||
{
|
||||
return MediaFormatProfile.LPCM16_48_MONO;
|
||||
}
|
||||
if (frequency.Value == 48000 && channels.Value == 1)
|
||||
{
|
||||
return MediaFormatProfile.LPCM16_48_STEREO;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Unsupported LPCM format of file %s. Only 44100 / 48000 Hz and Mono / Stereo files are allowed.");
|
||||
}
|
||||
|
||||
return MediaFormatProfile.LPCM16_48_STEREO;
|
||||
}
|
||||
|
||||
private MediaFormatProfile ResolveAudioMP4Format(int? bitrate, int? frequency, int? channels)
|
||||
{
|
||||
if (bitrate.HasValue && bitrate.Value <= 320)
|
||||
{
|
||||
return MediaFormatProfile.AAC_ISO_320;
|
||||
}
|
||||
return MediaFormatProfile.AAC_ISO;
|
||||
}
|
||||
|
||||
private MediaFormatProfile ResolveAudioADTSFormat(int? bitrate, int? frequency, int? channels)
|
||||
{
|
||||
if (bitrate.HasValue && bitrate.Value <= 320)
|
||||
{
|
||||
return MediaFormatProfile.AAC_ADTS_320;
|
||||
}
|
||||
return MediaFormatProfile.AAC_ADTS;
|
||||
}
|
||||
|
||||
public MediaFormatProfile ResolveImageFormat(string container, int? width, int? height)
|
||||
{
|
||||
if (string.Equals(container, "jpeg", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(container, "jpg", StringComparison.OrdinalIgnoreCase))
|
||||
return ResolveImageJPGFormat(width, height);
|
||||
if (string.Equals(container, "png", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.PNG_LRG;
|
||||
if (string.Equals(container, "gif", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.GIF_LRG;
|
||||
if (string.Equals(container, "raw", StringComparison.OrdinalIgnoreCase))
|
||||
return MediaFormatProfile.RAW;
|
||||
|
||||
throw new ArgumentException("Unsupported container: " + container);
|
||||
}
|
||||
|
||||
private MediaFormatProfile ResolveImageJPGFormat(int? width, int? height)
|
||||
{
|
||||
if (width.HasValue && height.HasValue)
|
||||
{
|
||||
if ((width.Value <= 640) && (height.Value <= 480))
|
||||
return MediaFormatProfile.JPEG_SM;
|
||||
|
||||
if ((width.Value <= 1024) && (height.Value <= 768))
|
||||
{
|
||||
return MediaFormatProfile.JPEG_MED;
|
||||
}
|
||||
}
|
||||
|
||||
return MediaFormatProfile.JPEG_LRG;
|
||||
}
|
||||
}
|
||||
}
|
@ -81,7 +81,8 @@ namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
ItemId = options.ItemId,
|
||||
MediaType = DlnaProfileType.Audio,
|
||||
MediaSourceId = item.Id
|
||||
MediaSourceId = item.Id,
|
||||
RunTimeTicks = item.RunTimeTicks
|
||||
};
|
||||
|
||||
var audioStream = item.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
@ -114,6 +115,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
if (transcodingProfile != null)
|
||||
{
|
||||
playlistItem.IsDirectStream = false;
|
||||
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
|
||||
playlistItem.Container = transcodingProfile.Container;
|
||||
playlistItem.AudioCodec = transcodingProfile.AudioCodec;
|
||||
|
||||
@ -150,7 +152,8 @@ namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
ItemId = options.ItemId,
|
||||
MediaType = DlnaProfileType.Video,
|
||||
MediaSourceId = item.Id
|
||||
MediaSourceId = item.Id,
|
||||
RunTimeTicks = item.RunTimeTicks
|
||||
};
|
||||
|
||||
var audioStream = item.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
@ -193,6 +196,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
playlistItem.IsDirectStream = false;
|
||||
playlistItem.Container = transcodingProfile.Container;
|
||||
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
|
||||
playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',').FirstOrDefault();
|
||||
playlistItem.VideoCodec = transcodingProfile.VideoCodec;
|
||||
|
||||
|
@ -46,6 +46,10 @@ namespace MediaBrowser.Model.Dlna
|
||||
public string DeviceProfileId { get; set; }
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
public long? RunTimeTicks { get; set; }
|
||||
|
||||
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
||||
|
||||
public string ToUrl(string baseUrl)
|
||||
{
|
||||
return ToDlnaUrl(baseUrl);
|
||||
|
@ -10,6 +10,7 @@ namespace MediaBrowser.Model.Dto
|
||||
public string Path { get; set; }
|
||||
|
||||
public string Container { get; set; }
|
||||
public long? Size { get; set; }
|
||||
|
||||
public LocationType LocationType { get; set; }
|
||||
|
||||
@ -25,6 +26,14 @@ namespace MediaBrowser.Model.Dto
|
||||
|
||||
public List<MediaStream> MediaStreams { get; set; }
|
||||
|
||||
public List<string> Formats { get; set; }
|
||||
|
||||
public int? Bitrate { get; set; }
|
||||
|
||||
public MediaSourceInfo()
|
||||
{
|
||||
Formats = new List<string>();
|
||||
MediaStreams = new List<MediaStream>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,18 @@ namespace MediaBrowser.Model.Entities
|
||||
/// <value>The primary image item identifier.</value>
|
||||
public string PrimaryImageItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logo image tag.
|
||||
/// </summary>
|
||||
/// <value>The logo image tag.</value>
|
||||
public Guid? LogoImageTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logo item identifier.
|
||||
/// </summary>
|
||||
/// <value>The logo item identifier.</value>
|
||||
public string LogoItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the thumb image tag.
|
||||
/// </summary>
|
||||
|
@ -72,6 +72,9 @@
|
||||
<Compile Include="Dlna\DeviceProfile.cs" />
|
||||
<Compile Include="Dlna\DeviceProfileInfo.cs" />
|
||||
<Compile Include="Dlna\DirectPlayProfile.cs" />
|
||||
<Compile Include="Dlna\DlnaMaps.cs" />
|
||||
<Compile Include="Dlna\MediaFormatProfile.cs" />
|
||||
<Compile Include="Dlna\MediaFormatProfileResolver.cs" />
|
||||
<Compile Include="Dlna\ResponseProfile.cs" />
|
||||
<Compile Include="Dlna\StreamBuilder.cs" />
|
||||
<Compile Include="Dlna\StreamInfo.cs" />
|
||||
|
@ -90,6 +90,19 @@ namespace MediaBrowser.Model.Session
|
||||
/// </summary>
|
||||
/// <value>The volume level.</value>
|
||||
public int? VolumeLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the play method.
|
||||
/// </summary>
|
||||
/// <value>The play method.</value>
|
||||
public PlayMethod PlayMethod { get; set; }
|
||||
}
|
||||
|
||||
public enum PlayMethod
|
||||
{
|
||||
Transcode = 0,
|
||||
DirectStream = 1,
|
||||
DirectPlay = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -233,5 +233,11 @@ namespace MediaBrowser.Model.Session
|
||||
/// </summary>
|
||||
/// <value>The now playing media version identifier.</value>
|
||||
public string MediaSourceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the play method.
|
||||
/// </summary>
|
||||
/// <value>The play method.</value>
|
||||
public PlayMethod? PlayMethod { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -121,10 +121,28 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
}
|
||||
|
||||
if (data.format != null)
|
||||
{
|
||||
audio.FormatName = data.format.format_name;
|
||||
|
||||
var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.');
|
||||
|
||||
audio.Container = extension;
|
||||
|
||||
if (!string.IsNullOrEmpty(data.format.size))
|
||||
{
|
||||
audio.Size = long.Parse(data.format.size , _usCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
audio.Size = null;
|
||||
}
|
||||
|
||||
if (data.format.tags != null)
|
||||
{
|
||||
FetchDataFromTags(audio, data.format.tags);
|
||||
}
|
||||
}
|
||||
|
||||
return _itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
@ -159,6 +160,29 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
|
||||
}
|
||||
|
||||
video.FormatName = (data.format.format_name ?? string.Empty)
|
||||
.Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (video.VideoType == VideoType.VideoFile)
|
||||
{
|
||||
var extension = (Path.GetExtension(video.Path) ?? string.Empty).TrimStart('.');
|
||||
|
||||
video.Container = extension;
|
||||
}
|
||||
else
|
||||
{
|
||||
video.Container = null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(data.format.size))
|
||||
{
|
||||
video.Size = long.Parse(data.format.size, _usCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
video.Size = null;
|
||||
}
|
||||
}
|
||||
|
||||
var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams;
|
||||
|
@ -1234,9 +1234,14 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||
Path = GetMappedPath(i),
|
||||
RunTimeTicks = i.RunTimeTicks,
|
||||
Video3DFormat = i.Video3DFormat,
|
||||
VideoType = i.VideoType
|
||||
VideoType = i.VideoType,
|
||||
Container = i.Container,
|
||||
Size = i.Size,
|
||||
Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList()
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(info.Container))
|
||||
{
|
||||
if (i.VideoType == VideoType.VideoFile || i.VideoType == VideoType.Iso)
|
||||
{
|
||||
var locationType = i.LocationType;
|
||||
@ -1245,6 +1250,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||
info.Container = Path.GetExtension(i.Path).TrimStart('.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var bitrate = info.MediaStreams.Where(m => m.Type == MediaStreamType.Audio || m.Type == MediaStreamType.Video).Select(m => m.BitRate ?? 0).Sum();
|
||||
|
||||
@ -1265,14 +1271,20 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||
MediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList(),
|
||||
Name = i.Name,
|
||||
Path = GetMappedPath(i),
|
||||
RunTimeTicks = i.RunTimeTicks
|
||||
RunTimeTicks = i.RunTimeTicks,
|
||||
Container = i.Container,
|
||||
Size = i.Size,
|
||||
Formats = (i.FormatName ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList()
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(info.Container))
|
||||
{
|
||||
var locationType = i.LocationType;
|
||||
if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)
|
||||
{
|
||||
info.Container = Path.GetExtension(i.Path).TrimStart('.');
|
||||
}
|
||||
}
|
||||
|
||||
var bitrate = info.MediaStreams.Where(m => m.Type == MediaStreamType.Audio).Select(m => m.BitRate ?? 0).Sum();
|
||||
|
||||
|
@ -158,15 +158,6 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
|
||||
continue;
|
||||
}
|
||||
|
||||
if (video.VideoType == VideoType.BluRay)
|
||||
{
|
||||
// Can only extract reliably on single file blurays
|
||||
if (video.PlayableStreamFileNames == null || video.PlayableStreamFileNames.Count != 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Add some time for the first chapter to make sure we don't end up with a black image
|
||||
var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks);
|
||||
|
||||
|
@ -294,6 +294,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||
session.PlayState.VolumeLevel = info.VolumeLevel;
|
||||
session.PlayState.AudioStreamIndex = info.AudioStreamIndex;
|
||||
session.PlayState.SubtitleStreamIndex = info.SubtitleStreamIndex;
|
||||
session.PlayState.PlayMethod = info.PlayMethod;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1253,8 +1254,8 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||
}
|
||||
|
||||
var backropItem = item.HasImage(ImageType.Backdrop) ? item : null;
|
||||
|
||||
var thumbItem = item.HasImage(ImageType.Thumb) ? item : null;
|
||||
var logoItem = item.HasImage(ImageType.Logo) ? item : null;
|
||||
|
||||
if (thumbItem == null)
|
||||
{
|
||||
@ -1292,6 +1293,11 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||
thumbItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Thumb));
|
||||
}
|
||||
|
||||
if (logoItem == null)
|
||||
{
|
||||
logoItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Logo));
|
||||
}
|
||||
|
||||
if (thumbItem != null)
|
||||
{
|
||||
info.ThumbImageTag = GetImageCacheTag(thumbItem, ImageType.Thumb);
|
||||
@ -1304,6 +1310,12 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||
info.BackdropItemId = GetDtoId(backropItem);
|
||||
}
|
||||
|
||||
if (logoItem != null)
|
||||
{
|
||||
info.LogoImageTag = GetImageCacheTag(logoItem, ImageType.Logo);
|
||||
info.LogoItemId = GetDtoId(logoItem);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
@ -506,7 +506,7 @@ namespace MediaBrowser.ServerApplication
|
||||
var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger);
|
||||
RegisterSingleInstance<IAppThemeManager>(appThemeManager);
|
||||
|
||||
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("DLNA"), JsonSerializer, UserManager, LibraryManager);
|
||||
var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("DLNA"), JsonSerializer, UserManager, LibraryManager, DtoService, ImageProcessor, UserDataManager);
|
||||
RegisterSingleInstance<IDlnaManager>(dlnaManager);
|
||||
|
||||
var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor);
|
||||
|
Loading…
Reference in New Issue
Block a user