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

View File

@ -1,10 +1,8 @@
using System;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Connect;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Dto;
using ServiceStack;
using System.Collections.Generic;
using System.Linq;
@ -75,28 +73,6 @@ namespace MediaBrowser.Api
public string ConnectUserId { get; set; }
}
[Route("/Connect/Supporters", "GET")]
[Authenticated(Roles = "Admin")]
public class GetConnectSupporterSummary : IReturn<ConnectSupporterSummary>
{
}
[Route("/Connect/Supporters", "DELETE")]
[Authenticated(Roles = "Admin")]
public class RemoveConnectSupporter : IReturnVoid
{
[ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
public string Id { get; set; }
}
[Route("/Connect/Supporters", "POST")]
[Authenticated(Roles = "Admin")]
public class AddConnectSupporter : IReturnVoid
{
[ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string Id { get; set; }
}
public class ConnectService : BaseApiService
{
private readonly IConnectManager _connectManager;
@ -108,35 +84,6 @@ namespace MediaBrowser.Api
_userManager = userManager;
}
public async Task<object> Get(GetConnectSupporterSummary request)
{
var result = await _connectManager.GetConnectSupporterSummary().ConfigureAwait(false);
var existingConnectUserIds = result.Users.Select(i => i.Id).ToList();
result.EligibleUsers = _userManager.Users
.Where(i => !string.IsNullOrWhiteSpace(i.ConnectUserId))
.Where(i => !existingConnectUserIds.Contains(i.ConnectUserId, StringComparer.OrdinalIgnoreCase))
.OrderBy(i => i.Name)
.Select(i => _userManager.GetUserDto(i))
.ToList();
return ToOptimizedResult(result);
}
public void Delete(RemoveConnectSupporter request)
{
var task = _connectManager.RemoveConnectSupporter(request.Id);
Task.WaitAll(task);
}
public void Post(AddConnectSupporter request)
{
var task = _connectManager.AddConnectSupporter(request.Id);
Task.WaitAll(task);
}
public object Post(CreateConnectLink request)
{
return _connectManager.LinkUser(request.Id, request.ConnectUsername);

View File

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

View File

@ -1462,6 +1462,13 @@ namespace MediaBrowser.Api.Playback
{
// Duplicating ItemId because of MediaMonkey
}
else if (i == 24)
{
if (videoRequest != null)
{
videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
}
}
}
}
@ -2021,6 +2028,11 @@ namespace MediaBrowser.Api.Playback
state.EstimateContentLength = transcodingProfile.EstimateContentLength;
state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
if (state.VideoRequest != null)
{
state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
}
}
}
}
@ -2184,9 +2196,9 @@ namespace MediaBrowser.Api.Playback
if (state.VideoRequest != null)
{
if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase))
if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.VideoRequest.CopyTimestamps)
{
//inputModifier += " -noaccurate_seek";
inputModifier += " -noaccurate_seek";
}
}

View File

@ -137,9 +137,9 @@ namespace MediaBrowser.Api.Playback.Progressive
var isOutputMkv = string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase);
if (state.RunTimeTicks.HasValue)
if (state.RunTimeTicks.HasValue && state.VideoRequest.CopyTimestamps)
{
//args += " -copyts -avoid_negative_ts disabled -start_at_zero";
args += " -copyts -avoid_negative_ts disabled -start_at_zero";
}
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))

View File

@ -187,6 +187,9 @@ namespace MediaBrowser.Api.Playback
[ApiMember(Name = "EnableAutoStreamCopy", Description = "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool EnableAutoStreamCopy { get; set; }
[ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool CopyTimestamps { get; set; }
[ApiMember(Name = "Cabac", Description = "Enable if cabac encoding is required", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? Cabac { get; set; }

View File

@ -415,23 +415,6 @@ namespace MediaBrowser.Api
{
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
if (string.IsNullOrWhiteSpace(auth.Client))
{
auth.Client = "Unknown app";
}
if (string.IsNullOrWhiteSpace(auth.Device))
{
auth.Device = "Unknown device";
}
if (string.IsNullOrWhiteSpace(auth.Version))
{
auth.Version = "Unknown version";
}
if (string.IsNullOrWhiteSpace(auth.DeviceId))
{
auth.DeviceId = "Unknown device id";
}
var result = await _sessionMananger.AuthenticateNewSession(new AuthenticationRequest
{
App = auth.Client,

View File

@ -76,25 +76,5 @@ namespace MediaBrowser.Controller.Connect
/// <param name="token">The token.</param>
/// <returns><c>true</c> if [is authorization token valid] [the specified token]; otherwise, <c>false</c>.</returns>
bool IsAuthorizationTokenValid(string token);
/// <summary>
/// Gets the connect supporter summary.
/// </summary>
/// <returns>Task&lt;ConnectSupporterSummary&gt;.</returns>
Task<ConnectSupporterSummary> GetConnectSupporterSummary();
/// <summary>
/// Removes the connect supporter.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>Task.</returns>
Task RemoveConnectSupporter(string id);
/// <summary>
/// Adds the connect supporter.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>Task.</returns>
Task AddConnectSupporter(string id);
}
}

View File

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

View File

@ -78,7 +78,17 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <value><c>true</c> if played; otherwise, <c>false</c>.</value>
public bool Played { get; set; }
/// <summary>
/// Gets or sets the index of the audio stream.
/// </summary>
/// <value>The index of the audio stream.</value>
public int? AudioStreamIndex { get; set; }
/// <summary>
/// Gets or sets the index of the subtitle stream.
/// </summary>
/// <value>The index of the subtitle stream.</value>
public int? SubtitleStreamIndex { get; set; }
/// <summary>
/// This is an interpreted property to indicate likes or dislikes
/// This should never be serialized.

View File

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

View File

@ -59,6 +59,12 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The clients.</value>
public List<string> Clients { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance can reset.
/// </summary>
/// <value><c>true</c> if this instance can reset; otherwise, <c>false</c>.</value>
public bool CanReset { get; set; }
public LiveTvTunerInfo()
{
Clients = new List<string>();

View File

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

View File

@ -250,6 +250,13 @@ namespace MediaBrowser.Controller.Session
/// <returns>Task{SessionInfo}.</returns>
Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request);
/// <summary>
/// Creates the new session.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>Task&lt;AuthenticationResult&gt;.</returns>
Task<AuthenticationResult> CreateNewSession(AuthenticationRequest request);
/// <summary>
/// Reports the capabilities.
/// </summary>

View File

@ -132,6 +132,8 @@ namespace MediaBrowser.Dlna.Ssdp
return;
}
_ssdpHandler.LogMessageReceived(args, true);
TryCreateDevice(args);
}
}
@ -219,14 +221,6 @@ namespace MediaBrowser.Dlna.Ssdp
return;
}
if (_config.GetDlnaConfiguration().EnableDebugLog)
{
var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
var headerText = string.Join(",", headerTexts.ToArray());
_logger.Debug("{0} Device message received from {1}. Headers: {2}", args.Method, args.EndPoint, headerText);
}
EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
}

View File

@ -93,17 +93,7 @@ namespace MediaBrowser.Dlna.Ssdp
return;
}
var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
if (enableDebugLogging)
{
var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
var headerText = string.Join(",", headerTexts.ToArray());
var protocol = isMulticast ? "Multicast" : "Unicast";
var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
_logger.Debug("{0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
}
LogMessageReceived(args, isMulticast);
var headers = args.Headers;
string st;
@ -125,6 +115,21 @@ namespace MediaBrowser.Dlna.Ssdp
EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger);
}
internal void LogMessageReceived(SsdpMessageEventArgs args, bool isMulticast)
{
var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
if (enableDebugLogging)
{
var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
var headerText = string.Join(",", headerTexts.ToArray());
var protocol = isMulticast ? "Multicast" : "Unicast";
var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
_logger.Debug("{0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
}
}
internal bool IgnoreMessage(SsdpMessageEventArgs args, bool isMulticast)
{
string usn;
@ -139,7 +144,7 @@ namespace MediaBrowser.Dlna.Ssdp
//var protocol = isMulticast ? "Multicast" : "Unicast";
//var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
//_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
return true;
}
}
@ -159,7 +164,7 @@ namespace MediaBrowser.Dlna.Ssdp
return true;
}
}
return false;
}
@ -298,9 +303,17 @@ namespace MediaBrowser.Dlna.Ssdp
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
SendDatagram(msg, endpoint, null, false, 1);
SendDatagram(msg, endpoint, new IPEndPoint(d.Address, 0), false, 1);
//SendDatagram(header, values, endpoint, null, true);
var ipEndPoint = endpoint as IPEndPoint;
if (ipEndPoint != null)
{
SendUnicastRequest(msg, ipEndPoint);
}
else
{
SendDatagram(msg, endpoint, null, false, 2);
SendDatagram(msg, endpoint, new IPEndPoint(d.Address, 0), false, 2);
//SendDatagram(header, values, endpoint, null, true);
}
if (enableDebugLogging)
{
@ -473,6 +486,7 @@ namespace MediaBrowser.Dlna.Ssdp
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
SendDatagram(msg, _ssdpEndp, new IPEndPoint(dev.Address, 0), true);
//SendUnicastRequest(msg, 1);
}
public void RegisterNotification(string uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services)
@ -577,12 +591,27 @@ namespace MediaBrowser.Dlna.Ssdp
}
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)
{
@ -592,18 +621,16 @@ namespace MediaBrowser.Dlna.Ssdp
_logger.Debug("Sending unicast search request");
byte[] req = Encoding.ASCII.GetBytes(request);
var ipSsdp = IPAddress.Parse(SSDPAddr);
var ipTxEnd = new IPEndPoint(ipSsdp, SSDPPort);
try
{
for (var i = 0; i < 3; i++)
for (var i = 0; i < sendCount; i++)
{
if (i > 0)
{
await Task.Delay(50).ConfigureAwait(false);
}
_unicastClient.Send(req, req.Length, ipTxEnd);
_unicastClient.Send(req, req.Length, toEndPoint);
}
}
catch (Exception ex)

View File

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

View File

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

View File

@ -27,7 +27,7 @@ namespace MediaBrowser.MediaEncoding.Probing
public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType videoType, bool isAudio, string path, MediaProtocol protocol)
{
var info = new Model.MediaInfo.MediaInfo
var info = new MediaInfo
{
Path = path,
Protocol = protocol
@ -56,40 +56,81 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
if (isAudio)
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);
// 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)
if (tagStream != null && tagStream.tags != null)
{
var audioStream = data.streams.FirstOrDefault(i => string.Equals(i.codec_type, "audio", StringComparison.OrdinalIgnoreCase));
if (audioStream != null && audioStream.tags != null)
{
foreach (var pair in audioStream.tags)
{
tags[pair.Key] = pair.Value;
}
}
}
if (data.format != null && data.format.tags != null)
{
foreach (var pair in data.format.tags)
foreach (var pair in tagStream.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;
}
}
FetchGenres(info, tags);
var overview = FFProbeHelpers.GetDictionaryValue(tags, "description");
if (!string.IsNullOrWhiteSpace(overview))
{
info.Overview = overview;
}
var title = FFProbeHelpers.GetDictionaryValue(tags, "title");
if (!string.IsNullOrWhiteSpace(title))
{
info.Name = title;
}
info.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
// Several different forms of retaildate
info.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "date");
if (isAudio)
{
SetAudioRuntimeTicks(data, info);
// tags are normally located under data.format, but we've seen some cases with ogg where they're part of the audio stream
// so let's create a combined list of both
SetAudioInfoFromTags(info, tags);
}
else
{
var iTunEXTC = FFProbeHelpers.GetDictionaryValue(tags, "iTunEXTC");
if (!string.IsNullOrWhiteSpace(iTunEXTC))
{
var parts = iTunEXTC.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
// Example
// mpaa|G|100|For crude humor
if (parts.Length == 4)
{
info.OfficialRating = parts[1];
info.OfficialRatingDescription = parts[3];
}
}
var itunesXml = FFProbeHelpers.GetDictionaryValue(tags, "iTunMOVI");
if (!string.IsNullOrWhiteSpace(itunesXml))
{
FetchFromItunesInfo(itunesXml, info);
}
if (data.format != null && !string.IsNullOrEmpty(data.format.duration))
{
info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, _usCulture)).Ticks;
@ -108,6 +149,11 @@ namespace MediaBrowser.MediaEncoding.Probing
return info;
}
private void FetchFromItunesInfo(string xml, MediaInfo info)
{
// <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>cast</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Blender Foundation</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Janus Bager Kristensen</string>\n\t\t</dict>\n\t</array>\n\t<key>directors</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Sacha Goedegebure</string>\n\t\t</dict>\n\t</array>\n\t<key>studio</key>\n\t<string>Blender Foundation</string>\n</dict>\n</plist>\n
}
/// <summary>
/// Converts ffprobe stream info to our MediaStream class
/// </summary>
@ -430,16 +476,8 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
private void SetAudioInfoFromTags(Model.MediaInfo.MediaInfo audio, Dictionary<string, string> tags)
private void SetAudioInfoFromTags(MediaInfo audio, Dictionary<string, string> tags)
{
var title = FFProbeHelpers.GetDictionaryValue(tags, "title");
// Only set Name if title was found in the dictionary
if (!string.IsNullOrEmpty(title))
{
audio.Title = title;
}
var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
if (!string.IsNullOrWhiteSpace(composer))
{
@ -458,6 +496,26 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist");
if (!string.IsNullOrWhiteSpace(lyricist))
{
foreach (var person in Split(lyricist, false))
{
audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist });
}
}
// Check for writer some music is tagged that way as alternative to composer/lyricist
var writer = FFProbeHelpers.GetDictionaryValue(tags, "writer");
if (!string.IsNullOrWhiteSpace(writer))
{
foreach (var person in Split(writer, false))
{
audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer });
}
}
audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists");
@ -511,22 +569,12 @@ namespace MediaBrowser.MediaEncoding.Probing
// Disc number
audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc");
audio.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
// Several different forms of retaildate
audio.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
FFProbeHelpers.GetDictionaryDateTime(tags, "date");
// If we don't have a ProductionYear try and get it from PremiereDate
if (audio.PremiereDate.HasValue && !audio.ProductionYear.HasValue)
{
audio.ProductionYear = audio.PremiereDate.Value.ToLocalTime().Year;
}
FetchGenres(audio, tags);
// There's several values in tags may or may not be present
FetchStudios(audio, tags, "organization");
FetchStudios(audio, tags, "ensemble");
@ -693,7 +741,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <param name="info">The information.</param>
/// <param name="tags">The tags.</param>
private void FetchGenres(Model.MediaInfo.MediaInfo info, Dictionary<string, string> tags)
private void FetchGenres(MediaInfo info, Dictionary<string, string> tags)
{
var val = FFProbeHelpers.GetDictionaryValue(tags, "genre");
@ -764,7 +812,7 @@ namespace MediaBrowser.MediaEncoding.Probing
private const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
private void FetchWtvInfo(Model.MediaInfo.MediaInfo video, InternalMediaInfoResult data)
private void FetchWtvInfo(MediaInfo video, InternalMediaInfoResult data)
{
if (data.format == null || data.format.tags == null)
{
@ -775,15 +823,16 @@ namespace MediaBrowser.MediaEncoding.Probing
if (!string.IsNullOrWhiteSpace(genres))
{
//genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre");
}
if (!string.IsNullOrWhiteSpace(genres))
{
video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
var genreList = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Trim())
.ToList();
// If this is empty then don't overwrite genres that might have been fetched earlier
if (genreList.Count > 0)
{
video.Genres = genreList;
}
}
var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -64,6 +64,12 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The clients.</value>
public List<string> Clients { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance can reset.
/// </summary>
/// <value><c>true</c> if this instance can reset; otherwise, <c>false</c>.</value>
public bool CanReset { get; set; }
public LiveTvTunerInfoDto()
{
Clients = new List<string>();

View File

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

View File

@ -125,9 +125,9 @@ namespace MediaBrowser.Providers.MediaInfo
private async Task FetchDataFromTags(Audio audio, Model.MediaInfo.MediaInfo data)
{
// Only set Name if title was found in the dictionary
if (!string.IsNullOrEmpty(data.Title))
if (!string.IsNullOrEmpty(data.Name))
{
audio.Name = data.Title;
audio.Name = data.Name;
}
if (!audio.LockedFields.Contains(MetadataFields.Cast))

View File

@ -383,6 +383,11 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
if (!string.IsNullOrWhiteSpace(data.OfficialRatingDescription) || isFullRefresh)
{
video.OfficialRatingDescription = data.OfficialRatingDescription;
}
if (!video.LockedFields.Contains(MetadataFields.Genres))
{
if (video.Genres.Count == 0 || isFullRefresh)
@ -437,6 +442,13 @@ namespace MediaBrowser.Providers.MediaInfo
video.ParentIndexNumber = data.ParentIndexNumber;
}
}
if (!string.IsNullOrWhiteSpace(data.Name))
{
if (string.IsNullOrWhiteSpace(video.Name) || string.Equals(video.Name, Path.GetFileNameWithoutExtension(video.Path), StringComparison.OrdinalIgnoreCase))
{
video.Name = data.Name;
}
}
// If we don't have a ProductionYear try and get it from PremiereDate
if (video.PremiereDate.HasValue && !video.ProductionYear.HasValue)

View File

@ -10,7 +10,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
@ -24,7 +23,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Connect
{
@ -121,7 +119,6 @@ namespace MediaBrowser.Server.Implementations.Connect
_securityManager = securityManager;
_fileSystem = fileSystem;
_userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated;
_config.ConfigurationUpdated += _config_ConfigurationUpdated;
LoadCachedData();
@ -1071,90 +1068,6 @@ namespace MediaBrowser.Server.Implementations.Connect
}
}
public async Task<ConnectSupporterSummary> GetConnectSupporterSummary()
{
var url = GetConnectUrl("keyAssociation");
var options = new HttpRequestOptions
{
Url = url,
CancellationToken = CancellationToken.None
};
var postData = new Dictionary<string, string>
{
{"serverId", ConnectServerId},
{"supporterKey", _securityManager.SupporterKey}
};
options.SetPostData(postData);
SetServerAccessToken(options);
SetApplicationHeader(options);
// No need to examine the response
using (var stream = (await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)).Content)
{
return _json.DeserializeFromStream<ConnectSupporterSummary>(stream);
}
}
public async Task AddConnectSupporter(string id)
{
var url = GetConnectUrl("keyAssociation");
var options = new HttpRequestOptions
{
Url = url,
CancellationToken = CancellationToken.None
};
var postData = new Dictionary<string, string>
{
{"serverId", ConnectServerId},
{"supporterKey", _securityManager.SupporterKey},
{"userId", id}
};
options.SetPostData(postData);
SetServerAccessToken(options);
SetApplicationHeader(options);
// No need to examine the response
using (var stream = (await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)).Content)
{
}
}
public async Task RemoveConnectSupporter(string id)
{
var url = GetConnectUrl("keyAssociation");
var options = new HttpRequestOptions
{
Url = url,
CancellationToken = CancellationToken.None
};
var postData = new Dictionary<string, string>
{
{"serverId", ConnectServerId},
{"supporterKey", _securityManager.SupporterKey},
{"userId", id}
};
options.SetPostData(postData);
SetServerAccessToken(options);
SetApplicationHeader(options);
// No need to examine the response
using (var stream = (await _httpClient.SendAsync(options, "DELETE").ConfigureAwait(false)).Content)
{
}
}
public async Task Authenticate(string username, string passwordMd5)
{
if (string.IsNullOrWhiteSpace(username))
@ -1186,64 +1099,6 @@ namespace MediaBrowser.Server.Implementations.Connect
}
}
async void _userManager_UserConfigurationUpdated(object sender, GenericEventArgs<User> e)
{
var user = e.Argument;
await TryUploadUserPreferences(user, CancellationToken.None).ConfigureAwait(false);
}
private async Task TryUploadUserPreferences(User user, CancellationToken cancellationToken)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
if (string.IsNullOrEmpty(user.ConnectUserId))
{
return;
}
if (string.IsNullOrEmpty(ConnectAccessKey))
{
return;
}
var url = GetConnectUrl("user/preferences");
url += "?userId=" + user.ConnectUserId;
url += "&key=userpreferences";
var options = new HttpRequestOptions
{
Url = url,
CancellationToken = cancellationToken
};
var postData = new Dictionary<string, string>();
postData["data"] = _json.SerializeToString(ConnectUserPreferences.FromUserConfiguration(user.Configuration));
options.SetPostData(postData);
SetServerAccessToken(options);
SetApplicationHeader(options);
try
{
// No need to examine the response
using (var stream = (await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)).Content)
{
}
}
catch (Exception ex)
{
_logger.ErrorException("Error uploading user preferences", ex);
}
}
private async Task DownloadUserPreferences(User user, CancellationToken cancellationToken)
{
}
public async Task<User> GetLocalUser(string connectUserId)
{
var user = _userManager.Users

View File

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

View File

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

View File

@ -348,6 +348,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer
return Task.FromResult(true);
}
if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl("web/pin.html");
return Task.FromResult(true);
}
if (!string.IsNullOrWhiteSpace(GlobalResponse))
{
httpRes.StatusCode = 503;

View File

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

View File

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

View File

@ -30,8 +30,9 @@ namespace MediaBrowser.Server.Implementations.Library
private IMediaSourceProvider[] _providers;
private readonly ILogger _logger;
private readonly IUserDataManager _userDataManager;
public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem)
public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IUserDataManager userDataManager)
{
_itemRepo = itemRepo;
_userManager = userManager;
@ -39,6 +40,7 @@ namespace MediaBrowser.Server.Implementations.Library
_logger = logger;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
_userDataManager = userDataManager;
}
public void AddParts(IEnumerable<IMediaSourceProvider> providers)
@ -140,7 +142,7 @@ namespace MediaBrowser.Server.Implementations.Library
{
if (user != null)
{
SetUserProperties(source, user);
SetUserProperties(hasMediaSources, source, user);
}
if (source.Protocol == MediaProtocol.File)
{
@ -257,25 +259,38 @@ namespace MediaBrowser.Server.Implementations.Library
{
foreach (var source in sources)
{
SetUserProperties(source, user);
SetUserProperties(item, source, user);
}
}
return sources;
}
private void SetUserProperties(MediaSourceInfo source, User user)
private void SetUserProperties(IHasUserData item, MediaSourceInfo source, User user)
{
var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
? new string[] { }
: new[] { user.Configuration.AudioLanguagePreference };
var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
SetDefaultAudioStreamIndex(source, userData, user);
SetDefaultSubtitleStreamIndex(source, userData, user);
}
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user)
{
if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections)
{
var index = userData.SubtitleStreamIndex.Value;
// Make sure the saved index is still valid
if (index == -1 || source.MediaStreams.Any(i => i.Type == MediaStreamType.Subtitle && i.Index == index))
{
source.DefaultSubtitleStreamIndex = index;
return;
}
}
var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
? new List<string> { }
: new List<string> { user.Configuration.SubtitleLanguagePreference };
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
var defaultAudioIndex = source.DefaultAudioStreamIndex;
var audioLangage = defaultAudioIndex == null
? null
@ -290,6 +305,26 @@ namespace MediaBrowser.Server.Implementations.Library
user.Configuration.SubtitleMode, audioLangage);
}
private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user)
{
if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections)
{
var index = userData.AudioStreamIndex.Value;
// Make sure the saved index is still valid
if (source.MediaStreams.Any(i => i.Type == MediaStreamType.Audio && i.Index == index))
{
source.DefaultAudioStreamIndex = index;
return;
}
}
var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
? new string[] { }
: new[] { user.Configuration.AudioLanguagePreference };
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
}
private IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
{
return sources.OrderBy(i =>
@ -349,11 +384,14 @@ namespace MediaBrowser.Server.Implementations.Library
var json = _jsonSerializer.SerializeToString(mediaSource);
_logger.Debug("Live stream opened: " + json);
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
if (!string.IsNullOrWhiteSpace(request.UserId))
{
var user = _userManager.GetUserById(request.UserId);
SetUserProperties(clone, user);
var item = string.IsNullOrWhiteSpace(request.ItemId)
? null
: _libraryManager.GetItemById(request.ItemId);
SetUserProperties(item, clone, user);
}
return new LiveStreamResponse

View File

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

View File

@ -116,7 +116,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
videoArgs = "-codec:v:0 copy";
}
var commandLineArgs = "-fflags +genpts -i \"{0}\" -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -i \"{0}\" -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
if (mediaSource.ReadAtNativeFramerate)
{
@ -143,7 +143,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
audioChannels = audioStream.Channels ?? audioChannels;
}
return "-codec:a:0 aac -strict experimental -ab 320000 -ac " + audioChannels.ToString(CultureInfo.InvariantCulture);
return "-codec:a:0 aac -strict experimental -ab 320000";
}
private bool EncodeVideo(MediaSourceInfo mediaSource)

