bump product version to 6.3.0.0.beta1
[LibreOffice.git] / avmedia / source / gstreamer / gstplayer.cxx
blobb2e5382353f9414a3a0421a9aef55c6b8f684102
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 <set>
29 #include <vector>
30 #include <math.h>
32 #include <cppuhelper/supportsservice.hxx>
34 #include <sal/log.hxx>
35 #include <rtl/string.hxx>
36 #include <salhelper/thread.hxx>
37 #include <vcl/svapp.hxx>
38 #include <vcl/syschild.hxx>
39 #include <vcl/sysdata.hxx>
41 #include "gstplayer.hxx"
42 #include "gstframegrabber.hxx"
43 #include "gstwindow.hxx"
45 #ifdef AVMEDIA_GST_0_10
46 # define AVMEDIA_GST_PLAYER_IMPLEMENTATIONNAME "com.sun.star.comp.avmedia.Player_GStreamer_0_10"
47 # define AVMEDIA_GST_PLAYER_SERVICENAME "com.sun.star.media.Player_GStreamer_0_10"
48 #else
49 # include <gst/video/videooverlay.h>
50 # define AVMEDIA_GST_PLAYER_IMPLEMENTATIONNAME "com.sun.star.comp.avmedia.Player_GStreamer"
51 # define AVMEDIA_GST_PLAYER_SERVICENAME "com.sun.star.media.Player_GStreamer"
52 #endif
54 #include <gst/pbutils/missing-plugins.h>
55 #include <gst/pbutils/pbutils.h>
57 #ifdef AVMEDIA_GST_0_10
58 # define AVVERSION "gst 0.10: "
59 #else
60 # define AVVERSION "gst 1.0: "
61 #endif
63 using namespace ::com::sun::star;
65 namespace avmedia { namespace gstreamer {
67 namespace {
69 class FlagGuard {
70 public:
71 explicit FlagGuard(bool & flag): flag_(flag) { flag_ = true; }
73 ~FlagGuard() { flag_ = false; }
75 private:
76 bool & flag_;
79 class MissingPluginInstallerThread: public salhelper::Thread {
80 public:
81 MissingPluginInstallerThread(): Thread("MissingPluginInstaller") {}
83 private:
84 void execute() override;
88 class MissingPluginInstaller {
89 friend class MissingPluginInstallerThread;
91 public:
92 MissingPluginInstaller(): launchNewThread_(true), inCleanUp_(false) {}
94 ~MissingPluginInstaller();
96 void report(rtl::Reference<Player> const & source, GstMessage * message);
98 // Player::~Player calls Player::disposing calls
99 // MissingPluginInstaller::detach, so do not take Player by rtl::Reference
100 // here (which would bump its refcount back from 0 to 1):
101 void detach(Player const * source);
103 private:
104 void processQueue();
106 DECL_STATIC_LINK(MissingPluginInstaller, launchUi, void*, void);
108 osl::Mutex mutex_;
109 std::set<OString> reported_;
110 std::map<OString, std::set<rtl::Reference<Player>>> queued_;
111 rtl::Reference<MissingPluginInstallerThread> currentThread_;
112 std::vector<OString> currentDetails_;
113 std::set<rtl::Reference<Player>> currentSources_;
114 bool launchNewThread_;
115 bool inCleanUp_;
119 MissingPluginInstaller::~MissingPluginInstaller() {
120 osl::MutexGuard g(mutex_);
121 SAL_WARN_IF(currentThread_.is(), "avmedia.gstreamer", "unjoined thread");
122 inCleanUp_ = true;
126 void MissingPluginInstaller::report(
127 rtl::Reference<Player> const & source, GstMessage * message)
129 // assert(gst_is_missing_plugin_message(message));
130 gchar * det = gst_missing_plugin_message_get_installer_detail(message);
131 if (det == nullptr) {
132 SAL_WARN(
133 "avmedia.gstreamer",
134 "gst_missing_plugin_message_get_installer_detail failed");
135 return;
137 std::size_t len = std::strlen(det);
138 if (len > sal_uInt32(SAL_MAX_INT32)) {
139 SAL_WARN("avmedia.gstreamer", "detail string too long");
140 g_free(det);
141 return;
143 OString detStr(det, len);
144 g_free(det);
145 rtl::Reference<MissingPluginInstallerThread> join;
146 rtl::Reference<MissingPluginInstallerThread> launch;
148 osl::MutexGuard g(mutex_);
149 if (reported_.find(detStr) != reported_.end()) {
150 return;
152 auto & i = queued_[detStr];
153 bool fresh = i.empty();
154 i.insert(source);
155 if (!(fresh && launchNewThread_)) {
156 return;
158 join = currentThread_;
159 currentThread_ = new MissingPluginInstallerThread;
161 FlagGuard f(inCleanUp_);
162 currentSources_.clear();
164 processQueue();
165 launchNewThread_ = false;
166 launch = currentThread_;
168 if (join.is()) {
169 join->join();
171 launch->acquire();
172 Application::PostUserEvent(
173 LINK(this, MissingPluginInstaller, launchUi), launch.get());
177 void eraseSource(std::set<rtl::Reference<Player>> & set, Player const * source)
179 auto i = std::find_if(
180 set.begin(), set.end(),
181 [source](rtl::Reference<Player> const & el) {
182 return el.get() == source;
184 if (i != set.end()) {
185 set.erase(i);
190 void MissingPluginInstaller::detach(Player const * source) {
191 rtl::Reference<MissingPluginInstallerThread> join;
193 osl::MutexGuard g(mutex_);
194 if (inCleanUp_) {
195 // Guard against ~MissingPluginInstaller with erroneously un-joined
196 // currentThread_ (thus non-empty currentSources_) calling
197 // destructor of currentSources_, calling ~Player, calling here,
198 // which would use currentSources_ while it is already being
199 // destroyed:
200 return;
202 for (auto i = queued_.begin(); i != queued_.end();) {
203 eraseSource(i->second, source);
204 if (i->second.empty()) {
205 i = queued_.erase(i);
206 } else {
207 ++i;
210 if (currentThread_.is()) {
211 assert(!currentSources_.empty());
212 eraseSource(currentSources_, source);
213 if (currentSources_.empty()) {
214 join = currentThread_;
215 currentThread_.clear();
216 launchNewThread_ = true;
220 if (join.is()) {
221 // missing cancellability of gst_install_plugins_sync
222 join->join();
227 void MissingPluginInstaller::processQueue() {
228 assert(!queued_.empty());
229 assert(currentDetails_.empty());
230 for (const auto& rEntry : queued_) {
231 reported_.insert(rEntry.first);
232 currentDetails_.push_back(rEntry.first);
233 currentSources_.insert(rEntry.second.begin(), rEntry.second.end());
235 queued_.clear();
239 IMPL_STATIC_LINK(MissingPluginInstaller, launchUi, void *, p, void)
241 MissingPluginInstallerThread* thread = static_cast<MissingPluginInstallerThread*>(p);
242 rtl::Reference<MissingPluginInstallerThread> ref(thread, SAL_NO_ACQUIRE);
243 gst_pb_utils_init();
244 // not thread safe; hopefully fine to consistently call from our event
245 // loop (which is the only reason to have this
246 // Application::PostUserEvent diversion, in case
247 // MissingPluginInstaller::report might be called from outside our event
248 // loop), and hopefully fine to call gst_is_missing_plugin_message and
249 // gst_missing_plugin_message_get_installer_detail before calling
250 // gst_pb_utils_init
251 ref->launch();
255 struct TheMissingPluginInstaller:
256 public rtl::Static<MissingPluginInstaller, TheMissingPluginInstaller>
260 void MissingPluginInstallerThread::execute() {
261 MissingPluginInstaller & inst = TheMissingPluginInstaller::get();
262 for (;;) {
263 std::vector<OString> details;
265 osl::MutexGuard g(inst.mutex_);
266 assert(!inst.currentDetails_.empty());
267 details.swap(inst.currentDetails_);
269 std::vector<char *> args;
270 args.reserve(details.size());
271 for (auto const& i : details)
273 args.push_back(const_cast<char *>(i.getStr()));
275 args.push_back(nullptr);
276 gst_install_plugins_sync(args.data(), nullptr);
278 osl::MutexGuard g(inst.mutex_);
279 if (inst.queued_.empty() || inst.launchNewThread_) {
280 inst.launchNewThread_ = true;
281 break;
283 inst.processQueue();
288 } // end anonymous namespace
291 Player::Player() :
292 GstPlayer_BASE( m_aMutex ),
293 mpPlaybin( nullptr ),
294 mpVolumeControl( nullptr ),
295 #if defined(ENABLE_GTKSINK)
296 mpGtkWidget( nullptr ),
297 #endif
298 mbUseGtkSink( false ),
299 mbFakeVideo (false ),
300 mnUnmutedVolume( 0 ),
301 mbPlayPending ( false ),
302 mbMuted( false ),
303 mbLooping( false ),
304 mbInitialized( false ),
305 mpDisplay( nullptr ),
306 mnWindowID( 0 ),
307 mpXOverlay( nullptr ),
308 mnDuration( 0 ),
309 mnWidth( 0 ),
310 mnHeight( 0 ),
311 mnWatchID( 0 ),
312 mbWatchID( false )
314 // Initialize GStreamer library
315 int argc = 1;
316 char name[] = "libreoffice";
317 char *arguments[] = { name };
318 char** argv = arguments;
319 GError* pError = nullptr;
321 mbInitialized = gst_init_check( &argc, &argv, &pError );
323 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::Player" );
325 if (pError != nullptr)
327 // TODO: throw an exception?
328 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::Player error '" << pError->message << "'" );
329 g_error_free (pError);
334 Player::~Player()
336 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::~Player" );
337 if( mbInitialized )
338 disposing();
342 void SAL_CALL Player::disposing()
344 TheMissingPluginInstaller::get().detach(this);
346 ::osl::MutexGuard aGuard(m_aMutex);
348 stop();
350 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::disposing" );
352 // Release the elements and pipeline
353 if( mbInitialized )
355 #if defined(ENABLE_GTKSINK)
356 if (mpGtkWidget)
358 gtk_widget_destroy(mpGtkWidget);
359 mpGtkWidget = nullptr;
361 #endif
363 if( mpPlaybin )
365 gst_element_set_state( mpPlaybin, GST_STATE_NULL );
366 g_object_unref( G_OBJECT( mpPlaybin ) );
368 mpPlaybin = nullptr;
369 mpVolumeControl = nullptr;
372 if( mpXOverlay ) {
373 g_object_unref( G_OBJECT ( mpXOverlay ) );
374 mpXOverlay = nullptr;
378 if (mbWatchID)
380 g_source_remove(mnWatchID);
381 mbWatchID = false;
386 static gboolean pipeline_bus_callback( GstBus *, GstMessage *message, gpointer data )
388 Player* pPlayer = static_cast<Player*>(data);
390 pPlayer->processMessage( message );
392 return TRUE;
396 static GstBusSyncReply pipeline_bus_sync_handler( GstBus *, GstMessage * message, gpointer data )
398 Player* pPlayer = static_cast<Player*>(data);
400 return pPlayer->processSyncMessage( message );
404 void Player::processMessage( GstMessage *message )
406 switch( GST_MESSAGE_TYPE( message ) ) {
407 case GST_MESSAGE_EOS:
408 gst_element_set_state( mpPlaybin, GST_STATE_READY );
409 mbPlayPending = false;
410 if (mbLooping)
411 start();
412 break;
413 case GST_MESSAGE_STATE_CHANGED:
414 if (message->src == GST_OBJECT(mpPlaybin))
416 GstState newstate, pendingstate;
418 gst_message_parse_state_changed (message, nullptr, &newstate, &pendingstate);
420 if (!mbUseGtkSink && newstate == GST_STATE_PAUSED &&
421 pendingstate == GST_STATE_VOID_PENDING && mpXOverlay)
423 gst_video_overlay_expose(mpXOverlay);
426 if (mbPlayPending)
427 mbPlayPending = ((newstate == GST_STATE_READY) || (newstate == GST_STATE_PAUSED));
429 break;
430 default:
431 break;
436 static gboolean wrap_element_query_position (GstElement *element, GstFormat format, gint64 *cur)
438 #ifdef AVMEDIA_GST_0_10
439 GstFormat my_format = format;
440 return gst_element_query_position( element, &my_format, cur) && my_format == format && *cur > 0;
441 #else
442 return gst_element_query_position( element, format, cur );
443 #endif
447 static gboolean wrap_element_query_duration (GstElement *element, GstFormat format, gint64 *duration)
449 #ifdef AVMEDIA_GST_0_10
450 GstFormat my_format = format;
451 return gst_element_query_duration( element, &my_format, duration) && my_format == format && *duration > 0;
452 #else
453 return gst_element_query_duration( element, format, duration );
454 #endif
457 #ifndef AVMEDIA_GST_0_10
459 #define LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE "GstWaylandDisplayHandleContextType"
461 static gboolean lcl_is_wayland_display_handle_need_context_message(GstMessage* msg)
463 g_return_val_if_fail(GST_IS_MESSAGE(msg), false);
465 if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_NEED_CONTEXT)
466 return false;
467 const gchar *type = nullptr;
468 if (!gst_message_parse_context_type(msg, &type))
469 return false;
470 return !g_strcmp0(type, LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE);
473 static GstContext* lcl_wayland_display_handle_context_new(void* display)
475 GstContext *context = gst_context_new(LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE, TRUE);
476 gst_structure_set (gst_context_writable_structure (context),
477 "handle", G_TYPE_POINTER, display, nullptr);
478 return context;
481 #endif
483 GstBusSyncReply Player::processSyncMessage( GstMessage *message )
485 #if OSL_DEBUG_LEVEL > 0
486 if ( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ERROR )
488 GError* error;
489 gchar* error_debug;
491 gst_message_parse_error( message, &error, &error_debug );
492 SAL_WARN(
493 "avmedia.gstreamer",
494 "error: '" << error->message << "' debug: '"
495 << error_debug << "'");
497 #endif
499 if (!mbUseGtkSink)
501 #ifdef AVMEDIA_GST_0_10
502 if (message->structure &&
503 !strcmp( gst_structure_get_name( message->structure ), "prepare-xwindow-id" ) )
504 #else
505 if (gst_is_video_overlay_prepare_window_handle_message (message) )
506 #endif
508 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " processSyncMessage prepare window id: " <<
509 GST_MESSAGE_TYPE_NAME( message ) << " " << static_cast<int>(mnWindowID) );
510 if( mpXOverlay )
511 g_object_unref( G_OBJECT ( mpXOverlay ) );
512 g_object_set( GST_MESSAGE_SRC( message ), "force-aspect-ratio", FALSE, nullptr );
513 mpXOverlay = GST_VIDEO_OVERLAY( GST_MESSAGE_SRC( message ) );
514 g_object_ref( G_OBJECT ( mpXOverlay ) );
515 if ( mnWindowID != 0 )
517 gst_video_overlay_set_window_handle( mpXOverlay, mnWindowID );
518 #ifndef AVMEDIA_GST_0_10
519 gst_video_overlay_handle_events(mpXOverlay, 0); // Let the parent window handle events.
520 if (maArea.Width > 0 && maArea.Height > 0)
521 gst_video_overlay_set_render_rectangle(mpXOverlay, maArea.X, maArea.Y, maArea.Width, maArea.Height);
522 #endif
525 return GST_BUS_DROP;
527 #ifndef AVMEDIA_GST_0_10
528 else if (lcl_is_wayland_display_handle_need_context_message(message))
530 GstContext *context = lcl_wayland_display_handle_context_new(mpDisplay);
531 gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context);
533 return GST_BUS_DROP;
535 #endif
538 #ifdef AVMEDIA_GST_0_10
539 if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_STATE_CHANGED ) {
540 if( message->src == GST_OBJECT( mpPlaybin ) ) {
541 GstState newstate, pendingstate;
543 gst_message_parse_state_changed (message, nullptr, &newstate, &pendingstate);
545 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " state change received, new state " << static_cast<int>(newstate) << " pending " << static_cast<int>(pendingstate) );
546 if( newstate == GST_STATE_PAUSED &&
547 pendingstate == GST_STATE_VOID_PENDING ) {
549 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " change to paused received" );
551 if( mnDuration == 0) {
552 gint64 gst_duration = 0;
553 if( wrap_element_query_duration( mpPlaybin, GST_FORMAT_TIME, &gst_duration) )
554 mnDuration = gst_duration;
557 if( mnWidth == 0 ) {
558 GList *pStreamInfo = nullptr;
560 g_object_get( G_OBJECT( mpPlaybin ), "stream-info", &pStreamInfo, nullptr );
562 for ( ; pStreamInfo != nullptr; pStreamInfo = pStreamInfo->next) {
563 GObject *pInfo = G_OBJECT( pStreamInfo->data );
565 if( !pInfo )
566 continue;
568 int nType;
569 g_object_get( pInfo, "type", &nType, nullptr );
570 GEnumValue *pValue = g_enum_get_value( G_PARAM_SPEC_ENUM( g_object_class_find_property( G_OBJECT_GET_CLASS( pInfo ), "type" ) )->enum_class,
571 nType );
573 if( !g_ascii_strcasecmp( pValue->value_nick, "video" ) ) {
574 GstStructure *pStructure;
575 GstPad *pPad;
577 g_object_get( pInfo, "object", &pPad, nullptr );
578 pStructure = gst_caps_get_structure( GST_PAD_CAPS( pPad ), 0 );
579 if( pStructure ) {
580 gst_structure_get_int( pStructure, "width", &mnWidth );
581 gst_structure_get_int( pStructure, "height", &mnHeight );
582 SAL_INFO( "avmedia.gstreamer", AVVERSION "queried size: " << mnWidth << "x" << mnHeight );
584 g_object_unref (pPad);
588 maSizeCondition.set();
592 #else
593 // We get to use the exciting new playbin2 ! (now known as playbin)
594 if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ASYNC_DONE ) {
595 if( mnDuration == 0) {
596 gint64 gst_duration = 0;
597 if( wrap_element_query_duration( mpPlaybin, GST_FORMAT_TIME, &gst_duration) )
598 mnDuration = gst_duration;
600 if( mnWidth == 0 ) {
601 GstPad *pad = nullptr;
603 g_signal_emit_by_name( mpPlaybin, "get-video-pad", 0, &pad );
605 if( pad ) {
606 int w = 0, h = 0;
608 GstCaps *caps = gst_pad_get_current_caps( pad );
610 if( gst_structure_get( gst_caps_get_structure( caps, 0 ),
611 "width", G_TYPE_INT, &w,
612 "height", G_TYPE_INT, &h,
613 nullptr ) ) {
614 mnWidth = w;
615 mnHeight = h;
617 SAL_INFO( "avmedia.gstreamer", AVVERSION "queried size: " << mnWidth << "x" << mnHeight );
620 gst_caps_unref( caps );
621 g_object_unref( pad );
624 maSizeCondition.set();
626 #endif // AVMEDIA_GST_0_10
627 } else if (gst_is_missing_plugin_message(message)) {
628 TheMissingPluginInstaller::get().report(this, message);
629 if( mnWidth == 0 ) {
630 // an error occurred, set condition so that OOo thread doesn't wait for us
631 maSizeCondition.set();
633 } else if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ERROR ) {
634 if( mnWidth == 0 ) {
635 // an error occurred, set condition so that OOo thread doesn't wait for us
636 maSizeCondition.set();
640 return GST_BUS_PASS;
643 void Player::preparePlaybin( const OUString& rURL, GstElement *pSink )
645 #if defined(ENABLE_GTKSINK)
646 if (mpGtkWidget)
648 gtk_widget_destroy(mpGtkWidget);
649 mpGtkWidget = nullptr;
651 #endif
653 if (mpPlaybin != nullptr)
655 gst_element_set_state( mpPlaybin, GST_STATE_NULL );
656 mbPlayPending = false;
657 g_object_unref( mpPlaybin );
660 mpPlaybin = gst_element_factory_make( "playbin", nullptr );
662 //tdf#96989 on systems with flat-volumes setting the volume directly on the
663 //playbin to 100% results in setting the global volume to 100% of the
664 //maximum. We expect to set as % of the current volume.
665 mpVolumeControl = gst_element_factory_make( "volume", nullptr );
666 GstElement *pAudioSink = gst_element_factory_make( "autoaudiosink", nullptr );
667 GstElement* pAudioOutput = gst_bin_new("audio-output-bin");
668 gst_bin_add_many(GST_BIN(pAudioOutput), mpVolumeControl, pAudioSink, nullptr);
669 gst_element_link(mpVolumeControl, pAudioSink);
670 GstPad *pPad = gst_element_get_static_pad(mpVolumeControl, "sink");
671 gst_element_add_pad(GST_ELEMENT(pAudioOutput), gst_ghost_pad_new("sink", pPad));
672 gst_object_unref(GST_OBJECT(pPad));
673 g_object_set(G_OBJECT(mpPlaybin), "audio-sink", pAudioOutput, nullptr);
675 if( pSink != nullptr ) // used for getting preferred size etc.
677 g_object_set( G_OBJECT( mpPlaybin ), "video-sink", pSink, nullptr );
678 mbFakeVideo = true;
680 else
681 mbFakeVideo = false;
683 OString ascURL = OUStringToOString( rURL, RTL_TEXTENCODING_UTF8 );
684 g_object_set( G_OBJECT( mpPlaybin ), "uri", ascURL.getStr() , nullptr );
686 GstBus *pBus = gst_element_get_bus( mpPlaybin );
687 if (mbWatchID)
689 g_source_remove(mnWatchID);
690 mbWatchID = false;
692 mnWatchID = gst_bus_add_watch( pBus, pipeline_bus_callback, this );
693 mbWatchID = true;
694 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " set sync handler" );
695 #ifdef AVMEDIA_GST_0_10
696 gst_bus_set_sync_handler( pBus, pipeline_bus_sync_handler, this );
697 #else
698 gst_bus_set_sync_handler( pBus, pipeline_bus_sync_handler, this, nullptr );
699 #endif
700 g_object_unref( pBus );
704 bool Player::create( const OUString& rURL )
706 bool bRet = false;
708 // create all the elements and link them
710 SAL_INFO( "avmedia.gstreamer", "create player, URL: '" << rURL << "'" );
712 if( mbInitialized && !rURL.isEmpty() )
714 // fakesink for pre-roll & sizing ...
715 preparePlaybin( rURL, gst_element_factory_make( "fakesink", nullptr ) );
717 gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
718 mbPlayPending = false;
720 bRet = true;
723 if( bRet )
724 maURL = rURL;
725 else
726 maURL.clear();
728 return bRet;
732 void SAL_CALL Player::start()
734 ::osl::MutexGuard aGuard(m_aMutex);
736 // set the pipeline state to READY and run the loop
737 if( mbInitialized && mpPlaybin != nullptr )
739 gst_element_set_state( mpPlaybin, GST_STATE_PLAYING );
740 mbPlayPending = true;
745 void SAL_CALL Player::stop()
747 ::osl::MutexGuard aGuard(m_aMutex);
749 // set the pipeline in PAUSED STATE
750 if( mpPlaybin )
751 gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
753 mbPlayPending = false;
754 SAL_INFO( "avmedia.gstreamer", AVVERSION "stop " << mpPlaybin );
758 sal_Bool SAL_CALL Player::isPlaying()
760 ::osl::MutexGuard aGuard(m_aMutex);
762 bool bRet = mbPlayPending;
764 // return whether the pipeline is in PLAYING STATE or not
765 if( !mbPlayPending && mbInitialized && mpPlaybin )
767 bRet = GST_STATE( mpPlaybin ) == GST_STATE_PLAYING;
770 SAL_INFO( "avmedia.gstreamer", AVVERSION "isPlaying " << bRet );
772 return bRet;
776 double SAL_CALL Player::getDuration()
778 ::osl::MutexGuard aGuard(m_aMutex);
780 // slideshow checks for non-zero duration, so cheat here
781 double duration = 0.3;
783 if( mpPlaybin && mnDuration > 0 ) {
784 duration = mnDuration / GST_SECOND;
787 return duration;
791 void SAL_CALL Player::setMediaTime( double fTime )
793 ::osl::MutexGuard aGuard(m_aMutex);
795 if( mpPlaybin ) {
796 gint64 gst_position = llround (fTime * GST_SECOND);
798 gst_element_seek( mpPlaybin, 1.0,
799 GST_FORMAT_TIME,
800 GST_SEEK_FLAG_FLUSH,
801 GST_SEEK_TYPE_SET, gst_position,
802 GST_SEEK_TYPE_NONE, 0 );
803 if( !isPlaying() )
804 gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
806 SAL_INFO( "avmedia.gstreamer", AVVERSION "seek to: " << gst_position << " ns original: " << fTime << " s" );
811 double SAL_CALL Player::getMediaTime()
813 ::osl::MutexGuard aGuard(m_aMutex);
815 double position = 0.0;
817 if( mpPlaybin ) {
818 // get current position in the stream
819 gint64 gst_position;
820 if( wrap_element_query_position( mpPlaybin, GST_FORMAT_TIME, &gst_position ) )
821 position = gst_position / GST_SECOND;
824 return position;
828 void SAL_CALL Player::setPlaybackLoop( sal_Bool bSet )
830 ::osl::MutexGuard aGuard(m_aMutex);
831 // TODO check how to do with GST
832 mbLooping = bSet;
836 sal_Bool SAL_CALL Player::isPlaybackLoop()
838 ::osl::MutexGuard aGuard(m_aMutex);
839 // TODO check how to do with GST
840 return mbLooping;
844 void SAL_CALL Player::setMute( sal_Bool bSet )
846 ::osl::MutexGuard aGuard(m_aMutex);
848 SAL_INFO( "avmedia.gstreamer", AVVERSION "set mute: " << bSet << " muted: " << mbMuted << " unmuted volume: " << mnUnmutedVolume );
850 // change the volume to 0 or the unmuted volume
851 if( mpPlaybin && mbMuted != bool(bSet) )
853 double nVolume = mnUnmutedVolume;
854 if( bSet )
856 nVolume = 0.0;
859 g_object_set( G_OBJECT( mpVolumeControl ), "volume", nVolume, nullptr );
861 mbMuted = bSet;
866 sal_Bool SAL_CALL Player::isMute()
868 ::osl::MutexGuard aGuard(m_aMutex);
870 return mbMuted;
874 void SAL_CALL Player::setVolumeDB( sal_Int16 nVolumeDB )
876 ::osl::MutexGuard aGuard(m_aMutex);
878 mnUnmutedVolume = pow( 10.0, nVolumeDB / 20.0 );
880 SAL_INFO( "avmedia.gstreamer", AVVERSION "set volume: " << nVolumeDB << " gst volume: " << mnUnmutedVolume );
882 // change volume
883 if( !mbMuted && mpPlaybin )
885 g_object_set( G_OBJECT( mpVolumeControl ), "volume", mnUnmutedVolume, nullptr );
890 sal_Int16 SAL_CALL Player::getVolumeDB()
892 ::osl::MutexGuard aGuard(m_aMutex);
894 sal_Int16 nVolumeDB(0);
896 if( mpPlaybin ) {
897 double nGstVolume = 0.0;
899 g_object_get( G_OBJECT( mpVolumeControl ), "volume", &nGstVolume, nullptr );
901 nVolumeDB = static_cast<sal_Int16>( 20.0*log10 ( nGstVolume ) );
904 return nVolumeDB;
908 awt::Size SAL_CALL Player::getPreferredPlayerWindowSize()
910 ::osl::MutexGuard aGuard(m_aMutex);
912 awt::Size aSize( 0, 0 );
914 if( maURL.isEmpty() )
916 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::getPreferredPlayerWindowSize - empty URL => 0x0" );
917 return aSize;
920 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " pre-Player::getPreferredPlayerWindowSize, member " << mnWidth << "x" << mnHeight );
922 osl::Condition::Result aResult = maSizeCondition.wait( std::chrono::seconds(10) );
924 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::getPreferredPlayerWindowSize after waitCondition " << aResult << ", member " << mnWidth << "x" << mnHeight );
926 if( mnWidth != 0 && mnHeight != 0 ) {
927 aSize.Width = mnWidth;
928 aSize.Height = mnHeight;
931 return aSize;
934 uno::Reference< ::media::XPlayerWindow > SAL_CALL Player::createPlayerWindow( const uno::Sequence< uno::Any >& rArguments )
936 ::osl::MutexGuard aGuard(m_aMutex);
938 uno::Reference< ::media::XPlayerWindow > xRet;
939 awt::Size aSize;
941 if (rArguments.getLength() > 1 && (rArguments[1] >>= maArea))
942 aSize = awt::Size(maArea.Width, maArea.Height);
943 else
944 aSize = getPreferredPlayerWindowSize();
946 if( mbFakeVideo )
947 preparePlaybin( maURL, nullptr );
949 SAL_INFO( "avmedia.gstreamer", AVVERSION "Player::createPlayerWindow " << aSize.Width << "x" << aSize.Height << " length: " << rArguments.getLength() );
951 if( aSize.Width > 0 && aSize.Height > 0 )
953 ::avmedia::gstreamer::Window* pWindow = new ::avmedia::gstreamer::Window;
955 xRet = pWindow;
957 if( rArguments.getLength() > 2 )
959 sal_IntPtr pIntPtr = 0;
960 rArguments[ 2 ] >>= pIntPtr;
961 SystemChildWindow *pParentWindow = reinterpret_cast< SystemChildWindow* >( pIntPtr );
963 const SystemEnvData* pEnvData = pParentWindow ? pParentWindow->GetSystemData() : nullptr;
964 OSL_ASSERT(pEnvData);
965 if (pEnvData)
967 OUString aToolkit = OUString::createFromAscii(pEnvData->pToolkit);
968 OUString aPlatform = OUString::createFromAscii(pEnvData->pPlatformName);
970 // tdf#124027: the position of embedded window is identical w/ the position
971 // of media object in all other vclplugs (gtk, kde5, gen), in gtk3 w/o gtksink it
972 // needs to be translated
973 if (aToolkit == "gtk3")
975 if (pParentWindow)
977 Point aPoint = pParentWindow->GetPosPixel();
978 maArea.X = aPoint.getX();
979 maArea.Y = aPoint.getY();
983 GstElement *pVideosink = nullptr;
984 #if defined(ENABLE_GTKSINK)
985 pVideosink = (aToolkit == "gtk3") ?
986 gst_element_factory_make("gtksink", "gtksink") : nullptr;
987 if (pVideosink)
989 mbUseGtkSink = true;
990 g_object_get(pVideosink, "widget", &mpGtkWidget, nullptr);
991 gtk_widget_set_vexpand(mpGtkWidget, true);
992 gtk_widget_set_hexpand(mpGtkWidget, true);
993 GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
994 gtk_container_add (GTK_CONTAINER(pParent), mpGtkWidget);
996 g_object_set( G_OBJECT( mpPlaybin ), "video-sink", pVideosink, nullptr);
997 g_object_set( G_OBJECT( mpPlaybin ), "force-aspect-ratio", FALSE, nullptr);
999 gtk_widget_show_all (pParent);
1001 else
1002 #endif
1004 if (aPlatform == "wayland")
1005 pVideosink = gst_element_factory_make("waylandsink", "video-output");
1006 else
1007 pVideosink = gst_element_factory_make("autovideosink", "video-output");
1008 if (!pVideosink)
1010 xRet.clear();
1011 return nullptr;
1013 g_object_set(G_OBJECT(mpPlaybin), "video-sink", pVideosink, nullptr);
1014 mbUseGtkSink = false;
1015 mnWindowID = pEnvData->aWindow;
1016 mpDisplay = pEnvData->pDisplay;
1017 SAL_INFO( "avmedia.gstreamer", AVVERSION "set window id to " << static_cast<int>(mnWindowID) << " XOverlay " << mpXOverlay);
1018 gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
1019 if ( mpXOverlay != nullptr )
1020 gst_video_overlay_set_window_handle( mpXOverlay, mnWindowID );
1026 return xRet;
1029 uno::Reference< media::XFrameGrabber > SAL_CALL Player::createFrameGrabber()
1031 ::osl::MutexGuard aGuard(m_aMutex);
1032 FrameGrabber* pFrameGrabber = nullptr;
1033 const awt::Size aPrefSize( getPreferredPlayerWindowSize() );
1035 if( ( aPrefSize.Width > 0 ) && ( aPrefSize.Height > 0 ) )
1036 pFrameGrabber = FrameGrabber::create( maURL );
1037 SAL_INFO( "avmedia.gstreamer", AVVERSION "created FrameGrabber " << pFrameGrabber );
1039 return pFrameGrabber;
1043 OUString SAL_CALL Player::getImplementationName()
1045 return OUString( AVMEDIA_GST_PLAYER_IMPLEMENTATIONNAME );
1049 sal_Bool SAL_CALL Player::supportsService( const OUString& ServiceName )
1051 return cppu::supportsService(this, ServiceName);
1055 uno::Sequence< OUString > SAL_CALL Player::getSupportedServiceNames()
1057 return { AVMEDIA_GST_PLAYER_SERVICENAME };
1060 } // namespace gstreamer
1061 } // namespace avmedia
1063 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */