tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / avmedia / source / gstreamer / gstplayer.cxx
blob1a91a689299e299d39b28f3cfd372185c55382da
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <algorithm>
23 #include <cassert>
24 #include <chrono>
25 #include <cstddef>
26 #include <cstring>
27 #include <map>
28 #include <mutex>
29 #include <set>
30 #include <vector>
31 #include <math.h>
33 #include <com/sun/star/text/GraphicCrop.hpp>
35 #include <cppuhelper/supportsservice.hxx>
36 #include <sal/log.hxx>
37 #include <rtl/string.hxx>
38 #include <salhelper/thread.hxx>
39 #include <vcl/svapp.hxx>
40 #include <vcl/syschild.hxx>
41 #include <vcl/sysdata.hxx>
42 #include <vcl/graph.hxx>
43 #include <avmedia/mediaitem.hxx>
45 #include "gstplayer.hxx"
46 #include "gstframegrabber.hxx"
47 #include "gstwindow.hxx"
49 #include <gst/video/videooverlay.h>
50 #include <gst/pbutils/missing-plugins.h>
51 #include <gst/pbutils/pbutils.h>
53 #define AVVERSION "gst 1.0: "
55 using namespace ::com::sun::star;
57 namespace avmedia::gstreamer {
59 namespace {
61 class FlagGuard {
62 public:
63 explicit FlagGuard(bool & flag): flag_(flag) { flag_ = true; }
65 ~FlagGuard() { flag_ = false; }
67 private:
68 bool & flag_;
71 class MissingPluginInstallerThread: public salhelper::Thread {
72 public:
73 MissingPluginInstallerThread(): Thread("MissingPluginInstaller") {}
75 private:
76 void execute() override;
80 class MissingPluginInstaller {
81 friend class MissingPluginInstallerThread;
83 public:
84 MissingPluginInstaller(): launchNewThread_(true), inCleanUp_(false) {}
86 ~MissingPluginInstaller();
88 void report(rtl::Reference<Player> const & source, GstMessage * message);
90 // Player::~Player calls Player::disposing calls
91 // MissingPluginInstaller::detach, so do not take Player by rtl::Reference
92 // here (which would bump its refcount back from 0 to 1):
93 void detach(Player const * source);
95 private:
96 void processQueue();
98 DECL_STATIC_LINK(MissingPluginInstaller, launchUi, void*, void);
100 std::recursive_mutex mutex_;
101 std::set<OString> reported_;
102 std::map<OString, std::set<rtl::Reference<Player>>> queued_;
103 rtl::Reference<MissingPluginInstallerThread> currentThread_;
104 std::vector<OString> currentDetails_;
105 std::set<rtl::Reference<Player>> currentSources_;
106 bool launchNewThread_;
107 bool inCleanUp_;
111 MissingPluginInstaller::~MissingPluginInstaller() {
112 std::unique_lock g(mutex_);
113 SAL_WARN_IF(currentThread_.is(), "avmedia.gstreamer", "unjoined thread");
114 inCleanUp_ = true;
118 void MissingPluginInstaller::report(
119 rtl::Reference<Player> const & source, GstMessage * message)
121 // assert(gst_is_missing_plugin_message(message));
122 gchar * det = gst_missing_plugin_message_get_installer_detail(message);
123 if (det == nullptr) {
124 SAL_WARN(
125 "avmedia.gstreamer",
126 "gst_missing_plugin_message_get_installer_detail failed");
127 return;
129 std::size_t len = std::strlen(det);
130 if (len > SAL_MAX_INT32) {
131 SAL_WARN("avmedia.gstreamer", "detail string too long");
132 g_free(det);
133 return;
135 OString detStr(det, len);
136 g_free(det);
137 rtl::Reference<MissingPluginInstallerThread> join;
138 rtl::Reference<MissingPluginInstallerThread> launch;
140 std::unique_lock g(mutex_);
141 if (reported_.find(detStr) != reported_.end()) {
142 return;
144 auto & i = queued_[detStr];
145 bool fresh = i.empty();
146 i.insert(source);
147 if (!(fresh && launchNewThread_)) {
148 return;
150 join = currentThread_;
151 currentThread_ = new MissingPluginInstallerThread;
153 FlagGuard f(inCleanUp_);
154 currentSources_.clear();
156 processQueue();
157 launchNewThread_ = false;
158 launch = currentThread_;
160 if (join.is()) {
161 join->join();
163 launch->acquire();
164 Application::PostUserEvent(
165 LINK(this, MissingPluginInstaller, launchUi), launch.get());
169 void eraseSource(std::set<rtl::Reference<Player>> & set, Player const * source)
171 auto i = std::find_if(
172 set.begin(), set.end(),
173 [source](rtl::Reference<Player> const & el) {
174 return el.get() == source;
176 if (i != set.end()) {
177 set.erase(i);
182 void MissingPluginInstaller::detach(Player const * source) {
183 rtl::Reference<MissingPluginInstallerThread> join;
185 std::unique_lock g(mutex_);
186 if (inCleanUp_) {
187 // Guard against ~MissingPluginInstaller with erroneously un-joined
188 // currentThread_ (thus non-empty currentSources_) calling
189 // destructor of currentSources_, calling ~Player, calling here,
190 // which would use currentSources_ while it is already being
191 // destroyed:
192 return;
194 for (auto i = queued_.begin(); i != queued_.end();) {
195 eraseSource(i->second, source);
196 if (i->second.empty()) {
197 i = queued_.erase(i);
198 } else {
199 ++i;
202 if (currentThread_.is()) {
203 assert(!currentSources_.empty());
204 eraseSource(currentSources_, source);
205 if (currentSources_.empty()) {
206 join = currentThread_;
207 currentThread_.clear();
208 launchNewThread_ = true;
212 if (join.is()) {
213 // missing cancellability of gst_install_plugins_sync
214 join->join();
219 void MissingPluginInstaller::processQueue() {
220 assert(!queued_.empty());
221 assert(currentDetails_.empty());
222 for (const auto& rEntry : queued_) {
223 reported_.insert(rEntry.first);
224 currentDetails_.push_back(rEntry.first);
225 currentSources_.insert(rEntry.second.begin(), rEntry.second.end());
227 queued_.clear();
231 IMPL_STATIC_LINK(MissingPluginInstaller, launchUi, void *, p, void)
233 MissingPluginInstallerThread* thread = static_cast<MissingPluginInstallerThread*>(p);
234 rtl::Reference<MissingPluginInstallerThread> ref(thread, SAL_NO_ACQUIRE);
235 gst_pb_utils_init();
236 // not thread safe; hopefully fine to consistently call from our event
237 // loop (which is the only reason to have this
238 // Application::PostUserEvent diversion, in case
239 // MissingPluginInstaller::report might be called from outside our event
240 // loop), and hopefully fine to call gst_is_missing_plugin_message and
241 // gst_missing_plugin_message_get_installer_detail before calling
242 // gst_pb_utils_init
243 ref->launch();
247 MissingPluginInstaller& TheMissingPluginInstaller()
249 static MissingPluginInstaller theInstaller;
250 return theInstaller;
254 void MissingPluginInstallerThread::execute() {
255 MissingPluginInstaller & inst = TheMissingPluginInstaller();
256 for (;;) {
257 std::vector<OString> details;
259 std::unique_lock g(inst.mutex_);
260 assert(!inst.currentDetails_.empty());
261 details.swap(inst.currentDetails_);
263 std::vector<char *> args;
264 args.reserve(details.size());
265 for (auto const& i : details)
267 args.push_back(const_cast<char *>(i.getStr()));
269 args.push_back(nullptr);
270 gst_install_plugins_sync(args.data(), nullptr);
272 std::unique_lock g(inst.mutex_);
273 if (inst.queued_.empty() || inst.launchNewThread_) {
274 inst.launchNewThread_ = true;
275 break;
277 inst.processQueue();
282 } // end anonymous namespace
285 Player::Player() :
286 GstPlayer_BASE( m_aMutex ),
287 mpPlaybin( nullptr ),
288 mpVolumeControl( nullptr ),
289 mbUseGtkSink( false ),
290 mbFakeVideo (false ),
291 mnUnmutedVolume( 0 ),
292 mbMuted( false ),
293 mbLooping( false ),
294 mbInitialized( false ),
295 mpDisplay( nullptr ),
296 mnWindowID( 0 ),
297 mpXOverlay( nullptr ),
298 mnDuration( 0 ),
299 mnWidth( 0 ),
300 mnHeight( 0 ),
301 mnWatchID( 0 ),
302 mbWatchID( false )
304 // Initialize GStreamer library
305 int argc = 1;
306 char name[] = "libreoffice";
307 char *arguments[] = { name };
308 char** argv = arguments;
309 GError* pError = nullptr;
311 mbInitialized = gst_init_check( &argc, &argv, &pError );
313 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::Player" );
315 if (pError != nullptr)
317 // TODO: throw an exception?
318 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::Player error '" << pError->message << "'" );
319 g_error_free (pError);
324 Player::~Player()
326 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::~Player" );
327 if( mbInitialized )
328 disposing();
332 void SAL_CALL Player::disposing()
334 TheMissingPluginInstaller().detach(this);
336 ::osl::MutexGuard aGuard(m_aMutex);
338 stop();
340 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::disposing" );
342 // Release the elements and pipeline
343 if( mbInitialized )
345 if( mpPlaybin )
347 gst_element_set_state( mpPlaybin, GST_STATE_NULL );
348 g_object_unref( G_OBJECT( mpPlaybin ) );
350 mpPlaybin = nullptr;
351 mpVolumeControl = nullptr;
354 if( mpXOverlay ) {
355 g_object_unref( G_OBJECT ( mpXOverlay ) );
356 mpXOverlay = nullptr;
360 if (mbWatchID)
362 g_source_remove(mnWatchID);
363 mbWatchID = false;
368 static gboolean pipeline_bus_callback( GstBus *, GstMessage *message, gpointer data )
370 Player* pPlayer = static_cast<Player*>(data);
372 pPlayer->processMessage( message );
374 return true;
378 static GstBusSyncReply pipeline_bus_sync_handler( GstBus *, GstMessage * message, gpointer data )
380 Player* pPlayer = static_cast<Player*>(data);
382 return pPlayer->processSyncMessage( message );
386 void Player::processMessage( GstMessage *message )
388 switch( GST_MESSAGE_TYPE( message ) ) {
389 case GST_MESSAGE_EOS:
390 gst_element_set_state( mpPlaybin, GST_STATE_READY );
391 if (mbLooping)
392 start();
393 break;
394 case GST_MESSAGE_STATE_CHANGED:
395 if (message->src == GST_OBJECT(mpPlaybin))
397 GstState newstate, pendingstate;
399 gst_message_parse_state_changed (message, nullptr, &newstate, &pendingstate);
401 if (!mbUseGtkSink && newstate == GST_STATE_PAUSED &&
402 pendingstate == GST_STATE_VOID_PENDING && mpXOverlay)
404 gst_video_overlay_expose(mpXOverlay);
407 break;
408 default:
409 break;
413 #define LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE "GstWaylandDisplayHandleContextType"
415 static bool lcl_is_wayland_display_handle_need_context_message(GstMessage* msg)
417 g_return_val_if_fail(GST_IS_MESSAGE(msg), false);
419 if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_NEED_CONTEXT)
420 return false;
421 const gchar *type = nullptr;
422 if (!gst_message_parse_context_type(msg, &type))
423 return false;
424 return !g_strcmp0(type, LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE);
427 static GstContext* lcl_wayland_display_handle_context_new(void* display)
429 GstContext *context = gst_context_new(LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE, true);
430 gst_structure_set (gst_context_writable_structure (context),
431 "handle", G_TYPE_POINTER, display, nullptr);
432 return context;
435 GstBusSyncReply Player::processSyncMessage( GstMessage *message )
437 #if OSL_DEBUG_LEVEL > 0
438 if ( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ERROR )
440 GError* error;
441 gchar* error_debug;
443 gst_message_parse_error( message, &error, &error_debug );
444 SAL_WARN(
445 "avmedia.gstreamer",
446 "error: '" << error->message << "' debug: '"
447 << error_debug << "'");
449 else if ( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_WARNING )
451 GError* error;
452 gchar* error_debug;
454 gst_message_parse_warning( message, &error, &error_debug );
455 SAL_WARN(
456 "avmedia.gstreamer",
457 "warning: '" << error->message << "' debug: '"
458 << error_debug << "'");
460 else if ( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_INFO )
462 GError* error;
463 gchar* error_debug;
465 gst_message_parse_info( message, &error, &error_debug );
466 SAL_WARN(
467 "avmedia.gstreamer",
468 "info: '" << error->message << "' debug: '"
469 << error_debug << "'");
471 #endif
473 if (!mbUseGtkSink)
475 if (gst_is_video_overlay_prepare_window_handle_message (message) )
477 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " processSyncMessage prepare window id: " <<
478 GST_MESSAGE_TYPE_NAME( message ) << " " << static_cast<int>(mnWindowID) );
479 if( mpXOverlay )
480 g_object_unref( G_OBJECT ( mpXOverlay ) );
481 g_object_set( GST_MESSAGE_SRC( message ), "force-aspect-ratio", FALSE, nullptr );
482 mpXOverlay = GST_VIDEO_OVERLAY( GST_MESSAGE_SRC( message ) );
483 g_object_ref( G_OBJECT ( mpXOverlay ) );
484 if ( mnWindowID != 0 )
486 gst_video_overlay_set_window_handle( mpXOverlay, mnWindowID );
487 gst_video_overlay_handle_events(mpXOverlay, 0); // Let the parent window handle events.
488 if (maArea.Width > 0 && maArea.Height > 0)
489 gst_video_overlay_set_render_rectangle(mpXOverlay, maArea.X, maArea.Y, maArea.Width, maArea.Height);
492 return GST_BUS_DROP;
494 else if (lcl_is_wayland_display_handle_need_context_message(message))
496 GstContext *context = lcl_wayland_display_handle_context_new(mpDisplay);
497 gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context);
499 return GST_BUS_DROP;
503 if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ASYNC_DONE ) {
504 if( mnDuration == 0) {
505 gint64 gst_duration = 0;
506 if( gst_element_query_duration( mpPlaybin, GST_FORMAT_TIME, &gst_duration) )
507 mnDuration = gst_duration;
509 if( mnWidth == 0 ) {
510 GstPad *pad = nullptr;
512 g_signal_emit_by_name( mpPlaybin, "get-video-pad", 0, &pad );
514 if( pad ) {
515 int w = 0, h = 0;
517 GstCaps *caps = gst_pad_get_current_caps( pad );
519 if( gst_structure_get( gst_caps_get_structure( caps, 0 ),
520 "width", G_TYPE_INT, &w,
521 "height", G_TYPE_INT, &h,
522 nullptr ) ) {
523 mnWidth = w;
524 mnHeight = h;
526 SAL_INFO( "avmedia.gstreamer", AVVERSION "queried size: " << mnWidth << "x" << mnHeight );
529 gst_caps_unref( caps );
530 g_object_unref( pad );
533 maSizeCondition.set();
535 } else if (gst_is_missing_plugin_message(message)) {
536 TheMissingPluginInstaller().report(this, message);
537 if( mnWidth == 0 ) {
538 // an error occurred, set condition so that OOo thread doesn't wait for us
539 maSizeCondition.set();
541 } else if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ERROR ) {
542 if( mnWidth == 0 ) {
543 // an error occurred, set condition so that OOo thread doesn't wait for us
544 maSizeCondition.set();
548 return GST_BUS_PASS;
551 void Player::preparePlaybin( std::u16string_view rURL, GstElement *pSink )
553 if (mpPlaybin != nullptr)
555 gst_element_set_state( mpPlaybin, GST_STATE_NULL );
556 g_object_unref( mpPlaybin );
559 mpPlaybin = gst_element_factory_make( "playbin", nullptr );
561 //tdf#96989 on systems with flat-volumes setting the volume directly on the
562 //playbin to 100% results in setting the global volume to 100% of the
563 //maximum. We expect to set as % of the current volume.
564 mpVolumeControl = gst_element_factory_make( "volume", nullptr );
565 GstElement *pAudioSink = gst_element_factory_make( "autoaudiosink", nullptr );
566 GstElement* pAudioOutput = gst_bin_new("audio-output-bin");
567 assert(pAudioOutput);
568 if (pAudioSink)
569 gst_bin_add(GST_BIN(pAudioOutput), pAudioSink);
570 if (mpVolumeControl)
572 gst_bin_add(GST_BIN(pAudioOutput), mpVolumeControl);
573 if (pAudioSink)
574 gst_element_link(mpVolumeControl, pAudioSink);
575 GstPad *pPad = gst_element_get_static_pad(mpVolumeControl, "sink");
576 gst_element_add_pad(GST_ELEMENT(pAudioOutput), gst_ghost_pad_new("sink", pPad));
577 gst_object_unref(GST_OBJECT(pPad));
579 g_object_set(G_OBJECT(mpPlaybin), "audio-sink", pAudioOutput, nullptr);
581 if( pSink != nullptr ) // used for getting preferred size etc.
583 g_object_set( G_OBJECT( mpPlaybin ), "video-sink", pSink, nullptr );
584 mbFakeVideo = true;
586 else
587 mbFakeVideo = false;
589 OString ascURL = OUStringToOString( rURL, RTL_TEXTENCODING_UTF8 );
590 g_object_set( G_OBJECT( mpPlaybin ), "uri", ascURL.getStr() , nullptr );
592 GstBus *pBus = gst_element_get_bus( mpPlaybin );
593 if (mbWatchID)
595 g_source_remove(mnWatchID);
596 mbWatchID = false;
598 mnWatchID = gst_bus_add_watch( pBus, pipeline_bus_callback, this );
599 mbWatchID = true;
600 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " set sync handler" );
601 gst_bus_set_sync_handler( pBus, pipeline_bus_sync_handler, this, nullptr );
602 g_object_unref( pBus );
606 bool Player::create( const OUString& rURL )
608 bool bRet = false;
610 // create all the elements and link them
612 SAL_INFO( "avmedia.gstreamer", "create player, URL: '" << rURL << "'" );
614 if( mbInitialized && !rURL.isEmpty() )
616 // fakesink for pre-roll & sizing ...
617 preparePlaybin( rURL, gst_element_factory_make( "fakesink", nullptr ) );
619 gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
621 bRet = true;
624 if( bRet )
625 maURL = rURL;
626 else
627 maURL.clear();
629 return bRet;
632 void SAL_CALL Player::start()
634 ::osl::MutexGuard aGuard(m_aMutex);
636 // set the pipeline state to READY and run the loop
637 if( mbInitialized && mpPlaybin != nullptr )
639 gst_element_set_state( mpPlaybin, GST_STATE_PLAYING );
642 SAL_INFO( "avmedia.gstreamer", AVVERSION "start " << mpPlaybin );
645 void SAL_CALL Player::stop()
647 ::osl::MutexGuard aGuard(m_aMutex);
649 // set the pipeline in PAUSED STATE
650 if( mpPlaybin )
651 gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
653 SAL_INFO( "avmedia.gstreamer", AVVERSION "stop " << mpPlaybin );
656 sal_Bool SAL_CALL Player::isPlaying()
658 ::osl::MutexGuard aGuard(m_aMutex);
660 bool bRet = false;
662 // return whether the pipeline target is PLAYING STATE or not
663 if (mbInitialized && mpPlaybin)
665 bRet = GST_STATE_TARGET(mpPlaybin) == GST_STATE_PLAYING;
668 return bRet;
671 double SAL_CALL Player::getDuration()
673 ::osl::MutexGuard aGuard(m_aMutex);
675 // slideshow checks for non-zero duration, so cheat here
676 double duration = 0.3;
678 if( mpPlaybin && mnDuration > 0 ) {
679 duration = mnDuration / GST_SECOND;
682 return duration;
686 void SAL_CALL Player::setMediaTime( double fTime )
688 ::osl::MutexGuard aGuard(m_aMutex);
690 if( !mpPlaybin )
691 return;
693 gint64 gst_position = llround (fTime * GST_SECOND);
695 gst_element_seek( mpPlaybin, 1.0,
696 GST_FORMAT_TIME,
697 GST_SEEK_FLAG_FLUSH,
698 GST_SEEK_TYPE_SET, gst_position,
699 GST_SEEK_TYPE_NONE, 0 );
701 SAL_INFO( "avmedia.gstreamer", AVVERSION "seek to: " << gst_position << " ns original: " << fTime << " s" );
705 double SAL_CALL Player::getMediaTime()
707 ::osl::MutexGuard aGuard(m_aMutex);
709 double position = 0.0;
711 if( mpPlaybin ) {
712 // get current position in the stream
713 gint64 gst_position;
714 if( gst_element_query_position( mpPlaybin, GST_FORMAT_TIME, &gst_position ) )
715 position = gst_position / GST_SECOND;
718 return position;
722 void SAL_CALL Player::setPlaybackLoop( sal_Bool bSet )
724 ::osl::MutexGuard aGuard(m_aMutex);
725 // TODO check how to do with GST
726 mbLooping = bSet;
730 sal_Bool SAL_CALL Player::isPlaybackLoop()
732 ::osl::MutexGuard aGuard(m_aMutex);
733 // TODO check how to do with GST
734 return mbLooping;
738 void SAL_CALL Player::setMute( sal_Bool bSet )
740 ::osl::MutexGuard aGuard(m_aMutex);
742 SAL_INFO( "avmedia.gstreamer", AVVERSION "set mute: " << bSet << " muted: " << mbMuted << " unmuted volume: " << mnUnmutedVolume );
744 // change the volume to 0 or the unmuted volume
745 if (mpVolumeControl && mbMuted != bool(bSet))
747 double nVolume = mnUnmutedVolume;
748 if( bSet )
750 nVolume = 0.0;
753 g_object_set( G_OBJECT( mpVolumeControl ), "volume", nVolume, nullptr );
755 mbMuted = bSet;
760 sal_Bool SAL_CALL Player::isMute()
762 ::osl::MutexGuard aGuard(m_aMutex);
764 return mbMuted;
768 void SAL_CALL Player::setVolumeDB( sal_Int16 nVolumeDB )
770 ::osl::MutexGuard aGuard(m_aMutex);
772 mnUnmutedVolume = pow( 10.0, nVolumeDB / 20.0 );
774 SAL_INFO( "avmedia.gstreamer", AVVERSION "set volume: " << nVolumeDB << " gst volume: " << mnUnmutedVolume );
776 // change volume
777 if (mpVolumeControl && !mbMuted)
779 g_object_set( G_OBJECT( mpVolumeControl ), "volume", mnUnmutedVolume, nullptr );
784 sal_Int16 SAL_CALL Player::getVolumeDB()
786 ::osl::MutexGuard aGuard(m_aMutex);
788 sal_Int16 nVolumeDB(0);
790 if (mpVolumeControl)
792 double nGstVolume = 0.0;
794 g_object_get( G_OBJECT( mpVolumeControl ), "volume", &nGstVolume, nullptr );
796 nVolumeDB = static_cast<sal_Int16>( 20.0*log10 ( nGstVolume ) );
799 return nVolumeDB;
803 awt::Size SAL_CALL Player::getPreferredPlayerWindowSize()
805 ::osl::MutexGuard aGuard(m_aMutex);
807 awt::Size aSize( 0, 0 );
809 if( maURL.isEmpty() )
811 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::getPreferredPlayerWindowSize - empty URL => 0x0" );
812 return aSize;
815 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " pre-Player::getPreferredPlayerWindowSize, member " << mnWidth << "x" << mnHeight );
817 osl::Condition::Result aResult = maSizeCondition.wait( std::chrono::seconds(10) );
819 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::getPreferredPlayerWindowSize after waitCondition " << aResult << ", member " << mnWidth << "x" << mnHeight );
821 if( mnWidth != 0 && mnHeight != 0 ) {
822 aSize.Width = mnWidth;
823 aSize.Height = mnHeight;
826 return aSize;
829 uno::Reference< ::media::XPlayerWindow > SAL_CALL Player::createPlayerWindow( const uno::Sequence< uno::Any >& rArguments )
831 ::osl::MutexGuard aGuard(m_aMutex);
833 uno::Reference< ::media::XPlayerWindow > xRet;
835 if (rArguments.getLength() > 1)
836 rArguments[1] >>= maArea;
838 awt::Size aSize = getPreferredPlayerWindowSize();
840 if( mbFakeVideo )
841 preparePlaybin( maURL, nullptr );
843 SAL_INFO( "avmedia.gstreamer", AVVERSION "Player::createPlayerWindow " << aSize.Width << "x" << aSize.Height << " length: " << rArguments.getLength() );
845 if( aSize.Width > 0 && aSize.Height > 0 )
847 if (rArguments.getLength() <= 2)
849 xRet = new ::avmedia::gstreamer::Window;
850 return xRet;
853 sal_IntPtr pIntPtr = 0;
854 rArguments[ 2 ] >>= pIntPtr;
855 SystemChildWindow *pParentWindow = reinterpret_cast< SystemChildWindow* >( pIntPtr );
856 if (!pParentWindow)
857 return nullptr;
859 const SystemEnvData* pEnvData = pParentWindow->GetSystemData();
860 if (!pEnvData)
861 return nullptr;
863 // tdf#124027: the position of embedded window is identical w/ the position
864 // of media object in all other vclplugs (kf5, gen), in gtk3 w/o gtksink it
865 // needs to be translated
866 if (pEnvData->toolkit == SystemEnvData::Toolkit::Gtk)
868 Point aPoint = pParentWindow->GetPosPixel();
869 maArea.X = aPoint.getX();
870 maArea.Y = aPoint.getY();
873 mbUseGtkSink = false;
875 GstElement *pVideosink = static_cast<GstElement*>(pParentWindow->CreateGStreamerSink());
876 if (pVideosink)
878 if (pEnvData->toolkit == SystemEnvData::Toolkit::Gtk)
879 mbUseGtkSink = true;
881 else
883 if (pEnvData->platform == SystemEnvData::Platform::Wayland)
884 pVideosink = gst_element_factory_make("waylandsink", "video-output");
885 else
886 pVideosink = gst_element_factory_make("autovideosink", "video-output");
887 if (!pVideosink)
888 return nullptr;
891 xRet = new ::avmedia::gstreamer::Window;
893 g_object_set(G_OBJECT(mpPlaybin), "video-sink", pVideosink, nullptr);
894 g_object_set(G_OBJECT(mpPlaybin), "force-aspect-ratio", FALSE, nullptr);
896 if ((rArguments.getLength() >= 4) && (rArguments[3] >>= pIntPtr) && pIntPtr)
898 auto pItem = reinterpret_cast<const avmedia::MediaItem*>(pIntPtr);
899 Graphic aGraphic = pItem->getGraphic();
900 const text::GraphicCrop& rCrop = pItem->getCrop();
901 if (!aGraphic.IsNone() && (rCrop.Bottom > 0 || rCrop.Left > 0 || rCrop.Right > 0 || rCrop.Top > 0))
903 // The media item has a non-empty cropping set. Try to crop the video accordingly.
904 Size aPref = aGraphic.GetPrefSize();
905 Size aPixel = aGraphic.GetSizePixel();
906 tools::Long nLeft = aPixel.getWidth() * rCrop.Left / aPref.getWidth();
907 tools::Long nTop = aPixel.getHeight() * rCrop.Top / aPref.getHeight();
908 tools::Long nRight = aPixel.getWidth() * rCrop.Right / aPref.getWidth();
909 tools::Long nBottom = aPixel.getHeight() * rCrop.Bottom / aPref.getHeight();
910 GstElement* pVideoFilter = gst_element_factory_make("videocrop", nullptr);
911 if (pVideoFilter)
913 g_object_set(G_OBJECT(pVideoFilter), "left", nLeft, nullptr);
914 g_object_set(G_OBJECT(pVideoFilter), "top", nTop, nullptr);
915 g_object_set(G_OBJECT(pVideoFilter), "right", nRight, nullptr);
916 g_object_set(G_OBJECT(pVideoFilter), "bottom", nBottom, nullptr);
917 g_object_set(G_OBJECT(mpPlaybin), "video-filter", pVideoFilter, nullptr);
922 if (!mbUseGtkSink)
924 mnWindowID = pEnvData->GetWindowHandle(pParentWindow->ImplGetFrame());
925 mpDisplay = pEnvData->pDisplay;
926 SAL_INFO( "avmedia.gstreamer", AVVERSION "set window id to " << static_cast<int>(mnWindowID) << " XOverlay " << mpXOverlay);
928 gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
929 if (!mbUseGtkSink && mpXOverlay)
930 gst_video_overlay_set_window_handle( mpXOverlay, mnWindowID );
933 return xRet;
936 uno::Reference< media::XFrameGrabber > SAL_CALL Player::createFrameGrabber()
938 ::osl::MutexGuard aGuard(m_aMutex);
939 rtl::Reference<FrameGrabber> pFrameGrabber;
940 const awt::Size aPrefSize( getPreferredPlayerWindowSize() );
942 if( ( aPrefSize.Width > 0 ) && ( aPrefSize.Height > 0 ) )
943 pFrameGrabber = FrameGrabber::create( maURL );
944 SAL_INFO( "avmedia.gstreamer", AVVERSION "created FrameGrabber " << pFrameGrabber.get() );
946 return pFrameGrabber;
950 OUString SAL_CALL Player::getImplementationName()
952 return u"com.sun.star.comp.avmedia.Player_GStreamer"_ustr;
956 sal_Bool SAL_CALL Player::supportsService( const OUString& ServiceName )
958 return cppu::supportsService(this, ServiceName);
962 uno::Sequence< OUString > SAL_CALL Player::getSupportedServiceNames()
964 return { u"com.sun.star.media.Player_GStreamer"_ustr };
967 } // namespace
969 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */