Skip to content

electron VLC Player Video 技术调研

支持 Windows

支持 Mac

如何获取 path_to_vlc_sdk

path_to_vlc_sdk 是指您机器上 VLC SDK(即 libVLC)的路径。如果您正在使用 Windows,您需要下载 VLC SDK 并将其解压到您的机器上。

以下是如何获取 VLC SDK:

1. 下载 VLC Windows SDK

前往 VLC 官方下载页面: https://get.videolan.org/vlc/

根据您的需求选择相应的 VLC 版本。例如,选择 3.0.9.2(或更高的版本)。在版本页面下方,您会看到 Win32Win64 的下载链接。根据您的应用程序(32 位或 64 位)选择相应的链接。

比如下载这个文件:https://get.videolan.org/vlc/3.0.9.2/win64/vlc-3.0.9.2-win64.7z

在相应的页面,找到“SDK 包”链接,点击它以下载 ZIP 文件。

2. 解压缩 VLC SDK

一旦下载完成,解压 ZIP 文件到适当的位置,例如 C:\vlc-sdk\

3. 更新 binding.gyp 文件

现在,您知道了 SDK 的实际路径,您可以在 binding.gyp 文件中相应地替换 path_to_vlc_sdk。例如:

json
"include_dirs": [
  "C:/vlc-sdk/include"
],
"libraries": [
  "-LC:/vlc-sdk/lib",
  "-lvlc"
]

请注意,您应该使用正斜杠 /,即使是在 Windows 中,因为 GYP 脚本通常期望这样的路径分隔符。

现在,您应该能够正确链接到 VLC SDK 并构建您的 Node.js 扩展。

https://chat.openai.com/c/46ceba4e-1631-4321-bade-5a2fb03527cf

GetPlayerState

LibVLC 为播放器的状态定义了多个枚举值。这里是完整的可能的状态列表,以及它们的简短描述:

  1. libvlc_NothingSpecial: 无特定状态,播放器初始化时的默认状态。
  2. libvlc_Opening: 正在打开媒体。
  3. libvlc_Buffering: 正在缓冲。
  4. libvlc_Playing: 媒体正在播放。
  5. libvlc_Paused: 媒体已暂停。
  6. libvlc_Stopped: 媒体已停止。
  7. libvlc_Ended: 媒体播放完毕。
  8. libvlc_Error: 播放出错。

你可以在上面给出的GetPlayerState函数中,使用switch语句来处理所有这些状态,为每个状态返回一个相应的字符串值。例如:

cpp
switch (state) {
    case libvlc_NothingSpecial:
        stateString = String::NewFromUtf8(isolate, "nothing-special", v8::NewStringType::kNormal).ToLocalChecked();
        break;
    case libvlc_Opening:
        stateString = String::NewFromUtf8(isolate, "opening", v8::NewStringType::kNormal).ToLocalChecked();
        break;
    case libvlc_Buffering:
        stateString = String::NewFromUtf8(isolate, "buffering", v8::NewStringType::kNormal).ToLocalChecked();
        break;
    case libvlc_Playing:
        stateString = String::NewFromUtf8(isolate, "playing", v8::NewStringType::kNormal).ToLocalChecked();
        break;
    case libvlc_Paused:
        stateString = String::NewFromUtf8(isolate, "paused", v8::NewStringType::kNormal).ToLocalChecked();
        break;
    case libvlc_Stopped:
        stateString = String::NewFromUtf8(isolate, "stopped", v8::NewStringType::kNormal).ToLocalChecked();
        break;
    case libvlc_Ended:
        stateString = String::NewFromUtf8(isolate, "ended", v8::NewStringType::kNormal).ToLocalChecked();
        break;
    case libvlc_Error:
        stateString = String::NewFromUtf8(isolate, "error", v8::NewStringType::kNormal).ToLocalChecked();
        break;
    default:
        stateString = String::NewFromUtf8(isolate, "unknown", v8::NewStringType::kNormal).ToLocalChecked();
}

获取和设置 当前的播放速度

你可以使用以下两个 LibVLC 函数来获取和设置当前的播放速度:

  1. libvlc_media_player_get_rate:获取当前的播放速度。
  2. libvlc_media_player_set_rate:设置播放速度。

以下是如何在main.cc中将这两个功能暴露为 Node.js 的扩展方法:

cpp
void GetPlaybackRate(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();

    if (!mp) {
        isolate->ThrowException(v8::Exception::Error(
            String::NewFromUtf8(isolate, "Media player not initialized.", v8::NewStringType::kNormal).ToLocalChecked()
        ));
        return;
    }

    float rate = libvlc_media_player_get_rate(mp);
    args.GetReturnValue().Set(v8::Number::New(isolate, static_cast<double>(rate)));
}

void SetPlaybackRate(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();

    if (args.Length() < 1 || !args[0]->IsNumber()) {
        isolate->ThrowException(v8::Exception::TypeError(
            String::NewFromUtf8(isolate, "Expected playback rate as a number.", v8::NewStringType::kNormal).ToLocalChecked()
        ));
        return;
    }

    if (!mp) {
        isolate->ThrowException(v8::Exception::Error(
            String::NewFromUtf8(isolate, "Media player not initialized.", v8::NewStringType::kNormal).ToLocalChecked()
        ));
        return;
    }

    float rate = static_cast<float>(args[0]->NumberValue(isolate->GetCurrentContext()).ToChecked());
    libvlc_media_player_set_rate(mp, rate);
}

// ... 在 Initialize 函数内部

NODE_SET_METHOD(exports, "getPlaybackRate", GetPlaybackRate);
NODE_SET_METHOD(exports, "setPlaybackRate", SetPlaybackRate);

这样,你就可以在 Electron 应用的 JavaScript 端调用这些方法来获取和设置当前的播放速度了。例如:

javascript
const rate = vlcAddon.getPlaybackRate();
vlcAddon.setPlaybackRate(1.5); // 设置为1.5倍速

注意:设置的播放速度应该在合理的范围内,太快或太慢可能会导致播放效果不佳。

PlayMedia 介绍

cpp
void PlayMedia(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();

    // Verify if the first parameter is a string (representing media path or URL)
    if (args.Length() < 1 || !args[0]->IsString()) {
        isolate->ThrowException(v8::Exception::TypeError(
            String::NewFromUtf8(isolate, "Expected a media path string.", v8::NewStringType::kNormal).ToLocalChecked()
        ));
        return;
    }

    v8::String::Utf8Value utf8Value(isolate, args[0]);
    std::string mediaPath = std::string(*utf8Value);

    libvlc_media_t* media;
    if (mediaPath.substr(0, 7) == "http://" || mediaPath.substr(0, 8) == "https://") {
        media = libvlc_media_new_location(inst, mediaPath.c_str());

        // If the second parameter is provided and it is an object,
        // then we assume it contains HTTP request header information
        if (args.Length() > 1 && args[1]->IsObject()) {
            Local<v8::Object> headersObj = args[1].As<v8::Object>();
            Local<v8::Array> propertyNames = headersObj->GetOwnPropertyNames(isolate->GetCurrentContext()).ToLocalChecked();
            for (uint32_t i = 0; i < propertyNames->Length(); i++) {
                Local<Value> key = propertyNames->Get(isolate->GetCurrentContext(), i).ToLocalChecked();
                Local<Value> value = headersObj->Get(isolate->GetCurrentContext(), key).ToLocalChecked();
                v8::String::Utf8Value keyStr(isolate, key);
                v8::String::Utf8Value valueStr(isolate, value);
                std::string httpHeader = ":http-header=" + std::string(*keyStr) + ": " + std::string(*valueStr);
                libvlc_media_add_option(media, httpHeader.c_str());
            }
        }
    } else {
        media = libvlc_media_new_path(inst, mediaPath.c_str());
    }

    if (!inst) {
        inst = libvlc_new(0, NULL);
        if (!inst) {
            isolate->ThrowException(v8::Exception::Error(
                String::NewFromUtf8(isolate, "Failed to initialize libVLC.", v8::NewStringType::kNormal).ToLocalChecked()
            ));
            return;
        }
    }

    if (!mp) {
        mp = libvlc_media_player_new(inst);
        if (!mp) {
            isolate->ThrowException(v8::Exception::Error(
                String::NewFromUtf8(isolate, "Failed to create media player.", v8::NewStringType::kNormal).ToLocalChecked()
            ));
            return;
        }
    } else {
        libvlc_media_player_stop(mp);
    }

    libvlc_media_player_set_media(mp, media);
    libvlc_media_player_play(mp);
    libvlc_media_release(media);  // We release here, media player increases ref count when setting
}

调用方式:

  1. 播放本地文件

    javascript
    vlcAddon.playMedia("D:\\path_to_your_file.mp4");
  2. 播放 HTTP/HTTPS URL(不带请求头)

    javascript
    vlcAddon.playMedia("http://example.com/video.mp4");
  3. 播放 HTTP/HTTPS URL(带请求头)

    javascript
    vlcAddon.playMedia("http://example.com/video.mp4", {
      Authorization: "Bearer YOUR_TOKEN_HERE",
      AnotherHeader: "HeaderValue",
    });

这样,你可以使用 PlayMedia 函数播放本地文件,或者通过 HTTP/HTTPS 播放媒体,并选择性地提供请求头信息。

PauseMedia

cpp
void PauseMedia(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    bool shouldToggle = true; // 默认为切换播放/暂停
    bool shouldPause = false;

    // 如果有参数并且为布尔值,则读取其值
    if (args.Length() >= 1 && args[0]->IsBoolean()) {
        shouldToggle = false;
        shouldPause = args[0]->BooleanValue(isolate);
    }

    if (mp) {
        if (shouldToggle) {
            if (libvlc_media_player_is_playing(mp)) {
                libvlc_media_player_pause(mp);
            } else {
                libvlc_media_player_play(mp);
            }
        } else {
            if (shouldPause) {
                if (libvlc_media_player_is_playing(mp)) {
                    libvlc_media_player_pause(mp);
                }
            } else {
                if (libvlc_media_player_get_state(mp) == libvlc_Ended) {
                    libvlc_media_player_stop(mp);
                    libvlc_media_player_play(mp);
                } else if (!libvlc_media_player_is_playing(mp)) {
                    libvlc_media_player_play(mp);
                }
            }
        }
    }
}

