Strange Behavior From WebInputStream


#1

Hello,

I know I’ve posted with a little confusion around this before, sorry for any redundancy, but the issue is a bit clearer.

Using a WebInputStream into an AudioFormatManager to generate a Reader creates what seems like multiple unnecessary downloads from the InputStream.

Here is a test app showing the issue. Hitting the download button begins a download which seems to occur twice before the FormatManager is able to read from it. It then appears that the FormatManager reads the stream a third time, meaning that to download an audio file and play it, the app is needing to download it three times!

Here is some code based from the GUI App template:

MainComponent.h:

class MainContentComponent   : public Component,
                               public Thread,
                               public Button::Listener,
                               public Timer
{
public:
    //==============================================================================
    MainContentComponent();
    ~MainContentComponent();

    void paint (Graphics&) override;
    
    void timerCallback() override;
    void buttonClicked(Button *b) override;
    void run() override;
    
private:
    
    ScopedPointer<TextButton> downloadFileBtn;
    void downloadFile();
    
    ScopedPointer<AudioFormatManager> formatManager;
    InputStream* audioInputStream;
    
    AudioSampleBuffer downloadBuffer;
    
    bool fileLoaded = false;
    bool downloading = false;
    bool buffering = false;
    bool threadFirstRun = true;
    int totalLength = 0;
    int currentPosition = 0;
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

MainComponent.cpp

//==============================================================================
MainContentComponent::MainContentComponent() : Thread("Thread"),
                                               audioInputStream(nullptr)
{
    setSize (300, 100);
    
    downloadFileBtn = new TextButton("Download");
    downloadFileBtn->setBounds(0, 0, getWidth(), getHeight()/2);
    downloadFileBtn->addListener(this);
    addAndMakeVisible(downloadFileBtn);
    
    formatManager = new AudioFormatManager;
    formatManager->registerBasicFormats();
}

MainContentComponent::~MainContentComponent()
{
}

void MainContentComponent::paint (Graphics& g)
{
    if (audioInputStream != nullptr)
    {
        g.fillAll(Colours::black);
        g.setColour(Colours::red);
        float width = jmap((float)currentPosition, 0.0f, (float)totalLength, 0.0f, (float)getWidth());
        g.fillRect(0, getHeight()/2, (int)width, getHeight()/2);
    }
    else
    {
        g.fillAll(Colours::black);
    }
    
}

void MainContentComponent::timerCallback()
{
    if (audioInputStream != nullptr)
    {
        currentPosition = audioInputStream->getPosition();
        
        totalLength = audioInputStream->getTotalLength();
        if (totalLength < 1)
            totalLength = currentPosition + 1;
        
        DBG("CURRENT POSITION:  " << currentPosition);
        DBG("TOTAL LENGTH:      " << totalLength);
        
        repaint();
    }
}

void MainContentComponent::buttonClicked(juce::Button *b)
{
    if (b == downloadFileBtn)
        startThread();
}

void MainContentComponent::run()
{
    while (! threadShouldExit())
    {
        DBG("THREAD RUN");
        if (threadFirstRun == true)
        {
            threadFirstRun = false;
            downloadFile();
        }
        else
        {
            signalThreadShouldExit();
        }
    }
}

void MainContentComponent::downloadFile()
{
    DBG("DOWNLOAD");
    if (fileLoaded != true)
    {
        startTimer(50);
        downloading = true;
        
        URL theURL("https://jakemumu.github.io/08.wav");
        ScopedPointer<AudioFormatReader> reader;
        reader = formatManager->createReaderFor(audioInputStream = theURL.createInputStream(false));
        
        if (reader != nullptr)
        {
            downloading = false;
            buffering = true;
            
            downloadBuffer.setSize(reader->numChannels, reader->lengthInSamples);
            reader->read(&downloadBuffer, 0, reader->lengthInSamples, 0, true, true);
            DBG("FILE LOADED");
            
            fileLoaded = true;
            buffering = false;
            stopTimer();
            signalThreadShouldExit();
        }
    }
}

Note you may have to run the app twice to get this behavior, but it is a regular occurence. My setup is currently OS X El Capitan btw.


#2

This is a basic C++ error: Code in a class definition is not executed, so your variables are uninitialized.
That also explains why you get different behaviour on different runs…

Put the initialisation into your constructor, and see, if the issue is gone…


#3

in c++ 11 you can initialize variables in your header file, this also is just ripped from a project in which these were being initialized in the constructor.

I also just moved them to the constructor in the test app provided to be certain, same result. It just occasionally will successfully pass to the FormatManager after the first download, but more often than not takes two downloads. Thanks though Daniel! : )


#4

Ok, that’s new to me… Sorry, I learned C++ 15 Years ago, should get myself up to date with C++11 features… :slight_smile:


#5

Well .wav files are just fundamentally not streamable as they contain chunks which could occur in any order in the file. Streamable formats are mp3, vorbis, aac and opus. I’ve put together a project here which streams an mp3 from the web just fine.


#6

Wow,

Fabian thank you so much for the example, that is extremely useful. I tried getting streaming working as well, thinking that I would need to manually access the stream to convert the bytes to floats myself and pack a buffer, but it appears there was a fundamental lack of understanding in my approach. I thought the uncompressed media format would make this transformation easier in my initial attempts.

In the test app I provided I wasn’t trying to stream, just simply download a .wav. If you get a chance to run it maybe you’ll see the behavior I was speaking of, but thank you again for that : )

Jake


#7

If the audio file is not streamable (as is the case with .wav files) then you need to download the whole file first - store it to memory or to disk. And then open a new stream to the stored file/memory and create an AudioFormatReader which uses this new stream.