Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
631bba1d3c
|
@ -1,5 +1,4 @@
|
||||||
using System.Text;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Extensions;
|
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
@ -22,6 +21,7 @@ using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -827,7 +827,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return "msmpeg4";
|
return "wmv2";
|
||||||
}
|
}
|
||||||
if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
@ -937,8 +937,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType);
|
ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType);
|
||||||
|
|
||||||
state.LogFileStream.Dispose();
|
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1096,23 +1094,12 @@ namespace MediaBrowser.Api.Playback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="process">The process.</param>
|
/// <param name="process">The process.</param>
|
||||||
/// <param name="state">The state.</param>
|
/// <param name="state">The state.</param>
|
||||||
protected async void OnFfMpegProcessExited(Process process, StreamState state)
|
protected void OnFfMpegProcessExited(Process process, StreamState state)
|
||||||
{
|
{
|
||||||
if (state.IsoMount != null)
|
state.Dispose();
|
||||||
{
|
|
||||||
state.IsoMount.Dispose();
|
|
||||||
state.IsoMount = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.StandardInputCancellationTokenSource != null)
|
|
||||||
{
|
|
||||||
state.StandardInputCancellationTokenSource.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
var outputFilePath = GetOutputFilePath(state);
|
var outputFilePath = GetOutputFilePath(state);
|
||||||
|
|
||||||
state.LogFileStream.Dispose();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.Info("FFMpeg exited with code {0} for {1}", process.ExitCode, outputFilePath);
|
Logger.Info("FFMpeg exited with code {0} for {1}", process.ExitCode, outputFilePath);
|
||||||
|
@ -1121,18 +1108,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
Logger.Info("FFMpeg exited with an error for {0}", outputFilePath);
|
Logger.Info("FFMpeg exited with an error for {0}", outputFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(state.LiveTvStreamId))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await LiveTvManager.CloseLiveStream(state.LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.ErrorException("Error closing live tv stream", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected double? GetFramerateParam(StreamState state)
|
protected double? GetFramerateParam(StreamState state)
|
||||||
|
@ -1357,7 +1332,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
request.AudioCodec = InferAudioCodec(url);
|
request.AudioCodec = InferAudioCodec(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
var state = new StreamState
|
var state = new StreamState(LiveTvManager, Logger)
|
||||||
{
|
{
|
||||||
Request = request,
|
Request = request,
|
||||||
RequestedUrl = url
|
RequestedUrl = url
|
||||||
|
@ -1515,6 +1490,14 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.AudioStream != null)
|
||||||
|
{
|
||||||
|
//if (CanStreamCopyAudio(request, state.AudioStream))
|
||||||
|
//{
|
||||||
|
// request.AudioCodec = "copy";
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1538,7 +1521,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
|
|
||||||
// If client is requesting a specific video profile, it must match the source
|
// If client is requesting a specific video profile, it must match the source
|
||||||
if (!string.IsNullOrEmpty(request.Profile) && !string.Equals(request.Profile, videoStream.Profile))
|
if (!string.IsNullOrEmpty(request.Profile) && !string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1599,9 +1582,47 @@ namespace MediaBrowser.Api.Playback
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SupportsAutomaticVideoStreamCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanStreamCopyAudio(StreamRequest request, MediaStream audioStream)
|
||||||
|
{
|
||||||
|
// Source and target codecs must match
|
||||||
|
if (string.IsNullOrEmpty(request.AudioCodec) || !string.Equals(request.AudioCodec, audioStream.Codec, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Video bitrate must fall within requested value
|
||||||
|
if (request.AudioBitRate.HasValue)
|
||||||
|
{
|
||||||
|
if (!audioStream.BitRate.HasValue || audioStream.BitRate.Value > request.AudioBitRate.Value)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channels must fall within requested value
|
||||||
|
var channels = request.AudioChannels ?? request.MaxAudioChannels;
|
||||||
|
if (channels.HasValue)
|
||||||
|
{
|
||||||
|
if (!audioStream.Channels.HasValue || audioStream.Channels.Value > channels.Value)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sample rate must fall within requested value
|
||||||
|
if (request.AudioSampleRate.HasValue)
|
||||||
|
{
|
||||||
|
if (!audioStream.SampleRate.HasValue || audioStream.SampleRate.Value > request.AudioSampleRate.Value)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return SupportsAutomaticVideoStreamCopy;
|
return SupportsAutomaticVideoStreamCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1623,7 +1644,7 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
|
|
||||||
var profile = string.IsNullOrWhiteSpace(state.Request.DeviceProfileId) ?
|
var profile = string.IsNullOrWhiteSpace(state.Request.DeviceProfileId) ?
|
||||||
null :
|
DlnaManager.GetProfile(headers) :
|
||||||
DlnaManager.GetProfile(state.Request.DeviceProfileId);
|
DlnaManager.GetProfile(state.Request.DeviceProfileId);
|
||||||
|
|
||||||
if (profile == null)
|
if (profile == null)
|
||||||
|
@ -1708,7 +1729,10 @@ namespace MediaBrowser.Api.Playback
|
||||||
// Byte-based seeking only possible when not transcoding
|
// Byte-based seeking only possible when not transcoding
|
||||||
orgOp += isStaticallyStreamed || state.TranscodeSeekInfo == TranscodeSeekInfo.Bytes ? "1" : "0";
|
orgOp += isStaticallyStreamed || state.TranscodeSeekInfo == TranscodeSeekInfo.Bytes ? "1" : "0";
|
||||||
|
|
||||||
AddTimeSeekResponseHeaders(state, responseHeaders);
|
if (!isStaticallyStreamed)
|
||||||
|
{
|
||||||
|
AddTimeSeekResponseHeaders(state, responseHeaders);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1719,7 +1743,21 @@ namespace MediaBrowser.Api.Playback
|
||||||
// 0 = native, 1 = transcoded
|
// 0 = native, 1 = transcoded
|
||||||
var orgCi = isStaticallyStreamed ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
|
var orgCi = isStaticallyStreamed ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
|
||||||
|
|
||||||
const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000";
|
var flagValue = DlnaFlags.DLNA_ORG_FLAG_STREAMING_TRANSFER_MODE |
|
||||||
|
DlnaFlags.DLNA_ORG_FLAG_BACKGROUND_TRANSFERT_MODE |
|
||||||
|
DlnaFlags.DLNA_ORG_FLAG_DLNA_V15;
|
||||||
|
|
||||||
|
if (isStaticallyStreamed)
|
||||||
|
{
|
||||||
|
flagValue = flagValue | DlnaFlags.DLNA_ORG_FLAG_BYTE_BASED_SEEK;
|
||||||
|
}
|
||||||
|
else if (state.RunTimeTicks.HasValue)
|
||||||
|
{
|
||||||
|
flagValue = flagValue | DlnaFlags.DLNA_ORG_FLAG_TIME_BASED_SEEK;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}000000000000000000000000",
|
||||||
|
Enum.Format(typeof(DlnaFlags), flagValue, "x"));
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(state.OrgPn))
|
if (!string.IsNullOrWhiteSpace(state.OrgPn))
|
||||||
{
|
{
|
||||||
|
@ -1769,6 +1807,23 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum DlnaFlags
|
||||||
|
{
|
||||||
|
DLNA_ORG_FLAG_SENDER_PACED = (1 << 31),
|
||||||
|
DLNA_ORG_FLAG_TIME_BASED_SEEK = (1 << 30),
|
||||||
|
DLNA_ORG_FLAG_BYTE_BASED_SEEK = (1 << 29),
|
||||||
|
DLNA_ORG_FLAG_PLAY_CONTAINER = (1 << 28),
|
||||||
|
DLNA_ORG_FLAG_S0_INCREASE = (1 << 27),
|
||||||
|
DLNA_ORG_FLAG_SN_INCREASE = (1 << 26),
|
||||||
|
DLNA_ORG_FLAG_RTSP_PAUSE = (1 << 25),
|
||||||
|
DLNA_ORG_FLAG_STREAMING_TRANSFER_MODE = (1 << 24),
|
||||||
|
DLNA_ORG_FLAG_INTERACTIVE_TRANSFERT_MODE = (1 << 23),
|
||||||
|
DLNA_ORG_FLAG_BACKGROUND_TRANSFERT_MODE = (1 << 22),
|
||||||
|
DLNA_ORG_FLAG_CONNECTION_STALL = (1 << 21),
|
||||||
|
DLNA_ORG_FLAG_DLNA_V15 = (1 << 20),
|
||||||
|
};
|
||||||
|
|
||||||
private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
|
private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
|
||||||
{
|
{
|
||||||
var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture);
|
var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture);
|
||||||
|
|
|
@ -93,10 +93,12 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
|
|
||||||
if (!state.VideoRequest.VideoBitRate.HasValue && (string.IsNullOrEmpty(state.VideoRequest.VideoCodec) || !string.Equals(state.VideoRequest.VideoCodec, "copy", StringComparison.OrdinalIgnoreCase)))
|
if (!state.VideoRequest.VideoBitRate.HasValue && (string.IsNullOrEmpty(state.VideoRequest.VideoCodec) || !string.Equals(state.VideoRequest.VideoCodec, "copy", StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
|
state.Dispose();
|
||||||
throw new ArgumentException("A video bitrate is required");
|
throw new ArgumentException("A video bitrate is required");
|
||||||
}
|
}
|
||||||
if (!state.Request.AudioBitRate.HasValue && (string.IsNullOrEmpty(state.Request.AudioCodec) || !string.Equals(state.Request.AudioCodec, "copy", StringComparison.OrdinalIgnoreCase)))
|
if (!state.Request.AudioBitRate.HasValue && (string.IsNullOrEmpty(state.Request.AudioCodec) || !string.Equals(state.Request.AudioCodec, "copy", StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
|
state.Dispose();
|
||||||
throw new ArgumentException("An audio bitrate is required");
|
throw new ArgumentException("An audio bitrate is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +109,16 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
if (!File.Exists(playlist))
|
if (!File.Exists(playlist))
|
||||||
{
|
{
|
||||||
isPlaylistNewlyCreated = true;
|
isPlaylistNewlyCreated = true;
|
||||||
await StartFfMpeg(state, playlist).ConfigureAwait(false);
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await StartFfMpeg(state, playlist).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
state.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -103,7 +103,10 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||||
.Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
|
.Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
.ToList())
|
.ToList())
|
||||||
{
|
{
|
||||||
ExtendPlaylistTimer(playlist);
|
if (!string.IsNullOrEmpty(playlist))
|
||||||
|
{
|
||||||
|
ExtendPlaylistTimer(playlist);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,11 +121,19 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
|
|
||||||
var responseHeaders = new Dictionary<string, string>();
|
var responseHeaders = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
// Static remote stream
|
||||||
if (request.Static && state.IsRemote)
|
if (request.Static && state.IsRemote)
|
||||||
{
|
{
|
||||||
AddDlnaHeaders(state, responseHeaders, true);
|
AddDlnaHeaders(state, responseHeaders, true);
|
||||||
|
|
||||||
return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result;
|
try
|
||||||
|
{
|
||||||
|
return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
state.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputPath = GetOutputFilePath(state);
|
var outputPath = GetOutputFilePath(state);
|
||||||
|
@ -136,21 +144,47 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
|
|
||||||
AddDlnaHeaders(state, responseHeaders, isStatic);
|
AddDlnaHeaders(state, responseHeaders, isStatic);
|
||||||
|
|
||||||
|
// Static stream
|
||||||
if (request.Static)
|
if (request.Static)
|
||||||
{
|
{
|
||||||
var contentType = state.GetMimeType(state.MediaPath);
|
var contentType = state.GetMimeType(state.MediaPath);
|
||||||
|
|
||||||
return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
|
try
|
||||||
|
{
|
||||||
|
return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
state.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not static but transcode cache file exists
|
||||||
if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive))
|
if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive))
|
||||||
{
|
{
|
||||||
var contentType = state.GetMimeType(outputPath);
|
var contentType = state.GetMimeType(outputPath);
|
||||||
|
|
||||||
return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
|
try
|
||||||
|
{
|
||||||
|
return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
state.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetStreamResult(state, responseHeaders, isHeadRequest).Result;
|
// Need to start ffmpeg
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return GetStreamResult(state, responseHeaders, isHeadRequest).Result;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
state.Dispose();
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -251,6 +285,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
|
ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
|
||||||
|
state.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem);
|
var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem);
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback
|
namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
public class StreamState
|
public class StreamState : IDisposable
|
||||||
{
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly ILiveTvManager _liveTvManager;
|
||||||
|
|
||||||
public string RequestedUrl { get; set; }
|
public string RequestedUrl { get; set; }
|
||||||
|
|
||||||
public StreamRequest Request { get; set; }
|
public StreamRequest Request { get; set; }
|
||||||
|
@ -51,8 +57,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
public bool HasMediaStreams { get; set; }
|
public bool HasMediaStreams { get; set; }
|
||||||
|
|
||||||
public CancellationTokenSource StandardInputCancellationTokenSource { get; set; }
|
|
||||||
|
|
||||||
public string LiveTvStreamId { get; set; }
|
public string LiveTvStreamId { get; set; }
|
||||||
|
|
||||||
public int SegmentLength = 10;
|
public int SegmentLength = 10;
|
||||||
|
@ -63,6 +67,12 @@ namespace MediaBrowser.Api.Playback
|
||||||
public string AudioSync = "1";
|
public string AudioSync = "1";
|
||||||
public string VideoSync = "vfr";
|
public string VideoSync = "vfr";
|
||||||
|
|
||||||
|
public StreamState(ILiveTvManager liveTvManager, ILogger logger)
|
||||||
|
{
|
||||||
|
_liveTvManager = liveTvManager;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
public string InputAudioSync { get; set; }
|
public string InputAudioSync { get; set; }
|
||||||
public string InputVideoSync { get; set; }
|
public string InputVideoSync { get; set; }
|
||||||
|
|
||||||
|
@ -93,5 +103,61 @@ namespace MediaBrowser.Api.Playback
|
||||||
|
|
||||||
return MimeTypes.GetMimeType(outputPath);
|
return MimeTypes.GetMimeType(outputPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
DisposeLiveStream();
|
||||||
|
DisposeLogStream();
|
||||||
|
DisposeIsoMount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeLogStream()
|
||||||
|
{
|
||||||
|
if (LogFileStream != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LogFileStream.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error disposing log stream", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogFileStream = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeIsoMount()
|
||||||
|
{
|
||||||
|
if (IsoMount != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsoMount.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error disposing iso mount", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
IsoMount = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void DisposeLiveStream()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(LiveTvStreamId))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _liveTvManager.CloseLiveStream(LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error closing live tv stream", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -245,18 +245,16 @@ namespace MediaBrowser.Api
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
|
|
||||||
private readonly IDtoService _dtoService;
|
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SessionsService" /> class.
|
/// Initializes a new instance of the <see cref="SessionsService" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sessionManager">The session manager.</param>
|
/// <param name="sessionManager">The session manager.</param>
|
||||||
/// <param name="dtoService">The dto service.</param>
|
/// <param name="userManager">The user manager.</param>
|
||||||
public SessionsService(ISessionManager sessionManager, IDtoService dtoService, IUserManager userManager)
|
public SessionsService(ISessionManager sessionManager, IUserManager userManager)
|
||||||
{
|
{
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
_dtoService = dtoService;
|
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +287,7 @@ namespace MediaBrowser.Api
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ToOptimizedResult(result.Select(_dtoService.GetSessionInfoDto).ToList());
|
return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Post(SendPlaystateCommand request)
|
public void Post(SendPlaystateCommand request)
|
||||||
|
|
|
@ -257,6 +257,12 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
/// <value>The id.</value>
|
/// <value>The id.</value>
|
||||||
[ApiMember(Name = "QueueableMediaTypes", Description = "A list of media types that can be queued from this item, comma delimited. Audio,Video,Book,Game", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
[ApiMember(Name = "QueueableMediaTypes", Description = "A list of media types that can be queued from this item, comma delimited. Audio,Video,Book,Game", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
|
||||||
public string QueueableMediaTypes { get; set; }
|
public string QueueableMediaTypes { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||||
|
public int? AudioStreamIndex { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||||
|
public int? SubtitleStreamIndex { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -295,6 +301,15 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
|
|
||||||
[ApiMember(Name = "IsMuted", Description = "Indicates if the player is muted.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
|
[ApiMember(Name = "IsMuted", Description = "Indicates if the player is muted.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
|
||||||
public bool IsMuted { get; set; }
|
public bool IsMuted { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "AudioStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||||
|
public int? AudioStreamIndex { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "SubtitleStreamIndex", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||||
|
public int? SubtitleStreamIndex { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "VolumeLevel", Description = "Scale of 0-100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "POST")]
|
||||||
|
public int? VolumeLevel { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -737,7 +752,9 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
Item = item,
|
Item = item,
|
||||||
SessionId = GetSession(_sessionManager).Id,
|
SessionId = GetSession(_sessionManager).Id,
|
||||||
QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(),
|
QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(),
|
||||||
MediaSourceId = request.MediaSourceId
|
MediaSourceId = request.MediaSourceId,
|
||||||
|
AudioStreamIndex = request.AudioStreamIndex,
|
||||||
|
SubtitleStreamIndex = request.SubtitleStreamIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
_sessionManager.OnPlaybackStart(info);
|
_sessionManager.OnPlaybackStart(info);
|
||||||
|
@ -760,7 +777,10 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
IsMuted = request.IsMuted,
|
IsMuted = request.IsMuted,
|
||||||
IsPaused = request.IsPaused,
|
IsPaused = request.IsPaused,
|
||||||
SessionId = GetSession(_sessionManager).Id,
|
SessionId = GetSession(_sessionManager).Id,
|
||||||
MediaSourceId = request.MediaSourceId
|
MediaSourceId = request.MediaSourceId,
|
||||||
|
AudioStreamIndex = request.AudioStreamIndex,
|
||||||
|
SubtitleStreamIndex = request.SubtitleStreamIndex,
|
||||||
|
VolumeLevel = request.VolumeLevel
|
||||||
};
|
};
|
||||||
|
|
||||||
var task = _sessionManager.OnPlaybackProgress(info);
|
var task = _sessionManager.OnPlaybackProgress(info);
|
||||||
|
|
|
@ -323,7 +323,7 @@ namespace MediaBrowser.Api
|
||||||
var result = new AuthenticationResult
|
var result = new AuthenticationResult
|
||||||
{
|
{
|
||||||
User = _dtoService.GetUserDto(user),
|
User = _dtoService.GetUserDto(user),
|
||||||
SessionInfo = _dtoService.GetSessionInfoDto(session)
|
SessionInfo = _sessionMananger.GetSessionInfoDto(session)
|
||||||
};
|
};
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Dto;
|
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
|
@ -14,8 +13,6 @@ namespace MediaBrowser.Api.WebSocket
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfoDto>, object>
|
class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfoDto>, object>
|
||||||
{
|
{
|
||||||
private readonly IDtoService _dtoService;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name.
|
/// Gets the name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -35,11 +32,10 @@ namespace MediaBrowser.Api.WebSocket
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="sessionManager">The session manager.</param>
|
/// <param name="sessionManager">The session manager.</param>
|
||||||
public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager, IDtoService dtoService)
|
public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager)
|
||||||
: base(logger)
|
: base(logger)
|
||||||
{
|
{
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
_dtoService = dtoService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -49,7 +45,7 @@ namespace MediaBrowser.Api.WebSocket
|
||||||
/// <returns>Task{SystemInfo}.</returns>
|
/// <returns>Task{SystemInfo}.</returns>
|
||||||
protected override Task<IEnumerable<SessionInfoDto>> GetDataToSend(object state)
|
protected override Task<IEnumerable<SessionInfoDto>> GetDataToSend(object state)
|
||||||
{
|
{
|
||||||
return Task.FromResult(_sessionManager.Sessions.Where(i => i.IsActive).Select(_dtoService.GetSessionInfoDto));
|
return Task.FromResult(_sessionManager.Sessions.Where(i => i.IsActive).Select(_sessionManager.GetSessionInfoDto));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Session;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Session;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
@ -21,13 +19,6 @@ namespace MediaBrowser.Controller.Dto
|
||||||
/// <returns>UserDto.</returns>
|
/// <returns>UserDto.</returns>
|
||||||
UserDto GetUserDto(User user);
|
UserDto GetUserDto(User user);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the session info dto.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="session">The session.</param>
|
|
||||||
/// <returns>SessionInfoDto.</returns>
|
|
||||||
SessionInfoDto GetSessionInfoDto(SessionInfo session);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the dto id.
|
/// Gets the dto id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -253,6 +253,7 @@
|
||||||
<Compile Include="Session\PlaybackInfo.cs" />
|
<Compile Include="Session\PlaybackInfo.cs" />
|
||||||
<Compile Include="Session\PlaybackProgressInfo.cs" />
|
<Compile Include="Session\PlaybackProgressInfo.cs" />
|
||||||
<Compile Include="Session\PlaybackStopInfo.cs" />
|
<Compile Include="Session\PlaybackStopInfo.cs" />
|
||||||
|
<Compile Include="Session\SessionEventArgs.cs" />
|
||||||
<Compile Include="Session\SessionInfo.cs" />
|
<Compile Include="Session\SessionInfo.cs" />
|
||||||
<Compile Include="Sorting\IBaseItemComparer.cs" />
|
<Compile Include="Sorting\IBaseItemComparer.cs" />
|
||||||
<Compile Include="Sorting\IUserBaseItemComparer.cs" />
|
<Compile Include="Sorting\IUserBaseItemComparer.cs" />
|
||||||
|
|
|
@ -89,6 +89,14 @@ namespace MediaBrowser.Controller.Session
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task SendServerShutdownNotification(CancellationToken cancellationToken);
|
Task SendServerShutdownNotification(CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends the session ended notification.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sessionInfo">The session information.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends the server restart notification.
|
/// Sends the server restart notification.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -28,6 +28,16 @@ namespace MediaBrowser.Controller.Session
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EventHandler<PlaybackStopEventArgs> PlaybackStopped;
|
event EventHandler<PlaybackStopEventArgs> PlaybackStopped;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [session started].
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler<SessionEventArgs> SessionStarted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when [session ended].
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler<SessionEventArgs> SessionEnded;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the sessions.
|
/// Gets the sessions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -83,6 +93,13 @@ namespace MediaBrowser.Controller.Session
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task ReportSessionEnded(Guid sessionId);
|
Task ReportSessionEnded(Guid sessionId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the session info dto.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">The session.</param>
|
||||||
|
/// <returns>SessionInfoDto.</returns>
|
||||||
|
SessionInfoDto GetSessionInfoDto(SessionInfo session);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends the general command.
|
/// Sends the general command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -40,5 +40,17 @@ namespace MediaBrowser.Controller.Session
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The media version identifier.</value>
|
/// <value>The media version identifier.</value>
|
||||||
public string MediaSourceId { get; set; }
|
public string MediaSourceId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the index of the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The index of the audio stream.</value>
|
||||||
|
public int? AudioStreamIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the index of the subtitle stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The index of the subtitle stream.</value>
|
||||||
|
public int? SubtitleStreamIndex { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,5 +40,23 @@ namespace MediaBrowser.Controller.Session
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The media version identifier.</value>
|
/// <value>The media version identifier.</value>
|
||||||
public string MediaSourceId { get; set; }
|
public string MediaSourceId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the volume level.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The volume level.</value>
|
||||||
|
public int? VolumeLevel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the index of the audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The index of the audio stream.</value>
|
||||||
|
public int? AudioStreamIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the index of the subtitle stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The index of the subtitle stream.</value>
|
||||||
|
public int? SubtitleStreamIndex { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
MediaBrowser.Controller/Session/SessionEventArgs.cs
Normal file
9
MediaBrowser.Controller/Session/SessionEventArgs.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.Session
|
||||||
|
{
|
||||||
|
public class SessionEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public SessionInfo SessionInfo { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -126,7 +126,6 @@ namespace MediaBrowser.Controller.Session
|
||||||
/// <value>The now playing media version identifier.</value>
|
/// <value>The now playing media version identifier.</value>
|
||||||
public string NowPlayingMediaSourceId { get; set; }
|
public string NowPlayingMediaSourceId { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the now playing run time ticks.
|
/// Gets or sets the now playing run time ticks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -150,6 +149,16 @@ namespace MediaBrowser.Controller.Session
|
||||||
/// <value><c>true</c> if this instance is muted; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance is muted; otherwise, <c>false</c>.</value>
|
||||||
public bool IsMuted { get; set; }
|
public bool IsMuted { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the volume level, on a scale of 0-100
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The volume level.</value>
|
||||||
|
public int? VolumeLevel { get; set; }
|
||||||
|
|
||||||
|
public int? NowPlayingAudioStreamIndex { get; set; }
|
||||||
|
|
||||||
|
public int? NowPlayingSubtitleStreamIndex { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the device id.
|
/// Gets or sets the device id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using MediaBrowser.Common.Configuration;
|
using System.Text;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
@ -128,11 +129,29 @@ namespace MediaBrowser.Dlna
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Debug("No matching device profile found. The default will need to be used.");
|
_logger.Debug("No matching device profile found. The default will need to be used.");
|
||||||
|
LogUnmatchedProfile(deviceInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LogUnmatchedProfile(DeviceIdentification profile)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.AppendLine(string.Format("DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty));
|
||||||
|
builder.AppendLine(string.Format("FriendlyName:{0}", profile.FriendlyName ?? string.Empty));
|
||||||
|
builder.AppendLine(string.Format("Manufacturer:{0}", profile.Manufacturer ?? string.Empty));
|
||||||
|
builder.AppendLine(string.Format("ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty));
|
||||||
|
builder.AppendLine(string.Format("ModelDescription:{0}", profile.ModelDescription ?? string.Empty));
|
||||||
|
builder.AppendLine(string.Format("ModelName:{0}", profile.ModelName ?? string.Empty));
|
||||||
|
builder.AppendLine(string.Format("ModelNumber:{0}", profile.ModelNumber ?? string.Empty));
|
||||||
|
builder.AppendLine(string.Format("ModelUrl:{0}", profile.ModelUrl ?? string.Empty));
|
||||||
|
builder.AppendLine(string.Format("SerialNumber:{0}", profile.SerialNumber ?? string.Empty));
|
||||||
|
|
||||||
|
_logger.LogMultiline("No matching device profile found. The default will need to be used.", LogSeverity.Info, builder);
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
|
private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(profileInfo.DeviceDescription))
|
if (!string.IsNullOrWhiteSpace(profileInfo.DeviceDescription))
|
||||||
|
|
|
@ -71,6 +71,8 @@
|
||||||
<Compile Include="PlayTo\ServiceAction.cs" />
|
<Compile Include="PlayTo\ServiceAction.cs" />
|
||||||
<Compile Include="Profiles\Foobar2000Profile.cs" />
|
<Compile Include="Profiles\Foobar2000Profile.cs" />
|
||||||
<Compile Include="Profiles\Windows81Profile.cs" />
|
<Compile Include="Profiles\Windows81Profile.cs" />
|
||||||
|
<Compile Include="Profiles\WindowsMediaCenterProfile.cs" />
|
||||||
|
<Compile Include="Profiles\WindowsPhoneProfile.cs" />
|
||||||
<Compile Include="Ssdp\SsdpHelper.cs" />
|
<Compile Include="Ssdp\SsdpHelper.cs" />
|
||||||
<Compile Include="PlayTo\SsdpHttpClient.cs" />
|
<Compile Include="PlayTo\SsdpHttpClient.cs" />
|
||||||
<Compile Include="PlayTo\StateVariable.cs" />
|
<Compile Include="PlayTo\StateVariable.cs" />
|
||||||
|
|
|
@ -512,17 +512,15 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
if (result == null || result.Document == null)
|
if (result == null || result.Document == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var track = result.Document.Descendants("CurrentURIMetaData").Select(i => i.Value).FirstOrDefault();
|
var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault();
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(track))
|
if (track == null)
|
||||||
{
|
{
|
||||||
CurrentId = null;
|
CurrentId = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var uPnpResponse = XElement.Parse(track);
|
var e = track.Element(uPnpNamespaces.items) ?? track;
|
||||||
|
|
||||||
var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
|
|
||||||
|
|
||||||
var uTrack = uParser.CreateObjectFromXML(new uParserObject
|
var uTrack = uParser.CreateObjectFromXML(new uParserObject
|
||||||
{
|
{
|
||||||
|
@ -556,7 +554,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
|
var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
|
||||||
var duration = durationElem == null ? null : durationElem.Value;
|
var duration = durationElem == null ? null : durationElem.Value;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(duration))
|
if (!string.IsNullOrWhiteSpace(duration) && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Duration = TimeSpan.Parse(duration, UsCulture);
|
Duration = TimeSpan.Parse(duration, UsCulture);
|
||||||
}
|
}
|
||||||
|
@ -564,25 +562,22 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
|
var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
|
||||||
var position = positionElem == null ? null : positionElem.Value;
|
var position = positionElem == null ? null : positionElem.Value;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(position))
|
if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Position = TimeSpan.Parse(position, UsCulture);
|
Position = TimeSpan.Parse(position, UsCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
var track = result.Document.Descendants("TrackMetaData").Select(i => i.Value)
|
var track = result.Document.Descendants("TrackMetaData").FirstOrDefault();
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(track))
|
if (track == null)
|
||||||
{
|
{
|
||||||
//If track is null, some vendors do this, use GetMediaInfo instead
|
//If track is null, some vendors do this, use GetMediaInfo instead
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var uPnpResponse = XElement.Parse(track);
|
var e = track.Element(uPnpNamespaces.items) ?? track;
|
||||||
|
|
||||||
var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
|
var uTrack = CreateUBaseObject(e);
|
||||||
|
|
||||||
var uTrack = uBaseObject.Create(e);
|
|
||||||
|
|
||||||
if (uTrack == null)
|
if (uTrack == null)
|
||||||
return true;
|
return true;
|
||||||
|
@ -592,6 +587,48 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static uBaseObject CreateUBaseObject(XElement container)
|
||||||
|
{
|
||||||
|
if (container == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("container");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new uBaseObject
|
||||||
|
{
|
||||||
|
Id = container.GetAttributeValue(uPnpNamespaces.Id),
|
||||||
|
ParentId = container.GetAttributeValue(uPnpNamespaces.ParentId),
|
||||||
|
Title = container.GetValue(uPnpNamespaces.title),
|
||||||
|
IconUrl = container.GetValue(uPnpNamespaces.Artwork),
|
||||||
|
SecondText = "",
|
||||||
|
Url = container.GetValue(uPnpNamespaces.Res),
|
||||||
|
ProtocolInfo = GetProtocolInfo(container),
|
||||||
|
MetaData = container.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] GetProtocolInfo(XElement container)
|
||||||
|
{
|
||||||
|
if (container == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("container");
|
||||||
|
}
|
||||||
|
|
||||||
|
var resElement = container.Element(uPnpNamespaces.Res);
|
||||||
|
|
||||||
|
if (resElement != null)
|
||||||
|
{
|
||||||
|
var info = resElement.Attribute(uPnpNamespaces.ProtocolInfo);
|
||||||
|
|
||||||
|
if (info != null && !string.IsNullOrWhiteSpace(info.Value))
|
||||||
|
{
|
||||||
|
return info.Value.Split(':');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new string[4];
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region From XML
|
#region From XML
|
||||||
|
|
|
@ -184,27 +184,26 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
if ((_device.IsPlaying || _device.IsPaused))
|
if ((_device.IsPlaying || _device.IsPaused))
|
||||||
{
|
{
|
||||||
var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
|
var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
|
||||||
if (playlistItem != null && playlistItem.Transcode)
|
|
||||||
{
|
|
||||||
await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo
|
|
||||||
{
|
|
||||||
Item = _currentItem,
|
|
||||||
SessionId = _session.Id,
|
|
||||||
PositionTicks = _device.Position.Ticks + playlistItem.StartPositionTicks,
|
|
||||||
IsMuted = _device.IsMuted,
|
|
||||||
IsPaused = _device.IsPaused
|
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
if (playlistItem != null)
|
||||||
}
|
|
||||||
else if (_currentItem != null)
|
|
||||||
{
|
{
|
||||||
|
var ticks = _device.Position.Ticks;
|
||||||
|
|
||||||
|
if (playlistItem.Transcode)
|
||||||
|
{
|
||||||
|
ticks += playlistItem.StartPositionTicks;
|
||||||
|
}
|
||||||
|
|
||||||
await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo
|
await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo
|
||||||
{
|
{
|
||||||
Item = _currentItem,
|
Item = _currentItem,
|
||||||
SessionId = _session.Id,
|
SessionId = _session.Id,
|
||||||
PositionTicks = _device.Position.Ticks,
|
PositionTicks = ticks,
|
||||||
IsMuted = _device.IsMuted,
|
IsMuted = _device.IsMuted,
|
||||||
IsPaused = _device.IsPaused
|
IsPaused = _device.IsPaused,
|
||||||
|
MediaSourceId = playlistItem.MediaSourceId,
|
||||||
|
AudioStreamIndex = playlistItem.AudioStreamIndex,
|
||||||
|
SubtitleStreamIndex = playlistItem.SubtitleStreamIndex
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -284,16 +283,16 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
return _device.SetPlay();
|
return _device.SetPlay();
|
||||||
|
|
||||||
case PlaystateCommand.Seek:
|
case PlaystateCommand.Seek:
|
||||||
var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
|
//var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
|
||||||
if (playlistItem != null && playlistItem.Transcode && playlistItem.MediaType == DlnaProfileType.Video && _currentItem != null)
|
//if (playlistItem != null && playlistItem.Transcode && _currentItem != null)
|
||||||
{
|
//{
|
||||||
var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
|
// var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
|
||||||
playlistItem.StartPositionTicks = newItem.StartPositionTicks;
|
// playlistItem.StartPositionTicks = newItem.StartPositionTicks;
|
||||||
playlistItem.StreamUrl = newItem.StreamUrl;
|
// playlistItem.StreamUrl = newItem.StreamUrl;
|
||||||
playlistItem.Didl = newItem.Didl;
|
// playlistItem.Didl = newItem.Didl;
|
||||||
return _device.SetAvTransport(playlistItem.StreamUrl, GetDlnaHeaders(playlistItem), playlistItem.Didl);
|
// return _device.SetAvTransport(playlistItem.StreamUrl, GetDlnaHeaders(playlistItem), playlistItem.Didl);
|
||||||
|
|
||||||
}
|
//}
|
||||||
return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0));
|
return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0));
|
||||||
|
|
||||||
|
|
||||||
|
@ -324,6 +323,11 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
public Task SendServerShutdownNotification(CancellationToken cancellationToken)
|
public Task SendServerShutdownNotification(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.FromResult(true);
|
return Task.FromResult(true);
|
||||||
|
|
|
@ -40,6 +40,13 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
return node == null ? null : node.Value;
|
return node == null ? null : node.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetAttributeValue(this XElement container, XName name)
|
||||||
|
{
|
||||||
|
var node = container.Attribute(name);
|
||||||
|
|
||||||
|
return node == null ? null : node.Value;
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetDescendantValue(this XElement container, XName name)
|
public static string GetDescendantValue(this XElement container, XName name)
|
||||||
{
|
{
|
||||||
var node = container.Descendants(name)
|
var node = container.Descendants(name)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -44,6 +45,8 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
public async Task SubscribeAsync(string url,
|
public async Task SubscribeAsync(string url,
|
||||||
string ip,
|
string ip,
|
||||||
int port,
|
int port,
|
||||||
|
@ -58,11 +61,13 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
LogRequest = _config.Configuration.DlnaOptions.EnableDebugLogging
|
LogRequest = _config.Configuration.DlnaOptions.EnableDebugLogging
|
||||||
};
|
};
|
||||||
|
|
||||||
options.RequestHeaders["HOST"] = ip + ":" + port;
|
options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture);
|
||||||
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport + ">";
|
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport.ToString(_usCulture) + ">";
|
||||||
options.RequestHeaders["NT"] = "upnp:event";
|
options.RequestHeaders["NT"] = "upnp:event";
|
||||||
options.RequestHeaders["TIMEOUT"] = "Second - " + timeOut;
|
options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture);
|
||||||
|
|
||||||
|
// TODO: Method should be SUBSCRIBE
|
||||||
|
// https://github.com/stormboy/node-upnp-controlpoint/blob/master/lib/upnp-service.js#L106
|
||||||
using (await _httpClient.Get(options).ConfigureAwait(false))
|
using (await _httpClient.Get(options).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -72,7 +77,8 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
string ip,
|
string ip,
|
||||||
int port,
|
int port,
|
||||||
string localIp,
|
string localIp,
|
||||||
int eventport)
|
int eventport,
|
||||||
|
int timeOut = 3600)
|
||||||
{
|
{
|
||||||
var options = new HttpRequestOptions
|
var options = new HttpRequestOptions
|
||||||
{
|
{
|
||||||
|
@ -80,10 +86,10 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
UserAgent = USERAGENT
|
UserAgent = USERAGENT
|
||||||
};
|
};
|
||||||
|
|
||||||
options.RequestHeaders["HOST"] = ip + ":" + port;
|
options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture);
|
||||||
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport + ">";
|
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport.ToString(_usCulture) + ">";
|
||||||
options.RequestHeaders["NT"] = "upnp:event";
|
options.RequestHeaders["NT"] = "upnp:event";
|
||||||
options.RequestHeaders["TIMEOUT"] = "Second - 3600";
|
options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture);
|
||||||
|
|
||||||
using (await _httpClient.Get(options).ConfigureAwait(false))
|
using (await _httpClient.Get(options).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.PlayTo
|
namespace MediaBrowser.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class uBaseObject
|
public class uBaseObject
|
||||||
|
@ -20,47 +18,5 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
|
|
||||||
public string[] ProtocolInfo { get; set; }
|
public string[] ProtocolInfo { get; set; }
|
||||||
|
|
||||||
public static uBaseObject Create(XElement container)
|
|
||||||
{
|
|
||||||
if (container == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("container");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new uBaseObject
|
|
||||||
{
|
|
||||||
Id = container.Attribute(uPnpNamespaces.Id).Value,
|
|
||||||
ParentId = container.Attribute(uPnpNamespaces.ParentId).Value,
|
|
||||||
Title = container.GetValue(uPnpNamespaces.title),
|
|
||||||
IconUrl = container.GetValue(uPnpNamespaces.Artwork),
|
|
||||||
SecondText = "",
|
|
||||||
Url = container.GetValue(uPnpNamespaces.Res),
|
|
||||||
ProtocolInfo = GetProtocolInfo(container),
|
|
||||||
MetaData = container.ToString()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string[] GetProtocolInfo(XElement container)
|
|
||||||
{
|
|
||||||
if (container == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("container");
|
|
||||||
}
|
|
||||||
|
|
||||||
var resElement = container.Element(uPnpNamespaces.Res);
|
|
||||||
|
|
||||||
if (resElement != null)
|
|
||||||
{
|
|
||||||
var info = resElement.Attribute(uPnpNamespaces.ProtocolInfo);
|
|
||||||
|
|
||||||
if (info != null && !string.IsNullOrWhiteSpace(info.Value))
|
|
||||||
{
|
|
||||||
return info.Value.Split(':');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new string[4];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using MediaBrowser.Model.Dlna;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.Profiles
|
namespace MediaBrowser.Dlna.Profiles
|
||||||
{
|
{
|
||||||
|
@ -32,7 +31,7 @@ namespace MediaBrowser.Dlna.Profiles
|
||||||
|
|
||||||
new TranscodingProfile
|
new TranscodingProfile
|
||||||
{
|
{
|
||||||
Container = "ts",
|
Container = "mp4",
|
||||||
Type = DlnaProfileType.Video,
|
Type = DlnaProfileType.Video,
|
||||||
AudioCodec = "aac",
|
AudioCodec = "aac",
|
||||||
VideoCodec = "h264",
|
VideoCodec = "h264",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
using System.Xml.Serialization;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Dlna;
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
namespace MediaBrowser.Dlna.Profiles
|
namespace MediaBrowser.Dlna.Profiles
|
||||||
{
|
{
|
||||||
|
|
257
MediaBrowser.Dlna/Profiles/WindowsMediaCenterProfile.cs
Normal file
257
MediaBrowser.Dlna/Profiles/WindowsMediaCenterProfile.cs
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Dlna.Profiles
|
||||||
|
{
|
||||||
|
[XmlRoot("Profile")]
|
||||||
|
public class WindowsMediaCenterProfile : DefaultProfile
|
||||||
|
{
|
||||||
|
public WindowsMediaCenterProfile()
|
||||||
|
{
|
||||||
|
Name = "Windows Media Center";
|
||||||
|
|
||||||
|
TranscodingProfiles = new[]
|
||||||
|
{
|
||||||
|
new TranscodingProfile
|
||||||
|
{
|
||||||
|
Container = "mp3",
|
||||||
|
AudioCodec = "mp3",
|
||||||
|
Type = DlnaProfileType.Audio
|
||||||
|
},
|
||||||
|
new TranscodingProfile
|
||||||
|
{
|
||||||
|
Container = "asf",
|
||||||
|
VideoCodec = "msmpeg4",
|
||||||
|
AudioCodec = "wmav2",
|
||||||
|
Type = DlnaProfileType.Video
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DirectPlayProfiles = new[]
|
||||||
|
{
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "avi",
|
||||||
|
VideoCodec = "mpeg4",
|
||||||
|
AudioCodec = "ac3,mp3",
|
||||||
|
Type = DlnaProfileType.Video
|
||||||
|
},
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "avi",
|
||||||
|
VideoCodec = "h264",
|
||||||
|
AudioCodec = "aac",
|
||||||
|
Type = DlnaProfileType.Video
|
||||||
|
},
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "mp4,mov",
|
||||||
|
VideoCodec = "h264,mpeg4",
|
||||||
|
AudioCodec = "aac,ac3",
|
||||||
|
Type = DlnaProfileType.Video
|
||||||
|
},
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "asf",
|
||||||
|
VideoCodec = "wmv2,wmv3,vc1",
|
||||||
|
AudioCodec = "wmav2,wmapro",
|
||||||
|
Type = DlnaProfileType.Video
|
||||||
|
},
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "asf",
|
||||||
|
AudioCodec = "wmav2,wmapro,wmavoice",
|
||||||
|
Type = DlnaProfileType.Audio
|
||||||
|
},
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "mp3",
|
||||||
|
AudioCodec = "mp3",
|
||||||
|
Type = DlnaProfileType.Audio
|
||||||
|
},
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "jpeg",
|
||||||
|
Type = DlnaProfileType.Photo
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ResponseProfiles = new[]
|
||||||
|
{
|
||||||
|
new ResponseProfile
|
||||||
|
{
|
||||||
|
Container = "avi",
|
||||||
|
MimeType = "video/avi",
|
||||||
|
Type = DlnaProfileType.Video
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ContainerProfiles = new[]
|
||||||
|
{
|
||||||
|
new ContainerProfile
|
||||||
|
{
|
||||||
|
Type = DlnaProfileType.Video,
|
||||||
|
Container = "mp4,mov",
|
||||||
|
|
||||||
|
Conditions = new []
|
||||||
|
{
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.Has64BitOffsets,
|
||||||
|
Value = "false",
|
||||||
|
IsRequired = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CodecProfiles = new[]
|
||||||
|
{
|
||||||
|
new CodecProfile
|
||||||
|
{
|
||||||
|
Type = CodecType.Video,
|
||||||
|
Codec = "mpeg4",
|
||||||
|
Conditions = new []
|
||||||
|
{
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.Width,
|
||||||
|
Value = "1280"
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.Height,
|
||||||
|
Value = "720"
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.VideoFramerate,
|
||||||
|
Value = "30",
|
||||||
|
IsRequired = false
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.VideoBitrate,
|
||||||
|
Value = "5120000",
|
||||||
|
IsRequired = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
new CodecProfile
|
||||||
|
{
|
||||||
|
Type = CodecType.Video,
|
||||||
|
Codec = "h264",
|
||||||
|
Conditions = new []
|
||||||
|
{
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.Width,
|
||||||
|
Value = "1920"
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.Height,
|
||||||
|
Value = "1080"
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.VideoLevel,
|
||||||
|
Value = "41",
|
||||||
|
IsRequired = false
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.VideoBitrate,
|
||||||
|
Value = "10240000",
|
||||||
|
IsRequired = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
new CodecProfile
|
||||||
|
{
|
||||||
|
Type = CodecType.Video,
|
||||||
|
Codec = "wmv2,wmv3,vc1",
|
||||||
|
Conditions = new []
|
||||||
|
{
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.Width,
|
||||||
|
Value = "1920"
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.Height,
|
||||||
|
Value = "1080"
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.VideoFramerate,
|
||||||
|
Value = "30",
|
||||||
|
IsRequired = false
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.VideoBitrate,
|
||||||
|
Value = "15360000",
|
||||||
|
IsRequired = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
new CodecProfile
|
||||||
|
{
|
||||||
|
Type = CodecType.VideoAudio,
|
||||||
|
Codec = "ac3,wmav2,wmapro",
|
||||||
|
Conditions = new []
|
||||||
|
{
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.AudioChannels,
|
||||||
|
Value = "6",
|
||||||
|
IsRequired = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
new CodecProfile
|
||||||
|
{
|
||||||
|
Type = CodecType.VideoAudio,
|
||||||
|
Codec = "aac",
|
||||||
|
Conditions = new []
|
||||||
|
{
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.AudioChannels,
|
||||||
|
Value = "2",
|
||||||
|
IsRequired = false
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.Equals,
|
||||||
|
Property = ProfileConditionValue.AudioProfile,
|
||||||
|
Value = "lc",
|
||||||
|
IsRequired = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
200
MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs
Normal file
200
MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Dlna.Profiles
|
||||||
|
{
|
||||||
|
[XmlRoot("Profile")]
|
||||||
|
public class WindowsPhoneProfile : DefaultProfile
|
||||||
|
{
|
||||||
|
public WindowsPhoneProfile()
|
||||||
|
{
|
||||||
|
Name = "Windows Phone";
|
||||||
|
|
||||||
|
TranscodingProfiles = new[]
|
||||||
|
{
|
||||||
|
new TranscodingProfile
|
||||||
|
{
|
||||||
|
Container = "mp3",
|
||||||
|
AudioCodec = "mp3",
|
||||||
|
Type = DlnaProfileType.Audio
|
||||||
|
},
|
||||||
|
new TranscodingProfile
|
||||||
|
{
|
||||||
|
Container = "mp4",
|
||||||
|
VideoCodec = "h264",
|
||||||
|
AudioCodec = "aac",
|
||||||
|
Type = DlnaProfileType.Video,
|
||||||
|
VideoProfile = "Baseline"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DirectPlayProfiles = new[]
|
||||||
|
{
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "mp4,mov",
|
||||||
|
VideoCodec = "h264",
|
||||||
|
AudioCodec = "aac,mp3",
|
||||||
|
Type = DlnaProfileType.Video
|
||||||
|
},
|
||||||
|
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "mp4,avi",
|
||||||
|
VideoCodec = "mpeg4,msmpeg4",
|
||||||
|
AudioCodec = "aac,mp3",
|
||||||
|
Type = DlnaProfileType.Video
|
||||||
|
},
|
||||||
|
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "asf",
|
||||||
|
VideoCodec = "wmv2,wmv3,vc1",
|
||||||
|
AudioCodec = "wmav2,wmapro,wmavoice",
|
||||||
|
Type = DlnaProfileType.Video
|
||||||
|
},
|
||||||
|
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "asf",
|
||||||
|
AudioCodec = "wmav2,wmapro,wmavoice",
|
||||||
|
Type = DlnaProfileType.Audio
|
||||||
|
},
|
||||||
|
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "mp4,aac",
|
||||||
|
AudioCodec = "aac",
|
||||||
|
Type = DlnaProfileType.Audio
|
||||||
|
},
|
||||||
|
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "mp3",
|
||||||
|
AudioCodec = "mp3",
|
||||||
|
Type = DlnaProfileType.Audio
|
||||||
|
},
|
||||||
|
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "jpeg,png,gif,bmp",
|
||||||
|
Type = DlnaProfileType.Photo
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CodecProfiles = new[]
|
||||||
|
{
|
||||||
|
new CodecProfile
|
||||||
|
{
|
||||||
|
Type = CodecType.Video,
|
||||||
|
Codec="h264",
|
||||||
|
Conditions = new []
|
||||||
|
{
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.Width,
|
||||||
|
Value = "800"
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.Height,
|
||||||
|
Value = "480"
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.VideoBitrate,
|
||||||
|
Value = "1000000",
|
||||||
|
IsRequired = false
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.VideoFramerate,
|
||||||
|
Value = "24",
|
||||||
|
IsRequired = false
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.VideoLevel,
|
||||||
|
Value = "3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
new CodecProfile
|
||||||
|
{
|
||||||
|
Type = CodecType.Video,
|
||||||
|
Conditions = new []
|
||||||
|
{
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.Width,
|
||||||
|
Value = "800"
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.Height,
|
||||||
|
Value = "480"
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.VideoBitrate,
|
||||||
|
Value = "1000000",
|
||||||
|
IsRequired = false
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.VideoFramerate,
|
||||||
|
Value = "24",
|
||||||
|
IsRequired = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
new CodecProfile
|
||||||
|
{
|
||||||
|
Type = CodecType.VideoAudio,
|
||||||
|
Conditions = new []
|
||||||
|
{
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.AudioBitrate,
|
||||||
|
Value = "128000"
|
||||||
|
},
|
||||||
|
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.AudioChannels,
|
||||||
|
Value = "2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
new CodecProfile
|
||||||
|
{
|
||||||
|
Type = CodecType.Audio,
|
||||||
|
Conditions = new []
|
||||||
|
{
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.AudioBitrate,
|
||||||
|
Value = "128000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -768,7 +768,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
|
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
|
||||||
var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2},thumbnail=50\" -f image2 \"{1}\"", inputPath, "-", vf) :
|
var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2},thumbnail=40\" -f image2 \"{1}\"", inputPath, "-", vf) :
|
||||||
string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, "-", vf);
|
string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, "-", vf);
|
||||||
|
|
||||||
var probeSize = GetProbeSizeArgument(type);
|
var probeSize = GetProbeSizeArgument(type);
|
||||||
|
|
|
@ -28,7 +28,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
var streams = mediaSources.Select(i => BuildAudioItem(options.ItemId, i, options.Profile)).ToList();
|
var streams = mediaSources.Select(i => BuildAudioItem(i, options)).ToList();
|
||||||
|
|
||||||
foreach (var stream in streams)
|
foreach (var stream in streams)
|
||||||
{
|
{
|
||||||
|
@ -75,36 +75,40 @@ namespace MediaBrowser.Model.Dlna
|
||||||
streams.FirstOrDefault();
|
streams.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private StreamInfo BuildAudioItem(string itemId, MediaSourceInfo item, DeviceProfile profile)
|
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
|
||||||
{
|
{
|
||||||
var playlistItem = new StreamInfo
|
var playlistItem = new StreamInfo
|
||||||
{
|
{
|
||||||
ItemId = itemId,
|
ItemId = options.ItemId,
|
||||||
MediaType = DlnaProfileType.Audio,
|
MediaType = DlnaProfileType.Audio,
|
||||||
MediaSourceId = item.Id
|
MediaSourceId = item.Id
|
||||||
};
|
};
|
||||||
|
|
||||||
var audioStream = item.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
var audioStream = item.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||||
|
|
||||||
var directPlay = profile.DirectPlayProfiles
|
// Honor the max bitrate setting
|
||||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsAudioProfileSupported(i, item, audioStream));
|
if (IsAudioEligibleForDirectPlay(item, options))
|
||||||
|
|
||||||
if (directPlay != null)
|
|
||||||
{
|
{
|
||||||
var audioCodec = audioStream == null ? null : audioStream.Codec;
|
var directPlay = options.Profile.DirectPlayProfiles
|
||||||
|
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsAudioDirectPlaySupported(i, item, audioStream));
|
||||||
|
|
||||||
// Make sure audio codec profiles are satisfied
|
if (directPlay != null)
|
||||||
if (!string.IsNullOrEmpty(audioCodec) && profile.CodecProfiles.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(audioCodec))
|
|
||||||
.All(i => AreConditionsSatisfied(i.Conditions, item.Path, null, audioStream)))
|
|
||||||
{
|
{
|
||||||
playlistItem.IsDirectStream = true;
|
var audioCodec = audioStream == null ? null : audioStream.Codec;
|
||||||
playlistItem.Container = item.Container;
|
|
||||||
|
|
||||||
return playlistItem;
|
// Make sure audio codec profiles are satisfied
|
||||||
|
if (!string.IsNullOrEmpty(audioCodec) && options.Profile.CodecProfiles.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(audioCodec))
|
||||||
|
.All(i => AreConditionsSatisfied(i.Conditions, item.Path, null, audioStream)))
|
||||||
|
{
|
||||||
|
playlistItem.IsDirectStream = true;
|
||||||
|
playlistItem.Container = item.Container;
|
||||||
|
|
||||||
|
return playlistItem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var transcodingProfile = profile.TranscodingProfiles
|
var transcodingProfile = options.Profile.TranscodingProfiles
|
||||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType);
|
.FirstOrDefault(i => i.Type == playlistItem.MediaType);
|
||||||
|
|
||||||
if (transcodingProfile != null)
|
if (transcodingProfile != null)
|
||||||
|
@ -113,12 +117,28 @@ namespace MediaBrowser.Model.Dlna
|
||||||
playlistItem.Container = transcodingProfile.Container;
|
playlistItem.Container = transcodingProfile.Container;
|
||||||
playlistItem.AudioCodec = transcodingProfile.AudioCodec;
|
playlistItem.AudioCodec = transcodingProfile.AudioCodec;
|
||||||
|
|
||||||
var audioTranscodingConditions = profile.CodecProfiles
|
var audioTranscodingConditions = options.Profile.CodecProfiles
|
||||||
.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(transcodingProfile.AudioCodec))
|
.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(transcodingProfile.AudioCodec))
|
||||||
.Take(1)
|
.Take(1)
|
||||||
.SelectMany(i => i.Conditions);
|
.SelectMany(i => i.Conditions);
|
||||||
|
|
||||||
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
|
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
|
||||||
|
|
||||||
|
// Honor requested max channels
|
||||||
|
if (options.MaxAudioChannels.HasValue)
|
||||||
|
{
|
||||||
|
var currentValue = playlistItem.MaxAudioChannels ?? options.MaxAudioChannels.Value;
|
||||||
|
|
||||||
|
playlistItem.MaxAudioChannels = Math.Min(options.MaxAudioChannels.Value, currentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Honor requested max bitrate
|
||||||
|
if (options.MaxBitrate.HasValue)
|
||||||
|
{
|
||||||
|
var currentValue = playlistItem.AudioBitrate ?? options.MaxBitrate.Value;
|
||||||
|
|
||||||
|
playlistItem.AudioBitrate = Math.Min(options.MaxBitrate.Value, currentValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return playlistItem;
|
return playlistItem;
|
||||||
|
@ -140,7 +160,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
// See if it can be direct played
|
// See if it can be direct played
|
||||||
var directPlay = options.Profile.DirectPlayProfiles
|
var directPlay = options.Profile.DirectPlayProfiles
|
||||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsVideoProfileSupported(i, item, videoStream, audioStream));
|
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsVideoDirectPlaySupported(i, item, videoStream, audioStream));
|
||||||
|
|
||||||
if (directPlay != null)
|
if (directPlay != null)
|
||||||
{
|
{
|
||||||
|
@ -189,6 +209,37 @@ namespace MediaBrowser.Model.Dlna
|
||||||
.SelectMany(i => i.Conditions);
|
.SelectMany(i => i.Conditions);
|
||||||
|
|
||||||
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
|
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
|
||||||
|
|
||||||
|
// Honor requested max channels
|
||||||
|
if (options.MaxAudioChannels.HasValue)
|
||||||
|
{
|
||||||
|
var currentValue = playlistItem.MaxAudioChannels ?? options.MaxAudioChannels.Value;
|
||||||
|
|
||||||
|
playlistItem.MaxAudioChannels = Math.Min(options.MaxAudioChannels.Value, currentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Honor requested max bitrate
|
||||||
|
if (options.MaxAudioTranscodingBitrate.HasValue)
|
||||||
|
{
|
||||||
|
var currentValue = playlistItem.AudioBitrate ?? options.MaxAudioTranscodingBitrate.Value;
|
||||||
|
|
||||||
|
playlistItem.AudioBitrate = Math.Min(options.MaxAudioTranscodingBitrate.Value, currentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Honor max rate
|
||||||
|
if (options.MaxBitrate.HasValue)
|
||||||
|
{
|
||||||
|
var videoBitrate = options.MaxBitrate.Value;
|
||||||
|
|
||||||
|
if (playlistItem.AudioBitrate.HasValue)
|
||||||
|
{
|
||||||
|
videoBitrate -= playlistItem.AudioBitrate.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentValue = playlistItem.VideoBitrate ?? videoBitrate;
|
||||||
|
|
||||||
|
playlistItem.VideoBitrate = Math.Min(videoBitrate, currentValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return playlistItem;
|
return playlistItem;
|
||||||
|
@ -207,7 +258,13 @@ namespace MediaBrowser.Model.Dlna
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return IsAudioEligibleForDirectPlay(item, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, AudioOptions options)
|
||||||
|
{
|
||||||
|
// Honor the max bitrate setting
|
||||||
|
return !options.MaxBitrate.HasValue || (item.Bitrate.HasValue && item.Bitrate.Value <= options.MaxBitrate.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateInput(VideoOptions options)
|
private void ValidateInput(VideoOptions options)
|
||||||
|
@ -331,7 +388,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsAudioProfileSupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
|
private bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
|
||||||
{
|
{
|
||||||
if (profile.Container.Length > 0)
|
if (profile.Container.Length > 0)
|
||||||
{
|
{
|
||||||
|
@ -346,7 +403,7 @@ namespace MediaBrowser.Model.Dlna
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsVideoProfileSupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream)
|
private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream)
|
||||||
{
|
{
|
||||||
// Only plain video files can be direct played
|
// Only plain video files can be direct played
|
||||||
if (item.VideoType != VideoType.VideoFile)
|
if (item.VideoType != VideoType.VideoFile)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using MediaBrowser.Model.Dto;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using MediaBrowser.Model.Dto;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Dlna
|
namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
|
@ -107,10 +107,25 @@ namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
public string ItemId { get; set; }
|
public string ItemId { get; set; }
|
||||||
public List<MediaSourceInfo> MediaSources { get; set; }
|
public List<MediaSourceInfo> MediaSources { get; set; }
|
||||||
public int? MaxBitrateSetting { get; set; }
|
|
||||||
public DeviceProfile Profile { get; set; }
|
public DeviceProfile Profile { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional. Only needed if a specific AudioStreamIndex or SubtitleStreamIndex are requested.
|
||||||
|
/// </summary>
|
||||||
public string MediaSourceId { get; set; }
|
public string MediaSourceId { get; set; }
|
||||||
|
|
||||||
public string DeviceId { get; set; }
|
public string DeviceId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows an override of supported number of audio channels
|
||||||
|
/// Example: DeviceProfile supports five channel, but user only has stereo speakers
|
||||||
|
/// </summary>
|
||||||
|
public int? MaxAudioChannels { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The application's configured quality setting
|
||||||
|
/// </summary>
|
||||||
|
public int? MaxBitrate { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -120,5 +135,11 @@ namespace MediaBrowser.Model.Dlna
|
||||||
{
|
{
|
||||||
public int? AudioStreamIndex { get; set; }
|
public int? AudioStreamIndex { get; set; }
|
||||||
public int? SubtitleStreamIndex { get; set; }
|
public int? SubtitleStreamIndex { get; set; }
|
||||||
|
public int? MaxAudioTranscodingBitrate { get; set; }
|
||||||
|
|
||||||
|
public VideoOptions()
|
||||||
|
{
|
||||||
|
MaxAudioTranscodingBitrate = 128000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,5 +24,7 @@ namespace MediaBrowser.Model.Dto
|
||||||
public Video3DFormat? Video3DFormat { get; set; }
|
public Video3DFormat? Video3DFormat { get; set; }
|
||||||
|
|
||||||
public List<MediaStream> MediaStreams { get; set; }
|
public List<MediaStream> MediaStreams { get; set; }
|
||||||
|
|
||||||
|
public int? Bitrate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,7 +152,11 @@ namespace MediaBrowser.Model.Entities
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The subtitle
|
/// The subtitle
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Subtitle
|
Subtitle,
|
||||||
|
/// <summary>
|
||||||
|
/// The embedded image
|
||||||
|
/// </summary>
|
||||||
|
EmbeddedImage
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MediaInfo
|
public class MediaInfo
|
||||||
|
|
|
@ -43,6 +43,9 @@ namespace MediaBrowser.Model.Session
|
||||||
VolumeDown = 18,
|
VolumeDown = 18,
|
||||||
Mute = 19,
|
Mute = 19,
|
||||||
Unmute = 20,
|
Unmute = 20,
|
||||||
ToggleMute = 21
|
ToggleMute = 21,
|
||||||
|
SetVolume = 22,
|
||||||
|
SetAudioStreamIndex = 23,
|
||||||
|
SetSubtitleStreamIndex = 24
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,10 @@ namespace MediaBrowser.Model.Session
|
||||||
|
|
||||||
public string[] QueueableMediaTypes { get; set; }
|
public string[] QueueableMediaTypes { get; set; }
|
||||||
|
|
||||||
|
public int? AudioStreamIndex { get; set; }
|
||||||
|
|
||||||
|
public int? SubtitleStreamIndex { get; set; }
|
||||||
|
|
||||||
public PlaybackStartInfo()
|
public PlaybackStartInfo()
|
||||||
{
|
{
|
||||||
QueueableMediaTypes = new string[] { };
|
QueueableMediaTypes = new string[] { };
|
||||||
|
@ -38,6 +42,12 @@ namespace MediaBrowser.Model.Session
|
||||||
public bool IsPaused { get; set; }
|
public bool IsPaused { get; set; }
|
||||||
|
|
||||||
public bool IsMuted { get; set; }
|
public bool IsMuted { get; set; }
|
||||||
|
|
||||||
|
public int? AudioStreamIndex { get; set; }
|
||||||
|
|
||||||
|
public int? SubtitleStreamIndex { get; set; }
|
||||||
|
|
||||||
|
public int? VolumeLevel { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -111,6 +111,24 @@ namespace MediaBrowser.Model.Session
|
||||||
/// <value>The name of the device.</value>
|
/// <value>The name of the device.</value>
|
||||||
public string DeviceName { get; set; }
|
public string DeviceName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the volume level.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The volume level.</value>
|
||||||
|
public int? VolumeLevel { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the index of the now playing audio stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The index of the now playing audio stream.</value>
|
||||||
|
public int? NowPlayingAudioStreamIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the index of the now playing subtitle stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The index of the now playing subtitle stream.</value>
|
||||||
|
public int? NowPlayingSubtitleStreamIndex { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance is paused.
|
/// Gets or sets a value indicating whether this instance is paused.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -141,24 +159,12 @@ namespace MediaBrowser.Model.Session
|
||||||
/// <value>The device id.</value>
|
/// <value>The device id.</value>
|
||||||
public string DeviceId { get; set; }
|
public string DeviceId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether [supports fullscreen toggle].
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if [supports fullscreen toggle]; otherwise, <c>false</c>.</value>
|
|
||||||
public bool SupportsFullscreenToggle { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether [supports remote control].
|
/// Gets or sets a value indicating whether [supports remote control].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if [supports remote control]; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if [supports remote control]; otherwise, <c>false</c>.</value>
|
||||||
public bool SupportsRemoteControl { get; set; }
|
public bool SupportsRemoteControl { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether [supports osd toggle].
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if [supports osd toggle]; otherwise, <c>false</c>.</value>
|
|
||||||
public bool SupportsOsdToggle { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether [supports navigation commands].
|
/// Gets or sets a value indicating whether [supports navigation commands].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -321,6 +321,7 @@ namespace MediaBrowser.Providers.Manager
|
||||||
|
|
||||||
// Only one local provider allowed per item
|
// Only one local provider allowed per item
|
||||||
hasLocalMetadata = true;
|
hasLocalMetadata = true;
|
||||||
|
item.IsUnidentified = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -245,127 +245,6 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SessionInfoDto GetSessionInfoDto(SessionInfo session)
|
|
||||||
{
|
|
||||||
var dto = new SessionInfoDto
|
|
||||||
{
|
|
||||||
Client = session.Client,
|
|
||||||
DeviceId = session.DeviceId,
|
|
||||||
DeviceName = session.DeviceName,
|
|
||||||
Id = session.Id.ToString("N"),
|
|
||||||
LastActivityDate = session.LastActivityDate,
|
|
||||||
NowPlayingPositionTicks = session.NowPlayingPositionTicks,
|
|
||||||
SupportsRemoteControl = session.SupportsRemoteControl,
|
|
||||||
IsPaused = session.IsPaused,
|
|
||||||
IsMuted = session.IsMuted,
|
|
||||||
NowViewingContext = session.NowViewingContext,
|
|
||||||
NowViewingItemId = session.NowViewingItemId,
|
|
||||||
NowViewingItemName = session.NowViewingItemName,
|
|
||||||
NowViewingItemType = session.NowViewingItemType,
|
|
||||||
ApplicationVersion = session.ApplicationVersion,
|
|
||||||
CanSeek = session.CanSeek,
|
|
||||||
QueueableMediaTypes = session.QueueableMediaTypes,
|
|
||||||
PlayableMediaTypes = session.PlayableMediaTypes,
|
|
||||||
RemoteEndPoint = session.RemoteEndPoint,
|
|
||||||
AdditionalUsers = session.AdditionalUsers,
|
|
||||||
SupportedCommands = session.SupportedCommands
|
|
||||||
};
|
|
||||||
|
|
||||||
if (session.NowPlayingItem != null)
|
|
||||||
{
|
|
||||||
dto.NowPlayingItem = GetNowPlayingInfo(session.NowPlayingItem, session.NowPlayingMediaSourceId, session.NowPlayingRunTimeTicks);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.UserId.HasValue)
|
|
||||||
{
|
|
||||||
dto.UserId = session.UserId.Value.ToString("N");
|
|
||||||
}
|
|
||||||
dto.UserName = session.UserName;
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a BaseItem to a BaseItemInfo
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item.</param>
|
|
||||||
/// <param name="mediaSourceId">The media version identifier.</param>
|
|
||||||
/// <param name="nowPlayingRuntimeTicks">The now playing runtime ticks.</param>
|
|
||||||
/// <returns>BaseItemInfo.</returns>
|
|
||||||
/// <exception cref="System.ArgumentNullException">item</exception>
|
|
||||||
private BaseItemInfo GetNowPlayingInfo(BaseItem item, string mediaSourceId, long? nowPlayingRuntimeTicks)
|
|
||||||
{
|
|
||||||
if (item == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException("item");
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = new BaseItemInfo
|
|
||||||
{
|
|
||||||
Id = GetDtoId(item),
|
|
||||||
Name = item.Name,
|
|
||||||
MediaType = item.MediaType,
|
|
||||||
Type = item.GetClientTypeName(),
|
|
||||||
RunTimeTicks = nowPlayingRuntimeTicks,
|
|
||||||
MediaSourceId = mediaSourceId
|
|
||||||
};
|
|
||||||
|
|
||||||
info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary);
|
|
||||||
|
|
||||||
var backropItem = item.HasImage(ImageType.Backdrop) ? item : null;
|
|
||||||
|
|
||||||
var thumbItem = item.HasImage(ImageType.Thumb) ? item : null;
|
|
||||||
|
|
||||||
if (thumbItem == null)
|
|
||||||
{
|
|
||||||
var episode = item as Episode;
|
|
||||||
|
|
||||||
if (episode != null)
|
|
||||||
{
|
|
||||||
var series = episode.Series;
|
|
||||||
|
|
||||||
if (series != null && series.HasImage(ImageType.Thumb))
|
|
||||||
{
|
|
||||||
thumbItem = series;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backropItem == null)
|
|
||||||
{
|
|
||||||
var episode = item as Episode;
|
|
||||||
|
|
||||||
if (episode != null)
|
|
||||||
{
|
|
||||||
var series = episode.Series;
|
|
||||||
|
|
||||||
if (series != null && series.HasImage(ImageType.Backdrop))
|
|
||||||
{
|
|
||||||
backropItem = series;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thumbItem == null)
|
|
||||||
{
|
|
||||||
thumbItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Thumb));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thumbItem != null)
|
|
||||||
{
|
|
||||||
info.ThumbImageTag = GetImageCacheTag(thumbItem, ImageType.Thumb);
|
|
||||||
info.ThumbItemId = GetDtoId(thumbItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thumbItem != null)
|
|
||||||
{
|
|
||||||
info.BackdropImageTag = GetImageCacheTag(backropItem, ImageType.Backdrop);
|
|
||||||
info.BackdropItemId = GetDtoId(backropItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets client-side Id of a server-side BaseItem
|
/// Gets client-side Id of a server-side BaseItem
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1367,6 +1246,13 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bitrate = info.MediaStreams.Where(m => m.Type == MediaStreamType.Audio || m.Type == MediaStreamType.Video).Select(m => m.BitRate ?? 0).Sum();
|
||||||
|
|
||||||
|
if (bitrate > 0)
|
||||||
|
{
|
||||||
|
info.Bitrate = bitrate;
|
||||||
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1388,6 +1274,13 @@ namespace MediaBrowser.Server.Implementations.Dto
|
||||||
info.Container = Path.GetExtension(i.Path).TrimStart('.');
|
info.Container = Path.GetExtension(i.Path).TrimStart('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bitrate = info.MediaStreams.Where(m => m.Type == MediaStreamType.Audio).Select(m => m.BitRate ?? 0).Sum();
|
||||||
|
|
||||||
|
if (bitrate > 0)
|
||||||
|
{
|
||||||
|
info.Bitrate = bitrate;
|
||||||
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -785,12 +785,12 @@ namespace MediaBrowser.Server.Implementations.Library
|
||||||
private T GetItemByName<T>(string path, string name)
|
private T GetItemByName<T>(string path, string name)
|
||||||
where T : BaseItem, new()
|
where T : BaseItem, new()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException("path");
|
throw new ArgumentNullException("path");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(name))
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException("name");
|
throw new ArgumentNullException("name");
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,9 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
|
||||||
|
|
||||||
var people = _libraryManager.RootFolder.GetRecursiveChildren()
|
var people = _libraryManager.RootFolder.GetRecursiveChildren()
|
||||||
.SelectMany(c => c.People)
|
.SelectMany(c => c.People)
|
||||||
.DistinctBy(p => p.Name, StringComparer.OrdinalIgnoreCase)
|
.Where(i => !string.IsNullOrWhiteSpace(i.Name))
|
||||||
|
.Select(i => i.Name)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var numComplete = 0;
|
var numComplete = 0;
|
||||||
|
@ -61,13 +63,13 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var item = _libraryManager.GetPerson(person.Name);
|
var item = _libraryManager.GetPerson(person);
|
||||||
|
|
||||||
await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
|
await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.ErrorException("Error validating IBN entry {0}", ex, person.Name);
|
_logger.ErrorException("Error validating IBN entry {0}", ex, person);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update progress
|
// Update progress
|
||||||
|
|
|
@ -46,7 +46,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
||||||
|
|
||||||
new SystemEventTrigger{ SystemEvent = SystemEvent.WakeFromSleep},
|
new SystemEventTrigger{ SystemEvent = SystemEvent.WakeFromSleep},
|
||||||
|
|
||||||
new IntervalTrigger{ Interval = TimeSpan.FromHours(3)}
|
new IntervalTrigger{ Interval = TimeSpan.FromHours(4)}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{"SettingsSaved":"\u062a\u0645 \u062d\u0641\u0638 \u0627\u0644\u0627\u0639\u062f\u0627\u062f\u0627\u062a.","AddUser":"\u0627\u0636\u0627\u0641\u0629 \u0645\u0633\u062a\u062e\u062f\u0645","Users":"\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646","Delete":"\u062d\u0630\u0641","Administrator":"\u0627\u0644\u0645\u0633\u0624\u0648\u0644","Password":"\u0643\u0644\u0645\u0629 \u0627\u0644\u0633\u0631","DeleteImage":"\u062d\u0630\u0641 \u0635\u0648\u0631\u0629","DeleteImageConfirmation":"\u0647\u0644 \u0627\u0646\u062a \u0645\u062a\u0627\u0643\u062f \u0645\u0646 \u062d\u0630\u0641 \u0647\u0630\u0647 \u0627\u0644\u0635\u0648\u0631\u0629\u061f","FileReadCancelled":"\u062a\u0645 \u0627\u0644\u063a\u0627\u0621 \u0642\u0631\u0627\u0621\u0629 \u0627\u0644\u0645\u0644\u0641.","FileNotFound":"\u0627\u0644\u0645\u0644\u0641 \u063a\u064a\u0631 \u0645\u0648\u062c\u0648\u062f.","FileReadError":"\u062d\u062f\u062b \u062e\u0637\u0623 \u0628\u0642\u0631\u0627\u0621\u0629 \u0627\u0644\u0645\u0644\u0641.","DeleteUser":"\u062d\u0630\u0641 \u0645\u0633\u062a\u062e\u062f\u0645","DeleteUserConfirmation":"\u0647\u0644 \u0627\u0646\u062a \u0645\u062a\u0627\u0643\u062f \u0645\u0646 \u0627\u0646\u0643 \u062a\u0631\u064a\u062f \u062d\u0630\u0641 {0} \u061f","PasswordResetHeader":"\u0627\u0639\u0627\u062f\u0629 \u062a\u0639\u064a\u064a\u0646 \u0643\u0644\u0645\u0629 \u0627\u0644\u0633\u0631","PasswordResetComplete":"\u0644\u0642\u062f \u062a\u0645 \u0627\u0639\u0627\u062f\u0629 \u062a\u0639\u064a\u064a\u0646 \u0643\u0644\u0645\u0629 \u0627\u0644\u0633\u0631.","PasswordResetConfirmation":"\u0647\u0644 \u0627\u0646\u062a \u0645\u062a\u0627\u0643\u062f \u0645\u0646 \u0627\u0646\u0643 \u062a\u0631\u064a\u062f \u0627\u0639\u0627\u062f\u0629 \u062a\u0639\u064a\u064a\u0646 \u0643\u0644\u0645\u0629 \u0627\u0644\u0633\u0631\u061f","PasswordSaved":"\u062a\u0645 \u062d\u0641\u0638 \u0643\u0644\u0645\u0629 \u0627\u0644\u0633\u0631.","PasswordMatchError":"\u0643\u0644\u0645\u0629 \u0627\u0644\u0633\u0631 \u0648\u062a\u0627\u0643\u064a\u062f\u0647\u0627 \u064a\u062c\u0628 \u0627\u0646 \u064a\u062a\u0637\u0627\u0628\u0642\u0627\u0646.","OptionOff":"\u0627\u064a\u0642\u0627\u0641","OptionOn":"\u062a\u0634\u063a\u064a\u0644","OptionRelease":"\u0627\u0644\u0627\u0635\u062f\u0627\u0631 \u0627\u0644\u0631\u0633\u0645\u0649","OptionBeta":"\u0628\u064a\u062a\u0627","OptionDev":"\u062a\u0637\u0648\u0631\u0649 (\u063a\u064a\u0631 \u0645\u0633\u062a\u0642\u0631)","UninstallPluginHeader":"\u0627\u0644\u063a\u0627\u0621 \u0627\u0644\u0645\u0644\u062d\u0642","UninstallPluginConfirmation":"\u0647\u0644 \u0627\u0646\u062a \u0645\u062a\u0627\u0643\u062f \u0627\u0646\u0643 \u062a\u0631\u064a\u062f \u0627\u0644\u063a\u0627\u0621 {0} \u061f","NoPluginConfigurationMessage":"\u0647\u0630\u0627 \u0627\u0644\u0645\u0644\u062d\u0642 \u0644\u064a\u0633 \u0644\u0647 \u0636\u0628\u0637.","NoPluginsInstalledMessage":"\u0644\u0627 \u064a\u0648\u062c\u062f \u0644\u062f\u064a\u0643 \u0627\u0649 \u0645\u0644\u0627\u062d\u0642 \u0645\u062b\u0628\u062a\u0629.","BrowsePluginCatalogMessage":"\u062a\u0635\u0641\u062d \u0642\u0627\u0626\u0645\u062a\u0646\u0627 \u0644\u0644\u0645\u0644\u062d\u0642 \u0644\u062a\u0631\u0649 \u0627\u0644\u0645\u062a\u0648\u0641\u0631 \u0645\u0646 \u0627\u0644\u0645\u0644\u0627\u062d\u0642."}
|
|
@ -0,0 +1 @@
|
||||||
|
{"SettingsSaved":"\u039f\u03b9 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c4\u03b7\u03ba\u03b1\u03bd","AddUser":"\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7","Users":"\u039f\u03b9 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b5\u03c2","Delete":"\u0394\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03c4\u03b5","Administrator":"\u03c4\u03bf \u03b4\u03b9\u03b1\u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03c4\u03ae\u03c2","Password":"\u03c4\u03bf\u03bd \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2","DeleteImage":"\u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1","DeleteImageConfirmation":"\u0395\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03b1\u03c5\u03c4\u03ae \u03c4\u03b7\u03bd \u03b5\u03b9\u03ba\u03cc\u03bd\u03b1;","FileReadCancelled":"\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03b4\u03b9\u03b1\u03b2\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03ad\u03c7\u03b5\u03b9 \u03b1\u03ba\u03c5\u03c1\u03c9\u03b8\u03b5\u03af","FileNotFound":"\u03a4\u03bf \u03b1\u03c1\u03c7\u03b5\u03af\u03bf \u03b4\u03b5\u03bd \u03b2\u03c1\u03ad\u03b8\u03b7\u03ba\u03b5","FileReadError":"\u03a0\u03b1\u03c1\u03bf\u03c5\u03c3\u03b9\u03ac\u03c3\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1 \u03ba\u03b1\u03c4\u03ac \u03c4\u03b7\u03bd \u03b1\u03bd\u03ac\u03b3\u03bd\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 \u03b1\u03c1\u03c7\u03b5\u03af\u03bf\u03c5","DeleteUser":"\u0394\u03b9\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7","DeleteUserConfirmation":"\u0395\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5","PasswordResetHeader":"\u0395\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03ba\u03c9\u03b4\u03b9\u03ba\u03bf\u03cd \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2","PasswordResetComplete":"\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03b3\u03af\u03bd\u03b5\u03b9 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac","PasswordResetConfirmation":"\u0395\u03af\u03c3\u03c4\u03b5 \u03c3\u03af\u03b3\u03bf\u03c5\u03c1\u03bf\u03b9 \u03cc\u03c4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03ad\u03c1\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2;","PasswordSaved":"\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c4\u03b7\u03ba\u03b5","PasswordMatchError":"\u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03b5\u03c0\u03b9\u03b2\u03b5\u03b2\u03b1\u03af\u03c9\u03c3\u03b7\u03c2 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c4\u03b1\u03b9\u03c1\u03b9\u03ac\u03b6\u03bf\u03c5\u03bd","OptionOff":"Off","OptionOn":"On","OptionRelease":"Official Release","OptionBeta":"Beta","OptionDev":"Dev (Unstable)","UninstallPluginHeader":"Uninstall Plugin","UninstallPluginConfirmation":"Are you sure you wish to uninstall {0}?","NoPluginConfigurationMessage":"This plugin has nothing to configure.","NoPluginsInstalledMessage":"You have no plugins installed.","BrowsePluginCatalogMessage":"Browse our plugin catalog to view available plugins."}
|
|
@ -1 +1 @@
|
||||||
{"SettingsSaved":"Settings saved.","AddUser":"Add User","Users":"Users","Delete":"Delete","Administrator":"Administrator","Password":"Password","DeleteImage":"Delete Image","DeleteImageConfirmation":"Are you sure you wish to delete this image?","FileReadCancelled":"The file read has been cancelled.","FileNotFound":"File not found.","FileReadError":"An error occurred while reading the file.","DeleteUser":"Delete User","DeleteUserConfirmation":"Are you sure you wish to delete {0}?","PasswordResetHeader":"Password Reset","PasswordResetComplete":"The password has been reset.","PasswordResetConfirmation":"Are you sure you wish to reset the password?","PasswordSaved":"Password saved.","PasswordMatchError":"Password and password confirmation must match.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Release","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Uninstall Plugin","UninstallPluginConfirmation":"Are you sure you wish to uninstall {0}?","NoPluginConfigurationMessage":"This plugin has nothing to configure.","NoPluginsInstalledMessage":"You have no plugins installed.","BrowsePluginCatalogMessage":"Browse our plugin catalog to view available plugins."}
|
{"SettingsSaved":"Settings saved.","AddUser":"Add User","Users":"Users","Delete":"Delete","Administrator":"Administrator","Password":"Password","DeleteImage":"Delete Image","DeleteImageConfirmation":"Are you sure you wish to delete this image?","FileReadCancelled":"The file read has been canceled.","FileNotFound":"File not found.","FileReadError":"An error occurred while reading the file.","DeleteUser":"Delete User","DeleteUserConfirmation":"Are you sure you wish to delete {0}?","PasswordResetHeader":"Password Reset","PasswordResetComplete":"The password has been reset.","PasswordResetConfirmation":"Are you sure you wish to reset the password?","PasswordSaved":"Password saved.","PasswordMatchError":"Password and password confirmation must match.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Official Release","OptionBeta":"Beta","OptionDev":"Dev (Unstable)","UninstallPluginHeader":"Uninstall Plugin","UninstallPluginConfirmation":"Are you sure you wish to uninstall {0}?","NoPluginConfigurationMessage":"This plugin has nothing to configure.","NoPluginsInstalledMessage":"You have no plugins installed.","BrowsePluginCatalogMessage":"Browse our plugin catalog to view available plugins."}
|
|
@ -1 +1 @@
|
||||||
{"SettingsSaved":"Configuracion guardada","AddUser":"Agregar usuario","Users":"Usuarios","Delete":"Borrar","Administrator":"Administrador","Password":"Contrase\u00f1a","DeleteImage":"Borrar Imagen","DeleteImageConfirmation":"Esta seguro que desea borrar esta imagen?","FileReadCancelled":"La lectura del archivo se ha cancelado.","FileNotFound":"Archivo no encontrado.","FileReadError":"Se encontr\u00f3 un error al leer el archivo.","DeleteUser":"Borrar Usuario","DeleteUserConfirmation":"Esta seguro que desea eliminar a {0}?","PasswordResetHeader":"Restablecer contrase\u00f1a","PasswordResetComplete":"La contrase\u00f1a se ha restablecido.","PasswordResetConfirmation":"Esta seguro que desea restablecer la contrase\u00f1a?","PasswordSaved":"Contrase\u00f1a guardada.","PasswordMatchError":"La contrase\u00f1a y la confirmaci\u00f3n de la contrase\u00f1a deben de ser iguales.","OptionOff":"Apagado","OptionOn":"Prendido","OptionRelease":"Liberar","OptionBeta":"Beta","OptionDev":"Desarrollo","UninstallPluginHeader":"Desinstalar Plugin","UninstallPluginConfirmation":"Esta seguro que desea desinstalar {0}?","NoPluginConfigurationMessage":"El plugin no requiere configuraci\u00f3n","NoPluginsInstalledMessage":"No tiene plugins instalados.","BrowsePluginCatalogMessage":"Navegar el catalogo de plugins para ver los plugins disponibles."}
|
{"SettingsSaved":"Configuraci\u00f3n guardada","AddUser":"Agregar usuario","Users":"Usuarios","Delete":"Borrar","Administrator":"Administrador","Password":"Contrase\u00f1a","DeleteImage":"Borrar Imagen","DeleteImageConfirmation":"Est\u00e1 seguro que desea borrar esta imagen?","FileReadCancelled":"La lectura del archivo se ha cancelado.","FileNotFound":"Archivo no encontrado.","FileReadError":"Se encontr\u00f3 un error al leer el archivo.","DeleteUser":"Borrar Usuario","DeleteUserConfirmation":"Esta seguro que desea eliminar a {0}?","PasswordResetHeader":"Restablecer contrase\u00f1a","PasswordResetComplete":"La contrase\u00f1a se ha restablecido.","PasswordResetConfirmation":"Esta seguro que desea restablecer la contrase\u00f1a?","PasswordSaved":"Contrase\u00f1a guardada.","PasswordMatchError":"La contrase\u00f1a y la confirmaci\u00f3n de la contrase\u00f1a deben de ser iguales.","OptionOff":"Apagado","OptionOn":"Encendido","OptionRelease":"Liberar","OptionBeta":"Beta","OptionDev":"Desarrollo","UninstallPluginHeader":"Desinstalar Plugin","UninstallPluginConfirmation":"Esta seguro que desea desinstalar {0}?","NoPluginConfigurationMessage":"El plugin no requiere configuraci\u00f3n","NoPluginsInstalledMessage":"No tiene plugins instalados.","BrowsePluginCatalogMessage":"Navegar el catalogo de plugins para ver los plugins disponibles."}
|
|
@ -1 +1 @@
|
||||||
{"SettingsSaved":"Configuraci\u00f3n guardada.","AddUser":"Agregar usuario","Users":"Usuarios","Delete":"Eliminar","Administrator":"Administrador","Password":"Contrase\u00f1a","DeleteImage":"Eliminar imagen","DeleteImageConfirmation":"\u00bfEst\u00e1 seguro que desea eliminar esta imagen?","FileReadCancelled":"La lectura del archivo ha sido cancelada.","FileNotFound":"Archivo no encontrado.","FileReadError":"Ha ocurrido un error al leer el archivo.","DeleteUser":"Eliminar Usuario","DeleteUserConfirmation":"\u00bfEsta seguro que desea eliminar a {0}?","PasswordResetHeader":"Restablecer Contrase\u00f1a","PasswordResetComplete":"La contrase\u00f1a ha sido restablecida.","PasswordResetConfirmation":"\u00bfEst\u00e1 seguro que desea restablecer la contrase\u00f1a?","PasswordSaved":"Contrase\u00f1a guardada.","PasswordMatchError":"La Contrase\u00f1a y la confirmaci\u00f3n de la contrase\u00f1a deben coincidir.","OptionOff":"Apagado","OptionOn":"Encendido","OptionRelease":"Liberar","OptionBeta":"Beta","OptionDev":"Desarrollo","UninstallPluginHeader":"Desinstalar Complemento","UninstallPluginConfirmation":"\u00bfEst\u00e1 seguro que desea desinstalar {0}?","NoPluginConfigurationMessage":"El complemento no requiere configuraci\u00f3n","NoPluginsInstalledMessage":"No tiene complementos instalados.","BrowsePluginCatalogMessage":"Navege en el catalogo de complementos para ver los complementos disponibles."}
|
{"SettingsSaved":"Configuraci\u00f3n guardada.","AddUser":"Agregar usuario","Users":"Usuarios","Delete":"Eliminar","Administrator":"Administrador","Password":"Contrase\u00f1a","DeleteImage":"Eliminar imagen","DeleteImageConfirmation":"\u00bfEst\u00e1 seguro que desea eliminar esta imagen?","FileReadCancelled":"La lectura del archivo ha sido cancelada.","FileNotFound":"Archivo no encontrado.","FileReadError":"Ha ocurrido un error al leer el archivo.","DeleteUser":"Eliminar Usuario","DeleteUserConfirmation":"\u00bfEsta seguro que desea eliminar a {0}?","PasswordResetHeader":"Restablecer Contrase\u00f1a","PasswordResetComplete":"La contrase\u00f1a ha sido restablecida.","PasswordResetConfirmation":"\u00bfEst\u00e1 seguro que desea restablecer la contrase\u00f1a?","PasswordSaved":"Contrase\u00f1a guardada.","PasswordMatchError":"La Contrase\u00f1a y la confirmaci\u00f3n de la contrase\u00f1a deben coincidir.","OptionOff":"Apagado","OptionOn":"Encendido","OptionRelease":"Versi\u00f3n Oficial","OptionBeta":"Beta","OptionDev":"Desarrollo (Inestable)","UninstallPluginHeader":"Desinstalar Complemento","UninstallPluginConfirmation":"\u00bfEst\u00e1 seguro que desea desinstalar {0}?","NoPluginConfigurationMessage":"El complemento no requiere configuraci\u00f3n","NoPluginsInstalledMessage":"No tiene complementos instalados.","BrowsePluginCatalogMessage":"Navege en el catalogo de complementos para ver los complementos disponibles."}
|
|
@ -1 +1 @@
|
||||||
{"SettingsSaved":"Param\u00e8tres sauvegard\u00e9s.","AddUser":"Ajouter utilisateur","Users":"Utilisateurs","Delete":"Supprimer","Administrator":"Administrateur","Password":"Mot de passe","DeleteImage":"Supprimer Image","DeleteImageConfirmation":"\u00cates-vous s\u00fbr de vouloir supprimer l'image?","FileReadCancelled":"La lecture du fichier a \u00e9t\u00e9 annul\u00e9e.","FileNotFound":"Fichier non trouv\u00e9","FileReadError":"Un erreur est survenue pendant la lecture du fichier.","DeleteUser":"Supprimer utilisateur","DeleteUserConfirmation":"\u00cates-vous s\u00fbr de vouloir supprimer {0}?","PasswordResetHeader":"R\u00e9initialisation du mot de passe","PasswordResetComplete":"Le mot de passe a \u00e9t\u00e9 r\u00e9initialis\u00e9.","PasswordResetConfirmation":"\u00cates-vous s\u00fbr de vouloir r\u00e9initialiser le mot de passe?","PasswordSaved":"Mot de passe sauvegard\u00e9.","PasswordMatchError":"Le mot de passe et sa confirmation doivent correspondre.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Lancement","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"D\u00e9sinstaller Plug-in","UninstallPluginConfirmation":"\u00cates-vous s\u00fbr de vouloir d\u00e9sinstaller {0}?","NoPluginConfigurationMessage":"Ce module d'extension n'a rien \u00e0 configurer.","NoPluginsInstalledMessage":"Vous n'avez aucun module d'extension install\u00e9.","BrowsePluginCatalogMessage":"Explorer notre catalogue de Plug-ins disponibles."}
|
{"SettingsSaved":"Param\u00e8tres sauvegard\u00e9s.","AddUser":"Ajouter utilisateur","Users":"Utilisateurs","Delete":"Supprimer","Administrator":"Administrateur","Password":"Mot de passe","DeleteImage":"Supprimer Image","DeleteImageConfirmation":"\u00cates-vous s\u00fbr de vouloir supprimer l'image?","FileReadCancelled":"La lecture du fichier a \u00e9t\u00e9 annul\u00e9e.","FileNotFound":"Fichier non trouv\u00e9","FileReadError":"Un erreur est survenue pendant la lecture du fichier.","DeleteUser":"Supprimer utilisateur","DeleteUserConfirmation":"\u00cates-vous s\u00fbr de vouloir supprimer {0}?","PasswordResetHeader":"R\u00e9initialisation du mot de passe","PasswordResetComplete":"Le mot de passe a \u00e9t\u00e9 r\u00e9initialis\u00e9.","PasswordResetConfirmation":"\u00cates-vous s\u00fbr de vouloir r\u00e9initialiser le mot de passe?","PasswordSaved":"Mot de passe sauvegard\u00e9.","PasswordMatchError":"Le mot de passe et sa confirmation doivent correspondre.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Lancement","OptionBeta":"Beta","OptionDev":"Dev (Instable)","UninstallPluginHeader":"D\u00e9sinstaller Plug-in","UninstallPluginConfirmation":"\u00cates-vous s\u00fbr de vouloir d\u00e9sinstaller {0}?","NoPluginConfigurationMessage":"Ce module d'extension n'a rien \u00e0 configurer.","NoPluginsInstalledMessage":"Vous n'avez aucun module d'extension install\u00e9.","BrowsePluginCatalogMessage":"Explorer notre catalogue de Plug-ins disponibles."}
|
|
@ -0,0 +1 @@
|
||||||
|
{"SettingsSaved":"\u05d4\u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05e0\u05e9\u05de\u05e8\u05d5.","AddUser":"\u05d4\u05d5\u05e1\u05e3 \u05de\u05e9\u05ea\u05de\u05e9","Users":"\u05de\u05e9\u05ea\u05de\u05e9\u05d9\u05dd","Delete":"\u05de\u05d7\u05e7","Administrator":"\u05de\u05e0\u05d4\u05dc","Password":"\u05e1\u05d9\u05e1\u05de\u05d0","DeleteImage":"\u05de\u05d7\u05e7 \u05ea\u05de\u05d5\u05e0\u05d4","DeleteImageConfirmation":"\u05d4\u05d0\u05dd \u05d0\u05ea\u05d4 \u05d1\u05d8\u05d5\u05d7 \u05e9\u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05de\u05d7\u05d5\u05e7 \u05ea\u05de\u05d5\u05e0\u05d4 \u05d6\u05d5?","FileReadCancelled":"\u05e7\u05e8\u05d9\u05d0\u05ea \u05d4\u05e7\u05d5\u05d1\u05e5 \u05d1\u05d5\u05d8\u05dc\u05d4.","FileNotFound":"\u05e7\u05d5\u05d1\u05e5 \u05dc\u05d0 \u05e0\u05de\u05e6\u05d0.","FileReadError":"\u05d7\u05dc\u05d4 \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05e7\u05e8\u05d9\u05d0\u05ea \u05d4\u05e7\u05d5\u05d1\u05e5.","DeleteUser":"\u05de\u05d7\u05e7 \u05de\u05e9\u05ea\u05de\u05e9","DeleteUserConfirmation":"\u05d4\u05d0\u05dd \u05d0\u05ea\u05d4 \u05d1\u05d8\u05d5\u05d7 \u05db\u05d9 \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05de\u05d7\u05d5\u05e7 {0}?","PasswordResetHeader":"\u05d0\u05d9\u05e4\u05d5\u05e1 \u05e1\u05d9\u05e1\u05de\u05d0","PasswordResetComplete":"\u05d4\u05e1\u05d9\u05e1\u05de\u05d0 \u05d0\u05d5\u05e4\u05e1\u05d4.","PasswordResetConfirmation":"\u05d4\u05d0\u05dd \u05d0\u05ea\u05d4 \u05d1\u05d8\u05d5\u05d7 \u05e9\u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d0\u05e4\u05e1 \u05d0\u05ea \u05d4\u05e1\u05d9\u05e1\u05de\u05d0?","PasswordSaved":"\u05d4\u05e1\u05d9\u05e1\u05de\u05d0 \u05e0\u05e9\u05de\u05e8\u05d4.","PasswordMatchError":"\u05d4\u05e1\u05d9\u05e1\u05de\u05d0 \u05d5\u05d0\u05d9\u05de\u05d5\u05ea \u05d4\u05e1\u05d9\u05e1\u05de\u05d0 \u05e6\u05e8\u05d9\u05db\u05d5\u05ea \u05dc\u05d4\u05d9\u05d5\u05ea \u05d6\u05d4\u05d5\u05ea.","OptionOff":"\u05e1\u05db\u05d5\u05d9","OptionOn":"\u05e4\u05d5\u05e2\u05dc","OptionRelease":"\u05e9\u05d9\u05d7\u05e8\u05d5\u05e8 \u05e8\u05e9\u05de\u05d9","OptionBeta":"\u05d1\u05d8\u05d0","OptionDev":"\u05de\u05e4\u05ea\u05d7 (\u05dc\u05d0 \u05d9\u05e6\u05d9\u05d1)","UninstallPluginHeader":"\u05d4\u05e1\u05e8 \u05ea\u05d5\u05e1\u05e3","UninstallPluginConfirmation":"\u05d4\u05d0\u05dd \u05d0\u05ea\u05d4 \u05d1\u05d8\u05d5\u05d7 \u05e9\u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05e1\u05d9\u05e8 {0}?","NoPluginConfigurationMessage":"\u05dc\u05ea\u05d5\u05e1\u05e3 \u05d4\u05d6\u05d4 \u05d0\u05d9\u05df \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05de\u05d9\u05d5\u05d7\u05d3\u05d5\u05ea.","NoPluginsInstalledMessage":"\u05d0\u05d9\u05df \u05dc\u05da \u05ea\u05d5\u05e1\u05e4\u05d9\u05dd \u05de\u05d5\u05ea\u05e7\u05e0\u05d9\u05dd.","BrowsePluginCatalogMessage":"\u05e2\u05d1\u05d5\u05e8 \u05dc\u05e7\u05d8\u05dc\u05d5\u05d2 \u05d4\u05ea\u05d5\u05e1\u05e4\u05d9\u05dd \u05dc\u05e8\u05d0\u05d5\u05ea \u05d0\u05d9\u05dc\u05d5 \u05d6\u05de\u05d9\u05e0\u05d9\u05dd."}
|
|
@ -1 +1 @@
|
||||||
{"SettingsSaved":"Settaggi salvati.","AddUser":"Aggiungi utente","Users":"Utenti","Delete":"Elimina","Administrator":"Amministratore","Password":"Password","DeleteImage":"Elimina immagine","DeleteImageConfirmation":"Sei sicuro di voler eliminare questa immagine?","FileReadCancelled":"Il file letto \u00e8 stato cancellato.","FileNotFound":"File non trovato","FileReadError":"Errore durante la lettura del file.","DeleteUser":"Elimina utente","DeleteUserConfirmation":"Sei sicuro di voler eliminare {0}?","PasswordResetHeader":"Ripristina Password","PasswordResetComplete":"la password \u00e8 stata ripristinata.","PasswordResetConfirmation":"Sei sicuro di voler ripristinare la password?","PasswordSaved":"Password salvata.","PasswordMatchError":"Le password non coincidono.","OptionOff":"Spegni","OptionOn":"Accendi","OptionRelease":"Versione","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Disinstalla Plugin","UninstallPluginConfirmation":"Sei sicuro di voler Disinstallare {0}?","NoPluginConfigurationMessage":"Questo Plugin non \u00e8 stato configurato.","NoPluginsInstalledMessage":"non ci sono Plugins installati.","BrowsePluginCatalogMessage":"Sfoglia il catalogo dei Plugins."}
|
{"SettingsSaved":"Settaggi salvati.","AddUser":"Aggiungi utente","Users":"Utenti","Delete":"Elimina","Administrator":"Amministratore","Password":"Password","DeleteImage":"Elimina immagine","DeleteImageConfirmation":"Sei sicuro di voler eliminare questa immagine?","FileReadCancelled":"Il file letto \u00e8 stato cancellato.","FileNotFound":"File non trovato","FileReadError":"Errore durante la lettura del file.","DeleteUser":"Elimina utente","DeleteUserConfirmation":"Sei sicuro di voler eliminare {0}?","PasswordResetHeader":"Ripristina Password","PasswordResetComplete":"la password \u00e8 stata ripristinata.","PasswordResetConfirmation":"Sei sicuro di voler ripristinare la password?","PasswordSaved":"Password salvata.","PasswordMatchError":"Le password non coincidono.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Versione Ufficiale","OptionBeta":"Beta","OptionDev":"Dev (instabile)","UninstallPluginHeader":"Disinstalla Plugin","UninstallPluginConfirmation":"Sei sicuro di voler Disinstallare {0}?","NoPluginConfigurationMessage":"Questo Plugin non \u00e8 stato configurato.","NoPluginsInstalledMessage":"non ci sono Plugins installati.","BrowsePluginCatalogMessage":"Sfoglia il catalogo dei Plugins."}
|
|
@ -7,7 +7,7 @@
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
"DeleteImage": "Delete Image",
|
"DeleteImage": "Delete Image",
|
||||||
"DeleteImageConfirmation": "Are you sure you wish to delete this image?",
|
"DeleteImageConfirmation": "Are you sure you wish to delete this image?",
|
||||||
"FileReadCancelled": "The file read has been cancelled.",
|
"FileReadCancelled": "The file read has been canceled.",
|
||||||
"FileNotFound": "File not found.",
|
"FileNotFound": "File not found.",
|
||||||
"FileReadError": "An error occurred while reading the file.",
|
"FileReadError": "An error occurred while reading the file.",
|
||||||
"DeleteUser": "Delete User",
|
"DeleteUser": "Delete User",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"PasswordMatchError": "Password and password confirmation must match.",
|
"PasswordMatchError": "Password and password confirmation must match.",
|
||||||
"OptionOff": "Off",
|
"OptionOff": "Off",
|
||||||
"OptionOn": "On",
|
"OptionOn": "On",
|
||||||
"OptionRelease": "Release",
|
"OptionRelease": "Official Release",
|
||||||
"OptionBeta": "Beta",
|
"OptionBeta": "Beta",
|
||||||
"OptionDev": "Dev",
|
"OptionDev": "Dev (Unstable)",
|
||||||
"UninstallPluginHeader": "Uninstall Plugin",
|
"UninstallPluginHeader": "Uninstall Plugin",
|
||||||
"UninstallPluginConfirmation": "Are you sure you wish to uninstall {0}?",
|
"UninstallPluginConfirmation": "Are you sure you wish to uninstall {0}?",
|
||||||
"NoPluginConfigurationMessage": "This plugin has nothing to configure.",
|
"NoPluginConfigurationMessage": "This plugin has nothing to configure.",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{"SettingsSaved":"Innstillinger lagret","AddUser":"Legg til bruker","Users":"Brukere","Delete":"Slett","Administrator":"Administrator","Password":"PAssord","DeleteImage":"Slett bilde","DeleteImageConfirmation":"Er du sikker p\u00e5 at du vil slette bildet?","FileReadCancelled":"Lesing av filen avbrutt","FileNotFound":"Fil ikke funnet","FileReadError":"Feil oppstod i det filen ble lest","DeleteUser":"Slett bruker","DeleteUserConfirmation":"Er du sikker p\u00e5 at du vil slette{0}?","PasswordResetHeader":"Resett passord","PasswordResetComplete":"Passordet har blitt resatt","PasswordResetConfirmation":"Er du sikker p\u00e5 at du vil resette passordet?","PasswordSaved":"Passord lagret","PasswordMatchError":"Passord og passord-verifiseringen m\u00e5 matche","OptionOff":"Av","OptionOn":"P\u00e5","OptionRelease":"Sluppet","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Avinstaller plugin","UninstallPluginConfirmation":"Are you sure you wish to uninstall {0}?","NoPluginConfigurationMessage":"Denne pluginn-en har intet \u00e5 konfigurere","NoPluginsInstalledMessage":"Du har ikke installert noen plugins enn\u00e5","BrowsePluginCatalogMessage":"Browse v\u00e5r plugin-katalog for \u00e5 se tilgjengelige plugins"}
|
|
@ -1 +1 @@
|
||||||
{"SettingsSaved":"Instellingen opgeslagen.","AddUser":"Gebruiker toevoegen","Users":"Gebruikers","Delete":"Verwijderen","Administrator":"Beheerder","Password":"Wachtwoord","DeleteImage":"Verwijder afbeelding","DeleteImageConfirmation":"Weet je zeker dat je deze afbeelding wilt verwijderen?","FileReadCancelled":"Het lezen van het bestand is geannuleerd","FileNotFound":"Bestand niet gevonden.","FileReadError":"Er is een fout opgetreden bij het lezen van het bestand.","DeleteUser":"Verwijder gebruiker","DeleteUserConfirmation":"Weet je zeker dat je {0} wilt verwijderen?","PasswordResetHeader":"Wachtwoord opnieuw instellen","PasswordResetComplete":"Het wachtwoord is opnieuw ingesteld.","PasswordResetConfirmation":"Weet je zeker dat je het wachtwoord opnieuw in wilt stellen?","PasswordSaved":"Wachtwoord opgeslagen.","PasswordMatchError":"Wachtwoord en wachtwoord bevestiging moeten hetzelfde zijn.","OptionOff":"Uit","OptionOn":"Aan","OptionRelease":"Release","OptionBeta":"Beta","OptionDev":"Ontwikkeling","UninstallPluginHeader":"Deinstalleer Plugin","UninstallPluginConfirmation":"Weet u zeker dat u {0} wilt deinstalleren?","NoPluginConfigurationMessage":"Deze plugin heeft niets in te stellen","NoPluginsInstalledMessage":"U heeft geen plugins geinstalleerd","BrowsePluginCatalogMessage":"Blader door de Plugincatalogus voor beschikbare plugins."}
|
{"SettingsSaved":"Instellingen opgeslagen.","AddUser":"Gebruiker toevoegen","Users":"Gebruikers","Delete":"Verwijderen","Administrator":"Beheerder","Password":"Wachtwoord","DeleteImage":"Verwijder afbeelding","DeleteImageConfirmation":"Weet je zeker dat je deze afbeelding wilt verwijderen?","FileReadCancelled":"Bestand lezen is geannuleerd.","FileNotFound":"Bestand niet gevonden.","FileReadError":"Er is een fout opgetreden bij het lezen van het bestand.","DeleteUser":"Verwijder gebruiker","DeleteUserConfirmation":"Weet je zeker dat je {0} wilt verwijderen?","PasswordResetHeader":"Wachtwoord opnieuw instellen","PasswordResetComplete":"Het wachtwoord is opnieuw ingesteld.","PasswordResetConfirmation":"Weet je zeker dat je het wachtwoord opnieuw in wilt stellen?","PasswordSaved":"Wachtwoord opgeslagen.","PasswordMatchError":"Wachtwoord en wachtwoord bevestiging moeten hetzelfde zijn.","OptionOff":"Uit","OptionOn":"Aan","OptionRelease":"Offici\u00eble Release","OptionBeta":"Beta","OptionDev":"Alpha (Onstabiel)","UninstallPluginHeader":"Plug-in de\u00efnstalleren","UninstallPluginConfirmation":"Weet u zeker dat u {0} wilt de\u00efnstalleren?","NoPluginConfigurationMessage":"Deze plug-in heeft niets in te stellen","NoPluginsInstalledMessage":"U heeft geen plug-ins ge\u00efnstalleerd","BrowsePluginCatalogMessage":"Bekijk de Plug-in catalogus voor beschikbare plug-ins."}
|
|
@ -1 +1 @@
|
||||||
{"SettingsSaved":"Prefer\u00eancias salvas.","AddUser":"Adicionar Usu\u00e1rio","Users":"Usu\u00e1rios","Delete":"Apagar","Administrator":"Administrador","Password":"Senha","DeleteImage":"Apagar Imagem","DeleteImageConfirmation":"Tem certeza que deseja apagar esta imagem?","FileReadCancelled":"A leitura do arquivo foi cancelada.","FileNotFound":"Arquivo n\u00e3o encontrado.","FileReadError":"Ocorreu um erro ao ler o arquivo.","DeleteUser":"Apagar Usu\u00e1rio","DeleteUserConfirmation":"Tem certeza que deseja apagar {0}?","PasswordResetHeader":"Redefinir Senha","PasswordResetComplete":"A senha foi redefinida.","PasswordResetConfirmation":"Deseja realmente redefinir a senha?","PasswordSaved":"Senha salva.","PasswordMatchError":"A senha e confirma\u00e7\u00e3o da senha devem conferir.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Release","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Desintalar Plugin","UninstallPluginConfirmation":"Deseja realmente desinstalar {0}?","NoPluginConfigurationMessage":"Este plugin n\u00e3o necessita configurar.","NoPluginsInstalledMessage":"N\u00e3o existem plugins instalados.","BrowsePluginCatalogMessage":"Navegue pelo cat\u00e1logo de plugins para ver os dispon\u00edveis."}
|
{"SettingsSaved":"Prefer\u00eancias salvas.","AddUser":"Adicionar Usu\u00e1rio","Users":"Usu\u00e1rios","Delete":"Apagar","Administrator":"Administrador","Password":"Senha","DeleteImage":"Apagar Imagem","DeleteImageConfirmation":"Tem certeza que deseja apagar esta imagem?","FileReadCancelled":"A leitura do arquivo foi cancelada.","FileNotFound":"Arquivo n\u00e3o encontrado.","FileReadError":"Ocorreu um erro ao ler o arquivo.","DeleteUser":"Apagar Usu\u00e1rio","DeleteUserConfirmation":"Tem certeza que deseja apagar {0}?","PasswordResetHeader":"Redefinir Senha","PasswordResetComplete":"A senha foi redefinida.","PasswordResetConfirmation":"Deseja realmente redefinir a senha?","PasswordSaved":"Senha salva.","PasswordMatchError":"A senha e confirma\u00e7\u00e3o da senha devem conferir.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Lan\u00e7amento Oficial","OptionBeta":"Beta","OptionDev":"Dev (Inst\u00e1vel)","UninstallPluginHeader":"Desintalar Plugin","UninstallPluginConfirmation":"Deseja realmente desinstalar {0}?","NoPluginConfigurationMessage":"Este plugin n\u00e3o necessita configurar.","NoPluginsInstalledMessage":"N\u00e3o existem plugins instalados.","BrowsePluginCatalogMessage":"Navegue pelo cat\u00e1logo de plugins para ver os dispon\u00edveis."}
|
|
@ -1 +1 @@
|
||||||
{"SettingsSaved":"Configura\u00e7\u00f5es guardadas.","AddUser":"Adicionar Utilizador","Users":"Utilizadores","Delete":"Apagar","Administrator":"Administrador","Password":"Senha","DeleteImage":"Apagar Imagem","DeleteImageConfirmation":"Tem a certeza que deseja apagar a imagem?","FileReadCancelled":"A leitura do ficheiro foi cancelada.","FileNotFound":"Ficheiro n\u00e3o encontrado.","FileReadError":"Ocorreu um erro ao ler o ficheiro.","DeleteUser":"Apagar Utilizador","DeleteUserConfirmation":"Tem a certeza que deseja apagar {0}?","PasswordResetHeader":"Redefinir Senha","PasswordResetComplete":"A senha foi redefinida.","PasswordResetConfirmation":"Tem a certeza que deseja redefinir a senha?","PasswordSaved":"Senha guardada.","PasswordMatchError":"A senha e a confirma\u00e7\u00e3o da senha devem coincidir.","OptionOff":"Desligado","OptionOn":"Ligado","OptionRelease":"Final","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Desinstalar extens\u00e3o","UninstallPluginConfirmation":"Tem a certeza que deseja desinstalar {0}?","NoPluginConfigurationMessage":"Esta extens\u00e3o n\u00e3o \u00e9 configur\u00e1vel.","NoPluginsInstalledMessage":"N\u00e3o tem extens\u00f5es instaladas.","BrowsePluginCatalogMessage":"Navegue o nosso cat\u00e1logo de extens\u00f5es, para ver as extens\u00f5es dispon\u00edveis."}
|
{"SettingsSaved":"Configura\u00e7\u00f5es guardadas.","AddUser":"Adicionar Utilizador","Users":"Utilizadores","Delete":"Apagar","Administrator":"Administrador","Password":"Senha","DeleteImage":"Apagar Imagem","DeleteImageConfirmation":"Tem a certeza que deseja apagar a imagem?","FileReadCancelled":"A leitura do ficheiro foi cancelada.","FileNotFound":"Ficheiro n\u00e3o encontrado.","FileReadError":"Ocorreu um erro ao ler o ficheiro.","DeleteUser":"Apagar Utilizador","DeleteUserConfirmation":"Tem a certeza que deseja apagar {0}?","PasswordResetHeader":"Redefinir Senha","PasswordResetComplete":"A senha foi redefinida.","PasswordResetConfirmation":"Tem a certeza que deseja redefinir a senha?","PasswordSaved":"Senha guardada.","PasswordMatchError":"A senha e a confirma\u00e7\u00e3o da senha devem coincidir.","OptionOff":"Desligado","OptionOn":"Ligado","OptionRelease":"Lan\u00e7amento Oficial","OptionBeta":"Beta","OptionDev":"Dev (Inst\u00e1vel)","UninstallPluginHeader":"Desinstalar extens\u00e3o","UninstallPluginConfirmation":"Tem a certeza que deseja desinstalar {0}?","NoPluginConfigurationMessage":"Esta extens\u00e3o n\u00e3o \u00e9 configur\u00e1vel.","NoPluginsInstalledMessage":"N\u00e3o tem extens\u00f5es instaladas.","BrowsePluginCatalogMessage":"Navegue o nosso cat\u00e1logo de extens\u00f5es, para ver as extens\u00f5es dispon\u00edveis."}
|
|
@ -1 +1 @@
|
||||||
{"SettingsSaved":"\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b","AddUser":"\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f","Users":"\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438","Delete":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c","Administrator":"\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440","Password":"\u041f\u0430\u0440\u043e\u043b\u044c","DeleteImage":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435","DeleteImageConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u044d\u0442\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435?","FileReadCancelled":"\u0427\u0442\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 \u0431\u044b\u043b\u043e \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043e","FileNotFound":"\u0424\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d","FileReadError":"\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430","DeleteUser":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f","DeleteUserConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?","PasswordResetHeader":"\u0421\u0431\u0440\u043e\u0441 \u043f\u0430\u0440\u043e\u043b\u044f","PasswordResetComplete":"\u041f\u0430\u0440\u043e\u043b\u044c \u0431\u044b\u043b \u0441\u0431\u0440\u043e\u0448\u0435\u043d","PasswordResetConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c?","PasswordSaved":"\u041f\u0430\u0440\u043e\u043b\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d","PasswordMatchError":"\u041f\u043e\u043b\u044f \u041f\u0430\u0440\u043e\u043b\u044c \u0438 \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c","OptionOff":"\u0412\u044b\u043a\u043b","OptionOn":"\u0412\u043a\u043b","OptionRelease":"\u0412\u044b\u043f\u0443\u0441\u043a","OptionBeta":"\u0411\u0435\u0442\u0430","OptionDev":"\u0420\u0430\u0437\u0440\u0430\u0431","UninstallPluginHeader":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043b\u0430\u0433\u0438\u043d","UninstallPluginConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?","NoPluginConfigurationMessage":"\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043d\u0435\u0447\u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c.","NoPluginsInstalledMessage":"\u0423 \u0412\u0430\u0441 \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430.","BrowsePluginCatalogMessage":"\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u043d\u0430\u0448\u0438\u043c \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043e\u043c \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432."}
|
{"SettingsSaved":"\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b","AddUser":"\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f","Users":"\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438","Delete":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c","Administrator":"\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440","Password":"\u041f\u0430\u0440\u043e\u043b\u044c","DeleteImage":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435","DeleteImageConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u043e\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435?","FileReadCancelled":"\u0427\u0442\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 \u0431\u044b\u043b\u043e \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043e","FileNotFound":"\u0424\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d","FileReadError":"\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430","DeleteUser":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f","DeleteUserConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?","PasswordResetHeader":"\u0421\u0431\u0440\u043e\u0441 \u043f\u0430\u0440\u043e\u043b\u044f","PasswordResetComplete":"\u041f\u0430\u0440\u043e\u043b\u044c \u0431\u044b\u043b \u0441\u0431\u0440\u043e\u0448\u0435\u043d","PasswordResetConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c?","PasswordSaved":"\u041f\u0430\u0440\u043e\u043b\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d","PasswordMatchError":"\u041f\u043e\u043b\u044f \u041f\u0430\u0440\u043e\u043b\u044c \u0438 \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c","OptionOff":"\u0412\u044b\u043a\u043b","OptionOn":"\u0412\u043a\u043b","OptionRelease":"\u041e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0432\u044b\u043f\u0443\u0441\u043a","OptionBeta":"\u0411\u0435\u0442\u0430","OptionDev":"\u0420\u0430\u0437\u0440\u0430\u0431 (\u043d\u0435\u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u043e)","UninstallPluginHeader":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043b\u0430\u0433\u0438\u043d","UninstallPluginConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?","NoPluginConfigurationMessage":"\u0414\u043b\u044f \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043d\u0435\u0447\u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c.","NoPluginsInstalledMessage":"\u0423 \u0412\u0430\u0441 \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430.","BrowsePluginCatalogMessage":"\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u043d\u0430\u0448\u0438\u043c \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043e\u043c \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432."}
|
|
@ -1 +1 @@
|
||||||
{"SettingsSaved":"\u8a2d\u7f6e\u5df2\u4fdd\u5b58","AddUser":"Add User","Users":"\u7528\u6236","Delete":"\u522a\u9664","Administrator":"\u7ba1\u7406\u54e1","Password":"\u5bc6\u78bc","DeleteImage":"\u522a\u9664\u5716\u50cf","DeleteImageConfirmation":"\u4f60\u78ba\u5b9a\u8981\u522a\u9664\u9019\u5f35\u5716\u7247\uff1f","FileReadCancelled":"The file read has been cancelled.","FileNotFound":"File not found.","FileReadError":"An error occurred while reading the file.","DeleteUser":"\u522a\u9664\u7528\u6236","DeleteUserConfirmation":"Are you sure you wish to delete {0}?","PasswordResetHeader":"\u91cd\u8a2d\u5bc6\u78bc","PasswordResetComplete":"\u5bc6\u78bc\u5df2\u91cd\u8a2d","PasswordResetConfirmation":"\u4f60\u78ba\u5b9a\u8981\u91cd\u8a2d\u5bc6\u78bc\uff1f","PasswordSaved":"\u5bc6\u78bc\u5df2\u4fdd\u5b58\u3002","PasswordMatchError":"\u5bc6\u78bc\u548c\u78ba\u8a8d\u5bc6\u78bc\u5fc5\u9808\u4e00\u81f4\u3002","OptionOff":"Off","OptionOn":"On","OptionRelease":"Release","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Uninstall Plugin","UninstallPluginConfirmation":"Are you sure you wish to uninstall {0}?","NoPluginConfigurationMessage":"This plugin has nothing to configure.","NoPluginsInstalledMessage":"You have no plugins installed.","BrowsePluginCatalogMessage":"Browse our plugin catalog to view available plugins."}
|
{"SettingsSaved":"\u8a2d\u7f6e\u5df2\u4fdd\u5b58\u3002","AddUser":"\u6dfb\u52a0\u7528\u6236","Users":"\u7528\u6236","Delete":"\u522a\u9664","Administrator":"\u7ba1\u7406\u54e1","Password":"\u5bc6\u78bc","DeleteImage":"\u522a\u9664\u5716\u50cf","DeleteImageConfirmation":"\u4f60\u78ba\u5b9a\u8981\u522a\u9664\u9019\u5f35\u5716\u50cf\uff1f","FileReadCancelled":"\u6a94\u6848\u8b80\u53d6\u5df2\u88ab\u53d6\u6d88\u3002","FileNotFound":"\u672a\u627e\u5230\u6a94\u6848\u3002","FileReadError":"\u5728\u8b80\u53d6\u6a94\u6848\u6642\u767c\u751f\u932f\u8aa4\u3002","DeleteUser":"\u522a\u9664\u7528\u6236","DeleteUserConfirmation":"\u4f60\u78ba\u5b9a\u8981\u522a\u9664{0}\uff1f","PasswordResetHeader":"\u91cd\u8a2d\u5bc6\u78bc","PasswordResetComplete":"\u5bc6\u78bc\u5df2\u91cd\u8a2d","PasswordResetConfirmation":"\u4f60\u78ba\u5b9a\u8981\u91cd\u8a2d\u5bc6\u78bc\uff1f","PasswordSaved":"\u5bc6\u78bc\u5df2\u4fdd\u5b58\u3002","PasswordMatchError":"\u5bc6\u78bc\u548c\u5bc6\u78bc\u78ba\u8a8d\u5fc5\u9808\u4e00\u81f4\u3002","OptionOff":"\u95dc\u9589","OptionOn":"\u958b\u555f","OptionRelease":"\u6b63\u5f0f\u7248\u672c","OptionBeta":"\u516c\u6e2c\u7248\u672c","OptionDev":"\u958b\u767c\u7248\u672c","UninstallPluginHeader":"\u5378\u8f09\u63d2\u4ef6","UninstallPluginConfirmation":"\u4f60\u78ba\u5b9a\u8981\u5378\u8f09{0}\uff1f","NoPluginConfigurationMessage":"\u9019\u500b\u63d2\u4ef6\u6c92\u6709\u8a2d\u5b9a\u9078\u9805\u3002","NoPluginsInstalledMessage":"\u4f60\u6c92\u6709\u5b89\u88dd\u63d2\u4ef6\u3002","BrowsePluginCatalogMessage":"\u700f\u89bd\u6211\u5011\u7684\u63d2\u4ef6\u76ee\u9304\u4f86\u67e5\u770b\u53ef\u7528\u7684\u63d2\u4ef6\u3002"}
|
|
@ -333,13 +333,17 @@ namespace MediaBrowser.Server.Implementations.Localization
|
||||||
{
|
{
|
||||||
return new List<LocalizatonOption>
|
return new List<LocalizatonOption>
|
||||||
{
|
{
|
||||||
|
new LocalizatonOption{ Name="Arabic", Value="ar"},
|
||||||
|
new LocalizatonOption{ Name="English (United Kingdom)", Value="en-GB"},
|
||||||
new LocalizatonOption{ Name="English (United States)", Value="en-us"},
|
new LocalizatonOption{ Name="English (United States)", Value="en-us"},
|
||||||
new LocalizatonOption{ Name="Chinese Traditional", Value="zh-TW"},
|
new LocalizatonOption{ Name="Chinese Traditional", Value="zh-TW"},
|
||||||
new LocalizatonOption{ Name="Dutch", Value="nl"},
|
new LocalizatonOption{ Name="Dutch", Value="nl"},
|
||||||
new LocalizatonOption{ Name="French", Value="fr"},
|
new LocalizatonOption{ Name="French", Value="fr"},
|
||||||
new LocalizatonOption{ Name="German", Value="de"},
|
new LocalizatonOption{ Name="German", Value="de"},
|
||||||
|
new LocalizatonOption{ Name="Greek", Value="el"},
|
||||||
new LocalizatonOption{ Name="Hebrew", Value="he"},
|
new LocalizatonOption{ Name="Hebrew", Value="he"},
|
||||||
new LocalizatonOption{ Name="Italian", Value="it"},
|
new LocalizatonOption{ Name="Italian", Value="it"},
|
||||||
|
new LocalizatonOption{ Name="Norwegian Bokmål", Value="nb"},
|
||||||
new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"},
|
new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"},
|
||||||
new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"},
|
new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"},
|
||||||
new LocalizatonOption{ Name="Russian", Value="ru"},
|
new LocalizatonOption{ Name="Russian", Value="ru"},
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -134,7 +134,7 @@
|
||||||
"OptionVideoBitrate": "Video Bitrate",
|
"OptionVideoBitrate": "Video Bitrate",
|
||||||
"OptionResumable": "Resumable",
|
"OptionResumable": "Resumable",
|
||||||
"ScheduledTasksHelp": "Click a task to adjust its schedule.",
|
"ScheduledTasksHelp": "Click a task to adjust its schedule.",
|
||||||
"ScheduledTasksTitle": "ScheduledTasks",
|
"ScheduledTasksTitle": "Scheduled Tasks",
|
||||||
"TabMyPlugins": "My Plugins",
|
"TabMyPlugins": "My Plugins",
|
||||||
"TabCatalog": "Catalog",
|
"TabCatalog": "Catalog",
|
||||||
"TabUpdates": "Updates",
|
"TabUpdates": "Updates",
|
||||||
|
@ -207,5 +207,100 @@
|
||||||
"OptionAllowBrowsingLiveTv": "Allow browsing of live tv",
|
"OptionAllowBrowsingLiveTv": "Allow browsing of live tv",
|
||||||
"OptionAllowDeleteLibraryContent": "Allow this user to delete library content",
|
"OptionAllowDeleteLibraryContent": "Allow this user to delete library content",
|
||||||
"OptionAllowManageLiveTv": "Allow management of live tv recordings",
|
"OptionAllowManageLiveTv": "Allow management of live tv recordings",
|
||||||
"OptionAllowRemoteControlOthers": "Allow this user to remote control other users"
|
"OptionAllowRemoteControlOthers": "Allow this user to remote control other users",
|
||||||
|
"OptionMissingTmdbId": "Missing Tmdb Id",
|
||||||
|
"OptionIsHD": "HD",
|
||||||
|
"OptionIsSD": "SD",
|
||||||
|
"OptionMetascore": "Metascore",
|
||||||
|
"OptionImdbRating": "IMDb rating",
|
||||||
|
"ButtonSelect": "Select",
|
||||||
|
"ButtonGroupVersions": "Group Versions",
|
||||||
|
"PismoMessage": "Utilizing Pismo File Mount through a donated license.",
|
||||||
|
"PleaseSupportOtherProduces": "Please support other free products we utilize:",
|
||||||
|
"VersionNumber": "Version {0}",
|
||||||
|
"TabPaths": "Paths",
|
||||||
|
"TabServer": "Server",
|
||||||
|
"TabTranscoding": "Transcoding",
|
||||||
|
"TitleAdvanced": "Advanced",
|
||||||
|
"LabelAutomaticUpdateLevel": "Automatic update level",
|
||||||
|
"OptionRelease": "Official Release",
|
||||||
|
"OptionBeta": "Beta",
|
||||||
|
"OptionDev": "Dev (Unstable)",
|
||||||
|
"LabelAllowServerAutoRestart": "Allow the server to restart automatically to apply updates",
|
||||||
|
"LabelAllowServerAutoRestartHelp": "The server will only restart during idle periods, when no users are active.",
|
||||||
|
"LabelEnableDebugLogging": "Enable debug logging",
|
||||||
|
"LabelRunServerAtStartup": "Run server at startup",
|
||||||
|
"LabelRunServerAtStartupHelp": "This will start the tray icon on windows startup. To start the windows service, uncheck this and run the service from the windows control panel. Please note that you cannot run both at the same time, so you will need to exit the tray icon before starting the service.",
|
||||||
|
"ButtonSelectDirectory": "Select Directory",
|
||||||
|
"LabelCustomPaths": "Specify custom paths where desired. Leave fields empty to use the defaults.",
|
||||||
|
"LabelCachePath": "Cache path:",
|
||||||
|
"LabelCachePathHelp": "This folder contains server cache files, such as images.",
|
||||||
|
"LabelImagesByNamePath": "Images by name path:",
|
||||||
|
"LabelImagesByNamePathHelp": "This folder contains actor, artist, genre and studio images.",
|
||||||
|
"LabelMetadataPath": "Metadata path:",
|
||||||
|
"LabelMetadataPathHelp": "This location contains downloaded artwork and metadata that is not configured to be stored in media folders.",
|
||||||
|
"LabelTranscodingTempPath": "Transcoding temporary path:",
|
||||||
|
"LabelTranscodingTempPathHelp": "This folder contains working files used by the transcoder.",
|
||||||
|
"TabBasics": "Basics",
|
||||||
|
"TabTV": "TV",
|
||||||
|
"TabGames": "Games",
|
||||||
|
"TabMusic": "Music",
|
||||||
|
"TabOthers": "Others",
|
||||||
|
"HeaderExtractChapterImagesFor": "Extract chapter images for:",
|
||||||
|
"OptionMovies": "Movies",
|
||||||
|
"OptionEpisodes": "Episodes",
|
||||||
|
"OptionOtherVideos": "Other Videos",
|
||||||
|
"TitleMetadata": "Metadata",
|
||||||
|
"LabelAutomaticUpdatesFanart": "Enable automatic updates from FanArt.tv",
|
||||||
|
"LabelAutomaticUpdatesTmdb": "Enable automatic updates from TheMovieDB.org",
|
||||||
|
"LabelAutomaticUpdatesTvdb": "Enable automatic updates from TheTVDB.com",
|
||||||
|
"LabelAutomaticUpdatesFanartHelp": "If enabled, new images will be downloaded automatically as they're added to fanart.tv. Existing images will not be replaced.",
|
||||||
|
"LabelAutomaticUpdatesTmdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheMovieDB.org. Existing images will not be replaced.",
|
||||||
|
"LabelAutomaticUpdatesTvdbHelp": "If enabled, new images will be downloaded automatically as they're added to TheTVDB.com. Existing images will not be replaced.",
|
||||||
|
"ExtractChapterImagesHelp": "Extracting chapter images will allow clients to display graphical scene selection menus. The process can be slow, cpu-intensive and may require several gigabytes of space. It runs as a nightly scheduled task at 4am, although this is configurable in the scheduled tasks area. It is not recommended to run this task during peak usage hours.",
|
||||||
|
"LabelMetadataDownloadLanguage": "Preferred language:",
|
||||||
|
"ButtonAutoScroll": "Auto-scroll",
|
||||||
|
"LabelImageSavingConvention": "Image saving convention:",
|
||||||
|
"LabelImageSavingConventionHelp": "Media Browser recognizes images from most major media applications. Choosing your downloading convention is useful if you also use other products.",
|
||||||
|
"OptionImageSavingCompatible": "Compatible - MB3/Plex/Xbmc",
|
||||||
|
"OptionImageSavingStandard": "Standard - MB3/MB2",
|
||||||
|
"ButtonSignIn": "Sign In",
|
||||||
|
"TitleSignIn": "Sign In",
|
||||||
|
"HeaderPleaseSignIn": "Please sign in",
|
||||||
|
"LabelUser": "User:",
|
||||||
|
"LabelPassword": "Password:",
|
||||||
|
"ButtonManualLogin": "Manual Login:",
|
||||||
|
"PasswordLocalhostMessage": "Passwords are not required when logging in from localhost.",
|
||||||
|
"TabGuide": "Guide",
|
||||||
|
"TabChannels": "Channels",
|
||||||
|
"HeaderChannels": "Channels",
|
||||||
|
"TabRecordings": "Recordings",
|
||||||
|
"TabScheduled": "Scheduled",
|
||||||
|
"TabSeries": "Series",
|
||||||
|
"ButtonCancelRecording": "Cancel Recording",
|
||||||
|
"HeaderPrePostPadding": "Pre/Post Padding",
|
||||||
|
"LabelPrePaddingMinutes": "Pre-padding minutes:",
|
||||||
|
"OptionPrePaddingRequired": "Pre-padding is required in order to record.",
|
||||||
|
"LabelPostPaddingMinutes": "Post-padding minutes:",
|
||||||
|
"OptionPostPaddingRequired": "Post-padding is required in order to record.",
|
||||||
|
"HeaderWhatsOnTV": "What's On",
|
||||||
|
"HeaderUpcomingTV": "Upcoming TV",
|
||||||
|
"TabStatus": "Status",
|
||||||
|
"TabSettings": "Settings",
|
||||||
|
"ButtonRefreshGuideData": "Refresh Guide Data",
|
||||||
|
"OptionPriority": "Priority",
|
||||||
|
"OptionRecordOnAllChannels": "Record program on all channels",
|
||||||
|
"OptionRecordAnytime": "Record program at any time",
|
||||||
|
"OptionRecordOnlyNewEpisodes": "Record only new episodes",
|
||||||
|
"HeaderDays": "Days",
|
||||||
|
"HeaderActiveRecordings": "Active Recordings",
|
||||||
|
"HeaderLatestRecordings": "Latest Recordings",
|
||||||
|
"HeaderAllRecordings": "All Recordings",
|
||||||
|
"ButtonPlay": "Play",
|
||||||
|
"ButtonEdit": "Edit",
|
||||||
|
"ButtonRecord": "Record",
|
||||||
|
"ButtonDelete": "Delete",
|
||||||
|
"OptionRecordSeries": "Record Series",
|
||||||
|
"HeaderDetails": "Details",
|
||||||
|
"ButtonCancelRecording": "Cancel Recording"
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
|
@ -307,6 +307,14 @@
|
||||||
<EmbeddedResource Include="Localization\Server\it.json" />
|
<EmbeddedResource Include="Localization\Server\it.json" />
|
||||||
<EmbeddedResource Include="Localization\Server\es_MX.json" />
|
<EmbeddedResource Include="Localization\Server\es_MX.json" />
|
||||||
<EmbeddedResource Include="Localization\JavaScript\es_MX.json" />
|
<EmbeddedResource Include="Localization\JavaScript\es_MX.json" />
|
||||||
|
<EmbeddedResource Include="Localization\JavaScript\ar.json" />
|
||||||
|
<EmbeddedResource Include="Localization\JavaScript\he.json" />
|
||||||
|
<EmbeddedResource Include="Localization\JavaScript\nb.json" />
|
||||||
|
<EmbeddedResource Include="Localization\Server\ar.json" />
|
||||||
|
<EmbeddedResource Include="Localization\Server\el.json" />
|
||||||
|
<EmbeddedResource Include="Localization\Server\nb.json" />
|
||||||
|
<EmbeddedResource Include="Localization\JavaScript\el.json" />
|
||||||
|
<EmbeddedResource Include="Localization\Server\en_GB.json" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -41,6 +41,11 @@ namespace MediaBrowser.Server.Implementations.Roku
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken)
|
public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return SendCommand(new WebSocketMessage<MessageCommand>
|
return SendCommand(new WebSocketMessage<MessageCommand>
|
||||||
|
@ -81,11 +86,10 @@ namespace MediaBrowser.Server.Implementations.Roku
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Task _cachedTask = Task.FromResult(true);
|
|
||||||
public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
|
public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Roku probably won't care about this
|
// Roku probably won't care about this
|
||||||
return _cachedTask;
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
|
public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
|
||||||
|
@ -101,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.Roku
|
||||||
public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
|
public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Roku probably won't care about this
|
// Roku probably won't care about this
|
||||||
return _cachedTask;
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendServerShutdownNotification(CancellationToken cancellationToken)
|
public Task SendServerShutdownNotification(CancellationToken cancellationToken)
|
||||||
|
@ -137,7 +141,6 @@ namespace MediaBrowser.Server.Implementations.Roku
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return SendCommand(new WebSocketMessage<GeneralCommand>
|
return SendCommand(new WebSocketMessage<GeneralCommand>
|
||||||
|
|
|
@ -157,11 +157,10 @@ namespace MediaBrowser.Server.Implementations.ServerManager
|
||||||
var info = new WebSocketMessageInfo
|
var info = new WebSocketMessageInfo
|
||||||
{
|
{
|
||||||
MessageType = stub.MessageType,
|
MessageType = stub.MessageType,
|
||||||
Data = stub.Data == null ? null : stub.Data.ToString()
|
Data = stub.Data == null ? null : stub.Data.ToString(),
|
||||||
|
Connection = this
|
||||||
};
|
};
|
||||||
|
|
||||||
info.Connection = this;
|
|
||||||
|
|
||||||
OnReceive(info);
|
OnReceive(info);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
using MediaBrowser.Common.Events;
|
using System.IO;
|
||||||
|
using MediaBrowser.Common.Events;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Controller.Drawing;
|
||||||
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
@ -42,6 +46,8 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IMusicManager _musicManager;
|
private readonly IMusicManager _musicManager;
|
||||||
|
private readonly IDtoService _dtoService;
|
||||||
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the configuration manager.
|
/// Gets or sets the configuration manager.
|
||||||
|
@ -68,6 +74,10 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<PlaybackStopEventArgs> PlaybackStopped;
|
public event EventHandler<PlaybackStopEventArgs> PlaybackStopped;
|
||||||
|
|
||||||
|
public event EventHandler<SessionEventArgs> SessionStarted;
|
||||||
|
|
||||||
|
public event EventHandler<SessionEventArgs> SessionEnded;
|
||||||
|
|
||||||
private IEnumerable<ISessionControllerFactory> _sessionFactories = new List<ISessionControllerFactory>();
|
private IEnumerable<ISessionControllerFactory> _sessionFactories = new List<ISessionControllerFactory>();
|
||||||
|
|
||||||
private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
||||||
|
@ -80,7 +90,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
/// <param name="logger">The logger.</param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="userRepository">The user repository.</param>
|
/// <param name="userRepository">The user repository.</param>
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager)
|
public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor)
|
||||||
{
|
{
|
||||||
_userDataRepository = userDataRepository;
|
_userDataRepository = userDataRepository;
|
||||||
_configurationManager = configurationManager;
|
_configurationManager = configurationManager;
|
||||||
|
@ -89,6 +99,8 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_musicManager = musicManager;
|
_musicManager = musicManager;
|
||||||
|
_dtoService = dtoService;
|
||||||
|
_imageProcessor = imageProcessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -109,6 +121,47 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
get { return _activeConnections.Values.OrderByDescending(c => c.LastActivityDate).ToList(); }
|
get { return _activeConnections.Values.OrderByDescending(c => c.LastActivityDate).ToList(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnSessionStarted(SessionInfo info)
|
||||||
|
{
|
||||||
|
EventHelper.QueueEventIfNotNull(SessionStarted, this, new SessionEventArgs
|
||||||
|
{
|
||||||
|
SessionInfo = info
|
||||||
|
|
||||||
|
}, _logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnSessionEnded(SessionInfo info)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SendSessionEndedNotification(info, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error in SendSessionEndedNotification", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventHelper.QueueEventIfNotNull(SessionEnded, this, new SessionEventArgs
|
||||||
|
{
|
||||||
|
SessionInfo = info
|
||||||
|
|
||||||
|
}, _logger);
|
||||||
|
|
||||||
|
var disposable = info.SessionController as IDisposable;
|
||||||
|
|
||||||
|
if (disposable != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error disposing session controller", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Logs the user activity.
|
/// Logs the user activity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -194,19 +247,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
|
|
||||||
if (_activeConnections.TryRemove(key, out removed))
|
if (_activeConnections.TryRemove(key, out removed))
|
||||||
{
|
{
|
||||||
var disposable = removed.SessionController as IDisposable;
|
OnSessionEnded(removed);
|
||||||
|
|
||||||
if (disposable != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
disposable.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error disposing session controller", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
@ -222,11 +263,9 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
/// <param name="item">The item.</param>
|
/// <param name="item">The item.</param>
|
||||||
/// <param name="mediaSourceId">The media version identifier.</param>
|
/// <param name="mediaSourceId">The media version identifier.</param>
|
||||||
/// <param name="isPaused">if set to <c>true</c> [is paused].</param>
|
/// <param name="isPaused">if set to <c>true</c> [is paused].</param>
|
||||||
/// <param name="isMuted">if set to <c>true</c> [is muted].</param>
|
|
||||||
/// <param name="currentPositionTicks">The current position ticks.</param>
|
/// <param name="currentPositionTicks">The current position ticks.</param>
|
||||||
private void UpdateNowPlayingItem(SessionInfo session, BaseItem item, string mediaSourceId, bool isPaused, bool isMuted, long? currentPositionTicks = null)
|
private void UpdateNowPlayingItem(SessionInfo session, BaseItem item, string mediaSourceId, bool isPaused, long? currentPositionTicks = null)
|
||||||
{
|
{
|
||||||
session.IsMuted = isMuted;
|
|
||||||
session.IsPaused = isPaused;
|
session.IsPaused = isPaused;
|
||||||
session.NowPlayingPositionTicks = currentPositionTicks;
|
session.NowPlayingPositionTicks = currentPositionTicks;
|
||||||
session.NowPlayingItem = item;
|
session.NowPlayingItem = item;
|
||||||
|
@ -291,12 +330,19 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var connection = _activeConnections.GetOrAdd(key, keyName => new SessionInfo
|
var connection = _activeConnections.GetOrAdd(key, keyName =>
|
||||||
{
|
{
|
||||||
Client = clientType,
|
var sessionInfo = new SessionInfo
|
||||||
DeviceId = deviceId,
|
{
|
||||||
ApplicationVersion = appVersion,
|
Client = clientType,
|
||||||
Id = Guid.NewGuid()
|
DeviceId = deviceId,
|
||||||
|
ApplicationVersion = appVersion,
|
||||||
|
Id = Guid.NewGuid()
|
||||||
|
};
|
||||||
|
|
||||||
|
OnSessionStarted(sessionInfo);
|
||||||
|
|
||||||
|
return sessionInfo;
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.DeviceName = deviceName;
|
connection.DeviceName = deviceName;
|
||||||
|
@ -372,11 +418,14 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
|
|
||||||
var mediaSourceId = GetMediaSourceId(item, info.MediaSourceId);
|
var mediaSourceId = GetMediaSourceId(item, info.MediaSourceId);
|
||||||
|
|
||||||
UpdateNowPlayingItem(session, item, mediaSourceId, false, false);
|
UpdateNowPlayingItem(session, item, mediaSourceId, false);
|
||||||
|
|
||||||
session.CanSeek = info.CanSeek;
|
session.CanSeek = info.CanSeek;
|
||||||
session.QueueableMediaTypes = info.QueueableMediaTypes;
|
session.QueueableMediaTypes = info.QueueableMediaTypes;
|
||||||
|
|
||||||
|
session.NowPlayingAudioStreamIndex = info.AudioStreamIndex;
|
||||||
|
session.NowPlayingSubtitleStreamIndex = info.SubtitleStreamIndex;
|
||||||
|
|
||||||
var key = item.GetUserDataKey();
|
var key = item.GetUserDataKey();
|
||||||
|
|
||||||
var users = GetUsers(session);
|
var users = GetUsers(session);
|
||||||
|
@ -442,7 +491,12 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
|
|
||||||
var mediaSourceId = GetMediaSourceId(info.Item, info.MediaSourceId);
|
var mediaSourceId = GetMediaSourceId(info.Item, info.MediaSourceId);
|
||||||
|
|
||||||
UpdateNowPlayingItem(session, info.Item, mediaSourceId, info.IsPaused, info.IsMuted, info.PositionTicks);
|
UpdateNowPlayingItem(session, info.Item, mediaSourceId, info.IsPaused, info.PositionTicks);
|
||||||
|
|
||||||
|
session.IsMuted = info.IsMuted;
|
||||||
|
session.VolumeLevel = info.VolumeLevel;
|
||||||
|
session.NowPlayingAudioStreamIndex = info.AudioStreamIndex;
|
||||||
|
session.NowPlayingSubtitleStreamIndex = info.SubtitleStreamIndex;
|
||||||
|
|
||||||
var key = info.Item.GetUserDataKey();
|
var key = info.Item.GetUserDataKey();
|
||||||
|
|
||||||
|
@ -919,6 +973,27 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Task SendSessionEndedNotification(SessionInfo sessionInfo, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList();
|
||||||
|
var dto = GetSessionInfoDto(sessionInfo);
|
||||||
|
|
||||||
|
var tasks = sessions.Select(session => Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await session.SessionController.SendSessionEndedNotification(dto, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error in SendSessionEndedNotification.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
}, cancellationToken));
|
||||||
|
|
||||||
|
return Task.WhenAll(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the additional user.
|
/// Adds the additional user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1017,5 +1092,147 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
session.PlayableMediaTypes = capabilities.PlayableMediaTypes;
|
session.PlayableMediaTypes = capabilities.PlayableMediaTypes;
|
||||||
session.SupportedCommands = capabilities.SupportedCommands;
|
session.SupportedCommands = capabilities.SupportedCommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SessionInfoDto GetSessionInfoDto(SessionInfo session)
|
||||||
|
{
|
||||||
|
var dto = new SessionInfoDto
|
||||||
|
{
|
||||||
|
Client = session.Client,
|
||||||
|
DeviceId = session.DeviceId,
|
||||||
|
DeviceName = session.DeviceName,
|
||||||
|
Id = session.Id.ToString("N"),
|
||||||
|
LastActivityDate = session.LastActivityDate,
|
||||||
|
NowPlayingPositionTicks = session.NowPlayingPositionTicks,
|
||||||
|
SupportsRemoteControl = session.SupportsRemoteControl,
|
||||||
|
IsPaused = session.IsPaused,
|
||||||
|
IsMuted = session.IsMuted,
|
||||||
|
NowViewingContext = session.NowViewingContext,
|
||||||
|
NowViewingItemId = session.NowViewingItemId,
|
||||||
|
NowViewingItemName = session.NowViewingItemName,
|
||||||
|
NowViewingItemType = session.NowViewingItemType,
|
||||||
|
ApplicationVersion = session.ApplicationVersion,
|
||||||
|
CanSeek = session.CanSeek,
|
||||||
|
QueueableMediaTypes = session.QueueableMediaTypes,
|
||||||
|
PlayableMediaTypes = session.PlayableMediaTypes,
|
||||||
|
RemoteEndPoint = session.RemoteEndPoint,
|
||||||
|
AdditionalUsers = session.AdditionalUsers,
|
||||||
|
SupportedCommands = session.SupportedCommands,
|
||||||
|
NowPlayingAudioStreamIndex = session.NowPlayingAudioStreamIndex,
|
||||||
|
NowPlayingSubtitleStreamIndex = session.NowPlayingSubtitleStreamIndex,
|
||||||
|
UserName = session.UserName,
|
||||||
|
VolumeLevel = session.VolumeLevel
|
||||||
|
};
|
||||||
|
|
||||||
|
if (session.NowPlayingItem != null)
|
||||||
|
{
|
||||||
|
dto.NowPlayingItem = GetNowPlayingInfo(session.NowPlayingItem, session.NowPlayingMediaSourceId, session.NowPlayingRunTimeTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.UserId.HasValue)
|
||||||
|
{
|
||||||
|
dto.UserId = session.UserId.Value.ToString("N");
|
||||||
|
}
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a BaseItem to a BaseItemInfo
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <param name="mediaSourceId">The media version identifier.</param>
|
||||||
|
/// <param name="nowPlayingRuntimeTicks">The now playing runtime ticks.</param>
|
||||||
|
/// <returns>BaseItemInfo.</returns>
|
||||||
|
/// <exception cref="System.ArgumentNullException">item</exception>
|
||||||
|
private BaseItemInfo GetNowPlayingInfo(BaseItem item, string mediaSourceId, long? nowPlayingRuntimeTicks)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("item");
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = new BaseItemInfo
|
||||||
|
{
|
||||||
|
Id = GetDtoId(item),
|
||||||
|
Name = item.Name,
|
||||||
|
MediaType = item.MediaType,
|
||||||
|
Type = item.GetClientTypeName(),
|
||||||
|
RunTimeTicks = nowPlayingRuntimeTicks,
|
||||||
|
MediaSourceId = mediaSourceId
|
||||||
|
};
|
||||||
|
|
||||||
|
info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary);
|
||||||
|
|
||||||
|
var backropItem = item.HasImage(ImageType.Backdrop) ? item : null;
|
||||||
|
|
||||||
|
var thumbItem = item.HasImage(ImageType.Thumb) ? item : null;
|
||||||
|
|
||||||
|
if (thumbItem == null)
|
||||||
|
{
|
||||||
|
var episode = item as Episode;
|
||||||
|
|
||||||
|
if (episode != null)
|
||||||
|
{
|
||||||
|
var series = episode.Series;
|
||||||
|
|
||||||
|
if (series != null && series.HasImage(ImageType.Thumb))
|
||||||
|
{
|
||||||
|
thumbItem = series;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backropItem == null)
|
||||||
|
{
|
||||||
|
var episode = item as Episode;
|
||||||
|
|
||||||
|
if (episode != null)
|
||||||
|
{
|
||||||
|
var series = episode.Series;
|
||||||
|
|
||||||
|
if (series != null && series.HasImage(ImageType.Backdrop))
|
||||||
|
{
|
||||||
|
backropItem = series;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbItem == null)
|
||||||
|
{
|
||||||
|
thumbItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Thumb));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbItem != null)
|
||||||
|
{
|
||||||
|
info.ThumbImageTag = GetImageCacheTag(thumbItem, ImageType.Thumb);
|
||||||
|
info.ThumbItemId = GetDtoId(thumbItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbItem != null)
|
||||||
|
{
|
||||||
|
info.BackdropImageTag = GetImageCacheTag(backropItem, ImageType.Backdrop);
|
||||||
|
info.BackdropItemId = GetDtoId(backropItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Guid? GetImageCacheTag(BaseItem item, ImageType type)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _imageProcessor.GetImageCacheTag(item, type);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error getting {0} image info", ex, type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDtoId(BaseItem item)
|
||||||
|
{
|
||||||
|
return _dtoService.GetDtoId(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using MediaBrowser.Common.Net;
|
using System.Globalization;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
@ -187,6 +188,8 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reports the playback start.
|
/// Reports the playback start.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -228,6 +231,16 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
info.MediaSourceId = vals[3];
|
info.MediaSourceId = vals[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vals.Length > 4 && !string.IsNullOrWhiteSpace(vals[4]))
|
||||||
|
{
|
||||||
|
info.AudioStreamIndex = int.Parse(vals[4], _usCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vals.Length > 5 && !string.IsNullOrWhiteSpace(vals[5]))
|
||||||
|
{
|
||||||
|
info.SubtitleStreamIndex = int.Parse(vals[5], _usCulture);
|
||||||
|
}
|
||||||
|
|
||||||
_sessionManager.OnPlaybackStart(info);
|
_sessionManager.OnPlaybackStart(info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,6 +288,21 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
info.MediaSourceId = vals[4];
|
info.MediaSourceId = vals[4];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vals.Length > 5 && !string.IsNullOrWhiteSpace(vals[5]))
|
||||||
|
{
|
||||||
|
info.VolumeLevel = int.Parse(vals[5], _usCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vals.Length > 5 && !string.IsNullOrWhiteSpace(vals[6]))
|
||||||
|
{
|
||||||
|
info.AudioStreamIndex = int.Parse(vals[6], _usCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vals.Length > 7 && !string.IsNullOrWhiteSpace(vals[7]))
|
||||||
|
{
|
||||||
|
info.SubtitleStreamIndex = int.Parse(vals[7], _usCulture);
|
||||||
|
}
|
||||||
|
|
||||||
_sessionManager.OnPlaybackProgress(info);
|
_sessionManager.OnPlaybackProgress(info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,5 +198,17 @@ namespace MediaBrowser.Server.Implementations.Session
|
||||||
|
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var socket = GetActiveSocket();
|
||||||
|
|
||||||
|
return socket.SendAsync(new WebSocketMessage<SessionInfoDto>
|
||||||
|
{
|
||||||
|
MessageType = "SessionEnded",
|
||||||
|
Data = sessionInfo
|
||||||
|
|
||||||
|
}, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -466,9 +466,6 @@ namespace MediaBrowser.ServerApplication
|
||||||
|
|
||||||
RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager));
|
RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager));
|
||||||
|
|
||||||
SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager);
|
|
||||||
RegisterSingleInstance(SessionManager);
|
|
||||||
|
|
||||||
HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", "mediabrowser", "dashboard/index.html");
|
HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", "mediabrowser", "dashboard/index.html");
|
||||||
RegisterSingleInstance(HttpServer, false);
|
RegisterSingleInstance(HttpServer, false);
|
||||||
progress.Report(10);
|
progress.Report(10);
|
||||||
|
@ -488,6 +485,9 @@ namespace MediaBrowser.ServerApplication
|
||||||
DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager);
|
DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager);
|
||||||
RegisterSingleInstance(DtoService);
|
RegisterSingleInstance(DtoService);
|
||||||
|
|
||||||
|
SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor);
|
||||||
|
RegisterSingleInstance(SessionManager);
|
||||||
|
|
||||||
var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer);
|
var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer);
|
||||||
RegisterSingleInstance<INewsService>(newsService);
|
RegisterSingleInstance<INewsService>(newsService);
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using WebMarkupMin.Core.Minifiers;
|
using WebMarkupMin.Core.Minifiers;
|
||||||
|
@ -335,16 +334,16 @@ namespace MediaBrowser.WebDashboard.Api
|
||||||
html = html.Replace("<html>", "<html lang=\"" + lang + "\">");
|
html = html.Replace("<html>", "<html lang=\"" + lang + "\">");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
//try
|
||||||
{
|
//{
|
||||||
var minifier = new HtmlMinifier(new HtmlMinificationSettings(true));
|
// var minifier = new HtmlMinifier(new HtmlMinificationSettings(true));
|
||||||
|
|
||||||
html = minifier.Minify(html).MinifiedContent;
|
// html = minifier.Minify(html).MinifiedContent;
|
||||||
}
|
//}
|
||||||
catch (Exception ex)
|
//catch (Exception ex)
|
||||||
{
|
//{
|
||||||
Logger.ErrorException("Error minifying html", ex);
|
// Logger.ErrorException("Error minifying html", ex);
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
var version = GetType().Assembly.GetName().Version;
|
var version = GetType().Assembly.GetName().Version;
|
||||||
|
@ -510,10 +509,10 @@ namespace MediaBrowser.WebDashboard.Api
|
||||||
"librarylist.js",
|
"librarylist.js",
|
||||||
"editorsidebar.js",
|
"editorsidebar.js",
|
||||||
"librarymenu.js",
|
"librarymenu.js",
|
||||||
|
"mediacontroller.js",
|
||||||
"chromecast.js",
|
"chromecast.js",
|
||||||
"contextmenu.js",
|
"contextmenu.js",
|
||||||
|
|
||||||
"mediacontroller.js",
|
|
||||||
"mediaplayer.js",
|
"mediaplayer.js",
|
||||||
"mediaplayer-video.js",
|
"mediaplayer-video.js",
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
|
||||||
return name;
|
return name;
|
||||||
}());
|
}());
|
||||||
|
|
||||||
|
self.deviceName = function () {
|
||||||
|
return deviceName;
|
||||||
|
};
|
||||||
|
|
||||||
self.deviceId = function () {
|
self.deviceId = function () {
|
||||||
return deviceId;
|
return deviceId;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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.347</version>
|
<version>3.0.348</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.347" />
|
<dependency id="MediaBrowser.Common" version="3.0.348" />
|
||||||
<dependency id="NLog" version="2.1.0" />
|
<dependency id="NLog" version="2.1.0" />
|
||||||
<dependency id="SimpleInjector" version="2.4.1" />
|
<dependency id="SimpleInjector" version="2.4.1" />
|
||||||
<dependency id="sharpcompress" version="0.10.2" />
|
<dependency id="sharpcompress" version="0.10.2" />
|
||||||
|
|
|
@ -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.347</version>
|
<version>3.0.348</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>
|
||||||
|
|
|
@ -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.347</version>
|
<version>3.0.348</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.347" />
|
<dependency id="MediaBrowser.Common" version="3.0.348" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
<files>
|
<files>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user