Tujuan dokumen ini adalah membantu Anda mulai membangun Aplikasi Chrome dengan framework Sencha Ext JS. Untuk mencapai tujuan ini, kita akan mempelajari aplikasi media player yang di-build oleh Sencha. Kode sumber dan Dokumentasi API tersedia di GitHub.
Aplikasi ini menemukan server media yang tersedia milik pengguna, termasuk perangkat media yang terhubung ke komputer dan software yang mengelola media melalui jaringan. Pengguna dapat menjelajahi media, memutarnya melalui jaringan, atau menyimpan secara offline.
Berikut adalah hal-hal penting yang harus Anda lakukan untuk membangun aplikasi media player menggunakan Sencha Ext JS:
- Buat manifes,
manifest.json
. - Buat halaman acara,
background.js
. - Logika aplikasi Sandbox.
- Berkomunikasi antara Aplikasi Chrome dan file dalam sandbox.
- Temukan server media.
- Menjelajahi dan memutar media.
- Simpan media ke offline.
Buat manifes
Semua Aplikasi Chrome memerlukan file manifes yang berisi informasi yang diperlukan Chrome untuk meluncurkan aplikasi. Seperti yang ditunjukkan dalam manifes, aplikasi pemutar media ditetapkan ke "offline_enabled"; aset media dapat disimpan secara lokal, diakses, dan diputar terlepas dari konektivitas.
Kolom "sandbox" digunakan untuk melakukan sandbox logika utama aplikasi di asal yang unik. Semua konten yang di-sandbox dikecualikan dari Kebijakan Keamanan Konten Aplikasi Chrome, tetapi tidak dapat mengakses API Aplikasi Chrome secara langsung. Manifes ini juga menyertakan izin "soket"; aplikasi media player menggunakan API soket 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"
]
}
]
}
Buat halaman acara
Semua Aplikasi Chrome memerlukan background.js
untuk meluncurkan aplikasi. Halaman utama pemutar media,
index.html
, akan 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 (CSP) yang ketat. Aplikasi pemutar media memerlukan hak istimewa yang lebih tinggi untuk merender komponen Ext JS. Untuk mematuhi CSP dan menjalankan logika aplikasi, halaman utama aplikasi, index.html
, membuat iframe yang berfungsi 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 aplikasi 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>
Skrip app.js mengeksekusi semua kode Ext JS dan merender tampilan pemutar media. Karena skrip ini di-sandbox, skrip tidak dapat mengakses API Aplikasi Chrome secara langsung. Komunikasi antara app.js
dan file yang bukan sandbox dilakukan menggunakan HTML5 Post Message API.
Berkomunikasi antar-file
Agar aplikasi pemutar media dapat mengakses API Aplikasi Chrome, seperti membuat kueri jaringan untuk server
media, app.js
memposting pesan ke index.js. Tidak seperti app.js
dengan sandbox, index.js
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
untuk 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, '*');
}
Menemukan server media
Ada banyak hal yang diperlukan
untuk menemukan server media. Pada level tinggi, alur kerja penemuan
dimulai oleh tindakan pengguna untuk menelusuri server media yang tersedia. Pengontrol MediaServer
memposting pesan ke index.js
; index.js
akan memproses pesan ini, dan memanggil
Upnp.js saat diterima.
Upnp library
menggunakan API soket Aplikasi Chrome untuk menghubungkan aplikasi pemutar media dengan
server media yang ditemukan dan menerima data media dari server media. Upnp.js
juga menggunakan
soapclient.js untuk mengurai data server media. Bagian selanjutnya menjelaskan
alur kerja ini secara lebih mendetail.
Posting pesan
Saat pengguna mengklik tombol Server Media di bagian tengah aplikasi pemutar media, MediaServers.js
akan memanggil discoverServers()
. Fungsi ini akan memeriksa terlebih dahulu apakah ada permintaan penemuan yang belum terselesaikan, dan jika benar, akan membatalkannya agar permintaan baru dapat dimulai. Selanjutnya, pengontrol memposting pesan ke index.js
dengan key upnp-discovery, 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 pesan 'upnp-discover' 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 menjelajahi serta mengurai data server media; soapclient.js
akan mengurai elemen media
berdasarkan nama tag ke dalam dokumen.
Hubungkan ke server media
Upnp.js
terhubung ke server media yang ditemukan dan menerima data media menggunakan API soket Aplikasi
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);
});
});
});
Jelajahi dan putar media
Pengontrol MediaExplorer mencantumkan semua file media dalam folder server media dan bertanggung jawab untuk memperbarui navigasi breadcrumb di jendela aplikasi pemutar media. Saat pengguna
memilih file media, pengontrol akan memposting pesan ke index.js
dengan kunci '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
memproses 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 ke offline
Sebagian besar pekerjaan keras untuk menyimpan media secara offline dilakukan oleh library filer.js. Anda dapat membaca library ini selengkapnya dalam Memperkenalkan filer.js.
Proses dimulai saat pengguna memilih satu atau beberapa file dan memulai tindakan 'Jadikan offline'.
Pengontrol MediaExplorer memposting pesan ke index.js
dengan kunci 'download-media';
index.js
akan 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 panel
hierarki pemutar media.