Live link face app /Transport

Hi there- just wondering if anyone is using the official Live link face app- in the documentation there is a /Transport OSC message that can be used to retrieve data from the phone. Unfortunately it is vague about what arguments are needed for it to work, and how to designate where the files are sent. Anybody got any ideas?

from the /RecordStopConfirm message I get three strings

[0] a 00:00:00 formatted number, possibly a time code or some arguments for the /transport command to work
[1] [local phone part of the CSV file
[2] local phone path of the .mov file

official documentation-

"Using a path returned by the /RecordStopConfirm command (above), requests the app to transport the contents of the file to the specified IP address and port. The app will open a TCP connection to that address and port. It first sends an int32 that contains the total size of the file, in big-endian format. It then sends the contents of the file."

regards

luke

If anybody ends up curious about this, I was able to reverse-engineer it.

/Transport command is for downloading a single file over a TCP connection. You can use the returned .mov file from /RecordStopConfirm as the input for /Transport.

Before transporting you have to spin up a TCP listener and handle the response data. The format is fileSize:int32, data:tarray{byte}

If you want all of the files, you’ll need to look at what files that the Capture Manager window normally inputs, and request each of these 1 by 1.

class FLiveLinkTcpListener : public FRunnable
{
public:
	FLiveLinkTcpListener(int32 InPort);
	virtual ~FLiveLinkTcpListener();

	void SetFileName(const FString& InFileName);

	// FRunnable interface
	virtual bool Init() override;
	virtual uint32 Run() override;
	virtual void Stop() override;

	DECLARE_DELEGATE_OneParam(FOnFileTransferComplete, const FString& /*FileName*/);
	FOnFileTransferComplete OnFileTransferComplete;

private:
	void HandleLiveLinkData(FSocket* ConnectionSocket);
	const FIPv4Endpoint& GetLocalEndpoint() const;
	FSocket* GetSocket() const;
	bool IsActive() const;
	FOnTcpListenerConnectionAccepted& OnConnectionAccepted();

	bool bDeleteSocket = false;
	FIPv4Endpoint Endpoint;
	FTimespan SleepTime;
	FSocket* Socket = nullptr;
	bool bStopping = false;
	bool bSocketReusable = true;
	FRunnableThread* Thread = nullptr;
	FString FileName;

	DECLARE_DELEGATE_RetVal_TwoParams(bool, FOnTcpListenerConnectionAccepted, FSocket*, const FIPv4Endpoint&)
	FOnTcpListenerConnectionAccepted ConnectionAcceptedDelegate;
};

#include "CaptureOrchestrator/LiveLinkTcpListener.h"

#include "HAL/RunnableThread.h"
#include "Async/Async.h"

FLiveLinkTcpListener::FLiveLinkTcpListener(int32 InPort)
{
    FIPv4Address Addr;
    FIPv4Address::Parse(TEXT("0.0.0.0"), Addr);
    Endpoint = FIPv4Endpoint(Addr, InPort);

    Thread = FRunnableThread::Create(this, TEXT("FLiveLinkTcpListener"), 8 * 1024, TPri_Normal);
}

FLiveLinkTcpListener::~FLiveLinkTcpListener()
{
    Stop();
    if (Thread != nullptr)
    {
        Thread->Kill(true);
        delete Thread;
    }
}

void FLiveLinkTcpListener::SetFileName(const FString& InFileName)
{
    FileName = InFileName;
}

bool FLiveLinkTcpListener::Init()
{
    if (Socket == nullptr)
    {
        Socket = FTcpSocketBuilder(TEXT("FTcpListener server"))
            .AsReusable(bSocketReusable)
            .BoundToEndpoint(Endpoint)
            .Listening(8)
            .WithSendBufferSize(2 * 1024 * 1024);
    }

    return (Socket != nullptr);
}

uint32 FLiveLinkTcpListener::Run()
{
    TSharedRef<FInternetAddr> RemoteAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();

    while (!bStopping)
    {
        bool Pending = false;
        if (Socket->WaitForPendingConnection(Pending, SleepTime) && Pending)
        {
            FSocket* ConnectionSocket = Socket->Accept(*RemoteAddress, TEXT("FTcpListener client"));
            if (ConnectionSocket != nullptr)
            {
                // New method for processing incoming data from Live Link
                HandleLiveLinkData(ConnectionSocket);
            }
        }
        else if (!Pending && SleepTime == FTimespan::Zero())
        {
            FPlatformProcess::Sleep(0.f);
        }
        else
        {
            FPlatformProcess::Sleep(SleepTime.GetSeconds());
        }
    }

    return 0;
}

void FLiveLinkTcpListener::Stop()
{
    bStopping = true;
}

void FLiveLinkTcpListener::HandleLiveLinkData(FSocket* ConnectionSocket)
{
    if (!ensure(!FileName.IsEmpty()))
    {
        return;
    }

    FString BaseDir = FPaths::ProjectContentDir() / TEXT("Temp") / FPaths::GetPath(FileName);
    IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();

    PlatformFile.CreateDirectoryTree(*BaseDir);
    FString FilePath = BaseDir / FPaths::GetCleanFilename(FileName);
    IFileHandle* FileHandle = PlatformFile.OpenWrite(*FilePath);

    if (!FileHandle)
    {
        return;
    }

    int32 TotalBytesRead = 0;
    int32 BytesRead = 0;
    int32 FileSize = 0;

    ConnectionSocket->Recv((uint8*)&FileSize, sizeof(int32), BytesRead);
    FileSize = ByteSwap(FileSize);

    if (FileSize > 0)
    {
        TArray<uint8> DataBuffer;
        const int32 ChunkSize = 8192;
        DataBuffer.SetNumUninitialized(ChunkSize);

        while (TotalBytesRead < FileSize)
        {
            int32 Remaining = FMath::Min(ChunkSize, FileSize - TotalBytesRead);
            if (ConnectionSocket->Recv(DataBuffer.GetData(), Remaining, BytesRead))
            {
                FileHandle->Write(DataBuffer.GetData(), BytesRead);
                TotalBytesRead += BytesRead;
            }
            else
            {
                break;
            }
        }
    }

    delete FileHandle;

    if (OnFileTransferComplete.IsBound())
    {
        OnFileTransferComplete.Execute(FileName);
    }
}

const FIPv4Endpoint& FLiveLinkTcpListener::GetLocalEndpoint() const
{
    return Endpoint;
}

FSocket* FLiveLinkTcpListener::GetSocket() const
{
    return Socket;
}

bool FLiveLinkTcpListener::IsActive() const
{
    return ((Socket != nullptr) && !bStopping);
}

FOnTcpListenerConnectionAccepted& FLiveLinkTcpListener::OnConnectionAccepted()
{
    return ConnectionAcceptedDelegate;
}