Add source for media player
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 8 4 L 3 10 L 13 10 L 8 4 z M 3 11 L 3 12 L 13 12 L 13 11 L 3 11 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 404 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 8 4 L 3 10 L 13 10 L 8 4 z M 3 11 L 3 12 L 13 12 L 13 11 L 3 11 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 404 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 3 4 L 8 10 L 13 4 L 3 4 z M 3 11 L 3 12 L 13 12 L 13 11 L 3 11 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 405 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 3 4 L 8 10 L 13 4 L 3 4 z M 3 11 L 3 12 L 13 12 L 13 11 L 3 11 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 405 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m 3 3 0 10 4 0 0 -10 z m 6 0 0 10 4 0 0 -10 z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 382 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m 3 3 0 10 4 0 0 -10 z m 6 0 0 10 4 0 0 -10 z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 382 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m 3 3 0 10 10 -5 z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 355 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m 3 3 0 10 10 -5 z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 355 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m 3 3 0 10 10 0 0 -10 z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 360 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m 3 3 0 10 10 0 0 -10 z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 360 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 3 2 A 1 1 0 0 0 2 3 A 1 1 0 0 0 3 4 A 1 1 0 0 0 4 3 A 1 1 0 0 0 3 2 z M 5 2 L 5 3 L 7 3 L 13 3 L 13 2 L 7 2 L 5 2 z M 5 4 L 5 5 L 7 5 L 11 5 L 11 4 L 7 4 L 5 4 z M 10 7 L 10 10 L 7 10 L 7 11 L 10 11 L 10 14 L 11 14 L 11 11 L 14 11 L 14 10 L 11 10 L 11 7 L 10 7 z M 3 8 A 1 1 0 0 0 2 9 A 1 1 0 0 0 3 10 A 1 1 0 0 0 4 9 A 1 1 0 0 0 3 8 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 674 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 3 2 A 1 1 0 0 0 2 3 A 1 1 0 0 0 3 4 A 1 1 0 0 0 4 3 A 1 1 0 0 0 3 2 z M 5 2 L 5 3 L 7 3 L 13 3 L 13 2 L 7 2 L 5 2 z M 5 4 L 5 5 L 7 5 L 11 5 L 11 4 L 7 4 L 5 4 z M 10 7 L 10 10 L 7 10 L 7 11 L 10 11 L 10 14 L 11 14 L 11 11 L 14 11 L 14 10 L 11 10 L 11 7 L 10 7 z M 3 8 A 1 1 0 0 0 2 9 A 1 1 0 0 0 3 10 A 1 1 0 0 0 4 9 A 1 1 0 0 0 3 8 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 674 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 16 5 L 16 7 L 3 7 L 3 8 L 16 8 L 16 10 L 19 7.5 L 16 5 z M 16 12 L 16 14 L 3 14 L 3 15 L 16 15 L 16 17 L 19 14.5 L 16 12 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 465 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 16 5 L 16 7 L 3 7 L 3 8 L 16 8 L 16 10 L 19 7.5 L 16 5 z M 16 12 L 16 14 L 3 14 L 3 15 L 16 15 L 16 17 L 19 14.5 L 16 12 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 465 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 11 3 C 6.568002 3 3 6.568002 3 11 C 3 15.431998 6.568002 19 11 19 C 11.692084 19 12.360349 18.903948 13 18.740234 L 13 17.707031 C 12.366247 17.894565 11.696167 18 11 18 C 7.122001 18 4 14.877999 4 11 C 4 7.122001 7.122001 4 11 4 C 14.877999 4 18 7.122001 18 11 C 18 11.696167 17.894565 12.366247 17.707031 13 L 18.740234 13 C 18.903948 12.360349 19 11.692084 19 11 C 19 6.568002 15.431998 3 11 3 z M 10 5 L 10 12 L 11 12 L 16 12 L 16 11 L 11 11 L 11 5 L 10 5 z M 14 13 L 14 19 L 20 16 L 14 13 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 838 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 8 2 C 4.6862915 2 2 4.6862915 2 8 C 2 11.313708 4.6862915 14 8 14 L 8 13 C 5.2385763 13 3 10.761424 3 8 C 3 5.2385763 5.2385763 3 8 3 C 10.419003 3 12.43717 4.7176961 12.900391 7 L 13.910156 7 C 13.433066 4.1634955 10.972138 2 8 2 z M 7 4 L 7 8 L 7 9 L 8 9 L 9 9 L 9 8 L 8 8 L 8 7 L 8 4 L 7 4 z M 10 8 L 10 14 L 15 11 L 10 8 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 665 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 11 3.5 L 11 5 L 5 5 C 3.338 5 2 6.338 2 8 L 3 8 C 3 6.892 3.892 6 5 6 L 11 6 L 11 7.5 L 14 5.5 L 11 3.5 z M 13 8 C 13 9.108 12.108 10 11 10 L 5 10 L 5 8.5 L 2 10.5 L 5 12.5 L 5 11 L 11 11 C 12.662 11 14 9.662 14 8 L 13 8 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 561 B |
|
@ -0,0 +1,7 @@
|
|||
<svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#dedede">
|
||||
<path d="m3.994 2c-1.258 0.015-2.179-0.03-2.931 0.385a1.88 1.88 0 0 0-0.838 0.998c-0.165 0.437-0.225 0.96-0.225 1.617v6c0 0.658 0.06 1.179 0.225 1.617 0.164 0.439 0.461 0.79 0.838 0.998 0.752 0.416 1.673 0.37 2.931 0.385h8.012c1.258-0.015 2.179 0.03 2.932-0.385a1.88 1.88 0 0 0 0.838-0.998c0.164-0.438 0.224-0.96 0.224-1.617v-6c0-0.658-0.06-1.179-0.225-1.617a1.88 1.88 0 0 0-0.838-0.998c-0.752-0.416-1.673-0.37-2.931-0.385h-1.006v1h1c1.259 0.015 2.087 0.06 2.453 0.262 0.184 0.1 0.29 0.212 0.387 0.472s0.16 0.674 0.16 1.266v6c0 0.592-0.063 1.006-0.16 1.266-0.098 0.26-0.203 0.371-0.387 0.472-0.366 0.202-1.194 0.247-2.453 0.262h-8c-1.259-0.015-2.09-0.06-2.455-0.262-0.183-0.1-0.287-0.212-0.385-0.472-0.097-0.26-0.16-0.674-0.16-1.266v-6c0-0.592 0.063-1.006 0.16-1.266 0.098-0.26 0.202-0.371 0.385-0.472 0.365-0.202 1.195-0.247 2.455-0.262h2.5v-1h-2.502z" color="#000000" font-family="sans-serif" font-weight="400" overflow="visible" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none"/>
|
||||
<path d="m5 0 1e-3 5c0.76-0.348 1.535-0.737 2.323-1.166a44.09 44.09 0 0 0 2.259-1.334 51.555 51.555 0 0 0-2.26-1.32 43.088 43.088 0 0 0-2.323-1.18z" color="#000000" overflow="visible"/>
|
||||
<path d="m6.117 6.247c0.167-0.07 0.34-0.147 0.52-0.234 0.184-0.092 0.363-0.19 0.536-0.294a8.2 8.2 0 0 0 0.494-0.338c0.161-0.121 0.306-0.248 0.433-0.381h0.9v6h-1v-4.652c-0.448 0.347-1.012 0.566-1.494 0.762z" font-family="Ubuntu" font-weight="700" letter-spacing="0" text-anchor="middle" word-spacing="0"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,7 @@
|
|||
<svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#363636">
|
||||
<path d="m3.994 2c-1.258 0.015-2.179-0.03-2.931 0.385a1.88 1.88 0 0 0-0.838 0.998c-0.165 0.437-0.225 0.96-0.225 1.617v6c0 0.658 0.06 1.179 0.225 1.617 0.164 0.439 0.461 0.79 0.838 0.998 0.752 0.416 1.673 0.37 2.931 0.385h8.012c1.258-0.015 2.179 0.03 2.932-0.385a1.88 1.88 0 0 0 0.838-0.998c0.164-0.438 0.224-0.96 0.224-1.617v-6c0-0.658-0.06-1.179-0.225-1.617a1.88 1.88 0 0 0-0.838-0.998c-0.752-0.416-1.673-0.37-2.931-0.385h-1.006v1h1c1.259 0.015 2.087 0.06 2.453 0.262 0.184 0.1 0.29 0.212 0.387 0.472s0.16 0.674 0.16 1.266v6c0 0.592-0.063 1.006-0.16 1.266-0.098 0.26-0.203 0.371-0.387 0.472-0.366 0.202-1.194 0.247-2.453 0.262h-8c-1.259-0.015-2.09-0.06-2.455-0.262-0.183-0.1-0.287-0.212-0.385-0.472-0.097-0.26-0.16-0.674-0.16-1.266v-6c0-0.592 0.063-1.006 0.16-1.266 0.098-0.26 0.202-0.371 0.385-0.472 0.365-0.202 1.195-0.247 2.455-0.262h2.5v-1h-2.502z" color="#000000" font-family="sans-serif" font-weight="400" overflow="visible" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;isolation:auto;mix-blend-mode:normal;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none"/>
|
||||
<path d="m5 0 1e-3 5c0.76-0.348 1.535-0.737 2.323-1.166a44.09 44.09 0 0 0 2.259-1.334 51.555 51.555 0 0 0-2.26-1.32 43.088 43.088 0 0 0-2.323-1.18z" color="#000000" overflow="visible"/>
|
||||
<path d="m6.117 6.247c0.167-0.07 0.34-0.147 0.52-0.234 0.184-0.092 0.363-0.19 0.536-0.294a8.2 8.2 0 0 0 0.494-0.338c0.161-0.121 0.306-0.248 0.433-0.381h0.9v6h-1v-4.652c-0.448 0.347-1.012 0.566-1.494 0.762z" font-family="Ubuntu" font-weight="700" letter-spacing="0" text-anchor="middle" word-spacing="0"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 11 3.5 L 11 5 L 5 5 C 3.338 5 2 6.338 2 8 L 3 8 C 3 6.892 3.892 6 5 6 L 11 6 L 11 7.5 L 14 5.5 L 11 3.5 z M 13 8 C 13 9.108 12.108 10 11 10 L 5 10 L 5 8.5 L 2 10.5 L 5 12.5 L 5 11 L 11 11 C 12.662 11 14 9.662 14 8 L 13 8 z "
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 561 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m11 3.5v1.5h-2c-1.662 0-3 1.338-3 3 0 1.108-.892 2-2 2h-2v1h2c1.662 0 3-1.338 3-3 0-1.108.892-2 2-2h2v1.5l3-2zm-9 1.5v1h2c.401 0 .772.119 1.084.32.077-.333.21-.642.387-.922-.436-.246-.933-.398-1.471-.398zm9 3.5v1.5h-2c-.401 0-.772-.119-1.084-.32-.077.333-.21.642-.387.922.436.246.933.398 1.471.398h2v1.5l3-2z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 644 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m11 3.5v1.5h-2c-1.662 0-3 1.338-3 3 0 1.108-.892 2-2 2h-2v1h2c1.662 0 3-1.338 3-3 0-1.108.892-2 2-2h2v1.5l3-2zm-9 1.5v1h2c.401 0 .772.119 1.084.32.077-.333.21-.642.387-.922-.436-.246-.933-.398-1.471-.398zm9 3.5v1.5h-2c-.401 0-.772-.119-1.084-.32-.077.333-.21.642-.387.922.436.246.933.398 1.471.398h2v1.5l3-2z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 644 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 11 4 4 11 11 18 11,11 Z M 11 11 l 7 7 0 -14 z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 383 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="M 8 3 3 8 8 13 8 8 Z M 8 8 l 5 5 0 -10 z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 377 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m 4 4 0 14 L 11 11 Z M 11 11 11 18 18 11 11 4 Z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 383 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m 3 3 0 10 L 8 8 Z M 8 8 l 0 5 L 13 8 8 3 Z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 380 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m 4 4 0 14 5 0 0 -7 0 -7 z M 9 11 l 9 7 0 -14 z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 383 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m 3 3 0 10 3 0 0 -5 0 -5 z M 6 8 l 7 5 0 -10 z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 383 B |
|
@ -0,0 +1,14 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dedede;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path
|
||||
style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m 4 4 0 14 L 13 11 Z M 13 11 l 0 7 5 0 0 -14 -5 0 z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 387 B |
|
@ -0,0 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#363636;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none"
|
||||
d="m 3 3 0 10 L 10 8 Z M 10 8 l 0 5 3 0 0 -10 -3 0 z"
|
||||
class="ColorScheme-Text"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 386 B |
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
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);
|
|
@ -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
|
|
@ -1,6 +1,6 @@
|
|||
#include "DrawApp.h"
|
||||
#include "MyFinder.h"
|
||||
#define NAME_MAX 256
|
||||
#include "MyLimit.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
"<span color=\"green\" size='12pt'>%s</span>", 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),
|
||||
// "<span color=\"green\" size='12pt'>No Lyric File Found!</span>");
|
||||
// }
|
||||
}
|
||||
|
||||
// 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("<span color=\"green\" size='12pt'>%s</span>",
|
||||
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;
|
||||
}
|
|
@ -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);
|
|
@ -0,0 +1,40 @@
|
|||
#include "MediaItem.h"
|
||||
#include <cstring>
|
||||
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
// 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);
|
|
@ -0,0 +1,762 @@
|
|||
#include "MyMediaPlayer.h"
|
||||
#include "LyricsParser.h"
|
||||
#include "MediaItem.h"
|
||||
#include "../json_nlohmann/json.hpp"
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
|
||||
using json = nlohmann::json;
|
||||
typedef std::vector<std::string> 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("<span color=\"green\" size='12pt'>%s</span>", 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),
|
||||
"<span color=\"red\" size='12pt'>No media file playing!</span>");
|
||||
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));
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
#include "ScoresItem.h"
|
||||
#define NAME_MAX 256
|
||||
#include "MyLimit.h"
|
||||
|
||||
struct _ScoresItem
|
||||
{
|
||||
|
|