Jitsi 動画会議

そのエラーは、お使いのブラウザ上のJitsiがローカルストレージにアクセスできないことを示しているようです。ローカルストレージを使用するiframeをブロックする可能性のある、制限の厳しいセキュリティ設定を持つブラウザをお使いではありませんか?

「いいね!」 2

コンソールに少し"面白い"ことが表示され、いくつかのエラーも出ています。例えば:

コンテンツセキュリティポリシー:ページの設定により、eval でのリソース読み込みがブロックされました(“script-src”)。ソース:(function injected(eventName, injectedIntoContentWindow)
{

しかし、動作はしています::thinking:

以下のインスタンスのいずれかをお試しください:
https://framatalk.org/accueil/fr/info/

「いいね!」 1

はい、Chrome でサードパーティのクッキーを許可しないように設定しています。その設定を無効にすると、この問題は解決します。その原因がわかるように警告を出す方法があると良いのですが、これはめったにない問題なのでしょう。

Jitsi で、ビデオ通話を新しいタブや別々のポップアップで開く方法はありますか?通話中にフォーラムの閲覧を続けたり、トピックへの返信を追加したりするために、ユーザーが通話中に別のタブに移動してしまうのではないかと心配です。

「いいね!」 2

[quote=“Benjamin_D, post:19, topic:146046”]
external_api.js を https://meet.jit.si ではなく settings.meet_jitsi_domain からインポートした方がよいのではないかと私も考えていました。[/quote]

おそらくそうではないでしょう。そうすると about.json 内の csp_extensions が複雑化してしまうためです。

「いいね!」 1

いいえ、現在のテーマコンポーネントのバージョンは、すでにJitsi APIパスをCSPソースに追加しています。これは@Benjamin_Dが指摘したように、about.jsonの以下の行で行われています:

「いいね!」 1

@pmusaraj その新しい手順を Discourse 自体で設定することは可能でしょうか?

:smiley: はい、試行錯誤しながら進めています…
この関数:injected(eventName, injectedIntoContentWindow) などが、なぜ CSP を通過しないのかまだわかりません。

コンソール

Content Security Policy: ページの設定により、eval でのリソースの読み込みがブロックされました(“script-src”)。ソース: (function injected(eventName, injectedIntoContentWindow)
{
let checkRequest;

/*

  • フレームコンテキストのラッパー
  • 一部の特殊なケースでは、Chrome がフレーム内でコンテンツスクリプトを実行しません。
  • ウェブサイトがこの事実を悪用し、フレームの contentWindow を介してラップされていない API にアクセスするようになりました(#4586, 5207)。
  • そのため、Chrome がすべてのフレームで一貫してコンテンツスクリプトを実行するようになるまで、
  • 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”
);

// 古いバージョンの Chrome(例:51)では HTMLObjectElement.prototype.contentWindow が存在しないようです。
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;

// Firefox には WebRTC を無効化するオプション(media.peerconnection.enabled)があり、
// その場合 RealRTCPeerConnection は undefined になります。
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;
};

// RTCPeerConnection インスタンスから正規化された安全な設定を取得するために
// .getConfiguration メソッドを使用すればずっと簡単だったはずです。
// しかし、Chrome 不安定版 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]);
        }
      }
    }
  }
};

// Chrome 不安定版(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 コンストラクタはオプションの第 2 引数を受け取るため、
  // それを渡すよう注意する必要があります。古いバージョンの Chrome(例: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 ドロップダウンを追加した方法を DM で教えてください。ありがとう。

わかりました!
それは広告ブロッカーによるもので、Jitsi には関係ありません…:sweat_smile:

「いいね!」 1

@sunjam 新しい手順はありません。コンポーネントは、ホワイトリストに追加する必要があるスクリプトを自動的にホワイトリストに登録します。

「いいね!」 1

現時点ではできませんが、これは良い改善点ですね。機会があれば対応します。

「いいね!」 4

とても素晴らしいですね!いくつかの簡単なコメントを。

モバイル版では、イベント開始ボタンをクリックした後、「アプリに続ける」リンクがクリックできない場合があります!Safari で直接表示すれば機能しますが、Chrome や Discourse Hub の iOS アプリでは機能しません。

これにも賛成です。ルーム ID を自動生成できる簡単な方法があれば非常に役立ちます。特に Jitsi に詳しくない方にとって、そのフィールドに既存のルームを入力する必要があるのか、それとも任意のランダムな文字列を入力するだけで即座に新しいルームが生成されるのかが明確でない場合があります。

「いいね!」 2

見事な発見です!これは「アプリに続行」リンクが org.jitsi.meet:// で始まるカスタム URL リクエストを行うためです。DiscourseHub でこのカスタムスキームをホワイトリストに登録しましたので、次のアプリリリースでこの問題は解決するはずです(残念ながら Chrome での修正はできないと思います)。

ランダム生成オプションを追加しました。ID フィールドを空欄にしておくだけで、ID が自動生成されます。また、これを説明するテキストも追加しました:

なお、ID には単語は含まれず、文字と数字のランダムな組み合わせになります。

「いいね!」 4

これを優先していただけますか?社内チャットに Jitsi を使おうとしています。

問題は以下の通りです:

  1. カレンダープラグインを使ってイベントとしてスレッドを作成し、その時間に Jitsi 通話を含めます。同じスレッド内で Doodle のような投票 を行った後に行うこともあります。Jitsi のリンクは OP に記載します。
  2. 通話が開始され、問題なく動作します。
  3. 同じタブを使って何かを検索したり、スレッドに返信したり(例えばその場で議事録を書くなど)すると、通話から切断されてしまいます。

リンクを iframe ではなく空白ページに飛ばすようにするのはそれほど難しくないはずです。私にはそのスキルはありませんが!

「いいね!」 2

さて、@nathank、単に Jitsi の部屋へのリンクが欲しいだけなら、このコンポーネントは少し過剰かもしれません。https://meet.jit.si/ROOMID をリンクとして追加するだけで、派手なテーマコンポーネントなしでも目的を達成できます。

ただし、このコンポーネントにオプションを追加しました。モバイル用、デスクトップ用、または両方の iframe を表示するかどうかを選べるようになりました:

デフォルトでは、ビデオ会議は iframe で読み込まれます。チェックを外すと、フルウィンドウでビデオ通話に遷移します。BigBlueButton ビデオ会議 では、特にモバイル環境でこれが望ましいため、ここでは同じように実装しました。

なお、フルウィンドウの Jitsi 部屋へのリンクは、デフォルトでは新しいタブで開きません。新しいタブで開きたい場合は、target="_blank" を指定したアンカータグを使用してください。

「いいね!」 8

私には問題ありませんが、技術に詳しくないユーザーにはあまり便利ではありませんね。iframe の強化、ありがとうございます。とても役立ちました。ただ、モバイル環境で iframe をデフォルトにするのは適切ではないかもしれません。その点を変更するご検討はいただけませんでしょうか。

また、別の場所で、#config.disableDeepLinking=true をルーム ID に追加することで、モバイル端末に表示される「Jitsi アプリをダウンロード」ページを回避できることを発見しました。
https://meet.jit.si/YourMeetingNameHere#config.disableDeepLinking=true

「いいね!」 4

Discourse 2.7.0.beta3 のインスタンスにこのテーマコンポーネントを追加しようとしましたが、何を試しても、コンポーザーバーに会議 ID へのリンクを追加するアイコンが表示されません。

テーマコンポーネントの設定で「show in options dropdown」がチェックされているため、表示されるはずです。エラーの原因をどこで確認すればよいでしょうか?

そのオプションがチェックされた状態の新しい Discourse サイトで私が確認しているものは以下の通りです:

「いいね!」 1

はい、問題は解決しました。おそらくキャッシュの問題だったのでしょう。
それでは、ご返信いただきありがとうございます。

「いいね!」 1

このテーマコンポーネントの動作が本当に気に入っています。Jitsiのオーディオの問題にはまだ苦労していますが、それ以外はすべてスムーズに動作するはずです。フォーラムでJitsiを使用しており、不満がない方が現在いらっしゃいますか?私のオーディオの問題は、おそらくWebRTCが原因です----同様の経験をしたことがある人はいますか?

「いいね!」 1