Generate Your iTunes/Podcast RSS Feed With Rock

  • By Jim Michael One Year Ago

The Problem

Out of the box Rock ships with two content channels (Podcast Series and Podcast Message) that are configured to drive the display of your service messages on the web site, and the default external web site demonstrates these channels in action. However, Rock is not set up to generate a podcast feed that can be consumed by iTunes and podcast apps. The goal is to generate a podcast RSS feed from these same content channels, so that as your messages are added to the channel, your podcast feed is automatically updated as well. This article will walk you through creating such a feed.


First Things First

We’ll assume that you already have both the Podcast Series and Podcast Message content channels set up and working for your needs, populated with content from your service messages. To make things work we'll need to add some additional attributes that relate to the RSS feed, and we’ll describe those in context below... but this will not be a step-by-step tutorial on configuring the podcast content channels from scratch, or customizing their display on the web site, since they are (mostly) set up out of the box to do this already.


The first thing to notice is that little “Enable RSS” checkbox in the settings of a Rock Content Channel. This seemingly minor setting enables a powerful feature in Rock, which can generate a RSS/XML/whatever “feed” of a content channel’s… uh… content, all formatted via (of course) Lava! This lets you use the same content channels that drive messages on the web site to also generate any number of specialized feeds for whatever other needs you have… without duplicating the content itself. Oh yeah!

Enable RSS


That said, the first thing you need to do to get any sort of feed from a content channel is to check that box. For our needs, we wanted an iTunes RSS feed that publishes our individual weekend messages, so we clicked Enable RSS on the Podcast Message content channel.


The Basics

Now that we have RSS enabled, how do we access it? Per the documentation, there’s a convenient endpoint built into Rock at http://yourserver.com/GetChannelFeed.ashx?ChannelId=x that will generate a feed. The only required parameter at this URL is ChannelId=x, that tells Rock which Content Channel (Id) to generate a feed for. Note that you can only point ChannelId=x at a content channel that has RSS enabled, otherwise it will return an error. This is an important safety feature to prevent someone from visiting this URL and trying out various random Channel Ids to expose (potentially) private information within any content channel in your system. Hopefully it’s clear that you should only enable RSS on a content channel you DO intend to expose to the public. Make sense?

Assuming you’ve enabled RSS on the channel, if you visit http://yourserver.com/GetChannelFeed.ashs?ChannelId=x (where x is the Id of your Podcast Message content channel) you should get some simple RSS back that looks like this (cut off here after one item for brevity).

    <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
    <title>Podcast Message</title>
    <link>https://thecrossing.church
    <description>Channel to store information about the podcast messages.</description>
    <language>en-us</language>
    <ttl>120</ttl>
    <lastBuildDate>Sun, 11 Jun 2017 15:40:00 -0500</lastBuildDate>
    <item>
    <title>June 11</title>
    <guid>?Item=1038</guid>
    <link>?Item=1038
    <pubDate>Sun, 11 Jun 2017 00:00:00 -0500</pubDate>
    <description></description>
    </item>
    </channel>
    </rss>
Note: I recommend using Chrome when visiting a feed as it will show the raw XML. Firefox and Safari are too “smart” and recognize the content as RSS and want to subscribe you to it, or display it in a rendered way, which is not what we want when developing the feed and need to see its contents. You can also add the &debug=1 parameter to the querystring to show the XML (as well as all available merge fields) in any browser, if you prefer.

Of course, the information returned in your feed will be completely different than ours, but you get the idea. This is a super-basic RSS feed Rock is configured with out of the box, and you’re probably wondering where it's configured… glad you asked! If you drill down to Admin Tools | General Settings | Defined Types and open the Global | Lava Templates Defined Type you’ll see this Default RSS Feed.

Unfortunately, while this template is valid RSS, it is not rich enough to publish a podcast, as it’s missing a bunch of necessary tags. It also doesn’t grab any of the media URLs for the content channel items, which is kind of the entire point of a podcast… so this feed -- while a good start -- won’t get the job done.

Back to the RSS URL… the Rock devs thought ahead and gave us the flexibility to not have just one RSS feed, but any number of them, for any purpose such a feed is needed. So how do we tell Rock which feed we want to get? Via another query parameter: TemplateId=y. This is the Id of the Defined Value (template) we create in the Global | Lava Templates Defined Type. When you hit GetChannelFeed.ashx with no TemplateID=y parameter, Rock returns the Default RSS feed... but by adding another defined value of our own creation, we can tell Rock to return that via http://yourserver.com/GetChannelFeed.ashx?ChannelID=x&TemplateId=y where y is the Id of the template we created to hold our iTunes RSS feed. If your head is spinning, sit tight… it should all make sense soon.


Create Your Feed

