Crea app con Sencha Ext JS

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.