This commit is contained in:
Tavares André 2016-02-22 18:41:38 +01:00
commit 15a98c5eaa
53 changed files with 957 additions and 582 deletions

View File

@ -1,10 +1,8 @@
using System; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Connect; using MediaBrowser.Controller.Connect;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Connect; using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Dto;
using ServiceStack; using ServiceStack;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -75,28 +73,6 @@ namespace MediaBrowser.Api
public string ConnectUserId { get; set; } 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 public class ConnectService : BaseApiService
{ {
private readonly IConnectManager _connectManager; private readonly IConnectManager _connectManager;
@ -108,35 +84,6 @@ namespace MediaBrowser.Api
_userManager = userManager; _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) public object Post(CreateConnectLink request)
{ {
return _connectManager.LinkUser(request.Id, request.ConnectUsername); return _connectManager.LinkUser(request.Id, request.ConnectUsername);

View File

@ -1,9 +1,14 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Globalization; using System.Globalization;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Connect; using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Session;
using ServiceStack; using ServiceStack;
namespace MediaBrowser.Api namespace MediaBrowser.Api
@ -13,6 +18,8 @@ namespace MediaBrowser.Api
{ {
[ApiMember(Name = "DeviceId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] [ApiMember(Name = "DeviceId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string DeviceId { get; set; } 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")] [Route("/Auth/Pin", "GET", Summary = "Gets pin status")]
@ -35,7 +42,7 @@ namespace MediaBrowser.Api
[Route("/Auth/Pin/Validate", "POST", Summary = "Validates a pin")] [Route("/Auth/Pin/Validate", "POST", Summary = "Validates a pin")]
[Authenticated] [Authenticated]
public class ValidatePinRequest : IReturnVoid public class ValidatePinRequest : IReturn<SessionInfoDto>
{ {
[ApiMember(Name = "Pin", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] [ApiMember(Name = "Pin", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string Pin { get; set; } public string Pin { get; set; }
@ -43,10 +50,27 @@ namespace MediaBrowser.Api
public class PinLoginService : BaseApiService 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) 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 pin = GetNewPin();
var value = new MyPinStatus var value = new MyPinStatus
@ -55,7 +79,8 @@ namespace MediaBrowser.Api
IsConfirmed = false, IsConfirmed = false,
IsExpired = false, IsExpired = false,
Pin = pin, Pin = pin,
DeviceId = request.DeviceId DeviceId = request.DeviceId,
AppName = request.AppName
}; };
_activeRequests.AddOrUpdate(pin, value, (k, v) => value); _activeRequests.AddOrUpdate(pin, value, (k, v) => value);
@ -75,6 +100,7 @@ namespace MediaBrowser.Api
if (!_activeRequests.TryGetValue(request.Pin, out status)) if (!_activeRequests.TryGetValue(request.Pin, out status))
{ {
Logger.Debug("Pin {0} not found.", request.Pin);
throw new ResourceNotFoundException(); throw new ResourceNotFoundException();
} }
@ -88,12 +114,13 @@ namespace MediaBrowser.Api
}); });
} }
public object Post(ExchangePinRequest request) public async Task<object> Post(ExchangePinRequest request)
{ {
MyPinStatus status; MyPinStatus status;
if (!_activeRequests.TryGetValue(request.Pin, out status)) if (!_activeRequests.TryGetValue(request.Pin, out status))
{ {
Logger.Debug("Pin {0} not found.", request.Pin);
throw new ResourceNotFoundException(); throw new ResourceNotFoundException();
} }
@ -104,14 +131,24 @@ namespace MediaBrowser.Api
throw new ResourceNotFoundException(); 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 App = auth.Client,
UserId = status.UserId 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; MyPinStatus status;
@ -124,12 +161,18 @@ namespace MediaBrowser.Api
status.IsConfirmed = true; status.IsConfirmed = true;
status.UserId = AuthorizationContext.GetAuthorizationInfo(Request).UserId; status.UserId = AuthorizationContext.GetAuthorizationInfo(Request).UserId;
return ToOptimizedResult(new ValidatePinResult
{
AppName = status.AppName
});
} }
private void EnsureValid(string requestedDeviceId, MyPinStatus status) private void EnsureValid(string requestedDeviceId, MyPinStatus status)
{ {
if (!string.Equals(requestedDeviceId, status.DeviceId, StringComparison.OrdinalIgnoreCase)) 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(); throw new ResourceNotFoundException();
} }
@ -145,6 +188,7 @@ namespace MediaBrowser.Api
if (status.IsExpired) if (status.IsExpired)
{ {
Logger.Debug("Pin {0} is expired", status.Pin);
throw new ResourceNotFoundException(); throw new ResourceNotFoundException();
} }
} }
@ -163,16 +207,7 @@ namespace MediaBrowser.Api
private string GetNewPinInternal() private string GetNewPinInternal()
{ {
var length = 5; return new Random().Next(10000, 99999).ToString(CultureInfo.InvariantCulture);
var pin = string.Empty;
while (pin.Length < length)
{
var digit = new Random().Next(0, 9);
pin += digit.ToString(CultureInfo.InvariantCulture);
}
return pin;
} }
private bool IsPinActive(string pin) private bool IsPinActive(string pin)
@ -181,15 +216,15 @@ namespace MediaBrowser.Api
if (!_activeRequests.TryGetValue(pin, out status)) if (!_activeRequests.TryGetValue(pin, out status))
{ {
return true; return false;
} }
if (status.IsExpired) if (status.IsExpired)
{ {
return true; return false;
} }
return false; return true;
} }
public class MyPinStatus : PinStatusResult public class MyPinStatus : PinStatusResult
@ -197,6 +232,12 @@ namespace MediaBrowser.Api
public DateTime CreationTimeUtc { get; set; } public DateTime CreationTimeUtc { get; set; }
public string DeviceId { get; set; } public string DeviceId { get; set; }
public string UserId { get; set; } public string UserId { get; set; }
public string AppName { get; set; }
} }
} }
public class ValidatePinResult
{
public string AppName { get; set; }
}
} }

View File

@ -1462,6 +1462,13 @@ namespace MediaBrowser.Api.Playback
{ {
// Duplicating ItemId because of MediaMonkey // 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.EstimateContentLength = transcodingProfile.EstimateContentLength;
state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; 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 (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";
} }
} }

View File

@ -137,9 +137,9 @@ namespace MediaBrowser.Api.Playback.Progressive
var isOutputMkv = string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase); 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)) if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))

View File

@ -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")] [ApiMember(Name = "EnableAutoStreamCopy", Description = "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool EnableAutoStreamCopy { get; set; } public 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")] [ApiMember(Name = "Cabac", Description = "Enable if cabac encoding is required", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? Cabac { get; set; } public bool? Cabac { get; set; }

View File

@ -415,23 +415,6 @@ namespace MediaBrowser.Api
{ {
var auth = AuthorizationContext.GetAuthorizationInfo(Request); 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 var result = await _sessionMananger.AuthenticateNewSession(new AuthenticationRequest
{ {
App = auth.Client, App = auth.Client,

View File

@ -76,25 +76,5 @@ namespace MediaBrowser.Controller.Connect
/// <param name="token">The token.</param> /// <param name="token">The token.</param>
/// <returns><c>true</c> if [is authorization token valid] [the specified token]; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if [is authorization token valid] [the specified token]; otherwise, <c>false</c>.</returns>
bool IsAuthorizationTokenValid(string token); 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);
} }
} }

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
{ {
public interface IHasMediaSources : IHasId public interface IHasMediaSources : IHasUserData
{ {
/// <summary> /// <summary>
/// Gets the media sources. /// Gets the media sources.

View File

@ -78,6 +78,16 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
/// <value><c>true</c> if played; otherwise, <c>false</c>.</value> /// <value><c>true</c> if played; otherwise, <c>false</c>.</value>
public bool Played { get; set; } 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> /// <summary>
/// This is an interpreted property to indicate likes or dislikes /// This is an interpreted property to indicate likes or dislikes

View File

@ -46,6 +46,9 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;List&lt;MediaSourceInfo&gt;&gt;.</returns> /// <returns>Task&lt;List&lt;MediaSourceInfo&gt;&gt;.</returns>
Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken); Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
}
public interface IConfigurableTunerHost
{
/// <summary> /// <summary>
/// Validates the specified information. /// Validates the specified information.
/// </summary> /// </summary>

View File

@ -59,6 +59,12 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The clients.</value> /// <value>The clients.</value>
public List<string> Clients { get; set; } 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() public LiveTvTunerInfo()
{ {
Clients = new List<string>(); Clients = new List<string>();

View File

@ -44,6 +44,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public int? CpuCoreLimit { get; set; } public int? CpuCoreLimit { get; set; }
public bool ReadInputAtNativeFramerate { get; set; } public bool ReadInputAtNativeFramerate { get; set; }
public SubtitleDeliveryMethod SubtitleMethod { get; set; } public SubtitleDeliveryMethod SubtitleMethod { get; set; }
public bool CopyTimestamps { get; set; }
/// <summary> /// <summary>
/// Gets a value indicating whether this instance has fixed resolution. /// Gets a value indicating whether this instance has fixed resolution.

View File

@ -250,6 +250,13 @@ namespace MediaBrowser.Controller.Session
/// <returns>Task{SessionInfo}.</returns> /// <returns>Task{SessionInfo}.</returns>
Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request); 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> /// <summary>
/// Reports the capabilities. /// Reports the capabilities.
/// </summary> /// </summary>

View File

@ -132,6 +132,8 @@ namespace MediaBrowser.Dlna.Ssdp
return; return;
} }
_ssdpHandler.LogMessageReceived(args, true);
TryCreateDevice(args); TryCreateDevice(args);
} }
} }
@ -219,14 +221,6 @@ namespace MediaBrowser.Dlna.Ssdp
return; 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); EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
} }

View File

@ -93,17 +93,7 @@ namespace MediaBrowser.Dlna.Ssdp
return; return;
} }
var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog; LogMessageReceived(args, isMulticast);
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);
}
var headers = args.Headers; var headers = args.Headers;
string st; string st;
@ -125,6 +115,21 @@ namespace MediaBrowser.Dlna.Ssdp
EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger); 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) internal bool IgnoreMessage(SsdpMessageEventArgs args, bool isMulticast)
{ {
string usn; string usn;
@ -298,9 +303,17 @@ namespace MediaBrowser.Dlna.Ssdp
var msg = new SsdpMessageBuilder().BuildMessage(header, values); var msg = new SsdpMessageBuilder().BuildMessage(header, values);
SendDatagram(msg, endpoint, null, false, 1); var ipEndPoint = endpoint as IPEndPoint;
SendDatagram(msg, endpoint, new IPEndPoint(d.Address, 0), false, 1); if (ipEndPoint != null)
//SendDatagram(header, values, endpoint, null, true); {
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) if (enableDebugLogging)
{ {
@ -473,6 +486,7 @@ namespace MediaBrowser.Dlna.Ssdp
var msg = new SsdpMessageBuilder().BuildMessage(header, values); var msg = new SsdpMessageBuilder().BuildMessage(header, values);
SendDatagram(msg, _ssdpEndp, new IPEndPoint(dev.Address, 0), true); SendDatagram(msg, _ssdpEndp, new IPEndPoint(dev.Address, 0), true);
//SendUnicastRequest(msg, 1);
} }
public void RegisterNotification(string uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services) public void RegisterNotification(string uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services)
@ -582,7 +596,22 @@ namespace MediaBrowser.Dlna.Ssdp
} }
} }
private async void SendUnicastRequest(string request) private void SendUnicastRequest(string request, int sendCount = 3)
{
if (_unicastClient == null)
{
return;
}
_logger.Debug("Sending unicast search 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) if (_unicastClient == null)
{ {
@ -592,18 +621,16 @@ namespace MediaBrowser.Dlna.Ssdp
_logger.Debug("Sending unicast search request"); _logger.Debug("Sending unicast search request");
byte[] req = Encoding.ASCII.GetBytes(request); byte[] req = Encoding.ASCII.GetBytes(request);
var ipSsdp = IPAddress.Parse(SSDPAddr);
var ipTxEnd = new IPEndPoint(ipSsdp, SSDPPort);
try try
{ {
for (var i = 0; i < 3; i++) for (var i = 0; i < sendCount; i++)
{ {
if (i > 0) if (i > 0)
{ {
await Task.Delay(50).ConfigureAwait(false); await Task.Delay(50).ConfigureAwait(false);
} }
_unicastClient.Send(req, req.Length, ipTxEnd); _unicastClient.Send(req, req.Length, toEndPoint);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -794,6 +794,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.EstimateContentLength = transcodingProfile.EstimateContentLength; state.EstimateContentLength = transcodingProfile.EstimateContentLength;
state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
state.Options.CopyTimestamps = transcodingProfile.CopyTimestamps;
} }
} }
} }

View File

@ -129,7 +129,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns> /// <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; var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters;
@ -175,7 +175,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MediaInfoResult}.</returns> /// <returns>Task{MediaInfoResult}.</returns>
/// <exception cref="System.ApplicationException">ffprobe failed - streams and format are both null.</exception> /// <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, string primaryPath,
MediaProtocol protocol, MediaProtocol protocol,
bool extractChapters, bool extractChapters,
@ -934,7 +934,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
_mediaEncoder._runningProcesses.Remove(this); _mediaEncoder._runningProcesses.Remove(this);
} }
process.Dispose(); try
{
process.Dispose();
}
catch (Exception ex)
{
}
} }
private bool _disposed; private bool _disposed;

View File

@ -27,7 +27,7 @@ namespace MediaBrowser.MediaEncoding.Probing
public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType videoType, bool isAudio, string path, MediaProtocol protocol) 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, Path = path,
Protocol = protocol Protocol = protocol
@ -56,40 +56,81 @@ namespace MediaBrowser.MediaEncoding.Probing
} }
} }
if (isAudio) var tags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var tagStreamType = isAudio ? "audio" : "video";
if (data.streams != null)
{ {
SetAudioRuntimeTicks(data, info); var tagStream = data.streams.FirstOrDefault(i => string.Equals(i.codec_type, tagStreamType, StringComparison.OrdinalIgnoreCase));
var tags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); if (tagStream != null && tagStream.tags != null)
// 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 audioStream = data.streams.FirstOrDefault(i => string.Equals(i.codec_type, "audio", StringComparison.OrdinalIgnoreCase)); foreach (var pair in tagStream.tags)
if (audioStream != null && audioStream.tags != null)
{
foreach (var pair in audioStream.tags)
{
tags[pair.Key] = pair.Value;
}
}
}
if (data.format != null && data.format.tags != null)
{
foreach (var pair in data.format.tags)
{ {
tags[pair.Key] = pair.Value; tags[pair.Key] = pair.Value;
} }
} }
}
if (data.format != null && data.format.tags != null)
{
foreach (var pair in data.format.tags)
{
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); SetAudioInfoFromTags(info, tags);
} }
else 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)) if (data.format != null && !string.IsNullOrEmpty(data.format.duration))
{ {
info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks; info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
@ -108,6 +149,11 @@ namespace MediaBrowser.MediaEncoding.Probing
return info; 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> /// <summary>
/// Converts ffprobe stream info to our MediaStream class /// Converts ffprobe stream info to our MediaStream class
/// </summary> /// </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"); var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
if (!string.IsNullOrWhiteSpace(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"); audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists"); var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists");
@ -511,22 +569,12 @@ namespace MediaBrowser.MediaEncoding.Probing
// Disc number // Disc number
audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc"); 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 we don't have a ProductionYear try and get it from PremiereDate
if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue) if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue)
{ {
audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year; audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year;
} }
FetchGenres(audio, tags);
// There's several values in tags may or may not be present // There's several values in tags may or may not be present
FetchStudios(audio, tags, "organization"); FetchStudios(audio, tags, "organization");
FetchStudios(audio, tags, "ensemble"); FetchStudios(audio, tags, "ensemble");
@ -693,7 +741,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary> /// </summary>
/// <param name="info">The information.</param> /// <param name="info">The information.</param>
/// <param name="tags">The tags.</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"); 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 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) if (data.format == null || data.format.tags == null)
{ {
@ -775,15 +823,16 @@ namespace MediaBrowser.MediaEncoding.Probing
if (!string.IsNullOrWhiteSpace(genres)) if (!string.IsNullOrWhiteSpace(genres))
{ {
//genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre"); var genreList = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
}
if (!string.IsNullOrWhiteSpace(genres))
{
video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i)) .Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Trim()) .Select(i => i.Trim())
.ToList(); .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"); var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");

View File

@ -182,8 +182,6 @@ namespace MediaBrowser.Model.Configuration
public PeopleMetadataOptions PeopleMetadataOptions { get; set; } public PeopleMetadataOptions PeopleMetadataOptions { get; set; }
public bool FindInternetTrailers { get; set; } public bool FindInternetTrailers { get; set; }
public string[] InsecureApps9 { get; set; }
public bool SaveMetadataHidden { get; set; } public bool SaveMetadataHidden { get; set; }
public NameValuePair[] ContentTypes { get; set; } public NameValuePair[] ContentTypes { get; set; }
@ -256,11 +254,6 @@ namespace MediaBrowser.Model.Configuration
PeopleMetadataOptions = new PeopleMetadataOptions(); PeopleMetadataOptions = new PeopleMetadataOptions();
InsecureApps9 = new[]
{
"Windows Phone"
};
MetadataOptions = new[] MetadataOptions = new[]
{ {
new MetadataOptions(1, 1280) {ItemType = "Book"}, new MetadataOptions(1, 1280) {ItemType = "Book"},

View File

@ -48,11 +48,19 @@ namespace MediaBrowser.Model.Configuration
public bool HidePlayedInLatest { get; set; } public bool HidePlayedInLatest { get; set; }
public bool DisplayChannelsInline { get; set; } public bool DisplayChannelsInline { get; set; }
public bool RememberAudioSelections { get; set; }
public bool RememberSubtitleSelections { get; set; }
public bool EnableEpisodeAutoQueue { get; set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UserConfiguration" /> class. /// Initializes a new instance of the <see cref="UserConfiguration" /> class.
/// </summary> /// </summary>
public UserConfiguration() public UserConfiguration()
{ {
EnableEpisodeAutoQueue = true;
RememberAudioSelections = true;
RememberSubtitleSelections = true;
HidePlayedInLatest = true; HidePlayedInLatest = true;
PlayDefaultAudioTrack = true; PlayDefaultAudioTrack = true;

View File

@ -425,6 +425,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',')[0]; playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',')[0];
playlistItem.VideoCodec = transcodingProfile.VideoCodec; playlistItem.VideoCodec = transcodingProfile.VideoCodec;
playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps;
playlistItem.SubProtocol = transcodingProfile.Protocol; playlistItem.SubProtocol = transcodingProfile.Protocol;
playlistItem.AudioStreamIndex = audioStreamIndex; playlistItem.AudioStreamIndex = audioStreamIndex;

View File

@ -32,6 +32,7 @@ namespace MediaBrowser.Model.Dlna
public string VideoProfile { get; set; } public string VideoProfile { get; set; }
public bool? Cabac { get; set; } public bool? Cabac { get; set; }
public bool CopyTimestamps { get; set; }
public string AudioCodec { get; set; } public string AudioCodec { get; set; }
public int? AudioStreamIndex { get; set; } public int? AudioStreamIndex { get; set; }
@ -232,6 +233,8 @@ namespace MediaBrowser.Model.Dlna
list.Add(new NameValuePair("ItemId", item.ItemId)); list.Add(new NameValuePair("ItemId", item.ItemId));
} }
list.Add(new NameValuePair("CopyTimestamps", (item.CopyTimestamps).ToString().ToLower()));
return list; return list;
} }
@ -269,7 +272,7 @@ namespace MediaBrowser.Model.Dlna
// HLS will preserve timestamps so we can just grab the full subtitle stream // HLS will preserve timestamps so we can just grab the full subtitle stream
long startPositionTicks = StringHelper.EqualsIgnoreCase(SubProtocol, "hls") long startPositionTicks = StringHelper.EqualsIgnoreCase(SubProtocol, "hls")
? 0 ? 0
: (this.PlayMethod == PlayMethod.Transcode ? StartPositionTicks : 0); : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
// First add the selected track // First add the selected track
if (SubtitleStreamIndex.HasValue) if (SubtitleStreamIndex.HasValue)

View File

@ -29,6 +29,9 @@ namespace MediaBrowser.Model.Dlna
[XmlAttribute("transcodeSeekInfo")] [XmlAttribute("transcodeSeekInfo")]
public TranscodeSeekInfo TranscodeSeekInfo { get; set; } public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
[XmlAttribute("copyTimestamps")]
public bool CopyTimestamps { get; set; }
[XmlAttribute("context")] [XmlAttribute("context")]
public EncodingContext Context { get; set; } public EncodingContext Context { get; set; }

View File

@ -34,5 +34,9 @@ namespace MediaBrowser.Model.Entities
/// The conductor /// The conductor
/// </summary> /// </summary>
public const string Conductor = "Conductor"; public const string Conductor = "Conductor";
/// <summary>
/// The lyricist
/// </summary>
public const string Lyricist = "Lyricist";
} }
} }

View File

@ -64,6 +64,12 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The clients.</value> /// <value>The clients.</value>
public List<string> Clients { get; set; } 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() public LiveTvTunerInfoDto()
{ {
Clients = new List<string>(); Clients = new List<string>();

View File

@ -9,11 +9,6 @@ namespace MediaBrowser.Model.MediaInfo
{ {
public List<ChapterInfo> Chapters { get; set; } public List<ChapterInfo> Chapters { get; set; }
/// <summary>
/// Gets or sets the title.
/// </summary>
/// <value>The title.</value>
public string Title { get; set; }
/// <summary> /// <summary>
/// Gets or sets the album. /// Gets or sets the album.
/// </summary> /// </summary>
@ -47,6 +42,11 @@ namespace MediaBrowser.Model.MediaInfo
/// <value>The official rating.</value> /// <value>The official rating.</value>
public string OfficialRating { get; set; } public string OfficialRating { get; set; }
/// <summary> /// <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. /// Gets or sets the overview.
/// </summary> /// </summary>
/// <value>The overview.</value> /// <value>The overview.</value>

View File

@ -125,9 +125,9 @@ namespace MediaBrowser.Providers.MediaInfo
private async Task FetchDataFromTags(Audio audio, Model.MediaInfo.MediaInfo data) private async Task FetchDataFromTags(Audio audio, Model.MediaInfo.MediaInfo data)
{ {
// Only set Name if title was found in the dictionary // 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)) if (!audio.LockedFields.Contains(MetadataFields.Cast))

View File

@ -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.LockedFields.Contains(MetadataFields.Genres))
{ {
if (video.Genres.Count == 0 || isFullRefresh) if (video.Genres.Count == 0 || isFullRefresh)
@ -437,6 +442,13 @@ namespace MediaBrowser.Providers.MediaInfo
video.ParentIndexNumber = data.ParentIndexNumber; 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 we don't have a ProductionYear try and get it from PremiereDate
if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue) if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue)

View File

@ -10,7 +10,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Connect; using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
@ -24,7 +23,6 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Connect namespace MediaBrowser.Server.Implementations.Connect
{ {
@ -121,7 +119,6 @@ namespace MediaBrowser.Server.Implementations.Connect
_securityManager = securityManager; _securityManager = securityManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated;
_config.ConfigurationUpdated += _config_ConfigurationUpdated; _config.ConfigurationUpdated += _config_ConfigurationUpdated;
LoadCachedData(); 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) public async Task Authenticate(string username, string passwordMd5)
{ {
if (string.IsNullOrWhiteSpace(username)) 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) public async Task<User> GetLocalUser(string connectUserId)
{ {
var user = _userManager.Users var user = _userManager.Users

View File

@ -639,6 +639,8 @@ namespace MediaBrowser.Server.Implementations.Dto
private IEnumerable<string> GetCacheTags(BaseItem item, ImageType type, int limit) private IEnumerable<string> GetCacheTags(BaseItem item, ImageType type, int limit)
{ {
return item.GetImages(type) return item.GetImages(type)
// Convert to a list now in case GetImageCacheTag is slow
.ToList()
.Select(p => GetImageCacheTag(item, p)) .Select(p => GetImageCacheTag(item, p))
.Where(i => i != null) .Where(i => i != null)
.Take(limit) .Take(limit)

View File

@ -502,7 +502,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
} }
} }
return series ?? new Series(); return series;
} }
/// <summary> /// <summary>

View File

@ -348,6 +348,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer
return Task.FromResult(true); 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)) if (!string.IsNullOrWhiteSpace(GlobalResponse))
{ {
httpRes.StatusCode = 503; httpRes.StatusCode = 503;

View File

@ -134,20 +134,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues) private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues)
{ {
if (!_config.Configuration.IsStartupWizardCompleted && if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard)
authAttribtues.AllowBeforeStartupWizard)
{ {
return true; return true;
} }
return _config.Configuration.InsecureApps9.Contains(auth.Client ?? string.Empty, return false;
StringComparer.OrdinalIgnoreCase);
} }
private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, AuthenticationInfo tokenInfo) private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, AuthenticationInfo tokenInfo)
{ {
if (!_config.Configuration.IsStartupWizardCompleted && if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard)
authAttribtues.AllowBeforeStartupWizard)
{ {
return true; return true;
} }

View File

@ -45,7 +45,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
{ {
var auth = GetAuthorizationDictionary(httpReq); var auth = GetAuthorizationDictionary(httpReq);
string userId = null;
string deviceId = null; string deviceId = null;
string device = null; string device = null;
string client = null; string client = null;
@ -53,9 +52,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
if (auth != null) if (auth != null)
{ {
// TODO: Remove this
auth.TryGetValue("UserId", out userId);
auth.TryGetValue("DeviceId", out deviceId); auth.TryGetValue("DeviceId", out deviceId);
auth.TryGetValue("Device", out device); auth.TryGetValue("Device", out device);
auth.TryGetValue("Client", out client); auth.TryGetValue("Client", out client);
@ -78,7 +74,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
Client = client, Client = client,
Device = device, Device = device,
DeviceId = deviceId, DeviceId = deviceId,
UserId = userId,
Version = version, Version = version,
Token = token Token = token
}; };

View File

@ -30,8 +30,9 @@ namespace MediaBrowser.Server.Implementations.Library
private IMediaSourceProvider[] _providers; private IMediaSourceProvider[] _providers;
private readonly ILogger _logger; 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; _itemRepo = itemRepo;
_userManager = userManager; _userManager = userManager;
@ -39,6 +40,7 @@ namespace MediaBrowser.Server.Implementations.Library
_logger = logger; _logger = logger;
_jsonSerializer = jsonSerializer; _jsonSerializer = jsonSerializer;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_userDataManager = userDataManager;
} }
public void AddParts(IEnumerable<IMediaSourceProvider> providers) public void AddParts(IEnumerable<IMediaSourceProvider> providers)
@ -140,7 +142,7 @@ namespace MediaBrowser.Server.Implementations.Library
{ {
if (user != null) if (user != null)
{ {
SetUserProperties(source, user); SetUserProperties(hasMediaSources, source, user);
} }
if (source.Protocol == MediaProtocol.File) if (source.Protocol == MediaProtocol.File)
{ {
@ -257,25 +259,38 @@ namespace MediaBrowser.Server.Implementations.Library
{ {
foreach (var source in sources) foreach (var source in sources)
{ {
SetUserProperties(source, user); SetUserProperties(item, source, user);
} }
} }
return sources; 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) var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
? new string[] { }
: new[] { user.Configuration.AudioLanguagePreference }; 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) var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
? new List<string> { } ? new List<string> { }
: new List<string> { user.Configuration.SubtitleLanguagePreference }; : new List<string> { user.Configuration.SubtitleLanguagePreference };
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
var defaultAudioIndex = source.DefaultAudioStreamIndex; var defaultAudioIndex = source.DefaultAudioStreamIndex;
var audioLangage = defaultAudioIndex == null var audioLangage = defaultAudioIndex == null
? null ? null
@ -290,6 +305,26 @@ namespace MediaBrowser.Server.Implementations.Library
user.Configuration.SubtitleMode, audioLangage); 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) private IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
{ {
return sources.OrderBy(i => return sources.OrderBy(i =>
@ -353,7 +388,10 @@ namespace MediaBrowser.Server.Implementations.Library
if (!string.IsNullOrWhiteSpace(request.UserId)) if (!string.IsNullOrWhiteSpace(request.UserId))
{ {
var user = _userManager.GetUserById(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 return new LiveStreamResponse

View File

@ -771,6 +771,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
recordPath = Path.ChangeExtension(recordPath, ".mp4"); recordPath = Path.ChangeExtension(recordPath, ".mp4");
} }
_libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
recording.Path = recordPath; recording.Path = recordPath;
recording.Status = RecordingStatus.InProgress; recording.Status = RecordingStatus.InProgress;
recording.DateLastUpdated = DateTime.UtcNow; recording.DateLastUpdated = DateTime.UtcNow;
@ -801,6 +803,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{ {
result.Item2.Release(); result.Item2.Release();
} }
_libraryMonitor.ReportFileSystemChangeComplete(recordPath, false);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)

View File

@ -116,7 +116,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
videoArgs = "-codec:v:0 copy"; 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) if (mediaSource.ReadAtNativeFramerate)
{ {
@ -143,7 +143,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{ {
audioChannels = audioStream.Channels ?? audioChannels; 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) private bool EncodeVideo(MediaSourceInfo mediaSource)

View File

@ -178,7 +178,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
SourceType = info.SourceType, SourceType = info.SourceType,
Status = info.Status, Status = info.Status,
ChannelName = channelName, ChannelName = channelName,
Url = info.Url Url = info.Url,
CanReset = info.CanReset
}; };
if (!string.IsNullOrEmpty(info.ChannelId)) if (!string.IsNullOrEmpty(info.ChannelId))

View File

@ -801,11 +801,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{ {
if (!string.IsNullOrWhiteSpace(info.ImagePath)) 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)) 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(); 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(); var config = GetConfiguration();

View File

@ -64,7 +64,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return list; return list;
} }
private List<TunerHostInfo> GetTunerHosts() protected virtual List<TunerHostInfo> GetTunerHosts()
{ {
return GetConfiguration().TunerHosts return GetConfiguration().TunerHosts
.Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase)) .Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))

View File

@ -20,7 +20,7 @@ using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
public class HdHomerunHost : BaseTunerHost, ITunerHost public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{ {
private readonly IHttpClient _httpClient; private readonly IHttpClient _httpClient;

View File

@ -8,19 +8,17 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonIO; using CommonIO;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{ {
public class M3UTunerHost : BaseTunerHost, ITunerHost public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{ {
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient; 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) protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
{ {
var urlHash = info.Url.GetMD5().ToString("N"); return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(info.Url, ChannelIdPrefix, cancellationToken).ConfigureAwait(false);
// 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;
} }
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken) public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
{ {
var list = GetConfiguration().TunerHosts var list = GetTunerHosts()
.Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
.Select(i => new LiveTvTunerInfo() .Select(i => new LiveTvTunerInfo()
{ {
Name = Name, Name = Name,
@ -125,18 +70,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return sources.First(); return sources.First();
} }
class M3UChannel : ChannelInfo
{
public string Path { get; set; }
public M3UChannel()
{
}
}
public async Task Validate(TunerHostInfo info) 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); 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) protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
{ {
var urlHash = info.Url.GetMD5().ToString("N"); var urlHash = info.Url.GetMD5().ToString("N");

View File

@ -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; }
}
}

View File

@ -1,10 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
@ -13,6 +17,7 @@ using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{ {
@ -24,14 +29,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
private readonly ILiveTvManager _liveTvManager; private readonly ILiveTvManager _liveTvManager;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IHttpClient _httpClient; 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; _deviceDiscovery = deviceDiscovery;
_config = config; _config = config;
_logger = logger; _logger = logger;
_liveTvManager = liveTvManager; _liveTvManager = liveTvManager;
_httpClient = httpClient; _httpClient = httpClient;
_json = json;
Current = this;
} }
public void Run() public void Run()
@ -42,7 +59,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
{ {
string st = null; 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; string location;
if (e.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location)) if (e.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
@ -61,26 +83,23 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
try try
{ {
var options = GetConfiguration(); if (_discoveredHosts.Any(i => string.Equals(i.Type, SatIpHost.DeviceType, StringComparison.OrdinalIgnoreCase) && string.Equals(location, i.Url, StringComparison.OrdinalIgnoreCase)))
{
return;
}
//if (options.TunerHosts.Any(i => _logger.Debug("Will attempt to add SAT device {0}", location);
// string.Equals(i.Type, SatIpHost.DeviceType, StringComparison.OrdinalIgnoreCase) && var info = await GetInfo(location, CancellationToken.None).ConfigureAwait(false);
// UriEquals(i.Url, url)))
//{
// return;
//}
//// Strip off the port _discoveredHosts.Add(info);
//url = new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped).TrimEnd('/'); }
catch (OperationCanceledException)
{
//await TestUrl(url).ConfigureAwait(false); }
catch (NotImplementedException)
{
//await _liveTvManager.SaveTunerHost(new TunerHostInfo
//{
// Type = SatIpHost.DeviceType,
// Url = url
//}).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -92,43 +111,141 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
} }
} }
private async Task TestUrl(string url)
{
// Test it by pulling down the lineup
using (await _httpClient.Get(new HttpRequestOptions
{
Url = string.Format("{0}/lineup.json", url),
CancellationToken = CancellationToken.None
}))
{
}
}
private bool UriEquals(string savedUri, string location)
{
return string.Equals(NormalizeUrl(location), NormalizeUrl(savedUri), StringComparison.OrdinalIgnoreCase);
}
private string NormalizeUrl(string url)
{
if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
url = "http://" + url;
}
url = url.TrimEnd('/');
// Strip off the port
return new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped);
}
private LiveTvOptions GetConfiguration()
{
return _config.GetConfiguration<LiveTvOptions>("livetv");
}
public void Dispose() public void Dispose()
{ {
} }
public async Task<SatIpTunerHostInfo> GetInfo(string url, CancellationToken cancellationToken)
{
var result = new SatIpTunerHostInfo
{
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();
}
// Device hasn't implemented an m3u list
if (string.IsNullOrWhiteSpace(result.M3UUrl))
{
result.IsEnabled = false;
}
else if (!result.M3UUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
var fullM3uUrl = url.Substring(0, url.LastIndexOf('/'));
result.M3UUrl = fullM3uUrl + "/" + result.M3UUrl.TrimStart('/');
}
_logger.Debug("SAT device result: {0}", _json.SerializeToString(result));
return result;
}
private void FillFromDeviceNode(SatIpTunerHostInfo info, XmlReader reader)
{
reader.MoveToContent();
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; }
} }
} }

View File

@ -1,45 +1,171 @@
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp using System;
{ using System.Collections.Generic;
public class SatIpHost /*: BaseTunerHost*/ using System.Globalization;
{ using System.Linq;
//public SatIpHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder) using System.Threading;
// : base(config, logger, jsonSerializer, mediaEncoder) 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;
//protected override Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken) namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
//{ {
// throw new NotImplementedException(); public class SatIpHost : BaseTunerHost, ITunerHost
//} {
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;
return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(satInfo.M3UUrl, ChannelIdPrefix, cancellationToken).ConfigureAwait(false);
}
public static string DeviceType public static string DeviceType
{ {
get { return "satip"; } get { return "satip"; }
} }
//public override string Type public override string Type
//{ {
// get { return DeviceType; } get { return DeviceType; }
//} }
//protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken) protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
//{ {
// throw new NotImplementedException(); var urlHash = tuner.Url.GetMD5().ToString("N");
//} var prefix = ChannelIdPrefix + urlHash;
if (!channelId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
return null;
}
//protected override Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken) var channels = await GetChannels(tuner, true, cancellationToken).ConfigureAwait(false);
//{ var m3uchannels = channels.Cast<M3UChannel>();
// throw new NotImplementedException(); 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;
}
//protected override Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken) var mediaSource = new MediaSourceInfo
//{ {
// throw new NotImplementedException(); 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
//protected override bool IsValidChannelId(string channelId) }
//{ },
// throw new NotImplementedException(); 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);
return updatedInfo.TunersAvailable > 0;
}
protected override bool IsValidChannelId(string channelId)
{
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
protected override List<TunerHostInfo> GetTunerHosts()
{
return SatIpDiscovery.Current.DiscoveredHosts;
}
public string Name
{
get { return "Sat IP"; }
}
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;
}
} }
} }

View File

@ -52,9 +52,9 @@
<Reference Include="Interfaces.IO"> <Reference Include="Interfaces.IO">
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath> <HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
</Reference> </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> <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>
<Reference Include="MoreLinq"> <Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath> <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\BaseTunerHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" /> <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" /> <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" />
<Compile Include="LiveTv\TunerHosts\M3uParser.cs" />
<Compile Include="LiveTv\TunerHosts\M3UTunerHost.cs" /> <Compile Include="LiveTv\TunerHosts\M3UTunerHost.cs" />
<Compile Include="LiveTv\ProgramImageProvider.cs" /> <Compile Include="LiveTv\ProgramImageProvider.cs" />
<Compile Include="LiveTv\RecordingImageProvider.cs" /> <Compile Include="LiveTv\RecordingImageProvider.cs" />

View File

@ -1014,7 +1014,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
if (!reader.IsDBNull(31)) if (!reader.IsDBNull(31))
{ {
item.OfficialRating = reader.GetString(31); item.OfficialRatingDescription = reader.GetString(31);
} }
if (!reader.IsDBNull(32)) if (!reader.IsDBNull(32))

View File

@ -56,6 +56,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
}; };
_connection.RunQueries(queries, Logger); _connection.RunQueries(queries, Logger);
_connection.AddColumn(Logger, "userdata", "AudioStreamIndex", "int");
_connection.AddColumn(Logger, "userdata", "SubtitleStreamIndex", "int");
} }
/// <summary> /// <summary>
@ -127,7 +130,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand()) 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, "@key", DbType.String).Value = key;
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId; 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, "@isFavorite", DbType.Boolean).Value = userData.IsFavorite;
cmd.Parameters.Add(cmd, "@playbackPositionTicks", DbType.Int64).Value = userData.PlaybackPositionTicks; cmd.Parameters.Add(cmd, "@playbackPositionTicks", DbType.Int64).Value = userData.PlaybackPositionTicks;
cmd.Parameters.Add(cmd, "@lastPlayedDate", DbType.DateTime).Value = userData.LastPlayedDate; 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; cmd.Transaction = transaction;
@ -199,7 +204,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{ {
using (var cmd = _connection.CreateCommand()) 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, "@key", DbType.String).Value = userItemData.Key;
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId; 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, "@isFavorite", DbType.Boolean).Value = userItemData.IsFavorite;
cmd.Parameters.Add(cmd, "@playbackPositionTicks", DbType.Int64).Value = userItemData.PlaybackPositionTicks; cmd.Parameters.Add(cmd, "@playbackPositionTicks", DbType.Int64).Value = userItemData.PlaybackPositionTicks;
cmd.Parameters.Add(cmd, "@lastPlayedDate", DbType.DateTime).Value = userItemData.LastPlayedDate; 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; cmd.Transaction = transaction;
@ -275,7 +282,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand()) 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, "@key", DbType.String).Value = key;
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId; cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@ -310,7 +317,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand()) 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; cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@ -350,6 +357,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
userData.LastPlayedDate = reader.GetDateTime(7).ToUniversalTime(); 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; return userData;
} }

