Skip to content

Commit

Permalink
Merge pull request #7378 from shenwill:dev-v2
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 336875300
  • Loading branch information
kim-vde committed Oct 13, 2020
2 parents d700627 + 34ef9b2 commit 2e5f2f1
Show file tree
Hide file tree
Showing 13 changed files with 1,366 additions and 7 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
([#7988](https:/google/ExoPlayer/issues/7988)).
* Ignore negative payload size in PES packets
([#8005](https:/google/ExoPlayer/issues/8005)).
* Make FLV files seekable by using the key frame index
([#7378](https:/google/ExoPlayer/issues/7378)).
* HLS:
* Fix crash affecting chunkful preparation of master playlists that start
with an I-FRAME only variant
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.exoplayer2.extractor;

import static com.google.android.exoplayer2.util.Assertions.checkArgument;

import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Util;

/**
* A {@link SeekMap} implementation based on a mapping between times and positions in the input
* stream.
*/
public final class IndexSeekMap implements SeekMap {

private final long[] positions;
private final long[] timesUs;
private final long durationUs;
private final boolean isSeekable;

/**
* Creates an instance.
*
* @param positions The positions in the stream corresponding to {@code timesUs}, in bytes.
* @param timesUs The times corresponding to {@code positions}, in microseconds.
* @param durationUs The duration of the input stream, or {@link C#TIME_UNSET} if it is unknown.
*/
public IndexSeekMap(long[] positions, long[] timesUs, long durationUs) {
checkArgument(positions.length == timesUs.length);
int length = timesUs.length;
isSeekable = length > 0;
if (isSeekable && timesUs[0] > 0) {
// Add (position = 0, timeUs = 0) as first entry.
this.positions = new long[length + 1];
this.timesUs = new long[length + 1];
System.arraycopy(positions, 0, this.positions, 1, length);
System.arraycopy(timesUs, 0, this.timesUs, 1, length);
} else {
this.positions = positions;
this.timesUs = timesUs;
}
this.durationUs = durationUs;
}

@Override
public boolean isSeekable() {
return isSeekable;
}

@Override
public long getDurationUs() {
return durationUs;
}

@Override
public SeekMap.SeekPoints getSeekPoints(long timeUs) {
if (!isSeekable) {
return new SeekMap.SeekPoints(SeekPoint.START);
}
int targetIndex =
Util.binarySearchFloor(timesUs, timeUs, /* inclusive= */ true, /* stayInBounds= */ true);
SeekPoint leftSeekPoint = new SeekPoint(timesUs[targetIndex], positions[targetIndex]);
if (leftSeekPoint.timeUs >= timeUs || targetIndex == timesUs.length - 1) {
return new SeekMap.SeekPoints(leftSeekPoint);
} else {
SeekPoint rightSeekPoint =
new SeekPoint(timesUs[targetIndex + 1], positions[targetIndex + 1]);
return new SeekMap.SeekPoints(leftSeekPoint, rightSeekPoint);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.IndexSeekMap;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.util.Assertions;
Expand Down Expand Up @@ -135,8 +136,12 @@ public void init(ExtractorOutput output) {

@Override
public void seek(long position, long timeUs) {
state = STATE_READING_FLV_HEADER;
outputFirstSample = false;
if (position == 0) {
state = STATE_READING_FLV_HEADER;
outputFirstSample = false;
} else {
state = STATE_READING_TAG_HEADER;
}
bytesToNextTagHeader = 0;
}

Expand Down Expand Up @@ -267,7 +272,11 @@ private boolean readTagData(ExtractorInput input) throws IOException {
wasSampleOutput = metadataReader.consume(prepareTagData(input), timestampUs);
long durationUs = metadataReader.getDurationUs();
if (durationUs != C.TIME_UNSET) {
extractorOutput.seekMap(new SeekMap.Unseekable(durationUs));
extractorOutput.seekMap(
new IndexSeekMap(
metadataReader.getKeyFrameTagPositions(),
metadataReader.getKeyFrameTimesUs(),
durationUs));
outputSeekMap = true;
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
Expand All @@ -31,6 +32,9 @@

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;
Expand All @@ -43,16 +47,28 @@
private static final int AMF_TYPE_DATE = 11;

private long durationUs;
private long[] keyFrameTimesUs;
private long[] keyFrameTagPositions;

public ScriptTagPayloadReader() {
super(new DummyTrackOutput());
durationUs = C.TIME_UNSET;
keyFrameTimesUs = new long[0];
keyFrameTagPositions = new long[0];
}

public long getDurationUs() {
return durationUs;
}

public long[] getKeyFrameTimesUs() {
return keyFrameTimesUs;
}

public long[] getKeyFrameTagPositions() {
return keyFrameTagPositions;
}

@Override
public void seek() {
// Do nothing.
Expand Down Expand Up @@ -80,14 +96,41 @@ protected boolean parsePayload(ParsableByteArray data, long timeUs) {
// We're not interested in this metadata.
return false;
}
// Set the duration to the value contained in the metadata, if present.
Map<String, Object> metadata = readAmfEcmaArray(data);
if (metadata.containsKey(KEY_DURATION)) {
double durationSeconds = (double) metadata.get(KEY_DURATION);
// Set the duration to the value contained in the metadata, if present.
@Nullable Object durationSecondsObj = metadata.get(KEY_DURATION);
if (durationSecondsObj instanceof Double) {
double durationSeconds = (double) durationSecondsObj;
if (durationSeconds > 0.0) {
durationUs = (long) (durationSeconds * C.MICROS_PER_SECOND);
}
}
// Set the key frame times and positions to the value contained in the metadata, if present.
@Nullable Object keyFramesObj = metadata.get(KEY_KEY_FRAMES);
if (keyFramesObj instanceof Map) {
Map<?, ?> keyFrames = (Map<?, ?>) keyFramesObj;
@Nullable Object positionsObj = keyFrames.get(KEY_FILE_POSITIONS);
@Nullable Object timesSecondsObj = keyFrames.get(KEY_TIMES);
if (positionsObj instanceof List && timesSecondsObj instanceof List) {
List<?> positions = (List<?>) positionsObj;
List<?> timesSeconds = (List<?>) timesSecondsObj;
int keyFrameCount = timesSeconds.size();
keyFrameTimesUs = new long[keyFrameCount];
keyFrameTagPositions = new long[keyFrameCount];
for (int i = 0; i < keyFrameCount; i++) {
Object positionObj = positions.get(i);
Object timeSecondsObj = timesSeconds.get(i);
if (timeSecondsObj instanceof Double && positionObj instanceof Double) {
keyFrameTimesUs[i] = (long) (((Double) timeSecondsObj) * C.MICROS_PER_SECOND);
keyFrameTagPositions[i] = ((Double) positionObj).longValue();
} else {
keyFrameTimesUs = new long[0];
keyFrameTagPositions = new long[0];
break;
}
}
}
}
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public SeekPoints getSeekPoints(long timeUs) {
int targetIndex =
Util.binarySearchFloor(timesUs, timeUs, /* inclusive= */ true, /* stayInBounds= */ true);
SeekPoint seekPoint = new SeekPoint(timesUs.get(targetIndex), positions.get(targetIndex));
if (seekPoint.timeUs >= timeUs || targetIndex == timesUs.size() - 1) {
if (seekPoint.timeUs == timeUs || targetIndex == timesUs.size() - 1) {
return new SeekPoints(seekPoint);
} else {
SeekPoint nextSeekPoint =
Expand Down
Loading

0 comments on commit 2e5f2f1

Please sign in to comment.