Skip to content

Node 使用 VLC 播放音视频开发文档 - 3

增加更多 libVLC 控制方法

定义暴露的 index.js 方法

js
const dllPath =
  "C:\\Users\\Administrator\\Desktop\\electron-vlc\\vlc-3.0.19-win64\\vlc-3.0.19";
process.env.PATH = dllPath + ";" + process.env.PATH;

const addon = require("./build/Release/vlc_node_extension.node");
console.log("addon", addon);

module.exports = {
  /**
   * 播放给定URL的HTTP流。
   * @param {string} url - 要播放的流的URL。
   * @param {number} [cache=1000] - 缓存大小,单位为毫秒。默认为1000ms。
   */
  playMedia: addon.playMedia,
  /**
   * 暂停或继续当前的媒体播放。
   */
  pauseOrResume: addon.pauseOrResume,
  /**
   * 设置播放器音量。
   * @param {number} volume - 播放器音量。
   */
  setVolume: addon.setVolume,
  /**
   * 获取播放器当前音量。
   * @returns {number} - 返回播放器当前音量。
   */
  getVolume: addon.getVolume,
  /**
   * 获取媒体的总长度。
   * @returns {number} - 返回媒体的总长度,单位为毫秒。
   */
  getMediaLength: addon.getMediaLength,
  /**
   * 获取当前播放时间。
   * @returns {number} - 返回当前播放时间,单位为毫秒。
   */
  getCurrentPlaybackTime: addon.getCurrentPlaybackTime,
  /**
   * 获取播放速率。
   * @returns {number} - 返回当前播放速率。
   */
  getPlaybackRate: addon.getPlaybackRate,
  /**
   * 设置播放速率。
   * @param {number} rate - 播放速率。
   */
  setPlaybackRate: addon.setPlaybackRate,
  /**
   * 获取播放器状态。
   * @returns {string} - 返回播放器的当前状态。可能的返回值有:
   * "nothing-special" - 没有特殊的播放活动。
   * "opening" - 媒体正在打开。
   * "buffering" - 媒体正在缓冲。
   * "playing" - 媒体正在播放。
   * "paused" - 媒体已暂停。
   * "stopped" - 播放器已停止。
   * "ended" - 媒体已播放完毕。
   * "error" - 播放过程中出现错误。
   * "unknown" - 未知的播放器状态(非常规情况)。
   */
  getPlayerState: addon.getPlayerState,
  /**
   * 获取媒体的当前播放进度。
   * @returns {number} - 返回媒体的播放进度,为0.0至1.0之间的浮点数,其中0.0表示播放开始,1.0表示播放结束。
   */
  getPlaybackProgress: addon.getPlaybackProgress,
  /**
   * 设置媒体的播放进度。
   * @param {number} position - 希望设置的播放进度,为0.0至1.0之间的浮点数,其中0.0表示播放开始,1.0表示播放结束。
   */
  setPlaybackProgress: addon.setPlaybackProgress,

  /**
   * 检查媒体是否已结束。
   * @returns {boolean} - 如果媒体已结束,返回true;否则返回false。
   */
  isMediaEnded: addon.isMediaEnded,
  /**
   * 设置是否启用日志记录。
   * @param {boolean} enable - 如果为true,则启用日志记录;如果为false,则禁用。
   */
  setLoggingEnabled: addon.setLoggingEnabled,
  /**
   * 设置日志文件的最大大小。
   * @param {number} size - 日志文件的最大大小,以字节为单位。
   */
  setMaxLogSize: addon.setMaxLogSize,
};

修改 addon.cc

cpp
#include <iostream>
#include <node.h>
#include <vlc/vlc.h>
#include <sys/stat.h>

using namespace v8;

FILE* log_file = nullptr;
const char* log_filename = "vlc_log.txt";
const char* old_log_filename = "vlc_log.old.txt";
const int default_max_log_size = 1 * 1024 * 1024; // 1MB
int max_log_size = default_max_log_size;
bool logging_enabled = true;

libvlc_media_player_t *mp = nullptr; // Make it global to control it from other functions.

void check_log_size_and_rotate() {
    struct stat st;
    if (fstat(fileno(log_file), &st) == 0 && st.st_size > max_log_size) {
        fclose(log_file);
        remove(old_log_filename);
        rename(log_filename, old_log_filename);
        log_file = fopen(log_filename, "w");
    }
}

void log_callback(void* data, int level, const libvlc_log_t* ctx, const char* fmt, va_list args) {
    if (log_file && logging_enabled) {
        vfprintf(log_file, fmt, args);
        fprintf(log_file, "\n");
        fflush(log_file);
        check_log_size_and_rotate();
    }
}

void SetLoggingEnabled(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    if (args.Length() < 1 || !args[0]->IsBoolean()) {
        isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Expected a boolean argument for logging status").ToLocalChecked()));
        return;
    }
    logging_enabled = args[0]->BooleanValue(isolate);
}

void SetMaxLogSize(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    if (args.Length() < 1 || !args[0]->IsNumber()) {
        isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Expected a number argument for max log size").ToLocalChecked()));
        return;
    }
    max_log_size = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust();
}

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

    if (args.Length() < 1 || !args[0]->IsString()) {
        isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Expected a string argument for the media path or URL").ToLocalChecked()));
        return;
    }

    String::Utf8Value mediaPath(isolate, args[0]);
    int cache = args[1]->IsNumber() ? args[1]->Int32Value(isolate->GetCurrentContext()).FromJust() : 1000; // Default 1000ms if applicable

    libvlc_instance_t *inst;
    libvlc_media_t *m;

    inst = libvlc_new(0, nullptr);
    libvlc_log_set(inst, log_callback, log_file); // Set log callback

    // Check if the provided media path is an HTTP URL or a local file
    if (strncmp(*mediaPath, "http://", 7) == 0 || strncmp(*mediaPath, "https://", 8) == 0) {
        m = libvlc_media_new_location(inst, *mediaPath);
        std::string cache_option = ":network-caching=" + std::to_string(cache);
        libvlc_media_add_option(m, cache_option.c_str());
    } else {
        m = libvlc_media_new_path(inst, *mediaPath);
    }

    mp = libvlc_media_player_new_from_media(m);
    libvlc_media_release(m);

    if (args.Length() > 2 && args[2]->IsNumber()) {
        uintptr_t hwnd = args[2]->IntegerValue(isolate->GetCurrentContext()).FromJust();
        libvlc_media_player_set_hwnd(mp, (void*)hwnd);
    }

    libvlc_media_player_play(mp);
    // Return the media player pointer as handle
    args.GetReturnValue().Set(Number::New(isolate, reinterpret_cast<uintptr_t>(mp)));
}

void PauseOrResume(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    bool isPlaying = false;

    if (mp) {
        if (libvlc_media_player_is_playing(mp)) {
            libvlc_media_player_pause(mp);
        } else {
            libvlc_media_player_play(mp);
            isPlaying = true;
        }
        fprintf(log_file, "PauseOrResume has mp: %p\n", mp);
        fflush(log_file);
    } else {
        fprintf(log_file, "PauseOrResume not mp: %p\n", mp);
        fflush(log_file);
    }

    // Return the playing status
    args.GetReturnValue().Set(Boolean::New(isolate, isPlaying));
}
void SetPlaybackProgress(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();

    if (args.Length() < 1 || !args[0]->IsNumber()) {
        isolate->ThrowException(v8::Exception::TypeError(
            String::NewFromUtf8(isolate, "Expected progress (as float between 0.0 and 1.0).", 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 progress = static_cast<float>(args[0]->NumberValue(isolate->GetCurrentContext()).ToChecked());

    if (progress < 0.0f || progress > 1.0f) {
        isolate->ThrowException(v8::Exception::RangeError(
            String::NewFromUtf8(isolate, "Progress value should be between 0.0 and 1.0.", v8::NewStringType::kNormal).ToLocalChecked()
        ));
        return;
    }

    libvlc_time_t length = libvlc_media_player_get_length(mp);
    libvlc_time_t targetTime = static_cast<libvlc_time_t>(progress * length);
    libvlc_media_player_set_time(mp, targetTime);
}
void GetPlaybackProgress(const FunctionCallbackInfo<v8::Value>& args) {
    Isolate* isolate = args.GetIsolate();
    float progress = 0.0f;
    if (mp) {
        progress = libvlc_media_player_get_position(mp);
    }
    args.GetReturnValue().Set(v8::Number::New(isolate, progress));
}
void SetVolume(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();

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

    int volume = args[0]->Int32Value(isolate->GetCurrentContext()).ToChecked();
    if (mp) {
        libvlc_audio_set_volume(mp, volume);
    }
}

void GetVolume(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    int volume = 0;
    if (mp) {
        volume = libvlc_audio_get_volume(mp);
    }

    args.GetReturnValue().Set(volume);
}

// Gets the total length of the media in milliseconds
void GetMediaLength(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;
    }

    libvlc_time_t length = libvlc_media_player_get_length(mp);
    args.GetReturnValue().Set(v8::Number::New(isolate, static_cast<double>(length)));

}
// Get the current playback position in milliseconds
void GetCurrentPlaybackTime(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;
    }

    libvlc_time_t currentTime = libvlc_media_player_get_time(mp);
    args.GetReturnValue().Set(v8::Number::New(isolate, static_cast<double>(currentTime)));
}
// Gets the current playback speed
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)));
}
// Set the playback speed
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);
}

void GetPlayerState(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;
    }

    libvlc_state_t state = libvlc_media_player_get_state(mp);
    Local<String> stateString;

    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();
    }

    args.GetReturnValue().Set(stateString);
}

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));
}
void Init(Local<Object> exports) {
    log_file = fopen(log_filename, "a"); // Open or append to existing log file
    if (!log_file) {
        fprintf(stderr, "Failed to open %s for logging\n", log_filename);
        return;
    }
    NODE_SET_METHOD(exports, "playMedia", PlayMedia);
    NODE_SET_METHOD(exports, "pauseOrResume", PauseOrResume);
    NODE_SET_METHOD(exports, "setPlaybackProgress", SetPlaybackProgress);
    NODE_SET_METHOD(exports, "getPlaybackProgress", GetPlaybackProgress);
    NODE_SET_METHOD(exports, "setVolume", SetVolume);
    NODE_SET_METHOD(exports, "getVolume", GetVolume);
    NODE_SET_METHOD(exports, "getMediaLength", GetMediaLength);
    NODE_SET_METHOD(exports, "getCurrentPlaybackTime", GetCurrentPlaybackTime);
    NODE_SET_METHOD(exports, "getPlaybackRate", GetPlaybackRate);
    NODE_SET_METHOD(exports, "setPlaybackRate", SetPlaybackRate);
    NODE_SET_METHOD(exports, "getPlayerState", GetPlayerState);
    NODE_SET_METHOD(exports, "isMediaEnded", IsMediaEnded);
    NODE_SET_METHOD(exports, "setLoggingEnabled", SetLoggingEnabled);
    NODE_SET_METHOD(exports, "setMaxLogSize", SetMaxLogSize);
}

NODE_MODULE(addon, Init)

main.js 增加定时任务获取播放状态

js
const playHwnd = vlcAddon.playMedia(newURL, 5000); // 5000ms caching

console.log("playHwnd", playHwnd);

// 假设您有一个定时器,定期检查播放进度
const intervalIdToPlayer = setInterval(() => {
  try {
    let totalLength = vlcAddon.getMediaLength();
    let currentTime = vlcAddon.getCurrentPlaybackTime();
    const progress = vlcAddon.getPlaybackProgress();
    const state = vlcAddon.getPlayerState();
    const rate = vlcAddon.getPlaybackRate();
    const ended = vlcAddon.isMediaEnded();
    const volume = vlcAddon.getVolume();

    console.log("PlayerState", {
      volume,
      ended,
      rate,
      state,
      totalLength,
      currentTime: ended ? totalLength : currentTime,
      progress: ended ? 1 : progress,
    });
  } catch (error) {
    console.error("setInterval error", error);
  }
}, 1000); // 每秒更新一次

setTimeout(() => {
  vlcAddon.pauseOrResume();
  console.log("调用暂停");

  vlcAddon.setPlaybackProgress(0.7);

  vlcAddon.setPlaybackRate(2);
  vlcAddon.setVolume(200);

  setTimeout(() => {
    console.log("调用继续");
    vlcAddon.pauseOrResume();
    vlcAddon.setPlaybackProgress(0.3);
  }, 5000);
}, 5000);