// Copyright Epic Games, Inc. All Rights Reserved. #include "PixelStreaming2Toolbar.h" #include "ToolMenus.h" #include "PixelStreaming2Commands.h" #include "PixelStreaming2Style.h" #include "IPixelStreaming2Module.h" #include "Widgets/SViewport.h" #include "PixelStreaming2EditorModule.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SButton.h" #include #include "Widgets/Input/SNumericEntryBox.h" #include "PixelStreaming2PluginSettings.h" #include "Logging.h" #include "UtilsCoder.h" #include "Video/Encoders/Configs/VideoEncoderConfigAV1.h" #include "Video/Encoders/Configs/VideoEncoderConfigH264.h" #include "IDesktopPlatform.h" #include "DesktopPlatformModule.h" #define LOCTEXT_NAMESPACE "PixelStreaming2Editor" DECLARE_LOG_CATEGORY_EXTERN(LogPixelStreaming2Toolbar, Log, All); DEFINE_LOG_CATEGORY(LogPixelStreaming2Toolbar); void SetCodec(EVideoCodec Codec) { UPixelStreaming2PluginSettings::CVarEncoderCodec.AsVariable()->SetWithCurrentPriority(*UE::PixelStreaming2::GetCVarStringFromEnum(Codec)); } EVideoCodec GetCodec() { return UE::PixelStreaming2::GetEnumFromCVar(UPixelStreaming2PluginSettings::CVarEncoderCodec); } void SetUseRemoteSignallingServer(bool UseRemoteSignallingServer) { UPixelStreaming2PluginSettings::CVarEditorUseRemoteSignallingServer.AsVariable()->SetWithCurrentPriority(UseRemoteSignallingServer); } bool GetUseRemoteSignallingServer() { return UPixelStreaming2PluginSettings::CVarEditorUseRemoteSignallingServer.GetValueOnAnyThread(); } void SetServeHttps(bool bServeHttps) { IPixelStreaming2EditorModule::Get().SetServeHttps(bServeHttps); } bool GetServeHttps() { return IPixelStreaming2EditorModule::Get().GetServeHttps(); } void SetSSLCertificatePath(const FString& Path) { IPixelStreaming2EditorModule::Get().SetSSLCertificatePath(Path); } FString GetSSLCertificatePath() { return IPixelStreaming2EditorModule::Get().GetSSLCertificatePath(); } void SetSSLPrivateKeyPath(const FString& Path) { IPixelStreaming2EditorModule::Get().SetSSLPrivateKeyPath(Path); } FString GetSSLPrivateKeyPath() { return IPixelStreaming2EditorModule::Get().GetSSLPrivateKeyPath(); } namespace UE::EditorPixelStreaming2 { TSharedRef CreateTextboxWithFileSelector(const FString& Label, float MaxWidth, TFunction GetTextLambda, TFunction OnTextChangedLambda, TFunction OnTextCommittedLambda, TFunction OnClickedLambda, TFunction GetVisibilityLambda, TFunction IsEnabledLambda) { TSharedRef Widget = SNew(SHorizontalBox) .Visibility_Lambda([GetVisibilityLambda]() { return GetVisibilityLambda(); }) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(FMargin(36.0f, 3.0f, 8.0f, 3.0f)) [SNew(STextBlock) .Text(FText::FromString(Label)) .ColorAndOpacity(FSlateColor(FLinearColor(1.0f, 1.0f, 1.0f)))] + SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) .MaxWidth(MaxWidth) [SNew(SEditableTextBox) .OverflowPolicy(ETextOverflowPolicy::MiddleEllipsis) .Text_Lambda([GetTextLambda]() { return FText::FromString(GetTextLambda()); }) .OnTextChanged_Lambda([OnTextChangedLambda](const FText& Text) { OnTextChangedLambda(Text); }) .OnTextCommitted_Lambda([OnTextCommittedLambda](const FText& Text, const ETextCommit::Type CommitType) { OnTextCommittedLambda(Text, CommitType); }) .IsEnabled_Lambda([IsEnabledLambda]() { return IsEnabledLambda(); }) ] + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Right) [SNew(SButton) .OnClicked_Lambda([OnClickedLambda]() { OnClickedLambda(); return FReply::Handled(); }) .IsEnabled_Lambda([IsEnabledLambda]() { return IsEnabledLambda(); }) .ButtonStyle(FAppStyle::Get(), "SimpleButton") [SNew(SImage) .Image(FAppStyle::Get().GetBrush("Icons.BrowseContent")) .ColorAndOpacity(FSlateColor::UseForeground())]]; return Widget; } FPixelStreaming2Toolbar::FPixelStreaming2Toolbar() { FPixelStreaming2Commands::Register(); PluginCommands = MakeShared(); PluginCommands->MapAction( FPixelStreaming2Commands::Get().ExternalSignalling, FExecuteAction::CreateLambda([]() { SetUseRemoteSignallingServer(!GetUseRemoteSignallingServer()); IPixelStreaming2EditorModule::Get().StopSignalling(true); }), FCanExecuteAction::CreateLambda([] { TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); if (!SignallingServer.IsValid() || !SignallingServer->HasLaunched()) { return true; } return false; }), FIsActionChecked::CreateLambda([]() { return GetUseRemoteSignallingServer(); })); PluginCommands->MapAction( FPixelStreaming2Commands::Get().ServeHttps, FExecuteAction::CreateLambda([]() { SetServeHttps(!GetServeHttps()); }), FCanExecuteAction::CreateLambda([] { TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); if (!SignallingServer.IsValid() || !SignallingServer->HasLaunched()) { return true; } return false; }), FIsActionChecked::CreateLambda([]() { return GetServeHttps(); })); PluginCommands->MapAction( FPixelStreaming2Commands::Get().StreamLevelEditor, FExecuteAction::CreateLambda([]() { IPixelStreaming2EditorModule::Get().StartStreaming(EPixelStreaming2EditorStreamTypes::LevelEditorViewport); }), FCanExecuteAction::CreateLambda([] { FPixelStreaming2EditorModule& EditorModule = static_cast(IPixelStreaming2EditorModule::Get()); if (TSharedPtr Streamer = IPixelStreaming2Module::Get().FindStreamer(EditorModule.GetEditorStreamerID())) { return !Streamer->IsStreaming(); } return false; })); PluginCommands->MapAction( FPixelStreaming2Commands::Get().StreamEditor, FExecuteAction::CreateLambda([]() { IPixelStreaming2EditorModule::Get().StartStreaming(EPixelStreaming2EditorStreamTypes::Editor); }), FCanExecuteAction::CreateLambda([] { FPixelStreaming2EditorModule& EditorModule = static_cast(IPixelStreaming2EditorModule::Get()); if (TSharedPtr Streamer = IPixelStreaming2Module::Get().FindStreamer(EditorModule.GetEditorStreamerID())) { return !Streamer->IsStreaming(); } return false; })); PluginCommands->MapAction( FPixelStreaming2Commands::Get().StartSignalling, FExecuteAction::CreateLambda([]() { IPixelStreaming2EditorModule::Get().StartSignalling(); }), FCanExecuteAction::CreateLambda([] { TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); if (!SignallingServer.IsValid() || !SignallingServer->HasLaunched()) { return true; } return false; })); PluginCommands->MapAction( FPixelStreaming2Commands::Get().StopSignalling, FExecuteAction::CreateLambda([]() { IPixelStreaming2EditorModule::Get().StopSignalling(true); }), FCanExecuteAction::CreateLambda([] { TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); if (SignallingServer.IsValid() && SignallingServer->HasLaunched()) { return true; } return false; })); PluginCommands->MapAction( FPixelStreaming2Commands::Get().VP8, FExecuteAction::CreateLambda([]() { SetCodec(EVideoCodec::VP8); }), FCanExecuteAction::CreateLambda([] { return true; }), FIsActionChecked::CreateLambda([]() { return GetCodec() == EVideoCodec::VP8; })); PluginCommands->MapAction( FPixelStreaming2Commands::Get().VP9, FExecuteAction::CreateLambda([]() { SetCodec(EVideoCodec::VP9); }), FCanExecuteAction::CreateLambda([] { return true; }), FIsActionChecked::CreateLambda([]() { return GetCodec() == EVideoCodec::VP9; })); PluginCommands->MapAction( FPixelStreaming2Commands::Get().H264, FExecuteAction::CreateLambda([]() { SetCodec(EVideoCodec::H264); }), FCanExecuteAction::CreateLambda([] { bool bCanChangeCodec = UE::PixelStreaming2::IsEncoderSupported(); return bCanChangeCodec; }), FIsActionChecked::CreateLambda([]() { return GetCodec() == EVideoCodec::H264; })); PluginCommands->MapAction( FPixelStreaming2Commands::Get().AV1, FExecuteAction::CreateLambda([]() { SetCodec(EVideoCodec::AV1); }), FCanExecuteAction::CreateLambda([] { bool bCanChangeCodec = UE::PixelStreaming2::IsEncoderSupported(); return bCanChangeCodec; }), FIsActionChecked::CreateLambda([]() { return GetCodec() == EVideoCodec::AV1; })); UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FPixelStreaming2Toolbar::RegisterMenus)); } FPixelStreaming2Toolbar::~FPixelStreaming2Toolbar() { FPixelStreaming2Commands::Unregister(); } void FPixelStreaming2Toolbar::RegisterMenus() { FToolMenuOwnerScoped OwnerScoped(this); { UToolMenu* CustomToolBar = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.User"); { FToolMenuSection& Section = CustomToolBar->AddSection("PixelStreaming2"); Section.AddSeparator("PixelStreaming2Seperator"); { // Settings dropdown FToolMenuEntry SettingsEntry = FToolMenuEntry::InitComboButton( "PixelStreaming2Menus", FUIAction(), FOnGetContent::CreateLambda( [&]() { FMenuBuilder MenuBuilder(false, PluginCommands, MakeShared(), false, &FCoreStyle::Get(), false); // Use external signalling server option MenuBuilder.BeginSection("Signalling Server Location", LOCTEXT("PixelStreaming2SSLocation", "Signalling Server Location")); MenuBuilder.AddMenuEntry(FPixelStreaming2Commands::Get().ExternalSignalling); MenuBuilder.EndSection(); // Embedded Signalling Server Config (streamer port & http port) RegisterEmbeddedSignallingServerConfig(MenuBuilder); // Signalling Server Viewer URLs RegisterSignallingServerURLs(MenuBuilder); // Remote Signalling Server Config (URL) RegisterRemoteSignallingServerConfig(MenuBuilder); // Pixel Streaming streamer controls RegisterStreamerControls(MenuBuilder); // Codec Config RegisterCodecConfig(MenuBuilder); return MenuBuilder.MakeWidget(); }), LOCTEXT("PixelStreaming2Menu", "Pixel Streaming 2"), LOCTEXT("PixelStreaming2MenuTooltip", "Configure Pixel Streaming"), FSlateIcon(FPixelStreaming2Style::GetStyleSetName(), "PixelStreaming2.Icon"), false, "PixelStreaming2Menu"); SettingsEntry.StyleNameOverride = "CalloutToolbar"; SettingsEntry.SetCommandList(PluginCommands); Section.AddEntry(SettingsEntry); } } } } void FPixelStreaming2Toolbar::RegisterEmbeddedSignallingServerConfig(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection( "Signalling Server Options", LOCTEXT("PixelStreaming2EmbeddedSSOptions", "Embedded Signalling Server Options"), TAttribute::Create([]() { return !GetUseRemoteSignallingServer() ? EVisibility::Visible : EVisibility::Collapsed; })); MenuBuilder.AddMenuEntry( FPixelStreaming2Commands::Get().ServeHttps, // Command NAME_None, // InExtensionHook TAttribute(), // Label override (leave empty to use default) TAttribute(), // Tooltip override (leave empty to use default) FSlateIcon(), // Icon override (leave default) NAME_None, // Tutorial highlight TAttribute::Create([]() { // Visibility lambda TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); return !GetUseRemoteSignallingServer() ? EVisibility::Visible : EVisibility::Collapsed; }) ); TSharedRef SSLCertificateInputBlock = CreateTextboxWithFileSelector( TEXT("SSL Certificate: "), // Label 150, // MaxWidth []() { return GetSSLCertificatePath(); }, // GetTextLambda [](const FText& Text) { SetSSLCertificatePath(Text.ToString()); }, // OnTextChangedLambda [](const FText& Text, const ETextCommit::Type CommitType) { SetSSLCertificatePath(Text.ToString()); }, // OnTextCommittedLambda [this]() { OnOpenFileBrowserClicked(EFileType::Certificate); }, // OnClickedLambda []() { return !GetUseRemoteSignallingServer() &&GetServeHttps() ? EVisibility::Visible : EVisibility::Collapsed; }, // GetVisibilityLambda []() { TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); return !(SignallingServer.IsValid() && SignallingServer->HasLaunched()); } // IsEnabledLambda ); MenuBuilder.AddWidget(SSLCertificateInputBlock, FText(), true); TSharedRef SSLPrivateKeyInputBlock = CreateTextboxWithFileSelector( TEXT("SSL Private Key: "), // Label 150, // MaxWidth []() { return GetSSLPrivateKeyPath(); }, // GetTextLambda [](const FText& Text) { SetSSLPrivateKeyPath(Text.ToString()); }, // OnTextChangedLambda [](const FText& Text, const ETextCommit::Type CommitType) { SetSSLPrivateKeyPath(Text.ToString()); }, // OnTextCommittedLambda [this]() { OnOpenFileBrowserClicked(EFileType::PrivateKey); }, // OnClickedLambda []() { return !GetUseRemoteSignallingServer() && GetServeHttps() ? EVisibility::Visible : EVisibility::Collapsed; }, // GetVisibilityLambda []() { TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); return !(SignallingServer.IsValid() && SignallingServer->HasLaunched()); } // IsEnabledLambda ); MenuBuilder.AddWidget(SSLPrivateKeyInputBlock, FText(), true); TSharedRef StreamerPortInputBlock = SNew(SHorizontalBox) .Visibility_Lambda([]() { return !GetUseRemoteSignallingServer() ? EVisibility::Visible : EVisibility::Collapsed; }) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(FMargin(36.0f, 3.0f, 8.0f, 3.0f)) [ SNew(STextBlock) .Text(FText::FromString(TEXT("Streamer Port: "))) .ToolTipText(LOCTEXT("PixelStreaming2StreamerPortTooltip", "The port the signalling server will listen for streamer connections on.")) .ColorAndOpacity(FSlateColor(FLinearColor(1.0f, 1.0f, 1.0f))) ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SNumericEntryBox) .MinValue(1) .Value_Lambda([]() { return IPixelStreaming2EditorModule::Get().GetStreamerPort(); }) .OnValueChanged_Lambda([](int32 InStreamerPort) { IPixelStreaming2EditorModule::Get().SetStreamerPort(InStreamerPort); }) .OnValueCommitted_Lambda([](int32 InStreamerPort, ETextCommit::Type InCommitType) { IPixelStreaming2EditorModule::Get().SetStreamerPort(InStreamerPort); }) .IsEnabled_Lambda([]() { TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); return !(SignallingServer.IsValid() && SignallingServer->HasLaunched()); }) ]; MenuBuilder.AddWidget(StreamerPortInputBlock, FText(), true); TSharedRef ViewerPortInputBlock = SNew(SHorizontalBox) .Visibility_Lambda([]() { return !GetUseRemoteSignallingServer() ? EVisibility::Visible : EVisibility::Collapsed; }) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(FMargin(36.0f, 3.0f, 8.0f, 3.0f)) [ SNew(STextBlock) .Text(FText::FromString(TEXT("Viewer Port: "))) .ToolTipText(LOCTEXT("PixelStreaming2ViewerPortTooltip", "The port the signalling server will listen for viewer connections on.")) .ColorAndOpacity(FSlateColor(FLinearColor(1.0f, 1.0f, 1.0f))) ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SNumericEntryBox) .MinValue(1) .Value_Lambda([]() { return IPixelStreaming2EditorModule::Get().GetViewerPort(); }) .OnValueChanged_Lambda([](int32 InViewerPort) { IPixelStreaming2EditorModule::Get().SetViewerPort(InViewerPort); }) .OnValueCommitted_Lambda([](int32 InViewerPort, ETextCommit::Type InCommitType) { IPixelStreaming2EditorModule::Get().SetViewerPort(InViewerPort); }) .IsEnabled_Lambda([]() { TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); return !(SignallingServer.IsValid() && SignallingServer->HasLaunched()); }) ]; MenuBuilder.AddWidget(ViewerPortInputBlock, FText(), true); MenuBuilder.AddMenuEntry( FPixelStreaming2Commands::Get().StartSignalling, // Command NAME_None, // InExtensionHook TAttribute(), // Label override (leave empty to use default) TAttribute(), // Tooltip override (leave empty to use default) FSlateIcon(), // Icon override (leave default) NAME_None, // Tutorial highlight TAttribute::Create([]() { // Visibility lambda TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); return !GetUseRemoteSignallingServer() && !(SignallingServer.IsValid() && SignallingServer->HasLaunched()) ? EVisibility::Visible : EVisibility::Collapsed; }) ); MenuBuilder.AddMenuEntry( FPixelStreaming2Commands::Get().StopSignalling, // Command NAME_None, // InExtensionHook TAttribute(), // Label override (leave empty to use default) TAttribute(), // Tooltip override (leave empty to use default) FSlateIcon(), // Icon override (leave default) NAME_None, // Tutorial highlight TAttribute::Create([]() { // Visibility lambda TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); return !GetUseRemoteSignallingServer() &&(SignallingServer.IsValid() && SignallingServer->HasLaunched()) ? EVisibility::Visible : EVisibility::Collapsed; }) ); MenuBuilder.EndSection(); } void FPixelStreaming2Toolbar::RegisterRemoteSignallingServerConfig(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection( "Remote Signalling Server Options", LOCTEXT("PixelStreaming2RemoteSSOptions", "Remote Signalling Server Options"), TAttribute::Create([]() { return GetUseRemoteSignallingServer() ? EVisibility::Visible : EVisibility::Collapsed; })); { TSharedRef URLInputBlock = SNew(SHorizontalBox) .Visibility_Lambda([]() { return GetUseRemoteSignallingServer() ? EVisibility::Visible : EVisibility::Collapsed; }) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(FMargin(36.0f, 3.0f, 8.0f, 3.0f)) [ SNew(STextBlock) .Text(FText::FromString(TEXT("Remote Signalling Server URL"))) .ColorAndOpacity(FSlateColor(FLinearColor(1.0f, 1.0f, 1.0f))) ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SEditableTextBox) .Text_Lambda([]() { FPixelStreaming2EditorModule& EditorModule = static_cast(IPixelStreaming2EditorModule::Get()); TSharedPtr Streamer = IPixelStreaming2Module::Get().FindStreamer(EditorModule.GetEditorStreamerID()); return FText::FromString(Streamer->GetConnectionURL()); }) .OnTextChanged_Lambda([](const FText& InText) { IPixelStreaming2Module::Get().ForEachStreamer([InText](TSharedPtr Streamer) { Streamer->SetConnectionURL(InText.ToString()); }); }) .OnTextCommitted_Lambda([](const FText& InText, ETextCommit::Type InTextCommit) { IPixelStreaming2Module::Get().ForEachStreamer([InText](TSharedPtr Streamer) { Streamer->SetConnectionURL(InText.ToString()); }); }) .IsEnabled_Lambda([]() { bool bCanChangeURL = true; IPixelStreaming2Module::Get().ForEachStreamer([&bCanChangeURL](TSharedPtr Streamer) { bCanChangeURL &= !Streamer->IsStreaming(); }); return bCanChangeURL; })]; MenuBuilder.AddWidget(URLInputBlock, FText(), true); } MenuBuilder.EndSection(); } void FPixelStreaming2Toolbar::RegisterSignallingServerURLs(FMenuBuilder& MenuBuilder) { TSharedRef AdapterBox = SNew(SVerticalBox); TArray> AdapterAddresses; ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetLocalAdapterAddresses(AdapterAddresses); for (TSharedPtr AdapterAddress : AdapterAddresses) { AdapterBox->AddSlot() .AutoHeight() .Padding(FMargin(32.f, 3.f, 0.f, 3.f)) [ SNew(STextBlock) .ColorAndOpacity(FSlateColor::UseSubduedForeground()) .Text_Lambda([AdapterAddress]() { return FText::FromString(FString::Printf( TEXT("%s://%s:%d"), GetServeHttps() ? TEXT("https") : TEXT("http"), *AdapterAddress->ToString(false), IPixelStreaming2EditorModule::Get().GetViewerPort() )); }) ]; } MenuBuilder.BeginSection( "Signalling Server URLs", LOCTEXT("PixelStreaming2SignallingURLs", "Signalling Server URLs"), TAttribute::Create([]() { TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); return (SignallingServer.IsValid() && SignallingServer->HasLaunched()) ? EVisibility::Visible : EVisibility::Collapsed; }) ); { MenuBuilder.AddWidget( SNew(SVerticalBox) .Visibility_Lambda([]() { TSharedPtr SignallingServer = IPixelStreaming2EditorModule::Get().GetSignallingServer(); return (SignallingServer.IsValid() && SignallingServer->HasLaunched()) ? EVisibility::Visible : EVisibility::Collapsed; }) + SVerticalBox::Slot() .AutoHeight() .Padding(FMargin(16.0f, 3.0f)) [ SNew(STextBlock) .ColorAndOpacity(FSlateColor::UseSubduedForeground()) .Text(LOCTEXT("SignallingTip", "The Signalling Server is running and may be accessed via the following URLs (network settings permitting):")) .WrapTextAt(400) ] + SVerticalBox::Slot() .AutoHeight() .Padding(FMargin(32.0f, 3.0f)) [ SNew(STextBlock) .ColorAndOpacity(FSlateColor::UseSubduedForeground()) .Text_Lambda([]() { return FText::FromString( FString::Printf(TEXT("%s://127.0.0.1:%d"), GetServeHttps() ? TEXT("https") : TEXT("http"), IPixelStreaming2EditorModule::Get().GetViewerPort())); }) ] + SVerticalBox::Slot() .AutoHeight() [ AdapterBox ] , FText() // No label needed ); } MenuBuilder.EndSection(); } void FPixelStreaming2Toolbar::RegisterStreamerControls(FMenuBuilder& MenuBuilder) { IPixelStreaming2Module::Get().ForEachStreamer([&](TSharedPtr Streamer) { FString StreamerId = Streamer->GetId(); MenuBuilder.BeginSection(FName(*StreamerId), FText::FromString(FString::Printf(TEXT("Streamer - %s"), *StreamerId))); { MenuBuilder.AddWidget( SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(SBox) .Padding(FMargin(16.f, 3.f)) [ SNew(STextBlock) .ColorAndOpacity(FSlateColor::UseSubduedForeground()) .Text_Lambda([Streamer]() -> FText { if (Streamer->IsStreaming()) { FString VideoProducer = TEXT("nothing (no video input)"); if (TSharedPtr Video = Streamer->GetVideoProducer().Pin()) { VideoProducer = Video->ToString(); } return FText::FromString(FString::Printf(TEXT("Streaming %s"), *VideoProducer)); } else { return FText::GetEmpty(); } }) .Visibility_Lambda([Streamer]() { return Streamer->IsStreaming() ? EVisibility::Visible : EVisibility::Collapsed; }) .WrapTextAt(400) ] ] ], FText() ); FPixelStreaming2EditorModule& EditorModule = static_cast(IPixelStreaming2EditorModule::Get()); if (Streamer->GetId() == EditorModule.GetEditorStreamerID()) { MenuBuilder.AddMenuEntry( FPixelStreaming2Commands::Get().StreamLevelEditor, // Command NAME_None, // InExtensionHook TAttribute(), // Label override (leave empty to use default) TAttribute(), // Tooltip override (leave empty to use default) FSlateIcon(), // Icon override (leave default) NAME_None, // Tutorial highlight TAttribute::Create([Streamer]() { return !Streamer->IsStreaming() ? EVisibility::Visible : EVisibility::Collapsed; }) ); MenuBuilder.AddMenuEntry( FPixelStreaming2Commands::Get().StreamEditor, // Command NAME_None, // InExtensionHook TAttribute(), // Label override (leave empty to use default) TAttribute(), // Tooltip override (leave empty to use default) FSlateIcon(), // Icon override (leave default) NAME_None, // Tutorial highlight TAttribute::Create([Streamer]() { return !Streamer->IsStreaming() ? EVisibility::Visible : EVisibility::Collapsed; }) ); } else { MenuBuilder.AddMenuEntry( LOCTEXT("PixelStreaming2_StartStreaming", "Start Streaming"), LOCTEXT("PixelStreaming2_StartStreamingToolTip", "Start this streamer"), FSlateIcon(), FExecuteAction::CreateLambda([Streamer]() { Streamer->StartStreaming(); }), NAME_None, EUserInterfaceActionType::Button, NAME_None, TAttribute(), TAttribute::Create([Streamer]() { return !Streamer->IsStreaming() ? EVisibility::Visible : EVisibility::Collapsed; }) ); } MenuBuilder.AddMenuEntry( LOCTEXT("PixelStreaming2_StopStreaming", "Stop Streaming"), LOCTEXT("PixelStreaming2_StopStreamingToolTip", "Stop this streamer"), FSlateIcon(), FExecuteAction::CreateLambda([Streamer]() { Streamer->StopStreaming(); }), NAME_None, EUserInterfaceActionType::Button, NAME_None, TAttribute(), TAttribute::Create([Streamer]() { return Streamer->IsStreaming() ? EVisibility::Visible : EVisibility::Collapsed; }) ); } MenuBuilder.EndSection(); }); } void FPixelStreaming2Toolbar::RegisterCodecConfig(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("Codec", LOCTEXT("PixelStreaming2CodecSettings", "Codec")); { MenuBuilder.AddMenuEntry(FPixelStreaming2Commands::Get().H264); MenuBuilder.AddMenuEntry(FPixelStreaming2Commands::Get().AV1); MenuBuilder.AddMenuEntry(FPixelStreaming2Commands::Get().VP8); MenuBuilder.AddMenuEntry(FPixelStreaming2Commands::Get().VP9); } MenuBuilder.EndSection(); } void FPixelStreaming2Toolbar::OnOpenFileBrowserClicked(EFileType FileType) { bool bFileSelected = false; if (IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get()) { const FString Title = LOCTEXT("PixelStreaming2_FileBrowser", "Choose file").ToString(); if (LastBrowsePath.IsEmpty()) { LastBrowsePath = FPaths::ProjectPluginsDir(); } FString Filter = TEXT(""); TArray OutFiles; bFileSelected = DesktopPlatform->OpenFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), Title, LastBrowsePath, LastBrowsePath, Filter, EFileDialogFlags::None, OutFiles); if (!OutFiles.IsEmpty()) { FString OutFilename = OutFiles[0]; switch (FileType) { case EFileType::Certificate: UE_LOGFMT(LogPixelStreaming2Editor, Display, "Selecting certificate: {0}", OutFilename); SetSSLCertificatePath(OutFilename); break; case EFileType::PrivateKey: UE_LOGFMT(LogPixelStreaming2Editor, Display, "Selecting private key: {0}", OutFilename); SetSSLPrivateKeyPath(OutFilename); break; default: checkNoEntry(); } } } } TSharedRef FPixelStreaming2Toolbar::GeneratePixelStreaming2MenuContent(TSharedPtr InCommandList) { FToolMenuContext MenuContext(InCommandList); return UToolMenus::Get()->GenerateWidget("LevelEditor.LevelEditorToolBar.AddQuickMenu", MenuContext); } } // namespace UE::EditorPixelStreaming2 #undef LOCTEXT_NAMESPACE