Report streaming failed.

This commit is contained in:
John Preston 2019-03-05 17:56:27 +04:00
parent 71b733a018
commit 6887993f92
20 changed files with 268 additions and 193 deletions

View file

@ -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";

View file

@ -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

View file

@ -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

View file

@ -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();
}

View file

@ -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;

View file

@ -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);

View file

@ -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:

View file

@ -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.

View file

@ -100,7 +100,11 @@ struct Update {
Finished> data;
};
struct Error {
enum class Error {
OpenFailed,
LoadFailed,
InvalidData,
NotStreamable,
};
struct FrameRequest {

View file

@ -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();
}

View file

@ -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;
};

View file

@ -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.

View file

@ -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()

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;
};

View file

@ -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)

View file

@ -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;

View file

@ -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

View file

@ -39,7 +39,7 @@ struct TrackState;
namespace Streaming {
struct Information;
struct Update;
struct Error;
enum class Error;
} // namespace Streaming
} // namespace Media