Bladeren bron

fixes #888 - Support m3u8 subtitle playlists

Luke Pulverenti 11 jaren geleden
bovenliggende
commit
3ff3d04284
33 gewijzigde bestanden met toevoegingen van 187 en 84 verwijderingen
  1. 2 0
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  2. 64 9
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  3. 3 0
      MediaBrowser.Api/Playback/StreamState.cs
  4. 73 1
      MediaBrowser.Api/Subtitles/SubtitleService.cs
  5. 3 2
      MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs
  6. 3 2
      MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs
  7. 3 2
      MediaBrowser.Dlna/Profiles/Windows81Profile.cs
  8. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Android.xml
  9. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Default.xml
  10. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml
  11. 0 2
      MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml
  12. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml
  13. 0 2
      MediaBrowser.Dlna/Profiles/Xml/MediaMonkey.xml
  14. 3 4
      MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml
  15. 3 4
      MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml
  16. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml
  17. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml
  18. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml
  19. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml
  20. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml
  21. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml
  22. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml
  23. 0 2
      MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml
  24. 3 4
      MediaBrowser.Dlna/Profiles/Xml/Windows 8 RT.xml
  25. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Windows Phone.xml
  26. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml
  27. 0 2
      MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml
  28. 0 2
      MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml
  29. 1 5
      MediaBrowser.Model/Dlna/DeviceProfile.cs
  30. 4 4
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  31. 5 1
      MediaBrowser.Model/Dlna/StreamInfo.cs
  32. 3 0
      MediaBrowser.Model/Dlna/SubtitleProfile.cs
  33. 14 10
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

+ 2 - 0
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1605,6 +1605,8 @@ namespace MediaBrowser.Api.Playback
             {
                 state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
             }
+
+            state.AllMediaStreams = mediaStreams;
         }
 
         private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,

+ 64 - 9
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -5,6 +5,8 @@ using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using ServiceStack;
 using System;
@@ -18,20 +20,20 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Playback.Hls
 {
-    [Route("/Videos/{Id}/master.m3u8", "GET")]
-    [Api(Description = "Gets a video stream using HTTP live streaming.")]
+    [Route("/Videos/{Id}/master.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
     public class GetMasterHlsVideoStream : VideoStreamRequest
     {
         public bool EnableAdaptiveBitrateStreaming { get; set; }
 
+        public SubtitleDeliveryMethod SubtitleMethod { get; set; }
+
         public GetMasterHlsVideoStream()
         {
             EnableAdaptiveBitrateStreaming = true;
         }
     }
 
-    [Route("/Videos/{Id}/main.m3u8", "GET")]
-    [Api(Description = "Gets a video stream using HTTP live streaming.")]
+    [Route("/Videos/{Id}/main.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
     public class GetMainHlsVideoStream : VideoStreamRequest
     {
     }
@@ -359,7 +361,17 @@ namespace MediaBrowser.Api.Playback.Hls
             var playlistUrl = (state.RunTimeTicks ?? 0) > 0 ? "main.m3u8" : "live.m3u8";
             playlistUrl += queryString;
 
-            AppendPlaylist(builder, playlistUrl, totalBitrate);
+            var request = (GetMasterHlsVideoStream) state.Request;
+
+            var subtitleStreams = state.AllMediaStreams
+                .Where(i => i.IsTextSubtitleStream)
+                .ToList();
+
+            var subtitleGroup = subtitleStreams.Count > 0 && request.SubtitleMethod == SubtitleDeliveryMethod.Hls ? 
+                "subs" : 
+                null;
+
+            AppendPlaylist(builder, playlistUrl, totalBitrate, subtitleGroup);
 
             if (EnableAdaptiveBitrateStreaming(state))
             {
@@ -369,16 +381,52 @@ namespace MediaBrowser.Api.Playback.Hls
                 var variation = GetBitrateVariation(totalBitrate);
 
                 var newBitrate = totalBitrate - variation;
-                AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate);
+                AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate, subtitleGroup);
 
                 variation *= 2;
                 newBitrate = totalBitrate - variation;
-                AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate);
+                AppendPlaylist(builder, playlistUrl.Replace(requestedVideoBitrate.ToString(UsCulture), (requestedVideoBitrate - variation).ToString(UsCulture)), newBitrate, subtitleGroup);
+            }
+
+            if (!string.IsNullOrWhiteSpace(subtitleGroup))
+            {
+                AddSubtitles(state, subtitleStreams, builder);
             }
 
             return builder.ToString();
         }
 
+        private void AddSubtitles(StreamState state, IEnumerable<MediaStream> subtitles, StringBuilder builder)
+        {
+            var selectedIndex = state.SubtitleStream == null ? (int?)null : state.SubtitleStream.Index;
+
+            foreach (var stream in subtitles)
+            {
+                const string format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},URI=\"{3}\",LANGUAGE=\"{4}\"";
+
+                var name = stream.Language;
+
+                var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index;
+                var isForced = stream.IsForced;
+
+                if (string.IsNullOrWhiteSpace(name)) name = stream.Codec ?? "Unknown";
+
+                var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}",
+                    state.Request.MediaSourceId,
+                    stream.Index.ToString(UsCulture),
+                    30.ToString(UsCulture));
+
+                var line = string.Format(format,
+                    name,
+                    isDefault ? "YES" : "NO",
+                    isForced ? "YES" : "NO",
+                    url,
+                    stream.Language ?? "Unknown");
+
+                builder.AppendLine(line);
+            }
+        }
+
         private bool EnableAdaptiveBitrateStreaming(StreamState state)
         {
             var request = state.Request as GetMasterHlsVideoStream;
@@ -397,9 +445,16 @@ namespace MediaBrowser.Api.Playback.Hls
             return state.VideoRequest.VideoBitRate.HasValue;
         }
 
-        private void AppendPlaylist(StringBuilder builder, string url, int bitrate)
+        private void AppendPlaylist(StringBuilder builder, string url, int bitrate, string subtitleGroup)
         {
-            builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + bitrate.ToString(UsCulture));
+            var header = "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + bitrate.ToString(UsCulture);
+
+            if (!string.IsNullOrWhiteSpace(subtitleGroup))
+            {
+                header += string.Format(",SUBTITLES=\"{0}\"", subtitleGroup);
+            }
+
+            builder.AppendLine(header);
             builder.AppendLine(url);
         }
 

+ 3 - 0
MediaBrowser.Api/Playback/StreamState.cs

@@ -38,6 +38,8 @@ namespace MediaBrowser.Api.Playback
 
         public string InputContainer { get; set; }
 
+        public List<MediaStream> AllMediaStreams { get; set; }
+        
         public MediaStream AudioStream { get; set; }
         public MediaStream VideoStream { get; set; }
         public MediaStream SubtitleStream { get; set; }
@@ -78,6 +80,7 @@ namespace MediaBrowser.Api.Playback
             SupportedAudioCodecs = new List<string>();
             PlayableStreamFileNames = new List<string>();
             RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+            AllMediaStreams = new List<MediaStream>();
         }
 
         public string InputAudioSync { get; set; }

+ 73 - 1
MediaBrowser.Api/Subtitles/SubtitleService.cs

