Show option to download on streaming error.

This commit is contained in:
John Preston 2019-03-04 15:28:52 +04:00
parent 003d01206f
commit dafa286b18
17 changed files with 262 additions and 169 deletions

View File

@ -1492,6 +1492,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_player_message_today" = "Today at {time}";
"lng_player_message_yesterday" = "Yesterday at {time}";
"lng_player_message_date" = "{date} at {time}";
"lng_player_cant_play" = "This file can't be played in Telegram Desktop.\n\nWould you like to download it and open it in an external player?";
"lng_player_download" = "Download";
"lng_rights_edit_admin" = "Manage permissions";
"lng_rights_edit_admin_header" = "What can this admin do?";

View File

@ -300,6 +300,9 @@ void DocumentOpenClickHandler::Open(
Core::App().showDocument(data, context);
location.accessDisable();
return;
} else if (data->inappPlaybackFailed()) {
::Data::HandleUnsupportedMedia(data, msgId);
return;
} else if (data->canBePlayed()) {
if (data->isAudioFile() || data->isVoiceMessage()) {
Media::Player::instance()->playPause({ data, msgId });
@ -705,55 +708,32 @@ void DocumentData::performActionOnLoad() {
return;
}
auto loc = location(true);
auto already = loc.name();
auto item = _actionOnLoadMsgId.msg ? App::histItemById(_actionOnLoadMsgId) : nullptr;
auto showImage = !isVideoFile() && (size < App::kImageSizeLimit);
auto playVoice = isVoiceMessage() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen);
auto playMusic = isAudioFile() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen);
auto playAnimation = isAnimation()
&& (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen)
const auto loc = location(true);
const auto &already = loc.name();
const auto item = _actionOnLoadMsgId.msg
? App::histItemById(_actionOnLoadMsgId)
: nullptr;
const auto showImage = !isVideoFile() && (size < App::kImageSizeLimit);
const auto playVoice = isVoiceMessage();
const auto playMusic = isAudioFile();
const auto playAnimation = isAnimation()
&& loaded()
&& showImage
&& item;
if (auto applyTheme = isTheme()) {
if (isTheme()) {
if (!loc.isEmpty() && loc.accessEnable()) {
Core::App().showDocument(this, item);
loc.accessDisable();
return;
}
}
if (playVoice || playMusic) {
DocumentOpenClickHandler::Open({}, this, item, ActionOnLoadNone);
} else if (playAnimation) {
if (loaded()) {
if (_actionOnLoad == ActionOnLoadPlayInline && item) {
_owner->requestAnimationPlayInline(item);
} else {
Core::App().showDocument(this, item);
}
}
} else {
if (already.isEmpty()) return;
if (_actionOnLoad == ActionOnLoadOpenWith) {
} else if (_actionOnLoad == ActionOnLoadOpenWith) {
if (!already.isEmpty()) {
File::OpenWith(already, QCursor::pos());
} else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) {
if (isVoiceMessage() || isAudioFile() || isVideoFile()) {
if (Data::IsValidMediaFile(already)) {
File::Launch(already);
}
_owner->markMediaRead(this);
} else if (loc.accessEnable()) {
if (showImage && QImageReader(loc.name()).canRead()) {
Core::App().showDocument(this, item);
} else {
LaunchWithWarning(already, item);
}
loc.accessDisable();
} else {
LaunchWithWarning(already, item);
}
}
} else if (playVoice
|| playMusic
|| playAnimation
|| !already.isEmpty()) {
DocumentOpenClickHandler::Open({}, this, item, _actionOnLoad);
}
_actionOnLoad = ActionOnLoadNone;
}
@ -1222,6 +1202,14 @@ bool DocumentData::canBePlayed() const {
&& (loaded() || canBeStreamed());
}
void DocumentData::setInappPlaybackFailed() {
_inappPlaybackFailed = true;
}
bool DocumentData::inappPlaybackFailed() const {
return _inappPlaybackFailed;
}
auto DocumentData::createStreamingLoader(Data::FileOrigin origin) const
-> std::unique_ptr<Media::Streaming::Loader> {
// #TODO streaming create local file loader
@ -1596,4 +1584,28 @@ base::binary_guard ReadImageAsync(
return std::move(right);
}
void HandleUnsupportedMedia(
not_null<DocumentData*> document,
FullMsgId contextId) {
document->setInappPlaybackFailed();
auto filepath = document->filepath(
DocumentData::FilePathResolveSaveFromData);
if (filepath.isEmpty()) {
const auto save = [=] {
Ui::hideLayer();
DocumentSaveClickHandler::Save(
(contextId ? contextId : Data::FileOrigin()),
document,
App::histItemById(contextId));
};
Ui::show(Box<ConfirmBox>(
lang(lng_player_cant_play),
lang(lng_player_download),
lang(lng_cancel),
save));
} else if (IsValidMediaFile(filepath)) {
File::Launch(filepath);
}
}
} // namespace Data

