Webradio Tag bei Startup Spielt nicht

Ich habe mir eine m3u Datei erstellt und 3 urls mit Webradio streams eingefügt. Ein Solches „Radio Tag“ finde ich echt klasse. Auflegen und Radio hören.

Wenn ich allerdings den ESPuino abschalte und dann wieder anschalte (mit dem Webradio Tag bereits aufgelegt) kommt es zum Problem. Offensichtlich wird das aufgelegte Tag bereits ausgewertet wenn die WiFi Verbindung noch nicht steht und dann schlägt das Streaming fehl. Da ich in den Einstellungen auch noch den Schalter „Denselben RFID Tag nicht erneut akzeptieren“ aktiv habe, muss ich zuerst ein anderes Tag auflegen und dann wieder mein Webradio Tag damit der Stream gestartet wird. Das Problem tritt sowohl mit einer m3U Datei mit mehreren Streams als auch direkt mit einem Tag, das direkt mit einem Webstream verknüpft ist.Ich habe ein bisschen analysiert und zwei Ursachen für das Problem gefunden.

  1. Kein erneuter Versuch nach WLAN-Verbindung.
    RFID-Tags werden beim Booten sofort eingelesen und an AudioPlayer_SetPlaylist() weitergereicht, während das WLAN im Hintergrund asynchron verbindet (das kann mehrere Sekunden dauern). Der allererste Verbindungsversuch zum Stream scheitert deshalb sofort, und ein weiterer Versuch findet nicht statt sobald das WLAN tatsächlich verbunden ist. (Ein
    Retry-Mechanismus existiert bereits im Code, ist jedoch fest an das separate Feature „letzte RFID nach Neustart abspielen“ gekoppelt und spielt nur das im NVS gespeicherte Tag erneut ab – nicht zwangsläufig das Tag, das physisch auf dem Lesegerät liegt.)
  2. DONT_ACCEPT_SAME_RFID_TWICE bleibt dauerhaft blockiert.
    Die „Gleiches-Tag“-Sperre (gOldRfidTagId) soll sich zurücksetzen, sobald die Wiedergabe in den Leerlauf geht – umgesetzt über eine zweistufige Sperrlogik (Latch) in AudioPlayer_Loop(): Sie wird nur „scharf geschaltet“, wenn die Loop-Funktion den Player irgendwann im Zustand „aktiv“ beobachtet, und erst beim nächsten Beobachten von „Leerlauf“ tatsächlich zurückgesetzt. Jeder Fehlerpfad in dieser Funktion kehrt jedoch sofort zurück (return), bevor der Code erreicht wird, der die Sperre scharf schalten würde. Bei einer Playlist, deren erster (und eventuell einziger) Titel sofort scheitert – genau das Szenario „Webstream vor WLAN-Verbindung“ – tritt dieser „aktiv“-Zustand nie ein, die Sperre wird nie
    scharf geschaltet, und das Tag bleibt dauerhaft gesperrt. Deshalb hilft auch ein manuelles erneutes Auflegen nach erfolgter WLAN-Verbindung nicht.

Ich weiß jetzt nicht wie häufig dieses Problem bei anderen Benutzern auftritt. Vielleicht vermehrt wenn die Nutzer schon etwas älter sind (und Radio hören interessanter wird) und den ESPuino vermehrt als Webradio Spieler einsetzen.
Soll ich hier einen Vorschlag für einen Fix machen? Wie würde das Ablaufen? ich kann wohl kaum einen direkten Pull request auf den dev branch machen.

Ah, ich habe gerade gesehen, dass es in der Tat einen Pull request zu diesem Thema gibt:
Wait when trying to play a webstream while connecting to wifi#284
Allerdings halte ich diese Lösung nicht für gut. Ich würde nicht warten sondern eine retry machen wenn wifi da ist.

Ich meine ich hatte mich dazu auch irgendwo mit Bedenken geäußert, dass ein Warten ggf. nicht so gut ist. Da es aber offenbar nicht im PR ist, muss es wohl hier im Forum vergraben sein.

Konkret aufgetreten ist das Problem damals in einem leicht anderen Kontext. Und zwar gibt’s ja die Möglichkeit, dass der zuletzt aktive Abspielvorgang nach einem ESPuino-Neustart automatisch fortgesetzt wird (ohne Auflegen der Karte). Und das in Kombination mit Webradio hat dann eben dieses Problem erzeugt, das du jetzt beschreibst. Früher hatten wir das Problem so nicht, da aktiv auf’s WLAN beim Starten gewartet wurde. Nach dem async. WLAN-Start hat sich dann das gezeigte Problem gegeben, aber es ist bisher keine Lösung daraus entstanden, mit der alle zufrieden sind :slight_smile:

Korrekt. Der Vorteil ist einfach, dass man nicht auf’s WLAN warten muss und vorher schon hören kann. Weil lokal von SD-Karte hören dürfte bei weitem der Standardfall sein.

Inzwischen haben wir ja sogar noch einen Playmodus:

Genau so, skizziere eine Lösung.

Also einreichen kannst du das natürlich. Ich find’s allerdings gut, wenn das vorher beschrieben wird.

Na das mach ich doch gerne:

angefasste Dateien: src/main.h, src/main.cpp, src/AudioPlayer.cpp, src/Wlan.cpp,
src/logmessages.h, src/LogMessages_EN.cpp, src/LogMessages_DE.cpp,
src/LogMessages_FR.cpp

  • Neue Variable gRetryRfidOnWifiConnect hinzugefügt. Wird ein
    Webstream-/m3u-Webstream-Verbindungsversuch übersprungen, weil das WLAN noch nicht
    verbunden ist (AudioPlayer.cpp, innerhalb von AudioPlayer_Loop()), wird
    stattdessen dieses Flag gesetzt, statt einfach nur zu scheitern.
  • Im WLAN-Verbunden-Handler (Wlan.cpp, Pfad nach erfolgreicher Verbindung) wird,
    falls dieses Flag gesetzt ist, das RFID-Tag, das ursprünglich die gescheiterte
    Playlist ausgelöst hat (gPlayProperties.playRfidTag), automatisch erneut in die
    RFID-Warteschlange eingereiht – unter Wiederverwendung desselben Mechanismus, der
    bereits an anderer Stelle zum Fortsetzen der Wiedergabe nach einer
    Sprachausgabe-Unterbrechung genutzt wird. Dies funktioniert unabhängig von der
    Einstellung „letzte RFID nach Neustart abspielen“ und gilt somit für jedes Tag,
    nicht nur für eine automatisch fortgesetzte Sitzung.
  • Der WEBSTREAM-Fall in AudioPlayer_SetPlaylist() scheitert nicht mehr sofort,
    wenn das WLAN noch nicht verbunden ist; er durchläuft jetzt denselben
    Dispatch-/Retry-Pfad wie LOCAL_M3U, sodass auch reine Webstream-Tags (nicht nur
    m3u-Playlists) davon profitieren.
  • Die Sperrlogik von DONT_ACCEPT_SAME_RFID_TWICE wurde korrigiert: Sie wird jetzt
    bereits scharf geschaltet, sobald eine neue Playlist akzeptiert wird
    (AudioPlayer_ResetOldRfidOnIdle, gesetzt an der Stelle, an der
    newPlayListAvailable verarbeitet wird), statt nur durch eine spätere
    Loop-Iteration scharf geschaltet zu werden, die einen „aktiv“-Zustand beobachtet –
    einen Zustand, den eine Playlist, die bereits beim ersten Titel scheitert, nie
    erreicht.
  • Eine neue übersetzte Log-Meldung (retryingRfidAfterWifiConnected) hinzugefügt,
    die beim automatischen erneuten Versuch protokolliert wird, in Englisch, Deutsch
    und Französisch.

