مؤتمر فيديو Jitsi

يبدو أن هذا الخطأ يشير إلى أن Jitsi في متصفحك لا يملك حق الوصول إلى التخزين المحلي. هل تستخدم متصفحًا بإعدادات أمان تقييدية قد تمنع الإطارات المضمنة من استخدام التخزين المحلي؟

إعجابَين (2)

شيء “مضحك” في وحدة التحكم الخاصة بي، مع بعض الأخطاء أيضًا، مثل:

سياسة أمان المحتوى: تمنع إعدادات الصفحة تحميل مورد في eval (“script-src”). المصدر: (function injected(eventName, injectedIntoContentWindow)
{

على الرغم من أنها تعمل :thinking:

يمكنك تجربة أي من هذه النسخ:
https://framatalk.org/accueil/fr/info/

إعجاب واحد (1)

نعم - لقد قمت بضبط Chrome لعدم السماح بملفات تعريف الارتباط من جهات خارجية. يؤدي تعطيل هذا الإعداد إلى حل هذه المشكلة. سيكون من الجميل وجود طريقة للتنبيه بشأن هذا السبب، لكن يبدو أن هذه مشكلة نادرة الحدوث.

هل توجد في Jitsi طريقة لفتح مكالمة فيديو في علامة تبويب جديدة أو نافذة منبثقة منفصلة؟ أتساءل عما إذا كان المستخدمون سيغادرون الصفحة أثناء المكالمة لاستمرار التصفح في المنتدى أثناء التحدث أو لإضافة رد إلى الموضوع.

إعجابَين (2)

ربما لا، لأن ذلك سيعقّد csp_extensions في about.json

إعجاب واحد (1)

لا، إصدار مكون السمة الحالي يضيف بالفعل مسار واجهة برمجة تطبيقات Jitsi إلى مصادر سياسة أمن المحتوى. يتم ذلك من خلال هذا السطر في ملف about.json كما لاحظ Benjamin_D:

إعجاب واحد (1)

@pmusaraj هل من الممكن تعيين هذه الخطوة الأحدث داخل ديسكورد نفسه؟

:smiley: نعم، أنا أتعلم أثناء الممارسة…
ما زلت أتساءل لماذا لا تمرر هذه الدالة: injected(eventName, injectedIntoContentWindow) وما إلى ذلك… سياسة أمان المحتوى (CSP)

console

سياسة أمان المحتوى: منعت إعدادات الصفحة تحميل مورد في eval (“script-src”). المصدر: (function injected(eventName, injectedIntoContentWindow)
{
let checkRequest;

/*

  • غلاف سياق الإطار
  • في بعض الحالات الحدية، لن تقوم كروم بتشغيل سكريبتات المحتوى داخل الإطارات.
  • بدأت المواقع في استغلال هذه الحقيقة للوصول إلى واجهات برمجة التطبيقات غير المغلفة عبر
  • contentWindow للإطار (#4586, 5207). لذلك، حتى تقوم كروم بتشغيل سكريبتات المحتوى بشكل متسق لجميع الإطارات، يجب أن نحرص على إعادة حقن أغلفتنا عند الوصول إلى contentWindow.
    */
    let injectedToString = Function.prototype.toString.bind(injected);
    let injectedFrames = new WeakSet();
    let injectedFramesAdd = WeakSet.prototype.add.bind(injectedFrames);
    let injectedFramesHas = WeakSet.prototype.has.bind(injectedFrames);

function injectIntoContentWindow(contentWindow)
{
if (contentWindow && !injectedFramesHas(contentWindow))
{
injectedFramesAdd(contentWindow);
try
{
contentWindow[eventName] = checkRequest;
contentWindow.eval(
“(” + injectedToString() + “)('” + eventName + “', true);”
);
delete contentWindow[eventName];
}
catch (e) {}
}
}

for (let element of [HTMLFrameElement, HTMLIFrameElement, HTMLObjectElement])
{
let contentDocumentDesc = Object.getOwnPropertyDescriptor(
element.prototype, “contentDocument”
);
let contentWindowDesc = Object.getOwnPropertyDescriptor(
element.prototype, “contentWindow”
);

// يبدو أن HTMLObjectElement.prototype.contentWindow غير موجود
// في إصدارات قديمة من كروم مثل الإصدار 51.
if (!contentWindowDesc)
  continue;

let getContentDocument = Function.prototype.call.bind(
  contentDocumentDesc.get
);
let getContentWindow = Function.prototype.call.bind(
  contentWindowDesc.get
);

contentWindowDesc.get = function()
{
  let contentWindow = getContentWindow(this);
  injectIntoContentWindow(contentWindow);
  return contentWindow;
};
contentDocumentDesc.get = function()
{
  injectIntoContentWindow(getContentWindow(this));
  return getContentDocument(this);
};
Object.defineProperty(element.prototype, "contentWindow",
                      contentWindowDesc);
Object.defineProperty(element.prototype, "contentDocument",
                      contentDocumentDesc);

}

/*

// إذا تم حقننا في إطار عبر contentWindow، فيمكننا ببساطة
// الحصول على نسخة checkRequest التي تركها لنا المستند الأصلي. وإلا
// فنحن بحاجة إلى إعدادها الآن، جنبًا إلى جنب مع دوال معالجة الأحداث.
if (injectedIntoContentWindow)
checkRequest = window[eventName];
else
{
let addEventListener = document.addEventListener.bind(document);
let dispatchEvent = document.dispatchEvent.bind(document);
let removeEventListener = document.removeEventListener.bind(document);
checkRequest = (url, callback) =>
{
let incomingEventName = eventName + “-” + url;

  function listener(event)
  {
    callback(event.detail);
    removeEventListener(incomingEventName, listener);
  }
  addEventListener(incomingEventName, listener);

  dispatchEvent(new RealCustomEvent(eventName, {detail: {url}}));
};

}

// يُستدعى فقط قبل كود الصفحة، وليس مقوى.
function copyProperties(src, dest, properties)
{
for (let name of properties)
{
if (Object.prototype.hasOwnProperty.call(src, name))
{
Object.defineProperty(dest, name,
Object.getOwnPropertyDescriptor(src, name));
}
}
}

let RealRTCPeerConnection = window.RTCPeerConnection ||
window.webkitRTCPeerConnection;

// يحتوي فايرفوكس على خيار (media.peerconnection.enabled) لتعطيل WebRTC
وفي هذه الحالة يكون RealRTCPeerConnection غير معرف.
if (typeof RealRTCPeerConnection != “undefined”)
{
let closeRTCPeerConnection = Function.prototype.call.bind(
RealRTCPeerConnection.prototype.close
);
let RealArray = Array;
let RealString = String;
let {create: createObject, defineProperty} = Object;

let normalizeUrl = url =>
{
  if (typeof url != "undefined")
    return RealString(url);
};

let safeCopyArray = (originalArray, transform) =>
{
  if (originalArray == null || typeof originalArray != "object")
    return originalArray;

  let safeArray = RealArray(originalArray.length);
  for (let i = 0; i < safeArray.length; i++)
  {
    defineProperty(safeArray, i, {
      configurable: false, enumerable: false, writable: false,
      value: transform(originalArray[i])
    });
  }
  defineProperty(safeArray, "length", {
    configurable: false, enumerable: false, writable: false,
    value: safeArray.length
  });
  return safeArray;
};

// سيكون من الأسهل بكثير استخدام طريقة .getConfiguration للحصول على
// التكوين المعيار والآمن من مثيل RTCPeerConnection.
للأسف، لم يتم تطبيقها حتى الآن في كروم غير المستقر 59.
انظر https://www.chromestatus.com/feature/5271355306016768
let protectConfiguration = configuration =>
{
  if (configuration == null || typeof configuration != "object")
    return configuration;

  let iceServers = safeCopyArray(
    configuration.iceServers,
    iceServer =>
    {
      let {url, urls} = iceServer;

      // RTCPeerConnection لا يتكرر عبر المصفوفات الوهمية لـ urls.
      if (typeof urls != "undefined" && !(urls instanceof RealArray))
        urls = [urls];

      return createObject(iceServer, {
        url: {
          configurable: false, enumerable: false, writable: false,
          value: normalizeUrl(url)
        },
        urls: {
          configurable: false, enumerable: false, writable: false,
          value: safeCopyArray(urls, normalizeUrl)
        }
      });
    }
  );

  return createObject(configuration, {
    iceServers: {
      configurable: false, enumerable: false, writable: false,
      value: iceServers
    }
  });
};

let checkUrl = (peerconnection, url) =>
{
  checkRequest(url, blocked =>
  {
    if (blocked)
    {
      // يؤدي استدعاء .close() إلى رمي استثناء إذا كان مغلقًا بالفعل.
      try
      {
        closeRTCPeerConnection(peerconnection);
      }
      catch (e) {}
    }
  });
};

let checkConfiguration = (peerconnection, configuration) =>
{
  if (configuration && configuration.iceServers)
  {
    for (let i = 0; i < configuration.iceServers.length; i++)
    {
      let iceServer = configuration.iceServers[i];
      if (iceServer)
      {
        if (iceServer.url)
          checkUrl(peerconnection, iceServer.url);

        if (iceServer.urls)
        {
          for (let j = 0; j < iceServer.urls.length; j++)
            checkUrl(peerconnection, iceServer.urls[j]);
        }
      }
    }
  }
};

يحتوي كروم غير المستقر (تم اختباره مع 59) بالفعل على تطبيق
setConfiguration، لذا نحتاج إلى تغليفه إذا كان موجودًا أيضًا.
https://www.chromestatus.com/feature/5596193748942848
if (RealRTCPeerConnection.prototype.setConfiguration)
{
  let realSetConfiguration = Function.prototype.call.bind(
    RealRTCPeerConnection.prototype.setConfiguration
  );

  RealRTCPeerConnection.prototype.setConfiguration = function(configuration)
  {
    configuration = protectConfiguration(configuration);

    // استدعاء الطريقة الحقيقية أولاً، للتحقق من صحة التكوين لنا.
    كما يمكننا فعل ذلك لأن checkRequest غير متزامن على أي حال.
    realSetConfiguration(this, configuration);
    checkConfiguration(this, configuration);
  };
}

let WrappedRTCPeerConnection = function(...args)
{
  if (!(this instanceof WrappedRTCPeerConnection))
    return RealRTCPeerConnection();

  let configuration = protectConfiguration(args[0]);

  // بما أن باني webkitRTCPeerConnection القديم يأخذ حجة ثانية اختيارية
  ، فنحن بحاجة إلى الحرص على تمريرها. ضروري
  للإصدارات القديمة من كروم مثل 51.
  let constraints = undefined;
  if (args.length > 1)
    constraints = args[1];

  let peerconnection = new RealRTCPeerConnection(configuration,
                                                 constraints);
  checkConfiguration(peerconnection, configuration);
  return peerconnection;
};

WrappedRTCPeerConnection.prototype = RealRTCPeerConnection.prototype;

let boundWrappedRTCPeerConnection = WrappedRTCPeerConnection.bind();
copyProperties(RealRTCPeerConnection, boundWrappedRTCPeerConnection,
               ["generateCertificate", "name", "prototype"]);
RealRTCPeerConnection.prototype.constructor = boundWrappedRTCPeerConnection;

if ("RTCPeerConnection" in window)
  window.RTCPeerConnection = boundWrappedRTCPeerConnection;
if ("webkitRTCPeerConnection" in window)
  window.webkitRTCPeerConnection = boundWrappedRTCPeerConnection;

}
})(‘abp-request-o6i81ij12x’, true);. bd833f87-4c58-41b0-a0cd-15b978834599:27:22

هل قمت بتفعيل سياسة أمان المحتوى؟
أيضًا، يُرجى مراسلتي عبر الرسائل الخاصة حول كيفية إضافة قائمة console المنسدلة الأنيقة. شكرًا لك.

فهمتُ!
هذا بسبب حجب الإعلانات، وليس له علاقة بـ Jitsi… :sweat_smile:

إعجاب واحد (1)

@sunjam لا توجد خطوة جديدة، حيث يقوم المكون بإضافة السكربت الذي يحتاج إلى إضافة إلى القائمة البيضاء تلقائيًا.

إعجاب واحد (1)

حاليًا لا، لكن هذا سيكون تحسينًا جيدًا، وسأعمل عليه عندما تتاح لي الفرصة.

4 إعجابات

رائع جداً! ملاحظات سريعة قليلة—

بالنسبة للجوال، بعد النقر على الزر لبدء الحدث، قد يكون رابط “الانتقال إلى التطبيق” غير قابل للنقر في بعض الأحيان! إنه يعمل عند المشاهدة مباشرة عبر Safari، لكن لا يعمل عبر Chrome، ولا عبر تطبيق Discourse Hub لنظام iOS…

أؤيد هذه الفكرة أيضاً، فوجود طريقة سريعة لتوليد معرف غرفة تلقائياً قد يكون مفيداً جداً. أعتقد خصوصاً لأي شخص غير ملم مسبقاً بـ Jitsي، أنه ليس واضحاً تماماً ما إذا كان يجب إدخال غرفة موجودة في ذلك الحقل، أو إذا كان يتم إنشاء غرفة جديدة على الفور بمجرد استخدام أي سلسلة عشوائية اعتباطية.

إعجابَين (2)

ملاحظة ممتازة! والسبب هو أن رابط “المتابعة إلى التطبيق” يقوم بطلب عنوان URL مخصص يبدأ بـ org.jitsi.meet://. لقد أضفت هذا المخطط المخصص إلى القائمة البيضاء في DiscourseHub، مما ينبغي أن يحل المشكلة في الإصدار التالي من التطبيق. (لا أعتقد أن هناك حلاً لذلك في Chrome، للأسف.)

لقد أضفت خيار التوليد العشوائي؛ يحتاج المستخدمون فقط إلى ترك حقل المعرف فارغًا، وسيتم توليد معرف. كما أضفت بعض النصوص التوضيحية لشرح ذلك:

لاحظ أن المعرف لن يحتوي على كلمات، بل سيكون مزيجًا عشوائيًا من الحروف والأرقام.

4 إعجابات

هل من الممكن إعطاء الأولوية لهذا؟ لقد كنا نستخدم Jitsi لمحادثاتنا الداخلية.

المشكلة هي كالتالي:

  1. نبدأ موضوعًا كحدث باستخدام إضافة التقويم، وندرج مكالمة Jitsi في الوقت المحدد. أحيانًا نفعل ذلك بعد استطلاع شبيه بـ Doodle في نفس الموضوع. رابط Jitsي يوضع في المنشور الأصلي.
  2. تُطلق المكالمة، وكل شيء على ما يرام.
  3. يستخدم شخص ما نفس التبويب للبحث عن شيء ما أو للرد في الموضوع (على سبيل المثال لكتابة المحضر مباشرة) - فينقطع عن المكالمة.

لا ينبغي أن يكون من الصعب جعل الرابط يفتح في صفحة فارغة بدلاً من أن يكون داخل إطار مضمن، أليس كذلك؟ ليس لأنني أملك المهارات اللازمة!

إعجابَين (2)

حسنًا، @nathank، هذا المكون مبالغ فيه بعض الشيء إذا كنت تريد مجرد رابط إلى غرفة Jitsi. يمكنك إضافة https://meet.jit.si/ROOMID إلى رابط، وهذا يجب أن يكفي دون الحاجة إلى مكون موضوع معقد.

ومع ذلك، لقد أضفت هذا الخيار إلى المكون، ويمكنك الآن اختيار ما إذا كنت تريد تضمين الإطار (iframe) للجوال أو سطح المكتب أو كليهما:

بشكل افتراضي، يتم تحميل مؤتمرات الفيديو داخل إطار (iframe). وعند إلغاء تحديد هذا الخيار، سيتم الانتقال إلى مكالمة الفيديو في نافذة كاملة. وكان هذا الأمر مرغوبًا به بشكل خاص على الجوال مع مؤتمرات الفيديو BigBlueButton، لذا قمت بنفس الشيء هنا.

لاحظ أيضًا أن رابط غرفة Jitsi في النافذة الكاملة لا يفتح في تبويب جديد. إذا كنت ترغب في ذلك، استخدم رابطًا مع target="_blank".

8 إعجابات

مناسب لي، لكن أقل ملاءمة لمستخدمي الأقل خبرة تقنية! شكرًا لك على تحسين الـ iframe، إنه مفيد للغاية. لست متأكدًا من أن جعل الـ iframe هو الافتراضي للجوال منطقي؛ هل يمكنك النظر في تغيير ذلك؟

لقد اكتشفت في مكان آخر أنه يمكنك تجاوز صفحة “قم بتنزيل تطبيق Jitsi” التي تظهر على الجوال عن طريق إضافة #config.disableDeepLinking=true إلى معرف الغرفة:
https://meet.jit.si/YourMeetingNameHere#config.disableDeepLinking=true

4 إعجابات

لقد جربت للتو إضافة مكون هذا السمة إلى نسخة discourse 2.7.0.beta3 الخاصة بنا، ولكن مهما فعلت، لا أرى أيقونة إضافية في شريط المحرر للربط بمعرف المؤتمر.

تم تحديد خيار “إظهار في قائمة الخيارات المنسدلة” في تكوين السمة، وبالتالي يجب أن يكون مرئيًا. هل لديك أي فكرة عن المكان الذي يمكنني البحث فيه عن الخطأ؟

إليك ما أراه في موقع Discourse جديد مع تحديد هذا الخيار:

إعجاب واحد (1)

نعم، تم حل المشكلة الآن، ربما كانت مشكلة في التخزين المؤقت.
ومع ذلك، شكرًا لك على ردك.

إعجاب واحد (1)

أنا حقًا أحب كيف تعمل هذه المكونات الخاصة بالموضوع. ما زلت أواجه مشاكل في صوت Jitsi، وإلا لكان كل شيء قد سار بسلاسة. كنت أتساءل عما إذا كان أي شخص يستخدم Jitsi حاليًا في منتداه ولا توجد لديه شكاوى بشأنه. ربما تكون مشاكل الصوت الخاصة بي بسبب WebRTC ---- هل لدى أي شخص تجربة مماثلة؟

إعجاب واحد (1)