تصميم تطبيقات باستخدام Sencha Ext JS

يهدف هذا المستند إلى مساعدتك على البدء في إنشاء تطبيقات Chrome باستخدام Sencha Ext JS. إطار العمل. ولتحقيق هذا الهدف، سنتعمق في تطبيق مشغّل وسائط من تصميم Sencha. يحدد المصدر ومستندات واجهة برمجة التطبيقات على GitHub.

يكتشف هذا التطبيق خوادم الوسائط المتاحة للمستخدم، بما في ذلك أجهزة الوسائط المتصلة بجهاز الكمبيوتر برنامج يدير الوسائط عبر الشبكة. يمكن للمستخدمين تصفُّح الوسائط أو تشغيلها عبر الشبكة أو حفظها بلا اتصال بالإنترنت.

في ما يلي الخطوات الأساسية التي يجب اتّباعها لإنشاء تطبيق مشغّل وسائط باستخدام Sencha Ext JS:

  • إنشاء بيان، manifest.json
  • إنشاء صفحة حدث، background.js.
  • منطق تطبيق وضع الحماية.
  • التواصل بين تطبيق Chrome والملفات في وضع الحماية.
  • تعرَّف على خوادم الوسائط.
  • استكشاف الوسائط وتشغيلها
  • حفظ الوسائط بلا اتصال بالإنترنت

إنشاء بيان

تتطلب جميع تطبيقات Chrome ملف بيان يحتوي على المعلومات التي يحتاج Chrome لتشغيلها التطبيقات. كما هو موضح في البيان، فإن تطبيق مشغّل الوسائط هو "offline_enabled"؛ مواد عرض الوسائط ويتم حفظها محليًا، ويمكن الوصول إليها وتشغيلها بغض النظر عن الاتصال.

"وضع الحماية" يُستخدم لوضع الحماية للمنطق الرئيسي للتطبيق في مصدر فريد. تم وضع الحماية محتوى إعفاء من سياسة أمان المحتوى لتطبيقات Chrome، ولكن لا يمكنه الوصول مباشرةً إلى واجهات برمجة التطبيقات لتطبيقات Chrome. يتضمن البيان أيضًا "socket" إذن يستخدم تطبيق مشغّل الوسائط 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). يحتاج تطبيق مشغّل الوسائط إلى بعض الامتيازات الأعلى لعرض مكوّنات JavaScript الإضافية. إلى الالتزام بسياسة CSP وتنفيذ منطق التطبيق، حيث تنشئ الصفحة الرئيسية للتطبيق، index.html، إطار iframe بمثابة بيئة وضع الحماية:

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

يشير إطار iframe إلى sandbox.html يتضمن الملفات المطلوبة لامتداد JavaScript JS. app:

<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 كل رموز JavaScript JS ويعرض مشاهدات مشغّل الوسائط. نظرًا لأن ذلك في وضع الحماية للنص البرمجي، لا يمكنه الوصول مباشرةً إلى واجهات برمجة تطبيقات تطبيق Chrome. الاتصالات بين app.js والملفات التي لم يتم وضع الحماية لها باستخدام واجهة برمجة تطبيقات Post Message5 بتنسيق HTML5.

التواصل بين الملفات

من أجل وصول تطبيق مشغّل الوسائط إلى واجهات برمجة تطبيقات Chrome App، مثل الاستعلام عن الوسائط في الشبكة الخوادم، app.js ينشر الرسائل إلى index.js. على عكس app.js الذي تم وضع الحماية له، يمكن لـ index.js الوصول مباشرةً إلى واجهات برمجة تطبيقات تطبيقات Chrome.

ينشئ index.js إطار iframe:

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

iframeWindow = iframe.contentWindow;

ويستمع إلى الرسائل من الملفات في وضع الحماية:

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 رسالة إلى index.js تطلب المفتاح. "extension-baseurl":

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 واجهة برمجة التطبيقات socket API لتطبيقات Chrome لربط تطبيق مشغّل الوسائط بأي مما يلي: خوادم وسائط جديدة وتستقطب بيانات الوسائط من خادم الوسائط. يستخدم Upnp.js أيضًا soapclient.js لتحليل بيانات خادم الوسائط. يصف باقي هذا القسم هذا وسير العمل بمزيد من التفصيل.

نشر الرسالة

عندما ينقر أحد المستخدمين على الزر "خوادم الوسائط" في منتصف تطبيق مشغّل الوسائط، MediaServers.js يتصل بـ discoverServers(). تفحص هذه الدالة أولاً أي طلبات استكشاف معلّقة، وما إذا true، يتم إلغاؤها كي يمكن بدء الطلب الجديد. بعد ذلك، تنشر وحدة التحكم رسالة إلى index.js باستخدام مفتاح upnp ديسكفري، بالإضافة إلى اثنين من أدوات استماع لمعاودة الاتصال:

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-detect" رسالة من app.js ويتم الرد من خلال الاتصال upnpDiscover() عندما يتم اكتشاف خادم وسائط، يستخرج index.js نطاق خادم الوسائط. من المعلمات، ويحفظ الخادم محليًا، وينسق بيانات خادم الوسائط، ويدفع البيانات إلى وحدة التحكم MediaServer.

تحليل بيانات خادم الوسائط

عندما يكتشف "Upnp.js" خادم وسائط جديدًا، يسترد بعد ذلك وصفًا للجهاز ويرسل طلب Soaprequest لتصفّح بيانات خادم الوسائط وتحليلها تحليل soapclient.js لعناصر الوسائط حسب اسم العلامة في مستند.

الاتصال بخادم الوسائط

يتصل Upnp.js بخوادم الوسائط المكتشفة ويتلقى بيانات الوسائط باستخدام مقبس تطبيق 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 جميع ملفات الوسائط داخل مجلد خادم الوسائط المسؤول عن تحديث شريط التنقّل في نافذة تطبيق مشغّل الوسائط عندما يختار المستخدم ملف وسائط، وستنشر وحدة التحكّم رسالة إلى index.js تحتوي على عبارة "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" سماع رسالة المشاركة هذه ويردّ من خلال الاتصال بـ 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 رسالة إلى index.js تحتوي على مفتاح "download-media". ينتظر 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" قائمة ملفات الوسائط والوسائط. لوحة شجرة اللاعبين.