Цель этого документа — помочь вам начать создание приложений Chrome с помощью платформы Sencha Ext JS . Чтобы достичь этой цели, мы углубимся в приложение медиаплеера, созданное Sencha. Исходный код и документация по API доступны на GitHub.
Это приложение обнаруживает доступные медиа-серверы пользователя, включая мультимедийные устройства, подключенные к компьютеру, и программное обеспечение, которое управляет мультимедиа по сети. Пользователи могут просматривать медиафайлы, воспроизводить их по сети или сохранять в автономном режиме.
Вот ключевые вещи, которые вам необходимо сделать для создания приложения медиаплеера с использованием Sencha Ext JS:
- Создайте манифест,
manifest.json
. - Создайте страницу события ,
background.js
. - Логика приложения- песочницы .
- Обмен данными между приложением Chrome и файлами в песочнице.
- Откройте для себя медиа-серверы.
- Исследуйте и воспроизводите медиа.
- Сохраняйте медиа в автономном режиме.
Создать манифест
Всем приложениям Chrome требуется файл манифеста , содержащий информацию, необходимую Chrome для запуска приложений. Как указано в манифесте, приложение медиаплеера имеет статус «offline_enabled»; Медиа-ресурсы можно сохранять локально, получать к ним доступ и воспроизводить независимо от подключения.
Поле «песочница» используется для изолирования основной логики приложения в уникальном источнике. Весь изолированный контент не подпадает под действие Политики безопасности контента приложений Chrome, но не имеет прямого доступа к API приложений Chrome. Манифест также включает разрешение «сокета»; приложение медиаплеера использует API сокетов для подключения к медиасерверу по сети.
{
"name": "Video Player",
"description": "Features network media discovery and playlist management",
"version": "1.0.0",
"manifest_version": 2,
"offline_enabled": true,
"app": {
"background": {
"scripts": [
"background.js"
]
}
},
...
"sandbox": {
"pages": ["sandbox.html"]
},
"permissions": [
"experimental",
"http://*/*",
"unlimitedStorage",
{
"socket": [
"tcp-connect",
"udp-send-to",
"udp-bind"
]
}
]
}
Создать страницу мероприятия
Всем приложениям Chrome для запуска приложения требуется background.js
Главная страница медиаплеера index.html
открывается в окне указанных размеров:
chrome.app.runtime.onLaunched.addListener(function(launchData) {
var opt = {
width: 1000,
height: 700
};
chrome.app.window.create('index.html', opt, function (win) {
win.launchData = launchData;
});
});
Логика приложения-песочницы
Приложения Chrome работают в контролируемой среде, в которой применяется строгая Политика безопасности контента (CSP) . Приложению медиаплеера требуются более высокие привилегии для рендеринга компонентов Ext JS. Чтобы соответствовать CSP и выполнить логику приложения, на главной странице приложения index.html
создается iframe, который действует как среда песочницы:
<iframe id="sandbox-frame" sandbox="allow-scripts" src="sandbox.html"></iframe>
iframe указывает на файл sandbox.html , который включает файлы, необходимые для приложения Ext JS:
<html>
<head>
<link rel="stylesheet" type="text/css" href="resources/css/app.css" />'
<script src="sdk/ext-all-dev.js"></script>'
<script src="lib/ext/data/PostMessage.js"></script>'
<script src="lib/ChromeProxy.js"></script>'
<script src="app.js"></script>
</head>
<body></body>
</html>
Скрипт app.js выполняет весь код Ext JS и отображает представления медиаплеера. Поскольку этот скрипт находится в изолированной программной среде, он не может напрямую получить доступ к API-интерфейсам приложений Chrome. Связь между app.js
и файлами, не изолированными в песочнице, осуществляется с помощью HTML5 Post Message API .
Обмен файлами
Чтобы приложение медиаплеера могло получить доступ к API приложений Chrome, например, запросить в сети медиасерверы, app.js
отправляет сообщения в index.js . В отличие от изолированного app.js
, index.js
может напрямую обращаться к API-интерфейсам приложений Chrome.
index.js
создает iframe:
var iframe = document.getElementById('sandbox-frame');
iframeWindow = iframe.contentWindow;
И прослушивает сообщения из файлов песочницы:
window.addEventListener('message', function(e) {
var data= e.data,
key = data.key;
console.log('[index.js] Post Message received with key ' + key);
switch (key) {
case 'extension-baseurl':
extensionBaseUrl(data);
break;
case 'upnp-discover':
upnpDiscover(data);
break;
case 'upnp-browse':
upnpBrowse(data);
break;
case 'play-media':
playMedia(data);
break;
case 'download-media':
downloadMedia(data);
break;
case 'cancel-download':
cancelDownload(data);
break;
default:
console.log('[index.js] unidentified key for Post Message: "' + key + '"');
}
}, false);
В следующем примере app.js
отправляет сообщение index.js
с запросом ключа «extension-baseurl»:
Ext.data.PostMessage.request({
key: 'extension-baseurl',
success: function(data) {
//...
}
});
index.js
получает запрос, присваивает результат и отвечает, отправляя обратно базовый URL-адрес:
function extensionBaseUrl(data) {
data.result = chrome.extension.getURL('/');
iframeWindow.postMessage(data, '*');
}
Откройте для себя медиа-серверы
Для обнаружения медиа-серверов требуется очень многое. На высоком уровне рабочий процесс обнаружения инициируется действием пользователя по поиску доступных медиасерверов. Контроллер MediaServer отправляет сообщение в index.js
; index.js
прослушивает это сообщение и при его получении вызывает Upnp.js.
Upnp library
использует API-интерфейс сокета приложения Chrome для подключения приложения медиаплеера к любым обнаруженным медиасерверам и получения медиаданных с медиасервера. Upnp.js
также использует Soapclient.js для анализа данных медиасервера. В оставшейся части этого раздела этот рабочий процесс описан более подробно.
Опубликовать сообщение
Когда пользователь нажимает кнопку «Медиа-серверы» в центре приложения медиаплеера, MediaServers.js
вызывает discoverServers()
. Эта функция сначала проверяет наличие невыполненных запросов на обнаружение и, если это правда, прерывает их, чтобы можно было инициировать новый запрос. Затем контроллер отправляет сообщение в index.js
с ключом upnp-discovery и двумя прослушивателями обратного вызова:
me.activeDiscoverRequest = Ext.data.PostMessage.request({
key: 'upnp-discover',
success: function(data) {
var items = [];
delete me.activeDiscoverRequest;
if (serversGraph.isDestroyed) {
return;
}
mainBtn.isLoading = false;
mainBtn.removeCls('pop-in');
mainBtn.setIconCls('ico-server');
mainBtn.setText('Media Servers');
//add servers
Ext.each(data, function(server) {
var icon,
urlBase = server.urlBase;
if (urlBase) {
if (urlBase.substr(urlBase.length-1, 1) === '/'){
urlBase = urlBase.substr(0, urlBase.length-1);
}
}
if (server.icons && server.icons.length) {
if (server.icons[1]) {
icon = server.icons[1].url;
}
else {
icon = server.icons[0].url;
}
icon = urlBase + icon;
}
items.push({
itemId: server.id,
text: server.friendlyName,
icon: icon,
data: server
});
});
...
},
failure: function() {
delete me.activeDiscoverRequest;
if (serversGraph.isDestroyed) {
return;
}
mainBtn.isLoading = false;
mainBtn.removeCls('pop-in');
mainBtn.setIconCls('ico-error');
mainBtn.setText('Error...click to retry');
}
});
Вызовите upnpDiscover().
index.js
прослушивает сообщение upnp-discover от app.js
и отвечает вызовом upnpDiscover()
. При обнаружении медиасервера index.js
извлекает домен медиасервера из параметров, сохраняет сервер локально, форматирует данные медиасервера и отправляет данные в контроллер MediaServer
.
Анализ данных медиасервера
Когда Upnp.js
обнаруживает новый медиасервер, он затем получает описание устройства и отправляет Soaprequest для просмотра и анализа данных медиасервера; soapclient.js
анализирует элементы мультимедиа по имени тега в документе.
Подключиться к медиасерверу
Upnp.js
подключается к обнаруженным медиа-серверам и получает медиа-данные с помощью API сокета приложения Chrome:
socket.create("udp", {}, function(info) {
var socketId = info.socketId;
//bind locally
socket.bind(socketId, "0.0.0.0", 0, function(info) {
//pack upnp message
var message = String.toBuffer(UPNP_MESSAGE);
//broadcast to upnp
socket.sendTo(socketId, message, UPNP_ADDRESS, UPNP_PORT, function(info) {
// Wait 1 second
setTimeout(function() {
//receive
socket.recvFrom(socketId, function(info) {
//unpack message
var data = String.fromBuffer(info.data),
servers = [],
locationReg = /^location:/i;
//extract location info
if (data) {
data = data.split("\r\n");
data.forEach(function(value) {
if (locationReg.test(value)){
servers.push(value.replace(locationReg, "").trim());
}
});
}
//success
callback(servers);
});
}, 1000);
});
});
});
Исследуйте и воспроизводите медиа
Контроллер MediaExplorer перечисляет все медиафайлы в папке медиасервера и отвечает за обновление навигационной цепочки в окне приложения медиаплеера. Когда пользователь выбирает медиафайл, контроллер отправляет сообщение в index.js
с ключом play-media:
onFileDblClick: function(explorer, record) {
var serverPanel, node,
type = record.get('type'),
url = record.get('url'),
name = record.get('name'),
serverId= record.get('serverId');
if (type === 'audio' || type === 'video') {
Ext.data.PostMessage.request({
key : 'play-media',
params : {
url: url,
name: name,
type: type
}
});
}
},
index.js
прослушивает это сообщение и отвечает, вызывая playMedia()
:
function playMedia(data) {
var type = data.params.type,
url = data.params.url,
playerCt = document.getElementById('player-ct'),
audioBody = document.getElementById('audio-body'),
videoBody = document.getElementById('video-body'),
mediaEl = playerCt.getElementsByTagName(type)[0],
mediaBody = type === 'video' ? videoBody : audioBody,
isLocal = false;
//save data
filePlaying = {
url : url,
type: type,
name: data.params.name
};
//hide body els
audioBody.style.display = 'none';
videoBody.style.display = 'none';
var animEnd = function(e) {
//show body el
mediaBody.style.display = '';
//play media
mediaEl.play();
//clear listeners
playerCt.removeEventListener( 'transitionend', animEnd, false );
animEnd = null;
};
//load media
mediaEl.src = url;
mediaEl.load();
//animate in player
playerCt.addEventListener( 'transitionend', animEnd, false );
playerCt.style.transform = "translateY(0)";
//reply postmessage
data.result = true;
sendMessage(data);
}
Сохраняйте медиафайлы в автономном режиме
Большую часть тяжелой работы по сохранению мультимедиа в автономном режиме выполняет библиотека filer.js . Подробнее об этой библиотеке можно прочитать в разделе Знакомство с filer.js .
Процесс запускается, когда пользователь выбирает один или несколько файлов и инициирует действие «Отключиться от сети». Контроллер MediaExplorer отправляет сообщение в index.js
с ключом «download-media»; index.js
прослушивает это сообщение и вызывает функцию downloadMedia()
, чтобы инициировать процесс загрузки:
function downloadMedia(data) {
DownloadProcess.run(data.params.files, function() {
data.result = true;
sendMessage(data);
});
}
Метод утилиты DownloadProcess
создает запрос xhr для получения данных с медиа-сервера и ожидает статуса завершения. Это инициирует обратный вызов при загрузке, который проверяет полученный контент и сохраняет данные локально с помощью функции filer.js
:
filer.write(
saveUrl,
{
data: Util.arrayBufferToBlob(fileArrayBuf),
type: contentType
},
function(fileEntry, fileWriter) {
console.log('file saved!');
//increment downloaded
me.completedFiles++;
//if reached the end, finalize the process
if (me.completedFiles === me.totalFiles) {
sendMessage({
key : 'download-progresss',
totalFiles : me.totalFiles,
completedFiles : me.completedFiles
});
me.completedFiles = me.totalFiles = me.percentage = me.downloadedFiles = 0;
delete me.percentages;
//reload local
loadLocalFiles(callback);
}
},
function(e) {
console.log(e);
}
);
По завершении процесса загрузки MediaExplorer
обновляет список медиафайлов и панель дерева медиаплеера.