Das Ergebnis:
Ein beim Booten aufgelegtes Tag, das auf einen Webstream verweist (direkt oder via
m3u), wird jetzt automatisch erneut versucht, sobald das WLAN tatsächlich verbunden
ist – ohne dass ein erneutes Auflegen nötig ist. Und das funktioniert unabhängig von
DONT_ACCEPT_SAME_RFID_TWICE, da sich die „Gleiches-Tag“-Sperre nun korrekt
zurücksetzt, sobald der gescheiterte Versuch in den Leerlauf geht.

Änderungen im Detail:
main.cpp: hinzufügen der globalen Variable gRetryRfidOnWifiConnect

voher:
bool gPlayLastRfIdWhenWiFiConnected = false;
bool gTriedToConnectToHost = false;

nachher:
bool gPlayLastRfIdWhenWiFiConnected = false;
bool gTriedToConnectToHost = false;
bool gRetryRfidOnWifiConnect = false;

Main.h

extern bool gRetryRfidOnWifiConnect;

audioplayer.cpp (95) hinzufügen der statischen Variable AudioPlayer_ResetOldRfidOnIdle

vorher:
static bool AudioPlayer_UploadActive = false;
static bool AudioPlayer_WasPausedBeforeUpload = false; // remember pre-upload pause state

nachher:
static bool AudioPlayer_UploadActive = false;
static bool AudioPlayer_WasPausedBeforeUpload = false; // remember pre-upload pause state

// Armed as soon as a new playlist is accepted; consumed once playback goes idle again, at which
// point gOldRfidTagId is reset (dontAcceptRfidTwice). Armed here (rather than only when the loop
// observes an "active" iteration) because playlists that fail on their very first track (e.g. a
// webstream attempted before WiFi is up) never have such an iteration - every early-return would
// otherwise leave gOldRfidTagId stuck forever.
static bool AudioPlayer_ResetOldRfidOnIdle = false;

audioplayer.cpp(633) AudioPlayer_ResetOldRfidOnIdle scharf schalten

