From 6bf2d4089355a69c3b0c991ff61769b39036d62e Mon Sep 17 00:00:00 2001 From: Joel Date: Sun, 9 May 2021 14:18:51 +0200 Subject: [PATCH] initial upload to git --- CMakeLists.txt | 51 ++ vlc-video-extended-plugin.c | 242 +++++++ vlc-video-extended-plugin.h | 246 +++++++ vlc-video-extended-source.c | 1199 +++++++++++++++++++++++++++++++++++ 4 files changed, 1738 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 vlc-video-extended-plugin.c create mode 100644 vlc-video-extended-plugin.h create mode 100644 vlc-video-extended-source.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..58dc62e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,51 @@ +project(vlc-video-extended) + +if(DISABLE_VLC_EXTENDED) + message(STATUS "VLC video extended plugin disabled") + return() +endif() + +if(ENABLE_VLC_EXTENDED) + find_package(LibVLC REQUIRED) +else() + find_package(LibVLC) + if(NOT LibVLC_FOUND) + message(STATUS "VLC video extended plugin disabled") + return() + endif() +endif() + +include_directories(${LIBVLC_INCLUDE_DIRS}) +add_definitions(${LIBVLC_DEFINITIONS}) + +if(MSVC) + set(vlc-video_PLATFORM_DEPS + w32-pthreads) +endif() + +set(vlc-video-extended_HEADERS + vlc-video-extended-plugin.h + ) + +set(vlc-video-extended_SOURCES + vlc-video-extended-plugin.c + vlc-video-extended-source.c + ) + +if(WIN32) + set(MODULE_DESCRIPTION "OBS VLC module") + configure_file(${CMAKE_SOURCE_DIR}/cmake/winrc/obs-module.rc.in vlc-video.rc) + list(APPEND vlc-video_SOURCES + vlc-video.rc) +endif() + +add_library(vlc-video-extended MODULE + ${vlc-video-extended_SOURCES} + ${vlc-video-extended_HEADERS}) +# instead of linking vlc we load at runtime. +target_link_libraries(vlc-video-extended + libobs + ${vlc-video_PLATFORM_DEPS}) +set_target_properties(vlc-video-extended PROPERTIES FOLDER "plugins") + +install_obs_plugin_with_data(vlc-video-extended data) diff --git a/vlc-video-extended-plugin.c b/vlc-video-extended-plugin.c new file mode 100644 index 0000000..934dd98 --- /dev/null +++ b/vlc-video-extended-plugin.c @@ -0,0 +1,242 @@ +#ifdef _WIN32 +#include +#endif + +#include +#include "vlc-video-extended-plugin.h" + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE("vlc-video-extended", "en-US") +MODULE_EXPORT const char *obs_module_description(void) +{ + return "VLC playlist source extended"; +} + +/* libvlc core */ +LIBVLC_NEW libvlc_new_; +LIBVLC_RELEASE libvlc_release_; +LIBVLC_CLOCK libvlc_clock_; +LIBVLC_EVENT_ATTACH libvlc_event_attach_; + +/* libvlc media */ +LIBVLC_MEDIA_NEW_PATH libvlc_media_new_path_; +LIBVLC_MEDIA_NEW_LOCATION libvlc_media_new_location_; +LIBVLC_MEDIA_ADD_OPTION libvlc_media_add_option_; +LIBVLC_MEDIA_RELEASE libvlc_media_release_; +LIBVLC_MEDIA_RELEASE libvlc_media_retain_; +LIBVLC_MEDIA_GET_META libvlc_media_get_meta_; + +/* libvlc media player */ +LIBVLC_MEDIA_PLAYER_NEW libvlc_media_player_new_; +LIBVLC_MEDIA_PLAYER_NEW_FROM_MEDIA libvlc_media_player_new_from_media_; +LIBVLC_MEDIA_PLAYER_RELEASE libvlc_media_player_release_; +LIBVLC_VIDEO_SET_CALLBACKS libvlc_video_set_callbacks_; +LIBVLC_VIDEO_SET_FORMAT_CALLBACKS libvlc_video_set_format_callbacks_; +LIBVLC_AUDIO_SET_CALLBACKS libvlc_audio_set_callbacks_; +LIBVLC_AUDIO_SET_FORMAT_CALLBACKS libvlc_audio_set_format_callbacks_; +LIBVLC_MEDIA_PLAYER_PLAY libvlc_media_player_play_; +LIBVLC_MEDIA_PLAYER_STOP libvlc_media_player_stop_; +LIBVLC_MEDIA_PLAYER_GET_TIME libvlc_media_player_get_time_; +LIBVLC_MEDIA_PLAYER_SET_TIME libvlc_media_player_set_time_; +LIBVLC_VIDEO_GET_SIZE libvlc_video_get_size_; +LIBVLC_MEDIA_PLAYER_EVENT_MANAGER libvlc_media_player_event_manager_; +LIBVLC_MEDIA_PLAYER_GET_STATE libvlc_media_player_get_state_; +LIBVLC_MEDIA_PLAYER_GET_LENGTH libvlc_media_player_get_length_; +LIBVLC_MEDIA_PLAYER_GET_MEDIA libvlc_media_player_get_media_; + +/* libvlc media list */ +LIBVLC_MEDIA_LIST_NEW libvlc_media_list_new_; +LIBVLC_MEDIA_LIST_RELEASE libvlc_media_list_release_; +LIBVLC_MEDIA_LIST_ADD_MEDIA libvlc_media_list_add_media_; +LIBVLC_MEDIA_LIST_LOCK libvlc_media_list_lock_; +LIBVLC_MEDIA_LIST_UNLOCK libvlc_media_list_unlock_; +LIBVLC_MEDIA_LIST_EVENT_MANAGER libvlc_media_list_event_manager_; + +/* libvlc media list player */ +LIBVLC_MEDIA_LIST_PLAYER_NEW libvlc_media_list_player_new_; +LIBVLC_MEDIA_LIST_PLAYER_RELEASE libvlc_media_list_player_release_; +LIBVLC_MEDIA_LIST_PLAYER_PLAY libvlc_media_list_player_play_; +LIBVLC_MEDIA_LIST_PLAYER_PAUSE libvlc_media_list_player_pause_; +LIBVLC_MEDIA_LIST_PLAYER_SET_PAUSE libvlc_media_list_player_set_pause_; +LIBVLC_MEDIA_LIST_PLAYER_STOP libvlc_media_list_player_stop_; +LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_PLAYER +libvlc_media_list_player_set_media_player_; +LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_LIST libvlc_media_list_player_set_media_list_; +LIBVLC_MEDIA_LIST_PLAYER_EVENT_MANAGER libvlc_media_list_player_event_manager_; +LIBVLC_MEDIA_LIST_PLAYER_SET_PLAYBACK_MODE +libvlc_media_list_player_set_playback_mode_; +LIBVLC_MEDIA_LIST_PLAYER_NEXT libvlc_media_list_player_next_; +LIBVLC_MEDIA_LIST_PLAYER_PREVIOUS libvlc_media_list_player_previous_; + +void *libvlc_module = NULL; +#ifdef __APPLE__ +void *libvlc_core_module = NULL; +#endif + +libvlc_instance_t *libvlc = NULL; +uint64_t time_start = 0; + +static bool load_vlc_funcs(void) +{ +#define LOAD_VLC_FUNC(func) \ + do { \ + func##_ = os_dlsym(libvlc_module, #func); \ + if (!func##_) { \ + blog(LOG_WARNING, \ + "Could not func VLC function %s, " \ + "VLC loading failed", \ + #func); \ + return false; \ + } \ + } while (false) + + /* libvlc core */ + LOAD_VLC_FUNC(libvlc_new); + LOAD_VLC_FUNC(libvlc_release); + LOAD_VLC_FUNC(libvlc_clock); + LOAD_VLC_FUNC(libvlc_event_attach); + + /* libvlc media */ + LOAD_VLC_FUNC(libvlc_media_new_path); + LOAD_VLC_FUNC(libvlc_media_new_location); + LOAD_VLC_FUNC(libvlc_media_add_option); + LOAD_VLC_FUNC(libvlc_media_release); + LOAD_VLC_FUNC(libvlc_media_retain); + LOAD_VLC_FUNC(libvlc_media_get_meta); + + /* libvlc media player */ + LOAD_VLC_FUNC(libvlc_media_player_new); + LOAD_VLC_FUNC(libvlc_media_player_new_from_media); + LOAD_VLC_FUNC(libvlc_media_player_release); + LOAD_VLC_FUNC(libvlc_video_set_callbacks); + LOAD_VLC_FUNC(libvlc_video_set_format_callbacks); + LOAD_VLC_FUNC(libvlc_audio_set_callbacks); + LOAD_VLC_FUNC(libvlc_audio_set_format_callbacks); + LOAD_VLC_FUNC(libvlc_media_player_play); + LOAD_VLC_FUNC(libvlc_media_player_stop); + LOAD_VLC_FUNC(libvlc_media_player_get_time); + LOAD_VLC_FUNC(libvlc_media_player_set_time); + LOAD_VLC_FUNC(libvlc_video_get_size); + LOAD_VLC_FUNC(libvlc_media_player_event_manager); + LOAD_VLC_FUNC(libvlc_media_player_get_state); + LOAD_VLC_FUNC(libvlc_media_player_get_length); + LOAD_VLC_FUNC(libvlc_media_player_get_media); + + /* libvlc media list */ + LOAD_VLC_FUNC(libvlc_media_list_new); + LOAD_VLC_FUNC(libvlc_media_list_release); + LOAD_VLC_FUNC(libvlc_media_list_add_media); + LOAD_VLC_FUNC(libvlc_media_list_lock); + LOAD_VLC_FUNC(libvlc_media_list_unlock); + LOAD_VLC_FUNC(libvlc_media_list_event_manager); + + /* libvlc media list player */ + LOAD_VLC_FUNC(libvlc_media_list_player_new); + LOAD_VLC_FUNC(libvlc_media_list_player_release); + LOAD_VLC_FUNC(libvlc_media_list_player_play); + LOAD_VLC_FUNC(libvlc_media_list_player_pause); + LOAD_VLC_FUNC(libvlc_media_list_player_set_pause); + LOAD_VLC_FUNC(libvlc_media_list_player_stop); + LOAD_VLC_FUNC(libvlc_media_list_player_set_media_player); + LOAD_VLC_FUNC(libvlc_media_list_player_set_media_list); + LOAD_VLC_FUNC(libvlc_media_list_player_event_manager); + LOAD_VLC_FUNC(libvlc_media_list_player_set_playback_mode); + LOAD_VLC_FUNC(libvlc_media_list_player_next); + LOAD_VLC_FUNC(libvlc_media_list_player_previous); + return true; +} + +static bool load_libvlc_module(void) +{ +#ifdef _WIN32 + char *path_utf8 = NULL; + wchar_t path[1024]; + LSTATUS status; + DWORD size; + HKEY key; + + memset(path, 0, 1024 * sizeof(wchar_t)); + + status = RegOpenKeyW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\VideoLAN\\VLC", + &key); + if (status != ERROR_SUCCESS) + return false; + + size = 1024; + status = RegQueryValueExW(key, L"InstallDir", NULL, NULL, (LPBYTE)path, + &size); + if (status == ERROR_SUCCESS) { + wcscat(path, L"\\libvlc.dll"); + os_wcs_to_utf8_ptr(path, 0, &path_utf8); + libvlc_module = os_dlopen(path_utf8); + bfree(path_utf8); + } + + RegCloseKey(key); +#else + +#ifdef __APPLE__ +#define LIBVLC_DIR "/Applications/VLC.app/Contents/MacOS/" +/* According to otoolo -L, this is what libvlc.dylib wants. */ +#define LIBVLC_CORE_FILE LIBVLC_DIR "lib/libvlccore.dylib" +#define LIBVLC_FILE LIBVLC_DIR "lib/libvlc.5.dylib" + setenv("VLC_PLUGIN_PATH", LIBVLC_DIR "plugins", false); + libvlc_core_module = os_dlopen(LIBVLC_CORE_FILE); + + if (!libvlc_core_module) + return false; +#else +#define LIBVLC_FILE "libvlc.so.5" +#endif + libvlc_module = os_dlopen(LIBVLC_FILE); + +#endif + + return libvlc_module != NULL; +} + +extern struct obs_source_info vlc_source_info; + +bool load_libvlc(void) +{ + if (libvlc) + return true; + + libvlc = libvlc_new_(0, 0); + if (!libvlc) { + blog(LOG_INFO, "Couldn't create libvlc instance"); + return false; + } + + time_start = (uint64_t)libvlc_clock_() * 1000ULL; + return true; +} + +bool obs_module_load(void) +{ + if (!load_libvlc_module()) { + blog(LOG_INFO, "Couldn't find VLC installation, VLC video " + "source disabled"); + return true; + } + + if (!load_vlc_funcs()) + return true; + + blog(LOG_INFO, "VLC found, VLC video source enabled"); + + obs_register_source(&vlc_source_info); + return true; +} + +void obs_module_unload(void) +{ + if (libvlc) + libvlc_release_(libvlc); +#ifdef __APPLE__ + if (libvlc_core_module) + os_dlclose(libvlc_core_module); +#endif + if (libvlc_module) + os_dlclose(libvlc_module); +} diff --git a/vlc-video-extended-plugin.h b/vlc-video-extended-plugin.h new file mode 100644 index 0000000..b500500 --- /dev/null +++ b/vlc-video-extended-plugin.h @@ -0,0 +1,246 @@ +#include +#include + +#ifdef _MSC_VER +#include +typedef SSIZE_T ssize_t; +#endif + +#include +#include +#include +#include +#include + +extern libvlc_instance_t *libvlc; +extern uint64_t time_start; + +extern bool load_libvlc(void); + +/* libvlc core */ +typedef libvlc_instance_t *(*LIBVLC_NEW)(int argc, const char *const *argv); +typedef void (*LIBVLC_RELEASE)(libvlc_instance_t *p_instance); +typedef int64_t (*LIBVLC_CLOCK)(void); +typedef int (*LIBVLC_EVENT_ATTACH)(libvlc_event_manager_t *p_event_manager, + libvlc_event_type_t i_event_type, + libvlc_callback_t f_callback, + void *user_data); + +/* libvlc media */ +typedef libvlc_media_t *(*LIBVLC_MEDIA_NEW_PATH)(libvlc_instance_t *p_instance, + const char *path); +typedef libvlc_media_t *(*LIBVLC_MEDIA_NEW_LOCATION)( + libvlc_instance_t *p_instance, const char *location); +typedef void (*LIBVLC_MEDIA_ADD_OPTION)(libvlc_media_t *p_md, + const char *options); +typedef void (*LIBVLC_MEDIA_RETAIN)(libvlc_media_t *p_md); +typedef void (*LIBVLC_MEDIA_RELEASE)(libvlc_media_t *p_md); +typedef char *(*LIBVLC_MEDIA_GET_META)(libvlc_media_t *p_md, + libvlc_meta_t e_meta); + +/* libvlc media player */ +typedef libvlc_media_player_t *(*LIBVLC_MEDIA_PLAYER_NEW)( + libvlc_instance_t *p_libvlc); +typedef libvlc_media_player_t *(*LIBVLC_MEDIA_PLAYER_NEW_FROM_MEDIA)( + libvlc_media_t *p_md); +typedef void (*LIBVLC_MEDIA_PLAYER_RELEASE)(libvlc_media_player_t *p_mi); +typedef void (*LIBVLC_VIDEO_SET_CALLBACKS)(libvlc_media_player_t *mp, + libvlc_video_lock_cb lock, + libvlc_video_unlock_cb unlock, + libvlc_video_display_cb display, + void *opaque); +typedef void (*LIBVLC_VIDEO_SET_FORMAT_CALLBACKS)( + libvlc_media_player_t *mp, libvlc_video_format_cb setup, + libvlc_video_cleanup_cb cleanup); +typedef void (*LIBVLC_AUDIO_SET_CALLBACKS)( + libvlc_media_player_t *mp, libvlc_audio_play_cb play, + libvlc_audio_pause_cb pause, libvlc_audio_resume_cb resume, + libvlc_audio_flush_cb flush, libvlc_audio_drain_cb drain, void *opaque); +typedef void (*LIBVLC_AUDIO_SET_FORMAT_CALLBACKS)( + libvlc_media_player_t *mp, libvlc_audio_setup_cb setup, + libvlc_audio_cleanup_cb cleanup); +typedef int (*LIBVLC_MEDIA_PLAYER_PLAY)(libvlc_media_player_t *p_mi); +typedef void (*LIBVLC_MEDIA_PLAYER_STOP)(libvlc_media_player_t *p_mi); +typedef libvlc_time_t (*LIBVLC_MEDIA_PLAYER_GET_TIME)( + libvlc_media_player_t *p_mi); +typedef void (*LIBVLC_MEDIA_PLAYER_SET_TIME)(libvlc_media_player_t *p_mi, + libvlc_time_t i_time); +typedef int (*LIBVLC_VIDEO_GET_SIZE)(libvlc_media_player_t *p_mi, unsigned num, + unsigned *px, unsigned *py); +typedef libvlc_event_manager_t *(*LIBVLC_MEDIA_PLAYER_EVENT_MANAGER)( + libvlc_media_player_t *p_mp); +typedef libvlc_state_t (*LIBVLC_MEDIA_PLAYER_GET_STATE)( + libvlc_media_player_t *p_mi); +typedef libvlc_time_t (*LIBVLC_MEDIA_PLAYER_GET_LENGTH)( + libvlc_media_player_t *p_mi); +typedef libvlc_media_t *(*LIBVLC_MEDIA_PLAYER_GET_MEDIA)( + libvlc_media_player_t *p_mi); + +/* libvlc media list */ +typedef libvlc_media_list_t *(*LIBVLC_MEDIA_LIST_NEW)( + libvlc_instance_t *p_instance); +typedef void (*LIBVLC_MEDIA_LIST_RELEASE)(libvlc_media_list_t *p_ml); +typedef int (*LIBVLC_MEDIA_LIST_ADD_MEDIA)(libvlc_media_list_t *p_ml, + libvlc_media_t *p_md); +typedef void (*LIBVLC_MEDIA_LIST_LOCK)(libvlc_media_list_t *p_ml); +typedef void (*LIBVLC_MEDIA_LIST_UNLOCK)(libvlc_media_list_t *p_ml); +typedef libvlc_event_manager_t *(*LIBVLC_MEDIA_LIST_EVENT_MANAGER)( + libvlc_media_list_t *p_ml); + +/* libvlc media list player */ +typedef libvlc_media_list_player_t *(*LIBVLC_MEDIA_LIST_PLAYER_NEW)( + libvlc_instance_t *p_instance); +typedef void (*LIBVLC_MEDIA_LIST_PLAYER_RELEASE)( + libvlc_media_list_player_t *p_mlp); +typedef void (*LIBVLC_MEDIA_LIST_PLAYER_PLAY)(libvlc_media_list_player_t *p_mlp); +typedef void (*LIBVLC_MEDIA_LIST_PLAYER_PAUSE)( + libvlc_media_list_player_t *p_mlp); +typedef void (*LIBVLC_MEDIA_LIST_PLAYER_SET_PAUSE)( + libvlc_media_list_player_t *p_mlp, int do_pause); +typedef void (*LIBVLC_MEDIA_LIST_PLAYER_STOP)(libvlc_media_list_player_t *p_mlp); +typedef void (*LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_PLAYER)( + libvlc_media_list_player_t *p_mlp, libvlc_media_player_t *p_mp); +typedef void (*LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_LIST)( + libvlc_media_list_player_t *p_mlp, libvlc_media_list_t *p_mlist); +typedef libvlc_event_manager_t *(*LIBVLC_MEDIA_LIST_PLAYER_EVENT_MANAGER)( + libvlc_media_list_player_t *p_mlp); +typedef void (*LIBVLC_MEDIA_LIST_PLAYER_SET_PLAYBACK_MODE)( + libvlc_media_list_player_t *p_mlp, libvlc_playback_mode_t e_mode); +typedef int (*LIBVLC_MEDIA_LIST_PLAYER_NEXT)(libvlc_media_list_player_t *p_mlp); +typedef int (*LIBVLC_MEDIA_LIST_PLAYER_PREVIOUS)( + libvlc_media_list_player_t *p_mlp); + +/* -------------------------------------------------------------------- */ + +/* libvlc core */ +extern LIBVLC_NEW libvlc_new_; +extern LIBVLC_RELEASE libvlc_release_; +extern LIBVLC_CLOCK libvlc_clock_; +extern LIBVLC_EVENT_ATTACH libvlc_event_attach_; + +/* libvlc media */ +extern LIBVLC_MEDIA_NEW_PATH libvlc_media_new_path_; +extern LIBVLC_MEDIA_NEW_LOCATION libvlc_media_new_location_; +extern LIBVLC_MEDIA_ADD_OPTION libvlc_media_add_option_; +extern LIBVLC_MEDIA_RELEASE libvlc_media_release_; +extern LIBVLC_MEDIA_RETAIN libvlc_media_retain_; +extern LIBVLC_MEDIA_GET_META libvlc_media_get_meta_; + +/* libvlc media player */ +extern LIBVLC_MEDIA_PLAYER_NEW libvlc_media_player_new_; +extern LIBVLC_MEDIA_PLAYER_NEW_FROM_MEDIA libvlc_media_player_new_from_media_; +extern LIBVLC_MEDIA_PLAYER_RELEASE libvlc_media_player_release_; +extern LIBVLC_VIDEO_SET_CALLBACKS libvlc_video_set_callbacks_; +extern LIBVLC_VIDEO_SET_FORMAT_CALLBACKS libvlc_video_set_format_callbacks_; +extern LIBVLC_AUDIO_SET_CALLBACKS libvlc_audio_set_callbacks_; +extern LIBVLC_AUDIO_SET_FORMAT_CALLBACKS libvlc_audio_set_format_callbacks_; +extern LIBVLC_MEDIA_PLAYER_PLAY libvlc_media_player_play_; +extern LIBVLC_MEDIA_PLAYER_STOP libvlc_media_player_stop_; +extern LIBVLC_MEDIA_PLAYER_GET_TIME libvlc_media_player_get_time_; +extern LIBVLC_MEDIA_PLAYER_SET_TIME libvlc_media_player_set_time_; +extern LIBVLC_VIDEO_GET_SIZE libvlc_video_get_size_; +extern LIBVLC_MEDIA_PLAYER_EVENT_MANAGER libvlc_media_player_event_manager_; +extern LIBVLC_MEDIA_PLAYER_GET_STATE libvlc_media_player_get_state_; +extern LIBVLC_MEDIA_PLAYER_GET_LENGTH libvlc_media_player_get_length_; +extern LIBVLC_MEDIA_PLAYER_GET_MEDIA libvlc_media_player_get_media_; + +/* libvlc media list */ +extern LIBVLC_MEDIA_LIST_NEW libvlc_media_list_new_; +extern LIBVLC_MEDIA_LIST_RELEASE libvlc_media_list_release_; +extern LIBVLC_MEDIA_LIST_ADD_MEDIA libvlc_media_list_add_media_; +extern LIBVLC_MEDIA_LIST_LOCK libvlc_media_list_lock_; +extern LIBVLC_MEDIA_LIST_UNLOCK libvlc_media_list_unlock_; +extern LIBVLC_MEDIA_LIST_EVENT_MANAGER libvlc_media_list_event_manager_; + +/* libvlc media list player */ +extern LIBVLC_MEDIA_LIST_PLAYER_NEW libvlc_media_list_player_new_; +extern LIBVLC_MEDIA_LIST_PLAYER_RELEASE libvlc_media_list_player_release_; +extern LIBVLC_MEDIA_LIST_PLAYER_PLAY libvlc_media_list_player_play_; +extern LIBVLC_MEDIA_LIST_PLAYER_PAUSE libvlc_media_list_player_pause_; +extern LIBVLC_MEDIA_LIST_PLAYER_SET_PAUSE libvlc_media_list_player_set_pause_; +extern LIBVLC_MEDIA_LIST_PLAYER_STOP libvlc_media_list_player_stop_; +extern LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_PLAYER + libvlc_media_list_player_set_media_player_; +extern LIBVLC_MEDIA_LIST_PLAYER_SET_MEDIA_LIST + libvlc_media_list_player_set_media_list_; +extern LIBVLC_MEDIA_LIST_PLAYER_EVENT_MANAGER + libvlc_media_list_player_event_manager_; +extern LIBVLC_MEDIA_LIST_PLAYER_SET_PLAYBACK_MODE + libvlc_media_list_player_set_playback_mode_; +extern LIBVLC_MEDIA_LIST_PLAYER_NEXT libvlc_media_list_player_next_; +extern LIBVLC_MEDIA_LIST_PLAYER_PREVIOUS libvlc_media_list_player_previous_; + +#define EXTENSIONS_AUDIO \ + "*.3ga;" \ + "*.669;" \ + "*.a52;" \ + "*.aac;" \ + "*.ac3;" \ + "*.adt;" \ + "*.adts;" \ + "*.aif;" \ + "*.aifc;" \ + "*.aiff;" \ + "*.amb;" \ + "*.amr;" \ + "*.aob;" \ + "*.ape;" \ + "*.au;" \ + "*.awb;" \ + "*.caf;" \ + "*.dts;" \ + "*.flac;" \ + "*.it;" \ + "*.kar;" \ + "*.m4a;" \ + "*.m4b;" \ + "*.m4p;" \ + "*.m5p;" \ + "*.mid;" \ + "*.mka;" \ + "*.mlp;" \ + "*.mod;" \ + "*.mpa;" \ + "*.mp1;" \ + "*.mp2;" \ + "*.mp3;" \ + "*.mpc;" \ + "*.mpga;" \ + "*.mus;" \ + "*.oga;" \ + "*.ogg;" \ + "*.oma;" \ + "*.opus;" \ + "*.qcp;" \ + "*.ra;" \ + "*.rmi;" \ + "*.s3m;" \ + "*.sid;" \ + "*.spx;" \ + "*.tak;" \ + "*.thd;" \ + "*.tta;" \ + "*.voc;" \ + "*.vqf;" \ + "*.w64;" \ + "*.wav;" \ + "*.wma;" \ + "*.wv;" \ + "*.xa;" \ + "*.xm" + +#define EXTENSIONS_VIDEO \ + "*.3g2;*.3gp;*.3gp2;*.3gpp;*.amv;*.asf;*.avi;" \ + "*.bik;*.bin;*.crf;*.divx;*.drc;*.dv;*.evo;*.f4v;*.flv;*.gvi;*.gxf;" \ + "*.iso;*.m1v;*.m2v;*.m2t;*.m2ts;*.m4v;*.mkv;*.mov;*.mp2;*.mp2v;*.mp4;" \ + "*.mp4v;*.mpe;*.mpeg;*.mpeg1;*.mpeg2;*.mpeg4;*.mpg;*.mpv2;*.mts;" \ + "*.mtv;*.mxf;*.mxg;*.nsv;*.nuv;*.ogg;*.ogm;*.ogv;*.ogx;*.ps;*.rec;" \ + "*.rm;*.rmvb;*.rpl;*.thp;*.tod;*.ts;*.tts;*.txd;*.vob;*.vro;*.webm;" \ + "*.wm;*.wmv;*.wtv;*.xesc" + +#define EXTENSIONS_PLAYLIST \ + "*.asx;*.b4s;*.cue;*.ifo;*.m3u;*.m3u8;*.pls;" \ + "*.ram;*.rar;*.sdp;*.vlc;*.xspf;*.wax;*.wvx;*.zip;*.conf" + +#define EXTENSIONS_MEDIA \ + EXTENSIONS_VIDEO ";" EXTENSIONS_AUDIO ";" EXTENSIONS_PLAYLIST diff --git a/vlc-video-extended-source.c b/vlc-video-extended-source.c new file mode 100644 index 0000000..1ca6bda --- /dev/null +++ b/vlc-video-extended-source.c @@ -0,0 +1,1199 @@ +#include "vlc-video-extended-plugin.h" +#include +#include +#include +#include + + +#define do_log(level, format, ...) \ + blog(level, "[vlc_source: '%s'] " format, \ + obs_source_get_name(ss->source), ##__VA_ARGS__) + +#define warn(format, ...) do_log(LOG_WARNING, format, ##__VA_ARGS__) + +/* clang-format off */ + +#define S_PLAYLIST "playlist" +#define S_LOOP "loop" +#define S_SHUFFLE "shuffle" +#define S_PLAYANDPAUSE "playandpause" +#define S_BEHAVIOR "playback_behavior" +#define S_BEHAVIOR_STOP_RESTART "stop_restart" +#define S_BEHAVIOR_PAUSE_UNPAUSE "pause_unpause" +#define S_BEHAVIOR_ALWAYS_PLAY "always_play" +#define S_NETWORK_CACHING "network_caching" +#define S_TRACK "track" +#define S_SUBTITLE_ENABLE "subtitle_enable" +#define S_SUBTITLE_TRACK "subtitle" + +#define T_(text) obs_module_text(text) +#define T_PLAYLIST T_("Playlist") +#define T_LOOP T_("LoopPlaylist") +#define T_SHUFFLE T_("shuffle") +#define T_PLAYANDPAUSE T_("playandpause") +#define T_BEHAVIOR T_("PlaybackBehavior") +#define T_BEHAVIOR_STOP_RESTART T_("PlaybackBehavior.StopRestart") +#define T_BEHAVIOR_PAUSE_UNPAUSE T_("PlaybackBehavior.PauseUnpause") +#define T_BEHAVIOR_ALWAYS_PLAY T_("PlaybackBehavior.AlwaysPlay") +#define T_NETWORK_CACHING T_("NetworkCaching") +#define T_TRACK T_("AudioTrack") +#define T_SUBTITLE_ENABLE T_("SubtitleEnable") +#define T_SUBTITLE_TRACK T_("SubtitleTrack") + +/* clang-format on */ + +/* ------------------------------------------------------------------------- */ + +struct media_file_data { + char *path; + libvlc_media_t *media; +}; + +enum behavior { + BEHAVIOR_STOP_RESTART, + BEHAVIOR_PAUSE_UNPAUSE, + BEHAVIOR_ALWAYS_PLAY, +}; + +struct vlc_source { + obs_source_t *source; + + libvlc_media_player_t *media_player; + libvlc_media_list_player_t *media_list_player; + + struct obs_source_frame frame; + struct obs_source_audio audio; + size_t audio_capacity; + + pthread_mutex_t mutex; + DARRAY(struct media_file_data) files; + enum behavior behavior; + bool loop; + bool shuffle; + bool playandpause; + bool playing; + + obs_hotkey_id play_pause_hotkey; + obs_hotkey_id restart_hotkey; + obs_hotkey_id stop_hotkey; + obs_hotkey_id playlist_next_hotkey; + obs_hotkey_id playlist_prev_hotkey; +}; + +static libvlc_media_t *get_media(struct darray *array, const char *path) +{ + DARRAY(struct media_file_data) files; + libvlc_media_t *media = NULL; + + files.da = *array; + + for (size_t i = 0; i < files.num; i++) { + const char *cur_path = files.array[i].path; + + if (strcmp(path, cur_path) == 0) { + media = files.array[i].media; + libvlc_media_retain_(media); + break; + } + } + + return media; +} + +static inline libvlc_media_t *create_media_from_file(const char *file) +{ + return (file && strstr(file, "://") != NULL) + ? libvlc_media_new_location_(libvlc, file) + : libvlc_media_new_path_(libvlc, file); +} + +static void free_files(struct darray *array) +{ + DARRAY(struct media_file_data) files; + files.da = *array; + + for (size_t i = 0; i < files.num; i++) { + bfree(files.array[i].path); + libvlc_media_release_(files.array[i].media); + } + + da_free(files); +} + +#define MAKEFORMAT(ch0, ch1, ch2, ch3) \ + ((uint32_t)(uint8_t)(ch0) | ((uint32_t)(uint8_t)(ch1) << 8) | \ + ((uint32_t)(uint8_t)(ch2) << 16) | ((uint32_t)(uint8_t)(ch3) << 24)) + +static inline bool chroma_is(const char *chroma, const char *val) +{ + return *(uint32_t *)chroma == *(uint32_t *)val; +} + +static enum video_format convert_vlc_video_format(char *chroma, bool *full) +{ + *full = false; + +#define CHROMA_TEST(val, ret) \ + if (chroma_is(chroma, val)) \ + return ret +#define CHROMA_CONV(val, new_val, ret) \ + do { \ + if (chroma_is(chroma, val)) { \ + *(uint32_t *)chroma = (uint32_t)new_val; \ + return ret; \ + } \ + } while (false) +#define CHROMA_CONV_FULL(val, new_val, ret) \ + do { \ + *full = true; \ + CHROMA_CONV(val, new_val, ret); \ + } while (false) + + CHROMA_TEST("RGBA", VIDEO_FORMAT_RGBA); + CHROMA_TEST("BGRA", VIDEO_FORMAT_BGRA); + + /* 4:2:0 formats */ + CHROMA_TEST("NV12", VIDEO_FORMAT_NV12); + CHROMA_TEST("I420", VIDEO_FORMAT_I420); + CHROMA_TEST("IYUV", VIDEO_FORMAT_I420); + CHROMA_CONV("NV21", MAKEFORMAT('N', 'V', '1', '2'), VIDEO_FORMAT_NV12); + CHROMA_CONV("I422", MAKEFORMAT('N', 'V', '1', '2'), VIDEO_FORMAT_NV12); + CHROMA_CONV("Y42B", MAKEFORMAT('N', 'V', '1', '2'), VIDEO_FORMAT_NV12); + CHROMA_CONV("YV12", MAKEFORMAT('N', 'V', '1', '2'), VIDEO_FORMAT_NV12); + CHROMA_CONV("yv12", MAKEFORMAT('N', 'V', '1', '2'), VIDEO_FORMAT_NV12); + + CHROMA_CONV_FULL("J420", MAKEFORMAT('J', '4', '2', '0'), + VIDEO_FORMAT_I420); + + /* 4:2:2 formats */ + CHROMA_TEST("UYVY", VIDEO_FORMAT_UYVY); + CHROMA_TEST("UYNV", VIDEO_FORMAT_UYVY); + CHROMA_TEST("UYNY", VIDEO_FORMAT_UYVY); + CHROMA_TEST("Y422", VIDEO_FORMAT_UYVY); + CHROMA_TEST("HDYC", VIDEO_FORMAT_UYVY); + CHROMA_TEST("AVUI", VIDEO_FORMAT_UYVY); + CHROMA_TEST("uyv1", VIDEO_FORMAT_UYVY); + CHROMA_TEST("2vuy", VIDEO_FORMAT_UYVY); + CHROMA_TEST("2Vuy", VIDEO_FORMAT_UYVY); + CHROMA_TEST("2Vu1", VIDEO_FORMAT_UYVY); + + CHROMA_TEST("YUY2", VIDEO_FORMAT_YUY2); + CHROMA_TEST("YUYV", VIDEO_FORMAT_YUY2); + CHROMA_TEST("YUNV", VIDEO_FORMAT_YUY2); + CHROMA_TEST("V422", VIDEO_FORMAT_YUY2); + + CHROMA_TEST("YVYU", VIDEO_FORMAT_YVYU); + + CHROMA_CONV("v210", MAKEFORMAT('U', 'Y', 'V', 'Y'), VIDEO_FORMAT_UYVY); + CHROMA_CONV("cyuv", MAKEFORMAT('U', 'Y', 'V', 'Y'), VIDEO_FORMAT_UYVY); + CHROMA_CONV("CYUV", MAKEFORMAT('U', 'Y', 'V', 'Y'), VIDEO_FORMAT_UYVY); + CHROMA_CONV("VYUY", MAKEFORMAT('U', 'Y', 'V', 'Y'), VIDEO_FORMAT_UYVY); + CHROMA_CONV("NV16", MAKEFORMAT('U', 'Y', 'V', 'Y'), VIDEO_FORMAT_UYVY); + CHROMA_CONV("NV61", MAKEFORMAT('U', 'Y', 'V', 'Y'), VIDEO_FORMAT_UYVY); + CHROMA_CONV("I410", MAKEFORMAT('U', 'Y', 'V', 'Y'), VIDEO_FORMAT_UYVY); + CHROMA_CONV("I422", MAKEFORMAT('U', 'Y', 'V', 'Y'), VIDEO_FORMAT_UYVY); + CHROMA_CONV("Y42B", MAKEFORMAT('U', 'Y', 'V', 'Y'), VIDEO_FORMAT_UYVY); + CHROMA_CONV("J422", MAKEFORMAT('U', 'Y', 'V', 'Y'), VIDEO_FORMAT_UYVY); + + /* 4:4:4 formats */ + CHROMA_TEST("I444", VIDEO_FORMAT_I444); + CHROMA_CONV_FULL("J444", MAKEFORMAT('R', 'G', 'B', 'A'), + VIDEO_FORMAT_RGBA); + CHROMA_CONV("YUVA", MAKEFORMAT('R', 'G', 'B', 'A'), VIDEO_FORMAT_RGBA); + + /* 4:4:0 formats */ + CHROMA_CONV("I440", MAKEFORMAT('I', '4', '4', '4'), VIDEO_FORMAT_I444); + CHROMA_CONV("J440", MAKEFORMAT('I', '4', '4', '4'), VIDEO_FORMAT_I444); + + /* 4:1:0 formats */ + CHROMA_CONV("YVU9", MAKEFORMAT('N', 'V', '1', '2'), VIDEO_FORMAT_UYVY); + CHROMA_CONV("I410", MAKEFORMAT('N', 'V', '1', '2'), VIDEO_FORMAT_UYVY); + + /* 4:1:1 formats */ + CHROMA_CONV("I411", MAKEFORMAT('N', 'V', '1', '2'), VIDEO_FORMAT_UYVY); + CHROMA_CONV("Y41B", MAKEFORMAT('N', 'V', '1', '2'), VIDEO_FORMAT_UYVY); + + /* greyscale formats */ + CHROMA_TEST("GREY", VIDEO_FORMAT_Y800); + CHROMA_TEST("Y800", VIDEO_FORMAT_Y800); + CHROMA_TEST("Y8 ", VIDEO_FORMAT_Y800); +#undef CHROMA_CONV_FULL +#undef CHROMA_CONV +#undef CHROMA_TEST + + *(uint32_t *)chroma = (uint32_t)MAKEFORMAT('B', 'G', 'R', 'A'); + return VIDEO_FORMAT_BGRA; +} + +static inline unsigned get_format_lines(enum video_format format, + unsigned height, size_t plane) +{ + switch (format) { + case VIDEO_FORMAT_I420: + case VIDEO_FORMAT_NV12: + return (plane == 0) ? height : height / 2; + case VIDEO_FORMAT_YVYU: + case VIDEO_FORMAT_YUY2: + case VIDEO_FORMAT_UYVY: + case VIDEO_FORMAT_I444: + case VIDEO_FORMAT_RGBA: + case VIDEO_FORMAT_BGRA: + case VIDEO_FORMAT_BGRX: + case VIDEO_FORMAT_Y800: + return height; + case VIDEO_FORMAT_NONE: + default: + break; + } + + return 0; +} + +static enum audio_format convert_vlc_audio_format(char *format) +{ +#define AUDIO_TEST(val, ret) \ + if (chroma_is(format, val)) \ + return ret +#define AUDIO_CONV(val, new_val, ret) \ + do { \ + if (chroma_is(format, val)) { \ + *(uint32_t *)format = (uint32_t)new_val; \ + return ret; \ + } \ + } while (false) + + AUDIO_TEST("S16N", AUDIO_FORMAT_16BIT); + AUDIO_TEST("S32N", AUDIO_FORMAT_32BIT); + AUDIO_TEST("FL32", AUDIO_FORMAT_FLOAT); + + AUDIO_CONV("U16N", MAKEFORMAT('S', '1', '6', 'N'), AUDIO_FORMAT_16BIT); + AUDIO_CONV("U32N", MAKEFORMAT('S', '3', '2', 'N'), AUDIO_FORMAT_32BIT); + AUDIO_CONV("S24N", MAKEFORMAT('S', '3', '2', 'N'), AUDIO_FORMAT_32BIT); + AUDIO_CONV("U24N", MAKEFORMAT('S', '3', '2', 'N'), AUDIO_FORMAT_32BIT); + AUDIO_CONV("FL64", MAKEFORMAT('F', 'L', '3', '2'), AUDIO_FORMAT_FLOAT); + + AUDIO_CONV("S16I", MAKEFORMAT('S', '1', '6', 'N'), AUDIO_FORMAT_16BIT); + AUDIO_CONV("U16I", MAKEFORMAT('S', '1', '6', 'N'), AUDIO_FORMAT_16BIT); + AUDIO_CONV("S24I", MAKEFORMAT('S', '3', '2', 'N'), AUDIO_FORMAT_32BIT); + AUDIO_CONV("U24I", MAKEFORMAT('S', '3', '2', 'N'), AUDIO_FORMAT_32BIT); + AUDIO_CONV("S32I", MAKEFORMAT('S', '3', '2', 'N'), AUDIO_FORMAT_32BIT); + AUDIO_CONV("U32I", MAKEFORMAT('S', '3', '2', 'N'), AUDIO_FORMAT_32BIT); +#undef AUDIO_CONV +#undef AUDIO_TEST + + *(uint32_t *)format = (uint32_t)MAKEFORMAT('F', 'L', '3', '2'); + return AUDIO_FORMAT_FLOAT; +} + +/* ------------------------------------------------------------------------- */ + +static void vlcs_get_metadata(void *data, calldata_t *cd) +{ + struct vlc_source *vlcs = data; + const char *data_id = calldata_string(cd, "tag_id"); + + if (!vlcs || !data_id) + return; + libvlc_media_t *media = + libvlc_media_player_get_media_(vlcs->media_player); + + if (!media) + return; + +#define VLC_META(media, cd, did, tid, tag) \ + else if (strcmp(did, tid) == 0) \ + { \ + calldata_set_string(cd, "tag_data", \ + libvlc_media_get_meta_(media, tag)); \ + } + + if (strcmp(data_id, "title") == 0) + calldata_set_string(cd, "tag_data", + libvlc_media_get_meta_(media, + libvlc_meta_Title)); + + VLC_META(media, cd, data_id, "artist", libvlc_meta_Artist) + VLC_META(media, cd, data_id, "genre", libvlc_meta_Genre) + VLC_META(media, cd, data_id, "copyright", libvlc_meta_Copyright) + VLC_META(media, cd, data_id, "album", libvlc_meta_Album) + VLC_META(media, cd, data_id, "track_number", libvlc_meta_TrackNumber) + VLC_META(media, cd, data_id, "description", libvlc_meta_Description) + VLC_META(media, cd, data_id, "rating", libvlc_meta_Rating) + VLC_META(media, cd, data_id, "date", libvlc_meta_Date) + VLC_META(media, cd, data_id, "setting", libvlc_meta_Setting) + VLC_META(media, cd, data_id, "url", libvlc_meta_URL) + VLC_META(media, cd, data_id, "language", libvlc_meta_Language) + VLC_META(media, cd, data_id, "now_playing", libvlc_meta_NowPlaying) + VLC_META(media, cd, data_id, "publisher", libvlc_meta_Publisher) + VLC_META(media, cd, data_id, "encoded_by", libvlc_meta_EncodedBy) + VLC_META(media, cd, data_id, "artwork_url", libvlc_meta_ArtworkURL) + VLC_META(media, cd, data_id, "track_id", libvlc_meta_TrackID) + VLC_META(media, cd, data_id, "track_total", libvlc_meta_TrackTotal) + VLC_META(media, cd, data_id, "director", libvlc_meta_Director) + VLC_META(media, cd, data_id, "season", libvlc_meta_Season) + VLC_META(media, cd, data_id, "episode", libvlc_meta_Episode) + VLC_META(media, cd, data_id, "show_name", libvlc_meta_ShowName) + VLC_META(media, cd, data_id, "actors", libvlc_meta_Actors) + VLC_META(media, cd, data_id, "album_artist", libvlc_meta_AlbumArtist) + VLC_META(media, cd, data_id, "disc_number", libvlc_meta_DiscNumber) + VLC_META(media, cd, data_id, "disc_total", libvlc_meta_DiscTotal) + + libvlc_media_release_(media); +#undef VLC_META +} + +/* ------------------------------------------------------------------------- */ + +static const char *vlcs_get_name(void *unused) +{ + UNUSED_PARAMETER(unused); + return obs_module_text("VLCSource"); +} + +static void vlcs_destroy(void *data) +{ + struct vlc_source *c = data; + + if (c->media_list_player) { + libvlc_media_list_player_stop_(c->media_list_player); + libvlc_media_list_player_release_(c->media_list_player); + } + if (c->media_player) { + libvlc_media_player_release_(c->media_player); + } + + bfree((void *)c->audio.data[0]); + obs_source_frame_free(&c->frame); + + free_files(&c->files.da); + pthread_mutex_destroy(&c->mutex); + bfree(c); +} + +static void *vlcs_video_lock(void *data, void **planes) +{ + struct vlc_source *c = data; + for (size_t i = 0; i < MAX_AV_PLANES && c->frame.data[i] != NULL; i++) + planes[i] = c->frame.data[i]; + return NULL; +} + +static void vlcs_video_display(void *data, void *picture) +{ + struct vlc_source *c = data; + c->frame.timestamp = (uint64_t)libvlc_clock_() * 1000ULL - time_start; + obs_source_output_video(c->source, &c->frame); + + UNUSED_PARAMETER(picture); +} + +static unsigned vlcs_video_format(void **p_data, char *chroma, unsigned *width, + unsigned *height, unsigned *pitches, + unsigned *lines) +{ + struct vlc_source *c = *p_data; + enum video_format new_format; + enum video_range_type range; + bool new_range; + unsigned new_width = 0; + unsigned new_height = 0; + size_t i = 0; + + new_format = convert_vlc_video_format(chroma, &new_range); + + /* This is used because VLC will by default try to use a different + * scaling than what the file uses (probably for optimization reasons). + * For example, if the file is 1920x1080, it will try to render it by + * 1920x1088, which isn't what we want. Calling libvlc_video_get_size + * gets the actual video file's size, and thus fixes the problem. + * However this doesn't work with URLs, so if it returns a 0 value, it + * shouldn't be used. */ + libvlc_video_get_size_(c->media_player, 0, &new_width, &new_height); + + if (new_width && new_height) { + *width = new_width; + *height = new_height; + } + + /* don't allocate a new frame if format/width/height hasn't changed */ + if (c->frame.format != new_format || c->frame.width != *width || + c->frame.height != *height) { + obs_source_frame_free(&c->frame); + obs_source_frame_init(&c->frame, new_format, *width, *height); + + c->frame.format = new_format; + c->frame.full_range = new_range; + range = c->frame.full_range ? VIDEO_RANGE_FULL + : VIDEO_RANGE_PARTIAL; + video_format_get_parameters(VIDEO_CS_DEFAULT, range, + c->frame.color_matrix, + c->frame.color_range_min, + c->frame.color_range_max); + } + + while (c->frame.data[i]) { + pitches[i] = (unsigned)c->frame.linesize[i]; + lines[i] = get_format_lines(c->frame.format, *height, i); + i++; + } + + return 1; +} + +static void vlcs_audio_play(void *data, const void *samples, unsigned count, + int64_t pts) +{ + struct vlc_source *c = data; + size_t size = get_audio_size(c->audio.format, c->audio.speakers, count); + + if (c->audio_capacity < count) { + c->audio.data[0] = brealloc((void *)c->audio.data[0], size); + c->audio_capacity = count; + } + + memcpy((void *)c->audio.data[0], samples, size); + c->audio.timestamp = (uint64_t)pts * 1000ULL - time_start; + c->audio.frames = count; + + obs_source_output_audio(c->source, &c->audio); +} + +static int vlcs_audio_setup(void **p_data, char *format, unsigned *rate, + unsigned *channels) +{ + struct vlc_source *c = *p_data; + enum audio_format new_audio_format; + + new_audio_format = convert_vlc_audio_format(format); + if (*channels > 2) + *channels = 2; + + /* don't free audio data if the data is the same format */ + if (c->audio.format == new_audio_format && + c->audio.samples_per_sec == *rate && + c->audio.speakers == (enum speaker_layout) * channels) + return 0; + + c->audio_capacity = 0; + bfree((void *)c->audio.data[0]); + + memset(&c->audio, 0, sizeof(c->audio)); + c->audio.speakers = (enum speaker_layout) * channels; + c->audio.samples_per_sec = *rate; + c->audio.format = new_audio_format; + return 0; +} + +static void add_file(struct vlc_source *c, struct darray *array, + const char *path, int network_caching, int track_index, + int subtitle_index, bool subtitle_enable) +{ + DARRAY(struct media_file_data) new_files; + struct media_file_data data; + struct dstr new_path = {0}; + libvlc_media_t *new_media; + bool is_url = path && strstr(path, "://") != NULL; + + new_files.da = *array; + + dstr_copy(&new_path, path); +#ifdef _WIN32 + if (!is_url) + dstr_replace(&new_path, "/", "\\"); +#endif + path = new_path.array; + + new_media = get_media(&c->files.da, path); + + if (!new_media) + new_media = get_media(&new_files.da, path); + if (!new_media) + new_media = create_media_from_file(path); + + if (new_media) { + if (is_url) { + struct dstr network_caching_option = {0}; + dstr_catf(&network_caching_option, + ":network-caching=%d", network_caching); + libvlc_media_add_option_(new_media, + network_caching_option.array); + dstr_free(&network_caching_option); + } + struct dstr track_option = {0}; + dstr_catf(&track_option, ":audio-track=%d", track_index - 1); + libvlc_media_add_option_(new_media, track_option.array); + dstr_free(&track_option); + + struct dstr sub_option = {0}; + if (subtitle_enable) { + dstr_catf(&sub_option, ":sub-track=%d", + subtitle_index - 1); + } + libvlc_media_add_option_(new_media, sub_option.array); + dstr_free(&sub_option); + + data.path = new_path.array; + data.media = new_media; + da_push_back(new_files, &data); + } else { + dstr_free(&new_path); + } + + *array = new_files.da; +} + +static bool valid_extension(const char *ext) +{ + struct dstr test = {0}; + bool valid = false; + const char *b; + const char *e; + + if (!ext || !*ext) + return false; + + b = EXTENSIONS_MEDIA + 1; + e = strchr(b, ';'); + + for (;;) { + if (e) + dstr_ncopy(&test, b, e - b); + else + dstr_copy(&test, b); + + if (dstr_cmpi(&test, ext) == 0) { + valid = true; + break; + } + + if (!e) + break; + + b = e + 2; + e = strchr(b, ';'); + } + + dstr_free(&test); + return valid; +} + +static void vlcs_update(void *data, obs_data_t *settings) +{ + DARRAY(struct media_file_data) new_files; + DARRAY(struct media_file_data) old_files; + libvlc_media_list_t *media_list; + struct vlc_source *c = data; + obs_data_array_t *array; + const char *behavior; + size_t count; + int network_caching; + int track_index; + int subtitle_index; + bool subtitle_enable; + + da_init(new_files); + da_init(old_files); + + array = obs_data_get_array(settings, S_PLAYLIST); + count = obs_data_array_count(array); + + c->loop = obs_data_get_bool(settings, S_LOOP); + + behavior = obs_data_get_string(settings, S_BEHAVIOR); + + network_caching = (int)obs_data_get_int(settings, S_NETWORK_CACHING); + + track_index = (int)obs_data_get_int(settings, S_TRACK); + + subtitle_index = (int)obs_data_get_int(settings, S_SUBTITLE_TRACK); + + subtitle_enable = obs_data_get_bool(settings, S_SUBTITLE_ENABLE); + + if (astrcmpi(behavior, S_BEHAVIOR_PAUSE_UNPAUSE) == 0) { + c->behavior = BEHAVIOR_PAUSE_UNPAUSE; + } else if (astrcmpi(behavior, S_BEHAVIOR_ALWAYS_PLAY) == 0) { + c->behavior = BEHAVIOR_ALWAYS_PLAY; + } else { /* S_BEHAVIOR_STOP_RESTART */ + c->behavior = BEHAVIOR_STOP_RESTART; + } + + /* ------------------------------------- */ + /* create new list of sources */ + + for (size_t i = 0; i < count; i++) { + obs_data_t *item = obs_data_array_item(array, i); + const char *path = obs_data_get_string(item, "value"); + os_dir_t *dir = os_opendir(path); + + if (dir) { + struct dstr dir_path = {0}; + struct os_dirent *ent; + + for (;;) { + const char *ext; + + ent = os_readdir(dir); + if (!ent) + break; + if (ent->directory) + continue; + + ext = os_get_path_extension(ent->d_name); + if (!valid_extension(ext)) + continue; + + dstr_copy(&dir_path, path); + dstr_cat_ch(&dir_path, '/'); + dstr_cat(&dir_path, ent->d_name); + add_file(c, &new_files.da, dir_path.array, + network_caching, track_index, + subtitle_index, subtitle_enable); + } + + dstr_free(&dir_path); + os_closedir(dir); + } else { + add_file(c, &new_files.da, path, network_caching, + track_index, subtitle_index, subtitle_enable); + } + + obs_data_release(item); + } + + /* ------------------------------------- */ + /* update settings data */ + + libvlc_media_list_player_stop_(c->media_list_player); + + pthread_mutex_lock(&c->mutex); + old_files.da = c->files.da; + c->files.da = new_files.da; + pthread_mutex_unlock(&c->mutex); + + /* ------------------------------------- */ + /* shuffle playlist */ + + c->shuffle = obs_data_get_bool(settings, S_SHUFFLE); + + if (c->files.num > 1 && c->shuffle) { + DARRAY(struct media_file_data) new_files; + DARRAY(size_t) idxs; + + da_init(new_files); + da_init(idxs); + da_resize(idxs, c->files.num); + da_reserve(new_files, c->files.num); + + for (size_t i = 0; i < c->files.num; i++) { + idxs.array[i] = i; + } + for (size_t i = idxs.num; i > 0; i--) { + size_t val = rand() % i; + size_t idx = idxs.array[val]; + da_push_back(new_files, &c->files.array[idx]); + da_erase(idxs, val); + } + + da_free(c->files); + da_free(idxs); + c->files.da = new_files.da; + } + + c->playandpause = obs_data_get_bool(settings, S_PLAYANDPAUSE); + c->playing = false; + + /* ------------------------------------- */ + /* clean up and restart playback */ + + free_files(&old_files.da); + + media_list = libvlc_media_list_new_(libvlc); + + libvlc_media_list_lock_(media_list); + for (size_t i = 0; i < c->files.num; i++) + libvlc_media_list_add_media_(media_list, + c->files.array[i].media); + libvlc_media_list_unlock_(media_list); + + libvlc_media_list_player_set_media_list_(c->media_list_player, + media_list); + libvlc_media_list_release_(media_list); + + libvlc_media_list_player_set_playback_mode_( + c->media_list_player, c->loop ? libvlc_playback_mode_loop + : libvlc_playback_mode_default); + + if (c->files.num && (c->behavior == BEHAVIOR_ALWAYS_PLAY || + obs_source_active(c->source))) + libvlc_media_list_player_play_(c->media_list_player); + else + obs_source_output_video(c->source, NULL); + + obs_data_array_release(array); +} + + + +static void vlcs_started(const struct libvlc_event_t *event, void *data) +{ + struct vlc_source *c = data; + obs_source_media_started(c->source); + if (c->playandpause && c->playing) { + obs_source_media_play_pause(c->source, true); + libvlc_media_list_player_set_pause_(c->media_list_player, 1); + } + else + c->playing = true; + UNUSED_PARAMETER(event); +} + +static void vlcs_stopped(const struct libvlc_event_t *event, void *data) +{ + struct vlc_source *c = data; + if (!c->loop) { + obs_source_output_video(c->source, NULL); + obs_source_media_ended(c->source); + } + UNUSED_PARAMETER(event); +} + +static enum obs_media_state vlcs_get_state(void *data) +{ + struct vlc_source *c = data; + + libvlc_state_t state = libvlc_media_player_get_state_(c->media_player); + + switch (state) { + case libvlc_NothingSpecial: + return OBS_MEDIA_STATE_NONE; + case libvlc_Opening: + return OBS_MEDIA_STATE_OPENING; + case libvlc_Buffering: + return OBS_MEDIA_STATE_BUFFERING; + case libvlc_Playing: + return OBS_MEDIA_STATE_PLAYING; + case libvlc_Paused: + return OBS_MEDIA_STATE_PAUSED; + case libvlc_Stopped: + return OBS_MEDIA_STATE_STOPPED; + case libvlc_Ended: + return OBS_MEDIA_STATE_ENDED; + case libvlc_Error: + return OBS_MEDIA_STATE_ERROR; + } + + return 0; +} + +static void vlcs_play_pause(void *data, bool pause) +{ + struct vlc_source *c = data; + + if (pause) + libvlc_media_list_player_pause_(c->media_list_player); + else + libvlc_media_list_player_play_(c->media_list_player); +} + +static void vlcs_restart(void *data) +{ + struct vlc_source *c = data; + + libvlc_media_list_player_stop_(c->media_list_player); + c->playing = false; + libvlc_media_list_player_play_(c->media_list_player); +} + +static void vlcs_stop(void *data) +{ + struct vlc_source *c = data; + + libvlc_media_list_player_stop_(c->media_list_player); + obs_source_output_video(c->source, NULL); +} + +static void vlcs_playlist_next(void *data) +{ + struct vlc_source *c = data; + c->playing = false; + libvlc_media_list_player_next_(c->media_list_player); +} + +static void vlcs_playlist_prev(void *data) +{ + struct vlc_source *c = data; + c->playing = false; + libvlc_media_list_player_previous_(c->media_list_player); +} + +static int64_t vlcs_get_duration(void *data) +{ + struct vlc_source *c = data; + + return (int64_t)libvlc_media_player_get_length_(c->media_player); +} + +static int64_t vlcs_get_time(void *data) +{ + struct vlc_source *c = data; + + return (int64_t)libvlc_media_player_get_time_(c->media_player); +} + +static void vlcs_set_time(void *data, int64_t ms) +{ + struct vlc_source *c = data; + + libvlc_media_player_set_time_(c->media_player, (libvlc_time_t)ms); +} + +static void vlcs_play_pause_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct vlc_source *c = data; + + enum obs_media_state state = obs_source_media_get_state(c->source); + + if (pressed && obs_source_showing(c->source)) { + if (state == OBS_MEDIA_STATE_PLAYING) + obs_source_media_play_pause(c->source, true); + else if (state == OBS_MEDIA_STATE_PAUSED) + obs_source_media_play_pause(c->source, false); + } +} + +static void vlcs_restart_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct vlc_source *c = data; + + if (pressed && obs_source_showing(c->source)) + obs_source_media_restart(c->source); +} + +static void vlcs_stop_hotkey(void *data, obs_hotkey_id id, obs_hotkey_t *hotkey, + bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct vlc_source *c = data; + + if (pressed && obs_source_showing(c->source)) + obs_source_media_stop(c->source); +} + +static void vlcs_playlist_next_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct vlc_source *c = data; + + if (pressed && obs_source_showing(c->source)) + obs_source_media_next(c->source); +} + +static void vlcs_playlist_prev_hotkey(void *data, obs_hotkey_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + struct vlc_source *c = data; + + if (pressed && obs_source_showing(c->source)) + obs_source_media_previous(c->source); +} + +static void *vlcs_create(obs_data_t *settings, obs_source_t *source) +{ + struct vlc_source *c = bzalloc(sizeof(*c)); + c->source = source; + + c->play_pause_hotkey = obs_hotkey_register_source( + source, "VLCSource.PlayPause", obs_module_text("PlayPause"), + vlcs_play_pause_hotkey, c); + + c->restart_hotkey = obs_hotkey_register_source( + source, "VLCSource.Restart", obs_module_text("Restart"), + vlcs_restart_hotkey, c); + + c->stop_hotkey = obs_hotkey_register_source(source, "VLCSource.Stop", + obs_module_text("Stop"), + vlcs_stop_hotkey, c); + + c->playlist_next_hotkey = obs_hotkey_register_source( + source, "VLCSource.PlaylistNext", + obs_module_text("PlaylistNext"), vlcs_playlist_next_hotkey, c); + + c->playlist_prev_hotkey = obs_hotkey_register_source( + source, "VLCSource.PlaylistPrev", + obs_module_text("PlaylistPrev"), vlcs_playlist_prev_hotkey, c); + + pthread_mutex_init_value(&c->mutex); + if (pthread_mutex_init(&c->mutex, NULL) != 0) + goto error; + + if (!load_libvlc()) + goto error; + + c->media_list_player = libvlc_media_list_player_new_(libvlc); + if (!c->media_list_player) + goto error; + + c->media_player = libvlc_media_player_new_(libvlc); + if (!c->media_player) + goto error; + + libvlc_media_list_player_set_media_player_(c->media_list_player, + c->media_player); + + libvlc_video_set_callbacks_(c->media_player, vlcs_video_lock, NULL, + vlcs_video_display, c); + libvlc_video_set_format_callbacks_(c->media_player, vlcs_video_format, + NULL); + + libvlc_audio_set_callbacks_(c->media_player, vlcs_audio_play, NULL, + NULL, NULL, NULL, c); + libvlc_audio_set_format_callbacks_(c->media_player, vlcs_audio_setup, + NULL); + + libvlc_event_manager_t *event_manager; + event_manager = libvlc_media_player_event_manager_(c->media_player); + libvlc_event_attach_(event_manager, libvlc_MediaPlayerEndReached, + vlcs_stopped, c); + libvlc_event_attach_(event_manager, libvlc_MediaPlayerOpening, + vlcs_started, c); + + proc_handler_t *ph = obs_source_get_proc_handler(source); + proc_handler_add( + ph, "void get_metadata(in string tag_id out string tag_data)", + vlcs_get_metadata, c); + + obs_source_update(source, NULL); + + UNUSED_PARAMETER(settings); + return c; + +error: + vlcs_destroy(c); + return NULL; +} + +static void vlcs_activate(void *data) +{ + struct vlc_source *c = data; + + if (c->behavior == BEHAVIOR_STOP_RESTART) { + libvlc_media_list_player_play_(c->media_list_player); + + } else if (c->behavior == BEHAVIOR_PAUSE_UNPAUSE) { + libvlc_media_list_player_play_(c->media_list_player); + obs_source_media_play_pause(c->source, false); + } +} + +static void vlcs_deactivate(void *data) +{ + struct vlc_source *c = data; + + if (c->behavior == BEHAVIOR_STOP_RESTART) { + libvlc_media_list_player_stop_(c->media_list_player); + obs_source_output_video(c->source, NULL); + c->playing = false; + + } else if (c->behavior == BEHAVIOR_PAUSE_UNPAUSE) { + libvlc_media_list_player_pause_(c->media_list_player); + } +} + +static void vlcs_defaults(obs_data_t *settings) +{ + obs_data_set_default_bool(settings, S_LOOP, true); + obs_data_set_default_bool(settings, S_SHUFFLE, false); + obs_data_set_default_bool(settings, S_PLAYANDPAUSE, false); + obs_data_set_default_string(settings, S_BEHAVIOR, + S_BEHAVIOR_STOP_RESTART); + obs_data_set_default_int(settings, S_NETWORK_CACHING, 400); + obs_data_set_default_int(settings, S_TRACK, 1); + obs_data_set_default_bool(settings, S_SUBTITLE_ENABLE, false); + obs_data_set_default_int(settings, S_SUBTITLE_TRACK, 1); +} + +static obs_properties_t *vlcs_properties(void *data) +{ + obs_properties_t *ppts = obs_properties_create(); + struct vlc_source *c = data; + struct dstr filter = {0}; + struct dstr exts = {0}; + struct dstr path = {0}; + obs_property_t *p; + + obs_properties_set_flags(ppts, OBS_PROPERTIES_DEFER_UPDATE); + obs_properties_add_bool(ppts, S_LOOP, T_LOOP); + obs_properties_add_bool(ppts, S_SHUFFLE, T_SHUFFLE); + obs_properties_add_bool(ppts, S_PLAYANDPAUSE, T_PLAYANDPAUSE); + + if (c) { + pthread_mutex_lock(&c->mutex); + if (c->files.num) { + struct media_file_data *last = da_end(c->files); + const char *slash; + + dstr_copy(&path, last->path); + dstr_replace(&path, "\\", "/"); + slash = strrchr(path.array, '/'); + if (slash) + dstr_resize(&path, slash - path.array + 1); + } + pthread_mutex_unlock(&c->mutex); + } + + p = obs_properties_add_list(ppts, S_BEHAVIOR, T_BEHAVIOR, + OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + obs_property_list_add_string(p, T_BEHAVIOR_STOP_RESTART, + S_BEHAVIOR_STOP_RESTART); + obs_property_list_add_string(p, T_BEHAVIOR_PAUSE_UNPAUSE, + S_BEHAVIOR_PAUSE_UNPAUSE); + obs_property_list_add_string(p, T_BEHAVIOR_ALWAYS_PLAY, + S_BEHAVIOR_ALWAYS_PLAY); + + dstr_cat(&filter, "Media Files ("); + dstr_copy(&exts, EXTENSIONS_MEDIA); + dstr_replace(&exts, ";", " "); + dstr_cat_dstr(&filter, &exts); + + dstr_cat(&filter, ");;Video Files ("); + dstr_copy(&exts, EXTENSIONS_VIDEO); + dstr_replace(&exts, ";", " "); + dstr_cat_dstr(&filter, &exts); + + dstr_cat(&filter, ");;Audio Files ("); + dstr_copy(&exts, EXTENSIONS_AUDIO); + dstr_replace(&exts, ";", " "); + dstr_cat_dstr(&filter, &exts); + + dstr_cat(&filter, ");;Playlist Files ("); + dstr_copy(&exts, EXTENSIONS_PLAYLIST); + dstr_replace(&exts, ";", " "); + dstr_cat_dstr(&filter, &exts); + dstr_cat(&filter, ")"); + + obs_properties_add_editable_list(ppts, S_PLAYLIST, T_PLAYLIST, + OBS_EDITABLE_LIST_TYPE_FILES_AND_URLS, + filter.array, path.array); + dstr_free(&path); + dstr_free(&filter); + dstr_free(&exts); + + obs_properties_add_int(ppts, S_NETWORK_CACHING, T_NETWORK_CACHING, 100, + 60000, 10); + obs_properties_add_int(ppts, S_TRACK, T_TRACK, 1, 10, 1); + obs_properties_add_bool(ppts, S_SUBTITLE_ENABLE, T_SUBTITLE_ENABLE); + obs_properties_add_int(ppts, S_SUBTITLE_TRACK, T_SUBTITLE_TRACK, 1, 10, + 1); + + return ppts; +} + +static void missing_file_callback(void *src, const char *new_path, void *data) +{ + struct vlc_source *s = src; + const char *orig_path = data; + + obs_source_t *source = s->source; + obs_data_t *settings = obs_source_get_settings(source); + obs_data_array_t *files = obs_data_get_array(settings, S_PLAYLIST); + + size_t l = obs_data_array_count(files); + for (size_t i = 0; i < l; i++) { + obs_data_t *file = obs_data_array_item(files, i); + const char *path = obs_data_get_string(file, "value"); + + if (strcmp(path, orig_path) == 0) { + obs_data_set_string(file, "value", new_path); + + obs_data_release(file); + break; + } + + obs_data_release(file); + } + + obs_source_update(source, settings); + + obs_data_array_release(files); + obs_data_release(settings); +} + +/*static obs_missing_files_t *vlcs_missingfiles(void *data) +{ + struct vlc_source *s = data; + obs_missing_files_t *missing_files = obs_missing_files_create(); + + obs_source_t *source = s->source; + obs_data_t *settings = obs_source_get_settings(source); + obs_data_array_t *files = obs_data_get_array(settings, S_PLAYLIST); + + size_t l = obs_data_array_count(files); + for (size_t i = 0; i < l; i++) { + obs_data_t *item = obs_data_array_item(files, i); + const char *path = obs_data_get_string(item, "value"); + + if (strcmp(path, "") != 0) { + if (!os_file_exists(path) && + strstr(path, "://") == NULL) { + obs_missing_file_t *file = + obs_missing_file_create( + path, missing_file_callback, + OBS_MISSING_FILE_SOURCE, source, + (void *)path); + + obs_missing_files_add_file(missing_files, file); + } + } + + obs_data_release(item); + } + + obs_data_array_release(files); + obs_data_release(settings); + + return missing_files; +}*/ + +struct obs_source_info vlc_source_info = { + .id = "vlc_source_extended", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO | + OBS_SOURCE_DO_NOT_DUPLICATE | + OBS_SOURCE_CONTROLLABLE_MEDIA, + .get_name = vlcs_get_name, + .create = vlcs_create, + .destroy = vlcs_destroy, + .update = vlcs_update, + .get_defaults = vlcs_defaults, + .get_properties = vlcs_properties, + .activate = vlcs_activate, + .deactivate = vlcs_deactivate, + //.missing_files = vlcs_missingfiles, + .icon_type = OBS_ICON_TYPE_MEDIA, + .media_play_pause = vlcs_play_pause, + .media_restart = vlcs_restart, + .media_stop = vlcs_stop, + .media_next = vlcs_playlist_next, + .media_previous = vlcs_playlist_prev, + .media_get_duration = vlcs_get_duration, + .media_get_time = vlcs_get_time, + .media_set_time = vlcs_set_time, + .media_get_state = vlcs_get_state, +};