מטרת המסמך הזה היא לעזור לך להתחיל לפתח אפליקציות Chrome באמצעות Sencha Ext JS . כדי להשיג את המטרה הזו, נתעמק באפליקציה של נגן מדיה שפותחה על ידי Sencha. הקוד code ומסמכי תיעוד API זמינים ב-GitHub.
האפליקציה הזו מזהה את שרתי המדיה הזמינים של המשתמש, כולל מכשירי מדיה שמחוברים למחשב וגם תוכנה שמנהלת מדיה דרך הרשת. המשתמשים יכולים לדפדף במדיה, להפעיל תכנים דרך הרשת או לשמור במצב אופליין.
אלה הפעולות העיקריות שצריך לעשות כדי ליצור אפליקציה של נגן מדיה באמצעות Sencha Ext JS:
- יצירת מניפסט,
manifest.json
. - יצירת דף אירוע,
background.js
. - לוגיקת האפליקציה Sandbox.
- תקשורת בין אפליקציית Chrome לקבצים בארגז חול (sandbox).
- מגלים שרתי מדיה.
- חיפוש והפעלה של מדיה.
- שמירת המדיה במצב אופליין.
יצירת מניפסט
לכל אפליקציות Chrome נדרש קובץ מניפסט שמכיל את המידע שדרוש ל-Chrome כדי להפעיל באפליקציות. כפי שמצוין במניפסט, האפליקציה של נגן המדיה במצב "אופליין_enabled"; נכסי מדיה יכולים להיות נשמר באופן מקומי, ניגש אליו ומופעל ללא קשר לקישוריות.
את "ארגז החול" משמש להרצה בארגז חול ללוגיקה הראשית של האפליקציה במקור ייחודי. כל ההרצה בארגז החול התוכן פטור ממדיניות אבטחת התוכן של אפליקציית Chrome, אבל הוא לא יכול לגשת ישירות ממשקי API של אפליקציות 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;
});
});
הלוגיקה של אפליקציית Sandbox
אפליקציות 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 ומעבד את התצוגות של נגן המדיה. מאחר ש
הסקריפט נמצא בארגז חול (sandbox), הוא לא יכול לגשת ישירות לממשקי ה-API של אפליקציות Chrome. התקשורת בין app.js
וקבצים שאינם בארגז חול (sandbox) מתבצעים באמצעות HTML5 Post Message API.
תקשורת בין קבצים
כדי שאפליקציית נגן המדיה תוכל לגשת לממשקי API של אפליקציות Chrome, כמו שליחת שאילתות לרשת לגבי מדיה
שרתים, app.js
מפרסם הודעות ב-index.js. בניגוד ל-app.js
שבארגז החול, index.js
יכול
לגשת ישירות לממשקי ה-API של אפליקציות Chrome.
ה-iframe נוצר על ידי index.js
:
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-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-discovery' הודעה מאת app.js
ותתבצע שיחה
upnpDiscover()
. כשמתגלה שרת מדיה, index.js
מחלץ את הדומיין של שרת המדיה
מהפרמטרים, שומר את השרת באופן מקומי, מעצב את הנתונים של שרת המדיה ודוחף את הנתונים
הבקר של MediaServer
.
ניתוח של נתוני שרת המדיה
כש-Upnp.js
מגלה שרת מדיה חדש, הוא מאחזר תיאור של המכשיר ושולח
בקשת Soaprequest לעיון בנתוני שרת המדיה ולניתוח שלהם; soapclient.js
מנתח את רכיבי המדיה
לפי שם התג במסמך.
התחברות לשרת מדיה
Upnp.js
מתחבר לשרתי המדיה שהתגלו ומקבלים נתוני מדיה באמצעות שקע אפליקציית 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);
});
});
});
חיפוש והפעלה של מדיה
הבקר של MediaExplorer מציג את כל קובצי המדיה שנמצאים בתיקיית שרת המדיה,
אחראי לעדכון הניווט של נתיבי הניווט בחלון של אפליקציית המדיה של Google. כשמשתמש
בוחר קובץ מדיה, הבקר מפרסם הודעה אל 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 כדי לקבל נתונים משרת המדיה
ממתין לסטטוס ההשלמה. מופעלת קריאה חוזרת (callback) של onload שבודקת את התוכן שהתקבל
ושומר את הנתונים באופן מקומי באמצעות הפונקציה 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
מעדכן את רשימת קובצי המדיה ואת פריט המדיה
חלונית עץ הנגן.