add channel downloading settings

This commit is contained in:
Luke Pulverenti 2014-06-02 15:32:41 -04:00
parent 36648d2708
commit 858c37b860
45 changed files with 980 additions and 303 deletions

View File

@ -117,7 +117,8 @@ namespace MediaBrowser.Api
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="sourcePath">The source path.</param>
/// <param name="deviceId">The device id.</param>
public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, long? startTimeTicks, string sourcePath, string deviceId)
/// <param name="cancellationTokenSource">The cancellation token source.</param>
public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, long? startTimeTicks, string sourcePath, string deviceId, CancellationTokenSource cancellationTokenSource)
{
lock (_activeTranscodingJobs)
{
@ -129,7 +130,8 @@ namespace MediaBrowser.Api
ActiveRequestCount = 1,
StartTimeTicks = startTimeTicks,
SourcePath = sourcePath,
DeviceId = deviceId
DeviceId = deviceId,
CancellationTokenSource = cancellationTokenSource
});
}
}
@ -276,6 +278,11 @@ namespace MediaBrowser.Api
{
_activeTranscodingJobs.Remove(job);
if (!job.CancellationTokenSource.IsCancellationRequested)
{
job.CancellationTokenSource.Cancel();
}
if (job.KillTimer != null)
{
job.KillTimer.Dispose();
@ -329,7 +336,7 @@ namespace MediaBrowser.Api
private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
{
if (retryCount >= 10)
if (retryCount >= 8)
{
return;
}
@ -432,6 +439,8 @@ namespace MediaBrowser.Api
public long? StartTimeTicks { get; set; }
public string SourcePath { get; set; }
public string DeviceId { get; set; }
public CancellationTokenSource CancellationTokenSource { get; set; }
}
/// <summary>

View File

@ -43,6 +43,11 @@ namespace MediaBrowser.Api
public string Id { get; set; }
}
[Route("/Channels/Features", "GET", Summary = "Gets features for a channel")]
public class GetAllChannelFeatures : IReturn<List<ChannelFeatures>>
{
}
[Route("/Channels/{Id}/Items", "GET", Summary = "Gets channel items")]
public class GetChannelItems : IReturn<QueryResult<BaseItemDto>>
{
@ -108,6 +113,13 @@ namespace MediaBrowser.Api
_channelManager = channelManager;
}
public object Get(GetAllChannelFeatures request)
{
var result = _channelManager.GetAllChannelFeatures().ToList();
return ToOptimizedResult(result);
}
public object Get(GetChannelFeatures request)
{
var result = _channelManager.GetChannelFeatures(request.Id);

View File

@ -1,6 +1,7 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
using ServiceStack;
using System;
using System.Collections.Generic;
@ -89,15 +90,6 @@ namespace MediaBrowser.Api.Images
{
}
public class ImageByNameInfo
{
public string Name { get; set; }
public string Theme { get; set; }
public string Context { get; set; }
public long FileLength { get; set; }
public string Format { get; set; }
}
/// <summary>
/// Class ImageByNameService
/// </summary>

View File

@ -105,7 +105,6 @@
<Compile Include="Playback\Hls\DynamicHlsService.cs" />
<Compile Include="Playback\Hls\HlsSegmentService.cs" />
<Compile Include="Playback\Hls\VideoHlsService.cs" />
<Compile Include="Playback\ProgressiveStreamService.cs" />
<Compile Include="Playback\Progressive\AudioService.cs" />
<Compile Include="Playback\Progressive\BaseProgressiveStreamingService.cs" />
<Compile Include="Playback\BaseStreamingService.cs" />

View File

@ -1,5 +1,6 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@ -13,6 +14,7 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Library;
@ -72,20 +74,14 @@ namespace MediaBrowser.Api.Playback
protected ILiveTvManager LiveTvManager { get; private set; }
protected IDlnaManager DlnaManager { get; private set; }
protected IChannelManager ChannelManager { get; private set; }
protected IHttpClient HttpClient { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
/// <param name="serverConfig">The server configuration.</param>
/// <param name="userManager">The user manager.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="isoManager">The iso manager.</param>
/// <param name="mediaEncoder">The media encoder.</param>
/// <param name="dtoService">The dto service.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="itemRepository">The item repository.</param>
protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager)
protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient)
{
HttpClient = httpClient;
ChannelManager = channelManager;
DlnaManager = dlnaManager;
EncodingManager = encodingManager;
@ -483,8 +479,12 @@ namespace MediaBrowser.Api.Playback
/// <param name="state">The state.</param>
/// <param name="outputVideoCodec">The output video codec.</param>
/// <param name="performTextSubtitleConversion">if set to <c>true</c> [perform text subtitle conversion].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
protected string GetOutputSizeParam(StreamState state, string outputVideoCodec, bool performTextSubtitleConversion)
protected string GetOutputSizeParam(StreamState state,
string outputVideoCodec,
bool performTextSubtitleConversion,
CancellationToken cancellationToken)
{
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
@ -496,7 +496,7 @@ namespace MediaBrowser.Api.Playback
if (state.SubtitleStream != null && !state.SubtitleStream.IsGraphicalSubtitleStream)
{
assSubtitleParam = GetTextSubtitleParam(state, performTextSubtitleConversion);
assSubtitleParam = GetTextSubtitleParam(state, performTextSubtitleConversion, cancellationToken);
copyTsParam = " -copyts";
}
@ -592,11 +592,15 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="state">The state.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
protected string GetTextSubtitleParam(StreamState state, bool performConversion)
protected string GetTextSubtitleParam(StreamState state,
bool performConversion,
CancellationToken cancellationToken)
{
var path = state.SubtitleStream.IsExternal ? GetConvertedAssPath(state.MediaPath, state.SubtitleStream, performConversion) :
GetExtractedAssPath(state, performConversion);
var path = state.SubtitleStream.IsExternal ?
GetConvertedAssPath(state.SubtitleStream, performConversion, cancellationToken) :
GetExtractedAssPath(state, performConversion, cancellationToken);
if (string.IsNullOrEmpty(path))
{
@ -615,8 +619,11 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="state">The state.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
private string GetExtractedAssPath(StreamState state, bool performConversion)
private string GetExtractedAssPath(StreamState state,
bool performConversion,
CancellationToken cancellationToken)
{
var path = EncodingManager.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream.Index, ".ass");
@ -636,7 +643,7 @@ namespace MediaBrowser.Api.Playback
// See https://lists.ffmpeg.org/pipermail/ffmpeg-cvslog/2013-April/063616.html
var isAssSubtitle = string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase);
var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, isAssSubtitle, path, CancellationToken.None);
var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, isAssSubtitle, path, cancellationToken);
Task.WaitAll(task);
}
@ -652,11 +659,13 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the converted ass path.
/// </summary>
/// <param name="mediaPath">The media path.</param>
/// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
private string GetConvertedAssPath(string mediaPath, MediaStream subtitleStream, bool performConversion)
private string GetConvertedAssPath(MediaStream subtitleStream,
bool performConversion,
CancellationToken cancellationToken)
{
var path = EncodingManager.GetSubtitleCachePath(subtitleStream.Path, ".ass");
@ -668,7 +677,7 @@ namespace MediaBrowser.Api.Playback
Directory.CreateDirectory(parentPath);
var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, CancellationToken.None);
var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, cancellationToken);
Task.WaitAll(task);
}
@ -696,7 +705,7 @@ namespace MediaBrowser.Api.Playback
// Add resolution params, if specified
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
{
outputSizeParam = GetOutputSizeParam(state, outputVideoCodec, false).TrimEnd('"');
outputSizeParam = GetOutputSizeParam(state, outputVideoCodec, false, CancellationToken.None).TrimEnd('"');
outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
}
@ -842,7 +851,7 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected string GetInputArgument(StreamState state)
{
var type = InputType.File;
var type = state.IsRemote ? InputType.Url : InputType.File;
var inputPath = new[] { state.MediaPath };
@ -862,8 +871,10 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="state">The state.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationTokenSource">The cancellation token source.</param>
/// <returns>Task.</returns>
protected async Task StartFfMpeg(StreamState state, string outputPath)
/// <exception cref="System.InvalidOperationException">ffmpeg was not found at + MediaEncoder.EncoderPath</exception>
protected async Task StartFfMpeg(StreamState state, string outputPath, CancellationTokenSource cancellationTokenSource)
{
if (!File.Exists(MediaEncoder.EncoderPath))
{
@ -874,7 +885,7 @@ namespace MediaBrowser.Api.Playback
if (state.IsInputVideo && state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
{
state.IsoMount = await IsoManager.Mount(state.MediaPath, CancellationToken.None).ConfigureAwait(false);
state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
}
var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
@ -906,7 +917,7 @@ namespace MediaBrowser.Api.Playback
EnableRaisingEvents = true
};
ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId);
ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId, cancellationTokenSource);
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
Logger.Info(commandLineLogMessage);
@ -918,7 +929,7 @@ namespace MediaBrowser.Api.Playback
state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine);
await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length).ConfigureAwait(false);
await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
process.Exited += (sender, args) => OnFfMpegProcessExited(process, state);
@ -946,19 +957,19 @@ namespace MediaBrowser.Api.Playback
// Wait for the file to exist before proceeeding
while (!File.Exists(outputPath))
{
await Task.Delay(100).ConfigureAwait(false);
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
}
// Allow a small amount of time to buffer a little
if (state.IsInputVideo)
{
await Task.Delay(500).ConfigureAwait(false);
await Task.Delay(500, cancellationTokenSource.Token).ConfigureAwait(false);
}
// This is arbitrary, but add a little buffer time when internet streaming
if (state.IsRemote)
{
await Task.Delay(3000).ConfigureAwait(false);
await Task.Delay(3000, cancellationTokenSource.Token).ConfigureAwait(false);
}
}
@ -1050,13 +1061,19 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the user agent param.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
private string GetUserAgentParam(string path)
private string GetUserAgentParam(StreamState state)
{
var useragent = GetUserAgent(path);
string useragent;
state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
if (!string.IsNullOrEmpty(useragent))
if (string.IsNullOrWhiteSpace(useragent))
{
useragent = GetUserAgent(state.MediaPath);
}
if (!string.IsNullOrWhiteSpace(useragent))
{
return "-user-agent \"" + useragent + "\"";
}
@ -1337,9 +1354,7 @@ namespace MediaBrowser.Api.Playback
state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault();
}
var item = string.IsNullOrEmpty(request.MediaSourceId) ?
LibraryManager.GetItemById(request.Id) :
LibraryManager.GetItemById(request.MediaSourceId);
var item = LibraryManager.GetItemById(request.Id);
if (user != null && item.GetPlayAccess(user) != PlayAccess.Full)
{
@ -1427,19 +1442,24 @@ namespace MediaBrowser.Api.Playback
}
else if (item is IChannelMediaItem)
{
var source = await GetChannelMediaInfo(request.Id, CancellationToken.None).ConfigureAwait(false);
var source = await GetChannelMediaInfo(request.Id, request.MediaSourceId, cancellationToken).ConfigureAwait(false);
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
state.IsRemote = source.IsRemote;
state.IsRemote = source.LocationType == LocationType.Remote;
state.MediaPath = source.Path;
state.RunTimeTicks = item.RunTimeTicks;
mediaStreams = GetMediaStreams(source).ToList();
state.RemoteHttpHeaders = source.RequiredHttpHeaders;
mediaStreams = source.MediaStreams;
}
else
{
state.MediaPath = item.Path;
state.IsRemote = item.LocationType == LocationType.Remote;
var mediaSource = string.IsNullOrWhiteSpace(request.MediaSourceId)
? item
: LibraryManager.GetItemById(request.MediaSourceId);
var video = item as Video;
state.MediaPath = mediaSource.Path;
state.IsRemote = mediaSource.LocationType == LocationType.Remote;
var video = mediaSource as Video;
if (video != null)
{
@ -1461,20 +1481,20 @@ namespace MediaBrowser.Api.Playback
state.InputContainer = video.Container;
}
var audio = item as Audio;
var audio = mediaSource as Audio;
if (audio != null)
{
state.InputContainer = audio.Container;
}
state.RunTimeTicks = item.RunTimeTicks;
state.RunTimeTicks = mediaSource.RunTimeTicks;
}
var videoRequest = request as VideoStreamRequest;
mediaStreams = mediaStreams ?? ItemRepository.GetMediaStreams(new MediaStreamQuery
{
ItemId = item.Id
ItemId = new Guid(string.IsNullOrWhiteSpace(request.MediaSourceId) ? request.Id : request.MediaSourceId)
}).ToList();
@ -1545,65 +1565,32 @@ namespace MediaBrowser.Api.Playback
}
}
private IEnumerable<MediaStream> GetMediaStreams(ChannelMediaInfo info)
{
var list = new List<MediaStream>();
if (!string.IsNullOrWhiteSpace(info.VideoCodec) &&
!string.IsNullOrWhiteSpace(info.AudioCodec))
{
list.Add(new MediaStream
{
Type = MediaStreamType.Video,
Width = info.Width,
RealFrameRate = info.Framerate,
Profile = info.VideoProfile,
Level = info.VideoLevel,
Index = -1,
Height = info.Height,
Codec = info.VideoCodec,
BitRate = info.VideoBitrate,
AverageFrameRate = info.Framerate
});
list.Add(new MediaStream
{
Type = MediaStreamType.Audio,
Index = -1,
Codec = info.AudioCodec,
BitRate = info.AudioBitrate,
Channels = info.AudioChannels,
SampleRate = info.AudioSampleRate
});
}
return list;
}
private async Task<ChannelMediaInfo> GetChannelMediaInfo(string id, CancellationToken cancellationToken)
private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,
string mediaSourceId,
CancellationToken cancellationToken)
{
var channelMediaSources = await ChannelManager.GetChannelItemMediaSources(id, cancellationToken)
.ConfigureAwait(false);
var list = channelMediaSources.ToList();
var preferredWidth = ServerConfigurationManager.Configuration.ChannelOptions.PreferredStreamingWidth;
if (preferredWidth.HasValue)
if (!string.IsNullOrWhiteSpace(mediaSourceId))
{
var val = preferredWidth.Value;
var source = list
.FirstOrDefault(i => string.Equals(mediaSourceId, i.Id));
return list
.OrderBy(i => Math.Abs(i.Width ?? 0 - val))
.ThenByDescending(i => i.Width ?? 0)
.ThenBy(list.IndexOf)
.First();
if (source != null)
{
return source;
}
Logger.Warn("Invalid channel MediaSourceId requested, defaulting to first. Item: {0}. Requested MediaSourceId: {1}.",
id,
mediaSourceId
);
}
return list
.OrderByDescending(i => i.Width ?? 0)
.ThenBy(list.IndexOf)
.First();
return list.First();
}
private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
@ -1932,7 +1919,11 @@ namespace MediaBrowser.Api.Playback
inputModifier += " " + probeSize;
inputModifier = inputModifier.Trim();
inputModifier += " " + GetUserAgentParam(state.MediaPath);
if (state.IsRemote)
{
inputModifier += " " + GetUserAgentParam(state);
}
inputModifier = inputModifier.Trim();
inputModifier += " " + GetFastSeekCommandLineParameter(state.Request);

