diff --git a/Gtk4_Reset/CMakeLists.txt b/Gtk4_Reset/CMakeLists.txt
index c8c6fd9..4245017 100644
--- a/Gtk4_Reset/CMakeLists.txt
+++ b/Gtk4_Reset/CMakeLists.txt
@@ -31,7 +31,8 @@ set(SOURCES src/core/main.cpp src/core/MainWin.cpp src/core/MyStack.cpp
src/game24_app/Game24.cpp src/game24_app/Game24App.cpp src/text_app/TextEditor.cpp
src/text_app/MyInfoBar.cpp src/image_app/ImageApp.cpp src/image_app/MyImage.cpp
src/mine_app/MineSweeper.cpp src/mine_app/MineCell.cpp src/mine_app/InputBox.cpp
- src/mine_app/ScoresItem.cpp src/mine_app/ScoresWin.cpp)
+ src/mine_app/ScoresItem.cpp src/mine_app/ScoresWin.cpp src/media_app/LyricsParser.cpp
+ src/media_app/MyMediaPlayer.cpp src/media_app/MediaItem.cpp)
#Compile resources with GCR_CMake
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-eject-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-eject-dark.svg
new file mode 100644
index 0000000..d174abd
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-eject-dark.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-eject.svg b/Gtk4_Reset/res/icons/scalable/status/media-eject.svg
new file mode 100644
index 0000000..73841e7
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-eject.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-mount-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-mount-dark.svg
new file mode 100644
index 0000000..b1fbe42
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-mount-dark.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-mount.svg b/Gtk4_Reset/res/icons/scalable/status/media-mount.svg
new file mode 100644
index 0000000..cad6178
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-mount.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playback-pause-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-playback-pause-dark.svg
new file mode 100644
index 0000000..70309bd
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playback-pause-dark.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playback-pause.svg b/Gtk4_Reset/res/icons/scalable/status/media-playback-pause.svg
new file mode 100644
index 0000000..2a9a7a5
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playback-pause.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playback-start-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-playback-start-dark.svg
new file mode 100644
index 0000000..8bcc4ad
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playback-start-dark.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playback-start.svg b/Gtk4_Reset/res/icons/scalable/status/media-playback-start.svg
new file mode 100644
index 0000000..28b7b44
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playback-start.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playback-stop-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-playback-stop-dark.svg
new file mode 100644
index 0000000..6a3528c
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playback-stop-dark.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playback-stop.svg b/Gtk4_Reset/res/icons/scalable/status/media-playback-stop.svg
new file mode 100644
index 0000000..7ba228d
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playback-stop.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playlist-append-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-playlist-append-dark.svg
new file mode 100644
index 0000000..c363da5
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playlist-append-dark.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playlist-append.svg b/Gtk4_Reset/res/icons/scalable/status/media-playlist-append.svg
new file mode 100644
index 0000000..41152de
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playlist-append.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playlist-normal-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-playlist-normal-dark.svg
new file mode 100644
index 0000000..aadbd5d
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playlist-normal-dark.svg
@@ -0,0 +1,14 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playlist-normal.svg b/Gtk4_Reset/res/icons/scalable/status/media-playlist-normal.svg
new file mode 100644
index 0000000..0cbf623
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playlist-normal.svg
@@ -0,0 +1,14 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playlist-play-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-playlist-play-dark.svg
new file mode 100644
index 0000000..04fc475
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playlist-play-dark.svg
@@ -0,0 +1,14 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playlist-play.svg b/Gtk4_Reset/res/icons/scalable/status/media-playlist-play.svg
new file mode 100644
index 0000000..07db50a
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playlist-play.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playlist-repeat-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-playlist-repeat-dark.svg
new file mode 100644
index 0000000..ec8fe6e
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playlist-repeat-dark.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playlist-repeat-one-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-playlist-repeat-one-dark.svg
new file mode 100644
index 0000000..bdd668a
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playlist-repeat-one-dark.svg
@@ -0,0 +1,7 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playlist-repeat-one.svg b/Gtk4_Reset/res/icons/scalable/status/media-playlist-repeat-one.svg
new file mode 100644
index 0000000..6139e63
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playlist-repeat-one.svg
@@ -0,0 +1,7 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playlist-repeat.svg b/Gtk4_Reset/res/icons/scalable/status/media-playlist-repeat.svg
new file mode 100644
index 0000000..d0562ee
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playlist-repeat.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playlist-shuffle-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-playlist-shuffle-dark.svg
new file mode 100644
index 0000000..3d7cfec
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playlist-shuffle-dark.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-playlist-shuffle.svg b/Gtk4_Reset/res/icons/scalable/status/media-playlist-shuffle.svg
new file mode 100644
index 0000000..294d81a
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-playlist-shuffle.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-seek-backward-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-seek-backward-dark.svg
new file mode 100644
index 0000000..82624ac
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-seek-backward-dark.svg
@@ -0,0 +1,14 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-seek-backward.svg b/Gtk4_Reset/res/icons/scalable/status/media-seek-backward.svg
new file mode 100644
index 0000000..f5e24f4
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-seek-backward.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-seek-forward-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-seek-forward-dark.svg
new file mode 100644
index 0000000..984cae5
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-seek-forward-dark.svg
@@ -0,0 +1,14 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-seek-forward.svg b/Gtk4_Reset/res/icons/scalable/status/media-seek-forward.svg
new file mode 100644
index 0000000..4dc81fd
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-seek-forward.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-skip-backward-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-skip-backward-dark.svg
new file mode 100644
index 0000000..683171c
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-skip-backward-dark.svg
@@ -0,0 +1,14 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-skip-backward.svg b/Gtk4_Reset/res/icons/scalable/status/media-skip-backward.svg
new file mode 100644
index 0000000..3f97270
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-skip-backward.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-skip-forward-dark.svg b/Gtk4_Reset/res/icons/scalable/status/media-skip-forward-dark.svg
new file mode 100644
index 0000000..bc05c67
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-skip-forward-dark.svg
@@ -0,0 +1,14 @@
+
diff --git a/Gtk4_Reset/res/icons/scalable/status/media-skip-forward.svg b/Gtk4_Reset/res/icons/scalable/status/media-skip-forward.svg
new file mode 100644
index 0000000..a905ae1
--- /dev/null
+++ b/Gtk4_Reset/res/icons/scalable/status/media-skip-forward.svg
@@ -0,0 +1,13 @@
+
diff --git a/Gtk4_Reset/src/apps/MyMediaPlayer.h b/Gtk4_Reset/src/apps/MyMediaPlayer.h
new file mode 100644
index 0000000..eb97f61
--- /dev/null
+++ b/Gtk4_Reset/src/apps/MyMediaPlayer.h
@@ -0,0 +1,37 @@
+#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);
+
+MyMediaPlayer *my_media_player_new(GtkApplication *app);
diff --git a/Gtk4_Reset/src/core/MyLimit.h b/Gtk4_Reset/src/core/MyLimit.h
new file mode 100644
index 0000000..da8ff9e
--- /dev/null
+++ b/Gtk4_Reset/src/core/MyLimit.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#ifdef _WIN32
+
+// Definition fix for microsoft windows
+// On linux, the limits.h is automatically included
+#define NAME_MAX 256
+
+#endif
\ No newline at end of file
diff --git a/Gtk4_Reset/src/draw_app/DrawApp.cpp b/Gtk4_Reset/src/draw_app/DrawApp.cpp
index 942644d..4f53023 100644
--- a/Gtk4_Reset/src/draw_app/DrawApp.cpp
+++ b/Gtk4_Reset/src/draw_app/DrawApp.cpp
@@ -1,6 +1,6 @@
#include "DrawApp.h"
#include "MyFinder.h"
-#define NAME_MAX 256
+#include "MyLimit.h"
typedef enum
{
diff --git a/Gtk4_Reset/src/media_app/LyricsParser.cpp b/Gtk4_Reset/src/media_app/LyricsParser.cpp
new file mode 100644
index 0000000..479e657
--- /dev/null
+++ b/Gtk4_Reset/src/media_app/LyricsParser.cpp
@@ -0,0 +1,483 @@
+#include "MediaItem.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)
+ {
+ // 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 update_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];
+
+ // 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
+ snprintf(label_string, lyrics_max_length,
+ "%s", current_lyrics);
+ gtk_label_set_markup(my_media_player_get_lyrics_widget(player),
+ label_string);
+ line_read = FALSE;
+ }
+}
+
+// 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
+ update_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_Reset/src/media_app/LyricsParser.h b/Gtk4_Reset/src/media_app/LyricsParser.h
new file mode 100644
index 0000000..a117f41
--- /dev/null
+++ b/Gtk4_Reset/src/media_app/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 update_lyrics(MyMediaPlayer *player);
diff --git a/Gtk4_Reset/src/media_app/MediaItem.cpp b/Gtk4_Reset/src/media_app/MediaItem.cpp
new file mode 100644
index 0000000..a9e3e1f
--- /dev/null
+++ b/Gtk4_Reset/src/media_app/MediaItem.cpp
@@ -0,0 +1,40 @@
+#include "MediaItem.h"
+#include
+
+struct _MediaItem
+{
+ GObject parent_instance;
+ char disp_name[name_max_length];
+ char file_name[path_max_length];
+};
+
+G_DEFINE_TYPE(MediaItem, media_item, G_TYPE_OBJECT)
+
+const char *media_item_get_filename(MediaItem *item)
+{
+ // Get true file name
+ return item->file_name;
+}
+
+const char *media_item_get_dispname(MediaItem *item)
+{
+ // Get Base name
+ return item->disp_name;
+}
+
+static void media_item_init(MediaItem *self)
+{
+}
+
+static void media_item_class_init(MediaItemClass *klass)
+{
+}
+
+MediaItem *media_item_new(const char *dispname, const char *filename)
+{
+ // Create a new item
+ MediaItem *item = Media_Item(g_object_new(media_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_Reset/src/media_app/MediaItem.h b/Gtk4_Reset/src/media_app/MediaItem.h
new file mode 100644
index 0000000..0f33bc0
--- /dev/null
+++ b/Gtk4_Reset/src/media_app/MediaItem.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include
+
+// File name and path limits
+#define name_max_length 256
+#define path_max_length 4096
+
+#define MEDIA_ITEM_TYPE (media_item_get_type())
+G_DECLARE_FINAL_TYPE(MediaItem, media_item, Media, Item, GObject)
+
+const char *media_item_get_filename(MediaItem *item);
+
+const char *media_item_get_dispname(MediaItem *item);
+
+MediaItem *media_item_new(const char *dispname, const char *filename);
diff --git a/Gtk4_Reset/src/media_app/MyMediaPlayer.cpp b/Gtk4_Reset/src/media_app/MyMediaPlayer.cpp
new file mode 100644
index 0000000..ac76ad6
--- /dev/null
+++ b/Gtk4_Reset/src/media_app/MyMediaPlayer.cpp
@@ -0,0 +1,762 @@
+#include "MyMediaPlayer.h"
+#include "LyricsParser.h"
+#include "MediaItem.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 *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,
+ media_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),
+ media_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
+ MediaItem *item = Media_Item(g_list_model_get_item(G_LIST_MODEL(player->music_store), i));
+ disp_name = std::string(media_item_get_dispname(item));
+ file_path = std::string(media_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(MediaItem *item, MyMediaPlayer *player)
+{
+ // Load the audio file
+ GFile *music_file;
+ const char *file_name, *disp_name;
+
+ file_name = media_item_get_filename(item);
+ music_file = g_file_new_for_path(file_name);
+ disp_name = media_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
+ update_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
+ MediaItem *item;
+
+ // Get selection and open the music file
+ item = Media_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
+ MediaItem *file_item = Media_Item(gtk_list_item_get_item(item));
+ gtk_label_set_label(GTK_LABEL(label),
+ media_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
+ MediaItem *item = Media_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
+ MediaItem *item = Media_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
+ MediaItem *item = Media_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
+ MediaItem *item = Media_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;
+}
+
+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->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(MEDIA_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->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_Reset/src/mine_app/ScoresItem.cpp b/Gtk4_Reset/src/mine_app/ScoresItem.cpp
index 6bbc085..9cb6da1 100644
--- a/Gtk4_Reset/src/mine_app/ScoresItem.cpp
+++ b/Gtk4_Reset/src/mine_app/ScoresItem.cpp
@@ -1,5 +1,5 @@
#include "ScoresItem.h"
-#define NAME_MAX 256
+#include "MyLimit.h"
struct _ScoresItem
{