LibreOffice Module avmedia (master) 1
gtkplayer.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
9
10#include <sal/config.h>
11
12#include <mutex>
13
15#include <sal/log.hxx>
16#include <rtl/string.hxx>
17#include <tools/link.hxx>
18#include <vcl/BitmapTools.hxx>
19#include <vcl/graph.hxx>
20#include <vcl/svapp.hxx>
21#include <vcl/syschild.hxx>
22#include <vcl/sysdata.hxx>
23#include <vcl/timer.hxx>
24
25#include <gstwindow.hxx>
26#include "gtkplayer.hxx"
27
28#include <gtk/gtk.h>
29
31 = u"com.sun.star.comp.avmedia.Player_Gtk";
32constexpr OUStringLiteral AVMEDIA_GTK_PLAYER_SERVICENAME = u"com.sun.star.media.Player_Gtk";
33
34using namespace ::com::sun::star;
35
36namespace avmedia::gtk
37{
40 , m_lListener(m_aMutex)
41 , m_pStream(nullptr)
42 , m_pVideo(nullptr)
43 , m_nNotifySignalId(0)
44 , m_nInvalidateSizeSignalId(0)
45 , m_nTimeoutId(0)
46 , m_nUnmutedVolume(0)
47{
48}
49
51
52static gboolean gtk_media_stream_unref(gpointer user_data)
53{
54 g_object_unref(user_data);
55 return FALSE;
56}
57
59{
60 if (m_pVideo)
61 {
62 gtk_widget_unparent(m_pVideo);
63 m_pVideo = nullptr;
64 }
65
66 if (m_pStream)
67 {
69
70 // shouldn't have to attempt this unref on idle, but with gtk4-4.4.1 I get
71 // intermittent "instance of invalid non-instantiable type '(null)'"
72 // on some mysterious gst dbus callback
73 if (g_main_context_default())
74 g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, gtk_media_stream_unref, m_pStream, nullptr);
75 else
76 g_object_unref(m_pStream);
77 m_pStream = nullptr;
78 }
79}
80
81void SAL_CALL GtkPlayer::disposing()
82{
83 osl::MutexGuard aGuard(m_aMutex);
84
85 stop();
86
87 cleanup();
88}
89
90static void do_notify(GtkPlayer* pThis)
91{
92 rtl::Reference<GtkPlayer> xThis(pThis);
93 xThis->notifyListeners();
94 xThis->uninstallNotify();
95}
96
97static void invalidate_size_cb(GdkPaintable* /*pPaintable*/, GtkPlayer* pThis) { do_notify(pThis); }
98
99static void notify_cb(GtkMediaStream* /*pStream*/, GParamSpec* pspec, GtkPlayer* pThis)
100{
101 if (g_str_equal(pspec->name, "prepared") || g_str_equal(pspec->name, "error"))
102 do_notify(pThis);
103}
104
105static bool timeout_cb(GtkPlayer* pThis)
106{
107 do_notify(pThis);
108 return false;
109}
110
112{
114 return;
115 m_nNotifySignalId = g_signal_connect(m_pStream, "notify", G_CALLBACK(notify_cb), this);
116 // notify should be enough, but there is an upstream bug so also try "invalidate-size" and add a timeout for
117 // audio-only case where that won't happen, see: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4513
119 = g_signal_connect(m_pStream, "invalidate-size", G_CALLBACK(invalidate_size_cb), this);
120 m_nTimeoutId = g_timeout_add_seconds(10, G_SOURCE_FUNC(timeout_cb), this);
121}
122
124{
126 return;
127 g_signal_handler_disconnect(m_pStream, m_nNotifySignalId);
129 g_signal_handler_disconnect(m_pStream, m_nInvalidateSizeSignalId);
131 g_source_remove(m_nTimeoutId);
132 m_nTimeoutId = 0;
133}
134
135bool GtkPlayer::create(const OUString& rURL)
136{
137 bool bRet = false;
138
139 cleanup();
140
141 if (!rURL.isEmpty())
142 {
143 GFile* pFile = g_file_new_for_uri(OUStringToOString(rURL, RTL_TEXTENCODING_UTF8).getStr());
144 m_pStream = gtk_media_file_new_for_file(pFile);
145 g_object_unref(pFile);
146
147 bRet = gtk_media_stream_get_error(m_pStream) == nullptr;
148 }
149
150 if (bRet)
151 m_aURL = rURL;
152 else
153 m_aURL.clear();
154
155 return bRet;
156}
157
159{
162 if (!pContainer)
163 return;
164
165 css::lang::EventObject aEvent;
166 aEvent.Source = getXWeak();
167
168 comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer);
169 while (pIterator.hasMoreElements())
170 {
171 css::uno::Reference<css::media::XPlayerListener> xListener(
172 static_cast<css::media::XPlayerListener*>(pIterator.next()));
173 xListener->preferredPlayerWindowSizeAvailable(aEvent);
174 }
175}
176
177void SAL_CALL GtkPlayer::start()
178{
179 osl::MutexGuard aGuard(m_aMutex);
180
181 if (m_pStream)
182 gtk_media_stream_play(m_pStream);
183}
184
185void SAL_CALL GtkPlayer::stop()
186{
187 osl::MutexGuard aGuard(m_aMutex);
188
189 if (m_pStream)
190 gtk_media_stream_pause(m_pStream);
191}
192
194{
195 osl::MutexGuard aGuard(m_aMutex);
196
197 bool bRet = false;
198
199 if (m_pStream)
200 bRet = gtk_media_stream_get_playing(m_pStream);
201
202 return bRet;
203}
204
205double SAL_CALL GtkPlayer::getDuration()
206{
207 osl::MutexGuard aGuard(m_aMutex);
208
209 double duration = 0.0;
210
211 if (m_pStream)
212 duration = gtk_media_stream_get_duration(m_pStream) / 1000000.0;
213
214 return duration;
215}
216
217void SAL_CALL GtkPlayer::setMediaTime(double fTime)
218{
219 osl::MutexGuard aGuard(m_aMutex);
220
221 if (!m_pStream)
222 return;
223
224 gint64 gst_position = llround(fTime * 1000000);
225
226 gtk_media_stream_seek(m_pStream, gst_position);
227
228 // on resetting back to zero the reported timestamp doesn't seem to get
229 // updated in a reasonable time, so on zero just force an update of
230 // timestamp to 0
231 if (gst_position == 0 && gtk_media_stream_is_prepared(m_pStream))
232 gtk_media_stream_update(m_pStream, gst_position);
233}
234
235double SAL_CALL GtkPlayer::getMediaTime()
236{
237 osl::MutexGuard aGuard(m_aMutex);
238
239 double position = 0.0;
240
241 if (m_pStream)
242 position = gtk_media_stream_get_timestamp(m_pStream) / 1000000.0;
243
244 return position;
245}
246
248{
249 osl::MutexGuard aGuard(m_aMutex);
250 gtk_media_stream_set_loop(m_pStream, bSet);
251}
252
254{
255 osl::MutexGuard aGuard(m_aMutex);
256 return gtk_media_stream_get_loop(m_pStream);
257}
258
259// gtk4-4.4.1 docs state: "Muting a stream will cause no audio to be played, but
260// it does not modify the volume. This means that muting and then unmuting the
261// stream will restore the volume settings." but that doesn't seem to be my
262// experience at all
263void SAL_CALL GtkPlayer::setMute(sal_Bool bSet)
264{
265 osl::MutexGuard aGuard(m_aMutex);
266 bool bMuted = gtk_media_stream_get_muted(m_pStream);
267 if (bMuted == static_cast<bool>(bSet))
268 return;
269 gtk_media_stream_set_muted(m_pStream, bSet);
270 if (!bSet)
272}
273
275{
276 osl::MutexGuard aGuard(m_aMutex);
277 return gtk_media_stream_get_muted(m_pStream);
278}
279
280void SAL_CALL GtkPlayer::setVolumeDB(sal_Int16 nVolumeDB)
281{
282 osl::MutexGuard aGuard(m_aMutex);
283
284 // range is -40 for silence to 0 for full volume
285 m_nUnmutedVolume = std::clamp<sal_Int16>(nVolumeDB, -40, 0);
286 double fValue = (m_nUnmutedVolume + 40) / 40.0;
287 gtk_media_stream_set_volume(m_pStream, fValue);
288}
289
290sal_Int16 SAL_CALL GtkPlayer::getVolumeDB()
291{
292 osl::MutexGuard aGuard(m_aMutex);
293
294 if (gtk_media_stream_get_muted(m_pStream))
295 return m_nUnmutedVolume;
296
297 double fVolume = gtk_media_stream_get_volume(m_pStream);
298
299 m_nUnmutedVolume = (fVolume * 40) - 40;
300
301 return m_nUnmutedVolume;
302}
303
305{
306 osl::MutexGuard aGuard(m_aMutex);
307
308 awt::Size aSize(0, 0);
309
310 if (m_pStream)
311 {
312 aSize.Width = gdk_paintable_get_intrinsic_width(GDK_PAINTABLE(m_pStream));
313 aSize.Height = gdk_paintable_get_intrinsic_height(GDK_PAINTABLE(m_pStream));
314 }
315
316 return aSize;
317}
318
319uno::Reference<::media::XPlayerWindow>
320 SAL_CALL GtkPlayer::createPlayerWindow(const uno::Sequence<uno::Any>& rArguments)
321{
322 osl::MutexGuard aGuard(m_aMutex);
323
324 uno::Reference<::media::XPlayerWindow> xRet;
325
326 if (rArguments.getLength() > 1)
327 rArguments[1] >>= m_aArea;
328
329 if (rArguments.getLength() <= 2)
330 {
331 xRet = new ::avmedia::gstreamer::Window;
332 return xRet;
333 }
334
335 sal_IntPtr pIntPtr = 0;
336 rArguments[2] >>= pIntPtr;
337 SystemChildWindow* pParentWindow = reinterpret_cast<SystemChildWindow*>(pIntPtr);
338 if (!pParentWindow)
339 return nullptr;
340
341 const SystemEnvData* pEnvData = pParentWindow->GetSystemData();
342 if (!pEnvData)
343 return nullptr;
344
345 m_pVideo = gtk_picture_new_for_paintable(GDK_PAINTABLE(m_pStream));
346#if GTK_CHECK_VERSION(4, 7, 2)
347 gtk_picture_set_content_fit(GTK_PICTURE(m_pVideo), GTK_CONTENT_FIT_FILL);
348#else
349 gtk_picture_set_keep_aspect_ratio(GTK_PICTURE(m_pVideo), false);
350#endif
351 gtk_widget_set_can_target(m_pVideo, false);
352 gtk_widget_set_vexpand(m_pVideo, true);
353 gtk_widget_set_hexpand(m_pVideo, true);
354
355 GtkWidget* pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
356 gtk_widget_set_can_target(pParent, false);
357 gtk_grid_attach(GTK_GRID(pParent), m_pVideo, 0, 0, 1, 1);
358 // "‘void gtk_widget_show(GtkWidget*)’ is deprecated: Use 'gtk_widget_set_visible or
359 // gtk_window_present' instead":
361 gtk_widget_show(m_pVideo);
362 gtk_widget_show(pParent);
364
365 xRet = new ::avmedia::gstreamer::Window;
366
367 return xRet;
368}
369
370void SAL_CALL
371GtkPlayer::addPlayerListener(const css::uno::Reference<css::media::XPlayerListener>& rListener)
372{
374 if (gtk_media_stream_is_prepared(m_pStream))
375 {
376 css::lang::EventObject aEvent;
377 aEvent.Source = getXWeak();
378 rListener->preferredPlayerWindowSizeAvailable(aEvent);
379 }
380 else
382}
383
384void SAL_CALL
385GtkPlayer::removePlayerListener(const css::uno::Reference<css::media::XPlayerListener>& rListener)
386{
388}
389
390namespace
391{
392class GtkFrameGrabber : public ::cppu::WeakImplHelper<css::media::XFrameGrabber>
393{
394private:
395 awt::Size m_aSize;
397
398public:
399 GtkFrameGrabber(GtkMediaStream* pStream, const awt::Size& rSize)
400 : m_aSize(rSize)
401 , m_pStream(pStream)
402 {
403 g_object_ref(m_pStream);
404 }
405
406 virtual ~GtkFrameGrabber() override { g_object_unref(m_pStream); }
407
408 // XFrameGrabber
409 virtual css::uno::Reference<css::graphic::XGraphic>
410 SAL_CALL grabFrame(double fMediaTime) override
411 {
412 gint64 gst_position = llround(fMediaTime * 1000000);
413 gtk_media_stream_seek(m_pStream, gst_position);
414
415 cairo_surface_t* surface
416 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, m_aSize.Width, m_aSize.Height);
417
418 GtkSnapshot* snapshot = gtk_snapshot_new();
419 gdk_paintable_snapshot(GDK_PAINTABLE(m_pStream), snapshot, m_aSize.Width, m_aSize.Height);
420 GskRenderNode* node = gtk_snapshot_free_to_node(snapshot);
421
422 cairo_t* cr = cairo_create(surface);
423 gsk_render_node_draw(node, cr);
424 cairo_destroy(cr);
425
426 gsk_render_node_unref(node);
427
428 std::unique_ptr<BitmapEx> xBitmap(
429 vcl::bitmap::CreateFromCairoSurface(Size(m_aSize.Width, m_aSize.Height), surface));
430
431 cairo_surface_destroy(surface);
432
433 return Graphic(*xBitmap).GetXGraphic();
434 }
435};
436}
437
438uno::Reference<media::XFrameGrabber> SAL_CALL GtkPlayer::createFrameGrabber()
439{
440 osl::MutexGuard aGuard(m_aMutex);
441
443
444 const awt::Size aPrefSize(getPreferredPlayerWindowSize());
445
446 if (aPrefSize.Width > 0 && aPrefSize.Height > 0)
447 xFrameGrabber.set(new GtkFrameGrabber(m_pStream, aPrefSize));
448
449 return xFrameGrabber;
450}
451
453{
455}
456
457sal_Bool SAL_CALL GtkPlayer::supportsService(const OUString& ServiceName)
458{
460}
461
462uno::Sequence<OUString> SAL_CALL GtkPlayer::getSupportedServiceNames()
463{
465}
466
467} // namespace
468
469/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
AnyEventRef aEvent
struct _cairo_surface cairo_surface_t
struct _cairo cairo_t
css::uno::Reference< css::graphic::XGraphic > GetXGraphic() const
virtual const SystemEnvData * GetSystemData() const override
css::awt::Rectangle m_aArea
Definition: gtkplayer.hxx:78
virtual css::uno::Reference< css::media::XFrameGrabber > SAL_CALL createFrameGrabber() override
Definition: gtkplayer.cxx:438
virtual OUString SAL_CALL getImplementationName() override
Definition: gtkplayer.cxx:452
virtual void SAL_CALL addPlayerListener(const css::uno::Reference< css::media::XPlayerListener > &rListener) override
Definition: gtkplayer.cxx:371
unsigned long m_nInvalidateSizeSignalId
Definition: gtkplayer.hxx:82
virtual sal_Bool SAL_CALL isMute() override
Definition: gtkplayer.cxx:274
virtual void SAL_CALL setMediaTime(double fTime) override
Definition: gtkplayer.cxx:217
virtual void SAL_CALL stop() override
Definition: gtkplayer.cxx:185
virtual void SAL_CALL setVolumeDB(sal_Int16 nVolumeDB) override
Definition: gtkplayer.cxx:280
virtual css::awt::Size SAL_CALL getPreferredPlayerWindowSize() override
Definition: gtkplayer.cxx:304
virtual double SAL_CALL getMediaTime() override
Definition: gtkplayer.cxx:235
virtual sal_Int16 SAL_CALL getVolumeDB() override
Definition: gtkplayer.cxx:290
virtual sal_Bool SAL_CALL supportsService(const OUString &ServiceName) override
Definition: gtkplayer.cxx:457
virtual sal_Bool SAL_CALL isPlaybackLoop() override
Definition: gtkplayer.cxx:253
unsigned long m_nNotifySignalId
Definition: gtkplayer.hxx:81
comphelper::OMultiTypeInterfaceContainerHelper2 m_lListener
Definition: gtkplayer.hxx:75
GtkMediaStream * m_pStream
Definition: gtkplayer.hxx:79
sal_Int16 m_nUnmutedVolume
Definition: gtkplayer.hxx:84
unsigned long m_nTimeoutId
Definition: gtkplayer.hxx:83
virtual void SAL_CALL disposing() final override
Definition: gtkplayer.cxx:81
virtual double SAL_CALL getDuration() override
Definition: gtkplayer.cxx:205
virtual void SAL_CALL start() override
Definition: gtkplayer.cxx:177
virtual sal_Bool SAL_CALL isPlaying() override
Definition: gtkplayer.cxx:193
virtual void SAL_CALL removePlayerListener(const css::uno::Reference< css::media::XPlayerListener > &rListener) override
Definition: gtkplayer.cxx:385
virtual ~GtkPlayer() override
Definition: gtkplayer.cxx:50
virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override
Definition: gtkplayer.cxx:462
virtual void SAL_CALL setMute(sal_Bool bSet) override
Definition: gtkplayer.cxx:263
virtual css::uno::Reference< css::media::XPlayerWindow > SAL_CALL createPlayerWindow(const css::uno::Sequence< css::uno::Any > &rArgs) override
Definition: gtkplayer.cxx:320
virtual void SAL_CALL setPlaybackLoop(sal_Bool bSet) override
Definition: gtkplayer.cxx:247
bool create(const OUString &rURL)
Definition: gtkplayer.cxx:135
css::uno::XInterface * next()
OInterfaceContainerHelper2 * getContainer(const css::uno::Type &rKey) const
sal_Int32 removeInterface(const css::uno::Type &rKey, const css::uno::Reference< css::uno::XInterface > &rxIFace)
sal_Int32 addInterface(const css::uno::Type &rKey, const css::uno::Reference< css::uno::XInterface > &r)
mutable::osl::Mutex m_aMutex
float u
awt::Size m_aSize
Definition: gtkplayer.cxx:395
constexpr OUStringLiteral AVMEDIA_GTK_PLAYER_IMPLEMENTATIONNAME
Definition: gtkplayer.cxx:31
constexpr OUStringLiteral AVMEDIA_GTK_PLAYER_SERVICENAME
Definition: gtkplayer.cxx:32
GtkMediaStream * m_pStream
Definition: gtkplayer.cxx:396
struct _GtkWidget GtkWidget
Definition: gtkplayer.hxx:24
struct _GtkMediaStream GtkMediaStream
Definition: gtkplayer.hxx:23
std::mutex m_aMutex
#define FALSE
def position(n=-1)
static void invalidate_size_cb(GdkPaintable *, GtkPlayer *pThis)
Definition: gtkplayer.cxx:97
static void do_notify(GtkPlayer *pThis)
Definition: gtkplayer.cxx:90
static bool timeout_cb(GtkPlayer *pThis)
Definition: gtkplayer.cxx:105
static void notify_cb(GtkMediaStream *, GParamSpec *pspec, GtkPlayer *pThis)
Definition: gtkplayer.cxx:99
static gboolean gtk_media_stream_unref(gpointer user_data)
Definition: gtkplayer.cxx:52
cppu::WeakComponentImplHelper< css::media::XPlayer, css::media::XPlayerNotifier, css::lang::XServiceInfo > GtkPlayer_BASE
Definition: gtkplayer.hxx:30
bool CPPUHELPER_DLLPUBLIC supportsService(css::lang::XServiceInfo *implementation, rtl::OUString const &name)
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
#define SAL_WNODEPRECATED_DECLARATIONS_POP
unsigned char sal_Bool
#define SAL_WNODEPRECATED_DECLARATIONS_PUSH