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

com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayer Maven / Gradle / Ivy

The newest version!
package com.sedmelluq.discord.lavaplayer.player;

import com.sedmelluq.discord.lavaplayer.filter.PcmFilterFactory;
import com.sedmelluq.discord.lavaplayer.player.event.*;
import com.sedmelluq.discord.lavaplayer.tools.ExceptionTools;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;
import com.sedmelluq.discord.lavaplayer.track.InternalAudioTrack;
import com.sedmelluq.discord.lavaplayer.track.TrackStateListener;
import com.sedmelluq.discord.lavaplayer.track.playback.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason.*;

/**
 * An audio player that is capable of playing audio tracks and provides audio frames from the currently playing track.
 */
public class DefaultAudioPlayer implements AudioPlayer, TrackStateListener {
    private static final Logger log = LoggerFactory.getLogger(AudioPlayer.class);

    private volatile InternalAudioTrack activeTrack;
    private volatile long lastRequestTime;
    private volatile long lastReceiveTime;
    private volatile boolean stuckEventSent;
    private volatile InternalAudioTrack shadowTrack;
    private final AtomicBoolean paused;
    private final DefaultAudioPlayerManager manager;
    private final List listeners;
    private final Object trackSwitchLock;
    private final AudioPlayerOptions options;

    /**
     * @param manager Audio player manager which this player is attached to
     */
    public DefaultAudioPlayer(DefaultAudioPlayerManager manager) {
        this.manager = manager;
        activeTrack = null;
        paused = new AtomicBoolean();
        listeners = new ArrayList<>();
        trackSwitchLock = new Object();
        options = new AudioPlayerOptions();
    }

    /**
     * @return Currently playing track
     */
    public AudioTrack getPlayingTrack() {
        return activeTrack;
    }

    /**
     * @param track The track to start playing
     */
    public void playTrack(AudioTrack track) {
        startTrack(track, false);
    }

    /**
     * @param track       The track to start playing, passing null will stop the current track and return false
     * @param noInterrupt Whether to only start if nothing else is playing
     * @return True if the track was started
     */
    public boolean startTrack(AudioTrack track, boolean noInterrupt) {
        InternalAudioTrack newTrack = (InternalAudioTrack) track;
        InternalAudioTrack previousTrack;

        synchronized (trackSwitchLock) {
            previousTrack = activeTrack;

            if (noInterrupt && previousTrack != null) {
                return false;
            }

            activeTrack = newTrack;
            lastRequestTime = System.currentTimeMillis();
            lastReceiveTime = System.nanoTime();
            stuckEventSent = false;

            if (previousTrack != null) {
                previousTrack.stop();
                dispatchEvent(new TrackEndEvent(this, previousTrack, newTrack == null ? STOPPED : REPLACED));

                shadowTrack = previousTrack;
            }
        }

        if (newTrack == null) {
            shadowTrack = null;
            return false;
        }

        dispatchEvent(new TrackStartEvent(this, newTrack));

        manager.executeTrack(this, newTrack, manager.getConfiguration(), options);
        return true;
    }

    /**
     * Stop currently playing track.
     */
    public void stopTrack() {
        stopWithReason(STOPPED);
    }

    private void stopWithReason(AudioTrackEndReason reason) {
        shadowTrack = null;

        synchronized (trackSwitchLock) {
            InternalAudioTrack previousTrack = activeTrack;
            activeTrack = null;

            if (previousTrack != null) {
                previousTrack.stop();
                dispatchEvent(new TrackEndEvent(this, previousTrack, reason));
            }
        }
    }

    private AudioFrame provideShadowFrame() {
        InternalAudioTrack shadow = shadowTrack;
        AudioFrame frame = null;

        if (shadow != null) {
            frame = shadow.provide();

            if (frame != null && frame.isTerminator()) {
                shadowTrack = null;
                frame = null;
            }
        }

        return frame;
    }

    private boolean provideShadowFrame(MutableAudioFrame targetFrame) {
        InternalAudioTrack shadow = shadowTrack;

        if (shadow != null && shadow.provide(targetFrame)) {
            if (targetFrame.isTerminator()) {
                shadowTrack = null;
                return false;
            }

            return true;
        }

        return false;
    }

    @Override
    public AudioFrame provide() {
        return AudioFrameProviderTools.delegateToTimedProvide(this);
    }

    @Override
    public AudioFrame provide(long timeout, TimeUnit unit) throws TimeoutException, InterruptedException {
        InternalAudioTrack track;

        lastRequestTime = System.currentTimeMillis();

        if (timeout == 0 && paused.get()) {
            return null;
        }

        while ((track = activeTrack) != null) {
            AudioFrame frame = timeout > 0 ? track.provide(timeout, unit) : track.provide();

            if (frame != null) {
                lastReceiveTime = System.nanoTime();
                shadowTrack = null;

                if (frame.isTerminator()) {
                    handleTerminator(track);
                    continue;
                }
            } else if (timeout == 0) {
                checkStuck(track);

                frame = provideShadowFrame();
            }

            return frame;
        }

        return null;
    }

