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

View file

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

View file

@ -1,6 +1,7 @@
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
using ServiceStack; using ServiceStack;
using System; using System;
using System.Collections.Generic; 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> /// <summary>
/// Class ImageByNameService /// Class ImageByNameService
/// </summary> /// </summary>

View file

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

View file

@ -1,5 +1,6 @@
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
@ -13,6 +14,7 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Library; using MediaBrowser.Model.Library;
@ -72,20 +74,14 @@ namespace MediaBrowser.Api.Playback
protected ILiveTvManager LiveTvManager { get; private set; } protected ILiveTvManager LiveTvManager { get; private set; }
protected IDlnaManager DlnaManager { get; private set; } protected IDlnaManager DlnaManager { get; private set; }
protected IChannelManager ChannelManager { get; private set; } protected IChannelManager ChannelManager { get; private set; }
protected IHttpClient HttpClient { get; private set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class. /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary> /// </summary>
/// <param name="serverConfig">The server configuration.</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, IHttpClient httpClient)
/// <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)
{ {
HttpClient = httpClient;
ChannelManager = channelManager; ChannelManager = channelManager;
DlnaManager = dlnaManager; DlnaManager = dlnaManager;
EncodingManager = encodingManager; EncodingManager = encodingManager;
@ -483,8 +479,12 @@ namespace MediaBrowser.Api.Playback
/// <param name="state">The state.</param> /// <param name="state">The state.</param>
/// <param name="outputVideoCodec">The output video codec.</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="performTextSubtitleConversion">if set to <c>true</c> [perform text subtitle conversion].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns> /// <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/ // 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) if (state.SubtitleStream != null && !state.SubtitleStream.IsGraphicalSubtitleStream)
{ {
assSubtitleParam = GetTextSubtitleParam(state, performTextSubtitleConversion); assSubtitleParam = GetTextSubtitleParam(state, performTextSubtitleConversion, cancellationToken);
copyTsParam = " -copyts"; copyTsParam = " -copyts";
} }
@ -592,11 +592,15 @@ namespace MediaBrowser.Api.Playback
/// </summary> /// </summary>
/// <param name="state">The state.</param> /// <param name="state">The state.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param> /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns> /// <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) : var path = state.SubtitleStream.IsExternal ?
GetExtractedAssPath(state, performConversion); GetConvertedAssPath(state.SubtitleStream, performConversion, cancellationToken) :
GetExtractedAssPath(state, performConversion, cancellationToken);
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
{ {
@ -615,8 +619,11 @@ namespace MediaBrowser.Api.Playback
/// </summary> /// </summary>
/// <param name="state">The state.</param> /// <param name="state">The state.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param> /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns> /// <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"); 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 // 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 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); Task.WaitAll(task);
} }
@ -652,11 +659,13 @@ namespace MediaBrowser.Api.Playback
/// <summary> /// <summary>
/// Gets the converted ass path. /// Gets the converted ass path.
/// </summary> /// </summary>
/// <param name="mediaPath">The media path.</param>
/// <param name="subtitleStream">The subtitle stream.</param> /// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param> /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns> /// <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"); var path = EncodingManager.GetSubtitleCachePath(subtitleStream.Path, ".ass");
@ -668,7 +677,7 @@ namespace MediaBrowser.Api.Playback
Directory.CreateDirectory(parentPath); 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); Task.WaitAll(task);
} }
@ -696,7 +705,7 @@ namespace MediaBrowser.Api.Playback
// Add resolution params, if specified // Add resolution params, if specified
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) 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)); outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
} }
@ -842,7 +851,7 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns> /// <returns>System.String.</returns>
protected string GetInputArgument(StreamState state) protected string GetInputArgument(StreamState state)
{ {
var type = InputType.File; var type = state.IsRemote ? InputType.Url : InputType.File;
var inputPath = new[] { state.MediaPath }; var inputPath = new[] { state.MediaPath };
@ -862,8 +871,10 @@ namespace MediaBrowser.Api.Playback
/// </summary> /// </summary>
/// <param name="state">The state.</param> /// <param name="state">The state.</param>
/// <param name="outputPath">The output path.</param> /// <param name="outputPath">The output path.</param>
/// <param name="cancellationTokenSource">The cancellation token source.</param>
/// <returns>Task.</returns> /// <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)) 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)) 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); var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
@ -906,7 +917,7 @@ namespace MediaBrowser.Api.Playback
EnableRaisingEvents = true 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; var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
Logger.Info(commandLineLogMessage); Logger.Info(commandLineLogMessage);
@ -918,7 +929,7 @@ namespace MediaBrowser.Api.Playback
state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true); state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine); 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); process.Exited += (sender, args) => OnFfMpegProcessExited(process, state);
@ -946,19 +957,19 @@ namespace MediaBrowser.Api.Playback
// Wait for the file to exist before proceeeding // Wait for the file to exist before proceeeding
while (!File.Exists(outputPath)) 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 // Allow a small amount of time to buffer a little
if (state.IsInputVideo) 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 // This is arbitrary, but add a little buffer time when internet streaming
if (state.IsRemote) 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> /// <summary>
/// Gets the user agent param. /// Gets the user agent param.
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="state">The state.</param>
/// <returns>System.String.</returns> /// <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 + "\""; return "-user-agent \"" + useragent + "\"";
} }
@ -1337,9 +1354,7 @@ namespace MediaBrowser.Api.Playback
state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(); state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault();
} }
var item = string.IsNullOrEmpty(request.MediaSourceId) ? var item = LibraryManager.GetItemById(request.Id);
LibraryManager.GetItemById(request.Id) :
LibraryManager.GetItemById(request.MediaSourceId);
if (user != null && item.GetPlayAccess(user) != PlayAccess.Full) if (user != null && item.GetPlayAccess(user) != PlayAccess.Full)
{ {
@ -1427,19 +1442,24 @@ namespace MediaBrowser.Api.Playback
} }
else if (item is IChannelMediaItem) 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.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
state.IsRemote = source.IsRemote; state.IsRemote = source.LocationType == LocationType.Remote;
state.MediaPath = source.Path; state.MediaPath = source.Path;
state.RunTimeTicks = item.RunTimeTicks; state.RunTimeTicks = item.RunTimeTicks;
mediaStreams = GetMediaStreams(source).ToList(); state.RemoteHttpHeaders = source.RequiredHttpHeaders;
mediaStreams = source.MediaStreams;
} }
else else
{ {
state.MediaPath = item.Path; var mediaSource = string.IsNullOrWhiteSpace(request.MediaSourceId)
state.IsRemote = item.LocationType == LocationType.Remote; ? 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) if (video != null)
{ {
@ -1461,20 +1481,20 @@ namespace MediaBrowser.Api.Playback
state.InputContainer = video.Container; state.InputContainer = video.Container;
} }
var audio = item as Audio; var audio = mediaSource as Audio;
if (audio != null) if (audio != null)
{ {
state.InputContainer = audio.Container; state.InputContainer = audio.Container;
} }
state.RunTimeTicks = item.RunTimeTicks; state.RunTimeTicks = mediaSource.RunTimeTicks;
} }
var videoRequest = request as VideoStreamRequest; var videoRequest = request as VideoStreamRequest;
mediaStreams = mediaStreams ?? ItemRepository.GetMediaStreams(new MediaStreamQuery mediaStreams = mediaStreams ?? ItemRepository.GetMediaStreams(new MediaStreamQuery
{ {
ItemId = item.Id ItemId = new Guid(string.IsNullOrWhiteSpace(request.MediaSourceId) ? request.Id : request.MediaSourceId)
}).ToList(); }).ToList();
@ -1545,65 +1565,32 @@ namespace MediaBrowser.Api.Playback
} }
} }
private IEnumerable<MediaStream> GetMediaStreams(ChannelMediaInfo info) private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,
{ string mediaSourceId,
var list = new List<MediaStream>(); CancellationToken cancellationToken)
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)
{ {
var channelMediaSources = await ChannelManager.GetChannelItemMediaSources(id, cancellationToken) var channelMediaSources = await ChannelManager.GetChannelItemMediaSources(id, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
var list = channelMediaSources.ToList(); var list = channelMediaSources.ToList();
var preferredWidth = ServerConfigurationManager.Configuration.ChannelOptions.PreferredStreamingWidth; if (!string.IsNullOrWhiteSpace(mediaSourceId))
if (preferredWidth.HasValue)
{ {
var val = preferredWidth.Value; var source = list
.FirstOrDefault(i => string.Equals(mediaSourceId, i.Id));
return list if (source != null)
.OrderBy(i => Math.Abs(i.Width ?? 0 - val)) {
.ThenByDescending(i => i.Width ?? 0) return source;
.ThenBy(list.IndexOf) }
.First();
Logger.Warn("Invalid channel MediaSourceId requested, defaulting to first. Item: {0}. Requested MediaSourceId: {1}.",
id,
mediaSourceId
);
} }
return list return list.First();
.OrderByDescending(i => i.Width ?? 0)
.ThenBy(list.IndexOf)
.First();
} }
private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream) private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
@ -1932,7 +1919,11 @@ namespace MediaBrowser.Api.Playback
inputModifier += " " + probeSize; inputModifier += " " + probeSize;
inputModifier = inputModifier.Trim(); inputModifier = inputModifier.Trim();
inputModifier += " " + GetUserAgentParam(state.MediaPath); if (state.IsRemote)
{
inputModifier += " " + GetUserAgentParam(state);
}
inputModifier = inputModifier.Trim(); inputModifier = inputModifier.Trim();
inputModifier += " " + GetFastSeekCommandLineParameter(state.Request); inputModifier += " " + GetFastSeekCommandLineParameter(state.Request);

View file

@ -25,7 +25,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary> /// </summary>
public abstract class BaseHlsService : BaseStreamingService 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> /// </exception>
private async Task<object> ProcessRequestAsync(StreamRequest request) 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); var playlist = GetOutputFilePath(state);
@ -92,7 +94,7 @@ namespace MediaBrowser.Api.Playback.Hls
} }
else else
{ {
await FfmpegStartLock.WaitAsync().ConfigureAwait(false); await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
try try
{ {
if (File.Exists(playlist)) if (File.Exists(playlist))
@ -104,16 +106,16 @@ namespace MediaBrowser.Api.Playback.Hls
// If the playlist doesn't already exist, startup ffmpeg // If the playlist doesn't already exist, startup ffmpeg
try try
{ {
await StartFfMpeg(state, playlist).ConfigureAwait(false); await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false);
} }
catch catch
{ {
state.Dispose(); state.Dispose();
throw; throw;
} }
}
await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false); await WaitForMinimumSegmentCount(playlist, GetSegmentWait(), cancellationTokenSource.Token).ConfigureAwait(false);
}
} }
finally finally
{ {
@ -220,10 +222,12 @@ namespace MediaBrowser.Api.Playback.Hls
return builder.ToString(); return builder.ToString();
} }
protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount) protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken)
{ {
while (true) while (true)
{ {
cancellationToken.ThrowIfCancellationRequested();
string fileText; string fileText;
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written // 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; 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 // If performSubtitleConversions is true we're actually starting ffmpeg
var startNumberParam = performSubtitleConversions ? GetStartNumber(state).ToString(UsCulture) : "0"; 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, itsOffset,
inputModifier, inputModifier,
GetInputArgument(state), GetInputArgument(state),
@ -309,7 +313,7 @@ namespace MediaBrowser.Api.Playback.Hls
var bitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? 64000; 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, threads,
bitrate / 2, bitrate / 2,
state.SegmentLength.ToString(UsCulture), state.SegmentLength.ToString(UsCulture),

View file

@ -1,4 +1,5 @@
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
@ -16,6 +17,7 @@ using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MimeTypes = ServiceStack.MimeTypes;
namespace MediaBrowser.Api.Playback.Hls namespace MediaBrowser.Api.Playback.Hls
{ {
@ -60,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Hls
public class DynamicHlsService : BaseHlsService 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)) 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); 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) 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.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
@ -54,7 +56,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary> /// </summary>
public class VideoHlsService : BaseHlsService 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="state">The state.</param>
/// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param> /// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param>
/// <returns>System.String.</returns> /// <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); 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) 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> /// </summary>
public class AudioService : BaseProgressiveStreamingService 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); 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, inputModifier,
GetInputArgument(state), GetInputArgument(state),
threads, threads,

View file

@ -25,12 +25,10 @@ namespace MediaBrowser.Api.Playback.Progressive
public abstract class BaseProgressiveStreamingService : BaseStreamingService public abstract class BaseProgressiveStreamingService : BaseStreamingService
{ {
protected readonly IImageProcessor ImageProcessor; 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; ImageProcessor = imageProcessor;
HttpClient = httpClient;
} }
/// <summary> /// <summary>
@ -280,7 +278,7 @@ namespace MediaBrowser.Api.Playback.Progressive
if (!File.Exists(outputPath)) if (!File.Exists(outputPath))
{ {
await StartFfMpeg(state, outputPath).ConfigureAwait(false); await StartFfMpeg(state, outputPath, new CancellationTokenSource()).ConfigureAwait(false);
} }
else else
{ {

View file

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

View file

@ -1,3 +1,4 @@
using System.Threading;
using MediaBrowser.Common.IO; using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
@ -60,7 +61,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary> /// </summary>
public class VideoService : BaseProgressiveStreamingService 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); 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, inputModifier,
GetInputArgument(state), GetInputArgument(state),
keyFrame, keyFrame,
@ -156,7 +157,7 @@ namespace MediaBrowser.Api.Playback.Progressive
{ {
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) 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() public StreamState()
{ {
PlayableStreamFileNames = new List<string>(); PlayableStreamFileNames = new List<string>();
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
} }
public Dictionary<string, string> RemoteHttpHeaders { get; set; }
/// <summary> /// <summary>
/// Gets or sets the log file stream. /// Gets or sets the log file stream.
/// </summary> /// </summary>
@ -40,7 +43,7 @@ namespace MediaBrowser.Api.Playback
public Stream LogFileStream { get; set; } public Stream LogFileStream { get; set; }
public string InputContainer { get; set; } public string InputContainer { get; set; }
public MediaStream AudioStream { get; set; } public MediaStream AudioStream { get; set; }
public MediaStream VideoStream { get; set; } public MediaStream VideoStream { get; set; }
public MediaStream SubtitleStream { get; set; } public MediaStream SubtitleStream { get; set; }
@ -277,8 +280,8 @@ namespace MediaBrowser.Api.Playback
{ {
get get
{ {
var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ? var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ?
TransportStreamTimestamp.Valid : TransportStreamTimestamp.Valid :
TransportStreamTimestamp.None; TransportStreamTimestamp.None;
return !Request.Static return !Request.Static

View file

@ -371,7 +371,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
Headers = new NameValueCollection(httpResponse.Headers), 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> /// <value>The type of the content.</value>
public string ContentType { get; set; } public string ContentType { get; set; }
/// <summary>
/// Gets or sets the response URL.
/// </summary>
/// <value>The response URL.</value>
public string ResponseUrl { get; set; }
/// <summary> /// <summary>
/// Gets or sets the content. /// Gets or sets the content.
/// </summary> /// </summary>

View file

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

View file

@ -35,5 +35,10 @@ namespace MediaBrowser.Controller.Channels
{ {
Tags = new List<string>(); 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 namespace MediaBrowser.Controller.Channels
{ {
@ -27,7 +28,7 @@ namespace MediaBrowser.Controller.Channels
public ChannelMediaInfo() public ChannelMediaInfo()
{ {
RequiredHttpHeaders = new Dictionary<string, string>(); RequiredHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
IsRemote = true; IsRemote = true;
} }
} }

View file

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

View file

@ -49,6 +49,14 @@ namespace MediaBrowser.Controller.Channels
/// <returns>Task{IEnumerable{ChannelItemInfo}}.</returns> /// <returns>Task{IEnumerable{ChannelItemInfo}}.</returns>
Task<IEnumerable<ChannelItemInfo>> Search(ChannelSearchInfo searchInfo, User user, CancellationToken cancellationToken); 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> /// <summary>
/// Gets the channel items. /// Gets the channel items.
/// </summary> /// </summary>

View file

@ -16,6 +16,12 @@ namespace MediaBrowser.Controller.Channels
/// <param name="factories">The factories.</param> /// <param name="factories">The factories.</param>
void AddParts(IEnumerable<IChannel> channels, IEnumerable<IChannelFactory> factories); 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> /// <summary>
/// Gets the channel features. /// Gets the channel features.
/// </summary> /// </summary>
@ -23,6 +29,12 @@ namespace MediaBrowser.Controller.Channels
/// <returns>ChannelFeatures.</returns> /// <returns>ChannelFeatures.</returns>
ChannelFeatures GetChannelFeatures(string id); ChannelFeatures GetChannelFeatures(string id);
/// <summary>
/// Gets all channel features.
/// </summary>
/// <returns>IEnumerable{ChannelFeatures}.</returns>
IEnumerable<ChannelFeatures> GetAllChannelFeatures();
/// <summary> /// <summary>
/// Gets the channel. /// Gets the channel.
/// </summary> /// </summary>
@ -38,6 +50,14 @@ namespace MediaBrowser.Controller.Channels
/// <returns>Task{QueryResult{BaseItemDto}}.</returns> /// <returns>Task{QueryResult{BaseItemDto}}.</returns>
Task<QueryResult<BaseItemDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken); 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> /// <summary>
/// Gets the channel items. /// Gets the channel items.
/// </summary> /// </summary>
@ -52,6 +72,6 @@ namespace MediaBrowser.Controller.Channels
/// <param name="id">The identifier.</param> /// <param name="id">The identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{ChannelMediaInfo}}.</returns> /// <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; } bool IsInfiniteStream { get; set; }
long? RunTimeTicks { get; set; }
string MediaType { get; }
ChannelMediaContentType ContentType { get; set; } ChannelMediaContentType ContentType { get; set; }
List<ChannelMediaInfo> ChannelMediaSources { 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> /// <value><c>true</c> if this instance can search; otherwise, <c>false</c>.</value>
public bool CanSearch { get; set; } 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> /// <summary>
/// Gets or sets the media types. /// Gets or sets the media types.
/// </summary> /// </summary>

View file

@ -17,4 +17,9 @@ namespace MediaBrowser.Controller.Channels
public bool SortDescending { get; set; } 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"> <Compile Include="..\MediaBrowser.Model\Dto\IItemDto.cs">
<Link>Dto\IItemDto.cs</Link> <Link>Dto\IItemDto.cs</Link>
</Compile> </Compile>
<Compile Include="..\MediaBrowser.Model\Dto\ImageByNameInfo.cs">
<Link>Dto\ImageByNameInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dto\ImageInfo.cs"> <Compile Include="..\MediaBrowser.Model\Dto\ImageInfo.cs">
<Link>Dto\ImageInfo.cs</Link> <Link>Dto\ImageInfo.cs</Link>
</Compile> </Compile>

View file

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

View file

@ -4,12 +4,30 @@ namespace MediaBrowser.Model.Channels
{ {
public class ChannelFeatures 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> /// <summary>
/// Gets or sets a value indicating whether this instance can search. /// Gets or sets a value indicating whether this instance can search.
/// </summary> /// </summary>
/// <value><c>true</c> if this instance can search; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance can search; otherwise, <c>false</c>.</value>
public bool CanSearch { get; set; } 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> /// <summary>
/// Gets or sets the media types. /// Gets or sets the media types.
/// </summary> /// </summary>
@ -44,6 +62,12 @@ namespace MediaBrowser.Model.Channels
/// <value><c>true</c> if this instance can filter; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance can filter; otherwise, <c>false</c>.</value>
public bool CanFilter { get; set; } 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() public ChannelFeatures()
{ {
MediaTypes = new List<ChannelMediaType>(); MediaTypes = new List<ChannelMediaType>();

View file

@ -20,4 +20,33 @@
/// <value>The limit.</value> /// <value>The limit.</value>
public int? Limit { get; set; } 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 class ChannelOptions
{ {
public int? PreferredStreamingWidth { get; set; } 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.Entities;
using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Runtime.Serialization;
@ -35,11 +34,13 @@ namespace MediaBrowser.Model.Dto
public int? Bitrate { get; set; } public int? Bitrate { get; set; }
public TransportStreamTimestamp? Timestamp { get; set; } public TransportStreamTimestamp? Timestamp { get; set; }
public Dictionary<string, string> RequiredHttpHeaders { get; set; }
public MediaSourceInfo() public MediaSourceInfo()
{ {
Formats = new List<string>(); Formats = new List<string>();
MediaStreams = new List<MediaStream>(); MediaStreams = new List<MediaStream>();
RequiredHttpHeaders = new Dictionary<string, string>();
} }
public int? DefaultAudioStreamIndex { get; set; } public int? DefaultAudioStreamIndex { get; set; }

View file

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

View file

@ -46,48 +46,51 @@ namespace MediaBrowser.Providers.Music
{ {
var updateType = base.BeforeSave(item); var updateType = base.BeforeSave(item);
var songs = item.RecursiveChildren.OfType<Audio>().ToList(); var songs = item.RecursiveChildren.OfType<Audio>().ToList();
if (!item.LockedFields.Contains(MetadataFields.Genres)) if (!item.IsLocked)
{ {
var currentList = item.Genres.ToList(); if (!item.LockedFields.Contains(MetadataFields.Genres))
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))
{ {
updateType = updateType | ItemUpdateType.MetadataDownload; var currentList = item.Genres.ToList();
}
}
if (!item.LockedFields.Contains(MetadataFields.Studios)) item.Genres = songs.SelectMany(i => i.Genres)
{ .Distinct(StringComparer.OrdinalIgnoreCase)
var currentList = item.Studios.ToList(); .ToList();
item.Studios = songs.SelectMany(i => i.Studios) if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
.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 | 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); 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(); _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() private IEnumerable<IChannel> GetAllChannels()
{ {
return _factories return _factories
@ -156,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Channels
progress.Report(100); 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); var item = (IChannelMediaItem)_libraryManager.GetItemById(id);
@ -166,12 +179,149 @@ namespace MediaBrowser.Server.Implementations.Channels
var requiresCallback = channelPlugin as IRequiresMediaInfoCallback; var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
IEnumerable<ChannelMediaInfo> results;
if (requiresCallback != null) 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) 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)); 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) public ChannelFeatures GetChannelFeatures(string id)
{ {
var channel = GetChannel(id); var channel = GetChannel(id);
var channelProvider = GetChannelProvider(channel); 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 return new ChannelFeatures
{ {
CanFilter = !features.MaxPageSize.HasValue, CanFilter = !features.MaxPageSize.HasValue,
CanGetAllMedia = features.CanGetAllMedia,
CanSearch = features.CanSearch, CanSearch = features.CanSearch,
ContentTypes = features.ContentTypes, ContentTypes = features.ContentTypes,
DefaultSortFields = features.DefaultSortFields, DefaultSortFields = features.DefaultSortFields,
MaxPageSize = features.MaxPageSize, MaxPageSize = features.MaxPageSize,
MediaTypes = features.MediaTypes, 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)); 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) public async Task<QueryResult<BaseItemDto>> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken)
{ {
var queryChannelId = query.ChannelId; var queryChannelId = query.ChannelId;
@ -301,7 +541,7 @@ namespace MediaBrowser.Server.Implementations.Channels
ChannelItemSortField? sortField = null; ChannelItemSortField? sortField = null;
ChannelItemSortField parsedField; ChannelItemSortField parsedField;
if (query.SortBy.Length == 1 && if (query.SortBy.Length == 1 &&
Enum.TryParse(query.SortBy[0], true, out parsedField)) Enum.TryParse(query.SortBy[0], true, out parsedField))
{ {
sortField = parsedField; sortField = parsedField;
@ -309,11 +549,11 @@ namespace MediaBrowser.Server.Implementations.Channels
var sortDescending = query.SortOrder.HasValue && query.SortOrder.Value == SortOrder.Descending; var sortDescending = query.SortOrder.HasValue && query.SortOrder.Value == SortOrder.Descending;
var itemsResult = await GetChannelItems(channelProvider, var itemsResult = await GetChannelItems(channelProvider,
user, user,
query.FolderId, query.FolderId,
providerStartIndex, providerStartIndex,
providerLimit, providerLimit,
sortField, sortField,
sortDescending, sortDescending,
cancellationToken) cancellationToken)

View file

@ -846,16 +846,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.Path)) if (fields.Contains(ItemFields.Path))
{ {
var locationType = item.LocationType; dto.Path = GetMappedPath(item);
if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
{
dto.Path = GetMappedPath(item.Path);
}
else
{
dto.Path = item.Path;
}
} }
dto.PremiereDate = item.PremiereDate; dto.PremiereDate = item.PremiereDate;
@ -1315,14 +1306,12 @@ namespace MediaBrowser.Server.Implementations.Dto
var locationType = item.LocationType; 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; return path;
@ -1418,16 +1407,6 @@ namespace MediaBrowser.Server.Implementations.Dto
return string.Join("/", terms.ToArray()); 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) private void SetProductionLocations(BaseItem item, BaseItemDto dto)
{ {
var hasProductionLocations = item as IHasProductionLocations; 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); var result = await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false);
Sanitize(result);
_logger.Debug("Live stream info: " + _json.SerializeToString(result)); _logger.Debug("Live stream info: " + _json.SerializeToString(result));
if (!string.IsNullOrEmpty(result.Id)) if (!string.IsNullOrEmpty(result.Id))
@ -332,6 +334,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var result = await service.GetChannelStream(channel.ExternalId, cancellationToken).ConfigureAwait(false); var result = await service.GetChannelStream(channel.ExternalId, cancellationToken).ConfigureAwait(false);
Sanitize(result);
_logger.Debug("Live stream info: " + _json.SerializeToString(result)); _logger.Debug("Live stream info: " + _json.SerializeToString(result));
if (!string.IsNullOrEmpty(result.Id)) 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) private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
{ {
var path = Path.Combine(_config.ApplicationPaths.ItemsByNamePath, "tvchannels", _fileSystem.GetValidFilename(channelInfo.Name)); 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.", "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.", "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.", "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", "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.", "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.", "ButtonEditOtherUserPreferences": "Edit this user's personal preferences.",
"ChannelStreamOptionBestAvailable": "Best available", "LabelChannelStreamQuality": "Preferred internet stream quality:",
"LabelChannelStreamOptionBestAvailable": "Preferred streaming quality:", "LabelChannelStreamQualityHelp": "In a low bandwidth environment, limiting quality can help ensure a smooth streaming experience.",
"LabelChannelStreamOptionBestAvailableHelp": "Determines the selected quality when channel content is available in multiple resolutions." "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"> <Compile Include="..\SharedVersion.cs">
<Link>Properties\SharedVersion.cs</Link> <Link>Properties\SharedVersion.cs</Link>
</Compile> </Compile>
<Compile Include="Channels\ChannelDownloadScheduledTask.cs" />
<Compile Include="Channels\ChannelImageProvider.cs" /> <Compile Include="Channels\ChannelImageProvider.cs" />
<Compile Include="Channels\ChannelItemImageProvider.cs" /> <Compile Include="Channels\ChannelItemImageProvider.cs" />
<Compile Include="Channels\ChannelManager.cs" /> <Compile Include="Channels\ChannelManager.cs" />

View file

@ -6,6 +6,7 @@ using MediaBrowser.Model.Session;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
@ -125,13 +126,16 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
{ {
return Task.FromResult(true); var dict = new Dictionary<string, string>();
//return SendMessage(new WebSocketMessage<PlayRequest>
//{
// MessageType = "Play",
// Data = command
//}, 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) public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
@ -140,7 +144,12 @@ namespace MediaBrowser.Server.Implementations.Session
if (command.Command == PlaystateCommand.Seek) 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); return SendMessage(command.Command.ToString(), cancellationToken);

View file

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata> <metadata>
<id>MediaBrowser.Common.Internal</id> <id>MediaBrowser.Common.Internal</id>
<version>3.0.391</version> <version>3.0.393</version>
<title>MediaBrowser.Common.Internal</title> <title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors> <authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners> <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> <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> <copyright>Copyright © Media Browser 2013</copyright>
<dependencies> <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="NLog" version="2.1.0" />
<dependency id="SimpleInjector" version="2.5.0" /> <dependency id="SimpleInjector" version="2.5.0" />
<dependency id="sharpcompress" version="0.10.2" /> <dependency id="sharpcompress" version="0.10.2" />

View file

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

View file

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>MediaBrowser.Server.Core</id> <id>MediaBrowser.Server.Core</id>
<version>3.0.391</version> <version>3.0.393</version>
<title>Media Browser.Server.Core</title> <title>Media Browser.Server.Core</title>
<authors>Media Browser Team</authors> <authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners> <owners>ebr,Luke,scottisafool</owners>
@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Media Browser Server.</description> <description>Contains core components required to build plugins for Media Browser Server.</description>
<copyright>Copyright © Media Browser 2013</copyright> <copyright>Copyright © Media Browser 2013</copyright>
<dependencies> <dependencies>
<dependency id="MediaBrowser.Common" version="3.0.391" /> <dependency id="MediaBrowser.Common" version="3.0.393" />
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>