Hello.

I am Paul Kinlan.

A Developer Advocate for Chrome and the Open Web at Google.

Creating a commit with multiple files to Github with JS on the web

Paul Kinlan

Meu site é entirely static . É construído com Hugo e hospedado com Zeit . Estou muito feliz com a configuração, chego perto de versões instantâneas e entrega de conteúdo CDN super rápida e posso fazer todas as coisas que preciso porque não preciso gerenciar nenhum estado. Eu criei um simple UI para este site e também o meu podcast creator que me permite publicar rapidamente novos conteúdos para o meu site estaticamente hospedado.

Read More

Paul Kinlan

Trying to make the web and developers better.

RSS Github Medium

Screen Recorder: recording microphone and the desktop audio at the same time

Paul Kinlan

Eu tenho um objetivo de construir o software de gravação de tela mais simples do mundo e tenho andado devagarinho pelo projeto nos últimos dois meses (quero dizer, muito devagar).

Em postagens anteriores, eu tinha conseguido o screen recording and a voice overlay por futzing com os fluxos de todas as fontes de entrada. Uma área de frustração era que eu não conseguia descobrir como obter o áudio da área de trabalho * e * sobrepor o áudio do alto-falante. Eu finalmente trabalhei como fazer isso.

Em primeiro lugar, o getDisplayMedia no Chrome agora permite a captura de áudio, parece um erro estranho na especificação, pois não permite que você especifique audio: true na chamada de função, agora você pode.

const audio = audioToggle.checked || false;
desktopStream = await navigator.mediaDevices.getDisplayMedia({ video:true, audio: audio });

Em segundo lugar, inicialmente pensei que, criando duas faixas no fluxo de áudio, eu conseguiria o que queria, no entanto, aprendi que a API MediaRecorder do Chrome só pode produzir uma faixa e, segundo, ela não teria funcionado de qualquer maneira, porque faixas são como as faixas de áudio múltiplas do DVD em que apenas uma pode tocar de cada vez.

A solução é provavelmente simples para muitas pessoas, mas era nova para mim: Use o Web Audio.

Acontece que a API WebAudio possui createMediaStreamSource e createMediaStreamDestination , sendo que ambas são APIs necessárias para resolver o problema. O createMediaStreamSource pode receber fluxos do áudio e do microfone da minha área de trabalho e, ao conectar os dois juntos no objeto criado pelo createMediaStreamDestination , é possível canalizar esse fluxo para a API MediaRecorder .

const mergeAudioStreams = (desktopStream, voiceStream) => {
  const context = new AudioContext();
    
  // Create a couple of sources
  const source1 = context.createMediaStreamSource(desktopStream);
  const source2 = context.createMediaStreamSource(voiceStream);
  const destination = context.createMediaStreamDestination();
  
  const desktopGain = context.createGain();
  const voiceGain = context.createGain();
    
  desktopGain.gain.value = 0.7;
  voiceGain.gain.value = 0.7;
   
  source1.connect(desktopGain).connect(destination);
  // Connect source2
  source2.connect(voiceGain).connect(destination);
    
  return destination.stream.getAudioTracks();
};

Simples

O código completo pode ser encontrado em my glitch , e a demonstração pode ser encontrada aqui: https://screen-record-voice.glitch.me/

{{<fast-youtube oGIdqcMFKlA>}}

Extracting text from an image: Experiments with Shape Detection

Paul Kinlan

Tive um pouco de folga depois que o Google IO e eu quisemos coçar uma coceira de longo prazo que eu tive. Eu só quero ser capaz de copiar o texto que é mantido dentro de imagens no navegador. Isso é tudo. Eu acho que seria um recurso legal para todos.

Não é fácil adicionar funcionalidade diretamente ao Chrome, mas sei que posso aproveitar o sistema de intenção no Android e agora posso fazer isso com a Web (ou pelo menos o Chrome no Android).

Duas novas adições à plataforma da web - compartilhar o nível 2 de destino (ou como eu gosto de chamar de compartilhamento de arquivo) e o TextDetector na API de detecção de forma - have allowed me to build a utility that I can Share images to and get the text held inside them .

A implementação básica é relativamente direta, você cria um Share Target e um manipulador no Service Worker e, depois de ter a imagem que o usuário compartilhou, você executa o TextDetector nele.

O Share Target API permite que seu aplicativo da Web faça parte do subsistema de compartilhamento nativo e, nesse caso, agora você pode se registrar para manipular todos os tipos de image/* , declarando-o dentro de suas Web App Manifest seguinte maneira.

"share_target": {
  "action": "/index.html",
  "method": "POST",
  "enctype": "multipart/form-data",
  "params": {
    "files": [
      {
        "name": "file",
        "accept": ["image/*"]
      }
    ]
  }
}

Quando o seu PWA estiver instalado, você o verá em todos os lugares onde você compartilha imagens da seguinte maneira:

A API Share Target trata o compartilhamento de arquivos como uma postagem de formulário. Quando o arquivo é compartilhado no Web App, o service worker é ativado fetch manipulador fetch é chamado com os dados do arquivo. Os dados estão agora dentro do Service Worker, mas eu preciso dele na janela atual para que eu possa processá-lo, o serviço sabe qual janela invocou a solicitação, para que você possa facilmente direcionar o cliente e enviar os dados.

self.addEventListener('fetch', event => {
  if (event.request.method === 'POST') {
    event.respondWith(Response.redirect('/index.html'));
    event.waitUntil(async function () {
      const data = await event.request.formData();
      const client = await self.clients.get(event.resultingClientId || event.clientId);
      const file = data.get('file');
      client.postMessage({ file, action: 'load-image' });
    }());
    
    return;
  }
  ...
  ...
}

Quando a imagem está na interface do usuário, eu a procuro com a API de detecção de texto.

navigator.serviceWorker.onmessage = (event) => {  
  const file = event.data.file;
  const imgEl = document.getElementById('img');
  const outputEl = document.getElementById('output');
  const objUrl = URL.createObjectURL(file);
  imgEl.src = objUrl;
  imgEl.onload = () => {
    const texts = await textDetector.detect(imgEl);
    texts.forEach(text => {
      const textEl = document.createElement('p');
      textEl.textContent = text.rawValue;
      outputEl.appendChild(textEl);
    });
  };
  ...
};

O maior problema é que o navegador não gira naturalmente a imagem (como você pode ver abaixo), e a API de Detecção de Forma precisa que o texto esteja na orientação de leitura correta.

Eu usei o bastante fácil de usar EXIF-Js library para detectar a rotação e, em seguida, fazer alguma manipulação básica da tela para reorientar a imagem.

EXIF.getData(imgEl, async function() {
  // http://sylvana.net/jpegcrop/exif_orientation.html
  const orientation = EXIF.getTag(this, 'Orientation');
  const [width, height] = (orientation > 4) 
                  ? [ imgEl.naturalWidth, imgEl.naturalHeight ]
                  : [ imgEl.naturalHeight, imgEl.naturalWidth ];

  canvas.width = width;
  canvas.height = height;
  const context = canvas.getContext('2d');
  // We have to get the correct orientation for the image
  // See also https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images
  switch(orientation) {
    case 2: context.transform(-1, 0, 0, 1, width, 0); break;
    case 3: context.transform(-1, 0, 0, -1, width, height); break;
    case 4: context.transform(1, 0, 0, -1, 0, height); break;
    case 5: context.transform(0, 1, 1, 0, 0, 0); break;
    case 6: context.transform(0, 1, -1, 0, height, 0); break;
    case 7: context.transform(0, -1, -1, 0, height, width); break;
    case 8: context.transform(0, -1, 1, 0, 0, width); break;
  }
  context.drawImage(imgEl, 0, 0);
}

E Voila, se você compartilhar uma imagem para o aplicativo, ele irá girar a imagem e, em seguida, analisá-la retornando a saída do texto que encontrou.

Foi incrivelmente divertido criar este pequeno experimento, e foi imediatamente útil para mim. No entanto, destaca as inconsistency of the web platform . Essas APIs não estão disponíveis em todos os navegadores, nem estão disponíveis em todas as versões do Chrome. Isso significa que, enquanto escrevo este artigo, o SO Chrome não é possível usar o aplicativo, mas, ao mesmo tempo, quando posso usá-lo … OMG, tão legal.

Wood Carving found in Engakuji Shrine near Kamakura

Sakura

Paul Kinlan

Me disseram que mais especificamente que isso é 'Yaezakura'

Read More

Debugging Web Pages on the Nokia 8110 with KaiOS using Chrome OS

Paul Kinlan

Esta postagem é uma continuação da postagem sobre a depuração de KaiOS device with Web IDE , mas, em vez de usar o macOS, agora você pode usar o Chrome OS (m75) com o Crostini. Eu estou cribbing do KaiOS Environment Setup que é um bom começo, mas não o suficiente para começar com o Chrome OS e Crostini. Abaixo está o guia aproximado que eu segui. Certifique-se de que você está usando pelo menos o Chrome OS m75 (atualmente o canal de desenvolvimento a partir de 15 de abril) e, em seguida:

Read More

New WebKit Features in Safari 12.1 | WebKit

Paul Kinlan

Grandes atualizações para o mais recente Safari!

Eu pensei que este foi um anúncio muito grande, e o oposto do Google, que há pouco tempo disse que o Google Pay Lib é a maneira recomendada de implementar pagamentos … Quero dizer, não é um milhão de milhas de distância, o Google Pay é construído no topo Pedido de pagamento, mas não é PR primeiro.

Payment Request is now the recommended way to pay implement Apple Pay on the web.

Read full post .

E meu recurso favorito, dada a minha história com o Web Intents.

Web Share API

The Web Share API adds navigator.share(), a promise-based API developers can use to invoke a native sharing dialog provided the host operating system. This allows users to share text, links, and other content to an arbitrary destination of their choice, such as apps or contacts.

Agora, apenas para obter a API do Target de compartilhamento e somos um vencedor! :)

Offline fallback page with service worker

Paul Kinlan

Anos atrás, fiz algumas pesquisas sobre como os aplicativos nativos responderam à falta de conectividade de rede. Embora eu tenha perdido o link para a análise (eu poderia jurar que estava no Google+), a narrativa abrangente era que muitos aplicativos nativos estão inextricavelmente ligados à internet e que eles apenas se recusam a funcionar. Parece um monte de aplicativos web, o que os diferencia da web é que a experiência ainda era 'on-brand', Bart Simpson diria que você precisa estar online (por exemplo), e ainda para o Na grande maioria das experiências na web, você obtém um 'Dino' (veja chrome: // dino).

Estamos trabalhando no Service Worker há muito tempo e, embora vejamos cada vez mais sites com páginas controladas por um Service Worker, a grande maioria dos sites nem sequer tem uma experiência básica de fallback quando a rede não é acessível.

Eu perguntei ao meu bom amigo, Jake, se temos alguma orientação sobre como construir uma página genérica de retorno, supondo que você não queira criar uma primeira experiência totalmente off-line, e em 10 minutos ele a criou. Check it out .

Para simplificar, colei o código abaixo porque ele tem apenas 20 linhas. Ele armazena em cache os ativos off-line e, para cada busca que é uma busca de 'navegação', ela verifica se há erros (por causa da rede) e, em seguida, renderiza a página off-line no lugar do conteúdo original.

addEventListener('install', (event) => {
  event.waitUntil(async function() {
    const cache = await caches.open('static-v1');
    await cache.addAll(['offline.html', 'styles.css']);
  }());
});

// See https://developers.google.com/web/updates/2017/02/navigation-preload#activating_navigation_preload
addEventListener('activate', event => {
  event.waitUntil(async function() {
    // Feature-detect
    if (self.registration.navigationPreload) {
      // Enable navigation preloads!
      await self.registration.navigationPreload.enable();
    }
  }());
});

addEventListener('fetch', (event) => {
  const { request } = event;

  // Always bypass for range requests, due to browser bugs
  if (request.headers.has('range')) return;
  event.respondWith(async function() {
    // Try to get from the cache:
    const cachedResponse = await caches.match(request);
    if (cachedResponse) return cachedResponse;

    try {
      // See https://developers.google.com/web/updates/2017/02/navigation-preload#using_the_preloaded_response
      const response = await event.preloadResponse;
      if (response) return response;

      // Otherwise, get from the network
      return await fetch(request);
    } catch (err) {
      // If this was a navigation, show the offline page:
      if (request.mode === 'navigate') {
        return caches.match('offline.html');
      }

      // Otherwise throw
      throw err;
    }
  }());
});

Isso é tudo. Quando o usuário está online, ele verá a experiência padrão.

E quando o usuário estiver offline, ele receberá a página de fallback.

Eu acho este script simples incrivelmente poderoso, e sim, enquanto ele ainda pode ser melhorado, eu acredito que mesmo apenas uma simples mudança na maneira como falamos com nossos usuários quando há um problema com a rede tem a capacidade de melhorar fundamentalmente a percepção da web para usuários em todo o mundo.

** Atualização ** Jeffrey Posnick kinldy me lembrou sobre o uso do Pré-carregamento de Navegação para não ter que esperar pela inicialização do SW para todas as solicitações, isso é especialmente importante se você estiver controlando apenas solicitações de rede failed.

testing block image upload

Paul Kinlan

Este é apenas um teste para ver se recebi o upload da imagem corretamente. Se você ver isso, então sim eu fiz :)

Read More

Editor.js

Paul Kinlan

Eu atualizei pelo editor Hugo para tentar usar o EditorJS como, bem, o editor do blog.

Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor’s Core.

Read full post .

Eu acho que funciona.

Eu lutei um pouco com a base de código, os exemplos usam todos os módulos ES, no entanto, o NPM dist é todo produzido no código II ES5. Mas uma vez que superei esse obstáculo, foi muito fácil construir uma interface de usuário que parece um pouco mais com mídia.

Debugging Web Pages on the Nokia 8110 with KaiOS

Paul Kinlan

Nós temos feito muito desenvolvimento em feature phones recentemente e tem sido difícil, mas divertido. O mais difícil é que no KaiOS achamos impossível depurar páginas da web, especialmente no hardware que tínhamos (o Nokia 8110). A Nokia é um ótimo dispositivo, é construído com KaiOS que sabemos que é baseado em algo semelhante ao Firefox 48, mas está bloqueado, não há modo de desenvolvedor tradicional como você entrar em outros dispositivos Android, o que significa que você não pode conectar o Firefox WebIDE facilmente.

Read More

Object Detection and Augmentation

Paul Kinlan

Eu tenho tocado muito com o Shape Detection API no Chrome e eu realmente gosto do potencial que ele tem, por exemplo, um QRCode detector muito simples que escrevi há muito tempo tem um polyfill JS, mas usa a API new BarcodeDetector() se estiver disponível.

Você pode ver algumas das outras demos que construí aqui usando os outros recursos da API de detecção de formas: Face Detection , Barcode Detection e Text Detection .

Fiquei agradavelmente surpreso quando tropecei no Jeeliz no fim de semana e fiquei incrivelmente impressionado com o desempenho de seu kit de ferramentas - sabendo que estava usando um Pixel3 XL, mas a detecção de rostos pareceu significativamente mais rápida do que é possível com a API FaceDetector .

Checkout some of their demos .

Isso me fez pensar muito. Esse kit de ferramentas para Detecção de Objetos (e outros) usa APIs amplamente disponíveis na Web, especificamente acesso à câmera, WebGL e WASM, que, ao contrário da API de Detecção de Forma do Chrome (que é apenas no Chrome e não consistente em todas as plataformas em que o Chrome está) ) pode ser usado para criar experiências ricas facilmente e alcançar bilhões de usuários com uma experiência consistente em todas as plataformas.

Aumento é onde fica interessante (e realmente o que eu queria mostrar neste post) e onde você precisa de bibliotecas de middleware que estão chegando agora à plataforma, podemos construir os divertidos aplicativos de filtro de face sem os usuários instalarem aplicativos MASSIVE que coletam uma enorme quantidade de dados do dispositivo do usuário (porque não há acesso subjacente ao sistema).

Fora das demonstrações divertidas, é possível resolver casos de uso muito avançados de forma rápida e simples para o usuário, como:

  • Seleção de texto diretamente da câmera ou foto do usuário
  • Tradução ao vivo de idiomas da câmera
  • Detecção Inline QRCode para que as pessoas não precisem abrir o WeChat o tempo todo :)
  • Auto extrair URLs do site ou endereço de uma imagem
  • Detecção de cartão de crédito e extração de números (faça com que os usuários se inscrevam em seu site com mais rapidez)
  • Pesquisa visual de produtos na aplicação Web da sua loja.
  • Pesquisa de código de barras para mais detalhes do produto em seu aplicativo da web de lojas.
  • Recorte rápido de fotos de perfil no rosto das pessoas.
  • Recursos simples do A11Y para permitir que o usuário ouça o texto encontrado nas imagens.

Eu passei apenas 5 minutos pensando nesses casos de uso - eu sei que há muito mais -, mas me ocorreu que não vemos muitos sites ou aplicativos da web usando a câmera, em vez disso, vemos muitos sites perguntando por eles. os usuários fazem o download de um aplicativo, e acho que não precisamos mais fazer isso.

** Atualização ** Thomas Steiner em nossa equipe mencionou em nosso bate-papo da equipe que parece que eu não gosto da API atual do ShapeDetection . Eu adoro o fato de que essa API nos dá acesso às implementações nativas de envio de cada um dos respectivos sistemas, no entanto, como escrevi em The Lumpy Web , os desenvolvedores da Web desejam consistência na plataforma e há vários problemas com a API de detecção de forma que podem ser resumido como:

  1. A API é apenas no Chrome
  2. A API no Chrome é muito diferente em todas as plataformas porque suas implementações subjacentes são diferentes. O Android só tem pontos para pontos de referência como boca e olhos, onde o macOS tem contornos. No Android o TextDetector retorna o texto detectado, onde como no macOS ele retorna um indicador 'Presença de Texto' … Isso sem mencionar todos os bugs que o Surma encontrou.

A web como plataforma de distribuição faz tanto sentido para experiências como essas que acho que seria negligente não fazê-lo, mas os dois agrupamentos de questões acima me levam a questionar a necessidade de longo prazo de implementar todos os recursos em a plataforma web nativamente, quando pudemos implementar boas soluções em um pacote que é enviado usando os recursos da plataforma hoje, como WebGL, WASM e no futuro Web GPU.

De qualquer forma, eu amo o fato de que podemos fazer isso na web e eu estou olhando para a frente para ver sites enviados com eles.

Got web performance problems? Just wait...

Paul Kinlan

Eu vi um tweet de um bom amigo e colega, Mariko , sobre testes em uma série de dispositivos de baixo custo, mantendo você realmente ancorado.

O contexto do tweet é que estamos vendo como é o desenvolvimento da Web ao criar para usuários que vivem diariamente nessas classes de dispositivos.

A equipe está fazendo muito trabalho agora neste espaço, mas passei um dia construindo um site e foi incrivelmente difícil fazer qualquer coisa funcionar em um nível um pouco razoável de desempenho - aqui estão alguns dos problemas que encontrei:

  • Esquisitices na porta de visualização e misteriosa reintrodução de 300ms de atraso de clique (pode funcionar).
  • Enormes repaints de tela inteira, e é lento.
  • Rede é lenta
  • A memória é restrita e os GCs subsequentes bloqueiam o thread principal por vários segundos
  • Execução JS incrivelmente lenta
  • A manipulação de DOM é lenta

Para muitas das páginas que eu estava criando, mesmo em uma conexão rápida, as páginas demoravam vários segundos para serem carregadas, e as interações subsequentes eram simplesmente lentas. Foi difícil, envolvi tentar obter o máximo possível do tópico principal, mas também foi incrivelmente gratificante em nível técnico ver mudanças nos algoritmos e na lógica que eu não teria feito em todo o meu desenvolvimento tradicional da Web, rendimento grandes melhorias no desempenho.

Não sei o que fazer a longo prazo, suspeito que uma grande quantidade de desenvolvedores com quem trabalhamos nos mercados mais desenvolvidos terá uma reação: "Não estou construindo sites para usuários em [inserir país x]" e De alto nível, é difícil argumentar com essa afirmação, mas não posso ignorar o fato de que 10 milhões de novos usuários estão chegando à computação todos os anos e eles usarão esses dispositivos e queremos que a Web seja a * plataforma * de escolha para conteúdo e aplicativos para que não fiquemos felizes com o rise of the meta platform .

Precisamos continuar pressionando o desempenho por muito tempo. Continuaremos criando ferramentas e orientações para ajudar os desenvolvedores a carregar rapidamente e ter interfaces de usuário suaves :)

Browser Bug Searcher

Paul Kinlan

Eu estava apenas refletindo sobre algumas das work our team has done e encontrei um projeto de 2017 que Robert Nyman e Eric Bidelman criaram. Browser Bug Searcher! .

É incrível que, com apenas algumas teclas, você tenha uma excelente visão geral dos seus recursos favoritos em todos os principais mecanismos de navegação.

Source code available .

Isso realmente destaca um dos problemas que tenho com os rastreadores de bugs crbug e webkit, eles não têm uma maneira simples de obter feeds de dados em formatos como RSS. Eu adoraria poder usar o meu agregador topicdeck com categorias de bugs, etc, então eu tenho um painel de todas as coisas que eu estou interessado em com base nas informações mais recentes de cada um dos rastreadores de bugs.

Github's Web Components

Paul Kinlan

Eu estava procurando por um editor de markdown rápido em https://www.webcomponents.org/ para que eu pudesse facilitar a postagem neste blog e me deparei com um conjunto de componentes por github .

Eu sabia que eles tinham o <time-element> mas eu não sabia que eles tinham um conjunto tão bom e simples de elementos úteis.

London from Kingscross

The GDPR mess

Paul Kinlan

A forma como nós (como indústria) implementamos o consentimento do GDPR é uma bagunça. Não sei por que alguém escolheria outra coisa senão "Usar apenas cookies necessários", no entanto, eu realmente não posso dizer a diferença entre uma ou outra opção e o compromisso de qualquer escolha, para não mencionar que posso verificar que é apenas usando apenas os cookies necessários.

Read More

Brexit: History will judge us all

Paul Kinlan

A história julgará a todos nós nesta confusão, e espero que seja um estudo de caso para todos sobre os efeitos do nacionalismo, do interesse próprio, da arrogância colonial, da celebridade-bafoonery.

Filhos da puta.

File Web Share Target

Paul Kinlan

Eu sempre disse que para os aplicativos da web competirem efetivamente no mundo dos aplicativos, eles precisam estar integrados em todos os lugares que os usuários esperam que os aplicativos sejam. A comunicação entre aplicativos é uma das principais peças que faltam na plataforma da Web e, especificamente, um dos principais recursos ausentes é o compartilhamento em nível nativo: os aplicativos da Web precisam conseguir o data out of their silo em outros sites e aplicativos da Web; eles também precisam receber os dados de outros aplicativos e sites nativos.

Read More

Testing-file-share-target-from-camera

Paul Kinlan

Isso está testando o compartilhamento diretamente do aplicativo da câmera. Parece que funcionou :)

Read More