diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index 183b2bacb6..db50f463d4 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.LiveTv;
+using System.Threading;
+using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
 using ServiceStack.ServiceHost;
@@ -49,8 +50,13 @@ namespace MediaBrowser.Api.LiveTv
     [Api(Description = "Gets live tv recordings")]
     public class GetRecordings : IReturn<QueryResult<RecordingInfoDto>>
     {
+        [ApiMember(Name = "ServiceName", Description = "Optional filter by service.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ServiceName { get; set; }
+
+        [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ChannelId { get; set; }
     }
-    
+
     [Route("/LiveTv/Programs", "GET")]
     [Api(Description = "Gets available live tv epgs..")]
     public class GetPrograms : IReturn<QueryResult<ProgramInfoDto>>
@@ -80,7 +86,7 @@ namespace MediaBrowser.Api.LiveTv
 
             if (!string.IsNullOrEmpty(serviceName))
             {
-                services = services.Where(i => string.Equals(i.Name, serviceName, System.StringComparison.OrdinalIgnoreCase));
+                services = services.Where(i => string.Equals(i.Name, serviceName, StringComparison.OrdinalIgnoreCase));
             }
 
             return services;
@@ -130,14 +136,20 @@ namespace MediaBrowser.Api.LiveTv
                 ServiceName = request.ServiceName,
                 ChannelIdList = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(),
                 UserId = request.UserId
-            });
+
+            }, CancellationToken.None).Result;
 
             return ToOptimizedResult(result);
         }
 
         public object Get(GetRecordings request)
         {
-            var result = _liveTvManager.GetRecordings();
+            var result = _liveTvManager.GetRecordings(new RecordingQuery
+            {
+                ChannelId = request.ChannelId,
+                ServiceName = request.ServiceName
+
+            }, CancellationToken.None).Result;
 
             return ToOptimizedResult(result);
         }
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index a234844ff6..8ebfe17e41 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -3,6 +3,8 @@ using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
 using System.Runtime.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Entities.Audio
 {
@@ -30,13 +32,24 @@ namespace MediaBrowser.Controller.Entities.Audio
             {
                 if (IsAccessedByName)
                 {
-                    throw new InvalidOperationException("Artists accessed by name do not have children.");
+                    return new List<BaseItem>();
                 }
 
                 return base.ActualChildren;
             }
         }
 
+        protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
+        {
+            if (IsAccessedByName)
+            {
+                // Should never get in here anyway
+                return Task.FromResult(true);
+            }
+
+            return base.ValidateChildrenInternal(progress, cancellationToken, recursive, forceRefreshMetadata);
+        }
+
         public override string GetClientTypeName()
         {
             if (IsAccessedByName)
diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
index 721c1e40a8..27fc596303 100644
--- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
@@ -25,12 +25,6 @@ namespace MediaBrowser.Controller.LiveTv
         /// <value>The id of the channel.</value>
         public string Id { get; set; }
 
-        /// <summary>
-        /// Gets or sets the name of the service.
-        /// </summary>
-        /// <value>The name of the service.</value>
-        public string ServiceName { get; set; }
-
         /// <summary>
         /// Gets or sets the type of the channel.
         /// </summary>
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index 9e49813318..a04072d28d 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -1,6 +1,8 @@
-using MediaBrowser.Model.LiveTv;
+using System.Threading;
+using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
 using System.Collections.Generic;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -15,6 +17,13 @@ namespace MediaBrowser.Controller.LiveTv
         /// <value>The services.</value>
         IReadOnlyList<ILiveTvService> Services { get; }
 
+        /// <summary>
+        /// Schedules the recording.
+        /// </summary>
+        /// <param name="programId">The program identifier.</param>
+        /// <returns>Task.</returns>
+        Task ScheduleRecording(string programId);
+
         /// <summary>
         /// Adds the parts.
         /// </summary>
@@ -31,8 +40,10 @@ namespace MediaBrowser.Controller.LiveTv
         /// <summary>
         /// Gets the recordings.
         /// </summary>
+        /// <param name="query">The query.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>QueryResult{RecordingInfoDto}.</returns>
-        QueryResult<RecordingInfoDto> GetRecordings();
+        Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the channel.
@@ -53,7 +64,8 @@ namespace MediaBrowser.Controller.LiveTv
         /// Gets the programs.
         /// </summary>
         /// <param name="query">The query.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>IEnumerable{ProgramInfo}.</returns>
-        QueryResult<ProgramInfoDto> GetPrograms(ProgramQuery query);
+        Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken);
     }
 }
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
index 4e4f8dcc78..e903ad5ec6 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
@@ -1,5 +1,4 @@
 using MediaBrowser.Common.Net;
-using System;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
@@ -25,12 +24,12 @@ namespace MediaBrowser.Controller.LiveTv
         Task<IEnumerable<ChannelInfo>> GetChannelsAsync(CancellationToken cancellationToken);
 
         /// <summary>
-        /// Cancels the recording asynchronous.
+        /// Cancels the timer asynchronous.
         /// </summary>
-        /// <param name="recordingId">The recording identifier.</param>
+        /// <param name="timerId">The timer identifier.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        Task CancelRecordingAsync(string recordingId, CancellationToken cancellationToken);
+        Task CancelTimerAsync(string timerId, CancellationToken cancellationToken);
 
         /// <summary>
         /// Deletes the recording asynchronous.
@@ -39,18 +38,23 @@ namespace MediaBrowser.Controller.LiveTv
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken);
-        
+
         /// <summary>
-        /// Schedules the recording asynchronous.
+        /// Creates the timer asynchronous.
         /// </summary>
-        /// <param name="name">The name for the recording</param>
-        /// <param name="channelId">The channel identifier.</param>
-        /// <param name="startTime">The start time.</param>
-        /// <param name="duration">The duration.</param>
+        /// <param name="info">The information.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        Task ScheduleRecordingAsync(string name, string channelId, DateTime startTime, TimeSpan duration, CancellationToken cancellationToken);
+        Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken);
 
+        /// <summary>
+        /// Updates the timer asynchronous.
+        /// </summary>
+        /// <param name="info">The information.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken);
+        
         /// <summary>
         /// Gets the channel image asynchronous.
         /// </summary>
@@ -66,6 +70,13 @@ namespace MediaBrowser.Controller.LiveTv
         /// <returns>Task{IEnumerable{RecordingInfo}}.</returns>
         Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken);
 
+        /// <summary>
+        /// Gets the recordings asynchronous.
+        /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{IEnumerable{RecordingInfo}}.</returns>
+        Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken);
+        
         /// <summary>
         /// Gets the programs asynchronous.
         /// </summary>
diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
index f6df495459..58a15be3e5 100644
--- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
@@ -43,6 +43,12 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         public DateTime EndDate { get; set; }
 
+        /// <summary>
+        /// Gets or sets the aspect ratio.
+        /// </summary>
+        /// <value>The aspect ratio.</value>
+        public string AspectRatio { get; set; }
+
         /// <summary>
         /// Genre of the program.
         /// </summary>
diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
index f61bd9e785..f0daac5f91 100644
--- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
@@ -1,6 +1,5 @@
 using MediaBrowser.Model.LiveTv;
 using System;
-using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -20,12 +19,6 @@ namespace MediaBrowser.Controller.LiveTv
         /// ChannelName of the recording.
         /// </summary>
         public string ChannelName { get; set; }
-
-        /// <summary>
-        /// Gets or sets the program identifier.
-        /// </summary>
-        /// <value>The program identifier.</value>
-        public string ProgramId { get; set; }
         
         /// <summary>
         /// Name of the recording.
@@ -52,31 +45,5 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <value>The status.</value>
         public RecordingStatus Status { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether this instance is recurring.
-        /// </summary>
-        /// <value><c>true</c> if this instance is recurring; otherwise, <c>false</c>.</value>
-        public bool IsRecurring { get; set; }
-
-        /// <summary>
-        /// Parent recurring.
-        /// </summary>
-        public string RecurringParent { get; set; }
-
-        /// <summary>
-        /// Start date for the recurring, in UTC.
-        /// </summary>
-        public DateTime RecurrringStartDate { get; set; }
-
-        /// <summary>
-        /// End date for the recurring, in UTC
-        /// </summary>
-        public DateTime RecurringEndDate { get; set; }
-
-        /// <summary>
-        /// When do we need the recording?
-        /// </summary>
-        public List<string> DayMask { get; set; }
     }
 }
diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
new file mode 100644
index 0000000000..f0f936b526
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
@@ -0,0 +1,67 @@
+using MediaBrowser.Model.LiveTv;
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+    public class TimerInfo
+    {
+        /// <summary>
+        /// Id of the recording.
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// ChannelId of the recording.
+        /// </summary>
+        public string ChannelId { get; set; }
+
+        /// <summary>
+        /// ChannelName of the recording.
+        /// </summary>
+        public string ChannelName { get; set; }
+
+        /// <summary>
+        /// Name of the recording.
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Description of the recording.
+        /// </summary>
+        public string Description { get; set; }
+
+        /// <summary>
+        /// The start date of the recording, in UTC.
+        /// </summary>
+        public DateTime StartDate { get; set; }
+
+        /// <summary>
+        /// The end date of the recording, in UTC.
+        /// </summary>
+        public DateTime EndDate { get; set; }
+
+        /// <summary>
+        /// Gets or sets the status.
+        /// </summary>
+        /// <value>The status.</value>
+        public RecordingStatus Status { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is recurring.
+        /// </summary>
+        /// <value><c>true</c> if this instance is recurring; otherwise, <c>false</c>.</value>
+        public bool IsRecurring { get; set; }
+
+        /// <summary>
+        /// Gets or sets the recurring days.
+        /// </summary>
+        /// <value>The recurring days.</value>
+        public List<DayOfWeek> RecurringDays { get; set; }
+
+        public TimerInfo()
+        {
+            RecurringDays = new List<DayOfWeek>();
+        }
+    }
+}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index ed63bc1647..2068fccf83 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -110,6 +110,7 @@
     <Compile Include="LiveTv\ILiveTvService.cs" />
     <Compile Include="LiveTv\ProgramInfo.cs" />
     <Compile Include="LiveTv\RecordingInfo.cs" />
+    <Compile Include="LiveTv\TimerInfo.cs" />
     <Compile Include="Localization\ILocalizationManager.cs" />
     <Compile Include="Notifications\INotificationsRepository.cs" />
     <Compile Include="Notifications\NotificationUpdateEventArgs.cs" />
diff --git a/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs b/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs
index 9e1a829c7c..2cafd288df 100644
--- a/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs
@@ -27,13 +27,13 @@ namespace MediaBrowser.Model.LiveTv
         /// </summary>
         /// <value>The community rating.</value>
         public float? CommunityRating { get; set; }
-        
+
         /// <summary>
-        /// Gets or sets the recording identifier.
+        /// Gets or sets the aspect ratio.
         /// </summary>
-        /// <value>The recording identifier.</value>
-        public string RecordingId { get; set; }
-
+        /// <value>The aspect ratio.</value>
+        public string AspectRatio { get; set; }
+        
         /// <summary>
         /// Gets or sets the official rating.
         /// </summary>
@@ -88,6 +88,18 @@ namespace MediaBrowser.Model.LiveTv
         /// </summary>
         /// <value>The original air date.</value>
         public DateTime? OriginalAirDate { get; set; }
+
+        /// <summary>
+        /// Gets or sets the recording identifier.
+        /// </summary>
+        /// <value>The recording identifier.</value>
+        public string RecordingId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the recording status.
+        /// </summary>
+        /// <value>The recording status.</value>
+        public RecordingStatus? RecordingStatus { get; set; }
         
         public ProgramInfoDto()
         {
diff --git a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
index 8b0a28ed0c..926198b93f 100644
--- a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
@@ -51,11 +51,6 @@ namespace MediaBrowser.Model.LiveTv
         /// </summary>
         public DateTime EndDate { get; set; }
 
-        /// <summary>
-        /// IsRecurring recording?
-        /// </summary>
-        public bool IsRecurring { get; set; }
-
         /// <summary>
         /// Gets or sets the status.
         /// </summary>
diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
index 8d9866b5ec..8c83b0fcbd 100644
--- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
@@ -5,6 +5,16 @@
     /// </summary>
     public class RecordingQuery
     {
+        /// <summary>
+        /// Gets or sets the channel identifier.
+        /// </summary>
+        /// <value>The channel identifier.</value>
         public string ChannelId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name of the service.
+        /// </summary>
+        /// <value>The name of the service.</value>
+        public string ServiceName { get; set; }
     }
 }
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index dd1e2b9fa6..5333518753 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -207,7 +207,7 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             if (!string.IsNullOrEmpty(image))
             {
-                dto.PrimaryImageTag = _imageProcessor.GetImageCacheTag(user, ImageType.Primary, image);
+                dto.PrimaryImageTag = GetImageCacheTag(user, ImageType.Primary, image);
 
                 try
                 {
@@ -285,13 +285,7 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             if (!string.IsNullOrEmpty(imagePath))
             {
-                try
-                {
-                    info.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary, imagePath);
-                }
-                catch (IOException)
-                {
-                }
+                info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary, imagePath);
             }
 
             return info;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index e48cea0f7c..12241876a1 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -41,9 +41,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         private List<Channel> _channels = new List<Channel>();
         private List<ProgramInfoDto> _programs = new List<ProgramInfoDto>();
-        private List<RecordingInfoDto> _recordings = new List<RecordingInfoDto>();
-
-        private readonly SemaphoreSlim _updateSemaphore = new SemaphoreSlim(1, 1);
 
         public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserManager userManager, ILocalizationManager localization, IUserDataManager userDataManager, IDtoService dtoService)
         {
@@ -104,11 +101,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return dto;
         }
 
-        private ILiveTvService GetService(ChannelInfo channel)
-        {
-            return _services.FirstOrDefault(i => string.Equals(channel.ServiceName, i.Name, StringComparison.OrdinalIgnoreCase));
-        }
-
         private Guid? GetLogoImageTag(Channel info)
         {
             var path = info.PrimaryImagePath;
@@ -210,7 +202,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 Quality = program.Quality,
                 OriginalAirDate = program.OriginalAirDate,
                 Audio = program.Audio,
-                CommunityRating = program.CommunityRating
+                CommunityRating = program.CommunityRating,
+                AspectRatio = program.AspectRatio
             };
         }
 
@@ -228,9 +221,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return name.ToLower().GetMD5();
         }
 
-        private async Task<Channel> GetChannel(ChannelInfo channelInfo, CancellationToken cancellationToken)
+        private async Task<Channel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
         {
-            var path = Path.Combine(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(channelInfo.ServiceName), _fileSystem.GetValidFilename(channelInfo.Name));
+            var path = Path.Combine(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(serviceName), _fileSystem.GetValidFilename(channelInfo.Name));
 
             var fileInfo = new DirectoryInfo(path);
 
@@ -249,7 +242,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 isNew = true;
             }
 
-            var id = GetInternalChannelId(channelInfo.ServiceName, channelInfo.Id);
+            var id = GetInternalChannelId(serviceName, channelInfo.Id);
 
             var item = _itemRepo.RetrieveItem(id) as Channel;
 
@@ -264,7 +257,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                     Path = path,
                     ChannelId = channelInfo.Id,
                     ChannelNumber = channelInfo.Number,
-                    ServiceName = channelInfo.ServiceName
+                    ServiceName = serviceName
                 };
 
                 isNew = true;
@@ -278,7 +271,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return item;
         }
 
-        public QueryResult<ProgramInfoDto> GetPrograms(ProgramQuery query)
+        public async Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken)
         {
             IEnumerable<ProgramInfoDto> programs = _programs
                 .OrderBy(i => i.StartDate)
@@ -298,35 +291,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             var returnArray = programs.ToArray();
 
-            return new QueryResult<ProgramInfoDto>
+            var recordings = await GetRecordings(new RecordingQuery
             {
-                Items = returnArray,
-                TotalRecordCount = returnArray.Length
-            };
-        }
 
-        internal async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            await _updateSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
 
-            try
-            {
-                await RefreshChannelsInternal(progress, cancellationToken).ConfigureAwait(false);
-            }
-            finally
+            }, cancellationToken).ConfigureAwait(false);
+
+            foreach (var program in returnArray)
             {
-                _updateSemaphore.Release();
+                var recording = recordings.Items
+                    .FirstOrDefault(i => string.Equals(i.ProgramId, program.Id));
+
+                program.RecordingId = recording == null ? null : recording.Id;
+                program.RecordingStatus = recording == null ? (RecordingStatus?)null : recording.Status;
             }
 
-            await RefreshRecordings(new Progress<double>(), cancellationToken).ConfigureAwait(false);
+            return new QueryResult<ProgramInfoDto>
+            {
+                Items = returnArray,
+                TotalRecordCount = returnArray.Length
+            };
         }
 
-        private async Task RefreshChannelsInternal(IProgress<double> progress, CancellationToken cancellationToken)
+        internal async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
         {
             // Avoid implicitly captured closure
             var currentCancellationToken = cancellationToken;
 
-            var channelTasks = _services.Select(i => i.GetChannelsAsync(currentCancellationToken));
+            var channelTasks = _services.Select(i => GetChannels(i, currentCancellationToken));
 
             progress.Report(10);
 
@@ -343,11 +335,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             {
                 try
                 {
-                    var item = await GetChannel(channelInfo, cancellationToken).ConfigureAwait(false);
+                    var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, cancellationToken).ConfigureAwait(false);
 
-                    var service = GetService(channelInfo);
+                    var service = _services.First(i => string.Equals(channelInfo.Item1, i.Name, StringComparison.OrdinalIgnoreCase));
 
-                    var channelPrograms = await service.GetProgramsAsync(channelInfo.Id, cancellationToken).ConfigureAwait(false);
+                    var channelPrograms = await service.GetProgramsAsync(channelInfo.Item2.Id, cancellationToken).ConfigureAwait(false);
 
                     programs.AddRange(channelPrograms.Select(program => GetProgramInfoDto(program, item)));
 
@@ -359,7 +351,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 }
                 catch (Exception ex)
                 {
-                    _logger.ErrorException("Error getting channel information for {0}", ex, channelInfo.Name);
+                    _logger.ErrorException("Error getting channel information for {0}", ex, channelInfo.Item2.Name);
                 }
 
                 numComplete++;
@@ -373,32 +365,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             _channels = list;
         }
 
-        internal async Task RefreshRecordings(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            await _updateSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                await RefreshRecordingsInternal(progress, cancellationToken).ConfigureAwait(false);
-            }
-            finally
-            {
-                _updateSemaphore.Release();
-            }
-        }
-
-        private async Task RefreshRecordingsInternal(IProgress<double> progress, CancellationToken cancellationToken)
+        private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)
         {
-            var list = new List<RecordingInfoDto>();
-
-            foreach (var service in _services)
-            {
-                var recordings = await GetRecordings(service, cancellationToken).ConfigureAwait(false);
-
-                list.AddRange(recordings);
-            }
+            var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false);
 
-            _recordings = list;
+            return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i));
         }
 
         private async Task<IEnumerable<RecordingInfoDto>> GetRecordings(ILiveTvService service, CancellationToken cancellationToken)
@@ -419,7 +390,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 Description = info.Description,
                 EndDate = info.EndDate,
                 Name = info.Name,
-                IsRecurring = info.IsRecurring,
                 StartDate = info.StartDate,
                 Id = id,
                 ExternalId = info.Id,
@@ -427,17 +397,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 Status = info.Status
             };
 
-            if (!string.IsNullOrEmpty(info.ProgramId))
-            {
-                dto.ProgramId = GetInternalProgramIdId(service.Name, info.ProgramId).ToString("N");
-            }
-
             return dto;
         }
 
-        public QueryResult<RecordingInfoDto> GetRecordings()
+        public async Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken)
         {
-            var returnArray = _recordings.ToArray();
+            var list = new List<RecordingInfoDto>();
+
+            foreach (var service in GetServices(query.ServiceName, query.ChannelId))
+            {
+                var recordings = await GetRecordings(service, cancellationToken).ConfigureAwait(false);
+
+                list.AddRange(recordings);
+            }
+
+            if (!string.IsNullOrEmpty(query.ChannelId))
+            {
+                list = list.Where(i => string.Equals(i.ChannelId, query.ChannelId))
+                    .ToList();
+            }
+
+            var returnArray = list.ToArray();
 
             return new QueryResult<RecordingInfoDto>
             {
@@ -445,5 +425,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 TotalRecordCount = returnArray.Length
             };
         }
+
+        private IEnumerable<ILiveTvService> GetServices(string serviceName, string channelId)
+        {
+            IEnumerable<ILiveTvService> services = _services;
+
+            if (string.IsNullOrEmpty(serviceName) && !string.IsNullOrEmpty(channelId))
+            {
+                var channelIdGuid = new Guid(channelId);
+
+                serviceName = _channels.Where(i => i.Id == channelIdGuid)
+                    .Select(i => i.ServiceName)
+                    .FirstOrDefault();
+            }
+
+            if (!string.IsNullOrEmpty(serviceName))
+            {
+                services = services.Where(i => string.Equals(i.Name, serviceName, StringComparison.OrdinalIgnoreCase));
+            }
+
+            return services;
+        }
+
+        public Task ScheduleRecording(string programId)
+        {
+            throw new NotImplementedException();
+        }
     }
 }
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index d5a264689e..2b2c47db80 100644
--- a/Nuget/MediaBrowser.Common.Internal.nuspec
+++ b/Nuget/MediaBrowser.Common.Internal.nuspec
@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.250</version>
+        <version>3.0.251</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.250" />
+            <dependency id="MediaBrowser.Common" version="3.0.251" />
             <dependency id="NLog" version="2.1.0" />
             <dependency id="ServiceStack.Text" version="3.9.58" />
             <dependency id="SimpleInjector" version="2.3.6" />
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index f5333c078e..a7a3150e68 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/Nuget/MediaBrowser.Common.nuspec
@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.250</version>
+        <version>3.0.251</version>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index 6b3448f0e2..da0705c77e 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/Nuget/MediaBrowser.Server.Core.nuspec
@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.250</version>
+        <version>3.0.251</version>
         <title>Media Browser.Server.Core</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Media Browser Server.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.250" />
+            <dependency id="MediaBrowser.Common" version="3.0.251" />
         </dependencies>
     </metadata>
     <files>