c12df614ad
Change-Id: Ib56a05e171f485ad01cb54ed87e01c3536ea733d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158142 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
469 lines
12 KiB
C++
469 lines
12 KiB
C++
/* -*- 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: */
|