diff --git a/data/com.github.rafostar.Clapper.gschema.xml b/data/com.github.rafostar.Clapper.gschema.xml
index ed1c57e2..f43b58f4 100644
--- a/data/com.github.rafostar.Clapper.gschema.xml
+++ b/data/com.github.rafostar.Clapper.gschema.xml
@@ -6,35 +6,31 @@
false
Automatically enter fullscreen when first file is loaded
-
- "restore"
- Mode used for startup volume value
+
+ false
+ Set custom volume value at startup
100
Custom initial volume value in percentage after startup
-
- false
- Keep showing last video frame after playback finishes
-
-
- false
- Automatically close the app after playback finishes
+
+ 0
+ What to do after playback finishes
-
-
- "normal"
+
+
+ 0
Mode used for seeking
10
Time amount to seek with single press of arrow keys
-
- "second"
- Unit to use with seeking value
+
+ 0
+ Unit ID to use with seeking value
true
@@ -50,13 +46,13 @@
-
+
0
Offset time for audio tracks relative to video (milliseconds)
-
+
0
Offset time for subtitle tracks relative to video (milliseconds)
@@ -95,7 +91,7 @@
- '[{"apply":false,"name":"vah264dec","rank":300}]'
+ '{}'
Custom values for GStreamer plugin ranking
@@ -108,8 +104,8 @@
false
Enable to use adaptive streaming for YouTube
-
- "hfr"
+
+ 1
Max YouTube video quality type
diff --git a/src/actions.js b/src/actions.js
index 53201880..1ac67648 100644
--- a/src/actions.js
+++ b/src/actions.js
@@ -1,5 +1,6 @@
const { Gtk } = imports.gi;
const Dialogs = imports.src.dialogs;
+const Prefs = imports.src.prefs;
const Misc = imports.src.misc;
var actions = {
@@ -42,7 +43,7 @@ function handleAction(action, window)
new Dialogs.UriDialog(window);
break;
case 'prefs':
- new Dialogs.PrefsDialog(window);
+ new Prefs.PrefsWindow(window);
break;
case 'shortcuts':
if(!window.get_help_overlay()) {
diff --git a/src/controls.js b/src/controls.js
index afd539bd..5eea6ff0 100644
--- a/src/controls.js
+++ b/src/controls.js
@@ -469,7 +469,7 @@ class ClapperControls extends Gtk.Box
scrollController.connect('scroll', clapperWidget._onScroll.bind(clapperWidget));
this.volumeButton.add_controller(scrollController);
- const initialVolume = (settings.get_string('volume-initial') === 'custom')
+ const initialVolume = (settings.get_boolean('volume-custom'))
? settings.get_int('volume-value') / 100
: settings.get_double('volume-last');
diff --git a/src/dialogs.js b/src/dialogs.js
index f7e16c9d..b5cefc63 100644
--- a/src/dialogs.js
+++ b/src/dialogs.js
@@ -3,8 +3,6 @@ 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;
const { debug } = Debug;
@@ -247,88 +245,6 @@ class ClapperResumeDialog extends Gtk.MessageDialog
}
});
-var PrefsDialog = GObject.registerClass(
-class ClapperPrefsDialog extends Gtk.Dialog
-{
- _init(window)
- {
- super._init({
- transient_for: window,
- destroy_with_parent: true,
- modal: true,
- title: 'Preferences',
- default_width: 460,
- default_height: 400,
- });
-
- const pages = [
- {
- title: 'Player',
- pages: [
- {
- title: 'General',
- widget: Prefs.GeneralPage,
- },
- {
- title: 'Behaviour',
- widget: Prefs.BehaviourPage,
- },
- {
- title: 'Audio',
- widget: Prefs.AudioPage,
- },
- {
- title: 'Subtitles',
- widget: Prefs.SubtitlesPage,
- },
- {
- title: 'Network',
- widget: Prefs.NetworkPage,
- },
- {
- title: 'YouTube',
- widget: Prefs.YouTubePage,
- }
- ]
- },
- {
- title: 'Advanced',
- pages: [
- {
- title: 'GStreamer',
- widget: Prefs.GStreamerPage,
- },
- {
- title: 'Tweaks',
- widget: Prefs.TweaksPage,
- }
- ]
- }
- ];
-
- const prefsNotebook = new PrefsBase.Notebook(pages);
- prefsNotebook.add_css_class('prefsnotebook');
-
- const area = this.get_content_area();
- area.append(prefsNotebook);
-
- this.closeSignal = this.connect('close-request', this._onCloseRequest.bind(this));
- this.show();
- }
-
- _onCloseRequest(dialog)
- {
- debug('closing prefs dialog');
-
- dialog.disconnect(this.closeSignal);
- this.closeSignal = null;
-
- const area = dialog.get_content_area();
- const notebook = area.get_first_child();
- notebook._onClose();
- }
-});
-
var AboutDialog = GObject.registerClass(
class ClapperAboutDialog extends Gtk.AboutDialog
{
diff --git a/src/player.js b/src/player.js
index d75b94d0..1d07d06d 100644
--- a/src/player.js
+++ b/src/player.js
@@ -44,7 +44,7 @@ class ClapperPlayer extends GstClapper.Clapper
this.ytClient = null;
this.playlistWidget = new PlaylistWidget();
- this.seek_done = true;
+ this.seekDone = true;
this.needsFastSeekRestore = false;
this.customVideoTitle = null;
@@ -70,6 +70,7 @@ class ClapperPlayer extends GstClapper.Clapper
set_and_bind_settings()
{
const settingsToSet = [
+ 'after-playback',
'seeking-mode',
'audio-offset',
'subtitle-offset',
@@ -81,7 +82,6 @@ class ClapperPlayer extends GstClapper.Clapper
this._onSettingsKeyChanged(settings, key);
const flag = Gio.SettingsBindFlags.GET;
- settings.bind('keep-last-frame', this.widget, 'keep-last-frame', flag);
settings.bind('subtitle-font', this.pipeline, 'subtitle-font-desc', flag);
}
@@ -96,25 +96,23 @@ class ClapperPlayer extends GstClapper.Clapper
set_all_plugins_ranks()
{
- let data = [];
+ let data = {};
/* Set empty plugin list if someone messed it externally */
try {
data = JSON.parse(settings.get_string('plugin-ranking'));
- if(!Array.isArray(data))
- throw new Error('plugin ranking data is not an array!');
+ if(Array.isArray(data)) {
+ data = {};
+ throw new Error('plugin ranking data is not an object');
+ }
}
catch(err) {
debug(err);
- settings.set_string('plugin-ranking', "[]");
+ settings.set_string('plugin-ranking', "{}");
}
- for(let plugin of data) {
- if(!plugin.apply || !plugin.name)
- continue;
-
- this.set_plugin_rank(plugin.name, plugin.rank);
- }
+ for(let plugin of Object.keys(data))
+ this.set_plugin_rank(plugin, data[plugin]);
}
set_plugin_rank(name, rank)
@@ -284,7 +282,7 @@ class ClapperPlayer extends GstClapper.Clapper
if(this.needsTocUpdate)
return;
- this.seek_done = false;
+ this.seekDone = false;
if(this.state === GstClapper.ClapperState.STOPPED)
this.pause();
@@ -292,8 +290,6 @@ class ClapperPlayer extends GstClapper.Clapper
if(position < 0)
position = 0;
- debug(`${this.seekingMode} seeking to position: ${position}`);
-
super.seek(position);
}
@@ -304,13 +300,12 @@ class ClapperPlayer extends GstClapper.Clapper
seek_chapter(seconds)
{
- if(this.seekingMode !== 'fast') {
+ if(this.seek_mode !== GstClapper.ClapperSeekMode.FAST) {
this.seek_seconds(seconds);
return;
}
this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT);
- this.seekingMode = 'normal';
this.needsFastSeekRestore = true;
this.seek_seconds(seconds);
@@ -318,20 +313,19 @@ class ClapperPlayer extends GstClapper.Clapper
adjust_position(isIncrease)
{
- this.seek_done = false;
+ this.seekDone = false;
const { controls } = this.widget.get_ancestor(Gtk.Grid);
const max = controls.positionAdjustment.get_upper();
- const seekingUnit = settings.get_string('seeking-unit');
let seekingValue = settings.get_int('seeking-value');
- switch(seekingUnit) {
- case 'minute':
- seekingValue *= 60;
+ switch(settings.get_int('seeking-unit')) {
+ case 2: /* Percentage */
+ seekingValue *= max / 100;
break;
- case 'percentage':
- seekingValue = max * seekingValue / 100;
+ case 1: /* Minute */
+ seekingValue *= 60;
break;
default:
break;
@@ -538,16 +532,15 @@ class ClapperPlayer extends GstClapper.Clapper
const clapperWidget = player.widget.get_ancestor(Gtk.Grid);
if(!clapperWidget) return;
- if(!this.seek_done && state !== GstClapper.ClapperState.BUFFERING) {
+ if(!this.seekDone && state !== GstClapper.ClapperState.BUFFERING) {
clapperWidget.updateTime();
if(this.needsFastSeekRestore) {
this.set_seek_mode(GstClapper.ClapperSeekMode.FAST);
- this.seekingMode = 'fast';
this.needsFastSeekRestore = false;
}
- this.seek_done = true;
+ this.seekDone = true;
debug('seeking finished');
clapperWidget._onPlayerPositionUpdated(this, this.position);
@@ -566,7 +559,8 @@ class ClapperPlayer extends GstClapper.Clapper
if(this.playlistWidget._handleStreamEnded(player))
return;
- if(settings.get_boolean('close-auto')) {
+ /* After playback equal 2 means close the app */
+ if(settings.get_int('after-playback') === 2) {
/* Stop will be automatically called soon afterwards */
this.quitOnStop = true;
this._performCloseCleanup(this.widget.get_root());
@@ -640,16 +634,18 @@ class ClapperPlayer extends GstClapper.Clapper
let root, value, action;
switch(key) {
+ case 'after-playback':
+ this.widget.keep_last_frame = (settings.get_int(key) === 1);
+ break;
case 'seeking-mode':
- this.seekingMode = settings.get_string('seeking-mode');
- switch(this.seekingMode) {
- case 'fast':
+ switch(settings.get_int(key)) {
+ case 2: /* Fast */
this.set_seek_mode(GstClapper.ClapperSeekMode.FAST);
break;
- case 'accurate':
+ case 1: /* Accurate */
this.set_seek_mode(GstClapper.ClapperSeekMode.ACCURATE);
break;
- default:
+ default: /* Normal */
this.set_seek_mode(GstClapper.ClapperSeekMode.DEFAULT);
break;
}
@@ -669,12 +665,12 @@ class ClapperPlayer extends GstClapper.Clapper
root[action + '_css_class'](gpuClass);
break;
case 'audio-offset':
- value = Math.round(settings.get_double(key) * -Gst.MSECOND);
+ value = Math.round(settings.get_int(key) * -Gst.MSECOND);
this.set_audio_video_offset(value);
debug(`set audio-video offset: ${value}`);
break;
case 'subtitle-offset':
- value = Math.round(settings.get_double(key) * -Gst.MSECOND);
+ value = Math.round(settings.get_int(key) * -Gst.MSECOND);
this.set_subtitle_video_offset(value);
debug(`set subtitle-video offset: ${value}`);
break;
diff --git a/src/prefs.js b/src/prefs.js
index 6d4047fb..d2a657d8 100644
--- a/src/prefs.js
+++ b/src/prefs.js
@@ -1,354 +1,475 @@
-const { GObject, Gst, Gtk, Pango } = imports.gi;
+const { Adw, GObject, Gio, Gst, Gtk } = imports.gi;
+const Debug = imports.src.debug;
const Misc = imports.src.misc;
-const PrefsBase = imports.src.prefsBase;
+const { debug } = Debug;
const { settings } = Misc;
/* PlayFlags are not exported through GI */
Gst.PlayFlags = {
- VIDEO: 1,
- AUDIO: 2,
- TEXT: 4,
- VIS: 8,
- SOFT_VOLUME: 16,
- NATIVE_AUDIO: 32,
- NATIVE_VIDEO: 64,
- DOWNLOAD: 128,
- BUFFERING: 256,
- DEINTERLACE: 512,
- SOFT_COLORBALANCE: 1024,
- FORCE_FILTERS: 2048,
- FORCE_SW_DECODERS: 4096,
+ VIDEO: 1,
+ AUDIO: 2,
+ TEXT: 4,
+ VIS: 8,
+ SOFT_VOLUME: 16,
+ NATIVE_AUDIO: 32,
+ NATIVE_VIDEO: 64,
+ DOWNLOAD: 128,
+ BUFFERING: 256,
+ DEINTERLACE: 512,
+ SOFT_COLORBALANCE: 1024,
+ FORCE_FILTERS: 2048,
+ FORCE_SW_DECODERS: 4096,
};
-var GeneralPage = GObject.registerClass(
-class ClapperGeneralPage extends PrefsBase.Grid
+const widgetOpts = {
+ halign: Gtk.Align.CENTER,
+ valign: Gtk.Align.CENTER,
+};
+
+function getCommonProps()
{
- _init()
+ return {
+ 'schema-name': GObject.ParamSpec.string(
+ 'schema-name',
+ 'GSchema setting name',
+ 'Name of the setting to bind',
+ GObject.ParamFlags.WRITABLE,
+ null
+ ),
+ };
+}
+
+const flags = Gio.SettingsBindFlags.DEFAULT;
+
+let PrefsActionRow = GObject.registerClass({
+ GTypeName: 'ClapperPrefsActionRow',
+ Properties: getCommonProps(),
+},
+class ClapperPrefsActionRow extends Adw.ActionRow
+{
+ _init(widget)
{
super._init();
- this.addTitle('Startup');
- this.addCheckButton('Auto enter fullscreen', 'fullscreen-auto');
+ this._schemaName = null;
+ this._bindProp = null;
- this.addTitle('Volume');
- const comboBox = this.addComboBoxText('Initial value', [
- ['restore', "Restore"],
- ['custom', "Custom"],
- ], 'volume-initial');
- const spinButton = this.addSpinButton('Value (percentage)', 0, 200, 'volume-value');
- this._onVolumeInitialChanged(spinButton, comboBox);
- 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');
+ this.add_suffix(widget);
+ this.set_activatable_widget(widget);
}
- _onVolumeInitialChanged(spinButton, comboBox)
+ set schema_name(value)
{
- const value = comboBox.get_active_id();
- spinButton.set_visible(value === 'custom');
- }
-});
-
-var BehaviourPage = GObject.registerClass(
-class ClapperBehaviourPage extends PrefsBase.Grid
-{
- _init()
- {
- super._init();
-
- this.addTitle('Seeking');
- this.addComboBoxText('Mode', [
- ['normal', "Normal"],
- ['accurate', "Accurate"],
- ['fast', "Fast"],
- ], 'seeking-mode');
- this.addComboBoxText('Unit', [
- ['second', "Second"],
- ['minute', "Minute"],
- ['percentage', "Percentage"],
- ], 'seeking-unit');
- this.addSpinButton('Value', 1, 99, 'seeking-value');
-
- this.addTitle('Resume');
- this.addCheckButton('Ask to resume last unfinished video', 'resume-enabled');
-
- this.addTitle('Floating Mode');
- this.addCheckButton('Show on all workspaces', 'floating-stick');
- }
-});
-
-var AudioPage = GObject.registerClass(
-class ClapperAudioPage extends PrefsBase.Grid
-{
- _init()
- {
- super._init();
-
- this.addTitle('Synchronization');
- this.addSpinButton('Offset (milliseconds)', -1000, 1000, 'audio-offset', 25);
-
- this.addTitle('Processing');
- this.addPlayFlagCheckButton('Only use native audio formats', Gst.PlayFlags.NATIVE_AUDIO);
- }
-});
-
-var SubtitlesPage = GObject.registerClass(
-class ClapperSubtitlesPage extends PrefsBase.Grid
-{
- _init()
- {
- super._init();
-
- /* FIXME: This should be moved to subtitles popup and displayed only when
- external subtitles were added for easier customization per video. */
- //this.addTitle('Synchronization');
- //this.addSpinButton('Offset (milliseconds)', -5000, 5000, 'subtitle-offset', 25);
-
- this.addTitle('External Subtitles');
- this.addFontButton('Default font', 'subtitle-font');
- }
-});
-
-var NetworkPage = GObject.registerClass(
-class ClapperNetworkPage extends PrefsBase.Grid
-{
- _init()
- {
- super._init();
-
- this.addTitle('Client');
- this.addPlayFlagCheckButton('Progressive download buffering', Gst.PlayFlags.DOWNLOAD);
-
- this.addTitle('Server');
- const webServer = this.addCheckButton('Control player remotely', 'webserver-enabled');
- const serverPort = this.addSpinButton('Listening port', 1024, 65535, 'webserver-port');
- webServer.bind_property('active', serverPort, 'visible', GObject.BindingFlags.SYNC_CREATE);
- const webApp = this.addCheckButton('Start built-in web application', 'webapp-enabled');
- webServer.bind_property('active', webApp, 'visible', GObject.BindingFlags.SYNC_CREATE);
- const webAppPort = this.addSpinButton('Web application port', 1024, 65535, 'webapp-port');
- webServer.bind_property('active', webAppPort, 'visible', GObject.BindingFlags.SYNC_CREATE);
- }
-});
-
-var YouTubePage = GObject.registerClass(
-class ClapperYouTubePage extends PrefsBase.Grid
-{
- _init()
- {
- super._init();
-
- this.addTitle('YouTube');
- this.addCheckButton('Prefer adaptive streaming', 'yt-adaptive-enabled');
- this.addComboBoxText('Max quality', [
- ['normal', "Normal"],
- ['hfr', "HFR"],
- ], 'yt-quality-type');
- }
-});
-
-var GStreamerPage = GObject.registerClass(
-class ClapperGStreamerPage extends PrefsBase.Grid
-{
- _init()
- {
- super._init();
-
- this.addTitle('Plugin Ranking');
- const listStore = new Gtk.ListStore();
- listStore.set_column_types([
- GObject.TYPE_BOOLEAN,
- GObject.TYPE_STRING,
- GObject.TYPE_STRING,
- ]);
- const treeView = new Gtk.TreeView({
- hexpand: true,
- vexpand: true,
- enable_search: false,
- model: listStore,
- });
- const treeSelection = treeView.get_selection();
-
- const apply = new Gtk.TreeViewColumn({
- title: "Apply",
- });
- const name = new Gtk.TreeViewColumn({
- title: "Plugin",
- expand: true,
- });
- const rank = new Gtk.TreeViewColumn({
- title: "Rank",
- min_width: 90,
- });
-
- const applyCell = new Gtk.CellRendererToggle();
- const nameCell = new Gtk.CellRendererText({
- editable: true,
- placeholder_text: "Insert plugin name",
- });
- const rankCell = new Gtk.CellRendererText({
- editable: true,
- weight: Pango.Weight.BOLD,
- placeholder_text: "Insert plugin rank",
- });
-
- apply.pack_start(applyCell, true);
- name.pack_start(nameCell, true);
- rank.pack_start(rankCell, true);
-
- apply.add_attribute(applyCell, 'active', 0);
- name.add_attribute(nameCell, 'text', 1);
- rank.add_attribute(rankCell, 'text', 2);
-
- treeView.insert_column(apply, 0);
- treeView.insert_column(name, 1);
- treeView.insert_column(rank, 2);
-
- const frame = new Gtk.Frame({
- child: treeView
- });
- this.addToGrid(frame);
-
- const addButton = new Gtk.Button({
- icon_name: 'list-add-symbolic',
- halign: Gtk.Align.END,
- });
- const removeButton = new Gtk.Button({
- icon_name: 'list-remove-symbolic',
- sensitive: false,
- halign: Gtk.Align.END,
- });
- const label = new Gtk.Label({
- label: 'Changes require player restart',
- halign: Gtk.Align.START,
- hexpand: true,
- ellipsize: Pango.EllipsizeMode.END,
- });
- const box = new Gtk.Box({
- orientation: Gtk.Orientation.HORIZONTAL,
- spacing: 6,
- hexpand: true,
- });
- box.append(label);
- box.append(removeButton);
- box.append(addButton);
- this.addToGrid(box);
-
- applyCell.connect('toggled', this._onApplyCellEdited.bind(this));
- nameCell.connect('edited', this._onNameCellEdited.bind(this));
- rankCell.connect('edited', this._onRankCellEdited.bind(this));
- addButton.connect('clicked', this._onAddButtonClicked.bind(this, listStore));
- removeButton.connect('clicked', this._onRemoveButtonClicked.bind(this, listStore));
- treeSelection.connect('changed', this._onTreeSelectionChanged.bind(this, removeButton));
-
- this.settingsChangedSignal = settings.connect(
- 'changed::plugin-ranking', this.refreshListStore.bind(this, listStore)
- );
-
- this.refreshListStore(listStore);
+ this._schemaName = value;
}
- refreshListStore(listStore)
+ vfunc_realize()
{
- const data = JSON.parse(settings.get_string('plugin-ranking'));
- listStore.clear();
+ super.vfunc_realize();
- for(let plugin of data) {
- listStore.set(
- listStore.append(),
- [0, 1, 2], [
- plugin.apply || false,
- plugin.name || '',
- plugin.rank || 0
- ]
+ if(this._schemaName && this._bindProp) {
+ settings.bind(this._schemaName,
+ this.activatable_widget, this._bindProp, flags
);
}
- }
-
- updatePlugin(index, prop, value)
- {
- const data = JSON.parse(settings.get_string('plugin-ranking'));
- data[index][prop] = value;
- settings.set_string('plugin-ranking', JSON.stringify(data));
- }
-
- _onTreeSelectionChanged(removeButton, treeSelection)
- {
- const [isSelected, model, iter] = treeSelection.get_selected();
- this.activeIndex = -1;
-
- if(isSelected) {
- this.activeIndex = Number(model.get_string_from_iter(iter));
- }
-
- removeButton.set_sensitive(this.activeIndex >= 0);
- }
-
- _onAddButtonClicked(listStore, button)
- {
- const data = JSON.parse(settings.get_string('plugin-ranking'));
- data.push({
- apply: false,
- name: '',
- rank: 0,
- });
- settings.set_string('plugin-ranking', JSON.stringify(data));
- }
-
- _onRemoveButtonClicked(listStore, button)
- {
- if(this.activeIndex < 0)
- return;
-
- const data = JSON.parse(settings.get_string('plugin-ranking'));
- data.splice(this.activeIndex, 1);
- settings.set_string('plugin-ranking', JSON.stringify(data));
- }
-
- _onApplyCellEdited(cell, path)
- {
- const newState = !cell.active;
- this.updatePlugin(path, 'apply', newState);
- }
-
- _onNameCellEdited(cell, path, newText)
- {
- newText = newText.trim();
- this.updatePlugin(path, 'name', newText);
- }
-
- _onRankCellEdited(cell, path, newText)
- {
- newText = newText.trim();
-
- if(isNaN(newText))
- newText = 0;
-
- this.updatePlugin(path, 'rank', Number(newText));
- }
-
- _onClose()
- {
- super._onClose('gstreamer');
-
- settings.disconnect(this.settingsChangedSignal);
- this.settingsChangedSignal = null;
+ this._schemaName = null;
}
});
-var TweaksPage = GObject.registerClass(
-class ClapperTweaksPage extends PrefsBase.Grid
+let PrefsSubpageRow = GObject.registerClass({
+ GTypeName: 'ClapperPrefsSubpageRow',
+ Properties: getCommonProps(),
+},
+class ClapperPrefsSubpageRow extends Adw.ActionRow
+{
+ _init(widget)
+ {
+ super._init({
+ activatable: true,
+ });
+
+ this._prefsSubpage = null;
+
+ const icon = new Gtk.Image({
+ icon_name: 'go-next-symbolic',
+ });
+ this.add_suffix(icon);
+ }
+
+ vfunc_activate()
+ {
+ super.vfunc_activate();
+
+ if(!this._prefsSubpage)
+ this._prefsSubpage = this._createSubpage();
+
+ const prefs = this.get_ancestor(PrefsWindow);
+ prefs.present_subpage(this._prefsSubpage);
+ }
+
+ _createSubpage()
+ {
+ /* For override */
+ return null;
+ }
+});
+
+GObject.registerClass({
+ GTypeName: 'ClapperPrefsSwitch',
+},
+class ClapperPrefsSwitch extends PrefsActionRow
+{
+ _init()
+ {
+ super._init(new Gtk.Switch(widgetOpts));
+ this._bindProp = 'active';
+ }
+});
+
+GObject.registerClass({
+ GTypeName: 'ClapperPrefsPlayFlagSwitch',
+ Properties: {
+ 'play-flag': GObject.ParamSpec.int(
+ 'play-flag',
+ 'PlayFlag',
+ 'Value of the gstreamer play flag to toggle',
+ GObject.ParamFlags.WRITABLE,
+ 1, 4096, 1,
+ ),
+ },
+},
+class ClapperPrefsPlayFlagSwitch extends PrefsActionRow
+{
+ _init()
+ {
+ super._init(new Gtk.Switch(widgetOpts));
+
+ this._flag = 1;
+ this._doneRealize = false;
+ }
+
+ set play_flag(value)
+ {
+ this._flag = value;
+ }
+
+ vfunc_realize()
+ {
+ super.vfunc_realize();
+
+ if(!this._doneRealize) {
+ const playFlags = settings.get_int('play-flags');
+
+ this.activatable_widget.active = (
+ (playFlags & this._flag) === this._flag
+ );
+ this.activatable_widget.connect(
+ 'notify::active', this._onPlayFlagToggled.bind(this)
+ );
+ }
+ this._doneRealize = true;
+ }
+
+ _onPlayFlagToggled()
+ {
+ let playFlags = settings.get_int('play-flags');
+
+ if(this.activatable_widget.active)
+ playFlags |= this._flag;
+ else
+ playFlags &= ~this._flag;
+
+ settings.set_int('play-flags', playFlags);
+ }
+});
+
+GObject.registerClass({
+ GTypeName: 'ClapperPrefsSpin',
+ Properties: {
+ 'spin-adjustment': GObject.ParamSpec.object(
+ 'spin-adjustment',
+ 'GtkAdjustment',
+ 'Custom GtkAdjustment for spin button',
+ GObject.ParamFlags.WRITABLE,
+ Gtk.Adjustment
+ ),
+ },
+},
+class ClapperPrefsSpin extends PrefsActionRow
+{
+ _init()
+ {
+ super._init(new Gtk.SpinButton(widgetOpts));
+ this._bindProp = 'value';
+ }
+
+ set spin_adjustment(value)
+ {
+ this.activatable_widget.set_adjustment(value);
+ }
+});
+
+let PrefsPluginFeature = GObject.registerClass(
+class PrefsPluginFeature extends Adw.ActionRow
+{
+ _init(featureObj)
+ {
+ super._init({
+ title: featureObj.name,
+ });
+
+ const enableSwitch = new Gtk.Switch(widgetOpts);
+ const spinButton = new Gtk.SpinButton(widgetOpts);
+
+ spinButton.set_range(0, 512);
+ spinButton.set_increments(1, 1);
+
+ enableSwitch.active = featureObj.enabled;
+ spinButton.value = featureObj.rank;
+ this.currentRank = featureObj.rank;
+
+ this.add_suffix(enableSwitch);
+ this.add_suffix(spinButton);
+
+ enableSwitch.bind_property('active', spinButton, 'sensitive',
+ GObject.BindingFlags.SYNC_CREATE
+ );
+
+ enableSwitch.connect('notify::active', this._onSwitchActivate.bind(this));
+ spinButton.connect('value-changed', this._onValueChanged.bind(this));
+ }
+
+ _updateRanking(data)
+ {
+ settings.set_string('plugin-ranking', JSON.stringify(data));
+ }
+
+ _onSwitchActivate(enableSwitch)
+ {
+ const { settingsData } = this.get_ancestor(PrefsPluginRankingSubpage);
+
+ if(enableSwitch.active)
+ settingsData[this.title] = this.currentRank;
+ else if(settingsData[this.title])
+ delete settingsData[this.title];
+
+ this._updateRanking(settingsData);
+ }
+
+ _onValueChanged(spinButton)
+ {
+ const { settingsData } = this.get_ancestor(PrefsPluginRankingSubpage);
+
+ this.currentRank = spinButton.value;
+ settingsData[this.title] = this.currentRank;
+
+ this._updateRanking(settingsData);
+ }
+});
+
+GObject.registerClass({
+ GTypeName: 'ClapperPrefsFont',
+},
+class ClapperPrefsFont extends PrefsActionRow
+{
+ _init()
+ {
+ const opts = {
+ use_font: true,
+ use_size: true,
+ };
+ Object.assign(opts, widgetOpts);
+
+ super._init(new Gtk.FontButton(opts));
+ this._bindProp = 'font';
+ }
+});
+
+GObject.registerClass({
+ GTypeName: 'ClapperPrefsCombo',
+ Properties: getCommonProps(),
+},
+class ClapperPrefsCombo extends Adw.ComboRow
+{
+ _init()
+ {
+ super._init();
+ this._schemaName = null;
+ }
+
+ set schema_name(value)
+ {
+ this._schemaName = value;
+ }
+
+ vfunc_realize()
+ {
+ super.vfunc_realize();
+
+ if(this._schemaName)
+ settings.bind(this._schemaName, this, 'selected', flags);
+
+ this._schemaName = null;
+ }
+});
+
+GObject.registerClass({
+ GTypeName: 'ClapperPrefsExpander',
+ Properties: getCommonProps(),
+},
+class ClapperPrefsExpander extends Adw.ExpanderRow
+{
+ _init()
+ {
+ super._init({
+ show_enable_switch: true,
+ });
+ }
+
+ set schema_name(value)
+ {
+ settings.bind(value, this, 'enable-expansion', flags);
+ }
+});
+
+GObject.registerClass({
+ GTypeName: 'ClapperPrefsPluginRankingSubpageRow',
+},
+class ClapperPrefsPluginRankingSubpageRow extends PrefsSubpageRow
+{
+ _createSubpage()
+ {
+ return new PrefsPluginRankingSubpage();
+ }
+});
+
+let PrefsPluginExpander = GObject.registerClass(
+class ClapperPrefsPluginExpander extends Adw.ExpanderRow
+{
+ _init(plugin)
+ {
+ super._init({
+ title: plugin,
+ show_enable_switch: false,
+ });
+
+ this.expandSignal = this.connect(
+ 'notify::expanded', this._onExpandedNotify.bind(this)
+ );
+ }
+
+ _onExpandedNotify()
+ {
+ if(!this.expanded)
+ return;
+
+ this.disconnect(this.expandSignal);
+ this.expandSignal = null;
+
+ const { pluginsData } = this.get_ancestor(PrefsPluginRankingSubpage);
+
+ pluginsData[this.title].sort((a, b) =>
+ (a.name > b.name) - (a.name < b.name)
+ );
+ const featuresNames = Object.keys(pluginsData[this.title]);
+ debug(`Adding ${featuresNames.length} features to the list of plugin: ${this.title}`);
+
+ for(let featureObj of pluginsData[this.title])
+ this.add(new PrefsPluginFeature(featureObj));
+ }
+});
+
+let PrefsPluginRankingSubpage = GObject.registerClass({
+ GTypeName: 'ClapperPrefsPluginRankingSubpage',
+ Template: `file://${Misc.getClapperPath()}/ui/preferences-plugin-ranking-subpage.ui`,
+ InternalChildren: ['decoders_group'],
+},
+class ClapperPrefsPluginRankingSubpage extends Gtk.Box
{
_init()
{
super._init();
- this.addTitle('Appearance');
- this.addCheckButton('Enable dark theme', 'dark-theme');
+ if(!Gst.is_initialized())
+ Gst.init(null);
- this.addTitle('Performance');
- this.addCheckButton('Render window shadows', 'render-shadows');
+ const gstRegistry = Gst.Registry.get();
+ const decoders = gstRegistry.feature_filter(this._decodersFilterCb, false);
+
+ const plugins = {};
+ this.settingsData = {};
+
+ /* In case someone messed up gsettings values */
+ try {
+ this.settingsData = JSON.parse(settings.get_string('plugin-ranking'));
+ /* Might be an array in older Clapper versions */
+ if(Array.isArray(this.settingsData))
+ this.settingsData = {};
+ }
+ catch(err) { /* Ignore */ }
+
+ for(let decoder of decoders) {
+ const pluginName = decoder.get_plugin_name();
+
+ /* Do not add unsupported plugins */
+ switch(pluginName) {
+ case 'playback':
+ continue;
+ default:
+ break;
+ }
+
+ if(!plugins[pluginName])
+ plugins[pluginName] = [];
+
+ const decName = decoder.get_name();
+
+ plugins[pluginName].push({
+ name: decName,
+ rank: decoder.get_rank(),
+ enabled: this.settingsData[decName] != null,
+ });
+ }
+
+ const pluginsNames = Object.keys(plugins);
+ debug(`Adding ${pluginsNames.length} found plugins to the list`);
+
+ this.pluginsData = pluginsNames.sort().reduce((res, key) =>
+ (res[key] = plugins[key], res), {}
+ );
+
+ for(let plugin in this.pluginsData)
+ this._decoders_group.add(new PrefsPluginExpander(plugin));
+ }
+
+ _decodersFilterCb(feature)
+ {
+ return (
+ feature.list_is_type
+ && feature.list_is_type(Gst.ELEMENT_FACTORY_TYPE_DECODER)
+ );
+ }
+
+ _onReturnClicked(button)
+ {
+ const prefs = this.get_ancestor(PrefsWindow);
+ prefs.close_subpage();
+ }
+});
+
+var PrefsWindow = GObject.registerClass({
+ GTypeName: 'ClapperPrefsWindow',
+ Template: `file://${Misc.getClapperPath()}/ui/preferences-window.ui`,
+},
+class ClapperPrefsWindow extends Adw.PreferencesWindow
+{
+ _init(window)
+ {
+ super._init({
+ transient_for: window,
+ });
+
+ this.show();
}
});
diff --git a/src/prefsBase.js b/src/prefsBase.js
deleted file mode 100644
index 2d12442a..00000000
--- a/src/prefsBase.js
+++ /dev/null
@@ -1,238 +0,0 @@
-const { Gio, GObject, Gtk } = imports.gi;
-const Debug = imports.src.debug;
-const Misc = imports.src.misc;
-
-const { debug } = Debug;
-const { settings } = Misc;
-
-var Notebook = GObject.registerClass(
-class ClapperPrefsNotebook extends Gtk.Notebook
-{
- _init(pages, isSubpage)
- {
- super._init({
- show_border: false,
- vexpand: true,
- hexpand: true,
- });
-
- if(isSubpage) {
- this.set_tab_pos(Gtk.PositionType.LEFT);
- this.add_css_class('prefssubpage');
- }
-
- this.addArrayPages(pages);
- }
-
- addArrayPages(array)
- {
- for(let obj of array)
- this.addObjectPages(obj);
- }
-
- addObjectPages(item)
- {
- const widget = (item.pages)
- ? new Notebook(item.pages, true)
- : new item.widget();
-
- this.addToNotebook(widget, item.title);
- }
-
- addToNotebook(widget, title)
- {
- const label = new Gtk.Label({
- label: title,
- });
- this.append_page(widget, label);
- }
-
- _onClose()
- {
- const totalPages = this.get_n_pages();
- let index = 0;
-
- while(index < totalPages) {
- const page = this.get_nth_page(index);
- page._onClose();
- index++;
- }
- }
-});
-
-var Grid = GObject.registerClass(
-class ClapperPrefsGrid extends Gtk.Grid
-{
- _init()
- {
- super._init({
- row_spacing: 6,
- column_spacing: 20,
- });
-
- this.flag = Gio.SettingsBindFlags.DEFAULT;
- this.gridIndex = 0;
- this.widgetDefaults = {
- width_request: 160,
- halign: Gtk.Align.END,
- valign: Gtk.Align.CENTER,
- };
- }
-
- addToGrid(leftWidget, rightWidget)
- {
- let spanWidth = 2;
-
- if(rightWidget) {
- spanWidth = 1;
- rightWidget.bind_property('visible', leftWidget, 'visible',
- GObject.BindingFlags.SYNC_CREATE
- );
- this.attach(rightWidget, 1, this.gridIndex, 1, 1);
- }
-
- this.attach(leftWidget, 0, this.gridIndex, spanWidth, 1);
- this.gridIndex++;
-
- return rightWidget || leftWidget;
- }
-
- addTitle(text)
- {
- const label = this.getLabel(text, true);
-
- return this.addToGrid(label);
- }
-
- addComboBoxText(text, entries, setting)
- {
- const label = this.getLabel(text + ':');
- const widget = this.getComboBoxText(entries, setting);
-
- return this.addToGrid(label, widget);
- }
-
- addSpinButton(text, min, max, setting, precision)
- {
- const label = this.getLabel(text + ':');
- const widget = this.getSpinButton(min, max, setting, precision);
-
- return this.addToGrid(label, widget);
- }
-
- addCheckButton(text, setting)
- {
- const widget = this.getCheckButton(text, setting);
-
- return this.addToGrid(widget);
- }
-
- addPlayFlagCheckButton(text, flag)
- {
- const checkButton = this.addCheckButton(text);
- const playFlags = settings.get_int('play-flags');
-
- checkButton.active = ((playFlags & flag) === flag);
- checkButton.connect('toggled', this._onPlayFlagToggled.bind(this, flag));
-
- return checkButton;
- }
-
- addFontButton(text, setting)
- {
- const label = this.getLabel(text + ':');
- const widget = this.getFontButton(setting);
-
- return this.addToGrid(label, widget);
- }
-
- getLabel(text, isTitle)
- {
- const marginTop = (isTitle && this.gridIndex > 0) ? 16 : 0;
- const marginBottom = (isTitle) ? 2 : 0;
-
- let marginLR = 0;
-
- if(isTitle)
- text = '' + text + '';
- else
- marginLR = 12;
-
- return new Gtk.Label({
- label: text,
- use_markup: true,
- hexpand: true,
- halign: Gtk.Align.START,
- margin_top: marginTop,
- margin_bottom: marginBottom,
- margin_start: marginLR,
- margin_end: marginLR,
- });
- }
-
- getComboBoxText(entries, setting)
- {
- const comboBox = new Gtk.ComboBoxText(this.widgetDefaults);
-
- for(let entry of entries)
- comboBox.append(entry[0], entry[1]);
-
- settings.bind(setting, comboBox, 'active-id', this.flag);
-
- return comboBox;
- }
-
- getSpinButton(min, max, setting, precision)
- {
- precision = precision || 1;
-
- const spinButton = new Gtk.SpinButton(this.widgetDefaults);
- spinButton.set_range(min, max);
- spinButton.set_digits(precision % 1 === 0 ? 0 : 3);
- spinButton.set_increments(precision, 1);
- settings.bind(setting, spinButton, 'value', this.flag);
-
- return spinButton;
- }
-
- getCheckButton(text, setting)
- {
- const checkButton = new Gtk.CheckButton({
- label: text || null,
- });
-
- if(setting)
- settings.bind(setting, checkButton, 'active', this.flag);
-
- return checkButton;
- }
-
- getFontButton(setting)
- {
- const fontButton = new Gtk.FontButton({
- use_font: true,
- use_size: true,
- });
- settings.bind(setting, fontButton, 'font', this.flag);
-
- return fontButton;
- }
-
- _onPlayFlagToggled(flag, button)
- {
- let playFlags = settings.get_int('play-flags');
-
- if(button.active)
- playFlags |= flag;
- else
- playFlags &= ~flag;
-
- settings.set_int('play-flags', playFlags);
- }
-
- _onClose(name)
- {
- if(name)
- debug(`cleanup of prefs ${name} page`);
- }
-});
diff --git a/src/widget.js b/src/widget.js
index 4c6fcc1c..af3a1791 100644
--- a/src/widget.js
+++ b/src/widget.js
@@ -492,7 +492,7 @@ class ClapperWidget extends Gtk.Grid
if(
!this.isSeekable
|| this.controls.isPositionDragging
- || !player.seek_done
+ || !player.seekDone
)
return;
diff --git a/src/youtube.js b/src/youtube.js
index b4e0fdbc..7cd7d8e1 100644
--- a/src/youtube.js
+++ b/src/youtube.js
@@ -317,7 +317,7 @@ var YouTubeClient = GObject.registerClass({
width: monitor.geometry.width * monitor.scale_factor,
height: monitor.geometry.height * monitor.scale_factor,
codec: 'h264',
- type: settings.get_string('yt-quality-type'),
+ type: YTItags.QualityType[settings.get_int('yt-quality-type')],
adaptive: settings.get_boolean('yt-adaptive-enabled'),
};
diff --git a/src/youtubeItags.js b/src/youtubeItags.js
index 374ea8a1..09b69326 100644
--- a/src/youtubeItags.js
+++ b/src/youtubeItags.js
@@ -1,3 +1,8 @@
+var QualityType = {
+ 0: 'normal',
+ 1: 'hfr',
+};
+
const Itags = {
video: {
h264: {
diff --git a/ui/preferences-plugin-ranking-subpage.ui b/ui/preferences-plugin-ranking-subpage.ui
new file mode 100644
index 00000000..6d98919e
--- /dev/null
+++ b/ui/preferences-plugin-ranking-subpage.ui
@@ -0,0 +1,30 @@
+
+
+
+ vertical
+ True
+ True
+
+
+
+
+
+ Return to the preferences
+ center
+ center
+ 12
+ 12
+
+
+
+
+
+
diff --git a/ui/preferences-window.ui b/ui/preferences-window.ui
new file mode 100644
index 00000000..8a71aa4b
--- /dev/null
+++ b/ui/preferences-window.ui
@@ -0,0 +1,292 @@
+
+
+
+ Preferences
+ True
+ True
+ True
+ True
+ True
+
+
+ General
+ user-home-symbolic
+
+
+ Behavior
+
+
+ Auto fullscreen
+ Enter fullscreen when playlist is replaced except floating mode
+ fullscreen-auto
+
+
+
+
+ Ask to resume recent media
+ resume-enabled
+
+
+
+
+ Float on all workspaces
+ This option only works on GNOME
+ floating-stick
+
+
+
+
+ After playback
+ after-playback
+
+
+
+ - Do nothing
+ - Freeze last frame
+ - Close the app
+
+
+
+
+
+
+
+
+
+ Volume
+
+
+ Custom initial value
+ Set custom volume at startup instead of restoring it
+ volume-custom
+
+
+ Volume percentage
+ volume-value
+ volume_adjustment
+
+
+
+
+
+
+
+
+ Seeking
+
+
+ Mode
+ seeking-mode
+
+
+
+ - Normal
+ - Accurate
+ - Fast
+
+
+
+
+
+
+
+ Unit
+ seeking-unit
+
+
+
+ - Second
+ - Minute
+ - Percentage
+
+
+
+
+
+
+
+ Value
+ seeking-value
+ seeking_adjustment
+
+
+
+
+
+
+
+
+ Playback
+ camera-video-symbolic
+
+
+ Audio
+
+
+ Offset in milliseconds
+ audio-offset
+ audio_offset_adjustment
+
+
+
+
+ Only native audio formats
+ 32
+
+
+
+
+
+
+ Subtitles
+
+
+ Default font
+ subtitle-font
+
+
+
+
+
+
+
+
+ Network
+ preferences-system-network-symbolic
+
+
+ Client
+
+
+ Progressive download buffering
+ 128
+
+
+
+
+
+
+ Server
+
+
+ Control player remotely
+ webserver-enabled
+
+
+ Listening port
+ webserver-port
+ web_server_adjustment
+
+
+
+
+ Run web application in background
+ Requires GTK compiled with Broadway backend
+ webapp-enabled
+
+
+
+
+ Web application port
+ webapp-port
+ web_app_adjustment
+
+
+
+
+
+
+
+
+ YouTube
+
+
+ Prefer adaptive streaming
+ yt-adaptive-enabled
+
+
+
+
+ Max quality
+ yt-quality-type
+
+
+
+ - Normal
+ - HFR
+
+
+
+
+
+
+
+
+
+
+
+ Tweaks
+ applications-engineering-symbolic
+
+
+ Appearance
+
+
+ Dark theme
+ dark-theme
+
+
+
+
+ Render window shadows
+ Disable to increase performance when windowed
+ render-shadows
+
+
+
+
+
+
+ GStreamer
+
+
+
+
+
+
+
+
+
+ 0
+ 99
+ 1
+ 1
+
+
+ 0
+ 150
+ 1
+ 1
+
+
+ -1000
+ 1000
+ 25
+ 1
+
+
+ 1024
+ 65535
+ 1
+ 1
+
+
+ 1024
+ 65535
+ 1
+ 1
+
+