NetNewsWire icon indicating copy to clipboard operation
NetNewsWire copied to clipboard

YouTube Inline Video Support

Open vincode-io opened this issue 1 year ago • 3 comments

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.

vincode-io avatar Sep 22 '22 12:09 vincode-io

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.

brentsimmons avatar Sep 24 '22 17:09 brentsimmons

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.

vincode-io avatar Sep 24 '22 19:09 vincode-io

For anyone requesting this feature I've been watching inline youtube using NNW for a while now with very few issues:

nnw

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.

levitatingpineapple avatar Nov 01 '23 15:11 levitatingpineapple