ساخت اپلیکیشن با Sencha Ext JS

هدف این سند این است که شما را در ساخت برنامه های Chrome با چارچوب Sencha Ext JS شروع کنید. برای دستیابی به این هدف، ما به یک برنامه پخش کننده رسانه ساخته شده توسط Sencha شیرجه می‌زنیم. کد منبع و اسناد API در GitHub در دسترس هستند.

این برنامه سرورهای رسانه ای موجود کاربر را کشف می کند، از جمله دستگاه های رسانه متصل به رایانه شخصی و نرم افزاری که رسانه ها را از طریق شبکه مدیریت می کند. کاربران می توانند رسانه ها را مرور کنند، از طریق شبکه بازی کنند یا به صورت آفلاین ذخیره کنند.

در اینجا کارهای کلیدی که باید برای ساخت یک برنامه پخش کننده رسانه با استفاده از Sencha Ext JS انجام دهید آمده است:

  • manifest, manifest.json را ایجاد کنید.
  • ایجاد صفحه رویداد ، background.js .
  • منطق برنامه سندباکس
  • بین برنامه Chrome و فایل‌های جعبه ایمنی ارتباط برقرار کنید.
  • سرورهای رسانه را کشف کنید
  • کاوش و پخش رسانه
  • ذخیره رسانه به صورت آفلاین

مانیفست ایجاد کنید

همه برنامه‌های Chrome به یک فایل مانیفست نیاز دارند که حاوی اطلاعاتی باشد که Chrome برای راه‌اندازی برنامه‌ها نیاز دارد. همانطور که در مانیفست نشان داده شده است، برنامه پخش کننده رسانه "offline_enabled" است. دارایی های رسانه را می توان به صورت محلی ذخیره کرد، بدون توجه به اتصال، به آنها دسترسی پیدا کرد و پخش کرد.

فیلد "sandbox" برای سندباکس منطق اصلی برنامه در یک مبدا منحصر به فرد استفاده می شود. همه محتوای جعبه ایمنی از خط‌مشی امنیتی محتوای برنامه Chrome مستثنی هستند، اما نمی‌توانند مستقیماً به APIهای برنامه Chrome دسترسی داشته باشند. مانیفست همچنین شامل مجوز "socket" است. برنامه پخش کننده رسانه از سوکت API برای اتصال به سرور رسانه از طریق شبکه استفاده می کند.

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

ایجاد صفحه رویداد

همه برنامه‌های Chrome برای راه‌اندازی برنامه background.js نیاز دارند. صفحه اصلی پخش کننده رسانه، index.html ، در پنجره ای با ابعاد مشخص شده باز می شود:

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

});

منطق برنامه سندباکس

برنامه‌های Chrome در یک محیط کنترل‌شده اجرا می‌شوند که یک خط‌مشی امنیتی محتوا (CSP) سختگیرانه را اعمال می‌کند. برنامه پخش کننده رسانه برای ارائه مولفه های Ext JS به امتیازات بالاتری نیاز دارد. برای مطابقت با CSP و اجرای منطق برنامه، صفحه اصلی برنامه، index.html ، یک iframe ایجاد می کند که به عنوان یک محیط sandbox عمل می کند:

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

iframe به sandbox.html اشاره می کند که شامل فایل های مورد نیاز برای برنامه 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>

اسکریپت app.js تمام کدهای Ext JS را اجرا می کند و نماهای پخش کننده رسانه را ارائه می دهد. از آنجایی که این اسکریپت دارای جعبه ایمنی است، نمی‌تواند مستقیماً به APIهای برنامه Chrome دسترسی داشته باشد. ارتباط بین app.js و فایل‌های بدون سندباکس با استفاده از HTML5 Post Message API انجام می‌شود.

ارتباط بین فایل ها

برای اینکه برنامه پخش کننده رسانه به APIهای برنامه Chrome دسترسی پیدا کند، مانند جستجو در شبکه برای سرورهای رسانه، app.js پیام‌هایی را به index.js پست می‌کند. برخلاف sandboxed app.js ، index.js می‌تواند مستقیماً به APIهای برنامه Chrome دسترسی داشته باشد.

index.js iframe را ایجاد می کند:

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

iframeWindow = iframe.contentWindow;

و به پیام‌های فایل‌های 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);

