From d2907e165c585ae0e956b5796f4a8c4f511f664b Mon Sep 17 00:00:00 2001 From: devoxin Date: Fri, 3 Jul 2026 15:58:53 +0100 Subject: [PATCH] Implement stream serial tracking for Ogg files --- .../container/ogg/OggPacketInputStream.java | 49 ++++++++++++++----- .../container/ogg/OggPageScanner.java | 10 ++-- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/ogg/OggPacketInputStream.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/ogg/OggPacketInputStream.java index f3b8228a..2280c587 100644 --- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/ogg/OggPacketInputStream.java +++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/ogg/OggPacketInputStream.java @@ -26,6 +26,7 @@ public class OggPacketInputStream extends InputStream { private List seekPoints; private OggPageHeader pageHeader; + private Integer trackSerial; private int bytesLeftInPacket; private boolean packetContinues; private int nextPacketSegmentIndex; @@ -60,6 +61,7 @@ public boolean startNewTrack() { } pageHeader = null; + trackSerial = null; state = State.PACKET_BOUNDARY; return true; } @@ -162,16 +164,40 @@ private boolean loadNextPage() throws IOException { return false; } - if (!readPageHeader()) { - if (packetContinues) { - throw new IllegalStateException("Stream ended in the middle of a packet."); + while (true) { + if (!readPageHeader()) { + if (packetContinues) { + throw new IllegalStateException("Stream ended in the middle of a packet."); + } + return false; } - return false; + + if (trackSerial == null) { + // track the first stream we detect. Ideally, this is an audio stream. + // If an Ogg file starts with, i.e. a Theora video stream, it won't be detected anyway + // This ensures that we're only tracking pages that belong to the initially detected audio stream + // so we aren't mixing in packets from other streams. + trackSerial = pageHeader.streamIdentifier; + } else if (pageHeader.streamIdentifier != trackSerial) { + // skip segments not belonging to the stream we're tracking + inputStream.skipFully(getTotalPageSegmentLength()); + continue; + } + + nextPacketSegmentIndex = 0; + state = State.PACKET_READ; + return true; } + } - nextPacketSegmentIndex = 0; - state = State.PACKET_READ; - return true; + private long getTotalPageSegmentLength() { + long segmentLength = 0; + + for (int i = 0; i < pageHeader.segmentCount; i++) { + segmentLength += segmentSizes[i]; + } + + return segmentLength; } /** @@ -330,7 +356,8 @@ public List createSeekTable(int sampleRate) throws IOException { byte[] data = new byte[(int) inputStream.getContentLength()]; int dataLength = StreamTools.readUntilEnd(inputStream, data, 0, data.length); - List seekPoints = new OggPageScanner(absoluteOffset, data, dataLength).createSeekTable(sampleRate); + List seekPoints = new OggPageScanner(absoluteOffset, data, dataLength) + .createSeekTable(sampleRate, pageHeader.streamIdentifier); inputStream.seek(savedPosition); return seekPoints; @@ -373,7 +400,7 @@ private OggStreamSizeInfo scanForSizeInfo(int tailLength, int sampleRate) throws int dataLength = StreamTools.readUntilEnd(inputStream, data, 0, data.length); return new OggPageScanner(absoluteOffset, data, dataLength) - .scanForSizeInfo(pageHeader.byteStreamPosition, sampleRate); + .scanForSizeInfo(pageHeader.byteStreamPosition, sampleRate, pageHeader.streamIdentifier); } /** @@ -393,11 +420,9 @@ private boolean continuePacket() throws IOException { // Load more segments for this packet from the next page. if (!loadNextNonEmptyPage()) { throw new IllegalStateException("Track or stream end reached within an incomplete packet."); - } else if (!initialisePacket()) { - return false; } - return true; + return initialisePacket(); } private enum State { diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/ogg/OggPageScanner.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/ogg/OggPageScanner.java index 1326a925..b3740202 100644 --- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/ogg/OggPageScanner.java +++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/ogg/OggPageScanner.java @@ -19,6 +19,7 @@ public class OggPageScanner { private int pageSize; private long byteStreamPosition; private int pageSequence; + private int streamSerial; /** * @param absoluteOffset Current position of the stream in bytes. @@ -37,7 +38,7 @@ public OggPageScanner(long absoluteOffset, byte[] data, int dataLength) { * @return If the data contains the header of the last page in the OGG stream, then stream size information, * otherwise null. */ - public OggStreamSizeInfo scanForSizeInfo(long firstPageOffset, int sampleRate) { + public OggStreamSizeInfo scanForSizeInfo(long firstPageOffset, int sampleRate, int targetSerial) { ByteBuffer buffer = ByteBuffer.wrap(data, 0, dataLength); int head = buffer.getInt(0); @@ -47,7 +48,7 @@ public OggStreamSizeInfo scanForSizeInfo(long firstPageOffset, int sampleRate) { if (attemptReadHeader(buffer)) { do { - if ((flags & OggPageHeader.FLAG_LAST_PAGE) != 0) { + if (streamSerial == targetSerial && (flags & OggPageHeader.FLAG_LAST_PAGE) != 0) { return new OggStreamSizeInfo((byteStreamPosition - firstPageOffset) + pageSize, Long.reverseBytes(reversedPosition), firstPageOffset, byteStreamPosition, sampleRate); } @@ -68,7 +69,7 @@ public OggStreamSizeInfo scanForSizeInfo(long firstPageOffset, int sampleRate) { * @param sampleRate Sample rate of the track in the stream. * @return A list of OggSeekPoint objects representing the seek points in the stream. */ - public List createSeekTable(int sampleRate) { + public List createSeekTable(int sampleRate, int targetSerial) { List seekPoints = new ArrayList<>(); ByteBuffer buffer = ByteBuffer.wrap(data, 0, dataLength); @@ -78,7 +79,7 @@ public List createSeekTable(int sampleRate) { if (head == OGG_PAGE_HEADER_INT) { buffer.position(i); - if (attemptReadHeader(buffer)) { + if (attemptReadHeader(buffer) && streamSerial == targetSerial) { long position = byteStreamPosition; long granulePosition = Long.reverseBytes(reversedPosition); long timecode = granulePosition / (sampleRate / 1000); @@ -124,6 +125,7 @@ private boolean attemptReadHeader(ByteBuffer buffer) { flags = buffer.get(start + 5) & 0xFF; reversedPosition = buffer.getLong(start + 6); + streamSerial = Integer.reverseBytes(buffer.getInt(start + 14)); byteStreamPosition = absoluteOffset + start; pageSize = minimumCapacity;