View File

@ -228,6 +228,9 @@ public:
[[nodiscard]] auto createStreamingLoader(Data::FileOrigin origin) const
-> std::unique_ptr<Media::Streaming::Loader>;
void setInappPlaybackFailed();
[[nodiscard]] bool inappPlaybackFailed() const;
~DocumentData();
DocumentId id = 0;
@ -272,6 +275,7 @@ private:
int32 _duration = -1;
bool _isImage = false;
bool _supportsStreaming = false;
bool _inappPlaybackFailed = false;
ActionOnLoad _actionOnLoad = ActionOnLoadNone;
FullMsgId _actionOnLoadMsgId;
@ -393,4 +397,8 @@ base::binary_guard ReadImageAsync(
FnMut<QImage(QImage)> postprocess,
FnMut<void(QImage&&)> done);
void HandleUnsupportedMedia(
not_null<DocumentData*> document,
FullMsgId contextId);
} // namespace Data

View File

@ -389,11 +389,9 @@ MainWidget::MainWidget(
connect(_dialogs, SIGNAL(cancelled()), this, SLOT(dialogsCancelled()));
connect(this, SIGNAL(dialogsUpdated()), _dialogs, SLOT(onListScroll()));
connect(_history, SIGNAL(cancelled()), _dialogs, SLOT(activate()));
subscribe(Media::Player::Updated(), [this](const AudioMsgId &audioId) {
if (audioId.type() != AudioMsgId::Type::Video) {
handleAudioUpdate(audioId);
}
});
subscribe(
Media::Player::instance()->updatedNotifier(),
[=](const Media::Player::TrackState &state) { handleAudioUpdate(state); });
subscribe(session().calls().currentCallChanged(), [this](Calls::Call *call) { setCurrentCall(call); });
session().data().currentExportView(
@ -1173,29 +1171,17 @@ void MainWidget::messagesAffected(
}
}
void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) {
void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) {
using State = Media::Player::State;
const auto document = audioId.audio();
auto state = Media::Player::instance()->getState(audioId.type());
if (state.id == audioId && state.state == State::StoppedAtStart) {
state.state = State::Stopped;
Media::Player::mixer()->clearStoppedAtStart(audioId);
auto filepath = document->filepath(DocumentData::FilePathResolveSaveFromData);
if (!filepath.isEmpty()) {
if (Data::IsValidMediaFile(filepath)) {
File::Launch(filepath);
}
}
const auto document = state.id.audio();
if (!Media::Player::IsStoppedOrStopping(state.state)) {
createPlayer();
} else if (state.state == State::StoppedAtStart) {
Data::HandleUnsupportedMedia(document, state.id.contextId());
closeBothPlayers();
}
if (state.id == audioId) {
if (!Media::Player::IsStoppedOrStopping(state.state)) {
createPlayer();
}
}
if (const auto item = App::histItemById(audioId.contextId())) {
if (const auto item = App::histItemById(state.id.contextId())) {
session().data().requestItemRepaint(item);
}
if (const auto items = InlineBots::Layout::documentItems()) {

View File

@ -42,6 +42,7 @@ namespace Player {
class Widget;
class VolumeWidget;
class Panel;
struct TrackState;
} // namespace Player
} // namespace Media
@ -350,7 +351,7 @@ private:
void animationCallback();
void handleAdaptiveLayoutUpdate();
void updateWindowAdaptiveLayout();
void handleAudioUpdate(const AudioMsgId &audioId);
void handleAudioUpdate(const Media::Player::TrackState &state);
void updateMediaPlayerPosition();
void updateMediaPlaylistPosition(int x);
void updateControlsGeometry();

View File

@ -1247,14 +1247,6 @@ void Mixer::setStoppedState(Track *current, State state) {
}
}
void Mixer::clearStoppedAtStart(const AudioMsgId &audio) {
QMutexLocker lock(&AudioMutex);
auto track = trackForType(audio.type());
if (track && track->state.id == audio && track->state.state == State::StoppedAtStart) {
setStoppedState(track);
}
}
// Thread: Main. Must be locked: AudioMutex.
void Mixer::detachTracks() {
for (auto i = 0; i != kTogetherLimit; ++i) {

View File

@ -153,8 +153,6 @@ public:
TrackState currentState(AudioMsgId::Type type);
void clearStoppedAtStart(const AudioMsgId &audio);
// Thread: Main. Must be locked: AudioMutex.
void detachTracks();

View File

@ -327,7 +327,9 @@ void Instance::play(AudioMsgId::Type type) {
if (IsStopped(state.state)) {
play(state.id);
} else if (data->streamed) {
data->streamed->player.resume();
if (data->streamed->player.active()) {
data->streamed->player.resume();
}
emitUpdate(type);
} else {
mixer()->resume(state.id);
@ -414,7 +416,9 @@ Streaming::PlaybackOptions Instance::streamingOptions(
void Instance::pause(AudioMsgId::Type type) {
if (const auto data = getData(type)) {
if (data->streamed) {
data->streamed->player.pause();
if (data->streamed->player.active()) {
data->streamed->player.pause();
}
emitUpdate(type);
} else {
const auto state = getState(type);
@ -442,12 +446,9 @@ void Instance::stop(AudioMsgId::Type type) {
void Instance::playPause(AudioMsgId::Type type) {
if (const auto data = getData(type)) {
if (data->streamed) {
if (data->streamed->player.finished()
|| data->streamed->player.failed()) {
auto options = Streaming::PlaybackOptions();
options.mode = Streaming::Mode::Audio;
options.audioId = data->streamed->id;
data->streamed->player.play(options);
if (!data->streamed->player.active()) {
data->streamed->player.play(
streamingOptions(data->streamed->id));
} else if (data->streamed->player.paused()) {
data->streamed->player.resume();
} else {
@ -702,12 +703,19 @@ void Instance::handleStreamingUpdate(
};
finishTrack(data->streamed->info.audio.state);
emitUpdate(data->type);
if (data->streamed && data->streamed->player.finished()) {
data->streamed = nullptr;
}
});
}
void Instance::handleStreamingError(
not_null<Data*> data,
Streaming::Error &&error) {
emitUpdate(data->type);
if (data->streamed && data->streamed->player.failed()) {
data->streamed = nullptr;
}
}
} // namespace Player

View File

@ -98,7 +98,7 @@ void File::Context::logFatal(QLatin1String method, AvErrorWrap error) {
Stream File::Context::initStream(AVMediaType type) {
auto result = Stream();
const auto index = result.index = av_find_best_stream(
_formatContext,
_format.get(),
type,
-1,
-1,
@ -108,7 +108,7 @@ Stream File::Context::initStream(AVMediaType type) {
return {};
}
const auto info = _formatContext->streams[index];
const auto info = _format->streams[index];
if (type == AVMEDIA_TYPE_VIDEO) {
result.rotation = ReadRotationFromMetadata(info);
} else if (type == AVMEDIA_TYPE_AUDIO) {
@ -130,7 +130,7 @@ Stream File::Context::initStream(AVMediaType type) {
result.timeBase = info->time_base;
result.duration = (info->duration != AV_NOPTS_VALUE)
? PtsToTimeCeil(info->duration, result.timeBase)
: PtsToTimeCeil(_formatContext->duration, kUniversalTimeBase);
: PtsToTimeCeil(_format->duration, kUniversalTimeBase);
if (result.duration == kTimeUnknown || !result.duration) {
return {};
}
@ -151,7 +151,7 @@ void File::Context::seekToPosition(
//
//const auto seekFlags = 0;
//error = av_seek_frame(
// _formatContext,
// _format,
// streamIndex,
// TimeToPts(position, kUniversalTimeBase),
// seekFlags);
@ -160,7 +160,7 @@ void File::Context::seekToPosition(
//}
//
error = av_seek_frame(
_formatContext,
_format.get(),
stream.index,
TimeToPts(
std::clamp(position, crl::time(0), stream.duration - 1),
@ -176,7 +176,7 @@ base::variant<Packet, AvErrorWrap> File::Context::readPacket() {
auto error = AvErrorWrap();
auto result = Packet();
error = av_read_frame(_formatContext, &result.fields());
error = av_read_frame(_format.get(), &result.fields());
if (unroll()) {
return AvErrorWrap();
} else if (!error) {
@ -193,37 +193,16 @@ void File::Context::start(crl::time position) {
if (unroll()) {
return;
}
_ioBuffer = reinterpret_cast<uchar*>(av_malloc(AVBlockSize));
_ioContext = avio_alloc_context(
_ioBuffer,
AVBlockSize,
0,
static_cast<void*>(this),
_format = MakeFormatPointer(
static_cast<void *>(this),
&Context::Read,
nullptr,
&Context::Seek);
_formatContext = avformat_alloc_context();
if (!_formatContext) {
return logFatal(qstr("avformat_alloc_context"));
if (!_format) {
return fail();
}
_formatContext->pb = _ioContext;
auto options = (AVDictionary*)nullptr;
const auto guard = gsl::finally([&] { av_dict_free(&options); });
av_dict_set(&options, "usetoc", "1", 0);
error = avformat_open_input(
&_formatContext,
nullptr,
nullptr,
&options);
if (error) {
_ioBuffer = nullptr;
return logFatal(qstr("avformat_open_input"), error);
}
_opened = true;
_formatContext->flags |= AVFMT_FLAG_FAST_SEEK;
if ((error = avformat_find_stream_info(_formatContext, nullptr))) {
if ((error = avformat_find_stream_info(_format.get(), nullptr))) {
return logFatal(qstr("avformat_find_stream_info"), error);
}
@ -299,20 +278,7 @@ void File::Context::fail() {
_delegate->fileError();
}
File::Context::~Context() {
if (_opened) {
avformat_close_input(&_formatContext);
}
if (_ioContext) {
av_freep(&_ioContext->buffer);
av_freep(&_ioContext);
} else if (_ioBuffer) {
av_freep(&_ioBuffer);
}
if (_formatContext) {
avformat_free_context(_formatContext);
}
}
File::Context::~Context() = default;
bool File::Context::finished() const {
// #TODO streaming later looping
@ -348,6 +314,7 @@ void File::stop() {
_context->interrupt();
_thread.join();
}
_reader.stop();
_context.reset();
}

View File

@ -87,9 +87,7 @@ private:
crl::semaphore _semaphore;
std::atomic<bool> _interrupted = false;
uchar *_ioBuffer = nullptr;
AVIOContext *_ioContext = nullptr;
AVFormatContext *_formatContext = nullptr;
FormatPointer _format;
};

View File

@ -355,7 +355,7 @@ void Player::provideStartInformation() {
void Player::fail() {
_sessionLifetime = rpl::lifetime();
const auto stopGuarded = crl::guard(&_sessionGuard, [=] { stop(); });
_stage = Stage::Failed;
_lastFailureStage = _stage;
_updates.fire_error({});
stopGuarded();
}
@ -364,6 +364,7 @@ void Player::play(const PlaybackOptions &options) {
Expects(options.speed >= 0.5 && options.speed <= 2.);
stop();
_lastFailureStage = Stage::Uninitialized;
_options = options;
if (!Media::Audio::SupportsSpeedControl()) {
@ -374,14 +375,14 @@ void Player::play(const PlaybackOptions &options) {
}
void Player::pause() {
Expects(valid());
Expects(active());
_pausedByUser = true;
updatePausedState();
}
void Player::resume() {
Expects(valid());
Expects(active());
_pausedByUser = false;
updatePausedState();
@ -510,9 +511,7 @@ void Player::start() {
void Player::stop() {
_file->stop();
_sessionLifetime = rpl::lifetime();
if (_stage != Stage::Failed) {
_stage = Stage::Uninitialized;
}
_stage = Stage::Uninitialized;
_audio = nullptr;
_video = nullptr;
invalidate_weak_ptrs(&_sessionGuard);
@ -526,11 +525,14 @@ void Player::stop() {
}
bool Player::failed() const {
return (_stage == Stage::Failed);
return (_lastFailureStage != Stage::Uninitialized);
}
bool Player::playing() const {
return (_stage == Stage::Started) && !_paused && !finished();
return (_stage == Stage::Started)
&& !paused()
&& !finished()
&& !failed();
}
bool Player::buffering() const {
@ -548,7 +550,7 @@ bool Player::finished() const {
}
void Player::setSpeed(float64 speed) {
Expects(valid());
Expects(active());
Expects(speed >= 0.5 && speed <= 2.);
if (!Media::Audio::SupportsSpeedControl()) {
@ -565,12 +567,12 @@ void Player::setSpeed(float64 speed) {
}
}
bool Player::valid() const {
return (_stage != Stage::Uninitialized) && (_stage != Stage::Failed);
bool Player::active() const {
return (_stage != Stage::Uninitialized) && !finished() && !failed();
}
bool Player::ready() const {
return valid() && (_stage != Stage::Initializing);
return (_stage != Stage::Uninitialized) && (_stage != Stage::Initializing);
}
rpl::producer<Update, Error> Player::updates() const {
@ -588,7 +590,11 @@ Media::Player::TrackState Player::prepareLegacyState() const {
auto result = Media::Player::TrackState();
result.id = _audioId.externalPlayId() ? _audioId : _options.audioId;
result.state = finished()
result.state = (_lastFailureStage == Stage::Started)
? State::StoppedAtError
: failed()
? State::StoppedAtStart
: finished()
? State::StoppedAtEnd
: paused()
? State::Paused
@ -609,6 +615,8 @@ Media::Player::TrackState Player::prepareLegacyState() const {
: document->duration();
if (duration > 0) {
result.length = duration * crl::time(1000);
} else {
result.length = std::max(result.position, crl::time(0));
}
}
result.frequency = kMsFrequency;

View File

@ -44,10 +44,10 @@ public:
void resume();
void stop();
bool valid() const;
bool ready() const;
[[nodiscard]] bool active() const;
[[nodiscard]] bool ready() const;
float64 speed() const;
[[nodiscard]] float64 speed() const;
void setSpeed(float64 speed); // 0.5 <= speed <= 2.
[[nodiscard]] bool playing() const;
@ -72,7 +72,6 @@ private:
Initializing,
Ready,
Started,
Failed
};
// Thread-safe.
@ -143,6 +142,7 @@ private:
// Belongs to the main thread.
Information _information;
Stage _stage = Stage::Uninitialized;
Stage _lastFailureStage = Stage::Uninitialized;
bool _pausedByUser = false;
bool _pausedByWaitingForData = false;
bool _paused = false;

View File

@ -540,6 +540,10 @@ Reader::Reader(
}
}
void Reader::stop() {
_waiting = nullptr;
}
std::shared_ptr<Reader::CacheHelper> Reader::InitCacheHelper(
std::optional<Storage::Cache::Key> baseKey) {
if (!baseKey) {

View File

@ -39,6 +39,8 @@ public:
void headerDone();
void stop();
~Reader();
private:

View File

@ -22,6 +22,7 @@ constexpr auto kSkipInvalidDataPackets = 10;
constexpr auto kAlignImageBy = 16;
constexpr auto kPixelBytesSize = 4;
constexpr auto kImageFormat = QImage::Format_ARGB32_Premultiplied;
constexpr auto kAvioBlockSize = 4096;
void AlignedImageBufferCleanupHandler(void* data) {
const auto buffer = static_cast<uchar*>(data);
@ -68,6 +69,82 @@ QImage CreateFrameStorage(QSize size) {
cleanupData);
}
IOPointer MakeIOPointer(
void *opaque,
int(*read)(void *opaque, uint8_t *buffer, int bufferSize),
int(*write)(void *opaque, uint8_t *buffer, int bufferSize),
int64_t(*seek)(void *opaque, int64_t offset, int whence)) {
auto buffer = reinterpret_cast<uchar*>(av_malloc(kAvioBlockSize));
if (!buffer) {
LogError(qstr("av_malloc"));
return {};
}
auto result = IOPointer(avio_alloc_context(
buffer,
kAvioBlockSize,
write ? 1 : 0,
opaque,
read,
write,
seek));
if (!result) {
av_freep(&buffer);
LogError(qstr("avio_alloc_context"));
return {};
}
return result;
}
void IODeleter::operator()(AVIOContext *value) {
if (value) {
av_freep(&value->buffer);
avio_context_free(&value);
}
}
FormatPointer MakeFormatPointer(
void *opaque,
int(*read)(void *opaque, uint8_t *buffer, int bufferSize),
int(*write)(void *opaque, uint8_t *buffer, int bufferSize),
int64_t(*seek)(void *opaque, int64_t offset, int whence)) {
auto io = MakeIOPointer(opaque, read, write, seek);
if (!io) {
return {};
}
auto result = avformat_alloc_context();
if (!result) {
LogError(qstr("avformat_alloc_context"));
return {};
}
result->pb = io.get();
auto options = (AVDictionary*)nullptr;
const auto guard = gsl::finally([&] { av_dict_free(&options); });
av_dict_set(&options, "usetoc", "1", 0);
const auto error = AvErrorWrap(avformat_open_input(
&result,
nullptr,
nullptr,
&options));
if (error) {
// avformat_open_input freed 'result' in case an error happened.
LogError(qstr("avformat_open_input"), error);
return {};
}
result->flags |= AVFMT_FLAG_FAST_SEEK;
// Now FormatPointer will own and free the IO context.
io.release();
return FormatPointer(result);
}
void FormatDeleter::operator()(AVFormatContext *value) {
if (value) {
const auto deleter = IOPointer(value->pb);
avformat_close_input(&value);
}
}
CodecPointer MakeCodecPointer(not_null<AVStream*> stream) {
auto error = AvErrorWrap();
@ -120,10 +197,10 @@ void FrameDeleter::operator()(AVFrame *value) {
av_frame_free(&value);
}
SwsContextPointer MakeSwsContextPointer(
SwscalePointer MakeSwscalePointer(
not_null<AVFrame*> frame,
QSize resize,
SwsContextPointer *existing) {
SwscalePointer *existing) {
// We have to use custom caching for SwsContext, because
// sws_getCachedContext checks passed flags with existing context flags,
// and re-creates context if they're different, but in the process of
@ -139,7 +216,7 @@ SwsContextPointer MakeSwsContextPointer(
}
if (frame->format <= AV_PIX_FMT_NONE || frame->format >= AV_PIX_FMT_NB) {
LogError(qstr("frame->format"));
return SwsContextPointer();
return SwscalePointer();
}
const auto result = sws_getCachedContext(
@ -157,12 +234,12 @@ SwsContextPointer MakeSwsContextPointer(
if (!result) {
LogError(qstr("sws_getCachedContext"));
}
return SwsContextPointer(
return SwscalePointer(
result,
{ resize, QSize{ frame->width, frame->height }, frame->format });
}
void SwsContextDeleter::operator()(SwsContext *value) {
void SwscaleDeleter::operator()(SwsContext *value) {
if (value) {
sws_freeContext(value);
}
@ -341,11 +418,11 @@ QImage ConvertFrame(
from += deltaFrom;
}
} else {
stream.swsContext = MakeSwsContextPointer(
stream.swscale = MakeSwscalePointer(
frame,
resize,
&stream.swsContext);
if (!stream.swsContext) {
&stream.swscale);
if (!stream.swscale) {
return QImage();
}
@ -354,7 +431,7 @@ QImage ConvertFrame(
int linesize[AV_NUM_DATA_POINTERS] = { storage.bytesPerLine(), 0 };
const auto lines = sws_scale(
stream.swsContext.get(),
stream.swscale.get(),
frame->data,
frame->linesize,
0,

View File

@ -115,6 +115,26 @@ private:
};
struct IODeleter {
void operator()(AVIOContext *value);
};
using IOPointer = std::unique_ptr<AVIOContext, IODeleter>;
[[nodiscard]] IOPointer MakeIOPointer(
void *opaque,
int(*read)(void *opaque, uint8_t *buffer, int bufferSize),
int(*write)(void *opaque, uint8_t *buffer, int bufferSize),
int64_t(*seek)(void *opaque, int64_t offset, int whence));
struct FormatDeleter {
void operator()(AVFormatContext *value);
};
using FormatPointer = std::unique_ptr<AVFormatContext, FormatDeleter>;
[[nodiscard]] FormatPointer MakeFormatPointer(
void *opaque,
int(*read)(void *opaque, uint8_t *buffer, int bufferSize),
int(*write)(void *opaque, uint8_t *buffer, int bufferSize),
int64_t(*seek)(void *opaque, int64_t offset, int whence));
struct CodecDeleter {
void operator()(AVCodecContext *value);
};
@ -129,18 +149,18 @@ using FramePointer = std::unique_ptr<AVFrame, FrameDeleter>;
[[nodiscard]] bool FrameHasData(AVFrame *frame);
void ClearFrameMemory(AVFrame *frame);
struct SwsContextDeleter {
struct SwscaleDeleter {
QSize resize;
QSize frameSize;
int frameFormat = int(AV_PIX_FMT_NONE);
void operator()(SwsContext *value);
};
using SwsContextPointer = std::unique_ptr<SwsContext, SwsContextDeleter>;
[[nodiscard]] SwsContextPointer MakeSwsContextPointer(
using SwscalePointer = std::unique_ptr<SwsContext, SwscaleDeleter>;
[[nodiscard]] SwscalePointer MakeSwscalePointer(
not_null<AVFrame*> frame,
QSize resize,
SwsContextPointer *existing = nullptr);
SwscalePointer *existing = nullptr);
struct Stream {
int index = -1;
@ -156,7 +176,7 @@ struct Stream {
// Video only.
int rotation = 0;
SwsContextPointer swsContext;
SwscalePointer swscale;
};
void LogError(QLatin1String method);

View File

@ -1925,9 +1925,12 @@ void OverlayWidget::streamingReady(Streaming::Information &&info) {
validateStreamedGoodThumbnail();
if (videoShown()) {
const auto contentSize = ConvertScale(videoSize());
_w = contentSize.width();
_h = contentSize.height();
contentSizeChanged();
if (_w != contentSize.width() || _h != contentSize.height()) {
update(contentRect());
_w = contentSize.width();
_h = contentSize.height();
contentSizeChanged();
}
}
this->update(contentRect());
playbackWaitingChange(false);
@ -2014,6 +2017,7 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) {
void OverlayWidget::handleStreamingError(Streaming::Error &&error) {
playbackWaitingChange(false);
updatePlaybackState();
}
void OverlayWidget::playbackWaitingChange(bool waiting) {
@ -2124,7 +2128,13 @@ void OverlayWidget::refreshClipControllerGeometry() {
}
void OverlayWidget::playbackControlsPlay() {
playbackPauseResume();
const auto legacy = _streamed->player.prepareLegacyState();
if (legacy.state == Player::State::StoppedAtStart) {
Data::HandleUnsupportedMedia(_doc, _msgid);
close();
} else {
playbackPauseResume();
}
}
void OverlayWidget::playbackControlsPause() {