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);