mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-30 16:02:00 +02:00
Compare commits
211 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
37d2f7aebd | ||
|
f2ac3b20a3 | ||
|
ade60b93a4 | ||
|
48bc96f074 | ||
|
0d9cb91705 | ||
|
21ccab1cc2 | ||
|
bea3b1670d | ||
|
0d4d3f1a8c | ||
|
fc525ffcb1 | ||
|
6f1a5626bc | ||
|
fbe6a8804c | ||
|
c0b92c190b | ||
|
8fc64eaf73 | ||
|
0b7f31b7c2 | ||
|
2d4353aaec | ||
|
7062af472b | ||
|
1f4698448a | ||
|
95c8316af6 | ||
|
06d9f302c2 | ||
|
6246777f06 | ||
|
1f781716d7 | ||
|
0d7ef22c88 | ||
|
57664f32da | ||
|
f8a7abe195 | ||
|
5f259b28fe | ||
|
9f776e9ecb | ||
|
edb799bafa | ||
|
7535c4e598 | ||
|
f0475ee055 | ||
|
68d7205ead | ||
|
f08ffad178 | ||
|
c2de0b7b33 | ||
|
ac7be5956c | ||
|
76a1efab58 | ||
|
a2bbd2708d | ||
|
9e77660cac | ||
|
5ea22450c0 | ||
|
6ae38327ca | ||
|
9006e56534 | ||
|
af0e082c43 | ||
|
2f5d6d60ed | ||
|
9d537c7318 | ||
|
970b1487ac | ||
|
fc51fd857c | ||
|
b419ed7922 | ||
|
4f69183b85 | ||
|
98df55b231 | ||
|
6a4f5f2560 | ||
|
efe9439633 | ||
|
4179176ce8 | ||
|
a7288adf4c | ||
|
9bb3f999b1 | ||
|
0e6507682a | ||
|
2d8471dea0 | ||
|
68f49c1495 | ||
|
a8bb6c40f4 | ||
|
71db78a0f6 | ||
|
4133557086 | ||
|
d926e6b389 | ||
|
fd2de8b9b6 | ||
|
de65eee106 | ||
|
9b07ff7dc5 | ||
|
047dd12fbb | ||
|
3238270c0d | ||
|
997e47b93c | ||
|
ec1d4619a7 | ||
|
f4e48c9f8c | ||
|
1da6b94efc | ||
|
e4335721be | ||
|
45d2702e01 | ||
|
a8aca7b3c0 | ||
|
c6e8824e3b | ||
|
e92ad68220 | ||
|
a98ca53dfb | ||
|
32995fc6a6 | ||
|
6b5240ddbc | ||
|
46ef6bcd1d | ||
|
bd13a3c15a | ||
|
edfa85b5cc | ||
|
084f78a851 | ||
|
c9b2f25192 | ||
|
6f39b3939a | ||
|
ee78ffb1e4 | ||
|
bfbbc517d5 | ||
|
7559a61c9f | ||
|
deb273179f | ||
|
231af36ef6 | ||
|
2e892c923b | ||
|
0ab0b66825 | ||
|
d901eb4712 | ||
|
fe03719b38 | ||
|
f0ea7ae798 | ||
|
380236b8ba | ||
|
e721130a63 | ||
|
eaf090d2e2 | ||
|
87115f43d7 | ||
|
33a5ec18fa | ||
|
ab8cafa0b8 | ||
|
62b6de6db2 | ||
|
643c2029d0 | ||
|
9799783ee5 | ||
|
457cbde25e | ||
|
2fd94fdc70 | ||
|
3a998fb91e | ||
|
b02f54a3a6 | ||
|
ca7b44092e | ||
|
adbcfecb5e | ||
|
a717e481e8 | ||
|
4766efbbc4 | ||
|
28c1daf709 | ||
|
aa49d25df5 | ||
|
774687710f | ||
|
901fc8d760 | ||
|
ab32b2dbbc | ||
|
24bb9f298b | ||
|
2efa3e0bf6 | ||
|
92e3f7d93c | ||
|
85804ea297 | ||
|
7cf86e92eb | ||
|
b5711b145b | ||
|
9271392397 | ||
|
175de5bd6d | ||
|
7b97f29aaf | ||
|
8d7fb761f7 | ||
|
aec2166c11 | ||
|
c21b214477 | ||
|
d9939a94c2 | ||
|
dafa2cfdf5 | ||
|
ebe72f20b5 | ||
|
fa1455556b | ||
|
8fb6b971fe | ||
|
1bf46a2f12 | ||
|
a39c67e5e7 | ||
|
a3f78432f8 | ||
|
7a7a04554f | ||
|
93de3dc056 | ||
|
eda80f314e | ||
|
b3e6890571 | ||
|
c767b3e4b2 | ||
|
ec6157763b | ||
|
cca3077936 | ||
|
b5e1b3ab86 | ||
|
b15b94fc90 | ||
|
28d8986072 | ||
|
30a7229b33 | ||
|
9502e062f4 | ||
|
6a9c77dfad | ||
|
133cda1b41 | ||
|
e68a7fe31a | ||
|
7f69bee11c | ||
|
295af9fd24 | ||
|
d04297620b | ||
|
43acfddb06 | ||
|
d54781eda7 | ||
|
96e5c5aa7c | ||
|
66ce006f00 | ||
|
3fd30e41bf | ||
|
a6316c940c | ||
|
84d9cc7416 | ||
|
a3499e9b47 | ||
|
4a60e01131 | ||
|
b404eb2f56 | ||
|
1f18796e0d | ||
|
254aa538a5 | ||
|
58cc45ec7d | ||
|
7a75c6d4ff | ||
|
2e97fc362c | ||
|
d762a59cc4 | ||
|
b42843be1f | ||
|
6dc825dfb3 | ||
|
e89b3599c9 | ||
|
79e12a6e36 | ||
|
36d4a5c848 | ||
|
38e5bae199 | ||
|
fcff4b4450 | ||
|
4021745a56 | ||
|
bd20d305ba | ||
|
d9b35b7fb8 | ||
|
f1e00434ba | ||
|
918157be04 | ||
|
72b55939b4 | ||
|
e0a3ef78db | ||
|
4f46a7eaa8 | ||
|
050ef440dc | ||
|
a4d55f8114 | ||
|
aa60c56a58 | ||
|
8c307dc90f | ||
|
5b6141ee8c | ||
|
8f294604dc | ||
|
06f8e5d259 | ||
|
6370e1126b | ||
|
270e59137d | ||
|
ec18ca989a | ||
|
46d24536c0 | ||
|
c89d488c30 | ||
|
01c26cbbc3 | ||
|
4375077dbc | ||
|
fceb8ff70a | ||
|
6dc37088cf | ||
|
4e85f6b749 | ||
|
0cd82b1b8a | ||
|
39da52dd62 | ||
|
9c12afbf80 | ||
|
e3c9b112e2 | ||
|
13d675beff | ||
|
95c3845398 | ||
|
93549a67af | ||
|
07fb0a9a46 | ||
|
fe3fd32932 | ||
|
637212f7e8 | ||
|
8b2e63ac48 |
6
.gitattributes
vendored
6
.gitattributes
vendored
@@ -1 +1,5 @@
|
||||
lib/* linguist-vendored
|
||||
extras/**/* linguist-vendored
|
||||
lib/**/* linguist-vendored
|
||||
lib/**/**/* linguist-vendored
|
||||
lib/gst/clapper/gstclapper-mpris* linguist-vendored=false
|
||||
lib/gst/clapper/gtk4/* linguist-vendored=false
|
||||
|
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "pkgs/flatpak/shared-modules"]
|
||||
path = pkgs/flatpak/shared-modules
|
||||
url = https://github.com/flathub/shared-modules.git
|
||||
[submodule "pkgs/flatpak/flathub"]
|
||||
path = pkgs/flatpak/flathub
|
||||
url = https://github.com/flathub/com.github.rafostar.Clapper.git
|
||||
|
18
README.md
18
README.md
@@ -28,18 +28,21 @@ The media player is using [GStreamer](https://gstreamer.freedesktop.org/) as a m
|
||||
The `Flatpak` package includes all required dependencies and codecs.
|
||||
Additionally it also has a few patches, thus some functionalities work better (or are only available) in `Flatpak` version (until my changes are accepted upstream). List of patches used in this version can be found [here](https://github.com/Rafostar/clapper/issues/35).
|
||||
|
||||
```sh
|
||||
flatpak install https://rafostar.github.io/flatpak/com.github.rafostar.Clapper.flatpakref
|
||||
```
|
||||
<a href='https://flathub.org/apps/details/com.github.rafostar.Clapper'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a>
|
||||
|
||||
**Important:** If you have been using the flatpak package from my custom 3rd party repo, please remove it and replace your installation with version from Flathub. That repository will not be maintained any longer. Thank you for understanding.
|
||||
|
||||
## Packages
|
||||
The [pkgs folder](https://github.com/Rafostar/clapper/tree/master/pkgs) in this repository contains build scripts for various package formats. You can use them to build package yourself or download one of pre-built packages:
|
||||
|
||||
#### Debian, Fedora & openSUSE
|
||||
Pre-built packages are available in [my repo](https://software.opensuse.org//download.html?project=home%3ARafostar&package=clapper) ([see status](https://build.opensuse.org/package/show/home:Rafostar/clapper))
|
||||
Pre-built packages are available in [my repo](https://software.opensuse.org//download.html?project=home%3ARafostar&package=clapper) ([see status](https://build.opensuse.org/package/show/home:Rafostar/clapper)).<br>
|
||||
Those are automatically build on each git commit, thus are considered unstable.
|
||||
|
||||
#### Arch Linux
|
||||
You can get Clapper from the AUR: [clapper-git](https://aur.archlinux.org/packages/clapper-git)
|
||||
You can get Clapper from the AUR:
|
||||
* [clapper](https://aur.archlinux.org/packages/clapper) (stable version)
|
||||
* [clapper-git](https://aur.archlinux.org/packages/clapper-git)
|
||||
|
||||
## Installation from source code
|
||||
```sh
|
||||
@@ -57,11 +60,10 @@ All these libs are acting "on their own" and no function calls from `GJS` relate
|
||||
**Q:** What settings should I set to maximize performance?<br>
|
||||
**A:** As of now, player works best on `Wayland` session. `Wayland` users can try enabling highly experimental `vah264dec` plugin for improved performance (this plugin does not work on `Xorg` right now) for standard (8-bit) `H.264` videos.
|
||||
It can be enabled from inside player preferences dialog inside `Advanced -> GStreamer` tab using customizable `Plugin Ranking` feature.
|
||||
Since the whole app is rendered using your GPU, users of VERY weak GPUs might try to disable the "render window shadows" option to have more GPU power available for non-fullscreen video rendering.
|
||||
Since the whole app is rendered using your GPU, users of VERY weak GPUs might want to disable the "render window shadows" option to have more GPU power available for non-fullscreen video rendering.
|
||||
|
||||
## Other Questions?
|
||||
Feel free to ask me any questions.<br>
|
||||
Use either GitHub [discussions](https://github.com/Rafostar/clapper/discussions) or come and talk on Matrix: **#clapper-player:matrix.org**
|
||||
Feel free to ask me any questions. Come and talk on Matrix: [#clapper-player:matrix.org](https://matrix.to/#/#clapper-player:matrix.org)
|
||||
|
||||
## Special Thanks
|
||||
Many thanks to [sp1ritCS](https://github.com/sp1ritCS) for creating and maintaining package build files.
|
||||
|
12
TODO.md
12
TODO.md
@@ -15,11 +15,11 @@
|
||||
- [X] Picture-in-Picture mode window (floating window)
|
||||
- [ ] Touch gestures/swipes support
|
||||
- Media playlists:
|
||||
- [ ] Add more items to playlist via GUI
|
||||
- [X] Add more items to playlist via D&D
|
||||
- [X] Select video from playlist
|
||||
- [ ] Reorder playlist items via D&D
|
||||
- [X] Load special playlist file (.claps)
|
||||
- [ ] Save to playlist file from GUI
|
||||
- [X] Save to playlist file from GUI
|
||||
- Seeking:
|
||||
- [X] Customizable seek time
|
||||
- [X] Set seek mode (default, accurate, fast)
|
||||
@@ -31,7 +31,7 @@
|
||||
- [X] Audio offset
|
||||
- [ ] MDNS and UPNP (discovering media in local network)
|
||||
- [X] DND files from Nautilus to play (ignore incompatible ones)
|
||||
- [ ] Support dropping whole folders
|
||||
- [X] Support dropping whole folders
|
||||
- [ ] Search for subtitles, download and activate (SMplayer)
|
||||
- [ ] Auto add subtitles from same folder
|
||||
- [ ] Set global subtitles folders
|
||||
@@ -40,7 +40,7 @@
|
||||
- [X] Remote playback controls via HTTP (VLC) + WebSockets
|
||||
- [ ] Expand available API
|
||||
- [ ] API documentation
|
||||
- [ ] Integration with the top bar
|
||||
- [ ] MPRIS support
|
||||
- [ ] Controls in the notifications panel
|
||||
- [X] Integration with the top bar
|
||||
- [X] MPRIS support
|
||||
- [X] Controls in the notifications panel
|
||||
- [ ] Progress bar in the notifications panel (maybe via extension)
|
||||
|
@@ -9,7 +9,7 @@ radio {
|
||||
.osd list {
|
||||
background: none;
|
||||
}
|
||||
.osd list row {
|
||||
.osd list row image {
|
||||
-gtk-icon-shadow: none;
|
||||
}
|
||||
.gtk402 trough highlight {
|
||||
@@ -25,7 +25,7 @@ radio {
|
||||
border: transparent;
|
||||
}
|
||||
.linkseparator {
|
||||
background: alpha(@borders, 0.75);
|
||||
background: rgba(24,24,24,0.72);
|
||||
min-width: 1px;
|
||||
}
|
||||
.linkedleft image {
|
||||
@@ -61,23 +61,43 @@ radio {
|
||||
.roundedcorners {
|
||||
border-radius: 8px;
|
||||
}
|
||||
.adwthemedark scale trough highlight {
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
.videowidget {
|
||||
min-width: 320px;
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.tvmode popover box {
|
||||
.fullscreen.tvmode popover box {
|
||||
text-shadow: none;
|
||||
font-size: 21px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.tvmode button {
|
||||
.adwicons .playercontrols {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.playercontrols {
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.playercontrols button {
|
||||
margin: 3px;
|
||||
margin-left: 1px;
|
||||
margin-right: 1px;
|
||||
}
|
||||
.fullscreen.tvmode .playercontrols button {
|
||||
min-width: 32px;
|
||||
min-height: 32px;
|
||||
margin: 5px;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.fullscreen.tvmode button image {
|
||||
-gtk-icon-shadow: none;
|
||||
}
|
||||
.tvmode radio {
|
||||
.fullscreen.tvmode radio {
|
||||
margin-left: 0px;
|
||||
margin-right: 4px;
|
||||
border: 2px solid;
|
||||
@@ -85,40 +105,38 @@ radio {
|
||||
min-height: 17px;
|
||||
}
|
||||
|
||||
.tvmode .playercontrols {
|
||||
.fullscreen.tvmode .playercontrols button image {
|
||||
-gtk-icon-size: 24px;
|
||||
}
|
||||
.playbackicon {
|
||||
.adwicons .playbackicon {
|
||||
-gtk-icon-size: 20px;
|
||||
}
|
||||
.tvmode .playbackicon {
|
||||
.adwicons.fullscreen.tvmode .playbackicon {
|
||||
-gtk-icon-size: 28px;
|
||||
}
|
||||
.labelbutton {
|
||||
.labelbuttonlabel {
|
||||
margin-left: -4px;
|
||||
margin-right: -4px;
|
||||
margin-top: 1px;
|
||||
min-width: 8px;
|
||||
font-family: 'Cantarell', sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 600;
|
||||
}
|
||||
.tvmode .labelbutton {
|
||||
margin-top: 0px;
|
||||
font-size: 23px;
|
||||
.fullscreen.tvmode .labelbuttonlabel {
|
||||
font-size: 22px;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
/* Top Revealer */
|
||||
.tvmode .revealertopgrid {
|
||||
.fullscreen.tvmode .revealertopgrid {
|
||||
font-family: 'Cantarell', sans-serif;
|
||||
}
|
||||
.tvmode .tvtitle {
|
||||
.fullscreen.tvmode .tvtitle {
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
text-shadow: none;
|
||||
}
|
||||
.tvtime {
|
||||
.fullscreen.tvmode .tvtime {
|
||||
margin-top: -2px;
|
||||
margin-bottom: -2px;
|
||||
min-height: 4px;
|
||||
@@ -126,7 +144,7 @@ radio {
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.tvendtime {
|
||||
.fullscreen.tvmode .tvendtime {
|
||||
margin-top: -4px;
|
||||
margin-bottom: 2px;
|
||||
min-height: 6px;
|
||||
@@ -143,11 +161,9 @@ radio {
|
||||
|
||||
/* Position Scale */
|
||||
.positionscale {
|
||||
margin-top: -2px;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
.tvmode .positionscale {
|
||||
margin-top: -1px;
|
||||
margin: -2px;
|
||||
margin-left: -4px;
|
||||
margin-right: -4px;
|
||||
}
|
||||
.positionscale trough highlight {
|
||||
min-height: 4px;
|
||||
@@ -155,7 +171,7 @@ radio {
|
||||
.osd .positionscale trough highlight {
|
||||
min-height: 6px;
|
||||
}
|
||||
.tvmode .positionscale trough slider {
|
||||
.fullscreen.tvmode .positionscale trough slider {
|
||||
color: transparent;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
@@ -167,11 +183,11 @@ radio {
|
||||
.positionscale.fine-tune mark indicator {
|
||||
min-height: 6px;
|
||||
}
|
||||
.tvmode .positionscale mark indicator {
|
||||
.fullscreen.tvmode .positionscale mark indicator {
|
||||
min-height: 7px;
|
||||
min-width: 2px;
|
||||
}
|
||||
.tvmode .positionscale.fine-tune mark indicator {
|
||||
.fullscreen.tvmode .positionscale.fine-tune mark indicator {
|
||||
min-height: 7px;
|
||||
min-width: 2px;
|
||||
}
|
||||
@@ -183,17 +199,17 @@ radio {
|
||||
margin-top: 4px;
|
||||
margin-bottom: -6px;
|
||||
}
|
||||
.tvmode .positionscale marks.top {
|
||||
.fullscreen.tvmode .positionscale marks.top {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.tvmode .positionscale marks.bottom {
|
||||
.fullscreen.tvmode .positionscale marks.bottom {
|
||||
margin-top: 2px;
|
||||
}
|
||||
.tvmode .positionscale trough highlight {
|
||||
.fullscreen.tvmode .positionscale trough highlight {
|
||||
border-radius: 3px;
|
||||
min-height: 20px;
|
||||
}
|
||||
.tvmode .positionscale.fine-tune trough highlight {
|
||||
.fullscreen.tvmode .positionscale.fine-tune trough highlight {
|
||||
border-radius: 3px;
|
||||
min-height: 20px;
|
||||
}
|
||||
@@ -205,7 +221,7 @@ radio {
|
||||
margin-right: -6px;
|
||||
min-height: 180px;
|
||||
}
|
||||
.tvmode .volumescale {
|
||||
.fullscreen.tvmode .volumescale {
|
||||
margin: 2px;
|
||||
margin-left: -6px;
|
||||
margin-right: -4px;
|
||||
@@ -216,7 +232,7 @@ radio {
|
||||
margin-top: -4px;
|
||||
margin-bottom: -6px;
|
||||
}
|
||||
.tvmode .volumescale trough highlight {
|
||||
.fullscreen.tvmode .volumescale trough highlight {
|
||||
min-width: 6px;
|
||||
}
|
||||
.overamp trough highlight {
|
||||
@@ -230,10 +246,10 @@ radio {
|
||||
.elapsedpopoverbox box separator {
|
||||
background: @insensitive_fg_color;
|
||||
}
|
||||
.tvmode .elapsedpopoverbox {
|
||||
.fullscreen.tvmode .elapsedpopoverbox {
|
||||
min-width: 360px;
|
||||
}
|
||||
.tvmode .speedscale trough highlight {
|
||||
.fullscreen.tvmode .speedscale trough highlight {
|
||||
min-height: 6px;
|
||||
}
|
||||
|
||||
@@ -256,7 +272,7 @@ radio {
|
||||
.chapterlabel {
|
||||
min-width: 32px;
|
||||
}
|
||||
.tvmode .chapterlabel {
|
||||
.fullscreen.tvmode .chapterlabel {
|
||||
min-width: 40px;
|
||||
text-shadow: none;
|
||||
font-size: 22px;
|
||||
@@ -298,12 +314,9 @@ radio {
|
||||
.gpufriendly {
|
||||
box-shadow: -8px -8px transparent, 8px 8px transparent;
|
||||
}
|
||||
.gpufriendlyfs {
|
||||
.fullscreen.gpufriendlyfs {
|
||||
box-shadow: none;
|
||||
}
|
||||
.brightscale trough highlight {
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
/* Error BG */
|
||||
.blackbackground {
|
||||
|
115
data/com.github.rafostar.Clapper-symbolic.svg
Normal file
115
data/com.github.rafostar.Clapper-symbolic.svg
Normal file
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 4.2333333 4.2333334"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
sodipodi:docname="com.github.rafostar.Clapper-symbolic.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:snap-midpoints="true"
|
||||
inkscape:snap-global="false"
|
||||
units="px"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="6.078125"
|
||||
inkscape:cy="8.09375"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="981"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect1853"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
satellites_param="F,0,0,1,0,1.8520833,0,1 @ F,0,0,1,0,1.8520833,0,1 @ F,0,0,1,0,1.8520833,0,1 @ F,0,0,1,0,1.8520833,0,1"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
radius="7"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect1732"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
satellites_param="F,0,0,1,0,1.8520833,0,1 @ F,0,0,1,0,1.8520833,0,1 @ F,0,0,1,0,1.8520833,0,1 @ F,0,0,1,0,1.8520833,0,1"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
radius="7"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g2022"
|
||||
transform="matrix(0.06169519,0,0,0.06168906,-4.7800087,-3.2713603)">
|
||||
<path
|
||||
id="rect973"
|
||||
style="fill:#000000;stroke-width:1.30776;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
|
||||
d="m 88.193064,81.795006 c -0.699254,0 -1.342327,0.227875 -1.864484,0.609782 h 51.32503 c -0.52216,-0.381907 -1.16471,-0.609782 -1.86397,-0.609782 z m -3.157945,10.475846 v 26.225278 c 0,1.74939 1.40856,3.15743 3.157945,3.15743 h 47.596576 c 1.74939,0 3.15795,-1.40804 3.15795,-3.15743 V 92.270852 Z m 20.323311,4.964038 15.40009,9.27283 -15.5205,9.56634 z" />
|
||||
<path
|
||||
style="fill:#000000;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 83.974394,69.464471 7.344033,-0.06509 a 2.7923103,2.7923103 33.047712 0 1 2.587466,1.683384 l 7.009937,16.201526 a 1.2163248,1.2163248 123.30899 0 1 -1.116623,1.699322 l -15.720141,-0.004 a 1.862525,1.862525 44.853691 0 1 -1.862019,-1.852534 l -0.08473,-15.79409 a 1.8585738,1.8585738 134.59241 0 1 1.842075,-1.868472 z"
|
||||
id="path1422"
|
||||
inkscape:path-effect="#path-effect1732"
|
||||
inkscape:original-d="m 82.122383,69.480886 11.048055,-0.09792 8.480852,19.601124 -19.424307,-0.005 z" />
|
||||
<rect
|
||||
style="fill:#000000;stroke-width:1.3;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
|
||||
id="rect1544"
|
||||
width="59.366463"
|
||||
height="9.8661175"
|
||||
x="82"
|
||||
y="79.292183"
|
||||
ry="1.2306831" />
|
||||
<path
|
||||
id="rect1847"
|
||||
style="fill:#000000;stroke-width:4.91339;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
|
||||
d="m 522.12695,200.42773 c -0.45798,3.8e-4 -0.92335,0.0696 -1.38476,0.21289 l -172.88672,53.70313 5.28515,-0.0469 a 10.55362,10.55362 0 0 1 9.7793,6.36328 l 10.69922,24.72656 158.18359,-49.13477 c 2.46089,-0.7644 3.82691,-3.36137 3.0625,-5.82226 l -8.30078,-26.72657 c -0.62108,-1.99947 -2.4529,-3.277 -4.4375,-3.27539 z m -203.69531,63.05469 -3.08398,0.95899 c -2.46089,0.7644 -3.82691,3.35942 -3.0625,5.82031 l 6.29101,20.2539 z"
|
||||
transform="scale(0.26458333)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.8 KiB |
@@ -14,6 +14,10 @@
|
||||
<default>100</default>
|
||||
<summary>Custom initial volume value in percentage after startup</summary>
|
||||
</key>
|
||||
<key name="keep-last-frame" type="b">
|
||||
<default>false</default>
|
||||
<summary>Keep showing last video frame after playback finishes</summary>
|
||||
</key>
|
||||
<key name="close-auto" type="b">
|
||||
<default>false</default>
|
||||
<summary>Automatically close the app after playback finishes</summary>
|
||||
@@ -84,10 +88,6 @@
|
||||
<default>true</default>
|
||||
<summary>Enable to force the app to use dark theme variant</summary>
|
||||
</key>
|
||||
<key name="brighter-sliders" type="b">
|
||||
<default>true</default>
|
||||
<summary>Enable to make all sliders/bars brighter</summary>
|
||||
</key>
|
||||
<key name="render-shadows" type="b">
|
||||
<default>true</default>
|
||||
<summary>Enable rendering window shadows (only if theme has them)</summary>
|
||||
@@ -103,9 +103,19 @@
|
||||
<summary>Set PlayFlags for playbin</summary>
|
||||
</key>
|
||||
|
||||
<!-- YouTube -->
|
||||
<key name="yt-adaptive-enabled" type="b">
|
||||
<default>false</default>
|
||||
<summary>Enable to use adaptive streaming for YouTube</summary>
|
||||
</key>
|
||||
<key name="yt-quality-type" type="s">
|
||||
<default>"hfr"</default>
|
||||
<summary>Max YouTube video quality type</summary>
|
||||
</key>
|
||||
|
||||
<!-- Other -->
|
||||
<key name="window-size" type="s">
|
||||
<default>'[960, 583]'</default>
|
||||
<default>'[800, 490]'</default>
|
||||
<summary>Stores window size to restore on next launch</summary>
|
||||
</key>
|
||||
<key name="volume-last" type="d">
|
||||
|
@@ -25,7 +25,7 @@
|
||||
<p>
|
||||
For best stability Wayland session is recommended. Wayland users with AMD/Intel GPUs
|
||||
can try enabling HIGHLY EXPERIMENTAL "vah264dec" plugin inside player preferences
|
||||
for reduced CPU and GPU usage on standard (8-bit) H.264 videos.
|
||||
for reduced CPU and GPU usage on H.264 videos.
|
||||
</p>
|
||||
</description>
|
||||
<developer_name>Rafał Dzięgiel</developer_name>
|
||||
@@ -52,6 +52,64 @@
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release version="0.3.0" date="2021-06-18">
|
||||
<description>
|
||||
<p>Changes:</p>
|
||||
<ul>
|
||||
<li>Added MPRIS support</li>
|
||||
<li>Added repeat modes: single video, whole playlist and shuffle</li>
|
||||
<li>Support opening folders with media files</li>
|
||||
<li>Append playlist items by holding Ctrl while doing Drag and Drop</li>
|
||||
<li>Improved handling of keyboard shortcuts</li>
|
||||
<li>Added more keyboard shortcuts</li>
|
||||
<li>Added window that shows available keyboard shortcuts</li>
|
||||
<li>Show black screen by default after playback (make showing last frame optional instead)</li>
|
||||
<li>Added ability to export playlist to file</li>
|
||||
<li>Improve handling of changing displays with different resolutions</li>
|
||||
<li>Added support for EGL under x11 with GTK 4.3.1 or later</li>
|
||||
<li>Added missing symbolic app icon</li>
|
||||
<li>Some misc bug fixes and code cleanups</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.2.1" date="2021-04-19">
|
||||
<description>
|
||||
<p>Player:</p>
|
||||
<ul>
|
||||
<li>Fix missing top left menu buttons on some system configurations</li>
|
||||
<li>Fix potential video sink deadlock</li>
|
||||
<li>Do not show mobile controls transition on launch</li>
|
||||
<li>Show tooltip with full playlist item text on hover</li>
|
||||
</ul>
|
||||
<p>YouTube:</p>
|
||||
<ul>
|
||||
<li>Auto select best matching resolution for used monitor</li>
|
||||
<li>Added some YouTube related preferences</li>
|
||||
<li>Added support for live HLS videos</li>
|
||||
<li>Added support for non-adaptive live HLS streaming</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.2.0" date="2021-04-13">
|
||||
<description>
|
||||
<p>New features:</p>
|
||||
<ul>
|
||||
<li>YouTube support - drag and drop videos from youtube or use open URI dialog to play them</li>
|
||||
<li>Added convenient ways of opening external subtitles</li>
|
||||
</ul>
|
||||
<p>Changes:</p>
|
||||
<ul>
|
||||
<li>Few GUI layout improvements</li>
|
||||
<li>Simplified video sink code</li>
|
||||
<li>Fixed missing Ctrl+O common keybinding</li>
|
||||
<li>Fixed error when playback finishes during controls reveal animation</li>
|
||||
<li>Fixed startup window size on Xorg</li>
|
||||
<li>Fixed top time not showing up on fullscreen startup</li>
|
||||
<li>Fixed missing file extensions in online URIs</li>
|
||||
<li>Fixed some error messages not being displayed</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.1.0" date="2021-02-26">
|
||||
<description>
|
||||
<p>First stable release</p>
|
||||
|
52
data/gstclapper-mpris-gdbus.xml
Normal file
52
data/gstclapper-mpris-gdbus.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<node>
|
||||
<interface name="org.mpris.MediaPlayer2">
|
||||
<method name="Raise"/>
|
||||
<method name="Quit"/>
|
||||
<property name="CanQuit" type="b" access="read"/>
|
||||
<property name="Fullscreen" type="b" access="readwrite"/>
|
||||
<property name="CanSetFullscreen" type="b" access="read"/>
|
||||
<property name="CanRaise" type="b" access="read"/>
|
||||
<property name="HasTrackList" type="b" access="read"/>
|
||||
<property name="Identity" type="s" access="read"/>
|
||||
<property name="DesktopEntry" type="s" access="read"/>
|
||||
<property name="SupportedUriSchemes" type="as" access="read"/>
|
||||
<property name="SupportedMimeTypes" type="as" access="read"/>
|
||||
</interface>
|
||||
<interface name="org.mpris.MediaPlayer2.Player">
|
||||
<method name="Next"/>
|
||||
<method name="Previous"/>
|
||||
<method name="Pause"/>
|
||||
<method name="PlayPause"/>
|
||||
<method name="Stop"/>
|
||||
<method name="Play"/>
|
||||
<method name="Seek">
|
||||
<arg name="Offset" type="x" direction="in"/>
|
||||
</method>
|
||||
<method name="SetPosition">
|
||||
<arg name="TrackId" type="o" direction="in"/>
|
||||
<arg name="Position" type="x" direction="in"/>
|
||||
</method>
|
||||
<method name="OpenUri">
|
||||
<arg name="Uri" type="s" direction="in"/>
|
||||
</method>
|
||||
<signal name="Seeked">
|
||||
<arg type="x" name="Position"/>
|
||||
</signal>
|
||||
<property name="PlaybackStatus" type="s" access="read"/>
|
||||
<property name="LoopStatus" type="s" access="readwrite"/>
|
||||
<property name="Rate" type="d" access="readwrite"/>
|
||||
<property name="Shuffle" type="b" access="readwrite"/>
|
||||
<property name="Metadata" type="a{sv}" access="read"/>
|
||||
<property name="Volume" type="d" access="readwrite"/>
|
||||
<property name="Position" type="x" access="read"/>
|
||||
<property name="MinimumRate" type="d" access="read"/>
|
||||
<property name="MaximumRate" type="d" access="read"/>
|
||||
<property name="CanGoNext" type="b" access="read"/>
|
||||
<property name="CanGoPrevious" type="b" access="read"/>
|
||||
<property name="CanPlay" type="b" access="read"/>
|
||||
<property name="CanPause" type="b" access="read"/>
|
||||
<property name="CanSeek" type="b" access="read"/>
|
||||
<property name="CanControl" type="b" access="read"/>
|
||||
</interface>
|
||||
</node>
|
@@ -4,6 +4,9 @@ iconsdir = join_paths(sharedir, 'icons', 'hicolor')
|
||||
install_data('com.github.rafostar.Clapper.svg',
|
||||
install_dir: join_paths(iconsdir, 'scalable', 'apps')
|
||||
)
|
||||
install_data('com.github.rafostar.Clapper-symbolic.svg',
|
||||
install_dir: join_paths(iconsdir, 'symbolic', 'apps')
|
||||
)
|
||||
install_data('com.github.rafostar.Clapper.gschema.xml',
|
||||
install_dir: join_paths(sharedir, 'glib-2.0', 'schemas')
|
||||
)
|
||||
|
1
lib/gst/clapper/clapper.h
vendored
1
lib/gst/clapper/clapper.h
vendored
@@ -28,6 +28,7 @@
|
||||
#include <gst/clapper/gstclapper-g-main-context-signal-dispatcher.h>
|
||||
#include <gst/clapper/gstclapper-video-overlay-video-renderer.h>
|
||||
#include <gst/clapper/gstclapper-visualization.h>
|
||||
#include <gst/clapper/gstclapper-mpris.h>
|
||||
#include <gst/clapper/gstclapper-gtk4-plugin.h>
|
||||
|
||||
#endif /* __CLAPPER_H__ */
|
||||
|
28
lib/gst/clapper/gstclapper-gtk4-plugin.c
vendored
28
lib/gst/clapper/gstclapper-gtk4-plugin.c
vendored
@@ -30,7 +30,7 @@
|
||||
#endif
|
||||
|
||||
#include "gstclapper-gtk4-plugin.h"
|
||||
#include "gtk4/gstgtkglsink.h"
|
||||
#include "gtk4/gstclapperglsink.h"
|
||||
|
||||
enum
|
||||
{
|
||||
@@ -77,9 +77,7 @@ gst_clapper_gtk4_plugin_constructed (GObject * object)
|
||||
{
|
||||
GstClapperGtk4Plugin *self = GST_CLAPPER_GTK4_PLUGIN (object);
|
||||
|
||||
if (!self->video_sink)
|
||||
self->video_sink = g_object_new (GST_TYPE_GTK_GL_SINK, NULL);
|
||||
|
||||
self->video_sink = g_object_new (GST_TYPE_CLAPPER_GL_SINK, NULL);
|
||||
gst_object_ref_sink (self->video_sink);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
@@ -111,35 +109,15 @@ gst_clapper_gtk4_plugin_finalize (GObject * object)
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
#define C_ENUM(v) ((gint) v)
|
||||
|
||||
GType
|
||||
gst_clapper_gtk4_plugin_type_get_type (void)
|
||||
{
|
||||
static gsize id = 0;
|
||||
static const GEnumValue values[] = {
|
||||
{C_ENUM (GST_CLAPPER_GTK4_PLUGIN_TYPE_GLAREA), "GST_CLAPPER_GTK4_PLUGIN_TYPE_GLAREA", "glarea"},
|
||||
{0, NULL, NULL}
|
||||
};
|
||||
|
||||
if (g_once_init_enter (&id)) {
|
||||
GType tmp = g_enum_register_static ("GstClapperGtk4PluginType", values);
|
||||
g_once_init_leave (&id, tmp);
|
||||
}
|
||||
|
||||
return (GType) id;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_clapper_gtk4_plugin_new:
|
||||
* @plugin_type: (allow-none): Requested GstClapperGtk4PluginType
|
||||
*
|
||||
* Creates a new GTK4 plugin.
|
||||
*
|
||||
* Returns: (transfer full): the new GstClapperGtk4Plugin
|
||||
*/
|
||||
GstClapperGtk4Plugin *
|
||||
gst_clapper_gtk4_plugin_new (G_GNUC_UNUSED const GstClapperGtk4PluginType plugin_type)
|
||||
gst_clapper_gtk4_plugin_new (void)
|
||||
{
|
||||
return g_object_new (GST_TYPE_CLAPPER_GTK4_PLUGIN, NULL);
|
||||
}
|
||||
|
16
lib/gst/clapper/gstclapper-gtk4-plugin.h
vendored
16
lib/gst/clapper/gstclapper-gtk4-plugin.h
vendored
@@ -26,20 +26,6 @@
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/* PluginType */
|
||||
GST_CLAPPER_API
|
||||
GType gst_clapper_gtk4_plugin_type_get_type (void);
|
||||
#define GST_TYPE_CLAPPER_GTK4_PLUGIN_TYPE (gst_clapper_gtk4_plugin_type_get_type ())
|
||||
|
||||
/**
|
||||
* GstClapperGtk4PluginType:
|
||||
* @GST_CLAPPER_GTK4_PLUGIN_TYPE_GLAREA: GTK4 GLArea sink.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
GST_CLAPPER_GTK4_PLUGIN_TYPE_GLAREA,
|
||||
} GstClapperGtk4PluginType;
|
||||
|
||||
#define GST_TYPE_CLAPPER_GTK4_PLUGIN (gst_clapper_gtk4_plugin_get_type ())
|
||||
#define GST_IS_CLAPPER_GTK4_PLUGIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_GTK4_PLUGIN))
|
||||
#define GST_IS_CLAPPER_GTK4_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_GTK4_PLUGIN))
|
||||
@@ -79,7 +65,7 @@ GST_CLAPPER_API
|
||||
GType gst_clapper_gtk4_plugin_get_type (void);
|
||||
|
||||
GST_CLAPPER_API
|
||||
GstClapperGtk4Plugin * gst_clapper_gtk4_plugin_new (const GstClapperGtk4PluginType plugin_type);
|
||||
GstClapperGtk4Plugin * gst_clapper_gtk4_plugin_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@@ -45,6 +45,7 @@ struct _GstClapperSubtitleInfo
|
||||
{
|
||||
GstClapperStreamInfo parent;
|
||||
|
||||
gchar *title;
|
||||
gchar *language;
|
||||
};
|
||||
|
||||
@@ -108,7 +109,7 @@ struct _GstClapperMediaInfo
|
||||
GList *video_stream_list;
|
||||
GList *subtitle_stream_list;
|
||||
|
||||
GstClockTime duration;
|
||||
GstClockTime duration;
|
||||
};
|
||||
|
||||
struct _GstClapperMediaInfoClass
|
||||
|
20
lib/gst/clapper/gstclapper-media-info.c
vendored
20
lib/gst/clapper/gstclapper-media-info.c
vendored
@@ -379,6 +379,7 @@ gst_clapper_subtitle_info_finalize (GObject * object)
|
||||
{
|
||||
GstClapperSubtitleInfo *info = GST_CLAPPER_SUBTITLE_INFO (object);
|
||||
|
||||
g_free (info->title);
|
||||
g_free (info->language);
|
||||
|
||||
G_OBJECT_CLASS (gst_clapper_subtitle_info_parent_class)->finalize (object);
|
||||
@@ -392,6 +393,20 @@ gst_clapper_subtitle_info_class_init (GstClapperSubtitleInfoClass * klass)
|
||||
gobject_class->finalize = gst_clapper_subtitle_info_finalize;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_clapper_subtitle_info_get_title:
|
||||
* @info: a #GstClapperSubtitleInfo
|
||||
*
|
||||
* Returns: the title of the stream, or NULL if unknown.
|
||||
*/
|
||||
const gchar *
|
||||
gst_clapper_subtitle_info_get_title (const GstClapperSubtitleInfo * info)
|
||||
{
|
||||
g_return_val_if_fail (GST_IS_CLAPPER_SUBTITLE_INFO (info), NULL);
|
||||
|
||||
return info->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_clapper_subtitle_info_get_language:
|
||||
* @info: a #GstClapperSubtitleInfo
|
||||
@@ -513,6 +528,8 @@ gst_clapper_subtitle_info_copy (GstClapperSubtitleInfo * ref)
|
||||
GstClapperSubtitleInfo *ret;
|
||||
|
||||
ret = gst_clapper_subtitle_info_new ();
|
||||
if (ref->title)
|
||||
ret->title = g_strdup (ref->title);
|
||||
if (ref->language)
|
||||
ret->language = g_strdup (ref->language);
|
||||
|
||||
@@ -767,7 +784,8 @@ gst_clapper_media_info_get_toc (const GstClapperMediaInfo * info)
|
||||
* gst_clapper_media_info_get_title:
|
||||
* @info: a #GstClapperMediaInfo
|
||||
*
|
||||
* Returns: the media title.
|
||||
* Returns: the media title. When metadata does not contain title,
|
||||
* returns title parsed from URI.
|
||||
*/
|
||||
const gchar *
|
||||
gst_clapper_media_info_get_title (const GstClapperMediaInfo * info)
|
||||
|
5
lib/gst/clapper/gstclapper-media-info.h
vendored
5
lib/gst/clapper/gstclapper-media-info.h
vendored
@@ -170,7 +170,10 @@ GST_CLAPPER_API
|
||||
GType gst_clapper_subtitle_info_get_type (void);
|
||||
|
||||
GST_CLAPPER_API
|
||||
const gchar * gst_clapper_subtitle_info_get_language (const GstClapperSubtitleInfo* info);
|
||||
const gchar * gst_clapper_subtitle_info_get_title (const GstClapperSubtitleInfo *info);
|
||||
|
||||
GST_CLAPPER_API
|
||||
const gchar * gst_clapper_subtitle_info_get_language (const GstClapperSubtitleInfo *info);
|
||||
|
||||
#define GST_TYPE_CLAPPER_MEDIA_INFO \
|
||||
(gst_clapper_media_info_get_type())
|
||||
|
43
lib/gst/clapper/gstclapper-mpris-private.h
Normal file
43
lib/gst/clapper/gstclapper-mpris-private.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_CLAPPER_MPRIS_PRIVATE_H__
|
||||
#define __GST_CLAPPER_MPRIS_PRIVATE_H__
|
||||
|
||||
#include <gst/clapper/gstclapper-mpris.h>
|
||||
#include <gst/clapper/gstclapper.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void gst_clapper_mpris_set_clapper (GstClapperMpris *self, GstClapper *clapper,
|
||||
GstClapperSignalDispatcher *signal_dispatcher);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void gst_clapper_mpris_set_media_info (GstClapperMpris *self, GstClapperMediaInfo *info);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void gst_clapper_mpris_set_playback_status (GstClapperMpris *self, const gchar *status);
|
||||
|
||||
G_GNUC_INTERNAL
|
||||
void gst_clapper_mpris_set_position (GstClapperMpris *self, gint64 position);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_CLAPPER_MPRIS_PRIVATE_H__ */
|
778
lib/gst/clapper/gstclapper-mpris.c
Normal file
778
lib/gst/clapper/gstclapper-mpris.c
Normal file
@@ -0,0 +1,778 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstclapper-mpris-gdbus.h"
|
||||
#include "gstclapper-mpris.h"
|
||||
#include "gstclapper-mpris-private.h"
|
||||
#include "gstclapper-signal-dispatcher-private.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_clapper_mpris_debug);
|
||||
#define GST_CAT_DEFAULT gst_clapper_mpris_debug
|
||||
|
||||
#define MPRIS_DEFAULT_VOLUME 1.0
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_OWN_NAME,
|
||||
PROP_ID_PATH,
|
||||
PROP_IDENTITY,
|
||||
PROP_DESKTOP_ENTRY,
|
||||
PROP_DEFAULT_ART_URL,
|
||||
PROP_VOLUME,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
struct _GstClapperMpris
|
||||
{
|
||||
GObject parent;
|
||||
|
||||
GstClapperMprisMediaPlayer2 *base_skeleton;
|
||||
GstClapperMprisMediaPlayer2Player *player_skeleton;
|
||||
|
||||
GstClapperSignalDispatcher *signal_dispatcher;
|
||||
GstClapperMediaInfo *media_info;
|
||||
|
||||
guint name_id;
|
||||
|
||||
/* Properties */
|
||||
gchar *own_name;
|
||||
gchar *id_path;
|
||||
gchar *identity;
|
||||
gchar *desktop_entry;
|
||||
gchar *default_art_url;
|
||||
|
||||
gboolean parse_media_info;
|
||||
|
||||
/* Current status */
|
||||
gchar *playback_status;
|
||||
gboolean can_play;
|
||||
guint64 position;
|
||||
|
||||
GThread *thread;
|
||||
GMutex lock;
|
||||
GCond cond;
|
||||
GMainContext *context;
|
||||
GMainLoop *loop;
|
||||
};
|
||||
|
||||
struct _GstClapperMprisClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
#define parent_class gst_clapper_mpris_parent_class
|
||||
G_DEFINE_TYPE (GstClapperMpris, gst_clapper_mpris, G_TYPE_OBJECT);
|
||||
|
||||
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
|
||||
|
||||
static void gst_clapper_mpris_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec);
|
||||
static void gst_clapper_mpris_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec);
|
||||
static void gst_clapper_mpris_dispose (GObject * object);
|
||||
static void gst_clapper_mpris_finalize (GObject * object);
|
||||
static void gst_clapper_mpris_constructed (GObject * object);
|
||||
static gpointer gst_clapper_mpris_main (gpointer data);
|
||||
|
||||
static void unregister (GstClapperMpris * self);
|
||||
|
||||
static void
|
||||
gst_clapper_mpris_init (GstClapperMpris * self)
|
||||
{
|
||||
GST_DEBUG_CATEGORY_INIT (gst_clapper_mpris_debug, "ClapperMpris", 0,
|
||||
"GstClapperMpris");
|
||||
GST_TRACE_OBJECT (self, "Initializing");
|
||||
|
||||
self = gst_clapper_mpris_get_instance_private (self);
|
||||
|
||||
g_mutex_init (&self->lock);
|
||||
g_cond_init (&self->cond);
|
||||
|
||||
self->context = g_main_context_new ();
|
||||
self->loop = g_main_loop_new (self->context, FALSE);
|
||||
|
||||
self->base_skeleton = gst_clapper_mpris_media_player2_skeleton_new ();
|
||||
self->player_skeleton = gst_clapper_mpris_media_player2_player_skeleton_new ();
|
||||
|
||||
self->name_id = 0;
|
||||
self->own_name = NULL;
|
||||
self->id_path = NULL;
|
||||
self->identity = NULL;
|
||||
self->desktop_entry = NULL;
|
||||
self->default_art_url = NULL;
|
||||
|
||||
self->signal_dispatcher = NULL;
|
||||
self->media_info = NULL;
|
||||
self->parse_media_info = FALSE;
|
||||
|
||||
self->playback_status = g_strdup ("Stopped");
|
||||
self->can_play = FALSE;
|
||||
self->position = 0;
|
||||
|
||||
GST_TRACE_OBJECT (self, "Initialized");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_mpris_class_init (GstClapperMprisClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
|
||||
gobject_class->set_property = gst_clapper_mpris_set_property;
|
||||
gobject_class->get_property = gst_clapper_mpris_get_property;
|
||||
gobject_class->dispose = gst_clapper_mpris_dispose;
|
||||
gobject_class->finalize = gst_clapper_mpris_finalize;
|
||||
gobject_class->constructed = gst_clapper_mpris_constructed;
|
||||
|
||||
param_specs[PROP_OWN_NAME] =
|
||||
g_param_spec_string ("own-name", "DBus own name",
|
||||
"DBus name to own on connection",
|
||||
NULL, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
param_specs[PROP_ID_PATH] =
|
||||
g_param_spec_string ("id-path", "DBus id path",
|
||||
"A valid D-Bus path describing this player",
|
||||
NULL, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
param_specs[PROP_IDENTITY] =
|
||||
g_param_spec_string ("identity", "Player name",
|
||||
"A friendly name to identify the media player",
|
||||
NULL, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
param_specs[PROP_DESKTOP_ENTRY] =
|
||||
g_param_spec_string ("desktop-entry", "Desktop entry filename",
|
||||
"The basename of an installed .desktop file",
|
||||
NULL, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
param_specs[PROP_DEFAULT_ART_URL] =
|
||||
g_param_spec_string ("default-art-url", "Default Art URL",
|
||||
"Default art to show when media does not provide one",
|
||||
NULL, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
param_specs[PROP_VOLUME] =
|
||||
g_param_spec_double ("volume", "Volume", "Volume",
|
||||
0, 1.5, MPRIS_DEFAULT_VOLUME, G_PARAM_READWRITE |
|
||||
G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_mpris_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstClapperMpris *self = GST_CLAPPER_MPRIS (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_OWN_NAME:
|
||||
self->own_name = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_ID_PATH:
|
||||
self->id_path = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_IDENTITY:
|
||||
self->identity = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_DESKTOP_ENTRY:
|
||||
self->desktop_entry = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_DEFAULT_ART_URL:
|
||||
self->default_art_url = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_VOLUME:
|
||||
g_object_set_property (G_OBJECT (self->player_skeleton), "volume", value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_mpris_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstClapperMpris *self = GST_CLAPPER_MPRIS (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_VOLUME:
|
||||
g_object_get_property (G_OBJECT (self->player_skeleton), "volume", value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_mpris_dispose (GObject * object)
|
||||
{
|
||||
GstClapperMpris *self = GST_CLAPPER_MPRIS (object);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Stopping main thread");
|
||||
|
||||
if (self->loop) {
|
||||
g_main_loop_quit (self->loop);
|
||||
|
||||
if (self->thread != g_thread_self ())
|
||||
g_thread_join (self->thread);
|
||||
else
|
||||
g_thread_unref (self->thread);
|
||||
self->thread = NULL;
|
||||
|
||||
g_main_loop_unref (self->loop);
|
||||
self->loop = NULL;
|
||||
|
||||
g_main_context_unref (self->context);
|
||||
self->context = NULL;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_mpris_finalize (GObject * object)
|
||||
{
|
||||
GstClapperMpris *self = GST_CLAPPER_MPRIS (object);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Finalize");
|
||||
|
||||
g_free (self->own_name);
|
||||
g_free (self->id_path);
|
||||
g_free (self->identity);
|
||||
g_free (self->desktop_entry);
|
||||
g_free (self->default_art_url);
|
||||
g_free (self->playback_status);
|
||||
|
||||
if (self->base_skeleton)
|
||||
g_object_unref (self->base_skeleton);
|
||||
if (self->player_skeleton)
|
||||
g_object_unref (self->player_skeleton);
|
||||
if (self->signal_dispatcher)
|
||||
g_object_unref (self->signal_dispatcher);
|
||||
if (self->media_info)
|
||||
g_object_unref (self->media_info);
|
||||
|
||||
g_mutex_clear (&self->lock);
|
||||
g_cond_clear (&self->cond);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_mpris_constructed (GObject * object)
|
||||
{
|
||||
GstClapperMpris *self = GST_CLAPPER_MPRIS (object);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Constructed");
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
self->thread = g_thread_new ("GstClapperMpris",
|
||||
gst_clapper_mpris_main, self);
|
||||
while (!self->loop || !g_main_loop_is_running (self->loop))
|
||||
g_cond_wait (&self->cond, &self->lock);
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
main_loop_running_cb (gpointer user_data)
|
||||
{
|
||||
GstClapperMpris *self = GST_CLAPPER_MPRIS (user_data);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Main loop running now");
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
g_cond_signal (&self->cond);
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_play_cb (GstClapperMprisMediaPlayer2Player * player_skeleton,
|
||||
GDBusMethodInvocation * invocation, gpointer user_data)
|
||||
{
|
||||
GstClapper *clapper = GST_CLAPPER (user_data);
|
||||
|
||||
GST_DEBUG ("Handle Play");
|
||||
|
||||
gst_clapper_play (clapper);
|
||||
gst_clapper_mpris_media_player2_player_complete_play (player_skeleton, invocation);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_pause_cb (GstClapperMprisMediaPlayer2Player * player_skeleton,
|
||||
GDBusMethodInvocation * invocation, gpointer user_data)
|
||||
{
|
||||
GstClapper *clapper = GST_CLAPPER (user_data);
|
||||
|
||||
GST_DEBUG ("Handle Pause");
|
||||
|
||||
gst_clapper_pause (clapper);
|
||||
gst_clapper_mpris_media_player2_player_complete_pause (player_skeleton, invocation);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_play_pause_cb (GstClapperMprisMediaPlayer2Player * player_skeleton,
|
||||
GDBusMethodInvocation * invocation, gpointer user_data)
|
||||
{
|
||||
GstClapper *clapper = GST_CLAPPER (user_data);
|
||||
|
||||
GST_DEBUG ("Handle PlayPause");
|
||||
|
||||
gst_clapper_toggle_play (clapper);
|
||||
gst_clapper_mpris_media_player2_player_complete_play_pause (player_skeleton, invocation);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_seek_cb (GstClapperMprisMediaPlayer2Player * player_skeleton,
|
||||
GDBusMethodInvocation * invocation, gint64 offset, gpointer user_data)
|
||||
{
|
||||
GstClapper *clapper = GST_CLAPPER (user_data);
|
||||
|
||||
GST_DEBUG ("Handle Seek");
|
||||
|
||||
gst_clapper_seek_offset (clapper, offset * GST_USECOND);
|
||||
gst_clapper_mpris_media_player2_player_complete_seek (player_skeleton, invocation);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_set_position_cb (GstClapperMprisMediaPlayer2Player * player_skeleton,
|
||||
GDBusMethodInvocation * invocation, const gchar * track_id,
|
||||
gint64 position, gpointer user_data)
|
||||
{
|
||||
GstClapper *clapper = GST_CLAPPER (user_data);
|
||||
|
||||
GST_DEBUG ("Handle SetPosition");
|
||||
|
||||
gst_clapper_seek (clapper, position * GST_USECOND);
|
||||
gst_clapper_mpris_media_player2_player_complete_set_position (player_skeleton, invocation);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
handle_open_uri_cb (GstClapperMprisMediaPlayer2Player * player_skeleton,
|
||||
GDBusMethodInvocation * invocation, const gchar * uri,
|
||||
gpointer user_data)
|
||||
{
|
||||
GstClapper *clapper = GST_CLAPPER (user_data);
|
||||
|
||||
GST_DEBUG ("Handle OpenUri");
|
||||
|
||||
/* FIXME: set one item playlist instead */
|
||||
gst_clapper_set_uri (clapper, uri);
|
||||
gst_clapper_mpris_media_player2_player_complete_open_uri (player_skeleton, invocation);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
volume_notify_dispatch (gpointer user_data)
|
||||
{
|
||||
GstClapperMpris *self = user_data;
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_VOLUME]);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_volume_notify_cb (G_GNUC_UNUSED GObject * obj,
|
||||
G_GNUC_UNUSED GParamSpec * pspec, GstClapperMpris * self)
|
||||
{
|
||||
gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, NULL,
|
||||
volume_notify_dispatch, g_object_ref (self),
|
||||
(GDestroyNotify) g_object_unref);
|
||||
}
|
||||
|
||||
static void
|
||||
unregister (GstClapperMpris * self)
|
||||
{
|
||||
if (!self->name_id)
|
||||
return;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Unregister");
|
||||
g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->base_skeleton));
|
||||
g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->player_skeleton));
|
||||
g_bus_unown_name (self->name_id);
|
||||
self->name_id = 0;
|
||||
}
|
||||
|
||||
static const gchar *
|
||||
_get_mpris_trackid (GstClapperMpris * self)
|
||||
{
|
||||
/* TODO: Support more tracks */
|
||||
return g_strdup_printf ("%s%s%i", self->id_path, "/Track/", 0);
|
||||
}
|
||||
|
||||
static void
|
||||
_set_supported_uri_schemes (GstClapperMpris * self)
|
||||
{
|
||||
const gchar *uri_schemes[96] = {};
|
||||
GList *elements, *el;
|
||||
guint index = 0;
|
||||
|
||||
elements = gst_element_factory_list_get_elements (
|
||||
GST_ELEMENT_FACTORY_TYPE_SRC, GST_RANK_NONE);
|
||||
|
||||
for (el = elements; el != NULL; el = el->next) {
|
||||
const gchar *const *protocols;
|
||||
GstElementFactory *factory = GST_ELEMENT_FACTORY (el->data);
|
||||
|
||||
if (gst_element_factory_get_uri_type (factory) != GST_URI_SRC)
|
||||
continue;
|
||||
|
||||
protocols = gst_element_factory_get_uri_protocols (factory);
|
||||
if (protocols == NULL || *protocols == NULL)
|
||||
continue;
|
||||
|
||||
while (*protocols != NULL) {
|
||||
guint j = index;
|
||||
|
||||
while (j--) {
|
||||
if (strcmp (uri_schemes[j], *protocols) == 0)
|
||||
goto next;
|
||||
}
|
||||
uri_schemes[index] = *protocols;
|
||||
GST_DEBUG_OBJECT (self, "Added supported URI scheme: %s", *protocols);
|
||||
++index;
|
||||
next:
|
||||
++protocols;
|
||||
}
|
||||
}
|
||||
gst_plugin_feature_list_free (elements);
|
||||
|
||||
gst_clapper_mpris_media_player2_set_supported_uri_schemes (
|
||||
self->base_skeleton, uri_schemes);
|
||||
}
|
||||
|
||||
static void
|
||||
name_acquired_cb (GDBusConnection * connection,
|
||||
const gchar *name, gpointer user_data)
|
||||
{
|
||||
GstClapperMpris *self = GST_CLAPPER_MPRIS (user_data);
|
||||
GVariantBuilder builder;
|
||||
|
||||
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->base_skeleton),
|
||||
connection, "/org/mpris/MediaPlayer2", NULL);
|
||||
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->player_skeleton),
|
||||
connection, "/org/mpris/MediaPlayer2", NULL);
|
||||
|
||||
if (self->identity)
|
||||
gst_clapper_mpris_media_player2_set_identity (self->base_skeleton, self->identity);
|
||||
if (self->desktop_entry)
|
||||
gst_clapper_mpris_media_player2_set_desktop_entry (self->base_skeleton, self->desktop_entry);
|
||||
|
||||
_set_supported_uri_schemes (self);
|
||||
|
||||
gst_clapper_mpris_media_player2_player_set_playback_status (self->player_skeleton, "Stopped");
|
||||
gst_clapper_mpris_media_player2_player_set_minimum_rate (self->player_skeleton, 0.01);
|
||||
gst_clapper_mpris_media_player2_player_set_maximum_rate (self->player_skeleton, 2.0);
|
||||
gst_clapper_mpris_media_player2_player_set_can_seek (self->player_skeleton, TRUE);
|
||||
gst_clapper_mpris_media_player2_player_set_can_control (self->player_skeleton, TRUE);
|
||||
|
||||
g_object_bind_property (self->player_skeleton, "can-play",
|
||||
self->player_skeleton, "can-pause", G_BINDING_DEFAULT);
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
|
||||
g_variant_builder_add (&builder, "{sv}", "mpris:trackid", g_variant_new_string (_get_mpris_trackid (self)));
|
||||
g_variant_builder_add (&builder, "{sv}", "mpris:length", g_variant_new_uint64 (0));
|
||||
if (self->default_art_url)
|
||||
g_variant_builder_add (&builder, "{sv}", "mpris:artUrl", g_variant_new_string (self->default_art_url));
|
||||
gst_clapper_mpris_media_player2_player_set_metadata (self->player_skeleton, g_variant_builder_end (&builder));
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Ready");
|
||||
}
|
||||
|
||||
static void
|
||||
name_lost_cb (GDBusConnection * connection,
|
||||
const gchar * name, gpointer user_data)
|
||||
{
|
||||
GstClapperMpris *self = GST_CLAPPER_MPRIS (user_data);
|
||||
|
||||
unregister (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
mpris_update_props_dispatch (gpointer user_data)
|
||||
{
|
||||
GstClapperMpris *self = GST_CLAPPER_MPRIS (user_data);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Updating MPRIS props");
|
||||
g_mutex_lock (&self->lock);
|
||||
|
||||
if (self->parse_media_info) {
|
||||
GVariantBuilder builder;
|
||||
guint64 duration;
|
||||
const gchar *track_id, *uri, *title;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Parsing media info");
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
|
||||
|
||||
track_id = _get_mpris_trackid (self);
|
||||
uri = gst_clapper_media_info_get_uri (self->media_info);
|
||||
title = gst_clapper_media_info_get_title (self->media_info);
|
||||
|
||||
if (track_id) {
|
||||
g_variant_builder_add (&builder, "{sv}", "mpris:trackid",
|
||||
g_variant_new_string (track_id));
|
||||
GST_DEBUG_OBJECT (self, "mpris:trackid: %s", track_id);
|
||||
}
|
||||
if (uri) {
|
||||
g_variant_builder_add (&builder, "{sv}", "xesam:url",
|
||||
g_variant_new_string (uri));
|
||||
GST_DEBUG_OBJECT (self, "xesam:url: %s", uri);
|
||||
}
|
||||
if (title) {
|
||||
g_variant_builder_add (&builder, "{sv}", "xesam:title",
|
||||
g_variant_new_string (title));
|
||||
GST_DEBUG_OBJECT (self, "xesam:title: %s", title);
|
||||
}
|
||||
|
||||
duration = gst_clapper_media_info_get_duration (self->media_info);
|
||||
duration = (duration != GST_CLOCK_TIME_NONE) ? duration / GST_USECOND : 0;
|
||||
g_variant_builder_add (&builder, "{sv}", "mpris:length", g_variant_new_uint64 (duration));
|
||||
GST_DEBUG_OBJECT (self, "mpris:length: %ld", duration);
|
||||
|
||||
/* TODO: Check for image sample */
|
||||
if (self->default_art_url) {
|
||||
g_variant_builder_add (&builder, "{sv}", "mpris:artUrl", g_variant_new_string (self->default_art_url));
|
||||
GST_DEBUG_OBJECT (self, "mpris:artUrl: %s", self->default_art_url);
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Media info parsed");
|
||||
self->parse_media_info = FALSE;
|
||||
|
||||
gst_clapper_mpris_media_player2_player_set_metadata (
|
||||
self->player_skeleton, g_variant_builder_end (&builder));
|
||||
}
|
||||
if (gst_clapper_mpris_media_player2_player_get_can_play (
|
||||
self->player_skeleton) != self->can_play) {
|
||||
/* "can-play" is bound with "can-pause" */
|
||||
gst_clapper_mpris_media_player2_player_set_can_play (
|
||||
self->player_skeleton, self->can_play);
|
||||
GST_DEBUG_OBJECT (self, "CanPlay/CanPause: %s", self->can_play ? "yes" : "no");
|
||||
}
|
||||
if (strcmp (gst_clapper_mpris_media_player2_player_get_playback_status (
|
||||
self->player_skeleton), self->playback_status) != 0) {
|
||||
gst_clapper_mpris_media_player2_player_set_playback_status (
|
||||
self->player_skeleton, self->playback_status);
|
||||
GST_DEBUG_OBJECT (self, "PlaybackStatus: %s", self->playback_status);
|
||||
}
|
||||
if (gst_clapper_mpris_media_player2_player_get_position (
|
||||
self->player_skeleton) != self->position) {
|
||||
gst_clapper_mpris_media_player2_player_set_position (
|
||||
self->player_skeleton, self->position);
|
||||
GST_DEBUG_OBJECT (self, "Position: %ld", self->position);
|
||||
}
|
||||
|
||||
g_mutex_unlock (&self->lock);
|
||||
GST_DEBUG_OBJECT (self, "MPRIS props updated");
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
mpris_dispatcher_update_dispatch (GstClapperMpris * self)
|
||||
{
|
||||
if (!self->name_id)
|
||||
return;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Queued update props dispatch");
|
||||
|
||||
g_main_context_invoke_full (self->context,
|
||||
G_PRIORITY_DEFAULT, mpris_update_props_dispatch,
|
||||
g_object_ref (self), g_object_unref);
|
||||
}
|
||||
|
||||
static gpointer
|
||||
gst_clapper_mpris_main (gpointer data)
|
||||
{
|
||||
GstClapperMpris *self = GST_CLAPPER_MPRIS (data);
|
||||
|
||||
GDBusConnectionFlags flags;
|
||||
GDBusConnection *connection;
|
||||
GSource *source;
|
||||
gchar *address;
|
||||
|
||||
GST_TRACE_OBJECT (self, "Starting main thread");
|
||||
|
||||
g_main_context_push_thread_default (self->context);
|
||||
|
||||
source = g_idle_source_new ();
|
||||
g_source_set_callback (source, (GSourceFunc) main_loop_running_cb, self,
|
||||
NULL);
|
||||
g_source_attach (source, self->context);
|
||||
g_source_unref (source);
|
||||
|
||||
address = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
||||
if (!address) {
|
||||
GST_WARNING_OBJECT (self, "No MPRIS bus address");
|
||||
goto no_mpris;
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Obtained MPRIS DBus address");
|
||||
|
||||
flags = G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
|
||||
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION;
|
||||
connection = g_dbus_connection_new_for_address_sync (address,
|
||||
flags, NULL, NULL, NULL);
|
||||
g_free (address);
|
||||
|
||||
if (!connection) {
|
||||
GST_WARNING_OBJECT (self, "No MPRIS bus connection");
|
||||
goto no_mpris;
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Obtained MPRIS DBus connection");
|
||||
|
||||
self->name_id = g_bus_own_name_on_connection (connection, self->own_name,
|
||||
G_BUS_NAME_OWNER_FLAGS_NONE,
|
||||
(GBusNameAcquiredCallback) name_acquired_cb,
|
||||
(GBusNameLostCallback) name_lost_cb,
|
||||
self, NULL);
|
||||
g_object_unref (connection);
|
||||
goto done;
|
||||
|
||||
no_mpris:
|
||||
g_warning ("GstClapperMpris: failed to create DBus connection");
|
||||
|
||||
done:
|
||||
GST_TRACE_OBJECT (self, "Starting main loop");
|
||||
g_main_loop_run (self->loop);
|
||||
GST_TRACE_OBJECT (self, "Stopped main loop");
|
||||
|
||||
unregister (self);
|
||||
g_main_context_pop_thread_default (self->context);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Stopped main thread");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
gst_clapper_mpris_set_clapper (GstClapperMpris * self, GstClapper * clapper,
|
||||
GstClapperSignalDispatcher * signal_dispatcher)
|
||||
{
|
||||
if (signal_dispatcher)
|
||||
self->signal_dispatcher = g_object_ref (signal_dispatcher);
|
||||
|
||||
g_signal_connect (self->player_skeleton, "handle-play",
|
||||
G_CALLBACK (handle_play_cb), clapper);
|
||||
g_signal_connect (self->player_skeleton, "handle-pause",
|
||||
G_CALLBACK (handle_pause_cb), clapper);
|
||||
g_signal_connect (self->player_skeleton, "handle-play-pause",
|
||||
G_CALLBACK (handle_play_pause_cb), clapper);
|
||||
g_signal_connect (self->player_skeleton, "handle-seek",
|
||||
G_CALLBACK (handle_seek_cb), clapper);
|
||||
g_signal_connect (self->player_skeleton, "handle-set-position",
|
||||
G_CALLBACK (handle_set_position_cb), clapper);
|
||||
g_signal_connect (self->player_skeleton, "handle-open-uri",
|
||||
G_CALLBACK (handle_open_uri_cb), clapper);
|
||||
|
||||
g_object_bind_property (clapper, "volume", self, "volume", G_BINDING_BIDIRECTIONAL);
|
||||
g_signal_connect (self->player_skeleton, "notify::volume",
|
||||
G_CALLBACK (handle_volume_notify_cb), self);
|
||||
}
|
||||
|
||||
void
|
||||
gst_clapper_mpris_set_playback_status (GstClapperMpris * self, const gchar * status)
|
||||
{
|
||||
g_mutex_lock (&self->lock);
|
||||
if (strcmp (self->playback_status, status) == 0) {
|
||||
g_mutex_unlock (&self->lock);
|
||||
return;
|
||||
}
|
||||
g_free (self->playback_status);
|
||||
self->playback_status = g_strdup (status);
|
||||
self->can_play = strcmp (status, "Stopped") != 0;
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
mpris_dispatcher_update_dispatch (self);
|
||||
}
|
||||
|
||||
void
|
||||
gst_clapper_mpris_set_position (GstClapperMpris * self, gint64 position)
|
||||
{
|
||||
position /= GST_USECOND;
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
if (self->position == position) {
|
||||
g_mutex_unlock (&self->lock);
|
||||
return;
|
||||
}
|
||||
self->position = position;
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
mpris_dispatcher_update_dispatch (self);
|
||||
}
|
||||
|
||||
void
|
||||
gst_clapper_mpris_set_media_info (GstClapperMpris *self, GstClapperMediaInfo *info)
|
||||
{
|
||||
g_mutex_lock (&self->lock);
|
||||
if (self->media_info)
|
||||
g_object_unref (self->media_info);
|
||||
self->media_info = info;
|
||||
self->parse_media_info = TRUE;
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
mpris_dispatcher_update_dispatch (self);
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_clapper_mpris_new:
|
||||
* @own_name: DBus own name
|
||||
* @id_path: DBus id path used for prefix
|
||||
* @identity: (allow-none): friendly name
|
||||
* @desktop_entry: (allow-none): Desktop entry filename
|
||||
* @default_art_url: (allow-none): filepath to default art
|
||||
*
|
||||
* Creates a new #GstClapperMpris instance.
|
||||
*
|
||||
* Returns: (transfer full): a new #GstClapperMpris instance
|
||||
*/
|
||||
GstClapperMpris *
|
||||
gst_clapper_mpris_new (const gchar * own_name, const gchar * id_path,
|
||||
const gchar * identity, const gchar * desktop_entry,
|
||||
const gchar * default_art_url)
|
||||
{
|
||||
return g_object_new (GST_TYPE_CLAPPER_MPRIS,
|
||||
"own-name", own_name, "id_path", id_path,
|
||||
"identity", identity, "desktop-entry", desktop_entry,
|
||||
"default-art-url", default_art_url, NULL);
|
||||
}
|
54
lib/gst/clapper/gstclapper-mpris.h
Normal file
54
lib/gst/clapper/gstclapper-mpris.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_CLAPPER_MPRIS_H__
|
||||
#define __GST_CLAPPER_MPRIS_H__
|
||||
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include <gst/clapper/clapper-prelude.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _GstClapperMpris GstClapperMpris;
|
||||
typedef struct _GstClapperMprisClass GstClapperMprisClass;
|
||||
|
||||
#define GST_TYPE_CLAPPER_MPRIS (gst_clapper_mpris_get_type ())
|
||||
#define GST_IS_CLAPPER_MPRIS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_MPRIS))
|
||||
#define GST_IS_CLAPPER_MPRIS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_MPRIS))
|
||||
#define GST_CLAPPER_MPRIS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_MPRIS, GstClapperMprisClass))
|
||||
#define GST_CLAPPER_MPRIS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_MPRIS, GstClapperMpris))
|
||||
#define GST_CLAPPER_MPRIS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_MPRIS, GstClapperMprisClass))
|
||||
#define GST_CLAPPER_MPRIS_CAST(obj) ((GstClapperMpris*)(obj))
|
||||
|
||||
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstClapperMpris, g_object_unref)
|
||||
#endif
|
||||
|
||||
GST_CLAPPER_API
|
||||
GType gst_clapper_mpris_get_type (void);
|
||||
|
||||
GST_CLAPPER_API
|
||||
GstClapperMpris * gst_clapper_mpris_new (const gchar *own_name, const gchar *id_path, const gchar *identity,
|
||||
const gchar *desktop_entry, const gchar *default_art_url);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_CLAPPER_MPRIS_H__ */
|
497
lib/gst/clapper/gstclapper.c
vendored
497
lib/gst/clapper/gstclapper.c
vendored
@@ -46,10 +46,12 @@
|
||||
#include "gstclapper-signal-dispatcher-private.h"
|
||||
#include "gstclapper-video-renderer-private.h"
|
||||
#include "gstclapper-media-info-private.h"
|
||||
#include "gstclapper-mpris-private.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_clapper_debug);
|
||||
#define GST_CAT_DEFAULT gst_clapper_debug
|
||||
|
||||
#define DEFAULT_STATE GST_CLAPPER_STATE_STOPPED
|
||||
#define DEFAULT_URI NULL
|
||||
#define DEFAULT_POSITION GST_CLOCK_TIME_NONE
|
||||
#define DEFAULT_DURATION GST_CLOCK_TIME_NONE
|
||||
@@ -75,6 +77,8 @@ enum
|
||||
PROP_0,
|
||||
PROP_VIDEO_RENDERER,
|
||||
PROP_SIGNAL_DISPATCHER,
|
||||
PROP_MPRIS,
|
||||
PROP_STATE,
|
||||
PROP_URI,
|
||||
PROP_SUBURI,
|
||||
PROP_POSITION,
|
||||
@@ -106,6 +110,7 @@ enum
|
||||
SIGNAL_ERROR,
|
||||
SIGNAL_WARNING,
|
||||
SIGNAL_VIDEO_DIMENSIONS_CHANGED,
|
||||
SIGNAL_MEDIA_INFO_UPDATED,
|
||||
SIGNAL_MUTE_CHANGED,
|
||||
SIGNAL_LAST
|
||||
};
|
||||
@@ -124,6 +129,7 @@ struct _GstClapper
|
||||
|
||||
GstClapperVideoRenderer *video_renderer;
|
||||
GstClapperSignalDispatcher *signal_dispatcher;
|
||||
GstClapperMpris *mpris;
|
||||
|
||||
gchar *uri;
|
||||
gchar *redirect_uri;
|
||||
@@ -138,7 +144,7 @@ struct _GstClapper
|
||||
GstElement *playbin;
|
||||
GstBus *bus;
|
||||
GstState target_state, current_state;
|
||||
gboolean is_live, is_eos;
|
||||
gboolean is_live;
|
||||
GSource *tick_source, *ready_timeout_source;
|
||||
GstClockTime cached_duration;
|
||||
|
||||
@@ -168,6 +174,13 @@ struct _GstClapper
|
||||
* is emitted after gst_clapper_stop/pause() has been called by the user. */
|
||||
gboolean inhibit_sigs;
|
||||
|
||||
/* If TRUE, player is in initial ready state after
|
||||
* new media was loaded and it can be played */
|
||||
gboolean can_start;
|
||||
|
||||
/* If should emit media info updated signal */
|
||||
gboolean needs_info_update;
|
||||
|
||||
/* For playbin3 */
|
||||
gboolean use_playbin3;
|
||||
GstStreamCollection *collection;
|
||||
@@ -263,6 +276,9 @@ gst_clapper_init (GstClapper * self)
|
||||
self->seek_position = GST_CLOCK_TIME_NONE;
|
||||
self->last_seek_time = GST_CLOCK_TIME_NONE;
|
||||
self->inhibit_sigs = FALSE;
|
||||
self->needs_info_update = FALSE;
|
||||
self->can_start = FALSE;
|
||||
self->app_state = GST_CLAPPER_STATE_STOPPED;
|
||||
|
||||
GST_TRACE_OBJECT (self, "Initialized");
|
||||
}
|
||||
@@ -292,6 +308,18 @@ gst_clapper_class_init (GstClapperClass * klass)
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
param_specs[PROP_MPRIS] =
|
||||
g_param_spec_object ("mpris",
|
||||
"MPRIS", "Clapper MPRIS for playback control over DBus",
|
||||
GST_TYPE_CLAPPER_MPRIS,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
param_specs[PROP_STATE] =
|
||||
g_param_spec_enum ("state", "Clapper State", "Current player state",
|
||||
GST_TYPE_CLAPPER_STATE, DEFAULT_STATE, G_PARAM_READABLE |
|
||||
G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
param_specs[PROP_URI] = g_param_spec_string ("uri", "URI", "Current URI",
|
||||
DEFAULT_URI, G_PARAM_READWRITE |
|
||||
G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
@@ -421,6 +449,11 @@ gst_clapper_class_init (GstClapperClass * klass)
|
||||
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
|
||||
NULL, NULL, G_TYPE_NONE, 1, G_TYPE_ERROR);
|
||||
|
||||
signals[SIGNAL_MEDIA_INFO_UPDATED] =
|
||||
g_signal_new ("media-info-updated", G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
|
||||
NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_CLAPPER_MEDIA_INFO);
|
||||
|
||||
signals[SIGNAL_VIDEO_DIMENSIONS_CHANGED] =
|
||||
g_signal_new ("video-dimensions-changed", G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL,
|
||||
@@ -468,7 +501,7 @@ gst_clapper_finalize (GObject * object)
|
||||
{
|
||||
GstClapper *self = GST_CLAPPER (object);
|
||||
|
||||
GST_TRACE_OBJECT (self, "Finalizing");
|
||||
GST_TRACE_OBJECT (self, "Finalize");
|
||||
|
||||
g_free (self->uri);
|
||||
g_free (self->redirect_uri);
|
||||
@@ -484,6 +517,8 @@ gst_clapper_finalize (GObject * object)
|
||||
g_object_unref (self->video_renderer);
|
||||
if (self->signal_dispatcher)
|
||||
g_object_unref (self->signal_dispatcher);
|
||||
if (self->mpris)
|
||||
g_object_unref (self->mpris);
|
||||
if (self->current_vis_element)
|
||||
gst_object_unref (self->current_vis_element);
|
||||
if (self->collection)
|
||||
@@ -540,10 +575,10 @@ gst_clapper_set_uri_internal (gpointer user_data)
|
||||
gst_clapper_stop_internal (self, FALSE);
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Changing URI to '%s'", GST_STR_NULL (self->uri));
|
||||
|
||||
g_object_set (self->playbin, "uri", self->uri, NULL);
|
||||
g_object_set (self->playbin, "suburi", NULL, NULL);
|
||||
self->can_start = TRUE;
|
||||
|
||||
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
|
||||
signals[SIGNAL_URI_LOADED], 0, NULL, NULL, NULL) != 0) {
|
||||
@@ -556,8 +591,6 @@ gst_clapper_set_uri_internal (gpointer user_data)
|
||||
(GDestroyNotify) uri_loaded_signal_data_free);
|
||||
}
|
||||
|
||||
g_object_set (self->playbin, "suburi", NULL, NULL);
|
||||
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
@@ -628,6 +661,9 @@ gst_clapper_set_property (GObject * object, guint prop_id,
|
||||
case PROP_SIGNAL_DISPATCHER:
|
||||
self->signal_dispatcher = g_value_dup_object (value);
|
||||
break;
|
||||
case PROP_MPRIS:
|
||||
self->mpris = g_value_dup_object (value);
|
||||
break;
|
||||
case PROP_URI:{
|
||||
g_mutex_lock (&self->lock);
|
||||
g_free (self->uri);
|
||||
@@ -720,6 +756,16 @@ gst_clapper_get_property (GObject * object, guint prop_id,
|
||||
GstClapper *self = GST_CLAPPER (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_MPRIS:
|
||||
g_mutex_lock (&self->lock);
|
||||
g_value_set_object (value, self->mpris);
|
||||
g_mutex_unlock (&self->lock);
|
||||
break;
|
||||
case PROP_STATE:
|
||||
g_mutex_lock (&self->lock);
|
||||
g_value_set_enum (value, self->app_state);
|
||||
g_mutex_unlock (&self->lock);
|
||||
break;
|
||||
case PROP_URI:
|
||||
g_mutex_lock (&self->lock);
|
||||
g_value_set_string (value, self->uri);
|
||||
@@ -838,6 +884,49 @@ main_loop_running_cb (gpointer user_data)
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstClapper *clapper;
|
||||
GstClapperMediaInfo *info;
|
||||
} MediaInfoUpdatedSignalData;
|
||||
|
||||
static void
|
||||
media_info_updated_dispatch (gpointer user_data)
|
||||
{
|
||||
MediaInfoUpdatedSignalData *data = user_data;
|
||||
|
||||
if (data->clapper->inhibit_sigs)
|
||||
return;
|
||||
|
||||
if (data->clapper->target_state >= GST_STATE_PAUSED) {
|
||||
g_signal_emit (data->clapper, signals[SIGNAL_MEDIA_INFO_UPDATED], 0,
|
||||
data->info);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
free_media_info_updated_signal_data (MediaInfoUpdatedSignalData * data)
|
||||
{
|
||||
g_object_unref (data->clapper);
|
||||
g_object_unref (data->info);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static void
|
||||
emit_media_info_updated (GstClapper * self)
|
||||
{
|
||||
MediaInfoUpdatedSignalData *data = g_new (MediaInfoUpdatedSignalData, 1);
|
||||
self->needs_info_update = FALSE;
|
||||
data->clapper = g_object_ref (self);
|
||||
g_mutex_lock (&self->lock);
|
||||
data->info = gst_clapper_media_info_copy (self->media_info);
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self,
|
||||
media_info_updated_dispatch, data,
|
||||
(GDestroyNotify) free_media_info_updated_signal_data);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstClapper *clapper;
|
||||
@@ -893,9 +982,12 @@ change_state (GstClapper * self, GstClapperState state)
|
||||
gst_clapper_state_get_name (state));
|
||||
self->app_state = state;
|
||||
|
||||
if (state == GST_CLAPPER_STATE_STOPPED && self->rate != 1.0) {
|
||||
self->rate = 1.0;
|
||||
emit_rate_notify (self);
|
||||
if (state == GST_CLAPPER_STATE_STOPPED) {
|
||||
self->needs_info_update = FALSE;
|
||||
if (self->rate != 1.0) {
|
||||
self->rate = 1.0;
|
||||
emit_rate_notify (self);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
|
||||
@@ -908,6 +1000,23 @@ change_state (GstClapper * self, GstClapperState state)
|
||||
state_changed_dispatch, data,
|
||||
(GDestroyNotify) state_changed_signal_data_free);
|
||||
}
|
||||
|
||||
if (!self->mpris)
|
||||
return;
|
||||
|
||||
switch (state) {
|
||||
case GST_CLAPPER_STATE_STOPPED:
|
||||
gst_clapper_mpris_set_playback_status (self->mpris, "Stopped");
|
||||
break;
|
||||
case GST_CLAPPER_STATE_PAUSED:
|
||||
gst_clapper_mpris_set_playback_status (self->mpris, "Paused");
|
||||
break;
|
||||
case GST_CLAPPER_STATE_PLAYING:
|
||||
gst_clapper_mpris_set_playback_status (self->mpris, "Playing");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct
|
||||
@@ -959,6 +1068,8 @@ tick_cb (gpointer user_data)
|
||||
position_updated_dispatch, data,
|
||||
(GDestroyNotify) position_updated_signal_data_free);
|
||||
}
|
||||
if (self->mpris)
|
||||
gst_clapper_mpris_set_position (self->mpris, position);
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
@@ -1073,7 +1184,6 @@ emit_error (GstClapper * self, GError * err)
|
||||
self->target_state = GST_STATE_NULL;
|
||||
self->current_state = GST_STATE_NULL;
|
||||
self->is_live = FALSE;
|
||||
self->is_eos = FALSE;
|
||||
gst_element_set_state (self->playbin, GST_STATE_NULL);
|
||||
change_state (self, GST_CLAPPER_STATE_STOPPED);
|
||||
self->buffering = 100;
|
||||
@@ -1255,14 +1365,13 @@ eos_cb (G_GNUC_UNUSED GstBus * bus, G_GNUC_UNUSED GstMessage * msg,
|
||||
tick_cb (self);
|
||||
remove_tick_source (self);
|
||||
|
||||
/* When connected client should handle what to do (stop/repeat) */
|
||||
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
|
||||
signals[SIGNAL_END_OF_STREAM], 0, NULL, NULL, NULL) != 0) {
|
||||
gst_clapper_signal_dispatcher_dispatch (self->signal_dispatcher, self,
|
||||
eos_dispatch, g_object_ref (self), (GDestroyNotify) g_object_unref);
|
||||
}
|
||||
change_state (self, GST_CLAPPER_STATE_STOPPED);
|
||||
self->buffering = 100;
|
||||
self->is_eos = TRUE;
|
||||
} else
|
||||
gst_clapper_stop_internal (self, FALSE);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
@@ -1468,7 +1577,14 @@ notify_caps_cb (G_GNUC_UNUSED GObject * object,
|
||||
{
|
||||
GstClapper *self = GST_CLAPPER (user_data);
|
||||
|
||||
check_video_dimensions_changed (self);
|
||||
if (self->target_state >= GST_STATE_PAUSED) {
|
||||
check_video_dimensions_changed (self);
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
if (self->media_info != NULL)
|
||||
self->needs_info_update = TRUE;
|
||||
g_mutex_unlock (&self->lock);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct
|
||||
@@ -1501,7 +1617,8 @@ duration_changed_signal_data_free (DurationChangedSignalData * data)
|
||||
static void
|
||||
emit_duration_changed (GstClapper * self, GstClockTime duration)
|
||||
{
|
||||
if (self->cached_duration == duration)
|
||||
if (self->cached_duration == duration
|
||||
|| self->cached_duration / (250 * GST_MSECOND) == duration / (250 * GST_MSECOND))
|
||||
return;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Duration changed %" GST_TIME_FORMAT,
|
||||
@@ -1568,6 +1685,16 @@ state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
|
||||
} else {
|
||||
self->cached_duration = GST_CLOCK_TIME_NONE;
|
||||
}
|
||||
emit_media_info_updated (self);
|
||||
if (self->mpris) {
|
||||
GstClapperMediaInfo *info;
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
info = gst_clapper_media_info_copy (self->media_info);
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
gst_clapper_mpris_set_media_info (self->mpris, info);
|
||||
}
|
||||
}
|
||||
|
||||
if (new_state == GST_STATE_PAUSED
|
||||
@@ -1679,8 +1806,12 @@ request_state_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
|
||||
static void
|
||||
media_info_update (GstClapper * self, GstClapperMediaInfo * info)
|
||||
{
|
||||
g_free (info->title);
|
||||
info->title = get_from_tags (self, info, get_title);
|
||||
/* Update title from new tags or leave the title from URI */
|
||||
gchar *tags_title = get_from_tags (self, info, get_title);
|
||||
if (tags_title) {
|
||||
g_free (info->title);
|
||||
info->title = tags_title;
|
||||
}
|
||||
|
||||
g_free (info->container);
|
||||
info->container = get_from_tags (self, info, get_container_format);
|
||||
@@ -1812,6 +1943,27 @@ element_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
qos_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
|
||||
{
|
||||
GstClapper *self = GST_CLAPPER (user_data);
|
||||
gboolean live;
|
||||
guint64 running_time, stream_time, timestamp, duration;
|
||||
|
||||
gst_message_parse_qos (msg, &live, &running_time, &stream_time,
|
||||
×tamp, &duration);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "QOS dropped buffer"
|
||||
", element live: %s"
|
||||
", running time: %" GST_TIME_FORMAT
|
||||
", stream time: %" GST_TIME_FORMAT
|
||||
", timestamp: %" GST_TIME_FORMAT
|
||||
", duration: %" GST_TIME_FORMAT,
|
||||
live ? "yes" : "no", GST_TIME_ARGS (running_time),
|
||||
GST_TIME_ARGS (stream_time), GST_TIME_ARGS (timestamp),
|
||||
GST_TIME_ARGS (duration));
|
||||
}
|
||||
|
||||
/* Must be called with lock */
|
||||
static gboolean
|
||||
update_stream_collection (GstClapper * self, GstStreamCollection * collection)
|
||||
@@ -1911,6 +2063,16 @@ streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
|
||||
g_mutex_unlock (&self->lock);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
clapper_get_has_flag (GstClapper * self, gint pos)
|
||||
{
|
||||
gint flags;
|
||||
|
||||
g_object_get (self->playbin, "flags", &flags, NULL);
|
||||
|
||||
return (flags & pos) == pos;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_set_flag (GstClapper * self, gint pos)
|
||||
{
|
||||
@@ -1965,11 +2127,14 @@ gst_clapper_subtitle_info_update (GstClapper * self,
|
||||
{
|
||||
GstClapperSubtitleInfo *info = (GstClapperSubtitleInfo *) stream_info;
|
||||
|
||||
if (stream_info->tags) {
|
||||
/* Free the old info */
|
||||
g_free (info->title);
|
||||
info->title = NULL;
|
||||
g_free (info->language);
|
||||
info->language = NULL;
|
||||
|
||||
/* free the old language info */
|
||||
g_free (info->language);
|
||||
info->language = NULL;
|
||||
if (stream_info->tags) {
|
||||
gst_tag_list_get_string (stream_info->tags, GST_TAG_TITLE, &info->title);
|
||||
|
||||
/* First try to get the language full name from tag, if name is not
|
||||
* available then try language code. If we find the language code
|
||||
@@ -2011,13 +2176,10 @@ gst_clapper_subtitle_info_update (GstClapper * self,
|
||||
g_free (suburi);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
g_free (info->language);
|
||||
info->language = NULL;
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (self, "language=%s", info->language);
|
||||
GST_DEBUG_OBJECT (self, "Subtitle title: %s", info->title);
|
||||
GST_DEBUG_OBJECT (self, "Subtitle language: %s", info->language);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -2209,19 +2371,6 @@ gst_clapper_stream_info_find_from_stream_id (GstClapperMediaInfo * media_info,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
is_track_enabled (GstClapper * self, gint pos)
|
||||
{
|
||||
gint flags;
|
||||
|
||||
g_object_get (G_OBJECT (self->playbin), "flags", &flags, NULL);
|
||||
|
||||
if ((flags & pos))
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GstClapperStreamInfo *
|
||||
gst_clapper_stream_info_get_current (GstClapper * self, const gchar * prop,
|
||||
GType type)
|
||||
@@ -2269,6 +2418,7 @@ stream_notify_cb (GstStreamCollection * collection, GstStream * stream,
|
||||
{
|
||||
GstClapperStreamInfo *info;
|
||||
const gchar *stream_id;
|
||||
gboolean emit_update = FALSE;
|
||||
|
||||
if (!self->media_info)
|
||||
return;
|
||||
@@ -2281,9 +2431,14 @@ stream_notify_cb (GstStreamCollection * collection, GstStream * stream,
|
||||
g_mutex_lock (&self->lock);
|
||||
info =
|
||||
gst_clapper_stream_info_find_from_stream_id (self->media_info, stream_id);
|
||||
if (info)
|
||||
if (info) {
|
||||
gst_clapper_stream_info_update_from_stream (self, info, stream);
|
||||
emit_update = (self->needs_info_update && GST_IS_CLAPPER_VIDEO_INFO (info));
|
||||
}
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
if (emit_update)
|
||||
emit_media_info_updated (self);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -2523,6 +2678,32 @@ subtitle_changed_cb (G_GNUC_UNUSED GObject * object, gpointer user_data)
|
||||
g_mutex_unlock (&self->lock);
|
||||
}
|
||||
|
||||
static gchar *
|
||||
get_title_from_uri (const gchar * uri)
|
||||
{
|
||||
gchar *proto = gst_uri_get_protocol (uri);
|
||||
gchar *title = NULL;
|
||||
|
||||
if (strcmp (proto, "file") == 0) {
|
||||
const gchar *ext = strrchr (uri, '.');
|
||||
if (ext && strlen (ext) < 8) {
|
||||
gchar *filename = g_filename_from_uri (uri, NULL, NULL);
|
||||
if (filename) {
|
||||
gchar *base = g_path_get_basename (filename);
|
||||
g_free (filename);
|
||||
title = g_strndup (base, strlen (base) - strlen (ext));
|
||||
g_free (base);
|
||||
}
|
||||
}
|
||||
} else if (strcmp (proto, "dvb") == 0) {
|
||||
const gchar *channel = strrchr (uri, '/') + 1;
|
||||
title = g_strdup (channel);
|
||||
}
|
||||
g_free (proto);
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
static void *
|
||||
get_title (GstTagList * tags)
|
||||
{
|
||||
@@ -2639,12 +2820,15 @@ gst_clapper_media_info_create (GstClapper * self)
|
||||
}
|
||||
|
||||
media_info->title = get_from_tags (self, media_info, get_title);
|
||||
if (!media_info->title)
|
||||
media_info->title = get_title_from_uri (self->uri);
|
||||
|
||||
media_info->container =
|
||||
get_from_tags (self, media_info, get_container_format);
|
||||
media_info->image_sample = get_from_tags (self, media_info, get_cover_sample);
|
||||
|
||||
GST_DEBUG_OBJECT (self, "uri: %s title: %s duration: %" GST_TIME_FORMAT
|
||||
" seekable: %s live: %s container: %s image_sample %p",
|
||||
GST_DEBUG_OBJECT (self, "uri: %s, title: %s, duration: %" GST_TIME_FORMAT
|
||||
", seekable: %s, live: %s, container: %s, image_sample %p",
|
||||
media_info->uri, media_info->title, GST_TIME_ARGS (media_info->duration),
|
||||
media_info->seekable ? "yes" : "no", media_info->is_live ? "yes" : "no",
|
||||
media_info->container, media_info->image_sample);
|
||||
@@ -2672,8 +2856,13 @@ static void
|
||||
video_tags_changed_cb (G_GNUC_UNUSED GstElement * playbin, gint stream_index,
|
||||
gpointer user_data)
|
||||
{
|
||||
tags_changed_cb (GST_CLAPPER (user_data), stream_index,
|
||||
GstClapper *self = GST_CLAPPER (user_data);
|
||||
|
||||
tags_changed_cb (self, stream_index,
|
||||
GST_TYPE_CLAPPER_VIDEO_INFO);
|
||||
|
||||
if (self->needs_info_update)
|
||||
emit_media_info_updated (self);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -2741,9 +2930,30 @@ mute_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
|
||||
}
|
||||
|
||||
static void
|
||||
source_setup_cb (GstElement * playbin, GstElement * source, GstClapper * self)
|
||||
element_setup_cb (GstElement * playbin, GstElement * element, GstClapper * self)
|
||||
{
|
||||
GstElementFactory *factory;
|
||||
GParamSpec *prop;
|
||||
|
||||
factory = gst_element_get_factory (element);
|
||||
if (factory) {
|
||||
gchar *plugin_name = gst_object_get_name (GST_OBJECT_CAST (factory));
|
||||
if (plugin_name) {
|
||||
GST_INFO_OBJECT (self, "Plugin setup: %s", plugin_name);
|
||||
|
||||
/* TODO: Set plugin props */
|
||||
}
|
||||
g_free (plugin_name);
|
||||
}
|
||||
|
||||
prop = g_object_class_find_property (G_OBJECT_GET_CLASS (element), "user-agent");
|
||||
if (prop && prop->value_type == G_TYPE_STRING) {
|
||||
const gchar *user_agent =
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:86.0) Gecko/20100101 Firefox/86.0";
|
||||
|
||||
GST_INFO_OBJECT (self, "Setting element user-agent: %s", user_agent);
|
||||
g_object_set (element, "user-agent", user_agent, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static gpointer
|
||||
@@ -2787,12 +2997,23 @@ gst_clapper_main (gpointer data)
|
||||
GstElement *video_sink =
|
||||
gst_clapper_video_renderer_create_video_sink (self->video_renderer, self);
|
||||
if (video_sink) {
|
||||
const gchar *fps_env;
|
||||
GstPad *video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
|
||||
if (video_sink_pad) {
|
||||
g_signal_connect (video_sink_pad, "notify::caps",
|
||||
(GCallback) notify_caps_cb, self);
|
||||
gst_object_unref (video_sink_pad);
|
||||
}
|
||||
fps_env = g_getenv ("GST_CLAPPER_DISPLAY_FPS");
|
||||
if (fps_env && g_str_has_prefix (fps_env, "1")) {
|
||||
GstElement *fpsdisplaysink =
|
||||
gst_element_factory_make ("fpsdisplaysink", "fpsdisplaysink");
|
||||
if (fpsdisplaysink) {
|
||||
GST_DEBUG_OBJECT (self, "FPS display enabled");
|
||||
g_object_set (fpsdisplaysink, "video-sink", video_sink, NULL);
|
||||
video_sink = fpsdisplaysink;
|
||||
}
|
||||
}
|
||||
g_object_set (self->playbin, "video-sink", video_sink, NULL);
|
||||
}
|
||||
}
|
||||
@@ -2808,6 +3029,9 @@ gst_clapper_main (gpointer data)
|
||||
self->bus = bus = gst_element_get_bus (self->playbin);
|
||||
gst_bus_add_signal_watch (bus);
|
||||
|
||||
if (self->mpris)
|
||||
gst_clapper_mpris_set_clapper (self->mpris, self, self->signal_dispatcher);
|
||||
|
||||
g_signal_connect (G_OBJECT (bus), "message::error", G_CALLBACK (error_cb),
|
||||
self);
|
||||
g_signal_connect (G_OBJECT (bus), "message::warning", G_CALLBACK (warning_cb),
|
||||
@@ -2830,6 +3054,9 @@ gst_clapper_main (gpointer data)
|
||||
g_signal_connect (G_OBJECT (bus), "message::tag", G_CALLBACK (tags_cb), self);
|
||||
g_signal_connect (G_OBJECT (bus), "message::toc", G_CALLBACK (toc_cb), self);
|
||||
|
||||
if (gst_debug_category_get_threshold (gst_clapper_debug) >= GST_LEVEL_DEBUG)
|
||||
g_signal_connect (G_OBJECT (bus), "message::qos", G_CALLBACK (qos_cb), self);
|
||||
|
||||
if (self->use_playbin3) {
|
||||
g_signal_connect (G_OBJECT (bus), "message::stream-collection",
|
||||
G_CALLBACK (stream_collection_cb), self);
|
||||
@@ -2855,14 +3082,13 @@ gst_clapper_main (gpointer data)
|
||||
G_CALLBACK (volume_notify_cb), self);
|
||||
g_signal_connect (self->playbin, "notify::mute",
|
||||
G_CALLBACK (mute_notify_cb), self);
|
||||
g_signal_connect (self->playbin, "source-setup",
|
||||
G_CALLBACK (source_setup_cb), self);
|
||||
g_signal_connect (self->playbin, "element-setup",
|
||||
G_CALLBACK (element_setup_cb), self);
|
||||
|
||||
self->target_state = GST_STATE_NULL;
|
||||
self->current_state = GST_STATE_NULL;
|
||||
change_state (self, GST_CLAPPER_STATE_STOPPED);
|
||||
self->buffering = 100;
|
||||
self->is_eos = FALSE;
|
||||
self->is_live = FALSE;
|
||||
self->rate = 1.0;
|
||||
self->seek_mode = DEFAULT_SEEK_MODE;
|
||||
@@ -2906,6 +3132,7 @@ gst_clapper_main (gpointer data)
|
||||
* gst_clapper_new:
|
||||
* @video_renderer: (transfer full) (allow-none): GstClapperVideoRenderer to use
|
||||
* @signal_dispatcher: (transfer full) (allow-none): GstClapperSignalDispatcher to use
|
||||
* @mpris: (transfer full) (allow-none): GstClapperMpris to use
|
||||
*
|
||||
* Creates a new #GstClapper instance that uses @signal_dispatcher to dispatch
|
||||
* signals to some event loop system, or emits signals directly if NULL is
|
||||
@@ -2919,18 +3146,20 @@ gst_clapper_main (gpointer data)
|
||||
*/
|
||||
GstClapper *
|
||||
gst_clapper_new (GstClapperVideoRenderer * video_renderer,
|
||||
GstClapperSignalDispatcher * signal_dispatcher)
|
||||
GstClapperSignalDispatcher * signal_dispatcher,
|
||||
GstClapperMpris * mpris)
|
||||
{
|
||||
GstClapper *self;
|
||||
|
||||
self =
|
||||
g_object_new (GST_TYPE_CLAPPER, "video-renderer", video_renderer,
|
||||
"signal-dispatcher", signal_dispatcher, NULL);
|
||||
self = g_object_new (GST_TYPE_CLAPPER, "video-renderer", video_renderer,
|
||||
"signal-dispatcher", signal_dispatcher, "mpris", mpris, NULL);
|
||||
|
||||
if (video_renderer)
|
||||
g_object_unref (video_renderer);
|
||||
if (signal_dispatcher)
|
||||
g_object_unref (signal_dispatcher);
|
||||
if (mpris)
|
||||
g_object_unref (mpris);
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -2956,7 +3185,7 @@ gst_clapper_play_internal (gpointer user_data)
|
||||
if (self->current_state < GST_STATE_PAUSED)
|
||||
change_state (self, GST_CLAPPER_STATE_BUFFERING);
|
||||
|
||||
if (self->current_state >= GST_STATE_PAUSED && !self->is_eos
|
||||
if (self->current_state >= GST_STATE_PAUSED
|
||||
&& self->buffering >= 100 && !(self->seek_position != GST_CLOCK_TIME_NONE
|
||||
|| self->seek_pending)) {
|
||||
state_ret = gst_element_set_state (self->playbin, GST_STATE_PLAYING);
|
||||
@@ -2973,21 +3202,6 @@ gst_clapper_play_internal (gpointer user_data)
|
||||
GST_DEBUG_OBJECT (self, "Pipeline is live");
|
||||
}
|
||||
|
||||
if (self->is_eos) {
|
||||
gboolean ret;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Was EOS, seeking to beginning");
|
||||
self->is_eos = FALSE;
|
||||
ret =
|
||||
gst_element_seek_simple (self->playbin, GST_FORMAT_TIME,
|
||||
GST_SEEK_FLAG_FLUSH, 0);
|
||||
if (!ret) {
|
||||
GST_ERROR_OBJECT (self, "Seek to beginning failed");
|
||||
gst_clapper_stop_internal (self, TRUE);
|
||||
gst_clapper_play_internal (self);
|
||||
}
|
||||
}
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
@@ -3002,8 +3216,14 @@ gst_clapper_play (GstClapper * self)
|
||||
{
|
||||
g_return_if_fail (GST_IS_CLAPPER (self));
|
||||
|
||||
if (!self->can_start && self->app_state == GST_CLAPPER_STATE_STOPPED) {
|
||||
GST_DEBUG_OBJECT (self, "Player stopped, play request ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
self->inhibit_sigs = FALSE;
|
||||
self->can_start = FALSE;
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
|
||||
@@ -3044,21 +3264,6 @@ gst_clapper_pause_internal (gpointer user_data)
|
||||
GST_DEBUG_OBJECT (self, "Pipeline is live");
|
||||
}
|
||||
|
||||
if (self->is_eos) {
|
||||
gboolean ret;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Was EOS, seeking to beginning");
|
||||
self->is_eos = FALSE;
|
||||
ret =
|
||||
gst_element_seek_simple (self->playbin, GST_FORMAT_TIME,
|
||||
GST_SEEK_FLAG_FLUSH, 0);
|
||||
if (!ret) {
|
||||
GST_ERROR_OBJECT (self, "Seek to beginning failed");
|
||||
gst_clapper_stop_internal (self, TRUE);
|
||||
gst_clapper_pause_internal (self);
|
||||
}
|
||||
}
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
@@ -3073,15 +3278,40 @@ gst_clapper_pause (GstClapper * self)
|
||||
{
|
||||
g_return_if_fail (GST_IS_CLAPPER (self));
|
||||
|
||||
/* Do not try to pause on DVD navigation */
|
||||
if (G_LIKELY (self->cached_duration > 1000000000)) {
|
||||
g_mutex_lock (&self->lock);
|
||||
self->inhibit_sigs = FALSE;
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
|
||||
gst_clapper_pause_internal, self, NULL);
|
||||
if (self->app_state == GST_CLAPPER_STATE_STOPPED) {
|
||||
GST_DEBUG_OBJECT (self, "Player stopped, pause request ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
if (G_UNLIKELY (self->cached_duration <= GST_SECOND)) {
|
||||
GST_DEBUG_OBJECT (self, "Cannot pause on this stream");
|
||||
return;
|
||||
}
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
self->inhibit_sigs = FALSE;
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
g_main_context_invoke_full (self->context, G_PRIORITY_DEFAULT,
|
||||
gst_clapper_pause_internal, self, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_clapper_toggle_play:
|
||||
* @clapper: #GstClapper instance
|
||||
*
|
||||
* Toggle between play and pause on the loaded stream.
|
||||
* This function does nothing if player is stopped.
|
||||
*/
|
||||
void
|
||||
gst_clapper_toggle_play (GstClapper * self)
|
||||
{
|
||||
g_return_if_fail (GST_IS_CLAPPER (self));
|
||||
|
||||
if (self->app_state == GST_CLAPPER_STATE_PLAYING)
|
||||
gst_clapper_pause (self);
|
||||
else
|
||||
gst_clapper_play (self);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -3102,14 +3332,11 @@ gst_clapper_stop_internal (GstClapper * self, gboolean transient)
|
||||
self->target_state = GST_STATE_NULL;
|
||||
self->current_state = GST_STATE_READY;
|
||||
self->is_live = FALSE;
|
||||
self->is_eos = FALSE;
|
||||
gst_bus_set_flushing (self->bus, TRUE);
|
||||
gst_element_set_state (self->playbin, GST_STATE_READY);
|
||||
gst_bus_set_flushing (self->bus, FALSE);
|
||||
change_state (self, transient
|
||||
&& self->app_state !=
|
||||
GST_CLAPPER_STATE_STOPPED ? GST_CLAPPER_STATE_BUFFERING :
|
||||
GST_CLAPPER_STATE_STOPPED);
|
||||
change_state (self, transient && self->app_state != GST_CLAPPER_STATE_STOPPED
|
||||
? GST_CLAPPER_STATE_BUFFERING : GST_CLAPPER_STATE_STOPPED);
|
||||
self->buffering = 100;
|
||||
self->cached_duration = GST_CLOCK_TIME_NONE;
|
||||
g_mutex_lock (&self->lock);
|
||||
@@ -3213,7 +3440,6 @@ gst_clapper_seek_internal_locked (GstClapper * self)
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
remove_tick_source (self);
|
||||
self->is_eos = FALSE;
|
||||
|
||||
flags |= GST_SEEK_FLAG_FLUSH;
|
||||
|
||||
@@ -3353,6 +3579,29 @@ gst_clapper_seek (GstClapper * self, GstClockTime position)
|
||||
g_mutex_unlock (&self->lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_clapper_seek_offset:
|
||||
* @clapper: #GstClapper instance
|
||||
* @offset: offset from current position to seek to in nanoseconds
|
||||
*
|
||||
* Seeks the currently-playing stream to the @offset time
|
||||
* in nanoseconds.
|
||||
*/
|
||||
void
|
||||
gst_clapper_seek_offset (GstClapper * self, GstClockTime offset)
|
||||
{
|
||||
GstClockTime position;
|
||||
|
||||
g_return_if_fail (GST_IS_CLAPPER (self));
|
||||
g_return_if_fail (GST_CLOCK_TIME_IS_VALID (offset));
|
||||
|
||||
position = gst_clapper_get_position (self);
|
||||
|
||||
/* TODO: Prevent negative values */
|
||||
|
||||
gst_clapper_seek (self, position + offset);
|
||||
}
|
||||
|
||||
static void
|
||||
remove_seek_source (GstClapper * self)
|
||||
{
|
||||
@@ -3364,6 +3613,24 @@ remove_seek_source (GstClapper * self)
|
||||
self->seek_source = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_clapper_get_state:
|
||||
* @clapper: #GstClapper instance
|
||||
*
|
||||
* Returns: Current player state
|
||||
*/
|
||||
GstClapperState
|
||||
gst_clapper_get_state (GstClapper * self)
|
||||
{
|
||||
GstClapperState state;
|
||||
|
||||
g_return_val_if_fail (GST_IS_CLAPPER (self), DEFAULT_STATE);
|
||||
|
||||
g_object_get (self, "state", &state, NULL);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_clapper_get_uri:
|
||||
* @clapper: #GstClapper instance
|
||||
@@ -3567,6 +3834,28 @@ gst_clapper_get_pipeline (GstClapper * self)
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_clapper_get_mpris:
|
||||
* @clapper: #GstClapper instance
|
||||
*
|
||||
* A Function to get the #GstClapperMpris instance.
|
||||
*
|
||||
* Returns: (transfer full): mpris instance.
|
||||
*
|
||||
* The caller should free it with g_object_unref()
|
||||
*/
|
||||
GstClapperMpris *
|
||||
gst_clapper_get_mpris (GstClapper * self)
|
||||
{
|
||||
GstClapperMpris *val;
|
||||
|
||||
g_return_val_if_fail (GST_IS_CLAPPER (self), NULL);
|
||||
|
||||
g_object_get (self, "mpris", &val, NULL);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_clapper_get_media_info:
|
||||
* @clapper: #GstClapper instance
|
||||
@@ -3611,7 +3900,7 @@ gst_clapper_get_current_audio_track (GstClapper * self)
|
||||
|
||||
g_return_val_if_fail (GST_IS_CLAPPER (self), NULL);
|
||||
|
||||
if (!is_track_enabled (self, GST_PLAY_FLAG_AUDIO))
|
||||
if (!clapper_get_has_flag (self, GST_PLAY_FLAG_AUDIO))
|
||||
return NULL;
|
||||
|
||||
if (self->use_playbin3) {
|
||||
@@ -3643,7 +3932,7 @@ gst_clapper_get_current_video_track (GstClapper * self)
|
||||
|
||||
g_return_val_if_fail (GST_IS_CLAPPER (self), NULL);
|
||||
|
||||
if (!is_track_enabled (self, GST_PLAY_FLAG_VIDEO))
|
||||
if (!clapper_get_has_flag (self, GST_PLAY_FLAG_VIDEO))
|
||||
return NULL;
|
||||
|
||||
if (self->use_playbin3) {
|
||||
@@ -3675,7 +3964,7 @@ gst_clapper_get_current_subtitle_track (GstClapper * self)
|
||||
|
||||
g_return_val_if_fail (GST_IS_CLAPPER (self), NULL);
|
||||
|
||||
if (!is_track_enabled (self, GST_PLAY_FLAG_SUBTITLE))
|
||||
if (!clapper_get_has_flag (self, GST_PLAY_FLAG_SUBTITLE))
|
||||
return NULL;
|
||||
|
||||
if (self->use_playbin3) {
|
||||
@@ -3732,7 +4021,7 @@ gst_clapper_set_audio_track (GstClapper * self, gint stream_index)
|
||||
GstClapperStreamInfo *info;
|
||||
gboolean ret = TRUE;
|
||||
|
||||
g_return_val_if_fail (GST_IS_CLAPPER (self), 0);
|
||||
g_return_val_if_fail (GST_IS_CLAPPER (self), FALSE);
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
info = gst_clapper_stream_info_find (self->media_info,
|
||||
@@ -3773,7 +4062,7 @@ gst_clapper_set_video_track (GstClapper * self, gint stream_index)
|
||||
GstClapperStreamInfo *info;
|
||||
gboolean ret = TRUE;
|
||||
|
||||
g_return_val_if_fail (GST_IS_CLAPPER (self), 0);
|
||||
g_return_val_if_fail (GST_IS_CLAPPER (self), FALSE);
|
||||
|
||||
/* check if stream_index exist in our internal media_info list */
|
||||
g_mutex_lock (&self->lock);
|
||||
@@ -3815,7 +4104,7 @@ gst_clapper_set_subtitle_track (GstClapper * self, gint stream_index)
|
||||
GstClapperStreamInfo *info;
|
||||
gboolean ret = TRUE;
|
||||
|
||||
g_return_val_if_fail (GST_IS_CLAPPER (self), 0);
|
||||
g_return_val_if_fail (GST_IS_CLAPPER (self), FALSE);
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
info = gst_clapper_stream_info_find (self->media_info,
|
||||
@@ -3954,7 +4243,7 @@ gst_clapper_get_current_visualization (GstClapper * self)
|
||||
|
||||
g_return_val_if_fail (GST_IS_CLAPPER (self), NULL);
|
||||
|
||||
if (!is_track_enabled (self, GST_PLAY_FLAG_VIS))
|
||||
if (!clapper_get_has_flag (self, GST_PLAY_FLAG_VIS))
|
||||
return NULL;
|
||||
|
||||
g_object_get (self->playbin, "vis-plugin", &vis_plugin, NULL);
|
||||
|
18
lib/gst/clapper/gstclapper.h
vendored
18
lib/gst/clapper/gstclapper.h
vendored
@@ -30,6 +30,7 @@
|
||||
#include <gst/clapper/gstclapper-signal-dispatcher.h>
|
||||
#include <gst/clapper/gstclapper-video-renderer.h>
|
||||
#include <gst/clapper/gstclapper-media-info.h>
|
||||
#include <gst/clapper/gstclapper-mpris.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
@@ -153,7 +154,8 @@ GST_CLAPPER_API
|
||||
GType gst_clapper_get_type (void);
|
||||
|
||||
GST_CLAPPER_API
|
||||
GstClapper * gst_clapper_new (GstClapperVideoRenderer *video_renderer, GstClapperSignalDispatcher *signal_dispatcher);
|
||||
GstClapper * gst_clapper_new (GstClapperVideoRenderer *video_renderer, GstClapperSignalDispatcher *signal_dispatcher,
|
||||
GstClapperMpris *mpris);
|
||||
|
||||
GST_CLAPPER_API
|
||||
void gst_clapper_play (GstClapper *clapper);
|
||||
@@ -161,12 +163,22 @@ void gst_clapper_play (GstClapper *clapper
|
||||
GST_CLAPPER_API
|
||||
void gst_clapper_pause (GstClapper *clapper);
|
||||
|
||||
GST_CLAPPER_API
|
||||
void gst_clapper_toggle_play (GstClapper *clapper);
|
||||
|
||||
GST_CLAPPER_API
|
||||
void gst_clapper_stop (GstClapper *clapper);
|
||||
|
||||
GST_CLAPPER_API
|
||||
void gst_clapper_seek (GstClapper *clapper, GstClockTime position);
|
||||
|
||||
GST_CLAPPER_API
|
||||
void gst_clapper_seek_offset (GstClapper *clapper, GstClockTime offset);
|
||||
|
||||
GST_CLAPPER_API
|
||||
GstClapperState
|
||||
gst_clapper_get_state (GstClapper *clapper);
|
||||
|
||||
GST_CLAPPER_API
|
||||
GstClapperSeekMode
|
||||
gst_clapper_get_seek_mode (GstClapper *clapper);
|
||||
@@ -213,6 +225,10 @@ void gst_clapper_set_mute (GstClapper *clapper
|
||||
GST_CLAPPER_API
|
||||
GstElement * gst_clapper_get_pipeline (GstClapper *clapper);
|
||||
|
||||
GST_CLAPPER_API
|
||||
GstClapperMpris *
|
||||
gst_clapper_get_mpris (GstClapper *clapper);
|
||||
|
||||
GST_CLAPPER_API
|
||||
void gst_clapper_set_video_track_enabled (GstClapper *clapper, gboolean enabled);
|
||||
|
||||
|
745
lib/gst/clapper/gtk4/gstclapperglsink.c
Normal file
745
lib/gst/clapper/gtk4/gstclapperglsink.c
Normal file
@@ -0,0 +1,745 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2020 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:gstclapperglsink
|
||||
* @title: GstClapperGLSink
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
|
||||
#include "gstclapperglsink.h"
|
||||
#include "gstgtkutils.h"
|
||||
|
||||
GST_DEBUG_CATEGORY (gst_debug_clapper_gl_sink);
|
||||
#define GST_CAT_DEFAULT gst_debug_clapper_gl_sink
|
||||
|
||||
static GstStaticPadTemplate gst_clapper_gl_sink_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
|
||||
(GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA") "; "
|
||||
GST_VIDEO_CAPS_MAKE_WITH_FEATURES
|
||||
(GST_CAPS_FEATURE_MEMORY_GL_MEMORY ", "
|
||||
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, "RGBA")));
|
||||
|
||||
static void gst_clapper_gl_sink_finalize (GObject * object);
|
||||
static void gst_clapper_gl_sink_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * param_spec);
|
||||
static void gst_clapper_gl_sink_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * param_spec);
|
||||
|
||||
static gboolean gst_clapper_gl_sink_propose_allocation (GstBaseSink * bsink,
|
||||
GstQuery * query);
|
||||
static gboolean gst_clapper_gl_sink_query (GstBaseSink * bsink, GstQuery * query);
|
||||
static gboolean gst_clapper_gl_sink_start (GstBaseSink * bsink);
|
||||
static gboolean gst_clapper_gl_sink_stop (GstBaseSink * bsink);
|
||||
static GstFlowReturn gst_clapper_gl_sink_wait_event (GstBaseSink * bsink, GstEvent * event);
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_clapper_gl_sink_change_state (GstElement * element,
|
||||
GstStateChange transition);
|
||||
|
||||
static void gst_clapper_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
|
||||
GstClockTime * start, GstClockTime * end);
|
||||
static GstCaps *gst_clapper_gl_sink_get_caps (GstBaseSink * bsink,
|
||||
GstCaps * filter);
|
||||
static gboolean gst_clapper_gl_sink_set_caps (GstBaseSink * bsink,
|
||||
GstCaps * caps);
|
||||
static GstFlowReturn gst_clapper_gl_sink_show_frame (GstVideoSink * bsink,
|
||||
GstBuffer * buf);
|
||||
|
||||
static void
|
||||
gst_clapper_gl_sink_navigation_interface_init (GstNavigationInterface * iface);
|
||||
|
||||
#define gst_clapper_gl_sink_parent_class parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (GstClapperGLSink, gst_clapper_gl_sink,
|
||||
GST_TYPE_VIDEO_SINK,
|
||||
G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
|
||||
gst_clapper_gl_sink_navigation_interface_init);
|
||||
GST_DEBUG_CATEGORY_INIT (gst_debug_clapper_gl_sink,
|
||||
"clapperglsink", 0, "Clapper GL Sink"));
|
||||
|
||||
static void
|
||||
gst_clapper_gl_sink_class_init (GstClapperGLSinkClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
GstBaseSinkClass *gstbasesink_class;
|
||||
GstVideoSinkClass *gstvideosink_class;
|
||||
GstClapperGLSinkClass *gstclapperglsink_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
gstbasesink_class = (GstBaseSinkClass *) klass;
|
||||
gstvideosink_class = (GstVideoSinkClass *) klass;
|
||||
gstclapperglsink_class = (GstClapperGLSinkClass *) klass;
|
||||
|
||||
gobject_class->set_property = gst_clapper_gl_sink_set_property;
|
||||
gobject_class->get_property = gst_clapper_gl_sink_get_property;
|
||||
gobject_class->finalize = gst_clapper_gl_sink_finalize;
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_WIDGET,
|
||||
g_param_spec_object ("widget", "GTK Widget",
|
||||
"The GtkWidget to place in the widget hierarchy "
|
||||
"(must only be get from the GTK main thread)",
|
||||
GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
gst_gtk_install_shared_properties (gobject_class);
|
||||
|
||||
gstelement_class->change_state = gst_clapper_gl_sink_change_state;
|
||||
|
||||
gstbasesink_class->get_caps = gst_clapper_gl_sink_get_caps;
|
||||
gstbasesink_class->set_caps = gst_clapper_gl_sink_set_caps;
|
||||
gstbasesink_class->get_times = gst_clapper_gl_sink_get_times;
|
||||
gstbasesink_class->propose_allocation = gst_clapper_gl_sink_propose_allocation;
|
||||
gstbasesink_class->query = gst_clapper_gl_sink_query;
|
||||
gstbasesink_class->start = gst_clapper_gl_sink_start;
|
||||
gstbasesink_class->stop = gst_clapper_gl_sink_stop;
|
||||
gstbasesink_class->wait_event = gst_clapper_gl_sink_wait_event;
|
||||
|
||||
gstvideosink_class->show_frame = gst_clapper_gl_sink_show_frame;
|
||||
|
||||
gstclapperglsink_class->create_widget = gtk_clapper_gl_widget_new;
|
||||
gstclapperglsink_class->window_title = "GTK4 GL Renderer";
|
||||
|
||||
gst_element_class_set_metadata (gstelement_class,
|
||||
"GTK4 GL Video Sink",
|
||||
"Sink/Video", "A video sink that renders to a GtkWidget using OpenGL",
|
||||
"Matthew Waters <matthew@centricular.com>, "
|
||||
"Rafał Dzięgiel <rafostar.github@gmail.com>");
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&gst_clapper_gl_sink_template);
|
||||
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_CLAPPER_GL_SINK, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_gl_sink_init (GstClapperGLSink * clapper_sink)
|
||||
{
|
||||
clapper_sink->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
|
||||
clapper_sink->par_n = DEFAULT_PAR_N;
|
||||
clapper_sink->par_d = DEFAULT_PAR_D;
|
||||
clapper_sink->keep_last_frame = DEFAULT_KEEP_LAST_FRAME;
|
||||
|
||||
clapper_sink->had_eos = FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_gl_sink_finalize (GObject * object)
|
||||
{
|
||||
GstClapperGLSink *clapper_sink = GST_CLAPPER_GL_SINK (object);
|
||||
|
||||
GST_DEBUG ("Finalizing Clapper GL sink");
|
||||
|
||||
GST_OBJECT_LOCK (clapper_sink);
|
||||
if (clapper_sink->window && clapper_sink->window_destroy_id)
|
||||
g_signal_handler_disconnect (clapper_sink->window, clapper_sink->window_destroy_id);
|
||||
if (clapper_sink->widget && clapper_sink->widget_destroy_id)
|
||||
g_signal_handler_disconnect (clapper_sink->widget, clapper_sink->widget_destroy_id);
|
||||
|
||||
g_clear_object (&clapper_sink->widget);
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
widget_destroy_cb (GtkWidget * widget, GstClapperGLSink * clapper_sink)
|
||||
{
|
||||
GST_OBJECT_LOCK (clapper_sink);
|
||||
g_clear_object (&clapper_sink->widget);
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
}
|
||||
|
||||
static void
|
||||
window_destroy_cb (GtkWidget * widget, GstClapperGLSink * clapper_sink)
|
||||
{
|
||||
GST_OBJECT_LOCK (clapper_sink);
|
||||
if (clapper_sink->widget) {
|
||||
if (clapper_sink->widget_destroy_id) {
|
||||
g_signal_handler_disconnect (clapper_sink->widget,
|
||||
clapper_sink->widget_destroy_id);
|
||||
clapper_sink->widget_destroy_id = 0;
|
||||
}
|
||||
g_clear_object (&clapper_sink->widget);
|
||||
}
|
||||
clapper_sink->window = NULL;
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
}
|
||||
|
||||
static GtkClapperGLWidget *
|
||||
gst_clapper_gl_sink_get_widget (GstClapperGLSink * clapper_sink)
|
||||
{
|
||||
if (clapper_sink->widget != NULL)
|
||||
return clapper_sink->widget;
|
||||
|
||||
/* Ensure GTK is initialized, this has no side effect if it was already
|
||||
* initialized. Also, we do that lazily, so the application can be first */
|
||||
if (!gtk_init_check ()) {
|
||||
GST_ERROR_OBJECT (clapper_sink, "Could not ensure GTK initialization.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_assert (GST_CLAPPER_GL_SINK_GET_CLASS (clapper_sink)->create_widget);
|
||||
clapper_sink->widget = (GtkClapperGLWidget *)
|
||||
GST_CLAPPER_GL_SINK_GET_CLASS (clapper_sink)->create_widget ();
|
||||
|
||||
g_object_bind_property (clapper_sink, "force-aspect-ratio", clapper_sink->widget,
|
||||
"force-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
g_object_bind_property (clapper_sink, "pixel-aspect-ratio", clapper_sink->widget,
|
||||
"pixel-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
g_object_bind_property (clapper_sink, "keep-last-frame", clapper_sink->widget,
|
||||
"keep-last-frame", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
|
||||
/* Take the floating ref, other wise the destruction of the container will
|
||||
* make this widget disappear possibly before we are done. */
|
||||
gst_object_ref_sink (clapper_sink->widget);
|
||||
|
||||
clapper_sink->widget_destroy_id = g_signal_connect (clapper_sink->widget, "destroy",
|
||||
G_CALLBACK (widget_destroy_cb), clapper_sink);
|
||||
|
||||
/* back pointer */
|
||||
gtk_clapper_gl_widget_set_element (GTK_CLAPPER_GL_WIDGET (clapper_sink->widget),
|
||||
GST_ELEMENT (clapper_sink));
|
||||
|
||||
return clapper_sink->widget;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_gl_sink_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstClapperGLSink *clapper_sink = GST_CLAPPER_GL_SINK (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_WIDGET:
|
||||
{
|
||||
GObject *widget = NULL;
|
||||
|
||||
GST_OBJECT_LOCK (clapper_sink);
|
||||
if (clapper_sink->widget != NULL)
|
||||
widget = G_OBJECT (clapper_sink->widget);
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
|
||||
if (!widget)
|
||||
widget =
|
||||
gst_gtk_invoke_on_main ((GThreadFunc) gst_clapper_gl_sink_get_widget,
|
||||
clapper_sink);
|
||||
|
||||
g_value_set_object (value, widget);
|
||||
break;
|
||||
}
|
||||
case PROP_FORCE_ASPECT_RATIO:
|
||||
g_value_set_boolean (value, clapper_sink->force_aspect_ratio);
|
||||
break;
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
gst_value_set_fraction (value, clapper_sink->par_n, clapper_sink->par_d);
|
||||
break;
|
||||
case PROP_KEEP_LAST_FRAME:
|
||||
g_value_set_boolean (value, clapper_sink->keep_last_frame);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_gl_sink_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstClapperGLSink *clapper_sink = GST_CLAPPER_GL_SINK (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_FORCE_ASPECT_RATIO:
|
||||
clapper_sink->force_aspect_ratio = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
clapper_sink->par_n = gst_value_get_fraction_numerator (value);
|
||||
clapper_sink->par_d = gst_value_get_fraction_denominator (value);
|
||||
break;
|
||||
case PROP_KEEP_LAST_FRAME:
|
||||
clapper_sink->keep_last_frame = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_gl_sink_navigation_send_event (GstNavigation * navigation,
|
||||
GstStructure * structure)
|
||||
{
|
||||
GstClapperGLSink *sink = GST_CLAPPER_GL_SINK (navigation);
|
||||
GstEvent *event;
|
||||
GstPad *pad;
|
||||
|
||||
event = gst_event_new_navigation (structure);
|
||||
pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink));
|
||||
|
||||
GST_TRACE_OBJECT (sink, "navigation event %" GST_PTR_FORMAT, structure);
|
||||
|
||||
if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
|
||||
if (!gst_pad_send_event (pad, gst_event_ref (event))) {
|
||||
/* If upstream didn't handle the event we'll post a message with it
|
||||
* for the application in case it wants to do something with it */
|
||||
gst_element_post_message (GST_ELEMENT_CAST (sink),
|
||||
gst_navigation_message_new_event (GST_OBJECT_CAST (sink), event));
|
||||
}
|
||||
gst_event_unref (event);
|
||||
gst_object_unref (pad);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_gl_sink_navigation_interface_init (GstNavigationInterface * iface)
|
||||
{
|
||||
iface->send_event = gst_clapper_gl_sink_navigation_send_event;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
|
||||
{
|
||||
GstClapperGLSink *clapper_sink = GST_CLAPPER_GL_SINK (bsink);
|
||||
GstBufferPool *pool = NULL;
|
||||
GstStructure *config;
|
||||
GstCaps *caps;
|
||||
GstVideoInfo info;
|
||||
guint size;
|
||||
gboolean need_pool;
|
||||
GstStructure *allocation_meta = NULL;
|
||||
gint display_width, display_height;
|
||||
|
||||
if (!clapper_sink->display || !clapper_sink->context)
|
||||
return FALSE;
|
||||
|
||||
gst_query_parse_allocation (query, &caps, &need_pool);
|
||||
|
||||
if (caps == NULL)
|
||||
goto no_caps;
|
||||
|
||||
if (!gst_video_info_from_caps (&info, caps))
|
||||
goto invalid_caps;
|
||||
|
||||
/* the normal size of a frame */
|
||||
size = info.size;
|
||||
|
||||
if (need_pool) {
|
||||
GST_DEBUG_OBJECT (clapper_sink, "create new pool");
|
||||
pool = gst_gl_buffer_pool_new (clapper_sink->context);
|
||||
|
||||
config = gst_buffer_pool_get_config (pool);
|
||||
gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
|
||||
gst_buffer_pool_config_add_option (config,
|
||||
GST_BUFFER_POOL_OPTION_GL_SYNC_META);
|
||||
|
||||
if (!gst_buffer_pool_set_config (pool, config))
|
||||
goto config_failed;
|
||||
}
|
||||
|
||||
/* we need at least 2 buffer because we hold on to the last one */
|
||||
gst_query_add_allocation_pool (query, pool, size, 2, 0);
|
||||
if (pool)
|
||||
gst_object_unref (pool);
|
||||
|
||||
GST_OBJECT_LOCK (clapper_sink);
|
||||
display_width = clapper_sink->display_width;
|
||||
display_height = clapper_sink->display_height;
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
|
||||
if (display_width != 0 && display_height != 0) {
|
||||
GST_DEBUG_OBJECT (clapper_sink, "sending alloc query with size %dx%d",
|
||||
display_width, display_height);
|
||||
allocation_meta = gst_structure_new ("GstVideoOverlayCompositionMeta",
|
||||
"width", G_TYPE_UINT, display_width,
|
||||
"height", G_TYPE_UINT, display_height, NULL);
|
||||
}
|
||||
|
||||
gst_query_add_allocation_meta (query,
|
||||
GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, allocation_meta);
|
||||
|
||||
if (allocation_meta)
|
||||
gst_structure_free (allocation_meta);
|
||||
|
||||
/* we also support various metadata */
|
||||
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0);
|
||||
|
||||
if (clapper_sink->context->gl_vtable->FenceSync)
|
||||
gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0);
|
||||
|
||||
return TRUE;
|
||||
|
||||
/* ERRORS */
|
||||
no_caps:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "no caps specified");
|
||||
return FALSE;
|
||||
}
|
||||
invalid_caps:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "invalid caps specified");
|
||||
return FALSE;
|
||||
}
|
||||
config_failed:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "failed setting config");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_gl_sink_query (GstBaseSink * bsink, GstQuery * query)
|
||||
{
|
||||
GstClapperGLSink *clapper_sink = GST_CLAPPER_GL_SINK (bsink);
|
||||
gboolean res = FALSE;
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_CONTEXT:
|
||||
{
|
||||
if (gst_gl_handle_context_query ((GstElement *) clapper_sink, query,
|
||||
clapper_sink->display, clapper_sink->context, clapper_sink->gtk_context))
|
||||
return TRUE;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_gl_sink_start_on_main (GstBaseSink * bsink)
|
||||
{
|
||||
GstClapperGLSink *gst_sink = GST_CLAPPER_GL_SINK (bsink);
|
||||
GstClapperGLSinkClass *klass = GST_CLAPPER_GL_SINK_GET_CLASS (bsink);
|
||||
GtkWidget *toplevel;
|
||||
GtkRoot *root;
|
||||
|
||||
if (gst_clapper_gl_sink_get_widget (gst_sink) == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* After this point, clapper_sink->widget will always be set */
|
||||
|
||||
root = gtk_widget_get_root (GTK_WIDGET (gst_sink->widget));
|
||||
if (!GTK_IS_ROOT (root)) {
|
||||
GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (gst_sink->widget));
|
||||
if (parent) {
|
||||
GtkWidget *temp_parent;
|
||||
while ((temp_parent = gtk_widget_get_parent (parent)))
|
||||
parent = temp_parent;
|
||||
}
|
||||
toplevel = (parent) ? parent : GTK_WIDGET (gst_sink->widget);
|
||||
|
||||
/* sanity check */
|
||||
g_assert (klass->window_title);
|
||||
|
||||
/* User did not add widget its own UI, let's popup a new GtkWindow to
|
||||
* make gst-launch-1.0 work. */
|
||||
gst_sink->window = gtk_window_new ();
|
||||
gtk_window_set_default_size (GTK_WINDOW (gst_sink->window), 640, 480);
|
||||
gtk_window_set_title (GTK_WINDOW (gst_sink->window), klass->window_title);
|
||||
gtk_window_set_child (GTK_WINDOW (gst_sink->window), toplevel);
|
||||
|
||||
gst_sink->window_destroy_id = g_signal_connect (
|
||||
GTK_WINDOW (gst_sink->window),
|
||||
"destroy", G_CALLBACK (window_destroy_cb), gst_sink);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_gl_sink_start (GstBaseSink * bsink)
|
||||
{
|
||||
GstClapperGLSink *clapper_sink = GST_CLAPPER_GL_SINK (bsink);
|
||||
GtkClapperGLWidget *clapper_widget;
|
||||
|
||||
if (!(! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
|
||||
gst_clapper_gl_sink_start_on_main, bsink)))
|
||||
return FALSE;
|
||||
|
||||
/* After this point, clapper_sink->widget will always be set */
|
||||
clapper_widget = GTK_CLAPPER_GL_WIDGET (clapper_sink->widget);
|
||||
|
||||
if (!gtk_clapper_gl_widget_init_winsys (clapper_widget)) {
|
||||
GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s",
|
||||
"Failed to initialize OpenGL with GTK"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!clapper_sink->display)
|
||||
clapper_sink->display = gtk_clapper_gl_widget_get_display (clapper_widget);
|
||||
if (!clapper_sink->context)
|
||||
clapper_sink->context = gtk_clapper_gl_widget_get_context (clapper_widget);
|
||||
if (!clapper_sink->gtk_context)
|
||||
clapper_sink->gtk_context = gtk_clapper_gl_widget_get_gtk_context (clapper_widget);
|
||||
|
||||
if (!clapper_sink->display || !clapper_sink->context || !clapper_sink->gtk_context) {
|
||||
GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s",
|
||||
"Failed to retrieve OpenGL context from GTK"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gst_gl_element_propagate_display_context (GST_ELEMENT (bsink),
|
||||
clapper_sink->display);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_gl_sink_stop_on_main (GstBaseSink * bsink)
|
||||
{
|
||||
GstClapperGLSink *gst_sink = GST_CLAPPER_GL_SINK (bsink);
|
||||
|
||||
if (gst_sink->window) {
|
||||
gtk_window_destroy (GTK_WINDOW (gst_sink->window));
|
||||
gst_sink->window = NULL;
|
||||
gst_sink->widget = NULL;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_gl_sink_stop (GstBaseSink * bsink)
|
||||
{
|
||||
GstClapperGLSink *clapper_sink = GST_CLAPPER_GL_SINK (bsink);
|
||||
|
||||
if (clapper_sink->display) {
|
||||
gst_object_unref (clapper_sink->display);
|
||||
clapper_sink->display = NULL;
|
||||
}
|
||||
if (clapper_sink->context) {
|
||||
gst_object_unref (clapper_sink->context);
|
||||
clapper_sink->context = NULL;
|
||||
}
|
||||
if (clapper_sink->gtk_context) {
|
||||
gst_object_unref (clapper_sink->gtk_context);
|
||||
clapper_sink->gtk_context = NULL;
|
||||
}
|
||||
if (clapper_sink->window)
|
||||
return ! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
|
||||
gst_clapper_gl_sink_stop_on_main, bsink);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_window_show_all_and_unref (GtkWidget * window)
|
||||
{
|
||||
gtk_window_present (GTK_WINDOW (window));
|
||||
g_object_unref (window);
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_clapper_gl_sink_change_state (GstElement * element, GstStateChange transition)
|
||||
{
|
||||
GstClapperGLSink *clapper_sink = GST_CLAPPER_GL_SINK (element);
|
||||
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
|
||||
|
||||
GST_DEBUG_OBJECT (element, "changing state: %s => %s",
|
||||
gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
|
||||
gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
|
||||
|
||||
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
return ret;
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_NULL_TO_READY:
|
||||
GST_OBJECT_LOCK (clapper_sink);
|
||||
clapper_sink->had_eos = FALSE;
|
||||
if (clapper_sink->widget) {
|
||||
GTK_CLAPPER_GL_WIDGET_LOCK (clapper_sink->widget);
|
||||
clapper_sink->widget->ignore_buffers = FALSE;
|
||||
GTK_CLAPPER_GL_WIDGET_UNLOCK (clapper_sink->widget);
|
||||
}
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
break;
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:{
|
||||
GtkWindow *window = NULL;
|
||||
|
||||
GST_OBJECT_LOCK (clapper_sink);
|
||||
if (clapper_sink->window)
|
||||
window = g_object_ref (GTK_WINDOW (clapper_sink->window));
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
|
||||
if (window) {
|
||||
gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
|
||||
gst_gtk_window_show_all_and_unref, window);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_STATE_CHANGE_READY_TO_NULL:
|
||||
GST_OBJECT_LOCK (clapper_sink);
|
||||
if (clapper_sink->widget) {
|
||||
GTK_CLAPPER_GL_WIDGET_LOCK (clapper_sink->widget);
|
||||
clapper_sink->widget->ignore_buffers =
|
||||
!clapper_sink->had_eos || !clapper_sink->keep_last_frame;
|
||||
GTK_CLAPPER_GL_WIDGET_UNLOCK (clapper_sink->widget);
|
||||
}
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
/* Fall through to render black bg */
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
GST_OBJECT_LOCK (clapper_sink);
|
||||
if (clapper_sink->widget)
|
||||
gtk_clapper_gl_widget_set_buffer (clapper_sink->widget, NULL);
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_clapper_gl_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
|
||||
GstClockTime * start, GstClockTime * end)
|
||||
{
|
||||
GstClapperGLSink *clapper_sink = GST_CLAPPER_GL_SINK (bsink);
|
||||
|
||||
if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
|
||||
*start = GST_BUFFER_TIMESTAMP (buf);
|
||||
if (GST_BUFFER_DURATION_IS_VALID (buf))
|
||||
*end = *start + GST_BUFFER_DURATION (buf);
|
||||
else {
|
||||
if (GST_VIDEO_INFO_FPS_N (&clapper_sink->v_info) > 0) {
|
||||
*end = *start +
|
||||
gst_util_uint64_scale_int (GST_SECOND,
|
||||
GST_VIDEO_INFO_FPS_D (&clapper_sink->v_info),
|
||||
GST_VIDEO_INFO_FPS_N (&clapper_sink->v_info));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gst_clapper_gl_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
|
||||
{
|
||||
GstCaps *tmp = NULL;
|
||||
GstCaps *result = NULL;
|
||||
|
||||
tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink));
|
||||
|
||||
if (filter) {
|
||||
GST_DEBUG_OBJECT (bsink, "intersecting with filter caps %" GST_PTR_FORMAT,
|
||||
filter);
|
||||
|
||||
result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
|
||||
gst_caps_unref (tmp);
|
||||
} else {
|
||||
result = tmp;
|
||||
}
|
||||
|
||||
result = gst_gl_overlay_compositor_add_caps (result);
|
||||
|
||||
GST_DEBUG_OBJECT (bsink, "returning caps: %" GST_PTR_FORMAT, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_clapper_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
|
||||
{
|
||||
GstClapperGLSink *clapper_sink = GST_CLAPPER_GL_SINK (bsink);
|
||||
|
||||
GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
|
||||
|
||||
if (!gst_video_info_from_caps (&clapper_sink->v_info, caps))
|
||||
return FALSE;
|
||||
|
||||
GST_OBJECT_LOCK (clapper_sink);
|
||||
|
||||
if (clapper_sink->widget == NULL) {
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
GST_ELEMENT_ERROR (clapper_sink, RESOURCE, NOT_FOUND,
|
||||
("%s", "Output widget was destroyed"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!gtk_clapper_gl_widget_set_format (clapper_sink->widget, &clapper_sink->v_info)) {
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
return FALSE;
|
||||
}
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_clapper_gl_sink_wait_event (GstBaseSink * bsink, GstEvent * event)
|
||||
{
|
||||
GstClapperGLSink *clapper_sink = GST_CLAPPER_GL_SINK (bsink);
|
||||
GstFlowReturn ret;
|
||||
|
||||
ret = GST_BASE_SINK_CLASS (parent_class)->wait_event (bsink, event);
|
||||
|
||||
switch (event->type) {
|
||||
case GST_EVENT_EOS:
|
||||
if (ret == GST_FLOW_OK) {
|
||||
GST_OBJECT_LOCK (clapper_sink);
|
||||
clapper_sink->had_eos = TRUE;
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_clapper_gl_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
|
||||
{
|
||||
GstClapperGLSink *clapper_sink;
|
||||
|
||||
GST_TRACE ("rendering buffer:%p", buf);
|
||||
|
||||
clapper_sink = GST_CLAPPER_GL_SINK (vsink);
|
||||
|
||||
GST_OBJECT_LOCK (clapper_sink);
|
||||
|
||||
if (clapper_sink->widget == NULL) {
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
GST_ELEMENT_ERROR (clapper_sink, RESOURCE, NOT_FOUND,
|
||||
("%s", "Output widget was destroyed"), (NULL));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
gtk_clapper_gl_widget_set_buffer (clapper_sink->widget, buf);
|
||||
|
||||
GST_OBJECT_UNLOCK (clapper_sink);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
105
lib/gst/clapper/gtk4/gstclapperglsink.h
Normal file
105
lib/gst/clapper/gtk4/gstclapperglsink.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2020 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_CLAPPER_GL_SINK_H__
|
||||
#define __GST_CLAPPER_GL_SINK_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/gstvideosink.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/gl/gl.h>
|
||||
|
||||
#include "gtkclapperglwidget.h"
|
||||
|
||||
#define GST_TYPE_CLAPPER_GL_SINK (gst_clapper_gl_sink_get_type())
|
||||
#define GST_CLAPPER_GL_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CLAPPER_GL_SINK,GstClapperGLSink))
|
||||
#define GST_CLAPPER_GL_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CLAPPER_GL_SINK,GstClapperGLClass))
|
||||
#define GST_CLAPPER_GL_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_GL_SINK, GstClapperGLSinkClass))
|
||||
#define GST_IS_CLAPPER_GL_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CLAPPER_GL_SINK))
|
||||
#define GST_IS_CLAPPER_GL_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CLAPPER_GL_SINK))
|
||||
#define GST_CLAPPER_GL_SINK_CAST(obj) ((GstClapperGLSink*)(obj))
|
||||
|
||||
G_BEGIN_DECLS
|
||||
typedef struct _GstClapperGLSink GstClapperGLSink;
|
||||
typedef struct _GstClapperGLSinkClass GstClapperGLSinkClass;
|
||||
|
||||
GType gst_clapper_gl_sink_get_type (void);
|
||||
|
||||
/**
|
||||
* GstClapperGLSink:
|
||||
*
|
||||
* Opaque #GstClapperGLSink object
|
||||
*/
|
||||
struct _GstClapperGLSink
|
||||
{
|
||||
/* <private> */
|
||||
GstVideoSink parent;
|
||||
|
||||
GstVideoInfo v_info;
|
||||
|
||||
GtkClapperGLWidget *widget;
|
||||
|
||||
gboolean had_eos;
|
||||
|
||||
/* properties */
|
||||
gboolean force_aspect_ratio;
|
||||
gint par_n, par_d;
|
||||
gboolean keep_last_frame;
|
||||
|
||||
gboolean ignore_textures;
|
||||
|
||||
GtkWidget *window;
|
||||
gulong widget_destroy_id;
|
||||
gulong window_destroy_id;
|
||||
|
||||
GstGLDisplay *display;
|
||||
GstGLContext *context;
|
||||
GstGLContext *gtk_context;
|
||||
|
||||
GstGLUpload *upload;
|
||||
GstBuffer *uploaded_buffer;
|
||||
|
||||
/* read/write with object lock */
|
||||
gint display_width, display_height;
|
||||
};
|
||||
|
||||
/**
|
||||
* GstClapperGLSinkClass:
|
||||
*
|
||||
* The #GstClapperGLSinkClass struct only contains private data
|
||||
*/
|
||||
struct _GstClapperGLSinkClass
|
||||
{
|
||||
GstVideoSinkClass object_class;
|
||||
|
||||
/* metadata */
|
||||
const gchar *window_title;
|
||||
|
||||
/* virtuals */
|
||||
GtkWidget* (*create_widget) (void);
|
||||
};
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperGLSink, gst_object_unref)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_CLAPPER_GL_SINK_H__ */
|
@@ -1,574 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2020 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:gtkgstsink
|
||||
* @title: GstGtkBaseSink
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "gstgtkbasesink.h"
|
||||
#include "gstgtkutils.h"
|
||||
|
||||
GST_DEBUG_CATEGORY (gst_debug_gtk_base_sink);
|
||||
#define GST_CAT_DEFAULT gst_debug_gtk_base_sink
|
||||
|
||||
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
|
||||
#define DEFAULT_PAR_N 0
|
||||
#define DEFAULT_PAR_D 1
|
||||
#define DEFAULT_IGNORE_ALPHA TRUE
|
||||
#define DEFAULT_IGNORE_TEXTURES FALSE
|
||||
|
||||
static void gst_gtk_base_sink_finalize (GObject * object);
|
||||
static void gst_gtk_base_sink_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * param_spec);
|
||||
static void gst_gtk_base_sink_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * param_spec);
|
||||
|
||||
static gboolean gst_gtk_base_sink_start (GstBaseSink * bsink);
|
||||
static gboolean gst_gtk_base_sink_stop (GstBaseSink * bsink);
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_gtk_base_sink_change_state (GstElement * element,
|
||||
GstStateChange transition);
|
||||
|
||||
static void gst_gtk_base_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
|
||||
GstClockTime * start, GstClockTime * end);
|
||||
static gboolean gst_gtk_base_sink_set_caps (GstBaseSink * bsink,
|
||||
GstCaps * caps);
|
||||
static GstFlowReturn gst_gtk_base_sink_show_frame (GstVideoSink * bsink,
|
||||
GstBuffer * buf);
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_navigation_interface_init (GstNavigationInterface * iface);
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_WIDGET,
|
||||
PROP_FORCE_ASPECT_RATIO,
|
||||
PROP_PIXEL_ASPECT_RATIO,
|
||||
PROP_IGNORE_ALPHA,
|
||||
PROP_IGNORE_TEXTURES,
|
||||
};
|
||||
|
||||
#define gst_gtk_base_sink_parent_class parent_class
|
||||
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstGtkBaseSink, gst_gtk_base_sink,
|
||||
GST_TYPE_VIDEO_SINK,
|
||||
G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
|
||||
gst_gtk_base_sink_navigation_interface_init);
|
||||
GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_sink,
|
||||
"gtkbasesink", 0, "GTK Video Sink base class"));
|
||||
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_class_init (GstGtkBaseSinkClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
GstBaseSinkClass *gstbasesink_class;
|
||||
GstVideoSinkClass *gstvideosink_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
gstbasesink_class = (GstBaseSinkClass *) klass;
|
||||
gstvideosink_class = (GstVideoSinkClass *) klass;
|
||||
|
||||
gobject_class->set_property = gst_gtk_base_sink_set_property;
|
||||
gobject_class->get_property = gst_gtk_base_sink_get_property;
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_WIDGET,
|
||||
g_param_spec_object ("widget", "GTK Widget",
|
||||
"The GtkWidget to place in the widget hierarchy "
|
||||
"(must only be get from the GTK main thread)",
|
||||
GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
|
||||
g_param_spec_boolean ("force-aspect-ratio",
|
||||
"Force aspect ratio",
|
||||
"When enabled, scaling will respect original aspect ratio",
|
||||
DEFAULT_FORCE_ASPECT_RATIO,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
|
||||
gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
|
||||
"The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D,
|
||||
G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
/* Disabling alpha was removed in GTK4 */
|
||||
#if !defined(BUILD_FOR_GTK4)
|
||||
g_object_class_install_property (gobject_class, PROP_IGNORE_ALPHA,
|
||||
g_param_spec_boolean ("ignore-alpha", "Ignore Alpha",
|
||||
"When enabled, alpha will be ignored and converted to black",
|
||||
DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
#endif
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_IGNORE_TEXTURES,
|
||||
g_param_spec_boolean ("ignore-textures", "Ignore Textures",
|
||||
"When enabled, textures will be ignored and not drawn",
|
||||
DEFAULT_IGNORE_TEXTURES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
gobject_class->finalize = gst_gtk_base_sink_finalize;
|
||||
|
||||
gstelement_class->change_state = gst_gtk_base_sink_change_state;
|
||||
gstbasesink_class->set_caps = gst_gtk_base_sink_set_caps;
|
||||
gstbasesink_class->get_times = gst_gtk_base_sink_get_times;
|
||||
gstbasesink_class->start = gst_gtk_base_sink_start;
|
||||
gstbasesink_class->stop = gst_gtk_base_sink_stop;
|
||||
|
||||
gstvideosink_class->show_frame = gst_gtk_base_sink_show_frame;
|
||||
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_GTK_BASE_SINK, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_init (GstGtkBaseSink * gtk_sink)
|
||||
{
|
||||
gtk_sink->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
|
||||
gtk_sink->par_n = DEFAULT_PAR_N;
|
||||
gtk_sink->par_d = DEFAULT_PAR_D;
|
||||
gtk_sink->ignore_alpha = DEFAULT_IGNORE_ALPHA;
|
||||
gtk_sink->ignore_textures = DEFAULT_IGNORE_TEXTURES;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_finalize (GObject * object)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object);
|
||||
|
||||
GST_DEBUG ("finalizing base sink");
|
||||
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
if (gtk_sink->window && gtk_sink->window_destroy_id)
|
||||
g_signal_handler_disconnect (gtk_sink->window, gtk_sink->window_destroy_id);
|
||||
if (gtk_sink->widget && gtk_sink->widget_destroy_id)
|
||||
g_signal_handler_disconnect (gtk_sink->widget, gtk_sink->widget_destroy_id);
|
||||
|
||||
g_clear_object (>k_sink->widget);
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
widget_destroy_cb (GtkWidget * widget, GstGtkBaseSink * gtk_sink)
|
||||
{
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
g_clear_object (>k_sink->widget);
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
}
|
||||
|
||||
static void
|
||||
window_destroy_cb (GtkWidget * widget, GstGtkBaseSink * gtk_sink)
|
||||
{
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
if (gtk_sink->widget) {
|
||||
if (gtk_sink->widget_destroy_id) {
|
||||
g_signal_handler_disconnect (gtk_sink->widget,
|
||||
gtk_sink->widget_destroy_id);
|
||||
gtk_sink->widget_destroy_id = 0;
|
||||
}
|
||||
g_clear_object (>k_sink->widget);
|
||||
}
|
||||
gtk_sink->window = NULL;
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
}
|
||||
|
||||
static GtkGstBaseWidget *
|
||||
gst_gtk_base_sink_get_widget (GstGtkBaseSink * gtk_sink)
|
||||
{
|
||||
if (gtk_sink->widget != NULL)
|
||||
return gtk_sink->widget;
|
||||
|
||||
/* Ensure GTK is initialized, this has no side effect if it was already
|
||||
* initialized. Also, we do that lazily, so the application can be first */
|
||||
if (!gtk_init_check (
|
||||
#if !defined(BUILD_FOR_GTK4)
|
||||
NULL, NULL
|
||||
#endif
|
||||
)) {
|
||||
GST_ERROR_OBJECT (gtk_sink, "Could not ensure GTK initialization.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_assert (GST_GTK_BASE_SINK_GET_CLASS (gtk_sink)->create_widget);
|
||||
gtk_sink->widget = (GtkGstBaseWidget *)
|
||||
GST_GTK_BASE_SINK_GET_CLASS (gtk_sink)->create_widget ();
|
||||
|
||||
gtk_sink->bind_aspect_ratio =
|
||||
g_object_bind_property (gtk_sink, "force-aspect-ratio", gtk_sink->widget,
|
||||
"force-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
gtk_sink->bind_pixel_aspect_ratio =
|
||||
g_object_bind_property (gtk_sink, "pixel-aspect-ratio", gtk_sink->widget,
|
||||
"pixel-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
#if !defined(BUILD_FOR_GTK4)
|
||||
gtk_sink->bind_ignore_alpha =
|
||||
g_object_bind_property (gtk_sink, "ignore-alpha", gtk_sink->widget,
|
||||
"ignore-alpha", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
#endif
|
||||
|
||||
gtk_sink->bind_ignore_textures =
|
||||
g_object_bind_property (gtk_sink, "ignore-textures", gtk_sink->widget,
|
||||
"ignore-textures", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
|
||||
/* Take the floating ref, other wise the destruction of the container will
|
||||
* make this widget disappear possibly before we are done. */
|
||||
gst_object_ref_sink (gtk_sink->widget);
|
||||
|
||||
gtk_sink->widget_destroy_id = g_signal_connect (gtk_sink->widget, "destroy",
|
||||
G_CALLBACK (widget_destroy_cb), gtk_sink);
|
||||
|
||||
/* back pointer */
|
||||
gtk_gst_base_widget_set_element (GTK_GST_BASE_WIDGET (gtk_sink->widget),
|
||||
GST_ELEMENT (gtk_sink));
|
||||
|
||||
return gtk_sink->widget;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_WIDGET:
|
||||
{
|
||||
GObject *widget = NULL;
|
||||
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
if (gtk_sink->widget != NULL)
|
||||
widget = G_OBJECT (gtk_sink->widget);
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
if (!widget)
|
||||
widget =
|
||||
gst_gtk_invoke_on_main ((GThreadFunc) gst_gtk_base_sink_get_widget,
|
||||
gtk_sink);
|
||||
|
||||
g_value_set_object (value, widget);
|
||||
break;
|
||||
}
|
||||
case PROP_FORCE_ASPECT_RATIO:
|
||||
g_value_set_boolean (value, gtk_sink->force_aspect_ratio);
|
||||
break;
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
gst_value_set_fraction (value, gtk_sink->par_n, gtk_sink->par_d);
|
||||
break;
|
||||
case PROP_IGNORE_ALPHA:
|
||||
g_value_set_boolean (value, gtk_sink->ignore_alpha);
|
||||
break;
|
||||
case PROP_IGNORE_TEXTURES:
|
||||
g_value_set_boolean (value, gtk_sink->ignore_textures);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_FORCE_ASPECT_RATIO:
|
||||
gtk_sink->force_aspect_ratio = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
gtk_sink->par_n = gst_value_get_fraction_numerator (value);
|
||||
gtk_sink->par_d = gst_value_get_fraction_denominator (value);
|
||||
break;
|
||||
case PROP_IGNORE_ALPHA:
|
||||
gtk_sink->ignore_alpha = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_IGNORE_TEXTURES:
|
||||
gtk_sink->ignore_textures = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_navigation_send_event (GstNavigation * navigation,
|
||||
GstStructure * structure)
|
||||
{
|
||||
GstGtkBaseSink *sink = GST_GTK_BASE_SINK (navigation);
|
||||
GstEvent *event;
|
||||
GstPad *pad;
|
||||
|
||||
event = gst_event_new_navigation (structure);
|
||||
pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink));
|
||||
|
||||
GST_TRACE_OBJECT (sink, "navigation event %" GST_PTR_FORMAT, structure);
|
||||
|
||||
if (GST_IS_PAD (pad) && GST_IS_EVENT (event)) {
|
||||
if (!gst_pad_send_event (pad, gst_event_ref (event))) {
|
||||
/* If upstream didn't handle the event we'll post a message with it
|
||||
* for the application in case it wants to do something with it */
|
||||
gst_element_post_message (GST_ELEMENT_CAST (sink),
|
||||
gst_navigation_message_new_event (GST_OBJECT_CAST (sink), event));
|
||||
}
|
||||
gst_event_unref (event);
|
||||
gst_object_unref (pad);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_navigation_interface_init (GstNavigationInterface * iface)
|
||||
{
|
||||
iface->send_event = gst_gtk_base_sink_navigation_send_event;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_base_sink_start_on_main (GstBaseSink * bsink)
|
||||
{
|
||||
GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink);
|
||||
GstGtkBaseSinkClass *klass = GST_GTK_BASE_SINK_GET_CLASS (bsink);
|
||||
GtkWidget *toplevel;
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
GtkRoot *root;
|
||||
#endif
|
||||
|
||||
if (gst_gtk_base_sink_get_widget (gst_sink) == NULL)
|
||||
return FALSE;
|
||||
|
||||
/* After this point, gtk_sink->widget will always be set */
|
||||
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
root = gtk_widget_get_root (GTK_WIDGET (gst_sink->widget));
|
||||
if (!GTK_IS_ROOT (root)) {
|
||||
GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (gst_sink->widget));
|
||||
if (parent) {
|
||||
GtkWidget *temp_parent;
|
||||
while ((temp_parent = gtk_widget_get_parent (parent)))
|
||||
parent = temp_parent;
|
||||
}
|
||||
toplevel = (parent) ? parent : GTK_WIDGET (gst_sink->widget);
|
||||
#else
|
||||
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gst_sink->widget));
|
||||
if (!gtk_widget_is_toplevel (toplevel)) {
|
||||
#endif
|
||||
/* sanity check */
|
||||
g_assert (klass->window_title);
|
||||
|
||||
/* User did not add widget its own UI, let's popup a new GtkWindow to
|
||||
* make gst-launch-1.0 work. */
|
||||
gst_sink->window = gtk_window_new (
|
||||
#if !defined(BUILD_FOR_GTK4)
|
||||
GTK_WINDOW_TOPLEVEL
|
||||
#endif
|
||||
);
|
||||
gtk_window_set_default_size (GTK_WINDOW (gst_sink->window), 640, 480);
|
||||
gtk_window_set_title (GTK_WINDOW (gst_sink->window), klass->window_title);
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
gtk_window_set_child (GTK_WINDOW (
|
||||
#else
|
||||
gtk_container_add (GTK_CONTAINER (
|
||||
#endif
|
||||
gst_sink->window), toplevel);
|
||||
|
||||
gst_sink->window_destroy_id = g_signal_connect (
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
GTK_WINDOW (gst_sink->window),
|
||||
#else
|
||||
gst_sink->window,
|
||||
#endif
|
||||
"destroy", G_CALLBACK (window_destroy_cb), gst_sink);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_base_sink_start (GstBaseSink * bsink)
|
||||
{
|
||||
return ! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
|
||||
gst_gtk_base_sink_start_on_main, bsink);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_base_sink_stop_on_main (GstBaseSink * bsink)
|
||||
{
|
||||
GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink);
|
||||
|
||||
if (gst_sink->window) {
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
gtk_window_destroy (GTK_WINDOW (gst_sink->window));
|
||||
#else
|
||||
gtk_widget_destroy (gst_sink->window);
|
||||
#endif
|
||||
gst_sink->window = NULL;
|
||||
gst_sink->widget = NULL;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_base_sink_stop (GstBaseSink * bsink)
|
||||
{
|
||||
GstGtkBaseSink *gst_sink = GST_GTK_BASE_SINK (bsink);
|
||||
|
||||
if (gst_sink->window)
|
||||
return ! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
|
||||
gst_gtk_base_sink_stop_on_main, bsink);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_window_show_all_and_unref (GtkWidget * window)
|
||||
{
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
gtk_window_present (GTK_WINDOW (window));
|
||||
#else
|
||||
gtk_widget_show_all (window);
|
||||
#endif
|
||||
g_object_unref (window);
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_gtk_base_sink_change_state (GstElement * element, GstStateChange transition)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (element);
|
||||
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
|
||||
|
||||
GST_DEBUG_OBJECT (element, "changing state: %s => %s",
|
||||
gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
|
||||
gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
|
||||
|
||||
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||||
if (ret == GST_STATE_CHANGE_FAILURE)
|
||||
return ret;
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
{
|
||||
GtkWindow *window = NULL;
|
||||
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
if (gtk_sink->window)
|
||||
window = g_object_ref (GTK_WINDOW (gtk_sink->window));
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
if (window) {
|
||||
gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
|
||||
gst_gtk_window_show_all_and_unref, window);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
if (gtk_sink->widget)
|
||||
gtk_gst_base_widget_set_buffer (gtk_sink->widget, NULL);
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_base_sink_get_times (GstBaseSink * bsink, GstBuffer * buf,
|
||||
GstClockTime * start, GstClockTime * end)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink;
|
||||
|
||||
gtk_sink = GST_GTK_BASE_SINK (bsink);
|
||||
|
||||
if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
|
||||
*start = GST_BUFFER_TIMESTAMP (buf);
|
||||
if (GST_BUFFER_DURATION_IS_VALID (buf))
|
||||
*end = *start + GST_BUFFER_DURATION (buf);
|
||||
else {
|
||||
if (GST_VIDEO_INFO_FPS_N (>k_sink->v_info) > 0) {
|
||||
*end = *start +
|
||||
gst_util_uint64_scale_int (GST_SECOND,
|
||||
GST_VIDEO_INFO_FPS_D (>k_sink->v_info),
|
||||
GST_VIDEO_INFO_FPS_N (>k_sink->v_info));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
gst_gtk_base_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink = GST_GTK_BASE_SINK (bsink);
|
||||
|
||||
GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps);
|
||||
|
||||
if (!gst_video_info_from_caps (>k_sink->v_info, caps))
|
||||
return FALSE;
|
||||
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
|
||||
if (gtk_sink->widget == NULL) {
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
GST_ELEMENT_ERROR (gtk_sink, RESOURCE, NOT_FOUND,
|
||||
("%s", "Output widget was destroyed"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!gtk_gst_base_widget_set_format (gtk_sink->widget, >k_sink->v_info)) {
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
return FALSE;
|
||||
}
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_gtk_base_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
|
||||
{
|
||||
GstGtkBaseSink *gtk_sink;
|
||||
|
||||
GST_TRACE ("rendering buffer:%p", buf);
|
||||
|
||||
gtk_sink = GST_GTK_BASE_SINK (vsink);
|
||||
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
|
||||
if (gtk_sink->widget == NULL) {
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
GST_ELEMENT_ERROR (gtk_sink, RESOURCE, NOT_FOUND,
|
||||
("%s", "Output widget was destroyed"), (NULL));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
gtk_gst_base_widget_set_buffer (gtk_sink->widget, buf);
|
||||
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_GTK_BASE_SINK_H__
|
||||
#define __GST_GTK_BASE_SINK_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/gstvideosink.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#include "gtkgstbasewidget.h"
|
||||
|
||||
#define GST_TYPE_GTK_BASE_SINK (gst_gtk_base_sink_get_type())
|
||||
#define GST_GTK_BASE_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GTK_BASE_SINK,GstGtkBaseSink))
|
||||
#define GST_GTK_BASE_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GTK_BASE_SINK,GstGtkBaseSinkClass))
|
||||
#define GST_GTK_BASE_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_GTK_BASE_SINK, GstGtkBaseSinkClass))
|
||||
#define GST_IS_GTK_BASE_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GTK_BASE_SINK))
|
||||
#define GST_IS_GTK_BASE_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GTK_BASE_SINK))
|
||||
#define GST_GTK_BASE_SINK_CAST(obj) ((GstGtkBaseSink*)(obj))
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _GstGtkBaseSink GstGtkBaseSink;
|
||||
typedef struct _GstGtkBaseSinkClass GstGtkBaseSinkClass;
|
||||
|
||||
GType gst_gtk_base_sink_get_type (void);
|
||||
|
||||
/**
|
||||
* GstGtkBaseSink:
|
||||
*
|
||||
* Opaque #GstGtkBaseSink object
|
||||
*/
|
||||
struct _GstGtkBaseSink
|
||||
{
|
||||
/* <private> */
|
||||
GstVideoSink parent;
|
||||
|
||||
GstVideoInfo v_info;
|
||||
|
||||
GtkGstBaseWidget *widget;
|
||||
|
||||
/* properties */
|
||||
gboolean force_aspect_ratio;
|
||||
GBinding *bind_aspect_ratio;
|
||||
|
||||
gint par_n;
|
||||
gint par_d;
|
||||
GBinding *bind_pixel_aspect_ratio;
|
||||
|
||||
gboolean ignore_alpha;
|
||||
GBinding *bind_ignore_alpha;
|
||||
|
||||
gboolean ignore_textures;
|
||||
GBinding *bind_ignore_textures;
|
||||
|
||||
GtkWidget *window;
|
||||
gulong widget_destroy_id;
|
||||
gulong window_destroy_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* GstGtkBaseSinkClass:
|
||||
*
|
||||
* The #GstGtkBaseSinkClass struct only contains private data
|
||||
*/
|
||||
struct _GstGtkBaseSinkClass
|
||||
{
|
||||
GstVideoSinkClass object_class;
|
||||
|
||||
/* metadata */
|
||||
const gchar *window_title;
|
||||
|
||||
/* virtuals */
|
||||
GtkWidget* (*create_widget) (void);
|
||||
};
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstGtkBaseSink, gst_object_unref)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_GTK_BASE_SINK_H__ */
|
@@ -1,336 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2020 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-gtkglsink
|
||||
* @title: gtkglsink
|
||||
*/
|
||||
|
||||
/**
|
||||
* SECTION:element-gtk4glsink
|
||||
* @title: gtk4glsink
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
|
||||
#include "gtkconfig.h"
|
||||
#include "gstgtkglsink.h"
|
||||
#include "gtkgstglwidget.h"
|
||||
|
||||
GST_DEBUG_CATEGORY (gst_debug_gtk_gl_sink);
|
||||
#define GST_CAT_DEFAULT gst_debug_gtk_gl_sink
|
||||
|
||||
static gboolean gst_gtk_gl_sink_start (GstBaseSink * bsink);
|
||||
static gboolean gst_gtk_gl_sink_stop (GstBaseSink * bsink);
|
||||
static gboolean gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query);
|
||||
static gboolean gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink,
|
||||
GstQuery * query);
|
||||
static GstCaps *gst_gtk_gl_sink_get_caps (GstBaseSink * bsink,
|
||||
GstCaps * filter);
|
||||
|
||||
static void gst_gtk_gl_sink_finalize (GObject * object);
|
||||
|
||||
static GstStaticPadTemplate gst_gtk_gl_sink_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
|
||||
(GST_CAPS_FEATURE_MEMORY_GL_MEMORY, "RGBA") "; "
|
||||
GST_VIDEO_CAPS_MAKE_WITH_FEATURES
|
||||
(GST_CAPS_FEATURE_MEMORY_GL_MEMORY ", "
|
||||
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, "RGBA")));
|
||||
|
||||
#define gst_gtk_gl_sink_parent_class parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (GstGtkGLSink, gst_gtk_gl_sink,
|
||||
GST_TYPE_GTK_BASE_SINK, GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_gl_sink,
|
||||
GTKCONFIG_GLSINK, 0, GTKCONFIG_NAME " GL Video Sink"));
|
||||
|
||||
static void
|
||||
gst_gtk_gl_sink_class_init (GstGtkGLSinkClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
GstBaseSinkClass *gstbasesink_class;
|
||||
GstGtkBaseSinkClass *gstgtkbasesink_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
gstbasesink_class = (GstBaseSinkClass *) klass;
|
||||
gstgtkbasesink_class = (GstGtkBaseSinkClass *) klass;
|
||||
|
||||
gobject_class->finalize = gst_gtk_gl_sink_finalize;
|
||||
|
||||
gstbasesink_class->query = gst_gtk_gl_sink_query;
|
||||
gstbasesink_class->propose_allocation = gst_gtk_gl_sink_propose_allocation;
|
||||
gstbasesink_class->start = gst_gtk_gl_sink_start;
|
||||
gstbasesink_class->stop = gst_gtk_gl_sink_stop;
|
||||
gstbasesink_class->get_caps = gst_gtk_gl_sink_get_caps;
|
||||
|
||||
gstgtkbasesink_class->create_widget = gtk_gst_gl_widget_new;
|
||||
gstgtkbasesink_class->window_title = GTKCONFIG_NAME " GL Renderer";
|
||||
|
||||
gst_element_class_set_metadata (gstelement_class,
|
||||
GTKCONFIG_NAME " GL Video Sink",
|
||||
"Sink/Video", "A video sink that renders to a GtkWidget using OpenGL",
|
||||
"Matthew Waters <matthew@centricular.com>, "
|
||||
"Rafał Dzięgiel <rafostar.github@gmail.com>");
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class,
|
||||
&gst_gtk_gl_sink_template);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_gl_sink_init (GstGtkGLSink * gtk_sink)
|
||||
{
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_gl_sink_query (GstBaseSink * bsink, GstQuery * query)
|
||||
{
|
||||
GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
|
||||
gboolean res = FALSE;
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_CONTEXT:
|
||||
{
|
||||
if (gst_gl_handle_context_query ((GstElement *) gtk_sink, query,
|
||||
gtk_sink->display, gtk_sink->context, gtk_sink->gtk_context))
|
||||
return TRUE;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_cb (GtkWidget * widget, GstGtkGLSink * gtk_sink)
|
||||
{
|
||||
if (gtk_sink->widget_destroy_sig_handler) {
|
||||
g_signal_handler_disconnect (widget, gtk_sink->widget_destroy_sig_handler);
|
||||
gtk_sink->widget_destroy_sig_handler = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_gl_sink_start (GstBaseSink * bsink)
|
||||
{
|
||||
GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (bsink);
|
||||
GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
|
||||
GtkGstGLWidget *gst_widget;
|
||||
|
||||
if (!GST_BASE_SINK_CLASS (parent_class)->start (bsink))
|
||||
return FALSE;
|
||||
|
||||
/* After this point, gtk_sink->widget will always be set */
|
||||
gst_widget = GTK_GST_GL_WIDGET (base_sink->widget);
|
||||
|
||||
if (!gtk_sink->widget_destroy_sig_handler) {
|
||||
gtk_sink->widget_destroy_sig_handler =
|
||||
g_signal_connect (gst_widget, "destroy", G_CALLBACK (destroy_cb),
|
||||
gtk_sink);
|
||||
}
|
||||
|
||||
if (!gtk_gst_gl_widget_init_winsys (gst_widget)) {
|
||||
GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s",
|
||||
"Failed to initialize OpenGL with GTK"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!gtk_sink->display)
|
||||
gtk_sink->display = gtk_gst_gl_widget_get_display (gst_widget);
|
||||
if (!gtk_sink->context)
|
||||
gtk_sink->context = gtk_gst_gl_widget_get_context (gst_widget);
|
||||
if (!gtk_sink->gtk_context)
|
||||
gtk_sink->gtk_context = gtk_gst_gl_widget_get_gtk_context (gst_widget);
|
||||
|
||||
if (!gtk_sink->display || !gtk_sink->context || !gtk_sink->gtk_context) {
|
||||
GST_ELEMENT_ERROR (bsink, RESOURCE, NOT_FOUND, ("%s",
|
||||
"Failed to retrieve OpenGL context from GTK"), (NULL));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gst_gl_element_propagate_display_context (GST_ELEMENT (bsink),
|
||||
gtk_sink->display);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_gl_sink_stop (GstBaseSink * bsink)
|
||||
{
|
||||
GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
|
||||
GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (bsink);
|
||||
|
||||
if (gtk_sink->display) {
|
||||
gst_object_unref (gtk_sink->display);
|
||||
gtk_sink->display = NULL;
|
||||
}
|
||||
|
||||
if (gtk_sink->context) {
|
||||
gst_object_unref (gtk_sink->context);
|
||||
gtk_sink->context = NULL;
|
||||
}
|
||||
|
||||
if (gtk_sink->gtk_context) {
|
||||
gst_object_unref (gtk_sink->gtk_context);
|
||||
gtk_sink->gtk_context = NULL;
|
||||
}
|
||||
|
||||
return GST_BASE_SINK_CLASS (parent_class)->stop (bsink);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_gtk_gl_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
|
||||
{
|
||||
GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (bsink);
|
||||
GstBufferPool *pool = NULL;
|
||||
GstStructure *config;
|
||||
GstCaps *caps;
|
||||
GstVideoInfo info;
|
||||
guint size;
|
||||
gboolean need_pool;
|
||||
GstStructure *allocation_meta = NULL;
|
||||
gint display_width, display_height;
|
||||
|
||||
if (!gtk_sink->display || !gtk_sink->context)
|
||||
return FALSE;
|
||||
|
||||
gst_query_parse_allocation (query, &caps, &need_pool);
|
||||
|
||||
if (caps == NULL)
|
||||
goto no_caps;
|
||||
|
||||
if (!gst_video_info_from_caps (&info, caps))
|
||||
goto invalid_caps;
|
||||
|
||||
/* the normal size of a frame */
|
||||
size = info.size;
|
||||
|
||||
if (need_pool) {
|
||||
GST_DEBUG_OBJECT (gtk_sink, "create new pool");
|
||||
pool = gst_gl_buffer_pool_new (gtk_sink->context);
|
||||
|
||||
config = gst_buffer_pool_get_config (pool);
|
||||
gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
|
||||
gst_buffer_pool_config_add_option (config,
|
||||
GST_BUFFER_POOL_OPTION_GL_SYNC_META);
|
||||
|
||||
if (!gst_buffer_pool_set_config (pool, config))
|
||||
goto config_failed;
|
||||
}
|
||||
|
||||
/* we need at least 2 buffer because we hold on to the last one */
|
||||
gst_query_add_allocation_pool (query, pool, size, 2, 0);
|
||||
if (pool)
|
||||
gst_object_unref (pool);
|
||||
|
||||
GST_OBJECT_LOCK (gtk_sink);
|
||||
display_width = gtk_sink->display_width;
|
||||
display_height = gtk_sink->display_height;
|
||||
GST_OBJECT_UNLOCK (gtk_sink);
|
||||
|
||||
if (display_width != 0 && display_height != 0) {
|
||||
GST_DEBUG_OBJECT (gtk_sink, "sending alloc query with size %dx%d",
|
||||
display_width, display_height);
|
||||
allocation_meta = gst_structure_new ("GstVideoOverlayCompositionMeta",
|
||||
"width", G_TYPE_UINT, display_width,
|
||||
"height", G_TYPE_UINT, display_height, NULL);
|
||||
}
|
||||
|
||||
gst_query_add_allocation_meta (query,
|
||||
GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, allocation_meta);
|
||||
|
||||
if (allocation_meta)
|
||||
gst_structure_free (allocation_meta);
|
||||
|
||||
/* we also support various metadata */
|
||||
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, 0);
|
||||
|
||||
if (gtk_sink->context->gl_vtable->FenceSync)
|
||||
gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, 0);
|
||||
|
||||
return TRUE;
|
||||
|
||||
/* ERRORS */
|
||||
no_caps:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "no caps specified");
|
||||
return FALSE;
|
||||
}
|
||||
invalid_caps:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "invalid caps specified");
|
||||
return FALSE;
|
||||
}
|
||||
config_failed:
|
||||
{
|
||||
GST_DEBUG_OBJECT (bsink, "failed setting config");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static GstCaps *
|
||||
gst_gtk_gl_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
|
||||
{
|
||||
GstCaps *tmp = NULL;
|
||||
GstCaps *result = NULL;
|
||||
|
||||
tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink));
|
||||
|
||||
if (filter) {
|
||||
GST_DEBUG_OBJECT (bsink, "intersecting with filter caps %" GST_PTR_FORMAT,
|
||||
filter);
|
||||
|
||||
result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
|
||||
gst_caps_unref (tmp);
|
||||
} else {
|
||||
result = tmp;
|
||||
}
|
||||
|
||||
result = gst_gl_overlay_compositor_add_caps (result);
|
||||
|
||||
GST_DEBUG_OBJECT (bsink, "returning caps: %" GST_PTR_FORMAT, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_gtk_gl_sink_finalize (GObject * object)
|
||||
{
|
||||
GstGtkGLSink *gtk_sink = GST_GTK_GL_SINK (object);
|
||||
GstGtkBaseSink *base_sink = GST_GTK_BASE_SINK (object);
|
||||
|
||||
if (gtk_sink->widget_destroy_sig_handler) {
|
||||
g_signal_handler_disconnect (base_sink->widget,
|
||||
gtk_sink->widget_destroy_sig_handler);
|
||||
gtk_sink->widget_destroy_sig_handler = 0;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_GTK_GL_SINK_H__
|
||||
#define __GST_GTK_GL_SINK_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/gstvideosink.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/gl/gl.h>
|
||||
|
||||
#include "gstgtkbasesink.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_GTK_GL_SINK (gst_gtk_gl_sink_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GstGtkGLSink, gst_gtk_gl_sink, GST, GTK_GL_SINK,
|
||||
GstGtkBaseSink);
|
||||
|
||||
/**
|
||||
* GstGtkGLSink:
|
||||
*
|
||||
* Opaque #GstGtkGLSink object
|
||||
*/
|
||||
struct _GstGtkGLSink
|
||||
{
|
||||
/* <private> */
|
||||
GstGtkBaseSink parent;
|
||||
|
||||
GstGLDisplay *display;
|
||||
GstGLContext *context;
|
||||
GstGLContext *gtk_context;
|
||||
|
||||
GstGLUpload *upload;
|
||||
GstBuffer *uploaded_buffer;
|
||||
|
||||
/* read/write with object lock */
|
||||
gint display_width;
|
||||
gint display_height;
|
||||
|
||||
gulong widget_destroy_sig_handler;
|
||||
};
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_GTK_GL_SINK_H__ */
|
@@ -19,6 +19,7 @@
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include "gstgtkutils.h"
|
||||
|
||||
struct invoke_context
|
||||
@@ -69,3 +70,26 @@ gst_gtk_invoke_on_main (GThreadFunc func, gpointer data)
|
||||
|
||||
return info.res;
|
||||
}
|
||||
|
||||
void
|
||||
gst_gtk_install_shared_properties (GObjectClass *gobject_class)
|
||||
{
|
||||
g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
|
||||
g_param_spec_boolean ("force-aspect-ratio",
|
||||
"Force aspect ratio",
|
||||
"When enabled, scaling will respect original aspect ratio",
|
||||
DEFAULT_FORCE_ASPECT_RATIO,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
|
||||
gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
|
||||
"The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D,
|
||||
G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_KEEP_LAST_FRAME,
|
||||
g_param_spec_boolean ("keep-last-frame",
|
||||
"Keep last frame",
|
||||
"Keep showing last video frame after playback instead of black screen",
|
||||
DEFAULT_KEEP_LAST_FRAME,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
}
|
||||
|
@@ -22,8 +22,25 @@
|
||||
#ifndef __GST_GTK_UTILS_H__
|
||||
#define __GST_GTK_UTILS_H__
|
||||
|
||||
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
|
||||
#define DEFAULT_PAR_N 0
|
||||
#define DEFAULT_PAR_D 1
|
||||
#define DEFAULT_KEEP_LAST_FRAME FALSE
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_WIDGET,
|
||||
PROP_FORCE_ASPECT_RATIO,
|
||||
PROP_PIXEL_ASPECT_RATIO,
|
||||
PROP_KEEP_LAST_FRAME
|
||||
};
|
||||
|
||||
gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data);
|
||||
|
||||
void gst_gtk_install_shared_properties (GObjectClass *gobject_class);
|
||||
|
||||
#endif /* __GST_GTK_UTILS_H__ */
|
||||
|
1096
lib/gst/clapper/gtk4/gtkclapperglwidget.c
Normal file
1096
lib/gst/clapper/gtk4/gtkclapperglwidget.c
Normal file
File diff suppressed because it is too large
Load Diff
112
lib/gst/clapper/gtk4/gtkclapperglwidget.h
Normal file
112
lib/gst/clapper/gtk4/gtkclapperglwidget.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2020 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GTK_CLAPPER_GL_WIDGET_H__
|
||||
#define __GTK_CLAPPER_GL_WIDGET_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
#include <gst/gl/gl.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GType gtk_clapper_gl_widget_get_type (void);
|
||||
#define GTK_TYPE_CLAPPER_GL_WIDGET (gtk_clapper_gl_widget_get_type())
|
||||
#define GTK_CLAPPER_GL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GTK_TYPE_CLAPPER_GL_WIDGET,GtkClapperGLWidget))
|
||||
#define GTK_CLAPPER_GL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GTK_TYPE_CLAPPER_GL_WIDGET,GtkClapperGLWidgetClass))
|
||||
#define GTK_IS_CLAPPER_GL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GTK_TYPE_CLAPPER_GL_WIDGET))
|
||||
#define GTK_IS_CLAPPER_GL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GTK_TYPE_CLAPPER_GL_WIDGET))
|
||||
#define GTK_CLAPPER_GL_WIDGET_CAST(obj) ((GtkClapperGLWidget*)(obj))
|
||||
#define GTK_CLAPPER_GL_WIDGET_LOCK(w) g_mutex_lock(&((GtkClapperGLWidget*)(w))->lock)
|
||||
#define GTK_CLAPPER_GL_WIDGET_UNLOCK(w) g_mutex_unlock(&((GtkClapperGLWidget*)(w))->lock)
|
||||
|
||||
typedef struct _GtkClapperGLWidget GtkClapperGLWidget;
|
||||
typedef struct _GtkClapperGLWidgetClass GtkClapperGLWidgetClass;
|
||||
typedef struct _GtkClapperGLWidgetPrivate GtkClapperGLWidgetPrivate;
|
||||
|
||||
struct _GtkClapperGLWidget
|
||||
{
|
||||
/* <private> */
|
||||
GtkGLArea parent;
|
||||
GtkClapperGLWidgetPrivate *priv;
|
||||
|
||||
/* properties */
|
||||
gboolean force_aspect_ratio;
|
||||
gint par_n, par_d;
|
||||
gboolean keep_last_frame;
|
||||
|
||||
gint display_width;
|
||||
gint display_height;
|
||||
|
||||
/* Widget dimensions */
|
||||
gint scaled_width;
|
||||
gint scaled_height;
|
||||
|
||||
/* Position coords */
|
||||
gdouble last_pos_x;
|
||||
gdouble last_pos_y;
|
||||
|
||||
gboolean negotiated;
|
||||
gboolean ignore_buffers;
|
||||
GstBuffer *pending_buffer;
|
||||
GstBuffer *buffer;
|
||||
GstVideoInfo v_info;
|
||||
|
||||
/* resize */
|
||||
gboolean pending_resize;
|
||||
GstVideoInfo pending_v_info;
|
||||
guint display_ratio_num;
|
||||
guint display_ratio_den;
|
||||
|
||||
/*< private >*/
|
||||
GMutex lock;
|
||||
GWeakRef element;
|
||||
|
||||
/* event controllers */
|
||||
GtkEventController *key_controller;
|
||||
GtkEventController *motion_controller;
|
||||
GtkGesture *click_gesture;
|
||||
|
||||
/* Pending draw idles callback */
|
||||
guint draw_id;
|
||||
};
|
||||
|
||||
struct _GtkClapperGLWidgetClass
|
||||
{
|
||||
GtkGLAreaClass parent_class;
|
||||
};
|
||||
|
||||
/* API */
|
||||
gboolean gtk_clapper_gl_widget_set_format (GtkClapperGLWidget * widget, GstVideoInfo * v_info);
|
||||
void gtk_clapper_gl_widget_set_buffer (GtkClapperGLWidget * widget, GstBuffer * buffer);
|
||||
void gtk_clapper_gl_widget_set_element (GtkClapperGLWidget * widget, GstElement * element);
|
||||
|
||||
GtkWidget * gtk_clapper_gl_widget_new (void);
|
||||
|
||||
gboolean gtk_clapper_gl_widget_init_winsys (GtkClapperGLWidget * widget);
|
||||
GstGLDisplay * gtk_clapper_gl_widget_get_display (GtkClapperGLWidget * widget);
|
||||
GstGLContext * gtk_clapper_gl_widget_get_context (GtkClapperGLWidget * widget);
|
||||
GstGLContext * gtk_clapper_gl_widget_get_gtk_context (GtkClapperGLWidget * widget);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_CLAPPER_GL_WIDGET_H__ */
|
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2020 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
#define GTKCONFIG_PLUGIN gtk4
|
||||
#define GTKCONFIG_NAME "GTK4"
|
||||
#define GTKCONFIG_SINK "gtk4sink"
|
||||
#define GTKCONFIG_GLSINK "gtk4glsink"
|
||||
#else
|
||||
#define GTKCONFIG_PLUGIN gtk
|
||||
#define GTKCONFIG_NAME "GTK"
|
||||
#define GTKCONFIG_SINK "gtksink"
|
||||
#define GTKCONFIG_GLSINK "gtkglsink"
|
||||
#endif
|
@@ -1,619 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2020 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "gtkgstbasewidget.h"
|
||||
|
||||
GST_DEBUG_CATEGORY (gst_debug_gtk_base_widget);
|
||||
#define GST_CAT_DEFAULT gst_debug_gtk_base_widget
|
||||
|
||||
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
|
||||
#define DEFAULT_PAR_N 0
|
||||
#define DEFAULT_PAR_D 1
|
||||
#define DEFAULT_IGNORE_ALPHA TRUE
|
||||
#define DEFAULT_IGNORE_TEXTURES FALSE
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_FORCE_ASPECT_RATIO,
|
||||
PROP_PIXEL_ASPECT_RATIO,
|
||||
PROP_IGNORE_ALPHA,
|
||||
PROP_IGNORE_TEXTURES,
|
||||
};
|
||||
|
||||
static void
|
||||
gtk_gst_base_widget_get_preferred_width (GtkWidget * widget, gint * min,
|
||||
gint * natural)
|
||||
{
|
||||
GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
|
||||
gint video_width = gst_widget->display_width;
|
||||
|
||||
if (!gst_widget->negotiated)
|
||||
video_width = 10;
|
||||
|
||||
if (min)
|
||||
*min = 1;
|
||||
if (natural)
|
||||
*natural = video_width;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_base_widget_get_preferred_height (GtkWidget * widget, gint * min,
|
||||
gint * natural)
|
||||
{
|
||||
GtkGstBaseWidget *gst_widget = (GtkGstBaseWidget *) widget;
|
||||
gint video_height = gst_widget->display_height;
|
||||
|
||||
if (!gst_widget->negotiated)
|
||||
video_height = 10;
|
||||
|
||||
if (min)
|
||||
*min = 1;
|
||||
if (natural)
|
||||
*natural = video_height;
|
||||
}
|
||||
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
static void
|
||||
gtk_gst_base_widget_measure (GtkWidget * widget, GtkOrientation orientation,
|
||||
gint for_size, gint * min, gint * natural,
|
||||
gint * minimum_baseline, gint * natural_baseline)
|
||||
{
|
||||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
gtk_gst_base_widget_get_preferred_width (widget, min, natural);
|
||||
else
|
||||
gtk_gst_base_widget_get_preferred_height (widget, min, natural);
|
||||
|
||||
*minimum_baseline = -1;
|
||||
*natural_baseline = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
gtk_gst_base_widget_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_FORCE_ASPECT_RATIO:
|
||||
gtk_widget->force_aspect_ratio = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
gtk_widget->par_n = gst_value_get_fraction_numerator (value);
|
||||
gtk_widget->par_d = gst_value_get_fraction_denominator (value);
|
||||
break;
|
||||
case PROP_IGNORE_ALPHA:
|
||||
gtk_widget->ignore_alpha = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_IGNORE_TEXTURES:
|
||||
gtk_widget->ignore_textures = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_base_widget_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GtkGstBaseWidget *gtk_widget = GTK_GST_BASE_WIDGET (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_FORCE_ASPECT_RATIO:
|
||||
g_value_set_boolean (value, gtk_widget->force_aspect_ratio);
|
||||
break;
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
gst_value_set_fraction (value, gtk_widget->par_n, gtk_widget->par_d);
|
||||
break;
|
||||
case PROP_IGNORE_ALPHA:
|
||||
g_value_set_boolean (value, gtk_widget->ignore_alpha);
|
||||
break;
|
||||
case PROP_IGNORE_TEXTURES:
|
||||
g_value_set_boolean (value, gtk_widget->ignore_textures);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_calculate_par (GtkGstBaseWidget * widget, GstVideoInfo * info)
|
||||
{
|
||||
gboolean ok;
|
||||
gint width, height;
|
||||
gint par_n, par_d;
|
||||
gint display_par_n, display_par_d;
|
||||
|
||||
width = GST_VIDEO_INFO_WIDTH (info);
|
||||
height = GST_VIDEO_INFO_HEIGHT (info);
|
||||
|
||||
par_n = GST_VIDEO_INFO_PAR_N (info);
|
||||
par_d = GST_VIDEO_INFO_PAR_D (info);
|
||||
|
||||
if (!par_n)
|
||||
par_n = 1;
|
||||
|
||||
/* get display's PAR */
|
||||
if (widget->par_n != 0 && widget->par_d != 0) {
|
||||
display_par_n = widget->par_n;
|
||||
display_par_d = widget->par_d;
|
||||
} else {
|
||||
display_par_n = 1;
|
||||
display_par_d = 1;
|
||||
}
|
||||
|
||||
|
||||
ok = gst_video_calculate_display_ratio (&widget->display_ratio_num,
|
||||
&widget->display_ratio_den, width, height, par_n, par_d, display_par_n,
|
||||
display_par_d);
|
||||
|
||||
if (ok) {
|
||||
GST_LOG ("PAR: %u/%u DAR:%u/%u", par_n, par_d, display_par_n,
|
||||
display_par_d);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
_apply_par (GtkGstBaseWidget * widget)
|
||||
{
|
||||
guint display_ratio_num, display_ratio_den;
|
||||
gint width, height;
|
||||
|
||||
width = GST_VIDEO_INFO_WIDTH (&widget->v_info);
|
||||
height = GST_VIDEO_INFO_HEIGHT (&widget->v_info);
|
||||
|
||||
display_ratio_num = widget->display_ratio_num;
|
||||
display_ratio_den = widget->display_ratio_den;
|
||||
|
||||
if (height % display_ratio_den == 0) {
|
||||
GST_DEBUG ("keeping video height");
|
||||
widget->display_width = (guint)
|
||||
gst_util_uint64_scale_int (height, display_ratio_num,
|
||||
display_ratio_den);
|
||||
widget->display_height = height;
|
||||
} else if (width % display_ratio_num == 0) {
|
||||
GST_DEBUG ("keeping video width");
|
||||
widget->display_width = width;
|
||||
widget->display_height = (guint)
|
||||
gst_util_uint64_scale_int (width, display_ratio_den, display_ratio_num);
|
||||
} else {
|
||||
GST_DEBUG ("approximating while keeping video height");
|
||||
widget->display_width = (guint)
|
||||
gst_util_uint64_scale_int (height, display_ratio_num,
|
||||
display_ratio_den);
|
||||
widget->display_height = height;
|
||||
}
|
||||
|
||||
GST_DEBUG ("scaling to %dx%d", widget->display_width, widget->display_height);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_queue_draw (GtkGstBaseWidget * widget)
|
||||
{
|
||||
GTK_GST_BASE_WIDGET_LOCK (widget);
|
||||
widget->draw_id = 0;
|
||||
|
||||
if (widget->pending_resize) {
|
||||
widget->pending_resize = FALSE;
|
||||
|
||||
widget->v_info = widget->pending_v_info;
|
||||
widget->negotiated = TRUE;
|
||||
|
||||
_apply_par (widget);
|
||||
|
||||
gtk_widget_queue_resize (GTK_WIDGET (widget));
|
||||
} else {
|
||||
gtk_widget_queue_draw (GTK_WIDGET (widget));
|
||||
}
|
||||
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (widget);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static const gchar *
|
||||
_gdk_key_to_navigation_string (guint keyval)
|
||||
{
|
||||
/* TODO: expand */
|
||||
switch (keyval) {
|
||||
#define KEY(key) case GDK_KEY_ ## key: return G_STRINGIFY(key)
|
||||
KEY (Up);
|
||||
KEY (Down);
|
||||
KEY (Left);
|
||||
KEY (Right);
|
||||
KEY (Home);
|
||||
KEY (End);
|
||||
#undef KEY
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static GdkEvent *
|
||||
_get_current_event (GtkEventController * controller)
|
||||
{
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
return gtk_event_controller_get_current_event (controller);
|
||||
#else
|
||||
return gtk_get_current_event ();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
_gdk_event_free (GdkEvent * event)
|
||||
{
|
||||
#if !defined(BUILD_FOR_GTK4)
|
||||
if (event)
|
||||
gdk_event_free (event);
|
||||
#endif
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_base_widget_key_event (GtkEventControllerKey * key_controller,
|
||||
guint keyval, guint keycode, GdkModifierType state)
|
||||
{
|
||||
GtkEventController *controller = GTK_EVENT_CONTROLLER (key_controller);
|
||||
GtkWidget *widget = gtk_event_controller_get_widget (controller);
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
|
||||
GstElement *element;
|
||||
|
||||
if ((element = g_weak_ref_get (&base_widget->element))) {
|
||||
if (GST_IS_NAVIGATION (element)) {
|
||||
GdkEvent *event = _get_current_event (controller);
|
||||
const gchar *str = _gdk_key_to_navigation_string (keyval);
|
||||
|
||||
if (str) {
|
||||
const gchar *key_type =
|
||||
gdk_event_get_event_type (event) ==
|
||||
GDK_KEY_PRESS ? "key-press" : "key-release";
|
||||
gst_navigation_send_key_event (GST_NAVIGATION (element), key_type, str);
|
||||
}
|
||||
_gdk_event_free (event);
|
||||
}
|
||||
g_object_unref (element);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
_fit_stream_to_allocated_size (GtkGstBaseWidget * base_widget,
|
||||
GtkAllocation * allocation, GstVideoRectangle * result)
|
||||
{
|
||||
if (base_widget->force_aspect_ratio) {
|
||||
GstVideoRectangle src, dst;
|
||||
|
||||
src.x = 0;
|
||||
src.y = 0;
|
||||
src.w = base_widget->display_width;
|
||||
src.h = base_widget->display_height;
|
||||
|
||||
dst.x = 0;
|
||||
dst.y = 0;
|
||||
dst.w = allocation->width;
|
||||
dst.h = allocation->height;
|
||||
|
||||
gst_video_sink_center_rect (src, dst, result, TRUE);
|
||||
} else {
|
||||
result->x = 0;
|
||||
result->y = 0;
|
||||
result->w = allocation->width;
|
||||
result->h = allocation->height;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_display_size_to_stream_size (GtkGstBaseWidget * base_widget, gdouble x,
|
||||
gdouble y, gdouble * stream_x, gdouble * stream_y)
|
||||
{
|
||||
gdouble stream_width, stream_height;
|
||||
GtkAllocation allocation;
|
||||
GstVideoRectangle result;
|
||||
|
||||
gtk_widget_get_allocation (GTK_WIDGET (base_widget), &allocation);
|
||||
_fit_stream_to_allocated_size (base_widget, &allocation, &result);
|
||||
|
||||
stream_width = (gdouble) GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
|
||||
stream_height = (gdouble) GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
|
||||
|
||||
/* from display coordinates to stream coordinates */
|
||||
if (result.w > 0)
|
||||
*stream_x = (x - result.x) / result.w * stream_width;
|
||||
else
|
||||
*stream_x = 0.;
|
||||
|
||||
/* clip to stream size */
|
||||
if (*stream_x < 0.)
|
||||
*stream_x = 0.;
|
||||
if (*stream_x > GST_VIDEO_INFO_WIDTH (&base_widget->v_info))
|
||||
*stream_x = GST_VIDEO_INFO_WIDTH (&base_widget->v_info);
|
||||
|
||||
/* same for y-axis */
|
||||
if (result.h > 0)
|
||||
*stream_y = (y - result.y) / result.h * stream_height;
|
||||
else
|
||||
*stream_y = 0.;
|
||||
|
||||
if (*stream_y < 0.)
|
||||
*stream_y = 0.;
|
||||
if (*stream_y > GST_VIDEO_INFO_HEIGHT (&base_widget->v_info))
|
||||
*stream_y = GST_VIDEO_INFO_HEIGHT (&base_widget->v_info);
|
||||
|
||||
GST_TRACE ("transform %fx%f into %fx%f", x, y, *stream_x, *stream_y);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_base_widget_button_event (
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
GtkGestureClick * gesture,
|
||||
#else
|
||||
GtkGestureMultiPress * gesture,
|
||||
#endif
|
||||
gint n_press, gdouble x, gdouble y)
|
||||
{
|
||||
GtkEventController *controller = GTK_EVENT_CONTROLLER (gesture);
|
||||
GtkWidget *widget = gtk_event_controller_get_widget (controller);
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
|
||||
GstElement *element;
|
||||
|
||||
if ((element = g_weak_ref_get (&base_widget->element))) {
|
||||
if (GST_IS_NAVIGATION (element)) {
|
||||
GdkEvent *event = _get_current_event (controller);
|
||||
const gchar *key_type =
|
||||
gdk_event_get_event_type (event) == GDK_BUTTON_PRESS
|
||||
? "mouse-button-press" : "mouse-button-release";
|
||||
gdouble stream_x, stream_y;
|
||||
#if !defined(BUILD_FOR_GTK4)
|
||||
guint button;
|
||||
gdk_event_get_button (event, &button);
|
||||
#endif
|
||||
|
||||
_display_size_to_stream_size (base_widget, x, y, &stream_x, &stream_y);
|
||||
|
||||
gst_navigation_send_mouse_event (GST_NAVIGATION (element), key_type,
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
/* Gesture is set to ignore other buttons so we do not have to check */
|
||||
GDK_BUTTON_PRIMARY,
|
||||
#else
|
||||
button,
|
||||
#endif
|
||||
stream_x, stream_y);
|
||||
|
||||
_gdk_event_free (event);
|
||||
}
|
||||
g_object_unref (element);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_base_widget_motion_event (GtkEventControllerMotion * motion_controller,
|
||||
gdouble x, gdouble y)
|
||||
{
|
||||
GtkEventController *controller = GTK_EVENT_CONTROLLER (motion_controller);
|
||||
GtkWidget *widget = gtk_event_controller_get_widget (controller);
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
|
||||
GstElement *element;
|
||||
|
||||
if ((element = g_weak_ref_get (&base_widget->element))) {
|
||||
if (GST_IS_NAVIGATION (element)) {
|
||||
gdouble stream_x, stream_y;
|
||||
|
||||
_display_size_to_stream_size (base_widget, x, y, &stream_x, &stream_y);
|
||||
|
||||
gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move",
|
||||
0, stream_x, stream_y);
|
||||
}
|
||||
g_object_unref (element);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_klass = (GObjectClass *) klass;
|
||||
GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass;
|
||||
|
||||
gobject_klass->set_property = gtk_gst_base_widget_set_property;
|
||||
gobject_klass->get_property = gtk_gst_base_widget_get_property;
|
||||
|
||||
g_object_class_install_property (gobject_klass, PROP_FORCE_ASPECT_RATIO,
|
||||
g_param_spec_boolean ("force-aspect-ratio",
|
||||
"Force aspect ratio",
|
||||
"When enabled, scaling will respect original aspect ratio",
|
||||
DEFAULT_FORCE_ASPECT_RATIO,
|
||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_klass, PROP_PIXEL_ASPECT_RATIO,
|
||||
gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
|
||||
"The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D,
|
||||
G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_klass, PROP_IGNORE_ALPHA,
|
||||
g_param_spec_boolean ("ignore-alpha", "Ignore Alpha",
|
||||
"When enabled, alpha will be ignored and converted to black",
|
||||
DEFAULT_IGNORE_ALPHA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (gobject_klass, PROP_IGNORE_TEXTURES,
|
||||
g_param_spec_boolean ("ignore-textures", "Ignore Textures",
|
||||
"When enabled, textures will be ignored and not drawn",
|
||||
DEFAULT_IGNORE_TEXTURES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
widget_klass->measure = gtk_gst_base_widget_measure;
|
||||
#else
|
||||
widget_klass->get_preferred_width = gtk_gst_base_widget_get_preferred_width;
|
||||
widget_klass->get_preferred_height = gtk_gst_base_widget_get_preferred_height;
|
||||
#endif
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (gst_debug_gtk_base_widget, "gtkbasewidget", 0,
|
||||
"GTK Video Base Widget");
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_base_widget_init (GtkGstBaseWidget * widget)
|
||||
{
|
||||
widget->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
|
||||
widget->par_n = DEFAULT_PAR_N;
|
||||
widget->par_d = DEFAULT_PAR_D;
|
||||
widget->ignore_alpha = DEFAULT_IGNORE_ALPHA;
|
||||
widget->ignore_textures = DEFAULT_IGNORE_TEXTURES;
|
||||
|
||||
gst_video_info_init (&widget->v_info);
|
||||
gst_video_info_init (&widget->pending_v_info);
|
||||
|
||||
g_weak_ref_init (&widget->element, NULL);
|
||||
g_mutex_init (&widget->lock);
|
||||
|
||||
widget->key_controller = gtk_event_controller_key_new (
|
||||
#if !defined(BUILD_FOR_GTK4)
|
||||
GTK_WIDGET (widget)
|
||||
#endif
|
||||
);
|
||||
g_signal_connect (widget->key_controller, "key-pressed",
|
||||
G_CALLBACK (gtk_gst_base_widget_key_event), NULL);
|
||||
g_signal_connect (widget->key_controller, "key-released",
|
||||
G_CALLBACK (gtk_gst_base_widget_key_event), NULL);
|
||||
|
||||
widget->motion_controller = gtk_event_controller_motion_new (
|
||||
#if !defined(BUILD_FOR_GTK4)
|
||||
GTK_WIDGET (widget)
|
||||
#endif
|
||||
);
|
||||
g_signal_connect (widget->motion_controller, "motion",
|
||||
G_CALLBACK (gtk_gst_base_widget_motion_event), NULL);
|
||||
|
||||
widget->click_gesture =
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
gtk_gesture_click_new ();
|
||||
#else
|
||||
gtk_gesture_multi_press_new (GTK_WIDGET (widget));
|
||||
#endif
|
||||
g_signal_connect (widget->click_gesture, "pressed",
|
||||
G_CALLBACK (gtk_gst_base_widget_button_event), NULL);
|
||||
g_signal_connect (widget->click_gesture, "released",
|
||||
G_CALLBACK (gtk_gst_base_widget_button_event), NULL);
|
||||
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
/* Otherwise widget in grid will appear as a 1x1px
|
||||
* video which might be misleading for users */
|
||||
gtk_widget_set_hexpand (GTK_WIDGET (widget), TRUE);
|
||||
gtk_widget_set_vexpand (GTK_WIDGET (widget), TRUE);
|
||||
|
||||
gtk_widget_set_focusable (GTK_WIDGET (widget), TRUE);
|
||||
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (widget->click_gesture),
|
||||
GDK_BUTTON_PRIMARY);
|
||||
|
||||
gtk_widget_add_controller (GTK_WIDGET (widget), widget->key_controller);
|
||||
gtk_widget_add_controller (GTK_WIDGET (widget), widget->motion_controller);
|
||||
gtk_widget_add_controller (GTK_WIDGET (widget),
|
||||
GTK_EVENT_CONTROLLER (widget->click_gesture));
|
||||
#endif
|
||||
|
||||
gtk_widget_set_can_focus (GTK_WIDGET (widget), TRUE);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_base_widget_finalize (GObject * object)
|
||||
{
|
||||
GtkGstBaseWidget *widget = GTK_GST_BASE_WIDGET (object);
|
||||
|
||||
/* GTK4 takes ownership of EventControllers
|
||||
* while GTK3 still needs manual unref */
|
||||
#if !defined(BUILD_FOR_GTK4)
|
||||
g_object_unref (widget->key_controller);
|
||||
g_object_unref (widget->motion_controller);
|
||||
g_object_unref (widget->click_gesture);
|
||||
#endif
|
||||
|
||||
gst_buffer_replace (&widget->pending_buffer, NULL);
|
||||
gst_buffer_replace (&widget->buffer, NULL);
|
||||
g_mutex_clear (&widget->lock);
|
||||
g_weak_ref_clear (&widget->element);
|
||||
|
||||
if (widget->draw_id)
|
||||
g_source_remove (widget->draw_id);
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget,
|
||||
GstElement * element)
|
||||
{
|
||||
g_weak_ref_set (&widget->element, element);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget,
|
||||
GstVideoInfo * v_info)
|
||||
{
|
||||
GTK_GST_BASE_WIDGET_LOCK (widget);
|
||||
|
||||
if (gst_video_info_is_equal (&widget->pending_v_info, v_info)) {
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (widget);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!_calculate_par (widget, v_info)) {
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (widget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
widget->pending_resize = TRUE;
|
||||
widget->pending_v_info = *v_info;
|
||||
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (widget);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer)
|
||||
{
|
||||
/* As we have no type, this is better then no check */
|
||||
g_return_if_fail (GTK_IS_WIDGET (widget));
|
||||
|
||||
GTK_GST_BASE_WIDGET_LOCK (widget);
|
||||
|
||||
gst_buffer_replace (&widget->pending_buffer, buffer);
|
||||
|
||||
if (!widget->draw_id) {
|
||||
widget->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
|
||||
(GSourceFunc) _queue_draw, widget, NULL);
|
||||
}
|
||||
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (widget);
|
||||
}
|
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2020 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GTK_GST_BASE_WIDGET_H__
|
||||
#define __GTK_GST_BASE_WIDGET_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#if !defined(BUILD_FOR_GTK4)
|
||||
#include <gdk/gdk.h>
|
||||
#endif
|
||||
|
||||
#define GTK_GST_BASE_WIDGET(w) ((GtkGstBaseWidget *)(w))
|
||||
#define GTK_GST_BASE_WIDGET_CLASS(k) ((GtkGstBaseWidgetClass *)(k))
|
||||
#define GTK_GST_BASE_WIDGET_LOCK(w) g_mutex_lock(&((GtkGstBaseWidget*)(w))->lock)
|
||||
#define GTK_GST_BASE_WIDGET_UNLOCK(w) g_mutex_unlock(&((GtkGstBaseWidget*)(w))->lock)
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _GtkGstBaseWidget GtkGstBaseWidget;
|
||||
typedef struct _GtkGstBaseWidgetClass GtkGstBaseWidgetClass;
|
||||
|
||||
struct _GtkGstBaseWidget
|
||||
{
|
||||
union {
|
||||
GtkGLArea gl_area;
|
||||
} parent;
|
||||
|
||||
/* properties */
|
||||
gboolean force_aspect_ratio;
|
||||
gint par_n, par_d;
|
||||
gboolean ignore_alpha;
|
||||
gboolean ignore_textures;
|
||||
|
||||
gint display_width;
|
||||
gint display_height;
|
||||
|
||||
gboolean negotiated;
|
||||
GstBuffer *pending_buffer;
|
||||
GstBuffer *buffer;
|
||||
GstVideoInfo v_info;
|
||||
|
||||
/* resize */
|
||||
gboolean pending_resize;
|
||||
GstVideoInfo pending_v_info;
|
||||
guint display_ratio_num;
|
||||
guint display_ratio_den;
|
||||
|
||||
/*< private >*/
|
||||
GMutex lock;
|
||||
GWeakRef element;
|
||||
|
||||
/* event controllers */
|
||||
GtkEventController *key_controller;
|
||||
GtkEventController *motion_controller;
|
||||
GtkGesture *click_gesture;
|
||||
|
||||
/* Pending draw idles callback */
|
||||
guint draw_id;
|
||||
};
|
||||
|
||||
struct _GtkGstBaseWidgetClass
|
||||
{
|
||||
union {
|
||||
GtkGLAreaClass gl_area_class;
|
||||
} parent_class;
|
||||
};
|
||||
|
||||
/* For implementer */
|
||||
void gtk_gst_base_widget_class_init (GtkGstBaseWidgetClass * klass);
|
||||
void gtk_gst_base_widget_init (GtkGstBaseWidget * widget);
|
||||
|
||||
void gtk_gst_base_widget_finalize (GObject * object);
|
||||
|
||||
/* API */
|
||||
gboolean gtk_gst_base_widget_set_format (GtkGstBaseWidget * widget, GstVideoInfo * v_info);
|
||||
void gtk_gst_base_widget_set_buffer (GtkGstBaseWidget * widget, GstBuffer * buffer);
|
||||
void gtk_gst_base_widget_set_element (GtkGstBaseWidget * widget, GstElement * element);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_GST_BASE_WIDGET_H__ */
|
@@ -1,604 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
* Copyright (C) 2020 Rafał Dzięgiel <rafostar.github@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "gtkgstglwidget.h"
|
||||
#include "gstgtkutils.h"
|
||||
#include <gst/gl/gstglfuncs.h>
|
||||
#include <gst/video/video.h>
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11)
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
#include <gdk/x11/gdkx.h>
|
||||
#else
|
||||
#include <gdk/gdkx.h>
|
||||
#endif
|
||||
#include <gst/gl/x11/gstgldisplay_x11.h>
|
||||
#endif
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND)
|
||||
#if defined(BUILD_FOR_GTK4)
|
||||
#include <gdk/wayland/gdkwayland.h>
|
||||
#else
|
||||
#include <gdk/gdkwayland.h>
|
||||
#endif
|
||||
#include <gst/gl/wayland/gstgldisplay_wayland.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* SECTION:gtkgstglwidget
|
||||
* @title: GtkGstGlWidget
|
||||
* @short_description: a #GtkGLArea that renders GStreamer video #GstBuffers
|
||||
* @see_also: #GtkGLArea, #GstBuffer
|
||||
*
|
||||
* #GtkGstGLWidget is an #GtkWidget that renders GStreamer video buffers.
|
||||
*/
|
||||
|
||||
#define GST_CAT_DEFAULT gtk_gst_gl_widget_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
struct _GtkGstGLWidgetPrivate
|
||||
{
|
||||
gboolean initiated;
|
||||
GstGLDisplay *display;
|
||||
GdkGLContext *gdk_context;
|
||||
GstGLContext *other_context;
|
||||
GstGLContext *context;
|
||||
GstGLUpload *upload;
|
||||
GstGLShader *shader;
|
||||
GLuint vao;
|
||||
GLuint vertex_buffer;
|
||||
GLint attr_position;
|
||||
GLint attr_texture;
|
||||
GLuint current_tex;
|
||||
GstGLOverlayCompositor *overlay_compositor;
|
||||
};
|
||||
|
||||
static const GLfloat vertices[] = {
|
||||
1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
|
||||
-1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
|
||||
-1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
|
||||
1.0f, -1.0f, 0.0f, 1.0f, 1.0f
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkGstGLWidget, gtk_gst_gl_widget, GTK_TYPE_GL_AREA,
|
||||
G_ADD_PRIVATE (GtkGstGLWidget)
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "gtkgstglwidget", 0,
|
||||
"GTK Gst GL Widget"));
|
||||
|
||||
static void
|
||||
gtk_gst_gl_widget_bind_buffer (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
const GstGLFuncs *gl = priv->context->gl_vtable;
|
||||
|
||||
gl->BindBuffer (GL_ARRAY_BUFFER, priv->vertex_buffer);
|
||||
|
||||
/* Load the vertex position */
|
||||
gl->VertexAttribPointer (priv->attr_position, 3, GL_FLOAT, GL_FALSE,
|
||||
5 * sizeof (GLfloat), (void *) 0);
|
||||
|
||||
/* Load the texture coordinate */
|
||||
gl->VertexAttribPointer (priv->attr_texture, 2, GL_FLOAT, GL_FALSE,
|
||||
5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
|
||||
|
||||
gl->EnableVertexAttribArray (priv->attr_position);
|
||||
gl->EnableVertexAttribArray (priv->attr_texture);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_gl_widget_unbind_buffer (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
const GstGLFuncs *gl = priv->context->gl_vtable;
|
||||
|
||||
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
|
||||
|
||||
gl->DisableVertexAttribArray (priv->attr_position);
|
||||
gl->DisableVertexAttribArray (priv->attr_texture);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_gl_widget_init_redisplay (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
const GstGLFuncs *gl = priv->context->gl_vtable;
|
||||
GError *error = NULL;
|
||||
|
||||
gst_gl_insert_debug_marker (priv->other_context, "initializing redisplay");
|
||||
if (!(priv->shader = gst_gl_shader_new_default (priv->context, &error))) {
|
||||
GST_ERROR ("Failed to initialize shader: %s", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
priv->attr_position =
|
||||
gst_gl_shader_get_attribute_location (priv->shader, "a_position");
|
||||
priv->attr_texture =
|
||||
gst_gl_shader_get_attribute_location (priv->shader, "a_texcoord");
|
||||
|
||||
if (gl->GenVertexArrays) {
|
||||
gl->GenVertexArrays (1, &priv->vao);
|
||||
gl->BindVertexArray (priv->vao);
|
||||
}
|
||||
|
||||
gl->GenBuffers (1, &priv->vertex_buffer);
|
||||
gl->BindBuffer (GL_ARRAY_BUFFER, priv->vertex_buffer);
|
||||
gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices,
|
||||
GL_STATIC_DRAW);
|
||||
|
||||
if (gl->GenVertexArrays) {
|
||||
gtk_gst_gl_widget_bind_buffer (gst_widget);
|
||||
gl->BindVertexArray (0);
|
||||
}
|
||||
|
||||
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
|
||||
|
||||
priv->overlay_compositor =
|
||||
gst_gl_overlay_compositor_new (priv->other_context);
|
||||
|
||||
priv->initiated = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
_redraw_texture (GtkGstGLWidget * gst_widget, guint tex)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
const GstGLFuncs *gl = priv->context->gl_vtable;
|
||||
const GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
|
||||
|
||||
if (gst_widget->base.force_aspect_ratio) {
|
||||
GstVideoRectangle src, dst, result;
|
||||
gint widget_width, widget_height, widget_scale;
|
||||
|
||||
gl->ClearColor (0.0, 0.0, 0.0, 1.0);
|
||||
gl->Clear (GL_COLOR_BUFFER_BIT);
|
||||
|
||||
widget_scale = gtk_widget_get_scale_factor ((GtkWidget *) gst_widget);
|
||||
widget_width = gtk_widget_get_allocated_width ((GtkWidget *) gst_widget);
|
||||
widget_height = gtk_widget_get_allocated_height ((GtkWidget *) gst_widget);
|
||||
|
||||
src.x = 0;
|
||||
src.y = 0;
|
||||
src.w = gst_widget->base.display_width;
|
||||
src.h = gst_widget->base.display_height;
|
||||
|
||||
dst.x = 0;
|
||||
dst.y = 0;
|
||||
dst.w = widget_width * widget_scale;
|
||||
dst.h = widget_height * widget_scale;
|
||||
|
||||
gst_video_sink_center_rect (src, dst, &result, TRUE);
|
||||
|
||||
gl->Viewport (result.x, result.y, result.w, result.h);
|
||||
}
|
||||
|
||||
gst_gl_shader_use (priv->shader);
|
||||
|
||||
if (gl->BindVertexArray)
|
||||
gl->BindVertexArray (priv->vao);
|
||||
gtk_gst_gl_widget_bind_buffer (gst_widget);
|
||||
|
||||
gl->ActiveTexture (GL_TEXTURE0);
|
||||
gl->BindTexture (GL_TEXTURE_2D, tex);
|
||||
gst_gl_shader_set_uniform_1i (priv->shader, "tex", 0);
|
||||
|
||||
gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
|
||||
|
||||
if (gl->BindVertexArray)
|
||||
gl->BindVertexArray (0);
|
||||
else
|
||||
gtk_gst_gl_widget_unbind_buffer (gst_widget);
|
||||
|
||||
gl->BindTexture (GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
static inline void
|
||||
_draw_black (GstGLContext * context)
|
||||
{
|
||||
const GstGLFuncs *gl = context->gl_vtable;
|
||||
|
||||
gst_gl_insert_debug_marker (context, "rendering black");
|
||||
gl->ClearColor (0.0, 0.0, 0.0, 1.0);
|
||||
gl->Clear (GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
static inline void
|
||||
_draw_black_with_gdk (GdkGLContext * gdk_context)
|
||||
{
|
||||
GST_DEBUG ("rendering empty frame with gdk context %p", gdk_context);
|
||||
glClearColor (0.0, 0.0, 0.0, 1.0);
|
||||
glClear (GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_gst_gl_widget_render (GtkGLArea * widget, GdkGLContext * context)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = GTK_GST_GL_WIDGET (widget)->priv;
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (widget);
|
||||
|
||||
GTK_GST_BASE_WIDGET_LOCK (widget);
|
||||
|
||||
/* Draw black with GDK context when priv is not available yet.
|
||||
GTK calls render with GDK context already active. */
|
||||
if (!priv->context || !priv->other_context || base_widget->ignore_textures) {
|
||||
_draw_black_with_gdk (context);
|
||||
goto done;
|
||||
}
|
||||
|
||||
gst_gl_context_activate (priv->other_context, TRUE);
|
||||
|
||||
if (!priv->initiated || !base_widget->negotiated) {
|
||||
if (!priv->initiated)
|
||||
gtk_gst_gl_widget_init_redisplay (GTK_GST_GL_WIDGET (widget));
|
||||
|
||||
_draw_black (priv->other_context);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Upload latest buffer */
|
||||
if (base_widget->pending_buffer) {
|
||||
GstBuffer *buffer = base_widget->pending_buffer;
|
||||
GstVideoFrame gl_frame;
|
||||
GstGLSyncMeta *sync_meta;
|
||||
|
||||
if (!gst_video_frame_map (&gl_frame, &base_widget->v_info, buffer,
|
||||
GST_MAP_READ | GST_MAP_GL)) {
|
||||
_draw_black (priv->other_context);
|
||||
goto done;
|
||||
}
|
||||
|
||||
priv->current_tex = *(guint *) gl_frame.data[0];
|
||||
gst_gl_insert_debug_marker (priv->other_context, "redrawing texture %u",
|
||||
priv->current_tex);
|
||||
|
||||
gst_gl_overlay_compositor_upload_overlays (priv->overlay_compositor,
|
||||
buffer);
|
||||
|
||||
sync_meta = gst_buffer_get_gl_sync_meta (buffer);
|
||||
if (sync_meta) {
|
||||
/* XXX: the set_sync() seems to be needed for resizing */
|
||||
gst_gl_sync_meta_set_sync_point (sync_meta, priv->context);
|
||||
gst_gl_sync_meta_wait (sync_meta, priv->other_context);
|
||||
}
|
||||
|
||||
gst_video_frame_unmap (&gl_frame);
|
||||
|
||||
if (base_widget->buffer)
|
||||
gst_buffer_unref (base_widget->buffer);
|
||||
|
||||
/* Keep the buffer to ensure current_tex stay valid */
|
||||
base_widget->buffer = buffer;
|
||||
base_widget->pending_buffer = NULL;
|
||||
}
|
||||
|
||||
GST_DEBUG ("rendering buffer %p with gdk context %p",
|
||||
base_widget->buffer, context);
|
||||
|
||||
_redraw_texture (GTK_GST_GL_WIDGET (widget), priv->current_tex);
|
||||
gst_gl_overlay_compositor_draw_overlays (priv->overlay_compositor);
|
||||
|
||||
gst_gl_insert_debug_marker (priv->other_context, "texture %u redrawn",
|
||||
priv->current_tex);
|
||||
|
||||
done:
|
||||
if (priv->other_context)
|
||||
gst_gl_context_activate (priv->other_context, FALSE);
|
||||
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (widget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
_reset_gl (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
const GstGLFuncs *gl = priv->other_context->gl_vtable;
|
||||
|
||||
if (!priv->gdk_context)
|
||||
priv->gdk_context = gtk_gl_area_get_context (GTK_GL_AREA (gst_widget));
|
||||
|
||||
if (priv->gdk_context == NULL)
|
||||
return;
|
||||
|
||||
gdk_gl_context_make_current (priv->gdk_context);
|
||||
gst_gl_context_activate (priv->other_context, TRUE);
|
||||
|
||||
if (priv->vao) {
|
||||
gl->DeleteVertexArrays (1, &priv->vao);
|
||||
priv->vao = 0;
|
||||
}
|
||||
|
||||
if (priv->vertex_buffer) {
|
||||
gl->DeleteBuffers (1, &priv->vertex_buffer);
|
||||
priv->vertex_buffer = 0;
|
||||
}
|
||||
|
||||
if (priv->upload) {
|
||||
gst_object_unref (priv->upload);
|
||||
priv->upload = NULL;
|
||||
}
|
||||
|
||||
if (priv->shader) {
|
||||
gst_object_unref (priv->shader);
|
||||
priv->shader = NULL;
|
||||
}
|
||||
|
||||
if (priv->overlay_compositor)
|
||||
gst_object_unref (priv->overlay_compositor);
|
||||
|
||||
gst_gl_context_activate (priv->other_context, FALSE);
|
||||
|
||||
gst_object_unref (priv->other_context);
|
||||
priv->other_context = NULL;
|
||||
|
||||
gdk_gl_context_clear_current ();
|
||||
|
||||
g_object_unref (priv->gdk_context);
|
||||
priv->gdk_context = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_gl_widget_finalize (GObject * object)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = GTK_GST_GL_WIDGET (object)->priv;
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (object);
|
||||
|
||||
if (priv->other_context)
|
||||
gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) _reset_gl, base_widget);
|
||||
|
||||
if (priv->context)
|
||||
gst_object_unref (priv->context);
|
||||
|
||||
if (priv->display)
|
||||
gst_object_unref (priv->display);
|
||||
|
||||
gtk_gst_base_widget_finalize (object);
|
||||
G_OBJECT_CLASS (gtk_gst_gl_widget_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_gl_widget_class_init (GtkGstGLWidgetClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_klass = (GObjectClass *) klass;
|
||||
GtkGLAreaClass *gl_widget_klass = (GtkGLAreaClass *) klass;
|
||||
|
||||
gtk_gst_base_widget_class_init (GTK_GST_BASE_WIDGET_CLASS (klass));
|
||||
|
||||
gobject_klass->finalize = gtk_gst_gl_widget_finalize;
|
||||
gl_widget_klass->render = gtk_gst_gl_widget_render;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_gst_gl_widget_init (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstBaseWidget *base_widget = GTK_GST_BASE_WIDGET (gst_widget);
|
||||
GdkDisplay *display;
|
||||
GtkGstGLWidgetPrivate *priv;
|
||||
|
||||
gtk_gst_base_widget_init (base_widget);
|
||||
|
||||
gst_widget->priv = priv = gtk_gst_gl_widget_get_instance_private (gst_widget);
|
||||
|
||||
display = gdk_display_get_default ();
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11)
|
||||
if (GDK_IS_X11_DISPLAY (display)) {
|
||||
priv->display = (GstGLDisplay *)
|
||||
gst_gl_display_x11_new_with_display (gdk_x11_display_get_xdisplay
|
||||
(display));
|
||||
}
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND)
|
||||
if (GDK_IS_WAYLAND_DISPLAY (display)) {
|
||||
struct wl_display *wayland_display =
|
||||
gdk_wayland_display_get_wl_display (display);
|
||||
priv->display = (GstGLDisplay *)
|
||||
gst_gl_display_wayland_new_with_display (wayland_display);
|
||||
}
|
||||
#endif
|
||||
|
||||
(void) display;
|
||||
|
||||
if (!priv->display)
|
||||
priv->display = gst_gl_display_new ();
|
||||
|
||||
GST_INFO ("Created %" GST_PTR_FORMAT, priv->display);
|
||||
|
||||
/* GTK4 always has alpha */
|
||||
#if !defined(BUILD_FOR_GTK4)
|
||||
gtk_gl_area_set_has_alpha (GTK_GL_AREA (gst_widget),
|
||||
!base_widget->ignore_alpha);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
_get_gl_context (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
GstGLPlatform platform = GST_GL_PLATFORM_NONE;
|
||||
GstGLAPI gl_api = GST_GL_API_NONE;
|
||||
guintptr gl_handle = 0;
|
||||
|
||||
gtk_widget_realize (GTK_WIDGET (gst_widget));
|
||||
|
||||
if (priv->other_context)
|
||||
gst_object_unref (priv->other_context);
|
||||
priv->other_context = NULL;
|
||||
|
||||
if (priv->gdk_context)
|
||||
g_object_unref (priv->gdk_context);
|
||||
|
||||
priv->gdk_context = gtk_gl_area_get_context (GTK_GL_AREA (gst_widget));
|
||||
if (priv->gdk_context == NULL) {
|
||||
GError *error = gtk_gl_area_get_error (GTK_GL_AREA (gst_widget));
|
||||
|
||||
GST_ERROR_OBJECT (gst_widget, "Error creating GdkGLContext : %s",
|
||||
error ? error->message : "No error set by Gdk");
|
||||
g_clear_error (&error);
|
||||
return;
|
||||
}
|
||||
|
||||
g_object_ref (priv->gdk_context);
|
||||
|
||||
gdk_gl_context_make_current (priv->gdk_context);
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11)
|
||||
if (GST_IS_GL_DISPLAY_X11 (priv->display)) {
|
||||
#if GST_GL_HAVE_PLATFORM_GLX
|
||||
if (!gl_handle) {
|
||||
platform = GST_GL_PLATFORM_GLX;
|
||||
gl_handle = gst_gl_context_get_current_gl_context (platform);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if GST_GL_HAVE_PLATFORM_EGL
|
||||
if (!gl_handle) {
|
||||
platform = GST_GL_PLATFORM_EGL;
|
||||
gl_handle = gst_gl_context_get_current_gl_context (platform);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (gl_handle) {
|
||||
gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL);
|
||||
priv->other_context =
|
||||
gst_gl_context_new_wrapped (priv->display, gl_handle,
|
||||
platform, gl_api);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && GST_GL_HAVE_PLATFORM_EGL && defined (GDK_WINDOWING_WAYLAND)
|
||||
if (GST_IS_GL_DISPLAY_WAYLAND (priv->display)) {
|
||||
platform = GST_GL_PLATFORM_EGL;
|
||||
gl_api = gst_gl_context_get_current_gl_api (platform, NULL, NULL);
|
||||
gl_handle = gst_gl_context_get_current_gl_context (platform);
|
||||
if (gl_handle)
|
||||
priv->other_context =
|
||||
gst_gl_context_new_wrapped (priv->display, gl_handle,
|
||||
platform, gl_api);
|
||||
}
|
||||
#endif
|
||||
|
||||
(void) platform;
|
||||
(void) gl_api;
|
||||
(void) gl_handle;
|
||||
|
||||
if (priv->other_context) {
|
||||
GError *error = NULL;
|
||||
|
||||
GST_INFO ("Retrieved Gdk OpenGL context %" GST_PTR_FORMAT,
|
||||
priv->other_context);
|
||||
gst_gl_context_activate (priv->other_context, TRUE);
|
||||
if (!gst_gl_context_fill_info (priv->other_context, &error)) {
|
||||
GST_ERROR ("failed to retrieve gdk context info: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
g_object_unref (priv->other_context);
|
||||
priv->other_context = NULL;
|
||||
} else {
|
||||
gst_gl_context_activate (priv->other_context, FALSE);
|
||||
}
|
||||
} else {
|
||||
GST_WARNING ("Could not retrieve Gdk OpenGL context");
|
||||
}
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
gtk_gst_gl_widget_new (void)
|
||||
{
|
||||
return (GtkWidget *) g_object_new (GTK_TYPE_GST_GL_WIDGET, NULL);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtk_gst_gl_widget_init_winsys (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
GtkGstGLWidgetPrivate *priv = gst_widget->priv;
|
||||
GError *error = NULL;
|
||||
|
||||
g_return_val_if_fail (GTK_IS_GST_GL_WIDGET (gst_widget), FALSE);
|
||||
g_return_val_if_fail (priv->display != NULL, FALSE);
|
||||
|
||||
GTK_GST_BASE_WIDGET_LOCK (gst_widget);
|
||||
|
||||
if (priv->display && priv->gdk_context && priv->other_context) {
|
||||
GST_TRACE ("have already initialized contexts");
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!priv->other_context) {
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
|
||||
gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) _get_gl_context, gst_widget);
|
||||
GTK_GST_BASE_WIDGET_LOCK (gst_widget);
|
||||
}
|
||||
|
||||
if (!GST_IS_GL_CONTEXT (priv->other_context)) {
|
||||
GST_FIXME ("Could not retrieve Gdk OpenGL context");
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GST_OBJECT_LOCK (priv->display);
|
||||
if (!gst_gl_display_create_context (priv->display, priv->other_context,
|
||||
&priv->context, &error)) {
|
||||
GST_WARNING ("Could not create OpenGL context: %s",
|
||||
error ? error->message : "Unknown");
|
||||
g_clear_error (&error);
|
||||
GST_OBJECT_UNLOCK (priv->display);
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
|
||||
return FALSE;
|
||||
}
|
||||
gst_gl_display_add_context (priv->display, priv->context);
|
||||
GST_OBJECT_UNLOCK (priv->display);
|
||||
|
||||
GTK_GST_BASE_WIDGET_UNLOCK (gst_widget);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GstGLContext *
|
||||
gtk_gst_gl_widget_get_gtk_context (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
if (!gst_widget->priv->other_context)
|
||||
return NULL;
|
||||
|
||||
return gst_object_ref (gst_widget->priv->other_context);
|
||||
}
|
||||
|
||||
GstGLContext *
|
||||
gtk_gst_gl_widget_get_context (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
if (!gst_widget->priv->context)
|
||||
return NULL;
|
||||
|
||||
return gst_object_ref (gst_widget->priv->context);
|
||||
}
|
||||
|
||||
GstGLDisplay *
|
||||
gtk_gst_gl_widget_get_display (GtkGstGLWidget * gst_widget)
|
||||
{
|
||||
if (!gst_widget->priv->display)
|
||||
return NULL;
|
||||
|
||||
return gst_object_ref (gst_widget->priv->display);
|
||||
}
|
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* GStreamer
|
||||
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GTK_GST_GL_WIDGET_H__
|
||||
#define __GTK_GST_GL_WIDGET_H__
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/gl/gl.h>
|
||||
|
||||
#include "gtkgstbasewidget.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GType gtk_gst_gl_widget_get_type (void);
|
||||
#define GTK_TYPE_GST_GL_WIDGET (gtk_gst_gl_widget_get_type())
|
||||
#define GTK_GST_GL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GTK_TYPE_GST_GL_WIDGET,GtkGstGLWidget))
|
||||
#define GTK_GST_GL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GTK_TYPE_GST_GL_WIDGET,GtkGstGLWidgetClass))
|
||||
#define GTK_IS_GST_GL_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GTK_TYPE_GST_GL_WIDGET))
|
||||
#define GTK_IS_GST_GL_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GTK_TYPE_GST_GL_WIDGET))
|
||||
#define GTK_GST_GL_WIDGET_CAST(obj) ((GtkGstGLWidget*)(obj))
|
||||
|
||||
typedef struct _GtkGstGLWidget GtkGstGLWidget;
|
||||
typedef struct _GtkGstGLWidgetClass GtkGstGLWidgetClass;
|
||||
typedef struct _GtkGstGLWidgetPrivate GtkGstGLWidgetPrivate;
|
||||
|
||||
/**
|
||||
* GtkGstGLWidget:
|
||||
*
|
||||
* Opaque #GtkGstGLWidget object
|
||||
*/
|
||||
struct _GtkGstGLWidget
|
||||
{
|
||||
/* <private> */
|
||||
GtkGstBaseWidget base;
|
||||
|
||||
GtkGstGLWidgetPrivate *priv;
|
||||
};
|
||||
|
||||
/**
|
||||
* GtkGstGLWidgetClass:
|
||||
*
|
||||
* The #GtkGstGLWidgetClass struct only contains private data
|
||||
*/
|
||||
struct _GtkGstGLWidgetClass
|
||||
{
|
||||
/* <private> */
|
||||
GtkGstBaseWidgetClass base_class;
|
||||
};
|
||||
|
||||
GtkWidget * gtk_gst_gl_widget_new (void);
|
||||
|
||||
gboolean gtk_gst_gl_widget_init_winsys (GtkGstGLWidget * widget);
|
||||
GstGLDisplay * gtk_gst_gl_widget_get_display (GtkGstGLWidget * widget);
|
||||
GstGLContext * gtk_gst_gl_widget_get_context (GtkGstGLWidget * widget);
|
||||
GstGLContext * gtk_gst_gl_widget_get_gtk_context (GtkGstGLWidget * widget);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_GST_GL_WIDGET_H__ */
|
35
lib/gst/clapper/meson.build
vendored
35
lib/gst/clapper/meson.build
vendored
@@ -1,3 +1,5 @@
|
||||
gnome = import('gnome')
|
||||
|
||||
gstclapper_sources = [
|
||||
'gstclapper.c',
|
||||
'gstclapper-signal-dispatcher.c',
|
||||
@@ -6,13 +8,12 @@ gstclapper_sources = [
|
||||
'gstclapper-g-main-context-signal-dispatcher.c',
|
||||
'gstclapper-video-overlay-video-renderer.c',
|
||||
'gstclapper-visualization.c',
|
||||
'gstclapper-mpris.c',
|
||||
'gstclapper-gtk4-plugin.c',
|
||||
|
||||
'gtk4/gstgtkbasesink.c',
|
||||
'gtk4/gstclapperglsink.c',
|
||||
'gtk4/gstgtkutils.c',
|
||||
'gtk4/gtkgstbasewidget.c',
|
||||
'gtk4/gstgtkglsink.c',
|
||||
'gtk4/gtkgstglwidget.c',
|
||||
'gtk4/gtkclapperglwidget.c',
|
||||
]
|
||||
gstclapper_headers = [
|
||||
'clapper.h',
|
||||
@@ -25,6 +26,7 @@ gstclapper_headers = [
|
||||
'gstclapper-g-main-context-signal-dispatcher.h',
|
||||
'gstclapper-video-overlay-video-renderer.h',
|
||||
'gstclapper-visualization.h',
|
||||
'gstclapper-mpris.h',
|
||||
'gstclapper-gtk4-plugin.h',
|
||||
]
|
||||
gstclapper_defines = [
|
||||
@@ -32,7 +34,6 @@ gstclapper_defines = [
|
||||
'-DBUILDING_GST_CLAPPER',
|
||||
'-DGST_USE_UNSTABLE_API',
|
||||
'-DHAVE_GTK_GL',
|
||||
'-DBUILD_FOR_GTK4',
|
||||
]
|
||||
gtk_deps = [gstgl_dep, gstglproto_dep]
|
||||
have_gtk_gl_windowing = false
|
||||
@@ -43,10 +44,13 @@ if not gtk4_dep.version().version_compare('>=4.0.0')
|
||||
error('GTK4 version on this system is too old')
|
||||
endif
|
||||
|
||||
if gst_gl_have_window_x11 and gst_gl_have_platform_glx
|
||||
if gst_gl_have_window_x11 and (gst_gl_have_platform_egl or gst_gl_have_platform_glx)
|
||||
gtk_x11_dep = dependency('gtk4-x11', required : false)
|
||||
if gtk_x11_dep.found()
|
||||
gtk_deps += [gtk_x11_dep, gstglx11_dep]
|
||||
gtk_deps += gtk_x11_dep
|
||||
if gst_gl_have_platform_glx
|
||||
gtk_deps += gstglx11_dep
|
||||
endif
|
||||
have_gtk_gl_windowing = true
|
||||
endif
|
||||
endif
|
||||
@@ -54,24 +58,35 @@ endif
|
||||
if gst_gl_have_window_wayland and gst_gl_have_platform_egl
|
||||
gtk_wayland_dep = dependency('gtk4-wayland', required : false)
|
||||
if gtk_wayland_dep.found()
|
||||
gtk_deps += [gtk_wayland_dep, gstglegl_dep, gstglwayland_dep]
|
||||
gtk_deps += [gtk_wayland_dep, gstglwayland_dep]
|
||||
have_gtk_gl_windowing = true
|
||||
endif
|
||||
endif
|
||||
|
||||
if gst_gl_have_platform_egl
|
||||
gtk_deps += gstglegl_dep
|
||||
endif
|
||||
|
||||
if not have_gtk_gl_windowing
|
||||
error('GTK4 widget requires GL windowing')
|
||||
endif
|
||||
|
||||
gstclapper_mpris_gdbus = gnome.gdbus_codegen('gstclapper-mpris-gdbus',
|
||||
sources: '../../../data/gstclapper-mpris-gdbus.xml',
|
||||
interface_prefix: 'org.mpris.',
|
||||
namespace: 'GstClapperMpris'
|
||||
)
|
||||
|
||||
gstclapper = library('gstclapper-' + api_version,
|
||||
gstclapper_sources,
|
||||
gstclapper_sources + gstclapper_mpris_gdbus,
|
||||
c_args : gstclapper_defines,
|
||||
link_args : noseh_link_args,
|
||||
include_directories : [configinc, libsinc],
|
||||
version : libversion,
|
||||
install : true,
|
||||
install_dir : clapper_libdir,
|
||||
dependencies : [gtk4_dep, gstbase_dep, gstvideo_dep, gstaudio_dep,
|
||||
dependencies : [gtk4_dep, glib_dep, gio_dep,
|
||||
gstbase_dep, gstvideo_dep, gstaudio_dep,
|
||||
gsttag_dep, gstpbutils_dep, libm] + gtk_deps,
|
||||
)
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
project('com.github.rafostar.Clapper', 'c', 'cpp',
|
||||
version: '0.1.0',
|
||||
version: '0.3.0',
|
||||
meson_version: '>= 0.50.0',
|
||||
license: 'GPL3',
|
||||
default_options: [
|
||||
|
@@ -2,7 +2,7 @@ Format: 3.0 (quilt)
|
||||
Source: clapper
|
||||
Binary: clapper
|
||||
Architecture: any
|
||||
Version: 0.1.0
|
||||
Version: 0.3.0
|
||||
Maintainer: Rafostar <rafostar.github@gmail.com>
|
||||
Build-Depends: debhelper (>= 10),
|
||||
meson (>= 0.50),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
clapper (0.1.0) unstable; urgency=low
|
||||
clapper (0.3.0) unstable; urgency=low
|
||||
|
||||
* New version
|
||||
|
||||
-- Rafostar <rafostar.github@gmail.com> Fri, 26 Feb 2021 09:39:00 +0100
|
||||
-- Rafostar <rafostar.github@gmail.com> Fri, 18 Jun 2021 09:39:00 +0100
|
||||
|
2
pkgs/flatpak/.gitignore
vendored
2
pkgs/flatpak/.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
build/
|
||||
builddir/
|
||||
repo/
|
||||
.flatpak-builder/
|
||||
com.github.rafostar.Clapper.flatpak
|
||||
flathub/com.github.rafostar.Clapper.json
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"app-id": "com.github.rafostar.Clapper",
|
||||
"runtime": "org.gnome.Platform",
|
||||
"runtime-version": "3.38",
|
||||
"runtime-version": "40",
|
||||
"sdk": "org.gnome.Sdk",
|
||||
"command": "com.github.rafostar.Clapper",
|
||||
"finish-args": [
|
||||
@@ -13,39 +13,40 @@
|
||||
"--share=network",
|
||||
"--device=all",
|
||||
"--filesystem=xdg-videos",
|
||||
"--own-name=org.mpris.MediaPlayer2.Clapper",
|
||||
"--talk-name=org.gnome.Shell",
|
||||
"--env=GST_PLUGIN_SYSTEM_PATH=/app/lib/gstreamer-1.0",
|
||||
"--env=GST_VAAPI_ALL_DRIVERS=1"
|
||||
],
|
||||
"modules": [
|
||||
"lib/glib-networking.json",
|
||||
"shared-modules/gudev/gudev.json",
|
||||
"lib/pango.json",
|
||||
"lib/libsass.json",
|
||||
"lib/sassc.json",
|
||||
"lib/gtk4.json",
|
||||
"lib/liba52.json",
|
||||
"lib/libmpeg2.json",
|
||||
"lib/libdvdcss.json",
|
||||
"lib/libdvdread.json",
|
||||
"lib/libdvdnav.json",
|
||||
"lib/libass.json",
|
||||
"lib/ffmpeg.json",
|
||||
"lib/uchardet.json",
|
||||
"gstreamer-1.0/gstreamer.json",
|
||||
"gstreamer-1.0/gst-plugins-base.json",
|
||||
"gstreamer-1.0/gst-plugins-good.json",
|
||||
"gstreamer-1.0/gst-plugins-bad.json",
|
||||
"gstreamer-1.0/gst-plugins-ugly.json",
|
||||
"gstreamer-1.0/gst-libav.json",
|
||||
"gstreamer-1.0/gstreamer-vaapi.json",
|
||||
"flathub/lib/glib-networking.json",
|
||||
"flathub/shared-modules/gudev/gudev.json",
|
||||
"flathub/lib/pango.json",
|
||||
"flathub/lib/libsass.json",
|
||||
"flathub/lib/sassc.json",
|
||||
"flathub/lib/gtk4.json",
|
||||
"flathub/lib/liba52.json",
|
||||
"flathub/lib/libmpeg2.json",
|
||||
"flathub/lib/libdvdcss.json",
|
||||
"flathub/lib/libdvdread.json",
|
||||
"flathub/lib/libdvdnav.json",
|
||||
"flathub/lib/libass.json",
|
||||
"flathub/lib/ffmpeg.json",
|
||||
"flathub/lib/uchardet.json",
|
||||
"flathub/gstreamer-1.0/gstreamer.json",
|
||||
"flathub/gstreamer-1.0/gst-plugins-base.json",
|
||||
"flathub/gstreamer-1.0/gst-plugins-good.json",
|
||||
"flathub/gstreamer-1.0/gst-plugins-bad.json",
|
||||
"flathub/gstreamer-1.0/gst-plugins-ugly.json",
|
||||
"flathub/gstreamer-1.0/gst-libav.json",
|
||||
"flathub/gstreamer-1.0/gstreamer-vaapi.json",
|
||||
{
|
||||
"name": "clapper",
|
||||
"buildsystem": "meson",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/Rafostar/clapper.git"
|
||||
"type": "dir",
|
||||
"path": "../../."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
1
pkgs/flatpak/flathub
Submodule
1
pkgs/flatpak/flathub
Submodule
Submodule pkgs/flatpak/flathub added at 2a3ed05245
@@ -1,30 +0,0 @@
|
||||
From 2c371f17af1695bd42f572d5ccdb837152b8b67a Mon Sep 17 00:00:00 2001
|
||||
From: Thomas Coldrick <othko97@gmail.com>
|
||||
Date: Thu, 8 Nov 2018 17:46:53 +0000
|
||||
Subject: [PATCH] gst-libav-stop-caching-codecs
|
||||
|
||||
---
|
||||
ext/libav/gstav.c | 16 ++++++++++++++++
|
||||
1 file changed, 16 insertions(+)
|
||||
|
||||
diff --git a/ext/libav/gstav.c b/ext/libav/gstav.c
|
||||
index 2a88230..bfd19a1 100644
|
||||
--- a/ext/libav/gstav.c
|
||||
+++ b/ext/libav/gstav.c
|
||||
@@ -155,6 +155,13 @@ plugin_init (GstPlugin * plugin)
|
||||
/* build global ffmpeg param/property info */
|
||||
gst_ffmpeg_cfg_init ();
|
||||
|
||||
+ gst_plugin_add_dependency_simple (plugin, NULL,
|
||||
+ "/app/lib",
|
||||
+ "libavcodec.so.58,"
|
||||
+ "libavformat.so.58,"
|
||||
+ "libswscale.so.5",
|
||||
+ GST_PLUGIN_DEPENDENCY_FLAG_NONE);
|
||||
+
|
||||
gst_ffmpegaudenc_register (plugin);
|
||||
gst_ffmpegvidenc_register (plugin);
|
||||
gst_ffmpegauddec_register (plugin);
|
||||
--
|
||||
2.19.1
|
||||
|
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "gst-libav",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"-Ddoc=disabled",
|
||||
"-Dtests=disabled"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-libav.git",
|
||||
"tag": "1.18.1",
|
||||
"commit": "097313530cae4a49437a779a9ded0ade8113c26b"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gst-libav-stop-caching-codecs.patch"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,89 +0,0 @@
|
||||
From ab9ceccc8b7f0591f580abfa6901d27c49812a94 Mon Sep 17 00:00:00 2001
|
||||
From: Rafostar <40623528+Rafostar@users.noreply.github.com>
|
||||
Date: Sun, 10 Jan 2021 20:22:43 +0100
|
||||
Subject: [PATCH 1/2] assrender: fix mimetype detection
|
||||
|
||||
Previously gst_structure_has_name was used to get a string to compare with supported mimetypes.
|
||||
This is incorrect as above function returns a user defined structure name which is
|
||||
not the structure mimetype value.
|
||||
---
|
||||
ext/assrender/gstassrender.c | 21 ++++++++++++---------
|
||||
1 file changed, 12 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/ext/assrender/gstassrender.c b/ext/assrender/gstassrender.c
|
||||
index e6d31985b..a69d3fe78 100644
|
||||
--- a/ext/assrender/gstassrender.c
|
||||
+++ b/ext/assrender/gstassrender.c
|
||||
@@ -1557,7 +1557,7 @@ gst_ass_render_handle_tag_sample (GstAssRender * render, GstSample * sample)
|
||||
const GstStructure *structure;
|
||||
gboolean valid_mimetype, valid_extension;
|
||||
guint i;
|
||||
- const gchar *filename;
|
||||
+ const gchar *mimetype, *filename;
|
||||
|
||||
buf = gst_sample_get_buffer (sample);
|
||||
structure = gst_sample_get_info (sample);
|
||||
@@ -1565,20 +1565,23 @@ gst_ass_render_handle_tag_sample (GstAssRender * render, GstSample * sample)
|
||||
if (!buf || !structure)
|
||||
return;
|
||||
|
||||
+ filename = gst_structure_get_string (structure, "filename");
|
||||
+ if (!filename)
|
||||
+ return;
|
||||
+
|
||||
valid_mimetype = FALSE;
|
||||
valid_extension = FALSE;
|
||||
|
||||
- for (i = 0; i < G_N_ELEMENTS (mimetypes); i++) {
|
||||
- if (gst_structure_has_name (structure, mimetypes[i])) {
|
||||
- valid_mimetype = TRUE;
|
||||
- break;
|
||||
+ mimetype = gst_structure_get_string (structure, "mimetype");
|
||||
+ if (mimetype) {
|
||||
+ for (i = 0; i < G_N_ELEMENTS (mimetypes); i++) {
|
||||
+ if (strcmp (mimetype, mimetypes[i]) == 0) {
|
||||
+ valid_mimetype = TRUE;
|
||||
+ break;
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
- filename = gst_structure_get_string (structure, "filename");
|
||||
- if (!filename)
|
||||
- return;
|
||||
-
|
||||
if (!valid_mimetype) {
|
||||
guint len = strlen (filename);
|
||||
const gchar *extension = filename + len - 4;
|
||||
--
|
||||
2.28.0
|
||||
|
||||
|
||||
From fd7d46171b2abcd3ac247491f01a91444e7b95b2 Mon Sep 17 00:00:00 2001
|
||||
From: Rafostar <40623528+Rafostar@users.noreply.github.com>
|
||||
Date: Sun, 10 Jan 2021 20:26:58 +0100
|
||||
Subject: [PATCH 2/2] assrender: add "vnd.ms-opentype" to supported mimetypes
|
||||
|
||||
The "application/vnd.ms-opentype" mimetype is commonly used mimetype
|
||||
for fonts with .otf extension, handle it without checking the file extension.
|
||||
---
|
||||
ext/assrender/gstassrender.c | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ext/assrender/gstassrender.c b/ext/assrender/gstassrender.c
|
||||
index a69d3fe78..96b062c50 100644
|
||||
--- a/ext/assrender/gstassrender.c
|
||||
+++ b/ext/assrender/gstassrender.c
|
||||
@@ -1546,7 +1546,8 @@ gst_ass_render_handle_tag_sample (GstAssRender * render, GstSample * sample)
|
||||
static const gchar *mimetypes[] = {
|
||||
"application/x-font-ttf",
|
||||
"application/x-font-otf",
|
||||
- "application/x-truetype-font"
|
||||
+ "application/x-truetype-font",
|
||||
+ "application/vnd.ms-opentype"
|
||||
};
|
||||
static const gchar *extensions[] = {
|
||||
".otf",
|
||||
--
|
||||
2.28.0
|
||||
|
@@ -1,30 +0,0 @@
|
||||
From 1c8538d8f8c2181106d626d67784af6db094036e Mon Sep 17 00:00:00 2001
|
||||
From: Rafostar <rafostar.github@gmail.com>
|
||||
Date: Thu, 19 Nov 2020 18:03:11 +0100
|
||||
Subject: [PATCH] assrender: fix smooth scaling by disabling hinting
|
||||
|
||||
When ass hinting value is set to anything other than NONE,
|
||||
subtitles cannot use smooth scaling, thus all animations will jitter.
|
||||
|
||||
The libass author warns about possibility of breaking some scripts when it is enabled,
|
||||
so lets do what is recommended and disable it to get the smooth scaling working.
|
||||
---
|
||||
ext/assrender/gstassrender.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ext/assrender/gstassrender.c b/ext/assrender/gstassrender.c
|
||||
index e99458bf29..111987b9d8 100644
|
||||
--- a/ext/assrender/gstassrender.c
|
||||
+++ b/ext/assrender/gstassrender.c
|
||||
@@ -916,7 +916,7 @@ gst_ass_render_negotiate (GstAssRender * render, GstCaps * caps)
|
||||
ass_set_pixel_aspect (render->ass_renderer,
|
||||
(gdouble) render->info.par_n / (gdouble) render->info.par_d);
|
||||
ass_set_font_scale (render->ass_renderer, 1.0);
|
||||
- ass_set_hinting (render->ass_renderer, ASS_HINTING_LIGHT);
|
||||
+ ass_set_hinting (render->ass_renderer, ASS_HINTING_NONE);
|
||||
|
||||
ass_set_fonts (render->ass_renderer, "Arial", "sans-serif", 1, NULL, 1);
|
||||
ass_set_fonts (render->ass_renderer, NULL, "Sans", 1, NULL, 1);
|
||||
--
|
||||
GitLab
|
||||
|
@@ -1,75 +0,0 @@
|
||||
From f9af93d841546ca7898350ae14ed57448b24a644 Mon Sep 17 00:00:00 2001
|
||||
From: Seungha Yang <seungha@centricular.com>
|
||||
Date: Sat, 14 Nov 2020 03:16:07 +0900
|
||||
Subject: [PATCH 1/2] codecs: h264decoder: Don't give up to decode due to
|
||||
missing reference picture
|
||||
|
||||
Missing reference picture is very common thing for broken/malformed stream.
|
||||
Decoder should be able to keep decoding if it's not a very critical error.
|
||||
|
||||
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1809>
|
||||
---
|
||||
gst-libs/gst/codecs/gsth264decoder.c | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/gst-libs/gst/codecs/gsth264decoder.c b/gst-libs/gst/codecs/gsth264decoder.c
|
||||
index e6d20af208..40446d92df 100644
|
||||
--- a/gst-libs/gst/codecs/gsth264decoder.c
|
||||
+++ b/gst-libs/gst/codecs/gsth264decoder.c
|
||||
@@ -2354,7 +2354,7 @@ modify_ref_pic_list (GstH264Decoder * self, int list)
|
||||
if (!pic) {
|
||||
GST_WARNING_OBJECT (self, "Malformed stream, no pic num %d",
|
||||
pic_num_lx);
|
||||
- return FALSE;
|
||||
+ break;
|
||||
}
|
||||
shift_right_and_insert (ref_pic_listx, ref_idx_lx,
|
||||
num_ref_idx_lX_active_minus1, pic);
|
||||
@@ -2380,7 +2380,7 @@ modify_ref_pic_list (GstH264Decoder * self, int list)
|
||||
if (!pic) {
|
||||
GST_WARNING_OBJECT (self, "Malformed stream, no pic num %d",
|
||||
list_mod->value.long_term_pic_num);
|
||||
- return FALSE;
|
||||
+ break;
|
||||
}
|
||||
shift_right_and_insert (ref_pic_listx, ref_idx_lx,
|
||||
num_ref_idx_lX_active_minus1, pic);
|
||||
--
|
||||
GitLab
|
||||
|
||||
|
||||
From 9011a58491b089461762a8f550892de434af5c29 Mon Sep 17 00:00:00 2001
|
||||
From: Seungha Yang <seungha@centricular.com>
|
||||
Date: Sat, 14 Nov 2020 03:20:19 +0900
|
||||
Subject: [PATCH 2/2] vah264dec: Allow missing reference picture
|
||||
|
||||
baseclass might provide reference picture list with null picture.
|
||||
Ensure picture before filling picture information.
|
||||
|
||||
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1809>
|
||||
---
|
||||
sys/va/gstvah264dec.c | 8 +++++++-
|
||||
1 file changed, 7 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/sys/va/gstvah264dec.c b/sys/va/gstvah264dec.c
|
||||
index e90f84bb44..184af430fa 100644
|
||||
--- a/sys/va/gstvah264dec.c
|
||||
+++ b/sys/va/gstvah264dec.c
|
||||
@@ -198,7 +198,13 @@ _fill_ref_pic_list (VAPictureH264 va_reflist[32], GArray * reflist)
|
||||
|
||||
for (i = 0; i < reflist->len; i++) {
|
||||
GstH264Picture *picture = g_array_index (reflist, GstH264Picture *, i);
|
||||
- _fill_vaapi_pic (&va_reflist[i], picture);
|
||||
+
|
||||
+ if (picture) {
|
||||
+ _fill_vaapi_pic (&va_reflist[i], picture);
|
||||
+ } else {
|
||||
+ /* list might include null picture if reference picture was missing */
|
||||
+ _init_vaapi_pic (&va_reflist[i]);
|
||||
+ }
|
||||
}
|
||||
|
||||
for (; i < 32; i++)
|
||||
--
|
||||
GitLab
|
||||
|
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "gst-plugins-bad",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"-Ddoc=disabled",
|
||||
"-Dexamples=disabled",
|
||||
"-Dtests=disabled",
|
||||
"-Dnls=disabled",
|
||||
"-Dgobject-cast-checks=disabled",
|
||||
"-Dglib-asserts=disabled",
|
||||
"-Dglib-checks=disabled",
|
||||
"-Dextra-checks=disabled",
|
||||
|
||||
"-Dvulkan=disabled",
|
||||
"-Dwebrtc=disabled",
|
||||
"-Dwasapi=disabled",
|
||||
"-Dwasapi2=disabled",
|
||||
"-Dwinks=disabled",
|
||||
"-Dwinscreencap=disabled"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad.git",
|
||||
"tag": "1.18.1",
|
||||
"commit": "e5c3c106a2da607953fea36e3a253b382c939684"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gst-plugins-bad-vah264dec-fix-seeking-errors.patch"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gst-plugins-bad-assrender-smooth-scaling.patch"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gst-plugins-bad-assrender-fix-mimetype-detection.patch"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,142 +0,0 @@
|
||||
From 61a66babede5a587783a1d4eb28e950a755ff362 Mon Sep 17 00:00:00 2001
|
||||
From: Rafostar <rafostar.github@gmail.com>
|
||||
Date: Wed, 25 Nov 2020 14:44:21 +0100
|
||||
Subject: [PATCH] subparse: Autodetect subtitle text encoding
|
||||
|
||||
Use "uchardet" to guess the subtitle text encoding if it is not in UTF-8
|
||||
or manually specified instead of blindly guessing its "ISO-8859-15".
|
||||
The "uchardet" dependency is optional and when is not available at
|
||||
compile time, then old behaviour will be used.
|
||||
---
|
||||
gst/subparse/gstsubparse.c | 58 +++++++++++++++++++++++++++++++++-----
|
||||
gst/subparse/meson.build | 12 ++++++--
|
||||
2 files changed, 61 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/gst/subparse/gstsubparse.c b/gst/subparse/gstsubparse.c
|
||||
index 382e430f2..42283d2d1 100644
|
||||
--- a/gst/subparse/gstsubparse.c
|
||||
+++ b/gst/subparse/gstsubparse.c
|
||||
@@ -31,6 +31,10 @@
|
||||
#include <sys/types.h>
|
||||
#include <glib.h>
|
||||
|
||||
+#if defined(HAVE_UCHARDET)
|
||||
+#include <uchardet.h>
|
||||
+#endif
|
||||
+
|
||||
#include "gstsubparse.h"
|
||||
#include "gstssaparse.h"
|
||||
#include "samiparse.h"
|
||||
@@ -148,8 +152,9 @@ gst_sub_parse_class_init (GstSubParseClass * klass)
|
||||
"Encoding to assume if input subtitles are not in UTF-8 or any other "
|
||||
"Unicode encoding. If not set, the GST_SUBTITLE_ENCODING environment "
|
||||
"variable will be checked for an encoding to use. If that is not set "
|
||||
- "either, ISO-8859-15 will be assumed.", DEFAULT_ENCODING,
|
||||
- G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
+ "either, then if plugin was build with uchardet support it will be "
|
||||
+ "used to guess the encoding, otherwise ISO-8859-15 will be assumed.",
|
||||
+ DEFAULT_ENCODING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_property (object_class, PROP_VIDEOFPS,
|
||||
gst_param_spec_fraction ("video-fps", "Video framerate",
|
||||
@@ -439,6 +444,35 @@ detect_encoding (const gchar * str, gsize len)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+static gchar *
|
||||
+uchardet_detect_encoding (const gchar * str, gsize len)
|
||||
+{
|
||||
+ gchar *charset = NULL;
|
||||
+ gint retval;
|
||||
+
|
||||
+#if defined(HAVE_UCHARDET)
|
||||
+ uchardet_t handle = uchardet_new ();
|
||||
+ retval = uchardet_handle_data (handle, str, len);
|
||||
+
|
||||
+ GST_DEBUG ("detecting encoding with uchardet using %li characters", len);
|
||||
+
|
||||
+ if (retval != 0) {
|
||||
+ GST_WARNING ("could not handle data with uchardet");
|
||||
+ } else {
|
||||
+ uchardet_data_end (handle);
|
||||
+ charset = g_strdup (uchardet_get_charset (handle));
|
||||
+
|
||||
+ if (charset == NULL || *charset == '\0')
|
||||
+ GST_WARNING ("uchardet could not detect encoding");
|
||||
+ else
|
||||
+ GST_INFO ("uchardet detected encoding: %s", charset);
|
||||
+ }
|
||||
+ uchardet_delete (handle);
|
||||
+#endif
|
||||
+
|
||||
+ return charset;
|
||||
+}
|
||||
+
|
||||
static gchar *
|
||||
convert_encoding (GstSubParse * self, const gchar * str, gsize len,
|
||||
gsize * consumed)
|
||||
@@ -481,11 +515,18 @@ convert_encoding (GstSubParse * self, const gchar * str, gsize len,
|
||||
encoding = g_getenv ("GST_SUBTITLE_ENCODING");
|
||||
}
|
||||
if (encoding == NULL || *encoding == '\0') {
|
||||
- /* if local encoding is UTF-8 and no encoding specified
|
||||
- * via the environment variable, assume ISO-8859-15 */
|
||||
- if (g_get_charset (&encoding)) {
|
||||
+ /* no encoding specified via the environment variable either,
|
||||
+ * so try to autodetect with uchardet */
|
||||
+ encoding = uchardet_detect_encoding (str, len);
|
||||
+ }
|
||||
+
|
||||
+ /* if uchardet failed and local encoding is UTF-8, assume ISO-8859-15 */
|
||||
+ if (encoding == NULL || *encoding == '\0') {
|
||||
+ if (g_get_charset (&encoding))
|
||||
encoding = "ISO-8859-15";
|
||||
- }
|
||||
+ } else {
|
||||
+ /* reuse the detected encoding from now on */
|
||||
+ self->detected_encoding = g_strdup (encoding);
|
||||
}
|
||||
|
||||
ret = gst_convert_to_utf8 (str, len, encoding, consumed, &err);
|
||||
@@ -2159,7 +2200,10 @@ gst_subparse_type_find (GstTypeFind * tf, gpointer private)
|
||||
enc = g_getenv ("GST_SUBTITLE_ENCODING");
|
||||
if (enc == NULL || *enc == '\0') {
|
||||
/* if local encoding is UTF-8 and no encoding specified
|
||||
- * via the environment variable, assume ISO-8859-15 */
|
||||
+ * via the environment variable, assume ISO-8859-15
|
||||
+ *
|
||||
+ * Encoding here is only used for type find, so no need
|
||||
+ * to run through uchardet at this point */
|
||||
if (g_get_charset (&enc)) {
|
||||
enc = "ISO-8859-15";
|
||||
}
|
||||
diff --git a/gst/subparse/meson.build b/gst/subparse/meson.build
|
||||
index 9a76601f0..2dcf8830f 100644
|
||||
--- a/gst/subparse/meson.build
|
||||
+++ b/gst/subparse/meson.build
|
||||
@@ -6,12 +6,20 @@ subparse_sources = [
|
||||
'mpl2parse.c',
|
||||
'qttextparse.c',
|
||||
]
|
||||
+subparse_defines = []
|
||||
+subparse_optional_deps = []
|
||||
+
|
||||
+subparse_uchardet_dep = dependency('uchardet', required : false)
|
||||
+if subparse_uchardet_dep.found()
|
||||
+ subparse_defines += '-DHAVE_UCHARDET'
|
||||
+ subparse_optional_deps += subparse_uchardet_dep
|
||||
+endif
|
||||
|
||||
gstsubparse = library('gstsubparse',
|
||||
subparse_sources,
|
||||
- c_args : gst_plugins_base_args,
|
||||
+ c_args : gst_plugins_base_args + subparse_defines,
|
||||
include_directories: [configinc, libsinc],
|
||||
- dependencies : [gst_base_dep],
|
||||
+ dependencies : [gst_base_dep] + subparse_optional_deps,
|
||||
install : true,
|
||||
install_dir : plugins_install_dir,
|
||||
)
|
||||
--
|
||||
2.26.2
|
||||
|
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "gst-plugins-base",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"--wrap-mode=nofallback",
|
||||
|
||||
"-Ddoc=disabled",
|
||||
"-Dexamples=disabled",
|
||||
"-Dtests=disabled",
|
||||
"-Dnls=disabled",
|
||||
"-Dgobject-cast-checks=disabled",
|
||||
"-Dglib-asserts=disabled",
|
||||
"-Dglib-checks=disabled",
|
||||
|
||||
"-Dgl_api=opengl,gles2",
|
||||
"-Dgl_platform=egl,glx"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-base.git",
|
||||
"tag": "1.18.1",
|
||||
"commit": "4013b8003e78971dd01b055066c12f8aaadb8897"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gst-plugins-base-autodetect-subtitle-text-encoding.patch"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,67 +0,0 @@
|
||||
From b2ad7c68c3478c433a0ede4aed6afb2f0b32702c Mon Sep 17 00:00:00 2001
|
||||
From: Rafostar <40623528+Rafostar@users.noreply.github.com>
|
||||
Date: Sun, 10 Jan 2021 15:44:45 +0100
|
||||
Subject: [PATCH] matroska: fix attachments detection in large data blocks
|
||||
|
||||
Due to max block size limit being set to 15MB, large
|
||||
attachments (fonts of few MB in size) were undetected
|
||||
as attachments consist of single data block. Raise max
|
||||
data block limit to 30MB to fix that.
|
||||
---
|
||||
gst/matroska/matroska-demux.c | 34 ++++++++++++++++------------------
|
||||
1 file changed, 16 insertions(+), 18 deletions(-)
|
||||
|
||||
diff --git a/gst/matroska/matroska-demux.c b/gst/matroska/matroska-demux.c
|
||||
index 4d0234743..ce906e5a3 100644
|
||||
--- a/gst/matroska/matroska-demux.c
|
||||
+++ b/gst/matroska/matroska-demux.c
|
||||
@@ -5115,30 +5115,28 @@ gst_matroska_demux_parse_contents (GstMatroskaDemux * demux, GstEbmlRead * ebml)
|
||||
}
|
||||
|
||||
#define GST_FLOW_OVERFLOW GST_FLOW_CUSTOM_ERROR
|
||||
-
|
||||
-#define MAX_BLOCK_SIZE (15 * 1024 * 1024)
|
||||
+#define MAX_BLOCK_SIZE (60 * 1024 * 1024)
|
||||
|
||||
static inline GstFlowReturn
|
||||
gst_matroska_demux_check_read_size (GstMatroskaDemux * demux, guint64 bytes)
|
||||
{
|
||||
- if (G_UNLIKELY (bytes > MAX_BLOCK_SIZE)) {
|
||||
- /* only a few blocks are expected/allowed to be large,
|
||||
- * and will be recursed into, whereas others will be read and must fit */
|
||||
- if (demux->streaming) {
|
||||
- /* fatal in streaming case, as we can't step over easily */
|
||||
- GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL),
|
||||
- ("reading large block of size %" G_GUINT64_FORMAT " not supported; "
|
||||
- "file might be corrupt.", bytes));
|
||||
- return GST_FLOW_ERROR;
|
||||
- } else {
|
||||
- /* indicate higher level to quietly give up */
|
||||
- GST_DEBUG_OBJECT (demux,
|
||||
- "too large block of size %" G_GUINT64_FORMAT, bytes);
|
||||
- return GST_FLOW_ERROR;
|
||||
- }
|
||||
- } else {
|
||||
+ if (G_LIKELY (bytes <= MAX_BLOCK_SIZE))
|
||||
return GST_FLOW_OK;
|
||||
+
|
||||
+ /* only a few blocks are expected/allowed to be large,
|
||||
+ * and will be recursed into, whereas others will be read and must fit */
|
||||
+ if (demux->streaming) {
|
||||
+ /* fatal in streaming case, as we can't step over easily */
|
||||
+ GST_ELEMENT_ERROR (demux, STREAM, DEMUX, (NULL),
|
||||
+ ("reading large block of size %" G_GUINT64_FORMAT " not supported; "
|
||||
+ "file might be corrupt.", bytes));
|
||||
+ } else {
|
||||
+ /* indicate higher level to quietly give up */
|
||||
+ GST_DEBUG_OBJECT (demux, "too large block of size %" G_GUINT64_FORMAT,
|
||||
+ bytes);
|
||||
}
|
||||
+
|
||||
+ return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
/* returns TRUE if we truly are in error state, and should give up */
|
||||
--
|
||||
2.29.2
|
||||
|
@@ -1,36 +0,0 @@
|
||||
From 4e5b2b0c3aeefffdd9613e33678cade25fac3fe4 Mon Sep 17 00:00:00 2001
|
||||
From: Rafostar <rafostar.github@gmail.com>
|
||||
Date: Sun, 10 Jan 2021 19:55:31 +0100
|
||||
Subject: [PATCH] matroska: treat non-image structure as attachment and set
|
||||
mimetype
|
||||
|
||||
Otherwise each structure is named as GstTagImageInfo even if it does not contain any images
|
||||
which is misleading. Also set the structure mimetype to fix assrender fonts detection.
|
||||
---
|
||||
gst/matroska/matroska-read-common.c | 9 ++++++---
|
||||
1 file changed, 6 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/gst/matroska/matroska-read-common.c b/gst/matroska/matroska-read-common.c
|
||||
index 90d6e38e1..628e19669 100644
|
||||
--- a/gst/matroska/matroska-read-common.c
|
||||
+++ b/gst/matroska/matroska-read-common.c
|
||||
@@ -851,10 +851,13 @@ gst_matroska_read_common_parse_attached_file (GstMatroskaReadCommon * common,
|
||||
}
|
||||
|
||||
/* Set filename and description in the info */
|
||||
- if (info == NULL)
|
||||
- info = gst_structure_new_empty ("GstTagImageInfo");
|
||||
-
|
||||
+ if (info == NULL) {
|
||||
+ const gchar *structure_name = (image_type != GST_TAG_IMAGE_TYPE_NONE) ?
|
||||
+ "GstTagImageInfo" : "GstTagAttachmentInfo";
|
||||
+ info = gst_structure_new_empty (structure_name);
|
||||
+ }
|
||||
gst_structure_set (info, "filename", G_TYPE_STRING, filename, NULL);
|
||||
+ gst_structure_set (info, "mimetype", G_TYPE_STRING, mimetype, NULL);
|
||||
if (description)
|
||||
gst_structure_set (info, "description", G_TYPE_STRING, description, NULL);
|
||||
|
||||
--
|
||||
2.28.0
|
||||
|
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"name": "gst-plugins-good",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"-Ddoc=disabled",
|
||||
"-Dexamples=disabled",
|
||||
"-Dtests=disabled",
|
||||
"-Dnls=disabled",
|
||||
"-Dgobject-cast-checks=disabled",
|
||||
"-Dglib-asserts=disabled",
|
||||
"-Dglib-checks=disabled",
|
||||
|
||||
"-Dgtk3=disabled"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-good.git",
|
||||
"tag": "1.18.1",
|
||||
"commit": "7c44cdb0e00dd1c9932d8e5194b09fcf4e1e6fc1"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gst-plugins-good-matroska-fix-attachments-detection.patch"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gst-plugins-good-matroska-set-attachment-mimetype.patch"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "gst-plugins-ugly",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"-Ddoc=disabled",
|
||||
"-Dnls=disabled",
|
||||
"-Dtests=disabled",
|
||||
"-Dgobject-cast-checks=disabled",
|
||||
"-Dglib-asserts=disabled",
|
||||
"-Dglib-checks=disabled",
|
||||
|
||||
"-Dmpeg2dec=enabled"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-ugly.git",
|
||||
"tag": "1.18.1",
|
||||
"commit": "720672eed30b3be47b2f26d67554786c0d3693ad"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,83 +0,0 @@
|
||||
From 65fc08032a41ae8779d1845dce2c00b1efa2955c Mon Sep 17 00:00:00 2001
|
||||
From: Rafostar <rafostar.github@gmail.com>
|
||||
Date: Tue, 22 Dec 2020 15:08:21 +0100
|
||||
Subject: [PATCH] glx: Iterate over FBConfig and select 8 bit color size
|
||||
|
||||
---
|
||||
gst-libs/gst/vaapi/gstvaapiutils_glx.c | 40 ++++++++++++++++++++++++--
|
||||
1 file changed, 38 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/gst-libs/gst/vaapi/gstvaapiutils_glx.c b/gst-libs/gst/vaapi/gstvaapiutils_glx.c
|
||||
index ccd7832b..f73106c2 100644
|
||||
--- a/gst-libs/gst/vaapi/gstvaapiutils_glx.c
|
||||
+++ b/gst-libs/gst/vaapi/gstvaapiutils_glx.c
|
||||
@@ -301,9 +301,17 @@ gl_create_context (Display * dpy, int screen, GLContextState * parent)
|
||||
GLX_RED_SIZE, 8,
|
||||
GLX_GREEN_SIZE, 8,
|
||||
GLX_BLUE_SIZE, 8,
|
||||
+ GLX_ALPHA_SIZE, 8,
|
||||
None
|
||||
};
|
||||
|
||||
+ const GLint rgba_colors[4] = {
|
||||
+ GLX_RED_SIZE,
|
||||
+ GLX_GREEN_SIZE,
|
||||
+ GLX_BLUE_SIZE,
|
||||
+ GLX_ALPHA_SIZE
|
||||
+ };
|
||||
+
|
||||
cs = malloc (sizeof (*cs));
|
||||
if (!cs)
|
||||
goto error;
|
||||
@@ -333,11 +341,38 @@ gl_create_context (Display * dpy, int screen, GLContextState * parent)
|
||||
if (!fbconfigs)
|
||||
goto error;
|
||||
|
||||
- /* Find out a GLXFBConfig compatible with the parent context */
|
||||
+ /* Find out a 8 bit GLXFBConfig compatible with the parent context */
|
||||
for (n = 0; n < n_fbconfigs; n++) {
|
||||
+ gboolean sizes_correct = FALSE;
|
||||
+ int cn;
|
||||
+
|
||||
status = glXGetFBConfigAttrib (parent->display,
|
||||
fbconfigs[n], GLX_FBCONFIG_ID, &val);
|
||||
- if (status == Success && val == fbconfig_id)
|
||||
+ if (status != Success)
|
||||
+ goto error;
|
||||
+ if (val != fbconfig_id)
|
||||
+ continue;
|
||||
+
|
||||
+ /* Iterate over RGBA sizes in fbconfig */
|
||||
+ for (cn = 0; cn < 4; cn++) {
|
||||
+ int size = 0;
|
||||
+
|
||||
+ status = glXGetFBConfigAttrib (parent->display,
|
||||
+ fbconfigs[n], rgba_colors[cn], &size);
|
||||
+ if (status != Success)
|
||||
+ goto error;
|
||||
+
|
||||
+ /* Last check is for alpha
|
||||
+ * and alpha is optional */
|
||||
+ if (cn == 3) {
|
||||
+ if (size == 0 || size == 8) {
|
||||
+ sizes_correct = TRUE;
|
||||
+ break;
|
||||
+ }
|
||||
+ } else if (size != 8)
|
||||
+ break;
|
||||
+ }
|
||||
+ if (sizes_correct)
|
||||
break;
|
||||
}
|
||||
if (n == n_fbconfigs)
|
||||
@@ -809,6 +844,7 @@ gl_create_pixmap_object (Display * dpy, guint width, guint height)
|
||||
GLX_RED_SIZE, 8,
|
||||
GLX_GREEN_SIZE, 8,
|
||||
GLX_BLUE_SIZE, 8,
|
||||
+ GLX_ALPHA_SIZE, 8,
|
||||
GL_NONE,
|
||||
};
|
||||
|
||||
--
|
||||
2.28.0
|
||||
|
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "gstreamer-vaapi",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"-Ddoc=disabled",
|
||||
"-Dexamples=disabled",
|
||||
"-Dtests=disabled"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gstreamer-vaapi.git",
|
||||
"tag": "1.18.1",
|
||||
"commit": "f9e925af3645439f7b7a4580700fcd6ce17fc1c9"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gstreamer-vaapi-glx-select-8-bit-color-size.patch"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "gstreamer",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"-Ddoc=disabled",
|
||||
"-Dgtk_doc=disabled",
|
||||
"-Dexamples=disabled",
|
||||
"-Dtests=disabled",
|
||||
"-Dbenchmarks=disabled",
|
||||
"-Dnls=disabled",
|
||||
"-Dgobject-cast-checks=disabled",
|
||||
"-Dglib-asserts=disabled",
|
||||
"-Dglib-checks=disabled",
|
||||
"-Dextra-checks=disabled"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gstreamer.git",
|
||||
"tag": "1.18.1",
|
||||
"commit": "29a8099d1d4bd8717c13923e710e92e67e335353"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
--- a52dec-0.7.4/configure~ 2002-07-28 06:50:42.000000000 +0300
|
||||
+++ a52dec-0.7.4/configure 2006-02-16 23:03:07.000000000 +0200
|
||||
@@ -5839,7 +5839,7 @@
|
||||
shlibpath_overrides_runpath=unknown
|
||||
version_type=none
|
||||
dynamic_linker="$host_os ld.so"
|
||||
-sys_lib_dlsearch_path_spec="/lib /usr/lib"
|
||||
+sys_lib_dlsearch_path_spec="/lib64 /usr/lib64 /lib /usr/lib"
|
||||
sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
|
||||
|
||||
case $host_os in
|
||||
--- a52dec-0.7.4/aclocal.m4~ 2002-07-28 06:50:38.000000000 +0300
|
||||
+++ a52dec-0.7.4/aclocal.m4 2006-02-16 23:02:38.000000000 +0200
|
||||
@@ -2141,7 +2141,7 @@
|
||||
shlibpath_overrides_runpath=unknown
|
||||
version_type=none
|
||||
dynamic_linker="$host_os ld.so"
|
||||
-sys_lib_dlsearch_path_spec="/lib /usr/lib"
|
||||
+sys_lib_dlsearch_path_spec="/lib64 /usr/lib64 /lib /usr/lib"
|
||||
sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
|
||||
|
||||
case $host_os in
|
@@ -1,23 +0,0 @@
|
||||
--- ./configure~ 2002-07-28 06:50:42.000000000 +0300
|
||||
+++ ./configure 2003-04-13 17:20:53.000000000 +0300
|
||||
@@ -2857,13 +2857,13 @@
|
||||
|
||||
case "$host" in
|
||||
i?86-* | k?-*)
|
||||
- case "$host" in
|
||||
- i386-*) OPT_CFLAGS="$CFLAGS -mcpu=i386";;
|
||||
- i486-*) OPT_CFLAGS="$CFLAGS -mcpu=i486";;
|
||||
- i586-*) OPT_CFLAGS="$CFLAGS -mcpu=pentium";;
|
||||
- i686-*) OPT_CFLAGS="$CFLAGS -mcpu=pentiumpro";;
|
||||
- k6-*) OPT_CFLAGS="$CFLAGS -mcpu=k6";;
|
||||
- esac
|
||||
+# case "$host" in
|
||||
+# i386-*) OPT_CFLAGS="$CFLAGS -mcpu=i386";;
|
||||
+# i486-*) OPT_CFLAGS="$CFLAGS -mcpu=i486";;
|
||||
+# i586-*) OPT_CFLAGS="$CFLAGS -mcpu=pentium";;
|
||||
+# i686-*) OPT_CFLAGS="$CFLAGS -mcpu=pentiumpro";;
|
||||
+# k6-*) OPT_CFLAGS="$CFLAGS -mcpu=k6";;
|
||||
+# esac
|
||||
echo "$as_me:$LINENO: checking if $CC supports $OPT_CFLAGS flags" >&5
|
||||
echo $ECHO_N "checking if $CC supports $OPT_CFLAGS flags... $ECHO_C" >&6
|
||||
SAVE_CFLAGS="$CFLAGS"
|
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "ffmpeg",
|
||||
"cleanup": [
|
||||
"/lib/ffmpeg/examples"
|
||||
],
|
||||
"config-opts": [
|
||||
"--disable-debug",
|
||||
"--disable-doc",
|
||||
"--disable-static",
|
||||
"--disable-everything",
|
||||
"--enable-gpl",
|
||||
"--enable-version3",
|
||||
"--enable-optimizations",
|
||||
"--enable-runtime-cpudetect",
|
||||
"--enable-shared",
|
||||
"--enable-protocol=file",
|
||||
"--enable-decoder=flv,h263,h264,hevc,mjpeg,mpeg2video,mpeg4,mpegvideo,msmpeg4v1,msmpeg4v2,png,tiff,vc1,vp8,vp9,webp,wmv1,wmv2,wmv3,zerocodec",
|
||||
"--enable-decoder=aac,aac_fixed,aac_latm,ac3,ac3_fixed,eac3,flac,mp3,opus,tak,truehd,tta,wmalossless",
|
||||
"--enable-demuxer=gif,yuv4mpegpipe"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://git.ffmpeg.org/ffmpeg.git",
|
||||
"tag": "n4.3.1",
|
||||
"commit": "6b6b9e593dd4d3aaf75f48d40a13ef03bdef9fdb"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "glib-networking",
|
||||
"buildsystem": "meson",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.gnome.org/GNOME/glib-networking.git",
|
||||
"tag": "2.66.0",
|
||||
"commit": "61d7e024ca354e6d2e39930d66a2067f3de5842c"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
From c6320cfd75c65bfb1736b7ca5afc9c0f5ffc09d7 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Rafa=C5=82=20Dzi=C4=99giel?= <rafostar.github@gmail.com>
|
||||
Date: Thu, 25 Feb 2021 09:45:38 +0100
|
||||
Subject: [PATCH] Broadway: fix unsafe variable type
|
||||
|
||||
Only guint32 guarantees to be always 32bit on all platforms. Mixing 32bit and 64bit memory sizes leads to a crash.
|
||||
---
|
||||
gdk/broadway/gdkbroadway-server.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/gdk/broadway/gdkbroadway-server.c b/gdk/broadway/gdkbroadway-server.c
|
||||
index 02b6f93183..e6b96ff0b9 100644
|
||||
--- a/gdk/broadway/gdkbroadway-server.c
|
||||
+++ b/gdk/broadway/gdkbroadway-server.c
|
||||
@@ -235,7 +235,7 @@ static void
|
||||
parse_all_input (GdkBroadwayServer *server)
|
||||
{
|
||||
guint8 *p, *end;
|
||||
- size_t size;
|
||||
+ guint32 size;
|
||||
BroadwayReply *reply;
|
||||
|
||||
p = server->recv_buffer;
|
||||
--
|
||||
2.26.2
|
||||
|
@@ -1,31 +0,0 @@
|
||||
From b413ee2c7d458c7005d3d3d1da8822cd86893ac0 Mon Sep 17 00:00:00 2001
|
||||
From: Rafostar <40623528+Rafostar@users.noreply.github.com>
|
||||
Date: Fri, 4 Dec 2020 19:25:34 +0100
|
||||
Subject: [PATCH] popover: Call unrealize on hide
|
||||
|
||||
When popover is shown "realize" method is called which creates a new
|
||||
surface for popup. Unfortunately this causes performance drop on Wayland until that
|
||||
surface is destroyed what happens inside "unrealize" method during popover destruction.
|
||||
|
||||
This commit changes default behavior in a way that surface will be destroyed
|
||||
when popover is closed and app will ragain the performance it lost when
|
||||
popover was shown.
|
||||
---
|
||||
gtk/gtkpopover.c | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c
|
||||
index 504dcd6cc1..a7a764d483 100644
|
||||
--- a/gtk/gtkpopover.c
|
||||
+++ b/gtk/gtkpopover.c
|
||||
@@ -951,6 +951,7 @@ gtk_popover_hide (GtkWidget *widget)
|
||||
|
||||
gtk_popover_set_mnemonics_visible (GTK_POPOVER (widget), FALSE);
|
||||
_gtk_widget_set_visible_flag (widget, FALSE);
|
||||
+ gtk_widget_unrealize (widget);
|
||||
gtk_widget_unmap (widget);
|
||||
g_signal_emit (widget, signals[CLOSED], 0);
|
||||
}
|
||||
--
|
||||
GitLab
|
||||
|
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"name": "gtk",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"--wrap-mode=nofallback",
|
||||
"-Dbroadway-backend=true",
|
||||
"-Dwin32-backend=false",
|
||||
"-Dmacos-backend=false",
|
||||
"-Dmedia-ffmpeg=disabled",
|
||||
"-Dprint-cups=disabled",
|
||||
"-Dprint-cloudprint=disabled",
|
||||
"-Dintrospection=enabled",
|
||||
"-Ddemos=false",
|
||||
"-Dbuild-examples=false",
|
||||
"-Dbuild-tests=false"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.gnome.org/GNOME/gtk.git",
|
||||
"tag": "4.1.1",
|
||||
"commit": "1f284fcd706de5b0b8c54fee3ff61880caf1d167"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gtk4-popover-unrealize.patch"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gtk4-broadway-fix-unsafe-variable-type.patch"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
From 4c18c43b4d4ccb1d05ae73b813f26ba193fbeee3 Mon Sep 17 00:00:00 2001
|
||||
From: Bastien Nocera <hadess@hadess.net>
|
||||
Date: Fri, 18 Jan 2019 17:37:13 +0100
|
||||
Subject: [PATCH] Prefer PIC
|
||||
|
||||
---
|
||||
configure | 2 +-
|
||||
liba52/configure.incl | 2 +-
|
||||
2 files changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/configure b/configure
|
||||
index b81fdff..bc0267c 100755
|
||||
--- a/configure
|
||||
+++ b/configure
|
||||
@@ -9640,7 +9640,7 @@ _ACEOF
|
||||
|
||||
|
||||
|
||||
-LIBA52_CFLAGS="$LIBA52_CFLAGS -prefer-non-pic"
|
||||
+LIBA52_CFLAGS="$LIBA52_CFLAGS -prefer-pic"
|
||||
|
||||
# Check whether --enable-double or --disable-double was given.
|
||||
if test "${enable_double+set}" = set; then
|
||||
diff --git a/liba52/configure.incl b/liba52/configure.incl
|
||||
index 4dbbcea..5eb69ee 100644
|
||||
--- a/liba52/configure.incl
|
||||
+++ b/liba52/configure.incl
|
||||
@@ -2,7 +2,7 @@ AC_SUBST([LIBA52_CFLAGS])
|
||||
AC_SUBST([LIBA52_LIBS])
|
||||
|
||||
dnl avoid -fPIC when possible
|
||||
-LIBA52_CFLAGS="$LIBA52_CFLAGS -prefer-non-pic"
|
||||
+LIBA52_CFLAGS="$LIBA52_CFLAGS -prefer-pic"
|
||||
|
||||
AC_ARG_ENABLE([double],
|
||||
[ --enable-double use double-precision samples])
|
||||
--
|
||||
2.20.1
|
||||
|
@@ -1,17 +0,0 @@
|
||||
diff -ru a52dec.orig/liba52/imdct.c a52dec/liba52/imdct.c
|
||||
--- a52dec.orig/liba52/imdct.c 2012-02-06 19:40:21.000000000 +0200
|
||||
+++ a52dec/liba52/imdct.c 2012-02-06 19:40:53.000000000 +0200
|
||||
@@ -419,13 +419,11 @@
|
||||
|
||||
#ifdef LIBA52_DJBFFT
|
||||
if (mm_accel & MM_ACCEL_DJBFFT) {
|
||||
- fprintf (stderr, "Using djbfft for IMDCT transform\n");
|
||||
ifft128 = (void (*) (complex_t *)) fftc4_un128;
|
||||
ifft64 = (void (*) (complex_t *)) fftc4_un64;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
- fprintf (stderr, "No accelerated IMDCT transform found\n");
|
||||
ifft128 = ifft128_c;
|
||||
ifft64 = ifft64_c;
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "liba52",
|
||||
"config-opts": [ "--enable-shared", "--disable-static" ],
|
||||
"rm-configure": true,
|
||||
"cleanup": [ "/bin/*a52*" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "http://liba52.sourceforge.net/files/a52dec-0.7.4.tar.gz",
|
||||
"sha256": "a21d724ab3b3933330194353687df82c475b5dfb997513eef4c25de6c865ec33"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "a52dec-0.7.4-rpath64.patch"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "a52dec-configure-optflags.patch"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "liba52-silence.patch"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "liba52-prefer-pic.patch"
|
||||
},
|
||||
{
|
||||
"type":"script",
|
||||
"commands":[
|
||||
"autoreconf -fiv"
|
||||
],
|
||||
"dest-filename":"autogen.sh"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "libass",
|
||||
"config-opts": [ "--enable-shared", "--disable-static" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/libass/libass.git",
|
||||
"tag": "0.14.0",
|
||||
"commit": "73284b676b12b47e17af2ef1b430527299e10c17"
|
||||
},
|
||||
{
|
||||
"type":"script",
|
||||
"commands":[
|
||||
"autoreconf -fiv"
|
||||
],
|
||||
"dest-filename":"autogen.sh"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "libdvdcss",
|
||||
"config-opts": [ "--enable-shared", "--disable-static" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://code.videolan.org/videolan/libdvdcss.git",
|
||||
"tag": "1.4.2",
|
||||
"commit": "7b7c185704567398627ad0f9a0d948a63514394b"
|
||||
},
|
||||
{
|
||||
"type":"script",
|
||||
"commands":[
|
||||
"autoreconf -fiv"
|
||||
],
|
||||
"dest-filename":"autogen.sh"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "libdvdnav",
|
||||
"config-opts": [ "--enable-shared", "--disable-static" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://code.videolan.org/videolan/libdvdnav.git",
|
||||
"tag": "6.1.0",
|
||||
"commit": "4f48efd43efb2e3372cb494a8893342e1fb507ae"
|
||||
},
|
||||
{
|
||||
"type":"script",
|
||||
"commands":[
|
||||
"autoreconf -fiv"
|
||||
],
|
||||
"dest-filename":"autogen.sh"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "libdvdread",
|
||||
"config-opts": [ "--enable-shared", "--disable-static" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://code.videolan.org/videolan/libdvdread.git",
|
||||
"tag": "6.1.0",
|
||||
"commit": "d413571ce39acd404523b6742ba361215f6ada68"
|
||||
},
|
||||
{
|
||||
"type":"script",
|
||||
"commands":[
|
||||
"autoreconf -fiv"
|
||||
],
|
||||
"dest-filename":"autogen.sh"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "libgudev",
|
||||
"config-opts": [ "--enable-shared", "--disable-static", "--disable-umockdev" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.gnome.org/GNOME/libgudev.git",
|
||||
"tag": "234",
|
||||
"commit": "e9342ee019482a08fe435d6b656f8a6bdd196bce"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
--- libmpeg2/configure.ac 2016-01-20 15:31:37.933547037 +0100
|
||||
+++ libmpeg2.new/configure.ac 2016-01-20 15:05:40.931231465 +0100
|
||||
@@ -149,7 +149,6 @@
|
||||
|
||||
dnl Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_C_CONST
|
||||
-AC_C_ALWAYS_INLINE
|
||||
AC_C_RESTRICT
|
||||
AC_C_BUILTIN_EXPECT
|
||||
AC_C_BIGENDIAN
|
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "libmpeg2",
|
||||
"config-opts": [ "--enable-shared", "--disable-static" ],
|
||||
"rm-configure": true,
|
||||
"cleanup": [ "/bin/*mpeg2*" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "http://libmpeg2.sourceforge.net/files/libmpeg2-0.5.1.tar.gz",
|
||||
"sha256": "dee22e893cb5fc2b2b6ebd60b88478ab8556cb3b93f9a0d7ce8f3b61851871d4"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "libmpeg2-inline.patch"
|
||||
},
|
||||
{
|
||||
"type":"script",
|
||||
"commands":[
|
||||
"autoreconf -fiv"
|
||||
],
|
||||
"dest-filename":"autogen.sh"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "libsass",
|
||||
"buildsystem": "meson",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/lazka/libsass.git",
|
||||
"branch": "meson",
|
||||
"commit": "302397c0c8ae2d7ab02f45ea461c2c3d768f248e"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "pango",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"-Dgtk_doc=false"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.gnome.org/GNOME/pango.git",
|
||||
"tag": "1.48.0",
|
||||
"commit": "a39fea44c7c9f982fcca6d639929545dd3e09eb7"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "sassc",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"--wrap-mode=nofallback"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/lazka/sassc.git",
|
||||
"branch": "meson",
|
||||
"commit": "82803377c33247265d779af034eceb5949e78354"
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "uchardet",
|
||||
"buildsystem": "cmake",
|
||||
"builddir": true,
|
||||
"config-opts": [
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DCMAKE_INSTALL_LIBDIR=lib",
|
||||
"-DBUILD_STATIC=OFF",
|
||||
"-DBUILD_BINARY=OFF"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.freedesktop.org/uchardet/uchardet.git",
|
||||
"tag": "v0.0.7",
|
||||
"commit": "59f68dbe5709d708b53ad5ea95c7349d7ee6ebe4"
|
||||
}
|
||||
]
|
||||
}
|
Submodule pkgs/flatpak/shared-modules deleted from ca1664c5d1
@@ -26,7 +26,7 @@
|
||||
%global glib2_version 2.56.0
|
||||
|
||||
Name: clapper
|
||||
Version: 0.1.0
|
||||
Version: 0.3.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Simple and modern GNOME media player
|
||||
|
||||
@@ -126,6 +126,15 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop
|
||||
%{_libdir}/%{appname}/
|
||||
|
||||
%changelog
|
||||
* Fri Jun 18 2021 Rafostar <rafostar.github@gmail.com> - 0.3.0-1
|
||||
- New version
|
||||
|
||||
* Mon Apr 19 2021 Rafostar <rafostar.github@gmail.com> - 0.2.1-1
|
||||
- New version
|
||||
|
||||
* Tue Apr 13 2021 Rafostar <rafostar.github@gmail.com> - 0.2.0-1
|
||||
- New version
|
||||
|
||||
* Fri Feb 25 2021 Rafostar <rafostar.github@gmail.com> - 0.1.0-1
|
||||
- New version
|
||||
|
||||
|
105
src/actions.js
Normal file
105
src/actions.js
Normal file
@@ -0,0 +1,105 @@
|
||||
const { Gtk } = imports.gi;
|
||||
const Dialogs = imports.src.dialogs;
|
||||
const Misc = imports.src.misc;
|
||||
|
||||
var actions = {
|
||||
open_local: ['<Ctrl>O'],
|
||||
export_playlist: ['<Ctrl>E'],
|
||||
open_uri: ['<Ctrl>U'],
|
||||
prefs: null,
|
||||
shortcuts: ['F1', '<Ctrl>question'],
|
||||
about: null,
|
||||
progress_forward: ['Right'],
|
||||
progress_backward: ['Left'],
|
||||
next_chapter: ['<Shift>Right'],
|
||||
prev_chapter: ['<Shift>Left'],
|
||||
next_track: ['<Ctrl>Right'],
|
||||
prev_track: ['<Ctrl>Left'],
|
||||
volume_up: ['Up'],
|
||||
volume_down: ['Down'],
|
||||
toggle_play: ['space'],
|
||||
change_repeat: ['<Ctrl>r'],
|
||||
reveal_controls: ['Return'],
|
||||
toggle_fullscreen: ['F11', 'f'],
|
||||
quit: ['<Ctrl>q', 'q'],
|
||||
};
|
||||
|
||||
function handleAction(action, window)
|
||||
{
|
||||
const clapperWidget = window.child;
|
||||
if(!clapperWidget) return;
|
||||
|
||||
const { player } = clapperWidget;
|
||||
let bool = false;
|
||||
|
||||
switch(action.name) {
|
||||
case 'open_local':
|
||||
case 'export_playlist':
|
||||
new Dialogs.FileChooser(window, action.name);
|
||||
break;
|
||||
case 'open_uri':
|
||||
new Dialogs.UriDialog(window);
|
||||
break;
|
||||
case 'prefs':
|
||||
new Dialogs.PrefsDialog(window);
|
||||
break;
|
||||
case 'shortcuts':
|
||||
if(!window.get_help_overlay()) {
|
||||
const clapperPath = Misc.getClapperPath();
|
||||
const helpBuilder = Gtk.Builder.new_from_file(
|
||||
`${clapperPath}/ui/help-overlay.ui`
|
||||
);
|
||||
window.set_help_overlay(helpBuilder.get_object('help_overlay'));
|
||||
}
|
||||
clapperWidget.activate_action('win.show-help-overlay', null);
|
||||
break;
|
||||
case 'about':
|
||||
new Dialogs.AboutDialog(window);
|
||||
break;
|
||||
case 'progress_forward':
|
||||
bool = true;
|
||||
case 'progress_backward':
|
||||
player.adjust_position(bool);
|
||||
if(
|
||||
clapperWidget.isReleaseKeyEnabled
|
||||
&& clapperWidget.isFullscreenMode
|
||||
)
|
||||
clapperWidget.revealControls();
|
||||
/* Actual seek is handled on release */
|
||||
clapperWidget.isReleaseKeyEnabled = true;
|
||||
if(!clapperWidget.has_focus)
|
||||
clapperWidget.grab_focus();
|
||||
break;
|
||||
case 'volume_up':
|
||||
bool = true;
|
||||
case 'volume_down':
|
||||
player.adjust_volume(bool);
|
||||
break;
|
||||
case 'next_track':
|
||||
player.playlistWidget.nextTrack();
|
||||
break;
|
||||
case 'prev_track':
|
||||
player.playlistWidget.prevTrack();
|
||||
break;
|
||||
case 'reveal_controls':
|
||||
if(clapperWidget.isFullscreenMode)
|
||||
clapperWidget.revealControls();
|
||||
break;
|
||||
case 'toggle_fullscreen':
|
||||
clapperWidget.toggleFullscreen();
|
||||
break;
|
||||
case 'change_repeat':
|
||||
player.playlistWidget.changeRepeatMode();
|
||||
break;
|
||||
case 'quit':
|
||||
clapperWidget.activate_action('window.close', null);
|
||||
break;
|
||||
case 'toggle_play':
|
||||
case 'next_chapter':
|
||||
case 'prev_chapter':
|
||||
player[action.name]();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
38
src/app.js
38
src/app.js
@@ -12,10 +12,7 @@ class ClapperApp extends AppBase
|
||||
{
|
||||
super._init();
|
||||
|
||||
this.set_flags(
|
||||
this.get_flags()
|
||||
| Gio.ApplicationFlags.HANDLES_OPEN
|
||||
);
|
||||
this.flags |= Gio.ApplicationFlags.HANDLES_OPEN;
|
||||
}
|
||||
|
||||
vfunc_startup()
|
||||
@@ -23,45 +20,32 @@ class ClapperApp extends AppBase
|
||||
super.vfunc_startup();
|
||||
|
||||
const window = this.active_window;
|
||||
|
||||
window.isClapperApp = true;
|
||||
window.add_css_class('nobackground');
|
||||
|
||||
const clapperWidget = new Widget();
|
||||
window.set_child(clapperWidget);
|
||||
|
||||
const dummyHeaderbar = new Gtk.Box({
|
||||
can_focus: false,
|
||||
focusable: false,
|
||||
visible: false,
|
||||
});
|
||||
|
||||
window.add_css_class('nobackground');
|
||||
window.set_child(clapperWidget);
|
||||
window.set_titlebar(dummyHeaderbar);
|
||||
|
||||
this.mapSignal = window.connect('map', this._onWindowMap.bind(this));
|
||||
}
|
||||
|
||||
vfunc_open(files, hint)
|
||||
{
|
||||
super.vfunc_open(files, hint);
|
||||
|
||||
const { player } = this.active_window.get_child();
|
||||
|
||||
if(!this.doneFirstActivate)
|
||||
player._preparePlaylist(files);
|
||||
else
|
||||
player.set_playlist(files);
|
||||
|
||||
this.activate();
|
||||
this._openFilesAsync(files).then(() => this.activate()).catch(debug);
|
||||
}
|
||||
|
||||
_onWindowShow(window)
|
||||
_onWindowMap(window)
|
||||
{
|
||||
super._onWindowShow(window);
|
||||
window.disconnect(this.mapSignal);
|
||||
this.mapSignal = null;
|
||||
|
||||
const { player } = this.active_window.get_child();
|
||||
const success = player.playlistWidget.nextTrack();
|
||||
|
||||
if(!success)
|
||||
debug('playlist is empty');
|
||||
|
||||
player.widget.grab_focus();
|
||||
window.child._onWindowMap(window);
|
||||
}
|
||||
});
|
||||
|
100
src/appBase.js
100
src/appBase.js
@@ -1,7 +1,8 @@
|
||||
const { Gio, GLib, GObject, Gtk } = imports.gi;
|
||||
const Debug = imports.src.debug;
|
||||
const Menu = imports.src.menu;
|
||||
const FileOps = imports.src.fileOps;
|
||||
const Misc = imports.src.misc;
|
||||
const Actions = imports.src.actions;
|
||||
|
||||
const { debug } = Debug;
|
||||
const { settings } = Misc;
|
||||
@@ -16,6 +17,7 @@ class ClapperAppBase extends Gtk.Application
|
||||
});
|
||||
|
||||
this.doneFirstActivate = false;
|
||||
this.isFileAppend = false;
|
||||
}
|
||||
|
||||
vfunc_startup()
|
||||
@@ -33,21 +35,7 @@ class ClapperAppBase extends Gtk.Application
|
||||
if(!settings.get_boolean('render-shadows'))
|
||||
window.add_css_class('gpufriendly');
|
||||
|
||||
if(
|
||||
settings.get_boolean('dark-theme')
|
||||
&& settings.get_boolean('brighter-sliders')
|
||||
)
|
||||
window.add_css_class('brightscale');
|
||||
|
||||
for(let action in Menu.actions) {
|
||||
const simpleAction = new Gio.SimpleAction({
|
||||
name: action
|
||||
});
|
||||
simpleAction.connect(
|
||||
'activate', () => Menu.actions[action](this.active_window)
|
||||
);
|
||||
this.add_action(simpleAction);
|
||||
}
|
||||
window.add_css_class('gpufriendlyfs');
|
||||
}
|
||||
|
||||
vfunc_activate()
|
||||
@@ -62,8 +50,50 @@ class ClapperAppBase extends Gtk.Application
|
||||
);
|
||||
}
|
||||
|
||||
async _openFilesAsync(files)
|
||||
{
|
||||
const urisArr = [];
|
||||
|
||||
for(let file of files) {
|
||||
const uri = file.get_uri();
|
||||
if(!uri.startsWith('file:')) {
|
||||
urisArr.push(uri);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If file is not a dir its URI will be returned in an array */
|
||||
const uris = await FileOps.getDirFilesUrisPromise(file).catch(debug);
|
||||
if(uris && uris.length)
|
||||
urisArr.push(...uris);
|
||||
}
|
||||
|
||||
const [playlist, subs] = Misc.parsePlaylistFiles(urisArr);
|
||||
const { player } = this.active_window.get_child();
|
||||
const action = (this.isFileAppend) ? 'append' : 'set';
|
||||
|
||||
if(playlist && playlist.length)
|
||||
player[`${action}_playlist`](playlist);
|
||||
if(subs)
|
||||
player.set_subtitles(subs);
|
||||
|
||||
/* Restore default behavior */
|
||||
this.isFileAppend = false;
|
||||
}
|
||||
|
||||
_onFirstActivate()
|
||||
{
|
||||
for(let name in Actions.actions) {
|
||||
const simpleAction = new Gio.SimpleAction({ name });
|
||||
simpleAction.connect('activate', (action) =>
|
||||
Actions.handleAction(action, this.active_window)
|
||||
);
|
||||
this.add_action(simpleAction);
|
||||
|
||||
const accels = Actions.actions[name];
|
||||
if(accels)
|
||||
this.set_accels_for_action(`app.${name}`, accels);
|
||||
}
|
||||
|
||||
const gtkSettings = Gtk.Settings.get_default();
|
||||
settings.bind(
|
||||
'dark-theme', gtkSettings,
|
||||
@@ -71,24 +101,17 @@ class ClapperAppBase extends Gtk.Application
|
||||
Gio.SettingsBindFlags.GET
|
||||
);
|
||||
this._onThemeChanged(gtkSettings);
|
||||
this._onIconThemeChanged(gtkSettings);
|
||||
gtkSettings.connect('notify::gtk-theme-name', this._onThemeChanged.bind(this));
|
||||
|
||||
this.windowShowSignal = this.active_window.connect(
|
||||
'show', this._onWindowShow.bind(this)
|
||||
);
|
||||
gtkSettings.connect('notify::gtk-icon-theme-name', this._onIconThemeChanged.bind(this));
|
||||
this.doneFirstActivate = true;
|
||||
}
|
||||
|
||||
_onWindowShow(window)
|
||||
{
|
||||
window.disconnect(this.windowShowSignal);
|
||||
this.windowShowSignal = null;
|
||||
}
|
||||
|
||||
_onThemeChanged(gtkSettings)
|
||||
{
|
||||
const theme = gtkSettings.gtk_theme_name;
|
||||
const window = this.active_window;
|
||||
const hasAdwThemeDark = window.has_css_class('adwthemedark');
|
||||
|
||||
debug(`user selected theme: ${theme}`);
|
||||
|
||||
@@ -97,6 +120,17 @@ class ClapperAppBase extends Gtk.Application
|
||||
if(!window.has_css_class('adwrounded'))
|
||||
window.add_css_class('adwrounded');
|
||||
|
||||
if(theme.startsWith('Adwaita') || theme.startsWith('Default')) {
|
||||
const isDarkTheme = settings.get_boolean('dark-theme');
|
||||
|
||||
if(isDarkTheme && !hasAdwThemeDark)
|
||||
window.add_css_class('adwthemedark');
|
||||
else if(!isDarkTheme && hasAdwThemeDark)
|
||||
window.remove_css_class('adwthemedark');
|
||||
}
|
||||
else if(hasAdwThemeDark)
|
||||
window.remove_css_class('adwthemedark');
|
||||
|
||||
if(!theme.endsWith('-dark'))
|
||||
return;
|
||||
|
||||
@@ -107,4 +141,18 @@ class ClapperAppBase extends Gtk.Application
|
||||
gtkSettings.gtk_theme_name = parsedTheme;
|
||||
debug(`set theme: ${parsedTheme}`);
|
||||
}
|
||||
|
||||
_onIconThemeChanged(gtkSettings)
|
||||
{
|
||||
const iconTheme = gtkSettings.gtk_icon_theme_name;
|
||||
const window = this.active_window;
|
||||
const hasAdwIcons = window.has_css_class('adwicons');
|
||||
|
||||
if(iconTheme === 'Adwaita' || iconTheme === 'Default') {
|
||||
if(!hasAdwIcons)
|
||||
window.add_css_class('adwicons');
|
||||
}
|
||||
else if(hasAdwIcons)
|
||||
window.remove_css_class('adwicons');
|
||||
}
|
||||
});
|
||||
|
151
src/assets/node-ytdl-core/sig.js
Normal file
151
src/assets/node-ytdl-core/sig.js
Normal file
@@ -0,0 +1,151 @@
|
||||
/* Copyright (C) 2012-present by fent
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const jsVarStr = '[a-zA-Z_\\$][a-zA-Z_0-9]*';
|
||||
const jsSingleQuoteStr = `'[^'\\\\]*(:?\\\\[\\s\\S][^'\\\\]*)*'`;
|
||||
const jsDoubleQuoteStr = `"[^"\\\\]*(:?\\\\[\\s\\S][^"\\\\]*)*"`;
|
||||
const jsQuoteStr = `(?:${jsSingleQuoteStr}|${jsDoubleQuoteStr})`;
|
||||
const jsKeyStr = `(?:${jsVarStr}|${jsQuoteStr})`;
|
||||
const jsPropStr = `(?:\\.${jsVarStr}|\\[${jsQuoteStr}\\])`;
|
||||
const jsEmptyStr = `(?:''|"")`;
|
||||
const reverseStr = ':function\\(a\\)\\{' +
|
||||
'(?:return )?a\\.reverse\\(\\)' +
|
||||
'\\}';
|
||||
const sliceStr = ':function\\(a,b\\)\\{' +
|
||||
'return a\\.slice\\(b\\)' +
|
||||
'\\}';
|
||||
const spliceStr = ':function\\(a,b\\)\\{' +
|
||||
'a\\.splice\\(0,b\\)' +
|
||||
'\\}';
|
||||
const swapStr = ':function\\(a,b\\)\\{' +
|
||||
'var c=a\\[0\\];a\\[0\\]=a\\[b(?:%a\\.length)?\\];a\\[b(?:%a\\.length)?\\]=c(?:;return a)?' +
|
||||
'\\}';
|
||||
const actionsObjRegexp = new RegExp(
|
||||
`var (${jsVarStr})=\\{((?:(?:${
|
||||
jsKeyStr}${reverseStr}|${
|
||||
jsKeyStr}${sliceStr}|${
|
||||
jsKeyStr}${spliceStr}|${
|
||||
jsKeyStr}${swapStr
|
||||
}),?\\r?\\n?)+)\\};`);
|
||||
const actionsFuncRegexp = new RegExp(`${`function(?: ${jsVarStr})?\\(a\\)\\{` +
|
||||
`a=a\\.split\\(${jsEmptyStr}\\);\\s*` +
|
||||
`((?:(?:a=)?${jsVarStr}`}${
|
||||
jsPropStr
|
||||
}\\(a,\\d+\\);)+)` +
|
||||
`return a\\.join\\(${jsEmptyStr}\\)` +
|
||||
`\\}`);
|
||||
const reverseRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${reverseStr}`, 'm');
|
||||
const sliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${sliceStr}`, 'm');
|
||||
const spliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${spliceStr}`, 'm');
|
||||
const swapRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${swapStr}`, 'm');
|
||||
|
||||
const swapHeadAndPosition = (arr, position) => {
|
||||
const first = arr[0];
|
||||
arr[0] = arr[position % arr.length];
|
||||
arr[position] = first;
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
function decipher(sig, tokens) {
|
||||
sig = sig.split('');
|
||||
tokens = tokens.split(',');
|
||||
|
||||
for(let i = 0, len = tokens.length; i < len; i++) {
|
||||
let token = tokens[i], pos;
|
||||
switch (token[0]) {
|
||||
case 'r':
|
||||
sig = sig.reverse();
|
||||
break;
|
||||
case 'w':
|
||||
pos = ~~token.slice(1);
|
||||
sig = swapHeadAndPosition(sig, pos);
|
||||
break;
|
||||
case 's':
|
||||
pos = ~~token.slice(1);
|
||||
sig = sig.slice(pos);
|
||||
break;
|
||||
case 'p':
|
||||
pos = ~~token.slice(1);
|
||||
sig.splice(0, pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sig.join('');
|
||||
};
|
||||
|
||||
function extractActions(body) {
|
||||
const objResult = actionsObjRegexp.exec(body);
|
||||
const funcResult = actionsFuncRegexp.exec(body);
|
||||
|
||||
if(!objResult || !funcResult)
|
||||
return null;
|
||||
|
||||
const obj = objResult[1].replace(/\$/g, '\\$');
|
||||
const objBody = objResult[2].replace(/\$/g, '\\$');
|
||||
const funcBody = funcResult[1].replace(/\$/g, '\\$');
|
||||
|
||||
let result = reverseRegexp.exec(objBody);
|
||||
const reverseKey = result && result[1]
|
||||
.replace(/\$/g, '\\$')
|
||||
.replace(/\$|^'|^"|'$|"$/g, '');
|
||||
result = sliceRegexp.exec(objBody);
|
||||
const sliceKey = result && result[1]
|
||||
.replace(/\$/g, '\\$')
|
||||
.replace(/\$|^'|^"|'$|"$/g, '');
|
||||
result = spliceRegexp.exec(objBody);
|
||||
const spliceKey = result && result[1]
|
||||
.replace(/\$/g, '\\$')
|
||||
.replace(/\$|^'|^"|'$|"$/g, '');
|
||||
result = swapRegexp.exec(objBody);
|
||||
const swapKey = result && result[1]
|
||||
.replace(/\$/g, '\\$')
|
||||
.replace(/\$|^'|^"|'$|"$/g, '');
|
||||
|
||||
const keys = `(${[reverseKey, sliceKey, spliceKey, swapKey].join('|')})`;
|
||||
const myreg = `(?:a=)?${obj
|
||||
}(?:\\.${keys}|\\['${keys}'\\]|\\["${keys}"\\])` +
|
||||
`\\(a,(\\d+)\\)`;
|
||||
const tokenizeRegexp = new RegExp(myreg, 'g');
|
||||
const tokens = [];
|
||||
|
||||
while((result = tokenizeRegexp.exec(funcBody)) !== null) {
|
||||
const key = result[1] || result[2] || result[3];
|
||||
const pos = result[4];
|
||||
switch (key) {
|
||||
case swapKey:
|
||||
tokens.push(`w${result[4]}`);
|
||||
break;
|
||||
case reverseKey:
|
||||
tokens.push('r');
|
||||
break;
|
||||
case sliceKey:
|
||||
tokens.push(`s${result[4]}`);
|
||||
break;
|
||||
case spliceKey:
|
||||
tokens.push(`p${result[4]}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return tokens.join(',');
|
||||
}
|
@@ -1,5 +1,11 @@
|
||||
const { GObject, Gtk } = imports.gi;
|
||||
|
||||
/* Negative values from CSS */
|
||||
const PopoverOffset = {
|
||||
DEFAULT: -3,
|
||||
TVMODE: -5,
|
||||
};
|
||||
|
||||
var CustomButton = GObject.registerClass(
|
||||
class ClapperCustomButton extends Gtk.Button
|
||||
{
|
||||
@@ -8,10 +14,8 @@ class ClapperCustomButton extends Gtk.Button
|
||||
opts = opts || {};
|
||||
|
||||
const defaults = {
|
||||
margin_top: 4,
|
||||
margin_bottom: 4,
|
||||
margin_start: 2,
|
||||
margin_end: 2,
|
||||
halign: Gtk.Align.CENTER,
|
||||
valign: Gtk.Align.CENTER,
|
||||
can_focus: false,
|
||||
};
|
||||
Object.assign(opts, defaults);
|
||||
@@ -27,11 +31,6 @@ class ClapperCustomButton extends Gtk.Button
|
||||
if(this.isFullscreen === isFullscreen)
|
||||
return;
|
||||
|
||||
this.margin_top = (isFullscreen) ? 5 : 4;
|
||||
this.margin_start = (isFullscreen) ? 3 : 2;
|
||||
this.margin_end = (isFullscreen) ? 3 : 2;
|
||||
this.can_focus = isFullscreen;
|
||||
|
||||
/* Redraw icon after style class change */
|
||||
if(this.icon_name)
|
||||
this.set_icon_name(this.icon_name);
|
||||
@@ -79,10 +78,8 @@ class ClapperPopoverButtonBase extends Gtk.ToggleButton
|
||||
_init()
|
||||
{
|
||||
super._init({
|
||||
margin_top: 4,
|
||||
margin_bottom: 4,
|
||||
margin_start: 2,
|
||||
margin_end: 2,
|
||||
halign: Gtk.Align.CENTER,
|
||||
valign: Gtk.Align.CENTER,
|
||||
can_focus: false,
|
||||
});
|
||||
|
||||
@@ -97,7 +94,7 @@ class ClapperPopoverButtonBase extends Gtk.ToggleButton
|
||||
});
|
||||
|
||||
this.popover.set_child(this.popoverBox);
|
||||
this.popover.set_offset(0, -this.margin_top);
|
||||
this.popover.set_offset(0, PopoverOffset.DEFAULT);
|
||||
|
||||
if(this.isFullscreen)
|
||||
this.popover.add_css_class('osd');
|
||||
@@ -111,18 +108,18 @@ class ClapperPopoverButtonBase extends Gtk.ToggleButton
|
||||
if(this.isFullscreen === isFullscreen)
|
||||
return;
|
||||
|
||||
this.margin_top = (isFullscreen) ? 5 : 4;
|
||||
this.margin_start = (isFullscreen) ? 3 : 2;
|
||||
this.margin_end = (isFullscreen) ? 3 : 2;
|
||||
this.can_focus = isFullscreen;
|
||||
|
||||
/* Redraw icon after style class change */
|
||||
if(this.icon_name)
|
||||
this.set_icon_name(this.icon_name);
|
||||
|
||||
this.isFullscreen = isFullscreen;
|
||||
|
||||
this.popover.set_offset(0, -this.margin_top);
|
||||
/* TODO: Fullscreen non-tv mode */
|
||||
const offset = (isFullscreen)
|
||||
? PopoverOffset.TVMODE
|
||||
: PopoverOffset.DEFAULT;
|
||||
|
||||
this.popover.set_offset(0, offset);
|
||||
|
||||
const cssClass = 'osd';
|
||||
if(isFullscreen === this.popover.has_css_class(cssClass))
|
||||
@@ -151,8 +148,6 @@ class ClapperPopoverButtonBase extends Gtk.ToggleButton
|
||||
{
|
||||
const clapperWidget = this.get_ancestor(Gtk.Grid);
|
||||
|
||||
clapperWidget.player.widget.grab_focus();
|
||||
|
||||
/* Set again timeout as popover is now closed */
|
||||
if(clapperWidget.isFullscreenMode)
|
||||
clapperWidget.revealControls();
|
||||
@@ -189,7 +184,7 @@ class ClapperLabelPopoverButton extends PopoverButtonBase
|
||||
label: text,
|
||||
single_line_mode: true,
|
||||
});
|
||||
this.customLabel.add_css_class('labelbutton');
|
||||
this.customLabel.add_css_class('labelbuttonlabel');
|
||||
this.set_child(this.customLabel);
|
||||
}
|
||||
|
||||
|
68
src/controls.js
vendored
68
src/controls.js
vendored
@@ -4,12 +4,11 @@ const Debug = imports.src.debug;
|
||||
const Misc = imports.src.misc;
|
||||
const Revealers = imports.src.revealers;
|
||||
|
||||
const CONTROLS_MARGIN = 2;
|
||||
const CONTROLS_SPACING = 0;
|
||||
|
||||
const { debug } = Debug;
|
||||
const { settings } = Misc;
|
||||
|
||||
const INITIAL_ELAPSED = '00:00/00:00';
|
||||
|
||||
var Controls = GObject.registerClass(
|
||||
class ClapperControls extends Gtk.Box
|
||||
{
|
||||
@@ -17,15 +16,13 @@ class ClapperControls extends Gtk.Box
|
||||
{
|
||||
super._init({
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
margin_start: CONTROLS_MARGIN,
|
||||
margin_end: CONTROLS_MARGIN,
|
||||
spacing: CONTROLS_SPACING,
|
||||
valign: Gtk.Align.END,
|
||||
can_focus: false,
|
||||
});
|
||||
|
||||
this.minFullViewWidth = 560;
|
||||
|
||||
this.currentPosition = 0;
|
||||
this.currentDuration = 0;
|
||||
this.isPositionDragging = false;
|
||||
this.isMobile = false;
|
||||
this.isFullscreen = false;
|
||||
@@ -89,11 +86,6 @@ class ClapperControls extends Gtk.Box
|
||||
this.unfullscreenButton.connect('clicked', this._onUnfullscreenClicked.bind(this));
|
||||
this.unfullscreenButton.set_visible(false);
|
||||
|
||||
const keyController = new Gtk.EventControllerKey();
|
||||
keyController.connect('key-pressed', this._onControlsKeyPressed.bind(this));
|
||||
keyController.connect('key-released', this._onControlsKeyReleased.bind(this));
|
||||
this.add_controller(keyController);
|
||||
|
||||
this.add_css_class('playercontrols');
|
||||
this.realizeSignal = this.connect('realize', this._onRealize.bind(this));
|
||||
}
|
||||
@@ -107,8 +99,6 @@ class ClapperControls extends Gtk.Box
|
||||
button.setFullscreenMode(isFullscreen);
|
||||
|
||||
this.unfullscreenButton.visible = isFullscreen;
|
||||
this.can_focus = isFullscreen;
|
||||
|
||||
this.isFullscreen = isFullscreen;
|
||||
}
|
||||
|
||||
@@ -120,6 +110,21 @@ class ClapperControls extends Gtk.Box
|
||||
this.positionScale.visible = isSeekable;
|
||||
}
|
||||
|
||||
setInitialState()
|
||||
{
|
||||
this.currentPosition = 0;
|
||||
this.positionScale.set_value(0);
|
||||
this.positionScale.visible = false;
|
||||
|
||||
this.elapsedButton.set_label(INITIAL_ELAPSED);
|
||||
this.togglePlayButton.setPrimaryIcon();
|
||||
|
||||
for(let type of ['video', 'audio', 'subtitle'])
|
||||
this[`${type}TracksButton`].visible = false;
|
||||
|
||||
this.visualizationsButton.visible = false;
|
||||
}
|
||||
|
||||
updateElapsedLabel(value)
|
||||
{
|
||||
value = value || 0;
|
||||
@@ -242,9 +247,6 @@ class ClapperControls extends Gtk.Box
|
||||
}
|
||||
|
||||
if(checkButton.activeId < 0) {
|
||||
if(checkButton.type === 'video')
|
||||
clapperWidget.player.draw_black(true);
|
||||
|
||||
return clapperWidget.player[
|
||||
`set_${checkButton.type}_track_enabled`
|
||||
](false);
|
||||
@@ -254,9 +256,6 @@ class ClapperControls extends Gtk.Box
|
||||
|
||||
clapperWidget.player[setTrack](checkButton.activeId);
|
||||
clapperWidget.player[`${setTrack}_enabled`](true);
|
||||
|
||||
if(checkButton.type === 'video')
|
||||
clapperWidget.player.draw_black(false);
|
||||
}
|
||||
|
||||
_handleVisualizationChange(checkButton)
|
||||
@@ -302,7 +301,7 @@ class ClapperControls extends Gtk.Box
|
||||
_addElapsedButton()
|
||||
{
|
||||
const elapsedRevealer = new Revealers.ButtonsRevealer('SLIDE_RIGHT');
|
||||
this.elapsedButton = this.addElapsedPopoverButton('00:00/00:00', elapsedRevealer);
|
||||
this.elapsedButton = this.addElapsedPopoverButton(INITIAL_ELAPSED, elapsedRevealer);
|
||||
elapsedRevealer.set_reveal_child(true);
|
||||
this.revealersArr.push(elapsedRevealer);
|
||||
|
||||
@@ -439,7 +438,7 @@ class ClapperControls extends Gtk.Box
|
||||
const scaleHeight = this.positionScale.parent.get_height();
|
||||
|
||||
this.chapterPopover.set_pointing_to(new Gdk.Rectangle({
|
||||
x: 2,
|
||||
x: -2,
|
||||
y: -(controlsHeight - scaleHeight) / 2,
|
||||
width: 2 * end,
|
||||
height: 0,
|
||||
@@ -479,7 +478,7 @@ class ClapperControls extends Gtk.Box
|
||||
|
||||
_onPlayerResize(width, height)
|
||||
{
|
||||
const isMobile = (width < 560);
|
||||
const isMobile = (width < this.minFullViewWidth);
|
||||
if(this.isMobile === isMobile)
|
||||
return;
|
||||
|
||||
@@ -625,29 +624,6 @@ class ClapperControls extends Gtk.Box
|
||||
}
|
||||
}
|
||||
|
||||
/* Only happens when navigating through controls panel */
|
||||
_onControlsKeyPressed(controller, keyval, keycode, state)
|
||||
{
|
||||
const clapperWidget = this.get_ancestor(Gtk.Grid);
|
||||
clapperWidget._setHideControlsTimeout();
|
||||
}
|
||||
|
||||
_onControlsKeyReleased(controller, keyval, keycode, state)
|
||||
{
|
||||
switch(keyval) {
|
||||
case Gdk.KEY_space:
|
||||
case Gdk.KEY_Return:
|
||||
case Gdk.KEY_Escape:
|
||||
case Gdk.KEY_Right:
|
||||
case Gdk.KEY_Left:
|
||||
break;
|
||||
default:
|
||||
const { player } = this.get_ancestor(Gtk.Grid);
|
||||
player._onWidgetKeyReleased(controller, keyval, keycode, state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_onCloseRequest()
|
||||
{
|
||||
debug('controls close request');
|
||||
|
165
src/dash.js
Normal file
165
src/dash.js
Normal file
@@ -0,0 +1,165 @@
|
||||
const Debug = imports.src.debug;
|
||||
const FileOps = imports.src.fileOps;
|
||||
const Misc = imports.src.misc;
|
||||
|
||||
const { debug } = Debug;
|
||||
|
||||
function generateDash(dashInfo)
|
||||
{
|
||||
debug('generating dash');
|
||||
|
||||
const bufferSec = Math.min(4, dashInfo.duration);
|
||||
|
||||
const dash = [
|
||||
`<?xml version="1.0" encoding="UTF-8"?>`,
|
||||
`<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`,
|
||||
` xmlns="urn:mpeg:dash:schema:mpd:2011"`,
|
||||
` xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd"`,
|
||||
` type="static"`,
|
||||
` mediaPresentationDuration="PT${dashInfo.duration}S"`,
|
||||
` minBufferTime="PT${bufferSec}S"`,
|
||||
` profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">`,
|
||||
` <Period>`
|
||||
];
|
||||
|
||||
for(let adaptation of dashInfo.adaptations)
|
||||
dash.push(_addAdaptationSet(adaptation));
|
||||
|
||||
dash.push(
|
||||
` </Period>`,
|
||||
`</MPD>`
|
||||
);
|
||||
|
||||
debug('dash generated');
|
||||
|
||||
return dash.join('\n');
|
||||
}
|
||||
|
||||
function _addAdaptationSet(streamsArr)
|
||||
{
|
||||
/* We just need it for adaptation type,
|
||||
* so any stream will do */
|
||||
const { mimeInfo } = streamsArr[0];
|
||||
|
||||
const adaptArr = [
|
||||
`contentType="${mimeInfo.content}"`,
|
||||
`mimeType="${mimeInfo.type}"`,
|
||||
`subsegmentAlignment="true"`,
|
||||
`subsegmentStartsWithSAP="1"`,
|
||||
];
|
||||
|
||||
const widthArr = [];
|
||||
const heightArr = [];
|
||||
const fpsArr = [];
|
||||
|
||||
const representations = [];
|
||||
|
||||
for(let stream of streamsArr) {
|
||||
/* No point parsing if no URL */
|
||||
if(!stream.url)
|
||||
continue;
|
||||
|
||||
if(stream.width && stream.height) {
|
||||
widthArr.push(stream.width);
|
||||
heightArr.push(stream.height);
|
||||
}
|
||||
if(stream.fps)
|
||||
fpsArr.push(stream.fps);
|
||||
|
||||
representations.push(_getStreamRepresentation(stream));
|
||||
}
|
||||
|
||||
if(widthArr.length && heightArr.length) {
|
||||
const maxWidth = Math.max.apply(null, widthArr);
|
||||
const maxHeight = Math.max.apply(null, heightArr);
|
||||
const par = _getPar(maxWidth, maxHeight);
|
||||
|
||||
adaptArr.push(`maxWidth="${maxWidth}"`);
|
||||
adaptArr.push(`maxHeight="${maxHeight}"`);
|
||||
adaptArr.push(`par="${par}"`);
|
||||
}
|
||||
if(fpsArr.length) {
|
||||
const maxFps = Math.max.apply(null, fpsArr);
|
||||
|
||||
adaptArr.push(`maxFrameRate="${maxFps}"`);
|
||||
}
|
||||
|
||||
const adaptationSet = [
|
||||
` <AdaptationSet ${adaptArr.join(' ')}>`,
|
||||
representations.join('\n'),
|
||||
` </AdaptationSet>`
|
||||
];
|
||||
|
||||
return adaptationSet.join('\n');
|
||||
}
|
||||
|
||||
function _getStreamRepresentation(stream)
|
||||
{
|
||||
const repOptsArr = [
|
||||
`id="${stream.itag}"`,
|
||||
`codecs="${stream.mimeInfo.codecs}"`,
|
||||
`bandwidth="${stream.bitrate}"`,
|
||||
];
|
||||
|
||||
if(stream.width && stream.height) {
|
||||
repOptsArr.push(`width="${stream.width}"`);
|
||||
repOptsArr.push(`height="${stream.height}"`);
|
||||
repOptsArr.push(`sar="1:1"`);
|
||||
}
|
||||
if(stream.fps)
|
||||
repOptsArr.push(`frameRate="${stream.fps}"`);
|
||||
|
||||
const repArr = [
|
||||
` <Representation ${repOptsArr.join(' ')}>`,
|
||||
];
|
||||
if(stream.audioChannels) {
|
||||
const audioConfArr = [
|
||||
`schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"`,
|
||||
`value="${stream.audioChannels}"`,
|
||||
];
|
||||
repArr.push(` <AudioChannelConfiguration ${audioConfArr.join(' ')}/>`);
|
||||
}
|
||||
|
||||
repArr.push(
|
||||
` <BaseURL>${stream.url}</BaseURL>`
|
||||
);
|
||||
|
||||
if(stream.indexRange) {
|
||||
const segRange = `${stream.indexRange.start}-${stream.indexRange.end}`;
|
||||
repArr.push(
|
||||
` <SegmentBase indexRange="${segRange}">`
|
||||
);
|
||||
if(stream.initRange) {
|
||||
const initRange = `${stream.initRange.start}-${stream.initRange.end}`;
|
||||
repArr.push(
|
||||
` <Initialization range="${initRange}"/>`
|
||||
);
|
||||
}
|
||||
repArr.push(
|
||||
` </SegmentBase>`
|
||||
);
|
||||
}
|
||||
|
||||
repArr.push(
|
||||
` </Representation>`
|
||||
);
|
||||
|
||||
return repArr.join('\n');
|
||||
}
|
||||
|
||||
function _getPar(width, height)
|
||||
{
|
||||
const gcd = _getGCD(width, height);
|
||||
|
||||
width /= gcd;
|
||||
height /= gcd;
|
||||
|
||||
return `${width}:${height}`;
|
||||
}
|
||||
|
||||
function _getGCD(width, height)
|
||||
{
|
||||
return (height)
|
||||
? _getGCD(height, width % height)
|
||||
: width;
|
||||
}
|
@@ -17,6 +17,7 @@ const ShellProxyWrapper = Gio.DBusProxy.makeProxyWrapper(`
|
||||
|
||||
let shellProxy = null;
|
||||
|
||||
debug('initializing GNOME Shell DBus proxy');
|
||||
new ShellProxyWrapper(
|
||||
Gio.DBus.session,
|
||||
'org.gnome.Shell',
|
||||
|
65
src/debug.js
65
src/debug.js
@@ -19,23 +19,60 @@ clapperDebugger.enabled = (
|
||||
|| G_DEBUG_ENV != null
|
||||
&& G_DEBUG_ENV.includes('Clapper')
|
||||
);
|
||||
const clapperDebug = clapperDebugger.debug;
|
||||
|
||||
function debug(msg, levelName)
|
||||
const ytDebugger = new Debug.Debugger('YouTube', {
|
||||
name_printer: new Ink.Printer({
|
||||
font: Ink.Font.BOLD,
|
||||
color: Ink.Color.RED
|
||||
}),
|
||||
time_printer: new Ink.Printer({
|
||||
color: Ink.Color.LIGHT_BLUE
|
||||
}),
|
||||
high_precision: true,
|
||||
});
|
||||
|
||||
function _logStructured(debuggerName, msg, level)
|
||||
{
|
||||
levelName = levelName || 'LEVEL_DEBUG';
|
||||
|
||||
if(msg.message) {
|
||||
levelName = 'LEVEL_CRITICAL';
|
||||
msg = msg.message;
|
||||
}
|
||||
|
||||
if(levelName !== 'LEVEL_CRITICAL')
|
||||
return clapperDebug(msg);
|
||||
|
||||
GLib.log_structured(
|
||||
'Clapper', GLib.LogLevelFlags[levelName], {
|
||||
debuggerName, level, {
|
||||
MESSAGE: msg,
|
||||
SYSLOG_IDENTIFIER: 'clapper'
|
||||
SYSLOG_IDENTIFIER: debuggerName.toLowerCase()
|
||||
});
|
||||
}
|
||||
|
||||
function _debug(debuggerName, msg)
|
||||
{
|
||||
if(msg.message) {
|
||||
_logStructured(
|
||||
debuggerName,
|
||||
msg.message,
|
||||
GLib.LogLevelFlags.LEVEL_CRITICAL
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch(debuggerName) {
|
||||
case 'Clapper':
|
||||
clapperDebugger.debug(msg);
|
||||
break;
|
||||
case 'YouTube':
|
||||
ytDebugger.debug(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function debug(msg)
|
||||
{
|
||||
_debug('Clapper', msg);
|
||||
}
|
||||
|
||||
function ytDebug(msg)
|
||||
{
|
||||
_debug('YouTube', msg);
|
||||
}
|
||||
|
||||
function warn(msg)
|
||||
{
|
||||
_logStructured('Clapper', msg, GLib.LogLevelFlags.LEVEL_WARNING);
|
||||
}
|
||||
|
135
src/dialogs.js
135
src/dialogs.js
@@ -1,6 +1,7 @@
|
||||
const { Gio, GObject, Gtk, Gst } = imports.gi;
|
||||
const System = imports.system;
|
||||
const Debug = imports.src.debug;
|
||||
const FileOps = imports.src.fileOps;
|
||||
const Misc = imports.src.misc;
|
||||
const Prefs = imports.src.prefs;
|
||||
const PrefsBase = imports.src.prefsBase;
|
||||
@@ -10,31 +11,57 @@ const { debug } = Debug;
|
||||
var FileChooser = GObject.registerClass(
|
||||
class ClapperFileChooser extends Gtk.FileChooserNative
|
||||
{
|
||||
_init(window)
|
||||
_init(window, purpose)
|
||||
{
|
||||
super._init({
|
||||
transient_for: window,
|
||||
modal: true,
|
||||
select_multiple: true,
|
||||
});
|
||||
|
||||
switch(purpose) {
|
||||
case 'open_local':
|
||||
this._prepareOpenLocal();
|
||||
break;
|
||||
case 'export_playlist':
|
||||
this._prepareExportPlaylist();
|
||||
break;
|
||||
default:
|
||||
debug(new Error(`unknown file chooser purpose: ${purpose}`));
|
||||
break;
|
||||
}
|
||||
|
||||
this.chooserPurpose = purpose;
|
||||
this.responseSignal = this.connect('response', this._onResponse.bind(this));
|
||||
|
||||
/* File chooser closes itself when nobody is holding its ref */
|
||||
this.ref();
|
||||
this.show();
|
||||
}
|
||||
|
||||
_prepareOpenLocal()
|
||||
{
|
||||
this.select_multiple = true;
|
||||
|
||||
const filter = new Gtk.FileFilter({
|
||||
name: 'Media Files',
|
||||
});
|
||||
filter.add_mime_type('video/*');
|
||||
filter.add_mime_type('audio/*');
|
||||
filter.add_mime_type('application/claps');
|
||||
this.subsMimes = [
|
||||
'application/x-subrip',
|
||||
];
|
||||
this.subsMimes.forEach(mime => filter.add_mime_type(mime));
|
||||
Misc.subsMimes.forEach(mime => filter.add_mime_type(mime));
|
||||
this.add_filter(filter);
|
||||
}
|
||||
|
||||
this.responseSignal = this.connect('response', this._onResponse.bind(this));
|
||||
_prepareExportPlaylist()
|
||||
{
|
||||
this.action = Gtk.FileChooserAction.SAVE;
|
||||
this.set_current_name('playlist.claps');
|
||||
|
||||
/* File chooser closes itself when nobody is holding its ref */
|
||||
this.ref();
|
||||
this.show();
|
||||
const filter = new Gtk.FileFilter({
|
||||
name: 'Playlist Files',
|
||||
});
|
||||
filter.add_mime_type('application/claps');
|
||||
this.add_filter(filter);
|
||||
}
|
||||
|
||||
_onResponse(filechooser, response)
|
||||
@@ -45,40 +72,58 @@ class ClapperFileChooser extends Gtk.FileChooserNative
|
||||
this.responseSignal = null;
|
||||
|
||||
if(response === Gtk.ResponseType.ACCEPT) {
|
||||
const files = this.get_files();
|
||||
const playlist = [];
|
||||
|
||||
let index = 0;
|
||||
let file;
|
||||
let subs;
|
||||
|
||||
while((file = files.get_item(index))) {
|
||||
const filename = file.get_basename();
|
||||
const [type, isUncertain] = Gio.content_type_guess(filename, null);
|
||||
|
||||
if(this.subsMimes.includes(type)) {
|
||||
subs = file;
|
||||
files.remove(index);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
playlist.push(file);
|
||||
index++;
|
||||
switch(this.chooserPurpose) {
|
||||
case 'open_local':
|
||||
this._handleOpenLocal();
|
||||
break;
|
||||
case 'export_playlist':
|
||||
this._handleExportPlaylist();
|
||||
break;
|
||||
}
|
||||
|
||||
const { player } = this.get_transient_for().get_child();
|
||||
|
||||
if(playlist.length)
|
||||
player.set_playlist(playlist);
|
||||
|
||||
/* add subs to single selected video
|
||||
or to already playing file */
|
||||
if(subs && !files.get_item(1))
|
||||
player.set_subtitles(subs);
|
||||
}
|
||||
|
||||
this.unref();
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
_handleOpenLocal()
|
||||
{
|
||||
const files = this.get_files();
|
||||
const filesArray = [];
|
||||
|
||||
let index = 0;
|
||||
let file;
|
||||
|
||||
while((file = files.get_item(index))) {
|
||||
filesArray.push(file);
|
||||
index++;
|
||||
}
|
||||
|
||||
const { application } = this.transient_for;
|
||||
const isHandlesOpen = Boolean(
|
||||
application.flags & Gio.ApplicationFlags.HANDLES_OPEN
|
||||
);
|
||||
|
||||
/* Remote app does not handle open */
|
||||
if(isHandlesOpen)
|
||||
application.open(filesArray, "");
|
||||
else
|
||||
application._openFilesAsync(filesArray);
|
||||
}
|
||||
|
||||
_handleExportPlaylist()
|
||||
{
|
||||
const file = this.get_file();
|
||||
const { playlistWidget } = this.transient_for.child.player;
|
||||
const playlist = playlistWidget.getPlaylist(true);
|
||||
|
||||
FileOps.saveFileSimplePromise(file, playlist.join('\n'))
|
||||
.then(() => {
|
||||
debug(`exported playlist to file: ${file.get_path()}`);
|
||||
})
|
||||
.catch(err => {
|
||||
debug(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -125,8 +170,6 @@ class ClapperUriDialog extends Gtk.Dialog
|
||||
area.append(box);
|
||||
|
||||
this.closeSignal = this.connect('close-request', this._onCloseRequest.bind(this));
|
||||
|
||||
this.ref();
|
||||
this.show();
|
||||
}
|
||||
|
||||
@@ -241,6 +284,10 @@ class ClapperPrefsDialog extends Gtk.Dialog
|
||||
{
|
||||
title: 'Network',
|
||||
widget: Prefs.NetworkPage,
|
||||
},
|
||||
{
|
||||
title: 'YouTube',
|
||||
widget: Prefs.YouTubePage,
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -266,8 +313,6 @@ class ClapperPrefsDialog extends Gtk.Dialog
|
||||
area.append(prefsNotebook);
|
||||
|
||||
this.closeSignal = this.connect('close-request', this._onCloseRequest.bind(this));
|
||||
|
||||
this.ref();
|
||||
this.show();
|
||||
}
|
||||
|
||||
@@ -328,8 +373,6 @@ class ClapperAboutDialog extends Gtk.AboutDialog
|
||||
});
|
||||
|
||||
this.closeSignal = this.connect('close-request', this._onCloseRequest.bind(this));
|
||||
|
||||
this.ref();
|
||||
this.show();
|
||||
}
|
||||
|
||||
|
227
src/fileOps.js
Normal file
227
src/fileOps.js
Normal file
@@ -0,0 +1,227 @@
|
||||
const { Gio, GLib } = imports.gi;
|
||||
const ByteArray = imports.byteArray;
|
||||
const Debug = imports.src.debug;
|
||||
const Misc = imports.src.misc;
|
||||
|
||||
const { debug } = Debug;
|
||||
|
||||
/* FIXME: Use Gio._LocalFilePrototype once we are safe to assume
|
||||
* that GJS with https://gitlab.gnome.org/GNOME/gjs/-/commit/ec9385b8 is used. */
|
||||
const LocalFilePrototype = Gio.File.new_for_path('/').constructor.prototype;
|
||||
|
||||
Gio._promisify(LocalFilePrototype, 'load_bytes_async', 'load_bytes_finish');
|
||||
Gio._promisify(LocalFilePrototype, 'make_directory_async', 'make_directory_finish');
|
||||
Gio._promisify(LocalFilePrototype, 'replace_contents_bytes_async', 'replace_contents_finish');
|
||||
Gio._promisify(LocalFilePrototype, 'query_info_async', 'query_info_finish');
|
||||
Gio._promisify(LocalFilePrototype, 'enumerate_children_async', 'enumerate_children_finish');
|
||||
|
||||
Gio._promisify(Gio.FileEnumerator.prototype, 'close_async', 'close_finish');
|
||||
Gio._promisify(Gio.FileEnumerator.prototype, 'next_files_async', 'next_files_finish');
|
||||
|
||||
function createCacheDirPromise()
|
||||
{
|
||||
const dir = Gio.File.new_for_path(
|
||||
GLib.get_user_cache_dir() + '/' + Misc.appId
|
||||
);
|
||||
|
||||
return createDirPromise(dir);
|
||||
}
|
||||
|
||||
function createTempDirPromise()
|
||||
{
|
||||
const dir = Gio.File.new_for_path(
|
||||
GLib.get_tmp_dir() + '/' + Misc.appId
|
||||
);
|
||||
|
||||
return createDirPromise(dir);
|
||||
}
|
||||
|
||||
/* Creates dir and resolves with it */
|
||||
function createDirPromise(dir)
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
if(dir.query_exists(null))
|
||||
return resolve(dir);
|
||||
|
||||
dir.make_directory_async(
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
null
|
||||
)
|
||||
.then(success => {
|
||||
if(success)
|
||||
return resolve(dir);
|
||||
|
||||
reject(new Error(`could not create dir: ${dir.get_path()}`));
|
||||
})
|
||||
.catch(err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
/* Simple save data to GioFile */
|
||||
function saveFileSimplePromise(file, data)
|
||||
{
|
||||
return file.replace_contents_bytes_async(
|
||||
GLib.Bytes.new_take(data),
|
||||
null,
|
||||
false,
|
||||
Gio.FileCreateFlags.NONE,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/* Saves file in optional subdirectory and resolves with it */
|
||||
function saveFilePromise(place, subdirName, fileName, data)
|
||||
{
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let folderPath = GLib[`get_${place}_dir`]() + '/' + Misc.appId;
|
||||
|
||||
if(subdirName)
|
||||
folderPath += `/${subdirName}`;
|
||||
|
||||
const destDir = Gio.File.new_for_path(folderPath);
|
||||
const destPath = folderPath + '/' + fileName;
|
||||
|
||||
debug(`saving file: ${destPath}`);
|
||||
|
||||
const checkFolders = (subdirName)
|
||||
? [destDir.get_parent(), destDir]
|
||||
: [destDir];
|
||||
|
||||
for(let dir of checkFolders) {
|
||||
const createdDir = await createDirPromise(dir).catch(debug);
|
||||
if(!createdDir)
|
||||
return reject(new Error(`could not create dir: ${dir.get_path()}`));
|
||||
}
|
||||
|
||||
const destFile = destDir.get_child(fileName);
|
||||
saveFileSimplePromise(destFile, data)
|
||||
.then(() => {
|
||||
debug(`saved file: ${destPath}`);
|
||||
resolve(destFile);
|
||||
})
|
||||
.catch(err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
function getFileContentsPromise(place, subdirName, fileName)
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
let destPath = GLib[`get_${place}_dir`]() + '/' + Misc.appId;
|
||||
|
||||
if(subdirName)
|
||||
destPath += `/${subdirName}`;
|
||||
|
||||
destPath += `/${fileName}`;
|
||||
|
||||
const file = Gio.File.new_for_path(destPath);
|
||||
debug(`reading data from: ${destPath}`);
|
||||
|
||||
if(!file.query_exists(null)) {
|
||||
debug(`no such file: ${file.get_path()}`);
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
file.load_bytes_async(null)
|
||||
.then(result => {
|
||||
const data = result[0].get_data();
|
||||
if(!data || !data.length)
|
||||
return reject(new Error('source file is empty'));
|
||||
|
||||
debug(`read data from: ${destPath}`);
|
||||
|
||||
if(data instanceof Uint8Array)
|
||||
resolve(ByteArray.toString(data));
|
||||
else
|
||||
resolve(data);
|
||||
})
|
||||
.catch(err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
function _getDirUrisPromise(dir, isDeep)
|
||||
{
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const enumerator = await dir.enumerate_children_async(
|
||||
'standard::name,standard::type',
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
null
|
||||
).catch(debug);
|
||||
|
||||
if(!enumerator)
|
||||
return reject(new Error('could not create file enumerator'));
|
||||
|
||||
const dirPath = dir.get_path();
|
||||
const arr = [];
|
||||
|
||||
debug(`enumerating files in dir: ${dirPath}`);
|
||||
|
||||
while(true) {
|
||||
const infos = await enumerator.next_files_async(
|
||||
1,
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
null
|
||||
).catch(debug);
|
||||
|
||||
if(!infos || !infos.length)
|
||||
break;
|
||||
|
||||
const fileUri = dir.get_uri() + '/' + infos[0].get_name();
|
||||
|
||||
if(infos[0].get_file_type() !== Gio.FileType.DIRECTORY) {
|
||||
arr.push(fileUri);
|
||||
continue;
|
||||
}
|
||||
if(!isDeep)
|
||||
continue;
|
||||
|
||||
const subDir = Misc.getFileFromLocalUri(fileUri);
|
||||
const subDirUris = await _getDirUrisPromise(subDir, isDeep).catch(debug);
|
||||
|
||||
if(subDirUris && subDirUris.length)
|
||||
arr.push(...subDirUris);
|
||||
}
|
||||
|
||||
const isClosed = await enumerator.close_async(
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
null
|
||||
).catch(debug);
|
||||
|
||||
if(isClosed)
|
||||
debug(`closed enumerator for dir: ${dirPath}`);
|
||||
else
|
||||
debug(new Error(`could not close file enumerator for dir: ${dirPath}`));
|
||||
|
||||
resolve(arr);
|
||||
});
|
||||
}
|
||||
|
||||
/* Either GioFile or URI for dir arg */
|
||||
function getDirFilesUrisPromise(dir, isDeep)
|
||||
{
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if(!dir.get_path)
|
||||
dir = Misc.getFileFromLocalUri(dir);
|
||||
if(!dir)
|
||||
return reject(new Error('invalid directory'));
|
||||
|
||||
const fileInfo = await dir.query_info_async(
|
||||
'standard::type',
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
null
|
||||
).catch(debug);
|
||||
|
||||
if(!fileInfo)
|
||||
return reject(new Error('no file type info'));
|
||||
|
||||
if(fileInfo.get_file_type() !== Gio.FileType.DIRECTORY)
|
||||
return resolve([dir.get_uri()]);
|
||||
|
||||
const arr = await _getDirUrisPromise(dir, isDeep).catch(debug);
|
||||
if(!arr || !arr.length)
|
||||
return reject(new Error('enumerated files list is empty'));
|
||||
|
||||
resolve(arr.sort());
|
||||
});
|
||||
}
|
@@ -37,6 +37,7 @@ class ClapperHeaderBarBase extends Gtk.Box
|
||||
this.menuButton = new Gtk.MenuButton({
|
||||
icon_name: 'open-menu-symbolic',
|
||||
valign: Gtk.Align.CENTER,
|
||||
can_focus: false,
|
||||
});
|
||||
const mainMenuModel = uiBuilder.get_object('mainMenu');
|
||||
const mainMenuPopover = new HeaderBarPopover(mainMenuModel);
|
||||
@@ -53,6 +54,7 @@ class ClapperHeaderBarBase extends Gtk.Box
|
||||
|
||||
const floatButton = new Gtk.Button({
|
||||
icon_name: 'go-bottom-symbolic',
|
||||
can_focus: false,
|
||||
});
|
||||
floatButton.add_css_class('circular');
|
||||
floatButton.add_css_class('linkedleft');
|
||||
@@ -69,6 +71,7 @@ class ClapperHeaderBarBase extends Gtk.Box
|
||||
|
||||
const fullscreenButton = new Gtk.Button({
|
||||
icon_name: 'view-fullscreen-symbolic',
|
||||
can_focus: false,
|
||||
});
|
||||
fullscreenButton.add_css_class('circular');
|
||||
fullscreenButton.add_css_class('linkedright');
|
||||
@@ -151,7 +154,7 @@ class ClapperHeaderBarBase extends Gtk.Box
|
||||
|
||||
for(let name of layoutArr) {
|
||||
/* Menu might be named "appmenu" */
|
||||
if(!menuAdded && name === 'appmenu')
|
||||
if(!menuAdded && (!name || name === 'appmenu' || name === 'icon'))
|
||||
name = 'menu';
|
||||
|
||||
const widget = this[`${name}Widget`];
|
||||
@@ -196,6 +199,7 @@ class ClapperHeaderBarBase extends Gtk.Box
|
||||
const button = new Gtk.Button({
|
||||
icon_name: `window-${name}-symbolic`,
|
||||
valign: Gtk.Align.CENTER,
|
||||
can_focus: false,
|
||||
});
|
||||
button.add_css_class('circular');
|
||||
|
||||
@@ -263,7 +267,5 @@ class ClapperHeaderBarPopover extends Gtk.PopoverMenu
|
||||
|
||||
child.revealControls();
|
||||
child.isPopoverOpen = false;
|
||||
|
||||
child.player.widget.grab_focus();
|
||||
}
|
||||
});
|
||||
|
@@ -1,5 +1,6 @@
|
||||
imports.gi.versions.Gdk = '4.0';
|
||||
imports.gi.versions.Gtk = '4.0';
|
||||
imports.gi.versions.Soup = '2.4';
|
||||
|
||||
const { Gst } = imports.gi;
|
||||
Gst.init(null);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
imports.gi.versions.Gdk = '4.0';
|
||||
imports.gi.versions.Gtk = '4.0';
|
||||
imports.gi.versions.Soup = '2.4';
|
||||
|
||||
const { AppRemote } = imports.src.appRemote;
|
||||
const Misc = imports.src.misc;
|
||||
|
13
src/menu.js
13
src/menu.js
@@ -1,13 +0,0 @@
|
||||
const { GObject, Gtk } = imports.gi;
|
||||
const Dialogs = imports.src.dialogs;
|
||||
|
||||
var actions = {
|
||||
openLocal: (window) => new Dialogs.FileChooser(window),
|
||||
openUri: (window) => new Dialogs.UriDialog(window),
|
||||
prefs: (window) => new Dialogs.PrefsDialog(window),
|
||||
about: (window) => new Dialogs.AboutDialog(window),
|
||||
};
|
||||
|
||||
var accels = [
|
||||
['app.quit', ['q']],
|
||||
];
|
144
src/misc.js
144
src/misc.js
@@ -1,10 +1,14 @@
|
||||
const { Gio, GstAudio, Gdk, Gtk } = imports.gi;
|
||||
const { Gio, GLib, Gdk, Gtk } = imports.gi;
|
||||
const Debug = imports.src.debug;
|
||||
|
||||
const { debug } = Debug;
|
||||
|
||||
var appName = 'Clapper';
|
||||
var appId = 'com.github.rafostar.Clapper';
|
||||
var subsMimes = [
|
||||
'application/x-subrip',
|
||||
'text/x-ssa',
|
||||
];
|
||||
|
||||
var clapperPath = null;
|
||||
var clapperVersion = null;
|
||||
@@ -15,6 +19,16 @@ var settings = new Gio.Settings({
|
||||
|
||||
var maxVolume = 1.5;
|
||||
|
||||
/* Keys must be lowercase */
|
||||
const subsTitles = {
|
||||
sdh: 'SDH',
|
||||
cc: 'CC',
|
||||
traditional: 'Traditional',
|
||||
simplified: 'Simplified',
|
||||
honorifics: 'Honorifics',
|
||||
};
|
||||
const subsKeys = Object.keys(subsTitles);
|
||||
|
||||
let inhibitCookie;
|
||||
|
||||
function getClapperPath()
|
||||
@@ -35,6 +49,68 @@ function getClapperVersion()
|
||||
: '';
|
||||
}
|
||||
|
||||
function getClapperThemeIconUri()
|
||||
{
|
||||
const display = Gdk.Display.get_default();
|
||||
if(!display) return null;
|
||||
|
||||
const iconTheme = Gtk.IconTheme.get_for_display(display);
|
||||
if(!iconTheme || !iconTheme.has_icon(appId))
|
||||
return null;
|
||||
|
||||
const iconPaintable = iconTheme.lookup_icon(appId, null, 256, 1,
|
||||
Gtk.TextDirection.NONE, Gtk.IconLookupFlags.FORCE_REGULAR
|
||||
);
|
||||
const iconFile = iconPaintable.get_file();
|
||||
if(!iconFile) return null;
|
||||
|
||||
const iconPath = iconFile.get_path();
|
||||
if(!iconPath) return null;
|
||||
|
||||
let substractName = iconPath.substring(
|
||||
iconPath.indexOf('/icons/') + 7, iconPath.indexOf('/scalable/')
|
||||
);
|
||||
if(!substractName || substractName.includes('/'))
|
||||
return null;
|
||||
|
||||
substractName = substractName.toLowerCase();
|
||||
const postFix = (substractName === iconTheme.theme_name.toLowerCase())
|
||||
? substractName
|
||||
: 'hicolor';
|
||||
const cacheIconName = `clapper-${postFix}.svg`;
|
||||
|
||||
/* We need to have this icon placed in a folder
|
||||
* accessible from both app runtime and gnome-shell */
|
||||
const expectedFile = Gio.File.new_for_path(
|
||||
GLib.get_user_cache_dir() + `/${appId}/icons/${cacheIconName}`
|
||||
);
|
||||
if(!expectedFile.query_exists(null)) {
|
||||
debug('no cached icon file');
|
||||
|
||||
const dirPath = expectedFile.get_parent().get_path();
|
||||
GLib.mkdir_with_parents(dirPath, 493); // octal 755
|
||||
iconFile.copy(expectedFile,
|
||||
Gio.FileCopyFlags.TARGET_DEFAULT_PERMS, null, null
|
||||
);
|
||||
debug(`icon copied to cache dir: ${cacheIconName}`);
|
||||
}
|
||||
const iconUri = expectedFile.get_uri();
|
||||
debug(`using cached clapper icon uri: ${iconUri}`);
|
||||
|
||||
return iconUri;
|
||||
}
|
||||
|
||||
function getSubsTitle(infoTitle)
|
||||
{
|
||||
if(!infoTitle)
|
||||
return null;
|
||||
|
||||
const searchName = infoTitle.toLowerCase();
|
||||
const found = subsKeys.find(key => key === searchName);
|
||||
|
||||
return (found) ? subsTitles[found] : null;
|
||||
}
|
||||
|
||||
function loadCustomCss()
|
||||
{
|
||||
const clapperPath = getClapperPath();
|
||||
@@ -95,3 +171,69 @@ function getFormattedTime(time, showHours)
|
||||
const parsed = (hours) ? `${hours}:` : '';
|
||||
return parsed + `${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
function parsePlaylistFiles(filesArray)
|
||||
{
|
||||
let index = filesArray.length;
|
||||
let subs = null;
|
||||
|
||||
while(index--) {
|
||||
const file = filesArray[index];
|
||||
const filename = (file.get_basename)
|
||||
? file.get_basename()
|
||||
: file.substring(file.lastIndexOf('/') + 1);
|
||||
|
||||
const [type, isUncertain] = Gio.content_type_guess(filename, null);
|
||||
|
||||
if(subsMimes.includes(type)) {
|
||||
subs = file;
|
||||
filesArray.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* We only support single video
|
||||
* with external subtitles */
|
||||
if(subs && filesArray.length > 1)
|
||||
subs = null;
|
||||
|
||||
return [filesArray, subs];
|
||||
}
|
||||
|
||||
function getFileFromLocalUri(uri)
|
||||
{
|
||||
const file = Gio.file_new_for_uri(uri);
|
||||
|
||||
if(!file.query_exists(null)) {
|
||||
debug(new Error(`file does not exist: ${file.get_path()}`));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/* JS replacement of "Gst.Uri.get_protocol" */
|
||||
function getUriProtocol(uri)
|
||||
{
|
||||
const arr = uri.split(':');
|
||||
return (arr.length > 1) ? arr[0] : null;
|
||||
}
|
||||
|
||||
function encodeHTML(text)
|
||||
{
|
||||
return text.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function decodeURIPlus(uri)
|
||||
{
|
||||
return decodeURI(uri.replace(/\+/g, ' '));
|
||||
}
|
||||
|
||||
function isHex(num)
|
||||
{
|
||||
return Boolean(num.match(/[0-9a-f]+$/i));
|
||||
}
|
||||
|
555
src/player.js
555
src/player.js
@@ -2,32 +2,59 @@ const { Gdk, Gio, GObject, Gst, GstClapper, Gtk } = imports.gi;
|
||||
const ByteArray = imports.byteArray;
|
||||
const Debug = imports.src.debug;
|
||||
const Misc = imports.src.misc;
|
||||
const { PlayerBase } = imports.src.playerBase;
|
||||
const YouTube = imports.src.youtube;
|
||||
const { PlaylistWidget } = imports.src.playlist;
|
||||
const { WebApp } = imports.src.webApp;
|
||||
|
||||
const { debug } = Debug;
|
||||
const { debug, warn } = Debug;
|
||||
const { settings } = Misc;
|
||||
|
||||
let WebServer;
|
||||
|
||||
var Player = GObject.registerClass(
|
||||
class ClapperPlayer extends PlayerBase
|
||||
class ClapperPlayer extends GstClapper.Clapper
|
||||
{
|
||||
_init()
|
||||
{
|
||||
super._init();
|
||||
const gtk4plugin = new GstClapper.ClapperGtk4Plugin();
|
||||
const glsinkbin = Gst.ElementFactory.make('glsinkbin', null);
|
||||
glsinkbin.sink = gtk4plugin.video_sink;
|
||||
|
||||
super._init({
|
||||
signal_dispatcher: new GstClapper.ClapperGMainContextSignalDispatcher(),
|
||||
video_renderer: new GstClapper.ClapperVideoOverlayVideoRenderer({
|
||||
video_sink: glsinkbin,
|
||||
}),
|
||||
mpris: new GstClapper.ClapperMpris({
|
||||
own_name: `org.mpris.MediaPlayer2.${Misc.appName}`,
|
||||
id_path: '/' + Misc.appId.replace(/\./g, '/'),
|
||||
identity: Misc.appName,
|
||||
desktop_entry: Misc.appId,
|
||||
default_art_url: Misc.getClapperThemeIconUri(),
|
||||
}),
|
||||
});
|
||||
|
||||
this.widget = gtk4plugin.video_sink.widget;
|
||||
this.widget.add_css_class('videowidget');
|
||||
|
||||
this.visualization_enabled = false;
|
||||
|
||||
this.webserver = null;
|
||||
this.webapp = null;
|
||||
this.ytClient = null;
|
||||
this.playlistWidget = new PlaylistWidget();
|
||||
|
||||
this.seek_done = true;
|
||||
this.doneStartup = false;
|
||||
this.needsFastSeekRestore = false;
|
||||
this.customVideoTitle = null;
|
||||
|
||||
this.playOnFullscreen = false;
|
||||
this.windowMapped = false;
|
||||
this.quitOnStop = false;
|
||||
this.needsTocUpdate = true;
|
||||
|
||||
this.keyPressCount = 0;
|
||||
|
||||
const keyController = new Gtk.EventControllerKey();
|
||||
keyController.connect('key-pressed', this._onWidgetKeyPressed.bind(this));
|
||||
keyController.connect('key-released', this._onWidgetKeyReleased.bind(this));
|
||||
this.widget.add_controller(keyController);
|
||||
this.set_all_plugins_ranks();
|
||||
this.set_initial_config();
|
||||
this.set_and_bind_settings();
|
||||
|
||||
this.connect('state-changed', this._onStateChanged.bind(this));
|
||||
this.connect('uri-loaded', this._onUriLoaded.bind(this));
|
||||
@@ -35,25 +62,117 @@ class ClapperPlayer extends PlayerBase
|
||||
this.connect('warning', this._onPlayerWarning.bind(this));
|
||||
this.connect('error', this._onPlayerError.bind(this));
|
||||
|
||||
settings.connect('changed', this._onSettingsKeyChanged.bind(this));
|
||||
|
||||
this._realizeSignal = this.widget.connect('realize', this._onWidgetRealize.bind(this));
|
||||
}
|
||||
|
||||
set_and_bind_settings()
|
||||
{
|
||||
const settingsToSet = [
|
||||
'seeking-mode',
|
||||
'audio-offset',
|
||||
'subtitle-offset',
|
||||
'play-flags',
|
||||
'webserver-enabled'
|
||||
];
|
||||
|
||||
for(let key of settingsToSet)
|
||||
this._onSettingsKeyChanged(settings, key);
|
||||
|
||||
const flag = Gio.SettingsBindFlags.GET;
|
||||
settings.bind('keep-last-frame', this.widget, 'keep-last-frame', flag);
|
||||
settings.bind('subtitle-font', this.pipeline, 'subtitle-font-desc', flag);
|
||||
}
|
||||
|
||||
set_initial_config()
|
||||
{
|
||||
this.set_mute(false);
|
||||
|
||||
/* FIXME: change into option in preferences */
|
||||
const pipeline = this.get_pipeline();
|
||||
pipeline.ring_buffer_max_size = 8 * 1024 * 1024;
|
||||
}
|
||||
|
||||
set_all_plugins_ranks()
|
||||
{
|
||||
let data = [];
|
||||
|
||||
/* Set empty plugin list if someone messed it externally */
|
||||
try {
|
||||
data = JSON.parse(settings.get_string('plugin-ranking'));
|
||||
if(!Array.isArray(data))
|
||||
throw new Error('plugin ranking data is not an array!');
|
||||
}
|
||||
catch(err) {
|
||||
debug(err);
|
||||
settings.set_string('plugin-ranking', "[]");
|
||||
}
|
||||
|
||||
for(let plugin of data) {
|
||||
if(!plugin.apply || !plugin.name)
|
||||
continue;
|
||||
|
||||
this.set_plugin_rank(plugin.name, plugin.rank);
|
||||
}
|
||||
}
|
||||
|
||||
set_plugin_rank(name, rank)
|
||||
{
|
||||
const gstRegistry = Gst.Registry.get();
|
||||
const feature = gstRegistry.lookup_feature(name);
|
||||
if(!feature) {
|
||||
warn(`cannot change rank of unavailable plugin: ${name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const oldRank = feature.get_rank();
|
||||
if(rank === oldRank)
|
||||
return;
|
||||
|
||||
feature.set_rank(rank);
|
||||
debug(`changed rank: ${oldRank} -> ${rank} for ${name}`);
|
||||
}
|
||||
|
||||
set_uri(uri)
|
||||
{
|
||||
if(Gst.Uri.get_protocol(uri) !== 'file')
|
||||
return super.set_uri(uri);
|
||||
this.customVideoTitle = null;
|
||||
|
||||
let file = Gio.file_new_for_uri(uri);
|
||||
if(!file.query_exists(null)) {
|
||||
debug(`file does not exist: ${file.get_path()}`, 'LEVEL_WARNING');
|
||||
if(Misc.getUriProtocol(uri) !== 'file') {
|
||||
const [isYouTubeUri, videoId] = YouTube.checkYouTubeUri(uri);
|
||||
|
||||
if(!isYouTubeUri)
|
||||
return super.set_uri(uri);
|
||||
|
||||
if(!this.ytClient)
|
||||
this.ytClient = new YouTube.YouTubeClient();
|
||||
|
||||
const { root } = this.widget;
|
||||
const surface = root.get_surface();
|
||||
const monitor = root.display.get_monitor_at_surface(surface);
|
||||
|
||||
this.ytClient.getPlaybackDataAsync(videoId, monitor)
|
||||
.then(data => {
|
||||
this.customVideoTitle = data.title;
|
||||
super.set_uri(data.uri);
|
||||
})
|
||||
.catch(debug);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const file = Misc.getFileFromLocalUri(uri);
|
||||
if(!file) {
|
||||
if(!this.playlistWidget.nextTrack())
|
||||
debug('set media reached end of playlist');
|
||||
|
||||
return;
|
||||
}
|
||||
if(uri.endsWith('.claps'))
|
||||
return this.load_playlist_file(file);
|
||||
if(uri.endsWith('.claps')) {
|
||||
this.load_playlist_file(file);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
super.set_uri(uri);
|
||||
}
|
||||
@@ -86,36 +205,58 @@ class ClapperPlayer extends PlayerBase
|
||||
this.set_playlist(playlist);
|
||||
}
|
||||
|
||||
_preparePlaylist(playlist)
|
||||
{
|
||||
this.playlistWidget.removeAll();
|
||||
|
||||
for(let source of playlist) {
|
||||
const uri = (source.get_uri != null)
|
||||
? source.get_uri()
|
||||
: Gst.uri_is_valid(source)
|
||||
? source
|
||||
: Gst.filename_to_uri(source);
|
||||
|
||||
this.playlistWidget.addItem(uri);
|
||||
}
|
||||
}
|
||||
|
||||
set_playlist(playlist)
|
||||
{
|
||||
this._preparePlaylist(playlist);
|
||||
if(this.state !== GstClapper.ClapperState.STOPPED)
|
||||
this.stop();
|
||||
|
||||
const firstTrack = this.playlistWidget.get_row_at_index(0);
|
||||
if(!firstTrack) return;
|
||||
debug('new playlist');
|
||||
this.playlistWidget.removeAll();
|
||||
this._addPlaylistItems(playlist);
|
||||
|
||||
firstTrack.activate();
|
||||
if(settings.get_boolean('fullscreen-auto')) {
|
||||
const { root } = this.playlistWidget;
|
||||
/* Do not enter fullscreen when already in it
|
||||
* or when in floating mode */
|
||||
if(
|
||||
root
|
||||
&& root.child
|
||||
&& !root.child.isFullscreenMode
|
||||
&& root.child.controlsRevealer.reveal_child
|
||||
)
|
||||
root.fullscreen();
|
||||
}
|
||||
|
||||
/* If not mapped yet, first track will play after map */
|
||||
if(this.windowMapped)
|
||||
this._playFirstTrack();
|
||||
}
|
||||
|
||||
append_playlist(playlist)
|
||||
{
|
||||
debug('appending playlist');
|
||||
this._addPlaylistItems(playlist);
|
||||
|
||||
if(
|
||||
!this.windowMapped
|
||||
|| this.state !== GstClapper.ClapperState.STOPPED
|
||||
)
|
||||
return;
|
||||
|
||||
if(!this.playlistWidget.nextTrack())
|
||||
debug('playlist append failed');
|
||||
}
|
||||
|
||||
set_subtitles(source)
|
||||
{
|
||||
const uri = (source.get_uri)
|
||||
? source.get_uri()
|
||||
: source;
|
||||
const uri = this._getSourceUri(source);
|
||||
|
||||
/* Check local file existence */
|
||||
if(
|
||||
Misc.getUriProtocol(uri) === 'file'
|
||||
&& !Misc.getFileFromLocalUri(uri)
|
||||
)
|
||||
return;
|
||||
|
||||
this.set_subtitle_uri(uri);
|
||||
this.set_subtitle_track_enabled(true);
|
||||
@@ -158,7 +299,7 @@ class ClapperPlayer extends PlayerBase
|
||||
|
||||
seek_seconds(seconds)
|
||||
{
|
||||
this.seek(seconds * 1000000000);
|
||||
this.seek(seconds * Gst.SECOND);
|
||||
}
|
||||
|
||||
seek_chapter(seconds)
|
||||
@@ -168,12 +309,9 @@ class ClapperPlayer extends PlayerBase
|
||||
return;
|
||||
}
|
||||
|
||||
/* FIXME: Remove this check when GstPlay(er) have set_seek_mode function */
|
||||
if(this.set_seek_mode) {
|
||||
this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT);
|
||||
this.seekingMode = 'normal';
|
||||
this.needsFastSeekRestore = true;
|
||||
}
|
||||
this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT);
|
||||
this.seekingMode = 'normal';
|
||||
this.needsFastSeekRestore = true;
|
||||
|
||||
this.seek_seconds(seconds);
|
||||
}
|
||||
@@ -221,13 +359,22 @@ class ClapperPlayer extends PlayerBase
|
||||
controls.volumeScale.set_value(volume);
|
||||
}
|
||||
|
||||
toggle_play()
|
||||
next_chapter()
|
||||
{
|
||||
const action = (this.state === GstClapper.ClapperState.PLAYING)
|
||||
? 'pause'
|
||||
: 'play';
|
||||
return this._switchChapter(false);
|
||||
}
|
||||
|
||||
this[action]();
|
||||
prev_chapter()
|
||||
{
|
||||
return this._switchChapter(true);
|
||||
}
|
||||
|
||||
emitWs(action, value)
|
||||
{
|
||||
if(!this.webserver)
|
||||
return;
|
||||
|
||||
this.webserver.sendMessage({ action, value });
|
||||
}
|
||||
|
||||
receiveWs(action, value)
|
||||
@@ -236,9 +383,26 @@ class ClapperPlayer extends PlayerBase
|
||||
case 'toggle_play':
|
||||
case 'play':
|
||||
case 'pause':
|
||||
this[action]();
|
||||
break;
|
||||
case 'seek':
|
||||
case 'set_playlist':
|
||||
case 'append_playlist':
|
||||
case 'set_subtitles':
|
||||
this[action](value);
|
||||
break;
|
||||
case 'change_playlist_item':
|
||||
this.playlistWidget.changeActiveRow(value);
|
||||
break;
|
||||
case 'toggle_fullscreen':
|
||||
case 'volume_up':
|
||||
case 'volume_down':
|
||||
case 'next_track':
|
||||
case 'prev_track':
|
||||
case 'next_chapter':
|
||||
case 'prev_chapter':
|
||||
this.widget.activate_action(`app.${action}`, null);
|
||||
break;
|
||||
case 'toggle_maximized':
|
||||
action = 'toggle-maximized';
|
||||
case 'minimize':
|
||||
@@ -246,20 +410,64 @@ class ClapperPlayer extends PlayerBase
|
||||
this.widget.activate_action(`window.${action}`, null);
|
||||
break;
|
||||
default:
|
||||
const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
|
||||
|
||||
switch(action) {
|
||||
case 'toggle_fullscreen':
|
||||
clapperWidget.toggleFullscreen();
|
||||
break;
|
||||
default:
|
||||
super.receiveWs(action, value);
|
||||
break;
|
||||
}
|
||||
warn(`unhandled WebSocket action: ${action}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_switchChapter(isPrevious)
|
||||
{
|
||||
if(this.state === GstClapper.ClapperState.STOPPED)
|
||||
return false;
|
||||
|
||||
const { chapters } = this.widget.root.child.controls;
|
||||
if(!chapters)
|
||||
return false;
|
||||
|
||||
const now = this.position / Gst.SECOND;
|
||||
const chapterTimes = Object.keys(chapters).sort((a, b) => a - b);
|
||||
if(isPrevious)
|
||||
chapterTimes.reverse();
|
||||
|
||||
const chapter = chapterTimes.find(time => (isPrevious)
|
||||
? now - 2.5 > time
|
||||
: now < time
|
||||
);
|
||||
if(!chapter)
|
||||
return false;
|
||||
|
||||
this.seek_chapter(chapter);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_addPlaylistItems(playlist)
|
||||
{
|
||||
for(let source of playlist) {
|
||||
const uri = this._getSourceUri(source);
|
||||
|
||||
debug(`added uri: ${uri}`);
|
||||
this.playlistWidget.addItem(uri);
|
||||
}
|
||||
}
|
||||
|
||||
_getSourceUri(source)
|
||||
{
|
||||
return (source.get_uri != null)
|
||||
? source.get_uri()
|
||||
: Gst.uri_is_valid(source)
|
||||
? source
|
||||
: Gst.filename_to_uri(source);
|
||||
}
|
||||
|
||||
_playFirstTrack()
|
||||
{
|
||||
const firstTrack = this.playlistWidget.get_row_at_index(0);
|
||||
if(!firstTrack) return;
|
||||
|
||||
firstTrack.activate();
|
||||
}
|
||||
|
||||
_performCloseCleanup(window)
|
||||
{
|
||||
window.disconnect(this.closeRequestSignal);
|
||||
@@ -277,21 +485,21 @@ class ClapperPlayer extends PlayerBase
|
||||
}
|
||||
/* If "quitOnStop" is set here it means that we are in middle of autoclosing */
|
||||
if(this.state !== GstClapper.ClapperState.STOPPED && !this.quitOnStop) {
|
||||
let resumeInfo = {};
|
||||
if(settings.get_boolean('resume-enabled')) {
|
||||
const resumeTitle = this.playlistWidget.getActiveFilename();
|
||||
const resumeTime = Math.floor(this.position / 1000000000);
|
||||
const resumeDuration = this.duration / 1000000000;
|
||||
const playlistItem = this.playlistWidget.getActiveRow();
|
||||
|
||||
/* Do not save resume info when title is too long (random URI),
|
||||
* video is very short, just started or almost finished */
|
||||
let resumeInfo = {};
|
||||
if(playlistItem.isLocalFile && settings.get_boolean('resume-enabled')) {
|
||||
const resumeTime = Math.floor(this.position / Gst.SECOND);
|
||||
const resumeDuration = this.duration / Gst.SECOND;
|
||||
|
||||
/* Do not save resume info when video is very short,
|
||||
* just started or almost finished */
|
||||
if(
|
||||
resumeTitle.length < 300
|
||||
&& resumeDuration > 60
|
||||
resumeDuration > 60
|
||||
&& resumeTime > 15
|
||||
&& resumeDuration - resumeTime > 20
|
||||
) {
|
||||
resumeInfo.title = resumeTitle;
|
||||
resumeInfo.title = playlistItem.filename;
|
||||
resumeInfo.time = resumeTime;
|
||||
resumeInfo.duration = resumeDuration;
|
||||
|
||||
@@ -312,7 +520,6 @@ class ClapperPlayer extends PlayerBase
|
||||
|
||||
_onStateChanged(player, state)
|
||||
{
|
||||
this.state = state;
|
||||
this.emitWs('state_changed', state);
|
||||
|
||||
if(state !== GstClapper.ClapperState.BUFFERING) {
|
||||
@@ -356,7 +563,7 @@ class ClapperPlayer extends PlayerBase
|
||||
debug(`end of stream: ${lastTrackId}`);
|
||||
this.emitWs('end_of_stream', lastTrackId);
|
||||
|
||||
if(this.playlistWidget.nextTrack())
|
||||
if(this.playlistWidget._handleStreamEnded(player))
|
||||
return;
|
||||
|
||||
if(settings.get_boolean('close-auto')) {
|
||||
@@ -364,6 +571,10 @@ class ClapperPlayer extends PlayerBase
|
||||
this.quitOnStop = true;
|
||||
this._performCloseCleanup(this.widget.get_root());
|
||||
}
|
||||
|
||||
/* When this signal is connected player
|
||||
* wants us to decide if it should stop */
|
||||
this.stop();
|
||||
}
|
||||
|
||||
_onUriLoaded(player, uri)
|
||||
@@ -371,26 +582,12 @@ class ClapperPlayer extends PlayerBase
|
||||
debug(`URI loaded: ${uri}`);
|
||||
this.needsTocUpdate = true;
|
||||
|
||||
if(!this.doneStartup) {
|
||||
this.doneStartup = true;
|
||||
|
||||
if(settings.get_boolean('fullscreen-auto')) {
|
||||
const root = player.widget.get_root();
|
||||
const clapperWidget = root.get_child();
|
||||
if(!clapperWidget.isFullscreenMode) {
|
||||
this.playOnFullscreen = true;
|
||||
root.fullscreen();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.play();
|
||||
player.play();
|
||||
}
|
||||
|
||||
_onPlayerWarning(player, error)
|
||||
{
|
||||
debug(error.message, 'LEVEL_WARNING');
|
||||
debug(error.message);
|
||||
}
|
||||
|
||||
_onPlayerError(player, error)
|
||||
@@ -421,75 +618,10 @@ class ClapperPlayer extends PlayerBase
|
||||
);
|
||||
}
|
||||
|
||||
/* Widget only - does not happen when using controls navigation */
|
||||
_onWidgetKeyPressed(controller, keyval, keycode, state)
|
||||
_onWindowMap(window)
|
||||
{
|
||||
const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
|
||||
let bool = false;
|
||||
|
||||
this.keyPressCount++;
|
||||
|
||||
switch(keyval) {
|
||||
case Gdk.KEY_Up:
|
||||
bool = true;
|
||||
case Gdk.KEY_Down:
|
||||
this.adjust_volume(bool);
|
||||
break;
|
||||
case Gdk.KEY_Right:
|
||||
bool = true;
|
||||
case Gdk.KEY_Left:
|
||||
this.adjust_position(bool);
|
||||
if(this.keyPressCount > 1)
|
||||
clapperWidget.revealControls();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Also happens after using controls navigation for selected keys */
|
||||
_onWidgetKeyReleased(controller, keyval, keycode, state)
|
||||
{
|
||||
const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
|
||||
let value, root;
|
||||
|
||||
this.keyPressCount = 0;
|
||||
|
||||
switch(keyval) {
|
||||
case Gdk.KEY_space:
|
||||
this.toggle_play();
|
||||
break;
|
||||
case Gdk.KEY_Return:
|
||||
if(clapperWidget.isFullscreenMode)
|
||||
clapperWidget.revealControls(true);
|
||||
break;
|
||||
case Gdk.KEY_Right:
|
||||
case Gdk.KEY_Left:
|
||||
value = Math.round(
|
||||
clapperWidget.controls.positionScale.get_value()
|
||||
);
|
||||
this.seek_seconds(value);
|
||||
clapperWidget._setHideControlsTimeout();
|
||||
break;
|
||||
case Gdk.KEY_F11:
|
||||
case Gdk.KEY_f:
|
||||
case Gdk.KEY_F:
|
||||
clapperWidget.toggleFullscreen();
|
||||
break;
|
||||
case Gdk.KEY_Escape:
|
||||
if(clapperWidget.isFullscreenMode) {
|
||||
root = this.widget.get_root();
|
||||
root.unfullscreen();
|
||||
}
|
||||
break;
|
||||
case Gdk.KEY_q:
|
||||
case Gdk.KEY_Q:
|
||||
root = this.widget.get_root();
|
||||
root.emit('close-request');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.windowMapped = true;
|
||||
this._playFirstTrack();
|
||||
}
|
||||
|
||||
_onCloseRequest(window)
|
||||
@@ -502,4 +634,109 @@ class ClapperPlayer extends PlayerBase
|
||||
this.quitOnStop = true;
|
||||
this.stop();
|
||||
}
|
||||
|
||||
_onSettingsKeyChanged(settings, key)
|
||||
{
|
||||
let root, value, action;
|
||||
|
||||
switch(key) {
|
||||
case 'seeking-mode':
|
||||
this.seekingMode = settings.get_string('seeking-mode');
|
||||
switch(this.seekingMode) {
|
||||
case 'fast':
|
||||
this.set_seek_mode(GstClapper.ClapperSeekMode.FAST);
|
||||
break;
|
||||
case 'accurate':
|
||||
this.set_seek_mode(GstClapper.ClapperSeekMode.ACCURATE);
|
||||
break;
|
||||
default:
|
||||
this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'render-shadows':
|
||||
root = this.widget.get_root();
|
||||
if(!root) break;
|
||||
|
||||
const gpuClass = 'gpufriendly';
|
||||
const renderShadows = settings.get_boolean(key);
|
||||
const hasShadows = !root.has_css_class(gpuClass);
|
||||
|
||||
if(renderShadows === hasShadows)
|
||||
break;
|
||||
|
||||
action = (renderShadows) ? 'remove' : 'add';
|
||||
root[action + '_css_class'](gpuClass);
|
||||
break;
|
||||
case 'audio-offset':
|
||||
value = Math.round(settings.get_double(key) * -Gst.MSECOND);
|
||||
this.set_audio_video_offset(value);
|
||||
debug(`set audio-video offset: ${value}`);
|
||||
break;
|
||||
case 'subtitle-offset':
|
||||
value = Math.round(settings.get_double(key) * -Gst.MSECOND);
|
||||
this.set_subtitle_video_offset(value);
|
||||
debug(`set subtitle-video offset: ${value}`);
|
||||
break;
|
||||
case 'dark-theme':
|
||||
root = this.widget.get_root();
|
||||
if(!root) break;
|
||||
|
||||
root.application._onThemeChanged(Gtk.Settings.get_default());
|
||||
break;
|
||||
case 'play-flags':
|
||||
const initialFlags = this.pipeline.flags;
|
||||
const settingsFlags = settings.get_int(key);
|
||||
|
||||
if(initialFlags === settingsFlags)
|
||||
break;
|
||||
|
||||
this.pipeline.flags = settingsFlags;
|
||||
debug(`changed play flags: ${initialFlags} -> ${settingsFlags}`);
|
||||
break;
|
||||
case 'webserver-enabled':
|
||||
case 'webapp-enabled':
|
||||
const webserverEnabled = settings.get_boolean('webserver-enabled');
|
||||
|
||||
if(webserverEnabled) {
|
||||
if(!WebServer) {
|
||||
/* Probably most users will not use this,
|
||||
* so conditional import for faster startup */
|
||||
WebServer = imports.src.webServer.WebServer;
|
||||
}
|
||||
|
||||
if(!this.webserver) {
|
||||
this.webserver = new WebServer(settings.get_int('webserver-port'));
|
||||
this.webserver.passMsgData = this.receiveWs.bind(this);
|
||||
}
|
||||
this.webserver.startListening();
|
||||
|
||||
const webappEnabled = settings.get_boolean('webapp-enabled');
|
||||
|
||||
if(!this.webapp && !webappEnabled)
|
||||
break;
|
||||
|
||||
if(webappEnabled) {
|
||||
if(!this.webapp)
|
||||
this.webapp = new WebApp();
|
||||
|
||||
this.webapp.startDaemonApp(settings.get_int('webapp-port'));
|
||||
}
|
||||
}
|
||||
else if(this.webserver) {
|
||||
/* remote app will close when connection is lost
|
||||
* which will cause the daemon to close too */
|
||||
this.webserver.stopListening();
|
||||
}
|
||||
break;
|
||||
case 'webserver-port':
|
||||
if(!this.webserver)
|
||||
break;
|
||||
|
||||
this.webserver.setListeningPort(settings.get_int(key));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,256 +0,0 @@
|
||||
const { Gio, GLib, GObject, Gst, GstClapper, Gtk } = imports.gi;
|
||||
const Debug = imports.src.debug;
|
||||
const Misc = imports.src.misc;
|
||||
const { PlaylistWidget } = imports.src.playlist;
|
||||
const { WebApp } = imports.src.webApp;
|
||||
|
||||
const { debug } = Debug;
|
||||
const { settings } = Misc;
|
||||
|
||||
let WebServer;
|
||||
|
||||
var PlayerBase = GObject.registerClass(
|
||||
class ClapperPlayerBase extends GstClapper.Clapper
|
||||
{
|
||||
_init()
|
||||
{
|
||||
const gtk4plugin = new GstClapper.ClapperGtk4Plugin();
|
||||
const glsinkbin = Gst.ElementFactory.make('glsinkbin', null);
|
||||
glsinkbin.sink = gtk4plugin.video_sink;
|
||||
|
||||
const dispatcher = new GstClapper.ClapperGMainContextSignalDispatcher();
|
||||
const renderer = new GstClapper.ClapperVideoOverlayVideoRenderer({
|
||||
video_sink: glsinkbin
|
||||
});
|
||||
|
||||
super._init({
|
||||
signal_dispatcher: dispatcher,
|
||||
video_renderer: renderer
|
||||
});
|
||||
|
||||
this.widget = gtk4plugin.video_sink.widget;
|
||||
this.widget.add_css_class('videowidget');
|
||||
|
||||
this.state = GstClapper.ClapperState.STOPPED;
|
||||
this.visualization_enabled = false;
|
||||
|
||||
this.webserver = null;
|
||||
this.webapp = null;
|
||||
this.playlistWidget = new PlaylistWidget();
|
||||
|
||||
this.set_all_plugins_ranks();
|
||||
this.set_initial_config();
|
||||
this.set_and_bind_settings();
|
||||
|
||||
settings.connect('changed', this._onSettingsKeyChanged.bind(this));
|
||||
}
|
||||
|
||||
set_and_bind_settings()
|
||||
{
|
||||
const settingsToSet = [
|
||||
'seeking-mode',
|
||||
'audio-offset',
|
||||
'subtitle-offset',
|
||||
'play-flags',
|
||||
'webserver-enabled'
|
||||
];
|
||||
|
||||
for(let key of settingsToSet)
|
||||
this._onSettingsKeyChanged(settings, key);
|
||||
|
||||
const flag = Gio.SettingsBindFlags.GET;
|
||||
settings.bind('subtitle-font', this.pipeline, 'subtitle_font_desc', flag);
|
||||
}
|
||||
|
||||
set_initial_config()
|
||||
{
|
||||
this.set_mute(false);
|
||||
|
||||
/* FIXME: change into option in preferences */
|
||||
const pipeline = this.get_pipeline();
|
||||
pipeline.ring_buffer_max_size = 8 * 1024 * 1024;
|
||||
}
|
||||
|
||||
set_all_plugins_ranks()
|
||||
{
|
||||
let data = [];
|
||||
|
||||
/* Set empty plugin list if someone messed it externally */
|
||||
try {
|
||||
data = JSON.parse(settings.get_string('plugin-ranking'));
|
||||
if(!Array.isArray(data))
|
||||
throw new Error('plugin ranking data is not an array!');
|
||||
}
|
||||
catch(err) {
|
||||
debug(err);
|
||||
settings.set_string('plugin-ranking', "[]");
|
||||
}
|
||||
|
||||
for(let plugin of data) {
|
||||
if(!plugin.apply || !plugin.name)
|
||||
continue;
|
||||
|
||||
this.set_plugin_rank(plugin.name, plugin.rank);
|
||||
}
|
||||
}
|
||||
|
||||
set_plugin_rank(name, rank)
|
||||
{
|
||||
const gstRegistry = Gst.Registry.get();
|
||||
const feature = gstRegistry.lookup_feature(name);
|
||||
if(!feature)
|
||||
return debug(`plugin unavailable: ${name}`);
|
||||
|
||||
const oldRank = feature.get_rank();
|
||||
if(rank === oldRank)
|
||||
return;
|
||||
|
||||
feature.set_rank(rank);
|
||||
debug(`changed rank: ${oldRank} -> ${rank} for ${name}`);
|
||||
}
|
||||
|
||||
draw_black(isEnabled)
|
||||
{
|
||||
this.widget.ignore_textures = isEnabled;
|
||||
|
||||
if(this.state !== GstClapper.ClapperState.PLAYING)
|
||||
this.widget.queue_render();
|
||||
}
|
||||
|
||||
emitWs(action, value)
|
||||
{
|
||||
if(!this.webserver)
|
||||
return;
|
||||
|
||||
this.webserver.sendMessage({ action, value });
|
||||
}
|
||||
|
||||
receiveWs(action, value)
|
||||
{
|
||||
debug(`unhandled WebSocket action: ${action}`);
|
||||
}
|
||||
|
||||
_onSettingsKeyChanged(settings, key)
|
||||
{
|
||||
let root, value, action;
|
||||
|
||||
switch(key) {
|
||||
case 'seeking-mode':
|
||||
this.seekingMode = settings.get_string('seeking-mode');
|
||||
switch(this.seekingMode) {
|
||||
case 'fast':
|
||||
this.set_seek_mode(GstClapper.ClapperSeekMode.FAST);
|
||||
break;
|
||||
case 'accurate':
|
||||
this.set_seek_mode(GstClapper.ClapperSeekMode.ACCURATE);
|
||||
break;
|
||||
default:
|
||||
this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'render-shadows':
|
||||
root = this.widget.get_root();
|
||||
/* Editing theme of someone else app is taboo */
|
||||
if(!root || !root.isClapperApp)
|
||||
break;
|
||||
|
||||
const gpuClass = 'gpufriendly';
|
||||
const renderShadows = settings.get_boolean(key);
|
||||
const hasShadows = !root.has_css_class(gpuClass);
|
||||
|
||||
if(renderShadows === hasShadows)
|
||||
break;
|
||||
|
||||
action = (renderShadows) ? 'remove' : 'add';
|
||||
root[action + '_css_class'](gpuClass);
|
||||
break;
|
||||
case 'audio-offset':
|
||||
value = Math.round(settings.get_double(key) * -1000000);
|
||||
this.set_audio_video_offset(value);
|
||||
debug(`set audio-video offset: ${value}`);
|
||||
break;
|
||||
case 'subtitle-offset':
|
||||
value = Math.round(settings.get_double(key) * -1000000);
|
||||
this.set_subtitle_video_offset(value);
|
||||
debug(`set subtitle-video offset: ${value}`);
|
||||
break;
|
||||
case 'dark-theme':
|
||||
case 'brighter-sliders':
|
||||
root = this.widget.get_root();
|
||||
if(!root || !root.isClapperApp)
|
||||
break;
|
||||
|
||||
const brightClass = 'brightscale';
|
||||
const isBrighter = root.has_css_class(brightClass);
|
||||
|
||||
if(key === 'dark-theme' && isBrighter && !settings.get_boolean(key)) {
|
||||
root.remove_css_class(brightClass);
|
||||
debug('remove brighter sliders');
|
||||
break;
|
||||
}
|
||||
|
||||
const setBrighter = settings.get_boolean('brighter-sliders');
|
||||
if(setBrighter === isBrighter)
|
||||
break;
|
||||
|
||||
action = (setBrighter) ? 'add' : 'remove';
|
||||
root[action + '_css_class'](brightClass);
|
||||
debug(`${action} brighter sliders`);
|
||||
break;
|
||||
case 'play-flags':
|
||||
const initialFlags = this.pipeline.flags;
|
||||
const settingsFlags = settings.get_int(key);
|
||||
|
||||
if(initialFlags === settingsFlags)
|
||||
break;
|
||||
|
||||
this.pipeline.flags = settingsFlags;
|
||||
debug(`changed play flags: ${initialFlags} -> ${settingsFlags}`);
|
||||
break;
|
||||
case 'webserver-enabled':
|
||||
case 'webapp-enabled':
|
||||
const webserverEnabled = settings.get_boolean('webserver-enabled');
|
||||
|
||||
if(webserverEnabled) {
|
||||
if(!WebServer) {
|
||||
/* Probably most users will not use this,
|
||||
* so conditional import for faster startup */
|
||||
WebServer = imports.src.webServer.WebServer;
|
||||
}
|
||||
|
||||
if(!this.webserver) {
|
||||
this.webserver = new WebServer(settings.get_int('webserver-port'));
|
||||
this.webserver.passMsgData = this.receiveWs.bind(this);
|
||||
}
|
||||
this.webserver.startListening();
|
||||
|
||||
const webappEnabled = settings.get_boolean('webapp-enabled');
|
||||
|
||||
if(!this.webapp && !webappEnabled)
|
||||
break;
|
||||
|
||||
if(webappEnabled) {
|
||||
if(!this.webapp)
|
||||
this.webapp = new WebApp();
|
||||
|
||||
this.webapp.startDaemonApp(settings.get_int('webapp-port'));
|
||||
}
|
||||
}
|
||||
else if(this.webserver) {
|
||||
/* remote app will close when connection is lost
|
||||
* which will cause the daemon to close too */
|
||||
this.webserver.stopListening();
|
||||
}
|
||||
break;
|
||||
case 'webserver-port':
|
||||
if(!this.webserver)
|
||||
break;
|
||||
|
||||
this.webserver.setListeningPort(settings.get_int(key));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
@@ -23,17 +23,27 @@ class ClapperPlayerRemote extends GObject.Object
|
||||
const uris = [];
|
||||
|
||||
/* We can not send GioFiles via WebSocket */
|
||||
for(let source of playlist) {
|
||||
const uri = (source.get_uri != null)
|
||||
? source.get_uri()
|
||||
: source;
|
||||
|
||||
uris.push(uri);
|
||||
}
|
||||
for(let source of playlist)
|
||||
uris.push(this._getSourceUri(source));
|
||||
|
||||
this.webclient.sendMessage({
|
||||
action: 'set_playlist',
|
||||
value: uris
|
||||
});
|
||||
}
|
||||
|
||||
set_subtitles(source)
|
||||
{
|
||||
this.webclient.sendMessage({
|
||||
action: 'set_subtitles',
|
||||
value: this._getSourceUri(source)
|
||||
});
|
||||
}
|
||||
|
||||
_getSourceUri(source)
|
||||
{
|
||||
return (source.get_uri != null)
|
||||
? source.get_uri()
|
||||
: source;
|
||||
}
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user