question

brendan avatar image
brendan asked

How to upload files to PlayFab's Content Service

zacb
started a topic on Tue, 16 June 2015 at 2:16 PM

Scenario: I have a file (UB_Icon.png) located in my /Assets/StreamingAssets project folder that I need to upload to the PlayFab content service.

Problem: The out of the box WWW Unity3d class does not afford PUT capability that is compatible with AWS.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

brendan avatar image
brendan answered

Best Answer
zacb said on Fri, 19 June 2015 at 12:03 PM

This class (attached) provides a simple example for how to upload files to PlayFab's content service using .NET's built-in HttpWebRequest.

The generalized process:

  1. Determine your file path (At runtime, this will typically be the StreamingAssets folder)

  2. Determine the MIME type that corresponds to your file (Unity AssetBundles use MIME Type: application/x-gzip)

  3. Ask the PlayFab service for a remote upload endpoint using Admin/GetContentUploadUrl

  4. Convert your file to a byte[ ]

  5. Issue a HTTP PUT command to the provided endpoint, passing along the file in the request stream

  6. When you need to access the file again, ask the PlayFab service for a remote download endpoint using Client/GetContentDownloadUrl

  7. Issue a HTTP GET command to retrieve your file from the provided endpoint.

See attached file for a complete, Unity3d ready code sample.


6 Comments
johntube said on Fri, 19 June 2015 at 7:02 AM

@zacb :

GetContentUploadUrlResult is only available as Admin API call. Please confirm that this is the purpose of the code snippet you're posting here.

I have already requested that it should be made available as a Client API call.


zacb said on Fri, 19 June 2015 at 12:03 PM

This class (attached) provides a simple example for how to upload files to PlayFab's content service using .NET's built-in HttpWebRequest.

The generalized process:

  1. Determine your file path (At runtime, this will typically be the StreamingAssets folder)

  2. Determine the MIME type that corresponds to your file (Unity AssetBundles use MIME Type: application/x-gzip)

  3. Ask the PlayFab service for a remote upload endpoint using Admin/GetContentUploadUrl

  4. Convert your file to a byte[ ]

  5. Issue a HTTP PUT command to the provided endpoint, passing along the file in the request stream

  6. When you need to access the file again, ask the PlayFab service for a remote download endpoint using Client/GetContentDownloadUrl

  7. Issue a HTTP GET command to retrieve your file from the provided endpoint.

See attached file for a complete, Unity3d ready code sample.


zacb said on Fri, 19 June 2015 at 12:08 PM

@johntube

Yes, until we have a more complete CDN solution, we have decided to not let clients upload files at will.

This works very well for the scenario where the developer wants to upload new assets and content and have their clients dynamically stream and load the updates.

This is less than ideal for games that would like to store user generated content directly from the client.

Hope this clears up the usage around these systems.

-Zac


johntube said on Fri, 19 June 2015 at 7:00 PM

@zacb

typo in your answer in 6. Client/GetContentDownloadUrl instead of Client/GetContentUploadUrl

Yes everything is clear. I'm happy that you guys are thinking about a Client/GetContentDownloadUrl in the [near (I hope)] future.

