From f7d55005c485b03279d66e5bab4f028078bfdfb4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 1 Jul 2015 00:07:05 +0300 Subject: [PATCH] audio documents playback added, audio documents suppress on voice message, and both suppress on notify added --- Telegram/Resources/style.txt | 5 +- Telegram/SourceFiles/apiwrap.cpp | 2 +- Telegram/SourceFiles/app.cpp | 24 +- Telegram/SourceFiles/audio.cpp | 1197 +++++++++++++----- Telegram/SourceFiles/audio.h | 132 +- Telegram/SourceFiles/boxes/stickersetbox.cpp | 12 +- Telegram/SourceFiles/config.h | 5 +- Telegram/SourceFiles/dropdown.cpp | 14 +- Telegram/SourceFiles/history.cpp | 137 +- Telegram/SourceFiles/historywidget.cpp | 16 +- Telegram/SourceFiles/localimageloader.cpp | 36 +- Telegram/SourceFiles/localstorage.cpp | 64 +- Telegram/SourceFiles/localstorage.h | 4 +- Telegram/SourceFiles/mainwidget.cpp | 102 +- Telegram/SourceFiles/mainwidget.h | 3 +- Telegram/SourceFiles/mediaview.cpp | 8 +- Telegram/SourceFiles/settings.cpp | 2 +- Telegram/SourceFiles/structs.cpp | 56 +- Telegram/SourceFiles/structs.h | 80 +- 19 files changed, 1422 insertions(+), 477 deletions(-) diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index 89b40c4417..197816c126 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -1146,7 +1146,7 @@ notifyTextTop: 7px; notifySlowHideFunc: transition(easeInCirc); notifyWaitShortHide: 0; notifyWaitLongHide: 20000; -notifyFastAnim: 100; +notifyFastAnim: 150; notifyFastAnimFunc: transition(linear); notifyWidth: 316px; notifyHeight: 80px; @@ -1968,3 +1968,6 @@ webPagePhotoSize: 100px; webPagePhotoDelta: 8px; botDescSkip: 8px; + +suppressAll: 0.2; +suppressSong: 0.05; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 3db15bb4b2..c93bce8079 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -302,7 +302,7 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) int32 wasCount = -1; for (int32 i = 0, l = d_docs.size(); i != l; ++i) { DocumentData *doc = App::feedDocument(d_docs.at(i)); - if (!doc || !doc->sticker) continue; + if (!doc || !doc->sticker()) continue; if (wasCount < 0) wasCount = it->stickers.size(); if (it->stickers.indexOf(doc) < 0) { diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index da97b1ad90..ee59eb84e5 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -1295,7 +1295,7 @@ namespace App { } convert->id = document; convert->status = FileReady; - sentSticker = !!convert->sticker; + sentSticker = !!convert->sticker(); } convert->access = access; if (!convert->date && date) { @@ -1309,20 +1309,20 @@ namespace App { if (!thumb->isNull() && (convert->thumb->isNull() || convert->thumb->width() < thumb->width() || convert->thumb->height() < thumb->height())) { convert->thumb = thumb; } - if (convert->sticker && !attributes.isEmpty() && (convert->sticker->alt.isEmpty() || convert->sticker->set.type() == mtpc_inputStickerSetEmpty)) { + if (convert->sticker() && !attributes.isEmpty() && (convert->sticker()->alt.isEmpty() || convert->sticker()->set.type() == mtpc_inputStickerSetEmpty)) { for (QVector::const_iterator i = attributes.cbegin(), e = attributes.cend(); i != e; ++i) { if (i->type() == mtpc_documentAttributeSticker) { const MTPDdocumentAttributeSticker &d(i->c_documentAttributeSticker()); if (d.valt.c_string().v.length() > 0) { - convert->sticker->alt = qs(d.valt); - convert->sticker->set = d.vstickerset; + convert->sticker()->alt = qs(d.valt); + convert->sticker()->set = d.vstickerset; } } } } } - if (convert->sticker && !convert->sticker->loc.dc && thumbLocation.dc) { - convert->sticker->loc = thumbLocation; + if (convert->sticker() && !convert->sticker()->loc.dc && thumbLocation.dc) { + convert->sticker()->loc = thumbLocation; } if (convert->location.check()) { @@ -1336,7 +1336,7 @@ namespace App { result = convert; } else { result = new DocumentData(document, access, date, attributes, mime, thumb, dc, size); - if (result->sticker) result->sticker->loc = thumbLocation; + if (result->sticker()) result->sticker()->loc = thumbLocation; } documentsData.insert(document, result); } else { @@ -1354,19 +1354,19 @@ namespace App { if (!thumb->isNull() && (result->thumb->isNull() || result->thumb->width() < thumb->width() || result->thumb->height() < thumb->height())) { result->thumb = thumb; } - if (result->sticker && !attributes.isEmpty() && (result->sticker->alt.isEmpty() || result->sticker->set.type() == mtpc_inputStickerSetEmpty)) { + if (result->sticker() && !attributes.isEmpty() && (result->sticker()->alt.isEmpty() || result->sticker()->set.type() == mtpc_inputStickerSetEmpty)) { for (QVector::const_iterator i = attributes.cbegin(), e = attributes.cend(); i != e; ++i) { if (i->type() == mtpc_documentAttributeSticker) { const MTPDdocumentAttributeSticker &d(i->c_documentAttributeSticker()); if (d.valt.c_string().v.length() > 0) { - result->sticker->alt = qs(d.valt); - result->sticker->set = d.vstickerset; + result->sticker()->alt = qs(d.valt); + result->sticker()->set = d.vstickerset; } } } } - if (result->sticker && !result->sticker->loc.dc && thumbLocation.dc) { - result->sticker->loc = thumbLocation; + if (result->sticker() && !result->sticker()->loc.dc && thumbLocation.dc) { + result->sticker()->loc = thumbLocation; } } } diff --git a/Telegram/SourceFiles/audio.cpp b/Telegram/SourceFiles/audio.cpp index 4303618e99..f5c39e43a4 100644 --- a/Telegram/SourceFiles/audio.cpp +++ b/Telegram/SourceFiles/audio.cpp @@ -63,9 +63,13 @@ namespace { ALuint notifySource = 0; ALuint notifyBuffer = 0; + uint64 notifyLengthMs = 0; + QMutex playerMutex; AudioPlayer *player = 0; + float64 suppressAllGain = 1., suppressSongGain = 1.; + AudioCapture *capture = 0; } @@ -96,7 +100,12 @@ bool _checkALError() { return true; } +Q_DECLARE_METATYPE(AudioMsgId); +Q_DECLARE_METATYPE(SongMsgId); void audioInit() { + av_register_all(); + avcodec_register_all(); + if (!capture) { capture = new AudioCapture(); cSetHasAudioCapture(capture->check()); @@ -195,16 +204,22 @@ void audioInit() { } if (!format) return audioFinish(); - alBufferData(notifyBuffer, format, data, subchunk2Size, sampleRate); + int32 addBytes = (sampleRate * 15 / 100) * bytesPerSample * numChannels; // add 150ms of silence + QByteArray fullData(addBytes + subchunk2Size, (bytesPerSample == 1) ? 128 : 0); + memcpy(fullData.data() + addBytes, data, subchunk2Size); + alBufferData(notifyBuffer, format, fullData.constData(), fullData.size(), sampleRate); alSourcei(notifySource, AL_BUFFER, notifyBuffer); + + notifyLengthMs = (numSamples * 1000ULL / sampleRate); + if (!_checkALError()) return audioFinish(); + qRegisterMetaType(); + qRegisterMetaType(); + player = new AudioPlayer(); alcDevicePauseSOFT(audioDevice); - av_register_all(); - avcodec_register_all(); - LOG(("Audio init time: %1").arg(getms() - ms)); cSetHasAudioPlayer(true); } @@ -214,6 +229,7 @@ void audioPlayNotify() { audioPlayer()->resumeDevice(); alSourcePlay(notifySource); + emit audioPlayer()->suppressAll(); emit audioPlayer()->faderOnTimer(); } @@ -250,22 +266,32 @@ void audioFinish() { cSetHasAudioPlayer(false); } -AudioPlayer::AudioPlayer() : _current(0), +AudioPlayer::AudioPlayer() : _audioCurrent(0), _songCurrent(0), _fader(new AudioPlayerFader(&_faderThread)), _loader(new AudioPlayerLoaders(&_loaderThread)) { connect(this, SIGNAL(faderOnTimer()), _fader, SLOT(onTimer())); - connect(this, SIGNAL(loaderOnStart(AudioData*)), _loader, SLOT(onStart(AudioData*))); - connect(this, SIGNAL(loaderOnCancel(AudioData*)), _loader, SLOT(onCancel(AudioData*))); + connect(this, SIGNAL(suppressSong()), _fader, SLOT(onSuppressSong())); + connect(this, SIGNAL(unsuppressSong()), _fader, SLOT(onUnsuppressSong())); + connect(this, SIGNAL(suppressAll()), _fader, SLOT(onSuppressAll())); + connect(this, SIGNAL(loaderOnStart(const AudioMsgId&)), _loader, SLOT(onStart(const AudioMsgId&))); + connect(this, SIGNAL(loaderOnStart(const SongMsgId&)), _loader, SLOT(onStart(const SongMsgId&))); + connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&))); + connect(this, SIGNAL(loaderOnCancel(const SongMsgId&)), _loader, SLOT(onCancel(const SongMsgId&))); connect(&_faderThread, SIGNAL(started()), _fader, SLOT(onInit())); connect(&_loaderThread, SIGNAL(started()), _loader, SLOT(onInit())); connect(&_faderThread, SIGNAL(finished()), _fader, SLOT(deleteLater())); connect(&_loaderThread, SIGNAL(finished()), _loader, SLOT(deleteLater())); connect(_loader, SIGNAL(needToCheck()), _fader, SLOT(onTimer())); - connect(_loader, SIGNAL(error(AudioData*)), this, SLOT(onError(AudioData*))); - connect(_fader, SIGNAL(needToPreload(AudioData*)), _loader, SLOT(onLoad(AudioData*))); - connect(_fader, SIGNAL(playPositionUpdated(AudioData*)), this, SIGNAL(updated(AudioData*))); - connect(_fader, SIGNAL(audioStopped(AudioData*)), this, SIGNAL(stopped(AudioData*))); - connect(_fader, SIGNAL(error(AudioData*)), this, SLOT(onError(AudioData*))); + connect(_loader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&))); + connect(_loader, SIGNAL(error(const SongMsgId&)), this, SLOT(onError(const SongMsgId&))); + connect(_fader, SIGNAL(needToPreload(const AudioMsgId&)), _loader, SLOT(onLoad(const AudioMsgId&))); + connect(_fader, SIGNAL(needToPreload(const SongMsgId&)), _loader, SLOT(onLoad(const SongMsgId&))); + connect(_fader, SIGNAL(playPositionUpdated(const AudioMsgId&)), this, SIGNAL(updated(const AudioMsgId&))); + connect(_fader, SIGNAL(playPositionUpdated(const SongMsgId&)), this, SIGNAL(updated(const SongMsgId&))); + connect(_fader, SIGNAL(audioStopped(const AudioMsgId&)), this, SLOT(onStopped(const AudioMsgId&))); + connect(_fader, SIGNAL(audioStopped(const SongMsgId&)), this, SLOT(onStopped(const SongMsgId&))); + connect(_fader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&))); + connect(_fader, SIGNAL(error(const SongMsgId&)), this, SLOT(onError(const SongMsgId&))); _loaderThread.start(); _faderThread.start(); } @@ -277,16 +303,29 @@ AudioPlayer::~AudioPlayer() { } for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { - alSourceStop(_data[i].source); - if (alIsBuffer(_data[i].buffers[0])) { - alDeleteBuffers(3, _data[i].buffers); + alSourceStop(_audioData[i].source); + if (alIsBuffer(_audioData[i].buffers[0])) { + alDeleteBuffers(3, _audioData[i].buffers); for (int32 j = 0; j < 3; ++j) { - _data[i].buffers[j] = _data[i].samplesCount[j] = 0; + _audioData[i].buffers[j] = _audioData[i].samplesCount[j] = 0; } } - if (alIsSource(_data[i].source)) { - alDeleteSources(1, &_data[i].source); - _data[i].source = 0; + if (alIsSource(_audioData[i].source)) { + alDeleteSources(1, &_audioData[i].source); + _audioData[i].source = 0; + } + } + for (int32 i = 0; i < AudioSongSimultaneously; ++i) { + alSourceStop(_songData[i].source); + if (alIsBuffer(_songData[i].buffers[0])) { + alDeleteBuffers(3, _songData[i].buffers); + for (int32 j = 0; j < 3; ++j) { + _songData[i].buffers[j] = _songData[i].samplesCount[j] = 0; + } + } + if (alIsSource(_songData[i].source)) { + alDeleteSources(1, &_songData[i].source); + _songData[i].source = 0; } } _faderThread.quit(); @@ -295,114 +334,236 @@ AudioPlayer::~AudioPlayer() { _loaderThread.wait(); } -void AudioPlayer::onError(AudioData *audio) { +void AudioPlayer::onError(const AudioMsgId &audio) { emit stopped(audio); + emit unsuppressSong(); } -bool AudioPlayer::updateCurrentStarted(int32 pos) { +void AudioPlayer::onError(const SongMsgId &song) { + emit stopped(song); +} + +void AudioPlayer::onStopped(const AudioMsgId &audio) { + emit stopped(audio); + emit unsuppressSong(); +} + +void AudioPlayer::onStopped(const SongMsgId &song) { + emit stopped(song); +} + +bool AudioPlayer::updateCurrentStarted(MediaOverviewType type, int32 pos) { + Msg *data = 0; + switch (type) { + case OverviewAudios: data = &_audioData[_audioCurrent]; break; + case OverviewDocuments: data = &_songData[_songCurrent]; break; + } + if (!data) return false; + if (pos < 0) { - if (alIsSource(_data[_current].source)) { - alGetSourcei(_data[_current].source, AL_SAMPLE_OFFSET, &pos); + if (alIsSource(data->source)) { + alGetSourcei(data->source, AL_SAMPLE_OFFSET, &pos); } else { pos = 0; } } if (!_checkALError()) { - _data[_current].state = AudioPlayerStopped; - onError(_data[_current].audio); + data->state = AudioPlayerStopped; + switch (type) { + case OverviewAudios: onError(_audioData[_audioCurrent].audio); break; + case OverviewDocuments: onError(_songData[_songCurrent].song); break; + } return false; } - _data[_current].started = _data[_current].position = pos + _data[_current].skipStart; + data->started = data->position = pos + data->skipStart; return true; } -void AudioPlayer::play(AudioData *audio) { - AudioData *stopped = 0; +bool AudioPlayer::startedOther(MediaOverviewType type, bool &fadedStart) { + Msg *current = 0; + switch (type) { + case OverviewAudios: current = &_audioData[_audioCurrent]; break; + case OverviewDocuments: current = &_songData[_songCurrent]; break; + } + if (!current) return false; + switch (current->state) { + case AudioPlayerStarting: + case AudioPlayerResuming: + case AudioPlayerPlaying: + current->state = AudioPlayerFinishing; + updateCurrentStarted(type); + fadedStart = true; + break; + case AudioPlayerPausing: + current->state = AudioPlayerFinishing; + fadedStart = true; + break; + case AudioPlayerPaused: + current->state = AudioPlayerStopped; + return true; + } + return false; +} + +void AudioPlayer::play(const AudioMsgId &audio) { + AudioMsgId stopped; { QMutexLocker lock(&playerMutex); - bool startNow = true; - if (_data[_current].audio != audio) { - switch (_data[_current].state) { - case AudioPlayerStarting: - case AudioPlayerResuming: - case AudioPlayerPlaying: - _data[_current].state = AudioPlayerFinishing; - updateCurrentStarted(); - startNow = false; - break; - case AudioPlayerPausing: _data[_current].state = AudioPlayerFinishing; startNow = false; break; - case AudioPlayerPaused: _data[_current].state = AudioPlayerStopped; stopped = _data[_current].audio; break; + bool fadedStart = false; + AudioMsg *current = &_audioData[_audioCurrent]; + if (current->audio != audio) { + if (startedOther(OverviewAudios, fadedStart)) { + stopped = current->audio; } - if (_data[_current].audio) { - emit loaderOnCancel(_data[_current].audio); + if (current->audio) { + emit loaderOnCancel(current->audio); emit faderOnTimer(); } - } - int32 index = 0; - for (; index < AudioVoiceMsgSimultaneously; ++index) { - if (_data[index].audio == audio) { - _current = index; - break; + int32 index = 0; + for (; index < AudioVoiceMsgSimultaneously; ++index) { + if (_audioData[index].audio == audio) { + _audioCurrent = index; + break; + } } + if (index == AudioVoiceMsgSimultaneously && ++_audioCurrent >= AudioVoiceMsgSimultaneously) { + _audioCurrent -= AudioVoiceMsgSimultaneously; + } + current = &_audioData[_audioCurrent]; } - if (index == AudioVoiceMsgSimultaneously && ++_current >= AudioVoiceMsgSimultaneously) { - _current -= AudioVoiceMsgSimultaneously; - } - _data[_current].audio = audio; - _data[_current].fname = audio->already(true); - _data[_current].data = audio->data; - if (_data[_current].fname.isEmpty() && _data[_current].data.isEmpty()) { - _data[_current].state = AudioPlayerStopped; + current->audio = audio; + current->fname = audio.audio->already(true); + current->data = audio.audio->data; + if (current->fname.isEmpty() && current->data.isEmpty()) { + current->state = AudioPlayerStopped; onError(audio); - } else if (updateCurrentStarted(0)) { - _data[_current].state = startNow ? AudioPlayerPlaying : AudioPlayerStarting; - _data[_current].loading = true; + } else if (updateCurrentStarted(OverviewAudios, 0)) { + current->state = fadedStart ? AudioPlayerStarting : AudioPlayerPlaying; + current->loading = true; emit loaderOnStart(audio); + emit suppressSong(); } } if (stopped) emit updated(stopped); } -void AudioPlayer::pauseresume() { +void AudioPlayer::play(const SongMsgId &song) { + SongMsgId stopped; + { + QMutexLocker lock(&playerMutex); + + bool fadedStart = false; + SongMsg *current = &_songData[_songCurrent]; + if (current->song != song) { + if (startedOther(OverviewDocuments, fadedStart)) { + stopped = current->song; + } + if (current->song) { + emit loaderOnCancel(current->song); + emit faderOnTimer(); + } + + int32 index = 0; + for (; index < AudioSongSimultaneously; ++index) { + if (_songData[index].song == song) { + _songCurrent = index; + break; + } + } + if (index == AudioSongSimultaneously && ++_songCurrent >= AudioSongSimultaneously) { + _songCurrent -= AudioSongSimultaneously; + } + current = &_songData[_songCurrent]; + } + current->song = song; + current->fname = song.song->already(true); + current->data = song.song->data; + if (current->fname.isEmpty() && current->data.isEmpty()) { + current->state = AudioPlayerStopped; + onError(song); + } else if (updateCurrentStarted(OverviewDocuments, 0)) { + current->state = fadedStart ? AudioPlayerStarting : AudioPlayerPlaying; + current->loading = true; + emit loaderOnStart(song); + } + } + if (stopped) emit updated(stopped); +} + +void AudioPlayer::pauseresume(MediaOverviewType type) { QMutexLocker lock(&playerMutex); - switch (_data[_current].state) { + Msg *current = 0; + float64 suppressGain = 1.; + switch (type) { + case OverviewAudios: + current = &_audioData[_audioCurrent]; + suppressGain = suppressAllGain; + break; + case OverviewDocuments: + current = &_songData[_songCurrent]; + suppressGain = suppressSongGain; + break; + } + switch (current->state) { case AudioPlayerPausing: case AudioPlayerPaused: - if (_data[_current].state == AudioPlayerPaused) { - updateCurrentStarted(); + if (current->state == AudioPlayerPaused) { + updateCurrentStarted(type); } - _data[_current].state = AudioPlayerResuming; + current->state = AudioPlayerResuming; resumeDevice(); - alSourcePlay(_data[_current].source); + alSourcef(current->source, AL_GAIN, suppressGain); + alSourcePlay(current->source); + if (type == OverviewAudios) emit suppressSong(); break; case AudioPlayerStarting: case AudioPlayerResuming: case AudioPlayerPlaying: - _data[_current].state = AudioPlayerPausing; - updateCurrentStarted(); + current->state = AudioPlayerPausing; + updateCurrentStarted(type); + if (type == OverviewAudios) emit unsuppressSong(); break; - case AudioPlayerFinishing: _data[_current].state = AudioPlayerPausing; break; + case AudioPlayerFinishing: current->state = AudioPlayerPausing; break; } emit faderOnTimer(); } -void AudioPlayer::currentState(AudioData **audio, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency) { +void AudioPlayer::currentState(AudioMsgId *audio, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency) { QMutexLocker lock(&playerMutex); - if (audio) *audio = _data[_current].audio; - if (state) *state = _data[_current].state; - if (position) *position = _data[_current].position; - if (duration) *duration = _data[_current].duration; - if (frequency) *frequency = _data[_current].frequency; + AudioMsg *current = &_audioData[_audioCurrent]; + if (audio) *audio = current->audio; + return currentState(current, state, position, duration, frequency); } -void AudioPlayer::clearStoppedAtStart(AudioData *audio) { +void AudioPlayer::currentState(SongMsgId *song, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency) { QMutexLocker lock(&playerMutex); - if (_data[_current].audio == audio && _data[_current].state == AudioPlayerStoppedAtStart) { - _data[_current].state = AudioPlayerStopped; + SongMsg *current = &_songData[_songCurrent]; + if (song) *song = current->song; + return currentState(current, state, position, duration, frequency); +} + +void AudioPlayer::currentState(Msg *current, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency) { + if (state) *state = current->state; + if (position) *position = current->position; + if (duration) *duration = current->duration; + if (frequency) *frequency = current->frequency; +} + +void AudioPlayer::clearStoppedAtStart(const AudioMsgId &audio) { + QMutexLocker lock(&playerMutex); + if (_audioData[_audioCurrent].audio == audio && _audioData[_audioCurrent].state == AudioPlayerStoppedAtStart) { + _audioData[_audioCurrent].state = AudioPlayerStopped; + } +} + +void AudioPlayer::clearStoppedAtStart(const SongMsgId &song) { + QMutexLocker lock(&playerMutex); + if (_songData[_songCurrent].song == song && _songData[_songCurrent].state == AudioPlayerStoppedAtStart) { + _songData[_songCurrent].state = AudioPlayerStopped; } } @@ -453,7 +614,10 @@ AudioCapture *audioCapture() { return capture; } -AudioPlayerFader::AudioPlayerFader(QThread *thread) : _timer(this), _pauseFlag(false), _paused(true) { +AudioPlayerFader::AudioPlayerFader(QThread *thread) : _timer(this), _pauseFlag(false), _paused(true), +_suppressAll(false), _suppressAllAnim(false), _suppressSong(false), _suppressSongAnim(false), +_suppressAllGain(1., 1.), _suppressSongGain(1., 1.), +_suppressAllStart(0), _suppressSongStart(0) { moveToThread(thread); _timer.moveToThread(thread); _pauseTimer.moveToThread(thread); @@ -470,91 +634,75 @@ void AudioPlayerFader::onInit() { } void AudioPlayerFader::onTimer() { - bool hasFading = false, hasPlaying = false; QMutexLocker lock(&playerMutex); AudioPlayer *voice = audioPlayer(); if (!voice) return; + bool suppressAudioChanged = false, suppressSongChanged = false; + if (_suppressAll || _suppressSongAnim) { + uint64 ms = getms(); + float64 wasSong = suppressSongGain; + if (_suppressAll) { + float64 wasAudio = suppressAllGain; + if (ms >= _suppressAllStart + notifyLengthMs || ms < _suppressAllStart) { + _suppressAll = _suppressAllAnim = false; + _suppressAllGain = anim::fvalue(1., 1.); + } else if (ms > _suppressAllStart + notifyLengthMs - AudioFadeDuration) { + if (_suppressAllGain.to() != 1.) _suppressAllGain.start(1.); + _suppressAllGain.update(1. - ((_suppressAllStart + notifyLengthMs - ms) / float64(AudioFadeDuration)), anim::linear); + } else if (ms >= _suppressAllStart + st::notifyFastAnim) { + if (_suppressAllAnim) { + _suppressAllGain.finish(); + _suppressAllAnim = false; + } + } else if (ms > _suppressAllStart) { + _suppressAllGain.update((ms - _suppressAllStart) / st::notifyFastAnim, anim::linear); + } + suppressAllGain = _suppressAllGain.current(); + suppressAudioChanged = (suppressAllGain != wasAudio); + } + if (_suppressSongAnim) { + if (ms >= _suppressSongStart + AudioFadeDuration) { + _suppressSongGain.finish(); + _suppressSongAnim = false; + } else { + _suppressSongGain.update((ms - _suppressSongStart) / float64(AudioFadeDuration), anim::linear); + } + } + suppressSongGain = qMin(suppressAllGain, _suppressSongGain.current()); + suppressSongChanged = (suppressSongGain != wasSong); + } + bool hasFading = (_suppressAll || _suppressSongAnim), hasPlaying = false; + for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { - AudioPlayer::Msg &m(voice->_data[i]); + AudioPlayer::AudioMsg &m(voice->_audioData[i]); if (m.state == AudioPlayerStopped || m.state == AudioPlayerStoppedAtStart || m.state == AudioPlayerPaused || !m.source) continue; - bool playing = false, fading = false; - ALint pos = 0; - ALint state = AL_INITIAL; - alGetSourcei(m.source, AL_SAMPLE_OFFSET, &pos); - alGetSourcei(m.source, AL_SOURCE_STATE, &state); - if (!_checkALError()) { - m.state = AudioPlayerStopped; - emit error(m.audio); - } else { - switch (m.state) { - case AudioPlayerFinishing: - case AudioPlayerPausing: - case AudioPlayerStarting: - case AudioPlayerResuming: - fading = true; - break; - case AudioPlayerPlaying: - playing = true; - break; - } - if (fading && (state == AL_PLAYING || !m.loading)) { - if (state != AL_PLAYING) { - fading = false; - if (m.source) { - alSourcef(m.source, AL_GAIN, 1); - alSourceStop(m.source); - } - m.state = AudioPlayerStopped; - emit audioStopped(m.audio); - } else if (1000 * (pos + m.skipStart - m.started) >= AudioFadeDuration * m.frequency) { - fading = false; - alSourcef(m.source, AL_GAIN, 1); - switch (m.state) { - case AudioPlayerFinishing: alSourceStop(m.source); m.state = AudioPlayerStopped; break; - case AudioPlayerPausing: alSourcePause(m.source); m.state = AudioPlayerPaused; break; - case AudioPlayerStarting: - case AudioPlayerResuming: - m.state = AudioPlayerPlaying; - playing = true; - break; - } - } else { - float64 newGain = 1000. * (pos + m.skipStart - m.started) / (AudioFadeDuration * m.frequency); - if (m.state == AudioPlayerPausing || m.state == AudioPlayerFinishing) { - newGain = 1. - newGain; - } - alSourcef(m.source, AL_GAIN, newGain); - } - } else if (playing && (state == AL_PLAYING || !m.loading)) { - if (state != AL_PLAYING) { - playing = false; - if (m.source) { - alSourceStop(m.source); - alSourcef(m.source, AL_GAIN, 1); - } - m.state = AudioPlayerStopped; - emit audioStopped(m.audio); - } - } - if (state == AL_PLAYING && pos + m.skipStart - m.position >= AudioCheckPositionDelta) { - m.position = pos + m.skipStart; - emit playPositionUpdated(m.audio); - } - if (!m.loading && m.skipEnd > 0 && m.position + AudioPreloadSamples + m.skipEnd > m.duration) { - m.loading = true; - emit needToPreload(m.audio); - } - if (playing) hasPlaying = true; - if (fading) hasFading = true; - } + int32 emitSignals = updateOnePlayback(&m, hasPlaying, hasFading, suppressAllGain, suppressAudioChanged); + if (emitSignals & EmitError) emit error(m.audio); + if (emitSignals & EmitStopped) emit audioStopped(m.audio); + if (emitSignals & EmitPositionUpdated) emit playPositionUpdated(m.audio); + if (emitSignals & EmitNeedToPreload) emit needToPreload(m.audio); } - if (!hasPlaying) { - ALint state = AL_INITIAL; - alGetSourcei(notifySource, AL_SOURCE_STATE, &state); - if (_checkALError() && state == AL_PLAYING) { - hasPlaying = true; + + for (int32 i = 0; i < AudioSongSimultaneously; ++i) { + AudioPlayer::SongMsg &m(voice->_songData[i]); + if (m.state == AudioPlayerStopped || m.state == AudioPlayerStoppedAtStart || m.state == AudioPlayerPaused || !m.source) continue; + + int32 emitSignals = updateOnePlayback(&m, hasPlaying, hasFading, suppressSongGain, suppressSongChanged); + if (emitSignals & EmitError) emit error(m.song); + if (emitSignals & EmitStopped) emit audioStopped(m.song); + if (emitSignals & EmitPositionUpdated) emit playPositionUpdated(m.song); + if (emitSignals & EmitNeedToPreload) emit needToPreload(m.song); + } + + if (!hasFading) { + if (!hasPlaying) { + ALint state = AL_INITIAL; + alGetSourcei(notifySource, AL_SOURCE_STATE, &state); + if (_checkALError() && state == AL_PLAYING) { + hasPlaying = true; + } } } if (hasFading) { @@ -570,6 +718,87 @@ void AudioPlayerFader::onTimer() { } } +int32 AudioPlayerFader::updateOnePlayback(AudioPlayer::Msg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged) { + bool playing = false, fading = false; + + ALint pos = 0; + ALint state = AL_INITIAL; + alGetSourcei(m->source, AL_SAMPLE_OFFSET, &pos); + alGetSourcei(m->source, AL_SOURCE_STATE, &state); + if (!_checkALError()) { + m->state = AudioPlayerStopped; + return EmitError; + } + + int32 emitSignals = 0; + switch (m->state) { + case AudioPlayerFinishing: + case AudioPlayerPausing: + case AudioPlayerStarting: + case AudioPlayerResuming: + fading = true; + break; + case AudioPlayerPlaying: + playing = true; + break; + } + if (fading && (state == AL_PLAYING || !m->loading)) { + if (state != AL_PLAYING) { + fading = false; + if (m->source) { + alSourcef(m->source, AL_GAIN, 1); + alSourceStop(m->source); + } + m->state = AudioPlayerStopped; + emitSignals |= EmitStopped; + } else if (1000 * (pos + m->skipStart - m->started) >= AudioFadeDuration * m->frequency) { + fading = false; + alSourcef(m->source, AL_GAIN, 1. * suppressGain); + switch (m->state) { + case AudioPlayerFinishing: alSourceStop(m->source); m->state = AudioPlayerStopped; break; + case AudioPlayerPausing: alSourcePause(m->source); m->state = AudioPlayerPaused; break; + case AudioPlayerStarting: + case AudioPlayerResuming: + m->state = AudioPlayerPlaying; + playing = true; + break; + } + } else { + float64 newGain = 1000. * (pos + m->skipStart - m->started) / (AudioFadeDuration * m->frequency); + if (m->state == AudioPlayerPausing || m->state == AudioPlayerFinishing) { + newGain = 1. - newGain; + } + alSourcef(m->source, AL_GAIN, newGain * suppressGain); + } + } else if (playing && (state == AL_PLAYING || !m->loading)) { + if (state != AL_PLAYING) { + playing = false; + if (m->source) { + alSourceStop(m->source); + alSourcef(m->source, AL_GAIN, 1); + } + m->state = AudioPlayerStopped; + emitSignals |= EmitStopped; + } else if (suppressGainChanged) { + alSourcef(m->source, AL_GAIN, suppressGain); + } + } + if (state == AL_PLAYING && pos + m->skipStart - m->position >= AudioCheckPositionDelta) { + m->position = pos + m->skipStart; + emitSignals |= EmitPositionUpdated; + } + if (playing || m->state == AudioPlayerStarting || m->state == AudioPlayerResuming) { + if (!m->loading && m->skipEnd > 0 && m->position + AudioPreloadSamples + m->skipEnd > m->duration) { + m->loading = true; + emitSignals |= EmitNeedToPreload; + } + } + if (playing) hasPlaying = true; + if (fading) hasFading = true; + + return emitSignals; +} + void AudioPlayerFader::onPauseTimer() { QMutexLocker lock(&_pauseMutex); if (_pauseFlag) { @@ -582,6 +811,33 @@ void AudioPlayerFader::onPauseTimerStop() { if (_pauseTimer.isActive()) _pauseTimer.stop(); } +void AudioPlayerFader::onSuppressSong() { + if (!_suppressSong) { + _suppressSong = true; + _suppressSongAnim = true; + _suppressSongStart = getms(); + _suppressSongGain.start(st::suppressSong); + onTimer(); + } +} + +void AudioPlayerFader::onUnsuppressSong() { + if (_suppressSong) { + _suppressSong = false; + _suppressSongAnim = true; + _suppressSongStart = getms(); + _suppressSongGain.start(1.); + onTimer(); + } +} + +void AudioPlayerFader::onSuppressAll() { + _suppressAll = true; + _suppressAllStart = getms(); + _suppressAllGain.start(st::suppressAll); + onTimer(); +} + void AudioPlayerFader::resumeDevice() { QMutexLocker lock(&_pauseMutex); _pauseFlag = false; @@ -607,7 +863,6 @@ public: virtual int64 duration() = 0; virtual int32 frequency() = 0; virtual int32 format() = 0; - virtual void started() = 0; virtual bool readMore(QByteArray &result, int64 &samplesAdded) = 0; protected: @@ -777,9 +1032,6 @@ public: return fmt; } - void started() { - } - bool readMore(QByteArray &result, int64 &samplesAdded) { int res; if ((res = av_read_frame(fmtContext, &avpkt)) < 0) { @@ -916,113 +1168,81 @@ private: } }; -AudioPlayerLoaders::AudioPlayerLoaders(QThread *thread) { +AudioPlayerLoaders::AudioPlayerLoaders(QThread *thread) : _audioLoader(0), _songLoader(0) { moveToThread(thread); } AudioPlayerLoaders::~AudioPlayerLoaders() { - for (Loaders::iterator i = _loaders.begin(), e = _loaders.end(); i != e; ++i) { - delete i.value(); - } - _loaders.clear(); + delete _audioLoader; + delete _songLoader; } void AudioPlayerLoaders::onInit() { } -void AudioPlayerLoaders::onStart(AudioData *audio) { - Loaders::iterator i = _loaders.find(audio); - if (i != _loaders.end()) { - delete (*i); - _loaders.erase(i); - } +void AudioPlayerLoaders::onStart(const AudioMsgId &audio) { + _audio = AudioMsgId(); + delete _audioLoader; + _audioLoader = 0; onLoad(audio); } -void AudioPlayerLoaders::loadError(Loaders::iterator i) { - emit error(i.key()); - delete (*i); - _loaders.erase(i); +void AudioPlayerLoaders::onStart(const SongMsgId &song) { + _song = SongMsgId(); + delete _songLoader; + _songLoader = 0; + onLoad(song); } -void AudioPlayerLoaders::onLoad(AudioData *audio) { - bool started = false; - int32 audioindex = -1; - AudioPlayerLoader *l = 0; - Loaders::iterator j = _loaders.end(); - { - QMutexLocker lock(&playerMutex); - AudioPlayer *voice = audioPlayer(); - if (!voice) return; - - for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { - AudioPlayer::Msg &m(voice->_data[i]); - if (m.audio != audio || !m.loading) continue; - - audioindex = i; - j = _loaders.find(audio); - if (j != _loaders.end() && !j.value()->check(m.fname, m.data)) { - delete j.value(); - _loaders.erase(j); - j = _loaders.end(); - } - if (j == _loaders.end()) { - QByteArray header = m.data.mid(0, 8); - if (header.isEmpty()) { - QFile f(m.fname); - if (!f.open(QIODevice::ReadOnly)) { - LOG(("Audio Error: could not open file '%1'").arg(m.fname)); - m.state = AudioPlayerStoppedAtStart; - emit error(audio); - return; - } - header = f.read(8); - } - if (header.size() < 8) { - LOG(("Audio Error: could not read header from file '%1', data size %2").arg(m.fname).arg(m.data.isEmpty() ? QFileInfo(m.fname).size() : m.data.size())); - m.state = AudioPlayerStoppedAtStart; - emit error(audio); - return; - } - - l = (j = _loaders.insert(audio, new FFMpegLoader(m.fname, m.data))).value(); - - int ret; - if (!l->open()) { - m.state = AudioPlayerStoppedAtStart; - return loadError(j); - } - int64 duration = l->duration(); - if (duration <= 0) { - m.state = AudioPlayerStoppedAtStart; - return loadError(j); - } - m.duration = duration; - m.frequency = l->frequency(); - if (!m.frequency) m.frequency = AudioVoiceMsgFrequency; - m.skipStart = 0; - m.skipEnd = duration; - m.position = 0; - m.started = 0; - started = true; - } else { - if (!m.skipEnd) continue; - l = j.value(); - } - break; - } +void AudioPlayerLoaders::clear(MediaOverviewType type) { + switch (type) { + case OverviewAudios: clearAudio(); break; + case OverviewDocuments: clearSong(); break; } +} - if (j == _loaders.end()) { - LOG(("Audio Error: trying to load part of audio, that is not playing at the moment")); - emit error(audio); +void AudioPlayerLoaders::emitError(MediaOverviewType type) { + switch (type) { + case OverviewAudios: emit error(clearAudio()); break; + case OverviewDocuments: emit error(clearSong()); break; + } +} + +AudioMsgId AudioPlayerLoaders::clearAudio() { + AudioMsgId current = _audio; + _audio = AudioMsgId(); + delete _audioLoader; + _audioLoader = 0; + return current; +} + +SongMsgId AudioPlayerLoaders::clearSong() { + SongMsgId current = _song; + _song = SongMsgId(); + delete _songLoader; + _songLoader = 0; + return current; +} + +void AudioPlayerLoaders::onLoad(const AudioMsgId &audio) { + loadData(OverviewAudios, static_cast(&audio)); +} + +void AudioPlayerLoaders::onLoad(const SongMsgId &song) { + loadData(OverviewDocuments, static_cast(&song)); +} + +void AudioPlayerLoaders::loadData(MediaOverviewType type, const void *objId) { + SetupError err = SetupNoErrorStarted; + AudioPlayerLoader *l = setupLoader(type, objId, err); + if (!l) { + if (err == SetupErrorAtStart) { + emitError(type); + } return; } - if (started) { - l->started(); - } - bool finished = false; + bool started = (err == SetupNoErrorStarted), finished = false; QByteArray result; int64 samplesAdded = 0, frequency = l->frequency(), format = l->format(); @@ -1031,102 +1251,228 @@ void AudioPlayerLoaders::onLoad(AudioData *audio) { finished = true; break; } - { - QMutexLocker lock(&playerMutex); - AudioPlayer *voice = audioPlayer(); - if (!voice) return; - AudioPlayer::Msg &m(voice->_data[audioindex]); - if (m.audio != audio || !m.loading || !l->check(m.fname, m.data)) { - LOG(("Audio Error: playing changed while loading")); - m.state = AudioPlayerStopped; - return loadError(j); - } + QMutexLocker lock(&playerMutex); + if (!checkLoader(type)) { + clear(type); + return; } } QMutexLocker lock(&playerMutex); - AudioPlayer *voice = audioPlayer(); - if (!voice) return; - - AudioPlayer::Msg &m(voice->_data[audioindex]); - if (m.audio != audio || !m.loading || !l->check(m.fname, m.data)) { - LOG(("Audio Error: playing changed while loading")); - m.state = AudioPlayerStopped; - return loadError(j); + AudioPlayer::Msg *m = checkLoader(type); + if (!m) { + clear(type); + return; } if (started) { - if (m.source) { - alSourceStop(m.source); + if (m->source) { + alSourceStop(m->source); for (int32 i = 0; i < 3; ++i) { - if (m.samplesCount[i]) { - alSourceUnqueueBuffers(m.source, 1, m.buffers + i); - m.samplesCount[i] = 0; + if (m->samplesCount[i]) { + alSourceUnqueueBuffers(m->source, 1, m->buffers + i); + m->samplesCount[i] = 0; } } - m.nextBuffer = 0; + m->nextBuffer = 0; } } if (samplesAdded) { - if (!m.source) { - alGenSources(1, &m.source); - alSourcef(m.source, AL_PITCH, 1.f); - alSourcef(m.source, AL_GAIN, 1.f); - alSource3f(m.source, AL_POSITION, 0, 0, 0); - alSource3f(m.source, AL_VELOCITY, 0, 0, 0); - alSourcei(m.source, AL_LOOPING, 0); + if (!m->source) { + alGenSources(1, &m->source); + alSourcef(m->source, AL_PITCH, 1.f); + alSourcef(m->source, AL_GAIN, 1.f); + alSource3f(m->source, AL_POSITION, 0, 0, 0); + alSource3f(m->source, AL_VELOCITY, 0, 0, 0); + alSourcei(m->source, AL_LOOPING, 0); } - if (!m.buffers[m.nextBuffer]) alGenBuffers(3, m.buffers); + if (!m->buffers[m->nextBuffer]) alGenBuffers(3, m->buffers); if (!_checkALError()) { - m.state = AudioPlayerStopped; - return loadError(j); + m->state = AudioPlayerStopped; + emitError(type); + return; } - if (m.samplesCount[m.nextBuffer]) { - alSourceUnqueueBuffers(m.source, 1, m.buffers + m.nextBuffer); - m.skipStart += m.samplesCount[m.nextBuffer]; + if (m->samplesCount[m->nextBuffer]) { + alSourceUnqueueBuffers(m->source, 1, m->buffers + m->nextBuffer); + m->skipStart += m->samplesCount[m->nextBuffer]; } - m.samplesCount[m.nextBuffer] = samplesAdded; - alBufferData(m.buffers[m.nextBuffer], format, result.constData(), result.size(), frequency); - alSourceQueueBuffers(m.source, 1, m.buffers + m.nextBuffer); - m.skipEnd -= samplesAdded; + m->samplesCount[m->nextBuffer] = samplesAdded; + alBufferData(m->buffers[m->nextBuffer], format, result.constData(), result.size(), frequency); + alSourceQueueBuffers(m->source, 1, m->buffers + m->nextBuffer); + m->skipEnd -= samplesAdded; - m.nextBuffer = (m.nextBuffer + 1) % 3; + m->nextBuffer = (m->nextBuffer + 1) % 3; if (!_checkALError()) { - m.state = AudioPlayerStopped; - return loadError(j); + m->state = AudioPlayerStopped; + emitError(type); + return; } } else { finished = true; } if (finished) { - m.skipEnd = 0; - m.duration = m.skipStart + m.samplesCount[0] + m.samplesCount[1] + m.samplesCount[2]; - delete j.value(); - _loaders.erase(j); + m->skipEnd = 0; + m->duration = m->skipStart + m->samplesCount[0] + m->samplesCount[1] + m->samplesCount[2]; + clear(type); } - m.loading = false; - if (m.state == AudioPlayerResuming || m.state == AudioPlayerPlaying || m.state == AudioPlayerStarting) { + m->loading = false; + if (m->state == AudioPlayerResuming || m->state == AudioPlayerPlaying || m->state == AudioPlayerStarting) { ALint state = AL_INITIAL; - alGetSourcei(m.source, AL_SOURCE_STATE, &state); + alGetSourcei(m->source, AL_SOURCE_STATE, &state); if (_checkALError()) { if (state != AL_PLAYING) { - voice->resumeDevice(); - alSourcePlay(m.source); + audioPlayer()->resumeDevice(); + switch (type) { + case OverviewAudios: alSourcef(m->source, AL_GAIN, suppressAllGain); break; + case OverviewDocuments: alSourcef(m->source, AL_GAIN, suppressSongGain); break; + } + alSourcePlay(m->source); emit needToCheck(); } } } } -void AudioPlayerLoaders::onCancel(AudioData *audio) { - Loaders::iterator i = _loaders.find(audio); - if (i != _loaders.end()) { - delete (*i); - _loaders.erase(i); +AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const void *objId, SetupError &err) { + err = SetupErrorAtStart; + QMutexLocker lock(&playerMutex); + AudioPlayer *voice = audioPlayer(); + if (!voice) return 0; + + bool isGoodId = false; + AudioPlayer::Msg *m = 0; + AudioPlayerLoader **l = 0; + switch (type) { + case OverviewAudios: { + AudioPlayer::AudioMsg &msg(voice->_audioData[voice->_audioCurrent]); + const AudioMsgId &audio(*static_cast(objId)); + if (msg.audio != audio || !msg.loading) { + emit error(audio); + break; + } + m = &msg; + l = &_audioLoader; + isGoodId = (_audio == audio); + } break; + case OverviewDocuments: { + AudioPlayer::SongMsg &msg(voice->_songData[voice->_songCurrent]); + const SongMsgId &song(*static_cast(objId)); + if (msg.song != song || !msg.loading) { + emit error(song); + break; + } + m = &msg; + l = &_songLoader; + isGoodId = (_song == song); + } break; + } + if (!l || !m) { + LOG(("Audio Error: trying to load part of audio, that is not current at the moment")); + err = SetupErrorNotPlaying; + return 0; + } + + if (*l && (!isGoodId || !(*l)->check(m->fname, m->data))) { + delete *l; + *l = 0; + switch (type) { + case OverviewAudios: _audio = AudioMsgId(); break; + case OverviewDocuments: _song = SongMsgId(); break; + } + } + + if (!*l) { + switch (type) { + case OverviewAudios: _audio = *static_cast(objId); break; + case OverviewDocuments: _song = *static_cast(objId); break; + } + + QByteArray header = m->data.mid(0, 8); + if (header.isEmpty()) { + QFile f(m->fname); + if (!f.open(QIODevice::ReadOnly)) { + LOG(("Audio Error: could not open file '%1'").arg(m->fname)); + m->state = AudioPlayerStoppedAtStart; + return 0; + } + header = f.read(8); + } + if (header.size() < 8) { + LOG(("Audio Error: could not read header from file '%1', data size %2").arg(m->fname).arg(m->data.isEmpty() ? QFileInfo(m->fname).size() : m->data.size())); + m->state = AudioPlayerStoppedAtStart; + return 0; + } + + *l = new FFMpegLoader(m->fname, m->data); + + int ret; + if (!(*l)->open()) { + m->state = AudioPlayerStoppedAtStart; + return 0; + } + int64 duration = (*l)->duration(); + if (duration <= 0) { + m->state = AudioPlayerStoppedAtStart; + return 0; + } + m->duration = duration; + m->frequency = (*l)->frequency(); + if (!m->frequency) m->frequency = AudioVoiceMsgFrequency; + m->skipStart = 0; + m->skipEnd = duration; + m->position = 0; + m->started = 0; + err = SetupNoErrorStarted; + } else { + if (!m->skipEnd) { + err = SetupErrorLoadedFull; + LOG(("Audio Error: trying to load part of audio, that is already loaded to the end")); + return 0; + } + } + return *l; +} + +AudioPlayer::Msg *AudioPlayerLoaders::checkLoader(MediaOverviewType type) { + AudioPlayer *voice = audioPlayer(); + if (!voice) return 0; + + bool isGoodId = false; + AudioPlayer::Msg *m = 0; + AudioPlayerLoader **l = 0; + switch (type) { + case OverviewAudios: { + AudioPlayer::AudioMsg &msg(voice->_audioData[voice->_audioCurrent]); + isGoodId = (msg.audio == _audio); + l = &_audioLoader; + m = &msg; + } break; + case OverviewDocuments: { + AudioPlayer::SongMsg &msg(voice->_songData[voice->_songCurrent]); + isGoodId = (msg.song == _song); + l = &_songLoader; + m = &msg; + } break; + } + if (!l || !m) return 0; + + if (!isGoodId || !m->loading || !(*l)->check(m->fname, m->data)) { + LOG(("Audio Error: playing changed while loading")); + return 0; + } + + return m; +} + +void AudioPlayerLoaders::onCancel(const AudioMsgId &audio) { + if (_audio == audio) { + _audio = AudioMsgId(); + delete _audioLoader; + _audioLoader = 0; } QMutexLocker lock(&playerMutex); @@ -1134,13 +1480,32 @@ void AudioPlayerLoaders::onCancel(AudioData *audio) { if (!voice) return; for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) { - AudioPlayer::Msg &m(voice->_data[i]); + AudioPlayer::AudioMsg &m(voice->_audioData[i]); if (m.audio == audio) { m.loading = false; } } } +void AudioPlayerLoaders::onCancel(const SongMsgId &song) { + if (_song == song) { + _song = SongMsgId(); + delete _songLoader; + _songLoader = 0; + } + + QMutexLocker lock(&playerMutex); + AudioPlayer *voice = audioPlayer(); + if (!voice) return; + + for (int32 i = 0; i < AudioSongSimultaneously; ++i) { + AudioPlayer::SongMsg &m(voice->_songData[i]); + if (m.song == song) { + m.loading = false; + } + } +} + struct AudioCapturePrivate { AudioCapturePrivate() : device(0), fmt(0), ioBuffer(0), ioContext(0), fmtContext(0), stream(0), codec(0), codecContext(0), opened(false), @@ -1611,3 +1976,223 @@ void AudioCaptureInner::writeFrame(int32 offset, int32 framesize) { av_frame_free(&frame); } + +class FFMpegAttributesReader : public AudioPlayerLoader { +public: + + FFMpegAttributesReader(const QString &fname, const QByteArray &data) : AudioPlayerLoader(fname, data), + ioBuffer(0), ioContext(0), fmtContext(0), codec(0), streamId(0), + _opened(false) { + } + + bool open() { + if (!AudioPlayerLoader::openFile()) { + return false; + } + + ioBuffer = (uchar*)av_malloc(AVBlockSize); + if (data.isEmpty()) { + ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegAttributesReader::_read_file, 0, &FFMpegAttributesReader::_seek_file); + } else { + ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, static_cast(this), &FFMpegAttributesReader::_read_data, 0, &FFMpegAttributesReader::_seek_data); + } + fmtContext = avformat_alloc_context(); + if (!fmtContext) { + DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(fname).arg(data.size())); + return false; + } + fmtContext->pb = ioContext; + + int res = 0; + char err[AV_ERROR_MAX_STRING_SIZE] = { 0 }; + if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) { + DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + _opened = true; + + if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) { + DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res))); + return false; + } + + streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); + if (streamId >= 0) { + DEBUG_LOG(("Audio Read Error: Found video stream in file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId))); + return false; + } + + streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0); + if (streamId < 0) { + DEBUG_LOG(("Audio Read Error: Unable to av_find_best_stream for file '%1', data size '%2', error %3, %4").arg(fname).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId))); + return false; + } + + freq = fmtContext->streams[streamId]->codec->sample_rate; + len = (fmtContext->streams[streamId]->duration * freq) / fmtContext->streams[streamId]->time_base.den; + + for (int32 i = 0, l = fmtContext->nb_streams; i < l; ++i) { + AVStream *stream = fmtContext->streams[i]; + if (stream->disposition & AV_DISPOSITION_ATTACHED_PIC) { + const AVPacket &packet(stream->attached_pic); + if (packet.size) { + bool animated = false; + QByteArray cover((const char*)packet.data, packet.size), format; + _cover = App::readImage(cover, &format, true, &animated); + if (!_cover.isNull()) { + _coverBytes = cover; + _coverFormat = format; + break; + } + } + } + } + + extractMetaData(fmtContext->streams[streamId]->metadata); + extractMetaData(fmtContext->metadata); + + return true; + } + + void trySet(QString &to, AVDictionary *dict, const char *key) { + if (!to.isEmpty()) return; + if (AVDictionaryEntry* tag = av_dict_get(dict, key, 0, 0)) { + to = QString::fromUtf8(tag->value); + } + } + void extractMetaData(AVDictionary *dict) { + trySet(_title, dict, "title"); + trySet(_performer, dict, "artist"); + trySet(_performer, dict, "performer"); + trySet(_performer, dict, "album_artist"); + for (AVDictionaryEntry *tag = av_dict_get(dict, "", 0, AV_DICT_IGNORE_SUFFIX); tag; tag = av_dict_get(dict, "", tag, AV_DICT_IGNORE_SUFFIX)) { + const char *key = tag->key; + const char *value = tag->value; + QString tmp = QString::fromUtf8(value); + } + } + + int64 duration() { + return len; + } + + int32 frequency() { + return freq; + } + + int32 format() { + return 0; + } + + QString title() { + return _title; + } + + QString performer() { + return _performer; + } + + QImage cover() { + return _cover; + } + + QByteArray coverBytes() { + return _coverBytes; + } + + QByteArray coverFormat() { + return _coverFormat; + } + + bool readMore(QByteArray &result, int64 &samplesAdded) { + DEBUG_LOG(("Audio Read Error: should not call this")); + return false; + } + + ~FFMpegAttributesReader() { + if (ioContext) av_free(ioContext); + if (_opened) { + avformat_close_input(&fmtContext); + } else if (ioBuffer) { + av_free(ioBuffer); + } + if (fmtContext) avformat_free_context(fmtContext); + } + +private: + + QString fname, data; + + int32 freq; + int64 len; + QString _title, _performer; + QImage _cover; + QByteArray _coverBytes, _coverFormat; + + uchar *ioBuffer; + AVIOContext *ioContext; + AVFormatContext *fmtContext; + AVCodec *codec; + int32 streamId; + + bool _opened; + + static int _read_data(void *opaque, uint8_t *buf, int buf_size) { + FFMpegAttributesReader *l = reinterpret_cast(opaque); + + int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size)); + if (nbytes <= 0) { + return 0; + } + + memcpy(buf, l->data.constData() + l->dataPos, nbytes); + l->dataPos += nbytes; + return nbytes; + } + + static int64_t _seek_data(void *opaque, int64_t offset, int whence) { + FFMpegAttributesReader *l = reinterpret_cast(opaque); + + int32 newPos = -1; + switch (whence) { + case SEEK_SET: newPos = offset; break; + case SEEK_CUR: newPos = l->dataPos + offset; break; + case SEEK_END: newPos = l->data.size() + offset; break; + } + if (newPos < 0 || newPos > l->data.size()) { + return -1; + } + l->dataPos = newPos; + return l->dataPos; + } + + static int _read_file(void *opaque, uint8_t *buf, int buf_size) { + FFMpegAttributesReader *l = reinterpret_cast(opaque); + return int(l->f.read((char*)(buf), buf_size)); + } + + static int64_t _seek_file(void *opaque, int64_t offset, int whence) { + FFMpegAttributesReader *l = reinterpret_cast(opaque); + + switch (whence) { + case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1; + case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1; + case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1; + } + return -1; + } +}; + +MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat) { + FFMpegAttributesReader reader(fname, data); + if (reader.open()) { + int32 duration = reader.duration() / reader.frequency(); + if (reader.duration() > 0) { + cover = reader.cover(); + coverBytes = reader.coverBytes(); + coverFormat = reader.coverFormat(); + return MTP_documentAttributeAudio(MTP_int(duration), MTP_string(reader.title()), MTP_string(reader.performer())); + } + } + return MTP_documentAttributeFilename(MTP_string(fname)); +} diff --git a/Telegram/SourceFiles/audio.h b/Telegram/SourceFiles/audio.h index 8aa33952b5..f28a21d5ea 100644 --- a/Telegram/SourceFiles/audio.h +++ b/Telegram/SourceFiles/audio.h @@ -45,39 +45,59 @@ public: AudioPlayer(); - void play(AudioData *audio); - void pauseresume(); + void play(const AudioMsgId &audio); + void play(const SongMsgId &song); + void pauseresume(MediaOverviewType type); + + void currentState(AudioMsgId *audio, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0); + void currentState(SongMsgId *song, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0); + + void clearStoppedAtStart(const AudioMsgId &audio); + void clearStoppedAtStart(const SongMsgId &song); - void currentState(AudioData **audio, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0); - void clearStoppedAtStart(AudioData *audio); void resumeDevice(); ~AudioPlayer(); public slots: - void onError(AudioData *audio); + void onError(const AudioMsgId &audio); + void onError(const SongMsgId &song); + + void onStopped(const AudioMsgId &audio); + void onStopped(const SongMsgId &song); signals: - void updated(AudioData *audio); - void stopped(AudioData *audio); + void updated(const AudioMsgId &audio); + void updated(const SongMsgId &song); + + void stopped(const AudioMsgId &audio); + void stopped(const SongMsgId &song); + + void loaderOnStart(const AudioMsgId &audio); + void loaderOnStart(const SongMsgId &song); + + void loaderOnCancel(const AudioMsgId &audio); + void loaderOnCancel(const SongMsgId &song); void faderOnTimer(); - void loaderOnStart(AudioData *audio); - void loaderOnCancel(AudioData *audio); + void suppressSong(); + void unsuppressSong(); + void suppressAll(); private: - bool updateCurrentStarted(int32 pos = -1); + bool startedOther(MediaOverviewType type, bool &fadedStart); + bool updateCurrentStarted(MediaOverviewType type, int32 pos = -1); struct Msg { - Msg() : audio(0), position(0), duration(0), frequency(AudioVoiceMsgFrequency), skipStart(0), skipEnd(0), loading(0), started(0), + Msg() : position(0), duration(0), frequency(AudioVoiceMsgFrequency), skipStart(0), skipEnd(0), loading(0), started(0), state(AudioPlayerStopped), source(0), nextBuffer(0) { memset(buffers, 0, sizeof(buffers)); memset(samplesCount, 0, sizeof(samplesCount)); } - AudioData *audio; + QString fname; QByteArray data; int64 position, duration; @@ -92,9 +112,24 @@ private: uint32 buffers[3]; int64 samplesCount[3]; }; + struct AudioMsg : public Msg { + AudioMsg() { + } + AudioMsgId audio; + }; + struct SongMsg : public Msg { + SongMsg() { + } + SongMsgId song; + }; - int32 _current; - Msg _data[AudioVoiceMsgSimultaneously]; + void currentState(Msg *current, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency); + + int32 _audioCurrent; + AudioMsg _audioData[AudioVoiceMsgSimultaneously]; + + int32 _songCurrent; + SongMsg _songData[AudioSongSimultaneously]; QMutex _mutex; @@ -154,10 +189,14 @@ public: signals: - void error(AudioData *audio); - void playPositionUpdated(AudioData *audio); - void audioStopped(AudioData *audio); - void needToPreload(AudioData *audio); + void error(const AudioMsgId &audio); + void error(const SongMsgId &audio); + void playPositionUpdated(const AudioMsgId &audio); + void playPositionUpdated(const SongMsgId &audio); + void audioStopped(const AudioMsgId &audio); + void audioStopped(const SongMsgId &audio); + void needToPreload(const AudioMsgId &audio); + void needToPreload(const SongMsgId &audio); void stopPauseDevice(); @@ -168,12 +207,28 @@ public slots: void onPauseTimer(); void onPauseTimerStop(); + void onSuppressSong(); + void onUnsuppressSong(); + void onSuppressAll(); + private: + enum { + EmitError = 0x01, + EmitStopped = 0x02, + EmitPositionUpdated = 0x04, + EmitNeedToPreload = 0x08, + }; + int32 updateOnePlayback(AudioPlayer::Msg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged); + QTimer _timer, _pauseTimer; QMutex _pauseMutex; bool _pauseFlag, _paused; + bool _suppressAll, _suppressAllAnim, _suppressSong, _suppressSongAnim; + anim::fvalue _suppressAllGain, _suppressSongGain; + uint64 _suppressAllStart, _suppressSongStart; + }; class AudioPlayerLoader; @@ -187,22 +242,45 @@ public: signals: - void error(AudioData *audio); + void error(const AudioMsgId &audio); + void error(const SongMsgId &song); void needToCheck(); public slots: void onInit(); - void onStart(AudioData *audio); - void onLoad(AudioData *audio); - void onCancel(AudioData *audio); - + + void onStart(const AudioMsgId &audio); + void onStart(const SongMsgId &audio); + + void onLoad(const AudioMsgId &audio); + void onLoad(const SongMsgId &audio); + + void onCancel(const AudioMsgId &audio); + void onCancel(const SongMsgId &audio); + private: - typedef QMap Loaders; - Loaders _loaders; + AudioMsgId _audio; + AudioPlayerLoader *_audioLoader; - void loadError(Loaders::iterator i); + SongMsgId _song; + AudioPlayerLoader *_songLoader; + + void emitError(MediaOverviewType type); + void clear(MediaOverviewType type); + AudioMsgId clearAudio(); + SongMsgId clearSong(); + + enum SetupError { + SetupErrorAtStart = 0, + SetupErrorNotPlaying = 1, + SetupErrorLoadedFull = 2, + SetupNoErrorStarted = 3, + }; + void loadData(MediaOverviewType type, const void *objId); + AudioPlayerLoader *setupLoader(MediaOverviewType type, const void *objId, SetupError &err); + AudioPlayer::Msg *checkLoader(MediaOverviewType type); }; @@ -239,3 +317,5 @@ private: QByteArray _captured; }; + +MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat); diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index dfc6c8986e..12dfd5a02a 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -46,7 +46,7 @@ void StickerSetInner::gotSet(const MTPmessages_StickerSet &set) { _pack.reserve(v.size()); for (int32 i = 0, l = v.size(); i < l; ++i) { DocumentData *doc = App::feedDocument(v.at(i)); - if (!doc || !doc->sticker) continue; + if (!doc || !doc->sticker()) continue; _pack.push_back(doc); } @@ -156,11 +156,11 @@ void StickerSetInner::paintEvent(QPaintEvent *e) { if (!doc->loader && doc->status != FileFailed && !already && !hasdata) { doc->save(QString()); } - if (doc->sticker->img->isNull() && (already || hasdata)) { + if (doc->sticker()->img->isNull() && (already || hasdata)) { if (already) { - doc->sticker->img = ImagePtr(doc->already()); + doc->sticker()->img = ImagePtr(doc->already()); } else { - doc->sticker->img = ImagePtr(doc->data); + doc->sticker()->img = ImagePtr(doc->data); } } } @@ -173,8 +173,8 @@ void StickerSetInner::paintEvent(QPaintEvent *e) { QPoint ppos = pos + QPoint((st::stickersSize.width() - w) / 2, (st::stickersSize.height() - h) / 2); if (goodThumb) { p.drawPixmapLeft(ppos, width(), doc->thumb->pix(w, h)); - } else if (!doc->sticker->img->isNull()) { - p.drawPixmapLeft(ppos, width(), doc->sticker->img->pix(w, h)); + } else if (!doc->sticker()->img->isNull()) { + p.drawPixmapLeft(ppos, width(), doc->sticker()->img->pix(w, h)); } } } diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index eff06ad20e..20e5d0d22f 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -85,9 +85,10 @@ enum { MediaOverviewPreloadCount = 4, AudioVoiceMsgSimultaneously = 4, + AudioSongSimultaneously = 4, AudioCheckPositionTimeout = 100, // 100ms per check audio pos AudioCheckPositionDelta = 4800, // update position called each 4800 samples - AudioFadeTimeout = 10, // 10ms + AudioFadeTimeout = 7, // 7ms AudioFadeDuration = 500, AudioVoiceMsgSkip = 400, // 200ms AudioVoiceMsgFade = 300, // 300ms @@ -100,7 +101,7 @@ enum { AudioVoiceMsgInMemory = 1024 * 1024, // 1 Mb audio is hold in memory and auto loaded AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over - StickerInMemory = 256 * 1024, // 128 Kb stickers hold in memory, auto loaded and displayed inline + StickerInMemory = 1024 * 1024, // 1024 Kb stickers hold in memory, auto loaded and displayed inline StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker MediaViewImageSizeLimit = 100 * 1024 * 1024, // show up to 100mb jpg/png/gif docs in app diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 38e78c7b27..cf41f42c36 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -1216,7 +1216,7 @@ void StickerPanInner::paintEvent(QPaintEvent *e) { float64 hover = _sets[c].hovers[index]; DocumentData *sticker = _sets[c].pack[index]; - if (!sticker->sticker) continue; + if (!sticker->sticker()) continue; QPoint pos(st::stickerPanPadding + j * st::stickerPanSize.width(), y + i * st::stickerPanSize.height()); if (hover > 0) { @@ -1235,11 +1235,11 @@ void StickerPanInner::paintEvent(QPaintEvent *e) { if (!sticker->loader && sticker->status != FileFailed && !already && !hasdata) { sticker->save(QString()); } - if (sticker->sticker->img->isNull() && (already || hasdata)) { + if (sticker->sticker()->img->isNull() && (already || hasdata)) { if (already) { - sticker->sticker->img = ImagePtr(sticker->already()); + sticker->sticker()->img = ImagePtr(sticker->already()); } else { - sticker->sticker->img = ImagePtr(sticker->data); + sticker->sticker()->img = ImagePtr(sticker->data); } } } @@ -1252,8 +1252,8 @@ void StickerPanInner::paintEvent(QPaintEvent *e) { QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2); if (goodThumb) { p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h)); - } else if (!sticker->sticker->img->isNull()) { - p.drawPixmapLeft(ppos, width(), sticker->sticker->img->pix(w, h)); + } else if (!sticker->sticker()->img->isNull()) { + p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h)); } if (hover > 0 && _sets[c].id == RecentStickerSetId && _custom.at(index)) { @@ -1411,7 +1411,7 @@ void StickerPanInner::preloadImages() { if (++k > StickerPanPerRow * (StickerPanPerRow + 1)) break; DocumentData *sticker = _sets.at(i).pack.at(j); - if (!sticker || !sticker->sticker) continue; + if (!sticker || !sticker->sticker()) continue; bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128)); if (goodThumb) { diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 8de3a9841a..6e25e4b978 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -2541,7 +2541,7 @@ void HistoryAudio::draw(QPainter &p, const HistoryItem *parent, bool selected, i fwd->drawForwardedFrom(p, st::mediaPadding.left(), fwdFrom, width - st::mediaPadding.left() - st::mediaPadding.right(), selected); } - AudioData *playing = 0; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; int64 playingPosition = 0, playingDuration = 0; int32 playingFrequency = 0; @@ -2563,7 +2563,7 @@ void HistoryAudio::draw(QPainter &p, const HistoryItem *parent, bool selected, i img = out ? st::mediaAudioOutImg : st::mediaAudioInImg; } else if (already || hasdata) { bool showPause = false; - if (playing == data && playingState != AudioPlayerStopped && playingState != AudioPlayerStoppedAtStart) { + if (playing.msgId == parent->id && playingState != AudioPlayerStopped && playingState != AudioPlayerStoppedAtStart) { statusText = formatDurationText(playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)) + qsl(" / ") + formatDurationText(playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); } else { @@ -2728,18 +2728,26 @@ HistoryMedia *HistoryAudio::clone() const { return new HistoryAudio(*this); } +namespace { + QString documentName(DocumentData *document) { + SongData *song = document->song(); + if (!song || (song->title.isEmpty() && song->performer.isEmpty())) return document->name; + if (song->performer.isEmpty()) return song->title; + return song->performer + QString::fromUtf8(" \xe2\x80\x94 ") + (song->title.isEmpty() ? qsl("Unknown Track") : song->title); + } +} + HistoryDocument::HistoryDocument(DocumentData *document) : HistoryMedia() , data(document) , _openl(new DocumentOpenLink(data)) , _savel(new DocumentSaveLink(data)) , _cancell(new DocumentCancelLink(data)) -, _name(data->name) +, _name(documentName(data)) , _dldDone(0) , _uplDone(0) { _namew = st::mediaFont->m.width(_name.isEmpty() ? qsl("Document") : _name); - - _size = formatSizeText(data->size); + _size = document->song() ? formatDurationAndSizeText(document->song()->duration, data->size) : formatSizeText(data->size); _height = _minh = st::mediaPadding.top() + st::mediaThumbSize + st::mediaPadding.bottom(); @@ -2800,7 +2808,7 @@ void HistoryDocument::draw(QPainter &p, const HistoryItem *parent, bool selected if (width < 0) width = w; if (width < 1) return; - bool out = parent->out(), hovered, pressed; + bool out = parent->out(), hovered, pressed, already = !data->already().isEmpty(), hasdata = !data->data.isEmpty(); if (parent == animated.msg) { int32 pw = animated.w / cIntRetinaFactor(), ph = animated.h / cIntRetinaFactor(); if (width < pw) { @@ -2835,8 +2843,6 @@ void HistoryDocument::draw(QPainter &p, const HistoryItem *parent, bool selected skipy += fwdFrom; } - data->thumb->checkload(); - if (width >= _maxw) { width = _maxw; } @@ -2854,8 +2860,8 @@ void HistoryDocument::draw(QPainter &p, const HistoryItem *parent, bool selected p.setPen((hovered ? st::mediaSaveButton.overColor : st::mediaSaveButton.color)->p); p.setFont(st::mediaSaveButton.font->f); - QString btnText(lang(data->loader ? lng_media_cancel : (data->already().isEmpty() ? lng_media_download : lng_media_open_with))); - int32 btnTextWidth = data->loader ? _cancelWidth : (data->already().isEmpty() ? _downloadWidth : _openWithWidth); + QString btnText(lang(data->loader ? lng_media_cancel : (already ? lng_media_open_with : lng_media_download))); + int32 btnTextWidth = data->loader ? _cancelWidth : (already ? _openWithWidth : _downloadWidth); p.drawText(btnx + (btnw - btnTextWidth) / 2, btny + (pressed ? st::mediaSaveButton.downTextTop : st::mediaSaveButton.textTop) + st::mediaSaveButton.font->ascent, btnText); width -= btnw + st::mediaSaveDelta; } @@ -2875,10 +2881,76 @@ void HistoryDocument::draw(QPainter &p, const HistoryItem *parent, bool selected } else if (fwd) { fwd->drawForwardedFrom(p, st::mediaPadding.left(), fwdFrom, width - st::mediaPadding.left() - st::mediaPadding.right(), selected); } - if (_thumbw) { - p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), data->thumb->pixSingle(_thumbw, 0, st::mediaThumbSize, st::mediaThumbSize)); + + QString statusText; + if (data->song()) { + SongMsgId playing; + AudioPlayerState playingState = AudioPlayerStopped; + int64 playingPosition = 0, playingDuration = 0; + int32 playingFrequency = 0; + if (audioPlayer()) { + audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency); + } + + QRect img; + if (data->status == FileFailed) { + statusText = lang(lng_attach_failed); + img = out ? st::mediaAudioOutImg : st::mediaAudioInImg; + } else if (data->status == FileUploading) { + if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) { + _uplDone = data->uploadOffset; + _uplTextCache = formatDownloadText(_uplDone, data->size); + } + statusText = _uplTextCache; + img = out ? st::mediaAudioOutImg : st::mediaAudioInImg; + } else if (already || hasdata) { + bool showPause = false; + if (playing.msgId == parent->id && playingState != AudioPlayerStopped && playingState != AudioPlayerStoppedAtStart) { + statusText = formatDurationText(playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)) + qsl(" / ") + formatDurationText(playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)); + showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting); + } else { + statusText = formatDurationText(data->song()->duration); + } + img = out ? (showPause ? st::mediaPauseOutImg : st::mediaPlayOutImg) : (showPause ? st::mediaPauseInImg : st::mediaPlayInImg); + } else { + if (data->loader) { + if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) { + _dldDone = data->loader->currentOffset(); + _dldTextCache = formatDownloadText(_dldDone, data->size); + } + statusText = _dldTextCache; + } else { + statusText = _size; + } + img = out ? st::mediaAudioOutImg : st::mediaAudioInImg; + } + + p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), App::sprite(), img); } else { - p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), App::sprite(), (out ? st::mediaDocOutImg : st::mediaDocInImg)); + if (data->status == FileFailed) { + statusText = lang(lng_attach_failed); + } else if (data->status == FileUploading) { + if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) { + _uplDone = data->uploadOffset; + _uplTextCache = formatDownloadText(_uplDone, data->size); + } + statusText = _uplTextCache; + } else if (data->loader) { + if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) { + _dldDone = data->loader->currentOffset(); + _dldTextCache = formatDownloadText(_dldDone, data->size); + } + statusText = _dldTextCache; + } else { + statusText = _size; + } + + if (_thumbw) { + data->thumb->checkload(); + p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), data->thumb->pixSingle(_thumbw, 0, st::mediaThumbSize, st::mediaThumbSize)); + } else { + p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), App::sprite(), (out ? st::mediaDocOutImg : st::mediaDocInImg)); + } } if (selected) { App::roundRect(p, st::mediaPadding.left(), skipy + st::mediaPadding.top(), st::mediaThumbSize, st::mediaThumbSize, textstyleCurrent()->selectOverlay, SelectedOverlayCorners); @@ -2897,28 +2969,9 @@ void HistoryDocument::draw(QPainter &p, const HistoryItem *parent, bool selected p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, _name); } - QString statusText; - style::color status(selected ? (out ? st::mediaOutSelectColor : st::mediaInSelectColor) : (out ? st::mediaOutColor : st::mediaInColor)); p.setPen(status->p); - if (data->status == FileFailed) { - statusText = lang(lng_attach_failed); - } else if (data->status == FileUploading) { - if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) { - _uplDone = data->uploadOffset; - _uplTextCache = formatDownloadText(_uplDone, data->size); - } - statusText = _uplTextCache; - } else if (data->loader) { - if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) { - _dldDone = data->loader->currentOffset(); - _dldTextCache = formatDownloadText(_dldDone, data->size); - } - statusText = _dldTextCache; - } else { - statusText = _size; - } p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaThumbSize - st::mediaDetailsShift - st::mediaFont->descent, statusText); p.setFont(st::msgDateFont->f); @@ -2975,11 +3028,11 @@ int32 HistoryDocument::resize(int32 width, bool dontRecountText, const HistoryIt } const QString HistoryDocument::inDialogsText() const { - return data->name.isEmpty() ? lang(lng_in_dlg_file) : data->name; + return _name.isEmpty() ? lang(lng_in_dlg_file) : _name; } const QString HistoryDocument::inHistoryText() const { - return qsl("[ ") + lang(lng_in_dlg_file) + (data->name.isEmpty() ? QString() : (qsl(" : ") + data->name)) + qsl(" ]"); + return qsl("[ ") + lang(lng_in_dlg_file) + (_name.isEmpty() ? QString() : (qsl(" : ") + _name)) + qsl(" ]"); } bool HistoryDocument::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const { @@ -3136,24 +3189,24 @@ void HistorySticker::draw(QPainter &p, const HistoryItem *parent, bool selected, if (!data->loader && data->status != FileFailed && !already && !hasdata) { data->save(QString()); } - if (data->sticker->img->isNull() && (already || hasdata)) { + if (data->sticker()->img->isNull() && (already || hasdata)) { if (already) { - data->sticker->img = ImagePtr(data->already()); + data->sticker()->img = ImagePtr(data->already()); } else { - data->sticker->img = ImagePtr(data->data); + data->sticker()->img = ImagePtr(data->data); } } if (selected) { - if (data->sticker->img->isNull()) { + if (data->sticker()->img->isNull()) { p.drawPixmap(QPoint(usex + (usew - pixw) / 2, (_minh - pixh) / 2), data->thumb->pixBlurredColored(st::msgStickerOverlay, pixw, pixh)); } else { - p.drawPixmap(QPoint(usex + (usew - pixw) / 2, (_minh - pixh) / 2), data->sticker->img->pixColored(st::msgStickerOverlay, pixw, pixh)); + p.drawPixmap(QPoint(usex + (usew - pixw) / 2, (_minh - pixh) / 2), data->sticker()->img->pixColored(st::msgStickerOverlay, pixw, pixh)); } } else { - if (data->sticker->img->isNull()) { + if (data->sticker()->img->isNull()) { p.drawPixmap(QPoint(usex + (usew - pixw) / 2, (_minh - pixh) / 2), data->thumb->pixBlurred(pixw, pixh)); } else { - p.drawPixmap(QPoint(usex + (usew - pixw) / 2, (_minh - pixh) / 2), data->sticker->img->pix(pixw, pixh)); + p.drawPixmap(QPoint(usex + (usew - pixw) / 2, (_minh - pixh) / 2), data->sticker()->img->pix(pixw, pixh)); } } @@ -4785,7 +4838,7 @@ void HistoryMessage::initMediaFromText(QString ¤tText) { } void HistoryMessage::initMediaFromDocument(DocumentData *doc) { - if (doc->type == StickerDocument && doc->sticker && doc->dimensions.width() > 0 && doc->dimensions.height() > 0 && doc->dimensions.width() <= StickerMaxSize && doc->dimensions.height() <= StickerMaxSize && doc->size < StickerInMemory) { + if (doc->sticker()) { _media = new HistorySticker(doc); } else { _media = new HistoryDocument(doc); diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 17ca869a32..e9e0344b19 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -839,9 +839,9 @@ void HistoryList::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { if (item && !isUponSelected && !_contextMenuLnk) { if (HistorySticker *sticker = dynamic_cast(msg ? msg->getMedia() : 0)) { DocumentData *doc = sticker->document(); - if (doc && doc->sticker && doc->sticker->set.type() != mtpc_inputStickerSetEmpty) { + if (doc && doc->sticker() && doc->sticker()->set.type() != mtpc_inputStickerSetEmpty) { if (!_menu) _menu = new ContextMenu(this); - _menu->addAction(lang(doc->sticker->setInstalled() ? lng_context_pack_info : lng_context_pack_add), historyWidget, SLOT(onStickerPackInfo())); + _menu->addAction(lang(doc->sticker()->setInstalled() ? lng_context_pack_info : lng_context_pack_add), historyWidget, SLOT(onStickerPackInfo())); } } QString contextMenuText = item->selectedText(FullItemSel); @@ -4349,8 +4349,10 @@ namespace { } if (document->type == AnimatedDocument) { attributes.push_back(MTP_documentAttributeAnimated()); - } else if (document->type == StickerDocument && document->sticker) { - attributes.push_back(MTP_documentAttributeSticker(MTP_string(document->sticker->alt), document->sticker->set)); + } else if (document->type == StickerDocument && document->sticker()) { + attributes.push_back(MTP_documentAttributeSticker(MTP_string(document->sticker()->alt), document->sticker()->set)); + } else if (document->type == SongDocument && document->song()) { + attributes.push_back(MTP_documentAttributeAudio(MTP_int(document->song()->duration), MTP_string(document->song()->title), MTP_string(document->song()->performer))); } return MTP_vector(attributes); } @@ -4864,7 +4866,7 @@ void HistoryWidget::onStickerSend(DocumentData *sticker) { App::main()->finishForwarding(hist); cancelReply(lastKeyboardUsed); - if (sticker->sticker) App::main()->incrementSticker(sticker); + if (sticker->sticker()) App::main()->incrementSticker(sticker); App::historyRegRandom(randomId, newId); App::main()->historyToDown(hist); @@ -4973,8 +4975,8 @@ void HistoryWidget::onReplyForwardPreviewCancel() { void HistoryWidget::onStickerPackInfo() { if (HistoryMessage *item = dynamic_cast(App::contextItem())) { if (HistorySticker *sticker = dynamic_cast(item->getMedia())) { - if (sticker->document() && sticker->document()->sticker && sticker->document()->sticker->set.type() != mtpc_inputStickerSetEmpty) { - App::main()->stickersBox(sticker->document()->sticker->set); + if (sticker->document() && sticker->document()->sticker() && sticker->document()->sticker()->set.type() != mtpc_inputStickerSetEmpty) { + App::main()->stickersBox(sticker->document()->sticker()->set); } } } diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index 146e69583e..d929e5d8b7 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -18,6 +18,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "localimageloader.h" #include "gui/filedialog.h" +#include "audio.h" #include LocalImageLoaderPrivate::LocalImageLoaderPrivate(int32 currentUser, LocalImageLoader *loader, QThread *thread) : QObject(0) @@ -164,7 +165,40 @@ void LocalImageLoaderPrivate::prepareImages() { MTPDocument document(MTP_documentEmpty(MTP_long(0))); MTPAudio audio(MTP_audioEmpty(MTP_long(0))); + bool isSong = false; QByteArray jpeg; + if (type == ToPrepareDocument) { + if (mime == qstr("audio/mp3") || mime == qstr("audio/m4a") || mime == qstr("audio/aac") || mime == qstr("audio/ogg") || + filename.endsWith(qstr(".mp3"), Qt::CaseInsensitive) || filename.endsWith(qstr(".m4a"), Qt::CaseInsensitive) || + filename.endsWith(qstr(".aac"), Qt::CaseInsensitive) || filename.endsWith(qstr(".ogg"), Qt::CaseInsensitive)) { + + QImage cover; + QByteArray coverBytes, coverFormat; + MTPDocumentAttribute audioAttribute = audioReadSongAttributes(file, data, cover, coverBytes, coverFormat); + if (audioAttribute.type() == mtpc_documentAttributeAudio) { + attributes.push_back(audioAttribute); + isSong = true; + if (!cover.isNull()) { // cover to thumb + int32 cw = cover.width(), ch = cover.height(); + if (cw < 20 * ch && ch < 20 * cw) { + QPixmap full = (cw > 90 || ch > 90) ? QPixmap::fromImage(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(cover, Qt::ColorOnly); + { + QByteArray thumbFormat = "JPG"; + int32 thumbQuality = 87; + + QBuffer jpegBuffer(&jpeg); + full.save(&jpegBuffer, thumbFormat, thumbQuality); + } + + photoThumbs.insert('0', full); + thumb = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0)); + + thumbId = MTP::nonce(); + } + } + } + } + } if (type == ToPreparePhoto) { int32 w = img.width(), h = img.height(); @@ -189,7 +223,7 @@ void LocalImageLoaderPrivate::prepareImages() { photo = MTP_photo(MTP_long(id), MTP_long(0), MTP_int(user), MTP_int(unixtime()), MTP_geoPointEmpty(), MTP_vector(photoSizes)); thumbId = id; - } else if ((type == ToPrepareVideo || type == ToPrepareDocument) && !img.isNull()) { + } else if ((type == ToPrepareVideo || type == ToPrepareDocument) && !img.isNull() && !isSong) { int32 w = img.width(), h = img.height(); QByteArray thumbFormat = "JPG"; int32 thumbQuality = 87; diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index feafd3cc2f..243a93f446 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -517,6 +517,8 @@ namespace { typedef QPair FileLocationPair; typedef QMap FileLocationPairs; FileLocationPairs _fileLocationPairs; + typedef QMap FileLocationAliases; + FileLocationAliases _fileLocationAliases; FileKey _locationsKey = 0; FileKey _recentStickersKeyOld = 0, _stickersKey = 0; @@ -566,14 +568,28 @@ namespace { _writeMap(WriteMapFast); } quint32 size = 0; - for (FileLocations::const_iterator i = _fileLocations.cbegin(); i != _fileLocations.cend(); ++i) { + for (FileLocations::const_iterator i = _fileLocations.cbegin(), e = _fileLocations.cend(); i != e; ++i) { // location + type + namelen + name + date + size size += sizeof(quint64) * 2 + sizeof(quint32) + _stringSize(i.value().name) + _dateTimeSize() + sizeof(quint32); } + //end mark + size += sizeof(quint64) * 2 + sizeof(quint32) + _stringSize(QString()) + _dateTimeSize() + sizeof(quint32); + size += sizeof(quint32); // aliases count + for (FileLocationAliases::const_iterator i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) { + // alias + location + size += sizeof(quint64) * 2 + sizeof(quint64) * 2; + } + EncryptedDescriptor data(size); for (FileLocations::const_iterator i = _fileLocations.cbegin(); i != _fileLocations.cend(); ++i) { data.stream << quint64(i.key().first) << quint64(i.key().second) << quint32(i.value().type) << i.value().name << i.value().modified << quint32(i.value().size); } + data.stream << quint64(0) << quint64(0) << quint32(0) << QString() << QDateTime::currentDateTime() << quint32(0); + data.stream << quint32(_fileLocationAliases.size()); + for (FileLocationAliases::const_iterator i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) { + data.stream << quint64(i.key().first) << quint64(i.key().second) << quint64(i.value().first) << quint64(i.value().second); + } + FileWriteDescriptor file(_locationsKey); file.writeEncrypted(data); } @@ -588,12 +604,18 @@ namespace { return; } + bool endMarkFound = false; while (!locations.stream.atEnd()) { quint64 first, second; FileLocation loc; quint32 type; locations.stream >> first >> second >> type >> loc.name >> loc.modified >> loc.size; + if (!first && !second && !type && loc.name.isEmpty() && !loc.size) { // end mark + endMarkFound = true; + break; + } + MediaKey key(first, second); loc.type = StorageFileType(type); @@ -604,6 +626,16 @@ namespace { _writeLocations(); } } + + if (endMarkFound) { + quint32 cnt; + locations.stream >> cnt; + for (int32 i = 0; i < cnt; ++i) { + quint64 kfirst, ksecond, vfirst, vsecond; + locations.stream >> kfirst >> ksecond >> vfirst >> vsecond; + _fileLocationAliases.insert(MediaKey(kfirst, ksecond), MediaKey(vfirst, vsecond)); + } + } } mtpDcOptions *_dcOpts = 0; @@ -1998,12 +2030,21 @@ namespace Local { return (_draftsPositionsMap.constFind(peer) != _draftsPositionsMap.cend()); } - void writeFileLocation(const MediaKey &location, const FileLocation &local) { + void writeFileLocation(MediaKey location, const FileLocation &local) { if (local.name.isEmpty()) return; + FileLocationAliases::const_iterator aliasIt = _fileLocationAliases.constFind(location); + if (aliasIt != _fileLocationAliases.cend()) { + location = aliasIt.value(); + } + FileLocationPairs::iterator i = _fileLocationPairs.find(local.name); if (i != _fileLocationPairs.cend()) { if (i.value().second == local) { + if (i.value().first != location) { + _fileLocationAliases.insert(location, i.value().first); + _writeLocations(WriteMapFast); + } return; } if (i.value().first != location) { @@ -2021,7 +2062,12 @@ namespace Local { _writeLocations(WriteMapFast); } - FileLocation readFileLocation(const MediaKey &location, bool check) { + FileLocation readFileLocation(MediaKey location, bool check) { + FileLocationAliases::const_iterator aliasIt = _fileLocationAliases.constFind(location); + if (aliasIt != _fileLocationAliases.cend()) { + location = aliasIt.value(); + } + FileLocations::iterator i = _fileLocations.find(location); for (FileLocations::iterator i = _fileLocations.find(location); (i != _fileLocations.end()) && (i.key() == location);) { if (check) { @@ -2253,8 +2299,8 @@ namespace Local { stream << quint64(it->id) << quint64(it->access) << it->title << it->shortName << qint32(it->stickers.size()) << qint32(it->hash) << qint32(it->flags); for (StickerPack::const_iterator j = it->stickers.cbegin(), e = it->stickers.cend(); j != e; ++j) { DocumentData *doc = *j; - stream << quint64(doc->id) << quint64(doc->access) << qint32(doc->date) << doc->name << doc->mime << qint32(doc->dc) << qint32(doc->size) << qint32(doc->dimensions.width()) << qint32(doc->dimensions.height()) << qint32(doc->type) << doc->sticker->alt; - switch (doc->sticker->set.type()) { + stream << quint64(doc->id) << quint64(doc->access) << qint32(doc->date) << doc->name << doc->mime << qint32(doc->dc) << qint32(doc->size) << qint32(doc->dimensions.width()) << qint32(doc->dimensions.height()) << qint32(doc->type) << doc->sticker()->alt; + switch (doc->sticker()->set.type()) { case mtpc_inputStickerSetID: { stream << qint32(StickerSetTypeID); } break; @@ -2266,7 +2312,7 @@ namespace Local { stream << qint32(StickerSetTypeEmpty); } break; } - const StorageImageLocation &loc(doc->sticker->loc); + const StorageImageLocation &loc(doc->sticker()->loc); stream << qint32(loc.width) << qint32(loc.height) << qint32(loc.dc) << quint64(loc.volume) << qint32(loc.local) << quint64(loc.secret); } } @@ -2301,7 +2347,7 @@ namespace Local { DocumentData *doc = *j; // id + access + date + namelen + name + mimelen + mime + dc + size + width + height + type + alt + type-of-set - size += sizeof(quint64) + sizeof(quint64) + sizeof(qint32) + _stringSize(doc->name) + _stringSize(doc->mime) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + _stringSize(doc->sticker->alt) + sizeof(qint32); + size += sizeof(quint64) + sizeof(quint64) + sizeof(qint32) + _stringSize(doc->name) + _stringSize(doc->mime) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + _stringSize(doc->sticker()->alt) + sizeof(qint32); // thumb-width + thumb-height + thumb-dc + thumb-volume + thumb-local + thumb-secret size += sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(quint64) + sizeof(qint32) + sizeof(quint64); @@ -2375,7 +2421,7 @@ namespace Local { } DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, ImagePtr(), dc, size, StorageImageLocation()); - if (!doc->sticker) continue; + if (!doc->sticker()) continue; if (value > 0) { def.stickers.push_back(doc); @@ -2499,7 +2545,7 @@ namespace Local { StorageImageLocation thumb(thumbWidth, thumbHeight, thumbDc, thumbVolume, thumbLocal, thumbSecret); DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, thumb.dc ? ImagePtr(thumb) : ImagePtr(), dc, size, thumb); - if (!doc->sticker) continue; + if (!doc->sticker()) continue; set.stickers.push_back(doc); ++set.count; diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index e306168eb9..d3df02d3bc 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -113,8 +113,8 @@ namespace Local { MessageCursor readDraftPositions(const PeerId &peer); bool hasDraftPositions(const PeerId &peer); - void writeFileLocation(const StorageKey &location, const FileLocation &local); - FileLocation readFileLocation(const StorageKey &location, bool check = true); + void writeFileLocation(MediaKey location, const FileLocation &local); + FileLocation readFileLocation(MediaKey location, bool check = true); void writeImage(const StorageKey &location, const ImagePtr &img); void writeImage(const StorageKey &location, const StorageImageSaved &jpeg, bool overwrite = true); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 0dc1e01b8d..635f152fc7 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -376,8 +376,10 @@ _failDifferenceTimeout(1), _lastUpdateTime(0), _cachedX(0), _cachedY(0), _backgr connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings())); connect(this, SIGNAL(showPeerAsync(quint64,qint32,bool,bool)), this, SLOT(showPeer(quint64,qint32,bool,bool)), Qt::QueuedConnection); if (audioPlayer()) { - connect(audioPlayer(), SIGNAL(updated(AudioData*)), this, SLOT(audioPlayProgress(AudioData*))); - connect(audioPlayer(), SIGNAL(stopped(AudioData*)), this, SLOT(audioPlayProgress(AudioData*))); + connect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); + connect(audioPlayer(), SIGNAL(stopped(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); + connect(audioPlayer(), SIGNAL(updated(const SongMsgId&)), this, SLOT(documentPlayProgress(const SongMsgId&))); + connect(audioPlayer(), SIGNAL(stopped(const SongMsgId&)), this, SLOT(documentPlayProgress(const SongMsgId&))); } connect(&_updateMutedTimer, SIGNAL(timeout()), this, SLOT(onUpdateMuted())); @@ -1409,16 +1411,16 @@ void MainWidget::audioLoadProgress(mtpFileLoader *loader) { if (audio->loader->done()) { audio->finish(); QString already = audio->already(); - bool play = audio->openOnSave > 0 && audioPlayer(); + bool play = audio->openOnSave > 0 && audio->openOnSaveMsgId && audioPlayer(); if ((!already.isEmpty() && audio->openOnSave) || (!audio->data.isEmpty() && play)) { if (play) { - AudioData *playing = 0; + AudioMsgId playing; AudioPlayerState state = AudioPlayerStopped; audioPlayer()->currentState(&playing, &state); - if (playing == audio && state != AudioPlayerStopped) { - audioPlayer()->pauseresume(); + if (playing.msgId == audio->openOnSaveMsgId && state != AudioPlayerStopped) { + audioPlayer()->pauseresume(OverviewAudios); } else { - audioPlayer()->play(audio); + audioPlayer()->play(AudioMsgId(audio, audio->openOnSaveMsgId)); if (App::main()) App::main()->audioMarkRead(audio); } } else { @@ -1442,12 +1444,14 @@ void MainWidget::audioLoadProgress(mtpFileLoader *loader) { } } -void MainWidget::audioPlayProgress(AudioData *audio) { - AudioData *playing = 0; +void MainWidget::audioPlayProgress(const AudioMsgId &audioId) { + AudioMsgId playing; AudioPlayerState state = AudioPlayerStopped; audioPlayer()->currentState(&playing, &state); - if (playing == audio && state == AudioPlayerStoppedAtStart) { - audioPlayer()->clearStoppedAtStart(audio); + if (playing == audioId && state == AudioPlayerStoppedAtStart) { + audioPlayer()->clearStoppedAtStart(audioId); + + AudioData *audio = audioId.audio; QString already = audio->already(true); if (already.isEmpty() && !audio->data.isEmpty()) { bool mp3 = (audio->mime == qstr("audio/mp3")); @@ -1469,12 +1473,53 @@ void MainWidget::audioPlayProgress(AudioData *audio) { } } - const AudioItems &items(App::audioItems()); - AudioItems::const_iterator i = items.constFind(audio); - if (i != items.cend()) { - for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) { - msgUpdated(j.key()->history()->peer->id, j.key()); + if (HistoryItem *item = App::histItemById(audioId.msgId)) { + msgUpdated(item->history()->peer->id, item); + } +} + +void MainWidget::documentPlayProgress(const SongMsgId &songId) { + SongMsgId playing; + AudioPlayerState state = AudioPlayerStopped; + audioPlayer()->currentState(&playing, &state); + if (playing == songId && state == AudioPlayerStoppedAtStart) { + audioPlayer()->clearStoppedAtStart(songId); + + DocumentData *document = songId.song; + QString already = document->already(true); + if (already.isEmpty() && !document->data.isEmpty()) { + QString name = document->name, filter; + MimeType mimeType = mimeTypeForName(document->mime); + QStringList p = mimeType.globPatterns(); + QString pattern = p.isEmpty() ? QString() : p.front(); + if (name.isEmpty()) { + name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString()); + } + if (pattern.isEmpty()) { + filter = qsl("All files (*.*)"); + } else { + filter = mimeType.filterString() + qsl(";;All files (*.*)"); + } + QString filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, false); + if (!filename.isEmpty()) { + QFile f(filename); + if (f.open(QIODevice::WriteOnly)) { + if (f.write(document->data) == document->data.size()) { + f.close(); + already = filename; + document->location = FileLocation(mtpToStorageType(mtpc_storage_filePartial), filename); + Local::writeFileLocation(mediaKey(mtpToLocationType(mtpc_inputDocumentFileLocation), document->dc, document->id), FileLocation(mtpToStorageType(mtpc_storage_filePartial), filename)); + } + } + } } + if (!already.isEmpty()) { + psOpenFile(already); + } + } + + if (HistoryItem *item = App::histItemById(songId.msgId)) { + msgUpdated(item->history()->peer->id, item); } } @@ -1499,11 +1544,22 @@ void MainWidget::documentLoadProgress(mtpFileLoader *loader) { if (document->loader->done()) { document->finish(); QString already = document->already(); - if (!already.isEmpty() && document->openOnSave) { - if (document->openOnSave > 0 && document->size < MediaViewImageSizeLimit) { + + HistoryItem *item = (document->openOnSave && document->openOnSaveMsgId) ? App::histItemById(document->openOnSaveMsgId) : 0; + bool play = document->song() && audioPlayer() && document->openOnSave && item; + if ((!already.isEmpty() || (!document->data.isEmpty() && play)) && document->openOnSave) { + if (play) { + SongMsgId playing; + AudioPlayerState playingState = AudioPlayerStopped; + audioPlayer()->currentState(&playing, &playingState); + if (playing.msgId == item->id && playingState != AudioPlayerStopped) { + audioPlayer()->pauseresume(OverviewDocuments); + } else { + audioPlayer()->play(SongMsgId(document, item->id)); + } + } else if(document->openOnSave > 0 && document->size < MediaViewImageSizeLimit) { QImageReader reader(already); if (reader.canRead()) { - HistoryItem *item = App::histItemById(document->openOnSaveMsgId); if (reader.supportsAnimation() && reader.imageCount() > 1 && item) { startGif(item, already); } else if (item) { @@ -2884,7 +2940,7 @@ void MainWidget::updateNotifySetting(PeerData *peer, bool enabled) { } void MainWidget::incrementSticker(DocumentData *sticker) { - if (!sticker || !sticker->sticker) return; + if (!sticker || !sticker->sticker()) return; RecentStickerPack &recent(cGetRecentStickers()); RecentStickerPack::iterator i = recent.begin(), e = recent.end(); @@ -2925,9 +2981,9 @@ void MainWidget::incrementSticker(DocumentData *sticker) { bool found = false; uint64 setId = 0; QString setName; - switch (sticker->sticker->set.type()) { - case mtpc_inputStickerSetID: setId = sticker->sticker->set.c_inputStickerSetID().vid.v; break; - case mtpc_inputStickerSetShortName: setName = qs(sticker->sticker->set.c_inputStickerSetShortName().vshort_name).toLower().trimmed(); break; + switch (sticker->sticker()->set.type()) { + case mtpc_inputStickerSetID: setId = sticker->sticker()->set.c_inputStickerSetID().vid.v; break; + case mtpc_inputStickerSetShortName: setName = qs(sticker->sticker()->set.c_inputStickerSetShortName().vshort_name).toLower().trimmed(); break; } StickerSets &sets(cRefStickerSets()); for (StickerSets::const_iterator i = sets.cbegin(); i != sets.cend(); ++i) { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index acb09a70cc..30c14ce49d 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -386,10 +386,11 @@ public slots: void audioLoadProgress(mtpFileLoader *loader); void audioLoadFailed(mtpFileLoader *loader, bool started); void audioLoadRetry(); - void audioPlayProgress(AudioData *audio); + void audioPlayProgress(const AudioMsgId &audioId); void documentLoadProgress(mtpFileLoader *loader); void documentLoadFailed(mtpFileLoader *loader, bool started); void documentLoadRetry(); + void documentPlayProgress(const SongMsgId &songId); void setInnerFocus(); void dialogsCancelled(); diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 7f1d02ab5e..56441c375d 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -800,9 +800,9 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { _caption = Text(); QString already = _doc->already(true); - if (_doc->sticker && !_doc->sticker->img->isNull() && _doc->sticker->img->loaded()) { + if (_doc->sticker() && !_doc->sticker()->img->isNull() && _doc->sticker()->img->loaded()) { _currentGif.stop(); - _current = _doc->sticker->img->pix(); + _current = _doc->sticker()->img->pix(); } else if (!already.isEmpty()) { QImageReader reader(already); if (reader.canRead()) { @@ -1010,7 +1010,7 @@ void MediaView::paintEvent(QPaintEvent *e) { QRect imgRect(_x, _y, _w, _h); const QPixmap *toDraw = _currentGif.isNull() ? &_current : &_currentGif.current(_currentGif.w, _currentGif.h, false); if (imgRect.intersects(r)) { - if (toDraw->hasAlpha() && (!_doc || !_doc->sticker || _doc->sticker->img->isNull())) { + if (toDraw->hasAlpha() && (!_doc || !_doc->sticker() || _doc->sticker()->img->isNull())) { p.fillRect(imgRect, _transparentBrush); } if (_zoom) { @@ -1415,7 +1415,7 @@ void MediaView::preloadData(int32 delta) { switch (media->type()) { case MediaTypePhoto: static_cast(media)->photo()->full->load(); break; case MediaTypeDocument: static_cast(media)->document()->thumb->load(); break; - case MediaTypeSticker: static_cast(media)->document()->sticker->img->load(); break; + case MediaTypeSticker: static_cast(media)->document()->sticker()->img->load(); break; } } } diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index 74814cbfa7..3634fb63c7 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -289,7 +289,7 @@ RecentStickerPack &cGetRecentStickers() { recent.reserve(p.size()); for (RecentStickerPreload::const_iterator i = p.cbegin(), e = p.cend(); i != e; ++i) { DocumentData *doc = App::document(i->first); - if (!doc || !doc->sticker) continue; + if (!doc || !doc->sticker()) continue; recent.push_back(qMakePair(doc, i->second)); } diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index fd4a9865f3..522c712e7a 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -442,16 +442,16 @@ void AudioOpenLink::onClick(Qt::MouseButton button) const { if ((!data->user && !data->date) || button != Qt::LeftButton) return; QString already = data->already(true); - bool play = audioPlayer(); + bool play = App::hoveredLinkItem() && audioPlayer(); if (!already.isEmpty() || (!data->data.isEmpty() && play)) { if (play) { - AudioData *playing = 0; + AudioMsgId playing; AudioPlayerState playingState = AudioPlayerStopped; audioPlayer()->currentState(&playing, &playingState); - if (playing == data && playingState != AudioPlayerStopped) { - audioPlayer()->pauseresume(); + if (playing.msgId == App::hoveredLinkItem()->id && playingState != AudioPlayerStopped) { + audioPlayer()->pauseresume(OverviewAudios); } else { - audioPlayer()->play(data); + audioPlayer()->play(AudioMsgId(data, App::hoveredLinkItem()->id)); if (App::main()) App::main()->audioMarkRead(data); } } else { @@ -549,9 +549,19 @@ void DocumentOpenLink::onClick(Qt::MouseButton button) const { DocumentData *data = document(); if (!data->date || button != Qt::LeftButton) return; + bool play = data->song() && App::hoveredLinkItem() && audioPlayer(); QString already = data->already(true); - if (!already.isEmpty()) { - if (data->size < MediaViewImageSizeLimit) { + if (!already.isEmpty() || (!data->data.isEmpty() && play)) { + if (play) { + SongMsgId playing; + AudioPlayerState playingState = AudioPlayerStopped; + audioPlayer()->currentState(&playing, &playingState); + if (playing.msgId == App::hoveredLinkItem()->id && playingState != AudioPlayerStopped) { + audioPlayer()->pauseresume(OverviewDocuments); + } else { + audioPlayer()->play(SongMsgId(data, App::hoveredLinkItem()->id)); + } + } else if (data->size < MediaViewImageSizeLimit) { QImageReader reader(already); if (reader.canRead()) { if (reader.supportsAnimation() && reader.imageCount() > 1 && App::hoveredLinkItem()) { @@ -646,7 +656,7 @@ void DocumentCancelLink::onClick(Qt::MouseButton button) const { } DocumentData::DocumentData(const DocumentId &id, const uint64 &access, int32 date, const QVector &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size) : -id(id), type(FileDocument), duration(0), access(access), date(date), mime(mime), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), openOnSaveMsgId(0), loader(0), sticker(0) { +id(id), type(FileDocument), access(access), date(date), mime(mime), thumb(thumb), dc(dc), size(size), status(FileReady), uploadOffset(0), openOnSave(0), openOnSaveMsgId(0), loader(0), _additional(0) { setattributes(attributes); location = Local::readFileLocation(mediaKey(DocumentFileLocation, dc, id)); } @@ -658,12 +668,17 @@ void DocumentData::setattributes(const QVector &attributes const MTPDdocumentAttributeImageSize &d(attributes[i].c_documentAttributeImageSize()); dimensions = QSize(d.vw.v, d.vh.v); } break; - case mtpc_documentAttributeAnimated: if (type == FileDocument || type == StickerDocument) type = AnimatedDocument; break; + case mtpc_documentAttributeAnimated: if (type == FileDocument || type == StickerDocument) { + type = AnimatedDocument; + delete _additional; + _additional = 0; + } break; case mtpc_documentAttributeSticker: { const MTPDdocumentAttributeSticker &d(attributes[i].c_documentAttributeSticker()); - if (type == FileDocument) type = StickerDocument; - if (type == StickerDocument && !sticker) sticker = new StickerData(); - if (sticker) { + if (type == FileDocument) { + type = StickerDocument; + StickerData *sticker = new StickerData(); + _additional = sticker; sticker->alt = qs(d.valt); sticker->set = d.vstickerset; } @@ -671,17 +686,28 @@ void DocumentData::setattributes(const QVector &attributes case mtpc_documentAttributeVideo: { const MTPDdocumentAttributeVideo &d(attributes[i].c_documentAttributeVideo()); type = VideoDocument; - duration = d.vduration.v; +// duration = d.vduration.v; dimensions = QSize(d.vw.v, d.vh.v); } break; case mtpc_documentAttributeAudio: { const MTPDdocumentAttributeAudio &d(attributes[i].c_documentAttributeAudio()); - type = AudioDocument; - duration = d.vduration.v; + type = SongDocument; + SongData *song = new SongData(); + _additional = song; + song->duration = d.vduration.v; + song->title = qs(d.vtitle); + song->performer = qs(d.vperformer); } break; case mtpc_documentAttributeFilename: name = qs(attributes[i].c_documentAttributeFilename().vfile_name); break; } } + if (type == StickerDocument) { + if (dimensions.width() <= 0 || dimensions.height() <= 0 || dimensions.width() > StickerMaxSize || dimensions.height() > StickerMaxSize || size > StickerInMemory) { + type = FileDocument; + delete _additional; + _additional = 0; + } + } } void DocumentData::save(const QString &toFile) { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 15d7987966..d73c90d224 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -413,6 +413,27 @@ struct AudioData { int32 md5[8]; }; +struct AudioMsgId { + AudioMsgId() : audio(0), msgId(0) { + } + AudioMsgId(AudioData *audio, MsgId msgId) : audio(audio), msgId(msgId) { + } + operator bool() const { + return audio; + } + AudioData *audio; + MsgId msgId; +}; +inline bool operator<(const AudioMsgId &a, const AudioMsgId &b) { + return quintptr(a.audio) < quintptr(b.audio) || (quintptr(a.audio) == quintptr(b.audio) && a.msgId < b.msgId); +} +inline bool operator==(const AudioMsgId &a, const AudioMsgId &b) { + return a.audio == b.audio && a.msgId == b.msgId; +} +inline bool operator!=(const AudioMsgId &a, const AudioMsgId &b) { + return !(a == b); +} + class AudioLink : public ITextLink { TEXT_LINK_CLASS(AudioLink) @@ -455,7 +476,18 @@ public: void onClick(Qt::MouseButton button) const; }; -struct StickerData { +enum DocumentType { + FileDocument = 0, + VideoDocument = 1, + SongDocument = 2, + StickerDocument = 3, + AnimatedDocument = 4, +}; + +struct DocumentAdditionalData { +}; + +struct StickerData : public DocumentAdditionalData { StickerData() : set(MTP_inputStickerSetEmpty()) { } ImagePtr img; @@ -467,20 +499,20 @@ struct StickerData { StorageImageLocation loc; // doc thumb location }; -enum DocumentType { - FileDocument = 0, - VideoDocument = 1, - AudioDocument = 2, - StickerDocument = 3, - AnimatedDocument = 4, +struct SongData : public DocumentAdditionalData { + SongData() : duration(0) { + } + int32 duration; + QString title, performer; }; + struct DocumentData { DocumentData(const DocumentId &id, const uint64 &access = 0, int32 date = 0, const QVector &attributes = QVector(), const QString &mime = QString(), const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0); void setattributes(const QVector &attributes); void forget() { thumb->forget(); - if (sticker) sticker->img->forget(); + if (sticker()) sticker()->img->forget(); replyPreview->forget(); } @@ -510,15 +542,20 @@ struct DocumentData { loader = 0; } ~DocumentData() { - delete sticker; + delete _additional; } QString already(bool check = false); + StickerData *sticker() { + return (type == StickerDocument) ? static_cast(_additional) : 0; + } + SongData *song() { + return (type == SongDocument) ? static_cast(_additional) : 0; + } DocumentId id; DocumentType type; QSize dimensions; - int32 duration; uint64 access; int32 date; QString name, mime; @@ -534,11 +571,32 @@ struct DocumentData { FileLocation location; QByteArray data; - StickerData *sticker; + DocumentAdditionalData *_additional; int32 md5[8]; }; +struct SongMsgId { + SongMsgId() : song(0), msgId(0) { + } + SongMsgId(DocumentData *song, MsgId msgId) : song(song), msgId(msgId) { + } + operator bool() const { + return song; + } + DocumentData *song; + MsgId msgId; +}; +inline bool operator<(const SongMsgId &a, const SongMsgId &b) { + return quintptr(a.song) < quintptr(b.song) || (quintptr(a.song) == quintptr(b.song) && a.msgId < b.msgId); +} +inline bool operator==(const SongMsgId &a, const SongMsgId &b) { + return a.song == b.song && a.msgId == b.msgId; +} +inline bool operator!=(const SongMsgId &a, const SongMsgId &b) { + return !(a == b); +} + class DocumentLink : public ITextLink { TEXT_LINK_CLASS(DocumentLink)