vorher:
	if (newPlayListAvailable || gPlayProperties.trackFinished || trackCommand != NO_ACTION) {
		if (newPlayListAvailable) {
			newPlayListAvailable = false;
			audio->stopSong();

			// destroy the old playlist and assign the new one
			freePlaylist(gPlayProperties.playlist);
			gPlayProperties.playlist = newPlayList;
			Log_Printf(LOGLEVEL_NOTICE, newPlaylistReceived, gPlayProperties.playlist->size());
			Log_Printf(LOGLEVEL_DEBUG, "Free heap: %u", ESP.getFreeHeap());
			playbackTimeoutStart = millis();
			gPlayProperties.pausePlay = false;
			gPlayProperties.trackFinished = false;
			gPlayProperties.playlistFinished = false;

nachher:
	if (newPlayListAvailable || gPlayProperties.trackFinished || trackCommand != NO_ACTION) {
		if (newPlayListAvailable) {
			newPlayListAvailable = false;
			audio->stopSong();

			// destroy the old playlist and assign the new one
			freePlaylist(gPlayProperties.playlist);
			gPlayProperties.playlist = newPlayList;
			Log_Printf(LOGLEVEL_NOTICE, newPlaylistReceived, gPlayProperties.playlist->size());
			Log_Printf(LOGLEVEL_DEBUG, "Free heap: %u", ESP.getFreeHeap());
			playbackTimeoutStart = millis();
			gPlayProperties.pausePlay = false;
			gPlayProperties.trackFinished = false;
			gPlayProperties.playlistFinished = false;
			AudioPlayer_ResetOldRfidOnIdle = true; // Make sure gOldRfidTagId gets reset once this playlist goes idle, even if it never gets past a failed first track

audioplayer.cpp (984) gRetryRfidOnWifiConnect scharf schalten

vorher:
	if (gPlayProperties.playMode == WEBSTREAM || (gPlayProperties.playMode == LOCAL_M3U && gPlayProperties.isWebstream)) { // Webstream
			audioReturnCode = audio->connecttohost(gPlayProperties.playlist->at(gPlayProperties.currentTrackNumber));

nachher:
		if (gPlayProperties.playMode == WEBSTREAM || (gPlayProperties.playMode == LOCAL_M3U && gPlayProperties.isWebstream)) { // Webstream
			if (Wlan_IsConnected()) {
				audioReturnCode = audio->connecttohost(gPlayProperties.playlist->at(gPlayProperties.currentTrackNumber));
			} else {
				// WiFi not up yet (e.g. right after boot): don't bother trying to connect, remember to
				// retry this RFID-tag automatically once WiFi becomes available (see handleWifiStateConnected())
				Log_Println(webstreamNotAvailable, LOGLEVEL_ERROR);
				gRetryRfidOnWifiConnect = true;
				audioReturnCode = false;
			}

audioplayer.cpp (1130) audio->isRunning() nicht mehrfach aufrufen

vorher:
	if (audio->isRunning()) {
		playbackTimeoutStart = millis();
	}

	// If error occured: move to the next track in the playlist
	const bool activeMode = (gPlayProperties.playMode != NO_PLAYLIST && gPlayProperties.playMode != BUSY);
	const bool noAudio = (!audio->isRunning() && !gPlayProperties.pausePlay);
nachher:
	const bool audioIsRunning = audio->isRunning();

	if (audioIsRunning) {
		playbackTimeoutStart = millis();
	}

	// If error occured: move to the next track in the playlist
	const bool activeMode = (gPlayProperties.playMode != NO_PLAYLIST && gPlayProperties.playMode != BUSY);
	const bool noAudio = (!audioIsRunning && !gPlayProperties.pausePlay);

audioplayer.cpp (1151 ) Umstellung resetOnNextIdle auf AudioPlayer_ResetOldRfidOnIdle

vorher:
	if (gPlayProperties.dontAcceptRfidTwice) {
		static uint8_t resetOnNextIdle = false;
		if (gPlayProperties.playlistFinished || gPlayProperties.playMode == NO_PLAYLIST) {
			if (resetOnNextIdle) {
				Rfid_ResetOldRfid();
				resetOnNextIdle = false;
			}
		} else {
			resetOnNextIdle = true;
		}
	}

nachher:
	if (gPlayProperties.dontAcceptRfidTwice) {
		if (gPlayProperties.playlistFinished || gPlayProperties.playMode == NO_PLAYLIST) {
			if (AudioPlayer_ResetOldRfidOnIdle) {
				Rfid_ResetOldRfid();
				AudioPlayer_ResetOldRfidOnIdle = false;
			}
		} else {
			AudioPlayer_ResetOldRfidOnIdle = true;
		}
	}

audioplayer.cpp (1398) playmode schleife nicht verlassen wenn WiFi noch nicht da ist.

vorher:
		case WEBSTREAM: { // This is always just one "track"
			Log_Println(modeWebstream, LOGLEVEL_NOTICE);
			if (!Wlan_IsConnected()) {
				Log_Println(webstreamNotAvailable, LOGLEVEL_ERROR);
				error = true;
			}
			break;
		}

nachher:
		case WEBSTREAM: { // This is always just one "track"
			Log_Println(modeWebstream, LOGLEVEL_NOTICE);
			// Don't bail out here if WiFi isn't connected yet (e.g. right after boot): the
			// dispatch in AudioPlayer_Loop() will retry automatically once WiFi comes up.
			break;
		}

Wlan.cpp (9) Queues.h hinzufügen

#include "Queues.h"

Wlan.cpp (507) Rerty für Webstreams anstossen wenn Wlan verfügbar

vorher:
	if (playLastRfidAfterReboot && gPlayLastRfIdWhenWiFiConnected && gTriedToConnectToHost) {
		gPlayLastRfIdWhenWiFiConnected = false;
		recoverLastRfidPlayedFromNvs(true);
	}

nachher:
	if (playLastRfidAfterReboot && gPlayLastRfIdWhenWiFiConnected && gTriedToConnectToHost) {
		gPlayLastRfIdWhenWiFiConnected = false;
		recoverLastRfidPlayedFromNvs(true);
	}

	// A webstream/m3u-webstream RFID-tag (e.g. applied right at boot) couldn't be started
	// because WiFi wasn't connected yet. Now that it is, retry it regardless of playLastRfidAfterReboot.
	if (gRetryRfidOnWifiConnect) {
		gRetryRfidOnWifiConnect = false;
		if (strlen(gPlayProperties.playRfidTag) > 0) {
			Log_Printf(LOGLEVEL_NOTICE, retryingRfidAfterWifiConnected, gPlayProperties.playRfidTag);
			xQueueSend(gRfidCardQueue, gPlayProperties.playRfidTag, 0);
		}
	}

LogMessagees_DE.cpp: Zusätzliche Infomeldung hinzufügen gleiches in EN und FR

const char retryingRfidAfterWifiConnected[] = "RFID-Tag %s wird erneut versucht, da WLAN nun verbunden ist";

Logmessages.h

extern const char retryingRfidAfterWifiConnected[];

Bin grad ein bisschen unter Wasser, sorry, Schaue mir das die Tage an.

Aus meiner Sicht ist das Thema gefixt - hab’s getestet:

Habe mir jetzt (zum ersten Mal bei ESPuino) Claude zu Hilfe genommen. Das werde ich jetzt öfter mal machen :slight_smile:

@Chris_KA Ich hoffe du bist mir jetzt nicht böse, dass ich das quasi „hijackt“ habe. Größtenteils ist Claude deinem Fix gefolgt.

Problem

Ein Webradio-Tag, das beim Booten aufliegt (physisch oder via Auto-Replay PLAY_LAST_RFID_AFTER_REBOOT), spielt nicht — weil das WLAN zum Zeitpunkt der Tag-Verarbeitung noch nicht verbunden ist. Der vorhandene Retry-Mechanismus war für genau diesen Fall toter Code (er hing an gTriedToConnectToHost, das nur gesetzt wird, wenn connecttohost() überhaupt erreicht wird — was im Fehlerfall nie passiert).

Fixes (2 zusammenhängende)

1. Retry nach WLAN-Connect

  • Scheitert ein Webstream mangels WLAN, wird der Tag gemerkt (gRetryRfidTagId) und gRetryRfidOnWifiConnect gesetzt.
  • Sobald WLAN steht, wird der Tag erneut in die RFID-Queue gelegt — nur wenn nichts anderes läuft, mit geprüftem xQueueSend (Fehlschlag wird geloggt statt still verworfen).
  • Funktioniert unabhängig vom Reboot-Feature und deckt auch m3u-Webstreams ab.
  • Der alte, kaputte Mechanismus (gPlayLastRfIdWhenWiFiConnected/gTriedToConnectToHost) wurde entfernt.

2. DONT_ACCEPT_SAME_RFID_TWICE-Lockout

  • Die Sperre wird jetzt beim Akzeptieren des Tags scharfgeschaltet statt erst bei aktiver Wiedergabe. Sonst bliebe ein Tag mit sofort scheiterndem ersten Track für immer gesperrt — was auch Fix 1 blockiert hätte.

Zwei Abweichungen vom Forums-Vorschlag (durch Code-Analyse erzwungen)

  • Retry nutzt gCurrentRfidTagId statt gPlayProperties.playRfidTag (letzteres ist auf dem Fehlerpfad nie gesetzt).
  • Idle-Check + Rückgabewert-Prüfung beim Re-Enqueue.