Let’s add a new template to hold our iTunes RSS feed. There’s no technical reason you couldn’t just modify the default RSS feed, but I prefer to leave the default one alone and create a new one for its intended purpose… and you’re likely going to need two custom feeds, anyway (assuming you want both Audio and Video podcasts). In Admin Tools | General Settings | Defined Types, open Global | Lava Templates and add a new template. Give it a name (we called ours iTunes Audio Feed), give it a description, and give it a Mime Type of application/rss+xml. Then save it. Ours ended up with the Id 969, but yours will surely be different. Just look at the URL of the page when you have your new template open to see its Id.

RSS Defined Values


Now edit your template again so we can continue configuring it. Here’s a complete listing of the iTunes Audio Feed we use here. Much of it might not make sense, but we’ll pick it apart line-by-line below. Ultimately you’ll want your feed to look almost identical to this, but (obviously) customized with your own information. Also, don’t be tempted to simply copy/paste this into your new template and try accessing it with GetChannelFeed.ashx… it will almost certainly throw a Lava error because you don’t (yet) have any of the additional attributes it's referencing.

Our RSS feed

{%- assign timezone = 'Now' | Date:'zzz' | Replace:':','' -%}
{%- assign imageURL = 'https://feeds.wcrossing.org/Content/iTunes/iTunes_Logo_1400x1400.png' -%}
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
<channel>
    <title>thecrossing.church (Audio)</title>
    <copyright>{{ 'Now' | Date: 'yyyy' }}</copyright>
    <link>https://thecrossing.church</link>
    <description>Audio messages from The Crossing Church in Chesterfield, Missouri, led by Pastor Greg Holder. We are a multi-site church with campuses around the St. Louis metro area. Visit us online https://thecrossing.church</description>
    <language>en-us</language>
    <webmaster>someone@yourcompany.com</webmaster>
    <managingEditor>someone@yourcompany.com</managingEditor>
    <ttl>{{ Channel.TimeToLive }}</ttl>
    <lastBuildDate>{{ 'Now' | Date:'ddd, dd MMM yyyy HH:mm:00' }} {{ timezone }}</lastBuildDate>
    <itunes:author>The Crossing Church, Chesterfield, MO</itunes:author>
    <itunes:summary>Audio messages from The Crossing Church in Chesterfield, Missouri, led by Pastor Greg Holder. We are a multi-site church with campuses around the St. Louis metro area. Visit us online at https://thecrossing.church</itunes:summary>
    <itunes:owner>
        <itunes:name>The Crossing Church</itunes:name>
        <itunes:email>podcast@thecrossing.church</itunes:email>
    </itunes:owner>
    <itunes:explicit>No</itunes:explicit>
    <itunes:image href="{{ imageURL }}"></itunes:image>
    <itunes:category text="Religion &amp; Spirituality">
    <itunes:category text="Christianity"></itunes:category>
    </itunes:category>
    <itunes:keywords>Crossing Church St. Louis Missouri God Sermon Bible Scripture Jesus Grace Hope Love Christian</itunes:keywords>
{%- for item in Items -%}
    {%- assign mediaURL = item | Attribute:'AudioLink', 'Url'-%}
    {%- assign parent = item | Property:'ParentItems' | First  | Property:'ContentChannelItem' -%}
    {%- assign expireDays = 'Now' | DateDiff: parent.ExpireDateTime, 'd' -%}
    {%- if mediaURL != '' and expireDays > 0 -%}
        {%- assign size = item | Attribute:'AudioFileSize' -%}
        {%- assign duration = item | Attribute:'ServiceDuration' -%}
        {%- assign summary = item.Content | StripHtml | Escape -%}
        {%- assign speaker = item | Attribute:'Speaker' | Escape -%}
        {%- assign pubdate = item.StartDateTime | Date:'ddd, dd MMM yyyy 11:00:00' -%}
        <item>
            <title>{{ parent.Title | Escape }}: {{ item.Title | Escape }}</title>
            <itunes:author>{{ speaker }}</itunes:author>
            <guid>{{ mediaURL }}</guid>
            <link>https://thecrossing.church</link>
            <pubDate>{{ pubdate }} {{ timezone}}</pubDate>
            <copyright>{{ pubdate | Date:'yyyy' }}</copyright>
            <description>{{ summary }} Speaker: {{ speaker }}</description>
            <enclosure url="{{ mediaURL }}" length="{{ size }}" type="audio/mpeg"></enclosure>
            <itunes:summary>{{ summary }} Speaker: {{ speaker }}</itunes:summary>
            <itunes:image href="{{ imageURL }}"></itunes:image>
            <itunes:duration>{{ duration }}</itunes:duration>
        </item>
    {%- endif -%}
{%- endfor -%}
</channel>
</rss>

Hopefully you can see that the feed is just a combination of hard-coded tags and Lava that grabs specific data from the content channel items (and its parent item) to construct a dynamic XML/RSS document that can be consumed by iTunes (or any similar podcast host.)

NOTE: RSS tags are case-sensitive, so be sure to enter them properly. A few that can trip you up are managingEditor, lastBuildDate, and pubDate.

That’s It! (sort of).

If you’re a web devevloper already familiar with iTunes, podcasts, and RSS feeds and you're tracking 100% with this and just wanted to know how/where to create your feed, you can stop here and get to it. You should have enough info per the above feed to make the necessary adds/changes for your environment. The rest of this article is for those who need a little more explanation of what’s going on and how to put it all together.


Picking The Feed Apart

Now that the know-it-alls have left, let’s dissect this file line by line so we really understand what’s going on: Note: It might be convenient to copy/paste the entire above feed into YOUR new template, and open it on another monitor so you can reference it and make necessary changes real-time as you read through the rest of this article. Just a thought.

Note: if you're new to Lava the {%- syntax (the addition of the - character in opening/closing Lava) might be unfamiliar. This tells Lava to remove any whitespace before and after the rendered result. This is important in this application, as all of the Lava assignments that don't render anything to "screen" would create tons of newlines within the resulting XML, which would still work but be very ugly.

Here we go!

       {%- assign timezone = 'Now' | Date:'zzz' | Replace:':','' -%}
{%- assign imageURL = 'https://feeds.wcrossing.org/Content/iTunes/iTunes_Logo_1400x1400.png' -%}

Right at the top we’re simply assigning some things to variables, as they get used multiple times throughout the file. timezone is used in a couple of places to append the web server’s timezone. imageURL is the complete publicly-accessible URL to your organization’s logo image. Apple has some pretty specific requirements for podcast images (they must be square, between 1400x1400 and 3000x3000 pixels, and must be a PNG or JPG using the RGB color space)… so you’ll probably need to create a version of your logo just for your podcasts and stick it out on a web server as it’s unlikely that your default organization logo in Rock meets these requirements.



    <?xml version="1.0" encoding="utf-8"?>
    <rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">

These two lines tell the client that it’s RSS XML, and an iTunes-specific flavor at that. You will see throughout this file tags containing :itunes. These are iTunes-specific (and often required) tags, but are generally ignored by other podcast hosts so shouldn’t cause a problem for non-iTunes use.



    <channel>
        <title>thecrossing.church (Audio)</title>

Here we’re opening the <channel> tag, which defines/encloses the podcast (content channel) itself, as well as the <title> of the podcast, which is the first child tag of <channel>. The default RSS feed we showed earlier grabs the name of the content channel for this, but we hard coded it here, for two reasons: 1. It’s unlikely that the name of your Content Channel (which is by default “Podcast Message”) is what you want to NAME your podcast to the world, and 2. Even if you’re ok with renaming your content channel to meet the needs of the podcast, you’ll likely want two different feeds (Audio & Video podcasts) anyway, and you wouldn’t want both of them to have the exact same name, right?… so hard coding the name you want for the specific feed makes sense. Here you can see we named our audio feed “thecrossing.church (Audio).” Obviously, change this to whatever makes sense for you.



    <copyright>{{ 'Now' | Date: 'yyyy' }}</copyright>

Here we’re using some Lava in the <copyright> tag to grab just the current year, which becomes the copyright date of the feed. Later you’ll see that each content item also gets its own specific copyright year.



    <link>https://thecrossing.church</link>
    <description>Audio messages from The Crossing Church in Chesterfield, Missouri, led by Pastor Greg Holder. We are a multi-site
    church with campuses around the St. Louis metro area. Visit us online at https://thecrossing.church</description>

Hopefully these two tags are self-explanatory. <link> is used by podcast clients to provide a link back to your “site.” It can be any URL, but pointing people at your own website makes the most sense, which is what we did here.

<description> is just what it sounds like. This is the long(ish) description of your podcast that people will see when they search for it in iTunes, or within the podcast app after they subscribe to it. Similar to <title>, we chose NOT to grab the description of the Content Channel via Lava for this and instead hard code it, for the same reasons.



    <language>en-us</language>
    <webMaster>someone@yourcompany.com</webMaster>
    <managingEditor>someone@yourcompany.com</managingEditor>
    <ttl>{{ Channel.TimeToLive }}</ttl>

<language> is necessary for iTunes, which determines the app stores your podcast may be exposed in. Set it to the code appropriate for your location.

<webmaster> and <managingEditor> should point at an email address for your organization that will always deliver to someone. We chose to create a podcast@thecrossing.church distribution list with someone from IT and web departments on it, in case anyone “out there” needs to contact someone at the organization regarding the podcast.

<ttl> (Time To Live) is a setting on the Podcast Message content channel that we’re grabbing with Lava. It defines (in minutes) how long the feed should be cached before needing to be refreshed. Set this to a reasonable number to prevent your feed from needing to re-generate every time someone subscribes to your podcast, but short enough that changes to the feed don’t take too long to be seen. We set ours to 120 minutes.


    <lastBuildDate>{{ 'Now' | Date:'ddd, dd MMM yyyy HH:mm:00' }} {{ timezone }}</lastBuildDate>
    <itunes:author>The Crossing Church, Chesterfield, MO</itunes:author>

<lastBuildDate> Here we're inserting the “now” Date & Time down to the second, and appending the timezone. This makes the last "build date" always the instant the RSS feed is generated.

<itunes:author> defines the “author” of the podcast feed itself. Typically this would be your church name, but you can put just about anything you want here. We chose to state our church name AND our location, to help distinguish us from the (many) other “Crossing”s out there.



    <itunes:summary>Audio messages from The Crossing Church in Chesterfield, Missouri, led by Pastor Greg Holder. We are a multi-site 
    church with campuses around the St. Louis metro area. Visit us online at https://thecrossing.church</itunes:summary>

Notice something familiar about this <itunes:summary> tag? Yes, it’s exactly the same content as the <description> tag! Here’s the deal… for archaic and unfathomable reasons, sometimes the <description> tag is used, and sometimes (in Apple-land) the <itunes:summary> tag is used. Even within the Apple ecosystem, iOS will use one and iTunes on OSX/PC will use the other. Bottom line: you need both in your feed, to cover all the bases. Don’t worry, it will only show up once on any client as none of them use BOTH tags, and the other is simply ignored.



    <itunes:owner>
        <itunes:name>The Crossing Church</itunes:name>
        <itunes:email>podcast@thecrossing.church</itunes:email>
    </itunes:owner>

These tags are similar in function to the prior <webmaster> and <managingEditor> tags, but are iTunes-specific, and necessary. <itunes:name> is the “owner” of the podcast… typically the name of your organization. <itunes:email> is an email address Apple will use to communicate with your regarding your podcast, so make sure it’s a valid address that delivers to someone in your org who will know what to do with any mail that arrives to it. Take care to note that this section defines an <itunes:owner> tag with two child tags. Using indentation as shown is important to maintaining the readability of your feed.



    <itunes:explicit>No</itunes:explicit>
    <itunes:image href="{{ imageURL }}" />
    <itunes:category text="Religion &amp; Spirituality">
    <itunes:category text="Christianity" />
    </itunes:category>

<itunes:explicit> is required, and can only be set to Yes or No. Hopefully you can enter “No” here… otherwise you’ve got one strange church!

<itunes:image> Your podcast image URL is inserted into this tag via the imageURL variable we defined at the top of the feed.

<itunes:category> is required, and Apple allows a primary category with sub-categories. Here we’re defining the top category as Religion & Spirituality with a sub-category of Christianity, which is what most churches will likely want. Note the & character must be escaped with &amp; as shown, as it’s a reserved character in XML. Pay close attention to the syntax here, where two <itunes:category text= tags are defined (the first one with JUST a closing > and the second with a closing />) and then a single </itunes:category> tag closes both of them.



    <itunes:keywords>Crossing Church St. Louis Missouri Sermon Bible Scripture Jesus Grace Hope Love Christian
    Christianity God</itunes:keywords>

<itunes:keywords> is a list of keywords you want your podcast indexed with, so searches in iTunes for those words will (theoretically) bring up your podcast in the results. I’ve seen conflicting information on whether this list should be words separated by spaces, or commas. I honestly don’t know which one is correct so let me know if you know for sure which way Apple wants it… but spaces seems to be working for us, here. Either way, you likely want a list of words that contain your church name, your pastor, your location, and other keywords you deem relevant to your podcast.



    {%- for item in Items -%}

So far everything we’ve done has been defining information about the podcast itself. Now we get to the good stuff, where we start pulling in data from the content channel about the individual items (messages) that become the episodes of your podcast. This first line of Lava iterates over all items within the Items collection, and everything between this opening {%- for item in Items -%} line and the closing {%- endfor -%} line will be repeated for every item in the content channel.



    {%- assign mediaURL = item | Attribute:'AudioLink' -%}

Here we are assigning mediaURL the AudioLink content item attribute, which must be populated with each item's URL to the media (mp3, mp4, etc) of your podcast episode. This assumes your files are already sitting on a publicly-accessible web server somewhere and that you've entered these URLs into each item in the content channel.



    {%- assign parent = item | Property:'ParentItems' | First  | Property:'ContentChannelItem' -%}
    {%- assign expireDays = 'Now' | DateDiff: parent.ExpireDateTime, 'd' -%}

Here we’re assigning two more variables for each content channel item (which is why they are defined here within the loop, instead of at the top of the feed.) parent gives us access to the content channel item’s parent content channel item (the Podcast Series), which we’ll need for a couple of things. expireDays gets us the ExpireDateTime property of the item’s parent item… specifically, the date the Podcast Series is set to expire, in number of days.

This is necessary since we want to limit how far back our podcasts go. If you’re a church who prefers to include “everything we’ve ever done” in the podcast RSS, you can modify the logic to ignore the Expiration Date of the series (you probably won’t even enter any expire dates on the series items). But if you’re like us and you only want to go back “so far” in your podcast RSS feed (regardless of how many content channel items there are), you’ll want to grab the Expire Date of the series as shown above.



    {%- if mediaURL != '' and expireDays > 0 -%}

Here’s where we determine if we want to show the specific item in the podcast feed. First we’re checking if there’s even a valid media URL for the item (if there’s no audio file, there’s no point in having that item in the podcast feed, right?) AND we’re checking to make sure the podcast’s series isn’t expired. If BOTH of these tests are true, we continue on to render the item… if not, we simply exit the loop and look for the next item.



    {%- assign size = item | Attribute:'AudioFileSize' -%}
    {%- assign duration = item | Attribute:'ServiceDuration' -%}
    {%- assign summary = item.Content | StripHtml | Escape -%}
    {%- assign speaker = item | Attribute:'Speaker' | Escape -%}
    {%- assign pubdate = item.StartDateTime | Date:'ddd, dd MMM yyyy 11:00:00' -%}

Now we’ve made it past the if statement and want to process this content channel item. Again, we make some initial variable assignments for various parts of the item. This isn’t strictly necessary as we could have just put the Lava in-place within the proper tags, but we thought it was more readable to have most of the complex Lava outside of the RSS itself and use variables within the tags. Do what you prefer.

size is assigned the AudioFileSize text attribute we added to our Podcast Message items. It’s expected that a podcast app will display the size of a media file before the person downloads it, and that’s what this accomplishes. Because your files are likely sitting on a cloud server with no easy (programatic) way to determine their size, we instead created an attribute on the item that gets manually entered with the size of the MP3 file (in bytes.) You'll need to add this AudioFileSize text attribute to your Podcast Message channel. NOTE: If you read down to the "Another approach to Audio Files" section below, we show you an alternative (better!) way to do this that lets you upload your media file FROM Rock and you don't have to manually enter the size into an attribute, especially if you're staring out fresh and don't have existing media in the cloud.

duration is assigned a ServiceDuration text attribute we added to our Podcast Message items. Similar to size, the length of a podcast is required in the RSS feed, and since there’s no (easy) way to programmatic ally determine the file’s length in time, we enter it manually on each item in HH:MM:SS format. You'll need to add this ServiceDuration text attribute to your Podcast Message channel.

summary is assigned the Content property of the item. This is usually the description of the specific service/message this item represents. The StripHtml and Escape filters do what they say. HTML is not allowed in a RSS summary, so we strip it in case someone used HTML there (perhaps for display on the web site). Escape takes care of escaping any reserved characters (& and ‘ are the most common ones found in summary text) to prevent the XML from breaking. You will see Escape used in several tags here to ensure the rendered XML is valid.

speaker is assigned the Speaker text attribute on the item and lets you enter the name of the person(s) who taught that message. It’s free-form text so you can enter things like “Bob Smith”, “Pastor Bob”, or “Jack & Jill Hill”.

pubdate is assigned the StartDateTime property (labelled “Active Date”) of the content channel item. However, there’s a wrinkle… podcast RSS requires a very specific format for the “publish date” of an item, which includes the Date, Time, and Timezone. But by default the “Active Date” property of a Podcast Message item hides the time component… so if you enter just a date there, Lava would return “12:00am” as the time component (because you never entered the time component of the property)... and you probably don’t want your podcasts to say they’re all published at 12:00am.

We chose to just hard-code-append 11:00:00 am to the date we grab from the item, because the file we upload is almost always from our 11:00am service on Sundays. The other way to fix this issue would be to modify the Podcast Message content channel type to NOT hide the time portion of the date (which it does by default). That would allow you to enter the time along with the date when you’re entering the content item details. If you do that, you’ll need to modify the pubdate = Lava to return the time component of the property instead of our hard-coded 11:00:00.



    <item>
        <title>{{ parent.Title | Escape }}: {{ item.Title | Escape }}</title>

Here we’re finally opening the <item> tag, which contains item-specific RSS tags. The first one is <title>. Here we’re inserting the title of the parent content channel item, which is the message series. Then we print a colon, then the item’s title (the title of this specific message.) This ends up rendering something similar to: <title>The Big Picture: Live with Hope</title> so each podcast episode follows a Series: Message format. If we didn’t grab the parent.Title, all of the podcast episodes would just show the item.Title and people would have no idea what series the message came from. If for some reason you don’t want the series included in a podcast title, just remove {{ parent.Title | Escape }}: and leave only the {{ item.Title | Escape }} Lava.



            <itunes:author>{{ speaker }}</itunes:author>
            <guid>{{ mediaURL }}</guid>
            <link>https://thecrossing.church</link>
            <pubDate>{{ pubdate }} {{ timezone}}</pubDate>

