Podcast RSS/Atom Feed Viewer using JavaFX

By Raghu Nair, May 7, 2009

PodcastFeedViewer is a JavaFX application that plays medias from Podcast Feeds ( RSS or Atom feeds). Features of the PodcastFeedViewer are

  • Stores all the valid podcast feed urls using Storage API
  • Navigation between peristed podcast feeds
  • Navigation between the medias from the podcast feed
  • Accepts RSS and Atom feeds
  • User can delete the stored feeds using delete button

How to use

Press on Add button and enter the FeedURL text box .(Clicking Add button will make textbox editable). Press on Start Polling Button. You can navigate between the medias with in the feed using the lower left and right arrow buttons. Pressing delete button will remove the currently playing feed url. Pressing add button will add one more feed url to the list and all valid feed urls will be stored locally. You can navigate between feed urls using upper left and right arrows. During the startup of application all the stored feed urls will be loaded.

Podcasts are obtained from Atom or Rss feeds using the JavaFX Feed APIs (javafx.data.feed.atom.AtomTask or javafx.data.feed.rss.RssTask). Medias from RSS feeds are obtained using callback method onEntry(item:Item) by reading the enclosure object(javafx.data.rss.Enclosure) in Item(javafx.data.feed.rss.Item) and in Atom it is obtained using callback method onE link (javafx.data.feed.atom.Link).Medias are displayed using MediaPlayer (javafx.scene.media.MediaPlayer)

Understanding the Code

Entered FeedURL is send to the FeedValidator which parses and validates the feed. if the input starts with feed or rss tag it is a valid feed.

Source Code
...
    override var onInput = function(input:InputStream){
        var parser = PullParser {input:input;}
        parser.forward(2);
        var e = parser.event;
        if ( e.qname.name == "rss" ) {
            type = PodcastParser.RSS;
        }else if (e.qname.name == "feed" ){
            type = PodcastParser.ATOM;
        }        
        input.close();
        podcastParser.type = type;
    }
...

FeedTask is the base class for AtomTask and RssTask. Here is the code which start the polling.

Source Code
...
    public var feedTask:FeedTask ;
...
    public var type:Integer on replace {

        if ( type == ATOM ){
            feedTask = AtomTask{
                location:location;
                interval:Main.feedInterval;
                onFeed: function(feed:Feed){
                    Main.log("Feed - {feed}");
                    podcastFeed.title = feed.title.text;
                }

                onEntry: function(entry:Entry){
                    Main.log(" Entry - {entry}");
                    var length:Long ;
                    var mediaURL = null;
                    var apptype = null;
                    var media:PodcastMedia = null;
                    for (link in entry.links){
                        Main.log("Link - {link}");
                        
                        if ( link.type != null and 
                            (link.type.trim().startsWith("audio") or
                            link.type.trim().startsWith("video")) )  {
                            Main.log("link.type - {link.type}");
                            apptype  = link.type;
                        }else {
                            continue;
                        }

                        Main.log("link.length - {link.length}");

                        try {
                            if ( link.length != null and
                                not link.length.trim().equals("")) {
                                length = Long.parseLong(link.length);
                            }else {
                                length = 0;
                            }
                        }catch (e){ length = 0}

                        if ( link.href != null ){
                            Main.log("link.href - {link.href}");
                            mediaURL = link.href;
                            insert
                            (media = PodcastMedia{
                                length:length
                                mediaURL:mediaURL
                                type:apptype })
                            into podcastFeed.medias;
                            Main.log ("Adding Media {mediaURL}");
                        }
                        if ( link.title != null and link.title != "") {
                            media.title = link.title;
                        }
                    }
                    if ( media != null ){
                        if ( media.title == null ){
                            media.title = entry.title.text;
                        }
                        media.data =  entry.summary.text;
                    }
                }
            }
        }else if ( type == RSS ) {
            feedTask = RssTask{
                location:location

                interval:Main.feedInterval

                onChannel:function(channel:Channel){
                    Main.log("Channel - {channel}");
                    podcastFeed.title = channel.title;
                    podcastFeed.description = channel.description;
                }
                onItem:function(item:Item){
                    Main.log("item - {item}");
                    var media:PodcastMedia;
                    var enclosure = item.enclosure;
                    if ( enclosure != null and 
                        (enclosure.type.startsWith("audio") or
                        enclosure.type.startsWith("video") ) and 
                        enclosure.url.startsWith("http")) {
                        media = PodcastMedia{
                            mediaURL: enclosure.url
                            type: enclosure.type
                            length: enclosure.length
                        }
                    }else return;
                    media.title = item.title;
                    media.data = item.description;
                    insert  media into podcastFeed.medias;
                }
            }
        }
        if ( feedTask != null ){
            //Create podcast feed for Atom/RSS.
            podcastFeed = PodcastFeed{location:location  feedTask:feedTask}
            feedTask.start();
            feedTask.onDone = function(){
                isDone = true;
            }
....

if the feed contains medias feed location is persisted using storage apis

Source Code
	public function store(location:String){
    		Main.log("Adding {location} to persistence store");
    		var loc = "{location}\n";
    		var storage = Storage{
        		source: Main.applicationTitle
    		};
    		//Append 
    		var outputStream = storage.resource.openOutputStream(false);
    		outputStream.write(loc.getBytes());
    		outputStream.close();
	}

During the starup of the application all the persisted feed urls will be loaded back

Source Code
...
    var locactions:String[];
    var storage = Storage {
        source: Main.applicationTitle
    };
    var inputStream = storage.resource.openInputStream();
    if ( inputStream == null ){
            Main.log("There is no persisted data");
        return;
    }
    var stream = new java.io.ByteArrayOutputStream();
    var char = -1;
    while ((

    char = inputStream.read()) != - 1){
            stream.write(char);
    }
        stream.close();
    var line = stream.toString();
...

References