exoplayer: sabr: update adapters

This commit is contained in:
Yuriy Liskov
2026-01-30 14:41:11 +02:00
parent 03d0e37599
commit 3100e3428b
6 changed files with 602 additions and 7 deletions

View File

@@ -25,8 +25,8 @@ import com.google.android.exoplayer2.source.sabr.manifest.AdaptationSet;
import com.google.android.exoplayer2.source.sabr.manifest.RangedUri;
import com.google.android.exoplayer2.source.sabr.manifest.Representation;
import com.google.android.exoplayer2.source.sabr.manifest.SabrManifest;
import com.google.android.exoplayer2.source.sabr.parser.adapter.SabrFragmentedMp4Adapter;
import com.google.android.exoplayer2.source.sabr.parser.adapter.SabrMatroskaAdapter;
import com.google.android.exoplayer2.source.sabr.parser.adapter.SabrFragmentedMp4Adapter2;
import com.google.android.exoplayer2.source.sabr.parser.adapter.SabrMatroskaAdapter2;
import com.google.android.exoplayer2.source.sabr.parser.core.SabrStream;
import com.google.android.exoplayer2.source.sabr.parser.models.AudioSelector;
import com.google.android.exoplayer2.source.sabr.parser.models.CaptionSelector;
@@ -964,14 +964,14 @@ public class DefaultSabrChunkSource implements SabrChunkSource {
if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
extractor = new RawCcExtractor(representation.format);
} else if (mimeTypeIsWebm(containerMimeType)) {
extractor = new SabrMatroskaAdapter(SabrMatroskaAdapter.FLAG_DISABLE_SEEK_FOR_CUES, sabrStream);
extractor = new SabrMatroskaAdapter2(SabrMatroskaAdapter2.FLAG_DISABLE_SEEK_FOR_CUES, sabrStream);
} else {
int flags = 0;
if (enableEventMessageTrack) {
flags |= SabrFragmentedMp4Adapter.FLAG_ENABLE_EMSG_TRACK;
flags |= SabrFragmentedMp4Adapter2.FLAG_ENABLE_EMSG_TRACK;
}
extractor =
new SabrFragmentedMp4Adapter(
new SabrFragmentedMp4Adapter2(
flags, null, null, null, closedCaptionFormats, playerEmsgTrackOutput, sabrStream);
}

View File

@@ -0,0 +1,86 @@
package com.google.android.exoplayer2.source.sabr.parser.adapter;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer2.extractor.mp4.Track;
import com.google.android.exoplayer2.source.sabr.parser.core.SabrStream;
import com.google.android.exoplayer2.source.sabr.parser.misc.SabrExtractorInput;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.io.IOException;
import java.util.List;
public class SabrFragmentedMp4Adapter2 extends FragmentedMp4Extractor {
private final SabrExtractorInput extractorInput;
public SabrFragmentedMp4Adapter2(SabrStream sabrStream) {
this.extractorInput = new SabrExtractorInput(sabrStream);
}
public SabrFragmentedMp4Adapter2(int flags, SabrStream sabrStream) {
super(flags);
this.extractorInput = new SabrExtractorInput(sabrStream);
}
public SabrFragmentedMp4Adapter2(
int flags,
@Nullable TimestampAdjuster timestampAdjuster,
SabrStream sabrStream) {
super(flags, timestampAdjuster);
this.extractorInput = new SabrExtractorInput(sabrStream);
}
public SabrFragmentedMp4Adapter2(
int flags,
@Nullable TimestampAdjuster timestampAdjuster,
@Nullable Track sideloadedTrack,
@Nullable DrmInitData sideloadedDrmInitData,
SabrStream sabrStream) {
super(flags, timestampAdjuster, sideloadedTrack, sideloadedDrmInitData);
this.extractorInput = new SabrExtractorInput(sabrStream);
}
public SabrFragmentedMp4Adapter2(
int flags,
@Nullable TimestampAdjuster timestampAdjuster,
@Nullable Track sideloadedTrack,
@Nullable DrmInitData sideloadedDrmInitData,
List<Format> closedCaptionFormats,
SabrStream sabrStream) {
super(flags, timestampAdjuster, sideloadedTrack, sideloadedDrmInitData, closedCaptionFormats);
this.extractorInput = new SabrExtractorInput(sabrStream);
}
public SabrFragmentedMp4Adapter2(
int flags,
@Nullable TimestampAdjuster timestampAdjuster,
@Nullable Track sideloadedTrack,
@Nullable DrmInitData sideloadedDrmInitData,
List<Format> closedCaptionFormats,
@Nullable TrackOutput additionalEmsgTrackOutput,
SabrStream sabrStream) {
super(flags, timestampAdjuster, sideloadedTrack, sideloadedDrmInitData, closedCaptionFormats, additionalEmsgTrackOutput);
this.extractorInput = new SabrExtractorInput(sabrStream);
}
@Override
public int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException {
int result;
try {
extractorInput.init(input);
result = super.read(extractorInput, seekPosition);
} finally {
extractorInput.dispose();
}
return result;
}
}

View File

@@ -0,0 +1,38 @@
package com.google.android.exoplayer2.source.sabr.parser.adapter;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.source.sabr.parser.core.SabrStream;
import com.google.android.exoplayer2.source.sabr.parser.misc.SabrExtractorInput;
import java.io.IOException;
public class SabrMatroskaAdapter2 extends MatroskaExtractor {
private static final String TAG = SabrMatroskaAdapter2.class.getSimpleName();
private final SabrExtractorInput extractorInput;
public SabrMatroskaAdapter2(SabrStream sabrStream) {
this.extractorInput = new SabrExtractorInput(sabrStream);
}
public SabrMatroskaAdapter2(int flags, SabrStream sabrStream) {
super(flags);
this.extractorInput = new SabrExtractorInput(sabrStream);
}
@Override
public int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException {
int result;
try {
extractorInput.init(input);
result = super.read(extractorInput, seekPosition);
} finally {
extractorInput.dispose();
}
return result;
}
}

View File

@@ -0,0 +1,459 @@
package com.google.android.exoplayer2.source.sabr.parser.misc;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.source.sabr.parser.core.SabrStream;
import com.google.android.exoplayer2.source.sabr.parser.parts.MediaSegmentDataSabrPart;
import com.google.android.exoplayer2.source.sabr.parser.parts.SabrPart;
import com.liskovsoft.sharedutils.mylogger.Log;
import java.io.EOFException;
import java.io.IOException;
public final class SabrExtractorInput implements ExtractorInput {
private static final String TAG = SabrExtractorInput.class.getSimpleName();
private final SabrStream sabrStream;
private ExtractorInput input;
private long position;
private int remaining;
private MediaSegmentDataSabrPart data;
public SabrExtractorInput(SabrStream sabrStream) {
this.sabrStream = sabrStream;
}
/** Should be called before passing the extractor to a handler */
public void init(ExtractorInput input) {
this.input = input;
this.position = input.getPosition();
this.remaining = C.LENGTH_UNSET;
}
public void dispose() {
input = null;
data = null;
position = C.POSITION_UNSET;
remaining = C.LENGTH_UNSET;
}
@Override
public int read(byte[] buffer, int offset, int length) throws IOException, InterruptedException {
if (remaining == 0) {
return C.RESULT_END_OF_INPUT;
}
if (remaining != C.LENGTH_UNSET) {
length = Math.min(length, remaining);
}
int read = readInt(buffer, offset, length);
if (remaining != C.LENGTH_UNSET && read > 0) {
remaining -= read;
}
return read;
}
@Override
public void readFully(byte[] buffer, int offset, int length) throws IOException, InterruptedException {
boolean exceeded = remaining != C.LENGTH_UNSET && length > remaining;
if (remaining != C.LENGTH_UNSET) {
length = Math.min(length, remaining);
}
readFullyInt(buffer, offset, length);
if (remaining != C.LENGTH_UNSET) {
remaining -= length;
}
if (exceeded) {
throw new EOFException("LimitedExtractorInput: chunk boundary exceeded");
}
}
@Override
public boolean readFully(
byte[] buffer,
int offset,
int length,
boolean allowEndOfInput) throws IOException, InterruptedException {
boolean exceeded = remaining != C.LENGTH_UNSET && length > remaining;
if (remaining != C.LENGTH_UNSET) {
length = Math.min(length, remaining);
}
boolean ok = readFullyInt(buffer, offset, length, allowEndOfInput);
if (remaining != C.LENGTH_UNSET) {
remaining -= length;
}
if (exceeded) {
if (allowEndOfInput) {
ok = false;
} else {
throw new EOFException("LimitedExtractorInput: chunk boundary exceeded");
}
}
return ok;
}
@Override
public int skip(int length) throws IOException, InterruptedException {
if (remaining != C.LENGTH_UNSET) {
length = Math.min(length, remaining);
}
int skipped = skipInt(length);
if (remaining != C.LENGTH_UNSET && skipped > 0) {
remaining -= skipped;
}
return skipped;
}
@Override
public void skipFully(int length) throws IOException, InterruptedException {
boolean exceeded = remaining != C.LENGTH_UNSET && length > remaining;
if (remaining != C.LENGTH_UNSET) {
length = Math.min(length, remaining);
}
skipFullyInt(length);
if (remaining != C.LENGTH_UNSET) {
remaining -= length;
}
if (exceeded) {
throw new EOFException("LimitedExtractorInput: chunk boundary exceeded");
}
}
@Override
public long getPosition() {
return getPositionInt();
}
@Override
public long getLength() {
return remaining;
}
@Override
public <E extends Throwable> void setRetryPosition(long p, E e) throws E {
setRetryPositionInt(p, e);
}
@Override
public boolean skipFully(
int length,
boolean allowEndOfInput) throws IOException, InterruptedException {
boolean exceeded = remaining != C.LENGTH_UNSET && length > remaining;
if (remaining != C.LENGTH_UNSET) {
length = Math.min(length, remaining);
}
boolean ok = skipFullyInt(length, allowEndOfInput);
if (remaining != C.LENGTH_UNSET) {
remaining -= length;
}
if (exceeded) {
if (allowEndOfInput) {
ok = false;
} else {
throw new EOFException("LimitedExtractorInput: chunk boundary exceeded");
}
}
return ok;
}
@Override
public boolean peekFully(
byte[] target,
int offset,
int length,
boolean allowEndOfInput) throws IOException, InterruptedException {
boolean exceeded = remaining != C.LENGTH_UNSET && length > remaining;
if (remaining != C.LENGTH_UNSET) {
length = Math.min(length, remaining);
}
boolean ok = peekFullyInt(target, offset, length, allowEndOfInput);
if (exceeded) {
if (allowEndOfInput) {
ok = false;
} else {
throw new EOFException("LimitedExtractorInput: chunk boundary exceeded");
}
}
return ok;
}
@Override
public void peekFully(
byte[] target,
int offset,
int length) throws IOException, InterruptedException {
boolean exceeded = remaining != C.LENGTH_UNSET && length > remaining;
if (remaining != C.LENGTH_UNSET) {
length = Math.min(length, remaining);
}
peekFullyInt(target, offset, length);
if (exceeded) {
throw new EOFException("LimitedExtractorInput: chunk boundary exceeded");
}
}
@Override
public boolean advancePeekPosition(
int length,
boolean allowEndOfInput) throws IOException, InterruptedException {
if (length > remaining) {
if (allowEndOfInput) {
return false;
}
throw new EOFException("LimitedExtractorInput: chunk boundary exceeded");
}
return advancePeekPositionInt(length, allowEndOfInput);
}
@Override
public void advancePeekPosition(int length)
throws IOException, InterruptedException {
if (length > remaining) {
throw new EOFException("LimitedExtractorInput: chunk boundary exceeded");
}
advancePeekPositionInt(length);
}
@Override
public void resetPeekPosition() {
resetPeekPositionInt();
}
@Override
public long getPeekPosition() {
return getPeekPositionInt();
}
private void fetchData() {
while (true) {
if (data != null) {
long advance = data.data.getPosition() - data.startPosition;
if (advance < data.contentLength) {
break;
} else {
data = null;
}
}
SabrPart sabrPart = sabrStream.parse(input);
if (sabrPart == null) {
break;
}
// Debug
//if (sabrPart instanceof MediaSegmentDataSabrPart) {
// MediaSegmentDataSabrPart data = (MediaSegmentDataSabrPart) sabrPart;
// Log.e(TAG, "Consumed contentLength: " + data.contentLength);
// data.data.skipFully(data.contentLength);
// continue;
//}
if (sabrPart instanceof MediaSegmentDataSabrPart) {
data = (MediaSegmentDataSabrPart) sabrPart;
break;
}
}
}
private int readInt(byte[] buffer, int offset, int length) throws IOException, InterruptedException {
int result = C.RESULT_END_OF_INPUT;
while (true) {
fetchData();
if (data == null) {
break;
}
long advance = data.data.getPosition() - data.startPosition;
int toRead = Math.min(data.contentLength - (int) advance, length);
result = data.data.read(buffer, offset, toRead);
if (result != C.RESULT_END_OF_INPUT) {
position += result;
}
if (result == C.RESULT_END_OF_INPUT || length <= data.contentLength || result < toRead) {
break;
}
offset += result;
length -= result;
Log.e(TAG, "read continue: offset=%s, length=%s", offset, length);
}
return result;
}
private void readFullyInt(byte[] buffer, int offset, int length) throws IOException, InterruptedException {
while (true) {
fetchData();
if (data == null) {
break;
}
long advance = data.data.getPosition() - data.startPosition;
int toRead = Math.min(data.contentLength - (int) advance, length);
data.data.readFully(buffer, offset, toRead);
position += toRead;
if (length <= data.contentLength) {
break;
}
offset += data.contentLength;
length -= data.contentLength;
Log.e(TAG, "readFully continue: offset=%s, length=%s", offset, length);
}
}
private boolean readFullyInt(byte[] buffer, int offset, int length, boolean allowEndOfInput) throws IOException, InterruptedException {
boolean result = false;
while (true) {
fetchData();
if (data == null) {
break;
}
long advance = data.data.getPosition() - data.startPosition;
int toRead = Math.min(data.contentLength - (int) advance, length);
result = data.data.readFully(buffer, offset, toRead, allowEndOfInput);
if (result) {
position += toRead;
}
if (!result || length <= data.contentLength) {
break;
}
offset += data.contentLength;
length -= data.contentLength;
Log.e(TAG, "readFully continue: offset=%s, length=%s", offset, length);
}
return result;
}
private int skipInt(int length) throws IOException, InterruptedException {
int result = C.RESULT_END_OF_INPUT;
while (true) {
fetchData();
if (data == null) {
break;
}
long advance = data.data.getPosition() - data.startPosition;
int toRead = Math.min(data.contentLength - (int) advance, length);
result = data.data.skip(toRead);
if (result != C.RESULT_END_OF_INPUT) {
position += result;
}
if (result == C.RESULT_END_OF_INPUT || length <= data.contentLength || result < toRead) {
break;
}
length -= result;
Log.e(TAG, "skip continue: length=%s", length);
}
return result;
}
private void skipFullyInt(int length) throws IOException, InterruptedException {
while (true) {
fetchData();
if (data == null) {
break;
}
long advance = data.data.getPosition() - data.startPosition;
int toRead = Math.min(data.contentLength - (int) advance, length);
data.data.skipFully(toRead);
position += toRead;
if (length <= data.contentLength) {
break;
}
length -= data.contentLength;
Log.e(TAG, "skipFully continue: length=%s", length);
}
}
private boolean skipFullyInt(int length, boolean allowEndOfInput) throws IOException, InterruptedException {
boolean result = false;
while (true) {
fetchData();
if (data == null) {
break;
}
long advance = data.data.getPosition() - data.startPosition;
int toRead = Math.min(data.contentLength - (int) advance, length);
result = data.data.skipFully(toRead, allowEndOfInput);
if (result) {
position += toRead;
}
if (!result || length <= data.contentLength) {
break;
}
length -= data.contentLength;
Log.e(TAG, "skipFully continue: length=%s, allowEndOfInput=%s", length, allowEndOfInput);
}
return result;
}
private long getPositionInt() {
return position;
}
// NOTE: The below methods not used!
private boolean peekFullyInt(byte[] target, int offset, int length, boolean allowEndOfInput) throws IOException, InterruptedException {
throw new UnsupportedOperationException("This method shouldn't be called");
}
private void peekFullyInt(byte[] target, int offset, int length) throws IOException, InterruptedException {
throw new UnsupportedOperationException("This method shouldn't be called");
}
private boolean advancePeekPositionInt(int length, boolean allowEndOfInput) throws IOException, InterruptedException {
throw new UnsupportedOperationException("This method shouldn't be called");
}
private void advancePeekPositionInt(int length) throws IOException, InterruptedException {
throw new UnsupportedOperationException("This method shouldn't be called");
}
private void resetPeekPositionInt() {
throw new UnsupportedOperationException("This method shouldn't be called");
}
private long getPeekPositionInt() {
throw new UnsupportedOperationException("This method shouldn't be called");
}
private <E extends Throwable> void setRetryPositionInt(long p, E e) throws E {
throw new UnsupportedOperationException("This method shouldn't be called");
}
}

View File

@@ -14,6 +14,7 @@ public class MediaSegmentDataSabrPart implements SabrPart {
public final ExtractorInput data;
public final int contentLength;
public final int segmentStartBytes;
public final long startPosition;
public MediaSegmentDataSabrPart(
FormatSelector formatSelector,
@@ -34,5 +35,7 @@ public class MediaSegmentDataSabrPart implements SabrPart {
this.data = data;
this.contentLength = contentLength;
this.segmentStartBytes = segmentStartBytes;
this.startPosition = data.getPosition();
}
}

View File

@@ -9,7 +9,8 @@ echo This could be helpful when you need to clean the repo after DMCA Notice.
cd /d "%~dp0"
REM Skip first 15 releases
for /F "skip=300 tokens=*" %%a in ('hub release') do call :cleanupRelease %%a
REM https://github.com/yuliskov/SmartTube/releases?page=46
for /F "skip=15 tokens=*" %%a in ('hub release') do call :cleanupRelease %%a
goto End
@@ -25,7 +26,15 @@ goto End
REM NOTE: don't add quotes around %%~nxf because there's a white space at the end.
REM NOTE: Empty message == don't change release title
REM Manual: https://hub.github.com/hub-release.1.html
for %%f in ("%%a") do hub release edit -a %%~nxf -m "" %TAG_NAME% 2>nul
REM for %%f in ("%%a") do hub release edit -a %%~nxf -m "" %TAG_NAME% 2>nul
for %%f in ("%%a") do (
hub release edit -a %%~nxf -m "" %TAG_NAME% 2>nul
if errorlevel 1 (
echo Rate limit hit. Sleeping for 5 minutes...
timeout /t 300 /nobreak >nul
)
)
)
:cleanupReleaseEnd