goxr3plus / java-stream-player

🌌Java Advanced Audio Controller Library (WAV, AU, AIFF, MP3, OGG VORBIS, FLAC, MONKEY's AUDIO and SPEEX audio formats )
GNU General Public License v3.0
147 stars 33 forks source link

Study the code of XtremeMediaPlayer #47

Open goxr3plus opened 5 years ago

goxr3plus commented 5 years ago

Here is the project => http://xtrememp.sourceforge.net/

These guys have taken StreamPlayer and boosted it's performance , actually they are not using synchronized locks as we do and they are having other performance boosts . I have studied the code in the past and added some things from them in java-stream-player .

Now i want us to take things from their code and implement it in java-stream-player.

Please have a look on their source code :)

goxr3plus commented 5 years ago

@HelgeStenstrom

goxr3plus commented 5 years ago

You can download the .zip folder with the .src from SourceForge , for example here is their StreamPlayer version ... very close to ours =>

I will upload their source code on a repository so we can easily navigate

/**
 * Xtreme Media Player a cross-platform media player.
 * Copyright (C) 2005-2011 Besmir Beqiri
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package xtrememp.player.audio;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.BooleanControl;
import javax.sound.sampled.Control;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tritonus.share.sampled.TAudioFormat;
import org.tritonus.share.sampled.file.TAudioFileFormat;

import javazoom.spi.PropertiesContainer;
import xtrememp.player.dsp.DigitalSignalSynchronizer;

/**
 *
 * @author Besmir Beqiri
 */
public class AudioPlayer implements Callable<Void> {

    private final Logger logger = LoggerFactory.getLogger(AudioPlayer.class);
    protected final int READ_BUFFER_SIZE = 4 * 1024;
    protected final Lock lock = new ReentrantLock();
    protected final Condition pauseCondition = lock.newCondition();
    protected Object audioSource;
    protected DigitalSignalSynchronizer dss;
    protected AudioFileFormat audioFileFormat;
    protected AudioInputStream audioInputStream;
    protected SourceDataLine sourceDataLine;
    protected String mixerName;
    protected List<PlaybackListener> listeners;
    protected ExecutorService execService;
    protected Future<Void> future;
    protected Map<String, Object> properties;
    protected FloatControl gainControl;
    protected FloatControl panControl;
    protected BooleanControl muteControl;
    protected int bufferSize = AudioSystem.NOT_SPECIFIED;
    public static final int INIT = 0;
    public static final int PLAY = 1;
    public static final int PAUSE = 2;
    public static final int SEEK = 3;
    public static final int STOP = 4;
    protected volatile int state = AudioSystem.NOT_SPECIFIED;
    protected Map<String, Object> emptyMap = new HashMap<String, Object>();
    protected long oldPosition = 0;

    public AudioPlayer() {
        execService = Executors.newFixedThreadPool(1);
        dss = new DigitalSignalSynchronizer();
        listeners = new ArrayList<PlaybackListener>();
        reset();
    }

    public void addPlaybackListener(PlaybackListener listener) {
        if (listener != null && !listeners.contains(listener)) {
            listeners.add(listener);
        }
    }

    public void removePlaybackListener(PlaybackListener listener) {
        if (listener != null) {
            listeners.remove(listener);
        }
    }

    public List<PlaybackListener> getPlaybackListeners() {
        return listeners;
    }

    protected void notifyEvent(Playback state) {
        notifyEvent(state, emptyMap);
    }

    protected void notifyEvent(Playback state, Map properties) {
        for (PlaybackListener listener : listeners) {
            PlaybackEventLauncher launcher = new PlaybackEventLauncher(this, 
                    state, getPosition() - oldPosition, properties, listener);
            launcher.start();
        }
        logger.info("{}", state);
    }

    private void reset() {
        if (sourceDataLine != null) {
            sourceDataLine.flush();
            sourceDataLine.close();
            sourceDataLine = null;
        }
        audioFileFormat = null;
        gainControl = null;
        panControl = null;
        muteControl = null;
        future = null;
        emptyMap.clear();
        oldPosition = 0;
    }

