Add playlist widget to elapsed time button popover

This commit is contained in:
Rafostar
2021-01-19 14:40:25 +01:00
parent fca7966ece
commit 3ba21d42ec
8 changed files with 345 additions and 118 deletions

View File

@@ -17,7 +17,6 @@ class ClapperApp extends AppBase
this.get_flags() this.get_flags()
| Gio.ApplicationFlags.HANDLES_OPEN | Gio.ApplicationFlags.HANDLES_OPEN
); );
this.playlist = [];
} }
vfunc_startup() vfunc_startup()
@@ -42,10 +41,12 @@ class ClapperApp extends AppBase
{ {
super.vfunc_open(files, hint); super.vfunc_open(files, hint);
this.playlist = files; const { player } = this.active_window.get_child();
if(this.doneFirstActivate) if(!this.doneFirstActivate)
this.setWindowPlaylist(this.active_window); player._preparePlaylist(files);
else
player.set_playlist(files);
this.activate(); this.activate();
} }
@@ -54,15 +55,12 @@ class ClapperApp extends AppBase
{ {
super._onWindowShow(window); super._onWindowShow(window);
this.setWindowPlaylist(window); const { player } = this.active_window.get_child();
} const success = player.playlistWidget.nextTrack();
setWindowPlaylist(window) if(!success)
{ debug('playlist is empty');
if(!this.playlist.length)
return;
const { player } = window.get_child(); player.widget.grab_focus();
player.set_playlist(this.playlist);
} }
}); });

View File

@@ -85,13 +85,6 @@ class ClapperIconButton extends CustomButton
}); });
this.floatUnaffected = true; this.floatUnaffected = true;
} }
setFullscreenMode(isFullscreen)
{
/* Redraw icon after style class change */
this.set_icon_name(this.icon_name);
super.setFullscreenMode(isFullscreen);
}
}); });
var IconToggleButton = GObject.registerClass( var IconToggleButton = GObject.registerClass(
@@ -116,38 +109,13 @@ class ClapperIconToggleButton extends IconButton
} }
}); });
var LabelButton = GObject.registerClass( var PopoverButtonBase = GObject.registerClass(
class ClapperLabelButton extends CustomButton class ClapperPopoverButtonBase extends CustomButton
{ {
_init(text) _init()
{ {
super._init({ 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.floatUnaffected = false;
this.popover = new Gtk.Popover({ this.popover = new Gtk.Popover({
position: Gtk.PositionType.TOP, position: Gtk.PositionType.TOP,
}); });
@@ -203,3 +171,59 @@ class ClapperPopoverButton extends IconButton
this.popover.unparent(); this.popover.unparent();
} }
}); });
var IconPopoverButton = GObject.registerClass(
class ClapperIconPopoverButton extends PopoverButtonBase
{
_init(icon)
{
super._init();
this.icon_name = icon;
}
});
var LabelPopoverButton = GObject.registerClass(
class ClapperLabelPopoverButton extends PopoverButtonBase
{
_init(text)
{
super._init();
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 ElapsedPopoverButton = GObject.registerClass(
class ClapperElapsedPopoverButton extends LabelPopoverButton
{
_init(text)
{
super._init(text);
this.scrolledWindow = new Gtk.ScrolledWindow({
max_content_height: 150,
min_content_width: 250,
propagate_natural_height: true,
});
this.popoverBox.append(this.scrolledWindow);
}
setFullscreenMode(isFullscreen)
{
super.setFullscreenMode(isFullscreen);
this.scrolledWindow.max_content_height = (isFullscreen)
? 190 : 150;
}
});

View File

@@ -42,7 +42,7 @@ class ClapperControls extends Gtk.Box
this._addTogglePlayButton(); this._addTogglePlayButton();
const elapsedRevealer = new Revealers.ButtonsRevealer('SLIDE_RIGHT'); const elapsedRevealer = new Revealers.ButtonsRevealer('SLIDE_RIGHT');
this.elapsedButton = this.addLabelButton('00:00/00:00', elapsedRevealer); this.elapsedButton = this.addElapsedPopoverButton('00:00/00:00', elapsedRevealer);
elapsedRevealer.set_reveal_child(true); elapsedRevealer.set_reveal_child(true);
this.revealersArr.push(elapsedRevealer); this.revealersArr.push(elapsedRevealer);
this.append(elapsedRevealer); this.append(elapsedRevealer);
@@ -59,22 +59,22 @@ class ClapperControls extends Gtk.Box
const tracksRevealer = new Revealers.ButtonsRevealer( const tracksRevealer = new Revealers.ButtonsRevealer(
'SLIDE_LEFT', revealTracksButton 'SLIDE_LEFT', revealTracksButton
); );
this.visualizationsButton = this.addPopoverButton( this.visualizationsButton = this.addIconPopoverButton(
'display-projector-symbolic', 'display-projector-symbolic',
tracksRevealer tracksRevealer
); );
this.visualizationsButton.set_visible(false); this.visualizationsButton.set_visible(false);
this.videoTracksButton = this.addPopoverButton( this.videoTracksButton = this.addIconPopoverButton(
'emblem-videos-symbolic', 'emblem-videos-symbolic',
tracksRevealer tracksRevealer
); );
this.videoTracksButton.set_visible(false); this.videoTracksButton.set_visible(false);
this.audioTracksButton = this.addPopoverButton( this.audioTracksButton = this.addIconPopoverButton(
'emblem-music-symbolic', 'emblem-music-symbolic',
tracksRevealer tracksRevealer
); );
this.audioTracksButton.set_visible(false); this.audioTracksButton.set_visible(false);
this.subtitleTracksButton = this.addPopoverButton( this.subtitleTracksButton = this.addIconPopoverButton(
'media-view-subtitles-symbolic', 'media-view-subtitles-symbolic',
tracksRevealer tracksRevealer
); );
@@ -165,17 +165,25 @@ class ClapperControls extends Gtk.Box
return button; return button;
} }
addLabelButton(text, revealer) addIconPopoverButton(iconName, revealer)
{ {
text = text || ''; const button = new Buttons.IconPopoverButton(iconName);
const button = new Buttons.LabelButton(text);
return this.addButton(button, revealer); return this.addButton(button, revealer);
} }
addPopoverButton(iconName, revealer) addLabelPopoverButton(text, revealer)
{ {
const button = new Buttons.PopoverButton(iconName); text = text || '';
const button = new Buttons.LabelPopoverButton(text);
return this.addButton(button, revealer);
}
addElapsedPopoverButton(text, revealer)
{
text = text || '';
const button = new Buttons.ElapsedPopoverButton(text);
return this.addButton(button, revealer); return this.addButton(button, revealer);
} }
@@ -363,7 +371,7 @@ class ClapperControls extends Gtk.Box
_addVolumeButton() _addVolumeButton()
{ {
this.volumeButton = this.addPopoverButton( this.volumeButton = this.addIconPopoverButton(
'audio-volume-muted-symbolic' 'audio-volume-muted-symbolic'
); );
this.volumeScale = new Gtk.Scale({ this.volumeScale = new Gtk.Scale({

View File

@@ -15,7 +15,6 @@ class ClapperPlayer extends PlayerBase
super._init(); super._init();
this.cursorInPlayer = false; this.cursorInPlayer = false;
this.is_local_file = false;
this.seek_done = true; this.seek_done = true;
this.dragAllowed = false; this.dragAllowed = false;
this.isWidgetDragging = false; this.isWidgetDragging = false;
@@ -32,9 +31,6 @@ class ClapperPlayer extends PlayerBase
this._maxVolume = Misc.getLinearValue(Misc.maxVolume); this._maxVolume = Misc.getLinearValue(Misc.maxVolume);
this._playlist = [];
this._trackId = 0;
this._hideCursorTimeout = null; this._hideCursorTimeout = null;
this._hideControlsTimeout = null; this._hideControlsTimeout = null;
this._updateTimeTimeout = null; this._updateTimeTimeout = null;
@@ -80,45 +76,24 @@ class ClapperPlayer extends PlayerBase
this._realizeSignal = this.widget.connect('realize', this._onWidgetRealize.bind(this)); this._realizeSignal = this.widget.connect('realize', this._onWidgetRealize.bind(this));
} }
set_media(source) set_uri(uri)
{ {
let file; if(Gst.Uri.get_protocol(uri) !== 'file')
return super.set_uri(uri);
if(source.get_path)
file = source;
else {
if(!Gst.uri_is_valid(source))
source = Gst.filename_to_uri(source);
if(!source)
return debug('parsing source to URI failed');
debug(`parsed source to URI: ${source}`);
if(Gst.Uri.get_protocol(source) !== 'file') {
this.is_local_file = false;
return this.set_uri(source);
}
file = Gio.file_new_for_uri(source);
}
let file = Gio.file_new_for_uri(uri);
if(!file.query_exists(null)) { if(!file.query_exists(null)) {
debug(`file does not exist: ${file.get_path()}`, 'LEVEL_WARNING'); debug(`file does not exist: ${file.get_path()}`, 'LEVEL_WARNING');
this._trackId++;
if(this._playlist.length <= this._trackId) if(!this.playlistWidget.nextTrack())
return debug('set media reached end of playlist'); debug('set media reached end of playlist');
return this.set_media(this._playlist[this._trackId]); return;
} }
const uri = file.get_uri();
if(uri.endsWith('.claps')) if(uri.endsWith('.claps'))
return this.load_playlist_file(file); return this.load_playlist_file(file);
this.is_local_file = true; super.set_uri(uri);
this.set_uri(uri);
} }
load_playlist_file(file) load_playlist_file(file)
@@ -140,7 +115,7 @@ class ClapperPlayer extends PlayerBase
if(!lineFile) if(!lineFile)
continue; continue;
line = lineFile.get_path(); line = lineFile.get_uri();
} }
debug(`new playlist item: ${line}`); debug(`new playlist item: ${line}`);
playlist.push(line); playlist.push(line);
@@ -149,20 +124,29 @@ class ClapperPlayer extends PlayerBase
this.set_playlist(playlist); this.set_playlist(playlist);
} }
set_playlist(playlist) _preparePlaylist(playlist)
{ {
if(!Array.isArray(playlist) || !playlist.length) this.playlistWidget.removeAll();
return;
this._trackId = 0; for(let source of playlist) {
this._playlist = playlist; const uri = (source.get_uri != null)
? source.get_uri()
: Gst.uri_is_valid(source)
? source
: Gst.filename_to_uri(source);
this.set_media(this._playlist[0]); this.playlistWidget.addItem(uri);
}
} }
get_playlist() set_playlist(playlist)
{ {
return this._playlist; this._preparePlaylist(playlist);
const firstTrack = this.playlistWidget.get_row_at_index(0);
if(!firstTrack) return;
firstTrack.activate();
} }
set_subtitles(source) set_subtitles(source)
@@ -433,14 +417,15 @@ class ClapperPlayer extends PlayerBase
_onStreamEnded(player) _onStreamEnded(player)
{ {
debug(`end of stream: ${this._trackId}`); const lastTrackId = this.playlistWidget.activeRowId;
this.emitWs('end_of_stream', this._trackId);
this._trackId++; debug(`end of stream: ${lastTrackId}`);
this.emitWs('end_of_stream', lastTrackId);
if(this._trackId < this._playlist.length) if(this.playlistWidget.nextTrack())
this.set_media(this._playlist[this._trackId]); return;
else if(settings.get_boolean('close-auto')) {
if(settings.get_boolean('close-auto')) {
/* Stop will be automatically called soon afterwards */ /* Stop will be automatically called soon afterwards */
this._performCloseCleanup(this.widget.get_root()); this._performCloseCleanup(this.widget.get_root());
this.quitOnStop = true; this.quitOnStop = true;

View File

@@ -1,6 +1,7 @@
const { Gio, GLib, GObject, Gst, GstPlayer, Gtk } = imports.gi; const { Gio, GLib, GObject, Gst, GstPlayer, Gtk } = imports.gi;
const Debug = imports.clapper_src.debug; const Debug = imports.clapper_src.debug;
const Misc = imports.clapper_src.misc; const Misc = imports.clapper_src.misc;
const { PlaylistWidget } = imports.clapper_src.playlist;
const { WebApp } = imports.clapper_src.webApp; const { WebApp } = imports.clapper_src.webApp;
const { debug } = Debug; const { debug } = Debug;
@@ -54,6 +55,7 @@ class ClapperPlayerBase extends GstPlayer.Player
this.webserver = null; this.webserver = null;
this.webapp = null; this.webapp = null;
this.playlistWidget = new PlaylistWidget();
this.set_all_plugins_ranks(); this.set_all_plugins_ranks();
this.set_initial_config(); this.set_initial_config();

207
clapper_src/playlist.js Normal file
View File

@@ -0,0 +1,207 @@
const { Gdk, GLib, GObject, Gst, Gtk, Pango } = imports.gi;
var PlaylistWidget = GObject.registerClass(
class ClapperPlaylistWidget extends Gtk.ListBox
{
_init()
{
super._init({
selection_mode: Gtk.SelectionMode.NONE,
});
this.activeRowId = -1;
this.connect('row-activated', this._onRowActivated.bind(this));
}
addItem(uri)
{
const item = new PlaylistItem(uri);
this.append(item);
}
removeItem(item)
{
const itemIndex = item.get_index();
/* TODO: Handle this case somehow (should app quit?)
* or disable remove button */
if(itemIndex === this.activeRowId)
return;
if(itemIndex < this.activeRowId)
this.activeRowId--;
this.remove(item);
}
removeAll()
{
let oldItem;
while((oldItem = this.get_row_at_index(0)))
this.remove(oldItem);
this.activeRowId = -1;
}
nextTrack()
{
const nextRow = this.get_row_at_index(this.activeRowId + 1);
if(!nextRow)
return false;
nextRow.activate();
return true;
}
getActiveFilename()
{
const row = this.get_row_at_index(this.activeRowId);
if(!row) return null;
return row.filename;
}
/* FIXME: Remove once/if GstPlay(er) gets
* less vague MediaInfo signals */
getActiveIsLocalFile()
{
const row = this.get_row_at_index(this.activeRowId);
if(!row) return null;
return row.isLocalFile;
}
_onRowActivated(listBox, row)
{
const { player } = this.get_ancestor(Gtk.Grid);
this.activeRowId = row.get_index();
player.set_uri(row.uri);
}
});
let PlaylistItem = GObject.registerClass(
class ClapperPlaylistItem extends Gtk.ListBoxRow
{
_init(uri)
{
super._init();
this.uri = uri;
this.isLocalFile = false;
let filename;
if(Gst.Uri.get_protocol(uri) === 'file') {
filename = GLib.path_get_basename(
GLib.filename_from_uri(uri)[0]
);
this.isLocalFile = true;
}
this.filename = filename || uri;
const box = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 6,
margin_start: 6,
margin_end: 6,
height_request: 22,
});
const icon = new Gtk.Image({
icon_name: 'open-menu-symbolic',
});
const label = new Gtk.Label({
label: filename,
single_line_mode: true,
ellipsize: Pango.EllipsizeMode.END,
width_chars: 5,
hexpand: true,
halign: Gtk.Align.START,
});
const button = new Gtk.Button({
icon_name: 'edit-delete-symbolic',
});
button.add_css_class('flat');
button.add_css_class('circular');
button.add_css_class('popoverbutton');
button.connect('clicked', this._onRemoveClicked.bind(this));
box.append(icon);
box.append(label);
box.append(button);
this.set_child(box);
/* FIXME: D&D inside popover is broken in GTK4
const dragSource = new Gtk.DragSource({
actions: Gdk.DragAction.MOVE
});
dragSource.connect('prepare', this._onDragPrepare.bind(this));
dragSource.connect('drag-begin', this._onDragBegin.bind(this));
dragSource.connect('drag-end', this._onDragEnd.bind(this));
this.add_controller(dragSource);
const dropTarget = new Gtk.DropTarget({
actions: Gdk.DragAction.MOVE,
preload: true,
});
dropTarget.set_gtypes([PlaylistItem]);
dropTarget.connect('enter', this._onEnter.bind(this));
dropTarget.connect('drop', this._onDrop.bind(this));
this.add_controller(dropTarget);
*/
}
_onRemoveClicked(button)
{
const listBox = this.get_ancestor(Gtk.ListBox);
listBox.removeItem(this);
}
_onDragPrepare(source, x, y)
{
const widget = source.get_widget();
const paintable = new Gtk.WidgetPaintable({ widget });
const staticImg = paintable.get_current_image();
source.set_icon(staticImg, x, y);
return Gdk.ContentProvider.new_for_value(widget);
}
_onDragBegin(source, drag)
{
this.child.set_opacity(0.3);
}
_onDragEnd(source, drag, deleteData)
{
this.child.set_opacity(1.0);
}
_onEnter(target, x, y)
{
return (target.value)
? Gdk.DragAction.MOVE
: 0;
}
_onDrop(target, value, x, y)
{
const destIndex = this.get_index();
const targetIndex = target.value.get_index();
if(destIndex === targetIndex)
return true;
const listBox = this.get_ancestor(Gtk.ListBox);
if(listBox && destIndex >= 0) {
listBox.remove(target.value);
listBox.insert(target.value, destIndex);
return true;
}
return false;
}
});

View File

@@ -1,4 +1,4 @@
const { Gdk, GLib, GObject, Gst, GstPlayer, Gtk } = imports.gi; const { Gdk, GLib, GObject, GstPlayer, Gtk } = imports.gi;
const { Controls } = imports.clapper_src.controls; const { Controls } = imports.clapper_src.controls;
const Debug = imports.clapper_src.debug; const Debug = imports.clapper_src.debug;
const Misc = imports.clapper_src.misc; const Misc = imports.clapper_src.misc;
@@ -45,6 +45,8 @@ class ClapperWidget extends Gtk.Grid
this.mapSignal = this.connect('map', this._onMap.bind(this)); this.mapSignal = this.connect('map', this._onMap.bind(this));
this.player = new Player(); this.player = new Player();
this.controls.elapsedButton.scrolledWindow.set_child(this.player.playlistWidget);
this.player.connect('position-updated', this._onPlayerPositionUpdated.bind(this)); this.player.connect('position-updated', this._onPlayerPositionUpdated.bind(this));
this.player.connect('duration-changed', this._onPlayerDurationChanged.bind(this)); this.player.connect('duration-changed', this._onPlayerDurationChanged.bind(this));
@@ -324,17 +326,10 @@ class ClapperWidget extends Gtk.Grid
updateTitles(mediaInfo) updateTitles(mediaInfo)
{ {
let title = mediaInfo.get_title(); let title = mediaInfo.get_title();
let subtitle = mediaInfo.get_uri(); let subtitle = this.player.playlistWidget.getActiveFilename();
if(Gst.Uri.get_protocol(subtitle) === 'file') {
subtitle = GLib.path_get_basename(
GLib.filename_from_uri(subtitle)[0]
);
}
if(!title) { if(!title) {
title = (!subtitle) title = (subtitle.includes('.'))
? this.defaultTitle
: (subtitle.includes('.'))
? subtitle.split('.').slice(0, -1).join('.') ? subtitle.split('.').slice(0, -1).join('.')
: subtitle; : subtitle;
@@ -461,7 +456,7 @@ class ClapperWidget extends Gtk.Grid
this.controls.positionScale.clear_marks(); this.controls.positionScale.clear_marks();
this.controls.chapters = null; this.controls.chapters = null;
} }
if(!player.is_local_file) { if(!player.playlistWidget.getActiveIsLocalFile()) {
this.needsTracksUpdate = true; this.needsTracksUpdate = true;
} }
break; break;

View File

@@ -22,6 +22,10 @@ radio {
min-width: 17px; min-width: 17px;
min-height: 17px; min-height: 17px;
} }
/* Adwaita is missing osd ListBox */
.osd list {
background: none;
}
.osd .playercontrols { .osd .playercontrols {
-gtk-icon-size: 24px; -gtk-icon-size: 24px;
} }
@@ -70,6 +74,10 @@ radio {
font-weight: 600; font-weight: 600;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
.popoverbutton {
min-width: 24px;
min-height: 24px;
}
/* Position Scale */ /* Position Scale */
.positionscale { .positionscale {