mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-01-12 10:10:00 +00:00
Report streaming failed.
This commit is contained in:
parent
71b733a018
commit
6887993f92
@ -1492,7 +1492,7 @@ 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_cant_stream" = "This file can't be played before it is fully downloaded.\n\nWould you like to download it?";
|
||||
"lng_player_download" = "Download";
|
||||
|
||||
"lng_rights_edit_admin" = "Manage permissions";
|
||||
|
@ -301,9 +301,6 @@ 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()
|
||||
@ -444,7 +441,7 @@ AuthSession &DocumentData::session() const {
|
||||
void DocumentData::setattributes(
|
||||
const QVector<MTPDocumentAttribute> &attributes) {
|
||||
_isImage = false;
|
||||
_supportsStreaming = false;
|
||||
_supportsStreaming = SupportsStreaming::Unknown;
|
||||
for (const auto &attribute : attributes) {
|
||||
attribute.match([&](const MTPDdocumentAttributeImageSize & data) {
|
||||
dimensions = QSize(data.vw.v, data.vh.v);
|
||||
@ -474,7 +471,7 @@ void DocumentData::setattributes(
|
||||
: VideoDocument;
|
||||
}
|
||||
_duration = data.vduration.v;
|
||||
_supportsStreaming = data.is_supports_streaming();
|
||||
setMaybeSupportsStreaming(data.is_supports_streaming());
|
||||
dimensions = QSize(data.vw.v, data.vh.v);
|
||||
}, [&](const MTPDdocumentAttributeAudio & data) {
|
||||
if (type == FileDocument) {
|
||||
@ -531,6 +528,11 @@ void DocumentData::setattributes(
|
||||
}
|
||||
}
|
||||
validateGoodThumbnail();
|
||||
if (isAudioFile()
|
||||
|| (isAnimation() && !isVideoMessage())
|
||||
|| isVoiceMessage()) {
|
||||
setMaybeSupportsStreaming(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool DocumentData::checkWallPaperProperties() {
|
||||
@ -1192,16 +1194,15 @@ bool DocumentData::hasRemoteLocation() const {
|
||||
}
|
||||
|
||||
bool DocumentData::canBeStreamed() const {
|
||||
return hasRemoteLocation()
|
||||
&& (isAudioFile()
|
||||
|| ((isAnimation() || isVideoFile()) && supportsStreaming()));
|
||||
return hasRemoteLocation() && supportsStreaming();
|
||||
}
|
||||
|
||||
bool DocumentData::canBePlayed() const {
|
||||
return (isAnimation()
|
||||
|| isVideoFile()
|
||||
|| isAudioFile()
|
||||
|| isVoiceMessage())
|
||||
return !_inappPlaybackFailed
|
||||
&& (isAnimation()
|
||||
|| isVideoFile()
|
||||
|| isAudioFile()
|
||||
|| isVoiceMessage())
|
||||
&& (loaded() || canBeStreamed());
|
||||
}
|
||||
|
||||
@ -1405,7 +1406,20 @@ bool DocumentData::isImage() const {
|
||||
}
|
||||
|
||||
bool DocumentData::supportsStreaming() const {
|
||||
return _supportsStreaming;
|
||||
return (_supportsStreaming == SupportsStreaming::MaybeYes);
|
||||
}
|
||||
|
||||
void DocumentData::setNotSupportsStreaming() {
|
||||
_supportsStreaming = SupportsStreaming::No;
|
||||
}
|
||||
|
||||
void DocumentData::setMaybeSupportsStreaming(bool supports) {
|
||||
if (_supportsStreaming == SupportsStreaming::No) {
|
||||
return;
|
||||
}
|
||||
_supportsStreaming = supports
|
||||
? SupportsStreaming::MaybeYes
|
||||
: SupportsStreaming::MaybeNo;
|
||||
}
|
||||
|
||||
void DocumentData::recountIsImage() {
|
||||
@ -1587,28 +1601,30 @@ base::binary_guard ReadImageAsync(
|
||||
return std::move(right);
|
||||
}
|
||||
|
||||
void HandleUnsupportedMedia(
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId contextId) {
|
||||
document->setInappPlaybackFailed();
|
||||
const 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);
|
||||
}
|
||||
}
|
||||
//void HandleUnsupportedMedia(
|
||||
// not_null<DocumentData*> document,
|
||||
// FullMsgId contextId) {
|
||||
// using Error = ::Media::Streaming::Error;
|
||||
//
|
||||
// document->setInappPlaybackFailed();
|
||||
// const 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_stream),
|
||||
// lang(lng_player_download),
|
||||
// lang(lng_cancel),
|
||||
// save));
|
||||
// } else if (IsValidMediaFile(filepath)) {
|
||||
// File::Launch(filepath);
|
||||
// }
|
||||
//}
|
||||
|
||||
} // namespace Data
|
||||
|
@ -167,6 +167,7 @@ public:
|
||||
[[nodiscard]] bool isImage() const;
|
||||
void recountIsImage();
|
||||
[[nodiscard]] bool supportsStreaming() const;
|
||||
void setNotSupportsStreaming();
|
||||
void setData(const QByteArray &data) {
|
||||
_data = data;
|
||||
}
|
||||
@ -244,10 +245,17 @@ public:
|
||||
std::unique_ptr<Data::UploadState> uploadingData;
|
||||
|
||||
private:
|
||||
enum class SupportsStreaming : uchar {
|
||||
Unknown,
|
||||
MaybeYes,
|
||||
MaybeNo,
|
||||
No,
|
||||
};
|
||||
friend class Serialize::Document;
|
||||
|
||||
LocationType locationType() const;
|
||||
void validateGoodThumbnail();
|
||||
void setMaybeSupportsStreaming(bool supports);
|
||||
|
||||
void destroyLoader(mtpFileLoader *newValue = nullptr) const;
|
||||
|
||||
@ -274,7 +282,7 @@ private:
|
||||
std::unique_ptr<DocumentAdditionalData> _additional;
|
||||
int32 _duration = -1;
|
||||
bool _isImage = false;
|
||||
bool _supportsStreaming = false;
|
||||
SupportsStreaming _supportsStreaming = SupportsStreaming::Unknown;
|
||||
bool _inappPlaybackFailed = false;
|
||||
|
||||
ActionOnLoad _actionOnLoad = ActionOnLoadNone;
|
||||
@ -397,8 +405,4 @@ base::binary_guard ReadImageAsync(
|
||||
FnMut<QImage(QImage)> postprocess,
|
||||
FnMut<void(QImage&&)> done);
|
||||
|
||||
void HandleUnsupportedMedia(
|
||||
not_null<DocumentData*> document,
|
||||
FullMsgId contextId);
|
||||
|
||||
} // namespace Data
|
||||
|
@ -1177,7 +1177,6 @@ void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) {
|
||||
if (!Media::Player::IsStoppedOrStopping(state.state)) {
|
||||
createPlayer();
|
||||
} else if (state.state == State::StoppedAtStart) {
|
||||
Data::HandleUnsupportedMedia(document, state.id.contextId());
|
||||
closeBothPlayers();
|
||||
}
|
||||
|
||||
|
@ -134,19 +134,18 @@ void Instance::setCurrent(const AudioMsgId &audioId) {
|
||||
if (data->current == audioId) {
|
||||
return;
|
||||
}
|
||||
const auto trackChanged = (data->current.audio() != audioId.audio())
|
||||
|| (data->current.contextId() != audioId.contextId());
|
||||
const auto changed = [&](const AudioMsgId & check) {
|
||||
return (check.audio() != audioId.audio())
|
||||
|| (check.contextId() != audioId.contextId());
|
||||
};
|
||||
const auto trackChanged = changed(data->current);
|
||||
if (trackChanged && data->streamed && changed(data->streamed->id)) {
|
||||
clearStreamed(data);
|
||||
}
|
||||
data->current = audioId;
|
||||
if (!trackChanged) {
|
||||
return;
|
||||
}
|
||||
const auto streamedId = data->streamed
|
||||
? data->streamed->id
|
||||
: AudioMsgId();
|
||||
if (streamedId.audio() != audioId.audio()
|
||||
|| streamedId.contextId() != audioId.contextId()) {
|
||||
data->streamed = nullptr;
|
||||
}
|
||||
data->current = audioId;
|
||||
data->isPlaying = false;
|
||||
|
||||
@ -165,6 +164,16 @@ void Instance::setCurrent(const AudioMsgId &audioId) {
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::clearStreamed(not_null<Data*> data) {
|
||||
if (!data->streamed) {
|
||||
return;
|
||||
}
|
||||
data->streamed->player.stop();
|
||||
data->isPlaying = false;
|
||||
emitUpdate(data->type);
|
||||
data->streamed = nullptr;
|
||||
}
|
||||
|
||||
void Instance::refreshPlaylist(not_null<Data*> data) {
|
||||
if (!validPlaylist(data)) {
|
||||
validatePlaylist(data);
|
||||
@ -386,6 +395,9 @@ void Instance::playStreamed(
|
||||
|
||||
const auto data = getData(audioId.type());
|
||||
Assert(data != nullptr);
|
||||
if (data->streamed) {
|
||||
clearStreamed(data);
|
||||
}
|
||||
data->streamed = std::make_unique<Streamed>(
|
||||
audioId,
|
||||
&audioId.audio()->owner(),
|
||||
@ -607,38 +619,10 @@ void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) {
|
||||
_tracksFinishedNotifier.notify(type);
|
||||
}
|
||||
}
|
||||
auto isPlaying = !IsStopped(state.state);
|
||||
if (data->isPlaying != isPlaying) {
|
||||
data->isPlaying = isPlaying;
|
||||
if (data->isPlaying) {
|
||||
preloadNext(data);
|
||||
}
|
||||
}
|
||||
data->isPlaying = !IsStopped(state.state);
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::preloadNext(not_null<Data*> data) {
|
||||
//if (!data->current || !data->playlistSlice || !data->playlistIndex) {
|
||||
// return;
|
||||
//}
|
||||
//const auto nextIndex = *data->playlistIndex + 1;
|
||||
//if (const auto item = itemByIndex(data, nextIndex)) {
|
||||
// if (const auto media = item->media()) {
|
||||
// if (const auto document = media->document()) {
|
||||
// const auto isLoaded = document->loaded(
|
||||
// DocumentData::FilePathResolveSaveFromDataSilent);
|
||||
// if (!isLoaded) {
|
||||
// DocumentOpenClickHandler::Open(
|
||||
// item->fullId(),
|
||||
// document,
|
||||
// item,
|
||||
// ActionOnLoadNone);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
void Instance::handleLogout() {
|
||||
const auto reset = [&](AudioMsgId::Type type) {
|
||||
const auto data = getData(type);
|
||||
@ -712,6 +696,23 @@ void Instance::handleStreamingUpdate(
|
||||
void Instance::handleStreamingError(
|
||||
not_null<Data*> data,
|
||||
Streaming::Error &&error) {
|
||||
Expects(data->streamed != nullptr);
|
||||
|
||||
const auto document = data->streamed->id.audio();
|
||||
const auto contextId = data->streamed->id.contextId();
|
||||
if (error == Streaming::Error::NotStreamable) {
|
||||
document->setNotSupportsStreaming();
|
||||
DocumentSaveClickHandler::Save(
|
||||
(contextId ? contextId : ::Data::FileOrigin()),
|
||||
document,
|
||||
App::histItemById(contextId));
|
||||
} else if (error == Streaming::Error::OpenFailed) {
|
||||
document->setInappPlaybackFailed();
|
||||
DocumentSaveClickHandler::Save(
|
||||
(contextId ? contextId : ::Data::FileOrigin()),
|
||||
document,
|
||||
App::histItemById(contextId));
|
||||
}
|
||||
emitUpdate(data->type);
|
||||
if (data->streamed && data->streamed->player.failed()) {
|
||||
data->streamed = nullptr;
|
||||
|
@ -19,7 +19,7 @@ namespace Streaming {
|
||||
class Loader;
|
||||
struct PlaybackOptions;
|
||||
struct Update;
|
||||
struct Error;
|
||||
enum class Error;
|
||||
} // namespace Streaming
|
||||
} // namespace Media
|
||||
|
||||
@ -188,7 +188,6 @@ private:
|
||||
void validatePlaylist(not_null<Data*> data);
|
||||
void playlistUpdated(not_null<Data*> data);
|
||||
bool moveInPlaylist(not_null<Data*> data, int delta, bool autonext);
|
||||
void preloadNext(not_null<Data*> data);
|
||||
HistoryItem *itemByIndex(not_null<Data*> data, int index);
|
||||
|
||||
void handleStreamingUpdate(
|
||||
@ -198,6 +197,7 @@ private:
|
||||
not_null<Data*> data,
|
||||
Streaming::Error &&error);
|
||||
|
||||
void clearStreamed(not_null<Data *> data);
|
||||
void emitUpdate(AudioMsgId::Type type);
|
||||
template <typename CheckCallback>
|
||||
void emitUpdate(AudioMsgId::Type type, CheckCallback check);
|
||||
|
@ -20,7 +20,7 @@ AudioTrack::AudioTrack(
|
||||
Stream &&stream,
|
||||
AudioMsgId audioId,
|
||||
FnMut<void(const Information &)> ready,
|
||||
Fn<void()> error)
|
||||
Fn<void(Error)> error)
|
||||
: _options(options)
|
||||
, _stream(std::move(stream))
|
||||
, _audioId(audioId)
|
||||
@ -51,7 +51,7 @@ void AudioTrack::process(Packet &&packet) {
|
||||
if (initialized()) {
|
||||
mixerEnqueue(std::move(packet));
|
||||
} else if (!tryReadFirstFrame(std::move(packet))) {
|
||||
_error();
|
||||
_error(Error::InvalidData);
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,7 +188,7 @@ rpl::producer<crl::time> AudioTrack::playPosition() {
|
||||
return;
|
||||
case State::StoppedAtError:
|
||||
case State::StoppedAtStart:
|
||||
_error();
|
||||
_error(Error::InvalidData);
|
||||
return;
|
||||
case State::Starting:
|
||||
case State::Playing:
|
||||
|
@ -21,7 +21,7 @@ public:
|
||||
Stream &&stream,
|
||||
AudioMsgId audioId,
|
||||
FnMut<void(const Information &)> ready,
|
||||
Fn<void()> error);
|
||||
Fn<void(Error)> error);
|
||||
|
||||
// Called from the main thread.
|
||||
// Must be called after 'ready' was invoked.
|
||||
@ -69,7 +69,7 @@ private:
|
||||
|
||||
// Assumed to be thread-safe.
|
||||
FnMut<void(const Information &)> _ready;
|
||||
const Fn<void()> _error;
|
||||
const Fn<void(Error)> _error;
|
||||
|
||||
// First set from the same unspecified thread before _ready is called.
|
||||
// After that is immutable.
|
||||
|
@ -100,7 +100,11 @@ struct Update {
|
||||
Finished> data;
|
||||
};
|
||||
|
||||
struct Error {
|
||||
enum class Error {
|
||||
OpenFailed,
|
||||
LoadFailed,
|
||||
InvalidData,
|
||||
NotStreamable,
|
||||
};
|
||||
|
||||
struct FrameRequest {
|
||||
|
@ -44,8 +44,8 @@ int File::Context::read(bytes::span buffer) {
|
||||
_semaphore.acquire();
|
||||
if (_interrupted) {
|
||||
return -1;
|
||||
} else if (_reader->failed()) {
|
||||
fail();
|
||||
} else if (const auto error = _reader->failed()) {
|
||||
fail(*error);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@ -84,21 +84,23 @@ void File::Context::logError(QLatin1String method, AvErrorWrap error) {
|
||||
void File::Context::logFatal(QLatin1String method) {
|
||||
if (!unroll()) {
|
||||
LogError(method);
|
||||
fail();
|
||||
fail(_format ? Error::InvalidData : Error::OpenFailed);
|
||||
}
|
||||
}
|
||||
|
||||
void File::Context::logFatal(QLatin1String method, AvErrorWrap error) {
|
||||
if (!unroll()) {
|
||||
LogError(method, error);
|
||||
fail();
|
||||
fail(_format ? Error::InvalidData : Error::OpenFailed);
|
||||
}
|
||||
}
|
||||
|
||||
Stream File::Context::initStream(AVMediaType type) {
|
||||
Stream File::Context::initStream(
|
||||
not_null<AVFormatContext*> format,
|
||||
AVMediaType type) {
|
||||
auto result = Stream();
|
||||
const auto index = result.index = av_find_best_stream(
|
||||
_format.get(),
|
||||
format,
|
||||
type,
|
||||
-1,
|
||||
-1,
|
||||
@ -108,7 +110,7 @@ Stream File::Context::initStream(AVMediaType type) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto info = _format->streams[index];
|
||||
const auto info = format->streams[index];
|
||||
if (type == AVMEDIA_TYPE_VIDEO) {
|
||||
result.rotation = ReadRotationFromMetadata(info);
|
||||
result.aspect = ValidateAspectRatio(info->sample_aspect_ratio);
|
||||
@ -131,7 +133,7 @@ Stream File::Context::initStream(AVMediaType type) {
|
||||
result.timeBase = info->time_base;
|
||||
result.duration = (info->duration != AV_NOPTS_VALUE)
|
||||
? PtsToTime(info->duration, result.timeBase)
|
||||
: PtsToTime(_format->duration, kUniversalTimeBase);
|
||||
: PtsToTime(format->duration, kUniversalTimeBase);
|
||||
if (result.duration == kTimeUnknown || !result.duration) {
|
||||
return {};
|
||||
}
|
||||
@ -142,6 +144,7 @@ Stream File::Context::initStream(AVMediaType type) {
|
||||
}
|
||||
|
||||
void File::Context::seekToPosition(
|
||||
not_null<AVFormatContext*> format,
|
||||
const Stream &stream,
|
||||
crl::time position) {
|
||||
auto error = AvErrorWrap();
|
||||
@ -155,7 +158,7 @@ void File::Context::seekToPosition(
|
||||
//
|
||||
//const auto seekFlags = 0;
|
||||
//error = av_seek_frame(
|
||||
// _format,
|
||||
// format,
|
||||
// streamIndex,
|
||||
// TimeToPts(position, kUniversalTimeBase),
|
||||
// seekFlags);
|
||||
@ -164,7 +167,7 @@ void File::Context::seekToPosition(
|
||||
//}
|
||||
//
|
||||
error = av_seek_frame(
|
||||
_format.get(),
|
||||
format,
|
||||
stream.index,
|
||||
TimeToPts(
|
||||
std::clamp(position, crl::time(0), stream.duration - 1),
|
||||
@ -197,43 +200,41 @@ void File::Context::start(crl::time position) {
|
||||
if (unroll()) {
|
||||
return;
|
||||
}
|
||||
_format = MakeFormatPointer(
|
||||
auto format = MakeFormatPointer(
|
||||
static_cast<void *>(this),
|
||||
&Context::Read,
|
||||
nullptr,
|
||||
&Context::Seek);
|
||||
if (!_format) {
|
||||
return fail();
|
||||
if (!format) {
|
||||
return fail(Error::OpenFailed);
|
||||
}
|
||||
|
||||
if ((error = avformat_find_stream_info(_format.get(), nullptr))) {
|
||||
if ((error = avformat_find_stream_info(format.get(), nullptr))) {
|
||||
return logFatal(qstr("avformat_find_stream_info"), error);
|
||||
}
|
||||
|
||||
auto video = initStream(AVMEDIA_TYPE_VIDEO);
|
||||
auto video = initStream(format.get(), AVMEDIA_TYPE_VIDEO);
|
||||
if (unroll()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto audio = initStream(AVMEDIA_TYPE_AUDIO);
|
||||
auto audio = initStream(format.get(), AVMEDIA_TYPE_AUDIO);
|
||||
if (unroll()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_reader->headerDone();
|
||||
_totalDuration = std::max(
|
||||
video.codec ? video.duration : kTimeUnknown,
|
||||
audio.codec ? audio.duration : kTimeUnknown);
|
||||
if (video.codec || audio.codec) {
|
||||
seekToPosition(video.codec ? video : audio, position);
|
||||
seekToPosition(format.get(), video.codec ? video : audio, position);
|
||||
}
|
||||
if (unroll()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_delegate->fileReady(std::move(video), std::move(audio))) {
|
||||
return fail();
|
||||
return fail(Error::OpenFailed);
|
||||
}
|
||||
_format = std::move(format);
|
||||
}
|
||||
|
||||
void File::Context::readNextPacket() {
|
||||
@ -293,15 +294,14 @@ bool File::Context::unroll() const {
|
||||
return failed() || interrupted();
|
||||
}
|
||||
|
||||
void File::Context::fail() {
|
||||
void File::Context::fail(Error error) {
|
||||
_failed = true;
|
||||
_delegate->fileError();
|
||||
_delegate->fileError(error);
|
||||
}
|
||||
|
||||
File::Context::~Context() = default;
|
||||
|
||||
bool File::Context::finished() const {
|
||||
// #TODO streaming later looping
|
||||
return unroll() || _readTillEnd;
|
||||
}
|
||||
|
||||
@ -338,6 +338,10 @@ void File::stop() {
|
||||
_context.reset();
|
||||
}
|
||||
|
||||
bool File::isRemoteLoader() const {
|
||||
return _reader.isRemoteLoader();
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
stop();
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ public:
|
||||
void wake();
|
||||
void stop();
|
||||
|
||||
[[nodiscard]] bool isRemoteLoader() const;
|
||||
|
||||
~File();
|
||||
|
||||
private:
|
||||
@ -66,10 +68,15 @@ private:
|
||||
void logError(QLatin1String method, AvErrorWrap error);
|
||||
void logFatal(QLatin1String method);
|
||||
void logFatal(QLatin1String method, AvErrorWrap error);
|
||||
void fail();
|
||||
void fail(Error error);
|
||||
|
||||
Stream initStream(AVMediaType type);
|
||||
void seekToPosition(const Stream &stream, crl::time position);
|
||||
Stream initStream(
|
||||
not_null<AVFormatContext *> format,
|
||||
AVMediaType type);
|
||||
void seekToPosition(
|
||||
not_null<AVFormatContext *> format,
|
||||
const Stream &stream,
|
||||
crl::time position);
|
||||
|
||||
// TODO base::expected.
|
||||
[[nodiscard]] base::variant<Packet, AvErrorWrap> readPacket();
|
||||
@ -88,7 +95,6 @@ private:
|
||||
std::atomic<bool> _interrupted = false;
|
||||
|
||||
FormatPointer _format;
|
||||
crl::time _totalDuration = kTimeUnknown;
|
||||
|
||||
};
|
||||
|
||||
|
@ -12,13 +12,14 @@ namespace Streaming {
|
||||
|
||||
struct Stream;
|
||||
class Packet;
|
||||
enum class Error;
|
||||
|
||||
class FileDelegate {
|
||||
public:
|
||||
[[nodiscard]] virtual bool fileReady(
|
||||
Stream &&video,
|
||||
Stream &&audio) = 0;
|
||||
virtual void fileError() = 0;
|
||||
virtual void fileError(Error error) = 0;
|
||||
virtual void fileWaitingForData() = 0;
|
||||
|
||||
// Return true if reading and processing more packets is desired.
|
||||
|
@ -19,7 +19,8 @@ namespace Streaming {
|
||||
namespace {
|
||||
|
||||
constexpr auto kBufferFor = 3 * crl::time(1000);
|
||||
constexpr auto kLoadInAdvanceFor = 64 * crl::time(1000);
|
||||
constexpr auto kLoadInAdvanceForRemote = 64 * crl::time(1000);
|
||||
constexpr auto kLoadInAdvanceForLocal = 5 * crl::time(1000);
|
||||
constexpr auto kMsFrequency = 1000; // 1000 ms per second.
|
||||
|
||||
// If we played for 3 seconds and got stuck it looks like we're loading
|
||||
@ -79,6 +80,7 @@ Player::Player(
|
||||
not_null<Data::Session*> owner,
|
||||
std::unique_ptr<Loader> loader)
|
||||
: _file(std::make_unique<File>(owner, std::move(loader)))
|
||||
, _remoteLoader(_file->isRemoteLoader())
|
||||
, _renderFrameTimer([=] { checkNextFrame(); }) {
|
||||
}
|
||||
|
||||
@ -123,7 +125,7 @@ void Player::trackReceivedTill(
|
||||
state.receivedTill = position;
|
||||
}
|
||||
if (!_pauseReading
|
||||
&& bothReceivedEnough(kLoadInAdvanceFor)
|
||||
&& bothReceivedEnough(loadInAdvanceFor())
|
||||
&& !receivedTillEnd()) {
|
||||
_pauseReading = true;
|
||||
}
|
||||
@ -145,7 +147,7 @@ void Player::trackPlayedTill(
|
||||
_updates.fire({ PlaybackUpdate<Track>{ value } });
|
||||
}
|
||||
if (_pauseReading
|
||||
&& (!bothReceivedEnough(kLoadInAdvanceFor) || receivedTillEnd())) {
|
||||
&& (!bothReceivedEnough(loadInAdvanceFor()) || receivedTillEnd())) {
|
||||
_pauseReading = false;
|
||||
_file->wake();
|
||||
++wakes;
|
||||
@ -204,10 +206,10 @@ bool Player::fileReady(Stream &&video, Stream &&audio) {
|
||||
});
|
||||
};
|
||||
const auto error = [&](auto &stream) {
|
||||
return [=, &stream] {
|
||||
return [=, &stream](Error error) {
|
||||
crl::on_main(weak, [=, &stream] {
|
||||
stream = nullptr;
|
||||
streamFailed();
|
||||
streamFailed(error);
|
||||
});
|
||||
};
|
||||
};
|
||||
@ -253,11 +255,11 @@ bool Player::fileReady(Stream &&video, Stream &&audio) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Player::fileError() {
|
||||
void Player::fileError(Error error) {
|
||||
_waitingForData = false;
|
||||
|
||||
crl::on_main(&_sessionGuard, [=] {
|
||||
fail();
|
||||
fail(error);
|
||||
});
|
||||
}
|
||||
|
||||
@ -331,11 +333,11 @@ void Player::streamReady(Information &&information) {
|
||||
provideStartInformation();
|
||||
}
|
||||
|
||||
void Player::streamFailed() {
|
||||
void Player::streamFailed(Error error) {
|
||||
if (_stage == Stage::Initializing) {
|
||||
provideStartInformation();
|
||||
} else {
|
||||
fail();
|
||||
fail(error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -348,7 +350,7 @@ void Player::provideStartInformation() {
|
||||
} else if ((!_audio && !_video)
|
||||
|| (!_audio && _options.mode == Mode::Audio)
|
||||
|| (!_video && _options.mode == Mode::Video)) {
|
||||
fail();
|
||||
fail(Error::OpenFailed);
|
||||
} else {
|
||||
_stage = Stage::Ready;
|
||||
|
||||
@ -365,11 +367,11 @@ void Player::provideStartInformation() {
|
||||
}
|
||||
}
|
||||
|
||||
void Player::fail() {
|
||||
void Player::fail(Error error) {
|
||||
_sessionLifetime = rpl::lifetime();
|
||||
const auto stopGuarded = crl::guard(&_sessionGuard, [=] { stop(); });
|
||||
_lastFailureStage = _stage;
|
||||
_updates.fire_error({});
|
||||
_lastFailure = error;
|
||||
_updates.fire_error(std::move(error));
|
||||
stopGuarded();
|
||||
}
|
||||
|
||||
@ -382,7 +384,7 @@ void Player::play(const PlaybackOptions &options) {
|
||||
const auto previous = getCurrentReceivedTill();
|
||||
|
||||
stop();
|
||||
_lastFailureStage = Stage::Uninitialized;
|
||||
_lastFailure = std::nullopt;
|
||||
|
||||
savePreviousReceivedTill(options, previous);
|
||||
_options = options;
|
||||
@ -404,6 +406,10 @@ void Player::savePreviousReceivedTill(
|
||||
: kTimeUnknown;
|
||||
}
|
||||
|
||||
crl::time Player::loadInAdvanceFor() const {
|
||||
return _remoteLoader ? kLoadInAdvanceForRemote : kLoadInAdvanceForLocal;
|
||||
}
|
||||
|
||||
void Player::pause() {
|
||||
Expects(active());
|
||||
|
||||
@ -560,8 +566,8 @@ void Player::stop() {
|
||||
_information = Information();
|
||||
}
|
||||
|
||||
bool Player::failed() const {
|
||||
return (_lastFailureStage != Stage::Uninitialized);
|
||||
std::optional<Error> Player::failed() const {
|
||||
return _lastFailure;
|
||||
}
|
||||
|
||||
bool Player::playing() const {
|
||||
@ -626,10 +632,11 @@ Media::Player::TrackState Player::prepareLegacyState() const {
|
||||
|
||||
auto result = Media::Player::TrackState();
|
||||
result.id = _audioId.externalPlayId() ? _audioId : _options.audioId;
|
||||
result.state = (_lastFailureStage == Stage::Started)
|
||||
? State::StoppedAtError
|
||||
: failed()
|
||||
result.state = (_lastFailure == Error::OpenFailed
|
||||
|| _lastFailure == Error::NotStreamable)
|
||||
? State::StoppedAtStart
|
||||
: _lastFailure
|
||||
? State::StoppedAtError
|
||||
: finished()
|
||||
? State::StoppedAtEnd
|
||||
: paused()
|
||||
|
@ -53,7 +53,7 @@ public:
|
||||
[[nodiscard]] bool playing() const;
|
||||
[[nodiscard]] bool buffering() const;
|
||||
[[nodiscard]] bool paused() const;
|
||||
[[nodiscard]] bool failed() const;
|
||||
[[nodiscard]] std::optional<Error> failed() const;
|
||||
[[nodiscard]] bool finished() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<Update, Error> updates() const;
|
||||
@ -80,17 +80,17 @@ private:
|
||||
|
||||
// FileDelegate methods are called only from the File thread.
|
||||
bool fileReady(Stream &&video, Stream &&audio) override;
|
||||
void fileError() override;
|
||||
void fileError(Error error) override;
|
||||
void fileWaitingForData() override;
|
||||
bool fileProcessPacket(Packet &&packet) override;
|
||||
bool fileReadMore() override;
|
||||
|
||||
// Called from the main thread.
|
||||
void streamReady(Information &&information);
|
||||
void streamFailed();
|
||||
void streamFailed(Error error);
|
||||
void start();
|
||||
void provideStartInformation();
|
||||
void fail();
|
||||
void fail(Error error);
|
||||
void checkNextFrame();
|
||||
void renderFrame(crl::time now);
|
||||
void audioReceivedTill(crl::time position);
|
||||
@ -109,6 +109,7 @@ private:
|
||||
void savePreviousReceivedTill(
|
||||
const PlaybackOptions &options,
|
||||
crl::time previousReceivedTill);
|
||||
[[nodiscard]] crl::time loadInAdvanceFor() const;
|
||||
|
||||
template <typename Track>
|
||||
void trackReceivedTill(
|
||||
@ -150,12 +151,13 @@ private:
|
||||
// Belongs to the main thread.
|
||||
Information _information;
|
||||
Stage _stage = Stage::Uninitialized;
|
||||
Stage _lastFailureStage = Stage::Uninitialized;
|
||||
std::optional<Error> _lastFailure;
|
||||
bool _pausedByUser = false;
|
||||
bool _pausedByWaitingForData = false;
|
||||
bool _paused = false;
|
||||
bool _audioFinished = false;
|
||||
bool _videoFinished = false;
|
||||
bool _remoteLoader = false;
|
||||
|
||||
crl::time _startedTime = kTimeUnknown;
|
||||
crl::time _pausedTime = kTimeUnknown;
|
||||
@ -163,7 +165,7 @@ private:
|
||||
base::Timer _renderFrameTimer;
|
||||
rpl::event_stream<Update, Error> _updates;
|
||||
|
||||
crl::time _totalDuration = 0;
|
||||
crl::time _totalDuration = kTimeUnknown;
|
||||
crl::time _loopingShift = 0;
|
||||
crl::time _previousReceivedTill = kTimeUnknown;
|
||||
|
||||
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "media/streaming/media_streaming_reader.h"
|
||||
|
||||
#include "media/streaming/media_streaming_common.h"
|
||||
#include "media/streaming/media_streaming_loader.h"
|
||||
#include "storage/cache/storage_cache_database.h"
|
||||
#include "data/data_session.h"
|
||||
@ -18,11 +19,14 @@ namespace {
|
||||
constexpr auto kPartSize = Loader::kPartSize;
|
||||
constexpr auto kPartsInSlice = 64;
|
||||
constexpr auto kInSlice = kPartsInSlice * kPartSize;
|
||||
constexpr auto kMaxPartsInHeader = 72;
|
||||
constexpr auto kMaxInHeader = kMaxPartsInHeader * kPartSize;
|
||||
constexpr auto kMaxPartsInHeader = 16;
|
||||
constexpr auto kMaxOnlyInHeader = 80 * kPartSize;
|
||||
constexpr auto kSlicesInMemory = 2;
|
||||
|
||||
// 1 MB of header parts can be outside the first slice for us to still
|
||||
// put the whole first slice of the file in the header cache entry.
|
||||
//constexpr auto kMaxOutsideHeaderPartsForOptimizedMode = 8;
|
||||
|
||||
// 1 MB of parts are requested from cloud ahead of reading demand.
|
||||
constexpr auto kPreloadPartsAhead = 8;
|
||||
|
||||
@ -286,6 +290,11 @@ void Reader::Slices::headerDone(bool fromCache) {
|
||||
}
|
||||
}
|
||||
|
||||
bool Reader::Slices::headerWontBeFilled() const {
|
||||
return (_headerMode == HeaderMode::Unknown)
|
||||
&& (_header.parts.size() == kMaxPartsInHeader);
|
||||
}
|
||||
|
||||
void Reader::Slices::applyHeaderCacheData() {
|
||||
if (_header.parts.empty()) {
|
||||
return;
|
||||
@ -318,21 +327,21 @@ bool Reader::Slices::processCacheResult(
|
||||
return success;
|
||||
}
|
||||
|
||||
bool Reader::Slices::processPart(int offset, QByteArray &&bytes) {
|
||||
std::optional<Error> Reader::Slices::processPart(
|
||||
int offset,
|
||||
QByteArray &&bytes) {
|
||||
Expects(offset / kInSlice < _data.size());
|
||||
|
||||
const auto index = offset / kInSlice;
|
||||
if (_headerMode == HeaderMode::Unknown) {
|
||||
if (_header.parts.contains(offset)) {
|
||||
return true;
|
||||
} else if (_header.parts.size() == kMaxPartsInHeader) {
|
||||
// #TODO streaming later unavailable, full load?
|
||||
return false;
|
||||
return {};
|
||||
} else if (_header.parts.size() < kMaxPartsInHeader) {
|
||||
_header.addPart(offset, bytes);
|
||||
}
|
||||
_header.addPart(offset, bytes);
|
||||
}
|
||||
_data[index].addPart(offset - index * kInSlice, std::move(bytes));
|
||||
return true;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Reader::Slices::fill(int offset, bytes::span buffer) -> FillResult {
|
||||
@ -544,6 +553,10 @@ void Reader::stop() {
|
||||
_waiting = nullptr;
|
||||
}
|
||||
|
||||
bool Reader::isRemoteLoader() const {
|
||||
return _loader->baseCacheKey().has_value();
|
||||
}
|
||||
|
||||
std::shared_ptr<Reader::CacheHelper> Reader::InitCacheHelper(
|
||||
std::optional<Storage::Cache::Key> baseKey) {
|
||||
if (!baseKey) {
|
||||
@ -584,7 +597,7 @@ int Reader::size() const {
|
||||
return _loader->size();
|
||||
}
|
||||
|
||||
bool Reader::failed() const {
|
||||
std::optional<Error> Reader::failed() const {
|
||||
return _failed;
|
||||
}
|
||||
|
||||
@ -652,6 +665,10 @@ bool Reader::fillFromSlices(int offset, bytes::span buffer) {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
auto result = _slices.fill(offset, buffer);
|
||||
if (!result.filled && _slices.headerWontBeFilled()) {
|
||||
_failed = Error::NotStreamable;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto sliceNumber : result.sliceNumbersFromCache.values()) {
|
||||
readFromCache(sliceNumber);
|
||||
@ -724,14 +741,16 @@ bool Reader::processLoadedParts() {
|
||||
if (part.offset == LoadedPart::kFailedOffset
|
||||
|| (part.bytes.size() != Loader::kPartSize
|
||||
&& part.offset + part.bytes.size() != size())) {
|
||||
_failed = true;
|
||||
_failed = Error::LoadFailed;
|
||||
return false;
|
||||
} else if (!_loadingOffsets.remove(part.offset)) {
|
||||
continue;
|
||||
} else if (!_slices.processPart(
|
||||
part.offset,
|
||||
std::move(part.bytes))) {
|
||||
_failed = true;
|
||||
}
|
||||
const auto error = _slices.processPart(
|
||||
part.offset,
|
||||
std::move(part.bytes));
|
||||
if (error) {
|
||||
_failed = *error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -25,22 +25,25 @@ namespace Streaming {
|
||||
|
||||
class Loader;
|
||||
struct LoadedPart;
|
||||
enum class Error;
|
||||
|
||||
class Reader final {
|
||||
public:
|
||||
Reader(not_null<Data::Session*> owner, std::unique_ptr<Loader> loader);
|
||||
|
||||
int size() const;
|
||||
bool fill(
|
||||
[[nodiscard]] int size() const;
|
||||
[[nodiscard]] bool fill(
|
||||
int offset,
|
||||
bytes::span buffer,
|
||||
not_null<crl::semaphore*> notify);
|
||||
bool failed() const;
|
||||
[[nodiscard]] std::optional<Error> failed() const;
|
||||
|
||||
void headerDone();
|
||||
|
||||
void stop();
|
||||
|
||||
[[nodiscard]] bool isRemoteLoader() const;
|
||||
|
||||
~Reader();
|
||||
|
||||
private:
|
||||
@ -111,9 +114,10 @@ private:
|
||||
Slices(int size, bool useCache);
|
||||
|
||||
void headerDone(bool fromCache);
|
||||
bool headerWontBeFilled() const;
|
||||
|
||||
bool processCacheResult(int sliceNumber, QByteArray &&result);
|
||||
bool processPart(int offset, QByteArray &&bytes);
|
||||
std::optional<Error> processPart(int offset, QByteArray &&bytes);
|
||||
|
||||
FillResult fill(int offset, bytes::span buffer);
|
||||
SerializedSlice unloadToCache();
|
||||
@ -167,7 +171,7 @@ private:
|
||||
PriorityQueue _loadingOffsets;
|
||||
|
||||
Slices _slices;
|
||||
bool _failed = false;
|
||||
std::optional<Error> _failed;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
@ -31,7 +31,7 @@ public:
|
||||
Stream &&stream,
|
||||
const AudioMsgId &audioId,
|
||||
FnMut<void(const Information &)> ready,
|
||||
Fn<void()> error);
|
||||
Fn<void(Error)> error);
|
||||
|
||||
void process(Packet &&packet);
|
||||
|
||||
@ -80,7 +80,7 @@ private:
|
||||
AudioMsgId _audioId;
|
||||
bool _noMoreData = false;
|
||||
FnMut<void(const Information &)> _ready;
|
||||
Fn<void()> _error;
|
||||
Fn<void(Error)> _error;
|
||||
crl::time _pausedTime = kTimeUnknown;
|
||||
crl::time _resumedTime = kTimeUnknown;
|
||||
mutable TimePoint _syncTimePoint;
|
||||
@ -103,7 +103,7 @@ VideoTrackObject::VideoTrackObject(
|
||||
Stream &&stream,
|
||||
const AudioMsgId &audioId,
|
||||
FnMut<void(const Information &)> ready,
|
||||
Fn<void()> error)
|
||||
Fn<void(Error)> error)
|
||||
: _weak(std::move(weak))
|
||||
, _options(options)
|
||||
, _shared(shared)
|
||||
@ -141,7 +141,7 @@ void VideoTrackObject::process(Packet &&packet) {
|
||||
_stream.queue.push_back(std::move(packet));
|
||||
queueReadFrames();
|
||||
} else if (!tryReadFirstFrame(std::move(packet))) {
|
||||
_error();
|
||||
_error(Error::InvalidData);
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +209,7 @@ auto VideoTrackObject::readFrame(not_null<Frame*> frame) -> FrameResult {
|
||||
}
|
||||
} else if (error.code() != AVERROR(EAGAIN) || _noMoreData) {
|
||||
interrupt();
|
||||
_error();
|
||||
_error(Error::InvalidData);
|
||||
return FrameResult::Error;
|
||||
}
|
||||
Assert(_stream.queue.empty());
|
||||
@ -220,7 +220,7 @@ auto VideoTrackObject::readFrame(not_null<Frame*> frame) -> FrameResult {
|
||||
LOG(("GOT FRAME: %1 (queue %2)").arg(position).arg(_stream.queue.size()));
|
||||
if (position == kTimeUnknown) {
|
||||
interrupt();
|
||||
_error();
|
||||
_error(Error::InvalidData);
|
||||
return FrameResult::Error;
|
||||
}
|
||||
std::swap(frame->decoded, _stream.frame);
|
||||
@ -244,7 +244,7 @@ void VideoTrackObject::presentFrameIfNeeded() {
|
||||
if (frame->original.isNull()) {
|
||||
frame->prepared = QImage();
|
||||
interrupt();
|
||||
_error();
|
||||
_error(Error::InvalidData);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -581,7 +581,7 @@ VideoTrack::VideoTrack(
|
||||
Stream &&stream,
|
||||
const AudioMsgId &audioId,
|
||||
FnMut<void(const Information &)> ready,
|
||||
Fn<void()> error)
|
||||
Fn<void(Error)> error)
|
||||
: _streamIndex(stream.index)
|
||||
, _streamTimeBase(stream.timeBase)
|
||||
, _streamDuration(stream.duration)
|
||||
|
@ -25,7 +25,7 @@ public:
|
||||
Stream &&stream,
|
||||
const AudioMsgId &audioId,
|
||||
FnMut<void(const Information &)> ready,
|
||||
Fn<void()> error);
|
||||
Fn<void(Error)> error);
|
||||
|
||||
// Thread-safe.
|
||||
[[nodiscard]] int streamIndex() const;
|
||||
|
@ -163,6 +163,7 @@ struct OverlayWidget::Streamed {
|
||||
base::Timer timer;
|
||||
|
||||
bool resumeOnCallEnd = false;
|
||||
std::optional<Streaming::Error> lastError;
|
||||
};
|
||||
|
||||
OverlayWidget::Streamed::Streamed(
|
||||
@ -327,7 +328,7 @@ bool OverlayWidget::videoIsGifv() const {
|
||||
QImage OverlayWidget::videoFrame() const {
|
||||
Expects(videoShown());
|
||||
|
||||
auto request = Media::Streaming::FrameRequest();
|
||||
auto request = Streaming::FrameRequest();
|
||||
//request.radius = (_doc && _doc->isVideoMessage())
|
||||
// ? ImageRoundRadius::Ellipse
|
||||
// : ImageRoundRadius::None;
|
||||
@ -893,7 +894,7 @@ void OverlayWidget::showSaveMsgFile() {
|
||||
|
||||
void OverlayWidget::updateMixerVideoVolume() const {
|
||||
if (_streamed) {
|
||||
Media::Player::mixer()->setVideoVolume(Global::VideoVolume());
|
||||
Player::mixer()->setVideoVolume(Global::VideoVolume());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1514,19 +1515,19 @@ void OverlayWidget::refreshCaption(HistoryItem *item) {
|
||||
void OverlayWidget::refreshGroupThumbs() {
|
||||
const auto existed = (_groupThumbs != nullptr);
|
||||
if (_index && _sharedMediaData) {
|
||||
Media::View::GroupThumbs::Refresh(
|
||||
View::GroupThumbs::Refresh(
|
||||
_groupThumbs,
|
||||
*_sharedMediaData,
|
||||
*_index,
|
||||
_groupThumbsAvailableWidth);
|
||||
} else if (_index && _userPhotosData) {
|
||||
Media::View::GroupThumbs::Refresh(
|
||||
View::GroupThumbs::Refresh(
|
||||
_groupThumbs,
|
||||
*_userPhotosData,
|
||||
*_index,
|
||||
_groupThumbsAvailableWidth);
|
||||
} else if (_index && _collageData) {
|
||||
Media::View::GroupThumbs::Refresh(
|
||||
View::GroupThumbs::Refresh(
|
||||
_groupThumbs,
|
||||
{ _msgid, &*_collageData },
|
||||
*_index,
|
||||
@ -1555,8 +1556,8 @@ void OverlayWidget::initGroupThumbs() {
|
||||
}, _groupThumbs->lifetime());
|
||||
|
||||
_groupThumbs->activateRequests(
|
||||
) | rpl::start_with_next([this](Media::View::GroupThumbs::Key key) {
|
||||
using CollageKey = Media::View::GroupThumbs::CollageKey;
|
||||
) | rpl::start_with_next([this](View::GroupThumbs::Key key) {
|
||||
using CollageKey = View::GroupThumbs::CollageKey;
|
||||
if (const auto photoId = base::get_if<PhotoId>(&key)) {
|
||||
const auto photo = Auth().data().photo(*photoId);
|
||||
moveToEntity({ photo, nullptr });
|
||||
@ -2027,7 +2028,7 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) {
|
||||
playbackWaitingChange(update.waiting);
|
||||
}, [&](MutedByOther) {
|
||||
}, [&](Finished) {
|
||||
const auto finishTrack = [](Media::Streaming::TrackState &state) {
|
||||
const auto finishTrack = [](Streaming::TrackState &state) {
|
||||
state.position = state.receivedTill = state.duration;
|
||||
};
|
||||
finishTrack(_streamed->info.audio.state);
|
||||
@ -2037,8 +2038,19 @@ void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) {
|
||||
}
|
||||
|
||||
void OverlayWidget::handleStreamingError(Streaming::Error &&error) {
|
||||
playbackWaitingChange(false);
|
||||
updatePlaybackState();
|
||||
if (error == Streaming::Error::NotStreamable) {
|
||||
_doc->setNotSupportsStreaming();
|
||||
} else if (error == Streaming::Error::OpenFailed) {
|
||||
_doc->setInappPlaybackFailed();
|
||||
}
|
||||
if (!_doc->canBePlayed()) {
|
||||
clearStreaming();
|
||||
displayDocument(_doc, App::histItemById(_msgid));
|
||||
} else {
|
||||
_streamed->lastError = std::move(error);
|
||||
playbackWaitingChange(false);
|
||||
updatePlaybackState();
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackWaitingChange(bool waiting) {
|
||||
@ -2149,13 +2161,7 @@ void OverlayWidget::refreshClipControllerGeometry() {
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackControlsPlay() {
|
||||
const auto legacy = _streamed->player.prepareLegacyState();
|
||||
if (legacy.state == Player::State::StoppedAtStart) {
|
||||
Data::HandleUnsupportedMedia(_doc, _msgid);
|
||||
close();
|
||||
} else {
|
||||
playbackPauseResume();
|
||||
}
|
||||
playbackPauseResume();
|
||||
}
|
||||
|
||||
void OverlayWidget::playbackControlsPause() {
|
||||
@ -2174,9 +2180,11 @@ void OverlayWidget::playbackPauseResume() {
|
||||
Expects(_streamed != nullptr);
|
||||
|
||||
_streamed->resumeOnCallEnd = false;
|
||||
_streamed->lastError = std::nullopt;
|
||||
if (const auto item = App::histItemById(_msgid)) {
|
||||
if (_streamed->player.failed()) {
|
||||
displayDocument(_doc, item);
|
||||
clearStreaming();
|
||||
initStreaming();
|
||||
} else if (_streamed->player.finished()) {
|
||||
restartAtSeekPosition(0);
|
||||
} else if (_streamed->player.paused()) {
|
||||
@ -2209,8 +2217,8 @@ void OverlayWidget::restartAtSeekPosition(crl::time position) {
|
||||
}
|
||||
_streamed->player.play(options);
|
||||
|
||||
Media::Player::instance()->pause(AudioMsgId::Type::Voice);
|
||||
Media::Player::instance()->pause(AudioMsgId::Type::Song);
|
||||
Player::instance()->pause(AudioMsgId::Type::Voice);
|
||||
Player::instance()->pause(AudioMsgId::Type::Song);
|
||||
|
||||
_streamed->info.audio.state.position
|
||||
= _streamed->info.video.state.position
|
||||
|
@ -39,7 +39,7 @@ struct TrackState;
|
||||
namespace Streaming {
|
||||
struct Information;
|
||||
struct Update;
|
||||
struct Error;
|
||||
enum class Error;
|
||||
} // namespace Streaming
|
||||
} // namespace Media
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user