    /**
     * Open file to play.
     * @param file
     * @throws PlayerException
     */
    public void open(File file) throws PlayerException {
        if (file != null) {
            audioSource = file;
            init();
        }
    }

    /**
     * Open URL to play.
     * @param url
     * @throws PlayerException 
     */
    public void open(URL url) throws PlayerException {
        if (url != null) {
            audioSource = url;
            init();
        }
    }

    protected void init() throws PlayerException {
        notifyEvent(Playback.BUFFERING);
        int oldState = state;
        state = AudioSystem.NOT_SPECIFIED;
        if (oldState == INIT || oldState == PAUSE) {
            lock.lock();
            try {
                pauseCondition.signal();
            } finally {
                lock.unlock();
            }
        }
        awaitTermination();
        lock.lock();
        try {
            reset();
            initAudioInputStream();
            initSourceDataLine();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Inits AudioInputStream and AudioFileFormat from the data source.
     * @throws PlayerException
     */
    @SuppressWarnings("unchecked")
    protected void initAudioInputStream() throws PlayerException {
        // Close any previous opened audio stream before creating a new one.
        closeStream();
        if (audioInputStream == null) {
            try {
                logger.info("Data source: {}", audioSource);
                if (audioSource instanceof File) {
                    initAudioInputStream((File) audioSource);
                } else if (audioSource instanceof URL) {
                    initAudioInputStream((URL) audioSource);
                }
                AudioFormat sourceAudioFormat = audioInputStream.getFormat();
                logger.info("Source format: {}", sourceAudioFormat);
                int nSampleSizeInBits = sourceAudioFormat.getSampleSizeInBits();
                if (nSampleSizeInBits <= 0) {
                    nSampleSizeInBits = 16;
                }
                if ((sourceAudioFormat.getEncoding() == AudioFormat.Encoding.ULAW) || (sourceAudioFormat.getEncoding() == AudioFormat.Encoding.ALAW)) {
                    nSampleSizeInBits = 16;
                }
                if (nSampleSizeInBits != 8) {
                    nSampleSizeInBits = 16;
                }
                AudioFormat targetAudioFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, sourceAudioFormat.getSampleRate(), nSampleSizeInBits, sourceAudioFormat.getChannels(), sourceAudioFormat.getChannels() * (nSampleSizeInBits / 8), sourceAudioFormat.getSampleRate(), false);
                logger.info("Target format: {}", targetAudioFormat);
                // Create decoded stream.
                audioInputStream = AudioSystem.getAudioInputStream(targetAudioFormat, audioInputStream);
                if (audioFileFormat instanceof TAudioFileFormat) {
                    // Tritonus SPI compliant audio file format.
                    properties = ((TAudioFileFormat) audioFileFormat).properties();
                    // Clone the Map because it is not mutable.
                    properties = deepCopy(properties);
                } else {
                    properties = new HashMap<String, Object>();
                }
                // Add JavaSound properties.
                if (audioFileFormat.getByteLength() > 0) {
                    properties.put("audio.length.bytes", new Integer(audioFileFormat.getByteLength()));
                }
                if (audioFileFormat.getFrameLength() > 0) {
                    properties.put("audio.length.frames", new Integer(audioFileFormat.getFrameLength()));
                }
                if (audioFileFormat.getType() != null) {
                    properties.put("audio.type", audioFileFormat.getType().toString());
                }
                // Audio format.
                AudioFormat audioFormat = audioFileFormat.getFormat();
                if (audioFormat.getFrameRate() > 0) {
                    properties.put("audio.framerate.fps", new Float(audioFormat.getFrameRate()));
                }
                if (audioFormat.getFrameSize() > 0) {
                    properties.put("audio.framesize.bytes", new Integer(audioFormat.getFrameSize()));
                }
                if (audioFormat.getSampleRate() > 0) {
                    properties.put("audio.samplerate.hz", new Float(audioFormat.getSampleRate()));
                }
                if (audioFormat.getSampleSizeInBits() > 0) {
                    properties.put("audio.samplesize.bits", new Integer(audioFormat.getSampleSizeInBits()));
                }
                if (audioFormat.getChannels() > 0) {
                    properties.put("audio.channels", new Integer(audioFormat.getChannels()));
                }
                if (audioFormat instanceof TAudioFormat) {
                    // Tritonus SPI compliant audio format.
                    properties.putAll(((TAudioFormat) audioFormat).properties());
                }
                for (String key : properties.keySet()) {
                    logger.info("Audio Format Properties: {} = {}", key, properties.get(key));
                }
            } catch (UnsupportedAudioFileException ex) {
                throw new PlayerException(ex);
            } catch (IOException ex) {
                throw new PlayerException(ex);
            }
        }
    }

    /**
     * Inits Audio resources from file.
     * @param file
     * @throws javax.sound.sampled.UnsupportedAudioFileException
     * @throws java.io.IOException
     */
    protected void initAudioInputStream(File file) throws UnsupportedAudioFileException, IOException {
        audioInputStream = AudioSystem.getAudioInputStream(file);
        audioFileFormat = AudioSystem.getAudioFileFormat(file);
    }

    /**
     * Inits Audio resources from URL.
     * @param url
     * @throws javax.sound.sampled.UnsupportedAudioFileException
     * @throws java.io.IOException
     */
    protected void initAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException {
        audioInputStream = AudioSystem.getAudioInputStream(url);
        audioFileFormat = AudioSystem.getAudioFileFormat(url);
    }

    /**
     * Inits Audio resources from AudioSystem.
     * @throws PlayerException
     */
    protected void initSourceDataLine() throws PlayerException {
        if (sourceDataLine == null) {
            try {
                logger.info("Create Source Data Line");
                AudioFormat audioFormat = audioInputStream.getFormat();
                DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, audioFormat, AudioSystem.NOT_SPECIFIED);
                if (!AudioSystem.isLineSupported(lineInfo)) {
                    throw new PlayerException(lineInfo + " is not supported");
                }

                if (mixerName == null) {
                    // Primary Sound Driver
                    mixerName = getMixers().get(0);
                }
                Mixer mixer = getMixer(mixerName);
                if (mixer != null) {
                    logger.info("Mixer: {}", mixer.getMixerInfo().toString());
                    sourceDataLine = (SourceDataLine) mixer.getLine(lineInfo);
                } else {
                    sourceDataLine = (SourceDataLine) AudioSystem.getLine(lineInfo);
                    mixerName = null;
                }

                sourceDataLine.addLineListener(dss);

                logger.info("Line Info: {}", sourceDataLine.getLineInfo().toString());
                logger.info("Line AudioFormat: {}", sourceDataLine.getFormat().toString());

                if (bufferSize <= 0) {
                    bufferSize = sourceDataLine.getBufferSize();
                }
                sourceDataLine.open(audioFormat, bufferSize);

                logger.info("Line BufferSize: {}", sourceDataLine.getBufferSize());
                for (Control c : sourceDataLine.getControls()) {
                    logger.info("Line Controls: {}", c);
                }

                if (sourceDataLine.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
                    gainControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN);
                }
                if (sourceDataLine.isControlSupported(FloatControl.Type.PAN)) {
                    panControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.PAN);
                }
                if (sourceDataLine.isControlSupported(BooleanControl.Type.MUTE)) {
                    muteControl = (BooleanControl) sourceDataLine.getControl(BooleanControl.Type.MUTE);
                }
                sourceDataLine.start();
                state = INIT;
                future = execService.submit(this);
                notifyEvent(Playback.OPENED);
            } catch (LineUnavailableException ex) {
                throw new PlayerException(ex);
            }
        }
    }

    /**
     * Set SourceDataLine buffer size. It affects audio latency
     * (the delay between SourceDataLine.write(data) and real sound).
     * @param bufferSize if equal to -1 (AudioSystem.NOT_SPECIFIED)
     * means maximum buffer size available.
     */
    public void setBufferSize(int bufferSize) {
        if (bufferSize <= 0) {
            this.bufferSize = AudioSystem.NOT_SPECIFIED;
        } else {
            this.bufferSize = bufferSize;
        }
    }

    /**
     * Return SourceDataLine buffer size.
     * @return -1 (AudioSystem.NOT_SPECIFIED) for maximum buffer size.
     */
    public int getBufferSize() {
        return bufferSize;
    }

    /**
     * Deep copy of a Map.
     * @param src
     * @return map
     */
    protected Map<String, Object> deepCopy(Map<String, Object> src) {
        Map<String, Object> map = new HashMap<String, Object>();
        if (src != null) {
            Set<String> keySet = src.keySet();
            for (String key : keySet) {
                Object value = src.get(key);
                map.put(key, value);
//                logger.info("key: {}, value: {}", key, value);
            }
        }
        return map;
    }

    public List<String> getMixers() {
        List<String> mixers = new ArrayList<String>();
        Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo();
        if (mixerInfos != null) {
            for (int i = 0, len = mixerInfos.length; i < len; i++) {
                Line.Info lineInfo = new Line.Info(SourceDataLine.class);
                Mixer _mixer = AudioSystem.getMixer(mixerInfos[i]);
                if (_mixer.isLineSupported(lineInfo)) {
                    mixers.add(mixerInfos[i].getName());
                }
            }
        }
        return mixers;
    }

    public Mixer getMixer(String name) {
        Mixer _mixer = null;
        if (name != null) {
            Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo();
            if (mixerInfos != null) {
                for (int i = 0, len = mixerInfos.length; i < len; i++) {
                    if (mixerInfos[i].getName().equals(name)) {
                        _mixer = AudioSystem.getMixer(mixerInfos[i]);
                        break;
                    }
                }
            }
        }
        return _mixer;
    }

    public String getMixerName() {
        return mixerName;
    }

    public void setMixerName(String name) {
        mixerName = name;
    }

    public long getDuration() {
        long duration = AudioSystem.NOT_SPECIFIED;
        if (properties.containsKey("duration")) {
            duration = ((Long) properties.get("duration")).longValue();
        } else {
            duration = getTimeLengthEstimation(properties);
        }
        return duration;
    }

    public int getByteLength() {
        int bytesLength = AudioSystem.NOT_SPECIFIED;
        if (properties != null) {
            if (properties.containsKey("audio.length.bytes")) {
                bytesLength = ((Integer) properties.get("audio.length.bytes")).intValue();
            }
        }
        return bytesLength;
    }

    public int getPositionByte() {
        int positionByte = AudioSystem.NOT_SPECIFIED;
        if (properties != null) {
            if (properties.containsKey("mp3.position.byte")) {
                positionByte = ((Integer) properties.get("mp3.position.byte")).intValue();
                return positionByte;
            }
            if (properties.containsKey("ogg.position.byte")) {
                positionByte = ((Integer) properties.get("ogg.position.byte")).intValue();
                return positionByte;
            }
        }
        return positionByte;
    }

    protected long getTimeLengthEstimation(Map properties) {
        long milliseconds = AudioSystem.NOT_SPECIFIED;
        int byteslength = AudioSystem.NOT_SPECIFIED;
        if (properties != null) {
            if (properties.containsKey("audio.length.bytes")) {
                byteslength = ((Integer) properties.get("audio.length.bytes")).intValue();
            }
            if (properties.containsKey("duration")) {
                milliseconds = (int) (((Long) properties.get("duration")).longValue()) / 1000;
            } else {
                // Try to compute duration
                int bitspersample = AudioSystem.NOT_SPECIFIED;
                int channels = AudioSystem.NOT_SPECIFIED;
                float samplerate = AudioSystem.NOT_SPECIFIED;
                int framesize = AudioSystem.NOT_SPECIFIED;
                if (properties.containsKey("audio.samplesize.bits")) {
                    bitspersample = ((Integer) properties.get("audio.samplesize.bits")).intValue();
                }
                if (properties.containsKey("audio.channels")) {
                    channels = ((Integer) properties.get("audio.channels")).intValue();
                }
                if (properties.containsKey("audio.samplerate.hz")) {
                    samplerate = ((Float) properties.get("audio.samplerate.hz")).floatValue();
                }
                if (properties.containsKey("audio.framesize.bytes")) {
                    framesize = ((Integer) properties.get("audio.framesize.bytes")).intValue();
                }
                if (bitspersample > 0) {
                    milliseconds = (long) (1000.0f * byteslength / (samplerate * channels * (bitspersample / 8)));
                } else {
                    milliseconds = (long) (1000.0f * byteslength / (samplerate * framesize));
                }
            }
        }
        return milliseconds * 1000;
    }

    /**
     * Sets Gain value.
     * @param gain a value bitween -1.0 and +1.0
     * @throws PlayerException
     */
    public void setGain(float gain) throws PlayerException {
        if (gainControl != null) {
            double minGain = gainControl.getMinimum();
            double maxGain = gainControl.getMaximum();
            double ampGain = ((10.0f / 20.0f) * maxGain) - minGain;
            double cste = Math.log(10.0) / 20;
            double value = minGain + (1 / cste) * Math.log(1 + (Math.exp(cste * ampGain) - 1) * gain);
            gainControl.setValue((float) value);
            logger.info("{}", gainControl.toString());
        } else {
            throw new PlayerException("Gain control not supported");
        }
    }

    public float getGain() {
        float gain = 0.0f;
        if (gainControl != null) {
            gain = gainControl.getValue();
        }
        return gain;
    }

    /**
     * Sets Pan value.
     * @param pan a value bitween -1.0 and +1.0
     * @throws PlayerException
     */
    public void setPan(float pan) throws PlayerException {
        if (panControl != null) {
            panControl.setValue(pan);
            logger.info("{}", panControl.toString());
        } else {
            throw new PlayerException("Pan control not supported");
        }
    }

    public float getPan() {
        float pan = 0.0f;
        if (panControl != null) {
            pan = panControl.getValue();
        }
        return pan;
    }

    /**
     * Sets Mute value.
     * @param mute a boolean value
     * @throws PlayerException
     */
    public void setMuted(boolean mute) throws PlayerException {
        if (muteControl != null) {
            muteControl.setValue(mute);
            logger.info("{}", muteControl.toString());
        } else {
            throw new PlayerException("Mute control not supported");
        }
    }

    public boolean isMuted() {
        boolean muted = false;
        if (muteControl != null) {
            muted = muteControl.getValue();
        }
        return muted;
    }

    public long getPosition() {
        long pos = 0;
        if (sourceDataLine != null) {
            pos = sourceDataLine.getMicrosecondPosition();
        }
        return pos;
    }

    public int getState() {
        return state;
    }

    public DigitalSignalSynchronizer getDSS() {
        return dss;
    }

    @Override
    public Void call() throws PlayerException {
        logger.info("Decoding thread started");
        int nBytesRead = 0;
        int audioDataLength = READ_BUFFER_SIZE;
        ByteBuffer audioDataBuffer = ByteBuffer.allocate(audioDataLength);
        audioDataBuffer.order(ByteOrder.LITTLE_ENDIAN);
        lock.lock();
        try {
            while ((nBytesRead != -1) && (state != STOP) && (state != SEEK) && (state != AudioSystem.NOT_SPECIFIED)) {
                try {
                    if (state == PLAY) {
                        int toRead = audioDataLength;
                        int totalRead = 0;
                        while (toRead > 0 && (nBytesRead = audioInputStream.read(audioDataBuffer.array(), totalRead, toRead)) != -1) {
                            totalRead += nBytesRead;
                            toRead -= nBytesRead;
                        }
                        if (totalRead > 0) {
                            byte[] trimBuffer = audioDataBuffer.array();
                            if (totalRead < trimBuffer.length) {
                                trimBuffer = new byte[totalRead];
                                System.arraycopy(audioDataBuffer.array(), 0, trimBuffer, 0, totalRead);
                            }
                            sourceDataLine.write(trimBuffer, 0, totalRead);
                            dss.writeAudioData(trimBuffer, 0, totalRead);
                            for (PlaybackListener pl : listeners) {
                                PlaybackEvent pe = new PlaybackEvent(this, Playback.PLAYING, getPosition() - oldPosition, emptyMap);
                                if (audioInputStream instanceof PropertiesContainer) {
                                    // Pass audio parameters such as instant bitrate, ...
                                    pe.setProperties(((PropertiesContainer) audioInputStream).properties());
                                }
                                pl.playbackProgress(pe);
                            }
                        }
                    } else if (state == INIT || state == PAUSE) {
                        if (sourceDataLine != null && sourceDataLine.isRunning()) {
                            sourceDataLine.flush();
                            sourceDataLine.stop();
                        }
                        pauseCondition.awaitUninterruptibly();
                    }
                } catch (IOException ex) {
                    logger.error("Decoder Exception: ", ex);
                    state = STOP;
                    notifyEvent(Playback.STOPPED);
                    throw new PlayerException(ex);
                }
            }
            if (sourceDataLine != null) {
                sourceDataLine.flush();
                sourceDataLine.stop();
                sourceDataLine.close();
                sourceDataLine = null;
            }
            closeStream();
            if (nBytesRead == -1) {
                notifyEvent(Playback.EOM);
            }
        } finally {
            lock.unlock();
        }
        logger.info("Decoding thread completed");
        return null;
    }

    private void awaitTermination() {
        if (future != null && !future.isDone()) {
            try {
                future.get();
            } catch (InterruptedException ex) {
                logger.error(ex.getMessage(), ex);
            } catch (ExecutionException ex) {
                logger.error(ex.getMessage(), ex);
            } finally {
                // Harmless if task already completed
                future.cancel(true); // interrupt if running
            }
        }
    }

    public void play() throws PlayerException {
        lock.lock();
        try {
            switch (state) {
                case STOP:
                    initAudioInputStream();
                    initSourceDataLine();
                default:
                    if (sourceDataLine != null && !sourceDataLine.isRunning()) {
                        sourceDataLine.start();
                    }
                    state = PLAY;
                    pauseCondition.signal();
                    notifyEvent(Playback.PLAYING);
                    break;
            }
        } finally {
            lock.unlock();
        }
    }

    public void pause() {
        if (sourceDataLine != null) {
            if (state == PLAY) {
                state = PAUSE;
                notifyEvent(Playback.PAUSED);
            }
        }
    }

    public void stop() {
        if (state != STOP) {
            int oldState = state;
            state = STOP;
            if (oldState == INIT || oldState == PAUSE) {
                lock.lock();
                try {
                    pauseCondition.signal();
                } finally {
                    lock.unlock();
                }
            }
            awaitTermination();
            notifyEvent(Playback.STOPPED);
        }
    }

    public long seek(long bytes) throws PlayerException {
        long totalSkipped = 0;
        if (audioSource instanceof File) {
            int bytesLength = getByteLength();
            if ((bytesLength <= 0) || (bytes >= bytesLength)) {
                notifyEvent(Playback.EOM);
                return totalSkipped;
            }
            logger.info("Bytes to skip: {}", bytes);
            oldPosition = getPosition();
            int oldState = state;
            if (state == PLAY) {
                state = PAUSE;
            }
            lock.lock();
            try {
                notifyEvent(Playback.SEEKING);
                initAudioInputStream();
                if (audioInputStream != null) {
//                    long skipped = 0;
//                    while (totalSkipped < bytes) {
//                        skipped = audioInputStream.skip(bytes - totalSkipped);
//                        totalSkipped += skipped;
//                        if (skipped == 0) {
//                            break;
//                        }
//                    }
                    totalSkipped = audioInputStream.skip(bytes);
                    logger.info("Skipped bytes: {}/{}", totalSkipped, bytes);
                    if (totalSkipped == -1) {
                        throw new PlayerException("Seek not supported");
                    }
                    initSourceDataLine();
                }
            } catch (IOException ex) {
                throw new PlayerException(ex);
            } finally {
                lock.unlock();
            }
            if (oldState == PLAY) {
                play();
            }
        }
        return totalSkipped;
    }

    protected void closeStream() {
        if (audioInputStream != null) {
            try {
                audioInputStream.close();
                audioInputStream = null;
                logger.info("Stream closed");
            } catch (IOException ex) {
                logger.error("Cannot close stream", ex);
            }
        }
    }
}
goxr3plus commented 5 years ago

