作者:程序员CKeen
博客:http://ckeen.cn
长期坚持做有价值的事!积累沉淀,持续成长,升维思考!希望把编码作为长期兴趣爱好
最近遇到一个很特别的需求,需要将嵌入式设备的里面的视频流和图片通过websocket直接传到浏览器端进行显示和存储(因为设备的存储空间较有限,不能将视频和图片存在设备上),并且本地浏览器还可以查看视频和图片列表以及提供视频和图片的本地下载。不过还好的是,不用考虑所有浏览器。具体需求参考如下:
通过分析, 貌似要做的就是将websocket传过来的视频裸流和图片数据,通过web端浏览器接口直接存储在本地,以及从本地读取这些文件。 于是就开始去找web浏览器关于本地文件操作的方法, 在MDN找到了web端对File操作的相关的API。
参考链接:https://developer.mozilla.org/en-US/docs/Web/API
MDN介绍的File相关的API主要有以下三个:
File API 。文件API使web应用程序能够访问文件及其内容。
当用户使文件可用时,Web应用程序可以访问文件,可以使用file 元素,也可以通过拖放操作。
以这种方式提供的文件集被表示为FileList对象,它使web应用程序能够检索单个File对象。反过来,File对象提供对元数据的访问,例如文件的名称、大小、类型和最后修改日期。
文件对象可以传递给FileReader对象来访问文件的内容。FileReader接口是异步的,但只有在web worker中可用的同步版本由FileReaderSync接口提供。
File and Directory Entries API。
文件和目录条目API模拟了一个本地文件系统,web应用程序可以在其中导航和访问文件。你可以开发在虚拟的沙盒文件系统中读取、写入和创建文件和/或目录的应用。
File System Access API。
文件系统访问API允许读、写和文件管理功能。该API允许与用户本地设备上的文件或用户可访问的网络文件系统上的文件进行交互。该API的核心功能包括读取文件、写入或保存文件以及访问目录结构。
大多数与文件和目录的交互都是通过句柄完成的。父类FileSystemHandle帮助定义两个子类:FileSystemFileHandle和FileSystemDirectoryHandle,分别用于文件和目录。
而今天我们主要用到的是文件和目录的API:File and Directory Entries API。该API提供了对本地目录的创建,以及对文件的创建和读写操作。
上面已经简单了介绍了File and Directory Entries API的用途,下面我们主要介绍该分类下的API接口。
该API详细的介绍可以参考提案:https://wicg.github.io/entries-api/#api-domfilesystem
文件和目录的API主要有以下5个
FileSystem
:表示文件系统。当我们需要对文件进行操作时,需要先获取FileSystem的对象,然后通过FileSystem对象来操作具体的目录和文件对象。FileSystemEntry
:表示文件系统中单个条目的基本接口。这是由表示文件或目录的其他接口实现的。FileSystemDirectoryEntry
:表示文件系统中的单个目录。FileSystemFileEntry
:表示文件系统中的单个文件。FileSystemDirectoryReader
:该接口通过调用 FileSystemDirectoryEntry.createReader()
(en-US) 创建,提供了允许读取目录内容的功能。下面是不同浏览器对File and Directory Entries API支持的情况:
这里没有对浏览器提出要求,我们选择使用chrome浏览器来进行测试
FileSystem
文件和目录条目API接口文件系统用于表示文件系统。这些对象可以从任何文件系统条目的文件系统属性中获得。一些浏览器提供了额外的api来创建和管理文件系统,比如Chrome的requestFileSystem()方法。后面我将通过Chrome的全局方法获取该对象
有两种方法可以访问FileSystem对象:
你可以通过调用window.requestFileSystem()直接请求一个代表为你的web应用创建的沙盒文件系统。如果调用成功,则执行回调处理程序,该处理程序接收描述文件系统的FileSystem对象作为参数。
您可以通过文件系统条目对象的文件系统属性获得它。
实例属性
filessystem .name
表示文件系统名称的字符串。这个名称在整个公开的文件系统列表中是唯一的。
filessystem.root
FileSystemDirectoryEntry对象,表示文件系统的根目录。通过该对象,可以访问文件系统中的所有文件和目录。
FileSystemEntry
文件和目录条目API的FileSystemEntry接口表示文件系统中的单个条目。条目可以是文件或目录(目录由FileSystemDirectoryEntry接口表示)。它包括处理文件的方法——包括复制、移动、删除和读取文件——以及它指向的文件的信息——包括文件名和从根目录到条目的路径。
您不直接创建FileSystemEntry对象。相反,您将通过其他api接收基于此接口的对象。该接口作为FileSystemFileEntry和FileSystemDirectoryEntry接口的基类,这两个接口分别提供特定于表示文件和目录的文件系统项的特性。
FileSystemEntry接口包括操作文件和目录所需的方法,但它还包括获取条目URL的方便方法:toURL()。它还引入了一个新的URL方案:filesystem:。
你可以在Google Chrome上使用filesystem: scheme来查看存储在应用程序原点的所有文件和文件夹。只需使用filesystem: scheme作为应用程序原点的根目录。例如,如果你的应用程序位于http://www.example.com,在一个选项卡中打开filesystem:http://www.example.com/temporary/。Chrome显示了存储在你的应用程序的所有文件和文件夹的只读列表。
属性(只读):
fullPath: 相对于虚拟文件系统的根目录的完整路径
isDirectory: 是否为目录
isFile: 是否为文件
name: entry的名称
实例方法:
copyTo(): 将当前目录或者文件拷贝到心的位置
getMetadata(): 获取file的元信息,比如修改时间及大小
getParent(): 获取父目录
moveTo(): 将当前文件或者目录移动到新位置,也可以用来重命名文件和目录
remove(): 移除文件和目录, 如果是移除目录需要保证目录为空
FileSystemDirectoryEntry
文件和目录条目API的FileSystemDirectoryEntry接口表示文件系统中的目录。它提供了一些方法,使访问和操作目录中的文件以及访问目录中的条目成为可能。
您可以通过调用getDirectory()来创建一个新目录。如果要创建子目录,请依次创建每个子目录。如果尝试使用包含尚不存在的父目录的完整路径创建目录,则返回错误。因此,通过在创建父目录后递归地添加新路径来创建层次结构。
createReader()
FileSystemFileEntry
文件和目录项API的FileSystemFileEntry接口表示文件系统中的一个文件。它提供了描述文件属性的属性,以及file()方法,该方法创建可用于读取文件的file对象。
实例属性
继承其父接口FileSystemEntry的属性,但没有此接口独有的属性。
file(successCallback)
file(successCallback, errorCallback)
createWriter(successCallback)
createWriter(successCallback, errorCallback)
FileSystemDirectoryReader
文件和目录条目API的FileSystemDirectoryReader接口允许您访问代表目录中每个条目的基于FileSystemFileEntry的对象(通常是FileSystemFileEntry或FileSystemDirectoryEntry)。
最重要的方法:
readEntries()
返回一个包含一定数量目录项的数组。数组中的每一项都是一个基于FileSystemEntry的对象——通常是FileSystemFileEntry或FileSystemDirectoryEntry。
下面我们主要通过一个demo,来实现使用FileSystem的API将图片存储到本地,从本地文件系统进行获取以及下载
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<script>
// 这里为文件base64数据,实际过程中可以自己生成一个图片base64的数据,或者是从过websocket传输一个base64文件
var base64ImageData = "xxxxx";
/**
* 将base64转为Blob对象
* @param base64
* @param contentType
*/
function base64ToBlob(base64, contentType){
var arr = base64.split(',') //去掉base64格式图片的头部
var bstr = atob(arr[1]) //atob()方法将数据解码
var leng = bstr.length
var u8arr = new Uint8Array(leng)
while(leng--){
u8arr[leng] = bstr.charCodeAt(leng) //返回指定位置的字符的 Unicode 编码
}
var blob = new Blob([u8arr],{type:contentType})
var blobImg = {}
blobImg.url = URL.createObjectURL(blob) //创建URL,
blobImg.name = new Date().getTime() + '.jpeg'
return blob
}
/**
* 读取FileEntry对象,并进行下载操作
* @param fs
* @param entries
*/
function readFiles(fs,entries){
if (entries.length > 0) {
// entry & add record file(s) to play list
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
if (entry.isFile) {
console.log(entry)
download_file(fs, entry.name)
}
}
}
}
/**
* 使用a标签,对文件进行自动下载
* @param fs
* @param fileName
*/
function download_file(fs,fileName) {
if (null == fs)
return;
// 创建a标签,用于触发下载
const a = document.createElement('a');
// 将 a 标签的 download 属性设置为要下载的文件名
a.download = fileName || 'image';
fs.root.getFile('images/' + fileName, {}, function(fileEntry) {
fileEntry.file(function (file) {
a.href = window.URL.createObjectURL(file);
// 将a标签暂时添加到 body 中,触发下载
document.body.appendChild(a);
a.click();
// 下载完成后,将 a 标签从 body 中移除
document.body.removeChild(a);
});
});
}
/**
* 加载本地目录下的所有文件
*/
function loadFiles() {
window.webkitRequestFileSystem(window.PERSISTENT, 16 * 1024*1024*1024, function (f) {
console.log(f);
f.root.getDirectory("images",{create:true},function(dirEntry){
var dirReader = dirEntry.createReader();
dirReader.readEntries(function(entries) {
console.log(f, entries)
readFiles(f, entries)
})
})
})
}
/**
* 保存文件到本地指定目录下
*/
function saveFile(){
window.webkitRequestFileSystem(window.PERSISTENT, 16 * 1024*1024*1024, function (f) {
console.log(f);
f.root.getDirectory("images",{create:true},function(g){
console.log(g)
})
for (i = 0;i<10;i++){
f.root.getFile("images/" + i + ".jpg" ,{create:true},function(h){
console.log(h)
h.createWriter(function(j){
console.log(j)
var blob = base64ToBlob(base64ImageData, 'image/jpeg')
j.write(blob);
});
})
}
})
}
function saveLocalFile(){
console.log("保存文件到本地")
saveFile()
}
function downloadLocalFile(){
console.log("下载文件")
loadFiles()
}
script>
<div>
<input type="button" value="添加文件到本地" onclick="saveLocalFile()">
<input type="button" value="下载文件列表" onclick="downloadLocalFile()">
div>
body>
html>
效果展示: