Unity Android load local MP3 or WAV as AudioClip - c#

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

Related

How To Load A Local File as an AssetBundle with WWW

So I have been trying to make it possible for users to load a .obj file and read it as an AssetBundle, but I can't figure it out.
I have figured out how to get the path of the file, but I can't load it as an asset bundle, it just returns null.
Here is my code :
WWW bundleRequest = new WWW(#"file://" + pathName);
while (!bundleRequest.isDone)
{
yield return null;
}
AssetBundle bundle = null;
if (bundleRequest.bytesDownloaded > 0)
{
AssetBundleCreateRequest myRequest = AssetBundle.LoadFromMemoryAsync(bundleRequest.bytes);
while (!myRequest.isDone)
{
Debug.Log("loading....");
yield return null;
}
if (myRequest.assetBundle != null)
{
bundle = myRequest.assetBundle;
GameObject model = null;
if (bundle != null)
{
AssetBundleRequest newRequest = bundle.LoadAssetAsync<GameObject>("Test");
while (!newRequest.isDone)
{
Debug.Log("loading ASSET....");
yield return null;
}
model = (GameObject)newRequest.asset;
bundle.Unload(false);
}
}
else
{
Debug.LogError("COULDN'T DOWNLOAD ASSET BUNDLE FROM URL");
}
}
else
{
Debug.LogError("COULDN'T DOWNLOAD ASSET BUNDLE FROM URL");
}
pathName here is: "C:\\Users\\mySuperCoolName\\OneDrive\\Documents\\Fun\\Programming\\Ungoing projects\\ThiefCop\\Unity Mobile\\Assets\\Prefabs\\TestOBJ.obj". Everything seems to work until AssetBundleCreateRequest when AssetBundle.LoadFromMemoryAsync() is called, where myRequest.assetBundle == null even if the file was downloaded correctly.
I also get an error which probably is linked with my problem : I have searched for what it meant but I couldn't find...
It is really hard to explain what I mean, but I really hope you can find an answer to this, I've been searching for hours and between us, I don't understand much of File loadind and Reading... Don't hesitate to ask if you didn't understand my bad english... Thank you in advance :)
https://docs.unity3d.com/ScriptReference/BuildPipeline.BuildAssetBundles.html
To be short :
Import your object in Unity
Give it a AssetBundle name (click on it then on bottom of the inspector view)
Call this function
for load obj files directly to you project, you need todo code what unpacks object file and then converts it to the Unity engine.
in assert store look at the Runtime OBJ Importer,
this should be right direction for your issue.
https://assetstore.unity.com/packages/tools/modeling/runtime-obj-importer-49547

App works in Unity editor but not on Android

Building a Unity front-end app that communicates with a PHP/MySQL back-end. Right now I am working on the register and login portions of it. In the Unity editor, all works perfectly, on the browser front end registration is working fine as well. But as soon as I take the working build in Unity and build it to test on my Google Pixel phone, registration and login both fail on my phone. The error message I am getting on Login is "Error parsing response from server, please try again!" which is in my UILogin.cs. Yet there is nothing else attached. I tried following the Unity documentation for debugging, adb, and DDMS to get access to find out what is happening but I have failed on all those ventures. Is there something particularly different about Android and WWW objects or web communications? Here is a portion of my UILogin.cs and also will include the Login.php.
UILogin.cs
IEnumerator AttemptLogin(bool quickLogin)
{
CreateLoadingScreen();
DeactivateForm();
WWWForm form = new WWWForm();
form.AddField("email", Email.text);
form.AddField("password", Password.text);
WWW www = new WWW(URL.GetLoginURL, form);
yield return www;
DestroyLoadingScreen();
ActivateForm();
ParseResult(www.text, quickLogin);
}
void ParseResult(string result, bool quickLogin)
{
byte resultCode;
if (!byte.TryParse(result, out resultCode))
Result.text = "Error parsing response from server, please try again!";
else if (resultCode == 0)
{
if (RememberToggle.isOn && !quickLogin) // Remember me
{
PlayerPrefs.SetInt("remember", 1);
PlayerPrefs.SetString("email", Email.text);
PlayerPrefs.SetString("password", Password.text);
}
else if (!quickLogin)
{
TemporaryAccount.Email = Email.text;
TemporaryAccount.Password = Password.text;
}
SceneManager.LoadScene(3);
}
else // Failure
{
if (quickLogin)
ShowForm();
Result.text = WebError.GetLoginError(resultCode);
}
}
Login.php
<?php
require "conn.php";
$stmt = $pdo->prepare("SELECT * FROM account WHERE email=:email");
$stmt->bindParam(":email", $_POST['email']);
$stmt->execute();
$count = $stmt->rowCount(); // gets count of records found
if($count > 0) {
$result = $stmt->fetch(); // gets resultset
if(!password_verify($_POST['password'], $result[4]))
echo "2";
else
echo "0";
}
else {
echo "1";
}
?>
Any ideas?
Update #1
I printed www.text and www.error after the yield return www; Text is null and error says: Unknown Error.
Update #2
Interestingly enough, I get Unknown Error from this as well:
Clickme.cs
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class Clickme : MonoBehaviour
{
public Text Result;
public void OnClick()
{
StartCoroutine(RunScript());
}
IEnumerator RunScript()
{
WWW www = new WWW("http://www.familypolaris.com/helloWorld.php");
yield return www;
if (www.text == "")
Result.text = "Result was empty, error: " + www.error;
}
}
HelloWorld.php
<?php
echo "Hello World!";
?>
I figured it out, so I will post the answer here. After somebody commented that they tried my code and it worked just fine, I decided to try another device here. That device also failed to register and login. This told me something was wrong with my build settings. My first idea was to switch build type from Gradle to Internal, and that solved the problem for both my devices immediately.

check if the image is already downloaded?

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.

APPNAME was compiled with optimization - stepping may behave oddly; variables may not be available

This project is develop in Unity3d and build it to iOS.
This is my .cs file.
IEnumerator DownloadModel(string modelUrl)
{
isDownloading = true;
// Wait for the Caching system to be ready
while (!Caching.ready)
yield return null;
// Load the AssetBundle file from Cache if it exists with the same version or download and store it in the cache
WWW www = WWW.LoadFromCacheOrDownload (modelUrl, 1);
Debug.Log ("modelURL : " + modelUrl);
while (!www.isDone) {
progressText.text = "Progress : " + (www.progress * 100).ToString ("N0") + "%";
Debug.Log("Download Progress : " + (www.progress * 100).ToString ("N0") + "%");
yield return null;
}
yield return www;
isDownloading = false;
if (www.error != null) {
throw new UnityException ("WWW download had an error:" + www.error);
}
AssetBundle bundle = www.assetBundle;
downloadedObjectContainer = bundle.LoadAllAssets ();
// tempObj = Instantiate(downloadedObjectContainer[0]) as GameObject;
//
// tempObj.transform.SetParent(this.transform);
isDownloading = false;
// Unload the AssetBundles compressed contents to conserve memory
bundle.Unload (false);
// memory is freed from the web stream (www.Dispose() gets called implicitly)
Debug.LogWarning ("START CALLING OUT OBJECT");
if (downloadedObjectContainer[0] != null) {
Debug.Log("Downloadable content count : " + downloadedObjectContainer.Length );
currentDownloadedModel = Instantiate(downloadedObjectContainer[0]) as GameObject;
Debug.Log("OBJECT INSTANTIATE.");
currentDownloadedModel.transform.SetParent(this.transform);
//set the ARContent and ImageTarget
sic.setARContentAndMarker(currentDownloadedModel, ImageTarget);
Debug.Log("CONTENT MARKER SET.");
currentDownloadedModel.SetActive(true);
}
Debug.LogWarning ("COROUTINE FINISHED");
}
and my "currentDownloadedModel" is declared at the top as GameObject.
public class cloudTrackableEventHandler : MonoBehaviour{
GameObject currentDownloadedModel;
When i build my App to Android, there is no problem at all. But once i build it in iOS, this error occurs
START CALLING OUT OBJECT
(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/UnityEngineDebugBindings.gen.cpp Line: 64)
Downloadable content count : 1
(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/UnityEngineDebugBindings.gen.cpp Line: 64)
MarcV2 was compiled with optimization - stepping may behave oddly; variables may not be available.
By the Debug.Log(), I found out that the problem occurs when I want to assign the currentDownloadedModel with the model i instantiate. Can anyone help me on this ? Thanks in advance.
Note : the "Script call optimization" at the unity player settings is set to "slow and safe"
For anyone who also face this problem, try uncheck the "Strip Engine Code" at File > Build Settings > Player Settings (iOS).
http://docs.unity3d.com/Manual/iphone-playerSizeOptimization.html

Unity C# Add Song on runtime then play that song

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.

Categories

Resources