Here i made the repository with all the source code of XtrempPlayer => https://github.com/goxr3plus/XtremeMediaPlayer

image

HelgeStenstrom commented 5 years ago

I managed to download the repo and run the program.

What are the most interesting aspects of XtremeMediaPlayer? What lines of code should I look at first? In which way is performance boosted, and how is it boosted?

goxr3plus commented 5 years ago

They have their own BasicPLayer class which is almost the similar as our (StreamPLayer) except they are not using synchronized as we but they are using atomic block. Appart from that i will have a look at the way they are implementing new Visualizers as shown in the picture which we don't have in XR3Player and generally we can make a library out of them.

So actually I want us to replace the synchronized blocks based on how they did it. It's faster from what I have read in some articles long time ago. :)

goxr3plus commented 5 years ago

I think that they also can skip flac unlike our library...

HelgeStenstrom commented 5 years ago

I don't find BasicPLayer. Please be as specific as you can and tell me which file and method I should look closely at.

goxr3plus commented 5 years ago

@HelgeStenstrom Here you go :) => https://github.com/goxr3plus/XtremeMediaPlayer/blob/73fe33598dae59e04dac68610d84851490d5b4a4/src/xtrememp/player/audio/AudioPlayer.java#L1-L781 i made a mistake on the name , generally the package xtrememp/player/audio has the same classes as our StreamPlayer before you make the latest pull requests .

Have a look on their https://github.com/goxr3plus/XtremeMediaPlayer/blob/73fe33598dae59e04dac68610d84851490d5b4a4/src/xtrememp/player/audio/AudioPlayer.java#L1-L781 how they don't use synchronized block :)

HelgeStenstrom commented 5 years ago

Rather than line 1-781, I would focus on lines 603-656, the part between lock and unlock.

But it depends on what you mean by "better performance". Have you measured the performance, so you have numerical results, or is it just a general feeling you have?

Assuming there is a difference, do you think it is in the playback loop, or in the graphical rendering, or somewhere else? Graphics is outside the scope of java-stream-player, so measurements of performance must make sure that they measure the right thing.

HelgeStenstrom commented 5 years ago

https://github.com/goxr3plus/XtremeMediaPlayer/blob/73fe33598dae59e04dac68610d84851490d5b4a4/src/xtrememp/player/audio/AudioPlayer.java#L628

It looks nice to have a method with only one parameter here. But I don't think it improves performance.

goxr3plus commented 5 years ago

Why Locks

Why Locks , 2

goxr3plus commented 5 years ago

@HelgeStenstrom A huge performance eater is also the drawing of that Spectrum Analyzers in XR3Player , they are like ffff monsters which need to be optimized .

HelgeStenstrom commented 5 years ago

So leave XR3Player out when you compare performance of locking methods.

HelgeStenstrom commented 5 years ago

This looks interesting: https://plumbr.io/blog/locked-threads/improving-lock-performance-in-java