--- title: File APIs(Blob, BlobURL, ArrayBuffer, FileReader) tags: blob BlobURL ArrayBuffer FileReader HTML5 author: TypoScript slide: false --- # まえがき 以前の JavaScript はバイナリデータの扱いがとても下手でした。 バイナリデータをブラウザ上に一旦保存し再利用するには、サイズが1.3倍になることを覚悟した上でデータをDataURIに変換する必要がありましたし、XHRを使ってサーバからデータを取得する場合も、一度全てメモリに読み込んでから、ループでマスク処理を施し、Base64に変換し、DataURIに変換し… と、何重にも変換を繰り返す必要がありました。 2009年に出版された JavaScript Good Parts では、ビット演算子が「使うべきではない悪いパーツ」と評価されていた事を思い出す方もいるでしょう。 あれから5年、もはや時代が違います。 2015年の JavaScript においては、バイナリデータはもはや扱いづらい困った存在ではありません。 ハードウェアと JIT コンパイラの進化に合わせ、大容量のバイナリデータを扱う事を前提としたHTML5 APIが多数実装されています。 バイナリデータはもはや敵ではないのです。 # File API and Objects このエントリでは、WebWorkers はもとより Storage(IndexedDB, FileSystem), WebGL, WebRTC, WebAudio などで必要とされ、今後の JavaScript プログラミングシーンで大活躍するであろう File API と、そこで定義されている Blob, File, FileList オブジェクトについて説明します。 ## Blob [Blob][] は immutable なバイナリデータやファイルデータを上手く扱うための機能を提供します。 ```js class Blob { readonly size: UINT64 = 0, readonly type: String = "", constructor(source:AnyArray, option:BlobTypeObject = null):Blob { }, slice(start:INT64 = 0, end:INT64 = 0, contentType:String = ""):Blob { }, close():void {} } class BlobTypeObject { readonly type: String = "" } ``` Blob はプロパティ{ size:UINT64, type:String } とメソッド { slice, close } を持ちます。 Blob は ArrayBuffer や BlobURL に変換したり、元に戻したりできます。 ```js var source = new Uint8Array([ 0,1,2 ]); var blob = new Blob([ source ]); var reader = new FileReader(); reader.onload = function() { var result = new Uint8Array(reader.result); // reader.result is ArrayBuffer }; reader.readAsArrayBuffer(blob); ``` Blob コンストラクタの第一引数(source)には配列を指定します。 この配列に指定可能な型は、Blob, ArrayBuffer, TypedArray, String またはオブジェクトです。 オブジェクトを指定した場合は、toString() メソッドで文字列に変換します。 複数の要素がある場合は、先頭から順番に連結されたものが Blob オブジェクトの内容になります。 Blob コンストラクタの第二引数には、{ type: MimeTypeString } を指定します。 ```js // Create a empty Blob object. var blob = new Blob(); ``` ```js // Create a octet-binary Blob object. var buffer1 = new Uint8Array(100); var buffer2 = new Uint8Array(100); var blob = new Blob([buffer1, buffer2], { type: "application/octet-binary" }); ``` MimeTypeString にはBlobの種類を示す MimeType を指定します。 汎用的なバイナリデータとして扱いたい場合は、{ type: "application/octet-binary" } を指定します。 ```js var blob = new Blob([file], { type: "image/png" }); var blob = new Blob([binary], { type: "application/octet-binary" }); var blob = new Blob(["文字列"], { type: "text/plain" }); var blob = new Blob([""], { type: "text/plain;charset=UTF-8" }); ``` ## File [File][] は Blob を継承しています。Blob に対してプロパティ { name:String, lastModifiedDate:Date } が追加されています。 File は、JavaScript からローカルファイルを扱う数少ない方法の1つです。 ```js class File extends Blob { readonly name: String = "", readonly lastModifiedDate: Date = null, readonly webkitRelativePath: String = "", constructor(source:Blob, fileName:String):File { }, } ``` Chrome 39 では、File のコンストラクタには配列を指定する必要があります。 `new File([], ...)` プロパティ { lastModified: Integer, webkitRelativePath: String } が拡張されています。 ```js // Chrome の実装 class File extends Blob { readonly name: String = "", readonly lastModified: Integer = 0, readonly lastModifiedDate: Date = null, readonly webkitRelativePath: String = "", constructor(source:AnyArray, fileName:String):File { }, } ``` File は Blob を継承しているので、Blob を要求する FileReader#readAsArrayBuffer() API などにそのまま渡す事ができます。 ```js var reader = new FileReader(); reader.readAsArrayBuffer(file); ``` ```js function _toBlob(url, callback) { var xhr = new XMLHttpRequest(); xhr.responseType = "blob"; xhr.onload = function() { callback(xhr.response, url); }; xhr.open("GET", url); xhr.send(); } _toBlob(location.href, function(blob) { var blobURL = URL.createObjectURL(blob); var file = new File([blob], "foo.html"); // または new File(blob, ...) }); ``` ## FileList FileList は `` の onchange イベントから取得できる File のリストです。 ```js document.querySelector('input[type="file"]').addEventListener("change", function(event) { var fileList = event.target.files; // ArrayLikeObject for (var i = 0, iz = fileList.length; i < iz; ++i) { var file = fileList[0]; ... } }); ``` ```js class FileList { readonly length: UINT32 = 0, item(index:UINT32):File { }, } ``` ## FileReader [FileReader][] クラスは、バイナリデータをメモリに読み込む機能を提供します。 readAsArrayBuffer を使うと Blob を ArrayBuffer に変換します。変換した結果は result プロパティに設定されています。 同様に readAsDataURL や readAsText もありますが、こちらを利用する機会はほとんどないでしょう。 ```js class FileReader extends EventHandler { EMPTY: UINT16 = 0, LOADING: UINT16 = 1, DONE: UINT16 = 2, readonly error: Error = null, readonly result: ArrayBuffer|String = null, readonly readyState: UINT16 = EMPTY, onload: EventHandler = null, onloadstart: EventHandler = null, onloadend: EventHandler = null, onprogress: EventHandler = null, onabort: EventHandler = null, onerror: EventHandler = null, // async read methods readAsArrayBuffer(blob:Blob): void {}, readAsDataURL(blob:Blob):void {}, readAsText(blob:Blob, label:String = ""):void {}, abort():void {}, } ``` Chrome 39 にはこのほかに `readAsBinaryString(blob:Blob):void {}` が存在します。 ## BlobURL の作成と解放 [BlobURL][] は "blob:..." から始まる仮想的なURLです。 URL.createObjectURL(source:Blob):BlobURLString で動的に生成する事ができます。 ```js var uint8Array = new Uint8Array([1, 2, 3]); var blob = new Blob([uint8Array], { type: "application/octet-binary" }); var blobURL = URL.createObjectURL(blob); // "blob:http%3A//dev.w3.org/207c851d-7486-42ec-97b1-d3cd26d08ed9" ``` URL.createObjectURL は常にユニークな BlobURL を生成します(同じリソースに対しても毎回異なる仮想URLを生成します)。 生成したBlobURLはブラウザか終了するまで有効です。 BlobURL を解放するには URL.revokeObjectURL(blobURL:BlobURLString):void を使用します。 リソースの解放を自動的に行う URL.createFor(Blob):BlobURLString もありますが、 まだ実装しているブラウザはありません(2014/11) ```js var blobURL = URL.createObjectURL(file); ... 必要な処理 ... URL.revokeObjectURL(blobURL); ``` img.src や XMLHttpRequest#open(method, URL) や Audio API が URL を要求しますが、そのような場合は Blob から BlobURL を生成して渡す事ができます。 BlobURL は DataURI を使った場合に比べて速度・メモリともに効率的です。Audio などのストリーミングデータを扱う場合はその差が顕著に現れます。 ```js var img = document.createElement("img"); img.src = blobURL; ``` 有効な BlobURL は以下の方法で確認する事ができます。 - Chrome chrome://blob-internals/ ## Blob, File, BlobURL の相互変換 BlobURL は XMLHttpRequest を使う事で、Blob に戻したり ArrayBuffer や他の型に変換することができます。 ```js var xhr = new XMLHttpRequest(); xhr.onload = function() { var result = xhr.response; // ArrayBuffer }; xhr.responseType = "arraybuffer"; //xhr.responseType = "blob"; xhr.open("GET", blobURL); xhr.send(); ``` Blob, BlobURLString, ArrayBuffer の相互変換は以下のメソッドで行います。 | from | to | method | |--------------|--------------------|--------------------------------| | Blob/File | BlobURLString | URL.createObjectURL(...) | | BlobURL | Blob | XMLHttpRequest#responseType = "blob" | | BlobURL | ArrayBuffer | XMLHttpRequest#responseType = "arraybuffer" | | Blob/File | ArrayBuffer | FileReader#readAsArrayBuffer(...) | | Blob/File | BinaryString | FileReader#readAsBinaryString(...) | | Blob/File | DataURLString | FileReader#readAsDataURL(...) | | Blob/File | String | FileReader#readAsText(..., 文字コード) | ArrayBuffer への変換メソッドは以下のように実装すると良いでしょう。 ```js function _toArrayBuffer(source, // @arg BlobURLString|URLString|Blob|File|TypedArray|ArrayBuffer callback) { // @arg Function - callback(result:ArrayBuffer, source:Any):void if (source.buffer) { // TypedArray callback(source.buffer, source); } else if (source instanceof ArrayBuffer) { // ArrayBuffer callback(source, source); } else if (source instanceof Blob) { // Blob or File var reader = new FileReader(); reader.onload = function() { callback(reader.result, source); }; reader.readAsArrayBuffer(source); } else if (typeof source === "string") { // BlobURLString or URLString var xhr = new XMLHttpRequest(); xhr.responseType = "arraybuffer"; xhr.onload = function() { callback(xhr.response, source); }; xhr.open("GET", source); xhr.send(); } else { throw new TypeError("Unknown type"); } } ``` FileReader#readAsText(blob, "UTF-8") は、第二引数に文字コードを指定できます。デフォルトは "UTF-8" です。 ```js var reader = new FileReader(); reader.onload = function() { var buffer = reader.result; // String }; reader.readAsText(file, "UTF-16"); ``` [Blob]: http://dev.w3.org/2006/webapi/FileAPI/#blob [File]: http://dev.w3.org/2006/webapi/FileAPI/#file [FileList]: http://dev.w3.org/2006/webapi/FileAPI/#FileReader-interface [FileReader]: http://dev.w3.org/2006/webapi/FileAPI/#APIASynch [BlobURL]: http://dev.w3.org/2006/webapi/FileAPI/#DefinitionOfScheme