chromecast updates

pull/702/head
Luke Pulverenti 10 years ago
parent 241c43e9a1
commit c60103df64

@ -1,5 +1,4 @@
using System.Text;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@ -22,6 +21,7 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -1490,6 +1490,14 @@ namespace MediaBrowser.Api.Playback
}
}
if (state.AudioStream != null)
{
//if (CanStreamCopyAudio(request, state.AudioStream))
//{
// request.AudioCodec = "copy";
//}
}
return state;
}
@ -1513,7 +1521,7 @@ namespace MediaBrowser.Api.Playback
}
// 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;
}
@ -1574,12 +1582,50 @@ 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;
}
// 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;
}
protected virtual bool SupportsAutomaticVideoStreamCopy
{
get
@ -1697,7 +1743,21 @@ namespace MediaBrowser.Api.Playback
// 0 = native, 1 = transcoded
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))
{
@ -1747,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)
{
var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture);

@ -103,7 +103,10 @@ namespace MediaBrowser.Api.Playback.Hls
.Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1)
.ToList())
{
ExtendPlaylistTimer(playlist);
if (!string.IsNullOrEmpty(playlist))
{
ExtendPlaylistTimer(playlist);
}
}
}

@ -115,7 +115,15 @@ namespace MediaBrowser.Api.Playback
{
if (LogFileStream != null)
{
LogFileStream.Dispose();
try
{
LogFileStream.Dispose();
}
catch (Exception ex)
{
_logger.ErrorException("Error disposing log stream", ex);
}
LogFileStream = null;
}
}
@ -124,7 +132,15 @@ namespace MediaBrowser.Api.Playback
{
if (IsoMount != null)
{
IsoMount.Dispose();
try
{
IsoMount.Dispose();
}
catch (Exception ex)
{
_logger.ErrorException("Error disposing iso mount", ex);
}
IsoMount = null;
}
}

@ -245,18 +245,16 @@ namespace MediaBrowser.Api
/// </summary>
private readonly ISessionManager _sessionManager;
private readonly IDtoService _dtoService;
private readonly IUserManager _userManager;
/// <summary>
/// Initializes a new instance of the <see cref="SessionsService" /> class.
/// </summary>
/// <param name="sessionManager">The session manager.</param>
/// <param name="dtoService">The dto service.</param>
public SessionsService(ISessionManager sessionManager, IDtoService dtoService, IUserManager userManager)
/// <param name="userManager">The user manager.</param>
public SessionsService(ISessionManager sessionManager, IUserManager userManager)
{
_sessionManager = sessionManager;
_dtoService = dtoService;
_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)

@ -257,6 +257,12 @@ namespace MediaBrowser.Api.UserLibrary
/// <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)]
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>
@ -282,7 +288,7 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string MediaSourceId { get; set; }
/// <summary>
/// Gets or sets the position ticks.
/// </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")]
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>
@ -320,7 +335,7 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
public string MediaSourceId { get; set; }
/// <summary>
/// Gets or sets the position ticks.
/// </summary>
@ -737,7 +752,9 @@ namespace MediaBrowser.Api.UserLibrary
Item = item,
SessionId = GetSession(_sessionManager).Id,
QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(),
MediaSourceId = request.MediaSourceId
MediaSourceId = request.MediaSourceId,
AudioStreamIndex = request.AudioStreamIndex,
SubtitleStreamIndex = request.SubtitleStreamIndex
};
_sessionManager.OnPlaybackStart(info);
@ -760,7 +777,10 @@ namespace MediaBrowser.Api.UserLibrary
IsMuted = request.IsMuted,
IsPaused = request.IsPaused,
SessionId = GetSession(_sessionManager).Id,
MediaSourceId = request.MediaSourceId
MediaSourceId = request.MediaSourceId,
AudioStreamIndex = request.AudioStreamIndex,
SubtitleStreamIndex = request.SubtitleStreamIndex,
VolumeLevel = request.VolumeLevel
};
var task = _sessionManager.OnPlaybackProgress(info);

@ -323,7 +323,7 @@ namespace MediaBrowser.Api
var result = new AuthenticationResult
{
User = _dtoService.GetUserDto(user),
SessionInfo = _dtoService.GetSessionInfoDto(session)
SessionInfo = _sessionMananger.GetSessionInfoDto(session)
};
return result;