View File

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

View File

@ -801,11 +801,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
if (!string.IsNullOrWhiteSpace(info.ImagePath))
{
item.SetImagePath(ImageType.Primary, info.ImagePath);
item.SetImage(new ItemImageInfo
{
Path = info.ImagePath,
Type = ImageType.Primary,
IsPlaceholder = true
}, 0);
}
else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
{
item.SetImagePath(ImageType.Primary, info.ImageUrl);
item.SetImage(new ItemImageInfo
{
Path = info.ImageUrl,
Type = ImageType.Primary,
IsPlaceholder = true
}, 0);
}
}
@ -2343,7 +2353,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
throw new ResourceNotFoundException();
}
await provider.Validate(info).ConfigureAwait(false);
var configurable = provider as IConfigurableTunerHost;
if (configurable != null)
{
await configurable.Validate(info).ConfigureAwait(false);
}
var config = GetConfiguration();

View File

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

View File

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

View File

@ -8,19 +8,17 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
public class M3UTunerHost : BaseTunerHost, ITunerHost
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient;
@ -46,65 +44,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
{
var urlHash = info.Url.GetMD5().ToString("N");
// Read the file and display it line by line.
using (var reader = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false)))
{
return GetChannels(reader, urlHash);
}
}
private List<M3UChannel> GetChannels(StreamReader reader, string urlHash)
{
var channels = new List<M3UChannel>();
string channnelName = null;
string channelNumber = null;
string line;
while ((line = reader.ReadLine()) != null)
{
line = line.Trim();
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (line.StartsWith("#EXTINF:", StringComparison.OrdinalIgnoreCase))
{
line = line.Substring(8);
Logger.Info("Found m3u channel: {0}", line);
var parts = line.Split(new[] { ',' }, 2);
channelNumber = parts[0];
channnelName = parts[1];
}
else if (!string.IsNullOrWhiteSpace(channelNumber))
{
channels.Add(new M3UChannel
{
Name = channnelName,
Number = channelNumber,
Id = ChannelIdPrefix + urlHash + line.GetMD5().ToString("N"),
Path = line
});
channelNumber = null;
channnelName = null;
}
}
return channels;
return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(info.Url, ChannelIdPrefix, cancellationToken).ConfigureAwait(false);
}
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
{
var list = GetConfiguration().TunerHosts
.Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
var list = GetTunerHosts()
.Select(i => new LiveTvTunerInfo()
{
Name = Name,
@ -125,18 +70,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return sources.First();
}
class M3UChannel : ChannelInfo
{
public string Path { get; set; }
public M3UChannel()
{
}
}
public async Task Validate(TunerHostInfo info)
{
using (var stream = await GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
using (var stream = await new M3uParser(Logger, _fileSystem, _httpClient).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
{
}
@ -147,15 +83,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
private Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
{
if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
return _httpClient.Get(info.Url, cancellationToken);
}
return Task.FromResult(_fileSystem.OpenRead(info.Url));
}
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
{
var urlHash = info.Url.GetMD5().ToString("N");

View File

@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
public class M3uParser
{
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient;
public M3uParser(ILogger logger, IFileSystem fileSystem, IHttpClient httpClient)
{
_logger = logger;
_fileSystem = fileSystem;
_httpClient = httpClient;
}
public async Task<List<M3UChannel>> Parse(string url, string channelIdPrefix, CancellationToken cancellationToken)
{
var urlHash = url.GetMD5().ToString("N");
// Read the file and display it line by line.
using (var reader = new StreamReader(await GetListingsStream(url, cancellationToken).ConfigureAwait(false)))
{
return GetChannels(reader, urlHash, channelIdPrefix);
}
}
public Task<Stream> GetListingsStream(string url, CancellationToken cancellationToken)
{
if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
return _httpClient.Get(url, cancellationToken);
}
return Task.FromResult(_fileSystem.OpenRead(url));
}
private List<M3UChannel> GetChannels(StreamReader reader, string urlHash, string channelIdPrefix)
{
var channels = new List<M3UChannel>();
string channnelName = null;
string channelNumber = null;
string line;
string imageUrl = null;
while ((line = reader.ReadLine()) != null)
{
line = line.Trim();
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
{
continue;
}
if (line.StartsWith("#EXTINF:", StringComparison.OrdinalIgnoreCase))
{
line = line.Substring(8);
_logger.Info("Found m3u channel: {0}", line);
var parts = line.Split(new[] { ',' }, 2);
channelNumber = parts[0].Trim().Split(' ')[0] ?? "0";
channnelName = FindProperty("tvg-name", line, parts[1]);
imageUrl = FindProperty("tvg-logo", line, null);
}
else if (!string.IsNullOrWhiteSpace(channelNumber))
{
channels.Add(new M3UChannel
{
Name = channnelName,
Number = channelNumber,
Id = channelIdPrefix + urlHash + line.GetMD5().ToString("N"),
ImageUrl = imageUrl
});
imageUrl = null;
channelNumber = null;
channnelName = null;
}
}
return channels;
}
public string FindProperty(string property, string properties, string defaultResult = "")
{
var reg = new Regex(@"([a-z0-9\-_]+)=\""([^""]+)\""", RegexOptions.IgnoreCase);
var matches = reg.Matches(properties);
foreach (Match match in matches)
{
if (match.Groups[1].Value == property)
{
return match.Groups[2].Value;
}
}
return defaultResult;
}
}
public class M3UChannel : ChannelInfo
{
public string Path { get; set; }
}
}

View File

@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@ -13,6 +17,7 @@ using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
@ -24,14 +29,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
private readonly ILiveTvManager _liveTvManager;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IHttpClient _httpClient;
private readonly IJsonSerializer _json;
public SatIpDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient)
public static SatIpDiscovery Current;
private readonly List<TunerHostInfo> _discoveredHosts = new List<TunerHostInfo>();
public List<TunerHostInfo> DiscoveredHosts
{
get { return _discoveredHosts.ToList(); }
}
public SatIpDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient, IJsonSerializer json)
{
_deviceDiscovery = deviceDiscovery;
_config = config;
_logger = logger;
_liveTvManager = liveTvManager;
_httpClient = httpClient;
_json = json;
Current = this;
}
public void Run()
@ -42,7 +59,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
{
string st = null;
if (e.Headers.TryGetValue("ST", out st) && string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
string nt = null;
e.Headers.TryGetValue("ST", out st);
e.Headers.TryGetValue("NT", out nt);
if (string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(nt, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
{
string location;
if (e.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
@ -61,26 +83,23 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
try
{
var options = GetConfiguration();
if (_discoveredHosts.Any(i => string.Equals(i.Type, SatIpHost.DeviceType, StringComparison.OrdinalIgnoreCase) && string.Equals(location, i.Url, StringComparison.OrdinalIgnoreCase)))
{
return;
}
//if (options.TunerHosts.Any(i =>
// string.Equals(i.Type, SatIpHost.DeviceType, StringComparison.OrdinalIgnoreCase) &&
// UriEquals(i.Url, url)))
//{
// return;
//}
_logger.Debug("Will attempt to add SAT device {0}", location);
var info = await GetInfo(location, CancellationToken.None).ConfigureAwait(false);
//// Strip off the port
//url = new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped).TrimEnd('/');
_discoveredHosts.Add(info);
}
catch (OperationCanceledException)
{
//await TestUrl(url).ConfigureAwait(false);
}
catch (NotImplementedException)
{
//await _liveTvManager.SaveTunerHost(new TunerHostInfo
//{
// Type = SatIpHost.DeviceType,
// Url = url
//}).ConfigureAwait(false);
}
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 async Task<SatIpTunerHostInfo> GetInfo(string url, CancellationToken cancellationToken)
{
var result = new SatIpTunerHostInfo
{
Url = url,
IsEnabled = true,
Type = SatIpHost.DeviceType,
Tuners = 1,
TunersAvailable = 1
};
using (var stream = await _httpClient.Get(url, cancellationToken).ConfigureAwait(false))
{
using (var streamReader = new StreamReader(stream))
{
// Use XmlReader for best performance
using (var reader = XmlReader.Create(streamReader))
{
reader.MoveToContent();
// Loop through each element
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{
case "device":
using (var subtree = reader.ReadSubtree())
{
FillFromDeviceNode(result, subtree);
}
break;
default:
reader.Skip();
break;
}
}
}
}
}
}
if (string.IsNullOrWhiteSpace(result.Id))
{
throw new NotImplementedException();
}
// Device hasn't implemented an m3u list
if (string.IsNullOrWhiteSpace(result.M3UUrl))
{
result.IsEnabled = false;
}
else if (!result.M3UUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
var fullM3uUrl = url.Substring(0, url.LastIndexOf('/'));
result.M3UUrl = fullM3uUrl + "/" + result.M3UUrl.TrimStart('/');
}
_logger.Debug("SAT device result: {0}", _json.SerializeToString(result));
return result;
}
private void FillFromDeviceNode(SatIpTunerHostInfo info, XmlReader reader)
{
reader.MoveToContent();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.LocalName)
{
case "UDN":
{
info.Id = reader.ReadElementContentAsString();
break;
}
case "friendlyName":
{
info.FriendlyName = reader.ReadElementContentAsString();
break;
}
case "satip:X_SATIPCAP":
case "X_SATIPCAP":
{
// <satip:X_SATIPCAP xmlns:satip="urn:ses-com:satip">DVBS2-2</satip:X_SATIPCAP>
var value = reader.ReadElementContentAsString() ?? string.Empty;
var parts = value.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 2)
{
int intValue;
if (int.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out intValue))
{
info.TunersAvailable = intValue;
}
if (int.TryParse(parts[0].Substring(parts[0].Length - 1), NumberStyles.Any, CultureInfo.InvariantCulture, out intValue))
{
info.Tuners = intValue;
}
}
break;
}
case "satip:X_SATIPM3U":
case "X_SATIPM3U":
{
// <satip:X_SATIPM3U xmlns:satip="urn:ses-com:satip">/channellist.lua?select=m3u</satip:X_SATIPM3U>
info.M3UUrl = reader.ReadElementContentAsString();
break;
}
default:
reader.Skip();
break;
}
}
}
}
}
public class SatIpTunerHostInfo : TunerHostInfo
{
public int Tuners { get; set; }
public int TunersAvailable { get; set; }
public string M3UUrl { get; set; }
public string FriendlyName { get; set; }
}
}

View File

@ -1,45 +1,171 @@
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
public class SatIpHost /*: BaseTunerHost*/
{
//public SatIpHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder)
// : base(config, logger, jsonSerializer, mediaEncoder)
//{
//}
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
//protected override Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
//{
// throw new NotImplementedException();
//}
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
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
{
get { return "satip"; }
}
//public override string Type
//{
// get { return DeviceType; }
//}
public override string Type
{
get { return DeviceType; }
}
//protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
//{
// throw new NotImplementedException();
//}
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
{
var urlHash = tuner.Url.GetMD5().ToString("N");
var prefix = ChannelIdPrefix + urlHash;
if (!channelId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
return null;
}
//protected override Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken)
//{
// throw new NotImplementedException();
//}
var channels = await GetChannels(tuner, true, cancellationToken).ConfigureAwait(false);
var m3uchannels = channels.Cast<M3UChannel>();
var channel = m3uchannels.FirstOrDefault(c => string.Equals(c.Id, channelId, StringComparison.OrdinalIgnoreCase));
if (channel != null)
{
var path = channel.Path;
MediaProtocol protocol = MediaProtocol.File;
if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
protocol = MediaProtocol.Http;
}
else if (path.StartsWith("rtmp", StringComparison.OrdinalIgnoreCase))
{
protocol = MediaProtocol.Rtmp;
}
else if (path.StartsWith("rtsp", StringComparison.OrdinalIgnoreCase))
{
protocol = MediaProtocol.Rtsp;
}
//protected override Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
//{
// throw new NotImplementedException();
//}
var mediaSource = new MediaSourceInfo
{
Path = channel.Path,
Protocol = protocol,
MediaStreams = new List<MediaStream>
{
new MediaStream
{
Type = MediaStreamType.Video,
// Set the index to -1 because we don't know the exact index of the video stream within the container
Index = -1,
IsInterlaced = true
},
new MediaStream
{
Type = MediaStreamType.Audio,
// Set the index to -1 because we don't know the exact index of the audio stream within the container
Index = -1
//protected override bool IsValidChannelId(string channelId)
//{
// throw new NotImplementedException();
//}
}
},
RequiresOpening = false,
RequiresClosing = false
};
return new List<MediaSourceInfo> { mediaSource };
}
return new List<MediaSourceInfo> { };
}
protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken)
{
var sources = await GetChannelStreamMediaSources(tuner, channelId, cancellationToken).ConfigureAwait(false);
return sources.First();
}
protected override async Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
{
var updatedInfo = await SatIpDiscovery.Current.GetInfo(tuner.Url, cancellationToken).ConfigureAwait(false);
return updatedInfo.TunersAvailable > 0;
}
protected override bool IsValidChannelId(string channelId)
{
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
protected override List<TunerHostInfo> GetTunerHosts()
{
return SatIpDiscovery.Current.DiscoveredHosts;
}
public string Name
{
get { return "Sat IP"; }
}
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
{
var list = GetTunerHosts()
.SelectMany(i => GetTunerInfos(i, cancellationToken))
.ToList();
return Task.FromResult(list);
}
public List<LiveTvTunerInfo> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
{
var satInfo = (SatIpTunerHostInfo) info;
var list = new List<LiveTvTunerInfo>();
for (var i = 0; i < satInfo.Tuners; i++)
{
list.Add(new LiveTvTunerInfo
{
Name = satInfo.FriendlyName ?? Name,
SourceType = Type,
Status = LiveTvTunerStatus.Available,
Id = info.Url.GetMD5().ToString("N") + i.ToString(CultureInfo.InvariantCulture),
Url = info.Url
});
}
return list;
}
}
}

View File

@ -52,9 +52,9 @@
<Reference Include="Interfaces.IO">
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
</Reference>
<Reference Include="MediaBrowser.Naming, Version=1.0.5884.23751, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="MediaBrowser.Naming, Version=1.0.5891.29179, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.47\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
<HintPath>..\packages\MediaBrowser.Naming.1.0.0.48\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
</Reference>
<Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
@ -234,6 +234,7 @@
<Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" />
<Compile Include="LiveTv\TunerHosts\M3uParser.cs" />
<Compile Include="LiveTv\TunerHosts\M3UTunerHost.cs" />
<Compile Include="LiveTv\ProgramImageProvider.cs" />
<Compile Include="LiveTv\RecordingImageProvider.cs" />

View File

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

View File

@ -56,6 +56,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
};
_connection.RunQueries(queries, Logger);
_connection.AddColumn(Logger, "userdata", "AudioStreamIndex", "int");
_connection.AddColumn(Logger, "userdata", "SubtitleStreamIndex", "int");
}
/// <summary>
@ -127,7 +130,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = "replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate)";
cmd.CommandText = "replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)";
cmd.Parameters.Add(cmd, "@key", DbType.String).Value = key;
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@ -137,6 +140,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@isFavorite", DbType.Boolean).Value = userData.IsFavorite;
cmd.Parameters.Add(cmd, "@playbackPositionTicks", DbType.Int64).Value = userData.PlaybackPositionTicks;
cmd.Parameters.Add(cmd, "@lastPlayedDate", DbType.DateTime).Value = userData.LastPlayedDate;
cmd.Parameters.Add(cmd, "@AudioStreamIndex", DbType.Int32).Value = userData.AudioStreamIndex;
cmd.Parameters.Add(cmd, "@SubtitleStreamIndex", DbType.Int32).Value = userData.SubtitleStreamIndex;
cmd.Transaction = transaction;
@ -199,7 +204,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = "replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate)";
cmd.CommandText = "replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)";
cmd.Parameters.Add(cmd, "@key", DbType.String).Value = userItemData.Key;
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@ -209,6 +214,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@isFavorite", DbType.Boolean).Value = userItemData.IsFavorite;
cmd.Parameters.Add(cmd, "@playbackPositionTicks", DbType.Int64).Value = userItemData.PlaybackPositionTicks;
cmd.Parameters.Add(cmd, "@lastPlayedDate", DbType.DateTime).Value = userItemData.LastPlayedDate;
cmd.Parameters.Add(cmd, "@AudioStreamIndex", DbType.Int32).Value = userItemData.AudioStreamIndex;
cmd.Parameters.Add(cmd, "@SubtitleStreamIndex", DbType.Int32).Value = userItemData.SubtitleStreamIndex;
cmd.Transaction = transaction;
@ -275,7 +282,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate from userdata where key = @key and userId=@userId";
cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where key = @key and userId=@userId";
cmd.Parameters.Add(cmd, "@key", DbType.String).Value = key;
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@ -310,7 +317,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate from userdata where userId=@userId";
cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where userId=@userId";
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@ -350,6 +357,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
userData.LastPlayedDate = reader.GetDateTime(7).ToUniversalTime();
}
if (!reader.IsDBNull(8))
{
userData.AudioStreamIndex = reader.GetInt32(8);
}
if (!reader.IsDBNull(9))
{
userData.SubtitleStreamIndex = reader.GetInt32(9);
}
return userData;
}

View File

@ -680,7 +680,7 @@ namespace MediaBrowser.Server.Implementations.Session
foreach (var user in users)
{
await OnPlaybackProgress(user.Id, key, libraryItem, info.PositionTicks).ConfigureAwait(false);
await OnPlaybackProgress(user, key, libraryItem, info).ConfigureAwait(false);
}
}
@ -712,15 +712,40 @@ namespace MediaBrowser.Server.Implementations.Session
StartIdleCheckTimer();
}
private async Task OnPlaybackProgress(Guid userId, string userDataKey, BaseItem item, long? positionTicks)
private async Task OnPlaybackProgress(User user, string userDataKey, BaseItem item, PlaybackProgressInfo info)
{
var data = _userDataRepository.GetUserData(userId, userDataKey);
var data = _userDataRepository.GetUserData(user.Id, userDataKey);
var positionTicks = info.PositionTicks;
if (positionTicks.HasValue)
{
_userDataRepository.UpdatePlayState(item, data, positionTicks.Value);
await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
UpdatePlaybackSettings(user, info, data);
await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
}
}
private void UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data)
{
if (user.Configuration.RememberAudioSelections)
{
data.AudioStreamIndex = info.AudioStreamIndex;
}
else
{
data.AudioStreamIndex = null;
}
if (user.Configuration.RememberSubtitleSelections)
{
data.SubtitleStreamIndex = info.SubtitleStreamIndex;
}
else
{
data.SubtitleStreamIndex = null;
}
}
@ -1268,7 +1293,17 @@ namespace MediaBrowser.Server.Implementations.Session
/// </summary>
/// <param name="request">The request.</param>
/// <returns>Task{SessionInfo}.</returns>
public async Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
public Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
{
return AuthenticateNewSessionInternal(request, true);
}
public Task<AuthenticationResult> CreateNewSession(AuthenticationRequest request)
{
return AuthenticateNewSessionInternal(request, false);
}
private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword)
{
var user = _userManager.Users
.FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
@ -1281,13 +1316,16 @@ namespace MediaBrowser.Server.Implementations.Session
}
}
var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
if (!result)
if (enforcePassword)
{
EventHelper.FireEventIfNotNull(AuthenticationFailed, this, new GenericEventArgs<AuthenticationRequest>(request), _logger);
var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
throw new SecurityException("Invalid user or password entered.");
if (!result)
{
EventHelper.FireEventIfNotNull(AuthenticationFailed, this, new GenericEventArgs<AuthenticationRequest>(request), _logger);
throw new SecurityException("Invalid user or password entered.");
}
}
var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.AppVersion, request.DeviceName).ConfigureAwait(false);
@ -1310,7 +1348,8 @@ namespace MediaBrowser.Server.Implementations.Session
ServerId = _appHost.SystemId
};
}
private async Task<string> GetAuthorizationToken(string userId, string deviceId, string app, string appVersion, string deviceName)
{
var existing = _authRepo.Get(new AuthenticationInfoQuery

View File

@ -19,6 +19,7 @@ using MediaBrowser.Model.Sync;
using MoreLinq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@ -125,7 +126,23 @@ namespace MediaBrowser.Server.Implementations.Sync
private string GetSyncJobItemName(BaseItem item)
{
return item.Name;
var name = item.Name;
var episode = item as Episode;
if (episode != null)
{
if (episode.IndexNumber.HasValue)
{
name = "E" + episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture) + " - " + name;
}
if (episode.ParentIndexNumber.HasValue)
{
name = "S" + episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture) + ", " + name;
}
}
return name;
}
public Task UpdateJobStatus(string id)
@ -699,7 +716,7 @@ namespace MediaBrowser.Server.Implementations.Sync
var path = Path.Combine(temporaryPath, filename);
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, cancellationToken).ConfigureAwait(false))
{

View File

@ -3,7 +3,7 @@
<package id="CommonIO" version="1.0.0.7" targetFramework="net45" />
<package id="Emby.XmlTv" version="1.0.0.48" targetFramework="net45" />
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
<package id="MediaBrowser.Naming" version="1.0.0.47" targetFramework="net45" />
<package id="MediaBrowser.Naming" version="1.0.0.48" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />

View File

@ -490,7 +490,7 @@ namespace MediaBrowser.Server.Startup.Common
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LogManager.GetLogger("ChannelManager"), ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
RegisterSingleInstance(ChannelManager);
MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager"), JsonSerializer, FileSystemManager);
MediaSourceManager = new MediaSourceManager(ItemRepository, UserManager, LibraryManager, LogManager.GetLogger("MediaSourceManager"), JsonSerializer, FileSystemManager, UserDataManager);
RegisterSingleInstance(MediaSourceManager);
SessionManager = new SessionManager(UserDataManager, LogManager.GetLogger("SessionManager"), UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager);

View File

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