Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for multiple markers per track #56

Merged
merged 5 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,27 @@ public interface AudioTrack extends AudioItem {
void setPosition(long position);

/**
* Set the track position marker. This will clear all existing markers.
*
* @param marker Track position marker to place
*/
void setMarker(TrackMarker marker);

/**
* Adds a marker to the track.
* Markers can be used to execute code when the track reaches a certain position.
*
* @param marker The marker to add.
*/
void addMarker(TrackMarker marker);

/**
* Removes a marker from the track.
*
* @param marker The marker to remove.
*/
void removeMarker(TrackMarker marker);

/**
* @return Duration of the track in milliseconds
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ public void setMarker(TrackMarker marker) {
getActiveExecutor().setMarker(marker);
}

@Override
public void addMarker(TrackMarker marker) {
getActiveExecutor().addMarker(marker);
}

@Override
public void removeMarker(TrackMarker marker) {
getActiveExecutor().removeMarker(marker);
}

@Override
public AudioFrame provide() {
return getActiveExecutor().provide();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,53 +1,88 @@
package com.sedmelluq.discord.lavaplayer.track;

import java.util.concurrent.atomic.AtomicReference;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import static com.sedmelluq.discord.lavaplayer.track.TrackMarkerHandler.MarkerState.*;

/**
* Tracks the state of a track position marker.
*/
public class TrackMarkerTracker {
private final AtomicReference<TrackMarker> current = new AtomicReference<>();
private final List<TrackMarker> markerList = new CopyOnWriteArrayList<>();

/**
* Set a new track position marker.
* Set a new track position marker. This removes all previously set markers.
*
* @param marker Marker
* @param currentTimecode Current timecode of the track when this marker is set
*/
public void set(TrackMarker marker, long currentTimecode) {
TrackMarker previous = current.getAndSet(marker);
if (marker == null) {
trigger(REMOVED);
} else {
trigger(OVERWRITTEN);

if (previous != null) {
previous.handler.handle(marker != null ? OVERWRITTEN : REMOVED);
add(marker, currentTimecode);
}
}

if (marker != null && currentTimecode >= marker.timecode) {
trigger(marker, LATE);
public void add(TrackMarker marker, long currentTimecode) {
if (marker != null) {
if (currentTimecode >= marker.timecode) {
marker.handler.handle(LATE);
} else {
markerList.add(marker);
}
}
}

public void remove(TrackMarker marker) {
trigger(marker, REMOVED);
}

/**
* Remove the current marker.
* Removes the first marker in the list.
*
* @return The removed marker. Null if there are no markers.
*
* @return The removed marker.
* @deprecated Use {@link #getMarkers()} and {@link #clear()} instead.
*/
@Deprecated
public TrackMarker remove() {
return current.getAndSet(null);
if (markerList.isEmpty()) {
return null;
}

return markerList.remove(0);
}

/**
* @return The current unmodifiable list of timecode markers stored in this tracker.
* @see #add(TrackMarker, long)
* @see #remove(TrackMarker)
* @see #clear()
*/
public List<TrackMarker> getMarkers() {
return Collections.unmodifiableList(markerList);
}

public void clear() {
markerList.clear();
}

/**
* Trigger and remove the marker with the specified state.
* Triggers and removes all markers with the specified state.
*
* @param state The state of the marker to pass to the handler.
*/
public void trigger(TrackMarkerHandler.MarkerState state) {
duncte123 marked this conversation as resolved.
Show resolved Hide resolved
TrackMarker marker = current.getAndSet(null);

if (marker != null) {
for (TrackMarker marker : markerList) {
marker.handler.handle(state);
}

this.clear();
}

/**
Expand All @@ -56,10 +91,10 @@ public void trigger(TrackMarkerHandler.MarkerState state) {
* @param timecode Timecode which was reached by normal playback.
*/
public void checkPlaybackTimecode(long timecode) {
TrackMarker marker = current.get();

if (marker != null && timecode >= marker.timecode) {
trigger(marker, REACHED);
for (TrackMarker marker : markerList) {
if (marker != null && timecode >= marker.timecode) {
trigger(marker, REACHED);
}
}
}

Expand All @@ -69,15 +104,15 @@ public void checkPlaybackTimecode(long timecode) {
* @param timecode Timecode which was reached by seeking.
*/
public void checkSeekTimecode(long timecode) {
TrackMarker marker = current.get();

if (marker != null && timecode >= marker.timecode) {
trigger(marker, BYPASSED);
for (TrackMarker marker : markerList) {
if (marker != null && timecode >= marker.timecode) {
trigger(marker, BYPASSED);
}
}
}

private void trigger(TrackMarker marker, TrackMarkerHandler.MarkerState state) {
if (current.compareAndSet(marker, null)) {
if (markerList.remove(marker)) {
marker.handler.handle(state);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,27 @@ public interface AudioTrackExecutor extends AudioFrameProvider {
AudioTrackState getState();

/**
* Set track position marker.
* Set the track position marker. This will clear all existing markers.
*
* @param marker Track position marker to set.
*/
void setMarker(TrackMarker marker);

/**
* Adds a marker to the track.
* Markers can be used to execute code when the track reaches a certain position.
*
* @param marker The marker to add.
*/
void addMarker(TrackMarker marker);

/**
* Removes a marker from the track.
*
* @param marker The marker to remove.
*/
void removeMarker(TrackMarker marker);

/**
* @return True if this track threw an exception before it provided any audio.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,16 @@ public void setMarker(TrackMarker marker) {
markerTracker.set(marker, getPosition());
}

@Override
public void addMarker(TrackMarker marker) {
markerTracker.add(marker, getPosition());
}

@Override
public void removeMarker(TrackMarker marker) {
markerTracker.remove(marker);
}

@Override
public boolean failedBeforeLoad() {
return trackException != null && !frameBuffer.hasReceivedFrames();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ public void setMarker(TrackMarker marker) {
markerTracker.set(marker, position);
}

@Override
public void addMarker(TrackMarker marker) {
markerTracker.add(marker, getPosition());
}

@Override
public void removeMarker(TrackMarker marker) {
markerTracker.remove(marker);
}

@Override
public boolean failedBeforeLoad() {
return false;
Expand Down Expand Up @@ -100,6 +110,10 @@ public void applyStateToExecutor(AudioTrackExecutor executor) {
executor.setPosition(position);
}

executor.setMarker(markerTracker.remove());
for (TrackMarker marker : markerTracker.getMarkers()) {
executor.addMarker(marker);
}

markerTracker.clear();
}
}
3 changes: 2 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ include(
":extensions:youtube-rotator",
":extensions:format-xm",
":natives",
":natives-publish"
":natives-publish",
":testbot"
)

// https://github.com/gradle/gradle/issues/19254
Expand Down
15 changes: 15 additions & 0 deletions testbot/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
plugins {
java
application
}

dependencies {
implementation(projects.main)
implementation(libs.base64)
implementation(libs.slf4j)
runtimeOnly(libs.logback.classic)
}

application {
mainClass.set("com.sedmelluq.discord.lavaplayer.demo.LocalPlayerDemo")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.sedmelluq.discord.lavaplayer.demo;

import com.sedmelluq.discord.lavaplayer.format.AudioDataFormat;
import com.sedmelluq.discord.lavaplayer.format.AudioPlayerInputStream;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.player.FunctionalResultHandler;
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.TrackMarker;
import com.sedmelluq.discord.lavaplayer.track.TrackMarkerHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

import static com.sedmelluq.discord.lavaplayer.format.StandardAudioDataFormats.COMMON_PCM_S16_BE;

public class LocalPlayerDemo {
private static final Logger log = LoggerFactory.getLogger(LocalPlayerDemo.class);

private static final long CROSSFADE_BEGIN = TimeUnit.SECONDS.toMillis(5);
private static final long CROSSFADE_PRELOAD = CROSSFADE_BEGIN + TimeUnit.SECONDS.toMillis(3);

public static void main(String[] args) throws LineUnavailableException, IOException {
AudioPlayerManager manager = new DefaultAudioPlayerManager();
AudioSourceManagers.registerRemoteSources(manager);
manager.getConfiguration().setOutputFormat(COMMON_PCM_S16_BE);

AudioPlayer player = manager.createPlayer();

manager.loadItem("ytsearch: zmvgDMe5Wxo", new FunctionalResultHandler(null, playlist -> {
AudioTrack audioTrack = playlist.getTracks().get(0);

applyMakers(audioTrack);

player.playTrack(audioTrack);
}, null, null));

AudioDataFormat format = manager.getConfiguration().getOutputFormat();
AudioInputStream stream = AudioPlayerInputStream.createStream(player, format, 10000L, false);
SourceDataLine.Info info = new DataLine.Info(SourceDataLine.class, stream.getFormat());
SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);

line.open(stream.getFormat());
line.start();

byte[] buffer = new byte[COMMON_PCM_S16_BE.maximumChunkSize()];
int chunkSize;

while ((chunkSize = stream.read(buffer)) >= 0) {
line.write(buffer, 0, chunkSize);
}
}

private static void applyMakers(AudioTrack track) {
final TrackMarkerHandler xfadeLoadHandler = (TrackMarkerHandler.MarkerState state) -> {
if (state == TrackMarkerHandler.MarkerState.REACHED) {
log.info("Fade begin handler has been reached");
}
};

final TrackMarkerHandler xfadeBufferHandler = (TrackMarkerHandler.MarkerState state) -> {
if (state == TrackMarkerHandler.MarkerState.REACHED) {
log.info("Buffer begin handler has been reached");
}
};

track.addMarker(new TrackMarker(track.getDuration() - CROSSFADE_BEGIN, xfadeLoadHandler));
track.addMarker(new TrackMarker(track.getDuration() - CROSSFADE_PRELOAD, xfadeBufferHandler));
}
}