sharing pictures between clients at runtime ?

Hey there,

I would like my users (clients) to share pictures from their local disk.

Seems like a simple problem enough, but i have been working my *** off to do that, couldn’t succeed, so here am i.

I have a function that makes a UTexture2D from any (jpg, bmp, png…) file on disk. This is made through a TArray<uint8>.
It works locally.

Next task is to send the picture to other clients.

I tried to simply replicate the texture but got this message:


LogNetPackageMap:Warning: FNetGUIDCache::SupportsObject: Texture2D /Engine/Transient.Texture2D_2 NOT Supported.

Then i tried to share the TArray but i got this message:


LogNetTraffic:Error: SerializeProperties_DynamicArray_r: ArrayNum > MAX_ARRAY_SIZE (RawFileData)
LogNet: Error: Can't send function 'ServerSetTexture' on 'BP_YagCharacter_C /Game/maps/UEDPIE_4_WelcomeMap.WelcomeMap:PersistentLevel.BP_YagCharacter_C_20': RPC bunch overflowed (too much data in parameters?)

So i tried to recompile the editor with a modified MAX_ARRAY_SIZE. I changed it from the original 2048 to a more comfortable 1048576 (to allow 1 Mo pictures transfer).

Unfortunately this causes a loss of connection:


LogNetPartialBunch:Warning: Channel[14] - Actor=BP_YagCharacter_C /Game/maps/UEDPIE_4_WelcomeMap.WelcomeMap:PersistentLevel.BP_YagCharacter_C_11 (Role=2 RemoteRole=3)State=open. Reliable partial bunch overflows reliable buffer!
 LogNetPartialBunch:Warning:    Num OutgoingBunches: 262. NumOutRec: 0
 LogNetTraffic:Warning: -------------------------
 LogNet: UNetConnection::Close: Name: IpConnection_2, Driver: GameNetDriver IpNetDriver_3, PC: BP_YagPlayerController_C_5, Owner: BP_YagPlayerController_C_5, Channels: 15, RemoteAddr: 127.0.0.1:7777, Time: 2014.12.14-17.24.48
 LogNet: UChannel::Close: Sending CloseBunch. ChIndex == 0. Name: ControlChannel_2
 LogNet: UChannel::ReceivedSequencedBunch: Bunch.bClose == true. ChIndex == 0. Calling ConditionalCleanUp.
 LogNet: UChannel::CleanUp: [GameNetDriver] [BP_YagPlayerController_C_2] [BP_YagPlayerController_C_2]. ChIndex == 0. Closing connection.
 LogNet: UNetConnection::Close: Name: IpConnection_5, Driver: GameNetDriver IpNetDriver_0, PC: BP_YagPlayerController_C_2, Owner: BP_YagPlayerController_C_2, Channels: 15, RemoteAddr: 127.0.0.1:59691, Time: 2014.12.14-17.24.48
 LogNet: UChannel::Close: Sending CloseBunch. ChIndex == 0. Name: ControlChannel_5
 LogNet:Warning: Network Failure: GameNetDriver[ConnectionLost]: Your connection to the host has been lost.
 LogNet:Warning: Network Failure: GameNetDriver[ConnectionLost]: Your connection to the host has been lost.
 LogNet:Warning: Network Failure: GameNetDriver[ConnectionLost]: Your connection to the host has been lost.
 LogNet: NetworkFailure: ConnectionLost, Error: 'Your connection to the host has been lost.'
 LogNet: DestroyNamedNetDriver IpNetDriver_3 [GameNetDriver]
 LogExit: GameNetDriver IpNetDriver_3 shut down

I could split the giant TArray into little 2048 sized TArrays, then make a TArray of TArray (possibly pushing up to the 3rd dimension in case i need pictures > 4 Mo (2048x2048)), but i get the feeling that it’s becoming really ugly.

Another possible way would be to send the image file itself to the server/other clients in a normalized dir and then use rpcs to send everybody instructions to reconstruct the texture, but i tried every keywords i could think of in google, tried to search the API doc for some sort of file sending function, i found nothing.

At last resort i could follow Rama’s sockets tut:

and try to make my own transfer lib, but besides the fact that it would be over my current skills and would require a lot of time for me, that would also mean asking my users to open a second port, which i would prefer not. I would like to remain as standard as possible.

So could anyone point me to any walkable path ? Should the sharing be done at texture level, TArray level or file level ?

Sharing avatar picture can’t be that difficult right ?

Cheers

File sharing should be done at file level.

So if you want to share avatar picture you may want make it simpler and upload the image only once / player.

Upload the desired image to a public server where then everyone can download the image and display it.

With this plugin you can make a simple webservice. (with php for example)
You can upload the image with a post request, return an url from the webservice, then just send the url to all the other clients.
As i see you can not parse a files with this plugin so it may require an another file IO plugin to read file content.

I hope it helps.

Hi Azarus,

This is indeed an excellent idea, very helpful and very appreciated !

I’ll give a try to this plugin or get some inspiration from it.

Thank you very much !

Hey everyone !

Happy new year to you all !

Ok, i am now trying to do this myself through IHttpRequest.

I have set up a small web server (apache + php) that works well.

I can download images from it using a GET IHttpRequest.

I can upload images to it using an html form.

But i currently can’t upload images directly from my code. I don’t know how to create a multipart/form-data request with UE4 api.

Here is the doc i am using:

http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4

I need to create something like that:


 Content-Type: multipart/form-data; boundary=AaB03x

   --AaB03x
   Content-Disposition: form-data; name="submit-name"

   Larry
   --AaB03x
   Content-Disposition: form-data; name="files"; filename="file1.txt"
   Content-Type: text/plain

   ... contents of file1.txt ...
   --AaB03x--

And here is my current code (not working):


// the local image file
FString UpFilePath = "/MyPath/MyFile.jpg";

// the data
TArray<uint8> UpFileRawData;
FFileHelper::LoadFileToArray(UpFileRawData, *UpFilePath);

// the request
TSharedRef<IHttpRequest> FileUploadRequest = (&FHttpModule::Get())->CreateRequest();
FileUploadRequest->OnProcessRequestComplete().BindRaw(this, &SYagOpenFileWidget::HttpUploadYagFileComplete);
FileUploadRequest->SetURL("http://MyServer.org/MyUploadPhpScript.php");
FileUploadRequest->SetVerb("POST");
FileUploadRequest->SetHeader("Content-Type", "multipart/form-data; boundary=---------------------------BoNjOuRcOcO");

// header group for the file
FileUploadRequest->SetHeader("Content-Disposition", "form-data; name=\"fileToUpload\"; filename=\"MyFile.jpg\"");
FileUploadRequest->SetHeader("Content-Type", "image/jpeg");
//FileUploadRequest->SetHeader("Content-Encoding", "gzip");

// content
FileUploadRequest->SetContent(UpFileRawData);
//FileUploadRequest->SetHeader("Content-Length", FString::FromInt(UpFileRawData.Num()));  //useless ? cf doc SetHeader(): "Content-Length is the only header set for you"

// request processing
FileUploadRequest->ProcessRequest();

When using this code in game, i get an error response from the php script telling me that there is an “undefined index: fileToUpload”.
So obviously my Content-Disposition header is not understood by the script.

One thing that is certainly wrong in my request is the absence of “boundary” lines, how do i add them ?
Another question is: am i using correctly the SetContent function ? It is possible to sent multiple contents through http multipart requests, but the SetContent() function seems to be unique to each request.
Am i missing something ?

Any http/ue4 guru reading this, your help will be greatly appreciated :slight_smile:

Cheers

Hi all !

Finally got this working. Hard work, boy i tried everything !!

So just in case it would help anyone landing here, here is my working code.


// TexturePath contains the local file full path

// file name
int32 LastSlashPos;
TexturePath.FindLastChar('/', LastSlashPos);
FString FileName = TexturePath.RightChop(LastSlashPos + 1);

// get data
TArray<uint8> UpFileRawData;
FFileHelper::LoadFileToArray(UpFileRawData, *TexturePath);

// prepare json data
FString JsonString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<TCHAR>::Create(&JsonString);

JsonWriter->WriteObjectStart();
JsonWriter->WriteValue("FileToUpload", FileName);
JsonWriter->WriteValue("ImageData", FBase64::Encode(UpFileRawData));
JsonWriter->WriteObjectEnd();
JsonWriter->Close();

// the json request
TSharedRef<IHttpRequest> SendJsonRequest = (&FHttpModule::Get())->CreateRequest();
SendJsonRequest->OnProcessRequestComplete().BindRaw(this, &SYagOpenFileWidget::HttpUploadYagFileComplete);
SendJsonRequest->SetURL("http://myserver/MyJsonHandlingPhpScript.php");
SendJsonRequest->SetVerb("POST");
SendJsonRequest->SetContentAsString(JsonString);
SendJsonRequest->ProcessRequest();

Hope this helps :slight_smile:

Cheers

Hi ,

Would you mind letting me know what includes you used? Also, I get some linker errors…

Nevermind - I figured out that I needed to include the HTTP dependency in the build.cs.

Good day, can i use similar method for upload save files on server? Could you show working PHP script?

Hello Kelt,

Sorry for the delay, i just saw your post.

Yes, you can use that for upload. In fact, what i was doing was 1/ uploading on the server and 2/ use a standard http request to get it back from server. So the uploading was naturally part of the process.
The problem with that was of course that the users data were stored on a private server, which is obviously unacceptable for public distribution.

Unfortunately for your question, i stopped using this mechanism and since it was 4 years ago, i don’t have the php script anymore.
But from what i recall, it was very standard php scripting, and as i wasn’t and still am not a regular php user, i merely adapted some lines of code i found on google, nothing fancy, very standard et quite easy.
Sorry for not being more helpful and good luck.