Mídia

Este guia apresenta uma visão geral dos formatos de vídeo e áudio suportados, descreve como controlar o áudio, obtém informações sobre webcams e microfones disponíveis, etc.

Codecs

Google Chrome e Chromium diferem de várias formas, incluindo os conjuntos de codecs de áudio e vídeo que suportam.

A tabela abaixo mostra quais os codecs suportados pela base de código dos navegadores correspondentes.

  Chromium Google Chrome
AAC   sim
AV1 sim sim
FLAC sim sim
H.264   sim
HEVC   sim
MP3 sim sim
Opus sim sim
Theora sim sim
Vorbis sim sim
VP8 sim sim
VP9 sim sim
WAV sim sim


Como você pode ver, o Google Chrome suporta determinados codecs que o Chromium não suporta. O motivo é que estes codecs são proprietários e não podem ser utilizados num projeto de código aberto ou comercial sem a obtenção de licenças dos titulares das patentes correspondentes.

Os diferentes codecs têm diferentes detentores de patentes. Por exemplo, para utilizar o H.264, as empresas têm de adquirir a licença da empresa MPEG-LA. Você pode ler mais sobre os termos da licença no site site do MPEG-LA.

Codecs Proprietários

Os detentores de patentes não licenciam codecs para o software que representa apenas uma parte do produto final implementado para os usuários finais, por exemplo, bibliotecas como o JxBrowser.

Para suportar H.264 e AAC nos seus produtos, é necessário adquirir as licenças adequadas e ativar as seguintes funcionalidades proprietárias:

Engine engine = Engine.newInstance(
        EngineOptions.newBuilder(renderingMode)
                .enableProprietaryFeature(ProprietaryFeature.AAC)
                .enableProprietaryFeature(ProprietaryFeature.H_264)
                .build());
val engine = Engine.newInstance(
        EngineOptions.newBuilder(renderingMode)
                .enableProprietaryFeature(ProprietaryFeature.AAC)
                .enableProprietaryFeature(ProprietaryFeature.H_264)
                .build())

Com a licença e as funcionalidades proprietárias ativadas, será possível carregar páginas Web com os formatos AAC e H.264, e reproduzir arquivos de áudio e vídeo, tal como no Google Chrome. Por padrão, os codecs proprietários estão desativados.

Importante: Os codecs H.264 e AAC são os componentes proprietários. Ao ativar estes codecs, você declara estar ciente de que o H.264 e o AAC são componentes proprietários e que deve ter uma licença para utilizá-los. Para mais informações, você pode contatar os titulares das patentes: Via Licensing e MPEG LA. A TeamDev não será responsável pela sua utilização dos codecs H.264 e AAC.

Video

O JxBrowser suporta totalmente o elemento <video> HTML5 e pode reproduzir vídeo nos formatos suportados.

Se a biblioteca não conseguir reproduzir um vídeo ou se um formato de vídeo não for suportado, o JxBrowser sugere o download do arquivo de vídeo. Consulte Downloads para obter orientações sobre o gerenciamento de downloads.

Vídeo HTML5

Audio

Controle de Áudio

Utilizando Audio é possível saber se o áudio está sendo reproduzido na página web carregada:

boolean audioPlaying = audio.isPlaying();
val audioPlaying = audio.isPlaying()

Se necessário, você pode ativar ou desativar o áudio na página Web carregada:

audio.mute();
audio.unmute();
audio.mute()
audio.unmute()

Para verificar se o áudio está silenciado, utilize o seguinte código:

boolean audioMuted = audio.isMuted();
val audioMuted = audio.isMuted()

Eventos de Áudio

Para saber se o áudio começou/parou de ser reproduzido na página Web carregada, pode subscrever os seguintes eventos:

browser.on(AudioStartedPlaying.class, event -> {});
browser.on(AudioStoppedPlaying.class, event -> {});
browser.on(AudioStartedPlaying::class.java) { event -> }
browser.on(AudioStartedPlaying::class.java) { event -> }

