Quantcast
Channel: Multimedia – BCmoney MobileTV
Viewing all articles
Browse latest Browse all 38

JS Podcatcher (a Podcast client written in JavaScript)

$
0
0
No Gravatar
English: The "Made for iPod, iPhone, iPad...

English: The “Made for iPod, iPhone, iPad” emblem appearing on accessories approved by Apple Inc. for iPod, iPhone, and iPad. (Photo credit: Wikipedia)

So just this month my 5-year old iPhone 3GS finally bit the dust. I had been hanging on and managed to make it through a major liquid submersion (thanks to the good folks at Atlantic Cell Phone Repair) several cracked screens (thanks to the good folks at iCracked). I’m pretty stubborn, and now that I’ve finished off my Mobile contract for it, pretty much at all costs I really didn’t want to have to buy another discounted device which usually requires one to agree to the terms of a foolishly one-sided/restrictive 2-year or 3-year contract; likewise, I really don’t want to shell out anywhere near the full asking price in the $500-$1000 price range for a new smartphone. So it’s either go back to my old Nokia flip-phone and live in the early 2000’s or hack my old 4th generation iPod Touch into something with phone call abilities. Of course, I opted for the latter!

Luckily thanks to an excellent VoIP app called BRIA (of which a 4th gen. iOS 4 version is still available in the iTunes App Store), I was able to continue using my Anveo VoIP service (please enter Referral Code 5334764 if registering) which I was already using through BRIA on the iPhone. I’ve already described Anveo in the post “My Experiment in Cutting Cords (and costs) with VoIP” where I went over setting the VoIP service up on an iPhone and just how much could actually be saved per month by taking the plunge and switching to VoIP. I’ve found that with a little patience (often using replacements, such as Slingplayer in place of Bell MobileTV, or, SoundHound in place of Shazam) along with some disappointment (can’t get an older versions of Netflix, UFC, Fitocracy, Skype, Instapaper, and several other key apps), I was able to get a good amount (about half) of the apps I was most frequently using on my iPhone, downloaded to the iPod in their older iOS 4-supported versions.

One somewhat irreplaceable one though that I just simply could not find, nor find a replacement for was the basic “Podcasts” app built by Apple (common alternatives RSSradio, Overcast, Downcast, TuneIN, Slacker, etc all did not work either on my device). I mean, seriously Apple, WTF!? Even the very first iPod devices were within a few years of their release to become known as a type of “Podcatcher”. The term “podcasting” was first mentioned by Ben Hammersley in The Guardian newspaper in a February 2004 article as a portmanteau of the words “pod”, from the success in consumerizing digital music with the iPod and “broadcast”. As such, the native “Podcasts” app has been around since the early days, as Podcatching (better known as receiving and listening to Podcasts), became one of the main functions of iPods just as it continues to be a core functionality on the many other iOS devices. Why then, are older (iOS < 6) versions of the Podcasts app not still available through the iTunes App Store? They existed then, and now they’re just plain unavailable it seems. What if a legacy iPod user (anyone still on iOS 4 or lower for that matter) accidentally wipes or restores their device to factory settings? Tough luck if they didn’t store a backup that had the app. This is an example of planned obsolescence at its worst!!!

Could the Podcast app’s functionality be replaced with a quickly hacked together web app though? That’s the question I wanted an answer to. So I realized it definitely should be doable, as Podcasts to me have always simply been RSS news feeds with links to Audio files embedded in them in a variety of ways (and thanks to Apple’s aforementioned “Podcatching” dominance, also garnished with plenty of Apple-specific iTunes namespace syntactic metadata to appeal to the behemoth that is the iTunes Store and rank better therein).

Hence this post, which aims to concisely (from here on) describe how I took my original RSS parser from the post “RSS Reader in jQuery .vs. JavaScript”) on using JavaScript and/or jQuery to implement an RSS news reader, and modified it a few weeks ago to allow me to read the media links and embed codes.

I needed to add some conditional checks for the different ways of publishing the actual audio/video files shared by podcasts and vodcasts, respectively which differ between not just type of content (Podcast/Vodcast) but also between individual content publishers of the Podcast/Vodcast sources. What I found was that these variations amongst Publishers included using the <guid> element (RSS 1.0), other times using the url attribute of the <enclosure> (RSS 2.0), or other times still using the url attribute of the <media:content> (mRSS) element. All different XML elements and attributes to share the same basic thing, the location of their media files for playback (or in laymans terms, “the goods”). Here’s the simple modification:

function parsePodcast(data, limit) {
	var rss = data.getElementsByTagName("rss");		//<rss>
	  //Parse RSS Feed channel properties
	  channel = data.getElementsByTagName("channel");							//<channel>
	  title = data.getElementsByTagName("title")[0].childNodes[0].nodeValue;		//<title>
	  link = '#';
	  try {
		link = data.getElementsByTagName("link")[0].childNodes[0].nodeValue;		//<link>
	  } catch (ex) { }
	  description = data.getElementsByTagName("description")[0].childNodes[0].nodeValue; //<description>
	  image_url = 'podcast.png', image_link = '#', image_html=title+' (RSS Feed)';
		image = data.getElementsByTagName("image")[0];					//<image> (optional RSS element)
		if (!empty(image)) {
                    try {
		      image_url = image.getElementsByTagName("url")[0].childNodes[0].nodeValue;		//<url>
		      image_link = image.getElementsByTagName("link")[0].childNodes[0].nodeValue;	//<link>
                    } catch(ex) { }	
		}
		iTunesImage = '';
		try {
			iTunesImage = data.getElementsByTagName("itunes:image")[0];					//<itunes:image> (optional RSS element)
			if (!empty(iTunesImage)) {
				image_url = iTunesImage.getAttribute("href");		// ... href=""		
			}
		} catch (ex) { }
   	      //language = data.getElementsByTagName("language")[0].childNodes[0].nodeValue;//<language>
		  //copyright = data.getElementsByTagName("copyright")[0].childNodes[0].nodeValue;	//<copyright>
          //lastBuildDate = data.getElementsByTagName("lastBuildDate")[0].childNodes[0].nodeValue; //<lastBuildDate>
          //docs = data.getElementsByTagName("docs")[0].childNodes[0].nodeValue;	  		//<docs>
          //ttl = data.getElementsByTagName("ttl")[0].childNodes[0].nodeValue;				//<ttl>		
		  metadata = '<div style="display:block; clear:both;"><a href="'+link+'" class="external">'+'<img width="20%" src="'+image_url+'" title="'+title+'": "'+description+'" style="vertical-align:middle;"/>'+'</a></div>';//"<br/>" + lastBuildDate + " | " + copyright + "<br/>Following spec at: " +  docs + " | Cache for: " + ttl + " | " + language + "<div style="clear:both;">&nbsp;</div>";      

	  //Parse RSS Feed items 
      items = data.getElementsByTagName("item"); 											//<item>
      news = metadata+"<nav><ul>";
      for (i = 0; (i < items.length && i < limit); i++) {	  	
           title = items[i].getElementsByTagName("title")[0].childNodes[0].nodeValue;	  		  //<title>
			guid = items[i].getElementsByTagName("guid")[0].childNodes[0].nodeValue;				  //<guid>	
				id = !empty(guid) ? guid : title.substring(0,12)+'-'+item;					//unique ID
		   image = 'podcast.png';
			try {
				pic = items[i].getElementsByTagName("media:thumbnail")[0].getAttribute("url");  		  //<media:thumbnail> (image) mRSS2.0 only
					image = !empty(pic) ? pic : 'podcast.png';
			}
			  catch (ex) {
			}
			link = '#';		  		  //<link>
			try	{
				link = items[i].getElementsByTagName("link")[0].childNodes[0].nodeValue||'';		  		  //<link>				
			}
			  catch (ex) {
			}		   
           pubDate = items[i].getElementsByTagName("pubDate")[0].childNodes[0].nodeValue;		  //<pubDate>
		   desc = items[i].getElementsByTagName("description")[0].childNodes[0].nodeValue; //<description>		  
				description = desc.replace(/&lt;/gi,'<').replace(/&gt;/gi,'>');		   
		   audio = '#'; //AUDIO file
				try	{					
					audio = items[i].getElementsByTagName("enclosure")[0].getAttribute('url');		//RSS 2.0
				} catch (ex) {
					try {
						audio = items[i].getElementsByTagName("media:content")[0].getAttribute('');		//mRSS and iTunes <media:content url="..."/>
					} catch (ex) {					
						audio = guid;																//RSS 1.0
					}
				}			
			player = '';
				if (getFileExtension(audio)) {
					player =  '<audio controls><source src="'+audio+'" type="audio/mpeg">Player unable to play media file or non-HTML5 browser.</audio>';
				}			
		   news += '<div class="article"><a href="#lb'+i+'" title="Click once for summary, twice to visit source..."><img class="icon" src="'+image+'" alt="'+title+'" /> '+title+'</a><div id="lb'+i+'" class="lb"><a class="external" href="'+link+'"><img src="'+image+'" alt="'+title+'" /></a><a href="#top" class="close"><img src="close.gif" alt="Close" /></a><div class="story">'+description+player+'</div></div></div>';
      }
	  news += "</ul></nav>";
	return news;
}

You can try it out or download the code below:


-OR-

The demo works by taking a particular podcast’s web URL (for the marked-up RSS podcast feed) and putting it in the “Add” podcast field by hitting the “+” icon, then entering it, then clicking the “Add” button. The podcast being displayed will then immediately reload. The default limit set for number of podcast feed items to display is 20 (however this is easily adjusted). This limit was put in place because some podcast feeds actually list all podcast archives going back to their first show which can result in quite a high data filesize (and corresponding data plan hit when loading via Mobile network) not to mention slow to load, especially if the podcast has been around for a while or publishes updates frequently.

Since this post was auto-scheduled quite some time ago and I’ve been busy with work and life, I completely forgot to get around to adding a few other features I wanted in my JS Podcatcher. Those are, namely:

  1. the ability to add multiple podcasts where each one is clickable and shows a feed like the basic one,
  2. the ability to save those Podcasts so that your entire Podcast list would be remembered past the lifetime of a single Page or Session
  3. cache and synchronize the Podcasts to save data loading size/costs and not reload unless new Podcast episodes are available on the client (and/or server, possibly even parse feed server-side and inject a single episode at a time to the client)
  4. integration of my already developed iTunes API connector code, which would enable users to search through iTunes for specific podcast URLs for convenience, and lastly
  5. the audio files are embedded in HTML5 audio, however if that fails I should put an embedded Flash player in its place instead of just a download link to the actual media file (this would cover non-iOS mobile devices such as my Playbook which may support Flash but not HTML5 fully).

But it works for me, for now, since I’ve bookmarked a link which lists all my podcasts URLs, and should work for you too if you do something similar. The fact of the matter is a person can only listen to one Podcast audio file at a time, unless you have some kind of uncanny abilities that might qualify as super-powers. That said, for completeness, stay tuned for a future post where I add those five things and clean up the user experience in the new year.

flattr this!


Viewing all articles
Browse latest Browse all 38

Trending Articles