Membangun Aplikasi dengan Sencha Ext JS

Tujuan dokumen ini adalah untuk membantu Anda mulai membuat Aplikasi Chrome dengan Sencha Ext JS Google Workspace for Education. Untuk mencapai tujuan ini, kita akan membahas aplikasi pemutar media yang dibuat oleh Sencha. Elemen sumber dan Dokumentasi API tersedia di GitHub.

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

Berikut adalah hal-hal utama yang harus Anda lakukan untuk membuat aplikasi pemutar media menggunakan Sencha Ext JS:

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

Membuat manifes

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

"Sandbox" digunakan untuk melakukan sandbox logika utama aplikasi di origin yang unik. Semua dalam sandbox konten dikecualikan dari Kebijakan Keamanan Konten Aplikasi Chrome, tetapi tidak dapat mengakses langsung API Aplikasi Chrome. Manifes juga menyertakan "socket" izin; aplikasi pemutar media menggunakan socket API 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"
            ]
        }
    ]
}

Halaman Create event

Semua Aplikasi Chrome memerlukan background.js untuk meluncurkan aplikasi. Halaman utama pemutar media, index.html, 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 yang ketat (CSP). Aplikasi media player memerlukan beberapa hak istimewa yang lebih tinggi untuk merender komponen Ext JS. Kepada mematuhi CSP dan menjalankan logika aplikasi, halaman utama aplikasi, index.html, membuat iframe yang bertindak 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 Ext JS aplikasi:

<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 JS Ekstra dan merender tampilan pemutar media. Karena langkah ini skrip di-sandbox, sehingga tidak dapat langsung mengakses API Aplikasi Chrome. Komunikasi antara app.js dan file yang tidak di-sandbox dilakukan menggunakan HTML5 Post Message API.

Berkomunikasi antar-file

Agar aplikasi pemutar media dapat mengakses API Aplikasi Chrome, seperti membuat kueri jaringan untuk media server, app.js akan memposting pesan ke index.js. Tidak seperti app.js yang di-sandbox, index.js dapat 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 yang 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, '*');
}

Temukan server media

Ada banyak hal yang perlu dilakukan untuk menemukan server media. Secara umum, alur kerja penemuan dimulai oleh tindakan pengguna untuk mencari server media yang tersedia. Pengontrol MediaServer memposting pesan ke index.js; index.js mendengarkan pesan ini dan saat diterima, panggilan Upnp.js.

Upnp library menggunakan socket API Aplikasi Chrome untuk menghubungkan aplikasi pemutar media dengan semua server media yang ditemukan dan menerima data media dari server media. Upnp.js juga menggunakan soapclient.js untuk mengurai data server media. Selanjutnya, bagian ini akan menjelaskan hal ini alur kerja Anda secara lebih detail.

Posting pesan

Saat pengguna mengklik tombol Server Media di bagian tengah aplikasi pemutar media, MediaServers.js memanggil discoverServers(). Fungsi ini terlebih dahulu memeriksa apakah ada permintaan penemuan yang belum diproses, dan jika true, membatalkan proses tersebut sehingga permintaan baru dapat dimulai. Selanjutnya, {i>controller<i} memposting pesan ke index.js dengan upnp-Discovery kunci, 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 'upnp-discover' pesan 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 menelusuri dan mengurai data server media; soapclient.js mengurai elemen media menurut nama tag ke dalam dokumen.

Hubungkan ke server media

Upnp.js terhubung ke server media yang ditemukan dan menerima data media menggunakan soket Aplikasi 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);
        });
    });
});

Menjelajahi dan memutar media

Pengontrol MediaExplorer mencantumkan semua file media di dalam folder server media dan bertanggung jawab untuk mengupdate navigasi breadcrumb di jendela aplikasi media player. Saat pengguna memilih file media, pengontrol memposting pesan ke index.js dengan parameter 'play-media' kunci:

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 mendengarkan 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 secara offline

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

Proses ini dimulai saat pengguna memilih satu atau beberapa file dan memulai 'Ambil offline' tindakan. Pengontrol MediaExplorer memposting pesan ke index.js dengan kunci 'download-media'; index.js 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 media panel Hierarki pemain.