aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2014-04-18 01:03:01 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2014-04-18 01:03:01 -0400
commit7f320ce0638cd3c26b45bb75184d526f03b18651 (patch)
tree7af43a7b7006f10ae78e933c79df6b66978c49c3
parent818d7990915ef8970a95a226aa4be1851ba3b9e6 (diff)
add basic dlna server browsing
-rw-r--r--MediaBrowser.Api/Dlna/DlnaServerService.cs15
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs50
-rw-r--r--MediaBrowser.Controller/Dlna/ControlRequest.cs4
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs4
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs4
-rw-r--r--MediaBrowser.Dlna/DlnaManager.cs18
-rw-r--r--MediaBrowser.Dlna/MediaBrowser.Dlna.csproj1
-rw-r--r--MediaBrowser.Dlna/PlayTo/DidlBuilder.cs1
-rw-r--r--MediaBrowser.Dlna/PlayTo/DlnaController.cs104
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToManager.cs8
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs18
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlaylistItem.cs35
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs439
-rw-r--r--MediaBrowser.Dlna/PlayTo/StreamHelper.cs69
-rw-r--r--MediaBrowser.Dlna/Server/ControlHandler.cs736
-rw-r--r--MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs2
-rw-r--r--MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs12
-rw-r--r--MediaBrowser.Dlna/Server/SsdpHandler.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs2
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj9
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj9
-rw-r--r--MediaBrowser.Model/Dlna/DlnaMaps.cs62
-rw-r--r--MediaBrowser.Model/Dlna/MediaFormatProfile.cs122
-rw-r--r--MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs342
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs8
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs4
-rw-r--r--MediaBrowser.Model/Dto/MediaVersionInfo.cs9
-rw-r--r--MediaBrowser.Model/Entities/BaseItemInfo.cs12
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj3
-rw-r--r--MediaBrowser.Model/Session/PlaybackReports.cs13
-rw-r--r--MediaBrowser.Model/Session/SessionInfoDto.cs6
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs22
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs24
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs30
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs9
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs14
-rw-r--r--MediaBrowser.ServerApplication/ApplicationHost.cs2
38 files changed, 1367 insertions, 859 deletions
diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs
index 097552ab4..0dd7ee7d1 100644
--- a/MediaBrowser.Api/Dlna/DlnaServerService.cs
+++ b/MediaBrowser.Api/Dlna/DlnaServerService.cs
@@ -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
});
}
}
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 15cac7e3b..04deea3cf 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -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=";
-
- if (state.RunTimeTicks.HasValue)
- {
- // Time-based seeking currently only possible when transcoding
- orgOp += isStaticallyStreamed ? "0" : "1";
+ var orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetOrgOpValue(state.RunTimeTicks.HasValue, isStaticallyStreamed, state.TranscodeSeekInfo);
- // Byte-based seeking only possible when not transcoding
- orgOp += isStaticallyStreamed || state.TranscodeSeekInfo == TranscodeSeekInfo.Bytes ? "1" : "0";
-
- if (!isStaticallyStreamed)
- {
- AddTimeSeekResponseHeaders(state, responseHeaders);
- }
- }
- else
+ if (state.RunTimeTicks.HasValue && !isStaticallyStreamed)
{
- // No seeking is available if we don't know the content runtime
- orgOp += "00";
+ AddTimeSeekResponseHeaders(state, responseHeaders);
}
// 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);
diff --git a/MediaBrowser.Controller/Dlna/ControlRequest.cs b/MediaBrowser.Controller/Dlna/ControlRequest.cs
index 74e68b7d0..a2b9f7a92 100644
--- a/MediaBrowser.Controller/Dlna/ControlRequest.cs
+++ b/MediaBrowser.Controller/Dlna/ControlRequest.cs
@@ -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>();
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 0ca1ffde0..ef12d8816 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -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>();
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 1a02eb056..3ec9e7650 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -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>();
diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs
index 83e3df798..9993b0e21 100644
--- a/MediaBrowser.Dlna/DlnaManager.cs
+++ b/MediaBrowser.Dlna/DlnaManager.cs
@@ -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);
}
diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
index bf38f19dc..016cac4d6 100644
--- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
+++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
@@ -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" />
diff --git a/MediaBrowser.Dlna/PlayTo/DidlBuilder.cs b/MediaBrowser.Dlna/PlayTo/DidlBuilder.cs
index 853064c60..0304008fc 100644
--- a/MediaBrowser.Dlna/PlayTo/DidlBuilder.cs
+++ b/MediaBrowser.Dlna/PlayTo/DidlBuilder.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)
{
diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs
index 75667d54e..183a20447 100644
--- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs
+++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs
@@ -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.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress);
- playlistItem.Didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, streams, profile.EnableAlbumArtInDidl);
+ 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 orgOp = !streamInfo.IsDirectStream ? ";DLNA.ORG_OP=00" : ";DLNA.ORG_OP=01";
- var orgCi = item.Transcode ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
+ 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
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
index 5cc3f13e5..655a11693 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
@@ -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);
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs
index 91b03bc23..c0af1295f 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs
@@ -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)
diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs
index be6e9a0c9..95167cfe9 100644
--- a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs
@@ -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; }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs
index 34394fa32..29d44bca0 100644
--- a/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs
@@ -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)
+ public PlaylistItem Create(Photo item, 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)))
+ StreamInfo = new StreamInfo
{
- playlistItem.Transcode = false;
- playlistItem.Container = Path.GetExtension(item.Path);
-
- return playlistItem;
+ ItemId = item.Id.ToString("N"),
+ MediaType = DlnaProfileType.Photo,
}
- }
-
- 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
- {
- ItemId = item.Id.ToString("N"),
- 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;
- }
}
}
diff --git a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs b/MediaBrowser.Dlna/PlayTo/StreamHelper.cs
deleted file mode 100644
index b65e94fd1..000000000
--- a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs
+++ /dev/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()));
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Dlna/Server/ControlHandler.cs b/MediaBrowser.Dlna/Server/ControlHandler.cs
index d05062f38..049eaff65 100644
--- a/MediaBrowser.Dlna/Server/ControlHandler.cs
+++ b/MediaBrowser.Dlna/Server/ControlHandler.cs
@@ -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"))
{
@@ -245,40 +276,29 @@ namespace MediaBrowser.Dlna.Server
}
else
{
- foreach (var i in children.OfType<Folder>())
+ if (start > 0)
{
- if (start > 0)
- {
- start--;
- continue;
- }
-
- var childCount = i.GetChildren(user, true).Count();
-
- Browse_AddFolder(result, i, childCount);
-
- if (++provided == requested)
- {
- break;
- }
+ children = children.Skip(start).ToList();
+ }
+ if (requested > 0)
+ {
+ children = children.Take(requested).ToList();
}
- if (provided != requested)
+ provided = children.Count;
+
+ foreach (var i in children)
{
- foreach (var i in children.Where(i => !i.IsFolder))
+ if (i.IsFolder)
{
- if (start > 0)
- {
- start--;
- continue;
- }
+ var f = (Folder)i;
+ var childCount = GetChildrenSorted(f, user).Count();
+ Browse_AddFolder(result, f, childCount);
+ }
+ else
+ {
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,217 @@ 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 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
- // ));
+ var audio = item as Audio;
+ if (audio != null)
+ {
+ AddAudioResource(element, audio);
+ }
- 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 (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";
}
@@ -397,7 +603,14 @@ namespace MediaBrowser.Dlna.Server
}
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
- objectClass.InnerText = "object.item.videoItem.movie";
+ if (!_profile.RequiresPlainVideoItems && item is Movie)
+ {
+ objectClass.InnerText = "object.item.videoItem.movie";
+ }
+ else
+ {
+ objectClass.InnerText = "object.item.videoItem";
+ }
}
else
{
@@ -407,152 +620,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);
+ }
}
- private void AddGeneralProperties(BaseItem item, XmlElement element)
+ /// <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)
{
- //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);
- //}
+ 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))
{
- var e = element.OwnerDocument.CreateElement("dc", "description", NS_DC);
- e.InnerText = item.Overview;
- element.AppendChild(e);
- }
-
- //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 (props.TryGetValue("Director", out prop))
- //{
- // var e = item.OwnerDocument.CreateElement("upnp", "director", NS_UPNP);
- // e.InnerText = prop;
- // item.AppendChild(e);
- //}
+ 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)
+ {
+ AddCommonFields(item, element);
+
+ var audio = item as Audio;
+
+ if (audio != null)
+ {
+ if (audio.Artists.Count > 0)
+ {
+ AddValue(element, "upnp", "artist", audio.Artists[0], NS_UPNP);
+ }
+
+ if (!string.IsNullOrEmpty(audio.Album))
+ {
+ AddValue(element, "upnp", "album", audio.Album, NS_UPNP);
+ }
+
+ 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 res = result.CreateElement(string.Empty, "res", NS_DIDL);
- // res.InnerText = curl;
-
- // 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 imageInfo = GetImageInfo(item);
+
+ if (imageInfo == null)
+ {
+ 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);
}
}
}
diff --git a/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs b/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
index ee7fae6f9..33286a474 100644
--- a/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
+++ b/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
@@ -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;
diff --git a/MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs b/MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs
index 702aa6f99..050d89f49 100644
--- a/MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs
+++ b/MediaBrowser.Dlna/Server/DlnaServerEntryPoint.cs
@@ -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)
diff --git a/MediaBrowser.Dlna/Server/SsdpHandler.cs b/MediaBrowser.Dlna/Server/SsdpHandler.cs
index c908eb4a8..0c3e6c735 100644
--- a/MediaBrowser.Dlna/Server/SsdpHandler.cs
+++ b/MediaBrowser.Dlna/Server/SsdpHandler.cs
@@ -44,7 +44,7 @@ namespace MediaBrowser.Dlna.Server
Start();
}
- private IEnumerable<UpnpDevice> Devices
+ public IEnumerable<UpnpDevice> Devices
{
get
{
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
index cdd9df49b..a7fde61f8 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
@@ -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:
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 361e09b14..389046c9e 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -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);
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index 99928bcba..2e27015ee 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -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>
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index 3738decff..1a38398da 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -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>
diff --git a/MediaBrowser.Model/Dlna/DlnaMaps.cs b/MediaBrowser.Model/Dlna/DlnaMaps.cs
new file mode 100644
index 000000000..eb0c33315
--- /dev/null
+++ b/MediaBrowser.Model/Dlna/DlnaMaps.cs
@@ -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)
+ }
+}
diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfile.cs b/MediaBrowser.Model/Dlna/MediaFormatProfile.cs
new file mode 100644
index 000000000..e686fe932
--- /dev/null
+++ b/MediaBrowser.Model/Dlna/MediaFormatProfile.cs
@@ -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
+ }
+}
diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
new file mode 100644
index 000000000..76480930f
--- /dev/null
+++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
@@ -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;
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 3960b49ae..d975b1c4b 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -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;
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 209f33930..6ba7fe399 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -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);
diff --git a/MediaBrowser.Model/Dto/MediaVersionInfo.cs b/MediaBrowser.Model/Dto/MediaVersionInfo.cs
index e174c5d55..cfef83d2c 100644
--- a/MediaBrowser.Model/Dto/MediaVersionInfo.cs
+++ b/MediaBrowser.Model/Dto/MediaVersionInfo.cs
@@ -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>();
+ }
}
}
diff --git a/MediaBrowser.Model/Entities/BaseItemInfo.cs b/MediaBrowser.Model/Entities/BaseItemInfo.cs
index 5554e40d5..a280b1f71 100644
--- a/MediaBrowser.Model/Entities/BaseItemInfo.cs
+++ b/MediaBrowser.Model/Entities/BaseItemInfo.cs
@@ -52,6 +52,18 @@ namespace MediaBrowser.Model.Entities
/// </summary>
/// <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.
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index ec78a2b1b..5e373441f 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -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" />
diff --git a/MediaBrowser.Model/Session/PlaybackReports.cs b/MediaBrowser.Model/Session/PlaybackReports.cs
index 24594fcb1..80524c06e 100644
--- a/MediaBrowser.Model/Session/PlaybackReports.cs
+++ b/MediaBrowser.Model/Session/PlaybackReports.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>
diff --git a/MediaBrowser.Model/Session/SessionInfoDto.cs b/MediaBrowser.Model/Session/SessionInfoDto.cs
index b9dcf996e..686af4849 100644
--- a/MediaBrowser.Model/Session/SessionInfoDto.cs
+++ b/MediaBrowser.Model/Session/SessionInfoDto.cs
@@ -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; }
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
index 75a9d9c36..2725d4e78 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
@@ -121,9 +121,27 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
- if (data.format.tags != null)
+ if (data.format != null)
{
- FetchDataFromTags(audio, data.format.tags);
+ 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);
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index cb326c5ad..5e48f24b3 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -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;
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index b625c2d51..166d26b3c 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -1234,15 +1234,21 @@ 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 (i.VideoType == VideoType.VideoFile || i.VideoType == VideoType.Iso)
+ if (string.IsNullOrEmpty(info.Container))
{
- var locationType = i.LocationType;
- if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)
+ if (i.VideoType == VideoType.VideoFile || i.VideoType == VideoType.Iso)
{
- info.Container = Path.GetExtension(i.Path).TrimStart('.');
+ var locationType = i.LocationType;
+ if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)
+ {
+ info.Container = Path.GetExtension(i.Path).TrimStart('.');
+ }
}
}
@@ -1265,13 +1271,19 @@ 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()
};
- var locationType = i.LocationType;
- if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)
+ if (string.IsNullOrEmpty(info.Container))
{
- info.Container = Path.GetExtension(i.Path).TrimStart('.');
+ 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();
diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
index 7237ffee5..470e58b66 100644
--- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
+++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
@@ -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);
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index 2fc63039d..a05af246c 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
@@ -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;
}
diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs
index 39c32a856..27e73450c 100644
--- a/MediaBrowser.ServerApplication/ApplicationHost.cs
+++ b/MediaBrowser.ServerApplication/ApplicationHost.cs
@@ -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);