mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Use native sound support in macOS notifications.
This commit is contained in:
parent
07fd9b3074
commit
d135151477
15 changed files with 689 additions and 351 deletions
|
@ -1135,6 +1135,8 @@ PRIVATE
|
||||||
media/audio/media_audio_loader.h
|
media/audio/media_audio_loader.h
|
||||||
media/audio/media_audio_loaders.cpp
|
media/audio/media_audio_loaders.cpp
|
||||||
media/audio/media_audio_loaders.h
|
media/audio/media_audio_loaders.h
|
||||||
|
media/audio/media_audio_local_cache.cpp
|
||||||
|
media/audio/media_audio_local_cache.h
|
||||||
media/audio/media_audio_track.cpp
|
media/audio/media_audio_track.cpp
|
||||||
media/audio/media_audio_track.h
|
media/audio/media_audio_track.h
|
||||||
media/audio/media_child_ffmpeg_loader.cpp
|
media/audio/media_child_ffmpeg_loader.cpp
|
||||||
|
|
97
Telegram/SourceFiles/ffmpeg/ffmpeg_bytes_io_wrap.h
Normal file
97
Telegram/SourceFiles/ffmpeg/ffmpeg_bytes_io_wrap.h
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ffmpeg/ffmpeg_utility.h"
|
||||||
|
|
||||||
|
namespace FFmpeg {
|
||||||
|
|
||||||
|
struct ReadBytesWrap {
|
||||||
|
int64 size = 0;
|
||||||
|
int64 offset = 0;
|
||||||
|
const uchar *data = nullptr;
|
||||||
|
|
||||||
|
static int Read(void *opaque, uint8_t *buf, int buf_size) {
|
||||||
|
auto wrap = static_cast<ReadBytesWrap*>(opaque);
|
||||||
|
const auto toRead = std::min(
|
||||||
|
int64(buf_size),
|
||||||
|
wrap->size - wrap->offset);
|
||||||
|
if (toRead > 0) {
|
||||||
|
memcpy(buf, wrap->data + wrap->offset, toRead);
|
||||||
|
wrap->offset += toRead;
|
||||||
|
}
|
||||||
|
return toRead;
|
||||||
|
}
|
||||||
|
static int64_t Seek(void *opaque, int64_t offset, int whence) {
|
||||||
|
auto wrap = static_cast<ReadBytesWrap*>(opaque);
|
||||||
|
auto updated = int64(-1);
|
||||||
|
switch (whence) {
|
||||||
|
case SEEK_SET: updated = offset; break;
|
||||||
|
case SEEK_CUR: updated = wrap->offset + offset; break;
|
||||||
|
case SEEK_END: updated = wrap->size + offset; break;
|
||||||
|
case AVSEEK_SIZE: return wrap->size; break;
|
||||||
|
}
|
||||||
|
if (updated < 0 || updated > wrap->size) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
wrap->offset = updated;
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WriteBytesWrap {
|
||||||
|
QByteArray content;
|
||||||
|
int64 offset = 0;
|
||||||
|
|
||||||
|
#if DA_FFMPEG_CONST_WRITE_CALLBACK
|
||||||
|
static int Write(void *opaque, const uint8_t *_buf, int buf_size) {
|
||||||
|
uint8_t *buf = const_cast<uint8_t *>(_buf);
|
||||||
|
#else
|
||||||
|
static int Write(void *opaque, uint8_t *buf, int buf_size) {
|
||||||
|
#endif
|
||||||
|
auto wrap = static_cast<WriteBytesWrap*>(opaque);
|
||||||
|
if (const auto total = wrap->offset + int64(buf_size)) {
|
||||||
|
const auto size = int64(wrap->content.size());
|
||||||
|
constexpr auto kReserve = 1024 * 1024;
|
||||||
|
wrap->content.reserve((total / kReserve) * kReserve);
|
||||||
|
const auto overwrite = std::min(
|
||||||
|
size - wrap->offset,
|
||||||
|
int64(buf_size));
|
||||||
|
if (overwrite) {
|
||||||
|
memcpy(wrap->content.data() + wrap->offset, buf, overwrite);
|
||||||
|
}
|
||||||
|
if (const auto append = buf_size - overwrite) {
|
||||||
|
wrap->content.append(
|
||||||
|
reinterpret_cast<const char*>(buf) + overwrite,
|
||||||
|
append);
|
||||||
|
}
|
||||||
|
wrap->offset += buf_size;
|
||||||
|
}
|
||||||
|
return buf_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t Seek(void *opaque, int64_t offset, int whence) {
|
||||||
|
auto wrap = static_cast<WriteBytesWrap*>(opaque);
|
||||||
|
const auto &content = wrap->content;
|
||||||
|
const auto checkedSeek = [&](int64_t offset) {
|
||||||
|
if (offset < 0 || offset > int64(content.size())) {
|
||||||
|
return int64_t(-1);
|
||||||
|
}
|
||||||
|
return int64_t(wrap->offset = offset);
|
||||||
|
};
|
||||||
|
switch (whence) {
|
||||||
|
case SEEK_SET: return checkedSeek(offset);
|
||||||
|
case SEEK_CUR: return checkedSeek(wrap->offset + offset);
|
||||||
|
case SEEK_END: return checkedSeek(int64(content.size()) + offset);
|
||||||
|
case AVSEEK_SIZE: return int64(content.size());
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace FFmpeg
|
|
@ -1115,7 +1115,7 @@ void DraftOptionsBox(
|
||||||
? tr::lng_settings_save()
|
? tr::lng_settings_save()
|
||||||
: tr::lng_reply_quote_selected();
|
: tr::lng_reply_quote_selected();
|
||||||
}) | rpl::flatten_latest();
|
}) | rpl::flatten_latest();
|
||||||
box->addButton(std::move(save), [=] {
|
const auto submit = [=] {
|
||||||
if (state->quote.current().overflown) {
|
if (state->quote.current().overflown) {
|
||||||
show->showToast({
|
show->showToast({
|
||||||
.title = tr::lng_reply_quote_long_title(tr::now),
|
.title = tr::lng_reply_quote_long_title(tr::now),
|
||||||
|
@ -1125,12 +1125,22 @@ void DraftOptionsBox(
|
||||||
const auto options = state->forward.options;
|
const auto options = state->forward.options;
|
||||||
finish(resolveReply(), state->webpage, options);
|
finish(resolveReply(), state->webpage, options);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
box->addButton(std::move(save), submit);
|
||||||
|
|
||||||
box->addButton(tr::lng_cancel(), [=] {
|
box->addButton(tr::lng_cancel(), [=] {
|
||||||
box->closeBox();
|
box->closeBox();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
box->events() | rpl::start_with_next([=](not_null<QEvent*> e) {
|
||||||
|
if (e->type() == QEvent::KeyPress) {
|
||||||
|
const auto key = static_cast<QKeyEvent*>(e.get())->key();
|
||||||
|
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||||
|
submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, box->lifetime());
|
||||||
|
|
||||||
args.show->session().data().itemRemoved(
|
args.show->session().data().itemRemoved(
|
||||||
) | rpl::start_with_next([=](not_null<const HistoryItem*> removed) {
|
) | rpl::start_with_next([=](not_null<const HistoryItem*> removed) {
|
||||||
const auto inReply = (state->quote.current().item == removed);
|
const auto inReply = (state->quote.current().item == removed);
|
||||||
|
|
343
Telegram/SourceFiles/media/audio/media_audio_local_cache.cpp
Normal file
343
Telegram/SourceFiles/media/audio/media_audio_local_cache.cpp
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "media/audio/media_audio_local_cache.h"
|
||||||
|
|
||||||
|
#include "ffmpeg/ffmpeg_bytes_io_wrap.h"
|
||||||
|
#include "ffmpeg/ffmpeg_utility.h"
|
||||||
|
|
||||||
|
namespace Media::Audio {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kMaxDuration = 10 * crl::time(1000);
|
||||||
|
constexpr auto kMaxStreams = 2;
|
||||||
|
constexpr auto kFrameSize = 4096;
|
||||||
|
|
||||||
|
[[nodiscard]] QByteArray ConvertAndCut(const QByteArray &bytes) {
|
||||||
|
using namespace FFmpeg;
|
||||||
|
|
||||||
|
auto wrap = ReadBytesWrap{
|
||||||
|
.size = bytes.size(),
|
||||||
|
.data = reinterpret_cast<const uchar*>(bytes.constData()),
|
||||||
|
};
|
||||||
|
|
||||||
|
auto input = MakeFormatPointer(
|
||||||
|
&wrap,
|
||||||
|
&ReadBytesWrap::Read,
|
||||||
|
nullptr,
|
||||||
|
&ReadBytesWrap::Seek);
|
||||||
|
if (!input) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto error = AvErrorWrap(avformat_find_stream_info(input.get(), 0));
|
||||||
|
if (error) {
|
||||||
|
LogError(u"avformat_find_stream_info"_q, error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto inCodec = (const AVCodec*)nullptr;
|
||||||
|
const auto streamId = av_find_best_stream(
|
||||||
|
input.get(),
|
||||||
|
AVMEDIA_TYPE_AUDIO,
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
&inCodec,
|
||||||
|
0);
|
||||||
|
if (streamId < 0) {
|
||||||
|
LogError(u"av_find_best_stream"_q, AvErrorWrap(streamId));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto inStream = input->streams[streamId];
|
||||||
|
auto inCodecPar = inStream->codecpar;
|
||||||
|
auto inCodecContext = CodecPointer(avcodec_alloc_context3(nullptr));
|
||||||
|
if (!inCodecContext) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avcodec_parameters_to_context(inCodecContext.get(), inCodecPar) < 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avcodec_open2(inCodecContext.get(), inCodec, nullptr) < 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = WriteBytesWrap();
|
||||||
|
auto outFormat = MakeWriteFormatPointer(
|
||||||
|
static_cast<void*>(&result),
|
||||||
|
nullptr,
|
||||||
|
&WriteBytesWrap::Write,
|
||||||
|
&WriteBytesWrap::Seek,
|
||||||
|
"wav"_q);
|
||||||
|
if (!outFormat) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find and open output codec
|
||||||
|
auto outCodec = avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE);
|
||||||
|
if (!outCodec) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto outStream = avformat_new_stream(outFormat.get(), outCodec);
|
||||||
|
if (!outStream) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto outCodecContext = CodecPointer(
|
||||||
|
avcodec_alloc_context3(outCodec));
|
||||||
|
if (!outCodecContext) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DA_FFMPEG_NEW_CHANNEL_LAYOUT
|
||||||
|
auto mono = AVChannelLayout(AV_CHANNEL_LAYOUT_MONO);
|
||||||
|
auto stereo = AVChannelLayout(AV_CHANNEL_LAYOUT_STEREO);
|
||||||
|
const auto in = &inCodecContext->ch_layout;
|
||||||
|
if (!av_channel_layout_compare(in, &mono)
|
||||||
|
|| !av_channel_layout_compare(in, &stereo)) {
|
||||||
|
av_channel_layout_copy(&outCodecContext->ch_layout, in);
|
||||||
|
} else {
|
||||||
|
outCodecContext->ch_layout = AV_CHANNEL_LAYOUT_STEREO;
|
||||||
|
}
|
||||||
|
#else // DA_FFMPEG_NEW_CHANNEL_LAYOUT
|
||||||
|
const auto in = inCodecContext->channels;
|
||||||
|
if (in == 1 || in == 2) {
|
||||||
|
outCodecContext->channels = in;
|
||||||
|
outCodecContext->channel_layout = inCodecContext->channel_layout;
|
||||||
|
} else {
|
||||||
|
outCodecContext->channels = 2;
|
||||||
|
outCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;
|
||||||
|
}
|
||||||
|
#endif // DA_FFMPEG_NEW_CHANNEL_LAYOUT
|
||||||
|
const auto inrate = inCodecContext->sample_rate;
|
||||||
|
const auto rate = (inrate == 44'100 || inrate == 48'000)
|
||||||
|
? inrate
|
||||||
|
: 44'100;
|
||||||
|
outCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;
|
||||||
|
outCodecContext->time_base = AVRational{ 1, rate };
|
||||||
|
outCodecContext->bit_rate = 64 * 1024;
|
||||||
|
outCodecContext->sample_rate = rate;
|
||||||
|
|
||||||
|
error = avcodec_open2(outCodecContext.get(), outCodec, nullptr);
|
||||||
|
if (error) {
|
||||||
|
LogError("avcodec_open2", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
error = avcodec_parameters_from_context(
|
||||||
|
outStream->codecpar,
|
||||||
|
outCodecContext.get());
|
||||||
|
if (error) {
|
||||||
|
LogError("avcodec_parameters_from_context", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
error = avformat_write_header(outFormat.get(), nullptr);
|
||||||
|
if (error) {
|
||||||
|
LogError("avformat_write_header", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto swrContext = MakeSwresamplePointer(
|
||||||
|
#if DA_FFMPEG_NEW_CHANNEL_LAYOUT
|
||||||
|
&inCodecContext->ch_layout,
|
||||||
|
inCodecContext->sample_fmt,
|
||||||
|
inCodecContext->sample_rate,
|
||||||
|
&outCodecContext->ch_layout,
|
||||||
|
#else // DA_FFMPEG_NEW_CHANNEL_LAYOUT
|
||||||
|
&inCodecContext->channel_layout,
|
||||||
|
inCodecContext->sample_fmt,
|
||||||
|
inCodecContext->sample_rate,
|
||||||
|
&outCodecContext->channel_layout,
|
||||||
|
#endif // DA_FFMPEG_NEW_CHANNEL_LAYOUT
|
||||||
|
outCodecContext->sample_fmt,
|
||||||
|
outCodecContext->sample_rate);
|
||||||
|
if (!swrContext) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto packet = av_packet_alloc();
|
||||||
|
const auto guard = gsl::finally([&] {
|
||||||
|
av_packet_free(&packet);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto frame = MakeFramePointer();
|
||||||
|
if (!frame) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto outFrame = MakeFramePointer();
|
||||||
|
if (!outFrame) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
outFrame->nb_samples = kFrameSize;
|
||||||
|
outFrame->format = outCodecContext->sample_fmt;
|
||||||
|
#if DA_FFMPEG_NEW_CHANNEL_LAYOUT
|
||||||
|
av_channel_layout_copy(
|
||||||
|
&outFrame->ch_layout,
|
||||||
|
&outCodecContext->ch_layout);
|
||||||
|
#else // DA_FFMPEG_NEW_CHANNEL_LAYOUT
|
||||||
|
outFrame->channel_layout = outCodecContext->channel_layout;
|
||||||
|
outFrame->channels = outCodecContext->channels;
|
||||||
|
#endif // DA_FFMPEG_NEW_CHANNEL_LAYOUT
|
||||||
|
outFrame->sample_rate = outCodecContext->sample_rate;
|
||||||
|
|
||||||
|
error = av_frame_get_buffer(outFrame.get(), 0);
|
||||||
|
if (error) {
|
||||||
|
LogError("av_frame_get_buffer", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pts = int64_t(0);
|
||||||
|
auto maxPts = int64_t(kMaxDuration) * rate / 1000;
|
||||||
|
const auto writeFrame = [&](AVFrame *frame) { // nullptr to flush
|
||||||
|
error = avcodec_send_frame(outCodecContext.get(), frame);
|
||||||
|
if (error) {
|
||||||
|
LogError("avcodec_send_frame", error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
auto pkt = av_packet_alloc();
|
||||||
|
const auto guard = gsl::finally([&] {
|
||||||
|
av_packet_free(&pkt);
|
||||||
|
});
|
||||||
|
while (true) {
|
||||||
|
error = avcodec_receive_packet(outCodecContext.get(), pkt);
|
||||||
|
if (error) {
|
||||||
|
if (error.code() != AVERROR(EAGAIN)
|
||||||
|
&& error.code() != AVERROR_EOF) {
|
||||||
|
LogError("avcodec_receive_packet", error);
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
pkt->stream_index = outStream->index;
|
||||||
|
av_packet_rescale_ts(
|
||||||
|
pkt,
|
||||||
|
outCodecContext->time_base,
|
||||||
|
outStream->time_base);
|
||||||
|
error = av_interleaved_write_frame(outFormat.get(), pkt);
|
||||||
|
if (error) {
|
||||||
|
LogError("av_interleaved_write_frame", error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
while (pts < maxPts) {
|
||||||
|
error = av_read_frame(input.get(), packet);
|
||||||
|
const auto finished = (error.code() == AVERROR_EOF);
|
||||||
|
if (!finished) {
|
||||||
|
if (error) {
|
||||||
|
LogError("av_read_frame", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto guard = gsl::finally([&] {
|
||||||
|
av_packet_unref(packet);
|
||||||
|
});
|
||||||
|
if (packet->stream_index != streamId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
error = avcodec_send_packet(inCodecContext.get(), packet);
|
||||||
|
if (error) {
|
||||||
|
LogError("avcodec_send_packet", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
error = avcodec_receive_frame(inCodecContext.get(), frame.get());
|
||||||
|
if (error) {
|
||||||
|
if (error.code() == AVERROR(EAGAIN)
|
||||||
|
|| error.code() == AVERROR_EOF) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
LogError("avcodec_receive_frame", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error = swr_convert(
|
||||||
|
swrContext.get(),
|
||||||
|
outFrame->data,
|
||||||
|
kFrameSize,
|
||||||
|
(const uint8_t**)frame->data,
|
||||||
|
frame->nb_samples);
|
||||||
|
if (error) {
|
||||||
|
LogError("swr_convert", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto samples = error.code();
|
||||||
|
if (!samples) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
outFrame->nb_samples = samples;
|
||||||
|
outFrame->pts = pts;
|
||||||
|
pts += samples;
|
||||||
|
if (pts > maxPts) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = writeFrame(outFrame.get());
|
||||||
|
if (error && error.code() != AVERROR(EAGAIN)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finished) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error = writeFrame(nullptr);
|
||||||
|
if (error && error.code() != AVERROR_EOF) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
error = av_write_trailer(outFormat.get());
|
||||||
|
if (error) {
|
||||||
|
LogError("av_write_trailer", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return result.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
LocalCache::~LocalCache() {
|
||||||
|
for (const auto &[id, path] : _cache) {
|
||||||
|
QFile::remove(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LocalCache::path(
|
||||||
|
DocumentId id,
|
||||||
|
Fn<QByteArray()> resolveBytes,
|
||||||
|
Fn<QByteArray()> fallbackBytes) {
|
||||||
|
auto &result = _cache[id];
|
||||||
|
if (!result.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const auto bytes = resolveBytes();
|
||||||
|
if (bytes.isEmpty()) {
|
||||||
|
return fallbackBytes ? path(0, fallbackBytes, nullptr) : QString();
|
||||||
|
}
|
||||||
|
const auto prefix = cWorkingDir() + u"tdata/audio_cache"_q;
|
||||||
|
QDir().mkpath(prefix);
|
||||||
|
|
||||||
|
const auto name = QString::number(id, 16).toUpper();
|
||||||
|
result = u"%1/%2.wav"_q.arg(prefix, name);
|
||||||
|
auto file = QFile(result);
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
|
return fallbackBytes ? path(0, fallbackBytes, nullptr) : QString();
|
||||||
|
}
|
||||||
|
file.write(ConvertAndCut(bytes));
|
||||||
|
file.close();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Media::Audio
|
27
Telegram/SourceFiles/media/audio/media_audio_local_cache.h
Normal file
27
Telegram/SourceFiles/media/audio/media_audio_local_cache.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Media::Audio {
|
||||||
|
|
||||||
|
class LocalCache final {
|
||||||
|
public:
|
||||||
|
LocalCache() = default;
|
||||||
|
~LocalCache();
|
||||||
|
|
||||||
|
[[nodiscard]] QString path(
|
||||||
|
DocumentId id,
|
||||||
|
Fn<QByteArray()> resolveBytes,
|
||||||
|
Fn<QByteArray()> fallbackBytes);
|
||||||
|
|
||||||
|
private:
|
||||||
|
base::flat_map<DocumentId, QString> _cache;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Media::Audio
|
|
@ -167,17 +167,14 @@ GLib::Variant AnyVectorToVariant(const std::vector<std::any> &value) {
|
||||||
class NotificationData final : public base::has_weak_ptr {
|
class NotificationData final : public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
using NotificationId = Window::Notifications::Manager::NotificationId;
|
using NotificationId = Window::Notifications::Manager::NotificationId;
|
||||||
|
using Info = Window::Notifications::NativeManager::NotificationInfo;
|
||||||
|
|
||||||
NotificationData(
|
NotificationData(
|
||||||
not_null<Manager*> manager,
|
not_null<Manager*> manager,
|
||||||
XdgNotifications::NotificationsProxy proxy,
|
XdgNotifications::NotificationsProxy proxy,
|
||||||
NotificationId id);
|
NotificationId id);
|
||||||
|
|
||||||
[[nodiscard]] bool init(
|
[[nodiscard]] bool init(const Info &info);
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
Window::Notifications::Manager::DisplayOptions options);
|
|
||||||
|
|
||||||
NotificationData(const NotificationData &other) = delete;
|
NotificationData(const NotificationData &other) = delete;
|
||||||
NotificationData &operator=(const NotificationData &other) = delete;
|
NotificationData &operator=(const NotificationData &other) = delete;
|
||||||
|
@ -232,18 +229,17 @@ NotificationData::NotificationData(
|
||||||
, _imageKey(GetImageKey()) {
|
, _imageKey(GetImageKey()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NotificationData::init(
|
bool NotificationData::init(const Info &info) {
|
||||||
const QString &title,
|
const auto &title = info.title;
|
||||||
const QString &subtitle,
|
const auto &subtitle = info.subtitle;
|
||||||
const QString &msg,
|
//const auto sound = info.soundPath ? info.soundPath() : QString();
|
||||||
Window::Notifications::Manager::DisplayOptions options) {
|
|
||||||
if (_application) {
|
if (_application) {
|
||||||
_notification = Gio::Notification::new_(
|
_notification = Gio::Notification::new_(
|
||||||
subtitle.isEmpty()
|
subtitle.isEmpty()
|
||||||
? title.toStdString()
|
? title.toStdString()
|
||||||
: subtitle.toStdString() + " (" + title.toStdString() + ')');
|
: subtitle.toStdString() + " (" + title.toStdString() + ')');
|
||||||
|
|
||||||
_notification.set_body(msg.toStdString());
|
_notification.set_body(info.message.toStdString());
|
||||||
|
|
||||||
_notification.set_icon(
|
_notification.set_icon(
|
||||||
Gio::ThemedIcon::new_(base::IconName().toStdString()));
|
Gio::ThemedIcon::new_(base::IconName().toStdString()));
|
||||||
|
@ -270,7 +266,7 @@ bool NotificationData::init(
|
||||||
"app.notification-activate",
|
"app.notification-activate",
|
||||||
idVariant);
|
idVariant);
|
||||||
|
|
||||||
if (!options.hideMarkAsRead) {
|
if (!info.options.hideMarkAsRead) {
|
||||||
_notification.add_button_with_target(
|
_notification.add_button_with_target(
|
||||||
tr::lng_context_mark_read(tr::now).toStdString(),
|
tr::lng_context_mark_read(tr::now).toStdString(),
|
||||||
"app.notification-mark-as-read",
|
"app.notification-mark-as-read",
|
||||||
|
@ -284,27 +280,28 @@ bool NotificationData::init(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto &text = info.message;
|
||||||
if (HasCapability("body-markup")) {
|
if (HasCapability("body-markup")) {
|
||||||
_title = title.toStdString();
|
_title = title.toStdString();
|
||||||
|
|
||||||
_body = subtitle.isEmpty()
|
_body = subtitle.isEmpty()
|
||||||
? msg.toHtmlEscaped().toStdString()
|
? text.toHtmlEscaped().toStdString()
|
||||||
: u"<b>%1</b>\n%2"_q.arg(
|
: u"<b>%1</b>\n%2"_q.arg(
|
||||||
subtitle.toHtmlEscaped(),
|
subtitle.toHtmlEscaped(),
|
||||||
msg.toHtmlEscaped()).toStdString();
|
text.toHtmlEscaped()).toStdString();
|
||||||
} else {
|
} else {
|
||||||
_title = subtitle.isEmpty()
|
_title = subtitle.isEmpty()
|
||||||
? title.toStdString()
|
? title.toStdString()
|
||||||
: subtitle.toStdString() + " (" + title.toStdString() + ')';
|
: subtitle.toStdString() + " (" + title.toStdString() + ')';
|
||||||
|
|
||||||
_body = msg.toStdString();
|
_body = text.toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasCapability("actions")) {
|
if (HasCapability("actions")) {
|
||||||
_actions.push_back("default");
|
_actions.push_back("default");
|
||||||
_actions.push_back(tr::lng_open_link(tr::now).toStdString());
|
_actions.push_back(tr::lng_open_link(tr::now).toStdString());
|
||||||
|
|
||||||
if (!options.hideMarkAsRead) {
|
if (!info.options.hideMarkAsRead) {
|
||||||
// icon name according to https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
|
// icon name according to https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
|
||||||
_actions.push_back("mail-mark-read");
|
_actions.push_back("mail-mark-read");
|
||||||
_actions.push_back(
|
_actions.push_back(
|
||||||
|
@ -312,7 +309,7 @@ bool NotificationData::init(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasCapability("inline-reply")
|
if (HasCapability("inline-reply")
|
||||||
&& !options.hideReplyButton) {
|
&& !info.options.hideReplyButton) {
|
||||||
_actions.push_back("inline-reply");
|
_actions.push_back("inline-reply");
|
||||||
_actions.push_back(
|
_actions.push_back(
|
||||||
tr::lng_notification_reply(tr::now).toStdString());
|
tr::lng_notification_reply(tr::now).toStdString());
|
||||||
|
@ -555,14 +552,8 @@ public:
|
||||||
void init(XdgNotifications::NotificationsProxy proxy);
|
void init(XdgNotifications::NotificationsProxy proxy);
|
||||||
|
|
||||||
void showNotification(
|
void showNotification(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView);
|
||||||
Ui::PeerUserpicView &userpicView,
|
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options);
|
|
||||||
void clearAll();
|
void clearAll();
|
||||||
void clearFromItem(not_null<HistoryItem*> item);
|
void clearFromItem(not_null<HistoryItem*> item);
|
||||||
void clearFromTopic(not_null<Data::ForumTopic*> topic);
|
void clearFromTopic(not_null<Data::ForumTopic*> topic);
|
||||||
|
@ -778,32 +769,23 @@ void Manager::Private::init(XdgNotifications::NotificationsProxy proxy) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::Private::showNotification(
|
void Manager::Private::showNotification(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView) {
|
||||||
Ui::PeerUserpicView &userpicView,
|
const auto peer = info.peer;
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options) {
|
|
||||||
const auto key = ContextId{
|
const auto key = ContextId{
|
||||||
.sessionId = peer->session().uniqueId(),
|
.sessionId = peer->session().uniqueId(),
|
||||||
.peerId = peer->id,
|
.peerId = peer->id,
|
||||||
.topicRootId = topicRootId,
|
.topicRootId = info.topicRootId,
|
||||||
};
|
};
|
||||||
const auto notificationId = NotificationId{
|
const auto notificationId = NotificationId{
|
||||||
.contextId = key,
|
.contextId = key,
|
||||||
.msgId = msgId,
|
.msgId = info.itemId,
|
||||||
};
|
};
|
||||||
auto notification = std::make_unique<NotificationData>(
|
auto notification = std::make_unique<NotificationData>(
|
||||||
_manager,
|
_manager,
|
||||||
_proxy,
|
_proxy,
|
||||||
notificationId);
|
notificationId);
|
||||||
const auto inited = notification->init(
|
const auto inited = notification->init(info);
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
msg,
|
|
||||||
options);
|
|
||||||
if (!inited) {
|
if (!inited) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -945,23 +927,9 @@ void Manager::clearNotification(NotificationId id) {
|
||||||
Manager::~Manager() = default;
|
Manager::~Manager() = default;
|
||||||
|
|
||||||
void Manager::doShowNativeNotification(
|
void Manager::doShowNativeNotification(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView) {
|
||||||
Ui::PeerUserpicView &userpicView,
|
_private->showNotification(std::move(info), userpicView);
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options) {
|
|
||||||
_private->showNotification(
|
|
||||||
peer,
|
|
||||||
topicRootId,
|
|
||||||
userpicView,
|
|
||||||
msgId,
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
msg,
|
|
||||||
options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::doClearAllFast() {
|
void Manager::doClearAllFast() {
|
||||||
|
|
|
@ -20,14 +20,8 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void doShowNativeNotification(
|
void doShowNativeNotification(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView) override;
|
||||||
Ui::PeerUserpicView &userpicView,
|
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options) override;
|
|
||||||
void doClearAllFast() override;
|
void doClearAllFast() override;
|
||||||
void doClearFromItem(not_null<HistoryItem*> item) override;
|
void doClearFromItem(not_null<HistoryItem*> item) override;
|
||||||
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
|
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
|
||||||
|
|
|
@ -20,14 +20,8 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void doShowNativeNotification(
|
void doShowNativeNotification(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView) override;
|
||||||
Ui::PeerUserpicView &userpicView,
|
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options) override;
|
|
||||||
void doClearAllFast() override;
|
void doClearAllFast() override;
|
||||||
void doClearFromItem(not_null<HistoryItem*> item) override;
|
void doClearFromItem(not_null<HistoryItem*> item) override;
|
||||||
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
|
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
|
||||||
|
|
|
@ -209,14 +209,8 @@ public:
|
||||||
Private(Manager *manager);
|
Private(Manager *manager);
|
||||||
|
|
||||||
void showNotification(
|
void showNotification(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView);
|
||||||
Ui::PeerUserpicView &userpicView,
|
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options);
|
|
||||||
void clearAll();
|
void clearAll();
|
||||||
void clearFromItem(not_null<HistoryItem*> item);
|
void clearFromItem(not_null<HistoryItem*> item);
|
||||||
void clearFromTopic(not_null<Data::ForumTopic*> topic);
|
void clearFromTopic(not_null<Data::ForumTopic*> topic);
|
||||||
|
@ -296,23 +290,18 @@ Manager::Private::Private(Manager *manager)
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::Private::showNotification(
|
void Manager::Private::showNotification(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView) {
|
||||||
Ui::PeerUserpicView &userpicView,
|
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options) {
|
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
|
|
||||||
|
const auto peer = info.peer;
|
||||||
NSUserNotification *notification = [[[NSUserNotification alloc] init] autorelease];
|
NSUserNotification *notification = [[[NSUserNotification alloc] init] autorelease];
|
||||||
if ([notification respondsToSelector:@selector(setIdentifier:)]) {
|
if ([notification respondsToSelector:@selector(setIdentifier:)]) {
|
||||||
auto identifier = _managerIdString
|
auto identifier = _managerIdString
|
||||||
+ '_'
|
+ '_'
|
||||||
+ QString::number(peer->id.value)
|
+ QString::number(peer->id.value)
|
||||||
+ '_'
|
+ '_'
|
||||||
+ QString::number(msgId.bare);
|
+ QString::number(info.itemId.bare);
|
||||||
auto identifierValue = Q2NSString(identifier);
|
auto identifierValue = Q2NSString(identifier);
|
||||||
[notification setIdentifier:identifierValue];
|
[notification setIdentifier:identifierValue];
|
||||||
}
|
}
|
||||||
|
@ -322,30 +311,35 @@ void Manager::Private::showNotification(
|
||||||
@"session",
|
@"session",
|
||||||
[NSNumber numberWithUnsignedLongLong:peer->id.value],
|
[NSNumber numberWithUnsignedLongLong:peer->id.value],
|
||||||
@"peer",
|
@"peer",
|
||||||
[NSNumber numberWithLongLong:topicRootId.bare],
|
[NSNumber numberWithLongLong:info.topicRootId.bare],
|
||||||
@"topic",
|
@"topic",
|
||||||
[NSNumber numberWithLongLong:msgId.bare],
|
[NSNumber numberWithLongLong:info.itemId.bare],
|
||||||
@"msgid",
|
@"msgid",
|
||||||
[NSNumber numberWithUnsignedLongLong:_managerId],
|
[NSNumber numberWithUnsignedLongLong:_managerId],
|
||||||
@"manager",
|
@"manager",
|
||||||
nil]];
|
nil]];
|
||||||
|
|
||||||
[notification setTitle:Q2NSString(title)];
|
[notification setTitle:Q2NSString(info.title)];
|
||||||
[notification setSubtitle:Q2NSString(subtitle)];
|
[notification setSubtitle:Q2NSString(info.subtitle)];
|
||||||
[notification setInformativeText:Q2NSString(msg)];
|
[notification setInformativeText:Q2NSString(info.message)];
|
||||||
if (!options.hideNameAndPhoto
|
if (!info.options.hideNameAndPhoto
|
||||||
&& [notification respondsToSelector:@selector(setContentImage:)]) {
|
&& [notification respondsToSelector:@selector(setContentImage:)]) {
|
||||||
NSImage *img = Q2NSImage(
|
NSImage *img = Q2NSImage(
|
||||||
Window::Notifications::GenerateUserpic(peer, userpicView));
|
Window::Notifications::GenerateUserpic(peer, userpicView));
|
||||||
[notification setContentImage:img];
|
[notification setContentImage:img];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.hideReplyButton
|
if (!info.options.hideReplyButton
|
||||||
&& [notification respondsToSelector:@selector(setHasReplyButton:)]) {
|
&& [notification respondsToSelector:@selector(setHasReplyButton:)]) {
|
||||||
[notification setHasReplyButton:YES];
|
[notification setHasReplyButton:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
[notification setSoundName:nil];
|
const auto sound = info.soundPath ? info.soundPath() : QString();
|
||||||
|
if (!sound.isEmpty()) {
|
||||||
|
[notification setSoundName:Q2NSString(sound)];
|
||||||
|
} else {
|
||||||
|
[notification setSoundName:nil];
|
||||||
|
}
|
||||||
|
|
||||||
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
|
||||||
[center deliverNotification:notification];
|
[center deliverNotification:notification];
|
||||||
|
@ -572,23 +566,9 @@ Manager::Manager(Window::Notifications::System *system) : NativeManager(system)
|
||||||
Manager::~Manager() = default;
|
Manager::~Manager() = default;
|
||||||
|
|
||||||
void Manager::doShowNativeNotification(
|
void Manager::doShowNativeNotification(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView) {
|
||||||
Ui::PeerUserpicView &userpicView,
|
_private->showNotification(std::move(info), userpicView);
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options) {
|
|
||||||
_private->showNotification(
|
|
||||||
peer,
|
|
||||||
topicRootId,
|
|
||||||
userpicView,
|
|
||||||
msgId,
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
msg,
|
|
||||||
options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::doClearAllFast() {
|
void Manager::doClearAllFast() {
|
||||||
|
@ -620,11 +600,11 @@ bool Manager::doSkipToast() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::doMaybePlaySound(Fn<void()> playSound) {
|
void Manager::doMaybePlaySound(Fn<void()> playSound) {
|
||||||
_private->invokeIfNotFocused(std::move(playSound));
|
// Play through native notification system.
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::doMaybeFlashBounce(Fn<void()> flashBounce) {
|
void Manager::doMaybeFlashBounce(Fn<void()> flashBounce) {
|
||||||
_private->invokeIfNotFocused(std::move(flashBounce));
|
flashBounce();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Notifications
|
} // namespace Notifications
|
||||||
|
|
|
@ -428,18 +428,12 @@ void Create(Window::Notifications::System *system) {
|
||||||
|
|
||||||
class Manager::Private {
|
class Manager::Private {
|
||||||
public:
|
public:
|
||||||
|
using Info = Window::Notifications::NativeManager::NotificationInfo;
|
||||||
|
|
||||||
explicit Private(Manager *instance);
|
explicit Private(Manager *instance);
|
||||||
bool init();
|
bool init();
|
||||||
|
|
||||||
bool showNotification(
|
bool showNotification(Info &&info, Ui::PeerUserpicView &userpicView);
|
||||||
not_null<PeerData*> peer,
|
|
||||||
MsgId topicRootId,
|
|
||||||
Ui::PeerUserpicView &userpicView,
|
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options);
|
|
||||||
void clearAll();
|
void clearAll();
|
||||||
void clearFromItem(not_null<HistoryItem*> item);
|
void clearFromItem(not_null<HistoryItem*> item);
|
||||||
void clearFromTopic(not_null<Data::ForumTopic*> topic);
|
void clearFromTopic(not_null<Data::ForumTopic*> topic);
|
||||||
|
@ -457,14 +451,8 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool showNotificationInTryCatch(
|
bool showNotificationInTryCatch(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView);
|
||||||
Ui::PeerUserpicView &userpicView,
|
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options);
|
|
||||||
void tryHide(const ToastNotification ¬ification);
|
void tryHide(const ToastNotification ¬ification);
|
||||||
[[nodiscard]] std::wstring ensureSendButtonIcon();
|
[[nodiscard]] std::wstring ensureSendButtonIcon();
|
||||||
|
|
||||||
|
@ -677,28 +665,14 @@ void Manager::Private::handleActivation(const ToastActivation &activation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Manager::Private::showNotification(
|
bool Manager::Private::showNotification(
|
||||||
not_null<PeerData*> peer,
|
Info &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView) {
|
||||||
Ui::PeerUserpicView &userpicView,
|
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options) {
|
|
||||||
if (!_notifier) {
|
if (!_notifier) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base::WinRT::Try([&] {
|
return base::WinRT::Try([&] {
|
||||||
return showNotificationInTryCatch(
|
return showNotificationInTryCatch(std::move(info), userpicView);
|
||||||
peer,
|
|
||||||
topicRootId,
|
|
||||||
userpicView,
|
|
||||||
msgId,
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
msg,
|
|
||||||
options);
|
|
||||||
}).value_or(false);
|
}).value_or(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -712,36 +686,31 @@ std::wstring Manager::Private::ensureSendButtonIcon() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Manager::Private::showNotificationInTryCatch(
|
bool Manager::Private::showNotificationInTryCatch(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView) {
|
||||||
Ui::PeerUserpicView &userpicView,
|
const auto withSubtitle = !info.subtitle.isEmpty();
|
||||||
MsgId msgId,
|
const auto peer = info.peer;
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options) {
|
|
||||||
const auto withSubtitle = !subtitle.isEmpty();
|
|
||||||
auto toastXml = XmlDocument();
|
auto toastXml = XmlDocument();
|
||||||
|
|
||||||
const auto key = ContextId{
|
const auto key = ContextId{
|
||||||
.sessionId = peer->session().uniqueId(),
|
.sessionId = peer->session().uniqueId(),
|
||||||
.peerId = peer->id,
|
.peerId = peer->id,
|
||||||
.topicRootId = topicRootId,
|
.topicRootId = info.topicRootId,
|
||||||
};
|
};
|
||||||
const auto notificationId = NotificationId{
|
const auto notificationId = NotificationId{
|
||||||
.contextId = key,
|
.contextId = key,
|
||||||
.msgId = msgId
|
.msgId = info.itemId,
|
||||||
};
|
};
|
||||||
const auto idString = u"pid=%1&session=%2&peer=%3&topic=%4&msg=%5"_q
|
const auto idString = u"pid=%1&session=%2&peer=%3&topic=%4&msg=%5"_q
|
||||||
.arg(GetCurrentProcessId())
|
.arg(GetCurrentProcessId())
|
||||||
.arg(key.sessionId)
|
.arg(key.sessionId)
|
||||||
.arg(key.peerId.value)
|
.arg(key.peerId.value)
|
||||||
.arg(topicRootId.bare)
|
.arg(info.topicRootId.bare)
|
||||||
.arg(msgId.bare);
|
.arg(info.itemId.bare);
|
||||||
|
|
||||||
const auto modern = Platform::IsWindows10OrGreater();
|
const auto modern = Platform::IsWindows10OrGreater();
|
||||||
if (modern) {
|
if (modern) {
|
||||||
toastXml.LoadXml(NotificationTemplate(idString, options));
|
toastXml.LoadXml(NotificationTemplate(idString, info.options));
|
||||||
} else {
|
} else {
|
||||||
toastXml = ToastNotificationManager::GetTemplateContent(
|
toastXml = ToastNotificationManager::GetTemplateContent(
|
||||||
(withSubtitle
|
(withSubtitle
|
||||||
|
@ -751,7 +720,7 @@ bool Manager::Private::showNotificationInTryCatch(
|
||||||
SetAction(toastXml, idString);
|
SetAction(toastXml, idString);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto userpicKey = options.hideNameAndPhoto
|
const auto userpicKey = info.options.hideNameAndPhoto
|
||||||
? InMemoryKey()
|
? InMemoryKey()
|
||||||
: peer->userpicUniqueKey(userpicView);
|
: peer->userpicUniqueKey(userpicView);
|
||||||
const auto userpicPath = _cachedUserpics.get(
|
const auto userpicPath = _cachedUserpics.get(
|
||||||
|
@ -760,13 +729,13 @@ bool Manager::Private::showNotificationInTryCatch(
|
||||||
userpicView);
|
userpicView);
|
||||||
const auto userpicPathWide = QDir::toNativeSeparators(
|
const auto userpicPathWide = QDir::toNativeSeparators(
|
||||||
userpicPath).toStdWString();
|
userpicPath).toStdWString();
|
||||||
if (modern && !options.hideReplyButton) {
|
if (modern && !info.options.hideReplyButton) {
|
||||||
SetReplyIconSrc(toastXml, ensureSendButtonIcon());
|
SetReplyIconSrc(toastXml, ensureSendButtonIcon());
|
||||||
SetReplyPlaceholder(
|
SetReplyPlaceholder(
|
||||||
toastXml,
|
toastXml,
|
||||||
tr::lng_message_ph(tr::now).toStdWString());
|
tr::lng_message_ph(tr::now).toStdWString());
|
||||||
}
|
}
|
||||||
if (modern && !options.hideMarkAsRead) {
|
if (modern && !info.options.hideMarkAsRead) {
|
||||||
SetMarkAsReadText(
|
SetMarkAsReadText(
|
||||||
toastXml,
|
toastXml,
|
||||||
tr::lng_context_mark_read(tr::now).toStdWString());
|
tr::lng_context_mark_read(tr::now).toStdWString());
|
||||||
|
@ -779,17 +748,20 @@ bool Manager::Private::showNotificationInTryCatch(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetNodeValueString(toastXml, nodeList.Item(0), title.toStdWString());
|
SetNodeValueString(
|
||||||
|
toastXml,
|
||||||
|
nodeList.Item(0),
|
||||||
|
info.title.toStdWString());
|
||||||
if (withSubtitle) {
|
if (withSubtitle) {
|
||||||
SetNodeValueString(
|
SetNodeValueString(
|
||||||
toastXml,
|
toastXml,
|
||||||
nodeList.Item(1),
|
nodeList.Item(1),
|
||||||
subtitle.toStdWString());
|
info.subtitle.toStdWString());
|
||||||
}
|
}
|
||||||
SetNodeValueString(
|
SetNodeValueString(
|
||||||
toastXml,
|
toastXml,
|
||||||
nodeList.Item(withSubtitle ? 2 : 1),
|
nodeList.Item(withSubtitle ? 2 : 1),
|
||||||
msg.toStdWString());
|
info.message.toStdWString());
|
||||||
|
|
||||||
const auto weak = std::weak_ptr(_guarded);
|
const auto weak = std::weak_ptr(_guarded);
|
||||||
const auto performOnMainQueue = [=](FnMut<void(Manager *manager)> task) {
|
const auto performOnMainQueue = [=](FnMut<void(Manager *manager)> task) {
|
||||||
|
@ -860,7 +832,7 @@ bool Manager::Private::showNotificationInTryCatch(
|
||||||
|
|
||||||
auto i = _notifications.find(key);
|
auto i = _notifications.find(key);
|
||||||
if (i != _notifications.cend()) {
|
if (i != _notifications.cend()) {
|
||||||
auto j = i->second.find(msgId);
|
auto j = i->second.find(info.itemId);
|
||||||
if (j != i->second.end()) {
|
if (j != i->second.end()) {
|
||||||
const auto existing = j->second;
|
const auto existing = j->second;
|
||||||
i->second.erase(j);
|
i->second.erase(j);
|
||||||
|
@ -880,7 +852,7 @@ bool Manager::Private::showNotificationInTryCatch(
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
i->second.emplace(msgId, toast);
|
i->second.emplace(info.itemId, toast);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -910,23 +882,9 @@ void Manager::handleActivation(const ToastActivation &activation) {
|
||||||
Manager::~Manager() = default;
|
Manager::~Manager() = default;
|
||||||
|
|
||||||
void Manager::doShowNativeNotification(
|
void Manager::doShowNativeNotification(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView) {
|
||||||
Ui::PeerUserpicView &userpicView,
|
_private->showNotification(std::move(info), userpicView);
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options) {
|
|
||||||
_private->showNotification(
|
|
||||||
peer,
|
|
||||||
topicRootId,
|
|
||||||
userpicView,
|
|
||||||
msgId,
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
msg,
|
|
||||||
options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::doClearAllFast() {
|
void Manager::doClearAllFast() {
|
||||||
|
|
|
@ -26,14 +26,8 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void doShowNativeNotification(
|
void doShowNativeNotification(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView) override;
|
||||||
Ui::PeerUserpicView &userpicView,
|
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options) override;
|
|
||||||
void doClearAllFast() override;
|
void doClearAllFast() override;
|
||||||
void doClearFromItem(not_null<HistoryItem*> item) override;
|
void doClearFromItem(not_null<HistoryItem*> item) override;
|
||||||
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
|
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "base/concurrent_timer.h"
|
#include "base/concurrent_timer.h"
|
||||||
#include "base/debug_log.h"
|
#include "base/debug_log.h"
|
||||||
|
#include "ffmpeg/ffmpeg_bytes_io_wrap.h"
|
||||||
#include "ffmpeg/ffmpeg_utility.h"
|
#include "ffmpeg/ffmpeg_utility.h"
|
||||||
#include "media/audio/media_audio_capture.h"
|
#include "media/audio/media_audio_capture.h"
|
||||||
#include "ui/image/image_prepare.h"
|
#include "ui/image/image_prepare.h"
|
||||||
|
@ -40,39 +41,6 @@ constexpr auto kMinScale = 0.7;
|
||||||
|
|
||||||
using namespace FFmpeg;
|
using namespace FFmpeg;
|
||||||
|
|
||||||
struct ReadBytesWrap {
|
|
||||||
int64 size = 0;
|
|
||||||
int64 offset = 0;
|
|
||||||
const uchar *data = nullptr;
|
|
||||||
|
|
||||||
static int Read(void *opaque, uint8_t *buf, int buf_size) {
|
|
||||||
auto wrap = static_cast<ReadBytesWrap*>(opaque);
|
|
||||||
const auto toRead = std::min(
|
|
||||||
int64(buf_size),
|
|
||||||
wrap->size - wrap->offset);
|
|
||||||
if (toRead > 0) {
|
|
||||||
memcpy(buf, wrap->data + wrap->offset, toRead);
|
|
||||||
wrap->offset += toRead;
|
|
||||||
}
|
|
||||||
return toRead;
|
|
||||||
};
|
|
||||||
static int64_t Seek(void *opaque, int64_t offset, int whence) {
|
|
||||||
auto wrap = static_cast<ReadBytesWrap*>(opaque);
|
|
||||||
auto updated = int64(-1);
|
|
||||||
switch (whence) {
|
|
||||||
case SEEK_SET: updated = offset; break;
|
|
||||||
case SEEK_CUR: updated = wrap->offset + offset; break;
|
|
||||||
case SEEK_END: updated = wrap->size + offset; break;
|
|
||||||
case AVSEEK_SIZE: return wrap->size; break;
|
|
||||||
}
|
|
||||||
if (updated < 0 || updated > wrap->size) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
wrap->offset = updated;
|
|
||||||
return updated;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
[[nodiscard]] int MinithumbSize() {
|
[[nodiscard]] int MinithumbSize() {
|
||||||
const auto full = st::historySendSize.height();
|
const auto full = st::historySendSize.height();
|
||||||
const auto margin = st::historyRecordWaveformBgMargins;
|
const auto margin = st::historyRecordWaveformBgMargins;
|
||||||
|
@ -107,22 +75,6 @@ private:
|
||||||
std::array<int64, kMaxStreams> lastDts = { 0 };
|
std::array<int64, kMaxStreams> lastDts = { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
#if DA_FFMPEG_CONST_WRITE_CALLBACK
|
|
||||||
static int Write(void *opaque, const uint8_t *_buf, int buf_size) {
|
|
||||||
uint8_t *buf = const_cast<uint8_t *>(_buf);
|
|
||||||
#else
|
|
||||||
static int Write(void *opaque, uint8_t *buf, int buf_size) {
|
|
||||||
#endif
|
|
||||||
return static_cast<Private*>(opaque)->write(buf, buf_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int64_t Seek(void *opaque, int64_t offset, int whence) {
|
|
||||||
return static_cast<Private*>(opaque)->seek(offset, whence);
|
|
||||||
}
|
|
||||||
|
|
||||||
int write(uint8_t *buf, int buf_size);
|
|
||||||
int64_t seek(int64_t offset, int whence);
|
|
||||||
|
|
||||||
void initEncoding();
|
void initEncoding();
|
||||||
void initCircleMask();
|
void initCircleMask();
|
||||||
void initMinithumbsCanvas();
|
void initMinithumbsCanvas();
|
||||||
|
@ -188,8 +140,7 @@ private:
|
||||||
crl::time _firstAudioChunkFinished = 0;
|
crl::time _firstAudioChunkFinished = 0;
|
||||||
crl::time _firstVideoFrameTime = 0;
|
crl::time _firstVideoFrameTime = 0;
|
||||||
|
|
||||||
QByteArray _result;
|
WriteBytesWrap _result;
|
||||||
int64_t _resultOffset = 0;
|
|
||||||
crl::time _resultDuration = 0;
|
crl::time _resultDuration = 0;
|
||||||
bool _finished = false;
|
bool _finished = false;
|
||||||
|
|
||||||
|
@ -236,49 +187,12 @@ RoundVideoRecorder::Private::~Private() {
|
||||||
finishEncoding();
|
finishEncoding();
|
||||||
}
|
}
|
||||||
|
|
||||||
int RoundVideoRecorder::Private::write(uint8_t *buf, int buf_size) {
|
|
||||||
if (const auto total = _resultOffset + int64(buf_size)) {
|
|
||||||
const auto size = int64(_result.size());
|
|
||||||
constexpr auto kReserve = 1024 * 1024;
|
|
||||||
_result.reserve((total / kReserve) * kReserve);
|
|
||||||
const auto overwrite = std::min(
|
|
||||||
size - _resultOffset,
|
|
||||||
int64(buf_size));
|
|
||||||
if (overwrite) {
|
|
||||||
memcpy(_result.data() + _resultOffset, buf, overwrite);
|
|
||||||
}
|
|
||||||
if (const auto append = buf_size - overwrite) {
|
|
||||||
_result.append(
|
|
||||||
reinterpret_cast<const char*>(buf) + overwrite,
|
|
||||||
append);
|
|
||||||
}
|
|
||||||
_resultOffset += buf_size;
|
|
||||||
}
|
|
||||||
return buf_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t RoundVideoRecorder::Private::seek(int64_t offset, int whence) {
|
|
||||||
const auto checkedSeek = [&](int64_t offset) {
|
|
||||||
if (offset < 0 || offset > int64(_result.size())) {
|
|
||||||
return int64_t(-1);
|
|
||||||
}
|
|
||||||
return int64_t(_resultOffset = offset);
|
|
||||||
};
|
|
||||||
switch (whence) {
|
|
||||||
case SEEK_SET: return checkedSeek(offset);
|
|
||||||
case SEEK_CUR: return checkedSeek(_resultOffset + offset);
|
|
||||||
case SEEK_END: return checkedSeek(int64(_result.size()) + offset);
|
|
||||||
case AVSEEK_SIZE: return int64(_result.size());
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoundVideoRecorder::Private::initEncoding() {
|
void RoundVideoRecorder::Private::initEncoding() {
|
||||||
_format = MakeWriteFormatPointer(
|
_format = MakeWriteFormatPointer(
|
||||||
static_cast<void*>(this),
|
static_cast<void*>(&_result),
|
||||||
nullptr,
|
nullptr,
|
||||||
&Private::Write,
|
&WriteBytesWrap::Write,
|
||||||
&Private::Seek,
|
&WriteBytesWrap::Seek,
|
||||||
"mp4"_q);
|
"mp4"_q);
|
||||||
|
|
||||||
if (!initVideo()) {
|
if (!initVideo()) {
|
||||||
|
@ -427,10 +341,10 @@ bool RoundVideoRecorder::Private::initAudio() {
|
||||||
&_swrContext);
|
&_swrContext);
|
||||||
#else // DA_FFMPEG_NEW_CHANNEL_LAYOUT
|
#else // DA_FFMPEG_NEW_CHANNEL_LAYOUT
|
||||||
_swrContext = MakeSwresamplePointer(
|
_swrContext = MakeSwresamplePointer(
|
||||||
&_audioCodec->ch_layout,
|
&_audioCodec->channel_layout,
|
||||||
AV_SAMPLE_FMT_S16,
|
AV_SAMPLE_FMT_S16,
|
||||||
_audioCodec->sample_rate,
|
_audioCodec->sample_rate,
|
||||||
&_audioCodec->ch_layout,
|
&_audioCodec->channel_layout,
|
||||||
_audioCodec->sample_fmt,
|
_audioCodec->sample_fmt,
|
||||||
_audioCodec->sample_rate,
|
_audioCodec->sample_rate,
|
||||||
&_swrContext);
|
&_swrContext);
|
||||||
|
@ -487,7 +401,7 @@ RoundVideoResult RoundVideoRecorder::Private::finish() {
|
||||||
}
|
}
|
||||||
finishEncoding();
|
finishEncoding();
|
||||||
auto result = appendToPrevious({
|
auto result = appendToPrevious({
|
||||||
.content = base::take(_result),
|
.content = base::take(_result.content),
|
||||||
.duration = base::take(_resultDuration),
|
.duration = base::take(_resultDuration),
|
||||||
//.waveform = {},
|
//.waveform = {},
|
||||||
.minithumbs = base::take(_minithumbs),
|
.minithumbs = base::take(_minithumbs),
|
||||||
|
@ -518,10 +432,10 @@ RoundVideoResult RoundVideoRecorder::Private::appendToPrevious(
|
||||||
}
|
}
|
||||||
|
|
||||||
auto output = MakeWriteFormatPointer(
|
auto output = MakeWriteFormatPointer(
|
||||||
static_cast<void*>(this),
|
static_cast<void*>(&_result),
|
||||||
nullptr,
|
nullptr,
|
||||||
&Private::Write,
|
&WriteBytesWrap::Write,
|
||||||
&Private::Seek,
|
&WriteBytesWrap::Seek,
|
||||||
"mp4"_q);
|
"mp4"_q);
|
||||||
|
|
||||||
for (auto i = 0; i != input1->nb_streams; ++i) {
|
for (auto i = 0; i != input1->nb_streams; ++i) {
|
||||||
|
@ -562,7 +476,7 @@ RoundVideoResult RoundVideoRecorder::Private::appendToPrevious(
|
||||||
fail(Error::Encoding);
|
fail(Error::Encoding);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
video.content = base::take(_result);
|
video.content = base::take(_result.content);
|
||||||
video.duration += _previous.duration;
|
video.duration += _previous.duration;
|
||||||
return video;
|
return video;
|
||||||
}
|
}
|
||||||
|
@ -685,7 +599,7 @@ void RoundVideoRecorder::Private::deinitEncoding() {
|
||||||
_firstAudioChunkFinished = 0;
|
_firstAudioChunkFinished = 0;
|
||||||
_firstVideoFrameTime = 0;
|
_firstVideoFrameTime = 0;
|
||||||
|
|
||||||
_resultOffset = 0;
|
_result.offset = 0;
|
||||||
|
|
||||||
_maxLevelSinceLastUpdate = 0;
|
_maxLevelSinceLastUpdate = 0;
|
||||||
_lastUpdateDuration = 0;
|
_lastUpdateDuration = 0;
|
||||||
|
|
|
@ -107,6 +107,22 @@ constexpr auto kSystemAlertDuration = crl::time(0);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<DocumentId> MaybeSoundFor(
|
||||||
|
not_null<Data::Thread*> thread,
|
||||||
|
PeerData *from) {
|
||||||
|
const auto notifySettings = &thread->owner().notifySettings();
|
||||||
|
const auto threadUnknown = notifySettings->muteUnknown(thread);
|
||||||
|
const auto threadAlert = !threadUnknown
|
||||||
|
&& !notifySettings->isMuted(thread);
|
||||||
|
const auto fromUnknown = (!from
|
||||||
|
|| notifySettings->muteUnknown(from));
|
||||||
|
const auto fromAlert = !fromUnknown
|
||||||
|
&& !notifySettings->isMuted(from);
|
||||||
|
return (threadAlert || fromAlert)
|
||||||
|
? notifySettings->sound(thread).id
|
||||||
|
: std::optional<DocumentId>();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
const char kOptionGNotification[] = "gnotification";
|
const char kOptionGNotification[] = "gnotification";
|
||||||
|
@ -538,10 +554,12 @@ void System::showGrouped() {
|
||||||
_manager->showNotification({
|
_manager->showNotification({
|
||||||
.item = lastItem,
|
.item = lastItem,
|
||||||
.forwardedCount = _lastForwardedCount,
|
.forwardedCount = _lastForwardedCount,
|
||||||
|
.soundId = _lastSoundId,
|
||||||
});
|
});
|
||||||
_lastForwardedCount = 0;
|
_lastForwardedCount = 0;
|
||||||
_lastHistoryItemId = FullMsgId();
|
_lastHistoryItemId = FullMsgId();
|
||||||
_lastHistorySessionId = 0;
|
_lastHistorySessionId = 0;
|
||||||
|
_lastSoundId = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -568,23 +586,16 @@ void System::showNext() {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto ms = crl::now(), nextAlert = crl::time(0);
|
auto ms = crl::now(), nextAlert = crl::time(0);
|
||||||
auto alertThread = (Data::Thread*)nullptr;
|
auto alertThread = (Data::Thread*)nullptr;
|
||||||
|
auto alertSoundId = std::optional<DocumentId>();
|
||||||
for (auto i = _whenAlerts.begin(); i != _whenAlerts.end();) {
|
for (auto i = _whenAlerts.begin(); i != _whenAlerts.end();) {
|
||||||
while (!i->second.empty() && i->second.begin()->first <= ms) {
|
while (!i->second.empty() && i->second.begin()->first <= ms) {
|
||||||
const auto thread = i->first;
|
const auto thread = i->first;
|
||||||
const auto notifySettings = &thread->owner().notifySettings();
|
|
||||||
const auto threadUnknown = notifySettings->muteUnknown(thread);
|
|
||||||
const auto threadAlert = !threadUnknown
|
|
||||||
&& !notifySettings->isMuted(thread);
|
|
||||||
const auto from = i->second.begin()->second;
|
const auto from = i->second.begin()->second;
|
||||||
const auto fromUnknown = (!from
|
if (const auto soundId = MaybeSoundFor(thread, from)) {
|
||||||
|| notifySettings->muteUnknown(from));
|
|
||||||
const auto fromAlert = !fromUnknown
|
|
||||||
&& !notifySettings->isMuted(from);
|
|
||||||
if (threadAlert || fromAlert) {
|
|
||||||
alertThread = thread;
|
alertThread = thread;
|
||||||
|
alertSoundId = soundId;
|
||||||
}
|
}
|
||||||
while (!i->second.empty()
|
while (!i->second.empty()
|
||||||
&& i->second.begin()->first <= ms + kMinimalAlertDelay) {
|
&& i->second.begin()->first <= ms + kMinimalAlertDelay) {
|
||||||
|
@ -627,7 +638,9 @@ void System::showNext() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_waiters.empty() || !settings.desktopNotify() || _manager->skipToast()) {
|
if (_waiters.empty()
|
||||||
|
|| !settings.desktopNotify()
|
||||||
|
|| _manager->skipToast()) {
|
||||||
if (nextAlert) {
|
if (nextAlert) {
|
||||||
_waitTimer.callOnce(nextAlert - ms);
|
_waitTimer.callOnce(nextAlert - ms);
|
||||||
}
|
}
|
||||||
|
@ -759,6 +772,9 @@ void System::showNext() {
|
||||||
if (!_lastHistoryItemId && groupedItem) {
|
if (!_lastHistoryItemId && groupedItem) {
|
||||||
_lastHistorySessionId = groupedItem->history()->session().uniqueId();
|
_lastHistorySessionId = groupedItem->history()->session().uniqueId();
|
||||||
_lastHistoryItemId = groupedItem->fullId();
|
_lastHistoryItemId = groupedItem->fullId();
|
||||||
|
_lastSoundId = MaybeSoundFor(
|
||||||
|
notifyThread,
|
||||||
|
groupedItem->specialNotificationPeer());
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the current notification is grouped.
|
// If the current notification is grouped.
|
||||||
|
@ -777,6 +793,9 @@ void System::showNext() {
|
||||||
_lastForwardedCount += forwardedCount;
|
_lastForwardedCount += forwardedCount;
|
||||||
_lastHistorySessionId = groupedItem->history()->session().uniqueId();
|
_lastHistorySessionId = groupedItem->history()->session().uniqueId();
|
||||||
_lastHistoryItemId = groupedItem->fullId();
|
_lastHistoryItemId = groupedItem->fullId();
|
||||||
|
_lastSoundId = MaybeSoundFor(
|
||||||
|
notifyThread,
|
||||||
|
groupedItem->specialNotificationPeer());
|
||||||
_waitForAllGroupedTimer.callOnce(kWaitingForAllGroupedDelay);
|
_waitForAllGroupedTimer.callOnce(kWaitingForAllGroupedDelay);
|
||||||
} else {
|
} else {
|
||||||
// If the current notification is not grouped
|
// If the current notification is not grouped
|
||||||
|
@ -788,12 +807,16 @@ void System::showNext() {
|
||||||
const auto reaction = reactionNotification
|
const auto reaction = reactionNotification
|
||||||
? notify->item->lookupUnreadReaction(notify->reactionSender)
|
? notify->item->lookupUnreadReaction(notify->reactionSender)
|
||||||
: Data::ReactionId();
|
: Data::ReactionId();
|
||||||
|
const auto soundFrom = reactionNotification
|
||||||
|
? notify->reactionSender
|
||||||
|
: notify->item->specialNotificationPeer();
|
||||||
if (!reactionNotification || !reaction.empty()) {
|
if (!reactionNotification || !reaction.empty()) {
|
||||||
_manager->showNotification({
|
_manager->showNotification({
|
||||||
.item = notify->item,
|
.item = notify->item,
|
||||||
.forwardedCount = forwardedCount,
|
.forwardedCount = forwardedCount,
|
||||||
.reactionFrom = notify->reactionSender,
|
.reactionFrom = notify->reactionSender,
|
||||||
.reactionId = reaction,
|
.reactionId = reaction,
|
||||||
|
.soundId = MaybeSoundFor(notifyThread, soundFrom),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -808,6 +831,25 @@ void System::showNext() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray System::lookupSoundBytes(
|
||||||
|
not_null<Data::Session*> owner,
|
||||||
|
DocumentId id) {
|
||||||
|
if (id) {
|
||||||
|
const auto ¬ifySettings = owner->notifySettings();
|
||||||
|
const auto custom = notifySettings.lookupRingtone(id);
|
||||||
|
return custom ? ReadRingtoneBytes(custom) : QByteArray();
|
||||||
|
}
|
||||||
|
auto f = QFile(Core::App().settings().getSoundPath(u"msg_incoming"_q));
|
||||||
|
if (f.open(QIODevice::ReadOnly)) {
|
||||||
|
return f.readAll();
|
||||||
|
}
|
||||||
|
auto fallback = QFile(u":/sounds/msg_incoming.mp3"_q);
|
||||||
|
if (fallback.open(QIODevice::ReadOnly)) {
|
||||||
|
return fallback.readAll();
|
||||||
|
}
|
||||||
|
Unexpected("Embedded sound not found!");
|
||||||
|
}
|
||||||
|
|
||||||
not_null<Media::Audio::Track*> System::lookupSound(
|
not_null<Media::Audio::Track*> System::lookupSound(
|
||||||
not_null<Data::Session*> owner,
|
not_null<Data::Session*> owner,
|
||||||
DocumentId id) {
|
DocumentId id) {
|
||||||
|
@ -819,17 +861,14 @@ not_null<Media::Audio::Track*> System::lookupSound(
|
||||||
if (i != end(_customSoundTracks)) {
|
if (i != end(_customSoundTracks)) {
|
||||||
return i->second.get();
|
return i->second.get();
|
||||||
}
|
}
|
||||||
const auto ¬ifySettings = owner->notifySettings();
|
const auto bytes = lookupSoundBytes(owner, id);
|
||||||
if (const auto custom = notifySettings.lookupRingtone(id)) {
|
if (!bytes.isEmpty()) {
|
||||||
const auto bytes = ReadRingtoneBytes(custom);
|
const auto j = _customSoundTracks.emplace(
|
||||||
if (!bytes.isEmpty()) {
|
id,
|
||||||
const auto j = _customSoundTracks.emplace(
|
Media::Audio::Current().createTrack()
|
||||||
id,
|
).first;
|
||||||
Media::Audio::Current().createTrack()
|
j->second->fillFromData(bytes::make_vector(bytes));
|
||||||
).first;
|
return j->second.get();
|
||||||
j->second->fillFromData(bytes::make_vector(bytes));
|
|
||||||
return j->second.get();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ensureSoundCreated();
|
ensureSoundCreated();
|
||||||
return _soundTrack.get();
|
return _soundTrack.get();
|
||||||
|
@ -1212,15 +1251,24 @@ void NativeManager::doShowNotification(NotificationFields &&fields) {
|
||||||
|
|
||||||
// #TODO optimize
|
// #TODO optimize
|
||||||
auto userpicView = item->history()->peer->createUserpicView();
|
auto userpicView = item->history()->peer->createUserpicView();
|
||||||
doShowNativeNotification(
|
const auto owner = &item->history()->owner();
|
||||||
item->history()->peer,
|
const auto soundPath = fields.soundId ? [=, id = *fields.soundId] {
|
||||||
item->topicRootId(),
|
return _localSoundCache.path(id, [=] {
|
||||||
userpicView,
|
return Core::App().notifications().lookupSoundBytes(owner, id);
|
||||||
item->id,
|
}, [=] {
|
||||||
scheduled ? WrapFromScheduled(fullTitle) : fullTitle,
|
return Core::App().notifications().lookupSoundBytes(owner, 0);
|
||||||
subtitle,
|
});
|
||||||
text,
|
} : Fn<QString()>();
|
||||||
options);
|
doShowNativeNotification({
|
||||||
|
.peer = item->history()->peer,
|
||||||
|
.topicRootId = item->topicRootId(),
|
||||||
|
.itemId = item->id,
|
||||||
|
.title = scheduled ? WrapFromScheduled(fullTitle) : fullTitle,
|
||||||
|
.subtitle = subtitle,
|
||||||
|
.message = text,
|
||||||
|
.soundPath = soundPath,
|
||||||
|
.options = options,
|
||||||
|
}, userpicView);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NativeManager::forceHideDetails() const {
|
bool NativeManager::forceHideDetails() const {
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_message_reaction_id.h"
|
#include "data/data_message_reaction_id.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
#include "base/type_traits.h"
|
#include "base/type_traits.h"
|
||||||
|
#include "media/audio/media_audio_local_cache.h"
|
||||||
|
|
||||||
class History;
|
class History;
|
||||||
|
|
||||||
|
@ -117,6 +118,9 @@ public:
|
||||||
void notifySettingsChanged(ChangeType type);
|
void notifySettingsChanged(ChangeType type);
|
||||||
|
|
||||||
void playSound(not_null<Main::Session*> session, DocumentId id);
|
void playSound(not_null<Main::Session*> session, DocumentId id);
|
||||||
|
[[nodiscard]] QByteArray lookupSoundBytes(
|
||||||
|
not_null<Data::Session*> owner,
|
||||||
|
DocumentId id);
|
||||||
|
|
||||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||||
return _lifetime;
|
return _lifetime;
|
||||||
|
@ -217,6 +221,7 @@ private:
|
||||||
int _lastForwardedCount = 0;
|
int _lastForwardedCount = 0;
|
||||||
uint64 _lastHistorySessionId = 0;
|
uint64 _lastHistorySessionId = 0;
|
||||||
FullMsgId _lastHistoryItemId;
|
FullMsgId _lastHistoryItemId;
|
||||||
|
std::optional<DocumentId> _lastSoundId;
|
||||||
|
|
||||||
rpl::lifetime _lifetime;
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
@ -277,6 +282,7 @@ public:
|
||||||
int forwardedCount = 0;
|
int forwardedCount = 0;
|
||||||
PeerData *reactionFrom = nullptr;
|
PeerData *reactionFrom = nullptr;
|
||||||
Data::ReactionId reactionId;
|
Data::ReactionId reactionId;
|
||||||
|
std::optional<DocumentId> soundId;
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Manager(not_null<System*> system) : _system(system) {
|
explicit Manager(not_null<System*> system) : _system(system) {
|
||||||
|
@ -313,11 +319,11 @@ public:
|
||||||
void notificationReplied(NotificationId id, const TextWithTags &reply);
|
void notificationReplied(NotificationId id, const TextWithTags &reply);
|
||||||
|
|
||||||
struct DisplayOptions {
|
struct DisplayOptions {
|
||||||
bool hideNameAndPhoto = false;
|
bool hideNameAndPhoto : 1 = false;
|
||||||
bool hideMessageText = false;
|
bool hideMessageText : 1 = false;
|
||||||
bool hideMarkAsRead = false;
|
bool hideMarkAsRead : 1 = false;
|
||||||
bool hideReplyButton = false;
|
bool hideReplyButton : 1 = false;
|
||||||
bool spoilerLoginCode = false;
|
bool spoilerLoginCode : 1 = false;
|
||||||
};
|
};
|
||||||
[[nodiscard]] DisplayOptions getNotificationOptions(
|
[[nodiscard]] DisplayOptions getNotificationOptions(
|
||||||
HistoryItem *item,
|
HistoryItem *item,
|
||||||
|
@ -393,6 +399,17 @@ public:
|
||||||
return ManagerType::Native;
|
return ManagerType::Native;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct NotificationInfo {
|
||||||
|
not_null<PeerData*> peer;
|
||||||
|
MsgId topicRootId = 0;
|
||||||
|
MsgId itemId = 0;
|
||||||
|
QString title;
|
||||||
|
QString subtitle;
|
||||||
|
QString message;
|
||||||
|
Fn<QString()> soundPath;
|
||||||
|
DisplayOptions options;
|
||||||
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
using Manager::Manager;
|
using Manager::Manager;
|
||||||
|
|
||||||
|
@ -407,14 +424,11 @@ protected:
|
||||||
bool forceHideDetails() const override;
|
bool forceHideDetails() const override;
|
||||||
|
|
||||||
virtual void doShowNativeNotification(
|
virtual void doShowNativeNotification(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView) = 0;
|
||||||
Ui::PeerUserpicView &userpicView,
|
|
||||||
MsgId msgId,
|
private:
|
||||||
const QString &title,
|
Media::Audio::LocalCache _localSoundCache;
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options) = 0;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -428,14 +442,8 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void doShowNativeNotification(
|
void doShowNativeNotification(
|
||||||
not_null<PeerData*> peer,
|
NotificationInfo &&info,
|
||||||
MsgId topicRootId,
|
Ui::PeerUserpicView &userpicView) override {
|
||||||
Ui::PeerUserpicView &userpicView,
|
|
||||||
MsgId msgId,
|
|
||||||
const QString &title,
|
|
||||||
const QString &subtitle,
|
|
||||||
const QString &msg,
|
|
||||||
DisplayOptions options) override {
|
|
||||||
}
|
}
|
||||||
void doClearAllFast() override {
|
void doClearAllFast() override {
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ nice_target_sources(lib_ffmpeg ${src_loc}
|
||||||
PRIVATE
|
PRIVATE
|
||||||
ffmpeg/ffmpeg_frame_generator.cpp
|
ffmpeg/ffmpeg_frame_generator.cpp
|
||||||
ffmpeg/ffmpeg_frame_generator.h
|
ffmpeg/ffmpeg_frame_generator.h
|
||||||
|
ffmpeg/ffmpeg_bytes_io_wrap.h
|
||||||
ffmpeg/ffmpeg_utility.cpp
|
ffmpeg/ffmpeg_utility.cpp
|
||||||
ffmpeg/ffmpeg_utility.h
|
ffmpeg/ffmpeg_utility.h
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue