Using media player for voice / video messages.

Media::Player::Widget displays the current voice / video message
if it is played and the current song otherwise.

It is created when a voice / video message starts and is destroyed
when all the voice / video messages in the playlist are finished.
This commit is contained in:
John Preston 2017-05-21 16:16:39 +03:00
parent 2661fe5cd5
commit 7873cb4373
24 changed files with 325 additions and 192 deletions

View File

@ -1165,6 +1165,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_call_rate_label" = "Please rate the quality of your call";
"lng_call_rate_comment" = "Comment (optional)";
"lng_player_message_today" = "Today at {time}";
"lng_player_message_yesterday" = "Yesterday at {time}";
"lng_player_message_date" = "{date} at {time}";
// Not used
"lng_topbar_info" = "Info";

View File

@ -997,7 +997,7 @@ void HistoryItem::audioTrackUpdated() {
auto audio = reader->audioMsgId();
auto current = Media::Player::mixer()->currentState(audio.type());
if (current.id != audio || Media::Player::IsStopped(current.state) || current.state == Media::Player::State::Finishing) {
if (current.id != audio || Media::Player::IsStoppedOrStopping(current.state)) {
media->stopInline();
} else if (Media::Player::IsPaused(current.state) || current.state == Media::Player::State::Pausing) {
if (!reader->videoPaused()) {

View File

@ -1427,7 +1427,7 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req
auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin;
if (x >= nameleft && x < nameleft + namewidth && y >= nametop && y < waveformbottom) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state)) {
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
if (!voice->seeking()) {
voice->setSeekingStart((x - nameleft) / float64(namewidth));
}
@ -1585,7 +1585,7 @@ bool HistoryDocument::updateStatusText() const {
statusSize = FileStatusSizeLoaded;
if (_data->voice()) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
if (auto voice = Get<HistoryDocumentVoice>()) {
bool was = (voice->_playback != nullptr);
voice->ensurePlayback(this);
@ -1615,7 +1615,7 @@ bool HistoryDocument::updateStatusText() const {
}
} else if (_data->song()) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
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);
@ -2337,10 +2337,10 @@ void HistoryGif::updateStatusText() const {
if (state.id == _gif->audioMsgId()) {
if (state.length) {
auto position = int64(0);
if (!Media::Player::IsStopped(state.state) && state.state != Media::Player::State::Finishing) {
position = state.position;
} else if (state.state == Media::Player::State::StoppedAtEnd) {
if (Media::Player::IsStoppedAtEnd(state.state)) {
position = state.length;
} else if (!Media::Player::IsStoppedOrStopping(state.state)) {
position = state.position;
}
accumulate_max(statusSize, -1 - int((state.length - position) / state.frequency + 1));
}

View File

@ -836,7 +836,7 @@ bool File::updateStatusText() const {
if (document->voice()) {
statusSize = FileStatusSizeLoaded;
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(document, FullMsgId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
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);
@ -844,7 +844,7 @@ bool File::updateStatusText() const {
} else if (document->song()) {
statusSize = FileStatusSizeLoaded;
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (state.id == AudioMsgId(document, FullMsgId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
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);

View File

@ -186,6 +186,14 @@ MainWidget::MainWidget(QWidget *parent, gsl::not_null<Window::Controller*> contr
_playerPlaylist->hideFromOther();
}
});
subscribe(Media::Player::instance()->tracksFinishedNotifier(), [this](AudioMsgId::Type type) {
if (type == AudioMsgId::Type::Voice) {
auto songState = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (!songState.id || IsStoppedOrStopping(songState.state)) {
closeBothPlayers();
}
}
});
subscribe(Adaptive::Changed(), [this]() { handleAdaptiveLayoutUpdate(); });
@ -1540,14 +1548,9 @@ void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) {
}
}
if (state.id == audioId && audioId.type() == AudioMsgId::Type::Song) {
if (!Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
if (!_playerUsingPanel && !_player) {
createPlayer();
}
} else if (_player && _player->isHidden() && !_playerUsingPanel) {
_player.destroyDelayed();
_playerVolume.destroyDelayed();
if (state.id == audioId && (audioId.type() == AudioMsgId::Type::Song || audioId.type() == AudioMsgId::Type::Voice)) {
if (!Media::Player::IsStoppedOrStopping(state.state)) {
createPlayer();
}
}
@ -1604,30 +1607,38 @@ void MainWidget::closeBothPlayers() {
Media::Player::instance()->usePanelPlayer().notify(false, true);
_playerPanel->hideIgnoringEnterEvents();
_playerPlaylist->hideIgnoringEnterEvents();
Media::Player::instance()->stop(AudioMsgId::Type::Voice);
Media::Player::instance()->stop(AudioMsgId::Type::Song);
Shortcuts::disableMediaShortcuts();
}
void MainWidget::createPlayer() {
_player.create(this, [this] { playerHeightUpdated(); });
_player->entity()->setCloseCallback([this] { closeBothPlayers(); });
_playerVolume.create(this);
_player->entity()->volumeWidgetCreated(_playerVolume);
orderWidgets();
if (_a_show.animating()) {
_player->showFast();
_player->hide();
} else {
_player->hideFast();
if (_player) {
if (_playerUsingPanel) {
return;
}
if (!_player) {
_player.create(this, [this] { playerHeightUpdated(); });
_player->entity()->setCloseCallback([this] { closeBothPlayers(); });
_playerVolume.create(this);
_player->entity()->volumeWidgetCreated(_playerVolume);
orderWidgets();
if (_a_show.animating()) {
_player->showFast();
_player->hide();
Shortcuts::enableMediaShortcuts();
} else {
_player->hideFast();
}
}
if (_player && _player->isHiddenOrHiding()) {
if (!_a_show.animating()) {
_player->showAnimated();
_playerHeight = _contentScrollAddToY = _player->contentHeight();
updateControlsGeometry();
Shortcuts::enableMediaShortcuts();
}
}
Shortcuts::enableMediaShortcuts();
}
void MainWidget::playerHeightUpdated() {
@ -1638,8 +1649,8 @@ void MainWidget::playerHeightUpdated() {
updateControlsGeometry();
}
if (!_playerHeight && _player->isHidden()) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (state.id && Media::Player::IsStopped(state.state)) {
auto state = Media::Player::mixer()->currentState(Media::Player::instance()->getActiveType());
if (!state.id || Media::Player::IsStoppedOrStopping(state.state)) {
_playerVolume.destroyDelayed();
_player.destroyDelayed();
}

View File

@ -596,12 +596,12 @@ bool Mixer::fadedStop(AudioMsgId::Type type, bool *fadedStart) {
case State::Starting:
case State::Resuming:
case State::Playing: {
current->state.state = State::Finishing;
current->state.state = State::Stopping;
resetFadeStartPosition(type);
if (fadedStart) *fadedStart = true;
} break;
case State::Pausing: {
current->state.state = State::Finishing;
current->state.state = State::Stopping;
if (fadedStart) *fadedStart = true;
} break;
case State::Paused:
@ -642,7 +642,7 @@ void Mixer::play(const AudioMsgId &audio, std::unique_ptr<VideoSoundData> videoD
current->state.state = State::Pausing;
resetFadeStartPosition(type);
} break;
case State::Finishing: {
case State::Stopping: {
current->state.state = State::Pausing;
} break;
@ -781,7 +781,7 @@ void Mixer::pause(const AudioMsgId &audio, bool fast) {
}
} break;
case State::Finishing: {
case State::Stopping: {
track->state.state = fast ? State::Paused : State::Pausing;
} break;
}
@ -874,7 +874,7 @@ void Mixer::seek(AudioMsgId::Type type, int64 position) {
auto fastSeek = (position >= current->bufferedPosition && position < current->bufferedPosition + current->bufferedLength - (current->loaded ? 0 : kDefaultFrequency));
if (!streamCreated) {
fastSeek = false;
} else if (IsStopped(current->state.state) || (current->state.state == State::Finishing)) {
} else if (IsStoppedOrStopping(current->state.state)) {
fastSeek = false;
}
if (fastSeek) {
@ -908,7 +908,7 @@ void Mixer::seek(AudioMsgId::Type type, int64 position) {
emit unsuppressSong();
}
} break;
case State::Finishing:
case State::Stopping:
case State::Stopped:
case State::StoppedAtEnd:
case State::StoppedAtError:
@ -940,6 +940,29 @@ void Mixer::stop(const AudioMsgId &audio) {
if (current) emit updated(current);
}
void Mixer::stop(const AudioMsgId &audio, State state) {
Expects(IsStopped(state));
AudioMsgId current;
{
QMutexLocker lock(&AudioMutex);
auto type = audio.type();
auto track = trackForType(type);
if (!track || track->state.id != audio || IsStopped(track->state.state)) {
return;
}
current = track->state.id;
setStoppedState(track, state);
if (type == AudioMsgId::Type::Voice) {
emit unsuppressSong();
} else if (type == AudioMsgId::Type::Video) {
track->clear();
}
}
if (current) emit updated(current);
}
void Mixer::stopAndClear() {
Track *current_audio = nullptr, *current_song = nullptr;
{
@ -1012,11 +1035,7 @@ void Mixer::reattachIfNeeded() {
auto reattachNeeded = [this] {
auto isPlayingState = [](const Track &track) {
auto state = track.state.state;
return (state == State::Starting)
|| (state == State::Playing)
|| (state == State::Finishing)
|| (state == State::Pausing)
|| (state == State::Resuming);
return (state == State::Playing) || IsFading(state);
};
for (auto i = 0; i != kTogetherLimit; ++i) {
if (isPlayingState(*trackForType(AudioMsgId::Type::Voice, i))
@ -1174,7 +1193,7 @@ int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasF
}
switch (track->state.state) {
case State::Finishing:
case State::Stopping:
case State::Pausing:
case State::Starting:
case State::Resuming: {
@ -1186,29 +1205,33 @@ int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasF
}
auto fullPosition = track->bufferedPosition + positionInBuffered;
if (fading && (state == AL_PLAYING || !track->loading)) {
auto fadingForSamplesCount = (fullPosition - track->fadeStartPosition);
if (state != AL_PLAYING) {
if (state != AL_PLAYING && !track->loading) {
if (fading || playing) {
fading = false;
if (track->stream.source) {
playing = false;
if (track->isStreamCreated()) {
alSourceStop(track->stream.source);
alSourcef(track->stream.source, AL_GAIN, 1);
if (errorHappened()) return EmitError;
}
if (track->state.state == State::Pausing) {
track->state.state = State::PausedAtEnd;
} else if (track->state.state == State::Stopping) {
setStoppedState(track, State::Stopped);
} else {
setStoppedState(track, State::StoppedAtEnd);
}
emitSignals |= EmitStopped;
} else if (TimeMs(1000) * fadingForSamplesCount >= kFadeDuration * track->state.frequency) {
}
} else if (fading && state == AL_PLAYING) {
auto fadingForSamplesCount = (fullPosition - track->fadeStartPosition);
if (TimeMs(1000) * fadingForSamplesCount >= kFadeDuration * track->state.frequency) {
fading = false;
alSourcef(track->stream.source, AL_GAIN, 1. * volumeMultiplier);
if (errorHappened()) return EmitError;
switch (track->state.state) {
case State::Finishing: {
case State::Stopping: {
alSourceStop(track->stream.source);
if (errorHappened()) return EmitError;
@ -1229,23 +1252,14 @@ int32 Fader::updateOnePlayback(Mixer::Track *track, bool &hasPlaying, bool &hasF
}
} else {
auto newGain = TimeMs(1000) * fadingForSamplesCount / float64(kFadeDuration * track->state.frequency);
if (track->state.state == State::Pausing || track->state.state == State::Finishing) {
if (track->state.state == State::Pausing || track->state.state == State::Stopping) {
newGain = 1. - newGain;
}
alSourcef(track->stream.source, AL_GAIN, newGain * volumeMultiplier);
if (errorHappened()) return EmitError;
}
} else if (playing && (state == AL_PLAYING || !track->loading)) {
if (state != AL_PLAYING) {
playing = false;
if (track->isStreamCreated()) {
alSourceStop(track->stream.source);
alSourcef(track->stream.source, AL_GAIN, 1);
if (errorHappened()) return EmitError;
}
setStoppedState(track, State::StoppedAtEnd);
emitSignals |= EmitStopped;
} else if (volumeChanged) {
} else if (playing && state == AL_PLAYING) {
if (volumeChanged) {
alSourcef(track->stream.source, AL_GAIN, 1. * volumeMultiplier);
if (errorHappened()) return EmitError;
}

View File

@ -69,7 +69,7 @@ enum class State {
Starting = 0x08,
Playing = 0x10,
Finishing = 0x18,
Stopping = 0x18,
Pausing = 0x20,
Paused = 0x28,
PausedAtEnd = 0x30,
@ -83,6 +83,14 @@ inline bool IsStopped(State state) {
|| (state == State::StoppedAtStart);
}
inline bool IsStoppedOrStopping(State state) {
return IsStopped(state) || (state == State::Stopping);
}
inline bool IsStoppedAtEnd(State state) {
return (state == State::StoppedAtEnd);
}
inline bool IsPaused(State state) {
return (state == State::Paused)
|| (state == State::PausedAtEnd);
@ -90,7 +98,7 @@ inline bool IsPaused(State state) {
inline bool IsFading(State state) {
return (state == State::Starting)
|| (state == State::Finishing)
|| (state == State::Stopping)
|| (state == State::Pausing)
|| (state == State::Resuming);
}
@ -119,6 +127,7 @@ public:
void resume(const AudioMsgId &audio, bool fast = false);
void seek(AudioMsgId::Type type, int64 position); // type == AudioMsgId::Type::Song
void stop(const AudioMsgId &audio);
void stop(const AudioMsgId &audio, State state);
// Video player audio stream interface.
void feedFromVideo(VideoSoundPart &&part);

View File

@ -218,18 +218,6 @@ TimeMs FFMpegReaderImplementation::durationMs() const {
return (_fmtContext->streams[_streamId]->duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den;
}
void FFMpegReaderImplementation::pauseAudio() {
if (_audioStreamId >= 0) {
Player::mixer()->pause(_audioMsgId, true);
}
}
void FFMpegReaderImplementation::resumeAudio() {
if (_audioStreamId >= 0) {
Player::mixer()->resume(_audioMsgId, true);
}
}
bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) {
Expects(_frameRead);
_frameRead = false;
@ -425,7 +413,7 @@ bool FFMpegReaderImplementation::start(Mode mode, TimeMs &positionMs) {
positionMs = countPacketMs(&packet);
}
if (_audioStreamId >= 0) {
if (hasAudio()) {
auto position = (positionMs * soundData->frequency) / 1000LL;
Player::mixer()->play(_audioMsgId, std::move(soundData), position);
}
@ -480,10 +468,6 @@ QString FFMpegReaderImplementation::logData() const {
}
FFMpegReaderImplementation::~FFMpegReaderImplementation() {
if (_audioStreamId >= 0) {
Player::mixer()->stop(_audioMsgId);
}
clearPacketQueue();
if (_frameRead) {

View File

@ -48,8 +48,6 @@ public:
bool hasAudio() const override {
return (_audioStreamId >= 0);
}
void pauseAudio() override;
void resumeAudio() override;
bool start(Mode mode, TimeMs &positionMs) override;
bool inspectAt(TimeMs &positionMs);

View File

@ -55,8 +55,6 @@ public:
virtual TimeMs durationMs() const = 0;
virtual bool hasAudio() const = 0;
virtual void pauseAudio() = 0;
virtual void resumeAudio() = 0;
virtual bool start(Mode mode, TimeMs &positionMs) = 0;

View File

@ -42,10 +42,6 @@ public:
bool hasAudio() const override {
return false;
}
void pauseAudio() override {
}
void resumeAudio() override {
}
bool start(Mode mode, TimeMs &positionMs) override;

View File

@ -421,8 +421,8 @@ public:
}
if (!_started) {
_started = true;
if (!_videoPausedAtMs) {
_implementation->resumeAudio();
if (!_videoPausedAtMs && _hasAudio) {
Player::mixer()->resume(_audioMsgId, true);
}
}
@ -436,7 +436,7 @@ public:
auto frameMs = _seekPositionMs + ms - _animationStarted;
auto readResult = _implementation->readFramesTill(frameMs, ms);
if (readResult == internal::ReaderImplementation::ReadResult::EndOfFile) {
stop();
stop(Player::State::StoppedAtEnd);
_state = State::Finished;
return ProcessResult::Finished;
} else if (readResult == internal::ReaderImplementation::ReadResult::Error) {
@ -501,7 +501,9 @@ public:
if (_videoPausedAtMs) return; // Paused already.
_videoPausedAtMs = ms;
_implementation->pauseAudio();
if (_hasAudio) {
Player::mixer()->pause(_audioMsgId, true);
}
}
void resumeVideo(TimeMs ms) {
@ -512,17 +514,22 @@ public:
_nextFrameWhen += delta;
_videoPausedAtMs = 0;
_implementation->resumeAudio();
if (_hasAudio) {
Player::mixer()->resume(_audioMsgId, true);
}
}
ProcessResult error() {
stop();
stop(Player::State::StoppedAtError);
_state = State::Error;
return ProcessResult::Error;
}
void stop() {
void stop(Player::State audioState) {
_implementation = nullptr;
if (_hasAudio) {
Player::mixer()->stop(_audioMsgId, audioState);
}
if (_location) {
if (_accessed) {
@ -534,7 +541,7 @@ public:
}
~ReaderPrivate() {
stop();
stop(Player::State::Stopped);
_data.clear();
}

View File

@ -117,17 +117,25 @@ CoverWidget::CoverWidget(QWidget *parent) : TWidget(parent)
Global::RefSongVolumeChanged().notify();
});
subscribe(Global::RefSongVolumeChanged(), [this] { updateVolumeToggleIcon(); });
subscribe(instance()->repeatChangedNotifier(), [this] {
updateRepeatTrackIcon();
subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) {
if (type == AudioMsgId::Type::Song) {
updateRepeatTrackIcon();
}
});
subscribe(instance()->playlistChangedNotifier(), [this] {
handlePlaylistUpdate();
subscribe(instance()->playlistChangedNotifier(), [this](AudioMsgId::Type type) {
if (type == AudioMsgId::Type::Song) {
handlePlaylistUpdate();
}
});
subscribe(instance()->updatedNotifier(), [this](const TrackState &state) {
handleSongUpdate(state);
if (state.id.type() == AudioMsgId::Type::Song) {
handleSongUpdate(state);
}
});
subscribe(instance()->songChangedNotifier(), [this] {
handleSongChange();
subscribe(instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) {
if (type == AudioMsgId::Type::Song) {
handleSongChange();
}
});
handleSongChange();
@ -246,7 +254,7 @@ void CoverWidget::handleSongUpdate(const TrackState &state) {
_playback->updateState(state);
}
auto stopped = (IsStopped(state.state) || state.state == State::Finishing);
auto stopped = IsStoppedOrStopping(state.state);
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
if (instance()->isSeeking(AudioMsgId::Type::Song)) {
showPause = true;
@ -268,7 +276,7 @@ void CoverWidget::updateTimeText(const TrackState &state) {
QString time;
qint64 position = 0, length = 0, display = 0;
auto frequency = state.frequency;
if (!IsStopped(state.state) && state.state != State::Finishing) {
if (!IsStoppedOrStopping(state.state)) {
display = position = state.position;
length = state.length;
} else {
@ -305,7 +313,7 @@ void CoverWidget::updateTimeLabel() {
}
void CoverWidget::handleSongChange() {
auto &current = instance()->current(AudioMsgId::Type::Song);
auto current = instance()->current(AudioMsgId::Type::Song);
auto song = current.audio()->song();
if (!song) {
return;
@ -325,8 +333,8 @@ void CoverWidget::handleSongChange() {
}
void CoverWidget::handlePlaylistUpdate() {
auto &current = instance()->current(AudioMsgId::Type::Song);
auto &playlist = instance()->playlist(AudioMsgId::Type::Song);
auto current = instance()->current(AudioMsgId::Type::Song);
auto playlist = instance()->playlist(AudioMsgId::Type::Song);
auto index = playlist.indexOf(current.contextId());
if (!current || index < 0) {
destroyPrevNextButtons();

View File

@ -86,7 +86,7 @@ AudioMsgId::Type Instance::getActiveType() const {
auto voiceData = getData(AudioMsgId::Type::Voice);
if (voiceData->current) {
auto state = mixer()->currentState(voiceData->type);
if (IsActive(state.state)) {
if (voiceData->current == state.id && !IsStoppedOrStopping(state.state)) {
return voiceData->type;
}
}
@ -136,7 +136,7 @@ void Instance::setCurrent(const AudioMsgId &audioId) {
data->history = nullptr;
data->migrated = nullptr;
}
_songChangedNotifier.notify(true);
_trackChangedNotifier.notify(data->type, true);
if (data->history != history || data->migrated != migrated) {
rebuildPlaylist(data);
}
@ -163,25 +163,27 @@ void Instance::rebuildPlaylist(Data *data) {
data->playlist.push_back(FullMsgId(data->history->channelId(), msgId));
}
}
_playlistChangedNotifier.notify(true);
_playlistChangedNotifier.notify(data->type, true);
}
void Instance::moveInPlaylist(Data *data, int delta) {
bool Instance::moveInPlaylist(Data *data, int delta) {
Expects(data != nullptr);
auto index = data->playlist.indexOf(data->current.contextId());
auto newIndex = index + delta;
if (!data->current || index < 0 || newIndex < 0 || newIndex >= data->playlist.size()) {
rebuildPlaylist(data);
return;
return false;
}
auto msgId = data->playlist[newIndex];
if (auto item = App::histItemById(msgId)) {
if (auto media = item->getMedia()) {
media->playInline();
return true;
}
}
return false;
}
Instance *instance() {
@ -254,16 +256,18 @@ void Instance::playPause(AudioMsgId::Type type) {
}
}
void Instance::next(AudioMsgId::Type type) {
bool Instance::next(AudioMsgId::Type type) {
if (auto data = getData(type)) {
moveInPlaylist(data, 1);
return moveInPlaylist(data, 1);
}
return false;
}
void Instance::previous(AudioMsgId::Type type) {
bool Instance::previous(AudioMsgId::Type type) {
if (auto data = getData(type)) {
moveInPlaylist(data, -1);
return moveInPlaylist(data, -1);
}
return false;
}
void Instance::playPauseCancelClicked(AudioMsgId::Type type) {
@ -272,7 +276,7 @@ void Instance::playPauseCancelClicked(AudioMsgId::Type type) {
}
auto state = mixer()->currentState(type);
auto stopped = (IsStopped(state.state) || state.state == State::Finishing);
auto stopped = IsStoppedOrStopping(state.state);
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
auto audio = state.id.audio();
if (audio && audio->loading()) {
@ -319,8 +323,8 @@ void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) {
if (data->isPlaying && state.state == State::StoppedAtEnd) {
if (data->repeatEnabled) {
play(data->current);
} else {
next(type);
} else if (!next(type)) {
_tracksFinishedNotifier.notify(type);
}
}
auto isPlaying = !IsStopped(state.state);

View File

@ -42,8 +42,10 @@ public:
void pause(AudioMsgId::Type type);
void stop(AudioMsgId::Type type);
void playPause(AudioMsgId::Type type);
void next(AudioMsgId::Type type);
void previous(AudioMsgId::Type type);
bool next(AudioMsgId::Type type);
bool previous(AudioMsgId::Type type);
AudioMsgId::Type getActiveType() const;
void play() {
play(getActiveType());
@ -57,11 +59,11 @@ public:
void playPause() {
playPause(getActiveType());
}
void next() {
next(getActiveType());
bool next() {
return next(getActiveType());
}
void previous() {
previous(getActiveType());
bool previous() {
return previous(getActiveType());
}
void playPauseCancelClicked(AudioMsgId::Type type);
@ -83,7 +85,7 @@ public:
void toggleRepeat(AudioMsgId::Type type) {
if (auto data = getData(type)) {
data->repeatEnabled = !data->repeatEnabled;
_repeatChangedNotifier.notify();
_repeatChangedNotifier.notify(type);
}
}
@ -115,13 +117,16 @@ public:
base::Observable<TrackState> &updatedNotifier() {
return _updatedNotifier;
}
base::Observable<void> &playlistChangedNotifier() {
base::Observable<AudioMsgId::Type> &tracksFinishedNotifier() {
return _tracksFinishedNotifier;
}
base::Observable<AudioMsgId::Type> &playlistChangedNotifier() {
return _playlistChangedNotifier;
}
base::Observable<void> &songChangedNotifier() {
return _songChangedNotifier;
base::Observable<AudioMsgId::Type> &trackChangedNotifier() {
return _trackChangedNotifier;
}
base::Observable<void> &repeatChangedNotifier() {
base::Observable<AudioMsgId::Type> &repeatChangedNotifier() {
return _repeatChangedNotifier;
}
@ -148,8 +153,6 @@ private:
bool isPlaying = false;
};
AudioMsgId::Type getActiveType() const;
// Observed notifications.
void notifyPeerUpdated(const Notify::PeerUpdate &update);
void handleSongUpdate(const AudioMsgId &audioId);
@ -157,7 +160,7 @@ private:
void checkPeerUpdate(AudioMsgId::Type type, const Notify::PeerUpdate &update);
void setCurrent(const AudioMsgId &audioId);
void rebuildPlaylist(Data *data);
void moveInPlaylist(Data *data, int delta);
bool moveInPlaylist(Data *data, int delta);
void preloadNext(Data *data);
void handleLogout();
@ -189,9 +192,10 @@ private:
base::Observable<bool> _titleButtonOver;
base::Observable<bool> _playerWidgetOver;
base::Observable<TrackState> _updatedNotifier;
base::Observable<void> _playlistChangedNotifier;
base::Observable<void> _songChangedNotifier;
base::Observable<void> _repeatChangedNotifier;
base::Observable<AudioMsgId::Type> _tracksFinishedNotifier;
base::Observable<AudioMsgId::Type> _playlistChangedNotifier;
base::Observable<AudioMsgId::Type> _trackChangedNotifier;
base::Observable<AudioMsgId::Type> _repeatChangedNotifier;
};

View File

@ -30,7 +30,7 @@ namespace Player {
ListWidget::ListWidget(QWidget *parent) : TWidget(parent) {
setMouseTracking(true);
playlistUpdated();
subscribe(instance()->playlistChangedNotifier(), [this] { playlistUpdated(); });
subscribe(instance()->playlistChangedNotifier(), [this](AudioMsgId::Type type) { playlistUpdated(); });
subscribe(Global::RefItemRemoved(), [this](HistoryItem *item) {
itemRemoved(item);
});
@ -183,7 +183,7 @@ int ListWidget::marginTop() const {
void ListWidget::playlistUpdated() {
auto newHeight = 0;
auto &playlist = instance()->playlist(AudioMsgId::Type::Song);
auto playlist = instance()->playlist(AudioMsgId::Type::Song);
auto playlistSize = playlist.size();
auto existingSize = _list.size();
if (playlistSize > existingSize) {

View File

@ -25,6 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/shadow.h"
#include "ui/widgets/buttons.h"
#include "ui/effects/ripple_animation.h"
#include "lang.h"
#include "media/media_audio.h"
#include "media/view/media_clip_playback.h"
#include "media/player/media_player_button.h"
@ -107,15 +108,21 @@ Widget::Widget(QWidget *parent) : TWidget(parent)
_playbackSlider->setValue(value);
});
_playbackSlider->setChangeProgressCallback([this](float64 value) {
if (_type != AudioMsgId::Type::Song) {
return; // Round video seek is not supported for now :(
}
handleSeekProgress(value);
_playback->setValue(value, false);
});
_playbackSlider->setChangeFinishedCallback([this](float64 value) {
if (_type != AudioMsgId::Type::Song) {
return; // Round video seek is not supported for now :(
}
handleSeekFinished(value);
_playback->setValue(value, false);
});
_playPause->setClickedCallback([this] {
instance()->playPauseCancelClicked(AudioMsgId::Type::Song);
instance()->playPauseCancelClicked(_type);
});
updateVolumeToggleIcon();
@ -131,23 +138,35 @@ Widget::Widget(QWidget *parent) : TWidget(parent)
instance()->toggleRepeat(AudioMsgId::Type::Song);
});
subscribe(instance()->repeatChangedNotifier(), [this] {
updateRepeatTrackIcon();
});
subscribe(instance()->playlistChangedNotifier(), [this] {
handlePlaylistUpdate();
});
subscribe(instance()->updatedNotifier(), [this](const TrackState &state) {
if (state.id.type() == AudioMsgId::Type::Song) {
handleSongUpdate(state);
subscribe(instance()->repeatChangedNotifier(), [this](AudioMsgId::Type type) {
if (type == _type) {
updateRepeatTrackIcon();
}
});
subscribe(instance()->songChangedNotifier(), [this] {
handleSongChange();
subscribe(instance()->playlistChangedNotifier(), [this](AudioMsgId::Type type) {
if (type == _type) {
handlePlaylistUpdate();
}
});
handleSongChange();
handleSongUpdate(mixer()->currentState(AudioMsgId::Type::Song));
subscribe(instance()->updatedNotifier(), [this](const TrackState &state) {
handleSongUpdate(state);
});
subscribe(instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) {
if (type == _type) {
handleSongChange();
}
});
subscribe(instance()->tracksFinishedNotifier(), [this](AudioMsgId::Type type) {
if (type == AudioMsgId::Type::Voice) {
_voiceIsActive = false;
auto currentSong = instance()->current(AudioMsgId::Type::Song);
auto songState = mixer()->currentState(AudioMsgId::Type::Song);
if (currentSong == songState.id && !IsStoppedOrStopping(songState.state)) {
setType(AudioMsgId::Type::Song);
}
}
});
setType(AudioMsgId::Type::Song);
_playPause->finishTransform();
}
@ -167,8 +186,19 @@ void Widget::updateVolumeToggleIcon() {
_volumeToggle->setIconOverride(icon());
}
void Widget::setCloseCallback(CloseCallback &&callback) {
_close->setClickedCallback(std::move(callback));
void Widget::setCloseCallback(base::lambda<void()> callback) {
_close->setClickedCallback([this, callback = std::move(callback)] {
_voiceIsActive = false;
if (_type == AudioMsgId::Type::Voice) {
auto songData = instance()->current(AudioMsgId::Type::Song);
auto songState = mixer()->currentState(AudioMsgId::Type::Song);
if (songData == songState.id && !IsStoppedOrStopping(songState.state)) {
instance()->stop(AudioMsgId::Type::Voice);
return;
}
}
callback();
});
}
void Widget::setShadowGeometryToLeft(int x, int y, int w, int h) {
@ -206,7 +236,7 @@ void Widget::handleSeekProgress(float64 progress) {
_seekPositionMs = positionMs;
updateTimeLabel();
instance()->startSeeking(AudioMsgId::Type::Song);
instance()->startSeeking(_type);
}
}
@ -216,13 +246,12 @@ void Widget::handleSeekFinished(float64 progress) {
auto positionMs = snap(static_cast<TimeMs>(progress * _lastDurationMs), 0LL, _lastDurationMs);
_seekPositionMs = -1;
auto type = AudioMsgId::Type::Song;
auto state = mixer()->currentState(type);
auto state = mixer()->currentState(_type);
if (state.id && state.length) {
mixer()->seek(type, qRound(progress * state.length));
mixer()->seek(_type, qRound(progress * state.length));
}
instance()->stopSeeking(AudioMsgId::Type::Song);
instance()->stopSeeking(_type);
}
void Widget::resizeEvent(QResizeEvent *e) {
@ -287,7 +316,10 @@ int Widget::getLabelsLeft() const {
}
int Widget::getLabelsRight() const {
auto result = st::mediaPlayerCloseRight + _close->width() + _repeatTrack->width() + _volumeToggle->width();
auto result = st::mediaPlayerCloseRight + _close->width();
if (_type == AudioMsgId::Type::Song) {
result += _repeatTrack->width() + _volumeToggle->width();
}
result += st::mediaPlayerPadding;
return result;
}
@ -310,8 +342,35 @@ void Widget::updateRepeatTrackIcon() {
_repeatTrack->setRippleColorOverride(repeating ? nullptr : &st::mediaPlayerRepeatDisabledRippleBg);
}
void Widget::checkForTypeChange() {
auto hasActiveType = [](AudioMsgId::Type type) {
auto current = instance()->current(type);
auto state = mixer()->currentState(type);
return (current == state.id && !IsStoppedOrStopping(state.state));
};
if (hasActiveType(AudioMsgId::Type::Voice)) {
_voiceIsActive = true;
setType(AudioMsgId::Type::Voice);
} else if (!_voiceIsActive && hasActiveType(AudioMsgId::Type::Song)) {
setType(AudioMsgId::Type::Song);
}
}
void Widget::setType(AudioMsgId::Type type) {
if (_type != type) {
_type = type;
_repeatTrack->setVisible(_type == AudioMsgId::Type::Song);
_volumeToggle->setVisible(_type == AudioMsgId::Type::Song);
_playbackSlider->setVisible(_type == AudioMsgId::Type::Song);
updateLabelsGeometry();
handleSongChange();
handleSongUpdate(mixer()->currentState(_type));
}
}
void Widget::handleSongUpdate(const TrackState &state) {
if (!state.id.audio()) {
checkForTypeChange();
if (state.id.type() != _type || !state.id.audio()) {
return;
}
@ -321,9 +380,9 @@ void Widget::handleSongUpdate(const TrackState &state) {
_playback->updateState(state);
}
auto stopped = (IsStopped(state.state) || state.state == State::Finishing);
auto stopped = IsStoppedOrStopping(state.state);
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
if (instance()->isSeeking(AudioMsgId::Type::Song)) {
if (instance()->isSeeking(_type)) {
showPause = true;
}
auto buttonState = [audio = state.id.audio(), showPause] {
@ -343,7 +402,7 @@ void Widget::updateTimeText(const TrackState &state) {
QString time;
qint64 position = 0, length = 0, display = 0;
auto frequency = state.frequency;
if (!IsStopped(state.state) && state.state != State::Finishing) {
if (!IsStoppedOrStopping(state.state)) {
display = position = state.position;
length = state.length;
} else if (state.length) {
@ -381,16 +440,41 @@ void Widget::updateTimeLabel() {
}
void Widget::handleSongChange() {
auto &current = instance()->current(AudioMsgId::Type::Song);
auto song = current.audio()->song();
auto current = instance()->current(_type);
if (!current || !current.audio()) {
return;
}
TextWithEntities textWithEntities;
if (!song || song->performer.isEmpty()) {
textWithEntities.text = (!song || song->title.isEmpty()) ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title;
if (current.audio()->voice() || current.audio()->isRoundVideo()) {
if (auto item = App::histItemById(current.contextId())) {
auto name = App::peerName(item->fromOriginal());
auto date = [item] {
auto date = item->date.date();
auto time = item->date.time().toString(cTimeFormat());
auto today = QDateTime::currentDateTime().date();
if (date == today) {
return lng_player_message_today(lt_time, time);
} else if (date.addDays(1) == today) {
return lng_player_message_yesterday(lt_time, time);
}
return lng_player_message_date(lt_date, langDayOfMonthFull(date), lt_time, time);
};
textWithEntities.text = name + ' ' + date();
textWithEntities.entities.append({ EntityInTextBold, 0, name.size(), QString() });
} else {
textWithEntities.text = lang(lng_media_audio);
}
} else {
auto title = song->title.isEmpty() ? qsl("Unknown Track") : textClean(song->title);
textWithEntities.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + title;
textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() });
auto song = current.audio()->song();
if (!song || song->performer.isEmpty()) {
textWithEntities.text = (!song || song->title.isEmpty()) ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title;
} else {
auto title = song->title.isEmpty() ? qsl("Unknown Track") : textClean(song->title);
textWithEntities.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + title;
textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() });
}
}
_nameLabel->setMarkedText(textWithEntities);
@ -398,8 +482,8 @@ void Widget::handleSongChange() {
}
void Widget::handlePlaylistUpdate() {
auto &current = instance()->current(AudioMsgId::Type::Song);
auto &playlist = instance()->playlist(AudioMsgId::Type::Song);
auto current = instance()->current(_type);
auto playlist = instance()->playlist(_type);
auto index = playlist.indexOf(current.contextId());
if (!current || index < 0) {
destroyPrevNextButtons();

View File

@ -45,8 +45,7 @@ class Widget : public TWidget, private base::Subscriber {
public:
Widget(QWidget *parent);
using CloseCallback = base::lambda<void()>;
void setCloseCallback(CloseCallback &&callback);
void setCloseCallback(base::lambda<void()> callback);
void setShadowGeometryToLeft(int x, int y, int w, int h);
void showShadow();
@ -81,6 +80,8 @@ private:
void updateVolumeToggleIcon();
void checkForTypeChange();
void setType(AudioMsgId::Type type);
void handleSongUpdate(const TrackState &state);
void handleSongChange();
void handlePlaylistUpdate();
@ -92,6 +93,13 @@ private:
TimeMs _lastDurationMs = 0;
QString _time;
// We display all the controls according to _type.
// We switch to Type::Voice if a voice/video message is played.
// We switch to Type::Song only if _voiceIsActive == false.
// We change _voiceIsActive to false only manually or from tracksFinished().
AudioMsgId::Type _type = AudioMsgId::Type::Unknown;
bool _voiceIsActive = false;
class PlayButton;
object_ptr<Ui::FlatLabel> _nameLabel;
object_ptr<Ui::LabelSimple> _timeLabel;

View File

@ -133,10 +133,10 @@ void Controller::updatePlayPauseResumeState(const Player::TrackState &state) {
void Controller::updateTimeTexts(const Player::TrackState &state) {
qint64 position = 0, length = state.length;
if (!Player::IsStopped(state.state) && state.state != Player::State::Finishing) {
position = state.position;
} else if (state.state == Player::State::StoppedAtEnd) {
if (Player::IsStoppedAtEnd(state.state)) {
position = state.length;
} else if (!Player::IsStoppedOrStopping(state.state)) {
position = state.position;
} else {
position = 0;
}

View File

@ -46,10 +46,10 @@ void Playback::updateState(const Player::TrackState &state) {
}
_playing = !Player::IsStopped(state.state);
if (_playing || state.state == Player::State::Stopped) {
position = state.position;
} else if (state.state == Player::State::StoppedAtEnd) {
if (Player::IsStoppedAtEnd(state.state)) {
position = state.length;
} else if (!Player::IsStoppedOrStopping(state.state)) {
position = state.position;
} else {
position = 0;
}

View File

@ -666,7 +666,7 @@ bool Voice::updateStatusText() {
statusSize = FileStatusSizeLoaded;
using State = Media::Player::State;
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
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);
@ -950,7 +950,7 @@ bool Document::updateStatusText() {
statusSize = FileStatusSizeLoaded;
using State = Media::Player::State;
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
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);

View File

@ -36,4 +36,4 @@ void disableMediaShortcuts();
void finish();
}
} // namespace Shortcuts

View File

@ -1202,7 +1202,7 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
using State = Media::Player::State;
if (playVoice) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStoppedOrStopping(state.state)) {
if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) {
Media::Player::mixer()->resume(state.id);
} else {
@ -1218,7 +1218,7 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
}
} else if (playMusic) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStoppedOrStopping(state.state)) {
if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) {
Media::Player::mixer()->resume(state.id);
} else {
@ -1510,7 +1510,7 @@ void DocumentData::performActionOnLoad() {
if (playVoice) {
if (loaded()) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStoppedOrStopping(state.state)) {
if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) {
Media::Player::mixer()->resume(state.id);
} else {
@ -1524,7 +1524,7 @@ void DocumentData::performActionOnLoad() {
} else if (playMusic) {
if (loaded()) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStoppedOrStopping(state.state)) {
if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) {
Media::Player::mixer()->resume(state.id);
} else {

View File

@ -53,6 +53,10 @@ public:
}
void toggleFast(bool visible);
bool isHiddenOrHiding() const {
return isHidden() || (_a_height.animating() && _hiding);
}
void finishAnimation() {
_a_height.finish();
myEnsureResized(_entity);