Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser
This commit is contained in:
commit
13196544c2
89
MediaBrowser.Api/Dlna/DlnaServerService.cs
Normal file
89
MediaBrowser.Api/Dlna/DlnaServerService.cs
Normal file
|
@ -0,0 +1,89 @@
|
|||
using MediaBrowser.Controller.Dlna;
|
||||
using ServiceStack;
|
||||
using ServiceStack.Web;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Api.Dlna
|
||||
{
|
||||
[Route("/Dlna/{UuId}/description.xml", "GET", Summary = "Gets dlna server info")]
|
||||
[Route("/Dlna/{UuId}/description", "GET", Summary = "Gets dlna server info")]
|
||||
public class GetDescriptionXml
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UuId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/{UuId}/contentdirectory.xml", "GET", Summary = "Gets dlna content directory xml")]
|
||||
[Route("/Dlna/{UuId}/contentdirectory", "GET", Summary = "Gets the content directory xml")]
|
||||
public class GetContentDirectory
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UuId { get; set; }
|
||||
}
|
||||
|
||||
[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; }
|
||||
}
|
||||
|
||||
public class DlnaServerService : BaseApiService
|
||||
{
|
||||
private readonly IDlnaManager _dlnaManager;
|
||||
|
||||
public DlnaServerService(IDlnaManager dlnaManager)
|
||||
{
|
||||
_dlnaManager = dlnaManager;
|
||||
}
|
||||
|
||||
public object Get(GetDescriptionXml request)
|
||||
{
|
||||
var xml = _dlnaManager.GetServerDescriptionXml(GetRequestHeaders(), request.UuId);
|
||||
|
||||
return ResultFactory.GetResult(xml, "text/xml");
|
||||
}
|
||||
|
||||
public object Get(GetContentDirectory request)
|
||||
{
|
||||
var xml = _dlnaManager.GetContentDirectoryXml(GetRequestHeaders());
|
||||
|
||||
return ResultFactory.GetResult(xml, "text/xml");
|
||||
}
|
||||
|
||||
public object Post(ProcessControlRequest request)
|
||||
{
|
||||
var response = PostAsync(request).Result;
|
||||
|
||||
return ResultFactory.GetResult(response.Xml, "text/xml");
|
||||
}
|
||||
|
||||
private async Task<ControlResponse> PostAsync(ProcessControlRequest request)
|
||||
{
|
||||
using (var reader = new StreamReader(request.RequestStream))
|
||||
{
|
||||
return _dlnaManager.ProcessControlRequest(new ControlRequest
|
||||
{
|
||||
Headers = GetRequestHeaders(),
|
||||
InputXml = await reader.ReadToEndAsync().ConfigureAwait(false)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private IDictionary<string, string> GetRequestHeaders()
|
||||
{
|
||||
var headers = new Dictionary<string, string>();
|
||||
|
||||
foreach (var key in Request.Headers.AllKeys)
|
||||
{
|
||||
headers[key] = Request.Headers[key];
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ using ServiceStack;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MediaBrowser.Api
|
||||
namespace MediaBrowser.Api.Dlna
|
||||
{
|
||||
[Route("/Dlna/ProfileInfos", "GET", Summary = "Gets a list of profiles")]
|
||||
public class GetProfileInfos : IReturn<List<DeviceProfileInfo>>
|
|
@ -36,7 +36,7 @@ namespace MediaBrowser.Api.Library
|
|||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Videos/{Id}/Subtitle/{Index}", "GET")]
|
||||
[Route("/Videos/{Id}/Subtitles/{Index}", "GET")]
|
||||
[Api(Description = "Gets an external subtitle file")]
|
||||
public class GetSubtitle
|
||||
{
|
||||
|
|
|
@ -66,7 +66,8 @@
|
|||
<Link>Properties\SharedVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="ChannelService.cs" />
|
||||
<Compile Include="DlnaService.cs" />
|
||||
<Compile Include="Dlna\DlnaServerService.cs" />
|
||||
<Compile Include="Dlna\DlnaService.cs" />
|
||||
<Compile Include="Movies\CollectionService.cs" />
|
||||
<Compile Include="Music\AlbumsService.cs" />
|
||||
<Compile Include="AppThemeService.cs" />
|
||||
|
|
|
@ -185,9 +185,9 @@ namespace MediaBrowser.Api.Playback
|
|||
{
|
||||
var args = string.Empty;
|
||||
|
||||
if (state.IsRemote || !state.HasMediaStreams)
|
||||
if (!state.HasMediaStreams)
|
||||
{
|
||||
return string.Empty;
|
||||
return state.IsInputVideo ? "-sn" : string.Empty;
|
||||
}
|
||||
|
||||
if (state.VideoStream != null)
|
||||
|
@ -1341,6 +1341,12 @@ namespace MediaBrowser.Api.Playback
|
|||
RequestedUrl = url
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.AudioCodec))
|
||||
{
|
||||
state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault();
|
||||
}
|
||||
|
||||
var item = string.IsNullOrEmpty(request.MediaSourceId) ?
|
||||
DtoService.GetItemByDtoId(request.Id) :
|
||||
DtoService.GetItemByDtoId(request.MediaSourceId);
|
||||
|
@ -1487,33 +1493,27 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
if (videoRequest != null)
|
||||
{
|
||||
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream, state.VideoType))
|
||||
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
|
||||
{
|
||||
videoRequest.VideoCodec = "copy";
|
||||
}
|
||||
|
||||
//if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream))
|
||||
//{
|
||||
// request.AudioCodec = "copy";
|
||||
//}
|
||||
if (state.AudioStream != null && CanStreamCopyAudio(request, state.AudioStream, state.SupportedAudioCodecs))
|
||||
{
|
||||
request.AudioCodec = "copy";
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream, VideoType videoType)
|
||||
private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
|
||||
{
|
||||
if (videoStream.IsInterlaced)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not going to attempt this with folder rips
|
||||
if (videoType != VideoType.VideoFile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Source and target codecs must match
|
||||
if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -1584,13 +1584,13 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
return SupportsAutomaticVideoStreamCopy;
|
||||
return request.EnableAutoStreamCopy;
|
||||
}
|
||||
|
||||
private bool CanStreamCopyAudio(StreamRequest request, MediaStream audioStream)
|
||||
private bool CanStreamCopyAudio(StreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs)
|
||||
{
|
||||
// Source and target codecs must match
|
||||
if (string.IsNullOrEmpty(request.AudioCodec) || !string.Equals(request.AudioCodec, audioStream.Codec, StringComparison.OrdinalIgnoreCase))
|
||||
if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -1623,15 +1623,7 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
return SupportsAutomaticVideoStreamCopy;
|
||||
}
|
||||
|
||||
protected virtual bool SupportsAutomaticVideoStreamCopy
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ApplyDeviceProfileSettings(StreamState state)
|
||||
|
|
|
@ -24,7 +24,8 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
/// </summary>
|
||||
public abstract class BaseHlsService : BaseStreamingService
|
||||
{
|
||||
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager)
|
||||
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager)
|
||||
: base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -77,6 +78,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
return ProcessRequestAsync(request).Result;
|
||||
}
|
||||
|
||||
private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1);
|
||||
/// <summary>
|
||||
/// Processes the request async.
|
||||
/// </summary>
|
||||
|
@ -103,31 +105,40 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
}
|
||||
|
||||
var playlist = GetOutputFilePath(state);
|
||||
var isPlaylistNewlyCreated = false;
|
||||
|
||||
// If the playlist doesn't already exist, startup ffmpeg
|
||||
if (!File.Exists(playlist))
|
||||
{
|
||||
isPlaylistNewlyCreated = true;
|
||||
|
||||
try
|
||||
{
|
||||
await StartFfMpeg(state, playlist).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
state.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (File.Exists(playlist))
|
||||
{
|
||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
|
||||
}
|
||||
|
||||
if (isPlaylistNewlyCreated)
|
||||
else
|
||||
{
|
||||
await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false);
|
||||
await FfmpegStartLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (File.Exists(playlist))
|
||||
{
|
||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the playlist doesn't already exist, startup ffmpeg
|
||||
try
|
||||
{
|
||||
await StartFfMpeg(state, playlist).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
state.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
FfmpegStartLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
int audioBitrate;
|
||||
|
@ -295,7 +306,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
// If performSubtitleConversions is true we're actually starting ffmpeg
|
||||
var startNumberParam = performSubtitleConversions ? GetStartNumber(state).ToString(UsCulture) : "0";
|
||||
|
||||
|
||||
var args = string.Format("{0} {1} -i {2}{3} -map_metadata -1 -threads {4} {5} {6} -sc_threshold 0 {7} -hls_time {8} -start_number {9} -hls_list_size {10} \"{11}\"",
|
||||
itsOffset,
|
||||
inputModifier,
|
||||
|
|
|
@ -98,14 +98,6 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
|
||||
}
|
||||
|
||||
protected override bool SupportsAutomaticVideoStreamCopy
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified request.
|
||||
/// </summary>
|
||||
|
|
|
@ -171,5 +171,13 @@ namespace MediaBrowser.Api.Playback
|
|||
return Width.HasValue || Height.HasValue;
|
||||
}
|
||||
}
|
||||
|
||||
[ApiMember(Name = "EnableAutoStreamCopy", Description = "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||
public bool EnableAutoStreamCopy { get; set; }
|
||||
|
||||
public VideoStreamRequest()
|
||||
{
|
||||
EnableAutoStreamCopy = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,10 +67,13 @@ namespace MediaBrowser.Api.Playback
|
|||
public string AudioSync = "1";
|
||||
public string VideoSync = "vfr";
|
||||
|
||||
public List<string> SupportedAudioCodecs { get; set; }
|
||||
|
||||
public StreamState(ILiveTvManager liveTvManager, ILogger logger)
|
||||
{
|
||||
_liveTvManager = liveTvManager;
|
||||
_logger = logger;
|
||||
SupportedAudioCodecs = new List<string>();
|
||||
}
|
||||
|
||||
public string InputAudioSync { get; set; }
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Session;
|
||||
using ServiceStack;
|
||||
|
@ -32,10 +31,10 @@ namespace MediaBrowser.Api
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class BrowseTo
|
||||
/// Class DisplayContent
|
||||
/// </summary>
|
||||
[Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")]
|
||||
public class BrowseTo : IReturnVoid
|
||||
public class DisplayContent : IReturnVoid
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the id.
|
||||
|
@ -218,6 +217,7 @@ namespace MediaBrowser.Api
|
|||
public Guid UserId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")]
|
||||
[Route("/Sessions/{Id}/Capabilities", "POST", Summary = "Updates capabilities for a device")]
|
||||
public class PostCapabilities : IReturnVoid
|
||||
{
|
||||
|
@ -226,7 +226,7 @@ namespace MediaBrowser.Api
|
|||
/// </summary>
|
||||
/// <value>The id.</value>
|
||||
[ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public Guid Id { get; set; }
|
||||
public string Id { get; set; }
|
||||
|
||||
[ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string PlayableMediaTypes { get; set; }
|
||||
|
@ -307,7 +307,7 @@ namespace MediaBrowser.Api
|
|||
/// Posts the specified request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
public void Post(BrowseTo request)
|
||||
public void Post(DisplayContent request)
|
||||
{
|
||||
var command = new BrowseRequest
|
||||
{
|
||||
|
@ -421,7 +421,11 @@ namespace MediaBrowser.Api
|
|||
|
||||
public void Post(PostCapabilities request)
|
||||
{
|
||||
_sessionManager.ReportCapabilities(request.Id, new SessionCapabilities
|
||||
if (string.IsNullOrWhiteSpace(request.Id))
|
||||
{
|
||||
request.Id = GetSession().Id.ToString("N");
|
||||
}
|
||||
_sessionManager.ReportCapabilities(new Guid(request.Id), new SessionCapabilities
|
||||
{
|
||||
PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
|
||||
|
||||
|
|
|
@ -15,6 +15,44 @@ namespace MediaBrowser.Common.Implementations.Networking
|
|||
/// </summary>
|
||||
/// <returns>IPAddress.</returns>
|
||||
public IEnumerable<string> GetLocalIpAddresses()
|
||||
{
|
||||
var list = GetIPsDefault().Where(i => !IPAddress.IsLoopback(i)).Select(i => i.ToString()).ToList();
|
||||
|
||||
if (list.Count > 0)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
return GetLocalIpAddressesFallback();
|
||||
}
|
||||
|
||||
private IEnumerable<IPAddress> GetIPsDefault()
|
||||
{
|
||||
foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces())
|
||||
{
|
||||
var props = adapter.GetIPProperties();
|
||||
var gateways = from ga in props.GatewayAddresses
|
||||
where !ga.Address.Equals(IPAddress.Any)
|
||||
select true;
|
||||
|
||||
if (!gateways.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var uni in props.UnicastAddresses)
|
||||
{
|
||||
var address = uni.Address;
|
||||
if (address.AddressFamily != AddressFamily.InterNetwork)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
yield return address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetLocalIpAddressesFallback()
|
||||
{
|
||||
var host = Dns.GetHostEntry(Dns.GetHostName());
|
||||
|
||||
|
@ -25,7 +63,7 @@ namespace MediaBrowser.Common.Implementations.Networking
|
|||
.Select(i => i.ToString())
|
||||
.Reverse();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a random port number that is currently available
|
||||
/// </summary>
|
||||
|
@ -50,6 +88,7 @@ namespace MediaBrowser.Common.Implementations.Networking
|
|||
.Select(i => BitConverter.ToString(i.GetPhysicalAddress().GetAddressBytes()))
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified endpointstring.
|
||||
/// </summary>
|
||||
|
|
28
MediaBrowser.Controller/Dlna/ControlRequest.cs
Normal file
28
MediaBrowser.Controller/Dlna/ControlRequest.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Controller.Dlna
|
||||
{
|
||||
public class ControlRequest
|
||||
{
|
||||
public IDictionary<string, string> Headers { get; set; }
|
||||
|
||||
public string InputXml { get; set; }
|
||||
|
||||
public ControlRequest()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
|
||||
public class ControlResponse
|
||||
{
|
||||
public IDictionary<string, string> Headers { get; set; }
|
||||
|
||||
public string Xml { get; set; }
|
||||
|
||||
public ControlResponse()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,5 +55,27 @@ namespace MediaBrowser.Controller.Dlna
|
|||
/// <param name="deviceInfo">The device information.</param>
|
||||
/// <returns>DeviceProfile.</returns>
|
||||
DeviceProfile GetProfile(DeviceIdentification deviceInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server description XML.
|
||||
/// </summary>
|
||||
/// <param name="headers">The headers.</param>
|
||||
/// <param name="serverUuId">The server uu identifier.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content directory XML.
|
||||
/// </summary>
|
||||
/// <param name="headers">The headers.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
string GetContentDirectoryXml(IDictionary<string, string> headers);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the control request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <returns>ControlResponse.</returns>
|
||||
ControlResponse ProcessControlRequest(ControlRequest request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
/// <summary>
|
||||
/// Class Audio
|
||||
/// </summary>
|
||||
public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<SongInfo>, IHasSeries
|
||||
public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<SongInfo>
|
||||
{
|
||||
public Audio()
|
||||
{
|
||||
|
@ -51,15 +51,6 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string SeriesName
|
||||
{
|
||||
get
|
||||
{
|
||||
return Album;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the artist.
|
||||
/// </summary>
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
/// <summary>
|
||||
/// Class MusicAlbum
|
||||
/// </summary>
|
||||
public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasTags, IHasLookupInfo<AlbumInfo>, IHasSeries
|
||||
public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasTags, IHasLookupInfo<AlbumInfo>
|
||||
{
|
||||
public List<Guid> SoundtrackIds { get; set; }
|
||||
|
||||
|
@ -67,15 +67,6 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public string SeriesName
|
||||
{
|
||||
get
|
||||
{
|
||||
return AlbumArtist;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this to true if class should be grouped under a container in indicies
|
||||
/// The container class should be defined via IndexContainer
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public class LiveStreamInfo
|
||||
|
@ -20,5 +22,22 @@ namespace MediaBrowser.Controller.LiveTv
|
|||
/// </summary>
|
||||
/// <value>The identifier.</value>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the media container.
|
||||
/// </summary>
|
||||
/// <value>The media container.</value>
|
||||
public string MediaContainer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the media streams.
|
||||
/// </summary>
|
||||
/// <value>The media streams.</value>
|
||||
public List<MediaStream> MediaStreams { get; set; }
|
||||
|
||||
public LiveStreamInfo()
|
||||
{
|
||||
MediaStreams = new List<MediaStream>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@
|
|||
<Compile Include="Channels\Channel.cs" />
|
||||
<Compile Include="Collections\CollectionCreationOptions.cs" />
|
||||
<Compile Include="Collections\ICollectionManager.cs" />
|
||||
<Compile Include="Dlna\ControlRequest.cs" />
|
||||
<Compile Include="Dlna\IDlnaManager.cs" />
|
||||
<Compile Include="Drawing\IImageProcessor.cs" />
|
||||
<Compile Include="Drawing\ImageFormat.cs" />
|
||||
|
|
|
@ -103,8 +103,9 @@ namespace MediaBrowser.Controller.Resolvers
|
|||
".wav",
|
||||
".ape",
|
||||
".ogg",
|
||||
".oga"
|
||||
|
||||
".oga",
|
||||
".asf",
|
||||
".mp4"
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, string> AudioFileExtensionsDictionary = AudioFileExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
|
@ -35,14 +35,6 @@ namespace MediaBrowser.Controller.Session
|
|||
/// <returns>Task.</returns>
|
||||
Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the browse command.
|
||||
/// </summary>
|
||||
/// <param name="command">The command.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task SendBrowseCommand(BrowseRequest command, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the playstate command.
|
||||
/// </summary>
|
||||
|
@ -96,6 +88,22 @@ namespace MediaBrowser.Controller.Session
|
|||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the playback start notification.
|
||||
/// </summary>
|
||||
/// <param name="sessionInfo">The session information.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the playback start notification.
|
||||
/// </summary>
|
||||
/// <param name="sessionInfo">The session information.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the server restart notification.
|
||||
|
|
12
MediaBrowser.Dlna/Common/Argument.cs
Normal file
12
MediaBrowser.Dlna/Common/Argument.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
|
||||
namespace MediaBrowser.Dlna.Common
|
||||
{
|
||||
public class Argument
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Direction { get; set; }
|
||||
|
||||
public string RelatedStateVariable { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
namespace MediaBrowser.Dlna.Common
|
||||
{
|
||||
public class DeviceIcon
|
||||
{
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
namespace MediaBrowser.Dlna.Common
|
||||
{
|
||||
public class DeviceService
|
||||
{
|
||||
|
@ -13,15 +13,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
|
||||
public string EventSubUrl { get; set; }
|
||||
|
||||
public DeviceService(string serviceType, string serviceId, string scpdUrl, string controlUrl, string eventSubUrl)
|
||||
{
|
||||
ServiceType = serviceType;
|
||||
ServiceId = serviceId;
|
||||
ScpdUrl = scpdUrl;
|
||||
ControlUrl = controlUrl;
|
||||
EventSubUrl = eventSubUrl;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}", ServiceId);
|
21
MediaBrowser.Dlna/Common/ServiceAction.cs
Normal file
21
MediaBrowser.Dlna/Common/ServiceAction.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Dlna.Common
|
||||
{
|
||||
public class ServiceAction
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<Argument> ArgumentList { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public ServiceAction()
|
||||
{
|
||||
ArgumentList = new List<Argument>();
|
||||
}
|
||||
}
|
||||
}
|
25
MediaBrowser.Dlna/Common/StateVariable.cs
Normal file
25
MediaBrowser.Dlna/Common/StateVariable.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Dlna.Common
|
||||
{
|
||||
public class StateVariable
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string DataType { get; set; }
|
||||
|
||||
public bool SendsEvents { get; set; }
|
||||
|
||||
public List<string> AllowedValues { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public StateVariable()
|
||||
{
|
||||
AllowedValues = new List<string>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
using System.Text;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Dlna.Profiles;
|
||||
using MediaBrowser.Dlna.Server;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
@ -11,6 +11,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MediaBrowser.Dlna
|
||||
|
@ -476,5 +477,26 @@ namespace MediaBrowser.Dlna
|
|||
internal DeviceProfileInfo Info { get; set; }
|
||||
internal string Path { get; set; }
|
||||
}
|
||||
|
||||
public string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId)
|
||||
{
|
||||
var profile = GetProfile(headers) ??
|
||||
GetDefaultProfile();
|
||||
|
||||
return new DescriptionXmlBuilder(profile, serverUuId).GetXml();
|
||||
}
|
||||
|
||||
public string GetContentDirectoryXml(IDictionary<string, string> headers)
|
||||
{
|
||||
var profile = GetProfile(headers) ??
|
||||
GetDefaultProfile();
|
||||
|
||||
return new ContentDirectoryXmlBuilder(profile).GetXml();
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
{
|
||||
return new ControlHandler(_logger).ProcessControlRequest(request);
|
||||
}
|
||||
}
|
||||
}
|
BIN
MediaBrowser.Dlna/Images/logo-120.jpg
Normal file
BIN
MediaBrowser.Dlna/Images/logo-120.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
BIN
MediaBrowser.Dlna/Images/logo-120.png
Normal file
BIN
MediaBrowser.Dlna/Images/logo-120.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
BIN
MediaBrowser.Dlna/Images/logo-48.jpg
Normal file
BIN
MediaBrowser.Dlna/Images/logo-48.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
MediaBrowser.Dlna/Images/logo-48.png
Normal file
BIN
MediaBrowser.Dlna/Images/logo-48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
|
@ -52,13 +52,13 @@
|
|||
<Link>Properties\SharedVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="DlnaManager.cs" />
|
||||
<Compile Include="PlayTo\Argument.cs" />
|
||||
<Compile Include="Common\Argument.cs" />
|
||||
<Compile Include="PlayTo\CurrentIdEventArgs.cs" />
|
||||
<Compile Include="PlayTo\Device.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="PlayTo\DeviceInfo.cs" />
|
||||
<Compile Include="PlayTo\DeviceService.cs" />
|
||||
<Compile Include="Common\DeviceService.cs" />
|
||||
<Compile Include="PlayTo\DidlBuilder.cs" />
|
||||
<Compile Include="PlayTo\DlnaController.cs" />
|
||||
<Compile Include="PlayTo\Extensions.cs" />
|
||||
|
@ -68,20 +68,25 @@
|
|||
<Compile Include="PlayTo\PlaylistItemFactory.cs" />
|
||||
<Compile Include="PlayTo\PlayToManager.cs" />
|
||||
<Compile Include="PlayTo\PlayToServerEntryPoint.cs" />
|
||||
<Compile Include="PlayTo\ServiceAction.cs" />
|
||||
<Compile Include="Common\ServiceAction.cs" />
|
||||
<Compile Include="Profiles\Foobar2000Profile.cs" />
|
||||
<Compile Include="Profiles\Windows81Profile.cs" />
|
||||
<Compile Include="Profiles\WindowsMediaCenterProfile.cs" />
|
||||
<Compile Include="Profiles\WindowsPhoneProfile.cs" />
|
||||
<Compile Include="Server\ControlHandler.cs" />
|
||||
<Compile Include="Server\ServiceActionListBuilder.cs" />
|
||||
<Compile Include="Server\ContentDirectoryXmlBuilder.cs" />
|
||||
<Compile Include="Server\Datagram.cs" />
|
||||
<Compile Include="Server\DescriptionXmlBuilder.cs" />
|
||||
<Compile Include="Ssdp\SsdpHelper.cs" />
|
||||
<Compile Include="PlayTo\SsdpHttpClient.cs" />
|
||||
<Compile Include="PlayTo\StateVariable.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" />
|
||||
<Compile Include="PlayTo\uContainer.cs" />
|
||||
<Compile Include="PlayTo\DeviceIcon.cs" />
|
||||
<Compile Include="Common\DeviceIcon.cs" />
|
||||
<Compile Include="PlayTo\uParser.cs" />
|
||||
<Compile Include="PlayTo\uPnpNamespaces.cs" />
|
||||
<Compile Include="Profiles\DefaultProfile.cs" />
|
||||
|
@ -141,6 +146,12 @@
|
|||
<ItemGroup>
|
||||
<EmbeddedResource Include="Profiles\Xml\Default.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Images\logo-120.jpg" />
|
||||
<EmbeddedResource Include="Images\logo-120.png" />
|
||||
<EmbeddedResource Include="Images\logo-48.jpg" />
|
||||
<EmbeddedResource Include="Images\logo-48.png" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class Argument
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Direction { get; set; }
|
||||
|
||||
public string RelatedStateVariable { get; set; }
|
||||
|
||||
public static Argument FromXml(XElement container)
|
||||
{
|
||||
if (container == null)
|
||||
{
|
||||
throw new ArgumentNullException("container");
|
||||
}
|
||||
|
||||
return new Argument
|
||||
{
|
||||
Name = container.GetValue(uPnpNamespaces.svc + "name"),
|
||||
Direction = container.GetValue(uPnpNamespaces.svc + "direction"),
|
||||
RelatedStateVariable = container.GetValue(uPnpNamespaces.svc + "relatedStateVariable")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
using System.Globalization;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Dlna.Common;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -209,6 +210,9 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
return SetVolume(tmp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets volume on a scale of 0-100
|
||||
/// </summary>
|
||||
public async Task<bool> SetVolume(int value)
|
||||
{
|
||||
var command = RendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
|
||||
|
@ -337,7 +341,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
throw new InvalidOperationException("Unable to find service");
|
||||
}
|
||||
|
||||
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
|
||||
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, 1))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
_lapsCount = GetLapsCount();
|
||||
|
@ -352,7 +356,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
|
||||
var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
|
||||
|
||||
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
|
||||
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, 1))
|
||||
.ConfigureAwait(false);
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
return true;
|
||||
|
@ -366,7 +370,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
|
||||
var service = Properties.Services.FirstOrDefault(s => s.ServiceType == ServiceAvtransportType);
|
||||
|
||||
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1))
|
||||
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, AvCommands.BuildPost(command, service.ServiceType, 1))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
|
@ -575,7 +579,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
return false;
|
||||
}
|
||||
|
||||
var trackString = (string) track;
|
||||
var trackString = (string)track;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -583,7 +587,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
}
|
||||
|
||||
XElement uPnpResponse;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
uPnpResponse = XElement.Parse(trackString);
|
||||
|
@ -838,7 +842,14 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
var controlURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("controlURL"));
|
||||
var eventSubURL = element.GetDescendantValue(uPnpNamespaces.ud.GetName("eventSubURL"));
|
||||
|
||||
return new DeviceService(type, id, scpdUrl, controlURL, eventSubURL);
|
||||
return new DeviceService
|
||||
{
|
||||
ControlUrl = controlURL,
|
||||
EventSubUrl = eventSubURL,
|
||||
ScpdUrl = scpdUrl,
|
||||
ServiceId = id,
|
||||
ServiceType = type
|
||||
};
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using MediaBrowser.Controller.Dlna;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Dlna.Common;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using MediaBrowser.Common.Net;
|
||||
using System.Globalization;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
@ -206,7 +207,8 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
IsPaused = _device.IsPaused,
|
||||
MediaSourceId = playlistItem.MediaSourceId,
|
||||
AudioStreamIndex = playlistItem.AudioStreamIndex,
|
||||
SubtitleStreamIndex = playlistItem.SubtitleStreamIndex
|
||||
SubtitleStreamIndex = playlistItem.SubtitleStreamIndex,
|
||||
VolumeLevel = _device.Volume
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
@ -331,12 +333,17 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task SendServerShutdownNotification(CancellationToken cancellationToken)
|
||||
public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task SendBrowseCommand(BrowseRequest command, CancellationToken cancellationToken)
|
||||
public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task SendServerShutdownNotification(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
@ -609,11 +616,13 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
}
|
||||
}
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
GeneralCommandType commandType;
|
||||
|
||||
if (!Enum.TryParse(command.Name, true, out commandType))
|
||||
if (Enum.TryParse(command.Name, true, out commandType))
|
||||
{
|
||||
switch (commandType)
|
||||
{
|
||||
|
@ -627,6 +636,24 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
return _device.VolumeUp(true);
|
||||
case GeneralCommandType.ToggleMute:
|
||||
return _device.ToggleMute();
|
||||
case GeneralCommandType.SetVolume:
|
||||
{
|
||||
string volumeArg;
|
||||
|
||||
if (command.Arguments.TryGetValue("Volume", out volumeArg))
|
||||
{
|
||||
int volume;
|
||||
|
||||
if (int.TryParse(volumeArg, NumberStyles.Any, _usCulture, out volume))
|
||||
{
|
||||
return _device.SetVolume(volume);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Unsupported volume value supplied.");
|
||||
}
|
||||
|
||||
throw new ArgumentException("Volume argument cannot be null");
|
||||
}
|
||||
default:
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
|
|
@ -265,7 +265,8 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
GeneralCommandType.VolumeUp.ToString(),
|
||||
GeneralCommandType.Mute.ToString(),
|
||||
GeneralCommandType.Unmute.ToString(),
|
||||
GeneralCommandType.ToggleMute.ToString()
|
||||
GeneralCommandType.ToggleMute.ToString(),
|
||||
GeneralCommandType.SetVolume.ToString()
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class ServiceAction
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<Argument> ArgumentList { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public static ServiceAction FromXml(XElement container)
|
||||
{
|
||||
var argumentList = new List<Argument>();
|
||||
|
||||
foreach (var arg in container.Descendants(uPnpNamespaces.svc + "argument"))
|
||||
{
|
||||
argumentList.Add(Argument.FromXml(arg));
|
||||
}
|
||||
|
||||
return new ServiceAction
|
||||
{
|
||||
Name = container.GetValue(uPnpNamespaces.svc + "name"),
|
||||
|
||||
ArgumentList = argumentList
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Dlna.Common;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class StateVariable
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string DataType { get; set; }
|
||||
|
||||
private List<string> _allowedValues = new List<string>();
|
||||
public List<string> AllowedValues
|
||||
{
|
||||
get
|
||||
{
|
||||
return _allowedValues;
|
||||
}
|
||||
set
|
||||
{
|
||||
_allowedValues = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public static StateVariable FromXml(XElement container)
|
||||
{
|
||||
var allowedValues = new List<string>();
|
||||
var element = container.Descendants(uPnpNamespaces.svc + "allowedValueList")
|
||||
.FirstOrDefault();
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
var values = element.Descendants(uPnpNamespaces.svc + "allowedValue");
|
||||
|
||||
allowedValues.AddRange(values.Select(child => child.Value));
|
||||
}
|
||||
|
||||
return new StateVariable
|
||||
{
|
||||
Name = container.GetValue(uPnpNamespaces.svc + "name"),
|
||||
DataType = container.GetValue(uPnpNamespaces.svc + "dataType"),
|
||||
AllowedValues = allowedValues
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using MediaBrowser.Dlna.Common;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
|
@ -40,7 +42,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
|
||||
foreach (var container in actionList.Descendants(uPnpNamespaces.svc + "action"))
|
||||
{
|
||||
command.ServiceActions.Add(ServiceAction.FromXml(container));
|
||||
command.ServiceActions.Add(ServiceActionFromXml(container));
|
||||
}
|
||||
|
||||
var stateValues = document.Descendants(uPnpNamespaces.ServiceStateTable).FirstOrDefault();
|
||||
|
@ -49,13 +51,66 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
{
|
||||
foreach (var container in stateValues.Elements(uPnpNamespaces.svc + "stateVariable"))
|
||||
{
|
||||
command.StateVariables.Add(StateVariable.FromXml(container));
|
||||
command.StateVariables.Add(FromXml(container));
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private static ServiceAction ServiceActionFromXml(XElement container)
|
||||
{
|
||||
var argumentList = new List<Argument>();
|
||||
|
||||
foreach (var arg in container.Descendants(uPnpNamespaces.svc + "argument"))
|
||||
{
|
||||
argumentList.Add(ArgumentFromXml(arg));
|
||||
}
|
||||
|
||||
return new ServiceAction
|
||||
{
|
||||
Name = container.GetValue(uPnpNamespaces.svc + "name"),
|
||||
|
||||
ArgumentList = argumentList
|
||||
};
|
||||
}
|
||||
|
||||
private static Argument ArgumentFromXml(XElement container)
|
||||
{
|
||||
if (container == null)
|
||||
{
|
||||
throw new ArgumentNullException("container");
|
||||
}
|
||||
|
||||
return new Argument
|
||||
{
|
||||
Name = container.GetValue(uPnpNamespaces.svc + "name"),
|
||||
Direction = container.GetValue(uPnpNamespaces.svc + "direction"),
|
||||
RelatedStateVariable = container.GetValue(uPnpNamespaces.svc + "relatedStateVariable")
|
||||
};
|
||||
}
|
||||
|
||||
public static StateVariable FromXml(XElement container)
|
||||
{
|
||||
var allowedValues = new List<string>();
|
||||
var element = container.Descendants(uPnpNamespaces.svc + "allowedValueList")
|
||||
.FirstOrDefault();
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
var values = element.Descendants(uPnpNamespaces.svc + "allowedValue");
|
||||
|
||||
allowedValues.AddRange(values.Select(child => child.Value));
|
||||
}
|
||||
|
||||
return new StateVariable
|
||||
{
|
||||
Name = container.GetValue(uPnpNamespaces.svc + "name"),
|
||||
DataType = container.GetValue(uPnpNamespaces.svc + "dataType"),
|
||||
AllowedValues = allowedValues
|
||||
};
|
||||
}
|
||||
|
||||
public string BuildPost(ServiceAction action, string xmlNamespace)
|
||||
{
|
||||
var stateString = string.Empty;
|
||||
|
|
235
MediaBrowser.Dlna/Server/ContentDirectoryXmlBuilder.cs
Normal file
235
MediaBrowser.Dlna/Server/ContentDirectoryXmlBuilder.cs
Normal file
|
@ -0,0 +1,235 @@
|
|||
using MediaBrowser.Dlna.Common;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using System.Collections.Generic;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
|
||||
namespace MediaBrowser.Dlna.Server
|
||||
{
|
||||
public class ContentDirectoryXmlBuilder
|
||||
{
|
||||
private readonly DeviceProfile _profile;
|
||||
|
||||
public ContentDirectoryXmlBuilder(DeviceProfile profile)
|
||||
{
|
||||
_profile = profile;
|
||||
}
|
||||
|
||||
public string GetXml()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.Append("<?xml version=\"1.0\"?>");
|
||||
builder.Append("scpd xmlns=\"urn:schemas-upnp-org:service-1-0\"");
|
||||
|
||||
builder.Append("<specVersion>");
|
||||
builder.Append("<major>1</major>");
|
||||
builder.Append("<minor>0</minor>");
|
||||
builder.Append("</specVersion>");
|
||||
|
||||
AppendActionList(builder);
|
||||
AppendServiceStateTable(builder);
|
||||
|
||||
builder.Append("</scpd>");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private void AppendActionList(StringBuilder builder)
|
||||
{
|
||||
builder.Append("<actionList>");
|
||||
|
||||
foreach (var item in new ServiceActionListBuilder().GetActions())
|
||||
{
|
||||
builder.Append("<action>");
|
||||
|
||||
builder.Append("<name>" + SecurityElement.Escape(item.Name ?? string.Empty) + "</name>");
|
||||
|
||||
builder.Append("<argumentList>");
|
||||
|
||||
foreach (var argument in item.ArgumentList)
|
||||
{
|
||||
builder.Append("<argument>");
|
||||
|
||||
builder.Append("<name>" + SecurityElement.Escape(argument.Name ?? string.Empty) + "</name>");
|
||||
builder.Append("<direction>" + SecurityElement.Escape(argument.Direction ?? string.Empty) + "</direction>");
|
||||
builder.Append("<relatedStateVariable>" + SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty) + "</relatedStateVariable>");
|
||||
|
||||
builder.Append("</argument>");
|
||||
}
|
||||
|
||||
builder.Append("</argumentList>");
|
||||
|
||||
builder.Append("</action>");
|
||||
}
|
||||
|
||||
builder.Append("</actionList>");
|
||||
}
|
||||
|
||||
private void AppendServiceStateTable(StringBuilder builder)
|
||||
{
|
||||
builder.Append("<serviceStateTable>");
|
||||
|
||||
foreach (var item in GetStateVariables())
|
||||
{
|
||||
var sendEvents = item.SendsEvents ? "yes" : "no";
|
||||
|
||||
builder.Append("<stateVariable sendEvents=\"" + sendEvents + "\">");
|
||||
|
||||
builder.Append("<name>" + SecurityElement.Escape(item.Name ?? string.Empty) + "</name>");
|
||||
builder.Append("<dataType>" + SecurityElement.Escape(item.DataType ?? string.Empty) + "</dataType>");
|
||||
|
||||
if (item.AllowedValues.Count > 0)
|
||||
{
|
||||
builder.Append("<allowedValueList>");
|
||||
foreach (var allowedValue in item.AllowedValues)
|
||||
{
|
||||
builder.Append("<allowedValue>" + SecurityElement.Escape(allowedValue) + "</allowedValue>");
|
||||
}
|
||||
builder.Append("</allowedValueList>");
|
||||
}
|
||||
|
||||
builder.Append("</stateVariable>");
|
||||
}
|
||||
|
||||
builder.Append("</serviceStateTable>");
|
||||
}
|
||||
|
||||
private IEnumerable<StateVariable> GetStateVariables()
|
||||
{
|
||||
var list = new List<StateVariable>();
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_SortCriteria",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_UpdateID",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_SearchCriteria",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_Filter",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_Result",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_Index",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_ObjectID",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "SortCapabilities",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "SearchCapabilities",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_Count",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_BrowseFlag",
|
||||
DataType = "string",
|
||||
SendsEvents = false,
|
||||
|
||||
AllowedValues = new List<string>
|
||||
{
|
||||
"BrowseMetadata",
|
||||
"BrowseDirectChildren"
|
||||
}
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "SystemUpdateID",
|
||||
DataType = "ui4",
|
||||
SendsEvents = true
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_BrowseLetter",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_CategoryType",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_RID",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_PosSec",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_Featurelist",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return GetXml();
|
||||
}
|
||||
}
|
||||
}
|
190
MediaBrowser.Dlna/Server/ControlHandler.cs
Normal file
190
MediaBrowser.Dlna/Server/ControlHandler.cs
Normal file
|
@ -0,0 +1,190 @@
|
|||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
namespace MediaBrowser.Dlna.Server
|
||||
{
|
||||
public class ControlHandler
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
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/";
|
||||
private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
|
||||
private const string NS_SEC = "http://www.sec.co.kr/";
|
||||
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
||||
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
||||
|
||||
private const int systemID = 0;
|
||||
|
||||
public ControlHandler(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
{
|
||||
var soap = new XmlDocument();
|
||||
soap.LoadXml(request.InputXml);
|
||||
var sparams = new Headers();
|
||||
var body = soap.GetElementsByTagName("Body", NS_SOAPENV).Item(0);
|
||||
|
||||
var method = body.FirstChild;
|
||||
|
||||
foreach (var p in method.ChildNodes)
|
||||
{
|
||||
var e = p as XmlElement;
|
||||
if (e == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
sparams.Add(e.LocalName, e.InnerText.Trim());
|
||||
}
|
||||
|
||||
var env = new XmlDocument();
|
||||
env.AppendChild(env.CreateXmlDeclaration("1.0", "utf-8", "yes"));
|
||||
var envelope = env.CreateElement("SOAP-ENV", "Envelope", NS_SOAPENV);
|
||||
env.AppendChild(envelope);
|
||||
envelope.SetAttribute("encodingStyle", NS_SOAPENV, "http://schemas.xmlsoap.org/soap/encoding/");
|
||||
|
||||
var rbody = env.CreateElement("SOAP-ENV:Body", NS_SOAPENV);
|
||||
env.DocumentElement.AppendChild(rbody);
|
||||
|
||||
IEnumerable<KeyValuePair<string, string>> result;
|
||||
switch (method.LocalName)
|
||||
{
|
||||
case "GetSearchCapabilities":
|
||||
result = HandleGetSearchCapabilities();
|
||||
break;
|
||||
case "GetSortCapabilities":
|
||||
result = HandleGetSortCapabilities();
|
||||
break;
|
||||
case "GetSystemUpdateID":
|
||||
result = HandleGetSystemUpdateID();
|
||||
break;
|
||||
case "Browse":
|
||||
result = HandleBrowse(sparams);
|
||||
break;
|
||||
case "X_GetFeatureList":
|
||||
result = HandleXGetFeatureList();
|
||||
break;
|
||||
case "X_SetBookmark":
|
||||
result = HandleXSetBookmark(sparams);
|
||||
break;
|
||||
default:
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
var response = env.CreateElement(String.Format("u:{0}Response", method.LocalName), method.NamespaceURI);
|
||||
rbody.AppendChild(response);
|
||||
|
||||
foreach (var i in result)
|
||||
{
|
||||
var ri = env.CreateElement(i.Key);
|
||||
ri.InnerText = i.Value;
|
||||
response.AppendChild(ri);
|
||||
}
|
||||
|
||||
var controlResponse = new ControlResponse
|
||||
{
|
||||
Xml = env.OuterXml
|
||||
};
|
||||
|
||||
controlResponse.Headers.Add("EXT", string.Empty);
|
||||
|
||||
return controlResponse;
|
||||
}
|
||||
|
||||
private Headers HandleXSetBookmark(Headers sparams)
|
||||
{
|
||||
var id = sparams["ObjectID"];
|
||||
//var item = GetItem(id) as IBookmarkable;
|
||||
//if (item != null)
|
||||
//{
|
||||
// var newbookmark = long.Parse(sparams["PosSecond"]);
|
||||
// if (newbookmark > 30)
|
||||
// {
|
||||
// newbookmark -= 5;
|
||||
// }
|
||||
// if (newbookmark > 30 || !item.Bookmark.HasValue || item.Bookmark.Value < 60)
|
||||
// {
|
||||
// item.Bookmark = newbookmark;
|
||||
// }
|
||||
//}
|
||||
return new Headers();
|
||||
}
|
||||
|
||||
private Headers HandleGetSearchCapabilities()
|
||||
{
|
||||
return new Headers { { "SearchCaps", string.Empty } };
|
||||
}
|
||||
|
||||
private Headers HandleGetSortCapabilities()
|
||||
{
|
||||
return new Headers { { "SortCaps", string.Empty } };
|
||||
}
|
||||
|
||||
private Headers HandleGetSystemUpdateID()
|
||||
{
|
||||
return new Headers { { "Id", systemID.ToString() } };
|
||||
}
|
||||
|
||||
private Headers HandleXGetFeatureList()
|
||||
{
|
||||
return new Headers { { "FeatureList", GetFeatureListXml() } };
|
||||
}
|
||||
|
||||
private string GetFeatureListXml()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
builder.Append("<Features xmlns=\"urn:schemas-upnp-org:av:avs\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\">");
|
||||
|
||||
builder.Append("<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">");
|
||||
builder.Append("<container id=\"I\" type=\"object.item.imageItem\"/>");
|
||||
builder.Append("<container id=\"A\" type=\"object.item.audioItem\"/>");
|
||||
builder.Append("<container id=\"V\" type=\"object.item.videoItem\"/>");
|
||||
builder.Append("</Feature>");
|
||||
|
||||
builder.Append("</Features>");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, string>> HandleBrowse(Headers sparams)
|
||||
{
|
||||
var id = sparams["ObjectID"];
|
||||
var flag = sparams["BrowseFlag"];
|
||||
|
||||
int requested;
|
||||
var provided = 0;
|
||||
int start;
|
||||
|
||||
if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out requested) && requested <= 0)
|
||||
{
|
||||
requested = 20;
|
||||
}
|
||||
if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out start) && start <= 0)
|
||||
{
|
||||
start = 0;
|
||||
}
|
||||
|
||||
//var root = GetItem(id) as IMediaFolder;
|
||||
var result = new XmlDocument();
|
||||
|
||||
var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL);
|
||||
didl.SetAttribute("xmlns:dc", NS_DC);
|
||||
didl.SetAttribute("xmlns:dlna", NS_DLNA);
|
||||
didl.SetAttribute("xmlns:upnp", NS_UPNP);
|
||||
didl.SetAttribute("xmlns:sec", NS_SEC);
|
||||
result.AppendChild(didl);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
65
MediaBrowser.Dlna/Server/Datagram.cs
Normal file
65
MediaBrowser.Dlna/Server/Datagram.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace MediaBrowser.Dlna.Server
|
||||
{
|
||||
public class Datagram
|
||||
{
|
||||
public IPEndPoint EndPoint { get; private set; }
|
||||
public IPAddress LocalAddress { get; private set; }
|
||||
public string Message { get; private set; }
|
||||
public bool Sticky { get; private set; }
|
||||
|
||||
public int SendCount { get; private set; }
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public Datagram(IPEndPoint endPoint, IPAddress localAddress, ILogger logger, string message, bool sticky)
|
||||
{
|
||||
Message = message;
|
||||
_logger = logger;
|
||||
Sticky = sticky;
|
||||
LocalAddress = localAddress;
|
||||
EndPoint = endPoint;
|
||||
}
|
||||
|
||||
public void Send()
|
||||
{
|
||||
var msg = Encoding.ASCII.GetBytes(Message);
|
||||
try
|
||||
{
|
||||
var client = new UdpClient();
|
||||
client.Client.Bind(new IPEndPoint(LocalAddress, 0));
|
||||
client.BeginSend(msg, msg.Length, EndPoint, result =>
|
||||
{
|
||||
try
|
||||
{
|
||||
client.EndSend(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error sending Datagram", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
client.Close();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error sending Datagram", ex);
|
||||
}
|
||||
++SendCount;
|
||||
}
|
||||
}
|
||||
}
|
190
MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
Normal file
190
MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
Normal file
|
@ -0,0 +1,190 @@
|
|||
using MediaBrowser.Dlna.Common;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
|
||||
namespace MediaBrowser.Dlna.Server
|
||||
{
|
||||
public class DescriptionXmlBuilder
|
||||
{
|
||||
private readonly DeviceProfile _profile;
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private readonly string _serverUdn;
|
||||
|
||||
public DescriptionXmlBuilder(DeviceProfile profile, string serverUdn)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(serverUdn))
|
||||
{
|
||||
throw new ArgumentNullException("serverUdn");
|
||||
}
|
||||
|
||||
_profile = profile;
|
||||
_serverUdn = serverUdn;
|
||||
}
|
||||
|
||||
public string GetXml()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.Append("<?xml version=\"1.0\"?>");
|
||||
builder.Append("<root xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\" xmlns:sec=\"http://www.sec.co.kr/dlna\">");
|
||||
|
||||
builder.Append("<specVersion>");
|
||||
builder.Append("<major>1</major>");
|
||||
builder.Append("<minor>0</minor>");
|
||||
builder.Append("</specVersion>");
|
||||
|
||||
AppendDeviceInfo(builder);
|
||||
|
||||
builder.Append("</root>");
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private void AppendDeviceInfo(StringBuilder builder)
|
||||
{
|
||||
builder.Append("<device>");
|
||||
AppendDeviceProperties(builder);
|
||||
|
||||
AppendIconList(builder);
|
||||
AppendServiceList(builder);
|
||||
builder.Append("</device>");
|
||||
}
|
||||
|
||||
private void AppendDeviceProperties(StringBuilder builder)
|
||||
{
|
||||
builder.Append("<UDN>" + SecurityElement.Escape(_serverUdn) + "</UDN>");
|
||||
builder.Append("<dlna:X_DLNACAP>" + SecurityElement.Escape(_profile.XDlnaCap ?? string.Empty) + "</dlna:X_DLNACAP>");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_profile.XDlnaDoc))
|
||||
{
|
||||
builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" +
|
||||
SecurityElement.Escape(_profile.XDlnaDoc) + "</dlna:X_DLNADOC>");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append("<dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">DMS-1.50</dlna:X_DLNADOC>");
|
||||
}
|
||||
|
||||
builder.Append("<friendlyName>" + SecurityElement.Escape(_profile.FriendlyName ?? string.Empty) + "</friendlyName>");
|
||||
builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>");
|
||||
builder.Append("<manufacturer>" + SecurityElement.Escape(_profile.Manufacturer ?? string.Empty) + "</manufacturer>");
|
||||
builder.Append("<manufacturerURL>" + SecurityElement.Escape(_profile.ManufacturerUrl ?? string.Empty) + "</manufacturerURL>");
|
||||
builder.Append("<modelName>" + SecurityElement.Escape(_profile.ModelName ?? string.Empty) + "</modelName>");
|
||||
builder.Append("<modelDescription>" + SecurityElement.Escape(_profile.ModelDescription ?? string.Empty) + "</modelDescription>");
|
||||
builder.Append("<modelNumber>" + SecurityElement.Escape(_profile.ModelNumber ?? string.Empty) + "</modelNumber>");
|
||||
builder.Append("<modelURL>" + SecurityElement.Escape(_profile.ModelUrl ?? string.Empty) + "</modelURL>");
|
||||
builder.Append("<serialNumber>" + SecurityElement.Escape(_profile.SerialNumber ?? string.Empty) + "</serialNumber>");
|
||||
|
||||
builder.Append("<sec:ProductCap>DCM10,getMediaInfo.sec</sec:ProductCap>");
|
||||
builder.Append("<sec:X_ProductCap>DCM10,getMediaInfo.sec</sec:X_ProductCap>");
|
||||
}
|
||||
|
||||
private void AppendIconList(StringBuilder builder)
|
||||
{
|
||||
builder.Append("<iconList>");
|
||||
|
||||
foreach (var icon in GetIcons())
|
||||
{
|
||||
builder.Append("<icon>");
|
||||
|
||||
builder.Append("<mimetype>" + SecurityElement.Escape(icon.MimeType ?? string.Empty) + "</mimetype>");
|
||||
builder.Append("<width>" + SecurityElement.Escape(icon.Width.ToString(_usCulture)) + "</width>");
|
||||
builder.Append("<height>" + SecurityElement.Escape(icon.Height.ToString(_usCulture)) + "</height>");
|
||||
builder.Append("<depth>" + SecurityElement.Escape(icon.Depth ?? string.Empty) + "</depth>");
|
||||
builder.Append("<url>" + SecurityElement.Escape(icon.Url ?? string.Empty) + "</url>");
|
||||
|
||||
builder.Append("</icon>");
|
||||
}
|
||||
|
||||
builder.Append("</iconList>");
|
||||
}
|
||||
|
||||
private void AppendServiceList(StringBuilder builder)
|
||||
{
|
||||
builder.Append("<serviceList>");
|
||||
|
||||
foreach (var service in GetServices())
|
||||
{
|
||||
builder.Append("<service>");
|
||||
|
||||
builder.Append("<serviceType>" + SecurityElement.Escape(service.ServiceType ?? string.Empty) + "</serviceType>");
|
||||
builder.Append("<serviceId>" + SecurityElement.Escape(service.ServiceId ?? string.Empty) + "</serviceId>");
|
||||
builder.Append("<SCPDURL>" + SecurityElement.Escape(service.ScpdUrl ?? string.Empty) + "</SCPDURL>");
|
||||
builder.Append("<controlURL>" + SecurityElement.Escape(service.ControlUrl ?? string.Empty) + "</controlURL>");
|
||||
builder.Append("<eventSubURL>" + SecurityElement.Escape(service.EventSubUrl ?? string.Empty) + "</eventSubURL>");
|
||||
|
||||
builder.Append("</service>");
|
||||
}
|
||||
|
||||
builder.Append("</serviceList>");
|
||||
}
|
||||
|
||||
private IEnumerable<DeviceIcon> GetIcons()
|
||||
{
|
||||
var list = new List<DeviceIcon>();
|
||||
|
||||
list.Add(new DeviceIcon
|
||||
{
|
||||
MimeType = "image/jpeg",
|
||||
Depth = "24",
|
||||
Width = 48,
|
||||
Height = 48,
|
||||
Url = "/mediabrowser/dlna/icons/small.jpg"
|
||||
});
|
||||
|
||||
list.Add(new DeviceIcon
|
||||
{
|
||||
MimeType = "image/jpeg",
|
||||
Depth = "24",
|
||||
Width = 120,
|
||||
Height = 120,
|
||||
Url = "/mediabrowser/dlna/icons/large.jpg"
|
||||
});
|
||||
|
||||
list.Add(new DeviceIcon
|
||||
{
|
||||
MimeType = "image/png",
|
||||
Depth = "24",
|
||||
Width = 48,
|
||||
Height = 48,
|
||||
Url = "/mediabrowser/dlna/icons/small.png"
|
||||
});
|
||||
|
||||
list.Add(new DeviceIcon
|
||||
{
|
||||
MimeType = "image/png",
|
||||
Depth = "24",
|
||||
Width = 120,
|
||||
Height = 120,
|
||||
Url = "/mediabrowser/dlna/icons/large.png"
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private IEnumerable<DeviceService> GetServices()
|
||||
{
|
||||
var list = new List<DeviceService>();
|
||||
|
||||
list.Add(new DeviceService
|
||||
{
|
||||
ServiceType = "urn:schemas-upnp-org:service:ContentDirectory:1",
|
||||
ServiceId = "urn:upnp-org:serviceId:ContentDirectory",
|
||||
ScpdUrl = "/mediabrowser/dlna/contentdirectory.xml",
|
||||
ControlUrl = "/servicecontrol"
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return GetXml();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace MediaBrowser.Dlna.Server
|
||||
{
|
||||
|
@ -13,11 +16,13 @@ namespace MediaBrowser.Dlna.Server
|
|||
|
||||
private SsdpHandler _ssdpHandler;
|
||||
private readonly IApplicationHost _appHost;
|
||||
private readonly INetworkManager _network;
|
||||
|
||||
public DlnaServerEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost)
|
||||
public DlnaServerEntryPoint(IServerConfigurationManager config, ILogManager logManager, IApplicationHost appHost, INetworkManager network)
|
||||
{
|
||||
_config = config;
|
||||
_appHost = appHost;
|
||||
_network = network;
|
||||
_logger = logManager.GetLogger("DlnaServer");
|
||||
}
|
||||
|
||||
|
@ -25,12 +30,12 @@ namespace MediaBrowser.Dlna.Server
|
|||
{
|
||||
_config.ConfigurationUpdated += ConfigurationUpdated;
|
||||
|
||||
//ReloadServer();
|
||||
ReloadServer();
|
||||
}
|
||||
|
||||
void ConfigurationUpdated(object sender, EventArgs e)
|
||||
{
|
||||
//ReloadServer();
|
||||
ReloadServer();
|
||||
}
|
||||
|
||||
private void ReloadServer()
|
||||
|
@ -57,6 +62,8 @@ namespace MediaBrowser.Dlna.Server
|
|||
try
|
||||
{
|
||||
_ssdpHandler = new SsdpHandler(_logger, _config, signature);
|
||||
|
||||
RegisterEndpoints();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -65,6 +72,20 @@ namespace MediaBrowser.Dlna.Server
|
|||
}
|
||||
}
|
||||
|
||||
private void RegisterEndpoints()
|
||||
{
|
||||
foreach (var address in _network.GetLocalIpAddresses())
|
||||
{
|
||||
var guid = address.GetMD5();
|
||||
|
||||
var descriptorURI = "/mediabrowser/dlna/" + guid.ToString("N") + "/description.xml";
|
||||
|
||||
var uri = new Uri(string.Format("http://{0}:{1}{2}", address, _config.Configuration.HttpServerPortNumber, descriptorURI));
|
||||
|
||||
_ssdpHandler.RegisterNotification(guid, uri, IPAddress.Parse(address));
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeServer()
|
||||
{
|
||||
lock (_syncLock)
|
||||
|
|
209
MediaBrowser.Dlna/Server/ServiceActionListBuilder.cs
Normal file
209
MediaBrowser.Dlna/Server/ServiceActionListBuilder.cs
Normal file
|
@ -0,0 +1,209 @@
|
|||
using MediaBrowser.Dlna.Common;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Dlna.Server
|
||||
{
|
||||
public class ServiceActionListBuilder
|
||||
{
|
||||
public IEnumerable<ServiceAction> GetActions()
|
||||
{
|
||||
var list = new List<ServiceAction>
|
||||
{
|
||||
GetGetSystemUpdateIDAction(),
|
||||
GetSearchCapabilitiesAction(),
|
||||
GetSortCapabilitiesAction(),
|
||||
GetBrowseAction(),
|
||||
GetX_GetFeatureListAction(),
|
||||
GetXSetBookmarkAction()
|
||||
};
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private ServiceAction GetGetSystemUpdateIDAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "GetSystemUpdateID"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Id",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "SystemUpdateID"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private ServiceAction GetSearchCapabilitiesAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "GetSearchCapabilities"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "SearchCaps",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "SearchCapabilities"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private ServiceAction GetSortCapabilitiesAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "GetSortCapabilities"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "SortCaps",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "SortCapabilities"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private ServiceAction GetX_GetFeatureListAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "X_GetFeatureList"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "FeatureList",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Featurelist"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private ServiceAction GetBrowseAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "Browse"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "ObjectID",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ObjectID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "BrowseFlag",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_BrowseFlag"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Filter",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Filter"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "StartingIndex",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Index"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "RequestedCount",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Count"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "SortCriteria",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_SortCriteria"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Result",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Result"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "NumberReturned",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Count"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "TotalMatches",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Count"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "UpdateID",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_UpdateID"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private ServiceAction GetXSetBookmarkAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "X_SetBookmark"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "CategoryType",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_CategoryType"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "RID",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_RID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "ObjectID",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ObjectID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "PosSecond",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_PosSec"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +1,26 @@
|
|||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace MediaBrowser.Dlna.Server
|
||||
{
|
||||
public class SsdpHandler : IDisposable
|
||||
{
|
||||
private readonly AutoResetEvent _datagramPosted = new AutoResetEvent(false);
|
||||
private readonly ConcurrentQueue<Datagram> _messageQueue = new ConcurrentQueue<Datagram>();
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly string _serverSignature;
|
||||
private bool _isDisposed = false;
|
||||
private bool _isDisposed;
|
||||
|
||||
const string SSDPAddr = "239.255.255.250";
|
||||
const int SSDPPort = 1900;
|
||||
|
@ -27,6 +32,9 @@ namespace MediaBrowser.Dlna.Server
|
|||
|
||||
private readonly Dictionary<Guid, List<UpnpDevice>> _devices = new Dictionary<Guid, List<UpnpDevice>>();
|
||||
|
||||
private Timer _queueTimer;
|
||||
private Timer _notificationTimer;
|
||||
|
||||
public SsdpHandler(ILogger logger, IServerConfigurationManager config, string serverSignature)
|
||||
{
|
||||
_logger = logger;
|
||||
|
@ -59,6 +67,8 @@ namespace MediaBrowser.Dlna.Server
|
|||
_udpClient.JoinMulticastGroup(_ssdpIp, 2);
|
||||
_logger.Info("SSDP service started");
|
||||
Receive();
|
||||
|
||||
StartNotificationTimer();
|
||||
}
|
||||
|
||||
private void Receive()
|
||||
|
@ -137,7 +147,7 @@ namespace MediaBrowser.Dlna.Server
|
|||
|
||||
foreach (var d in Devices)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(req) && req != d.Type)
|
||||
if (!string.IsNullOrEmpty(req) && !string.Equals(req, d.Type, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -157,28 +167,67 @@ namespace MediaBrowser.Dlna.Server
|
|||
headers.Add("ST", dev.Type);
|
||||
headers.Add("USN", dev.USN);
|
||||
|
||||
SendDatagram(endpoint, String.Format("HTTP/1.1 200 OK\r\n{0}\r\n", headers.HeaderBlock), false);
|
||||
var msg = String.Format("HTTP/1.1 200 OK\r\n{0}\r\n", headers.HeaderBlock);
|
||||
|
||||
SendDatagram(endpoint, dev.Address, msg, false);
|
||||
|
||||
_logger.Info("{1} - Responded to a {0} request", dev.Type, endpoint);
|
||||
}
|
||||
|
||||
private void SendDatagram(IPEndPoint endpoint, string msg, bool sticky)
|
||||
private void SendDatagram(IPEndPoint endpoint, IPAddress localAddress, string msg, bool sticky)
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//var dgram = new Datagram(endpoint, msg, sticky);
|
||||
//if (messageQueue.Count == 0)
|
||||
//{
|
||||
// dgram.Send();
|
||||
//}
|
||||
//messageQueue.Enqueue(dgram);
|
||||
//queueTimer.Enabled = true;
|
||||
|
||||
var dgram = new Datagram(endpoint, localAddress, _logger, msg, sticky);
|
||||
if (_messageQueue.Count == 0)
|
||||
{
|
||||
dgram.Send();
|
||||
}
|
||||
_messageQueue.Enqueue(dgram);
|
||||
StartQueueTimer();
|
||||
}
|
||||
|
||||
private void QueueTimerCallback(object state)
|
||||
{
|
||||
while (_messageQueue.Count != 0)
|
||||
{
|
||||
Datagram msg;
|
||||
if (!_messageQueue.TryPeek(out msg))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (msg != null && (!_isDisposed || msg.Sticky))
|
||||
{
|
||||
msg.Send();
|
||||
if (msg.SendCount > 2)
|
||||
{
|
||||
_messageQueue.TryDequeue(out msg);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
_messageQueue.TryDequeue(out msg);
|
||||
}
|
||||
|
||||
_datagramPosted.Set();
|
||||
|
||||
if (_messageQueue.Count > 0)
|
||||
{
|
||||
StartQueueTimer();
|
||||
}
|
||||
else
|
||||
{
|
||||
DisposeQueueTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyAll()
|
||||
{
|
||||
_logger.Debug("NotifyAll");
|
||||
_logger.Debug("Sending alive notifications");
|
||||
foreach (var d in Devices)
|
||||
{
|
||||
NotifyDevice(d, "alive", false);
|
||||
|
@ -197,64 +246,128 @@ namespace MediaBrowser.Dlna.Server
|
|||
headers.Add("NT", dev.Type);
|
||||
headers.Add("USN", dev.USN);
|
||||
|
||||
SendDatagram(_ssdpEndp, String.Format("NOTIFY * HTTP/1.1\r\n{0}\r\n", headers.HeaderBlock), sticky);
|
||||
var msg = String.Format("NOTIFY * HTTP/1.1\r\n{0}\r\n", headers.HeaderBlock);
|
||||
|
||||
_logger.Debug("{0} said {1}", dev.USN, type);
|
||||
SendDatagram(_ssdpEndp, dev.Address, msg, sticky);
|
||||
}
|
||||
|
||||
private void RegisterNotification(Guid UUID, Uri Descriptor)
|
||||
public void RegisterNotification(Guid uuid, Uri descriptor, IPAddress address)
|
||||
{
|
||||
List<UpnpDevice> list;
|
||||
lock (_devices)
|
||||
{
|
||||
if (!_devices.TryGetValue(UUID, out list))
|
||||
if (!_devices.TryGetValue(uuid, out list))
|
||||
{
|
||||
_devices.Add(UUID, list = new List<UpnpDevice>());
|
||||
_devices.Add(uuid, list = new List<UpnpDevice>());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var t in new[] { "upnp:rootdevice", "urn:schemas-upnp-org:device:MediaServer:1", "urn:schemas-upnp-org:service:ContentDirectory:1", "uuid:" + UUID })
|
||||
foreach (var t in new[]
|
||||
{
|
||||
list.Add(new UpnpDevice(UUID, t, Descriptor));
|
||||
"upnp:rootdevice",
|
||||
"urn:schemas-upnp-org:device:MediaServer:1",
|
||||
"urn:schemas-upnp-org:service:ContentDirectory:1",
|
||||
"uuid:" + uuid
|
||||
})
|
||||
{
|
||||
list.Add(new UpnpDevice(uuid, t, descriptor, address));
|
||||
}
|
||||
|
||||
NotifyAll();
|
||||
_logger.Debug("Registered mount {0}", UUID);
|
||||
_logger.Debug("Registered mount {0} at {1}", uuid, descriptor);
|
||||
}
|
||||
|
||||
internal void UnregisterNotification(Guid UUID)
|
||||
private void UnregisterNotification(Guid uuid)
|
||||
{
|
||||
List<UpnpDevice> dl;
|
||||
lock (_devices)
|
||||
{
|
||||
if (!_devices.TryGetValue(UUID, out dl))
|
||||
if (!_devices.TryGetValue(uuid, out dl))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_devices.Remove(UUID);
|
||||
_devices.Remove(uuid);
|
||||
}
|
||||
foreach (var d in dl)
|
||||
{
|
||||
NotifyDevice(d, "byebye", true);
|
||||
}
|
||||
_logger.Debug("Unregistered mount {0}", UUID);
|
||||
_logger.Debug("Unregistered mount {0}", uuid);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_isDisposed = true;
|
||||
//while (messageQueue.Count != 0)
|
||||
//{
|
||||
// datagramPosted.WaitOne();
|
||||
//}
|
||||
while (_messageQueue.Count != 0)
|
||||
{
|
||||
_datagramPosted.WaitOne();
|
||||
}
|
||||
|
||||
_udpClient.DropMulticastGroup(_ssdpIp);
|
||||
_udpClient.Close();
|
||||
|
||||
//notificationTimer.Enabled = false;
|
||||
//queueTimer.Enabled = false;
|
||||
//notificationTimer.Dispose();
|
||||
//queueTimer.Dispose();
|
||||
//datagramPosted.Dispose();
|
||||
DisposeNotificationTimer();
|
||||
DisposeQueueTimer();
|
||||
_datagramPosted.Dispose();
|
||||
}
|
||||
|
||||
private readonly object _queueTimerSyncLock = new object();
|
||||
private void StartQueueTimer()
|
||||
{
|
||||
lock (_queueTimerSyncLock)
|
||||
{
|
||||
if (_queueTimer == null)
|
||||
{
|
||||
_queueTimer = new Timer(QueueTimerCallback, null, 1000, Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
_queueTimer.Change(1000, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeQueueTimer()
|
||||
{
|
||||
lock (_queueTimerSyncLock)
|
||||
{
|
||||
if (_queueTimer != null)
|
||||
{
|
||||
_queueTimer.Dispose();
|
||||
_queueTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object _notificationTimerSyncLock = new object();
|
||||
private void StartNotificationTimer()
|
||||
{
|
||||
const int intervalMs = 60000;
|
||||
|
||||
lock (_notificationTimerSyncLock)
|
||||
{
|
||||
if (_notificationTimer == null)
|
||||
{
|
||||
_notificationTimer = new Timer(state => NotifyAll(), null, intervalMs, intervalMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
_notificationTimer.Change(intervalMs, intervalMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeNotificationTimer()
|
||||
{
|
||||
lock (_notificationTimerSyncLock)
|
||||
{
|
||||
if (_notificationTimer != null)
|
||||
{
|
||||
_notificationTimer.Dispose();
|
||||
_notificationTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace MediaBrowser.Dlna.Server
|
||||
{
|
||||
|
@ -8,13 +9,16 @@ namespace MediaBrowser.Dlna.Server
|
|||
public readonly string Type;
|
||||
public readonly string USN;
|
||||
public readonly Guid Uuid;
|
||||
public readonly IPAddress Address;
|
||||
|
||||
public UpnpDevice(Guid aUuid, string aType, Uri aDescriptor)
|
||||
public UpnpDevice(Guid aUuid, string aType, Uri aDescriptor, IPAddress address)
|
||||
{
|
||||
Uuid = aUuid;
|
||||
Type = aType;
|
||||
Descriptor = aDescriptor;
|
||||
|
||||
Address = address;
|
||||
|
||||
if (Type.StartsWith("uuid:"))
|
||||
{
|
||||
USN = Type;
|
||||
|
|
|
@ -31,6 +31,11 @@
|
|||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Release Mono</OutputPath>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BDInfo">
|
||||
<HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.10\lib\net35\BDInfo.dll</HintPath>
|
||||
|
@ -75,7 +80,6 @@
|
|||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
|
|
@ -62,11 +62,10 @@ namespace MediaBrowser.Model.ApiClient
|
|||
/// <summary>
|
||||
/// Reports the capabilities.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
/// <param name="capabilities">The capabilities.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task ReportCapabilities(string sessionId, ClientCapabilities capabilities, CancellationToken cancellationToken);
|
||||
Task ReportCapabilities(ClientCapabilities capabilities, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the game players.
|
||||
|
@ -771,6 +770,13 @@ namespace MediaBrowser.Model.ApiClient
|
|||
/// <returns>System.String.</returns>
|
||||
string GetImageUrl(ProgramInfoDto item, ImageOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subtitle URL.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
string GetSubtitleUrl(SubtitleOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an image url that can be used to download an image from the api
|
||||
/// </summary>
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
public string ModelDescription { get; set; }
|
||||
public string ModelNumber { get; set; }
|
||||
public string ModelUrl { get; set; }
|
||||
public string SerialNumber { get; set; }
|
||||
public bool IgnoreTranscodeByteRangeRequests { get; set; }
|
||||
public bool EnableAlbumArtInDidl { get; set; }
|
||||
|
||||
|
|
|
@ -158,4 +158,19 @@
|
|||
/// <value>The device id.</value>
|
||||
public string DeviceId { get; set; }
|
||||
}
|
||||
|
||||
public class SubtitleOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the item identifier.
|
||||
/// </summary>
|
||||
/// <value>The item identifier.</value>
|
||||
public string ItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the stream.
|
||||
/// </summary>
|
||||
/// <value>The index of the stream.</value>
|
||||
public int StreamIndex { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MediaBrowser.Model.Dto
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
|
@ -46,6 +47,12 @@ namespace MediaBrowser.Model.Entities
|
|||
/// <value>The primary image tag.</value>
|
||||
public Guid? PrimaryImageTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the primary image item identifier.
|
||||
/// </summary>
|
||||
/// <value>The primary image item identifier.</value>
|
||||
public string PrimaryImageItemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the thumb image tag.
|
||||
/// </summary>
|
||||
|
@ -75,6 +82,54 @@ namespace MediaBrowser.Model.Entities
|
|||
/// </summary>
|
||||
/// <value>The media version identifier.</value>
|
||||
public string MediaSourceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the premiere date.
|
||||
/// </summary>
|
||||
/// <value>The premiere date.</value>
|
||||
public DateTime? PremiereDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the production year.
|
||||
/// </summary>
|
||||
/// <value>The production year.</value>
|
||||
public int? ProductionYear { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index number.
|
||||
/// </summary>
|
||||
/// <value>The index number.</value>
|
||||
public int? IndexNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index number end.
|
||||
/// </summary>
|
||||
/// <value>The index number end.</value>
|
||||
public int? IndexNumberEnd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parent index number.
|
||||
/// </summary>
|
||||
/// <value>The parent index number.</value>
|
||||
public int? ParentIndexNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the series.
|
||||
/// </summary>
|
||||
/// <value>The name of the series.</value>
|
||||
public string SeriesName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the album.
|
||||
/// </summary>
|
||||
/// <value>The album.</value>
|
||||
public string Album { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the artists.
|
||||
/// </summary>
|
||||
/// <value>The artists.</value>
|
||||
public List<string> Artists { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance has primary image.
|
||||
|
@ -85,5 +140,10 @@ namespace MediaBrowser.Model.Entities
|
|||
{
|
||||
get { return PrimaryImageTag.HasValue; }
|
||||
}
|
||||
|
||||
public BaseItemInfo()
|
||||
{
|
||||
Artists = new List<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,8 @@ namespace MediaBrowser.Model.Session
|
|||
ToggleMute = 21,
|
||||
SetVolume = 22,
|
||||
SetAudioStreamIndex = 23,
|
||||
SetSubtitleStreamIndex = 24
|
||||
SetSubtitleStreamIndex = 24,
|
||||
ToggleFullscreen = 25,
|
||||
DisplayContent = 26
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,10 +31,6 @@ namespace MediaBrowser.Model.Session
|
|||
/// </summary>
|
||||
Seek,
|
||||
/// <summary>
|
||||
/// The fullscreen
|
||||
/// </summary>
|
||||
Fullscreen,
|
||||
/// <summary>
|
||||
/// The rewind
|
||||
/// </summary>
|
||||
Rewind,
|
||||
|
|
|
@ -51,6 +51,12 @@ namespace MediaBrowser.Model.Session
|
|||
/// <value>The user id.</value>
|
||||
public string UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user primary image tag.
|
||||
/// </summary>
|
||||
/// <value>The user primary image tag.</value>
|
||||
public Guid? UserPrimaryImageTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the user.
|
||||
/// </summary>
|
||||
|
@ -164,12 +170,6 @@ namespace MediaBrowser.Model.Session
|
|||
/// </summary>
|
||||
/// <value><c>true</c> if [supports remote control]; otherwise, <c>false</c>.</value>
|
||||
public bool SupportsRemoteControl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [supports navigation commands].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [supports navigation commands]; otherwise, <c>false</c>.</value>
|
||||
public bool SupportsNavigationControl { get; set; }
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
|
@ -203,10 +203,12 @@ namespace MediaBrowser.Model.Session
|
|||
public class ClientCapabilities
|
||||
{
|
||||
public List<string> PlayableMediaTypes { get; set; }
|
||||
public List<string> SupportedCommands { get; set; }
|
||||
|
||||
public ClientCapabilities()
|
||||
{
|
||||
PlayableMediaTypes = new List<string>();
|
||||
SupportedCommands = new List<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Mono",
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Dlna", "MediaBrowser.Dlna\MediaBrowser.Dlna.csproj", "{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x86 = Debug|x86
|
||||
|
@ -29,6 +31,14 @@ Global
|
|||
Release Mono|Any CPU = Release Mono|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release Mono|Any CPU.Build.0 = Release|Any CPU
|
||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|x86.Build.0 = Release|Any CPU
|
||||
{175A9388-F352-4586-A6B4-070DED62B644}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{175A9388-F352-4586-A6B4-070DED62B644}.Debug|x86.Build.0 = Debug|x86
|
||||
{175A9388-F352-4586-A6B4-070DED62B644}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
|
||||
|
@ -77,6 +87,14 @@ Global
|
|||
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.Build.0 = Release|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|x86.Build.0 = Release|Any CPU
|
||||
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
|
||||
|
@ -101,14 +119,6 @@ Global
|
|||
{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|x86.Build.0 = Release|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(MonoDevelopProperties) = preSolution
|
||||
StartupItem = MediaBrowser.Server.Mono\MediaBrowser.Server.Mono.csproj
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<File FileName="MediaBrowser.Server.Mono\app.config" Line="1" Column="1" />
|
||||
<File FileName="MediaBrowser.ServerApplication\ApplicationHost.cs" Line="1" Column="1" />
|
||||
<File FileName="MediaBrowser.Server.Mono\Native\NativeApp.cs" Line="1" Column="1" />
|
||||
<File FileName="MediaBrowser.Server.Mono\Program.cs" Line="36" Column="34" />
|
||||
<File FileName="MediaBrowser.Server.Mono\Program.cs" Line="36" Column="37" />
|
||||
</Files>
|
||||
</MonoDevelop.Ide.Workbench>
|
||||
<MonoDevelop.Ide.DebuggingService.Breakpoints>
|
||||
|
|
|
@ -71,10 +71,18 @@ namespace MediaBrowser.Providers.Manager
|
|||
|
||||
var saveLocally = item.SupportsLocalMetadata && item.IsSaveLocalMetadataEnabled() && !item.IsOwnedItem && !(item is Audio);
|
||||
|
||||
if (item is IItemByName || item is User)
|
||||
if (item is User)
|
||||
{
|
||||
saveLocally = true;
|
||||
}
|
||||
if (item is IItemByName)
|
||||
{
|
||||
var hasDualAccess = item as IHasDualAccess;
|
||||
if (hasDualAccess == null || hasDualAccess.IsAccessedByName)
|
||||
{
|
||||
saveLocally = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (type != ImageType.Primary && item is Episode)
|
||||
{
|
||||
|
|
|
@ -139,8 +139,6 @@ namespace MediaBrowser.Providers.Movies
|
|||
/// </summary>
|
||||
private TmdbSettingsResult _tmdbSettings;
|
||||
|
||||
private readonly SemaphoreSlim _tmdbSettingsSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TMDB settings.
|
||||
/// </summary>
|
||||
|
@ -152,32 +150,17 @@ namespace MediaBrowser.Providers.Movies
|
|||
return _tmdbSettings;
|
||||
}
|
||||
|
||||
await _tmdbSettingsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
using (var json = await GetMovieDbResponse(new HttpRequestOptions
|
||||
{
|
||||
// Check again in case it got populated while we were waiting.
|
||||
if (_tmdbSettings != null)
|
||||
{
|
||||
return _tmdbSettings;
|
||||
}
|
||||
Url = string.Format(TmdbConfigUrl, ApiKey),
|
||||
CancellationToken = cancellationToken,
|
||||
AcceptHeader = AcceptHeader
|
||||
|
||||
using (var json = await GetMovieDbResponse(new HttpRequestOptions
|
||||
{
|
||||
Url = string.Format(TmdbConfigUrl, ApiKey),
|
||||
CancellationToken = cancellationToken,
|
||||
AcceptHeader = AcceptHeader
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
{
|
||||
_tmdbSettings = _jsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json);
|
||||
|
||||
return _tmdbSettings;
|
||||
}
|
||||
}
|
||||
finally
|
||||
}).ConfigureAwait(false))
|
||||
{
|
||||
_tmdbSettingsSemaphore.Release();
|
||||
_tmdbSettings = _jsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json);
|
||||
|
||||
return _tmdbSettings;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,11 +9,8 @@ using MediaBrowser.Model.Providers;
|
|||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -64,7 +61,7 @@ namespace MediaBrowser.Providers.Music
|
|||
return result;
|
||||
}
|
||||
|
||||
protected virtual async Task FetchLastfmData(MusicArtist item, string musicBrainzId, CancellationToken cancellationToken)
|
||||
protected async Task FetchLastfmData(MusicArtist item, string musicBrainzId, CancellationToken cancellationToken)
|
||||
{
|
||||
// Get artist info with provided id
|
||||
var url = RootUrl + String.Format("method=artist.getInfo&mbid={0}&api_key={1}&format=json", UrlEncode(musicBrainzId), ApiKey);
|
||||
|
|
|
@ -78,7 +78,7 @@ namespace MediaBrowser.Providers.TV
|
|||
try
|
||||
{
|
||||
var seriesOffset = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds);
|
||||
if (seriesOffset != null)
|
||||
if (seriesOffset != null && seriesOffset.Value != 0)
|
||||
return TvdbSeasonImageProvider.GetImages(path, language, seriesOffset.Value + 1, cancellationToken);
|
||||
|
||||
return GetImages(path, language, cancellationToken);
|
||||
|
|
|
@ -360,7 +360,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
{
|
||||
return _imageProcessor.GetImageCacheTag(item, type);
|
||||
}
|
||||
catch (IOException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting {0} image info", ex, type);
|
||||
return null;
|
||||
|
@ -373,7 +373,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
{
|
||||
return _imageProcessor.GetImageCacheTag(item, image);
|
||||
}
|
||||
catch (IOException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting {0} image info for {1}", ex, image.Type, image.Path);
|
||||
return null;
|
||||
|
|
|
@ -34,8 +34,10 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
|
|||
{
|
||||
var collectionType = args.GetCollectionType();
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.IsNullOrWhiteSpace(collectionType))
|
||||
var isStandalone = args.Parent == null;
|
||||
|
||||
if (isStandalone ||
|
||||
string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Controller.Entities.Audio.Audio();
|
||||
}
|
||||
|
|
|
@ -31,9 +31,21 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
|
||||
public async Task<QueryResult<SearchHintInfo>> GetSearchHints(SearchQuery query)
|
||||
{
|
||||
var user = _userManager.GetUserById(new Guid(query.UserId));
|
||||
IEnumerable<BaseItem> inputItems;
|
||||
|
||||
var inputItems = user.RootFolder.GetRecursiveChildren(user, null).Where(i => !(i is ICollectionFolder));
|
||||
if (string.IsNullOrEmpty(query.UserId))
|
||||
{
|
||||
inputItems = _libraryManager.RootFolder.RecursiveChildren;
|
||||
}
|
||||
else
|
||||
{
|
||||
var user = _userManager.GetUserById(new Guid(query.UserId));
|
||||
|
||||
inputItems = user.RootFolder.GetRecursiveChildren(user, null);
|
||||
}
|
||||
|
||||
|
||||
inputItems = inputItems.Where(i => !(i is ICollectionFolder));
|
||||
|
||||
inputItems = _libraryManager.ReplaceVideosWithPrimaryVersions(inputItems);
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"SettingsSaved":"Configuraci\u00f3 guardada.","AddUser":"Afegir Usuari","Users":"Usuaris","Delete":"Esborrar","Administrator":"Administrador","Password":"Contrasenya","DeleteImage":"Esborrar Imatge","DeleteImageConfirmation":"Esteu segur que voleu suprimir aquesta imatge?","FileReadCancelled":"La lectura de l'arxiu ha estat cancel\u00b7lada.","FileNotFound":"Arxiu no trobat.","FileReadError":"S'ha produ\u00eft un error en llegir el fitxer.","DeleteUser":"Esborrar Usuari","DeleteUserConfirmation":"Esteu segur que voleu suprimir {0}?","PasswordResetHeader":"Restablir contrasenya","PasswordResetComplete":"La contrasenya s'ha restablert.","PasswordResetConfirmation":"Esteu segur que voleu restablir la contrasenya?","PasswordSaved":"S'ha guardat la contrasenya.","PasswordMatchError":"Confirmaci\u00f3 de la contrasenya i la contrasenya han de coincidir.","OptionOff":"Apagat","OptionOn":"Enc\u00e8s","OptionRelease":"Versi\u00f3 Oficial","OptionBeta":"Beta","OptionDev":"Dev (Inestable)","UninstallPluginHeader":"Desinstal\u00b7lar Plugin.","UninstallPluginConfirmation":"Esteu segur que voleu desinstal\u00b7lar {0}?","NoPluginConfigurationMessage":"Aquest plugin no necessita configuraci\u00f3.","NoPluginsInstalledMessage":"No t\u00e9 cap plugin instal\u00b7lat.","BrowsePluginCatalogMessage":"Consulti el nostre cat\u00e0leg per veure els plugins disponibles."}
|
|
@ -1 +1 @@
|
|||
{"SettingsSaved":"Einstellungen gespeichert","AddUser":"Benutzer hinzuf\u00fcgen","Users":"Benutzer","Delete":"L\u00f6schen","Administrator":"Administrator","Password":"Passwort","DeleteImage":"Bild l\u00f6schen","DeleteImageConfirmation":"M\u00f6chten Sie das Bild wirklich l\u00f6schen?","FileReadCancelled":"Das Einlesen der Datei wurde abgebrochen.","FileNotFound":"Datei nicht gefunden","FileReadError":"Beim Lesen der Datei ist ein Fehler aufgetreten.","DeleteUser":"Benutzer l\u00f6schen","DeleteUserConfirmation":"M\u00f6chten Sie {0} wirklich l\u00f6schen?","PasswordResetHeader":"Passwort zur\u00fccksetzen","PasswordResetComplete":"Das Passwort wurde zur\u00fcckgesetzt.","PasswordResetConfirmation":"M\u00f6chten Sie das Passwort wirklich zur\u00fccksetzen?","PasswordSaved":"Passwort gespeichert","PasswordMatchError":"Passwort und Passwortbest\u00e4tigung stimmen nicht \u00fcberein.","OptionOff":"Aus","OptionOn":"Ein","OptionRelease":"Release","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Deinstalliere Plugin","UninstallPluginConfirmation":"M\u00f6chten Sie {0} wirklich deinstallieren?","NoPluginConfigurationMessage":"Bei diesem Plugin kann nichts eingestellt werden.","NoPluginsInstalledMessage":"Sie haben keine Plugins installiert.","BrowsePluginCatalogMessage":"Durchsuchen Sie unsere Bibliothek um alle verf\u00fcgbaren Plugins anzuzeigen."}
|
||||
{"SettingsSaved":"Einstellungen gespeichert","AddUser":"Benutzer hinzuf\u00fcgen","Users":"Benutzer","Delete":"L\u00f6schen","Administrator":"Administrator","Password":"Passwort","DeleteImage":"Bild l\u00f6schen","DeleteImageConfirmation":"M\u00f6chten Sie das Bild wirklich l\u00f6schen?","FileReadCancelled":"Das Einlesen der Datei wurde abgebrochen.","FileNotFound":"Datei nicht gefunden","FileReadError":"Beim Lesen der Datei ist ein Fehler aufgetreten.","DeleteUser":"Benutzer l\u00f6schen","DeleteUserConfirmation":"M\u00f6chten Sie {0} wirklich l\u00f6schen?","PasswordResetHeader":"Passwort zur\u00fccksetzen","PasswordResetComplete":"Das Passwort wurde zur\u00fcckgesetzt.","PasswordResetConfirmation":"M\u00f6chten Sie das Passwort wirklich zur\u00fccksetzen?","PasswordSaved":"Passwort gespeichert","PasswordMatchError":"Passwort und Passwortbest\u00e4tigung stimmen nicht \u00fcberein.","OptionOff":"Aus","OptionOn":"Ein","OptionRelease":"Offizielles Release","OptionBeta":"Beta","OptionDev":"Entwickler (instabil)","UninstallPluginHeader":"Deinstalliere Plugin","UninstallPluginConfirmation":"M\u00f6chten Sie {0} wirklich deinstallieren?","NoPluginConfigurationMessage":"Bei diesem Plugin kann nichts eingestellt werden.","NoPluginsInstalledMessage":"Sie haben keine Plugins installiert.","BrowsePluginCatalogMessage":"Durchsuchen Sie unsere Bibliothek um alle verf\u00fcgbaren Plugins anzuzeigen."}
|
|
@ -0,0 +1 @@
|
|||
{"SettingsSaved":"Seting Disimpan","AddUser":"Tambah Pengguna","Users":"Para Pengguna","Delete":"Padam","Administrator":"Administrator","Password":"Password","DeleteImage":"Delete Image","DeleteImageConfirmation":"Are you sure you wish to delete this image?","FileReadCancelled":"The file read has been canceled.","FileNotFound":"File not found.","FileReadError":"An error occurred while reading the file.","DeleteUser":"Delete User","DeleteUserConfirmation":"Are you sure you wish to delete {0}?","PasswordResetHeader":"Password Reset","PasswordResetComplete":"The password has been reset.","PasswordResetConfirmation":"Are you sure you wish to reset the password?","PasswordSaved":"Password saved.","PasswordMatchError":"Password and password confirmation must match.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Official Release","OptionBeta":"Beta","OptionDev":"Dev (Unstable)","UninstallPluginHeader":"Uninstall Plugin","UninstallPluginConfirmation":"Are you sure you wish to uninstall {0}?","NoPluginConfigurationMessage":"This plugin has nothing to configure.","NoPluginsInstalledMessage":"You have no plugins installed.","BrowsePluginCatalogMessage":"Browse our plugin catalog to view available plugins."}
|
|
@ -1 +1 @@
|
|||
{"SettingsSaved":"Prefer\u00eancias salvas.","AddUser":"Adicionar Usu\u00e1rio","Users":"Usu\u00e1rios","Delete":"Apagar","Administrator":"Administrador","Password":"Senha","DeleteImage":"Apagar Imagem","DeleteImageConfirmation":"Tem certeza que deseja apagar esta imagem?","FileReadCancelled":"A leitura do arquivo foi cancelada.","FileNotFound":"Arquivo n\u00e3o encontrado.","FileReadError":"Ocorreu um erro ao ler o arquivo.","DeleteUser":"Apagar Usu\u00e1rio","DeleteUserConfirmation":"Tem certeza que deseja apagar {0}?","PasswordResetHeader":"Redefinir Senha","PasswordResetComplete":"A senha foi redefinida.","PasswordResetConfirmation":"Deseja realmente redefinir a senha?","PasswordSaved":"Senha salva.","PasswordMatchError":"A senha e confirma\u00e7\u00e3o da senha devem conferir.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Lan\u00e7amento Oficial","OptionBeta":"Beta","OptionDev":"Dev (Inst\u00e1vel)","UninstallPluginHeader":"Desintalar Plugin","UninstallPluginConfirmation":"Deseja realmente desinstalar {0}?","NoPluginConfigurationMessage":"Este plugin n\u00e3o necessita configurar.","NoPluginsInstalledMessage":"N\u00e3o existem plugins instalados.","BrowsePluginCatalogMessage":"Navegue pelo cat\u00e1logo de plugins para ver os dispon\u00edveis."}
|
||||
{"SettingsSaved":"Ajustes salvos.","AddUser":"Adicionar Usu\u00e1rio","Users":"Usu\u00e1rios","Delete":"Apagar","Administrator":"Administrador","Password":"Senha","DeleteImage":"Apagar Imagem","DeleteImageConfirmation":"Tem certeza que deseja apagar esta imagem?","FileReadCancelled":"A leitura do arquivo foi cancelada.","FileNotFound":"Arquivo n\u00e3o encontrado.","FileReadError":"Ocorreu um erro ao ler o arquivo.","DeleteUser":"Apagar Usu\u00e1rio","DeleteUserConfirmation":"Deseja realmente apagar {0}?","PasswordResetHeader":"Redefinir Senha","PasswordResetComplete":"A senha foi redefinida.","PasswordResetConfirmation":"Deseja realmente redefinir a senha?","PasswordSaved":"Senha salva.","PasswordMatchError":"A senha e a confirma\u00e7\u00e3o da senha devem ser iguais.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Lan\u00e7amento Oficial","OptionBeta":"Beta","OptionDev":"Dev (Inst\u00e1vel)","UninstallPluginHeader":"Desinstalar Plugin","UninstallPluginConfirmation":"Deseja realmente desinstalar {0}?","NoPluginConfigurationMessage":"Este plugin n\u00e3o necessita configurar.","NoPluginsInstalledMessage":"N\u00e3o existem plugins instalados.","BrowsePluginCatalogMessage":"Navegue pelo cat\u00e1logo de plugins para ver os dispon\u00edveis."}
|
|
@ -1 +1 @@
|
|||
{"SettingsSaved":"\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b","AddUser":"\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f","Users":"\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438","Delete":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c","Administrator":"\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440","Password":"\u041f\u0430\u0440\u043e\u043b\u044c","DeleteImage":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435","DeleteImageConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435?","FileReadCancelled":"\u0427\u0442\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 \u0431\u044b\u043b\u043e \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043e.","FileNotFound":"\u0424\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d","FileReadError":"\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430","DeleteUser":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f","DeleteUserConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?","PasswordResetHeader":"\u0421\u0431\u0440\u043e\u0441 \u043f\u0430\u0440\u043e\u043b\u044f","PasswordResetComplete":"\u041f\u0430\u0440\u043e\u043b\u044c \u0431\u044b\u043b \u0441\u0431\u0440\u043e\u0448\u0435\u043d","PasswordResetConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c?","PasswordSaved":"\u041f\u0430\u0440\u043e\u043b\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d","PasswordMatchError":"\u041f\u043e\u043b\u044f \u041f\u0430\u0440\u043e\u043b\u044c \u0438 \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c","OptionOff":"\u0412\u044b\u043a\u043b","OptionOn":"\u0412\u043a\u043b","OptionRelease":"\u041e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0432\u044b\u043f\u0443\u0441\u043a","OptionBeta":"\u0411\u0435\u0442\u0430","OptionDev":"\u0420\u0430\u0437\u0440\u0430\u0431 (\u043d\u0435\u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e)","UninstallPluginHeader":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043b\u0430\u0433\u0438\u043d","UninstallPluginConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?","NoPluginConfigurationMessage":"\u0414\u043b\u044f \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043d\u0435\u0447\u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c.","NoPluginsInstalledMessage":"\u0423 \u0412\u0430\u0441 \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430.","BrowsePluginCatalogMessage":"\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u043d\u0430\u0448\u0438\u043c \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043e\u043c \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432."}
|
||||
{"SettingsSaved":"\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b","AddUser":"\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f","Users":"\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438","Delete":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c","Administrator":"\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440","Password":"\u041f\u0430\u0440\u043e\u043b\u044c","DeleteImage":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0440\u0438\u0441\u0443\u043d\u043e\u043a","DeleteImageConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0439 \u0440\u0438\u0441\u0443\u043d\u043e\u043a?","FileReadCancelled":"\u0427\u0442\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 \u0431\u044b\u043b\u043e \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043e.","FileNotFound":"\u0424\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d","FileReadError":"\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430","DeleteUser":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f","DeleteUserConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?","PasswordResetHeader":"\u0421\u0431\u0440\u043e\u0441 \u043f\u0430\u0440\u043e\u043b\u044f","PasswordResetComplete":"\u041f\u0430\u0440\u043e\u043b\u044c \u0431\u044b\u043b \u0441\u0431\u0440\u043e\u0448\u0435\u043d","PasswordResetConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c?","PasswordSaved":"\u041f\u0430\u0440\u043e\u043b\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d","PasswordMatchError":"\u041f\u043e\u043b\u044f \u041f\u0430\u0440\u043e\u043b\u044c \u0438 \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c","OptionOff":"\u0412\u044b\u043a\u043b","OptionOn":"\u0412\u043a\u043b","OptionRelease":"\u041e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0432\u044b\u043f\u0443\u0441\u043a","OptionBeta":"\u0411\u0435\u0442\u0430","OptionDev":"\u0420\u0430\u0437\u0440\u0430\u0431 (\u043d\u0435\u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e)","UninstallPluginHeader":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043b\u0430\u0433\u0438\u043d","UninstallPluginConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?","NoPluginConfigurationMessage":"\u0414\u043b\u044f \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043d\u0435\u0447\u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c.","NoPluginsInstalledMessage":"\u0423 \u0412\u0430\u0441 \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430.","BrowsePluginCatalogMessage":"\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u043d\u0430\u0448 \u043a\u0430\u0442\u0430\u043b\u043e\u0433, \u0447\u0442\u043e\u0431\u044b \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c\u0438 \u043f\u043b\u0430\u0433\u0438\u043d\u0430\u043c\u0438."}
|
|
@ -125,7 +125,18 @@ namespace MediaBrowser.Server.Implementations.Localization
|
|||
public IEnumerable<CountryInfo> GetCountries()
|
||||
{
|
||||
return CultureInfo.GetCultures(CultureTypes.SpecificCultures)
|
||||
.Select(c => new RegionInfo(c.LCID))
|
||||
.Select(c =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return new RegionInfo(c.LCID);
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.Where(i => i != null)
|
||||
.OrderBy(c => c.DisplayName)
|
||||
.DistinctBy(c => c.TwoLetterISORegionName)
|
||||
.Select(c => new CountryInfo
|
||||
|
@ -356,7 +367,7 @@ namespace MediaBrowser.Server.Implementations.Localization
|
|||
}.OrderBy(i => i.Name);
|
||||
}
|
||||
|
||||
public string LocalizeDocument(string document, string culture, Func<string,string> tokenBuilder)
|
||||
public string LocalizeDocument(string document, string culture, Func<string, string> tokenBuilder)
|
||||
{
|
||||
foreach (var pair in GetLocalizationDictionary(culture).ToList())
|
||||
{
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -450,5 +450,70 @@
|
|||
"LabelMinResumeDuration": "Min resume duration (seconds):",
|
||||
"LabelMinResumePercentageHelp": "Titles are assumed unplayed if stopped before this time",
|
||||
"LabelMaxResumePercentageHelp": "Titles are assumed fully played if stopped after this time",
|
||||
"LabelMinResumeDurationHelp": "Titles shorter than this will not be resumable"
|
||||
"LabelMinResumeDurationHelp": "Titles shorter than this will not be resumable",
|
||||
"TitleAutoOrganize": "Auto-Organize",
|
||||
"TabActivityLog": "Activity Log",
|
||||
"HeaderName": "Name",
|
||||
"HeaderDate": "Date",
|
||||
"HeaderSource": "Source",
|
||||
"HeaderStatus": "Status",
|
||||
"HeaderDestination": "Destination",
|
||||
"HeaderProgram": "Program",
|
||||
"HeaderClients": "Clients",
|
||||
"LabelCompleted": "Completed",
|
||||
"LabelFailed": "Failed",
|
||||
"LabelSkipped": "Skipped",
|
||||
"HeaderEpisodeOrganization": "Episode Organization",
|
||||
"LabelSeries": "Series:",
|
||||
"LabelSeasonNumber": "Season number:",
|
||||
"LabelEpisodeNumber": "Episode number:",
|
||||
"LabelEndingEpisodeNumber": "Ending episode number:",
|
||||
"LabelEndingEpisodeNumberHelp": "Only required for multi-episode files",
|
||||
"HeaderSupportTheTeam": "Support the Media Browser Team",
|
||||
"LabelSupportAmount": "Amount (USD)",
|
||||
"HeaderSupportTheTeamHelp": "Help ensure the continued development of this project by donating. A portion of all donations will be contributed to other free tools we depend on.",
|
||||
"ButtonEnterSupporterKey": "Enter supporter key",
|
||||
"DonationNextStep": "Once complete, please return and enter your supporter key, which you will receive by email.",
|
||||
"AutoOrganizeHelp": "Auto-organize monitors your download folders for new files and moves them to your media directories.",
|
||||
"AutoOrganizeTvHelp": "TV file organizing will only add episodes to existing series. It will not create new series folders.",
|
||||
"OptionEnableEpisodeOrganization": "Enable new episode organization",
|
||||
"LabelWatchFolder": "Watch folder:",
|
||||
"LabelWatchFolderHelp": "The server will poll this folder during the 'Organize new media files' scheduled task.",
|
||||
"ButtonViewScheduledTasks": "View scheduled tasks",
|
||||
"LabelMinFileSizeForOrganize": "Minimum file size (MB):",
|
||||
"LabelMinFileSizeForOrganizeHelp": "Files under this size will be ignored.",
|
||||
"LabelSeasonFolderPattern": "Season folder pattern:",
|
||||
"LabelSeasonZeroFolderName": "Season zero folder name:",
|
||||
"HeaderEpisodeFilePattern": "Episode file pattern",
|
||||
"LabelEpisodePattern": "Episode pattern:",
|
||||
"LabelMultiEpisodePattern": "Multi-Episode pattern:",
|
||||
"HeaderSupportedPatterns": "Supported Patterns",
|
||||
"HeaderTerm": "Term",
|
||||
"HeaderPattern": "Pattern",
|
||||
"HeaderResult": "Result",
|
||||
"LabelDeleteEmptyFolders": "Delete empty folders after organizing",
|
||||
"LabelDeleteEmptyFoldersHelp": "Enable this to keep the download directory clean.",
|
||||
"LabelDeleteLeftOverFiles": "Delete left over files with the following extensions:",
|
||||
"LabelDeleteLeftOverFilesHelp": "Separate with ;. For example: .nfo;.txt",
|
||||
"OptionOverwriteExistingEpisodes": "Overwrite existing episodes",
|
||||
"LabelTransferMethod": "Transfer method",
|
||||
"OptionCopy": "Copy",
|
||||
"OptionMove": "Move",
|
||||
"LabelTransferMethodHelp": "Copy or move files from the watch folder",
|
||||
"HeaderLatestNews": "Latest News",
|
||||
"HeaderHelpImproveMediaBrowser": "Help Improve Media Browser",
|
||||
"HeaderRunningTasks": "Running Tasks",
|
||||
"HeaderActiveDevices": "Active Devices",
|
||||
"HeaderPendingInstallations": "Pending Installations",
|
||||
"HeaerServerInformation": "Server Information",
|
||||
"ButtonRestartNow": "Restart Now",
|
||||
"ButtonRestart": "Restart",
|
||||
"ButtonShutdown": "Shutdown",
|
||||
"ButtonUpdateNow": "Update Now",
|
||||
"PleaseUpdateManually": "Please shutdown the server and update manually.",
|
||||
"NewServerVersionAvailable": "A new version of Media Browser Server is available!",
|
||||
"ServerUpToDate": "Media Browser Server is up to date",
|
||||
"ErrorConnectingToMediaBrowserRepository": "There was an error connecting to the remote Media Browser repository.",
|
||||
"LabelComponentsUpdated": "The following components have been installed or updated:",
|
||||
"MessagePleaseRestartServerToFinishUpdating": "Please restart the server to finish applying updates."
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -319,6 +319,10 @@
|
|||
<EmbeddedResource Include="Localization\Server\sv.json" />
|
||||
<EmbeddedResource Include="Localization\JavaScript\cs.json" />
|
||||
<EmbeddedResource Include="Localization\Server\cs.json" />
|
||||
<EmbeddedResource Include="Localization\JavaScript\ca.json" />
|
||||
<EmbeddedResource Include="Localization\JavaScript\ms.json" />
|
||||
<EmbeddedResource Include="Localization\Server\ca.json" />
|
||||
<EmbeddedResource Include="Localization\Server\ms.json" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -46,6 +46,16 @@ namespace MediaBrowser.Server.Implementations.Roku
|
|||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
return SendCommand(new WebSocketMessage<MessageCommand>
|
||||
|
@ -66,16 +76,6 @@ namespace MediaBrowser.Server.Implementations.Roku
|
|||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SendBrowseCommand(BrowseRequest command, CancellationToken cancellationToken)
|
||||
{
|
||||
return SendCommand(new WebSocketMessage<BrowseRequest>
|
||||
{
|
||||
MessageType = "Browse",
|
||||
Data = command
|
||||
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
|
||||
{
|
||||
return SendCommand(new WebSocketMessage<PlaystateRequest>
|
||||
|
|
|
@ -8,6 +8,7 @@ using MediaBrowser.Controller.Entities;
|
|||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
@ -444,6 +445,8 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
MediaSourceId = info.MediaSourceId
|
||||
|
||||
}, _logger);
|
||||
|
||||
await SendPlaybackStartNotification(session, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -583,6 +586,8 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
MediaSourceId = mediaSourceId
|
||||
|
||||
}, _logger);
|
||||
|
||||
await SendPlaybackStoppedNotification(session, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private string GetMediaSourceId(BaseItem item, string reportedMediaSourceId)
|
||||
|
@ -858,12 +863,17 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
|
||||
public Task SendBrowseCommand(Guid controllingSessionId, Guid sessionId, BrowseRequest command, CancellationToken cancellationToken)
|
||||
{
|
||||
var session = GetSessionForRemoteControl(sessionId);
|
||||
var generalCommand = new GeneralCommand
|
||||
{
|
||||
Name = GeneralCommandType.DisplayContent.ToString()
|
||||
};
|
||||
|
||||
var controllingSession = GetSession(controllingSessionId);
|
||||
AssertCanControl(session, controllingSession);
|
||||
generalCommand.Arguments["Context"] = command.Context;
|
||||
generalCommand.Arguments["ItemId"] = command.ItemId;
|
||||
generalCommand.Arguments["ItemName"] = command.ItemName;
|
||||
generalCommand.Arguments["ItemType"] = command.ItemType;
|
||||
|
||||
return session.SessionController.SendBrowseCommand(command, cancellationToken);
|
||||
return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SendPlaystateCommand(Guid controllingSessionId, Guid sessionId, PlaystateRequest command, CancellationToken cancellationToken)
|
||||
|
@ -972,7 +982,6 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
|
||||
public Task SendSessionEndedNotification(SessionInfo sessionInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList();
|
||||
|
@ -994,6 +1003,48 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
public Task SendPlaybackStartNotification(SessionInfo sessionInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList();
|
||||
var dto = GetSessionInfoDto(sessionInfo);
|
||||
|
||||
var tasks = sessions.Select(session => Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await session.SessionController.SendPlaybackStartNotification(dto, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error in SendPlaybackStartNotification.", ex);
|
||||
}
|
||||
|
||||
}, cancellationToken));
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
public Task SendPlaybackStoppedNotification(SessionInfo sessionInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList();
|
||||
var dto = GetSessionInfoDto(sessionInfo);
|
||||
|
||||
var tasks = sessions.Select(session => Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await session.SessionController.SendPlaybackStoppedNotification(dto, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error in SendPlaybackStoppedNotification.", ex);
|
||||
}
|
||||
|
||||
}, cancellationToken));
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the additional user.
|
||||
/// </summary>
|
||||
|
@ -1131,6 +1182,13 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
if (session.UserId.HasValue)
|
||||
{
|
||||
dto.UserId = session.UserId.Value.ToString("N");
|
||||
|
||||
var user = _userManager.GetUserById(session.UserId.Value);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
dto.UserPrimaryImageTag = GetImageCacheTag(user, ImageType.Primary);
|
||||
}
|
||||
}
|
||||
|
||||
return dto;
|
||||
|
@ -1158,19 +1216,84 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
MediaType = item.MediaType,
|
||||
Type = item.GetClientTypeName(),
|
||||
RunTimeTicks = nowPlayingRuntimeTicks,
|
||||
MediaSourceId = mediaSourceId
|
||||
MediaSourceId = mediaSourceId,
|
||||
IndexNumber = item.IndexNumber,
|
||||
ParentIndexNumber = item.ParentIndexNumber,
|
||||
PremiereDate = item.PremiereDate,
|
||||
ProductionYear = item.ProductionYear
|
||||
};
|
||||
|
||||
info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary);
|
||||
if (info.PrimaryImageTag.HasValue)
|
||||
{
|
||||
info.PrimaryImageItemId = GetDtoId(item);
|
||||
}
|
||||
|
||||
var episode = item as Episode;
|
||||
if (episode != null)
|
||||
{
|
||||
info.IndexNumberEnd = episode.IndexNumberEnd;
|
||||
}
|
||||
|
||||
var hasSeries = item as IHasSeries;
|
||||
if (hasSeries != null)
|
||||
{
|
||||
info.SeriesName = hasSeries.SeriesName;
|
||||
}
|
||||
|
||||
var recording = item as ILiveTvRecording;
|
||||
if (recording != null && recording.RecordingInfo != null)
|
||||
{
|
||||
if (recording.RecordingInfo.IsSeries)
|
||||
{
|
||||
info.Name = recording.RecordingInfo.EpisodeTitle;
|
||||
info.SeriesName = recording.RecordingInfo.Name;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(info.Name))
|
||||
{
|
||||
info.Name = recording.RecordingInfo.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var audio = item as Audio;
|
||||
if (audio != null)
|
||||
{
|
||||
info.Album = audio.Album;
|
||||
info.Artists = audio.Artists;
|
||||
|
||||
if (!info.PrimaryImageTag.HasValue)
|
||||
{
|
||||
var album = audio.Parents.OfType<MusicAlbum>().FirstOrDefault();
|
||||
|
||||
if (album != null && album.HasImage(ImageType.Primary))
|
||||
{
|
||||
info.PrimaryImageTag = GetImageCacheTag(album, ImageType.Primary);
|
||||
if (info.PrimaryImageTag.HasValue)
|
||||
{
|
||||
info.PrimaryImageItemId = GetDtoId(album);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var musicVideo = item as MusicVideo;
|
||||
if (musicVideo != null)
|
||||
{
|
||||
info.Album = musicVideo.Album;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(musicVideo.Artist))
|
||||
{
|
||||
info.Artists.Add(musicVideo.Artist);
|
||||
}
|
||||
}
|
||||
|
||||
var backropItem = item.HasImage(ImageType.Backdrop) ? item : null;
|
||||
|
||||
var thumbItem = item.HasImage(ImageType.Thumb) ? item : null;
|
||||
|
||||
if (thumbItem == null)
|
||||
{
|
||||
var episode = item as Episode;
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
var series = episode.Series;
|
||||
|
@ -1184,8 +1307,6 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
|
||||
if (backropItem == null)
|
||||
{
|
||||
var episode = item as Episode;
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
var series = episode.Series;
|
||||
|
@ -1197,6 +1318,11 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
}
|
||||
}
|
||||
|
||||
if (backropItem == null)
|
||||
{
|
||||
backropItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Backdrop));
|
||||
}
|
||||
|
||||
if (thumbItem == null)
|
||||
{
|
||||
thumbItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Thumb));
|
||||
|
@ -1208,7 +1334,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
info.ThumbItemId = GetDtoId(thumbItem);
|
||||
}
|
||||
|
||||
if (thumbItem != null)
|
||||
if (backropItem != null)
|
||||
{
|
||||
info.BackdropImageTag = GetImageCacheTag(backropItem, ImageType.Backdrop);
|
||||
info.BackdropItemId = GetDtoId(backropItem);
|
||||
|
|
|
@ -81,18 +81,6 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SendBrowseCommand(BrowseRequest command, CancellationToken cancellationToken)
|
||||
{
|
||||
var socket = GetActiveSocket();
|
||||
|
||||
return socket.SendAsync(new WebSocketMessage<BrowseRequest>
|
||||
{
|
||||
MessageType = "Browse",
|
||||
Data = command
|
||||
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
|
||||
{
|
||||
var socket = GetActiveSocket();
|
||||
|
@ -210,5 +198,29 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var socket = GetActiveSocket();
|
||||
|
||||
return socket.SendAsync(new WebSocketMessage<SessionInfoDto>
|
||||
{
|
||||
MessageType = "PlaybackStart",
|
||||
Data = sessionInfo
|
||||
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var socket = GetActiveSocket();
|
||||
|
||||
return socket.SendAsync(new WebSocketMessage<SessionInfoDto>
|
||||
{
|
||||
MessageType = "PlaybackStopped",
|
||||
Data = sessionInfo
|
||||
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Threading;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
<Name>MediaBrowser.Providers</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MediaBrowser.Dlna\MediaBrowser.Dlna.csproj">
|
||||
<Project>{734098eb-6dc1-4dd0-a1ca-3140dcd2737c}</Project>
|
||||
<Project>{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}</Project>
|
||||
<Name>MediaBrowser.Dlna</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
|
||||
|
@ -124,6 +124,10 @@
|
|||
<Project>{4FD51AC5-2C16-4308-A993-C3A84F3B4582}</Project>
|
||||
<Name>MediaBrowser.Api</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj">
|
||||
<Project>{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}</Project>
|
||||
<Name>MediaBrowser.MediaEncoding</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="EntryPoints\" />
|
||||
|
|
|
@ -16,6 +16,7 @@ using System.Net.Security;
|
|||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
// MONOMKBUNDLE: For the embedded version, mkbundle tool
|
||||
#if MONOMKBUNDLE
|
||||
using Mono.Unix;
|
||||
|
@ -39,8 +40,13 @@ namespace MediaBrowser.Server.Mono
|
|||
#else
|
||||
var applicationPath = Assembly.GetEntryAssembly ().Location;
|
||||
#endif
|
||||
|
||||
var commandArgs = Environment.GetCommandLineArgs();
|
||||
|
||||
// Allow this to be specified on the command line.
|
||||
var customProgramDataPath = commandArgs.ElementAtOrDefault(1);
|
||||
|
||||
var appPaths = CreateApplicationPaths(applicationPath);
|
||||
var appPaths = CreateApplicationPaths(applicationPath, customProgramDataPath);
|
||||
|
||||
var logManager = new NlogManager(appPaths.LogDirectoryPath, "server");
|
||||
logManager.ReloadLogger(LogSeverity.Info);
|
||||
|
@ -70,9 +76,14 @@ namespace MediaBrowser.Server.Mono
|
|||
}
|
||||
}
|
||||
|
||||
private static ServerApplicationPaths CreateApplicationPaths(string applicationPath)
|
||||
private static ServerApplicationPaths CreateApplicationPaths(string applicationPath, string programDataPath)
|
||||
{
|
||||
return new ServerApplicationPaths(applicationPath);
|
||||
if (string.IsNullOrEmpty(programDataPath))
|
||||
{
|
||||
return new ServerApplicationPaths(applicationPath);
|
||||
}
|
||||
|
||||
return new ServerApplicationPaths(programDataPath, applicationPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -372,8 +372,11 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
sb.Append("<meta http-equiv=\"X-UA-Compatibility\" content=\"IE=Edge\">");
|
||||
sb.Append("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">");
|
||||
sb.Append("<meta name=\"apple-mobile-web-app-capable\" content=\"yes\">");
|
||||
sb.Append("<meta name=\"mobile-web-app-capable\" content=\"yes\">");
|
||||
//sb.Append("<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">");
|
||||
|
||||
sb.Append("<link rel=\"icon\" sizes=\"114x114\" href=\"css/images/touchicon114.png\" />");
|
||||
|
||||
// http://developer.apple.com/library/ios/#DOCUMENTATION/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html
|
||||
sb.Append("<link rel=\"apple-touch-icon\" href=\"css/images/touchicon.png\" />");
|
||||
sb.Append("<link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"css/images/touchicon72.png\" />");
|
||||
|
@ -419,6 +422,7 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
{
|
||||
"scripts/all.js" + versionString,
|
||||
"thirdparty/jstree1.0/jquery.jstree.min.js",
|
||||
"thirdparty/jquery.unveil-custom.js",
|
||||
"https://www.gstatic.com/cv/js/sender/v1/cast_sender.js"
|
||||
};
|
||||
|
||||
|
@ -453,17 +457,17 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false);
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var assembly = GetType().Assembly;
|
||||
using (var stream = assembly.GetManifestResourceStream("MediaBrowser.WebDashboard.ApiClient.js"))
|
||||
|
||||
using (var fs = _fileSystem.GetFileStream(GetDashboardResourcePath("thirdparty/mediabrowser.apiclient.js"), FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
|
||||
{
|
||||
using (var streamReader = new StreamReader(stream))
|
||||
using (var streamReader = new StreamReader(fs))
|
||||
{
|
||||
var text = await streamReader.ReadToEndAsync().ConfigureAwait(false);
|
||||
builder.Append(text);
|
||||
builder.Append(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var file in GetScriptFiles())
|
||||
{
|
||||
var path = GetDashboardResourcePath("scripts/" + file);
|
||||
|
@ -515,6 +519,7 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
|
||||
"mediaplayer.js",
|
||||
"mediaplayer-video.js",
|
||||
"nowplayingbar.js",
|
||||
|
||||
"ratingdialog.js",
|
||||
"aboutpage.js",
|
||||
|
@ -603,6 +608,7 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
"supporterkeypage.js",
|
||||
"supporterpage.js",
|
||||
"episodes.js",
|
||||
"thememediaplayer.js",
|
||||
"tvgenres.js",
|
||||
"tvlatest.js",
|
||||
"tvpeople.js",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user