code
This commit is contained in:
@ -0,0 +1,716 @@
|
||||
// ------------------------------------------------
|
||||
// Copyright Joe Marshall 2024- All Rights Reserved
|
||||
// ------------------------------------------------
|
||||
//
|
||||
// The main IMediaPlayer implementation. This is a
|
||||
// shim which provides Unreal interfaces, and then
|
||||
// defers everything vulkan related to the main
|
||||
// AndroidVulkanVideoImpl class.
|
||||
// ------------------------------------------------
|
||||
|
||||
#include "AndroidVulkanMediaPlayer.h"
|
||||
#include "AndroidVulkanTextureSample.h"
|
||||
|
||||
#include "IVulkanImpl.h"
|
||||
|
||||
#include "VideoMediaSampleHolder.h"
|
||||
|
||||
#include "UnrealArchiveFileSource.h"
|
||||
#include "UnrealAudioOut.h"
|
||||
|
||||
#include "HAL/FileManager.h"
|
||||
#include "HAL/PlatformFilemanager.h"
|
||||
#include "IPlatformFilePak.h"
|
||||
#include "Misc/ConfigCacheIni.h" // for GConfig access
|
||||
|
||||
#include "UnrealLogging.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FAndroidVulkanVideoModule"
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
static void *implDLL = NULL;
|
||||
|
||||
// logging object passed to impl
|
||||
static UnrealLogger logger;
|
||||
|
||||
typedef IVulkanImpl *(*CreateImplType)();
|
||||
typedef void (*DestroyImplType)(IVulkanImpl *);
|
||||
static CreateImplType createImpl = NULL;
|
||||
static DestroyImplType destroyImpl = NULL;
|
||||
|
||||
FAndroidVulkanMediaPlayer::FAndroidVulkanMediaPlayer(IMediaEventSink &InEventSink)
|
||||
: SampleQueue(MakeShared<FVideoMediaSampleHolder, ESPMode::ThreadSafe>()),
|
||||
EventSink(InEventSink)
|
||||
{
|
||||
if (implDLL == NULL)
|
||||
{
|
||||
implDLL = dlopen("libVkLayer_OverrideLib.so", RTLD_NOW | RTLD_LOCAL);
|
||||
createImpl = (CreateImplType)(dlsym(implDLL, "createImpl"));
|
||||
destroyImpl = (DestroyImplType)(dlsym(implDLL, "destroyImpl"));
|
||||
}
|
||||
impl = createImpl();
|
||||
impl->setDataCallback(this);
|
||||
impl->setLogger(&logger);
|
||||
impl->setLooping(Looping);
|
||||
// get log format bitmask from defaultEngine.ini
|
||||
int32 LogVisibility;
|
||||
if (GConfig->GetInt(TEXT("DirectVideo"), TEXT("LogBitmask"), LogVisibility, GEngineIni))
|
||||
{
|
||||
logger.SetLogVisibilityBitmask((int64)LogVisibility);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.SetLogVisibilityBitmask(ILogger::LogTypes::ALL_LOGS_BITMASK);
|
||||
}
|
||||
// get output format from defaultEngine.ini
|
||||
FString OutFormat;
|
||||
EMediaTextureSampleFormat SampleFormat = EMediaTextureSampleFormat::CharBGR10A2;
|
||||
if (GConfig->GetString(TEXT("DirectVideo"), TEXT("OutputFormat"), OutFormat, GEngineIni))
|
||||
{
|
||||
if (OutFormat.Equals(TEXT("CharBGR10A2"), ESearchCase::IgnoreCase))
|
||||
{
|
||||
SampleFormat = EMediaTextureSampleFormat::CharBGR10A2;
|
||||
}
|
||||
else if (OutFormat.Equals(TEXT("CharBGRA"), ESearchCase::IgnoreCase))
|
||||
{
|
||||
SampleFormat = EMediaTextureSampleFormat::CharBGRA;
|
||||
}
|
||||
|
||||
else if (OutFormat.Equals(TEXT("CharRGBA"), ESearchCase::IgnoreCase))
|
||||
{
|
||||
SampleFormat = EMediaTextureSampleFormat::CharRGBA;
|
||||
}
|
||||
else if (OutFormat.Equals(TEXT("RGBA16"), ESearchCase::IgnoreCase))
|
||||
{
|
||||
SampleFormat = EMediaTextureSampleFormat::RGBA16;
|
||||
}
|
||||
|
||||
else if (OutFormat.Equals(TEXT("FloatRGB"), ESearchCase::IgnoreCase))
|
||||
{
|
||||
SampleFormat = EMediaTextureSampleFormat::FloatRGB;
|
||||
}
|
||||
else if (OutFormat.Equals(TEXT("FloatRGBA"), ESearchCase::IgnoreCase))
|
||||
{
|
||||
SampleFormat = EMediaTextureSampleFormat::FloatRGBA;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, Error, "Bad sample format in defaultEngine.ini");
|
||||
}
|
||||
}
|
||||
AndroidVulkanTextureSample::SetVideoFormat(SampleFormat);
|
||||
|
||||
AudioOut = new UnrealAudioOut(NULL);
|
||||
impl->setAudioOut(AudioOut);
|
||||
PlayState = EMediaState::Closed;
|
||||
CurInfo.Empty();
|
||||
DelegateEnterBackground = FCoreDelegates::ApplicationWillEnterBackgroundDelegate.AddRaw(
|
||||
this, &FAndroidVulkanMediaPlayer::OnEnterBackground);
|
||||
DelegateEnterForeground = FCoreDelegates::ApplicationHasEnteredForegroundDelegate.AddRaw(
|
||||
this, &FAndroidVulkanMediaPlayer::OnEnterForeground);
|
||||
SeekIndex = 0;
|
||||
SentBlankFrame = false;
|
||||
Seeking = false;
|
||||
}
|
||||
|
||||
FAndroidVulkanMediaPlayer::~FAndroidVulkanMediaPlayer()
|
||||
{
|
||||
Close();
|
||||
SentBlankFrame = true;
|
||||
if (DelegateEnterBackground.IsValid())
|
||||
{
|
||||
FCoreDelegates::ApplicationWillEnterBackgroundDelegate.Remove(DelegateEnterBackground);
|
||||
}
|
||||
if (DelegateEnterForeground.IsValid())
|
||||
{
|
||||
FCoreDelegates::ApplicationHasEnteredForegroundDelegate.Remove(DelegateEnterForeground);
|
||||
}
|
||||
destroyImpl(impl);
|
||||
impl = NULL;
|
||||
delete AudioOut;
|
||||
AudioOut = NULL;
|
||||
}
|
||||
|
||||
// IMediaPlayer
|
||||
// --------------------------------
|
||||
void FAndroidVulkanMediaPlayer::Close()
|
||||
{
|
||||
SentBlankFrame = false;
|
||||
|
||||
if (PlayState != EMediaState::Closed)
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Media player close called");
|
||||
SampleQueue->FlushSamples();
|
||||
VideoSamplePool.ReleaseEverything();
|
||||
SeekIndex = 0;
|
||||
Seeking = false;
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::TracksChanged);
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::MediaClosed);
|
||||
PlayState = EMediaState::Closed;
|
||||
// close the impl last because it will
|
||||
// kill all the texture images that we release in the pool above
|
||||
impl->close();
|
||||
}
|
||||
}
|
||||
FString FAndroidVulkanMediaPlayer::GetInfo() const
|
||||
{
|
||||
return CurInfo;
|
||||
}
|
||||
FGuid FAndroidVulkanMediaPlayer::GetPlayerPluginGUID() const
|
||||
{
|
||||
static FGuid OurGUID(0x9bf2d7c6, 0xb2b84d26, 0xb6ae5a3a, 0xc9883569);
|
||||
return OurGUID;
|
||||
}
|
||||
FString FAndroidVulkanMediaPlayer::GetStats() const
|
||||
{
|
||||
return TEXT("Not implemented");
|
||||
}
|
||||
FString FAndroidVulkanMediaPlayer::GetUrl() const
|
||||
{
|
||||
return VideoURL;
|
||||
}
|
||||
bool FAndroidVulkanMediaPlayer::Open(const FString &Url, const IMediaOptions *Options)
|
||||
{
|
||||
Close();
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Try open Url {0}", Url);
|
||||
SentBlankFrame = false;
|
||||
bool started = false;
|
||||
FString fullPath = Url;
|
||||
if (fullPath.StartsWith("file://"))
|
||||
{
|
||||
fullPath = fullPath.RightChop(7);
|
||||
}
|
||||
if (fullPath.Contains("://"))
|
||||
{
|
||||
// a (non-file url)
|
||||
started = impl->startVideoURL(TCHAR_TO_UTF8(*fullPath), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// a file path - check if it is local or not
|
||||
if (fullPath.StartsWith("./"))
|
||||
{
|
||||
fullPath = FPaths::ProjectContentDir() + fullPath.RightChop(2);
|
||||
}
|
||||
FPaths::NormalizeFilename(fullPath);
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Start video {0}", fullPath);
|
||||
IAndroidPlatformFile &PlatformFile = IAndroidPlatformFile::GetPlatformPhysical();
|
||||
if (PlatformFile.FileExists(*fullPath))
|
||||
{
|
||||
int64 FileOffset = PlatformFile.FileStartOffset(*fullPath);
|
||||
int64 FileSize = PlatformFile.FileSize(*fullPath);
|
||||
FString FileRootPath = PlatformFile.FileRootPath(*fullPath);
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "File exists: {0} {1} {2} {3}", FileRootPath,
|
||||
FileOffset, FileSize, fullPath);
|
||||
started =
|
||||
impl->startVideoFile(TCHAR_TO_UTF8(*FileRootPath), FileOffset, FileSize, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// check if the file exists in an archive / encrypted etc.
|
||||
FPakPlatformFile *PakPlatformFile =
|
||||
(FPakPlatformFile *)(FPlatformFileManager::Get().FindPlatformFile(
|
||||
FPakPlatformFile::GetTypeName()));
|
||||
|
||||
TRefCountPtr<FPakFile> PakFile;
|
||||
FPakEntry FileEntry;
|
||||
if (PakPlatformFile != nullptr &&
|
||||
PakPlatformFile->FindFileInPakFiles(*fullPath, &PakFile, &FileEntry))
|
||||
{
|
||||
// we have the file in a pak file
|
||||
// use a custom media data source to read it
|
||||
// n.b. if pak file is uncompressed and the file isn't encrypted we could skip this
|
||||
// step but this should work whatever
|
||||
TSharedRef<FArchive, ESPMode::ThreadSafe> Archive =
|
||||
MakeShareable(IFileManager::Get().CreateFileReader(*fullPath));
|
||||
UnrealArchiveFileSource *fs = new UnrealArchiveFileSource(Archive);
|
||||
started = impl->startVideoCustomSource(fs, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (started)
|
||||
{
|
||||
PlayState = EMediaState::Stopped;
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::TracksChanged);
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::MediaOpened);
|
||||
UE_LOGFMT(LogDirectVideo, Verbose, "Opening {0}", *Url);
|
||||
SeekIndex = 0;
|
||||
Seeking = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// couldn't open
|
||||
UE_LOGFMT(LogDirectVideo, Error, "Can't find video file {0}", *Url);
|
||||
}
|
||||
return started;
|
||||
}
|
||||
bool FAndroidVulkanMediaPlayer::Open(const TSharedRef<FArchive, ESPMode::ThreadSafe> &Archive,
|
||||
const FString &OriginalUrl, const IMediaOptions *Options)
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, Error, "Opening archive {0} not supported", *OriginalUrl);
|
||||
return false;
|
||||
}
|
||||
void FAndroidVulkanMediaPlayer::SetGuid(const FGuid &Guid)
|
||||
{
|
||||
PlayerGUID = Guid;
|
||||
}
|
||||
|
||||
void FAndroidVulkanMediaPlayer::TickFetch(FTimespan DeltaTime, FTimespan Timecode)
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "TickFetch");
|
||||
}
|
||||
|
||||
void FAndroidVulkanMediaPlayer::TickInput(FTimespan DeltaTime, FTimespan Timecode)
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "TickInput start");
|
||||
|
||||
/* int64_t presTimeNs = impl->getTimeNS();
|
||||
SampleQueue->SetLastTimeStamp(
|
||||
FMediaTimeStamp(DeltaTime,
|
||||
#if UE_VERSION_OLDER_THAN(5, 3, 0)
|
||||
static_cast<int64>(SeekIndex) << 32));
|
||||
#else
|
||||
FMediaTimeStamp::MakeSequenceIndex(SeekIndex, 0)));
|
||||
#endif
|
||||
*/
|
||||
HasVideoThisFrame = false;
|
||||
VideoSamplePool.Tick();
|
||||
if (!SentBlankFrame && impl->numVideoTracks() == 0)
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "blank frame");
|
||||
SentBlankFrame = true;
|
||||
auto textureSample = VideoSamplePool.AcquireShared();
|
||||
textureSample->InitNoVideo();
|
||||
SampleQueue->AddVideo(textureSample);
|
||||
}
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "TickInput dt:{0} tc {1} num {2}", DeltaTime.GetTicks(),
|
||||
Timecode.GetTicks(), SampleQueue->NumVideoSamples());
|
||||
}
|
||||
|
||||
bool FAndroidVulkanMediaPlayer::GetPlayerFeatureFlag(EFeatureFlag flag) const
|
||||
{
|
||||
|
||||
switch (flag)
|
||||
{
|
||||
case EFeatureFlag::PlayerUsesInternalFlushOnSeek:
|
||||
return true;
|
||||
case EFeatureFlag::AlwaysPullNewestVideoFrame:
|
||||
return true;
|
||||
case EFeatureFlag::UsePlaybackTimingV2:
|
||||
return true;
|
||||
case EFeatureFlag::IsTrackSwitchSeamless:
|
||||
return true;
|
||||
case EFeatureFlag::UseRealtimeWithVideoOnly:
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Feature flag query");
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// IMediaControl
|
||||
// --------------------------------
|
||||
|
||||
bool FAndroidVulkanMediaPlayer::CanControl(EMediaControl Control) const
|
||||
{
|
||||
switch (Control)
|
||||
{
|
||||
case EMediaControl::Pause:
|
||||
return PlayState == EMediaState::Playing;
|
||||
case EMediaControl::Resume:
|
||||
return PlayState == EMediaState::Stopped;
|
||||
case EMediaControl::Seek:
|
||||
return PlayState != EMediaState::Closed;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FTimespan FAndroidVulkanMediaPlayer::GetDuration() const
|
||||
{
|
||||
int64_t timeNS = impl->getDurationNS();
|
||||
if (timeNS >= 0 && PlayState != EMediaState::Closed)
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get Duration {0}", int64(timeNS));
|
||||
return FTimespan(timeNS / 100LL);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get Duration empty");
|
||||
return FTimespan::MaxValue();
|
||||
}
|
||||
}
|
||||
|
||||
float FAndroidVulkanMediaPlayer::GetRate() const
|
||||
{
|
||||
if (PlayState == EMediaState::Playing)
|
||||
{
|
||||
return impl->getRate();
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
EMediaState FAndroidVulkanMediaPlayer::GetState() const
|
||||
{
|
||||
return PlayState;
|
||||
}
|
||||
|
||||
EMediaStatus FAndroidVulkanMediaPlayer::GetStatus() const
|
||||
{
|
||||
// not supported yet
|
||||
return EMediaStatus::None;
|
||||
}
|
||||
|
||||
TRangeSet<float> FAndroidVulkanMediaPlayer::GetSupportedRates(EMediaRateThinning Thinning) const
|
||||
{
|
||||
TRangeSet<float> Retval;
|
||||
if (impl->numAudioTracks() > 0)
|
||||
{
|
||||
Retval.Add(TRange<float>(0.0f));
|
||||
Retval.Add(TRange<float>(1.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
Retval.Add(TRange<float>(TRange<float>::BoundsType::Inclusive(0.0f),
|
||||
TRange<float>::BoundsType::Inclusive(10.0f)));
|
||||
}
|
||||
return Retval;
|
||||
}
|
||||
|
||||
FTimespan FAndroidVulkanMediaPlayer::GetTime() const
|
||||
{
|
||||
int64_t timeNS = impl->getTimeNS();
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get Time {0}", int64(timeNS));
|
||||
if (timeNS >= 0)
|
||||
{
|
||||
return FTimespan(timeNS / 100LL);
|
||||
}
|
||||
return FTimespan::Zero();
|
||||
}
|
||||
|
||||
bool FAndroidVulkanMediaPlayer::IsLooping() const
|
||||
{
|
||||
return Looping;
|
||||
}
|
||||
|
||||
bool FAndroidVulkanMediaPlayer::Seek(const FTimespan &Time)
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, Verbose, "Seek index:{0}", SeekIndex);
|
||||
Seeking = true;
|
||||
int64_t ticks = Time.GetTicks();
|
||||
int64_t nanoseconds = ticks * 100LL;
|
||||
impl->seek(nanoseconds);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FAndroidVulkanMediaPlayer::SetLooping(bool Loop)
|
||||
{
|
||||
Looping = Loop;
|
||||
impl->setLooping(Looping);
|
||||
return true;
|
||||
}
|
||||
bool FAndroidVulkanMediaPlayer::SetRate(float Rate)
|
||||
{
|
||||
bool retval = false;
|
||||
int iInitialState = (int)PlayState;
|
||||
switch (PlayState)
|
||||
{
|
||||
case EMediaState::Playing:
|
||||
if (Rate == 0.0)
|
||||
{
|
||||
impl->setPlaying(false);
|
||||
PlayState = EMediaState::Stopped;
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackSuspended);
|
||||
retval = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return impl->setRate(Rate);
|
||||
retval = true;
|
||||
}
|
||||
break;
|
||||
case EMediaState::Stopped:
|
||||
if (Rate == 0.0)
|
||||
{
|
||||
retval = true;
|
||||
}
|
||||
else if (Rate > 0.0)
|
||||
{
|
||||
impl->setPlaying(true);
|
||||
PlayState = EMediaState::Playing;
|
||||
retval = impl->setRate(Rate);
|
||||
if (retval)
|
||||
{
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackResumed);
|
||||
}
|
||||
}
|
||||
break;
|
||||
};
|
||||
int iFinalState = (int)PlayState;
|
||||
UE_LOGFMT(LogDirectVideo, Verbose, "Set rate {0} {1} state:{2}->{3}", Rate, retval,
|
||||
iInitialState, iFinalState);
|
||||
// return false;
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool FAndroidVulkanMediaPlayer::SetNativeVolume(float Volume)
|
||||
{
|
||||
return AudioOut->setVolume(Volume);
|
||||
}
|
||||
|
||||
// IMediaTracks
|
||||
// --------------------------------
|
||||
bool FAndroidVulkanMediaPlayer::GetAudioTrackFormat(int32 TrackIndex, int32 FormatIndex,
|
||||
FMediaAudioTrackFormat &OutFormat) const
|
||||
{
|
||||
if (FormatIndex != 0 || PlayState == EMediaState::Closed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int32 bitsPerSample;
|
||||
int32 channels;
|
||||
int32 rate;
|
||||
if (!impl->getAudioTrackFormat(TrackIndex, &bitsPerSample, &channels, &rate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
OutFormat.BitsPerSample = bitsPerSample;
|
||||
OutFormat.NumChannels = channels;
|
||||
OutFormat.SampleRate = rate;
|
||||
OutFormat.TypeName = TEXT("Native");
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get audio trackformat {0} {1} {2} {3}", TrackIndex,
|
||||
bitsPerSample, channels, rate);
|
||||
return true;
|
||||
}
|
||||
int32 FAndroidVulkanMediaPlayer::GetNumTracks(EMediaTrackType TrackType) const
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get num tracks type: {0}", int(TrackType));
|
||||
// TODO: support audio / video only
|
||||
switch (TrackType)
|
||||
{
|
||||
case EMediaTrackType::Audio:
|
||||
return impl->numAudioTracks();
|
||||
case EMediaTrackType::Video:
|
||||
return impl->numVideoTracks();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int32 FAndroidVulkanMediaPlayer::GetNumTrackFormats(EMediaTrackType TrackType,
|
||||
int32 TrackIndex) const
|
||||
{
|
||||
if (TrackIndex >= GetNumTracks(TrackType))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
switch (TrackType)
|
||||
{
|
||||
case EMediaTrackType::Audio:
|
||||
return 1;
|
||||
case EMediaTrackType::Video:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int32 FAndroidVulkanMediaPlayer::GetSelectedTrack(EMediaTrackType TrackType) const
|
||||
{
|
||||
if (TrackType == EMediaTrackType::Audio)
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Returning no selected audio track");
|
||||
return INDEX_NONE;
|
||||
}
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Returning selected track type {0}", int(TrackType));
|
||||
return 0;
|
||||
}
|
||||
|
||||
FText FAndroidVulkanMediaPlayer::GetTrackDisplayName(EMediaTrackType TrackType,
|
||||
int32 TrackIndex) const
|
||||
{
|
||||
// TODO: pass through display names
|
||||
|
||||
return FText::Format(LOCTEXT("TrackName", "Track {0} type {1}"), (int)TrackType, TrackIndex);
|
||||
}
|
||||
|
||||
int32 FAndroidVulkanMediaPlayer::GetTrackFormat(EMediaTrackType TrackType, int32 TrackIndex) const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
FString FAndroidVulkanMediaPlayer::GetTrackLanguage(EMediaTrackType TrackType,
|
||||
int32 TrackIndex) const
|
||||
{
|
||||
// TODO - pass language through
|
||||
return TEXT("");
|
||||
}
|
||||
|
||||
FString FAndroidVulkanMediaPlayer::GetTrackName(EMediaTrackType TrackType, int32 TrackIndex) const
|
||||
{
|
||||
if (TrackIndex == 0 && PlayState != EMediaState::Closed)
|
||||
{
|
||||
return TEXT("TRACK");
|
||||
}
|
||||
else
|
||||
{
|
||||
return TEXT("");
|
||||
}
|
||||
}
|
||||
|
||||
bool FAndroidVulkanMediaPlayer::GetVideoTrackFormat(int32 TrackIndex, int32 FormatIndex,
|
||||
FMediaVideoTrackFormat &OutFormat) const
|
||||
{
|
||||
if (FormatIndex != 0 || PlayState == EMediaState::Closed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int w = 0, h = 0;
|
||||
float frameRate = 0;
|
||||
if (!impl->getVideoTrackFormat(TrackIndex, &w, &h, &frameRate))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
OutFormat.Dim.X = w;
|
||||
OutFormat.Dim.Y = h;
|
||||
OutFormat.FrameRate = frameRate;
|
||||
OutFormat.FrameRates = TRange<float>(frameRate);
|
||||
OutFormat.TypeName = TEXT("Vulkan Video Frame");
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Got out video format");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FAndroidVulkanMediaPlayer::SelectTrack(EMediaTrackType TrackType, int32 TrackIndex)
|
||||
{
|
||||
if (TrackType == EMediaTrackType::Audio)
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Not selecting audio track");
|
||||
return false;
|
||||
}
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Selecting track type {0} = {0}", int(TrackType),
|
||||
TrackIndex);
|
||||
return true;
|
||||
// return TrackIndex == 0 && PlayState != EMediaState::Closed;
|
||||
}
|
||||
|
||||
bool FAndroidVulkanMediaPlayer::SetTrackFormat(EMediaTrackType TrackType, int32 TrackIndex,
|
||||
int32 FormatIndex)
|
||||
{
|
||||
// todo: support multiple formats / track
|
||||
return TrackIndex == 0 && FormatIndex == 0 && PlayState != EMediaState::Closed;
|
||||
}
|
||||
|
||||
IMediaSamples &FAndroidVulkanMediaPlayer::GetSamples()
|
||||
{
|
||||
return *SampleQueue.Get();
|
||||
}
|
||||
|
||||
void FAndroidVulkanMediaPlayer::onVideoFrame(void *frameHwBuffer, int w, int h, int64_t presTimeNs)
|
||||
{
|
||||
if (PlayState == EMediaState::Closed)
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Releasing frame as we are closed");
|
||||
impl->releaseFrame(frameHwBuffer);
|
||||
return;
|
||||
}
|
||||
if (HasVideoThisFrame)
|
||||
{
|
||||
impl->releaseFrame(frameHwBuffer);
|
||||
return;
|
||||
}
|
||||
HasVideoThisFrame = true;
|
||||
auto textureSample = VideoSamplePool.AcquireShared();
|
||||
textureSample->Init(impl, frameHwBuffer, w, h,
|
||||
FMediaTimeStamp(FTimespan(presTimeNs / 100LL),
|
||||
|
||||
FMediaTimeStamp::MakeSequenceIndex(SeekIndex, 0)));
|
||||
|
||||
SampleQueue->AddVideo(textureSample);
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Add video sample");
|
||||
// UE_LOGFMT(LogDirectVideo, VeryVerbose, "Video samples {0}",
|
||||
// SampleQueue->NumVideoSamples());
|
||||
// NB: this needs to happen after the sample is in the queue, so that
|
||||
// the queue has something in when media player facade samples it for playback
|
||||
// time
|
||||
if (Seeking)
|
||||
{
|
||||
SeekIndex += 1;
|
||||
UE_LOGFMT(LogDirectVideo, Verbose, "Seek completed {0}", SeekIndex);
|
||||
Seeking = false;
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::SeekCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
void FAndroidVulkanMediaPlayer::OnEnterBackground()
|
||||
{
|
||||
// going into backgroud - if playing, pause implementation player
|
||||
if (PlayState == EMediaState::Playing)
|
||||
{
|
||||
impl->setPlaying(false);
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackSuspended);
|
||||
UE_LOGFMT(LogDirectVideo, Verbose, "Enter suspend");
|
||||
}
|
||||
}
|
||||
void FAndroidVulkanMediaPlayer::OnEnterForeground()
|
||||
{
|
||||
// back into foreground - if playing, start
|
||||
if (PlayState == EMediaState::Playing)
|
||||
{
|
||||
impl->setPlaying(true);
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackResumed);
|
||||
UE_LOGFMT(LogDirectVideo, Verbose, "Enter foreground");
|
||||
}
|
||||
}
|
||||
|
||||
void FAndroidVulkanMediaPlayer::ProcessVideoSamples()
|
||||
{
|
||||
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Process video samples");
|
||||
}
|
||||
|
||||
void *FAndroidVulkanMediaPlayer::getVkDeviceProcAddr(const char *name)
|
||||
{
|
||||
void *result = static_cast<void *>(
|
||||
(static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI))->RHIGetVkDeviceProcAddr(name));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
VkDevice FAndroidVulkanMediaPlayer::getVkDevice()
|
||||
{
|
||||
IVulkanDynamicRHI *rhi = static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI);
|
||||
if (rhi == NULL)
|
||||
{
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
return rhi->RHIGetVkDevice();
|
||||
}
|
||||
|
||||
const VkAllocationCallbacks *FAndroidVulkanMediaPlayer::getVkAllocationCallbacks()
|
||||
{
|
||||
IVulkanDynamicRHI *rhi = static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI);
|
||||
if (rhi == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return rhi->RHIGetVkAllocationCallbacks();
|
||||
}
|
||||
|
||||
VkPhysicalDevice FAndroidVulkanMediaPlayer::getNativePhysicalDevice()
|
||||
{
|
||||
return static_cast<VkPhysicalDevice>(GDynamicRHI->RHIGetNativePhysicalDevice());
|
||||
}
|
||||
|
||||
void FAndroidVulkanMediaPlayer::onPlaybackEnd(bool looping)
|
||||
{
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackEndReached);
|
||||
if (!looping)
|
||||
{
|
||||
PlayState = EMediaState::Stopped;
|
||||
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackSuspended);
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
Reference in New Issue
Block a user