Files
clapper/src/playerBase.js
Rafał Dzięgiel 340cb36ecd Move "clapper_src" dir to "src"
The "clapper_src" directory name was unusual. This was done to make it work as a widget for other apps. Now that this functionality got removed it can be named simply "src" as recommended by guidelines.
2021-01-21 14:19:04 +01:00

316 lines
10 KiB
JavaScript

const { Gio, GLib, GObject, Gst, GstPlayer, Gtk } = imports.gi;
const Debug = imports.src.debug;
const Misc = imports.src.misc;
const { PlaylistWidget } = imports.src.playlist;
const { WebApp } = imports.src.webApp;
const { debug } = Debug;
const { settings } = Misc;
let WebServer;
var PlayerBase = GObject.registerClass(
class ClapperPlayerBase extends GstPlayer.Player
{
_init()
{
if(!Gst.is_initialized())
Gst.init(null);
const plugin = 'gtk4glsink';
const gtk4glsink = Gst.ElementFactory.make(plugin, null);
if(!gtk4glsink) {
debug(new Error(
`Could not load "${plugin}".`
+ ' Do you have gstreamer-plugins-good-gtk4 installed?'
));
}
const glsinkbin = Gst.ElementFactory.make('glsinkbin', null);
glsinkbin.sink = gtk4glsink;
const context = GLib.MainContext.ref_thread_default();
const acquired = context.acquire();
debug(`default context acquired: ${acquired}`);
const dispatcher = new GstPlayer.PlayerGMainContextSignalDispatcher({
application_context: context,
});
const renderer = new GstPlayer.PlayerVideoOverlayVideoRenderer({
video_sink: glsinkbin
});
super._init({
signal_dispatcher: dispatcher,
video_renderer: renderer
});
this.widget = gtk4glsink.widget;
this.widget.vexpand = true;
this.widget.hexpand = true;
this.state = GstPlayer.PlayerState.STOPPED;
this.visualization_enabled = false;
this.webserver = null;
this.webapp = null;
this.playlistWidget = new PlaylistWidget();
this.set_all_plugins_ranks();
this.set_initial_config();
this.set_and_bind_settings();
settings.connect('changed', this._onSettingsKeyChanged.bind(this));
/* FIXME: additional reference for working around GstPlayer
* buggy signal dispatcher on self. Remove when ported to BUS API */
this.ref();
}
set_and_bind_settings()
{
const settingsToSet = [
'seeking-mode',
'audio-offset',
'subtitle-offset',
'play-flags',
'webserver-enabled'
];
for(let key of settingsToSet)
this._onSettingsKeyChanged(settings, key);
const flag = Gio.SettingsBindFlags.GET;
settings.bind('subtitle-font', this.pipeline, 'subtitle_font_desc', flag);
}
set_initial_config()
{
const gstPlayerConfig = {
position_update_interval: 1000,
user_agent: 'clapper',
};
for(let option of Object.keys(gstPlayerConfig))
this.set_config_option(option, gstPlayerConfig[option]);
this.set_mute(false);
/* FIXME: change into option in preferences */
const pipeline = this.get_pipeline();
pipeline.ring_buffer_max_size = 8 * 1024 * 1024;
}
set_config_option(option, value)
{
const setOption = GstPlayer.Player[`config_set_${option}`];
if(!setOption)
return debug(`unsupported option: ${option}`, 'LEVEL_WARNING');
const config = this.get_config();
setOption(config, value);
const success = this.set_config(config);
if(!success)
debug(`could not change option: ${option}`);
}
set_all_plugins_ranks()
{
let data = [];
/* Set empty plugin list if someone messed it externally */
try {
data = JSON.parse(settings.get_string('plugin-ranking'));
if(!Array.isArray(data))
throw new Error('plugin ranking data is not an array!');
}
catch(err) {
debug(err);
settings.set_string('plugin-ranking', "[]");
}
for(let plugin of data) {
if(!plugin.apply || !plugin.name)
continue;
this.set_plugin_rank(plugin.name, plugin.rank);
}
}
set_plugin_rank(name, rank)
{
const gstRegistry = Gst.Registry.get();
const feature = gstRegistry.lookup_feature(name);
if(!feature)
return debug(`plugin unavailable: ${name}`);
const oldRank = feature.get_rank();
if(rank === oldRank)
return;
feature.set_rank(rank);
debug(`changed rank: ${oldRank} -> ${rank} for ${name}`);
}
draw_black(isEnabled)
{
this.widget.ignore_textures = isEnabled;
if(this.state !== GstPlayer.PlayerState.PLAYING)
this.widget.queue_render();
}
emitWs(action, value)
{
if(!this.webserver)
return;
this.webserver.sendMessage({ action, value });
}
receiveWs(action, value)
{
debug(`unhandled WebSocket action: ${action}`);
}
_onSettingsKeyChanged(settings, key)
{
let root, value, action;
switch(key) {
case 'seeking-mode':
const isSeekMode = (typeof this.set_seek_mode !== 'undefined');
this.seekingMode = settings.get_string('seeking-mode');
switch(this.seekingMode) {
case 'fast':
if(isSeekMode)
this.set_seek_mode(GstPlayer.PlayerSeekMode.FAST);
else
this.set_config_option('seek_fast', true);
break;
case 'accurate':
if(isSeekMode)
this.set_seek_mode(GstPlayer.PlayerSeekMode.ACCURATE);
else {
this.set_config_option('seek_fast', false);
this.set_config_option('seek_accurate', true);
}
break;
default:
if(isSeekMode)
this.set_seek_mode(GstPlayer.PlayerSeekMode.DEFAULT);
else {
this.set_config_option('seek_fast', false);
this.set_config_option('seek_accurate', false);
}
break;
}
break;
case 'render-shadows':
root = this.widget.get_root();
/* Editing theme of someone else app is taboo */
if(!root || !root.isClapperApp)
break;
const gpuClass = 'gpufriendly';
const renderShadows = settings.get_boolean(key);
const hasShadows = !root.has_css_class(gpuClass);
if(renderShadows === hasShadows)
break;
action = (renderShadows) ? 'remove' : 'add';
root[action + '_css_class'](gpuClass);
break;
case 'audio-offset':
value = Math.round(settings.get_double(key) * -1000000);
this.set_audio_video_offset(value);
debug(`set audio-video offset: ${value}`);
break;
case 'subtitle-offset':
value = Math.round(settings.get_double(key) * -1000000);
this.set_subtitle_video_offset(value);
debug(`set subtitle-video offset: ${value}`);
break;
case 'dark-theme':
case 'brighter-sliders':
root = this.widget.get_root();
if(!root || !root.isClapperApp)
break;
const brightClass = 'brightscale';
const isBrighter = root.has_css_class(brightClass);
if(key === 'dark-theme' && isBrighter && !settings.get_boolean(key)) {
root.remove_css_class(brightClass);
debug('remove brighter sliders');
break;
}
const setBrighter = settings.get_boolean('brighter-sliders');
if(setBrighter === isBrighter)
break;
action = (setBrighter) ? 'add' : 'remove';
root[action + '_css_class'](brightClass);
debug(`${action} brighter sliders`);
break;
case 'play-flags':
const initialFlags = this.pipeline.flags;
const settingsFlags = settings.get_int(key);
if(initialFlags === settingsFlags)
break;
this.pipeline.flags = settingsFlags;
debug(`changed play flags: ${initialFlags} -> ${settingsFlags}`);
break;
case 'webserver-enabled':
case 'webapp-enabled':
const webserverEnabled = settings.get_boolean('webserver-enabled');
if(webserverEnabled) {
if(!WebServer) {
/* Probably most users will not use this,
* so conditional import for faster startup */
WebServer = imports.src.webServer.WebServer;
}
if(!this.webserver) {
this.webserver = new WebServer(settings.get_int('webserver-port'));
this.webserver.passMsgData = this.receiveWs.bind(this);
}
this.webserver.startListening();
const webappEnabled = settings.get_boolean('webapp-enabled');
if(!this.webapp && !webappEnabled)
break;
if(webappEnabled) {
if(!this.webapp)
this.webapp = new WebApp();
this.webapp.startDaemonApp(settings.get_int('webapp-port'));
}
}
else if(this.webserver) {
/* remote app will close when connection is lost
* which will cause the daemon to close too */
this.webserver.stopListening();
}
break;
case 'webserver-port':
if(!this.webserver)
break;
this.webserver.setListeningPort(settings.get_int(key));
break;
default:
break;
}
}
});