Membangun Aplikasi dengan Sencha Ext JS

Tujuan dokumen ini adalah membantu Anda mulai membangun Aplikasi Chrome dengan framework Sencha Ext JS. Untuk mencapai tujuan ini, kita akan mempelajari aplikasi media player yang di-build oleh Sencha. Kode sumber dan Dokumentasi API tersedia di GitHub.

Aplikasi ini menemukan server media yang tersedia milik pengguna, termasuk perangkat media yang terhubung ke komputer dan software yang mengelola media melalui jaringan. Pengguna dapat menjelajahi media, memutarnya melalui jaringan, atau menyimpan secara offline.

Berikut adalah hal-hal penting yang harus Anda lakukan untuk membangun aplikasi media player menggunakan Sencha Ext JS:

  • Buat manifes, manifest.json.
  • Buat halaman acara, background.js.
  • Logika aplikasi Sandbox.
  • Berkomunikasi antara Aplikasi Chrome dan file dalam sandbox.
  • Temukan server media.
  • Menjelajahi dan memutar media.
  • Simpan media ke offline.

Buat manifes

Semua Aplikasi Chrome memerlukan file manifes yang berisi informasi yang diperlukan Chrome untuk meluncurkan aplikasi. Seperti yang ditunjukkan dalam manifes, aplikasi pemutar media ditetapkan ke "offline_enabled"; aset media dapat disimpan secara lokal, diakses, dan diputar terlepas dari konektivitas.

Kolom "sandbox" digunakan untuk melakukan sandbox logika utama aplikasi di asal yang unik. Semua konten yang di-sandbox dikecualikan dari Kebijakan Keamanan Konten Aplikasi Chrome, tetapi tidak dapat mengakses API Aplikasi Chrome secara langsung. Manifes ini juga menyertakan izin "soket"; aplikasi media player menggunakan API soket untuk terhubung ke server media melalui jaringan.

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

Buat halaman acara

Semua Aplikasi Chrome memerlukan background.js untuk meluncurkan aplikasi. Halaman utama pemutar media, index.html, akan terbuka di jendela dengan dimensi yang ditentukan:

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

Aplikasi Chrome berjalan di lingkungan terkontrol yang menerapkan Kebijakan Keamanan Konten (CSP) yang ketat. Aplikasi pemutar media memerlukan hak istimewa yang lebih tinggi untuk merender komponen Ext JS. Untuk mematuhi CSP dan menjalankan logika aplikasi, halaman utama aplikasi, index.html, membuat iframe yang berfungsi sebagai lingkungan sandbox:

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

iframe mengarah ke sandbox.html yang menyertakan file yang diperlukan untuk aplikasi 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>

Skrip app.js mengeksekusi semua kode Ext JS dan merender tampilan pemutar media. Karena skrip ini di-sandbox, skrip tidak dapat mengakses API Aplikasi Chrome secara langsung. Komunikasi antara app.js dan file yang bukan sandbox dilakukan menggunakan HTML5 Post Message API.

Berkomunikasi antar-file

Agar aplikasi pemutar media dapat mengakses API Aplikasi Chrome, seperti membuat kueri jaringan untuk server media, app.js memposting pesan ke index.js. Tidak seperti app.js dengan sandbox, index.js dapat langsung mengakses API Aplikasi Chrome.

index.js membuat iframe:

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

iframeWindow = iframe.contentWindow;

Dan memproses pesan dari file dalam 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);

Pada contoh berikut, app.js mengirim pesan ke index.js untuk meminta kunci 'extension-baseurl':

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

index.js menerima permintaan, menetapkan hasil, dan membalas dengan mengirimkan kembali URL Dasar:

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

Menemukan server media

Ada banyak hal yang diperlukan untuk menemukan server media. Pada level tinggi, alur kerja penemuan dimulai oleh tindakan pengguna untuk menelusuri server media yang tersedia. Pengontrol MediaServer memposting pesan ke index.js; index.js akan memproses pesan ini, dan memanggil Upnp.js saat diterima.

Upnp library menggunakan API soket Aplikasi Chrome untuk menghubungkan aplikasi pemutar media dengan server media yang ditemukan dan menerima data media dari server media. Upnp.js juga menggunakan soapclient.js untuk mengurai data server media. Bagian selanjutnya menjelaskan alur kerja ini secara lebih mendetail.

Posting pesan

Saat pengguna mengklik tombol Server Media di bagian tengah aplikasi pemutar media, MediaServers.js akan memanggil discoverServers(). Fungsi ini akan memeriksa terlebih dahulu apakah ada permintaan penemuan yang belum terselesaikan, dan jika benar, akan membatalkannya agar permintaan baru dapat dimulai. Selanjutnya, pengontrol memposting pesan ke index.js dengan key upnp-discovery, dan dua pemroses 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');
    }
});

Memanggil upnpDiscover()

index.js memproses pesan 'upnp-discover' dari app.js dan merespons dengan memanggil upnpDiscover(). Saat server media ditemukan, index.js akan mengekstrak domain server media dari parameter, menyimpan server secara lokal, memformat data server media, dan mengirim data ke pengontrol MediaServer.

Mengurai data server media

Saat menemukan server media baru, Upnp.js akan mengambil deskripsi perangkat dan mengirimkan Soaprequest untuk menjelajahi serta mengurai data server media; soapclient.js akan mengurai elemen media berdasarkan nama tag ke dalam dokumen.

Hubungkan ke server media

Upnp.js terhubung ke server media yang ditemukan dan menerima data media menggunakan API soket Aplikasi Chrome:

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

Jelajahi dan putar media

Pengontrol MediaExplorer mencantumkan semua file media dalam folder server media dan bertanggung jawab untuk memperbarui navigasi breadcrumb di jendela aplikasi pemutar media. Saat pengguna memilih file media, pengontrol akan memposting pesan ke index.js dengan kunci '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 memproses pesan postingan ini dan merespons dengan memanggil 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);
}

Simpan media ke offline

Sebagian besar pekerjaan keras untuk menyimpan media secara offline dilakukan oleh library filer.js. Anda dapat membaca library ini selengkapnya dalam Memperkenalkan filer.js.

Proses dimulai saat pengguna memilih satu atau beberapa file dan memulai tindakan 'Jadikan offline'. Pengontrol MediaExplorer memposting pesan ke index.js dengan kunci 'download-media'; index.js akan memproses pesan ini dan memanggil fungsi downloadMedia() untuk memulai proses download:

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

Metode utilitas DownloadProcess membuat permintaan xhr untuk mendapatkan data dari server media dan menunggu status penyelesaian. Tindakan ini akan memulai callback saat pemuatan yang memeriksa konten yang diterima dan menyimpan data secara lokal menggunakan fungsi 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);
    }
);

Saat proses download selesai, MediaExplorer akan memperbarui daftar file media dan panel hierarki pemutar media.