这样,不传入参数时,它会切换播放/暂停状态,如下:

javascript
vlcAddon.pauseMedia(); // 切换播放/暂停状态

而当传入truefalse参数时,它将暂停或播放视频,如下:

javascript
vlcAddon.pauseMedia(true); // 暂停
vlcAddon.pauseMedia(false); // 播放

IsMediaEnded

要将IsMediaEnded函数暴露给 Node.js,您需要遵循与其他函数相同的模式,即创建一个包装该函数的新 V8 函数并在模块初始化时将其导出。

这里是如何实现的:

  1. 首先,修改IsMediaEnded函数以使用 V8 的参数和返回值模式:
cpp
void IsMediaEnded(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();

    bool ended = false;

    if (mp) {
        ended = libvlc_media_player_get_state(mp) == libvlc_Ended;
    }

    args.GetReturnValue().Set(v8::Boolean::New(isolate, ended));
}
  1. 在模块的初始化部分,将这个新函数添加到导出的对象上:
cpp
void Initialize(Local<Object> exports) {
    NODE_SET_METHOD(exports, "playMedia", PlayMedia);
    NODE_SET_METHOD(exports, "pauseMedia", PauseMedia);
    // ... 其他方法 ...

    NODE_SET_METHOD(exports, "isMediaEnded", IsMediaEnded); // 这是新添加的方法
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

完成以上步骤后,您应该能够在 Node.js 中像调用其他函数一样调用isMediaEnded函数,例如:

javascript
const vlcAddon = require("./build/Release/vlc_node_extension");

let ended = vlcAddon.isMediaEnded();
console.log("Is media ended?", ended);

请确保已经在文件的顶部包含了 V8 的所有相关头文件,如:

cpp
#include <node.h>
#include <v8.h>

注意:为了确保一切正常,您可能还需要重新编译 addon。

如何将外部的应用嵌入到 electron 应用中

检索关键字:Electron NativeView overlay

https://github.com/electron/electron/issues/10547

json
"build": {
    "extraResources": [
      {
        "from": "./vlc-3.0.9.2",
        "to": "vlc-3.0.9.2"
      },
      {
        "from": "./bin/win32-x64-116",
        "to": "vlc-3.0.9.2"
      }
    ],
    "files": [
      "**/*",
      "!bin/**/*",
      "!build/**/*",
      "!vlc-3.0.9.2/**/*",
      "!path_to_some_media_file.mp4"
    ]
  },

electron rebuild 生成 .node 文件

https://www.npmjs.com/package/@electron/rebuild

powershell
npm install --save-dev @electron/rebuild
powershell
.\node_modules\.bin\electron-rebuild.cmd

Mac 中如何开发

https://get.videolan.org/vlc/3.0.9.2/macosx/

  1. 点击 下载 Mac VLC 安装包

https://get.videolan.org/vlc/3.0.9.2/macosx/vlc-3.0.9.2.dmg

  1. binding.gyp 中指定 vlc 的 sdk 路径
json
{
  "targets": [
    {
      "target_name": "vlc_node_extension",
      "sources": ["main.cc"],
      "conditions": [
        [
          "OS=='win'",
          {
            "include_dirs": [
              "C:/Users/Administrator/Desktop/electron-vlc/vlc-3.0.9.2/sdk/include"
            ],
            "libraries": [
              "C:/Users/Administrator/Desktop/electron-vlc/vlc-3.0.9.2/sdk/lib/libvlc.lib",
              "C:/Users/Administrator/Desktop/electron-vlc/vlc-3.0.9.2/sdk/lib/libvlccore.lib"
            ]
          }
        ],
        [
          "OS=='mac'",
          {
            "include_dirs": ["/Applications/VLC.app/Contents/MacOS/include"],
            "libraries": [
              "-L/Applications/VLC.app/Contents/MacOS/lib",
              "-lvlc",
              "-lvlccore"
            ],
            "xcode_settings": {
              "OTHER_LDFLAGS": ["-framework CoreFoundation", "-framework Cocoa"]
            }
          }
        ],
        [
          "OS=='linux'",
          {
            "include_dirs": ["<!@(pkg-config --cflags libvlc)"],
            "libraries": ["<!@(pkg-config --libs libvlc)"]
          }
        ]
      ]
    }
  ]
}
  1. 运行下面命令 生成 .node 文件

npm run rebuild

生成 binbuild 目录就可以了

  1. 主进程中 配置 vlc 的路径

  2. 更新 xcode

bash
dyld[60482]: missing symbol called
[60485:1012/195958.046143:ERROR:child_thread_impl.cc(235)] Invalid PlatformChannel receive right
/Users/shaohai.li/project/electron-vlc/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron exited with signal SIGABRT
shaohai.li@192 electron-vlc %