Sencha Ext JS ile Uygulama Geliştirin

Bu dokümanın amacı, Sencha Ext JS çerçevesiyle Chrome Uygulamaları oluşturmaya başlamanızı sağlamaktır. Bu hedefe ulaşmak için Sencha tarafından geliştirilen bir medya oynatıcı uygulamasını kullanıma sunacağız. Kaynak kodu ve API Belgeleri, GitHub'da mevcuttur.

Bu uygulama, bilgisayara bağlı medya cihazları ve ağ üzerinden medyayı yöneten yazılım dahil olmak üzere kullanıcının kullanılabilir medya sunucularını keşfeder. Kullanıcılar medya içeriğine göz atabilir, ağ üzerinde oynatabilir veya çevrimdışına kaydedebilir.

Sencha Ext JS kullanarak medya oynatıcı uygulaması oluşturmak için yapmanız gereken önemli şeyler şunlardır:

  • Manifest oluşturun, manifest.json.
  • Etkinlik sayfasını oluşturun, background.js.
  • Sandbox uygulamasının mantığını kullanabilirsiniz.
  • Chrome uygulaması ile korumalı alana alınmış dosyalar arasında iletişim kurun.
  • Medya sunucularını keşfedin.
  • Medya içeriklerini keşfedip oynatın.
  • Medyayı çevrimdışı kaydedin.

Manifest oluştur

Tüm Chrome Uygulamaları, Chrome'un uygulamaları başlatmak için ihtiyaç duyduğu bilgileri içeren bir manifest dosyasına ihtiyaç duyar. Manifest'te belirtildiği gibi, medya oynatıcı uygulaması "offline_enabled"; medya öğeleri yerel olarak kaydedilebilir, bağlantıdan bağımsız olarak erişilebilir ve oynatılabilir.

"Korumalı alan" alanı, uygulamanın ana mantığını benzersiz bir kaynakta korumalı alana almak için kullanılır. Korumalı alana alınan tüm içerikler Chrome Uygulaması İçerik Güvenliği Politikası'ndan muaftır ancak Chrome Uygulama API'lerine doğrudan erişemez. Manifest "socket" iznini de içerir; medya oynatıcı uygulaması, ağ üzerinden bir medya sunucusuna bağlanmak için socket API'yi kullanır.

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

Etkinlik sayfası oluştur

Tüm Chrome Uygulamaları'nın uygulamayı başlatması için background.js gereklidir. Medya oynatıcının ana sayfası (index.html), belirtilen boyutlarla bir pencerede açılır:

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

});

Korumalı alan uygulamasının mantığı

Chrome Uygulamaları, katı bir İçerik Güvenliği Politikası (İGP) uygulayan kontrollü bir ortamda çalışır. Medya oynatıcı uygulamasının Ext JS bileşenlerini oluşturmak için bazı daha yüksek ayrıcalıklara ihtiyacı vardır. CSP'ye uymak ve uygulama mantığını yürütmek için uygulamanın ana sayfası index.html, korumalı alan ortamı görevi gören bir iframe oluşturur:

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

iframe, Ext JS uygulaması için gerekli dosyaları içeren sandbox.html dosyasını işaret eder:

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

app.js komut dosyası, tüm Ext JS kodunu yürütür ve medya oynatıcı görünümlerini oluşturur. Bu komut dosyası korumalı alan içerdiğinden, Chrome Uygulama API'lerine doğrudan erişemez. app.js dosyaları ile korumalı alanda olmayan dosyalar arasındaki iletişim HTML5 Post Message API kullanılarak yapılır.

Dosyalar arasında iletişim kurma

Medya oynatıcı uygulamasının, medya sunucuları için ağı sorgulama gibi Chrome Uygulama API'lerine erişebilmesi için app.js, mesajları index.js dosyasına yayınlar. Korumalı alana alınmış app.js uygulamasının aksine index.js, Chrome Uygulama API'lerine doğrudan erişebilir.

index.js iframe'i oluşturur:

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

iframeWindow = iframe.contentWindow;

Ayrıca, korumalı alana alınmış dosyalardaki iletileri dinler:

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

Aşağıdaki örnekte app.js, index.js adresine "extension-baseurl" anahtarını isteyen bir mesaj gönderir:

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

index.js isteği alır, sonucu atar ve Temel URL'yi geri göndererek yanıt verir:

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

Medya sunucularını keşfedin

Medya sunucularının keşfedilmesi için çok iş yapılması gerekir. Üst düzeyde, keşif iş akışı mevcut medya sunucularını aramak için bir kullanıcı işlemiyle başlatılır. MediaServer denetleyici, index.js adresine bir mesaj yayınlar; index.js bu mesajı dinler ve alındığında, Upnp.js komutunu çağırır.

Upnp library, medya oynatıcı uygulamasını keşfedilen medya sunucularına bağlamak ve medya sunucusundan medya verilerini almak için Chrome Uygulaması socket API'yi kullanır. Upnp.js, medya sunucusu verilerini ayrıştırmak için soapclient.js'yi de kullanır. Bu bölümün geri kalanında bu iş akışı daha ayrıntılı olarak açıklanmaktadır.

Mesajı yayınla

Bir kullanıcı, medya oynatıcı uygulamasının ortasındaki Medya Sunucuları düğmesini tıkladığında, MediaServers.js discoverServers() yöntemini çağırır. Bu işlev önce bekleyen keşif isteklerini kontrol eder ve doğruysa yeni isteğin başlatılabilmesi için bunları iptal eder. Ardından kumanda, bir anahtar upnp-discovery ve iki geri çağırma dinleyicisi ile index.js adlı cihaza bir mesaj yayınlar:

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

upnpKeşfet() işlevini çağırın

index.js, app.js tarafından gönderilen "upnp-discover" mesajını dinler ve upnpDiscover() çağrısıyla yanıt verir. Bir medya sunucusu bulunduğunda, index.js parametrelerden medya sunucusu alanını çıkarır, sunucuyu yerel olarak kaydeder, medya sunucusu verilerini biçimlendirir ve verileri MediaServer denetleyicisine aktarır.

Medya sunucusu verilerini ayrıştırma

Upnp.js yeni bir medya sunucusu bulduğunda cihazın açıklamasını alır ve medya sunucusu verilerine göz atıp verileri ayrıştırmak için bir Soap isteği gönderir. soapclient.js, medya öğelerini etiket adına göre bir belgede ayrıştırır.

Medya sunucusuna bağlan

Upnp.js, keşfedilen medya sunucularına bağlanır ve Chrome App socket API'yi kullanarak medya verilerini alır:

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

Medya keşfedip oynatın

MediaGezgin denetleyici, bir medya sunucusu klasöründeki tüm medya dosyalarını listeler ve medya oynatıcı uygulama penceresinde içerik haritası gezinmesini güncellemekten sorumludur. Kullanıcı bir medya dosyası seçtiğinde kumanda, "play-media" anahtarını içeren index.js ürününe bir mesaj yayınlar:

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 bu yayın mesajını dinler ve playMedia() numaralı telefonu arayarak yanıt verir:

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

Medyayı çevrimdışına kaydet

Medyayı çevrimdışına kaydetmek için gereken işlerin çoğu filer.js kitaplığı tarafından yapılır. Filer.js ile tanışın bölümünde bu kitaplıkla ilgili daha fazla bilgi edinebilirsiniz.

Bu süreç, kullanıcı bir veya daha fazla dosya seçip "Çevrimdışına al" işlemini başlattığında başlar. MediaGezgin denetleyici, index.js sitesine "download-media" anahtarıyla bir mesaj yayınlar; index.js bu mesajı dinler ve indirme işlemini başlatmak için downloadMedia() işlevini çağırır:

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

DownloadProcess yardımcı program yöntemi, medya sunucusundan veri almak için bir xhr isteği oluşturur ve tamamlanma durumunu bekler. Bu işlem, alınan içeriği kontrol eden ve filer.js işlevini kullanarak verileri yerel olarak kaydeden onload geri çağırmasını başlatır:

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

İndirme işlemi tamamlandığında MediaExplorer, medya dosyası listesini ve medya oynatıcı ağaç panelini günceller.