Merge pull request #8 from Rafostar/GTK4

We are moving to GTK4! Porting took a little longer than expected, cause I had to port GStreamer first. This change will be problematic for users of non-rolling linux distros, but its worth it. Both GTK4 and GStreamer 1.18+ have important GL changes. Clapper player takes full advantage of them.
This commit is contained in:
Rafostar
2020-10-14 18:52:23 +02:00
committed by GitHub
15 changed files with 720 additions and 613 deletions

132
README.md
View File

@@ -28,10 +28,11 @@ Using hardware acceleration is highly recommended. As stated in `GStreamer` wiki
In the case of OpenGL based elements, the buffers have the GstVideoGLTextureUploadMeta meta, which In the case of OpenGL based elements, the buffers have the GstVideoGLTextureUploadMeta meta, which
efficiently copies the content of the VA-API surface into a GL texture. efficiently copies the content of the VA-API surface into a GL texture.
``` ```
Clapper uses `OpenGL` based sinks, so when `VA-API` is available, both CPU and RAM usage is much lower. Clapper uses `OpenGL` based sinks, so when `VA-API` is available, both CPU and RAM usage is much lower. Especially if you have `gst-plugins-bad` 1.18+ with new `vah264dec` decoder which shares a single GL context with Clapper and uses DRM connection. If you have an AMD/Intel GPU, I highly recommend this new decoder.
To use `VA-API` make sure you have `gstreamer1-vaapi` installed. Verify with: To use `VA-API` with H.264 videos, make sure you have `gst-plugins-bad` 1.18+. For other codecs additionally install `gstreamer1-vaapi`. Verify with:
```shell ```shell
gst-inspect-1.0 vah264dec
gst-inspect-1.0 vaapi gst-inspect-1.0 vaapi
``` ```
On some older GPUs you might need to export `GST_VAAPI_ALL_DRIVERS=1` environment variable. On some older GPUs you might need to export `GST_VAAPI_ALL_DRIVERS=1` environment variable.
@@ -40,11 +41,13 @@ Other acceleration methods (supported by `GStreamer`) should also work, but I ha
</details> </details>
## Requirements ## Requirements
Clapper uses `GStreamer` bindings from `GI` repository, so if your distro ships them as separate package, they must be installed first. Clapper uses GTK4 along with `GStreamer` bindings from `GI` repository, so if your distro ships them as separate package, they must be installed first.
Additionally Clapper requires these `GStreamer` elements: Additionally Clapper requires these `GStreamer` elements:
* [gtkglsink](https://gstreamer.freedesktop.org/documentation/gtk/gtkglsink.html) * [gtk4glsink](https://gstreamer.freedesktop.org/documentation/gtk/gtkglsink.html)
* [glsinkbin](https://gstreamer.freedesktop.org/documentation/opengl/glsinkbin.html) * [glsinkbin](https://gstreamer.freedesktop.org/documentation/opengl/glsinkbin.html)
**Attention:** `gtk4glsink` is my own port of current GStreamer `gtkglsink` to GTK4. The element is not part of GStreamer yet (pending review). Fedora package is available in my OBS repository. It will be installed along with Clapper if you add my repo to `dnf` package manager. Otherwise you might want to build it yourself from [source code](https://gitlab.freedesktop.org/Rafostar/gst-plugins-good/-/tree/GTK4) of my gstreamer GTK4 branch.
Other required plugins (codecs) depend on video format. Other required plugins (codecs) depend on video format.
Recommended additional packages you should install manually via package manager: Recommended additional packages you should install manually via package manager:
@@ -53,69 +56,90 @@ Recommended additional packages you should install manually via package manager:
Please note that packages naming varies by distro. Please note that packages naming varies by distro.
## Installation
Run in terminal:
```shell
meson builddir --prefix=/usr/local
sudo meson install -C builddir
```
Additional GStreamer elements installation:
<details>
<summary>Fedora</summary>
Enable RPM Fusion and run:
```shell
sudo dnf install \
gstreamer1-plugins-base \
gstreamer1-plugins-good-gtk \
gstreamer1-libav \
gstreamer1-vaapi
```
</details>
<details>
<summary>openSUSE</summary>
```shell
sudo zypper install \
gstreamer-plugins-base \
gstreamer-plugins-good \
gstreamer-plugins-libav \
gstreamer-plugins-vaapi
```
</details>
<details>
<summary>Arch Linux</summary>
```shell
sudo pacman -S \
gst-plugins-base \
gst-plugin-gtk \
gst-libav \
gstreamer-vaapi
```
</details>
## Packages ## Packages
The [pkgs folder](https://github.com/Rafostar/clapper/tree/master/pkgs) in this repository contains build scripts for various package formats. You can use them to build package yourself or download one of pre-built packages: 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:
<details> <details>
<summary>Fedora, openSUSE & SLE (rpm)</summary> <summary><b>Debian, Fedora, openSUSE & Ubuntu</b></summary>
Pre-built packages are available here:<br> 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))
[software.opensuse.org//download.html?project=home:sp1rit&package=clapper](https://software.opensuse.org//download.html?project=home%3Asp1rit&package=clapper) ([See status](https://build.opensuse.org/package/show/home:sp1rit/clapper))
</details> </details>
<details> <details>
<summary>Arch Linux</summary> <summary><b>Arch Linux</b></summary>
You can get clapper from the AUR: [clapper-git](https://aur.archlinux.org/packages/clapper-git), or You can get Clapper from the AUR: [clapper-git](https://aur.archlinux.org/packages/clapper-git), or
```shell ```shell
cd pkgs/arch cd pkgs/arch
makepkg -si makepkg -si
``` ```
</details> </details>
## Installation from source code
Run in terminal:
```shell
meson builddir --prefix=/usr/local
sudo meson install -C builddir
```
GStreamer elements installation:
<details>
<summary><b>Debian/Ubuntu</b></summary>
```shell
sudo apt install \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-gl \
gstreamer1.0-gtk4 \
gstreamer1.0-libav \
gstreamer-vaapi
```
</details>
<details>
<summary><b>Fedora</b></summary>
Enable RPM Fusion and run:
```shell
sudo dnf install \
gstreamer1-plugins-base \
gstreamer1-plugins-good \
gstreamer1-plugins-good-gtk4 \
gstreamer1-plugins-bad-free \
gstreamer1-plugins-bad-free-extras \
gstreamer1-libav \
gstreamer1-vaapi
```
</details>
<details>
<summary><b>openSUSE</b></summary>
```shell
sudo zypper install \
gstreamer-plugins-base \
gstreamer-plugins-good \
gstreamer-plugins-good-gtk4 \
gstreamer-plugins-bad \
gstreamer-plugins-libav \
gstreamer-plugins-vaapi
```
</details>
<details>
<summary><b>Arch Linux</b></summary>
```shell
sudo pacman -S \
gst-plugins-base \
gst-plugins-good \
gst-plugin-gtk4 \
gst-plugins-bad-libs \
gst-libav \
gstreamer-vaapi
```
</details>
## Special Thanks ## Special Thanks
Many thanks to [sp1ritCS](https://github.com/sp1ritCS) for creating and maintaining package build files. Many thanks to [sp1ritCS](https://github.com/sp1ritCS) for creating and maintaining package build files.

View File

@@ -1,5 +1,6 @@
const { Gdk, GLib, GObject, Gtk, GstPlayer } = imports.gi; const { Gdk, GLib, GObject, Gtk, GstPlayer } = imports.gi;
const Debug = imports.clapper_src.debug; const Debug = imports.clapper_src.debug;
const { HeaderBar } = imports.clapper_src.headerbar;
const { Interface } = imports.clapper_src.interface; const { Interface } = imports.clapper_src.interface;
const { Player } = imports.clapper_src.player; const { Player } = imports.clapper_src.player;
const { Window } = imports.clapper_src.window; const { Window } = imports.clapper_src.window;
@@ -20,8 +21,6 @@ var App = GObject.registerClass({
{ {
_init(opts) _init(opts)
{ {
GLib.set_prgname(APP_NAME);
super._init({ super._init({
application_id: pkg.name application_id: pkg.name
}); });
@@ -39,7 +38,10 @@ var App = GObject.registerClass({
this.window = null; this.window = null;
this.interface = null; this.interface = null;
this.player = null; this.player = null;
this.dragStartReady = false; this.dragAllowed = false;
this.posX = 0;
this.posY = 0;
} }
vfunc_startup() vfunc_startup()
@@ -51,35 +53,43 @@ var App = GObject.registerClass({
'realize', this._onWindowRealize.bind(this) 'realize', this._onWindowRealize.bind(this)
); );
this.window.connect( this.window.connect(
'key-press-event', this._onWindowKeyPressEvent.bind(this) 'fullscreen-changed', this._onWindowFullscreenChanged.bind(this)
); );
this.window.connect( this.window.connect(
'fullscreen-changed', this._onWindowFullscreenChanged.bind(this) 'close-request', this._onWindowCloseRequest.bind(this)
); );
this.interface = new Interface(); this.interface = new Interface();
let headerBar = new Gtk.HeaderBar({
title: APP_NAME, let headerStart = [];
show_close_button: true, let headerEnd = [
}); this.interface.controls.openMenuButton,
headerBar.pack_end(this.interface.controls.openMenuButton); this.interface.controls.fullscreenButton
headerBar.pack_end(this.interface.controls.fullscreenButton); ];
let headerBar = new HeaderBar(this.window, headerStart, headerEnd);
this.interface.addHeaderBar(headerBar, APP_NAME); this.interface.addHeaderBar(headerBar, APP_NAME);
this.interface.controls.fullscreenButton.connect( this.interface.controls.fullscreenButton.connect(
'clicked', () => this._onInterfaceToggleFullscreenClicked(true) 'clicked', () => this.activeWindow.fullscreen()
); );
this.interface.controls.unfullscreenButton.connect( this.interface.controls.unfullscreenButton.connect(
'clicked', () => this._onInterfaceToggleFullscreenClicked(false) 'clicked', () => this.activeWindow.unfullscreen()
); );
this.window.set_titlebar(this.interface.headerBar); this.window.set_titlebar(this.interface.headerBar);
this.window.add(this.interface); this.window.set_child(this.interface);
} }
vfunc_activate() vfunc_activate()
{ {
super.vfunc_activate(); super.vfunc_activate();
this.window.show_all();
this.window.present();
Gtk.StyleContext.add_provider_for_display(
Gdk.Display.get_default(),
this.cssProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
);
} }
run(arr) run(arr)
@@ -94,8 +104,8 @@ var App = GObject.registerClass({
this.hideCursorTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => { this.hideCursorTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
this.hideCursorTimeout = null; this.hideCursorTimeout = null;
if(this.isCursorInPlayer) if(this.player.motionController.is_pointer)
this.playerWindow.set_cursor(this.blankCursor); this.player.widget.set_cursor(this.blankCursor);
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
}); });
@@ -107,7 +117,7 @@ var App = GObject.registerClass({
this.hideControlsTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3, () => { this.hideControlsTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3, () => {
this.hideControlsTimeout = null; this.hideControlsTimeout = null;
if(this.window.isFullscreen && this.isCursorInPlayer) { if(this.window.isFullscreen && this.player.motionController.is_pointer) {
this.clearTimeout('updateTime'); this.clearTimeout('updateTime');
this.interface.revealControls(false); this.interface.revealControls(false);
} }
@@ -146,80 +156,63 @@ var App = GObject.registerClass({
{ {
this.window.disconnect(this.windowRealizeSignal); this.window.disconnect(this.windowRealizeSignal);
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(),
this.cssProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
);
this.player = new Player(); this.player = new Player();
this.player.widget.add_events(
Gdk.EventMask.SCROLL_MASK
| Gdk.EventMask.ENTER_NOTIFY_MASK
| Gdk.EventMask.LEAVE_NOTIFY_MASK
);
this.interface.addPlayer(this.player);
this.player.connect('warning', this._onPlayerWarning.bind(this)); if(!this.player.widget)
this.player.connect('error', this._onPlayerError.bind(this)); return this.quit();
this.player.widget.width_request = 960;
this.player.widget.height_request = 540;
this.interface.addPlayer(this.player);
this.player.connect('state-changed', this._onPlayerStateChanged.bind(this)); this.player.connect('state-changed', this._onPlayerStateChanged.bind(this));
this.player.connectWidget( this.player.clickGesture.connect(
'button-press-event', this._onPlayerButtonPressEvent.bind(this) 'pressed', this._onPlayerPressed.bind(this)
); );
this.player.connectWidget( this.player.keyController.connect(
'enter-notify-event', this._onPlayerEnterNotifyEvent.bind(this) 'key-pressed', this._onPlayerKeyPressed.bind(this)
); );
this.player.connectWidget( this.player.motionController.connect(
'leave-notify-event', this._onPlayerLeaveNotifyEvent.bind(this) 'enter', this._onPlayerEnter.bind(this)
); );
this.player.connectWidget( this.player.motionController.connect(
'motion-notify-event', this._onPlayerMotionNotifyEvent.bind(this) 'leave', this._onPlayerLeave.bind(this)
);
this.player.motionController.connect(
'motion', this._onPlayerMotion.bind(this)
);
this.player.dragGesture.connect(
'drag-update', this._onPlayerDragUpdate.bind(this)
); );
/* Widget signals that are disconnected after first run */ /* Widget signals that are disconnected after first run */
this._playerRealizeSignal = this.player.widget.connect( this._playerRealizeSignal = this.player.widget.connect(
'realize', this._onPlayerRealize.bind(this) 'realize', this._onPlayerRealize.bind(this)
); );
this._playerDrawSignal = this.player.widget.connect( this._playerMapSignal = this.player.widget.connect(
'draw', this._onPlayerDraw.bind(this) 'map', this._onPlayerMap.bind(this)
); );
} }
_onWindowFullscreenChanged(window, isFullscreen) _onWindowFullscreenChanged(window, isFullscreen)
{ {
// when changing fullscreen pango layout of popup is lost
// and we need to re-add marks to the new layout
this.interface.controls.setVolumeMarks(false);
if(isFullscreen) { if(isFullscreen) {
this.setUpdateTimeInterval(); this.setUpdateTimeInterval();
this.setHideControlsTimeout(); this.setHideControlsTimeout();
this.interface.controls.unfullscreenButton.set_sensitive(true);
this.interface.controls.unfullscreenButton.show();
this.interface.showControls(true);
} }
else { else {
this.clearTimeout('updateTime'); this.clearTimeout('updateTime');
this.interface.controls.unfullscreenButton.set_sensitive(false);
this.interface.controls.unfullscreenButton.hide();
this.interface.showControls(false);
} }
this.interface.setControlsOnVideo(isFullscreen); this.interface.setFullscreenMode(isFullscreen);
this.interface.controls.setVolumeMarks(true);
this.interface.controls.setFullscreenMode(isFullscreen);
} }
_onWindowKeyPressEvent(self, event) _onPlayerKeyPressed(self, keyval, keycode, state)
{ {
let [res, key] = event.get_keyval();
if(!res) return;
//let keyName = Gdk.keyval_name(key);
let bool = false; let bool = false;
switch(key) { switch(keyval) {
case Gdk.KEY_space: case Gdk.KEY_space:
case Gdk.KEY_Return: case Gdk.KEY_Return:
this.player.toggle_play(); this.player.toggle_play();
@@ -244,42 +237,27 @@ var App = GObject.registerClass({
break; break;
case Gdk.KEY_q: case Gdk.KEY_q:
case Gdk.KEY_Q: case Gdk.KEY_Q:
this.window.destroy(); this._onWindowCloseRequest();
break; break;
default: default:
break; break;
} }
} }
_onInterfaceToggleFullscreenClicked(isFsRequested)
{
if(this.window.isFullscreen === isFsRequested)
return;
this.window.toggleFullscreen();
}
_onPlayerRealize() _onPlayerRealize()
{ {
this.player.widget.disconnect(this._playerRealizeSignal); this.player.widget.disconnect(this._playerRealizeSignal);
this.player.renderer.expose(); this.player.renderer.expose();
let display = this.player.widget.get_display(); this.defaultCursor = Gdk.Cursor.new_from_name('default', null);
this.blankCursor = Gdk.Cursor.new_from_name('none', null);
this.defaultCursor = Gdk.Cursor.new_from_name(
display, 'default'
);
this.blankCursor = Gdk.Cursor.new_for_display(
display, Gdk.CursorType.BLANK_CURSOR
);
this.playerWindow = this.player.widget.get_window();
this.setHideCursorTimeout(); this.setHideCursorTimeout();
} }
_onPlayerDraw(self, data) _onPlayerMap(self, data)
{ {
this.player.widget.disconnect(this._playerDrawSignal); this.player.widget.disconnect(this._playerMapSignal);
this.emit('ready', true); this.emit('ready', true);
if(this.playlist.length) if(this.playlist.length)
@@ -291,6 +269,7 @@ var App = GObject.registerClass({
if(state === GstPlayer.PlayerState.BUFFERING) if(state === GstPlayer.PlayerState.BUFFERING)
return; return;
let isInhibited = false;
let flags = Gtk.ApplicationInhibitFlags.SUSPEND let flags = Gtk.ApplicationInhibitFlags.SUSPEND
| Gtk.ApplicationInhibitFlags.IDLE; | Gtk.ApplicationInhibitFlags.IDLE;
@@ -303,79 +282,66 @@ var App = GObject.registerClass({
flags, flags,
'video is playing' 'video is playing'
); );
if(!this.inhibitCookie)
debug(new Error('could not inhibit session!'));
isInhibited = (this.inhibitCookie > 0);
} }
else { else {
if(!this.inhibitCookie) //if(!this.inhibitCookie)
return; return;
/* Uninhibit seems to be broken as of GTK 3.99.2
this.uninhibit(this.inhibitCookie); this.uninhibit(this.inhibitCookie);
this.inhibitCookie = null; this.inhibitCookie = null;
*/
} }
debug('set prevent suspend to: ' + this.is_inhibited(flags)); debug(`set prevent suspend to: ${isInhibited}`);
} }
_onPlayerButtonPressEvent(self, event) _onPlayerPressed(gesture, nPress, x, y)
{ {
let [res, button] = event.get_button(); let button = gesture.get_current_button();
if(!res) return; let isDouble = (nPress % 2 == 0);
this.dragAllowed = !isDouble;
this.dragStartReady = false;
switch(button) { switch(button) {
case Gdk.BUTTON_PRIMARY: case Gdk.BUTTON_PRIMARY:
this._handlePrimaryButtonPress(event, button); if(isDouble)
this.window.toggleFullscreen();
break; break;
case Gdk.BUTTON_SECONDARY: case Gdk.BUTTON_SECONDARY:
if(event.get_event_type() === Gdk.EventType.BUTTON_PRESS) this.player.toggle_play();
this.player.toggle_play();
break; break;
default: default:
break; break;
} }
} }
_handlePrimaryButtonPress(event, button) _onPlayerEnter(controller, x, y)
{ {
let eventType = event.get_event_type();
switch(eventType) {
case Gdk.EventType.BUTTON_PRESS:
let [res, x, y] = event.get_root_coords();
if(!res)
break;
this.dragStartX = x;
this.dragStartY = y;
this.dragStartReady = true;
break;
case Gdk.EventType.DOUBLE_BUTTON_PRESS:
this.window.toggleFullscreen();
break;
default:
break;
}
}
_onPlayerEnterNotifyEvent(self, event)
{
this.isCursorInPlayer = true;
this.setHideCursorTimeout(); this.setHideCursorTimeout();
if(this.window.isFullscreen) if(this.window.isFullscreen)
this.setHideControlsTimeout(); this.setHideControlsTimeout();
} }
_onPlayerLeaveNotifyEvent(self, event) _onPlayerLeave(controller)
{ {
this.isCursorInPlayer = false;
this.clearTimeout('hideCursor'); this.clearTimeout('hideCursor');
this.clearTimeout('hideControls'); this.clearTimeout('hideControls');
} }
_onPlayerMotionNotifyEvent(self, event) _onPlayerMotion(self, posX, posY)
{ {
this.playerWindow.set_cursor(this.defaultCursor); /* GTK4 sometimes generates motions with same coords */
if(this.posX === posX && this.posY === posY)
return;
this.posX = posX;
this.posY = posY;
this.player.widget.set_cursor(this.defaultCursor);
this.setHideCursorTimeout(); this.setHideCursorTimeout();
if(this.window.isFullscreen) { if(this.window.isFullscreen) {
@@ -388,36 +354,40 @@ var App = GObject.registerClass({
else if(this.hideControlsTimeout) { else if(this.hideControlsTimeout) {
this.clearTimeout('hideControls'); this.clearTimeout('hideControls');
} }
}
if(!this.dragStartReady || this.window.isFullscreen) _onPlayerDragUpdate(gesture, offsetX, offsetY)
{
if(!this.dragAllowed || this.activeWindow.isFullscreen)
return; return;
let [res, x, y] = event.get_root_coords(); let { gtk_double_click_distance } = this.player.widget.get_settings();
if(!res) return;
let startDrag = this.player.widget.drag_check_threshold( if (
this.dragStartX, this.dragStartY, x, y Math.abs(offsetX) > gtk_double_click_distance
); || Math.abs(offsetY) > gtk_double_click_distance
if(!startDrag) return; ) {
let [isActive, startX, startY] = gesture.get_start_point();
if(!isActive) return;
this.dragStartReady = false; this.activeWindow.get_surface().begin_move(
let timestamp = event.get_time(); gesture.get_device(),
gesture.get_current_button(),
startX,
startY,
gesture.get_current_event_time()
);
this.window.begin_move_drag( gesture.reset();
Gdk.BUTTON_PRIMARY, }
this.dragStartX,
this.dragStartY,
timestamp
);
} }
_onPlayerWarning(self, error) _onWindowCloseRequest()
{ {
debug(error.message, 'LEVEL_WARNING'); this.window.destroy();
} this.player.widget.emit('destroy');
this.interface.emit('destroy');
_onPlayerError(self, error) this.quit();
{
debug(error);
} }
}); });

View File

@@ -1,41 +1,24 @@
const { GObject, Gtk } = imports.gi; const { GObject, Gtk } = imports.gi;
var BoxedIconButton = GObject.registerClass( var CustomButton = GObject.registerClass(
class BoxedIconButton extends Gtk.Button class ClapperCustomButton extends Gtk.Button
{ {
_init(icon, size, isFullscreen) _init(opts)
{ {
super._init({ opts = opts || {};
let defaults = {
margin_top: 4, margin_top: 4,
margin_bottom: 4, margin_bottom: 4,
can_focus: false, margin_start: 1,
can_default: false, margin_end: 1,
}); };
Object.assign(opts, defaults);
this.isFullscreen = isFullscreen || false; super._init(opts);
size = size || Gtk.IconSize.SMALL_TOOLBAR; this.isFullscreen = false;
let image = Gtk.Image.new_from_icon_name(icon, size); this.add_css_class('flat');
if(image)
this.set_image(image);
this.image.defaultSize = size;
this.image.fullscreenSize = (size === Gtk.IconSize.SMALL_TOOLBAR)
? Gtk.IconSize.LARGE_TOOLBAR
: Gtk.IconSize.DND;
this.get_style_context().add_class('flat');
this.box = new Gtk.Box();
this.box.pack_start(this, false, false, 0);
super.show();
}
get visible()
{
return this.box.visible;
} }
setFullscreenMode(isFullscreen) setFullscreenMode(isFullscreen)
@@ -43,62 +26,126 @@ class BoxedIconButton extends Gtk.Button
if(this.isFullscreen === isFullscreen) if(this.isFullscreen === isFullscreen)
return; return;
this.image.icon_size = (isFullscreen) this.margin_top = (isFullscreen) ? 6 : 4;
? this.image.fullscreenSize
: this.image.defaultSize;
this.isFullscreen = isFullscreen; this.isFullscreen = isFullscreen;
} }
show_all()
{
this.box.show_all();
}
show()
{
this.box.show();
}
hide()
{
this.box.hide();
}
}); });
var BoxedPopoverButton = GObject.registerClass( var IconButton = GObject.registerClass(
class BoxedPopoverButton extends BoxedIconButton class ClapperIconButton extends CustomButton
{ {
_init(icon, size, isFullscreen) _init(icon)
{ {
super._init(icon, size, isFullscreen); super._init({
icon_name: icon,
this.popover = new Gtk.Popover({
relative_to: this.box
}); });
this.popoverBox = new Gtk.Box({ }
orientation: Gtk.Orientation.VERTICAL });
});
this.popover.add(this.popoverBox);
this.popoverBox.show();
if(this.isFullscreen) var IconToggleButton = GObject.registerClass(
this.popover.get_style_context().add_class('osd'); class ClapperIconToggleButton extends IconButton
{
_init(primaryIcon, secondaryIcon)
{
super._init(primaryIcon);
this.primaryIcon = primaryIcon;
this.secondaryIcon = secondaryIcon;
} }
setFullscreenMode(isEnabled) setPrimaryIcon()
{ {
if(this.isFullscreen === isEnabled) this.icon_name = this.primaryIcon;
}
setSecondaryIcon()
{
this.icon_name = this.secondaryIcon;
}
});
var LabelButton = GObject.registerClass(
class ClapperLabelButton extends CustomButton
{
_init(text)
{
super._init({
margin_start: 0,
margin_end: 0,
});
this.customLabel = new Gtk.Label({
label: text,
single_line_mode: true,
});
this.customLabel.add_css_class('labelbutton');
this.set_child(this.customLabel);
}
set_label(text)
{
this.customLabel.set_text(text);
}
});
var PopoverButton = GObject.registerClass(
class ClapperPopoverButton extends IconButton
{
_init(icon)
{
super._init(icon);
this.popover = new Gtk.Popover({
position: Gtk.PositionType.TOP,
});
this.popoverBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
});
this.popover.set_parent(this);
this.popover.set_child(this.popoverBox);
this.popover.set_offset(0, -this.margin_top);
if(this.isFullscreen)
this.popover.add_css_class('osd');
this.changeStateSignal = this.popover.connect('closed', () =>
this.unset_state_flags(Gtk.StateFlags.CHECKED)
);
this.destroySignal = this.connect('destroy', this._onDestroy.bind(this));
}
setFullscreenMode(isFullscreen)
{
if(this.isFullscreen === isFullscreen)
return; return;
let action = (isEnabled) ? 'add_class' : 'remove_class'; super.setFullscreenMode(isFullscreen);
this.popover.get_style_context()[action]('osd');
super.setFullscreenMode(isEnabled); this.popover.set_offset(0, -this.margin_top);
let cssClass = 'osd';
if(isFullscreen == this.popover.has_css_class(cssClass))
return;
let action = (isFullscreen) ? 'add' : 'remove';
this.popover[action + '_css_class'](cssClass);
} }
vfunc_clicked() vfunc_clicked()
{ {
this.set_state_flags(Gtk.StateFlags.CHECKED, false);
this.popover.popup(); this.popover.popup();
} }
_onDestroy()
{
this.disconnect(this.destroySignal);
this.popover.disconnect(this.changeStateSignal);
this.popover.unparent();
this.popoverBox.emit('destroy');
this.popover.emit('destroy');
}
}); });

View File

@@ -31,8 +31,8 @@ var Controls = GObject.registerClass({
valign: Gtk.Align.END, valign: Gtk.Align.END,
}); });
this.fullscreenMode = false;
this.durationFormated = '00:00:00'; this.durationFormated = '00:00:00';
this.elapsedInitial = '00:00:00/00:00:00';
this.buttonsArr = []; this.buttonsArr = [];
this._addTogglePlayButton(); this._addTogglePlayButton();
@@ -52,142 +52,110 @@ var Controls = GObject.registerClass({
this._addVolumeButton(); this._addVolumeButton();
this.unfullscreenButton = this.addButton( this.unfullscreenButton = this.addButton(
'view-restore-symbolic', 'view-restore-symbolic',
Gtk.IconSize.SMALL_TOOLBAR,
true
); );
this.unfullscreenButton.set_visible(false);
this.fullscreenButton = Gtk.Button.new_from_icon_name( this.fullscreenButton = Gtk.Button.new_from_icon_name(
'view-fullscreen-symbolic', 'view-fullscreen-symbolic',
Gtk.IconSize.SMALL_TOOLBAR
); );
this.setDefaultWidgetBehaviour(this.fullscreenButton);
this.openMenuButton = Gtk.Button.new_from_icon_name( this.openMenuButton = Gtk.Button.new_from_icon_name(
'open-menu-symbolic', 'open-menu-symbolic',
Gtk.IconSize.SMALL_TOOLBAR
); );
this.setDefaultWidgetBehaviour(this.openMenuButton);
this.forall(this.setDefaultWidgetBehaviour);
this.realizeSignal = this.connect( this.add_css_class('playercontrols');
'realize', this._onControlsRealize.bind(this)
);
}
pack_start(widget, expand, fill, padding) this.realizeSignal = this.connect('realize', this._onRealize.bind(this));
{ this.destroySignal = this.connect('destroy', this._onDestroy.bind(this));
if(
widget.box
&& widget.box.constructor
&& widget.box.constructor === Gtk.Box
)
widget = widget.box;
super.pack_start(widget, expand, fill, padding);
} }
setFullscreenMode(isFullscreen) setFullscreenMode(isFullscreen)
{ {
if(isFullscreen === this.fullscreenMode)
return;
for(let button of this.buttonsArr) for(let button of this.buttonsArr)
button.setFullscreenMode(isFullscreen); button.setFullscreenMode(isFullscreen);
this.fullscreenMode = isFullscreen; this.unfullscreenButton.set_visible(isFullscreen);
} }
addButton(iconName, size, noPack) addButton(buttonIcon)
{ {
let button = new Buttons.BoxedIconButton( let button = (buttonIcon instanceof Gtk.Button)
iconName, size, this.fullscreenMode ? buttonIcon
); : new Buttons.IconButton(buttonIcon);
if(!noPack) this.append(button);
this.pack_start(button, false, false, 0);
this.buttonsArr.push(button);
return button;
}
addPopoverButton(iconName, size)
{
let button = new Buttons.BoxedPopoverButton(
iconName, size, this.fullscreenMode
);
this.pack_start(button, false, false, 0);
this.buttonsArr.push(button); this.buttonsArr.push(button);
return button; return button;
} }
addRadioButtons(box, array, activeId) addLabelButton(text)
{
text = text || '';
let button = new Buttons.LabelButton(text);
return this.addButton(button);
}
addPopoverButton(iconName)
{
let button = new Buttons.PopoverButton(iconName);
return this.addButton(button);
}
addCheckButtons(box, array, activeId)
{ {
let group = null; let group = null;
let children = box.get_children(); let child = box.get_first_child();
let lastEl = (children.length > array.length) let i = 0;
? children.length
: array.length;
for(let i = 0; i < lastEl; i++) { while(child || i < array.length) {
if(i >= array.length) { if(i >= array.length) {
children[i].hide(); child.hide();
debug(`hiding unused ${children[i].type} radioButton nr: ${i}`); debug(`hiding unused ${child.type} checkButton nr: ${i}`);
i++;
child = child.get_next_sibling();
continue; continue;
} }
let el = array[i]; let el = array[i];
let radioButton; let checkButton;
if(i < children.length) { if(child) {
radioButton = children[i]; checkButton = child;
debug(`reusing ${el.type} radioButton nr: ${i}`); debug(`reusing ${el.type} checkButton nr: ${i}`);
} }
else { else {
debug(`creating new ${el.type} radioButton nr: ${i}`); debug(`creating new ${el.type} checkButton nr: ${i}`);
radioButton = new Gtk.RadioButton({ checkButton = new Gtk.CheckButton({
group: group, group: group,
}); });
radioButton.connect( checkButton.connect(
'toggled', 'toggled',
this._onRadioButtonToggled.bind(this, radioButton) this._onCheckButtonToggled.bind(this, checkButton)
); );
this.setDefaultWidgetBehaviour(radioButton); box.append(checkButton);
box.add(radioButton);
} }
radioButton.label = el.label; checkButton.label = el.label;
debug(`radioButton label: ${radioButton.label}`); debug(`checkButton label: ${checkButton.label}`);
radioButton.type = el.type; checkButton.type = el.type;
debug(`radioButton type: ${radioButton.type}`); debug(`checkButton type: ${checkButton.type}`);
radioButton.activeId = el.activeId; checkButton.activeId = el.activeId;
debug(`radioButton id: ${radioButton.activeId}`); debug(`checkButton id: ${checkButton.activeId}`);
if(radioButton.activeId === activeId) { if(checkButton.activeId === activeId) {
radioButton.set_active(true); checkButton.set_active(true);
debug(`activated ${el.type} radioButton nr: ${i}`); debug(`activated ${el.type} checkButton nr: ${i}`);
} }
if(!group) if(!group)
group = radioButton; group = checkButton;
radioButton.show(); i++;
if(child)
child = child.get_next_sibling();
} }
} }
setDefaultWidgetBehaviour(widget)
{
widget.can_focus = false;
widget.can_default = false;
}
setVolumeMarks(isAdded)
{
if(!isAdded)
return this.volumeScale.clear_marks();
this.volumeScale.add_mark(0, Gtk.PositionType.LEFT, '0%');
this.volumeScale.add_mark(1, Gtk.PositionType.LEFT, '100%');
this.volumeScale.add_mark(2, Gtk.PositionType.LEFT, '200%');
}
handleScaleIncrement(type, isUp) handleScaleIncrement(type, isUp)
{ {
let value = this[`${type}Scale`].get_value(); let value = this[`${type}Scale`].get_value();
@@ -206,49 +174,45 @@ var Controls = GObject.registerClass({
_addTogglePlayButton() _addTogglePlayButton()
{ {
this.togglePlayButton = this.addButton( this.togglePlayButton = new Buttons.IconToggleButton(
'media-playback-start-symbolic', 'media-playback-start-symbolic',
Gtk.IconSize.LARGE_TOOLBAR 'media-playback-pause-symbolic'
); );
this.togglePlayButton.setPlayImage = () => this.togglePlayButton.add_css_class('playbackicon');
{ this.addButton(this.togglePlayButton);
this.togglePlayButton.image.set_from_icon_name(
'media-playback-start-symbolic',
this.togglePlayButton.image.icon_size
);
}
this.togglePlayButton.setPauseImage = () =>
{
this.togglePlayButton.image.set_from_icon_name(
'media-playback-pause-symbolic',
this.togglePlayButton.image.icon_size
);
}
} }
_addPositionScale() _addPositionScale()
{ {
this.elapsedButton = this.addLabelButton(this.elapsedInitial);
this.positionScale = new Gtk.Scale({ this.positionScale = new Gtk.Scale({
orientation: Gtk.Orientation.HORIZONTAL, orientation: Gtk.Orientation.HORIZONTAL,
value_pos: Gtk.PositionType.LEFT, value_pos: Gtk.PositionType.LEFT,
draw_value: true, draw_value: false,
hexpand: true, hexpand: true,
valign: Gtk.Align.CENTER,
can_focus: false,
}); });
let style = this.positionScale.get_style_context();
style.add_class('positionscale');
this.positionScale.connect( this.togglePlayButton.bind_property('margin_top',
'format-value', this._onPositionScaleFormatValue.bind(this) this.positionScale, 'margin_top', GObject.BindingFlags.SYNC_CREATE
); );
this.positionScale.connect( this.togglePlayButton.bind_property('margin_bottom',
'button-press-event', this._onPositionScaleButtonPressEvent.bind(this) this.positionScale, 'margin_bottom', GObject.BindingFlags.SYNC_CREATE
); );
this.positionScale.add_css_class('positionscale');
this.positionScale.connect('value-changed', this._onPositionScaleValueChanged.bind(this));
/* GTK4 is missing pressed/released signals for GtkRange/GtkScale.
* We cannot add controllers, cause it already has them, so we
* workaround this by observing css classes it currently has */
this.positionScale.connect( this.positionScale.connect(
'button-release-event', this._onPositionScaleButtonReleaseEvent.bind(this) 'notify::css-classes', this._onPositionScaleDragging.bind(this)
); );
this.positionAdjustment = this.positionScale.get_adjustment(); this.positionAdjustment = this.positionScale.get_adjustment();
this.pack_start(this.positionScale, true, true, 0); this.append(this.positionScale);
} }
_addVolumeButton() _addVolumeButton()
@@ -256,10 +220,14 @@ var Controls = GObject.registerClass({
this.volumeButton = this.addPopoverButton( this.volumeButton = this.addPopoverButton(
'audio-volume-muted-symbolic' 'audio-volume-muted-symbolic'
); );
this.volumeButton.add_events(Gdk.EventMask.SCROLL_MASK); let scrollController = new Gtk.EventControllerScroll();
this.volumeButton.connect( scrollController.set_flags(
'scroll-event', (self, event) => this._onScrollEvent(event) Gtk.EventControllerScrollFlags.VERTICAL
| Gtk.EventControllerScrollFlags.DISCRETE
); );
scrollController.connect('scroll', this._onScroll.bind(this));
this.volumeButton.add_controller(scrollController);
this.volumeScale = new Gtk.Scale({ this.volumeScale = new Gtk.Scale({
orientation: Gtk.Orientation.VERTICAL, orientation: Gtk.Orientation.VERTICAL,
inverted: true, inverted: true,
@@ -268,18 +236,18 @@ var Controls = GObject.registerClass({
round_digits: 2, round_digits: 2,
vexpand: true, vexpand: true,
}); });
this.volumeScale.get_style_context().add_class('volumescale'); this.volumeScale.add_css_class('volumescale');
this.volumeAdjustment = this.volumeScale.get_adjustment(); this.volumeAdjustment = this.volumeScale.get_adjustment();
this.volumeAdjustment.set_upper(2); this.volumeAdjustment.set_upper(2);
this.volumeAdjustment.set_step_increment(0.05); this.volumeAdjustment.set_step_increment(0.05);
this.volumeAdjustment.set_page_increment(0.05); this.volumeAdjustment.set_page_increment(0.05);
this.setDefaultWidgetBehaviour(this.volumeScale);
this.volumeButton.popoverBox.add(this.volumeScale); for(let i = 0; i <= 2; i++) {
this.volumeButton.popoverBox.show_all(); let text = (i) ? `${i}00%` : '0%';
this.volumeScale.add_mark(i, Gtk.PositionType.LEFT, text);
this.setVolumeMarks(true); }
this.volumeButton.popoverBox.append(this.volumeScale);
} }
_getFormatedTime(time) _getFormatedTime(time)
@@ -293,25 +261,25 @@ var Controls = GObject.registerClass({
return `${hours}:${minutes}:${seconds}`; return `${hours}:${minutes}:${seconds}`;
} }
_onRadioButtonToggled(self, radioButton) _onCheckButtonToggled(self, checkButton)
{ {
if(!radioButton.get_active()) if(!checkButton.get_active())
return; return;
switch(radioButton.type) { switch(checkButton.type) {
case 'video': case 'video':
case 'audio': case 'audio':
case 'subtitle': case 'subtitle':
this.emit( this.emit(
'track-change-requested', 'track-change-requested',
radioButton.type, checkButton.type,
radioButton.activeId checkButton.activeId
); );
break; break;
case 'visualization': case 'visualization':
this.emit( this.emit(
`${radioButton.type}-change-requested`, `${checkButton.type}-change-requested`,
radioButton.activeId checkButton.activeId
); );
break; break;
default: default:
@@ -319,25 +287,26 @@ var Controls = GObject.registerClass({
} }
} }
_onPositionScaleFormatValue(self, value) _onPositionScaleValueChanged()
{ {
return this._getFormatedTime(value) let elapsed = this._getFormatedTime(this.positionScale.get_value())
+ '/' + this.durationFormated; + '/' + this.durationFormated;
this.elapsedButton.set_label(elapsed);
} }
_onPositionScaleButtonPressEvent() _onPositionScaleDragging(scale)
{ {
this.isPositionSeeking = true; let isPositionSeeking = scale.has_css_class('dragging');
if(this.isPositionSeeking === isPositionSeeking)
return;
this.isPositionSeeking = isPositionSeeking;
this.emit('position-seeking-changed', this.isPositionSeeking); this.emit('position-seeking-changed', this.isPositionSeeking);
} }
_onPositionScaleButtonReleaseEvent() _onRealize()
{
this.isPositionSeeking = false;
this.emit('position-seeking-changed', this.isPositionSeeking);
}
_onControlsRealize()
{ {
this.disconnect(this.realizeSignal); this.disconnect(this.realizeSignal);
@@ -352,27 +321,25 @@ var Controls = GObject.registerClass({
this[`${name}Button`].hide(); this[`${name}Button`].hide();
} }
_onScrollEvent(event) _onScroll(controller, dx, dy)
{ {
let [res, direction] = event.get_scroll_direction(); let isVertical = Math.abs(dy) >= Math.abs(dx);
if(!res) return; let isIncrease = (isVertical) ? dy < 0 : dx < 0;
let type = (isVertical) ? 'volume' : 'position';
let type = 'volume'; this.handleScaleIncrement(type, isIncrease);
switch(direction) { return true;
case Gdk.ScrollDirection.RIGHT: }
case Gdk.ScrollDirection.LEFT:
type = 'position'; _onDestroy()
case Gdk.ScrollDirection.UP: {
case Gdk.ScrollDirection.DOWN: this.disconnect(this.destroySignal);
let isUp = (
direction === Gdk.ScrollDirection.UP this.visualizationsButton.emit('destroy');
|| direction === Gdk.ScrollDirection.RIGHT this.videoTracksButton.emit('destroy');
); this.audioTracksButton.emit('destroy');
this.handleScaleIncrement(type, isUp); this.subtitleTracksButton.emit('destroy');
break; this.volumeButton.emit('destroy');
default:
break;
}
} }
}); });

76
clapper_src/headerbar.js Normal file
View File

@@ -0,0 +1,76 @@
const { GLib, GObject, Gtk, Pango } = imports.gi;
var HeaderBar = GObject.registerClass(
class ClapperHeaderBar extends Gtk.HeaderBar
{
_init(window, startButtons, endButtons)
{
super._init({
can_focus: false,
});
this.set_title_widget(this._createWidgetForWindow(window));
startButtons.forEach(btn => this.pack_start(btn));
endButtons.forEach(btn => this.pack_end(btn));
}
updateHeaderBar(mediaInfo)
{
let title = mediaInfo.get_title();
let subtitle = mediaInfo.get_uri() || null;
if(subtitle && subtitle.startsWith('file://')) {
subtitle = GLib.path_get_basename(
GLib.filename_from_uri(subtitle)[0]
);
}
if(!title) {
title = (!subtitle)
? this.defaultTitle
: (subtitle.includes('.'))
? subtitle.split('.').slice(0, -1).join('.')
: subtitle;
subtitle = null;
}
this.titleLabel.label = title;
this.subtitleLabel.visible = (subtitle !== null);
if(subtitle)
this.subtitleLabel.label = subtitle;
}
_createWidgetForWindow(window)
{
let box = new Gtk.Box ({
orientation: Gtk.Orientation.VERTICAL,
valign: Gtk.Align.CENTER,
});
this.titleLabel = new Gtk.Label({
halign: Gtk.Align.CENTER,
single_line_mode: true,
ellipsize: Pango.EllipsizeMode.END,
width_chars: 5,
});
this.titleLabel.add_css_class('title');
this.titleLabel.set_parent(box);
window.bind_property('title', this.titleLabel, 'label',
GObject.BindingFlags.SYNC_CREATE
);
this.subtitleLabel = new Gtk.Label({
halign: Gtk.Align.CENTER,
single_line_mode: true,
ellipsize: Pango.EllipsizeMode.END,
});
this.subtitleLabel.add_css_class('subtitle');
this.subtitleLabel.set_parent(box);
this.subtitleLabel.visible = false;
return box;
}
});

View File

@@ -19,7 +19,7 @@ class ClapperInterface extends Gtk.Grid
}; };
Object.assign(this, defaults, opts); Object.assign(this, defaults, opts);
this.controlsInVideo = false; this.fullscreenMode = false;
this.lastVolumeValue = null; this.lastVolumeValue = null;
this.lastPositionValue = 0; this.lastPositionValue = 0;
this.lastRevealerEventTime = 0; this.lastRevealerEventTime = 0;
@@ -33,29 +33,32 @@ class ClapperInterface extends Gtk.Grid
this.revealerBottom = new Revealers.RevealerBottom(); this.revealerBottom = new Revealers.RevealerBottom();
this.controls = new Controls(); this.controls = new Controls();
this.videoBox.get_style_context().add_class('videobox'); this.videoBox.add_css_class('videobox');
this.videoBox.pack_start(this.overlay, true, true, 0); this.videoBox.append(this.overlay);
this.attach(this.videoBox, 0, 0, 1, 1); this.attach(this.videoBox, 0, 0, 1, 1);
this.attach(this.controls, 0, 1, 1, 1); this.attach(this.controls, 0, 1, 1, 1);
this.destroySignal = this.connect('destroy', this._onDestroy.bind(this));
} }
addPlayer(player) addPlayer(player)
{ {
this._player = player; this._player = player;
this._player.widget.expand = true; this._player.widget.vexpand = true;
this._player.widget.hexpand = true;
this._player.connect('state-changed', this._onPlayerStateChanged.bind(this)); this._player.connect('state-changed', this._onPlayerStateChanged.bind(this));
this._player.connect('volume-changed', this._onPlayerVolumeChanged.bind(this)); this._player.connect('volume-changed', this._onPlayerVolumeChanged.bind(this));
this._player.connect('duration-changed', this._onPlayerDurationChanged.bind(this)); this._player.connect('duration-changed', this._onPlayerDurationChanged.bind(this));
this._player.connect('position-updated', this._onPlayerPositionUpdated.bind(this)); this._player.connect('position-updated', this._onPlayerPositionUpdated.bind(this));
this._player.connectWidget( this._player.scrollController.connect(
'scroll-event', (self, event) => this.controls._onScrollEvent(event) 'scroll', (ctl, dx, dy) => this.controls._onScroll(ctl, dx, dy)
); );
this.controls.togglePlayButton.connect( this.controls.togglePlayButton.connect(
'clicked', this._onControlsTogglePlayClicked.bind(this) 'clicked', this._onControlsTogglePlayClicked.bind(this)
); );
this.controls.positionScale.connect( this.scaleSig = this.controls.positionScale.connect(
'value-changed', this._onControlsPositionChanged.bind(this) 'value-changed', this._onControlsPositionChanged.bind(this)
); );
this.controls.volumeScale.connect( this.controls.volumeScale.connect(
@@ -70,9 +73,9 @@ class ClapperInterface extends Gtk.Grid
this.controls.connect( this.controls.connect(
'visualization-change-requested', this._onVisualizationChangeRequested.bind(this) 'visualization-change-requested', this._onVisualizationChangeRequested.bind(this)
); );
this.revealerTop.connect('event-after', (self, event) => this._player.widget.event(event)); //this.revealerTop.connect('event-after', (self, event) => this._player.widget.event(event));
this.overlay.add(this._player.widget); this.overlay.set_child(this._player.widget);
this.overlay.add_overlay(this.revealerTop); this.overlay.add_overlay(this.revealerTop);
this.overlay.add_overlay(this.revealerBottom); this.overlay.add_overlay(this.revealerBottom);
@@ -98,32 +101,33 @@ class ClapperInterface extends Gtk.Grid
this[`revealer${pos}`].showChild(isShow); this[`revealer${pos}`].showChild(isShow);
} }
setControlsOnVideo(isOnVideo) setFullscreenMode(isFullscreen)
{ {
if(this.controlsInVideo === isOnVideo) if(this.fullscreenMode === isFullscreen)
return; return;
if(isOnVideo) { if(isFullscreen) {
this.remove(this.controls); this.remove(this.controls);
this.controls.pack_start(this.controls.unfullscreenButton.box, false, false, 0); this.revealerBottom.append(this.controls);
this.revealerBottom.addWidget(this.controls);
} }
else { else {
this.revealerBottom.removeWidget(this.controls); this.revealerBottom.remove(this.controls);
this.controls.remove(this.controls.unfullscreenButton.box);
this.attach(this.controls, 0, 1, 1, 1); this.attach(this.controls, 0, 1, 1, 1);
} }
this.controlsInVideo = isOnVideo; this.controls.setFullscreenMode(isFullscreen);
debug(`placed controls in overlay: ${isOnVideo}`); this.showControls(isFullscreen);
this.fullscreenMode = isFullscreen;
debug(`interface in fullscreen mode: ${isFullscreen}`);
} }
updateMediaTracks() updateMediaTracks()
{ {
let mediaInfo = this._player.get_media_info(); let mediaInfo = this._player.get_media_info();
// set titlebar media title and path /* Set titlebar media title and path */
this.updateHeaderBar(mediaInfo); this.updateTitles(mediaInfo);
// we can also check if video is "live" or "seekable" (right now unused) // we can also check if video is "live" or "seekable" (right now unused)
// it might be a good idea to hide position seek bar and disable seeking // it might be a good idea to hide position seek bar and disable seeking
@@ -208,7 +212,7 @@ class ClapperInterface extends Gtk.Grid
} }
continue; continue;
} }
this.controls.addRadioButtons( this.controls.addCheckButtons(
this.controls[`${type}TracksButton`].popoverBox, this.controls[`${type}TracksButton`].popoverBox,
parsedInfo[`${type}Tracks`], parsedInfo[`${type}Tracks`],
activeId activeId
@@ -220,32 +224,12 @@ class ClapperInterface extends Gtk.Grid
} }
} }
updateHeaderBar(mediaInfo) updateTitles(mediaInfo)
{ {
if(!this.headerBar) if(this.headerBar)
return; this.headerBar.updateHeaderBar(mediaInfo);
let title = mediaInfo.get_title(); this.revealerTop.setMediaTitle(this.headerBar.titleLabel.label);
let subtitle = mediaInfo.get_uri() || null;
if(subtitle.startsWith('file://')) {
subtitle = GLib.filename_from_uri(subtitle)[0];
subtitle = GLib.path_get_basename(subtitle);
}
if(!title) {
title = (!subtitle)
? this.defaultTitle
: (subtitle.includes('.'))
? subtitle.split('.').slice(0, -1).join('.')
: subtitle;
subtitle = null;
}
this.headerBar.set_title(title);
this.headerBar.set_subtitle(subtitle);
this.revealerTop.setMediaTitle(title);
} }
updateTime() updateTime()
@@ -281,7 +265,7 @@ class ClapperInterface extends Gtk.Grid
}); });
}); });
this.controls.addRadioButtons( this.controls.addCheckButtons(
this.controls.visualizationsButton.popoverBox, this.controls.visualizationsButton.popoverBox,
parsedVisArr, parsedVisArr,
null null
@@ -300,8 +284,8 @@ class ClapperInterface extends Gtk.Grid
_onTrackChangeRequested(self, type, activeId) _onTrackChangeRequested(self, type, activeId)
{ {
// reenabling audio is slow (as expected), /* Reenabling audio is slow (as expected),
// so it is better to toggle mute instead * so it is better to toggle mute instead */
if(type === 'audio') { if(type === 'audio') {
if(activeId < 0) if(activeId < 0)
return this._player.set_mute(true); return this._player.set_mute(true);
@@ -313,8 +297,8 @@ class ClapperInterface extends Gtk.Grid
} }
if(activeId < 0) { if(activeId < 0) {
// disabling video leaves last frame frozen, /* Disabling video leaves last frame frozen,
// so we hide it by making it transparent * so we hide it by making it transparent */
if(type === 'video') if(type === 'video')
this._player.widget.set_opacity(0); this._player.widget.set_opacity(0);
@@ -365,10 +349,10 @@ class ClapperInterface extends Gtk.Grid
case GstPlayer.PlayerState.STOPPED: case GstPlayer.PlayerState.STOPPED:
this.needsTracksUpdate = true; this.needsTracksUpdate = true;
case GstPlayer.PlayerState.PAUSED: case GstPlayer.PlayerState.PAUSED:
this.controls.togglePlayButton.setPlayImage(); this.controls.togglePlayButton.setPrimaryIcon();
break; break;
case GstPlayer.PlayerState.PLAYING: case GstPlayer.PlayerState.PLAYING:
this.controls.togglePlayButton.setPauseImage(); this.controls.togglePlayButton.setSecondaryIcon();
if(this.needsTracksUpdate) { if(this.needsTracksUpdate) {
this.needsTracksUpdate = false; this.needsTracksUpdate = false;
this.updateMediaTracks(); this.updateMediaTracks();
@@ -449,7 +433,7 @@ class ClapperInterface extends Gtk.Grid
this.lastPositionValue = positionSeconds; this.lastPositionValue = positionSeconds;
this._player.seek_seconds(positionSeconds); this._player.seek_seconds(positionSeconds);
if(this.controls.fullscreenMode) if(this.fullscreenMode)
this.updateTime(); this.updateTime();
} }
@@ -468,14 +452,10 @@ class ClapperInterface extends Gtk.Grid
: 'overamplified'; : 'overamplified';
let iconName = `audio-volume-${icon}-symbolic`; let iconName = `audio-volume-${icon}-symbolic`;
if(this.controls.volumeButton.icon_name !== iconName)
if(this.controls.volumeButton.image.icon_name !== iconName)
{ {
debug(`set volume icon: ${icon}`); debug(`set volume icon: ${icon}`);
this.controls.volumeButton.image.set_from_icon_name( this.controls.volumeButton.set_icon_name(iconName);
iconName,
this.controls.volumeButton.image.icon_size
);
} }
if(volume === this.lastVolumeValue) if(volume === this.lastVolumeValue)
@@ -484,4 +464,10 @@ class ClapperInterface extends Gtk.Grid
this.lastVolumeValue = volume; this.lastVolumeValue = volume;
this._player.set_volume(volume); this._player.set_volume(volume);
} }
_onDestroy()
{
this.disconnect(this.destroySignal);
this.controls.emit('destroy');
}
}); });

View File

@@ -1,4 +1,4 @@
const { Gio, GLib, GObject, Gst, GstPlayer } = imports.gi; const { Gio, GLib, GObject, Gst, GstPlayer, Gtk } = imports.gi;
const ByteArray = imports.byteArray; const ByteArray = imports.byteArray;
const Debug = imports.clapper_src.debug; const Debug = imports.clapper_src.debug;
@@ -21,7 +21,16 @@ class ClapperPlayer extends GstPlayer.Player
if(!Gst.is_initialized()) if(!Gst.is_initialized())
Gst.init(null); Gst.init(null);
let gtkglsink = Gst.ElementFactory.make('gtkglsink', null); let plugin = 'gtk4glsink';
let gtkglsink = Gst.ElementFactory.make(plugin, null);
if(!gtkglsink) {
return debug(new Error(
`Could not load "${plugin}".`
+ ' Do you have gstreamer-plugins-good-gtk4 installed?'
));
}
let glsinkbin = Gst.ElementFactory.make('glsinkbin', null); let glsinkbin = Gst.ElementFactory.make('glsinkbin', null);
glsinkbin.sink = gtkglsink; glsinkbin.sink = gtkglsink;
@@ -35,8 +44,7 @@ class ClapperPlayer extends GstPlayer.Player
video_renderer: renderer video_renderer: renderer
}); });
// assign elements to player for later access /* Assign elements to player for later access */
// and make sure that GJS will not free them early
this.gtkglsink = gtkglsink; this.gtkglsink = gtkglsink;
this.glsinkbin = glsinkbin; this.glsinkbin = glsinkbin;
this.dispatcher = dispatcher; this.dispatcher = dispatcher;
@@ -60,6 +68,7 @@ class ClapperPlayer extends GstPlayer.Player
this.set_config(config); this.set_config(config);
this.set_mute(false); this.set_mute(false);
this.set_plugin_rank('vah264dec', 300);
this.loop = GLib.MainLoop.new(null, false); this.loop = GLib.MainLoop.new(null, false);
this.run_loop = opts.run_loop || false; this.run_loop = opts.run_loop || false;
@@ -71,9 +80,28 @@ class ClapperPlayer extends GstPlayer.Player
this._trackId = 0; this._trackId = 0;
this.playlist_ext = opts.playlist_ext || 'claps'; this.playlist_ext = opts.playlist_ext || 'claps';
this.keyController = new Gtk.EventControllerKey();
this.motionController = new Gtk.EventControllerMotion();
this.scrollController = new Gtk.EventControllerScroll();
this.clickGesture = new Gtk.GestureClick();
this.dragGesture = new Gtk.GestureDrag();
this.scrollController.set_flags(
Gtk.EventControllerScrollFlags.BOTH_AXES
);
this.clickGesture.set_button(0);
this.widget.add_controller(this.keyController);
this.widget.add_controller(this.motionController);
this.widget.add_controller(this.scrollController);
this.widget.add_controller(this.clickGesture);
this.widget.add_controller(this.dragGesture);
this.connect('state-changed', this._onStateChanged.bind(this)); this.connect('state-changed', this._onStateChanged.bind(this));
this.connect('uri-loaded', this._onUriLoaded.bind(this)); this.connect('uri-loaded', this._onUriLoaded.bind(this));
this.connect('end-of-stream', this._onStreamEnded.bind(this)); this.connect('end-of-stream', this._onStreamEnded.bind(this));
this.connect('warning', this._onPlayerWarning.bind(this));
this.connect('error', this._onPlayerError.bind(this));
this.connectWidget('destroy', this._onWidgetDestroy.bind(this)); this.connectWidget('destroy', this._onWidgetDestroy.bind(this));
} }
@@ -241,6 +269,16 @@ class ClapperPlayer extends GstPlayer.Player
this.loop.run(); this.loop.run();
} }
_onPlayerWarning(self, error)
{
debug(error.message, 'LEVEL_WARNING');
}
_onPlayerError(self, error)
{
debug(error);
}
_onWidgetDestroy() _onWidgetDestroy()
{ {
while(this._widgetSignals.length) while(this._widgetSignals.length)

View File

@@ -10,6 +10,13 @@ class ClapperCustomRevealer extends Gtk.Revealer
{ {
_init(opts) _init(opts)
{ {
opts = opts || {};
let defaults = {
visible: false,
};
Object.assign(opts, defaults);
super._init(opts); super._init(opts);
this.revealerName = ''; this.revealerName = '';
@@ -19,10 +26,7 @@ class ClapperCustomRevealer extends Gtk.Revealer
{ {
if(isReveal) { if(isReveal) {
this._clearTimeout(); this._clearTimeout();
if(!this.visible) this.set_visible(isReveal);
this.show();
this._setShowTimeout();
} }
else else
this._setHideTimeout(); this._setHideTimeout();
@@ -30,68 +34,39 @@ class ClapperCustomRevealer extends Gtk.Revealer
this._timedReveal(isReveal, REVEAL_TIME); this._timedReveal(isReveal, REVEAL_TIME);
} }
show()
{
if(this.visible)
return;
// Decreased size = lower CPU usage
this._setTopAlign('START');
super.show();
debug(`showing ${this.revealerName} revealer in drawing area`);
}
hide()
{
if(!this.visible)
return;
super.hide();
debug(`removed ${this.revealerName} revealer from drawing area`);
}
showChild(isReveal) showChild(isReveal)
{ {
this._clearTimeout(); this._clearTimeout();
this.set_visible(isReveal);
if(isReveal)
this.show();
else if(!isReveal)
this.hide();
this._timedReveal(isReveal, 0); this._timedReveal(isReveal, 0);
} }
set_visible(isVisible)
{
if(this.visible === isVisible)
return false;
super.set_visible(isVisible);
debug(`${this.revealerName} revealer visible: ${isVisible}`);
return true;
}
_timedReveal(isReveal, time) _timedReveal(isReveal, time)
{ {
this.set_transition_duration(time); this.set_transition_duration(time);
this.set_reveal_child(isReveal); this.set_reveal_child(isReveal);
} }
// Drawing revealers on top of video frames /* Drawing revealers on top of video frames
// increases CPU usage, so we hide them * increases CPU usage, so we hide them */
_setHideTimeout() _setHideTimeout()
{ {
this._clearTimeout(); this._clearTimeout();
this._setTopAlign('FILL');
this._revealerTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, REVEAL_TIME + 20, () => { this._revealerTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, REVEAL_TIME + 20, () => {
this._revealerTimeout = null; this._revealerTimeout = null;
this.hide(); this.set_visible(false);
return GLib.SOURCE_REMOVE;
});
}
_setShowTimeout()
{
this._clearTimeout();
this._setTopAlign('FILL');
this._revealerTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, REVEAL_TIME + 20, () => {
this._revealerTimeout = null;
this._setTopAlign('START');
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
}); });
@@ -105,17 +80,6 @@ class ClapperCustomRevealer extends Gtk.Revealer
GLib.source_remove(this._revealerTimeout); GLib.source_remove(this._revealerTimeout);
this._revealerTimeout = null; this._revealerTimeout = null;
} }
_setTopAlign(align)
{
if(
this.revealerName !== 'top'
|| this.valign === Gtk.Align[align]
)
return;
this.valign = Gtk.Align[align];
}
}); });
var RevealerTop = GObject.registerClass( var RevealerTop = GObject.registerClass(
@@ -130,7 +94,7 @@ class ClapperRevealerTop extends CustomRevealer
}); });
this.revealerName = 'top'; this.revealerName = 'top';
/*
this.set_events( this.set_events(
Gdk.EventMask.BUTTON_PRESS_MASK Gdk.EventMask.BUTTON_PRESS_MASK
| Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK
@@ -141,7 +105,7 @@ class ClapperRevealerTop extends CustomRevealer
| Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.ENTER_NOTIFY_MASK
| Gdk.EventMask.LEAVE_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK
); );
*/
let initTime = GLib.DateTime.new_now_local().format('%X'); let initTime = GLib.DateTime.new_now_local().format('%X');
this.timeFormat = (initTime.length > 8) this.timeFormat = (initTime.length > 8)
? '%I:%M %p' ? '%I:%M %p'
@@ -150,13 +114,13 @@ class ClapperRevealerTop extends CustomRevealer
this.revealerGrid = new Gtk.Grid({ this.revealerGrid = new Gtk.Grid({
column_spacing: 8 column_spacing: 8
}); });
let gridContext = this.revealerGrid.get_style_context(); this.revealerGrid.add_css_class('osd');
gridContext.add_class('osd'); this.revealerGrid.add_css_class('reavealertop');
gridContext.add_class('reavealertop');
this.mediaTitle = new Gtk.Label({ this.mediaTitle = new Gtk.Label({
ellipsize: Pango.EllipsizeMode.END, ellipsize: Pango.EllipsizeMode.END,
expand: true, vexpand: true,
hexpand: true,
margin_top: 14, margin_top: 14,
margin_start: 12, margin_start: 12,
xalign: 0, xalign: 0,
@@ -169,17 +133,16 @@ class ClapperRevealerTop extends CustomRevealer
yalign: 0, yalign: 0,
}; };
this.currentTime = new Gtk.Label(timeLabelOpts); this.currentTime = new Gtk.Label(timeLabelOpts);
this.currentTime.get_style_context().add_class('osdtime'); this.currentTime.add_css_class('osdtime');
this.endTime = new Gtk.Label(timeLabelOpts); this.endTime = new Gtk.Label(timeLabelOpts);
this.endTime.get_style_context().add_class('osdendtime'); this.endTime.add_css_class('osdendtime');
this.revealerGrid.attach(this.mediaTitle, 0, 0, 1, 1); this.revealerGrid.attach(this.mediaTitle, 0, 0, 1, 1);
this.revealerGrid.attach(this.currentTime, 1, 0, 1, 1); this.revealerGrid.attach(this.currentTime, 1, 0, 1, 1);
this.revealerGrid.attach(this.endTime, 1, 0, 1, 1); this.revealerGrid.attach(this.endTime, 1, 0, 1, 1);
this.add(this.revealerGrid); this.set_child(this.revealerGrid);
this.revealerGrid.show_all();
} }
setMediaTitle(title) setMediaTitle(title)
@@ -195,8 +158,8 @@ class ClapperRevealerTop extends CustomRevealer
this.currentTime.set_label(now); this.currentTime.set_label(now);
this.endTime.set_label(end); this.endTime.set_label(end);
// Make sure that next timeout is always run after clock changes, /* Make sure that next timeout is always run after clock changes,
// by delaying it for additional few milliseconds * by delaying it for additional few milliseconds */
let nextUpdate = 60002 - parseInt(currTime.get_seconds() * 1000); let nextUpdate = 60002 - parseInt(currTime.get_seconds() * 1000);
debug(`updated current time: ${now}`); debug(`updated current time: ${now}`);
@@ -217,19 +180,48 @@ class ClapperRevealerBottom extends CustomRevealer
this.revealerName = 'bottom'; this.revealerName = 'bottom';
this.revealerBox = new Gtk.Box(); this.revealerBox = new Gtk.Box();
this.revealerBox.get_style_context().add_class('osd'); this.revealerBox.add_css_class('osd');
this.add(this.revealerBox); this.set_child(this.revealerBox);
this.revealerBox.show_all();
} }
addWidget(widget) append(widget)
{ {
this.revealerBox.pack_start(widget, false, true, 0); this.revealerBox.append(widget);
} }
removeWidget(widget) remove(widget)
{ {
this.revealerBox.remove(widget); this.revealerBox.remove(widget);
} }
set_visible(isVisible)
{
let isChange = super.set_visible(isVisible);
if(!isChange) return;
let parent = this.get_parent();
let playerWidget = parent.get_first_child();
if(!playerWidget) return;
if(isVisible) {
let box = this.get_first_child();
if(!box) return;
let controls = box.get_first_child();
if(!controls) return;
let togglePlayButton = controls.get_first_child();
if(togglePlayButton) {
togglePlayButton.grab_focus();
debug('focus moved to toggle play button');
}
playerWidget.set_can_focus(false);
}
else {
playerWidget.set_can_focus(true);
playerWidget.grab_focus();
debug('focus moved to player widget');
}
}
}); });

