Skip to content

Commit

Permalink
Allow for multiple markers per track (#56)
Browse files Browse the repository at this point in the history
* Init

* Allow for adding and removing multiple markers

* Remove marker overriding and fix state system

* Fix comments and add more docs
  • Loading branch information
duncte123 authored Dec 28, 2023
1 parent df1355b commit cbc0225
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 27 deletions.
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) {
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));
}
}

0 comments on commit cbc0225

Please sign in to comment.