<itunes:author> is set here to be the person (speaker) who delivered the message, which is our preference. If you prefer, you can remove {{ speaker }} and simply hard-code your organization’s name and then the podcast author would show “Your Organization”

<guid> is a required tag that must be unique for every item, as it is used by clients to detect a new or changed item. The common convention is to use the URL of the media for the item as that should always be unique.

<link> is just like the prior tag at the channel level, but they want a link back to your web site for each item, too. Not sure why.

<pubDate> Here we’re inserting the pubdate variable we defined earlier and appending the timezone. The format of this is very specific, and in my experimentation iTunes would not show ANY date on the item if I left off the time or timezone components, for example. So just do it as shown.



    <copyright>{{ pubdate | Date:'yyyy' }}</copyright>
    <description>{{ summary }} Speaker: {{ speaker }}</description>

<copyright> is similar to the same tag defined at the channel level, but here we’re grabbing the Year component from the pubdate of the item, so each item will have a copyright in the year it was added to the channel. Slick, eh?

<description> is similar to the same tag defined at the channel level, but here we’re inserting the summary of the item, which is the description of this specific sermon/message. We’re then appending Speaker: {{ speaker }} here because most podcast apps will not expose the <intunes: author> tag obviously in the UI… you have to dig for it in the episode properties or similar. So we configured the item summary to always end with the name of the speaker, which can be useful to the end user if your church has different people speak regularly. If you don't want that, just delete that part of the Lava.



    <enclosure url="{{ mediaURL }}" length="{{ size }}" type="audio/mpeg"/>

Ah, we’re finally doing something useful. The <enclosure> tag is where we insert URL to the media, the size of the file, and the mime type. Don't be confused by that length= parameter. It's not the duration (length in time) of the file, but the size of it, in bytes.



    <itunes:summary>{{ summary }} Speaker: {{ speaker }}</itunes:summary>

Uh… remember earlier where I said the <description> and <itunes:summary> tags hold the exact same info, because different clients use one or the other? Yeah, same thing here... so we’re doing exactly what we did in <description> for the item.



    <itunes:image href="{{ imageURL }}" />
    <itunes:duration>{{ duration }}</itunes:duration>

Supposedly there’s a concept of a per-item image, so we’re just inserting our main podcast feed image into the <itunes:image> tag here. I haven’t see either iTunes or iOS show an image per-item, so maybe this isn’t used at all… but it causes no harm.

In The <duration> tag we’re inserting the variable we defined that grabs the ServiceLength attribute for the item. This lets the podcast user know how long the content is they’re about to listen to.



    </item>
    {%- endif -%}
    {%- endfor -%}
    </channel>
    </rss>

Whoa! We’ve reached the end of our feed! These last remaining bits are closing the <item> section, ending the if statement, ending the for loop, and closing off the <channel> and <rss> sections. Whew!


Finishing Up

Ok, so if you somehow made it this far and were actually following along, you should now have a shiny new RSS feed (template) defined using your information. The only thing left to do is test it and submit it.

Go ahead and point your browser at http://yourserver.com/GetChannelFeed.ashx?ChannelId=x&TemplateId=y where x is the Id of your Podcast Message content channel, and y is the Id of the template in which you just edited all that RSS (under Admin Tools | General Settings | Defined Types and opening the Global | Lava Templates defined type.) If you're not using Chrome, add &debug=1 to the end of the URL to force it to display the XML.


If all goes well you should end up with results that look something like this (reduced here to one item for brevity):

<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
<channel>
    <title>thecrossing.church (Audio)</title>
    <copyright>2017</copyright>
    <link>https://thecrossing.church</link>
    <description>Audio messages from The Crossing Church in Chesterfield, Missouri, led by Pastor Greg Holder. We are a multi-site church with campuses around the St. Louis metro area. Visit us online at thecrossing.church</description>
    <language>en-us</language>
    <webMaster>podcast@thecrossing.church</webMaster>
    <managingEditor>podcast&thecrossing.church</managingEditor>
    <ttl>120</ttl>
    <lastBuildDate>Mon, 12 Jun 2017 14:40:00 -0500</lastBuildDate>
    <itunes:author>The Crossing Church, Chesterfield, MO</itunes:author>
    <itunes:summary>Audio messages from The Crossing Church in Chesterfield, Missouri, led by Pastor Greg Holder. We are a multi-site church with campuses around the St. Louis metro area. Visit us online at https://thecrossing.church</itunes:summary>
    <itunes:owner>
        <itunes:name>The Crossing Church</itunes:name>
        <itunes:email>podcast@thecrossing.church</itunes:email>
    </itunes:owner>
    <itunes:explicit>No</itunes:explicit>
    <itunes:image href="https://feeds.wcrossing.org/Content/iTunes/iTunes_Logo_1400x1400.png" />
    <itunes:category text="Religion &amp; Spirituality">
    <itunes:category text="Christianity" />
    </itunes:category>
    <itunes:keywords>Crossing Church Greg Holder St. Louis Missouri Sermon Bible Scripture Jesus Grace Hope Love Christian Christianity God</itunes:keywords>
        <item>
            <title>The Big Picture: Live With Hope</title>
            <itunes:author>Greg Holder</itunes:author>
            <guid>37178e301fab404e8df62240b4b50e3b_20170611_JesusInvitesUs_01.mp3</guid>
            <link>https://thecrossing.church</link>
            <pubDate>Sun, 28 May 2017 11:00:00 -0500</pubDate>
            <copyright>2017</copyright>
            <description>When God moves in a story, big things happen. We have to remember what God has already done for us to have hope for where He is leading our future. Speaker: Greg Holder</description>
            <enclosure url="https://thecrossing.blob.core.windows.net/podcasts/37178e301fab404e8df62240b4b50e3b_20170611_JesusInvitesUs_01.mp3" length="43618774" type="audio/mpeg"/>
            <itunes:summary>When God moves in a story, big things happen. We have to remember what God has already done for us to have hope for where He is leading our future. Speaker: Greg Holder</itunes:summary>
            <itunes:image href="https://feeds.wcrossing.org/Content/iTunes/iTunes_Logo_1400x1400.png" />
            <itunes:duration>45:21</itunes:duration>
        </item>
</channel>
</rss>

If your feed doesn’t render but instead blows up, it’s likely you made a typo somewhere, or you’re referencing an attribute you didn’t actually add to the content channel, or you’ve got the wrong channel or template ID in the URL, etc. If you don’t see something obviously wrong after carefully looking through your feed code, try going to http://castfeedvalidator.com and entering the full URL to your feed.

There are many “feed validator” sites on the web but I’ve found this one to be particularly good. It’s accurate, doesn't consider the iTunes-specific tags "bad syntax", and usually can tell you the line/column of the error in your feed (just realize that the line numbers it reports won’t match up exactly to your feed unless you take into account all of the lines of Lava that don’t end up rendered in the output.) MANY other feed validation sites I tried would fail on a perfectly valid feed, so don’t take their results at face value. This site had no trouble validating my correct feed.


Only Seeing 10 Items?

Ah… right. I failed to mention earlier that by default, Rock will only return 10 items from a content channel. You can override this behavior by adding a Count=z query parameter to the URL, where z is the number of items you want returned. This should be set to the number that makes sense for your feed… Example: if you’re expiring your series at 2 years (that’s what we do), then you would want a count of at least 106 to account for an item every week for 2 years. If you’re fine with only publishing the most recent 10 items, just leave Count off of the querystring.


Now What?

Assuming you’ve tested your feed and it validates, the next thing to do is manually subscribe to it via iTunes, or the podcast app on your phone. All podcast apps have a way to enter a URL to the RSS feed directly, without searching in a store. In iTunes this is found under File | Subscribe to Podcast. Subscribe your feed and verify that it looks correct in the app, that your image works, etc. This is a great way to catch issues with your feed before submitting it to iTunes.


Submit Your Feed

Finally, assuming everything is good at this point, it’s time to submit it to the iTunes store. Head on over to https://podcastsconnect.apple.com and log in with an Apple account. This account MUST be tied to the iTunes store or it won’t work. I also recommend you DO NOT use your personal Apple ID, as someone in the future will have a difficult time dealing with your podcast feed long after you’ve moved on. Instead, create a new ID for this purpose, make sure it can log into the iTunes store, THEN log in to this site with those credentials (and be sure to document/save them somewhere!)


Podcast Connect Site

On the Apple site, you should be prompted to enter the full URL of your RSS feed and click Validate. Assuming everything in the feed is still ok, Apple will successfully validate your feed, and you can then click Submit and follow the instructions. In my experience it usually takes a few hours to get your feed "approved" and up to a day after that for it to appear in the searchable iTunes store.


When submitting your feed, remember to add Count=z to your query string where z = the number of items you want to return, as THAT is part of the exact URL iTunes will use, and it’s not trivial to change it to a different count after you’ve submitted it.


Odds & Ends


A Different Approach To Audio Files

Above, we showed the default way to handle podcast audio files... which is simply getting them onto a server somewhere, then pointing at those URLs in the feed via the AudioLink or VideoLink item attributes. Super simple. However, during this process of moving our podcast feed to Rock, we also wanted to move the MP3 files to cloud storage (previously they were sitting on a on-premises web server.)

