Files
UnrealEngine/Engine/Plugins/Media/ElectraPlayer/Source/ElectraPlayerPlugin/Private/ElectraPlayerPluginModule.cpp
2025-05-18 13:04:45 +08:00

212 lines
8.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "RHI.h"
#include "ElectraPlayerMisc.h"
#include "IElectraPlayerPluginModule.h"
#include "IElectraPlayerRuntimeModule.h"
#include "ElectraPlayerPlugin.h"
#include "ParameterDictionary.h"
#include "SimpleElectraAudioPlayer.h"
#include "IElectraDecoderResourceDelegateBase.h"
#include "IElectraPlayerDecoderResourceManager.h"
#define LOCTEXT_NAMESPACE "ElectraPlayerPluginModule"
DEFINE_LOG_CATEGORY(LogElectraPlayerPlugin);
DECLARE_CYCLE_STAT(TEXT("Electra AsyncJob"), STAT_ElectraAsyncJob, STATGROUP_Media);
// -----------------------------------------------------------------------------------------------------------------------------------
class FElectraPlayerPluginModule : public IElectraPlayerPluginModule
{
public:
// IElectraPlayerPluginModule interface
bool IsInitialized() const override
{
return bInitialized;
}
TSharedPtr<IMediaPlayer, ESPMode::ThreadSafe> CreatePlayer(IMediaEventSink& EventSink) override
{
if (bInitialized)
{
TSharedPtr<FElectraPlayerPlugin, ESPMode::ThreadSafe> NewPlayer = MakeShared<FElectraPlayerPlugin, ESPMode::ThreadSafe>();
check(NewPlayer.IsValid());
if (NewPlayer->Initialize(EventSink, SendAnalyticMetricsDelegate, SendAnalyticMetricsPerMinuteDelegate, ReportVideoStreamingErrorDelegate, ReportSubtitlesMetricsDelegate))
{
return NewPlayer;
}
}
return nullptr;
}
void SendAnalyticMetrics(const TSharedPtr<IAnalyticsProviderET>& AnalyticsProvider, const FGuid& PlayerGuid) override
{
SendAnalyticMetricsDelegate.Broadcast(AnalyticsProvider, PlayerGuid);
}
void SendAnalyticMetricsPerMinute(const TSharedPtr<IAnalyticsProviderET>& AnalyticsProvider) override
{
SendAnalyticMetricsPerMinuteDelegate.Broadcast(AnalyticsProvider);
ISimpleElectraAudioPlayer::SendAnalyticMetrics(AnalyticsProvider);
}
void ReportVideoStreamingError(const FGuid& PlayerGuid, const FString& LastError) override
{
ReportVideoStreamingErrorDelegate.Broadcast(PlayerGuid, LastError);
}
void ReportSubtitlesMetrics(const FGuid& PlayerGuid, const FString& URL, double ResponseTime, const FString& LastError) override
{
ReportSubtitlesMetricsDelegate.Broadcast(PlayerGuid, URL, ResponseTime, LastError);
}
// Create a suitable video decoder resource delegate for and via the Electra Player runtime to be used with it by external means.
TSharedPtr<Electra::IVideoDecoderResourceDelegate, ESPMode::ThreadSafe> CreatePlatformVideoDecoderResourceDelegate() override
{
#if PLATFORM_WINDOWS
// This is a bit of a convoluted process due to this whole plugin/adapter/delegate nature.
class FDummyAdapter : public IElectraPlayerAdapterDelegate
{
public:
FDummyAdapter(TSharedPtr<IElectraPlayerResourceDelegate, ESPMode::ThreadSafe> InPlayerResourceDlg) : PlayerResourceDlg(MoveTemp(InPlayerResourceDlg)) { }
virtual ~FDummyAdapter() { }
Electra::FVariantValue QueryOptions(EOptionType Type, const Electra::FVariantValue& Param = Electra::FVariantValue()) override { return Electra::FVariantValue(); }
void BlobReceived(const TSharedPtr<TArray<uint8>, ESPMode::ThreadSafe>& InBlobData, EBlobResultType InResultType, int32 InResultCode, const Electra::FParamDict* InExtraInfo) override { }
void SendMediaEvent(EPlayerEvent Event) override { }
void OnVideoFlush() override { }
void OnAudioFlush() override { }
void OnSubtitleFlush() override { }
void PresentVideoFrame(const FVideoDecoderOutputPtr& InVideoFrame) override { }
void PresentAudioFrame(const IAudioDecoderOutputPtr& InAudioFrame) override { }
void PresentSubtitleSample(const ISubtitleDecoderOutputPtr& InSubtitleSample) override { }
void PresentMetadataSample(const IMetaDataDecoderOutputPtr& InMetadataSample) override { }
bool CanReceiveVideoSamples(int32 NumFrames) override { return false; }
bool CanReceiveAudioSamples(int32 NumFrames) override { return false; }
FString GetVideoAdapterName() const override { return FString(); }
TSharedPtr<IElectraPlayerResourceDelegate, ESPMode::ThreadSafe> GetResourceDelegate() const override { return PlayerResourceDlg; }
private:
TSharedPtr<IElectraPlayerResourceDelegate, ESPMode::ThreadSafe> PlayerResourceDlg;
};
return FElectraPlayerDecoderResourceManager::CreatePlatformVideoDecoderResourceDelegate(MakeShareable(new FDummyAdapter(MakeShareable(FElectraPlayerPlugin::PlatformCreateStaticPlayerResourceDelegate()))));
#else
return nullptr;
#endif
}
public:
// IModuleInterface interface
static void GetDynamicRHIInfo(void** OutGDynamicRHI, int64* OutGDynamicRHIType)
{
if (OutGDynamicRHI)
{
*OutGDynamicRHI = GDynamicRHI ? GDynamicRHI->RHIGetNativeDevice() : nullptr;
}
if (OutGDynamicRHIType)
{
*OutGDynamicRHIType = (int64)RHIGetInterfaceType();
}
}
class FAsyncConsecutiveTaskSync : public IElectraDecoderResourceDelegateBase::IAsyncConsecutiveTaskSync
{
public:
FAsyncConsecutiveTaskSync() {}
virtual ~FAsyncConsecutiveTaskSync() {}
FGraphEventRef GraphEvent;
};
static TSharedPtr<IElectraDecoderResourceDelegateBase::IAsyncConsecutiveTaskSync, ESPMode::ThreadSafe> CreateAsyncConsecutiveTaskSync()
{
return MakeShared<FAsyncConsecutiveTaskSync, ESPMode::ThreadSafe>();
}
static void RunCodeAsync(TFunction<void()>&& CodeToRun, IElectraDecoderResourceDelegateBase::IAsyncConsecutiveTaskSync* TaskSync)
{
FScopeLock Lock(&AsyncJobAccessCS);
// Execute code async
// (We assume this to be code copying buffer data around. Hence we allow only ONE at any time to not clog up
// the buses even more and delay the copy process further)
FGraphEventArray Events;
auto& Event = TaskSync ? static_cast<FAsyncConsecutiveTaskSync*>(TaskSync)->GraphEvent : RunCodeAsyncEvent;
if (Event.IsValid())
{
Events.Add(Event);
}
Event = FFunctionGraphTask::CreateAndDispatchWhenReady(MoveTemp(CodeToRun), GET_STATID(STAT_ElectraAsyncJob), &Events);
}
void StartupModule() override
{
// Check that we have the player module and that it has initialized successfully.
if (FModuleManager::Get().GetModule("ElectraPlayerRuntime"))
{
IElectraPlayerRuntimeModule* ElectraPlayer = &FModuleManager::Get().GetModuleChecked<IElectraPlayerRuntimeModule>("ElectraPlayerRuntime");
if (!ElectraPlayer || !ElectraPlayer->IsInitialized())
{
return;
}
}
else
{
return;
}
if (!bInitialized)
{
//WE COULD ALSO CHECK FOR SINGLE THREADING ISSUES HERE!? -- kinda piggy back on this?
// to detect cooking and other commandlets that run with NullRHI
if (GDynamicRHI == nullptr || RHIGetInterfaceType() == ERHIInterfaceType::Null)
{
UE_LOG(LogElectraPlayerPlugin, Log, TEXT("Dummy Dynamic RHI detected. Electra Player plugin is not initialised."));
return;
}
Electra::FParamDict Params;
Params.Set(FName(TEXT("GetDeviceTypeCallback")), Electra::FVariantValue((void*)&FElectraPlayerPluginModule::GetDynamicRHIInfo));
Params.Set(FName(TEXT("CreateAsyncConsecutiveTaskSync")), Electra::FVariantValue((void*)&FElectraPlayerPluginModule::CreateAsyncConsecutiveTaskSync));
Params.Set(FName(TEXT("RunCodeAsyncCallback")), Electra::FVariantValue((void*)&FElectraPlayerPluginModule::RunCodeAsync));
if (!FElectraPlayerPlatform::StartupPlatformResources(Params))
{
UE_LOG(LogElectraPlayerPlugin, Log, TEXT("Platform resource setup failed! Electra Player plugin is not initialised."));
return;
}
bInitialized = true;
}
}
void ShutdownModule() override
{
bInitialized = false;
}
private:
bool bInitialized = false;
FElectraPlayerSendAnalyticMetricsDelegate SendAnalyticMetricsDelegate;
FElectraPlayerSendAnalyticMetricsPerMinuteDelegate SendAnalyticMetricsPerMinuteDelegate;
FElectraPlayerReportVideoStreamingErrorDelegate ReportVideoStreamingErrorDelegate;
FElectraPlayerReportSubtitlesMetricsDelegate ReportSubtitlesMetricsDelegate;
static FCriticalSection AsyncJobAccessCS;
static FGraphEventRef RunCodeAsyncEvent;
};
FCriticalSection FElectraPlayerPluginModule::AsyncJobAccessCS;
FGraphEventRef FElectraPlayerPluginModule::RunCodeAsyncEvent;
IMPLEMENT_MODULE(FElectraPlayerPluginModule, ElectraPlayerPlugin);
#undef LOCTEXT_NAMESPACE