View File

@ -25,7 +25,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public abstract class BaseHlsService : BaseStreamingService
{
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager)
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient)
{
}
@ -82,7 +82,9 @@ namespace MediaBrowser.Api.Playback.Hls
/// </exception>
private async Task<object> ProcessRequestAsync(StreamRequest request)
{
var state = GetState(request, CancellationToken.None).Result;
var cancellationTokenSource = new CancellationTokenSource();
var state = GetState(request, cancellationTokenSource.Token).Result;
var playlist = GetOutputFilePath(state);
@ -92,7 +94,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
else
{
await FfmpegStartLock.WaitAsync().ConfigureAwait(false);
await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
try
{
if (File.Exists(playlist))
@ -104,16 +106,16 @@ namespace MediaBrowser.Api.Playback.Hls
// If the playlist doesn't already exist, startup ffmpeg
try
{
await StartFfMpeg(state, playlist).ConfigureAwait(false);
await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false);
}
catch
{
state.Dispose();
throw;
}
}
await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false);
await WaitForMinimumSegmentCount(playlist, GetSegmentWait(), cancellationTokenSource.Token).ConfigureAwait(false);
}
}
finally
{
@ -220,10 +222,12 @@ namespace MediaBrowser.Api.Playback.Hls
return builder.ToString();
}
protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount)
protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken)
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
string fileText;
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
@ -240,7 +244,7 @@ namespace MediaBrowser.Api.Playback.Hls
break;
}
await Task.Delay(25).ConfigureAwait(false);
await Task.Delay(25, cancellationToken).ConfigureAwait(false);
}
}
@ -287,7 +291,7 @@ namespace MediaBrowser.Api.Playback.Hls
// If performSubtitleConversions is true we're actually starting ffmpeg
var startNumberParam = performSubtitleConversions ? GetStartNumber(state).ToString(UsCulture) : "0";
var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9} \"{10}\"",
var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"",
itsOffset,
inputModifier,
GetInputArgument(state),
@ -309,7 +313,7 @@ namespace MediaBrowser.Api.Playback.Hls
var bitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? 64000;
var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time {2} -start_number {3} -hls_list_size {4} \"{5}\"",
var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time {2} -start_number {3} -hls_list_size {4} -y \"{5}\"",
threads,
bitrate / 2,
state.SegmentLength.ToString(UsCulture),

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@ -16,6 +17,7 @@ using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MimeTypes = ServiceStack.MimeTypes;
namespace MediaBrowser.Api.Playback.Hls
{
@ -60,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Hls
public class DynamicHlsService : BaseHlsService
{
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager)
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient)
{
}
@ -98,9 +100,9 @@ namespace MediaBrowser.Api.Playback.Hls
if (!File.Exists(playlistPath))
{
await StartFfMpeg(state, playlistPath).ConfigureAwait(false);
await StartFfMpeg(state, playlistPath, new CancellationTokenSource()).ConfigureAwait(false);
await WaitForMinimumSegmentCount(playlistPath, GetSegmentWait()).ConfigureAwait(false);
await WaitForMinimumSegmentCount(playlistPath, GetSegmentWait(), CancellationToken.None).ConfigureAwait(false);
}
return GetSegementResult(path);
@ -283,7 +285,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue)
{
args += GetOutputSizeParam(state, codec, performSubtitleConversion);
args += GetOutputSizeParam(state, codec, performSubtitleConversion, CancellationToken.None);
}
}

View File

@ -1,4 +1,6 @@
using System.Threading;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@ -54,7 +56,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public class VideoHlsService : BaseHlsService
{
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager)
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient)
{
}
@ -155,7 +157,8 @@ namespace MediaBrowser.Api.Playback.Hls
/// <param name="state">The state.</param>
/// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param>
/// <returns>System.String.</returns>
protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion)
protected override string GetVideoArguments(StreamState state,
bool performSubtitleConversion)
{
var codec = GetVideoCodec(state.VideoRequest);
@ -178,7 +181,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue)
{
args += GetOutputSizeParam(state, codec, performSubtitleConversion);
args += GetOutputSizeParam(state, codec, performSubtitleConversion, CancellationToken.None);
}
}

View File

@ -44,7 +44,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
public class AudioService : BaseProgressiveStreamingService
{
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, imageProcessor, httpClient)
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient, imageProcessor)
{
}
@ -105,7 +105,7 @@ namespace MediaBrowser.Api.Playback.Progressive
var inputModifier = GetInputModifier(state);
return string.Format("{0} -i {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 \"{5}\"",
return string.Format("{0} -i {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
inputModifier,
GetInputArgument(state),
threads,

View File

@ -25,12 +25,10 @@ namespace MediaBrowser.Api.Playback.Progressive
public abstract class BaseProgressiveStreamingService : BaseStreamingService
{
protected readonly IImageProcessor ImageProcessor;
protected readonly IHttpClient HttpClient;
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager)
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient)
{
ImageProcessor = imageProcessor;
HttpClient = httpClient;
}
/// <summary>
@ -280,7 +278,7 @@ namespace MediaBrowser.Api.Playback.Progressive
if (!File.Exists(outputPath))
{
await StartFfMpeg(state, outputPath).ConfigureAwait(false);
await StartFfMpeg(state, outputPath, new CancellationTokenSource()).ConfigureAwait(false);
}
else
{

View File

@ -32,6 +32,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
/// <param name="path">The path.</param>
/// <param name="logger">The logger.</param>
/// <param name="fileSystem">The file system.</param>
public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem)
{
Path = path;

View File

@ -1,3 +1,4 @@
using System.Threading;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
@ -60,7 +61,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
public class VideoService : BaseProgressiveStreamingService
{
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, imageProcessor, httpClient)
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient, imageProcessor)
{
}
@ -108,7 +109,7 @@ namespace MediaBrowser.Api.Playback.Progressive
var inputModifier = GetInputModifier(state);
return string.Format("{0} -i {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} \"{8}\"",
return string.Format("{0} -i {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
inputModifier,
GetInputArgument(state),
keyFrame,
@ -156,7 +157,7 @@ namespace MediaBrowser.Api.Playback.Progressive
{
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
{
args += GetOutputSizeParam(state, codec, performSubtitleConversion);
args += GetOutputSizeParam(state, codec, performSubtitleConversion, CancellationToken.None);
}
}

View File

@ -1,73 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Api.Playback.Progressive;
namespace MediaBrowser.Api.Playback
{
//public class GetProgressiveAudioStream : StreamRequest
//{
//}
//public class ProgressiveStreamService : BaseApiService
//{
// public object Get(GetProgressiveAudioStream request)
// {
// return ProcessRequest(request, false);
// }
// /// <summary>
// /// Gets the specified request.
// /// </summary>
// /// <param name="request">The request.</param>
// /// <returns>System.Object.</returns>
// public object Head(GetProgressiveAudioStream request)
// {
// return ProcessRequest(request, true);
// }
// protected object ProcessRequest(StreamRequest request, bool isHeadRequest)
// {
// var state = GetState(request, CancellationToken.None).Result;
// var responseHeaders = new Dictionary<string, string>();
// if (request.Static && state.IsRemote)
// {
// AddDlnaHeaders(state, responseHeaders, true);
// return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result;
// }
// var outputPath = GetOutputFilePath(state);
// var outputPathExists = File.Exists(outputPath);
// var isStatic = request.Static ||
// (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive));
// AddDlnaHeaders(state, responseHeaders, isStatic);
// if (request.Static)
// {
// var contentType = state.GetMimeType(state.MediaPath);
// return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
// }
// if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive))
// {
// var contentType = state.GetMimeType(outputPath);
// return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
// }
// return GetStreamResult(state, responseHeaders, isHeadRequest).Result;
// }
//}
}

View File

@ -31,8 +31,11 @@ namespace MediaBrowser.Api.Playback
public StreamState()
{
PlayableStreamFileNames = new List<string>();
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public Dictionary<string, string> RemoteHttpHeaders { get; set; }
/// <summary>
/// Gets or sets the log file stream.
/// </summary>
@ -40,7 +43,7 @@ namespace MediaBrowser.Api.Playback
public Stream LogFileStream { get; set; }
public string InputContainer { get; set; }
public MediaStream AudioStream { get; set; }
public MediaStream VideoStream { get; set; }
public MediaStream SubtitleStream { get; set; }
@ -277,8 +280,8 @@ namespace MediaBrowser.Api.Playback
{
get
{
var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ?
TransportStreamTimestamp.Valid :
var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ?
TransportStreamTimestamp.Valid :
TransportStreamTimestamp.None;
return !Request.Static

View File

@ -371,7 +371,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
Headers = new NameValueCollection(httpResponse.Headers),
ContentLength = contentLength
ContentLength = contentLength,
ResponseUrl = httpResponse.ResponseUri.ToString()
};
}

View File

@ -15,6 +15,12 @@ namespace MediaBrowser.Common.Net
/// <value>The type of the content.</value>
public string ContentType { get; set; }
/// <summary>
/// Gets or sets the response URL.
/// </summary>
/// <value>The response URL.</value>
public string ResponseUrl { get; set; }
/// <summary>
/// Gets or sets the content.
/// </summary>

View File

@ -28,6 +28,11 @@ namespace MediaBrowser.Controller.Channels
return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent);
}
public override string GetUserDataKey()
{
return ExternalId;
}
public override bool SupportsLocalMetadata
{
get

View File

@ -35,5 +35,10 @@ namespace MediaBrowser.Controller.Channels
{
Tags = new List<string>();
}
public override string GetUserDataKey()
{
return ExternalId;
}
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Channels
{
@ -27,7 +28,7 @@ namespace MediaBrowser.Controller.Channels
public ChannelMediaInfo()
{
RequiredHttpHeaders = new Dictionary<string, string>();
RequiredHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
IsRemote = true;
}
}

View File

@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Channels
}
}
return base.GetUserDataKey();
return ExternalId;
}
protected override bool GetBlockUnratedValue(UserConfiguration config)

View File

@ -49,6 +49,14 @@ namespace MediaBrowser.Controller.Channels
/// <returns>Task{IEnumerable{ChannelItemInfo}}.</returns>
Task<IEnumerable<ChannelItemInfo>> Search(ChannelSearchInfo searchInfo, User user, CancellationToken cancellationToken);
/// <summary>
/// Gets all media.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{ChannelItemResult}.</returns>
Task<ChannelItemResult> GetAllMedia(InternalAllChannelMediaQuery query, CancellationToken cancellationToken);
/// <summary>
/// Gets the channel items.
/// </summary>

View File

@ -16,6 +16,12 @@ namespace MediaBrowser.Controller.Channels
/// <param name="factories">The factories.</param>
void AddParts(IEnumerable<IChannel> channels, IEnumerable<IChannelFactory> factories);
/// <summary>
/// Gets the channel download path.
/// </summary>
/// <value>The channel download path.</value>
string ChannelDownloadPath { get; }
/// <summary>
/// Gets the channel features.
/// </summary>
@ -23,6 +29,12 @@ namespace MediaBrowser.Controller.Channels
/// <returns>ChannelFeatures.</returns>
ChannelFeatures GetChannelFeatures(string id);
/// <summary>
/// Gets all channel features.
/// </summary>
/// <returns>IEnumerable{ChannelFeatures}.</returns>
IEnumerable<ChannelFeatures> GetAllChannelFeatures();
/// <summary>
/// Gets the channel.
/// </summary>
@ -38,6 +50,14 @@ namespace MediaBrowser.Controller.Channels
/// <returns>Task{QueryResult{BaseItemDto}}.</returns>
Task<QueryResult<BaseItemDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken);
/// <summary>
/// Gets all media.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{QueryResult{BaseItemDto}}.</returns>
Task<QueryResult<BaseItemDto>> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken);
/// <summary>
/// Gets the channel items.
/// </summary>
@ -52,6 +72,6 @@ namespace MediaBrowser.Controller.Channels
/// <param name="id">The identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{ChannelMediaInfo}}.</returns>
Task<IEnumerable<ChannelMediaInfo>> GetChannelItemMediaSources(string id, CancellationToken cancellationToken);
Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, CancellationToken cancellationToken);
}
}

View File

@ -7,6 +7,9 @@ namespace MediaBrowser.Controller.Channels
{
bool IsInfiniteStream { get; set; }
long? RunTimeTicks { get; set; }
string MediaType { get; }
ChannelMediaContentType ContentType { get; set; }
List<ChannelMediaInfo> ChannelMediaSources { get; set; }

View File

@ -11,6 +11,12 @@ namespace MediaBrowser.Controller.Channels
/// <value><c>true</c> if this instance can search; otherwise, <c>false</c>.</value>
public bool CanSearch { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance can get all media.
/// </summary>
/// <value><c>true</c> if this instance can get all media; otherwise, <c>false</c>.</value>
public bool CanGetAllMedia { get; set; }
/// <summary>
/// Gets or sets the media types.
/// </summary>

View File

@ -17,4 +17,9 @@ namespace MediaBrowser.Controller.Channels
public bool SortDescending { get; set; }
}
public class InternalAllChannelMediaQuery
{
public User User { get; set; }
}
}

View File

@ -284,6 +284,9 @@
<Compile Include="..\MediaBrowser.Model\Dto\IItemDto.cs">
<Link>Dto\IItemDto.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dto\ImageByNameInfo.cs">
<Link>Dto\ImageByNameInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dto\ImageInfo.cs">
<Link>Dto\ImageInfo.cs</Link>
</Compile>

View File

@ -271,6 +271,9 @@
<Compile Include="..\MediaBrowser.Model\Dto\IItemDto.cs">
<Link>Dto\IItemDto.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dto\ImageByNameInfo.cs">
<Link>Dto\ImageByNameInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dto\ImageInfo.cs">
<Link>Dto\ImageInfo.cs</Link>
</Compile>

View File

@ -4,12 +4,30 @@ namespace MediaBrowser.Model.Channels
{
public class ChannelFeatures
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>The identifier.</value>
public string Id { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance can search.
/// </summary>
/// <value><c>true</c> if this instance can search; otherwise, <c>false</c>.</value>
public bool CanSearch { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance can get all media.
/// </summary>
/// <value><c>true</c> if this instance can get all media; otherwise, <c>false</c>.</value>
public bool CanGetAllMedia { get; set; }
/// <summary>
/// Gets or sets the media types.
/// </summary>
@ -44,6 +62,12 @@ namespace MediaBrowser.Model.Channels
/// <value><c>true</c> if this instance can filter; otherwise, <c>false</c>.</value>
public bool CanFilter { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance can download all media.
/// </summary>
/// <value><c>true</c> if this instance can download all media; otherwise, <c>false</c>.</value>
public bool CanDownloadAllMedia { get; set; }
public ChannelFeatures()
{
MediaTypes = new List<ChannelMediaType>();

View File

@ -20,4 +20,33 @@
/// <value>The limit.</value>
public int? Limit { get; set; }
}
public class AllChannelMediaQuery
{
public string[] ChannelIds { get; set; }
/// <summary>
/// Gets or sets the user identifier.
/// </summary>
/// <value>The user identifier.</value>
public string UserId { get; set; }
/// <summary>
/// Skips over a given number of items within the results. Use for paging.
/// </summary>
/// <value>The start index.</value>
public int? StartIndex { get; set; }
/// <summary>
/// The maximum number of items to return
/// </summary>
/// <value>The limit.</value>
public int? Limit { get; set; }
public AllChannelMediaQuery()
{
ChannelIds = new string[] { };
}
}
}

View File

@ -303,5 +303,16 @@ namespace MediaBrowser.Model.Configuration
public class ChannelOptions
{
public int? PreferredStreamingWidth { get; set; }
public string DownloadPath { get; set; }
public int? MaxDownloadAge { get; set; }
public string[] DownloadingChannels { get; set; }
public ChannelOptions()
{
DownloadingChannels = new string[] { };
MaxDownloadAge = 30;
}
}
}

View File

@ -0,0 +1,32 @@

namespace MediaBrowser.Model.Dto
{
public class ImageByNameInfo
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the theme.
/// </summary>
/// <value>The theme.</value>
public string Theme { get; set; }
/// <summary>
/// Gets or sets the context.
/// </summary>
/// <value>The context.</value>
public string Context { get; set; }
/// <summary>
/// Gets or sets the length of the file.
/// </summary>
/// <value>The length of the file.</value>
public long FileLength { get; set; }
/// <summary>
/// Gets or sets the format.
/// </summary>
/// <value>The format.</value>
public string Format { get; set; }
}
}

View File

@ -1,7 +1,6 @@
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
@ -35,11 +34,13 @@ namespace MediaBrowser.Model.Dto
public int? Bitrate { get; set; }
public TransportStreamTimestamp? Timestamp { get; set; }
public Dictionary<string, string> RequiredHttpHeaders { get; set; }
public MediaSourceInfo()
{
Formats = new List<string>();
MediaStreams = new List<MediaStream>();
RequiredHttpHeaders = new Dictionary<string, string>();
}
public int? DefaultAudioStreamIndex { get; set; }

View File

@ -125,6 +125,7 @@
<Compile Include="Dto\ChapterInfoDto.cs" />
<Compile Include="Dto\GameSystemSummary.cs" />
<Compile Include="Dto\IItemDto.cs" />
<Compile Include="Dto\ImageByNameInfo.cs" />
<Compile Include="Dto\ImageInfo.cs" />
<Compile Include="Dto\ItemByNameCounts.cs" />
<Compile Include="Dto\ItemCounts.cs" />

View File

@ -46,48 +46,51 @@ namespace MediaBrowser.Providers.Music
{
var updateType = base.BeforeSave(item);
var songs = item.RecursiveChildren.OfType<Audio>().ToList();
if (!item.LockedFields.Contains(MetadataFields.Genres))
var songs = item.RecursiveChildren.OfType<Audio>().ToList();
if (!item.IsLocked)
{
var currentList = item.Genres.ToList();
item.Genres = songs.SelectMany(i => i.Genres)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
if (!item.LockedFields.Contains(MetadataFields.Genres))
{
updateType = updateType | ItemUpdateType.MetadataDownload;
}
}
var currentList = item.Genres.ToList();
if (!item.LockedFields.Contains(MetadataFields.Studios))
{
var currentList = item.Studios.ToList();
item.Genres = songs.SelectMany(i => i.Genres)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
item.Studios = songs.SelectMany(i => i.Studios)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (currentList.Count != item.Studios.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
{
updateType = updateType | ItemUpdateType.MetadataDownload;
}
}
if (!item.LockedFields.Contains(MetadataFields.Name))
{
var name = songs.Select(i => i.Album).FirstOrDefault(i => !string.IsNullOrEmpty(i));
if (!string.IsNullOrEmpty(name))
{
if (!string.Equals(item.Name, name, StringComparison.Ordinal))
if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
{
item.Name = name;
updateType = updateType | ItemUpdateType.MetadataDownload;
}
}
if (!item.LockedFields.Contains(MetadataFields.Studios))
{
var currentList = item.Studios.ToList();
item.Studios = songs.SelectMany(i => i.Studios)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (currentList.Count != item.Studios.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
{
updateType = updateType | ItemUpdateType.MetadataDownload;
}
}
if (!item.LockedFields.Contains(MetadataFields.Name))
{
var name = songs.Select(i => i.Album).FirstOrDefault(i => !string.IsNullOrEmpty(i));
if (!string.IsNullOrEmpty(name))
{
if (!string.Equals(item.Name, name, StringComparison.Ordinal))
{
item.Name = name;
updateType = updateType | ItemUpdateType.MetadataDownload;
}
}
}
}
updateType = updateType | SetAlbumArtistFromSongs(item, songs);

View File

@ -0,0 +1,277 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Channels
{
public class ChannelDownloadScheduledTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly IChannelManager _manager;
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
public ChannelDownloadScheduledTask(IChannelManager manager, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem, ILibraryManager libraryManager)
{
_manager = manager;
_config = config;
_logger = logger;
_httpClient = httpClient;
_fileSystem = fileSystem;
_libraryManager = libraryManager;
}
public string Name
{
get { return "Download channel content"; }
}
public string Description
{
get { return "Downloads channel content based on configuration."; }
}
public string Category
{
get { return "Channels"; }
}
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
CleanChannelContent(cancellationToken);
progress.Report(5);
await DownloadChannelContent(cancellationToken, progress).ConfigureAwait(false);
progress.Report(100);
}
private void CleanChannelContent(CancellationToken cancellationToken)
{
if (!_config.Configuration.ChannelOptions.MaxDownloadAge.HasValue)
{
return;
}
var minDateModified = DateTime.UtcNow.AddDays(0 - _config.Configuration.ChannelOptions.MaxDownloadAge.Value);
var path = _manager.ChannelDownloadPath;
try
{
DeleteCacheFilesFromDirectory(cancellationToken, path, minDateModified, new Progress<double>());
}
catch (DirectoryNotFoundException)
{
// No biggie here. Nothing to delete
}
}
private async Task DownloadChannelContent(CancellationToken cancellationToken, IProgress<double> progress)
{
if (_config.Configuration.ChannelOptions.DownloadingChannels.Length == 0)
{
return;
}
var result = await _manager.GetAllMedia(new AllChannelMediaQuery
{
ChannelIds = _config.Configuration.ChannelOptions.DownloadingChannels
}, cancellationToken).ConfigureAwait(false);
var path = _manager.ChannelDownloadPath;
var numComplete = 0;
foreach (var item in result.Items)
{
try
{
await DownloadChannelItem(item, cancellationToken, path);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
_logger.ErrorException("Error downloading channel content for {0}", ex, item.Name);
}
numComplete++;
double percent = numComplete;
percent /= result.Items.Length;
progress.Report(percent * 95 + 5);
}
}
private async Task DownloadChannelItem(BaseItemDto item,
CancellationToken cancellationToken,
string path)
{
var sources = await _manager.GetChannelItemMediaSources(item.Id, cancellationToken)
.ConfigureAwait(false);
var list = sources.ToList();
var cachedVersions = list.Where(i => i.LocationType == LocationType.FileSystem).ToList();
if (cachedVersions.Count > 0)
{
await RefreshMediaSourceItems(cachedVersions, item.IsVideo, cancellationToken).ConfigureAwait(false);
return;
}
var source = list.First();
var options = new HttpRequestOptions
{
CancellationToken = cancellationToken,
Url = source.Path,
Progress = new Progress<double>()
};
foreach (var header in source.RequiredHttpHeaders)
{
options.RequestHeaders[header.Key] = header.Value;
}
var destination = Path.Combine(path, item.ChannelId, source.Path.GetMD5().ToString("N"));
Directory.CreateDirectory(Path.GetDirectoryName(destination));
// Determine output extension
var response = await _httpClient.GetTempFileResponse(options).ConfigureAwait(false);
if (item.IsVideo && response.ContentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
{
var extension = response.ContentType.Split('/')
.Last();
destination += "." + extension;
}
else if (item.IsAudio && response.ContentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase))
{
var extension = response.ContentType.Replace("audio/mpeg", "audio/mp3", StringComparison.OrdinalIgnoreCase)
.Split('/')
.Last();
destination += "." + extension;
}
else
{
throw new ApplicationException("Unexpected response type encountered: " + response.ContentType);
}
File.Move(response.TempFilePath, destination);
await RefreshMediaSourceItem(destination, item.IsVideo, cancellationToken).ConfigureAwait(false);
}
private async Task RefreshMediaSourceItems(IEnumerable<MediaSourceInfo> items, bool isVideo, CancellationToken cancellationToken)
{
foreach (var item in items)
{
await RefreshMediaSourceItem(item.Path, isVideo, cancellationToken).ConfigureAwait(false);
}
}
private async Task RefreshMediaSourceItem(string path, bool isVideo, CancellationToken cancellationToken)
{
var item = _libraryManager.ResolvePath(new FileInfo(path));
if (item != null)
{
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
}
}
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
{
return new ITaskTrigger[]
{
new DailyTrigger { TimeOfDay = TimeSpan.FromHours(3) },
};
}
/// <summary>
/// Deletes the cache files from directory with a last write time less than a given date
/// </summary>
/// <param name="cancellationToken">The task cancellation token.</param>
/// <param name="directory">The directory.</param>
/// <param name="minDateModified">The min date modified.</param>
/// <param name="progress">The progress.</param>
private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress)
{
var filesToDelete = new DirectoryInfo(directory).EnumerateFiles("*", SearchOption.AllDirectories)
.Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
.ToList();
var index = 0;
foreach (var file in filesToDelete)
{
double percent = index;
percent /= filesToDelete.Count;
progress.Report(100 * percent);
cancellationToken.ThrowIfCancellationRequested();
DeleteFile(file.FullName);
index++;
}
progress.Report(100);
}
/// <summary>
/// Deletes the file.
/// </summary>
/// <param name="path">The path.</param>
private void DeleteFile(string path)
{
try
{
File.Delete(path);
}
catch (IOException ex)
{
_logger.ErrorException("Error deleting file {0}", ex, path);
}
}
public bool IsHidden
{
get
{
return !_manager.GetAllChannelFeatures()
.Any(i => i.CanDownloadAllMedia && _config.Configuration.ChannelOptions.DownloadingChannels.Contains(i.Id));
}
}
public bool IsEnabled
{
get
{
return true;
}
}
}
}

View File

@ -54,6 +54,19 @@ namespace MediaBrowser.Server.Implementations.Channels
_factories = factories.ToArray();
}
public string ChannelDownloadPath
{
get
{
if (!string.IsNullOrWhiteSpace(_config.Configuration.ChannelOptions.DownloadPath))
{
return _config.Configuration.ChannelOptions.DownloadPath;
}
return Path.Combine(_config.ApplicationPaths.ProgramDataPath, "channels");
}
}
private IEnumerable<IChannel> GetAllChannels()
{
return _factories
@ -156,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Channels
progress.Report(100);
}
public Task<IEnumerable<ChannelMediaInfo>> GetChannelItemMediaSources(string id, CancellationToken cancellationToken)
public async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, CancellationToken cancellationToken)
{
var item = (IChannelMediaItem)_libraryManager.GetItemById(id);
@ -166,12 +179,149 @@ namespace MediaBrowser.Server.Implementations.Channels
var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
IEnumerable<ChannelMediaInfo> results;
if (requiresCallback != null)
{
return requiresCallback.GetChannelItemMediaInfo(item.ExternalId, cancellationToken);
results = await requiresCallback.GetChannelItemMediaInfo(item.ExternalId, cancellationToken)
.ConfigureAwait(false);
}
else
{
results = item.ChannelMediaSources;
}
return Task.FromResult<IEnumerable<ChannelMediaInfo>>(item.ChannelMediaSources);
var sources = SortMediaInfoResults(results).Select(i => GetMediaSource(item, i))
.ToList();
var channelIdString = channel.Id.ToString("N");
var isVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
var cachedVersionTasks = sources
.Select(i => GetCachedVersion(channelIdString, i, isVideo, cancellationToken));
var cachedVersions = await Task.WhenAll(cachedVersionTasks).ConfigureAwait(false);
sources.InsertRange(0, cachedVersions.Where(i => i != null));
return sources;
}
private MediaSourceInfo GetMediaSource(IChannelMediaItem item, ChannelMediaInfo info)
{
var id = info.Path.GetMD5().ToString("N");
var source = new MediaSourceInfo
{
MediaStreams = GetMediaStreams(info).ToList(),
Container = info.Container,
LocationType = info.IsRemote ? LocationType.Remote : LocationType.FileSystem,
Path = info.Path,
RequiredHttpHeaders = info.RequiredHttpHeaders,
RunTimeTicks = item.RunTimeTicks,
Name = id,
Id = id
};
return source;
}
private async Task<MediaSourceInfo> GetCachedVersion(string channelId,
MediaSourceInfo info,
bool isVideo,
CancellationToken cancellationToken)
{
var filename = info.Path.GetMD5().ToString("N");
var path = Path.Combine(ChannelDownloadPath, channelId, filename);
try
{
var file = Directory.EnumerateFiles(Path.GetDirectoryName(path), "*", SearchOption.TopDirectoryOnly)
.FirstOrDefault(i => (Path.GetFileName(i) ?? string.Empty).StartsWith(filename, StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(file))
{
var source = new MediaSourceInfo
{
Path = file,
LocationType = LocationType.FileSystem,
Name = "Cached " + info.Name,
Id = file.GetMD5().ToString("N")
};
if (isVideo)
{
source.VideoType = VideoType.VideoFile;
}
return source;
}
}
catch (DirectoryNotFoundException)
{
return null;
}
return null;
}
private IEnumerable<MediaStream> GetMediaStreams(ChannelMediaInfo info)
{
var list = new List<MediaStream>();
if (!string.IsNullOrWhiteSpace(info.VideoCodec) &&
!string.IsNullOrWhiteSpace(info.AudioCodec))
{
list.Add(new MediaStream
{
Type = MediaStreamType.Video,
Width = info.Width,
RealFrameRate = info.Framerate,
Profile = info.VideoProfile,
Level = info.VideoLevel,
Index = -1,
Height = info.Height,
Codec = info.VideoCodec,
BitRate = info.VideoBitrate,
AverageFrameRate = info.Framerate
});
list.Add(new MediaStream
{
Type = MediaStreamType.Audio,
Index = -1,
Codec = info.AudioCodec,
BitRate = info.AudioBitrate,
Channels = info.AudioChannels,
SampleRate = info.AudioSampleRate
});
}
return list;
}
private IEnumerable<ChannelMediaInfo> SortMediaInfoResults(IEnumerable<ChannelMediaInfo> channelMediaSources)
{
var list = channelMediaSources.ToList();
var width = _config.Configuration.ChannelOptions.PreferredStreamingWidth;
if (width.HasValue)
{
var val = width.Value;
return list
.OrderBy(i => i.Width.HasValue && i.Width.Value <= val)
.ThenBy(i => Math.Abs(i.Width ?? 0 - val))
.ThenByDescending(i => i.Width ?? 0)
.ThenBy(list.IndexOf);
}
return list
.OrderByDescending(i => i.Width ?? 0)
.ThenBy(list.IndexOf);
}
private async Task<Channel> GetChannel(IChannel channelInfo, CancellationToken cancellationToken)
@ -237,26 +387,37 @@ namespace MediaBrowser.Server.Implementations.Channels
return (Channel)_libraryManager.GetItemById(new Guid(id));
}
public IEnumerable<ChannelFeatures> GetAllChannelFeatures()
{
return _channelEntities
.OrderBy(i => i.SortName)
.Select(i => GetChannelFeatures(i.Id.ToString("N")));
}
public ChannelFeatures GetChannelFeatures(string id)
{
var channel = GetChannel(id);
var channelProvider = GetChannelProvider(channel);
return GetChannelFeaturesDto(channelProvider.GetChannelFeatures());
return GetChannelFeaturesDto(channel, channelProvider.GetChannelFeatures());
}
public ChannelFeatures GetChannelFeaturesDto(InternalChannelFeatures features)
public ChannelFeatures GetChannelFeaturesDto(Channel channel, InternalChannelFeatures features)
{
return new ChannelFeatures
{
CanFilter = !features.MaxPageSize.HasValue,
CanGetAllMedia = features.CanGetAllMedia,
CanSearch = features.CanSearch,
ContentTypes = features.ContentTypes,
DefaultSortFields = features.DefaultSortFields,
MaxPageSize = features.MaxPageSize,
MediaTypes = features.MediaTypes,
SupportsSortOrderToggle = features.SupportsSortOrderToggle
SupportsSortOrderToggle = features.SupportsSortOrderToggle,
Name = channel.Name,
Id = channel.Id.ToString("N"),
CanDownloadAllMedia = features.CanGetAllMedia
};
}
@ -270,6 +431,85 @@ namespace MediaBrowser.Server.Implementations.Channels
return ("Channel " + name).GetMBId(typeof(Channel));
}
public async Task<QueryResult<BaseItemDto>> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken)
{
var user = string.IsNullOrWhiteSpace(query.UserId)
? null
: _userManager.GetUserById(new Guid(query.UserId));
var channels = _channels;
if (query.ChannelIds.Length > 0)
{
channels = channels
.Where(i => query.ChannelIds.Contains(GetInternalChannelId(i.Name).ToString("N")))
.ToArray();
}
var tasks = channels
.Where(i => i.GetChannelFeatures().CanGetAllMedia)
.Select(async i =>
{
try
{
var result = await i.GetAllMedia(new InternalAllChannelMediaQuery
{
User = user
}, cancellationToken).ConfigureAwait(false);
return new Tuple<IChannel, ChannelItemResult>(i, result);
}
catch (Exception ex)
{
_logger.ErrorException("Error getting all media from {0}", ex, i.Name);
return new Tuple<IChannel, ChannelItemResult>(i, new ChannelItemResult { });
}
});
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
var totalCount = results.Length;
IEnumerable<Tuple<IChannel, ChannelItemInfo>> items = results
.SelectMany(i => i.Item2.Items.Select(m => new Tuple<IChannel, ChannelItemInfo>(i.Item1, m)))
.OrderBy(i => i.Item2.Name);
if (query.StartIndex.HasValue)
{
items = items.Skip(query.StartIndex.Value);
}
if (query.Limit.HasValue)
{
items = items.Take(query.Limit.Value);
}
// Avoid implicitly captured closure
var token = cancellationToken;
var itemTasks = items.Select(i =>
{
var channelProvider = i.Item1;
var channel = GetChannel(GetInternalChannelId(channelProvider.Name).ToString("N"));
return GetChannelItemEntity(i.Item2, channelProvider, channel, token);
});
var internalItems = await Task.WhenAll(itemTasks).ConfigureAwait(false);
// Get everything
var fields = Enum.GetNames(typeof(ItemFields))
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
.ToList();
var returnItemArray = internalItems.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
.ToArray();
return new QueryResult<BaseItemDto>
{
TotalRecordCount = totalCount,
Items = returnItemArray
};
}
public async Task<QueryResult<BaseItemDto>> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken)
{
var queryChannelId = query.ChannelId;
@ -301,7 +541,7 @@ namespace MediaBrowser.Server.Implementations.Channels
ChannelItemSortField? sortField = null;
ChannelItemSortField parsedField;
if (query.SortBy.Length == 1 &&
if (query.SortBy.Length == 1 &&
Enum.TryParse(query.SortBy[0], true, out parsedField))
{
sortField = parsedField;
@ -309,11 +549,11 @@ namespace MediaBrowser.Server.Implementations.Channels
var sortDescending = query.SortOrder.HasValue && query.SortOrder.Value == SortOrder.Descending;
var itemsResult = await GetChannelItems(channelProvider,
user,
query.FolderId,
providerStartIndex,
providerLimit,
var itemsResult = await GetChannelItems(channelProvider,
user,
query.FolderId,
providerStartIndex,
providerLimit,
sortField,
sortDescending,
cancellationToken)

View File

@ -846,16 +846,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.Path))
{
var locationType = item.LocationType;
if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
{
dto.Path = GetMappedPath(item.Path);
}
else
{
dto.Path = item.Path;
}
dto.Path = GetMappedPath(item);
}
dto.PremiereDate = item.PremiereDate;
@ -1315,14 +1306,12 @@ namespace MediaBrowser.Server.Implementations.Dto
var locationType = item.LocationType;
if (locationType != LocationType.FileSystem && locationType != LocationType.Offline)
if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
{
return path;
}
foreach (var map in _config.Configuration.PathSubstitutions)
{
path = _fileSystem.SubstitutePath(path, map.From, map.To);
foreach (var map in _config.Configuration.PathSubstitutions)
{
path = _fileSystem.SubstitutePath(path, map.From, map.To);
}
}
return path;
@ -1418,16 +1407,6 @@ namespace MediaBrowser.Server.Implementations.Dto
return string.Join("/", terms.ToArray());
}
private string GetMappedPath(string path)
{
foreach (var map in _config.Configuration.PathSubstitutions)
{
path = _fileSystem.SubstitutePath(path, map.From, map.To);
}
return path;
}
private void SetProductionLocations(BaseItem item, BaseItemDto dto)
{
var hasProductionLocations = item as IHasProductionLocations;

View File

@ -297,6 +297,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var result = await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false);
Sanitize(result);
_logger.Debug("Live stream info: " + _json.SerializeToString(result));
if (!string.IsNullOrEmpty(result.Id))
@ -332,6 +334,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var result = await service.GetChannelStream(channel.ExternalId, cancellationToken).ConfigureAwait(false);
Sanitize(result);
_logger.Debug("Live stream info: " + _json.SerializeToString(result));
if (!string.IsNullOrEmpty(result.Id))
@ -353,6 +357,55 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
private void Sanitize(LiveStreamInfo info)
{
// Clean some bad data coming from providers
foreach (var stream in info.MediaStreams)
{
if (stream.BitDepth.HasValue && stream.BitDepth <= 0)
{
stream.BitDepth = null;
}
if (stream.BitRate.HasValue && stream.BitRate <= 0)
{
stream.BitRate = null;
}
if (stream.Channels.HasValue && stream.Channels <= 0)
{
stream.Channels = null;
}
if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0)
{
stream.AverageFrameRate = null;
}
if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0)
{
stream.RealFrameRate = null;
}
if (stream.Width.HasValue && stream.Width <= 0)
{
stream.Width = null;
}
if (stream.Height.HasValue && stream.Height <= 0)
{
stream.Height = null;
}
if (stream.SampleRate.HasValue && stream.SampleRate <= 0)
{
stream.SampleRate = null;
}
if (stream.Level.HasValue && stream.Level <= 0)
{
stream.Level = null;
}
if (stream.PacketLength.HasValue && stream.PacketLength <= 0)
{
stream.PacketLength = null;
}
}
}
private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
{
var path = Path.Combine(_config.ApplicationPaths.ItemsByNamePath, "tvchannels", _fileSystem.GetValidFilename(channelInfo.Name));

View File

@ -136,5 +136,7 @@
"HeaderSelectServerCachePathHelp": "Browse or enter the path to use for server cache files. The folder must be writeable. The location of this folder will directly impact server performance and should ideally be placed on a solid state drive.",
"HeaderSelectTranscodingPathHelp": "Browse or enter the path to use for transcoding temporary files. The folder must be writeable.",
"HeaderSelectImagesByNamePathHelp": "Browse or enter the path to your items by name folder. The folder must be writeable.",
"HeaderSelectMetadataPathHelp": "Browse or enter the path you'd like to store metadata within. The folder must be writeable."
"HeaderSelectMetadataPathHelp": "Browse or enter the path you'd like to store metadata within. The folder must be writeable.",
"HeaderSelectChannelDownloadPath": "Select Channel Download Path",
"HeaderSelectChannelDownloadPathHelp": "Browse or enter the path to use for storing channel cache files. The folder must be writeable."
}

