All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.mathiewz.slick.openal.OpenALStreamPlayer Maven / Gradle / Ivy

Go to download

The main purpose of this libraryis to modernize and maintain the slick2D library.

The newest version!
package com.github.mathiewz.slick.openal;

import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.AL11;
import org.lwjgl.openal.OpenALException;

import com.github.mathiewz.slick.util.Log;
import com.github.mathiewz.slick.util.ResourceLoader;

/**
 * A generic tool to work on a supplied stream, pulling out PCM data and buffered it to OpenAL
 * as required.
 *
 * @author Kevin Glass
 * @author Nathan Sweet 
 * @author Rockstar play and setPosition cleanup
 */
public class OpenALStreamPlayer {
    /** The number of buffers to maintain */
    public static final int BUFFER_COUNT = 3;
    /** The size of the sections to stream from the stream */
    private static final int sectionSize = 4096 * 20;

    /** The buffer read from the data stream */
    private final byte[] buffer = new byte[sectionSize];
    /** Holds the OpenAL buffer names */
    private final IntBuffer bufferNames;
    /** The byte buffer passed to OpenAL containing the section */
    private final ByteBuffer bufferData = BufferUtils.createByteBuffer(sectionSize);
    /** The buffer holding the names of the OpenAL buffer thats been fully played back */
    private final IntBuffer unqueued = BufferUtils.createIntBuffer(1);
    /** The source we're playing back on */
    private final int source;
    /** The number of buffers remaining */
    private int remainingBufferCount;
    /** True if we should loop the track */
    private boolean loop;
    /** True if we've completed play back */
    private boolean done = true;
    /** The stream we're currently reading from */
    private AudioInputStream audio;
    /** The source of the data */
    private String ref;
    /** The source of the data */
    private URL url;
    /** The pitch of the music */
    private float pitch;
    /** Position in seconds of the previously played buffers */
    private float positionOffset;

    /**
     * Create a new player to work on an audio stream
     *
     * @param source
     *            The source on which we'll play the audio
     * @param ref
     *            A reference to the audio file to stream
     */
    public OpenALStreamPlayer(int source, String ref) {
        this.source = source;
        this.ref = ref;

        bufferNames = BufferUtils.createIntBuffer(BUFFER_COUNT);
        AL10.alGenBuffers(bufferNames);
    }

    /**
     * Create a new player to work on an audio stream
     *
     * @param source
     *            The source on which we'll play the audio
     * @param url
     *            A reference to the audio file to stream
     */
    public OpenALStreamPlayer(int source, URL url) {
        this.source = source;
        this.url = url;

        bufferNames = BufferUtils.createIntBuffer(BUFFER_COUNT);
        AL10.alGenBuffers(bufferNames);
    }

    /**
     * Initialise our connection to the underlying resource
     *
     * @throws IOException
     *             Indicates a failure to open the underling resource
     */
    private void initStreams() throws IOException {
        if (audio != null) {
            audio.close();
        }

        OggInputStream audio;

        if (url != null) {
            audio = new OggInputStream(url.openStream());
        } else {
            audio = new OggInputStream(ResourceLoader.getResourceAsStream(ref));
        }

        this.audio = audio;
        positionOffset = 0;
    }

    /**
     * Get the source of this stream
     *
     * @return The name of the source of string
     */
    public String getSource() {
        return url == null ? ref : url.toString();
    }

    /**
     * Clean up the buffers applied to the sound source
     */
    private void removeBuffers() {
        IntBuffer buffer = BufferUtils.createIntBuffer(1);
        int queued = AL10.alGetSourcei(source, AL10.AL_BUFFERS_QUEUED);

        while (queued > 0) {
            AL10.alSourceUnqueueBuffers(source, buffer);
            queued--;
        }
    }

    /**
     * Start this stream playing
     *
     * @param loop
     *            True if the stream should loop
     * @throws IOException
     *             Indicates a failure to read from the stream
     */
    public void play(boolean loop) throws IOException {
        this.loop = loop;
        initStreams();

        done = false;

        AL10.alSourceStop(source);
        removeBuffers();

        startPlayback();
    }

    /**
     * Setup the playback properties
     *
     * @param pitch
     *            The pitch to play back at
     */
    public void setup(float pitch) {
        this.pitch = pitch;
    }

    /**
     * Check if the playback is complete. Note this will never
     * return true if we're looping
     *
     * @return True if we're looping
     */
    public boolean done() {
        return done;
    }

    /**
     * Poll the bufferNames - check if we need to fill the bufferNames with another
     * section.
     *
     * Most of the time this should be reasonably quick
     */
    public void update() {
        if (done) {
            return;
        }

        float sampleRate = audio.getRate();
        float sampleSize;
        if (audio.getChannels() > 1) {
            sampleSize = 4; // AL10.AL_FORMAT_STEREO16
        } else {
            sampleSize = 2; // AL10.AL_FORMAT_MONO16
        }

        int processed = AL10.alGetSourcei(source, AL10.AL_BUFFERS_PROCESSED);
        while (processed > 0) {
            unqueued.clear();
            AL10.alSourceUnqueueBuffers(source, unqueued);

            int bufferIndex = unqueued.get(0);

            float bufferLength = AL10.alGetBufferi(bufferIndex, AL10.AL_SIZE) / sampleSize / sampleRate;
            positionOffset += bufferLength;

            if (stream(bufferIndex)) {
                AL10.alSourceQueueBuffers(source, unqueued);
            } else {
                remainingBufferCount--;
                if (remainingBufferCount == 0) {
                    done = true;
                }
            }
            processed--;
        }

        int state = AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE);

        if (state != AL10.AL_PLAYING) {
            AL10.alSourcePlay(source);
        }
    }

    /**
     * Stream some data from the audio stream to the buffer indicates by the ID
     *
     * @param bufferId
     *            The ID of the buffer to fill
     * @return True if another section was available
     */
    public boolean stream(int bufferId) {
        try {
            int count = audio.read(buffer);

            if (count != -1) {
                bufferData.clear();
                bufferData.put(buffer, 0, count);
                bufferData.flip();

                int format = audio.getChannels() > 1 ? AL10.AL_FORMAT_STEREO16 : AL10.AL_FORMAT_MONO16;
                try {
                    AL10.alBufferData(bufferId, format, bufferData, audio.getRate());
                } catch (OpenALException e) {
                    Log.error("Failed to loop buffer: " + bufferId + " " + format + " " + count + " " + audio.getRate(), e);
                    return false;
                }
            } else {
                if (loop) {
                    initStreams();
                    stream(bufferId);
                } else {
                    done = true;
                    return false;
                }
            }

            return true;
        } catch (IOException e) {
            Log.error(e);
            return false;
        }
    }

    /**
     * Seeks to a position in the music.
     *
     * @param position
     *            Position in seconds.
     * @return True if the setting of the position was successful
     */
    public boolean setPosition(float position) {
        try {
            if (getPosition() > position) {
                initStreams();
            }

            float sampleRate = audio.getRate();
            float sampleSize;
            if (audio.getChannels() > 1) {
                sampleSize = 4; // AL10.AL_FORMAT_STEREO16
            } else {
                sampleSize = 2; // AL10.AL_FORMAT_MONO16
            }

            while (positionOffset < position) {
                int count = audio.read(buffer);
                if (count != -1) {
                    float bufferLength = count / sampleSize / sampleRate;
                    positionOffset += bufferLength;
                } else {
                    if (loop) {
                        initStreams();
                    } else {
                        done = true;
                    }
                    return false;
                }
            }

            startPlayback();

            return true;
        } catch (IOException e) {
            Log.error(e);
            return false;
        }
    }

    /**
     * Starts the streaming.
     */
    private void startPlayback() {
        AL10.alSourcei(source, AL10.AL_LOOPING, AL10.AL_FALSE);
        AL10.alSourcef(source, AL10.AL_PITCH, pitch);

        remainingBufferCount = BUFFER_COUNT;

        for (int i = 0; i < BUFFER_COUNT; i++) {
            stream(bufferNames.get(i));
        }

        AL10.alSourceQueueBuffers(source, bufferNames);
        AL10.alSourcePlay(source);
    }

    /**
     * Return the current playing position in the sound
     *
     * @return The current position in seconds.
     */
    public float getPosition() {
        return positionOffset + AL10.alGetSourcef(source, AL11.AL_SEC_OFFSET);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy