I've been banging my head off an issue for some time now.
I'm working on a childrens game (flash as3) in which stories, poems, songs, etc are read to the child. There's a voice recording reading/singing the text, the text highlights as the words are spoken/sung. The child may toggle on and off the voice and word highlighting independantly. The child may also opt to make their own recording. Now said recording works fine. I'm able to capture it from the mic and the child may play it back no problem.
The issue lies in that the client wants the child to be able to save this recording to a web server.
I thought I had the issue solved by using a URLRequest and URLLoader object. The mp3 was showing up in the specified folder on the webserver. I played it with a media player and it worked. The child could also load said file no problem.
THEN, when I tried it via browser(instead of flash player) I got the dreaded sandbox error. The only way such an operation with said objects can occur in a browser environment is if it's user initiated via a dialogue window. This is children were talking about and we're not saving locally anyway.
The child is provided 3 save slots which they click on (WSprites). Which technically is user initiated, but flash has no way of knowing that. The 3 save slots hold the sounds in memory and only change when the user records or loads. When the user saves, they get sent off to php to save.
Now I did try using js as a middleman, but I ended up losing my bytes in the process and my js and php are probly the weakest areas of all my programming skills.
My question is, does anyone know a way of sending a byte array off to php without setting off the sandbox. (preferably without js, but if I have to I have to)
Below is the php script:
<?php
$default_path = 'images/';
// check to see if a path was sent in from flash //
$target_path = ($_POST['dir']) ? $_POST['dir'] : $default_path;
if (!file_exists($target_path)) mkdir($target_path, 0777, true);
// full path to the saved image including filename //
$destination = $target_path . basename( $_FILES[ 'Filedata' ][ 'name' ] );
// move the image into the specified directory //
if (move_uploaded_file($_FILES[ 'Filedata' ][ 'tmp_name' ], $destination)) {
echo "The file " . basename( $_FILES[ 'Filedata' ][ 'name' ] ) . " has been uploaded;";
} else {
echo "FILE UPLOAD FAILED";
}
?>
Below is the as3 method that interacts with it:
public function save(slotNum:uint, byteArray:ByteArray, fileName:String,
$destination:String = null, $script:String=null,
parameters:Object = null):void
{
//trace("this happens"); //debug
_curRecordSlot = slotNum; //set slot number
_recorder = _recordSlots[_curRecordSlot]; //set recorder to new slot
_saveFileName = "recording" + _curRecordSlot.toString() + ".mp3"; //set recording file name
var i: int;
var bytes:String;
var postData:ByteArray = new ByteArray();
postData.endian = Endian.BIG_ENDIAN;
var ldr:URLLoader = new URLLoader(); //instantiate a url loader
ldr.dataFormat = URLLoaderDataFormat.BINARY; //set loader format
_request = new URLRequest(); //reinstantiate request
_request.url = $script; //set path to upload script
//add Filename to parameters
if (parameters == null)
{
parameters = new Object();
}
parameters.Filename = fileName;
//add parameters to postData
for (var name:String in parameters)
{
postData = BOUNDARY(postData);
postData = LINEBREAK(postData);
bytes = 'Content-Disposition: form-data; name="' + name + '"';
for ( i = 0; i < bytes.length; i++ )
{
postData.writeByte( bytes.charCodeAt(i) );
}
postData = LINEBREAK(postData);
postData = LINEBREAK(postData);
postData.writeUTFBytes(parameters[name]);
postData = LINEBREAK(postData);
}
//add img destination directory to postData if provided //
if ($destination)
{
postData = BOUNDARY(postData);
postData = LINEBREAK(postData);
bytes = 'Content-Disposition: form-data; name="dir"';
for ( i = 0; i < bytes.length; i++ )
{
postData.writeByte( bytes.charCodeAt(i) );
}
postData = LINEBREAK(postData);
postData = LINEBREAK(postData);
postData.writeUTFBytes($destination);
postData = LINEBREAK(postData);
}
//add Filedata to postData
postData = BOUNDARY(postData);
postData = LINEBREAK(postData);
bytes = 'Content-Disposition: form-data; name="Filedata"; filename="';
for ( i = 0; i < bytes.length; i++ )
{
postData.writeByte( bytes.charCodeAt(i) );
}
postData.writeUTFBytes(fileName);
postData = QUOTATIONMARK(postData);
postData = LINEBREAK(postData);
bytes = 'Content-Type: application/octet-stream';
for ( i = 0; i < bytes.length; i++ )
{
postData.writeByte( bytes.charCodeAt(i) );
}
postData = LINEBREAK(postData);
postData = LINEBREAK(postData);
postData.writeBytes(byteArray, 0, byteArray.length);
postData = LINEBREAK(postData);
//add upload file to postData
postData = LINEBREAK(postData);
postData = BOUNDARY(postData);
postData = LINEBREAK(postData);
bytes = 'Content-Disposition: form-data; name="Upload"';
for ( i = 0; i < bytes.length; i++ )
{
postData.writeByte( bytes.charCodeAt(i) );
}
postData = LINEBREAK(postData);
postData = LINEBREAK(postData);
bytes = 'Submit Query';
for ( i = 0; i < bytes.length; i++ )
{
postData.writeByte( bytes.charCodeAt(i) );
}
postData = LINEBREAK(postData);
//closing boundary
postData = BOUNDARY(postData);
postData = DOUBLEDASH(postData);
//finally set up the urlrequest object //
_request.data = postData;
_request.contentType = 'multipart/form-data; boundary=' + _boundary;
_request.method = URLRequestMethod.POST;
_request.requestHeaders.push( new URLRequestHeader( 'Cache-Control', 'no-cache' ) );
//add listener to listen for completion
ldr.addEventListener(Event.COMPLETE, onSaveComplete, false, 0, true);
//add listener for io errors
ldr.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler, false, 0, true);
//add listener for security errors
ldr.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError, false,
0, true);
ldr.load(_request); //load the file
}
The above code works great in a flash player, but triggers sandbox error in browser.
Edit:
As requested here is my embed code(i replaced anywhere that had the name of the game with TITLEOFGAME):
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>TITLEOFGAME</title>
<meta name="description" content="" />
<script src="js/swfobject.js"></script>
<script>
var flashvars = {
};
var params = {
menu: "false",
scale: "noScale",
allowFullscreen: "true",
allowScriptAccess: "always",
bgcolor: "",
wmode: "direct" // can cause issues with FP settings & webcam
};
var attributes = {
id:"TITLEOFGAME"
};
swfobject.embedSWF(
"Hub.swf",
"altContent", "900", "506", "10.0.0",
"expressInstall.swf",
flashvars, params, attributes,
{name:"TITLEOFGAME"}
);
</script>
<style>
html, body { height:100%; overflow:hidden; }
body { margin:0; }
</style>
</head>
<body>
<div id="altContent">
<h1>TITLEOFGAME</h1>
<p><a href="http://www.adobe.com/go/getflashplayer">Get Adobe Flash
player</a></p> //this line was just moved down for limitations text input for
//this post
</div>
</body>
</html>
Seems it may be the content type or losing the mouse event in the stack.
Apparently this only happens if the URLLoader POST contains a 'filename' attribute in the Content-Disposition header.
bytes = 'Content-Disposition: form-data; name="Filedata"; filename="';
Try base64 encoding and sending as a string to get around it.
[EDIT]
My guess is here is the offending code.
bytes = 'Content-Disposition: form-data; name="Filedata"; filename="';
for ( i = 0; i < bytes.length; i++ )
{
postData.writeByte( bytes.charCodeAt(i) );
}
When you have Content-Disposition with form-data and filename, it will trigger a security error.
Form data with a file in it can only be sent with user interaction(IE:mouse click) in the stack.
With that being said you need to remove Content-Disposition: form-data; name="Filedata"; filename="' and replace it with a string.
Personally I would scratch this method. The developer plainly did not test this code in a production environment.
// disclaimer none of this code is tested as I pretty much just wrote it.
// however it should at least compile and you should be able to get a little idea of whats going on
// first create the endoder
var encoder:Base64Encoder = new Base64Encoder( )
// now encode the bytearray
encoder.encodeBytes( byteArrayToEncode )
// get the encoded data as a string
var myByteArrayString:String = encoder.toString()
// lets verify the data should see a sting with base64 characters
trace( "show me the string->" + myByteArrayString )
// create the variables object that we want to POST to the server
var urlVars:URLVariables = new URLVariables();
// assign the base64 encoded bytearray to the POST variable of your choice here is use "data"
urlVars.data = myByteArrayString;
// create the request object with the url you are sending the data to
var request:URLRequest = new URLRequest( 'Url of the PHP page below' );
// assign the POST data to the request
request.data = urlVars
// just making sure POST method is being used
request.method = URLRequestMethod.POST;
// here we make a loader even though it is a loader it can be used to send POST data along with the request to a page to load
var urlLoader:URLLoader = new URLLoader();
// just making sure the server knows we are sending data as a string
urlLoader.dataFormat = URLLoaderDataFormat.TEXT;
// create your call back functions of your choice
// urlLoader.addEventListener(Event.COMPLETE, recievedData );
// urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler );
// urlLoader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler );
// wrap the load in a try catch because we are special
try {
// load the request object we just created
urlLoader.load( request );
} catch (e:Error) {
trace(e);
}
The trace should output something like this dG8gQ29udmVydA== except much longer the equals signs at the end of the string are fillers so this should give you a hint on if the data is converted properly. Notice how the string is all alpha numeric characters.
Since, your new to the byteArray thing I would suggest doing a little googling on it in your free time tonight and try to understand what it is and how it works.
<?php
$decodedData= null;
if (!empty($_POST['data'])){
// here is your data in pre-encoded format do what you want with it
$decodedData= base64_decode( $_POST['data'] );
file_put_contents("test.txt",$decodedData);
}
?>