I just want to add that for those who need file upload from Clients should think about Base64 encoding and the mind the overhead in size (Amazon's DynamoDB limit for fields (key+value) = 400KB).

I also want to mention that Admin API calls are available from Server SDK.


Oleg.ragozin said on Tue, 01 September 2015 at 1:23 AM

Hello

Very nice example and works fine in Unity then I use it in a Play mode. But if I try to use it incide Unity Editor (in Editor or EditorWindow) for creating the Unity IDE based interface for updating files at PlayFab it is not working. I think that problem is somewere in a threading because www requests works fine in PlayFabHTTP, but callbacks has never been calling.

Could you please provide a working example in this case?


zacb said on Wed, 02 September 2015 at 9:41 AM

Hey Oleg,

Having stand-alone and editor tools to assist with using PlayFab is definitely something that we are looking to provide; however, I cannot say when we will get a chance to test and release these to the community. Stay tuned, I will post back here when I have additional information.

-z

4 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

alcomsoftstr avatar image alcomsoftstr commented ·

>3. Ask the PlayFab service for a remote upload endpoint using Admin/GetContentUploadUrl

How to use the Admin API?

0 Likes 0 ·
brendan avatar image brendan alcomsoftstr commented ·

The Admin API is called the same way any other API is called in our service, whether Client, Server, Matchmaker, or Admin. It's an http Web Request to the endpoint for your title. In the case of Admin and Server API calls, they must have the Secret Key for the title in the headers. Can you be more specific about what it is you're trying to do, what API call you're using, and what problems you're having using the response data?

0 Likes 0 ·
alcomsoftstr avatar image alcomsoftstr brendan commented ·

I want to upload an avatar Unity3D. Did not find Admin API in asset. I wanted to download via the CloudScript, but there is also no Admin API. I do not understand how to get a link to upload an image

0 Likes 0 ·
Show more comments
contact@gentlymad.org avatar image
contact@gentlymad.org answered

I have the same problem with WWW not supporting PUT.
Is the mentioned example still available somewhere? The attached file seems to be gone.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

brendan avatar image
brendan answered

Sorry about that - our new support system lacks file attachments (other than images). We'll look into finding another way to enable that. Meanwhile, here's the complete contents of the original file:

 

using UnityEngine;
using System.Collections;
using System.IO;
using System.Net;
using System.Text;
using PlayFab;
using PlayFab.AdminModels;
using PlayFab.ClientModels;

/// <summary>
/// The out of the box WWW Unity3d class does not afford compatable PUT capability with AWS
/// This class provides a simple example for how to upload files to PlayFab's content service using .NET's built-in HttpWebRequest.
///
/// Scenario: I have a file (UB_Icon.png) located in my /Assets/StreamingAssets project folder that I need to upload to the PlayFab content service and download at a later time for use within my client.
/// </summary>
public class UploadToPlayFabContentService : MonoBehaviour {

public string testPngFileName = "UB_Icon.png"; // The name of the file to send
public string mimeType = "image/png"; // The MIME type that corresponds to the file above
public string contentKey = "images/UB_Icon.png"; // this is the location and 'identifier' for your uploaded file, you will need this key to access the file after uploading

private string assetPath = ""; // The assetPath where the file can be found (will varry depending on the platform)
private Texture2D downloadedImage; // We are storing the downloaded file in a Texture2D as a simple example of the service
private bool isImageDownloaded = false;
private bool isImageUploaded = false;

// Use this for initialization
void Start () {
this.downloadedImage = new Texture2D(0,0);
StartCoroutine(GetFilePath());
}

/// <summary>
/// Gets the reletive file path; works acrocss Unity build targets (Web, iOS, Android, PC, Mac)
/// </summary>
/// <returns> The assetPath where the file can be found (will varry depending on the platform) </returns>
IEnumerator GetFilePath()
{
string streamingAssetPath = Application.streamingAssetsPath;
string filePath = System.IO.Path.Combine(streamingAssetPath, testPngFileName);
if (filePath.Contains("://"))
{
WWW www = new WWW(filePath);
yield return www;
this.assetPath = www.text;
}
else
{
this.assetPath = filePath;
}
}


void OnGUI()
{
if(GUI.Button(new Rect(Screen.width /2 - 100, Screen.height/2 - 50,200, 100), "TEST UPLOAD"))
{
this.isImageUploaded = false;
this.isImageDownloaded = false;
Debug.Log(string.Format("File: {0}",assetPath));
GetContentUploadURL();
}

if(this.isImageUploaded == true)
{
if(this.isImageDownloaded == false)
{
if(GUI.Button(new Rect(Screen.width /2 - 100, Screen.height/2 + 50,200, 100), "TEST DOWNLOAD"))
{
GetContentDownloadURL();
}
}
else
{
if(GUI.Button(new Rect(Screen.width /2 - 100, Screen.height/2 + 50,200, 100), this.downloadedImage))
{
Debug.Log("Demo Complete!");
}
}
}
}


/// <summary>
/// Requests a remote endpoint for uploads from the PlaFab service.
/// </summary>
void GetContentUploadURL()
{
GetContentUploadUrlRequest request = new GetContentUploadUrlRequest();

request.Key = this.contentKey; // folder location & file name to use on the remote server
request.ContentType = this.mimeType; // mime type to match the file

PlayFabAdminAPI.GetContentUploadUrl(request, OnGetContentUploadURLSuccess, OnPlayFabError);
}

/// <summary>
/// Called after a successful GetContentUploadUrl request
/// </summary>
/// <param name="result"> GetContentUploadUrlRequest details </param>
void OnGetContentUploadURLSuccess(GetContentUploadUrlResult result)
{
Debug.Log(string.Format("Endpoint URL Recieved: {0}", result.URL));
byte[] fileContents = File.ReadAllBytes(assetPath);
PutFile(result.URL, fileContents);
}

/// <summary>
/// Requests a remote endpoint for downloads from the PlaFab service.
/// </summary>
void GetContentDownloadURL()
{
/* Developer Note:
To test this script in isolation ( i.e. running in a project without a login)
Use this is a shortcut for accessing client API calls
*/

// PlayFabClientAPI.AuthKey = "Paste your valid AuthKey here (returned from any PlayFab login API call)";

GetContentDownloadUrlRequest request = new GetContentDownloadUrlRequest ();
request.Key = this.contentKey;
PlayFabClientAPI.GetContentDownloadUrl (request, OnGetContentDownloadURLSuccess, OnPlayFabError);
}

/// <summary>
/// Called after a successful GetContentUploadUrl request
/// </summary>
/// <param name="result"> GetContentUploadUrlRequest details </param>
void OnGetContentDownloadURLSuccess(GetContentDownloadUrlResult result)
{
Debug.Log(string.Format("Endpoint URL Recieved: {0}", result.URL));
StartCoroutine(GetFile(result.URL));

}

/// <summary>
/// Called after a failed GetContentUploadUrl request
/// </summary>
/// <param name="result">Error details</param>
void OnPlayFabError(PlayFabError error)
{
Debug.LogWarning(string.Format("ERROR: [{0}] -- {1}", error.Error, error.ErrorMessage));
}

/// <summary>
/// Puts the file.
/// </summary>
/// <param name="postUrl">Remote URL to use (obtained from GetContentUploadUrl) </param>
/// <param name="payload">The file to send converted to a byte[] </param>
public void PutFile(string putURL, byte[] payload)
{
var request = (HttpWebRequest)WebRequest.Create(putURL);
request.Method = "PUT";
request.ContentType = this.mimeType;

if (payload != null)
{
Stream dataStream = request.GetRequestStream();
dataStream.Write(payload, 0, payload.Length);
dataStream.Close();
}
else
{
Debug.LogWarning(string.Format("ERROR: Byte arrry was empty or null"));
return;
}

Debug.Log("Starting HTTP PUT...");
HttpWebResponse response = (HttpWebResponse)request.GetResponse();

if(response.StatusCode == HttpStatusCode.OK)
{
Debug.Log("...HTTP PUT Successful");
this.isImageUploaded = true;
}
else
{
Debug.LogWarning(string.Format("ERROR: [{0}] -- {1}", response.StatusCode, response.StatusDescription));
}
}

/// <summary>
/// Gets the file from the PlayFab content service.
/// </summary>
/// <param name="getURL">The URL location of the file</param>
public IEnumerator GetFile(string getURL)
{
// Start a download of the given URL
var www = new WWW(getURL);

// wait until the download is done
yield return www;

Debug.Log("...HTTP GET Successful");

// assign the downloaded image to a Texture2D
this.downloadedImage.LoadImage(www.bytes);
this.isImageDownloaded = true;
}

void Update()
{

}
}
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

jrDev avatar image
jrDev answered

Hello, I am trying to use this but I get the error:

 

 The type or namespace name `AdminModels' does not exist in the namespace `PlayFab'. Are you missing an assembly reference?

Any help?

 

Thanks,

jrDev

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

brendan avatar image
brendan answered

You can find everything rolled up in one in the Combined asset folder in the Unity SDK, here:

https://github.com/PlayFab/UnitySDK/tree/master/PlayFabCombinedTestingSample/Assets/PlayFabSDK/Public. This includes all four APIs - Client, Server, Matchmaker, and Admin.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

jrDev avatar image
jrDev answered

Hey,

 

That's what I needed. Also, is the best practice for Unity to upload files Asset Bundles? I mean, currently in the code posted here, it's to upload one image. But for my situation, I would like to store sound files that need to be downloaded into the game, I will be adding more sounds when I send out updates and I don't want to have the file of the game be too big. So is Asset Bundles what I should use, and how would I implement this, like the MIME type and such.

 

Thanks,

jrDev

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

brendan avatar image
brendan answered

Presumably you mean our Content service, is that correct? That's definitely one option. Our Content service uses CloudFront for CDN, so we pass along the cost for that directly, with a 10% addition to cover our costs, as we do for EC2 custom game server hosting. When adding Unity Asset packages as Content, we use application/x-gzip.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

jrDev avatar image
jrDev answered

Ok, so I don't know if this is the thread to go into detail about it. But as I said above, the plan is the have the basic small file for initial download for players, and new content (which will include probably thousands of small sound files) will be automatically downloaded when the player opens the app. You mentioned that this is one option, what are the others? And what are the costs you mentioned above too?

 

Thanks,

jrDev

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

brendan avatar image
brendan answered

For small amounts of content, Title Data would also be a way to store the data for all players. Since you're talking about Unity asset files though, it'll be more convenient for you to use Content. The costs for CloudFront can be found on the AWS page for that service, here: https://aws.amazon.com/cloudfront/pricing/. And to be clear, it's the CloudFront price listed there plus 10% to cover our costs.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

jrDev avatar image
jrDev answered

So by using Cloudfront, I can just upload the Asset File without having to use this script? Is there a workflow I must be following?

 

Thanks,

jrDev

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.