@ -1,5 +1,4 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Session;
@ -14,8 +13,6 @@ namespace MediaBrowser.Api.WebSocket
/// </summary>
class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfoDto>, object>
{
private readonly IDtoService _dtoService;
/// <summary>
/// Gets the name.
/// </summary>
@ -35,11 +32,10 @@ namespace MediaBrowser.Api.WebSocket
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="sessionManager">The session manager.</param>
public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager, IDtoService dtoService)
public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager)
: base(logger)
{
_sessionManager = sessionManager;
_dtoService = dtoService;
}
/// <summary>
@ -49,7 +45,7 @@ namespace MediaBrowser.Api.WebSocket
/// <returns>Task{SystemInfo}.</returns>
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.Session;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Session;
using System;
using System.Collections.Generic;
@ -21,13 +19,6 @@ namespace MediaBrowser.Controller.Dto
/// <returns>UserDto.</returns>
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>
/// Gets the dto id.
/// </summary>

@ -253,6 +253,7 @@
<Compile Include="Session\PlaybackInfo.cs" />
<Compile Include="Session\PlaybackProgressInfo.cs" />
<Compile Include="Session\PlaybackStopInfo.cs" />
<Compile Include="Session\SessionEventArgs.cs" />
<Compile Include="Session\SessionInfo.cs" />
<Compile Include="Sorting\IBaseItemComparer.cs" />
<Compile Include="Sorting\IUserBaseItemComparer.cs" />

@ -89,6 +89,14 @@ namespace MediaBrowser.Controller.Session
/// <returns>Task.</returns>
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>
/// Sends the server restart notification.
/// </summary>

@ -28,6 +28,16 @@ namespace MediaBrowser.Controller.Session
/// </summary>
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>
/// Gets the sessions.
/// </summary>
@ -83,6 +93,13 @@ namespace MediaBrowser.Controller.Session
/// <returns>Task.</returns>
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>
/// Sends the general command.
/// </summary>

@ -40,5 +40,17 @@ namespace MediaBrowser.Controller.Session
/// </summary>
/// <value>The media version identifier.</value>
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>
/// <value>The media version identifier.</value>
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; }
}
}

@ -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>
public string NowPlayingMediaSourceId { get; set; }
/// <summary>
/// Gets or sets the now playing run time ticks.
/// </summary>
@ -150,6 +149,16 @@ namespace MediaBrowser.Controller.Session
/// <value><c>true</c> if this instance is muted; otherwise, <c>false</c>.</value>
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>
/// Gets or sets the device id.
/// </summary>

@ -1,4 +1,5 @@
using MediaBrowser.Common.Configuration;
using System.Text;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Dlna;
@ -128,11 +129,29 @@ namespace MediaBrowser.Dlna
else
{
_logger.Debug("No matching device profile found. The default will need to be used.");
LogUnmatchedProfile(deviceInfo);
}
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)
{
if (!string.IsNullOrWhiteSpace(profileInfo.DeviceDescription))

@ -72,6 +72,7 @@
<Compile Include="Profiles\Foobar2000Profile.cs" />
<Compile Include="Profiles\Windows81Profile.cs" />
<Compile Include="Profiles\WindowsMediaCenterProfile.cs" />
<Compile Include="Profiles\WindowsPhoneProfile.cs" />
<Compile Include="Ssdp\SsdpHelper.cs" />
<Compile Include="PlayTo\SsdpHttpClient.cs" />
<Compile Include="PlayTo\StateVariable.cs" />

@ -512,17 +512,15 @@ namespace MediaBrowser.Dlna.PlayTo
if (result == null || result.Document == null)
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;
return;
}
var uPnpResponse = XElement.Parse(track);
var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
var e = track.Element(uPnpNamespaces.items) ?? track;
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 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);
}
@ -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 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);
}
var track = result.Document.Descendants("TrackMetaData").Select(i => i.Value)
.FirstOrDefault();
var track = result.Document.Descendants("TrackMetaData").FirstOrDefault();
if (String.IsNullOrEmpty(track))
if (track == null)
{
//If track is null, some vendors do this, use GetMediaInfo instead
return false;
}
var uPnpResponse = XElement.Parse(track);
var e = uPnpResponse.Element(uPnpNamespaces.items) ?? uPnpResponse;
var e = track.Element(uPnpNamespaces.items) ?? track;
var uTrack = uBaseObject.Create(e);
var uTrack = CreateUBaseObject(e);
if (uTrack == null)
return true;
@ -592,6 +587,48 @@ namespace MediaBrowser.Dlna.PlayTo
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
#region From XML

