mirror of
https://github.com/androidx/media.git
synced 2025-05-07 23:50:44 +08:00
Make FLV video seekable by a seekMap.
This commit is contained in:
parent
535e14cb4d
commit
99960acec3
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.flv;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.ChunkIndex;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||
@ -29,6 +30,7 @@ import java.io.IOException;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.List;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
@ -83,6 +85,7 @@ public final class FlvExtractor implements Extractor {
|
||||
private int tagDataSize;
|
||||
private long tagTimestampUs;
|
||||
private boolean outputSeekMap;
|
||||
private boolean seekMapIsSeekable;
|
||||
private @MonotonicNonNull AudioTagPayloadReader audioReader;
|
||||
private @MonotonicNonNull VideoTagPayloadReader videoReader;
|
||||
|
||||
@ -133,7 +136,12 @@ public final class FlvExtractor implements Extractor {
|
||||
|
||||
@Override
|
||||
public void seek(long position, long timeUs) {
|
||||
state = STATE_READING_FLV_HEADER;
|
||||
if (seekMapIsSeekable) {
|
||||
state = STATE_READING_TAG_HEADER;
|
||||
} else {
|
||||
state = STATE_READING_FLV_HEADER;
|
||||
mediaTagTimestampOffsetUs = C.TIME_UNSET;
|
||||
}
|
||||
outputFirstSample = false;
|
||||
bytesToNextTagHeader = 0;
|
||||
}
|
||||
@ -263,11 +271,13 @@ public final class FlvExtractor implements Extractor {
|
||||
wasSampleOutput = videoReader.consume(prepareTagData(input), timestampUs);
|
||||
} else if (tagType == TAG_TYPE_SCRIPT_DATA && !outputSeekMap) {
|
||||
wasSampleOutput = metadataReader.consume(prepareTagData(input), timestampUs);
|
||||
long durationUs = metadataReader.getDurationUs();
|
||||
if (durationUs != C.TIME_UNSET) {
|
||||
extractorOutput.seekMap(new SeekMap.Unseekable(durationUs));
|
||||
outputSeekMap = true;
|
||||
}
|
||||
SeekMap seekMap = buildSeekMap(metadataReader.getSeekMapTimes(),
|
||||
metadataReader.getSeekMapFilePositions(),
|
||||
metadataReader.getDurationUs(),
|
||||
input.getLength());
|
||||
seekMapIsSeekable = seekMap.isSeekable();
|
||||
extractorOutput.seekMap(seekMap);
|
||||
outputSeekMap = true;
|
||||
} else {
|
||||
input.skipFully(tagDataSize);
|
||||
wasConsumed = false;
|
||||
@ -301,6 +311,32 @@ public final class FlvExtractor implements Extractor {
|
||||
}
|
||||
}
|
||||
|
||||
private SeekMap buildSeekMap(List<Double> times, List<Double> filePositions, long durationUs,
|
||||
long flvDataSize) {
|
||||
if (durationUs == C.TIME_UNSET
|
||||
|| times == null || times.size() == 0
|
||||
|| filePositions == null || filePositions.size() != times.size()) {
|
||||
// Key frames information is missing or incomplete.
|
||||
return new SeekMap.Unseekable(durationUs);
|
||||
}
|
||||
int keyFrameSize = times.size();
|
||||
int[] sizes = new int[keyFrameSize];
|
||||
long[] offsets = new long[keyFrameSize];
|
||||
long[] durationsUs = new long[keyFrameSize];
|
||||
long[] timesUs = new long[keyFrameSize];
|
||||
for (int i = 0; i < keyFrameSize; i++) {
|
||||
timesUs[i] = (long) (times.get(i) * C.MICROS_PER_SECOND);
|
||||
offsets[i] = (long) (filePositions.get(i) + 0);
|
||||
}
|
||||
for (int i = 0; i < keyFrameSize - 1; i++) {
|
||||
sizes[i] = (int) (offsets[i + 1] - offsets[i]);
|
||||
durationsUs[i] = timesUs[i + 1] - timesUs[i];
|
||||
}
|
||||
sizes[keyFrameSize - 1] = (int) (flvDataSize - sizes[keyFrameSize - 2]);
|
||||
durationsUs[keyFrameSize - 1] = durationUs - timesUs[keyFrameSize - 1];
|
||||
return new ChunkIndex(sizes, offsets, durationsUs, timesUs);
|
||||
}
|
||||
|
||||
private long getCurrentTimestampUs() {
|
||||
return outputFirstSample
|
||||
? (mediaTagTimestampOffsetUs + tagTimestampUs)
|
||||
|
@ -23,6 +23,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -32,6 +33,9 @@ import java.util.Map;
|
||||
|
||||
private static final String NAME_METADATA = "onMetaData";
|
||||
private static final String KEY_DURATION = "duration";
|
||||
private static final String KEY_KEY_FRAMES = "keyframes";
|
||||
private static final String KEY_FILE_POSITIONS = "filepositions";
|
||||
private static final String KEY_TIMES = "times";
|
||||
|
||||
// AMF object types
|
||||
private static final int AMF_TYPE_NUMBER = 0;
|
||||
@ -45,6 +49,9 @@ import java.util.Map;
|
||||
|
||||
private long durationUs;
|
||||
|
||||
private List<Double> seekMapFilePositions;
|
||||
private List<Double> seekMapTimes;
|
||||
|
||||
public ScriptTagPayloadReader() {
|
||||
super(new DummyTrackOutput());
|
||||
durationUs = C.TIME_UNSET;
|
||||
@ -89,6 +96,16 @@ import java.util.Map;
|
||||
durationUs = (long) (durationSeconds * C.MICROS_PER_SECOND);
|
||||
}
|
||||
}
|
||||
if (metadata.containsKey(KEY_KEY_FRAMES)) {
|
||||
Object frames = metadata.get(KEY_KEY_FRAMES);
|
||||
if (frames instanceof Map) {
|
||||
Map framesMap = (Map) metadata.get(KEY_KEY_FRAMES);
|
||||
if (framesMap.size() > 0) {
|
||||
seekMapFilePositions = (List<Double>) framesMap.get(KEY_FILE_POSITIONS);
|
||||
seekMapTimes = (List<Double>) framesMap.get(KEY_TIMES);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -224,4 +241,12 @@ import java.util.Map;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Double> getSeekMapFilePositions() {
|
||||
return seekMapFilePositions;
|
||||
}
|
||||
|
||||
public List<Double> getSeekMapTimes() {
|
||||
return seekMapTimes;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user