If stuck wait for three seconds of packets.

This commit is contained in:
John Preston 2019-02-22 18:28:10 +04:00
parent 3e9b811875
commit d37b65e624
17 changed files with 302 additions and 147 deletions

View File

@ -297,9 +297,10 @@ void StartStreaming(
static auto player = std::unique_ptr<Player>();
static auto pauseOnSeek = false;
static auto position = crl::time(0);
static auto preloaded = crl::time(0);
static auto preloadedAudio = crl::time(0);
static auto preloadedVideo = crl::time(0);
static auto duration = crl::time(0);
static auto options = Media::Streaming::PlaybackOptions();
static auto options = PlaybackOptions();
static auto speed = 1.;
static auto step = pow(2., 1. / 12);
static auto frame = QImage();
@ -343,10 +344,14 @@ void StartStreaming(
if (player->ready()) {
frame = player->frame({});
}
preloaded = position = options.position = std::clamp(
(duration * e->pos().x()) / width(),
crl::time(0),
crl::time(duration));
preloadedAudio
= preloadedVideo
= position
= options.position
= std::clamp(
(duration * e->pos().x()) / width(),
crl::time(0),
crl::time(duration));
player->play(options);
}
@ -367,7 +372,7 @@ void StartStreaming(
options.speed = speed;
//options.syncVideoByAudio = false;
preloaded = position = options.position = 0;
preloadedAudio = preloadedVideo = position = options.position = 0;
frame = QImage();
player->play(options);
player->updates(
@ -391,7 +396,9 @@ void StartStreaming(
? (position * video->width() / duration)
: 0;
const auto till2 = duration
? (preloaded * video->width() / duration)
? (std::min(preloadedAudio, preloadedVideo)
* video->width()
/ duration)
: 0;
if (player->ready()) {
Painter(video.get()).drawImage(
@ -437,9 +444,11 @@ void StartStreaming(
}, video->lifetime());
}
}, [&](PreloadedVideo &update) {
if (preloaded < update.till) {
preloaded = update.till;
video->update();
if (preloadedVideo < update.till) {
if (preloadedVideo < preloadedAudio) {
video->update();
}
preloadedVideo = update.till;
}
}, [&](UpdateVideo &update) {
Expects(video != nullptr);
@ -449,11 +458,11 @@ void StartStreaming(
}
video->update();
}, [&](PreloadedAudio &update) {
if (preloaded < update.till) {
preloaded = update.till;
if (video) {
if (preloadedAudio < update.till) {
if (video && preloadedAudio < preloadedVideo) {
video->update();
}
preloadedAudio = update.till;
}
}, [&](UpdateAudio &update) {
if (position < update.position) {

View File

@ -645,7 +645,7 @@ bool HistoryDocument::updateStatusText() const {
statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
showPause = Media::Player::ShowPauseIcon(state.state);
} else {
if (auto voice = Get<HistoryDocumentVoice>()) {
voice->checkPlaybackFinished();
@ -660,7 +660,7 @@ bool HistoryDocument::updateStatusText() const {
&& !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
showPause = Media::Player::ShowPauseIcon(state.state);
} else {
}
if (!showPause && (state.id == AudioMsgId(_data, _parent->data()->fullId()))) {

View File

@ -901,7 +901,7 @@ bool File::updateStatusText() const {
if (state.id == AudioMsgId(_document, FullMsgId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
showPause = Media::Player::ShowPauseIcon(state.state);
}
} else if (_document->isAudioFile()) {
statusSize = FileStatusSizeLoaded;
@ -909,7 +909,7 @@ bool File::updateStatusText() const {
if (state.id == AudioMsgId(_document, FullMsgId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
showPause = Media::Player::ShowPauseIcon(state.state);
}
if (!showPause && (state.id == AudioMsgId(_document, FullMsgId())) && Media::Player::instance()->isSeeking(AudioMsgId::Type::Song)) {
showPause = true;

View File

@ -436,7 +436,9 @@ void Mixer::Track::reattach(AudioMsgId::Type type) {
}
alSourcei(stream.source, AL_SAMPLE_OFFSET, qMax(state.position - bufferedPosition, 0LL));
if (!IsStopped(state.state) && state.state != State::PausedAtEnd) {
if (!IsStopped(state.state)
&& (state.state != State::PausedAtEnd)
&& !state.waitingForData) {
alSourcef(stream.source, AL_GAIN, ComputeVolume(type));
alSourcePlay(stream.source);
if (IsPaused(state.state)) {
@ -449,6 +451,7 @@ void Mixer::Track::reattach(AudioMsgId::Type type) {
}
void Mixer::Track::detach() {
getNotQueuedBufferIndex();
resetStream();
destroyStream();
}
@ -711,20 +714,28 @@ void Mixer::resetFadeStartPosition(AudioMsgId::Type type, int positionInBuffered
if (positionInBuffered < 0) {
Audio::AttachToDevice();
if (track->isStreamCreated()) {
ALint currentPosition = 0;
alGetSourcei(track->stream.source, AL_SAMPLE_OFFSET, &currentPosition);
ALint alSampleOffset = 0;
ALint alState = AL_INITIAL;
alGetSourcei(track->stream.source, AL_SAMPLE_OFFSET, &alSampleOffset);
alGetSourcei(track->stream.source, AL_SOURCE_STATE, &alState);
if (Audio::PlaybackErrorHappened()) {
setStoppedState(track, State::StoppedAtError);
onError(track->state.id);
return;
}
if (currentPosition == 0 && !internal::CheckAudioDeviceConnected()) {
} else if ((alState == AL_STOPPED)
&& (alSampleOffset == 0)
&& !internal::CheckAudioDeviceConnected()) {
track->fadeStartPosition = track->state.position;
return;
}
positionInBuffered = currentPosition;
const auto stoppedAtEnd = (alState == AL_STOPPED)
&& (!IsStopped(track->state.state)
|| IsStoppedAtEnd(track->state.state))
|| track->state.waitingForData;
positionInBuffered = stoppedAtEnd
? track->bufferedLength
: alSampleOffset;
} else {
positionInBuffered = 0;
}
@ -1406,10 +1417,7 @@ void Fader::onTimer() {
}
int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasFading, float64 volumeMultiplier, bool volumeChanged) {
auto playing = false;
auto fading = false;
auto errorHappened = [this, track] {
const auto errorHappened = [&] {
if (Audio::PlaybackErrorHappened()) {
setStoppedState(track, State::StoppedAtError);
return true;
@ -1417,32 +1425,34 @@ int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasF
return false;
};
ALint positionInBuffered = 0;
ALint state = AL_INITIAL;
alGetSourcei(track->stream.source, AL_SAMPLE_OFFSET, &positionInBuffered);
alGetSourcei(track->stream.source, AL_SOURCE_STATE, &state);
if (errorHappened()) return EmitError;
ALint alSampleOffset = 0;
ALint alState = AL_INITIAL;
alGetSourcei(track->stream.source, AL_SAMPLE_OFFSET, &alSampleOffset);
alGetSourcei(track->stream.source, AL_SOURCE_STATE, &alState);
if (errorHappened()) {
return EmitError;
} else if ((alState == AL_STOPPED)
&& (alSampleOffset == 0)
&& !internal::CheckAudioDeviceConnected()) {
return 0;
}
int32 emitSignals = 0;
const auto stoppedAtEnd = (alState == AL_STOPPED)
&& (!IsStopped(track->state.state)
|| IsStoppedAtEnd(track->state.state))
|| track->state.waitingForData;
const auto positionInBuffered = stoppedAtEnd
? track->bufferedLength
: alSampleOffset;
const auto waitingForDataOld = track->state.waitingForData;
track->state.waitingForData = stoppedAtEnd
&& (track->state.state != State::Stopping);
const auto fullPosition = track->bufferedPosition + positionInBuffered;
if (state == AL_STOPPED && positionInBuffered == 0 && !internal::CheckAudioDeviceConnected()) {
return emitSignals;
}
switch (track->state.state) {
case State::Stopping:
case State::Pausing:
case State::Starting:
case State::Resuming: {
fading = true;
} break;
case State::Playing: {
playing = true;
} break;
}
auto fullPosition = track->bufferedPosition + positionInBuffered;
if (state != AL_PLAYING && !track->loading) {
auto playing = (track->state.state == State::Playing);
auto fading = IsFading(track->state.state);
if (alState != AL_PLAYING && !track->loading) {
if (fading || playing) {
fading = false;
playing = false;
@ -1456,7 +1466,7 @@ int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasF
if (errorHappened()) return EmitError;
emitSignals |= EmitStopped;
}
} else if (fading && state == AL_PLAYING) {
} else if (fading && alState == AL_PLAYING) {
auto fadingForSamplesCount = (fullPosition - track->fadeStartPosition);
if (crl::time(1000) * fadingForSamplesCount >= kFadeDuration * track->state.frequency) {
fading = false;
@ -1466,7 +1476,7 @@ int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasF
switch (track->state.state) {
case State::Stopping: {
setStoppedState(track);
state = AL_STOPPED;
alState = AL_STOPPED;
} break;
case State::Pausing: {
alSourcePause(track->stream.source);
@ -1488,15 +1498,22 @@ int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasF
alSourcef(track->stream.source, AL_GAIN, newGain * volumeMultiplier);
if (errorHappened()) return EmitError;
}
} else if (playing && state == AL_PLAYING) {
} else if (playing && alState == AL_PLAYING) {
if (volumeChanged) {
alSourcef(track->stream.source, AL_GAIN, 1. * volumeMultiplier);
if (errorHappened()) return EmitError;
}
}
if (state == AL_PLAYING && fullPosition >= track->state.position + kCheckPlaybackPositionDelta) {
if (alState == AL_PLAYING && fullPosition >= track->state.position + kCheckPlaybackPositionDelta) {
track->state.position = fullPosition;
emitSignals |= EmitPositionUpdated;
} else if (track->state.waitingForData && !waitingForDataOld) {
if (fullPosition > track->state.position) {
track->state.position = fullPosition;
}
// When stopped because of insufficient data while streaming,
// inform the player about the last position we were at.
emitSignals |= EmitPositionUpdated;
}
if (playing || track->state.state == State::Starting || track->state.state == State::Resuming) {
if (!track->loaded && !track->loading) {

View File

@ -88,6 +88,10 @@ inline bool IsPaused(State state) {
|| (state == State::PausedAtEnd);
}
inline bool IsPausedOrPausing(State state) {
return IsPaused(state) || (state == State::Pausing);
}
inline bool IsFading(State state) {
return (state == State::Starting)
|| (state == State::Stopping)
@ -99,12 +103,18 @@ inline bool IsActive(State state) {
return !IsStopped(state) && !IsPaused(state);
}
inline bool ShowPauseIcon(State state) {
return !IsStoppedOrStopping(state)
&& !IsPausedOrPausing(state);
}
struct TrackState {
AudioMsgId id;
State state = State::Stopped;
int64 position = 0;
int64 length = 0;
int frequency = kDefaultFrequency;
bool waitingForData = false;
};
class Mixer : public QObject, private base::Subscriber {

View File

@ -227,9 +227,10 @@ void Loaders::loadData(AudioMsgId audio, crl::time positionMs) {
return;
}
if (started) {
if (started || samplesCount) {
Audio::AttachToDevice();
}
if (started) {
track->started();
if (!internal::audioCheckError()) {
setStoppedState(track, State::StoppedAtStart);
@ -263,12 +264,6 @@ void Loaders::loadData(AudioMsgId audio, crl::time positionMs) {
l->setForceToBuffer(false);
}
//LOG(("[%4] PUSHING %1 SAMPLES (%2 BYTES) %3ms"
// ).arg(samplesCount
// ).arg(samples.size()
// ).arg((samplesCount * 1000LL) / track->frequency
// ).arg(crl::now() % 10000, 4, 10, QChar('0')));
track->bufferSamples[bufferIndex] = samples;
track->samplesCount[bufferIndex] = samplesCount;
track->bufferedLength += samplesCount;
@ -287,6 +282,7 @@ void Loaders::loadData(AudioMsgId audio, crl::time positionMs) {
}
finished = true;
}
track->state.waitingForData = false;
if (finished) {
track->loaded = true;
@ -295,44 +291,47 @@ void Loaders::loadData(AudioMsgId audio, crl::time positionMs) {
}
track->loading = false;
if (track->state.state == State::Resuming || track->state.state == State::Playing || track->state.state == State::Starting) {
ALint state = AL_INITIAL;
alGetSourcei(track->stream.source, AL_SOURCE_STATE, &state);
if (internal::audioCheckError()) {
if (state != AL_PLAYING) {
if (state == AL_STOPPED && !internal::CheckAudioDeviceConnected()) {
return;
}
if (IsPausedOrPausing(track->state.state)
|| IsStoppedOrStopping(track->state.state)) {
return;
}
ALint state = AL_INITIAL;
alGetSourcei(track->stream.source, AL_SOURCE_STATE, &state);
if (!internal::audioCheckError()) {
setStoppedState(track, State::StoppedAtError);
emitError(type);
return;
}
alSourcef(track->stream.source, AL_GAIN, ComputeVolume(type));
if (!internal::audioCheckError()) {
setStoppedState(track, State::StoppedAtError);
emitError(type);
return;
}
if (state == AL_PLAYING) {
return;
} else if (state == AL_STOPPED && !internal::CheckAudioDeviceConnected()) {
return;
}
if (state == AL_STOPPED) {
alSourcei(track->stream.source, AL_SAMPLE_OFFSET, qMax(track->state.position - track->bufferedPosition, 0LL));
if (!internal::audioCheckError()) {
setStoppedState(track, State::StoppedAtError);
emitError(type);
return;
}
}
alSourcePlay(track->stream.source);
if (!internal::audioCheckError()) {
setStoppedState(track, State::StoppedAtError);
emitError(type);
return;
}
alSourcef(track->stream.source, AL_GAIN, ComputeVolume(type));
if (!internal::audioCheckError()) {
setStoppedState(track, State::StoppedAtError);
emitError(type);
return;
}
emit needToCheck();
}
} else {
if (state == AL_STOPPED) {
alSourcei(track->stream.source, AL_SAMPLE_OFFSET, qMax(track->state.position - track->bufferedPosition, 0LL));
if (!internal::audioCheckError()) {
setStoppedState(track, State::StoppedAtError);
emitError(type);
return;
}
}
alSourcePlay(track->stream.source);
if (!internal::audioCheckError()) {
setStoppedState(track, State::StoppedAtError);
emitError(type);
return;
}
emit needToCheck();
}
AudioPlayerLoader *Loaders::setupLoader(

View File

@ -246,7 +246,7 @@ void CoverWidget::handleSongUpdate(const TrackState &state) {
}
auto stopped = IsStoppedOrStopping(state.state);
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
auto showPause = ShowPauseIcon(state.state);
if (instance()->isSeeking(AudioMsgId::Type::Song)) {
showPause = true;
}

View File

@ -403,7 +403,7 @@ void Instance::playPauseCancelClicked(AudioMsgId::Type type) {
auto state = mixer()->currentState(type);
auto stopped = IsStoppedOrStopping(state.state);
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
auto showPause = ShowPauseIcon(state.state);
auto audio = state.id.audio();
if (audio && audio->loading()) {
audio->cancel();

View File

@ -436,7 +436,7 @@ void Widget::handleSongUpdate(const TrackState &state) {
}
auto stopped = IsStoppedOrStopping(state.state);
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
auto showPause = ShowPauseIcon(state.state);
if (instance()->isSeeking(_type)) {
showPause = true;
}

View File

@ -164,6 +164,10 @@ void AudioTrack::setSpeed(float64 speed) {
Media::Player::mixer()->setSpeedFromVideo(_audioId, speed);
}
rpl::producer<> AudioTrack::waitingForData() const {
return _waitingForData.events();
}
rpl::producer<crl::time> AudioTrack::playPosition() {
Expects(_ready == nullptr);
@ -194,6 +198,9 @@ rpl::producer<crl::time> AudioTrack::playPosition() {
case State::Stopping:
case State::Pausing:
case State::Resuming:
if (state.waitingForData) {
_waitingForData.fire({});
}
_playPosition = state.position * 1000 / state.frequency;
return;
case State::Paused:

View File

@ -30,6 +30,7 @@ public:
// Called from the main thread.
void setSpeed(float64 speed);
[[nodiscard]] rpl::producer<> waitingForData() const;
// Called from the main thread.
// Non-const, because we subscribe to changes on the first call.
@ -75,6 +76,7 @@ private:
// Accessed from the main thread.
base::Subscription _subscription;
rpl::event_stream<> _waitingForData;
// First set from the same unspecified thread before _ready is called.
// After that accessed from the main thread.
rpl::variable<crl::time> _playPosition;

View File

@ -17,6 +17,24 @@ namespace Media {
namespace Streaming {
namespace {
constexpr auto kReceivedTillEnd = std::numeric_limits<crl::time>::max();
constexpr auto kBufferFor = crl::time(3000);
[[nodiscard]] crl::time TrackClampReceivedTill(
crl::time position,
const TrackState &state) {
return (state.duration == kTimeUnknown || position == kTimeUnknown)
? position
: (position == kReceivedTillEnd)
? state.duration
: std::clamp(position, 0LL, state.duration - 1);
}
[[nodiscard]] bool FullTrackReceived(const TrackState &state) {
return (state.duration != kTimeUnknown)
&& (state.receivedTill == state.duration);
}
void SaveValidStateInformation(TrackState &to, TrackState &&from) {
Expects(from.position != kTimeUnknown);
Expects(from.receivedTill != kTimeUnknown);
@ -74,6 +92,7 @@ not_null<FileDelegate*> Player::delegate() {
void Player::checkNextFrame() {
Expects(_nextFrameTime != kTimeUnknown);
Expects(!_renderFrameTimer.isActive());
const auto now = crl::now();
if (now < _nextFrameTime) {
@ -85,12 +104,12 @@ void Player::checkNextFrame() {
}
void Player::renderFrame(crl::time now) {
if (_video) {
const auto position = _video->markFrameDisplayed(now);
if (position != kTimeUnknown) {
videoPlayedTill(position);
}
}
Expects(_video != nullptr);
const auto position = _video->markFrameDisplayed(now);
Assert(position != kTimeUnknown);
videoPlayedTill(position);
}
template <typename Track>
@ -104,7 +123,7 @@ void Player::trackReceivedTill(
position = std::clamp(position, 0LL, state.duration);
if (state.receivedTill < position) {
state.receivedTill = position;
_updates.fire({ PreloadedUpdate<Track>{ position } });
trackSendReceivedTill(track, state);
}
} else {
state.receivedTill = position;
@ -125,10 +144,22 @@ void Player::trackPlayedTill(
}
}
template <typename Track>
void Player::trackSendReceivedTill(
const Track &track,
TrackState &state) {
Expects(state.duration != kTimeUnknown);
Expects(state.receivedTill != kTimeUnknown);
_updates.fire({ PreloadedUpdate<Track>{ state.receivedTill } });
}
void Player::audioReceivedTill(crl::time position) {
Expects(_audio != nullptr);
position = TrackClampReceivedTill(position, _information.audio.state);
trackReceivedTill(*_audio, _information.audio.state, position);
checkResumeFromWaitingForData();
}
void Player::audioPlayedTill(crl::time position) {
@ -140,6 +171,7 @@ void Player::audioPlayedTill(crl::time position) {
void Player::videoReceivedTill(crl::time position) {
Expects(_video != nullptr);
position = TrackClampReceivedTill(position, _information.video.state);
trackReceivedTill(*_video, _information.video.state, position);
}
@ -298,12 +330,13 @@ void Player::provideStartInformation() {
if (_stage == Stage::Ready && !_paused) {
_paused = true;
resume();
updatePausedState();
}
}
}
void Player::fail() {
_sessionLifetime = rpl::lifetime();
const auto stopGuarded = crl::guard(&_sessionGuard, [=] { stop(); });
_stage = Stage::Failed;
_updates.fire_error({});
@ -326,11 +359,35 @@ void Player::play(const PlaybackOptions &options) {
void Player::pause() {
Expects(valid());
if (_paused) {
_pausedByUser = true;
updatePausedState();
}
void Player::resume() {
Expects(valid());
_pausedByUser = false;
updatePausedState();
}
void Player::updatePausedState() {
const auto paused = _pausedByUser || _pausedByWaitingForData;
if (_paused == paused) {
return;
}
_paused = true;
if (_stage == Stage::Started) {
_paused = paused;
if (!_paused && _stage == Stage::Ready) {
const auto guard = base::make_weak(&_sessionGuard);
start();
if (!guard) {
return;
}
}
if (_stage != Stage::Started) {
return;
}
if (_paused) {
_pausedTime = crl::now();
if (_audio) {
_audio->pause(_pausedTime);
@ -338,20 +395,7 @@ void Player::pause() {
if (_video) {
_video->pause(_pausedTime);
}
}
}
void Player::resume() {
Expects(valid());
if (!_paused) {
return;
}
_paused = false;
if (_stage == Stage::Ready) {
start();
}
if (_stage == Stage::Started) {
} else {
_startedTime = crl::now();
if (_audio) {
_audio->resume(_startedTime);
@ -362,48 +406,85 @@ void Player::resume() {
}
}
bool Player::trackReceivedEnough(const TrackState &state) const {
return FullTrackReceived(state)
|| (state.position + kBufferFor <= state.receivedTill);
}
void Player::checkResumeFromWaitingForData() {
if (_pausedByWaitingForData
&& (!_audio || trackReceivedEnough(_information.audio.state))
&& (!_video || trackReceivedEnough(_information.video.state))) {
_pausedByWaitingForData = false;
updatePausedState();
}
}
void Player::start() {
Expects(_stage == Stage::Ready);
_stage = Stage::Started;
if (_audio) {
const auto guard = base::make_weak(&_sessionGuard);
rpl::merge(
_audio ? _audio->waitingForData() : rpl::never(),
_video ? _video->waitingForData() : rpl::never()
) | rpl::filter([=] {
return !FullTrackReceived(_information.video.state)
|| !FullTrackReceived(_information.audio.state);
}) | rpl::start_with_next([=] {
_pausedByWaitingForData = true;
updatePausedState();
_updates.fire({ WaitingForData() });
}, _sessionLifetime);
if (guard && _audio) {
_audio->playPosition(
) | rpl::start_with_next_done([=](crl::time position) {
audioPlayedTill(position);
}, [=] {
if (_stage == Stage::Started) {
_audioFinished = true;
if (!_video || _videoFinished) {
_updates.fire({ Finished() });
}
Expects(_stage == Stage::Started);
_audioFinished = true;
if (!_video || _videoFinished) {
_updates.fire({ Finished() });
}
}, _lifetime);
}, _sessionLifetime);
}
if (_video) {
if (guard && _video) {
_video->renderNextFrame(
) | rpl::start_with_next_done([=](crl::time when) {
_nextFrameTime = when;
checkNextFrame();
}, [=] {
if (_stage == Stage::Started) {
_videoFinished = true;
if (!_audio || _audioFinished) {
_updates.fire({ Finished() });
}
}
}, _lifetime);
}
Expects(_stage == Stage::Started);
_videoFinished = true;
if (!_audio || _audioFinished) {
_updates.fire({ Finished() });
}
}, _sessionLifetime);
}
if (guard && _audio) {
trackSendReceivedTill(*_audio, _information.audio.state);
}
if (guard && _video) {
trackSendReceivedTill(*_video, _information.video.state);
}
}
void Player::stop() {
_file->stop();
_sessionLifetime = rpl::lifetime();
if (_stage != Stage::Failed) {
_stage = Stage::Uninitialized;
}
_audio = nullptr;
_video = nullptr;
invalidate_weak_ptrs(&_sessionGuard);
_paused = false;
_pausedByUser = _pausedByWaitingForData = _paused = false;
_renderFrameTimer.cancel();
_audioFinished = false;
_videoFinished = false;
_readTillEnd = false;
@ -418,8 +499,12 @@ bool Player::playing() const {
return (_stage == Stage::Started) && !_paused;
}
bool Player::buffering() const {
return _pausedByWaitingForData;
}
bool Player::paused() const {
return _paused;
return _pausedByUser;
}
void Player::setSpeed(float64 speed) {

View File

@ -46,6 +46,7 @@ public:
[[nodiscard]] bool failed() const;
[[nodiscard]] bool playing() const;
[[nodiscard]] bool buffering() const;
[[nodiscard]] bool paused() const;
[[nodiscard]] rpl::producer<Update, Error> updates() const;
@ -57,9 +58,6 @@ public:
~Player();
private:
static constexpr auto kReceivedTillEnd
= std::numeric_limits<crl::time>::max();
enum class Stage {
Uninitialized,
Initializing,
@ -91,12 +89,21 @@ private:
void videoReceivedTill(crl::time position);
void videoPlayedTill(crl::time position);
void updatePausedState();
[[nodiscard]] bool trackReceivedEnough(const TrackState &state) const;
void checkResumeFromWaitingForData();
template <typename Track>
void trackReceivedTill(
const Track &track,
TrackState &state,
crl::time position);
template <typename Track>
void trackSendReceivedTill(
const Track &track,
TrackState &state);
template <typename Track>
void trackPlayedTill(
const Track &track,
@ -121,6 +128,8 @@ private:
// Belongs to the main thread.
Information _information;
Stage _stage = Stage::Uninitialized;
bool _pausedByUser = false;
bool _pausedByWaitingForData = false;
bool _paused = false;
bool _audioFinished = false;
bool _videoFinished = false;
@ -130,7 +139,9 @@ private:
crl::time _nextFrameTime = kTimeUnknown;
base::Timer _renderFrameTimer;
rpl::event_stream<Update, Error> _updates;
rpl::lifetime _lifetime;
rpl::lifetime _sessionLifetime;
};

View File

@ -36,6 +36,7 @@ public:
void process(Packet &&packet);
[[nodisacrd]] rpl::producer<crl::time> displayFrameAt() const;
[[nodisacrd]] rpl::producer<> waitingForData() const;
void pause(crl::time time);
void resume(crl::time time);
@ -76,6 +77,7 @@ private:
mutable TimePoint _syncTimePoint;
mutable crl::time _previousFramePosition = kTimeUnknown;
rpl::variable<crl::time> _nextFrameDisplayTime = kTimeUnknown;
rpl::event_stream<> _waitingForData;
bool _queued = false;
base::ConcurrentTimer _readFramesTimer;
@ -111,6 +113,10 @@ rpl::producer<crl::time> VideoTrackObject::displayFrameAt() const {
: _nextFrameDisplayTime.value();
}
rpl::producer<> VideoTrackObject::waitingForData() const {
return interrupted() ? rpl::never() : _waitingForData.events();
}
void VideoTrackObject::process(Packet &&packet) {
if (interrupted()) {
return;
@ -162,6 +168,8 @@ bool VideoTrackObject::readFrame(not_null<Frame*> frame) {
} else if (error.code() != AVERROR(EAGAIN) || _noMoreData) {
interrupt();
_error();
} else if (_stream.queue.empty()) {
_waitingForData.fire({});
}
return false;
}
@ -604,6 +612,12 @@ rpl::producer<crl::time> VideoTrack::renderNextFrame() const {
});
}
rpl::producer<> VideoTrack::waitingForData() const {
return _wrapped.producer_on_main([](const Implementation &unwrapped) {
return unwrapped.waitingForData();
});
}
VideoTrack::~VideoTrack() {
_wrapped.with([shared = std::move(_shared)](Implementation &unwrapped) {
unwrapped.interrupt();

View File

@ -48,6 +48,7 @@ public:
[[nodiscard]] crl::time markFrameDisplayed(crl::time now);
[[nodiscard]] QImage frame(const FrameRequest &request) const;
[[nodiscard]] rpl::producer<crl::time> renderNextFrame() const;
[[nodiscard]] rpl::producer<> waitingForData() const;
// Called from the main thread.
~VideoTrack();

View File

@ -131,7 +131,7 @@ void Controller::updatePlayback(const Player::TrackState &state) {
}
void Controller::updatePlayPauseResumeState(const Player::TrackState &state) {
auto showPause = (state.state == Player::State::Playing || state.state == Player::State::Resuming || _seekPositionMs >= 0);
auto showPause = ShowPauseIcon(state.state) || (_seekPositionMs >= 0);
if (showPause != _showPause) {
disconnect(_playPauseResume, SIGNAL(clicked()), this, _showPause ? SIGNAL(pausePressed()) : SIGNAL(playPressed()));
_showPause = showPause;

View File

@ -854,7 +854,7 @@ bool Voice::updateStatusText() {
if (state.id == AudioMsgId(_data, parent()->fullId(), state.id.playId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
showPause = Media::Player::ShowPauseIcon(state.state);
}
} else {
statusSize = FileStatusSizeReady;
@ -1225,7 +1225,7 @@ bool Document::updateStatusText() {
if (state.id == AudioMsgId(_data, parent()->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
statusSize = -1 - (state.position / state.frequency);
realDuration = (state.length / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
showPause = Media::Player::ShowPauseIcon(state.state);
}
if (!showPause && (state.id == AudioMsgId(_data, parent()->fullId())) && Media::Player::instance()->isSeeking(AudioMsgId::Type::Song)) {
showPause = true;