@ -184,27 +184,26 @@ namespace MediaBrowser.Dlna.PlayTo
if ((_device.IsPlaying || _device.IsPaused))
{
var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
if (playlistItem != null && playlistItem.Transcode)
if (playlistItem != null)
{
await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo
var ticks = _device.Position.Ticks;
if (playlistItem.Transcode)
{
Item = _currentItem,
SessionId = _session.Id,
PositionTicks = _device.Position.Ticks + playlistItem.StartPositionTicks,
IsMuted = _device.IsMuted,
IsPaused = _device.IsPaused
ticks += playlistItem.StartPositionTicks;
}
}).ConfigureAwait(false);
}
else if (_currentItem != null)
{
await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo
{
Item = _currentItem,
SessionId = _session.Id,
PositionTicks = _device.Position.Ticks,
PositionTicks = ticks,
IsMuted = _device.IsMuted,
IsPaused = _device.IsPaused
IsPaused = _device.IsPaused,
MediaSourceId = playlistItem.MediaSourceId,
AudioStreamIndex = playlistItem.AudioStreamIndex,
SubtitleStreamIndex = playlistItem.SubtitleStreamIndex
}).ConfigureAwait(false);
}
@ -284,16 +283,16 @@ namespace MediaBrowser.Dlna.PlayTo
return _device.SetPlay();
case PlaystateCommand.Seek:
var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
if (playlistItem != null && playlistItem.Transcode && playlistItem.MediaType == DlnaProfileType.Video && _currentItem != null)
{
var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
playlistItem.StartPositionTicks = newItem.StartPositionTicks;
playlistItem.StreamUrl = newItem.StreamUrl;
playlistItem.Didl = newItem.Didl;
return _device.SetAvTransport(playlistItem.StreamUrl, GetDlnaHeaders(playlistItem), playlistItem.Didl);
}
//var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
//if (playlistItem != null && playlistItem.Transcode && _currentItem != null)
//{
// var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
// playlistItem.StartPositionTicks = newItem.StartPositionTicks;
// playlistItem.StreamUrl = newItem.StreamUrl;
// playlistItem.Didl = newItem.Didl;
// return _device.SetAvTransport(playlistItem.StreamUrl, GetDlnaHeaders(playlistItem), playlistItem.Didl);
//}
return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0));
@ -324,6 +323,11 @@ namespace MediaBrowser.Dlna.PlayTo
return Task.FromResult(true);
}
public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task SendServerShutdownNotification(CancellationToken cancellationToken)
{
return Task.FromResult(true);

@ -40,6 +40,13 @@ namespace MediaBrowser.Dlna.PlayTo
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)
{
var node = container.Descendants(name)

@ -1,6 +1,7 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using System;
using System.Globalization;
using System.IO;
using System.Text;
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,
string ip,
int port,
@ -58,11 +61,13 @@ namespace MediaBrowser.Dlna.PlayTo
LogRequest = _config.Configuration.DlnaOptions.EnableDebugLogging
};
options.RequestHeaders["HOST"] = ip + ":" + port;
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport + ">";
options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture);
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport.ToString(_usCulture) + ">";
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))
{
}
@ -71,8 +76,9 @@ namespace MediaBrowser.Dlna.PlayTo
public async Task RespondAsync(Uri url,
string ip,
int port,
string localIp,
int eventport)
string localIp,
int eventport,
int timeOut = 3600)
{
var options = new HttpRequestOptions
{
@ -80,10 +86,10 @@ namespace MediaBrowser.Dlna.PlayTo
UserAgent = USERAGENT
};
options.RequestHeaders["HOST"] = ip + ":" + port;
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport + ">";
options.RequestHeaders["HOST"] = ip + ":" + port.ToString(_usCulture);
options.RequestHeaders["CALLBACK"] = "<" + localIp + ":" + eventport.ToString(_usCulture) + ">";
options.RequestHeaders["NT"] = "upnp:event";
options.RequestHeaders["TIMEOUT"] = "Second - 3600";
options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture);
using (await _httpClient.Get(options).ConfigureAwait(false))
{

@ -1,6 +1,4 @@
using System;
using System.Xml.Linq;

namespace MediaBrowser.Dlna.PlayTo
{
public class uBaseObject
@ -20,47 +18,5 @@ namespace MediaBrowser.Dlna.PlayTo
public string Url { 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 MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{
@ -32,7 +31,7 @@ namespace MediaBrowser.Dlna.Profiles
new TranscodingProfile
{
Container = "ts",
Container = "mp4",
Type = DlnaProfileType.Video,
AudioCodec = "aac",
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
{

@ -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.
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);
var probeSize = GetProbeSizeArgument(type);

@ -28,7 +28,7 @@ namespace MediaBrowser.Model.Dlna
.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)
{
@ -75,36 +75,40 @@ namespace MediaBrowser.Model.Dlna
streams.FirstOrDefault();
}
private StreamInfo BuildAudioItem(string itemId, MediaSourceInfo item, DeviceProfile profile)
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
{
var playlistItem = new StreamInfo
{
ItemId = itemId,
ItemId = options.ItemId,
MediaType = DlnaProfileType.Audio,
MediaSourceId = item.Id
};
var audioStream = item.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
var directPlay = profile.DirectPlayProfiles
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsAudioProfileSupported(i, item, audioStream));
if (directPlay != null)
// Honor the max bitrate setting
if (IsAudioEligibleForDirectPlay(item, options))
{
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 (!string.IsNullOrEmpty(audioCodec) && profile.CodecProfiles.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(audioCodec))
.All(i => AreConditionsSatisfied(i.Conditions, item.Path, null, audioStream)))
if (directPlay != null)
{
playlistItem.IsDirectStream = true;
playlistItem.Container = item.Container;
var audioCodec = audioStream == null ? null : audioStream.Codec;
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);
if (transcodingProfile != null)
@ -113,12 +117,28 @@ namespace MediaBrowser.Model.Dlna
playlistItem.Container = transcodingProfile.Container;
playlistItem.AudioCodec = transcodingProfile.AudioCodec;
var audioTranscodingConditions = profile.CodecProfiles
var audioTranscodingConditions = options.Profile.CodecProfiles
.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(transcodingProfile.AudioCodec))
.Take(1)
.SelectMany(i => i.Conditions);
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;
@ -140,7 +160,7 @@ namespace MediaBrowser.Model.Dlna
{
// See if it can be direct played
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)
{
@ -189,6 +209,37 @@ namespace MediaBrowser.Model.Dlna
.SelectMany(i => i.Conditions);
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;
@ -207,7 +258,13 @@ namespace MediaBrowser.Model.Dlna
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)
@ -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)
{
@ -346,7 +403,7 @@ namespace MediaBrowser.Model.Dlna
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
if (item.VideoType != VideoType.VideoFile)

@ -1,7 +1,7 @@
using System;
using MediaBrowser.Model.Dto;
using System;
using System.Collections.Generic;
using System.Globalization;
using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.Dlna
{
@ -107,10 +107,25 @@ namespace MediaBrowser.Model.Dlna
{
public string ItemId { get; set; }
public List<MediaSourceInfo> MediaSources { get; set; }
public int? MaxBitrateSetting { 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 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>
@ -120,5 +135,11 @@ namespace MediaBrowser.Model.Dlna
{
public int? AudioStreamIndex { 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 List<MediaStream> MediaStreams { get; set; }
public int? Bitrate { get; set; }
}
}

@ -152,7 +152,11 @@ namespace MediaBrowser.Model.Entities
/// <summary>
/// The subtitle
/// </summary>
Subtitle
Subtitle,
/// <summary>
/// The embedded image
/// </summary>
EmbeddedImage
}
public class MediaInfo

@ -43,6 +43,9 @@ namespace MediaBrowser.Model.Session
VolumeDown = 18,
Mute = 19,
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 int? AudioStreamIndex { get; set; }
public int? SubtitleStreamIndex { get; set; }
public PlaybackStartInfo()
{
QueueableMediaTypes = new string[] { };
@ -38,6 +42,12 @@ namespace MediaBrowser.Model.Session
public bool IsPaused { get; set; }
public bool IsMuted { get; set; }
public int? AudioStreamIndex { get; set; }
public int? SubtitleStreamIndex { get; set; }
public int? VolumeLevel { get; set; }
}
/// <summary>

@ -111,6 +111,24 @@ namespace MediaBrowser.Model.Session
/// <value>The name of the device.</value>
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>
/// Gets or sets a value indicating whether this instance is paused.
/// </summary>
@ -140,12 +158,6 @@ namespace MediaBrowser.Model.Session
/// </summary>
/// <value>The device id.</value>
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>
/// Gets or sets a value indicating whether [supports remote control].
@ -153,12 +165,6 @@ namespace MediaBrowser.Model.Session
/// <value><c>true</c> if [supports remote control]; otherwise, <c>false</c>.</value>
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>
/// Gets or sets a value indicating whether [supports navigation commands].
/// </summary>

@ -245,127 +245,6 @@ namespace MediaBrowser.Server.Implementations.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>
/// Gets client-side Id of a server-side BaseItem
/// </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;
}
@ -1388,6 +1274,13 @@ namespace MediaBrowser.Server.Implementations.Dto
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;
}

@ -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":"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."}
{"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":"\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\u05d7\u05e8\u05e8","OptionBeta":"\u05d1\u05d8\u05d0","OptionDev":"\u05de\u05e4\u05ea\u05d7\u05d9\u05dd","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."}
{"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 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."}
{"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",
"DeleteImage": "Delete 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.",
"FileReadError": "An error occurred while reading the file.",
"DeleteUser": "Delete User",

@ -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":"Offici\u00eble Release","OptionBeta":"Beta","OptionDev":"Ontwikkeling (Onstabiel)","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":"\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":"\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 \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."}

@ -334,6 +334,7 @@ namespace MediaBrowser.Server.Implementations.Localization
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="Chinese Traditional", Value="zh-TW"},
new LocalizatonOption{ Name="Dutch", Value="nl"},

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

@ -313,6 +313,8 @@
<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" />
</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)
{
return SendCommand(new WebSocketMessage<MessageCommand>
@ -81,11 +86,10 @@ namespace MediaBrowser.Server.Implementations.Roku
}, cancellationToken);
}
private readonly Task _cachedTask = Task.FromResult(true);
public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
{
// Roku probably won't care about this
return _cachedTask;
return Task.FromResult(true);
}
public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
@ -101,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.Roku
public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
{
// Roku probably won't care about this
return _cachedTask;
return Task.FromResult(true);
}
public Task SendServerShutdownNotification(CancellationToken cancellationToken)
@ -137,7 +141,6 @@ namespace MediaBrowser.Server.Implementations.Roku
});
}
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
{
return SendCommand(new WebSocketMessage<GeneralCommand>

@ -157,11 +157,10 @@ namespace MediaBrowser.Server.Implementations.ServerManager
var info = new WebSocketMessageInfo
{
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);
}
catch (Exception ex)

@ -1,8 +1,12 @@
using MediaBrowser.Common.Events;
using System.IO;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Session;
@ -42,6 +46,8 @@ namespace MediaBrowser.Server.Implementations.Session
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly IMusicManager _musicManager;
private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
/// <summary>
/// Gets or sets the configuration manager.
@ -68,6 +74,10 @@ namespace MediaBrowser.Server.Implementations.Session
/// </summary>
public event EventHandler<PlaybackStopEventArgs> PlaybackStopped;
public event EventHandler<SessionEventArgs> SessionStarted;
public event EventHandler<SessionEventArgs> SessionEnded;
private IEnumerable<ISessionControllerFactory> _sessionFactories = new List<ISessionControllerFactory>();
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="userRepository">The user repository.</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;
_configurationManager = configurationManager;
@ -89,6 +99,8 @@ namespace MediaBrowser.Server.Implementations.Session
_libraryManager = libraryManager;
_userManager = userManager;
_musicManager = musicManager;
_dtoService = dtoService;
_imageProcessor = imageProcessor;
}
/// <summary>
@ -109,6 +121,47 @@ namespace MediaBrowser.Server.Implementations.Session
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>
/// Logs the user activity.
/// </summary>
@ -194,19 +247,7 @@ namespace MediaBrowser.Server.Implementations.Session
if (_activeConnections.TryRemove(key, out removed))
{
var disposable = removed.SessionController as IDisposable;
if (disposable != null)
{
try
{
disposable.Dispose();
}
catch (Exception ex)
{
_logger.ErrorException("Error disposing session controller", ex);
}
}
OnSessionEnded(removed);
}
}
finally
@ -222,11 +263,9 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="item">The item.</param>
/// <param name="mediaSourceId">The media version identifier.</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>
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.NowPlayingPositionTicks = currentPositionTicks;
session.NowPlayingItem = item;
@ -291,12 +330,19 @@ namespace MediaBrowser.Server.Implementations.Session
try
{
var connection = _activeConnections.GetOrAdd(key, keyName => new SessionInfo
var connection = _activeConnections.GetOrAdd(key, keyName =>
{
Client = clientType,
DeviceId = deviceId,
ApplicationVersion = appVersion,
Id = Guid.NewGuid()
var sessionInfo = new SessionInfo
{
Client = clientType,
DeviceId = deviceId,
ApplicationVersion = appVersion,
Id = Guid.NewGuid()
};
OnSessionStarted(sessionInfo);
return sessionInfo;
});
connection.DeviceName = deviceName;
@ -372,11 +418,14 @@ namespace MediaBrowser.Server.Implementations.Session
var mediaSourceId = GetMediaSourceId(item, info.MediaSourceId);
UpdateNowPlayingItem(session, item, mediaSourceId, false, false);
UpdateNowPlayingItem(session, item, mediaSourceId, false);
session.CanSeek = info.CanSeek;
session.QueueableMediaTypes = info.QueueableMediaTypes;
session.NowPlayingAudioStreamIndex = info.AudioStreamIndex;
session.NowPlayingSubtitleStreamIndex = info.SubtitleStreamIndex;
var key = item.GetUserDataKey();
var users = GetUsers(session);
@ -442,7 +491,12 @@ namespace MediaBrowser.Server.Implementations.Session
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();
@ -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>
/// Adds the additional user.
/// </summary>
@ -1017,5 +1092,147 @@ namespace MediaBrowser.Server.Implementations.Session
session.PlayableMediaTypes = capabilities.PlayableMediaTypes;
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.Dto;
using MediaBrowser.Controller.Session;
@ -187,6 +188,8 @@ namespace MediaBrowser.Server.Implementations.Session
return result;
}
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
/// <summary>
/// Reports the playback start.
/// </summary>
@ -228,6 +231,16 @@ namespace MediaBrowser.Server.Implementations.Session
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);
}
}
@ -275,6 +288,21 @@ namespace MediaBrowser.Server.Implementations.Session
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);
}
}

