1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4 
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #pragma once
9 
10 #include "ui/chat/attach/attach_prepare.h"
11 #include "ui/image/image_prepare.h"
12 
13 #include <QtCore/QTimer>
14 #include <QtCore/QMutex>
15 
16 namespace Core {
17 class FileLocation;
18 } // namespace Core
19 
20 namespace Media {
21 namespace Clip {
22 
23 enum class State {
24 	Reading,
25 	Error,
26 	Finished,
27 };
28 
29 struct FrameRequest {
validFrameRequest30 	bool valid() const {
31 		return factor > 0;
32 	}
33 	int factor = 0;
34 	int framew = 0;
35 	int frameh = 0;
36 	int outerw = 0;
37 	int outerh = 0;
38 	ImageRoundRadius radius = ImageRoundRadius::None;
39 	RectParts corners = RectPart::AllCorners;
40 };
41 
42 enum ReaderSteps : int {
43 	WaitingForDimensionsStep = -3, // before ReaderPrivate read the first image and got the original frame size
44 	WaitingForRequestStep = -2, // before Reader got the original frame size and prepared the frame request
45 	WaitingForFirstFrameStep = -1, // before ReaderPrivate got the frame request and started waiting for the 1-2 delay
46 };
47 
48 enum Notification : int {
49 	NotificationReinit,
50 	NotificationRepaint,
51 };
52 
53 class ReaderPrivate;
54 class Reader {
55 public:
56 	using Callback = Fn<void(Notification)>;
57 	enum class Mode {
58 		Gif,
59 		Video,
60 	};
61 
62 	Reader(const Core::FileLocation &location, const QByteArray &data, Callback &&callback);
63 	Reader(const QString &filepath, Callback &&callback);
64 	Reader(const QByteArray &data, Callback &&callback);
65 
66 	// Reader can be already deleted.
67 	static void callback(Reader *reader, qint32 threadIndex, qint32 notification);
68 
69 	void start(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius, RectParts corners);
70 	QPixmap current(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius, RectParts corners, crl::time ms);
frameOriginal()71 	QPixmap frameOriginal() const {
72 		if (auto frame = frameToShow()) {
73 			auto result = QPixmap::fromImage(frame->original);
74 			result.detach();
75 			return result;
76 		}
77 		return QPixmap();
78 	}
currentDisplayed()79 	bool currentDisplayed() const {
80 		auto frame = frameToShow();
81 		return frame ? (frame->displayed.loadAcquire() != 0) : true;
82 	}
autoPausedGif()83 	bool autoPausedGif() const {
84 		return _autoPausedGif.loadAcquire();
85 	}
86 	bool videoPaused() const;
threadIndex()87 	int threadIndex() const {
88 		return _threadIndex;
89 	}
90 
91 	int width() const;
92 	int height() const;
93 
94 	State state() const;
started()95 	bool started() const {
96 		auto step = _step.loadAcquire();
97 		return (step == WaitingForFirstFrameStep) || (step >= 0);
98 	}
99 	bool ready() const;
100 
101 	crl::time getPositionMs() const;
102 	crl::time getDurationMs() const;
103 	void pauseResumeVideo();
104 
105 	void stop();
106 	void error();
107 	void finished();
108 
109 	~Reader();
110 
111 private:
112 	void init(const Core::FileLocation &location, const QByteArray &data);
113 
114 	Callback _callback;
115 	State _state = State::Reading;
116 
117 	crl::time _durationMs = 0;
118 
119 	mutable int _width = 0;
120 	mutable int _height = 0;
121 
122 	// -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3
123 	mutable QAtomicInt _step = WaitingForDimensionsStep;
124 	struct Frame {
clearFrame125 		void clear() {
126 			pix = QPixmap();
127 			original = QImage();
128 		}
129 		QPixmap pix;
130 		QImage original;
131 		FrameRequest request;
132 		QAtomicInt displayed = 0;
133 
134 		// Should be counted from the end,
135 		// so that positionMs <= _durationMs.
136 		crl::time positionMs = 0;
137 	};
138 	mutable Frame _frames[3];
139 	Frame *frameToShow(int *index = nullptr) const; // 0 means not ready
140 	Frame *frameToWrite(int *index = nullptr) const; // 0 means not ready
141 	Frame *frameToWriteNext(bool check, int *index = nullptr) const;
142 	void moveToNextShow() const;
143 	void moveToNextWrite() const;
144 
145 	QAtomicInt _autoPausedGif = 0;
146 	QAtomicInt _videoPauseRequest = 0;
147 	int32 _threadIndex;
148 
149 	friend class Manager;
150 
151 	ReaderPrivate *_private = nullptr;
152 
153 };
154 
155 class ReaderPointer {
156 public:
157 	ReaderPointer(std::nullptr_t = nullptr) {
158 	}
ReaderPointer(Reader * pointer)159 	explicit ReaderPointer(Reader *pointer) : _pointer(pointer) {
160 	}
161 	ReaderPointer(const ReaderPointer &other) = delete;
162 	ReaderPointer &operator=(const ReaderPointer &other) = delete;
ReaderPointer(ReaderPointer && other)163 	ReaderPointer(ReaderPointer &&other) : _pointer(base::take(other._pointer)) {
164 	}
165 	ReaderPointer &operator=(ReaderPointer &&other) {
166 		swap(other);
167 		return *this;
168 	}
swap(ReaderPointer & other)169 	void swap(ReaderPointer &other) {
170 		qSwap(_pointer, other._pointer);
171 	}
get()172 	Reader *get() const {
173 		return valid() ? _pointer : nullptr;
174 	}
175 	Reader *operator->() const {
176 		return get();
177 	}
setBad()178 	void setBad() {
179 		reset();
180 		_pointer = BadPointer;
181 	}
reset()182 	void reset() {
183 		ReaderPointer temp;
184 		swap(temp);
185 	}
isBad()186 	bool isBad() const {
187 		return (_pointer == BadPointer);
188 	}
valid()189 	bool valid() const {
190 		return _pointer && !isBad();
191 	}
192 	explicit operator bool() const {
193 		return valid();
194 	}
Bad()195 	static inline ReaderPointer Bad() {
196 		ReaderPointer result;
197 		result.setBad();
198 		return result;
199 	}
200 	~ReaderPointer();
201 
202 private:
203 	Reader *_pointer = nullptr;
204 	static Reader *const BadPointer;
205 
206 };
207 
208 template <typename ...Args>
MakeReader(Args &&...args)209 inline ReaderPointer MakeReader(Args&&... args) {
210 	return ReaderPointer(new Reader(std::forward<Args>(args)...));
211 }
212 
213 enum class ProcessResult {
214 	Error,
215 	Started,
216 	Finished,
217 	Paused,
218 	Repaint,
219 	CopyFrame,
220 	Wait,
221 };
222 
223 class Manager : public QObject {
224 public:
225 	explicit Manager(QThread *thread);
226 	~Manager();
227 
loadLevel()228 	int loadLevel() const {
229 		return _loadLevel;
230 	}
231 	void append(Reader *reader, const Core::FileLocation &location, const QByteArray &data);
232 	void start(Reader *reader);
233 	void update(Reader *reader);
234 	void stop(Reader *reader);
235 	bool carries(Reader *reader) const;
236 
237 private:
238 	void process();
239 	void finish();
240 	void callback(Reader *reader, Notification notification);
241 	void clear();
242 
243 	QAtomicInt _loadLevel;
244 	using ReaderPointers = QMap<Reader*, QAtomicInt>;
245 	ReaderPointers _readerPointers;
246 	mutable QMutex _readerPointersMutex;
247 
248 	ReaderPointers::const_iterator constUnsafeFindReaderPointer(ReaderPrivate *reader) const;
249 	ReaderPointers::iterator unsafeFindReaderPointer(ReaderPrivate *reader);
250 
251 	bool handleProcessResult(ReaderPrivate *reader, ProcessResult result, crl::time ms);
252 
253 	enum ResultHandleState {
254 		ResultHandleRemove,
255 		ResultHandleStop,
256 		ResultHandleContinue,
257 	};
258 	ResultHandleState handleResult(ReaderPrivate *reader, ProcessResult result, crl::time ms);
259 
260 	using Readers = QMap<ReaderPrivate*, crl::time>;
261 	Readers _readers;
262 
263 	QTimer _timer;
264 	QThread *_processingInThread = nullptr;
265 	bool _needReProcess = false;
266 
267 };
268 
269 [[nodiscard]] Ui::PreparedFileInformation::Video PrepareForSending(
270 	const QString &fname,
271 	const QByteArray &data);
272 
273 void Finish();
274 
275 } // namespace Clip
276 } // namespace Media
277