public void updateInDecoderThread(float tpf) {
        if (audioDisabled) {
            return;
        }

        for (int i = 0; i < channels.length; i++) {
            AudioSource src = channelSources[i];

            // Only process streaming sources associated with this channel
            if (src == null || !(src.getAudioData() instanceof AudioStream)) {
                continue;
            }

            int sourceId = channels[i];
            AudioStream stream = (AudioStream) src.getAudioData();

            // Get current AL state, primarily to check if we need to restart playback
            int openALState = al.alGetSourcei(sourceId, AL_SOURCE_STATE);
            Status openALStatus = convertStatus(openALState);
            Status jmeStatus = src.getStatus();

            // Keep filling data (even if we are stopped / paused)
            boolean buffersWereFilled = fillStreamingSource(sourceId, stream, src.isLooping());

            // Check if the source stopped due to buffer starvation while it was supposed to be playing
            if (buffersWereFilled
                    && openALStatus == Status.Stopped
                    && jmeStatus == Status.Playing) {
                // The source got stopped due to buffer starvation.
                // Start it again.
                logger.log(Level.WARNING, "Buffer starvation detected for stream on channel {0}. Restarting playback.", i);
                al.alSourcePlay(sourceId);
            }
        }

        // Delete any unused objects (buffers, filters) that are no longer referenced.
        objManager.deleteUnused(this);
    }
Internal update logic called from the decoder thread within the lock.
Fills streaming buffers and restarts starved sources. Deletes unused objects.

@param tpf Time per frame (currently unused).
jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java