أحاول استخدام مكتبة Mediaelement.js لتجاوز مشغل الصوت المخصص، وقد نجحت في ذلك عند تحميل الصفحة/تحديثها، ولكن عند التنقل من موضوع إلى آخر، لا يبدو أنه يتم إعادة تحميله، ويظهر بدلاً من ذلك مشغل الصوت الأصلي لـ Discourse.
أنا متأكد من أنني أفعل شيئًا خاطئًا مع تحميل mejs ولكنني اعتقدت ربما لم يكن الأمر كذلك وأن عليّ القيام بشيء متقدم مع onPageChange أو شيء من هذا القبيل.
سيكون هذا المنشور طويلاً بعض الشيء نظرًا لأنك نشرت في Development. ومع ذلك، فهو عام جدًا، ويركز أكثر على شرح الأنماط التي يجب استخدامها في Discourse بغض النظر عن المكتبة التي ترغب في دمجها أو عناصر المنشور التي تريد استهدافها. يحدث فقط أن يستخدم السكربت الذي اخترته كمثال رئيسي.
إذن، تمنحك MediaElement عدة طرق لتهيئة مشغل صوت جديد.
يمكنك إضافة فئة CSS وبعض السمات إلى العنصر، وسيتم التعامل معها تلقائيًا.
تقوم بتهيئتها يدويًا.
أنت حاليًا تستخدم الطريقة رقم 1، لذا دعنا ننظر فيها قليلاً. عندما يمنحك السكربت “تهيئة تلقائية على عنصر”، فإن هذا عادةً ما يكون تحسينًا لجودة الحياة يضيفه كاتب السكربت. في السكربت، يستمعون عادةً إلى حدث تحميل المستند، ويقومون ببعض العمل على عناصر DOM التي تحتوي على الفئة/السمات التي يخبرونك بإضافتها.
حسنًا، إذن لماذا لا يعمل؟ كما رأيت، يعمل بشكل جيد عند تحميل الصفحة الأولي، لكنه لا يعمل عند التنقل داخل التطبيق لاحقًا. ما الأمر؟
الإجابة المختصرة هي أن Discourse هو تطبيق صفحة واحدة (Single-Page Application). عناصر مثل وسوم HTML و body تُرسل مرة واحدة. لذا، وبمعنى ما، يتم تحميل المستند مرة واحدة فقط. لذلك، عند التنقل بعد تحميل الصفحة الأولية، لا توجد أحداث “تحميل” أصلية أخرى تُرسل. تذكر، أن المستند قد تم تحميله بالفعل عند عرض الصفحة الأولية. كل ما يحدث بعد ذلك يتم التعامل معه بواسطة Discourse.
بطبيعة الحال، هذا لا يعني عدم وجود أحداث تعمل عند التنقل اللاحق. ومع ذلك، فإن هذه أحداث خاصة بـ Discourse. لذا، لن يكون لدى كتاب السكربتات التابعة لجهات خارجية أي طريقة لمعرفة ذلك مسبقًا. تخيل أنك كاتب سكربت وتضطر إلى تلبية احتياجات مئات المنصات المختلفة؟ ليس جيدًا، أليس كذلك؟
لذا، لا يمكننا استخدام طريقة تحسين جودة الحياة التي أضافها كاتب السكربت بهذه اللطف. ماذا بعد؟ حسنًا، تذكر أننا لا يزال بإمكاننا تهيئة السكربت يدويًا على العناصر المستهدفة. لذا، دعنا نحاول فعل ذلك.
لقد ذكرت سابقًا أن هناك حدث تحميل أصلي واحد فقط (على مستوى المتصفح)، لكن منصة مثل Discourse لن تعمل بشكل جيد إذا لم يكن لديها نظام أحداث خاص بها. على سبيل المثال، يحتوي واجهة برمجة التطبيقات (API) للإضافات على طريقة تسمح لك بتشغيل السكربتات عند التنقل الافتراضي في الصفحة.
هل يجب عليك استخدام هذه الطريقة؟ لا. هذه الطريقة مفيدة جدًا للأشياء مثل التحليلات وما إلى ذلك. لا فائدة من تشغيل سكريبت يتعامل فقط مع وسوم audio في كل صفحة - خاصة إذا لم تكن الصفحة تحتوي على أي من هذه الوسوم.
إذن، ما الخطوة التالية؟ حسنًا، الخبر السار هو أنك اكتشفت ذلك بالفعل. decorateCookedElement هي الطريقة الصحيحة للاستخدام هنا.
إنها تمنحك طريقة لـ… انتظرها… تزيين المنشورات
تضمن Discourse أن أي مزيّن (decorator) تضيفه سيتم تطبيقه على كل منشور.
حسنًا، أنت تقوم بتحميل السكربت في مزيّن منشور، لذا يجب إضافته، ويجب أن يعمل. كيف لا يعمل عند التنقل اللاحق؟
لأجل هذا، يجب عليك فهم كيفية عمل loadScript(). كودك يتحقق بالفعل من وجود عناصر مستهدفة صالحة قبل تحميل السكربت، لذا
ومع ذلك، تخيل موقفًا يكون فيه لديك 20 - 30 منشورًا متتاليًا تحتوي جميعها على عناصر صالحة. هل من المنطقي تحميل السكربت 20 - 30 مرة؟ بالطبع لا.
loadScript() ذكي بما يكفي للكشف عما إذا كان السكربت قد تم تحميله بالفعل. لن يقوم بتحميل نسخ مكررة، ولن يعيد تحميل سكريبت إذا كان قد اكتمل تحميله بالفعل. يمكنك رؤية ذلك هنا.
fullUrl أعلاه هو عنوان URL الذي تمرره إلى loadScript() عند استدعائه، تمامًا كما في مثالك.
إذن، الآن بعد أن عرفنا هذا، يمكننا نوعًا ما رؤية السبب في أنه لا يعمل عند التنقل اللاحق.
تقوم بزيارة الموضوع-أ > يحتوي على عنصر صوتي > loadScript() يقوم بتحميل السكربت > السكربت يقوم بشيء "التهيئة التلقائية" الرائع > السكربت يتم تهيئته على عناصرك > تحصل على عناصر صوتية مخصصة
ثم...
تقوم بزيارة الموضوع-ب > يحتوي على عناصر صوتية > loadScript() يرى أن السكربت محمل بالفعل > لا يوجد شيء "تهيئة تلقائية" رائع > تحصل على عناصر صوتية افتراضية > يليها الحزن
إذن، كيف تحل هذه المشكلة؟ حسنًا، هذا هو الغرض من النقطة رقم 2 من السابق.
يمكنك إضافة فئة CSS وبعض السمات إلى العنصر، وسيتم التعامل معها تلقائيًا.
تقوم بتهيئتها يدويًا.
لذا، دعنا نفعل ذلك. إنه موثق بالفعل في الصفحة التي شاركتها. نحتاج إلى استدعاء ذلك على عنصرنا المستهدف على النحو التالي
// يمكنك استخدام إما سلسلة لمعرف المشغل (أي، `player`)،
// أو `document.querySelector()` لأي محدد
var player = new MediaElementPlayer("player", {
// ... خيارات
});
كودك يتعامل بالفعل مع كل عنصر صوتي على حدة لذا نحتاج فقط إلى تعديل هذا
دعنا نوقف هذا مؤقتًا وننظر إلى بقية المزيّن. إليك ما لدينا حتى الآن
let loadScript = require("discourse/lib/load-script").default;
api.decorateCookedElement(
element => {
const audioplayers = element.querySelectorAll("audio");
// console.log("player: " + audioplayers[0]);
if (Object.entries(audioplayers).length > 0) {
// console.log("audioplayers has length");
loadScript(
`https://cdnjs.cloudflare.com/ajax/libs/mediaelement/5.0.4/mediaelement.min.js`
);
loadScript(
`https://cdnjs.cloudflare.com/ajax/libs/mediaelement/5.0.4/mediaelement-and-player.min.js`
);
}
// forEach used to be here
},
{ id: "mediaelement-js", onlyStream: true }
);
إذا لاحظت، فأنت تقوم باستدعاء loadScript() على سكريبتين مختلفين. لست متأكدًا مما إذا كان هذا مقصودًا، لكنك تحتاج فقط إلى أحدهما. فكر في الأمر على أنه حزمة كاملة وحزمة خفيفة الوزن. تريد مشغل الصوت المخصص. لذا، تحتاج إلى الحزمة الكاملة. دعنا نحذف الآخر.
let loadScript = require("discourse/lib/load-script").default;
api.decorateCookedElement(
element => {
const audioplayers = element.querySelectorAll("audio");
// console.log("player: " + audioplayers[0]);
if (Object.entries(audioplayers).length > 0) {
// console.log("audioplayers has length");
-- loadScript(
-- `https://cdnjs.cloudflare.com/ajax/libs/mediaelement/5.0.4/mediaelement.min.js`
-- );
loadScript(
`https://cdnjs.cloudflare.com/ajax/libs/mediaelement/5.0.4/mediaelement-and-player.min.js`
);
}
// forEach used to be here
},
{ id: "mediaelement-js", onlyStream: true }
);
أنت تتحقق مما إذا كان هناك مشغلات صوت في المنشور وتقوم بتحميل السكربت شرطيًا بناءً على ذلك. يمكن تبسيط ذلك على النحو التالي. أولاً، تحقق من الطول مباشرة.
let loadScript = require("discourse/lib/load-script").default;
api.decorateCookedElement(
element => {
const audioplayers = element.querySelectorAll("audio");
// console.log("player: " + audioplayers[0]);
-- if (Object.entries(audioplayers).length > 0) {
++ if (audioplayers.length) {
// console.log("audioplayers has length");
loadScript(
`https://cdnjs.cloudflare.com/ajax/libs/mediaelement/5.0.4/mediaelement-and-player.min.js`
);
}
// forEach used to be here
},
{ id: "mediaelement-js", onlyStream: true }
);
ثم انقل ذلك إلى الأعلى وقم فقط بـ return إذا كان الطول غير صحيح (length < 0). كما قمت بإزالة التعليقات في الكود
let loadScript = require("discourse/lib/load-script").default;
api.decorateCookedElement(
element => {
const audioplayers = element.querySelectorAll("audio");
++ if (!audioplayers.length) {
++ return;
++ }
loadScript(
`https://cdnjs.cloudflare.com/ajax/libs/mediaelement/5.0.4/mediaelement-and-player.min.js`
);
// forEach used to be here
},
{ id: "mediaelement-js", onlyStream: true }
);
بما أن src للسكربت لن يتغير أبدًا، دعنا ننقل ذلك إلى const. loadScript() هو أيضًا دائمًا نفس الشيء. دعنا نجعله const أيضًا.
++ const loadScript = require("discourse/lib/load-script").default;
++ const MEDIA_ELEMENT_SRC =
++ "https://cdnjs.cloudflare.com/ajax/libs/mediaelement/5.0.4/mediaelement-and-player.min.js";
api.decorateCookedElement(
element => {
const audioplayers = element.querySelectorAll("audio");
if (!audioplayers.length) {
return;
}
++ loadScript(MEDIA_ELEMENT_SRC);
// forEach used to be here
},
{ id: "mediaelement-js", onlyStream: true }
);
ودعنا نوقف هذا أيضًا مؤقتًا. قبل أن نتمكن من المتابعة، يجب أن نتحدث عن كيفية عمل loadScript() لفترة أطول قليلاً.
إذا كنت ترغب في استخدام جزء من سكريبت، فأنت تريد التأكد من تحميله قبل القيام بأي عمل، أليس كذلك؟ حسنًا، loadScript() يتعامل مع ذلك نيابةً عنك. إنه يعيد وعدًا (Promise). تبدو الوعود مخيفة في البداية لكنها بسيطة جدًا. الوعد هو حرفيًا ذلك… وعد.
تريد القيام ببعض العمل… تعد المتصفح بأنك ستخبره عندما ينتهي العمل… المتصفح ينتظرك. الأمر بسيط حقًا مثل ذلك. الباقي هو مجرد فهم الصيغة.
لن أقضي الكثير من الوقت في ذلك لأن ذلك خارج نطاق هذا الموضوع قليلاً.
دعنا نكمل. loadScript() يعتمد على الوعود. تعد Discourse المتصفح بإخباره عندما يتم تحميل السكربت بالكامل - سواء لم يكن السكربت موجودًا وكان يجب تحميله أو ببساطة للتحقق مما إذا كان قد تم تحميله بالفعل.
لذا، إذا قمنا بشيء مثل هذا
const loadScript = require("discourse/lib/load-script").default;
const MEDIA_ELEMENT_SRC =
"https://cdnjs.cloudflare.com/ajax/libs/mediaelement/5.0.4/mediaelement-and-player.min.js";
api.decorateCookedElement(
element => {
const audioplayers = element.querySelectorAll("audio");
if (!audioplayers.length) {
return;
}
++ loadScript(MEDIA_ELEMENT_SRC).then(() => {
++ // هذا سيُشغَّل فقط إذا تم تحميل السكربت/يتم تحميله
++ console.log("تم تحميل سكريبت الخاص بي");
++ });
// forEach used to be here
},
{ id: "mediaelement-js", onlyStream: true }
);
إذن، الآن يمكننا العودة إلى حلقة forEach من السابق وإضافتها مباشرة هناك، وسنكون متأكدين من أن السكربت سيكون متاحًا.
يجب أن ترى الآن المشغل المخصص في كل منشور يحتوي على عناصر صالحة.
بعد أن انتهى ذلك، يجب أن تلاحظ أن المكتبة التي اخترتها قديمة جدًا. إنها مُترجمة للمتصفحات القديمة، وتحاول ملء (polyfill) العديد من الميزات التي أصبحت معيارية منذ ذلك الحين.
إذا كنت تعرف لماذا تريد استخدامها، فلا بأس بذلك. ومع ذلك، إذا كنت تستخدمها فقط لتخصيص شكل المشغل، فإنني أنصحك بتجنبها. لم أتحقق، لكن هناك على الأرجح بدائل حديثة أخف وزنًا.
أفضل شيء في كل هذا هو أن التطبيق لا يتغير عن المذكور أعلاه. بغض النظر عن العناصر التي تريد استهدافها والسكربتات التي تريد استخدامها. ينطبق نفس النمط. الشيء الوحيد الذي يتغير هو تهيئة السكربت المخصص. كل مكتبة جيدة لديها توثيق جيد جدًا سيوجهك خلال ذلك. ثم، تضع ذلك ببساطة في النمط المذكور أعلاه.
كنت آمل أن يعطيني أحدهم نصيحة على الأقل، ومع ذلك، أخذت الوقت الكافي لكتابة شرح شامل لكيفية وصولي إلى ما وصلت إليه وأرشدتني خلال كيفية جعله يعمل، وعلمتني الكثير على طول الطريق (حتى مع التفاصيل الصغيرة حول الارتجاج!).
أتعلم كيف يمكن لهيكل Discourse أن يساعد في تشجيع مثل هذا السلوك، بمعنى أنه إذا أجبت عليه جيدًا مرة واحدة، يمكن للآخرين رؤيته ولن أضطر إلى الإجابة عليه مرة أخرى - مما يشجعني على مواصلة بناء مجتمعات بهذا الشكل؛ ومع ذلك، لا أعتقد أن هذا يفسر تمامًا سبب كتابتك لهذا لي، ورغبتك في القيام بذلك قد تشجعني على استخدام هذه المنصة أكثر.
شكرا لك.
بخصوص Mediaelement، نعم، إنها قديمة، لكنها تتناسب جيدًا مع موقع Wordpress الذي لدي وقد قمت بتخصيصها كثيرًا هناك، محاولًا توفير مظهر مألوف للمستخدم (وأيضًا عدم محاولة تعلم مكتبة أخرى في الوقت الحالي )
متابعة وربما سؤال غبي: أحاول تحميل عدة نصوص برمجية الآن، حيث تسمح Mediaelement بتحميل نصوص برمجية إضافية. أريد التأكد من تحميل جميع النصوص البرمجية قبل إرجاع الوعد.
لقد حاولت القيام بذلك عن طريق المرور عبر الثوابت لمصادر النصوص البرمجية ثم إنشاء مصفوفة من الوعود، ثم استخدام Promise.all() لتهيئة المشغلات، ومع ذلك عندما أفعل ذلك، أحصل على خطأ يقول إن mejs غير موجود، وهو ما أعتقد أنه مساحة الاسم أو شيء من هذا القبيل لاستدعاء وظائف مختلفة داخل mediaelement-and-player.
في هذه الحالة، أستخدم عددًا قليلاً فقط من النصوص البرمجية، لذلك قمت بكتابتها يدويًا، كنت أتساءل فقط عما إذا كنت أفوت شيئًا واضحًا حول وظيفة Promise.all()، أو إذا كانت هناك وظيفة Discourse تسمح لي بتحميل نصوص برمجية متعددة من مصفوفة.
يجب أن يعمل الكود الخاص بك بشكل جيد. لقد صادفت للتو خطأ في Discourse.
loadScript() لا يقوم بتعيين السمة async للبرامج النصية التي يقوم بتحميلها. لذلك، فإنه يرجع افتراضيًا إلى async="true" وهذا يفسد ترتيب التحميل الخاص بك. إنها نزوة في المتصفح. عليك فرض async="false" للبرامج النصية التي تم تحميلها باستخدام JS.
الإضافات أصغر، لذا فهي تُحمّل بشكل أسرع من الحزمة الرئيسية، ولكن كونها async يعني أنها لم تعد تحترم ترتيب التحميل - انتظر حتى يتم تحميل الحزمة الرئيسية وتنفيذها قبل التنفيذ.
ربما لم يلاحظ ذلك لأن loadScript غير متداخل في أي مكان في النواة، على حد علمي. عادةً ما تقوم بتجميع الملفات التي تحتاج إلى العمل معًا. لذا، للإجابة على سؤالك الآخر. لا، لا توجد وظائف Discourse تتعامل مع هذا النوع من الأشياء.
يجب أن يعمل المقتطف الآخر الخاص بك أيضًا. لجعله أسهل قليلاً في القراءة، ربما حاول ربطها دون تداخلها.
// This goes outside the decorator
const PLUGINS = {
speed: "https://example.com/foo.js",
skipBack: "https://example.com/bar.js",
jumpForward: "https://example.com/baz.js"
};
// Then do your work inside the decorator
loadScript(MEDIA_ELEMENT_SRC)
.then(() => loadScript(PLUGINS.speed))
.then(() => loadScript(PLUGINS.skipBack))
.then(() => loadScript(PLUGINS.jumpForward))
.then(() => {
audioplayers.forEach(function (el) {
new MediaElementPlayer(el, MEDIA_ELEMENT_CONFIG);
});
});