mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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();
|
sampleQueues.valueAt(i).clear();
|
||||||
}
|
}
|
||||||
loadable = createLoadableFromStart();
|
loadable = createLoadableFromStart();
|
||||||
} else if (!seekMap.isSeekable()) {
|
} else if (!seekMap.isSeekable() && maxTrackDurationUs == C.UNKNOWN_TIME_US) {
|
||||||
// We're playing a non-seekable stream. Assume it's live, and therefore that the data at
|
// We're playing a non-seekable stream with unknown duration. Assume it's live, and
|
||||||
// the uri is a continuously shifting window of the latest available media. For this case
|
// therefore that the data at the uri is a continuously shifting window of the latest
|
||||||
// there's no way to continue loading from where a previous load finished, and hence it's
|
// available media. For this case there's no way to continue loading from where a previous
|
||||||
// necessary to load from the start whenever commencing a new load.
|
// load finished, so it's necessary to load from the start whenever commencing a new load.
|
||||||
for (int i = 0; i < sampleQueues.size(); i++) {
|
for (int i = 0; i < sampleQueues.size(); i++) {
|
||||||
sampleQueues.valueAt(i).clear();
|
sampleQueues.valueAt(i).clear();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,9 @@ public final class Mp3Extractor implements Extractor {
|
||||||
return RESULT_CONTINUE;
|
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)
|
private long maybeResynchronize(ExtractorInput extractorInput)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
inputBuffer.mark();
|
inputBuffer.mark();
|
||||||
|
|
|
||||||
|
|
@ -45,23 +45,18 @@ import com.google.android.exoplayer.util.Util;
|
||||||
long firstFramePosition = position + mpegAudioHeader.frameSize;
|
long firstFramePosition = position + mpegAudioHeader.frameSize;
|
||||||
|
|
||||||
int flags = frame.readInt();
|
int flags = frame.readInt();
|
||||||
// Frame count, size and table of contents are required to use this header.
|
int frameCount;
|
||||||
if ((flags & 0x07) != 0x07) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
long durationUs = Util.scaleLargeTimestamp(frameCount, samplesPerFrame * 1000000L, sampleRate);
|
||||||
// Read frame count, as (flags & 1) == 1.
|
if ((flags & 0x06) != 0x06) {
|
||||||
int frameCount = frame.readUnsignedIntToInt();
|
// If the size in bytes or table of contents is missing, the stream is not seekable.
|
||||||
if (frameCount == 0) {
|
return new XingSeeker(inputLength, firstFramePosition, durationUs);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
long durationUs =
|
|
||||||
Util.scaleLargeTimestamp(frameCount, samplesPerFrame * 1000000L, sampleRate);
|
|
||||||
|
|
||||||
// Read size in bytes, as (flags & 2) == 2.
|
|
||||||
long sizeBytes = frame.readUnsignedIntToInt();
|
long sizeBytes = frame.readUnsignedIntToInt();
|
||||||
|
|
||||||
// Read table-of-contents as (flags & 4) == 4.
|
|
||||||
frame.skipBytes(1);
|
frame.skipBytes(1);
|
||||||
long[] tableOfContents = new long[99];
|
long[] tableOfContents = new long[99];
|
||||||
for (int i = 0; i < 99; i++) {
|
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:
|
// TODO: Handle encoder delay and padding in 3 bytes offset by xingBase + 213 bytes:
|
||||||
// delay = (frame.readUnsignedByte() << 4) + (frame.readUnsignedByte() >> 4);
|
// delay = (frame.readUnsignedByte() << 4) + (frame.readUnsignedByte() >> 4);
|
||||||
// padding = ((frame.readUnsignedByte() & 0x0F) << 8) + frame.readUnsignedByte();
|
// 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[] tableOfContents;
|
||||||
private final long firstFramePosition;
|
private final long firstFramePosition;
|
||||||
private final long sizeBytes;
|
private final long sizeBytes;
|
||||||
private final long durationUs;
|
private final long durationUs;
|
||||||
private final long inputLength;
|
private final long inputLength;
|
||||||
|
|
||||||
private XingSeeker(long[] tableOfContents, long firstFramePosition, long sizeBytes,
|
private XingSeeker(long inputLength, long firstFramePosition, long durationUs) {
|
||||||
long durationUs, long inputLength) {
|
this(inputLength, firstFramePosition, durationUs, null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private XingSeeker(long inputLength, long firstFramePosition, long durationUs,
|
||||||
|
long[] tableOfContents, long sizeBytes) {
|
||||||
this.tableOfContents = tableOfContents;
|
this.tableOfContents = tableOfContents;
|
||||||
this.firstFramePosition = firstFramePosition;
|
this.firstFramePosition = firstFramePosition;
|
||||||
this.sizeBytes = sizeBytes;
|
this.sizeBytes = sizeBytes;
|
||||||
|
|
@ -92,11 +93,14 @@ import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSeekable() {
|
public boolean isSeekable() {
|
||||||
return true;
|
return tableOfContents != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getPosition(long timeUs) {
|
public long getPosition(long timeUs) {
|
||||||
|
if (!isSeekable()) {
|
||||||
|
return firstFramePosition;
|
||||||
|
}
|
||||||
float percent = timeUs * 100f / durationUs;
|
float percent = timeUs * 100f / durationUs;
|
||||||
float fx;
|
float fx;
|
||||||
if (percent <= 0f) {
|
if (percent <= 0f) {
|
||||||
|
|
@ -125,6 +129,9 @@ import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getTimeUs(long position) {
|
public long getTimeUs(long position) {
|
||||||
|
if (!isSeekable()) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
long offsetByte = 256 * (position - firstFramePosition) / sizeBytes;
|
long offsetByte = 256 * (position - firstFramePosition) / sizeBytes;
|
||||||
int previousIndex = Util.binarySearchFloor(tableOfContents, offsetByte, true, false);
|
int previousIndex = Util.binarySearchFloor(tableOfContents, offsetByte, true, false);
|
||||||
long previousTime = getTimeUsForTocIndex(previousIndex);
|
long previousTime = getTimeUsForTocIndex(previousIndex);
|
||||||
|
|
@ -146,7 +153,9 @@ import com.google.android.exoplayer.util.Util;
|
||||||
return durationUs;
|
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) {
|
private long getTimeUsForTocIndex(int tocIndex) {
|
||||||
return durationUs * (tocIndex + 1) / 100;
|
return durationUs * (tocIndex + 1) / 100;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue