Ephes Blog

Miscellaneous things. Mostly Weeknotes and links I stumbled upon.


Weeknotes 2024-11-18

, Jochen

I learned all my HTML<br>
From a code camp held yearly in hell<br>
We started on `<s>`<br>
Then striked through the rest<br>
Now I have a career in DevRel --Heydon Pickering


Published a new podcast episode discussing Python 3.13. I’ve been using Jupyter notebooks to generate transcripts for the episodes and have started consolidating that workflow into a dedicated Python command-line application.

Articles

Books

Software

Out of Context Images


Weeknotes 2024-11-11

, Jochen
AAAAAAAAAAAAAAAAAAAAAAAHHHHHHHH --Endless Screaming

This is fine 🔥.

In other news, I attended the PyDDF autumn sprint! After putting off the transcripts feature for django-cast for quite some time, I finally started working on it. You can already see some transcript functionality in the web player on my podcast website. There’s still plenty to do, but at least it’s underway now.

Articles

Videos

Software

Fediverse

Out of Context Images


Weeknotes 2024-11-04

, Jochen
By age 35, your 500-foot burial pyramid should already be 80% complete. --batkaren

I’ve implemented some performance improvements on my podcast page and published an article about them: TIL: Podlove Web Player Performance Improvements. Additionally, I released a new version of django-cast that incorporates the new podlove-player web component and offers official support for Wagtail 6.3.

Articles

Software

Books

Fediverse

Weeknotes

Videos

Out of Context Images


Podlove Web Player Performance Improvements

, Jochen

I recently attended the SUBSCRIBE 11 podcasting conference, where I had the opportunity to chat with the author of Pods-Blitz about the Podlove Web Player. After we both listened to a talk about it, he asked me how I liked the Web Player. I mentioned that I really enjoyed it but suspected it was slowing down my website. He immediately agreed, noting that he had observed the same behavior.

Here's a screenshot illustrating the issue:

python-podcast-performance_original_scores

The poor performance seems to stem from the player loading a substantial amount of JavaScript and CSS assets.

podlove-player_slow_assets

Today, I decided to ask on Discord what might be causing this problem. Below is my original JavaScript code used to initialize the five audio players on the overview page of my podcast:

<script defer src="/static/cast/js/web-player/embed.5.28903a742256.js"></script>
<script>
  function initializePodlovePlayers() {
    document.querySelectorAll('section.block-audio div[id^="audio_"]').forEach(div => {
      const audioId = div.id;
      const url = div.getAttribute('data-url');
      podlovePlayer(`#${audioId}`, url, "/api/audios/player_config/");
    });
  }

  document.body.addEventListener('htmx:afterSwap', (event) => {
    if (event.detail.target.id === 'paging-area') {
      initializePodlovePlayers();
    }
  });

  // Call the function on initial load
  document.addEventListener('DOMContentLoaded', (event) => {
    initializePodlovePlayers();
  });
</script>

Alexander Heimbuch suggested that I use the load event instead of DOMContentLoaded to avoid blocking the main thread. He also advised using the IntersectionObserver API to defer the initialization until the player becomes visible in the viewport. I'm really grateful for Alex's help—it made a significant difference in optimizing my website. Based on his suggestions, I updated my code:

<script defer src="{% static 'cast/js/web-player/embed.5.js' %}"></script>
<script>
  function initializePodlovePlayerWhenVisible(div) {
    const observer = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const audioId = div.id;
          const url = div.getAttribute('data-url');
          podlovePlayer(`#${audioId}`, url, "/api/audios/player_config/");
          observer.unobserve(div); // Stop observing since we don't need to initialize again
        }
      });
    });
    observer.observe(div);
  }

  function initializePodlovePlayers() {
    document.querySelectorAll('.podlove-player-container').forEach(div => {
      initializePodlovePlayerWhenVisible(div);
    });
  }

  document.body.addEventListener('htmx:afterSettle', (event) => {
    if (event.detail.target.id === 'paging-area') {
      setTimeout(() => {
        initializePodlovePlayers();
      }, 1500); // Adjust the delay as needed -> wait to finish scrolling up
    }
   });

  // Attach to the load event to ensure the page has fully loaded
  window.addEventListener('load', function() {
    initializePodlovePlayers();
  });
</script>

These changes made a significant difference—the performance score for mobile increased from 60 to 83. Realizing that it wouldn't be easy to reduce the connections to cdn.podlove.org—since each player is isolated in its own iframe and the URL is hardcoded in the pre-built player bundle—I added an additional optimization by including a preconnect link in the header of my website:

<link rel="preconnect" href="https://cdn.podlove.org" />

This also helped a lot to improve performance. Here are the final results:

python-podcast_optimized_scores

After grappling with htmx and pagination issues, I decided to encapsulate all the JavaScript into a web component. Now, the player script is dynamically imported when the web component becomes visible.

{% load static %}
{% if page.pk %}
  <podlove-player
    id="audio_{{ value.pk }}"
    data-variant="xl"
    data-url="{% url 'cast:api:audio_podlove_detail' pk=value.pk post_id=page.pk %}"
    data-embed="{% static 'cast/js/web-player/embed.5.js' %}"
    data-config="{% url 'cast:api:player_config' %}"
  >
  </podlove-player>
{% endif %}
// podlove-player.ts
class PodlovePlayerElement extends HTMLElement {
  constructor() {
    super();
    this.observer = null;
    this.shadow = this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.renderPlaceholder();

    if (document.readyState === 'complete') {
      // The page is already fully loaded
      this.observeElement();
    } else {
      // Wait for the 'load' event before initializing
      window.addEventListener('load', () => {
        this.observeElement();
      }, { once: true });
    }
  }

  disconnectedCallback() {
    if (this.observer) {
      this.observer.disconnect();
    }
  }

  renderPlaceholder() {
    // Reserve space to prevent layout shifts
    const container = document.createElement('div');
    container.classList.add('podlove-player-container');

    // Apply styles
    const style = document.createElement('style');
    style.textContent = `
      .podlove-player-container {
        width: 100%;
        max-width: 936px;
        height: 300px;
        margin: 0 auto;
      }
      @media (max-width: 768px) {
        .podlove-player-container {
          max-width: 366px;
          height: 500px;
        }
      }
      .podlove-player-container iframe {
        width: 100%;
        height: 100%;
        border: none;
      }
    `;

    this.shadow.appendChild(style);
    this.shadow.appendChild(container);
  }

  observeElement() {
    this.observer = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          this.initializePlayer();
          observer.unobserve(this);
        }
      });
    });
    this.observer.observe(this);
  }

  initializePlayer() {
    const container = this.shadow.querySelector('.podlove-player-container');
    const audioId = this.getAttribute('id') || `podlove-player-${Date.now()}`;
    const url = this.getAttribute('data-url');
    const configUrl = this.getAttribute('data-config') || '/api/audios/player_config/';
    const podloveTemplate = this.getAttribute('data-template');
    let embedUrl = this.getAttribute('data-embed') || 'https://cdn.podlove.org/web-player/5.x/embed.js';

    // If host ist localhost use local embed url
    const { protocol, hostname, port } = window.location;


    console.log("data template: ", podloveTemplate);
    const playerDiv = document.createElement('div');
    playerDiv.id = audioId;

    // set the template attribute if it is set (needed for pp theme)
    if (podloveTemplate !== null) {
        playerDiv.setAttribute('data-template', podloveTemplate);
    }
    container.appendChild(playerDiv);

    if (typeof podlovePlayer === 'function') {
      podlovePlayer(playerDiv, url, configUrl);
    } else {
      // If in dev mode on localhost and embedUrl starts with a slash, use the local embedUrl
      // otherwise the vite url 5173 will be used -> which will not work
      if (hostname === 'localhost' && embedUrl.startsWith("/")) {
        embedUrl = `http://localhost:${port}${embedUrl}`;
      }
      // Dynamically load the Podlove player script
      import(embedUrl).then(() => {
        podlovePlayer(playerDiv, url, configUrl);
      });
    }
  }
}

console.log("Registering podlove-player!");
customElements.define('podlove-player', PodlovePlayerElement);

Weeknotes 2024-10-28

, Jochen
From Facebook .....
I just learned the professional way to say "I told you so":
"This was identified early on as a likely outcome." --Christos Argyropoulos MD, PhD

Last week, I wrote a lot of Playwright end-to-end tests, and I really appreciate how easy it is these days to copy a snippet of HTML from a website and simply ask an LLM, 'How do I click the submit button?' or 'How can I select option "x" with Playwright?' It quickly generates a working line of code that I can drop straight into my test.

Articles

Videos

Fediverse

Software

Out of Context Images