Conferência de Vídeo Jitsi

Esse erro parece indicar que o Jitsi no seu navegador não tem acesso ao armazenamento local. Você está usando um navegador com configurações de segurança restritivas que podem bloquear iframes de usar o armazenamento local?

2 curtidas

alguma coisa “engraçada” no meu console, alguns erros também, como:

Política de Segurança de Conteúdo: As configurações da página bloquearam o carregamento de um recurso em eval (“script-src”). Origem: (function injected(eventName, injectedIntoContentWindow)
{

embora funcione :thinking:

você pode tentar qualquer uma dessas instâncias:
https://framatalk.org/accueil/fr/info/

1 curtida

Sim — configurei o Chrome para não permitir cookies de terceiros. Desativar essa configuração resolve esse problema. Seria bom ter alguma forma de ser alertado sobre essa causa, mas imagino que seja um problema suficientemente raro.

Existe alguma maneira no Jitsi de abrir uma chamada de vídeo em uma nova aba ou em uma janela pop-up separada? Fico imaginando se as pessoas vão clicar para fora durante a chamada para continuar navegando no fórum enquanto conversam ou para adicionar uma resposta ao tópico.

2 curtidas

Talvez não, pois isso complicaria as csp_extensions em about.json

1 curtida

Não, a versão atual do componente do tema já adiciona o caminho da API do Jitsi às fontes do CSP. Isso é feito por esta linha do about.json, como @Benjamin_D observou:

1 curtida

@pmusaraj É possível definir essa etapa mais recente dentro do próprio Discourse?

:smiley: sim, estou descobrindo conforme avanço…
Ainda estou me perguntando por que esta função: injected(eventName, injectedIntoContentWindow) etc… não passa pela CSP

console

Política de Segurança de Conteúdo: As configurações da página bloquearam o carregamento de um recurso em eval (“script-src”). Origem: (function injected(eventName, injectedIntoContentWindow)
{
let checkRequest;

/*

  • Wrapper de contexto de frame
  • Em alguns casos extremos, o Chrome não executa scripts de conteúdo dentro de frames.
  • Sites começaram a abusar desse fato para acessar APIs não envoltas via
  • contentWindow de um frame (#4586, 5207). Portanto, até que o Chrome execute scripts de conteúdo
  • consistentemente para todos os frames, devemos tomar cuidado para (re)injetar nossos
  • wrappers quando o contentWindow for acessado.
    */
    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”
);

// Aparentemente, em HTMLObjectElement.prototype.contentWindow não existe
// em versões mais antigas do Chrome, como a 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);

}

/*

// Se fomos injetados em um frame via contentWindow, podemos simplesmente
// pegar a cópia de checkRequest deixada para nós pelo documento pai. Caso contrário,
// precisamos configurá-la agora, junto com as funções de tratamento de eventos.
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}}));
};

}

// Apenas para ser chamado antes do código da página, não endurecido.
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;

// O Firefox tem a opção (media.peerconnection.enabled) para desabilitar WebRTC,
// caso em que RealRTCPeerConnection é indefinido.
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;
};

// Seria muito mais fácil usar o método .getConfiguration para obter
// a configuração normalizada e segura a partir da instância RTCPeerConnection.
// Infelizmente, não está implementado no Chrome instável 59.
// Veja 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 não itera sobre pseudo Arrays de 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)
    {
      // Chamar .close() lança erro se já estiver fechado.
      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]);
        }
      }
    }
  }
};

// O Chrome instável (testado com 59) já implementou
// setConfiguration, então precisamos envolver isso também, se existir.
// 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);

    // Chame o método real primeiro, para que ele valide a configuração para
    // nós. Também podemos fazer isso, já que checkRequest é assíncrono de qualquer forma.
    realSetConfiguration(this, configuration);
    checkConfiguration(this, configuration);
  };
}

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

  let configuration = protectConfiguration(args[0]);

  // Como o construtor antigo webkitRTCPeerConnection aceita um segundo
  // argumento opcional, precisamos garantir que ele seja passado. Necessário
  // para versões mais antigas do Chrome, como a 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

Você ativou sua política de segurança de conteúdo? Além disso, por favor, envie-me uma mensagem privada explicando como você adicionou esse elegante menu suspenso console. Obrigado.

Entendi!
É o adblock, nada a ver com o Jitsi… :sweat_smile:

1 curtida

@sunjam não há nenhuma nova etapa; o componente adiciona automaticamente o script necessário à lista de permissões.

1 curtida

Atualmente não, mas isso seria uma boa melhoria. Vou trabalhar nisso quando tiver oportunidade.

4 curtidas

Muito legal! Algumas observações rápidas—

No celular, após clicar no botão para iniciar o evento, o link “Continuar para o aplicativo” às vezes não é clicável! Funciona quando acessado diretamente no Safari, mas não no Chrome e nem no aplicativo Discourse Hub para iOS…

Apoio também essa sugestão. Uma maneira rápida de gerar automaticamente um ID de sala seria bastante útil. Acho que, especialmente para quem ainda não está muito familiarizado com o Jitsi, não fica totalmente claro se é necessário inserir uma sala existente nesse campo ou se você está gerando uma sob demanda, apenas usando qualquer string aleatória.

2 curtidas

Ótima observação! Isso ocorre porque o link “Continuar para o aplicativo” faz uma solicitação de URL personalizada que começa com org.jitsi.meet://. Adicionei esse esquema de consulta personalizado à lista de permitidos no DiscourseHub, o que deve resolver o problema na próxima versão do aplicativo. (Infelizmente, não creio que haja uma correção para isso no Chrome.)

Adicionei uma opção de geração aleatória: os usuários só precisam deixar o campo de ID vazio, e um ID será gerado. Também incluí um texto explicativo sobre isso:

Observe que o ID não conterá palavras; será uma combinação aleatória de letras e números.

4 curtidas

Haveria alguma possibilidade de priorizar isso? Temos usado o Jitsi para nossas conversas internas.

O problema é o seguinte:

  1. Iniciamos um tópico como um Evento usando o plugin de Calendário e incluímos uma chamada Jitsi agendada para aquele horário. Às vezes, fazemos isso após uma pesquisa estilo Doodle no mesmo tópico. O link do Jitsi vai na primeira mensagem.
  2. A chamada é iniciada, tudo certo.
  3. Alguém usa a mesma aba para pesquisar algo ou responder no tópico (por exemplo, para redigir atas em tempo real) — e acaba sendo desconectado da chamada.

Não deveria ser muito difícil fazer o link abrir em uma nova aba em branco, em vez de estar em um iframe, certo? Não que eu tenha as habilidades para isso!

2 curtidas

Bem, @nathank, este componente é um exagero se você quiser apenas um link para uma sala do Jitsi. Você pode adicionar https://meet.jit.si/ROOMID a um link, e isso deve resolver o problema sem precisar de um componente de tema sofisticado.

No entanto, adicionei essa opção ao componente; agora você pode escolher se deseja o iframe para dispositivos móveis, desktop ou ambos:

Por padrão, a videoconferência é carregada em um iframe. Quando desmarcado, ele levará à chamada de vídeo na janela completa. Com a videoconferência BigBlueButton, isso era desejável especialmente em dispositivos móveis, então fiz o mesmo aqui.

Observe também que o link para a sala completa do Jitsi não abre em uma nova aba. Se quiser isso, use uma âncora com target="_blank".

8 curtidas

Funciona bem para mim, mas menos para meus usuários menos técnicos! Obrigado pela melhoria do iframe, super útil. Não tenho certeza de que faz sentido o iframe ser o padrão para dispositivos móveis; você consideraria mudar isso?

Descobri em outro lugar que é possível contornar a página “baixe o aplicativo Jitsi” que aparece em dispositivos móveis adicionando #config.disableDeepLinking=true ao ID da sala:
https://meet.jit.si/YourMeetingNameHere#config.disableDeepLinking=true

4 curtidas

Acabei de tentar adicionar este componente de tema à nossa instância do Discourse 2.7.0.beta3, mas, independentemente do que faço, não vejo um ícone adicional na barra do editor para vincular ao ID da conferência.

A opção “mostrar no menu suspenso de opções” na configuração do componente do tema está marcada, portanto, deveria estar visível. Alguma ideia de onde posso procurar o erro?

Aqui está o que vejo em um site Discourse recém-criado com essa opção marcada:

1 curtida

Sim, o problema foi resolvido agora. Talvez tenha sido algum problema de cache.
De qualquer forma, obrigado pela sua resposta.

1 curtida

Eu realmente amo como este componente de tema funciona. Ainda estou lutando com problemas de áudio do Jitsi, caso contrário, tudo teria funcionado perfeitamente. Eu estava me perguntando se alguém está usando o Jitsi em seu fórum e não tem reclamações sobre isso. Meus problemas de áudio são talvez devido ao WebRTC ---- alguém também tem experiência semelhante?

1 curtida