El objetivo de este documento es que comiences a compilar apps de Chrome con el framework Sencha Ext JS. Para lograr este objetivo, exploraremos en detalle una app de reproductor multimedia creada por Sencha. El código fuente y la documentación de la API están disponibles en GitHub.
Esta app descubre los servidores de medios disponibles del usuario, incluidos los dispositivos multimedia conectados a la PC y el software que administra el contenido multimedia en la red. Los usuarios pueden explorar contenido multimedia, reproducir contenido mediante la red o guardar contenido sin conexión.
Estas son las acciones clave que debes realizar para compilar una app de reproductor multimedia con Sencha Ext JS:
- Crea el manifiesto,
manifest.json
. - Crea una página del evento,
background.js
. - La lógica de la app de Sandbox
- Comunicación entre la app de Chrome y los archivos de la zona de pruebas
- Descubre servidores de medios.
- Explora y reproduce contenido multimedia.
- Guardar contenido multimedia sin conexión
Crear manifiesto
Todas las apps de Chrome requieren un archivo de manifiesto que contenga la información que Chrome necesita para iniciar las apps. Como se indica en el manifiesto, la app de reproducción multimedia es "offline_enabled"; los elementos multimedia se pueden guardar de forma local, acceder a ellos y reproducirlos sin importar la conectividad.
El campo "zona de pruebas" se usa para eliminar la lógica principal de la app en un origen único. Todo el contenido de la zona de pruebas está exento de la Política de Seguridad del Contenido de la app de Chrome, pero no puede acceder directamente a las APIs de la app de Chrome. El manifiesto también incluye el permiso "socket"; la app de reproductor multimedia usa la API de socket para conectarse a un servidor multimedia en la red.
{
"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 para crear un evento
Todas las apps de Chrome requieren background.js
para iniciar la aplicación. La página principal del reproductor multimedia, index.html
, se abre en una ventana con las dimensiones 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 de la app de zona de pruebas
Las Apps de Chrome se ejecutan en un entorno controlado que aplica una Política de Seguridad del Contenido (CSP) estricta. La app de reproducción multimedia necesita algunos privilegios más altos para procesar los componentes de Ext JS. Para cumplir con la CSP y ejecutar la lógica de la app, index.html
, la página principal de la app, crea un iframe que actúa como un entorno de zona de pruebas:
<iframe id="sandbox-frame" sandbox="allow-scripts" src="sandbox.html"></iframe>
El iframe apunta a sandbox.html, que incluye los archivos necesarios para la aplicación 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>
La secuencia de comandos app.js ejecuta todo el código Ext JS y procesa las vistas del reproductor multimedia. Dado que esta secuencia de comandos está en una zona de pruebas, no puede acceder directamente a las APIs de la app de Chrome. La comunicación entre los archivos app.js
y los que no están en la zona de pruebas se realiza mediante la API de Post Message de HTML5.
Cómo comunicarse entre archivos
Para que la app de reproductor multimedia acceda a las APIs de la app de Chrome, como consultar la red en busca de servidores multimedia, app.js
publica mensajes en index.js. A diferencia de la zona de pruebas app.js
, index.js
puede acceder directamente a las APIs de la app de Chrome.
index.js
crea el iframe:
var iframe = document.getElementById('sandbox-frame');
iframeWindow = iframe.contentWindow;
Además, escucha los mensajes de los archivos de la zona de pruebas:
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);
En el siguiente ejemplo, app.js
envía un mensaje a index.js
para solicitar la clave
"extension-baseurl":
Ext.data.PostMessage.request({
key: 'extension-baseurl',
success: function(data) {
//...
}
});
index.js
recibe la solicitud, asigna el resultado y responde enviando de vuelta la URL base:
function extensionBaseUrl(data) {
data.result = chrome.extension.getURL('/');
iframeWindow.postMessage(data, '*');
}
Cómo descubrir servidores de contenido multimedia
Hay muchos aspectos para descubrir servidores multimedia. En un nivel superior, el flujo de trabajo de descubrimiento se inicia mediante una acción del usuario para buscar servidores de medios disponibles. El controlador de MediaServer publica un mensaje en index.js
; index.js
escucha este mensaje y, cuando se recibe, llama a Upnp.js.
Upnp library
usa la API de socket de la app de Chrome para conectar la app de reproductor multimedia con cualquier servidor multimedia descubierto y recibir datos multimedia de este. Upnp.js
también usa soapclient.js para analizar los datos del servidor de medios. En el resto de esta sección, se describe este flujo de trabajo con más detalle.
Publicar mensaje
Cuando un usuario hace clic en el botón Servidores de contenido multimedia en el centro de la app de reproductor multimedia, MediaServers.js
llama a discoverServers()
. Esta función primero verifica si hay solicitudes de descubrimiento pendientes y, si es verdadera, las anula para que se pueda iniciar la solicitud nueva. A continuación, el controlador publica un mensaje en index.js
con una clave upnp-discovery y dos objetos de escucha de devolución de llamada:
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');
}
});
Cómo llamar a upnpDiscover()
index.js
escucha el mensaje "upnp-discover" de app.js
y responde llamando a upnpDiscover()
. Cuando se detecta un servidor multimedia, index.js
extrae el dominio del servidor multimedia de los parámetros, guarda el servidor de forma local, da formato a los datos del servidor multimedia y envía los datos al controlador MediaServer
.
Cómo analizar los datos del servidor de medios
Cuando Upnp.js
descubre un servidor multimedia nuevo, recupera una descripción del dispositivo y envía una Soaprequest para explorar y analizar los datos del servidor de contenido multimedia. soapclient.js
analiza los elementos multimedia por nombre de etiqueta en un documento.
Conectarse al servidor multimedia
Upnp.js
se conecta a los servidores de medios detectados y recibe datos multimedia mediante la API de socket de la app de 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);
});
});
});
Explora y reproduce contenido multimedia
El controlador MediaExplorer enumera todos los archivos multimedia dentro de una carpeta del servidor multimedia y es responsable de actualizar la navegación de la ruta de navegación en la ventana de la app del reproductor multimedia. Cuando un usuario selecciona un archivo multimedia, el control publica un mensaje en index.js
con la tecla "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
escucha el mensaje de la publicación y responde llamando a 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);
}
Guardar contenido multimedia sin conexión
La mayor parte del trabajo duro para guardar contenido multimedia sin conexión lo realiza la biblioteca filer.js. Puedes obtener más información sobre esta biblioteca en Introducción a filer.js.
El proceso se inicia cuando un usuario selecciona uno o más archivos e inicia la acción "Sin conexión".
El controlador MediaExplorer publica un mensaje en index.js
con una clave "download-media";
index.js
escucha este mensaje y llama a la función downloadMedia()
para iniciar el
proceso de descarga:
function downloadMedia(data) {
DownloadProcess.run(data.params.files, function() {
data.result = true;
sendMessage(data);
});
}
El método de utilidad DownloadProcess
crea una solicitud xhr para obtener datos del servidor multimedia y espera el estado de finalización. Esto inicia la devolución de llamada de carga que verifica el contenido recibido y guarda los datos de forma local con la función 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);
}
);
Cuando finaliza el proceso de descarga, MediaExplorer
actualiza la lista de archivos multimedia y el panel del árbol del reproductor multimedia.