L'obiettivo di questo documento è aiutarti a iniziare a creare app di Chrome con il framework Sencha Ext JS. Per raggiungere questo obiettivo, esamineremo l'app per media player creata da Sencha. Il codice sorgente e la documentazione dell'API sono disponibili su GitHub.
Questa app rileva i server multimediali disponibili di un utente, inclusi i dispositivi multimediali collegati al PC e il software che gestisce i contenuti multimediali sulla rete. Gli utenti possono sfogliare i contenuti multimediali, riprodurli sulla rete o salvarli offline.
Di seguito sono riportati i passaggi principali per creare un'app per media player utilizzando Sencha Ext JS:
- Crea il manifest,
manifest.json
. - Crea la pagina dell'evento,
background.js
. - La logica dell'app sandbox.
- Comunicazione tra app Chrome e file sandbox.
- Scopri i server multimediali.
- Esplora e riproduci contenuti multimediali.
- Salva contenuti multimediali offline.
Crea manifest
Tutte le app di Chrome richiedono un file manifest contenente le informazioni necessarie a Chrome per avviare le app. Come indicato nel manifest, l'app del media player è "offline_enabled". Gli asset multimediali possono essere salvati in locale, accessibili e riprodotti indipendentemente dalla connettività.
Il campo "sandbox" viene utilizzato per limitare la logica principale dell'app in un'origine unica. Tutti i contenuti con sandbox sono esenti dai Criteri di sicurezza del contenuto delle app di Chrome, ma non possono accedere direttamente alle API delle app di Chrome. Il file manifest include anche l'autorizzazione "socket"; l'app del media player utilizza l'API socket per connettersi a un server multimediale sulla rete.
{
"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"
]
}
]
}
Pagina Crea evento
Tutte le app di Chrome richiedono l'autorizzazione background.js
per avviare l'applicazione. La pagina principale del media player, index.html
, si apre in una finestra con le dimensioni specificate:
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;
});
});
Logica dell'app sandbox
Le app di Chrome vengono eseguite in un ambiente controllato che applica un Criterio di sicurezza del contenuto (CSP) rigoroso. L'app Media player richiede privilegi superiori per il rendering dei componenti Ext JS. Per rispettare CSP ed eseguire la logica dell'app, la pagina principale dell'app, index.html
, crea un iframe che funge da ambiente sandbox:
<iframe id="sandbox-frame" sandbox="allow-scripts" src="sandbox.html"></iframe>
L'iframe rimanda a sandbox.html, che include i file necessari per l'applicazione 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>
Lo script app.js esegue tutto il codice Ext JS e mostra le visualizzazioni del media player. Poiché questo script è limitato tramite sandbox, non può accedere direttamente alle API delle app di Chrome. La comunicazione tra i file app.js
e i file senza sandbox viene eseguita utilizzando l'API HTML5 Post Message.
Comunicare tra file
Per consentire all'app del media player di accedere alle API dell'app di Chrome, ad esempio per eseguire query sui server multimediali nella rete, app.js
pubblica messaggi in index.js. A differenza dell'app.js
con sandbox, index.js
può
accedere direttamente alle API dell'app Chrome.
index.js
crea l'iframe:
var iframe = document.getElementById('sandbox-frame');
iframeWindow = iframe.contentWindow;
E ascolta i messaggi provenienti dai file 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);
Nell'esempio seguente, app.js
invia un messaggio a index.js
richiedendo la chiave "extension-baseurl":
Ext.data.PostMessage.request({
key: 'extension-baseurl',
success: function(data) {
//...
}
});
index.js
riceve la richiesta, assegna il risultato e risponde restituendo l'URL di base:
function extensionBaseUrl(data) {
data.result = chrome.extension.getURL('/');
iframeWindow.postMessage(data, '*');
}
Scopri i server multimediali
Sono molte le cose da fare per scoprire i server multimediali. A livello generale, il flusso di lavoro di rilevamento viene avviato da un'azione utente per cercare i server multimediali disponibili. Il controller MediaServer
pubblica un messaggio su index.js
. index.js
rimane in ascolto del messaggio e, quando lo riceve, chiama
Upnp.js.
Upnp library
utilizza l'API socket dell'app Chrome per connettere l'app Media Player a qualsiasi server multimediale rilevato e per ricevere i dati multimediali dal server multimediale. Upnp.js
utilizza anche
soapclient.js per analizzare i dati del server multimediale. Il resto di questa sezione descrive questo
flusso di lavoro in modo più dettagliato.
Messaggio post
Quando un utente fa clic sul pulsante Media Server al centro dell'app del lettore multimediale, MediaServers.js
chiama discoverServers()
. Questa funzione verifica innanzitutto la presenza di eventuali richieste di rilevamento in sospeso e, se
true, le interrompe in modo da poter avviare la nuova richiesta. Successivamente, il controller pubblica un messaggio su index.js
con una chiave upnp-discovery e due listener di 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');
}
});
Richiama upnpDiscover()
index.js
ascolta il messaggio "upnp-discover" di app.js
e risponde chiamando
upnpDiscover()
. Quando viene rilevato un server multimediale, index.js
estrae il dominio del server multimediale
dai parametri, salva il server localmente, formatta i dati del server multimediale e ne esegue il push al
controller MediaServer
.
Analizza i dati del server multimediale
Quando Upnp.js
scopre un nuovo server multimediale, recupera una descrizione del dispositivo e invia una richiesta Soaprequest per sfogliare e analizzare i dati del server multimediale; soapclient.js
analizza gli elementi multimediali in base al nome del tag in un documento.
Connetti a server multimediale
Upnp.js
si connette ai server multimediali rilevati e riceve dati multimediali utilizzando l'API Chrome App Socket:
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);
});
});
});
Esplora e riproduci contenuti multimediali
Il controller MediaExplorer elenca tutti i file multimediali all'interno di una cartella del server multimediale ed è responsabile dell'aggiornamento della navigazione dei breadcrumb nella finestra dell'app del media player. Quando un utente seleziona un file multimediale, il controller pubblica un messaggio su index.js
con il tasto "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
ascolta questo messaggio del post e risponde chiamando 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);
}
Salva contenuti multimediali offline
La maggior parte del lavoro per salvare i contenuti multimediali offline è svolto dalla libreria filer.js. Puoi scoprire di più su questa libreria in Introduzione a filer.js.
Il processo inizia quando un utente seleziona uno o più file e avvia l'azione "Metti offline".
Il controller MediaExplorer pubblica un messaggio su index.js
con la chiave "download-media".
index.js
rimane in ascolto del messaggio e chiama la funzione downloadMedia()
per avviare il
processo di download:
function downloadMedia(data) {
DownloadProcess.run(data.params.files, function() {
data.result = true;
sendMessage(data);
});
}
Il metodo dell'utilità DownloadProcess
crea una richiesta xhr per ottenere i dati dal server multimediale e attende lo stato di completamento. Viene avviato il callback onload che controlla i contenuti ricevuti
e salva i dati localmente utilizzando la funzione 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);
}
);
Al termine del processo di download, MediaExplorer
aggiorna l'elenco dei file multimediali e il riquadro ad albero del player multimediale.