diff --git a/data/com.github.rafostar.Clapper.gschema.xml b/data/com.github.rafostar.Clapper.gschema.xml
index f0789201..93c3e91a 100644
--- a/data/com.github.rafostar.Clapper.gschema.xml
+++ b/data/com.github.rafostar.Clapper.gschema.xml
@@ -32,6 +32,14 @@
"second"
Unit to use with seeking value
+
+ true
+ Ask to resume unfinished video
+
+
+ '[]'
+ Data storing unfinished videos resume info
+
diff --git a/src/dialogs.js b/src/dialogs.js
index 4c153d70..10ce82da 100644
--- a/src/dialogs.js
+++ b/src/dialogs.js
@@ -162,6 +162,45 @@ class ClapperUriDialog extends Gtk.Dialog
}
});
+var ResumeDialog = GObject.registerClass(
+class ClapperResumeDialog extends Gtk.MessageDialog
+{
+ _init(window, resumeInfo)
+ {
+ const percentage = Math.round((resumeInfo.time / resumeInfo.duration) * 100);
+
+ const msg = [
+ `Title: ${resumeInfo.title}`,
+ `Completed: ${percentage}%`
+ ].join('\n');
+
+ super._init({
+ transient_for: window,
+ modal: true,
+ message_type: Gtk.MessageType.QUESTION,
+ buttons: Gtk.ButtonsType.YES_NO,
+ text: 'Resume playback?',
+ secondary_use_markup: true,
+ secondary_text: msg,
+ });
+
+ this.resumeInfo = resumeInfo;
+ this.connect('response', this._onResponse.bind(this));
+
+ this.show();
+ }
+
+ _onResponse(dialog, respId)
+ {
+ const { player } = this.transient_for.child;
+
+ if(respId === Gtk.ResponseType.YES)
+ player.seek_seconds(this.resumeInfo.time);
+
+ this.destroy();
+ }
+});
+
var PrefsDialog = GObject.registerClass(
class ClapperPrefsDialog extends Gtk.Dialog
{
diff --git a/src/player.js b/src/player.js
index f6092ea0..c57597ac 100644
--- a/src/player.js
+++ b/src/player.js
@@ -372,6 +372,27 @@ class ClapperPlayer extends PlayerBase
if(size[0] > 0 && size[1] > 0)
clapperWidget._saveWindowSize(size);
}
+ /* If "quitOnStop" is set here it means that we are in middle of autoclosing */
+ if(this.state !== GstClapper.ClapperState.STOPPED && !this.quitOnStop) {
+ let resumeInfo = {};
+ if(settings.get_boolean('resume-enabled')) {
+ const resumeTime = Math.floor(this.position / 1000000000);
+ const resumeDuration = this.duration / 1000000000;
+
+ /* Do not save resume info when video is short, just started or almost finished */
+ if(resumeDuration > 60 && resumeTime > 15 && resumeDuration - resumeTime > 20) {
+ resumeInfo.title = this.playlistWidget.getActiveFilename();
+ resumeInfo.time = resumeTime;
+ resumeInfo.duration = resumeDuration;
+
+ debug(`saving resume info for: ${resumeInfo.title}`);
+ debug(`resume time: ${resumeInfo.time}, duration: ${resumeInfo.duration}`);
+ }
+ else
+ debug('resume info is not worth saving');
+ }
+ settings.set_string('resume-database', JSON.stringify([resumeInfo]));
+ }
settings.set_double('volume-last', this.volume);
clapperWidget.controls._onCloseRequest();
@@ -427,8 +448,8 @@ class ClapperPlayer extends PlayerBase
if(settings.get_boolean('close-auto')) {
/* Stop will be automatically called soon afterwards */
- this._performCloseCleanup(this.widget.get_root());
this.quitOnStop = true;
+ this._performCloseCleanup(this.widget.get_root());
}
}
diff --git a/src/prefs.js b/src/prefs.js
index 1e516d15..c780dba7 100644
--- a/src/prefs.js
+++ b/src/prefs.js
@@ -70,6 +70,9 @@ class ClapperBehaviourPage extends PrefsBase.Grid
['percentage', "Percentage"],
], 'seeking-unit');
this.addSpinButton('Value', 1, 99, 'seeking-value');
+
+ this.addTitle('Resume');
+ this.addCheckButton('Ask to resume last unfinished video', 'resume-enabled');
}
});
diff --git a/src/widget.js b/src/widget.js
index 2298b9ec..8224066a 100644
--- a/src/widget.js
+++ b/src/widget.js
@@ -1,6 +1,7 @@
const { Gdk, GLib, GObject, GstClapper, Gtk } = imports.gi;
const { Controls } = imports.src.controls;
const Debug = imports.src.debug;
+const Dialogs = imports.src.dialogs;
const Misc = imports.src.misc;
const { Player } = imports.src.player;
const Revealers = imports.src.revealers;
@@ -487,21 +488,45 @@ class ClapperWidget extends Gtk.Grid
this.revealerTop.endTime.set_visible(isNotStopped);
}
- _onPlayerDurationChanged(player)
+ _onPlayerDurationChanged(player, duration)
{
- const duration = Math.floor(player.get_duration() / 1000000000);
+ const durationSeconds = duration / 1000000000;
+ const durationFloor = Math.floor(durationSeconds);
/* Sometimes GstPlayer might re-emit
* duration changed during playback */
- if(this.controls.currentDuration === duration)
+ if(this.controls.currentDuration === durationFloor)
return;
- this.controls.currentDuration = duration;
- this.controls.showHours = (duration >= 3600);
+ this.controls.currentDuration = durationFloor;
+ this.controls.showHours = (durationFloor >= 3600);
- this.controls.positionAdjustment.set_upper(duration);
- this.controls.durationFormatted = Misc.getFormattedTime(duration);
+ this.controls.positionAdjustment.set_upper(durationFloor);
+ this.controls.durationFormatted = Misc.getFormattedTime(durationFloor);
this.controls.updateElapsedLabel();
+
+ if(settings.get_boolean('resume-enabled')) {
+ const resumeDatabase = JSON.parse(settings.get_string('resume-database'));
+ const title = player.playlistWidget.getActiveFilename();
+
+ debug(`searching database for resume info: ${title}`);
+
+ const resumeInfo = resumeDatabase.find(info => {
+ return (info.title === title && info.duration === durationSeconds);
+ });
+
+ if(resumeInfo) {
+ debug('found resume info: ' + JSON.stringify(resumeInfo));
+ new Dialogs.ResumeDialog(this.root, resumeInfo);
+
+ const shrunkDatabase = resumeDatabase.filter(info => {
+ return !(info.title === title && info.duration === durationSeconds);
+ });
+ settings.set_string('resume-database', JSON.stringify(shrunkDatabase));
+ }
+ else
+ debug('resume info not found');
+ }
}
_onPlayerPositionUpdated(player, position)