From dd2d7b618f18e7f961c38f0634c98e228f82b326 Mon Sep 17 00:00:00 2001 From: Mariusz Plucinski Date: Fri, 6 Apr 2018 17:50:08 +0200 Subject: [PATCH 1/6] Add stub for video playing function --- CMakeLists.txt | 2 ++ src/logic/scriptExternals/Stubs.cpp | 6 +++--- src/media/Video.cpp | 11 +++++++++++ src/media/Video.h | 13 +++++++++++++ src/ui/Menu.cpp | 14 ++++++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 src/media/Video.cpp create mode 100644 src/media/Video.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 43ece235..4be38681 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,6 +194,8 @@ file(GLOB ENGINE_SRC "src/utils/*.h" "src/math/*.cpp" "src/math/*.h" + "src/media/*.cpp" + "src/media/*.h" "src/ui/*.cpp" "src/ui/*.h" "src/logic/*.cpp" diff --git a/src/logic/scriptExternals/Stubs.cpp b/src/logic/scriptExternals/Stubs.cpp index 3fbb1122..d4877a9d 100644 --- a/src/logic/scriptExternals/Stubs.cpp +++ b/src/logic/scriptExternals/Stubs.cpp @@ -2,6 +2,8 @@ #include #include +#include + void ::Logic::ScriptExternals::registerStubs(Daedalus::DaedalusVM& vm, bool verbose) { vm.registerExternalFunction("npc_getequippedarmor", [=](Daedalus::DaedalusVM& vm) { @@ -839,9 +841,7 @@ void ::Logic::ScriptExternals::registerStubs(Daedalus::DaedalusVM& vm, bool verb }); vm.registerExternalFunction("playvideo", [=](Daedalus::DaedalusVM& vm) { - if (verbose) LogInfo() << "playvideo"; - std::string filename = vm.popString(); - if (verbose) LogInfo() << "filename: " << filename; + Video::playVideo(vm.popString()); // this function is actually declared as int, but the return value is never used in the original scripts // and the Gothic compiler doesn't pop unused expressions vm.setReturn(0); diff --git a/src/media/Video.cpp b/src/media/Video.cpp new file mode 100644 index 00000000..91382ec5 --- /dev/null +++ b/src/media/Video.cpp @@ -0,0 +1,11 @@ +// +// Created by mplucinski on 06.04.18. +// + +#include +#include "Video.h" + +void Video::playVideo(const std::string &fileName) +{ + std::cout << "playVideo(" << fileName << ")" << std::endl; +} diff --git a/src/media/Video.h b/src/media/Video.h new file mode 100644 index 00000000..bca8e9dc --- /dev/null +++ b/src/media/Video.h @@ -0,0 +1,13 @@ +// +// Created by mplucinski on 06.04.18. +// + +#pragma once + +#include + +namespace Video { + +void playVideo(const std::string &fileName); + +} diff --git a/src/ui/Menu.cpp b/src/ui/Menu.cpp index b7e6a0f5..11a9f3bc 100644 --- a/src/ui/Menu.cpp +++ b/src/ui/Menu.cpp @@ -9,6 +9,7 @@ #include "MenuItemListbox.h" #include "MenuItemScrollableText.h" #include "TextView.h" +#include #include #include #include @@ -179,6 +180,11 @@ bool UI::Menu::loadMenuDAT() m_pVM = new Daedalus::DaedalusVM(datFile); Daedalus::registerGothicEngineClasses(*m_pVM); + m_pVM->registerExternalFunction("playvideo", [=](Daedalus::DaedalusVM &vm){ + Video::playVideo(vm.popString()); + vm.setReturn(0); + }); + return true; } @@ -333,6 +339,14 @@ void UI::Menu::performSelectAction(Daedalus::GameState::MenuItemHandle item) std::string customFn = iData->getItemScriptData().onSelAction_S[0]; if (!customFn.empty()) onCustomAction(customFn); + + for (const auto &eventAction: iData->getItemScriptData().onEventAction) + { + if (eventAction != 0) { // FIXME: any better way to check if the field was not initialized? + m_pVM->prepareRunFunction(); + m_pVM->runFunctionBySymIndex(eventAction); + } + } } UI::Hud& UI::Menu::getHud() { From f6c946cdcb39d0d296fc4e88732b061d1b76eaf8 Mon Sep 17 00:00:00 2001 From: Mariusz Plucinski Date: Sat, 2 Jun 2018 17:41:18 +0200 Subject: [PATCH 2/6] Add FFMPEG submodule --- .gitmodules | 3 +++ .travis.yml | 3 ++- cmake/ffmpeg.cmake | 35 +++++++++++++++++++++++++++++++++++ lib/ffmpeg | 1 + 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 cmake/ffmpeg.cmake create mode 160000 lib/ffmpeg diff --git a/.gitmodules b/.gitmodules index d0c91b5a..fc488956 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "lib/libdmusic"] path = lib/libdmusic url = https://github.com/frabert/libdmusic.git +[submodule "lib/ffmpeg"] + path = lib/ffmpeg + url = https://github.com/FFmpeg/FFmpeg.git diff --git a/.travis.yml b/.travis.yml index f6a76843..1c722c27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,7 @@ matrix: - libasound2-dev - alsa-utils - alsa-oss + - nasm script: ./bin/build_unix.sh before_deploy: ./bin/archive_unix.sh - os: linux @@ -81,6 +82,6 @@ matrix: compiler: clang before_install: - brew update - - brew install libsndfile + - brew install libsndfile nasm script: ./bin/build_unix.sh before_deploy: ./bin/archive_unix.sh diff --git a/cmake/ffmpeg.cmake b/cmake/ffmpeg.cmake new file mode 100644 index 00000000..9d146092 --- /dev/null +++ b/cmake/ffmpeg.cmake @@ -0,0 +1,35 @@ +include(ExternalProject) + +function(FFMPEG_BUILD SOURCE_DIR BUILD_DIR) + if (UNIX) + set(BINARY_DIR "${BUILD_DIR}/build") + set(INSTALL_DIR "${BUILD_DIR}/install") + + if(NOT EXISTS "${BINARY_DIR}") + file(MAKE_DIRECTORY "${BINARY_DIR}") + endif() + + ExternalProject_Add(FFMPEG_PROJECT + SOURCE_DIR "${SOURCE_DIR}" + CONFIGURE_COMMAND "${SOURCE_DIR}/configure" "--prefix=\"${INSTALL_DIR}\"" + --disable-iconv + --disable-programs + --disable-doc + --disable-everything + BINARY_DIR "${BINARY_DIR}" + BUILD_COMMAND make + ) + + foreach(LIBRARY ${ARGN}) + add_library(FFMPEG_${LIBRARY} IMPORTED STATIC) + set_property(TARGET FFMPEG_${LIBRARY} + PROPERTY IMPORTED_LOCATION "${INSTALL_DIR}/lib/lib${LIBRARY}.a" + ) + add_dependencies(FFMPEG_${LIBRARY} FFMPEG_PROJECT) + endforeach() + + set(FFMPEG_ENABLED 1 PARENT_SCOPE) + else() + message(WARNING "No support for video player on this platform") + endif() +endfunction() diff --git a/lib/ffmpeg b/lib/ffmpeg new file mode 160000 index 00000000..e28b1fa6 --- /dev/null +++ b/lib/ffmpeg @@ -0,0 +1 @@ +Subproject commit e28b1fa6e99c9a50d1b647c5418e5f75a3f957e4 From dbf37dc3703ffff7254a38e8ea912930d35cd9dc Mon Sep 17 00:00:00 2001 From: Mariusz Plucinski Date: Sat, 7 Apr 2018 13:02:04 +0200 Subject: [PATCH 3/6] Preliminary video playing (from menu only) --- CMakeLists.txt | 11 + cmake/ffmpeg.cmake | 18 +- src/content/Texture.cpp | 7 + src/content/Texture.h | 2 + src/engine/BaseEngine.cpp | 7 +- src/engine/BaseEngine.h | 7 + src/logic/scriptExternals/Externals.cpp | 22 ++ src/logic/scriptExternals/Stubs.cpp | 20 -- src/media/Video.cpp | 298 +++++++++++++++++++++++- src/media/Video.h | 22 +- src/ui/Menu.cpp | 2 +- 11 files changed, 385 insertions(+), 31 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4be38681..14c6fe24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -289,6 +289,17 @@ include_directories(${CMAKE_SOURCE_DIR}/lib/libdmusic/include) target_link_libraries(engine dmusic) +# ------------------ FFmpeg ------------------ + +include("cmake/ffmpeg.cmake") +ffmpeg_build("${CMAKE_CURRENT_SOURCE_DIR}/lib/ffmpeg" "${CMAKE_CURRENT_BINARY_DIR}/lib/ffmpeg" + avcodec avutil avformat +) +if (FFMPEG_ENABLED) + add_definitions(-DRE_ENABLE_FFMPEG) + target_link_libraries(engine FFMPEG_avformat FFMPEG_avcodec FFMPEG_avutil) +endif() + # ------------------ Other ------------------ include_directories(src) diff --git a/cmake/ffmpeg.cmake b/cmake/ffmpeg.cmake index 9d146092..57544d1e 100644 --- a/cmake/ffmpeg.cmake +++ b/cmake/ffmpeg.cmake @@ -1,7 +1,8 @@ -include(ExternalProject) function(FFMPEG_BUILD SOURCE_DIR BUILD_DIR) if (UNIX) + include(ExternalProject) + set(BINARY_DIR "${BUILD_DIR}/build") set(INSTALL_DIR "${BUILD_DIR}/install") @@ -16,15 +17,24 @@ function(FFMPEG_BUILD SOURCE_DIR BUILD_DIR) --disable-programs --disable-doc --disable-everything + --disable-zlib + --enable-demuxer=bink + --enable-decoder=bink,binkaudio_dct,binkaudio_rdft + --enable-protocol=file BINARY_DIR "${BINARY_DIR}" BUILD_COMMAND make ) foreach(LIBRARY ${ARGN}) + set(LOCATION "${INSTALL_DIR}/lib/lib${LIBRARY}.a") + set(INCLUDE_DIR "${INSTALL_DIR}/include") + message(STATUS "FFMPEG library ${LIBRARY} expected at ${LOCATION} (include path: ${INCLUDE_DIR})") + add_library(FFMPEG_${LIBRARY} IMPORTED STATIC) - set_property(TARGET FFMPEG_${LIBRARY} - PROPERTY IMPORTED_LOCATION "${INSTALL_DIR}/lib/lib${LIBRARY}.a" - ) + set_property(TARGET FFMPEG_${LIBRARY} PROPERTY IMPORTED_LOCATION "${LOCATION}") + + file(MAKE_DIRECTORY "${INCLUDE_DIR}") # workaround for CMake Issue #15052 + set_property(TARGET FFMPEG_${LIBRARY} PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${INCLUDE_DIR}") add_dependencies(FFMPEG_${LIBRARY} FFMPEG_PROJECT) endforeach() diff --git a/src/content/Texture.cpp b/src/content/Texture.cpp index 9b07de56..0fbd3e71 100644 --- a/src/content/Texture.cpp +++ b/src/content/Texture.cpp @@ -174,6 +174,13 @@ Handle::TextureHandle TextureAllocator::loadTextureVDF(const std::string& name) return loadTextureVDF(m_Engine.getVDFSIndex(), name); } +void TextureAllocator::asyncFinalizeLoad(Handle::TextureHandle h) +{ + m_Engine.getJobManager().executeInMainThread([this, h](Engine::BaseEngine* pEngine) { + finalizeLoad(h); + }); +} + bool TextureAllocator::finalizeLoad(Handle::TextureHandle h) { Texture& tx = m_Allocator.getElement(h); diff --git a/src/content/Texture.h b/src/content/Texture.h index 126d1ef0..3aed44b8 100644 --- a/src/content/Texture.h +++ b/src/content/Texture.h @@ -57,6 +57,8 @@ namespace Textures * @return Rough estimation about how much memory the loaded textures need on the GPU in bytes */ size_t getEstimatedGPUMemoryConsumption() { return m_EstimatedGPUBytes; } + + void asyncFinalizeLoad(Handle::TextureHandle h); protected: /** * Pushes the loaded data to the GPU. Needs to run on the main-thread. diff --git a/src/engine/BaseEngine.cpp b/src/engine/BaseEngine.cpp index a43cbbf8..1a9f53fc 100644 --- a/src/engine/BaseEngine.cpp +++ b/src/engine/BaseEngine.cpp @@ -118,6 +118,8 @@ void BaseEngine::initEngine(int argc, char** argv) m_AudioEngine = new Audio::AudioEngine(snd_device); + m_VideoPlayer = new Media::VideoPlayer(*this); + // Init HUD m_pFontCache = new UI::zFontCache(*this); m_pHUD = new UI::Hud(*this); @@ -126,7 +128,10 @@ void BaseEngine::initEngine(int argc, char** argv) void BaseEngine::frameUpdate(double dt, uint16_t width, uint16_t height) { - onFrameUpdate(dt * getGameClock().getGameEngineSpeedFactor(), width, height); + if (m_VideoPlayer->active()) + m_VideoPlayer->frameUpdate(dt, width, height); + else + onFrameUpdate(dt * getGameClock().getGameEngineSpeedFactor(), width, height); } void BaseEngine::loadArchives() diff --git a/src/engine/BaseEngine.h b/src/engine/BaseEngine.h index 1101be79..343f6af3 100644 --- a/src/engine/BaseEngine.h +++ b/src/engine/BaseEngine.h @@ -9,8 +9,10 @@ #include #include #include +#include #include #include +#include namespace UI { @@ -116,6 +118,9 @@ namespace Engine * @return Base-level UI-View. Parent of all other views. */ UI::View& getRootUIView() { return m_RootUIView; } + + Media::VideoPlayer& getVideoPlayer() { return *m_VideoPlayer; } + /** * // TODO: Move to GameEngine, or pass GameEngine to world! * @return HUD @@ -220,6 +225,8 @@ namespace Engine Audio::AudioEngine* m_AudioEngine = nullptr; + Media::VideoPlayer* m_VideoPlayer = nullptr; + /** * Base UI-View */ diff --git a/src/logic/scriptExternals/Externals.cpp b/src/logic/scriptExternals/Externals.cpp index 0e5122b6..7c6ec6a6 100644 --- a/src/logic/scriptExternals/Externals.cpp +++ b/src/logic/scriptExternals/Externals.cpp @@ -1503,4 +1503,26 @@ void ::Logic::ScriptExternals::registerEngineExternals(World::WorldInstance& wor npc.playerController->drawWeaponMelee(true); } }); + + vm->registerExternalFunction("playvideo", [=](Daedalus::DaedalusVM& vm) { + engine->getVideoPlayer().play(vm.popString()); + // this function is actually declared as int, but the return value is never used in the original scripts + // and the Gothic compiler doesn't pop unused expressions + vm.setReturn(0); + }); + + vm->registerExternalFunction("playvideoex", [=](Daedalus::DaedalusVM& vm) { + if (verbose) LogInfo() << "playvideoex"; + int exitsession = vm.popDataValue(); + if (verbose) LogInfo() << "exitsession: " << exitsession; + int screenblend = vm.popDataValue(); + if (verbose) LogInfo() << "screenblend: " << screenblend; + std::string filename = vm.popString(); + if (verbose) LogInfo() << "filename: " << filename; + + engine->getVideoPlayer().play(filename); // TODO: use exitseesion and screenblend parameters + // this function is actually declared as int, but the return value is never used in the original scripts + // and the Gothic compiler doesn't pop unused expressions + vm.setReturn(0); + }); } diff --git a/src/logic/scriptExternals/Stubs.cpp b/src/logic/scriptExternals/Stubs.cpp index d4877a9d..b5cd833a 100644 --- a/src/logic/scriptExternals/Stubs.cpp +++ b/src/logic/scriptExternals/Stubs.cpp @@ -840,26 +840,6 @@ void ::Logic::ScriptExternals::registerStubs(Daedalus::DaedalusVM& vm, bool verb vm.setReturn(0); }); - vm.registerExternalFunction("playvideo", [=](Daedalus::DaedalusVM& vm) { - Video::playVideo(vm.popString()); - // this function is actually declared as int, but the return value is never used in the original scripts - // and the Gothic compiler doesn't pop unused expressions - vm.setReturn(0); - }); - - vm.registerExternalFunction("playvideoex", [=](Daedalus::DaedalusVM& vm) { - if (verbose) LogInfo() << "playvideoex"; - int exitsession = vm.popDataValue(); - if (verbose) LogInfo() << "exitsession: " << exitsession; - int screenblend = vm.popDataValue(); - if (verbose) LogInfo() << "screenblend: " << screenblend; - std::string filename = vm.popString(); - if (verbose) LogInfo() << "filename: " << filename; - // this function is actually declared as int, but the return value is never used in the original scripts - // and the Gothic compiler doesn't pop unused expressions - vm.setReturn(0); - }); - vm.registerExternalFunction("printdialog", [=](Daedalus::DaedalusVM& vm) { if (verbose) LogInfo() << "printdialog"; int i5 = vm.popDataValue(); diff --git a/src/media/Video.cpp b/src/media/Video.cpp index 91382ec5..f70f2518 100644 --- a/src/media/Video.cpp +++ b/src/media/Video.cpp @@ -3,9 +3,303 @@ // #include + +#include +#include +#include +#include #include "Video.h" +#include "ui/ImageView.h" + +#ifdef RE_ENABLE_FFMPEG +extern "C" { +#include +#include +#include +} + +namespace { + +struct deleteAVCodecContext +{ + void operator()(AVCodecContext *ptr) + { + if (ptr) + avcodec_free_context(&ptr); + } +}; + +struct deleteAVFormatContext +{ + void operator()(AVFormatContext *ptr) + { + if (ptr) + avformat_close_input(&ptr); + } +}; -void Video::playVideo(const std::string &fileName) +struct deleteAVFrame { - std::cout << "playVideo(" << fileName << ")" << std::endl; + void operator()(AVFrame *ptr) + { + if (ptr) + av_frame_free(&ptr); + } +}; + +using AVCodecContextPtr = std::unique_ptr; +using AVFormatContextPtr = std::unique_ptr; +using AVFramePtr = std::unique_ptr; + +} + +namespace Media { + class Video + { + public: + Video(VideoPlayer &player, Engine::BaseEngine &engine, const std::string &fileName) + : player{player}, engine{engine}, fileName{fileName} + { + av_register_all(); + + view = new UI::ImageView(engine); + view->setHidden(true); + view->setRelativeSize(false); + + engine.getRootUIView().addChild(view); + } + + bool init(const uint16_t width, const uint16_t height) + { + frame.reset(av_frame_alloc()); + if (!frame) { + LogWarn() << "Could not allocate frame"; + return false; + } + + av_init_packet(&packet); + packet.data = nullptr; + packet.size = 0; + + std::string filePath = "/_work/data/video/" + fileName; + std::string fileFullPath = Utils::getCaseSensitivePath(filePath, engine.getEngineArgs().gameBaseDirectory); + + if (!Utils::fileExists(fileFullPath)) + { + LogError() << "Failed to find the video file at: " << fileFullPath; + return false; + } + + AVFormatContext *formatContextPtr = nullptr; + if (avformat_open_input(&formatContextPtr, fileFullPath.c_str(), nullptr, nullptr) < 0) { + LogWarn() << "Could not open file: " << fileFullPath; + return false; + } + formatContext.reset(formatContextPtr); + + if (avformat_find_stream_info(formatContext.get(), nullptr) < 0) { + LogWarn() << "Could not find stream information"; + return false; + } + + if (!openCodecContext(videoStreamIndex, videoCodecContext, AVMEDIA_TYPE_VIDEO)) { + LogWarn() << "Could not open video context"; + return false; + } + + if (!openCodecContext(audioStreamIndex, audioCodecContext, AVMEDIA_TYPE_AUDIO)) { + LogWarn() << "Could not open audio context"; + return false; + } + + av_dump_format(formatContext.get(), 0, fileFullPath.c_str(), 0); // TODO: dump to logger + + initialized_ = true; + return true; + } + + private: + bool openCodecContext(int &streamIndex, AVCodecContextPtr &codecContext, AVMediaType mediaType) + { + int index = av_find_best_stream(formatContext.get(), mediaType, -1, -1, nullptr, 0); + if (index < 0) { + LogWarn() << "Could not find stream" << av_get_media_type_string(mediaType); + return false; + } + + AVStream *stream = formatContext->streams[index]; + + AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id); + if (!codec) { + LogWarn() << "Could not find codec for stream" << av_get_media_type_string(mediaType); + return false; + } + + AVCodecContextPtr context{avcodec_alloc_context3(codec)}; + if (!context) { + LogWarn() << "Could not allocate codec context for stream" << av_get_media_type_string(mediaType); + return false; + } + + if (avcodec_parameters_to_context(context.get(), stream->codecpar) < 0) { + LogWarn() << "Could not copy decoder parameters to decoder context for stream" << av_get_media_type_string(mediaType); + return false; + } + + AVDictionary *opts = nullptr; + av_dict_set(&opts, "refcounted_frames", "0", 0); + if (avcodec_open2(context.get(), codec, &opts) < 0) { + LogWarn() << "Could not open codec for stream" << av_get_media_type_string(mediaType); + return false; + } + + streamIndex = index; + std::swap(codecContext, context); + return true; + } + + public: + void nextFrame() + { + int r = av_read_frame(formatContext.get(), &packet); + if (r < 0) {// no more frames or error + std::cout << "nextframe abort 1" << std::endl; + ::abort(); + } + + if (packet.stream_index == videoStreamIndex) { + r = avcodec_send_packet(videoCodecContext.get(), &packet); + if(r < 0 || r == AVERROR(EAGAIN) || r == AVERROR_EOF) { + std::cout << "avcodec_send_packet: " << r << std::endl; + ::abort(); + } + + while (r >= 0) { + r = avcodec_receive_frame(videoCodecContext.get(), frame.get()); + if (r == AVERROR(EAGAIN) || r == AVERROR_EOF) { + std::cout << "avcodec_receive_frame: " << r << std::endl; + break; + } + std::cout << "video frame: " << videoCodecContext->frame_number << std::endl; + + view->setHidden(false); + view->setSize(Math::float2(1,1)); + + if (videoCodecContext->pix_fmt != AV_PIX_FMT_YUV420P) { + LogWarn() << "Unknown pixel format"; + return; + } + + Textures::TextureAllocator& alloc = engine.getEngineTextureAlloc(); + + std::vector data; + int count = videoCodecContext->width * videoCodecContext->height; + auto clamp = [](int x){ return std::min(255, std::max(0, x)); }; + + // conversion from YUV420p to RGBA + // TODO: this can be probably accelerated by doing conversion within the fragment shader + + uint8_t *src = frame->data[0]; + int linesize = frame->linesize[0]; + int w = videoCodecContext->width; + int h = videoCodecContext->height; + + uint8_t *dataY = frame->data[0]; + int linesizeY = frame->linesize[0]; + + uint8_t *dataU = frame->data[1]; + int linesizeU = frame->linesize[1]; + + uint8_t *dataV = frame->data[2]; + int linesizeV = frame->linesize[2]; + + for (int y = 0; y < h; ++y) + for (int x = 0; x < w; ++x) { + int Y = dataY[y*linesizeY+x]; + int U = dataU[y/2*linesizeU+x/2]; + int V = dataV[y/2*linesizeV+x/2]; + + int C = Y - 16; + int D = U - 128; + int E = V - 128; + + int R = clamp( (298*C + 409*E + 128 ) >> 8 ); + int G = clamp( (298*C - 100*D - 208*E + 128 ) >> 8 ); + int B = clamp( (298*C + 516*D + 128 ) >> 8 ); + + data.push_back(R); + data.push_back(G); + data.push_back(B); + data.push_back(255); + } + + if (!texture.isValid()) + texture = alloc.loadTextureRGBA8(data, videoCodecContext->width, videoCodecContext->height); + else + alloc.getTexture(texture).imageData = data; + alloc.asyncFinalizeLoad(texture); + view->setImage(texture, videoCodecContext->width, videoCodecContext->height); + } + } else if(packet.stream_index == audioStreamIndex) { + std::cout << "An audio frame" << std::endl; + } else { + LogWarn() << "Invalid stream index"; + } + } + + bool initialized() const { + return initialized_; + } + + VideoPlayer &player; + Engine::BaseEngine &engine; + const std::string fileName; + bool initialized_ = false; + AVFormatContextPtr formatContext; + int videoStreamIndex, audioStreamIndex; + AVCodecContextPtr videoCodecContext, audioCodecContext; + AVFramePtr frame; + AVPacket packet; + Handle::TextureHandle texture; + UI::ImageView *view; + }; +} +#endif + +Media::VideoPlayer::VideoPlayer(Engine::BaseEngine &engine): engine{engine} +{ +} + +Media::VideoPlayer::~VideoPlayer() {} + +void Media::VideoPlayer::play(const std::string &fileName) +{ + std::cout << "playVideo(" << fileName << ")" << std::endl; +#ifdef RE_ENABLE_FFMPEG + currentVideo = std::make_unique