NetNewsWire
NetNewsWire copied to clipboard
YouTube Inline Video Support
A common request from users is the ability to subscribe to YouTube RSS feeds and view YouTube videos within NetNewsWire. Since NetNewsWire is designed for reading articles instead of viewing videos, we haven't felt that adding this capability is worth the extensive effort of parsing the Media RSS tags and adding them to the Articles database.
I think we can add YouTube capabilities without having to change anything in the Articles database by adding the concept of a FeedTransformer and updating some core classes to support it. This concept isn't a complete solution to #2538, but will at least work for YouTube (and other video services).
FeedTransformer
This protocol would need to be implemented by any RSS feeds that we would like to massage prior to pushing them into the Articles database. Its purpose is to help the FeedFinder find unusual URL's and help the LocalAccountRefresher transform unusual article structures into something that fits in the Articles database.
protocol FeedTransformer {
/// Determines if this FeedTransformer is to be used for the given Feed URL
func owner(url: URL) -> Bool
/// Changes the URL using special rules to help find it
func correct(url: URL) -> URL
/// Transform the feed into something we would rather use
func transform(parsedFeed: ParsedFeed) -> ParsedFeed
}
FeedTransformerManager
This is a class used to retrieve all the current transformers in use.
class FeedTransformerManager {
public static let transformers: [FeedTransformer]
}
FeedFinder changes
In FeedFinder.find
we would need to get all the transformers and check to see if any of them are a owner. If so, we would need to correct the URL that we are searching for.
LocalAccountRefresher changes
Prior to calling account.update
we should check all the transformers to see if any of them are an owner. If so we would need to transform the ParsedFeed
using FeedTransformer.transform
.
RSParser Changes for Media RSS tags
We would need to update the ParsedItem
struct to have the optional structures necessary to hold the Media RSS elements. We would also need to enhance RSAtomParser
to populate the new ParsedItem
structs from the feed.
YouTubeFeedTransformer.owner
If the URL starts with https://www.youtube.com/watch?v=
we want to return true
.
YouTubeFeedTransformer.correct
We need to be able to construct URL of the YouTube RSS feed. See this article for more info on how to do this. https://eggfreckles.net/notes/youtube-rss
YouTubeFeedTransformer.transform
Each of the ParsedItems
in the given ParsedFeed
needs to be transformed. We should take the media:group
property and use it to populate the contentHTML
with the correct HTML. This should be the same embed code that websites use to embed YouTube videos. I think transforming the Media RSS tags and discarding them is perfectly fine since we would never use them in database queries.
This example of YouTube embed code will probably have to be tweaked to work better with NetNewsWire:
<iframe width="560" height="315" src="https://www.youtube.com/embed/585RFqVK8Dc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Edit: It might be better to just use a Video tag. Vinegar for Youtube does something like this as a browser extension.
We should keep in mind that other video services like Vimeo could use this transform code and make it reusable. In fact, if the YouTube implementation works, we already have 90% of the Vimeo implementation done and might as well go ahead and implement it.
Summary
I think this is a fairly straight forward implementation and not much work with the exception of the RSParser changes. Even that isn't too complicated as long as you understand a little Obj-C and how a SAX parser works.
Comments and critiques are welcome.
Though I’ve wanted NetNewsWire to be a reading app as much as possible, I’ve come to understand that there will be no end of requests for better YouTube support.
To my surprise, I myself have wanted better YouTube support. I started watching videos about making music (Rick Beato, Adam Neely, etc.) and it sure would be nice to be able to watch those in the app.
So, yes, let’s add better YouTube support.
On this specific proposal — it seems like FeedTransformer is doing two things: helping find feeds and transforming article content.
My initial thought is that these functions should be unbundled. We can have feed-finder-helpers and article-content-enrichers as separate things (since support for hypothetical website X might need only one of those things).
Feel free to convince me that the two functions really should be bundled into one, though — I could easily be wrong.
Another thought: if we separate out article-enrichers from feed-finder-helpers, that means we can put article-enrichers in our RSParser code rather than in the app, which means other developers could more easily benefit from them.
I think separating the two functions does make sense and is a better design. We would just need to make the owner
function common between the two, which isn't hard with a protocol extension.
If we put the article-enrichers in RSParser, we would probably want a way to make them optional and configurable by the user of FeedParser
in case they didn't want to use the article-enricher. That is kind of tricky because the parse
functions are static.
I guess we could put a static
variable on AtomParser
that you would set with the article-enrichers that you want to use. I'm just not fond of global static variables.
Maybe a better option is to put the article-enricher code in RSParser and leave it up to the implementer to execute the article-enricher in the client code after calling FeedParser.parse
. That way other consumers of RSParser have the code, but it is up to them to decide if they want to use it or not.
Another hiccup is that we would need the owner
function logic in RSParser to see if the article-enricher should be applied to a specific article based on the URL of the Feed.
For anyone requesting this feature I've been watching inline youtube using NNW for a while now with very few issues:
My solution was to run a simple external service that transforms the feed: https://github.com/levitatingpineapple/yt-rss
If you are using an Apple Silicon laptop - there is a brew
package for running it locally.
(If someone would be willing to test it, I could cross-compile one for Intel Macs too)
This way no changes to the reader were required.
Motivation
The service relies of yt-dlp for acquiring the actual source of the video. I preferred this approach since it already handles age and geo restricted content and does not show distracting recommendations after the end of the video.