Tạo ứng dụng bằng Sencha Ext JS

Mục tiêu của tài liệu này là giúp bạn bắt đầu xây dựng Ứng dụng Chrome bằng khung Sencha Ext JS. Để đạt được mục tiêu này, chúng ta sẽ tìm hiểu về ứng dụng trình phát nội dung đa phương tiện do Sencha tạo ra. Mã nguồnTài liệu API có trên GitHub.

Ứng dụng này phát hiện các máy chủ nội dung nghe nhìn có sẵn của người dùng, bao gồm cả thiết bị đa phương tiện kết nối với máy tính và phần mềm quản lý nội dung nghe nhìn qua mạng. Người dùng có thể duyệt qua nội dung đa phương tiện, phát qua mạng hoặc lưu khi không có mạng.

Sau đây là những việc chính bạn cần làm để tạo ứng dụng trình phát nội dung đa phương tiện bằng Sencha Ext JS:

  • Tạo tệp kê khai, manifest.json.
  • Tạo trang sự kiện, background.js.
  • Logic của ứng dụng Sandbox.
  • Giao tiếp giữa ứng dụng Chrome và các tệp có hộp cát.
  • Khám phá các máy chủ phương tiện.
  • Khám phá và phát nội dung nghe nhìn.
  • Lưu phương tiện để xem khi không có mạng.

Tạo tệp kê khai

Tất cả các Ứng dụng Chrome đều yêu cầu phải có một tệp kê khai chứa thông tin mà Chrome cần để khởi chạy ứng dụng. Như đã nêu trong tệp kê khai, ứng dụng trình phát nội dung đa phương tiện có trạng thái "offline_enabled"; các thành phần nội dung đa phương tiện có thể được lưu cục bộ, truy cập và phát bất kể kết nối.

Trường "sandbox" (hộp cát) dùng để tạo hộp cát cho logic chính của ứng dụng ở một nguồn gốc riêng biệt. Tất cả nội dung trong hộp cát đều được miễn tuân theo Chính sách bảo mật nội dung của ứng dụng Chrome, nhưng không thể truy cập trực tiếp vào API Ứng dụng Chrome. Tệp kê khai cũng bao gồm quyền "socket"; ứng dụng đa phương tiện sử dụng API ổ cắm để kết nối với một máy chủ đa phương tiện qua mạng.

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

Tạo trang sự kiện

Tất cả các Ứng dụng Chrome đều yêu cầu background.js để chạy ứng dụng. Trang chính của trình phát nội dung đa phương tiện, index.html, sẽ mở trong một cửa sổ có kích thước đã chỉ định:

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

});

Logic của ứng dụng hộp cát

Ứng dụng Chrome chạy trong một môi trường được kiểm soát có thực thi một Chính sách bảo mật nội dung (CSP) nghiêm ngặt. Ứng dụng trình phát nội dung đa phương tiện cần một số đặc quyền cao hơn để hiển thị các thành phần Ext JS. Để tuân thủ CSP và thực thi logic của ứng dụng, trang chính của ứng dụng, index.html, sẽ tạo một iframe hoạt động như một môi trường hộp cát:

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

iframe này trỏ đến sandbox.html bao gồm các tệp cần thiết cho ứng dụng 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>

Tập lệnh app.js thực thi tất cả mã Ext JS và hiển thị chế độ xem của trình phát nội dung đa phương tiện. Vì tập lệnh này ở dạng hộp cát nên tập lệnh này không thể truy cập trực tiếp vào API Ứng dụng Chrome. Hoạt động giao tiếp giữa app.js và các tệp không có hộp cát được thực hiện bằng HTML5 Post Message API.

Giao tiếp giữa các tệp

Để ứng dụng trình phát nội dung đa phương tiện truy cập các API ứng dụng Chrome, chẳng hạn như truy vấn mạng cho máy chủ nội dung nghe nhìn, app.js sẽ đăng thông báo lên index.js. Không giống như app.js dạng hộp cát, index.js có thể truy cập trực tiếp vào các API Ứng dụng Chrome.

index.js tạo iframe:

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

iframeWindow = iframe.contentWindow;

Và lắng nghe thư từ các tệp hộp cát:

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

Trong ví dụ sau, app.js gửi một thông báo đến index.js để yêu cầu khoá "extension-baseurl":

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

index.js nhận yêu cầu, chỉ định kết quả và trả lời bằng cách gửi lại URL cơ sở:

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

Khám phá các máy chủ phương tiện

Có rất nhiều việc cần làm để khám phá máy chủ đa phương tiện. Ở cấp độ cao, quy trình khám phá là do một hành động của người dùng bắt đầu để tìm kiếm các máy chủ nội dung nghe nhìn có sẵn. Bộ điều khiển MediaServer đăng thông báo lên index.js; index.js sẽ theo dõi thông báo này và khi nhận được, hãy gọi Upnp.js.

Upnp library sử dụng API ổ cắm của ứng dụng Chrome để kết nối ứng dụng trình phát nội dung đa phương tiện với mọi máy chủ nội dung nghe nhìn đã phát hiện và nhận dữ liệu nội dung nghe nhìn từ máy chủ nội dung nghe nhìn. Upnp.js cũng sử dụng soapclient.js để phân tích cú pháp dữ liệu máy chủ nội dung đa phương tiện. Phần còn lại của phần này sẽ mô tả chi tiết hơn về quy trình này.

Đăng tin nhắn

Khi người dùng nhấp vào nút Media Servers ở giữa ứng dụng trình phát nội dung đa phương tiện, MediaServers.js sẽ gọi discoverServers(). Trước tiên, hàm này sẽ kiểm tra mọi yêu cầu khám phá chưa xử lý. Nếu đúng, hãy huỷ các yêu cầu đó để có thể bắt đầu yêu cầu mới. Tiếp theo, bộ điều khiển sẽ đăng một thông báo tới index.js kèm theo một key upnp-discovery và 2 trình nghe lệnh gọi lại:

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

Gọi upnpExplore()

index.js theo dõi thông báo "upnp-discovery" từ app.js và phản hồi bằng cách gọi upnpDiscover(). Khi phát hiện máy chủ nội dung đa phương tiện, index.js sẽ trích xuất miền máy chủ nội dung nghe nhìn từ các tham số, lưu máy chủ cục bộ, định dạng dữ liệu máy chủ nội dung nghe nhìn và đẩy dữ liệu đến bộ điều khiển MediaServer.

Phân tích cú pháp dữ liệu máy chủ đa phương tiện

Khi phát hiện thấy một máy chủ nội dung đa phương tiện mới, Upnp.js sẽ truy xuất nội dung mô tả về thiết bị và gửi Soaprequest để duyệt qua và phân tích cú pháp dữ liệu máy chủ nội dung đa phương tiện; soapclient.js phân tích cú pháp các thành phần nội dung đa phương tiện theo tên thẻ thành một tài liệu.

Kết nối với máy chủ phương tiện

Upnp.js kết nối với các máy chủ nội dung nghe nhìn đã phát hiện và nhận dữ liệu nội dung nghe nhìn bằng API Chrome App socket:

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

Khám phá và phát nội dung nghe nhìn

Trình điều khiển Media Explorer liệt kê tất cả tệp nội dung nghe nhìn bên trong một thư mục máy chủ nội dung đa phương tiện và chịu trách nhiệm cập nhật thao tác điều hướng breadcrumb (tập hợp liên kết phân cấp) trong cửa sổ ứng dụng trình phát nội dung đa phương tiện. Khi người dùng chọn một tệp nội dung nghe nhìn, tay điều khiển sẽ đăng thông báo tới index.js bằng phím "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 theo dõi tin nhắn bài đăng này và trả lời bằng cách gọi 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);
}

Lưu nội dung nghe nhìn để xem khi không có mạng

Hầu hết những công việc khó khăn để lưu phương tiện ngoại tuyến là do thư viện filer.js thực hiện. Bạn có thể đọc thêm thư viện này trong phần Giới thiệu filer.js.

Quá trình này bắt đầu khi người dùng chọn một hoặc nhiều tệp và bắt đầu hành động "Tắt". Bộ điều khiển Media Explorer sẽ đăng thông báo lên index.js bằng khoá "download-media"; index.js lắng nghe thông báo này và gọi hàm downloadMedia() để bắt đầu quá trình tải xuống:

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

Phương thức tiện ích DownloadProcess tạo một yêu cầu xhr để lấy dữ liệu từ máy chủ nội dung đa phương tiện và chờ trạng thái hoàn thành. Thao tác này sẽ bắt đầu lệnh gọi lại onload để kiểm tra nội dung đã nhận và lưu dữ liệu cục bộ bằng cách sử dụng hàm 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);
    }
);

Khi quá trình tải xuống hoàn tất, MediaExplorer sẽ cập nhật danh sách tệp nội dung đa phương tiện và bảng điều khiển cây trình phát nội dung đa phương tiện.