mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
fixes #888 - Support m3u8 subtitle playlists
This commit is contained in:
parent
3ba6364f25
commit
3ff3d04284
@ -1605,6 +1605,8 @@ namespace MediaBrowser.Api.Playback
|
|||||||
{
|
{
|
||||||
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.AllMediaStreams = mediaStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,
|
private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,
|
||||||
|
@ -5,6 +5,8 @@ using MediaBrowser.Controller.Dlna;
|
|||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
@ -18,20 +20,20 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace MediaBrowser.Api.Playback.Hls
|
namespace MediaBrowser.Api.Playback.Hls
|
||||||
{
|
{
|
||||||
[Route("/Videos/{Id}/master.m3u8", "GET")]
|
[Route("/Videos/{Id}/master.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
|
||||||
[Api(Description = "Gets a video stream using HTTP live streaming.")]
|
|
||||||
public class GetMasterHlsVideoStream : VideoStreamRequest
|
public class GetMasterHlsVideoStream : VideoStreamRequest
|
||||||
{
|
{
|
||||||
public bool EnableAdaptiveBitrateStreaming { get; set; }
|
public bool EnableAdaptiveBitrateStreaming { get; set; }
|
||||||
|
|
||||||
|
public SubtitleDeliveryMethod SubtitleMethod { get; set; }
|
||||||
|
|
||||||
public GetMasterHlsVideoStream()
|
public GetMasterHlsVideoStream()
|
||||||
{
|
{
|
||||||
EnableAdaptiveBitrateStreaming = true;
|
EnableAdaptiveBitrateStreaming = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("/Videos/{Id}/main.m3u8", "GET")]
|
[Route("/Videos/{Id}/main.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
|
||||||
[Api(Description = "Gets a video stream using HTTP live streaming.")]
|
|
||||||
public class GetMainHlsVideoStream : VideoStreamRequest
|
public class GetMainHlsVideoStream : VideoStreamRequest
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -359,7 +361,17 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
var playlistUrl = (state.RunTimeTicks ?? 0) > 0 ? "main.m3u8" : "live.m3u8";
|
var playlistUrl = (state.RunTimeTicks ?? 0) > 0 ? "main.m3u8" : "live.m3u8";
|
||||||
playlistUrl += queryString;
|
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))
|
if (EnableAdaptiveBitrateStreaming(state))
|
||||||
{
|
{
|
||||||
@ -369,16 +381,52 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
var variation = GetBitrateVariation(totalBitrate);
|
var variation = GetBitrateVariation(totalBitrate);
|
||||||
|
|
||||||
var newBitrate = totalBitrate - variation;
|
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;
|
variation *= 2;
|
||||||
newBitrate = totalBitrate - variation;
|
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();
|
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)
|
private bool EnableAdaptiveBitrateStreaming(StreamState state)
|
||||||
{
|
{
|
||||||
var request = state.Request as GetMasterHlsVideoStream;
|
var request = state.Request as GetMasterHlsVideoStream;
|
||||||
@ -397,9 +445,16 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
return state.VideoRequest.VideoBitRate.HasValue;
|
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);
|
builder.AppendLine(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,8 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
public string InputContainer { get; set; }
|
public string InputContainer { get; set; }
|
||||||
|
|
||||||
|
public List<MediaStream> AllMediaStreams { get; set; }
|
||||||
|
|
||||||
public MediaStream AudioStream { get; set; }
|
public MediaStream AudioStream { get; set; }
|
||||||
public MediaStream VideoStream { get; set; }
|
public MediaStream VideoStream { get; set; }
|
||||||
public MediaStream SubtitleStream { get; set; }
|
public MediaStream SubtitleStream { get; set; }
|
||||||
@ -78,6 +80,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
SupportedAudioCodecs = new List<string>();
|
SupportedAudioCodecs = new List<string>();
|
||||||
PlayableStreamFileNames = new List<string>();
|
PlayableStreamFileNames = new List<string>();
|
||||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
AllMediaStreams = new List<MediaStream>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string InputAudioSync { get; set; }
|
public string InputAudioSync { get; set; }
|
||||||
|
@ -9,8 +9,10 @@ using MediaBrowser.Model.Providers;
|
|||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -91,6 +93,29 @@ namespace MediaBrowser.Api.Subtitles
|
|||||||
|
|
||||||
[ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "StartPositionTicks", Description = "StartPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
public long StartPositionTicks { get; set; }
|
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
|
public class SubtitleService : BaseApiService
|
||||||
@ -106,6 +131,53 @@ namespace MediaBrowser.Api.Subtitles
|
|||||||
_subtitleEncoder = subtitleEncoder;
|
_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)
|
public object Get(GetSubtitle request)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(request.Format))
|
if (string.IsNullOrEmpty(request.Format))
|
||||||
@ -133,7 +205,7 @@ namespace MediaBrowser.Api.Subtitles
|
|||||||
request.Index,
|
request.Index,
|
||||||
request.Format,
|
request.Format,
|
||||||
request.StartPositionTicks,
|
request.StartPositionTicks,
|
||||||
null,
|
request.EndPositionTicks,
|
||||||
CancellationToken.None).ConfigureAwait(false);
|
CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,11 +193,12 @@ namespace MediaBrowser.Dlna.Profiles
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SoftSubtitleProfiles = new[]
|
SubtitleProfiles = new[]
|
||||||
{
|
{
|
||||||
new SubtitleProfile
|
new SubtitleProfile
|
||||||
{
|
{
|
||||||
Format = "srt"
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.External
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -339,11 +339,12 @@ namespace MediaBrowser.Dlna.Profiles
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SoftSubtitleProfiles = new[]
|
SubtitleProfiles = new[]
|
||||||
{
|
{
|
||||||
new SubtitleProfile
|
new SubtitleProfile
|
||||||
{
|
{
|
||||||
Format = "smi"
|
Format = "smi",
|
||||||
|
Method = SubtitleDeliveryMethod.External
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -155,11 +155,12 @@ namespace MediaBrowser.Dlna.Profiles
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ExternalSubtitleProfiles = new[]
|
SubtitleProfiles = new[]
|
||||||
{
|
{
|
||||||
new SubtitleProfile
|
new SubtitleProfile
|
||||||
{
|
{
|
||||||
Format = "ttml"
|
Format = "ttml",
|
||||||
|
Method = SubtitleDeliveryMethod.External
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,4 @@
|
|||||||
</CodecProfile>
|
</CodecProfile>
|
||||||
</CodecProfiles>
|
</CodecProfiles>
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -35,6 +35,4 @@
|
|||||||
<ContainerProfiles />
|
<ContainerProfiles />
|
||||||
<CodecProfiles />
|
<CodecProfiles />
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -39,6 +39,4 @@
|
|||||||
<ContainerProfiles />
|
<ContainerProfiles />
|
||||||
<CodecProfiles />
|
<CodecProfiles />
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -73,6 +73,4 @@
|
|||||||
</CodecProfile>
|
</CodecProfile>
|
||||||
</CodecProfiles>
|
</CodecProfiles>
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -39,6 +39,4 @@
|
|||||||
<ContainerProfiles />
|
<ContainerProfiles />
|
||||||
<CodecProfiles />
|
<CodecProfiles />
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -45,6 +45,4 @@
|
|||||||
<ContainerProfiles />
|
<ContainerProfiles />
|
||||||
<CodecProfiles />
|
<CodecProfiles />
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -68,8 +68,7 @@
|
|||||||
</CodecProfile>
|
</CodecProfile>
|
||||||
</CodecProfiles>
|
</CodecProfiles>
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles>
|
<SubtitleProfiles>
|
||||||
<SubtitleProfile format="srt" />
|
<SubtitleProfile format="srt" method="External" />
|
||||||
</SoftSubtitleProfiles>
|
</SubtitleProfiles>
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -106,8 +106,7 @@
|
|||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles>
|
<SubtitleProfiles>
|
||||||
<SubtitleProfile format="smi" />
|
<SubtitleProfile format="smi" method="External" />
|
||||||
</SoftSubtitleProfiles>
|
</SubtitleProfiles>
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -71,6 +71,4 @@
|
|||||||
</CodecProfile>
|
</CodecProfile>
|
||||||
</CodecProfiles>
|
</CodecProfiles>
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -99,6 +99,4 @@
|
|||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -107,6 +107,4 @@
|
|||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -110,6 +110,4 @@
|
|||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -93,6 +93,4 @@
|
|||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -93,6 +93,4 @@
|
|||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -93,6 +93,4 @@
|
|||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -78,6 +78,4 @@
|
|||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -62,8 +62,7 @@
|
|||||||
</CodecProfile>
|
</CodecProfile>
|
||||||
</CodecProfiles>
|
</CodecProfiles>
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
<SubtitleProfiles>
|
||||||
<ExternalSubtitleProfiles>
|
<SubtitleProfile format="ttml" method="External" />
|
||||||
<SubtitleProfile format="ttml" />
|
</SubtitleProfiles>
|
||||||
</ExternalSubtitleProfiles>
|
|
||||||
</Profile>
|
</Profile>
|
@ -76,6 +76,4 @@
|
|||||||
</CodecProfile>
|
</CodecProfile>
|
||||||
</CodecProfiles>
|
</CodecProfiles>
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -100,6 +100,4 @@
|
|||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -90,6 +90,4 @@
|
|||||||
<Conditions />
|
<Conditions />
|
||||||
</ResponseProfile>
|
</ResponseProfile>
|
||||||
</ResponseProfiles>
|
</ResponseProfiles>
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -45,6 +45,4 @@
|
|||||||
<ContainerProfiles />
|
<ContainerProfiles />
|
||||||
<CodecProfiles />
|
<CodecProfiles />
|
||||||
<ResponseProfiles />
|
<ResponseProfiles />
|
||||||
<SoftSubtitleProfiles />
|
|
||||||
<ExternalSubtitleProfiles />
|
|
||||||
</Profile>
|
</Profile>
|
@ -90,8 +90,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
public CodecProfile[] CodecProfiles { get; set; }
|
public CodecProfile[] CodecProfiles { get; set; }
|
||||||
public ResponseProfile[] ResponseProfiles { get; set; }
|
public ResponseProfile[] ResponseProfiles { get; set; }
|
||||||
|
|
||||||
public SubtitleProfile[] SoftSubtitleProfiles { get; set; }
|
public SubtitleProfile[] SubtitleProfiles { get; set; }
|
||||||
public SubtitleProfile[] ExternalSubtitleProfiles { get; set; }
|
|
||||||
|
|
||||||
public DeviceProfile()
|
public DeviceProfile()
|
||||||
{
|
{
|
||||||
@ -100,9 +99,6 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
ResponseProfiles = new ResponseProfile[] { };
|
ResponseProfiles = new ResponseProfile[] { };
|
||||||
CodecProfiles = new CodecProfile[] { };
|
CodecProfiles = new CodecProfile[] { };
|
||||||
ContainerProfiles = new ContainerProfile[] { };
|
ContainerProfiles = new ContainerProfile[] { };
|
||||||
|
|
||||||
SoftSubtitleProfiles = new SubtitleProfile[] { };
|
|
||||||
ExternalSubtitleProfiles = new SubtitleProfile[] { };
|
|
||||||
|
|
||||||
XmlRootAttributes = new XmlAttribute[] { };
|
XmlRootAttributes = new XmlAttribute[] { };
|
||||||
|
|
||||||
|
@ -518,7 +518,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
{
|
{
|
||||||
// See if the device can retrieve the subtitles externally
|
// See if the device can retrieve the subtitles externally
|
||||||
bool supportsSubsExternally = options.Context == EncodingContext.Streaming &&
|
bool supportsSubsExternally = options.Context == EncodingContext.Streaming &&
|
||||||
ContainsSubtitleFormat(options.Profile.ExternalSubtitleProfiles, _serverTextSubtitleOutputs);
|
ContainsSubtitleFormat(options.Profile.SubtitleProfiles, SubtitleDeliveryMethod.External, _serverTextSubtitleOutputs);
|
||||||
|
|
||||||
if (supportsSubsExternally)
|
if (supportsSubsExternally)
|
||||||
{
|
{
|
||||||
@ -526,7 +526,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See if the device can retrieve the subtitles externally
|
// 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)
|
if (supportsEmbedded)
|
||||||
{
|
{
|
||||||
@ -547,11 +547,11 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return codec;
|
return codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ContainsSubtitleFormat(SubtitleProfile[] profiles, string[] formats)
|
private bool ContainsSubtitleFormat(SubtitleProfile[] profiles, SubtitleDeliveryMethod method, string[] formats)
|
||||||
{
|
{
|
||||||
foreach (SubtitleProfile profile in profiles)
|
foreach (SubtitleProfile profile in profiles)
|
||||||
{
|
{
|
||||||
if (ListHelper.ContainsIgnoreCase(formats, profile.Format))
|
if (method == profile.Method && ListHelper.ContainsIgnoreCase(formats, profile.Format))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -476,6 +476,10 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The external
|
/// The external
|
||||||
/// </summary>
|
/// </summary>
|
||||||
External = 2
|
External = 2,
|
||||||
|
/// <summary>
|
||||||
|
/// The HLS
|
||||||
|
/// </summary>
|
||||||
|
Hls = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,5 +9,8 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
[XmlAttribute("protocol")]
|
[XmlAttribute("protocol")]
|
||||||
public string Protocol { get; set; }
|
public string Protocol { get; set; }
|
||||||
|
|
||||||
|
[XmlAttribute("method")]
|
||||||
|
public SubtitleDeliveryMethod Method { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
isNew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.RecordingInfo = info;
|
item.RecordingInfo = info;
|
||||||
item.ServiceName = serviceName;
|
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
|
await item.RefreshMetadata(new MetadataRefreshOptions
|
||||||
{
|
{
|
||||||
ForceSave = isNew
|
ForceSave = isNew || pathChanged
|
||||||
|
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user