Im using unity3d www to download an image from a web server and stored it to my local storage. The only checking that I have is System.IO.File.Exists() to check if its already downloaded. My problem is that it will not re-download when the image from the web server is updated or replace with the same exact filename. I also tried www.responseHeaders but it will only be available once the image is downloaded. I know about assetbundle but I don't want to create assetbundle for just one image.
Edit:
Here is my code, slightly modified. Fist I connect to webserver to fetch all the projects data including the image url as an array of data in json type. Then i will loop through that array which has the url of the image to download.
void ConnectToWeb()
{
StartCoroutine(AppManager.Instance.WebRequest("http://www.google.com",
(AppManager.HttpResponse callback) =>
{
if (callback.ResponseCode == 0 || callback.ResponseCode != 200)
{
throw new ArgumentException(callback.Error);
}
if (callback.Done == true)
{
StartCoroutine(LoadData(callback.JsonResponse));
}
})
);
}
private IEnumerator LoadData(JsonData itemData)
{
for (int i = 0; i < itemData["results"].Count; i++)
{
if (!System.IO.File.Exists(itemData["results"][i]["picture_name"].ToString()))
{
WWW www = new WWW(itemData["results"][i]["picture_url"].ToString());
while (!www.isDone)
{
Debug.Log("downloaded " + (www.progress * 100).ToString() + "%...");
yield return null;
}
File.WriteAllBytes(imagePath, www.bytes);
}
}
}
Use PlayerPrefs
public static void SetBool(string key, bool state)
{
PlayerPrefs.SetInt(key, state ? 1 : 0);
}
public static bool GetBool(string key)
{
int value = PlayerPrefs.GetInt(key);
return value == 1 ? true : false;
}
void DownloadImage() {
var isDownloaded = GetBool("IsImageDownloaded"); // <-- Check this first!
if(!isDownloaded) {
DownloadImageInternal(); // The real function to download an image
SetBool("IsImageDownloaded", true);
}
}
Note that System.IO.File.Exists() may or may not work for Desktop, Android and iOS apps, it is likely your app works fine in Unity Editor, but crashes when deployed to Android or iOS devices.
Related
After some advise around calls to firebase storage primarily how (from a sync POV) I should be checking to see if a file exists.
To set the context I am reading in a state file as json using the DownloadCoroutine function below and passing this into a JsonTextReader, this is working fine as long as the file exists in the first place (which as a new user it will not).
So then I wrote the below checkIfFile exists function which also works in a standalone capacity (Grabs the URL to prove that this file does in fact exist). Once this function has completed I then set a bool (SaveFileExists) to say this is/is not an existing file then go create one dependent on the state.
Where my problem lies is the order in which these functions are executed, I need the check to happen before any other methods are executed, they are both called in the LoadScene function currently. What I think I need to do is make the check an Async method returning a task? If so how would this look and where should I be calling it from, I have tried this but I think it keeps locking the main thread.
So the state right now is that because that bool doesnt change in time, the download of the storage file doesnt happen and the Json is never read in and throws an error, at the end of the console output the checkfile URL is outputted, any help would be great ,thanks.
private IEnumerator DownloadCoroutine(string path)
{
var storage = FirebaseStorage.DefaultInstance;
var storageReference = storage.GetReference(path);
if (SaveFileExists == true)
{
var DownloadTask = storageReference.GetBytesAsync(long.MaxValue);
yield return new WaitUntil(predicate: () => DownloadTask.IsCompleted);
byte[] fileContents = DownloadTask.Result;
retrievedSaveFile = Encoding.Default.GetString(fileContents);
Debug.Log("Downloading the save file");
}
else
{
createNewSaveFile(path);
}
}
Check to see if the Json file exists
private void CheckIfFileExists(string path)
{
var storage = FirebaseStorage.DefaultInstance;
var storageReference = storage.GetReference(path);
storageReference.GetDownloadUrlAsync().ContinueWith(task => {
if (!task.IsFaulted && !task.IsCanceled) {
Debug.Log("Download URL: " + task.Result);
SaveFileExists = true;
}
else{
Debug.Log("file doesnt exist so we create one");
}
});
}
Load scene
public IEnumerator LoadLastScene()
{
var User = FirebaseAuth.DefaultInstance.CurrentUser;
Debug.Log("USERID IS " + User.UserId.ToString());
CheckIfFileExists("Saves://" + User.UserId.ToString() + "saveFile.json");
yield return DownloadCoroutine("Saves://" + User.UserId.ToString() +
"saveFile.json");
}
The things you have to do is that
User authentication
Everything must be inside an async task function
check if the file exists
await storageRef.Root.Child(fUser.UserId).Child("Data").GetDownloadUrlAsync().ContinueWith(async task2 =>
{
if (task2.IsFaulted || task2.IsCanceled)
{
Debug.Log("<color=Red>File Not Exists</color>");
}
else
{
Debug.Log("<color=green>File Exists</color>");
await DownloadData();
}
});
I'm able to load audio using UnityWebRequestMultimedia.GetAudioClip in the Unity Editor with the following code but when I run it on Android I am unable to load any files from the user's device.
void Start() {
audio = GetComponent<AudioSource>();
string fullPath = Path.Combine("file://" + previewSong);
StartCoroutine(GetAudioClip(fullPath));
}
IEnumerator GetAudioClip(string fullPath)
{
using (var uwr = UnityWebRequestMultimedia.GetAudioClip(fullPath, AudioType.MPEG))
{
((DownloadHandlerAudioClip)uwr.downloadHandler).streamAudio = true;
yield return uwr.SendWebRequest();
if (uwr.isNetworkError || uwr.isHttpError)
{
debugSongPath2.text = uwr.error;
yield break;
}
DownloadHandlerAudioClip dlHandler = (DownloadHandlerAudioClip)uwr.downloadHandler;
if (dlHandler.isDone)
{
audio.clip = dlHandler.audioClip;
if (audio.clip != null)
{
audio.clip = DownloadHandlerAudioClip.GetContent(uwr);
Debug.Log("Playing song using Audio Source!");
}
else
{
Debug.Log("Couldn't find a valid AudioClip.");
}
}
else
{
Debug.Log("The download process is not completely finished.");
}
}
}
The errors I experience vary depending on how I form the start of the URL.
Path.Combine("file:/" + previewSong);
malformed URL
Path.Combine("file://" + previewSong);
http:/1.1 404 not found
Path.Combine("file:///" + previewSong);
unknown error
Path.Combine("file:////" + previewSong);
unknown error
When I output the URLs on my phone they look correct. Here's an example path:
file:///storage/emulated/0/Music/Deorro/Deorro - Five Hours.mp3
I was previously loading audio from this URL successfully with WWW.GetAudioClip but that's obsolete. This leads me to believe the URL is correct. I've tried building with and without Development Mode. Not sure what else to try. I'd like to get this working with UnityWebRequestMultimedia but if there is an alternative, more effective way of loading local files I'm open to it.
Change your build settings to give write access to external sd card. I am using below code to get audio from specified location in android. I tried your code but somehow its not working.
IEnumerator GetAudioClip2(string fullPath)
{
debugSongPath2.text = previewSong;
using (UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(fullPath, AudioType.MPEG))
{
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
}
else
{
AudioClip myClip = DownloadHandlerAudioClip.GetContent(www);
audioSource.clip = myClip;
audioSource.Play();
}
}
}
My full path was "file:///storage/emulated/0/Samsung/Music/Over the Horizon.mp3"
The issue was with the new Android 11 scoped storage which disables READ_EXTERNAL_STORAGE. I had to add the following to my AndroidManifest.xml:
<manifest ... >
<!-- This attribute is "false" by default on apps targeting Android 10 or higher. -->
<application android:requestLegacyExternalStorage="true" ... >
...
</application>
</manifest>
https://developer.android.com/training/data-storage/use-cases#opt-out-scoped-storage
I want the embedded GeckoFx 60 to download a file and then open it with the default app.
By default it seems like GeckoFx does not do anything when the client requests to download a file.
To handle the download request I enabled an event handler:
LauncherDialog.Download += LauncherDialog_Download;
Then I found two possibilities to download or open a file via the HelperAppLauncher.
This one saves the requested file to a temp folder and opens it:
private void LauncherDialog_Download(object sender, LauncherDialogEvent e)
{
// direct open, file will be stored in C:\Users\Username\AppData\Local\Temp\
e.HelperAppLauncher.LaunchWithApplication(null, false);
}
I did not find a way to configure the save path. This other possible solution allows me to set the save path myself:
private void LauncherDialog_Download(object sender, LauncherDialogEvent e)
{
nsILocalFileWin objTarget = Xpcom.CreateInstance<nsILocalFileWin>("#mozilla.org/file/local;1");
var downloadPath = #Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\CustomFolder\\" + e.Filename;
using (nsAString tmp = new nsAString(downloadPath))
{
objTarget.InitWithPath(tmp);
}
e.HelperAppLauncher.SaveToDisk(objTarget, false);
Task.Run(() =>
{
Thread.Sleep(3000);
System.Diagnostics.Process.Start(downloadPath);
});
}
That Task.Run() works, but is quite ugly and error prone. I could not find a better solution though. I tried adding a WebProgressListener like this:
var webProgressListener = new WebProgressListener();
webProgressListener.OnStatusChangeCallback+= OnStatusChangeCallback;
e.HelperAppLauncher.SetWebProgressListener(webProgressListener);
webProgressListener.IsListening is true, but my method OnStatusChangeCallback is never called. Am I doing something wrong? Is there a newer way?
How can I get notified that the download is completed?
Or how do I set the path for LaunchWithApplication?
Not the best solution but here is my solution :
Task.Run(() =>
{
long sizefirst = 0;
while (true)
{
Thread.Sleep(1000);
if (File.Exists(downloadPath))
{
if (sizefirst == 0)
{
sizefirst = new FileInfo(downloadPath).Length;
continue;
}
long len_now = new FileInfo(downloadPath).Length;
if (len_now > sizefirst)
{
sizefirst = len_now;
continue;
}
else
{
System.Diagnostics.Process.Start(downloadPath);
break;
}
}
}
});
I am trying to allow the user to Import a song using a file browser Add on from the unity store. Currently I am trying to just play the song in the menu, just to see if I have loaded it correctly but it seems I cannot get past this stage. The WWW function makes no sense to me and I will need someone to explain it very simply. In my eyes this should open the file browser, the user then selects that file, then the selected file should be loaded into the audio clip. then the debug play button should play that audio clip, but from pausing the game I can see that the file browser finds the file and has the correct string but the audio is never assigned to the clip correctly. I am using .wav and .ogg files so no problem with formats.
public FileBrowser fb = new FileBrowser();
public bool toggleBrowser = true;
public string userAudio;
public AudioSource aSource;
public AudioListener aListener;
public AudioClip aClip;
void Start()
{
if (aSource == null)
{
aSource = gameObject.AddComponent<AudioSource>();
aListener = gameObject.AddComponent<AudioListener>();
}
}
void OnGUI()
{
// File browser, cancel returns to menu and select chooses music file to load
if (fb.draw())
{
if (fb.outputFile == null)
{
Application.LoadLevel("_WaveRider_Menu");
}
else
{
Debug.Log("Ouput File = \"" + fb.outputFile.ToString() + "\"");
// Taking the selected file and assigning it a string
userAudio = fb.outputFile.ToString();
}
}
}
public void playTrack()
{
//assigns the clip to the audio source and plays it
aSource.clip = aClip;
aSource.Play();
}
IEnumerator LoadFilePC(string filePath)
{
filePath = userAudio;
print("loading " + filePath);
//Loading the string file from the File browser
WWW www = new WWW("file:///" + filePath);
//create audio clip from the www
aClip = www.GetAudioClip(false);
while (!aClip.isReadyToPlay)
{
yield return www;
}
}
}
There are a few things that may be causing you some trouble.
I'm not sure how exactly the file browser add-on works, but in the code you posted the PlayTrack() function is never called, and the LoadFilePC coroutine is never started. Even if you're loading the file correctly, it's never being assigned to the AudioSource or playing.
Try adding "playTrack();" at the end of your coroutine, and starting it using the MonoBehaviour.StartCoroutine() function after the file is selected.
Firstly I must point out that I am very new to C#.
I am developing an application using Unity3D, and part of the application requires that I parse a JSON file stored on my server.
The problem that I am having is that sometimes everything works perfectly, and other times the app hangs on the downloading the JSON. I don't receive any errors, the script just never reaches 100% on the progress.
Here is my code:
public IEnumerator DownloadJSONFile(string url)
{
Debug.Log("JSON URL: "+ url);
mJsonInfo = new WWW(url);
yield return mJsonInfo;
mIsJSONRequested = true;
}
private void LoadJSONData(string jsonUrl)
{
Debug.LogWarning("LoadJSONData, url= "+jsonUrl);
if(!mIsJSONRequested){
StartCoroutine(DownloadJSONFile(jsonUrl));
} else {
if(mJsonInfo.progress >= 1)
{
if(mJsonInfo.error == null )
{
//** PARSE THE JSON HERE **//
}else
{
Debug.LogError("Error downloading JSON");
mIsLoadingData = false;
}
} else {
Debug.LogWarning("!! ### JSON DOWNLOADING: "+mJsonInfo.progress+"%");
if(mJsonInfo.error != null )
{
Debug.LogError("Error downloading JSON");
Debug.LogError("JSON Error:"+mJsonInfo.error);
mIsLoadingData = false;
}
}
}
}
Like I said, 50% of the time the JSON data gets loaded nearly instantly, and 50% of the time the progress never reaches 1. I never receive an error in form the mJsonInfo.error variable.
Any suggestions as to what I am doing wrong would be greatly appreciated!
You need to wait until the download is complete.
As stated in the documentation you need to:
var www = new WWW(...);
yield return www;
So you need to modify the return type of your method from void to IEnumerator.
private IEnumerator LoadJSONData(string jsonUrl)
{
Debug.LogWarning("LoadJSONData, url= "+jsonUrl);
if(!mIsJSONRequested)
{
// Gets the json book info from the url
mJsonInfo = new WWW(jsonUrl);
yield return mJsonInfo; //Wait for download to complete
mIsJSONRequested = true;
}
else
{
...
}
}
isDone is that you need,
WWW lWWW = new WWW(...)
if(lWWW.isDone)
then parse it
I found the problem, I thought I would post my solution for anyone else experiencing the same problem.
The solution in the end was the JSON file on the server. I was using PHP (CakePHP) to generate the JSON, when opening the PHP generated file via the browser the response time was instant, but from my mobile app for some reason it would hang. So I changed my server side code to actually create and updated an actual JSON file, and now everything works fine.