clementine-player / Clementine

:tangerine: Clementine Music Player
https://www.clementine-player.org/
GNU General Public License v3.0
3.76k stars 677 forks source link

.pls file from Radio-browser.info stream generates "This appears to be a text file" #6988

Closed diracsbracket closed 3 years ago

diracsbracket commented 3 years ago

When selecting the channel "KBS Classic FM" from Korea in Radio-Browser.info stream, I get a "This appears to be a text file" message from GStreamer.

URL handler for QUrl("radiobrowser:c038a859-7be1-11e9-aa30-52543be04c81") returned "http://serpent0.duckdns.org:8088/kbsfm.pls" (query removed)
20:03:38.504 WARN  unknown                          libpng warning: iCCP: known incorrect sRGB profile

0:00:04.715749337 55968 0x559a3f473000 FIXME                    bin gstbin.c:4338:gst_bin_query: implement duration caching in GstBin again
0:00:05.249986982 55968 0x7f08643c94f0 WARN               decodebin gstdecodebin2.c:2852:type_found:<decodebin0> error: This appears to be a text file
0:00:05.250674058 55968 0x7f08643c94f0 WARN               decodebin gstdecodebin2.c:2852:type_found:<decodebin0> error: decodebin cannot decode plain text files
20:03:39.270 ERROR GstEnginePipeline:733            1 "gstdecodebin2.c(2852): type_found (): /GstPipeline:audio-pipeline-1/GstURIDecodeBin:uridecodebin-0/GstDecodeBin:decodebin0:"
20:03:39.271 ERROR GstEnginePipeline:733            1 "decodebin cannot decode plain text files"
20:03:39.271 WARN  GstEngine:719                    Gstreamer error: "This appears to be a text file"

When the .pls URL is added manually via the Add Stream menu, it works however.

Steps to reproduce:

Radio-Browser-info --> By Country --> The Republic of Korea --> KBS Classic FM

To add it manually from menu : Playlist | Add Stream, with URL

http://serpent0.duckdns.org:8088/kbsfm.pls

Platform: Linux Buster

name -a
Linux debian 4.19.0-16-amd64 #1 SMP Debian 4.19.181-1 (2021-03-19) x86_64 GNU/Linux

Clementine built from source:

Version 1.4.0rc1-557-g009642d12

GStreamer packages installed:

buster@debian:~$ dpkg -l | grep gstreamer
ii  gir1.2-gstreamer-1.0:amd64             1.14.4-1                                     amd64        GObject introspection data for the GStreamer library
ii  gstreamer1.0-alsa:amd64                1.14.4-2                                     amd64        GStreamer plugin for ALSA
ii  gstreamer1.0-clutter-3.0:amd64         3.0.26-2                                     amd64        Clutter PLugin for GStreamer 1.0
ii  gstreamer1.0-gl:amd64                  1.14.4-2                                     amd64        GStreamer plugins for GL
ii  gstreamer1.0-gtk3:amd64                1.14.4-1                                     amd64        GStreamer plugin for GTK+3
ii  gstreamer1.0-libav:amd64               1.15.0.1+git20180723+db823502-2              amd64        libav plugin for GStreamer
ii  gstreamer1.0-packagekit                1.1.12-5                                     amd64        GStreamer plugin to install codecs using PackageKit
ii  gstreamer1.0-plugins-bad:amd64         1.14.4-1+deb10u1                             amd64        GStreamer plugins from the "bad" set
ii  gstreamer1.0-plugins-base:amd64        1.14.4-2                                     amd64        GStreamer plugins from the "base" set
ii  gstreamer1.0-plugins-good:amd64        1.14.4-1                                     amd64        GStreamer plugins from the "good" set
ii  gstreamer1.0-plugins-ugly:amd64        1.14.4-1                                     amd64        GStreamer plugins from the "ugly" set
ii  gstreamer1.0-pulseaudio:amd64          1.14.4-1                                     amd64        GStreamer plugin for PulseAudio
ii  gstreamer1.0-tools                     1.14.4-1                                     amd64        Tools for use with GStreamer
ii  gstreamer1.0-x:amd64                   1.14.4-2                                     amd64        GStreamer plugins for X11 and Pango
ii  libgstreamer-gl1.0-0:amd64             1.14.4-2                                     amd64        GStreamer GL libraries
ii  libgstreamer-plugins-bad1.0-0:amd64    1.14.4-1+deb10u1                             amd64        GStreamer libraries from the "bad" set
ii  libgstreamer-plugins-base1.0-0:amd64   1.14.4-2                                     amd64        GStreamer libraries from the "base" set
ii  libgstreamer-plugins-base1.0-dev:amd64 1.14.4-2                                     amd64        GStreamer development files for libraries from the "base" set
ii  libgstreamer1.0-0:amd64                1.14.4-1                                     amd64        Core GStreamer libraries and elements
ii  libgstreamer1.0-dev:amd64              1.14.4-1                                     amd64        GStreamer core development files
ii  libreoffice-avmedia-backend-gstreamer  1:6.1.5-3+deb10u7                            amd64        GStreamer backend for LibreOffice
buster@debian:~$ 
jbroadus commented 3 years ago

It's working for me with gstreamer 1.18.2. Do you have an aac decoder element installed? (You can look at gst-inspect-1.0)

diracsbracket commented 3 years ago

@jbroadus

Do you have an aac decoder element installed

As mentioned, it works when the stream is added manually? In any case, this is the output of gst-inspect-1.0:

buster@debian:~$ gst-inspect-1.0 | grep aac
voaacenc:  voaacenc: AAC audio encoder
libav:  avmux_adts: libav ADTS AAC (Advanced Audio Coding) muxer (not recommended, use aacparse instead)
libav:  avdec_aac_latm: libav AAC LATM (Advanced Audio Coding LATM syntax) decoder
libav:  avdec_aac_fixed: libav AAC (Advanced Audio Coding) decoder
libav:  avdec_aac: libav AAC (Advanced Audio Coding) decoder
libav:  avenc_aac: libav AAC (Advanced Audio Coding) encoder
audioparsers:  aacparse: AAC audio stream parser
typefindfunctions: audio/aac: aac, adts, adif, loas
diracsbracket commented 3 years ago

Also tried with Clementine build version:

Version 1.4.0rc1-566-gf04657e7e
diracsbracket commented 3 years ago

I just build GStreamer from source,

[gst-master] buster@debian:~/gst-build/build$ gst-launch-1.0 --version
gst-launch-1.0 version 1.19.0
GStreamer 1.19.0 (GIT)
Unknown package origin
[gst-master] buster@debian:~/gst-build/build$ 

And when I launch:

gst-master] buster@debian:~/gst-build/build$ GST_DEBUG=*:3 gst-launch-1.0 -v playbin uri= http://serpent0.duckdns.org:8088/kbsfm.pls

I get:

[gst-master] buster@debian:~/gst-build/build$ GST_DEBUG=*:3 gst-launch-1.0 -v playbin uri= http://serpent0.duckdns.org:8088/kbsfm.pls
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0: ring-buffer-max-size = 0
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0: buffer-size = -1
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0: buffer-duration = -1
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0: force-sw-decoders = false
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0: use-buffering = false
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0: download = false
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0: uri = http://serpent0.duckdns.org:8088/kbsfm.pls
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0: connection-speed = 0
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0: source = "\(GstSoupHTTPSrc\)\ source"
0:00:00.038604197 25480 0x55922d154070 WARN               structure gststructure.c:2093:priv_gst_structure_append_to_gstring: No value transform to serialize field 'session' of type 'SoupSession'
Got context from element 'source': gst.soup.session=context, session=(SoupSession)NULL, force=(boolean)false;
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstTypeFindElement:typefindelement0.GstPad:src: caps = text/plain
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstDecodeBin:decodebin0/GstTypeFindElement:typefind: force-caps = text/plain
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstDecodeBin:decodebin0: sink-caps = text/plain
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstQueue2:queue2-0: bitrate = 0
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstQueue2:queue2-0: bitrate = 0
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstQueue2:queue2-0.GstPad:sink: caps = text/plain
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstQueue2:queue2-0: bitrate = 0
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstQueue2:queue2-0: bitrate = 0
Setting pipeline to PLAYING ...
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstQueue2:queue2-0.GstPad:src: caps = text/plain
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstQueue2:queue2-0: bitrate = 0
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstDecodeBin:decodebin0.GstGhostPad:sink.GstProxyPad:proxypad0: caps = text/plain
/GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstDecodeBin:decodebin0/GstTypeFindElement:typefind.GstPad:src: caps = text/plain
0:00:00.230288908 25480 0x7f610c413770 WARN               decodebin gstdecodebin2.c:2890:type_found:<decodebin0> error: This appears to be a text file
0:00:00.230350827 25480 0x7f610c413770 WARN               decodebin gstdecodebin2.c:2890:type_found:<decodebin0> error: decodebin cannot decode plain text files
ERROR: from element /GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstDecodeBin:decodebin0: This appears to be a text file
Additional debug info:
../subprojects/gst-plugins-base/gst/playback/gstdecodebin2.c(2890): type_found (): /GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstDecodeBin:decodebin0:
decodebin cannot decode plain text files
0:00:00.230483403 25480 0x7f610c413770 WARN                  queue2 gstqueue2.c:3242:gst_queue2_loop:<queue2-0> error: Internal data stream error.
0:00:00.230531074 25480 0x7f610c413770 WARN                  queue2 gstqueue2.c:3242:gst_queue2_loop:<queue2-0> error: streaming stopped, reason not-linked (-1)
Execution ended after 0:00:00.000350034
Setting pipeline to NULL ...
ERROR: from element /GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstQueue2:queue2-0: Internal data stream error.
Additional debug info:
../subprojects/gstreamer/plugins/elements/gstqueue2.c(3242): gst_queue2_loop (): /GstPlayBin:playbin0/GstURIDecodeBin:uridecodebin0/GstQueue2:queue2-0:
streaming stopped, reason not-linked (-1)
Freeing pipeline ..
diracsbracket commented 3 years ago

