Don Rhummy
Don Rhummy

Reputation: 25820

Attempt to store blob in iOS Safari 8 throws DataCloneError

When I try to store a blob (retrieved via an XMLHttpRequest GET request, Safari on iOS 8.4 throws the error:

DataCloneError: DOM IDBDatabase Exception 25: The data being stored could
not be cloned by the internal structured cloning algorithm

This happen with my code and also this example: http://robnyman.github.io/html5demos/indexeddb/

This is the line that cause my code (and the above example) to fail:

//This throws the error
var put = transaction.objectStore("elephants").put(blob, "image");

Is there a fix for this? Does the blob need to be base64 encoded first (like you had to do with WebSQL)?

My CODE (works in Desktop Chrome/Firefox and Chrome/Firefox on Android):

var xhr = new XMLHttpRequest();
var blob;

//Get the Video
xhr.open( "GET", "test.mp4", true );

//Set as blob
xhr.responseType = "blob";

//Listen for blob
xhr.addEventListener("load", function () {
        if (xhr.status === 200) {
            blob = xhr.response;

            //Start transaction
            var transaction = db.transaction(["Videos"], "readwrite");

            //IT FAILS HERE
            var put = transaction.objectStore("Videos").put(blob, "savedvideo");

        }
        else {
            console.log("ERROR: Unable to download video." );
        }
}, false);

xhr.send();

Upvotes: 3

Views: 1584

Answers (1)

Don Rhummy
Don Rhummy

Reputation: 25820

For some odd reason (it's a bug), just like iOS Safari 7's WebSQL, you cannot store a BLOB in IndexedDB on iOS Safari 8. You have to convert it to base64 and then it will store without an error. (I repeat, this is a bug)

So, change the code to this:

Change response type

xhr.responseType = "arraybuffer";

Storing in database after retrieving from XMLHttpRequest

//We'll make an array of unsigned ints to convert
var uInt8Array = new Uint8Array(xhr.response);
var i = uInt8Array.length;
var binaryString = new Array(i);
while (i--)
{
    //Convert each to character
    binaryString[i] = String.fromCharCode(uInt8Array[i]);
}

//Make into a string
var data = binaryString.join('');

//Use built in btoa to make it a base64 encoded string
var base64 = window.btoa(data);

//Now we can save it
var transaction = db.transaction(["Videos"], "readwrite");
var put = transaction.objectStore("Videos").put(base64, "savedvideo");

After retrieving form the IndexedDB, convert it back:

//Convert back to bytes
var data = atob( event.target.result );

//Make back into unsigned array
var asArray = new Uint8Array(data.length);

for( var i = 0, len = data.length; i < len; ++i ) 
{
    //Get back as int
    asArray[i] = data.charCodeAt(i);    
}

//Make into a blob of proper type
var blob = new Blob( [ asArray.buffer ], {type: "video/mp4"} );

//Make into object url for video source
var videoURL = URL.createObjectURL(blob);

Upvotes: 3

Related Questions