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