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