diff --git a/LuaSTG/CMakeLists.txt b/LuaSTG/CMakeLists.txt index 9cf83316d..8e90c261f 100644 --- a/LuaSTG/CMakeLists.txt +++ b/LuaSTG/CMakeLists.txt @@ -144,6 +144,10 @@ set(LUASTG_ENGINE_SOURCES LuaSTG/LuaBinding/modern/SwapChain.cpp LuaSTG/LuaBinding/modern/Texture2D.hpp LuaSTG/LuaBinding/modern/Texture2D.cpp + LuaSTG/LuaBinding/modern/Video.hpp + LuaSTG/LuaBinding/modern/Video.cpp + LuaSTG/LuaBinding/VideoBindingHelpers.hpp + LuaSTG/LuaBinding/VideoBindingHelpers.cpp LuaSTG/LuaBinding/modern/RenderTarget.hpp LuaSTG/LuaBinding/modern/RenderTarget.cpp LuaSTG/LuaBinding/modern/DepthStencilBuffer.hpp diff --git a/LuaSTG/LuaSTG/GameResource/Implement/ResourceTextureImpl.hpp b/LuaSTG/LuaSTG/GameResource/Implement/ResourceTextureImpl.hpp index bc7c5357d..66ddc7864 100644 --- a/LuaSTG/LuaSTG/GameResource/Implement/ResourceTextureImpl.hpp +++ b/LuaSTG/LuaSTG/GameResource/Implement/ResourceTextureImpl.hpp @@ -21,6 +21,7 @@ namespace luastg core::IRenderTarget* GetRenderTarget() { return m_rt.get(); } core::IDepthStencilBuffer* GetDepthStencilBuffer() { return m_ds.get(); } bool IsRenderTarget() { return m_is_rendertarget; } + bool IsVideoTexture() { return m_texture && m_texture->isVideoTexture(); } bool HasDepthStencilBuffer() { return m_enable_depthbuffer; } public: // 纹理容器 @@ -47,6 +48,7 @@ namespace luastg core::IRenderTarget* GetRenderTarget() override { return m_rt.get(); } core::IDepthStencilBuffer* GetDepthStencilBuffer() override { return m_ds.get(); } bool IsRenderTarget() override { return true; } + bool IsVideoTexture() override { return false; } bool HasDepthStencilBuffer() override { return !!m_ds; } // RenderTargetStackResourceTextureImpl diff --git a/LuaSTG/LuaSTG/GameResource/ResourceDebug.cpp b/LuaSTG/LuaSTG/GameResource/ResourceDebug.cpp index ecda732f2..0ab5cf8c0 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourceDebug.cpp +++ b/LuaSTG/LuaSTG/GameResource/ResourceDebug.cpp @@ -81,18 +81,21 @@ namespace luastg }; auto draw_texture = [](IResourceTexture* p_res, bool show_info, float scale) -> void { - auto const size = p_res->GetTexture()->getSize(); + auto* p_tex = p_res->GetTexture(); + auto const size = p_tex->getSize(); if (show_info) { ImGui::Text("Size: %u x %u", size.x, size.y); - ImGui::Text("RenderTarget: %s", p_res->IsRenderTarget() ? "Yes" : "Not"); - ImGui::Text("Dynamic: %s", p_res->IsRenderTarget() ? "Yes" : "Not"); - unsigned long long mem_usage = size.x * size.y * 4; + char const* type_str = p_tex->isVideoTexture() ? "Video" : (p_res->IsRenderTarget() ? "RenderTarget" : "Texture"); + ImGui::Text("Type: %s", type_str); + ImGui::Text("Dynamic: %s", p_tex->isDynamic() ? "Yes" : "Not"); + unsigned long long display_mem = (unsigned long long)size.x * size.y * 4; + unsigned long long mem_usage = display_mem; ImGui::Text("Adapter Memory Usage (Approximate): %s", bytes_count_to_string(mem_usage).c_str()); } ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, 1.0); ImGui::Image( - reinterpret_cast(p_res->GetTexture()->getNativeView()), + reinterpret_cast(p_tex->getNativeView()), ImVec2(scale * (float)size.x, scale * (float)size.y), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f)); diff --git a/LuaSTG/LuaSTG/GameResource/ResourceManager.h b/LuaSTG/LuaSTG/GameResource/ResourceManager.h index 3f6de2e48..ca82b9762 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourceManager.h +++ b/LuaSTG/LuaSTG/GameResource/ResourceManager.h @@ -1,5 +1,6 @@ #pragma once #include "core/SmartReference.hpp" +#include "core/VideoDecoder.hpp" #include "GameResource/ResourceTexture.hpp" #include "GameResource/ResourceSprite.hpp" #include "GameResource/ResourceAnimation.hpp" @@ -98,6 +99,8 @@ namespace luastg // 纹理 bool LoadTexture(const char* name, const char* path, bool mipmaps = true) noexcept; bool CreateTexture(const char* name, int width, int height) noexcept; + // 视频纹理(options 为 nullptr 时使用默认选项) + bool LoadVideo(const char* name, const char* path, core::VideoOpenOptions const* options = nullptr) noexcept; // 渲染目标 bool CreateRenderTarget(const char* name, int width = 0, int height = 0, bool depth_buffer = false) noexcept; // 图片精灵 diff --git a/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp b/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp index 90ccb910e..24ef04411 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp +++ b/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp @@ -9,6 +9,7 @@ #include "GameResource/Implement/ResourcePostEffectShaderImpl.hpp" #include "GameResource/Implement/ResourceModelImpl.hpp" #include "core/FileSystem.hpp" +#include "core/AudioEngine.hpp" #include "AppFrame.h" #include "lua/plus.hpp" @@ -227,6 +228,47 @@ namespace luastg return true; } + bool ResourcePool::LoadVideo(const char* name, const char* path, core::VideoOpenOptions const* options) noexcept + { + if (m_TexturePool.find(std::string_view(name)) != m_TexturePool.end()) + { + if (ResourceMgr::GetResourceLoadingLog()) + { + spdlog::warn("[luastg] LoadVideo: 纹理 '{}' 已存在,加载操作已取消", name); + } + return true; + } + + core::SmartReference p_texture; + bool ok = options + ? LAPP.getGraphicsDevice()->createVideoTexture(path, *options, p_texture.put()) + : LAPP.getGraphicsDevice()->createVideoTexture(path, p_texture.put()); + if (!ok) + { + spdlog::error("[luastg] 从 '{}' 创建视频纹理 '{}' 失败", path, name); + return false; + } + + try + { + core::SmartReference tRes; + tRes.attach(new ResourceTextureImpl(name, p_texture.get())); + m_TexturePool.emplace(name, tRes); + } + catch (std::exception const& e) + { + spdlog::error("[luastg] LoadVideo: 创建视频纹理 '{}' 失败 ({})", name, e.what()); + return false; + } + + if (ResourceMgr::GetResourceLoadingLog()) + { + spdlog::info("[luastg] LoadVideo: 已从 '{}' 加载视频 '{}' ({})", path, name, getResourcePoolTypeName()); + } + + return true; + } + bool ResourcePool::CreateTexture(const char* name, int width, int height) noexcept { if (m_TexturePool.find(std::string_view(name)) != m_TexturePool.end()) diff --git a/LuaSTG/LuaSTG/GameResource/ResourceTexture.hpp b/LuaSTG/LuaSTG/GameResource/ResourceTexture.hpp index b692ef27e..ac6c54690 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourceTexture.hpp +++ b/LuaSTG/LuaSTG/GameResource/ResourceTexture.hpp @@ -12,6 +12,7 @@ namespace luastg virtual core::IRenderTarget* GetRenderTarget() = 0; virtual core::IDepthStencilBuffer* GetDepthStencilBuffer() = 0; virtual bool IsRenderTarget() = 0; + virtual bool IsVideoTexture() = 0; virtual bool HasDepthStencilBuffer() = 0; }; }; diff --git a/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp b/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp index 8fbc43612..7647108f2 100644 --- a/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp +++ b/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp @@ -1,6 +1,9 @@ #include "LuaBinding/LuaWrapper.hpp" +#include "LuaBinding/VideoBindingHelpers.hpp" #include "lua/plus.hpp" #include "AppFrame.h" +#include "d3d11/VideoTexture.hpp" +#include "core/VideoDecoder.hpp" void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { @@ -52,6 +55,25 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept return luaL_error(L, "can't load texture from file '%s'.", path); return 0; } + static int LoadVideo(lua_State* L) noexcept + { + lua::stack_t const ctx(L); + const char* name = luaL_checkstring(L, 1); + const char* path = luaL_checkstring(L, 2); + + ResourcePool* pActivedPool = LRES.GetActivedPool(); + if (!pActivedPool) + return luaL_error(L, "can't load resource at this time."); + + core::VideoOpenOptions opt; + bool const has_options = ctx.index_of_top() >= 3 && ctx.is_table(3); + if (has_options) + video::parseVideoOptions(L, 3, opt); + + if (!pActivedPool->LoadVideo(name, path, has_options ? &opt : nullptr)) + return luaL_error(L, "can't load video from file '%s'.", path); + return 0; + } static int LoadSprite(lua_State* L) noexcept { const char* name = luaL_checkstring(L, 1); @@ -414,6 +436,14 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept lua_pushboolean(L, p->IsRenderTarget()); return 1; } + static int IsVideoTexture(lua_State* L) noexcept + { + core::SmartReference p = LRES.FindTexture(luaL_checkstring(L, 1)); + if (!p) + return luaL_error(L, "texture '%s' not found.", luaL_checkstring(L, 1)); + lua_pushboolean(L, p->IsVideoTexture()); + return 1; + } static int SetTexturePreMulAlphaState(lua_State* L) noexcept { core::SmartReference p = LRES.FindTexture(luaL_checkstring(L, 1)); @@ -696,6 +726,98 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept LRES.CacheTTFFontString(luaL_checkstring(L, 1), str, len); return 0; } + + // Video control functions + + static int VideoSeek(lua_State* L) noexcept { + lua::stack_t const ctx(L); + const char* name = luaL_checkstring(L, 1); + double time = luaL_checknumber(L, 2); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found or is not a video texture.", name); + bool ok = decoder->seek(time); + ctx.push_value(ok); + return 1; + } + + static int VideoSetLooping(lua_State* L) noexcept { + lua::stack_t const ctx(L); + const char* name = luaL_checkstring(L, 1); + bool loop = ctx.get_value(2); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + decoder->setLooping(loop); + return 0; + } + + static int VideoSetLoopRange(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + double loop_end = luaL_checknumber(L, 2); + double loop_duration = luaL_checknumber(L, 3); + decoder->setLoopRange(loop_end, loop_duration); + return 0; + } + + static int VideoUpdate(lua_State* L) noexcept { + lua::stack_t const ctx(L); + const char* name = luaL_checkstring(L, 1); + double time = luaL_checknumber(L, 2); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found or is not a video texture.", name); + bool ok = decoder->updateToTime(time); + ctx.push_value(ok); + return 1; + } + + static int VideoGetInfo(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + video::pushVideoInfoToLua(L, decoder); + return 1; + } + + static int VideoGetVideoStreams(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + video::pushVideoStreamsToLua(L, decoder); + return 1; + } + + static int VideoGetAudioStreams(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + video::pushAudioStreamsToLua(L, decoder); + return 1; + } + + static int VideoReopen(lua_State* L) noexcept { + lua::stack_t const ctx(L); + const char* name = luaL_checkstring(L, 1); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found or is not a video texture.", name); + core::VideoOpenOptions opt = decoder->getLastOpenOptions(); + if (ctx.index_of_top() >= 2 && ctx.is_table(2)) + video::parseVideoOptions(L, 2, opt); + if (!decoder->reopen(opt)) { + ctx.push_value(false); + return 1; + } + ctx.push_value(true); + return 1; + } }; luaL_Reg const lib[] = { @@ -703,6 +825,7 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { "SetResourceStatus", &Wrapper::SetResourceStatus }, { "GetResourceStatus", &Wrapper::GetResourceStatus }, { "LoadTexture", &Wrapper::LoadTexture }, + { "LoadVideo", &Wrapper::LoadVideo }, { "LoadImage", &Wrapper::LoadSprite }, { "CopyImage", &Wrapper::CopySprite }, { "LoadAnimation", &Wrapper::LoadAnimation }, @@ -716,6 +839,7 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { "LoadModel", &Wrapper::LoadModel }, { "CreateRenderTarget", &Wrapper::CreateRenderTarget }, { "IsRenderTarget", &Wrapper::IsRenderTarget }, + { "IsVideoTexture", &Wrapper::IsVideoTexture }, { "SetTexturePreMulAlphaState", &Wrapper::SetTexturePreMulAlphaState }, { "SetTextureSamplerState", &Wrapper::SetTextureSamplerState }, { "GetTextureSize", &Wrapper::GetTextureSize }, @@ -737,6 +861,17 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { "SetFontState", &Wrapper::SetFontState }, { "CacheTTFString", &Wrapper::CacheTTFString }, + + // Video control functions + { "VideoSeek", &Wrapper::VideoSeek }, + { "VideoSetLooping", &Wrapper::VideoSetLooping }, + { "VideoSetLoopRange", &Wrapper::VideoSetLoopRange }, + { "VideoUpdate", &Wrapper::VideoUpdate }, + { "VideoGetInfo", &Wrapper::VideoGetInfo }, + { "VideoGetVideoStreams", &Wrapper::VideoGetVideoStreams }, + { "VideoGetAudioStreams", &Wrapper::VideoGetAudioStreams }, + { "VideoReopen", &Wrapper::VideoReopen }, + { NULL, NULL }, }; diff --git a/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.cpp b/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.cpp index 67c0e4b52..b668ca65f 100644 --- a/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.cpp +++ b/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.cpp @@ -7,6 +7,7 @@ #include "LuaBinding/modern/Window.hpp" #include "LuaBinding/modern/SwapChain.hpp" #include "LuaBinding/modern/Texture2D.hpp" +#include "LuaBinding/modern/Video.hpp" #include "LuaBinding/modern/RenderTarget.hpp" #include "LuaBinding/modern/DepthStencilBuffer.hpp" #include "LuaBinding/modern/Mesh.hpp" @@ -82,6 +83,7 @@ namespace luastg::binding Window_Windows11Extension::registerClass(L); SwapChain::registerClass(L); Texture2D::registerClass(L); + Video::registerClass(L); RenderTarget::registerClass(L); DepthStencilBuffer::registerClass(L); Mesh::registerClass(L); diff --git a/LuaSTG/LuaSTG/LuaBinding/VideoBindingHelpers.cpp b/LuaSTG/LuaSTG/LuaBinding/VideoBindingHelpers.cpp new file mode 100644 index 000000000..904bae9a6 --- /dev/null +++ b/LuaSTG/LuaSTG/LuaBinding/VideoBindingHelpers.cpp @@ -0,0 +1,18 @@ +#include "VideoBindingHelpers.hpp" +#include "AppFrame.h" + +namespace luastg::binding::video { + core::IVideoDecoder* getDecoderFromResourceName(const char* name) noexcept { + auto texture = LRES.FindTexture(name); + if (!texture) { + return nullptr; + } + + auto texture2d = texture->GetTexture(); + if (!texture2d) { + return nullptr; + } + + return getDecoderFromTexture(texture2d); + } +} diff --git a/LuaSTG/LuaSTG/LuaBinding/VideoBindingHelpers.hpp b/LuaSTG/LuaSTG/LuaBinding/VideoBindingHelpers.hpp new file mode 100644 index 000000000..7fb9d95a2 --- /dev/null +++ b/LuaSTG/LuaSTG/LuaBinding/VideoBindingHelpers.hpp @@ -0,0 +1,123 @@ +#pragma once +#include "core/VideoDecoder.hpp" +#include "core/Texture2D.hpp" +#include "lua/plus.hpp" +#include + +namespace luastg::binding::video { + // Parse VideoOpenOptions from Lua table + inline void parseVideoOptions(lua_State* L, int index, core::VideoOpenOptions& opt) noexcept { + lua::stack_t stack(L); + lua::stack_index_t table_idx(index); + + if (!stack.is_table(table_idx)) { + return; + } + + opt.video_stream_index = stack.get_map_value(table_idx, "video_stream", opt.video_stream_index); + opt.output_width = stack.get_map_value(table_idx, "width", opt.output_width); + opt.output_height = stack.get_map_value(table_idx, "height", opt.output_height); + opt.premultiplied_alpha = stack.get_map_value(table_idx, "premultiplied_alpha", opt.premultiplied_alpha); + opt.looping = stack.get_map_value(table_idx, "looping", opt.looping); + opt.loop_end = stack.get_map_value(table_idx, "loop_end", opt.loop_end); + opt.loop_duration = stack.get_map_value(table_idx, "loop_duration", opt.loop_duration); + } + + // Get video decoder from texture object + inline core::IVideoDecoder* getDecoderFromTexture(core::ITexture2D* texture) noexcept { + if (!texture || !texture->isVideoTexture()) { + return nullptr; + } + return texture->getVideoDecoder(); + } + + // Get video decoder from resource name (via resource manager) + core::IVideoDecoder* getDecoderFromResourceName(const char* name) noexcept; + + // Push video stream info array to Lua stack + inline void pushVideoStreamsToLua(lua_State* L, core::IVideoDecoder* decoder) { + lua::stack_t stack(L); + + if (!decoder) { + stack.create_array(0); + return; + } + + std::vector list; + auto callback = [](core::VideoStreamInfo const& info, void* userdata) { + static_cast*>(userdata)->push_back(info); + }; + decoder->getVideoStreams(callback, &list); + + auto array_idx = stack.create_array(list.size()); + for (size_t i = 0; i < list.size(); ++i) { + auto item_idx = stack.create_map(5); + stack.set_map_value(item_idx, "index", list[i].index); + stack.set_map_value(item_idx, "width", list[i].width); + stack.set_map_value(item_idx, "height", list[i].height); + stack.set_map_value(item_idx, "fps", list[i].fps); + stack.set_map_value(item_idx, "duration", list[i].duration_seconds); + stack.set_array_value(array_idx, i + 1, item_idx); + } + } + + // Push audio stream info array to Lua stack + inline void pushAudioStreamsToLua(lua_State* L, core::IVideoDecoder* decoder) { + lua::stack_t stack(L); + + if (!decoder) { + stack.create_array(0); + return; + } + + std::vector list; + auto callback = [](core::AudioStreamInfo const& info, void* userdata) { + static_cast*>(userdata)->push_back(info); + }; + decoder->getAudioStreams(callback, &list); + + auto array_idx = stack.create_array(list.size()); + for (size_t i = 0; i < list.size(); ++i) { + auto item_idx = stack.create_map(4); + stack.set_map_value(item_idx, "index", list[i].index); + stack.set_map_value(item_idx, "channels", list[i].channels); + stack.set_map_value(item_idx, "sample_rate", list[i].sample_rate); + stack.set_map_value(item_idx, "duration", list[i].duration_seconds); + stack.set_array_value(array_idx, i + 1, item_idx); + } + } + + // Push video info table to Lua stack + inline void pushVideoInfoToLua(lua_State* L, core::IVideoDecoder* decoder) { + lua::stack_t stack(L); + + if (!decoder) { + stack.create_map(0); + return; + } + + auto map_idx = stack.create_map(10); + + stack.set_map_value(map_idx, "duration", decoder->getDuration()); + stack.set_map_value(map_idx, "time", decoder->getCurrentTime()); + stack.set_map_value(map_idx, "looping", decoder->isLooping()); + + double loop_end = 0.0, loop_duration = 0.0; + decoder->getLoopRange(&loop_end, &loop_duration); + stack.set_map_value(map_idx, "loop_end", loop_end); + stack.set_map_value(map_idx, "loop_duration", loop_duration); + + auto size = decoder->getVideoSize(); + stack.set_map_value(map_idx, "width", size.x); + stack.set_map_value(map_idx, "height", size.y); + + stack.set_map_value(map_idx, "video_stream", decoder->getVideoStreamIndex()); + + double frame_interval = decoder->getFrameInterval(); + stack.set_map_value(map_idx, "frame_interval", frame_interval); + + if (frame_interval > 0.0) { + stack.set_map_value(map_idx, "fps", 1.0 / frame_interval); + } + } +} diff --git a/LuaSTG/LuaSTG/LuaBinding/modern/Video.cpp b/LuaSTG/LuaSTG/LuaBinding/modern/Video.cpp new file mode 100644 index 000000000..852e0ed51 --- /dev/null +++ b/LuaSTG/LuaSTG/LuaBinding/modern/Video.cpp @@ -0,0 +1,289 @@ +#include "Video.hpp" +#include "Texture2D.hpp" +#include "VideoBindingHelpers.hpp" +#include "lua/plus.hpp" +#include "AppFrame.h" +#include "core/VideoDecoder.hpp" + +namespace luastg::binding { + const std::string_view Video::class_name{ "lstg.Video" }; + + struct VideoBinding : Video { + // meta methods + + // NOLINTBEGIN(*-reserved-identifier) + + static int __gc(lua_State* vm) { + if (auto const self = as(vm, 1); self->data) { + self->data->release(); + self->data = nullptr; + } + return 0; + } + static int __tostring(lua_State* vm) { + lua::stack_t const ctx(vm); + [[maybe_unused]] auto const self = as(vm, 1); + ctx.push_value(class_name); + return 1; + } + static int __eq(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (is(vm, 2)) { + auto const other = as(vm, 2); + ctx.push_value(self->data == other->data); + } else { + ctx.push_value(false); + } + return 1; + } + + // NOLINTEND(*-reserved-identifier) + + // method - video info + + static int getWidth(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + ctx.push_value(self->data->getSize().x); + return 1; + } + static int getHeight(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + ctx.push_value(self->data->getSize().y); + return 1; + } + + template + static int withDecoder(lua_State* vm, Func&& func) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + return func(ctx, decoder); + } + ctx.push_value(0.0); + return 1; + } + + static int getDuration(lua_State* vm) { + return withDecoder(vm, [](auto& ctx, auto decoder) { + ctx.push_value(decoder->getDuration()); + return 1; + }); + } + static int getCurrentTime(lua_State* vm) { + return withDecoder(vm, [](auto& ctx, auto decoder) { + ctx.push_value(decoder->getCurrentTime()); + return 1; + }); + } + static int getFPS(lua_State* vm) { + return withDecoder(vm, [](auto& ctx, auto decoder) { + auto interval = decoder->getFrameInterval(); + ctx.push_value(interval > 0.0 ? 1.0 / interval : 0.0); + return 1; + }); + } + static int isLooping(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + ctx.push_value(decoder->isLooping()); + } else { + ctx.push_value(false); + } + return 1; + } + static int getTexture(lua_State* vm) { + auto const self = as(vm, 1); + auto const texture = Texture2D::create(vm); + texture->data = self->data; + if (texture->data) { + texture->data->retain(); + } + return 1; + } + + // method - playback control + + static int seek(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + auto const time = ctx.get_value(2); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + ctx.push_value(decoder->seek(time)); + } else { + ctx.push_value(false); + } + return 1; + } + static int update(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + auto const time = ctx.get_value(2); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + ctx.push_value(decoder->updateToTime(time)); + } else { + ctx.push_value(false); + } + return 1; + } + static int setLooping(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + auto const loop = ctx.get_value(2); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + decoder->setLooping(loop); + } + return 0; + } + static int setLoopRange(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + auto const loop_end = ctx.get_value(2); + auto const loop_duration = ctx.get_value(3); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + decoder->setLoopRange(loop_end, loop_duration); + } + return 0; + } + static int getLoopRange(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + double loop_end = 0.0, loop_duration = 0.0; + decoder->getLoopRange(&loop_end, &loop_duration); + ctx.push_value(loop_end); + ctx.push_value(loop_duration); + return 2; + } + ctx.push_value(0.0); + ctx.push_value(0.0); + return 2; + } + + // method - stream info + + static int getVideoStreams(lua_State* vm) { + auto const self = as(vm, 1); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + video::pushVideoStreamsToLua(vm, decoder); + } else { + lua::stack_t const ctx(vm); + ctx.create_array(0); + } + return 1; + } + static int getAudioStreams(lua_State* vm) { + auto const self = as(vm, 1); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + video::pushAudioStreamsToLua(vm, decoder); + } else { + lua::stack_t const ctx(vm); + ctx.create_array(0); + } + return 1; + } + static int getVideoStreamIndex(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + ctx.push_value(decoder->getVideoStreamIndex()); + } else { + ctx.push_value(0); + } + return 1; + } + static int reopen(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + core::VideoOpenOptions opt = decoder->getLastOpenOptions(); + if (ctx.index_of_top() >= 2) { + video::parseVideoOptions(vm, 2, opt); + } + ctx.push_value(decoder->reopen(opt)); + } else { + ctx.push_value(false); + } + return 1; + } + + // static method + + static int create(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const path = ctx.get_value(1); + + core::VideoOpenOptions opt{}; + if (ctx.index_of_top() >= 2) { + video::parseVideoOptions(vm, 2, opt); + } + + core::SmartReference texture; + if (!LAPP.getGraphicsDevice()->createVideoTexture(path, opt, texture.put())) { + auto const error_message = std::format( + "create Video from file '{}' failed", path); + return luaL_error(vm, error_message.c_str()); + } + auto const self = Video::create(vm); + self->data = texture.detach(); + return 1; + } + }; + + bool Video::is(lua_State* vm, int const index) { + lua::stack_t const ctx(vm); + return ctx.is_metatable(index, class_name); + } + Video* Video::as(lua_State* vm, int const index) { + lua::stack_t const ctx(vm); + return ctx.as_userdata