commit 8d5dbef861e93aee597f48e6e8d63393f953aa77 Author: daleclack Date: Mon May 13 23:42:52 2024 +0800 Initial commit diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..bf26358 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "GCR_CMake"] + path = GCR_CMake + url = https://github.com/daleclack/GCR_CMake.git diff --git a/GCR_CMake b/GCR_CMake new file mode 160000 index 0000000..6ea69ec --- /dev/null +++ b/GCR_CMake @@ -0,0 +1 @@ +Subproject commit 6ea69eca6817aead20a2cb87cdd08ab8e9a173cd diff --git a/Gtk4/CMakeLists.txt b/Gtk4/CMakeLists.txt new file mode 100644 index 0000000..b72d797 --- /dev/null +++ b/Gtk4/CMakeLists.txt @@ -0,0 +1,102 @@ +set(CMAKE_CXX_STANDARD 17) +cmake_minimum_required(VERSION 3.5.0) +project(gtk154_mediaplayer3 VERSION 1.0.0) + +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../GCR_CMake/macros) +include(GlibCompileResourcesSupport) + +include(CTest) +enable_testing() + +set(CPACK_PROJECT_NAME ${PROJECT_NAME}) +set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) + +include(CPack) +include_directories(.) +include_directories(..) + +#Find PkgConfig to use gtkmm3 +find_package (PkgConfig REQUIRED) +pkg_check_modules (GTK4 REQUIRED gtk4) +include_directories (${GTK4_INCLUDE_DIRS}) +link_directories (${GTK4_LIBRARY_DIRS}) + +#Find Gettext +# find_package (Gettext REQUIRED) +# set(PO_DIR ${CMAKE_BINARY_DIR}/po/zh_CN/LC_MESSAGES) + +#Source files +set(SOURCE_FILE src/main.cpp src/MyMediaPlayer.cpp src/MyItem.cpp src/LyricsParser.cpp) + +#Compile Resource + +set(RESOURCE_LIST + icons/scalable/status/media-eject.svg + icons/scalable/status/media-mount.svg + icons/scalable/status/media-playback-pause.svg + icons/scalable/status/media-playback-start.svg + icons/scalable/status/media-playback-stop.svg + icons/scalable/status/media-playlist-append.svg + icons/scalable/status/media-playlist-normal.svg + icons/scalable/status/media-playlist-play.svg + icons/scalable/status/media-playlist-repeat-one.svg + icons/scalable/status/media-playlist-repeat.svg + icons/scalable/status/media-playlist-shuffle.svg + icons/scalable/status/media-seek-backward.svg + icons/scalable/status/media-seek-forward.svg + icons/scalable/status/media-skip-backward.svg + icons/scalable/status/media-skip-forward.svg + icons/scalable/status/media-eject-dark.svg + icons/scalable/status/media-mount-dark.svg + icons/scalable/status/media-playback-pause-dark.svg + icons/scalable/status/media-playback-start-dark.svg + icons/scalable/status/media-playback-stop-dark.svg + icons/scalable/status/media-playlist-append-dark.svg + icons/scalable/status/media-playlist-normal-dark.svg + icons/scalable/status/media-playlist-play-dark.svg + icons/scalable/status/media-playlist-repeat-one-dark.svg + icons/scalable/status/media-playlist-repeat-dark.svg + icons/scalable/status/media-playlist-shuffle-dark.svg + icons/scalable/status/media-seek-backward-dark.svg + icons/scalable/status/media-seek-forward-dark.svg + icons/scalable/status/media-skip-backward-dark.svg + icons/scalable/status/media-skip-forward-dark.svg + ) + +compile_gresources(RESOURCE_FILE + XML_OUT + TYPE EMBED_C + RESOURCES ${RESOURCE_LIST} + PREFIX "/org/gtk/daleclack" + SOURCE_DIR ${PROJECT_SOURCE_DIR}/res) + +# Add a custom target to the makefile. Now make builds our resource file. +# It depends on the output RESOURCE_FILE. + +add_custom_target(resource ALL DEPENDS ${RESOURCE_FILE}) + +#For win32 platform,use rc resource and .ico icon +if(WIN32) + SET(CMAKE_RC_COMPILER windres) + set(app_WINRC ../icon.rc) + set_property(SOURCE ../icon.rc APPEND PROPERTY + OBJECT_DEPENDS ${PROJECT_SOURCE_DIR}/../icon.ico + ) + add_executable(${PROJECT_NAME} ${app_WINRC} ${SOURCE_FILE} ${RESOURCE_FILE}) + add_custom_command( TARGET ${PROJECT_NAME} + COMMAND echo * > ${CMAKE_BINARY_DIR}/.gitignore + COMMAND echo **/* > ${CMAKE_BINARY_DIR}/.hgignore) +else() + add_executable(${PROJECT_NAME} ${SOURCE_FILE} ${RESOURCE_FILE}) + add_custom_command( TARGET ${PROJECT_NAME} + COMMAND echo \"*\" > ${CMAKE_BINARY_DIR}/.gitignore + COMMAND echo \"**/*\" > ${CMAKE_BINARY_DIR}/.hgignore) +endif(WIN32) + +#Add command to generate .gitignore and .mo files +# add_custom_command( TARGET ${PROJECT_NAME} +# COMMAND mkdir -p ${PO_DIR} +# COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} ${CMAKE_SOURCE_DIR}/po/zh_CN.po -o ${PO_DIR}/${PROJECT_NAME}.mo) + +SET (CMAKE_EXTRA_CXX_FLAGS ${GTK4_CFLAGS_OTHER}) +target_link_libraries (${PROJECT_NAME} ${GTK4_LIBRARIES} -lpthread) diff --git a/Gtk4/README b/Gtk4/README new file mode 100644 index 0000000..ca496a2 --- /dev/null +++ b/Gtk4/README @@ -0,0 +1,5 @@ +# README of My Reminder +This simple application is a reminder for the date, +and the window show the days left for the deadline set by user + +The reminded date and color of text can be changed by preferences dialog. \ No newline at end of file diff --git a/Gtk4/res/icons/scalable/status/media-eject-dark.svg b/Gtk4/res/icons/scalable/status/media-eject-dark.svg new file mode 100644 index 0000000..d174abd --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-eject-dark.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-eject.svg b/Gtk4/res/icons/scalable/status/media-eject.svg new file mode 100644 index 0000000..73841e7 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-eject.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-mount-dark.svg b/Gtk4/res/icons/scalable/status/media-mount-dark.svg new file mode 100644 index 0000000..b1fbe42 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-mount-dark.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-mount.svg b/Gtk4/res/icons/scalable/status/media-mount.svg new file mode 100644 index 0000000..cad6178 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-mount.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playback-pause-dark.svg b/Gtk4/res/icons/scalable/status/media-playback-pause-dark.svg new file mode 100644 index 0000000..70309bd --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playback-pause-dark.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playback-pause.svg b/Gtk4/res/icons/scalable/status/media-playback-pause.svg new file mode 100644 index 0000000..2a9a7a5 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playback-pause.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playback-start-dark.svg b/Gtk4/res/icons/scalable/status/media-playback-start-dark.svg new file mode 100644 index 0000000..8bcc4ad --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playback-start-dark.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playback-start.svg b/Gtk4/res/icons/scalable/status/media-playback-start.svg new file mode 100644 index 0000000..28b7b44 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playback-start.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playback-stop-dark.svg b/Gtk4/res/icons/scalable/status/media-playback-stop-dark.svg new file mode 100644 index 0000000..6a3528c --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playback-stop-dark.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playback-stop.svg b/Gtk4/res/icons/scalable/status/media-playback-stop.svg new file mode 100644 index 0000000..7ba228d --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playback-stop.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playlist-append-dark.svg b/Gtk4/res/icons/scalable/status/media-playlist-append-dark.svg new file mode 100644 index 0000000..c363da5 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playlist-append-dark.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playlist-append.svg b/Gtk4/res/icons/scalable/status/media-playlist-append.svg new file mode 100644 index 0000000..41152de --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playlist-append.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playlist-normal-dark.svg b/Gtk4/res/icons/scalable/status/media-playlist-normal-dark.svg new file mode 100644 index 0000000..aadbd5d --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playlist-normal-dark.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playlist-normal.svg b/Gtk4/res/icons/scalable/status/media-playlist-normal.svg new file mode 100644 index 0000000..0cbf623 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playlist-normal.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playlist-play-dark.svg b/Gtk4/res/icons/scalable/status/media-playlist-play-dark.svg new file mode 100644 index 0000000..04fc475 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playlist-play-dark.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playlist-play.svg b/Gtk4/res/icons/scalable/status/media-playlist-play.svg new file mode 100644 index 0000000..07db50a --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playlist-play.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playlist-repeat-dark.svg b/Gtk4/res/icons/scalable/status/media-playlist-repeat-dark.svg new file mode 100644 index 0000000..ec8fe6e --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playlist-repeat-dark.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playlist-repeat-one-dark.svg b/Gtk4/res/icons/scalable/status/media-playlist-repeat-one-dark.svg new file mode 100644 index 0000000..bdd668a --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playlist-repeat-one-dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playlist-repeat-one.svg b/Gtk4/res/icons/scalable/status/media-playlist-repeat-one.svg new file mode 100644 index 0000000..6139e63 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playlist-repeat-one.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playlist-repeat.svg b/Gtk4/res/icons/scalable/status/media-playlist-repeat.svg new file mode 100644 index 0000000..d0562ee --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playlist-repeat.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playlist-shuffle-dark.svg b/Gtk4/res/icons/scalable/status/media-playlist-shuffle-dark.svg new file mode 100644 index 0000000..3d7cfec --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playlist-shuffle-dark.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-playlist-shuffle.svg b/Gtk4/res/icons/scalable/status/media-playlist-shuffle.svg new file mode 100644 index 0000000..294d81a --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-playlist-shuffle.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-seek-backward-dark.svg b/Gtk4/res/icons/scalable/status/media-seek-backward-dark.svg new file mode 100644 index 0000000..82624ac --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-seek-backward-dark.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-seek-backward.svg b/Gtk4/res/icons/scalable/status/media-seek-backward.svg new file mode 100644 index 0000000..f5e24f4 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-seek-backward.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-seek-forward-dark.svg b/Gtk4/res/icons/scalable/status/media-seek-forward-dark.svg new file mode 100644 index 0000000..984cae5 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-seek-forward-dark.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-seek-forward.svg b/Gtk4/res/icons/scalable/status/media-seek-forward.svg new file mode 100644 index 0000000..4dc81fd --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-seek-forward.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-skip-backward-dark.svg b/Gtk4/res/icons/scalable/status/media-skip-backward-dark.svg new file mode 100644 index 0000000..683171c --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-skip-backward-dark.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-skip-backward.svg b/Gtk4/res/icons/scalable/status/media-skip-backward.svg new file mode 100644 index 0000000..3f97270 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-skip-backward.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-skip-forward-dark.svg b/Gtk4/res/icons/scalable/status/media-skip-forward-dark.svg new file mode 100644 index 0000000..bc05c67 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-skip-forward-dark.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/Gtk4/res/icons/scalable/status/media-skip-forward.svg b/Gtk4/res/icons/scalable/status/media-skip-forward.svg new file mode 100644 index 0000000..a905ae1 --- /dev/null +++ b/Gtk4/res/icons/scalable/status/media-skip-forward.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Gtk4/src/LyricsParser.cpp b/Gtk4/src/LyricsParser.cpp new file mode 100644 index 0000000..71ea3d8 --- /dev/null +++ b/Gtk4/src/LyricsParser.cpp @@ -0,0 +1,486 @@ +#include "MyItem.h" +#include "LyricsParser.h" +#include "MyMediaPlayer.h" + +#define lyrics_max_length 1024 + +static char *lyrics_content = NULL; +static gboolean lyrics_loaded = FALSE; +static gboolean line_read = FALSE, lyrics_updated = FALSE; +static char current_lyrics[lyrics_max_length]; +static gint64 lyric_time; + +// Replace the symbol +static void UTF8_Replace_and_Symbol(gint64 pos_and_char, char *utf8_string) +{ + size_t string_length = strlen(utf8_string); + + // Copy string after '&' + for (int i = pos_and_char + 1; i < string_length; i++) + { + utf8_string[i + 4] = utf8_string[i]; + } + + // Replace "&" with "&" + utf8_string[pos_and_char + 1] = 'a'; + utf8_string[pos_and_char + 2] = 'm'; + utf8_string[pos_and_char + 3] = 'p'; + utf8_string[pos_and_char + 4] = ';'; + + // Fix end symbol + utf8_string[string_length + 3] = '\0'; +} + +// Fix UTF-8 '&' String +static void UTF8_String_Fix(char *utf8_string) +{ + // Check for '&' String + gint64 pos_and_char = -1; + size_t string_length = strlen(utf8_string); + for (int i = 0; i < string_length; i++) + { + if (utf8_string[i] == '&') + { + UTF8_Replace_and_Symbol(i, utf8_string); + } + } +} + +// Get lyrics string +static void get_lrc_substr(char *src, char *dest, + size_t start, size_t end) +{ + // Copy string to the end + if (strlen(src) == 0 || strlen(src) == start + 1) + { + return; + } + for (int i = 0; i < end; i++) + { + dest[i] = src[i + start + 1]; + } + dest[end] = '\0'; +} + +// Get the position of timestamp end to separate lyrics and timestamp +static size_t get_lrc_timestamp_end_pos(const char *lrc_line) +{ + size_t timestamp_end; + // Find the end of timestamp + for (int i = 0; i < strlen(lrc_line); i++) + { + if (lrc_line[i] == ']') + { + timestamp_end = i; + } + } + return timestamp_end; +} + +// Get timestamp of a lyrics line +static gint64 get_lrc_line_timestamp(const char *lyrics_line, size_t timestamp_length) +{ + static gint64 lyric_time_tmp = 0; + // Minutes + gint64 lyric_min = (lyrics_line[1] - '0') * 10 + + (lyrics_line[2] - '0'); + + // Seconds + gint64 lyric_sec = (lyrics_line[4] - '0') * 10 + + (lyrics_line[5] - '0'); + + // Millseconds + gint64 lyric_micro = 0; + switch (timestamp_length) + { + case 9: + lyric_micro = (lyrics_line[7] - '0') * 100 + + (lyrics_line[8] - '0') * 10; + break; + case 10: + lyric_micro = (lyrics_line[7] - '0') * 100 + + (lyrics_line[8] - '0') * 10 + + (lyrics_line[9] - '0'); + break; + } + + lyric_time_tmp = lyric_micro + lyric_sec * 1000 + + lyric_min * 60 * 1000; + + return lyric_time_tmp; +} + +// Get a line of lyrics +static void get_lyrics_line(const char *lyrics, char *lyrics_line1, gboolean reset) +{ + static gint64 start_pos = 0, end_pos = 0; + // Reset position to load frm start, + // the lyrics and lyrics_line1 should be NULL to avoid problems + if (reset) + { + start_pos = 0; + end_pos = 0; + return; + } + + if (lyrics_loaded && lyrics) + { + // Find the '\n' + while (lyrics[end_pos] != '\n' && lyrics[end_pos] != '\0') + { + end_pos++; + } + + // Copy contents + for (int i = 0; i < end_pos - start_pos; i++) + { + lyrics_line1[i] = lyrics[i + start_pos]; + } + lyrics_line1[end_pos - start_pos] = '\0'; + + // Lyrics read finished + if (lyrics[end_pos] == '\0') + { + end_pos = 0; + // lyrics_loaded = FALSE; + } + end_pos++; + start_pos = end_pos; + } +} + +// Update lyrics +void load_lyrics(MyMediaPlayer *player) +{ + // Get position between filename and extension + int point_pos; + gsize length; + char *current_filename = my_media_player_get_filename(player); + // g_print("%s\n", current_filename); + for (int i = strlen(current_filename) - 1; i > 0; i--) + { + if (current_filename[i] == '.') + { + point_pos = i; + break; + } + } + + // Get Lyrics file name + char lyric_filename[path_max_length]; + strncpy(lyric_filename, current_filename, point_pos); + lyric_filename[point_pos] = '\0'; + strncat(lyric_filename, ".lrc", 4); + // g_print("%s\n", lyric_filename); + + // Load lyrics with gio to avoid coding problem + GFile *lyrics_file = g_file_new_for_path(lyric_filename); + + // Refresh before load + if (lyrics_content != NULL) + { + g_free(lyrics_content); + lyrics_content = NULL; + } + + // Load contents + if (g_file_load_contents(lyrics_file, NULL, &lyrics_content, &length, NULL, NULL)) + { + lyrics_loaded = TRUE; + } + else + { + lyrics_loaded = FALSE; + return; + } + g_object_unref(lyrics_file); + + // Reset load status + lyrics_updated = TRUE; + line_read = FALSE; + + // Reset line read starts + get_lyrics_line(NULL, NULL, TRUE); +} + +// Lyrics Line process +static void lyric_line_process(char *lyrics_line, + size_t ×tamp_length) +{ + // Some lrc files has empty lines + if (strlen(lyrics_line) == 0) + { + line_read = TRUE; + return; + } + + // Not a number + while (lyrics_line[1] < '0' || lyrics_line[1] > '9') + { + line_read = FALSE; + return; + } + + // Get timestamp length + timestamp_length = get_lrc_timestamp_end_pos(lyrics_line); + + // g_print("%s\n", lyrics_line); + lyric_time = get_lrc_line_timestamp(lyrics_line, timestamp_length); + + // Remove time stamp + get_lrc_substr(lyrics_line, current_lyrics, timestamp_length, + strlen(lyrics_line) - timestamp_length); + + // Fix some symbols + UTF8_String_Fix(current_lyrics); + + // A lyric line is read + line_read = TRUE; +} + +// Update label that show lyrics +static void lyrics_label_update(gint64 &curr_time, MyMediaPlayer *player) +{ + char label_string[lyrics_max_length]; + char *color_str; + + // if time is on a lyrics, update the label + if (curr_time >= lyric_time - 100 && curr_time <= lyric_time + 100 && + line_read || + lyric_time == 0) + { + // Since a new line is read and time match, load lyrics + color_str = my_media_player_get_color(player); + snprintf(label_string, lyrics_max_length, + "%s", color_str, current_lyrics); + gtk_label_set_markup(my_media_player_get_lyrics_widget(player), + label_string); + line_read = FALSE; + } + // free(color_str); +} + +// Get lyrics for a specfied time +static void get_lyrics(gint64 curr_time, gboolean playing, MyMediaPlayer *player) +{ + char lyrics_line[lyrics_max_length]; + static size_t timestamp_length; + + // Get lyrics data + if (lyrics_loaded) + { + // g_print("Lrc file load successful\n"); + if (playing && !line_read) + { + // Get lyrics line + get_lyrics_line(lyrics_content, lyrics_line, FALSE); + + g_print("%s\n", lyrics_line); + + // Process lyrics line + lyric_line_process(lyrics_line, timestamp_length); + } + // Try to update label + lyrics_label_update(curr_time, player); + } + // else + // { + // // g_print("Lyric file open failed!\n"); + // gtk_label_set_markup(my_media_player_get_lyrics_widget(player), + // "No Lyric File Found!"); + // } +} + +// Reset lyrics for drag of timeline +static void reset_lyrics(gint64 timestamp, GtkMediaStream *stream, + MyMediaPlayer *player) +{ + static size_t timestamp_length; + lyric_time = -1; + char lyrics_line[lyrics_max_length]; + char priv_lyrics_line[lyrics_max_length]; + + // Reset loading status + get_lyrics_line(NULL, NULL, TRUE); + + // Fine match lyrics line + do + { + // Get Privous lyrics line + strncpy(priv_lyrics_line, current_lyrics, lyrics_max_length); + + // Get lyrics line + get_lyrics_line(lyrics_content, lyrics_line, FALSE); + + // Process lyrics line + lyric_line_process(lyrics_line, timestamp_length); + } while (lyric_time < timestamp); + line_read = TRUE; + + // Show next lyrics + char *label_string = g_strdup_printf("%s", + priv_lyrics_line); + gtk_label_set_markup(my_media_player_get_lyrics_widget(player), + label_string); + g_free(label_string); + + // Try to update label + lyrics_label_update(timestamp, player); + + // Pause audio before seek to avoid isolation + if (timestamp != 0) + { + gtk_media_stream_pause(stream); + gtk_media_stream_seek(stream, timestamp * 1000); + gtk_media_stream_play(stream); + } +} + +// Check whether the media is playing +static gboolean get_media_playing(gint64 curr_time, + GtkMediaStream *stream, MyMediaPlayer *player) +{ + static gint64 tmp_time = -1; + if (curr_time == tmp_time) + { + return FALSE; + } + else + { + gint64 delta_time = abs(curr_time - tmp_time); + if (delta_time > 500) + { + // Reset lyrics load status + reset_lyrics(curr_time, stream, player); + } + tmp_time = curr_time; + return TRUE; + } +} + +// Check whether lyrics should be update +static gboolean get_media_stream_status(MyMediaPlayer *player, + GtkMediaStream *stream, gint64 timestamp) +{ + if (timestamp == 0 && !lyrics_updated && !lyrics_loaded) + { + // Load lyrics when a new media loaded + load_lyrics(player); + } + + if (get_media_playing(timestamp, stream, player)) + { + // Reset status when the media playing + lyrics_updated = FALSE; + lyrics_loaded = TRUE; + return TRUE; + } + else + { + return FALSE; + } + return FALSE; +} + +// Operations when a media play end +static void media_play_ended_handler(MyMediaPlayer *player) +{ + GtkMediaStream *stream; + + // Get Current Play mode + PlayMode play_mode = my_media_player_get_play_mode(player); + + switch (play_mode) + { + // Play a list of music once + // g_print("%d", play_mode); + case PlayMode::List_Once: + // Only play music when current audio is not the end of the audio list + // g_print("%d %d\n", my_media_player_get_current_index(player), + // my_media_player_get_n_audios(player)); + if (my_media_player_get_current_index(player) < + my_media_player_get_n_audios(player) - 1) + { + // use the function for play next button to load next audio + btnnext_clicked(NULL, player); + + // Get media stream to control + stream = gtk_video_get_media_stream(GTK_VIDEO( + my_media_player_get_video_widget(player))); + + // Play media stream associated with media file + gtk_media_stream_play(stream); + } + break; + case PlayMode::List_Repeat: + // In List Repeat Mode, use the function for play next button + btnnext_clicked(NULL, player); + + // Get Media stream to control + stream = gtk_video_get_media_stream(GTK_VIDEO( + my_media_player_get_video_widget(player))); + + // Play media stream associated with media file + gtk_media_stream_play(stream); + break; + case PlayMode::List_Shuffle: + // Play music with random index + my_media_player_load_random_audio(player); + + // Get Media stream to control + stream = gtk_video_get_media_stream(GTK_VIDEO( + my_media_player_get_video_widget(player))); + + // Play media stream associated with media file + gtk_media_stream_play(stream); + break; + case PlayMode::One_Repeat: + // Reload audio + my_media_player_reload_audio(player); + + // Get media stream to control + stream = gtk_video_get_media_stream(GTK_VIDEO( + my_media_player_get_video_widget(player))); + + // Play media stream associated with media file + gtk_media_stream_play(stream); + + break; + } +} + +// Time monitor +gboolean lyric_time_func(gpointer data) +{ + MyMediaPlayer *player = MYMEDIA_PLAYER(data); + // if music is loaded, try to get timestamp + if (my_media_player_get_music_loaded(player)) + { + // Get media stream + GtkMediaStream *stream; + stream = gtk_video_get_media_stream(GTK_VIDEO( + my_media_player_get_video_widget(player))); + + // only get timestamp when media stream vaild + if (GTK_IS_MEDIA_STREAM(stream)) + { + // get timestamp + gint64 timestamp = gtk_media_stream_get_timestamp(stream); + gint64 timestamp_ms = timestamp / 1000; + // g_print("%ld\n", timestamp_ms); + + // Update lyrics and Check whether media stopped + get_lyrics(timestamp_ms, get_media_stream_status(player, stream, timestamp_ms), + player); + + // The Media ended, reset the status + if (gtk_media_stream_get_ended(stream) && + my_media_player_get_music_loaded(player)) + { + my_media_player_set_music_loaded(player, FALSE); + media_play_ended_handler(player); + } + } + } + return TRUE; +} diff --git a/Gtk4/src/LyricsParser.h b/Gtk4/src/LyricsParser.h new file mode 100644 index 0000000..f7440a2 --- /dev/null +++ b/Gtk4/src/LyricsParser.h @@ -0,0 +1,9 @@ +#pragma once + +#include "MyMediaPlayer.h" + +// Timeout function for music played time +gboolean lyric_time_func(gpointer data); + +// Update lyrics file when a music will play +void load_lyrics(MyMediaPlayer *player); diff --git a/Gtk4/src/MyItem.cpp b/Gtk4/src/MyItem.cpp new file mode 100644 index 0000000..e0ddb5a --- /dev/null +++ b/Gtk4/src/MyItem.cpp @@ -0,0 +1,40 @@ +#include "MyItem.h" +#include + +struct _MyItem +{ + GObject parent_instance; + char disp_name[name_max_length]; + char file_name[path_max_length]; +}; + +G_DEFINE_TYPE(MyItem, my_item, G_TYPE_OBJECT) + +const char *my_item_get_filename(MyItem *item) +{ + // Get true file name + return item->file_name; +} + +const char *my_item_get_dispname(MyItem *item) +{ + // Get Base name + return item->disp_name; +} + +static void my_item_init(MyItem *self) +{ +} + +static void my_item_class_init(MyItemClass *klass) +{ +} + +MyItem *my_item_new(const char *dispname, const char *filename) +{ + // Create a new item + MyItem *item = MY_ITEM(g_object_new(my_item_get_type(), NULL)); + strncpy(item->disp_name, dispname, name_max_length); + strncpy(item->file_name, filename, path_max_length); + return item; +} diff --git a/Gtk4/src/MyItem.h b/Gtk4/src/MyItem.h new file mode 100644 index 0000000..41e00f6 --- /dev/null +++ b/Gtk4/src/MyItem.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +// File name and path limits +#define name_max_length 256 +#define path_max_length 4096 + +#define MY_ITEM_TYPE (my_item_get_type()) +G_DECLARE_FINAL_TYPE(MyItem, my_item, MY, ITEM, GObject) + +const char *my_item_get_filename(MyItem *item); + +const char *my_item_get_dispname(MyItem *item); + +MyItem *my_item_new(const char *dispname, const char *filename); diff --git a/Gtk4/src/MyMediaPlayer.cpp b/Gtk4/src/MyMediaPlayer.cpp new file mode 100644 index 0000000..5b42b44 --- /dev/null +++ b/Gtk4/src/MyMediaPlayer.cpp @@ -0,0 +1,779 @@ +#include "MyMediaPlayer.h" +#include "LyricsParser.h" +#include "MyItem.h" +#include "../json_nlohmann/json.hpp" +#include +#include +#include + +using json = nlohmann::json; +typedef std::vector string_vector; + +struct _MyMediaPlayer +{ + GtkApplicationWindow parent_instance; + GtkWidget *video, *label_lyrics; + GtkWidget *ctrl_box; + GtkWidget *btn_priv, *btn_play, *btn_next, + *btn_stop, *btn_playmode; + PlayMode current_play_mode; + GtkWidget *main_box, *btn_box; + GtkWidget *btn_add, *btn_remove; + GtkWidget *btn_load, *btn_save; + GtkWidget *list_expander, *list_box; + GtkWidget *btn_color; + GtkColorDialog *dialog_color; + GtkWidget *column_view; + GtkWidget *scrolled_window, *scrolled_lyrics; + GListStore *music_store; + char current_filename[path_max_length]; + guint n_items, current_audio_index; + gboolean music_loaded; + gboolean dark_mode; + GtkSingleSelection *music_selection; + GtkListItemFactory *filename_factory; + GtkColumnViewColumn *filename_column; +}; + +G_DEFINE_TYPE(MyMediaPlayer, my_media_player, GTK_TYPE_APPLICATION_WINDOW) + +// Add media file item to the list when the file open dialog accepted +void file_dialog_response(GObject *dialog, GAsyncResult *res, gpointer data) +{ + GFile *file; + MyMediaPlayer *player = MYMEDIA_PLAYER(data); + + // Get file + file = gtk_file_dialog_open_finish(GTK_FILE_DIALOG(dialog), res, NULL); + if (file != NULL) + { + // Get file name + char *path = g_file_get_path(file); + char *name = g_file_get_basename(file); + g_list_store_append(player->music_store, + my_item_new(name, path)); + g_object_unref(file); + g_free(path); + g_free(name); + + // Update items count of media files + player->n_items = g_list_model_get_n_items( + G_LIST_MODEL(player->music_store)); + // Update index information + player->current_audio_index = gtk_single_selection_get_selected( + player->music_selection); + } +} + +// Add a media file item +static void btnadd_clicked(GtkWidget *widget, MyMediaPlayer *player) +{ + // Create a file dialog window + GtkFileDialog *dialog = gtk_file_dialog_new(); + gtk_file_dialog_set_title(dialog, "Open media file"); + + // Open the file dialog + gtk_file_dialog_open(dialog, GTK_WINDOW(player), NULL, file_dialog_response, player); +} + +// Remove the selected item +static void btnremove_clicked(GtkWidget *widget, MyMediaPlayer *player) +{ + // Get selected position + guint pos = gtk_single_selection_get_selected(player->music_selection); + + // Remove the selected item + g_list_store_remove(player->music_store, pos); + + // Update index information + player->n_items = g_list_model_get_n_items( + G_LIST_MODEL(player->music_store)); + player->current_audio_index = gtk_single_selection_get_selected( + player->music_selection); +} + +// Load playlist +static void load_playlist(std::string filename, MyMediaPlayer *player) +{ + // Load a new json data to the list + std::fstream infile; + + infile.open(filename, std::ios_base::in); + if (infile.is_open()) + { + // Get json data + json data = json::parse(infile); + + // Check whether json data is empty + if (data.empty()) + { + return; + } + try + { + string_vector sound_names = data["name"]; + string_vector sound_paths = data["path"]; + std::string disp_name, file_path; + + for (int i = 0; i < sound_names.size(); i++) + { + // Append items + disp_name = sound_names[i]; + file_path = sound_paths[i]; + g_list_store_append(G_LIST_STORE(player->music_store), + my_item_new(disp_name.c_str(), file_path.c_str())); + } + } + catch (const nlohmann::detail::exception &ex) + { + g_print("%s\n", ex.what()); + } + + // Update count of media files + player->n_items = g_list_model_get_n_items( + G_LIST_MODEL(player->music_store)); + // Update index information + player->current_audio_index = gtk_single_selection_get_selected( + player->music_selection); + } +} + +// Response for the dialog of load playlist +static void load_dialog_response(GObject *dialog, GAsyncResult *res, gpointer data) +{ + GFile *file; + MyMediaPlayer *player = MYMEDIA_PLAYER(data); + + // Get file + file = gtk_file_dialog_open_finish(GTK_FILE_DIALOG(dialog), res, NULL); + if (file != NULL) + { + // Get file name + char *path = g_file_get_path(file); + load_playlist(path, player); + g_object_unref(file); + } +} + +// Handler for load playlist button +static void btnload_clicked(GtkWidget *widget, MyMediaPlayer *player) +{ + // Create a file dialog window + GtkFileDialog *dialog = gtk_file_dialog_new(); + gtk_file_dialog_set_title(dialog, "Open Playlist file"); + + // Create a filter + GtkFileFilter *filter_json = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter_json, "*.json"); + gtk_file_filter_set_name(filter_json, "json file"); + + // Create store for filters + GListStore *filters_store = g_list_store_new(GTK_TYPE_FILE_FILTER); + g_list_store_append(filters_store, filter_json); + gtk_file_dialog_set_filters(dialog, G_LIST_MODEL(filters_store)); + + // Set json filter for default + gtk_file_dialog_set_default_filter(dialog, filter_json); + + // Open the file dialog + gtk_file_dialog_open(dialog, GTK_WINDOW(player), NULL, load_dialog_response, player); +} + +// Save the play list to a path +static void save_playlist(std::string filename, MyMediaPlayer *player) +{ + // Save playlist to json data + string_vector sound_names, sound_paths; + std::string disp_name, file_path; + + // Get n items of the list + guint list_items = g_list_model_get_n_items(G_LIST_MODEL(player->music_store)); + + // Insert all items to the vectors + for (int i = 0; i < list_items; i++) + { + // Get sound name and the path + MyItem *item = MY_ITEM(g_list_model_get_item(G_LIST_MODEL(player->music_store), i)); + disp_name = std::string(my_item_get_dispname(item)); + file_path = std::string(my_item_get_filename(item)); + + // Push data to the vectors + sound_names.push_back(disp_name); + sound_paths.push_back(file_path); + } + + // Save data to json file + std::fstream outfile; + outfile.open(filename, std::ios::out); + if (outfile.is_open()) + { + // Load json data + json data = json::parse(R"( + { + "name":[""], + "path":[""] + } + )"); + data["name"] = sound_names; + data["path"] = sound_paths; + + // Save to file + outfile << data; + } + else + { + g_print("Failed to save file!\n"); + } + outfile.close(); +} + +// Handler for save list dialog +static void btnsave_clicked(GtkWidget *widget, MyMediaPlayer *player) +{ + // Currently just save the playlist to a default name + save_playlist("playlist.json", player); +} + +// Load a audio with specificed index +static void load_audio(MyItem *item, MyMediaPlayer *player) +{ + // Load the audio file + GFile *music_file; + const char *file_name, *disp_name; + + file_name = my_item_get_filename(item); + music_file = g_file_new_for_path(file_name); + disp_name = my_item_get_dispname(item); + if (music_file != NULL) + { + // Add file to video widget for play + gtk_video_set_file(GTK_VIDEO(player->video), music_file); + + // Mark the player is ready and update current file name + player->music_loaded = TRUE; + char *filename1 = player->current_filename; + strncpy(filename1, file_name, strlen(file_name)); + filename1[strlen(file_name)] = '\0'; + g_object_unref(music_file); + + // Set the label for initial status + char *label_str = g_strdup_printf("%s", disp_name); + gtk_label_set_markup(GTK_LABEL(player->label_lyrics), label_str); + g_free(label_str); + + // Force update lyrics file + load_lyrics(player); + + // Enable control button + gtk_widget_set_sensitive(player->btn_play, TRUE); + } +} + +static void column_view_activated(GtkColumnView *self, gint position, MyMediaPlayer *player) +{ + // Clear stream for player + GtkMediaStream *stream = gtk_video_get_media_stream(GTK_VIDEO(player->video)); + if (stream != NULL) + { + gtk_media_file_clear(GTK_MEDIA_FILE(stream)); + // g_object_unref(stream); + } + gtk_video_set_file(GTK_VIDEO(player->video), NULL); + + // Play the selected media + MyItem *item; + + // Get selection and open the music file + item = MY_ITEM(gtk_single_selection_get_selected_item(player->music_selection)); + load_audio(item, player); + + // Update Column index + player->current_audio_index = gtk_single_selection_get_selected(player->music_selection); +} + +static void filename_factory_setup(GtkListItemFactory *factory, + GtkListItem *item) +{ + // Create a label + GtkWidget *label; + label = gtk_label_new(" "); + + // Add the label to the item + gtk_widget_set_halign(label, GTK_ALIGN_START); + gtk_list_item_set_child(item, label); +} + +static void filename_factory_bind(GtkListItemFactory *factory, + GtkListItem *item) +{ + // Get child + GtkWidget *label; + label = gtk_list_item_get_child(item); + + // Get item + MyItem *file_item = MY_ITEM(gtk_list_item_get_item(item)); + gtk_label_set_label(GTK_LABEL(label), + my_item_get_dispname(file_item)); +} + +gboolean my_media_player_get_music_loaded(MyMediaPlayer *self) +{ + // Get whether music is loaded + return self->music_loaded; +} + +void my_media_player_set_music_loaded(MyMediaPlayer *self, gboolean music_loaded) +{ + // Set status of music loaded + self->music_loaded = music_loaded; +} + +GtkWidget *my_media_player_get_video_widget(MyMediaPlayer *self) +{ + // Get video widget + return self->video; +} + +GtkLabel *my_media_player_get_lyrics_widget(MyMediaPlayer *self) +{ + // Get Label for lyrics + return GTK_LABEL(self->label_lyrics); +} + +char *my_media_player_get_filename(MyMediaPlayer *self) +{ + // Get file name + return self->current_filename; +} + +PlayMode my_media_player_get_play_mode(MyMediaPlayer *self) +{ + // Get Current play mode + return self->current_play_mode; +} + +guint my_media_player_get_current_index(MyMediaPlayer *self) +{ + // Get the index of current playing audio + return self->current_audio_index; +} + +guint my_media_player_get_n_audios(MyMediaPlayer *self) +{ + // Get the number of audios in the list + return self->n_items; +} + +static void my_media_player_expander_activate(GtkExpander *self, MyMediaPlayer *player) +{ + if (!gtk_expander_get_expanded(self)) + { + g_print("Try to recover size!\n"); + gtk_widget_set_size_request(player->main_box, 300, 270); + gtk_widget_set_size_request(GTK_WIDGET(player), 300, 270); + gtk_widget_queue_resize(player->list_box); + gtk_widget_queue_resize(GTK_WIDGET(player)); + } +} + +// Create a button with dark icon support +static GtkWidget *player_button_new(const char *icon_name, gboolean dark_mode) +{ + char *icon_name1; + GtkWidget *button; + // Change icon name string for dark mode + if (dark_mode) + { + icon_name1 = g_strdup_printf("%s-dark", icon_name); + } + else + { + icon_name1 = g_strdup_printf("%s", icon_name); + } + + // Set icon name and free memory for icon name + button = gtk_button_new_from_icon_name(icon_name1); + g_free(icon_name1); + return button; +} + +// Set button icon name with dark icon theme support +static void player_button_set_icon_name(GtkButton *button, const char *icon_name, + gboolean dark_mode) +{ + char *icon_name1; + // Change icon name string for dark mode + if (dark_mode) + { + icon_name1 = g_strdup_printf("%s-dark", icon_name); + } + else + { + icon_name1 = g_strdup_printf("%s", icon_name); + } + + // Set icon name and free memory for icon name + gtk_button_set_icon_name(button, icon_name1); + g_free(icon_name1); +} + +// Play button +static void btnplay_clicked(GtkButton *self, MyMediaPlayer *player) +{ + // Get Media stream and play + GtkMediaStream *stream = gtk_video_get_media_stream(GTK_VIDEO(player->video)); + if (GTK_IS_MEDIA_STREAM(stream)) + { + if (gtk_media_stream_get_playing(stream)) + { + // Media is playing, pause it + gtk_media_stream_pause(stream); + player_button_set_icon_name(self, "media-playback-start", player->dark_mode); + } + else + { + // Media is not playing + gtk_media_stream_play(stream); + player_button_set_icon_name(self, "media-playback-pause", player->dark_mode); + } + } +} + +// Play previous music +static void btnpriv_clicked(GtkButton *self, MyMediaPlayer *player) +{ + // Clear stream for player + GtkMediaStream *stream = gtk_video_get_media_stream(GTK_VIDEO(player->video)); + if (stream != NULL) + { + gtk_media_file_clear(GTK_MEDIA_FILE(stream)); + // g_object_unref(stream); + } + + // Clear video widget + gtk_video_set_file(GTK_VIDEO(player->video), NULL); + + // Current index + if (player->current_audio_index == 0) + { + player->current_audio_index = player->n_items - 1; + } + else + { + player->current_audio_index -= 1; + } + + // Load music at index + // Get item + MyItem *item = MY_ITEM(g_list_model_get_item(G_LIST_MODEL(player->music_store), + player->current_audio_index)); + load_audio(item, player); + + // Update selected item + gtk_single_selection_set_selected(player->music_selection, + player->current_audio_index); +} + +// Play next music +void btnnext_clicked(GtkButton *self, MyMediaPlayer *player) +{ + // Clear stream for player + GtkMediaStream *stream = gtk_video_get_media_stream(GTK_VIDEO(player->video)); + if (stream != NULL) + { + gtk_media_file_clear(GTK_MEDIA_FILE(stream)); + // g_object_unref(stream); + } + + // Clear video widget + gtk_video_set_file(GTK_VIDEO(player->video), NULL); + + // Current index + if (player->current_audio_index == (player->n_items - 1)) + { + player->current_audio_index = 0; + } + else + { + player->current_audio_index += 1; + } + + // Load music at index + // Get item + MyItem *item = MY_ITEM(g_list_model_get_item(G_LIST_MODEL(player->music_store), + player->current_audio_index)); + load_audio(item, player); + + // Update selected item + gtk_single_selection_set_selected(player->music_selection, + player->current_audio_index); +} + +// Load music with random index +void my_media_player_load_random_audio(MyMediaPlayer *player) +{ + // Get music index + player->current_audio_index = rand() % (player->n_items); + + // Clear stream for player + GtkMediaStream *stream = gtk_video_get_media_stream(GTK_VIDEO(player->video)); + if (stream != NULL) + { + gtk_media_file_clear(GTK_MEDIA_FILE(stream)); + // g_object_unref(stream); + } + + // Clear video widget + gtk_video_set_file(GTK_VIDEO(player->video), NULL); + + // Load music at index + // Get item + MyItem *item = MY_ITEM(g_list_model_get_item(G_LIST_MODEL(player->music_store), + player->current_audio_index)); + load_audio(item, player); + + // Update selected item + gtk_single_selection_set_selected(player->music_selection, + player->current_audio_index); +} + +// Reload audio for repeat mode +void my_media_player_reload_audio(MyMediaPlayer *player) +{ + // Clear stream for player + GtkMediaStream *stream = gtk_video_get_media_stream(GTK_VIDEO(player->video)); + if (stream != NULL) + { + gtk_media_file_clear(GTK_MEDIA_FILE(stream)); + // g_object_unref(stream); + } + + // Clear video widget + gtk_video_set_file(GTK_VIDEO(player->video), NULL); + + // Load music at index + // Get item + MyItem *item = MY_ITEM(g_list_model_get_item(G_LIST_MODEL(player->music_store), + player->current_audio_index)); + load_audio(item, player); + + // Update selected item + gtk_single_selection_set_selected(player->music_selection, + player->current_audio_index); +} + +// Stop current music +static void btnstop_clicked(GtkButton *self, MyMediaPlayer *player) +{ + // Get Media stream and stop + GtkMediaStream *stream = gtk_video_get_media_stream(GTK_VIDEO(player->video)); + gtk_media_file_clear(GTK_MEDIA_FILE(stream)); + gtk_video_set_file(GTK_VIDEO(player->video), NULL); + player_button_set_icon_name(GTK_BUTTON(player->btn_play), "media-playback-start", + player->dark_mode); + gtk_widget_set_sensitive(player->btn_play, FALSE); +} + +// Switch play mode +static void btn_playmode_clicked(GtkButton *self, MyMediaPlayer *player) +{ + // Change play mode + switch (player->current_play_mode) + { + case PlayMode::List_Once: + player->current_play_mode = PlayMode::List_Repeat; + player_button_set_icon_name(self, "media-playlist-repeat", player->dark_mode); + break; + case PlayMode::List_Repeat: + player->current_play_mode = PlayMode::List_Shuffle; + player_button_set_icon_name(self, "media-playlist-shuffle", player->dark_mode); + break; + case PlayMode::List_Shuffle: + player->current_play_mode = PlayMode::One_Repeat; + player_button_set_icon_name(self, "media-playlist-repeat-one", player->dark_mode); + break; + case PlayMode::One_Repeat: + player->current_play_mode = PlayMode::List_Once; + player_button_set_icon_name(self, "media-playlist-normal", player->dark_mode); + break; + } +} + +static gboolean my_media_player_close_request(GtkWindow *window) +{ + // Save current list to a playlist file + save_playlist("playlist.json", MYMEDIA_PLAYER(window)); + gtk_window_destroy(window); + return TRUE; +} + +// Get whether use dark icon theme, to match icons with stack icons +static gboolean my_media_player_check_dark_theme(MyMediaPlayer *player) +{ + gboolean dark_mode = FALSE; + int theme_name_index = 0, theme_name_length; + char temp_string[5] = {0}; + // Get current theme + GtkIconTheme *theme = gtk_icon_theme_get_for_display( + gtk_widget_get_display(GTK_WIDGET(player))); + char *theme_name = gtk_icon_theme_get_theme_name(theme); + theme_name_length = strlen(theme_name); + + // Translate string to lower + for (int i = 0; i < theme_name_length; i++) + { + theme_name[i] = tolower(theme_name[i]); + } + + // Check "dark" string + for (int i = 0; i < 4; i++) + { + temp_string[i] = theme_name[theme_name_length - 4 + i]; + } + + if (strncmp(temp_string, "dark", 4) == 0) + { + dark_mode = TRUE; + } + free(theme_name); + + return dark_mode; +} + +char *my_media_player_get_color(MyMediaPlayer *player) +{ + const GdkRGBA *color_rgba; + color_rgba = gtk_color_dialog_button_get_rgba( + GTK_COLOR_DIALOG_BUTTON(player->btn_color)); + char *color_str = g_strdup_printf("#%02X%02X%02X", + (int)(color_rgba->red * 256), + (int)(color_rgba->green * 256), + (int)(color_rgba->blue * 256)); + return color_str; +} + +static void my_media_player_init(MyMediaPlayer *self) +{ + // Initalize window + gtk_window_set_icon_name(GTK_WINDOW(self), "org.gtk.daleclack"); + gtk_window_set_title(GTK_WINDOW(self), "Gtk4 Media Player 3"); + gtk_window_set_default_size(GTK_WINDOW(self), 300, 270); + gtk_window_set_resizable(GTK_WINDOW(self), TRUE); + + // Check whether use dark icon name + self->dark_mode = my_media_player_check_dark_theme(self); + + // Create widgets + self->main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + self->video = gtk_video_new(); + self->label_lyrics = gtk_label_new(" "); + self->ctrl_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + self->btn_play = player_button_new("media-playback-start", self->dark_mode); + self->btn_priv = player_button_new("media-skip-backward", self->dark_mode); + self->btn_next = player_button_new("media-skip-forward", self->dark_mode); + self->btn_stop = player_button_new("media-playback-stop", self->dark_mode); + self->btn_playmode = player_button_new("media-playlist-repeat", self->dark_mode); + self->dialog_color = gtk_color_dialog_new(); + self->btn_color = gtk_color_dialog_button_new(self->dialog_color); + self->btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + self->btn_add = gtk_button_new_from_icon_name("list-add"); + self->scrolled_window = gtk_scrolled_window_new(); + self->scrolled_lyrics = gtk_scrolled_window_new(); + self->list_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + self->list_expander = gtk_expander_new("Play List"); + self->btn_remove = gtk_button_new_from_icon_name("list-remove"); + self->btn_load = gtk_button_new_from_icon_name("go-up"); + self->btn_save = gtk_button_new_from_icon_name("document-save"); + + // Initalize widgets + gtk_widget_set_size_request(self->video, 300, 150); + gtk_widget_set_halign(self->ctrl_box, GTK_ALIGN_CENTER); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self->scrolled_window), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + // gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self->scrolled_window), + // GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER); + gtk_label_set_markup(GTK_LABEL(self->label_lyrics), + "No media file playing!"); + gtk_video_set_autoplay(GTK_VIDEO(self->video), FALSE); + gtk_widget_set_hexpand(self->main_box, TRUE); + gtk_widget_set_vexpand(self->main_box, TRUE); + + // Link signals for buttons + g_signal_connect(self->btn_add, "clicked", G_CALLBACK(btnadd_clicked), self); + g_signal_connect(self->btn_remove, "clicked", G_CALLBACK(btnremove_clicked), self); + g_signal_connect(self->btn_load, "clicked", G_CALLBACK(btnload_clicked), self); + g_signal_connect(self->btn_save, "clicked", G_CALLBACK(btnsave_clicked), self); + g_signal_connect(self->list_expander, "activate", + G_CALLBACK(my_media_player_expander_activate), self); + g_signal_connect(self->btn_play, "clicked", G_CALLBACK(btnplay_clicked), self); + g_signal_connect(self->btn_priv, "clicked", G_CALLBACK(btnpriv_clicked), self); + g_signal_connect(self->btn_next, "clicked", G_CALLBACK(btnnext_clicked), self); + g_signal_connect(self->btn_stop, "clicked", G_CALLBACK(btnstop_clicked), self); + g_signal_connect(self->btn_playmode, "clicked", + G_CALLBACK(btn_playmode_clicked), self); + + // Create store and list view + self->music_store = g_list_store_new(MY_ITEM_TYPE); + self->music_selection = gtk_single_selection_new(G_LIST_MODEL(self->music_store)); + self->column_view = gtk_column_view_new(GTK_SELECTION_MODEL(self->music_selection)); + gtk_widget_set_vexpand(self->column_view, TRUE); + + // Create factory for renderer + self->filename_factory = gtk_signal_list_item_factory_new(); + self->filename_column = gtk_column_view_column_new("File Name", + self->filename_factory); + g_signal_connect(self->filename_factory, "setup", + G_CALLBACK(filename_factory_setup), NULL); + g_signal_connect(self->filename_factory, "bind", + G_CALLBACK(filename_factory_bind), NULL); + g_signal_connect(self->column_view, "activate", G_CALLBACK(column_view_activated), self); + gtk_column_view_append_column(GTK_COLUMN_VIEW(self->column_view), + self->filename_column); + gtk_widget_set_size_request(self->column_view, 300, 250); + + // Add a timer for music playing + self->music_loaded = FALSE; + g_timeout_add(1, lyric_time_func, self); + + // Load a default playlist + load_playlist("playlist.json", self); + + // Default Play mode is List_Repeat mode + self->current_play_mode = PlayMode::List_Repeat; + + // Add widgets + gtk_box_append(GTK_BOX(self->main_box), self->video); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_lyrics), + self->label_lyrics); + gtk_box_append(GTK_BOX(self->main_box), self->scrolled_lyrics); + gtk_box_append(GTK_BOX(self->ctrl_box), self->btn_priv); + gtk_box_append(GTK_BOX(self->ctrl_box), self->btn_play); + gtk_box_append(GTK_BOX(self->ctrl_box), self->btn_next); + gtk_box_append(GTK_BOX(self->ctrl_box), self->btn_stop); + gtk_box_append(GTK_BOX(self->ctrl_box), self->btn_playmode); + gtk_box_append(GTK_BOX(self->ctrl_box), self->btn_color); + gtk_box_append(GTK_BOX(self->main_box), self->ctrl_box); + gtk_box_append(GTK_BOX(self->btn_box), self->btn_add); + gtk_box_append(GTK_BOX(self->btn_box), self->btn_remove); + gtk_box_append(GTK_BOX(self->btn_box), self->btn_load); + gtk_box_append(GTK_BOX(self->btn_box), self->btn_save); + gtk_box_append(GTK_BOX(self->list_box), self->btn_box); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), + self->column_view); + gtk_box_append(GTK_BOX(self->list_box), self->scrolled_window); + gtk_expander_set_child(GTK_EXPANDER(self->list_expander), self->list_box); + gtk_box_append(GTK_BOX(self->main_box), self->list_expander); + gtk_window_set_child(GTK_WINDOW(self), self->main_box); +} + +static void my_media_player_class_init(MyMediaPlayerClass *klass) +{ + GTK_WINDOW_CLASS(klass)->close_request = my_media_player_close_request; +} + +MyMediaPlayer *my_media_player_new(GtkApplication *app) +{ + // Create a window for media player + return MYMEDIA_PLAYER(g_object_new(my_media_player_get_type(), + "application", app, NULL)); +} diff --git a/Gtk4/src/MyMediaPlayer.h b/Gtk4/src/MyMediaPlayer.h new file mode 100644 index 0000000..e589a28 --- /dev/null +++ b/Gtk4/src/MyMediaPlayer.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +enum PlayMode{ + List_Once, // Play the media with a playlist once + List_Repeat, // Play the media with a playlist repeatly + List_Shuffle, // Random play a media in the playlist + One_Repeat // Repeat a media file + +}; + +G_DECLARE_FINAL_TYPE(MyMediaPlayer, my_media_player, MYMEDIA, PLAYER, GtkApplicationWindow) + +gboolean my_media_player_get_music_loaded(MyMediaPlayer *self); + +void my_media_player_set_music_loaded(MyMediaPlayer *self, gboolean music_loaded); + +GtkWidget *my_media_player_get_video_widget(MyMediaPlayer *self); + +GtkLabel *my_media_player_get_lyrics_widget(MyMediaPlayer *self); + +PlayMode my_media_player_get_play_mode(MyMediaPlayer *self); + +char *my_media_player_get_filename(MyMediaPlayer *self); + +guint my_media_player_get_current_index(MyMediaPlayer *self); + +guint my_media_player_get_n_audios(MyMediaPlayer *self); + +void btnnext_clicked(GtkButton *self, MyMediaPlayer *player); + +void my_media_player_load_random_audio(MyMediaPlayer *player); + +void my_media_player_reload_audio(MyMediaPlayer *player); + +char *my_media_player_get_color(MyMediaPlayer *player); + +MyMediaPlayer *my_media_player_new(GtkApplication *app); diff --git a/Gtk4/src/main.cpp b/Gtk4/src/main.cpp new file mode 100644 index 0000000..5dabab3 --- /dev/null +++ b/Gtk4/src/main.cpp @@ -0,0 +1,17 @@ +#include "MyMediaPlayer.h" + +static void gtkmain(GtkApplication *app, gpointer user_data) +{ + // Create a media player window and run + MyMediaPlayer *media_player = my_media_player_new(app); + gtk_window_present(GTK_WINDOW(media_player)); +} + +int main(int argc, char **argv) +{ + // Create a application and run + GtkApplication *app; + app = gtk_application_new("org.gtk.daleclack", G_APPLICATION_NON_UNIQUE); + g_signal_connect(app, "activate", G_CALLBACK(gtkmain), NULL); + return g_application_run(G_APPLICATION(app), argc, argv); +}