mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 22:54:01 +02:00
Allow showing stories in different contexts.
This commit is contained in:
parent
e7c0385aea
commit
b71d72ca7c
21 changed files with 231 additions and 112 deletions
|
@ -95,7 +95,9 @@ object_ptr<Ui::BoxContent> PrepareContactsBox(
|
||||||
|
|
||||||
raw->clicks(
|
raw->clicks(
|
||||||
) | rpl::start_with_next([=](uint64 id) {
|
) | rpl::start_with_next([=](uint64 id) {
|
||||||
sessionController->openPeerStories(PeerId(int64(id)), {});
|
sessionController->openPeerStories(
|
||||||
|
PeerId(int64(id)),
|
||||||
|
Data::StorySourcesList::All);
|
||||||
}, raw->lifetime());
|
}, raw->lifetime());
|
||||||
|
|
||||||
raw->showProfileRequests(
|
raw->showProfileRequests(
|
||||||
|
|
|
@ -744,7 +744,13 @@ void Stories::resolve(FullStoryId id, Fn<void()> done) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stories::loadAround(FullStoryId id) {
|
void Stories::loadAround(FullStoryId id, StoriesContext context) {
|
||||||
|
if (v::is<StoriesContextSingle>(context.data)) {
|
||||||
|
return;
|
||||||
|
} else if (v::is<StoriesContextSaved>(context.data)
|
||||||
|
|| v::is<StoriesContextArchive>(context.data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const auto i = _all.find(id.peer);
|
const auto i = _all.find(id.peer);
|
||||||
if (i == end(_all)) {
|
if (i == end(_all)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -139,6 +139,32 @@ enum class StorySourcesList : uchar {
|
||||||
All,
|
All,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct StoriesContextSingle {
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoriesContextPeer {
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoriesContextSaved {
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoriesContextArchive {
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoriesContext {
|
||||||
|
std::variant<
|
||||||
|
StoriesContextSingle,
|
||||||
|
StoriesContextPeer,
|
||||||
|
StoriesContextSaved,
|
||||||
|
StoriesContextArchive,
|
||||||
|
StorySourcesList> data;
|
||||||
|
|
||||||
|
friend inline auto operator<=>(
|
||||||
|
StoriesContext,
|
||||||
|
StoriesContext) = default;
|
||||||
|
friend inline bool operator==(StoriesContext, StoriesContext) = default;
|
||||||
|
};
|
||||||
|
|
||||||
inline constexpr auto kStorySourcesListCount = 2;
|
inline constexpr auto kStorySourcesListCount = 2;
|
||||||
|
|
||||||
class Stories final {
|
class Stories final {
|
||||||
|
@ -159,7 +185,7 @@ public:
|
||||||
|
|
||||||
void loadMore(StorySourcesList list);
|
void loadMore(StorySourcesList list);
|
||||||
void apply(const MTPDupdateStory &data);
|
void apply(const MTPDupdateStory &data);
|
||||||
void loadAround(FullStoryId id);
|
void loadAround(FullStoryId id, StoriesContext context);
|
||||||
|
|
||||||
[[nodiscard]] const base::flat_map<PeerId, StoriesSource> &all() const;
|
[[nodiscard]] const base::flat_map<PeerId, StoriesSource> &all() const;
|
||||||
[[nodiscard]] const std::vector<StoriesSourceInfo> &sources(
|
[[nodiscard]] const std::vector<StoriesSourceInfo> &sources(
|
||||||
|
|
|
@ -127,7 +127,7 @@ State::State(not_null<Data::Stories*> data, Data::StorySourcesList list)
|
||||||
}
|
}
|
||||||
|
|
||||||
Content State::next() {
|
Content State::next() {
|
||||||
auto result = Content();
|
auto result = Content{ .full = (_list == Data::StorySourcesList::All) };
|
||||||
const auto &all = _data->all();
|
const auto &all = _data->all();
|
||||||
const auto &sources = _data->sources(_list);
|
const auto &sources = _data->sources(_list);
|
||||||
result.users.reserve(sources.size());
|
result.users.reserve(sources.size());
|
||||||
|
|
|
@ -265,7 +265,8 @@ List::Layout List::computeLayout() const {
|
||||||
+ st::defaultDialogRow.photoSize
|
+ st::defaultDialogRow.photoSize
|
||||||
+ st::defaultDialogRow.padding.left();
|
+ st::defaultDialogRow.padding.left();
|
||||||
const auto narrow = (width() <= narrowWidth);
|
const auto narrow = (width() <= narrowWidth);
|
||||||
const auto smallWidth = st.photo + (itemsCount - 1) * st.shift;
|
const auto smallCount = std::min(kSmallUserpicsShown, itemsCount);
|
||||||
|
const auto smallWidth = st.photo + (smallCount - 1) * st.shift;
|
||||||
const auto leftSmall = narrow
|
const auto leftSmall = narrow
|
||||||
? ((narrowWidth - smallWidth) / 2 - st.photoLeft)
|
? ((narrowWidth - smallWidth) / 2 - st.photoLeft)
|
||||||
: st.left;
|
: st.left;
|
||||||
|
@ -278,7 +279,7 @@ List::Layout List::computeLayout() const {
|
||||||
(width() - leftFull + singleFull - 1) / singleFull,
|
(width() - leftFull + singleFull - 1) / singleFull,
|
||||||
itemsCount);
|
itemsCount);
|
||||||
const auto startIndexSmall = 0;
|
const auto startIndexSmall = 0;
|
||||||
const auto endIndexSmall = std::min(kSmallUserpicsShown, itemsCount);
|
const auto endIndexSmall = smallCount;
|
||||||
const auto cellLeftSmall = leftSmall;
|
const auto cellLeftSmall = leftSmall;
|
||||||
const auto userpicLeftFull = cellLeftFull + full.photoLeft;
|
const auto userpicLeftFull = cellLeftFull + full.photoLeft;
|
||||||
const auto userpicLeftSmall = cellLeftSmall + st.photoLeft;
|
const auto userpicLeftSmall = cellLeftSmall + st.photoLeft;
|
||||||
|
@ -714,10 +715,12 @@ void List::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
_menu->addAction(tr::lng_context_view_profile(tr::now), [=] {
|
_menu->addAction(tr::lng_context_view_profile(tr::now), [=] {
|
||||||
_showProfileRequests.fire_copy(id);
|
_showProfileRequests.fire_copy(id);
|
||||||
});
|
});
|
||||||
_menu->addAction(hidden
|
if (!_content.full || hidden) {
|
||||||
? tr::lng_stories_show_in_chats(tr::now)
|
_menu->addAction(hidden
|
||||||
: tr::lng_stories_hide_to_contacts(tr::now),
|
? tr::lng_stories_show_in_chats(tr::now)
|
||||||
[=] { _toggleShown.fire({ .id = id, .shown = hidden }); });
|
: tr::lng_stories_hide_to_contacts(tr::now),
|
||||||
|
[=] { _toggleShown.fire({ .id = id, .shown = hidden }); });
|
||||||
|
}
|
||||||
const auto updateAfterMenuDestroyed = [=] {
|
const auto updateAfterMenuDestroyed = [=] {
|
||||||
const auto globalPosition = QCursor::pos();
|
const auto globalPosition = QCursor::pos();
|
||||||
if (rect().contains(mapFromGlobal(globalPosition))) {
|
if (rect().contains(mapFromGlobal(globalPosition))) {
|
||||||
|
|
|
@ -37,6 +37,7 @@ struct User {
|
||||||
|
|
||||||
struct Content {
|
struct Content {
|
||||||
std::vector<User> users;
|
std::vector<User> users;
|
||||||
|
bool full = false;
|
||||||
|
|
||||||
friend inline bool operator==(
|
friend inline bool operator==(
|
||||||
const Content &a,
|
const Content &a,
|
||||||
|
|
|
@ -230,6 +230,7 @@ FormatPointer MakeFormatPointer(
|
||||||
if (!io) {
|
if (!io) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
io->seekable = (seek != nullptr);
|
||||||
auto result = avformat_alloc_context();
|
auto result = avformat_alloc_context();
|
||||||
if (!result) {
|
if (!result) {
|
||||||
LogError(u"avformat_alloc_context"_q);
|
LogError(u"avformat_alloc_context"_q);
|
||||||
|
@ -250,7 +251,9 @@ FormatPointer MakeFormatPointer(
|
||||||
LogError(u"avformat_open_input"_q, error);
|
LogError(u"avformat_open_input"_q, error);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
result->flags |= AVFMT_FLAG_FAST_SEEK;
|
if (seek) {
|
||||||
|
result->flags |= AVFMT_FLAG_FAST_SEEK;
|
||||||
|
}
|
||||||
|
|
||||||
// Now FormatPointer will own and free the IO context.
|
// Now FormatPointer will own and free the IO context.
|
||||||
io.release();
|
io.release();
|
||||||
|
|
|
@ -387,29 +387,54 @@ auto Controller::stickerOrEmojiChosen() const
|
||||||
|
|
||||||
void Controller::show(
|
void Controller::show(
|
||||||
not_null<Data::Story*> story,
|
not_null<Data::Story*> story,
|
||||||
Data::StorySourcesList list) {
|
Data::StoriesContext context) {
|
||||||
|
using namespace Data;
|
||||||
|
|
||||||
auto &stories = story->owner().stories();
|
auto &stories = story->owner().stories();
|
||||||
const auto &all = stories.all();
|
|
||||||
const auto &sources = stories.sources(list);
|
|
||||||
const auto storyId = story->fullId();
|
const auto storyId = story->fullId();
|
||||||
const auto id = storyId.story;
|
const auto id = storyId.story;
|
||||||
const auto i = ranges::find(
|
const auto &all = stories.all();
|
||||||
sources,
|
const auto inAll = all.find(storyId.peer);
|
||||||
storyId.peer,
|
auto source = (inAll != end(all)) ? &inAll->second : nullptr;
|
||||||
&Data::StoriesSourceInfo::id);
|
auto single = StoriesSource{ story->peer()->asUser() };
|
||||||
if (i == end(sources)) {
|
v::match(context.data, [&](StoriesContextSingle) {
|
||||||
|
source = &single;
|
||||||
|
hideSiblings();
|
||||||
|
}, [&](StoriesContextPeer) {
|
||||||
|
hideSiblings();
|
||||||
|
}, [&](StoriesContextSaved) {
|
||||||
|
hideSiblings();
|
||||||
|
}, [&](StoriesContextArchive) {
|
||||||
|
hideSiblings();
|
||||||
|
}, [&](StorySourcesList list) {
|
||||||
|
const auto &sources = stories.sources(list);
|
||||||
|
const auto i = ranges::find(
|
||||||
|
sources,
|
||||||
|
storyId.peer,
|
||||||
|
&StoriesSourceInfo::id);
|
||||||
|
if (i == end(sources)) {
|
||||||
|
source = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showSiblings(&story->session(), sources, (i - begin(sources)));
|
||||||
|
|
||||||
|
if (int(sources.end() - i) < kPreloadUsersCount) {
|
||||||
|
stories.loadMore(list);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const auto idDate = story->idDate();
|
||||||
|
if (!source) {
|
||||||
return;
|
return;
|
||||||
|
} else if (source == &single) {
|
||||||
|
single.ids.emplace(idDate);
|
||||||
|
_index = 0;
|
||||||
|
} else {
|
||||||
|
const auto k = source->ids.find(idDate);
|
||||||
|
if (k == end(source->ids)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_index = (k - begin(source->ids));
|
||||||
}
|
}
|
||||||
const auto j = all.find(storyId.peer);
|
|
||||||
if (j == end(all)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto &source = j->second;
|
|
||||||
const auto k = source.ids.lower_bound(Data::StoryIdDate{ id });
|
|
||||||
if (k == end(source.ids) || k->id != id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showSiblings(&story->session(), sources, (i - begin(sources)));
|
|
||||||
const auto guard = gsl::finally([&] {
|
const auto guard = gsl::finally([&] {
|
||||||
_paused = false;
|
_paused = false;
|
||||||
_started = false;
|
_started = false;
|
||||||
|
@ -419,12 +444,11 @@ void Controller::show(
|
||||||
_photoPlayback = nullptr;
|
_photoPlayback = nullptr;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (_source != source) {
|
if (_source != *source) {
|
||||||
_source = source;
|
_source = *source;
|
||||||
}
|
}
|
||||||
_index = (k - begin(source.ids));
|
_context = context;
|
||||||
_waitingForId = {};
|
_waitingForId = {};
|
||||||
|
|
||||||
if (_shown == storyId) {
|
if (_shown == storyId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -436,13 +460,13 @@ void Controller::show(
|
||||||
unfocusReply();
|
unfocusReply();
|
||||||
}
|
}
|
||||||
|
|
||||||
_header->show({ .user = source.user, .date = story->date() });
|
_header->show({ .user = source->user, .date = story->date() });
|
||||||
_slider->show({ .index = _index, .total = int(source.ids.size()) });
|
_slider->show({ .index = _index, .total = int(source->ids.size()) });
|
||||||
_replyArea->show({ .user = source.user, .id = id });
|
_replyArea->show({ .user = source->user, .id = id });
|
||||||
_recentViews->show({
|
_recentViews->show({
|
||||||
.list = story->recentViewers(),
|
.list = story->recentViewers(),
|
||||||
.total = story->views(),
|
.total = story->views(),
|
||||||
.valid = source.user->isSelf(),
|
.valid = source->user->isSelf(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const auto session = &story->session();
|
const auto session = &story->session();
|
||||||
|
@ -463,13 +487,10 @@ void Controller::show(
|
||||||
}, _sessionLifetime);
|
}, _sessionLifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (int(sources.end() - i) < kPreloadUsersCount) {
|
stories.loadAround(storyId, context);
|
||||||
stories.loadMore(list);
|
|
||||||
}
|
|
||||||
stories.loadAround(storyId);
|
|
||||||
|
|
||||||
updatePlayingAllowed();
|
updatePlayingAllowed();
|
||||||
source.user->updateFull();
|
source->user->updateFull();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::updatePlayingAllowed() {
|
void Controller::updatePlayingAllowed() {
|
||||||
|
@ -509,6 +530,11 @@ void Controller::showSiblings(
|
||||||
(index + 1 < sources.size()) ? sources[index + 1].id : PeerId());
|
(index + 1 < sources.size()) ? sources[index + 1].id : PeerId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Controller::hideSiblings() {
|
||||||
|
_siblingLeft = nullptr;
|
||||||
|
_siblingRight = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void Controller::showSibling(
|
void Controller::showSibling(
|
||||||
std::unique_ptr<Sibling> &sibling,
|
std::unique_ptr<Sibling> &sibling,
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
|
@ -616,10 +642,10 @@ void Controller::subjumpTo(int index) {
|
||||||
};
|
};
|
||||||
auto &stories = _source->user->owner().stories();
|
auto &stories = _source->user->owner().stories();
|
||||||
if (stories.lookup(id)) {
|
if (stories.lookup(id)) {
|
||||||
_delegate->storiesJumpTo(&_source->user->session(), id);
|
_delegate->storiesJumpTo(&_source->user->session(), id, _context);
|
||||||
} else if (_waitingForId != id) {
|
} else if (_waitingForId != id) {
|
||||||
_waitingForId = id;
|
_waitingForId = id;
|
||||||
stories.loadAround(id);
|
stories.loadAround(id, _context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -637,7 +663,8 @@ void Controller::checkWaitingFor() {
|
||||||
}
|
}
|
||||||
_delegate->storiesJumpTo(
|
_delegate->storiesJumpTo(
|
||||||
&_source->user->session(),
|
&_source->user->session(),
|
||||||
base::take(_waitingForId));
|
base::take(_waitingForId),
|
||||||
|
_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Controller::jumpFor(int delta) {
|
bool Controller::jumpFor(int delta) {
|
||||||
|
@ -645,7 +672,8 @@ bool Controller::jumpFor(int delta) {
|
||||||
if (const auto left = _siblingLeft.get()) {
|
if (const auto left = _siblingLeft.get()) {
|
||||||
_delegate->storiesJumpTo(
|
_delegate->storiesJumpTo(
|
||||||
&left->peer()->session(),
|
&left->peer()->session(),
|
||||||
left->shownId());
|
left->shownId(),
|
||||||
|
_context);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (delta == 1) {
|
} else if (delta == 1) {
|
||||||
|
@ -655,7 +683,8 @@ bool Controller::jumpFor(int delta) {
|
||||||
if (const auto right = _siblingRight.get()) {
|
if (const auto right = _siblingRight.get()) {
|
||||||
_delegate->storiesJumpTo(
|
_delegate->storiesJumpTo(
|
||||||
&right->peer()->session(),
|
&right->peer()->session(),
|
||||||
right->shownId());
|
right->shownId(),
|
||||||
|
_context);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@ public:
|
||||||
[[nodiscard]] auto stickerOrEmojiChosen() const
|
[[nodiscard]] auto stickerOrEmojiChosen() const
|
||||||
-> rpl::producer<ChatHelpers::FileChosen>;
|
-> rpl::producer<ChatHelpers::FileChosen>;
|
||||||
|
|
||||||
void show(not_null<Data::Story*> story, Data::StorySourcesList list);
|
void show(not_null<Data::Story*> story, Data::StoriesContext context);
|
||||||
void ready();
|
void ready();
|
||||||
|
|
||||||
void updateVideoPlayback(const Player::TrackState &state);
|
void updateVideoPlayback(const Player::TrackState &state);
|
||||||
|
@ -137,6 +137,7 @@ private:
|
||||||
void updatePlayingAllowed();
|
void updatePlayingAllowed();
|
||||||
void setPlayingAllowed(bool allowed);
|
void setPlayingAllowed(bool allowed);
|
||||||
|
|
||||||
|
void hideSiblings();
|
||||||
void showSiblings(
|
void showSiblings(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
const std::vector<Data::StoriesSourceInfo> &lists,
|
const std::vector<Data::StoriesSourceInfo> &lists,
|
||||||
|
@ -178,6 +179,7 @@ private:
|
||||||
|
|
||||||
FullStoryId _shown;
|
FullStoryId _shown;
|
||||||
TextWithEntities _captionText;
|
TextWithEntities _captionText;
|
||||||
|
Data::StoriesContext _context;
|
||||||
std::optional<Data::StoriesSource> _source;
|
std::optional<Data::StoriesSource> _source;
|
||||||
FullStoryId _waitingForId;
|
FullStoryId _waitingForId;
|
||||||
int _index = 0;
|
int _index = 0;
|
||||||
|
|
|
@ -12,6 +12,10 @@ class Show;
|
||||||
struct FileChosen;
|
struct FileChosen;
|
||||||
} // namespace ChatHelpers
|
} // namespace ChatHelpers
|
||||||
|
|
||||||
|
namespace Data {
|
||||||
|
struct StoriesContext;
|
||||||
|
} // namespace Data
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
@ -41,7 +45,8 @@ public:
|
||||||
-> rpl::producer<ChatHelpers::FileChosen> = 0;
|
-> rpl::producer<ChatHelpers::FileChosen> = 0;
|
||||||
virtual void storiesJumpTo(
|
virtual void storiesJumpTo(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
FullStoryId id) = 0;
|
FullStoryId id,
|
||||||
|
Data::StoriesContext context) = 0;
|
||||||
virtual void storiesClose() = 0;
|
virtual void storiesClose() = 0;
|
||||||
[[nodiscard]] virtual bool storiesPaused() = 0;
|
[[nodiscard]] virtual bool storiesPaused() = 0;
|
||||||
[[nodiscard]] virtual rpl::producer<bool> storiesLayerShown() = 0;
|
[[nodiscard]] virtual rpl::producer<bool> storiesLayerShown() = 0;
|
||||||
|
|
|
@ -22,8 +22,10 @@ View::View(not_null<Delegate*> delegate)
|
||||||
|
|
||||||
View::~View() = default;
|
View::~View() = default;
|
||||||
|
|
||||||
void View::show(not_null<Data::Story*> story, Data::StorySourcesList list) {
|
void View::show(
|
||||||
_controller->show(story, list);
|
not_null<Data::Story*> story,
|
||||||
|
Data::StoriesContext context) {
|
||||||
|
_controller->show(story, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void View::ready() {
|
void View::ready() {
|
||||||
|
|
|
@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class Story;
|
class Story;
|
||||||
enum class StorySourcesList : uchar;
|
struct StoriesContext;
|
||||||
struct FileOrigin;
|
struct FileOrigin;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ public:
|
||||||
explicit View(not_null<Delegate*> delegate);
|
explicit View(not_null<Delegate*> delegate);
|
||||||
~View();
|
~View();
|
||||||
|
|
||||||
void show(not_null<Data::Story*> story, Data::StorySourcesList list);
|
void show(not_null<Data::Story*> story, Data::StoriesContext context);
|
||||||
void ready();
|
void ready();
|
||||||
|
|
||||||
[[nodiscard]] bool canDownload() const;
|
[[nodiscard]] bool canDownload() const;
|
||||||
|
|
|
@ -40,11 +40,13 @@ enum class Mode {
|
||||||
struct PlaybackOptions {
|
struct PlaybackOptions {
|
||||||
Mode mode = Mode::Both;
|
Mode mode = Mode::Both;
|
||||||
crl::time position = 0;
|
crl::time position = 0;
|
||||||
|
crl::time durationOverride = 0;
|
||||||
float64 speed = 1.; // Valid values between 0.5 and 2.
|
float64 speed = 1.; // Valid values between 0.5 and 2.
|
||||||
AudioMsgId audioId;
|
AudioMsgId audioId;
|
||||||
bool syncVideoByAudio = true;
|
bool syncVideoByAudio = true;
|
||||||
bool waitForMarkAsShown = false;
|
bool waitForMarkAsShown = false;
|
||||||
bool hwAllowed = false;
|
bool hwAllowed = false;
|
||||||
|
bool seekable = true;
|
||||||
bool loop = false;
|
bool loop = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,7 @@ Stream File::Context::initStream(
|
||||||
not_null<AVFormatContext*> format,
|
not_null<AVFormatContext*> format,
|
||||||
AVMediaType type,
|
AVMediaType type,
|
||||||
Mode mode,
|
Mode mode,
|
||||||
bool hwAllowed) {
|
StartOptions options) {
|
||||||
auto result = Stream();
|
auto result = Stream();
|
||||||
const auto index = result.index = av_find_best_stream(
|
const auto index = result.index = av_find_best_stream(
|
||||||
format,
|
format,
|
||||||
|
@ -171,7 +171,7 @@ Stream File::Context::initStream(
|
||||||
}
|
}
|
||||||
result.codec = FFmpeg::MakeCodecPointer({
|
result.codec = FFmpeg::MakeCodecPointer({
|
||||||
.stream = info,
|
.stream = info,
|
||||||
.hwAllowed = hwAllowed,
|
.hwAllowed = options.hwAllow,
|
||||||
});
|
});
|
||||||
if (!result.codec) {
|
if (!result.codec) {
|
||||||
return result;
|
return result;
|
||||||
|
@ -196,7 +196,9 @@ Stream File::Context::initStream(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result.timeBase = info->time_base;
|
result.timeBase = info->time_base;
|
||||||
result.duration = (info->duration != AV_NOPTS_VALUE)
|
result.duration = options.durationOverride
|
||||||
|
? options.durationOverride
|
||||||
|
: (info->duration != AV_NOPTS_VALUE)
|
||||||
? FFmpeg::PtsToTime(info->duration, result.timeBase)
|
? FFmpeg::PtsToTime(info->duration, result.timeBase)
|
||||||
: UnreliableFormatDuration(format, info, mode)
|
: UnreliableFormatDuration(format, info, mode)
|
||||||
? kTimeUnknown
|
? kTimeUnknown
|
||||||
|
@ -269,17 +271,19 @@ std::variant<FFmpeg::Packet, FFmpeg::AvErrorWrap> File::Context::readPacket() {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::Context::start(crl::time position, bool hwAllow) {
|
void File::Context::start(StartOptions options) {
|
||||||
|
Expects(options.seekable || !options.position);
|
||||||
|
|
||||||
auto error = FFmpeg::AvErrorWrap();
|
auto error = FFmpeg::AvErrorWrap();
|
||||||
|
|
||||||
if (unroll()) {
|
if (unroll()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto format = FFmpeg::MakeFormatPointer(
|
auto format = FFmpeg::MakeFormatPointer(
|
||||||
static_cast<void *>(this),
|
static_cast<void*>(this),
|
||||||
&Context::Read,
|
&Context::Read,
|
||||||
nullptr,
|
nullptr,
|
||||||
&Context::Seek);
|
options.seekable ? &Context::Seek : nullptr);
|
||||||
if (!format) {
|
if (!format) {
|
||||||
return fail(Error::OpenFailed);
|
return fail(Error::OpenFailed);
|
||||||
}
|
}
|
||||||
|
@ -289,12 +293,20 @@ void File::Context::start(crl::time position, bool hwAllow) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto mode = _delegate->fileOpenMode();
|
const auto mode = _delegate->fileOpenMode();
|
||||||
auto video = initStream(format.get(), AVMEDIA_TYPE_VIDEO, mode, hwAllow);
|
auto video = initStream(
|
||||||
|
format.get(),
|
||||||
|
AVMEDIA_TYPE_VIDEO,
|
||||||
|
mode,
|
||||||
|
options);
|
||||||
if (unroll()) {
|
if (unroll()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto audio = initStream(format.get(), AVMEDIA_TYPE_AUDIO, mode, false);
|
auto audio = initStream(
|
||||||
|
format.get(),
|
||||||
|
AVMEDIA_TYPE_AUDIO,
|
||||||
|
mode,
|
||||||
|
options);
|
||||||
if (unroll()) {
|
if (unroll()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -303,8 +315,11 @@ void File::Context::start(crl::time position, bool hwAllow) {
|
||||||
if (_reader->isRemoteLoader()) {
|
if (_reader->isRemoteLoader()) {
|
||||||
sendFullInCache(true);
|
sendFullInCache(true);
|
||||||
}
|
}
|
||||||
if (video.codec || audio.codec) {
|
if (options.seekable && (video.codec || audio.codec)) {
|
||||||
seekToPosition(format.get(), video.codec ? video : audio, position);
|
seekToPosition(
|
||||||
|
format.get(),
|
||||||
|
video.codec ? video : audio,
|
||||||
|
options.position);
|
||||||
}
|
}
|
||||||
if (unroll()) {
|
if (unroll()) {
|
||||||
return;
|
return;
|
||||||
|
@ -434,10 +449,7 @@ File::File(std::shared_ptr<Reader> reader)
|
||||||
: _reader(std::move(reader)) {
|
: _reader(std::move(reader)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::start(
|
void File::start(not_null<FileDelegate*> delegate, StartOptions options) {
|
||||||
not_null<FileDelegate*> delegate,
|
|
||||||
crl::time position,
|
|
||||||
bool hwAllow) {
|
|
||||||
stop(true);
|
stop(true);
|
||||||
|
|
||||||
_reader->startStreaming();
|
_reader->startStreaming();
|
||||||
|
@ -445,7 +457,7 @@ void File::start(
|
||||||
|
|
||||||
_thread = std::thread([=, context = &*_context] {
|
_thread = std::thread([=, context = &*_context] {
|
||||||
crl::toggle_fp_exceptions(true);
|
crl::toggle_fp_exceptions(true);
|
||||||
context->start(position, hwAllow);
|
context->start(options);
|
||||||
while (!context->finished()) {
|
while (!context->finished()) {
|
||||||
context->readNextPacket();
|
context->readNextPacket();
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,13 @@ namespace Streaming {
|
||||||
|
|
||||||
class FileDelegate;
|
class FileDelegate;
|
||||||
|
|
||||||
|
struct StartOptions {
|
||||||
|
crl::time position = 0;
|
||||||
|
crl::time durationOverride = 0;
|
||||||
|
bool seekable = true;
|
||||||
|
bool hwAllow = false;
|
||||||
|
};
|
||||||
|
|
||||||
class File final {
|
class File final {
|
||||||
public:
|
public:
|
||||||
explicit File(std::shared_ptr<Reader> reader);
|
explicit File(std::shared_ptr<Reader> reader);
|
||||||
|
@ -28,10 +35,7 @@ public:
|
||||||
File(const File &other) = delete;
|
File(const File &other) = delete;
|
||||||
File &operator=(const File &other) = delete;
|
File &operator=(const File &other) = delete;
|
||||||
|
|
||||||
void start(
|
void start(not_null<FileDelegate*> delegate, StartOptions options);
|
||||||
not_null<FileDelegate*> delegate,
|
|
||||||
crl::time position,
|
|
||||||
bool hwAllow);
|
|
||||||
void wake();
|
void wake();
|
||||||
void stop(bool stillActive = false);
|
void stop(bool stillActive = false);
|
||||||
|
|
||||||
|
@ -46,7 +50,7 @@ private:
|
||||||
Context(not_null<FileDelegate*> delegate, not_null<Reader*> reader);
|
Context(not_null<FileDelegate*> delegate, not_null<Reader*> reader);
|
||||||
~Context();
|
~Context();
|
||||||
|
|
||||||
void start(crl::time position, bool hwAllow);
|
void start(StartOptions options);
|
||||||
void readNextPacket();
|
void readNextPacket();
|
||||||
|
|
||||||
void interrupt();
|
void interrupt();
|
||||||
|
@ -79,7 +83,7 @@ private:
|
||||||
not_null<AVFormatContext *> format,
|
not_null<AVFormatContext *> format,
|
||||||
AVMediaType type,
|
AVMediaType type,
|
||||||
Mode mode,
|
Mode mode,
|
||||||
bool hwAllowed);
|
StartOptions options);
|
||||||
void seekToPosition(
|
void seekToPosition(
|
||||||
not_null<AVFormatContext *> format,
|
not_null<AVFormatContext *> format,
|
||||||
const Stream &stream,
|
const Stream &stream,
|
||||||
|
|
|
@ -544,8 +544,16 @@ void Player::play(const PlaybackOptions &options) {
|
||||||
if (!Media::Audio::SupportsSpeedControl()) {
|
if (!Media::Audio::SupportsSpeedControl()) {
|
||||||
_options.speed = 1.;
|
_options.speed = 1.;
|
||||||
}
|
}
|
||||||
|
if (!_options.seekable) {
|
||||||
|
_options.position = 0;
|
||||||
|
}
|
||||||
_stage = Stage::Initializing;
|
_stage = Stage::Initializing;
|
||||||
_file->start(delegate(), _options.position, _options.hwAllowed);
|
_file->start(delegate(), {
|
||||||
|
.position = _options.position,
|
||||||
|
.durationOverride = options.durationOverride,
|
||||||
|
.seekable = _options.seekable,
|
||||||
|
.hwAllow = _options.hwAllowed,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::savePreviousReceivedTill(
|
void Player::savePreviousReceivedTill(
|
||||||
|
|
|
@ -8,17 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "data/data_cloud_themes.h"
|
#include "data/data_cloud_themes.h"
|
||||||
|
#include "data/data_stories.h"
|
||||||
|
|
||||||
class DocumentData;
|
class DocumentData;
|
||||||
class PeerData;
|
class PeerData;
|
||||||
class PhotoData;
|
class PhotoData;
|
||||||
class HistoryItem;
|
class HistoryItem;
|
||||||
|
|
||||||
namespace Data {
|
|
||||||
class Story;
|
|
||||||
enum class StorySourcesList : uchar;
|
|
||||||
} // namespace Data
|
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
class SessionController;
|
class SessionController;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
@ -75,14 +71,10 @@ public:
|
||||||
OpenRequest(
|
OpenRequest(
|
||||||
Window::SessionController *controller,
|
Window::SessionController *controller,
|
||||||
not_null<Data::Story*> story,
|
not_null<Data::Story*> story,
|
||||||
Data::StorySourcesList list,
|
Data::StoriesContext context)
|
||||||
bool continueStreaming = false,
|
|
||||||
crl::time startTime = 0)
|
|
||||||
: _controller(controller)
|
: _controller(controller)
|
||||||
, _story(story)
|
, _story(story)
|
||||||
, _storiesList(list)
|
, _storiesContext(context) {
|
||||||
, _continueStreaming(continueStreaming)
|
|
||||||
, _startTime(startTime) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] PeerData *peer() const {
|
[[nodiscard]] PeerData *peer() const {
|
||||||
|
@ -108,8 +100,8 @@ public:
|
||||||
[[nodiscard]] Data::Story *story() const {
|
[[nodiscard]] Data::Story *story() const {
|
||||||
return _story;
|
return _story;
|
||||||
}
|
}
|
||||||
[[nodiscard]] Data::StorySourcesList storiesList() const {
|
[[nodiscard]] Data::StoriesContext storiesContext() const {
|
||||||
return _storiesList;
|
return _storiesContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::optional<Data::CloudTheme> cloudTheme() const {
|
[[nodiscard]] std::optional<Data::CloudTheme> cloudTheme() const {
|
||||||
|
@ -133,7 +125,7 @@ private:
|
||||||
DocumentData *_document = nullptr;
|
DocumentData *_document = nullptr;
|
||||||
PhotoData *_photo = nullptr;
|
PhotoData *_photo = nullptr;
|
||||||
Data::Story *_story = nullptr;
|
Data::Story *_story = nullptr;
|
||||||
Data::StorySourcesList _storiesList = {};
|
Data::StoriesContext _storiesContext;
|
||||||
PeerData *_peer = nullptr;
|
PeerData *_peer = nullptr;
|
||||||
HistoryItem *_item = nullptr;
|
HistoryItem *_item = nullptr;
|
||||||
MsgId _topicRootId = 0;
|
MsgId _topicRootId = 0;
|
||||||
|
|
|
@ -287,6 +287,17 @@ struct OverlayWidget::PipWrap {
|
||||||
rpl::lifetime lifetime;
|
rpl::lifetime lifetime;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct OverlayWidget::ItemContext {
|
||||||
|
not_null<HistoryItem*> item;
|
||||||
|
MsgId topicRootId = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OverlayWidget::StoriesContext {
|
||||||
|
not_null<PeerData*> peer;
|
||||||
|
StoryId id = 0;
|
||||||
|
Data::StoriesContext within;
|
||||||
|
};
|
||||||
|
|
||||||
class OverlayWidget::Show final : public ChatHelpers::Show {
|
class OverlayWidget::Show final : public ChatHelpers::Show {
|
||||||
public:
|
public:
|
||||||
explicit Show(not_null<OverlayWidget*> widget) : _widget(widget) {
|
explicit Show(not_null<OverlayWidget*> widget) : _widget(widget) {
|
||||||
|
@ -3064,7 +3075,7 @@ void OverlayWidget::show(OpenRequest request) {
|
||||||
setContext(StoriesContext{
|
setContext(StoriesContext{
|
||||||
story->peer(),
|
story->peer(),
|
||||||
story->id(),
|
story->id(),
|
||||||
request.storiesList(),
|
request.storiesContext(),
|
||||||
});
|
});
|
||||||
} else if (contextPeer) {
|
} else if (contextPeer) {
|
||||||
setContext(contextPeer);
|
setContext(contextPeer);
|
||||||
|
@ -3085,7 +3096,11 @@ void OverlayWidget::show(OpenRequest request) {
|
||||||
setSession(&document->session());
|
setSession(&document->session());
|
||||||
|
|
||||||
if (story) {
|
if (story) {
|
||||||
setContext(StoriesContext{ story->peer(), story->id() });
|
setContext(StoriesContext{
|
||||||
|
story->peer(),
|
||||||
|
story->id(),
|
||||||
|
request.storiesContext(),
|
||||||
|
});
|
||||||
} else if (contextItem) {
|
} else if (contextItem) {
|
||||||
setContext(ItemContext{ contextItem, contextTopicRootId });
|
setContext(ItemContext{ contextItem, contextTopicRootId });
|
||||||
} else {
|
} else {
|
||||||
|
@ -3868,9 +3883,16 @@ void OverlayWidget::restartAtSeekPosition(crl::time position) {
|
||||||
_rotation = saved;
|
_rotation = saved;
|
||||||
updateContentRect();
|
updateContentRect();
|
||||||
}
|
}
|
||||||
auto options = Streaming::PlaybackOptions();
|
auto options = Streaming::PlaybackOptions{
|
||||||
options.position = position;
|
.position = position,
|
||||||
options.hwAllowed = Core::App().settings().hardwareAcceleratedVideo();
|
.durationOverride = ((_stories
|
||||||
|
&& _document
|
||||||
|
&& _document->getDuration() > 0)
|
||||||
|
? (_document->getDuration() * crl::time(1000) + crl::time(999))
|
||||||
|
: crl::time(0)),
|
||||||
|
.hwAllowed = Core::App().settings().hardwareAcceleratedVideo(),
|
||||||
|
.seekable = !_stories,
|
||||||
|
};
|
||||||
if (!_streamed->withSound) {
|
if (!_streamed->withSound) {
|
||||||
options.mode = Streaming::Mode::Video;
|
options.mode = Streaming::Mode::Video;
|
||||||
options.loop = true;
|
options.loop = true;
|
||||||
|
@ -4025,7 +4047,8 @@ auto OverlayWidget::storiesStickerOrEmojiChosen()
|
||||||
|
|
||||||
void OverlayWidget::storiesJumpTo(
|
void OverlayWidget::storiesJumpTo(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
FullStoryId id) {
|
FullStoryId id,
|
||||||
|
Data::StoriesContext context) {
|
||||||
Expects(_stories != nullptr);
|
Expects(_stories != nullptr);
|
||||||
Expects(id.valid());
|
Expects(id.valid());
|
||||||
|
|
||||||
|
@ -4035,7 +4058,11 @@ void OverlayWidget::storiesJumpTo(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto story = *maybeStory;
|
const auto story = *maybeStory;
|
||||||
setContext(StoriesContext{ story->peer(), story->id() });
|
setContext(StoriesContext{
|
||||||
|
story->peer(),
|
||||||
|
story->id(),
|
||||||
|
context,
|
||||||
|
});
|
||||||
clearStreaming();
|
clearStreaming();
|
||||||
_streamingStartPaused = false;
|
_streamingStartPaused = false;
|
||||||
v::match(story->media().data, [&](not_null<PhotoData*> photo) {
|
v::match(story->media().data, [&](not_null<PhotoData*> photo) {
|
||||||
|
@ -4982,7 +5009,7 @@ void OverlayWidget::setContext(
|
||||||
const auto maybeStory = stories.lookup(
|
const auto maybeStory = stories.lookup(
|
||||||
{ story->peer->id, story->id });
|
{ story->peer->id, story->id });
|
||||||
if (maybeStory) {
|
if (maybeStory) {
|
||||||
_stories->show(*maybeStory, story->list);
|
_stories->show(*maybeStory, story->within);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_message = nullptr;
|
_message = nullptr;
|
||||||
|
|
|
@ -30,7 +30,7 @@ enum class activation : uchar;
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class PhotoMedia;
|
class PhotoMedia;
|
||||||
class DocumentMedia;
|
class DocumentMedia;
|
||||||
enum class StorySourcesList : uchar;
|
struct StoriesContext;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
@ -134,6 +134,8 @@ private:
|
||||||
class Show;
|
class Show;
|
||||||
struct Streamed;
|
struct Streamed;
|
||||||
struct PipWrap;
|
struct PipWrap;
|
||||||
|
struct ItemContext;
|
||||||
|
struct StoriesContext;
|
||||||
class Renderer;
|
class Renderer;
|
||||||
class RendererSW;
|
class RendererSW;
|
||||||
class RendererGL;
|
class RendererGL;
|
||||||
|
@ -245,7 +247,8 @@ private:
|
||||||
-> rpl::producer<ChatHelpers::FileChosen> override;
|
-> rpl::producer<ChatHelpers::FileChosen> override;
|
||||||
void storiesJumpTo(
|
void storiesJumpTo(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
FullStoryId id) override;
|
FullStoryId id,
|
||||||
|
Data::StoriesContext context) override;
|
||||||
void storiesClose() override;
|
void storiesClose() override;
|
||||||
bool storiesPaused() override;
|
bool storiesPaused() override;
|
||||||
rpl::producer<bool> storiesLayerShown() override;
|
rpl::producer<bool> storiesLayerShown() override;
|
||||||
|
@ -300,15 +303,6 @@ private:
|
||||||
Entity entityForItemId(const FullMsgId &itemId) const;
|
Entity entityForItemId(const FullMsgId &itemId) const;
|
||||||
bool moveToEntity(const Entity &entity, int preloadDelta = 0);
|
bool moveToEntity(const Entity &entity, int preloadDelta = 0);
|
||||||
|
|
||||||
struct ItemContext {
|
|
||||||
not_null<HistoryItem*> item;
|
|
||||||
MsgId topicRootId = 0;
|
|
||||||
};
|
|
||||||
struct StoriesContext {
|
|
||||||
not_null<PeerData*> peer;
|
|
||||||
StoryId id = 0;
|
|
||||||
Data::StorySourcesList list = {};
|
|
||||||
};
|
|
||||||
void setContext(std::variant<
|
void setContext(std::variant<
|
||||||
v::null_t,
|
v::null_t,
|
||||||
ItemContext,
|
ItemContext,
|
||||||
|
|
|
@ -2467,13 +2467,13 @@ Ui::ChatThemeBackgroundData SessionController::backgroundData(
|
||||||
void SessionController::openPeerStory(
|
void SessionController::openPeerStory(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
StoryId storyId,
|
StoryId storyId,
|
||||||
Data::StorySourcesList list) {
|
Data::StoriesContext context) {
|
||||||
using namespace Media::View;
|
using namespace Media::View;
|
||||||
using namespace Data;
|
using namespace Data;
|
||||||
|
|
||||||
auto &stories = session().data().stories();
|
auto &stories = session().data().stories();
|
||||||
if (const auto from = stories.lookup({ peer->id, storyId })) {
|
if (const auto from = stories.lookup({ peer->id, storyId })) {
|
||||||
window().openInMediaView(OpenRequest(this, *from, list));
|
window().openInMediaView(OpenRequest(this, *from, context));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2492,7 +2492,7 @@ void SessionController::openPeerStories(
|
||||||
openPeerStory(
|
openPeerStory(
|
||||||
i->second.user,
|
i->second.user,
|
||||||
j != i->second.ids.end() ? j->id : i->second.ids.front().id,
|
j != i->second.ids.end() ? j->id : i->second.ids.front().id,
|
||||||
list);
|
{ list });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ enum class WindowLayout;
|
||||||
} // namespace Adaptive
|
} // namespace Adaptive
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
struct StoriesContext;
|
||||||
enum class StorySourcesList : uchar;
|
enum class StorySourcesList : uchar;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
|
@ -571,7 +572,7 @@ public:
|
||||||
void openPeerStory(
|
void openPeerStory(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
StoryId storyId,
|
StoryId storyId,
|
||||||
Data::StorySourcesList list);
|
Data::StoriesContext context);
|
||||||
void openPeerStories(PeerId peerId, Data::StorySourcesList list);
|
void openPeerStories(PeerId peerId, Data::StorySourcesList list);
|
||||||
|
|
||||||
struct PaintContextArgs {
|
struct PaintContextArgs {
|
||||||
|
|
Loading…
Add table
Reference in a new issue