mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-30 16:02:00 +02:00
Compare commits
88 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 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,3 +1,5 @@
|
||||
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
|
||||
|
@@ -36,7 +36,8 @@ Additionally it also has a few patches, thus some functionalities work better (o
|
||||
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:
|
||||
|
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)
|
||||
|
@@ -70,7 +70,7 @@ radio {
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.tvmode popover box {
|
||||
.fullscreen.tvmode popover box {
|
||||
text-shadow: none;
|
||||
font-size: 21px;
|
||||
font-weight: 500;
|
||||
@@ -87,17 +87,17 @@ radio {
|
||||
margin-left: 1px;
|
||||
margin-right: 1px;
|
||||
}
|
||||
.tvmode .playercontrols button {
|
||||
.fullscreen.tvmode .playercontrols button {
|
||||
min-width: 32px;
|
||||
min-height: 32px;
|
||||
margin: 5px;
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.tvmode button image {
|
||||
.fullscreen.tvmode button image {
|
||||
-gtk-icon-shadow: none;
|
||||
}
|
||||
.tvmode radio {
|
||||
.fullscreen.tvmode radio {
|
||||
margin-left: 0px;
|
||||
margin-right: 4px;
|
||||
border: 2px solid;
|
||||
@@ -105,13 +105,13 @@ radio {
|
||||
min-height: 17px;
|
||||
}
|
||||
|
||||
.tvmode .playercontrols button image {
|
||||
.fullscreen.tvmode .playercontrols button image {
|
||||
-gtk-icon-size: 24px;
|
||||
}
|
||||
.adwicons .playbackicon {
|
||||
-gtk-icon-size: 20px;
|
||||
}
|
||||
.adwicons.tvmode .playbackicon {
|
||||
.adwicons.fullscreen.tvmode .playbackicon {
|
||||
-gtk-icon-size: 28px;
|
||||
}
|
||||
.labelbuttonlabel {
|
||||
@@ -122,21 +122,21 @@ radio {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 600;
|
||||
}
|
||||
.tvmode .labelbuttonlabel {
|
||||
.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;
|
||||
@@ -144,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;
|
||||
@@ -171,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;
|
||||
@@ -183,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;
|
||||
}
|
||||
@@ -199,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;
|
||||
}
|
||||
@@ -221,7 +221,7 @@ radio {
|
||||
margin-right: -6px;
|
||||
min-height: 180px;
|
||||
}
|
||||
.tvmode .volumescale {
|
||||
.fullscreen.tvmode .volumescale {
|
||||
margin: 2px;
|
||||
margin-left: -6px;
|
||||
margin-right: -4px;
|
||||
@@ -232,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 {
|
||||
@@ -246,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;
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ radio {
|
||||
.chapterlabel {
|
||||
min-width: 32px;
|
||||
}
|
||||
.tvmode .chapterlabel {
|
||||
.fullscreen.tvmode .chapterlabel {
|
||||
min-width: 40px;
|
||||
text-shadow: none;
|
||||
font-size: 22px;
|
||||
@@ -314,7 +314,7 @@ radio {
|
||||
.gpufriendly {
|
||||
box-shadow: -8px -8px transparent, 8px 8px transparent;
|
||||
}
|
||||
.gpufriendlyfs {
|
||||
.fullscreen.gpufriendlyfs {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
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>
|
||||
|
@@ -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,26 @@
|
||||
</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>
|
||||
|
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__ */
|
||||
|
@@ -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__ */
|
399
lib/gst/clapper/gstclapper.c
vendored
399
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,
|
||||
@@ -125,6 +129,7 @@ struct _GstClapper
|
||||
|
||||
GstClapperVideoRenderer *video_renderer;
|
||||
GstClapperSignalDispatcher *signal_dispatcher;
|
||||
GstClapperMpris *mpris;
|
||||
|
||||
gchar *uri;
|
||||
gchar *redirect_uri;
|
||||
@@ -139,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;
|
||||
|
||||
@@ -169,6 +174,10 @@ 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;
|
||||
|
||||
@@ -268,6 +277,8 @@ gst_clapper_init (GstClapper * self)
|
||||
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");
|
||||
}
|
||||
@@ -297,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);
|
||||
@@ -478,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);
|
||||
@@ -494,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)
|
||||
@@ -550,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) {
|
||||
@@ -566,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;
|
||||
@@ -638,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);
|
||||
@@ -730,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);
|
||||
@@ -964,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
|
||||
@@ -1015,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;
|
||||
@@ -1129,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;
|
||||
@@ -1311,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
|
||||
@@ -1564,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,
|
||||
@@ -1632,6 +1686,15 @@ state_changed_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
|
||||
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
|
||||
@@ -1743,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);
|
||||
@@ -1876,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)
|
||||
@@ -1975,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)
|
||||
{
|
||||
@@ -2029,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
|
||||
@@ -2075,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
|
||||
@@ -2273,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)
|
||||
@@ -2593,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)
|
||||
{
|
||||
@@ -2709,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);
|
||||
@@ -2818,9 +2932,21 @@ mute_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
|
||||
static void
|
||||
element_setup_cb (GstElement * playbin, GstElement * element, GstClapper * self)
|
||||
{
|
||||
GParamSpec *prop = g_object_class_find_property (G_OBJECT_GET_CLASS (element),
|
||||
"user-agent");
|
||||
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";
|
||||
@@ -2871,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);
|
||||
}
|
||||
}
|
||||
@@ -2892,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),
|
||||
@@ -2914,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);
|
||||
@@ -2946,7 +3089,6 @@ gst_clapper_main (gpointer data)
|
||||
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;
|
||||
@@ -2990,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
|
||||
@@ -3003,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;
|
||||
}
|
||||
@@ -3040,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);
|
||||
@@ -3057,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;
|
||||
}
|
||||
|
||||
@@ -3086,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,
|
||||
@@ -3128,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;
|
||||
}
|
||||
|
||||
@@ -3157,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
|
||||
@@ -3186,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);
|
||||
@@ -3297,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;
|
||||
|
||||
@@ -3437,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)
|
||||
{
|
||||
@@ -3448,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
|
||||
@@ -3651,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
|
||||
@@ -3695,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) {
|
||||
@@ -3727,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) {
|
||||
@@ -3759,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) {
|
||||
@@ -3816,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,
|
||||
@@ -3857,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);
|
||||
@@ -3899,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,
|
||||
@@ -4038,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);
|
||||
|
||||
|
@@ -37,11 +37,6 @@
|
||||
GST_DEBUG_CATEGORY (gst_debug_clapper_gl_sink);
|
||||
#define GST_CAT_DEFAULT gst_debug_clapper_gl_sink
|
||||
|
||||
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
|
||||
#define DEFAULT_PAR_N 0
|
||||
#define DEFAULT_PAR_D 1
|
||||
#define DEFAULT_IGNORE_TEXTURES FALSE
|
||||
|
||||
static GstStaticPadTemplate gst_clapper_gl_sink_template =
|
||||
GST_STATIC_PAD_TEMPLATE ("sink",
|
||||
GST_PAD_SINK,
|
||||
@@ -63,6 +58,7 @@ static gboolean gst_clapper_gl_sink_propose_allocation (GstBaseSink * bsink,
|
||||
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,
|
||||
@@ -80,15 +76,6 @@ static GstFlowReturn gst_clapper_gl_sink_show_frame (GstVideoSink * bsink,
|
||||
static void
|
||||
gst_clapper_gl_sink_navigation_interface_init (GstNavigationInterface * iface);
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_WIDGET,
|
||||
PROP_FORCE_ASPECT_RATIO,
|
||||
PROP_PIXEL_ASPECT_RATIO,
|
||||
PROP_IGNORE_TEXTURES,
|
||||
};
|
||||
|
||||
#define gst_clapper_gl_sink_parent_class parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (GstClapperGLSink, gst_clapper_gl_sink,
|
||||
GST_TYPE_VIDEO_SINK,
|
||||
@@ -114,6 +101,7 @@ gst_clapper_gl_sink_class_init (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",
|
||||
@@ -121,24 +109,7 @@ gst_clapper_gl_sink_class_init (GstClapperGLSinkClass * klass)
|
||||
"(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));
|
||||
|
||||
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_clapper_gl_sink_finalize;
|
||||
gst_gtk_install_shared_properties (gobject_class);
|
||||
|
||||
gstelement_class->change_state = gst_clapper_gl_sink_change_state;
|
||||
|
||||
@@ -149,6 +120,7 @@ gst_clapper_gl_sink_class_init (GstClapperGLSinkClass * klass)
|
||||
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;
|
||||
|
||||
@@ -173,7 +145,9 @@ 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->ignore_textures = DEFAULT_IGNORE_TEXTURES;
|
||||
clapper_sink->keep_last_frame = DEFAULT_KEEP_LAST_FRAME;
|
||||
|
||||
clapper_sink->had_eos = FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -236,15 +210,12 @@ gst_clapper_gl_sink_get_widget (GstClapperGLSink * clapper_sink)
|
||||
clapper_sink->widget = (GtkClapperGLWidget *)
|
||||
GST_CLAPPER_GL_SINK_GET_CLASS (clapper_sink)->create_widget ();
|
||||
|
||||
clapper_sink->bind_aspect_ratio =
|
||||
g_object_bind_property (clapper_sink, "force-aspect-ratio", clapper_sink->widget,
|
||||
g_object_bind_property (clapper_sink, "force-aspect-ratio", clapper_sink->widget,
|
||||
"force-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
clapper_sink->bind_pixel_aspect_ratio =
|
||||
g_object_bind_property (clapper_sink, "pixel-aspect-ratio", clapper_sink->widget,
|
||||
g_object_bind_property (clapper_sink, "pixel-aspect-ratio", clapper_sink->widget,
|
||||
"pixel-aspect-ratio", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
|
||||
clapper_sink->bind_ignore_textures =
|
||||
g_object_bind_property (clapper_sink, "ignore-textures", clapper_sink->widget,
|
||||
"ignore-textures", 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. */
|
||||
@@ -290,8 +261,8 @@ gst_clapper_gl_sink_get_property (GObject * object, guint prop_id,
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
gst_value_set_fraction (value, clapper_sink->par_n, clapper_sink->par_d);
|
||||
break;
|
||||
case PROP_IGNORE_TEXTURES:
|
||||
g_value_set_boolean (value, clapper_sink->ignore_textures);
|
||||
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);
|
||||
@@ -313,8 +284,8 @@ gst_clapper_gl_sink_set_property (GObject * object, guint prop_id,
|
||||
clapper_sink->par_n = gst_value_get_fraction_numerator (value);
|
||||
clapper_sink->par_d = gst_value_get_fraction_denominator (value);
|
||||
break;
|
||||
case PROP_IGNORE_TEXTURES:
|
||||
clapper_sink->ignore_textures = g_value_get_boolean (value);
|
||||
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);
|
||||
@@ -604,8 +575,17 @@ gst_clapper_gl_sink_change_state (GstElement * element, GstStateChange transitio
|
||||
return ret;
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
||||
{
|
||||
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);
|
||||
@@ -619,6 +599,16 @@ gst_clapper_gl_sink_change_state (GstElement * element, GstStateChange transitio
|
||||
}
|
||||
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)
|
||||
@@ -706,6 +696,29 @@ gst_clapper_gl_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
|
||||
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)
|
||||
{
|
||||
|
@@ -58,15 +58,14 @@ struct _GstClapperGLSink
|
||||
|
||||
GtkClapperGLWidget *widget;
|
||||
|
||||
gboolean had_eos;
|
||||
|
||||
/* properties */
|
||||
gboolean force_aspect_ratio;
|
||||
GBinding *bind_aspect_ratio;
|
||||
|
||||
gint par_n, par_d;
|
||||
GBinding *bind_pixel_aspect_ratio;
|
||||
gboolean keep_last_frame;
|
||||
|
||||
gboolean ignore_textures;
|
||||
GBinding *bind_ignore_textures;
|
||||
|
||||
GtkWidget *window;
|
||||
gulong widget_destroy_id;
|
||||
|
@@ -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__ */
|
||||
|
@@ -32,8 +32,13 @@
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11)
|
||||
#include <gdk/x11/gdkx.h>
|
||||
#if GST_GL_HAVE_PLATFORM_EGL
|
||||
#include <gst/gl/egl/gstgldisplay_egl.h>
|
||||
#endif
|
||||
#if GST_GL_HAVE_PLATFORM_GLX
|
||||
#include <gst/gl/x11/gstgldisplay_x11.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND)
|
||||
#include <gdk/wayland/gdkwayland.h>
|
||||
@@ -52,11 +57,6 @@
|
||||
GST_DEBUG_CATEGORY (gst_debug_clapper_gl_widget);
|
||||
#define GST_CAT_DEFAULT gst_debug_clapper_gl_widget
|
||||
|
||||
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
|
||||
#define DEFAULT_PAR_N 0
|
||||
#define DEFAULT_PAR_D 1
|
||||
#define DEFAULT_IGNORE_TEXTURES FALSE
|
||||
|
||||
struct _GtkClapperGLWidgetPrivate
|
||||
{
|
||||
gboolean initiated;
|
||||
@@ -90,14 +90,6 @@ G_DEFINE_TYPE_WITH_CODE (GtkClapperGLWidget, gtk_clapper_gl_widget, GTK_TYPE_GL_
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "gtkclapperglwidget", 0,
|
||||
"GTK Clapper GL Widget"));
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_FORCE_ASPECT_RATIO,
|
||||
PROP_PIXEL_ASPECT_RATIO,
|
||||
PROP_IGNORE_TEXTURES,
|
||||
};
|
||||
|
||||
static void
|
||||
gtk_clapper_gl_widget_get_preferred_width (GtkWidget * widget, gint * min,
|
||||
gint * natural)
|
||||
@@ -171,8 +163,8 @@ gtk_clapper_gl_widget_set_property (GObject * object, guint prop_id,
|
||||
clapper_widget->par_n = gst_value_get_fraction_numerator (value);
|
||||
clapper_widget->par_d = gst_value_get_fraction_denominator (value);
|
||||
break;
|
||||
case PROP_IGNORE_TEXTURES:
|
||||
clapper_widget->ignore_textures = g_value_get_boolean (value);
|
||||
case PROP_KEEP_LAST_FRAME:
|
||||
clapper_widget->keep_last_frame = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
@@ -193,8 +185,8 @@ gtk_clapper_gl_widget_get_property (GObject * object, guint prop_id,
|
||||
case PROP_PIXEL_ASPECT_RATIO:
|
||||
gst_value_set_fraction (value, clapper_widget->par_n, clapper_widget->par_d);
|
||||
break;
|
||||
case PROP_IGNORE_TEXTURES:
|
||||
g_value_set_boolean (value, clapper_widget->ignore_textures);
|
||||
case PROP_KEEP_LAST_FRAME:
|
||||
g_value_set_boolean (value, clapper_widget->keep_last_frame);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
@@ -448,10 +440,13 @@ gtk_clapper_gl_widget_motion_event (GtkEventControllerMotion * motion_controller
|
||||
GtkClapperGLWidget *clapper_widget = GTK_CLAPPER_GL_WIDGET (widget);
|
||||
GstElement *element;
|
||||
|
||||
if ((element = g_weak_ref_get (&clapper_widget->element))) {
|
||||
if (x != clapper_widget->last_pos_x && y != clapper_widget->last_pos_y &&
|
||||
(element = g_weak_ref_get (&clapper_widget->element))) {
|
||||
if (GST_IS_NAVIGATION (element)) {
|
||||
gdouble stream_x, stream_y;
|
||||
|
||||
clapper_widget->last_pos_x = x;
|
||||
clapper_widget->last_pos_y = y;
|
||||
_display_size_to_stream_size (clapper_widget, x, y, &stream_x, &stream_y);
|
||||
|
||||
gst_navigation_send_mouse_event (GST_NAVIGATION (element), "mouse-move",
|
||||
@@ -463,6 +458,13 @@ gtk_clapper_gl_widget_motion_event (GtkEventControllerMotion * motion_controller
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_clapper_gl_widget_settings_changed (GtkGLArea * glarea)
|
||||
{
|
||||
GST_DEBUG ("GTK settings changed, queued render");
|
||||
gtk_gl_area_queue_render (glarea);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_clapper_gl_widget_bind_buffer (GtkClapperGLWidget * clapper_widget)
|
||||
{
|
||||
@@ -566,7 +568,7 @@ gtk_clapper_gl_widget_render (GtkGLArea * widget, GdkGLContext * context)
|
||||
|
||||
/* 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 || clapper_widget->ignore_textures) {
|
||||
if (!priv->context || !priv->other_context || clapper_widget->ignore_buffers) {
|
||||
_draw_black_with_gdk (context);
|
||||
goto done;
|
||||
}
|
||||
@@ -845,27 +847,23 @@ _get_gl_context (GtkClapperGLWidget * clapper_widget)
|
||||
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);
|
||||
}
|
||||
if (GST_IS_GL_DISPLAY_EGL (priv->display)) {
|
||||
platform = GST_GL_PLATFORM_EGL;
|
||||
gl_handle = gst_gl_context_get_current_gl_context (platform);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (gl_handle) {
|
||||
gl_api = _get_current_gl_api (platform);
|
||||
priv->other_context =
|
||||
gst_gl_context_new_wrapped (priv->display, gl_handle,
|
||||
platform, gl_api);
|
||||
}
|
||||
#if GST_GL_HAVE_PLATFORM_GLX
|
||||
if (!gl_handle && GST_IS_GL_DISPLAY_X11 (priv->display)) {
|
||||
platform = GST_GL_PLATFORM_GLX;
|
||||
gl_handle = gst_gl_context_get_current_gl_context (platform);
|
||||
}
|
||||
#endif
|
||||
if (gl_handle) {
|
||||
gl_api = _get_current_gl_api (platform);
|
||||
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)
|
||||
@@ -906,35 +904,20 @@ _get_gl_context (GtkClapperGLWidget * clapper_widget)
|
||||
static void
|
||||
gtk_clapper_gl_widget_class_init (GtkClapperGLWidgetClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_klass = (GObjectClass *) klass;
|
||||
GtkWidgetClass *widget_klass = (GtkWidgetClass *) klass;
|
||||
GtkGLAreaClass *gl_area_klass = (GtkGLAreaClass *) klass;
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
|
||||
GtkGLAreaClass *gl_area_class = (GtkGLAreaClass *) klass;
|
||||
|
||||
gobject_klass->set_property = gtk_clapper_gl_widget_set_property;
|
||||
gobject_klass->get_property = gtk_clapper_gl_widget_get_property;
|
||||
gobject_klass->finalize = gtk_clapper_gl_widget_finalize;
|
||||
gobject_class->set_property = gtk_clapper_gl_widget_set_property;
|
||||
gobject_class->get_property = gtk_clapper_gl_widget_get_property;
|
||||
gobject_class->finalize = gtk_clapper_gl_widget_finalize;
|
||||
|
||||
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));
|
||||
gst_gtk_install_shared_properties (gobject_class);
|
||||
|
||||
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));
|
||||
widget_class->measure = gtk_clapper_gl_widget_measure;
|
||||
widget_class->size_allocate = gtk_clapper_gl_widget_size_allocate;
|
||||
|
||||
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));
|
||||
|
||||
widget_klass->measure = gtk_clapper_gl_widget_measure;
|
||||
widget_klass->size_allocate = gtk_clapper_gl_widget_size_allocate;
|
||||
|
||||
gl_area_klass->render = gtk_clapper_gl_widget_render;
|
||||
gl_area_class->render = gtk_clapper_gl_widget_render;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -947,7 +930,10 @@ gtk_clapper_gl_widget_init (GtkClapperGLWidget * clapper_widget)
|
||||
clapper_widget->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
|
||||
clapper_widget->par_n = DEFAULT_PAR_N;
|
||||
clapper_widget->par_d = DEFAULT_PAR_D;
|
||||
clapper_widget->ignore_textures = DEFAULT_IGNORE_TEXTURES;
|
||||
clapper_widget->keep_last_frame = DEFAULT_KEEP_LAST_FRAME;
|
||||
clapper_widget->ignore_buffers = FALSE;
|
||||
clapper_widget->last_pos_x = 0;
|
||||
clapper_widget->last_pos_y = 0;
|
||||
|
||||
gst_video_info_init (&clapper_widget->v_info);
|
||||
gst_video_info_init (&clapper_widget->pending_v_info);
|
||||
@@ -992,9 +978,20 @@ gtk_clapper_gl_widget_init (GtkClapperGLWidget * clapper_widget)
|
||||
|
||||
#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));
|
||||
gpointer display_ptr;
|
||||
#if GST_GL_HAVE_PLATFORM_EGL && GTK_CHECK_VERSION(4,3,1)
|
||||
display_ptr = gdk_x11_display_get_egl_display (display);
|
||||
if (display_ptr)
|
||||
priv->display = (GstGLDisplay *)
|
||||
gst_gl_display_egl_new_with_egl_display (display_ptr);
|
||||
#endif
|
||||
#if GST_GL_HAVE_PLATFORM_GLX
|
||||
if (!priv->display) {
|
||||
display_ptr = gdk_x11_display_get_xdisplay (display);
|
||||
priv->display = (GstGLDisplay *)
|
||||
gst_gl_display_x11_new_with_display (display_ptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#if GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND)
|
||||
@@ -1014,6 +1011,9 @@ gtk_clapper_gl_widget_init (GtkClapperGLWidget * clapper_widget)
|
||||
GST_INFO ("Created %" GST_PTR_FORMAT, priv->display);
|
||||
|
||||
gtk_gl_area_set_auto_render (GTK_GL_AREA (widget), FALSE);
|
||||
|
||||
g_signal_connect_swapped (gtk_widget_get_settings (widget), "notify",
|
||||
G_CALLBACK (gtk_clapper_gl_widget_settings_changed), GTK_GL_AREA (widget));
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
|
@@ -52,7 +52,7 @@ struct _GtkClapperGLWidget
|
||||
/* properties */
|
||||
gboolean force_aspect_ratio;
|
||||
gint par_n, par_d;
|
||||
gboolean ignore_textures;
|
||||
gboolean keep_last_frame;
|
||||
|
||||
gint display_width;
|
||||
gint display_height;
|
||||
@@ -61,7 +61,12 @@ struct _GtkClapperGLWidget
|
||||
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;
|
||||
|
28
lib/gst/clapper/meson.build
vendored
28
lib/gst/clapper/meson.build
vendored
@@ -1,3 +1,5 @@
|
||||
gnome = import('gnome')
|
||||
|
||||
gstclapper_sources = [
|
||||
'gstclapper.c',
|
||||
'gstclapper-signal-dispatcher.c',
|
||||
@@ -6,6 +8,7 @@ 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/gstclapperglsink.c',
|
||||
@@ -23,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 = [
|
||||
@@ -40,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
|
||||
@@ -51,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.2.1',
|
||||
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.2.1
|
||||
Version: 0.3.0
|
||||
Maintainer: Rafostar <rafostar.github@gmail.com>
|
||||
Build-Depends: debhelper (>= 10),
|
||||
meson (>= 0.50),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
clapper (0.2.1) unstable; urgency=low
|
||||
clapper (0.3.0) unstable; urgency=low
|
||||
|
||||
* New version
|
||||
|
||||
-- Rafostar <rafostar.github@gmail.com> Mon, 19 Apr 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
|
||||
|
@@ -13,38 +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": [
|
||||
"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,86 +0,0 @@
|
||||
From be0f4bc94fad9fe182c97eef389954b5f63f7092 Mon Sep 17 00:00:00 2001
|
||||
From: Jun Xie <jun.xie@samsung.com>
|
||||
Date: Sat, 4 Nov 2017 14:48:54 +0800
|
||||
Subject: [PATCH] dashdemux: fix segmentBase type with 'sidx' not using range
|
||||
download issue
|
||||
|
||||
1. for utilizing range download and enable bitrate switch
|
||||
* update fragment info after 'sidx' is downloaded and parsed,
|
||||
so that media segment's range is set by 'sidx' entry info.
|
||||
* while updating fragment info, setting range_end by 'sidx' entry size.
|
||||
|
||||
2. for singleSegmentBase type WITHOUT @indexRange explicitly presented in MPD file
|
||||
* set '*sidx_seek_needed' to true, early terminate currently no-range downloading whole file,
|
||||
then jump to the requested SIDX entry by using sidx info.
|
||||
|
||||
3. for 'ref type 1' 'sidx'
|
||||
* keep current behaviour for 'ref type 1', download as a whole file without range download
|
||||
|
||||
https://bugzilla.gnome.org/show_bug.cgi?id=788763
|
||||
|
||||
diff --git a/ext/dash/gstdashdemux.c b/ext/dash/gstdashdemux.c
|
||||
index e38240800..7554a44b2 100644
|
||||
--- a/ext/dash/gstdashdemux.c
|
||||
+++ b/ext/dash/gstdashdemux.c
|
||||
@@ -1356,7 +1356,7 @@ gst_dash_demux_stream_update_fragment_info (GstAdaptiveDemuxStream * stream)
|
||||
stream->fragment.range_start + entry->size - 1;
|
||||
dashstream->actual_position += entry->duration;
|
||||
} else {
|
||||
- stream->fragment.range_end = fragment.range_end;
|
||||
+ stream->fragment.range_end = stream->fragment.range_start + entry->size - 1;
|
||||
}
|
||||
} else {
|
||||
dashstream->actual_position = stream->fragment.timestamp =
|
||||
@@ -1572,7 +1572,7 @@ gst_dash_demux_stream_has_next_subfragment (GstAdaptiveDemuxStream * stream)
|
||||
|
||||
if (dashstream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED) {
|
||||
if (stream->demux->segment.rate > 0.0) {
|
||||
- if (sidx->entry_index + 1 < sidx->entries_count)
|
||||
+ if (sidx->entry_index < sidx->entries_count)
|
||||
return TRUE;
|
||||
} else {
|
||||
if (sidx->entry_index >= 1)
|
||||
@@ -2903,6 +2903,7 @@ gst_dash_demux_parse_isobmff (GstAdaptiveDemux * demux,
|
||||
GstByteReader sub_reader;
|
||||
GstIsoffParserResult res;
|
||||
guint dummy;
|
||||
+ gboolean ref_type1_found = FALSE;
|
||||
|
||||
dash_stream->sidx_base_offset =
|
||||
dash_stream->isobmff_parser.current_start_offset + size;
|
||||
@@ -2932,6 +2933,7 @@ gst_dash_demux_parse_isobmff (GstAdaptiveDemux * demux,
|
||||
GST_FIXME_OBJECT (stream->pad, "SIDX ref_type 1 not supported yet");
|
||||
dash_stream->sidx_position = GST_CLOCK_TIME_NONE;
|
||||
gst_isoff_sidx_parser_clear (&dash_stream->sidx_parser);
|
||||
+ ref_type1_found = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2968,8 +2970,9 @@ gst_dash_demux_parse_isobmff (GstAdaptiveDemux * demux,
|
||||
}
|
||||
}
|
||||
|
||||
- if (dash_stream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED &&
|
||||
- SIDX (dash_stream)->entry_index != 0) {
|
||||
+ if ((dash_stream->sidx_parser.status == GST_ISOFF_SIDX_PARSER_FINISHED &&
|
||||
+ SIDX (dash_stream)->entry_index != 0) || (!stream->downloading_index &&
|
||||
+ !ref_type1_found)) {
|
||||
/* Need to jump to the requested SIDX entry. Push everything up to
|
||||
* the SIDX box below and let the caller handle everything else */
|
||||
*sidx_seek_needed = TRUE;
|
||||
diff --git a/gst-libs/gst/adaptivedemux/gstadaptivedemux.c b/gst-libs/gst/adaptivedemux/gstadaptivedemux.c
|
||||
index a495ec2e7..3a09a76b1 100644
|
||||
--- a/gst-libs/gst/adaptivedemux/gstadaptivedemux.c
|
||||
+++ b/gst-libs/gst/adaptivedemux/gstadaptivedemux.c
|
||||
@@ -3378,6 +3378,9 @@ gst_adaptive_demux_stream_download_header_fragment (GstAdaptiveDemuxStream *
|
||||
ret = gst_adaptive_demux_stream_download_uri (demux, stream,
|
||||
stream->fragment.index_uri, stream->fragment.index_range_start,
|
||||
stream->fragment.index_range_end, NULL);
|
||||
+
|
||||
+ gst_adaptive_demux_stream_update_fragment_info(stream->demux, stream);
|
||||
+
|
||||
stream->downloading_index = FALSE;
|
||||
}
|
||||
}
|
||||
--
|
||||
2.7.4
|
@@ -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,45 +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"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gst-plugins-bad-dashdemux-sdix-range-download.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,34 +0,0 @@
|
||||
From d42546dda8fdb3d044e715d0a6a1a74cd411acbe Mon Sep 17 00:00:00 2001
|
||||
From: Rafostar <40623528+Rafostar@users.noreply.github.com>
|
||||
Date: Mon, 5 Apr 2021 18:05:38 +0200
|
||||
Subject: [PATCH] GL: Do not set backbuffer on Wayland memory copy
|
||||
|
||||
This aims to workaround a Mesa bug that causes crash on Intel GPUs
|
||||
caused by calling "glDrawBuffer (GL_BACK)" on Wayland where
|
||||
there is no actual backbuffer in GStreamer OpenGL context.
|
||||
---
|
||||
gst-libs/gst/gl/gstglmemory.c | 8 +++++++-
|
||||
1 file changed, 7 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/gst-libs/gst/gl/gstglmemory.c b/gst-libs/gst/gl/gstglmemory.c
|
||||
index 76c04eb1b..cd3481847 100644
|
||||
--- a/gst-libs/gst/gl/gstglmemory.c
|
||||
+++ b/gst-libs/gst/gl/gstglmemory.c
|
||||
@@ -762,7 +762,13 @@ gst_gl_memory_copy_teximage (GstGLMemory * src, guint tex_id,
|
||||
gl->DeleteFramebuffers (n_fbos, &fbo[0]);
|
||||
|
||||
if (gl->DrawBuffer)
|
||||
- gl->DrawBuffer (GL_BACK);
|
||||
+ gl->DrawBuffer (
|
||||
+#if GST_GL_HAVE_WINDOW_WAYLAND
|
||||
+ GL_NONE
|
||||
+#else
|
||||
+ GL_BACK
|
||||
+#endif
|
||||
+ );
|
||||
}
|
||||
|
||||
gst_memory_unmap (GST_MEMORY_CAST (src), &sinfo);
|
||||
--
|
||||
2.28.0
|
||||
|
@@ -1,34 +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"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gst-plugins-base-do-not-set-backbuffer.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,30 +0,0 @@
|
||||
{
|
||||
"name": "ffmpeg",
|
||||
"cleanup": [
|
||||
"/lib/ffmpeg/examples"
|
||||
],
|
||||
"config-opts": [
|
||||
"--disable-debug",
|
||||
"--disable-doc",
|
||||
"--disable-static",
|
||||
"--disable-everything",
|
||||
"--enable-gpl",
|
||||
"--enable-version3",
|
||||
"--enable-shared",
|
||||
"--enable-optimizations",
|
||||
"--enable-runtime-cpudetect",
|
||||
"--enable-pthreads",
|
||||
"--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.4",
|
||||
"commit": "dc91b913b6260e85e1304c74ff7bb3c22a8c9fb1"
|
||||
}
|
||||
]
|
||||
}
|
@@ -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,28 +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",
|
||||
"commit": "5710df685b0af9b7dd306dfba6c7e174e428950e"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
"path": "gtk4-popover-unrealize.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.2.1
|
||||
Version: 0.3.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Simple and modern GNOME media player
|
||||
|
||||
@@ -126,6 +126,9 @@ 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
|
||||
|
||||
|
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,8 +38,7 @@ class ClapperApp extends AppBase
|
||||
{
|
||||
super.vfunc_open(files, hint);
|
||||
|
||||
this._openFiles(files);
|
||||
this.activate();
|
||||
this._openFilesAsync(files).then(() => this.activate()).catch(debug);
|
||||
}
|
||||
|
||||
_onWindowMap(window)
|
||||
|
@@ -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,18 +35,7 @@ class ClapperAppBase extends Gtk.Application
|
||||
if(!settings.get_boolean('render-shadows'))
|
||||
window.add_css_class('gpufriendly');
|
||||
|
||||
for(let action in Menu.actions) {
|
||||
const simpleAction = new Gio.SimpleAction({
|
||||
name: action
|
||||
});
|
||||
simpleAction.connect(
|
||||
'activate', () => Menu.actions[action].run(this.active_window)
|
||||
);
|
||||
this.add_action(simpleAction);
|
||||
|
||||
if(Menu.actions[action].accels)
|
||||
this.set_accels_for_action(`app.${action}`, Menu.actions[action].accels);
|
||||
}
|
||||
window.add_css_class('gpufriendlyfs');
|
||||
}
|
||||
|
||||
vfunc_activate()
|
||||
@@ -59,19 +50,50 @@ class ClapperAppBase extends Gtk.Application
|
||||
);
|
||||
}
|
||||
|
||||
_openFiles(files)
|
||||
async _openFilesAsync(files)
|
||||
{
|
||||
const [playlist, subs] = Misc.parsePlaylistFiles(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.set_playlist(playlist);
|
||||
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,
|
||||
|
@@ -31,8 +31,6 @@ class ClapperCustomButton extends Gtk.Button
|
||||
if(this.isFullscreen === isFullscreen)
|
||||
return;
|
||||
|
||||
this.can_focus = isFullscreen;
|
||||
|
||||
/* Redraw icon after style class change */
|
||||
if(this.icon_name)
|
||||
this.set_icon_name(this.icon_name);
|
||||
@@ -110,8 +108,6 @@ class ClapperPopoverButtonBase extends Gtk.ToggleButton
|
||||
if(this.isFullscreen === isFullscreen)
|
||||
return;
|
||||
|
||||
this.can_focus = isFullscreen;
|
||||
|
||||
/* Redraw icon after style class change */
|
||||
if(this.icon_name)
|
||||
this.set_icon_name(this.icon_name);
|
||||
@@ -152,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();
|
||||
|
56
src/controls.js
vendored
56
src/controls.js
vendored
@@ -7,6 +7,8 @@ const Revealers = imports.src.revealers;
|
||||
const { debug } = Debug;
|
||||
const { settings } = Misc;
|
||||
|
||||
const INITIAL_ELAPSED = '00:00/00:00';
|
||||
|
||||
var Controls = GObject.registerClass(
|
||||
class ClapperControls extends Gtk.Box
|
||||
{
|
||||
@@ -21,7 +23,6 @@ class ClapperControls extends Gtk.Box
|
||||
this.minFullViewWidth = 560;
|
||||
|
||||
this.currentPosition = 0;
|
||||
this.currentDuration = 0;
|
||||
this.isPositionDragging = false;
|
||||
this.isMobile = false;
|
||||
this.isFullscreen = false;
|
||||
@@ -85,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));
|
||||
}
|
||||
@@ -103,8 +99,6 @@ class ClapperControls extends Gtk.Box
|
||||
button.setFullscreenMode(isFullscreen);
|
||||
|
||||
this.unfullscreenButton.visible = isFullscreen;
|
||||
this.can_focus = isFullscreen;
|
||||
|
||||
this.isFullscreen = isFullscreen;
|
||||
}
|
||||
|
||||
@@ -116,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;
|
||||
@@ -238,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);
|
||||
@@ -250,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)
|
||||
@@ -298,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);
|
||||
|
||||
@@ -621,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');
|
||||
|
30
src/debug.js
30
src/debug.js
@@ -31,14 +31,23 @@ const ytDebugger = new Debug.Debugger('YouTube', {
|
||||
high_precision: true,
|
||||
});
|
||||
|
||||
function _debug(msg, debuggerName)
|
||||
function _logStructured(debuggerName, msg, level)
|
||||
{
|
||||
GLib.log_structured(
|
||||
debuggerName, level, {
|
||||
MESSAGE: msg,
|
||||
SYSLOG_IDENTIFIER: debuggerName.toLowerCase()
|
||||
});
|
||||
}
|
||||
|
||||
function _debug(debuggerName, msg)
|
||||
{
|
||||
if(msg.message) {
|
||||
GLib.log_structured(
|
||||
debuggerName, GLib.LogLevelFlags.LEVEL_CRITICAL, {
|
||||
MESSAGE: msg.message,
|
||||
SYSLOG_IDENTIFIER: debuggerName.toLowerCase()
|
||||
});
|
||||
_logStructured(
|
||||
debuggerName,
|
||||
msg.message,
|
||||
GLib.LogLevelFlags.LEVEL_CRITICAL
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -55,10 +64,15 @@ function _debug(msg, debuggerName)
|
||||
|
||||
function debug(msg)
|
||||
{
|
||||
_debug(msg, 'Clapper');
|
||||
_debug('Clapper', msg);
|
||||
}
|
||||
|
||||
function ytDebug(msg)
|
||||
{
|
||||
_debug(msg, 'YouTube');
|
||||
_debug('YouTube', msg);
|
||||
}
|
||||
|
||||
function warn(msg)
|
||||
{
|
||||
_logStructured('Clapper', msg, GLib.LogLevelFlags.LEVEL_WARNING);
|
||||
}
|
||||
|
116
src/dialogs.js
116
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,14 +11,37 @@ 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',
|
||||
});
|
||||
@@ -26,12 +50,18 @@ class ClapperFileChooser extends Gtk.FileChooserNative
|
||||
filter.add_mime_type('application/claps');
|
||||
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)
|
||||
@@ -42,30 +72,58 @@ class ClapperFileChooser extends Gtk.FileChooserNative
|
||||
this.responseSignal = null;
|
||||
|
||||
if(response === Gtk.ResponseType.ACCEPT) {
|
||||
const files = this.get_files();
|
||||
const filesArray = [];
|
||||
|
||||
let index = 0;
|
||||
let file;
|
||||
|
||||
while((file = files.get_item(index))) {
|
||||
filesArray.push(file);
|
||||
index++;
|
||||
switch(this.chooserPurpose) {
|
||||
case 'open_local':
|
||||
this._handleOpenLocal();
|
||||
break;
|
||||
case 'export_playlist':
|
||||
this._handleExportPlaylist();
|
||||
break;
|
||||
}
|
||||
|
||||
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._openFiles(filesArray);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -112,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();
|
||||
}
|
||||
|
||||
@@ -257,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();
|
||||
}
|
||||
|
||||
@@ -319,8 +373,6 @@ class ClapperAboutDialog extends Gtk.AboutDialog
|
||||
});
|
||||
|
||||
this.closeSignal = this.connect('close-request', this._onCloseRequest.bind(this));
|
||||
|
||||
this.ref();
|
||||
this.show();
|
||||
}
|
||||
|
||||
|
123
src/fileOps.js
123
src/fileOps.js
@@ -12,6 +12,11 @@ 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()
|
||||
{
|
||||
@@ -52,6 +57,18 @@ function createDirPromise(dir)
|
||||
});
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
@@ -77,18 +94,12 @@ function saveFilePromise(place, subdirName, fileName, data)
|
||||
}
|
||||
|
||||
const destFile = destDir.get_child(fileName);
|
||||
destFile.replace_contents_bytes_async(
|
||||
GLib.Bytes.new_take(data),
|
||||
null,
|
||||
false,
|
||||
Gio.FileCreateFlags.NONE,
|
||||
null
|
||||
)
|
||||
.then(() => {
|
||||
debug(`saved file: ${destPath}`);
|
||||
resolve(destFile);
|
||||
})
|
||||
.catch(err => reject(err));
|
||||
saveFileSimplePromise(destFile, data)
|
||||
.then(() => {
|
||||
debug(`saved file: ${destPath}`);
|
||||
resolve(destFile);
|
||||
})
|
||||
.catch(err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -126,3 +137,91 @@ function getFileContentsPromise(place, subdirName, fileName)
|
||||
.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');
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
19
src/menu.js
19
src/menu.js
@@ -1,19 +0,0 @@
|
||||
const { GObject, Gtk } = imports.gi;
|
||||
const Dialogs = imports.src.dialogs;
|
||||
|
||||
var actions = {
|
||||
openLocal: {
|
||||
run: (window) => new Dialogs.FileChooser(window),
|
||||
accels: ['<Ctrl>O'],
|
||||
},
|
||||
openUri: {
|
||||
run: (window) => new Dialogs.UriDialog(window),
|
||||
accels: ['<Ctrl>U'],
|
||||
},
|
||||
prefs: {
|
||||
run: (window) => new Dialogs.PrefsDialog(window),
|
||||
},
|
||||
about: {
|
||||
run: (window) => new Dialogs.AboutDialog(window),
|
||||
},
|
||||
};
|
82
src/misc.js
82
src/misc.js
@@ -1,4 +1,4 @@
|
||||
const { Gio, Gdk, Gtk } = imports.gi;
|
||||
const { Gio, GLib, Gdk, Gtk } = imports.gi;
|
||||
const Debug = imports.src.debug;
|
||||
|
||||
const { debug } = Debug;
|
||||
@@ -7,6 +7,7 @@ var appName = 'Clapper';
|
||||
var appId = 'com.github.rafostar.Clapper';
|
||||
var subsMimes = [
|
||||
'application/x-subrip',
|
||||
'text/x-ssa',
|
||||
];
|
||||
|
||||
var clapperPath = null;
|
||||
@@ -18,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()
|
||||
@@ -38,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();
|
||||
@@ -139,6 +212,13 @@ function getFileFromLocalUri(uri)
|
||||
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, '&')
|
||||
|
254
src/player.js
254
src/player.js
@@ -1,4 +1,4 @@
|
||||
const { Gdk, Gio, GLib, GObject, Gst, GstClapper, Gtk } = imports.gi;
|
||||
const { Gdk, Gio, GObject, Gst, GstClapper, Gtk } = imports.gi;
|
||||
const ByteArray = imports.byteArray;
|
||||
const Debug = imports.src.debug;
|
||||
const Misc = imports.src.misc;
|
||||
@@ -6,7 +6,7 @@ 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;
|
||||
@@ -20,24 +20,28 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
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
|
||||
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.state = GstClapper.ClapperState.STOPPED;
|
||||
this.visualization_enabled = false;
|
||||
|
||||
this.webserver = null;
|
||||
this.webapp = null;
|
||||
this.ytClient = null;
|
||||
this.playlistWidget = new PlaylistWidget();
|
||||
|
||||
this.seek_done = true;
|
||||
@@ -45,19 +49,9 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
this.customVideoTitle = null;
|
||||
|
||||
this.windowMapped = false;
|
||||
this.canAutoFullscreen = false;
|
||||
this.playOnFullscreen = false;
|
||||
this.quitOnStop = false;
|
||||
this.needsTocUpdate = true;
|
||||
|
||||
this.keyPressCount = 0;
|
||||
this.ytClient = null;
|
||||
|
||||
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();
|
||||
@@ -87,7 +81,8 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
this._onSettingsKeyChanged(settings, key);
|
||||
|
||||
const flag = Gio.SettingsBindFlags.GET;
|
||||
settings.bind('subtitle-font', this.pipeline, 'subtitle_font_desc', flag);
|
||||
settings.bind('keep-last-frame', this.widget, 'keep-last-frame', flag);
|
||||
settings.bind('subtitle-font', this.pipeline, 'subtitle-font-desc', flag);
|
||||
}
|
||||
|
||||
set_initial_config()
|
||||
@@ -126,8 +121,10 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
{
|
||||
const gstRegistry = Gst.Registry.get();
|
||||
const feature = gstRegistry.lookup_feature(name);
|
||||
if(!feature)
|
||||
return debug(`plugin unavailable: ${name}`);
|
||||
if(!feature) {
|
||||
warn(`cannot change rank of unavailable plugin: ${name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const oldRank = feature.get_rank();
|
||||
if(rank === oldRank)
|
||||
@@ -137,19 +134,11 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
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();
|
||||
}
|
||||
|
||||
set_uri(uri)
|
||||
{
|
||||
this.customVideoTitle = null;
|
||||
|
||||
if(Gst.Uri.get_protocol(uri) !== 'file') {
|
||||
if(Misc.getUriProtocol(uri) !== 'file') {
|
||||
const [isYouTubeUri, videoId] = YouTube.checkYouTubeUri(uri);
|
||||
|
||||
if(!isYouTubeUri)
|
||||
@@ -221,12 +210,21 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
if(this.state !== GstClapper.ClapperState.STOPPED)
|
||||
this.stop();
|
||||
|
||||
debug('new playlist');
|
||||
this.playlistWidget.removeAll();
|
||||
this.canAutoFullscreen = true;
|
||||
this._addPlaylistItems(playlist);
|
||||
|
||||
for(let source of playlist) {
|
||||
const uri = this._getSourceUri(source);
|
||||
this.playlistWidget.addItem(uri);
|
||||
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 */
|
||||
@@ -234,13 +232,28 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
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 = this._getSourceUri(source);
|
||||
|
||||
/* Check local file existence */
|
||||
if(
|
||||
Gst.Uri.get_protocol(uri) === 'file'
|
||||
Misc.getUriProtocol(uri) === 'file'
|
||||
&& !Misc.getFileFromLocalUri(uri)
|
||||
)
|
||||
return;
|
||||
@@ -346,13 +359,14 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
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)
|
||||
@@ -369,10 +383,26 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
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':
|
||||
@@ -380,20 +410,47 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
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:
|
||||
debug(`unhandled WebSocket action: ${action}`);
|
||||
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)
|
||||
@@ -463,7 +520,6 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
|
||||
_onStateChanged(player, state)
|
||||
{
|
||||
this.state = state;
|
||||
this.emitWs('state_changed', state);
|
||||
|
||||
if(state !== GstClapper.ClapperState.BUFFERING) {
|
||||
@@ -507,7 +563,7 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
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')) {
|
||||
@@ -515,6 +571,10 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
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)
|
||||
@@ -522,26 +582,7 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
debug(`URI loaded: ${uri}`);
|
||||
this.needsTocUpdate = true;
|
||||
|
||||
if(this.canAutoFullscreen) {
|
||||
this.canAutoFullscreen = false;
|
||||
|
||||
if(settings.get_boolean('fullscreen-auto')) {
|
||||
const root = player.widget.get_root();
|
||||
const clapperWidget = root.get_child();
|
||||
/* Do not enter fullscreen when already in it
|
||||
* or when in floating mode */
|
||||
if(
|
||||
!clapperWidget.isFullscreenMode
|
||||
&& clapperWidget.controlsRevealer.reveal_child
|
||||
) {
|
||||
this.playOnFullscreen = true;
|
||||
root.fullscreen();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.play();
|
||||
player.play();
|
||||
}
|
||||
|
||||
_onPlayerWarning(player, error)
|
||||
@@ -577,71 +618,6 @@ class ClapperPlayer extends GstClapper.Clapper
|
||||
);
|
||||
}
|
||||
|
||||
/* Widget only - does not happen when using controls navigation */
|
||||
_onWidgetKeyPressed(controller, keyval, keycode, state)
|
||||
{
|
||||
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_q:
|
||||
case Gdk.KEY_Q:
|
||||
root = this.widget.get_root();
|
||||
root.emit('close-request');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_onWindowMap(window)
|
||||
{
|
||||
this.windowMapped = true;
|
||||
|
206
src/playlist.js
206
src/playlist.js
@@ -1,4 +1,22 @@
|
||||
const { Gdk, GLib, GObject, Gst, Gtk, Pango } = imports.gi;
|
||||
const { Gdk, GLib, GObject, Gtk, Pango } = imports.gi;
|
||||
const Debug = imports.src.debug;
|
||||
const Misc = imports.src.misc;
|
||||
|
||||
const { debug, warn } = Debug;
|
||||
|
||||
var RepeatMode = {
|
||||
NONE: 0,
|
||||
TRACK: 1,
|
||||
PLAYLIST: 2,
|
||||
SHUFFLE: 3,
|
||||
};
|
||||
|
||||
const repeatIcons = [
|
||||
'media-playlist-consecutive-symbolic',
|
||||
'media-playlist-repeat-song-symbolic',
|
||||
'media-playlist-repeat-symbolic',
|
||||
'media-playlist-shuffle-symbolic',
|
||||
];
|
||||
|
||||
var PlaylistWidget = GObject.registerClass(
|
||||
class ClapperPlaylistWidget extends Gtk.ListBox
|
||||
@@ -9,6 +27,8 @@ class ClapperPlaylistWidget extends Gtk.ListBox
|
||||
selection_mode: Gtk.SelectionMode.NONE,
|
||||
});
|
||||
this.activeRowId = -1;
|
||||
this.repeatMode = RepeatMode.NONE;
|
||||
|
||||
this.connect('row-activated', this._onRowActivated.bind(this));
|
||||
}
|
||||
|
||||
@@ -23,9 +43,7 @@ class ClapperPlaylistWidget extends Gtk.ListBox
|
||||
const itemIndex = item.get_index();
|
||||
|
||||
if(itemIndex === this.activeRowId) {
|
||||
const root = this.get_root();
|
||||
root.emit('close-request');
|
||||
|
||||
this.activate_action('window.close', null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,13 +64,12 @@ class ClapperPlaylistWidget extends Gtk.ListBox
|
||||
|
||||
nextTrack()
|
||||
{
|
||||
const nextRow = this.get_row_at_index(this.activeRowId + 1);
|
||||
if(!nextRow)
|
||||
return false;
|
||||
return this._switchTrack(false);
|
||||
}
|
||||
|
||||
nextRow.activate();
|
||||
|
||||
return true;
|
||||
prevTrack()
|
||||
{
|
||||
return this._switchTrack(true);
|
||||
}
|
||||
|
||||
getActiveRow()
|
||||
@@ -60,6 +77,24 @@ class ClapperPlaylistWidget extends Gtk.ListBox
|
||||
return this.get_row_at_index(this.activeRowId);
|
||||
}
|
||||
|
||||
getPlaylist(useFilePaths)
|
||||
{
|
||||
const playlist = [];
|
||||
let index = 0;
|
||||
let item;
|
||||
|
||||
while((item = this.get_row_at_index(index))) {
|
||||
const path = (useFilePaths && item.isLocalFile)
|
||||
? GLib.filename_from_uri(item.uri)[0]
|
||||
: item.uri;
|
||||
|
||||
playlist.push(path);
|
||||
index++;
|
||||
}
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
getActiveFilename()
|
||||
{
|
||||
const row = this.getActiveRow();
|
||||
@@ -68,7 +103,43 @@ class ClapperPlaylistWidget extends Gtk.ListBox
|
||||
return row.filename;
|
||||
}
|
||||
|
||||
deactivateActiveItem()
|
||||
changeActiveRow(rowId)
|
||||
{
|
||||
const row = this.get_row_at_index(rowId);
|
||||
if(!row)
|
||||
return false;
|
||||
|
||||
row.activate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
changeRepeatMode(mode)
|
||||
{
|
||||
const lastMode = Object.keys(RepeatMode).length - 1;
|
||||
const row = this.getActiveRow();
|
||||
if(!row) return null;
|
||||
|
||||
if(mode < 0 || mode > lastMode) {
|
||||
warn(`ignored invalid repeat mode value: ${mode}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if(mode >= 0)
|
||||
this.repeatMode = mode;
|
||||
else {
|
||||
this.repeatMode++;
|
||||
if(this.repeatMode > lastMode)
|
||||
this.repeatMode = 0;
|
||||
}
|
||||
|
||||
const repeatButton = row.child.get_first_child();
|
||||
repeatButton.icon_name = repeatIcons[this.repeatMode];
|
||||
|
||||
debug(`set repeat mode: ${this.repeatMode}`);
|
||||
}
|
||||
|
||||
_deactivateActiveItem(isRemoveChange)
|
||||
{
|
||||
if(this.activeRowId < 0)
|
||||
return;
|
||||
@@ -76,26 +147,90 @@ class ClapperPlaylistWidget extends Gtk.ListBox
|
||||
const row = this.getActiveRow();
|
||||
if(!row) return null;
|
||||
|
||||
const icon = row.child.get_first_child();
|
||||
const button = row.child.get_last_child();
|
||||
const repeatButton = row.child.get_first_child();
|
||||
repeatButton.sensitive = false;
|
||||
repeatButton.icon_name = 'open-menu-symbolic';
|
||||
|
||||
icon.icon_name = 'open-menu-symbolic';
|
||||
button.icon_name = 'list-remove-symbolic';
|
||||
if(isRemoveChange) {
|
||||
const removeButton = row.child.get_last_child();
|
||||
removeButton.icon_name = 'list-remove-symbolic';
|
||||
}
|
||||
}
|
||||
|
||||
_switchTrack(isPrevious)
|
||||
{
|
||||
const rowId = (isPrevious)
|
||||
? this.activeRowId - 1
|
||||
: this.activeRowId + 1;
|
||||
|
||||
return this.changeActiveRow(rowId);
|
||||
}
|
||||
|
||||
_onRowActivated(listBox, row)
|
||||
{
|
||||
const { player } = this.get_ancestor(Gtk.Grid);
|
||||
const icon = row.child.get_first_child();
|
||||
const button = row.child.get_last_child();
|
||||
const repeatButton = row.child.get_first_child();
|
||||
const removeButton = row.child.get_last_child();
|
||||
|
||||
this.deactivateActiveItem();
|
||||
icon.icon_name = 'media-playback-start-symbolic';
|
||||
button.icon_name = 'window-close-symbolic';
|
||||
this._deactivateActiveItem(true);
|
||||
repeatButton.sensitive = true;
|
||||
repeatButton.icon_name = repeatIcons[this.repeatMode];
|
||||
removeButton.icon_name = 'window-close-symbolic';
|
||||
|
||||
this.activeRowId = row.get_index();
|
||||
player.set_uri(row.uri);
|
||||
}
|
||||
|
||||
_handleStreamEnded(player)
|
||||
{
|
||||
/* Seek to beginning when repeating track
|
||||
* or playlist with only one item */
|
||||
if(
|
||||
this.repeatMode === RepeatMode.TRACK
|
||||
|| (this.repeatMode !== RepeatMode.NONE
|
||||
&& this.activeRowId === 0
|
||||
&& !this.get_row_at_index(1))
|
||||
) {
|
||||
debug('seeking to beginning');
|
||||
|
||||
player.seek(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(this.repeatMode === RepeatMode.SHUFFLE) {
|
||||
const playlistIds = [];
|
||||
let index = 0;
|
||||
|
||||
debug('selecting random playlist item');
|
||||
|
||||
while(this.get_row_at_index(index)) {
|
||||
/* We prefer to not repeat the same track */
|
||||
if(index !== this.activeRowId)
|
||||
playlistIds.push(index);
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
/* We always have non-empty array here,
|
||||
* otherwise seek to beginning is performed */
|
||||
const randomId = playlistIds[
|
||||
Math.floor(Math.random() * playlistIds.length)
|
||||
];
|
||||
debug(`selected random playlist item: ${randomId}`);
|
||||
|
||||
return this.changeActiveRow(randomId);
|
||||
}
|
||||
|
||||
if(this.nextTrack())
|
||||
return true;
|
||||
|
||||
if(this.repeatMode === RepeatMode.PLAYLIST)
|
||||
return this.changeActiveRow(0);
|
||||
|
||||
this._deactivateActiveItem(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
let PlaylistItem = GObject.registerClass(
|
||||
@@ -104,7 +239,6 @@ class ClapperPlaylistItem extends Gtk.ListBoxRow
|
||||
_init(uri)
|
||||
{
|
||||
super._init({
|
||||
/* TODO: Fix playlist navigation in fullscreen */
|
||||
can_focus: false,
|
||||
});
|
||||
|
||||
@@ -112,7 +246,7 @@ class ClapperPlaylistItem extends Gtk.ListBoxRow
|
||||
this.isLocalFile = false;
|
||||
|
||||
let filename;
|
||||
if(Gst.Uri.get_protocol(uri) === 'file') {
|
||||
if(Misc.getUriProtocol(uri) === 'file') {
|
||||
filename = GLib.path_get_basename(
|
||||
GLib.filename_from_uri(uri)[0]
|
||||
);
|
||||
@@ -128,9 +262,14 @@ class ClapperPlaylistItem extends Gtk.ListBoxRow
|
||||
margin_end: 6,
|
||||
height_request: 22,
|
||||
});
|
||||
const icon = new Gtk.Image({
|
||||
const repeatButton = new Gtk.Button({
|
||||
icon_name: 'open-menu-symbolic',
|
||||
sensitive: false,
|
||||
});
|
||||
repeatButton.add_css_class('flat');
|
||||
repeatButton.add_css_class('circular');
|
||||
repeatButton.add_css_class('popoverbutton');
|
||||
repeatButton.connect('clicked', this._onRepeatClicked.bind(this));
|
||||
const label = new Gtk.Label({
|
||||
label: this.filename,
|
||||
single_line_mode: true,
|
||||
@@ -139,17 +278,17 @@ class ClapperPlaylistItem extends Gtk.ListBoxRow
|
||||
hexpand: true,
|
||||
halign: Gtk.Align.START,
|
||||
});
|
||||
const button = new Gtk.Button({
|
||||
const removeButton = new Gtk.Button({
|
||||
icon_name: 'list-remove-symbolic',
|
||||
});
|
||||
button.add_css_class('flat');
|
||||
button.add_css_class('circular');
|
||||
button.add_css_class('popoverbutton');
|
||||
button.connect('clicked', this._onRemoveClicked.bind(this));
|
||||
removeButton.add_css_class('flat');
|
||||
removeButton.add_css_class('circular');
|
||||
removeButton.add_css_class('popoverbutton');
|
||||
removeButton.connect('clicked', this._onRemoveClicked.bind(this));
|
||||
|
||||
box.append(icon);
|
||||
box.append(repeatButton);
|
||||
box.append(label);
|
||||
box.append(button);
|
||||
box.append(removeButton);
|
||||
this.set_child(box);
|
||||
|
||||
/* FIXME: D&D inside popover is broken in GTK4
|
||||
@@ -172,6 +311,13 @@ class ClapperPlaylistItem extends Gtk.ListBoxRow
|
||||
*/
|
||||
}
|
||||
|
||||
_onRepeatClicked(button)
|
||||
{
|
||||
const listBox = this.get_ancestor(Gtk.ListBox);
|
||||
|
||||
listBox.changeRepeatMode();
|
||||
}
|
||||
|
||||
_onRemoveClicked(button)
|
||||
{
|
||||
const listBox = this.get_ancestor(Gtk.ListBox);
|
||||
|
@@ -41,6 +41,7 @@ class ClapperGeneralPage extends PrefsBase.Grid
|
||||
comboBox.connect('changed', this._onVolumeInitialChanged.bind(this, spinButton));
|
||||
|
||||
this.addTitle('Finish');
|
||||
this.addCheckButton('Keep showing last frame', 'keep-last-frame');
|
||||
this.addCheckButton('Close after playback', 'close-auto');
|
||||
}
|
||||
|
||||
|
177
src/widget.js
177
src/widget.js
@@ -31,12 +31,14 @@ class ClapperWidget extends Gtk.Grid
|
||||
|
||||
this.isDragAllowed = false;
|
||||
this.isSwipePerformed = false;
|
||||
this.isReleaseKeyEnabled = false;
|
||||
|
||||
this.isCursorInPlayer = false;
|
||||
this.isPopoverOpen = false;
|
||||
|
||||
this._hideControlsTimeout = null;
|
||||
this._updateTimeTimeout = null;
|
||||
this.surfaceMapSignal = null;
|
||||
|
||||
this.needsCursorRestore = false;
|
||||
|
||||
@@ -103,18 +105,20 @@ class ClapperWidget extends Gtk.Grid
|
||||
|
||||
const dropTarget = this._getDropTarget();
|
||||
playerWidget.add_controller(dropTarget);
|
||||
|
||||
/* Applied only for widget to detect simple action key releases */
|
||||
const keyController = new Gtk.EventControllerKey();
|
||||
keyController.connect('key-released', this._onKeyReleased.bind(this));
|
||||
this.add_controller(keyController);
|
||||
}
|
||||
|
||||
revealControls(isAllowInput)
|
||||
revealControls()
|
||||
{
|
||||
this.revealerTop.revealChild(true);
|
||||
this.revealerBottom.revealChild(true);
|
||||
|
||||
this._checkSetUpdateTimeInterval();
|
||||
|
||||
if(isAllowInput)
|
||||
this.setControlsCanFocus(true);
|
||||
|
||||
/* Reset timeout if already revealed, otherwise
|
||||
* timeout will be set after reveal finishes */
|
||||
if(this.revealerTop.child_revealed)
|
||||
@@ -138,13 +142,6 @@ class ClapperWidget extends Gtk.Grid
|
||||
debug('changing fullscreen mode');
|
||||
this.isFullscreenMode = isFullscreen;
|
||||
|
||||
const root = this.get_root();
|
||||
const action = (isFullscreen) ? 'add' : 'remove';
|
||||
root[action + '_css_class']('gpufriendlyfs');
|
||||
|
||||
if(!this.isMobileMonitor)
|
||||
root[action + '_css_class']('tvmode');
|
||||
|
||||
if(!isFullscreen)
|
||||
this._clearTimeout('updateTime');
|
||||
|
||||
@@ -157,28 +154,9 @@ class ClapperWidget extends Gtk.Grid
|
||||
if(this.revealerTop.child_revealed)
|
||||
this._checkSetUpdateTimeInterval();
|
||||
|
||||
this.setControlsCanFocus(false);
|
||||
|
||||
if(this.player.playOnFullscreen && isFullscreen) {
|
||||
this.player.playOnFullscreen = false;
|
||||
this.player.play();
|
||||
}
|
||||
|
||||
debug(`interface in fullscreen mode: ${isFullscreen}`);
|
||||
}
|
||||
|
||||
setControlsCanFocus(isControlsFocus)
|
||||
{
|
||||
this.revealerBottom.can_focus = isControlsFocus;
|
||||
this.player.widget.can_focus = !isControlsFocus;
|
||||
|
||||
const focusWidget = (isControlsFocus)
|
||||
? this.controls.togglePlayButton
|
||||
: this.player.widget;
|
||||
|
||||
focusWidget.grab_focus();
|
||||
}
|
||||
|
||||
_changeControlsPlacement(isOnTop)
|
||||
{
|
||||
if(isOnTop) {
|
||||
@@ -253,7 +231,11 @@ class ClapperWidget extends Gtk.Grid
|
||||
break;
|
||||
case GstClapper.ClapperSubtitleInfo:
|
||||
type = 'subtitle';
|
||||
text = info.get_language() || 'Undetermined';
|
||||
const subsLang = info.get_language();
|
||||
text = (subsLang) ? subsLang.split(',')[0] : 'Undetermined';
|
||||
const subsTitle = Misc.getSubsTitle(info.get_title());
|
||||
if(subsTitle)
|
||||
text += ', ' + subsTitle;
|
||||
break;
|
||||
default:
|
||||
debug(`unrecognized media info type: ${info.constructor}`);
|
||||
@@ -286,7 +268,10 @@ class ClapperWidget extends Gtk.Grid
|
||||
debug(`${type} caps: ${caps.to_string()}`);
|
||||
}
|
||||
if(type === 'video') {
|
||||
const isShowVis = (parsedInfo[`${type}Tracks`].length === 0);
|
||||
const isShowVis = (
|
||||
!parsedInfo.videoTracks.length
|
||||
&& parsedInfo.audioTracks.length
|
||||
);
|
||||
this.showVisualizationsButton(isShowVis);
|
||||
}
|
||||
if(!parsedInfo[`${type}Tracks`].length) {
|
||||
@@ -310,17 +295,14 @@ class ClapperWidget extends Gtk.Grid
|
||||
|
||||
updateTitle(mediaInfo)
|
||||
{
|
||||
let title = mediaInfo.get_title();
|
||||
let title = this.player.customVideoTitle;
|
||||
|
||||
if(!title)
|
||||
title = this.player.customVideoTitle;
|
||||
title = mediaInfo.get_title();
|
||||
|
||||
if(!title) {
|
||||
const item = this.player.playlistWidget.getActiveRow();
|
||||
|
||||
title = (item.isLocalFile && item.filename.includes('.'))
|
||||
? item.filename.split('.').slice(0, -1).join('.')
|
||||
: item.filename;
|
||||
title = item.filename;
|
||||
}
|
||||
|
||||
this.root.title = title;
|
||||
@@ -447,10 +429,8 @@ class ClapperWidget extends Gtk.Grid
|
||||
break;
|
||||
case GstClapper.ClapperState.STOPPED:
|
||||
debug('player state changed to: STOPPED');
|
||||
this.controls.currentPosition = 0;
|
||||
this.controls.positionScale.set_value(0);
|
||||
this.controls.setInitialState();
|
||||
this.revealerTop.showTitle = false;
|
||||
this.controls.togglePlayButton.setPrimaryIcon();
|
||||
break;
|
||||
case GstClapper.ClapperState.PAUSED:
|
||||
debug('player state changed to: PAUSED');
|
||||
@@ -470,14 +450,9 @@ class ClapperWidget extends Gtk.Grid
|
||||
const durationSeconds = duration / Gst.SECOND;
|
||||
const durationFloor = Math.floor(durationSeconds);
|
||||
|
||||
/* Sometimes GstPlayer might re-emit
|
||||
* duration changed during playback */
|
||||
if(this.controls.currentDuration === durationFloor)
|
||||
return;
|
||||
debug(`duration changed: ${durationSeconds}`);
|
||||
|
||||
this.controls.currentDuration = durationFloor;
|
||||
this.controls.showHours = (durationFloor >= 3600);
|
||||
|
||||
this.controls.positionAdjustment.set_upper(durationFloor);
|
||||
this.controls.durationFormatted = Misc.getFormattedTime(durationFloor);
|
||||
this.controls.updateElapsedLabel();
|
||||
@@ -558,30 +533,70 @@ class ClapperWidget extends Gtk.Grid
|
||||
_onWindowMap(window)
|
||||
{
|
||||
const surface = window.get_surface();
|
||||
const monitor = window.display.get_monitor_at_surface(surface);
|
||||
const geometry = monitor.geometry;
|
||||
const size = JSON.parse(settings.get_string('window-size'));
|
||||
|
||||
debug(`monitor application-pixels: ${geometry.width}x${geometry.height}`);
|
||||
|
||||
if(geometry.width >= size[0] && geometry.height >= size[1]) {
|
||||
window.set_default_size(size[0], size[1]);
|
||||
debug(`restored window size: ${size[0]}x${size[1]}`);
|
||||
}
|
||||
|
||||
const monitorWidth = Math.max(geometry.width, geometry.height);
|
||||
|
||||
if(monitorWidth < 1280) {
|
||||
this.isMobileMonitor = true;
|
||||
debug('mobile monitor detected');
|
||||
}
|
||||
if(!surface.mapped)
|
||||
this.surfaceMapSignal = surface.connect(
|
||||
'notify::mapped', this._onSurfaceMapNotify.bind(this)
|
||||
);
|
||||
else
|
||||
this._onSurfaceMapNotify(surface);
|
||||
|
||||
surface.connect('notify::state', this._onStateNotify.bind(this));
|
||||
surface.connect('enter-monitor', this._onEnterMonitor.bind(this));
|
||||
surface.connect('layout', this._onLayoutUpdate.bind(this));
|
||||
|
||||
this.player._onWindowMap(window);
|
||||
}
|
||||
|
||||
_onSurfaceMapNotify(surface)
|
||||
{
|
||||
if(!surface.mapped)
|
||||
return;
|
||||
|
||||
if(this.surfaceMapSignal) {
|
||||
surface.disconnect(this.surfaceMapSignal);
|
||||
this.surfaceMapSignal = null;
|
||||
}
|
||||
|
||||
const monitor = surface.display.get_monitor_at_surface(surface);
|
||||
const size = JSON.parse(settings.get_string('window-size'));
|
||||
const hasMonitor = Boolean(monitor && monitor.geometry);
|
||||
|
||||
/* Let GTK handle window restore if no monitor, otherwise
|
||||
check if its size is greater then saved window size */
|
||||
if(
|
||||
!hasMonitor
|
||||
|| (monitor.geometry.width >= size[0]
|
||||
&& monitor.geometry.height >= size[1])
|
||||
) {
|
||||
if(!hasMonitor)
|
||||
debug('restoring window size without monitor geometry');
|
||||
|
||||
this.root.set_default_size(size[0], size[1]);
|
||||
debug(`restored window size: ${size[0]}x${size[1]}`);
|
||||
}
|
||||
}
|
||||
|
||||
_onEnterMonitor(surface, monitor)
|
||||
{
|
||||
debug('entered new monitor');
|
||||
|
||||
const { geometry } = monitor;
|
||||
debug(`monitor application-pixels: ${geometry.width}x${geometry.height}`);
|
||||
|
||||
const monitorWidth = Math.max(geometry.width, geometry.height);
|
||||
this.isMobileMonitor = (monitorWidth < 1280);
|
||||
debug(`mobile monitor detected: ${this.isMobileMonitor}`);
|
||||
|
||||
const hasTVCss = this.root.has_css_class('tvmode');
|
||||
if(hasTVCss === this.isMobileMonitor) {
|
||||
const action = (this.isMobileMonitor) ? 'remove' : 'add';
|
||||
this.root[action + '_css_class']('tvmode');
|
||||
}
|
||||
/* Update top revealer display mode */
|
||||
this.revealerTop.setFullscreenMode(this.isFullscreenMode, this.isMobileMonitor);
|
||||
}
|
||||
|
||||
_clearTimeout(name)
|
||||
{
|
||||
if(!this[`_${name}Timeout`])
|
||||
@@ -619,7 +634,6 @@ class ClapperWidget extends Gtk.Grid
|
||||
this.revealerTop.revealChild(false);
|
||||
this.revealerBottom.revealChild(false);
|
||||
}
|
||||
this.setControlsCanFocus(false);
|
||||
|
||||
return GLib.SOURCE_REMOVE;
|
||||
});
|
||||
@@ -632,6 +646,7 @@ class ClapperWidget extends Gtk.Grid
|
||||
&& !this.isMobileMonitor
|
||||
&& !this._updateTimeTimeout
|
||||
) {
|
||||
debug('setting update time interval');
|
||||
this._setUpdateTimeInterval();
|
||||
}
|
||||
}
|
||||
@@ -706,10 +721,11 @@ class ClapperWidget extends Gtk.Grid
|
||||
_getDropTarget()
|
||||
{
|
||||
const dropTarget = new Gtk.DropTarget({
|
||||
actions: Gdk.DragAction.COPY,
|
||||
actions: Gdk.DragAction.COPY | Gdk.DragAction.MOVE,
|
||||
preload: true,
|
||||
});
|
||||
dropTarget.set_gtypes([GObject.TYPE_STRING]);
|
||||
dropTarget.connect('motion', this._onDataMotion.bind(this));
|
||||
dropTarget.connect('drop', this._onDataDrop.bind(this));
|
||||
dropTarget.connect('notify::value', this._onDropValueNotify.bind(this));
|
||||
|
||||
@@ -766,6 +782,28 @@ class ClapperWidget extends Gtk.Grid
|
||||
}
|
||||
}
|
||||
|
||||
_onKeyReleased(controller, keyval, keycode, state)
|
||||
{
|
||||
/* Ignore releases that did not trigger keypress
|
||||
* e.g. while holding left "Super" key */
|
||||
if(!this.isReleaseKeyEnabled)
|
||||
return;
|
||||
|
||||
switch(keyval) {
|
||||
case Gdk.KEY_Right:
|
||||
case Gdk.KEY_Left:
|
||||
const value = Math.round(
|
||||
this.controls.positionScale.get_value()
|
||||
);
|
||||
this.player.seek_seconds(value);
|
||||
this._setHideControlsTimeout();
|
||||
this.isReleaseKeyEnabled = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_onDragUpdate(gesture, offsetX, offsetY)
|
||||
{
|
||||
if(!this.isDragAllowed || this.isFullscreenMode)
|
||||
@@ -915,6 +953,11 @@ class ClapperWidget extends Gtk.Grid
|
||||
ytClient.getVideoInfoPromise(videoId).catch(debug);
|
||||
}
|
||||
|
||||
_onDataMotion(dropTarget, x, y)
|
||||
{
|
||||
return Gdk.DragAction.MOVE;
|
||||
}
|
||||
|
||||
_onDataDrop(dropTarget, value, x, y)
|
||||
{
|
||||
const files = value.split(/\r?\n/).filter(uri => {
|
||||
@@ -927,7 +970,9 @@ class ClapperWidget extends Gtk.Grid
|
||||
for(let index in files)
|
||||
files[index] = Gio.File.new_for_uri(files[index]);
|
||||
|
||||
this.root.application.open(files, "");
|
||||
const app = this.root.application;
|
||||
app.isFileAppend = Boolean(dropTarget.drop.actions & Gdk.DragAction.COPY);
|
||||
app.open(files, "");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ class ClapperWidgetRemote extends Gtk.Grid
|
||||
this.togglePlayButton.remove_css_class('flat');
|
||||
this.togglePlayButton.child.add_css_class('playbackicon');
|
||||
this.togglePlayButton.connect(
|
||||
'clicked', this.sendWs.bind(this, 'toggle_play')
|
||||
'clicked', () => this.sendWs('toggle_play')
|
||||
);
|
||||
|
||||
this.attach(this.togglePlayButton, 0, 0, 1, 1);
|
||||
|
@@ -38,7 +38,7 @@ var YouTubeClient = GObject.registerClass({
|
||||
|
||||
this.lastInfo = null;
|
||||
this.postInfo = {
|
||||
clientVersion: null,
|
||||
clientVersion: "2.20210605.09.00",
|
||||
visitorData: "",
|
||||
};
|
||||
|
||||
@@ -729,6 +729,7 @@ var YouTubeClient = GObject.registerClass({
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = [
|
||||
`video_id=${videoId}`,
|
||||
`html5=1`,
|
||||
`el=embedded`,
|
||||
`eurl=https://youtube.googleapis.com/v/${videoId}`,
|
||||
`sts=${this.cachedSig.timestamp}`,
|
||||
|
@@ -4,11 +4,11 @@
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Open Local...</attribute>
|
||||
<attribute name="action">app.openLocal</attribute>
|
||||
<attribute name="action">app.open_local</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Open URI...</attribute>
|
||||
<attribute name="action">app.openUri</attribute>
|
||||
<attribute name="action">app.open_uri</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
@@ -16,14 +16,12 @@
|
||||
<attribute name="label" translatable="yes">Preferences</attribute>
|
||||
<attribute name="action">app.prefs</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<!--
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Keyboard Shortcuts</attribute>
|
||||
<attribute name="label" translatable="yes">Shortcuts</attribute>
|
||||
<attribute name="action">app.shortcuts</attribute>
|
||||
</item>
|
||||
-->
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">About Clapper</attribute>
|
||||
<attribute name="action">app.about</attribute>
|
||||
|
133
ui/help-overlay.ui
Normal file
133
ui/help-overlay.ui
Normal file
@@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkShortcutsWindow" id="help_overlay">
|
||||
<property name="modal">True</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsSection">
|
||||
<property name="section-name">app</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="title" translatable="yes">General</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Show shortcuts</property>
|
||||
<property name="accelerator">F1 <Ctrl>question</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Toggle fullscreen</property>
|
||||
<property name="accelerator">F11 f</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Reveal OSD (fullscreen only)</property>
|
||||
<property name="accelerator">Return</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Quit</property>
|
||||
<property name="accelerator"><Ctrl>Q Q</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="title" translatable="yes">Media</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Open Local</property>
|
||||
<property name="accelerator"><Ctrl>O</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Open URI</property>
|
||||
<property name="accelerator"><Ctrl>U</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="title" translatable="yes">Playlist</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Next item</property>
|
||||
<property name="accelerator"><Ctrl>Right</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Previous item</property>
|
||||
<property name="accelerator"><Ctrl>Left</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Change repeat mode</property>
|
||||
<property name="accelerator"><Ctrl>R</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Export to file</property>
|
||||
<property name="accelerator"><Ctrl>E</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsGroup">
|
||||
<property name="title" translatable="yes">Playback</property>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Toggle play</property>
|
||||
<property name="accelerator">space</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Volume up</property>
|
||||
<property name="accelerator">Up</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Volume down</property>
|
||||
<property name="accelerator">Down</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Seek forward</property>
|
||||
<property name="accelerator">Right</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Seek backward</property>
|
||||
<property name="accelerator">Left</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Next chapter</property>
|
||||
<property name="accelerator"><Shift>Right</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="title" translatable="yes">Previous chapter</property>
|
||||
<property name="accelerator"><Shift>Left</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
Reference in New Issue
Block a user