Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/guide/media/media-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ Duration is `0` for a source whose container does not say how long it runs, whic
source.play(); // start, or resume after a pause
source.pause(); // hold playback where it is
source.seek(30_000_000); // move to 30 seconds
source.fastSeek(30_000_000); // move to the keyframe at or before 30 seconds
source.setLooping(true); // start over instead of ending
source.getPositionUs(); // where playback has got to
source.getState(); // IDLE, PLAYING, PAUSED, ENDED or CLOSED
Expand All @@ -146,6 +147,8 @@ Looping keeps the timing across the seam, so a looping file works as a stand-in

A seek starts playback at the position asked for. The decoder has to go back to the keyframe at or before it to produce a picture at all, so the frames from there up to the position are decoded and dropped, and the audio is cut at it. How long a seek takes therefore depends on how often the media was encoded with keyframes, not on how long the media is: MKV and MP4 have an index that takes the reader straight to that keyframe. MPEG-TS has none, and a search for the position by its timestamps can land past the keyframe before it. The player then searches again further back, a second before the position, then two, four and so on, so that the seek still starts at the position.

`fastSeek` moves to that keyframe and plays from there, without decoding up to the position, so it takes no longer however far the keyframes are apart. That suits a slider being dragged, as browsers' `fastSeek()` does: fast seeks while it moves, and a `seek` once it is let go. In a container without an index, such as MPEG-TS, the keyframe can be the one after the position.

## Following Playback

