Entrada

Arreglar el streaming en Apple Podcasts con Castopod y Caddy

Arreglar el streaming en Apple Podcasts con Castopod y Caddy

Explicación del problema

Tengo alojado en mi servidor Castopod en un contenedor docker. A su vez tengo otro contenedor Caddy para que haga la redirección a Castopd cuando se le solicita el feed o un episodio para no exponer públicamente mi servidor.

Todo funcionaba bien menos que en Apple Podcast varios oyentes (aunque sería mejor decir escuchantes) me han comentado que cuando quieren reproducir en algunos reproductores no les dejaba hacerlo via streaming.

La explicación técnica la dejaremos para los que saben y yo lo que os hago aqui es una explicación con mis palabras y según lo he comprendido yo.

Todo se resume en que hay clientes que le piden el audio al servidor de una forma parcial y otros que le dicen tu dame todo que yo ya me apaño. Y aquí es donde empieza el problema, el que se apaña es capaz de reproducir via streaming aunque le den el fichero de golpe pero el que le pide 1 mb por ejemplo y le dan todo no sabe que hacer y da un error y para la reproducción. Esto se conoce como Range Requests. Existen comandos para imitar la solicitud que se haría y testear el resultado, se hace con un curl.

Dejo un ejemplo de curl

1
curl -I -H "Range: bytes=0-1023" https://papafrikifeed.duckdns.org/audio/@PapaFriki/ppf-loterias-y-apuesta-y-el-bono-vicio.mp3

En la salida vereis un HTTP/2 200 si lo ha servido completo y un HTTP/2 206 si lo ha servido de modo parcial, en la petición anterior se le piden los bytes 0-1023 o lo que es lo mismo un kylobite del audio para hacer la prueba.

Pues yo lo tenía con un HTTP/2 200 y necesitaba servirlo parcialmente para que, por ejemplo, Apple podcast entre otros clientes pudiera servirlo bien.

Solucionando el problema

Llegó sin esperarla de la mano de David Marzal que me pasó un episodio del podcast RTS/CTS donde Carlos nos contaba la ingeniosa solución que le dió al mismo problema que tenia yo. Su solución para por montar un contenedor auxiliar con Nginx para que capture las peticiones que se realizan a Castopod de los mp3 y en lugar de servilo Castpod hacerlo este Nginx del que tenemos un control completo. Os dejo el enlace a su podlink https://pod.link/1644614612

Asi que le escribí por mastondon y me facilitó los ficheros. Que mas o menos entendia lo que hacen pero que tuve que explicar a Gemini la situación en la que estaba, como Carlos la había solucionado y que me ayudara a hacerlo en mi instalación de Castopod y Caddy.

El resultado fue que lo logré y aqui lo dejo plasmado para compartirlo y para que el día de mañana si tengo que echar mano de él lo encuentre facilmente.

Paso 1: En la misma carpeta que uso para desplegar el contenedor de Castopod, junto al docker-compose.yml y las carpetas prpopiaas del programa se crea un fichero llamado nginx_sidecar.conf con el siguiente contenido:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server {
    listen 80;
    server_name _;

    # Servir audios directamente SIN compresión
    location /media/ {
        alias /var/www/castopod/public/media/;
        gzip off;          
        sendfile on;       
        add_header Accept-Ranges bytes;
    }

    # El resto del tráfico se lo devuelve a la App de Castopod
    location / {
        proxy_pass http://castopod-app:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
    }
}

Paso 2: Modificar el docker-compose.yml hay que añadir el servicio sidecar y compartirle el volumen de media, lo hacemos solo para lectura.

1
2
3
4
5
6
7
8
9
10
11
12
services:
  nginx-sidecar:
    image: nginx:alpine
    container_name: "castopod-nginx-sidecar"
    volumes:
      - ./nginx_sidecar.conf:/etc/nginx/conf.d/default.conf
      - ./media:/var/www/castopod/public/media:ro # Solo lectura
    networks:
      - castopod-app
    ports:
      - "192.168.1.129:8006:80" # Nuevo puerto de entrada
    restart: unless-stopped

Paso 3: Tuve unos problemas con los permisos de archivos en el Host con errores 403 Forbidden pues Nginx debe poder leer la carpeta de media. Para ello basta con ejecutar los siguientes comandos en el host

1
2
3
4
5
6
# Permite entrar en directorios y leer archivos
sudo find /home/pi/docker/castopod/media -type d -exec chmod 755 {} +
sudo find /home/pi/docker/castopod/media -type f -exec chmod 644 {} +

# Asegura que el usuario del host sea el dueño
sudo chown -R pi:pi /home/pi/docker/castopod/media

Añado un paso 3.1 que me ocurrió al intentar subir el primer episodio tras la actualización y es que daba errores por falta de permisos, lo solucioné de la peor forma:

1
sudo chmod -R 777 /home/pi/docker/castopod/media

Paso 4: Ajuste en el Caddyfile Toca ahora configurar Caddy para que apunte al puerto 8006 ( u otro que no sea el que usa tu instalación de castopod) y desactivar la compresión para audios:

Dejo parte del código tal y como lo tengo implementado yo al menos esto deberá estar en vuestra configuración de caddy, habría que añadirle mas seguridad pero éso no es material de esta entrada.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
papafrikifeed.duckdns.org {
	# Definimos qué archivos NO deben comprimirse para no romper el streaming (Range Requests)
	@no_compress {
		path *.mp3 *.m4a *.ogg *.wav
	}

	# Compresión inteligente: Comprime todo excepto los audios
	encode @no_compress zstd gzip

	# Enviamos todo el tráfico al Nginx Sidecar (Puerto 8006)
	# Él decidirá si sirve el MP3 directamente o lo pasa a Castopod
	reverse_proxy http://192.168.1.129:8006 {
		header_up X-Real-IP {remote_host}
		header_up X-Forwarded-Proto https
	}
	
	header Accept-Ranges bytes
}

Paso 5: Verificando el sistema

La prueba de fuego se hace con curl, como ya vimos antes, y buscamos recibir un HTTP 206 en las primeras líneas.

1
curl -I -H "Range: bytes=0-1023" https://papafrikifeed.duckdns.org/audio/@PapaFriki/ppf-loterias-y-apuesta-y-el-bono-vicio.mp3

Si toddo ha ido bien la respuesta va a empezar por HTTP/2 206 y habremos logrado que Apple Podcasts y otros clientes puedan servir el fichero en streaming.

Implementar un Nginx Sidecar puede parecer un paso extra en nuestra infraestructura, pero es la diferencia entre tener un servidor que ‘suelta’ archivos y uno que ‘gestiona’ streaming de forma eficiente.

Gracias a la solución inspirada por Carlos de RTS/CTS, ahora mis dos feeds, PapaFriki y Ángeles Custodios, son 100% compatibles con el estándar de peticiones parciales (206 Partial Content).

Si usas Docker y Caddy, esta es la pieza del puzle que te faltaba para que tu podcast puedan llegar a todos los reproductores.

Gracias por el tiempo dedicado a la lectura de la entrada si te surgen dudas y crees que hay cosas que debería explicar más no dudes en ponerte en contacto en mastodon (@PapaFriki@mas.to) o por correo electrónico alberto@papafriki.es

Nos vemos, nos leemos, nos escuchamos… adios!

Esta entrada está licenciada bajo CC BY 4.0 por el autor.