mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
Merge branch 'dev' of https://github.com/MediaBrowser/Emby into dev
This commit is contained in:
commit
15a98c5eaa
@ -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);
|
||||||
|
@ -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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
@ -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; }
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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<ConnectSupporterSummary>.</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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -78,7 +78,17 @@ 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
|
||||||
/// This should never be serialized.
|
/// This should never be serialized.
|
||||||
|
@ -46,6 +46,9 @@ namespace MediaBrowser.Controller.LiveTv
|
|||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task<List<MediaSourceInfo>>.</returns>
|
/// <returns>Task<List<MediaSourceInfo>>.</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>
|
||||||
|
@ -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>();
|
||||||
|
@ -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.
|
||||||
|
@ -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<AuthenticationResult>.</returns>
|
||||||
|
Task<AuthenticationResult> CreateNewSession(AuthenticationRequest request);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reports the capabilities.
|
/// Reports the capabilities.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
@ -139,7 +144,7 @@ namespace MediaBrowser.Dlna.Ssdp
|
|||||||
//var protocol = isMulticast ? "Multicast" : "Unicast";
|
//var protocol = isMulticast ? "Multicast" : "Unicast";
|
||||||
//var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
|
//var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
|
||||||
//_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
|
//_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +164,7 @@ namespace MediaBrowser.Dlna.Ssdp
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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)
|
||||||
@ -577,12 +591,27 @@ namespace MediaBrowser.Dlna.Ssdp
|
|||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
catch (ObjectDisposedException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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");
|
||||||
|
@ -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"},
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
@ -231,6 +232,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)
|
||||||
|
@ -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; }
|
||||||
|
|
||||||
|
@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>();
|
||||||
|
@ -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>
|
||||||
|
@ -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))
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -502,7 +502,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return series ?? new Series();
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
@ -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 =>
|
||||||
@ -349,11 +384,14 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||||||
var json = _jsonSerializer.SerializeToString(mediaSource);
|
var json = _jsonSerializer.SerializeToString(mediaSource);
|
||||||
_logger.Debug("Live stream opened: " + json);
|
_logger.Debug("Live stream opened: " + json);
|
||||||
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
|
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
|
||||||
|
|
||||||
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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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");
|
||||||
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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" />
|
||||||
|
@ -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))
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
@ -1310,7 +1348,8 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||||||
ServerId = _appHost.SystemId
|
ServerId = _appHost.SystemId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -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))
|
||||||
{
|
{
|
||||||
|
@ -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" />
|
||||||
|
@ -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);
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user