陈建华的博客
专注web开发
【chrome扩展】媒体库(第八章)
2014-11-30 10:45:05   阅读2598次

通过mediaGalleries接口Chrome应用可以操作计算机中的媒体库,如音乐文件夹、图片文件夹、iPod设备和iTunes等。

Chrome应用操作媒体库与操作文件系统类似——其实媒体库也是文件系统的一部分,但是mediaGalleries接口与fileSystem有些区别。

首先mediaGalleries能自动找到计算机中的媒体库而不必像fileSystem那样需要用户手动指定目录或文件位置,其次mediaGalleries只会获取到支持的媒体文件,其他文件会被自动过滤掉。

要使用mediaGalleries接口需要在Manifest中声明mediaGalleries权限:

"permissions": {
    {"mediaGalleries": ["read", "allAutoDetected"]} 
}

mediaGalleries权限的声明与fileSystem类似,需要指定更加详细的权限。"read"表示有读取文件内容的权限,"allAutoDetected"表示有自动获取媒体库位置的权限。其他的权限还包括"delete"和"copyTo",分别代表删除文件和复制文件。

需要注意的是mediaGalleries接口不提供"write"权限——直接在媒体库中创建或更改文件是禁止的,但可以在临时文件夹中创建文件后复制到媒体库中。


8.1获取媒体库

如果在Manifest中声明了了"allAutoDetected"权限,则Chrome应用可以无需用户手动指定,自动获取到媒体库的位置。

通过getMediaFileSystems方法可以获取到媒体库对应的fileSystem:

chrome.mediaGalleries.getMediaFileSystems({
    interactive: 'if_needed'
}, function(fileSystemArray){
    //We'll do something with fileSystemArray later
});

得到的是一个包含多个fileSystem对象的数组fileSystemArray。fileSystem对象我们在第7章虽有提及,但却接触不多,更多地是对Entry对象的操作。不过我们可以回忆一下7.1节在介绍fileSystem时提到过其有两个属性,分别是name和root,其中root是此文件系统的根目录DirectoryEntry。所以filesystem.root就是我们熟悉的Entry对象。

虽然通过filesystem.root可以像操作文件系统一样操作媒体库,但是除了文件系统中提供的属性外(如isDirectory和isFile等),对于媒体库我们还希望获得其他的信息——是否是媒体设备(如音乐播放器)、是否是可以移动设备(让我们来决定是否应进行同步操作)、目前设备是否可用等等。

为了得到这些信息,通过文件系统的接口是不够的,为此Chrome提供了获取此类信息的方法,getMediaFileSystemMetadata:

mediaInfo = chrome.mediaGalleries.getMediaFileSystemMetadata(mediaFileSystem);

它的传入参数是fileSystem对象,而不是Entry对象。

也可以通过getAllMediaFileSystemMetadata方法获取到全部的媒体库信息,但是getAllMediaFileSystemMetadata方法与getMediaFileSystemMetadata方法不同的是它使用回调函数的方式传回结果:

chrome.mediaGalleries.getAllMediaFileSystemMetadata(function(mediaInfoArray){
    //do something with mediaInfoArray
});

结果是一个数组,描述每个媒体库信息,这些对象的结构与通过getMediaFileSystemMetadata方法获取到的对象结构相同。

mediaInfo对象包含6个属性,分别是name、galleryId、deviceId、isRemovable、isMediaDevice和isAvailable。其中isRemovable表明此媒体库是否是一个可移动设备,isMediaDevice表明此媒体库是否是一个媒体设备,isAvailable表明此媒体库现在是否可用。

下面我们来一起制作一款媒体库管理应用,并在之后的小节中逐步完善它。

首先创建Manifest文件:

{
    "app": {
        "background": {
            "scripts": ["background.js"]
        }
    },
    "manifest_version": 2,
    "name": "Media Manager",
    "version": "1.0",
    "description": "A media manage tool.",
    "icons": {
        "128": "logo.png"
    },
    "permissions": [
        {"mediaGalleries": ["read", "delete", "copyTo", "allAutoDetected"]}
    ]
}

background.js用来监控应用启动事件,当用户启动应用后创建一个窗口:

chrome.app.runtime.onLaunched.addListener(function() {
    chrome.app.window.create('main.html', {
        id: 'main',
        bounds: {
            width: 800,
            height: 600
        }
    });
});

在main.html用于展示检测到的媒体库:

<html>
<head>
<style>
@font-face {
    font-family: 'iconfont';
    src: url('iconfont.woff') format('woff');
}
* {
    padding: 0;
    margin: 0;
}
body {
    background: #2D2D2D;
}
#appTitle {
    height: 60px;
    line-height: 60px;
    padding: 0 20px;
    font-size: 24px;
    color: #CCC;
    background: #222;
}
#path {
    height: 40px;
    line-height: 40px;
    padding: 0 20px;
    color: #888;
    font-size: 16px;
    background: #222;
    border-bottom: black 1px solid;
    box-sizing: border-box;
}
#path span {
    display: block;
    float: left;
}
#path span.name {
    max-width: 100px;
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
}
#path span.pointer {
    padding: 0 10px 0 5px;
}
#container .item {
    display: block;
    height: 100px;
    width: 100px;
    float: left;
    color: white;
}
#container .item .icon {
    display: block;
    text-align: center;
    height: 80px;
    line-height: 80px;
    font-size: 60px;
    font-family: 'iconfont';
}
#container .item .text {
    display: block;
    text-align: center;
    padding: 0 5px;
    height: 20px;
    line-height: 20px;
    font-size: 14px;
    text-overflow: ellipsis;
    overflow: hidden;
}
</style>
</head>
<body>
<div id="appTitle">Media Manager</div>
<div id="path"><span class="name">媒体库</span><span class="pointer">»</span></div>
<div id="container"></div>
<script src="main.js"></script>
</body>
</html>

其中用到了图标字体以显示矢量图标,字体来自http://www.iconfont.cn/。

下面来编写main.js:

function getMedia(){
    chrome.mediaGalleries.getMediaFileSystems({
        interactive: 'if_needed'
    }, listMediaGalleries);
}
function listMediaGalleries(fileSystemArray){
    document.getElementById('container').innerHTML = '';
    for(var i=0; i<fileSystemArray.length; i++){
        var info = chrome.mediaGalleries.getMediaFileSystemMetadata(fileSystemArray[i]);
        var item = document.createElement('span');
        item.className = 'item';
        item.title = info.name;
        document.getElementById('container').appendChild(item);
        var icon = document.createElement('span');
        icon.className = 'icon';
        icon.innerHTML = '&#xf00c5;';
        item.appendChild(icon);
        var text = document.createElement('span');
        text.className = 'text';
        text.innerHTML = info.name;
        item.appendChild(text);
    }
}
getMedia();

载入到Chrome后可以看到目前运行的截图。

01fj9w2QIpeG.png

获取媒体库

8.2添加及移除媒体库

除了通过"allAutoDetected"权限让Chrome应用自动查找媒体库外,也可以让用户手动添加或者移除媒体库。

在上一节中我们调用getMediaFileSystems方法时,将其参数中的interactive指定为了if_needed,如果将其指定为yes则会出现一个弹出让用户选择保留的媒体库或者添加其他媒体库:

01fjAJAkUUj1.png

媒体库选择弹窗

如果只想单纯提供添加其他位置的功能,可以使用addUserSelectedFolder方法,当调用addUserSelectedFolder方法时,会弹出一个目录选择窗口让用户选择新媒体库的位置:

01fjAJEKwydl.png

添加新媒体库位置

addUserSelectedFolder方法使用回调函数传递用户选择结果:

chrome.mediaGalleries.addUserSelectedFolder(function(mediaFileSystems, selectedFileSystemName){
    //We'll do something with mediaFileSystems later
});

其中mediaFileSystems是一个包含多个FileSystem的数组,其包含的是应用有权限访问的所有媒体库FileSystem,而非只是用户刚刚选择的。selectedFileSystemName是一个字符串,如果用户在添加媒体库完成前点击了取消按钮,则selectedFileSystemName返回一个空值。

使用dropPermissionForMediaFileSystem方法可以取消对指定媒体库的访问权1:

