Files
Velix-MPV-Core/sdk/native/product_binding.cpp
T

580 lines
23 KiB
C++

#include "player.h"
#include "render.h"
#include "velix/core.h"
#include <ace/xcomponent/native_interface_xcomponent.h>
#include <napi/native_api.h>
#include <cstdint>
#include <string>
#include <utility>
namespace {
PlaybackEngine gPlayer;
EglRenderer gRenderer(gPlayer);
napi_value boolean(napi_env env, bool value)
{
napi_value result = nullptr;
napi_get_boolean(env, value, &result);
return result;
}
void setString(napi_env env, napi_value object, const char* name, const std::string& value)
{
napi_value item = nullptr;
napi_create_string_utf8(env, value.c_str(), NAPI_AUTO_LENGTH, &item);
napi_set_named_property(env, object, name, item);
}
void setString(napi_env env, napi_value object, const char* name, const char* value)
{
napi_value item = nullptr;
napi_create_string_utf8(env, value == nullptr ? "" : value, NAPI_AUTO_LENGTH, &item);
napi_set_named_property(env, object, name, item);
}
void setDouble(napi_env env, napi_value object, const char* name, double value)
{
napi_value item = nullptr;
napi_create_double(env, value, &item);
napi_set_named_property(env, object, name, item);
}
void setInt64(napi_env env, napi_value object, const char* name, int64_t value)
{
napi_value item = nullptr;
napi_create_int64(env, value, &item);
napi_set_named_property(env, object, name, item);
}
void setBool(napi_env env, napi_value object, const char* name, bool value)
{
napi_value item = nullptr;
napi_get_boolean(env, value, &item);
napi_set_named_property(env, object, name, item);
}
bool firstArg(napi_env env, napi_callback_info info, napi_value& value)
{
size_t argc = 1;
napi_value argv[1] = {nullptr};
if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok || argc == 0) return false;
value = argv[0];
return true;
}
bool valueString(napi_env env, napi_value value, std::string& result)
{
size_t size = 0;
if (napi_get_value_string_utf8(env, value, nullptr, 0, &size) != napi_ok) return false;
result.resize(size + 1);
if (napi_get_value_string_utf8(env, value, result.data(), result.size(), &size) != napi_ok) return false;
result.resize(size);
return true;
}
bool namedString(napi_env env, napi_value object, const char* name, std::string& result)
{
bool has = false;
napi_value value = nullptr;
return napi_has_named_property(env, object, name, &has) == napi_ok && has &&
napi_get_named_property(env, object, name, &value) == napi_ok && valueString(env, value, result);
}
bool namedDouble(napi_env env, napi_value object, const char* name, double& result)
{
bool has = false;
napi_value value = nullptr;
return napi_has_named_property(env, object, name, &has) == napi_ok && has &&
napi_get_named_property(env, object, name, &value) == napi_ok &&
napi_get_value_double(env, value, &result) == napi_ok;
}
bool namedInt(napi_env env, napi_value object, const char* name, int64_t& result)
{
double number = 0.0;
if (!namedDouble(env, object, name, number)) return false;
result = static_cast<int64_t>(number);
return true;
}
bool namedBool(napi_env env, napi_value object, const char* name, bool& result)
{
bool has = false;
napi_value value = nullptr;
return napi_has_named_property(env, object, name, &has) == napi_ok && has &&
napi_get_named_property(env, object, name, &value) == napi_ok &&
napi_get_value_bool(env, value, &result) == napi_ok;
}
bool requestFromArg(napi_env env, napi_value argument, PlaybackRequestModel& request)
{
napi_valuetype type = napi_undefined;
if (napi_typeof(env, argument, &type) != napi_ok) return false;
if (type == napi_string) return valueString(env, argument, request.url) && !request.url.empty();
if (type != napi_object || !namedString(env, argument, "url", request.url) || request.url.empty()) return false;
namedString(env, argument, "title", request.title);
namedString(env, argument, "userAgent", request.userAgent);
namedString(env, argument, "referer", request.referer);
namedString(env, argument, "cookies", request.cookies);
int64_t number = -1;
if (namedInt(env, argument, "startPositionMs", number)) request.startPositionMs = number;
if (namedInt(env, argument, "audioTrackId", number)) request.audioTrackId = static_cast<int>(number);
if (namedInt(env, argument, "subtitleTrackId", number)) request.subtitleTrackId = static_cast<int>(number);
double decimal = 0.0;
if (namedDouble(env, argument, "playbackSpeed", decimal)) request.playbackSpeed = decimal;
if (namedDouble(env, argument, "networkTimeout", decimal)) request.networkTimeout = decimal;
bool enabled = true;
if (namedBool(env, argument, "subtitleEnabled", enabled)) request.subtitleEnabled = enabled ? 1 : 0;
napi_value headers = nullptr;
bool has = false;
if (napi_has_named_property(env, argument, "httpHeaders", &has) == napi_ok && has &&
napi_get_named_property(env, argument, "httpHeaders", &headers) == napi_ok) {
uint32_t length = 0;
if (napi_get_array_length(env, headers, &length) == napi_ok) {
for (uint32_t index = 0; index < length && index < 32; ++index) {
napi_value item = nullptr;
PlaybackHeader header;
if (napi_get_element(env, headers, index, &item) == napi_ok &&
namedString(env, item, "name", header.name) && namedString(env, item, "value", header.value)) {
request.headers.push_back(std::move(header));
}
}
}
}
return true;
}
void serializeTrack(napi_env env, napi_value object, const PlaybackTrack& track)
{
setInt64(env, object, "id", track.id);
setString(env, object, "type", track.type);
setString(env, object, "title", track.title);
setString(env, object, "language", track.language);
setString(env, object, "codec", track.codec);
setBool(env, object, "default", track.isDefault);
setBool(env, object, "forced", track.forced);
setBool(env, object, "selected", track.selected);
setBool(env, object, "external", track.external);
}
napi_value tracksValue(napi_env env, const std::vector<PlaybackTrack>& tracks)
{
napi_value result = nullptr;
napi_create_array_with_length(env, tracks.size(), &result);
for (size_t index = 0; index < tracks.size(); ++index) {
napi_value item = nullptr;
napi_create_object(env, &item);
serializeTrack(env, item, tracks[index]);
napi_set_element(env, result, index, item);
}
return result;
}
napi_value mediaValue(napi_env env, const PlaybackMediaInfo& media)
{
napi_value result = nullptr;
napi_create_object(env, &result);
setString(env, result, "container", media.container);
setString(env, result, "demuxer", media.demuxer);
setDouble(env, result, "duration", media.duration);
setString(env, result, "videoCodec", media.videoCodec);
setString(env, result, "audioCodec", media.audioCodec);
setString(env, result, "pixelFormat", media.pixelFormat);
setString(env, result, "colorPrimaries", media.colorPrimaries);
setString(env, result, "colorTransfer", media.colorTransfer);
setString(env, result, "colorSpace", media.colorSpace);
setString(env, result, "colorMatrix", media.colorMatrix);
setInt64(env, result, "width", media.width);
setInt64(env, result, "height", media.height);
setInt64(env, result, "videoWidth", media.videoWidth);
setInt64(env, result, "videoHeight", media.videoHeight);
setInt64(env, result, "bitDepth", media.bitDepth);
setInt64(env, result, "audioSampleRate", media.audioSampleRate);
setInt64(env, result, "audioChannels", media.audioChannels);
setString(env, result, "audioChannelLayout", media.audioChannelLayout);
setDouble(env, result, "fps", media.fps);
setInt64(env, result, "bitrate", media.bitrate);
setInt64(env, result, "videoBitrate", media.videoBitrate);
setInt64(env, result, "audioBitrate", media.audioBitrate);
setInt64(env, result, "trackCount", media.trackCount);
setInt64(env, result, "subtitleCount", media.subtitleCount);
return result;
}
napi_value metricsValue(napi_env env, const PlaybackSnapshot& state)
{
napi_value result = nullptr;
napi_create_object(env, &result);
setInt64(env, result, "droppedFrames", state.dropped);
setInt64(env, result, "renderCount", static_cast<int64_t>(state.renderCount));
setBool(env, result, "buffering", state.network.buffering);
setInt64(env, result, "bufferingEnterCount", state.metrics.buffering_enter_count);
setInt64(env, result, "bufferingExitCount", state.metrics.buffering_exit_count);
setInt64(env, result, "bufferingTotalMs", state.metrics.buffering_total_ms);
setInt64(env, result, "bufferingMaxSingleMs", state.metrics.buffering_max_single_ms);
setInt64(env, result, "networkWaitMs", state.metrics.network_wait_ms);
setInt64(env, result, "lateFrames", state.metrics.late_frames);
setInt64(env, result, "mistimedFrames", state.metrics.mistimed_frames);
setInt64(env, result, "decoderFrames", state.metrics.decoder_frames);
setInt64(env, result, "cacheBytes", state.network.cacheBytes);
setDouble(env, result, "cacheDuration", state.network.cacheDuration);
setInt64(env, result, "currentBitrate", state.network.currentBitrate);
return result;
}
napi_value stateValue(napi_env env)
{
const PlaybackSnapshot state = gPlayer.snapshot();
napi_value result = nullptr;
napi_create_object(env, &result);
setString(env, result, "state", state.state);
setInt64(env, result, "sessionId", static_cast<int64_t>(state.sessionId));
setDouble(env, result, "position", state.position);
setDouble(env, result, "duration", state.duration);
setDouble(env, result, "fps", state.fps);
setInt64(env, result, "dropped", state.dropped);
setInt64(env, result, "firstFrameMs", state.firstFrameMs);
setInt64(env, result, "renderCount", static_cast<int64_t>(state.renderCount));
setString(env, result, "lastError", state.lastError);
setString(env, result, "errorCode", state.errorCode);
setDouble(env, result, "speed", state.speed);
setDouble(env, result, "volume", state.volume);
setBool(env, result, "muted", state.muted);
setInt64(env, result, "subtitleDelayMs", state.subtitleDelayMs);
setDouble(env, result, "subtitleScale", state.subtitleScale);
setDouble(env, result, "subtitlePosition", state.subtitlePosition);
setString(env, result, "requestedDecoder", state.requestedDecoder);
setString(env, result, "actualDecoder", state.actualDecoder);
setString(env, result, "hwCapability", state.hwCapability);
setBool(env, result, "hwdecRequested", state.hwdecRequested);
setBool(env, result, "hwdecActive", state.hwdecActive);
setInt64(env, result, "hardwareFrameCount", state.hardwareFrameCount);
setInt64(env, result, "softwareFrameCount", state.softwareFrameCount);
setInt64(env, result, "hwFallbackCount", state.hwFallbackCount);
setInt64(env, result, "nativeBufferImportCount", state.nativeBufferImportCount);
setInt64(env, result, "nativeBufferImportFailure", state.nativeBufferImportFailure);
setInt64(env, result, "surfacePresentCount", state.surfacePresentCount);
setInt64(env, result, "gpuInteropFrameCount", state.gpuInteropFrameCount);
setString(env, result, "surface", gRenderer.surfaceStatus());
setString(env, result, "egl", gRenderer.eglStatus());
setString(env, result, "mpv", gRenderer.mpvStatus());
napi_set_named_property(env, result, "media", mediaValue(env, state.media));
napi_set_named_property(env, result, "tracks", tracksValue(env, state.tracks));
napi_set_named_property(env, result, "metrics", metricsValue(env, state));
return result;
}
napi_value createPlayer(napi_env env, napi_callback_info)
{
return boolean(env, gPlayer.initialize());
}
napi_value prepare(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
PlaybackRequestModel request;
return firstArg(env, info, argument) && requestFromArg(env, argument, request)
? boolean(env, gPlayer.load(request))
: boolean(env, false);
}
napi_value play(napi_env env, napi_callback_info)
{
gPlayer.play();
return boolean(env, true);
}
napi_value pause(napi_env env, napi_callback_info)
{
gPlayer.pause();
return boolean(env, true);
}
napi_value stop(napi_env env, napi_callback_info)
{
gPlayer.stop();
return boolean(env, true);
}
napi_value release(napi_env env, napi_callback_info)
{
gRenderer.onSurfaceDestroyed(nullptr);
gPlayer.release();
return boolean(env, true);
}
napi_value attachSurface(napi_env env, napi_callback_info)
{
gRenderer.requestRender();
return boolean(env, gRenderer.hasSurface());
}
napi_value detachSurface(napi_env env, napi_callback_info)
{
gRenderer.onSurfaceDestroyed(nullptr);
return boolean(env, true);
}
napi_value onForeground(napi_env env, napi_callback_info)
{
gRenderer.requestRender();
return boolean(env, true);
}
napi_value onBackground(napi_env env, napi_callback_info)
{
return boolean(env, true);
}
napi_value onSurfaceAvailable(napi_env env, napi_callback_info)
{
gRenderer.requestRender();
return boolean(env, true);
}
napi_value onSurfaceDestroyed(napi_env env, napi_callback_info)
{
gRenderer.onSurfaceDestroyed(nullptr);
return boolean(env, true);
}
napi_value seekTo(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
double milliseconds = 0.0;
if (!firstArg(env, info, argument) || napi_get_value_double(env, argument, &milliseconds) != napi_ok) {
return boolean(env, false);
}
return boolean(env, gPlayer.seekAbsolute(milliseconds / 1000.0, false, false));
}
napi_value seekBy(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
double milliseconds = 0.0;
if (!firstArg(env, info, argument) || napi_get_value_double(env, argument, &milliseconds) != napi_ok) {
return boolean(env, false);
}
gPlayer.seek(milliseconds / 1000.0);
return boolean(env, true);
}
napi_value setSpeed(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
double value = 1.0;
return firstArg(env, info, argument) && napi_get_value_double(env, argument, &value) == napi_ok
? boolean(env, gPlayer.setSpeed(value))
: boolean(env, false);
}
napi_value setVolume(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
double value = 100.0;
return firstArg(env, info, argument) && napi_get_value_double(env, argument, &value) == napi_ok
? boolean(env, gPlayer.setVolume(value))
: boolean(env, false);
}
napi_value setMute(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
bool value = false;
return firstArg(env, info, argument) && napi_get_value_bool(env, argument, &value) == napi_ok
? boolean(env, gPlayer.setMute(value))
: boolean(env, false);
}
napi_value selectAudioTrack(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
double value = -1;
return firstArg(env, info, argument) && napi_get_value_double(env, argument, &value) == napi_ok
? boolean(env, gPlayer.selectAudioTrack(static_cast<int>(value)))
: boolean(env, false);
}
napi_value selectSubtitleTrack(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
double value = -1;
return firstArg(env, info, argument) && napi_get_value_double(env, argument, &value) == napi_ok
? boolean(env, gPlayer.selectSubtitleTrack(static_cast<int>(value)))
: boolean(env, false);
}
napi_value disableSubtitle(napi_env env, napi_callback_info)
{
return boolean(env, gPlayer.disableSubtitle());
}
napi_value addSubtitle(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
if (!firstArg(env, info, argument)) return boolean(env, false);
PlaybackSubtitleSource source;
napi_valuetype type = napi_undefined;
if (napi_typeof(env, argument, &type) != napi_ok) return boolean(env, false);
if (type == napi_string) {
if (!valueString(env, argument, source.url)) return boolean(env, false);
} else if (type == napi_object) {
namedString(env, argument, "url", source.url);
namedString(env, argument, "title", source.title);
namedString(env, argument, "language", source.language);
namedString(env, argument, "encoding", source.encoding);
namedBool(env, argument, "forced", source.forced);
namedBool(env, argument, "select", source.select);
}
return boolean(env, !source.url.empty() && gPlayer.addSubtitle(source));
}
napi_value removeSubtitle(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
double value = -1;
return firstArg(env, info, argument) && napi_get_value_double(env, argument, &value) == napi_ok
? boolean(env, gPlayer.removeSubtitle(static_cast<int>(value)))
: boolean(env, false);
}
napi_value setSubtitleDelay(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
double value = 0.0;
return firstArg(env, info, argument) && napi_get_value_double(env, argument, &value) == napi_ok
? boolean(env, gPlayer.setSubtitleDelay(value))
: boolean(env, false);
}
napi_value setSubtitleScale(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
double value = 1.0;
return firstArg(env, info, argument) && napi_get_value_double(env, argument, &value) == napi_ok
? boolean(env, gPlayer.setSubtitleScale(value))
: boolean(env, false);
}
napi_value setSubtitlePosition(napi_env env, napi_callback_info info)
{
napi_value argument = nullptr;
double value = 100.0;
return firstArg(env, info, argument) && napi_get_value_double(env, argument, &value) == napi_ok
? boolean(env, gPlayer.setSubtitlePosition(value))
: boolean(env, false);
}
napi_value getState(napi_env env, napi_callback_info)
{
return stateValue(env);
}
napi_value getMediaInfo(napi_env env, napi_callback_info)
{
return mediaValue(env, gPlayer.snapshot().media);
}
napi_value getTracks(napi_env env, napi_callback_info)
{
return tracksValue(env, gPlayer.snapshot().tracks);
}
napi_value getMetrics(napi_env env, napi_callback_info)
{
return metricsValue(env, gPlayer.snapshot());
}
napi_value getBuildInfo(napi_env env, napi_callback_info)
{
const velix_core_build_info* info = velix_core_get_build_info();
napi_value result = nullptr;
napi_create_object(env, &result);
setInt64(env, result, "apiVersion", info->api_version);
setString(env, result, "sdkVersion", info->sdk_version);
setString(env, result, "mpvVersion", info->mpv_version);
setString(env, result, "mpvCommit", info->mpv_commit);
setString(env, result, "ffmpegCommit", info->ffmpeg_commit);
setString(env, result, "ffmpegHarmonyCommit", info->ffmpeg_harmony_commit);
setString(env, result, "buildDate", info->build_date);
setString(env, result, "target", info->target);
setString(env, result, "architecture", info->architecture);
setString(env, result, "profile", info->profile);
setString(env, result, "featureFlags", info->feature_flags);
return result;
}
void onSurfaceCreated(OH_NativeXComponent* component, void* window)
{
gRenderer.onSurfaceCreated(component, window);
}
void onSurfaceChanged(OH_NativeXComponent* component, void* window)
{
gRenderer.onSurfaceChanged(component, window);
}
void onSurfaceDestroyedCallback(OH_NativeXComponent*, void* window)
{
gRenderer.onSurfaceDestroyed(window);
}
void onTouch(OH_NativeXComponent*, void*) {}
napi_value Init(napi_env env, napi_value exports)
{
gPlayer.initialize();
napi_value xcomponent = nullptr;
if (napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &xcomponent) == napi_ok) {
OH_NativeXComponent* component = nullptr;
if (napi_unwrap(env, xcomponent, reinterpret_cast<void**>(&component)) == napi_ok && component != nullptr) {
static OH_NativeXComponent_Callback callbacks = {
onSurfaceCreated, onSurfaceChanged, onSurfaceDestroyedCallback, onTouch
};
OH_NativeXComponent_RegisterCallback(component, &callbacks);
}
}
napi_property_descriptor properties[] = {
{"createPlayer", nullptr, createPlayer, nullptr, nullptr, nullptr, napi_default, nullptr},
{"prepare", nullptr, prepare, nullptr, nullptr, nullptr, napi_default, nullptr},
{"play", nullptr, play, nullptr, nullptr, nullptr, napi_default, nullptr},
{"pause", nullptr, pause, nullptr, nullptr, nullptr, napi_default, nullptr},
{"stop", nullptr, stop, nullptr, nullptr, nullptr, napi_default, nullptr},
{"release", nullptr, release, nullptr, nullptr, nullptr, napi_default, nullptr},
{"attachSurface", nullptr, attachSurface, nullptr, nullptr, nullptr, napi_default, nullptr},
{"detachSurface", nullptr, detachSurface, nullptr, nullptr, nullptr, napi_default, nullptr},
{"onForeground", nullptr, onForeground, nullptr, nullptr, nullptr, napi_default, nullptr},
{"onBackground", nullptr, onBackground, nullptr, nullptr, nullptr, napi_default, nullptr},
{"onSurfaceAvailable", nullptr, onSurfaceAvailable, nullptr, nullptr, nullptr, napi_default, nullptr},
{"onSurfaceDestroyed", nullptr, onSurfaceDestroyed, nullptr, nullptr, nullptr, napi_default, nullptr},
{"seekTo", nullptr, seekTo, nullptr, nullptr, nullptr, napi_default, nullptr},
{"seekBy", nullptr, seekBy, nullptr, nullptr, nullptr, napi_default, nullptr},
{"setSpeed", nullptr, setSpeed, nullptr, nullptr, nullptr, napi_default, nullptr},
{"setVolume", nullptr, setVolume, nullptr, nullptr, nullptr, napi_default, nullptr},
{"setMute", nullptr, setMute, nullptr, nullptr, nullptr, napi_default, nullptr},
{"selectAudioTrack", nullptr, selectAudioTrack, nullptr, nullptr, nullptr, napi_default, nullptr},
{"selectSubtitleTrack", nullptr, selectSubtitleTrack, nullptr, nullptr, nullptr, napi_default, nullptr},
{"disableSubtitle", nullptr, disableSubtitle, nullptr, nullptr, nullptr, napi_default, nullptr},
{"addSubtitle", nullptr, addSubtitle, nullptr, nullptr, nullptr, napi_default, nullptr},
{"removeSubtitle", nullptr, removeSubtitle, nullptr, nullptr, nullptr, napi_default, nullptr},
{"setSubtitleDelay", nullptr, setSubtitleDelay, nullptr, nullptr, nullptr, napi_default, nullptr},
{"setSubtitleScale", nullptr, setSubtitleScale, nullptr, nullptr, nullptr, napi_default, nullptr},
{"setSubtitlePosition", nullptr, setSubtitlePosition, nullptr, nullptr, nullptr, napi_default, nullptr},
{"getState", nullptr, getState, nullptr, nullptr, nullptr, napi_default, nullptr},
{"getMediaInfo", nullptr, getMediaInfo, nullptr, nullptr, nullptr, napi_default, nullptr},
{"getTracks", nullptr, getTracks, nullptr, nullptr, nullptr, napi_default, nullptr},
{"getMetrics", nullptr, getMetrics, nullptr, nullptr, nullptr, napi_default, nullptr},
{"getBuildInfo", nullptr, getBuildInfo, nullptr, nullptr, nullptr, napi_default, nullptr},
};
napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties);
return exports;
}
} // namespace
NAPI_MODULE(velix_core_binding, Init)