diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 26053719bf..195d24b218 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -309,8 +309,8 @@ {4f26d5d8-a7b0-42b3-ba42-7cb7d245934e} SocketHttpListener.Portable - - ..\packages\Emby.XmlTv.1.0.4\lib\portable-net45+win8\Emby.XmlTv.dll + + ..\packages\Emby.XmlTv.1.0.5\lib\portable-net45+win8\Emby.XmlTv.dll True diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 2a4cc49b76..cf37366fb5 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -64,6 +64,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV episode.SeasonId = season.Id; episode.SeasonName = season.Name; } + + // Assume season 1 if there's no season folder and a season number could not be determined + if (season == null && !episode.ParentIndexNumber.HasValue && (episode.IndexNumber.HasValue || episode.PremiereDate.HasValue)) + { + episode.ParentIndexNumber = 1; + } } return episode; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index e6c88aa1a9..a47a3322e6 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.Library return new Tuple(item, index.Item1, index.Item2); })); - var returnValue = hints.Where(i => i.Item3 >= 0).OrderBy(i => i.Item3).Select(i => new SearchHintInfo + var returnValue = hints.Where(i => i.Item3 >= 0).OrderBy(i => i.Item3).ThenBy(i => i.Item1.SortName).Select(i => new SearchHintInfo { Item = i.Item1, MatchedTerm = i.Item2 diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 07fe813bdb..f942597540 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -847,6 +847,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var channelMappings = GetChannelMappings(provider.Item2); var channelNumber = channel.Number; + var tunerChannelId = channel.TunerChannelId; if (!string.IsNullOrWhiteSpace(channelNumber)) { @@ -858,7 +859,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channelNumber, channel.Name, startDateUtc, endDateUtc, cancellationToken) + var programs = await provider.Item1.GetProgramsAsync(provider.Item2, tunerChannelId, channelNumber, channel.Name, startDateUtc, endDateUtc, cancellationToken) .ConfigureAwait(false); var list = programs.ToList(); diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 66ad6911a0..e5790b875a 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings return dates; } - public async Task> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) + public async Task> GetProgramsAsync(ListingsProviderInfo info, string channelId, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) { List programsInfo = new List(); diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index abb853eb23..adb4f359eb 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -106,7 +106,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings return cacheFile; } - public async Task> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) + public async Task> GetProgramsAsync(ListingsProviderInfo info, string channelId, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken) { if (!await EmbyTV.EmbyTVRegistration.Instance.EnableXmlTv().ConfigureAwait(false)) { @@ -161,8 +161,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings } else { - var uniqueString = (p.Title ?? string.Empty) + (episodeTitle ?? string.Empty); + var uniqueString = (p.Title ?? string.Empty) + (episodeTitle ?? string.Empty) + (p.IceTvEpisodeNumber ?? string.Empty); + if (programInfo.SeasonNumber.HasValue) + { + uniqueString = "-" + programInfo.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture); + } if (programInfo.EpisodeNumber.HasValue) { uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 5e191ada9f..088c264f04 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -76,6 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var channels = new List(); string line; string extInf = ""; + while ((line = reader.ReadLine()) != null) { line = line.Trim(); @@ -111,6 +112,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts extInf = ""; } } + + var startingNumber = 1; + foreach (var channel in channels) + { + if (!string.IsNullOrWhiteSpace(channel.Number)) + { + continue; + } + + channel.Number = startingNumber.ToString(CultureInfo.InvariantCulture); + startingNumber++; + } return channels; } @@ -137,6 +150,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (attributes.TryGetValue("tvg-id", out value)) { channel.Id = value; + channel.TunerChannelId = value; } return channel; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs index 7b88be19c1..a7e1b3cf3b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/MulticastStream.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -11,10 +12,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { public class MulticastStream { - private readonly List _outputStreams = new List(); + private readonly ConcurrentDictionary _outputStreams = new ConcurrentDictionary(); private const int BufferSize = 81920; private CancellationToken _cancellationToken; private readonly ILogger _logger; + private readonly ConcurrentQueue _sharedBuffer = new ConcurrentQueue(); public MulticastStream(ILogger logger) { @@ -35,17 +37,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { byte[] copy = new byte[bytesRead]; Buffer.BlockCopy(buffer, 0, copy, 0, bytesRead); - - List streams = null; - lock (_outputStreams) + _sharedBuffer.Enqueue(copy); + + while (_sharedBuffer.Count > 3000) { - streams = _outputStreams.ToList(); + byte[] bytes; + _sharedBuffer.TryDequeue(out bytes); } - foreach (var stream in streams) + var allStreams = _outputStreams.ToList(); + foreach (var stream in allStreams) { - stream.Queue(copy); + stream.Value.Queue(copy); } if (onStarted != null) @@ -70,11 +74,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts OnFinished = OnFinished }; - lock (_outputStreams) + var initial = _sharedBuffer.ToList(); + var list = new List(); + + foreach (var bytes in initial) { - _outputStreams.Add(result); + list.AddRange(bytes); } + _logger.Info("QueueStream started with {0} initial bytes", list.Count); + + result.Queue(list.ToArray()); + + _outputStreams.TryAdd(result.Id, result); + result.Start(_cancellationToken); return result.TaskCompletion.Task; @@ -82,10 +95,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public void RemoveOutputStream(QueueStream stream) { - lock (_outputStreams) - { - _outputStreams.Remove(stream); - } + QueueStream removed; + _outputStreams.TryRemove(stream.Id, out removed); } private void OnFinished(QueueStream queueStream) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs index bd6f319062..7b48ce21a9 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/QueueStream.cs @@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public Action OnFinished { get; set; } private readonly ILogger _logger; - private bool _isActive; + public Guid Id = Guid.NewGuid(); public QueueStream(Stream outputStream, ILogger logger) { @@ -30,10 +30,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public void Queue(byte[] bytes) { - if (_isActive) - { - _queue.Enqueue(bytes); - } + _queue.Enqueue(bytes); } public void Start(CancellationToken cancellationToken) @@ -59,10 +56,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts try { - while (!cancellationToken.IsCancellationRequested) + while (true) { - _isActive = true; - var bytes = Dequeue(); if (bytes != null) { @@ -73,9 +68,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts await Task.Delay(50, cancellationToken).ConfigureAwait(false); } } - - TaskCompletion.TrySetResult(true); - _logger.Debug("QueueStream complete"); } catch (OperationCanceledException) { @@ -89,8 +81,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } finally { - _isActive = false; - if (OnFinished != null) { OnFinished(this); diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index a27d6b4e23..5249577e61 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -1,6 +1,6 @@  - + diff --git a/MediaBrowser.Api/BasePeriodicWebSocketListener.cs b/MediaBrowser.Api/BasePeriodicWebSocketListener.cs index fe7de387f8..8004d7e9bf 100644 --- a/MediaBrowser.Api/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Api/BasePeriodicWebSocketListener.cs @@ -23,8 +23,8 @@ namespace MediaBrowser.Api /// /// The _active connections /// - protected readonly List> ActiveConnections = - new List>(); + protected readonly List> ActiveConnections = + new List>(); /// /// Gets the name. @@ -132,11 +132,9 @@ namespace MediaBrowser.Api InitialDelayMs = dueTimeMs }; - var semaphore = new SemaphoreSlim(1, 1); - lock (ActiveConnections) { - ActiveConnections.Add(new Tuple(message.Connection, cancellationTokenSource, timer, state, semaphore)); + ActiveConnections.Add(new Tuple(message.Connection, cancellationTokenSource, timer, state)); } if (timer != null) @@ -153,7 +151,7 @@ namespace MediaBrowser.Api { var connection = (IWebSocketConnection)state; - Tuple tuple; + Tuple tuple; lock (ActiveConnections) { @@ -176,7 +174,7 @@ namespace MediaBrowser.Api protected void SendData(bool force) { - List> tuples; + List> tuples; lock (ActiveConnections) { @@ -204,14 +202,12 @@ namespace MediaBrowser.Api } } - private async void SendData(Tuple tuple) + private async void SendData(Tuple tuple) { var connection = tuple.Item1; try { - await tuple.Item5.WaitAsync(tuple.Item2.Token).ConfigureAwait(false); - var state = tuple.Item4; var data = await GetDataToSend(state).ConfigureAwait(false); @@ -227,8 +223,6 @@ namespace MediaBrowser.Api state.DateLastSendUtc = DateTime.UtcNow; } - - tuple.Item5.Release(); } catch (OperationCanceledException) { @@ -265,7 +259,7 @@ namespace MediaBrowser.Api /// Disposes the connection. /// /// The connection. - private void DisposeConnection(Tuple connection) + private void DisposeConnection(Tuple connection) { Logger.Debug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name); @@ -293,15 +287,6 @@ namespace MediaBrowser.Api } - try - { - connection.Item5.Dispose(); - } - catch (ObjectDisposedException) - { - - } - ActiveConnections.Remove(connection); } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 6a677abfc5..f5aa954ee1 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -2720,6 +2720,15 @@ namespace MediaBrowser.Api.Playback //inputModifier += " -noaccurate_seek"; } + if (!string.IsNullOrWhiteSpace(state.InputContainer)) + { + var inputFormat = GetInputFormat(state.InputContainer); + if (!string.IsNullOrWhiteSpace(inputFormat)) + { + inputModifier += " -f " + inputFormat; + } + } + if (state.RunTimeTicks.HasValue) { foreach (var stream in state.MediaSource.MediaStreams) @@ -2738,23 +2747,21 @@ namespace MediaBrowser.Api.Playback } } } - - //var videoStream = state.VideoStream; - //if (videoStream != null && !string.IsNullOrWhiteSpace(videoStream.Codec)) - //{ - // inputModifier += " -codec:0 " + videoStream.Codec; - - // var audioStream = state.AudioStream; - // if (audioStream != null && !string.IsNullOrWhiteSpace(audioStream.Codec)) - // { - // inputModifier += " -codec:1 " + audioStream.Codec; - // } - //} } return inputModifier; } + private string GetInputFormat(string container) + { + if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase)) + { + return "matroska"; + } + + return container; + } + private string GetDecoderFromCodec(string codec) { return null; diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 97072115dc..41b58a611f 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -103,7 +103,7 @@ namespace MediaBrowser.Api.Playback.Hls throw; } - var waitForSegments = state.SegmentLength >= 10 ? 2 : 3; + var waitForSegments = state.SegmentLength >= 10 ? 2 : 2; await WaitForMinimumSegmentCount(playlist, waitForSegments, cancellationTokenSource.Token).ConfigureAwait(false); } } diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs index 372b095fd1..fae9076588 100644 --- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs @@ -25,6 +25,8 @@ namespace MediaBrowser.Controller.LiveTv /// The id of the channel. public string Id { get; set; } + public string TunerChannelId { get; set; } + /// /// Gets or sets the tuner host identifier. /// diff --git a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs index 5ecd70cc5c..3d610544ee 100644 --- a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs +++ b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Controller.LiveTv { string Name { get; } string Type { get; } - Task> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken); + Task> GetProgramsAsync(ListingsProviderInfo info, string channelId, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken); Task AddMetadata(ListingsProviderInfo info, List channels, CancellationToken cancellationToken); Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings); Task> GetLineups(ListingsProviderInfo info, string country, string location);