@ -198,5 +198,17 @@ namespace MediaBrowser.Server.Implementations.Session
}, 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));
SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager);
RegisterSingleInstance(SessionManager);
HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", "mediabrowser", "dashboard/index.html");
RegisterSingleInstance(HttpServer, false);
progress.Report(10);
@ -488,6 +485,9 @@ namespace MediaBrowser.ServerApplication
DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager);
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);
RegisterSingleInstance<INewsService>(newsService);

@ -14,7 +14,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using WebMarkupMin.Core.Minifiers;
@ -335,16 +334,16 @@ namespace MediaBrowser.WebDashboard.Api
html = html.Replace("<html>", "<html lang=\"" + lang + "\">");
}
try
{
var minifier = new HtmlMinifier(new HtmlMinificationSettings(true));
//try
//{
// var minifier = new HtmlMinifier(new HtmlMinificationSettings(true));
html = minifier.Minify(html).MinifiedContent;
}
catch (Exception ex)
{
Logger.ErrorException("Error minifying html", ex);
}
// html = minifier.Minify(html).MinifiedContent;
//}
//catch (Exception ex)
//{
// Logger.ErrorException("Error minifying html", ex);
//}
}
var version = GetType().Assembly.GetName().Version;

@ -78,6 +78,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
return name;
}());
self.deviceName = function () {
return deviceName;
};
self.deviceId = function () {
return deviceId;
};

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
<version>3.0.347</version>
<version>3.0.348</version>
<title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners>
@ -12,7 +12,7 @@
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
<dependency id="MediaBrowser.Common" version="3.0.347" />
<dependency id="MediaBrowser.Common" version="3.0.348" />
<dependency id="NLog" version="2.1.0" />
<dependency id="SimpleInjector" version="2.4.1" />
<dependency id="sharpcompress" version="0.10.2" />

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

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

Loading…
Cancel
Save