From 2f964d0415185082e87a8831b1a56dd359ce6281 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 3 Jul 2020 17:59:55 +0300 Subject: [PATCH] Refactored and moved to separate file audio player in touchbar. --- Telegram/CMakeLists.txt | 2 + .../media/player/media_player_instance.cpp | 2 +- .../media/player/media_player_instance.h | 2 +- .../mac/touchbar/mac_touchbar_audio.h | 15 ++ .../mac/touchbar/mac_touchbar_audio.mm | 241 ++++++++++++++++++ 5 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_audio.h create mode 100644 Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_audio.mm diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 4fe8a07bbd..e0f17aa22b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -891,6 +891,8 @@ PRIVATE platform/mac/specific_mac_p.h platform/mac/window_title_mac.mm platform/mac/window_title_mac.h + platform/mac/touchbar/mac_touchbar_audio.h + platform/mac/touchbar/mac_touchbar_audio.mm platform/mac/touchbar/mac_touchbar_common.h platform/mac/touchbar/mac_touchbar_common.mm platform/win/audio_win.cpp diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 65854cf6e9..eb5cadaa6a 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -388,7 +388,7 @@ rpl::producer<> Media::Player::Instance::playlistChanges( return data->playlistChanges.events(); } -Instance *instance() { +not_null instance() { Expects(SingleInstance != nullptr); return SingleInstance; } diff --git a/Telegram/SourceFiles/media/player/media_player_instance.h b/Telegram/SourceFiles/media/player/media_player_instance.h index 6f11484d1a..2f9075791c 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.h +++ b/Telegram/SourceFiles/media/player/media_player_instance.h @@ -47,7 +47,7 @@ void SaveLastPlaybackPosition( not_null document, const TrackState &state); -Instance *instance(); +not_null instance(); class Instance : private base::Subscriber { public: diff --git a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_audio.h b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_audio.h new file mode 100644 index 0000000000..ec4596c67e --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_audio.h @@ -0,0 +1,15 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#import + +API_AVAILABLE(macos(10.12.2)) +@interface TouchBarAudioPlayer : NSTouchBar +- (rpl::producer<>)closeRequests; +@end diff --git a/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_audio.mm b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_audio.mm new file mode 100644 index 0000000000..4f12bcf39c --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/touchbar/mac_touchbar_audio.mm @@ -0,0 +1,241 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "platform/mac/touchbar/mac_touchbar_audio.h" + +#include "core/sandbox.h" +#include "media/audio/media_audio.h" +#include "media/player/media_player_instance.h" +#include "platform/mac/touchbar/mac_touchbar_common.h" +#include "styles/style_media_player.h" + +#import +#import +#import +#import + +#ifndef OS_OSX + +NSImage *qt_mac_create_nsimage(const QPixmap &pm); +using TouchBar::kCircleDiameter; +using TouchBar::CreateNSImageFromStyleIcon; + +namespace { + +constexpr auto kSongType = AudioMsgId::Type::Song; + +const auto *kCustomizationIdPlayer = @"telegram.touchbar"; + +inline NSTouchBarItemIdentifier Format(NSString *s) { + return [NSString stringWithFormat:@"%@.%@", kCustomizationIdPlayer, s]; +} +const auto kSeekBarItemIdentifier = Format(@"seekbar"); +const auto kPlayItemIdentifier = Format(@"play"); +const auto kNextItemIdentifier = Format(@"nextItem"); +const auto kPreviousItemIdentifier = Format(@"previousItem"); +const auto kClosePlayerItemIdentifier = Format(@"closePlayer"); +const auto kCurrentPositionItemIdentifier = Format(@"currentPosition"); + +API_AVAILABLE(macos(10.12.2)) +NSButton* CreateTouchBarButton( + const style::icon &icon, + rpl::lifetime &lifetime, + Fn callback) { + id block = [^{ + Core::Sandbox::Instance().customEnterFromEventLoop(callback); + } copy]; + + NSButton* button = [NSButton + buttonWithImage:CreateNSImageFromStyleIcon(icon, kCircleDiameter / 2) + target:block + action:@selector(invoke)]; + lifetime.add([=] { + [block release]; + }); + return button; +} + +} // namespace + +#pragma mark - TouchBarAudioPlayer + +@interface TouchBarAudioPlayer() +@end // @interface TouchBarAudioPlayer + +@implementation TouchBarAudioPlayer { + rpl::event_stream<> _closeRequests; + rpl::producer< Media::Player::TrackState> _trackState; + + rpl::lifetime _lifetime; +} + +- (id)init { + self = [super init]; + if (!self) { + return self; + } + self.delegate = self; + self.customizationIdentifier = kCustomizationIdPlayer.lowercaseString; + self.defaultItemIdentifiers = @[ + kPlayItemIdentifier, + kPreviousItemIdentifier, + kNextItemIdentifier, + kSeekBarItemIdentifier, + kClosePlayerItemIdentifier]; + self.customizationAllowedItemIdentifiers = @[ + kPlayItemIdentifier, + kPreviousItemIdentifier, + kNextItemIdentifier, + // kCurrentPositionItemIdentifier, // TODO. + kSeekBarItemIdentifier, + kClosePlayerItemIdentifier]; + + _trackState = Media::Player::instance()->updatedNotifier( + ) | rpl::filter([=](const Media::Player::TrackState &state) { + return state.id.type() == kSongType; + }); + + return self; +} + +- (NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar + makeItemForIdentifier:(NSTouchBarItemIdentifier)itemId { + if (!touchBar) { + return nil; + } + const auto mediaPlayer = Media::Player::instance(); + const auto isEqual = [&](NSString *string) { + return [itemId isEqualToString:string]; + }; + + if (isEqual(kSeekBarItemIdentifier)) { + auto *item = [[NSSliderTouchBarItem alloc] initWithIdentifier:itemId]; + item.slider.minValue = 0.0f; + item.slider.maxValue = 1.0f; + item.customizationLabel = @"Seek Bar"; + + id block = [^{ + // https://stackoverflow.com/a/45891017 + auto *event = [[NSApplication sharedApplication] currentEvent]; + const auto touchUp = [event + touchesMatchingPhase:NSTouchPhaseEnded + inView:nil].count > 0; + Core::Sandbox::Instance().customEnterFromEventLoop([=] { + if (touchUp) { + mediaPlayer->finishSeeking(kSongType, item.doubleValue); + } else { + mediaPlayer->startSeeking(kSongType); + } + }); + } copy]; + + rpl::duplicate( + _trackState + ) | rpl::start_with_next([=](const Media::Player::TrackState &state) { + const auto stop = Media::Player::IsStoppedOrStopping(state.state); + const auto duration = double(stop ? 0 : state.length); + auto slider = item.slider; + if (duration <= 0) { + slider.enabled = false; + slider.doubleValue = 0; + } else { + slider.enabled = true; + if (!slider.highlighted) { + const auto pos = stop + ? 0 + : std::max(state.position, int64(0)); + slider.doubleValue = (pos / duration) * slider.maxValue; + } + } + }, _lifetime); + + item.target = block; + item.action = @selector(invoke); + _lifetime.add([=] { + [block release]; + }); + return [item autorelease]; + } else if (isEqual(kNextItemIdentifier) + || isEqual(kPreviousItemIdentifier)) { + const auto isNext = isEqual(kNextItemIdentifier); + auto *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:itemId]; + + auto *button = CreateTouchBarButton( + isNext + ? st::touchBarIconPlayerNext + : st::touchBarIconPlayerPrevious, + _lifetime, + [=] { isNext // TODO + ? mediaPlayer->next(kSongType) + : mediaPlayer->previous(kSongType); }); + rpl::duplicate( + _trackState + ) | rpl::start_with_next([=] { + const auto newValue = isNext + ? mediaPlayer->nextAvailable(kSongType) + : mediaPlayer->previousAvailable(kSongType); + if (button.enabled != newValue) { + button.enabled = newValue; + } + }, _lifetime); + + item.view = button; + item.customizationLabel = [NSString + stringWithFormat:@"%@ Playlist Item", + isNext ? @"Next" : @"Previous"]; + return [item autorelease]; + } else if (isEqual(kPlayItemIdentifier)) { + auto *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:itemId]; + + auto *button = CreateTouchBarButton( + st::touchBarIconPlayerPause, + _lifetime, + [=] { mediaPlayer->playPause(kSongType); }); + + auto *pause = [button.image retain]; + auto *play = [CreateNSImageFromStyleIcon( + st::touchBarIconPlayerPlay, + kCircleDiameter / 2) retain]; + + rpl::duplicate( + _trackState + ) | rpl::start_with_next([=](const auto &state) { + button.image = (state.state == Media::Player::State::Playing) + ? pause + : play; + }, _lifetime); + + _lifetime.add([=] { + // Avoid a memory leak from retaining of images. + [pause release]; + [play release]; + }); + + item.view = button; + item.customizationLabel = @"Play/Pause"; + return [item autorelease]; + } else if (isEqual(kClosePlayerItemIdentifier)) { + auto *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:itemId]; + auto *button = CreateTouchBarButton( + st::touchBarIconPlayerClose, + _lifetime, + [=] { _closeRequests.fire({}); }); + + item.view = button; + item.customizationLabel = @"Close Player"; + return [item autorelease]; + } + return nil; +} + +- (rpl::producer<>)closeRequests { + return _closeRequests.events(); +} + +@end // @implementation TouchBarAudioPlayer + +#endif // OS_OSX