diff --git a/NostrStreamer/Controllers/PodcastController.cs b/NostrStreamer/Controllers/PodcastController.cs new file mode 100644 index 0000000..7a9f849 --- /dev/null +++ b/NostrStreamer/Controllers/PodcastController.cs @@ -0,0 +1,118 @@ +using System.Xml; +using System.Xml.Serialization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Nostr.Client.Identifiers; +using NostrStreamer.Database; + +namespace NostrStreamer.Controllers; + +[Route("/api/podcast")] +public class PodcastController(StreamerContext db, Config config) : Controller +{ + [HttpGet("{id:guid}.xml")] + public async Task GetFeed([FromRoute] Guid id) + { + var stream = await db.Streams + .AsNoTracking() + .Include(a => a.User) + .Where(a => a.Id == id) + .SingleOrDefaultAsync(); + + if (stream == default) + { + Response.StatusCode = 404; + return; + } + + var pod = new PodcastRoot(); + pod.LiveItem = new() + { + Guid = stream.Id, + Title = stream.User.Title ?? "", + Description = stream.User.Summary, + Status = stream.State.ToString().ToLower(), + Start = stream.Starts, + End = stream.Ends ?? new DateTime(), + ContentLink = new() + { + Href = $"https://zap.stream/{stream.ToIdentifier().ToBech32()}", + Text = "Watch live on zap.stream!" + }, + Enclosure = new() + { + Url = new Uri(config.DataHost, $"stream/{stream.Id}.m3u8").ToString(), + Type = "application/x-mpegurl", + Length = 0 + } + }; + + Response.ContentType = "text/xml"; + var ns = new XmlSerializerNamespaces(); + ns.Add("podcast", "https://podcastindex.org/namespace/1.0"); + + var ser = new XmlSerializer(typeof(PodcastRoot)); + using var ms = new MemoryStream(); + ser.Serialize(ms, pod, ns); + ms.Seek(0, SeekOrigin.Begin); + await ms.CopyToAsync(Response.Body); + } +} + +[XmlRoot(ElementName = "enclosure")] +public class Enclosure +{ + [XmlAttribute(AttributeName = "url")] + public string Url { get; set; } + + [XmlAttribute(AttributeName = "type")] + public string Type { get; set; } + + [XmlAttribute(AttributeName = "length")] + public int Length { get; set; } +} + +[XmlRoot(ElementName = "contentLink")] +public class ContentLink +{ + [XmlAttribute(AttributeName = "href")] + public string Href { get; set; } + + [XmlText] + public string Text { get; set; } +} + +[XmlRoot(ElementName = "liveItem", Namespace = "https://podcastindex.org/namespace/1.0")] +public class LiveItem +{ + [XmlElement(ElementName = "title", Namespace = "")] + public string Title { get; set; } = null!; + + [XmlElement(ElementName = "description", Namespace = "")] + public string? Description { get; set; } + + [XmlElement(ElementName = "guid", Namespace = "")] + public Guid Guid { get; set; } + + [XmlElement(ElementName = "enclosure", Namespace = "")] + public Enclosure Enclosure { get; set; } = null!; + + [XmlElement(ElementName = "contentLink", Namespace = "https://podcastindex.org/namespace/1.0")] + public ContentLink ContentLink { get; set; } = null!; + + [XmlAttribute(AttributeName = "status")] + public string Status { get; set; } = "live"; + + [XmlAttribute(AttributeName = "start")] + public DateTime Start { get; set; } + + [XmlAttribute(AttributeName = "end")] + public DateTime End { get; set; } +} + +[XmlRoot(ElementName = "rss")] +public class PodcastRoot +{ + [XmlElement(ElementName = "liveItem", Namespace = "https://podcastindex.org/namespace/1.0")] + public LiveItem? LiveItem { get; set; } +} diff --git a/NostrStreamer/test.sh b/NostrStreamer/test.sh new file mode 100755 index 0000000..75cbda1 --- /dev/null +++ b/NostrStreamer/test.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +ffmpeg \ + -f lavfi -i "sine=frequency=1000:sample_rate=48000" \ + -re -f lavfi -i testsrc -g 300 -r 60 -pix_fmt yuv420p -s 1280x720 \ + -c:v h264 -b:v 2000k -c:a aac -ac 2 -b:a 192k -f flv rtmp://localhost:9005/basic/$1 +