@jbroadus, It appears GStreamer just does not support .pls files, and that they must be parsed first by external means. When the stream is added manually, SongLoader::LoadRemotePlaylist() is invoked, which downloads the .pls file and parses it with a ParserBase object.

That does NOT happen when the corresponding RadioBrowser channel is clicked, as the .pls file is sent directly to GStreamer.

So, it's weird that it works on your side...

jbroadus commented 3 years ago

Yeah, I'm sorry, I completely missed this line in your original description: When the .pls URL is added manually via the Add Stream menu, it works however. I am seeing the same behavior. It seems like we should be able to use the same stream discovery logic in the stream services.

diracsbracket commented 3 years ago

Yeah, I'm sorry, I completely missed this line in your original description:

No worries. And thank you so much for the time and effort you put in all this.

diracsbracket commented 3 years ago

Hi. I found a possible solution to this, by duplicating (!) the code from SongLoader::LoadRemotePlaylist(() and adding it to the RadioBrowserService class like so:

//.h
#if 1
class PlaylistParser;
#endif

class RadioBrowserService : public InternetService {
  Q_OBJECT
   ...

#if 1
  void checkIfPlaylist(const QUrl& url, Song& song);
#endif
  ...

#if 1
 PlaylistParser* playlist_parser_;
#endif
}

//.cpp
#if 1
#include "library/librarybackend.h"
#include "playlistparsers/playlistparser.h"
#include "playlistparsers/parserbase.h"
#include "core/waitforsignal.h"
#endif
...

RadioBrowserService::RadioBrowserService(Application* app,
                                         InternetModel* parent)
    : InternetService(kServiceName, app, parent, parent)
    ...
#if 1
    , playlist_parser_(new PlaylistParser(app->library_backend(), this))
#endif
{
   ...
}

void RadioBrowserService::checkIfPlaylist(const QUrl& url, Song& song) {
    //Test if playlist
    QNetworkRequest req(url);
    // Getting headers:
    QNetworkReply* const headers_reply = network_->head(req);
    WaitForSignal(headers_reply, SIGNAL(finished()));

    if (headers_reply->error() != QNetworkReply::NoError) {
      qLog(Error) << url.toString() << headers_reply->errorString();
      return;
    }

    // Now we check if there is a parser that can handle that MIME type.
    QString mime_type = headers_reply->header(QNetworkRequest::ContentTypeHeader).toString();
    ParserBase* const parser = playlist_parser_->ParserForMimeType(mime_type);
    if (parser == nullptr) {
      qLog(Debug) << url.toString() << "seems to not be a playlist";
      return;
    }

    // We know it is a playlist!
    // Getting its contents:
    QNetworkReply* const data_reply = network_->get(req);
     WaitForSignal(data_reply, SIGNAL(finished()));

    if (data_reply->error() != QNetworkReply::NoError) {
      qLog(Error) << url.toString() << data_reply->errorString();
      return;
    }

    QList<Song> songs = parser->Load(data_reply, QString(), QString());
    if (songs.length()>0) {
        const QUrl new_url = songs.at(0).url();
        song.set_url(new_url);
        qLog(Debug) << "found playlist" << new_url.toString();
    }
}

and modifying RadioBrowserService::ResolveStationUrl() as follows:

void RadioBrowserService::ResolveStationUrl(const QUrl& original_url) {
    ...

#if 0 // UNUSED!
    StreamList list;
#endif
    ...
    Song ret;
    ret.set_valid(true);
    ret.set_title(item["name"].toString());
    QUrl url(item["url"].toString());
    ret.set_url(url);
    ret.set_art_automatic(item["favicon"].toString());

#if 1
    //Replace the url by the one in the playlist
    checkIfPlaylist(url, ret);
#endif

    emit StreamMetadataFound(original_url, ret);
  });
}

Hopefully you can find a better way that avoids the above code duplication.

jbroadus commented 3 years ago

It looks like we might just be able to use "url_resolved" instead of "url" from the station structure. https://fr1.api.radio-browser.info/#Struct_station

cc @ctrlaltca

diracsbracket commented 3 years ago

It looks like we might just be able to use "url_resolved" instead of "url" from the station structure.

Things get simpler and simpler.... Nice catch!

ctrlaltca commented 3 years ago

Sorry, I missed the notification for this issue. PR created: https://github.com/clementine-player/Clementine/pull/7035