mirror of
https://github.com/samsonjs/media.git
synced 2026-03-25 09:25:53 +00:00
Enhance SeekMaps to return SeekPoints
Issue: #2882 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=177814974
This commit is contained in:
parent
bb0fae3ee8
commit
fbfa43f5a3
70 changed files with 439 additions and 173 deletions
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8880
|
||||
getPosition(0) = [[timeUs=0, position=8880]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8880
|
||||
getPosition(0) = [[timeUs=0, position=8880]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8880
|
||||
getPosition(0) = [[timeUs=0, position=8880]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8880
|
||||
getPosition(0) = [[timeUs=0, position=8880]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
|||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||
import com.google.android.exoplayer2.util.FlacStreamInfo;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
|
|
@ -104,26 +105,11 @@ public final class FlacExtractor implements Extractor {
|
|||
}
|
||||
metadataParsed = true;
|
||||
|
||||
extractorOutput.seekMap(new SeekMap() {
|
||||
final boolean isSeekable = decoderJni.getSeekPosition(0) != -1;
|
||||
final long durationUs = streamInfo.durationUs();
|
||||
|
||||
@Override
|
||||
public boolean isSeekable() {
|
||||
return isSeekable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
return isSeekable ? decoderJni.getSeekPosition(timeUs) : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDurationUs() {
|
||||
return durationUs;
|
||||
}
|
||||
|
||||
});
|
||||
boolean isSeekable = decoderJni.getSeekPosition(0) != -1;
|
||||
extractorOutput.seekMap(
|
||||
isSeekable
|
||||
? new FlacSeekMap(streamInfo.durationUs(), decoderJni)
|
||||
: new SeekMap.Unseekable(streamInfo.durationUs(), 0));
|
||||
Format mediaFormat =
|
||||
Format.createAudioSampleFormat(
|
||||
null,
|
||||
|
|
@ -184,4 +170,30 @@ public final class FlacExtractor implements Extractor {
|
|||
}
|
||||
}
|
||||
|
||||
private static final class FlacSeekMap implements SeekMap {
|
||||
|
||||
private final long durationUs;
|
||||
private final FlacDecoderJni decoderJni;
|
||||
|
||||
public FlacSeekMap(long durationUs, FlacDecoderJni decoderJni) {
|
||||
this.durationUs = durationUs;
|
||||
this.decoderJni = decoderJni;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSeekable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
// TODO: Access the seek table via JNI to return two seek points when appropriate.
|
||||
return new SeekPoints(new SeekPoint(timeUs, decoderJni.getSeekPosition(timeUs)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDurationUs() {
|
||||
return durationUs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = 1136000
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 2
|
||||
track 8:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 1072000
|
||||
getPosition(0) = 5576
|
||||
getPosition(0) = [[timeUs=67000, position=5576]]
|
||||
numberOfTracks = 2
|
||||
track 1:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 1072000
|
||||
getPosition(0) = 5576
|
||||
getPosition(0) = [[timeUs=67000, position=5576]]
|
||||
numberOfTracks = 2
|
||||
track 1:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 1072000
|
||||
getPosition(0) = 5576
|
||||
getPosition(0) = [[timeUs=67000, position=5576]]
|
||||
numberOfTracks = 2
|
||||
track 1:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 1072000
|
||||
getPosition(0) = 5576
|
||||
getPosition(0) = [[timeUs=67000, position=5576]]
|
||||
numberOfTracks = 2
|
||||
track 1:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = 1000
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 1:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = 1000
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 1:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2784000
|
||||
getPosition(0) = 201
|
||||
getPosition(0) = [[timeUs=0, position=201]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2784000
|
||||
getPosition(0) = 201
|
||||
getPosition(0) = [[timeUs=0, position=201]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2784000
|
||||
getPosition(0) = 201
|
||||
getPosition(0) = [[timeUs=0, position=201]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2784000
|
||||
getPosition(0) = 201
|
||||
getPosition(0) = [[timeUs=0, position=201]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 26125
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 26125
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 26125
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 26125
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 1024000
|
||||
getPosition(0) = 48
|
||||
getPosition(0) = [[timeUs=0, position=48]]
|
||||
numberOfTracks = 2
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 1024000
|
||||
getPosition(0) = 48
|
||||
getPosition(0) = [[timeUs=0, position=48]]
|
||||
numberOfTracks = 2
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 1024000
|
||||
getPosition(0) = 48
|
||||
getPosition(0) = [[timeUs=0, position=48]]
|
||||
numberOfTracks = 2
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 1024000
|
||||
getPosition(0) = 48
|
||||
getPosition(0) = [[timeUs=0, position=48]]
|
||||
numberOfTracks = 2
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = 1828
|
||||
getPosition(0) = [[timeUs=0, position=1828]]
|
||||
numberOfTracks = 2
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = 1828
|
||||
getPosition(0) = [[timeUs=0, position=1828]]
|
||||
numberOfTracks = 3
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2747500
|
||||
getPosition(0) = 125
|
||||
getPosition(0) = [[timeUs=0, position=125]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2747500
|
||||
getPosition(0) = 125
|
||||
getPosition(0) = [[timeUs=0, position=125]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2747500
|
||||
getPosition(0) = 125
|
||||
getPosition(0) = [[timeUs=0, position=125]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2747500
|
||||
getPosition(0) = 125
|
||||
getPosition(0) = [[timeUs=0, position=125]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8457
|
||||
getPosition(0) = [[timeUs=0, position=8457]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8457
|
||||
getPosition(0) = [[timeUs=0, position=8457]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8457
|
||||
getPosition(0) = [[timeUs=0, position=8457]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8457
|
||||
getPosition(0) = [[timeUs=0, position=8457]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8457
|
||||
getPosition(0) = [[timeUs=0, position=8457]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8407
|
||||
getPosition(0) = [[timeUs=0, position=8407]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8407
|
||||
getPosition(0) = [[timeUs=0, position=8407]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8407
|
||||
getPosition(0) = [[timeUs=0, position=8407]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 8407
|
||||
getPosition(0) = [[timeUs=0, position=8407]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 3995
|
||||
getPosition(0) = [[timeUs=0, position=3995]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 3995
|
||||
getPosition(0) = [[timeUs=0, position=3995]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 3995
|
||||
getPosition(0) = [[timeUs=0, position=3995]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 2741000
|
||||
getPosition(0) = 3995
|
||||
getPosition(0) = [[timeUs=0, position=3995]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 2
|
||||
track 0:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 2
|
||||
track 192:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = 0
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 2
|
||||
track 256:
|
||||
format:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 1000000
|
||||
getPosition(0) = 78
|
||||
getPosition(0) = [[timeUs=0, position=78]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
@ -27,15 +27,15 @@ track 0:
|
|||
initializationData:
|
||||
sample count = 3
|
||||
sample 0:
|
||||
time = 884
|
||||
time = 0
|
||||
flags = 1
|
||||
data = length 32768, hash 9A8CEEBA
|
||||
sample 1:
|
||||
time = 372403
|
||||
time = 371519
|
||||
flags = 1
|
||||
data = length 32768, hash C1717317
|
||||
sample 2:
|
||||
time = 743922
|
||||
time = 743038
|
||||
flags = 1
|
||||
data = length 22664, hash 819F5F62
|
||||
tracksEnded = true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 1000000
|
||||
getPosition(0) = 78
|
||||
getPosition(0) = [[timeUs=0, position=78]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
@ -27,11 +27,11 @@ track 0:
|
|||
initializationData:
|
||||
sample count = 2
|
||||
sample 0:
|
||||
time = 334195
|
||||
time = 333310
|
||||
flags = 1
|
||||
data = length 32768, hash 42D6E860
|
||||
sample 1:
|
||||
time = 705714
|
||||
time = 704829
|
||||
flags = 1
|
||||
data = length 26034, hash 62692C38
|
||||
tracksEnded = true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 1000000
|
||||
getPosition(0) = 78
|
||||
getPosition(0) = [[timeUs=0, position=78]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
@ -27,7 +27,7 @@ track 0:
|
|||
initializationData:
|
||||
sample count = 1
|
||||
sample 0:
|
||||
time = 667528
|
||||
time = 666643
|
||||
flags = 1
|
||||
data = length 29402, hash 4241604E
|
||||
tracksEnded = true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
seekMap:
|
||||
isSeekable = true
|
||||
duration = 1000000
|
||||
getPosition(0) = 78
|
||||
getPosition(0) = [[timeUs=0, position=78]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
|
|
@ -27,7 +27,7 @@ track 0:
|
|||
initializationData:
|
||||
sample count = 1
|
||||
sample 0:
|
||||
time = 1000861
|
||||
time = 999977
|
||||
flags = 1
|
||||
data = length 2, hash 116
|
||||
tracksEnded = true
|
||||
|
|
|
|||
|
|
@ -91,8 +91,15 @@ public final class ChunkIndex implements SeekMap {
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
return offsets[getChunkIndex(timeUs)];
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
int chunkIndex = getChunkIndex(timeUs);
|
||||
SeekPoint seekPoint = new SeekPoint(timesUs[chunkIndex], offsets[chunkIndex]);
|
||||
if (seekPoint.timeUs >= timeUs || chunkIndex == length - 1) {
|
||||
return new SeekPoints(seekPoint);
|
||||
} else {
|
||||
SeekPoint nextSeekPoint = new SeekPoint(timesUs[chunkIndex + 1], offsets[chunkIndex + 1]);
|
||||
return new SeekPoints(seekPoint, nextSeekPoint);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,36 +16,36 @@
|
|||
package com.google.android.exoplayer2.extractor;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
|
||||
/**
|
||||
* Maps seek positions (in microseconds) to corresponding positions (byte offsets) in the stream.
|
||||
*/
|
||||
public interface SeekMap {
|
||||
|
||||
/**
|
||||
* A {@link SeekMap} that does not support seeking.
|
||||
*/
|
||||
/** A {@link SeekMap} that does not support seeking. */
|
||||
final class Unseekable implements SeekMap {
|
||||
|
||||
private final long durationUs;
|
||||
private final long startPosition;
|
||||
private final SeekPoints startSeekPoints;
|
||||
|
||||
/**
|
||||
* @param durationUs The duration of the stream in microseconds, or {@link C#TIME_UNSET} if
|
||||
* the duration is unknown.
|
||||
* @param durationUs The duration of the stream in microseconds, or {@link C#TIME_UNSET} if the
|
||||
* duration is unknown.
|
||||
*/
|
||||
public Unseekable(long durationUs) {
|
||||
this(durationUs, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param durationUs The duration of the stream in microseconds, or {@link C#TIME_UNSET} if
|
||||
* the duration is unknown.
|
||||
* @param durationUs The duration of the stream in microseconds, or {@link C#TIME_UNSET} if the
|
||||
* duration is unknown.
|
||||
* @param startPosition The position (byte offset) of the start of the media.
|
||||
*/
|
||||
public Unseekable(long durationUs, long startPosition) {
|
||||
this.durationUs = durationUs;
|
||||
this.startPosition = startPosition;
|
||||
startSeekPoints =
|
||||
new SeekPoints(startPosition == 0 ? SeekPoint.START : new SeekPoint(0, startPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -59,17 +59,58 @@ public interface SeekMap {
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
return startPosition;
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
return startSeekPoints;
|
||||
}
|
||||
}
|
||||
|
||||
/** Contains one or two {@link SeekPoint}s. */
|
||||
final class SeekPoints {
|
||||
|
||||
/** The first seek point. */
|
||||
public final SeekPoint first;
|
||||
/** The second seek point, or {@link #first} if there's only one seek point. */
|
||||
public final SeekPoint second;
|
||||
|
||||
/** @param point The single seek point. */
|
||||
public SeekPoints(SeekPoint point) {
|
||||
this(point, point);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param first The first seek point.
|
||||
* @param second The second seek point.
|
||||
*/
|
||||
public SeekPoints(SeekPoint first, SeekPoint second) {
|
||||
this.first = Assertions.checkNotNull(first);
|
||||
this.second = Assertions.checkNotNull(second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + first + (first.equals(second) ? "" : (", " + second)) + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SeekPoints other = (SeekPoints) obj;
|
||||
return first.equals(other.first) && second.equals(other.second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (31 * first.hashCode()) + second.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether seeking is supported.
|
||||
* <p>
|
||||
* If seeking is not supported then the only valid seek position is the start of the file, and so
|
||||
* {@link #getPosition(long)} will return 0 for all input values.
|
||||
*
|
||||
* @return Whether seeking is supported.
|
||||
*/
|
||||
|
|
@ -78,20 +119,22 @@ public interface SeekMap {
|
|||
/**
|
||||
* Returns the duration of the stream in microseconds.
|
||||
*
|
||||
* @return The duration of the stream in microseconds, or {@link C#TIME_UNSET} if the
|
||||
* duration is unknown.
|
||||
* @return The duration of the stream in microseconds, or {@link C#TIME_UNSET} if the duration is
|
||||
* unknown.
|
||||
*/
|
||||
long getDurationUs();
|
||||
|
||||
/**
|
||||
* Maps a seek position in microseconds to a corresponding position (byte offset) in the stream
|
||||
* from which data can be provided to the extractor.
|
||||
* Obtains seek points for the specified seek time in microseconds. The returned {@link
|
||||
* SeekPoints} will contain one or two distinct seek points.
|
||||
*
|
||||
* @param timeUs A seek position in microseconds.
|
||||
* @return The corresponding position (byte offset) in the stream from which data can be provided
|
||||
* to the extractor. If {@link #isSeekable()} returns false then the returned value will be
|
||||
* independent of {@code timeUs}, and will indicate the start of the media in the stream.
|
||||
* <p>Two seek points [A, B] are returned in the case that seeking can only be performed to
|
||||
* discrete points in time, there does not exist a seek point at exactly the requested time, and
|
||||
* there exist seek points on both sides of it. In this case A and B are the closest seek points
|
||||
* before and after the requested time. A single seek point is returned in all other cases.
|
||||
*
|
||||
* @param timeUs A seek time in microseconds.
|
||||
* @return The corresponding seek points.
|
||||
*/
|
||||
long getPosition(long timeUs);
|
||||
|
||||
SeekPoints getSeekPoints(long timeUs);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (C) 2017 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;
|
||||
|
||||
/** Defines a seek point in a media stream. */
|
||||
public final class SeekPoint {
|
||||
|
||||
/** A {@link SeekPoint} whose time and byte offset are both set to 0. */
|
||||
public static final SeekPoint START = new SeekPoint(0, 0);
|
||||
|
||||
/** The time of the seek point, in microseconds. */
|
||||
public final long timeUs;
|
||||
|
||||
/** The byte offset of the seek point. */
|
||||
public final long position;
|
||||
|
||||
/**
|
||||
* @param timeUs The time of the seek point, in microseconds.
|
||||
* @param position The byte offset of the seek point.
|
||||
*/
|
||||
public SeekPoint(long timeUs, long position) {
|
||||
this.timeUs = timeUs;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[timeUs=" + timeUs + ", position=" + position + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SeekPoint other = (SeekPoint) obj;
|
||||
return timeUs == other.timeUs && position == other.position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (int) timeUs;
|
||||
result = 31 * result + (int) position;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.mp3;
|
|||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/**
|
||||
|
|
@ -57,16 +58,25 @@ import com.google.android.exoplayer2.util.Util;
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
if (dataSize == C.LENGTH_UNSET) {
|
||||
return firstFramePosition;
|
||||
return new SeekPoints(new SeekPoint(0, firstFramePosition));
|
||||
}
|
||||
long positionOffset = (timeUs * bitrate) / (C.MICROS_PER_SECOND * BITS_PER_BYTE);
|
||||
// Constrain to nearest preceding frame offset.
|
||||
positionOffset = (positionOffset / frameSize) * frameSize;
|
||||
positionOffset = Util.constrainValue(positionOffset, 0, dataSize - frameSize);
|
||||
// Add data start position.
|
||||
return firstFramePosition + positionOffset;
|
||||
long seekPosition = firstFramePosition + positionOffset;
|
||||
long seekTimeUs = getTimeUs(seekPosition);
|
||||
SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekPosition);
|
||||
if (seekTimeUs >= timeUs || positionOffset == dataSize - frameSize) {
|
||||
return new SeekPoints(seekPoint);
|
||||
} else {
|
||||
long secondSeekPosition = seekPosition + frameSize;
|
||||
long secondSeekTimeUs = getTimeUs(secondSeekPosition);
|
||||
SeekPoint secondSeekPoint = new SeekPoint(secondSeekTimeUs, secondSeekPosition);
|
||||
return new SeekPoints(seekPoint, secondSeekPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.mp3;
|
|||
import android.util.Log;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
|
|
@ -106,8 +107,15 @@ import com.google.android.exoplayer2.util.Util;
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
return positions[Util.binarySearchFloor(timesUs, timeUs, true, true)];
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
int tableIndex = Util.binarySearchFloor(timesUs, timeUs, true, true);
|
||||
SeekPoint seekPoint = new SeekPoint(timesUs[tableIndex], positions[tableIndex]);
|
||||
if (seekPoint.timeUs >= timeUs || tableIndex == timesUs.length - 1) {
|
||||
return new SeekPoints(seekPoint);
|
||||
} else {
|
||||
SeekPoint nextSeekPoint = new SeekPoint(timesUs[tableIndex + 1], positions[tableIndex + 1]);
|
||||
return new SeekPoints(seekPoint, nextSeekPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.mp3;
|
|||
import android.util.Log;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
|
|
@ -107,10 +108,11 @@ import com.google.android.exoplayer2.util.Util;
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
if (!isSeekable()) {
|
||||
return dataStartPosition + xingFrameSize;
|
||||
return new SeekPoints(new SeekPoint(0, dataStartPosition + xingFrameSize));
|
||||
}
|
||||
timeUs = Util.constrainValue(timeUs, 0, durationUs);
|
||||
double percent = (timeUs * 100d) / durationUs;
|
||||
double scaledPosition;
|
||||
if (percent <= 0) {
|
||||
|
|
@ -129,7 +131,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||
long positionOffset = Math.round((scaledPosition / 256) * dataSize);
|
||||
// Ensure returned positions skip the frame containing the XING header.
|
||||
positionOffset = Util.constrainValue(positionOffset, xingFrameSize, dataSize - 1);
|
||||
return dataStartPosition + positionOffset;
|
||||
return new SeekPoints(new SeekPoint(timeUs, dataStartPosition + positionOffset));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
|||
import com.google.android.exoplayer2.extractor.GaplessInfoHolder;
|
||||
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||
import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
|
|
@ -108,6 +109,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||
// Extractor outputs.
|
||||
private ExtractorOutput extractorOutput;
|
||||
private Mp4Track[] tracks;
|
||||
private int firstVideoTrackIndex;
|
||||
private long durationUs;
|
||||
private boolean isQuickTime;
|
||||
|
||||
|
|
@ -196,21 +198,56 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
long earliestSamplePosition = Long.MAX_VALUE;
|
||||
for (Mp4Track track : tracks) {
|
||||
TrackSampleTable sampleTable = track.sampleTable;
|
||||
int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs);
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
if (tracks.length == 0) {
|
||||
return new SeekPoints(SeekPoint.START);
|
||||
}
|
||||
|
||||
long firstTimeUs;
|
||||
long firstOffset;
|
||||
long secondTimeUs = C.TIME_UNSET;
|
||||
long secondOffset = C.POSITION_UNSET;
|
||||
|
||||
// If we have a video track, use it to establish one or two seek points.
|
||||
if (firstVideoTrackIndex != C.INDEX_UNSET) {
|
||||
TrackSampleTable sampleTable = tracks[firstVideoTrackIndex].sampleTable;
|
||||
int sampleIndex = getSynchronizationSampleIndex(sampleTable, timeUs);
|
||||
if (sampleIndex == C.INDEX_UNSET) {
|
||||
// Handle the case where the requested time is before the first synchronization sample.
|
||||
sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs);
|
||||
return new SeekPoints(SeekPoint.START);
|
||||
}
|
||||
long offset = sampleTable.offsets[sampleIndex];
|
||||
if (offset < earliestSamplePosition) {
|
||||
earliestSamplePosition = offset;
|
||||
long sampleTimeUs = sampleTable.timestampsUs[sampleIndex];
|
||||
firstTimeUs = sampleTimeUs;
|
||||
firstOffset = sampleTable.offsets[sampleIndex];
|
||||
if (sampleTimeUs < timeUs && sampleIndex < sampleTable.sampleCount - 1) {
|
||||
int secondSampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs);
|
||||
if (secondSampleIndex != C.INDEX_UNSET && secondSampleIndex != sampleIndex) {
|
||||
secondTimeUs = sampleTable.timestampsUs[secondSampleIndex];
|
||||
secondOffset = sampleTable.offsets[secondSampleIndex];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
firstTimeUs = timeUs;
|
||||
firstOffset = Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
// Take into account other tracks.
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
if (i != firstVideoTrackIndex) {
|
||||
TrackSampleTable sampleTable = tracks[i].sampleTable;
|
||||
firstOffset = maybeAdjustSeekOffset(sampleTable, firstTimeUs, firstOffset);
|
||||
if (secondTimeUs != C.TIME_UNSET) {
|
||||
secondOffset = maybeAdjustSeekOffset(sampleTable, secondTimeUs, secondOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return earliestSamplePosition;
|
||||
|
||||
SeekPoint firstSeekPoint = new SeekPoint(firstTimeUs, firstOffset);
|
||||
if (secondTimeUs == C.TIME_UNSET) {
|
||||
return new SeekPoints(firstSeekPoint);
|
||||
} else {
|
||||
SeekPoint secondSeekPoint = new SeekPoint(secondTimeUs, secondOffset);
|
||||
return new SeekPoints(firstSeekPoint, secondSeekPoint);
|
||||
}
|
||||
}
|
||||
|
||||
// Private methods.
|
||||
|
|
@ -326,31 +363,11 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an ftyp atom to determine whether the media is QuickTime.
|
||||
*
|
||||
* @param atomData The ftyp atom data.
|
||||
* @return Whether the media is QuickTime.
|
||||
*/
|
||||
private static boolean processFtypAtom(ParsableByteArray atomData) {
|
||||
atomData.setPosition(Atom.HEADER_SIZE);
|
||||
int majorBrand = atomData.readInt();
|
||||
if (majorBrand == BRAND_QUICKTIME) {
|
||||
return true;
|
||||
}
|
||||
atomData.skipBytes(4); // minor_version
|
||||
while (atomData.bytesLeft() > 0) {
|
||||
if (atomData.readInt() == BRAND_QUICKTIME) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the stored track metadata to reflect the contents of the specified moov atom.
|
||||
*/
|
||||
private void processMoovAtom(ContainerAtom moov) throws ParserException {
|
||||
int firstVideoTrackIndex = C.INDEX_UNSET;
|
||||
long durationUs = C.TIME_UNSET;
|
||||
List<Mp4Track> tracks = new ArrayList<>();
|
||||
long earliestSampleOffset = Long.MAX_VALUE;
|
||||
|
|
@ -402,6 +419,9 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||
mp4Track.trackOutput.format(format);
|
||||
|
||||
durationUs = Math.max(durationUs, track.durationUs);
|
||||
if (track.type == C.TRACK_TYPE_VIDEO && firstVideoTrackIndex == C.INDEX_UNSET) {
|
||||
firstVideoTrackIndex = tracks.size();
|
||||
}
|
||||
tracks.add(mp4Track);
|
||||
|
||||
long firstSampleOffset = trackSampleTable.offsets[0];
|
||||
|
|
@ -409,8 +429,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||
earliestSampleOffset = firstSampleOffset;
|
||||
}
|
||||
}
|
||||
this.firstVideoTrackIndex = firstVideoTrackIndex;
|
||||
this.durationUs = durationUs;
|
||||
this.tracks = tracks.toArray(new Mp4Track[tracks.size()]);
|
||||
|
||||
extractorOutput.endTracks();
|
||||
extractorOutput.seekMap(this);
|
||||
}
|
||||
|
|
@ -538,6 +560,66 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts a seek point offset to take into account the track with the given {@code sampleTable},
|
||||
* for a given {@code seekTimeUs}.
|
||||
*
|
||||
* @param sampleTable The sample table to use.
|
||||
* @param seekTimeUs The seek time in microseconds.
|
||||
* @param offset The current offset.
|
||||
* @return The adjusted offset.
|
||||
*/
|
||||
private static long maybeAdjustSeekOffset(
|
||||
TrackSampleTable sampleTable, long seekTimeUs, long offset) {
|
||||
int sampleIndex = getSynchronizationSampleIndex(sampleTable, seekTimeUs);
|
||||
if (sampleIndex == C.INDEX_UNSET) {
|
||||
return offset;
|
||||
}
|
||||
long sampleOffset = sampleTable.offsets[sampleIndex];
|
||||
return Math.min(sampleOffset, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the synchronization sample before or at {@code timeUs}, or the index of
|
||||
* the first synchronization sample if located after {@code timeUs}, or {@link C#INDEX_UNSET} if
|
||||
* there are no synchronization samples in the table.
|
||||
*
|
||||
* @param sampleTable The sample table in which to locate a synchronization sample.
|
||||
* @param timeUs A time in microseconds.
|
||||
* @return The index of the synchronization sample before or at {@code timeUs}, or the index of
|
||||
* the first synchronization sample if located after {@code timeUs}, or {@link C#INDEX_UNSET}
|
||||
* if there are no synchronization samples in the table.
|
||||
*/
|
||||
private static int getSynchronizationSampleIndex(TrackSampleTable sampleTable, long timeUs) {
|
||||
int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs);
|
||||
if (sampleIndex == C.INDEX_UNSET) {
|
||||
// Handle the case where the requested time is before the first synchronization sample.
|
||||
sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs);
|
||||
}
|
||||
return sampleIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an ftyp atom to determine whether the media is QuickTime.
|
||||
*
|
||||
* @param atomData The ftyp atom data.
|
||||
* @return Whether the media is QuickTime.
|
||||
*/
|
||||
private static boolean processFtypAtom(ParsableByteArray atomData) {
|
||||
atomData.setPosition(Atom.HEADER_SIZE);
|
||||
int majorBrand = atomData.readInt();
|
||||
if (majorBrand == BRAND_QUICKTIME) {
|
||||
return true;
|
||||
}
|
||||
atomData.skipBytes(4); // minor_version
|
||||
while (atomData.bytesLeft() > 0) {
|
||||
if (atomData.readInt() == BRAND_QUICKTIME) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the extractor should decode a leaf atom with type {@code atom}.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.ogg;
|
|||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
|
|
@ -219,12 +220,13 @@ import java.io.IOException;
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
if (timeUs == 0) {
|
||||
return startPosition;
|
||||
return new SeekPoints(new SeekPoint(0, startPosition));
|
||||
}
|
||||
long granule = streamReader.convertTimeToGranule(timeUs);
|
||||
return getEstimatedPosition(startPosition, granule, DEFAULT_OFFSET);
|
||||
long estimatedPosition = getEstimatedPosition(startPosition, granule, DEFAULT_OFFSET);
|
||||
return new SeekPoints(new SeekPoint(timeUs, estimatedPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.ogg;
|
|||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.util.FlacStreamInfo;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
|
|
@ -192,10 +193,20 @@ import java.util.List;
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
long granule = convertTimeToGranule(timeUs);
|
||||
int index = Util.binarySearchFloor(seekPointGranules, granule, true, true);
|
||||
return firstFrameOffset + seekPointOffsets[index];
|
||||
long seekTimeUs = convertGranuleToTime(seekPointGranules[index]);
|
||||
long seekPosition = firstFrameOffset + seekPointOffsets[index];
|
||||
SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekPosition);
|
||||
if (seekTimeUs >= timeUs || index == seekPointGranules.length - 1) {
|
||||
return new SeekPoints(seekPoint);
|
||||
} else {
|
||||
long secondSeekTimeUs = convertGranuleToTime(seekPointGranules[index + 1]);
|
||||
long secondSeekPosition = firstFrameOffset + seekPointOffsets[index + 1];
|
||||
SeekPoint secondSeekPoint = new SeekPoint(secondSeekTimeUs, secondSeekPosition);
|
||||
return new SeekPoints(seekPoint, secondSeekPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.wav;
|
|||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/** Header for a WAV file. */
|
||||
|
|
@ -83,13 +84,22 @@ import com.google.android.exoplayer2.util.Util;
|
|||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
long positionOffset = (timeUs * averageBytesPerSecond) / C.MICROS_PER_SECOND;
|
||||
// Constrain to nearest preceding frame offset.
|
||||
positionOffset = (positionOffset / blockAlignment) * blockAlignment;
|
||||
positionOffset = Util.constrainValue(positionOffset, 0, dataSize - blockAlignment);
|
||||
// Add data start position.
|
||||
return dataStartPosition + positionOffset;
|
||||
long seekPosition = dataStartPosition + positionOffset;
|
||||
long seekTimeUs = getTimeUs(seekPosition);
|
||||
SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekPosition);
|
||||
if (seekTimeUs >= timeUs || positionOffset == dataSize - blockAlignment) {
|
||||
return new SeekPoints(seekPoint);
|
||||
} else {
|
||||
long secondSeekPosition = seekPosition + blockAlignment;
|
||||
long secondSeekTimeUs = getTimeUs(secondSeekPosition);
|
||||
SeekPoint secondSeekPoint = new SeekPoint(secondSeekTimeUs, secondSeekPosition);
|
||||
return new SeekPoints(seekPoint, secondSeekPoint);
|
||||
}
|
||||
}
|
||||
|
||||
// Misc getters.
|
||||
|
|
@ -100,7 +110,8 @@ import com.google.android.exoplayer2.util.Util;
|
|||
* @param position The position in bytes.
|
||||
*/
|
||||
public long getTimeUs(long position) {
|
||||
return position * C.MICROS_PER_SECOND / averageBytesPerSecond;
|
||||
long positionOffset = Math.max(0, position - dataStartPosition);
|
||||
return (positionOffset * C.MICROS_PER_SECOND) / averageBytesPerSecond;
|
||||
}
|
||||
|
||||
/** Returns the bytes per frame of this WAV. */
|
||||
|
|
|
|||
|
|
@ -549,7 +549,8 @@ import java.util.Arrays;
|
|||
pendingResetPositionUs = C.TIME_UNSET;
|
||||
return;
|
||||
}
|
||||
loadable.setLoadPosition(seekMap.getPosition(pendingResetPositionUs), pendingResetPositionUs);
|
||||
loadable.setLoadPosition(
|
||||
seekMap.getSeekPoints(pendingResetPositionUs).first.position, pendingResetPositionUs);
|
||||
pendingResetPositionUs = C.TIME_UNSET;
|
||||
}
|
||||
extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
|
|||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||
import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import org.junit.Before;
|
||||
|
|
@ -92,27 +94,39 @@ public final class XingSeekerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testGetPositionAtStartOfStream() {
|
||||
assertThat(seeker.getPosition(0)).isEqualTo(XING_FRAME_POSITION + xingFrameSize);
|
||||
assertThat(seekerWithInputLength.getPosition(0)).isEqualTo(XING_FRAME_POSITION + xingFrameSize);
|
||||
public void testGetSeekPointsAtStartOfStream() {
|
||||
SeekPoints seekPoints = seeker.getSeekPoints(0);
|
||||
SeekPoint seekPoint = seekPoints.first;
|
||||
assertThat(seekPoint).isEqualTo(seekPoints.second);
|
||||
assertThat(seekPoint.timeUs).isEqualTo(0);
|
||||
assertThat(seekPoint.position).isEqualTo(XING_FRAME_POSITION + xingFrameSize);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPositionAtEndOfStream() {
|
||||
assertThat(seeker.getPosition(STREAM_DURATION_US))
|
||||
.isEqualTo(STREAM_LENGTH - 1);
|
||||
assertThat(seekerWithInputLength.getPosition(STREAM_DURATION_US))
|
||||
.isEqualTo(STREAM_LENGTH - 1);
|
||||
public void testGetSeekPointsAtEndOfStream() {
|
||||
SeekPoints seekPoints = seeker.getSeekPoints(STREAM_DURATION_US);
|
||||
SeekPoint seekPoint = seekPoints.first;
|
||||
assertThat(seekPoint).isEqualTo(seekPoints.second);
|
||||
assertThat(seekPoint.timeUs).isEqualTo(STREAM_DURATION_US);
|
||||
assertThat(seekPoint.position).isEqualTo(STREAM_LENGTH - 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTimeForAllPositions() {
|
||||
for (int offset = xingFrameSize; offset < DATA_SIZE_BYTES; offset++) {
|
||||
int position = XING_FRAME_POSITION + offset;
|
||||
// Test seeker.
|
||||
long timeUs = seeker.getTimeUs(position);
|
||||
assertThat(seeker.getPosition(timeUs)).isEqualTo(position);
|
||||
SeekPoints seekPoints = seeker.getSeekPoints(timeUs);
|
||||
SeekPoint seekPoint = seekPoints.first;
|
||||
assertThat(seekPoint).isEqualTo(seekPoints.second);
|
||||
assertThat(seekPoint.position).isEqualTo(position);
|
||||
// Test seekerWithInputLength.
|
||||
timeUs = seekerWithInputLength.getTimeUs(position);
|
||||
assertThat(seekerWithInputLength.getPosition(timeUs)).isEqualTo(position);
|
||||
seekPoints = seekerWithInputLength.getSeekPoints(timeUs);
|
||||
seekPoint = seekPoints.first;
|
||||
assertThat(seekPoint).isEqualTo(seekPoints.second);
|
||||
assertThat(seekPoint.position).isEqualTo(position);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ public final class ExtractorAsserts {
|
|||
long durationUs = seekMap.getDurationUs();
|
||||
for (int j = 0; j < 4; j++) {
|
||||
long timeUs = (durationUs * j) / 3;
|
||||
long position = seekMap.getPosition(timeUs);
|
||||
long position = seekMap.getSeekPoints(timeUs).first.position;
|
||||
input.setPosition((int) position);
|
||||
for (int i = 0; i < extractorOutput.numberOfTracks; i++) {
|
||||
extractorOutput.trackOutputs.valueAt(i).clear();
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpab
|
|||
Assert.assertNotNull(seekMap);
|
||||
Assert.assertEquals(expected.seekMap.getClass(), seekMap.getClass());
|
||||
Assert.assertEquals(expected.seekMap.isSeekable(), seekMap.isSeekable());
|
||||
Assert.assertEquals(expected.seekMap.getPosition(0), seekMap.getPosition(0));
|
||||
Assert.assertEquals(expected.seekMap.getSeekPoints(0), seekMap.getSeekPoints(0));
|
||||
}
|
||||
for (int i = 0; i < numberOfTracks; i++) {
|
||||
Assert.assertEquals(expected.trackOutputs.keyAt(i), trackOutputs.keyAt(i));
|
||||
|
|
@ -114,10 +114,11 @@ public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpab
|
|||
@Override
|
||||
public void dump(Dumper dumper) {
|
||||
if (seekMap != null) {
|
||||
dumper.startBlock("seekMap")
|
||||
dumper
|
||||
.startBlock("seekMap")
|
||||
.add("isSeekable", seekMap.isSeekable())
|
||||
.addTime("duration", seekMap.getDurationUs())
|
||||
.add("getPosition(0)", seekMap.getPosition(0))
|
||||
.add("getPosition(0)", seekMap.getSeekPoints(0))
|
||||
.endBlock();
|
||||
}
|
||||
dumper.add("numberOfTracks", numberOfTracks);
|
||||
|
|
|
|||
Loading…
Reference in a new issue