Skip to content

Commit 2e5f2f1

Browse files
committed
Merge pull request #7378 from shenwill:dev-v2
PiperOrigin-RevId: 336875300
2 parents d700627 + 34ef9b2 commit 2e5f2f1

13 files changed

Lines changed: 1366 additions & 7 deletions

File tree

RELEASENOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
([#7988](https://github.com/google/ExoPlayer/issues/7988)).
5656
* Ignore negative payload size in PES packets
5757
([#8005](https://github.com/google/ExoPlayer/issues/8005)).
58+
* Make FLV files seekable by using the key frame index
59+
([#7378](https://github.com/google/ExoPlayer/issues/7378)).
5860
* HLS:
5961
* Fix crash affecting chunkful preparation of master playlists that start
6062
with an I-FRAME only variant
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2020 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.android.exoplayer2.extractor;
18+
19+
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
20+
21+
import com.google.android.exoplayer2.C;
22+
import com.google.android.exoplayer2.util.Util;
23+
24+
/**
25+
* A {@link SeekMap} implementation based on a mapping between times and positions in the input
26+
* stream.
27+
*/
28+
public final class IndexSeekMap implements SeekMap {
29+
30+
private final long[] positions;
31+
private final long[] timesUs;
32+
private final long durationUs;
33+
private final boolean isSeekable;
34+
35+
/**
36+
* Creates an instance.
37+
*
38+
* @param positions The positions in the stream corresponding to {@code timesUs}, in bytes.
39+
* @param timesUs The times corresponding to {@code positions}, in microseconds.
40+
* @param durationUs The duration of the input stream, or {@link C#TIME_UNSET} if it is unknown.
41+
*/
42+
public IndexSeekMap(long[] positions, long[] timesUs, long durationUs) {
43+
checkArgument(positions.length == timesUs.length);
44+
int length = timesUs.length;
45+
isSeekable = length > 0;
46+
if (isSeekable && timesUs[0] > 0) {
47+
// Add (position = 0, timeUs = 0) as first entry.
48+
this.positions = new long[length + 1];
49+
this.timesUs = new long[length + 1];
50+
System.arraycopy(positions, 0, this.positions, 1, length);
51+
System.arraycopy(timesUs, 0, this.timesUs, 1, length);
52+
} else {
53+
this.positions = positions;
54+
this.timesUs = timesUs;
55+
}
56+
this.durationUs = durationUs;
57+
}
58+
59+
@Override
60+
public boolean isSeekable() {
61+
return isSeekable;
62+
}
63+
64+
@Override
65+
public long getDurationUs() {
66+
return durationUs;
67+
}
68+
69+
@Override
70+
public SeekMap.SeekPoints getSeekPoints(long timeUs) {
71+
if (!isSeekable) {
72+
return new SeekMap.SeekPoints(SeekPoint.START);
73+
}
74+
int targetIndex =
75+
Util.binarySearchFloor(timesUs, timeUs, /* inclusive= */ true, /* stayInBounds= */ true);
76+
SeekPoint leftSeekPoint = new SeekPoint(timesUs[targetIndex], positions[targetIndex]);
77+
if (leftSeekPoint.timeUs >= timeUs || targetIndex == timesUs.length - 1) {
78+
return new SeekMap.SeekPoints(leftSeekPoint);
79+
} else {
80+
SeekPoint rightSeekPoint =
81+
new SeekPoint(timesUs[targetIndex + 1], positions[targetIndex + 1]);
82+
return new SeekMap.SeekPoints(leftSeekPoint, rightSeekPoint);
83+
}
84+
}
85+
}

library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.android.exoplayer2.extractor.ExtractorInput;
2424
import com.google.android.exoplayer2.extractor.ExtractorOutput;
2525
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
26+
import com.google.android.exoplayer2.extractor.IndexSeekMap;
2627
import com.google.android.exoplayer2.extractor.PositionHolder;
2728
import com.google.android.exoplayer2.extractor.SeekMap;
2829
import com.google.android.exoplayer2.util.Assertions;
@@ -135,8 +136,12 @@ public void init(ExtractorOutput output) {
135136

136137
@Override
137138
public void seek(long position, long timeUs) {
138-
state = STATE_READING_FLV_HEADER;
139-
outputFirstSample = false;
139+
if (position == 0) {
140+
state = STATE_READING_FLV_HEADER;
141+
outputFirstSample = false;
142+
} else {
143+
state = STATE_READING_TAG_HEADER;
144+
}
140145
bytesToNextTagHeader = 0;
141146
}
142147

@@ -267,7 +272,11 @@ private boolean readTagData(ExtractorInput input) throws IOException {
267272
wasSampleOutput = metadataReader.consume(prepareTagData(input), timestampUs);
268273
long durationUs = metadataReader.getDurationUs();
269274
if (durationUs != C.TIME_UNSET) {
270-
extractorOutput.seekMap(new SeekMap.Unseekable(durationUs));
275+
extractorOutput.seekMap(
276+
new IndexSeekMap(
277+
metadataReader.getKeyFrameTagPositions(),
278+
metadataReader.getKeyFrameTimesUs(),
279+
durationUs));
271280
outputSeekMap = true;
272281
}
273282
} else {

library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/ScriptTagPayloadReader.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.ArrayList;
2323
import java.util.Date;
2424
import java.util.HashMap;
25+
import java.util.List;
2526
import java.util.Map;
2627

2728
/**
@@ -31,6 +32,9 @@
3132

3233
private static final String NAME_METADATA = "onMetaData";
3334
private static final String KEY_DURATION = "duration";
35+
private static final String KEY_KEY_FRAMES = "keyframes";
36+
private static final String KEY_FILE_POSITIONS = "filepositions";
37+
private static final String KEY_TIMES = "times";
3438

3539
// AMF object types
3640
private static final int AMF_TYPE_NUMBER = 0;
@@ -43,16 +47,28 @@
4347
private static final int AMF_TYPE_DATE = 11;
4448

4549
private long durationUs;
50+
private long[] keyFrameTimesUs;
51+
private long[] keyFrameTagPositions;
4652

4753
public ScriptTagPayloadReader() {
4854
super(new DummyTrackOutput());
4955
durationUs = C.TIME_UNSET;
56+
keyFrameTimesUs = new long[0];
57+
keyFrameTagPositions = new long[0];
5058
}
5159

5260
public long getDurationUs() {
5361
return durationUs;
5462
}
5563

64+
public long[] getKeyFrameTimesUs() {
65+
return keyFrameTimesUs;
66+
}
67+
68+
public long[] getKeyFrameTagPositions() {
69+
return keyFrameTagPositions;
70+
}
71+
5672
@Override
5773
public void seek() {
5874
// Do nothing.
@@ -80,14 +96,41 @@ protected boolean parsePayload(ParsableByteArray data, long timeUs) {
8096
// We're not interested in this metadata.
8197
return false;
8298
}
83-
// Set the duration to the value contained in the metadata, if present.
8499
Map<String, Object> metadata = readAmfEcmaArray(data);
85-
if (metadata.containsKey(KEY_DURATION)) {
86-
double durationSeconds = (double) metadata.get(KEY_DURATION);
100+
// Set the duration to the value contained in the metadata, if present.
101+
@Nullable Object durationSecondsObj = metadata.get(KEY_DURATION);
102+
if (durationSecondsObj instanceof Double) {
103+
double durationSeconds = (double) durationSecondsObj;
87104
if (durationSeconds > 0.0) {
88105
durationUs = (long) (durationSeconds * C.MICROS_PER_SECOND);
89106
}
90107
}
108+
// Set the key frame times and positions to the value contained in the metadata, if present.
109+
@Nullable Object keyFramesObj = metadata.get(KEY_KEY_FRAMES);
110+
if (keyFramesObj instanceof Map) {
111+
Map<?, ?> keyFrames = (Map<?, ?>) keyFramesObj;
112+
@Nullable Object positionsObj = keyFrames.get(KEY_FILE_POSITIONS);
113+
@Nullable Object timesSecondsObj = keyFrames.get(KEY_TIMES);
114+
if (positionsObj instanceof List && timesSecondsObj instanceof List) {
115+
List<?> positions = (List<?>) positionsObj;
116+
List<?> timesSeconds = (List<?>) timesSecondsObj;
117+
int keyFrameCount = timesSeconds.size();
118+
keyFrameTimesUs = new long[keyFrameCount];
119+
keyFrameTagPositions = new long[keyFrameCount];
120+
for (int i = 0; i < keyFrameCount; i++) {
121+
Object positionObj = positions.get(i);
122+
Object timeSecondsObj = timesSeconds.get(i);
123+
if (timeSecondsObj instanceof Double && positionObj instanceof Double) {
124+
keyFrameTimesUs[i] = (long) (((Double) timeSecondsObj) * C.MICROS_PER_SECOND);
125+
keyFrameTagPositions[i] = ((Double) positionObj).longValue();
126+
} else {
127+
keyFrameTimesUs = new long[0];
128+
keyFrameTagPositions = new long[0];
129+
break;
130+
}
131+
}
132+
}
133+
}
91134
return false;
92135
}
93136

library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp3/IndexSeeker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public SeekPoints getSeekPoints(long timeUs) {
7070
int targetIndex =
7171
Util.binarySearchFloor(timesUs, timeUs, /* inclusive= */ true, /* stayInBounds= */ true);
7272
SeekPoint seekPoint = new SeekPoint(timesUs.get(targetIndex), positions.get(targetIndex));
73-
if (seekPoint.timeUs >= timeUs || targetIndex == timesUs.size() - 1) {
73+
if (seekPoint.timeUs == timeUs || targetIndex == timesUs.size() - 1) {
7474
return new SeekPoints(seekPoint);
7575
} else {
7676
SeekPoint nextSeekPoint =

0 commit comments

Comments
 (0)