diff --git a/TODO.md b/TODO.md index aaba4865..8f1c7c09 100644 --- a/TODO.md +++ b/TODO.md @@ -6,7 +6,7 @@ - [X] Switching video/audio/subtitles tracks from bottom bar (MPV) - [X] Over-amplification supported by default (VLC) - [X] Audio visualizations (VLC) -- [ ] Clock with current hour and "Ends at" time on top overlay (Kodi) +- [X] Clock with current hour and "Ends at" time on top overlay (Kodi) - [ ] Auto select subtitles matching OS language (Totem) - [ ] Picture-in-Picture mode window - [ ] Touch gestures/swipes support diff --git a/clapper_src/app.js b/clapper_src/app.js index 4ef618c2..1cf25ae3 100644 --- a/clapper_src/app.js +++ b/clapper_src/app.js @@ -70,8 +70,24 @@ var App = GObject.registerClass({ this.hideControlsTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3, () => { this.hideControlsTimeout = null; - if(this.window.isFullscreen && this.isCursorInPlayer) + if(this.window.isFullscreen && this.isCursorInPlayer) { + this.clearTimeout('updateTime'); this.interface.revealControls(false); + } + + return GLib.SOURCE_REMOVE; + }); + } + + setUpdateTimeInterval() + { + this.clearTimeout('updateTime'); + let nextUpdate = this.interface.updateTime(); + this.updateTimeTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, nextUpdate, () => { + this.updateTimeTimeout = null; + + if(this.window.isFullscreen) + this.setUpdateTimeInterval(); return GLib.SOURCE_REMOVE; }); @@ -84,6 +100,9 @@ var App = GObject.registerClass({ GLib.source_remove(this[`${name}Timeout`]); this[`${name}Timeout`] = null; + + if(name === 'updateTime') + debug('cleared update time interval'); } _buildUI() @@ -146,6 +165,9 @@ var App = GObject.registerClass({ this.player.connect('error', this._onPlayerError.bind(this)); this.player.connect('state-changed', this._onPlayerStateChanged.bind(this)); + this.interface.revealerTop.connect( + 'button-press-event', this._onPlayerButtonPressEvent.bind(this) + ); this.player.connectWidget( 'button-press-event', this._onPlayerButtonPressEvent.bind(this) ); @@ -177,12 +199,15 @@ var App = GObject.registerClass({ this.interface.controls.setVolumeMarks(false); if(isFullscreen) { + this.setUpdateTimeInterval(); this.interface.showControls(true); this.setHideControlsTimeout(); this.interface.controls.unfullscreenButton.set_sensitive(true); this.interface.controls.unfullscreenButton.show(); } else { + this.clearTimeout('updateTime'); + this.interface.showControls(false); this.interface.controls.unfullscreenButton.set_sensitive(false); this.interface.controls.unfullscreenButton.hide(); } @@ -308,7 +333,7 @@ var App = GObject.registerClass({ this._handlePrimaryButtonPress(event, button); break; case Gdk.BUTTON_SECONDARY: - if(event.get_event_type() !== Gdk.EventType.DOUBLE_BUTTON_PRESS) + if(event.get_event_type() === Gdk.EventType.BUTTON_PRESS) this.player.toggle_play(); break; default: @@ -360,8 +385,11 @@ var App = GObject.registerClass({ this.setHideCursorTimeout(); if(this.window.isFullscreen) { + if(!this.interface.revealerTop.get_reveal_child()) { + this.setUpdateTimeInterval(); + this.interface.revealControls(true); + } this.setHideControlsTimeout(); - this.interface.revealControls(true); } else if(this.hideControlsTimeout) { this.clearTimeout('hideControls'); diff --git a/clapper_src/interface.js b/clapper_src/interface.js index d5ceddbb..fe00abd2 100644 --- a/clapper_src/interface.js +++ b/clapper_src/interface.js @@ -1,4 +1,4 @@ -const { GLib, GObject, Gtk, Gst, GstPlayer } = imports.gi; +const { Gdk, GLib, GObject, Gtk, Gst, GstPlayer } = imports.gi; const { Controls } = imports.clapper_src.controls; const Debug = imports.clapper_src.debug; @@ -26,23 +26,64 @@ class ClapperInterface extends Gtk.Grid this.headerBar = null; this.defaultTitle = null; + let initTime = GLib.DateTime.new_now_local().format('%X'); + this.timeFormat = (initTime.length > 8) + ? '%I:%M %p' + : '%H:%M'; + this.videoBox = new Gtk.Box(); this.overlay = new Gtk.Overlay(); - this.revealer = new Gtk.Revealer({ + this.revealerTop = new Gtk.Revealer({ + transition_duration: this.revealTime, + transition_type: Gtk.RevealerTransitionType.CROSSFADE, + valign: Gtk.Align.START, + }); + this.revealerBottom = new Gtk.Revealer({ transition_duration: this.revealTime, transition_type: Gtk.RevealerTransitionType.SLIDE_UP, valign: Gtk.Align.END, }); - this.revealerBox = new Gtk.Box(); + this.revealerGridTop = new Gtk.Grid(); + this.revealerBoxBottom = new Gtk.Box(); this.controls = new Controls(); + this.fsTitle = new Gtk.Label({ + expand: true, + margin_left: 12, + xalign: 0, + yalign: 0.22, + }); + + let timeLabelOpts = { + margin_right: 10, + xalign: 1, + yalign: 0, + }; + this.fsTime = new Gtk.Label(timeLabelOpts); + this.fsEndTime = new Gtk.Label(timeLabelOpts); + + this.revealerGridTop.attach(this.fsTitle, 0, 0, 1, 1); + this.revealerGridTop.attach(this.fsTime, 1, 0, 1, 1); + this.revealerGridTop.attach(this.fsEndTime, 1, 0, 1, 1); + this.videoBox.get_style_context().add_class('videobox'); - this.revealerBox.get_style_context().add_class('osd'); + let revealerGridTopContext = this.revealerGridTop.get_style_context(); + revealerGridTopContext.add_class('osd'); + revealerGridTopContext.add_class('reavealertop'); + this.revealerBoxBottom.get_style_context().add_class('osd'); + + this.fsTime.get_style_context().add_class('osdtime'); + this.fsEndTime.get_style_context().add_class('osdendtime'); this.videoBox.pack_start(this.overlay, true, true, 0); - this.revealer.add(this.revealerBox); + this.revealerBottom.add(this.revealerBoxBottom); + this.revealerTop.add(this.revealerGridTop); this.attach(this.videoBox, 0, 0, 1, 1); this.attach(this.controls, 0, 1, 1, 1); + + this.revealerTop.add_events(Gdk.EventMask.BUTTON_PRESS_MASK); + this.revealerTop.show_all(); + this.revealerBottom.show_all(); } addPlayer(player) @@ -89,16 +130,18 @@ class ClapperInterface extends Gtk.Grid revealControls(isReveal) { - this.revealer.set_transition_duration(this.revealTime); - this.revealer.set_transition_type(Gtk.RevealerTransitionType.SLIDE_UP); - this.revealer.set_reveal_child(isReveal); + for(let pos of ['Bottom', 'Top']) { + this[`revealer${pos}`].set_transition_duration(this.revealTime); + this[`revealer${pos}`].set_reveal_child(isReveal); + } } showControls(isShow) { - this.revealer.set_transition_duration(0); - this.revealer.set_transition_type(Gtk.RevealerTransitionType.NONE); - this.revealer.set_reveal_child(isShow); + for(let pos of ['Bottom', 'Top']) { + this[`revealer${pos}`].set_transition_duration(0); + this[`revealer${pos}`].set_reveal_child(isShow); + } } setControlsOnVideo(isOnVideo) @@ -109,17 +152,16 @@ class ClapperInterface extends Gtk.Grid if(isOnVideo) { this.remove(this.controls); this.controls.pack_start(this.controls.unfullscreenButton.box, false, false, 0); - this.overlay.add_overlay(this.revealer); - this.revealerBox.pack_start(this.controls, false, true, 0); - this.revealer.show(); - this.revealerBox.show(); + this.overlay.add_overlay(this.revealerBottom); + this.overlay.add_overlay(this.revealerTop); + this.revealerBoxBottom.pack_start(this.controls, false, true, 0); } else { - this.revealerBox.remove(this.controls); + this.revealerBoxBottom.remove(this.controls); this.controls.remove(this.controls.unfullscreenButton.box); - this.overlay.remove(this.revealer); + this.overlay.remove(this.revealerBottom); + this.overlay.remove(this.revealerTop); this.attach(this.controls, 0, 1, 1, 1); - this.controls.show(); } this.controlsInVideo = isOnVideo; @@ -253,6 +295,27 @@ class ClapperInterface extends Gtk.Grid this.headerBar.set_title(title); this.headerBar.set_subtitle(subtitle); + + this.fsTitle.label = title; + } + + updateTime() + { + let currTime = GLib.DateTime.new_now_local(); + let endTime = currTime.add_seconds( + this.controls.positionAdjustment.get_upper() - this.lastPositionValue + ); + let now = currTime.format(this.timeFormat); + + this.fsTime.set_label(now); + this.fsEndTime.set_label(`Ends at: ${endTime.format(this.timeFormat)}`); + + // Make sure that next timeout is always run after clock changes, + // by delaying it for additional few milliseconds + let nextUpdate = 60002 - parseInt(currTime.get_seconds() * 1000); + debug(`updated current time: ${now}`); + + return nextUpdate; } showVisualizationsButton(isShow) @@ -444,6 +507,9 @@ class ClapperInterface extends Gtk.Grid this.lastPositionValue = positionSeconds; this._player.seek_seconds(positionSeconds); + + if(this.controls.fullscreenMode) + this.updateTime(); } _onControlsVolumeChanged(volumeScale) diff --git a/css/styles.css b/css/styles.css index 1f404d6a..8d10c645 100644 --- a/css/styles.css +++ b/css/styles.css @@ -24,6 +24,23 @@ scale marks { .videobox { background: black; } +.reavealertop { + min-height: 100px; + box-shadow: inset 0px 200px 10px -124px rgba(0,0,0,0.4); + font-size: 32px; + font-weight: 500; + background: transparent; +} +.osdtime { + margin-top: 0px; + font-size: 40px; + font-weight: 700; +} +.osdendtime { + margin-top: 42px; + font-size: 24px; + font-weight: 600; +} /* Position Scale */ .positionscale value {