L'objectif de ce document est de vous aider à créer des applications Chrome avec le framework Sencha Ext JS. Pour atteindre cet objectif, nous allons découvrir en détail une application de lecteur multimédia développée par Sencha. Le code source et la documentation de l'API sont disponibles sur GitHub.
Cette application détecte les serveurs multimédias disponibles d'un utilisateur, y compris les appareils multimédias connectés au PC et les logiciels qui gèrent les contenus multimédias sur le réseau. Les utilisateurs peuvent parcourir des contenus multimédias, les lire sur le réseau ou les enregistrer hors connexion.
Voici les principales étapes à suivre pour créer une application de lecteur multimédia à l'aide de Sencha Ext JS:
- Créer le fichier manifeste,
manifest.json
. - Créez une page d'événement,
background.js
. - Logique de l'application bac à sable.
- Communication entre l'application Chrome et les fichiers en bac à sable
- Découvrir les serveurs multimédias
- Explorer et lire des contenus multimédias
- Enregistrer le contenu multimédia hors connexion.
Créer un fichier manifeste
Toutes les applications Chrome nécessitent un fichier manifeste contenant les informations dont Chrome a besoin pour les lancer. Comme indiqué dans le fichier manifeste, l'application de lecteur multimédia est "offline_enabled". Les éléments multimédias peuvent être enregistrés localement, consultés et lus, quelle que soit la connectivité.
Le champ "sandbox" permet de mettre en bac à sable la logique principale de l'application dans une origine unique. Tout contenu en bac à sable est exempté de la Content Security Policy des applications Chrome, mais ne peut pas accéder directement aux API des applications Chrome. Le fichier manifeste inclut également l'autorisation "socket". L'application de lecteur multimédia utilise l'API de socket pour se connecter à un serveur multimédia sur le réseau.
{
"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"
]
}
]
}
Créer une page d'événement
Toutes les applications Chrome ont besoin de background.js
pour lancer l'application. La page principale du lecteur multimédia, index.html
, s'ouvre dans une fenêtre avec les dimensions spécifiées:
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;
});
});
Logique de l'application sandbox
Les applications Chrome s'exécutent dans un environnement contrôlé qui applique une Content Security Policy (CSP) stricte. L'application de lecteur multimédia nécessite des droits plus élevés pour afficher les composants Ext JS. Pour respecter CSP et exécuter la logique de l'application, la page principale de l'application, index.html
, crée un iFrame qui agit comme un environnement de bac à sable:
<iframe id="sandbox-frame" sandbox="allow-scripts" src="sandbox.html"></iframe>
L'iFrame renvoie vers sandbox.html, qui inclut les fichiers requis pour l'application 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>
Le script app.js exécute tout le code Ext JS et affiche les vues du lecteur multimédia. Comme ce script est en bac à sable, il ne peut pas accéder directement aux API Chrome App. La communication entre app.js
et les fichiers hors bac à sable s'effectue à l'aide de l'API Post Message HTML5.
Communiquer entre les fichiers
Pour que l'application de lecteur multimédia puisse accéder aux API des applications Chrome, par exemple pour interroger le réseau pour les serveurs multimédias, app.js
publie des messages dans le fichier index.js. Contrairement au app.js
en bac à sable, index.js
peut accéder directement aux API des applications Chrome.
index.js
crée l'iFrame:
var iframe = document.getElementById('sandbox-frame');
iframeWindow = iframe.contentWindow;
Il écoute les messages provenant des fichiers en bac à sable:
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);
Dans l'exemple suivant, app.js
envoie un message à index.js
demandant la clé "extension-baseurl":
Ext.data.PostMessage.request({
key: 'extension-baseurl',
success: function(data) {
//...
}
});
index.js
reçoit la requête, attribue le résultat et répond en renvoyant l'URL de base:
function extensionBaseUrl(data) {
data.result = chrome.extension.getURL('/');
iframeWindow.postMessage(data, '*');
}
Découvrir les serveurs multimédias
La découverte des serveurs multimédias
implique beaucoup de travail. De manière générale, le workflow de découverte est lancé par une action de l'utilisateur pour rechercher les serveurs multimédias disponibles. Le contrôleur MediaServer publie un message sur index.js
. index.js
l'écoute et appelle Upnp.js lorsqu'il le reçoit.
Upnp library
utilise l'API de socket de l'application Chrome pour connecter l'application de lecteur multimédia à tous les serveurs multimédias détectés et recevoir les données multimédias de ce serveur. Upnp.js
analyse également les données du serveur multimédia à l'aide de soapclient.js. Le reste de cette section décrit ce workflow plus en détail.
Publier le message
Lorsqu'un utilisateur clique sur le bouton "Serveurs multimédias" au centre de l'application de lecteur multimédia, MediaServers.js
appelle discoverServers()
. Cette fonction vérifie d'abord les requêtes de découverte en attente, puis, si elle est définie sur "true", les abandonne pour que la nouvelle requête puisse être lancée. Ensuite, le contrôleur publie un message sur index.js
avec une clé "upnp-discovery" et deux écouteurs de rappel:
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');
}
});
Appeler upnpDiscover()
index.js
écoute le message "upnp-discover" de app.js
et répond en appelant upnpDiscover()
. Lorsqu'un serveur multimédia est détecté, index.js
extrait le domaine du serveur multimédia à partir des paramètres, enregistre le serveur localement, met en forme les données du serveur multimédia et les transmet au contrôleur MediaServer
.
Analyser les données du serveur multimédia
Lorsque Upnp.js
découvre un nouveau serveur multimédia, il récupère une description de l'appareil et envoie une requête Soaprequest pour parcourir et analyser les données du serveur multimédia. soapclient.js
analyse les éléments multimédias par nom de balise dans un document.
Se connecter au serveur multimédia
Upnp.js
se connecte aux serveurs multimédias découverts et reçoit les données multimédias à l'aide de 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);
});
});
});
Explorer et lire des contenus multimédias
Le contrôleur MediaExplorer répertorie tous les fichiers multimédias d'un dossier de serveur multimédia et est chargé de mettre à jour le fil d'Ariane dans la fenêtre de l'application du lecteur multimédia. Lorsqu'un utilisateur sélectionne un fichier multimédia, le contrôleur publie un message sur index.js
avec la touche "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
écoute ce message et répond en appelant 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);
}
Enregistrer des contenus multimédias hors connexion
Le plus gros travail d'enregistrement des contenus multimédias hors connexion est effectué par la bibliothèque filer.js. Pour en savoir plus, consultez la section Présentation de filer.js.
Le processus démarre lorsqu'un utilisateur sélectionne un ou plusieurs fichiers et lance l'action "Passer hors connexion".
Le contrôleur MediaExplorer publie un message sur index.js
avec une clé "download-media". index.js
écoute ce message et appelle la fonction downloadMedia()
pour lancer le processus de téléchargement:
function downloadMedia(data) {
DownloadProcess.run(data.params.files, function() {
data.result = true;
sendMessage(data);
});
}
La méthode utilitaire DownloadProcess
crée une requête xhr pour obtenir les données du serveur multimédia et attend l'état d'avancement. Cela lance le rappel de chargement, qui vérifie le contenu reçu et enregistre les données localement à l'aide de la fonction 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);
}
);
Une fois le processus de téléchargement terminé, MediaExplorer
met à jour la liste des fichiers multimédias et le panneau d'arborescence du lecteur multimédia.