در مثال زیر، app.js با درخواست کلید «extension-baseurl» پیامی به index.js ارسال می‌کند:

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

index.js درخواست را دریافت می‌کند، نتیجه را اختصاص می‌دهد و با ارسال URL پایه به آن پاسخ می‌دهد:

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

سرورهای رسانه را کشف کنید

چیزهای زیادی برای کشف سرورهای رسانه وجود دارد. در سطح بالا، گردش کار کشف با یک اقدام کاربر برای جستجوی سرورهای رسانه ای موجود آغاز می شود. کنترلر MediaServer یک پیام به index.js ارسال می کند. index.js به این پیام گوش می دهد و پس از دریافت، Upnp.js را فرا می خواند.

Upnp library از API سوکت برنامه Chrome برای اتصال برنامه پخش کننده رسانه با سرورهای رسانه کشف شده و دریافت داده های رسانه از سرور رسانه استفاده می کند. Upnp.js همچنین از soapclient.js برای تجزیه داده های سرور رسانه استفاده می کند. بقیه این بخش این گردش کار را با جزئیات بیشتری توضیح می دهد.

ارسال پیام

وقتی کاربر روی دکمه Media Servers در مرکز برنامه پخش کننده رسانه کلیک می‌کند، MediaServers.js discoverServers() فرا می‌خواند. این تابع ابتدا درخواست‌های کشف برجسته را بررسی می‌کند و اگر درست باشد، آنها را لغو می‌کند تا درخواست جدید آغاز شود. سپس، کنترلر یک پیام به index.js با کلید upnp-discovery و دو شنونده پاسخ به تماس ارسال می کند:

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

فراخوانی upnpDiscover()

index.js به پیام 'upnp-discover' از app.js گوش می دهد و با فراخوانی upnpDiscover() پاسخ می دهد. هنگامی که یک سرور رسانه کشف می شود، index.js دامنه سرور رسانه را از پارامترها استخراج می کند، سرور را به صورت محلی ذخیره می کند، داده های سرور رسانه را قالب بندی می کند و داده ها را به کنترل کننده MediaServer فشار می دهد.

تجزیه و تحلیل داده های سرور رسانه

هنگامی که Upnp.js یک سرور رسانه جدید را کشف می کند، سپس شرحی از دستگاه را بازیابی می کند و درخواست Soap را برای مرور و تجزیه داده های سرور رسانه ارسال می کند. soapclient.js عناصر رسانه را با نام تگ در یک سند تجزیه می کند.

به سرور رسانه متصل شوید

Upnp.js با استفاده از API سوکت برنامه 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);
        });
    });
});

کاوش و پخش رسانه

کنترلر MediaExplorer تمام فایل های رسانه ای را در پوشه سرور رسانه فهرست می کند و مسئول به روز رسانی پیمایش خرده نان در پنجره برنامه پخش کننده رسانه است. هنگامی که کاربر یک فایل رسانه ای را انتخاب می کند، کنترل کننده با کلید "play-media" پیامی را به index.js ارسال می کند:

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 به این پیام پست گوش می دهد و با فراخوانی 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);
}

ذخیره رسانه به صورت آفلاین

بیشتر کار سخت برای ذخیره رسانه به صورت آفلاین توسط کتابخانه filer.js انجام می شود. می‌توانید این کتابخانه را در معرفی filer.js بیشتر بخوانید.

این فرآیند زمانی شروع می شود که کاربر یک یا چند فایل را انتخاب کرده و اقدام "در حالت آفلاین" را آغاز کند. کنترلر MediaExplorer پیامی را با کلید "download-media" به index.js ارسال می کند. index.js به این پیام گوش می دهد و تابع downloadMedia() را برای شروع فرآیند دانلود فراخوانی می کند:

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

متد ابزار DownloadProcess یک درخواست xhr برای دریافت داده‌ها از سرور رسانه ایجاد می‌کند و منتظر وضعیت تکمیل می‌شود. با این کار بازخوانی بارگذاری شده آغاز می شود که محتوای دریافتی را بررسی می کند و داده ها را به صورت محلی با استفاده از تابع 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);
    }
);

هنگامی که فرآیند دانلود به پایان رسید، MediaExplorer لیست فایل های رسانه و پانل درختی پخش کننده رسانه را به روز می کند.