/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "gtkplayer.hxx" #include 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 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::get()); if (!pContainer) return; css::lang::EventObject aEvent; aEvent.Source = getXWeak(); comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer); while (pIterator.hasMoreElements()) { css::uno::Reference xListener( static_cast(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(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(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& 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(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(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& rListener) { m_lListener.addInterface(cppu::UnoType::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& rListener) { m_lListener.removeInterface(cppu::UnoType::get(), rListener); } namespace { class GtkFrameGrabber : public ::cppu::WeakImplHelper { 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 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 xBitmap( vcl::bitmap::CreateFromCairoSurface(Size(m_aSize.Width, m_aSize.Height), surface)); cairo_surface_destroy(surface); return Graphic(*xBitmap).GetXGraphic(); } }; } uno::Reference SAL_CALL GtkPlayer::createFrameGrabber() { osl::MutexGuard aGuard(m_aMutex); rtl::Reference 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 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: */