This begged the question: "How will the person in charge of the podcast get the MP3 files to the new cloud storage?" We could have just implemented FTP or some other proprietary way to upload the file to cloud storage, but after thinking about it a bit we hit upon a better solution: Use Rock itself to transfer the file to the cloud… via an attribute!? This would let the podcast admin upload the file when adding the item to the content channel, without any special client/software...So that's just what we did.

Here’s how it works:

  1. We provisioned an Azure blob storage account and added a “podcast” container. (this would work fine with Amazon S3, too)
  2. We purchased the Azure Storage plugin from the Rock Shop. (there's also a S3 plugin in the Rock shop)
  3. We created a ‘Podcast Audio’ file type in Rock and configured it to use Azure Storage and our podcast container.
  4. We added a AudioFile file attribute of type Podcast Audio to the Podcast Message content channel.

With that set up, the workflow goes like this: When the person in charge of the content channel creates a new item, they also choose the item’s mp3 file and save it to the AudioFile attribute. Rock then "uploads" the MP3 to our Azure storage, even though it looks like the file is sitting in a Rock content channel attribute. There is a nice ‘spinning arrow’ animation while the file/attribute is being saved to let you know when it’s done. On our network it takes about 30 seconds to upload a typical 65MB podcast to Azure. Via {%- assign mediaURL = item | Attribute:'AudioFile', 'Url'-%} we have access to the URL of this file stored in Azure. Eureka! Finally, because there is a FileSize property on a binary file type, we can switch out the size assignment with {%- assign size = item | Attribute:'AudioFile', 'FileSize' -%} and grab the size directly from the attribute, so we no longer have to enter it! That’s the gist of how we’re handling audio files for our podcast. Step-by-step instructions for setting it all up are unfortunately beyond the scope of this article (though the list above should get you close), but we just wanted to let you know what’s possible if you want to do the same.


What About My Video Podcast?

Early on we mentioned that many churches will want two RSS feeds... one for audio messages and another for video, and we walked you through creating the RSS for the audio version of a podcast. So now you're wondering about the video version. The good news is that it's very simple to do... you basically just need to:

  1. Copy/paste your audio RSS template to a new one.
  2. Change the <title> to represent your video podcast (eg "Your Church Podcast (Video)")
  3. Change the line where you assign the mediaURL to {%- assign mediaURL = item | Attribute:'VideoLink' -%}
  4. Change the length= parameter in the <enclosure> tag to be length="" (see below for why)
  5. Change the type= parameter in the <enclosure> tag to be type="video/mp4" (this assumes that's your video type)
  6. Go through the same validation process you did before to make sure the feed is valid. Note that your new template will have a different Id, which is how you will tell Rock to serve up that RSS feed via &TemplateId=y where y is the Id of your new template.

One thing we haven't figured out a great solution for is giving the video podcast the SIZE of its file. Right now we're just not reporting the size at all (by making length="" in the <enclosure> tag for the video feed). We could just add another attribute to the content channel for the size of the video file and manually enter it, but we're hoping to use Vimeo's (where we host our videos) API to query it directly for the size. If we figure that out I'll update this article with the solution.


Feeds URL

You may have noticed that the URL to our feed is https://feeds.wcrossing.org and not our web site. Why would we do this? Well, we've been burned in the past when our prior ChMS was generating the RSS feed, but iTunes was pointing at our www URL, which meant that when we switched to Rock and it started handling the www URL, all podcast clients were suddenly looking for their RSS feed on the new site which of course didn't exist. The only remedies to this situation were to 1. Change the URL that the clients are pointing at via a cumbersome iTunes "move podcast" configuration, or 2. Use some URL rewrite rules on the new www server to point RSS clients BACK the OLD server (now listening on a different hostname.) Neither one of those options is "fun."

To avoid that pain in the future, we made the conscious decision this time around to NOT tie any of our iTunes, Roku, etc feeds to the web site URL, which is risky for the reasons I just mentioned. Now they have their own URL (which is just another Rock "site" listening on that hostname) that won't change unless we completely abandon that domain. There is absolutely no requirement to do this, and it's perfectly acceptable to just let your Rock www site serve-up the RSS feeds, but we're just pointing out the reasons we chose not to.

You might also have noticed that we're using https:// to get to our feeds. I don't know if Apple is requiring the use of secure HTTP at this point, but it's for-sure coming if not already here, so there's NO reason to implement a feed-specific URL that's not using HTTPS today. And if your main site isn't already "https everywhere" you might want to consider that, too...


The End

Hopefully this article has been helpful in getting your podcast RSS configured. I also hope your mind is now churning with other possibilities of leveraging this feature... for example, the potential to let Rock generate your Roku or AppleTV XML feeds! But that's for another article ;-) Thanks for reading, and good luck!




@jimmichael
The Crossing
St. Louis, MO

IT Manager at The Crossing in St. Louis. Lover of all things Rock!