1

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>
gord0
  • 13
  • 1
  • 5
  • Post your embed code. This should work. – The_asMan Feb 02 '12 at 23:48
  • i edited the original post to show the embed code – gord0 Feb 03 '12 at 00:03
  • try adding params.allownetworking = "all";and just to be safe cause I don't remember attributes.allownetworking = "all"; – The_asMan Feb 03 '12 at 00:15
  • I recall already trying that, but just tried it again incase I did it wrong the first time and the sandbox alert still goes off. I'm going to explore Mrugesh's ideas for the time being, or until better ideas are presented. Thx anyway : ] – gord0 Feb 03 '12 at 13:25
  • SecurityError: Error #2176: Certain actions, such as those that display a pop-up window, may only be invoked upon user interaction, for example by a mouse click or button press. at flash.net::URLStream/load() at flash.net::URLLoader/load() at ****.src.dataClasses::MicManager/save()[C:\Users\Owner\Desktop\****\****_sources\****\storyCentral\src\dataClasses\MicManager.as:451] The stack trace goes back further but as you can see it's the loader that's making it freak out. (the **** are where it showed the name of the game) – gord0 Feb 03 '12 at 16:25

2 Answers2

1

This has been asked before

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);

}

?>
Community
  • 1
  • 1
The_asMan
  • 6,364
  • 4
  • 23
  • 34
  • Ok, I have to tell you I'm relatively new to this byte array and encoding stuff. I only dove into it when I was asked to perfom these audio tasks. I'm also relatively new to php, 95% of the script above is back engineered from an example I found...which I believe originally was used to upload images. Anyway, would you say that I should use the .toString() on my byte array before sending, then use something like this: http://garry-lachman.com/2010/04/21/base64-encoding-class-in-actionscript-3/ on the string, then put that in the _request.data property. – gord0 Feb 03 '12 at 17:39
  • Then somehow undo the encoding php side, and somehow turn back into a byte array... (i'm also unsure of how that would be done in php without more googling) I'd like to know exactly what to do, before googling how....unless you guys know how and tell me :P . – gord0 Feb 03 '12 at 17:39
  • You did not state if you are using flash or flex so I went with the default mx(flex) libraries. If you are using flash you will need a bas64 library like the one you posted a linkI generally us Hurlant's http://code.google.com/p/as3crypto/source/browse/trunk/as3crypto/src/com/hurlant/util/Base64.as – The_asMan Feb 03 '12 at 19:31
  • thanks so much dude, i just read this post now and about to head home for the weekend. But this is definately back to number 1 task monday morning (i've been doing other unrelated tasks all day). – gord0 Feb 03 '12 at 21:00
  • ok I'm diving into this again, will let you know how it goes. Oh also I neglected to answer one of your questions, I'm using flex (Flashdevelop non-ide project) – gord0 Feb 06 '12 at 14:42
  • i'm about to hardcode the filename in php, but I was wondering how i might package the filename with the byte array? – gord0 Feb 06 '12 at 15:11
  • dude it so totally works! just need to figure out how to tell php what the filename should be. – gord0 Feb 06 '12 at 15:33
  • oh also, I had to use this: https://github.com/spjwebster/as3base64 mx.utils.Base64Encoder isn't part of mx.utils anymore... or at least not without digging around to find the swc it's in. – gord0 Feb 06 '12 at 16:17
  • Alright, now I've got the filename being passed in the POST. So now I can finally resolve this task in on jira! XD – gord0 Feb 06 '12 at 16:47
  • on the urlVars just add urlVars.fname and access it in php the same way $_POST['fname'] – The_asMan Feb 06 '12 at 16:49
0

You can send the your recorded data to the Server Side application as a byte array and Byte aaray will be saved as per required format like flv or any other file (supported format only). Please check with AMFPHP, PHP and Byte array saving using PHP & ActionScript 3.0. you will get example of it. Please check this link for Recoprding and converting audio in Byte Array : Click Here

Mrugesh
  • 461
  • 4
  • 16
  • I've alreayd explored the link you posted. I'm not having issues with recording. When the user finishes recording there is a byte array that is a .wav file. I convert that to a .mp3 (still as a byte array). I then save that to the server. As far as I know .mp3 is the only audio type flash deals with at run time. The mp3 saves just fine when playign the game in a flash player, but causes the sandbox error when playing from a browser. Are you suggesting I try to convert the mp3 byte array to a flv byte array with no video component? – gord0 Feb 03 '12 at 13:31