mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-31 16:31:58 +02:00
Add music visualizations
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -5,7 +5,7 @@
|
|||||||
- [X] Dragging player by video (MPV)
|
- [X] Dragging player by video (MPV)
|
||||||
- [X] Switching video/audio/subtitles tracks from bottom bar (MPV)
|
- [X] Switching video/audio/subtitles tracks from bottom bar (MPV)
|
||||||
- [X] Over-amplification supported by default (VLC)
|
- [X] Over-amplification supported by default (VLC)
|
||||||
- [ ] Audio visualizations (VLC)
|
- [X] Audio visualizations (VLC)
|
||||||
- [ ] Clock with current hour and "Ends at" time on top overlay (Kodi)
|
- [ ] Clock with current hour and "Ends at" time on top overlay (Kodi)
|
||||||
- [ ] Auto select subtitles matching OS language (Totem)
|
- [ ] Auto select subtitles matching OS language (Totem)
|
||||||
- [ ] Picture-in-Picture mode window
|
- [ ] Picture-in-Picture mode window
|
||||||
|
69
clapper_src/controls.js
vendored
69
clapper_src/controls.js
vendored
@@ -14,6 +14,9 @@ var Controls = GObject.registerClass({
|
|||||||
'track-change-requested': {
|
'track-change-requested': {
|
||||||
param_types: [GObject.TYPE_STRING, GObject.TYPE_INT]
|
param_types: [GObject.TYPE_STRING, GObject.TYPE_INT]
|
||||||
},
|
},
|
||||||
|
'visualization-change-requested': {
|
||||||
|
param_types: [GObject.TYPE_STRING]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}, class ClapperControls extends Gtk.HBox
|
}, class ClapperControls extends Gtk.HBox
|
||||||
{
|
{
|
||||||
@@ -32,6 +35,9 @@ var Controls = GObject.registerClass({
|
|||||||
|
|
||||||
this._addTogglePlayButton();
|
this._addTogglePlayButton();
|
||||||
this._addPositionScale();
|
this._addPositionScale();
|
||||||
|
this.visualizationsButton = this.addPopoverButton(
|
||||||
|
'display-projector-symbolic'
|
||||||
|
);
|
||||||
this.videoTracksButton = this.addPopoverButton(
|
this.videoTracksButton = this.addPopoverButton(
|
||||||
'emblem-videos-symbolic'
|
'emblem-videos-symbolic'
|
||||||
);
|
);
|
||||||
@@ -58,8 +64,11 @@ var Controls = GObject.registerClass({
|
|||||||
Gtk.IconSize.SMALL_TOOLBAR
|
Gtk.IconSize.SMALL_TOOLBAR
|
||||||
);
|
);
|
||||||
this.setDefaultWidgetBehaviour(this.openMenuButton);
|
this.setDefaultWidgetBehaviour(this.openMenuButton);
|
||||||
|
|
||||||
this.forall(this.setDefaultWidgetBehaviour);
|
this.forall(this.setDefaultWidgetBehaviour);
|
||||||
|
|
||||||
|
this.realizeSignal = this.connect(
|
||||||
|
'realize', this._onControlsRealize.bind(this)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
set fullscreenMode(isFullscreen)
|
set fullscreenMode(isFullscreen)
|
||||||
@@ -135,7 +144,7 @@ var Controls = GObject.registerClass({
|
|||||||
for(let i = 0; i < lastEl; i++) {
|
for(let i = 0; i < lastEl; i++) {
|
||||||
if(i >= array.length) {
|
if(i >= array.length) {
|
||||||
children[i].hide();
|
children[i].hide();
|
||||||
debug(`hiding unused ${children[i].trackType} radioButton nr: ${i}`);
|
debug(`hiding unused ${children[i].type} radioButton nr: ${i}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,19 +162,20 @@ var Controls = GObject.registerClass({
|
|||||||
});
|
});
|
||||||
radioButton.connect(
|
radioButton.connect(
|
||||||
'toggled',
|
'toggled',
|
||||||
this._onTrackRadioButtonToggled.bind(this, radioButton)
|
this._onRadioButtonToggled.bind(this, radioButton)
|
||||||
);
|
);
|
||||||
this.setDefaultWidgetBehaviour(radioButton);
|
this.setDefaultWidgetBehaviour(radioButton);
|
||||||
box.add(radioButton);
|
box.add(radioButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
radioButton.label = el.label;
|
radioButton.label = el.label;
|
||||||
debug(`radioButton label: ${radioButton.label}`);
|
debug(`radioButton label: ${radioButton.label}`);
|
||||||
radioButton.trackType = el.type;
|
radioButton.type = el.type;
|
||||||
debug(`radioButton type: ${radioButton.trackType}`);
|
debug(`radioButton type: ${radioButton.type}`);
|
||||||
radioButton.trackId = el.value;
|
radioButton.activeId = el.activeId;
|
||||||
debug(`radioButton track id: ${radioButton.trackId}`);
|
debug(`radioButton id: ${radioButton.activeId}`);
|
||||||
|
|
||||||
if(radioButton.trackId === activeId) {
|
if(radioButton.activeId === activeId) {
|
||||||
radioButton.set_active(true);
|
radioButton.set_active(true);
|
||||||
debug(`activated ${el.type} radioButton nr: ${i}`);
|
debug(`activated ${el.type} radioButton nr: ${i}`);
|
||||||
}
|
}
|
||||||
@@ -195,7 +205,7 @@ var Controls = GObject.registerClass({
|
|||||||
_addTogglePlayButton()
|
_addTogglePlayButton()
|
||||||
{
|
{
|
||||||
this.togglePlayButton = this.addButton(
|
this.togglePlayButton = this.addButton(
|
||||||
'media-playback-pause-symbolic',
|
'media-playback-start-symbolic',
|
||||||
Gtk.IconSize.LARGE_TOOLBAR
|
Gtk.IconSize.LARGE_TOOLBAR
|
||||||
);
|
);
|
||||||
this.togglePlayButton.setPlayImage = () =>
|
this.togglePlayButton.setPlayImage = () =>
|
||||||
@@ -287,16 +297,30 @@ var Controls = GObject.registerClass({
|
|||||||
button.popover.popup();
|
button.popover.popup();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onTrackRadioButtonToggled(self, radioButton)
|
_onRadioButtonToggled(self, radioButton)
|
||||||
{
|
{
|
||||||
if(!radioButton.get_active())
|
if(!radioButton.get_active())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.emit(
|
switch(radioButton.type) {
|
||||||
'track-change-requested',
|
case 'video':
|
||||||
radioButton.trackType,
|
case 'audio':
|
||||||
radioButton.trackId
|
case 'subtitle':
|
||||||
);
|
this.emit(
|
||||||
|
'track-change-requested',
|
||||||
|
radioButton.type,
|
||||||
|
radioButton.activeId
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'visualization':
|
||||||
|
this.emit(
|
||||||
|
`${radioButton.type}-change-requested`,
|
||||||
|
radioButton.activeId
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPositionScaleFormatValue(self, value)
|
_onPositionScaleFormatValue(self, value)
|
||||||
@@ -316,4 +340,19 @@ var Controls = GObject.registerClass({
|
|||||||
this.isPositionSeeking = false;
|
this.isPositionSeeking = false;
|
||||||
this.emit('position-seeking-changed', this.isPositionSeeking);
|
this.emit('position-seeking-changed', this.isPositionSeeking);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onControlsRealize()
|
||||||
|
{
|
||||||
|
this.disconnect(this.realizeSignal);
|
||||||
|
|
||||||
|
let hiddenButtons = [
|
||||||
|
'visualizations',
|
||||||
|
'videoTracks',
|
||||||
|
'audioTracks',
|
||||||
|
'subtitleTracks'
|
||||||
|
];
|
||||||
|
|
||||||
|
for(let name of hiddenButtons)
|
||||||
|
this[`${name}Button`].hide();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@@ -67,6 +67,9 @@ class ClapperInterface extends Gtk.Grid
|
|||||||
this.controls.connect(
|
this.controls.connect(
|
||||||
'track-change-requested', this._onTrackChangeRequested.bind(this)
|
'track-change-requested', this._onTrackChangeRequested.bind(this)
|
||||||
);
|
);
|
||||||
|
this.controls.connect(
|
||||||
|
'visualization-change-requested', this._onVisualizationChangeRequested.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
this.overlay.add(this._player.widget);
|
this.overlay.add(this._player.widget);
|
||||||
}
|
}
|
||||||
@@ -177,28 +180,33 @@ class ClapperInterface extends Gtk.Grid
|
|||||||
tracksArr[0] = {
|
tracksArr[0] = {
|
||||||
label: 'Disabled',
|
label: 'Disabled',
|
||||||
type: type,
|
type: type,
|
||||||
value: -1
|
activeId: -1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
tracksArr.push({
|
tracksArr.push({
|
||||||
label: text,
|
label: text,
|
||||||
type: type,
|
type: type,
|
||||||
value: info.get_index(),
|
activeId: info.get_index(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for(let type of ['video', 'audio', 'subtitle']) {
|
for(let type of ['video', 'audio', 'subtitle']) {
|
||||||
let currStream = this._player[`get_current_${type}_track`]();
|
let currStream = this._player[`get_current_${type}_track`]();
|
||||||
let activeId = (currStream) ? currStream.get_index() : -1;
|
let activeId = (currStream) ? currStream.get_index() : -1;
|
||||||
let buttonBox = this.controls[`${type}TracksButton`].get_parent();
|
|
||||||
|
|
||||||
if(currStream && type !== 'subtitle') {
|
if(currStream && type !== 'subtitle') {
|
||||||
let caps = currStream.get_caps();
|
let caps = currStream.get_caps();
|
||||||
debug(`${type} caps: ${caps.to_string()}`, 'LEVEL_INFO');
|
debug(`${type} caps: ${caps.to_string()}`, 'LEVEL_INFO');
|
||||||
}
|
}
|
||||||
|
if(type === 'video') {
|
||||||
|
let isShowVis = (parsedInfo[`${type}Tracks`].length === 0);
|
||||||
|
this.showVisualizationsButton(isShowVis);
|
||||||
|
}
|
||||||
if(!parsedInfo[`${type}Tracks`].length) {
|
if(!parsedInfo[`${type}Tracks`].length) {
|
||||||
debug(`hiding popover button without contents: ${type}`);
|
if(this.controls[`${type}TracksButton`].visible) {
|
||||||
buttonBox.hide();
|
debug(`hiding popover button without contents: ${type}`);
|
||||||
|
this.controls[`${type}TracksButton`].hide();
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.controls.addRadioButtons(
|
this.controls.addRadioButtons(
|
||||||
@@ -206,7 +214,10 @@ class ClapperInterface extends Gtk.Grid
|
|||||||
parsedInfo[`${type}Tracks`],
|
parsedInfo[`${type}Tracks`],
|
||||||
activeId
|
activeId
|
||||||
);
|
);
|
||||||
buttonBox.show();
|
if(!this.controls[`${type}TracksButton`].visible) {
|
||||||
|
debug(`showing popover button with contents: ${type}`);
|
||||||
|
this.controls[`${type}TracksButton`].show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,38 +248,104 @@ class ClapperInterface extends Gtk.Grid
|
|||||||
this.headerBar.set_subtitle(subtitle);
|
this.headerBar.set_subtitle(subtitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onTrackChangeRequested(self, trackType, trackId)
|
showVisualizationsButton(isShow)
|
||||||
|
{
|
||||||
|
if(isShow && !this.controls.visualizationsButton.isVisList) {
|
||||||
|
debug('creating visualizations list');
|
||||||
|
let visArr = GstPlayer.Player.visualizations_get();
|
||||||
|
if(!visArr.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let parsedVisArr = [{
|
||||||
|
label: 'Disabled',
|
||||||
|
type: 'visualization',
|
||||||
|
activeId: null
|
||||||
|
}];
|
||||||
|
|
||||||
|
visArr.forEach(vis => {
|
||||||
|
parsedVisArr.push({
|
||||||
|
label: vis.name[0].toUpperCase() + vis.name.substring(1),
|
||||||
|
type: 'visualization',
|
||||||
|
activeId: vis.name,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.controls.addRadioButtons(
|
||||||
|
this.controls.visualizationsButton.popoverBox,
|
||||||
|
parsedVisArr,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
this.controls.visualizationsButton.isVisList = true;
|
||||||
|
debug(`total visualizations: ${visArr.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.controls.visualizationsButton.visible === isShow)
|
||||||
|
return debug('visualizations button is already visible');
|
||||||
|
|
||||||
|
let action = (isShow) ? 'show' : 'hide';
|
||||||
|
this.controls.visualizationsButton[action]();
|
||||||
|
debug(`show visualizations button: ${isShow}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_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(trackType === 'audio') {
|
if(type === 'audio') {
|
||||||
if(trackId < 0)
|
if(activeId < 0)
|
||||||
return this._player.set_mute(true);
|
return this._player.set_mute(true);
|
||||||
|
|
||||||
if(this._player.get_mute())
|
if(this._player.get_mute())
|
||||||
this._player.set_mute(false);
|
this._player.set_mute(false);
|
||||||
|
|
||||||
return this._player[`set_${trackType}_track`](trackId);
|
return this._player[`set_${type}_track`](activeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(trackId < 0) {
|
if(activeId < 0) {
|
||||||
// disabling video leaves last frame frozen,
|
// disabling video leaves last frame frozen,
|
||||||
// so we also hide the widget
|
// so we also hide the widget
|
||||||
if(trackType === 'video')
|
if(type === 'video')
|
||||||
this._player.widget.hide();
|
this._player.widget.hide();
|
||||||
|
|
||||||
return this._player[`set_${trackType}_track_enabled`](false);
|
return this._player[`set_${type}_track_enabled`](false);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._player[`set_${trackType}_track`](trackId);
|
this._player[`set_${type}_track`](activeId);
|
||||||
this._player[`set_${trackType}_track_enabled`](true);
|
this._player[`set_${type}_track_enabled`](true);
|
||||||
|
|
||||||
if(trackType === 'video' && !this._player.widget.get_visible()) {
|
if(type === 'video' && !this._player.widget.get_visible()) {
|
||||||
this._player.widget.show();
|
this._player.widget.show();
|
||||||
this._player.renderer.expose();
|
this._player.renderer.expose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onVisualizationChangeRequested(self, visName)
|
||||||
|
{
|
||||||
|
let isEnabled = this._player.get_visualization_enabled();
|
||||||
|
|
||||||
|
if(!visName) {
|
||||||
|
if(isEnabled) {
|
||||||
|
this._player.set_visualization_enabled(false);
|
||||||
|
debug('disabled visualizations');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currVis = this._player.get_current_visualization();
|
||||||
|
|
||||||
|
if(currVis === visName)
|
||||||
|
return;
|
||||||
|
|
||||||
|
debug(`set visualization: ${visName}`);
|
||||||
|
this._player.set_visualization(visName);
|
||||||
|
|
||||||
|
if(!isEnabled) {
|
||||||
|
this._player.set_visualization_enabled(true);
|
||||||
|
debug('enabled visualizations');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onPlayerStateChanged(player, state)
|
_onPlayerStateChanged(player, state)
|
||||||
{
|
{
|
||||||
switch(state) {
|
switch(state) {
|
||||||
|
@@ -56,6 +56,7 @@ class ClapperPlayer extends GstPlayer.Player
|
|||||||
this.run_loop = opts.run_loop || false;
|
this.run_loop = opts.run_loop || false;
|
||||||
this.widget = gtkglsink.widget;
|
this.widget = gtkglsink.widget;
|
||||||
this.state = GstPlayer.PlayerState.STOPPED;
|
this.state = GstPlayer.PlayerState.STOPPED;
|
||||||
|
this.visualization_enabled = false;
|
||||||
|
|
||||||
this._playlist = [];
|
this._playlist = [];
|
||||||
this._trackId = 0;
|
this._trackId = 0;
|
||||||
@@ -103,6 +104,20 @@ class ClapperPlayer extends GstPlayer.Player
|
|||||||
return this._playlist;
|
return this._playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_visualization_enabled(value)
|
||||||
|
{
|
||||||
|
if(value === this.visualization_enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
super.set_visualization_enabled(value);
|
||||||
|
this.visualization_enabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_visualization_enabled()
|
||||||
|
{
|
||||||
|
return this.visualization_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
seek_seconds(position)
|
seek_seconds(position)
|
||||||
{
|
{
|
||||||
this.seek(position * 1000000000);
|
this.seek(position * 1000000000);
|
||||||
|
Reference in New Issue
Block a user