Tujuan dokumen ini adalah untuk membantu Anda mulai membuat Aplikasi Chrome dengan Sencha Ext JS Google Workspace for Education. Untuk mencapai tujuan ini, kita akan membahas aplikasi pemutar media yang dibuat oleh Sencha. Elemen sumber dan Dokumentasi API tersedia di GitHub.
Aplikasi ini menemukan server media yang tersedia milik pengguna, termasuk perangkat media yang terhubung ke komputer dan perangkat lunak yang mengelola media melalui jaringan. Pengguna dapat menjelajahi media, memutar melalui jaringan, atau menyimpan secara offline.
Berikut adalah hal-hal utama yang harus Anda lakukan untuk membuat aplikasi pemutar media menggunakan Sencha Ext JS:
- Buat manifes,
manifest.json
. - Buat halaman acara,
background.js
. - Logika aplikasi Sandbox.
- Berkomunikasi antara Aplikasi Chrome dan file yang di-sandbox.
- Temukan server media.
- Menjelajahi dan memutar media.
- Simpan media secara offline.
Membuat manifes
Semua Aplikasi Chrome memerlukan file manifes yang berisi informasi yang diperlukan Chrome untuk diluncurkan aplikasi. Seperti yang ditunjukkan dalam manifes, aplikasi pemutar media adalah "offline_enabled"; aset media dapat disimpan secara lokal, diakses, dan diputar terlepas dari konektivitas.
"Sandbox" digunakan untuk melakukan sandbox logika utama aplikasi di origin yang unik. Semua dalam sandbox konten dikecualikan dari Kebijakan Keamanan Konten Aplikasi Chrome, tetapi tidak dapat mengakses langsung API Aplikasi Chrome. Manifes juga menyertakan "socket" izin; aplikasi pemutar media menggunakan socket API 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"
]
}
]
}
Halaman Create event
Semua Aplikasi Chrome memerlukan background.js
untuk meluncurkan aplikasi. Halaman utama pemutar media,
index.html
, 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 yang ketat
(CSP). Aplikasi media player memerlukan beberapa hak istimewa yang lebih tinggi untuk merender komponen Ext JS. Kepada
mematuhi CSP dan menjalankan logika aplikasi, halaman utama aplikasi, index.html
, membuat iframe yang
bertindak 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 Ext JS aplikasi:
<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 JS Ekstra dan merender tampilan pemutar media. Karena langkah ini
skrip di-sandbox, sehingga tidak dapat langsung mengakses API Aplikasi Chrome. Komunikasi antara app.js
dan file yang tidak di-sandbox dilakukan menggunakan HTML5 Post Message API.
Berkomunikasi antar-file
Agar aplikasi pemutar media dapat mengakses API Aplikasi Chrome, seperti membuat kueri jaringan untuk media
server, app.js
akan memposting pesan ke index.js. Tidak seperti app.js
yang di-sandbox, index.js
dapat
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
yang 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, '*');
}
Temukan server media
Ada banyak hal yang perlu dilakukan
untuk menemukan server media. Secara umum, alur kerja penemuan
dimulai oleh tindakan pengguna untuk
mencari server media yang tersedia. Pengontrol MediaServer
memposting pesan ke index.js
; index.js
mendengarkan pesan ini dan saat diterima, panggilan
Upnp.js.
Upnp library
menggunakan socket API Aplikasi Chrome untuk menghubungkan aplikasi pemutar media dengan semua
server media yang ditemukan dan
menerima data media dari server media. Upnp.js
juga menggunakan
soapclient.js untuk mengurai data server media. Selanjutnya, bagian ini akan menjelaskan hal ini
alur kerja Anda secara lebih detail.
Posting pesan
Saat pengguna mengklik tombol Server Media di bagian tengah aplikasi pemutar media, MediaServers.js
memanggil discoverServers()
. Fungsi ini terlebih dahulu memeriksa apakah ada permintaan penemuan yang belum diproses, dan jika
true, membatalkan proses tersebut sehingga permintaan baru dapat dimulai. Selanjutnya, {i>controller<i}
memposting pesan ke
index.js
dengan upnp-Discovery kunci, 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 'upnp-discover' pesan 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 menelusuri dan mengurai data server media; soapclient.js
mengurai elemen media
menurut nama tag
ke dalam dokumen.
Hubungkan ke server media
Upnp.js
terhubung ke server media yang ditemukan dan menerima data media menggunakan soket Aplikasi Chrome
API:
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);
});
});
});
Menjelajahi dan memutar media
Pengontrol MediaExplorer mencantumkan semua file media di dalam folder server media dan
bertanggung jawab untuk mengupdate navigasi breadcrumb di jendela aplikasi media player. Saat pengguna
memilih file media, pengontrol memposting pesan ke index.js
dengan parameter 'play-media' kunci:
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
mendengarkan 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 secara offline
Sebagian besar kerja keras untuk menyimpan media secara offline dilakukan oleh library filer.js. Anda dapat membaca selengkapnya library ini di Memperkenalkan filer.js.
Proses ini dimulai saat pengguna memilih satu atau beberapa file dan memulai 'Ambil offline' tindakan.
Pengontrol MediaExplorer memposting pesan ke index.js
dengan kunci 'download-media';
index.js
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 media
panel Hierarki pemain.