[{"data":1,"prerenderedAt":5796},["ShallowReactive",2],{"page-\u002Fcpp\u002Fwgc捕获迁移到electron":3},{"id":4,"title":5,"body":6,"description":65,"extension":5790,"meta":5791,"navigation":81,"path":5792,"seo":5793,"stem":5794,"__hash__":5795},"content\u002Fcpp\u002FWGC捕获迁移到Electron.md","WGC捕获迁移到Electron",{"type":7,"value":8,"toc":5786},"minimark",[9,13,17,56,59,349,372,407,445,480,500,551,566,1035,1042,1045,1048,1055,1058,1989,1992,2171,2177,2191,2209,2212,2215,2456,2459,2781,2792,2802,2813,2829,2851,2872,2918,2933,2936,2944,2959,3588,3591,3767,3770,3875,3878,3881,3884,3922,3925,4832,4964,4967,5755,5780,5783],[10,11,12],"h2",{"id":12},"迁移",[14,15,16],"p",{},"上个笔记里我们用了WINRT、D3D和glfw制作了一个WGC的窗口捕获程序，但是现在如果要把功能给迁移到electron应用中去的话，就得去掉glfw，这个是多余的功能，我们可以保留D3D11CreateDevice、WGC、frame pool，FrameArrived、staging、Map->CPU缓冲这些功能，或者说以后做D3D纹理共享给Chromium（当做以后得目标吧）",[14,18,19,20,24,25,31,32,35,36,39,40,43,44,47,48,51,52,55],{},"我们需要安装一个叫做 node-addon-api 的 C++ 库，Windows 上 vcpkg 常写成 ",[21,22,23],"code",{},"node-addon-api:x64-windows","。我们要在源码中引入 ",[26,27,28],"strong",{},[21,29,30],{},"napi.h","（node-addon-api 包装层）来使用 ",[21,33,34],{},"Napi::Env"," 以及 ",[21,37,38],{},"Napi::Object"," \u002F ",[21,41,42],{},"Napi::Function","。编译得到的是 DLL，要给 Node\u002FElectron 使用，通常还要把输出名改成 ",[21,45,46],{},"capture_addon.node","（或你在 CMake 里设的 ",[21,49,50],{},"PREFIX \"\""," + ",[21,53,54],{},"SUFFIX \".node\"","）。",[14,57,58],{},"我们使用Imp的定义和实现分离的办法，定义一个,.h和一个.cpp文件:",[60,61,66],"pre",{"className":62,"code":63,"language":64,"meta":65,"style":65},"language-cpp shiki shiki-themes github-light github-dark","#pragma once\n\n#include \u003Catomic>\n#include \u003Ccstdint>\n#include \u003Cmemory>\n#include \u003Cmutex>\n#include \u003Cstring>\n#include \u003Cvector>\n\nstruct RecorderConfig;\n\n\u002F\u002F\u002F 默认显示器 WGC 捕获：D3D11 + 帧池；CPU 缓冲为 RGBA（自 BGRA 转换）。供 main \u002F N-API 等复用。\nclass MonitorCapture {\n    struct Impl;\n    std::unique_ptr\u003CImpl> impl_;\n\npublic:\n    MonitorCapture();\n    ~MonitorCapture();\n\n    MonitorCapture(const MonitorCapture&) = delete;\n    MonitorCapture& operator=(const MonitorCapture&) = delete;\n    MonitorCapture(MonitorCapture&&) = delete;\n    MonitorCapture& operator=(MonitorCapture&&) = delete;\n\n    bool prepare_default_monitor();\n    void begin_capture();\n    void stop();\n\n    bool is_prepared() const;\n    int width() const;\n    int height() const;\n\n    std::mutex& buffer_mutex();\n    std::vector\u003Cuint8_t>& pixel_buffer();\n    std::atomic\u003Cbool>& frame_dirty();\n\n    void set_preview_enabled(bool enabled);\n    bool preview_enabled() const;\n\n    void set_recording_enabled(bool enabled);\n    bool recording_enabled() const;\n\n    \u002F\u002F\u002F 输出 MP4（Media Foundation）。须已 prepare + begin_capture；path 为 UTF-16 本地路径。\n    bool start_recording(const std::wstring& output_path, const RecorderConfig& config);\n    void stop_recording();\n    bool is_recording() const;\n};\n","cpp","",[21,67,68,76,83,89,95,101,107,113,119,124,130,135,141,147,153,159,164,170,176,182,187,193,199,205,211,216,222,228,234,239,245,251,257,262,268,274,280,285,291,297,302,308,314,319,325,331,337,343],{"__ignoreMap":65},[69,70,73],"span",{"class":71,"line":72},"line",1,[69,74,75],{},"#pragma once\n",[69,77,79],{"class":71,"line":78},2,[69,80,82],{"emptyLinePlaceholder":81},true,"\n",[69,84,86],{"class":71,"line":85},3,[69,87,88],{},"#include \u003Catomic>\n",[69,90,92],{"class":71,"line":91},4,[69,93,94],{},"#include \u003Ccstdint>\n",[69,96,98],{"class":71,"line":97},5,[69,99,100],{},"#include \u003Cmemory>\n",[69,102,104],{"class":71,"line":103},6,[69,105,106],{},"#include \u003Cmutex>\n",[69,108,110],{"class":71,"line":109},7,[69,111,112],{},"#include \u003Cstring>\n",[69,114,116],{"class":71,"line":115},8,[69,117,118],{},"#include \u003Cvector>\n",[69,120,122],{"class":71,"line":121},9,[69,123,82],{"emptyLinePlaceholder":81},[69,125,127],{"class":71,"line":126},10,[69,128,129],{},"struct RecorderConfig;\n",[69,131,133],{"class":71,"line":132},11,[69,134,82],{"emptyLinePlaceholder":81},[69,136,138],{"class":71,"line":137},12,[69,139,140],{},"\u002F\u002F\u002F 默认显示器 WGC 捕获：D3D11 + 帧池；CPU 缓冲为 RGBA（自 BGRA 转换）。供 main \u002F N-API 等复用。\n",[69,142,144],{"class":71,"line":143},13,[69,145,146],{},"class MonitorCapture {\n",[69,148,150],{"class":71,"line":149},14,[69,151,152],{},"    struct Impl;\n",[69,154,156],{"class":71,"line":155},15,[69,157,158],{},"    std::unique_ptr\u003CImpl> impl_;\n",[69,160,162],{"class":71,"line":161},16,[69,163,82],{"emptyLinePlaceholder":81},[69,165,167],{"class":71,"line":166},17,[69,168,169],{},"public:\n",[69,171,173],{"class":71,"line":172},18,[69,174,175],{},"    MonitorCapture();\n",[69,177,179],{"class":71,"line":178},19,[69,180,181],{},"    ~MonitorCapture();\n",[69,183,185],{"class":71,"line":184},20,[69,186,82],{"emptyLinePlaceholder":81},[69,188,190],{"class":71,"line":189},21,[69,191,192],{},"    MonitorCapture(const MonitorCapture&) = delete;\n",[69,194,196],{"class":71,"line":195},22,[69,197,198],{},"    MonitorCapture& operator=(const MonitorCapture&) = delete;\n",[69,200,202],{"class":71,"line":201},23,[69,203,204],{},"    MonitorCapture(MonitorCapture&&) = delete;\n",[69,206,208],{"class":71,"line":207},24,[69,209,210],{},"    MonitorCapture& operator=(MonitorCapture&&) = delete;\n",[69,212,214],{"class":71,"line":213},25,[69,215,82],{"emptyLinePlaceholder":81},[69,217,219],{"class":71,"line":218},26,[69,220,221],{},"    bool prepare_default_monitor();\n",[69,223,225],{"class":71,"line":224},27,[69,226,227],{},"    void begin_capture();\n",[69,229,231],{"class":71,"line":230},28,[69,232,233],{},"    void stop();\n",[69,235,237],{"class":71,"line":236},29,[69,238,82],{"emptyLinePlaceholder":81},[69,240,242],{"class":71,"line":241},30,[69,243,244],{},"    bool is_prepared() const;\n",[69,246,248],{"class":71,"line":247},31,[69,249,250],{},"    int width() const;\n",[69,252,254],{"class":71,"line":253},32,[69,255,256],{},"    int height() const;\n",[69,258,260],{"class":71,"line":259},33,[69,261,82],{"emptyLinePlaceholder":81},[69,263,265],{"class":71,"line":264},34,[69,266,267],{},"    std::mutex& buffer_mutex();\n",[69,269,271],{"class":71,"line":270},35,[69,272,273],{},"    std::vector\u003Cuint8_t>& pixel_buffer();\n",[69,275,277],{"class":71,"line":276},36,[69,278,279],{},"    std::atomic\u003Cbool>& frame_dirty();\n",[69,281,283],{"class":71,"line":282},37,[69,284,82],{"emptyLinePlaceholder":81},[69,286,288],{"class":71,"line":287},38,[69,289,290],{},"    void set_preview_enabled(bool enabled);\n",[69,292,294],{"class":71,"line":293},39,[69,295,296],{},"    bool preview_enabled() const;\n",[69,298,300],{"class":71,"line":299},40,[69,301,82],{"emptyLinePlaceholder":81},[69,303,305],{"class":71,"line":304},41,[69,306,307],{},"    void set_recording_enabled(bool enabled);\n",[69,309,311],{"class":71,"line":310},42,[69,312,313],{},"    bool recording_enabled() const;\n",[69,315,317],{"class":71,"line":316},43,[69,318,82],{"emptyLinePlaceholder":81},[69,320,322],{"class":71,"line":321},44,[69,323,324],{},"    \u002F\u002F\u002F 输出 MP4（Media Foundation）。须已 prepare + begin_capture；path 为 UTF-16 本地路径。\n",[69,326,328],{"class":71,"line":327},45,[69,329,330],{},"    bool start_recording(const std::wstring& output_path, const RecorderConfig& config);\n",[69,332,334],{"class":71,"line":333},46,[69,335,336],{},"    void stop_recording();\n",[69,338,340],{"class":71,"line":339},47,[69,341,342],{},"    bool is_recording() const;\n",[69,344,346],{"class":71,"line":345},48,[69,347,348],{},"};\n",[14,350,351,354,355,51,358,361,362,367,368,371],{},[26,352,353],{},"说明（和旧笔记的差别）。"," 捕获逻辑仍然是一段 ",[21,356,357],{},"Impl",[21,359,360],{},"FrameArrived","，但实现文件已经 ",[26,363,364],{},[21,365,366],{},"main_n.cpp","（命名上和 GLFW 演示用的 ",[21,369,370],{},"main.cpp"," 区分开）。同一工程里后来叠了：",[373,374,375,390],"ul",{},[376,377,378,381,382,385,386,389],"li",{},[26,379,380],{},"预览","：",[21,383,384],{},"set_preview_enabled","，帧里可选择只喂录制、仍然更新 ",[21,387,388],{},"pixelBuffer"," 给 Canvas。",[376,391,392,381,395,398,399,402,403,406],{},[26,393,394],{},"录制",[21,396,397],{},"MediaRecorder"," 写 H.264\u002FAAC MP4；",[26,400,401],{},"WASAPI loopback"," 抓「播放设备上听到的」混音，混音多为 float 时在回调里夹一层 ",[26,404,405],{},"float→int16"," 再送进 MF。",[14,408,409,410,413,414,424,425,431,432,437,438,440,441,444],{},"MSVC 下若写了 ",[21,411,412],{},"std::max","， Windows.h 里的 ",[26,415,416,419,420,423],{},[21,417,418],{},"min","\u002F",[21,421,422],{},"max"," 宏","会把模板函数顶坏，所以在 ",[26,426,427,430],{},[21,428,429],{},"#include \u003CWindows.h>"," 之前"," 建议定义 ",[26,433,434],{},[21,435,436],{},"NOMINMAX","（我在 ",[21,439,366],{}," 里就是这么干的）；否则你会看到 ",[21,442,443],{},"C2589"," 一类语法错误。",[60,446,448],{"className":62,"code":447,"language":64,"meta":65,"style":65},"#ifndef NOMINMAX\n#define NOMINMAX\n#endif\n#include \u003CWindows.h>\n\u002F\u002F ...\nconst UINT32 ch = (std::max)(1u, m_audioChannelsFromDevice); \u002F\u002F 括号也可防宏展开\n",[21,449,450,455,460,465,470,475],{"__ignoreMap":65},[69,451,452],{"class":71,"line":72},[69,453,454],{},"#ifndef NOMINMAX\n",[69,456,457],{"class":71,"line":78},[69,458,459],{},"#define NOMINMAX\n",[69,461,462],{"class":71,"line":85},[69,463,464],{},"#endif\n",[69,466,467],{"class":71,"line":91},[69,468,469],{},"#include \u003CWindows.h>\n",[69,471,472],{"class":71,"line":97},[69,473,474],{},"\u002F\u002F ...\n",[69,476,477],{"class":71,"line":103},[69,478,479],{},"const UINT32 ch = (std::max)(1u, m_audioChannelsFromDevice); \u002F\u002F 括号也可防宏展开\n",[14,481,482,483,486,487,490,491,494,495,499],{},"完整 ",[21,484,485],{},"MonitorCapture::Impl","（含 D3D staging、WGC、",[21,488,489],{},"WasapiLoopback","、",[21,492,493],{},"feed_wasapi_audio","）以仓库 ",[26,496,497],{},[21,498,366],{}," 为准，这里不再整段粘贴，避免下次又和线上漂移。",[14,501,502,503,39,506,508,509,512,513,518,519,522,523,534,535,540,541,546,547,550],{},"因此我们需要把方法包装成 ",[21,504,505],{},"Napi::Value",[21,507,42],{}," 再绑到 ",[21,510,511],{},"exports"," 上。当前仓库里 ",[26,514,515],{},[21,516,517],{},"addon.cpp"," 除了 ",[21,520,521],{},"start \u002F stop \u002F getFrame \u002F getSize","，还多了 ",[26,524,525,39,528,39,531],{},[21,526,527],{},"startRecording",[21,529,530],{},"stopRecording",[21,532,533],{},"isRecording","，以及枚举播放设备的 ",[26,536,537],{},[21,538,539],{},"getAudioOutputDevices","（给 Vue 里下拉选 loopback 用）。录制参数里可选 ",[26,542,543],{},[21,544,545],{},"audioOutputDeviceId","（UTF-8），对应 ",[21,548,549],{},"RecorderConfig::audioOutputDeviceId","（宽字符串）。",[14,552,553,554,490,557,560,561,565],{},"下面这一段是「骨架」示意：UTF-8 路径宽字符转换、",[21,555,556],{},"ParseRecorderConfig",[21,558,559],{},"InitializeModule"," 里多挂了哪些名字——细节仍以仓库 ",[26,562,563],{},[21,564,517],{}," 为准。",[60,567,569],{"className":62,"code":568,"language":64,"meta":65,"style":65},"#include \u003Cnapi.h>\n#include \u003Cwinrt\u002Fbase.h>\n#include \u003CWindows.h>\n\n#include \u003Cmemory>\n#include \u003Cmutex>\n#include \u003Cstring>\n\n#include \"media_recorder.h\"\n#include \"screen_capture.h\"\n#include \"audio_output_devices.h\"\n\nnamespace {\n\n    std::unique_ptr\u003CMonitorCapture> g_capture;\n    std::once_flag g_winrt_apartment_once;\n\n    static void ensure_winrt_apartment()\n    {\n        std::call_once(g_winrt_apartment_once, []() {\n            try {\n                winrt::init_apartment(winrt::apartment_type::multi_threaded);\n            }\n            catch (winrt::hresult_error const& e) {\n                const auto c = static_cast\u003Cuint32_t>(e.code());\n                if (c != 0x80010106U)\n                    throw;\n                \u002F\u002F RPC_E_CHANGED_MODE：Electron 已初始化 COM，沿用现有单元即可\n            }\n        });\n    }\n\n    static RecorderConfig ParseRecorderConfig(Napi::Object const& o)\n    {\n        RecorderConfig cfg{};\n        cfg.width = o.Get(\"width\").As\u003CNapi::Number>().Uint32Value();\n        cfg.height = o.Get(\"height\").As\u003CNapi::Number>().Uint32Value();\n        cfg.fps = o.Get(\"fps\").As\u003CNapi::Number>().Uint32Value();\n        if (o.Has(\"videoBitrate\"))\n            cfg.videoBitrate = o.Get(\"videoBitrate\").As\u003CNapi::Number>().Uint32Value();\n        if (o.Has(\"audioSampleRate\"))\n            cfg.audioSampleRate = o.Get(\"audioSampleRate\").As\u003CNapi::Number>().Uint32Value();\n        if (o.Has(\"audioChannels\"))\n            cfg.audioChannels = o.Get(\"audioChannels\").As\u003CNapi::Number>().Uint32Value();\n        if (o.Has(\"enableAudio\"))\n            cfg.enableAudio = o.Get(\"enableAudio\").As\u003CNapi::Boolean>().Value();\n        if (o.Has(\"audioOutputDeviceId\")) {\n            const std::string u8 = o.Get(\"audioOutputDeviceId\").As\u003CNapi::String>().Utf8Value();\n            if (!u8.empty())\n                cfg.audioOutputDeviceId = Utf8ToWide(u8); \u002F\u002F MultiByteToWideChar CP_UTF8\n        }\n        return cfg;\n    }\n\n    \u002F\u002F Start \u002F GetFrame \u002F Stop \u002F GetSize：与旧笔记相同思路……\n\n    \u002F** JS: startRecording(outputPathUtf8, config) — path 用 UTF-8 *\u002F\n    Napi::Value StartRecording(const Napi::CallbackInfo& info) { \u002F* … *\u002F }\n\n    Napi::Value GetAudioOutputDevices(const Napi::CallbackInfo& info)\n    {\n        Napi::Env env = info.Env();\n        const auto devices = EnumerateAudioRenderDevices();\n        Napi::Array arr = Napi::Array::New(env, devices.size());\n        for (size_t i = 0; i \u003C devices.size(); ++i) {\n            Napi::Object row = Napi::Object::New(env);\n            row.Set(\"id\", Napi::String::New(env, WideToUtf8(devices[i].id)));\n            row.Set(\"name\", Napi::String::New(env, WideToUtf8(devices[i].friendlyName)));\n            arr.Set(static_cast\u003Cuint32_t>(i), row);\n        }\n        return arr;\n    }\n\n} \u002F\u002F namespace\n\nNapi::Object InitializeModule(Napi::Env env, Napi::Object exports)\n{\n    exports.Set(\"start\", Napi::Function::New(env, Start));\n    exports.Set(\"stop\", Napi::Function::New(env, Stop));\n    exports.Set(\"getFrame\", Napi::Function::New(env, GetFrame));\n    exports.Set(\"getSize\", Napi::Function::New(env, GetSize));\n    exports.Set(\"startRecording\", Napi::Function::New(env, StartRecording));\n    exports.Set(\"stopRecording\", Napi::Function::New(env, StopRecording));\n    exports.Set(\"isRecording\", Napi::Function::New(env, IsRecording));\n    exports.Set(\"getAudioOutputDevices\", Napi::Function::New(env, GetAudioOutputDevices));\n    return exports;\n}\n\nNODE_API_MODULE(capture_addon, InitializeModule)\n",[21,570,571,576,581,585,589,593,597,601,605,610,615,620,624,629,633,638,643,647,652,657,662,667,672,677,682,687,692,697,702,706,711,716,720,725,729,734,739,744,749,754,759,764,769,774,779,784,789,794,799,805,811,817,823,828,833,839,844,850,856,861,867,872,878,884,890,896,902,908,914,920,925,931,936,941,947,952,958,964,970,976,982,988,994,1000,1006,1012,1018,1024,1029],{"__ignoreMap":65},[69,572,573],{"class":71,"line":72},[69,574,575],{},"#include \u003Cnapi.h>\n",[69,577,578],{"class":71,"line":78},[69,579,580],{},"#include \u003Cwinrt\u002Fbase.h>\n",[69,582,583],{"class":71,"line":85},[69,584,469],{},[69,586,587],{"class":71,"line":91},[69,588,82],{"emptyLinePlaceholder":81},[69,590,591],{"class":71,"line":97},[69,592,100],{},[69,594,595],{"class":71,"line":103},[69,596,106],{},[69,598,599],{"class":71,"line":109},[69,600,112],{},[69,602,603],{"class":71,"line":115},[69,604,82],{"emptyLinePlaceholder":81},[69,606,607],{"class":71,"line":121},[69,608,609],{},"#include \"media_recorder.h\"\n",[69,611,612],{"class":71,"line":126},[69,613,614],{},"#include \"screen_capture.h\"\n",[69,616,617],{"class":71,"line":132},[69,618,619],{},"#include \"audio_output_devices.h\"\n",[69,621,622],{"class":71,"line":137},[69,623,82],{"emptyLinePlaceholder":81},[69,625,626],{"class":71,"line":143},[69,627,628],{},"namespace {\n",[69,630,631],{"class":71,"line":149},[69,632,82],{"emptyLinePlaceholder":81},[69,634,635],{"class":71,"line":155},[69,636,637],{},"    std::unique_ptr\u003CMonitorCapture> g_capture;\n",[69,639,640],{"class":71,"line":161},[69,641,642],{},"    std::once_flag g_winrt_apartment_once;\n",[69,644,645],{"class":71,"line":166},[69,646,82],{"emptyLinePlaceholder":81},[69,648,649],{"class":71,"line":172},[69,650,651],{},"    static void ensure_winrt_apartment()\n",[69,653,654],{"class":71,"line":178},[69,655,656],{},"    {\n",[69,658,659],{"class":71,"line":184},[69,660,661],{},"        std::call_once(g_winrt_apartment_once, []() {\n",[69,663,664],{"class":71,"line":189},[69,665,666],{},"            try {\n",[69,668,669],{"class":71,"line":195},[69,670,671],{},"                winrt::init_apartment(winrt::apartment_type::multi_threaded);\n",[69,673,674],{"class":71,"line":201},[69,675,676],{},"            }\n",[69,678,679],{"class":71,"line":207},[69,680,681],{},"            catch (winrt::hresult_error const& e) {\n",[69,683,684],{"class":71,"line":213},[69,685,686],{},"                const auto c = static_cast\u003Cuint32_t>(e.code());\n",[69,688,689],{"class":71,"line":218},[69,690,691],{},"                if (c != 0x80010106U)\n",[69,693,694],{"class":71,"line":224},[69,695,696],{},"                    throw;\n",[69,698,699],{"class":71,"line":230},[69,700,701],{},"                \u002F\u002F RPC_E_CHANGED_MODE：Electron 已初始化 COM，沿用现有单元即可\n",[69,703,704],{"class":71,"line":236},[69,705,676],{},[69,707,708],{"class":71,"line":241},[69,709,710],{},"        });\n",[69,712,713],{"class":71,"line":247},[69,714,715],{},"    }\n",[69,717,718],{"class":71,"line":253},[69,719,82],{"emptyLinePlaceholder":81},[69,721,722],{"class":71,"line":259},[69,723,724],{},"    static RecorderConfig ParseRecorderConfig(Napi::Object const& o)\n",[69,726,727],{"class":71,"line":264},[69,728,656],{},[69,730,731],{"class":71,"line":270},[69,732,733],{},"        RecorderConfig cfg{};\n",[69,735,736],{"class":71,"line":276},[69,737,738],{},"        cfg.width = o.Get(\"width\").As\u003CNapi::Number>().Uint32Value();\n",[69,740,741],{"class":71,"line":282},[69,742,743],{},"        cfg.height = o.Get(\"height\").As\u003CNapi::Number>().Uint32Value();\n",[69,745,746],{"class":71,"line":287},[69,747,748],{},"        cfg.fps = o.Get(\"fps\").As\u003CNapi::Number>().Uint32Value();\n",[69,750,751],{"class":71,"line":293},[69,752,753],{},"        if (o.Has(\"videoBitrate\"))\n",[69,755,756],{"class":71,"line":299},[69,757,758],{},"            cfg.videoBitrate = o.Get(\"videoBitrate\").As\u003CNapi::Number>().Uint32Value();\n",[69,760,761],{"class":71,"line":304},[69,762,763],{},"        if (o.Has(\"audioSampleRate\"))\n",[69,765,766],{"class":71,"line":310},[69,767,768],{},"            cfg.audioSampleRate = o.Get(\"audioSampleRate\").As\u003CNapi::Number>().Uint32Value();\n",[69,770,771],{"class":71,"line":316},[69,772,773],{},"        if (o.Has(\"audioChannels\"))\n",[69,775,776],{"class":71,"line":321},[69,777,778],{},"            cfg.audioChannels = o.Get(\"audioChannels\").As\u003CNapi::Number>().Uint32Value();\n",[69,780,781],{"class":71,"line":327},[69,782,783],{},"        if (o.Has(\"enableAudio\"))\n",[69,785,786],{"class":71,"line":333},[69,787,788],{},"            cfg.enableAudio = o.Get(\"enableAudio\").As\u003CNapi::Boolean>().Value();\n",[69,790,791],{"class":71,"line":339},[69,792,793],{},"        if (o.Has(\"audioOutputDeviceId\")) {\n",[69,795,796],{"class":71,"line":345},[69,797,798],{},"            const std::string u8 = o.Get(\"audioOutputDeviceId\").As\u003CNapi::String>().Utf8Value();\n",[69,800,802],{"class":71,"line":801},49,[69,803,804],{},"            if (!u8.empty())\n",[69,806,808],{"class":71,"line":807},50,[69,809,810],{},"                cfg.audioOutputDeviceId = Utf8ToWide(u8); \u002F\u002F MultiByteToWideChar CP_UTF8\n",[69,812,814],{"class":71,"line":813},51,[69,815,816],{},"        }\n",[69,818,820],{"class":71,"line":819},52,[69,821,822],{},"        return cfg;\n",[69,824,826],{"class":71,"line":825},53,[69,827,715],{},[69,829,831],{"class":71,"line":830},54,[69,832,82],{"emptyLinePlaceholder":81},[69,834,836],{"class":71,"line":835},55,[69,837,838],{},"    \u002F\u002F Start \u002F GetFrame \u002F Stop \u002F GetSize：与旧笔记相同思路……\n",[69,840,842],{"class":71,"line":841},56,[69,843,82],{"emptyLinePlaceholder":81},[69,845,847],{"class":71,"line":846},57,[69,848,849],{},"    \u002F** JS: startRecording(outputPathUtf8, config) — path 用 UTF-8 *\u002F\n",[69,851,853],{"class":71,"line":852},58,[69,854,855],{},"    Napi::Value StartRecording(const Napi::CallbackInfo& info) { \u002F* … *\u002F }\n",[69,857,859],{"class":71,"line":858},59,[69,860,82],{"emptyLinePlaceholder":81},[69,862,864],{"class":71,"line":863},60,[69,865,866],{},"    Napi::Value GetAudioOutputDevices(const Napi::CallbackInfo& info)\n",[69,868,870],{"class":71,"line":869},61,[69,871,656],{},[69,873,875],{"class":71,"line":874},62,[69,876,877],{},"        Napi::Env env = info.Env();\n",[69,879,881],{"class":71,"line":880},63,[69,882,883],{},"        const auto devices = EnumerateAudioRenderDevices();\n",[69,885,887],{"class":71,"line":886},64,[69,888,889],{},"        Napi::Array arr = Napi::Array::New(env, devices.size());\n",[69,891,893],{"class":71,"line":892},65,[69,894,895],{},"        for (size_t i = 0; i \u003C devices.size(); ++i) {\n",[69,897,899],{"class":71,"line":898},66,[69,900,901],{},"            Napi::Object row = Napi::Object::New(env);\n",[69,903,905],{"class":71,"line":904},67,[69,906,907],{},"            row.Set(\"id\", Napi::String::New(env, WideToUtf8(devices[i].id)));\n",[69,909,911],{"class":71,"line":910},68,[69,912,913],{},"            row.Set(\"name\", Napi::String::New(env, WideToUtf8(devices[i].friendlyName)));\n",[69,915,917],{"class":71,"line":916},69,[69,918,919],{},"            arr.Set(static_cast\u003Cuint32_t>(i), row);\n",[69,921,923],{"class":71,"line":922},70,[69,924,816],{},[69,926,928],{"class":71,"line":927},71,[69,929,930],{},"        return arr;\n",[69,932,934],{"class":71,"line":933},72,[69,935,715],{},[69,937,939],{"class":71,"line":938},73,[69,940,82],{"emptyLinePlaceholder":81},[69,942,944],{"class":71,"line":943},74,[69,945,946],{},"} \u002F\u002F namespace\n",[69,948,950],{"class":71,"line":949},75,[69,951,82],{"emptyLinePlaceholder":81},[69,953,955],{"class":71,"line":954},76,[69,956,957],{},"Napi::Object InitializeModule(Napi::Env env, Napi::Object exports)\n",[69,959,961],{"class":71,"line":960},77,[69,962,963],{},"{\n",[69,965,967],{"class":71,"line":966},78,[69,968,969],{},"    exports.Set(\"start\", Napi::Function::New(env, Start));\n",[69,971,973],{"class":71,"line":972},79,[69,974,975],{},"    exports.Set(\"stop\", Napi::Function::New(env, Stop));\n",[69,977,979],{"class":71,"line":978},80,[69,980,981],{},"    exports.Set(\"getFrame\", Napi::Function::New(env, GetFrame));\n",[69,983,985],{"class":71,"line":984},81,[69,986,987],{},"    exports.Set(\"getSize\", Napi::Function::New(env, GetSize));\n",[69,989,991],{"class":71,"line":990},82,[69,992,993],{},"    exports.Set(\"startRecording\", Napi::Function::New(env, StartRecording));\n",[69,995,997],{"class":71,"line":996},83,[69,998,999],{},"    exports.Set(\"stopRecording\", Napi::Function::New(env, StopRecording));\n",[69,1001,1003],{"class":71,"line":1002},84,[69,1004,1005],{},"    exports.Set(\"isRecording\", Napi::Function::New(env, IsRecording));\n",[69,1007,1009],{"class":71,"line":1008},85,[69,1010,1011],{},"    exports.Set(\"getAudioOutputDevices\", Napi::Function::New(env, GetAudioOutputDevices));\n",[69,1013,1015],{"class":71,"line":1014},86,[69,1016,1017],{},"    return exports;\n",[69,1019,1021],{"class":71,"line":1020},87,[69,1022,1023],{},"}\n",[69,1025,1027],{"class":71,"line":1026},88,[69,1028,82],{"emptyLinePlaceholder":81},[69,1030,1032],{"class":71,"line":1031},89,[69,1033,1034],{},"NODE_API_MODULE(capture_addon, InitializeModule)\n",[14,1036,1037,1038,1041],{},"我们使用了std::call_once，来配合std::once_flag，保证传入的初始化代码在整个进程执行一次，其他线程若随后也执行到了call_once的话，会阻塞等待第一次跑完来返回结果而不会跑第二遍。无论多少次调用 ",[21,1039,1040],{},"ensure_winrt_apartment()","、无论从哪个线程进来，WinRT\u002FCOM 这套「初始化公寓」逻辑至多执行一次。",[14,1043,1044],{},"COM经常是按照线程来初始化的，至于为什么叫做apartment，是因为一个线程上的COM并发模型分区：",[14,1046,1047],{},"线程在参与COM之前必须声明线程大概在哪中apartment里，常见的有STA单线程、MTA多线程等。不同的Apartment里创建的COM对象，跨线程互相调用的时候要守一套排队、封送规则，就像对象被关在隔间。",[14,1049,1050,1051,1054],{},"头文件的来源可以用vcpkg，但是链接的时候必须与当前的Electron自带的Node ABI一致，vcpkg解决node-addon-api 头从哪来；",[26,1052,1053],{},"ABI 对齐仍靠 Electron 官方流程","。",[14,1056,1057],{},"我们为了适配electron版本和原生模块的关系，能够用cmake-js针对electron干净地配置并且编译+确认缓冲指向eletron以及把.node模块放在preload中，能干净地按照固定的相对路径找到位置，指定cmake里面怎么链接winrt、delay-load、win_delay_load_hook，运行时是\u002FMD等。可以编写一个js脚本：",[60,1059,1063],{"className":1060,"code":1061,"language":1062,"meta":65,"style":65},"language-js shiki shiki-themes github-light github-dark","\u002F**\n * 必须用 cmake-js 针对 **Electron** 重新生成 CMake 缓存并链接 Electron 的 node.lib。\n * 若以前用默认 Node 编过，`build\u002F` 里会残留 NODE_RUNTIME=node，此时复制 .node 仍会一加载就崩。\n *\n * 用法：在 capture 目录执行 `pnpm run build:addon`\n *\u002F\nimport { spawnSync } from 'node:child_process'\nimport { copyFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst require = createRequire(import.meta.url)\nconst electronPkg = join(__dirname, '..\u002Fnode_modules\u002Felectron\u002Fpackage.json')\nconst electronVer = require(electronPkg).version\nconst addonRoot = join(__dirname, '..\u002F..\u002FglfwExample')\n\nconsole.log('[build:addon] cmake 工程:', addonRoot)\nconsole.log('[build:addon] Electron:', electronVer)\n\nconst npx = process.platform === 'win32' ? 'npx.cmd' : 'npx'\n\nfunction run(args, label) {\n  console.log('[build:addon]', label, args.join(' '))\n  const r = spawnSync(npx, args, { cwd: addonRoot, stdio: 'inherit', shell: true })\n  if ((r.status ?? 1) !== 0) {\n    console.error('[build:addon] 失败:', label)\n    process.exit(r.status ?? 1)\n  }\n}\n\n  \n\n\u002F** 清掉旧缓存，否则会一直链接「系统 Node」的 node.lib *\u002F\nrun(['cmake-js', 'clean'], 'clean')\nrun(\n  ['cmake-js', 'compile', '--runtime=electron', `--runtime-version=${electronVer}`],\n  'compile(electron)'\n)\n\n  \nconst cacheFile = join(addonRoot, 'build\u002FCMakeCache.txt')\nif (existsSync(cacheFile)) {\n  const cache = readFileSync(cacheFile, 'utf8')\n  const rtLine = cache.split('\\n').find((l) => l.startsWith('NODE_RUNTIME'))\n  if (!rtLine || !\u002Felectron\u002Fi.test(rtLine)) {\n    console.error('[build:addon] CMakeCache 未指向 electron runtime，当前行:', rtLine ?? '(无)')\n    console.error('[build:addon] 请先删除文件夹:', join(addonRoot, 'build'), '再重跑本脚本')\n    process.exit(1)\n  }\n}\n\nconst release = join(addonRoot, 'build\u002FRelease\u002Fcapture_addon.node')\nconst debug = join(addonRoot, 'build\u002FDebug\u002Fcapture_addon.node')\nconst src = existsSync(release) ? release : debug\nif (!existsSync(src)) {\n  console.error('[build:addon] 未找到:', release, debug)\n  process.exit(1)\n}\n\nconst dstDir = join(__dirname, '..\u002Fnative\u002Fwgc-addon')\nconst dst = join(dstDir, 'capture_addon.node')\nmkdirSync(dstDir, { recursive: true })\ncopyFileSync(src, dst)\nconsole.log('[build:addon] 已复制 ->', dst)\n","js",[21,1064,1065,1071,1076,1081,1086,1091,1096,1113,1125,1137,1149,1161,1165,1200,1223,1244,1258,1276,1280,1296,1310,1314,1344,1348,1371,1397,1425,1450,1466,1483,1488,1492,1496,1501,1505,1510,1533,1540,1571,1576,1580,1584,1588,1607,1621,1641,1696,1733,1754,1782,1795,1799,1803,1807,1825,1843,1870,1883,1897,1910,1914,1918,1936,1955,1967,1975],{"__ignoreMap":65},[69,1066,1067],{"class":71,"line":72},[69,1068,1070],{"class":1069},"sJ8bj","\u002F**\n",[69,1072,1073],{"class":71,"line":78},[69,1074,1075],{"class":1069}," * 必须用 cmake-js 针对 **Electron** 重新生成 CMake 缓存并链接 Electron 的 node.lib。\n",[69,1077,1078],{"class":71,"line":85},[69,1079,1080],{"class":1069}," * 若以前用默认 Node 编过，`build\u002F` 里会残留 NODE_RUNTIME=node，此时复制 .node 仍会一加载就崩。\n",[69,1082,1083],{"class":71,"line":91},[69,1084,1085],{"class":1069}," *\n",[69,1087,1088],{"class":71,"line":97},[69,1089,1090],{"class":1069}," * 用法：在 capture 目录执行 `pnpm run build:addon`\n",[69,1092,1093],{"class":71,"line":103},[69,1094,1095],{"class":1069}," *\u002F\n",[69,1097,1098,1102,1106,1109],{"class":71,"line":109},[69,1099,1101],{"class":1100},"szBVR","import",[69,1103,1105],{"class":1104},"sVt8B"," { spawnSync } ",[69,1107,1108],{"class":1100},"from",[69,1110,1112],{"class":1111},"sZZnC"," 'node:child_process'\n",[69,1114,1115,1117,1120,1122],{"class":71,"line":115},[69,1116,1101],{"class":1100},[69,1118,1119],{"class":1104}," { copyFileSync, existsSync, mkdirSync, readFileSync } ",[69,1121,1108],{"class":1100},[69,1123,1124],{"class":1111}," 'node:fs'\n",[69,1126,1127,1129,1132,1134],{"class":71,"line":121},[69,1128,1101],{"class":1100},[69,1130,1131],{"class":1104}," { createRequire } ",[69,1133,1108],{"class":1100},[69,1135,1136],{"class":1111}," 'node:module'\n",[69,1138,1139,1141,1144,1146],{"class":71,"line":126},[69,1140,1101],{"class":1100},[69,1142,1143],{"class":1104}," { dirname, join } ",[69,1145,1108],{"class":1100},[69,1147,1148],{"class":1111}," 'node:path'\n",[69,1150,1151,1153,1156,1158],{"class":71,"line":132},[69,1152,1101],{"class":1100},[69,1154,1155],{"class":1104}," { fileURLToPath } ",[69,1157,1108],{"class":1100},[69,1159,1160],{"class":1111}," 'node:url'\n",[69,1162,1163],{"class":71,"line":137},[69,1164,82],{"emptyLinePlaceholder":81},[69,1166,1167,1170,1174,1177,1181,1184,1187,1189,1191,1194,1197],{"class":71,"line":143},[69,1168,1169],{"class":1100},"const",[69,1171,1173],{"class":1172},"sj4cs"," __dirname",[69,1175,1176],{"class":1100}," =",[69,1178,1180],{"class":1179},"sScJk"," dirname",[69,1182,1183],{"class":1104},"(",[69,1185,1186],{"class":1179},"fileURLToPath",[69,1188,1183],{"class":1104},[69,1190,1101],{"class":1100},[69,1192,1193],{"class":1104},".",[69,1195,1196],{"class":1172},"meta",[69,1198,1199],{"class":1104},".url))\n",[69,1201,1202,1204,1207,1209,1212,1214,1216,1218,1220],{"class":71,"line":149},[69,1203,1169],{"class":1100},[69,1205,1206],{"class":1172}," require",[69,1208,1176],{"class":1100},[69,1210,1211],{"class":1179}," createRequire",[69,1213,1183],{"class":1104},[69,1215,1101],{"class":1100},[69,1217,1193],{"class":1104},[69,1219,1196],{"class":1172},[69,1221,1222],{"class":1104},".url)\n",[69,1224,1225,1227,1230,1232,1235,1238,1241],{"class":71,"line":155},[69,1226,1169],{"class":1100},[69,1228,1229],{"class":1172}," electronPkg",[69,1231,1176],{"class":1100},[69,1233,1234],{"class":1179}," join",[69,1236,1237],{"class":1104},"(__dirname, ",[69,1239,1240],{"class":1111},"'..\u002Fnode_modules\u002Felectron\u002Fpackage.json'",[69,1242,1243],{"class":1104},")\n",[69,1245,1246,1248,1251,1253,1255],{"class":71,"line":161},[69,1247,1169],{"class":1100},[69,1249,1250],{"class":1172}," electronVer",[69,1252,1176],{"class":1100},[69,1254,1206],{"class":1179},[69,1256,1257],{"class":1104},"(electronPkg).version\n",[69,1259,1260,1262,1265,1267,1269,1271,1274],{"class":71,"line":166},[69,1261,1169],{"class":1100},[69,1263,1264],{"class":1172}," addonRoot",[69,1266,1176],{"class":1100},[69,1268,1234],{"class":1179},[69,1270,1237],{"class":1104},[69,1272,1273],{"class":1111},"'..\u002F..\u002FglfwExample'",[69,1275,1243],{"class":1104},[69,1277,1278],{"class":71,"line":172},[69,1279,82],{"emptyLinePlaceholder":81},[69,1281,1282,1285,1288,1290,1293],{"class":71,"line":178},[69,1283,1284],{"class":1104},"console.",[69,1286,1287],{"class":1179},"log",[69,1289,1183],{"class":1104},[69,1291,1292],{"class":1111},"'[build:addon] cmake 工程:'",[69,1294,1295],{"class":1104},", addonRoot)\n",[69,1297,1298,1300,1302,1304,1307],{"class":71,"line":184},[69,1299,1284],{"class":1104},[69,1301,1287],{"class":1179},[69,1303,1183],{"class":1104},[69,1305,1306],{"class":1111},"'[build:addon] Electron:'",[69,1308,1309],{"class":1104},", electronVer)\n",[69,1311,1312],{"class":71,"line":189},[69,1313,82],{"emptyLinePlaceholder":81},[69,1315,1316,1318,1321,1323,1326,1329,1332,1335,1338,1341],{"class":71,"line":195},[69,1317,1169],{"class":1100},[69,1319,1320],{"class":1172}," npx",[69,1322,1176],{"class":1100},[69,1324,1325],{"class":1104}," process.platform ",[69,1327,1328],{"class":1100},"===",[69,1330,1331],{"class":1111}," 'win32'",[69,1333,1334],{"class":1100}," ?",[69,1336,1337],{"class":1111}," 'npx.cmd'",[69,1339,1340],{"class":1100}," :",[69,1342,1343],{"class":1111}," 'npx'\n",[69,1345,1346],{"class":71,"line":201},[69,1347,82],{"emptyLinePlaceholder":81},[69,1349,1350,1353,1356,1358,1362,1365,1368],{"class":71,"line":207},[69,1351,1352],{"class":1100},"function",[69,1354,1355],{"class":1179}," run",[69,1357,1183],{"class":1104},[69,1359,1361],{"class":1360},"s4XuR","args",[69,1363,1364],{"class":1104},", ",[69,1366,1367],{"class":1360},"label",[69,1369,1370],{"class":1104},") {\n",[69,1372,1373,1376,1378,1380,1383,1386,1389,1391,1394],{"class":71,"line":213},[69,1374,1375],{"class":1104},"  console.",[69,1377,1287],{"class":1179},[69,1379,1183],{"class":1104},[69,1381,1382],{"class":1111},"'[build:addon]'",[69,1384,1385],{"class":1104},", label, args.",[69,1387,1388],{"class":1179},"join",[69,1390,1183],{"class":1104},[69,1392,1393],{"class":1111},"' '",[69,1395,1396],{"class":1104},"))\n",[69,1398,1399,1402,1405,1407,1410,1413,1416,1419,1422],{"class":71,"line":218},[69,1400,1401],{"class":1100},"  const",[69,1403,1404],{"class":1172}," r",[69,1406,1176],{"class":1100},[69,1408,1409],{"class":1179}," spawnSync",[69,1411,1412],{"class":1104},"(npx, args, { cwd: addonRoot, stdio: ",[69,1414,1415],{"class":1111},"'inherit'",[69,1417,1418],{"class":1104},", shell: ",[69,1420,1421],{"class":1172},"true",[69,1423,1424],{"class":1104}," })\n",[69,1426,1427,1430,1433,1436,1439,1442,1445,1448],{"class":71,"line":224},[69,1428,1429],{"class":1100},"  if",[69,1431,1432],{"class":1104}," ((r.status ",[69,1434,1435],{"class":1100},"??",[69,1437,1438],{"class":1172}," 1",[69,1440,1441],{"class":1104},") ",[69,1443,1444],{"class":1100},"!==",[69,1446,1447],{"class":1172}," 0",[69,1449,1370],{"class":1104},[69,1451,1452,1455,1458,1460,1463],{"class":71,"line":230},[69,1453,1454],{"class":1104},"    console.",[69,1456,1457],{"class":1179},"error",[69,1459,1183],{"class":1104},[69,1461,1462],{"class":1111},"'[build:addon] 失败:'",[69,1464,1465],{"class":1104},", label)\n",[69,1467,1468,1471,1474,1477,1479,1481],{"class":71,"line":236},[69,1469,1470],{"class":1104},"    process.",[69,1472,1473],{"class":1179},"exit",[69,1475,1476],{"class":1104},"(r.status ",[69,1478,1435],{"class":1100},[69,1480,1438],{"class":1172},[69,1482,1243],{"class":1104},[69,1484,1485],{"class":71,"line":241},[69,1486,1487],{"class":1104},"  }\n",[69,1489,1490],{"class":71,"line":247},[69,1491,1023],{"class":1104},[69,1493,1494],{"class":71,"line":253},[69,1495,82],{"emptyLinePlaceholder":81},[69,1497,1498],{"class":71,"line":259},[69,1499,1500],{"class":1104},"  \n",[69,1502,1503],{"class":71,"line":264},[69,1504,82],{"emptyLinePlaceholder":81},[69,1506,1507],{"class":71,"line":270},[69,1508,1509],{"class":1069},"\u002F** 清掉旧缓存，否则会一直链接「系统 Node」的 node.lib *\u002F\n",[69,1511,1512,1515,1518,1521,1523,1526,1529,1531],{"class":71,"line":276},[69,1513,1514],{"class":1179},"run",[69,1516,1517],{"class":1104},"([",[69,1519,1520],{"class":1111},"'cmake-js'",[69,1522,1364],{"class":1104},[69,1524,1525],{"class":1111},"'clean'",[69,1527,1528],{"class":1104},"], ",[69,1530,1525],{"class":1111},[69,1532,1243],{"class":1104},[69,1534,1535,1537],{"class":71,"line":282},[69,1536,1514],{"class":1179},[69,1538,1539],{"class":1104},"(\n",[69,1541,1542,1545,1547,1549,1552,1554,1557,1559,1562,1565,1568],{"class":71,"line":287},[69,1543,1544],{"class":1104},"  [",[69,1546,1520],{"class":1111},[69,1548,1364],{"class":1104},[69,1550,1551],{"class":1111},"'compile'",[69,1553,1364],{"class":1104},[69,1555,1556],{"class":1111},"'--runtime=electron'",[69,1558,1364],{"class":1104},[69,1560,1561],{"class":1111},"`--runtime-version=${",[69,1563,1564],{"class":1104},"electronVer",[69,1566,1567],{"class":1111},"}`",[69,1569,1570],{"class":1104},"],\n",[69,1572,1573],{"class":71,"line":293},[69,1574,1575],{"class":1111},"  'compile(electron)'\n",[69,1577,1578],{"class":71,"line":299},[69,1579,1243],{"class":1104},[69,1581,1582],{"class":71,"line":304},[69,1583,82],{"emptyLinePlaceholder":81},[69,1585,1586],{"class":71,"line":310},[69,1587,1500],{"class":1104},[69,1589,1590,1592,1595,1597,1599,1602,1605],{"class":71,"line":316},[69,1591,1169],{"class":1100},[69,1593,1594],{"class":1172}," cacheFile",[69,1596,1176],{"class":1100},[69,1598,1234],{"class":1179},[69,1600,1601],{"class":1104},"(addonRoot, ",[69,1603,1604],{"class":1111},"'build\u002FCMakeCache.txt'",[69,1606,1243],{"class":1104},[69,1608,1609,1612,1615,1618],{"class":71,"line":321},[69,1610,1611],{"class":1100},"if",[69,1613,1614],{"class":1104}," (",[69,1616,1617],{"class":1179},"existsSync",[69,1619,1620],{"class":1104},"(cacheFile)) {\n",[69,1622,1623,1625,1628,1630,1633,1636,1639],{"class":71,"line":327},[69,1624,1401],{"class":1100},[69,1626,1627],{"class":1172}," cache",[69,1629,1176],{"class":1100},[69,1631,1632],{"class":1179}," readFileSync",[69,1634,1635],{"class":1104},"(cacheFile, ",[69,1637,1638],{"class":1111},"'utf8'",[69,1640,1243],{"class":1104},[69,1642,1643,1645,1648,1650,1653,1656,1658,1661,1664,1666,1669,1672,1675,1678,1680,1683,1686,1689,1691,1694],{"class":71,"line":333},[69,1644,1401],{"class":1100},[69,1646,1647],{"class":1172}," rtLine",[69,1649,1176],{"class":1100},[69,1651,1652],{"class":1104}," cache.",[69,1654,1655],{"class":1179},"split",[69,1657,1183],{"class":1104},[69,1659,1660],{"class":1111},"'",[69,1662,1663],{"class":1172},"\\n",[69,1665,1660],{"class":1111},[69,1667,1668],{"class":1104},").",[69,1670,1671],{"class":1179},"find",[69,1673,1674],{"class":1104},"((",[69,1676,1677],{"class":1360},"l",[69,1679,1441],{"class":1104},[69,1681,1682],{"class":1100},"=>",[69,1684,1685],{"class":1104}," l.",[69,1687,1688],{"class":1179},"startsWith",[69,1690,1183],{"class":1104},[69,1692,1693],{"class":1111},"'NODE_RUNTIME'",[69,1695,1396],{"class":1104},[69,1697,1698,1700,1702,1705,1708,1711,1714,1716,1720,1722,1725,1727,1730],{"class":71,"line":339},[69,1699,1429],{"class":1100},[69,1701,1614],{"class":1104},[69,1703,1704],{"class":1100},"!",[69,1706,1707],{"class":1104},"rtLine ",[69,1709,1710],{"class":1100},"||",[69,1712,1713],{"class":1100}," !",[69,1715,419],{"class":1111},[69,1717,1719],{"class":1718},"sA_wV","electron",[69,1721,419],{"class":1111},[69,1723,1724],{"class":1100},"i",[69,1726,1193],{"class":1104},[69,1728,1729],{"class":1179},"test",[69,1731,1732],{"class":1104},"(rtLine)) {\n",[69,1734,1735,1737,1739,1741,1744,1747,1749,1752],{"class":71,"line":345},[69,1736,1454],{"class":1104},[69,1738,1457],{"class":1179},[69,1740,1183],{"class":1104},[69,1742,1743],{"class":1111},"'[build:addon] CMakeCache 未指向 electron runtime，当前行:'",[69,1745,1746],{"class":1104},", rtLine ",[69,1748,1435],{"class":1100},[69,1750,1751],{"class":1111}," '(无)'",[69,1753,1243],{"class":1104},[69,1755,1756,1758,1760,1762,1765,1767,1769,1771,1774,1777,1780],{"class":71,"line":801},[69,1757,1454],{"class":1104},[69,1759,1457],{"class":1179},[69,1761,1183],{"class":1104},[69,1763,1764],{"class":1111},"'[build:addon] 请先删除文件夹:'",[69,1766,1364],{"class":1104},[69,1768,1388],{"class":1179},[69,1770,1601],{"class":1104},[69,1772,1773],{"class":1111},"'build'",[69,1775,1776],{"class":1104},"), ",[69,1778,1779],{"class":1111},"'再重跑本脚本'",[69,1781,1243],{"class":1104},[69,1783,1784,1786,1788,1790,1793],{"class":71,"line":807},[69,1785,1470],{"class":1104},[69,1787,1473],{"class":1179},[69,1789,1183],{"class":1104},[69,1791,1792],{"class":1172},"1",[69,1794,1243],{"class":1104},[69,1796,1797],{"class":71,"line":813},[69,1798,1487],{"class":1104},[69,1800,1801],{"class":71,"line":819},[69,1802,1023],{"class":1104},[69,1804,1805],{"class":71,"line":825},[69,1806,82],{"emptyLinePlaceholder":81},[69,1808,1809,1811,1814,1816,1818,1820,1823],{"class":71,"line":830},[69,1810,1169],{"class":1100},[69,1812,1813],{"class":1172}," release",[69,1815,1176],{"class":1100},[69,1817,1234],{"class":1179},[69,1819,1601],{"class":1104},[69,1821,1822],{"class":1111},"'build\u002FRelease\u002Fcapture_addon.node'",[69,1824,1243],{"class":1104},[69,1826,1827,1829,1832,1834,1836,1838,1841],{"class":71,"line":835},[69,1828,1169],{"class":1100},[69,1830,1831],{"class":1172}," debug",[69,1833,1176],{"class":1100},[69,1835,1234],{"class":1179},[69,1837,1601],{"class":1104},[69,1839,1840],{"class":1111},"'build\u002FDebug\u002Fcapture_addon.node'",[69,1842,1243],{"class":1104},[69,1844,1845,1847,1850,1852,1855,1858,1861,1864,1867],{"class":71,"line":841},[69,1846,1169],{"class":1100},[69,1848,1849],{"class":1172}," src",[69,1851,1176],{"class":1100},[69,1853,1854],{"class":1179}," existsSync",[69,1856,1857],{"class":1104},"(release) ",[69,1859,1860],{"class":1100},"?",[69,1862,1863],{"class":1104}," release ",[69,1865,1866],{"class":1100},":",[69,1868,1869],{"class":1104}," debug\n",[69,1871,1872,1874,1876,1878,1880],{"class":71,"line":846},[69,1873,1611],{"class":1100},[69,1875,1614],{"class":1104},[69,1877,1704],{"class":1100},[69,1879,1617],{"class":1179},[69,1881,1882],{"class":1104},"(src)) {\n",[69,1884,1885,1887,1889,1891,1894],{"class":71,"line":852},[69,1886,1375],{"class":1104},[69,1888,1457],{"class":1179},[69,1890,1183],{"class":1104},[69,1892,1893],{"class":1111},"'[build:addon] 未找到:'",[69,1895,1896],{"class":1104},", release, debug)\n",[69,1898,1899,1902,1904,1906,1908],{"class":71,"line":858},[69,1900,1901],{"class":1104},"  process.",[69,1903,1473],{"class":1179},[69,1905,1183],{"class":1104},[69,1907,1792],{"class":1172},[69,1909,1243],{"class":1104},[69,1911,1912],{"class":71,"line":863},[69,1913,1023],{"class":1104},[69,1915,1916],{"class":71,"line":869},[69,1917,82],{"emptyLinePlaceholder":81},[69,1919,1920,1922,1925,1927,1929,1931,1934],{"class":71,"line":874},[69,1921,1169],{"class":1100},[69,1923,1924],{"class":1172}," dstDir",[69,1926,1176],{"class":1100},[69,1928,1234],{"class":1179},[69,1930,1237],{"class":1104},[69,1932,1933],{"class":1111},"'..\u002Fnative\u002Fwgc-addon'",[69,1935,1243],{"class":1104},[69,1937,1938,1940,1943,1945,1947,1950,1953],{"class":71,"line":880},[69,1939,1169],{"class":1100},[69,1941,1942],{"class":1172}," dst",[69,1944,1176],{"class":1100},[69,1946,1234],{"class":1179},[69,1948,1949],{"class":1104},"(dstDir, ",[69,1951,1952],{"class":1111},"'capture_addon.node'",[69,1954,1243],{"class":1104},[69,1956,1957,1960,1963,1965],{"class":71,"line":886},[69,1958,1959],{"class":1179},"mkdirSync",[69,1961,1962],{"class":1104},"(dstDir, { recursive: ",[69,1964,1421],{"class":1172},[69,1966,1424],{"class":1104},[69,1968,1969,1972],{"class":71,"line":892},[69,1970,1971],{"class":1179},"copyFileSync",[69,1973,1974],{"class":1104},"(src, dst)\n",[69,1976,1977,1979,1981,1983,1986],{"class":71,"line":898},[69,1978,1284],{"class":1104},[69,1980,1287],{"class":1179},[69,1982,1183],{"class":1104},[69,1984,1985],{"class":1111},"'[build:addon] 已复制 ->'",[69,1987,1988],{"class":1104},", dst)\n",[14,1990,1991],{},"我们使用了spawnSync方法，同步运行一个子进程并且执行命令，一直等到这个命令结束，函数返回。这里的stdio:'inherit'表示子进程的标准输入\u002F错误 直接接到当前的终端，所以我们可以看到cmake的完整输出。shell:true表示系统shell里执行，windows上能够更稳找到npx.cmd。",[60,1993,1995],{"className":1060,"code":1994,"language":1062,"meta":65,"style":65},"const cacheFile = join(addonRoot, 'build\u002FCMakeCache.txt')\nif (existsSync(cacheFile)) {\n  const cache = readFileSync(cacheFile, 'utf8')\n  const rtLine = cache.split('\\n').find((l) => l.startsWith('NODE_RUNTIME'))\n  if (!rtLine || !\u002Felectron\u002Fi.test(rtLine)) {\n    console.error('[build:addon] CMakeCache 未指向 electron runtime，当前行:', rtLine ?? '(无)')\n    console.error('[build:addon] 请先删除文件夹:', join(addonRoot, 'build'), '再重跑本脚本')\n    process.exit(1)\n  }\n}\n",[21,1996,1997,2013,2023,2039,2081,2109,2127,2151,2163,2167],{"__ignoreMap":65},[69,1998,1999,2001,2003,2005,2007,2009,2011],{"class":71,"line":72},[69,2000,1169],{"class":1100},[69,2002,1594],{"class":1172},[69,2004,1176],{"class":1100},[69,2006,1234],{"class":1179},[69,2008,1601],{"class":1104},[69,2010,1604],{"class":1111},[69,2012,1243],{"class":1104},[69,2014,2015,2017,2019,2021],{"class":71,"line":78},[69,2016,1611],{"class":1100},[69,2018,1614],{"class":1104},[69,2020,1617],{"class":1179},[69,2022,1620],{"class":1104},[69,2024,2025,2027,2029,2031,2033,2035,2037],{"class":71,"line":85},[69,2026,1401],{"class":1100},[69,2028,1627],{"class":1172},[69,2030,1176],{"class":1100},[69,2032,1632],{"class":1179},[69,2034,1635],{"class":1104},[69,2036,1638],{"class":1111},[69,2038,1243],{"class":1104},[69,2040,2041,2043,2045,2047,2049,2051,2053,2055,2057,2059,2061,2063,2065,2067,2069,2071,2073,2075,2077,2079],{"class":71,"line":91},[69,2042,1401],{"class":1100},[69,2044,1647],{"class":1172},[69,2046,1176],{"class":1100},[69,2048,1652],{"class":1104},[69,2050,1655],{"class":1179},[69,2052,1183],{"class":1104},[69,2054,1660],{"class":1111},[69,2056,1663],{"class":1172},[69,2058,1660],{"class":1111},[69,2060,1668],{"class":1104},[69,2062,1671],{"class":1179},[69,2064,1674],{"class":1104},[69,2066,1677],{"class":1360},[69,2068,1441],{"class":1104},[69,2070,1682],{"class":1100},[69,2072,1685],{"class":1104},[69,2074,1688],{"class":1179},[69,2076,1183],{"class":1104},[69,2078,1693],{"class":1111},[69,2080,1396],{"class":1104},[69,2082,2083,2085,2087,2089,2091,2093,2095,2097,2099,2101,2103,2105,2107],{"class":71,"line":97},[69,2084,1429],{"class":1100},[69,2086,1614],{"class":1104},[69,2088,1704],{"class":1100},[69,2090,1707],{"class":1104},[69,2092,1710],{"class":1100},[69,2094,1713],{"class":1100},[69,2096,419],{"class":1111},[69,2098,1719],{"class":1718},[69,2100,419],{"class":1111},[69,2102,1724],{"class":1100},[69,2104,1193],{"class":1104},[69,2106,1729],{"class":1179},[69,2108,1732],{"class":1104},[69,2110,2111,2113,2115,2117,2119,2121,2123,2125],{"class":71,"line":103},[69,2112,1454],{"class":1104},[69,2114,1457],{"class":1179},[69,2116,1183],{"class":1104},[69,2118,1743],{"class":1111},[69,2120,1746],{"class":1104},[69,2122,1435],{"class":1100},[69,2124,1751],{"class":1111},[69,2126,1243],{"class":1104},[69,2128,2129,2131,2133,2135,2137,2139,2141,2143,2145,2147,2149],{"class":71,"line":109},[69,2130,1454],{"class":1104},[69,2132,1457],{"class":1179},[69,2134,1183],{"class":1104},[69,2136,1764],{"class":1111},[69,2138,1364],{"class":1104},[69,2140,1388],{"class":1179},[69,2142,1601],{"class":1104},[69,2144,1773],{"class":1111},[69,2146,1776],{"class":1104},[69,2148,1779],{"class":1111},[69,2150,1243],{"class":1104},[69,2152,2153,2155,2157,2159,2161],{"class":71,"line":115},[69,2154,1470],{"class":1104},[69,2156,1473],{"class":1179},[69,2158,1183],{"class":1104},[69,2160,1792],{"class":1172},[69,2162,1243],{"class":1104},[69,2164,2165],{"class":71,"line":121},[69,2166,1487],{"class":1104},[69,2168,2169],{"class":71,"line":126},[69,2170,1023],{"class":1104},[14,2172,2173,2174,2176],{},"这段使用cacheFile，cmake会把本次配置写进这个文件，里面会有NODE_RUNTIME=...之类的项，然后读文件按行找：找到以NODE_RUNTIME开头的那一行， 因为cmake-js会写入当前是按哪种runtime配置的。校验：这一行里必须能匹配 ",[21,2175,1719],{},"（不区分大小写）。",[14,2178,2179,2180,2183,2184,2186,2187,2190],{},"若找不到这一行，或里面是 ",[21,2181,2182],{},"node"," 而不是 ",[21,2185,1719],{},"，说明 缓存仍不对：最常见是以前用默认 Node 编过、",[21,2188,2189],{},"cmake-js clean"," 没生效、或手动改过目录但没重新配置。",[14,2192,2193,2194,2197,2198,2201,2202,2205,2206,1054],{},"失败时：打印当前读到的行（或「无」），提示你去 删掉整个 ",[21,2195,2196],{},"build"," 文件夹 再跑脚本，然后 ",[21,2199,2200],{},"process.exit(1)","，避免后面还以为编译成功、把错的 ",[21,2203,2204],{},".node"," 复制去 ",[21,2207,2208],{},"capture",[14,2210,2211],{},"最后正常的找到.node并且复制过去。",[14,2213,2214],{},"那么我们可以在终端里初始化这个c++项目，pnpm init，然后安装一下node-addon-api，之后在运行npx node-gyp install即可",[60,2216,2220],{"className":2217,"code":2218,"language":2219,"meta":65,"style":65},"language-shell shiki shiki-themes github-light github-dark","PS F:\\glfwExamle\\glfwExample> npx node-gyp install\nNeed to install the following packages:\nnode-gyp@12.3.0\nOk to proceed? (y) y\n\ngyp info it worked if it ends with ok\ngyp info using node-gyp@12.3.0\ngyp info using node@22.16.0 | win32 | x64\n\ngyp http GET https:\u002F\u002Fnodejs.org\u002Fdownload\u002Frelease\u002Fv22.16.0\u002Fnode-v22.16.0-headers.tar.gz\ngyp http 200 https:\u002F\u002Fnodejs.org\u002Fdownload\u002Frelease\u002Fv22.16.0\u002Fnode-v22.16.0-headers.tar.gz\ngyp http GET https:\u002F\u002Fnodejs.org\u002Fdownload\u002Frelease\u002Fv22.16.0\u002FSHASUMS256.txt\ngyp http GET https:\u002F\u002Fnodejs.org\u002Fdownload\u002Frelease\u002Fv22.16.0\u002Fwin-x64\u002Fnode.lib\ngyp http 200 https:\u002F\u002Fnodejs.org\u002Fdownload\u002Frelease\u002Fv22.16.0\u002FSHASUMS256.txt\ngyp http 200 https:\u002F\u002Fnodejs.org\u002Fdownload\u002Frelease\u002Fv22.16.0\u002Fwin-x64\u002Fnode.lib\ngyp info ok\nPS F:\\glfwExamle\\glfwExample>\n","shell",[21,2221,2222,2255,2275,2280,2293,2297,2325,2337,2359,2363,2376,2387,2398,2409,2419,2429,2437],{"__ignoreMap":65},[69,2223,2224,2227,2230,2233,2236,2238,2241,2244,2247,2249,2252],{"class":71,"line":72},[69,2225,2226],{"class":1179},"PS",[69,2228,2229],{"class":1111}," F:",[69,2231,2232],{"class":1172},"\\g",[69,2234,2235],{"class":1111},"lfwExamle",[69,2237,2232],{"class":1172},[69,2239,2240],{"class":1111},"lfwExampl",[69,2242,2243],{"class":1104},"e",[69,2245,2246],{"class":1100},">",[69,2248,1320],{"class":1111},[69,2250,2251],{"class":1111}," node-gyp",[69,2253,2254],{"class":1111}," install\n",[69,2256,2257,2260,2263,2266,2269,2272],{"class":71,"line":78},[69,2258,2259],{"class":1179},"Need",[69,2261,2262],{"class":1111}," to",[69,2264,2265],{"class":1111}," install",[69,2267,2268],{"class":1111}," the",[69,2270,2271],{"class":1111}," following",[69,2273,2274],{"class":1111}," packages:\n",[69,2276,2277],{"class":71,"line":85},[69,2278,2279],{"class":1179},"node-gyp@12.3.0\n",[69,2281,2282,2285,2287,2290],{"class":71,"line":91},[69,2283,2284],{"class":1179},"Ok",[69,2286,2262],{"class":1111},[69,2288,2289],{"class":1111}," proceed?",[69,2291,2292],{"class":1104}," (y) y\n",[69,2294,2295],{"class":71,"line":97},[69,2296,82],{"emptyLinePlaceholder":81},[69,2298,2299,2302,2305,2308,2311,2314,2316,2319,2322],{"class":71,"line":103},[69,2300,2301],{"class":1179},"gyp",[69,2303,2304],{"class":1111}," info",[69,2306,2307],{"class":1111}," it",[69,2309,2310],{"class":1111}," worked",[69,2312,2313],{"class":1111}," if",[69,2315,2307],{"class":1111},[69,2317,2318],{"class":1111}," ends",[69,2320,2321],{"class":1111}," with",[69,2323,2324],{"class":1111}," ok\n",[69,2326,2327,2329,2331,2334],{"class":71,"line":109},[69,2328,2301],{"class":1179},[69,2330,2304],{"class":1111},[69,2332,2333],{"class":1111}," using",[69,2335,2336],{"class":1111}," node-gyp@12.3.0\n",[69,2338,2339,2341,2343,2345,2348,2351,2354,2356],{"class":71,"line":115},[69,2340,2301],{"class":1179},[69,2342,2304],{"class":1111},[69,2344,2333],{"class":1111},[69,2346,2347],{"class":1111}," node@22.16.0",[69,2349,2350],{"class":1100}," |",[69,2352,2353],{"class":1179}," win32",[69,2355,2350],{"class":1100},[69,2357,2358],{"class":1179}," x64\n",[69,2360,2361],{"class":71,"line":121},[69,2362,82],{"emptyLinePlaceholder":81},[69,2364,2365,2367,2370,2373],{"class":71,"line":126},[69,2366,2301],{"class":1179},[69,2368,2369],{"class":1111}," http",[69,2371,2372],{"class":1111}," GET",[69,2374,2375],{"class":1111}," https:\u002F\u002Fnodejs.org\u002Fdownload\u002Frelease\u002Fv22.16.0\u002Fnode-v22.16.0-headers.tar.gz\n",[69,2377,2378,2380,2382,2385],{"class":71,"line":132},[69,2379,2301],{"class":1179},[69,2381,2369],{"class":1111},[69,2383,2384],{"class":1172}," 200",[69,2386,2375],{"class":1111},[69,2388,2389,2391,2393,2395],{"class":71,"line":137},[69,2390,2301],{"class":1179},[69,2392,2369],{"class":1111},[69,2394,2372],{"class":1111},[69,2396,2397],{"class":1111}," https:\u002F\u002Fnodejs.org\u002Fdownload\u002Frelease\u002Fv22.16.0\u002FSHASUMS256.txt\n",[69,2399,2400,2402,2404,2406],{"class":71,"line":143},[69,2401,2301],{"class":1179},[69,2403,2369],{"class":1111},[69,2405,2372],{"class":1111},[69,2407,2408],{"class":1111}," https:\u002F\u002Fnodejs.org\u002Fdownload\u002Frelease\u002Fv22.16.0\u002Fwin-x64\u002Fnode.lib\n",[69,2410,2411,2413,2415,2417],{"class":71,"line":149},[69,2412,2301],{"class":1179},[69,2414,2369],{"class":1111},[69,2416,2384],{"class":1172},[69,2418,2397],{"class":1111},[69,2420,2421,2423,2425,2427],{"class":71,"line":155},[69,2422,2301],{"class":1179},[69,2424,2369],{"class":1111},[69,2426,2384],{"class":1172},[69,2428,2408],{"class":1111},[69,2430,2431,2433,2435],{"class":71,"line":161},[69,2432,2301],{"class":1179},[69,2434,2304],{"class":1111},[69,2436,2324],{"class":1111},[69,2438,2439,2441,2443,2445,2447,2449,2451,2453],{"class":71,"line":166},[69,2440,2226],{"class":1179},[69,2442,2229],{"class":1111},[69,2444,2232],{"class":1172},[69,2446,2235],{"class":1111},[69,2448,2232],{"class":1172},[69,2450,2240],{"class":1111},[69,2452,2243],{"class":1104},[69,2454,2455],{"class":1100},">\n",[14,2457,2458],{},"我们编写一个cmake文件来制定对应的文件和制定编译规则和包含等信息：",[60,2460,2464],{"className":2461,"code":2462,"language":2463,"meta":65,"style":65},"language-cmake shiki shiki-themes github-light github-dark","cmake_minimum_required(VERSION 3.15)\nif(NOT WIN32)\n  message(FATAL_ERROR \"capture_addon: Windows only.\")\nendif()\nproject(capture_addon LANGUAGES CXX)\nset(NODE_ADDON_API_DIR \"${CMAKE_CURRENT_SOURCE_DIR}\u002Fnode_modules\u002Fnode-addon-api\")\nif(NOT EXISTS \"${NODE_ADDON_API_DIR}\u002Fnapi.h\")\n    set(NODE_ADDON_API_DIR \"${CMAKE_CURRENT_SOURCE_DIR}\u002F..\u002Fnode_modules\u002Fnode-addon-api\")\nendif()\n\n# 必须与 ${CMAKE_JS_SRC}（win_delay_load_hook.cc）一起链接，否则 \u002FDELAYLOAD:NODE.EXE 在 Electron 内无法正确解析，require(.node) 会崩溃。\n# 参考 cmake-js README：add_library(... ${CMAKE_JS_SRC})\nadd_library(${PROJECT_NAME} SHARED\n    \"addon.cpp\"\n    \"main_n.cpp\"\n    \"WasapiLoopback.cpp\"\n    \"audio_output_devices.cpp\"\n)\n\nif(CMAKE_JS_SRC)\n  target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_JS_SRC})\nendif()\n\n  \n\ntarget_include_directories(${PROJECT_NAME} PRIVATE\n  \"${CMAKE_CURRENT_SOURCE_DIR}\"\n  \"${NODE_ADDON_API_DIR}\"    # 提供 napi.h\n  \"${CMAKE_JS_INC}\"         # 提供 node_api.h (由 cmake-js 自动注入)\n)\n\ntarget_compile_definitions(${PROJECT_NAME} PRIVATE\n  WGC_NATIVE_NODE_ADDON\n  NAPI_CPP_EXCEPTIONS\n  FMT_HEADER_ONLY\n  WIN32_LEAN_AND_MEAN\n)\n\n  \nset_target_properties(${PROJECT_NAME} PROPERTIES\n    CXX_STANDARD 20\n    CXX_STANDARD_REQUIRED ON\n    PREFIX \"\"\n    SUFFIX \".node\"\n)\n\n  \n\n# 必须与 Node\u002FElectron 一致使用动态 CRT（\u002FMD）。cmake-js 默认 MultiThreaded(\u002FMT) 时，require(.node) 常在 Electron 内立刻崩溃。\nset_property(TARGET ${PROJECT_NAME} PROPERTY MSVC_RUNTIME_LIBRARY\n    \"$\u003C$\u003CCONFIG:Debug>:MultiThreadedDebugDLL>$\u003C$\u003CCONFIG:Release>:MultiThreadedDLL>$\u003C$\u003CCONFIG:RelWithDebInfo>:MultiThreadedDLL>$\u003C$\u003CCONFIG:MinSizeRel>:MultiThreadedDLL>\")\nif(MSVC)\n  # \u002Fawait 用于支持 C++\u002FWinRT 的异步操作\n  target_compile_options(${PROJECT_NAME} PRIVATE \u002Fawait \u002FEHsc \u002Fpermissive- \u002Futf-8)\nendif()\n\ntarget_link_libraries(${PROJECT_NAME} PRIVATE\n    d3d11.lib\n    windowsapp.lib\n    delayimp.lib              # 必须链接这个库来解决 __delayLoadHelper2 错误\n    mfplat.lib\n    mfuuid.lib\n    mfreadwrite.lib\n    avrt.lib\n    Mmdevapi.lib\n    \"${CMAKE_JS_LIB}\"\n)\n","cmake",[21,2465,2466,2471,2476,2481,2486,2491,2496,2501,2506,2510,2514,2519,2524,2529,2534,2539,2544,2549,2553,2557,2562,2567,2571,2575,2579,2583,2588,2593,2598,2603,2607,2611,2616,2621,2626,2631,2636,2640,2644,2648,2653,2658,2663,2668,2673,2677,2681,2685,2689,2694,2699,2704,2709,2714,2719,2723,2727,2732,2737,2742,2747,2752,2757,2762,2767,2772,2777],{"__ignoreMap":65},[69,2467,2468],{"class":71,"line":72},[69,2469,2470],{},"cmake_minimum_required(VERSION 3.15)\n",[69,2472,2473],{"class":71,"line":78},[69,2474,2475],{},"if(NOT WIN32)\n",[69,2477,2478],{"class":71,"line":85},[69,2479,2480],{},"  message(FATAL_ERROR \"capture_addon: Windows only.\")\n",[69,2482,2483],{"class":71,"line":91},[69,2484,2485],{},"endif()\n",[69,2487,2488],{"class":71,"line":97},[69,2489,2490],{},"project(capture_addon LANGUAGES CXX)\n",[69,2492,2493],{"class":71,"line":103},[69,2494,2495],{},"set(NODE_ADDON_API_DIR \"${CMAKE_CURRENT_SOURCE_DIR}\u002Fnode_modules\u002Fnode-addon-api\")\n",[69,2497,2498],{"class":71,"line":109},[69,2499,2500],{},"if(NOT EXISTS \"${NODE_ADDON_API_DIR}\u002Fnapi.h\")\n",[69,2502,2503],{"class":71,"line":115},[69,2504,2505],{},"    set(NODE_ADDON_API_DIR \"${CMAKE_CURRENT_SOURCE_DIR}\u002F..\u002Fnode_modules\u002Fnode-addon-api\")\n",[69,2507,2508],{"class":71,"line":121},[69,2509,2485],{},[69,2511,2512],{"class":71,"line":126},[69,2513,82],{"emptyLinePlaceholder":81},[69,2515,2516],{"class":71,"line":132},[69,2517,2518],{},"# 必须与 ${CMAKE_JS_SRC}（win_delay_load_hook.cc）一起链接，否则 \u002FDELAYLOAD:NODE.EXE 在 Electron 内无法正确解析，require(.node) 会崩溃。\n",[69,2520,2521],{"class":71,"line":137},[69,2522,2523],{},"# 参考 cmake-js README：add_library(... ${CMAKE_JS_SRC})\n",[69,2525,2526],{"class":71,"line":143},[69,2527,2528],{},"add_library(${PROJECT_NAME} SHARED\n",[69,2530,2531],{"class":71,"line":149},[69,2532,2533],{},"    \"addon.cpp\"\n",[69,2535,2536],{"class":71,"line":155},[69,2537,2538],{},"    \"main_n.cpp\"\n",[69,2540,2541],{"class":71,"line":161},[69,2542,2543],{},"    \"WasapiLoopback.cpp\"\n",[69,2545,2546],{"class":71,"line":166},[69,2547,2548],{},"    \"audio_output_devices.cpp\"\n",[69,2550,2551],{"class":71,"line":172},[69,2552,1243],{},[69,2554,2555],{"class":71,"line":178},[69,2556,82],{"emptyLinePlaceholder":81},[69,2558,2559],{"class":71,"line":184},[69,2560,2561],{},"if(CMAKE_JS_SRC)\n",[69,2563,2564],{"class":71,"line":189},[69,2565,2566],{},"  target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_JS_SRC})\n",[69,2568,2569],{"class":71,"line":195},[69,2570,2485],{},[69,2572,2573],{"class":71,"line":201},[69,2574,82],{"emptyLinePlaceholder":81},[69,2576,2577],{"class":71,"line":207},[69,2578,1500],{},[69,2580,2581],{"class":71,"line":213},[69,2582,82],{"emptyLinePlaceholder":81},[69,2584,2585],{"class":71,"line":218},[69,2586,2587],{},"target_include_directories(${PROJECT_NAME} PRIVATE\n",[69,2589,2590],{"class":71,"line":224},[69,2591,2592],{},"  \"${CMAKE_CURRENT_SOURCE_DIR}\"\n",[69,2594,2595],{"class":71,"line":230},[69,2596,2597],{},"  \"${NODE_ADDON_API_DIR}\"    # 提供 napi.h\n",[69,2599,2600],{"class":71,"line":236},[69,2601,2602],{},"  \"${CMAKE_JS_INC}\"         # 提供 node_api.h (由 cmake-js 自动注入)\n",[69,2604,2605],{"class":71,"line":241},[69,2606,1243],{},[69,2608,2609],{"class":71,"line":247},[69,2610,82],{"emptyLinePlaceholder":81},[69,2612,2613],{"class":71,"line":253},[69,2614,2615],{},"target_compile_definitions(${PROJECT_NAME} PRIVATE\n",[69,2617,2618],{"class":71,"line":259},[69,2619,2620],{},"  WGC_NATIVE_NODE_ADDON\n",[69,2622,2623],{"class":71,"line":264},[69,2624,2625],{},"  NAPI_CPP_EXCEPTIONS\n",[69,2627,2628],{"class":71,"line":270},[69,2629,2630],{},"  FMT_HEADER_ONLY\n",[69,2632,2633],{"class":71,"line":276},[69,2634,2635],{},"  WIN32_LEAN_AND_MEAN\n",[69,2637,2638],{"class":71,"line":282},[69,2639,1243],{},[69,2641,2642],{"class":71,"line":287},[69,2643,82],{"emptyLinePlaceholder":81},[69,2645,2646],{"class":71,"line":293},[69,2647,1500],{},[69,2649,2650],{"class":71,"line":299},[69,2651,2652],{},"set_target_properties(${PROJECT_NAME} PROPERTIES\n",[69,2654,2655],{"class":71,"line":304},[69,2656,2657],{},"    CXX_STANDARD 20\n",[69,2659,2660],{"class":71,"line":310},[69,2661,2662],{},"    CXX_STANDARD_REQUIRED ON\n",[69,2664,2665],{"class":71,"line":316},[69,2666,2667],{},"    PREFIX \"\"\n",[69,2669,2670],{"class":71,"line":321},[69,2671,2672],{},"    SUFFIX \".node\"\n",[69,2674,2675],{"class":71,"line":327},[69,2676,1243],{},[69,2678,2679],{"class":71,"line":333},[69,2680,82],{"emptyLinePlaceholder":81},[69,2682,2683],{"class":71,"line":339},[69,2684,1500],{},[69,2686,2687],{"class":71,"line":345},[69,2688,82],{"emptyLinePlaceholder":81},[69,2690,2691],{"class":71,"line":801},[69,2692,2693],{},"# 必须与 Node\u002FElectron 一致使用动态 CRT（\u002FMD）。cmake-js 默认 MultiThreaded(\u002FMT) 时，require(.node) 常在 Electron 内立刻崩溃。\n",[69,2695,2696],{"class":71,"line":807},[69,2697,2698],{},"set_property(TARGET ${PROJECT_NAME} PROPERTY MSVC_RUNTIME_LIBRARY\n",[69,2700,2701],{"class":71,"line":813},[69,2702,2703],{},"    \"$\u003C$\u003CCONFIG:Debug>:MultiThreadedDebugDLL>$\u003C$\u003CCONFIG:Release>:MultiThreadedDLL>$\u003C$\u003CCONFIG:RelWithDebInfo>:MultiThreadedDLL>$\u003C$\u003CCONFIG:MinSizeRel>:MultiThreadedDLL>\")\n",[69,2705,2706],{"class":71,"line":819},[69,2707,2708],{},"if(MSVC)\n",[69,2710,2711],{"class":71,"line":825},[69,2712,2713],{},"  # \u002Fawait 用于支持 C++\u002FWinRT 的异步操作\n",[69,2715,2716],{"class":71,"line":830},[69,2717,2718],{},"  target_compile_options(${PROJECT_NAME} PRIVATE \u002Fawait \u002FEHsc \u002Fpermissive- \u002Futf-8)\n",[69,2720,2721],{"class":71,"line":835},[69,2722,2485],{},[69,2724,2725],{"class":71,"line":841},[69,2726,82],{"emptyLinePlaceholder":81},[69,2728,2729],{"class":71,"line":846},[69,2730,2731],{},"target_link_libraries(${PROJECT_NAME} PRIVATE\n",[69,2733,2734],{"class":71,"line":852},[69,2735,2736],{},"    d3d11.lib\n",[69,2738,2739],{"class":71,"line":858},[69,2740,2741],{},"    windowsapp.lib\n",[69,2743,2744],{"class":71,"line":863},[69,2745,2746],{},"    delayimp.lib              # 必须链接这个库来解决 __delayLoadHelper2 错误\n",[69,2748,2749],{"class":71,"line":869},[69,2750,2751],{},"    mfplat.lib\n",[69,2753,2754],{"class":71,"line":874},[69,2755,2756],{},"    mfuuid.lib\n",[69,2758,2759],{"class":71,"line":880},[69,2760,2761],{},"    mfreadwrite.lib\n",[69,2763,2764],{"class":71,"line":886},[69,2765,2766],{},"    avrt.lib\n",[69,2768,2769],{"class":71,"line":892},[69,2770,2771],{},"    Mmdevapi.lib\n",[69,2773,2774],{"class":71,"line":898},[69,2775,2776],{},"    \"${CMAKE_JS_LIB}\"\n",[69,2778,2779],{"class":71,"line":904},[69,2780,1243],{},[14,2782,2783,2784,2787,2788,2791],{},"这里我们链接了d3d11.lib、windowsapp.lib（因为windows runtime 、WGC、以及CPP\u002Fwinrt生成的api最终都会落在这类导入上，没有它链接阶段就会缺符号）、delayimp.lib（这是延迟加载的组件，提供了__delayLoadHelper2等，配合链接器\u002FDELAYLOAD: ... cmake-js通常会带上对NODE的delay-load，如果没哟它就会出现缺delay load辅助函数一类的链接错误）、",[21,2785,2786],{},"\"${CMAKE_JS_LIB}\"","（cmake-js → Electron 自带的 ",[21,2789,2790],{},"node.lib","，native addon必须和当前的Eletron的ABI对应的node.lib链接，否则就会链接错版本或者加载失败）",[14,2793,2794,2795,2798,2799],{},"运行的时候electron会把node（同一套v8\u002Flibuv\u002FN-API）嵌入electron.exe中，一般不会单独给个node.lib的文件。",[26,2796,2797],{},"编译原生插件的时候，必须有一份和当前electron版本ABI一致的导入库才能在链接阶段","解析napi_create_* 、 node_* 等符号。这份东西就叫做node.lib，属于windows下的导入库，来源一般就是cmake-js \u002F node-gyp \u002F @electron\u002Frebuild 按照 runtime-verson去下载和缓存到本机：例如 ",[21,2800,2801],{},"~\\.cmake-js\\electron-x64\\v39.x.x\\x64\\node.lib",[14,2803,2804,2805,2808,2809,2812],{},"为什么需要delayimp.lib呢，在MSVC上，如果链接选项里使用了延迟加载（cmake-js的常见写法：\u002FDELAYLOAD:NODE.EXE） ，意思就是一开始不把node里成千上万个符号都从磁盘解析完，而是用到哪个符号的时候再加载。\n实现延迟加载需要运行时辅助函数（典型是 ",[21,2806,2807],{},"__delayLoadHelper2"," 等），它们写在 ",[21,2810,2811],{},"delayimp.lib","（Delay Importer）里。",[14,2814,2815,2816,2818,2819,2822,2823,2825,2826,2828],{},"因此：只要你的 ",[21,2817,2204],{}," 是用「带 ",[21,2820,2821],{},"\u002FDELAYLOAD","」的方式链 ",[21,2824,2790],{}," 的，就必须再链 ",[21,2827,2811],{},"，否则会报 「无法解析的外部符号 __delayLoadHelper2」 一类链接错误。",[14,2830,2831,2832,2835,2836,2839,2840,2843,2844,2846,2847,2850],{},"再结合 ",[21,2833,2834],{},"win_delay_load_hook.cc","：在运行期让delay-load在electron环境下指向正确模块，而不是去磁盘上的node.exe。\n它在延迟加载流程里「拦截」对 ",[21,2837,2838],{},"NODE.EXE"," 的解析，让符号实际从 当前 Electron 进程 里解析——这是 ",[21,2841,2842],{},"delayimp"," + ",[21,2845,2821],{}," + hook 这一套配合起来，Electron 里 ",[21,2848,2849],{},"require(.node)"," 才稳定。",[2852,2853,2854],"blockquote",{},[14,2855,2856,2859,2860,2863,2864,2867,2868,2871],{},[69,2857,2858],{},"!tip"," 符号\n可能有人第一次看到，有些不熟悉这个概念，符号symbol就是编译\u002F链接阶段用来标识某段代码和数据的名字，链接器靠他在多个.obj .lib .dll之间对错号。一般分为函数符号比如说napi_create_string_utf8 以及 ",[26,2861,2862],{},"数据\u002F全局符号","：例如",[26,2865,2866],{},"某些全局变量","。\n编译我们的addon.cpp来",[26,2869,2870],{},"生产obj的时候里面会有未解析的符号引用","，就比如我们调用的napi_get_undefine，此时还不知道地址，node.lib是导入库（里面主要都是桩信息，告诉链接器这些符号在实际运行的时候由哪个DLL\u002FEXE提供）。链接成capture_addon.node之后，PE会记下这些要入到运行时再去找某个模块里寻找。延迟加载的思想和这个差不多，意思也是启动\u002F刚加载.node的时候可以不先把每个符号都绑定完，用到的时候回去解析，和delayimp.lib、hook配合",[14,2873,2874,2875,2880,2881,2884,2885,39,2888,2891,2892,2897,2898,2901,2902,2904,2905,419,2908,490,2911,2914,2915,2917],{},"最后，为什么需要一致使用动态 CRT ",[26,2876,2877],{},[21,2878,2879],{},"\u002FMD","（对应 MSVC 选项 ",[26,2882,2883],{},"MultiThreadedDLL","：CRT 在 ",[21,2886,2887],{},"vcruntime*.dll",[21,2889,2890],{},"ucrtbase.dll"," 等系统 DLL 里，进程内各模块共用一套堆与实现）。若误用 ",[26,2893,2894],{},[21,2895,2896],{},"\u002FMT","（",[26,2899,2900],{},"MultiThreaded","：CRT 静态链进你的 ",[21,2903,2204],{},"，等于模块私有一套 CRT），跨 DLL 边界 ",[21,2906,2907],{},"malloc",[21,2909,2910],{},"free",[21,2912,2913],{},"FILE*","、locale 等很容易变成未定义行为，Electron 里常见表现就是加载 ",[21,2916,2204],{}," 立刻崩或随机崩。",[2852,2919,2920],{},[14,2921,2922,2925,2926,2928,2929,2932],{},[69,2923,2924],{},"!hint"," Electron \u002F 官方 Node Windows 发行版是按 ",[21,2927,2879],{}," 编的：主程序、",[21,2930,2931],{},"node.dll","、大量系统组件都在 同一套 UCRT + VCRuntime 上跑。",[14,2934,2935],{},"因为.node本质上是一个dll，加载进了进程之后，堆内存不能混用，如果一侧是A套CRT，一侧B套CRT，是未定义行为，常表现为访问冲突，N-API\u002FV8 会在边界上分配、传递缓冲区，很容易暴毙。",[2852,2937,2938],{},[14,2939,2940,2943],{},[69,2941,2942],{},"!tldr","  CRT: C Runtime Library，即 C 语言运行时库。提供了malloc、free、new、delete 、printf、fopen、FILE* 等。 在Windows + MSVC环境下可以静态链进exe\u002Fdll （MT）。也可以跟随系统里的CRT DLL公用（\u002FMD）。     locale是区域\u002F本地化设置，包括字符分类，数字格式和日期格式和排序。",[14,2945,2946,2947,2950,2951,2954,2955,2958],{},"然后执行 ",[21,2948,2949],{},"npx cmake-js compile"," 来编译（或用上面的 ",[21,2952,2953],{},"pnpm run build:addon"," 一条龙），需要时用 ",[26,2956,2957],{},"x64 Native Tools"," 环境。",[60,2960,2962],{"className":2217,"code":2961,"language":2219,"meta":65,"style":65},"F:\\glfwExamle\\glfwExample\\glfwExample>npx cmake-js compile -G \"Ninja\"\nINFO TOOL Using Ninja generator, as specified from commandline.\nINFO CMD BUILD\nINFO RUN [\n  'cmake',\n  '--build',\n  'F:\\\\glfwExamle\\\\glfwExample\\\\glfwExample\\\\build',\n  '--config',\n  'Release'\n]\nninja: error: loading 'build.ninja': The system cannot find the file specified.\n\nINFO REP Build has been failed, trying to do a full rebuild.\nINFO CMD CLEAN\nINFO RUN [\n  'cmake',\n  '-E',\n  'remove_directory',\n  'F:\\\\glfwExamle\\\\glfwExample\\\\glfwExample\\\\build'\n]\nINFO CMD CONFIGURE\nINFO RUN [\n  'cmake',\n  'F:\\\\glfwExamle\\\\glfwExample\\\\glfwExample',\n  '--no-warn-unused-cli',\n  '-G',\n  'Ninja',\n  '-DCMAKE_JS_VERSION=8.0.0',\n  '-DCMAKE_BUILD_TYPE=Release',\n  '-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=F:\\\\glfwExamle\\\\glfwExample\\\\glfwExample\\\\build',\n  '-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$\u003C$\u003CCONFIG:Debug>:Debug>',\n  '-DCMAKE_JS_INC=C:\\\\Users\\\\20742\\\\.cmake-js\\\\node-x64\\\\v22.16.0\\\\include\\\\node',\n  '-DCMAKE_JS_SRC=F:\u002FglfwExamle\u002FglfwExample\u002FglfwExample\u002Fnode_modules\u002F.pnpm\u002Fcmake-js@8.0.0\u002Fnode_modules\u002Fcmake-js\u002Flib\u002Fcpp\u002Fwin_delay_load_hook.cc',\n  '-DNODE_RUNTIME=node',\n  '-DNODE_RUNTIMEVERSION=22.16.0',\n  '-DNODE_ARCH=x64',\n  '-DCMAKE_JS_LIB=C:\\\\Users\\\\20742\\\\.cmake-js\\\\node-x64\\\\v22.16.0\\\\win-x64\\\\node.lib',\n  '-DCMAKE_SHARED_LINKER_FLAGS=\u002FDELAYLOAD:NODE.EXE'\n]\nNot searching for unused variables given on the command line.\n-- The CXX compiler identification is MSVC 19.44.35220.0\n-- Detecting CXX compiler ABI info\n-- Detecting CXX compiler ABI info - done\n-- Check for working CXX compiler: C:\u002FProgram Files (x86)\u002FMicrosoft Visual Studio\u002F2022\u002FBuildTools\u002FVC\u002FTools\u002FMSVC\u002F14.44.35207\u002Fbin\u002FHostx64\u002Fx64\u002Fcl.exe - skipped\n-- Detecting CXX compile features\n-- Detecting CXX compile features - done\n-- Configuring done (25.3s)\n-- Generating done (2.7s)\n-- Build files have been written to: F:\u002FglfwExamle\u002FglfwExample\u002FglfwExample\u002Fbuild\nINFO CMD BUILD\nINFO RUN [\n  'cmake',\n  '--build',\n  'F:\\\\glfwExamle\\\\glfwExample\\\\glfwExample\\\\build',\n  '--config',\n  'Release'\n]\n[3\u002F3] Linking CXX shared library capture_addon.node\n",[21,2963,2964,2986,3015,3025,3035,3043,3050,3057,3064,3069,3074,3108,3112,3148,3157,3165,3171,3178,3185,3190,3194,3203,3211,3217,3224,3231,3238,3245,3252,3259,3266,3273,3280,3287,3294,3301,3308,3315,3320,3324,3355,3380,3397,3417,3443,3456,3473,3486,3498,3521,3529,3537,3543,3549,3555,3561,3565,3569],{"__ignoreMap":65},[69,2965,2966,2969,2971,2974,2977,2980,2983],{"class":71,"line":72},[69,2967,2968],{"class":1179},"F:\\glfwExamle\\glfwExample\\glfwExample",[69,2970,2246],{"class":1104},[69,2972,2973],{"class":1111},"npx",[69,2975,2976],{"class":1111}," cmake-js",[69,2978,2979],{"class":1111}," compile",[69,2981,2982],{"class":1172}," -G",[69,2984,2985],{"class":1111}," \"Ninja\"\n",[69,2987,2988,2991,2994,2997,3000,3003,3006,3009,3012],{"class":71,"line":78},[69,2989,2990],{"class":1179},"INFO",[69,2992,2993],{"class":1111}," TOOL",[69,2995,2996],{"class":1111}," Using",[69,2998,2999],{"class":1111}," Ninja",[69,3001,3002],{"class":1111}," generator,",[69,3004,3005],{"class":1111}," as",[69,3007,3008],{"class":1111}," specified",[69,3010,3011],{"class":1111}," from",[69,3013,3014],{"class":1111}," commandline.\n",[69,3016,3017,3019,3022],{"class":71,"line":85},[69,3018,2990],{"class":1179},[69,3020,3021],{"class":1111}," CMD",[69,3023,3024],{"class":1111}," BUILD\n",[69,3026,3027,3029,3032],{"class":71,"line":91},[69,3028,2990],{"class":1179},[69,3030,3031],{"class":1111}," RUN",[69,3033,3034],{"class":1104}," [\n",[69,3036,3037,3040],{"class":71,"line":97},[69,3038,3039],{"class":1179},"  'cmake'",[69,3041,3042],{"class":1179},",\n",[69,3044,3045,3048],{"class":71,"line":103},[69,3046,3047],{"class":1179},"  '--build'",[69,3049,3042],{"class":1179},[69,3051,3052,3055],{"class":71,"line":109},[69,3053,3054],{"class":1179},"  'F:\\\\glfwExamle\\\\glfwExample\\\\glfwExample\\\\build'",[69,3056,3042],{"class":1179},[69,3058,3059,3062],{"class":71,"line":115},[69,3060,3061],{"class":1179},"  '--config'",[69,3063,3042],{"class":1179},[69,3065,3066],{"class":71,"line":121},[69,3067,3068],{"class":1179},"  'Release'\n",[69,3070,3071],{"class":71,"line":126},[69,3072,3073],{"class":1104},"]\n",[69,3075,3076,3079,3082,3085,3088,3091,3094,3097,3100,3102,3105],{"class":71,"line":132},[69,3077,3078],{"class":1179},"ninja:",[69,3080,3081],{"class":1111}," error:",[69,3083,3084],{"class":1111}," loading",[69,3086,3087],{"class":1111}," 'build.ninja':",[69,3089,3090],{"class":1111}," The",[69,3092,3093],{"class":1111}," system",[69,3095,3096],{"class":1111}," cannot",[69,3098,3099],{"class":1111}," find",[69,3101,2268],{"class":1111},[69,3103,3104],{"class":1111}," file",[69,3106,3107],{"class":1111}," specified.\n",[69,3109,3110],{"class":71,"line":137},[69,3111,82],{"emptyLinePlaceholder":81},[69,3113,3114,3116,3119,3122,3125,3128,3131,3134,3136,3139,3142,3145],{"class":71,"line":143},[69,3115,2990],{"class":1179},[69,3117,3118],{"class":1111}," REP",[69,3120,3121],{"class":1111}," Build",[69,3123,3124],{"class":1111}," has",[69,3126,3127],{"class":1111}," been",[69,3129,3130],{"class":1111}," failed,",[69,3132,3133],{"class":1111}," trying",[69,3135,2262],{"class":1111},[69,3137,3138],{"class":1111}," do",[69,3140,3141],{"class":1111}," a",[69,3143,3144],{"class":1111}," full",[69,3146,3147],{"class":1111}," rebuild.\n",[69,3149,3150,3152,3154],{"class":71,"line":149},[69,3151,2990],{"class":1179},[69,3153,3021],{"class":1111},[69,3155,3156],{"class":1111}," CLEAN\n",[69,3158,3159,3161,3163],{"class":71,"line":155},[69,3160,2990],{"class":1179},[69,3162,3031],{"class":1111},[69,3164,3034],{"class":1104},[69,3166,3167,3169],{"class":71,"line":161},[69,3168,3039],{"class":1179},[69,3170,3042],{"class":1179},[69,3172,3173,3176],{"class":71,"line":166},[69,3174,3175],{"class":1179},"  '-E'",[69,3177,3042],{"class":1179},[69,3179,3180,3183],{"class":71,"line":172},[69,3181,3182],{"class":1179},"  'remove_directory'",[69,3184,3042],{"class":1179},[69,3186,3187],{"class":71,"line":178},[69,3188,3189],{"class":1179},"  'F:\\\\glfwExamle\\\\glfwExample\\\\glfwExample\\\\build'\n",[69,3191,3192],{"class":71,"line":184},[69,3193,3073],{"class":1104},[69,3195,3196,3198,3200],{"class":71,"line":189},[69,3197,2990],{"class":1179},[69,3199,3021],{"class":1111},[69,3201,3202],{"class":1111}," CONFIGURE\n",[69,3204,3205,3207,3209],{"class":71,"line":195},[69,3206,2990],{"class":1179},[69,3208,3031],{"class":1111},[69,3210,3034],{"class":1104},[69,3212,3213,3215],{"class":71,"line":201},[69,3214,3039],{"class":1179},[69,3216,3042],{"class":1179},[69,3218,3219,3222],{"class":71,"line":207},[69,3220,3221],{"class":1179},"  'F:\\\\glfwExamle\\\\glfwExample\\\\glfwExample'",[69,3223,3042],{"class":1179},[69,3225,3226,3229],{"class":71,"line":213},[69,3227,3228],{"class":1179},"  '--no-warn-unused-cli'",[69,3230,3042],{"class":1179},[69,3232,3233,3236],{"class":71,"line":218},[69,3234,3235],{"class":1179},"  '-G'",[69,3237,3042],{"class":1179},[69,3239,3240,3243],{"class":71,"line":224},[69,3241,3242],{"class":1179},"  'Ninja'",[69,3244,3042],{"class":1179},[69,3246,3247,3250],{"class":71,"line":230},[69,3248,3249],{"class":1179},"  '-DCMAKE_JS_VERSION=8.0.0'",[69,3251,3042],{"class":1179},[69,3253,3254,3257],{"class":71,"line":236},[69,3255,3256],{"class":1179},"  '-DCMAKE_BUILD_TYPE=Release'",[69,3258,3042],{"class":1179},[69,3260,3261,3264],{"class":71,"line":241},[69,3262,3263],{"class":1179},"  '-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=F:\\\\glfwExamle\\\\glfwExample\\\\glfwExample\\\\build'",[69,3265,3042],{"class":1179},[69,3267,3268,3271],{"class":71,"line":247},[69,3269,3270],{"class":1179},"  '-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$\u003C$\u003CCONFIG:Debug>:Debug>'",[69,3272,3042],{"class":1179},[69,3274,3275,3278],{"class":71,"line":253},[69,3276,3277],{"class":1179},"  '-DCMAKE_JS_INC=C:\\\\Users\\\\20742\\\\.cmake-js\\\\node-x64\\\\v22.16.0\\\\include\\\\node'",[69,3279,3042],{"class":1179},[69,3281,3282,3285],{"class":71,"line":259},[69,3283,3284],{"class":1179},"  '-DCMAKE_JS_SRC=F:\u002FglfwExamle\u002FglfwExample\u002FglfwExample\u002Fnode_modules\u002F.pnpm\u002Fcmake-js@8.0.0\u002Fnode_modules\u002Fcmake-js\u002Flib\u002Fcpp\u002Fwin_delay_load_hook.cc'",[69,3286,3042],{"class":1179},[69,3288,3289,3292],{"class":71,"line":264},[69,3290,3291],{"class":1179},"  '-DNODE_RUNTIME=node'",[69,3293,3042],{"class":1179},[69,3295,3296,3299],{"class":71,"line":270},[69,3297,3298],{"class":1179},"  '-DNODE_RUNTIMEVERSION=22.16.0'",[69,3300,3042],{"class":1179},[69,3302,3303,3306],{"class":71,"line":276},[69,3304,3305],{"class":1179},"  '-DNODE_ARCH=x64'",[69,3307,3042],{"class":1179},[69,3309,3310,3313],{"class":71,"line":282},[69,3311,3312],{"class":1179},"  '-DCMAKE_JS_LIB=C:\\\\Users\\\\20742\\\\.cmake-js\\\\node-x64\\\\v22.16.0\\\\win-x64\\\\node.lib'",[69,3314,3042],{"class":1179},[69,3316,3317],{"class":71,"line":287},[69,3318,3319],{"class":1179},"  '-DCMAKE_SHARED_LINKER_FLAGS=\u002FDELAYLOAD:NODE.EXE'\n",[69,3321,3322],{"class":71,"line":293},[69,3323,3073],{"class":1104},[69,3325,3326,3329,3332,3335,3338,3341,3344,3347,3349,3352],{"class":71,"line":299},[69,3327,3328],{"class":1179},"Not",[69,3330,3331],{"class":1111}," searching",[69,3333,3334],{"class":1111}," for",[69,3336,3337],{"class":1111}," unused",[69,3339,3340],{"class":1111}," variables",[69,3342,3343],{"class":1111}," given",[69,3345,3346],{"class":1111}," on",[69,3348,2268],{"class":1111},[69,3350,3351],{"class":1111}," command",[69,3353,3354],{"class":1111}," line.\n",[69,3356,3357,3360,3362,3365,3368,3371,3374,3377],{"class":71,"line":304},[69,3358,3359],{"class":1179},"--",[69,3361,3090],{"class":1111},[69,3363,3364],{"class":1111}," CXX",[69,3366,3367],{"class":1111}," compiler",[69,3369,3370],{"class":1111}," identification",[69,3372,3373],{"class":1111}," is",[69,3375,3376],{"class":1111}," MSVC",[69,3378,3379],{"class":1172}," 19.44.35220.0\n",[69,3381,3382,3384,3387,3389,3391,3394],{"class":71,"line":310},[69,3383,3359],{"class":1179},[69,3385,3386],{"class":1111}," Detecting",[69,3388,3364],{"class":1111},[69,3390,3367],{"class":1111},[69,3392,3393],{"class":1111}," ABI",[69,3395,3396],{"class":1111}," info\n",[69,3398,3399,3401,3403,3405,3407,3409,3411,3414],{"class":71,"line":316},[69,3400,3359],{"class":1179},[69,3402,3386],{"class":1111},[69,3404,3364],{"class":1111},[69,3406,3367],{"class":1111},[69,3408,3393],{"class":1111},[69,3410,2304],{"class":1111},[69,3412,3413],{"class":1111}," -",[69,3415,3416],{"class":1111}," done\n",[69,3418,3419,3421,3424,3426,3429,3431,3434,3437,3440],{"class":71,"line":321},[69,3420,3359],{"class":1179},[69,3422,3423],{"class":1111}," Check",[69,3425,3334],{"class":1111},[69,3427,3428],{"class":1111}," working",[69,3430,3364],{"class":1111},[69,3432,3433],{"class":1111}," compiler:",[69,3435,3436],{"class":1111}," C:\u002FProgram",[69,3438,3439],{"class":1111}," Files",[69,3441,3442],{"class":1104}," (x86)\u002FMicrosoft Visual Studio\u002F2022\u002FBuildTools\u002FVC\u002FTools\u002FMSVC\u002F14.44.35207\u002Fbin\u002FHostx64\u002Fx64\u002Fcl.exe - skipped\n",[69,3444,3445,3447,3449,3451,3453],{"class":71,"line":327},[69,3446,3359],{"class":1179},[69,3448,3386],{"class":1111},[69,3450,3364],{"class":1111},[69,3452,2979],{"class":1111},[69,3454,3455],{"class":1111}," features\n",[69,3457,3458,3460,3462,3464,3466,3469,3471],{"class":71,"line":333},[69,3459,3359],{"class":1179},[69,3461,3386],{"class":1111},[69,3463,3364],{"class":1111},[69,3465,2979],{"class":1111},[69,3467,3468],{"class":1111}," features",[69,3470,3413],{"class":1111},[69,3472,3416],{"class":1111},[69,3474,3475,3477,3480,3483],{"class":71,"line":339},[69,3476,3359],{"class":1179},[69,3478,3479],{"class":1111}," Configuring",[69,3481,3482],{"class":1111}," done",[69,3484,3485],{"class":1104}," (25.3s)\n",[69,3487,3488,3490,3493,3495],{"class":71,"line":345},[69,3489,3359],{"class":1179},[69,3491,3492],{"class":1111}," Generating",[69,3494,3482],{"class":1111},[69,3496,3497],{"class":1104}," (2.7s)\n",[69,3499,3500,3502,3504,3507,3510,3512,3515,3518],{"class":71,"line":801},[69,3501,3359],{"class":1179},[69,3503,3121],{"class":1111},[69,3505,3506],{"class":1111}," files",[69,3508,3509],{"class":1111}," have",[69,3511,3127],{"class":1111},[69,3513,3514],{"class":1111}," written",[69,3516,3517],{"class":1111}," to:",[69,3519,3520],{"class":1111}," F:\u002FglfwExamle\u002FglfwExample\u002FglfwExample\u002Fbuild\n",[69,3522,3523,3525,3527],{"class":71,"line":807},[69,3524,2990],{"class":1179},[69,3526,3021],{"class":1111},[69,3528,3024],{"class":1111},[69,3530,3531,3533,3535],{"class":71,"line":813},[69,3532,2990],{"class":1179},[69,3534,3031],{"class":1111},[69,3536,3034],{"class":1104},[69,3538,3539,3541],{"class":71,"line":819},[69,3540,3039],{"class":1179},[69,3542,3042],{"class":1179},[69,3544,3545,3547],{"class":71,"line":825},[69,3546,3047],{"class":1179},[69,3548,3042],{"class":1179},[69,3550,3551,3553],{"class":71,"line":830},[69,3552,3054],{"class":1179},[69,3554,3042],{"class":1179},[69,3556,3557,3559],{"class":71,"line":835},[69,3558,3061],{"class":1179},[69,3560,3042],{"class":1179},[69,3562,3563],{"class":71,"line":841},[69,3564,3068],{"class":1179},[69,3566,3567],{"class":71,"line":846},[69,3568,3073],{"class":1104},[69,3570,3571,3574,3577,3579,3582,3585],{"class":71,"line":852},[69,3572,3573],{"class":1104},"[3\u002F3] ",[69,3575,3576],{"class":1179},"Linking",[69,3578,3364],{"class":1111},[69,3580,3581],{"class":1111}," shared",[69,3583,3584],{"class":1111}," library",[69,3586,3587],{"class":1111}," capture_addon.node\n",[14,3589,3590],{},"编译成功，我们得到了一个.node文件，是node.js可以直接调用的二进制模块。可以快速做一个验证，你在同级目录新建js文件：",[60,3592,3594],{"className":1060,"code":3593,"language":1062,"meta":65,"style":65},"const addon = require('.\u002Fbuild\u002Fcapture_addon.node');\n\nconsole.log('模块加载成功！');\n\ntry {\n    \u002F\u002F 既然对象里有 start，直接试试它\n    const result = addon.start(); \n    console.log('Start 结果:', result);\n    \n    const size = addon.getSize();\n    console.log(`显示器尺寸: ${size.width} x ${size.height}`);\n} catch (err) {\n    console.error('调用失败:', err);\n}\n",[21,3595,3596,3615,3619,3632,3636,3644,3649,3668,3683,3688,3705,3738,3749,3763],{"__ignoreMap":65},[69,3597,3598,3600,3603,3605,3607,3609,3612],{"class":71,"line":72},[69,3599,1169],{"class":1100},[69,3601,3602],{"class":1172}," addon",[69,3604,1176],{"class":1100},[69,3606,1206],{"class":1179},[69,3608,1183],{"class":1104},[69,3610,3611],{"class":1111},"'.\u002Fbuild\u002Fcapture_addon.node'",[69,3613,3614],{"class":1104},");\n",[69,3616,3617],{"class":71,"line":78},[69,3618,82],{"emptyLinePlaceholder":81},[69,3620,3621,3623,3625,3627,3630],{"class":71,"line":85},[69,3622,1284],{"class":1104},[69,3624,1287],{"class":1179},[69,3626,1183],{"class":1104},[69,3628,3629],{"class":1111},"'模块加载成功！'",[69,3631,3614],{"class":1104},[69,3633,3634],{"class":71,"line":91},[69,3635,82],{"emptyLinePlaceholder":81},[69,3637,3638,3641],{"class":71,"line":97},[69,3639,3640],{"class":1100},"try",[69,3642,3643],{"class":1104}," {\n",[69,3645,3646],{"class":71,"line":103},[69,3647,3648],{"class":1069},"    \u002F\u002F 既然对象里有 start，直接试试它\n",[69,3650,3651,3654,3657,3659,3662,3665],{"class":71,"line":109},[69,3652,3653],{"class":1100},"    const",[69,3655,3656],{"class":1172}," result",[69,3658,1176],{"class":1100},[69,3660,3661],{"class":1104}," addon.",[69,3663,3664],{"class":1179},"start",[69,3666,3667],{"class":1104},"(); \n",[69,3669,3670,3673,3675,3677,3680],{"class":71,"line":115},[69,3671,3672],{"class":1104},"    console.",[69,3674,1287],{"class":1179},[69,3676,1183],{"class":1104},[69,3678,3679],{"class":1111},"'Start 结果:'",[69,3681,3682],{"class":1104},", result);\n",[69,3684,3685],{"class":71,"line":121},[69,3686,3687],{"class":1104},"    \n",[69,3689,3690,3692,3695,3697,3699,3702],{"class":71,"line":126},[69,3691,3653],{"class":1100},[69,3693,3694],{"class":1172}," size",[69,3696,1176],{"class":1100},[69,3698,3661],{"class":1104},[69,3700,3701],{"class":1179},"getSize",[69,3703,3704],{"class":1104},"();\n",[69,3706,3707,3709,3711,3713,3716,3719,3721,3724,3727,3729,3731,3734,3736],{"class":71,"line":132},[69,3708,3672],{"class":1104},[69,3710,1287],{"class":1179},[69,3712,1183],{"class":1104},[69,3714,3715],{"class":1111},"`显示器尺寸: ${",[69,3717,3718],{"class":1104},"size",[69,3720,1193],{"class":1111},[69,3722,3723],{"class":1104},"width",[69,3725,3726],{"class":1111},"} x ${",[69,3728,3718],{"class":1104},[69,3730,1193],{"class":1111},[69,3732,3733],{"class":1104},"height",[69,3735,1567],{"class":1111},[69,3737,3614],{"class":1104},[69,3739,3740,3743,3746],{"class":71,"line":137},[69,3741,3742],{"class":1104},"} ",[69,3744,3745],{"class":1100},"catch",[69,3747,3748],{"class":1104}," (err) {\n",[69,3750,3751,3753,3755,3757,3760],{"class":71,"line":143},[69,3752,3672],{"class":1104},[69,3754,1457],{"class":1179},[69,3756,1183],{"class":1104},[69,3758,3759],{"class":1111},"'调用失败:'",[69,3761,3762],{"class":1104},", err);\n",[69,3764,3765],{"class":71,"line":149},[69,3766,1023],{"class":1104},[14,3768,3769],{},"输出：",[60,3771,3773],{"className":2217,"code":3772,"language":2219,"meta":65,"style":65},"(mmlab) PS F:\\glfwExamle\\glfwExample\\glfwExample> node .\\test.js\n模块加载成功！\nStart 结果: undefined\n显示器尺寸: 1920 x 1080\n(mmlab) PS F:\\glfwExamle\\glfwExample\\glfwExample> \n",[21,3774,3775,3817,3822,3833,3847],{"__ignoreMap":65},[69,3776,3777,3779,3782,3784,3786,3788,3790,3792,3794,3797,3799,3801,3803,3805,3808,3811,3814],{"class":71,"line":72},[69,3778,1183],{"class":1104},[69,3780,3781],{"class":1179},"mmlab",[69,3783,1441],{"class":1104},[69,3785,2226],{"class":1179},[69,3787,2229],{"class":1111},[69,3789,2232],{"class":1172},[69,3791,2235],{"class":1111},[69,3793,2232],{"class":1172},[69,3795,3796],{"class":1111},"lfwExample",[69,3798,2232],{"class":1172},[69,3800,2240],{"class":1111},[69,3802,2243],{"class":1104},[69,3804,2246],{"class":1100},[69,3806,3807],{"class":1111}," node",[69,3809,3810],{"class":1111}," .",[69,3812,3813],{"class":1172},"\\t",[69,3815,3816],{"class":1111},"est.js\n",[69,3818,3819],{"class":71,"line":78},[69,3820,3821],{"class":1179},"模块加载成功！\n",[69,3823,3824,3827,3830],{"class":71,"line":85},[69,3825,3826],{"class":1179},"Start",[69,3828,3829],{"class":1111}," 结果:",[69,3831,3832],{"class":1111}," undefined\n",[69,3834,3835,3838,3841,3844],{"class":71,"line":91},[69,3836,3837],{"class":1179},"显示器尺寸:",[69,3839,3840],{"class":1172}," 1920",[69,3842,3843],{"class":1111}," x",[69,3845,3846],{"class":1172}," 1080\n",[69,3848,3849,3851,3853,3855,3857,3859,3861,3863,3865,3867,3869,3871,3873],{"class":71,"line":97},[69,3850,1183],{"class":1104},[69,3852,3781],{"class":1179},[69,3854,1441],{"class":1104},[69,3856,2226],{"class":1179},[69,3858,2229],{"class":1111},[69,3860,2232],{"class":1172},[69,3862,2235],{"class":1111},[69,3864,2232],{"class":1172},[69,3866,3796],{"class":1111},[69,3868,2232],{"class":1172},[69,3870,2240],{"class":1111},[69,3872,2243],{"class":1104},[69,3874,2455],{"class":1100},[10,3876,3877],{"id":3877},"使用",[14,3879,3880],{},"测试通过之后，我们可以可以来真正使用这个原生模块了，由于Electron的主进程在nodejs中，渲染进程在chromium中，因此我们需要通过context bridge将原生模块能力桥接给前端。",[14,3882,3883],{},"为了让canvas可以渲染，我们需要一个能把c++的pixelBuffer传递给js的办法，我们可以使用Napi::Buffer，它可以允许JS直接访问C++的内存地址。",[60,3885,3887],{"className":62,"code":3886,"language":64,"meta":65,"style":65},"    Napi::Value GetFrame(const Napi::CallbackInfo& info) {\n        Napi::Env env = info.Env();\n        auto& buffer = g_capture->pixel_buffer();\n        auto& mutex = g_capture->buffer_mutex();\n        std::lock_guard\u003Cstd::mutex> lock(mutex);\n        return Napi::Buffer\u003Cuint8_t>::Copy(env, buffer.data(), buffer.size());\n    }\n",[21,3888,3889,3894,3898,3903,3908,3913,3918],{"__ignoreMap":65},[69,3890,3891],{"class":71,"line":72},[69,3892,3893],{},"    Napi::Value GetFrame(const Napi::CallbackInfo& info) {\n",[69,3895,3896],{"class":71,"line":78},[69,3897,877],{},[69,3899,3900],{"class":71,"line":85},[69,3901,3902],{},"        auto& buffer = g_capture->pixel_buffer();\n",[69,3904,3905],{"class":71,"line":91},[69,3906,3907],{},"        auto& mutex = g_capture->buffer_mutex();\n",[69,3909,3910],{"class":71,"line":97},[69,3911,3912],{},"        std::lock_guard\u003Cstd::mutex> lock(mutex);\n",[69,3914,3915],{"class":71,"line":103},[69,3916,3917],{},"        return Napi::Buffer\u003Cuint8_t>::Copy(env, buffer.data(), buffer.size());\n",[69,3919,3920],{"class":71,"line":109},[69,3921,715],{},[14,3923,3924],{},"在electron中，由于安全限制，不能直接在渲染进程require原生模块，因此我们最好的解决方法是写一个预加载脚本preload.js。",[60,3926,3928],{"className":1060,"code":3927,"language":1062,"meta":65,"style":65},"\nfunction loadAddon() {\n  if (addonTried) return addon\n  addonTried = true\n  if (process.env.SKIP_NATIVE === '1') return null\n  const p = join(__dirname, '..\u002F..\u002Fnative\u002Fwgc-addon\u002Fcapture_addon.node')\n  if (!existsSync(p)) {\n    console.error('[preload] 未找到', p)\n    return null\n  }\n  try {\n    addon = require(p)\n    return addon\n  } catch (e) {\n    console.error('[preload] require .node 失败', e)\n    return null\n  }\n}\n\nfunction getAddon() {\n  return loadAddon()\n}\n\nconst captureAPI = {\n  start: () => {\n    if (process.env.SKIP_NATIVE === '1') return Promise.resolve()\n    const a = getAddon()\n    if (!a) throw new Error('native addon not loaded')\n    return Promise.resolve(a.start())\n  },\n  stop: () => {\n    if (process.env.SKIP_NATIVE === '1') return undefined\n    const a = getAddon()\n    if (!a) return undefined\n    return a.stop()\n  },\n  getSize: () => {\n    if (process.env.SKIP_NATIVE === '1') return { width: 800, height: 600 }\n    const a = getAddon()\n    if (!a) return { width: 0, height: 0 }\n    return a.getSize()\n  },\n\n  getFrame: () => {\n    if (process.env.SKIP_NATIVE === '1') return null\n    const a = getAddon()\n    if (!a) return null\n    return a.getFrame()\n  },\n  \u002F** outputPath：UTF-8，如 C:\\\\\\\\out\\\\\\\\cap.mp4 *\u002F\n  startRecording: (outputPath, config) => {\n    if (process.env.SKIP_NATIVE === '1') return Promise.resolve()\n    const a = getAddon()\n    if (!a) throw new Error('native addon not loaded')\n    return Promise.resolve(a.startRecording(outputPath, config))\n  },\n  stopRecording: () => {\n    if (process.env.SKIP_NATIVE === '1') return undefined\n    const a = getAddon()\n    if (!a) return undefined\n    return a.stopRecording()\n  },\n  isRecording: () => {\n    if (process.env.SKIP_NATIVE === '1') return false\n    const a = getAddon()\n    if (!a) return false\n    return a.isRecording()\n  },\n  \u002F** 枚举播放设备；loopback 录制里可把返回的 id 填进 config.audioOutputDeviceId *\u002F\n  getAudioOutputDevices: () => {\n    if (process.env.SKIP_NATIVE === '1') return Promise.resolve([])\n    const a = getAddon()\n    if (!a) return Promise.resolve([])\n    return Promise.resolve(a.getAudioOutputDevices())\n  }\n}\n",[21,3929,3930,3934,3944,3957,3968,3991,4009,4022,4036,4043,4047,4054,4066,4072,4082,4096,4102,4106,4110,4114,4123,4133,4137,4141,4152,4164,4191,4204,4231,4249,4254,4265,4283,4295,4309,4321,4325,4336,4367,4379,4402,4412,4416,4420,4431,4449,4461,4475,4486,4490,4495,4517,4541,4553,4575,4592,4596,4607,4625,4637,4651,4661,4665,4676,4695,4707,4721,4731,4735,4740,4751,4776,4788,4808,4824,4828],{"__ignoreMap":65},[69,3931,3932],{"class":71,"line":72},[69,3933,82],{"emptyLinePlaceholder":81},[69,3935,3936,3938,3941],{"class":71,"line":78},[69,3937,1352],{"class":1100},[69,3939,3940],{"class":1179}," loadAddon",[69,3942,3943],{"class":1104},"() {\n",[69,3945,3946,3948,3951,3954],{"class":71,"line":85},[69,3947,1429],{"class":1100},[69,3949,3950],{"class":1104}," (addonTried) ",[69,3952,3953],{"class":1100},"return",[69,3955,3956],{"class":1104}," addon\n",[69,3958,3959,3962,3965],{"class":71,"line":91},[69,3960,3961],{"class":1104},"  addonTried ",[69,3963,3964],{"class":1100},"=",[69,3966,3967],{"class":1172}," true\n",[69,3969,3970,3972,3975,3978,3981,3984,3986,3988],{"class":71,"line":97},[69,3971,1429],{"class":1100},[69,3973,3974],{"class":1104}," (process.env.",[69,3976,3977],{"class":1172},"SKIP_NATIVE",[69,3979,3980],{"class":1100}," ===",[69,3982,3983],{"class":1111}," '1'",[69,3985,1441],{"class":1104},[69,3987,3953],{"class":1100},[69,3989,3990],{"class":1172}," null\n",[69,3992,3993,3995,3998,4000,4002,4004,4007],{"class":71,"line":103},[69,3994,1401],{"class":1100},[69,3996,3997],{"class":1172}," p",[69,3999,1176],{"class":1100},[69,4001,1234],{"class":1179},[69,4003,1237],{"class":1104},[69,4005,4006],{"class":1111},"'..\u002F..\u002Fnative\u002Fwgc-addon\u002Fcapture_addon.node'",[69,4008,1243],{"class":1104},[69,4010,4011,4013,4015,4017,4019],{"class":71,"line":109},[69,4012,1429],{"class":1100},[69,4014,1614],{"class":1104},[69,4016,1704],{"class":1100},[69,4018,1617],{"class":1179},[69,4020,4021],{"class":1104},"(p)) {\n",[69,4023,4024,4026,4028,4030,4033],{"class":71,"line":115},[69,4025,1454],{"class":1104},[69,4027,1457],{"class":1179},[69,4029,1183],{"class":1104},[69,4031,4032],{"class":1111},"'[preload] 未找到'",[69,4034,4035],{"class":1104},", p)\n",[69,4037,4038,4041],{"class":71,"line":121},[69,4039,4040],{"class":1100},"    return",[69,4042,3990],{"class":1172},[69,4044,4045],{"class":71,"line":126},[69,4046,1487],{"class":1104},[69,4048,4049,4052],{"class":71,"line":132},[69,4050,4051],{"class":1100},"  try",[69,4053,3643],{"class":1104},[69,4055,4056,4059,4061,4063],{"class":71,"line":137},[69,4057,4058],{"class":1104},"    addon ",[69,4060,3964],{"class":1100},[69,4062,1206],{"class":1179},[69,4064,4065],{"class":1104},"(p)\n",[69,4067,4068,4070],{"class":71,"line":143},[69,4069,4040],{"class":1100},[69,4071,3956],{"class":1104},[69,4073,4074,4077,4079],{"class":71,"line":149},[69,4075,4076],{"class":1104},"  } ",[69,4078,3745],{"class":1100},[69,4080,4081],{"class":1104}," (e) {\n",[69,4083,4084,4086,4088,4090,4093],{"class":71,"line":155},[69,4085,1454],{"class":1104},[69,4087,1457],{"class":1179},[69,4089,1183],{"class":1104},[69,4091,4092],{"class":1111},"'[preload] require .node 失败'",[69,4094,4095],{"class":1104},", e)\n",[69,4097,4098,4100],{"class":71,"line":161},[69,4099,4040],{"class":1100},[69,4101,3990],{"class":1172},[69,4103,4104],{"class":71,"line":166},[69,4105,1487],{"class":1104},[69,4107,4108],{"class":71,"line":172},[69,4109,1023],{"class":1104},[69,4111,4112],{"class":71,"line":178},[69,4113,82],{"emptyLinePlaceholder":81},[69,4115,4116,4118,4121],{"class":71,"line":184},[69,4117,1352],{"class":1100},[69,4119,4120],{"class":1179}," getAddon",[69,4122,3943],{"class":1104},[69,4124,4125,4128,4130],{"class":71,"line":189},[69,4126,4127],{"class":1100},"  return",[69,4129,3940],{"class":1179},[69,4131,4132],{"class":1104},"()\n",[69,4134,4135],{"class":71,"line":195},[69,4136,1023],{"class":1104},[69,4138,4139],{"class":71,"line":201},[69,4140,82],{"emptyLinePlaceholder":81},[69,4142,4143,4145,4148,4150],{"class":71,"line":207},[69,4144,1169],{"class":1100},[69,4146,4147],{"class":1172}," captureAPI",[69,4149,1176],{"class":1100},[69,4151,3643],{"class":1104},[69,4153,4154,4157,4160,4162],{"class":71,"line":213},[69,4155,4156],{"class":1179},"  start",[69,4158,4159],{"class":1104},": () ",[69,4161,1682],{"class":1100},[69,4163,3643],{"class":1104},[69,4165,4166,4169,4171,4173,4175,4177,4179,4181,4184,4186,4189],{"class":71,"line":218},[69,4167,4168],{"class":1100},"    if",[69,4170,3974],{"class":1104},[69,4172,3977],{"class":1172},[69,4174,3980],{"class":1100},[69,4176,3983],{"class":1111},[69,4178,1441],{"class":1104},[69,4180,3953],{"class":1100},[69,4182,4183],{"class":1172}," Promise",[69,4185,1193],{"class":1104},[69,4187,4188],{"class":1179},"resolve",[69,4190,4132],{"class":1104},[69,4192,4193,4196,4198,4200,4202],{"class":71,"line":224},[69,4194,4195],{"class":1100},"    const",[69,4197,3141],{"class":1172},[69,4199,1176],{"class":1100},[69,4201,4120],{"class":1179},[69,4203,4132],{"class":1104},[69,4205,4206,4208,4210,4212,4215,4218,4221,4224,4226,4229],{"class":71,"line":230},[69,4207,4168],{"class":1100},[69,4209,1614],{"class":1104},[69,4211,1704],{"class":1100},[69,4213,4214],{"class":1104},"a) ",[69,4216,4217],{"class":1100},"throw",[69,4219,4220],{"class":1100}," new",[69,4222,4223],{"class":1179}," Error",[69,4225,1183],{"class":1104},[69,4227,4228],{"class":1111},"'native addon not loaded'",[69,4230,1243],{"class":1104},[69,4232,4233,4235,4237,4239,4241,4244,4246],{"class":71,"line":236},[69,4234,4040],{"class":1100},[69,4236,4183],{"class":1172},[69,4238,1193],{"class":1104},[69,4240,4188],{"class":1179},[69,4242,4243],{"class":1104},"(a.",[69,4245,3664],{"class":1179},[69,4247,4248],{"class":1104},"())\n",[69,4250,4251],{"class":71,"line":241},[69,4252,4253],{"class":1104},"  },\n",[69,4255,4256,4259,4261,4263],{"class":71,"line":247},[69,4257,4258],{"class":1179},"  stop",[69,4260,4159],{"class":1104},[69,4262,1682],{"class":1100},[69,4264,3643],{"class":1104},[69,4266,4267,4269,4271,4273,4275,4277,4279,4281],{"class":71,"line":253},[69,4268,4168],{"class":1100},[69,4270,3974],{"class":1104},[69,4272,3977],{"class":1172},[69,4274,3980],{"class":1100},[69,4276,3983],{"class":1111},[69,4278,1441],{"class":1104},[69,4280,3953],{"class":1100},[69,4282,3832],{"class":1172},[69,4284,4285,4287,4289,4291,4293],{"class":71,"line":259},[69,4286,4195],{"class":1100},[69,4288,3141],{"class":1172},[69,4290,1176],{"class":1100},[69,4292,4120],{"class":1179},[69,4294,4132],{"class":1104},[69,4296,4297,4299,4301,4303,4305,4307],{"class":71,"line":264},[69,4298,4168],{"class":1100},[69,4300,1614],{"class":1104},[69,4302,1704],{"class":1100},[69,4304,4214],{"class":1104},[69,4306,3953],{"class":1100},[69,4308,3832],{"class":1172},[69,4310,4311,4313,4316,4319],{"class":71,"line":270},[69,4312,4040],{"class":1100},[69,4314,4315],{"class":1104}," a.",[69,4317,4318],{"class":1179},"stop",[69,4320,4132],{"class":1104},[69,4322,4323],{"class":71,"line":276},[69,4324,4253],{"class":1104},[69,4326,4327,4330,4332,4334],{"class":71,"line":282},[69,4328,4329],{"class":1179},"  getSize",[69,4331,4159],{"class":1104},[69,4333,1682],{"class":1100},[69,4335,3643],{"class":1104},[69,4337,4338,4340,4342,4344,4346,4348,4350,4352,4355,4358,4361,4364],{"class":71,"line":287},[69,4339,4168],{"class":1100},[69,4341,3974],{"class":1104},[69,4343,3977],{"class":1172},[69,4345,3980],{"class":1100},[69,4347,3983],{"class":1111},[69,4349,1441],{"class":1104},[69,4351,3953],{"class":1100},[69,4353,4354],{"class":1104}," { width: ",[69,4356,4357],{"class":1172},"800",[69,4359,4360],{"class":1104},", height: ",[69,4362,4363],{"class":1172},"600",[69,4365,4366],{"class":1104}," }\n",[69,4368,4369,4371,4373,4375,4377],{"class":71,"line":293},[69,4370,4195],{"class":1100},[69,4372,3141],{"class":1172},[69,4374,1176],{"class":1100},[69,4376,4120],{"class":1179},[69,4378,4132],{"class":1104},[69,4380,4381,4383,4385,4387,4389,4391,4393,4396,4398,4400],{"class":71,"line":299},[69,4382,4168],{"class":1100},[69,4384,1614],{"class":1104},[69,4386,1704],{"class":1100},[69,4388,4214],{"class":1104},[69,4390,3953],{"class":1100},[69,4392,4354],{"class":1104},[69,4394,4395],{"class":1172},"0",[69,4397,4360],{"class":1104},[69,4399,4395],{"class":1172},[69,4401,4366],{"class":1104},[69,4403,4404,4406,4408,4410],{"class":71,"line":304},[69,4405,4040],{"class":1100},[69,4407,4315],{"class":1104},[69,4409,3701],{"class":1179},[69,4411,4132],{"class":1104},[69,4413,4414],{"class":71,"line":310},[69,4415,4253],{"class":1104},[69,4417,4418],{"class":71,"line":316},[69,4419,82],{"emptyLinePlaceholder":81},[69,4421,4422,4425,4427,4429],{"class":71,"line":321},[69,4423,4424],{"class":1179},"  getFrame",[69,4426,4159],{"class":1104},[69,4428,1682],{"class":1100},[69,4430,3643],{"class":1104},[69,4432,4433,4435,4437,4439,4441,4443,4445,4447],{"class":71,"line":327},[69,4434,4168],{"class":1100},[69,4436,3974],{"class":1104},[69,4438,3977],{"class":1172},[69,4440,3980],{"class":1100},[69,4442,3983],{"class":1111},[69,4444,1441],{"class":1104},[69,4446,3953],{"class":1100},[69,4448,3990],{"class":1172},[69,4450,4451,4453,4455,4457,4459],{"class":71,"line":333},[69,4452,4195],{"class":1100},[69,4454,3141],{"class":1172},[69,4456,1176],{"class":1100},[69,4458,4120],{"class":1179},[69,4460,4132],{"class":1104},[69,4462,4463,4465,4467,4469,4471,4473],{"class":71,"line":339},[69,4464,4168],{"class":1100},[69,4466,1614],{"class":1104},[69,4468,1704],{"class":1100},[69,4470,4214],{"class":1104},[69,4472,3953],{"class":1100},[69,4474,3990],{"class":1172},[69,4476,4477,4479,4481,4484],{"class":71,"line":345},[69,4478,4040],{"class":1100},[69,4480,4315],{"class":1104},[69,4482,4483],{"class":1179},"getFrame",[69,4485,4132],{"class":1104},[69,4487,4488],{"class":71,"line":801},[69,4489,4253],{"class":1104},[69,4491,4492],{"class":71,"line":807},[69,4493,4494],{"class":1069},"  \u002F** outputPath：UTF-8，如 C:\\\\\\\\out\\\\\\\\cap.mp4 *\u002F\n",[69,4496,4497,4500,4503,4506,4508,4511,4513,4515],{"class":71,"line":813},[69,4498,4499],{"class":1179},"  startRecording",[69,4501,4502],{"class":1104},": (",[69,4504,4505],{"class":1360},"outputPath",[69,4507,1364],{"class":1104},[69,4509,4510],{"class":1360},"config",[69,4512,1441],{"class":1104},[69,4514,1682],{"class":1100},[69,4516,3643],{"class":1104},[69,4518,4519,4521,4523,4525,4527,4529,4531,4533,4535,4537,4539],{"class":71,"line":819},[69,4520,4168],{"class":1100},[69,4522,3974],{"class":1104},[69,4524,3977],{"class":1172},[69,4526,3980],{"class":1100},[69,4528,3983],{"class":1111},[69,4530,1441],{"class":1104},[69,4532,3953],{"class":1100},[69,4534,4183],{"class":1172},[69,4536,1193],{"class":1104},[69,4538,4188],{"class":1179},[69,4540,4132],{"class":1104},[69,4542,4543,4545,4547,4549,4551],{"class":71,"line":825},[69,4544,4195],{"class":1100},[69,4546,3141],{"class":1172},[69,4548,1176],{"class":1100},[69,4550,4120],{"class":1179},[69,4552,4132],{"class":1104},[69,4554,4555,4557,4559,4561,4563,4565,4567,4569,4571,4573],{"class":71,"line":830},[69,4556,4168],{"class":1100},[69,4558,1614],{"class":1104},[69,4560,1704],{"class":1100},[69,4562,4214],{"class":1104},[69,4564,4217],{"class":1100},[69,4566,4220],{"class":1100},[69,4568,4223],{"class":1179},[69,4570,1183],{"class":1104},[69,4572,4228],{"class":1111},[69,4574,1243],{"class":1104},[69,4576,4577,4579,4581,4583,4585,4587,4589],{"class":71,"line":835},[69,4578,4040],{"class":1100},[69,4580,4183],{"class":1172},[69,4582,1193],{"class":1104},[69,4584,4188],{"class":1179},[69,4586,4243],{"class":1104},[69,4588,527],{"class":1179},[69,4590,4591],{"class":1104},"(outputPath, config))\n",[69,4593,4594],{"class":71,"line":841},[69,4595,4253],{"class":1104},[69,4597,4598,4601,4603,4605],{"class":71,"line":846},[69,4599,4600],{"class":1179},"  stopRecording",[69,4602,4159],{"class":1104},[69,4604,1682],{"class":1100},[69,4606,3643],{"class":1104},[69,4608,4609,4611,4613,4615,4617,4619,4621,4623],{"class":71,"line":852},[69,4610,4168],{"class":1100},[69,4612,3974],{"class":1104},[69,4614,3977],{"class":1172},[69,4616,3980],{"class":1100},[69,4618,3983],{"class":1111},[69,4620,1441],{"class":1104},[69,4622,3953],{"class":1100},[69,4624,3832],{"class":1172},[69,4626,4627,4629,4631,4633,4635],{"class":71,"line":858},[69,4628,4195],{"class":1100},[69,4630,3141],{"class":1172},[69,4632,1176],{"class":1100},[69,4634,4120],{"class":1179},[69,4636,4132],{"class":1104},[69,4638,4639,4641,4643,4645,4647,4649],{"class":71,"line":863},[69,4640,4168],{"class":1100},[69,4642,1614],{"class":1104},[69,4644,1704],{"class":1100},[69,4646,4214],{"class":1104},[69,4648,3953],{"class":1100},[69,4650,3832],{"class":1172},[69,4652,4653,4655,4657,4659],{"class":71,"line":869},[69,4654,4040],{"class":1100},[69,4656,4315],{"class":1104},[69,4658,530],{"class":1179},[69,4660,4132],{"class":1104},[69,4662,4663],{"class":71,"line":874},[69,4664,4253],{"class":1104},[69,4666,4667,4670,4672,4674],{"class":71,"line":880},[69,4668,4669],{"class":1179},"  isRecording",[69,4671,4159],{"class":1104},[69,4673,1682],{"class":1100},[69,4675,3643],{"class":1104},[69,4677,4678,4680,4682,4684,4686,4688,4690,4692],{"class":71,"line":886},[69,4679,4168],{"class":1100},[69,4681,3974],{"class":1104},[69,4683,3977],{"class":1172},[69,4685,3980],{"class":1100},[69,4687,3983],{"class":1111},[69,4689,1441],{"class":1104},[69,4691,3953],{"class":1100},[69,4693,4694],{"class":1172}," false\n",[69,4696,4697,4699,4701,4703,4705],{"class":71,"line":892},[69,4698,4195],{"class":1100},[69,4700,3141],{"class":1172},[69,4702,1176],{"class":1100},[69,4704,4120],{"class":1179},[69,4706,4132],{"class":1104},[69,4708,4709,4711,4713,4715,4717,4719],{"class":71,"line":898},[69,4710,4168],{"class":1100},[69,4712,1614],{"class":1104},[69,4714,1704],{"class":1100},[69,4716,4214],{"class":1104},[69,4718,3953],{"class":1100},[69,4720,4694],{"class":1172},[69,4722,4723,4725,4727,4729],{"class":71,"line":904},[69,4724,4040],{"class":1100},[69,4726,4315],{"class":1104},[69,4728,533],{"class":1179},[69,4730,4132],{"class":1104},[69,4732,4733],{"class":71,"line":910},[69,4734,4253],{"class":1104},[69,4736,4737],{"class":71,"line":916},[69,4738,4739],{"class":1069},"  \u002F** 枚举播放设备；loopback 录制里可把返回的 id 填进 config.audioOutputDeviceId *\u002F\n",[69,4741,4742,4745,4747,4749],{"class":71,"line":922},[69,4743,4744],{"class":1179},"  getAudioOutputDevices",[69,4746,4159],{"class":1104},[69,4748,1682],{"class":1100},[69,4750,3643],{"class":1104},[69,4752,4753,4755,4757,4759,4761,4763,4765,4767,4769,4771,4773],{"class":71,"line":927},[69,4754,4168],{"class":1100},[69,4756,3974],{"class":1104},[69,4758,3977],{"class":1172},[69,4760,3980],{"class":1100},[69,4762,3983],{"class":1111},[69,4764,1441],{"class":1104},[69,4766,3953],{"class":1100},[69,4768,4183],{"class":1172},[69,4770,1193],{"class":1104},[69,4772,4188],{"class":1179},[69,4774,4775],{"class":1104},"([])\n",[69,4777,4778,4780,4782,4784,4786],{"class":71,"line":933},[69,4779,4195],{"class":1100},[69,4781,3141],{"class":1172},[69,4783,1176],{"class":1100},[69,4785,4120],{"class":1179},[69,4787,4132],{"class":1104},[69,4789,4790,4792,4794,4796,4798,4800,4802,4804,4806],{"class":71,"line":938},[69,4791,4168],{"class":1100},[69,4793,1614],{"class":1104},[69,4795,1704],{"class":1100},[69,4797,4214],{"class":1104},[69,4799,3953],{"class":1100},[69,4801,4183],{"class":1172},[69,4803,1193],{"class":1104},[69,4805,4188],{"class":1179},[69,4807,4775],{"class":1104},[69,4809,4810,4812,4814,4816,4818,4820,4822],{"class":71,"line":943},[69,4811,4040],{"class":1100},[69,4813,4183],{"class":1172},[69,4815,1193],{"class":1104},[69,4817,4188],{"class":1179},[69,4819,4243],{"class":1104},[69,4821,539],{"class":1179},[69,4823,4248],{"class":1104},[69,4825,4826],{"class":71,"line":949},[69,4827,1487],{"class":1104},[69,4829,4830],{"class":71,"line":954},[69,4831,1023],{"class":1104},[60,4833,4835],{"className":1060,"code":4834,"language":1062,"meta":65,"style":65},"if (process.contextIsolated) {\n  try {\n    contextBridge.exposeInMainWorld('captureAPI', captureAPI)\n    contextBridge.exposeInMainWorld('electron', electronAPI)\n    contextBridge.exposeInMainWorld('api', api)\n  } catch (error) {\n    console.error('[preload] expose failed:', error)\n  }\n} else {\n  window.captureAPI = captureAPI\n  window.electron = electronAPI\n  window.api = api\n}\n",[21,4836,4837,4844,4850,4866,4880,4894,4903,4917,4921,4930,4940,4950,4960],{"__ignoreMap":65},[69,4838,4839,4841],{"class":71,"line":72},[69,4840,1611],{"class":1100},[69,4842,4843],{"class":1104}," (process.contextIsolated) {\n",[69,4845,4846,4848],{"class":71,"line":78},[69,4847,4051],{"class":1100},[69,4849,3643],{"class":1104},[69,4851,4852,4855,4858,4860,4863],{"class":71,"line":85},[69,4853,4854],{"class":1104},"    contextBridge.",[69,4856,4857],{"class":1179},"exposeInMainWorld",[69,4859,1183],{"class":1104},[69,4861,4862],{"class":1111},"'captureAPI'",[69,4864,4865],{"class":1104},", captureAPI)\n",[69,4867,4868,4870,4872,4874,4877],{"class":71,"line":91},[69,4869,4854],{"class":1104},[69,4871,4857],{"class":1179},[69,4873,1183],{"class":1104},[69,4875,4876],{"class":1111},"'electron'",[69,4878,4879],{"class":1104},", electronAPI)\n",[69,4881,4882,4884,4886,4888,4891],{"class":71,"line":97},[69,4883,4854],{"class":1104},[69,4885,4857],{"class":1179},[69,4887,1183],{"class":1104},[69,4889,4890],{"class":1111},"'api'",[69,4892,4893],{"class":1104},", api)\n",[69,4895,4896,4898,4900],{"class":71,"line":103},[69,4897,4076],{"class":1104},[69,4899,3745],{"class":1100},[69,4901,4902],{"class":1104}," (error) {\n",[69,4904,4905,4907,4909,4911,4914],{"class":71,"line":109},[69,4906,1454],{"class":1104},[69,4908,1457],{"class":1179},[69,4910,1183],{"class":1104},[69,4912,4913],{"class":1111},"'[preload] expose failed:'",[69,4915,4916],{"class":1104},", error)\n",[69,4918,4919],{"class":71,"line":115},[69,4920,1487],{"class":1104},[69,4922,4923,4925,4928],{"class":71,"line":121},[69,4924,3742],{"class":1104},[69,4926,4927],{"class":1100},"else",[69,4929,3643],{"class":1104},[69,4931,4932,4935,4937],{"class":71,"line":126},[69,4933,4934],{"class":1104},"  window.captureAPI ",[69,4936,3964],{"class":1100},[69,4938,4939],{"class":1104}," captureAPI\n",[69,4941,4942,4945,4947],{"class":71,"line":132},[69,4943,4944],{"class":1104},"  window.electron ",[69,4946,3964],{"class":1100},[69,4948,4949],{"class":1104}," electronAPI\n",[69,4951,4952,4955,4957],{"class":71,"line":137},[69,4953,4954],{"class":1104},"  window.api ",[69,4956,3964],{"class":1100},[69,4958,4959],{"class":1104}," api\n",[69,4961,4962],{"class":71,"line":143},[69,4963,1023],{"class":1104},[14,4965,4966],{},"我们使用了exposeInMainWorld，渲染进程的网页拿不到node，也不能访问preload里面的变量，必须用contextBridge.exposeInMainWorld把一组经过允许的函数\u002F对象 代理到window上，才能让页面使用window.captureAPI等。",[60,4968,4972],{"className":4969,"code":4970,"language":4971,"meta":65,"style":65},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup>\nimport { onMounted, onUnmounted, ref } from 'vue'\nconst screenCanvas = ref(null)\nlet timerId = null\n\n  \nonMounted(async () => {\n  const canvas = screenCanvas.value\n  const ctx = canvas.getContext('2d')\n  try {\n    await window.captureAPI.start()\n  } catch (e) {\n    console.error('capture start failed', e)\n    return\n  }\n\n  const size = await window.captureAPI.getSize()\n  if (!size || size.width === 0) {\n    console.error('无法获取显示器尺寸')\n    return\n  }\n  canvas.width = size.width\n  canvas.height = size.height\n  const imgData = ctx.createImageData(size.width, size.height)\n\n  \u002F\u002F 原生已输出 RGBA；preload 调 getFrame。先约 15fps，后续可降采样 \u002F SharedArrayBuffer\n  const tick = async () => {\n    try {\n      const buffer = await window.captureAPI.getFrame()\n      if (buffer) {\n        imgData.data.set(new Uint8Array(buffer))\n        ctx.putImageData(imgData, 0, 0)\n      }\n    } catch (e) {\n      console.error('getFrame', e)\n    }\n  }\n  timerId = window.setInterval(tick, 66)\n})\n\n  \nonUnmounted(async () => {\n  if (timerId != null) window.clearInterval(timerId)\n  try {\n    await window.captureAPI.stop()\n  } catch (e) {\n    console.error('stop', e)\n  }\n})\n\u003C\u002Fscript>\n\n  \n\n\u003Ctemplate>\n  \u003Cdiv class=\"capture-container\">\n    \u003Ccanvas ref=\"screenCanvas\" class=\"screen-render\">\u003C\u002Fcanvas>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n  \n\n\u003Cstyle scoped>\n.capture-container {\n  width: 100%;\n  height: 100vh;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  background: #000;\n}\n.screen-render {\n  max-width: 100%;\n  max-height: 100%;\n  object-fit: contain;\n}\n\u003C\u002Fstyle>\n","vue",[21,4973,4974,4988,5000,5019,5031,5035,5039,5056,5068,5090,5096,5108,5116,5129,5134,5138,5142,5159,5181,5194,5198,5202,5212,5222,5240,5244,5249,5267,5274,5292,5300,5319,5338,5343,5352,5366,5371,5375,5396,5401,5405,5409,5424,5446,5452,5462,5470,5483,5487,5491,5500,5504,5508,5512,5521,5539,5568,5577,5585,5589,5593,5597,5609,5616,5633,5647,5659,5671,5682,5694,5698,5705,5718,5731,5743,5747],{"__ignoreMap":65},[69,4975,4976,4979,4983,4986],{"class":71,"line":72},[69,4977,4978],{"class":1104},"\u003C",[69,4980,4982],{"class":4981},"s9eBZ","script",[69,4984,4985],{"class":1179}," setup",[69,4987,2455],{"class":1104},[69,4989,4990,4992,4995,4997],{"class":71,"line":78},[69,4991,1101],{"class":1100},[69,4993,4994],{"class":1104}," { onMounted, onUnmounted, ref } ",[69,4996,1108],{"class":1100},[69,4998,4999],{"class":1111}," 'vue'\n",[69,5001,5002,5004,5007,5009,5012,5014,5017],{"class":71,"line":85},[69,5003,1169],{"class":1100},[69,5005,5006],{"class":1172}," screenCanvas",[69,5008,1176],{"class":1100},[69,5010,5011],{"class":1179}," ref",[69,5013,1183],{"class":1104},[69,5015,5016],{"class":1172},"null",[69,5018,1243],{"class":1104},[69,5020,5021,5024,5027,5029],{"class":71,"line":91},[69,5022,5023],{"class":1100},"let",[69,5025,5026],{"class":1104}," timerId ",[69,5028,3964],{"class":1100},[69,5030,3990],{"class":1172},[69,5032,5033],{"class":71,"line":97},[69,5034,82],{"emptyLinePlaceholder":81},[69,5036,5037],{"class":71,"line":103},[69,5038,1500],{"class":1104},[69,5040,5041,5044,5046,5049,5052,5054],{"class":71,"line":109},[69,5042,5043],{"class":1179},"onMounted",[69,5045,1183],{"class":1104},[69,5047,5048],{"class":1100},"async",[69,5050,5051],{"class":1104}," () ",[69,5053,1682],{"class":1100},[69,5055,3643],{"class":1104},[69,5057,5058,5060,5063,5065],{"class":71,"line":115},[69,5059,1401],{"class":1100},[69,5061,5062],{"class":1172}," canvas",[69,5064,1176],{"class":1100},[69,5066,5067],{"class":1104}," screenCanvas.value\n",[69,5069,5070,5072,5075,5077,5080,5083,5085,5088],{"class":71,"line":121},[69,5071,1401],{"class":1100},[69,5073,5074],{"class":1172}," ctx",[69,5076,1176],{"class":1100},[69,5078,5079],{"class":1104}," canvas.",[69,5081,5082],{"class":1179},"getContext",[69,5084,1183],{"class":1104},[69,5086,5087],{"class":1111},"'2d'",[69,5089,1243],{"class":1104},[69,5091,5092,5094],{"class":71,"line":126},[69,5093,4051],{"class":1100},[69,5095,3643],{"class":1104},[69,5097,5098,5101,5104,5106],{"class":71,"line":132},[69,5099,5100],{"class":1100},"    await",[69,5102,5103],{"class":1104}," window.captureAPI.",[69,5105,3664],{"class":1179},[69,5107,4132],{"class":1104},[69,5109,5110,5112,5114],{"class":71,"line":137},[69,5111,4076],{"class":1104},[69,5113,3745],{"class":1100},[69,5115,4081],{"class":1104},[69,5117,5118,5120,5122,5124,5127],{"class":71,"line":143},[69,5119,1454],{"class":1104},[69,5121,1457],{"class":1179},[69,5123,1183],{"class":1104},[69,5125,5126],{"class":1111},"'capture start failed'",[69,5128,4095],{"class":1104},[69,5130,5131],{"class":71,"line":149},[69,5132,5133],{"class":1100},"    return\n",[69,5135,5136],{"class":71,"line":155},[69,5137,1487],{"class":1104},[69,5139,5140],{"class":71,"line":161},[69,5141,82],{"emptyLinePlaceholder":81},[69,5143,5144,5146,5148,5150,5153,5155,5157],{"class":71,"line":166},[69,5145,1401],{"class":1100},[69,5147,3694],{"class":1172},[69,5149,1176],{"class":1100},[69,5151,5152],{"class":1100}," await",[69,5154,5103],{"class":1104},[69,5156,3701],{"class":1179},[69,5158,4132],{"class":1104},[69,5160,5161,5163,5165,5167,5170,5172,5175,5177,5179],{"class":71,"line":172},[69,5162,1429],{"class":1100},[69,5164,1614],{"class":1104},[69,5166,1704],{"class":1100},[69,5168,5169],{"class":1104},"size ",[69,5171,1710],{"class":1100},[69,5173,5174],{"class":1104}," size.width ",[69,5176,1328],{"class":1100},[69,5178,1447],{"class":1172},[69,5180,1370],{"class":1104},[69,5182,5183,5185,5187,5189,5192],{"class":71,"line":178},[69,5184,1454],{"class":1104},[69,5186,1457],{"class":1179},[69,5188,1183],{"class":1104},[69,5190,5191],{"class":1111},"'无法获取显示器尺寸'",[69,5193,1243],{"class":1104},[69,5195,5196],{"class":71,"line":184},[69,5197,5133],{"class":1100},[69,5199,5200],{"class":71,"line":189},[69,5201,1487],{"class":1104},[69,5203,5204,5207,5209],{"class":71,"line":195},[69,5205,5206],{"class":1104},"  canvas.width ",[69,5208,3964],{"class":1100},[69,5210,5211],{"class":1104}," size.width\n",[69,5213,5214,5217,5219],{"class":71,"line":201},[69,5215,5216],{"class":1104},"  canvas.height ",[69,5218,3964],{"class":1100},[69,5220,5221],{"class":1104}," size.height\n",[69,5223,5224,5226,5229,5231,5234,5237],{"class":71,"line":207},[69,5225,1401],{"class":1100},[69,5227,5228],{"class":1172}," imgData",[69,5230,1176],{"class":1100},[69,5232,5233],{"class":1104}," ctx.",[69,5235,5236],{"class":1179},"createImageData",[69,5238,5239],{"class":1104},"(size.width, size.height)\n",[69,5241,5242],{"class":71,"line":213},[69,5243,82],{"emptyLinePlaceholder":81},[69,5245,5246],{"class":71,"line":218},[69,5247,5248],{"class":1069},"  \u002F\u002F 原生已输出 RGBA；preload 调 getFrame。先约 15fps，后续可降采样 \u002F SharedArrayBuffer\n",[69,5250,5251,5253,5256,5258,5261,5263,5265],{"class":71,"line":224},[69,5252,1401],{"class":1100},[69,5254,5255],{"class":1179}," tick",[69,5257,1176],{"class":1100},[69,5259,5260],{"class":1100}," async",[69,5262,5051],{"class":1104},[69,5264,1682],{"class":1100},[69,5266,3643],{"class":1104},[69,5268,5269,5272],{"class":71,"line":230},[69,5270,5271],{"class":1100},"    try",[69,5273,3643],{"class":1104},[69,5275,5276,5279,5282,5284,5286,5288,5290],{"class":71,"line":236},[69,5277,5278],{"class":1100},"      const",[69,5280,5281],{"class":1172}," buffer",[69,5283,1176],{"class":1100},[69,5285,5152],{"class":1100},[69,5287,5103],{"class":1104},[69,5289,4483],{"class":1179},[69,5291,4132],{"class":1104},[69,5293,5294,5297],{"class":71,"line":241},[69,5295,5296],{"class":1100},"      if",[69,5298,5299],{"class":1104}," (buffer) {\n",[69,5301,5302,5305,5308,5310,5313,5316],{"class":71,"line":247},[69,5303,5304],{"class":1104},"        imgData.data.",[69,5306,5307],{"class":1179},"set",[69,5309,1183],{"class":1104},[69,5311,5312],{"class":1100},"new",[69,5314,5315],{"class":1179}," Uint8Array",[69,5317,5318],{"class":1104},"(buffer))\n",[69,5320,5321,5324,5327,5330,5332,5334,5336],{"class":71,"line":253},[69,5322,5323],{"class":1104},"        ctx.",[69,5325,5326],{"class":1179},"putImageData",[69,5328,5329],{"class":1104},"(imgData, ",[69,5331,4395],{"class":1172},[69,5333,1364],{"class":1104},[69,5335,4395],{"class":1172},[69,5337,1243],{"class":1104},[69,5339,5340],{"class":71,"line":259},[69,5341,5342],{"class":1104},"      }\n",[69,5344,5345,5348,5350],{"class":71,"line":264},[69,5346,5347],{"class":1104},"    } ",[69,5349,3745],{"class":1100},[69,5351,4081],{"class":1104},[69,5353,5354,5357,5359,5361,5364],{"class":71,"line":270},[69,5355,5356],{"class":1104},"      console.",[69,5358,1457],{"class":1179},[69,5360,1183],{"class":1104},[69,5362,5363],{"class":1111},"'getFrame'",[69,5365,4095],{"class":1104},[69,5367,5368],{"class":71,"line":276},[69,5369,5370],{"class":1104},"    }\n",[69,5372,5373],{"class":71,"line":282},[69,5374,1487],{"class":1104},[69,5376,5377,5380,5382,5385,5388,5391,5394],{"class":71,"line":287},[69,5378,5379],{"class":1104},"  timerId ",[69,5381,3964],{"class":1100},[69,5383,5384],{"class":1104}," window.",[69,5386,5387],{"class":1179},"setInterval",[69,5389,5390],{"class":1104},"(tick, ",[69,5392,5393],{"class":1172},"66",[69,5395,1243],{"class":1104},[69,5397,5398],{"class":71,"line":293},[69,5399,5400],{"class":1104},"})\n",[69,5402,5403],{"class":71,"line":299},[69,5404,82],{"emptyLinePlaceholder":81},[69,5406,5407],{"class":71,"line":304},[69,5408,1500],{"class":1104},[69,5410,5411,5414,5416,5418,5420,5422],{"class":71,"line":310},[69,5412,5413],{"class":1179},"onUnmounted",[69,5415,1183],{"class":1104},[69,5417,5048],{"class":1100},[69,5419,5051],{"class":1104},[69,5421,1682],{"class":1100},[69,5423,3643],{"class":1104},[69,5425,5426,5428,5431,5434,5437,5440,5443],{"class":71,"line":316},[69,5427,1429],{"class":1100},[69,5429,5430],{"class":1104}," (timerId ",[69,5432,5433],{"class":1100},"!=",[69,5435,5436],{"class":1172}," null",[69,5438,5439],{"class":1104},") window.",[69,5441,5442],{"class":1179},"clearInterval",[69,5444,5445],{"class":1104},"(timerId)\n",[69,5447,5448,5450],{"class":71,"line":321},[69,5449,4051],{"class":1100},[69,5451,3643],{"class":1104},[69,5453,5454,5456,5458,5460],{"class":71,"line":327},[69,5455,5100],{"class":1100},[69,5457,5103],{"class":1104},[69,5459,4318],{"class":1179},[69,5461,4132],{"class":1104},[69,5463,5464,5466,5468],{"class":71,"line":333},[69,5465,4076],{"class":1104},[69,5467,3745],{"class":1100},[69,5469,4081],{"class":1104},[69,5471,5472,5474,5476,5478,5481],{"class":71,"line":339},[69,5473,1454],{"class":1104},[69,5475,1457],{"class":1179},[69,5477,1183],{"class":1104},[69,5479,5480],{"class":1111},"'stop'",[69,5482,4095],{"class":1104},[69,5484,5485],{"class":71,"line":345},[69,5486,1487],{"class":1104},[69,5488,5489],{"class":71,"line":801},[69,5490,5400],{"class":1104},[69,5492,5493,5496,5498],{"class":71,"line":807},[69,5494,5495],{"class":1104},"\u003C\u002F",[69,5497,4982],{"class":4981},[69,5499,2455],{"class":1104},[69,5501,5502],{"class":71,"line":813},[69,5503,82],{"emptyLinePlaceholder":81},[69,5505,5506],{"class":71,"line":819},[69,5507,1500],{"class":1104},[69,5509,5510],{"class":71,"line":825},[69,5511,82],{"emptyLinePlaceholder":81},[69,5513,5514,5516,5519],{"class":71,"line":830},[69,5515,4978],{"class":1104},[69,5517,5518],{"class":4981},"template",[69,5520,2455],{"class":1104},[69,5522,5523,5526,5529,5532,5534,5537],{"class":71,"line":835},[69,5524,5525],{"class":1104},"  \u003C",[69,5527,5528],{"class":4981},"div",[69,5530,5531],{"class":1179}," class",[69,5533,3964],{"class":1104},[69,5535,5536],{"class":1111},"\"capture-container\"",[69,5538,2455],{"class":1104},[69,5540,5541,5544,5547,5549,5551,5554,5556,5558,5561,5564,5566],{"class":71,"line":841},[69,5542,5543],{"class":1104},"    \u003C",[69,5545,5546],{"class":4981},"canvas",[69,5548,5011],{"class":1179},[69,5550,3964],{"class":1104},[69,5552,5553],{"class":1111},"\"screenCanvas\"",[69,5555,5531],{"class":1179},[69,5557,3964],{"class":1104},[69,5559,5560],{"class":1111},"\"screen-render\"",[69,5562,5563],{"class":1104},">\u003C\u002F",[69,5565,5546],{"class":4981},[69,5567,2455],{"class":1104},[69,5569,5570,5573,5575],{"class":71,"line":846},[69,5571,5572],{"class":1104},"  \u003C\u002F",[69,5574,5528],{"class":4981},[69,5576,2455],{"class":1104},[69,5578,5579,5581,5583],{"class":71,"line":852},[69,5580,5495],{"class":1104},[69,5582,5518],{"class":4981},[69,5584,2455],{"class":1104},[69,5586,5587],{"class":71,"line":858},[69,5588,82],{"emptyLinePlaceholder":81},[69,5590,5591],{"class":71,"line":863},[69,5592,1500],{"class":1104},[69,5594,5595],{"class":71,"line":869},[69,5596,82],{"emptyLinePlaceholder":81},[69,5598,5599,5601,5604,5607],{"class":71,"line":874},[69,5600,4978],{"class":1104},[69,5602,5603],{"class":4981},"style",[69,5605,5606],{"class":1179}," scoped",[69,5608,2455],{"class":1104},[69,5610,5611,5614],{"class":71,"line":880},[69,5612,5613],{"class":1179},".capture-container",[69,5615,3643],{"class":1104},[69,5617,5618,5621,5624,5627,5630],{"class":71,"line":886},[69,5619,5620],{"class":1172},"  width",[69,5622,5623],{"class":1104},": ",[69,5625,5626],{"class":1172},"100",[69,5628,5629],{"class":1100},"%",[69,5631,5632],{"class":1104},";\n",[69,5634,5635,5638,5640,5642,5645],{"class":71,"line":892},[69,5636,5637],{"class":1172},"  height",[69,5639,5623],{"class":1104},[69,5641,5626],{"class":1172},[69,5643,5644],{"class":1100},"vh",[69,5646,5632],{"class":1104},[69,5648,5649,5652,5654,5657],{"class":71,"line":898},[69,5650,5651],{"class":1172},"  display",[69,5653,5623],{"class":1104},[69,5655,5656],{"class":1172},"flex",[69,5658,5632],{"class":1104},[69,5660,5661,5664,5666,5669],{"class":71,"line":904},[69,5662,5663],{"class":1172},"  justify-content",[69,5665,5623],{"class":1104},[69,5667,5668],{"class":1172},"center",[69,5670,5632],{"class":1104},[69,5672,5673,5676,5678,5680],{"class":71,"line":910},[69,5674,5675],{"class":1172},"  align-items",[69,5677,5623],{"class":1104},[69,5679,5668],{"class":1172},[69,5681,5632],{"class":1104},[69,5683,5684,5687,5689,5692],{"class":71,"line":916},[69,5685,5686],{"class":1172},"  background",[69,5688,5623],{"class":1104},[69,5690,5691],{"class":1172},"#000",[69,5693,5632],{"class":1104},[69,5695,5696],{"class":71,"line":922},[69,5697,1023],{"class":1104},[69,5699,5700,5703],{"class":71,"line":927},[69,5701,5702],{"class":1179},".screen-render",[69,5704,3643],{"class":1104},[69,5706,5707,5710,5712,5714,5716],{"class":71,"line":933},[69,5708,5709],{"class":1172},"  max-width",[69,5711,5623],{"class":1104},[69,5713,5626],{"class":1172},[69,5715,5629],{"class":1100},[69,5717,5632],{"class":1104},[69,5719,5720,5723,5725,5727,5729],{"class":71,"line":938},[69,5721,5722],{"class":1172},"  max-height",[69,5724,5623],{"class":1104},[69,5726,5626],{"class":1172},[69,5728,5629],{"class":1100},[69,5730,5632],{"class":1104},[69,5732,5733,5736,5738,5741],{"class":71,"line":943},[69,5734,5735],{"class":1172},"  object-fit",[69,5737,5623],{"class":1104},[69,5739,5740],{"class":1172},"contain",[69,5742,5632],{"class":1104},[69,5744,5745],{"class":71,"line":949},[69,5746,1023],{"class":1104},[69,5748,5749,5751,5753],{"class":71,"line":954},[69,5750,5495],{"class":1104},[69,5752,5603],{"class":4981},[69,5754,2455],{"class":1104},[14,5756,5757,5758,5763,5764,419,5766,490,5768,5770,5771,5773,5774,5779],{},"之后就可以正确显示了！当前仓库里的 ",[26,5759,5760],{},[21,5761,5762],{},"App.vue"," 在同一份思路上又加了：录制路径、",[21,5765,527],{},[21,5767,530],{},[21,5769,539],{}," 下拉选播放设备、",[21,5772,545],{}," 传给原生等——仍以 ",[26,5775,5776],{},[21,5777,5778],{},"capture\u002Fsrc\u002Frenderer\u002Fsrc\u002FApp.vue"," 为准，笔记里就不重复贴一整套表单了。",[5781,5782],"hr",{},[5603,5784,5785],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":65,"searchDepth":78,"depth":78,"links":5787},[5788,5789],{"id":12,"depth":78,"text":12},{"id":3877,"depth":78,"text":3877},"md",{},"\u002Fcpp\u002Fwgc捕获迁移到electron",{"description":65},"cpp\u002FWGC捕获迁移到Electron","XoGXH1xjM9mWR9eamnYvICUFJf_Gc9H8R8aHnTxl2to",1791042464282]