mirror of https://github.com/procxx/kepka.git
Play notification sound using Media::Audio::Track.
This commit is contained in:
parent
6f89d01452
commit
2e816f2a67
|
@ -2432,12 +2432,6 @@ namespace {
|
|||
return i.value();
|
||||
}
|
||||
|
||||
void playSound() {
|
||||
if (Global::SoundNotify() && !Platform::Notifications::SkipAudio()) {
|
||||
Media::Audio::PlayNotify();
|
||||
}
|
||||
}
|
||||
|
||||
void checkImageCacheSize() {
|
||||
int64 nowImageCacheSize = imageCacheSize();
|
||||
if (nowImageCacheSize > serviceImageCacheSize + MemoryForImageCache) {
|
||||
|
|
|
@ -221,7 +221,6 @@ namespace App {
|
|||
|
||||
void initMedia();
|
||||
void deinitMedia();
|
||||
void playSound();
|
||||
|
||||
void checkImageCacheSize();
|
||||
|
||||
|
|
|
@ -1535,7 +1535,7 @@ bool HistoryDocument::updateStatusText() const {
|
|||
bool was = (voice->_playback != nullptr);
|
||||
voice->ensurePlayback(this);
|
||||
if (!was || state.position != voice->_playback->_position) {
|
||||
float64 prg = state.duration ? snap(float64(state.position) / state.duration, 0., 1.) : 0.;
|
||||
auto prg = state.length ? snap(float64(state.position) / state.length, 0., 1.) : 0.;
|
||||
if (voice->_playback->_position < state.position) {
|
||||
voice->_playback->a_progress.start(prg);
|
||||
} else {
|
||||
|
@ -1544,11 +1544,11 @@ bool HistoryDocument::updateStatusText() const {
|
|||
voice->_playback->_position = state.position;
|
||||
voice->_playback->_a_progress.start();
|
||||
}
|
||||
voice->_lastDurationMs = static_cast<int>((state.duration * 1000LL) / state.frequency); // Bad :(
|
||||
voice->_lastDurationMs = static_cast<int>((state.length * 1000LL) / state.frequency); // Bad :(
|
||||
}
|
||||
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
realDuration = (state.duration / state.frequency);
|
||||
realDuration = (state.length / state.frequency);
|
||||
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
} else {
|
||||
if (auto voice = Get<HistoryDocumentVoice>()) {
|
||||
|
@ -1562,7 +1562,7 @@ bool HistoryDocument::updateStatusText() const {
|
|||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
realDuration = (state.duration / state.frequency);
|
||||
realDuration = (state.length / state.frequency);
|
||||
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
} else {
|
||||
}
|
||||
|
@ -1605,9 +1605,9 @@ void HistoryDocument::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool
|
|||
} else if (!pressed && voice->seeking()) {
|
||||
auto type = AudioMsgId::Type::Voice;
|
||||
auto state = Media::Player::mixer()->currentState(type);
|
||||
if (state.id == AudioMsgId(_data, _parent->fullId()) && state.duration) {
|
||||
if (state.id == AudioMsgId(_data, _parent->fullId()) && state.length) {
|
||||
auto currentProgress = voice->seekingCurrent();
|
||||
auto currentPosition = qRound(currentProgress * state.duration);
|
||||
auto currentPosition = qRound(currentProgress * state.length);
|
||||
Media::Player::mixer()->seek(type, currentPosition);
|
||||
|
||||
voice->ensurePlayback(this);
|
||||
|
|
|
@ -838,7 +838,7 @@ bool File::updateStatusText() const {
|
|||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
|
||||
if (state.id == AudioMsgId(document, FullMsgId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
realDuration = (state.duration / state.frequency);
|
||||
realDuration = (state.length / state.frequency);
|
||||
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
}
|
||||
} else if (document->song()) {
|
||||
|
@ -846,7 +846,7 @@ bool File::updateStatusText() const {
|
|||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id == AudioMsgId(document, FullMsgId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
realDuration = (state.duration / state.frequency);
|
||||
realDuration = (state.length / state.frequency);
|
||||
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
}
|
||||
if (!showPause && (state.id == AudioMsgId(document, FullMsgId())) && Media::Player::instance()->isSeeking(AudioMsgId::Type::Song)) {
|
||||
|
|
|
@ -154,138 +154,11 @@ bool CreatePlaybackDevice() {
|
|||
return true;
|
||||
}
|
||||
|
||||
struct NotifySound {
|
||||
QByteArray data;
|
||||
TimeMs lengthMs = 0;
|
||||
int sampleRate = 0;
|
||||
|
||||
ALenum alFormat = 0;
|
||||
|
||||
ALuint source = 0;
|
||||
ALuint buffer = 0;
|
||||
};
|
||||
NotifySound DefaultNotify;
|
||||
|
||||
// Thread: Main. Must be locked: AudioMutex.
|
||||
void PrepareNotifySound() {
|
||||
auto content = ([] {
|
||||
QFile soundFile(":/gui/art/newmsg.wav");
|
||||
soundFile.open(QIODevice::ReadOnly);
|
||||
return soundFile.readAll();
|
||||
})();
|
||||
auto data = content.constData();
|
||||
auto size = content.size();
|
||||
t_assert(size >= 44);
|
||||
|
||||
t_assert(*((const uint32*)(data + 0)) == 0x46464952); // ChunkID - "RIFF"
|
||||
t_assert(*((const uint32*)(data + 4)) == uint32(size - 8)); // ChunkSize
|
||||
t_assert(*((const uint32*)(data + 8)) == 0x45564157); // Format - "WAVE"
|
||||
t_assert(*((const uint32*)(data + 12)) == 0x20746d66); // Subchunk1ID - "fmt "
|
||||
auto subchunk1Size = *((const uint32*)(data + 16));
|
||||
auto extra = subchunk1Size - 16;
|
||||
t_assert(subchunk1Size >= 16 && (!extra || extra >= 2));
|
||||
t_assert(*((const uint16*)(data + 20)) == 1); // AudioFormat - PCM (1)
|
||||
|
||||
auto numChannels = *((const uint16*)(data + 22));
|
||||
t_assert(numChannels == 1 || numChannels == 2);
|
||||
|
||||
auto sampleRate = *((const uint32*)(data + 24));
|
||||
auto byteRate = *((const uint32*)(data + 28));
|
||||
|
||||
auto blockAlign = *((const uint16*)(data + 32));
|
||||
auto bitsPerSample = *((const uint16*)(data + 34));
|
||||
t_assert(!(bitsPerSample % 8));
|
||||
|
||||
auto bytesPerSample = bitsPerSample / 8;
|
||||
t_assert(bytesPerSample == 1 || bytesPerSample == 2);
|
||||
|
||||
t_assert(blockAlign == numChannels * bytesPerSample);
|
||||
t_assert(byteRate == sampleRate * blockAlign);
|
||||
|
||||
if (extra) {
|
||||
auto extraSize = *((const uint16*)(data + 36));
|
||||
t_assert(uint32(extraSize + 2) == extra);
|
||||
t_assert(uint32(size) >= 44 + extra);
|
||||
}
|
||||
|
||||
t_assert(*((const uint32*)(data + extra + 36)) == 0x61746164); // Subchunk2ID - "data"
|
||||
auto subchunk2Size = *((const uint32*)(data + extra + 40));
|
||||
|
||||
t_assert(!(subchunk2Size % (numChannels * bytesPerSample)));
|
||||
auto numSamples = subchunk2Size / (numChannels * bytesPerSample);
|
||||
|
||||
t_assert(uint32(size) >= 44 + extra + subchunk2Size);
|
||||
data += 44 + extra;
|
||||
|
||||
auto format = ALenum(0);
|
||||
switch (bytesPerSample) {
|
||||
case 1:
|
||||
switch (numChannels) {
|
||||
case 1: format = AL_FORMAT_MONO8; break;
|
||||
case 2: format = AL_FORMAT_STEREO8; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
switch (numChannels) {
|
||||
case 1: format = AL_FORMAT_MONO16; break;
|
||||
case 2: format = AL_FORMAT_STEREO16; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
t_assert(format != 0);
|
||||
|
||||
DefaultNotify.alFormat = format;
|
||||
DefaultNotify.sampleRate = sampleRate;
|
||||
auto addBytes = (sampleRate * 15 / 100) * bytesPerSample * numChannels; // add 150ms of silence
|
||||
DefaultNotify.data = QByteArray(addBytes + subchunk2Size, (bytesPerSample == 1) ? 128 : 0);
|
||||
memcpy(DefaultNotify.data.data() + addBytes, data, subchunk2Size);
|
||||
DefaultNotify.lengthMs = (numSamples * 1000LL / sampleRate);
|
||||
}
|
||||
|
||||
ALuint CreateSource() {
|
||||
auto source = ALuint(0);
|
||||
alGenSources(1, &source);
|
||||
alSourcef(source, AL_PITCH, 1.f);
|
||||
alSourcef(source, AL_GAIN, 1.f);
|
||||
alSource3f(source, AL_POSITION, 0, 0, 0);
|
||||
alSource3f(source, AL_VELOCITY, 0, 0, 0);
|
||||
alSourcei(source, AL_LOOPING, 0);
|
||||
return source;
|
||||
}
|
||||
|
||||
ALuint CreateBuffer() {
|
||||
auto buffer = ALuint(0);
|
||||
alGenBuffers(1, &buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void CreateDefaultNotify() {
|
||||
if (alIsSource(DefaultNotify.source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DefaultNotify.source = CreateSource();
|
||||
DefaultNotify.buffer = CreateBuffer();
|
||||
|
||||
alBufferData(DefaultNotify.buffer, DefaultNotify.alFormat, DefaultNotify.data.constData(), DefaultNotify.data.size(), DefaultNotify.sampleRate);
|
||||
alSourcei(DefaultNotify.source, AL_BUFFER, DefaultNotify.buffer);
|
||||
}
|
||||
|
||||
// Thread: Main. Must be locked: AudioMutex.
|
||||
void ClosePlaybackDevice() {
|
||||
if (!AudioDevice) return;
|
||||
|
||||
LOG(("Audio Info: Closing audio playback device."));
|
||||
if (alIsSource(DefaultNotify.source)) {
|
||||
alSourceStop(DefaultNotify.source);
|
||||
alSourcei(DefaultNotify.source, AL_BUFFER, AL_NONE);
|
||||
alDeleteBuffers(1, &DefaultNotify.buffer);
|
||||
alDeleteSources(1, &DefaultNotify.source);
|
||||
}
|
||||
DefaultNotify.buffer = 0;
|
||||
DefaultNotify.source = 0;
|
||||
|
||||
if (Player::mixer()) {
|
||||
Player::mixer()->detachTracks();
|
||||
}
|
||||
|
@ -303,9 +176,6 @@ void Start() {
|
|||
qRegisterMetaType<AudioMsgId>();
|
||||
qRegisterMetaType<VoiceWaveform>();
|
||||
|
||||
// No sync required yet.
|
||||
PrepareNotifySound();
|
||||
|
||||
auto loglevel = getenv("ALSOFT_LOGLEVEL");
|
||||
LOG(("OpenAL Logging Level: %1").arg(loglevel ? loglevel : "(not set)"));
|
||||
|
||||
|
@ -374,38 +244,6 @@ void StopDetachIfNotUsedSafe() {
|
|||
});
|
||||
}
|
||||
|
||||
// Thread: Main. Locks: AudioMutex.
|
||||
void PlayNotify() {
|
||||
QMutexLocker lock(&AudioMutex);
|
||||
auto m = Player::mixer();
|
||||
if (!m) return;
|
||||
|
||||
AttachToDevice();
|
||||
if (!AudioDevice) return;
|
||||
|
||||
CreateDefaultNotify();
|
||||
alSourcePlay(DefaultNotify.source);
|
||||
if (PlaybackErrorHappened()) {
|
||||
ClosePlaybackDevice();
|
||||
return;
|
||||
}
|
||||
|
||||
emit m->suppressAll();
|
||||
emit m->faderOnTimer();
|
||||
}
|
||||
|
||||
// Thread: Any. Must be locked: AudioMutex.
|
||||
bool NotifyIsPlaying() {
|
||||
if (alIsSource(DefaultNotify.source)) {
|
||||
ALint state = AL_INITIAL;
|
||||
alGetSourcei(DefaultNotify.source, AL_SOURCE_STATE, &state);
|
||||
if (!PlaybackErrorHappened() && state == AL_PLAYING) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Audio
|
||||
|
||||
namespace Player {
|
||||
|
@ -595,7 +433,7 @@ Mixer::Mixer()
|
|||
connect(this, SIGNAL(faderOnTimer()), _fader, SLOT(onTimer()), Qt::QueuedConnection);
|
||||
connect(this, SIGNAL(suppressSong()), _fader, SLOT(onSuppressSong()));
|
||||
connect(this, SIGNAL(unsuppressSong()), _fader, SLOT(onUnsuppressSong()));
|
||||
connect(this, SIGNAL(suppressAll()), _fader, SLOT(onSuppressAll()));
|
||||
connect(this, SIGNAL(suppressAll(qint64)), _fader, SLOT(onSuppressAll(qint64)));
|
||||
subscribe(Global::RefSongVolumeChanged(), [this] {
|
||||
QMetaObject::invokeMethod(_fader, "onSongVolumeChanged");
|
||||
});
|
||||
|
@ -1010,7 +848,7 @@ void Mixer::videoSoundProgress(const AudioMsgId &audio) {
|
|||
auto current = trackForType(type);
|
||||
t_assert(current != nullptr);
|
||||
|
||||
if (current->videoPlayId == _lastVideoPlayId && current->state.duration && current->state.frequency) {
|
||||
if (current->videoPlayId == _lastVideoPlayId && current->state.length && current->state.frequency) {
|
||||
if (current->state.state == State::Playing) {
|
||||
_lastVideoPlaybackWhen = getms();
|
||||
_lastVideoPlaybackCorrectedMs = (current->state.position * 1000ULL) / current->state.frequency;
|
||||
|
@ -1296,14 +1134,13 @@ void Fader::onTimer() {
|
|||
auto ms = getms();
|
||||
auto wasSong = suppressSongGain;
|
||||
if (_suppressAll) {
|
||||
auto notifyLengthMs = Audio::DefaultNotify.lengthMs;
|
||||
auto wasAudio = suppressAllGain;
|
||||
if (ms >= _suppressAllStart + notifyLengthMs || ms < _suppressAllStart) {
|
||||
if (ms >= _suppressAllEnd || ms < _suppressAllStart) {
|
||||
_suppressAll = _suppressAllAnim = false;
|
||||
_suppressAllGain = anim::value(1., 1.);
|
||||
} else if (ms > _suppressAllStart + notifyLengthMs - kFadeDuration) {
|
||||
} else if (ms > _suppressAllEnd - kFadeDuration) {
|
||||
if (_suppressAllGain.to() != 1.) _suppressAllGain.start(1.);
|
||||
_suppressAllGain.update(1. - ((_suppressAllStart + notifyLengthMs - ms) / float64(kFadeDuration)), anim::linear);
|
||||
_suppressAllGain.update(1. - ((_suppressAllEnd - ms) / float64(kFadeDuration)), anim::linear);
|
||||
} else if (ms >= _suppressAllStart + st::mediaPlayerSuppressDuration) {
|
||||
if (_suppressAllAnim) {
|
||||
_suppressAllGain.finish();
|
||||
|
@ -1351,9 +1188,6 @@ void Fader::onTimer() {
|
|||
|
||||
_songVolumeChanged = _videoVolumeChanged = false;
|
||||
|
||||
if (!hasFading && !hasPlaying && Audio::NotifyIsPlaying()) {
|
||||
hasPlaying = true;
|
||||
}
|
||||
if (hasFading) {
|
||||
_timer.start(kCheckFadingTimeout);
|
||||
Audio::StopDetachIfNotUsedSafe();
|
||||
|
@ -1361,7 +1195,6 @@ void Fader::onTimer() {
|
|||
_timer.start(kCheckPlaybackPositionTimeout);
|
||||
Audio::StopDetachIfNotUsedSafe();
|
||||
} else {
|
||||
LOG(("SCHEDULE DETACHED"));
|
||||
Audio::ScheduleDetachIfNotUsedSafe();
|
||||
}
|
||||
}
|
||||
|
@ -1510,9 +1343,13 @@ void Fader::onUnsuppressSong() {
|
|||
}
|
||||
}
|
||||
|
||||
void Fader::onSuppressAll() {
|
||||
void Fader::onSuppressAll(qint64 duration) {
|
||||
_suppressAll = true;
|
||||
_suppressAllStart = getms();
|
||||
auto now = getms();
|
||||
if (_suppressAllEnd < now + kFadeDuration) {
|
||||
_suppressAllStart = now;
|
||||
}
|
||||
_suppressAllEnd = now + duration;
|
||||
_suppressAllGain.start(st::suppressAll);
|
||||
onTimer();
|
||||
}
|
||||
|
@ -1683,8 +1520,8 @@ FileLoadTask::Song PrepareForSending(const QString &fname, const QByteArray &dat
|
|||
auto result = FileLoadTask::Song();
|
||||
FFMpegAttributesReader reader(FileLocation(fname), data);
|
||||
qint64 position = 0;
|
||||
if (reader.open(position) && reader.duration() > 0) {
|
||||
result.duration = reader.duration() / reader.frequency();
|
||||
if (reader.open(position) && reader.samplesCount() > 0) {
|
||||
result.duration = reader.samplesCount() / reader.samplesFrequency();
|
||||
result.title = reader.title();
|
||||
result.performer = reader.performer();
|
||||
result.cover = reader.cover();
|
||||
|
@ -1707,8 +1544,8 @@ public:
|
|||
|
||||
QByteArray buffer;
|
||||
buffer.reserve(AudioVoiceMsgBufferSize);
|
||||
int64 countbytes = sampleSize * duration(), processed = 0, sumbytes = 0;
|
||||
if (duration() < Media::Player::kWaveformSamplesCount) {
|
||||
int64 countbytes = sampleSize * samplesCount(), processed = 0, sumbytes = 0;
|
||||
if (samplesCount() < Media::Player::kWaveformSamplesCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,9 +43,6 @@ void ScheduleDetachFromDeviceSafe();
|
|||
void ScheduleDetachIfNotUsedSafe();
|
||||
void StopDetachIfNotUsedSafe();
|
||||
|
||||
// Thread: Main.
|
||||
void PlayNotify();
|
||||
|
||||
} // namespace Audio
|
||||
|
||||
namespace Player {
|
||||
|
@ -103,7 +100,7 @@ struct TrackState {
|
|||
AudioMsgId id;
|
||||
State state = State::Stopped;
|
||||
int64 position = 0;
|
||||
TimeMs duration = 0;
|
||||
int64 length = 0;
|
||||
int frequency = kDefaultFrequency;
|
||||
};
|
||||
|
||||
|
@ -164,7 +161,7 @@ signals:
|
|||
|
||||
void suppressSong();
|
||||
void unsuppressSong();
|
||||
void suppressAll();
|
||||
void suppressAll(qint64 duration);
|
||||
|
||||
private:
|
||||
bool fadedStop(AudioMsgId::Type type, bool *fadedStart = 0);
|
||||
|
@ -269,7 +266,7 @@ public slots:
|
|||
|
||||
void onSuppressSong();
|
||||
void onUnsuppressSong();
|
||||
void onSuppressAll();
|
||||
void onSuppressAll(qint64 duration);
|
||||
void onSongVolumeChanged();
|
||||
void onVideoVolumeChanged();
|
||||
|
||||
|
@ -293,6 +290,7 @@ private:
|
|||
bool _videoVolumeChanged = false;
|
||||
anim::value _suppressAllGain, _suppressSongGain;
|
||||
TimeMs _suppressAllStart = 0;
|
||||
TimeMs _suppressAllEnd = 0;
|
||||
TimeMs _suppressSongStart = 0;
|
||||
|
||||
};
|
||||
|
|
|
@ -66,11 +66,11 @@ bool AbstractFFMpegLoader::open(qint64 &position) {
|
|||
return false;
|
||||
}
|
||||
|
||||
freq = fmtContext->streams[streamId]->codecpar->sample_rate;
|
||||
_samplesFrequency = fmtContext->streams[streamId]->codecpar->sample_rate;
|
||||
if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) {
|
||||
len = (fmtContext->duration * freq) / AV_TIME_BASE;
|
||||
_samplesCount = (fmtContext->duration * _samplesFrequency) / AV_TIME_BASE;
|
||||
} else {
|
||||
len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den;
|
||||
_samplesCount = (fmtContext->streams[streamId]->duration * _samplesFrequency * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -241,7 +241,7 @@ bool FFMpegLoader::open(qint64 &position) {
|
|||
sampleSize = -1; // convert needed
|
||||
break;
|
||||
}
|
||||
if (freq != 44100 && freq != 48000) {
|
||||
if (_samplesFrequency != 44100 && _samplesFrequency != 48000) {
|
||||
sampleSize = -1; // convert needed
|
||||
}
|
||||
|
||||
|
@ -252,9 +252,9 @@ bool FFMpegLoader::open(qint64 &position) {
|
|||
return false;
|
||||
}
|
||||
int64_t src_ch_layout = layout, dst_ch_layout = AudioToChannelLayout;
|
||||
srcRate = freq;
|
||||
srcRate = _samplesFrequency;
|
||||
AVSampleFormat src_sample_fmt = inputFormat, dst_sample_fmt = AudioToFormat;
|
||||
dstRate = (freq != 44100 && freq != 48000) ? Media::Player::kDefaultFrequency : freq;
|
||||
dstRate = (_samplesFrequency != 44100 && _samplesFrequency != 48000) ? Media::Player::kDefaultFrequency : _samplesFrequency;
|
||||
|
||||
av_opt_set_int(swrContext, "in_channel_layout", src_ch_layout, 0);
|
||||
av_opt_set_int(swrContext, "in_sample_rate", srcRate, 0);
|
||||
|
@ -269,8 +269,8 @@ bool FFMpegLoader::open(qint64 &position) {
|
|||
}
|
||||
|
||||
sampleSize = AudioToChannels * sizeof(short);
|
||||
freq = dstRate;
|
||||
len = av_rescale_rnd(len, dstRate, srcRate, AV_ROUND_UP);
|
||||
_samplesFrequency = dstRate;
|
||||
_samplesCount = av_rescale_rnd(_samplesCount, dstRate, srcRate, AV_ROUND_UP);
|
||||
position = av_rescale_rnd(position, dstRate, srcRate, AV_ROUND_DOWN);
|
||||
fmt = AL_FORMAT_STEREO16;
|
||||
|
||||
|
@ -281,7 +281,7 @@ bool FFMpegLoader::open(qint64 &position) {
|
|||
}
|
||||
}
|
||||
if (position) {
|
||||
int64 ts = (position * fmtContext->streams[streamId]->time_base.den) / (freq * fmtContext->streams[streamId]->time_base.num);
|
||||
int64 ts = (position * fmtContext->streams[streamId]->time_base.den) / (_samplesFrequency * fmtContext->streams[streamId]->time_base.num);
|
||||
if (av_seek_frame(fmtContext, streamId, ts, AVSEEK_FLAG_ANY) < 0) {
|
||||
if (av_seek_frame(fmtContext, streamId, ts, 0) < 0) {
|
||||
}
|
||||
|
|
|
@ -39,19 +39,19 @@ public:
|
|||
|
||||
bool open(qint64 &position) override;
|
||||
|
||||
TimeMs duration() override {
|
||||
return len;
|
||||
int64 samplesCount() override {
|
||||
return _samplesCount;
|
||||
}
|
||||
|
||||
int32 frequency() override {
|
||||
return freq;
|
||||
int32 samplesFrequency() override {
|
||||
return _samplesFrequency;
|
||||
}
|
||||
|
||||
~AbstractFFMpegLoader();
|
||||
|
||||
protected:
|
||||
int32 freq = Media::Player::kDefaultFrequency;
|
||||
TimeMs len = 0;
|
||||
int32 _samplesFrequency = Media::Player::kDefaultFrequency;
|
||||
int64 _samplesCount = 0;
|
||||
|
||||
uchar *ioBuffer = nullptr;
|
||||
AVIOContext *ioContext = nullptr;
|
||||
|
|
|
@ -28,8 +28,8 @@ public:
|
|||
virtual bool check(const FileLocation &file, const QByteArray &data);
|
||||
|
||||
virtual bool open(qint64 &position) = 0;
|
||||
virtual TimeMs duration() = 0;
|
||||
virtual int32 frequency() = 0;
|
||||
virtual int64 samplesCount() = 0;
|
||||
virtual int32 samplesFrequency() = 0;
|
||||
virtual int32 format() = 0;
|
||||
|
||||
enum class ReadResult {
|
||||
|
|
|
@ -209,7 +209,7 @@ void Loaders::loadData(AudioMsgId audio, qint64 position) {
|
|||
track->fadeStartPosition = position;
|
||||
|
||||
track->format = l->format();
|
||||
track->frequency = l->frequency();
|
||||
track->frequency = l->samplesFrequency();
|
||||
}
|
||||
if (samplesCount) {
|
||||
track->ensureStreamCreated();
|
||||
|
@ -248,7 +248,7 @@ void Loaders::loadData(AudioMsgId audio, qint64 position) {
|
|||
|
||||
if (finished) {
|
||||
track->loaded = true;
|
||||
track->state.duration = track->bufferedPosition + track->bufferedLength;
|
||||
track->state.length = track->bufferedPosition + track->bufferedLength;
|
||||
clear(type);
|
||||
}
|
||||
|
||||
|
@ -337,13 +337,13 @@ AudioPlayerLoader *Loaders::setupLoader(const AudioMsgId &audio, SetupError &err
|
|||
track->state.state = State::StoppedAtStart;
|
||||
return nullptr;
|
||||
}
|
||||
int64 duration = l->duration();
|
||||
if (duration <= 0) {
|
||||
auto length = l->samplesCount();
|
||||
if (length <= 0) {
|
||||
track->state.state = State::StoppedAtStart;
|
||||
return nullptr;
|
||||
}
|
||||
track->state.duration = duration;
|
||||
track->state.frequency = l->frequency();
|
||||
track->state.length = length;
|
||||
track->state.frequency = l->samplesFrequency();
|
||||
if (!track->state.frequency) track->state.frequency = kDefaultFrequency;
|
||||
err = SetupNoErrorStarted;
|
||||
} else if (track->loaded) {
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace {
|
|||
|
||||
constexpr auto kMaxFileSize = 10 * 1024 * 1024;
|
||||
constexpr auto kDetachDeviceTimeout = TimeMs(500); // destroy the audio device after 500ms of silence
|
||||
constexpr auto kTrackUpdateTimeout = TimeMs(100);
|
||||
|
||||
ALuint CreateSource() {
|
||||
auto source = ALuint(0);
|
||||
|
@ -90,8 +91,8 @@ void Track::fillFromData(base::byte_vector &&data) {
|
|||
} while (true);
|
||||
|
||||
_alFormat = loader.format();
|
||||
_lengthMs = loader.duration();
|
||||
_sampleRate = loader.frequency();
|
||||
_sampleRate = loader.samplesFrequency();
|
||||
_lengthMs = (loader.samplesCount() * TimeMs(1000)) / _sampleRate;
|
||||
}
|
||||
|
||||
void Track::fillFromFile(const FileLocation &location) {
|
||||
|
@ -126,34 +127,29 @@ void Track::fillFromFile(const QString &filePath) {
|
|||
}
|
||||
}
|
||||
|
||||
void Track::playOnce() {
|
||||
void Track::playWithLooping(bool looping) {
|
||||
_active = true;
|
||||
if (failed() || _samples.empty()) {
|
||||
_instance->trackFinished().notify(this, true);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
createSource();
|
||||
ensureSourceCreated();
|
||||
alSourceStop(_alSource);
|
||||
_looping = false;
|
||||
alSourcei(_alSource, AL_LOOPING, 0);
|
||||
_active = true;
|
||||
_looping = looping;
|
||||
alSourcei(_alSource, AL_LOOPING, _looping ? 1 : 0);
|
||||
alSourcePlay(_alSource);
|
||||
emit Media::Player::mixer()->faderOnTimer();
|
||||
_instance->trackStarted(this);
|
||||
}
|
||||
|
||||
void Track::playInLoop() {
|
||||
if (failed()) {
|
||||
return;
|
||||
void Track::finish() {
|
||||
if (_active) {
|
||||
_active = false;
|
||||
_instance->trackFinished(this);
|
||||
}
|
||||
createSource();
|
||||
alSourceStop(_alSource);
|
||||
_looping = true;
|
||||
alSourcei(_alSource, AL_LOOPING, 1);
|
||||
_active = true;
|
||||
alSourcePlay(_alSource);
|
||||
emit Media::Player::mixer()->faderOnTimer();
|
||||
_alPosition = 0;
|
||||
}
|
||||
|
||||
void Track::createSource() {
|
||||
void Track::ensureSourceCreated() {
|
||||
if (alIsSource(_alSource)) {
|
||||
return;
|
||||
}
|
||||
|
@ -181,13 +177,7 @@ void Track::updateState() {
|
|||
auto state = ALint(0);
|
||||
alGetSourcei(_alSource, AL_SOURCE_STATE, &state);
|
||||
if (state != AL_PLAYING) {
|
||||
_alPosition = 0;
|
||||
if (_active) {
|
||||
_active = false;
|
||||
if (!_looping) {
|
||||
_instance->trackFinished().notify(this, true);
|
||||
}
|
||||
}
|
||||
finish();
|
||||
} else {
|
||||
auto currentPosition = ALint(0);
|
||||
alGetSourcei(_alSource, AL_SAMPLE_OFFSET, ¤tPosition);
|
||||
|
@ -211,17 +201,13 @@ void Track::reattachToDevice() {
|
|||
if (!isActive() || alIsSource(_alSource)) {
|
||||
return;
|
||||
}
|
||||
createSource();
|
||||
ensureSourceCreated();
|
||||
|
||||
alSourcei(_alSource, AL_LOOPING, _looping ? 1 : 0);
|
||||
alSourcei(_alSource, AL_SAMPLE_OFFSET, static_cast<ALint>(_alPosition));
|
||||
alSourcePlay(_alSource);
|
||||
}
|
||||
|
||||
bool Track::isActive() const {
|
||||
return _active;
|
||||
}
|
||||
|
||||
Track::~Track() {
|
||||
detachFromDevice();
|
||||
_instance->unregisterTrack(this);
|
||||
|
@ -240,7 +226,6 @@ Instance::Instance() {
|
|||
Audio::StopDetachIfNotUsedSafe();
|
||||
}
|
||||
});
|
||||
_updateTimer.callEach(100);
|
||||
|
||||
_detachFromDeviceTimer.setCallback([this] {
|
||||
_detachFromDeviceForce = false;
|
||||
|
@ -264,6 +249,23 @@ void Instance::unregisterTrack(Track *track) {
|
|||
_tracks.erase(track);
|
||||
}
|
||||
|
||||
void Instance::trackStarted(Track *track) {
|
||||
stopDetachIfNotUsed();
|
||||
if (!_updateTimer.isActive()) {
|
||||
_updateTimer.callEach(kTrackUpdateTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::trackFinished(Track *track) {
|
||||
if (!hasActiveTracks()) {
|
||||
_updateTimer.cancel();
|
||||
scheduleDetachIfNotUsed();
|
||||
}
|
||||
if (track->isLooping()) {
|
||||
trackFinished().notify(track, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::detachTracks() {
|
||||
for (auto track : _tracks) {
|
||||
track->detachFromDevice();
|
||||
|
|
|
@ -35,22 +35,37 @@ public:
|
|||
void fillFromFile(const FileLocation &location);
|
||||
void fillFromFile(const QString &filePath);
|
||||
|
||||
void playOnce();
|
||||
void playInLoop();
|
||||
void playOnce() {
|
||||
playWithLooping(false);
|
||||
}
|
||||
void playInLoop() {
|
||||
playWithLooping(true);
|
||||
}
|
||||
|
||||
bool isLooping() const {
|
||||
return _looping;
|
||||
}
|
||||
bool isActive() const {
|
||||
return _active;
|
||||
}
|
||||
bool failed() const {
|
||||
return _failed;
|
||||
}
|
||||
|
||||
int64 getLengthMs() const {
|
||||
return _lengthMs;
|
||||
}
|
||||
|
||||
void detachFromDevice();
|
||||
void reattachToDevice();
|
||||
bool isActive() const;
|
||||
void updateState();
|
||||
|
||||
~Track();
|
||||
|
||||
private:
|
||||
void createSource();
|
||||
void finish();
|
||||
void ensureSourceCreated();
|
||||
void playWithLooping(bool looping);
|
||||
|
||||
gsl::not_null<Instance*> _instance;
|
||||
|
||||
|
@ -97,6 +112,8 @@ private:
|
|||
friend class Track;
|
||||
void registerTrack(Track *track);
|
||||
void unregisterTrack(Track *track);
|
||||
void trackStarted(Track *track);
|
||||
void trackFinished(Track *track);
|
||||
|
||||
private:
|
||||
std::set<Track*> _tracks;
|
||||
|
|
|
@ -94,11 +94,11 @@ public:
|
|||
return _format;
|
||||
}
|
||||
|
||||
TimeMs duration() override {
|
||||
int64 samplesCount() override {
|
||||
return _parentData->length;
|
||||
}
|
||||
|
||||
int32 frequency() override {
|
||||
int32 samplesFrequency() override {
|
||||
return _parentData->frequency;
|
||||
}
|
||||
|
||||
|
|
|
@ -152,8 +152,8 @@ void CoverWidget::handleSeekFinished(float64 progress) {
|
|||
|
||||
auto type = AudioMsgId::Type::Song;
|
||||
auto state = Media::Player::mixer()->currentState(type);
|
||||
if (state.id && state.duration) {
|
||||
Media::Player::mixer()->seek(type, qRound(progress * state.duration));
|
||||
if (state.id && state.length) {
|
||||
Media::Player::mixer()->seek(type, qRound(progress * state.length));
|
||||
}
|
||||
|
||||
instance()->stopSeeking(type);
|
||||
|
@ -256,16 +256,16 @@ void CoverWidget::handleSongUpdate(const TrackState &state) {
|
|||
|
||||
void CoverWidget::updateTimeText(const TrackState &state) {
|
||||
QString time;
|
||||
qint64 position = 0, duration = 0, display = 0;
|
||||
qint64 position = 0, length = 0, display = 0;
|
||||
auto frequency = state.frequency;
|
||||
if (!IsStopped(state.state) && state.state != State::Finishing) {
|
||||
display = position = state.position;
|
||||
duration = state.duration;
|
||||
length = state.length;
|
||||
} else {
|
||||
display = state.duration ? state.duration : (state.id.audio()->song()->duration * frequency);
|
||||
length = state.length ? state.length : (state.id.audio()->song()->duration * frequency);
|
||||
}
|
||||
|
||||
_lastDurationMs = (state.duration * 1000LL) / frequency;
|
||||
_lastDurationMs = (state.length * 1000LL) / frequency;
|
||||
|
||||
if (state.id.audio()->loading()) {
|
||||
_time = QString::number(qRound(state.id.audio()->progress() * 100)) + '%';
|
||||
|
|
|
@ -206,8 +206,8 @@ void Widget::handleSeekFinished(float64 progress) {
|
|||
|
||||
auto type = AudioMsgId::Type::Song;
|
||||
auto state = mixer()->currentState(type);
|
||||
if (state.id && state.duration) {
|
||||
mixer()->seek(type, qRound(progress * state.duration));
|
||||
if (state.id && state.length) {
|
||||
mixer()->seek(type, qRound(progress * state.length));
|
||||
}
|
||||
|
||||
instance()->stopSeeking(AudioMsgId::Type::Song);
|
||||
|
@ -329,18 +329,18 @@ void Widget::handleSongUpdate(const TrackState &state) {
|
|||
|
||||
void Widget::updateTimeText(const TrackState &state) {
|
||||
QString time;
|
||||
qint64 position = 0, duration = 0, display = 0;
|
||||
qint64 position = 0, length = 0, display = 0;
|
||||
auto frequency = state.frequency;
|
||||
if (!IsStopped(state.state) && state.state != State::Finishing) {
|
||||
display = position = state.position;
|
||||
duration = state.duration;
|
||||
} else if (state.duration) {
|
||||
display = state.duration;
|
||||
length = state.length;
|
||||
} else if (state.length) {
|
||||
display = state.length;
|
||||
} else if (state.id.audio()->song()) {
|
||||
display = (state.id.audio()->song()->duration * frequency);
|
||||
}
|
||||
|
||||
_lastDurationMs = (state.duration * 1000LL) / frequency;
|
||||
_lastDurationMs = (state.length * 1000LL) / frequency;
|
||||
|
||||
if (state.id.audio()->loading()) {
|
||||
_time = QString::number(qRound(state.id.audio()->progress() * 100)) + '%';
|
||||
|
|
|
@ -122,20 +122,20 @@ void Controller::updatePlayPauseResumeState(const Player::TrackState &state) {
|
|||
}
|
||||
|
||||
void Controller::updateTimeTexts(const Player::TrackState &state) {
|
||||
qint64 position = 0, duration = state.duration;
|
||||
qint64 position = 0, length = state.length;
|
||||
|
||||
if (!Player::IsStopped(state.state) && state.state != Player::State::Finishing) {
|
||||
position = state.position;
|
||||
} else if (state.state == Player::State::StoppedAtEnd) {
|
||||
position = state.duration;
|
||||
position = state.length;
|
||||
} else {
|
||||
position = 0;
|
||||
}
|
||||
auto playFrequency = state.frequency;
|
||||
auto playAlready = position / playFrequency;
|
||||
auto playLeft = (state.duration / playFrequency) - playAlready;
|
||||
auto playLeft = (state.length / playFrequency) - playAlready;
|
||||
|
||||
_lastDurationMs = (state.duration * 1000LL) / playFrequency;
|
||||
_lastDurationMs = (state.length * 1000LL) / playFrequency;
|
||||
|
||||
_timeAlready = formatDurationText(playAlready);
|
||||
auto minus = QChar(8722);
|
||||
|
|
|
@ -30,7 +30,7 @@ Playback::Playback(Ui::ContinuousSlider *slider) : _slider(slider) {
|
|||
}
|
||||
|
||||
void Playback::updateState(const Player::TrackState &state) {
|
||||
qint64 position = 0, duration = state.duration;
|
||||
qint64 position = 0, length = state.length;
|
||||
|
||||
auto wasDisabled = _slider->isDisabled();
|
||||
if (wasDisabled) setDisabled(false);
|
||||
|
@ -39,22 +39,22 @@ void Playback::updateState(const Player::TrackState &state) {
|
|||
if (_playing || state.state == Player::State::Stopped) {
|
||||
position = state.position;
|
||||
} else if (state.state == Player::State::StoppedAtEnd) {
|
||||
position = state.duration;
|
||||
position = state.length;
|
||||
} else {
|
||||
position = 0;
|
||||
}
|
||||
|
||||
float64 progress = 0.;
|
||||
if (position > duration) {
|
||||
if (position > length) {
|
||||
progress = 1.;
|
||||
} else if (duration) {
|
||||
progress = duration ? snap(float64(position) / duration, 0., 1.) : 0.;
|
||||
} else if (length) {
|
||||
progress = length ? snap(float64(position) / length, 0., 1.) : 0.;
|
||||
}
|
||||
if (duration != _duration || position != _position || wasDisabled) {
|
||||
auto animated = (duration && _duration && progress > _slider->value());
|
||||
if (length != _length || position != _position || wasDisabled) {
|
||||
auto animated = (length && _length&& progress > _slider->value());
|
||||
_slider->setValue(progress, animated);
|
||||
_position = position;
|
||||
_duration = duration;
|
||||
_length = length;
|
||||
}
|
||||
_slider->update();
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ private:
|
|||
Ui::ContinuousSlider *_slider;
|
||||
|
||||
int64 _position = 0;
|
||||
TimeMs _duration = 0;
|
||||
int64 _length = 0;
|
||||
|
||||
bool _playing = false;
|
||||
|
||||
|
|
|
@ -1562,7 +1562,7 @@ void MediaView::restartVideoAtSeekPosition(TimeMs positionMs) {
|
|||
Media::Player::TrackState state;
|
||||
state.state = Media::Player::State::Playing;
|
||||
state.position = _videoPositionMs;
|
||||
state.duration = _videoDurationMs;
|
||||
state.length = _videoDurationMs;
|
||||
state.frequency = _videoFrequencyMs;
|
||||
updateVideoPlaybackState(state);
|
||||
}
|
||||
|
@ -1606,7 +1606,7 @@ void MediaView::onVideoPlayProgress(const AudioMsgId &audioId) {
|
|||
}
|
||||
|
||||
auto state = Media::Player::mixer()->currentVideoState(_gif->playId());
|
||||
if (state.duration) {
|
||||
if (state.length) {
|
||||
updateVideoPlaybackState(state);
|
||||
}
|
||||
|
||||
|
@ -1635,7 +1635,7 @@ void MediaView::updateSilentVideoPlaybackState() {
|
|||
state.state = Media::Player::State::Playing;
|
||||
}
|
||||
state.position = _videoPositionMs;
|
||||
state.duration = _videoDurationMs;
|
||||
state.length = _videoDurationMs;
|
||||
state.frequency = _videoFrequencyMs;
|
||||
updateVideoPlaybackState(state);
|
||||
}
|
||||
|
|
|
@ -668,7 +668,7 @@ bool Voice::updateStatusText() {
|
|||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
|
||||
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
realDuration = (state.duration / state.frequency);
|
||||
realDuration = (state.length / state.frequency);
|
||||
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
}
|
||||
} else {
|
||||
|
@ -952,7 +952,7 @@ bool Document::updateStatusText() {
|
|||
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
|
||||
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
|
||||
statusSize = -1 - (state.position / state.frequency);
|
||||
realDuration = (state.duration / state.frequency);
|
||||
realDuration = (state.length / state.frequency);
|
||||
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
|
||||
}
|
||||
if (!showPause && (state.id == AudioMsgId(_data, _parent->fullId())) && Media::Player::instance()->isSeeking(AudioMsgId::Type::Song)) {
|
||||
|
|
|
@ -22,6 +22,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "platform/platform_notifications_manager.h"
|
||||
#include "window/notifications_manager_default.h"
|
||||
#include "media/media_audio_track.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "lang.h"
|
||||
#include "mainwindow.h"
|
||||
#include "mainwidget.h"
|
||||
|
@ -235,7 +237,12 @@ void System::showNext() {
|
|||
}
|
||||
if (alert) {
|
||||
Platform::Notifications::FlashBounce();
|
||||
App::playSound();
|
||||
if (Global::SoundNotify() && !Platform::Notifications::SkipAudio()) {
|
||||
ensureSoundCreated();
|
||||
_soundTrack->playOnce();
|
||||
emit Media::Player::mixer()->suppressAll(_soundTrack->getLengthMs());
|
||||
emit Media::Player::mixer()->faderOnTimer();
|
||||
}
|
||||
}
|
||||
|
||||
if (_waiters.isEmpty() || !Global::DesktopNotify() || Platform::Notifications::SkipToast()) {
|
||||
|
@ -349,6 +356,15 @@ void System::showNext() {
|
|||
}
|
||||
}
|
||||
|
||||
void System::ensureSoundCreated() {
|
||||
if (_soundTrack) {
|
||||
return;
|
||||
}
|
||||
|
||||
_soundTrack = Media::Audio::Current().createTrack();
|
||||
_soundTrack->fillFromFile(qsl(":/gui/art/newmsg.wav"));
|
||||
}
|
||||
|
||||
void System::updateAll() {
|
||||
_manager->updateAll();
|
||||
}
|
||||
|
@ -415,5 +431,7 @@ void NativeManager::doShowNotification(HistoryItem *item, int forwardedCount) {
|
|||
doShowNativeNotification(item->history()->peer, item->id, title, subtitle, text, options.hideNameAndPhoto, options.hideReplyButton);
|
||||
}
|
||||
|
||||
System::~System() = default;
|
||||
|
||||
} // namespace Notifications
|
||||
} // namespace Window
|
||||
|
|
|
@ -28,6 +28,12 @@ class Manager;
|
|||
} // namespace Notifications
|
||||
} // namespace Platform
|
||||
|
||||
namespace Media {
|
||||
namespace Audio {
|
||||
class Track;
|
||||
} // namespace Audio
|
||||
} // namespace Media
|
||||
|
||||
namespace Window {
|
||||
namespace Notifications {
|
||||
|
||||
|
@ -79,8 +85,11 @@ public:
|
|||
return _authSession;
|
||||
}
|
||||
|
||||
~System();
|
||||
|
||||
private:
|
||||
void showNext();
|
||||
void ensureSoundCreated();
|
||||
|
||||
AuthSession *_authSession = nullptr;
|
||||
|
||||
|
@ -107,6 +116,8 @@ private:
|
|||
|
||||
base::Observable<ChangeType> _settingsChanged;
|
||||
|
||||
std::unique_ptr<Media::Audio::Track> _soundTrack;
|
||||
|
||||
};
|
||||
|
||||
class Manager {
|
||||
|
|
Loading…
Reference in New Issue