chrome.mediaGalleries.dropPermissionForMediaFileSystem(galleryId, function(){
    //do something after give up access a media gallery
});

1 从Chrome 36开始支持。

下面为Media Manager加上添加移除媒体库按钮:

<div id="appTitle">Media Manager<span id="edit">&#x3466;</span></div>

在CSS中添加按钮样式:

#edit {
    display: inline-block;
    font-size: 12px;
    font-family: 'iconfont';
    height: 20px;
    line-height: 20px;
    padding: 5px;
    cursor: pointer;
}

在JS中添加按钮事件:

document.getElementById('edit').onclick = function(){
    document.getElementById('container').innerHTML = '';
    chrome.mediaGalleries.getMediaFileSystems({
        interactive: 'yes'
    }, listMediaGalleries);
}

改进后的窗口如下所示。

01fjAKuz6zER.png

带有添加移除媒体库的窗口

8.3更新媒体库

有时我们需要更新媒体库以让应用自动发现最新的媒体库。

通过startMediaScan方法开始更新媒体库1:

chrome.mediaGalleries.startMediaScan();

1 从Chrome 35开始支持。

startMediaScan没有然后返回值,也不会调用任何回调函数,因为更新的过程所花费的时间可能非常长,所以要使用onScanProgress来监听更新过程:

chrome.mediaGalleries.onScanProgress.addListener(function(details){
    //do something with details
});

其中details是一个对象,包含5个属性,分别是type、galleryCount、audioCount、imageCount和videoCount。type的可能值有start、cancel、finish和error,分别对应于开始更新、取消更新、完成更新和遇到错误。

在更新媒体库的过程中,通过cancelMediaScan方法可以随时取消更新:

chrome.mediaGalleries.cancelMediaScan();

同样cancelMediaScan方法也没有提供回调函数,而应通过onScanProgress监测更新过程中的取消事件。

当onScanProgress监测到更新完成事件之后,可以通过addScanResults方法向用户展示一个选择添加最新检测到媒体库的窗口:

chrome.mediaGalleries.addScanResults(function(mediaFileSystems){
    //do something with mediaFileSystems
});

mediaFileSystems是一个包含多个FileSystem的数组,其包含的是应用有权限访问的所有媒体库FileSystem,而非只是用户刚刚选择的。

下面我们来将更新媒体库的功能加入到Media Manager。首先在HTML中添加更新按钮、loading元素和出错提示框:

<div id="error">更新失败</div>
<div id="appTitle">Media Manager<span id="edit">&#x3466;</span><span id="scan">&#xf015c;</span>
<div id="loading">
<div class="loading" index="0"></div>
<div class="loading" index="1"></div>
<div class="loading" index="2"></div>
<div class="loading" index="3"></div>
<div class="loading" index="4"></div>
</div>
</div>

之后在CSS中添加相应的样式:

#appTitle {
    height: 60px;
    line-height: 60px;
    padding: 0 20px;
    font-size: 24px;
    color: #CCC;
    background: #222;
    position: relative;
}
#edit, #scan {
    display: inline-block;
    font-size: 12px;
    font-family: 'iconfont';
    height: 20px;
    line-height: 20px;
    padding: 5px;
    cursor: pointer;
}
@-webkit-keyframes loading {
    0% {
        left: 0;
        opacity: 0;
    }
    5% {
        opacity: 1;
    }
    95% {
        opacity: 1;
    }
    100% {
        left: 100%;
        opacity: 0;
    }
}
.loading {
    width: 5px;
    height: 5px;
    background: #CCC;
    position: absolute;
    opacity: 0;
    -webkit-animation: loading 5s;
    -webkit-animation-timing-function: cubic-bezier(0.1, 0.48, 0.9, 0.52);
    -webkit-animation-iteration-count: infinite;
}
.loading[index="0"] {
    -webkit-animation-delay: 0.3s;
}
.loading[index="1"] {
    -webkit-animation-delay: 0.6s;
}
.loading[index="2"] {
    -webkit-animation-delay: 0.9s;
}
.loading[index="3"] {
    -webkit-animation-delay: 1.2s;
}
.loading[index="4"] {
    -webkit-animation-delay: 1.5s;
}
#loading {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 5px;
    display: none;
}
#error {
    background: rgba(255, 255, 255, 0.5);
    height: 20px;
    line-height: 20px;
    font-size: 14px;
    color: #222;
    text-align: center;
    display: none;
}

其中为了让loading元素位置相对于appTitle,将appTitle的position属性更改为了relative。

最后在JS中加入相应事件:

var scanning = false;
document.getElementById('scan').onclick = function(){
    scanning?
        chrome.mediaGalleries.startMediaScan&&chrome.mediaGalleries.startMediaScan():
        chrome.mediaGalleries.cancelMediaScan&&chrome.mediaGalleries.cancelMediaScan();
}
document.getElementById('error').onclick = function(){
    this.style.display = 'none';
}
chrome.mediaGalleries.onScanProgress&&chrome.mediaGalleries.onScanProgress.addListener(function(details){
    switch(details.type){
        case 'start':
            scanning = true;
            document.getElementById('loading').style.display = 'block';
            break;
        case 'cancel':
            scanning = false;
            document.getElementById('loading').style.display = 'none';
            break;
        case 'finish':
            scanning = false;
            document.getElementById('loading').style.display = 'none';
            chrome.mediaGalleries.addScanResults(listMediaGalleries);
            break;
        case 'error':
            scanning = false;
            document.getElementById('loading').style.display = 'none';
            document.getElementById('error').style.display = 'block';
            break;
    }
});

其中scanning变量用来记录应用是否正在更新媒体库,如果正在更新,当用户点击更新按钮后会停止更新,否则开始更新。

更新媒体库相关方法和事件在部分版本中尚未生效,所以在调用时均先做以判断,防止出现方法未定义的情况发生。

8.4获取媒体文件信息

在8.1节中提到过,通过getMediaFileSystems方法获取到的fileSystem中的root属性值就是Entry对象,结合第7章的内容就可以对媒体库中的文件进行操作。

通过getMetadata方法可以读取出媒体文件相关信息1:

chrome.mediaGalleries.getMetadata(mediaFile, {metadataType: 'all'}, function(metadata){
    //do something with metadata
});

1 目前处于Beta分支。

其中mediaFile为Blob类型数据。metadataType为获取信息的类型,如果不指定默认为all,即全部信息,还可以指定为mimeTypeOnly来只获取MIME。

metadata为一个包含媒体信息的对象,完整结构如下:

{
    mimeType: MIME类型,
    height: 视频或图片的高度,单位为像素,
    width: 视频或图片的宽度,单位为像素,
    xResolution: 照片的水平分辨率,用电视线表示,
    yResolution: 照片的垂直分辨率,用电视线表示,
    duration: 视频或音乐的长度,以秒为单位,
    rotation: 视频或图片的旋转角度,以度为单位,
    cameraMake: 图片中的相机制造商,
    cameraModel: 相机模式,
    exposureTimeSeconds: 曝光时间,
    flashFired: 是否开启闪光灯,
    fNumber: 光圈大小,
    focalLengthMm: 焦距,单位为毫米,
    isoEquivalent: 等效ISO,
    album: 视频或音乐专辑,
    artist: 艺术家,
    comment: 评分,
    copyright: 版权信息,
    disc: 盘片编号,
    genre: 流派,
    language: 语言,
    title: 标题,
    track: 音轨数,
    rawTags: [
        {
            type: 类型,如mp3、h264,
            tags: 标签
        }
    ]
}

最后值得说明的是,由于已经在Manifest中声明了媒体库的读取权限,而不必另外声明fileSystem权限用于读取媒体文件。

由于遍历目录和读取、复制、删除文件与文章讲解的内容无关,读者可自行参考第7章内容实现相关功能。Media Manager实例的源码可以通过https://github.com/sneezry/chrome_extensions_and_apps_programming/tree/master/media_manager下载到。



-----------------------------------------------------
转载请注明来源此处
原地址:#

-----网友评论----
暂无评论
-----发表评论----
微网聚博客乐园 ©2014 blog.mn886.net 鲁ICP备14012923号   网站导航