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 #include "media/player/media_player_instance.h"
9 
10 #include "data/data_document.h"
11 #include "data/data_session.h"
12 #include "data/data_streaming.h"
13 #include "data/data_file_click_handler.h"
14 #include "media/audio/media_audio.h"
15 #include "media/audio/media_audio_capture.h"
16 #include "media/streaming/media_streaming_instance.h"
17 #include "media/streaming/media_streaming_player.h"
18 #include "media/view/media_view_playback_progress.h"
19 #include "calls/calls_instance.h"
20 #include "history/history.h"
21 #include "history/history_item.h"
22 #include "data/data_media_types.h"
23 #include "data/data_file_origin.h"
24 #include "window/window_session_controller.h"
25 #include "core/shortcuts.h"
26 #include "core/application.h"
27 #include "main/main_domain.h" // Domain::activeSessionValue.
28 #include "mainwindow.h"
29 #include "main/main_session.h"
30 #include "main/main_account.h" // session->account().sessionChanges().
31 #include "main/main_session_settings.h"
32 
33 namespace Media {
34 namespace Player {
35 namespace {
36 
37 Instance *SingleInstance = nullptr;
38 
39 // Preload X message ids before and after current.
40 constexpr auto kIdsLimit = 32;
41 
42 // Preload next messages if we went further from current than that.
43 constexpr auto kIdsPreloadAfter = 28;
44 
45 constexpr auto kMinLengthForSavePosition = 20 * TimeId(60); // 20 minutes.
46 
VoicePlaybackSpeed()47 auto VoicePlaybackSpeed() {
48 	return std::clamp(Core::App().settings().voicePlaybackSpeed(), 0.6, 1.7);
49 }
50 
51 } // namespace
52 
53 struct Instance::Streamed {
54 	Streamed(
55 		AudioMsgId id,
56 		std::shared_ptr<Streaming::Document> document);
57 
58 	AudioMsgId id;
59 	Streaming::Instance instance;
60 	View::PlaybackProgress progress;
61 	bool clearing = false;
62 	rpl::lifetime lifetime;
63 };
64 
start(not_null<Audio::Instance * > instance)65 void start(not_null<Audio::Instance*> instance) {
66 	Audio::Start(instance);
67 	Capture::Start();
68 
69 	SingleInstance = new Instance();
70 }
71 
finish(not_null<Audio::Instance * > instance)72 void finish(not_null<Audio::Instance*> instance) {
73 	delete base::take(SingleInstance);
74 
75 	Capture::Finish();
76 	Audio::Finish(instance);
77 }
78 
SaveLastPlaybackPosition(not_null<DocumentData * > document,const TrackState & state)79 void SaveLastPlaybackPosition(
80 		not_null<DocumentData*> document,
81 		const TrackState &state) {
82 	const auto time = (state.position == kTimeUnknown
83 		|| state.length == kTimeUnknown
84 		|| state.state == State::PausedAtEnd
85 		|| IsStopped(state.state))
86 		? TimeId(0)
87 		: (state.length >= kMinLengthForSavePosition * state.frequency)
88 		? (state.position / state.frequency) * crl::time(1000)
89 		: TimeId(0);
90 	auto &session = document->session();
91 	if (session.settings().mediaLastPlaybackPosition(document->id) != time) {
92 		session.settings().setMediaLastPlaybackPosition(document->id, time);
93 		session.saveSettingsDelayed();
94 	}
95 }
96 
Streamed(AudioMsgId id,std::shared_ptr<Streaming::Document> document)97 Instance::Streamed::Streamed(
98 	AudioMsgId id,
99 	std::shared_ptr<Streaming::Document> document)
100 : id(id)
101 , instance(std::move(document), nullptr) {
102 }
103 
Data(AudioMsgId::Type type,SharedMediaType overview)104 Instance::Data::Data(AudioMsgId::Type type, SharedMediaType overview)
105 : type(type)
106 , overview(overview) {
107 }
108 
109 Instance::Data::Data(Data &&other) = default;
110 Instance::Data &Instance::Data::operator=(Data &&other) = default;
111 Instance::Data::~Data() = default;
112 
Instance()113 Instance::Instance()
114 : _songData(AudioMsgId::Type::Song, SharedMediaType::MusicFile)
115 , _voiceData(AudioMsgId::Type::Voice, SharedMediaType::RoundVoiceFile) {
116 	subscribe(Media::Player::Updated(), [this](const AudioMsgId &audioId) {
117 		handleSongUpdate(audioId);
118 	});
119 
120 	using namespace rpl::mappers;
121 	rpl::combine(
122 		Core::App().calls().currentCallValue(),
123 		Core::App().calls().currentGroupCallValue(),
124 		_1 || _2
125 	) | rpl::start_with_next([=](bool call) {
126 		if (call) {
127 			pauseOnCall(AudioMsgId::Type::Voice);
128 			pauseOnCall(AudioMsgId::Type::Song);
129 		} else {
130 			resumeOnCall(AudioMsgId::Type::Voice);
131 			resumeOnCall(AudioMsgId::Type::Song);
132 		}
133 	}, _lifetime);
134 
135 	setupShortcuts();
136 }
137 
138 Instance::~Instance() = default;
139 
getActiveType() const140 AudioMsgId::Type Instance::getActiveType() const {
141 	if (const auto data = getData(AudioMsgId::Type::Voice)) {
142 		if (data->current) {
143 			const auto state = getState(data->type);
144 			if (!IsStoppedOrStopping(state.state)) {
145 				return data->type;
146 			}
147 		}
148 	}
149 	return AudioMsgId::Type::Song;
150 }
151 
handleSongUpdate(const AudioMsgId & audioId)152 void Instance::handleSongUpdate(const AudioMsgId &audioId) {
153 	emitUpdate(audioId.type(), [&](const AudioMsgId &playing) {
154 		return (audioId == playing);
155 	});
156 }
157 
setCurrent(const AudioMsgId & audioId)158 void Instance::setCurrent(const AudioMsgId &audioId) {
159 	if (const auto data = getData(audioId.type())) {
160 		if (data->current == audioId) {
161 			return;
162 		}
163 		const auto changed = [&](const AudioMsgId & check) {
164 			return (check.audio() != audioId.audio())
165 				|| (check.contextId() != audioId.contextId());
166 		};
167 		if (changed(data->current)
168 			&& data->streamed
169 			&& changed(data->streamed->id)) {
170 			clearStreamed(data);
171 		}
172 		data->current = audioId;
173 		data->isPlaying = false;
174 
175 		const auto item = (audioId.audio() && audioId.contextId())
176 			? audioId.audio()->owner().message(audioId.contextId())
177 			: nullptr;
178 		if (item) {
179 			setHistory(data, item->history());
180 		} else {
181 			data->history = nullptr;
182 			data->migrated = nullptr;
183 			data->session = nullptr;
184 		}
185 		_trackChangedNotifier.notify(data->type, true);
186 		refreshPlaylist(data);
187 	}
188 }
189 
setHistory(not_null<Data * > data,History * history)190 void Instance::setHistory(not_null<Data*> data, History *history) {
191 	if (history) {
192 		data->history = history->migrateToOrMe();
193 		data->migrated = data->history->migrateFrom();
194 		setSession(data, &history->session());
195 	} else {
196 		data->history = data->migrated = nullptr;
197 		setSession(data, nullptr);
198 	}
199 }
200 
setSession(not_null<Data * > data,Main::Session * session)201 void Instance::setSession(not_null<Data*> data, Main::Session *session) {
202 	if (data->session == session) {
203 		return;
204 	}
205 	data->playlistLifetime.destroy();
206 	data->sessionLifetime.destroy();
207 	data->session = session;
208 	if (session) {
209 		session->account().sessionChanges(
210 		) | rpl::start_with_next([=] {
211 			setSession(data, nullptr);
212 		}, data->sessionLifetime);
213 		session->data().itemRemoved(
214 		) | rpl::filter([=](not_null<const HistoryItem*> item) {
215 			return (data->current.contextId() == item->fullId());
216 		}) | rpl::start_with_next([=] {
217 			stopAndClear(data);
218 		}, data->sessionLifetime);
219 	} else {
220 		stopAndClear(data);
221 	}
222 }
223 
clearStreamed(not_null<Data * > data,bool savePosition)224 void Instance::clearStreamed(not_null<Data*> data, bool savePosition) {
225 	if (!data->streamed || data->streamed->clearing) {
226 		return;
227 	}
228 	data->streamed->clearing = true;
229 	if (savePosition) {
230 		SaveLastPlaybackPosition(
231 			data->current.audio(),
232 			data->streamed->instance.player().prepareLegacyState());
233 	}
234 	data->streamed->instance.stop();
235 	data->isPlaying = false;
236 	requestRoundVideoResize();
237 	emitUpdate(data->type);
238 	data->streamed = nullptr;
239 
240 	_roundPlaying = false;
241 	if (const auto window = App::wnd()) {
242 		if (const auto controller = window->sessionController()) {
243 			controller->disableGifPauseReason(
244 				Window::GifPauseReason::RoundPlaying);
245 		}
246 	}
247 }
248 
refreshPlaylist(not_null<Data * > data)249 void Instance::refreshPlaylist(not_null<Data*> data) {
250 	if (!validPlaylist(data)) {
251 		validatePlaylist(data);
252 	}
253 	playlistUpdated(data);
254 }
255 
playlistUpdated(not_null<Data * > data)256 void Instance::playlistUpdated(not_null<Data*> data) {
257 	if (data->playlistSlice) {
258 		const auto fullId = data->current.contextId();
259 		data->playlistIndex = data->playlistSlice->indexOf(fullId);
260 	} else {
261 		data->playlistIndex = std::nullopt;
262 	}
263 	data->playlistChanges.fire({});
264 }
265 
validPlaylist(not_null<Data * > data)266 bool Instance::validPlaylist(not_null<Data*> data) {
267 	if (const auto key = playlistKey(data)) {
268 		if (!data->playlistSlice) {
269 			return false;
270 		}
271 		using Key = SliceKey;
272 		const auto inSameDomain = [](const Key &a, const Key &b) {
273 			return (a.peerId == b.peerId)
274 				&& (a.migratedPeerId == b.migratedPeerId)
275 				&& (a.scheduled == b.scheduled);
276 		};
277 		const auto countDistanceInData = [&](const Key &a, const Key &b) {
278 			return [&](const SparseIdsMergedSlice &data) {
279 				return inSameDomain(a, b)
280 					? data.distance(a, b)
281 					: std::optional<int>();
282 			};
283 		};
284 
285 		if (key == data->playlistRequestedKey) {
286 			return true;
287 		} else if (!data->playlistSliceKey
288 			|| !data->playlistRequestedKey
289 			|| *data->playlistRequestedKey != *data->playlistSliceKey) {
290 			return false;
291 		}
292 		auto distance = data->playlistSlice
293 			| countDistanceInData(*key, *data->playlistRequestedKey)
294 			| func::abs;
295 		if (distance) {
296 			return (*distance < kIdsPreloadAfter);
297 		}
298 	}
299 	return !data->playlistSlice;
300 }
301 
validatePlaylist(not_null<Data * > data)302 void Instance::validatePlaylist(not_null<Data*> data) {
303 	data->playlistLifetime.destroy();
304 	if (const auto key = playlistKey(data)) {
305 		data->playlistRequestedKey = key;
306 
307 		const auto sharedMediaViewer = key->scheduled
308 			? SharedScheduledMediaViewer
309 			: SharedMediaMergedViewer;
310 		sharedMediaViewer(
311 			&data->history->session(),
312 			SharedMediaMergedKey(*key, data->overview),
313 			kIdsLimit,
314 			kIdsLimit
315 		) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) {
316 			data->playlistSlice = std::move(update);
317 			data->playlistSliceKey = key;
318 			playlistUpdated(data);
319 		}, data->playlistLifetime);
320 	} else {
321 		data->playlistSlice = std::nullopt;
322 		data->playlistSliceKey = data->playlistRequestedKey = std::nullopt;
323 		playlistUpdated(data);
324 	}
325 }
326 
playlistKey(not_null<Data * > data) const327 auto Instance::playlistKey(not_null<Data*> data) const
328 -> std::optional<SliceKey> {
329 	const auto contextId = data->current.contextId();
330 	const auto history = data->history;
331 	if (!contextId || !history) {
332 		return {};
333 	}
334 	const auto item = data->history->owner().message(contextId);
335 	if (!item || (!item->isRegular() && !item->isScheduled())) {
336 		return {};
337 	}
338 
339 	const auto universalId = (contextId.channel == history->channelId())
340 		? contextId.msg
341 		: (contextId.msg - ServerMaxMsgId);
342 	return SliceKey(
343 		data->history->peer->id,
344 		data->migrated ? data->migrated->peer->id : 0,
345 		universalId,
346 		item->isScheduled());
347 }
348 
itemByIndex(not_null<Data * > data,int index)349 HistoryItem *Instance::itemByIndex(not_null<Data*> data, int index) {
350 	if (!data->playlistSlice
351 		|| index < 0
352 		|| index >= data->playlistSlice->size()) {
353 		return nullptr;
354 	}
355 	Assert(data->history != nullptr);
356 	const auto fullId = (*data->playlistSlice)[index];
357 	return data->history->owner().message(fullId);
358 }
359 
moveInPlaylist(not_null<Data * > data,int delta,bool autonext)360 bool Instance::moveInPlaylist(
361 		not_null<Data*> data,
362 		int delta,
363 		bool autonext) {
364 	if (!data->playlistIndex) {
365 		return false;
366 	}
367 	const auto newIndex = *data->playlistIndex + delta;
368 	if (const auto item = itemByIndex(data, newIndex)) {
369 		if (const auto media = item->media()) {
370 			if (const auto document = media->document()) {
371 				if (autonext) {
372 					_switchToNextNotifier.notify({
373 						data->current,
374 						item->fullId()
375 					});
376 				}
377 				if (document->isAudioFile()
378 					|| document->isVoiceMessage()
379 					|| document->isVideoMessage()) {
380 					play(AudioMsgId(document, item->fullId()));
381 				}
382 				return true;
383 			}
384 		}
385 	}
386 	return false;
387 }
388 
previousAvailable(AudioMsgId::Type type) const389 bool Instance::previousAvailable(AudioMsgId::Type type) const {
390 	const auto data = getData(type);
391 	Assert(data != nullptr);
392 	return data->playlistIndex
393 		&& data->playlistSlice
394 		&& (*data->playlistIndex > 0);
395 }
396 
nextAvailable(AudioMsgId::Type type) const397 bool Instance::nextAvailable(AudioMsgId::Type type) const {
398 	const auto data = getData(type);
399 	Assert(data != nullptr);
400 	return data->playlistIndex
401 		&& data->playlistSlice
402 		&& (*data->playlistIndex + 1 < data->playlistSlice->size());
403 }
404 
playlistChanges(AudioMsgId::Type type) const405 rpl::producer<> Media::Player::Instance::playlistChanges(
406 		AudioMsgId::Type type) const {
407 	const auto data = getData(type);
408 	Assert(data != nullptr);
409 	return data->playlistChanges.events();
410 }
411 
stops(AudioMsgId::Type type) const412 rpl::producer<> Media::Player::Instance::stops(AudioMsgId::Type type) const {
413 	return _playerStopped.events(
414 	) | rpl::filter([=](auto t) {
415 		return t == type;
416 	}) | rpl::to_empty;
417 }
418 
startsPlay(AudioMsgId::Type type) const419 rpl::producer<> Media::Player::Instance::startsPlay(
420 		AudioMsgId::Type type) const {
421 	return _playerStartedPlay.events(
422 	) | rpl::filter([=](auto t) {
423 		return t == type;
424 	}) | rpl::to_empty;
425 }
426 
seekingChanges(AudioMsgId::Type type) const427 auto Media::Player::Instance::seekingChanges(AudioMsgId::Type type) const
428 -> rpl::producer<Media::Player::Instance::Seeking> {
429 	return _seekingChanges.events(
430 	) | rpl::filter([=](SeekingChanges data) {
431 		return data.type == type;
432 	}) | rpl::map([](SeekingChanges data) {
433 		return data.seeking;
434 	});
435 }
436 
instance()437 not_null<Instance*> instance() {
438 	Expects(SingleInstance != nullptr);
439 	return SingleInstance;
440 }
441 
play(AudioMsgId::Type type)442 void Instance::play(AudioMsgId::Type type) {
443 	if (const auto data = getData(type)) {
444 		if (!data->streamed || IsStopped(getState(type).state)) {
445 			play(data->current);
446 		} else {
447 			if (data->streamed->instance.active()) {
448 				data->streamed->instance.resume();
449 			}
450 			emitUpdate(type);
451 		}
452 		data->resumeOnCallEnd = false;
453 	}
454 }
455 
play(const AudioMsgId & audioId)456 void Instance::play(const AudioMsgId &audioId) {
457 	const auto document = audioId.audio();
458 	if (!document) {
459 		return;
460 	}
461 	if (document->isAudioFile()
462 		|| document->isVoiceMessage()
463 		|| document->isVideoMessage()) {
464 		auto shared = document->owner().streaming().sharedDocument(
465 			document,
466 			audioId.contextId());
467 		if (!shared) {
468 			return;
469 		}
470 		playStreamed(audioId, std::move(shared));
471 	}
472 	if (document->isVoiceMessage() || document->isVideoMessage()) {
473 		document->owner().markMediaRead(document);
474 	}
475 	_playerStartedPlay.fire_copy({audioId.type()});
476 }
477 
playPause(const AudioMsgId & audioId)478 void Instance::playPause(const AudioMsgId &audioId) {
479 	const auto now = current(audioId.type());
480 	if (now.audio() == audioId.audio()
481 		&& now.contextId() == audioId.contextId()) {
482 		playPause(audioId.type());
483 	} else {
484 		play(audioId);
485 	}
486 }
487 
playStreamed(const AudioMsgId & audioId,std::shared_ptr<Streaming::Document> shared)488 void Instance::playStreamed(
489 		const AudioMsgId &audioId,
490 		std::shared_ptr<Streaming::Document> shared) {
491 	Expects(audioId.audio() != nullptr);
492 
493 	const auto data = getData(audioId.type());
494 	Assert(data != nullptr);
495 
496 	clearStreamed(data, data->current.audio() != audioId.audio());
497 	data->streamed = std::make_unique<Streamed>(
498 		audioId,
499 		std::move(shared));
500 	data->streamed->instance.lockPlayer();
501 
502 	data->streamed->instance.player().updates(
503 	) | rpl::start_with_next_error([=](Streaming::Update &&update) {
504 		handleStreamingUpdate(data, std::move(update));
505 	}, [=](Streaming::Error &&error) {
506 		handleStreamingError(data, std::move(error));
507 	}, data->streamed->lifetime);
508 
509 	data->streamed->instance.play(streamingOptions(audioId));
510 
511 	emitUpdate(audioId.type());
512 }
513 
streamingOptions(const AudioMsgId & audioId,crl::time position)514 Streaming::PlaybackOptions Instance::streamingOptions(
515 		const AudioMsgId &audioId,
516 		crl::time position) {
517 	const auto document = audioId.audio();
518 	auto result = Streaming::PlaybackOptions();
519 	result.mode = (document && document->isVideoMessage())
520 		? Streaming::Mode::Both
521 		: Streaming::Mode::Audio;
522 	result.speed = audioId.changeablePlaybackSpeed()
523 		? VoicePlaybackSpeed()
524 		: 1.;
525 	result.audioId = audioId;
526 	if (position >= 0) {
527 		result.position = position;
528 	} else if (document) {
529 		auto &settings = document->session().settings();
530 		result.position = settings.mediaLastPlaybackPosition(document->id);
531 		settings.setMediaLastPlaybackPosition(document->id, 0);
532 	} else {
533 		result.position = 0;
534 	}
535 	return result;
536 }
537 
pause(AudioMsgId::Type type)538 void Instance::pause(AudioMsgId::Type type) {
539 	if (const auto data = getData(type)) {
540 		if (data->streamed) {
541 			if (data->streamed->instance.active()) {
542 				data->streamed->instance.pause();
543 			}
544 			emitUpdate(type);
545 		}
546 	}
547 }
548 
stop(AudioMsgId::Type type)549 void Instance::stop(AudioMsgId::Type type) {
550 	if (const auto data = getData(type)) {
551 		if (data->streamed) {
552 			clearStreamed(data);
553 		}
554 		data->resumeOnCallEnd = false;
555 		_playerStopped.fire_copy({type});
556 	}
557 }
558 
stopAndClear(not_null<Data * > data)559 void Instance::stopAndClear(not_null<Data*> data) {
560 	stop(data->type);
561 	_tracksFinishedNotifier.notify(data->type);
562 	*data = Data(data->type, data->overview);
563 }
564 
playPause(AudioMsgId::Type type)565 void Instance::playPause(AudioMsgId::Type type) {
566 	if (const auto data = getData(type)) {
567 		if (!data->streamed) {
568 			play(data->current);
569 		} else {
570 			auto &streamed = data->streamed->instance;
571 			if (!streamed.active()) {
572 				streamed.play(streamingOptions(data->streamed->id));
573 			} else if (streamed.paused()) {
574 				streamed.resume();
575 			} else {
576 				streamed.pause();
577 			}
578 			emitUpdate(type);
579 		}
580 		data->resumeOnCallEnd = false;
581 	}
582 }
583 
pauseOnCall(AudioMsgId::Type type)584 void Instance::pauseOnCall(AudioMsgId::Type type) {
585 	const auto state = getState(type);
586 	if (!state.id
587 		|| IsStopped(state.state)
588 		|| IsPaused(state.state)
589 		|| state.state == State::Pausing) {
590 		return;
591 	}
592 	pause(type);
593 	if (const auto data = getData(type)) {
594 		data->resumeOnCallEnd = true;
595 	}
596 }
597 
resumeOnCall(AudioMsgId::Type type)598 void Instance::resumeOnCall(AudioMsgId::Type type) {
599 	if (const auto data = getData(type)) {
600 		if (data->resumeOnCallEnd) {
601 			data->resumeOnCallEnd = false;
602 			play(type);
603 		}
604 	}
605 }
606 
next(AudioMsgId::Type type)607 bool Instance::next(AudioMsgId::Type type) {
608 	if (const auto data = getData(type)) {
609 		return moveInPlaylist(data, 1, false);
610 	}
611 	return false;
612 }
613 
previous(AudioMsgId::Type type)614 bool Instance::previous(AudioMsgId::Type type) {
615 	if (const auto data = getData(type)) {
616 		return moveInPlaylist(data, -1, false);
617 	}
618 	return false;
619 }
620 
playPauseCancelClicked(AudioMsgId::Type type)621 void Instance::playPauseCancelClicked(AudioMsgId::Type type) {
622 	if (isSeeking(type)) {
623 		return;
624 	}
625 
626 	const auto data = getData(type);
627 	if (!data) {
628 		return;
629 	}
630 	const auto state = getState(type);
631 	const auto showPause = ShowPauseIcon(state.state);
632 	const auto audio = state.id.audio();
633 	if (audio && audio->loading() && !data->streamed) {
634 		audio->cancel();
635 	} else if (showPause) {
636 		pause(type);
637 	} else {
638 		play(type);
639 	}
640 }
641 
startSeeking(AudioMsgId::Type type)642 void Instance::startSeeking(AudioMsgId::Type type) {
643 	if (auto data = getData(type)) {
644 		data->seeking = data->current;
645 	}
646 	pause(type);
647 	emitUpdate(type);
648 	_seekingChanges.fire({ .seeking = Seeking::Start, .type = type });
649 }
650 
finishSeeking(AudioMsgId::Type type,float64 progress)651 void Instance::finishSeeking(AudioMsgId::Type type, float64 progress) {
652 	if (const auto data = getData(type)) {
653 		if (const auto streamed = data->streamed.get()) {
654 			const auto &info = streamed->instance.info();
655 			const auto duration = info.audio.state.duration;
656 			if (duration != kTimeUnknown) {
657 				const auto position = crl::time(base::SafeRound(
658 					std::clamp(progress, 0., 1.) * duration));
659 				streamed->instance.play(streamingOptions(
660 					streamed->id,
661 					position));
662 				emitUpdate(type);
663 			}
664 		}
665 	}
666 	cancelSeeking(type);
667 	_seekingChanges.fire({ .seeking = Seeking::Finish, .type = type });
668 }
669 
cancelSeeking(AudioMsgId::Type type)670 void Instance::cancelSeeking(AudioMsgId::Type type) {
671 	if (const auto data = getData(type)) {
672 		data->seeking = AudioMsgId();
673 	}
674 	emitUpdate(type);
675 	_seekingChanges.fire({ .seeking = Seeking::Cancel, .type = type });
676 }
677 
updateVoicePlaybackSpeed()678 void Instance::updateVoicePlaybackSpeed() {
679 	if (const auto data = getData(getActiveType())) {
680 		if (!data->current.changeablePlaybackSpeed()) {
681 			return;
682 		}
683 		if (const auto streamed = data->streamed.get()) {
684 			streamed->instance.setSpeed(VoicePlaybackSpeed());
685 		}
686 	}
687 }
688 
documentLoadProgress(DocumentData * document)689 void Instance::documentLoadProgress(DocumentData *document) {
690 	const auto type = document->isAudioFile()
691 		? AudioMsgId::Type::Song
692 		: AudioMsgId::Type::Voice;
693 	emitUpdate(type, [&](const AudioMsgId &audioId) {
694 		return (audioId.audio() == document);
695 	});
696 }
697 
emitUpdate(AudioMsgId::Type type)698 void Instance::emitUpdate(AudioMsgId::Type type) {
699 	emitUpdate(type, [](const AudioMsgId &playing) { return true; });
700 }
701 
getState(AudioMsgId::Type type) const702 TrackState Instance::getState(AudioMsgId::Type type) const {
703 	if (const auto data = getData(type)) {
704 		if (data->streamed) {
705 			return data->streamed->instance.player().prepareLegacyState();
706 		}
707 	}
708 	return TrackState();
709 }
710 
roundVideoStreamed(HistoryItem * item) const711 Streaming::Instance *Instance::roundVideoStreamed(HistoryItem *item) const {
712 	if (!item) {
713 		return nullptr;
714 	} else if (const auto data = getData(AudioMsgId::Type::Voice)) {
715 		if (const auto streamed = data->streamed.get()) {
716 			if (streamed->id.contextId() == item->fullId()) {
717 				const auto player = &streamed->instance.player();
718 				if (player->ready() && !player->videoSize().isEmpty()) {
719 					return &streamed->instance;
720 				}
721 			}
722 		}
723 	}
724 	return nullptr;
725 }
726 
roundVideoPlayback(HistoryItem * item) const727 View::PlaybackProgress *Instance::roundVideoPlayback(
728 		HistoryItem *item) const {
729 	return roundVideoStreamed(item)
730 		? &getData(AudioMsgId::Type::Voice)->streamed->progress
731 		: nullptr;
732 }
733 
734 template <typename CheckCallback>
emitUpdate(AudioMsgId::Type type,CheckCallback check)735 void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) {
736 	if (const auto data = getData(type)) {
737 		const auto state = getState(type);
738 		if (!state.id || !check(state.id)) {
739 			return;
740 		}
741 		setCurrent(state.id);
742 		if (const auto streamed = data->streamed.get()) {
743 			if (!streamed->instance.info().video.size.isEmpty()) {
744 				streamed->progress.updateState(state);
745 			}
746 		}
747 		_updatedNotifier.fire_copy({state});
748 		if (data->isPlaying && state.state == State::StoppedAtEnd) {
749 			if (data->repeatEnabled) {
750 				play(data->current);
751 			} else if (!moveInPlaylist(data, 1, true)) {
752 				_tracksFinishedNotifier.notify(type);
753 			}
754 		}
755 		data->isPlaying = !IsStopped(state.state);
756 	}
757 }
758 
setupShortcuts()759 void Instance::setupShortcuts() {
760 	Shortcuts::Requests(
761 	) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
762 		using Command = Shortcuts::Command;
763 		request->check(Command::MediaPlay) && request->handle([=] {
764 			playPause();
765 			return true;
766 		});
767 		request->check(Command::MediaPause) && request->handle([=] {
768 			pause();
769 			return true;
770 		});
771 		request->check(Command::MediaPlayPause) && request->handle([=] {
772 			playPause();
773 			return true;
774 		});
775 		request->check(Command::MediaStop) && request->handle([=] {
776 			stop();
777 			return true;
778 		});
779 		request->check(Command::MediaPrevious) && request->handle([=] {
780 			previous();
781 			return true;
782 		});
783 		request->check(Command::MediaNext) && request->handle([=] {
784 			next();
785 			return true;
786 		});
787 	}, _lifetime);
788 }
789 
pauseGifByRoundVideo() const790 bool Instance::pauseGifByRoundVideo() const {
791 	return _roundPlaying;
792 }
793 
handleStreamingUpdate(not_null<Data * > data,Streaming::Update && update)794 void Instance::handleStreamingUpdate(
795 		not_null<Data*> data,
796 		Streaming::Update &&update) {
797 	using namespace Streaming;
798 
799 	v::match(update.data, [&](Information &update) {
800 		if (!update.video.size.isEmpty()) {
801 			data->streamed->progress.setValueChangedCallback([=](
802 					float64,
803 					float64) {
804 				requestRoundVideoRepaint();
805 			});
806 			_roundPlaying = true;
807 			if (const auto window = App::wnd()) {
808 				if (const auto controller = window->sessionController()) {
809 					controller->enableGifPauseReason(
810 						Window::GifPauseReason::RoundPlaying);
811 				}
812 			}
813 			requestRoundVideoResize();
814 		}
815 		emitUpdate(data->type);
816 	}, [&](PreloadedVideo &update) {
817 		//emitUpdate(data->type, [](AudioMsgId) { return true; });
818 	}, [&](UpdateVideo &update) {
819 		emitUpdate(data->type);
820 	}, [&](PreloadedAudio &update) {
821 		//emitUpdate(data->type, [](AudioMsgId) { return true; });
822 	}, [&](UpdateAudio &update) {
823 		emitUpdate(data->type);
824 	}, [&](WaitingForData) {
825 	}, [&](MutedByOther) {
826 	}, [&](Finished) {
827 		emitUpdate(data->type);
828 		if (data->streamed && data->streamed->instance.player().finished()) {
829 			clearStreamed(data);
830 		}
831 	});
832 }
833 
roundVideoItem() const834 HistoryItem *Instance::roundVideoItem() const {
835 	const auto data = getData(AudioMsgId::Type::Voice);
836 	return (data->streamed
837 		&& !data->streamed->instance.info().video.size.isEmpty()
838 		&& data->history)
839 		? data->history->owner().message(data->streamed->id.contextId())
840 		: nullptr;
841 }
842 
requestRoundVideoResize() const843 void Instance::requestRoundVideoResize() const {
844 	if (const auto item = roundVideoItem()) {
845 		item->history()->owner().requestItemResize(item);
846 	}
847 }
848 
requestRoundVideoRepaint() const849 void Instance::requestRoundVideoRepaint() const {
850 	if (const auto item = roundVideoItem()) {
851 		item->history()->owner().requestItemRepaint(item);
852 	}
853 }
854 
handleStreamingError(not_null<Data * > data,Streaming::Error && error)855 void Instance::handleStreamingError(
856 		not_null<Data*> data,
857 		Streaming::Error &&error) {
858 	Expects(data->streamed != nullptr);
859 
860 	const auto document = data->streamed->id.audio();
861 	const auto contextId = data->streamed->id.contextId();
862 	if (error == Streaming::Error::NotStreamable) {
863 		DocumentSaveClickHandler::Save(
864 			(contextId ? contextId : ::Data::FileOrigin()),
865 			document);
866 	} else if (error == Streaming::Error::OpenFailed) {
867 		DocumentSaveClickHandler::Save(
868 			(contextId ? contextId : ::Data::FileOrigin()),
869 			document,
870 			DocumentSaveClickHandler::Mode::ToFile);
871 	}
872 	emitUpdate(data->type);
873 	if (data->streamed && data->streamed->instance.player().failed()) {
874 		clearStreamed(data);
875 	}
876 }
877 
878 } // namespace Player
879 } // namespace Media
880