mirror of https://github.com/procxx/kepka.git
Implement precise seek in streaming.
This commit is contained in:
parent
44c562d8ba
commit
3e9b811875
|
@ -302,6 +302,7 @@ void StartStreaming(
|
||||||
static auto options = Media::Streaming::PlaybackOptions();
|
static auto options = Media::Streaming::PlaybackOptions();
|
||||||
static auto speed = 1.;
|
static auto speed = 1.;
|
||||||
static auto step = pow(2., 1. / 12);
|
static auto step = pow(2., 1. / 12);
|
||||||
|
static auto frame = QImage();
|
||||||
|
|
||||||
class Panel
|
class Panel
|
||||||
#if defined Q_OS_MAC && !defined OS_MAC_OLD
|
#if defined Q_OS_MAC && !defined OS_MAC_OLD
|
||||||
|
@ -339,6 +340,9 @@ void StartStreaming(
|
||||||
player->pause();
|
player->pause();
|
||||||
}
|
}
|
||||||
void mouseReleaseEvent(QMouseEvent *e) override {
|
void mouseReleaseEvent(QMouseEvent *e) override {
|
||||||
|
if (player->ready()) {
|
||||||
|
frame = player->frame({});
|
||||||
|
}
|
||||||
preloaded = position = options.position = std::clamp(
|
preloaded = position = options.position = std::clamp(
|
||||||
(duration * e->pos().x()) / width(),
|
(duration * e->pos().x()) / width(),
|
||||||
crl::time(0),
|
crl::time(0),
|
||||||
|
@ -364,6 +368,7 @@ void StartStreaming(
|
||||||
options.speed = speed;
|
options.speed = speed;
|
||||||
//options.syncVideoByAudio = false;
|
//options.syncVideoByAudio = false;
|
||||||
preloaded = position = options.position = 0;
|
preloaded = position = options.position = 0;
|
||||||
|
frame = QImage();
|
||||||
player->play(options);
|
player->play(options);
|
||||||
player->updates(
|
player->updates(
|
||||||
) | rpl::start_with_next_error_done([=](Update &&update) {
|
) | rpl::start_with_next_error_done([=](Update &&update) {
|
||||||
|
@ -391,7 +396,11 @@ void StartStreaming(
|
||||||
if (player->ready()) {
|
if (player->ready()) {
|
||||||
Painter(video.get()).drawImage(
|
Painter(video.get()).drawImage(
|
||||||
video->rect(),
|
video->rect(),
|
||||||
player->frame(FrameRequest()));
|
player->frame({}));
|
||||||
|
} else if (!frame.isNull()) {
|
||||||
|
Painter(video.get()).drawImage(
|
||||||
|
video->rect(),
|
||||||
|
frame);
|
||||||
} else {
|
} else {
|
||||||
Painter(video.get()).fillRect(
|
Painter(video.get()).fillRect(
|
||||||
rect,
|
rect,
|
||||||
|
|
|
@ -61,29 +61,51 @@ bool AudioTrack::initialized() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioTrack::tryReadFirstFrame(Packet &&packet) {
|
bool AudioTrack::tryReadFirstFrame(Packet &&packet) {
|
||||||
// #TODO streaming fix seek to the end.
|
|
||||||
if (ProcessPacket(_stream, std::move(packet)).failed()) {
|
if (ProcessPacket(_stream, std::move(packet)).failed()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (const auto error = ReadNextFrame(_stream)) {
|
if (const auto error = ReadNextFrame(_stream)) {
|
||||||
if (error.code() == AVERROR_EOF) {
|
if (error.code() == AVERROR_EOF) {
|
||||||
// #TODO streaming fix seek to the end.
|
if (!_initialSkippingFrame) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
// Return the last valid frame if we seek too far.
|
||||||
|
_stream.frame = std::move(_initialSkippingFrame);
|
||||||
|
return processFirstFrame();
|
||||||
} else if (error.code() != AVERROR(EAGAIN) || _noMoreData) {
|
} else if (error.code() != AVERROR(EAGAIN) || _noMoreData) {
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
// Waiting for more packets.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} else if (!fillStateFromFrame()) {
|
} else if (!fillStateFromFrame()) {
|
||||||
return false;
|
return false;
|
||||||
|
} else if (_startedPosition < _options.position) {
|
||||||
|
// Seek was with AVSEEK_FLAG_BACKWARD so first we get old frames.
|
||||||
|
// Try skipping frames until one is after the requested position.
|
||||||
|
std::swap(_initialSkippingFrame, _stream.frame);
|
||||||
|
if (!_stream.frame) {
|
||||||
|
_stream.frame = MakeFramePointer();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return processFirstFrame();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AudioTrack::processFirstFrame() {
|
||||||
mixerInit();
|
mixerInit();
|
||||||
callReady();
|
callReady();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioTrack::fillStateFromFrame() {
|
bool AudioTrack::fillStateFromFrame() {
|
||||||
_startedPosition = FramePosition(_stream);
|
const auto position = FramePosition(_stream);
|
||||||
return (_startedPosition != kTimeUnknown);
|
if (position == kTimeUnknown) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_startedPosition = position;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioTrack::mixerInit() {
|
void AudioTrack::mixerInit() {
|
||||||
|
|
|
@ -52,6 +52,7 @@ private:
|
||||||
[[nodiscard]] bool initialized() const;
|
[[nodiscard]] bool initialized() const;
|
||||||
[[nodiscard]] bool tryReadFirstFrame(Packet &&packet);
|
[[nodiscard]] bool tryReadFirstFrame(Packet &&packet);
|
||||||
[[nodiscard]] bool fillStateFromFrame();
|
[[nodiscard]] bool fillStateFromFrame();
|
||||||
|
[[nodiscard]] bool processFirstFrame();
|
||||||
void mixerInit();
|
void mixerInit();
|
||||||
void mixerEnqueue(Packet &&packet);
|
void mixerEnqueue(Packet &&packet);
|
||||||
void mixerForceToBuffer();
|
void mixerForceToBuffer();
|
||||||
|
@ -78,6 +79,9 @@ private:
|
||||||
// After that accessed from the main thread.
|
// After that accessed from the main thread.
|
||||||
rpl::variable<crl::time> _playPosition;
|
rpl::variable<crl::time> _playPosition;
|
||||||
|
|
||||||
|
// For initial frame skipping for an exact seek.
|
||||||
|
FramePointer _initialSkippingFrame;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Streaming
|
} // namespace Streaming
|
||||||
|
|
|
@ -110,15 +110,8 @@ Stream File::Context::initStream(AVMediaType type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto info = _formatContext->streams[index];
|
const auto info = _formatContext->streams[index];
|
||||||
result.codec = MakeCodecPointer(info);
|
|
||||||
if (!result.codec) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == AVMEDIA_TYPE_VIDEO) {
|
if (type == AVMEDIA_TYPE_VIDEO) {
|
||||||
const auto codec = result.codec.get();
|
|
||||||
result.rotation = ReadRotationFromMetadata(info);
|
result.rotation = ReadRotationFromMetadata(info);
|
||||||
result.dimensions = QSize(codec->width, codec->height);
|
|
||||||
} else if (type == AVMEDIA_TYPE_AUDIO) {
|
} else if (type == AVMEDIA_TYPE_AUDIO) {
|
||||||
result.frequency = info->codecpar->sample_rate;
|
result.frequency = info->codecpar->sample_rate;
|
||||||
if (!result.frequency) {
|
if (!result.frequency) {
|
||||||
|
@ -126,6 +119,11 @@ Stream File::Context::initStream(AVMediaType type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.codec = MakeCodecPointer(info);
|
||||||
|
if (!result.codec) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
result.frame = MakeFramePointer();
|
result.frame = MakeFramePointer();
|
||||||
if (!result.frame) {
|
if (!result.frame) {
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -154,7 +154,6 @@ struct Stream {
|
||||||
|
|
||||||
// Video only.
|
// Video only.
|
||||||
int rotation = 0;
|
int rotation = 0;
|
||||||
QSize dimensions;
|
|
||||||
SwsContextPointer swsContext;
|
SwsContextPointer swsContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -47,10 +47,7 @@ private:
|
||||||
[[nodiscard]] bool interrupted() const;
|
[[nodiscard]] bool interrupted() const;
|
||||||
[[nodiscard]] bool tryReadFirstFrame(Packet &&packet);
|
[[nodiscard]] bool tryReadFirstFrame(Packet &&packet);
|
||||||
[[nodiscard]] bool fillStateFromFrame();
|
[[nodiscard]] bool fillStateFromFrame();
|
||||||
[[nodiscard]] bool fillStateFromFakeLastFrame();
|
[[nodiscard]] bool processFirstFrame();
|
||||||
[[nodiscard]] bool fillStateFromFrameTime(crl::time frameTime);
|
|
||||||
[[nodiscard]] QImage createFakeLastFrame() const;
|
|
||||||
[[nodiscard]] bool processFirstFrame(QImage frame);
|
|
||||||
void queueReadFrames(crl::time delay = 0);
|
void queueReadFrames(crl::time delay = 0);
|
||||||
void readFrames();
|
void readFrames();
|
||||||
[[nodiscard]] bool readFrame(not_null<Frame*> frame);
|
[[nodiscard]] bool readFrame(not_null<Frame*> frame);
|
||||||
|
@ -83,6 +80,9 @@ private:
|
||||||
bool _queued = false;
|
bool _queued = false;
|
||||||
base::ConcurrentTimer _readFramesTimer;
|
base::ConcurrentTimer _readFramesTimer;
|
||||||
|
|
||||||
|
// For initial frame skipping for an exact seek.
|
||||||
|
FramePointer _initialSkippingFrame;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
VideoTrackObject::VideoTrackObject(
|
VideoTrackObject::VideoTrackObject(
|
||||||
|
@ -259,10 +259,12 @@ bool VideoTrackObject::tryReadFirstFrame(Packet &&packet) {
|
||||||
auto frame = QImage();
|
auto frame = QImage();
|
||||||
if (const auto error = ReadNextFrame(_stream)) {
|
if (const auto error = ReadNextFrame(_stream)) {
|
||||||
if (error.code() == AVERROR_EOF) {
|
if (error.code() == AVERROR_EOF) {
|
||||||
if (!fillStateFromFakeLastFrame()) {
|
if (!_initialSkippingFrame) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return processFirstFrame(createFakeLastFrame());
|
// Return the last valid frame if we seek too far.
|
||||||
|
_stream.frame = std::move(_initialSkippingFrame);
|
||||||
|
return processFirstFrame();
|
||||||
} else if (error.code() != AVERROR(EAGAIN) || _noMoreData) {
|
} else if (error.code() != AVERROR(EAGAIN) || _noMoreData) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -271,22 +273,21 @@ bool VideoTrackObject::tryReadFirstFrame(Packet &&packet) {
|
||||||
}
|
}
|
||||||
} else if (!fillStateFromFrame()) {
|
} else if (!fillStateFromFrame()) {
|
||||||
return false;
|
return false;
|
||||||
|
} else if (_syncTimePoint.trackTime < _options.position) {
|
||||||
|
// Seek was with AVSEEK_FLAG_BACKWARD so first we get old frames.
|
||||||
|
// Try skipping frames until one is after the requested position.
|
||||||
|
std::swap(_initialSkippingFrame, _stream.frame);
|
||||||
|
if (!_stream.frame) {
|
||||||
|
_stream.frame = MakeFramePointer();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return processFirstFrame();
|
||||||
}
|
}
|
||||||
return processFirstFrame(ConvertFrame(_stream, QSize(), QImage()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage VideoTrackObject::createFakeLastFrame() const {
|
bool VideoTrackObject::processFirstFrame() {
|
||||||
if (_stream.dimensions.isEmpty()) {
|
auto frame = ConvertFrame(_stream, QSize(), QImage());
|
||||||
LOG(("Streaming Error: Can't seek to the end of the video "
|
|
||||||
"in case the codec doesn't provide valid dimensions."));
|
|
||||||
return QImage();
|
|
||||||
}
|
|
||||||
auto result = CreateImageForOriginalFrame(_stream.dimensions);
|
|
||||||
result.fill(Qt::black);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VideoTrackObject::processFirstFrame(QImage frame) {
|
|
||||||
if (frame.isNull()) {
|
if (frame.isNull()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -312,20 +313,11 @@ crl::time VideoTrackObject::currentFramePosition() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoTrackObject::fillStateFromFrame() {
|
bool VideoTrackObject::fillStateFromFrame() {
|
||||||
return fillStateFromFrameTime(currentFramePosition());
|
const auto position = currentFramePosition();
|
||||||
}
|
if (position == kTimeUnknown) {
|
||||||
|
|
||||||
bool VideoTrackObject::fillStateFromFakeLastFrame() {
|
|
||||||
return fillStateFromFrameTime(_stream.duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VideoTrackObject::fillStateFromFrameTime(crl::time frameTime) {
|
|
||||||
Expects(_syncTimePoint.trackTime == kTimeUnknown);
|
|
||||||
|
|
||||||
if (frameTime == kTimeUnknown) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_syncTimePoint.trackTime = frameTime;
|
_syncTimePoint.trackTime = position;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue