Tworzenie aplikacji za pomocą Sencha Ext JS

Ten dokument pomoże Ci rozpocząć tworzenie aplikacji Chrome za pomocą platformy Sencha Ext JS. Aby osiągnąć ten cel, zagłębimy się w aplikację odtwarzacza multimediów stworzoną przez firmę Sencha. Kod źródłowy i dokumentacja interfejsu API są dostępne na GitHubie.

Ta aplikacja wykrywa dostępne serwery multimediów użytkownika, w tym podłączone do komputera urządzenia multimedialne i oprogramowanie do zarządzania multimediami w sieci. Użytkownicy mogą przeglądać multimedia, odtwarzać treści w sieci i zapisywać pliki offline.

Oto najważniejsze rzeczy, które musisz wykonać, aby utworzyć aplikację odtwarzacza multimediów przy użyciu Sencha Ext JS:

  • Utwórz plik manifestu manifest.json.
  • Utwórz stronę wydarzenia, background.js.
  • Logika aplikacji Sandbox.
  • Komunikacja między aplikacją Chrome a plikami w piaskownicy.
  • Odkryj serwery multimediów.
  • Odkrywaj i odtwarzaj multimedia.
  • Zapisuj multimedia offline.

Utwórz plik manifestu

Wszystkie Aplikacje Chrome wymagają pliku manifestu zawierającego informacje potrzebne Chrome do uruchamiania aplikacji. Jak wskazujemy w pliku manifestu, aplikacja odtwarzacza multimediów ma ustawienie „offline_enabled”. Zasoby multimedialne można zapisywać lokalnie, otwierać i odtwarzać niezależnie od połączenia.

Pole „Piaskownica” służy do umieszczenia głównej logiki aplikacji w piaskownicy w unikalnym źródle. Wszystkie treści w piaskownicy są zwolnione z polityki bezpieczeństwa treści aplikacji Chrome, ale nie mają bezpośredniego dostępu do interfejsów API aplikacji Chrome. Plik manifestu zawiera też uprawnienie „socket”. Aplikacja odtwarzacza multimediów używa socket API do łączenia się z serwerem multimediów przez sieć.

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

Strona tworzenia wydarzenia

Wszystkie aplikacje Chrome, które można uruchomić, wymagają polecenia background.js. Strona główna odtwarzacza (index.html) otworzy się w oknie o określonych wymiarach:

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

});

Logika aplikacji piaskownicy

Aplikacje Chrome działają w kontrolowanym środowisku, które wymusza rygorystyczne zasady Content Security Policy (CSP). Aplikacja odtwarzacza multimediów potrzebuje większych uprawnień, aby renderować komponenty Ext JS. Aby zapewnić zgodność z CSP i wykonać logikę aplikacji, jej strona główna (index.html) tworzy element iframe, który działa jak środowisko piaskownicy:

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

Element iframe wskazuje stronę sandbox.html, która zawiera pliki wymagane przez aplikację 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>

Skrypt app.js wykonuje cały kod Ext JS i renderuje widoki odtwarzacza multimediów. Ten skrypt jest w trybie piaskownicy, więc nie ma bezpośredniego dostępu do interfejsów API aplikacji Chrome. Komunikacja między plikami app.js a plikami spoza piaskownicy odbywa się za pomocą interfejsu HTML5 Post Message API.

Komunikacja między plikami

Aby aplikacja odtwarzacza multimediów mogła uzyskiwać dostęp do interfejsów API aplikacji Chrome, np. wysyłać zapytania do sieci w poszukiwaniu serwerów multimediów, app.js publikuje wiadomości w tagu index.js. W przeciwieństwie do app.js w trybie piaskownicy index.js ma bezpośredni dostęp do interfejsów API aplikacji Chrome.

index.js tworzy element iframe:

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

iframeWindow = iframe.contentWindow;

Nasłuchuje też wiadomości z plików w piaskownicy:

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

W poniższym przykładzie app.js wysyła na adres index.js wiadomość z żądaniem klucza „extension-baseurl”:

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

index.js otrzymuje żądanie, przypisuje wynik i odpowiada, wysyłając z powrotem podstawowy adres URL:

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

Odkryj serwery multimediów

Odkrywanie serwerów multimediów wymaga wielkich nakładów pracy. Ogólnie proces wykrywania jest inicjowany przez działanie użytkownika w celu wyszukania dostępnych serwerów multimediów. Kontroler serwera mediów wysyła komunikat do index.js, index.js nasłuchuje tej wiadomości, a po jej otrzymaniu wywołuje Upnp.js.

Upnp library używa socket API aplikacji Chrome do łączenia aplikacji odtwarzacza multimediów ze wszystkimi wykrytymi serwerami multimediów i odbierania danych z serwera multimediów. Upnp.js używa też pliku soapclient.js do analizowania danych serwera multimediów. W pozostałej części tej sekcji znajdziesz bardziej szczegółowe opisy tego przepływu pracy.

Opublikuj wiadomość

Gdy użytkownik kliknie przycisk Serwery multimediów na środku aplikacji odtwarzacza, MediaServers.js wywoła metodę discoverServers(). Ta funkcja najpierw sprawdza, czy nie ma zaległych żądań wykrywania. Jeśli ma wartość prawda, przerywa je, aby umożliwić zainicjowanie nowego żądania. Następnie kontroler wysyła wiadomość do index.js z kluczem upnp-discovery i 2 detektorami wywołań zwrotnych:

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

Wywołaj funkcję upnpDiscover()

index.js czeka na wiadomość „upnp-discover” od nadawcy app.js i odpowiada, dzwoniąc pod numer upnpDiscover(). Po wykryciu serwera multimediów index.js wyodrębnia domenę serwera multimediów z parametrów, zapisuje serwer lokalnie, formatuje dane serwera multimediów i przekazuje je do kontrolera MediaServer.

Analizuj dane serwera multimediów

Gdy Upnp.js wykrywa nowy serwer multimediów, pobiera opis urządzenia i wysyła żądanie Soaprequest, aby przejrzeć i przeanalizować dane serwera multimediów. soapclient.js analizuje w dokumencie elementy multimediów według nazwy tagu.

Połącz z serwerem multimediów

Upnp.js łączy się z wykrytymi serwerami multimediów i odbiera dane multimediów za pomocą interfejsu 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);
        });
    });
});

Przeglądanie i odtwarzanie multimediów

Kontroler Media Explorer wyświetla wszystkie pliki multimedialne w folderze serwera multimediów i odpowiada za aktualizację menu nawigacyjnego w oknie aplikacji odtwarzacza multimediów. Gdy użytkownik wybierze plik multimedialny, kontroler wysyła wiadomość do przeglądarki index.js przy użyciu klucza „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 słucha tego posta i odpowiada, dzwoniąc pod numer 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);
}

Zapisywanie multimediów offline

Większość ciężkiej pracy związanej z zapisywaniem multimediów w trybie offline wykonuje biblioteka filer.js. Więcej informacji o tej bibliotece znajdziesz w artykule Przedstawiamy filer.js.

Proces rozpoczyna się, gdy użytkownik wybierze co najmniej 1 plik i zainicjuje działanie „Przejdź do trybu offline”. Kontroler Media Explorer wysyła do index.js wiadomość z kluczem „download-media”. index.js nasłuchuje tej wiadomości i wywołuje funkcję downloadMedia(), aby zainicjować proces pobierania:

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

Metoda narzędzi DownloadProcess tworzy żądanie xhr, aby pobrać dane z serwera multimediów i oczekuje na stan ukończenia. Zainicjuje to wywołanie zwrotne onload, które sprawdza otrzymaną treść i zapisze dane lokalnie za pomocą funkcji 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);
    }
);

Po zakończeniu pobierania MediaExplorer zaktualizuje listę plików multimedialnych i panel drzewa odtwarzacza.