[gnome-subtitles: 1/2] GStreamer media playback rewrite
- From: Pedro Castro <pcastro src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-subtitles: 1/2] GStreamer media playback rewrite
- Date: Sat, 15 May 2021 21:26:39 +0000 (UTC)
commit efedf76beb1986ea7462bfb3c18bb1b1673af1b5
Author: Pedro Castro <pedro gnomesubtitles org>
Date: Sat May 15 22:23:27 2021 +0100
GStreamer media playback rewrite
Closes #177. Key motivations are:
- it was based on code from the Fuse media project and contained unused
code, and other bits which weren't totally aligned with what's needed
in Gnome Subtitles
- it was hard to maintain
- fix a number of bugs
- implement an abstraction to facilitate adding support for other media
backends (mpv?) in the future
.gitignore | 15 +-
configure.ac | 8 +-
gnome-subtitles.csproj | 12 +-
src/External/GStreamerPlaybin/Engine.cs | 603 -----------------
src/External/GStreamerPlaybin/Events.cs | 107 ----
src/External/GStreamerPlaybin/main.c | 713 ---------------------
src/External/GstBackend/.vscode/settings.json | 4 +
src/External/GstBackend/GstBackend.cs | 210 ++++++
src/External/GstBackend/GstMediaInfo.cs | 76 +++
src/External/GstBackend/gst-backend.c | 421 ++++++++++++
.../Core/Command/InsertSubtitleCommand.cs | 4 +-
src/GnomeSubtitles/Core/EventHandlers.cs | 14 +-
src/GnomeSubtitles/Dialog/AboutDialog.cs | 2 +-
src/GnomeSubtitles/Dialog/BaseDialog.cs | 4 +-
.../Message/DialogUtil.cs} | 36 +-
.../Dialog/Message/FileOpenErrorDialog.cs | 75 ---
.../Dialog/Message/SubtitleFileOpenErrorDialog.cs | 47 +-
...oErrorDialog.cs => VideoFileOpenErrorDialog.cs} | 48 +-
src/GnomeSubtitles/Dialog/VideoSeekToDialog.cs | 8 +-
src/GnomeSubtitles/Execution/AssemblyInfo.cs.in | 4 +-
...itiateEngineException.cs => PlayerException.cs} | 12 +-
src/GnomeSubtitles/Ui/VideoPreview/MediaBackend.cs | 99 +++
src/GnomeSubtitles/Ui/VideoPreview/Player.cs | 310 +++++----
.../Ui/VideoPreview/PlayerPositionWatcher.cs | 21 +-
src/GnomeSubtitles/Ui/VideoPreview/Video.cs | 168 +++--
.../Ui/VideoPreview/VideoPosition.cs | 25 +-
src/Makefile.am | 14 +-
27 files changed, 1203 insertions(+), 1857 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 1fed8a1..6a189da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,8 +25,6 @@
/src/GnomeSubtitles/Execution/ConfigureDefines.cs
/src/GnomeSubtitles/Execution/AssemblyInfo.cs
/src/GnomeSubtitles/Execution/gnome-subtitles
-/src/libgstreamer_playbin.so
-/src/main.o
/src/Makefile
/src/Makefile.in
/build/*
@@ -49,9 +47,6 @@
/po/gnome-subtitles.pot
/config.sub
/config.guess
-/src/.libs/*
-/src/libgstreamer_playbin.la
-/src/main.lo
/m4/libtool.m4
/m4/lt~obsolete.m4
/m4/ltoptions.m4
@@ -59,11 +54,13 @@
/m4/ltversion.m4
/ltmain.sh
/libtool
-/src/External/GStreamerPlaybin/.deps/*
-/src/External/GStreamerPlaybin/.libs/*
-/src/External/GStreamerPlaybin/.dirstamp
-/src/External/GStreamerPlaybin/libgstreamer_playbin_la-main.lo
/po/missing
/.vs/gnome-subtitles/xs/UserPrefs.xml
/obj/*
compile
+/src/.libs
+/src/External/GstBackend/.deps
+/src/External/GstBackend/.dirstamp
+/src/External/GstBackend/.libs
+/src/External/GstBackend/libgst_backend_la-gst-backend.lo
+/src/libgst_backend.la
diff --git a/configure.ac b/configure.ac
index 37f58fa..c35f3f2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -74,8 +74,12 @@ GSTREAMER_REQUIRED_VERSION=1.0
PKG_CHECK_MODULES(MONO, [mono >= $MONO_REQUIRED_VERSION])
PKG_CHECK_MODULES(GTKSHARP, [gtk-sharp-3.0 >= $GTKSHARP_REQUIRED_VERSION])
PKG_CHECK_MODULES(GTK, [gtk+-3.0 >= $GTK_REQUIRED_VERSION])
-PKG_CHECK_MODULES(gstreamer, [gstreamer-video-1.0 >= $GSTREAMER_REQUIRED_VERSION])
-PKG_CHECK_MODULES(gstreamer_plugins_base, [gstreamer-plugins-base-1.0 >= $GSTREAMER_REQUIRED_VERSION])
+PKG_CHECK_MODULES(gstreamer, [
+ gstreamer-1.0 >= $GSTREAMER_REQUIRED_VERSION
+ gstreamer-video-1.0 >= $GSTREAMER_REQUIRED_VERSION
+ gstreamer-plugins-base-1.0 >= $GSTREAMER_REQUIRED_VERSION
+ gstreamer-pbutils-1.0 >= $GSTREAMER_REQUIRED_VERSION
+])
AC_SUBST(gstreamer_CFLAGS)
AC_SUBST(gstreamer_LIBS)
diff --git a/gnome-subtitles.csproj b/gnome-subtitles.csproj
index e0cc290..4d55860 100644
--- a/gnome-subtitles.csproj
+++ b/gnome-subtitles.csproj
@@ -64,8 +64,6 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
- <Compile Include="src\External\GStreamerPlaybin\Engine.cs" />
- <Compile Include="src\External\GStreamerPlaybin\Events.cs" />
<Compile Include="src\External\NCharDet\Big5Statistics.cs" />
<Compile Include="src\External\NCharDet\EUCJPStatistics.cs" />
<Compile Include="src\External\NCharDet\EUCKRStatistics.cs" />
@@ -151,11 +149,10 @@
<Compile Include="src\GnomeSubtitles\Dialog\VideoSeekToDialog.cs" />
<Compile Include="src\GnomeSubtitles\Dialog\Message\BasicErrorDialog.cs" />
<Compile Include="src\GnomeSubtitles\Dialog\Message\ErrorDialog.cs" />
- <Compile Include="src\GnomeSubtitles\Dialog\Message\FileOpenErrorDialog.cs" />
<Compile Include="src\GnomeSubtitles\Dialog\Message\FileSaveErrorDialog.cs" />
<Compile Include="src\GnomeSubtitles\Dialog\Message\SaveConfirmationDialog.cs" />
<Compile Include="src\GnomeSubtitles\Dialog\Message\SubtitleFileOpenErrorDialog.cs" />
- <Compile Include="src\GnomeSubtitles\Dialog\Message\VideoErrorDialog.cs" />
+ <Compile Include="src\GnomeSubtitles\Dialog\Message\VideoFileOpenErrorDialog.cs" />
<Compile Include="src\GnomeSubtitles\Dialog\Message\WarningDialog.cs" />
<Compile Include="src\GnomeSubtitles\Execution\AssemblyInfo.cs" />
<Compile Include="src\GnomeSubtitles\Execution\BugReporter.cs" />
@@ -182,8 +179,7 @@
<Compile Include="src\GnomeSubtitles\Ui\VideoPreview\Video.cs" />
<Compile Include="src\GnomeSubtitles\Ui\VideoPreview\VideoFiles.cs" />
<Compile Include="src\GnomeSubtitles\Ui\VideoPreview\VideoPosition.cs" />
- <Compile
Include="src\GnomeSubtitles\Ui\VideoPreview\Exceptions\PlayerCouldNotInitiateEngineException.cs" />
- <Compile Include="src\GnomeSubtitles\Ui\VideoPreview\Exceptions\PlayerEngineException.cs" />
+ <Compile Include="src\GnomeSubtitles\Ui\VideoPreview\Exceptions\PlayerException.cs" />
<Compile Include="src\GnomeSubtitles\Ui\View\CellRendererCenteredText.cs" />
<Compile Include="src\GnomeSubtitles\Ui\View\SelectionIntended.cs" />
<Compile Include="src\GnomeSubtitles\Ui\View\SelectionType.cs" />
@@ -279,6 +275,10 @@
<Compile Include="src\GnomeSubtitles\Ui\WindowState.cs" />
<Compile Include="src\External\Enchant\Enchant.cs" />
<Compile Include="src\External\Interop\Interop.cs" />
+ <Compile Include="src\External\GstBackend\GstBackend.cs" />
+ <Compile Include="src\External\GstBackend\GstMediaInfo.cs" />
+ <Compile Include="src\GnomeSubtitles\Ui\VideoPreview\MediaBackend.cs" />
+ <Compile Include="src\GnomeSubtitles\Dialog\Message\DialogUtil.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
diff --git a/src/External/GstBackend/.vscode/settings.json b/src/External/GstBackend/.vscode/settings.json
new file mode 100644
index 0000000..99e9c3f
--- /dev/null
+++ b/src/External/GstBackend/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "editor.insertSpaces": false,
+ "editor.detectIndentation": false
+}
\ No newline at end of file
diff --git a/src/External/GstBackend/GstBackend.cs b/src/External/GstBackend/GstBackend.cs
new file mode 100644
index 0000000..2184adb
--- /dev/null
+++ b/src/External/GstBackend/GstBackend.cs
@@ -0,0 +1,210 @@
+/*
+ * This file is part of Gnome Subtitles.
+ * Copyright (C) 2021 Pedro Castro
+ *
+ * Gnome Subtitles is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Gnome Subtitles is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using GnomeSubtitles.Core;
+using GnomeSubtitles.Ui.VideoPreview;
+using Gtk;
+using Mono.Unix;
+using SubLib.Util;
+using System;
+using System.Runtime.InteropServices;
+
+/*
+ * There are many ways to integrate gstreamer into an application. This implementation uses a gstreamer
+ * playbin (which is a gstreamer pipeline with a number of additional features) with a video sink set to a
+ * gtksink (instead of the default "auto" video sink). Once initialized, this video sink allows returning
+ * its GtkWidget, which is then added to the application UI. Most of the hard work is done in the auxiliary
+ * gst_backend C library, whereas here we use bindings to that library.
+ *
+ * Previously, this implementation relied on gst_video_overlay_set_window_handle which received the X11 id
+ * of the Gdk.Window to which we wanted gstreamer to attach to, however this implementation presented many
+ * glitches which weren't simple to solve (ex: screen went black when switching between apps, and also when
+ * updating the current subtitle while the video was paused).
+ *
+ * Reference to other ways to implement gstreamer into a GTK application:
+ * - https://github.com/GNOME/clutter-gst (does a lot more than a simple integration; used in Totem)
+ * - https://developer.gnome.org/totem/stable/BaconVideoWidget.html (Totem bacon video widget)
+ */
+
+namespace External.GStreamer {
+
+ /* Enums */
+
+ public class GstBackend : MediaBackend {
+ private HandleRef backend;
+ private GstMediaInfo mediaInfo = null;
+ private float speed = 1f; //Keeping it here as we need to pass it to every 'seek' call
+
+ /* Delegates */
+ private delegate void ErrorFoundHandler(ErrorType type, string errorMessage, string
debugMessage);
+ private delegate void LoadCompleteHandler(GstMediaInfo mediaInfo);
+
+ /* Enums */
+ private enum ErrorType { GStreamer, NoMediaInfo, NoVideoOrAudio};
+
+ /* Error messages */
+ private readonly string ErrorMessageNoMediaInfo = Catalog.GetString("Unable to obtain the
complete media information.");
+ private readonly string ErrorMessageNoVideoOrAudio = Catalog.GetString("The file contains no
video or audio.");
+
+ /* Public properties */
+
+ public override string Name {
+ get { return "GStreamer"; }
+ }
+
+ public override long CurrentPosition {
+ get { return gst_backend_get_position(backend); }
+ }
+
+ public override long Duration {
+ get { return mediaInfo.Duration; }
+ }
+
+ public override bool HasVideo {
+ get { return mediaInfo.HasVideo; }
+ }
+
+ public override float AspectRatio {
+ get { return mediaInfo.AspectRatio; }
+ }
+
+ public override float FrameRate {
+ get { return mediaInfo.FrameRate; }
+ }
+
+ public override bool HasAudio {
+ get { return mediaInfo.HasAudio; }
+ }
+
+
+ /* Public methods */
+
+ public override void Initialize() {
+ IntPtr ptr = gst_backend_init(OnErrorFound, OnLoadComplete, OnEndOfStreamReached);
+ if (ptr == IntPtr.Zero) {
+ throw new Exception("Unable to initialize gstreamer: gst_backend_init
returned no pointer.");
+ }
+
+ backend = new HandleRef(this, ptr);
+ }
+
+ public override bool Load(string uri) {
+ SetStatus(MediaStatus.Loading);
+
+ return gst_backend_load(backend, uri);
+ }
+ public override void Unload() {
+ gst_backend_unload(backend);
+ SetStatus(MediaStatus.Unloaded);
+
+ mediaInfo = null;
+ speed = 1f;
+ }
+
+ public override void Dispose() {
+ //Do nothing
+ }
+
+ public override void Play() {
+ gst_backend_play(backend);
+ SetStatus(MediaStatus.Playing);
+ }
+
+ public override void Pause() {
+ gst_backend_pause(backend);
+ SetStatus(MediaStatus.Paused);
+ }
+
+ public override void Seek(long time, bool isAbsolute) {
+ if ((Status == MediaStatus.Unloaded) || (Status == MediaStatus.Loading)) {
+ return;
+ }
+ gst_backend_seek(backend, time, isAbsolute, speed);
+ }
+
+ public override void SetSpeed (float speed) {
+ if ((Status == MediaStatus.Unloaded) || (Status == MediaStatus.Loading)) {
+ return;
+ }
+
+ this.speed = speed;
+ gst_backend_set_speed(backend, speed);
+ }
+
+ public override Widget CreateVideoWidget() {
+ return new Widget(gst_backend_get_video_widget(backend));
+ }
+
+
+ /* Event handlers */
+
+ private void OnLoadComplete(GstMediaInfo mediaInfo) {
+ this.mediaInfo = mediaInfo;
+ SetStatus(MediaStatus.Loaded);
+ }
+
+ private void OnErrorFound(ErrorType code, string userMessage, string debugMessage) {
+ //If we have a debug message, output it to the logs
+ if (!String.IsNullOrEmpty(debugMessage)) {
+ Logger.Error("[GstBackend] OnErrorFound debug message: {0}", debugMessage);
+ }
+
+ //If we have a user message, use it. Otherwise, show an error message according to
the specified code.
+ string errorMessage = userMessage;
+ if (String.IsNullOrEmpty(userMessage)) {
+ if (code == ErrorType.NoMediaInfo) {
+ errorMessage = ErrorMessageNoMediaInfo;
+ } else if (code == ErrorType.NoVideoOrAudio) {
+ errorMessage = ErrorMessageNoVideoOrAudio;
+ }
+ }
+ if (String.IsNullOrEmpty(errorMessage)) {
+ Logger.Error("[GstBackend] Got an empty error message for code '{0}'", code);
+ }
+ TriggerErrorFound(errorMessage);
+ }
+
+ private void OnEndOfStreamReached() {
+ TriggerEndOfStreamReached();
+ }
+
+
+ /* Imports */
+
+ [DllImport("gst_backend")]
+ private static extern IntPtr gst_backend_init(ErrorFoundHandler onErrorFound,
LoadCompleteHandler onLoadComplete, BasicEventHandler onEndOfStreamReached);
+ [DllImport("gst_backend")]
+ private static extern bool gst_backend_load(HandleRef backend, string uri);
+ [DllImport("gst_backend")]
+ private static extern void gst_backend_unload(HandleRef backend);
+ [DllImport("gst_backend")]
+ private static extern void gst_backend_play(HandleRef backend);
+ [DllImport("gst_backend")]
+ private static extern void gst_backend_pause(HandleRef backend);
+ [DllImport("gst_backend")]
+ private static extern long gst_backend_get_position(HandleRef backend);
+ [DllImport("gst_backend")]
+ private static extern void gst_backend_seek(HandleRef backend, long time, bool isAbsolute,
float speed);
+ [DllImport("gst_backend")]
+ private static extern void gst_backend_set_speed(HandleRef backend, float speed);
+ [DllImport("gst_backend")]
+ private static extern IntPtr gst_backend_get_video_widget(HandleRef backend);
+ }
+
+}
diff --git a/src/External/GstBackend/GstMediaInfo.cs b/src/External/GstBackend/GstMediaInfo.cs
new file mode 100644
index 0000000..e7022a1
--- /dev/null
+++ b/src/External/GstBackend/GstMediaInfo.cs
@@ -0,0 +1,76 @@
+/*
+ * This file is part of Gnome Subtitles.
+ * Copyright (C) 2021 Pedro Castro
+ *
+ * Gnome Subtitles is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Gnome Subtitles is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace External.GStreamer {
+
+[StructLayout(LayoutKind.Sequential)]
+public class GstMediaInfo {
+ long duration;
+
+ bool has_video;
+ int width;
+ int height;
+ float aspect_ratio;
+ float frame_rate;
+
+ bool has_audio;
+
+ public GstMediaInfo(IntPtr ptr) {
+ if (ptr != IntPtr.Zero) {
+ Marshal.PtrToStructure(ptr, this);
+ }
+ }
+
+
+ /* Public properties */
+
+ public long Duration {
+ get { return duration; }
+ }
+
+ public bool HasVideo {
+ get { return has_video; }
+ }
+
+ public int Width {
+ get { return width; }
+ }
+
+ public int Height
+ { get { return height; }
+ }
+
+ public float AspectRatio {
+ get { return aspect_ratio; }
+ }
+
+ public float FrameRate {
+ get { return frame_rate; }
+ }
+
+ public bool HasAudio {
+ get { return has_audio; }
+ }
+
+}
+
+}
diff --git a/src/External/GstBackend/gst-backend.c b/src/External/GstBackend/gst-backend.c
new file mode 100644
index 0000000..b166f26
--- /dev/null
+++ b/src/External/GstBackend/gst-backend.c
@@ -0,0 +1,421 @@
+/*
+ * This file is part of Gnome Subtitles.
+ * Copyright (C) 2021 Pedro Castro
+ *
+ * Gnome Subtitles is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Gnome Subtitles is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <gst/gst.h>
+#include <gst/pbutils/gstdiscoverer.h>
+
+
+/* Typedefs */
+
+typedef struct GstBackendMediaInfo GstBackendMediaInfo;
+typedef struct GstBackend GstBackend;
+
+
+/* Enums */
+
+enum GstBackendErrorType { GST_BACKEND_ERR_GSTREAMER, GST_BACKEND_ERR_NO_MEDIA_INFO,
GST_BACKEND_ERR_NO_VIDEO_OR_AUDIO };
+
+
+/* Callbacks */
+
+typedef void (*ErrorCallback)(enum GstBackendErrorType type, const gchar *user_message, const gchar
*debug_message);
+typedef void (*BasicCallback)();
+typedef void (*LoadCompleteCallback)(GstBackendMediaInfo *media_info);
+
+
+/* Structures */
+
+struct GstBackendMediaInfo {
+ /* video and audio */
+ gint64 duration;
+
+ /* video */
+ gboolean has_video;
+ gint width;
+ gint height;
+ gfloat aspect_ratio;
+ gfloat frame_rate;
+
+ /* audio */
+ gboolean has_audio;
+};
+
+struct GstBackend {
+ GstElement *element; //playbin
+ GstElement *video_element; //gtksink
+
+ GstBackendMediaInfo *media_info;
+
+ ErrorCallback error_callback;
+ LoadCompleteCallback load_complete_callback;
+ BasicCallback eos_reached_callback;
+};
+
+
+/* Static Functions */
+
+static GstBackend *gst_backend_new() {
+ GstBackend *backend = g_new0(GstBackend, 1);
+ return backend;
+}
+
+static GstBackendMediaInfo *gst_backend_media_info_new() {
+ GstBackendMediaInfo *media_info = g_new0(GstBackendMediaInfo, 1);
+
+ /* video and audio */
+ media_info->duration = -1;
+
+ /* video */
+ media_info->has_video = FALSE;
+ media_info->width = -1;
+ media_info->height = -1;
+ media_info->aspect_ratio = -1;
+ media_info->frame_rate = -1;
+
+ /* audio */
+ media_info->has_audio = FALSE;
+
+ return media_info;
+}
+
+static void throw_error(GstBackend *backend, enum GstBackendErrorType type, const gchar *user_message, const
gchar *debug_message) {
+ if (backend->error_callback != NULL) {
+ backend->error_callback(type, user_message, debug_message);
+ }
+}
+
+/*
+ * Blocks until a pending state change (if applicable) completes.
+ */
+static void wait_for_state_change_to_complete(GstBackend *backend) {
+ gst_element_get_state(backend->element, NULL, NULL, GST_CLOCK_TIME_NONE);
+}
+
+static gboolean media_has_video(GstBackend *backend) {
+ gint current_video;
+ g_object_get(backend->element, "current-video", ¤t_video, NULL);
+ return current_video != -1;
+}
+
+static gboolean media_has_audio(GstBackend *backend) {
+ gint current_audio;
+ g_object_get(backend->element, "current-audio", ¤t_audio, NULL);
+ return current_audio != -1;
+}
+
+static void load_media_info_video(GstBackend *backend) {
+ GstElement *video_sink;
+ g_object_get(G_OBJECT(backend->element), "video-sink", &video_sink, NULL);
+
+ if (video_sink == NULL) {
+ throw_error(backend, GST_BACKEND_ERR_NO_MEDIA_INFO, NULL, "Unable to obtain the video sink");
+ return;
+ }
+
+ GstPad *video_pad = gst_element_get_static_pad(GST_ELEMENT(video_sink), "sink");
+ if (video_pad == NULL) {
+ throw_error(backend, GST_BACKEND_ERR_NO_MEDIA_INFO, NULL, "Unable to obtain the video pad");
+ return;
+ }
+
+ GstCaps *caps = gst_pad_get_current_caps(video_pad);
+ if (caps == NULL) {
+ throw_error(backend, GST_BACKEND_ERR_NO_MEDIA_INFO, NULL, "Unable to obtain the video caps");
+ return;
+ }
+
+ guint caps_count = gst_caps_get_size(caps);
+ gboolean got_width = FALSE, got_height = FALSE, got_frame_rate = FALSE;
+ for (guint i = 0; i < caps_count; i++) {
+ GstStructure *structure = gst_caps_get_structure(caps, i);
+
+ /* Ignore if not video */
+ const gchar *name = gst_structure_get_name(structure);
+ if (!g_str_has_prefix(name, "video")) {
+ continue;
+ }
+
+ /* Get the values */
+ if (!got_width) {
+ got_width = gst_structure_get_int(structure, "width", &backend->media_info->width);
+ }
+ if (!got_height) {
+ got_height = gst_structure_get_int(structure, "height", &backend->media_info->height);
+ }
+ if (!got_frame_rate) {
+ gint numerator, denominator;
+ got_frame_rate = gst_structure_get_fraction(structure, "framerate", &numerator,
&denominator);
+ if (got_frame_rate) {
+ backend->media_info->frame_rate = (float)numerator / (float)denominator;
+ }
+ }
+
+ if (got_width && got_height && got_frame_rate) {
+ break;
+ }
+ }
+
+ gst_caps_unref(caps);
+
+ if (!got_width) {
+ throw_error(backend, GST_BACKEND_ERR_NO_MEDIA_INFO, NULL, "Unable to obtain the video width");
+ return;
+ }
+
+ if (!got_height) {
+ throw_error(backend, GST_BACKEND_ERR_NO_MEDIA_INFO, NULL, "Unable to obtain the video
height");
+ return;
+ }
+
+ if (!got_frame_rate) {
+ throw_error(backend, GST_BACKEND_ERR_NO_MEDIA_INFO, NULL, "Unable to obtain the video frame
rate");
+ return;
+ }
+
+ backend->media_info->aspect_ratio = (float)backend->media_info->width /
(float)backend->media_info->height;
+}
+
+static void on_discovered(GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *error, gpointer data) {
+ GstBackend *backend = (GstBackend *)data;
+ if (backend == NULL) {
+ g_debug("on_discovered user_data is NULL. Unable to continue."); //can't call
backend->error_callback because backend is null
+ return;
+ }
+
+ GstClockTime duration = gst_discoverer_info_get_duration(info);
+ backend->media_info->duration = duration / GST_MSECOND;
+
+ backend->load_complete_callback(backend->media_info);
+}
+
+static void load_media_info_duration(GstBackend *backend) {
+
+ gint64 duration;
+ if (gst_element_query_duration(backend->element, GST_FORMAT_TIME, &duration)) {
+ backend->media_info->duration = duration / GST_MSECOND;
+ backend->load_complete_callback(backend->media_info);
+ return;
+ }
+
+ /* Usually we can query the duration above. However, there are
+ * some files where this doesn't happen (for example, some audio files
+ * where the duration is only computed when starting to play the file).
+ * In those cases, the GstDiscoverer is used below.
+ */
+ g_debug("Unable to query the media duration. Using the discoverer.");
+ gchararray uri;
+ g_object_get(G_OBJECT(backend->element), "current-uri", &uri, NULL);
+
+ GstDiscoverer *discoverer = gst_discoverer_new(GST_SECOND, NULL);
+ if (discoverer == NULL) {
+ throw_error(backend, GST_BACKEND_ERR_NO_MEDIA_INFO, NULL, "Unable to get the gstreamer
discoverer");
+ return;
+ }
+
+ g_signal_connect(discoverer, "discovered", G_CALLBACK(on_discovered), backend);
+ gst_discoverer_start(discoverer);
+
+ if (!gst_discoverer_discover_uri_async(discoverer, uri)) {
+ throw_error(backend, GST_BACKEND_ERR_NO_MEDIA_INFO, NULL, "Failed to start the discoverer");
+ return;
+ }
+}
+
+static void load_media_info(GstBackend *backend) {
+
+ backend->media_info = gst_backend_media_info_new();
+
+ /* Check for video and audio */
+ backend->media_info->has_video = media_has_video(backend);
+ backend->media_info->has_audio = media_has_audio(backend);
+
+ if (!backend->media_info->has_video && !backend->media_info->has_audio) {
+ throw_error(backend, GST_BACKEND_ERR_NO_VIDEO_OR_AUDIO, NULL, NULL);
+ return;
+ }
+
+ if (backend->media_info->has_video) {
+ load_media_info_video(backend);
+ }
+
+ load_media_info_duration(backend);
+}
+
+static gboolean on_gst_message(GstBus *bus, GstMessage *message, gpointer data) {
+
+ GstBackend *backend = (GstBackend *)data;
+ if (backend == NULL) {
+ g_debug("on_gst_message user_data is NULL. Unable to continue."); //can't call
backend->error_callback because backend is null
+ return FALSE;
+ }
+
+ switch (GST_MESSAGE_TYPE(message)) {
+
+ /* ASYNC_DONE can be emitted many times (example: when returning back from
+ * pause to play). If the info is already loaded, don't do anything.
+ */
+ case GST_MESSAGE_ASYNC_DONE: {
+ if (!backend->media_info) {
+ load_media_info(backend);
+ }
+ break;
+ }
+
+ /*case GST_MESSAGE_STATE_CHANGED: {
+ GstState old_state, new_state, pending_state;
+ gst_message_parse_state_changed(message, &old_state, &new_state, &pending_state);
+ g_print("on_gst_message: STATE CHANGED: old=%s, new(current)=%s,
pending(target)=%s\n",
+ gst_element_state_get_name(old_state), gst_element_state_get_name(new_state),
gst_element_state_get_name(pending_state));
+ break;
+ }*/
+
+ case GST_MESSAGE_ERROR: {
+ if (backend->error_callback != NULL) {
+ GError *error;
+ gchar *debug;
+ gst_message_parse_error(message, &error, &debug);
+
+ throw_error(backend, GST_BACKEND_ERR_GSTREAMER, error->message, debug);
+
+ g_error_free(error);
+ g_free(debug);
+ }
+ break;
+ }
+
+ // the media file finished playing
+ case GST_MESSAGE_EOS: {
+ if (backend->eos_reached_callback != NULL) {
+ backend->eos_reached_callback();
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ return TRUE;
+}
+
+
+/* 'Public' functions */
+
+GstBackend *gst_backend_init(ErrorCallback error_callback, LoadCompleteCallback load_complete_callback,
+ BasicCallback eos_reached_callback) {
+
+ GstBackend *backend = gst_backend_new();
+
+ backend->error_callback = error_callback;
+ backend->load_complete_callback = load_complete_callback;
+ backend->eos_reached_callback = eos_reached_callback;
+
+ gst_init(NULL, NULL);
+
+ backend->element = gst_element_factory_make("playbin", "play");
+ if (backend->element == NULL) {
+ g_debug("gst_element_factory_make returned a null playbin element");
+ return NULL;
+ }
+
+ backend->video_element = gst_element_factory_make("gtksink", "gtksink");
+ if (backend->video_element == NULL) {
+ g_debug("gst_element_factory_make returned a null gtksink element");
+ return NULL;
+ }
+
+ g_object_set(G_OBJECT(backend->element), "video-sink", backend->video_element, NULL);
+
+ gst_bus_add_watch(gst_pipeline_get_bus(GST_PIPELINE(backend->element)), on_gst_message, backend);
+
+ return backend;
+}
+
+gboolean gst_backend_load(GstBackend *backend, char *uri) {
+ g_object_set(G_OBJECT(backend->element), "uri", uri, NULL);
+ return (gst_element_set_state(backend->element, GST_STATE_PAUSED) != GST_STATE_CHANGE_FAILURE);
+}
+
+void gst_backend_play(GstBackend *backend) {
+ gst_element_set_state(backend->element, GST_STATE_PLAYING);
+}
+
+void gst_backend_pause(GstBackend *backend) {
+ gst_element_set_state(backend->element, GST_STATE_PAUSED);
+}
+
+void gst_backend_unload(GstBackend *backend) {
+ gst_element_set_state(backend->element, GST_STATE_NULL);
+
+ if (backend->element != NULL) {
+ gst_object_unref(GST_OBJECT(backend->element));
+ backend->element = NULL;
+ }
+
+ g_free(backend->media_info);
+ backend->media_info = NULL;
+
+ g_free(backend);
+ backend = NULL;
+}
+
+gint64 gst_backend_get_position(GstBackend *backend) {
+ gint64 position;
+ if (gst_element_query_position(backend->element, GST_FORMAT_TIME, &position)) {
+ return position / GST_MSECOND;
+ }
+
+ /* Query position wasn't successful. This is usually due to a pending state change,
+ * which happens for example after seeking, so we wait for the state change to complete
+ * and try again.
+ */
+ wait_for_state_change_to_complete(backend);
+ if (gst_element_query_position(backend->element, GST_FORMAT_TIME, &position)) {
+ return position / GST_MSECOND;
+ }
+
+ /* If we're here, we're still unable to return the position after waiting for the state
+ * change to complete. Return -1. */
+ g_debug("gst_backend_get_position unable to query the current position");
+ return -1;
+}
+
+/*
+ * time: in ms
+ */
+void gst_backend_seek(GstBackend *backend, gint64 time, gboolean is_absolute, float speed) {
+ gint64 new_position = (is_absolute ? time : gst_backend_get_position(backend) + time);
+
+ /* Note: gst_element_seek_simple can't be used here because it always resets speed to 1,
+ * and we need to keep the current speed.
+ */
+ gst_element_seek(backend->element, speed, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
+ GST_SEEK_TYPE_SET, new_position * GST_MSECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
+}
+
+void gst_backend_set_speed(GstBackend *backend, float speed) {
+ gst_element_seek(backend->element, speed, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
+ GST_SEEK_TYPE_SET, gst_backend_get_position(backend) * GST_MSECOND, GST_SEEK_TYPE_NONE,
GST_CLOCK_TIME_NONE);
+}
+
+/* Gets the GtkWidget inside the gtksink video element */
+void *gst_backend_get_video_widget(GstBackend *backend) {
+ void *widget;
+ g_object_get(backend->video_element, "widget", &widget, NULL);
+ return widget;
+}
diff --git a/src/GnomeSubtitles/Core/Command/InsertSubtitleCommand.cs
b/src/GnomeSubtitles/Core/Command/InsertSubtitleCommand.cs
index 64261a4..cca3be2 100644
--- a/src/GnomeSubtitles/Core/Command/InsertSubtitleCommand.cs
+++ b/src/GnomeSubtitles/Core/Command/InsertSubtitleCommand.cs
@@ -1,6 +1,6 @@
/*
* This file is part of Gnome Subtitles.
- * Copyright (C) 2006-2019 Pedro Castro
+ * Copyright (C) 2006-2021 Pedro Castro
*
* Gnome Subtitles is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -131,7 +131,7 @@ public class InsertSubtitleAtVideoPositionCommand : InsertSubtitleCommand {
protected override TreePath GetNewPath () {
subtitleTime = Base.Ui.Video.Position.CurrentTime;
- if (Base.Ui.Video.IsStatePlaying && Base.Config.VideoApplyReactionDelay) {
+ if (Base.Ui.Video.IsStatusPlaying && Base.Config.VideoApplyReactionDelay) {
subtitleTime -= TimeSpan.FromMilliseconds(Base.Config.VideoReactionDelay);
}
diff --git a/src/GnomeSubtitles/Core/EventHandlers.cs b/src/GnomeSubtitles/Core/EventHandlers.cs
index 99186b3..8811e4c 100644
--- a/src/GnomeSubtitles/Core/EventHandlers.cs
+++ b/src/GnomeSubtitles/Core/EventHandlers.cs
@@ -1,6 +1,6 @@
/*
* This file is part of Gnome Subtitles.
- * Copyright (C) 2006-2019 Pedro Castro
+ * Copyright (C) 2006-2021 Pedro Castro
*
* Gnome Subtitles is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -307,14 +307,14 @@ public class EventHandlers {
public void OnVideoSetSubtitleStart (object o, EventArgs args) {
if (Base.TimingMode == TimingMode.Times) {
TimeSpan time = Base.Ui.Video.Position.CurrentTime;
- if (Base.Ui.Video.IsStatePlaying && Base.Config.VideoApplyReactionDelay) {
+ if (Base.Ui.Video.IsStatusPlaying && Base.Config.VideoApplyReactionDelay) {
time -= TimeSpan.FromMilliseconds(Base.Config.VideoReactionDelay);
}
Base.CommandManager.Execute(new VideoSetSubtitleStartCommand(time));
}
else {
int frames = Base.Ui.Video.Position.CurrentFrames;
- if (Base.Ui.Video.IsStatePlaying && Base.Config.VideoApplyReactionDelay) {
+ if (Base.Ui.Video.IsStatusPlaying && Base.Config.VideoApplyReactionDelay) {
frames -=
(int)TimingUtil.TimeMillisecondsToFrames(Base.Config.VideoReactionDelay, Base.Ui.Video.FrameRate);
}
Base.CommandManager.Execute(new VideoSetSubtitleStartCommand(frames));
@@ -324,14 +324,14 @@ public class EventHandlers {
public void OnVideoSetSubtitleEnd (object o, EventArgs args) {
if (Base.TimingMode == TimingMode.Times) {
TimeSpan time = Base.Ui.Video.Position.CurrentTime;
- if (Base.Ui.Video.IsStatePlaying && Base.Config.VideoApplyReactionDelay) {
+ if (Base.Ui.Video.IsStatusPlaying && Base.Config.VideoApplyReactionDelay) {
time -= TimeSpan.FromMilliseconds(Base.Config.VideoReactionDelay);
}
Base.CommandManager.Execute(new VideoSetSubtitleEndCommand(time));
}
else {
int frames = Base.Ui.Video.Position.CurrentFrames;
- if (Base.Ui.Video.IsStatePlaying && Base.Config.VideoApplyReactionDelay) {
+ if (Base.Ui.Video.IsStatusPlaying && Base.Config.VideoApplyReactionDelay) {
frames -=
(int)TimingUtil.TimeMillisecondsToFrames(Base.Config.VideoReactionDelay, Base.Ui.Video.FrameRate);
}
Base.CommandManager.Execute(new VideoSetSubtitleEndCommand(frames));
@@ -375,7 +375,7 @@ public class EventHandlers {
if (Base.TimingMode == TimingMode.Times) {
TimeSpan time = Base.Ui.Video.Position.CurrentTime;
- if (Base.Ui.Video.IsStatePlaying && Base.Config.VideoApplyReactionDelay) {
+ if (Base.Ui.Video.IsStatusPlaying && Base.Config.VideoApplyReactionDelay) {
time -= TimeSpan.FromMilliseconds(Base.Config.VideoReactionDelay);
}
Base.CommandManager.Execute(new VideoSetSubtitleEndCommand(time));
@@ -383,7 +383,7 @@ public class EventHandlers {
}
else {
int frames = Base.Ui.Video.Position.CurrentFrames;
- if (Base.Ui.Video.IsStatePlaying && Base.Config.VideoApplyReactionDelay) {
+ if (Base.Ui.Video.IsStatusPlaying && Base.Config.VideoApplyReactionDelay) {
frames -=
(int)TimingUtil.TimeMillisecondsToFrames(Base.Config.VideoReactionDelay, Base.Ui.Video.FrameRate);
}
Base.CommandManager.Execute(new VideoSetSubtitleEndCommand(frames));
diff --git a/src/GnomeSubtitles/Dialog/AboutDialog.cs b/src/GnomeSubtitles/Dialog/AboutDialog.cs
index dab9a27..369f2ab 100644
--- a/src/GnomeSubtitles/Dialog/AboutDialog.cs
+++ b/src/GnomeSubtitles/Dialog/AboutDialog.cs
@@ -45,7 +45,7 @@ public class AboutDialog : BaseDialog {
dialog.Website = "https://gnomesubtitles.org";
dialog.WebsiteLabel = dialog.Website;
dialog.Comments = Catalog.GetString("Video subtitling for the GNOME desktop");
- dialog.Copyright = "Copyright © 2006-2020 Pedro Castro";
+ dialog.Copyright = "Copyright © 2006-2021 Pedro Castro";
dialog.LicenseType = License.Gpl20;
dialog.Authors = new string[]{
diff --git a/src/GnomeSubtitles/Dialog/BaseDialog.cs b/src/GnomeSubtitles/Dialog/BaseDialog.cs
index 394ce2b..e27770b 100644
--- a/src/GnomeSubtitles/Dialog/BaseDialog.cs
+++ b/src/GnomeSubtitles/Dialog/BaseDialog.cs
@@ -1,6 +1,6 @@
/*
* This file is part of Gnome Subtitles.
- * Copyright (C) 2009-2019 Pedro Castro
+ * Copyright (C) 2009-2021 Pedro Castro
*
* Gnome Subtitles is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -99,7 +99,7 @@ public abstract class BaseDialog {
if (dialog.TransientFor == null) {
dialog.TransientFor = Base.Ui.Window;
}
-
+
Dialog.Response += OnResponse;
Dialog.DeleteEvent += OnDeleteEvent;
}
diff --git a/src/GnomeSubtitles/Ui/VideoPreview/Exceptions/PlayerEngineException.cs
b/src/GnomeSubtitles/Dialog/Message/DialogUtil.cs
similarity index 57%
rename from src/GnomeSubtitles/Ui/VideoPreview/Exceptions/PlayerEngineException.cs
rename to src/GnomeSubtitles/Dialog/Message/DialogUtil.cs
index 8fe108a..4dfe75e 100644
--- a/src/GnomeSubtitles/Ui/VideoPreview/Exceptions/PlayerEngineException.cs
+++ b/src/GnomeSubtitles/Dialog/Message/DialogUtil.cs
@@ -1,6 +1,6 @@
/*
* This file is part of Gnome Subtitles.
- * Copyright (C) 2008 Pedro Castro
+ * Copyright (C) 2021 Pedro Castro
*
* Gnome Subtitles is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,34 +17,14 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
-using System;
+namespace GnomeSubtitles.Dialog.Message {
-namespace GnomeSubtitles.Ui.VideoPreview.Exceptions {
+ public static class DialogUtil {
+
+ public static void ShowError(string primary, string secondary) {
+ new BasicErrorDialog(primary, secondary).Show();
+ }
-public class PlayerEngineException : ApplicationException {
- private string error = String.Empty;
- private string debug = String.Empty;
-
- public PlayerEngineException (string error, string debug) {
- this.error = error;
- this.debug = debug;
- }
-
- /* Properties */
-
- public string Error {
- get { return error; }
- }
-
- public string Debug {
- get { return debug; }
- }
-
- /* Public methods */
-
- public override string ToString () {
- return this.error + "; " + this.debug;
}
-}
-}
+}
\ No newline at end of file
diff --git a/src/GnomeSubtitles/Dialog/Message/SubtitleFileOpenErrorDialog.cs
b/src/GnomeSubtitles/Dialog/Message/SubtitleFileOpenErrorDialog.cs
index 36aeda6..a79b93e 100644
--- a/src/GnomeSubtitles/Dialog/Message/SubtitleFileOpenErrorDialog.cs
+++ b/src/GnomeSubtitles/Dialog/Message/SubtitleFileOpenErrorDialog.cs
@@ -1,6 +1,6 @@
/*
* This file is part of Gnome Subtitles.
- * Copyright (C) 2006-2019 Pedro Castro
+ * Copyright (C) 2006-2021 Pedro Castro
*
* Gnome Subtitles is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,22 +17,57 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
+using Gtk;
using Mono.Unix;
using SubLib.Exceptions;
+using SubLib.Util;
using System;
using System.IO;
using System.Security;
namespace GnomeSubtitles.Dialog.Message {
-public class SubtitleFileOpenErrorDialog : FileOpenErrorDialog {
+public class SubtitleFileOpenErrorDialog : ErrorDialog {
+
+ private string primaryTextStart = Catalog.GetString("Could not open the file {0}.");
+ private string actionLabel = Catalog.GetString("Open another file");
- public SubtitleFileOpenErrorDialog (string filename, Exception exception) : base(filename, exception)
{
+ public SubtitleFileOpenErrorDialog (string filename, Exception exception) {
+ Logger.Error(exception, "Subtitle file open error");
+
+ string primaryText = GetPrimaryText(filename);
+ string secondaryText = GetSecondaryText(exception);
+ SetText(primaryText, secondaryText);
}
/* Overridden members */
+
+ protected override void AddButtons () {
+ Button actionButton = dialog.AddButton(actionLabel, ResponseType.Accept) as Button;
+ actionButton.Image = new Image(Stock.Open, IconSize.Button);
+ dialog.AddButton(Stock.Ok, ResponseType.Ok);
+
+ dialog.DefaultResponse = ResponseType.Accept;
+ }
+
+
+ /* Private members */
+
+ private string GetPrimaryText (string filename) {
+ return string.Format(primaryTextStart, filename);
+ }
+
+ private string GetSecondaryText (Exception exception) {
+ string text = GetSecondaryTextFromException(exception);
+ if (text != null) {
+ return text;
+ }
+
+ return GetGeneralExceptionErrorMessage(exception);
+ }
+
- protected override string SecondaryTextFromException (Exception exception) {
+ private string GetSecondaryTextFromException (Exception exception) {
if (exception is UnknownSubtitleFormatException)
return Catalog.GetString("Unable to detect the subtitle format. Please check that the
file type is supported.");
else if (exception is EncodingNotSupportedException)
@@ -49,8 +84,10 @@ public class SubtitleFileOpenErrorDialog : FileOpenErrorDialog {
return Catalog.GetString("The file could not be found.");
else if (exception is FileTooLargeException)
return Catalog.GetString("The file appears to be too large for a text-based subtitle
file.");
+ else if (exception is UriFormatException)
+ return Catalog.GetString("The file path appears to be invalid.");
else
- return String.Empty;
+ return null;
}
}
diff --git a/src/GnomeSubtitles/Dialog/Message/VideoErrorDialog.cs
b/src/GnomeSubtitles/Dialog/Message/VideoFileOpenErrorDialog.cs
similarity index 51%
rename from src/GnomeSubtitles/Dialog/Message/VideoErrorDialog.cs
rename to src/GnomeSubtitles/Dialog/Message/VideoFileOpenErrorDialog.cs
index 583c743..f9e2cb1 100644
--- a/src/GnomeSubtitles/Dialog/Message/VideoErrorDialog.cs
+++ b/src/GnomeSubtitles/Dialog/Message/VideoFileOpenErrorDialog.cs
@@ -1,6 +1,6 @@
/*
* This file is part of Gnome Subtitles.
- * Copyright (C) 2007-2019 Pedro Castro
+ * Copyright (C) 2007-2021 Pedro Castro
*
* Gnome Subtitles is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,35 +17,31 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
-using GnomeSubtitles.Ui.VideoPreview.Exceptions;
using Mono.Unix;
-using System;
-using SubLib.Util;
+using Gtk;
namespace GnomeSubtitles.Dialog.Message {
-public class VideoErrorDialog : FileOpenErrorDialog {
+ public class VideoFileOpenErrorDialog : ErrorDialog {
+
+ private string primaryTextStart = Catalog.GetString("Could not play the file '{0}'.");
+ private string actionLabel = Catalog.GetString("Open another file");
+
+ public VideoFileOpenErrorDialog(string filename, string error) {
+ string primaryText = string.Format(primaryTextStart, filename);
+ string secondaryText = error;
+ SetText(primaryText, secondaryText);
+ }
+
+ /* Overridden members */
+
+ protected override void AddButtons() {
+ Button actionButton = dialog.AddButton(actionLabel, ResponseType.Accept) as Button;
+ actionButton.Image = new Image(Stock.Open, IconSize.Button);
+ dialog.AddButton(Stock.Ok, ResponseType.Ok);
+
+ dialog.DefaultResponse = ResponseType.Accept;
+ }
- /* Strings */
- private string primaryTextStart = Catalog.GetString("Could not play the file");
-
- public VideoErrorDialog (Uri uri, Exception exception) : base(uri, exception) {
- Logger.Error(exception, "Video error");
}
-
- /* Overridden members */
-
- protected override string GetPrimaryText (string filename) {
- return primaryTextStart + " " + filename + ".";
- }
-
- protected override string SecondaryTextFromException (Exception exception) {
- if (exception is PlayerEngineException)
- return (exception as PlayerEngineException).Error;
- else
- return String.Empty;
- }
-
-}
-
}
diff --git a/src/GnomeSubtitles/Dialog/VideoSeekToDialog.cs b/src/GnomeSubtitles/Dialog/VideoSeekToDialog.cs
index adbdd33..9b8ee10 100644
--- a/src/GnomeSubtitles/Dialog/VideoSeekToDialog.cs
+++ b/src/GnomeSubtitles/Dialog/VideoSeekToDialog.cs
@@ -1,6 +1,6 @@
/*
* This file is part of Gnome Subtitles.
- * Copyright (C) 2008-2018 Pedro Castro
+ * Copyright (C) 2008-2021 Pedro Castro
*
* Gnome Subtitles is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -98,14 +98,10 @@ public class VideoSeekToDialog : BaseDialog {
spinButton.Value = newValue;
}
- //private void OnClear (object o, EventArgs args) {
- // SetSpinButtonValue(0);
- //}
-
protected override bool ProcessResponse (ResponseType response) {
if (response == ResponseType.Ok) {
if (timingMode == TimingMode.Times) {
- TimeSpan position = TimeSpan.FromMilliseconds(spinButton.Value); //TODO move
to Util
+ TimeSpan position = TimeSpan.FromMilliseconds(spinButton.Value);
Base.Ui.Video.Seek(position);
}
else {
diff --git a/src/GnomeSubtitles/Execution/AssemblyInfo.cs.in b/src/GnomeSubtitles/Execution/AssemblyInfo.cs.in
index 73b39a5..18af87f 100644
--- a/src/GnomeSubtitles/Execution/AssemblyInfo.cs.in
+++ b/src/GnomeSubtitles/Execution/AssemblyInfo.cs.in
@@ -1,6 +1,6 @@
/*
* This file is part of Gnome Subtitles.
- * Copyright (C) 2006-2020 Pedro Castro
+ * Copyright (C) 2006-2021 Pedro Castro
*
* Gnome Subtitles is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,5 +22,5 @@ using System.Reflection;
[assembly: AssemblyVersion("@VERSION@")]
[assembly: AssemblyTitle ("Gnome Subtitles")]
[assembly: AssemblyDescription ("Video subtitling for the GNOME desktop")]
-[assembly: AssemblyCopyright ("Copyright (c) 2006-2020 Pedro Castro")]
+[assembly: AssemblyCopyright ("Copyright (c) 2006-2021 Pedro Castro")]
diff --git a/src/GnomeSubtitles/Ui/VideoPreview/Exceptions/PlayerCouldNotInitiateEngineException.cs
b/src/GnomeSubtitles/Ui/VideoPreview/Exceptions/PlayerException.cs
similarity index 80%
rename from src/GnomeSubtitles/Ui/VideoPreview/Exceptions/PlayerCouldNotInitiateEngineException.cs
rename to src/GnomeSubtitles/Ui/VideoPreview/Exceptions/PlayerException.cs
index 8e5bd87..a8d8719 100644
--- a/src/GnomeSubtitles/Ui/VideoPreview/Exceptions/PlayerCouldNotInitiateEngineException.cs
+++ b/src/GnomeSubtitles/Ui/VideoPreview/Exceptions/PlayerException.cs
@@ -1,6 +1,6 @@
/*
* This file is part of Gnome Subtitles.
- * Copyright (C) 2008-2018 Pedro Castro
+ * Copyright (C) 2021 Pedro Castro
*
* Gnome Subtitles is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,11 +21,11 @@ using System;
namespace GnomeSubtitles.Ui.VideoPreview.Exceptions {
-public class PlayerCouldNotInitiateEngineException : ApplicationException {
+ public class PlayerException : ApplicationException {
+
+ public PlayerException (string error) : base(error) {
+ }
- public PlayerCouldNotInitiateEngineException (string message) : base(message) {
}
-}
-
-}
+}
\ No newline at end of file
diff --git a/src/GnomeSubtitles/Ui/VideoPreview/MediaBackend.cs
b/src/GnomeSubtitles/Ui/VideoPreview/MediaBackend.cs
new file mode 100644
index 0000000..e9c44b1
--- /dev/null
+++ b/src/GnomeSubtitles/Ui/VideoPreview/MediaBackend.cs
@@ -0,0 +1,99 @@
+/*
+ * This file is part of Gnome Subtitles.
+ * Copyright (C) 2021 Pedro Castro
+ *
+ * Gnome Subtitles is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Gnome Subtitles is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using GnomeSubtitles.Core;
+using Gtk;
+
+namespace GnomeSubtitles.Ui.VideoPreview {
+
+ /* Enums */
+ public enum MediaStatus { Unloaded, Loading, Loaded, Paused, Playing };
+
+ /* Delegates */
+ public delegate void ErrorFoundHandler(string errorMessage);
+ public delegate void StatusChangedHandler(MediaStatus newStatus);
+
+ public abstract class MediaBackend {
+
+ private MediaStatus status = MediaStatus.Unloaded;
+
+
+ /* Events */
+ public event ErrorFoundHandler ErrorFound;
+ public event StatusChangedHandler StatusChanged;
+ public event BasicEventHandler EndOfStreamReached;
+
+
+ /* Public properties */
+
+ public MediaStatus Status {
+ get { return status; }
+ }
+
+ public abstract string Name { get; }
+
+ ///Returns the current media position in ms, or -1 if unable to get the value
+ public abstract long CurrentPosition { get; }
+ public abstract long Duration { get; }
+ public abstract bool HasVideo { get; }
+ public abstract float AspectRatio { get; }
+ public abstract float FrameRate { get; }
+ public abstract bool HasAudio { get; }
+
+
+ /* Public methods */
+
+ public abstract void Initialize();
+ public abstract bool Load(string uri);
+
+ public abstract void Unload();
+ public abstract void Dispose();
+
+ public abstract void Play();
+ public abstract void Pause();
+ public abstract void SetSpeed(float speed);
+ public abstract void Seek(long time, bool isAbsolute);
+ public abstract Widget CreateVideoWidget();
+
+
+ /* Protected members */
+
+ protected void SetStatus(MediaStatus status) {
+ this.status = status;
+
+ if (StatusChanged != null) {
+ StatusChanged(status);
+ }
+ }
+
+ protected void TriggerErrorFound(string error) {
+ if (ErrorFound != null) {
+ ErrorFound(error);
+ }
+ }
+
+ protected void TriggerEndOfStreamReached() {
+ if (EndOfStreamReached != null) {
+ EndOfStreamReached();
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/GnomeSubtitles/Ui/VideoPreview/Player.cs b/src/GnomeSubtitles/Ui/VideoPreview/Player.cs
index d3611f2..08d3e29 100644
--- a/src/GnomeSubtitles/Ui/VideoPreview/Player.cs
+++ b/src/GnomeSubtitles/Ui/VideoPreview/Player.cs
@@ -17,269 +17,245 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
-using GnomeSubtitles.Ui.VideoPreview.Exceptions;
-using GStreamer;
+using External.GStreamer;
+using GnomeSubtitles.Core;
using Gtk;
-using SubLib.Core.Domain;
using SubLib.Util;
using System;
namespace GnomeSubtitles.Ui.VideoPreview {
/* Delegates */
-public delegate void PlayerErrorEventHandler (Uri videoUri, Exception e);
-public delegate void VideoDurationEventHandler (TimeSpan duration);
+public delegate void PlayerErrorHandler(string error);
public class Player {
- private AspectFrame frame = null;
- private Playbin playbin = null;
+ private AspectFrame frame = null; //the backend video widget is added as a child of this frame
+ private MediaBackend backend = null;
private PlayerPositionWatcher position = null;
- private bool hasFoundDuration = false;
- private Uri videoUri = null;
- private VideoInfo videoInfo = null;
- private float speed = 1;
-
+ private MediaStatus status = MediaStatus.Unloaded; //We don't always have a backend, but we always
should have a status, so that's why it's here
+ private int speed = DefaultSpeed; //Speed is divided by 10 to get the actual speed (example: 10->1).
Storing as int to avoid floating rounding errors.
+
/* Constants */
- public const float DefaultAspectRatio = 1.67f;
- public const float DefaultMinSpeed = 0.1f;
- public const float DefaultSpeedStep = 0.1f;
- public const float DefaultMaxSpeed = 2;
-
- public Player (AspectFrame aspectFrame) {
- this.frame = aspectFrame;
+ private const int DefaultSpeed = 100;
+
+ public Player (AspectFrame frame) {
+ this.frame = frame;
InitializePositionWatcher();
}
/* Events */
- public event PlayerErrorEventHandler Error;
- public event EndOfStreamEventHandler EndOfStream;
- public event StateEventHandler StateChanged;
+ public event PlayerErrorHandler ErrorFound; //Note: all errors are considered fatal, meaning the
backend must be disposed immediately
+ public event BasicEventHandler EndOfStreamReached;
+ public event StatusChangedHandler StatusChanged;
public event PositionPulseEventHandler PositionPulse;
- public event VideoInfoEventHandler FoundVideoInfo;
- public event VideoDurationEventHandler FoundDuration;
+
/* Properties */
- public MediaStatus State {
- get { return playbin.CurrentStatus; }
+ public MediaStatus Status {
+ get { return status; }
+ }
+
+ public long Duration {
+ get { return backend.Duration; }
}
- public TimeSpan Duration {
- get { return playbin.Duration; }
+ public bool HasVideo {
+ get { return backend.HasVideo; }
}
public float AspectRatio {
- get { return videoInfo.AspectRatio; }
+ get { return backend.AspectRatio; }
}
public float FrameRate {
- get { return videoInfo.FrameRate; }
+ get { return backend.FrameRate; }
}
public bool HasAudio {
- get { return videoInfo.HasAudio; }
- }
-
- public bool HasVideo {
- get { return videoInfo.HasVideo; }
+ get { return backend.HasAudio; }
}
- public Uri VideoUri {
- get { return videoUri; }
- }
-
- public float Speed {
+ public int Speed {
get { return speed; }
}
- public bool IsLoadComplete {
- get { return (playbin.CurrentStatus != MediaStatus.Unloaded) && HasVideoInfo() &&
HasDuration(); }
- }
-
/* Public methods */
- public void Open (Uri videoUri) {
- this.videoUri = videoUri;
-
- InitializePlaybin();
- playbin.Load(videoUri.AbsoluteUri);
+ public void Open(Uri mediaUri) {
+ if (status != MediaStatus.Unloaded) {
+ throw new Exception("Trying to load a player which is not unloaded.");
+ }
+
+ backend = InitializeBackend();
+
+ if (!backend.Load(mediaUri.AbsoluteUri)) {
+ throw new Exception("Unable to load the media file.");
+ }
}
- public void Close () {
+ public void Close() {
position.Stop();
- playbin.Unload();
- DisposePlaybin();
-
- videoUri = null;
- hasFoundDuration = false;
- videoInfo = null;
- speed = 1;
+ //Unload
+ if ((status != MediaStatus.Unloaded) && (status != MediaStatus.Loading)) {
+ backend.Unload();
+ }
+ status = MediaStatus.Unloaded;
+
+ //Dispose
+ DisposeBackend();
+
+ speed = DefaultSpeed;
}
- public void Play () {
- playbin.Play();
+ public void Play() {
+ if (status == MediaStatus.Playing) {
+ return;
+ }
+
+ if ((status != MediaStatus.Loaded) && (status != MediaStatus.Paused)) {
+ throw new Exception(string.Format("Trying to play but the player status is {0}",
status));
+ }
+
+ backend.Play();
}
public void Pause () {
- playbin.Pause();
- }
-
- public void SpeedUp () {
- if (this.speed >= DefaultMaxSpeed)
- return;
-
- this.speed += DefaultSpeedStep;
- ChangeSpeed(this.speed);
- }
-
- public void SpeedDown () {
- if (this.speed <= DefaultMinSpeed)
- return;
+ if (status == MediaStatus.Paused) {
+ return;
+ }
+
+ if (status != MediaStatus.Playing) {
+ throw new Exception(string.Format("Trying to pause but the player status is {0}",
status));
+ }
- this.speed -= DefaultSpeedStep;
- ChangeSpeed(this.speed);
+ backend.Pause();
}
- public void SpeedReset () {
- this.speed = 1;
- ChangeSpeed(this.speed);
+ public void SetSpeed(int speed) {
+ this.speed = speed;
+ backend.SetSpeed(((float)speed)/100);
}
-
- public void Rewind (TimeSpan dec) {
- Seek(playbin.CurrentPosition - dec);
+
+ public void ResetSpeed() {
+ SetSpeed(DefaultSpeed);
}
-
- public void Forward (TimeSpan inc) {
- Seek(playbin.CurrentPosition + inc);
+
+ /// <summary>
+ /// Rewind the specified time.
+ /// </summary>
+ /// <param name="time">Time in ms.</param>
+ public void Rewind(long time) {
+ backend.Seek(-time, false);
}
- public void Seek (TimeSpan newPosition) {
- playbin.Seek(newPosition, speed);
+ /// <summary>
+ /// Forward the specified time.
+ /// </summary>
+ /// <param name="time">Time in ms.</param>
+ public void Forward(long time) {
+ backend.Seek(time, false);
}
- public void Seek (double newPosition) {
- playbin.Seek(newPosition, speed); // newPosition in milliseconds
+ /// <summary>
+ /// Seek to the specified position.
+ /// </summary>
+ /// <param name="position">Position in ms.</param>
+ public void Seek(long position) {
+ backend.Seek(position, true);
}
/* Private members */
- private bool HasDuration() {
- return playbin.Duration != TimeSpan.Zero;
- }
-
- public bool HasVideoInfo() {
- return videoInfo != null;
- }
-
- private void InitializePlaybin () {
- playbin = new Playbin();
-
- if (!playbin.Initiate()) {
- throw new PlayerCouldNotInitiateEngineException("Unable to initiate the playbin
engine");
- }
+ private MediaBackend InitializeBackend() {
+ MediaBackend backend = new GstBackend();
+ backend.Initialize();
- Widget videoWidget = playbin.GetVideoWidget();
+ Widget videoWidget = backend.CreateVideoWidget();
if (videoWidget == null) {
- throw new PlayerCouldNotInitiateEngineException("Unable to get the video widget from
the playbin engine");
+ throw new Exception("Unable to create the video widget");
}
frame.Child = videoWidget;
videoWidget.Realize();
videoWidget.Show();
- playbin.Error += OnPlaybinError;
- playbin.EndOfStream += OnPlaybinEndOfStream;
- playbin.StateChanged += OnPlaybinStateChanged;
- playbin.FoundVideoInfo += OnPlaybinFoundVideoInfo;
- playbin.FoundTag += OnPlaybinFoundTag;
+ backend.ErrorFound += OnBackendErrorFound;
+ backend.StatusChanged += OnBackendStatusChanged;
+ backend.EndOfStreamReached += OnBackendEndOfStreamReached;
+
+ return backend;
}
- private void DisposePlaybin () {
- playbin.Error -= OnPlaybinError;
- playbin.EndOfStream -= OnPlaybinEndOfStream;
- playbin.StateChanged -= OnPlaybinStateChanged;
- playbin.FoundVideoInfo -= OnPlaybinFoundVideoInfo;
- playbin.FoundTag -= OnPlaybinFoundTag;
+ private void DisposeBackend() {
+ backend.ErrorFound -= OnBackendErrorFound;
+ backend.StatusChanged -= OnBackendStatusChanged;
+ backend.EndOfStreamReached -= OnBackendEndOfStreamReached;
- Widget videoWidget = playbin.GetVideoWidget();
- frame.Remove(videoWidget);
+ frame.Remove(frame.Child);
- playbin.Dispose();
- playbin = null;
+ backend.Dispose();
+ backend = null;
}
- private void InitializePositionWatcher () {
+ private void InitializePositionWatcher() {
position = new PlayerPositionWatcher(GetPosition);
position.PositionPulse += OnPositionWatcherPulse;
}
- /// <summary>Gets the current player position.</summary>
- private TimeSpan GetPosition () {
- return playbin.CurrentPosition;
- }
-
- private void ChangeSpeed (float newSpeed) {
- playbin.Seek(playbin.CurrentPosition, newSpeed);
+ private long GetPosition() {
+ return backend.CurrentPosition;
}
/* Event members */
- private void OnPlaybinError (ErrorEventArgs args) {
- if (Error != null)
- Error(videoUri, new PlayerEngineException(args.Error, args.Debug));
+ private void OnPositionWatcherPulse (long time) {
+ if (PositionPulse != null)
+ PositionPulse(time);
}
- private void OnPlaybinEndOfStream () {
- position.Stop();
- if (EndOfStream != null)
- EndOfStream();
+ private void OnBackendErrorFound(string error) {
+ if (ErrorFound != null) {
+ ErrorFound(error);
+ }
}
-
- private void OnPlaybinStateChanged (StateEventArgs args) {
- if (args.State == MediaStatus.Unloaded)
+
+ private void OnBackendStatusChanged(MediaStatus newStatus) {
+ this.status = newStatus;
+
+ //Handle position watcher
+ if (newStatus == MediaStatus.Unloaded) {
position.Stop();
- else
+ } else if (newStatus != MediaStatus.Loading) {
position.Start();
-
- if (StateChanged != null)
- StateChanged(args);
- }
-
- private void OnPositionWatcherPulse (TimeSpan time) {
- if (PositionPulse != null)
- PositionPulse(time);
- }
-
- private void OnPlaybinFoundVideoInfo (VideoInfoEventArgs args) {
- Logger.Info("[Player] Media info: {0}", args.VideoInfo.ToString());
- this.videoInfo = args.VideoInfo;
-
- /* Set defaults if there is no video */
- if (!videoInfo.HasVideo) {
- videoInfo.FrameRate = SubtitleConstants.DefaultFrameRate;
- videoInfo.AspectRatio = DefaultAspectRatio;
+ }
+
+ //Print info when loaded
+ if (newStatus == MediaStatus.Loaded) {
+ Logger.Info("[Player] Media Loaded: Backend={0}, Duration={1}ms, HasVideo={2}, "
+ + "AspectRatio={3}, FrameRate={4}, HasAudio={5}",
+ backend.Name, backend.Duration, backend.HasVideo, backend.AspectRatio,
+ backend.FrameRate, backend.HasAudio);
}
- frame.Ratio = videoInfo.AspectRatio;
+ if (StatusChanged != null) {
+ StatusChanged(newStatus);
+ }
- if (FoundVideoInfo != null)
- FoundVideoInfo(args);
}
-
- private void OnPlaybinFoundTag (TagEventArgs args) {
- if ((!hasFoundDuration) && (FoundDuration != null) && (playbin.Duration != TimeSpan.Zero)) {
- TimeSpan duration = playbin.Duration;
- Logger.Info("[Player] Media duration: {0}", duration);
-
- hasFoundDuration = true;
- FoundDuration(duration);
+
+ private void OnBackendEndOfStreamReached() {
+ position.Stop();
+
+ if (EndOfStreamReached != null) {
+ EndOfStreamReached();
}
}
diff --git a/src/GnomeSubtitles/Ui/VideoPreview/PlayerPositionWatcher.cs
b/src/GnomeSubtitles/Ui/VideoPreview/PlayerPositionWatcher.cs
index 33d1dbe..f369d52 100644
--- a/src/GnomeSubtitles/Ui/VideoPreview/PlayerPositionWatcher.cs
+++ b/src/GnomeSubtitles/Ui/VideoPreview/PlayerPositionWatcher.cs
@@ -1,6 +1,6 @@
/*
* This file is part of Gnome Subtitles.
- * Copyright (C) 2006-2019 Pedro Castro
+ * Copyright (C) 2006-2021 Pedro Castro
*
* Gnome Subtitles is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,18 +17,16 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
-using System;
-
namespace GnomeSubtitles.Ui.VideoPreview {
/* Delegates */
-//Represents a function that gets a time from the player
-public delegate TimeSpan PlayerGetTimeFunc ();
+//Represents a function that gets the time from the player
+public delegate long PlayerGetTimeFunc ();
//Represents a function that handles a frequent pulse in the player position. This means the pulse is
//emitted every 'x' ms and not only when the position changes.
-public delegate void PositionPulseEventHandler (TimeSpan position);
+public delegate void PositionPulseEventHandler (long position);
public class PlayerPositionWatcher {
private uint timeoutId = 0;
@@ -74,12 +72,17 @@ public class PlayerPositionWatcher {
}
private bool CheckPosition () {
- TimeSpan position = PlayerGetPosition();
- EmitPositionPulse(position);
+ long position = PlayerGetPosition();
+
+ //-1 means we couln't get the position value atm, so ignore it
+ if (position != -1) {
+ EmitPositionPulse(position);
+ }
+
return true;
}
- private void EmitPositionPulse (TimeSpan position) {
+ private void EmitPositionPulse (long position) {
if (PositionPulse != null) {
PositionPulse(position);
}
diff --git a/src/GnomeSubtitles/Ui/VideoPreview/Video.cs b/src/GnomeSubtitles/Ui/VideoPreview/Video.cs
index 9d3f057..b222c48 100644
--- a/src/GnomeSubtitles/Ui/VideoPreview/Video.cs
+++ b/src/GnomeSubtitles/Ui/VideoPreview/Video.cs
@@ -20,11 +20,14 @@
using Gdk;
using GnomeSubtitles.Core;
using GnomeSubtitles.Dialog.Message;
+using GnomeSubtitles.Ui.VideoPreview.Exceptions;
using Gtk;
-using GStreamer;
+using Mono.Unix;
using SubLib.Core.Domain;
using SubLib.Core.Timing;
+using SubLib.Util;
using System;
+using System.IO;
namespace GnomeSubtitles.Ui.VideoPreview {
@@ -32,6 +35,7 @@ public class Video {
private Box videoArea = null;
private AspectFrame frame = null;
+ private Uri mediaUri = null;
private Player player = null;
private VideoPosition position = null;
private SubtitleOverlay overlay = null;
@@ -40,6 +44,15 @@ public class Video {
private bool isLoaded = false;
private bool playPauseToggleIsSilent = false; //Used to indicate whether toggling the button should
not issue the toggled signal
+ /* Constants */
+ private const float DefaultAspectRatio = 1.67f;
+ private const int MinSpeed = 10;
+ private const int SpeedStep = 10;
+ private const int MaxSpeed = 200;
+
+ private readonly string PlayerErrorPrimaryMessage = Catalog.GetString("Media Player Error");
+ private readonly string PlayerUnexpectedErrorSecondaryMessage = Catalog.GetString("An unexpected
error has occurred: '{0}'. See the log for additional information.");
+
public Video () {
videoArea = Base.GetWidget(WidgetNames.VideoAreaHBox) as Box;
@@ -72,16 +85,16 @@ public class Video {
get { return isLoaded; }
}
- public bool IsStatePlaying {
- get { return isLoaded && player.State == MediaStatus.Playing; }
+ public bool IsStatusPlaying {
+ get { return isLoaded && player.Status == MediaStatus.Playing; }
}
public float FrameRate {
- get { return player.FrameRate; }
+ get { return player.HasVideo ? player.FrameRate : SubtitleConstants.DefaultFrameRate; }
}
public TimeSpan Duration {
- get { return player.Duration; }
+ get { return TimeSpan.FromMilliseconds(player.Duration); }
}
public bool HasAudio {
@@ -104,34 +117,52 @@ public class Video {
}
/// <summary>Opens a video file.</summary>
- /// <exception cref="PlayerCouldNotOpenVideoException">Thrown if the player could not open the
video.</exception>
- public void Open (Uri videoUri) {
- Close();
+ public void Open(Uri mediaUri) {
+ this.mediaUri = mediaUri;
- frame.Show();
+ try {
+ player.Open(mediaUri);
+ } catch(Exception e) {
+ HandlePlayerError(e);
+ return;
+ }
- player.Open(videoUri);
+ frame.Show();
}
-
- public void Close () {
- if (!isLoaded)
+
+ public void Close() {
+ if (!isLoaded) {
return;
+ }
isLoaded = false;
+ mediaUri = null;
player.Close();
position.Disable();
tracker.Close();
overlay.Close();
/* Update the frame */
- frame.Ratio = Player.DefaultAspectRatio;
- frame.Hide(); //To keep the frame from showing the last video image that was being played
+ frame.Ratio = DefaultAspectRatio;
+ frame.Hide();
SilentDisablePlayPauseButton();
- UpdateSpeedControls(1);
+ UpdateSpeedControls(player.Speed);
SetControlsSensitivity(false);
}
+
+
+ //Things we need to do if there was an error while opening a file
+ private void CloseWhenOpeningHasFailed() {
+ mediaUri = null;
+
+ try {
+ player.Close();
+ } catch(Exception e) {
+ Logger.Error(e, "Player error while forcing it to close after a fatal error has
occurred when opening a file. Should be ok.");
+ }
+ }
public void Quit () {
if (isLoaded) {
@@ -155,17 +186,27 @@ public class Video {
}
public void SpeedUp () {
- player.SpeedUp();
- UpdateSpeedControls(player.Speed);
+ int newSpeed = player.Speed + SpeedStep;
+ if (newSpeed > MaxSpeed) {
+ return;
+ }
+
+ player.SetSpeed(newSpeed);
+ UpdateSpeedControls(newSpeed);
}
public void SpeedDown () {
- player.SpeedDown();
- UpdateSpeedControls(player.Speed);
+ int newSpeed = player.Speed - SpeedStep;
+ if (newSpeed < MinSpeed) {
+ return;
+ }
+
+ player.SetSpeed(newSpeed);
+ UpdateSpeedControls(newSpeed);
}
public void SpeedReset () {
- player.SpeedReset();
+ player.ResetSpeed();
UpdateSpeedControls(player.Speed);
}
@@ -175,7 +216,7 @@ public class Video {
if (!isLoaded)
return;
- player.Seek(time);
+ player.Seek((long)time.TotalMilliseconds);
}
public void Seek (int frames) {
@@ -222,11 +263,12 @@ public class Video {
player.Pause();
}
- private void UpdateSpeedControls (float speed) {
- (Base.GetWidget(WidgetNames.VideoSpeedButton) as Button).Label = String.Format("{0:0.0}x",
speed);
+ private void UpdateSpeedControls (int speed) {
+ float speedFraction = ((float)speed) / 100;
+ (Base.GetWidget(WidgetNames.VideoSpeedButton) as Button).Label = String.Format("{0:0.0}x",
speedFraction);
- Base.GetWidget(WidgetNames.VideoSpeedDownButton).Sensitive = (speed > Player.DefaultMinSpeed);
- Base.GetWidget(WidgetNames.VideoSpeedUpButton).Sensitive = (speed < Player.DefaultMaxSpeed);
+ Base.GetWidget(WidgetNames.VideoSpeedDownButton).Sensitive = (speed > MinSpeed);
+ Base.GetWidget(WidgetNames.VideoSpeedUpButton).Sensitive = (speed < MaxSpeed);
}
private void InitializeVideoFrame () {
@@ -249,22 +291,40 @@ public class Video {
bin.Add(videoFrameEventBox);
bin.ShowAll();
}
+
+ private void HandlePlayerError(Exception e) {
+ Logger.Error(e, "Player error (status {0})", player.Status);
+
+ string secondaryMessage = (e is PlayerException ? e.Message :
string.Format(PlayerUnexpectedErrorSecondaryMessage, e.Message));
+
+ /* All player errors are fatal, so we need to close it.
+ * If we get a player error and we're not loaded yet, it means we got the error
+ * while loading the file, so we need to do some cleanup.
+ * Otherwise, we just close it in the normal fashion.
+ */
+ if (!isLoaded) {
+ string filename = Path.GetFileName(mediaUri.LocalPath);
+ CloseWhenOpeningHasFailed();
+ ShowVideoFileOpenErrorDialog(filename, secondaryMessage);
+ } else {
+ Base.CloseVideo();
+ DialogUtil.ShowError(PlayerErrorPrimaryMessage, secondaryMessage);
+ }
+ }
private void InitializePlayer () {
player = new Player(frame);
- player.FoundVideoInfo += OnPlayerFoundVideoInfo;
- player.StateChanged += OnPlayerStateChanged;
- player.FoundDuration += OnPlayerFoundDuration;
- player.EndOfStream += OnPlayerEndOfStream;
- player.Error += OnPlayerError;
+ player.StatusChanged += OnPlayerStatusChanged;
+ player.EndOfStreamReached += OnPlayerEndOfStreamReached;
+ player.ErrorFound += OnPlayerErrorFound;
}
private void SetControlsSensitivity (bool sensitivity) {
Base.GetWidget(WidgetNames.VideoTimingsVBox).Sensitive = sensitivity;
Base.GetWidget(WidgetNames.VideoPlaybackHBox).Sensitive = sensitivity;
- if ((Core.Base.Ui.View.Selection.Count == 1) && sensitivity)
+ if ((Base.Ui.View.Selection.Count == 1) && sensitivity)
SetSelectionDependentControlsSensitivity(true);
else
SetSelectionDependentControlsSensitivity(false);
@@ -284,19 +344,6 @@ public class Video {
}
}
- private void HandlePlayerLoading () {
- if (isLoaded || (!IsPlayerLoadComplete()))
- return;
-
- isLoaded = true;
- SetControlsSensitivity(true);
- Base.UpdateFromVideoLoaded(player.VideoUri);
- }
-
- private bool IsPlayerLoadComplete () {
- return (player != null) && player.IsLoadComplete;
- }
-
/* Event members */
@@ -319,31 +366,30 @@ public class Video {
Pause();
}
- private void OnPlayerFoundVideoInfo (VideoInfoEventArgs args) {
- HandlePlayerLoading();
- }
-
- private void OnPlayerStateChanged (StateEventArgs args) {
- if (args.State == MediaStatus.Loaded) {
- HandlePlayerLoading();
+ private void OnPlayerStatusChanged(MediaStatus newStatus) {
+ if (newStatus == MediaStatus.Loaded) {
+ isLoaded = true;
+ frame.Ratio = player.HasVideo ? player.AspectRatio : DefaultAspectRatio;
+ SetControlsSensitivity(true);
+ Base.UpdateFromVideoLoaded(mediaUri);
}
}
- private void OnPlayerFoundDuration (TimeSpan duration) {
- HandlePlayerLoading();
- }
-
- private void OnPlayerEndOfStream () {
+ private void OnPlayerEndOfStreamReached() {
ToggleButton playPauseButton = Base.GetWidget(WidgetNames.VideoPlayPauseButton) as
ToggleButton;
playPauseButton.Active = false;
}
- private void OnPlayerError (Uri videoUri, Exception e) {
- Close();
- VideoErrorDialog dialog = new VideoErrorDialog(videoUri, e);
+ private void OnPlayerErrorFound(string error) {
+ HandlePlayerError(new PlayerException(error));
+ }
+
+ private void ShowVideoFileOpenErrorDialog(string filename, string error) {
+ VideoFileOpenErrorDialog dialog = new VideoFileOpenErrorDialog(filename, error);
bool toOpenAnother = dialog.WaitForResponse();
- if (toOpenAnother)
+ if (toOpenAnother) {
Base.Ui.OpenVideo();
+ }
}
private void OnSubtitleSelectionChanged (TreePath[] paths, Subtitle subtitle) {
diff --git a/src/GnomeSubtitles/Ui/VideoPreview/VideoPosition.cs
b/src/GnomeSubtitles/Ui/VideoPreview/VideoPosition.cs
index cc8b496..7a1694f 100644
--- a/src/GnomeSubtitles/Ui/VideoPreview/VideoPosition.cs
+++ b/src/GnomeSubtitles/Ui/VideoPreview/VideoPosition.cs
@@ -1,6 +1,6 @@
/*
* This file is part of Gnome Subtitles.
- * Copyright (C) 2006-2019 Pedro Castro
+ * Copyright (C) 2006-2021 Pedro Castro
*
* Gnome Subtitles is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -39,8 +39,8 @@ public class VideoPosition {
private bool isPlayerUpdate = false;
/* Constants */
- private const int userUpdateTimeout = 100; //Milliseconds
- private TimeSpan seekIncrement = TimeSpan.FromMilliseconds(500);
+ private const int userUpdateTimeout = 100; //ms
+ private long seekIncrement = 500; //ms
/* Delegates */
public delegate void VideoPositionPulseHandler (TimeSpan position);
@@ -62,11 +62,10 @@ public class VideoPosition {
/* Public properties */
- public TimeSpan SeekIncrement {
+ public long SeekIncrement {
get { return seekIncrement; }
}
- /// <summary>The current position, in seconds.</summary>
public TimeSpan CurrentTime {
get { return position; }
}
@@ -76,7 +75,7 @@ public class VideoPosition {
}
public TimeSpan Duration {
- get { return player.Duration; }
+ get { return TimeSpan.FromMilliseconds(player.Duration); }
}
public int DurationInFrames {
@@ -109,14 +108,14 @@ public class VideoPosition {
}
/// <summary>Handles changes in the player position.</summary>
- private void OnPlayerPositionPulse (TimeSpan newPosition) {
- position = newPosition;
+ private void OnPlayerPositionPulse (long newPosition) {
+ position = TimeSpan.FromMilliseconds(newPosition);
if (userUpdateTimeoutId == 0) //There is not a manual positioning going on
- UpdateSlider(newPosition);
+ UpdateSlider(position);
- UpdatePositionValueLabel(newPosition);
- EmitVideoPositionPulse(newPosition);
+ UpdatePositionValueLabel(position);
+ EmitVideoPositionPulse(position);
}
private void OnBaseVideoLoaded (Uri videoUri) {
@@ -127,7 +126,7 @@ public class VideoPosition {
private bool UpdatePlayerPosition () {
userUpdateTimeoutId = 0;
- player.Seek(slider.Value);
+ player.Seek((long)slider.Value);
return false;
}
@@ -165,7 +164,7 @@ public class VideoPosition {
private void OnBaseTimingModeChanged (TimingMode timingMode) {
UpdatePositionLabel(timingMode);
UpdatePositionValueLabel(position);
- TimeSpan length = player.Duration;
+ TimeSpan length = TimeSpan.FromMilliseconds(player.Duration);
UpdateLengthLabel(timingMode, length);
}
diff --git a/src/Makefile.am b/src/Makefile.am
index fd73c52..77e37e7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -12,16 +12,16 @@ ASSEMBLY_CONFIG = $(ASSEMBLY).config
ASSEMBLY_CONFIG_SRC = $(srcdir)/GnomeSubtitles/Execution/gnome-subtitles.exe.config
AM_CFLAGS = $(gstreamer_CFLAGS) -Wall -g -fPIC
-gnomesubtitles_LTLIBRARIES = libgstreamer_playbin.la
-libgstreamer_playbin_la_SOURCES = External/GStreamerPlaybin/main.c
-libgstreamer_playbin_la_LIBADD = $(gstreamer_LIBS)
-libgstreamer_playbin_la_LDFLAGS = -module -avoid-version
-libgstreamer_playbin_la_LIBTOOLFLAGS = --tag=disable-static
+gnomesubtitles_LTLIBRARIES = libgst_backend.la
+libgst_backend_la_SOURCES = External/GstBackend/gst-backend.c
+libgst_backend_la_LIBADD = $(gstreamer_LIBS)
+libgst_backend_la_LDFLAGS = -module -avoid-version
+libgst_backend_la_LIBTOOLFLAGS = --tag=disable-static
GSSOURCES = \
$(srcdir)/External/Enchant/*.cs \
$(srcdir)/External/GtkSpell/*.cs \
- $(srcdir)/External/GStreamerPlaybin/*.cs \
+ $(srcdir)/External/GstBackend/*.cs \
$(srcdir)/External/Interop/*.cs \
$(srcdir)/External/NCharDet/*.cs \
$(srcdir)/GnomeSubtitles/Core/*.cs \
@@ -59,7 +59,7 @@ $(ASSEMBLY): $(GSSOURCES) $(GS_RESOURCES)
$(ASSEMBLY_CONFIG): $(gnomesubtitles_LTLIBRARIES)
cp -f $(ASSEMBLY_CONFIG_SRC) $(GS_BUILDDIR)
- cp -f $(srcdir)/.libs/libgstreamer_playbin.so $(GS_BUILDDIR)
+ cp -f $(srcdir)/.libs/libgst_backend.so $(GS_BUILDDIR)
bin_SCRIPTS = $(srcdir)/GnomeSubtitles/Execution/gnome-subtitles
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]