الهدف من هذا المستند هو مساعدتك على البدء في إنشاء تطبيقات Chrome باستخدام إطار عمل Sencha Ext JS. ولتحقيق هذا الهدف، سوف نتعمق في تطبيق مشغّل الوسائط الذي أنشأه سينشا. يتوفّر رمز المصدر ووثائق واجهة برمجة التطبيقات على GitHub.
يكتشف هذا التطبيق خوادم الوسائط المتاحة للمستخدم، بما في ذلك أجهزة الوسائط المتصلة بالكمبيوتر والبرامج التي تدير الوسائط عبر الشبكة. يمكن للمستخدمين تصفح الوسائط أو تشغيلها عبر الشبكة أو حفظها في وضع عدم الاتصال.
في ما يلي الخطوات الأساسية التي يجب تنفيذها لإنشاء تطبيق مشغّل وسائط باستخدام Sencha Ext JS:
- إنشاء ملف البيان،
manifest.json
. - إنشاء صفحة الحدث،
background.js
. - منطق تطبيق Sandbox.
- الاتصال بين تطبيق Chrome والملفات المحمية في وضع الحماية
- اكتشاف خوادم الوسائط
- استكشاف الوسائط وتشغيلها
- حفظ الوسائط بلا اتصال بالإنترنت
إنشاء البيان
تتطلب جميع تطبيقات Chrome ملف بيان يحتوي على المعلومات التي يحتاجها Chrome لتشغيل التطبيقات. كما هو موضح في ملف البيان، فإن تطبيق مشغّل الوسائط "offline_enabled" (قد لا يتم الاتصال بالإنترنت)، يمكن حفظ مواد عرض الوسائط محليًا، والوصول إليها وتشغيلها بغض النظر عن الاتصال بالإنترنت.
يُستخدم حقل "وضع الحماية" لتوفير وضع الحماية للمنطق الرئيسي للتطبيق في مصدر فريد. يتم إعفاء كل المحتوى المحمي في وضع الحماية من سياسة أمان المحتوى لتطبيقات Chrome، ولكن لا يمكنه الوصول مباشرةً إلى واجهات برمجة تطبيقات تطبيقات Chrome. ويتضمن البيان أيضًا إذن "المقبس"، ويستخدم تطبيق مشغّل الوسائط واجهة برمجة تطبيقاتsocket للاتصال بخادم وسائط عبر الشبكة.
{
"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) صارمة. يحتاج تطبيق مشغّل الوسائط إلى بعض الامتيازات الأعلى لعرض مكوّنات JavaScript. للتوافق مع سياسة أمان المحتوى (CSP) وتنفيذ منطق التطبيق، تنشئ الصفحة الرئيسية للتطبيق، index.html
إطار iframe
يعمل كبيئة وضع حماية:
<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 ويعرض طرق عرض مشغّل الوسائط. بما أنّ هذا النص البرمجي في وضع الحماية، لا يمكنه الوصول مباشرةً إلى واجهات برمجة تطبيقات تطبيقات Chrome. يتم التواصل بين الملفات app.js
والملفات التي لا تتضمن وضع الحماية باستخدام واجهة برمجة التطبيقات لمشاركة رسائل HTML5.
التواصل بين الملفات
يرسل app.js
الرسائل إلى index.js ليتمكن تطبيق مشغّل الوسائط من الوصول إلى واجهات برمجة تطبيقات Chrome، مثل الاستعلام عن الشبكة عن خوادم الوسائط. على عكس 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()
. تتحقّق هذه الدالة أولاً من وجود أي طلبات اكتشاف مُعلّقة، وإذا كانت القيمة "صحيح"، فإنها تلغيها حتى يمكن بدء الطلب الجديد. بعد ذلك، تنشر وحدة التحكّم رسالة على
"index.js
" تتضمّن عملية تعزيز استكشافية رئيسية واثنين من المستمعين لمعاودة الاتصال:
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-discover" من "app.js
"، ويردّ عليه من خلال الاتصال على الرقم
upnpDiscover()
. عند اكتشاف خادم وسائط، يستخرج index.js
نطاق خادم الوسائط
من المعلَمات، ويحفظ الخادم محليًا، وينسق بيانات خادم الوسائط، ويدفع البيانات إلى
وحدة تحكم MediaServer
.
تحليل بيانات خادم الوسائط
عندما يكتشف Upnp.js
خادم وسائط جديدًا، يسترد بعد ذلك وصفًا للجهاز ويرسل طلب صابون لتصفّح بيانات خادم الوسائط وتحليلها، ويحلِّل soapclient.js
عناصر الوسائط حسب اسم العلامة في مستند.
الاتصال بخادم الوسائط
يتصل Upnp.js
بخوادم الوسائط التي تم اكتشافها ويتلقى بيانات الوسائط باستخدام واجهة برمجة التطبيقات 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);
});
});
});
استكشاف الوسائط وتشغيلها
تسرد وحدة تحكم 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 للحصول على البيانات من خادم الوسائط
وتنتظر حالة الإكمال. يؤدي ذلك إلى بدء استدعاء 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
قائمة ملفات الوسائط ولوحة العرض التدرّجي
لمشغّل الوسائط.