```java
Expand Down
4 changes: 2 additions & 2 deletions webrtc-media/src/main/cpp/include/JNI_MediaPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ extern "C" {
/*
* Class: dev_onvoid_webrtc_media_player_MediaPlayer
* Method: seek
* Signature: (JJ)V
* Signature: (JJZ)V
*/
JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_player_MediaPlayer_seek
(JNIEnv *, jclass, jlong, jlong);
(JNIEnv *, jclass, jlong, jlong, jboolean);

/*
* Class: dev_onvoid_webrtc_media_player_MediaPlayer
Expand Down
21 changes: 13 additions & 8 deletions webrtc-media/src/main/cpp/include/media/MediaPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,10 @@ namespace ffmpeg
void Pause();

// Moves playback to the given position, in microseconds from the
// start. What is already queued is dropped, and what is decoded
// between the keyframe the seek lands on and the position as well.
void Seek(int64_t position_us);
// start. What is already queued is dropped. A precise seek drops
// what is decoded between the keyframe it lands on and the
// position as well; one that is not plays from that keyframe.
void Seek(int64_t position_us, bool precise);

void SetLooping(bool looping);
bool IsLooping() const;
Expand Down Expand Up @@ -131,7 +132,7 @@ namespace ffmpeg
int DecodeVideo(bool at_end);
int DecodeAudio();

void PerformSeek(int64_t position_us);
void PerformSeek(int64_t position_us, bool precise);

// Moves the reader to the given time and starts decoding from
// there towards the position of the seek being carried out.
Expand All @@ -140,9 +141,10 @@ namespace ffmpeg

// Seeks again further back, after the video decoded since a seek
// started past its position: the reader landed past the keyframe
// before it. Returns whether it did; there is nothing further back
// to try once the reader would land where it did already, or the
// distance has grown past kMaxSeekBackUs.
// before it. Only a precise seek does: a fast one plays from the
// keyframe it lands on. Returns whether it did; there is nothing
// further back to try once the reader would land where it did
// already, or the distance has grown past kMaxSeekBackUs.
bool SeekFurtherBack();
void SetState(int state);

Expand Down Expand Up @@ -179,6 +181,7 @@ namespace ffmpeg

bool seek_pending_ = false;
int64_t seek_position_us_ = 0;
bool seek_precise_ = true;

// How far before the position a seek that landed too late looks
// next, doubling each time, and how far at most: past that, a
Expand All @@ -188,8 +191,10 @@ namespace ffmpeg
static constexpr int64_t kMaxSeekBackUs = 64000000;

// The seek being carried out, on the decode thread: its position,
// how far before it the reader was last sent, and where to.
// whether it is precise, how far before it the reader was last
// sent, and where to.
int64_t seek_target_us_ = 0;
bool seek_target_precise_ = true;
int64_t seek_back_us_ = 0;
int64_t seek_from_us_ = 0;

Expand Down
3 changes: 2 additions & 1 deletion webrtc-media/src/main/cpp/include/media/VideoDecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ namespace ffmpeg
// keyframe before the position, and the decoder had nothing to
// show until the next one. A container without an index, such as
// MPEG-TS, is searched by timestamp and can land that way.
// Reported once, by the call after that frame was taken.
// Reported once, by the call after that frame was taken, and
// forgotten by a flush.
bool LandedPastPosition();

// Hands a packet to the decoder, or null to start draining at the
Expand Down
4 changes: 2 additions & 2 deletions webrtc-media/src/main/cpp/src/JNI_MediaPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,11 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_player_MediaPlayer_suspend
}

JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_player_MediaPlayer_seek
(JNIEnv * env, jclass caller, jlong handle, jlong positionUs)
(JNIEnv * env, jclass caller, jlong handle, jlong positionUs, jboolean precise)
{
ffmpeg::CallFromJava(env, [&] {
if (handle != 0) {
PlayerOf(handle)->Seek(positionUs);
PlayerOf(handle)->Seek(positionUs, precise == JNI_TRUE);
}
});
}
Expand Down
28 changes: 20 additions & 8 deletions webrtc-media/src/main/cpp/src/media/MediaPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ namespace ffmpeg
ended_ = false;
seek_pending_ = true;
seek_position_us_ = 0;
seek_precise_ = true;
}

// Under the lock, together with the flag: the decode thread stops
Expand Down Expand Up @@ -158,7 +159,7 @@ namespace ffmpeg
}
}

void MediaPlayer::Seek(int64_t position_us)
void MediaPlayer::Seek(int64_t position_us, bool precise)
{
std::lock_guard<std::mutex> lock(mutex_);

Expand All @@ -168,6 +169,7 @@ namespace ffmpeg

seek_pending_ = true;
seek_position_us_ = position_us;
seek_precise_ = precise;
ended_ = false;

// The decode thread may be held up by a full queue, and it cannot act
Expand Down Expand Up @@ -273,6 +275,7 @@ namespace ffmpeg

for (;;) {
int64_t seek_to = 0;
bool seek_precise = true;
bool do_seek = false;

{
Expand All @@ -289,12 +292,13 @@ namespace ffmpeg
if (seek_pending_) {
seek_pending_ = false;
seek_to = seek_position_us_;
seek_precise = seek_precise_;
do_seek = true;
}
}

if (do_seek) {
PerformSeek(seek_to);
PerformSeek(seek_to, seek_precise);

continue;
}
Expand Down Expand Up @@ -509,9 +513,10 @@ namespace ffmpeg
}
}

void MediaPlayer::PerformSeek(int64_t position_us)
void MediaPlayer::PerformSeek(int64_t position_us, bool precise)
{
seek_target_us_ = position_us;
seek_target_precise_ = precise;
seek_back_us_ = 0;

int result = SeekReader(position_us);
Expand All @@ -523,6 +528,10 @@ namespace ffmpeg

bool MediaPlayer::SeekFurtherBack()
{
if (!seek_target_precise_) {
return false;
}

const int64_t back = seek_back_us_ == 0 ? kFirstSeekBackUs : seek_back_us_ * 2;
const int64_t from = std::max<int64_t>(seek_target_us_ - back, 0);

Expand Down Expand Up @@ -555,11 +564,14 @@ namespace ffmpeg
audio_decoder_.Flush();

// The reader lands on the keyframe at or before the position, which
// is where decoding has to begin. Playback begins at the position
// itself: what comes before it is decoded and dropped, and the audio
// frame it falls in is cut there.
video_decoder_.DiscardBefore(position_us);
audio_decoder_.DiscardBefore(position_us);
// is where decoding has to begin. A precise seek begins playback at
// the position itself: what comes before it is decoded and dropped,
// and the audio frame it falls in is cut there. Otherwise it begins
// at that keyframe, video and audio alike.
if (seek_target_precise_) {
video_decoder_.DiscardBefore(position_us);
audio_decoder_.DiscardBefore(position_us);
}

// A seek starts a timeline of its own: the pacer maps the first item
// that arrives onto the clock afresh, so the offset that kept looping
Expand Down
6 changes: 6 additions & 0 deletions webrtc-media/src/main/cpp/src/media/VideoDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,12 @@ namespace ffmpeg
awaiting_key_frame_ = false;
skipped_ = 0;
discarding_ = false;

// A late landing not asked about before the flush, as at the end of
// the stream, belongs to the position that was left too: taken after
// it, it would send the reader back from wherever decoding goes on.
dropped_before_ = false;
landed_past_ = false;
}

void VideoDecoder::DiscardBefore(int64_t timestamp_us)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,25 @@ public void pause() {
* Moves playback to the given position.
*
* @param positionUs The position in microseconds from the start.
*
* @see MediaPlayer#seek(long)
*/
public void seek(long positionUs) {
player.seek(positionUs);
}

/**
* Moves playback to the keyframe at or before the given position, which
* takes no longer however far the keyframes are apart.
*
* @param positionUs The position in microseconds from the start.
*
* @see MediaPlayer#fastSeek(long)
*/
public void fastSeek(long positionUs) {
player.fastSeek(positionUs);
}

/**
* Sets whether the source starts again when it runs out.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,34 @@ public void pause() {

/**
* Moves playback to the given position. Whatever has been decoded but not
* yet delivered is dropped.
* yet delivered is dropped. Video is decoded from the keyframe at or
* before the position, and what comes before the position is dropped, so
* the further the keyframes are apart, the longer this takes.
*
* @param positionUs The position in microseconds from the start.
*
* @see #fastSeek(long)
*/
public void seek(long positionUs) {
synchronized (lock) {
seek(handle, positionUs);
seek(handle, positionUs, true);
}
}

/**
* Moves playback to the keyframe at or before the given position, and
* plays from there. Nothing before the position is dropped, so this takes
* no longer however far the keyframes are apart, which is what a slider
* being dragged needs; a {@link #seek(long)} once it is let go lands on
* the position itself. Whatever has been decoded but not yet delivered is
* dropped. In a container without an index, such as MPEG-TS, the keyframe
* can be the one after the position.
*
* @param positionUs The position in microseconds from the start.
*/
public void fastSeek(long positionUs) {
synchronized (lock) {
seek(handle, positionUs, false);
}
}

Expand Down Expand Up @@ -350,7 +371,7 @@ private native long create(long readerHandle, long tableAddress,

private static native void suspend(long handle);

private static native void seek(long handle, long positionUs);
private static native void seek(long handle, long positionUs, boolean precise);

private static native void setLooping(long handle, boolean looping);

Expand Down
Loading
Loading