    @Override
    public boolean provide(MutableAudioFrame targetFrame) {
        try {
            return provide(targetFrame, 0, TimeUnit.MILLISECONDS);
        } catch (TimeoutException | InterruptedException e) {
            ExceptionTools.keepInterrupted(e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean provide(MutableAudioFrame targetFrame, long timeout, TimeUnit unit)
        throws TimeoutException, InterruptedException {

        InternalAudioTrack track;

        lastRequestTime = System.currentTimeMillis();

        if (timeout == 0 && paused.get()) {
            return false;
        }

        while ((track = activeTrack) != null) {
            if (timeout > 0 ? track.provide(targetFrame, timeout, unit) : track.provide(targetFrame)) {
                lastReceiveTime = System.nanoTime();
                shadowTrack = null;

                if (targetFrame.isTerminator()) {
                    handleTerminator(track);
                    continue;
                }

                return true;
            } else if (timeout == 0) {
                checkStuck(track);
                return provideShadowFrame(targetFrame);
            } else {
                return false;
            }
        }

        return false;
    }

    private void handleTerminator(InternalAudioTrack track) {
        synchronized (trackSwitchLock) {
            if (activeTrack == track) {
                try {
                    activeTrack = null;
                    dispatchEvent(new TrackEndEvent(this, track, track.getActiveExecutor().failedBeforeLoad() ? LOAD_FAILED : FINISHED));
                } finally {
                    track.stop();
                }
            }
        }
    }

    private void checkStuck(AudioTrack track) {
        if (!stuckEventSent && System.nanoTime() - lastReceiveTime > manager.getTrackStuckThresholdNanos()) {
            stuckEventSent = true;

            StackTraceElement[] stackTrace = getStackTrace(track);
            long threshold = TimeUnit.NANOSECONDS.toMillis(manager.getTrackStuckThresholdNanos());

            dispatchEvent(new TrackStuckEvent(this, track, threshold, stackTrace));
        }
    }

    private StackTraceElement[] getStackTrace(AudioTrack track) {
        if (track instanceof InternalAudioTrack) {
            AudioTrackExecutor executor = ((InternalAudioTrack) track).getActiveExecutor();

            if (executor instanceof LocalAudioTrackExecutor) {
                return ((LocalAudioTrackExecutor) executor).getStackTrace();
            }
        }

        return null;
    }

    public int getVolume() {
        return options.volumeLevel.get();
    }

    public void setVolume(int volume) {
        options.volumeLevel.set(Math.min(1000, Math.max(0, volume)));
    }

    public void setFilterFactory(PcmFilterFactory factory) {
        options.filterFactory.set(factory);
    }

    public void setFrameBufferDuration(Integer duration) {
        if (duration != null) {
            duration = Math.max(200, duration);
        }

        options.frameBufferDuration.set(duration);
    }

    /**
     * @return Whether the player is paused
     */
    public boolean isPaused() {
        return paused.get();
    }

    /**
     * @param value True to pause, false to resume
     */
    public void setPaused(boolean value) {
        if (paused.compareAndSet(!value, value)) {
            if (value) {
                dispatchEvent(new PlayerPauseEvent(this));
            } else {
                dispatchEvent(new PlayerResumeEvent(this));
                lastReceiveTime = System.nanoTime();
            }
        }
    }

    /**
     * Destroy the player and stop playing track.
     */
    public void destroy() {
        stopTrack();
    }

    /**
     * Add a listener to events from this player.
     *
     * @param listener New listener
     */
    public void addListener(AudioEventListener listener) {
        synchronized (trackSwitchLock) {
            listeners.add(listener);
        }
    }

    /**
     * Remove an attached listener using identity comparison.
     *
     * @param listener The listener to remove
     */
    public void removeListener(AudioEventListener listener) {
        synchronized (trackSwitchLock) {
            for (Iterator iterator = listeners.iterator(); iterator.hasNext(); ) {
                if (iterator.next() == listener) {
                    iterator.remove();
                }
            }
        }
    }

    private void dispatchEvent(AudioEvent event) {
        log.debug("Firing an event with class {}", event.getClass().getSimpleName());

        synchronized (trackSwitchLock) {
            for (AudioEventListener listener : listeners) {
                try {
                    listener.onEvent(event);
                } catch (Exception e) {
                    log.error("Handler of event {} threw an exception.", event, e);
                }
            }
        }
    }

    @Override
    public void onTrackException(AudioTrack track, FriendlyException exception) {
        dispatchEvent(new TrackExceptionEvent(this, track, exception));
    }

    @Override
    public void onTrackStuck(AudioTrack track, long thresholdMs) {
        dispatchEvent(new TrackStuckEvent(this, track, thresholdMs, null));
    }

    /**
     * Check if the player should be "cleaned up" - stopped due to nothing using it, with the given threshold.
     *
     * @param threshold Threshold in milliseconds to use
     */
    public void checkCleanup(long threshold) {
        AudioTrack track = getPlayingTrack();
        if (track != null && System.currentTimeMillis() - lastRequestTime >= threshold) {
            log.debug("Triggering cleanup on an audio player playing track {}", track);

            stopWithReason(CLEANUP);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy