NW.js Screen API 完全指南:屏幕信息查询、显示事件监听与桌面捕获
【免费下载链接】nw.jsCall all Node.js modules directly from DOM/WebWorker and enable a new way of writing applications with all Web technologies.项目地址: https://gitcode.com/gh_mirrors/nw/nw.js
nw.Screen是 NW.js 暴露给 Node.js/浏览器混合环境的屏幕管理单例对象,用于读取物理屏幕与工作区信息、监听屏幕增删与分辨率变化事件,并通过chooseDesktopMedia与DesktopCaptureMonitor两条路径实现桌面/窗口捕获(屏幕共享)。读完本文,你将掌握从初始化、事件订阅到getUserMedia采集桌面的完整实战链路,并理解其底层与 Chromiumgfx::Display及 WebRTC Desktop Capture 的对接原理。
概述:Screen 是一个单例 EventEmitter
在 NW.js 中,nw.Screen是EventEmitter的实例,因此可以使用nw.Screen.on(...)响应系统原生屏幕事件(显示器拔插、分辨率/排列变化等)。
与大多数 NW.js API 不同,Screen是一个单例对象,必须在调用任何nw.Screen方法之前,先通过nw.Screen.Init()初始化一次。从 screen.js 的实现可以看到:Init()内部用screenInstance缓存实例,只有第一次调用才会真正new Screen(),之后exports.Screen被替换为实例对象,重复调用是幂等的:
exports.Screen = { Init: function() { if (screenInstance == null) { screenInstance = new Screen(); } exports.Screen = screenInstance; return screenInstance; } };Screen对象通过nw.allocateObject分配原生对象句柄,并继承exports.Base;其on/addListener被重写,只允许监听四个事件:displayBoundsChanged、displayAdded、displayRemoved(以及内部使用的chooseDesktopMedia),监听其他事件名会直接抛出TypeError:
Screen.prototype.on = Screen.prototype.addListener = function(ev, callback) { if ( ev != "displayBoundsChanged" && ev != "displayAdded" && ev != "displayRemoved" && ev != "chooseDesktopMedia") throw new TypeError('only following event can be listened: displayBoundsChanged, displayAdded, displayRemoved'); ... };同时该重写实现了懒注册:第一个监听器挂上时才调用nw.callStaticMethodSync('Screen', 'AddScreenChangeCallback', [this.id])向原生侧注册屏幕变化回调,最后一个监听器移除时自动调用RemoveScreenChangeCallback注销,避免空转的系统监听开销。
基本用法(Synopsis)
文档给出的标准启动与监听示例:
// init must be called once during startup, before any function to nw.Screen can be called nw.Screen.Init(); var screenCB = { onDisplayBoundsChanged: function(screen) { console.log('displayBoundsChanged', screen); }, onDisplayAdded: function(screen) { console.log('displayAdded', screen); }, onDisplayRemoved: function(screen) { console.log('displayRemoved', screen) } }; // listen to screen events nw.Screen.on('displayBoundsChanged', screenCB.onDisplayBoundsChanged); nw.Screen.on('displayAdded', screenCB.onDisplayAdded); nw.Screen.on('displayRemoved', screenCB.onDisplayRemoved);提示:
nw.Screen.Init()建议放在应用启动阶段(如package.json的main脚本或dom-ready事件中)执行一次即可,无需也不应重复初始化。仓库的冒烟测试 screen-events/index.html 演示了先Init()再绑定三个显示事件的标准流程。
Screen.Init()
初始化Screen单例对象,整个应用生命周期只需调用一次。任何nw.Screen的方法或属性访问(尤其是screens与事件监听)都应在Init()之后进行。
Screen.screens
nw.Screen.screens返回连接到计算机的显示器数组(数组长度即屏幕数量)。在 JS 侧这是一个 getter,内部通过nw.callStaticMethodSync('Screen', 'GetScreens', [])同步获取并JSON.parse得到对象数组(见 screen.js)。
每个screen对象具有如下结构:
screen { // unique id for a screen id: int, // physical screen resolution, can be negative, not necessarily start from 0,depending on screen arrangement bounds: { x: int, y: int, width: int, height: int }, // useable area within the screen bound work_area: { x: int, y: int, width: int, height: int }, scaleFactor: float, isBuiltIn: bool, rotation: int, touchSupport: int }字段说明:
id:屏幕的唯一标识。bounds:物理屏幕分辨率(逻辑像素)。注意坐标可以为负、不一定从 0 开始——取决于多显示器的排列方式(例如副屏位于主屏左侧时x为负值)。work_area:屏幕边界内的可用区域(扣除任务栏/ Dock 等系统 UI 后的区域)。scaleFactor:设备缩放因子(如 Retina 屏为 2.0),对应高分屏缩放。isBuiltIn:是否为内置屏幕(如笔记本自带面板)。rotation:屏幕旋转角度(度)。touchSupport:触摸支持级别。
这些字段在原生侧由 screen.cc 的DisplayToJSON()从 Chromiumgfx::Display直接序列化而来:bounds()/work_area()取自gfx::Rect,scaleFactor对应device_scale_factor(),isBuiltIn对应IsInternal(),rotation对应RotationAsDegree()。GetScreens则遍历gfx::Screen::GetNativeScreen()->GetAllDisplays()拼接 JSON 数组(见 screen.cc)。Display的完整字段定义也可在 nw_screen.idl 中查看。
Screen.chooseDesktopMedia (sources, callback)
通过系统原生选择器让用户选择要共享的屏幕或窗口,然后回调返回可用于getUserMedia的streamId。
sources{String[]}:源类型数组,本 API 支持两种取值:"screen"与"window"。callback{Function}:携带所选streamId的回调函数;若执行失败或已存在活动的选择会话,则streamId为false。
注意:该功能依赖系统级选择器 GUI,目前仅在 Windows、OS X 以及部分 Linux 发行版上可用。
示例:
nw.Screen.Init(); // you only need to call this once nw.Screen.chooseDesktopMedia(["window","screen"], function(streamId) { var vid_constraint = { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: streamId, maxWidth: 1920, maxHeight: 1080 }, optional: [] }; navigator.webkitGetUserMedia({audio: false, video: vid_constraint}, success_func, fallback_func); } );在拿到streamId后,将其填入getUserMedia视频约束的chromeMediaSource: 'desktop'与chromeMediaSourceId: streamId,即可把屏幕/窗口画面作为媒体流交给success_func使用(如渲染到<video>标签、录制或推流)。
底层实现要点(见 desktop_capture_api.cc):
- 原生侧遍历
sources数组,识别"window"与"screen"分别置位show_windows/show_screens;若两者都不满足,直接返回错误"At least one source type must be specified."。 - 通过 WebRTC 的
webrtc::ScreenCapturer与webrtc::WindowCapturer构建NativeDesktopMediaList,再创建DesktopMediaPicker弹出选择器。DesktopMediaPicker只在TOOLKIT_VIEWS(Aura Linux/Windows)或OS_MAC下实现,其他平台返回"Desktop Capture API is not yet implemented for this platform."——这正是文档注明平台限制的原因。 - 回调结果经
ChooseDesktopMediaCallback以chooseDesktopMedia事件形式派发给 JS 侧(见 screen.cc),screen.js中chooseDesktopMedia返回true并注册一次性this.once('chooseDesktopMedia', callback)监听(见 screen.js)。 - 若上一次选择会话(
gpDCCDMF)尚未结束,再次调用会直接返回false,JS 侧chooseDesktopMedia也随之返回false而不触发回调(见 screen.cc)。
显示事件
以下三个事件均通过 Chromium 的gfx::DisplayObserver驱动:原生侧JavaScriptDisplayObserver在屏幕指标变化、新增、移除时把DisplayToJSON序列化后的屏幕对象以事件形式派发到 JS(见 screen.cc)。
Event: displayBoundsChanged(screen)
屏幕分辨率或排列(布局)发生变化时触发。回调携带 1 个参数screen,格式同 Screen.screens。
Event: displayAdded (screen)
检测到新的屏幕接入时触发。回调携带 1 个参数screen,格式同上。
Event: displayRemoved (screen)
已有屏幕被移除时触发。回调携带 1 个参数screen(被移除屏幕的信息),格式同上。
Screen.DesktopCaptureMonitor
Screen.DesktopCaptureMonitor提供了与chooseDesktopMedia类似的能力,但不带系统 GUI——适合希望自行实现选择 UI 的场景(如自定义缩略图选择面板)。它同样是EventEmitter实例,可用Screen.DesktopCaptureMonitor.on()监听事件。
Synopsis
var dcm = nw.Screen.DesktopCaptureMonitor; nw.Screen.Init(); dcm.on("added", function (id, name, order, type) { //select first stream and shutdown var constraints = { audio: { mandatory: { chromeMediaSource: "system", chromeMediaSourceId: dcm.registerStream(id) } }, video: { mandatory: { chromeMediaSource: 'desktop', chromeMediaSourceId: dcm.registerStream(id) } } }; // TODO: call getUserMedia with contraints dcm.stop(); }); dcm.on("removed", function (id) { }); dcm.on("orderchanged", function (id, new_order, old_order) { }); dcm.on("namechanged", function (id, name) { }); dcm.on("thumbnailchanged", function (id, thumbnail) { }); dcm.start(true, true);事件与原生侧的对应关系可在 nw_screen_api.cc 中找到:added对应OnSourceAdded、removed对应OnSourceRemoved、orderchanged对应OnSourceMoved、namechanged对应OnSourceNameChanged、thumbnailchanged对应OnSourceThumbnailChanged;事件名与参数定义见 nw_screen.idl。
Screen.DesktopCaptureMonitor.started
布尔值,表示DesktopCaptureMonitor是否已处于启动监控状态。
Screen.DesktopCaptureMonitor.start(should_include_screens, should_include_windows)
should_include_screens{Boolean}:是否监控屏幕源。should_include_windows{Boolean}:是否监控窗口源。
启动系统监控并开始触发事件。注意:监控运行期间屏幕画面可能出现闪烁,因此应尽量缩短监控窗口期。
Screen.DesktopCaptureMonitor.stop()
停止监控。选定一个流之后应立即调用stop(),避免持续占用捕获资源与引起画面闪烁。
Screen.DesktopCaptureMonitor.registerStream(id)
将事件回调中拿到的源id注册为有效的流 ID,并返回可直接填入getUserMedia约束chromeMediaSourceId的字符串。用法见上文 Synopsis。
Event: added (id, name, order, type, primary)
警告(行为变更):该特性在 0.13.0 中发生了变化,详见 Migration Notes from 0.12 to 0.13。
当新增一个可捕获源时触发。参数:
id{String}:媒体 ID。需调用registerStream(id)得到可用于getUserMedia()的合法流 ID。name{String}:窗口标题或屏幕名称。order{Integer}:窗口的 Z 轴顺序;若选择了屏幕,屏幕源会排在最前。type{String}:流类型,取值为"screen"、"window"、"other"或"unknown"。primary{Boolean}:仅 Windows 有效,源为主显示器时该值为true。
Event: removed (order)
当某个源不再可捕获(被移除)时触发。参数:
order{Integer}:被移除媒体源在列表中的顺序。
Event: orderchanged (id, new_order, old_order)
警告(行为变更):该特性在 0.13.0 中发生了变化,详见 Migration Notes from 0.12 to 0.13。
当某个源的 Z 轴顺序改变(例如窗口被聚焦/失焦导致层级变化)时触发。参数:
id{String}:Z 轴顺序发生变化的屏幕或窗口的媒体 ID。new_order{Integer}:新的 Z 轴顺序。old_order{Integer}:旧的 Z 轴顺序。
Event: namechanged (id, name)
警告(行为变更):该特性在 0.13.0 中发生了变化,详见 Migration Notes from 0.12 to 0.13。
当源名称改变(如窗口标题变化)时触发。参数:
id{String}:名称发生变化的屏幕或窗口的媒体 ID。name{String}:该屏幕或窗口的新名称。
Event: thumbnailchanged (id, thumbnail)
警告(行为变更):该特性在 0.13.0 中发生了变化,详见 Migration Notes from 0.12 to 0.13。
当源的缩略图更新时触发。参数:
id{String}:缩略图发生更新的屏幕或窗口的媒体 ID。thumbnail{String}:Base64 编码的 PNG 缩略图数据(可直接赋给<img src="data:image/png;base64,...">渲染)。
最佳实践与注意事项
- 先
Init()再使用:所有nw.Screen操作都依赖单例初始化;Init()幂等,可放心在启动阶段调用一次。 - 事件监听白名单:
nw.Screen.on只接受displayBoundsChanged、displayAdded、displayRemoved三个显示事件,传入其他事件名会抛TypeError;桌面捕获相关事件请挂在nw.Screen.DesktopCaptureMonitor上。 - 多屏坐标:
bounds与work_area的坐标原点不一定是(0, 0),拼接多屏布局时应以实际返回的x/y为准(可为负)。 chooseDesktopMedia的一次性约束:选择会话期间再次调用会返回false,回调不会触发;应避免重复发起选择。- 监控后及时
stop():DesktopCaptureMonitor运行期间屏幕可能闪烁,选定流后应立即停止;同时可先通过thumbnailchanged拿到缩略图渲染自定义选择 UI。 - 平台限制:
chooseDesktopMedia的桌面选择器仅在 Windows、macOS 与部分 Linux(Aura)发行版上可用;跨平台应用需做好降级或平台分支。
延伸阅读
- 本文主题的官方参考文档:Screen.md
- JS 绑定实现:src/api/screen/screen.js
- 原生
GetScreens/事件派发实现:src/api/screen/screen.cc - 桌面捕获(
chooseDesktopMedia)实现:src/api/screen/desktop_capture_api.cc - API 类型定义(IDL):src/api/nw_screen.idl
- 屏幕事件绑定的冒烟测试:test/sanity/screen-events/index.html
- 0.12 到 0.13 的迁移说明(涉及桌面捕获行为变更):From 0.12 to 0.13
【免费下载链接】nw.jsCall all Node.js modules directly from DOM/WebWorker and enable a new way of writing applications with all Web technologies.项目地址: https://gitcode.com/gh_mirrors/nw/nw.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考