mirror of
https://github.com/samsonjs/media.git
synced 2026-04-10 12:05:47 +00:00
Use XING headers without size/table of contents.
These MP3s are unseekable but allow calculating the VBR duration correctly. Treat streams as live only if they are unseekable and lack a duration. Issue: #713
This commit is contained in:
parent
6799d6dad0
commit
64c0e5c997
3 changed files with 35 additions and 24 deletions
|
|
@ -574,11 +574,11 @@ public final class ExtractorSampleSource implements SampleSource, SampleSourceRe
|
|||
sampleQueues.valueAt(i).clear();
|
||||
}
|
||||
loadable = createLoadableFromStart();
|
||||
} else if (!seekMap.isSeekable()) {
|
||||
// We're playing a non-seekable stream. Assume it's live, and therefore that the data at
|
||||
// the uri is a continuously shifting window of the latest available media. For this case
|
||||
// there's no way to continue loading from where a previous load finished, and hence it's
|
||||
// necessary to load from the start whenever commencing a new load.
|
||||
} else if (!seekMap.isSeekable() && maxTrackDurationUs == C.UNKNOWN_TIME_US) {
|
||||
// We're playing a non-seekable stream with unknown duration. Assume it's live, and
|
||||
// therefore that the data at the uri is a continuously shifting window of the latest
|
||||
// available media. For this case there's no way to continue loading from where a previous
|
||||
// load finished, so it's necessary to load from the start whenever commencing a new load.
|
||||
for (int i = 0; i < sampleQueues.size(); i++) {
|
||||
sampleQueues.valueAt(i).clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,7 +191,9 @@ public final class Mp3Extractor implements Extractor {
|
|||
return RESULT_CONTINUE;
|
||||
}
|
||||
|
||||
/** Attempts to read an MPEG audio header at the current offset, resynchronizing if necessary. */
|
||||
/**
|
||||
* Attempts to read an MPEG audio header at the current offset, resynchronizing if necessary.
|
||||
*/
|
||||
private long maybeResynchronize(ExtractorInput extractorInput)
|
||||
throws IOException, InterruptedException {
|
||||
inputBuffer.mark();
|
||||
|
|
|
|||
|
|
@ -45,23 +45,18 @@ import com.google.android.exoplayer.util.Util;
|
|||
long firstFramePosition = position + mpegAudioHeader.frameSize;
|
||||
|
||||
int flags = frame.readInt();
|
||||
// Frame count, size and table of contents are required to use this header.
|
||||
if ((flags & 0x07) != 0x07) {
|
||||
int frameCount;
|
||||
if ((flags & 0x01) != 0x01 || (frameCount = frame.readUnsignedIntToInt()) == 0) {
|
||||
// If the frame count is missing/invalid, the header can't be used to determine the duration.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read frame count, as (flags & 1) == 1.
|
||||
int frameCount = frame.readUnsignedIntToInt();
|
||||
if (frameCount == 0) {
|
||||
return null;
|
||||
long durationUs = Util.scaleLargeTimestamp(frameCount, samplesPerFrame * 1000000L, sampleRate);
|
||||
if ((flags & 0x06) != 0x06) {
|
||||
// If the size in bytes or table of contents is missing, the stream is not seekable.
|
||||
return new XingSeeker(inputLength, firstFramePosition, durationUs);
|
||||
}
|
||||
long durationUs =
|
||||
Util.scaleLargeTimestamp(frameCount, samplesPerFrame * 1000000L, sampleRate);
|
||||
|
||||
// Read size in bytes, as (flags & 2) == 2.
|
||||
long sizeBytes = frame.readUnsignedIntToInt();
|
||||
|
||||
// Read table-of-contents as (flags & 4) == 4.
|
||||
frame.skipBytes(1);
|
||||
long[] tableOfContents = new long[99];
|
||||
for (int i = 0; i < 99; i++) {
|
||||
|
|
@ -71,18 +66,24 @@ import com.google.android.exoplayer.util.Util;
|
|||
// TODO: Handle encoder delay and padding in 3 bytes offset by xingBase + 213 bytes:
|
||||
// delay = (frame.readUnsignedByte() << 4) + (frame.readUnsignedByte() >> 4);
|
||||
// padding = ((frame.readUnsignedByte() & 0x0F) << 8) + frame.readUnsignedByte();
|
||||
return new XingSeeker(tableOfContents, firstFramePosition, sizeBytes, durationUs, inputLength);
|
||||
return new XingSeeker(inputLength, firstFramePosition, durationUs, tableOfContents, sizeBytes);
|
||||
}
|
||||
|
||||
/** Entries are in the range [0, 255], but are stored as long integers for convenience. */
|
||||
/**
|
||||
* Entries are in the range [0, 255], but are stored as long integers for convenience.
|
||||
*/
|
||||
private final long[] tableOfContents;
|
||||
private final long firstFramePosition;
|
||||
private final long sizeBytes;
|
||||
private final long durationUs;
|
||||
private final long inputLength;
|
||||
|
||||
private XingSeeker(long[] tableOfContents, long firstFramePosition, long sizeBytes,
|
||||
long durationUs, long inputLength) {
|
||||
private XingSeeker(long inputLength, long firstFramePosition, long durationUs) {
|
||||
this(inputLength, firstFramePosition, durationUs, null, 0);
|
||||
}
|
||||
|
||||
private XingSeeker(long inputLength, long firstFramePosition, long durationUs,
|
||||
long[] tableOfContents, long sizeBytes) {
|
||||
this.tableOfContents = tableOfContents;
|
||||
this.firstFramePosition = firstFramePosition;
|
||||
this.sizeBytes = sizeBytes;
|
||||
|
|
@ -92,11 +93,14 @@ import com.google.android.exoplayer.util.Util;
|
|||
|
||||
@Override
|
||||
public boolean isSeekable() {
|
||||
return true;
|
||||
return tableOfContents != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
if (!isSeekable()) {
|
||||
return firstFramePosition;
|
||||
}
|
||||
float percent = timeUs * 100f / durationUs;
|
||||
float fx;
|
||||
if (percent <= 0f) {
|
||||
|
|
@ -125,6 +129,9 @@ import com.google.android.exoplayer.util.Util;
|
|||
|
||||
@Override
|
||||
public long getTimeUs(long position) {
|
||||
if (!isSeekable()) {
|
||||
return 0L;
|
||||
}
|
||||
long offsetByte = 256 * (position - firstFramePosition) / sizeBytes;
|
||||
int previousIndex = Util.binarySearchFloor(tableOfContents, offsetByte, true, false);
|
||||
long previousTime = getTimeUsForTocIndex(previousIndex);
|
||||
|
|
@ -146,7 +153,9 @@ import com.google.android.exoplayer.util.Util;
|
|||
return durationUs;
|
||||
}
|
||||
|
||||
/** Returns the time in microseconds corresponding to an index in the table of contents. */
|
||||
/**
|
||||
* Returns the time in microseconds corresponding to an index in the table of contents.
|
||||
*/
|
||||
private long getTimeUsForTocIndex(int tocIndex) {
|
||||
return durationUs * (tocIndex + 1) / 100;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue