Bouw apps met Sencha Ext JS

Het doel van dit document is om u op weg te helpen bij het bouwen van Chrome-apps met het Sencha Ext JS- framework. Om dit doel te bereiken duiken we in een mediaspeler-app gebouwd door Sencha. De broncode en API-documentatie zijn beschikbaar op GitHub.

Deze app ontdekt de beschikbare mediaservers van een gebruiker, inclusief media-apparaten die op de pc zijn aangesloten en software die media via het netwerk beheert. Gebruikers kunnen door media bladeren, via het netwerk afspelen of offline opslaan.

Hier zijn de belangrijkste dingen die u moet doen om een ​​mediaspeler-app te bouwen met Sencha Ext JS:

  • Maak manifest, manifest.json .
  • Maak een evenementenpagina , background.js .
  • Logica van de Sandbox- app.
  • Communiceer tussen de Chrome-app en bestanden in de sandbox.
  • Ontdek mediaservers.
  • Media verkennen en afspelen.
  • Bewaar media offline.

Maak een manifest

Voor alle Chrome-apps is een manifestbestand vereist dat de informatie bevat die Chrome nodig heeft om apps te starten. Zoals aangegeven in het manifest is de mediaspeler-app "offline_enabled"; media-items kunnen lokaal worden opgeslagen, geopend en afgespeeld, ongeacht de connectiviteit.

Het veld 'sandbox' wordt gebruikt om de hoofdlogica van de app in een unieke oorsprong te sandboxen. Alle inhoud in een sandbox is vrijgesteld van het Chrome App Content Security Policy , maar heeft geen directe toegang tot de Chrome App API's. Het manifest bevat ook de machtiging 'socket'; de mediaspeler-app gebruikt de socket API om via het netwerk verbinding te maken met een mediaserver.

{
    "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"
            ]
        }
    ]
}

Maak een evenementenpagina

Alle Chrome-apps hebben background.js nodig om de applicatie te starten. De hoofdpagina van de mediaspeler, index.html , wordt geopend in een venster met de opgegeven afmetingen:

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 van de Sandbox-app

Chrome-apps worden uitgevoerd in een gecontroleerde omgeving die een strikt Content Security Policy (CSP) afdwingt. De mediaspeler-app heeft wat hogere rechten nodig om de Ext JS-componenten weer te geven. Om te voldoen aan CSP en de app-logica uit te voeren, maakt de hoofdpagina van de app, index.html , een iframe dat fungeert als een sandbox-omgeving:

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

Het iframe verwijst naar sandbox.html , dat de bestanden bevat die nodig zijn voor de Ext JS-applicatie:

<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>

Het app.js- script voert alle Ext JS-code uit en geeft de weergaven van de mediaspeler weer. Omdat dit script in een sandbox is geplaatst, heeft het geen rechtstreekse toegang tot de API's van de Chrome-app. Communicatie tussen app.js en niet-sandboxbestanden vindt plaats met behulp van de HTML5 Post Message API .

Communiceer tussen bestanden

Om ervoor te zorgen dat de mediaspeler-app toegang krijgt tot de API's van de Chrome-app, zoals het netwerk doorzoekt naar mediaservers, plaatst app.js berichten naar index.js . In tegenstelling tot app.js in de sandbox heeft index.js rechtstreeks toegang tot de API's van de Chrome-app.

index.js maakt het iframe:

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

iframeWindow = iframe.contentWindow;

En luistert naar berichten uit de sandboxbestanden:

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);

In het volgende voorbeeld stuurt app.js een bericht naar index.js met het verzoek om de sleutel 'extension-baseurl':

Ext.data.PostMessage.request({
    key: 'extension-baseurl',
    success: function(data) {
        //...
    }
});

index.js ontvangt het verzoek, wijst het resultaat toe en antwoordt door de basis-URL terug te sturen:

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

Ontdek mediaservers

Er komt veel kijken bij het ontdekken van mediaservers. Op een hoog niveau wordt de ontdekkingsworkflow geïnitieerd door een gebruikersactie om naar beschikbare mediaservers te zoeken. De MediaServer-controller plaatst een bericht op index.js ; index.js luistert naar dit bericht en roept bij ontvangst Upnp.js aan.

De Upnp library gebruikt de Chrome App socket API om de mediaspeler-app te verbinden met eventueel ontdekte mediaservers en mediagegevens van de mediaserver te ontvangen. Upnp.js gebruikt ook soapclient.js om de mediaservergegevens te parseren. In de rest van deze sectie wordt deze workflow gedetailleerder beschreven.

Bericht plaatsen

Wanneer een gebruiker op de knop Media Servers in het midden van de mediaspeler-app klikt, roept MediaServers.js discoverServers() aan. Deze functie controleert eerst of er nog openstaande detectieverzoeken zijn en als dit waar is, worden deze afgebroken zodat het nieuwe verzoek kan worden gestart. Vervolgens plaatst de controller een bericht naar index.js met een sleutel upnp-discovery en twee callback-listeners:

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');
    }
});

Roep npDiscover() op

index.js luistert naar het 'upnp-discover'-bericht van app.js en reageert door upnpDiscover() aan te roepen. Wanneer een mediaserver wordt ontdekt, extraheert index.js het mediaserverdomein uit de parameters, slaat de server lokaal op, formatteert de mediaservergegevens en pusht de gegevens naar de MediaServer controller.

Parseer mediaservergegevens

Wanneer Upnp.js een nieuwe mediaserver ontdekt, haalt het een beschrijving van het apparaat op en verzendt het een Soaprequest om door de mediaservergegevens te bladeren en deze te parseren; soapclient.js parseert de media-elementen op tagnaam in een document.

Maak verbinding met de mediaserver

Upnp.js maakt verbinding met ontdekte mediaservers en ontvangt mediagegevens met behulp van de Chrome App socket 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);
        });
    });
});

Media verkennen en afspelen

De MediaExplorer-controller geeft een overzicht van alle mediabestanden in een mediaservermap en is verantwoordelijk voor het bijwerken van de broodkruimelnavigatie in het app-venster van de mediaspeler. Wanneer een gebruiker een mediabestand selecteert, plaatst de controller een bericht op index.js met de 'play-media'-toets:

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 luistert naar dit bericht en reageert door playMedia() aan te roepen:

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);
}

Bewaar media offline

Het meeste werk om media offline op te slaan wordt gedaan door de filer.js-bibliotheek . U kunt meer over deze bibliotheek lezen in Introductie van filer.js .

Het proces start wanneer een gebruiker een of meer bestanden selecteert en de actie 'Offline nemen' initieert. De MediaExplorer-controller plaatst een bericht op index.js met de sleutel 'download-media'; index.js luistert naar dit bericht en roept de downloadMedia() -functie aan om het downloadproces te starten:

function downloadMedia(data) {
        DownloadProcess.run(data.params.files, function() {
            data.result = true;
            sendMessage(data);
        });
    }

De DownloadProcess hulpprogrammamethode creëert een xhr-verzoek om gegevens van de mediaserver op te halen en wacht op de voltooiingsstatus. Dit initieert de onload callback die de ontvangen inhoud controleert en de gegevens lokaal opslaat met behulp van de filer.js functie:

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);
    }
);

Wanneer het downloadproces is voltooid, werkt MediaExplorer de lijst met mediabestanden en het boompaneel van de mediaspeler bij.