Crea apps con Sencha Ext JS

El objetivo de este documento es ayudarte a comenzar a compilar apps de Chrome con la herramienta Sencha Ext JS. en un framework de aplicaciones. Para lograr este objetivo, nos sumergiremos en una app de reproducción multimedia creada por Sencha. La fuente código 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 software que administra contenido multimedia a través de la red. Los usuarios pueden explorar contenido multimedia, reproducir contenido en la red o guardar contenido sin conexión.

Aquí están los pasos clave que debes seguir para compilar una app de reproducción multimedia con Sencha Ext JS:

  • Crear manifiesto, manifest.json.
  • Crear página del evento, background.js
  • Es la lógica de la app de zona de pruebas.
  • Comunicación entre la app de Chrome y los archivos de la zona de pruebas
  • Detecta servidores de contenido multimedia.
  • 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 iniciarse. de Google Chat. Como se indica en el manifiesto, la app de reproducción multimedia está "offline_enabled". los recursos multimedia se guardan localmente, se accede a ellos y se reproducen sin importar la conectividad.

La "zona de pruebas" se usa para realizar una zona de pruebas de la lógica principal de la app en un origen único. Todas las zonas de pruebas contenido está exento de la Política de Seguridad del Contenido de las Apps de Chrome, pero no puede acceder directamente a APIs de la app de Chrome. El manifiesto también incluye el "socket" permiso; la app de reproducción multimedia usa el API de socket para conectarse a un servidor multimedia a través de 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 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 estricta (CSP). La app de reproductor 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, la página principal de la app, index.html, crea un iframe que que funciona como un entorno de zona de pruebas:

<iframe id="sandbox-frame" sandbox="allow-scripts" src="sandbox.html"></iframe>

El iframe dirige a sandbox.html, que incluye los archivos necesarios para la extensión JS aplicación:

<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 de JS externo y procesa las vistas del reproductor multimedia. Desde este secuencia de comandos está en zona de pruebas, no puede acceder directamente a las API de las aplicaciones de Chrome. Comunicación entre app.js y que no están incluidos en la zona de pruebas se realiza con la API de HTML5 Post Message.

Cómo comunicarse entre archivos

Para que la app de reproductor multimedia acceda a las APIs de la app de Chrome, como consultar el contenido multimedia en la red servidores, 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 aplicación de Chrome.

index.js crea el iframe:

var iframe = document.getElementById('sandbox-frame');

iframeWindow = iframe.contentWindow;

Además, detecta 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 la URL base:

function extensionBaseUrl(data) {
    data.result = chrome.extension.getURL('/');
    iframeWindow.postMessage(data, '*');
}

Cómo detectar servidores multimedia

Hay mucho que explorar para descubrir servidores de medios. En términos generales, el flujo de trabajo del descubrimiento que se inicia con una acción del usuario para buscar servidores de medios disponibles. El controlador MediaServer publica un mensaje en index.js; index.js escucha este mensaje y, cuando lo reciba, las llamadas Upnp.js.

El Upnp library usa la API de socket de la app de Chrome para conectar la app del reproductor multimedia con cualquier de servidores de medios detectados y recibir datos de medios del servidor. Upnp.js también usa soapclient.js para analizar los datos del servidor multimedia. En el resto de esta sección, se describe de Terraform en mayor detalle.

Publicar mensaje

Cuando un usuario hace clic en el botón Servidores multimedia, en el centro de la app del reproductor multimedia, MediaServers.js llama a discoverServers(). Esta función primero comprueba si hay solicitudes de descubrimiento pendientes y, true, las anula para que se pueda iniciar la nueva solicitud. Luego, el controlador publica un mensaje para 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');
    }
});

Llama a upnpDiscover()

index.js escucha "upnp-discover" mensaje de app.js y responde llamando 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, formatea los datos del servidor multimedia y envía los datos a el controlador de MediaServer.

Analizar datos del servidor multimedia

Cuando Upnp.js descubre un nuevo servidor multimedia, recupera una descripción del dispositivo y envía una solicitud de Soaprequest para navegar y analizar los datos del servidor de medios; soapclient.js analiza los elementos multimedia. por nombre de etiqueta en un documento.

Conectar al servidor multimedia

Upnp.js se conecta a los servidores de contenido multimedia descubiertos y recibe datos multimedia mediante el socket de la app de 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);
        });
    });
});

Explora y reproduce contenido multimedia

El controlador MediaExplorer muestra todos los archivos multimedia dentro de una carpeta del servidor multimedia. 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 controlador publica un mensaje para index.js con el comando "play-media" clave:

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 este mensaje de 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);
}

Cómo guardar contenido multimedia sin conexión

La mayor parte del trabajo difícil para guardar contenido multimedia sin conexión lo realiza la biblioteca de filer.js. Más información esta biblioteca en Introducing filer.js.

El proceso se inicia cuando un usuario selecciona uno o más archivos e inicia la función "Quitar sin conexión". acción. El controlador de MediaExplorer publica un mensaje en index.js con la clave "download-media". index.js escucha este mensaje y llama a la función downloadMedia() para iniciar la 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. 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 los archivos multimedia. panel del árbol del reproductor.