Skip to content

Commit cbc0225

Browse files
authored
Allow for multiple markers per track (#56)
* Init * Allow for adding and removing multiple markers * Remove marker overriding and fix state system * Fix comments and add more docs
1 parent df1355b commit cbc0225

File tree

9 files changed

+223
-27
lines changed

9 files changed

+223
-27
lines changed

Diff for: main/src/main/java/com/sedmelluq/discord/lavaplayer/track/AudioTrack.java

+17
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,27 @@ public interface AudioTrack extends AudioItem {
4444
void setPosition(long position);
4545

4646
/**
47+
* Set the track position marker. This will clear all existing markers.
48+
*
4749
* @param marker Track position marker to place
4850
*/
4951
void setMarker(TrackMarker marker);
5052

53+
/**
54+
* Adds a marker to the track.
55+
* Markers can be used to execute code when the track reaches a certain position.
56+
*
57+
* @param marker The marker to add.
58+
*/
59+
void addMarker(TrackMarker marker);
60+
61+
/**
62+
* Removes a marker from the track.
63+
*
64+
* @param marker The marker to remove.
65+
*/
66+
void removeMarker(TrackMarker marker);
67+
5168
/**
5269
* @return Duration of the track in milliseconds
5370
*/

Diff for: main/src/main/java/com/sedmelluq/discord/lavaplayer/track/BaseAudioTrack.java

+10
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ public void setMarker(TrackMarker marker) {
8787
getActiveExecutor().setMarker(marker);
8888
}
8989

90+
@Override
91+
public void addMarker(TrackMarker marker) {
92+
getActiveExecutor().addMarker(marker);
93+
}
94+
95+
@Override
96+
public void removeMarker(TrackMarker marker) {
97+
getActiveExecutor().removeMarker(marker);
98+
}
99+
90100
@Override
91101
public AudioFrame provide() {
92102
return getActiveExecutor().provide();

Diff for: main/src/main/java/com/sedmelluq/discord/lavaplayer/track/TrackMarkerTracker.java

+59-24
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,88 @@
11
package com.sedmelluq.discord.lavaplayer.track;
22

3-
import java.util.concurrent.atomic.AtomicReference;
3+
import java.util.Collections;
4+
import java.util.List;
5+
import java.util.concurrent.CopyOnWriteArrayList;
46

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

79
/**
810
* Tracks the state of a track position marker.
911
*/
1012
public class TrackMarkerTracker {
11-
private final AtomicReference<TrackMarker> current = new AtomicReference<>();
13+
private final List<TrackMarker> markerList = new CopyOnWriteArrayList<>();
1214

1315
/**
14-
* Set a new track position marker.
16+
* Set a new track position marker. This removes all previously set markers.
1517
*
1618
* @param marker Marker
1719
* @param currentTimecode Current timecode of the track when this marker is set
1820
*/
1921
public void set(TrackMarker marker, long currentTimecode) {
20-
TrackMarker previous = current.getAndSet(marker);
22+
if (marker == null) {
23+
trigger(REMOVED);
24+
} else {
25+
trigger(OVERWRITTEN);
2126

22-
if (previous != null) {
23-
previous.handler.handle(marker != null ? OVERWRITTEN : REMOVED);
27+
add(marker, currentTimecode);
2428
}
29+
}
2530

26-
if (marker != null && currentTimecode >= marker.timecode) {
27-
trigger(marker, LATE);
31+
public void add(TrackMarker marker, long currentTimecode) {
32+
if (marker != null) {
33+
if (currentTimecode >= marker.timecode) {
34+
marker.handler.handle(LATE);
35+
} else {
36+
markerList.add(marker);
37+
}
2838
}
2939
}
3040

41+
public void remove(TrackMarker marker) {
42+
trigger(marker, REMOVED);
43+
}
44+
3145
/**
32-
* Remove the current marker.
46+
* Removes the first marker in the list.
47+
*
48+
* @return The removed marker. Null if there are no markers.
3349
*
34-
* @return The removed marker.
50+
* @deprecated Use {@link #getMarkers()} and {@link #clear()} instead.
3551
*/
52+
@Deprecated
3653
public TrackMarker remove() {
37-
return current.getAndSet(null);
54+
if (markerList.isEmpty()) {
55+
return null;
56+
}
57+
58+
return markerList.remove(0);
59+
}
60+
61+
/**
62+
* @return The current unmodifiable list of timecode markers stored in this tracker.
63+
* @see #add(TrackMarker, long)
64+
* @see #remove(TrackMarker)
65+
* @see #clear()
66+
*/
67+
public List<TrackMarker> getMarkers() {
68+
return Collections.unmodifiableList(markerList);
69+
}
70+
71+
public void clear() {
72+
markerList.clear();
3873
}
3974

4075
/**
41-
* Trigger and remove the marker with the specified state.
76+
* Triggers and removes all markers with the specified state.
4277
*
4378
* @param state The state of the marker to pass to the handler.
4479
*/
4580
public void trigger(TrackMarkerHandler.MarkerState state) {
46-
TrackMarker marker = current.getAndSet(null);
47-
48-
if (marker != null) {
81+
for (TrackMarker marker : markerList) {
4982
marker.handler.handle(state);
5083
}
84+
85+
this.clear();
5186
}
5287

5388
/**
@@ -56,10 +91,10 @@ public void trigger(TrackMarkerHandler.MarkerState state) {
5691
* @param timecode Timecode which was reached by normal playback.
5792
*/
5893
public void checkPlaybackTimecode(long timecode) {
59-
TrackMarker marker = current.get();
60-
61-
if (marker != null && timecode >= marker.timecode) {
62-
trigger(marker, REACHED);
94+
for (TrackMarker marker : markerList) {
95+
if (marker != null && timecode >= marker.timecode) {
96+
trigger(marker, REACHED);
97+
}
6398
}
6499
}
65100

@@ -69,15 +104,15 @@ public void checkPlaybackTimecode(long timecode) {
69104
* @param timecode Timecode which was reached by seeking.
70105
*/
71106
public void checkSeekTimecode(long timecode) {
72-
TrackMarker marker = current.get();
73-
74-
if (marker != null && timecode >= marker.timecode) {
75-
trigger(marker, BYPASSED);
107+
for (TrackMarker marker : markerList) {
108+
if (marker != null && timecode >= marker.timecode) {
109+
trigger(marker, BYPASSED);
110+
}
76111
}
77112
}
78113

79114
private void trigger(TrackMarker marker, TrackMarkerHandler.MarkerState state) {
80-
if (current.compareAndSet(marker, null)) {
115+
if (markerList.remove(marker)) {
81116
marker.handler.handle(state);
82117
}
83118
}

Diff for: main/src/main/java/com/sedmelluq/discord/lavaplayer/track/playback/AudioTrackExecutor.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,27 @@ public interface AudioTrackExecutor extends AudioFrameProvider {
4343
AudioTrackState getState();
4444

4545
/**
46-
* Set track position marker.
46+
* Set the track position marker. This will clear all existing markers.
4747
*
4848
* @param marker Track position marker to set.
4949
*/
5050
void setMarker(TrackMarker marker);
5151

52+
/**
53+
* Adds a marker to the track.
54+
* Markers can be used to execute code when the track reaches a certain position.
55+
*
56+
* @param marker The marker to add.
57+
*/
58+
void addMarker(TrackMarker marker);
59+
60+
/**
61+
* Removes a marker from the track.
62+
*
63+
* @param marker The marker to remove.
64+
*/
65+
void removeMarker(TrackMarker marker);
66+
5267
/**
5368
* @return True if this track threw an exception before it provided any audio.
5469
*/

Diff for: main/src/main/java/com/sedmelluq/discord/lavaplayer/track/playback/LocalAudioTrackExecutor.java

+10
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,16 @@ public void setMarker(TrackMarker marker) {
238238
markerTracker.set(marker, getPosition());
239239
}
240240

241+
@Override
242+
public void addMarker(TrackMarker marker) {
243+
markerTracker.add(marker, getPosition());
244+
}
245+
246+
@Override
247+
public void removeMarker(TrackMarker marker) {
248+
markerTracker.remove(marker);
249+
}
250+
241251
@Override
242252
public boolean failedBeforeLoad() {
243253
return trackException != null && !frameBuffer.hasReceivedFrames();

Diff for: main/src/main/java/com/sedmelluq/discord/lavaplayer/track/playback/PrimordialAudioTrackExecutor.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ public void setMarker(TrackMarker marker) {
6363
markerTracker.set(marker, position);
6464
}
6565

66+
@Override
67+
public void addMarker(TrackMarker marker) {
68+
markerTracker.add(marker, getPosition());
69+
}
70+
71+
@Override
72+
public void removeMarker(TrackMarker marker) {
73+
markerTracker.remove(marker);
74+
}
75+
6676
@Override
6777
public boolean failedBeforeLoad() {
6878
return false;
@@ -100,6 +110,10 @@ public void applyStateToExecutor(AudioTrackExecutor executor) {
100110
executor.setPosition(position);
101111
}
102112

103-
executor.setMarker(markerTracker.remove());
113+
for (TrackMarker marker : markerTracker.getMarkers()) {
114+
executor.addMarker(marker);
115+
}
116+
117+
markerTracker.clear();
104118
}
105119
}

Diff for: settings.gradle.kts

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ include(
88
":extensions:youtube-rotator",
99
":extensions:format-xm",
1010
":natives",
11-
":natives-publish"
11+
":natives-publish",
12+
":testbot"
1213
)
1314

1415
// https://github.com/gradle/gradle/issues/19254

Diff for: testbot/build.gradle.kts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
plugins {
2+
java
3+
application
4+
}
5+
6+
dependencies {
7+
implementation(projects.main)
8+
implementation(libs.base64)
9+
implementation(libs.slf4j)
10+
runtimeOnly(libs.logback.classic)
11+
}
12+
13+
application {
14+
mainClass.set("com.sedmelluq.discord.lavaplayer.demo.LocalPlayerDemo")
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.sedmelluq.discord.lavaplayer.demo;
2+
3+
import com.sedmelluq.discord.lavaplayer.format.AudioDataFormat;
4+
import com.sedmelluq.discord.lavaplayer.format.AudioPlayerInputStream;
5+
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
6+
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
7+
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
8+
import com.sedmelluq.discord.lavaplayer.player.FunctionalResultHandler;
9+
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
10+
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
11+
import com.sedmelluq.discord.lavaplayer.track.TrackMarker;
12+
import com.sedmelluq.discord.lavaplayer.track.TrackMarkerHandler;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
import javax.sound.sampled.AudioInputStream;
17+
import javax.sound.sampled.AudioSystem;
18+
import javax.sound.sampled.DataLine;
19+
import javax.sound.sampled.LineUnavailableException;
20+
import javax.sound.sampled.SourceDataLine;
21+
import java.io.IOException;
22+
import java.util.concurrent.TimeUnit;
23+
24+
import static com.sedmelluq.discord.lavaplayer.format.StandardAudioDataFormats.COMMON_PCM_S16_BE;
25+
26+
public class LocalPlayerDemo {
27+
private static final Logger log = LoggerFactory.getLogger(LocalPlayerDemo.class);
28+
29+
private static final long CROSSFADE_BEGIN = TimeUnit.SECONDS.toMillis(5);
30+
private static final long CROSSFADE_PRELOAD = CROSSFADE_BEGIN + TimeUnit.SECONDS.toMillis(3);
31+
32+
public static void main(String[] args) throws LineUnavailableException, IOException {
33+
AudioPlayerManager manager = new DefaultAudioPlayerManager();
34+
AudioSourceManagers.registerRemoteSources(manager);
35+
manager.getConfiguration().setOutputFormat(COMMON_PCM_S16_BE);
36+
37+
AudioPlayer player = manager.createPlayer();
38+
39+
manager.loadItem("ytsearch: zmvgDMe5Wxo", new FunctionalResultHandler(null, playlist -> {
40+
AudioTrack audioTrack = playlist.getTracks().get(0);
41+
42+
applyMakers(audioTrack);
43+
44+
player.playTrack(audioTrack);
45+
}, null, null));
46+
47+
AudioDataFormat format = manager.getConfiguration().getOutputFormat();
48+
AudioInputStream stream = AudioPlayerInputStream.createStream(player, format, 10000L, false);
49+
SourceDataLine.Info info = new DataLine.Info(SourceDataLine.class, stream.getFormat());
50+
SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);
51+
52+
line.open(stream.getFormat());
53+
line.start();
54+
55+
byte[] buffer = new byte[COMMON_PCM_S16_BE.maximumChunkSize()];
56+
int chunkSize;
57+
58+
while ((chunkSize = stream.read(buffer)) >= 0) {
59+
line.write(buffer, 0, chunkSize);
60+
}
61+
}
62+
63+
private static void applyMakers(AudioTrack track) {
64+
final TrackMarkerHandler xfadeLoadHandler = (TrackMarkerHandler.MarkerState state) -> {
65+
if (state == TrackMarkerHandler.MarkerState.REACHED) {
66+
log.info("Fade begin handler has been reached");
67+
}
68+
};
69+
70+
final TrackMarkerHandler xfadeBufferHandler = (TrackMarkerHandler.MarkerState state) -> {
71+
if (state == TrackMarkerHandler.MarkerState.REACHED) {
72+
log.info("Buffer begin handler has been reached");
73+
}
74+
};
75+
76+
track.addMarker(new TrackMarker(track.getDuration() - CROSSFADE_BEGIN, xfadeLoadHandler));
77+
track.addMarker(new TrackMarker(track.getDuration() - CROSSFADE_PRELOAD, xfadeBufferHandler));
78+
}
79+
}

0 commit comments

Comments
 (0)