mirror of https://github.com/procxx/kepka.git
Provide receivedTill for streamed tracks.
This commit is contained in:
parent
8e44a7f5c4
commit
26ea6c4e63
|
@ -330,7 +330,7 @@ void StartStreaming(
|
||||||
player->updates(
|
player->updates(
|
||||||
) | rpl::start_with_next_error_done([=](Update &&update) {
|
) | rpl::start_with_next_error_done([=](Update &&update) {
|
||||||
update.data.match([&](Information &update) {
|
update.data.match([&](Information &update) {
|
||||||
if (!update.videoCover.isNull()) {
|
if (!update.video.cover.isNull()) {
|
||||||
video = base::make_unique_q<Panel>();
|
video = base::make_unique_q<Panel>();
|
||||||
video->setAttribute(Qt::WA_OpaquePaintEvent);
|
video->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
video->paintRequest(
|
video->paintRequest(
|
||||||
|
@ -340,8 +340,8 @@ void StartStreaming(
|
||||||
player->frame(FrameRequest()));
|
player->frame(FrameRequest()));
|
||||||
}, video->lifetime());
|
}, video->lifetime());
|
||||||
const auto size = QSize(
|
const auto size = QSize(
|
||||||
ConvertScale(update.videoSize.width()),
|
ConvertScale(update.video.size.width()),
|
||||||
ConvertScale(update.videoSize.height()));
|
ConvertScale(update.video.size.height()));
|
||||||
const auto center = App::wnd()->geometry().center();
|
const auto center = App::wnd()->geometry().center();
|
||||||
video->setGeometry(QRect(
|
video->setGeometry(QRect(
|
||||||
center - QPoint(size.width(), size.height()) / 2,
|
center - QPoint(size.width(), size.height()) / 2,
|
||||||
|
@ -355,10 +355,12 @@ void StartStreaming(
|
||||||
}, video->lifetime());
|
}, video->lifetime());
|
||||||
}
|
}
|
||||||
player->start();
|
player->start();
|
||||||
|
}, [&](PreloadedVideo &update) {
|
||||||
}, [&](UpdateVideo &update) {
|
}, [&](UpdateVideo &update) {
|
||||||
Expects(video != nullptr);
|
Expects(video != nullptr);
|
||||||
|
|
||||||
video->update();
|
video->update();
|
||||||
|
}, [&](PreloadedAudio &update) {
|
||||||
}, [&](UpdateAudio &update) {
|
}, [&](UpdateAudio &update) {
|
||||||
}, [&](WaitingForData &update) {
|
}, [&](WaitingForData &update) {
|
||||||
}, [&](MutedByOther &update) {
|
}, [&](MutedByOther &update) {
|
||||||
|
|
|
@ -36,6 +36,7 @@ AVRational AudioTrack::streamTimeBase() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioTrack::process(Packet &&packet) {
|
void AudioTrack::process(Packet &&packet) {
|
||||||
|
_noMoreData = packet.empty();
|
||||||
if (_audioMsgId.playId()) {
|
if (_audioMsgId.playId()) {
|
||||||
mixerEnqueue(std::move(packet));
|
mixerEnqueue(std::move(packet));
|
||||||
} else if (!tryReadFirstFrame(std::move(packet))) {
|
} else if (!tryReadFirstFrame(std::move(packet))) {
|
||||||
|
@ -45,14 +46,18 @@ void AudioTrack::process(Packet &&packet) {
|
||||||
|
|
||||||
bool AudioTrack::tryReadFirstFrame(Packet &&packet) {
|
bool AudioTrack::tryReadFirstFrame(Packet &&packet) {
|
||||||
// #TODO streaming fix seek to the end.
|
// #TODO streaming fix seek to the end.
|
||||||
const auto last = packet.empty();
|
|
||||||
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)) {
|
||||||
return !last && (error.code() == AVERROR(EAGAIN));
|
if (error.code() == AVERROR_EOF) {
|
||||||
}
|
// #TODO streaming fix seek to the end.
|
||||||
if (!fillStateFromFrame()) {
|
return false;
|
||||||
|
} else if (error.code() != AVERROR(EAGAIN) || _noMoreData) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (!fillStateFromFrame()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
mixerInit();
|
mixerInit();
|
||||||
|
@ -61,8 +66,8 @@ bool AudioTrack::tryReadFirstFrame(Packet &&packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioTrack::fillStateFromFrame() {
|
bool AudioTrack::fillStateFromFrame() {
|
||||||
_state.position = _state.receivedTill = FramePosition(_stream);
|
_startedPosition = FramePosition(_stream);
|
||||||
return (_state.position != kTimeUnknown);
|
return (_startedPosition != kTimeUnknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioTrack::mixerInit() {
|
void AudioTrack::mixerInit() {
|
||||||
|
@ -77,16 +82,19 @@ void AudioTrack::mixerInit() {
|
||||||
Media::Player::mixer()->play(
|
Media::Player::mixer()->play(
|
||||||
_audioMsgId,
|
_audioMsgId,
|
||||||
std::move(data),
|
std::move(data),
|
||||||
_state.position);
|
_startedPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioTrack::callReady() {
|
void AudioTrack::callReady() {
|
||||||
Expects(_ready != nullptr);
|
Expects(_ready != nullptr);
|
||||||
|
|
||||||
auto data = Information();
|
auto data = AudioInformation();
|
||||||
data.audioDuration = _stream.duration;
|
data.state.duration = _stream.duration;
|
||||||
data.state.audio = _state;
|
data.state.position = _startedPosition;
|
||||||
base::take(_ready)(data);
|
data.state.receivedTill = _noMoreData
|
||||||
|
? _stream.duration
|
||||||
|
: _startedPosition;
|
||||||
|
base::take(_ready)({ VideoInformation(), data });
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioTrack::mixerEnqueue(Packet &&packet) {
|
void AudioTrack::mixerEnqueue(Packet &&packet) {
|
||||||
|
@ -100,24 +108,49 @@ void AudioTrack::mixerEnqueue(Packet &&packet) {
|
||||||
void AudioTrack::start() {
|
void AudioTrack::start() {
|
||||||
Expects(_ready == nullptr);
|
Expects(_ready == nullptr);
|
||||||
Expects(_audioMsgId.playId() != 0);
|
Expects(_audioMsgId.playId() != 0);
|
||||||
|
|
||||||
// #TODO streaming support start() when paused.
|
// #TODO streaming support start() when paused.
|
||||||
Media::Player::mixer()->resume(_audioMsgId, true);
|
Media::Player::mixer()->resume(_audioMsgId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<TrackState, Error> AudioTrack::state() {
|
rpl::producer<crl::time> AudioTrack::playPosition() {
|
||||||
Expects(_ready == nullptr);
|
Expects(_ready == nullptr);
|
||||||
|
|
||||||
if (!_subscription) {
|
if (!_subscription) {
|
||||||
auto &updated = Media::Player::instance()->updatedNotifier();
|
_subscription = Media::Player::Updated(
|
||||||
_subscription = updated.add_subscription([=](
|
).add_subscription([=](const AudioMsgId &id) {
|
||||||
const Media::Player::TrackState &state) {
|
using State = Media::Player::State;
|
||||||
// _state = state;
|
if (id != _audioMsgId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto type = AudioMsgId::Type::Video;
|
||||||
|
const auto state = Media::Player::mixer()->currentState(type);
|
||||||
|
if (state.id != _audioMsgId) {
|
||||||
|
// #TODO streaming muted by other
|
||||||
|
return;
|
||||||
|
} else switch (state.state) {
|
||||||
|
case State::Stopped:
|
||||||
|
case State::StoppedAtEnd:
|
||||||
|
case State::PausedAtEnd:
|
||||||
|
_playPosition.reset();
|
||||||
|
return;
|
||||||
|
case State::StoppedAtError:
|
||||||
|
case State::StoppedAtStart:
|
||||||
|
_error();
|
||||||
|
return;
|
||||||
|
case State::Starting:
|
||||||
|
case State::Playing:
|
||||||
|
case State::Stopping:
|
||||||
|
case State::Pausing:
|
||||||
|
case State::Resuming:
|
||||||
|
_playPosition = state.position * 1000 / state.frequency;
|
||||||
|
return;
|
||||||
|
case State::Paused:
|
||||||
|
return;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
//) | rpl::filter([](const State &state) {
|
|
||||||
// return !!state.id;
|
|
||||||
//});
|
|
||||||
}
|
}
|
||||||
return rpl::single<const TrackState&, Error>(_state);
|
return _playPosition.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioTrack::~AudioTrack() {
|
AudioTrack::~AudioTrack() {
|
||||||
|
|
|
@ -10,9 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "media/streaming/media_streaming_utility.h"
|
#include "media/streaming/media_streaming_utility.h"
|
||||||
|
|
||||||
namespace Media {
|
namespace Media {
|
||||||
namespace Player {
|
|
||||||
struct TrackState;
|
|
||||||
} // namespace Player
|
|
||||||
|
|
||||||
namespace Streaming {
|
namespace Streaming {
|
||||||
|
|
||||||
|
@ -32,7 +29,7 @@ public:
|
||||||
// Called from the main thread.
|
// Called from the main thread.
|
||||||
// Non-const, because we subscribe to changes on the first call.
|
// Non-const, because we subscribe to changes on the first call.
|
||||||
// Must be called after 'ready' was invoked.
|
// Must be called after 'ready' was invoked.
|
||||||
[[nodiscard]] rpl::producer<TrackState, Error> state();
|
[[nodiscard]] rpl::producer<crl::time> playPosition();
|
||||||
|
|
||||||
// Thread-safe.
|
// Thread-safe.
|
||||||
[[nodiscard]] int streamIndex() const;
|
[[nodiscard]] int streamIndex() const;
|
||||||
|
@ -54,6 +51,7 @@ private:
|
||||||
|
|
||||||
// Accessed from the same unspecified thread.
|
// Accessed from the same unspecified thread.
|
||||||
Stream _stream;
|
Stream _stream;
|
||||||
|
bool _noMoreData = false;
|
||||||
|
|
||||||
// Assumed to be thread-safe.
|
// Assumed to be thread-safe.
|
||||||
FnMut<void(const Information &)> _ready;
|
FnMut<void(const Information &)> _ready;
|
||||||
|
@ -62,11 +60,11 @@ private:
|
||||||
// First set from the same unspecified thread before _ready is called.
|
// First set from the same unspecified thread before _ready is called.
|
||||||
// After that is immutable.
|
// After that is immutable.
|
||||||
AudioMsgId _audioMsgId;
|
AudioMsgId _audioMsgId;
|
||||||
TrackState _state;
|
crl::time _startedPosition = kTimeUnknown;
|
||||||
|
|
||||||
// Accessed from the main thread.
|
// Accessed from the main thread.
|
||||||
base::Subscription _subscription;
|
base::Subscription _subscription;
|
||||||
rpl::event_stream<TrackState, Error> _stateChanges;
|
rpl::variable<crl::time> _playPosition;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@ namespace Streaming {
|
||||||
|
|
||||||
constexpr auto kTimeUnknown = std::numeric_limits<crl::time>::min();
|
constexpr auto kTimeUnknown = std::numeric_limits<crl::time>::min();
|
||||||
|
|
||||||
|
class VideoTrack;
|
||||||
|
class AudioTrack;
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
Both,
|
Both,
|
||||||
Audio,
|
Audio,
|
||||||
|
@ -22,32 +25,40 @@ enum class Mode {
|
||||||
struct TrackState {
|
struct TrackState {
|
||||||
crl::time position = kTimeUnknown;
|
crl::time position = kTimeUnknown;
|
||||||
crl::time receivedTill = kTimeUnknown;
|
crl::time receivedTill = kTimeUnknown;
|
||||||
|
crl::time duration = kTimeUnknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct State {
|
struct VideoInformation {
|
||||||
TrackState video;
|
TrackState state;
|
||||||
TrackState audio;
|
QSize size;
|
||||||
|
QImage cover;
|
||||||
|
int rotation = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AudioInformation {
|
||||||
|
TrackState state;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Information {
|
struct Information {
|
||||||
State state;
|
VideoInformation video;
|
||||||
|
AudioInformation audio;
|
||||||
crl::time videoDuration = kTimeUnknown;
|
|
||||||
QSize videoSize;
|
|
||||||
QImage videoCover;
|
|
||||||
int videoRotation = 0;
|
|
||||||
|
|
||||||
crl::time audioDuration = kTimeUnknown;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UpdateVideo {
|
template <typename Track>
|
||||||
crl::time position = 0;
|
struct PreloadedUpdate {
|
||||||
|
crl::time till = kTimeUnknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct UpdateAudio {
|
template <typename Track>
|
||||||
crl::time position = 0;
|
struct PlaybackUpdate {
|
||||||
|
crl::time position = kTimeUnknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using PreloadedVideo = PreloadedUpdate<VideoTrack>;
|
||||||
|
using UpdateVideo = PlaybackUpdate<VideoTrack>;
|
||||||
|
using PreloadedAudio = PreloadedUpdate<AudioTrack>;
|
||||||
|
using UpdateAudio = PlaybackUpdate<AudioTrack>;
|
||||||
|
|
||||||
struct WaitingForData {
|
struct WaitingForData {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -57,7 +68,9 @@ struct MutedByOther {
|
||||||
struct Update {
|
struct Update {
|
||||||
base::variant<
|
base::variant<
|
||||||
Information,
|
Information,
|
||||||
|
PreloadedVideo,
|
||||||
UpdateVideo,
|
UpdateVideo,
|
||||||
|
PreloadedAudio,
|
||||||
UpdateAudio,
|
UpdateAudio,
|
||||||
WaitingForData,
|
WaitingForData,
|
||||||
MutedByOther> data;
|
MutedByOther> data;
|
||||||
|
|
|
@ -128,8 +128,11 @@ Stream File::Context::initStream(AVMediaType type) {
|
||||||
}
|
}
|
||||||
result.timeBase = info->time_base;
|
result.timeBase = info->time_base;
|
||||||
result.duration = (info->duration != AV_NOPTS_VALUE)
|
result.duration = (info->duration != AV_NOPTS_VALUE)
|
||||||
? PtsToTime(info->duration, result.timeBase)
|
? PtsToTimeCeil(info->duration, result.timeBase)
|
||||||
: PtsToTime(_formatContext->duration, kUniversalTimeBase);
|
: PtsToTimeCeil(_formatContext->duration, kUniversalTimeBase);
|
||||||
|
if (result.duration == kTimeUnknown || !result.duration) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,52 +220,17 @@ void File::Context::start(crl::time position) {
|
||||||
_delegate->fileReady(std::move(video), std::move(audio));
|
_delegate->fileReady(std::move(video), std::move(audio));
|
||||||
}
|
}
|
||||||
|
|
||||||
//void File::Context::readInformation(crl::time position) {
|
|
||||||
// auto information = Information();
|
|
||||||
// auto result = readPacket();
|
|
||||||
// const auto packet = base::get_if<Packet>(&result);
|
|
||||||
// if (unroll()) {
|
|
||||||
// return;
|
|
||||||
// } else if (packet) {
|
|
||||||
// if (position > 0) {
|
|
||||||
// const auto time = CountPacketPosition(
|
|
||||||
// _formatContext->streams[packet->fields().stream_index],
|
|
||||||
// *packet);
|
|
||||||
// information.started = (time == Information::kDurationUnknown)
|
|
||||||
// ? position
|
|
||||||
// : time;
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// information.started = position;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (packet) {
|
|
||||||
// processPacket(std::move(*packet));
|
|
||||||
// } else {
|
|
||||||
// enqueueEofPackets();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// information.cover = readFirstVideoFrame();
|
|
||||||
// if (unroll()) {
|
|
||||||
// return;
|
|
||||||
// } else if (!information.cover.isNull()) {
|
|
||||||
// information.video = information.cover.size();
|
|
||||||
// information.rotation = _video.stream.rotation;
|
|
||||||
// if (RotationSwapWidthHeight(information.rotation)) {
|
|
||||||
// information.video.transpose();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// information.audio = (_audio.info != nullptr);
|
|
||||||
// _information = std::move(information);
|
|
||||||
//}
|
|
||||||
|
|
||||||
void File::Context::readNextPacket() {
|
void File::Context::readNextPacket() {
|
||||||
auto result = readPacket();
|
auto result = readPacket();
|
||||||
if (unroll()) {
|
if (unroll()) {
|
||||||
return;
|
return;
|
||||||
} else if (const auto packet = base::get_if<Packet>(&result)) {
|
} else if (const auto packet = base::get_if<Packet>(&result)) {
|
||||||
const auto more = _delegate->fileProcessPacket(std::move(*packet));
|
const auto more = _delegate->fileProcessPacket(std::move(*packet));
|
||||||
|
if (!more) {
|
||||||
|
do {
|
||||||
|
_semaphore.acquire();
|
||||||
|
} while (!unroll() && !_delegate->fileReadMore());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Still trying to read by drain.
|
// Still trying to read by drain.
|
||||||
Assert(result.is<AvErrorWrap>());
|
Assert(result.is<AvErrorWrap>());
|
||||||
|
@ -271,8 +239,8 @@ void File::Context::readNextPacket() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void File::Context::handleEndOfFile() {
|
void File::Context::handleEndOfFile() {
|
||||||
// #TODO streaming looping
|
|
||||||
const auto more = _delegate->fileProcessPacket(Packet());
|
const auto more = _delegate->fileProcessPacket(Packet());
|
||||||
|
// #TODO streaming looping
|
||||||
_readTillEnd = true;
|
_readTillEnd = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,6 +249,10 @@ void File::Context::interrupt() {
|
||||||
_semaphore.release();
|
_semaphore.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void File::Context::wake() {
|
||||||
|
_semaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
bool File::Context::interrupted() const {
|
bool File::Context::interrupted() const {
|
||||||
return _interrupted;
|
return _interrupted;
|
||||||
}
|
}
|
||||||
|
@ -339,6 +311,12 @@ void File::start(not_null<FileDelegate*> delegate, crl::time position) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void File::wake() {
|
||||||
|
Expects(_context.has_value());
|
||||||
|
|
||||||
|
_context->wake();
|
||||||
|
}
|
||||||
|
|
||||||
void File::stop() {
|
void File::stop() {
|
||||||
if (_thread.joinable()) {
|
if (_thread.joinable()) {
|
||||||
_context->interrupt();
|
_context->interrupt();
|
||||||
|
|
|
@ -47,6 +47,7 @@ private:
|
||||||
void readNextPacket();
|
void readNextPacket();
|
||||||
|
|
||||||
void interrupt();
|
void interrupt();
|
||||||
|
void wake();
|
||||||
[[nodiscard]] bool interrupted() const;
|
[[nodiscard]] bool interrupted() const;
|
||||||
[[nodiscard]] bool failed() const;
|
[[nodiscard]] bool failed() const;
|
||||||
[[nodiscard]] bool finished() const;
|
[[nodiscard]] bool finished() const;
|
||||||
|
|
|
@ -16,25 +16,45 @@ namespace Media {
|
||||||
namespace Streaming {
|
namespace Streaming {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
void SaveValidInformation(Information &to, Information &&from) {
|
void SaveValidStateInformation(TrackState &to, TrackState &&from) {
|
||||||
if (from.state.audio.position != kTimeUnknown) {
|
Expects(from.position != kTimeUnknown);
|
||||||
to.state.audio = from.state.audio;
|
Expects(from.receivedTill != kTimeUnknown);
|
||||||
|
Expects(from.duration != kTimeUnknown);
|
||||||
|
|
||||||
|
to.duration = from.duration;
|
||||||
|
to.position = from.position;
|
||||||
|
to.receivedTill = (to.receivedTill == kTimeUnknown)
|
||||||
|
? from.receivedTill
|
||||||
|
: std::clamp(
|
||||||
|
std::max(from.receivedTill, to.receivedTill),
|
||||||
|
to.position,
|
||||||
|
to.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveValidAudioInformation(
|
||||||
|
AudioInformation &to,
|
||||||
|
AudioInformation &&from) {
|
||||||
|
SaveValidStateInformation(to.state, std::move(from.state));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveValidVideoInformation(
|
||||||
|
VideoInformation &to,
|
||||||
|
VideoInformation &&from) {
|
||||||
|
Expects(!from.size.isEmpty());
|
||||||
|
Expects(!from.cover.isNull());
|
||||||
|
|
||||||
|
SaveValidStateInformation(to.state, std::move(from.state));
|
||||||
|
to.size = from.size;
|
||||||
|
to.cover = std::move(from.cover);
|
||||||
|
to.rotation = from.rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveValidStartInformation(Information &to, Information &&from) {
|
||||||
|
if (from.audio.state.duration != kTimeUnknown) {
|
||||||
|
SaveValidAudioInformation(to.audio, std::move(from.audio));
|
||||||
}
|
}
|
||||||
if (from.audioDuration != kTimeUnknown) {
|
if (from.video.state.duration != kTimeUnknown) {
|
||||||
to.audioDuration = from.audioDuration;
|
SaveValidVideoInformation(to.video, std::move(from.video));
|
||||||
}
|
|
||||||
if (from.state.video.position != kTimeUnknown) {
|
|
||||||
to.state.video = from.state.video;
|
|
||||||
}
|
|
||||||
if (from.videoDuration != kTimeUnknown) {
|
|
||||||
to.videoDuration = from.videoDuration;
|
|
||||||
}
|
|
||||||
if (!from.videoSize.isEmpty()) {
|
|
||||||
to.videoSize = from.videoSize;
|
|
||||||
}
|
|
||||||
if (!from.videoCover.isNull()) {
|
|
||||||
to.videoCover = std::move(from.videoCover);
|
|
||||||
to.videoRotation = from.videoRotation;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,16 +75,21 @@ void Player::start() {
|
||||||
Expects(_stage == Stage::Ready);
|
Expects(_stage == Stage::Ready);
|
||||||
|
|
||||||
_stage = Stage::Started;
|
_stage = Stage::Started;
|
||||||
//if (_audio) {
|
if (_audio) {
|
||||||
// _audio->state(
|
_audio->playPosition(
|
||||||
// ) | rpl::start_with_next([](const TrackState & state) {
|
) | rpl::start_with_next_done([=](crl::time position) {
|
||||||
// }, _lifetime);
|
audioPlayedTill(position);
|
||||||
//}
|
}, [=] {
|
||||||
|
// audio finished
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
if (_video) {
|
if (_video) {
|
||||||
_video->renderNextFrame(
|
_video->renderNextFrame(
|
||||||
) | rpl::start_with_next([=](crl::time when) {
|
) | rpl::start_with_next_done([=](crl::time when) {
|
||||||
_nextFrameTime = when;
|
_nextFrameTime = when;
|
||||||
checkNextFrame();
|
checkNextFrame();
|
||||||
|
}, [=] {
|
||||||
|
// video finished
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
if (_audio) {
|
if (_audio) {
|
||||||
|
@ -89,11 +114,69 @@ void Player::checkNextFrame() {
|
||||||
|
|
||||||
void Player::renderFrame(crl::time now) {
|
void Player::renderFrame(crl::time now) {
|
||||||
if (_video) {
|
if (_video) {
|
||||||
_video->markFrameDisplayed(now);
|
const auto position = _video->markFrameDisplayed(now);
|
||||||
_updates.fire({ UpdateVideo{ _nextFrameTime } });
|
if (position != kTimeUnknown) {
|
||||||
|
videoPlayedTill(position);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Track>
|
||||||
|
void Player::trackReceivedTill(
|
||||||
|
const Track &track,
|
||||||
|
TrackState &state,
|
||||||
|
crl::time position) {
|
||||||
|
if (position == kTimeUnknown) {
|
||||||
|
return;
|
||||||
|
} else if (state.duration != kTimeUnknown) {
|
||||||
|
position = std::clamp(position, 0LL, state.duration);
|
||||||
|
if (state.receivedTill < position) {
|
||||||
|
state.receivedTill = position;
|
||||||
|
_updates.fire({ PreloadedUpdate<Track>{ position } });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.receivedTill = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Track>
|
||||||
|
void Player::trackPlayedTill(
|
||||||
|
const Track &track,
|
||||||
|
TrackState &state,
|
||||||
|
crl::time position) {
|
||||||
|
const auto guard = base::make_weak(&_sessionGuard);
|
||||||
|
trackReceivedTill(track, state, position);
|
||||||
|
if (guard && position != kTimeUnknown) {
|
||||||
|
position = std::clamp(position, 0LL, state.duration);
|
||||||
|
state.position = position;
|
||||||
|
_updates.fire({ PlaybackUpdate<Track>{ position } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::audioReceivedTill(crl::time position) {
|
||||||
|
Expects(_audio != nullptr);
|
||||||
|
|
||||||
|
trackReceivedTill(*_audio, _information.audio.state, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::audioPlayedTill(crl::time position) {
|
||||||
|
Expects(_audio != nullptr);
|
||||||
|
|
||||||
|
trackPlayedTill(*_audio, _information.audio.state, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::videoReceivedTill(crl::time position) {
|
||||||
|
Expects(_video != nullptr);
|
||||||
|
|
||||||
|
trackReceivedTill(*_video, _information.video.state, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::videoPlayedTill(crl::time position) {
|
||||||
|
Expects(_video != nullptr);
|
||||||
|
|
||||||
|
trackPlayedTill(*_video, _information.video.state, position);
|
||||||
|
}
|
||||||
|
|
||||||
void Player::fileReady(Stream &&video, Stream &&audio) {
|
void Player::fileReady(Stream &&video, Stream &&audio) {
|
||||||
const auto weak = base::make_weak(&_sessionGuard);
|
const auto weak = base::make_weak(&_sessionGuard);
|
||||||
const auto ready = [=](const Information & data) {
|
const auto ready = [=](const Information & data) {
|
||||||
|
@ -142,14 +225,28 @@ bool Player::fileProcessPacket(Packet &&packet) {
|
||||||
if (packet.empty()) {
|
if (packet.empty()) {
|
||||||
_readTillEnd = true;
|
_readTillEnd = true;
|
||||||
if (_audio) {
|
if (_audio) {
|
||||||
|
crl::on_main(&_sessionGuard, [=] {
|
||||||
|
audioReceivedTill(kReceivedTillEnd);
|
||||||
|
});
|
||||||
_audio->process(Packet());
|
_audio->process(Packet());
|
||||||
}
|
}
|
||||||
if (_video) {
|
if (_video) {
|
||||||
|
crl::on_main(&_sessionGuard, [=] {
|
||||||
|
videoReceivedTill(kReceivedTillEnd);
|
||||||
|
});
|
||||||
_video->process(Packet());
|
_video->process(Packet());
|
||||||
}
|
}
|
||||||
} else if (_audio && _audio->streamIndex() == native.stream_index) {
|
} else if (_audio && _audio->streamIndex() == native.stream_index) {
|
||||||
|
const auto time = PacketPosition(packet, _audio->streamTimeBase());
|
||||||
|
crl::on_main(&_sessionGuard, [=] {
|
||||||
|
audioReceivedTill(time);
|
||||||
|
});
|
||||||
_audio->process(std::move(packet));
|
_audio->process(std::move(packet));
|
||||||
} else if (_video && _video->streamIndex() == native.stream_index) {
|
} else if (_video && _video->streamIndex() == native.stream_index) {
|
||||||
|
const auto time = PacketPosition(packet, _video->streamTimeBase());
|
||||||
|
crl::on_main(&_sessionGuard, [=] {
|
||||||
|
videoReceivedTill(time);
|
||||||
|
});
|
||||||
_video->process(std::move(packet));
|
_video->process(std::move(packet));
|
||||||
}
|
}
|
||||||
return fileReadMore();
|
return fileReadMore();
|
||||||
|
@ -161,7 +258,7 @@ bool Player::fileReadMore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::streamReady(Information &&information) {
|
void Player::streamReady(Information &&information) {
|
||||||
SaveValidInformation(_information, std::move(information));
|
SaveValidStartInformation(_information, std::move(information));
|
||||||
provideStartInformation();
|
provideStartInformation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,8 +273,8 @@ void Player::streamFailed() {
|
||||||
void Player::provideStartInformation() {
|
void Player::provideStartInformation() {
|
||||||
Expects(_stage == Stage::Initializing);
|
Expects(_stage == Stage::Initializing);
|
||||||
|
|
||||||
if ((_audio && _information.audioDuration == kTimeUnknown)
|
if ((_audio && _information.audio.state.duration == kTimeUnknown)
|
||||||
|| (_video && _information.videoDuration == kTimeUnknown)) {
|
|| (_video && _information.video.state.duration == kTimeUnknown)) {
|
||||||
return; // Not ready yet.
|
return; // Not ready yet.
|
||||||
} else if ((!_audio && !_video)
|
} else if ((!_audio && !_video)
|
||||||
|| (!_audio && _mode == Mode::Audio)
|
|| (!_audio && _mode == Mode::Audio)
|
||||||
|
@ -185,7 +282,12 @@ void Player::provideStartInformation() {
|
||||||
fail();
|
fail();
|
||||||
} else {
|
} else {
|
||||||
_stage = Stage::Ready;
|
_stage = Stage::Ready;
|
||||||
_updates.fire(Update{ std::move(_information) });
|
|
||||||
|
// Don't keep the reference to the video cover.
|
||||||
|
auto copy = _information;
|
||||||
|
_information.video.cover = QImage();
|
||||||
|
|
||||||
|
_updates.fire(Update{ std::move(copy) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ class VideoTrack;
|
||||||
class Player final : private FileDelegate {
|
class Player final : private FileDelegate {
|
||||||
public:
|
public:
|
||||||
// Public interfaces is used from the main thread.
|
// Public interfaces is used from the main thread.
|
||||||
|
|
||||||
Player(not_null<Data::Session*> owner, std::unique_ptr<Loader> loader);
|
Player(not_null<Data::Session*> owner, std::unique_ptr<Loader> loader);
|
||||||
|
|
||||||
// Because we remember 'this' in calls to crl::on_main.
|
// Because we remember 'this' in calls to crl::on_main.
|
||||||
|
@ -77,9 +76,28 @@ private:
|
||||||
void fail();
|
void fail();
|
||||||
void checkNextFrame();
|
void checkNextFrame();
|
||||||
void renderFrame(crl::time now);
|
void renderFrame(crl::time now);
|
||||||
|
void audioReceivedTill(crl::time position);
|
||||||
|
void audioPlayedTill(crl::time position);
|
||||||
|
void videoReceivedTill(crl::time position);
|
||||||
|
void videoPlayedTill(crl::time position);
|
||||||
|
|
||||||
|
template <typename Track>
|
||||||
|
void trackReceivedTill(
|
||||||
|
const Track &track,
|
||||||
|
TrackState &state,
|
||||||
|
crl::time position);
|
||||||
|
|
||||||
|
template <typename Track>
|
||||||
|
void trackPlayedTill(
|
||||||
|
const Track &track,
|
||||||
|
TrackState &state,
|
||||||
|
crl::time position);
|
||||||
|
|
||||||
const std::unique_ptr<File> _file;
|
const std::unique_ptr<File> _file;
|
||||||
|
|
||||||
|
static constexpr auto kReceivedTillEnd
|
||||||
|
= std::numeric_limits<crl::time>::max();
|
||||||
|
|
||||||
// Immutable while File is active.
|
// Immutable while File is active.
|
||||||
std::unique_ptr<AudioTrack> _audio;
|
std::unique_ptr<AudioTrack> _audio;
|
||||||
std::unique_ptr<VideoTrack> _video;
|
std::unique_ptr<VideoTrack> _video;
|
||||||
|
|
|
@ -167,13 +167,26 @@ crl::time PtsToTime(int64_t pts, AVRational timeBase) {
|
||||||
: ((pts * 1000LL * timeBase.num) / timeBase.den);
|
: ((pts * 1000LL * timeBase.num) / timeBase.den);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crl::time PtsToTimeCeil(int64_t pts, AVRational timeBase) {
|
||||||
|
return (pts == AV_NOPTS_VALUE || !timeBase.den)
|
||||||
|
? kTimeUnknown
|
||||||
|
: ((pts * 1000LL * timeBase.num + timeBase.den - 1) / timeBase.den);
|
||||||
|
}
|
||||||
|
|
||||||
int64_t TimeToPts(crl::time time, AVRational timeBase) {
|
int64_t TimeToPts(crl::time time, AVRational timeBase) {
|
||||||
return (time == kTimeUnknown || !timeBase.num)
|
return (time == kTimeUnknown || !timeBase.num)
|
||||||
? AV_NOPTS_VALUE
|
? AV_NOPTS_VALUE
|
||||||
: (time * timeBase.den) / (1000LL * timeBase.num);
|
: (time * timeBase.den) / (1000LL * timeBase.num);
|
||||||
}
|
}
|
||||||
|
|
||||||
crl::time FramePosition(Stream &stream) {
|
crl::time PacketPosition(const Packet &packet, AVRational timeBase) {
|
||||||
|
const auto &native = packet.fields();
|
||||||
|
return PtsToTime(
|
||||||
|
(native.pts == AV_NOPTS_VALUE) ? native.dts : native.pts,
|
||||||
|
timeBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
crl::time FramePosition(const Stream &stream) {
|
||||||
const auto pts = !stream.frame
|
const auto pts = !stream.frame
|
||||||
? AV_NOPTS_VALUE
|
? AV_NOPTS_VALUE
|
||||||
: (stream.frame->pts == AV_NOPTS_VALUE)
|
: (stream.frame->pts == AV_NOPTS_VALUE)
|
||||||
|
|
|
@ -149,8 +149,13 @@ void LogError(QLatin1String method);
|
||||||
void LogError(QLatin1String method, AvErrorWrap error);
|
void LogError(QLatin1String method, AvErrorWrap error);
|
||||||
|
|
||||||
[[nodiscard]] crl::time PtsToTime(int64_t pts, AVRational timeBase);
|
[[nodiscard]] crl::time PtsToTime(int64_t pts, AVRational timeBase);
|
||||||
|
// Used for full duration conversion.
|
||||||
|
[[nodiscard]] crl::time PtsToTimeCeil(int64_t pts, AVRational timeBase);
|
||||||
[[nodiscard]] int64_t TimeToPts(crl::time time, AVRational timeBase);
|
[[nodiscard]] int64_t TimeToPts(crl::time time, AVRational timeBase);
|
||||||
[[nodiscard]] crl::time FramePosition(Stream &stream);
|
[[nodiscard]] crl::time PacketPosition(
|
||||||
|
const Packet &packet,
|
||||||
|
AVRational timeBase);
|
||||||
|
[[nodiscard]] crl::time FramePosition(const Stream &stream);
|
||||||
[[nodiscard]] int ReadRotationFromMetadata(not_null<AVStream*> stream);
|
[[nodiscard]] int ReadRotationFromMetadata(not_null<AVStream*> stream);
|
||||||
[[nodiscard]] bool RotationSwapWidthHeight(int rotation);
|
[[nodiscard]] bool RotationSwapWidthHeight(int rotation);
|
||||||
[[nodiscard]] AvErrorWrap ProcessPacket(Stream &stream, Packet &&packet);
|
[[nodiscard]] AvErrorWrap ProcessPacket(Stream &stream, Packet &&packet);
|
||||||
|
|
|
@ -48,6 +48,9 @@ private:
|
||||||
void presentFrameIfNeeded();
|
void presentFrameIfNeeded();
|
||||||
void callReady();
|
void callReady();
|
||||||
|
|
||||||
|
// Force frame position to be clamped to [0, duration] and monotonic.
|
||||||
|
[[nodiscard]] crl::time currentFramePosition() const;
|
||||||
|
|
||||||
[[nodiscard]] crl::time trackTime() const;
|
[[nodiscard]] crl::time trackTime() const;
|
||||||
|
|
||||||
const crl::weak_on_queue<VideoTrackObject> _weak;
|
const crl::weak_on_queue<VideoTrackObject> _weak;
|
||||||
|
@ -62,7 +65,8 @@ private:
|
||||||
Fn<void()> _error;
|
Fn<void()> _error;
|
||||||
crl::time _startedTime = kTimeUnknown;
|
crl::time _startedTime = kTimeUnknown;
|
||||||
crl::time _startedPosition = kTimeUnknown;
|
crl::time _startedPosition = kTimeUnknown;
|
||||||
rpl::variable<crl::time> _nextFramePosition = kTimeUnknown;
|
mutable crl::time _previousFramePosition = kTimeUnknown;
|
||||||
|
rpl::variable<crl::time> _nextFrameDisplayPosition = kTimeUnknown;
|
||||||
|
|
||||||
bool _queued = false;
|
bool _queued = false;
|
||||||
base::ConcurrentTimer _readFramesTimer;
|
base::ConcurrentTimer _readFramesTimer;
|
||||||
|
@ -86,8 +90,9 @@ VideoTrackObject::VideoTrackObject(
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<crl::time> VideoTrackObject::displayFrameAt() const {
|
rpl::producer<crl::time> VideoTrackObject::displayFrameAt() const {
|
||||||
return _nextFramePosition.value() | rpl::map([=](crl::time position) {
|
return _nextFrameDisplayPosition.value(
|
||||||
return _startedTime + (position - _startedPosition);
|
) | rpl::map([=](crl::time displayPosition) {
|
||||||
|
return _startedTime + (displayPosition - _startedPosition);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +148,7 @@ bool VideoTrackObject::readFrame(not_null<Frame*> frame) {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto position = FramePosition(_stream);
|
const auto position = currentFramePosition();
|
||||||
if (position == kTimeUnknown) {
|
if (position == kTimeUnknown) {
|
||||||
interrupt();
|
interrupt();
|
||||||
_error();
|
_error();
|
||||||
|
@ -154,6 +159,7 @@ bool VideoTrackObject::readFrame(not_null<Frame*> frame) {
|
||||||
QSize(),
|
QSize(),
|
||||||
std::move(frame->original));
|
std::move(frame->original));
|
||||||
frame->position = position;
|
frame->position = position;
|
||||||
|
frame->displayPosition = position; // #TODO streaming adjust / sync
|
||||||
frame->displayed = kTimeUnknown;
|
frame->displayed = kTimeUnknown;
|
||||||
|
|
||||||
//frame->request
|
//frame->request
|
||||||
|
@ -164,8 +170,8 @@ bool VideoTrackObject::readFrame(not_null<Frame*> frame) {
|
||||||
|
|
||||||
void VideoTrackObject::presentFrameIfNeeded() {
|
void VideoTrackObject::presentFrameIfNeeded() {
|
||||||
const auto presented = _shared->presentFrame(trackTime());
|
const auto presented = _shared->presentFrame(trackTime());
|
||||||
if (presented.position != kTimeUnknown) {
|
if (presented.displayPosition != kTimeUnknown) {
|
||||||
_nextFramePosition = presented.position;
|
_nextFrameDisplayPosition = presented.displayPosition;
|
||||||
}
|
}
|
||||||
queueReadFrames(presented.nextCheckDelay);
|
queueReadFrames(presented.nextCheckDelay);
|
||||||
}
|
}
|
||||||
|
@ -210,9 +216,21 @@ bool VideoTrackObject::tryReadFirstFrame(Packet &&packet) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crl::time VideoTrackObject::currentFramePosition() const {
|
||||||
|
const auto position = std::min(
|
||||||
|
FramePosition(_stream),
|
||||||
|
_stream.duration);
|
||||||
|
if (_previousFramePosition != kTimeUnknown
|
||||||
|
&& position <= _previousFramePosition) {
|
||||||
|
return kTimeUnknown;
|
||||||
|
}
|
||||||
|
_previousFramePosition = position;
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
bool VideoTrackObject::fillStateFromFrame() {
|
bool VideoTrackObject::fillStateFromFrame() {
|
||||||
_startedPosition = FramePosition(_stream);
|
_startedPosition = currentFramePosition();
|
||||||
_nextFramePosition = _startedPosition;
|
_nextFrameDisplayPosition = _startedPosition;
|
||||||
return (_startedPosition != kTimeUnknown);
|
return (_startedPosition != kTimeUnknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,16 +240,19 @@ void VideoTrackObject::callReady() {
|
||||||
const auto frame = _shared->frameForPaint();
|
const auto frame = _shared->frameForPaint();
|
||||||
Assert(frame != nullptr);
|
Assert(frame != nullptr);
|
||||||
|
|
||||||
auto data = Information();
|
auto data = VideoInformation();
|
||||||
data.videoDuration = _stream.duration;
|
data.size = frame->original.size();
|
||||||
data.videoSize = frame->original.size();
|
|
||||||
if (RotationSwapWidthHeight(_stream.rotation)) {
|
if (RotationSwapWidthHeight(_stream.rotation)) {
|
||||||
data.videoSize.transpose();
|
data.size.transpose();
|
||||||
}
|
}
|
||||||
data.videoCover = frame->original;
|
data.cover = frame->original;
|
||||||
data.videoRotation = _stream.rotation;
|
data.rotation = _stream.rotation;
|
||||||
data.state.video.position = _startedPosition;
|
data.state.duration = _stream.duration;
|
||||||
base::take(_ready)(data);
|
data.state.position = _startedPosition;
|
||||||
|
data.state.receivedTill = _noMoreData
|
||||||
|
? _stream.duration
|
||||||
|
: _startedPosition;
|
||||||
|
base::take(_ready)({ data });
|
||||||
}
|
}
|
||||||
|
|
||||||
crl::time VideoTrackObject::trackTime() const {
|
crl::time VideoTrackObject::trackTime() const {
|
||||||
|
@ -248,6 +269,7 @@ void VideoTrack::Shared::init(QImage &&cover, crl::time position) {
|
||||||
|
|
||||||
_frames[0].original = std::move(cover);
|
_frames[0].original = std::move(cover);
|
||||||
_frames[0].position = position;
|
_frames[0].position = position;
|
||||||
|
_frames[0].displayPosition = position;
|
||||||
|
|
||||||
// Usually main thread sets displayed time before _counter increment.
|
// Usually main thread sets displayed time before _counter increment.
|
||||||
// But in this case we update _counter, so we set a fake displayed time.
|
// But in this case we update _counter, so we set a fake displayed time.
|
||||||
|
@ -271,7 +293,7 @@ not_null<VideoTrack::Frame*> VideoTrack::Shared::getFrame(int index) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoTrack::Shared::IsPrepared(not_null<Frame*> frame) {
|
bool VideoTrack::Shared::IsPrepared(not_null<Frame*> frame) {
|
||||||
return (frame->position != kTimeUnknown)
|
return (frame->displayPosition != kTimeUnknown)
|
||||||
&& (frame->displayed == kTimeUnknown)
|
&& (frame->displayed == kTimeUnknown)
|
||||||
&& !frame->original.isNull();
|
&& !frame->original.isNull();
|
||||||
}
|
}
|
||||||
|
@ -281,7 +303,7 @@ bool VideoTrack::Shared::IsStale(
|
||||||
crl::time trackTime) {
|
crl::time trackTime) {
|
||||||
Expects(IsPrepared(frame));
|
Expects(IsPrepared(frame));
|
||||||
|
|
||||||
return (frame->position < trackTime);
|
return (frame->displayPosition < trackTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto VideoTrack::Shared::prepareState(crl::time trackTime) -> PrepareState {
|
auto VideoTrack::Shared::prepareState(crl::time trackTime) -> PrepareState {
|
||||||
|
@ -297,7 +319,7 @@ auto VideoTrack::Shared::prepareState(crl::time trackTime) -> PrepareState {
|
||||||
} else if (!IsPrepared(next)) {
|
} else if (!IsPrepared(next)) {
|
||||||
return next;
|
return next;
|
||||||
} else {
|
} else {
|
||||||
return PrepareNextCheck(frame->position - trackTime + 1);
|
return PrepareNextCheck(frame->displayPosition - trackTime + 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const auto finishPrepare = [&](int index) {
|
const auto finishPrepare = [&](int index) {
|
||||||
|
@ -323,8 +345,9 @@ auto VideoTrack::Shared::presentFrame(crl::time trackTime) -> PresentFrame {
|
||||||
const auto present = [&](int counter, int index) -> PresentFrame {
|
const auto present = [&](int counter, int index) -> PresentFrame {
|
||||||
const auto frame = getFrame(index);
|
const auto frame = getFrame(index);
|
||||||
Assert(IsPrepared(frame));
|
Assert(IsPrepared(frame));
|
||||||
const auto position = frame->position;
|
const auto position = frame->displayPosition;
|
||||||
|
|
||||||
|
// Release this frame to the main thread for rendering.
|
||||||
_counter.store(
|
_counter.store(
|
||||||
(counter + 1) % (2 * kFramesCount),
|
(counter + 1) % (2 * kFramesCount),
|
||||||
std::memory_order_release);
|
std::memory_order_release);
|
||||||
|
@ -338,7 +361,7 @@ auto VideoTrack::Shared::presentFrame(crl::time trackTime) -> PresentFrame {
|
||||||
|| IsStale(frame, trackTime)) {
|
|| IsStale(frame, trackTime)) {
|
||||||
return { kTimeUnknown, crl::time(0) };
|
return { kTimeUnknown, crl::time(0) };
|
||||||
}
|
}
|
||||||
return { kTimeUnknown, (trackTime - frame->position + 1) };
|
return { kTimeUnknown, (trackTime - frame->displayPosition + 1) };
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (counter()) {
|
switch (counter()) {
|
||||||
|
@ -354,27 +377,27 @@ auto VideoTrack::Shared::presentFrame(crl::time trackTime) -> PresentFrame {
|
||||||
Unexpected("Counter value in VideoTrack::Shared::prepareState.");
|
Unexpected("Counter value in VideoTrack::Shared::prepareState.");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoTrack::Shared::markFrameDisplayed(crl::time now) {
|
crl::time VideoTrack::Shared::markFrameDisplayed(crl::time now) {
|
||||||
const auto markAndJump = [&](int counter, int index) {
|
const auto markAndJump = [&](int counter, int index) {
|
||||||
const auto frame = getFrame(index);
|
const auto frame = getFrame(index);
|
||||||
if (frame->displayed == kTimeUnknown) {
|
Assert(frame->displayed == kTimeUnknown);
|
||||||
frame->displayed = now;
|
|
||||||
}
|
frame->displayed = now;
|
||||||
_counter.store(
|
_counter.store(
|
||||||
(counter + 1) % (2 * kFramesCount),
|
(counter + 1) % (2 * kFramesCount),
|
||||||
std::memory_order_release);
|
std::memory_order_release);
|
||||||
return true;
|
return frame->position;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
switch (counter()) {
|
switch (counter()) {
|
||||||
case 0: return false;
|
case 0: return kTimeUnknown;
|
||||||
case 1: return markAndJump(1, 1);
|
case 1: return markAndJump(1, 1);
|
||||||
case 2: return false;
|
case 2: return kTimeUnknown;
|
||||||
case 3: return markAndJump(3, 2);
|
case 3: return markAndJump(3, 2);
|
||||||
case 4: return false;
|
case 4: return kTimeUnknown;
|
||||||
case 5: return markAndJump(5, 3);
|
case 5: return markAndJump(5, 3);
|
||||||
case 6: return false;
|
case 6: return kTimeUnknown;
|
||||||
case 7: return markAndJump(7, 0);
|
case 7: return markAndJump(7, 0);
|
||||||
}
|
}
|
||||||
Unexpected("Counter value in VideoTrack::Shared::markFrameDisplayed.");
|
Unexpected("Counter value in VideoTrack::Shared::markFrameDisplayed.");
|
||||||
|
@ -422,13 +445,14 @@ void VideoTrack::start() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoTrack::markFrameDisplayed(crl::time now) {
|
crl::time VideoTrack::markFrameDisplayed(crl::time now) {
|
||||||
if (!_shared->markFrameDisplayed(now)) {
|
const auto position = _shared->markFrameDisplayed(now);
|
||||||
return;
|
if (position != kTimeUnknown) {
|
||||||
|
_wrapped.with([](Implementation &unwrapped) {
|
||||||
|
unwrapped.frameDisplayed();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
_wrapped.with([](Implementation &unwrapped) {
|
return position;
|
||||||
unwrapped.frameDisplayed();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage VideoTrack::frame(const FrameRequest &request) const {
|
QImage VideoTrack::frame(const FrameRequest &request) const {
|
||||||
|
|
|
@ -34,7 +34,8 @@ public:
|
||||||
|
|
||||||
// Called from the main thread.
|
// Called from the main thread.
|
||||||
void start();
|
void start();
|
||||||
void markFrameDisplayed(crl::time now);
|
// Returns the position of the displayed frame.
|
||||||
|
[[nodiscard]] crl::time markFrameDisplayed(crl::time now);
|
||||||
[[nodiscard]] QImage frame(const FrameRequest &request) const;
|
[[nodiscard]] QImage frame(const FrameRequest &request) const;
|
||||||
[[nodiscard]] rpl::producer<crl::time> renderNextFrame() const;
|
[[nodiscard]] rpl::producer<crl::time> renderNextFrame() const;
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ private:
|
||||||
struct Frame {
|
struct Frame {
|
||||||
QImage original;
|
QImage original;
|
||||||
crl::time position = kTimeUnknown;
|
crl::time position = kTimeUnknown;
|
||||||
//crl::time presentation = kTimeUnknown;
|
crl::time displayPosition = kTimeUnknown;
|
||||||
crl::time displayed = kTimeUnknown;
|
crl::time displayed = kTimeUnknown;
|
||||||
|
|
||||||
FrameRequest request;
|
FrameRequest request;
|
||||||
|
@ -62,7 +63,7 @@ private:
|
||||||
PrepareFrame,
|
PrepareFrame,
|
||||||
PrepareNextCheck>;
|
PrepareNextCheck>;
|
||||||
struct PresentFrame {
|
struct PresentFrame {
|
||||||
crl::time position = kTimeUnknown;
|
crl::time displayPosition = kTimeUnknown;
|
||||||
crl::time nextCheckDelay = 0;
|
crl::time nextCheckDelay = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -74,7 +75,8 @@ private:
|
||||||
[[nodiscard]] PresentFrame presentFrame(crl::time trackTime);
|
[[nodiscard]] PresentFrame presentFrame(crl::time trackTime);
|
||||||
|
|
||||||
// Called from the main thread.
|
// Called from the main thread.
|
||||||
[[nodiscard]] bool markFrameDisplayed(crl::time now);
|
// Returns the position of the displayed frame.
|
||||||
|
[[nodiscard]] crl::time markFrameDisplayed(crl::time now);
|
||||||
[[nodiscard]] not_null<Frame*> frameForPaint();
|
[[nodiscard]] not_null<Frame*> frameForPaint();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -73,10 +73,10 @@ public:
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
auto events_starting_with(Value &&value) const {
|
auto events_starting_with(Value &&value) const {
|
||||||
return single(std::move(value)) | then(events());
|
return single<Value&&, Error>(std::move(value)) | then(events());
|
||||||
}
|
}
|
||||||
auto events_starting_with_copy(const Value &value) const {
|
auto events_starting_with_copy(const Value &value) const {
|
||||||
return single(value) | then(events());
|
return single<const Value&, Error>(value) | then(events());
|
||||||
}
|
}
|
||||||
bool has_consumers() const {
|
bool has_consumers() const {
|
||||||
return (_data != nullptr) && !_data->consumers.empty();
|
return (_data != nullptr) && !_data->consumers.empty();
|
||||||
|
|
|
@ -61,7 +61,7 @@ constexpr bool supports_equality_compare_v
|
||||||
|
|
||||||
} // namespace details
|
} // namespace details
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type, typename Error = no_error>
|
||||||
class variable final {
|
class variable final {
|
||||||
public:
|
public:
|
||||||
variable() : _data{} {
|
variable() : _data{} {
|
||||||
|
@ -127,6 +127,30 @@ public:
|
||||||
return _changes.events();
|
return _changes.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send 'done' to all subscribers and unsubscribe them.
|
||||||
|
template <
|
||||||
|
typename OtherType,
|
||||||
|
typename = std::enable_if_t<
|
||||||
|
std::is_assignable_v<Type&, OtherType>>>
|
||||||
|
void reset(OtherType &&data) {
|
||||||
|
_data = std::forward<OtherType>(data);
|
||||||
|
_changes = event_stream<Type, Error>();
|
||||||
|
}
|
||||||
|
void reset() {
|
||||||
|
reset(Type());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename OtherError,
|
||||||
|
typename = std::enable_if_t<
|
||||||
|
std::is_constructible_v<Error, OtherError&&>>>
|
||||||
|
void reset_with_error(OtherError &&error) {
|
||||||
|
_changes.fire_error(std::forward<OtherError>(error));
|
||||||
|
}
|
||||||
|
void reset_with_error() {
|
||||||
|
reset_with_error(Error());
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <typename OtherType>
|
template <typename OtherType>
|
||||||
variable &assign(OtherType &&data) {
|
variable &assign(OtherType &&data) {
|
||||||
|
@ -149,7 +173,7 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
Type _data;
|
Type _data;
|
||||||
event_stream<Type> _changes;
|
event_stream<Type, Error> _changes;
|
||||||
lifetime _lifetime;
|
lifetime _lifetime;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue