O objetivo deste documento é ajudar você a começar a criar apps do Chrome com o Sencha Ext JS de análise de dados em nuvem. Para alcançar esse objetivo, vamos nos aprofundar em um app de player de mídia criado por Sencha. A origem e a documentação da API estão disponíveis no GitHub.
Esse app descobre os servidores de mídia disponíveis do usuário, incluindo dispositivos de mídia conectados ao PC e um software que gerencia mídia pela rede. Os usuários podem procurar mídia, abrir na rede ou salvar off-line.
Aqui estão as principais coisas que você precisa fazer para criar um aplicativo de player de mídia usando o Sencha Ext JS:
- Criar o manifesto,
manifest.json
. - Crie uma página de evento:
background.js
. - Lógica do app Sandbox.
- Fazer uma comunicação entre o app do Chrome e os arquivos no sandbox
- Descobrir servidores de mídia.
- Conheça e acesse conteúdo de mídia.
- Salvar mídia off-line.
Criar manifesto
Todos os apps do Chrome exigem um arquivo de manifesto com as informações que o Chrome precisa para iniciar. apps. Conforme indicado no manifesto, o app player de mídia é "offline_enabled"; os recursos de mídia podem ser salvos localmente, acessados e reproduzidos independentemente da conectividade.
O "sandbox" é usado para colocar a lógica principal do aplicativo no sandbox em uma origem exclusiva. Todas no sandbox conteúdo está isento da Política de Segurança de Conteúdo de apps do Chrome, mas não pode acessar diretamente o APIs de aplicativos do Google Chrome. O manifesto também inclui o valor-chave permissão; o app player de mídia usa o API socket para se conectar a um servidor de mídia pela rede.
{
"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"
]
}
]
}
Página "Criar evento"
Todos os apps do Chrome exigem que o background.js
inicie o app. A página principal do player de mídia,
index.html
, abre em uma janela com as dimensões especificadas:
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;
});
});
Lógica do aplicativo sandbox
Os apps do Google Chrome são executados em um ambiente controlado que aplica uma Política de Segurança de Conteúdo rigorosa
(CSP). O app de reprodução de mídia precisa de alguns privilégios mais altos para renderizar os componentes Ext JS. Para
obedecer à CSP e executar a lógica do app, a página principal index.html
vai criar um iframe que
atua como um ambiente de sandbox:
<iframe id="sandbox-frame" sandbox="allow-scripts" src="sandbox.html"></iframe>
O iframe aponta para sandbox.html, que inclui os arquivos necessários para a extensão JS aplicativo:
<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>
O script app.js executa todo o código JS Ext e renderiza as visualizações do player de mídia. Como este
script está no modo sandbox, não pode acessar diretamente as APIs do aplicativo do Google Chrome. Comunicação entre app.js
e arquivos sem sandbox é feito com a API HTML5 Post Message.
Comunicação entre arquivos
Para que o app de player de mídia acesse as APIs de apps do Chrome, como consultar a rede em busca de mídia
servidores, app.js
publica mensagens em index.js. Ao contrário do app.js
no modo sandbox, o index.js
pode
acessar diretamente as APIs de aplicativos do Google Chrome.
index.js
cria o iframe:
var iframe = document.getElementById('sandbox-frame');
iframeWindow = iframe.contentWindow;
E detecta mensagens dos arquivos no sandbox:
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);
No exemplo a seguir, app.js
envia uma mensagem para index.js
solicitando a chave
"extension-baseurl":
Ext.data.PostMessage.request({
key: 'extension-baseurl',
success: function(data) {
//...
}
});
index.js
recebe a solicitação, atribui o resultado e responde enviando o URL de base de volta:
function extensionBaseUrl(data) {
data.result = chrome.extension.getURL('/');
iframeWindow.postMessage(data, '*');
}
Descobrir servidores de mídia
A descoberta dos servidores de mídia envolve muitos fatores. De modo geral, o fluxo de trabalho de descoberta
iniciado por uma ação do usuário para pesquisar os servidores de mídia disponíveis. O controlador do MediaServer (link em inglês)
posta uma mensagem no index.js
; O app index.js
vai ouvir esta mensagem e, quando for recebida, as ligações
Upnp.js:
O Upnp library
usa a API de soquete do app Chrome para conectar o app de player de mídia a qualquer
servidores de mídia descobertos e receber dados de mídia do servidor de mídia. O Upnp.js
também usa
soapclient.js para analisar os dados do servidor de mídia. No restante desta seção, descrevemos
fluxo de trabalho em mais detalhes.
Postar mensagem
Quando um usuário clica no botão "Servidores de mídia" no centro do app de player de mídia, MediaServers.js
chama discoverServers()
. Primeiro, essa função verifica se há solicitações de descoberta pendentes e se
true, as cancela para que a nova solicitação possa ser iniciada. Em seguida, o controlador publica uma mensagem no
index.js
com uma chave upnp-discovery e dois listeners de callback:
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');
}
});
Chamar upnpDiscover()
index.js
detecta "upnp-discover" de app.js
e responde chamando
upnpDiscover()
Quando um servidor de mídia é descoberto, o index.js
extrai o domínio do servidor de mídia
dos parâmetros, salva o servidor localmente, formata os dados do servidor de mídia e os envia para
o controlador MediaServer
.
Analisar dados do servidor de mídia
Quando o Upnp.js
descobre um novo servidor de mídia, ele recupera uma descrição do dispositivo e envia
uma solicitação Soap para navegar e analisar os dados do servidor de mídia; soapclient.js
analisa os elementos de mídia
por nome de tag em um documento.
Conectar ao servidor de mídia
O Upnp.js
se conecta aos servidores de mídia descobertos e recebe dados de mídia usando o soquete do app Chrome
API:
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);
});
});
});
Acessar e abrir mídia
O controlador MediaExplorer lista todos os arquivos de mídia dentro de uma pasta do servidor de mídia e é
responsável por atualizar a navegação estrutural na janela do app player de mídia. Quando um usuário
seleciona um arquivo de mídia, o controle posta uma mensagem no index.js
com a tag "play-media" chave:
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
detecta a mensagem da postagem e responde chamando 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);
}
Salvar mídia off-line
A maior parte do trabalho para salvar mídia off-line é feita pela biblioteca filer.js. Saiba mais essa biblioteca em Introdução ao filer.js.
O processo é iniciado quando um usuário seleciona um ou mais arquivos e inicia a ação "Ficar off-line" à ação.
O controlador do MediaExplorer (link em inglês) posta uma mensagem para index.js
com a chave "download-media".
index.js
detecta essa mensagem e chama a função downloadMedia()
para iniciar o
processo de download:
function downloadMedia(data) {
DownloadProcess.run(data.params.files, function() {
data.result = true;
sendMessage(data);
});
}
O método utilitário DownloadProcess
cria uma solicitação xhr para receber dados do servidor de mídia e
aguarda o status de conclusão. Isso inicia o callback onload, que verifica o conteúdo recebido
e salva os dados localmente usando a função 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);
}
);
Quando o processo de download for concluído, o MediaExplorer
vai atualizar a lista de arquivos de mídia e a mídia
painel de árvore do player.