diff --git a/backend/src/TeleWave.Api/Endpoints/IptvEndpoints.cs b/backend/src/TeleWave.Api/Endpoints/IptvEndpoints.cs index 14f46c0..bd5b475 100644 --- a/backend/src/TeleWave.Api/Endpoints/IptvEndpoints.cs +++ b/backend/src/TeleWave.Api/Endpoints/IptvEndpoints.cs @@ -82,8 +82,8 @@ public static class IptvEndpoints ); return Results.File( - Encoding.UTF8.GetBytes(RenderGuide(channels, Origin(request))), - "application/xml", + RenderGuide(channels, Origin(request)), + "application/xml; charset=utf-8", fileDownloadName: "telewave-epg.xml" ); } @@ -137,35 +137,45 @@ public static class IptvEndpoints private static string Attribute(string value) => value.Replace("\"", string.Empty).ReplaceLineEndings(" ").Trim(); - private static string RenderGuide(IReadOnlyList channels, string origin) + private static byte[] RenderGuide(IReadOnlyList channels, string origin) { - var output = new StringBuilder(); + using var output = new MemoryStream(); + // Пишем сразу в поток в UTF-8, а не в StringBuilder: XmlWriter поверх строки объявил бы + // encoding="utf-16" (строки .NET — UTF-16), тогда как байты мы всё равно отдаём в UTF-8. + // Часть плееров (Go encoding/xml) отвергает такое расхождение декларации и реальных байт. + // BOM не пишем: XMLTV его не ждёт, и он мешает строгим парсерам. // Через XmlWriter, а не склейкой строк: в названия и описания приходит текст из библиотеки, // и экранировать его руками — верный способ однажды отдать битый XML. - using var writer = XmlWriter.Create( - output, - new XmlWriterSettings { Indent = true, Encoding = Encoding.UTF8 } - ); - - writer.WriteStartDocument(); - writer.WriteStartElement("tv"); - writer.WriteAttributeString("generator-info-name", "TeleWave"); - - // Сначала все каналы, потом все передачи: XMLTV требует именно такого порядка секций. - foreach (var channel in channels) - WriteChannel(writer, channel, origin); - - foreach (var channel in channels) + using ( + var writer = XmlWriter.Create( + output, + new XmlWriterSettings + { + Indent = true, + Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), + } + ) + ) { - foreach (var programme in channel.Programmes) - WriteProgramme(writer, channel.Slug, programme, origin); + writer.WriteStartDocument(); + writer.WriteStartElement("tv"); + writer.WriteAttributeString("generator-info-name", "TeleWave"); + + // Сначала все каналы, потом все передачи: XMLTV требует именно такого порядка секций. + foreach (var channel in channels) + WriteChannel(writer, channel, origin); + + foreach (var channel in channels) + { + foreach (var programme in channel.Programmes) + WriteProgramme(writer, channel.Slug, programme, origin); + } + + writer.WriteEndElement(); + writer.WriteEndDocument(); } - writer.WriteEndElement(); - writer.WriteEndDocument(); - writer.Flush(); - - return output.ToString(); + return output.ToArray(); } private static void WriteChannel(XmlWriter writer, IptvChannelDto channel, string origin)