View File

@ -798,7 +798,13 @@
"ButtonDismiss": "Dismiss",
"MessageLearnHowToCustomize": "Learn how to customize this page to your own personal tastes. Click your user icon in the top right corner of the screen to view and update your preferences.",
"ButtonEditOtherUserPreferences": "Edit this user's personal preferences.",
"ChannelStreamOptionBestAvailable": "Best available",
"LabelChannelStreamOptionBestAvailable": "Preferred streaming quality:",
"LabelChannelStreamOptionBestAvailableHelp": "Determines the selected quality when channel content is available in multiple resolutions."
"LabelChannelStreamQuality": "Preferred internet stream quality:",
"LabelChannelStreamQualityHelp": "In a low bandwidth environment, limiting quality can help ensure a smooth streaming experience.",
"OptionBestAvailableStreamQuality": "Best available",
"LabelEnableChannelContentDownloadingFor": "Enable channel content downloading for:",
"LabelEnableChannelContentDownloadingForHelp": "Some channels support downloading content prior to viewing. Enable this in low bandwidth enviornments to download channel content during off hours.",
"LabelChannelDownloadPath": "Channel content download path:",
"LabelChannelDownloadPathHelp": "Specify a custom download path if desired. Leave empty to download to an internal program data folder.",
"LabelChannelDownloadAge": "Delete content after: (days)",
"LabelChannelDownloadAgeHelp": "Downloaded content older than this will be deleted. It will remain playable via internet streaming."
}