DRM

Widevine

Os serviços Web como Netflix ou Amazon Prime utilizam Widevine para distribuir os seus conteúdos codificados com DRM. Widevine é um componente propriedade da Google que é desativado por padrão. Para o ativar e reproduzir o conteúdo codificado por DRM, utilize a seguinte abordagem:

Engine engine = Engine.newInstance(
        EngineOptions.newBuilder(renderingMode)
                .enableProprietaryFeature(ProprietaryFeature.WIDEVINE)
                .build());
val engine = Engine.newInstance(
        EngineOptions.newBuilder(renderingMode)
                .enableProprietaryFeature(ProprietaryFeature.WIDEVINE)
                .build())

A versão do Chromium utilizada pela biblioteca suporta o Widevine apenas nas plataformas Windows e macOS. Não é suportado no Linux. Assim que o Chromium ativar o suporte do Widevine no Linux, também o ativaremos no JxBrowser.

Importante: Widevine é um componente proprietário da Google, regido pelos seus próprios termos de utilização. Para mais informações, consultar https://www.widevine.com/.

Câmera e Microfone

O JxBrowser suporta webcam e microfone.

Pode obter informações sobre todos os dispositivos de fluxo multimídia disponíveis utilizando o seguinte código:

MediaDevices mediaDevices = engine.mediaDevices();

// Obter todos os dispositivos de vídeo disponíveis, por exemplo, webcams.
List<MediaDevice> videoDevices = mediaDevices.list(MediaDeviceType.VIDEO_DEVICE);

// Obter todos os dispositivos de áudio disponíveis, por exemplo, microfones.
List<MediaDevice> audioDevices = mediaDevices.list(MediaDeviceType.AUDIO_DEVICE);

val mediaDevices = engine.mediaDevices()

// Obter todos os dispositivos de vídeo disponíveis, por exemplo, webcams.
val videoDevices = mediaDevices.list(MediaDeviceType.VIDEO_DEVICE)

// Obter todos os dispositivos de áudio disponíveis, por exemplo, microfones.
val audioDevices = mediaDevices.list(MediaDeviceType.AUDIO_DEVICE)

É possível detectar quando uma captura de mídia começa ou para de utilizar estes eventos:

browser.on(MediaStreamCaptureStarted.class, e -> {
    System.out.println("Started capturing " + e.mediaStreamType());
});

browser.on(MediaStreamCaptureStopped.class, e -> {
    System.out.println("Stopped capturing " + e.mediaStreamType());
});

browser.on(MediaStreamCaptureStarted::class.java) { e -> 
    println("Started capturing " + e.mediaStreamType())
}

browser.on(MediaStreamCaptureStopped::class.java) { e -> 
    println("Stopped capturing " + e.mediaStreamType())
}

Seleção do Dispositivo Multimídia

Quando uma página Web deseja utilizar uma webcam ou um microfone, você pode utilizar SelectMediaDeviceCallback para indicar à página Web qual dispositivo utilizar.

O exemplo seguinte demonstra como selecionar o primeiro dispositivo da lista de dispositivos disponíveis:

mediaDevices.set(SelectMediaDeviceCallback.class, params -> 
        Response.select(params.mediaDevices().get(0)));
mediaDevices.set(SelectMediaDeviceCallback::class.java,
    SelectMediaDeviceCallback { params ->
        Response.select(params.mediaDevices().first())
    }
)

A chamada de retorno não será invocada se não existirem dispositivos de entrada multimídia do tipo solicitado.

Para desativar o acesso a microfones e webcams, utilize RequestPermissionCallback como indicado abaixo:

engine.permissions().set(RequestPermissionCallback.class, (params, tell) -> {
    PermissionType type = params.permissionType();
    if (type == PermissionType.VIDEO_CAPTURE || type == PermissionType.AUDIO_CAPTURE) {
        tell.deny();
    } else {
        tell.grant();
    }
});
engine.permissions().set(RequestPermissionCallback::class.java,
    RequestPermissionCallback { params, tell ->
        val type = params.permissionType()
        if (type == PermissionType.VIDEO_CAPTURE || type == PermissionType.AUDIO_CAPTURE) {
            tell.deny()
        } else {
            tell.grant()
        }
    }
)

Transmissão

O Chromium tem uma funcionalidade integrada que permite a transmissão de conteúdos multimídia para dispositivos que suportam diferentes tecnologias sem fios, como Chromecast, Miracast, DLNA, AirPlay ou similares. Podem ser smart TVs, projetores e outros dispositivos.

O Diagrama Cast

Etapa Preliminar

Por padrão, desativamos o Chromium de procurar dispositivos multimídia na sua rede. Para ativar e permitir que o Chromium encontre os potenciais receptores, utilize a opção do Motor:

EngineOptions options = EngineOptions.newBuilder(renderingMode)
                                     .enableMediaRouting()
                                     .build();
Engine engine = Engine.newInstance(options);
val options = EngineOptions.newBuilder(renderingMode)
                           .enableMediaRouting()
                           .build()
val engine = Engine.newInstance(options)

Receptores Multimídia

Para começar a transmitir conteúdos multimídia para um receptor, é necessário obter um. Para este efeito, o JxBrowser fornece um serviço de perfil separado MediaReceivers que pode ser obtido desta forma:

MediaReceivers mediaReceivers = profile.mediaCasting().mediaReceivers();
val mediaReceivers = profile.mediaCasting().mediaReceivers()

Para saber quando um novo receptor foi descoberto, o JxBrowser fornece o evento MediaReceiverDiscovered:

MediaReceivers mediaReceivers = profile.mediaCasting().mediaReceivers();
mediaReceivers.on(MediaReceiverDiscovered.class, event -> {
    MediaReceiver receiver = event.mediaReceiver();
});
val mediaReceivers = profile.mediaCasting().mediaReceivers()
mediaReceivers.on(MediaReceiverDiscovered::class.java) { event -> 
    val receiver = event.mediaReceiver()
}

Para sua conveniência, o JxBrowser mantém um registro dos receptores descobertos. Se desejar obter a lista de receptores multimídia descobertos atualmente, utilize o método MediaReceivers.list():

MediaReceivers mediaReceivers = profile.mediaCasting().mediaReceivers();
List<MediaReceiver> receivers = mediaReceivers.list();
val mediaReceivers: MediaReceivers = profile.mediaCasting().mediaReceivers()
val receivers: List<MediaReceiver> = mediaReceivers.list()

Se estiver à procura de um receptor específico, você pode obtê-lo através do método de conveniência MediaReceivers.await(Predicate<MediaReceiver>). Ele aguarda até que seja descoberto o primeiro receptor que corresponda ao predicado e o retorna.

MediaReceivers mediaReceivers = profile.mediaCasting().mediaReceivers();
MediaReceiver receiver = mediaReceivers.await(it -> it.name().equals("Samsung Smart TV"));
val mediaReceivers = profile.mediaCasting().mediaReceivers()
val receiver = mediaReceivers.await { it.name() == "Samsung Smart TV" }

Para detectar que o receptor multimídia foi desconectado, ou seja, desligado ou desconectado da rede, utilize o evento MediaReceiverDisconnected:

receiver.on(MediaReceiverDisconnected.class, event -> {
    MediaReceiver mediaReceiver = event.mediaReceiver();
});
receiver.on(MediaReceiverDisconnected::class.java) { event -> 
    val mediaReceiver = event.mediaReceiver()
}

Transmitindo Conteúdo

A API JxBrowser permite a conversão de conteúdos de navegadores, telas e apresentação utilizando a API de apresentação JavaScript.

Os receptores de multimídia podem suportar diferentes fontes de multimídia. Uma fonte multimídia representa um tipo de conteúdo que pode ser transmitido a um receptor multimídia. Antes de iniciar a transmissão, certifique-se de que o receptor multimídia selecionado suporta a fonte multimídia correspondente.

Criando um Navegador

Para começar a transmitir o conteúdo do navegador, utilize o método Browser.cast(MediaReceiver):

MediaReceiver receiver = mediaReceivers.await(it -> it.name().contains("Samsung"));
if (receiver.supports(MediaSource.browser())) {
    CompletableFuture<CastSession> future = browser.cast(receiver);
}
val receiver = mediaReceivers.await { it.name().contains("Samsung")) }
if (receiver.supports(MediaSource.browser())) {
    val future: CompletableFuture<CastSession> = browser.cast(receiver)
}

Cada sessão de transmissão de conteúdos multimídia para um receptor multimídia é representada por uma instância da classe CastSession.

Request de Apresentação Padrão

Se a página Web contiver o PresentationRequest padrão, o browser começa a transmitir o conteúdo especificado neste request ao invés do conteúdo do navegador.

Para verificar se o navegador contém o PresentationRequest padrão, use:

MediaReceiver receiver = mediaReceivers.await(it -> it.name().contains("Samsung Smart TV"));
browser.defaultPresentationRequest().ifPresent(presentationRequest -> {
    if (receiver.supports(presentationRequest)) {
        CompletableFuture<CastSession> future = browser.cast(receiver);
    }
});
val receiver = mediaReceivers.await { it.name().contains("Samsung Smart TV") }
browser.defaultPresentationRequest().ifPresent {
    if (receiver.supports(it)) {
        val future: CompletableFuture<CastSession> = browser.cast(receiver)
    }
}

Transmitindo uma Tela

Para iniciar a transmissão do conteúdo da tela, utilize Browser.castScreen(MediaReceiver). Este método mostra uma caixa de diálogo padrão do Chromium para escolher a tela a transmitir.

MediaReceiver receiver = mediaReceivers.await(it -> it.name().contains("Samsung"));
if (receiver.supports(MediaSource.screen())) {
    CompletableFuture<CastSession> future = browser.castScreen(receiver);
}
val receiver = mediaReceivers.await { it.name().contains("Samsung")) }
if (receiver.supports(MediaSource.screen())) {
    val future: CompletableFuture<CastSession> = browser.castScreen(receiver)
}

Se desejar selecionar a tela de forma programática, utilize o método Browser.castScreen(MediaReceiver, ScreenCastOptions). Encontre a tela que necessita utilizando o serviço Screens:

MediaReceiver receiver = mediaReceivers.await(it -> it.name().contains("Samsung"));
Screen screen = profile.mediaCasting().screens().defaultScreen();
ScreenCastOptions options = ScreenCastOptions.newBuilder(screen)
                                             .withAudio()
                                             .build();
if (receiver.supports(MediaSource.screen())) {
    CompletableFuture<CastSession> future = browser.castScreen(receiver, options);
}
val receiver = mediaReceivers.await { it.name().contains("Samsung")) }
val screen = profile.mediaCasting().screens().defaultScreen()
val options = ScreenCastOptions.newBuilder(screen)
                               .withAudio()
                               .build()
if (receiver.supports(MediaSource.screen())) {
    val future: CompletableFuture<CastSession> = browser.castScreen(receiver, options)
}

Por enquanto, o Chromium suporta a transmissão de áudio apenas no Windows. Por isso, ativá-lo no macOS/Linux através de ScreenCastOptions.Builder.withAudio() não é uma opção. No Windows, ao selecionar a tela na caixa de diálogo do seletor, o Chromium fornece uma caixa de verificação separada para selecionar a transmissão de áudio.

API de Apresentação

O JxBrowser permite trabalhar com API de Apresentação JavaScript.

Quando o método PresentationRequest.start() é chamado do lado do JavaScript, o JxBrowser invoca o StartPresentationCallback onde você pode decidir iniciar ou cancelar a apresentação.

Para iniciar a apresentação a um receptor, utilize o método StartPresentationCallback.Action.start(MediaReceiver):

browser.set(StartPresentationCallback.class, (params, tell) -> {
    MediaReceiver receiver = params.mediaReceivers().await(it -> {
        return it.name().contains("Samsung");
    });
    if (receiver.supports(params.presentationRequest())) {
        tell.start(receiver);
    } else {
        tell.cancel();
    }
});
browser.set(StartPresentationCallback::class.java,
    StartPresentationCallback { params, tell -> 
        val receiver = params.mediaReceivers().await { it.name().contains("Samsung") }
        if (receiver.supports(params.presentationRequest())) {
            tell.start(receiver)
        } else {
            tell.cancel()
        }
    }
)

Descobrindo Sessões de Transmissão

Para ser notificado quando uma sessão de transmissão for descoberta, o JxBrowser fornece o evento CastSessionDiscovered:

profile.mediaCasting().castSessions().on(CastSessionDiscovered.class, event -> {
    CastSession castSession = event.castSession();
});
profile.mediaCasting().castSessions().on(CastSessionDiscovered::class.java) { event -> 
    val castSession = event.castSession()
}

O Chromium pode descobrir sessões iniciadas por outras aplicações ou instâncias do Chromium. Para indicar que a sessão de transmissão foi iniciada por este perfil, o JxBrowser fornece o método CastSession.isLocal(). Assim, se uma sessão de transmissão for iniciada por outro perfil ou mesmo outro processo do Chromium, o método retornará false.

Interrompendo Sessões de Transmissão

Para parar uma sessão de transmissão, utilize o método CastSession.stop(). Se desejar ser notificado quando uma sessão de transmissão tiver sido interrompida, utilize o evento CastSessionStopped:

CastSession session = profile.mediaCasting().castSessions().list().get(0);
session.on(CastSessionStopped.class, event -> {
    // Do something
});
...
session.stop();
val session = profile.mediaCasting().castSessions().list().first()
session.on(CastSessionStopped::class.java) { event -> 
    // Do something
}
...
session.stop()

A sessão pode ser interrompida por outras aplicações ou instâncias do Chromium, ou seja Google Chrome. Neste caso, o evento também será invocado.

Falhas

Por vezes, o Chromium pode não conseguir iniciar uma nova sessão de transmissão, ou seja, se o receptor multimídia não for encontrado ou se o tiver sido subitamente desconectado. Para detectar isso, use o evento CastSessionStartFailed:

MediaReceiver receiver = mediaReceivers.await(it -> it.name().contains("Samsung"));
profile.mediaCasting().castSessions().on(CastSessionStartFailed.class, event -> {
    System.out.println(event.errorMessage());
});
CompletableFuture<CastSession> future = browser.cast(receiver);
val receiver = mediaReceivers.await { it.name().contains("Samsung") }
profile.mediaCasting().castSessions().on(CastSessionStartFailed::class.java) { event ->
    println(event.errorMessage())
}
val future: CompletableFuture<CastSession> = browser.cast(receiver)

Trata-se de um acontecimento intencional devido à natureza assíncrona da transmissão de mídia.

Como os métodos Browser.cast... retornam CompletableFuture é possível detectar que o início da sessão de transmissão falhou. Neste caso, o JxBrowser conclui o futuro com a CastStartFailedException:

CompletableFuture<CastSession> future = browser.cast(receiver);
future.exceptionally(throwable -> {
    System.out.println(throwable.getMessage());
    return null;
});
val future: CompletableFuture<CastSession> = browser.cast(receiver)
future.exceptionally { throwable -> 
    println(throwable.message)
    null
}
Go Top