Add An Episode Index To The Anchor FM Podcasts Embed Player

November 8th, 2019




Recently I ran in to an annoying little issue. I wanted to use the Anchor FM podcasts embed player available on anchor.fm to one of the sites I manage. is possible, however Anchor FM only lets you embed the top part of the player that you see on their site, not the episode index.

The embed version of the anchor podcasts embed player
This is not enough!

Just having the top part of the player wasn’t the kind of experience we were looking for users to have on this site.

I found that Anchor FM gives you access to an RSS feed, with enough data to completely remake this feature on our own site.

The version of the Anchor podcasts embed player that we wish we had
We want this whole player, not just the top part.

After looking at various methods of getting an RSS feed which will return the data in a JSON format instead of XML, I resolved to use Feednami. This was a great choice. It returns JSON exactly as you’d expect it to, and is very reliable and quick to set up.

Ok, so let’s get started. We are going to replicate much of the HTML and CSS, and write some custom Javascript to output the RSS data.

The HTML

<div id="podcasts-player-container">
  <iframe id="anchor-podcast-iframe" src="https://anchor.fm/{YOUR ANCHOR SITE NAME}/embed" style="width: 100%;" frameborder="0" scrolling="no" name="iframe"></iframe>
  <script src="https://static.sekandocdn.net/static/feednami/feednami-client-v1.1.js"></script>
</div>

First, create a container for the player, embed the top part of the player, and include the Feednami script.
There are definitely better ways to include a script depending on your environment but for the sake of simplicity I will include it here, and it does work fine.

The CSS

#podcasts-player-container .wpb_raw_html {
    max-height: 145px; /* This is a fix for iPhone/Safari gap */
}

#podcasts-player-container .wpb_wrapper {
  line-height: 0;
}

#podcasts-player-container {
    zoom: 0.69; /* Fix for mobiles */
}

.styles__episodeFeed___3mOKz {
  position: relative;
  overflow-y: auto;
  background-color: #54595E;
  max-height: 417px;
  padding: 11px;
  margin-bottom: 70px;
  border-bottom-left-radius: 5px;
  border-bottom-right-radius: 5px;
  display: none;
  color: #fff;
}

.styles__episodeFeedItem___1U6E2 {
  min-height: 106px;
  color: #292f36;
  background: #fff;
  border-radius: 4px;
  padding: 14px 12px;
  overflow: hidden;
  position: relative;
}

.styles__episodeFeedItem___1U6E2 a {
  color: inherit;
  text-decoration: none;
  text-align: left;
}

.styles__episodeFeedItem___1U6E2 .styles__episodeImage___tMifW {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  overflow: hidden;
  float: left;
  margin: 0 6px 10px 0;
  position: relative;
  width: 47px;
  height: 47px;
}

.styles__circle___1g-9u {
  border: none;
  padding: 0;
  display: flex;
  justify-content: center;
  align-items: center;
}

.styles__white___372tQ {
  background: #fff;
}

.styles__episodeFeedItem___1U6E2 .styles__episodeImage___tMifW img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border-radius: 4px;
  vertical-align: middle;
}

.styles__playButton___1Ivi4 {
  cursor: pointer;
  text-align: center;
  padding: 0;
}

.styles__playButton___1uaGA {
  z-index: 0;
}

.styles__playButton___1Ivi4 svg {
  overflow: visible;
}

.styles__episodeFeedItem___1U6E2 a {
  color: inherit;
  text-decoration: none;
}

.styles__episodeFeedItem___1U6E2 .styles__episodeHeading___29q7v {
  font-weight: 700;
  font-size: 15px;
  color: #292f36;
}

.styles__episodeFeedItem___1U6E2 .styles__episodeDescription___C3oZg, .styles__episodeFeedItem___1U6E2 p {
  color: rgba(41,47,54,.7);
  font-size: 13px;
  line-height: 14px;
}

.styles__episodeFeedItem___1U6E2 .styles__episodeDescription___C3oZg {
  clear: left;
  padding-right: 2px;
  text-align: left;
}

.styles__episodeFeedItem___1U6E2 .styles__episodeDescription___C3oZg, .styles__episodeFeedItem___1U6E2 p {
  color: rgba(41,47,54,.7);
  font-size: 13px;
  line-height: 14px;
}

.styles__episodeFeedItem___1U6E2 .styles__episodeCreated___1zP5p, .styles__episodeFeedItem___1U6E2 .styles__episodeDuration___2I0Qb {
  margin-top: 8px;
  font-size: 11px;
  line-height: 13px;
  color: #c9cbcd;
}

.styles__episodeFeedItem___1U6E2 .styles__episodeDuration___2I0Qb {
  float: right;
}

.styles__episodeFeedItem___1U6E2 .styles__episodeCreated___1zP5p, .styles__episodeFeedItem___1U6E2 .styles__episodeDuration___2I0Qb {
  margin-top: 8px;
  font-size: 11px;
  line-height: 13px;
  color: #c9cbcd;
}

.styles__episodeFeedItem___1U6E2 .styles__episodeCreated___1zP5p {
  float: left;
}

.styles__episodeFeed___3mOKz {
  margin-bottom: 73px;
}

.styles__episodeFeed___3mOKz {
  display: block;
}

@media (max-width: 768px) {
  .styles__episodeFeedItem___1U6E2 {
    margin-bottom: 10px;
  }
}

@media (min-width: 769px) {

  .styles__episodeFeed___3mOKz {
    padding: 35px;
  }

  .styles__episodeFeedItem___1U6E2 {
    padding: 25px 22px;
  }
  .styles__episodeFeed___3mOKz>div {
    margin-bottom: 23px;
  }
  .styles__episodeFeedItem___1U6E2 .styles__episodeImage___tMifW {
    width: 56px;
    height: 56px;
    margin-right: 22px;
    margin-bottom: 0;
  }
  .styles__episodeFeedItem___1U6E2 .styles__episodeHeading___29q7v {
    font-weight: 700;
    font-size: 18px;
    color: #292f36;
    margin-bottom: 4px;
    width: 365px;
  }
  .styles__episodeFeedItem___1U6E2 .styles__episodeDescription___C3oZg, .styles__episodeFeedItem___1U6E2 p {
    font-size: 16px;
    line-height: 18px;
    color: #7f8287;
  }
  .styles__episodeFeedItem___1U6E2 .styles__episodeDescription___C3oZg {
    clear: none;
    width: 465px;
    margin-left: 78px;
  }
  .styles__episodeFeedItem___1U6E2 .styles__episodeDescription___C3oZg, .styles__episodeFeedItem___1U6E2 p {
    font-size: 16px;
    line-height: 18px;
    color: #7f8287;
  }
  .styles__episodeFeedItem___1U6E2 .styles__episodeCreated___1zP5p, .styles__episodeFeedItem___1U6E2 .styles__episodeDuration___2I0Qb {
    margin: 0;
    float: none;
    position: absolute;
    font-size: 13px;
    line-height: 15px;
  }
  .styles__episodeFeedItem___1U6E2 .styles__episodeDuration___2I0Qb {
    bottom: 12px;
    right: 22px;
  }
  .styles__episodeFeedItem___1U6E2 .styles__episodeCreated___1zP5p {
    top: 25px;
    right: 22px;
  }
  .styles__episodeFeedItem___1U6E2 .styles__episodeCreated___1zP5p, .styles__episodeFeedItem___1U6E2 .styles__episodeDuration___2I0Qb {
    margin: 0;
    float: none;
    position: absolute;
    font-size: 13px;
    line-height: 15px;
  }

}

Most of this is adapted directly from the Anchor players CSS. I have added some CSS to make this something that works for all devices. The player on the Anchor FM site actually disappears, and another player is shown for mobile. That’s not really an option for us, so I made this one work for mobiles as well.

The Javascript

// Your site url (must include https://anchor.fm/)
const siteUrl = 'https://anchor.fm/{YOUR SITE NAME}';
// Get position to split url for modification seen below
const urlModPosition = siteUrl.length;
// RSS feed URL (site key is found in RSS feed URL)
const rssUrl = 'https://anchor.fm/s/{YOUR SITE KEY}/podcast/rss';
// Container which holds the iframe - so that we can append the podcast list just below it
const iframeContainer = document.getElementById('podcasts-player-container');
 
// Use feednami to parse the RSS in to JSON and give us a JSON object
feednami.load(rssUrl)
  .then(feed => {
 
    // Add the container for the list before the loop
    iframeContainer.innerHTML += `<div id="podcast-list-container" class="styles__episodeFeed___3mOKz"></div>`;
    const podcastListContainer = document.getElementById('podcast-list-container');
 
    // Loop through the JSON to produce the list
    for(let entry of feed.entries){
 
      // add '/embed' to URL so that it works in an iframe
      const originalURL = entry.link;
      console.log(originalURL);
      const pathToAdd = '/embed';
      const revisedUrl = [originalURL.slice(0, urlModPosition), pathToAdd, originalURL.slice(urlModPosition)].join('');
 
      // create an excerpt out of the title
      const c = entry.title;
      const clength = 31;
      const titleExcerpt = c.substring(0, clength) + '...';
 
      // create an excerpt out of the description
      let d = entry.description;
      let dlength = 114;
      let descriptionExcerpt = d.substring(0, dlength) + '...';
 
      // get minutes and seconds from seconds formatted data
      let time = entry['itunes:duration']['#'];
      const minutes = Math.floor(time / 60);
      const seconds = time - minutes * 60;
      const hours = Math.floor(time / 3600);
      time = time - hours * 3600;
       
      function str_pad_left(string,pad,length) {
        return (new Array(length+1).join(pad)+string).slice(-length);
      }
     
      const finalTime = str_pad_left(minutes,'0',2)+':'+str_pad_left(seconds,'0',2);
 
      // convert JSON ISO 8601 formatted date in to a readable date
      const date = new Date(entry.date);
      const monthNames = [
        "January", "February", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"
      ];
      const month = monthNames[date.getMonth()];
      const day = date.getDate();
      const year = date.getFullYear();
 
      // Output the episode on the page with the data we have prepared
      podcastListContainer.innerHTML += `
        <div class="styles__episodeFeedItem___1U6E2">
          <a class="podcast-list-link styles__episodeImage___tMifW" href="${revisedUrl}" target="iframe">
            <img src="${entry.image.url}">
            <button class="styles__circle___1g-9u styles__white___372tQ styles__playButton___1Ivi4 styles__playButton___1uaGA" aria-label="" style="height: 31px; min-height: 31px; width: 31px; min-width: 31px; border-radius: 16px;">
              <svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 0 11 12" width="13" height="13"><rect width="12" height="12" fill="none"></rect><path d="M1 .81v10.38a.76.76 0 0 0 .75.75.67.67 0 0 0 .39-.12l8.42-5.18a.75.75 0 0 0 0-1.28L2.14.18a.75.75 0 0 0-1 .24.79.79 0 0 0-.14.39z" fill="#282F36"></path></svg>
            </button>
          </a>
          <a class="podcast-list-link" href="${revisedUrl}" target="iframe">
            <div class="styles__episodeHeading___29q7v" style="overflow: hidden;">
              <div>
                <div>
                  ${titleExcerpt}
                </div>
              </div>
            </div>
          </a>
          <div class="styles__episodeDescription___C3oZg ">
            <div class="styles__expander___1NNVb styles__expander--dark___3Qxhe" style="overflow: hidden;">
              <div>
                <div>
                  ${descriptionExcerpt}
                </div>
              </div>
            </div>
          </div>
          <div class="styles__episodeDuration___2I0Qb">
            ${finalTime}
          </div>
          <div class="styles__episodeCreated___1zP5p">
            ${month} ${day}, ${year}
          </div>
        </div>
      `;
    }
  });

This is where the heavy lifting is done! It should be pretty self explanatory due to the commenting.

That’s it! Now you should have an Anchor FM podcasts embed player that doesn’t just show a single episode at a time, but a whole index, the same as the one on anchor.fm. If you have any queries about this then don’t hesitate to get in contact here

If the demand is there, I may consider turning this in to a WordPress plugin, so feel free to get in touch with a request.

Was this post helpful?