Luke Pulverenti пре 11 година
родитељ
комит
c60103df64
69 измењених фајлова са 1009 додато и 344 уклоњено
  1. 82 5
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  2. 4 1
      MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
  3. 18 2
      MediaBrowser.Api/Playback/StreamState.cs
  4. 3 5
      MediaBrowser.Api/SessionsService.cs
  5. 24 4
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  6. 1 1
      MediaBrowser.Api/UserService.cs
  7. 2 6
      MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs
  8. 0 9
      MediaBrowser.Controller/Dto/IDtoService.cs
  9. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  10. 8 0
      MediaBrowser.Controller/Session/ISessionController.cs
  11. 17 0
      MediaBrowser.Controller/Session/ISessionManager.cs
  12. 12 0
      MediaBrowser.Controller/Session/PlaybackInfo.cs
  13. 18 0
      MediaBrowser.Controller/Session/PlaybackProgressInfo.cs
  14. 9 0
      MediaBrowser.Controller/Session/SessionEventArgs.cs
  15. 10 1
      MediaBrowser.Controller/Session/SessionInfo.cs
  16. 20 1
      MediaBrowser.Dlna/DlnaManager.cs
  17. 1 0
      MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
  18. 51 14
      MediaBrowser.Dlna/PlayTo/Device.cs
  19. 27 23
      MediaBrowser.Dlna/PlayTo/DlnaController.cs
  20. 7 0
      MediaBrowser.Dlna/PlayTo/Extensions.cs
  21. 14 8
      MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs
  22. 1 45
      MediaBrowser.Dlna/PlayTo/uBaseObject.cs
  23. 2 3
      MediaBrowser.Dlna/Profiles/DefaultProfile.cs
  24. 2 2
      MediaBrowser.Dlna/Profiles/Windows81Profile.cs
  25. 200 0
      MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs
  26. 1 1
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  27. 77 20
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  28. 24 3
      MediaBrowser.Model/Dlna/StreamInfo.cs
  29. 2 0
      MediaBrowser.Model/Dto/MediaVersionInfo.cs
  30. 5 1
      MediaBrowser.Model/Entities/MediaStream.cs
  31. 4 1
      MediaBrowser.Model/Session/GeneralCommand.cs
  32. 10 0
      MediaBrowser.Model/Session/PlaybackReports.cs
  33. 18 12
      MediaBrowser.Model/Session/SessionInfoDto.cs
  34. 14 121
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  35. 0 0
      MediaBrowser.Server.Implementations/Localization/JavaScript/el.json
  36. 1 1
      MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json
  37. 0 0
      MediaBrowser.Server.Implementations/Localization/JavaScript/he.json
  38. 1 1
      MediaBrowser.Server.Implementations/Localization/JavaScript/it.json
  39. 1 1
      MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
  40. 1 1
      MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json
  41. 0 0
      MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json
  42. 1 0
      MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
  43. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/ar.json
  44. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/de.json
  45. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/el.json
  46. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/en_GB.json
  47. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/en_US.json
  48. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/es.json
  49. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/es_MX.json
  50. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/fr.json
  51. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/he.json
  52. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/it.json
  53. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/nb.json
  54. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/nl.json
  55. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json
  56. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/ru.json
  57. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json
  58. 2 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  59. 7 4
      MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs
  60. 2 3
      MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs
  61. 242 25
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  62. 29 1
      MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
  63. 12 0
      MediaBrowser.Server.Implementations/Session/WebSocketController.cs
  64. 3 3
      MediaBrowser.ServerApplication/ApplicationHost.cs
  65. 9 10
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  66. 4 0
      MediaBrowser.WebDashboard/ApiClient.js
  67. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  68. 1 1
      Nuget/MediaBrowser.Common.nuspec
  69. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 82 - 5
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -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);

+ 4 - 1
MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs

@@ -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);
+                }
             }
         }
 

+ 18 - 2
MediaBrowser.Api/Playback/StreamState.cs

@@ -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;
             }
         }

+ 3 - 5
MediaBrowser.Api/SessionsService.cs

@@ -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)

+ 24 - 4
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -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);

+ 1 - 1
MediaBrowser.Api/UserService.cs

@@ -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;

+ 2 - 6
MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs

@@ -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));
         }
     }
 }

+ 0 - 9
MediaBrowser.Controller/Dto/IDtoService.cs

@@ -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>

+ 1 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -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" />

+ 8 - 0
MediaBrowser.Controller/Session/ISessionController.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>

+ 17 - 0
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -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>

+ 12 - 0
MediaBrowser.Controller/Session/PlaybackInfo.cs

@@ -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; }
     }
 }

+ 18 - 0
MediaBrowser.Controller/Session/PlaybackProgressInfo.cs

@@ -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; }
     }
 }

+ 9 - 0
MediaBrowser.Controller/Session/SessionEventArgs.cs

@@ -0,0 +1,9 @@
+using System;
+
+namespace MediaBrowser.Controller.Session
+{
+    public class SessionEventArgs : EventArgs
+    {
+        public SessionInfo SessionInfo { get; set; }
+    }
+}

+ 10 - 1
MediaBrowser.Controller/Session/SessionInfo.cs

@@ -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>

+ 20 - 1
MediaBrowser.Dlna/DlnaManager.cs

@@ -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))

+ 1 - 0
MediaBrowser.Dlna/MediaBrowser.Dlna.csproj

@@ -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" />

+ 51 - 14
MediaBrowser.Dlna/PlayTo/Device.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

+ 27 - 23
MediaBrowser.Dlna/PlayTo/DlnaController.cs

@@ -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);

+ 7 - 0
MediaBrowser.Dlna/PlayTo/Extensions.cs

@@ -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)

+ 14 - 8
MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs

@@ -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 - 45
MediaBrowser.Dlna/PlayTo/uBaseObject.cs

@@ -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];
-        }
     }
 }

+ 2 - 3
MediaBrowser.Dlna/Profiles/DefaultProfile.cs

@@ -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",

+ 2 - 2
MediaBrowser.Dlna/Profiles/Windows81Profile.cs

@@ -1,5 +1,5 @@
-using System.Xml.Serialization;
-using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dlna;
+using System.Xml.Serialization;
 
 namespace MediaBrowser.Dlna.Profiles
 {

+ 200 - 0
MediaBrowser.Dlna/Profiles/WindowsPhoneProfile.cs

@@ -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"
+                        }
+                    }
+                }
+            };
+
+        }
+    }
+}

+ 1 - 1
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -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);

+ 77 - 20
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -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)

+ 24 - 3
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -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;
+        }
     }
 }

+ 2 - 0
MediaBrowser.Model/Dto/MediaVersionInfo.cs

@@ -24,5 +24,7 @@ namespace MediaBrowser.Model.Dto
         public Video3DFormat? Video3DFormat { get; set; }
         
         public List<MediaStream> MediaStreams { get; set; }
+
+        public int? Bitrate { get; set; }
     }
 }

+ 5 - 1
MediaBrowser.Model/Entities/MediaStream.cs

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

+ 4 - 1
MediaBrowser.Model/Session/GeneralCommand.cs

@@ -43,6 +43,9 @@ namespace MediaBrowser.Model.Session
         VolumeDown = 18,
         Mute = 19,
         Unmute = 20,
-        ToggleMute = 21
+        ToggleMute = 21,
+        SetVolume = 22,
+        SetAudioStreamIndex = 23,
+        SetSubtitleStreamIndex = 24
     }
 }

+ 10 - 0
MediaBrowser.Model/Session/PlaybackReports.cs

@@ -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>

+ 18 - 12
MediaBrowser.Model/Session/SessionInfoDto.cs

@@ -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>

+ 14 - 121
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -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
MediaBrowser.Server.Implementations/Localization/JavaScript/el.json


+ 1 - 1
MediaBrowser.Server.Implementations/Localization/JavaScript/en_US.json

@@ -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."}

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/JavaScript/he.json


+ 1 - 1
MediaBrowser.Server.Implementations/Localization/JavaScript/it.json

@@ -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."}

+ 1 - 1
MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json

@@ -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
MediaBrowser.Server.Implementations/Localization/JavaScript/nl.json

@@ -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."}

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json


+ 1 - 0
MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs

@@ -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"},

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/ar.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/de.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/el.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/en_GB.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/en_US.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/es.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/es_MX.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/fr.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/he.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/it.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/nb.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/nl.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/ru.json


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json


+ 2 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -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>

+ 7 - 4
MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs

@@ -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>

+ 2 - 3
MediaBrowser.Server.Implementations/ServerManager/WebSocketConnection.cs

@@ -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)

+ 242 - 25
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -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);
+        }
     }
 }

+ 29 - 1
MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs

@@ -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);
             }
         }

+ 12 - 0
MediaBrowser.Server.Implementations/Session/WebSocketController.cs

@@ -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);
+        }
     }
 }

+ 3 - 3
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -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);
 

+ 9 - 10
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -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;

+ 4 - 0
MediaBrowser.WebDashboard/ApiClient.js

@@ -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 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.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" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

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

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

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

Неке датотеке нису приказане због велике количине промена