From 24b86ad3c6df561be48ed76503c0c99125355319 Mon Sep 17 00:00:00 2001 From: "Adam A. Jammary" Date: Sat, 16 Sep 2023 16:42:46 +0200 Subject: [PATCH] Release 3 (#8) * fix: close crashes when no audio driver is set * fix: allow LVP_Quit to be called multiple times without throwing an exception if not initialized * improve: exception handling when requesting media details for an invalid media file * fix: sub-scaling relative to video size * improve: send callback event when disabling subs * add: thumbnail to meta data * add: public methods to get media type of a specific file * improve: stream info parsing of media format context * fix: some meta data newline characters are not double-escaped * fix: thumbnail creation sometimes fails because of inaccurate pitch size * improve: add external subs to meta data * update: version to 1.3.0 (#3) * fix: audio filter fails for wma files (#4) * improve: ignore id3v2_priv (private) meta tags (#5) * add: public API endpoint to set mute state (#6) * improve: support for cross-platform build (#7) --- CMakeLists.txt | 213 +++++++++--- README.md | 214 +++++++++--- inc/libvoyaplayer.h | 100 ++++-- inc/libvoyaplayer_events.h | 5 + src/LVP_Color.cpp | 4 +- src/LVP_FileSystem.cpp | 23 +- src/LVP_Global.h | 40 ++- src/LVP_Media.cpp | 201 +++++++++++- src/LVP_Media.h | 3 + src/LVP_Player.cpp | 182 ++++++----- src/LVP_Player.h | 2 + src/LVP_SubStyle.cpp | 9 +- src/LVP_SubTextRenderer.cpp | 28 +- src/LVP_Text.cpp | 21 +- src/LVP_Text.h | 27 ++ src/main.cpp | 152 ++++++--- test/TestPlayer.cpp | 41 +-- test/TestPlayer.h | 9 +- test/TestWindow.cpp | 628 +++++------------------------------- test/TestWindow.h | 299 +++++++---------- test/main.cpp | 296 ++++------------- windows/libvoyaplayer.rc | 8 +- 22 files changed, 1176 insertions(+), 1329 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ec4cef..5517d4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.12) project (testvoyaplayer) -project (libvoyaplayer) +project (voyaplayer) # Voya Player library depends on the following third-party libraries: # - sdl2, sdl2_ttf, ffmpeg, libaom, zlib @@ -9,24 +9,47 @@ project (libvoyaplayer) set(EXT_LIB_DIR "${EXT_LIB_DIR}") set(INC_DIR "${EXT_LIB_DIR}/include") set(LIB_DIR "${EXT_LIB_DIR}/lib") -set(CPP_VERSION "20") -if (APPLE) +set(CMAKE_CXX_STANDARD "20") +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if (ANDROID) + set(OS "_android") +elseif (IOS) + set(OS "_ios") + + set(CMAKE_CXX_EXTENSIONS OFF) + set(CMAKE_MACOSX_BUNDLE OFF) + + set(IOS_LIBS_SDL2 "-liconv -framework AudioToolbox -framework AVFAudio -framework CoreBluetooth -framework CoreFoundation -framework CoreGraphics -framework CoreHaptics -framework CoreMotion -framework Foundation -framework GameController -framework ImageIO -framework Metal -framework MobileCoreServices -framework OpenGLES -framework QuartzCore -framework UIKit") + set(IOS_LIBS_FFMPEG "-laom -framework CoreMedia -framework CoreVideo -framework Security -framework VideoToolbox") +elseif (APPLE) set(OS "_macosx") -elseif (UNIX) + + set(CMAKE_CXX_EXTENSIONS OFF) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -x objective-c++") + + set(MACOSX_LIBS "-framework AppKit") +elseif (LINUX) set(OS "_linux") + + set(GTK_INC /usr/include/gtk-3.0 /usr/include/pango-1.0 /usr/include/glib-2.0 /usr/lib/x86_64-linux-gnu/glib-2.0/include /usr/include/harfbuzz /usr/include/freetype2 /usr/include/libpng16 /usr/include/libmount /usr/include/blkid /usr/include/fribidi /usr/include/cairo /usr/include/pixman-1 /usr/include/gdk-pixbuf-2.0 /usr/include/x86_64-linux-gnu /usr/include/gio-unix-2.0 /usr/include/atk-1.0 /usr/include/at-spi2-atk/2.0 /usr/include/at-spi-2.0 /usr/include/dbus-1.0 /usr/lib/x86_64-linux-gnu/dbus-1.0/include) + set(GTK_LIB -lgtk-3 -lgdk-3 -lz -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -latk-1.0 -lcairo-gobject -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0) elseif (WIN32) - set(OS "_windows") + set(OS "_windows") + set(RC "windows/libvoyaplayer.rc") + set(DIRENT_DIR "${DIRENT_DIR}") - set(RC "windows/libvoyaplayer.rc") set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT testvoyaplayer) endif() if (WIN32) - set(LIBS SDL2.lib SDL2_ttf.lib avcodec.lib avdevice.lib avfilter.lib avformat.lib avutil.lib swresample.lib swscale.lib zlib.lib) + set(LIBS SDL2.lib SDL2_ttf.lib avcodec.lib avdevice.lib avfilter.lib avformat.lib avutil.lib swresample.lib swscale.lib zlib.lib) + set(TEST_LIBS SDL2.lib SDL2_ttf.lib) else() - set(LIBS -lSDL2 -lSDL2_ttf -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lswresample -lswscale -lz) + set(LIBS -lSDL2 -lSDL2_ttf -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lswresample -lswscale -lz) + set(TEST_LIBS -lSDL2 -lSDL2_ttf) endif() file(GLOB INC_H "inc/*.h") @@ -35,67 +58,155 @@ file(GLOB SOURCES_CPP "src/*.cpp") file(GLOB TEST_H "test/*.h") file(GLOB TEST_CPP "test/*.cpp") -add_library(libvoyaplayer SHARED ${SOURCES_CPP} ${SOURCES_H} ${INC_H} ${RC}) -target_compile_definitions(libvoyaplayer PRIVATE ${OS} _CRT_SECURE_NO_WARNINGS MAKEDLL) -target_include_directories(libvoyaplayer PRIVATE "inc" ${INC_DIR} ${DIRENT_DIR}) -target_link_directories(libvoyaplayer PRIVATE ${LIB_DIR}) -target_link_libraries(libvoyaplayer PRIVATE ${LIBS}) -set_property(TARGET libvoyaplayer PROPERTY CXX_STANDARD ${CPP_VERSION}) +if (IOS) + add_library(voyaplayer STATIC ${SOURCES_CPP} ${SOURCES_H} ${INC_H} ${RC}) +else() + add_library(voyaplayer SHARED ${SOURCES_CPP} ${SOURCES_H} ${INC_H} ${RC}) +endif() + +target_compile_definitions(voyaplayer PRIVATE ${OS} _CRT_SECURE_NO_WARNINGS NOMINMAX MAKEDLL) +target_include_directories(voyaplayer PRIVATE "inc" ${INC_DIR} ${DIRENT_DIR}) +target_link_directories(voyaplayer PRIVATE ${LIB_DIR}) +target_link_libraries(voyaplayer PRIVATE ${LIBS}) add_executable(testvoyaplayer ${TEST_CPP} ${TEST_H} ${INC_H}) -target_compile_definitions(testvoyaplayer PRIVATE ${OS}) -target_include_directories(testvoyaplayer PRIVATE "inc" ${INC_DIR}) +target_compile_definitions(testvoyaplayer PRIVATE ${OS} NOMINMAX) +target_include_directories(testvoyaplayer PRIVATE "inc" ${INC_DIR} ${GTK_INC}) target_link_directories(testvoyaplayer PRIVATE ${LIB_DIR}) -target_link_libraries(testvoyaplayer PRIVATE libvoyaplayer SDL2.lib) -set_property(TARGET testvoyaplayer PROPERTY CXX_STANDARD ${CPP_VERSION}) +target_link_libraries(testvoyaplayer PRIVATE voyaplayer ${TEST_LIBS} ${GTK_LIB} ${MACOSX_LIBS} ${IOS_LIBS_SDL2} ${IOS_LIBS_FFMPEG}) + +if (WIN32) + set_target_properties(testvoyaplayer PROPERTIES LINK_FLAGS_DEBUG "-SUBSYSTEM:CONSOLE,5.02") + set_target_properties(testvoyaplayer PROPERTIES LINK_FLAGS_RELEASE "-SUBSYSTEM:WINDOWS,5.02") +endif() set(BUILD_DIR "${CMAKE_BINARY_DIR}") set(SRC_DIR "${CMAKE_SOURCE_DIR}") set(DIST_DIR "${BUILD_DIR}/dist") -if (APPLE) - set(DLL_EXT "dylib") +if (IOS) + set(DLL_EXT "a") set(LIB_EXT "a") set(OUT_DIR "${BUILD_DIR}/out") -elseif (UNIX) - set(DLL_EXT "so") - set(LIB_EXT "a") + set(CFG_DIR "Release-iphoneos/") +elseif (APPLE) + set(DLL_EXT "dylib") + set(LIB_EXT "dylib") set(OUT_DIR "${BUILD_DIR}/out") + set(CFG_DIR "Release/") elseif (WIN32) set(DLL_EXT "dll") set(LIB_EXT "lib") set(OUT_DIR "${BUILD_DIR}/$(Configuration)") +else() + set(DLL_EXT "so") + set(LIB_EXT "so") + set(OUT_DIR "${BUILD_DIR}/out") +endif() + +if (WIN32) + set(LIBZ "zlib") + set(TEST_EXE "testvoyaplayer.exe") +else() + set(LIB_PREP "lib") + set(LIBZ "libz") + set(TEST_EXE "testvoyaplayer") endif() -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND if not exist \"${DIST_DIR}/bin\" mkdir \"${DIST_DIR}/bin\") -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND if not exist \"${DIST_DIR}/inc\" mkdir \"${DIST_DIR}/inc\") -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND if not exist \"${DIST_DIR}/lib\" mkdir \"${DIST_DIR}/lib\") -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND if not exist \"${OUT_DIR}\" mkdir \"${OUT_DIR}\") - -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/avcodec*.${DLL_EXT} ${OUT_DIR}) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/avdevice*.${DLL_EXT} ${OUT_DIR}) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/avfilter*.${DLL_EXT} ${OUT_DIR}) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/avformat*.${DLL_EXT} ${OUT_DIR}) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/avutil*.${DLL_EXT} ${OUT_DIR}) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/SDL2.${DLL_EXT} ${OUT_DIR}) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/SDL2_ttf.${DLL_EXT} ${OUT_DIR}) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/swresample*.${DLL_EXT} ${OUT_DIR}) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/swscale*.${DLL_EXT} ${OUT_DIR}) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/zlib.${DLL_EXT} ${OUT_DIR}) - -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${OUT_DIR}/*.${DLL_EXT} ${DIST_DIR}/bin/) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${SRC_DIR}/inc/*.h ${DIST_DIR}/inc/) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${OUT_DIR}/libvoyaplayer*.${LIB_EXT} ${DIST_DIR}/lib/) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${SRC_DIR}/LICENSE ${DIST_DIR}/LICENSE.txt) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${EXT_LIB_DIR}/LICENSE-ffmpeg.txt ${DIST_DIR}/) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${EXT_LIB_DIR}/LICENSE-libaom.txt ${DIST_DIR}/) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${EXT_LIB_DIR}/LICENSE-sdl2.txt ${DIST_DIR}/) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${EXT_LIB_DIR}/LICENSE-sdl2_ttf.txt ${DIST_DIR}/) -add_custom_command(TARGET libvoyaplayer POST_BUILD COMMAND cp -f ${EXT_LIB_DIR}/LICENSE-zlib.txt ${DIST_DIR}/) +if (OS STREQUAL "_macosx") + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -id @rpath/libSDL2.dylib ${LIB_DIR}/libSDL2.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -id @rpath/libSDL2_ttf.dylib ${LIB_DIR}/libSDL2_ttf.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -id @rpath/libavcodec.60.dylib ${LIB_DIR}/libavcodec.60.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -id @rpath/libavdevice.60.dylib ${LIB_DIR}/libavdevice.60.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -id @rpath/libavfilter.9.dylib ${LIB_DIR}/libavfilter.9.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -id @rpath/libavformat.60.dylib ${LIB_DIR}/libavformat.60.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -id @rpath/libavutil.58.dylib ${LIB_DIR}/libavutil.58.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -id @rpath/libswresample.4.dylib ${LIB_DIR}/libswresample.4.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -id @rpath/libswscale.7.dylib ${LIB_DIR}/libswscale.7.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -id @rpath/libz.dylib ${LIB_DIR}/libz.dylib) + + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libSDL2.dylib @rpath/libSDL2.dylib ${LIB_DIR}/libSDL2_ttf.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libavcodec.60.dylib @rpath/libavcodec.60.dylib ${LIB_DIR}/libavdevice.60.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libavcodec.60.dylib @rpath/libavcodec.60.dylib ${LIB_DIR}/libavfilter.9.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libavcodec.60.dylib @rpath/libavcodec.60.dylib ${LIB_DIR}/libavformat.60.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libavformat.60.dylib @rpath/libavformat.60.dylib ${LIB_DIR}/libavdevice.60.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libavformat.60.dylib @rpath/libavformat.60.dylib ${LIB_DIR}/libavfilter.9.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libavutil.58.dylib @rpath/libavutil.58.dylib ${LIB_DIR}/libavcodec.60.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libavutil.58.dylib @rpath/libavutil.58.dylib ${LIB_DIR}/libavdevice.60.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libavutil.58.dylib @rpath/libavutil.58.dylib ${LIB_DIR}/libavfilter.9.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libavutil.58.dylib @rpath/libavutil.58.dylib ${LIB_DIR}/libavformat.60.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libavutil.58.dylib @rpath/libavutil.58.dylib ${LIB_DIR}/libswscale.7.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libswresample.4.dylib @rpath/libswresample.4.dylib ${LIB_DIR}/libavcodec.60.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libswresample.4.dylib @rpath/libswresample.4.dylib ${LIB_DIR}/libavdevice.60.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libswresample.4.dylib @rpath/libswresample.4.dylib ${LIB_DIR}/libavfilter.9.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libswresample.4.dylib @rpath/libswresample.4.dylib ${LIB_DIR}/libavformat.60.dylib) + add_custom_command(TARGET voyaplayer PRE_LINK COMMAND install_name_tool -change @executable_path/../Frameworks/libswscale.7.dylib @rpath/libswscale.7.dylib ${LIB_DIR}/libavfilter.9.dylib) +endif() if (WIN32) - set_target_properties(testvoyaplayer PROPERTIES LINK_FLAGS_DEBUG "-SUBSYSTEM:CONSOLE,5.02") - set_target_properties(testvoyaplayer PROPERTIES LINK_FLAGS_RELEASE "-SUBSYSTEM:WINDOWS,5.02") + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND if not exist \"${DIST_DIR}/bin\" mkdir \"${DIST_DIR}/bin\") + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND if not exist \"${DIST_DIR}/inc\" mkdir \"${DIST_DIR}/inc\") + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND if not exist \"${DIST_DIR}/lib\" mkdir \"${DIST_DIR}/lib\") +else() + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND mkdir -p \"${DIST_DIR}/bin\") + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND mkdir -p \"${DIST_DIR}/inc\") + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND mkdir -p \"${DIST_DIR}/lib\") + + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND mkdir \"${OUT_DIR}\") + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${CFG_DIR}${LIB_PREP}voyaplayer* ${OUT_DIR}/) +endif() - add_custom_command(TARGET testvoyaplayer POST_BUILD COMMAND cp -f ../img/*.bmp ${OUT_DIR}) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}aom.${DLL_EXT} ${OUT_DIR}/) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}SDL2.${DLL_EXT} ${OUT_DIR}/) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}SDL2_ttf.${DLL_EXT} ${OUT_DIR}/) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIBZ}.${DLL_EXT} ${OUT_DIR}/) + +if (OS STREQUAL "_macosx") + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avcodec.60.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avdevice.60.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avfilter.9.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avformat.60.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avutil.58.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}swresample.4.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}swscale.7.${DLL_EXT} ${OUT_DIR}/) +elseif (WIN32) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avcodec-60.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avdevice-60.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avfilter-9.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avformat-60.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avutil-58.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}swresample-4.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}swscale-7.${DLL_EXT} ${OUT_DIR}/) +else() + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avcodec.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avdevice.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avfilter.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avformat.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}avutil.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}swresample.${DLL_EXT} ${OUT_DIR}/) + add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${LIB_DIR}/${LIB_PREP}swscale.${DLL_EXT} ${OUT_DIR}/) endif() + +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${OUT_DIR}/*.${DLL_EXT} ${DIST_DIR}/bin/) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${OUT_DIR}/*.${DLL_EXT} ${DIST_DIR}/lib/) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${OUT_DIR}/*.${LIB_EXT} ${DIST_DIR}/lib/) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${SRC_DIR}/inc/*.h ${DIST_DIR}/inc/) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${SRC_DIR}/LICENSE ${DIST_DIR}/LICENSE.txt) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${EXT_LIB_DIR}/LICENSE-ffmpeg.txt ${DIST_DIR}/) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${EXT_LIB_DIR}/LICENSE-libaom.txt ${DIST_DIR}/) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${EXT_LIB_DIR}/LICENSE-sdl2.txt ${DIST_DIR}/) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${EXT_LIB_DIR}/LICENSE-sdl2_ttf.txt ${DIST_DIR}/) +add_custom_command(TARGET voyaplayer POST_BUILD COMMAND cp -f ${EXT_LIB_DIR}/LICENSE-zlib.txt ${DIST_DIR}/) + +add_custom_command(TARGET testvoyaplayer POST_BUILD COMMAND cp -f ../img/*.bmp ${OUT_DIR}/) + +if (NOT WIN32) + add_custom_command(TARGET testvoyaplayer POST_BUILD COMMAND cp -f ${CFG_DIR}${TEST_EXE} ${OUT_DIR}/) +endif() + +add_custom_command(TARGET testvoyaplayer POST_BUILD COMMAND cp -f ${OUT_DIR}/*.bmp ${DIST_DIR}/bin/) +add_custom_command(TARGET testvoyaplayer POST_BUILD COMMAND cp -f ${OUT_DIR}/${TEST_EXE} ${DIST_DIR}/bin/) + +install(DIRECTORY ${DIST_DIR}/inc/ DESTINATION ${CMAKE_INSTALL_PREFIX}/include/libvoyaplayer) +install(DIRECTORY ${DIST_DIR}/bin/ DESTINATION ${CMAKE_INSTALL_PREFIX}/bin PATTERN ${TEST_EXE} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(DIRECTORY ${DIST_DIR}/lib/ DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) diff --git a/README.md b/README.md index eca8c47..6b98f38 100644 --- a/README.md +++ b/README.md @@ -18,25 +18,109 @@ Supports most popular video codecs like H.265/HEVC, AV1, DivX, MPEG, Theora, WMV Library | Version | License ------- | ------- | ------- -[SDL2](https://www.libsdl.org/) | [2.28.1](https://www.libsdl.org/release/SDL2-2.28.1.tar.gz) | [zlib license](https://www.libsdl.org/license.php) +[SDL2](https://www.libsdl.org/) | [2.28.2](https://www.libsdl.org/release/SDL2-2.28.2.tar.gz) | [zlib license](https://www.libsdl.org/license.php) [SDL2_ttf](https://www.libsdl.org/projects/SDL_ttf/) | [2.20.2](https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-2.20.2.tar.gz) | [zlib license](https://www.libsdl.org/license.php) [FFmpeg](https://ffmpeg.org/) | [6.0](https://ffmpeg.org/releases/ffmpeg-6.0.tar.bz2) | [LGPL v.2.1 (GNU Lesser General Public License)](https://ffmpeg.org/legal.html) [libaom](https://aomedia.googlesource.com/aom/) | [3.6.1](https://storage.googleapis.com/aom-releases/libaom-3.6.1.tar.gz) | [Alliance for Open Media Patent License](https://aomedia.org/license/software-license/) -[zLib](http://www.zlib.net/) | [1.2.13](https://www.zlib.net/zlib-1.2.13.tar.gz) | [zlib license](http://www.zlib.net/zlib_license.html) +[zLib](http://www.zlib.net/) | [1.3](https://www.zlib.net/zlib-1.3.tar.gz) | [zlib license](http://www.zlib.net/zlib_license.html) + +## Platform-dependent Include Headers + +Platform | Header | Package +-------- | ------ | ------- +Linux | gtk/gtk.h | libgtk-3-dev +macOS | AppKit/AppKit.h | AppKit Framework +Windows | windows.h | Win32 API +Windows | Commdlg.h | Win32 API +Windows | dirent.h | [dirent.h](https://github.com/tronkko/dirent/raw/master/include/dirent.h) + +## Compilers and C++20 + +libvoyaplayer uses modern [C++20](https://en.cppreference.com/w/cpp/compiler_support#cpp20) features and requires the following minimum compiler versions. + +Compiler | Version +-------- | ------- +CLANG | 14 +GCC | 13 +MSVC | 2019 ## How to build 1. Build the third-party libraries and place the them in a common directory -1. You will also need the [dirent.h](https://github.com/tronkko/dirent/raw/master/include/dirent.h) header if you are building on Windows + - You will also need the [dirent.h](https://github.com/tronkko/dirent/raw/master/include/dirent.h) header if you are building on **Windows** 1. Make sure you have [cmake](https://cmake.org/download/) installed 1. Open a command prompt or terminal -1. Create a *build* directory `mkdir build` and enter it `cd build` -1. Run the command `cmake .. -D EXT_LIB_DIR="/path/to/libs" -D DIRENT_DIR="/path/to/dirent"` -1. The *build* directory will now contain a *makefile* or a *Visual Studio* solution +1. Create a **build** directory and enter it +1. Run `cmake` to create a **Makefile**, **Xcode** project or **Visual Studio** solution based on your target platform. +1. After building, the **dist** directory will contain all the output resources in the **inc**, **bin** and **lib** directories. -## Test project +```bash +mkdir build +cd build +``` + +### Android + +Make sure you have [Android NDK](https://developer.android.com/ndk/downloads) installed. + +```bash +cmake .. -G "Unix Makefiles" \ +-D CMAKE_SYSTEM_NAME="Android" \ +-D CMAKE_TOOLCHAIN_FILE="/path/to/ANDROID_NDK/build/cmake/android.toolchain.cmake" \ +-D ANDROID_NDK="/path/to/ANDROID_NDK" \ +-D ANDROID_ABI="arm64-v8a" \ +-D ANDROID_PLATFORM="android-26" \ +-D EXT_LIB_DIR="/path/to/libs" + +make +``` + +### iOS + +You can get the iOS SDK path with the following command: `xcrun --sdk iphoneos --show-sdk-path` + +```bash +/Applications/CMake.app/Contents/bin/cmake .. -G "Xcode" \ +-D CMAKE_SYSTEM_NAME="iOS" \ +-D CMAKE_OSX_ARCHITECTURES="arm64" \ +-D CMAKE_OSX_DEPLOYMENT_TARGET="13.0" \ +-D CMAKE_OSX_SYSROOT="/path/to/IOS_SDK" \ +-D EXT_LIB_DIR="/path/to/libs" + +xcodebuild IPHONEOS_DEPLOYMENT_TARGET="13.0" CODE_SIGNING_ALLOWED=NO -configuration "Release" -arch "arm64" -project voyaplayer.xcodeproj +``` + +### macOS -> The *test* project currently only has UI components for Windows using [Win32 Controls](https://learn.microsoft.com/en-us/windows/win32/controls/individual-control-info). +You can get the macOS SDK path with the following command: `xcrun --sdk macosx --show-sdk-path` + +```bash +/Applications/CMake.app/Contents/bin/cmake .. -G "Xcode" \ +-D CMAKE_OSX_ARCHITECTURES="x86_64" \ +-D CMAKE_OSX_DEPLOYMENT_TARGET="12.6" \ +-D CMAKE_OSX_SYSROOT="/path/to/MACOSX_SDK" \ +-D EXT_LIB_DIR="/path/to/libs" + +xcodebuild MACOSX_DEPLOYMENT_TARGET="12.6" -configuration "Release" -arch "x86_64" -project voyaplayer.xcodeproj +``` + +### Linux + +```bash +cmake .. -G "Unix Makefiles" -D EXT_LIB_DIR="/path/to/libs" + +make +``` + +### Windows + +```bash +cmake .. -G "Visual Studio 17 2022" -D EXT_LIB_DIR="/path/to/libs" -D DIRENT_DIR="/path/to/dirent" + +devenv.com voyaplayer.sln -build "Release|x64" +``` + +## Test project You must call [LVP_Initialize](#lvp_initialize) with your callback handlers before using other *LVP_\** methods, see the *test* project for examples. @@ -141,8 +225,8 @@ If you don't use SDL2 for rendering, you need to copy the pixels to a bitmap/ima - The `.pitch` property will contain the byte size of a row of RGB pixels ```cpp -SDL_Texture* Texture = nullptr; -SDL_Surface* VideoFrame = nullptr; +SDL_Texture* Texture = nullptr; +SDL_Surface* VideoFrame = nullptr; bool VideoIsAvailable = false; std::mutex VideoLock; @@ -310,7 +394,7 @@ void LVP_Initialize(const LVP_CallbackContext &callbackContext); Exceptions -- exception +- runtime_error ### LVP_GetAudioDevices @@ -330,7 +414,7 @@ std::vector LVP_GetChapters(); Exceptions -- exception +- runtime_error ### LVP_GetAudioTrack @@ -342,7 +426,7 @@ int LVP_GetAudioTrack(); Exceptions -- exception +- runtime_error ### LVP_GetAudioTracks @@ -354,7 +438,7 @@ std::vector LVP_GetAudioTracks(); Exceptions -- exception +- runtime_error ### LVP_GetDuration @@ -366,7 +450,7 @@ int64_t LVP_GetDuration(); Exceptions -- exception +- runtime_error ### LVP_GetFilePath @@ -378,7 +462,7 @@ std::string LVP_GetFilePath(); Exceptions -- exception +- runtime_error ### LVP_GetMediaDetails @@ -390,7 +474,7 @@ LVP_MediaDetails LVP_GetMediaDetails(); Exceptions -- exception +- runtime_error ### LVP_GetMediaDetails (string) @@ -406,7 +490,7 @@ Parameters Exceptions -- exception +- runtime_error ### LVP_GetMediaDetails (wstring) @@ -422,7 +506,7 @@ Parameters Exceptions -- exception +- runtime_error ### LVP_GetMediaType @@ -434,7 +518,39 @@ LVP_MediaType LVP_GetMediaType(); Exceptions -- exception +- runtime_error + +### LVP_GetMediaType (string) + +Returns the media type of the the provided media file. + +```cpp +LVP_MediaType LVP_GetMediaType(const std::string& filePath); +``` + +Parameters + +- **filePath** Full path to the media file + +Exceptions + +- runtime_error + +### LVP_GetMediaType (wstring) + +Returns the media type of the the provided media file. + +```cpp +LVP_MediaType LVP_GetMediaType(const std::wstring& filePath); +``` + +Parameters + +- **filePath** Full path to the media file + +Exceptions + +- runtime_error ### LVP_GetPlaybackSpeed @@ -446,7 +562,7 @@ double LVP_GetPlaybackSpeed(); Exceptions -- exception +- runtime_error ### LVP_GetProgress @@ -458,7 +574,7 @@ int64_t LVP_GetProgress(); Exceptions -- exception +- runtime_error ### LVP_GetSubtitleTrack @@ -470,7 +586,7 @@ int LVP_GetSubtitleTrack(); Exceptions -- exception +- runtime_error ### LVP_GetSubtitleTracks @@ -482,7 +598,7 @@ std::vector LVP_GetSubtitleTracks(); Exceptions -- exception +- runtime_error ### LVP_GetVideoTracks @@ -494,7 +610,7 @@ std::vector LVP_GetVideoTracks(); Exceptions -- exception +- runtime_error ### LVP_GetVolume @@ -506,7 +622,7 @@ double LVP_GetVolume(); Exceptions -- exception +- runtime_error ### LVP_IsMuted @@ -518,7 +634,7 @@ bool LVP_IsMuted(); Exceptions -- exception +- runtime_error ### LVP_IsPaused @@ -530,7 +646,7 @@ bool LVP_IsPaused(); Exceptions -- exception +- runtime_error ### LVP_IsPlaying @@ -542,7 +658,7 @@ bool LVP_IsPlaying(); Exceptions -- exception +- runtime_error ### LVP_IsStopped @@ -554,7 +670,7 @@ bool LVP_IsStopped(); Exceptions -- exception +- runtime_error ### LVP_Open @@ -570,7 +686,7 @@ Parameters Exceptions -- exception +- runtime_error ### LVP_Open (wstring) @@ -586,7 +702,7 @@ Parameters Exceptions -- exception +- runtime_error ### LVP_Quit @@ -596,10 +712,6 @@ Cleans up allocated resources. void LVP_Quit(); ``` -Exceptions - -- exception - ### LVP_Render Generates and renders a video frame. @@ -637,7 +749,7 @@ Parameters Exceptions -- exception +- runtime_error ### LVP_SetAudioDevice @@ -653,6 +765,22 @@ Parameters - **device** Name of the audio device +### LVP_SetMuted + +Mutes/unmutes the audio volume. + +```cpp +void LVP_SetMuted(bool muted); +``` + +Parameters + +- **muted** true to mute or false to unmute + +Exceptions + +- runtime_error + ### LVP_SetPlaybackSpeed Sets the given playback speed as a relative percent between 0.5 and 2.0, where 1.0 is normal/default. @@ -667,7 +795,7 @@ Parameters Exceptions -- exception +- runtime_error ### LVP_SetTrack @@ -683,7 +811,7 @@ Parameters Exceptions -- exception +- runtime_error ### LVP_SetVolume @@ -699,7 +827,7 @@ Parameters Exceptions -- exception +- runtime_error ### LVP_Stop @@ -711,7 +839,7 @@ void LVP_Stop(); Exceptions -- exception +- runtime_error ### LVP_ToggleMute @@ -723,7 +851,7 @@ void LVP_ToggleMute(); Exceptions -- exception +- runtime_error ### LVP_TogglePause @@ -735,4 +863,4 @@ void LVP_TogglePause(); Exceptions -- exception +- runtime_error diff --git a/inc/libvoyaplayer.h b/inc/libvoyaplayer.h index 5ad9347..011c2e6 100644 --- a/inc/libvoyaplayer.h +++ b/inc/libvoyaplayer.h @@ -1,19 +1,29 @@ #ifndef LIBVOYAPLAYER_H #define LIBVOYAPLAYER_H -#define DLL __stdcall - -#ifdef libvoyaplayer_EXPORTS - #define DLLEXPORT __declspec(dllexport) +#if defined _windows + #define DLL __cdecl + + #ifdef voyaplayer_EXPORTS + #define DLLEXPORT __declspec(dllexport) + #else + #define DLLEXPORT __declspec(dllimport) + #endif #else - #define DLLEXPORT __declspec(dllimport) + #define DLL + + #if __GNUC__ >= 4 + #define DLLEXPORT __attribute__ ((visibility ("default"))) + #else + #define DLLEXPORT + #endif #endif #include /** * @brief Tries to initialize the library and other dependencies. - * @throws exception + * @throws runtime_error */ DLLEXPORT void DLL LVP_Initialize(const LVP_CallbackContext &callbackContext); @@ -24,137 +34,150 @@ DLLEXPORT std::vector DLL LVP_GetAudioDevices(); /** * @returns a list of chapters in the currently loaded media. - * @throws exception + * @throws runtime_error */ DLLEXPORT std::vector DLL LVP_GetChapters(); /** * @returns the current audio track index number. - * @throws exception + * @throws runtime_error */ DLLEXPORT int DLL LVP_GetAudioTrack(); /** * @returns a list of audio tracks in the currently loaded media. - * @throws exception + * @throws runtime_error */ DLLEXPORT std::vector DLL LVP_GetAudioTracks(); /** * @returns the media duration in milliseconds (one thousandth of a second). - * @throws exception + * @throws runtime_error */ DLLEXPORT int64_t DLL LVP_GetDuration(); /** * @returns the current media file path. - * @throws exception + * @throws runtime_error */ DLLEXPORT std::string DLL LVP_GetFilePath(); /** * @returns media details of the currently loaded media. - * @throws exception + * @throws runtime_error */ DLLEXPORT LVP_MediaDetails DLL LVP_GetMediaDetails(); /** * @returns media details of the provided media file. * @param filePath Full path to the media file. - * @throws exception + * @throws runtime_error */ DLLEXPORT LVP_MediaDetails DLL LVP_GetMediaDetails(const std::string& filePath); /** * @returns media details of the provided media file. * @param filePath Full path to the media file. - * @throws exception + * @throws runtime_error */ DLLEXPORT LVP_MediaDetails DLL LVP_GetMediaDetails(const std::wstring& filePath); /** * @returns the media type of the currently loaded media. - * @throws exception + * @throws runtime_error */ DLLEXPORT LVP_MediaType DLL LVP_GetMediaType(); +/** + * @returns the media type of the provided media file. + * @param filePath Full path to the media file. + * @throws runtime_error + */ +DLLEXPORT LVP_MediaType DLL LVP_GetMediaType(const std::string& filePath); + +/** + * @returns the media type of the provided media file. + * @param filePath Full path to the media file. + * @throws runtime_error + */ +DLLEXPORT LVP_MediaType DLL LVP_GetMediaType(const std::wstring& filePath); + /** * @returns the current playback speed as a percent between 0.5 and 2.0. - * @throws exception + * @throws runtime_error */ DLLEXPORT double DLL LVP_GetPlaybackSpeed(); /** * @returns the media playback progress in milliseconds (one thousandth of a second). - * @throws exception + * @throws runtime_error */ DLLEXPORT int64_t DLL LVP_GetProgress(); /** * @returns the current subtitle track index number. - * @throws exception + * @throws runtime_error */ DLLEXPORT int DLL LVP_GetSubtitleTrack(); /** * @returns a list of subtitle tracks in the currently loaded media. - * @throws exception + * @throws runtime_error */ DLLEXPORT std::vector DLL LVP_GetSubtitleTracks(); /** * @returns a list of video tracks in the currently loaded media. - * @throws exception + * @throws runtime_error */ DLLEXPORT std::vector DLL LVP_GetVideoTracks(); /** * @returns the current audio volume as a percent between 0 and 1. - * @throws exception + * @throws runtime_error */ DLLEXPORT double DLL LVP_GetVolume(); /** * @returns true if audio volume is muted. - * @throws exception + * @throws runtime_error */ DLLEXPORT bool DLL LVP_IsMuted(); /** * @returns true if playback is paused. - * @throws exception + * @throws runtime_error */ DLLEXPORT bool DLL LVP_IsPaused(); /** * @returns true if playback is playing (not paused and not stopped). - * @throws exception + * @throws runtime_error */ DLLEXPORT bool DLL LVP_IsPlaying(); /** * @returns true if playback is stopped. - * @throws exception + * @throws runtime_error */ DLLEXPORT bool DLL LVP_IsStopped(); /** * @brief Tries to open and play the given media file. * @param filePath Full path to the media file. - * @throws exception + * @throws runtime_error */ DLLEXPORT void DLL LVP_Open(const std::string &filePath); /** * @brief Tries to open and play the given media file. * @param filePath Full path to the media file. - * @throws exception + * @throws runtime_error */ DLLEXPORT void DLL LVP_Open(const std::wstring &filePath); /** * @brief Cleans up allocated resources. - * @throws exception */ DLLEXPORT void DLL LVP_Quit(); @@ -174,7 +197,7 @@ DLLEXPORT void DLL LVP_Resize(); /** * @brief Seeks to the given position as a percent between 0 and 1. * @param percent [0.0-1.0] - * @throws exception + * @throws runtime_error */ DLLEXPORT void DLL LVP_SeekTo(double percent); @@ -185,42 +208,49 @@ DLLEXPORT void DLL LVP_SeekTo(double percent); */ DLLEXPORT bool DLL LVP_SetAudioDevice(const std::string &device); +/** + * @brief Mutes/unmutes the audio volume. + * @param muted true to mute or false to unmute + * @throws runtime_error + */ +DLLEXPORT void DLL LVP_SetMuted(bool muted); + /** * @brief Sets the given playback speed as a relative percent between 0.5 and 2.0, where 1.0 is normal/default. * @param speed [0.5-2.0] - * @throws exception + * @throws runtime_error */ DLLEXPORT void DLL LVP_SetPlaybackSpeed(double speed); /** * @brief Tries to set the given stream as the current stream if valid. * @param track -1 to disable subtitles or >= 0 for a valid media track. - * @throws exception + * @throws runtime_error */ DLLEXPORT void DLL LVP_SetTrack(const LVP_MediaTrack &track); /** * @brief Sets the given audio volume as a percent between 0 and 1. * @param percent [0.0-1.0] - * @throws exception + * @throws runtime_error */ DLLEXPORT void DLL LVP_SetVolume(double percent); /** * @brief Stops playback of the currently loaded media. - * @throws exception + * @throws runtime_error */ DLLEXPORT void DLL LVP_Stop(); /** * @brief Toggles muting audio volume on/off. - * @throws exception + * @throws runtime_error */ DLLEXPORT void DLL LVP_ToggleMute(); /** * @brief Toggles between pausing and playing. - * @throws exception + * @throws runtime_error */ DLLEXPORT void DLL LVP_TogglePause(); diff --git a/inc/libvoyaplayer_events.h b/inc/libvoyaplayer_events.h index bbdd0e8..6cebb35 100644 --- a/inc/libvoyaplayer_events.h +++ b/inc/libvoyaplayer_events.h @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #ifndef LIB_SDL2_H #define LIB_SDL2_H @@ -132,6 +135,8 @@ struct LVP_MediaDetails */ std::map meta; + SDL_Surface* thumbnail = nullptr; + std::vector audioTracks; std::vector subtitleTracks; std::vector videoTracks; diff --git a/src/LVP_Color.cpp b/src/LVP_Color.cpp index 93518ec..bd2ff79 100644 --- a/src/LVP_Color.cpp +++ b/src/LVP_Color.cpp @@ -31,12 +31,12 @@ Graphics::LVP_Color::LVP_Color() bool Graphics::LVP_Color::operator ==(const LVP_Color &color) { - return ((color.r == this->r) && (color.g == this->g) && (color.b == this->b) && (color.a == this->a)); + return ((this->r == color.r) && (this->g == color.g) && (this->b == color.b) && (this->a == color.a)); } bool Graphics::LVP_Color::operator !=(const LVP_Color &color) { - return !((LVP_Color)color == *this); + return !(*this == color); } Graphics::LVP_Color::operator SDL_Color() diff --git a/src/LVP_FileSystem.cpp b/src/LVP_FileSystem.cpp index 64dba78..9581f14 100644 --- a/src/LVP_FileSystem.cpp +++ b/src/LVP_FileSystem.cpp @@ -85,7 +85,7 @@ Strings System::LVP_FileSystem::getDirectoryContent(const std::string &directory return directoyContent; #if defined _windows - auto directoryPathUTF16 = (wchar_t*)SDL_iconv_utf8_ucs2(directoryPath.c_str()); + auto directoryPathUTF16 = (wchar_t*)LVP_Text::ToUTF16(directoryPath.c_str()); DIR* directory = opendir(directoryPathUTF16); SDL_free(directoryPathUTF16); @@ -176,8 +176,8 @@ size_t System::LVP_FileSystem::GetFileSize(const std::string &filePath) stat_t fileStruct; #if defined _windows - auto filePath16 = (wchar_t*)SDL_iconv_utf8_ucs2(filePath.c_str()); - int result = fstatw(filePath16, &fileStruct); + auto filePath16 = (wchar_t*)LVP_Text::ToUTF16(filePath.c_str()); + int result = fstat(filePath16, &fileStruct); SDL_free(filePath16); #else @@ -210,7 +210,7 @@ Strings System::LVP_FileSystem::GetSubtitleFilesForVideo(const std::string &vide continue; } - subtitleFiles.push_back(std::format("{}{}{}", directory.c_str(), PATH_SEPARATOR, file.c_str())); + subtitleFiles.push_back(LVP_Text::Format("%s%c%s", directory.c_str(), PATH_SEPARATOR, file.c_str())); if (LVP_FileSystem::GetFileExtension(file, true) == "IDX") idxFile = LVP_Text::ToUpper(LVP_FileSystem::getFileName(file, true)); @@ -232,7 +232,7 @@ bool System::LVP_FileSystem::IsBlurayAACS(const std::string &filePath, size_t fi const size_t SECTOR_SIZE = 6144; uint8_t buffer[SECTOR_SIZE]; - size_t nrSectors = min(1000, fileSize / SECTOR_SIZE); + size_t nrSectors = std::min((size_t)1000, fileSize / SECTOR_SIZE); auto file = fopen(filePath.c_str(), "rb"); size_t result = SECTOR_SIZE; size_t sector = 0; @@ -272,12 +272,13 @@ bool System::LVP_FileSystem::IsDVDCSS(const std::string &filePath, size_t fileSi if (filePath.empty() || (fileSize == 0)) return false; - const int SECTOR_SIZE = 2048; - char buffer[SECTOR_SIZE]; - size_t nrSectors = min(1000, fileSize / SECTOR_SIZE); - FILE* file = fopen(filePath.c_str(), "rb"); - size_t result = SECTOR_SIZE; - size_t sector = 0; + const size_t SECTOR_SIZE = 2048; + char buffer[SECTOR_SIZE]; + + size_t nrSectors = std::min((size_t)1000, fileSize / SECTOR_SIZE); + auto file = fopen(filePath.c_str(), "rb"); + size_t result = SECTOR_SIZE; + size_t sector = 0; std::rewind(file); diff --git a/src/LVP_Global.h b/src/LVP_Global.h index 45cbadf..8216487 100644 --- a/src/LVP_Global.h +++ b/src/LVP_Global.h @@ -1,12 +1,15 @@ #ifndef LVP_GLOBAL_H #define LVP_GLOBAL_H -#include -#include +#include // min/max(x) +#include // isspace(x) +#include // sprintf(x) +#include // atoi(x) +#include // memcpy(x) #include #include #include -#include // stringstream, to_string(x) +#include // stringstream, to_string(x) #include #include @@ -16,17 +19,23 @@ extern "C" } #if defined _android - #include // mkdir(x), opendir(x) - #include // chdir(x) + #include // opendir(x) + #include // chdir(x) + #include // stat64, lstat64(x) #elif defined _ios - #include // mkdir(x), opendir(x) + #include // opendir(x) + #include // chdir(x) + #include // stat64, lstat64(x) #elif defined _linux - #include // opendir(x) + #include // opendir(x) + #include // stat64, lstat64(x) #elif defined _macosx - #include // opendir(x) + #include // chdir(x) + #include // opendir(x) + #include // stat64, lstat64(x) #elif defined _windows - #include // _chdir(x), mkdir(x) - #include // opendir(x) + #include // _chdir(x) + #include // _wopendir(x) #endif namespace LibFFmpeg @@ -59,8 +68,8 @@ namespace LibVoyaPlayer #define fseek fseeko #define LOG(x, ...) os_log_error(OS_LOG_DEFAULT, x, ##__VA_ARGS__); #elif defined _macosx - #define fstat lstat64 - #define stat_t struct stat64 + #define fstat lstat + #define stat_t struct stat #define fseek fseeko #define LOG(x, ...) NSLog(@x, ##__VA_ARGS__); #elif defined _linux @@ -76,8 +85,7 @@ namespace LibVoyaPlayer #define closedir _wclosedir #define opendir _wopendir #define readdir _wreaddir - #define fstat _stat64 - #define fstatw _wstat64 + #define fstat _wstat64 #define stat_t struct _stat64 #define fseek _fseeki64 #define LOG(x, ...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, x, ##__VA_ARGS__); @@ -131,7 +139,7 @@ namespace LibVoyaPlayer namespace MediaPlayer { - #define ALIGN_CENTER(o, ts, s) max(0, ((o) + (max(0, ((ts) - (s))) / 2))) + #define ALIGN_CENTER(o, ts, s) std::max(0, ((o) + (std::max(0, ((ts) - (s))) / 2))) #define ARE_DIFFERENT_DOUBLES(a, b) ((a > (b + 0.01)) || (a < (b - 0.01))) #define HEX_STR_TO_UINT(h) (uint8_t)std::strtoul(std::string("0x" + h).c_str(), NULL, 16) @@ -164,10 +172,8 @@ namespace LibVoyaPlayer #define IS_VIDEO(t) (t == LibFFmpeg::AVMEDIA_TYPE_VIDEO) #if defined _windows - #define FONT_NAME(f, s) std::format(L"{}_{}", f, s) #define OPEN_FONT(f, s) TTF_OpenFontRW(System::LVP_FileSystem::FileOpenSDLRWops(_wfopen(f, L"rb")), 1, s) #else - #define FONT_NAME(f, s) std::format("{}_{}", f, s) #define OPEN_FONT(f, s) TTF_OpenFont(f, s) #endif diff --git a/src/LVP_Media.cpp b/src/LVP_Media.cpp index 5180b18..83d6805 100644 --- a/src/LVP_Media.cpp +++ b/src/LVP_Media.cpp @@ -6,7 +6,14 @@ std::string MediaPlayer::LVP_Media::GetAudioChannelLayout(const LibFFmpeg::AVCha LibFFmpeg::av_channel_layout_describe(&layout, buffer, DEFAULT_CHAR_BUFFER_SIZE); - return std::string(buffer); + auto channelLayout = std::string(buffer); + + if (channelLayout == "2 channels") + return "stereo"; + else if (channelLayout == "1 channel") + return "mono"; + + return channelLayout; } double MediaPlayer::LVP_Media::GetAudioPTS(const LVP_AudioContext &audioContext) @@ -107,6 +114,10 @@ int64_t MediaPlayer::LVP_Media::GetMediaDuration(LibFFmpeg::AVFormatContext* for if (formatContext == NULL) return 0; + // Perform an extra scan if duration was not calculated in the initial scan + if ((formatContext->duration < 1) && (LibFFmpeg::avformat_find_stream_info(formatContext, NULL) < 0)) + return 0; + if (formatContext->duration > 0) return (size_t)((double)formatContext->duration / AV_TIME_BASE_D); @@ -125,12 +136,15 @@ int64_t MediaPlayer::LVP_Media::GetMediaDuration(LibFFmpeg::AVFormatContext* for auto avRescaleB = (int64_t)audioStream->time_base.den; auto avRescaleC = (int64_t)(audioStream->codecpar->bit_rate * audioStream->codecpar->ch_layout.nb_channels * audioStream->time_base.num); - return (LibFFmpeg::av_rescale(avRescaleA, avRescaleB, avRescaleC) / AV_TIME_BASE_I64); + if (avRescaleC > 0) + return (LibFFmpeg::av_rescale(avRescaleA, avRescaleB, avRescaleC) / AV_TIME_BASE_I64); + + return 0; } /** * @throws invalid_argument - * @throws exception + * @throws runtime_error */ LibFFmpeg::AVFormatContext* MediaPlayer::LVP_Media::GetMediaFormatContext(const std::string &filePath, bool parseStreams, System::LVP_TimeOut* timeOut) { @@ -152,7 +166,7 @@ LibFFmpeg::AVFormatContext* MediaPlayer::LVP_Media::GetMediaFormatContext(const file.append(fileParts[i] + "|"); if (chdir(fileParts[0].c_str()) != 0) - throw std::invalid_argument(std::format("Failed to change directory: {}", fileParts[0])); + throw std::invalid_argument(System::LVP_Text::Format("Failed to change directory: %s", fileParts[0].c_str())); } if (timeOut != NULL) { @@ -163,23 +177,23 @@ LibFFmpeg::AVFormatContext* MediaPlayer::LVP_Media::GetMediaFormatContext(const formatContext->max_analyze_duration = (int64_t)(10 * AV_TIME_BASE); - int result = avformat_open_input(&formatContext, file.c_str(), NULL, NULL); + int result = LibFFmpeg::avformat_open_input(&formatContext, file.c_str(), NULL, NULL); if ((result < 0) || (formatContext == NULL)) { FREE_AVFORMAT(formatContext); - throw std::exception(std::format("[{}] Failed to open input: {}", result, file).c_str()); + throw std::runtime_error(System::LVP_Text::Format("[%d] Failed to open input: %s", result, file.c_str()).c_str()); } result = formatContext->probe_score; if (result < AVPROBE_SCORE_RETRY) { FREE_AVFORMAT(formatContext); - throw std::exception(std::format("[{}] Invalid probe score: {}", result, file).c_str()); + throw std::runtime_error(System::LVP_Text::Format("[%d] Invalid probe score: %s", result, file.c_str()).c_str()); } if (LVP_Media::isDRM(formatContext->metadata)) { FREE_AVFORMAT(formatContext); - throw std::exception("Media is DRM encrypted."); + throw std::runtime_error("Media is DRM encrypted."); } // Try to fix MP3 files with invalid header and codec type @@ -218,7 +232,7 @@ LibFFmpeg::AVFormatContext* MediaPlayer::LVP_Media::GetMediaFormatContext(const if ((result = LibFFmpeg::avformat_find_stream_info(formatContext, NULL)) < 0) { FREE_AVFORMAT(formatContext); - throw std::exception(std::format("[{}] Failed to find stream info: {}", result, file).c_str()); + throw std::runtime_error(System::LVP_Text::Format("[%d] Failed to find stream info: %s", result, file.c_str()).c_str()); } #if defined _DEBUG @@ -255,6 +269,141 @@ std::map MediaPlayer::LVP_Media::GetMediaMeta(LibFFmpe return LVP_Media::getMeta(formatContext != NULL ? formatContext->metadata : NULL); } +SDL_Surface* MediaPlayer::LVP_Media::GetMediaThumbnail(LibFFmpeg::AVFormatContext* formatContext) +{ + if (formatContext == NULL) + return NULL; + + auto videoStream = LVP_Media::GetMediaTrackThumbnail(formatContext); + + if (videoStream == NULL) + videoStream = LVP_Media::GetMediaTrackBest(formatContext, LibFFmpeg::AVMEDIA_TYPE_VIDEO); + + if ((videoStream == NULL) || (videoStream->codecpar == NULL)) + return NULL; + + auto decoder = LibFFmpeg::avcodec_find_decoder(videoStream->codecpar->codec_id); + auto codec = (decoder != NULL ? LibFFmpeg::avcodec_alloc_context3(decoder) : NULL); + + if (codec != NULL) + LibFFmpeg::avcodec_parameters_to_context(codec, videoStream->codecpar); + + if ((codec == NULL) || (LibFFmpeg::avcodec_open2(codec, decoder, NULL) < 0)) { + FREE_AVCODEC(codec); + return NULL; + } + + int result = -1; + auto frame = LibFFmpeg::av_frame_alloc(); + + if (videoStream->attached_pic.size > 0) + { + LibFFmpeg::avcodec_send_packet(codec, &videoStream->attached_pic); + result = LibFFmpeg::avcodec_receive_frame(codec, frame); + } + else + { + auto seekPos = LVP_Media::GetMediaThumbnailSeekPos(formatContext); + + if (seekPos > 0) + avformat_seek_file(formatContext, -1, INT64_MIN, seekPos, INT64_MAX, AV_SEEK_FLAGS(formatContext->iformat)); + + auto packet = LibFFmpeg::av_packet_alloc(); + + for (int i = 0; (LibFFmpeg::av_read_frame(formatContext, packet) == 0) && (i < 100); i++) + { + if (packet->stream_index != videoStream->index) { + LibFFmpeg::av_packet_unref(packet); + continue; + } + + LibFFmpeg::avcodec_send_packet(codec, packet); + result = LibFFmpeg::avcodec_receive_frame(codec, frame); + + if (result != AVERROR(EAGAIN)) + break; + + LibFFmpeg::av_frame_unref(frame); + LibFFmpeg::av_packet_unref(packet); + } + + FREE_AVPACKET(packet); + } + + if (result < 0) { + FREE_AVFRAME(frame); + FREE_AVCODEC(codec); + return NULL; + } + + auto frameRGB = LibFFmpeg::av_frame_alloc(); + + LibFFmpeg::av_image_alloc(frameRGB->data, frameRGB->linesize, frame->width, frame->height, LibFFmpeg::AV_PIX_FMT_RGB24, 24); + + auto contextRGB = LibFFmpeg::sws_getContext( + frame->width, frame->height, (LibFFmpeg::AVPixelFormat)frame->format, + frame->width, frame->height, LibFFmpeg::AV_PIX_FMT_RGB24, + DEFAULT_SCALE_FILTER, NULL, NULL, NULL + ); + + SDL_Surface* thumbnail = NULL; + + result = LibFFmpeg::sws_scale(contextRGB, frame->data, frame->linesize, 0, frame->height, frameRGB->data, frameRGB->linesize); + + if (result > 0) + thumbnail = SDL_CreateRGBSurfaceWithFormat(0, frame->width, frame->height, 24, SDL_PIXELFORMAT_RGB24); + + if (thumbnail != NULL) + { + bool lock = SDL_MUSTLOCK(thumbnail); + auto size = (size_t)(frame->width * thumbnail->format->BytesPerPixel * frame->height); + + if (lock) + SDL_LockSurface(thumbnail); + + thumbnail->pitch = frameRGB->linesize[0]; + + std::memcpy(thumbnail->pixels, frameRGB->data[0], size); + + if (lock) + SDL_UnlockSurface(thumbnail); + } + + FREE_SWS(contextRGB); + FREE_AVFRAME(frameRGB); + FREE_AVFRAME(frame); + FREE_AVCODEC(codec); + + return thumbnail; +} + +int64_t MediaPlayer::LVP_Media::GetMediaThumbnailSeekPos(LibFFmpeg::AVFormatContext* formatContext) +{ + if (formatContext == NULL) + return 0; + + const int64_t AV_TIME_100 = (100ll * AV_TIME_BASE_I64); + const int64_t AV_TIME_50 = (50ll * AV_TIME_BASE_I64); + const int64_t AV_TIME_10 = (10ll * AV_TIME_BASE_I64); + + int64_t seekPos = 0; + + if (formatContext->duration > AV_TIME_100) + seekPos = AV_TIME_100; + else if (formatContext->duration > AV_TIME_50) + seekPos = AV_TIME_50; + else if (formatContext->duration > AV_TIME_10) + seekPos = AV_TIME_10; + + if (AV_SEEK_FLAGS(formatContext->iformat) != AVSEEK_FLAG_BYTE) + return seekPos; + + auto fileSize = System::LVP_FileSystem::GetFileSize(formatContext->url); + auto percent = ((double)seekPos / (double)formatContext->duration); + + return (int64_t)((double)fileSize * percent); +} + LibFFmpeg::AVStream* MediaPlayer::LVP_Media::GetMediaTrackBest(LibFFmpeg::AVFormatContext* formatContext, LibFFmpeg::AVMediaType mediaType) { if ((formatContext == NULL) || (formatContext->nb_streams == 0)) @@ -287,10 +436,8 @@ size_t MediaPlayer::LVP_Media::getMediaTrackCount(LibFFmpeg::AVFormatContext* fo return 0; // Perform an extra scan if no streams were found in the initial scan - if (formatContext->nb_streams == 0) { - if (LibFFmpeg::avformat_find_stream_info(formatContext, NULL) < 0) - return 0; - } + if ((formatContext->nb_streams == 0) && (LibFFmpeg::avformat_find_stream_info(formatContext, NULL) < 0)) + return 0; size_t streamCount = 0; @@ -337,7 +484,19 @@ LibFFmpeg::AVMediaType MediaPlayer::LVP_Media::GetMediaType(LibFFmpeg::AVFormatC return LibFFmpeg::AVMEDIA_TYPE_UNKNOWN; } +LibFFmpeg::AVStream* MediaPlayer::LVP_Media::GetMediaTrackThumbnail(LibFFmpeg::AVFormatContext* formatContext) +{ + for (uint32_t i = 0; i < formatContext->nb_streams; i++) { + if ((formatContext->streams[i]->codecpar->codec_type == LibFFmpeg::AVMEDIA_TYPE_VIDEO) && (formatContext->streams[i]->attached_pic.size > 0)) + return formatContext->streams[i]; + } + + return NULL; +} + // http://wiki.multimedia.cx/index.php?title=FFmpeg_Metadata +// https://www.exiftool.org/TagNames/ID3.html + std::map MediaPlayer::LVP_Media::getMeta(LibFFmpeg::AVDictionary* metadata) { std::map meta; @@ -349,8 +508,18 @@ std::map MediaPlayer::LVP_Media::getMeta(LibFFmpeg::AV while ((entry = LibFFmpeg::av_dict_get(metadata, "", entry, AV_DICT_IGNORE_SUFFIX)) != NULL) { - if (strcmp(entry->value, "und") != 0) - meta[System::LVP_Text::ToLower(entry->key)] = entry->value; + if (strcmp(entry->value, "und") == 0) + continue; + + auto key = System::LVP_Text::ToLower(entry->key); + + if (key.starts_with("id3v2_priv.")) + continue; + + auto value = System::LVP_Text::Replace(entry->value, "\r", "\\r"); + value = System::LVP_Text::Replace(value, "\n", "\\n"); + + meta[key] = value; } return meta; @@ -557,7 +726,7 @@ void MediaPlayer::LVP_Media::SetMediaTrackByIndex(LibFFmpeg::AVFormatContext* fo case LibFFmpeg::AVMEDIA_TYPE_SUBTITLE: codec->pix_fmt = LibFFmpeg::AV_PIX_FMT_PAL8; break; - case LibFFmpeg::AVMEDIA_TYPE_VIDEO: + case LibFFmpeg::AVMEDIA_TYPE_VIDEO: codec->pix_fmt = LibFFmpeg::AV_PIX_FMT_YUV420P; break; default: diff --git a/src/LVP_Media.h b/src/LVP_Media.h index 82320e2..8d30fc3 100644 --- a/src/LVP_Media.h +++ b/src/LVP_Media.h @@ -23,8 +23,11 @@ namespace LibVoyaPlayer static LibFFmpeg::AVFormatContext* GetMediaFormatContext(const std::string &filePath, bool parseStreams, System::LVP_TimeOut* timeOut = NULL); static double GetMediaFrameRate(LibFFmpeg::AVStream* stream); static std::map GetMediaMeta(LibFFmpeg::AVFormatContext* formatContext); + static SDL_Surface* GetMediaThumbnail(LibFFmpeg::AVFormatContext* formatContext); static LibFFmpeg::AVStream* GetMediaTrackBest(LibFFmpeg::AVFormatContext* formatContext, LibFFmpeg::AVMediaType mediaType); static std::map GetMediaTrackMeta(LibFFmpeg::AVStream* stream); + static LibFFmpeg::AVStream* GetMediaTrackThumbnail(LibFFmpeg::AVFormatContext* formatContext); + static int64_t GetMediaThumbnailSeekPos(LibFFmpeg::AVFormatContext* formatContext); static LibFFmpeg::AVMediaType GetMediaType(LibFFmpeg::AVFormatContext* formatContext); static LVP_SubPTS GetSubtitlePTS(LibFFmpeg::AVPacket* packet, LibFFmpeg::AVSubtitle &frame, const LibFFmpeg::AVRational &timeBase, int64_t startTime); static double GetSubtitleEndPTS(LibFFmpeg::AVPacket* packet, const LibFFmpeg::AVRational &timeBase); diff --git a/src/LVP_Player.cpp b/src/LVP_Player.cpp index 0309358..a64299c 100644 --- a/src/LVP_Player.cpp +++ b/src/LVP_Player.cpp @@ -88,7 +88,7 @@ void MediaPlayer::LVP_Player::clearSubs() void MediaPlayer::LVP_Player::Close() { - if (LVP_Player::isStopping || LVP_Player::state.isStopped) + if (LVP_Player::isStopping) return; LVP_Player::callbackEvents(LVP_EVENT_MEDIA_STOPPING); @@ -134,13 +134,14 @@ void MediaPlayer::LVP_Player::closeAudio() FREE_THREAD_COND(LVP_Player::audioContext.condition); FREE_THREAD_MUTEX(LVP_Player::audioContext.mutex); - if (strcmp(SDL_GetCurrentAudioDriver(), "dummy") == 0) + auto audioDriver = SDL_GetCurrentAudioDriver(); + + if ((audioDriver != NULL) && strcmp(audioDriver, "dummy") == 0) { SDL_AudioQuit(); SDL_setenv("SDL_AUDIODRIVER", "", 1); SDL_AudioInit(NULL); } - } void MediaPlayer::LVP_Player::closePackets() @@ -368,7 +369,7 @@ MediaPlayer::LVP_FontFaceMap MediaPlayer::LVP_Player::getFontFaces(LibFFmpeg::AV auto fontStyleLower = System::LVP_Text::ToLower(fontStyle); #if defined _windows - auto font16 = (wchar_t*)SDL_iconv_utf8_ucs2(familyName); + auto font16 = (wchar_t*)System::LVP_Text::ToUTF16(familyName); auto familyNameLower = std::wstring(font16); SDL_free(font16); @@ -403,6 +404,7 @@ LVP_MediaDetails MediaPlayer::LVP_Player::GetMediaDetails() .fileSize = System::LVP_FileSystem::GetFileSize(LVP_Player::formatContext->url), .mediaType = (LVP_MediaType)LVP_Player::state.mediaType, .meta = LVP_Media::GetMediaMeta(LVP_Player::formatContext), + .thumbnail = LVP_Media::GetMediaThumbnail(LVP_Player::formatContext), .audioTracks = LVP_Player::GetAudioTracks(), .subtitleTracks = LVP_Player::GetSubtitleTracks(), .videoTracks = LVP_Player::GetVideoTracks() @@ -411,20 +413,25 @@ LVP_MediaDetails MediaPlayer::LVP_Player::GetMediaDetails() LVP_MediaDetails MediaPlayer::LVP_Player::GetMediaDetails(const std::string &filePath) { - auto formatContext = LVP_Media::GetMediaFormatContext(filePath, false); - auto audioStream = LVP_Media::GetMediaTrackBest(formatContext, LibFFmpeg::AVMEDIA_TYPE_AUDIO); + auto formatContext = LVP_Media::GetMediaFormatContext(filePath, false); + auto audioStream = LVP_Media::GetMediaTrackBest(formatContext, LibFFmpeg::AVMEDIA_TYPE_AUDIO); + auto mediaType = (LVP_MediaType)LVP_Media::GetMediaType(formatContext); + auto subFiles = (IS_VIDEO(mediaType) ? System::LVP_FileSystem::GetSubtitleFilesForVideo(filePath) : Strings()); + auto subFileContext = (!subFiles.empty() ? LVP_Media::GetMediaFormatContext(subFiles[0], false) : NULL); LVP_MediaDetails details = { .duration = LVP_Media::GetMediaDuration(formatContext, audioStream), .fileSize = System::LVP_FileSystem::GetFileSize(formatContext->url), - .mediaType = (LVP_MediaType)LVP_Media::GetMediaType(formatContext), + .mediaType = mediaType, .meta = LVP_Media::GetMediaMeta(formatContext), + .thumbnail = LVP_Media::GetMediaThumbnail(formatContext), .audioTracks = LVP_Player::GetAudioTracks(formatContext), - .subtitleTracks = LVP_Player::GetSubtitleTracks(formatContext), + .subtitleTracks = LVP_Player::GetSubtitleTracks(formatContext, subFileContext), .videoTracks = LVP_Player::GetVideoTracks(formatContext) }; + FREE_AVFORMAT(subFileContext); FREE_AVFORMAT(formatContext); return details; @@ -497,6 +504,16 @@ LVP_MediaType MediaPlayer::LVP_Player::GetMediaType() return (LVP_MediaType)LVP_Player::state.mediaType; } +LVP_MediaType MediaPlayer::LVP_Player::GetMediaType(const std::string &filePath) +{ + auto formatContext = LVP_Media::GetMediaFormatContext(filePath, false); + auto mediaType = (LVP_MediaType)LVP_Media::GetMediaType(formatContext); + + FREE_AVFORMAT(formatContext); + + return mediaType; +} + LibFFmpeg::AVPixelFormat MediaPlayer::LVP_Player::GetPixelFormatHardware() { return LVP_Player::videoContext.pixelFormatHardware; @@ -521,8 +538,8 @@ SDL_Rect* MediaPlayer::LVP_Player::getScaledVideoDestination(const SDL_Rect* des int videoHeight = LVP_Player::videoContext.stream->codecpar->height; int maxWidth = (int)((double)videoWidth / (double)videoHeight * (double)destination->h); int maxHeight = (int)((double)videoHeight / (double)videoWidth * (double)destination->w); - int scaledWidth = min(destination->w, maxWidth); - int scaledHeight = min(destination->h, maxHeight); + int scaledWidth = std::min(destination->w, maxWidth); + int scaledHeight = std::min(destination->h, maxHeight); auto scaledDestination = new SDL_Rect { @@ -557,6 +574,8 @@ SDL_YUV_CONVERSION_MODE MediaPlayer::LVP_Player::getSdlYuvConversionMode(LibFFmp default: break; } + + break; default: break; } @@ -606,7 +625,8 @@ void MediaPlayer::LVP_Player::handleSeek() LVP_Player::subContext.pts = {}; LVP_Player::videoContext.pts = 0.0; - LibFFmpeg::avcodec_flush_buffers(LVP_Player::subContext.codec); + if (LVP_Player::subContext.codec != NULL) + LibFFmpeg::avcodec_flush_buffers(LVP_Player::subContext.codec); const int SEEK_FLAGS = AV_SEEK_FLAGS(LVP_Player::formatContext->iformat); @@ -713,7 +733,7 @@ bool MediaPlayer::LVP_Player::IsStopped() /** * @throws invalid_argument - * @throws exception + * @throws runtime_error */ void MediaPlayer::LVP_Player::Open(const std::string &filePath) { @@ -721,7 +741,7 @@ void MediaPlayer::LVP_Player::Open(const std::string &filePath) throw std::invalid_argument("filePath cannot be empty"); if (System::LVP_FileSystem::IsSystemFile(filePath)) - throw std::exception(std::format("Invalid media file: {}", filePath).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Invalid media file: %s", filePath.c_str()).c_str()); if (!LVP_Player::state.isStopped) LVP_Player::Close(); @@ -738,7 +758,7 @@ void MediaPlayer::LVP_Player::Open(const std::string &filePath) } /** - * @throws exception + * @throws runtime_error */ void MediaPlayer::LVP_Player::openAudioDevice(const SDL_AudioSpec &wantedSpecs) { @@ -772,13 +792,13 @@ void MediaPlayer::LVP_Player::openAudioDevice(const SDL_AudioSpec &wantedSpecs) SDL_CloseAudioDevice(LVP_Player::audioContext.deviceID); - throw std::exception(std::format("Failed to open a valid audio device: {}", LVP_Player::audioContext.deviceID).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Failed to open a valid audio device: %u", LVP_Player::audioContext.deviceID).c_str()); } } /** * @throws invalid_argument - * @throws exception + * @throws runtime_error */ void MediaPlayer::LVP_Player::openFormatContext(const std::string &filePath) { @@ -795,7 +815,7 @@ void MediaPlayer::LVP_Player::openFormatContext(const std::string &filePath) if (!isValidMedia) { FREE_AVFORMAT(LVP_Player::formatContext); - throw std::exception(std::format("Invalid media type: {}", (int)mediaType).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Invalid media type: %d", (int)mediaType).c_str()); } auto fileExtension = System::LVP_FileSystem::GetFileExtension(filePath, true); @@ -808,7 +828,7 @@ void MediaPlayer::LVP_Player::openFormatContext(const std::string &filePath) if (!isValidMedia) { FREE_AVFORMAT(LVP_Player::formatContext); - throw std::exception("Media is DRM encrypted."); + throw std::runtime_error("Media is DRM encrypted."); } LVP_Player::state.filePath = filePath; @@ -819,7 +839,7 @@ void MediaPlayer::LVP_Player::openFormatContext(const std::string &filePath) } /** - * @throws exception + * @throws runtime_error */ void MediaPlayer::LVP_Player::openStreams() { @@ -831,7 +851,7 @@ void MediaPlayer::LVP_Player::openStreams() LVP_Media::SetMediaTrackBest(LVP_Player::formatContext, LibFFmpeg::AVMEDIA_TYPE_AUDIO, LVP_Player::audioContext); if (LVP_Player::audioContext.stream == NULL) - throw std::exception("Failed to find a valid audio track."); + throw std::runtime_error("Failed to find a valid audio track."); LVP_Player::state.duration = LVP_Media::GetMediaDuration(LVP_Player::formatContext, LVP_Player::audioContext.stream); @@ -841,7 +861,7 @@ void MediaPlayer::LVP_Player::openStreams() if (IS_VIDEO(LVP_Player::state.mediaType)) { if (LVP_Player::videoContext.stream == NULL) - throw std::exception("Failed to find a valid video track."); + throw std::runtime_error("Failed to find a valid video track."); // SUB TRACK LVP_Player::subContext.external = System::LVP_FileSystem::GetSubtitleFilesForVideo(LVP_Player::state.filePath); @@ -869,14 +889,12 @@ void MediaPlayer::LVP_Player::openSubExternal(int streamIndex) LVP_Player::formatContextExternal = LVP_Media::GetMediaFormatContext(subFile, true); - if (LVP_Player::formatContextExternal == NULL) - return; - - LVP_Media::SetMediaTrackByIndex(LVP_Player::formatContextExternal, fileStreamIndex, LVP_Player::subContext, true); + if (LVP_Player::formatContextExternal != NULL) + LVP_Media::SetMediaTrackByIndex(LVP_Player::formatContextExternal, fileStreamIndex, LVP_Player::subContext, true); } /** - * @throws exception + * @throws runtime_error */ void MediaPlayer::LVP_Player::openThreads() { @@ -890,16 +908,16 @@ void MediaPlayer::LVP_Player::openThreads() LVP_Player::packetsThread = SDL_CreateThread(LVP_Player::threadPackets, "packetsThread", NULL); if (LVP_Player::packetsThread == NULL) - throw std::exception(std::format("Failed to create a packets thread: {}", SDL_GetError()).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Failed to create a packets thread: %s", SDL_GetError()).c_str()); } /** - * @throws exception + * @throws runtime_error */ void MediaPlayer::LVP_Player::openThreadAudio() { if ((LVP_Player::audioContext.stream == NULL) || (LVP_Player::audioContext.stream->codecpar == NULL)) - throw std::exception("Audio context stream is missing a valid codec."); + throw std::runtime_error("Audio context stream is missing a valid codec."); LVP_Player::audioContext.frame = LibFFmpeg::av_frame_alloc(); LVP_Player::audioContext.condition = SDL_CreateCond(); @@ -907,7 +925,7 @@ void MediaPlayer::LVP_Player::openThreadAudio() LVP_Player::audioContext.index = LVP_Player::audioContext.stream->index; if ((LVP_Player::audioContext.frame == NULL) || (LVP_Player::audioContext.condition == NULL) || (LVP_Player::audioContext.mutex == NULL)) - throw std::exception("Failed to allocate an audio context frame."); + throw std::runtime_error("Failed to allocate an audio context frame."); auto channelCount = LVP_Player::audioContext.stream->codecpar->ch_layout.nb_channels; auto sampleRate = LVP_Player::audioContext.stream->codecpar->sample_rate; @@ -916,7 +934,7 @@ void MediaPlayer::LVP_Player::openThreadAudio() LibFFmpeg::av_channel_layout_default(&LVP_Player::audioContext.stream->codecpar->ch_layout, 2); if ((sampleRate < 1) || (channelCount < 1)) - throw std::exception(std::format("Invalid audio: {} channels, {} bps", channelCount, sampleRate).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Invalid audio: %d channels, %d bps", channelCount, sampleRate).c_str()); // https://developer.apple.com/documentation/audiotoolbox/kaudiounitproperty_maximumframesperslice @@ -933,17 +951,17 @@ void MediaPlayer::LVP_Player::openThreadAudio() LVP_Player::audioContext.bufferSize = AUDIO_BUFFER_SIZE; LVP_Player::audioContext.decodeFrame = true; - LVP_Player::audioContext.frameEncoded = (uint8_t*)malloc(LVP_Player::audioContext.bufferSize); + LVP_Player::audioContext.frameEncoded = (uint8_t*)std::malloc(LVP_Player::audioContext.bufferSize); LVP_Player::audioContext.writtenToStream = 0; if (LVP_Player::audioContext.frameEncoded == NULL) - throw std::exception("Failed to allocate an encoded audio context frame."); + throw std::runtime_error("Failed to allocate an encoded audio context frame."); LVP_Player::openThreadAudioFilter(); } /** - * @throws exception + * @throws runtime_error */ void MediaPlayer::LVP_Player::openThreadAudioFilter() { @@ -979,7 +997,7 @@ void MediaPlayer::LVP_Player::openThreadAudioFilter() LibFFmpeg::avfilter_link(filterATempo, 0, bufferSink, 0); if (LibFFmpeg::avfilter_graph_config(filterGraph, NULL) < 0) - throw std::exception("Failed to initialize a valid audio filter."); + throw std::runtime_error("Failed to initialize a valid audio filter."); LVP_Player::audioFilterLock.lock(); @@ -993,7 +1011,7 @@ void MediaPlayer::LVP_Player::openThreadAudioFilter() } /** - * @throws exception + * @throws runtime_error */ void MediaPlayer::LVP_Player::openThreadSub() { @@ -1052,6 +1070,8 @@ void MediaPlayer::LVP_Player::openThreadSub() }; } + LVP_Player::subContext.videoDimensions = { 0, 0, videoWidth, videoHeight }; + // SUB STYLES if (styleVersion != SUB_STYLE_VERSION_UNKNOWN) @@ -1088,7 +1108,7 @@ void MediaPlayer::LVP_Player::openThreadSub() } if ((LVP_Player::subContext.mutex == NULL) || (LVP_Player::subContext.condition == NULL)) - throw std::exception(std::format("Failed to create a subtitle context mutex: {}", SDL_GetError()).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Failed to create a subtitle context mutex: %s", SDL_GetError()).c_str()); if (LVP_Player::subContext.subsMutex == NULL) { LVP_Player::subContext.subsMutex = SDL_CreateMutex(); @@ -1096,22 +1116,22 @@ void MediaPlayer::LVP_Player::openThreadSub() } if ((LVP_Player::subContext.subsMutex == NULL) || (LVP_Player::subContext.subsCondition == NULL)) - throw std::exception(std::format("Failed to create a subtitles context mutex: {}", SDL_GetError()).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Failed to create a subtitles context mutex: %s", SDL_GetError()).c_str()); if (LVP_Player::subContext.thread == NULL) LVP_Player::subContext.thread = SDL_CreateThread(LVP_Player::threadSub, "subContext.thread", NULL); if (LVP_Player::subContext.thread == NULL) - throw std::exception(std::format("Failed to create a subtitles context thread: {}", SDL_GetError()).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Failed to create a subtitles context thread: %s", SDL_GetError()).c_str()); } /** - * @throws exception + * @throws runtime_error */ void MediaPlayer::LVP_Player::openThreadVideo() { if ((LVP_Player::videoContext.stream == NULL) || (LVP_Player::videoContext.stream->codecpar == NULL)) - throw std::exception("Video context stream is missing a valid codec."); + throw std::runtime_error("Video context stream is missing a valid codec."); // VIDEO RENDERER @@ -1126,7 +1146,7 @@ void MediaPlayer::LVP_Player::openThreadVideo() } if (LVP_Player::renderContext.renderer == NULL) - throw std::exception(std::format("Failed to create a renderer: {}", SDL_GetError()).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Failed to create a renderer: %s", SDL_GetError()).c_str()); // VIDEO TARGET TEXTURE @@ -1146,7 +1166,7 @@ void MediaPlayer::LVP_Player::openThreadVideo() (LVP_Player::videoContext.frameSoftware == NULL) || (LVP_Player::videoContext.frameRate <= 0)) { - throw std::exception("Failed to allocate a video context frame."); + throw std::runtime_error("Failed to allocate a video context frame."); } if (LVP_Player::videoContext.frameEncoded != NULL) @@ -1159,7 +1179,7 @@ void MediaPlayer::LVP_Player::openThreadVideo() LVP_Player::videoContext.thread = SDL_CreateThread(LVP_Player::threadVideo, "videoContext.thread", NULL); if ((LVP_Player::videoContext.thread == NULL) || (LVP_Player::videoContext.mutex == NULL) || (LVP_Player::videoContext.condition == NULL)) - throw std::exception(std::format("Failed to create a video thread: {}", SDL_GetError()).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Failed to create a video thread: %s", SDL_GetError()).c_str()); LVP_Player::videoContext.index = LVP_Player::videoContext.stream->index; } @@ -1269,7 +1289,7 @@ void MediaPlayer::LVP_Player::present() LVP_Player::subContext.textureNext = current; } - if (LVP_Player::subContext.textureCurrent != NULL) + if ((LVP_Player::subContext.textureCurrent != NULL) && (LVP_Player::subContext.index >= 0)) SDL_RenderCopy(LVP_Player::renderContext.renderer, LVP_Player::subContext.textureCurrent->data, NULL, NULL); SDL_SetRenderTarget(LVP_Player::renderContext.renderer, renderTarget); @@ -1308,7 +1328,7 @@ void MediaPlayer::LVP_Player::removeExpiredSubs() bool isExpired = sub->isExpiredPTS(LVP_Player::subContext, LVP_Player::state.progress); bool isSeeked = sub->isSeekedPTS(LVP_Player::subContext); - if (!isExpired && !isSeeked) { + if (!isExpired && !isSeeked && (LVP_Player::subContext.index >= 0)) { subIter++; continue; } @@ -1495,18 +1515,6 @@ void MediaPlayer::LVP_Player::renderVideo() auto videoWidth = LVP_Player::videoContext.stream->codecpar->width; auto videoHeight = LVP_Player::videoContext.stream->codecpar->height; - // SUB SCALING RELATIVE TO VIDEO - - if ((LVP_Player::subContext.size.x > 0) && (LVP_Player::subContext.size.y > 0)) - { - LVP_Player::subContext.scale = { - (float)((float)videoWidth / (float)LVP_Player::subContext.size.x), - (float)((float)videoHeight / (float)LVP_Player::subContext.size.y) - }; - } - - LVP_Player::subContext.videoDimensions = { 0, 0, videoWidth, videoHeight }; - if (!IS_VALID_TEXTURE(LVP_Player::videoContext.texture)) { LVP_Player::videoContext.texture = new Graphics::LVP_Texture( @@ -1561,7 +1569,7 @@ void MediaPlayer::LVP_Player::renderVideo() FREE_AVFRAME(LVP_Player::videoContext.frameEncoded); } - if (LVP_Player::videoContext.frameEncoded == NULL) + if ((LVP_Player::videoContext.frameEncoded == NULL) || (pixelFormat == LibFFmpeg::AV_PIX_FMT_NONE)) return; LVP_Player::renderContext.scaleContextVideo = LibFFmpeg::sws_getCachedContext( @@ -1595,7 +1603,7 @@ void MediaPlayer::LVP_Player::renderVideo() void MediaPlayer::LVP_Player::Render(const SDL_Rect* destination) { - if (LVP_Player::state.quit) + if (LVP_Player::state.isStopped || LVP_Player::state.quit) return; if (LVP_Player::seekRequested) @@ -1677,7 +1685,7 @@ void MediaPlayer::LVP_Player::SeekTo(double percent) return; int64_t position; - auto validPercent = max(0.0, min(1.0, percent)); + auto validPercent = std::max(0.0, std::min(1.0, percent)); if (AV_SEEK_BYTES(LVP_Player::formatContext->iformat, LVP_Player::state.fileSize)) position = (int64_t)((double)LVP_Player::state.fileSize * validPercent); @@ -1731,9 +1739,19 @@ bool MediaPlayer::LVP_Player::SetAudioDevice(const std::string &device) return isSuccess; } +void MediaPlayer::LVP_Player::SetMuted(bool muted) +{ + LVP_Player::audioContext.isMuted = muted; + + if (LVP_Player::audioContext.isMuted) + LVP_Player::callbackEvents(LVP_EVENT_AUDIO_MUTED); + else + LVP_Player::callbackEvents(LVP_EVENT_AUDIO_UNMUTED); +} + void MediaPlayer::LVP_Player::SetPlaybackSpeed(double speed) { - auto newSpeed = max(0.5, min(2.0, speed)); + auto newSpeed = std::max(0.5, std::min(2.0, speed)); if (ARE_DIFFERENT_DOUBLES(newSpeed, LVP_Player::state.playbackSpeed)) { @@ -1746,7 +1764,7 @@ void MediaPlayer::LVP_Player::SetPlaybackSpeed(double speed) } /** - * @throws exception + * @throws runtime_error */ void MediaPlayer::LVP_Player::SetTrack(const LVP_MediaTrack &track) { @@ -1754,7 +1772,7 @@ void MediaPlayer::LVP_Player::SetTrack(const LVP_MediaTrack &track) auto trackIndex = track.track; if (LVP_Player::formatContext == NULL) - throw std::exception(std::format("Failed to set track {}:\n- No media has been loaded.", trackIndex).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Failed to set track %d:\n- No media has been loaded.", trackIndex).c_str()); bool isSameAudioTrack = (IS_AUDIO(mediaType) && (trackIndex == LVP_Player::audioContext.index)); bool isSameSubTrack = (IS_SUB(mediaType) && (trackIndex == LVP_Player::subContext.index)); @@ -1764,8 +1782,11 @@ void MediaPlayer::LVP_Player::SetTrack(const LVP_MediaTrack &track) return; // Disable subs - if (IS_SUB(mediaType) && (trackIndex < 0)) { + if (IS_SUB(mediaType) && (trackIndex < 0)) + { LVP_Player::closeStream(LibFFmpeg::AVMEDIA_TYPE_SUBTITLE); + LVP_Player::callbackEvents(LVP_EVENT_MEDIA_TRACKS_UPDATED); + return; } @@ -1822,10 +1843,10 @@ void MediaPlayer::LVP_Player::SetTrack(const LVP_MediaTrack &track) void MediaPlayer::LVP_Player::SetVolume(double percent) { - auto validPercent = max(0.0, min(1.0, percent)); + auto validPercent = std::max(0.0, std::min(1.0, percent)); auto volume = (int)((double)SDL_MIX_MAXVOLUME * validPercent); - LVP_Player::audioContext.volume = max(0, min(SDL_MIX_MAXVOLUME, volume)); + LVP_Player::audioContext.volume = std::max(0, std::min(SDL_MIX_MAXVOLUME, volume)); LVP_Player::callbackEvents(LVP_EVENT_AUDIO_VOLUME_CHANGED); } @@ -2103,22 +2124,24 @@ int MediaPlayer::LVP_Player::threadPackets(void* userData) if (LVP_Player::timeOut != NULL) LVP_Player::timeOut->stop(); + // Is the media file completed (EOF) or did an error occur? + + if ((result == AVERROR_EOF) || LibFFmpeg::avio_feof(formatContext->pb)) + endOfFile = true; + else + errorCount++; + #if defined _DEBUG + if (!endOfFile) { char strerror[AV_ERROR_MAX_STRING_SIZE]; LibFFmpeg::av_strerror(result, strerror, AV_ERROR_MAX_STRING_SIZE); LOG("%s\n", strerror); + } #endif if (LVP_Player::state.quit) break; - // Is the media file completed (EOF) or did an error occur? - - if ((result == AVERROR_EOF) && LibFFmpeg::avio_feof(formatContext->pb)) - endOfFile = true; - else - errorCount++; - if (endOfFile || (errorCount >= MAX_ERRORS)) { FREE_AVPACKET(packet); @@ -2229,7 +2252,7 @@ int MediaPlayer::LVP_Player::threadSub(void* userData) // Get packet from queue auto packet = LVP_Player::packetGet(LVP_Player::subContext); - if ((packet == NULL)) + if (packet == NULL) continue; // Decode packet to frame @@ -2559,7 +2582,7 @@ int MediaPlayer::LVP_Player::threadVideo(void* userData) // Video is behind audio - wait or slow down if (sleepTime > 0) - SDL_Delay(min(sleepTime, videoFrameDuration2x)); + SDL_Delay(std::min(sleepTime, videoFrameDuration2x)); LVP_Player::videoContext.isReadyForPresent = true; } @@ -2569,12 +2592,7 @@ int MediaPlayer::LVP_Player::threadVideo(void* userData) void MediaPlayer::LVP_Player::ToggleMute() { - LVP_Player::audioContext.isMuted = !LVP_Player::audioContext.isMuted; - - if (LVP_Player::audioContext.isMuted) - LVP_Player::callbackEvents(LVP_EVENT_AUDIO_MUTED); - else - LVP_Player::callbackEvents(LVP_EVENT_AUDIO_UNMUTED); + LVP_Player::SetMuted(!LVP_Player::audioContext.isMuted); } void MediaPlayer::LVP_Player::TogglePause() diff --git a/src/LVP_Player.h b/src/LVP_Player.h index 4a215f2..5b4e7f7 100644 --- a/src/LVP_Player.h +++ b/src/LVP_Player.h @@ -115,6 +115,7 @@ namespace LibVoyaPlayer static LVP_MediaDetails GetMediaDetails(); static LVP_MediaDetails GetMediaDetails(const std::string &filePath); static LVP_MediaType GetMediaType(); + static LVP_MediaType GetMediaType(const std::string& filePath); static LibFFmpeg::AVPixelFormat GetPixelFormatHardware(); static double GetPlaybackSpeed(); static int64_t GetProgress(); @@ -132,6 +133,7 @@ namespace LibVoyaPlayer static void Resize(); static void SeekTo(double percent); static bool SetAudioDevice(const std::string &device = ""); + static void SetMuted(bool muted = true); static void SetPlaybackSpeed(double speed); static void SetTrack(const LVP_MediaTrack &track); static void SetVolume(double percent); diff --git a/src/LVP_SubStyle.cpp b/src/LVP_SubStyle.cpp index c6a73a1..93b16c8 100644 --- a/src/LVP_SubStyle.cpp +++ b/src/LVP_SubStyle.cpp @@ -43,7 +43,7 @@ MediaPlayer::LVP_SubStyle::LVP_SubStyle(Strings data, LVP_SubStyleVersion versio // FONT NAME #if defined _windows - auto fontName16 = (wchar_t*)SDL_iconv_utf8_ucs2(data[SUB_STYLE_V4_FONT_NAME].c_str()); + auto fontName16 = (wchar_t*)System::LVP_Text::ToUTF16(data[SUB_STYLE_V4_FONT_NAME].c_str()); this->fontName = std::wstring(fontName16); SDL_free(fontName16); @@ -151,7 +151,12 @@ TTF_Font* MediaPlayer::LVP_SubStyle::getFont(LVP_SubtitleContext &subContext) return NULL; auto fontSize = this->getFontSizeScaled(subContext.scale.y); - auto fontName = FONT_NAME(this->fontName.c_str(), fontSize); + + #if defined _windows + auto fontName = System::LVP_Text::FormatW(L"%s_%d", this->fontName.c_str(), fontSize); + #else + auto fontName = System::LVP_Text::Format("%s_%d", this->fontName.c_str(), fontSize); + #endif if (!subContext.styleFonts.contains(fontName)) subContext.styleFonts[fontName] = this->openFontInternal(subContext, fontSize); diff --git a/src/LVP_SubTextRenderer.cpp b/src/LVP_SubTextRenderer.cpp index 63a3629..9e003ca 100644 --- a/src/LVP_SubTextRenderer.cpp +++ b/src/LVP_SubTextRenderer.cpp @@ -506,7 +506,7 @@ void MediaPlayer::LVP_SubTextRenderer::formatOverrideStyleCat2(const Strings &an font = font.substr(0, font.size() - 1); #if defined _windows - auto font16 = (wchar_t*)SDL_iconv_utf8_ucs2(font.c_str()); + auto font16 = (wchar_t*)System::LVP_Text::ToUTF16(font.c_str()); sub->style->fontName = std::wstring(font16); SDL_free(font16); @@ -746,8 +746,8 @@ SDL_Rect MediaPlayer::LVP_SubTextRenderer::getDrawRect(const std::string &subLin drawRect.x = minDrawPosition.x; drawRect.y = minDrawPosition.y; - drawRect.w = (max(maxDrawPosition.x, drawRect.x) - drawRect.x); - drawRect.h = (max(maxDrawPosition.y, drawRect.y) - drawRect.y); + drawRect.w = (std::max(maxDrawPosition.x, drawRect.x) - drawRect.x); + drawRect.h = (std::max(maxDrawPosition.y, drawRect.y) - drawRect.y); if (style == NULL) return drawRect; @@ -797,8 +797,8 @@ SDL_Rect MediaPlayer::LVP_SubTextRenderer::getDrawRect(const std::string &subLin { drawRect.x = (startPosition.x + minDrawPosition.x); drawRect.y = (startPosition.y + minDrawPosition.y); - drawRect.w = (max(startPosition.x + maxDrawPosition.x, drawRect.x) - drawRect.x); - drawRect.h = (max(startPosition.y + maxDrawPosition.y, drawRect.y) - drawRect.y); + drawRect.w = (std::max(startPosition.x + maxDrawPosition.x, drawRect.x) - drawRect.x); + drawRect.h = (std::max(startPosition.y + maxDrawPosition.y, drawRect.y) - drawRect.y); } if (fontScale.x > MIN_FLOAT_ZERO) @@ -1263,10 +1263,10 @@ void MediaPlayer::LVP_SubTextRenderer::setSubPositionAbsolute(const Graphics::LV else if (subTexture->subtitle->isAlignedMiddle()) offsetY = (subTexture->locationRender.h / 2); - subTexture->subtitle->clip.x = max((subTexture->subtitle->clip.x - (position.x - offsetX)), 0); - subTexture->subtitle->clip.y = max((subTexture->subtitle->clip.y - (position.y - offsetY)), 0); - subTexture->subtitle->clip.w = min(subTexture->subtitle->clip.w, subTexture->locationRender.w); - subTexture->subtitle->clip.h = min(subTexture->subtitle->clip.h, subTexture->locationRender.h); + subTexture->subtitle->clip.x = std::max((subTexture->subtitle->clip.x - (position.x - offsetX)), 0); + subTexture->subtitle->clip.y = std::max((subTexture->subtitle->clip.y - (position.y - offsetY)), 0); + subTexture->subtitle->clip.w = std::min(subTexture->subtitle->clip.w, subTexture->locationRender.w); + subTexture->subtitle->clip.h = std::min(subTexture->subtitle->clip.h, subTexture->locationRender.h); } offsetX = (subTexture->locationRender.x + subTexture->locationRender.w + LVP_SubStyle::GetOffsetX(prevSub, subContext)); @@ -1571,7 +1571,7 @@ Strings16 MediaPlayer::LVP_SubTextRenderer::splitSub(uint16_t* subStringUTF16, i } else { - subStrings16.push_back(SDL_iconv_utf8_ucs2(" ")); + subStrings16.push_back(System::LVP_Text::ToUTF16(" ")); subStrings16.push_back(subStringUTF16); } @@ -1628,7 +1628,7 @@ Strings16 MediaPlayer::LVP_SubTextRenderer::splitSubDistributeByLines(const Stri if (((i + 1) % wordsPerLine == 0) || (i == words.size() - 1)) { - auto line16 = SDL_iconv_utf8_ucs2(line.c_str()); + auto line16 = System::LVP_Text::ToUTF16(line.c_str()); TTF_SizeUNICODE(font, line16, &lineWidth, &lineHeight); @@ -1672,7 +1672,7 @@ Strings16 MediaPlayer::LVP_SubTextRenderer::splitSubDistributeByWidth(const Stri if (lineString1.empty()) lineString1 = " "; - auto line16 = SDL_iconv_utf8_ucs2(lineString1.c_str()); + auto line16 = System::LVP_Text::ToUTF16(lineString1.c_str()); subStrings16.push_back(line16); @@ -1683,7 +1683,7 @@ Strings16 MediaPlayer::LVP_SubTextRenderer::splitSubDistributeByWidth(const Stri if (i == words.size() - 1) { - auto endLine16 = SDL_iconv_utf8_ucs2(lineString2.c_str()); + auto endLine16 = System::LVP_Text::ToUTF16(lineString2.c_str()); subStrings16.push_back(endLine16); } @@ -1761,7 +1761,7 @@ MediaPlayer::LVP_Subtitles MediaPlayer::LVP_SubTextRenderer::SplitAndFormatSub(c sub->text = LVP_SubTextRenderer::RemoveFormatting(subLine); // UTF-16 - sub->textUTF16 = SDL_iconv_utf8_ucs2(sub->text.c_str()); + sub->textUTF16 = System::LVP_Text::ToUTF16(sub->text.c_str()); if (sub->textUTF16 == NULL) { DELETE_POINTER(sub); diff --git a/src/LVP_Text.cpp b/src/LVP_Text.cpp index 168cf2c..bb28b26 100644 --- a/src/LVP_Text.cpp +++ b/src/LVP_Text.cpp @@ -33,7 +33,7 @@ int System::LVP_Text::GetWidth(const std::string &text, TTF_Font* font) { if (!text.empty()) { - auto text16 = SDL_iconv_utf8_ucs2(text.c_str()); + auto text16 = System::LVP_Text::ToUTF16(text.c_str()); int textWidth, h; TTF_SizeUNICODE(font, text16, &textWidth, &h); @@ -95,7 +95,7 @@ std::string System::LVP_Text::ToLower(const std::string &text) std::string lower = std::string(text); for (int i = 0; i < (int)lower.size(); i++) - lower[i] = tolower(lower[i]); + lower[i] = std::tolower(lower[i]); return lower; } @@ -106,7 +106,7 @@ std::wstring System::LVP_Text::ToLower(const std::wstring &text) std::wstring lower = std::wstring(text); for (int i = 0; i < (int)lower.size(); i++) - lower[i] = tolower(lower[i]); + lower[i] = std::tolower(lower[i]); return lower; } @@ -117,21 +117,30 @@ std::string System::LVP_Text::ToUpper(const std::string &text) std::string upper = std::string(text); for (int i = 0; i < (int)upper.size(); i++) - upper[i] = toupper(upper[i]); + upper[i] = std::toupper(upper[i]); return upper; } +uint16_t* System::LVP_Text::ToUTF16(const std::string &text) +{ + #if defined _linux + return (uint16_t*)SDL_iconv_string("UCS-2", "UTF-8", text.c_str(), SDL_strlen(text.c_str()) + 1); + #else + return SDL_iconv_utf8_ucs2(text.c_str()); + #endif +} + std::string System::LVP_Text::Trim(const std::string &text) { auto stringTrimmed = std::string(text); // TRIM FRONT - while (!stringTrimmed.empty() && std::isspace(stringTrimmed[0], std::locale())) + while (!stringTrimmed.empty() && std::isspace(stringTrimmed[0])) stringTrimmed = stringTrimmed.substr(1); // TRIM END - while (!stringTrimmed.empty() && std::isspace(stringTrimmed[stringTrimmed.size() - 1], std::locale())) + while (!stringTrimmed.empty() && std::isspace(stringTrimmed[stringTrimmed.size() - 1])) stringTrimmed = stringTrimmed.substr(0, stringTrimmed.size() - 1); return stringTrimmed; diff --git a/src/LVP_Text.h b/src/LVP_Text.h index 2460882..54e33cc 100644 --- a/src/LVP_Text.h +++ b/src/LVP_Text.h @@ -25,8 +25,35 @@ namespace LibVoyaPlayer static Strings Split(const std::string &text, const std::string &delimiter, bool returnEmpty = true); static std::string ToLower(const std::string &text); static std::string ToUpper(const std::string &text); + static uint16_t* ToUTF16(const std::string& text); static std::string Trim(const std::string &text); + template + static std::string Format(const char* formatString, const Args&... args) + { + if (!formatString) + return ""; + + char buffer[DEFAULT_CHAR_BUFFER_SIZE] = {}; + std::snprintf(buffer, DEFAULT_CHAR_BUFFER_SIZE, formatString, args...); + + return std::string(buffer); + } + + #if defined _windows + template + static std::wstring FormatW(const wchar_t* formatString, const Args&... args) + { + if (!formatString) + return L""; + + wchar_t buffer[DEFAULT_CHAR_BUFFER_SIZE] = {}; + std::swprintf(buffer, DEFAULT_CHAR_BUFFER_SIZE, formatString, args...); + + return std::wstring(buffer); + } + #endif + template static bool VectorContains(const std::vector &vector, const T &value) { diff --git a/src/main.cpp b/src/main.cpp index 84bb964..3be267e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,9 +14,6 @@ void initLibraries() SDL_SetHint(SDL_HINT_AUDIO_CATEGORY, "AVAudioSessionCategoryPlayback"); #endif - SDL_setenv("SDL_VIDEO_YUV_DIRECT", "1", 1); - SDL_setenv("SDL_VIDEO_YUV_HWACCEL", "1", 1); - SDL_SetHint(SDL_HINT_AUDIO_RESAMPLING_MODE, "3"); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2"); @@ -24,11 +21,11 @@ void initLibraries() (SDL_AudioInit(NULL) < 0) || (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)) { - throw std::exception(std::format("Failed to initialize SDL: {}", SDL_GetError()).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Failed to initialize SDL: %s", SDL_GetError())); } if (TTF_Init() < 0) - throw std::exception(std::format("Failed to initialize TTF: {}", TTF_GetError()).c_str()); + throw std::runtime_error(System::LVP_Text::Format("Failed to initialize TTF: %s", TTF_GetError())); #if defined _DEBUG LibFFmpeg::av_log_set_level(AV_LOG_VERBOSE); @@ -40,7 +37,7 @@ void initLibraries() (LibFFmpeg::avcodec_version() == 0) || (LibFFmpeg::avformat_version() == 0)) { - throw std::exception("Failed to initialize FFMPEG."); + throw std::runtime_error("Failed to initialize FFMPEG."); } } @@ -60,7 +57,7 @@ void LVP_Initialize(const LVP_CallbackContext &callbackContext) catch (const std::exception &e) { if (callbackContext.errorCB != nullptr) - callbackContext.errorCB(std::format("Failed to initialize libvoyaplayer:\n{}", e.what()), callbackContext.data); + callbackContext.errorCB(System::LVP_Text::Format("Failed to initialize libvoyaplayer:\n%s", e.what()), callbackContext.data); else throw e; } @@ -74,7 +71,7 @@ Strings LVP_GetAudioDevices() int LVP_GetAudioTrack() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetAudioTrack(); } @@ -82,7 +79,7 @@ int LVP_GetAudioTrack() std::vector LVP_GetAudioTracks() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetAudioTracks(); } @@ -90,7 +87,7 @@ std::vector LVP_GetAudioTracks() std::vector LVP_GetChapters() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetChapters(); } @@ -98,7 +95,7 @@ std::vector LVP_GetChapters() int64_t LVP_GetDuration() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetDuration(); } @@ -106,7 +103,7 @@ int64_t LVP_GetDuration() std::string LVP_GetFilePath() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetFilePath(); } @@ -114,7 +111,7 @@ std::string LVP_GetFilePath() LVP_MediaDetails LVP_GetMediaDetails() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetMediaDetails(); } @@ -122,36 +119,85 @@ LVP_MediaDetails LVP_GetMediaDetails() LVP_MediaDetails LVP_GetMediaDetails(const std::string &filePath) { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); + + LVP_MediaDetails mediaDetails = {}; + + try { + mediaDetails = MediaPlayer::LVP_Player::GetMediaDetails(filePath); + } catch (const std::exception& e) { + MediaPlayer::LVP_Player::CallbackError(System::LVP_Text::Format("Failed to parse media file:\n%s", e.what())); + } - return MediaPlayer::LVP_Player::GetMediaDetails(filePath); + return mediaDetails; } LVP_MediaDetails LVP_GetMediaDetails(const std::wstring &filePath) { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); - auto filePathUTF8 = SDL_iconv_wchar_utf8(filePath.c_str()); - auto mediaMeta = MediaPlayer::LVP_Player::GetMediaDetails(filePathUTF8); + LVP_MediaDetails mediaDetails = {}; - SDL_free(filePathUTF8); + try { + auto filePathUTF8 = SDL_iconv_wchar_utf8(filePath.c_str()); + mediaDetails = MediaPlayer::LVP_Player::GetMediaDetails(filePathUTF8); - return mediaMeta; + SDL_free(filePathUTF8); + } catch (const std::exception& e) { + MediaPlayer::LVP_Player::CallbackError(System::LVP_Text::Format("Failed to parse media file:\n%s", e.what())); + } + + return mediaDetails; } LVP_MediaType LVP_GetMediaType() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetMediaType(); } +LVP_MediaType LVP_GetMediaType(const std::string &filePath) +{ + if (!isInitialized) + throw std::runtime_error(ERROR_NO_INIT); + + LVP_MediaType mediaType = LVP_MEDIA_TYPE_UNKNOWN; + + try { + mediaType = MediaPlayer::LVP_Player::GetMediaType(filePath); + } catch (const std::exception& e) { + MediaPlayer::LVP_Player::CallbackError(System::LVP_Text::Format("Failed to parse media file:\n%s", e.what())); + } + + return mediaType; +} + +LVP_MediaType LVP_GetMediaType(const std::wstring &filePath) +{ + if (!isInitialized) + throw std::runtime_error(ERROR_NO_INIT); + + LVP_MediaType mediaType = LVP_MEDIA_TYPE_UNKNOWN; + + try { + auto filePathUTF8 = SDL_iconv_wchar_utf8(filePath.c_str()); + mediaType = MediaPlayer::LVP_Player::GetMediaType(filePathUTF8); + + SDL_free(filePathUTF8); + } catch (const std::exception& e) { + MediaPlayer::LVP_Player::CallbackError(System::LVP_Text::Format("Failed to parse media file:\n%s", e.what())); + } + + return mediaType; +} + double LVP_GetPlaybackSpeed() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetPlaybackSpeed(); } @@ -159,7 +205,7 @@ double LVP_GetPlaybackSpeed() int64_t LVP_GetProgress() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetProgress(); } @@ -167,7 +213,7 @@ int64_t LVP_GetProgress() int LVP_GetSubtitleTrack() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetSubtitleTrack(); } @@ -175,7 +221,7 @@ int LVP_GetSubtitleTrack() std::vector LVP_GetSubtitleTracks() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetSubtitleTracks(); } @@ -183,7 +229,7 @@ std::vector LVP_GetSubtitleTracks() std::vector LVP_GetVideoTracks() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetVideoTracks(); } @@ -191,7 +237,7 @@ std::vector LVP_GetVideoTracks() double LVP_GetVolume() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::GetVolume(); } @@ -199,7 +245,7 @@ double LVP_GetVolume() bool LVP_IsMuted() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::IsMuted(); } @@ -207,7 +253,7 @@ bool LVP_IsMuted() bool LVP_IsPaused() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::IsPaused(); } @@ -215,7 +261,7 @@ bool LVP_IsPaused() bool LVP_IsPlaying() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::IsPlaying(); } @@ -223,7 +269,7 @@ bool LVP_IsPlaying() bool LVP_IsStopped() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); return MediaPlayer::LVP_Player::IsStopped(); } @@ -231,19 +277,20 @@ bool LVP_IsStopped() void LVP_Open(const std::string &filePath) { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); try { MediaPlayer::LVP_Player::Open(filePath); } catch (const std::exception &e) { - MediaPlayer::LVP_Player::CallbackError(std::format("Failed to open media file:\n{}", e.what())); + MediaPlayer::LVP_Player::CallbackError(System::LVP_Text::Format("Failed to open media file:\n%s", e.what())); + LVP_Stop(); } } void LVP_Open(const std::wstring &filePath) { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); try { auto filePathUTF8 = SDL_iconv_wchar_utf8(filePath.c_str()); @@ -252,14 +299,17 @@ void LVP_Open(const std::wstring &filePath) SDL_free(filePathUTF8); } catch (const std::exception &e) { - MediaPlayer::LVP_Player::CallbackError(std::format("Failed to open media file:\n{}", e.what())); + MediaPlayer::LVP_Player::CallbackError(System::LVP_Text::Format("Failed to open media file:\n%s", e.what())); + LVP_Stop(); } } void LVP_Quit() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + return; + + isInitialized = false; MediaPlayer::LVP_Player::Close(); @@ -273,7 +323,7 @@ void LVP_Quit() void LVP_Render(const SDL_Rect* destination) { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); MediaPlayer::LVP_Player::Render(destination); } @@ -281,7 +331,7 @@ void LVP_Render(const SDL_Rect* destination) void LVP_Resize() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); MediaPlayer::LVP_Player::Resize(); } @@ -289,7 +339,7 @@ void LVP_Resize() void LVP_SeekTo(double percent) { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); MediaPlayer::LVP_Player::SeekTo(percent); } @@ -299,18 +349,18 @@ bool LVP_SetAudioDevice(const std::string &device) return MediaPlayer::LVP_Player::SetAudioDevice(device); } -void LVP_SetVolume(double percent) +void LVP_SetMuted(bool muted) { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); - MediaPlayer::LVP_Player::SetVolume(percent); + MediaPlayer::LVP_Player::SetMuted(muted); } void LVP_SetPlaybackSpeed(double speed) { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); MediaPlayer::LVP_Player::SetPlaybackSpeed(speed); } @@ -318,15 +368,23 @@ void LVP_SetPlaybackSpeed(double speed) void LVP_SetTrack(const LVP_MediaTrack &track) { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); MediaPlayer::LVP_Player::SetTrack(track); } +void LVP_SetVolume(double percent) +{ + if (!isInitialized) + throw std::runtime_error(ERROR_NO_INIT); + + MediaPlayer::LVP_Player::SetVolume(percent); +} + void LVP_Stop() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); MediaPlayer::LVP_Player::Close(); } @@ -334,7 +392,7 @@ void LVP_Stop() void LVP_ToggleMute() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); MediaPlayer::LVP_Player::ToggleMute(); } @@ -342,7 +400,7 @@ void LVP_ToggleMute() void LVP_TogglePause() { if (!isInitialized) - throw std::exception(ERROR_NO_INIT); + throw std::runtime_error(ERROR_NO_INIT); MediaPlayer::LVP_Player::TogglePause(); } diff --git a/test/TestPlayer.cpp b/test/TestPlayer.cpp index 1476d4b..e8929dc 100644 --- a/test/TestPlayer.cpp +++ b/test/TestPlayer.cpp @@ -1,7 +1,7 @@ #include "TestPlayer.h" -SDL_Texture* TestPlayer::texture = nullptr; -SDL_Surface* TestPlayer::videoFrame = nullptr; +SDL_Texture* TestPlayer::texture = nullptr; +SDL_Surface* TestPlayer::videoFrame = nullptr; bool TestPlayer::videoIsAvailable = false; std::mutex TestPlayer::videoLock; @@ -180,40 +180,3 @@ void TestPlayer::SeekForward() LVP_SeekTo(percent); } - -void TestPlayer::SeekToChapter(const LVP_MediaChapter &chapter) -{ - auto duration = (double)LVP_GetDuration(); - auto position = (double)chapter.startTime; - auto percent = (position / duration); - - LVP_SeekTo(percent); -} - -void TestPlayer::SetPlaybackSpeedDown() -{ - auto speed = LVP_GetPlaybackSpeed(); - - LVP_SetPlaybackSpeed(speed - SPEED_DIFF); -} - -void TestPlayer::SetPlaybackSpeedUp() -{ - auto speed = LVP_GetPlaybackSpeed(); - - LVP_SetPlaybackSpeed(speed + SPEED_DIFF); -} - -void TestPlayer::SetVolumeDown() -{ - auto volume = LVP_GetVolume(); - - LVP_SetVolume(volume - VOLUME_DIFF); -} - -void TestPlayer::SetVolumeUp() -{ - auto volume = LVP_GetVolume(); - - LVP_SetVolume(volume + VOLUME_DIFF); -} diff --git a/test/TestPlayer.h b/test/TestPlayer.h index 021454f..763f471 100644 --- a/test/TestPlayer.h +++ b/test/TestPlayer.h @@ -3,9 +3,7 @@ #include -const int64_t SEEK_DIFF = 20000; -const double SPEED_DIFF = 0.25; -const double VOLUME_DIFF = 0.05; +const int64_t SEEK_DIFF = 20000; class TestPlayer { @@ -25,11 +23,6 @@ class TestPlayer static void Render(SDL_Renderer* renderer, const SDL_Rect &destination); static void SeekBack(); static void SeekForward(); - static void SeekToChapter(const LVP_MediaChapter &chapter); - static void SetPlaybackSpeedDown(); - static void SetPlaybackSpeedUp(); - static void SetVolumeDown(); - static void SetVolumeUp(); private: static void freeResources(); diff --git a/test/TestWindow.cpp b/test/TestWindow.cpp index c952d67..383e1a4 100644 --- a/test/TestWindow.cpp +++ b/test/TestWindow.cpp @@ -1,113 +1,25 @@ #include "TestWindow.h" -std::unordered_map TestWindow::audioTracks; -std::unordered_map TestWindow::audioTrackIds; -std::unordered_map TestWindow::chapters; -std::unordered_map TestWindow::subtitleTracks; -std::unordered_map TestWindow::subtitleTrackIds; - -std::string TestWindow::about = "libvoyaplayer is a free cross-platform media player library that easily plays your music and video files.\n\nCopyright (C) 2021 Adam A. Jammary (Jammary Studio)"; -int TestWindow::height = 0; +ButtonIds TestWindow::buttonIds; +Buttons TestWindow::buttons; SDL_Renderer* TestWindow::renderer = nullptr; std::string TestWindow::title = "Voya Player Library"; -int TestWindow::width = 0; SDL_Window* TestWindow::window = nullptr; -#if defined _windows - HANDLE TestWindow::bitmaps[NR_OF_BITMAPS]; - HWND TestWindow::controls[NR_OF_CONTROLS]; - HMENU TestWindow::menus[NR_OF_MENUS]; -#endif - -void TestWindow::clearSubMenuItems(Menu menu, MenuItem firstItem) -{ - auto itemCount = GetMenuItemCount(TestWindow::menus[menu]); - - for (int i = 0; i < itemCount; i++) - RemoveMenu(TestWindow::menus[menu], (firstItem + i), MF_BYCOMMAND); -} - -LVP_MediaTrack TestWindow::GetAudioTrack(unsigned short id) -{ - return TestWindow::audioTracks[id]; -} - -unsigned short TestWindow::GetAudioTrackId(int track) -{ - return TestWindow::audioTrackIds[track]; -} - -LVP_MediaTrack TestWindow::GetSubtitleTrack(unsigned short id) -{ - return TestWindow::subtitleTracks[id]; -} - -unsigned short TestWindow::GetSubtitleTrackId(int track) -{ - return TestWindow::subtitleTrackIds[track]; -} - -LVP_MediaChapter TestWindow::GetChapter(unsigned short id) -{ - return TestWindow::chapters[id]; -} - -#if defined _windows -HANDLE TestWindow::getBitmap(const std::string &file) +void TestWindow::EnableButton(ButtonId id, bool enabled) { - return LoadImageA(NULL, file.c_str(), IMAGE_BITMAP, 0, 0, (LR_LOADFROMFILE | LR_DEFAULTSIZE | LR_SHARED)); + if (TestWindow::buttonIds.contains(id)) + TestWindow::buttonIds[id]->enable(enabled); } -HWND TestWindow::getControl(LPCSTR className, DWORD style, const SDL_Rect &location, const SDL_SysWMinfo &sysInfo, HMENU id) +Button* TestWindow::GetClickedButton(const SDL_Point& clickPosition) { - auto hwnd = sysInfo.info.win.window; - auto instance = sysInfo.info.win.hinstance; - - return CreateWindowA(className, NULL, style, location.x, location.y, location.w, location.h, hwnd, id, instance, NULL); -} - -HWND TestWindow::getControlButton(DWORD style, const SDL_Rect &location, const SDL_SysWMinfo &sysInfo, HMENU id) -{ - return TestWindow::getControl("BUTTON", style, location, sysInfo, id); -} - -HWND TestWindow::getControlStatic(DWORD style, const SDL_Rect &location, const SDL_SysWMinfo &sysInfo) -{ - return TestWindow::getControl("STATIC", style, location, sysInfo); -} - -ControlItem TestWindow::GetControlId(HWND handle) -{ - if (handle == TestWindow::controls[CONTROL_SEEK]) - return CONTROL_ITEM_SEEK; - else if (handle == TestWindow::controls[CONTROL_VOLUME]) - return CONTROL_ITEM_VOLUME; - - return CONTROL_ITEM_INVALID; -} - -#endif - -double TestWindow::GetControlSliderClickPosition(Control control) -{ - #if defined _windows - POINT mousePosition = {}; - GetCursorPos(&mousePosition); - - RECT controlPosition = {}; - GetWindowRect(TestWindow::controls[control], &controlPosition); - - RECT thumbPosition = {}; - SendMessageA(TestWindow::controls[control], TBM_GETTHUMBRECT, 0, (LPARAM)&thumbPosition); - - auto clickPosition = (mousePosition.x - controlPosition.left); - auto thumbWidth = (thumbPosition.right - thumbPosition.left); - auto controlWidth = (controlPosition.right - controlPosition.left - thumbWidth); - - return (double)((double)clickPosition / (double)controlWidth); - #endif + for (const auto& button : TestWindow::buttons) { + if (button->enabled && SDL_PointInRect(&clickPosition, &button->background)) + return button; + } - return 0; + return nullptr; } SDL_Rect TestWindow::GetDimensions() @@ -120,320 +32,65 @@ SDL_Rect TestWindow::GetDimensions() return dimensions; } -unsigned short TestWindow::GetMenuIdPlaybackSpeed(double speed) -{ - if (speed > 1.99) - return MENU_ITEM_PLAYBACK_SPEED_200X; - else if (speed > 1.74) - return MENU_ITEM_PLAYBACK_SPEED_175X; - else if (speed > 1.49) - return MENU_ITEM_PLAYBACK_SPEED_150X; - else if (speed > 1.24) - return MENU_ITEM_PLAYBACK_SPEED_125X; - else if (speed > 0.99) - return MENU_ITEM_PLAYBACK_SPEED_100X; - else if (speed > 0.74) - return MENU_ITEM_PLAYBACK_SPEED_075X; - - return MENU_ITEM_PLAYBACK_SPEED_050X; -} - -std::string TestWindow::GetMenuLabel(unsigned short id, Menu menu) -{ - char label[256] = {}; - - auto result = GetMenuStringA(TestWindow::menus[menu], id, label, 256, MF_BYCOMMAND); - - return (result > 0 ? std::string(label) : ""); -} - SDL_Renderer* TestWindow::GetRenderer() { return TestWindow::renderer; } -SDL_SysWMinfo TestWindow::getSysInfo() -{ - SDL_SysWMinfo sysInfo = {}; - SDL_VERSION(&sysInfo.version); - - SDL_GetWindowWMInfo(TestWindow::window, &sysInfo); - - return sysInfo; -} - void TestWindow::Init(int width, int height) { if (SDL_Init(SDL_INIT_EVENTS | SDL_INIT_VIDEO) < 0) - throw std::exception(std::format("Failed to initialize SDL: {}", SDL_GetError()).c_str()); + throw std::runtime_error(TextFormat("Failed to initialize SDL: %s", SDL_GetError())); - const auto FLAGS = (SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE); + if (TTF_Init() < 0) + throw std::runtime_error(TextFormat("Failed to initialize TTF: %s", TTF_GetError())); - TestWindow::window = SDL_CreateWindow(TestWindow::title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, FLAGS); + const auto WINDOW_FLAGS = (SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE); + + TestWindow::window = SDL_CreateWindow(TestWindow::title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, WINDOW_FLAGS); if (!TestWindow::window) - throw std::exception(std::format("Failed to create a window: {}", SDL_GetError()).c_str()); + throw std::runtime_error(TextFormat("Failed to create a window: %s", SDL_GetError())); SDL_SetWindowMinimumSize(TestWindow::window, 640, 480); - auto window = TestWindow::GetDimensions(); - - TestWindow::width = window.w; - TestWindow::height = window.h; - TestWindow::renderer = SDL_CreateRenderer(TestWindow::window, -1, SDL_RENDERER_ACCELERATED); if (!TestWindow::renderer) TestWindow::renderer = SDL_CreateRenderer(TestWindow::window, -1, SDL_RENDERER_SOFTWARE); if (!TestWindow::renderer) - throw std::exception(std::format("Failed to create a renderer: {}", SDL_GetError()).c_str()); - - auto sysInfo = TestWindow::getSysInfo(); - - #if defined _windows - TestWindow::initBitmaps(); - TestWindow::initMenu(sysInfo); - TestWindow::initControls(sysInfo); - #endif - - SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); -} - -#if defined _windows -void TestWindow::initBitmaps() -{ - TestWindow::bitmaps[BITMAP_MUTE] = TestWindow::getBitmap("mute-16.bmp"); - TestWindow::bitmaps[BITMAP_PAUSE] = TestWindow::getBitmap("pause-16.bmp"); - TestWindow::bitmaps[BITMAP_PLAY] = TestWindow::getBitmap("play-16.bmp"); - TestWindow::bitmaps[BITMAP_STOP] = TestWindow::getBitmap("stop-16.bmp"); - TestWindow::bitmaps[BITMAP_VOLUME] = TestWindow::getBitmap("volume-16.bmp"); -} - -void TestWindow::initControls(const SDL_SysWMinfo &sysInfo) -{ - for (auto i = 0; i < NR_OF_CONTROLS; i++) - DestroyWindow(TestWindow::controls[i]); - - const int DEFAULT_HEIGHT = PLAYER_CONTROLS_CONTENT_HEIGHT; - const DWORD DEFAULT_STYLE = (WS_CHILD | WS_VISIBLE); - const int OFFSET_Y = (TestWindow::height - PLAYER_CONTROLS_PANEL_HEIGHT); - - const DWORD STYLE_DEFPUSH_BUTTON = (DEFAULT_STYLE | BS_DEFPUSHBUTTON | BS_BITMAP); - const DWORD STYLE_PUSH_BUTTON = (DEFAULT_STYLE | BS_PUSHBUTTON | BS_BITMAP); - const DWORD STYLE_SLIDER = (DEFAULT_STYLE | TBS_ENABLESELRANGE | TBS_FIXEDLENGTH | TBS_HORZ | TBS_NOTICKS | TBS_BOTH); - const DWORD STYLE_TEXT = (DEFAULT_STYLE | SS_CENTERIMAGE); - - SDL_Rect panelLocation = { 0, OFFSET_Y, TestWindow::width, PLAYER_CONTROLS_PANEL_HEIGHT }; - - TestWindow::controls[CONTROL_PANEL] = TestWindow::getControlStatic(DEFAULT_STYLE, panelLocation, sysInfo); - - // PLAY/PAUSE - - SDL_Rect pauseLocation = { 2, (OFFSET_Y + 2), DEFAULT_HEIGHT, DEFAULT_HEIGHT }; - - TestWindow::controls[CONTROL_PAUSE] = TestWindow::getControlButton(STYLE_DEFPUSH_BUTTON, pauseLocation, sysInfo, (HMENU)CONTROL_ITEM_PAUSE); - - // STOP - - SDL_Rect stopLocation = { (pauseLocation.x + pauseLocation.w), (OFFSET_Y + 2), DEFAULT_HEIGHT, DEFAULT_HEIGHT }; - - TestWindow::controls[CONTROL_STOP] = TestWindow::getControlButton(STYLE_PUSH_BUTTON, stopLocation, sysInfo, (HMENU)CONTROL_ITEM_STOP); - - // SEEK - - int seekWidth = (TestWindow::width - pauseLocation.x - pauseLocation.w - stopLocation.w - PLAYER_CONTROLS_PROGRESS_WIDTH - PLAYER_CONTROLS_SPEED_WIDTH - PLAYER_CONTROLS_VOLUME_WIDTH - DEFAULT_HEIGHT); - SDL_Rect seekLocation = { (stopLocation.x + stopLocation.w), (OFFSET_Y + 4), seekWidth, DEFAULT_HEIGHT }; - - TestWindow::controls[CONTROL_SEEK] = TestWindow::getControl(TRACKBAR_CLASS, STYLE_SLIDER, seekLocation, sysInfo, (HMENU)CONTROL_ITEM_SEEK); - - SendMessageA(TestWindow::controls[CONTROL_SEEK], TBM_SETRANGE, (WPARAM)TRUE, (LPARAM)MAKELONG(0, 100)); - - // MUTE + throw std::runtime_error(TextFormat("Failed to create a renderer: %s", SDL_GetError())); - SDL_Rect muteLocation = { (seekLocation.x + seekLocation.w), (OFFSET_Y + 2), DEFAULT_HEIGHT, DEFAULT_HEIGHT }; - - TestWindow::controls[CONTROL_MUTE] = TestWindow::getControlButton(STYLE_PUSH_BUTTON, muteLocation, sysInfo, (HMENU)CONTROL_ITEM_MUTE); - - SendMessageA(TestWindow::controls[CONTROL_MUTE], BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)TestWindow::bitmaps[BITMAP_VOLUME]); - - // VOLUME SLIDER - - SDL_Rect volumeLocation = { (muteLocation.x + muteLocation.w), (OFFSET_Y + 4), PLAYER_CONTROLS_VOLUME_WIDTH, DEFAULT_HEIGHT }; - - TestWindow::controls[CONTROL_VOLUME] = TestWindow::getControl(TRACKBAR_CLASS, STYLE_SLIDER, volumeLocation, sysInfo, (HMENU)CONTROL_ITEM_VOLUME); - - SendMessageA(TestWindow::controls[CONTROL_VOLUME], TBM_SETRANGE, (WPARAM)TRUE, (LPARAM)MAKELONG(0, 100)); - - TestWindow::updateControlsSlider(CONTROL_VOLUME, 100); - - // PROGRESS/DURATION - - SDL_Rect progressLocation = { (volumeLocation.x + volumeLocation.w), OFFSET_Y, PLAYER_CONTROLS_PROGRESS_WIDTH, DEFAULT_HEIGHT }; - - TestWindow::controls[CONTROL_PROGRESS] = TestWindow::getControlStatic(STYLE_TEXT, progressLocation, sysInfo); - - // PLAYBACK SPEED - - SDL_Rect speedLocation = { (progressLocation.x + progressLocation.w), OFFSET_Y, PLAYER_CONTROLS_SPEED_WIDTH, DEFAULT_HEIGHT }; - - TestWindow::controls[CONTROL_SPEED] = TestWindow::getControlStatic(STYLE_TEXT, speedLocation, sysInfo); -} - -void TestWindow::initMenu(const SDL_SysWMinfo &sysInfo) -{ - TestWindow::menus[MENU] = CreateMenu(); - - TestWindow::menus[MENU_AUDIO] = CreatePopupMenu(); - TestWindow::menus[MENU_AUDIO_DEVICE] = CreatePopupMenu(); - TestWindow::menus[MENU_AUDIO_TRACK] = CreatePopupMenu(); - TestWindow::menus[MENU_FILE] = CreatePopupMenu(); - TestWindow::menus[MENU_PLAYBACK] = CreatePopupMenu(); - TestWindow::menus[MENU_PLAYBACK_CHAPTER] = CreatePopupMenu(); - TestWindow::menus[MENU_PLAYBACK_SPEED] = CreatePopupMenu(); - TestWindow::menus[MENU_SUBTITLE] = CreatePopupMenu(); - TestWindow::menus[MENU_SUBTITLE_TRACK] = CreatePopupMenu(); - TestWindow::menus[MENU_HELP] = CreatePopupMenu(); - - // MENU - - AppendMenuA(TestWindow::menus[MENU], MF_POPUP, (UINT_PTR)TestWindow::menus[MENU_FILE], MENU_LABEL_FILE); - AppendMenuA(TestWindow::menus[MENU], MF_POPUP, (UINT_PTR)TestWindow::menus[MENU_PLAYBACK], MENU_LABEL_PLAYBACK); - AppendMenuA(TestWindow::menus[MENU], MF_POPUP, (UINT_PTR)TestWindow::menus[MENU_AUDIO], MENU_LABEL_AUDIO); - AppendMenuA(TestWindow::menus[MENU], MF_POPUP, (UINT_PTR)TestWindow::menus[MENU_SUBTITLE], MENU_LABEL_SUBTITLE); - AppendMenuA(TestWindow::menus[MENU], MF_POPUP, (UINT_PTR)TestWindow::menus[MENU_HELP], MENU_LABEL_HELP); - - // FILE - - AppendMenuA(TestWindow::menus[MENU_FILE], MF_STRING, MENU_ITEM_FILE_OPEN, MENU_LABEL_FILE_OPEN); - AppendMenuA(TestWindow::menus[MENU_FILE], MF_SEPARATOR, NULL, NULL); - AppendMenuA(TestWindow::menus[MENU_FILE], MF_STRING, MENU_ITEM_FILE_QUIT, MENU_LABEL_FILE_QUIT); - - // PLAYBACK - - AppendMenuA(TestWindow::menus[MENU_PLAYBACK], MF_POPUP | MF_DISABLED, (UINT_PTR)TestWindow::menus[MENU_PLAYBACK_CHAPTER], MENU_LABEL_PLAYBACK_CHAPTER); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK], MF_SEPARATOR, NULL, NULL); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK], MF_POPUP | MF_DISABLED, (UINT_PTR)TestWindow::menus[MENU_PLAYBACK_SPEED], MENU_LABEL_PLAYBACK_SPEED); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK], MF_SEPARATOR, NULL, NULL); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK], MF_STRING | MF_DISABLED, MENU_ITEM_PLAYBACK_SEEK_FORWARD, MENU_LABEL_PLAYBACK_SEEK_FORWARD); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK], MF_STRING | MF_DISABLED, MENU_ITEM_PLAYBACK_SEEK_BACK, MENU_LABEL_PLAYBACK_SEEK_BACK); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK], MF_SEPARATOR, NULL, NULL); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK], MF_STRING | MF_DISABLED, MENU_ITEM_PLAYBACK_PAUSE, MENU_LABEL_PLAYBACK_PLAY); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK], MF_STRING | MF_DISABLED, MENU_ITEM_PLAYBACK_STOP, MENU_LABEL_PLAYBACK_STOP); - - AppendMenuA(TestWindow::menus[MENU_PLAYBACK_SPEED], MFT_RADIOCHECK | MF_UNCHECKED, MENU_ITEM_PLAYBACK_SPEED_050X, MENU_LABEL_PLAYBACK_SPEED_050X); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK_SPEED], MFT_RADIOCHECK | MF_UNCHECKED, MENU_ITEM_PLAYBACK_SPEED_075X, MENU_LABEL_PLAYBACK_SPEED_075X); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK_SPEED], MFT_RADIOCHECK | MF_CHECKED, MENU_ITEM_PLAYBACK_SPEED_100X, MENU_LABEL_PLAYBACK_SPEED_100X); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK_SPEED], MFT_RADIOCHECK | MF_UNCHECKED, MENU_ITEM_PLAYBACK_SPEED_125X, MENU_LABEL_PLAYBACK_SPEED_125X); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK_SPEED], MFT_RADIOCHECK | MF_UNCHECKED, MENU_ITEM_PLAYBACK_SPEED_150X, MENU_LABEL_PLAYBACK_SPEED_150X); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK_SPEED], MFT_RADIOCHECK | MF_UNCHECKED, MENU_ITEM_PLAYBACK_SPEED_175X, MENU_LABEL_PLAYBACK_SPEED_175X); - AppendMenuA(TestWindow::menus[MENU_PLAYBACK_SPEED], MFT_RADIOCHECK | MF_UNCHECKED, MENU_ITEM_PLAYBACK_SPEED_200X, MENU_LABEL_PLAYBACK_SPEED_200X); - - // AUDIO - - AppendMenuA(TestWindow::menus[MENU_AUDIO], MF_POPUP | MF_DISABLED, (UINT_PTR)TestWindow::menus[MENU_AUDIO_TRACK], MENU_LABEL_AUDIO_TRACK); - AppendMenuA(TestWindow::menus[MENU_AUDIO], MF_POPUP, (UINT_PTR)TestWindow::menus[MENU_AUDIO_DEVICE], MENU_LABEL_AUDIO_DEVICE); - AppendMenuA(TestWindow::menus[MENU_AUDIO], MF_SEPARATOR, NULL, NULL); - AppendMenuA(TestWindow::menus[MENU_AUDIO], MF_STRING | MF_DISABLED, MENU_ITEM_AUDIO_VOLUME_UP, MENU_LABEL_AUDIO_VOLUME_UP); - AppendMenuA(TestWindow::menus[MENU_AUDIO], MF_STRING | MF_DISABLED, MENU_ITEM_AUDIO_VOLUME_DOWN, MENU_LABEL_AUDIO_VOLUME_DOWN); - AppendMenuA(TestWindow::menus[MENU_AUDIO], MF_STRING | MF_DISABLED, MENU_ITEM_AUDIO_MUTE, MENU_LABEL_AUDIO_MUTE); - - // SUBTITLE - - AppendMenuA(TestWindow::menus[MENU_SUBTITLE], MF_POPUP | MF_DISABLED, (UINT_PTR)TestWindow::menus[MENU_SUBTITLE_TRACK], MENU_LABEL_SUBTITLE_TRACK); - - // HELP - - AppendMenuA(TestWindow::menus[MENU_HELP], MF_STRING, MENU_ITEM_HELP_ABOUT, MENU_LABEL_HELP_ABOUT); - - SetMenu(sysInfo.info.win.window, TestWindow::menus[MENU]); + TestWindow::initButtons(); } -#endif -void TestWindow::initSubMenuTracks( - const std::vector &tracks, - Menu menu, - MenuItem firstItem, - std::unordered_map &tracksMap, - std::unordered_map &trackIdsMap -) +void TestWindow::initButtons() { - std::vector trackTitles; - - tracksMap.clear(); + auto open = new Button(TestWindow::renderer, BUTTON_ID_OPEN, "OPEN"); - for (unsigned short i = 0; i < (unsigned short)tracks.size(); i++) - { - LVP_MediaTrack track = tracks[i]; + TestWindow::buttonIds[BUTTON_ID_OPEN] = open; + TestWindow::buttons.push_back(open); - tracksMap[firstItem + i] = track; - trackIdsMap[track.track] = (unsigned short)(firstItem + i); + auto stop = new Button(TestWindow::renderer, BUTTON_ID_STOP, "STOP", false); - std::string metaLanguage = track.meta["language"]; - std::string metaTitle = track.meta["title"]; + TestWindow::buttonIds[BUTTON_ID_STOP] = stop; + TestWindow::buttons.push_back(stop); - auto offset = (menu == MENU_SUBTITLE_TRACK ? 0 : 1); - auto title = (!metaTitle.empty() ? metaTitle : std::format("Track {}", (i + offset)));; - auto language = (!metaLanguage.empty() ? std::format("\t{}", metaLanguage) : "");; - auto trackTitle = std::format("{}{}", title, language); - - trackTitles.push_back(trackTitle); - } + auto seekBack = new Button(TestWindow::renderer, BUTTON_ID_SEEK_BACK, "<< SEEK", false); - TestWindow::initSubMenuItems(trackTitles, menu, firstItem); -} - -void TestWindow::InitSubMenuAudioTracks(const std::vector &tracks) -{ - TestWindow::initSubMenuTracks(tracks, MENU_AUDIO_TRACK, MENU_ITEM_AUDIO_TRACK1, TestWindow::audioTracks, TestWindow::audioTrackIds); -} - -void TestWindow::InitSubMenuSubtitleTracks(const std::vector &tracks) -{ - TestWindow::initSubMenuTracks(tracks, MENU_SUBTITLE_TRACK, MENU_ITEM_SUBTITLE_TRACK1, TestWindow::subtitleTracks, TestWindow::subtitleTrackIds); -} - -void TestWindow::InitSubMenuChapters(const std::vector &chapters) -{ - std::vector chapterTitles; - - TestWindow::chapters.clear(); - - for (unsigned short i = 0; i < (unsigned short)chapters.size(); i++) - { - LVP_MediaChapter chapter = chapters[i]; - - TestWindow::chapters[MENU_ITEM_PLAYBACK_CHAPTER1 + i] = chapter; - - auto startChrono = std::chrono::seconds(chapter.startTime / 1000); - auto chapterTitle = std::format("{}\t{:%T}", chapter.title, startChrono); - - chapterTitles.push_back(chapterTitle); - } - - TestWindow::initSubMenuItems(chapterTitles, MENU_PLAYBACK_CHAPTER, MENU_ITEM_PLAYBACK_CHAPTER1); -} - -void TestWindow::InitSubMenuItems(const std::vector &items, Menu menu, MenuItem firstItem, const std::string &defaultLabel) -{ - #if defined _windows - TestWindow::clearSubMenuItems(menu, firstItem); + TestWindow::buttonIds[BUTTON_ID_SEEK_BACK] = seekBack; + TestWindow::buttons.push_back(seekBack); - AppendMenuA(TestWindow::menus[menu], MFT_RADIOCHECK | MF_CHECKED, firstItem, defaultLabel.c_str()); + auto seekForward = new Button(TestWindow::renderer, BUTTON_ID_SEEK_FORWARD, "SEEK >>", false); - for (size_t i = 0; i < items.size(); i++) - AppendMenuA(TestWindow::menus[menu], MFT_RADIOCHECK | MF_UNCHECKED, ((size_t)firstItem + 1 + i), items[i].c_str()); - #endif -} + TestWindow::buttonIds[BUTTON_ID_SEEK_FORWARD] = seekForward; + TestWindow::buttons.push_back(seekForward); -void TestWindow::initSubMenuItems(const std::vector &items, Menu menu, MenuItem firstItem) -{ - #if defined _windows - TestWindow::clearSubMenuItems(menu, firstItem); + auto progress = new Button(TestWindow::renderer, BUTTON_ID_PROGRESS, "00:00:00 / 00:00:00", false); - for (size_t i = 0; i < items.size(); i++) - AppendMenuA(TestWindow::menus[menu], MFT_RADIOCHECK | (i > 0 ? MF_UNCHECKED : MF_CHECKED), (firstItem + i), items[i].c_str()); - #endif + TestWindow::buttonIds[BUTTON_ID_PROGRESS] = progress; + TestWindow::buttons.push_back(progress); } #if defined _linux @@ -536,13 +193,11 @@ std::wstring TestWindow::OpenFile() void TestWindow::Quit() { - #if defined _windows - for (auto i = 0; i < NR_OF_CONTROLS; i++) - DestroyWindow(TestWindow::controls[i]); + for (const auto& button : TestWindow::buttons) + delete button; - for (auto i = 0; i < NR_OF_MENUS; i++) - DestroyMenu(TestWindow::menus[i]); - #endif + TestWindow::buttonIds.clear(); + TestWindow::buttons.clear(); if (TestWindow::renderer) { SDL_DestroyRenderer(TestWindow::renderer); @@ -554,197 +209,74 @@ void TestWindow::Quit() TestWindow::window = nullptr; } + TTF_Quit(); SDL_Quit(); } -void TestWindow::Resize() +void TestWindow::RenderControls(const SDL_Rect& destination) { - auto sysInfo = TestWindow::getSysInfo(); - auto window = TestWindow::GetDimensions(); + SDL_Point mousePosition = {}; + SDL_GetMouseState(&mousePosition.x, &mousePosition.y); - TestWindow::width = window.w; - TestWindow::height = window.h; + SDL_Color backgroundColor = { 0x10, 0x10, 0x10, 0xFF }; + SDL_Color highlightColor = { 0x40, 0x40, 0x40, 0xFF }; + SDL_Color lineColor = { 0x80, 0x80, 0x80, 0xFF }; - #if defined _windows - TestWindow::initControls(sysInfo); - #endif -} + auto controlsSize = ((int)TestWindow::buttons.size() * 2 * 10); -void TestWindow::ShowAbout() -{ - #if defined _windows - MessageBoxA(nullptr, TestWindow::about.c_str(), "About", MB_OK); - #endif -} + for (const auto& button : TestWindow::buttons) + controlsSize += button->size.x; -void TestWindow::toggleBitmapsEnabled(bool isPlayerActive, bool isPaused, bool isMuted) -{ - auto muteBitmap = (isMuted ? BITMAP_MUTE : BITMAP_VOLUME); - auto pauseBitmap = (!isPlayerActive || isPaused ? BITMAP_PLAY : BITMAP_PAUSE); + auto offsetX = std::max(0, (std::max(0, (destination.w - controlsSize)) / 2)); - TestWindow::updateBitmap(CONTROL_MUTE, muteBitmap); - TestWindow::updateBitmap(CONTROL_PAUSE, pauseBitmap); - TestWindow::updateBitmap(CONTROL_STOP, BITMAP_STOP); -} + SDL_SetRenderDrawColor(TestWindow::renderer, backgroundColor.r, backgroundColor.g, backgroundColor.b, backgroundColor.a); + SDL_RenderFillRect(TestWindow::renderer, &destination); -void TestWindow::toggleControlsEnabled(bool isPlayerActive) -{ - #if defined _windows - for (auto i = 0; i < NR_OF_CONTROLS; i++) - { - if ((bool)IsWindowEnabled(TestWindow::controls[i]) != isPlayerActive) { - EnableWindow(TestWindow::controls[i], isPlayerActive); - InvalidateRect(TestWindow::controls[i], NULL, 1); - } - } - #endif -} + int lineY = (!TestWindow::buttons.empty() ? TestWindow::buttons[0]->background.y : 0); + int lineHeight = (!TestWindow::buttons.empty() ? TestWindow::buttons[0]->background.h : 0); -void TestWindow::ToggleMenuChecked(unsigned short id, Menu menu, MenuItem firstItem) -{ - #if defined _windows - auto itemCount = GetMenuItemCount(TestWindow::menus[menu]); - auto lastID = (firstItem + itemCount - 1); - - CheckMenuRadioItem(TestWindow::menus[menu], firstItem, lastID, id, MF_BYCOMMAND); - #endif -} - -void TestWindow::toggleMenuEnabled(bool isPlayerActive, bool isPaused, bool isMuted) -{ - auto muteLabel = (isMuted ? MENU_LABEL_AUDIO_UNMUTE : MENU_LABEL_AUDIO_MUTE); - auto pauseLabel = (!isPlayerActive || isPaused ? MENU_LABEL_PLAYBACK_PLAY : MENU_LABEL_PLAYBACK_PAUSE); - - TestWindow::updateToggleMenuItem(MENU_AUDIO, MENU_ITEM_AUDIO_MUTE, muteLabel); - TestWindow::updateToggleMenuItem(MENU_PLAYBACK, MENU_ITEM_PLAYBACK_PAUSE, pauseLabel); - - #if defined _windows - UINT AUDIO_TRACKS_ENABLED = (isPlayerActive && !TestWindow::audioTracks.empty() ? MF_ENABLED : MF_DISABLED); - UINT SUB_TRACKS_ENABLED = (isPlayerActive && !TestWindow::subtitleTracks.empty() ? MF_ENABLED : MF_DISABLED); - UINT CHAPTERS_ENABLED = (isPlayerActive && !TestWindow::chapters.empty() ? MF_ENABLED : MF_DISABLED); - UINT ENABLED = (isPlayerActive ? MF_ENABLED : MF_DISABLED); - - EnableMenuItem(TestWindow::menus[MENU_AUDIO], MENU_ITEM_AUDIO_MUTE, ENABLED); - EnableMenuItem(TestWindow::menus[MENU_AUDIO], MENU_ITEM_AUDIO_VOLUME_DOWN, ENABLED); - EnableMenuItem(TestWindow::menus[MENU_AUDIO], MENU_ITEM_AUDIO_VOLUME_UP, ENABLED); - EnableMenuItem(TestWindow::menus[MENU_PLAYBACK], MENU_ITEM_PLAYBACK_PAUSE, ENABLED); - EnableMenuItem(TestWindow::menus[MENU_PLAYBACK], MENU_ITEM_PLAYBACK_SEEK_BACK, ENABLED); - EnableMenuItem(TestWindow::menus[MENU_PLAYBACK], MENU_ITEM_PLAYBACK_SEEK_FORWARD, ENABLED); - EnableMenuItem(TestWindow::menus[MENU_PLAYBACK], MENU_ITEM_PLAYBACK_STOP, ENABLED); - - EnableMenuItem(TestWindow::menus[MENU_AUDIO], (UINT)((UINT_PTR)TestWindow::menus[MENU_AUDIO_TRACK]), AUDIO_TRACKS_ENABLED); - EnableMenuItem(TestWindow::menus[MENU_SUBTITLE], (UINT)((UINT_PTR)TestWindow::menus[MENU_SUBTITLE_TRACK]), SUB_TRACKS_ENABLED); - EnableMenuItem(TestWindow::menus[MENU_PLAYBACK], (UINT)((UINT_PTR)TestWindow::menus[MENU_PLAYBACK_CHAPTER]), CHAPTERS_ENABLED); - EnableMenuItem(TestWindow::menus[MENU_PLAYBACK], (UINT)((UINT_PTR)TestWindow::menus[MENU_PLAYBACK_SPEED]), ENABLED); - #endif -} + if (!TestWindow::buttons.empty()) { + SDL_SetRenderDrawColor(TestWindow::renderer, lineColor.r, lineColor.g, lineColor.b, lineColor.a); + SDL_RenderDrawLine(TestWindow::renderer, offsetX, lineY, offsetX, (lineY + lineHeight)); + } -void TestWindow::updateBitmap(Control control, Bitmap bitmap) -{ - #if defined _windows - auto handle = (HANDLE)SendMessageA(TestWindow::controls[control], BM_GETIMAGE, (WPARAM)IMAGE_BITMAP, 0); + offsetX += 10; - if (handle != TestWindow::bitmaps[bitmap]) - SendMessageA(TestWindow::controls[control], BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)TestWindow::bitmaps[bitmap]); - #endif -} - -void TestWindow::updateChapters(int64_t progress) -{ - for (const auto &chapter : TestWindow::chapters) + for (const auto& button : TestWindow::buttons) { - if ((progress >= chapter.second.startTime) && (progress <= chapter.second.endTime)) { - TestWindow::ToggleMenuChecked(chapter.first, MENU_PLAYBACK_CHAPTER, MENU_ITEM_PLAYBACK_CHAPTER1); - break; - } - } -} - -void TestWindow::updateControlsSlider(Control id, int64_t position) -{ - #if defined _windows - SendMessageA(TestWindow::controls[id], TBM_SETPOS, (WPARAM)TRUE, (LPARAM)position); - - if (position > 0) - SendMessageA(TestWindow::controls[id], TBM_SETSEL, (WPARAM)TRUE, (LPARAM)MAKELONG(0, position)); - else - SendMessageA(TestWindow::controls[id], TBM_CLEARSEL, (WPARAM)TRUE, 0); - #endif -} + SDL_Rect highlightArea = { button->background.x - 5, button->background.y, button->background.w + 10, button->background.h }; -void TestWindow::updateControlsText(Control id, const std::string &text) -{ - #if defined _windows - char oldText[256]; - GetWindowTextA(TestWindow::controls[id], oldText, 256); + if (button->enabled && SDL_PointInRect(&mousePosition, &highlightArea)) { + SDL_SetRenderDrawColor(TestWindow::renderer, highlightColor.r, highlightColor.g, highlightColor.b, highlightColor.a); + SDL_RenderFillRect(TestWindow::renderer, &highlightArea); + } - if (std::string(oldText) != text) - SendMessageA(TestWindow::controls[id], WM_SETTEXT, 0, (LPARAM)text.c_str()); - #endif -} + button->background = { offsetX, (destination.y + 7), button->size.x, button->size.y }; -void TestWindow::updateProgress(bool isEnabled, int64_t progress, int64_t duration) -{ - if (isEnabled) - { - auto durationChrono = std::chrono::seconds(duration / 1000); - auto progressChrono = std::chrono::seconds(progress / 1000); + SDL_RenderCopy(TestWindow::renderer, button->texture, nullptr, &button->background); - TestWindow::updateControlsText(CONTROL_PROGRESS, std::format("{:%T} / {:%T}", progressChrono, durationChrono)); - } else { - TestWindow::updateControlsText(CONTROL_PROGRESS, "00:00:00 / 00:00:00"); - } -} + offsetX += (button->background.w + 10); -void TestWindow::updateTitle(bool isEnabled, const std::string &filePath) -{ - std::string title; + SDL_SetRenderDrawColor(TestWindow::renderer, lineColor.r, lineColor.g, lineColor.b, lineColor.a); + SDL_RenderDrawLine(TestWindow::renderer, offsetX, lineY, offsetX, (lineY + lineHeight)); - if (isEnabled) { - auto file = filePath.substr(filePath.rfind(PATH_SEPARATOR) + 1); - title = std::format("{} - {}", file, TestWindow::title); - } else { - title = TestWindow::title; + offsetX += 10; } - - if (title != std::string(SDL_GetWindowTitle(TestWindow::window))) - SDL_SetWindowTitle(TestWindow::window, title.c_str()); } -void TestWindow::updateToggleMenuItem(Menu menu, MenuItem item, const std::string &label) +void TestWindow::UpdateButton(ButtonId id, const std::string& label) { - #if defined _windows - ModifyMenuA(TestWindow::menus[menu], item, MF_STRING, item, label.c_str()); - #endif + if (TestWindow::buttonIds.contains(id)) + TestWindow::buttonIds[id]->update(label); } -void TestWindow::UpdateUI(uint32_t deltaTime) +void TestWindow::UpdateProgress() { - auto duration = LVP_GetDuration(); - auto isMuted = LVP_IsMuted(); - auto isPaused = LVP_IsPaused(); - bool isPlayerActive = !LVP_IsStopped(); - auto progress = LVP_GetProgress(); - - if (isPlayerActive) - TestWindow::updateControlsSlider(CONTROL_SEEK, (progress * 100 / duration)); - - TestWindow::updateControlsSlider(CONTROL_VOLUME, (int64_t)(LVP_GetVolume() * 100)); - - TestWindow::updateControlsText(CONTROL_SPEED, std::format("[{:.2f}x]", LVP_GetPlaybackSpeed())); - - TestWindow::toggleControlsEnabled(isPlayerActive); - TestWindow::toggleBitmapsEnabled(isPlayerActive, isPaused, isMuted); - TestWindow::toggleMenuEnabled(isPlayerActive, isPaused, isMuted); - - if (deltaTime < UI_UDPATE_RATE_MS) + if (LVP_IsStopped()) return; - if (isPlayerActive) - TestWindow::updateChapters(progress); - else - TestWindow::updateControlsSlider(CONTROL_SEEK, 0); + auto durationLabel = TimeFormat(LVP_GetDuration()); + auto progressLabel = TimeFormat(LVP_GetProgress()); - TestWindow::updateProgress(isPlayerActive, progress, duration); - TestWindow::updateTitle(isPlayerActive, LVP_GetFilePath()); + TestWindow::UpdateButton(BUTTON_ID_PROGRESS, TextFormat("%s / %s", progressLabel.c_str(), durationLabel.c_str())); } diff --git a/test/TestWindow.h b/test/TestWindow.h index 27c8b39..040cf74 100644 --- a/test/TestWindow.h +++ b/test/TestWindow.h @@ -1,145 +1,142 @@ #ifndef TEST_WINDOW_H #define TEST_WINDOW_H -#include -#include +#include // min/max(x) +#include #include extern "C" { - #include - #include + #include } #if defined _linux - #include // gtk_file_chooser_dialog_new(x), gtk_dialog_run(x), gtk_file_chooser_get_uri(x) + #include // gtk_file_chooser_dialog_new(x), gtk_dialog_run(x), gtk_file_chooser_get_uri(x) #elif defined _macosx #include // NSOpenPanel #elif defined _windows #include - #include - #include - #include // GetOpenFileNameA(x) + #include // GetOpenFileNameW(x) #endif #include -const enum Bitmap +template +static std::string TextFormat(const char* formatString, const Args&... args) { - BITMAP_MUTE, - BITMAP_PAUSE, - BITMAP_PLAY, - BITMAP_STOP, - BITMAP_VOLUME, - NR_OF_BITMAPS -}; + if (!formatString) + return ""; -const enum Control -{ - CONTROL_MUTE, - CONTROL_PANEL, - CONTROL_PAUSE, - CONTROL_PROGRESS, - CONTROL_SEEK, - CONTROL_SPEED, - CONTROL_STOP, - CONTROL_VOLUME, - NR_OF_CONTROLS -}; + char buffer[1024] = {}; + std::snprintf(buffer, 1024, formatString, args...); + + return std::string(buffer); +} -const enum ControlItem +static std::string TimeFormat(int64_t milliSeconds) { - CONTROL_ITEM_INVALID, - CONTROL_ITEM_MUTE = 100, - CONTROL_ITEM_PAUSE, - CONTROL_ITEM_SEEK, - CONTROL_ITEM_STOP, - CONTROL_ITEM_VOLUME -}; + auto totSecs = (milliSeconds / 1000); + auto hours = (totSecs / 3600); + auto remSecs = (totSecs % 3600); + auto minutes = (remSecs / 60); + auto seconds = (remSecs % 60); + + return TextFormat("%02d:%02d:%02d", hours, minutes, seconds); +} -const enum Menu +enum ButtonId { - MENU, - MENU_AUDIO, - MENU_AUDIO_DEVICE, - MENU_AUDIO_TRACK, - MENU_FILE, - MENU_PLAYBACK, - MENU_PLAYBACK_CHAPTER, - MENU_PLAYBACK_SPEED, - MENU_SUBTITLE, - MENU_SUBTITLE_TRACK, - MENU_HELP, - NR_OF_MENUS + BUTTON_ID_UNKNOWN = -1, + BUTTON_ID_OPEN, + BUTTON_ID_PROGRESS, + BUTTON_ID_SEEK_BACK, + BUTTON_ID_SEEK_FORWARD, + BUTTON_ID_STOP }; -const enum MenuItem +struct Button { - MENU_ITEM_AUDIO_MUTE = 1000, - MENU_ITEM_AUDIO_VOLUME_DOWN, - MENU_ITEM_AUDIO_VOLUME_UP, - MENU_ITEM_FILE_OPEN, - MENU_ITEM_FILE_QUIT, - MENU_ITEM_HELP_ABOUT, - MENU_ITEM_PLAYBACK_PAUSE, - MENU_ITEM_PLAYBACK_SEEK_BACK, - MENU_ITEM_PLAYBACK_SEEK_FORWARD, - MENU_ITEM_PLAYBACK_STOP, - MENU_ITEM_PLAYBACK_SPEED_050X = 2000, - MENU_ITEM_PLAYBACK_SPEED_075X, - MENU_ITEM_PLAYBACK_SPEED_100X, - MENU_ITEM_PLAYBACK_SPEED_125X, - MENU_ITEM_PLAYBACK_SPEED_150X, - MENU_ITEM_PLAYBACK_SPEED_175X, - MENU_ITEM_PLAYBACK_SPEED_200X, - MENU_ITEM_AUDIO_DEVICE1 = 3000, - MENU_ITEM_AUDIO_TRACK1 = 4000, - MENU_ITEM_SUBTITLE_TRACK1 = 5000, - MENU_ITEM_PLAYBACK_CHAPTER1 = 6000 + SDL_Rect background = {}; + bool enabled = true; + ButtonId id = BUTTON_ID_UNKNOWN; + std::string label = ""; + SDL_Renderer* renderer = nullptr; + SDL_Point size = {}; + SDL_Texture* texture = nullptr; + + Button(SDL_Renderer* renderer, ButtonId id, const std::string& label, bool enabled = true) + { + this->enabled = enabled; + this->id = id; + this->label = label; + this->renderer = renderer; + + this->create(); + } + + ~Button() + { + this->destroy(); + } + + void create() + { + #if defined _android + const auto FONT_PATH = "/system/fonts/DroidSans.ttf"; + #elif defined _ios + const auto FONT_PATH = "/System/Library/Fonts/Cache/arialuni.ttf"; + #elif defined _linux + const auto FONT_PATH = "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"; + #elif defined _macosx + const auto FONT_PATH = "/System/Library/Fonts/Supplemental/Arial Unicode.ttf"; + #elif defined _windows + const auto FONT_PATH = "C:\\Windows\\Fonts\\ARIALUNI.TTF"; + #endif + + auto font = TTF_OpenFont(FONT_PATH, 14); + + if (!font) + throw std::runtime_error(TextFormat("Failed to open font '%s': %s", FONT_PATH, TTF_GetError())); + + const SDL_Color WHITE = { 0xFF, 0xFF, 0xFF, 0xFF }; + const SDL_Color GRAY = { 0xAA, 0xAA, 0xAA, 0xFF }; + + auto surface = TTF_RenderUTF8_Blended(font, this->label.c_str(), (this->enabled ? WHITE : GRAY)); + + TTF_CloseFont(font); + + this->size = { surface->w, surface->h }; + this->texture = SDL_CreateTextureFromSurface(this->renderer, surface); + + SDL_FreeSurface(surface); + } + + void destroy() + { + if (this->texture) { + SDL_DestroyTexture(this->texture); + this->texture = nullptr; + } + } + + void enable(bool enabled = true) + { + this->enabled = enabled; + + this->destroy(); + this->create(); + } + + void update(const std::string& label) + { + this->label = label; + + this->destroy(); + this->create(); + } }; -const char MENU_LABEL_AUDIO[] = "Audio"; -const char MENU_LABEL_AUDIO_DEVICE[] = "Audio Device"; -const char MENU_LABEL_AUDIO_DEVICE_DEFAULT[] = "Default"; -const char MENU_LABEL_AUDIO_MUTE[] = "Mute\tM"; -const char MENU_LABEL_AUDIO_TRACK[] = "Audio Track"; -const char MENU_LABEL_AUDIO_UNMUTE[] = "Unmute\tM"; -const char MENU_LABEL_AUDIO_VOLUME_DOWN[] = "Decrease Volume\tDOWN"; -const char MENU_LABEL_AUDIO_VOLUME_UP[] = "Increase Volume\tUP"; -const char MENU_LABEL_FILE[] = "File"; -const char MENU_LABEL_FILE_OPEN[] = "Open\tCtrl+O"; -const char MENU_LABEL_FILE_QUIT[] = "Quit\tCtrl+Q"; -const char MENU_LABEL_HELP[] = "Help"; -const char MENU_LABEL_HELP_ABOUT[] = "About"; -const char MENU_LABEL_PLAYBACK[] = "Playback"; -const char MENU_LABEL_PLAYBACK_CHAPTER[] = "Chapter"; -const char MENU_LABEL_PLAYBACK_PAUSE[] = "Pause\tSPACE"; -const char MENU_LABEL_PLAYBACK_PLAY[] = "Play\tSPACE"; -const char MENU_LABEL_PLAYBACK_SEEK_BACK[] = "Seek Backward\tLEFT"; -const char MENU_LABEL_PLAYBACK_SEEK_FORWARD[] = "Seek Forward\tRIGHT"; -const char MENU_LABEL_PLAYBACK_SPEED[] = "Speed"; -const char MENU_LABEL_PLAYBACK_SPEED_050X[] = "0.5x (Slow)"; -const char MENU_LABEL_PLAYBACK_SPEED_075X[] = "0.75x (Slow)"; -const char MENU_LABEL_PLAYBACK_SPEED_100X[] = "1x (Normal)"; -const char MENU_LABEL_PLAYBACK_SPEED_125X[] = "1.25x (Fast)"; -const char MENU_LABEL_PLAYBACK_SPEED_150X[] = "1.5x (Fast)"; -const char MENU_LABEL_PLAYBACK_SPEED_175X[] = "1.75x (Fast)"; -const char MENU_LABEL_PLAYBACK_SPEED_200X[] = "2x (Fast)"; -const char MENU_LABEL_PLAYBACK_STOP[] = "Stop\tS"; -const char MENU_LABEL_SUBTITLE[] = "Subtitle"; -const char MENU_LABEL_SUBTITLE_TRACK[] = "Subtitle Track"; - -#if defined _windows - const char PATH_SEPARATOR = '\\'; -#else - const char PATH_SEPARATOR = '/'; -#endif - -const int PLAYER_CONTROLS_PANEL_HEIGHT = 34; -const int PLAYER_CONTROLS_CONTENT_HEIGHT = 30; -const int PLAYER_CONTROLS_PROGRESS_WIDTH = 130; -const int PLAYER_CONTROLS_SPEED_WIDTH = 54; -const int PLAYER_CONTROLS_VOLUME_WIDTH = 100; -const int UI_UDPATE_RATE_MS = 500; +using ButtonIds = std::unordered_map; +using Buttons = std::vector; class TestWindow { @@ -148,79 +145,31 @@ class TestWindow ~TestWindow() {} private: - static std::unordered_map audioTracks; - static std::unordered_map audioTrackIds; - static std::unordered_map chapters; - static std::unordered_map subtitleTracks; - static std::unordered_map subtitleTrackIds; - - static std::string about; - static int height; + static ButtonIds buttonIds; + static Buttons buttons; static SDL_Renderer* renderer; static std::string title; - static int width; static SDL_Window* window; - #if defined _windows - static HANDLE bitmaps[NR_OF_BITMAPS]; - static HWND controls[NR_OF_CONTROLS]; - static HMENU menus[NR_OF_MENUS]; - #endif - public: - static LVP_MediaTrack GetAudioTrack(unsigned short id); - static unsigned short GetAudioTrackId(int track); - static LVP_MediaTrack GetSubtitleTrack(unsigned short id); - static unsigned short GetSubtitleTrackId(int track); - static LVP_MediaChapter GetChapter(unsigned short id); - static double GetControlSliderClickPosition(Control control); - static SDL_Rect GetDimensions(); - static unsigned short GetMenuIdPlaybackSpeed(double speed); - static std::string GetMenuLabel(unsigned short id, Menu menu); - static SDL_Renderer* GetRenderer(); - static void Init(int width, int height); - static void InitSubMenuAudioTracks(const std::vector &tracks); - static void InitSubMenuSubtitleTracks(const std::vector &tracks); - static void InitSubMenuChapters(const std::vector &chapters); - static void InitSubMenuItems(const std::vector &items, Menu menu, MenuItem firstItem, const std::string &defaultLabel); - static void Quit(); - static void Resize(); - static void ShowAbout(); - static void ToggleMenuChecked(unsigned short id, Menu menu, MenuItem firstItem); - static void UpdateUI(uint32_t deltaTime); + static void EnableButton(ButtonId id, bool enabled = true); + static Button* GetClickedButton(const SDL_Point& clickPosition); + static SDL_Rect GetDimensions(); + static SDL_Renderer* GetRenderer(); + static void Init(int width, int height); + static void Quit(); + static void RenderControls(const SDL_Rect& destination); + static void UpdateButton(ButtonId id, const std::string& label); + static void UpdateProgress(); #if defined _windows - static ControlItem GetControlId(HWND handle); static std::wstring OpenFile(); - #else + #elif defined _linux || defined _macosx static std::string OpenFile(); #endif private: - static void clearSubMenuItems(Menu menu, MenuItem firstItem); - static SDL_SysWMinfo getSysInfo(); - static void initSubMenuItems(const std::vector &items, Menu menu, MenuItem firstItem); - static void initSubMenuTracks(const std::vector &tracks, Menu menu, MenuItem firstItem, std::unordered_map &tracksMap, std::unordered_map &trackIdsMap); - static void toggleBitmapsEnabled(bool isPlayerActive, bool isPaused, bool isMuted); - static void toggleControlsEnabled(bool isPlayerActive); - static void toggleMenuEnabled(bool isPlayerActive, bool isPaused, bool isMuted); - static void updateBitmap(Control control, Bitmap bitmap); - static void updateChapters(int64_t progress); - static void updateControlsSlider(Control control, int64_t position); - static void updateControlsText(Control control, const std::string &text); - static void updateProgress(bool isEnabled, int64_t progress, int64_t duration); - static void updateTitle(bool isEnabled, const std::string &filePath); - static void updateToggleMenuItem(Menu menu, MenuItem item, const std::string &label); - - #if defined _windows - static HANDLE getBitmap(const std::string &file); - static HWND getControl(LPCSTR className, DWORD style, const SDL_Rect &location, const SDL_SysWMinfo &sysInfo, HMENU id = NULL); - static HWND getControlButton(DWORD style, const SDL_Rect &location, const SDL_SysWMinfo &sysInfo, HMENU id = NULL); - static HWND getControlStatic(DWORD style, const SDL_Rect &location, const SDL_SysWMinfo &sysInfo); - static void initBitmaps(); - static void initControls(const SDL_SysWMinfo &sysInfo); - static void initMenu(const SDL_SysWMinfo &sysInfo); - #endif + static void initButtons(); }; diff --git a/test/main.cpp b/test/main.cpp index 33d794a..3a2a912 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -8,78 +8,22 @@ const int MS_PER_FRAME_IDLE = 200; bool QUIT = false; -void handleOpenUI() -{ - TestWindow::InitSubMenuAudioTracks(LVP_GetAudioTracks()); - TestWindow::InitSubMenuSubtitleTracks(LVP_GetSubtitleTracks()); - TestWindow::InitSubMenuChapters(LVP_GetChapters()); - - auto audioTrack = LVP_GetAudioTrack(); - auto subtitleTrack = LVP_GetSubtitleTrack(); - - TestWindow::ToggleMenuChecked(TestWindow::GetAudioTrackId(audioTrack), MENU_AUDIO_TRACK, MENU_ITEM_AUDIO_TRACK1); - TestWindow::ToggleMenuChecked(TestWindow::GetSubtitleTrackId(subtitleTrack), MENU_SUBTITLE_TRACK, MENU_ITEM_SUBTITLE_TRACK1); -} - void handleOpenFile() { - auto file = TestWindow::OpenFile(); + #if defined _linux || defined _macosx || defined _windows + auto file = TestWindow::OpenFile(); + #else + auto file = std::string(""); + #endif - if (!file.empty()) { + if (!file.empty()) LVP_Open(file); - handleOpenUI(); - } -} - -void handleSeekToChapter(unsigned short id, Menu menu, MenuItem firstItem) -{ - TestPlayer::SeekToChapter(TestWindow::GetChapter(id)); - TestWindow::ToggleMenuChecked(id, menu, firstItem); -} - -void handleSetAudioDevice(unsigned short id, Menu menu, MenuItem firstItem) -{ - auto device = TestWindow::GetMenuLabel(id, menu); - - if (LVP_SetAudioDevice(device)) - TestWindow::ToggleMenuChecked(id, menu, firstItem); -} - -void handleSetPlaybackSpeed(unsigned short id) -{ - auto speedLabel = TestWindow::GetMenuLabel(id, MENU_PLAYBACK_SPEED); - auto speed = std::atof(speedLabel.c_str()); - - LVP_SetPlaybackSpeed(speed); - TestWindow::ToggleMenuChecked(id, MENU_PLAYBACK_SPEED, MENU_ITEM_PLAYBACK_SPEED_050X); -} - -void handleSetPlaybackSpeed() -{ - auto speed = LVP_GetPlaybackSpeed(); - auto id = TestWindow::GetMenuIdPlaybackSpeed(speed); - - TestWindow::ToggleMenuChecked(id, MENU_PLAYBACK_SPEED, MENU_ITEM_PLAYBACK_SPEED_050X); -} - -void handleSetTrack(LVP_MediaTrack track, unsigned short id, Menu menu, MenuItem firstItem) -{ - LVP_SetTrack(track); - TestWindow::ToggleMenuChecked(id, menu, firstItem); -} - -void handleStop() -{ - LVP_Stop(); - TestWindow::ToggleMenuChecked(MENU_ITEM_PLAYBACK_SPEED_100X, MENU_PLAYBACK_SPEED, MENU_ITEM_PLAYBACK_SPEED_050X); } void handleDropFileEvent(const SDL_Event &event) { LVP_Open(event.drop.file); SDL_free(event.drop.file); - - handleOpenUI(); } void handleKeyDownEvent(const SDL_KeyboardEvent &event) @@ -88,26 +32,12 @@ void handleKeyDownEvent(const SDL_KeyboardEvent &event) return; switch (event.keysym.sym) { - case SDLK_MINUS: case SDLK_KP_MINUS: - TestPlayer::SetPlaybackSpeedDown(); - handleSetPlaybackSpeed(); - break; - case SDLK_PLUS: case SDLK_KP_PLUS: - TestPlayer::SetPlaybackSpeedUp(); - handleSetPlaybackSpeed(); - break; case SDLK_LEFT: case SDLK_AUDIOREWIND: TestPlayer::SeekBack(); break; case SDLK_RIGHT: case SDLK_AUDIOFASTFORWARD: TestPlayer::SeekForward(); break; - case SDLK_DOWN: - TestPlayer::SetVolumeDown(); - break; - case SDLK_UP: - TestPlayer::SetVolumeUp(); - break; default: break; } @@ -136,11 +66,8 @@ void handleKeyUpEvent(const SDL_KeyboardEvent &event) return; switch (event.keysym.sym) { - case SDLK_m: - LVP_ToggleMute(); - break; case SDLK_s: case SDLK_AUDIOSTOP: - handleStop(); + LVP_Stop(); break; case SDLK_SPACE: case SDLK_AUDIOPLAY: LVP_TogglePause(); @@ -150,127 +77,33 @@ void handleKeyUpEvent(const SDL_KeyboardEvent &event) } } -void handleMouseScrollEvent(const SDL_MouseWheelEvent &event) +void handleMouseUpEvent(const SDL_MouseButtonEvent& event) { - if (LVP_IsStopped()) - return; - - int scrollDirection = (event.direction == SDL_MOUSEWHEEL_FLIPPED ? event.y * -1 : event.y); + SDL_Point clickPosition = { event.x, event.y }; + auto button = TestWindow::GetClickedButton(clickPosition); - if (scrollDirection > 0) - TestPlayer::SetVolumeUp(); - else - TestPlayer::SetVolumeDown(); -} - -#if defined _windows -void handleSystemCommandEvent(SDL_SysWMmsg* msg) -{ - auto id = LOWORD(msg->msg.win.wParam); + if (!button) + return; - switch (id) { - case MENU_ITEM_AUDIO_MUTE: case CONTROL_ITEM_MUTE: - LVP_ToggleMute(); - break; - case MENU_ITEM_AUDIO_VOLUME_DOWN: - TestPlayer::SetVolumeDown(); - break; - case MENU_ITEM_AUDIO_VOLUME_UP: - TestPlayer::SetVolumeUp(); - break; - case MENU_ITEM_FILE_OPEN: - handleOpenFile(); - break; - case MENU_ITEM_FILE_QUIT: - QUIT = true; - break; - case MENU_ITEM_HELP_ABOUT: - TestWindow::ShowAbout(); - break; - case MENU_ITEM_PLAYBACK_PAUSE: case CONTROL_ITEM_PAUSE: - LVP_TogglePause(); + switch (button->id) { + case BUTTON_ID_OPEN: + if (LVP_IsStopped()) + handleOpenFile(); + else + LVP_TogglePause(); break; - case MENU_ITEM_PLAYBACK_SEEK_BACK: + case BUTTON_ID_SEEK_BACK: TestPlayer::SeekBack(); break; - case MENU_ITEM_PLAYBACK_SEEK_FORWARD: + case BUTTON_ID_SEEK_FORWARD: TestPlayer::SeekForward(); break; - case MENU_ITEM_PLAYBACK_STOP: case CONTROL_ITEM_STOP: - handleStop(); + case BUTTON_ID_STOP: + LVP_Stop(); break; default: - if (id >= MENU_ITEM_PLAYBACK_CHAPTER1) - handleSeekToChapter(id, MENU_PLAYBACK_CHAPTER, MENU_ITEM_PLAYBACK_CHAPTER1); - else if (id >= MENU_ITEM_SUBTITLE_TRACK1) - handleSetTrack(TestWindow::GetSubtitleTrack(id), id, MENU_SUBTITLE_TRACK, MENU_ITEM_SUBTITLE_TRACK1); - else if (id >= MENU_ITEM_AUDIO_TRACK1) - handleSetTrack(TestWindow::GetAudioTrack(id), id, MENU_AUDIO_TRACK, MENU_ITEM_AUDIO_TRACK1); - else if (id >= MENU_ITEM_AUDIO_DEVICE1) - handleSetAudioDevice(id, MENU_AUDIO_DEVICE, MENU_ITEM_AUDIO_DEVICE1); - else if (id >= MENU_ITEM_PLAYBACK_SPEED_050X) - handleSetPlaybackSpeed(id); - break; - } - - SetFocus(msg->msg.win.hwnd); -} - -void handleSystemSliderEvent(SDL_SysWMmsg* msg) -{ - auto scrollType = LOWORD(msg->msg.win.wParam); - auto controlId = TestWindow::GetControlId((HWND)msg->msg.win.lParam); - - switch (scrollType) { - case TB_PAGEDOWN: case TB_PAGEUP: - switch (controlId) { - case CONTROL_ITEM_SEEK: - LVP_SeekTo(TestWindow::GetControlSliderClickPosition(CONTROL_SEEK)); - break; - case CONTROL_ITEM_VOLUME: - LVP_SetVolume(TestWindow::GetControlSliderClickPosition(CONTROL_VOLUME)); - break; - default: - break; - } - break; - case TB_THUMBTRACK: - switch (controlId) { - case CONTROL_ITEM_SEEK: - LVP_SeekTo((double)HIWORD(msg->msg.win.wParam) * 0.01); - break; - case CONTROL_ITEM_VOLUME: - LVP_SetVolume((double)HIWORD(msg->msg.win.wParam) * 0.01); - break; - default: - break; - } - break; - default: - break; - } - - SetFocus(msg->msg.win.hwnd); -} -#endif - -void handleSystemEvent(const SDL_SysWMEvent &event) -{ - #if defined _windows - switch (event.msg->msg.win.msg) { - case WM_COMMAND: - handleSystemCommandEvent(event.msg); - break; - case WM_HSCROLL: case WM_VSCROLL: - handleSystemSliderEvent(event.msg); - break; - case WM_DESTROY: case WM_QUERYENDSESSION: case WM_ENDSESSION: - QUIT = true; - break; - default: - break; - } - #endif + break; + } } void handleUserEvent(const SDL_UserEvent &event) @@ -278,8 +111,26 @@ void handleUserEvent(const SDL_UserEvent &event) auto eventType = (LVP_EventType)event.code; switch (eventType) { - case LVP_EVENT_MEDIA_TRACKS_UPDATED: case LVP_EVENT_METADATA_UPDATED: - handleOpenUI(); + case LVP_EVENT_MEDIA_OPENED: + TestWindow::UpdateButton(BUTTON_ID_OPEN, "PAUSE"); + + TestWindow::EnableButton(BUTTON_ID_SEEK_BACK, true); + TestWindow::EnableButton(BUTTON_ID_SEEK_FORWARD, true); + TestWindow::EnableButton(BUTTON_ID_STOP, true); + break; + case LVP_EVENT_MEDIA_PAUSED: + TestWindow::UpdateButton(BUTTON_ID_OPEN, "PLAY"); + break; + case LVP_EVENT_MEDIA_PLAYING: + TestWindow::UpdateButton(BUTTON_ID_OPEN, "PAUSE"); + break; + case LVP_EVENT_MEDIA_STOPPED: + TestWindow::UpdateButton(BUTTON_ID_OPEN, "OPEN"); + TestWindow::UpdateButton(BUTTON_ID_PROGRESS, "00:00:00 / 00:00:00"); + + TestWindow::EnableButton(BUTTON_ID_SEEK_BACK, false); + TestWindow::EnableButton(BUTTON_ID_SEEK_FORWARD, false); + TestWindow::EnableButton(BUTTON_ID_STOP, false); break; default: break; @@ -289,24 +140,23 @@ void handleUserEvent(const SDL_UserEvent &event) void handleWindowEvent(const SDL_WindowEvent &event) { switch (event.event) { - case SDL_WINDOWEVENT_CLOSE: - QUIT = true; - break; - case SDL_WINDOWEVENT_MOVED: case SDL_WINDOWEVENT_SIZE_CHANGED: - TestWindow::Resize(); - LVP_Resize(); - break; - default: - break; + case SDL_WINDOWEVENT_CLOSE: + QUIT = true; + break; + case SDL_WINDOWEVENT_MOVED: case SDL_WINDOWEVENT_SIZE_CHANGED: + LVP_Resize(); + break; + default: + break; } } int getSleepTime(uint32_t frameStart) { - auto timeToRenderFrame = (int)(SDL_GetTicks() - frameStart); - bool use60FPS = (LVP_IsPlaying() && (LVP_GetMediaType() == LVP_MEDIA_TYPE_VIDEO)); - auto timePerFrame = (use60FPS ? MS_PER_FRAME_FPS60 : MS_PER_FRAME_IDLE); - auto sleepTime = (timePerFrame - timeToRenderFrame); + auto timeToRender = (int)(SDL_GetTicks() - frameStart); + bool use60FPS = (LVP_IsPlaying() && (LVP_GetMediaType() == LVP_MEDIA_TYPE_VIDEO)); + auto timePerFrame = (use60FPS ? MS_PER_FRAME_FPS60 : MS_PER_FRAME_IDLE); + auto sleepTime = (timePerFrame - timeToRender); return sleepTime; } @@ -318,9 +168,8 @@ void handleEvents() while (SDL_PollEvent(&event)) { switch (event.type) { - case SDL_AUDIODEVICEADDED: - break; - case SDL_AUDIODEVICEREMOVED: + case SDL_QUIT: + QUIT = true; break; case SDL_DROPFILE: handleDropFileEvent(event); @@ -331,11 +180,8 @@ void handleEvents() case SDL_KEYUP: handleKeyUpEvent(event.key); break; - case SDL_MOUSEWHEEL: - handleMouseScrollEvent(event.wheel); - break; - case SDL_SYSWMEVENT: - handleSystemEvent(event.syswm); + case SDL_MOUSEBUTTONUP: + handleMouseUpEvent(event.button); break; case SDL_WINDOWEVENT: handleWindowEvent(event.window); @@ -351,8 +197,6 @@ void handleEvents() void init() { TestWindow::Init(800, 600); TestPlayer::Init(TestWindow::GetRenderer()); - - TestWindow::InitSubMenuItems(LVP_GetAudioDevices(), MENU_AUDIO_DEVICE, MENU_ITEM_AUDIO_DEVICE1, MENU_LABEL_AUDIO_DEVICE_DEFAULT); } void quit() { @@ -362,10 +206,13 @@ void quit() { void render() { + const int CONTROLS_HEIGHT = 34; + bool isPlayerActive = !LVP_IsStopped(); auto renderer = TestWindow::GetRenderer(); auto window = TestWindow::GetDimensions(); - SDL_Rect player = { 0, 0, window.w, window.h - PLAYER_CONTROLS_PANEL_HEIGHT }; + SDL_Rect player = { 0, 0, window.w, (window.h - CONTROLS_HEIGHT) }; + SDL_Rect controls = { 0, (window.h - CONTROLS_HEIGHT), window.w, CONTROLS_HEIGHT }; SDL_SetRenderTarget(renderer, nullptr); @@ -379,6 +226,8 @@ void render() if (isPlayerActive) TestPlayer::Render(renderer, player); + TestWindow::RenderControls(controls); + SDL_RenderPresent(renderer); } @@ -394,23 +243,12 @@ int SDL_main(int argc, char* argv[]) { init(); - const int MS_PER_FRAME_FPS60 = (1000 / 60); - const int MS_PER_FRAME_IDLE = 200; - - auto startTime = SDL_GetTicks(); - while (!QUIT) { auto frameStart = SDL_GetTicks(); handleEvents(); - - auto deltaTime = (SDL_GetTicks() - startTime); - - TestWindow::UpdateUI(deltaTime); - - if (deltaTime >= UI_UDPATE_RATE_MS) - startTime = SDL_GetTicks(); + TestWindow::UpdateProgress(); if (QUIT) break; diff --git a/windows/libvoyaplayer.rc b/windows/libvoyaplayer.rc index 8d34987..24ad6e4 100644 --- a/windows/libvoyaplayer.rc +++ b/windows/libvoyaplayer.rc @@ -1,8 +1,8 @@ #include 1 VERSIONINFO -FILEVERSION 1,2,0 -PRODUCTVERSION 1,2,0 +FILEVERSION 1,3,0 +PRODUCTVERSION 1,3,0 FILEOS VOS_NT FILETYPE VFT_DLL BEGIN @@ -14,8 +14,8 @@ BEGIN VALUE "LegalCopyright", "(c) 2021 Adam A. Jammary (Jammary Studio)" VALUE "ProductName", "libvoyaplayer" VALUE "FileDescription", "libvoyaplayer" - VALUE "FileVersion", "1.2.0" - VALUE "ProductVersion", "1.2.0" + VALUE "FileVersion", "1.3.0" + VALUE "ProductVersion", "1.3.0" VALUE "InternalName", "libvoyaplayer.dll" VALUE "OriginalFilename", "libvoyaplayer.dll" END