يهدف هذا المستند إلى مساعدتك على البدء في إنشاء تطبيقات Chrome باستخدام Sencha Ext JS. إطار العمل. ولتحقيق هذا الهدف، سنتعمق في تطبيق مشغّل وسائط من تصميم Sencha. يحدد المصدر ومستندات واجهة برمجة التطبيقات على GitHub.
يكتشف هذا التطبيق خوادم الوسائط المتاحة للمستخدم، بما في ذلك أجهزة الوسائط المتصلة بجهاز الكمبيوتر برنامج يدير الوسائط عبر الشبكة. يمكن للمستخدمين تصفُّح الوسائط أو تشغيلها عبر الشبكة أو حفظها بلا اتصال بالإنترنت.
في ما يلي الخطوات الأساسية التي يجب اتّباعها لإنشاء تطبيق مشغّل وسائط باستخدام Sencha Ext JS:
- إنشاء بيان،
manifest.json
- إنشاء صفحة حدث،
background.js
. - منطق تطبيق وضع الحماية.
- التواصل بين تطبيق Chrome والملفات في وضع الحماية.
- تعرَّف على خوادم الوسائط.
- استكشاف الوسائط وتشغيلها
- حفظ الوسائط بلا اتصال بالإنترنت
إنشاء بيان
تتطلب جميع تطبيقات Chrome ملف بيان يحتوي على المعلومات التي يحتاج Chrome لتشغيلها التطبيقات. كما هو موضح في البيان، فإن تطبيق مشغّل الوسائط هو "offline_enabled"؛ مواد عرض الوسائط ويتم حفظها محليًا، ويمكن الوصول إليها وتشغيلها بغض النظر عن الاتصال.
"وضع الحماية" يُستخدم لوضع الحماية للمنطق الرئيسي للتطبيق في مصدر فريد. تم وضع الحماية محتوى إعفاء من سياسة أمان المحتوى لتطبيقات Chrome، ولكن لا يمكنه الوصول مباشرةً إلى واجهات برمجة التطبيقات لتطبيقات 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;
});
});
منطق تطبيق وضع الحماية
تعمل تطبيقات Chrome في بيئة خاضعة للرقابة تفرض سياسة أمان محتوى صارمة.
(CSP). يحتاج تطبيق مشغّل الوسائط إلى بعض الامتيازات الأعلى لعرض مكوّنات JavaScript الإضافية. إلى
الالتزام بسياسة CSP وتنفيذ منطق التطبيق، حيث تنشئ الصفحة الرئيسية للتطبيق، index.html
، إطار iframe
بمثابة بيئة وضع الحماية:
<iframe id="sandbox-frame" sandbox="allow-scripts" src="sandbox.html"></iframe>
يشير إطار iframe إلى sandbox.html يتضمن الملفات المطلوبة لامتداد JavaScript JS. app:
<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 كل رموز JavaScript JS ويعرض مشاهدات مشغّل الوسائط. نظرًا لأن ذلك
في وضع الحماية للنص البرمجي، لا يمكنه الوصول مباشرةً إلى واجهات برمجة تطبيقات تطبيق Chrome. الاتصالات بين app.js
والملفات التي لم يتم وضع الحماية لها باستخدام واجهة برمجة تطبيقات Post Message5 بتنسيق HTML5.
التواصل بين الملفات
من أجل وصول تطبيق مشغّل الوسائط إلى واجهات برمجة تطبيقات Chrome App، مثل الاستعلام عن الوسائط في الشبكة
الخوادم، app.js
ينشر الرسائل إلى index.js. على عكس 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()
. تفحص هذه الدالة أولاً أي طلبات استكشاف معلّقة، وما إذا
true، يتم إلغاؤها كي يمكن بدء الطلب الجديد. بعد ذلك، تنشر وحدة التحكم رسالة إلى
index.js
باستخدام مفتاح upnp ديسكفري، بالإضافة إلى اثنين من أدوات استماع لمعاودة الاتصال:
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-detect" رسالة من app.js
ويتم الرد من خلال الاتصال
upnpDiscover()
عندما يتم اكتشاف خادم وسائط، يستخرج index.js
نطاق خادم الوسائط.
من المعلمات، ويحفظ الخادم محليًا، وينسق بيانات خادم الوسائط، ويدفع البيانات إلى
وحدة التحكم MediaServer
.
تحليل بيانات خادم الوسائط
عندما يكتشف "Upnp.js
" خادم وسائط جديدًا، يسترد بعد ذلك وصفًا للجهاز ويرسل
طلب Soaprequest لتصفّح بيانات خادم الوسائط وتحليلها تحليل soapclient.js
لعناصر الوسائط
حسب اسم العلامة في مستند.
الاتصال بخادم الوسائط
يتصل Upnp.js
بخوادم الوسائط المكتشفة ويتلقى بيانات الوسائط باستخدام مقبس تطبيق 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);
});
});
});
استكشاف الوسائط وتشغيلها
تعرض وحدة تحكم 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 للحصول على البيانات من خادم الوسائط و
ينتظر حالة الإكمال. يؤدي هذا إلى بدء معاودة الاتصال عند التحميل التي تتحقّق من المحتوى الذي تم استلامه.
وتحفظ البيانات محليًا باستخدام الدالة 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
" قائمة ملفات الوسائط والوسائط.
لوحة شجرة اللاعبين.