View File

@ -680,7 +680,7 @@ namespace MediaBrowser.Server.Implementations.Session
foreach (var user in users) 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(); 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) if (positionTicks.HasValue)
{ {
_userDataRepository.UpdatePlayState(item, data, positionTicks.Value); _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> /// </summary>
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
/// <returns>Task{SessionInfo}.</returns> /// <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 var user = _userManager.Users
.FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase)); .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 (enforcePassword)
if (!result)
{ {
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); var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.AppVersion, request.DeviceName).ConfigureAwait(false);
@ -1311,6 +1349,7 @@ namespace MediaBrowser.Server.Implementations.Session
}; };
} }
private async Task<string> GetAuthorizationToken(string userId, string deviceId, string app, string appVersion, string deviceName) private async Task<string> GetAuthorizationToken(string userId, string deviceId, string app, string appVersion, string deviceName)
{ {
var existing = _authRepo.Get(new AuthenticationInfoQuery var existing = _authRepo.Get(new AuthenticationInfoQuery

View File

@ -19,6 +19,7 @@ using MediaBrowser.Model.Sync;
using MoreLinq; using MoreLinq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -125,7 +126,23 @@ namespace MediaBrowser.Server.Implementations.Sync
private string GetSyncJobItemName(BaseItem item) 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) public Task UpdateJobStatus(string id)
@ -699,7 +716,7 @@ namespace MediaBrowser.Server.Implementations.Sync
var path = Path.Combine(temporaryPath, filename); 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)) using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, cancellationToken).ConfigureAwait(false))
{ {

View File

@ -3,7 +3,7 @@
<package id="CommonIO" version="1.0.0.7" targetFramework="net45" /> <package id="CommonIO" version="1.0.0.7" targetFramework="net45" />
<package id="Emby.XmlTv" version="1.0.0.48" 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="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="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" /> <package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" /> <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />

View File

@ -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); ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LogManager.GetLogger("ChannelManager"), ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
RegisterSingleInstance(ChannelManager); 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); RegisterSingleInstance(MediaSourceManager);
SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);

View File

@ -140,6 +140,9 @@
<Content Include="dashboard-ui\components\remotecontrolautoplay.js"> <Content Include="dashboard-ui\components\remotecontrolautoplay.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\devices\windowsphone\wp.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\legacy\buttonenabled.js"> <Content Include="dashboard-ui\legacy\buttonenabled.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -254,9 +257,6 @@
<Content Include="dashboard-ui\favorites.html"> <Content Include="dashboard-ui\favorites.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<None Include="dashboard-ui\legacy\deferred.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="dashboard-ui\livetvguideprovider.html"> <Content Include="dashboard-ui\livetvguideprovider.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -275,10 +275,10 @@
<Content Include="dashboard-ui\mysyncsettings.html"> <Content Include="dashboard-ui\mysyncsettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\robots.txt"> <Content Include="dashboard-ui\pin.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\scripts\actionsheet.js"> <Content Include="dashboard-ui\robots.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\scripts\globalize.js"> <Content Include="dashboard-ui\scripts\globalize.js">
@ -317,6 +317,9 @@
<Content Include="dashboard-ui\scripts\autoorganizesmart.js"> <Content Include="dashboard-ui\scripts\autoorganizesmart.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\scripts\pin.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\searchmenu.js"> <Content Include="dashboard-ui\scripts\searchmenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>