View File

@ -98,6 +98,7 @@
<Compile Include="..\SharedVersion.cs">
<Link>Properties\SharedVersion.cs</Link>
</Compile>
<Compile Include="Channels\ChannelDownloadScheduledTask.cs" />
<Compile Include="Channels\ChannelImageProvider.cs" />
<Compile Include="Channels\ChannelItemImageProvider.cs" />
<Compile Include="Channels\ChannelManager.cs" />

View File

@ -6,6 +6,7 @@ using MediaBrowser.Model.Session;
using MediaBrowser.Model.System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading;
@ -125,13 +126,16 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
{
return Task.FromResult(true);
//return SendMessage(new WebSocketMessage<PlayRequest>
//{
// MessageType = "Play",
// Data = command
var dict = new Dictionary<string, string>();
//}, cancellationToken);
dict["ItemIds"] = string.Join(",", command.ItemIds);
if (command.StartPositionTicks.HasValue)
{
dict["StartPositionTicks"] = command.StartPositionTicks.Value.ToString(CultureInfo.InvariantCulture);
}
return SendMessage(command.PlayCommand.ToString(), dict, cancellationToken);
}
public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
@ -140,7 +144,12 @@ namespace MediaBrowser.Server.Implementations.Session
if (command.Command == PlaystateCommand.Seek)
{
if (!command.SeekPositionTicks.HasValue)
{
throw new ArgumentException("SeekPositionTicks cannot be null");
}
args["StartPositionTicks"] = command.SeekPositionTicks.Value.ToString(CultureInfo.InvariantCulture);
}
return SendMessage(command.Command.ToString(), cancellationToken);

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
<version>3.0.391</version>
<version>3.0.393</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.391" />
<dependency id="MediaBrowser.Common" version="3.0.393" />
<dependency id="NLog" version="2.1.0" />
<dependency id="SimpleInjector" version="2.5.0" />
<dependency id="sharpcompress" version="0.10.2" />

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common</id>
<version>3.0.391</version>
<version>3.0.393</version>
<title>MediaBrowser.Common</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
<version>3.0.391</version>
<version>3.0.393</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.391" />
<dependency id="MediaBrowser.Common" version="3.0.393" />
</dependencies>
</metadata>
<files>