iT邦幫忙

DAY 21
2

且戰且走HTML5系列 第 21

且戰且走HTML5(21) 應用的主軸:FileAPI、URL

既然要用到File API以及URL(或者叫做Blob URL),還是對他做一個簡單的了解。
File API目前主要定義了幾個介面,主要是:Blob、File、FileList、URL。

Blob是一個代表二進位資料的結構。除了透過像是File Input或是Drag and Drop事件取得,也可以透過他的建構式來建立(可以利用ArrayBuffer、字串、其他Blob來建立)。取得Blob物件後,透過size屬性可以知道他的大小,透過type屬性可以知道他的mime type。另外,他提供了一個slice方法,可以用來切割出另一個Blob物件。

File繼承了Blob,多增加了兩個屬性,透過name可以取得他的檔名、透過lastModifiedDate可以取得最後修改日期。

FileList是一個File物件的list,通常透過File Input或Drag and Drop事件,可以取得這個物件,然後在透過他取得檔案。使用這個方式,就可以一次操作多個檔案。透過length屬性,可以知道FileList中有多少File物件,透過item(index)方法則可取得這些File物件。

FileReader的用途就是讀取File/Blob物件。Blob與File物件中,只有內容的資訊(size、type、name...),並不能取得他的內容。要讀取內容,就得透過FileReader。FileReader目前提供了三種讀取Blob的非同步方法:readAsArrayBuffer(Blob)、readAsText(Blob, encoding)、readAdDataURL(Blob),當onload事件被觸發時,代表讀取完畢,這時可以在事件處理函數中讀取FileReader的result屬性,裡面就是指定讀取的資料。

URL是比較新加入File API中的東西,他主要是用一個新的URL Scheme來到表Blob資料,然後透過這個URL讓一些元素可以直接存取,不用再做複雜的讀取動作。在沒有這個介面前,如果想要實作選取圖檔時,不必上傳就產生預覽圖片,那在透過File Input元素取得選取的File後,通常會使用到FileReader的readAsDataURL把它轉成DataURL(以圖檔來說,會轉成代表內容的Base64編碼字串,這個作法效率不太好...),然後把這個DataURL指派給預覽用的img元素的src屬性。

有了URL以及新的Blob URL Scheme後,就不用這麼麻煩。直接使用URL.createObjectURL(Blob),就會產生一個URL,代表Blob物件,直接把這個URL指配給img元素的src屬性就可以了。這樣效率比較好,而且操作也比較簡單。

除了處理Blob,其實如果瀏覽器開始支援WebRTC規格,那就可以用URL.createObjectURL(MediaStream),來把MediaStream直接用Video/Audio元素播放。

昨天的簡單概念驗證,其實就是利用File API,來在使用File Input選取檔案後產生可以直接存取的連結。簡單地說,從File Input取得File物件後,就在下方動態產生一個連結,連結的href就是由URL.createObjectURL產生的。點選連結,就可以在新視窗開啟。基本上在Firefox16都可以正確執行,不過在Chrome22,他無法處理的檔案內容,就會在瀏覽器視窗印出來...這樣會有點怪,還是Firefox產生下載檔案動作的方式可能好一點。

接下來,先搭配node.js的ws這個WebSocket伺服器套件,來做簡單的資源分享的概念驗證。之前做的程式都是搭配Socket.IO,不過他沒辦法傳輸Blob、ArrayBuffer等代表二進位的資料,所以先用ws來做驗證。之後可能需要擴展Socket.IO或是包裝ws,來讓操作行為比較像,然後可以把之前的程式跟目前做資源分享的需求可以結合在一起。

先來看伺服器:

 var Server = require('ws').Server;
 var app = new Server({port:8443});
 var sockets = {};
 app.on('connection', function(ws) {
 	var id = Math.random()*10000 + '' + new Date().getTime();
 	sockets[id] = ws;
 	var filename = '';
 	ws.on('message', function(data, flags) {
 		for(var i in sockets) {
 			if(i !== id) {
 				sockets[i].send(data, {binary:flags.binary, mask:false});
 			}
 		}
 	});
 });

為了可以管理所有的連線,這裡在每個連線建立(connection事件)時,同時產生一個unique的id,透過他把ws存到sockets物件中,之後需要群播時,就可以透過這個物件來做。這裡群播的方法也很簡單,就是只要跟自己id不一樣就把訊息轉送出去。ws送給message事件處理函數的flags物件參數,會有一個成員叫做binary,透過他就可以得知傳來的訊息是否是binary的。

接下來看網頁端的程式:

 <meta charset='utf-8'>
 
 <style>
 #panel {
 	border: solid 1px #336699;
 	width: 240;
 }
 </style>
 
 
 <input type='file' id='files'><br>
 <div id='panel'></div>
 <div id='show'></div>
 
 
 <script>
 var files = document.getElementById('files');
 var ws = new WebSocket('ws://localhost:8443');
 function getUrl() {
 	if(!!window.URL) {
 		return window.URL;
 	}
 	if(!!window.webkitURL) {
 		return window.webkitURL;
 	}
 }
 
 files.addEventListener('change', function(e) {
 	var URL = getUrl();
 	if(files.files.length>0) {
 		var file = files.files[0];
 		var src = URL.createObjectURL(file);
 		var a = document.createElement('a');
 		a.href = src;
 		a.innerHTML = file.name;
 		a.target = '_blank';
 		document.getElementById('panel').appendChild(a);
 		document.getElementById('panel').appendChild(document.createElement('br'));
 		ws.send(JSON.stringify({name: file.name,type: file.type}));
 		ws.send(file);
 	}
 });
 var fileinfo;
 ws.onmessage = function(e) {
 	if(typeof e.data === 'object') {
 		var URL = getUrl();
 		var a = document.createElement('a');
 		var file = new Blob([e.data], {type:fileinfo.type});
 		a.href = URL.createObjectURL(file);
 		a.innerHTML = fileinfo.name;
 		a.target = '_blank';
 		document.getElementById('panel').appendChild(a);
 		document.getElementById('panel').appendChild(document.createElement('br'));
 	} else {
 		fileinfo = JSON.parse(e.data);
 	}
 }
 </script>

WebSocket的Data Frame雖然可以支援UTF-8字串以及Blob/ArrayBuffer,但是沒辦法同時傳,為了可以把檔名及mime type也傳出去,只好先傳一次檔名及mime type資訊,然後才把Blob傳出去。透過WebSocket接收到的Blob物件有一個問題,就是他的type屬性是控字串,這會導致Chrome在顯示這個Blob時出問題。所以在這裡不直接用接收到的Blob物件,而是利用他產生一個新的Blob物件,把它的mime type屬性透過先收到的fileinfo設定好,然後才呼叫URL.createObjectURL()來產生Blob物件的URL。

再來看一下畫面,跟前一個範例看起來差不多,不過現在是透過WebSocket在Chrome與Firefox間傳送Blob。同時開啟Chrome與Firefox後,先用Chrome選一個檔案:

然後用firefox也選一個檔案:

在Chrome點選Firefox送過來Blob產生的連結,在Firefox則點選Chrome送過來的Blob產生的連結,分別跑出不同Blob所包含的圖片:

大致上就這樣。明天先來包裝ws跟改寫之前的伺服器程式,另外也加上Client使用的簡單Library,讓ws的操作感覺像Socket.IO,但是又可以傳送二進位的資料。


上一篇
且戰且走HTML5(20) 資源共享
下一篇
且戰且走HTML5(22) 調整ws模組
系列文
且戰且走HTML530

1 則留言

我要留言

立即登入留言