aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTavares André <tavares_and@hotmail.com>2016-02-22 18:41:38 +0100
committerTavares André <tavares_and@hotmail.com>2016-02-22 18:41:38 +0100
commit15a98c5eaa05dcd377de1778107acc0429457a2c (patch)
tree79f45e422fba7f4d8648f44529a6e4e56d2b8399
parent3eb1f47da05ce7708ee10a65b2b4f2a6de31055a (diff)
parent7f6004d1bc3b06cc20742c0eac82f4fbef1e2a53 (diff)
Merge branch 'dev' of https://github.com/MediaBrowser/Emby into dev
-rw-r--r--MediaBrowser.Api/ConnectService.cs55
-rw-r--r--MediaBrowser.Api/PinLoginService.cs85
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs16
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs4
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs3
-rw-r--r--MediaBrowser.Api/UserService.cs17
-rw-r--r--MediaBrowser.Controller/Connect/IConnectManager.cs20
-rw-r--r--MediaBrowser.Controller/Entities/IHasMediaSources.cs2
-rw-r--r--MediaBrowser.Controller/Entities/UserItemData.cs12
-rw-r--r--MediaBrowser.Controller/LiveTv/ITunerHost.cs3
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs6
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs1
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs7
-rw-r--r--MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs10
-rw-r--r--MediaBrowser.Dlna/Ssdp/SsdpHandler.cs69
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs12
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs145
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs7
-rw-r--r--MediaBrowser.Model/Configuration/UserConfiguration.cs8
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs1
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs5
-rw-r--r--MediaBrowser.Model/Dlna/TranscodingProfile.cs3
-rw-r--r--MediaBrowser.Model/Entities/PersonType.cs4
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvTunerInfoDto.cs6
-rw-r--r--MediaBrowser.Model/MediaInfo/MediaInfo.cs10
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs4
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs12
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectManager.cs145
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs2
-rw-r--r--MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs6
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs9
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs5
-rw-r--r--MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs60
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs4
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs4
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs3
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs20
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs81
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs115
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs199
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs186
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj5
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs25
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs61
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs21
-rw-r--r--MediaBrowser.Server.Implementations/packages.config2
-rw-r--r--MediaBrowser.Server.Startup.Common/ApplicationHost.cs2
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj13
53 files changed, 940 insertions, 565 deletions
diff --git a/MediaBrowser.Api/ConnectService.cs b/MediaBrowser.Api/ConnectService.cs
index bdd2eeaad..4bcd33d9e 100644
--- a/MediaBrowser.Api/ConnectService.cs
+++ b/MediaBrowser.Api/ConnectService.cs
@@ -1,10 +1,8 @@
-using System;
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Connect;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Connect;
-using MediaBrowser.Model.Dto;
using ServiceStack;
using System.Collections.Generic;
using System.Linq;
@@ -75,28 +73,6 @@ namespace MediaBrowser.Api
public string ConnectUserId { get; set; }
}
- [Route("/Connect/Supporters", "GET")]
- [Authenticated(Roles = "Admin")]
- public class GetConnectSupporterSummary : IReturn<ConnectSupporterSummary>
- {
- }
-
- [Route("/Connect/Supporters", "DELETE")]
- [Authenticated(Roles = "Admin")]
- public class RemoveConnectSupporter : IReturnVoid
- {
- [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
- [Route("/Connect/Supporters", "POST")]
- [Authenticated(Roles = "Admin")]
- public class AddConnectSupporter : IReturnVoid
- {
- [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string Id { get; set; }
- }
-
public class ConnectService : BaseApiService
{
private readonly IConnectManager _connectManager;
@@ -108,35 +84,6 @@ namespace MediaBrowser.Api
_userManager = userManager;
}
- public async Task<object> Get(GetConnectSupporterSummary request)
- {
- var result = await _connectManager.GetConnectSupporterSummary().ConfigureAwait(false);
- var existingConnectUserIds = result.Users.Select(i => i.Id).ToList();
-
- result.EligibleUsers = _userManager.Users
- .Where(i => !string.IsNullOrWhiteSpace(i.ConnectUserId))
- .Where(i => !existingConnectUserIds.Contains(i.ConnectUserId, StringComparer.OrdinalIgnoreCase))
- .OrderBy(i => i.Name)
- .Select(i => _userManager.GetUserDto(i))
- .ToList();
-
- return ToOptimizedResult(result);
- }
-
- public void Delete(RemoveConnectSupporter request)
- {
- var task = _connectManager.RemoveConnectSupporter(request.Id);
-
- Task.WaitAll(task);
- }
-
- public void Post(AddConnectSupporter request)
- {
- var task = _connectManager.AddConnectSupporter(request.Id);
-
- Task.WaitAll(task);
- }
-
public object Post(CreateConnectLink request)
{
return _connectManager.LinkUser(request.Id, request.ConnectUsername);
diff --git a/MediaBrowser.Api/PinLoginService.cs b/MediaBrowser.Api/PinLoginService.cs
index 8b63de10a..a4957651f 100644
--- a/MediaBrowser.Api/PinLoginService.cs
+++ b/MediaBrowser.Api/PinLoginService.cs
@@ -1,9 +1,14 @@
using System;
using System.Collections.Concurrent;
using System.Globalization;
+using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Connect;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Session;
using ServiceStack;
namespace MediaBrowser.Api
@@ -13,6 +18,8 @@ namespace MediaBrowser.Api
{
[ApiMember(Name = "DeviceId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string DeviceId { get; set; }
+ [ApiMember(Name = "AppName", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+ public string AppName { get; set; }
}
[Route("/Auth/Pin", "GET", Summary = "Gets pin status")]
@@ -35,7 +42,7 @@ namespace MediaBrowser.Api
[Route("/Auth/Pin/Validate", "POST", Summary = "Validates a pin")]
[Authenticated]
- public class ValidatePinRequest : IReturnVoid
+ public class ValidatePinRequest : IReturn<SessionInfoDto>
{
[ApiMember(Name = "Pin", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string Pin { get; set; }
@@ -43,10 +50,27 @@ namespace MediaBrowser.Api
public class PinLoginService : BaseApiService
{
- private readonly ConcurrentDictionary<string, MyPinStatus> _activeRequests = new ConcurrentDictionary<string, MyPinStatus>(StringComparer.OrdinalIgnoreCase);
+ private static readonly ConcurrentDictionary<string, MyPinStatus> _activeRequests = new ConcurrentDictionary<string, MyPinStatus>(StringComparer.OrdinalIgnoreCase);
+ private readonly ISessionManager _sessionManager;
+ private readonly IUserManager _userManager;
+
+ public PinLoginService(ISessionManager sessionManager, IUserManager userManager)
+ {
+ _sessionManager = sessionManager;
+ _userManager = userManager;
+ }
public object Post(CreatePinRequest request)
{
+ if (string.IsNullOrWhiteSpace(request.DeviceId))
+ {
+ throw new ArgumentNullException("DeviceId");
+ }
+ if (string.IsNullOrWhiteSpace(request.AppName))
+ {
+ throw new ArgumentNullException("AppName");
+ }
+
var pin = GetNewPin();
var value = new MyPinStatus
@@ -55,7 +79,8 @@ namespace MediaBrowser.Api
IsConfirmed = false,
IsExpired = false,
Pin = pin,
- DeviceId = request.DeviceId
+ DeviceId = request.DeviceId,
+ AppName = request.AppName
};
_activeRequests.AddOrUpdate(pin, value, (k, v) => value);
@@ -75,6 +100,7 @@ namespace MediaBrowser.Api
if (!_activeRequests.TryGetValue(request.Pin, out status))
{
+ Logger.Debug("Pin {0} not found.", request.Pin);
throw new ResourceNotFoundException();
}
@@ -88,12 +114,13 @@ namespace MediaBrowser.Api
});
}
- public object Post(ExchangePinRequest request)
+ public async Task<object> Post(ExchangePinRequest request)
{
MyPinStatus status;
if (!_activeRequests.TryGetValue(request.Pin, out status))
{
+ Logger.Debug("Pin {0} not found.", request.Pin);
throw new ResourceNotFoundException();
}
@@ -104,14 +131,24 @@ namespace MediaBrowser.Api
throw new ResourceNotFoundException();
}
- return ToOptimizedResult(new PinExchangeResult
+ var auth = AuthorizationContext.GetAuthorizationInfo(Request);
+ var user = _userManager.GetUserById(status.UserId);
+
+ var result = await _sessionManager.CreateNewSession(new AuthenticationRequest
{
- // TODO: Add access token
- UserId = status.UserId
- });
+ App = auth.Client,
+ AppVersion = auth.Version,
+ DeviceId = auth.DeviceId,
+ DeviceName = auth.Device,
+ RemoteEndPoint = Request.RemoteIp,
+ Username = user.Name
+
+ }).ConfigureAwait(false);
+
+ return ToOptimizedResult(result);
}
- public void Post(ValidatePinRequest request)
+ public object Post(ValidatePinRequest request)
{
MyPinStatus status;
@@ -124,12 +161,18 @@ namespace MediaBrowser.Api
status.IsConfirmed = true;
status.UserId = AuthorizationContext.GetAuthorizationInfo(Request).UserId;
+
+ return ToOptimizedResult(new ValidatePinResult
+ {
+ AppName = status.AppName
+ });
}
private void EnsureValid(string requestedDeviceId, MyPinStatus status)
{
if (!string.Equals(requestedDeviceId, status.DeviceId, StringComparison.OrdinalIgnoreCase))
{
+ Logger.Debug("Pin device Id's do not match. requestedDeviceId: {0}, status.DeviceId: {1}", requestedDeviceId, status.DeviceId);
throw new ResourceNotFoundException();
}
@@ -145,6 +188,7 @@ namespace MediaBrowser.Api
if (status.IsExpired)
{
+ Logger.Debug("Pin {0} is expired", status.Pin);
throw new ResourceNotFoundException();
}
}
@@ -163,16 +207,7 @@ namespace MediaBrowser.Api
private string GetNewPinInternal()
{
- var length = 5;
- var pin = string.Empty;
-
- while (pin.Length < length)
- {
- var digit = new Random().Next(0, 9);
- pin += digit.ToString(CultureInfo.InvariantCulture);
- }
-
- return pin;
+ return new Random().Next(10000, 99999).ToString(CultureInfo.InvariantCulture);
}
private bool IsPinActive(string pin)
@@ -181,15 +216,15 @@ namespace MediaBrowser.Api
if (!_activeRequests.TryGetValue(pin, out status))
{
- return true;
+ return false;
}
if (status.IsExpired)
{
- return true;
+ return false;
}
- return false;
+ return true;
}
public class MyPinStatus : PinStatusResult
@@ -197,6 +232,12 @@ namespace MediaBrowser.Api
public DateTime CreationTimeUtc { get; set; }
public string DeviceId { get; set; }
public string UserId { get; set; }
+ public string AppName { get; set; }
}
}
+
+ public class ValidatePinResult
+ {
+ public string AppName { get; set; }
+ }
}
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index bae8074fd..33995bee3 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -1462,6 +1462,13 @@ namespace MediaBrowser.Api.Playback
{
// Duplicating ItemId because of MediaMonkey
}
+ else if (i == 24)
+ {
+ if (videoRequest != null)
+ {
+ videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+ }
}
}
@@ -2021,6 +2028,11 @@ namespace MediaBrowser.Api.Playback
state.EstimateContentLength = transcodingProfile.EstimateContentLength;
state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
+
+ if (state.VideoRequest != null)
+ {
+ state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
+ }
}
}
}
@@ -2184,9 +2196,9 @@ namespace MediaBrowser.Api.Playback
if (state.VideoRequest != null)
{
- if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.VideoRequest.CopyTimestamps)
{
- //inputModifier += " -noaccurate_seek";
+ inputModifier += " -noaccurate_seek";
}
}
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index eaf65bd6b..50aa2df19 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
@@ -137,9 +137,9 @@ namespace MediaBrowser.Api.Playback.Progressive
var isOutputMkv = string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase);
- if (state.RunTimeTicks.HasValue)
+ if (state.RunTimeTicks.HasValue && state.VideoRequest.CopyTimestamps)
{
- //args += " -copyts -avoid_negative_ts disabled -start_at_zero";
+ args += " -copyts -avoid_negative_ts disabled -start_at_zero";
}
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
index 69f8e6e04..1135a3a54 100644
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ b/MediaBrowser.Api/Playback/StreamRequest.cs
@@ -187,6 +187,9 @@ namespace MediaBrowser.Api.Playback
[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; }
+ [ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+ public bool CopyTimestamps { get; set; }
+
[ApiMember(Name = "Cabac", Description = "Enable if cabac encoding is required", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? Cabac { get; set; }
diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs
index 3996a0311..a35a1c3a2 100644
--- a/MediaBrowser.Api/UserService.cs
+++ b/MediaBrowser.Api/UserService.cs
@@ -415,23 +415,6 @@ namespace MediaBrowser.Api
{
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
- if (string.IsNullOrWhiteSpace(auth.Client))
- {
- auth.Client = "Unknown app";
- }
- if (string.IsNullOrWhiteSpace(auth.Device))
- {
- auth.Device = "Unknown device";
- }
- if (string.IsNullOrWhiteSpace(auth.Version))
- {
- auth.Version = "Unknown version";
- }
- if (string.IsNullOrWhiteSpace(auth.DeviceId))
- {
- auth.DeviceId = "Unknown device id";
- }
-
var result = await _sessionMananger.AuthenticateNewSession(new AuthenticationRequest
{
App = auth.Client,
diff --git a/MediaBrowser.Controller/Connect/IConnectManager.cs b/MediaBrowser.Controller/Connect/IConnectManager.cs
index 1f7652221..e004eaccf 100644
--- a/MediaBrowser.Controller/Connect/IConnectManager.cs
+++ b/MediaBrowser.Controller/Connect/IConnectManager.cs
@@ -76,25 +76,5 @@ namespace MediaBrowser.Controller.Connect
/// <param name="token">The token.</param>
/// <returns><c>true</c> if [is authorization token valid] [the specified token]; otherwise, <c>false</c>.</returns>
bool IsAuthorizationTokenValid(string token);
-
- /// <summary>
- /// Gets the connect supporter summary.
- /// </summary>
- /// <returns>Task&lt;ConnectSupporterSummary&gt;.</returns>
- Task<ConnectSupporterSummary> GetConnectSupporterSummary();
-
- /// <summary>
- /// Removes the connect supporter.
- /// </summary>
- /// <param name="id">The identifier.</param>
- /// <returns>Task.</returns>
- Task RemoveConnectSupporter(string id);
-
- /// <summary>
- /// Adds the connect supporter.
- /// </summary>
- /// <param name="id">The identifier.</param>
- /// <returns>Task.</returns>
- Task AddConnectSupporter(string id);
}
}
diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs
index 85ce3c781..832b9f6c1 100644
--- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs
+++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
- public interface IHasMediaSources : IHasId
+ public interface IHasMediaSources : IHasUserData
{
/// <summary>
/// Gets the media sources.
diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs
index 5f0e62537..16c37e7d3 100644
--- a/MediaBrowser.Controller/Entities/UserItemData.cs
+++ b/MediaBrowser.Controller/Entities/UserItemData.cs
@@ -78,7 +78,17 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <value><c>true</c> if played; otherwise, <c>false</c>.</value>
public bool Played { get; set; }
-
+ /// <summary>
+ /// Gets or sets the index of the audio stream.
+ /// </summary>
+ /// <value>The index of the audio stream.</value>
+ public int? AudioStreamIndex { get; set; }
+ /// <summary>
+ /// Gets or sets the index of the subtitle stream.
+ /// </summary>
+ /// <value>The index of the subtitle stream.</value>
+ public int? SubtitleStreamIndex { get; set; }
+
/// <summary>
/// This is an interpreted property to indicate likes or dislikes
/// This should never be serialized.
diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
index 2e3a71f70..498602ddf 100644
--- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs
+++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
@@ -46,6 +46,9 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;List&lt;MediaSourceInfo&gt;&gt;.</returns>
Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
+ }
+ public interface IConfigurableTunerHost
+ {
/// <summary>
/// Validates the specified information.
/// </summary>
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs
index 46cf4dd98..2b1e2f21d 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvTunerInfo.cs
@@ -59,6 +59,12 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The clients.</value>
public List<string> Clients { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance can reset.
+ /// </summary>
+ /// <value><c>true</c> if this instance can reset; otherwise, <c>false</c>.</value>
+ public bool CanReset { get; set; }
+
public LiveTvTunerInfo()
{
Clients = new List<string>();
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
index bb8841222..f8f4e9ec9 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
@@ -44,6 +44,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public int? CpuCoreLimit { get; set; }
public bool ReadInputAtNativeFramerate { get; set; }
public SubtitleDeliveryMethod SubtitleMethod { get; set; }
+ public bool CopyTimestamps { get; set; }
/// <summary>
/// Gets a value indicating whether this instance has fixed resolution.
diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs
index dc9612c84..fa74c5749 100644
--- a/MediaBrowser.Controller/Session/ISessionManager.cs
+++ b/MediaBrowser.Controller/Session/ISessionManager.cs
@@ -251,6 +251,13 @@ namespace MediaBrowser.Controller.Session
Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request);
/// <summary>
+ /// Creates the new session.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <returns>Task&lt;AuthenticationResult&gt;.</returns>
+ Task<AuthenticationResult> CreateNewSession(AuthenticationRequest request);
+
+ /// <summary>
/// Reports the capabilities.
/// </summary>
/// <param name="sessionId">The session identifier.</param>
diff --git a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
index ff936346c..70f6d0e53 100644
--- a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
@@ -132,6 +132,8 @@ namespace MediaBrowser.Dlna.Ssdp
return;
}
+ _ssdpHandler.LogMessageReceived(args, true);
+
TryCreateDevice(args);
}
}
@@ -219,14 +221,6 @@ namespace MediaBrowser.Dlna.Ssdp
return;
}
- if (_config.GetDlnaConfiguration().EnableDebugLog)
- {
- var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
- var headerText = string.Join(",", headerTexts.ToArray());
-
- _logger.Debug("{0} Device message received from {1}. Headers: {2}", args.Method, args.EndPoint, headerText);
- }
-
EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
}
diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
index c2918aed5..278c34275 100644
--- a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
+++ b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
@@ -93,17 +93,7 @@ namespace MediaBrowser.Dlna.Ssdp
return;
}
- var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
- if (enableDebugLogging)
- {
- var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
- var headerText = string.Join(",", headerTexts.ToArray());
-
- var protocol = isMulticast ? "Multicast" : "Unicast";
- var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
- _logger.Debug("{0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
- }
+ LogMessageReceived(args, isMulticast);
var headers = args.Headers;
string st;
@@ -125,6 +115,21 @@ namespace MediaBrowser.Dlna.Ssdp
EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger);
}
+ internal void LogMessageReceived(SsdpMessageEventArgs args, bool isMulticast)
+ {
+ var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
+
+ if (enableDebugLogging)
+ {
+ var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
+ var headerText = string.Join(",", headerTexts.ToArray());
+
+ var protocol = isMulticast ? "Multicast" : "Unicast";
+ var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
+ _logger.Debug("{0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
+ }
+ }
+
internal bool IgnoreMessage(SsdpMessageEventArgs args, bool isMulticast)
{
string usn;
@@ -139,7 +144,7 @@ namespace MediaBrowser.Dlna.Ssdp
//var protocol = isMulticast ? "Multicast" : "Unicast";
//var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
//_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
-
+
return true;
}
}
@@ -159,7 +164,7 @@ namespace MediaBrowser.Dlna.Ssdp
return true;
}
}
-
+
return false;
}
@@ -298,9 +303,17 @@ namespace MediaBrowser.Dlna.Ssdp
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
- SendDatagram(msg, endpoint, null, false, 1);
- SendDatagram(msg, endpoint, new IPEndPoint(d.Address, 0), false, 1);
- //SendDatagram(header, values, endpoint, null, true);
+ var ipEndPoint = endpoint as IPEndPoint;
+ if (ipEndPoint != null)
+ {
+ SendUnicastRequest(msg, ipEndPoint);
+ }
+ else
+ {
+ SendDatagram(msg, endpoint, null, false, 2);
+ SendDatagram(msg, endpoint, new IPEndPoint(d.Address, 0), false, 2);
+ //SendDatagram(header, values, endpoint, null, true);
+ }
if (enableDebugLogging)
{
@@ -473,6 +486,7 @@ namespace MediaBrowser.Dlna.Ssdp
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
SendDatagram(msg, _ssdpEndp, new IPEndPoint(dev.Address, 0), true);
+ //SendUnicastRequest(msg, 1);
}
public void RegisterNotification(string uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services)
@@ -577,12 +591,12 @@ namespace MediaBrowser.Dlna.Ssdp
}
catch (ObjectDisposedException)
{
-
+
}
}
}
- private async void SendUnicastRequest(string request)
+ private void SendUnicastRequest(string request, int sendCount = 3)
{
if (_unicastClient == null)
{
@@ -591,19 +605,32 @@ namespace MediaBrowser.Dlna.Ssdp
_logger.Debug("Sending unicast search request");
- byte[] req = Encoding.ASCII.GetBytes(request);
var ipSsdp = IPAddress.Parse(SSDPAddr);
var ipTxEnd = new IPEndPoint(ipSsdp, SSDPPort);
+ SendUnicastRequest(request, ipTxEnd, sendCount);
+ }
+
+ private async void SendUnicastRequest(string request, IPEndPoint toEndPoint, int sendCount = 3)
+ {
+ if (_unicastClient == null)
+ {
+ return;
+ }
+
+ _logger.Debug("Sending unicast search request");
+
+ byte[] req = Encoding.ASCII.GetBytes(request);
+
try
{
- for (var i = 0; i < 3; i++)
+ for (var i = 0; i < sendCount; i++)
{
if (i > 0)
{
await Task.Delay(50).ConfigureAwait(false);
}
- _unicastClient.Send(req, req.Length, ipTxEnd);
+ _unicastClient.Send(req, req.Length, toEndPoint);
}
}
catch (Exception ex)
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
index 252386af0..c64b574a9 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
@@ -794,6 +794,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.EstimateContentLength = transcodingProfile.EstimateContentLength;
state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
+
+ state.Options.CopyTimestamps = transcodingProfile.CopyTimestamps;
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 30e50fecd..8d1b4057b 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -129,7 +129,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <param name="request">The request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- public Task<Model.MediaInfo.MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken)
+ public Task<MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken)
{
var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters;
@@ -175,7 +175,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MediaInfoResult}.</returns>
/// <exception cref="System.ApplicationException">ffprobe failed - streams and format are both null.</exception>
- private async Task<Model.MediaInfo.MediaInfo> GetMediaInfoInternal(string inputPath,
+ private async Task<MediaInfo> GetMediaInfoInternal(string inputPath,
string primaryPath,
MediaProtocol protocol,
bool extractChapters,
@@ -934,7 +934,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
_mediaEncoder._runningProcesses.Remove(this);
}
- process.Dispose();
+ try
+ {
+ process.Dispose();
+ }
+ catch (Exception ex)
+ {
+ }
}
private bool _disposed;
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 31f6af181..07f1d4578 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -27,7 +27,7 @@ namespace MediaBrowser.MediaEncoding.Probing
public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType videoType, bool isAudio, string path, MediaProtocol protocol)
{
- var info = new Model.MediaInfo.MediaInfo
+ var info = new MediaInfo
{
Path = path,
Protocol = protocol
@@ -56,40 +56,81 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
- if (isAudio)
- {
- SetAudioRuntimeTicks(data, info);
+ var tags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ var tagStreamType = isAudio ? "audio" : "video";
- var tags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- // tags are normally located under data.format, but we've seen some cases with ogg where they're part of the audio stream
- // so let's create a combined list of both
+ if (data.streams != null)
+ {
+ var tagStream = data.streams.FirstOrDefault(i => string.Equals(i.codec_type, tagStreamType, StringComparison.OrdinalIgnoreCase));
- if (data.streams != null)
+ if (tagStream != null && tagStream.tags != null)
{
- var audioStream = data.streams.FirstOrDefault(i => string.Equals(i.codec_type, "audio", StringComparison.OrdinalIgnoreCase));
-
- if (audioStream != null && audioStream.tags != null)
+ foreach (var pair in tagStream.tags)
{
- foreach (var pair in audioStream.tags)
- {
- tags[pair.Key] = pair.Value;
- }
+ tags[pair.Key] = pair.Value;
}
}
+ }
- if (data.format != null && data.format.tags != null)
+ if (data.format != null && data.format.tags != null)
+ {
+ foreach (var pair in data.format.tags)
{
- foreach (var pair in data.format.tags)
- {
- tags[pair.Key] = pair.Value;
- }
+ tags[pair.Key] = pair.Value;
}
+ }
+
+ FetchGenres(info, tags);
+ var overview = FFProbeHelpers.GetDictionaryValue(tags, "description");
+ if (!string.IsNullOrWhiteSpace(overview))
+ {
+ info.Overview = overview;
+ }
+
+ var title = FFProbeHelpers.GetDictionaryValue(tags, "title");
+ if (!string.IsNullOrWhiteSpace(title))
+ {
+ info.Name = title;
+ }
+
+ info.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
+
+ // Several different forms of retaildate
+ info.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
+ FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
+ FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
+ FFProbeHelpers.GetDictionaryDateTime(tags, "date");
+
+ if (isAudio)
+ {
+ SetAudioRuntimeTicks(data, info);
+
+ // tags are normally located under data.format, but we've seen some cases with ogg where they're part of the audio stream
+ // so let's create a combined list of both
SetAudioInfoFromTags(info, tags);
}
else
{
+ var iTunEXTC = FFProbeHelpers.GetDictionaryValue(tags, "iTunEXTC");
+ if (!string.IsNullOrWhiteSpace(iTunEXTC))
+ {
+ var parts = iTunEXTC.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
+ // Example
+ // mpaa|G|100|For crude humor
+ if (parts.Length == 4)
+ {
+ info.OfficialRating = parts[1];
+ info.OfficialRatingDescription = parts[3];
+ }
+ }
+
+ var itunesXml = FFProbeHelpers.GetDictionaryValue(tags, "iTunMOVI");
+ if (!string.IsNullOrWhiteSpace(itunesXml))
+ {
+ FetchFromItunesInfo(itunesXml, info);
+ }
+
if (data.format != null && !string.IsNullOrEmpty(data.format.duration))
{
info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
@@ -108,6 +149,11 @@ namespace MediaBrowser.MediaEncoding.Probing
return info;
}
+ private void FetchFromItunesInfo(string xml, MediaInfo info)
+ {
+ // <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>cast</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Blender Foundation</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Janus Bager Kristensen</string>\n\t\t</dict>\n\t</array>\n\t<key>directors</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Sacha Goedegebure</string>\n\t\t</dict>\n\t</array>\n\t<key>studio</key>\n\t<string>Blender Foundation</string>\n</dict>\n</plist>\n
+ }
+
/// <summary>
/// Converts ffprobe stream info to our MediaStream class
/// </summary>
@@ -430,16 +476,8 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
- private void SetAudioInfoFromTags(Model.MediaInfo.MediaInfo audio, Dictionary<string, string> tags)
+ private void SetAudioInfoFromTags(MediaInfo audio, Dictionary<string, string> tags)
{
- var title = FFProbeHelpers.GetDictionaryValue(tags, "title");
-
- // Only set Name if title was found in the dictionary
- if (!string.IsNullOrEmpty(title))
- {
- audio.Title = title;
- }
-
var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
if (!string.IsNullOrWhiteSpace(composer))
{
@@ -458,6 +496,26 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
+ var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist");
+
+ if (!string.IsNullOrWhiteSpace(lyricist))
+ {
+ foreach (var person in Split(lyricist, false))
+ {
+ audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist });
+ }
+ }
+ // Check for writer some music is tagged that way as alternative to composer/lyricist
+ var writer = FFProbeHelpers.GetDictionaryValue(tags, "writer");
+
+ if (!string.IsNullOrWhiteSpace(writer))
+ {
+ foreach (var person in Split(writer, false))
+ {
+ audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer });
+ }
+ }
+
audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists");
@@ -511,22 +569,12 @@ namespace MediaBrowser.MediaEncoding.Probing
// Disc number
audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc");
- audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
-
- // Several different forms of retaildate
- audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
- FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
- FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
- FFProbeHelpers.GetDictionaryDateTime(tags, "date");
-
// If we don't have a ProductionYear try and get it from PremiereDate
if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue)
{
audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year;
}
- FetchGenres(audio, tags);
-
// There's several values in tags may or may not be present
FetchStudios(audio, tags, "organization");
FetchStudios(audio, tags, "ensemble");
@@ -693,7 +741,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <param name="info">The information.</param>
/// <param name="tags">The tags.</param>
- private void FetchGenres(Model.MediaInfo.MediaInfo info, Dictionary<string, string> tags)
+ private void FetchGenres(MediaInfo info, Dictionary<string, string> tags)
{
var val = FFProbeHelpers.GetDictionaryValue(tags, "genre");
@@ -764,7 +812,7 @@ namespace MediaBrowser.MediaEncoding.Probing
private const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
- private void FetchWtvInfo(Model.MediaInfo.MediaInfo video, InternalMediaInfoResult data)
+ private void FetchWtvInfo(MediaInfo video, InternalMediaInfoResult data)
{
if (data.format == null || data.format.tags == null)
{
@@ -775,15 +823,16 @@ namespace MediaBrowser.MediaEncoding.Probing
if (!string.IsNullOrWhiteSpace(genres))
{
- //genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre");
- }
-
- if (!string.IsNullOrWhiteSpace(genres))
- {
- video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
+ var genreList = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Trim())
.ToList();
+
+ // If this is empty then don't overwrite genres that might have been fetched earlier
+ if (genreList.Count > 0)
+ {
+ video.Genres = genreList;
+ }
}
var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 3cb543e5d..bc102bd40 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -182,8 +182,6 @@ namespace MediaBrowser.Model.Configuration
public PeopleMetadataOptions PeopleMetadataOptions { get; set; }
public bool FindInternetTrailers { get; set; }
- public string[] InsecureApps9 { get; set; }
-
public bool SaveMetadataHidden { get; set; }
public NameValuePair[] ContentTypes { get; set; }
@@ -256,11 +254,6 @@ namespace MediaBrowser.Model.Configuration
PeopleMetadataOptions = new PeopleMetadataOptions();
- InsecureApps9 = new[]
- {
- "Windows Phone"
- };
-
MetadataOptions = new[]
{
new MetadataOptions(1, 1280) {ItemType = "Book"},
diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs
index d59974a2e..c2f21ed73 100644
--- a/MediaBrowser.Model/Configuration/UserConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs
@@ -48,11 +48,19 @@ namespace MediaBrowser.Model.Configuration
public bool HidePlayedInLatest { get; set; }
public bool DisplayChannelsInline { get; set; }
+ public bool RememberAudioSelections { get; set; }
+ public bool RememberSubtitleSelections { get; set; }
+ public bool EnableEpisodeAutoQueue { get; set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
/// </summary>
public UserConfiguration()
{
+ EnableEpisodeAutoQueue = true;
+ RememberAudioSelections = true;
+ RememberSubtitleSelections = true;
+
HidePlayedInLatest = true;
PlayDefaultAudioTrack = true;
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index c2713a6e7..d4ca379c0 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -425,6 +425,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',')[0];
playlistItem.VideoCodec = transcodingProfile.VideoCodec;
+ playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps;
playlistItem.SubProtocol = transcodingProfile.Protocol;
playlistItem.AudioStreamIndex = audioStreamIndex;
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index f5c54e160..79ee1b5c5 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -32,6 +32,7 @@ namespace MediaBrowser.Model.Dlna
public string VideoProfile { get; set; }
public bool? Cabac { get; set; }
+ public bool CopyTimestamps { get; set; }
public string AudioCodec { get; set; }
public int? AudioStreamIndex { get; set; }
@@ -231,6 +232,8 @@ namespace MediaBrowser.Model.Dlna
{
list.Add(new NameValuePair("ItemId", item.ItemId));
}
+
+ list.Add(new NameValuePair("CopyTimestamps", (item.CopyTimestamps).ToString().ToLower()));
return list;
}
@@ -269,7 +272,7 @@ namespace MediaBrowser.Model.Dlna
// HLS will preserve timestamps so we can just grab the full subtitle stream
long startPositionTicks = StringHelper.EqualsIgnoreCase(SubProtocol, "hls")
? 0
- : (this.PlayMethod == PlayMethod.Transcode ? StartPositionTicks : 0);
+ : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
// First add the selected track
if (SubtitleStreamIndex.HasValue)
diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
index d9963eb75..e59ee6d63 100644
--- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs
+++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
@@ -29,6 +29,9 @@ namespace MediaBrowser.Model.Dlna
[XmlAttribute("transcodeSeekInfo")]
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
+ [XmlAttribute("copyTimestamps")]
+ public bool CopyTimestamps { get; set; }
+
[XmlAttribute("context")]
public EncodingContext Context { get; set; }
diff --git a/MediaBrowser.Model/Entities/PersonType.cs b/MediaBrowser.Model/Entities/PersonType.cs
index bdf846095..bc274972d 100644
--- a/MediaBrowser.Model/Entities/PersonType.cs
+++ b/MediaBrowser.Model/Entities/PersonType.cs
@@ -34,5 +34,9 @@ namespace MediaBrowser.Model.Entities
/// The conductor
/// </summary>
public const string Conductor = "Conductor";
+ /// <summary>
+ /// The lyricist
+ /// </summary>
+ public const string Lyricist = "Lyricist";
}
}
diff --git a/MediaBrowser.Model/LiveTv/LiveTvTunerInfoDto.cs b/MediaBrowser.Model/LiveTv/LiveTvTunerInfoDto.cs
index fcb19427b..9af96df43 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvTunerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvTunerInfoDto.cs
@@ -64,6 +64,12 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The clients.</value>
public List<string> Clients { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance can reset.
+ /// </summary>
+ /// <value><c>true</c> if this instance can reset; otherwise, <c>false</c>.</value>
+ public bool CanReset { get; set; }
+
public LiveTvTunerInfoDto()
{
Clients = new List<string>();
diff --git a/MediaBrowser.Model/MediaInfo/MediaInfo.cs b/MediaBrowser.Model/MediaInfo/MediaInfo.cs
index 21f258693..126710197 100644
--- a/MediaBrowser.Model/MediaInfo/MediaInfo.cs
+++ b/MediaBrowser.Model/MediaInfo/MediaInfo.cs
@@ -10,11 +10,6 @@ namespace MediaBrowser.Model.MediaInfo
public List<ChapterInfo> Chapters { get; set; }
/// <summary>
- /// Gets or sets the title.
- /// </summary>
- /// <value>The title.</value>
- public string Title { get; set; }
- /// <summary>
/// Gets or sets the album.
/// </summary>
/// <value>The album.</value>
@@ -47,6 +42,11 @@ namespace MediaBrowser.Model.MediaInfo
/// <value>The official rating.</value>
public string OfficialRating { get; set; }
/// <summary>
+ /// Gets or sets the official rating description.
+ /// </summary>
+ /// <value>The official rating description.</value>
+ public string OfficialRatingDescription { get; set; }
+ /// <summary>
/// Gets or sets the overview.
/// </summary>
/// <value>The overview.</value>
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
index 4cf507d15..78906fa85 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
@@ -125,9 +125,9 @@ namespace MediaBrowser.Providers.MediaInfo
private async Task FetchDataFromTags(Audio audio, Model.MediaInfo.MediaInfo data)
{
// Only set Name if title was found in the dictionary
- if (!string.IsNullOrEmpty(data.Title))
+ if (!string.IsNullOrEmpty(data.Name))
{
- audio.Name = data.Title;
+ audio.Name = data.Name;
}
if (!audio.LockedFields.Contains(MetadataFields.Cast))
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index ebbc045ab..fb8e25892 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -383,6 +383,11 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
+ if (!string.IsNullOrWhiteSpace(data.OfficialRatingDescription) || isFullRefresh)
+ {
+ video.OfficialRatingDescription = data.OfficialRatingDescription;
+ }
+
if (!video.LockedFields.Contains(MetadataFields.Genres))
{
if (video.Genres.Count == 0 || isFullRefresh)
@@ -437,6 +442,13 @@ namespace MediaBrowser.Providers.MediaInfo
video.ParentIndexNumber = data.ParentIndexNumber;
}
}
+ if (!string.IsNullOrWhiteSpace(data.Name))
+ {
+ if (string.IsNullOrWhiteSpace(video.Name) || string.Equals(video.Name, Path.GetFileNameWithoutExtension(video.Path), StringComparison.OrdinalIgnoreCase))
+ {
+ video.Name = data.Name;
+ }
+ }
// If we don't have a ProductionYear try and get it from PremiereDate
if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue)
diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
index fdc7e9ee2..d7477225c 100644
--- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
+++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
@@ -10,7 +10,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
@@ -24,7 +23,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
-using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Connect
{
@@ -121,7 +119,6 @@ namespace MediaBrowser.Server.Implementations.Connect
_securityManager = securityManager;
_fileSystem = fileSystem;
- _userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated;
_config.ConfigurationUpdated += _config_ConfigurationUpdated;
LoadCachedData();
@@ -1071,90 +1068,6 @@ namespace MediaBrowser.Server.Implementations.Connect
}
}
- public async Task<ConnectSupporterSummary> GetConnectSupporterSummary()
- {
- var url = GetConnectUrl("keyAssociation");
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None
- };
-
- var postData = new Dictionary<string, string>
- {
- {"serverId", ConnectServerId},
- {"supporterKey", _securityManager.SupporterKey}
- };
-
- options.SetPostData(postData);
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- // No need to examine the response
- using (var stream = (await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)).Content)
- {
- return _json.DeserializeFromStream<ConnectSupporterSummary>(stream);
- }
- }
-
- public async Task AddConnectSupporter(string id)
- {
- var url = GetConnectUrl("keyAssociation");
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None
- };
-
- var postData = new Dictionary<string, string>
- {
- {"serverId", ConnectServerId},
- {"supporterKey", _securityManager.SupporterKey},
- {"userId", id}
- };
-
- options.SetPostData(postData);
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- // No need to examine the response
- using (var stream = (await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)).Content)
- {
- }
- }
-
- public async Task RemoveConnectSupporter(string id)
- {
- var url = GetConnectUrl("keyAssociation");
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None
- };
-
- var postData = new Dictionary<string, string>
- {
- {"serverId", ConnectServerId},
- {"supporterKey", _securityManager.SupporterKey},
- {"userId", id}
- };
-
- options.SetPostData(postData);
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- // No need to examine the response
- using (var stream = (await _httpClient.SendAsync(options, "DELETE").ConfigureAwait(false)).Content)
- {
- }
- }
-
public async Task Authenticate(string username, string passwordMd5)
{
if (string.IsNullOrWhiteSpace(username))
@@ -1186,64 +1099,6 @@ namespace MediaBrowser.Server.Implementations.Connect
}
}
- async void _userManager_UserConfigurationUpdated(object sender, GenericEventArgs<User> e)
- {
- var user = e.Argument;
-
- await TryUploadUserPreferences(user, CancellationToken.None).ConfigureAwait(false);
- }
-
- private async Task TryUploadUserPreferences(User user, CancellationToken cancellationToken)
- {
- if (user == null)
- {
- throw new ArgumentNullException("user");
- }
-
- if (string.IsNullOrEmpty(user.ConnectUserId))
- {
- return;
- }
- if (string.IsNullOrEmpty(ConnectAccessKey))
- {
- return;
- }
-
- var url = GetConnectUrl("user/preferences");
- url += "?userId=" + user.ConnectUserId;
- url += "&key=userpreferences";
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken
- };
-
- var postData = new Dictionary<string, string>();
- postData["data"] = _json.SerializeToString(ConnectUserPreferences.FromUserConfiguration(user.Configuration));
- options.SetPostData(postData);
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- try
- {
- // No need to examine the response
- using (var stream = (await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)).Content)
- {
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error uploading user preferences", ex);
- }
- }
-
- private async Task DownloadUserPreferences(User user, CancellationToken cancellationToken)
- {
-
- }
-
public async Task<User> GetLocalUser(string connectUserId)
{
var user = _userManager.Users
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index 590c5fd3f..07686e91c 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -639,6 +639,8 @@ namespace MediaBrowser.Server.Implementations.Dto
private IEnumerable<string> GetCacheTags(BaseItem item, ImageType type, int limit)
{
return item.GetImages(type)
+ // Convert to a list now in case GetImageCacheTag is slow
+ .ToList()
.Select(p => GetImageCacheTag(item, p))
.Where(i => i != null)
.Take(limit)
diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
index 9d43dabcd..51642cf5d 100644
--- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
+++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
@@ -502,7 +502,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
}
}
- return series ?? new Series();
+ return series;
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
index e69ff367a..c284007f7 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -348,6 +348,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer
return Task.FromResult(true);
}
+ if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase))
+ {
+ httpRes.RedirectToUrl("web/pin.html");
+ return Task.FromResult(true);
+ }
+
if (!string.IsNullOrWhiteSpace(GlobalResponse))
{
httpRes.StatusCode = 503;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
index f6b14fcab..d8f7d889c 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
@@ -134,20 +134,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues)
{
- if (!_config.Configuration.IsStartupWizardCompleted &&
- authAttribtues.AllowBeforeStartupWizard)
+ if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard)
{
return true;
}
- return _config.Configuration.InsecureApps9.Contains(auth.Client ?? string.Empty,
- StringComparer.OrdinalIgnoreCase);
+ return false;
}
private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, AuthenticationInfo tokenInfo)
{
- if (!_config.Configuration.IsStartupWizardCompleted &&
- authAttribtues.AllowBeforeStartupWizard)
+ if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard)
{
return true;
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
index 75d54a80a..357f5c976 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
@@ -45,7 +45,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
{
var auth = GetAuthorizationDictionary(httpReq);
- string userId = null;
string deviceId = null;
string device = null;
string client = null;
@@ -53,9 +52,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
if (auth != null)
{
- // TODO: Remove this
- auth.TryGetValue("UserId", out userId);
-
auth.TryGetValue("DeviceId", out deviceId);
auth.TryGetValue("Device", out device);
auth.TryGetValue("Client", out client);
@@ -78,7 +74,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
Client = client,
Device = device,
DeviceId = deviceId,
- UserId = userId,
Version = version,
Token = token
};
diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
index b132eedec..e4a085f42 100644
--- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
@@ -30,8 +30,9 @@ namespace MediaBrowser.Server.Implementations.Library
private IMediaSourceProvider[] _providers;
private readonly ILogger _logger;
+ private readonly IUserDataManager _userDataManager;
- public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem)
+ public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IUserDataManager userDataManager)
{
_itemRepo = itemRepo;
_userManager = userManager;
@@ -39,6 +40,7 @@ namespace MediaBrowser.Server.Implementations.Library
_logger = logger;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
+ _userDataManager = userDataManager;
}
public void AddParts(IEnumerable<IMediaSourceProvider> providers)
@@ -140,7 +142,7 @@ namespace MediaBrowser.Server.Implementations.Library
{
if (user != null)
{
- SetUserProperties(source, user);
+ SetUserProperties(hasMediaSources, source, user);
}
if (source.Protocol == MediaProtocol.File)
{
@@ -257,25 +259,38 @@ namespace MediaBrowser.Server.Implementations.Library
{
foreach (var source in sources)
{
- SetUserProperties(source, user);
+ SetUserProperties(item, source, user);
}
}
return sources;
}
- private void SetUserProperties(MediaSourceInfo source, User user)
+ private void SetUserProperties(IHasUserData item, MediaSourceInfo source, User user)
{
- var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
- ? new string[] { }
- : new[] { user.Configuration.AudioLanguagePreference };
+ var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+
+ SetDefaultAudioStreamIndex(source, userData, user);
+ SetDefaultSubtitleStreamIndex(source, userData, user);
+ }
+ private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user)
+ {
+ if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections)
+ {
+ var index = userData.SubtitleStreamIndex.Value;
+ // Make sure the saved index is still valid
+ if (index == -1 || source.MediaStreams.Any(i => i.Type == MediaStreamType.Subtitle && i.Index == index))
+ {
+ source.DefaultSubtitleStreamIndex = index;
+ return;
+ }
+ }
+
var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
? new List<string> { }
: new List<string> { user.Configuration.SubtitleLanguagePreference };
- source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
-
var defaultAudioIndex = source.DefaultAudioStreamIndex;
var audioLangage = defaultAudioIndex == null
? null
@@ -290,6 +305,26 @@ namespace MediaBrowser.Server.Implementations.Library
user.Configuration.SubtitleMode, audioLangage);
}
+ private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user)
+ {
+ if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections)
+ {
+ var index = userData.AudioStreamIndex.Value;
+ // Make sure the saved index is still valid
+ if (source.MediaStreams.Any(i => i.Type == MediaStreamType.Audio && i.Index == index))
+ {
+ source.DefaultAudioStreamIndex = index;
+ return;
+ }
+ }
+
+ var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
+ ? new string[] { }
+ : new[] { user.Configuration.AudioLanguagePreference };
+
+ source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
+ }
+
private IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
{
return sources.OrderBy(i =>
@@ -349,11 +384,14 @@ namespace MediaBrowser.Server.Implementations.Library
var json = _jsonSerializer.SerializeToString(mediaSource);
_logger.Debug("Live stream opened: " + json);
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
-
+
if (!string.IsNullOrWhiteSpace(request.UserId))
{
var user = _userManager.GetUserById(request.UserId);
- SetUserProperties(clone, user);
+ var item = string.IsNullOrWhiteSpace(request.ItemId)
+ ? null
+ : _libraryManager.GetItemById(request.ItemId);
+ SetUserProperties(item, clone, user);
}
return new LiveStreamResponse
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 9e4cb66a8..6071fd18b 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -771,6 +771,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
recordPath = Path.ChangeExtension(recordPath, ".mp4");
}
+ _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
+
recording.Path = recordPath;
recording.Status = RecordingStatus.InProgress;
recording.DateLastUpdated = DateTime.UtcNow;
@@ -801,6 +803,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
result.Item2.Release();
}
+
+ _libraryMonitor.ReportFileSystemChangeComplete(recordPath, false);
}
}
catch (OperationCanceledException)
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 5f4d32732..ac5cda95e 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -116,7 +116,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
videoArgs = "-codec:v:0 copy";
}
- var commandLineArgs = "-fflags +genpts -i \"{0}\" -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
+ var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -i \"{0}\" -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
if (mediaSource.ReadAtNativeFramerate)
{
@@ -143,7 +143,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
audioChannels = audioStream.Channels ?? audioChannels;
}
- return "-codec:a:0 aac -strict experimental -ab 320000 -ac " + audioChannels.ToString(CultureInfo.InvariantCulture);
+ return "-codec:a:0 aac -strict experimental -ab 320000";
}
private bool EncodeVideo(MediaSourceInfo mediaSource)
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 04f99cdce..81ad6a387 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -178,7 +178,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
SourceType = info.SourceType,
Status = info.Status,
ChannelName = channelName,
- Url = info.Url
+ Url = info.Url,
+ CanReset = info.CanReset
};
if (!string.IsNullOrEmpty(info.ChannelId))
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index 14bfcba27..cd21dc21a 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -801,11 +801,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
if (!string.IsNullOrWhiteSpace(info.ImagePath))
{
- item.SetImagePath(ImageType.Primary, info.ImagePath);
+ item.SetImage(new ItemImageInfo
+ {
+ Path = info.ImagePath,
+ Type = ImageType.Primary,
+ IsPlaceholder = true
+ }, 0);
}
else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
{
- item.SetImagePath(ImageType.Primary, info.ImageUrl);
+ item.SetImage(new ItemImageInfo
+ {
+ Path = info.ImageUrl,
+ Type = ImageType.Primary,
+ IsPlaceholder = true
+ }, 0);
}
}
@@ -2343,7 +2353,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
throw new ResourceNotFoundException();
}
- await provider.Validate(info).ConfigureAwait(false);
+ var configurable = provider as IConfigurableTunerHost;
+ if (configurable != null)
+ {
+ await configurable.Validate(info).ConfigureAwait(false);
+ }
var config = GetConfiguration();
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 4ebc173b5..fb27631e5 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -64,7 +64,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return list;
}
- private List<TunerHostInfo> GetTunerHosts()
+ protected virtual List<TunerHostInfo> GetTunerHosts()
{
return GetConfiguration().TunerHosts
.Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 0671a9b56..013dabe26 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -20,7 +20,7 @@ using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
- public class HdHomerunHost : BaseTunerHost, ITunerHost
+ public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
private readonly IHttpClient _httpClient;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index f87d4f43f..17e52fb8e 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -8,19 +8,17 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
-using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
- public class M3UTunerHost : BaseTunerHost, ITunerHost
+ public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient;
@@ -46,65 +44,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
{
- var urlHash = info.Url.GetMD5().ToString("N");
-
- // Read the file and display it line by line.
- using (var reader = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false)))
- {
- return GetChannels(reader, urlHash);
- }
- }
-
- private List<M3UChannel> GetChannels(StreamReader reader, string urlHash)
- {
- var channels = new List<M3UChannel>();
-
- string channnelName = null;
- string channelNumber = null;
- string line;
-
- while ((line = reader.ReadLine()) != null)
- {
- line = line.Trim();
- if (string.IsNullOrWhiteSpace(line))
- {
- continue;
- }
-
- if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- if (line.StartsWith("#EXTINF:", StringComparison.OrdinalIgnoreCase))
- {
- line = line.Substring(8);
- Logger.Info("Found m3u channel: {0}", line);
- var parts = line.Split(new[] { ',' }, 2);
- channelNumber = parts[0];
- channnelName = parts[1];
- }
- else if (!string.IsNullOrWhiteSpace(channelNumber))
- {
- channels.Add(new M3UChannel
- {
- Name = channnelName,
- Number = channelNumber,
- Id = ChannelIdPrefix + urlHash + line.GetMD5().ToString("N"),
- Path = line
- });
-
- channelNumber = null;
- channnelName = null;
- }
- }
- return channels;
+ return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(info.Url, ChannelIdPrefix, cancellationToken).ConfigureAwait(false);
}
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
{
- var list = GetConfiguration().TunerHosts
- .Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
+ var list = GetTunerHosts()
.Select(i => new LiveTvTunerInfo()
{
Name = Name,
@@ -125,18 +70,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return sources.First();
}
- class M3UChannel : ChannelInfo
- {
- public string Path { get; set; }
-
- public M3UChannel()
- {
- }
- }
-
public async Task Validate(TunerHostInfo info)
{
- using (var stream = await GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
+ using (var stream = await new M3uParser(Logger, _fileSystem, _httpClient).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
{
}
@@ -147,15 +83,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
- private Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
- {
- if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
- {
- return _httpClient.Get(info.Url, cancellationToken);
- }
- return Task.FromResult(_fileSystem.OpenRead(info.Url));
- }
-
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
{
var urlHash = info.Url.GetMD5().ToString("N");
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
new file mode 100644
index 000000000..9ec5809a9
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using CommonIO;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
+{
+ public class M3uParser
+ {
+ private readonly ILogger _logger;
+ private readonly IFileSystem _fileSystem;
+ private readonly IHttpClient _httpClient;
+
+ public M3uParser(ILogger logger, IFileSystem fileSystem, IHttpClient httpClient)
+ {
+ _logger = logger;
+ _fileSystem = fileSystem;
+ _httpClient = httpClient;
+ }
+
+ public async Task<List<M3UChannel>> Parse(string url, string channelIdPrefix, CancellationToken cancellationToken)
+ {
+ var urlHash = url.GetMD5().ToString("N");
+
+ // Read the file and display it line by line.
+ using (var reader = new StreamReader(await GetListingsStream(url, cancellationToken).ConfigureAwait(false)))
+ {
+ return GetChannels(reader, urlHash, channelIdPrefix);
+ }
+ }
+
+ public Task<Stream> GetListingsStream(string url, CancellationToken cancellationToken)
+ {
+ if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ {
+ return _httpClient.Get(url, cancellationToken);
+ }
+ return Task.FromResult(_fileSystem.OpenRead(url));
+ }
+
+ private List<M3UChannel> GetChannels(StreamReader reader, string urlHash, string channelIdPrefix)
+ {
+ var channels = new List<M3UChannel>();
+
+ string channnelName = null;
+ string channelNumber = null;
+ string line;
+ string imageUrl = null;
+ while ((line = reader.ReadLine()) != null)
+ {
+ line = line.Trim();
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
+
+ if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ if (line.StartsWith("#EXTINF:", StringComparison.OrdinalIgnoreCase))
+ {
+ line = line.Substring(8);
+ _logger.Info("Found m3u channel: {0}", line);
+ var parts = line.Split(new[] { ',' }, 2);
+ channelNumber = parts[0].Trim().Split(' ')[0] ?? "0";
+ channnelName = FindProperty("tvg-name", line, parts[1]);
+ imageUrl = FindProperty("tvg-logo", line, null);
+ }
+ else if (!string.IsNullOrWhiteSpace(channelNumber))
+ {
+ channels.Add(new M3UChannel
+ {
+ Name = channnelName,
+ Number = channelNumber,
+ Id = channelIdPrefix + urlHash + line.GetMD5().ToString("N"),
+ ImageUrl = imageUrl
+ });
+
+ imageUrl = null;
+ channelNumber = null;
+ channnelName = null;
+ }
+ }
+ return channels;
+ }
+ public string FindProperty(string property, string properties, string defaultResult = "")
+ {
+ var reg = new Regex(@"([a-z0-9\-_]+)=\""([^""]+)\""", RegexOptions.IgnoreCase);
+ var matches = reg.Matches(properties);
+ foreach (Match match in matches)
+ {
+ if (match.Groups[1].Value == property)
+ {
+ return match.Groups[2].Value;
+ }
+ }
+ return defaultResult;
+ }
+ }
+
+
+ public class M3UChannel : ChannelInfo
+ {
+ public string Path { get; set; }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
index 08c42bb46..6781e498a 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
@@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using System.Xml;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@@ -13,6 +17,7 @@ using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
@@ -24,14 +29,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
private readonly ILiveTvManager _liveTvManager;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IHttpClient _httpClient;
+ private readonly IJsonSerializer _json;
- public SatIpDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient)
+ public static SatIpDiscovery Current;
+
+ private readonly List<TunerHostInfo> _discoveredHosts = new List<TunerHostInfo>();
+
+ public List<TunerHostInfo> DiscoveredHosts
+ {
+ get { return _discoveredHosts.ToList(); }
+ }
+
+ public SatIpDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient, IJsonSerializer json)
{
_deviceDiscovery = deviceDiscovery;
_config = config;
_logger = logger;
_liveTvManager = liveTvManager;
_httpClient = httpClient;
+ _json = json;
+ Current = this;
}
public void Run()
@@ -42,7 +59,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
{
string st = null;
- if (e.Headers.TryGetValue("ST", out st) && string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
+ string nt = null;
+ e.Headers.TryGetValue("ST", out st);
+ e.Headers.TryGetValue("NT", out nt);
+
+ if (string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(nt, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
{
string location;
if (e.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
@@ -61,26 +83,23 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
try
{
- var options = GetConfiguration();
-
- //if (options.TunerHosts.Any(i =>
- // string.Equals(i.Type, SatIpHost.DeviceType, StringComparison.OrdinalIgnoreCase) &&
- // UriEquals(i.Url, url)))
- //{
- // return;
- //}
+ if (_discoveredHosts.Any(i => string.Equals(i.Type, SatIpHost.DeviceType, StringComparison.OrdinalIgnoreCase) && string.Equals(location, i.Url, StringComparison.OrdinalIgnoreCase)))
+ {
+ return;
+ }
- //// Strip off the port
- //url = new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped).TrimEnd('/');
+ _logger.Debug("Will attempt to add SAT device {0}", location);
+ var info = await GetInfo(location, CancellationToken.None).ConfigureAwait(false);
- //await TestUrl(url).ConfigureAwait(false);
+ _discoveredHosts.Add(info);
+ }
+ catch (OperationCanceledException)
+ {
- //await _liveTvManager.SaveTunerHost(new TunerHostInfo
- //{
- // Type = SatIpHost.DeviceType,
- // Url = url
+ }
+ catch (NotImplementedException)
+ {
- //}).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -92,43 +111,141 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
}
}
- private async Task TestUrl(string url)
+ public void Dispose()
{
- // Test it by pulling down the lineup
- using (await _httpClient.Get(new HttpRequestOptions
+ }
+
+ public async Task<SatIpTunerHostInfo> GetInfo(string url, CancellationToken cancellationToken)
+ {
+ var result = new SatIpTunerHostInfo
{
- Url = string.Format("{0}/lineup.json", url),
- CancellationToken = CancellationToken.None
- }))
+ Url = url,
+ IsEnabled = true,
+ Type = SatIpHost.DeviceType,
+ Tuners = 1,
+ TunersAvailable = 1
+ };
+
+ using (var stream = await _httpClient.Get(url, cancellationToken).ConfigureAwait(false))
+ {
+ using (var streamReader = new StreamReader(stream))
+ {
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader))
+ {
+ reader.MoveToContent();
+
+ // Loop through each element
+ while (reader.Read())
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "device":
+ using (var subtree = reader.ReadSubtree())
+ {
+ FillFromDeviceNode(result, subtree);
+ }
+ break;
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (string.IsNullOrWhiteSpace(result.Id))
{
+ throw new NotImplementedException();
}
- }
- private bool UriEquals(string savedUri, string location)
- {
- return string.Equals(NormalizeUrl(location), NormalizeUrl(savedUri), StringComparison.OrdinalIgnoreCase);
- }
+ // Device hasn't implemented an m3u list
+ if (string.IsNullOrWhiteSpace(result.M3UUrl))
+ {
+ result.IsEnabled = false;
+ }
- private string NormalizeUrl(string url)
- {
- if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ else if (!result.M3UUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
- url = "http://" + url;
+ var fullM3uUrl = url.Substring(0, url.LastIndexOf('/'));
+ result.M3UUrl = fullM3uUrl + "/" + result.M3UUrl.TrimStart('/');
}
- url = url.TrimEnd('/');
+ _logger.Debug("SAT device result: {0}", _json.SerializeToString(result));
- // Strip off the port
- return new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped);
+ return result;
}
- private LiveTvOptions GetConfiguration()
+ private void FillFromDeviceNode(SatIpTunerHostInfo info, XmlReader reader)
{
- return _config.GetConfiguration<LiveTvOptions>("livetv");
- }
+ reader.MoveToContent();
- public void Dispose()
- {
+ while (reader.Read())
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.LocalName)
+ {
+ case "UDN":
+ {
+ info.Id = reader.ReadElementContentAsString();
+ break;
+ }
+
+ case "friendlyName":
+ {
+ info.FriendlyName = reader.ReadElementContentAsString();
+ break;
+ }
+
+ case "satip:X_SATIPCAP":
+ case "X_SATIPCAP":
+ {
+ // <satip:X_SATIPCAP xmlns:satip="urn:ses-com:satip">DVBS2-2</satip:X_SATIPCAP>
+ var value = reader.ReadElementContentAsString() ?? string.Empty;
+ var parts = value.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length == 2)
+ {
+ int intValue;
+ if (int.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out intValue))
+ {
+ info.TunersAvailable = intValue;
+ }
+
+ if (int.TryParse(parts[0].Substring(parts[0].Length - 1), NumberStyles.Any, CultureInfo.InvariantCulture, out intValue))
+ {
+ info.Tuners = intValue;
+ }
+ }
+ break;
+ }
+
+ case "satip:X_SATIPM3U":
+ case "X_SATIPM3U":
+ {
+ // <satip:X_SATIPM3U xmlns:satip="urn:ses-com:satip">/channellist.lua?select=m3u</satip:X_SATIPM3U>
+ info.M3UUrl = reader.ReadElementContentAsString();
+ break;
+ }
+
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ }
}
}
+
+ public class SatIpTunerHostInfo : TunerHostInfo
+ {
+ public int Tuners { get; set; }
+ public int TunersAvailable { get; set; }
+ public string M3UUrl { get; set; }
+ public string FriendlyName { get; set; }
+ }
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
index 205cdf74e..976041bcc 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
@@ -1,45 +1,171 @@
-namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using CommonIO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Serialization;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
- public class SatIpHost /*: BaseTunerHost*/
+ public class SatIpHost : BaseTunerHost, ITunerHost
{
- //public SatIpHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder)
- // : base(config, logger, jsonSerializer, mediaEncoder)
- //{
- //}
+ private readonly IFileSystem _fileSystem;
+ private readonly IHttpClient _httpClient;
+
+ public SatIpHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient)
+ : base(config, logger, jsonSerializer, mediaEncoder)
+ {
+ _fileSystem = fileSystem;
+ _httpClient = httpClient;
+ }
+
+ private const string ChannelIdPrefix = "sat_";
+
+ protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
+ {
+ var satInfo = (SatIpTunerHostInfo) tuner;
- //protected override Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
- //{
- // throw new NotImplementedException();
- //}
+ return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(satInfo.M3UUrl, ChannelIdPrefix, cancellationToken).ConfigureAwait(false);
+ }
public static string DeviceType
{
get { return "satip"; }
}
- //public override string Type
- //{
- // get { return DeviceType; }
- //}
+ public override string Type
+ {
+ get { return DeviceType; }
+ }
+
+ protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
+ {
+ var urlHash = tuner.Url.GetMD5().ToString("N");
+ var prefix = ChannelIdPrefix + urlHash;
+ if (!channelId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
+ var channels = await GetChannels(tuner, true, cancellationToken).ConfigureAwait(false);
+ var m3uchannels = channels.Cast<M3UChannel>();
+ var channel = m3uchannels.FirstOrDefault(c => string.Equals(c.Id, channelId, StringComparison.OrdinalIgnoreCase));
+ if (channel != null)
+ {
+ var path = channel.Path;
+ MediaProtocol protocol = MediaProtocol.File;
+ if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ {
+ protocol = MediaProtocol.Http;
+ }
+ else if (path.StartsWith("rtmp", StringComparison.OrdinalIgnoreCase))
+ {
+ protocol = MediaProtocol.Rtmp;
+ }
+ else if (path.StartsWith("rtsp", StringComparison.OrdinalIgnoreCase))
+ {
+ protocol = MediaProtocol.Rtsp;
+ }
+
+ var mediaSource = new MediaSourceInfo
+ {
+ Path = channel.Path,
+ Protocol = protocol,
+ MediaStreams = new List<MediaStream>
+ {
+ new MediaStream
+ {
+ Type = MediaStreamType.Video,
+ // Set the index to -1 because we don't know the exact index of the video stream within the container
+ Index = -1,
+ IsInterlaced = true
+ },
+ new MediaStream
+ {
+ Type = MediaStreamType.Audio,
+ // Set the index to -1 because we don't know the exact index of the audio stream within the container
+ Index = -1
+
+ }
+ },
+ RequiresOpening = false,
+ RequiresClosing = false
+ };
+
+ return new List<MediaSourceInfo> { mediaSource };
+ }
+ return new List<MediaSourceInfo> { };
+ }
+
+ protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken)
+ {
+ var sources = await GetChannelStreamMediaSources(tuner, channelId, cancellationToken).ConfigureAwait(false);
+
+ return sources.First();
+ }
+
+ protected override async Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
+ {
+ var updatedInfo = await SatIpDiscovery.Current.GetInfo(tuner.Url, cancellationToken).ConfigureAwait(false);
- //protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
- //{
- // throw new NotImplementedException();
- //}
+ return updatedInfo.TunersAvailable > 0;
+ }
+
+ protected override bool IsValidChannelId(string channelId)
+ {
+ return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
+ }
- //protected override Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken)
- //{
- // throw new NotImplementedException();
- //}
+ protected override List<TunerHostInfo> GetTunerHosts()
+ {
+ return SatIpDiscovery.Current.DiscoveredHosts;
+ }
- //protected override Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
- //{
- // throw new NotImplementedException();
- //}
+ public string Name
+ {
+ get { return "Sat IP"; }
+ }
- //protected override bool IsValidChannelId(string channelId)
- //{
- // throw new NotImplementedException();
- //}
+ public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
+ {
+ var list = GetTunerHosts()
+ .SelectMany(i => GetTunerInfos(i, cancellationToken))
+ .ToList();
+
+ return Task.FromResult(list);
+ }
+
+ public List<LiveTvTunerInfo> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
+ {
+ var satInfo = (SatIpTunerHostInfo) info;
+
+ var list = new List<LiveTvTunerInfo>();
+
+ for (var i = 0; i < satInfo.Tuners; i++)
+ {
+ list.Add(new LiveTvTunerInfo
+ {
+ Name = satInfo.FriendlyName ?? Name,
+ SourceType = Type,
+ Status = LiveTvTunerStatus.Available,
+ Id = info.Url.GetMD5().ToString("N") + i.ToString(CultureInfo.InvariantCulture),
+ Url = info.Url
+ });
+ }
+
+ return list;
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 5f2fab457..21066a9f4 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -52,9 +52,9 @@
<Reference Include="Interfaces.IO">
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
</Reference>
- <Reference Include="MediaBrowser.Naming, Version=1.0.5884.23751, Culture=neutral, processorArchitecture=MSIL">
+ <Reference Include="MediaBrowser.Naming, Version=1.0.5891.29179, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\MediaBrowser.Naming.1.0.0.47\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
+ <HintPath>..\packages\MediaBrowser.Naming.1.0.0.48\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
</Reference>
<Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
@@ -234,6 +234,7 @@
<Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" />
+ <Compile Include="LiveTv\TunerHosts\M3uParser.cs" />
<Compile Include="LiveTv\TunerHosts\M3UTunerHost.cs" />
<Compile Include="LiveTv\ProgramImageProvider.cs" />
<Compile Include="LiveTv\RecordingImageProvider.cs" />
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
index a85872951..697ec2271 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
@@ -1014,7 +1014,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
if (!reader.IsDBNull(31))
{
- item.OfficialRating = reader.GetString(31);
+ item.OfficialRatingDescription = reader.GetString(31);
}
if (!reader.IsDBNull(32))
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
index 8b86d19a2..63c41c71f 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
@@ -56,6 +56,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
};
_connection.RunQueries(queries, Logger);
+
+ _connection.AddColumn(Logger, "userdata", "AudioStreamIndex", "int");
+ _connection.AddColumn(Logger, "userdata", "SubtitleStreamIndex", "int");
}
/// <summary>
@@ -127,7 +130,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate)";
+ cmd.CommandText = "replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)";
cmd.Parameters.Add(cmd, "@key", DbType.String).Value = key;
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@@ -137,6 +140,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@isFavorite", DbType.Boolean).Value = userData.IsFavorite;
cmd.Parameters.Add(cmd, "@playbackPositionTicks", DbType.Int64).Value = userData.PlaybackPositionTicks;
cmd.Parameters.Add(cmd, "@lastPlayedDate", DbType.DateTime).Value = userData.LastPlayedDate;
+ cmd.Parameters.Add(cmd, "@AudioStreamIndex", DbType.Int32).Value = userData.AudioStreamIndex;
+ cmd.Parameters.Add(cmd, "@SubtitleStreamIndex", DbType.Int32).Value = userData.SubtitleStreamIndex;
cmd.Transaction = transaction;
@@ -199,7 +204,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate)";
+ cmd.CommandText = "replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)";
cmd.Parameters.Add(cmd, "@key", DbType.String).Value = userItemData.Key;
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@@ -209,6 +214,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@isFavorite", DbType.Boolean).Value = userItemData.IsFavorite;
cmd.Parameters.Add(cmd, "@playbackPositionTicks", DbType.Int64).Value = userItemData.PlaybackPositionTicks;
cmd.Parameters.Add(cmd, "@lastPlayedDate", DbType.DateTime).Value = userItemData.LastPlayedDate;
+ cmd.Parameters.Add(cmd, "@AudioStreamIndex", DbType.Int32).Value = userItemData.AudioStreamIndex;
+ cmd.Parameters.Add(cmd, "@SubtitleStreamIndex", DbType.Int32).Value = userItemData.SubtitleStreamIndex;
cmd.Transaction = transaction;
@@ -275,7 +282,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate from userdata where key = @key and userId=@userId";
+ cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where key = @key and userId=@userId";
cmd.Parameters.Add(cmd, "@key", DbType.String).Value = key;
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@@ -310,7 +317,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate from userdata where userId=@userId";
+ cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where userId=@userId";
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@@ -350,6 +357,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
userData.LastPlayedDate = reader.GetDateTime(7).ToUniversalTime();
}
+ if (!reader.IsDBNull(8))
+ {
+ userData.AudioStreamIndex = reader.GetInt32(8);
+ }
+
+ if (!reader.IsDBNull(9))
+ {
+ userData.SubtitleStreamIndex = reader.GetInt32(9);
+ }
+
return userData;
}
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index a9ce5ba54..1074796c0 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
@@ -680,7 +680,7 @@ namespace MediaBrowser.Server.Implementations.Session
foreach (var user in users)
{
- await OnPlaybackProgress(user.Id, key, libraryItem, info.PositionTicks).ConfigureAwait(false);
+ await OnPlaybackProgress(user, key, libraryItem, info).ConfigureAwait(false);
}
}
@@ -712,15 +712,40 @@ namespace MediaBrowser.Server.Implementations.Session
StartIdleCheckTimer();
}
- private async Task OnPlaybackProgress(Guid userId, string userDataKey, BaseItem item, long? positionTicks)
+ private async Task OnPlaybackProgress(User user, string userDataKey, BaseItem item, PlaybackProgressInfo info)
{
- var data = _userDataRepository.GetUserData(userId, userDataKey);
+ var data = _userDataRepository.GetUserData(user.Id, userDataKey);
+
+ var positionTicks = info.PositionTicks;
if (positionTicks.HasValue)
{
_userDataRepository.UpdatePlayState(item, data, positionTicks.Value);
- await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
+ UpdatePlaybackSettings(user, info, data);
+
+ await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+
+ private void UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data)
+ {
+ if (user.Configuration.RememberAudioSelections)
+ {
+ data.AudioStreamIndex = info.AudioStreamIndex;
+ }
+ else
+ {
+ data.AudioStreamIndex = null;
+ }
+
+ if (user.Configuration.RememberSubtitleSelections)
+ {
+ data.SubtitleStreamIndex = info.SubtitleStreamIndex;
+ }
+ else
+ {
+ data.SubtitleStreamIndex = null;
}
}
@@ -1268,7 +1293,17 @@ namespace MediaBrowser.Server.Implementations.Session
/// </summary>
/// <param name="request">The request.</param>
/// <returns>Task{SessionInfo}.</returns>
- public async Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
+ public Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
+ {
+ return AuthenticateNewSessionInternal(request, true);
+ }
+
+ public Task<AuthenticationResult> CreateNewSession(AuthenticationRequest request)
+ {
+ return AuthenticateNewSessionInternal(request, false);
+ }
+
+ private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword)
{
var user = _userManager.Users
.FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
@@ -1281,13 +1316,16 @@ namespace MediaBrowser.Server.Implementations.Session
}
}
- var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
-
- if (!result)
+ if (enforcePassword)
{
- EventHelper.FireEventIfNotNull(AuthenticationFailed, this, new GenericEventArgs<AuthenticationRequest>(request), _logger);
+ var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
- throw new SecurityException("Invalid user or password entered.");
+ if (!result)
+ {
+ EventHelper.FireEventIfNotNull(AuthenticationFailed, this, new GenericEventArgs<AuthenticationRequest>(request), _logger);
+
+ throw new SecurityException("Invalid user or password entered.");
+ }
}
var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.AppVersion, request.DeviceName).ConfigureAwait(false);
@@ -1310,7 +1348,8 @@ namespace MediaBrowser.Server.Implementations.Session
ServerId = _appHost.SystemId
};
}
-
+
+
private async Task<string> GetAuthorizationToken(string userId, string deviceId, string app, string appVersion, string deviceName)
{
var existing = _authRepo.Get(new AuthenticationInfoQuery
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
index 95934908d..39779ecf2 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
@@ -19,6 +19,7 @@ using MediaBrowser.Model.Sync;
using MoreLinq;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@@ -125,7 +126,23 @@ namespace MediaBrowser.Server.Implementations.Sync
private string GetSyncJobItemName(BaseItem item)
{
- return item.Name;
+ var name = item.Name;
+ var episode = item as Episode;
+
+ if (episode != null)
+ {
+ if (episode.IndexNumber.HasValue)
+ {
+ name = "E" + episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture) + " - " + name;
+ }
+
+ if (episode.ParentIndexNumber.HasValue)
+ {
+ name = "S" + episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture) + ", " + name;
+ }
+ }
+
+ return name;
}
public Task UpdateJobStatus(string id)
@@ -699,7 +716,7 @@ namespace MediaBrowser.Server.Implementations.Sync
var path = Path.Combine(temporaryPath, filename);
- _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, cancellationToken).ConfigureAwait(false))
{
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index 816f85b42..4f163f8a4 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -3,7 +3,7 @@
<package id="CommonIO" version="1.0.0.7" targetFramework="net45" />
<package id="Emby.XmlTv" version="1.0.0.48" targetFramework="net45" />
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
- <package id="MediaBrowser.Naming" version="1.0.0.47" targetFramework="net45" />
+ <package id="MediaBrowser.Naming" version="1.0.0.48" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
index e61a64646..a54380850 100644
--- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
+++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
@@ -490,7 +490,7 @@ namespace MediaBrowser.Server.Startup.Common
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LogManager.GetLogger("ChannelManager"), ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
RegisterSingleInstance(ChannelManager);
- MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager"), JsonSerializer, FileSystemManager);
+ MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager"), JsonSerializer, FileSystemManager, UserDataManager);
RegisterSingleInstance(MediaSourceManager);
SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index 099a47d40..5caf66671 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -140,6 +140,9 @@
<Content Include="dashboard-ui\components\remotecontrolautoplay.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\devices\windowsphone\wp.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\legacy\buttonenabled.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -254,9 +257,6 @@
<Content Include="dashboard-ui\favorites.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <None Include="dashboard-ui\legacy\deferred.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
<Content Include="dashboard-ui\livetvguideprovider.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -275,10 +275,10 @@
<Content Include="dashboard-ui\mysyncsettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\robots.txt">
+ <Content Include="dashboard-ui\pin.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\actionsheet.js">
+ <Content Include="dashboard-ui\robots.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\globalize.js">
@@ -317,6 +317,9 @@
<Content Include="dashboard-ui\scripts\autoorganizesmart.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\pin.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\searchmenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>