【实用教程】如何利用 JxBrowser 在 Kotlin 中实现屏幕共享

news2024/11/19 6:10:36

JxBrowser是一个跨平台的 JVM 库,它允许您将基于 Chromium 的 Browser 控件集成到 Compose、Swing、JavaFX、SWT 应用程序中,并使用 Chromium 的数百种功能。为了在 Kotlin 中实现屏幕共享,我们利用了 Chromium 的 WebRTC 支持以及 JxBrowser 对其的编程访问能力。

点击下载最新版JxBrowser

在本文中,我们将展示如何利用 JxBrowser 的功能在两个 Compose Desktop 应用程序之间实现屏幕共享。

概述

WebRTC 是一个开放标准,允许通过常规 JavaScript API 进行实时通信。该技术在所有现代 Browser 以及所有主要平台的原生客户端上均可使用。我们将使用它将捕获的屏幕视频流从一个应用程序发送到另一个应用程序。

该项目由四个模块组成:

  • server – 用于在对等端之间建立直接媒体连接的信令服务器。
  • sender – 共享主屏幕的 Compose 应用程序。
  • receiver – 显示共享屏幕的 Compose 应用程序。
  • common – 保存 Kotlin 模块之间共享的通用代码。

信令服务器促进对等端之间初始的连接信息交换。这包括有关网络、会话描述符和媒体功能的信息。

Compose 客户端是两个桌面应用程序:一个通过单击即可共享屏幕,另一个接收并显示视频流。

服务器

WebRTC 的主要挑战之一是如何管理信令,它充当对等端的汇合点,而不传输实际数据。我们使用 PeerJS 库来抽象信令逻辑,使我们能够专注于应用程序的功能。该库提供了服务器和客户端实现。

我们需要做的只是创建一个 PeerServer 实例并运行创建的 Node.js 应用程序。

首先,我们添加所需的 NPM 依赖项:

npm install peer

然后,创建一个 PeerServer 实例:

PeerServer({
    port: 3000
});

并运行创建的应用程序:

node server.js

Compose 客户端

对于 Compose 应用程序,我们需要使用 JxBrowser 和 Compose 插件来初始化一个空的 Gradle 项目:

plugins {
    id("org.jetbrains.kotlin.jvm") version "2.0.0"
    id("com.teamdev.jxbrowser") version "1.1.0"
    id("org.jetbrains.compose") version "1.6.11"
}

jxbrowser {
    version = "8.1.0"
    includePreviewBuilds()
}

dependencies {
    implementation(jxbrowser.currentPlatform)
    implementation(jxbrowser.compose)
    implementation(compose.desktop.currentOs)
}

每个 Compose 客户端由三层组成:

  1. UI 层 – Compose 的 singleWindowApplication,用于显示界面。
  2. Browser 集成层 – JxBrowser 库,它将 Browser 嵌入到 Compose 应用程序中。
  3. WebRTC 组件 – PeerJS 库,用于建立 WebRTC 连接并处理屏幕共享的媒体流。

接收端应用程序

让我们先从显示共享屏幕的接收端应用程序开始。

我们需要实现一个接收 WebRTC 组件。它应该连接到信令服务器并订阅传入的呼叫。其 API 将由一个单独的 connect() 函数组成。该函数应暴露在全局范围内,以便从 Kotlin 端进行访问。

window.connect = (signalingServer) => {
    const peer = new Peer(RECEIVER_PEER_ID, signalingServer);
    peer.on('call', (call) => {
        call.answer();
        call.on('stream', (stream) => {
            showVideo(stream);
        });
        call.on('close', () => {
            hideVideo();
        });
    });
}

我们接听每个呼叫,并在 <video> 元素中显示接收到的流,否则该元素将被隐藏。在文章的末尾,我们会提供一个指向 GitHub 上完整源代码的链接。

接下来,我们将使用 JxBrowser 加载此组件,并使暴露的 JS 函数可以从 Kotlin 中调用:

class WebrtcReceiver(browser: Browser) {
    private const val webrtcComponent = "/receiving-peer.html"
    private val frame = browser.mainFrame!!

    init {
        browser.loadWebPage(webrtcComponent)
    }

    fun connect(server: SignalingServer) = executeJavaScript("connect($server)")

    // 在加载的 Frame 上执行给定的 JS 代码。
    private fun executeJavaScript(javaScript: String) = // ...

    // 从应用程序的资源中加载页面。
    private fun Browser.loadWebPage(webPage: String) = // ...
}

WebrtcReceiver 从应用程序的资源中加载实现的组件到 Browser 中,并公开一个单一的公共 connect(...) 方法,该方法直接调用其 JS 对应项。

最后,让我们创建一个 Compose 应用程序来将这些组件组合在一起:

singleWindowApplication(title = "Screen Viewer") {
    val engine = remember { createEngine() }
    val browser = remember { engine.newBrowser() }
    val webrtc = remember { WebrtcReceiver(browser) }

    BrowserView(browser)

    LaunchedEffect(Unit) {
        webrtc.connect(SIGNALING_SERVER)
    }
}

首先,我们创建 Engine、Browser 和 WebrtcReceiver 实例。然后,我们添加 BrowserView 组合项来显示 HTML5 视频播放器。在启动的效果中,我们连接到信令服务器,假设这是一个快速且不会失败的操作。

发送端应用程序

在发送端应用程序中,我们通过向接收端发起呼叫来共享主屏幕。

我们需要实现发送 WebRTC 组件。它应该能够连接到信令服务器,开始和停止屏幕共享会话。因此,它的 API 将由三个函数组成。这些函数应该暴露给全局作用域,以便从 Kotlin 端进行访问。

let peer;
let mediaConnection;
let mediaStream;

window.connect = (signalingServer) => {
    peer = new Peer(SENDER_PEER_ID, signalingServer);
}

window.startScreenSharing = () => {
    navigator.mediaDevices.getDisplayMedia({
        video: {cursor: 'always'}
    }).then(stream => {
        mediaConnection = peer.call(RECEIVER_PEER_ID, stream);
        mediaStream = stream;
    });
}

window.stopScreenSharing = () => {
    mediaConnection.close();
    mediaStream.getTracks().forEach(track => track.stop());
}

在建立与信令服务器的连接后,组件可以开始和停止屏幕共享会话。开始新的会话意味着选择一个媒体流来呼叫接收端。当共享停止时,应该关闭该流和点对点媒体连接。只要组件被加载,与信令服务器的连接就会保持活动状态。

与接收端应用程序类似,让我们为这个组件创建一个 Kotlin 包装器。我们将通过添加新功能来扩展 WebrtcReceiver 以创建 WebrtcSender。

首先,我们需要告诉 JxBrowser 使用哪个视频源。默认情况下,当网页想要从屏幕捕获视频时,Chromium 会显示一个对话框,我们可以在其中选择源。使用 JxBrowser API,我们可以直接在代码中指定捕获源:

init {
    // 当 Browser 即将开始捕获会话时,选择一个源。
    browser.register(StartCaptureSessionCallback { params: Params, tell: Action ->
        val primaryScreen = params.sources().screens()[0]
        tell.selectSource(primaryScreen, AudioCaptureMode.CAPTURE)
    })
}

其次,我们希望让用户界面知道当前是否有活动的共享会话。这是为了决定是显示 Start 还是 Stop 按钮。让我们创建一个可观察的属性,并将其绑定到 JxBrowser 的 CaptureSessionStarted 和 CaptureSessionsStopped 事件:

var isSharing by mutableStateOf(false)
    private set

init {
    // ...
    // 在会话开始和停止时更新 `isSharing` 状态变量。
    browser.subscribe<CaptureSessionStarted> { event: CaptureSessionStarted ->
        isSharing = true
        event.capture().subscribe<CaptureSessionStopped> {
            isSharing = false
        }
    }
}

最后要做的就是添加两个公共方法,用于调用其 JavaScript 对应项:

fun startScreenSharing() = executeJavaScript("startScreenSharing()")
fun stopScreenSharing() = executeJavaScript("stopScreenSharing()")

就是这样!

在本地运行时,应用程序看起来是这样的:

在不同的 PC 上运行

作为一项额外的好处,您可以轻松地使此示例在不同的 PC 上运行,而无需暴露本地运行的信令服务器。PeerJS 提供了一个免费的云托管 PeerServer 版本。如果未指定特定的服务器来使用,该库将自动连接到公共云服务器。请注意,在编写本文时,它是可运行的,但其正常运行时间并不保证。

要尝试此功能,您需要在发送和接收 WebRTC 组件中删除显式传递给 Peer 构造函数的服务器参数。

// 使用该服务器。
const peer = new Peer(RECEIVER_PEER_ID, signalingServer);
// 使用云托管实例。
const peer = new Peer(RECEIVER_PEER_ID);

另外,请注意,您将与其他人共享这个公共服务器,由于对等 ID 是手动设置的,所以可能会发生冲突。

结论

在本文中,我们展示了如何使用 JxBrowser 和 WebRTC 在一个 Compose 应用程序中共享屏幕,并在另一个应用程序中显示视频流。通过利用 JxBrowser 对 Chromium 的集成以及用于 WebRTC 的 PeerJS 库,我们可以快速构建一个功能完备的屏幕共享应用程序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2243196.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无人机动力系统节能技术的未来发展趋势——CKESC电调小课堂12.1

无人机动力系统节能技术的未来发展趋势包括以下几个方面&#xff1a; 1. 能源类型多元化与高效化 新型电池技术的发展&#xff1a;锂离子电池的性能将不断提升&#xff0c;能量密度增加、充放电速度加快、循环寿命延长。同时&#xff0c;固态电池技术有望取得突破并应用于无人…

【汇编语言】数据处理的两个基本问题(二) —— 解密汇编语言:数据长度与寻址方式的综合应用

文章目录 前言1. 指令要处理的数据有多长&#xff1f;1.1 通过寄存器指明数据的尺寸1.1.1 字操作1.1.2 字节操作 1.2 用操作符X ptr指明内存单元的长度1.2.1 访问字单元1.2.2 访问字节单元1.2.3 为什么要用操作符X ptr指明 1.3 其他方法 2. 寻址方式的综合应用2.1 问题背景&…

【算法】【优选算法】前缀和(下)

目录 一、560.和为K的⼦数组1.1 前缀和1.2 暴力枚举 二、974.和可被K整除的⼦数组2.1 前缀和2.2 暴力枚举 三、525.连续数组3.1 前缀和3.2 暴力枚举 四、1314.矩阵区域和4.1 前缀和4.2 暴力枚举 一、560.和为K的⼦数组 题目链接&#xff1a;560.和为K的⼦数组 题目描述&#x…

【进阶系列】正则表达式 #匹配

正则表达式 正则表达式是一个特殊的字符序列&#xff0c;它能帮助你方便的检查一个字符串是否与某种模式匹配。re模块使 Python 语言拥有全部的正则表达式功能。 一个正则表达式的匹配工具&#xff1a;regex101: build, test, and debug regex s "C:\\a\\b\\c" pri…

【技术解析】Dolphinscheduler实现MapReduce任务的高效管理

MapReduce是一种编程模型&#xff0c;用于处理和生成大数据集&#xff0c;主要用于大规模数据集&#xff08;TB级数据规模&#xff09;的并行运算。本文详细介绍了Dolphinscheduler在MapReduce任务中的应用&#xff0c;包括GenericOptionsParser与args的区别、hadoop jar命令参…

Debezium-MySqlConnectorTask

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 MySqlConnectorTask&#xff0c;用于读取MySQL的二进制日志并生成对应的数据变更事件 整体架构流程 技术名词解释 数据库模式&#xff08;Database Schema&#xff09; 数据库模式是指数据库中数据的组织结构和定义&…

剧本杀门店预约小程序,解锁沉浸式推理体验

一、开发背景 剧本杀作为一种热门娱乐游戏&#xff0c;深受大众的欢迎&#xff0c;但随着市场的快速发展&#xff0c;竞争也在不断加大&#xff0c;对于剧本杀线下商家来说面临着发展创新。 剧本杀线下门店数量目前正在逐渐增加&#xff0c;竞争激烈&#xff0c;而门店的获客…

今天你学C++了吗——C++启航之入门知识

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨ 个…

【element-tiptap】Tiptap编辑器核心概念----结构篇

core-concepts 前言&#xff1a;这篇文章来介绍一下 Tiptap 编辑器的一些核心概念 &#xff08;一&#xff09;结构 1、 Schemas 定义文档组成方式。一个文档就是标题、段落以及其他的节点组成的一棵树。 每一个 ProseMirror 的文档都有一个与之相关联的 schema&#xff0c;…

LC69---219存在重复元素(滑动窗口)---Java版

1.题目描述 2.思路 3.代码实现 public class Solution { public boolean containsNearbyDuplicate(int[] nums, int k) {Map<Integer,Integer> m1new HashMap<>();// 1:0, 2:1,3:2,1:3 key存数组的值&#xff0c;value存索引&#xff0c;为getnum[i]做准备&am…

【C++】了解map和set及平衡二叉树和红黑树的原理

目录 ​编辑 一、关联式容器 二、 键值对 三、pair介绍 四、树形结构的关联式容器 4.1 set 4.2 map 4.3 multiset 4.4 multimaps 五、底层结构&#xff08;重点&#xff09; 5.1 AVL 树 5.1.1 AVL树的概念 5.1.2 AVL树节点的定义 5.1.3 AVL树的旋转 5.1.4 AVL树的…

LeetCode 力扣 热题 100道(五)最长回文子串(C++)

最长回文子串 给你一个字符串 s&#xff0c;找到 s 中最长的 回文子串。 回文性 如果字符串向前和向后读都相同&#xff0c;则它满足 回文性 子字符串子字符串 是字符串中连续的 非空 字符序列。 动态规划法 class Solution { public:string longestPalindrome(string s) {i…

Spring Boot汽车资讯:科技与汽车的新融合

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了汽车资讯网站的开发全过程。通过分析汽车资讯网站管理的不足&#xff0c;创建了一个计算机管理汽车资讯网站的方案。文章介绍了汽车资讯网站的系统分析部分&…

逆向攻防世界CTF系列41-EASYHOOK

逆向攻防世界CTF系列41-EASYHOOK 看题目是一个Hook类型的&#xff0c;第一次接触&#xff0c;虽然学过相关理论&#xff0c;可以看我的文章 Hook入门(逆向)-CSDN博客 题解参考&#xff1a;https://www.cnblogs.com/c10udlnk/p/14214057.html和攻防世界逆向高手题之EASYHOOK-…

【网络】HTTP 协议

目录 基本概念基于 HTTP 的系统组成HTTP 的基本性质 HTTP 请求头 & 响应头HTTP 的请求方法HTTP 的返回码HTTP 的 CookieHTTP 缓存 Cache-Control会话HTTP/1.x 的连接管理 基本概念 HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超文本传输协议&#xff09;是一…

执行flink sql连接clickhouse库

手把手教学&#xff0c;flink connector打通clickhouse大数据库&#xff0c;通过下发flink sql&#xff0c;来使用ck。 组件版本jdk1.8flink1.17.2clickhouse23.12.2.59 1.背景 flink官方不支持clickhouse连接器&#xff0c;工作中难免会用到。 2.方案 利用GitHub大佬提供…

笔记|M芯片MAC (arm64) docker上使用 export / import / commit 构建amd64镜像

很简单的起因&#xff0c;我的东西最终需要跑在amd64上&#xff0c;但是因为mac的架构师arm64&#xff0c;所以直接构建好的代码是没办法跨平台运行的。直接在arm64上pull下来的docker镜像也都是arm64架构。 检查镜像架构&#xff1a; docker inspect 8135f475e221 | grep Arc…

免费送源码:Java+Springboot+MySQL Springboot多租户博客网站的设计 计算机毕业设计原创定制

Springboot多租户博客网站的设计 摘 要 博客网站是当今网络的热点&#xff0c;博客技术的出现使得每个人可以零成本、零维护地创建自己的网络媒体&#xff0c;Blog站点所形成的网状结构促成了不同于以往社区的Blog文化&#xff0c;Blog技术缔造了“博客”文化。本文课题研究的“…

代码随想录第46期 单调栈

这道题主要是单调栈的简单应用 class Solution { public:vector<int> dailyTemperatures(vector<int>& T) {vector<int> result(T.size(),0);stack<int> st;st.push(0);for(int i1;i<T.size();i){if(T[i]<T[st.top()]){st.push(i);}else{wh…

【Three.js基础学习】24. shader patterns

前言 课程回顾: ShaderMaterial 这里用的是着色器材质 所以顶点和片段着色器就不需要像原始着色器那样添加需要的属性 然后写 片段着色器需要属性 &#xff1a; 顶点 属性 -》变化 -》 片段中 顶点中的属性不需要声明 只需要声明传送的变量 例如 varying vec vUv; vUv uv; 补充…