@@ -9,8 +9,10 @@ using MediaBrowser.Model.Providers;
 using ServiceStack;
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.Linq;
+using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -91,6 +93,29 @@ namespace MediaBrowser.Api.Subtitles
 
         [ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public long StartPositionTicks { get; set; }
+
+        [ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public long? EndPositionTicks { get; set; }
+    }
+
+    [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")]
+    public class GetSubtitlePlaylist
+    {
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+
+        [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string MediaSourceId { get; set; }
+
+        [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
+        public int Index { get; set; }
+
+        [ApiMember(Name = "SegmentLength", Description = "The subtitle srgment length", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
+        public int SegmentLength { get; set; }
     }
 
     public class SubtitleService : BaseApiService
@@ -106,6 +131,53 @@ namespace MediaBrowser.Api.Subtitles
             _subtitleEncoder = subtitleEncoder;
         }
 
+        public object Get(GetSubtitlePlaylist request)
+        {
+            var item = (Video)_libraryManager.GetItemById(new Guid(request.Id));
+
+            var mediaSource = item.GetMediaSources(false)
+                .First(i => string.Equals(i.Id, request.MediaSourceId ?? request.Id));
+
+            var builder = new StringBuilder();
+
+            var runtime = mediaSource.RunTimeTicks ?? -1;
+
+            if (runtime <= 0)
+            {
+                throw new ArgumentException("HLS Subtitles are not supported for this media.");
+            }
+
+            builder.AppendLine("#EXTM3U");
+            builder.AppendLine("#EXT-X-TARGETDURATION:" + request.SegmentLength.ToString(CultureInfo.InvariantCulture));
+            builder.AppendLine("#EXT-X-VERSION:3");
+            builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
+
+            long positionTicks = 0;
+            var segmentLengthTicks = TimeSpan.FromSeconds(request.SegmentLength).Ticks;
+
+            while (positionTicks < runtime)
+            {
+                var remaining = runtime - positionTicks;
+                var lengthTicks = Math.Min(remaining, segmentLengthTicks);
+
+                builder.AppendLine("#EXTINF:" + TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture));
+
+                var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);
+
+                var url = string.Format("stream.srt?StartPositionTicks={0}&EndPositionTicks={1}",
+                    positionTicks.ToString(CultureInfo.InvariantCulture),
+                    endPositionTicks.ToString(CultureInfo.InvariantCulture));
+
+                builder.AppendLine(url);
+
+                positionTicks += segmentLengthTicks;
+            }
+
+            builder.AppendLine("#EXT-X-ENDLIST");
+            
+            return ResultFactory.GetResult(builder.ToString(), Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
+        }
+
         public object Get(GetSubtitle request)
         {
             if (string.IsNullOrEmpty(request.Format))
@@ -133,7 +205,7 @@ namespace MediaBrowser.Api.Subtitles
                 request.Index,
                 request.Format,
                 request.StartPositionTicks,
-                null,
+                request.EndPositionTicks,
                 CancellationToken.None).ConfigureAwait(false);
         }
 

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

@@ -193,11 +193,12 @@ namespace MediaBrowser.Dlna.Profiles
                }
            };
 
-            SoftSubtitleProfiles = new[]
+            SubtitleProfiles = new[]
             {
                 new SubtitleProfile
                 {
-                    Format = "srt"
+                    Format = "srt",
+                    Method = SubtitleDeliveryMethod.External
                 }
             };
         }

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

@@ -339,11 +339,12 @@ namespace MediaBrowser.Dlna.Profiles
                 }
             };
 
-            SoftSubtitleProfiles = new[]
+            SubtitleProfiles = new[]
             {
                 new SubtitleProfile
                 {
-                    Format = "smi"
+                    Format = "smi",
+                    Method = SubtitleDeliveryMethod.External
                 }
             };
         }

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

@@ -155,11 +155,12 @@ namespace MediaBrowser.Dlna.Profiles
                 }
             };
 
-            ExternalSubtitleProfiles = new[]
+            SubtitleProfiles = new[]
             {
                 new SubtitleProfile
                 {
-                    Format = "ttml"
+                    Format = "ttml",
+                    Method = SubtitleDeliveryMethod.External
                 }
             };
         }

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Android.xml

@@ -65,6 +65,4 @@
     </CodecProfile>
   </CodecProfiles>
   <ResponseProfiles />
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Default.xml

@@ -35,6 +35,4 @@
   <ContainerProfiles />
   <CodecProfiles />
   <ResponseProfiles />
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml

@@ -39,6 +39,4 @@
   <ContainerProfiles />
   <CodecProfiles />
   <ResponseProfiles />
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml

@@ -73,6 +73,4 @@
     </CodecProfile>
   </CodecProfiles>
   <ResponseProfiles />
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml

@@ -39,6 +39,4 @@
   <ContainerProfiles />
   <CodecProfiles />
   <ResponseProfiles />
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/MediaMonkey.xml

@@ -45,6 +45,4 @@
   <ContainerProfiles />
   <CodecProfiles />
   <ResponseProfiles />
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 3 - 4
MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml

@@ -68,8 +68,7 @@
     </CodecProfile>
   </CodecProfiles>
   <ResponseProfiles />
-  <SoftSubtitleProfiles>
-    <SubtitleProfile format="srt" />
-  </SoftSubtitleProfiles>
-  <ExternalSubtitleProfiles />
+  <SubtitleProfiles>
+    <SubtitleProfile format="srt" method="External" />
+  </SubtitleProfiles>
 </Profile>

+ 3 - 4
MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml

@@ -106,8 +106,7 @@
       <Conditions />
     </ResponseProfile>
   </ResponseProfiles>
-  <SoftSubtitleProfiles>
-    <SubtitleProfile format="smi" />
-  </SoftSubtitleProfiles>
-  <ExternalSubtitleProfiles />
+  <SubtitleProfiles>
+    <SubtitleProfile format="smi" method="External" />
+  </SubtitleProfiles>
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml

@@ -71,6 +71,4 @@
     </CodecProfile>
   </CodecProfiles>
   <ResponseProfiles />
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml

@@ -99,6 +99,4 @@
       <Conditions />
     </ResponseProfile>
   </ResponseProfiles>
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml

@@ -107,6 +107,4 @@
       <Conditions />
     </ResponseProfile>
   </ResponseProfiles>
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml

@@ -110,6 +110,4 @@
       <Conditions />
     </ResponseProfile>
   </ResponseProfiles>
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml

@@ -93,6 +93,4 @@
       <Conditions />
     </ResponseProfile>
   </ResponseProfiles>
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml

@@ -93,6 +93,4 @@
       <Conditions />
     </ResponseProfile>
   </ResponseProfiles>
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml

@@ -93,6 +93,4 @@
       <Conditions />
     </ResponseProfile>
   </ResponseProfiles>
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml

@@ -78,6 +78,4 @@
       <Conditions />
     </ResponseProfile>
   </ResponseProfiles>
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 3 - 4
MediaBrowser.Dlna/Profiles/Xml/Windows 8 RT.xml

@@ -62,8 +62,7 @@
     </CodecProfile>
   </CodecProfiles>
   <ResponseProfiles />
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles>
-    <SubtitleProfile format="ttml" />
-  </ExternalSubtitleProfiles>
+  <SubtitleProfiles>
+    <SubtitleProfile format="ttml" method="External" />
+  </SubtitleProfiles>
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Windows Phone.xml

@@ -76,6 +76,4 @@
     </CodecProfile>
   </CodecProfiles>
   <ResponseProfiles />
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml

@@ -100,6 +100,4 @@
       <Conditions />
     </ResponseProfile>
   </ResponseProfiles>
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml

@@ -90,6 +90,4 @@
       <Conditions />
     </ResponseProfile>
   </ResponseProfiles>
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 0 - 2
MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml

@@ -45,6 +45,4 @@
   <ContainerProfiles />
   <CodecProfiles />
   <ResponseProfiles />
-  <SoftSubtitleProfiles />
-  <ExternalSubtitleProfiles />
 </Profile>

+ 1 - 5
MediaBrowser.Model/Dlna/DeviceProfile.cs

@@ -90,8 +90,7 @@ namespace MediaBrowser.Model.Dlna
         public CodecProfile[] CodecProfiles { get; set; }
         public ResponseProfile[] ResponseProfiles { get; set; }
 
-        public SubtitleProfile[] SoftSubtitleProfiles { get; set; }
-        public SubtitleProfile[] ExternalSubtitleProfiles { get; set; }
+        public SubtitleProfile[] SubtitleProfiles { get; set; }
       
         public DeviceProfile()
         {
@@ -100,9 +99,6 @@ namespace MediaBrowser.Model.Dlna
             ResponseProfiles = new ResponseProfile[] { };
             CodecProfiles = new CodecProfile[] { };
             ContainerProfiles = new ContainerProfile[] { };
-
-            SoftSubtitleProfiles = new SubtitleProfile[] { };
-            ExternalSubtitleProfiles = new SubtitleProfile[] { };
             
             XmlRootAttributes = new XmlAttribute[] { };
             

+ 4 - 4
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -518,7 +518,7 @@ namespace MediaBrowser.Model.Dlna
             {
                 // See if the device can retrieve the subtitles externally
                 bool supportsSubsExternally = options.Context == EncodingContext.Streaming &&
-                    ContainsSubtitleFormat(options.Profile.ExternalSubtitleProfiles, _serverTextSubtitleOutputs);
+                    ContainsSubtitleFormat(options.Profile.SubtitleProfiles, SubtitleDeliveryMethod.External, _serverTextSubtitleOutputs);
 
                 if (supportsSubsExternally)
                 {
@@ -526,7 +526,7 @@ namespace MediaBrowser.Model.Dlna
                 }
 
                 // See if the device can retrieve the subtitles externally
-                bool supportsEmbedded = ContainsSubtitleFormat(options.Profile.SoftSubtitleProfiles, _serverTextSubtitleOutputs);
+                bool supportsEmbedded = ContainsSubtitleFormat(options.Profile.SubtitleProfiles, SubtitleDeliveryMethod.Embed, _serverTextSubtitleOutputs);
 
                 if (supportsEmbedded)
                 {
@@ -547,11 +547,11 @@ namespace MediaBrowser.Model.Dlna
             return codec;
         }
 
-        private bool ContainsSubtitleFormat(SubtitleProfile[] profiles, string[] formats)
+        private bool ContainsSubtitleFormat(SubtitleProfile[] profiles, SubtitleDeliveryMethod method, string[] formats)
         {
             foreach (SubtitleProfile profile in profiles)
             {
-                if (ListHelper.ContainsIgnoreCase(formats, profile.Format))
+                if (method == profile.Method && ListHelper.ContainsIgnoreCase(formats, profile.Format))
                 {
                     return true;
                 }

+ 5 - 1
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -476,6 +476,10 @@ namespace MediaBrowser.Model.Dlna
         /// <summary>
         /// The external
         /// </summary>
-        External = 2
+        External = 2,
+        /// <summary>
+        /// The HLS
+        /// </summary>
+        Hls = 3
     }
 }

+ 3 - 0
MediaBrowser.Model/Dlna/SubtitleProfile.cs

@@ -9,5 +9,8 @@ namespace MediaBrowser.Model.Dlna
 
         [XmlAttribute("protocol")]
         public string Protocol { get; set; }
+
+        [XmlAttribute("method")]
+        public SubtitleDeliveryMethod Method { get; set; }
     }
 }

+ 14 - 10
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -550,24 +550,28 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                     };
                 }
 
-                if (!string.IsNullOrEmpty(info.Path))
-                {
-                    item.Path = info.Path;
-                }
-                else if (!string.IsNullOrEmpty(info.Url))
-                {
-                    item.Path = info.Url;
-                }
-
                 isNew = true;
             }
 
             item.RecordingInfo = info;
             item.ServiceName = serviceName;
 
+            var originalPath = item.Path;
+
+            if (!string.IsNullOrEmpty(info.Path))
+            {
+                item.Path = info.Path;
+            }
+            else if (!string.IsNullOrEmpty(info.Url))
+            {
+                item.Path = info.Url;
+            }
+
+            var pathChanged = !string.Equals(originalPath, item.Path);
+
             await item.RefreshMetadata(new MetadataRefreshOptions
             {
-                ForceSave = isNew
+                ForceSave = isNew || pathChanged
 
             }, cancellationToken);