summaryrefslogtreecommitdiffstats
path: root/avmedia/source/gtk/gtkplayer.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'avmedia/source/gtk/gtkplayer.cxx')
-rw-r--r--avmedia/source/gtk/gtkplayer.cxx469
1 files changed, 469 insertions, 0 deletions
diff --git a/avmedia/source/gtk/gtkplayer.cxx b/avmedia/source/gtk/gtkplayer.cxx
new file mode 100644
index 0000000000..4dca3e202a
--- /dev/null
+++ b/avmedia/source/gtk/gtkplayer.cxx
@@ -0,0 +1,469 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+
+#include <mutex>
+
+#include <cppuhelper/supportsservice.hxx>
+#include <sal/log.hxx>
+#include <rtl/string.hxx>
+#include <tools/link.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/syschild.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/timer.hxx>
+
+#include <gstwindow.hxx>
+#include "gtkplayer.hxx"
+
+#include <gtk/gtk.h>
+
+constexpr OUStringLiteral AVMEDIA_GTK_PLAYER_IMPLEMENTATIONNAME
+ = u"com.sun.star.comp.avmedia.Player_Gtk";
+constexpr OUString AVMEDIA_GTK_PLAYER_SERVICENAME = u"com.sun.star.media.Player_Gtk"_ustr;
+
+using namespace ::com::sun::star;
+
+namespace avmedia::gtk
+{
+GtkPlayer::GtkPlayer()
+ : GtkPlayer_BASE(m_aMutex)
+ , m_lListener(m_aMutex)
+ , m_pStream(nullptr)
+ , m_pVideo(nullptr)
+ , m_nNotifySignalId(0)
+ , m_nInvalidateSizeSignalId(0)
+ , m_nTimeoutId(0)
+ , m_nUnmutedVolume(0)
+{
+}
+
+GtkPlayer::~GtkPlayer() { disposing(); }
+
+static gboolean gtk_media_stream_unref(gpointer user_data)
+{
+ g_object_unref(user_data);
+ return FALSE;
+}
+
+void GtkPlayer::cleanup()
+{
+ if (m_pVideo)
+ {
+ gtk_widget_unparent(m_pVideo);
+ m_pVideo = nullptr;
+ }
+
+ if (m_pStream)
+ {
+ uninstallNotify();
+
+ // shouldn't have to attempt this unref on idle, but with gtk4-4.4.1 I get
+ // intermittent "instance of invalid non-instantiable type '(null)'"
+ // on some mysterious gst dbus callback
+ if (g_main_context_default())
+ g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, gtk_media_stream_unref, m_pStream, nullptr);
+ else
+ g_object_unref(m_pStream);
+ m_pStream = nullptr;
+ }
+}
+
+void SAL_CALL GtkPlayer::disposing()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ stop();
+
+ cleanup();
+}
+
+static void do_notify(GtkPlayer* pThis)
+{
+ rtl::Reference<GtkPlayer> xThis(pThis);
+ xThis->notifyListeners();
+ xThis->uninstallNotify();
+}
+
+static void invalidate_size_cb(GdkPaintable* /*pPaintable*/, GtkPlayer* pThis) { do_notify(pThis); }
+
+static void notify_cb(GtkMediaStream* /*pStream*/, GParamSpec* pspec, GtkPlayer* pThis)
+{
+ if (g_str_equal(pspec->name, "prepared") || g_str_equal(pspec->name, "error"))
+ do_notify(pThis);
+}
+
+static bool timeout_cb(GtkPlayer* pThis)
+{
+ do_notify(pThis);
+ return false;
+}
+
+void GtkPlayer::installNotify()
+{
+ if (m_nNotifySignalId)
+ return;
+ m_nNotifySignalId = g_signal_connect(m_pStream, "notify", G_CALLBACK(notify_cb), this);
+ // notify should be enough, but there is an upstream bug so also try "invalidate-size" and add a timeout for
+ // audio-only case where that won't happen, see: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4513
+ m_nInvalidateSizeSignalId
+ = g_signal_connect(m_pStream, "invalidate-size", G_CALLBACK(invalidate_size_cb), this);
+ m_nTimeoutId = g_timeout_add_seconds(10, G_SOURCE_FUNC(timeout_cb), this);
+}
+
+void GtkPlayer::uninstallNotify()
+{
+ if (!m_nNotifySignalId)
+ return;
+ g_signal_handler_disconnect(m_pStream, m_nNotifySignalId);
+ m_nNotifySignalId = 0;
+ g_signal_handler_disconnect(m_pStream, m_nInvalidateSizeSignalId);
+ m_nInvalidateSizeSignalId = 0;
+ g_source_remove(m_nTimeoutId);
+ m_nTimeoutId = 0;
+}
+
+bool GtkPlayer::create(const OUString& rURL)
+{
+ bool bRet = false;
+
+ cleanup();
+
+ if (!rURL.isEmpty())
+ {
+ GFile* pFile = g_file_new_for_uri(OUStringToOString(rURL, RTL_TEXTENCODING_UTF8).getStr());
+ m_pStream = gtk_media_file_new_for_file(pFile);
+ g_object_unref(pFile);
+
+ bRet = gtk_media_stream_get_error(m_pStream) == nullptr;
+ }
+
+ if (bRet)
+ m_aURL = rURL;
+ else
+ m_aURL.clear();
+
+ return bRet;
+}
+
+void GtkPlayer::notifyListeners()
+{
+ comphelper::OInterfaceContainerHelper2* pContainer
+ = m_lListener.getContainer(cppu::UnoType<css::media::XPlayerListener>::get());
+ if (!pContainer)
+ return;
+
+ css::lang::EventObject aEvent;
+ aEvent.Source = getXWeak();
+
+ comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer);
+ while (pIterator.hasMoreElements())
+ {
+ css::uno::Reference<css::media::XPlayerListener> xListener(
+ static_cast<css::media::XPlayerListener*>(pIterator.next()));
+ xListener->preferredPlayerWindowSizeAvailable(aEvent);
+ }
+}
+
+void SAL_CALL GtkPlayer::start()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (m_pStream)
+ gtk_media_stream_play(m_pStream);
+}
+
+void SAL_CALL GtkPlayer::stop()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (m_pStream)
+ gtk_media_stream_pause(m_pStream);
+}
+
+sal_Bool SAL_CALL GtkPlayer::isPlaying()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ bool bRet = false;
+
+ if (m_pStream)
+ bRet = gtk_media_stream_get_playing(m_pStream);
+
+ return bRet;
+}
+
+double SAL_CALL GtkPlayer::getDuration()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ double duration = 0.0;
+
+ if (m_pStream)
+ duration = gtk_media_stream_get_duration(m_pStream) / 1000000.0;
+
+ return duration;
+}
+
+void SAL_CALL GtkPlayer::setMediaTime(double fTime)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (!m_pStream)
+ return;
+
+ gint64 gst_position = llround(fTime * 1000000);
+
+ gtk_media_stream_seek(m_pStream, gst_position);
+
+ // on resetting back to zero the reported timestamp doesn't seem to get
+ // updated in a reasonable time, so on zero just force an update of
+ // timestamp to 0
+ if (gst_position == 0 && gtk_media_stream_is_prepared(m_pStream))
+ gtk_media_stream_update(m_pStream, gst_position);
+}
+
+double SAL_CALL GtkPlayer::getMediaTime()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ double position = 0.0;
+
+ if (m_pStream)
+ position = gtk_media_stream_get_timestamp(m_pStream) / 1000000.0;
+
+ return position;
+}
+
+void SAL_CALL GtkPlayer::setPlaybackLoop(sal_Bool bSet)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+ gtk_media_stream_set_loop(m_pStream, bSet);
+}
+
+sal_Bool SAL_CALL GtkPlayer::isPlaybackLoop()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+ return gtk_media_stream_get_loop(m_pStream);
+}
+
+// gtk4-4.4.1 docs state: "Muting a stream will cause no audio to be played, but
+// it does not modify the volume. This means that muting and then unmuting the
+// stream will restore the volume settings." but that doesn't seem to be my
+// experience at all
+void SAL_CALL GtkPlayer::setMute(sal_Bool bSet)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+ bool bMuted = gtk_media_stream_get_muted(m_pStream);
+ if (bMuted == static_cast<bool>(bSet))
+ return;
+ gtk_media_stream_set_muted(m_pStream, bSet);
+ if (!bSet)
+ setVolumeDB(m_nUnmutedVolume);
+}
+
+sal_Bool SAL_CALL GtkPlayer::isMute()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+ return gtk_media_stream_get_muted(m_pStream);
+}
+
+void SAL_CALL GtkPlayer::setVolumeDB(sal_Int16 nVolumeDB)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ // range is -40 for silence to 0 for full volume
+ m_nUnmutedVolume = std::clamp<sal_Int16>(nVolumeDB, -40, 0);
+ double fValue = (m_nUnmutedVolume + 40) / 40.0;
+ gtk_media_stream_set_volume(m_pStream, fValue);
+}
+
+sal_Int16 SAL_CALL GtkPlayer::getVolumeDB()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (gtk_media_stream_get_muted(m_pStream))
+ return m_nUnmutedVolume;
+
+ double fVolume = gtk_media_stream_get_volume(m_pStream);
+
+ m_nUnmutedVolume = (fVolume * 40) - 40;
+
+ return m_nUnmutedVolume;
+}
+
+awt::Size SAL_CALL GtkPlayer::getPreferredPlayerWindowSize()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ awt::Size aSize(0, 0);
+
+ if (m_pStream)
+ {
+ aSize.Width = gdk_paintable_get_intrinsic_width(GDK_PAINTABLE(m_pStream));
+ aSize.Height = gdk_paintable_get_intrinsic_height(GDK_PAINTABLE(m_pStream));
+ }
+
+ return aSize;
+}
+
+uno::Reference<::media::XPlayerWindow>
+ SAL_CALL GtkPlayer::createPlayerWindow(const uno::Sequence<uno::Any>& rArguments)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ uno::Reference<::media::XPlayerWindow> xRet;
+
+ if (rArguments.getLength() > 1)
+ rArguments[1] >>= m_aArea;
+
+ if (rArguments.getLength() <= 2)
+ {
+ xRet = new ::avmedia::gstreamer::Window;
+ return xRet;
+ }
+
+ sal_IntPtr pIntPtr = 0;
+ rArguments[2] >>= pIntPtr;
+ SystemChildWindow* pParentWindow = reinterpret_cast<SystemChildWindow*>(pIntPtr);
+ if (!pParentWindow)
+ return nullptr;
+
+ const SystemEnvData* pEnvData = pParentWindow->GetSystemData();
+ if (!pEnvData)
+ return nullptr;
+
+ m_pVideo = gtk_picture_new_for_paintable(GDK_PAINTABLE(m_pStream));
+#if GTK_CHECK_VERSION(4, 7, 2)
+ gtk_picture_set_content_fit(GTK_PICTURE(m_pVideo), GTK_CONTENT_FIT_FILL);
+#else
+ gtk_picture_set_keep_aspect_ratio(GTK_PICTURE(m_pVideo), false);
+#endif
+ gtk_widget_set_can_target(m_pVideo, false);
+ gtk_widget_set_vexpand(m_pVideo, true);
+ gtk_widget_set_hexpand(m_pVideo, true);
+
+ GtkWidget* pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
+ gtk_widget_set_can_target(pParent, false);
+ gtk_grid_attach(GTK_GRID(pParent), m_pVideo, 0, 0, 1, 1);
+ // "‘void gtk_widget_show(GtkWidget*)’ is deprecated: Use 'gtk_widget_set_visible or
+ // gtk_window_present' instead":
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ gtk_widget_show(m_pVideo);
+ gtk_widget_show(pParent);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ xRet = new ::avmedia::gstreamer::Window;
+
+ return xRet;
+}
+
+void SAL_CALL
+GtkPlayer::addPlayerListener(const css::uno::Reference<css::media::XPlayerListener>& rListener)
+{
+ m_lListener.addInterface(cppu::UnoType<css::media::XPlayerListener>::get(), rListener);
+ if (gtk_media_stream_is_prepared(m_pStream))
+ {
+ css::lang::EventObject aEvent;
+ aEvent.Source = getXWeak();
+ rListener->preferredPlayerWindowSizeAvailable(aEvent);
+ }
+ else
+ installNotify();
+}
+
+void SAL_CALL
+GtkPlayer::removePlayerListener(const css::uno::Reference<css::media::XPlayerListener>& rListener)
+{
+ m_lListener.removeInterface(cppu::UnoType<css::media::XPlayerListener>::get(), rListener);
+}
+
+namespace
+{
+class GtkFrameGrabber : public ::cppu::WeakImplHelper<css::media::XFrameGrabber>
+{
+private:
+ awt::Size m_aSize;
+ GtkMediaStream* m_pStream;
+
+public:
+ GtkFrameGrabber(GtkMediaStream* pStream, const awt::Size& rSize)
+ : m_aSize(rSize)
+ , m_pStream(pStream)
+ {
+ g_object_ref(m_pStream);
+ }
+
+ virtual ~GtkFrameGrabber() override { g_object_unref(m_pStream); }
+
+ // XFrameGrabber
+ virtual css::uno::Reference<css::graphic::XGraphic>
+ SAL_CALL grabFrame(double fMediaTime) override
+ {
+ gint64 gst_position = llround(fMediaTime * 1000000);
+ gtk_media_stream_seek(m_pStream, gst_position);
+
+ cairo_surface_t* surface
+ = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, m_aSize.Width, m_aSize.Height);
+
+ GtkSnapshot* snapshot = gtk_snapshot_new();
+ gdk_paintable_snapshot(GDK_PAINTABLE(m_pStream), snapshot, m_aSize.Width, m_aSize.Height);
+ GskRenderNode* node = gtk_snapshot_free_to_node(snapshot);
+
+ cairo_t* cr = cairo_create(surface);
+ gsk_render_node_draw(node, cr);
+ cairo_destroy(cr);
+
+ gsk_render_node_unref(node);
+
+ std::unique_ptr<BitmapEx> xBitmap(
+ vcl::bitmap::CreateFromCairoSurface(Size(m_aSize.Width, m_aSize.Height), surface));
+
+ cairo_surface_destroy(surface);
+
+ return Graphic(*xBitmap).GetXGraphic();
+ }
+};
+}
+
+uno::Reference<media::XFrameGrabber> SAL_CALL GtkPlayer::createFrameGrabber()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ rtl::Reference<GtkFrameGrabber> xFrameGrabber;
+
+ const awt::Size aPrefSize(getPreferredPlayerWindowSize());
+
+ if (aPrefSize.Width > 0 && aPrefSize.Height > 0)
+ xFrameGrabber.set(new GtkFrameGrabber(m_pStream, aPrefSize));
+
+ return xFrameGrabber;
+}
+
+OUString SAL_CALL GtkPlayer::getImplementationName()
+{
+ return AVMEDIA_GTK_PLAYER_IMPLEMENTATIONNAME;
+}
+
+sal_Bool SAL_CALL GtkPlayer::supportsService(const OUString& ServiceName)
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL GtkPlayer::getSupportedServiceNames()
+{
+ return { AVMEDIA_GTK_PLAYER_SERVICENAME };
+}
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */