Media
本指南概述了 Fuzio 支持的视频和音频格式,并介绍了如何控制音频、获取可用的网络摄像头和麦克风信息等内容。
编解码器
Google Chrome 和 Chromium 在几个方面存在差异,包括它们所支持的音频和视频编解码器集合。
下表展示了不同浏览器代码库所支持的编解码器类型。
| Codec | Chromium | Google Chrome |
|---|---|---|
| AAC | ✓ | |
| AV1 | ✓ | ✓ |
| FLAC | ✓ | ✓ |
| H.264 | ✓ | |
| HEVC | ✓ | |
| MP3 | ✓ | ✓ |
| Opus | ✓ | ✓ |
| Theora | ✓ | ✓ |
| Vorbis | ✓ | ✓ |
| VP8 | ✓ | ✓ |
| VP9 | ✓ | ✓ |
| WAV | ✓ | ✓ |
如你所见,Google Chrome 支持某些 Chromium 不支持的编解码器。这是因为这些编解码器属于专有编解码器,在未获得相应专利持有方许可的情况下,无法在开源项目或商业项目中使用
不同的编解码器由不同的专利持有方管理。例如,若要使用 H.264 编解码器,企业需要从 Via LA 公司获取相应的授权。你可以在其官方网站上了解更多许可条款。
专有编解码器
专利持有方通常不会将编解码器授权给仅作为最终产品组成部分的软件,例如像 Fuzio 这样的库。
若要在你的产品中支持专有编解码器,你需要自行获取相应的许可证,并启用以下专有功能:
var engine = Engine.newInstance(
EngineOptions.newBuilder(renderingMode)
.enableProprietaryFeature(ProprietaryFeature.AAC)
.enableProprietaryFeature(ProprietaryFeature.H_264)
.enableProprietaryFeature(ProprietaryFeature.HEVC)
.build()
);
val engine = Engine(renderingMode) {
proprietaryFeatures = setOf(
ProprietaryFeature.AAC,
ProprietaryFeature.H_264,
ProprietaryFeature.HEVC
)
}
在获得许可证并启用上述专有功能后,你将能够加载包含 AAC、HEVC 和 H.264 格式的网页,并播放音频和视频文件,其行为与 Google Chrome 一致。默认情况下,专有编解码器是被禁用的。
重要提示: H.264、HEVC 和 AAC 均属于专有组件。启用这些编解码器即表示你已知晓它们属于专有组件,并确认你已获得使用这些编解码器所需的许可证。如需更多信息,请联系相关专利持有方:Via LA Licensing。Jiku 对你使用 H.264、HEVC 和 AAC 编解码器的行为不承担任何责任。
视频
Fuzio 完全支持 HTML5 的 <video> 元素,并可播放受支持的格式中的视频。
如果库无法播放某个视频,或该视频格式不受支持,Fuzio 会提示下载该视频文件。有关下载管理的说明,请参阅下载指南。

音频
音频控制
通过 Audio 接口,你可以判断当前加载的网页是否正在播放音频:
var audioPlaying = audio.isPlaying();
val audioPlaying = audio.isPlaying
如有需要,你可以将当前网页的音频设为静音或取消静音:
audio.mute();
audio.unmute();
audio.mute()
audio.unmute()
若要检查音频是否处于静音状态,可使用以下代码:
var audioMuted = audio.isMuted();
val audioMuted = audio.isMuted
音频事件
若要检测网页中的音频是否开始或停止播放,你可以订阅以下事件:
browser.audio().on(AudioStartedPlaying.class, event -> {});
browser.audio().on(AudioStoppedPlaying.class, event -> {});
val audio = browser.audio
audio.subscribe<AudioStartedPlaying> { event -> }
audio.subscribe<AudioStoppedPlaying> { event -> }
摄像头与麦克风
Fuzio 支持网络摄像头和麦克风。
你可以通过以下代码获取所有可用的媒体流设备信息:
var mediaDevices = engine.mediaDevices();
// 获取所有可用的视频设备,例如网络摄像头。
var videoDevices = mediaDevices.list(MediaDeviceType.VIDEO_DEVICE);
// 获取所有可用的音频设备,例如麦克风。
var audioDevices = mediaDevices.list(MediaDeviceType.AUDIO_DEVICE);
val mediaDevices = engine.mediaDevices
// 获取所有可用的视频设备,例如网络摄像头。
val videoDevices = mediaDevices.list(MediaDeviceType.VIDEO_DEVICE)
// 获取所有可用的音频设备,例如麦克风。
val audioDevices = mediaDevices.list(MediaDeviceType.AUDIO_DEVICE)
你可以通过以下事件检测媒体采集何时开始或停止:
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.subscribe<MediaStreamCaptureStarted> { event ->
println("Started capturing ${event.mediaStreamType()}")
}
browser.subscribe<MediaStreamCaptureStopped> { event ->
println("Stopped capturing ${event.mediaStreamType()}")
}
选择媒体设备
当网页希望使用网络摄像头或麦克风时,你可以使用 SelectMediaDeviceCallback 告诉网页应使用哪一个设备。
下面的示例演示了如何从可用设备列表中选择第一个设备:
mediaDevices.set(SelectMediaDeviceCallback.class, params ->
Response.select(params.mediaDevices().get(0))
);
mediaDevices.register(SelectMediaDeviceCallback { params ->
val firstDevice = params.mediaDevices().first()
Response.select(firstDevice)
})
如果所请求类型的媒体输入设备不存在,则该回调不会被调用。
如需禁用对麦克风和网络摄像头的访问,请按如下方式使用 RequestPermissionCallback:
engine.permissions().set(RequestPermissionCallback.class, (params, tell) -> {
var type = params.permissionType();
if (type == PermissionType.VIDEO_CAPTURE || type == PermissionType.AUDIO_CAPTURE) {
tell.deny();
} else {
tell.grant();
}
});
engine.permissions.register(RequestPermissionCallback { params, tell ->
val type = params.permissionType()
if (type == PermissionType.VIDEO_CAPTURE || type == PermissionType.AUDIO_CAPTURE) {
tell.deny()
} else {
tell.grant()
}
})
投屏
Chromium 内置了投屏功能,允许将媒体内容投射到支持不同无线技术的设备上,例如 Chromecast、Miracast、DLNA、AirPlay 等。相关设备可以是智能电视、投影仪或其他设备。

预备步骤
默认情况下,我们禁用 Chromium 扫描你的网络以查找媒体设备。若要启用它并让 Chromium 找到潜在的接收端,请使用以下 Engine 选项:
var options = EngineOptions.newBuilder(renderingMode)
.enableMediaRouting()
.build();
var engine = Engine.newInstance(options);
val engine = Engine(renderingMode) {
mediaRoutingEnabled = true
}
媒体接收端
要开始向某个接收端投屏媒体内容,你需要先获取接收端对象。为此,Fuzio 提供了一个独立的 Profile 服务 MediaReceivers,可按如下方式获取:
var mediaReceivers = profile.mediaCasting().mediaReceivers();
val mediaReceivers = profile.mediaCasting().mediaReceivers()
若要在发现新的接收端时获知该信息,Fuzio 提供了 MediaReceiverDiscovered 事件:
var mediaReceivers = profile.mediaCasting().mediaReceivers();
mediaReceivers.on(MediaReceiverDiscovered.class, event -> {
var receiver = event.mediaReceiver();
});
val mediaReceivers = profile.mediaCasting().mediaReceivers()
mediaReceivers.subscribe<MediaReceiverDiscovered> { event ->
val receiver = event.mediaReceiver()
}
为了便于使用,Fuzio 会跟踪已发现的接收端。如果你想获取当前已发现的媒体接收端列表,请使用 MediaReceivers.list() 方法:
var mediaReceivers = profile.mediaCasting().mediaReceivers();
var receivers = mediaReceivers.list();
val mediaReceivers = profile.mediaCasting().mediaReceivers()
val receivers = mediaReceivers.list()
如果你在查找某个特定接收端,可以通过 MediaReceivers.await(Predicate<MediaReceiver>) 便捷方法获取。该方法会等待直到发现第一个满足条件的接收端,然后将其返回。
var mediaReceivers = profile.mediaCasting().mediaReceivers();
var receiver = mediaReceivers.await(it -> it.name().equals("Samsung Smart TV"));
val mediaReceivers = profile.mediaCasting().mediaReceivers()
val receiver = mediaReceivers.await { it.name() == "Samsung Smart TV" }
为加快获取特定接收端的速度,你可以强制 Chromium 在后台刷新可用接收端列表。为此,请使用 MediaReceivers.refresh() 方法:
var mediaReceivers = profile.mediaCasting().mediaReceivers();
mediaReceivers.refresh();
val mediaReceivers = profile.mediaCasting().mediaReceivers()
mediaReceivers.refresh()
若要检测媒体接收端已断开连接(例如被拔掉或从网络断开),请使用 MediaReceiverDisconnected 事件:
receiver.on(MediaReceiverDisconnected.class, event -> {
var mediaReceiver = event.mediaReceiver();
});
receiver.subscribe<MediaReceiverDisconnected> { event ->
val mediaReceiver = event.mediaReceiver()
}
投屏内容
Fuzio API 允许通过 JavaScript Presentation API 投射浏览器内容、屏幕内容以及演示内容。
媒体接收端可以支持不同的媒体源(media source)。媒体源表示可投射到媒体接收端的内容类型。在开始投屏之前,请确保所选媒体接收端支持对应的媒体源。
投射浏览器内容
要投射浏览器内容,请使用 Browser.cast(MediaReceiver) 方法:
var receiver = mediaReceivers.await(it -> it.name().contains("Samsung"));
if (receiver.supports(MediaSource.browser())) {
var future = browser.cast(receiver);
}
val receiver = mediaReceivers.await { it.name().contains("Samsung") }
if (receiver.supports(MediaSource.browser())) {
val future: CompletableFuture<CastSession> = browser.cast(receiver)
}
每一次向媒体接收端投射媒体内容的会话,都由 CastSession 类的实例表示。
默认演示请求
如果网页包含默认 PresentationRequest,浏览器将投射该请求中指定的内容,而不是浏览器自身的内容。
要检查浏览器是否包含默认 PresentationRequest,请使用:
var receiver = mediaReceivers.await(it -> it.name().contains("Samsung Smart TV"));
browser.defaultPresentationRequest().ifPresent(request -> {
if (receiver.supports(request)) {
var future = browser.cast(receiver);
}
});
val receiver = mediaReceivers.await { it.name().contains("Samsung") }
browser.defaultPresentationRequest().ifPresent { request ->
if (receiver.supports(request)) {
val future = browser.cast(receiver)
}
}
投射屏幕内容
要投射屏幕内容,请使用 Browser.castScreen(MediaReceiver)。该方法会显示 Chromium 标准对话框,用于选择要投射的屏幕。
var receiver = mediaReceivers.await(it -> it.name().contains("Samsung"));
if (receiver.supports(MediaSource.screen())) {
var future = browser.castScreen(receiver);
}
val receiver = mediaReceivers.await { it.name().contains("Samsung") }
if (receiver.supports(MediaSource.screen())) {
val future: CompletableFuture<CastSession> =
browser.castScreen(receiver)
}
如果你希望以编程方式选择屏幕,请使用 Browser.castScreen(MediaReceiver, ScreenCastOptions) 方法。通过 Screens 服务找到所需的屏幕:
var receiver = mediaReceivers.await(it -> it.name().contains("Samsung"));
var screen = profile.mediaCasting().screens().defaultScreen();
var options = ScreenCastOptions.newBuilder(screen).withAudio().build();
if (receiver.supports(MediaSource.screen())) {
var future = browser.castScreen(receiver, options);
}
val receiver = mediaReceivers.await { it.name().contains("Samsung") }
val screen = profile.mediaCasting().screens().defaultScreen()
val options = ScreenCastOptions(screen, withAudio = true)
if (receiver.supports(MediaSource.screen())) {
val future: CompletableFuture<CastSession> =
browser.castScreen(receiver, options)
}
目前 Chromium 仅在 Windows 上支持音频投屏。因此,在 macOS/Linux 上通过 ScreenCastOptions.Builder.withAudio() 启用音频投屏是无效操作(no-op)。在 Windows 上,当在选择器对话框中选择屏幕时,Chromium 会提供一个单独的复选框用于选择是否投射音频。
Presentation API
Fuzio 支持使用 JavaScript Presentation API。
当 JavaScript 侧调用 PresentationRequest.start()方法时,Fuzio 会触发 StartPresentationCallback,你可以在其中决定启动或取消演示。
要启动向某个接收端的演示,请使用 StartPresentationCallback.Action.start(MediaReceiver) 方法:
browser.set(StartPresentationCallback.class, (params, tell) -> {
var receiver = params.mediaReceivers().await(it ->
it.name().contains("Samsung"));
if (receiver.supports(params.presentationRequest())) {
tell.start(receiver);
} else {
tell.cancel();
}
});
browser.register(StartPresentationCallback { params, tell ->
val receiver = params.mediaReceivers().await { it.name().contains("Samsung") }
if (receiver.supports(params.presentationRequest())) {
tell.start(receiver)
} else {
tell.cancel()
}
})
发现投屏会话
若要在发现投屏会话时收到通知,Fuzio 提供 CastSessionDiscovered 事件:
profile.mediaCasting().castSessions().on(CastSessionDiscovered.class, event -> {
var castSession = event.castSession();
});
val castSessions = profile.mediaCasting().castSessions()
castSessions.subscribe<CastSessionDiscovered> { event ->
val castSession = event.castSession()
}
Chromium 可以发现由其他应用程序或其他 Chromium 实例启动的会话。为了标识投屏会话是否由当前 Profile 启动,Fuzio 提供了 CastSession.isLocal() 方法。因此,如果投屏会话由其他 Profile,甚至由另一个 Chromium 进程启动,则该方法会返回 false。
停止投屏会话
要停止投屏会话,请使用 CastSession.stop() 方法。若要在投屏会话停止时收到通知,请使用 CastSessionStopped 事件:
var session = profile.mediaCasting().castSessions().list().get(0);
session.on(CastSessionStopped.class, event -> {
// 执行一些操作。
});
...
session.stop();
val session = profile.mediaCasting().castSessions().list().first()
session.subscribe<CastSessionStopped> { event ->
// 执行一些操作。
}
...
session.stop()
会话可能被其他应用程序或 Chromium 实例(即 Google Chrome)停止。在这种情况下,该事件也会被调用。
失败
有时,Chromium 可能无法启动新的投射会话,例如,如果找不到媒体接收器或它突然断开连接。要检测到这一点,请使用 CastSessionStartFailed 事件:
var receiver = mediaReceivers.await(it -> it.name().contains("Samsung"));
profile.mediaCasting().castSessions().on(CastSessionStartFailed.class, event ->
System.out.println(event.errorMessage())
);
var future = browser.cast(receiver);
val receiver = mediaReceivers.await { it.name().contains("Samsung") }
val castSessions = profile.mediaCasting().castSessions()
castSessions.subscribe<CastSessionStartFailed> { event ->
println(event.errorMessage())
}
val future: CompletableFuture<CastSession> = browser.cast(receiver)
这特意设计为一个事件,因为媒体投射具有异步性质。
由于 Browser.cast... 方法返回 CompletableFuture,您可以检测到投射会话启动失败。在这种情况下,Fuzio 会用 CastStartFailedException 完成 future:
var 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
}