View File

@@ -13,13 +13,11 @@ var Window = GObject.registerClass({
super._init({ super._init({
application: application, application: application,
title: title || 'Clapper', title: title || 'Clapper',
border_width: 0,
resizable: true, resizable: true,
window_position: Gtk.WindowPosition.CENTER, destroy_with_parent: true,
width_request: 960,
height_request: 642
}); });
this.isFullscreen = false; this.isFullscreen = false;
this.mapSignal = this.connect('map', this._onMap.bind(this));
} }
toggleFullscreen() toggleFullscreen()
@@ -28,13 +26,10 @@ var Window = GObject.registerClass({
this[`${un}fullscreen`](); this[`${un}fullscreen`]();
} }
vfunc_window_state_event(event) _onStateNotify(toplevel)
{ {
super.vfunc_window_state_event(event);
let isFullscreen = Boolean( let isFullscreen = Boolean(
event.new_window_state toplevel.state & Gdk.ToplevelState.FULLSCREEN
& Gdk.WindowState.FULLSCREEN
); );
if(this.isFullscreen === isFullscreen) if(this.isFullscreen === isFullscreen)
@@ -43,4 +38,12 @@ var Window = GObject.registerClass({
this.isFullscreen = isFullscreen; this.isFullscreen = isFullscreen;
this.emit('fullscreen-changed', this.isFullscreen); this.emit('fullscreen-changed', this.isFullscreen);
} }
_onMap()
{
this.disconnect(this.mapSignal);
let surface = this.get_surface();
surface.connect('notify::state', this._onStateNotify.bind(this));
}
}); });

View File

@@ -7,13 +7,15 @@ scale marks {
font-size: 22px; font-size: 22px;
font-weight: 500; font-weight: 500;
} }
.osd .playercontrols {
-gtk-icon-size: 24px;
}
.osd button { .osd button {
margin: 2px;
min-width: 36px; min-width: 36px;
min-height: 36px; min-height: 36px;
} }
.osd scale trough highlight { .osd scale trough highlight {
min-width: 6px; min-width: 0px;
min-height: 6px; min-height: 6px;
} }
.osd radio { .osd radio {
@@ -22,12 +24,23 @@ scale marks {
min-width: 18px; min-width: 18px;
min-height: 18px; min-height: 18px;
} }
.playbackicon {
-gtk-icon-size: 20px;
}
.osd .playbackicon {
-gtk-icon-size: 28px;
}
.labelbutton {
font-family: 'Cantarell', 'Noto Sans', sans-serif;
font-variant-numeric: tabular-nums;
font-weight: 600;
}
.videobox { .videobox {
background: black; background: black;
} }
.reavealertop { .reavealertop {
min-height: 100px; min-height: 90px;
box-shadow: inset 0px 200px 10px -124px rgba(0,0,0,0.4); box-shadow: inset 0px 200px 10px -124px rgba(0,0,0,0.3);
font-family: 'Cantarell', 'Noto Sans', sans-serif; font-family: 'Cantarell', 'Noto Sans', sans-serif;
font-size: 30px; font-size: 30px;
font-weight: 500; font-weight: 500;
@@ -43,25 +56,13 @@ scale marks {
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;
} }
.osd .labelbutton {
/* Position Scale */
.positionscale value {
font-weight: 600;
color: currentColor;
}
.positionscale trough highlight {
min-height: 4px;
}
.osd .positionscale value {
font-size: 24px; font-size: 24px;
} }
.positionscale contents {
margin-left: 4px; /* Position Scale */
margin-right: 2px; .positionscale trough highlight {
} min-height: 4px;
.osd .positionscale contents {
margin-left: 8px;
margin-right: 2px;
} }
.osd .positionscale trough slider { .osd .positionscale trough slider {
color: transparent; color: transparent;
@@ -75,12 +76,10 @@ scale marks {
/* Volume Scale */ /* Volume Scale */
.volumescale { .volumescale {
margin-left: 4px;
min-height: 180px; min-height: 180px;
} }
.osd .volumescale { .osd .volumescale {
margin: 6px; margin: 6px;
margin-left: 10px;
min-height: 280px; min-height: 280px;
} }
.volumescale marks label { .volumescale marks label {
@@ -91,3 +90,6 @@ scale marks {
.osd .volumescale marks label { .osd .volumescale marks label {
margin-bottom: -8px; margin-bottom: -8px;
} }
.osd .volumescale trough highlight {
min-width: 6px;
}

View File

@@ -1,5 +1,5 @@
imports.gi.versions.Gdk = '3.0'; imports.gi.versions.Gdk = '4.0';
imports.gi.versions.Gtk = '3.0'; imports.gi.versions.Gtk = '4.0';
imports.searchPath.unshift('@importspath@'); imports.searchPath.unshift('@importspath@');
const ClapperSrc = imports.clapper_src; const ClapperSrc = imports.clapper_src;

View File

@@ -1,5 +1,5 @@
imports.gi.versions.Gdk = '3.0'; imports.gi.versions.Gdk = '4.0';
imports.gi.versions.Gtk = '3.0'; imports.gi.versions.Gtk = '4.0';
const { Gst } = imports.gi; const { Gst } = imports.gi;
const { App } = imports.clapper_src.app; const { App } = imports.clapper_src.app;

View File

@@ -27,13 +27,13 @@ arch=(any)
url="https://github.com/Rafostar/clapper" url="https://github.com/Rafostar/clapper"
license=("GPL-3.0") license=("GPL-3.0")
depends=( depends=(
"gtk3>=3.19.4" "gtk4>=3.99.2"
"hicolor-icon-theme" "hicolor-icon-theme"
"gjs" "gjs"
"gst-plugins-base-libs" "gst-plugins-base-libs"
"gst-plugins-good" "gst-plugins-good"
"gst-plugins-bad-libs>=1.16.0" "gst-plugins-bad-libs>=1.18.0"
"gst-plugin-gtk" "gst-plugin-gtk4"
) )
makedepends=( makedepends=(
"meson>=0.50" "meson>=0.50"
@@ -44,7 +44,7 @@ optdepends=(
"gst-libav: Popular video decoders" "gst-libav: Popular video decoders"
"gstreamer-vaapi: Intel/AMD video acceleration" "gstreamer-vaapi: Intel/AMD video acceleration"
) )
source=("${pkgname%-git}::git+https://github.com/Rafostar/${pkgname%-git}.git#branch=master") source=("${pkgname%-git}::git+https://github.com/Rafostar/${pkgname%-git}.git")
provides=("${pkgname%-git}") provides=("${pkgname%-git}")
replaces=("${pkgname%-git}") replaces=("${pkgname%-git}")
conflicts=("${pkgname%-git}") conflicts=("${pkgname%-git}")

View File

@@ -14,14 +14,14 @@ Build-Depends: debhelper (>= 10),
Package: clapper Package: clapper
Architecture: all Architecture: all
Depends: gjs (>= 1.50), Depends: gjs (>= 1.50),
gir1.2-gtk-3.0 (>= 3.19), gir1.2-gtk-4.0 (>= 3.99.2),
hicolor-icon-theme, hicolor-icon-theme,
libgstreamer1.0-0, libgstreamer1.0-0,
gstreamer1.0-plugins-base, gstreamer1.0-plugins-base,
gstreamer1.0-plugins-good, gstreamer1.0-plugins-good,
gstreamer1.0-plugins-bad (>= 1.16), gstreamer1.0-plugins-bad (>= 1.18),
gstreamer1.0-gl, gstreamer1.0-gl,
gstreamer1.0-gtk3 gstreamer1.0-gtk4
Recommends: gstreamer1.0-libav, Recommends: gstreamer1.0-libav,
gstreamer1.0-pulseaudio gstreamer1.0-pulseaudio
Suggests: gstreamer-plugins-ugly, Suggests: gstreamer-plugins-ugly,

View File

@@ -19,8 +19,8 @@
%global appname com.github.rafostar.Clapper %global appname com.github.rafostar.Clapper
%global gst_version 1.16.0 %global gst_version 1.18.0
%global gtk3_version 3.19.4 %global gtk4_version 3.99.2
Name: clapper Name: clapper
Version: 0.0.0 Version: 0.0.0
@@ -39,7 +39,7 @@ BuildRequires: desktop-file-utils
BuildRequires: hicolor-icon-theme BuildRequires: hicolor-icon-theme
Requires: gjs Requires: gjs
Requires: gtk3 >= %{gtk3_version} Requires: gtk4 >= %{gtk4_version}
Requires: hicolor-icon-theme Requires: hicolor-icon-theme
%if 0%{?suse_version} %if 0%{?suse_version}
@@ -51,7 +51,7 @@ BuildRequires: update-desktop-files
Requires: gstreamer Requires: gstreamer
Requires: gstreamer-plugins-base Requires: gstreamer-plugins-base
Requires: gstreamer-plugins-good Requires: gstreamer-plugins-good
Requires: gstreamer-plugins-good-gtk Requires: gstreamer-plugins-good-gtk4
Requires: gstreamer-plugins-bad Requires: gstreamer-plugins-bad
Requires: libgstplayer-1_0-0 >= %{gst_version} Requires: libgstplayer-1_0-0 >= %{gst_version}
@@ -62,14 +62,12 @@ Recommends: gstreamer-plugins-libav
Suggests: gstreamer-plugins-ugly Suggests: gstreamer-plugins-ugly
# Intel/AMD video acceleration # Intel/AMD video acceleration
Suggests: gstreamer-plugins-vaapi Suggests: gstreamer-plugins-vaapi
%endif %else
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos_version}
BuildRequires: glibc-all-langpacks BuildRequires: glibc-all-langpacks
Requires: gstreamer1 Requires: gstreamer1
Requires: gstreamer1-plugins-base Requires: gstreamer1-plugins-base
Requires: gstreamer1-plugins-good Requires: gstreamer1-plugins-good
Requires: gstreamer1-plugins-good-gtk Requires: gstreamer1-plugins-good-gtk4
# Contains GstPlayer lib # Contains GstPlayer lib
Requires: gstreamer1-plugins-bad-free >= %{gst_version} Requires: gstreamer1-plugins-bad-free >= %{gst_version}
@@ -113,7 +111,11 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop
%{_datadir}/mime/packages/%{appname}.xml %{_datadir}/mime/packages/%{appname}.xml
%changelog %changelog
* Wed Oct 14 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-3
- Update to GTK4
* Sat Sep 19 22:02:00 CEST 2020 sp1rit - 0.0.0-2 * Sat Sep 19 22:02:00 CEST 2020 sp1rit - 0.0.0-2
- Added suse_update_desktop_file macro for SuSE packages - Added suse_update_desktop_file macro for SuSE packages
* Fri Sep 18 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-1 * Fri Sep 18 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-1
- Initial package - Initial package