LibreOffice Module avmedia (master) 1
gstplayer.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include <sal/config.h>
21
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>
31
32#include <com/sun/star/text/GraphicCrop.hpp>
33
35#include <sal/log.hxx>
36#include <rtl/string.hxx>
37#include <salhelper/thread.hxx>
38#include <vcl/svapp.hxx>
39#include <vcl/syschild.hxx>
40#include <vcl/sysdata.hxx>
41#include <vcl/graph.hxx>
42#include <avmedia/mediaitem.hxx>
43
44#include "gstplayer.hxx"
45#include "gstframegrabber.hxx"
46#include "gstwindow.hxx"
47
48#include <gst/video/videooverlay.h>
49#include <gst/pbutils/missing-plugins.h>
50#include <gst/pbutils/pbutils.h>
51
52constexpr OUStringLiteral AVMEDIA_GST_PLAYER_IMPLEMENTATIONNAME = u"com.sun.star.comp.avmedia.Player_GStreamer";
53constexpr OUStringLiteral AVMEDIA_GST_PLAYER_SERVICENAME = u"com.sun.star.media.Player_GStreamer";
54#define AVVERSION "gst 1.0: "
55
56using namespace ::com::sun::star;
57
58namespace avmedia::gstreamer {
59
60namespace {
61
62class FlagGuard {
63public:
64 explicit FlagGuard(bool & flag): flag_(flag) { flag_ = true; }
65
66 ~FlagGuard() { flag_ = false; }
67
68private:
69 bool & flag_;
70};
71
72class MissingPluginInstallerThread: public salhelper::Thread {
73public:
74 MissingPluginInstallerThread(): Thread("MissingPluginInstaller") {}
75
76private:
77 void execute() override;
78};
79
80
81class MissingPluginInstaller {
82 friend class MissingPluginInstallerThread;
83
84public:
85 MissingPluginInstaller(): launchNewThread_(true), inCleanUp_(false) {}
86
87 ~MissingPluginInstaller();
88
89 void report(rtl::Reference<Player> const & source, GstMessage * message);
90
91 // Player::~Player calls Player::disposing calls
92 // MissingPluginInstaller::detach, so do not take Player by rtl::Reference
93 // here (which would bump its refcount back from 0 to 1):
94 void detach(Player const * source);
95
96private:
97 void processQueue();
98
99 DECL_STATIC_LINK(MissingPluginInstaller, launchUi, void*, void);
100
101 osl::Mutex mutex_;
102 std::set<OString> reported_;
103 std::map<OString, std::set<rtl::Reference<Player>>> queued_;
105 std::vector<OString> currentDetails_;
106 std::set<rtl::Reference<Player>> currentSources_;
109};
110
111
112MissingPluginInstaller::~MissingPluginInstaller() {
113 osl::MutexGuard g(mutex_);
114 SAL_WARN_IF(currentThread_.is(), "avmedia.gstreamer", "unjoined thread");
115 inCleanUp_ = true;
116}
117
118
119void MissingPluginInstaller::report(
120 rtl::Reference<Player> const & source, GstMessage * message)
121{
122 // assert(gst_is_missing_plugin_message(message));
123 gchar * det = gst_missing_plugin_message_get_installer_detail(message);
124 if (det == nullptr) {
125 SAL_WARN(
126 "avmedia.gstreamer",
127 "gst_missing_plugin_message_get_installer_detail failed");
128 return;
129 }
130 std::size_t len = std::strlen(det);
131 if (len > SAL_MAX_INT32) {
132 SAL_WARN("avmedia.gstreamer", "detail string too long");
133 g_free(det);
134 return;
135 }
136 OString detStr(det, len);
137 g_free(det);
140 {
141 osl::MutexGuard g(mutex_);
142 if (reported_.find(detStr) != reported_.end()) {
143 return;
144 }
145 auto & i = queued_[detStr];
146 bool fresh = i.empty();
147 i.insert(source);
148 if (!(fresh && launchNewThread_)) {
149 return;
150 }
152 currentThread_ = new MissingPluginInstallerThread;
153 {
154 FlagGuard f(inCleanUp_);
155 currentSources_.clear();
156 }
157 processQueue();
158 launchNewThread_ = false;
159 launch = currentThread_;
160 }
161 if (join.is()) {
162 join->join();
163 }
164 launch->acquire();
166 LINK(this, MissingPluginInstaller, launchUi), launch.get());
167}
168
169
170void eraseSource(std::set<rtl::Reference<Player>> & set, Player const * source)
171{
172 auto i = std::find_if(
173 set.begin(), set.end(),
174 [source](rtl::Reference<Player> const & el) {
175 return el.get() == source;
176 });
177 if (i != set.end()) {
178 set.erase(i);
179 }
180}
181
182
183void MissingPluginInstaller::detach(Player const * source) {
185 {
186 osl::MutexGuard g(mutex_);
187 if (inCleanUp_) {
188 // Guard against ~MissingPluginInstaller with erroneously un-joined
189 // currentThread_ (thus non-empty currentSources_) calling
190 // destructor of currentSources_, calling ~Player, calling here,
191 // which would use currentSources_ while it is already being
192 // destroyed:
193 return;
194 }
195 for (auto i = queued_.begin(); i != queued_.end();) {
196 eraseSource(i->second, source);
197 if (i->second.empty()) {
198 i = queued_.erase(i);
199 } else {
200 ++i;
201 }
202 }
203 if (currentThread_.is()) {
204 assert(!currentSources_.empty());
205 eraseSource(currentSources_, source);
206 if (currentSources_.empty()) {
208 currentThread_.clear();
209 launchNewThread_ = true;
210 }
211 }
212 }
213 if (join.is()) {
214 // missing cancellability of gst_install_plugins_sync
215 join->join();
216 }
217}
218
219
220void MissingPluginInstaller::processQueue() {
221 assert(!queued_.empty());
222 assert(currentDetails_.empty());
223 for (const auto& rEntry : queued_) {
224 reported_.insert(rEntry.first);
225 currentDetails_.push_back(rEntry.first);
226 currentSources_.insert(rEntry.second.begin(), rEntry.second.end());
227 }
228 queued_.clear();
229}
230
231
232IMPL_STATIC_LINK(MissingPluginInstaller, launchUi, void *, p, void)
233{
234 MissingPluginInstallerThread* thread = static_cast<MissingPluginInstallerThread*>(p);
235 rtl::Reference<MissingPluginInstallerThread> ref(thread, SAL_NO_ACQUIRE);
236 gst_pb_utils_init();
237 // not thread safe; hopefully fine to consistently call from our event
238 // loop (which is the only reason to have this
239 // Application::PostUserEvent diversion, in case
240 // MissingPluginInstaller::report might be called from outside our event
241 // loop), and hopefully fine to call gst_is_missing_plugin_message and
242 // gst_missing_plugin_message_get_installer_detail before calling
243 // gst_pb_utils_init
244 ref->launch();
245}
246
247
248MissingPluginInstaller& TheMissingPluginInstaller()
249{
250 static MissingPluginInstaller theInstaller;
251 return theInstaller;
252}
253
254
255void MissingPluginInstallerThread::execute() {
256 MissingPluginInstaller & inst = TheMissingPluginInstaller();
257 for (;;) {
258 std::vector<OString> details;
259 {
260 osl::MutexGuard g(inst.mutex_);
261 assert(!inst.currentDetails_.empty());
262 details.swap(inst.currentDetails_);
263 }
264 std::vector<char *> args;
265 args.reserve(details.size());
266 for (auto const& i : details)
267 {
268 args.push_back(const_cast<char *>(i.getStr()));
269 }
270 args.push_back(nullptr);
271 gst_install_plugins_sync(args.data(), nullptr);
272 {
273 osl::MutexGuard g(inst.mutex_);
274 if (inst.queued_.empty() || inst.launchNewThread_) {
275 inst.launchNewThread_ = true;
276 break;
277 }
278 inst.processQueue();
279 }
280 }
281}
282
283} // end anonymous namespace
284
285
288 mpPlaybin( nullptr ),
289 mpVolumeControl( nullptr ),
290 mbUseGtkSink( false ),
291 mbFakeVideo (false ),
292 mnUnmutedVolume( 0 ),
293 mbMuted( false ),
294 mbLooping( false ),
295 mbInitialized( false ),
296 mpDisplay( nullptr ),
297 mnWindowID( 0 ),
298 mpXOverlay( nullptr ),
299 mnDuration( 0 ),
300 mnWidth( 0 ),
301 mnHeight( 0 ),
302 mnWatchID( 0 ),
303 mbWatchID( false )
304{
305 // Initialize GStreamer library
306 int argc = 1;
307 char name[] = "libreoffice";
308 char *arguments[] = { name };
309 char** argv = arguments;
310 GError* pError = nullptr;
311
312 mbInitialized = gst_init_check( &argc, &argv, &pError );
313
314 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::Player" );
315
316 if (pError != nullptr)
317 {
318 // TODO: throw an exception?
319 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::Player error '" << pError->message << "'" );
320 g_error_free (pError);
321 }
322}
323
324
326{
327 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::~Player" );
328 if( mbInitialized )
329 disposing();
330}
331
332
333void SAL_CALL Player::disposing()
334{
335 TheMissingPluginInstaller().detach(this);
336
337 ::osl::MutexGuard aGuard(m_aMutex);
338
339 stop();
340
341 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::disposing" );
342
343 // Release the elements and pipeline
344 if( mbInitialized )
345 {
346 if( mpPlaybin )
347 {
348 gst_element_set_state( mpPlaybin, GST_STATE_NULL );
349 g_object_unref( G_OBJECT( mpPlaybin ) );
350
351 mpPlaybin = nullptr;
352 mpVolumeControl = nullptr;
353 }
354
355 if( mpXOverlay ) {
356 g_object_unref( G_OBJECT ( mpXOverlay ) );
357 mpXOverlay = nullptr;
358 }
359
360 }
361 if (mbWatchID)
362 {
363 g_source_remove(mnWatchID);
364 mbWatchID = false;
365 }
366}
367
368
369static gboolean pipeline_bus_callback( GstBus *, GstMessage *message, gpointer data )
370{
371 Player* pPlayer = static_cast<Player*>(data);
372
373 pPlayer->processMessage( message );
374
375 return true;
376}
377
378
379static GstBusSyncReply pipeline_bus_sync_handler( GstBus *, GstMessage * message, gpointer data )
380{
381 Player* pPlayer = static_cast<Player*>(data);
382
383 return pPlayer->processSyncMessage( message );
384}
385
386
387void Player::processMessage( GstMessage *message )
388{
389 switch( GST_MESSAGE_TYPE( message ) ) {
390 case GST_MESSAGE_EOS:
391 gst_element_set_state( mpPlaybin, GST_STATE_READY );
392 if (mbLooping)
393 start();
394 break;
395 case GST_MESSAGE_STATE_CHANGED:
396 if (message->src == GST_OBJECT(mpPlaybin))
397 {
398 GstState newstate, pendingstate;
399
400 gst_message_parse_state_changed (message, nullptr, &newstate, &pendingstate);
401
402 if (!mbUseGtkSink && newstate == GST_STATE_PAUSED &&
403 pendingstate == GST_STATE_VOID_PENDING && mpXOverlay)
404 {
405 gst_video_overlay_expose(mpXOverlay);
406 }
407 }
408 break;
409 default:
410 break;
411 }
412}
413
414#define LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE "GstWaylandDisplayHandleContextType"
415
417{
418 g_return_val_if_fail(GST_IS_MESSAGE(msg), false);
419
420 if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_NEED_CONTEXT)
421 return false;
422 const gchar *type = nullptr;
423 if (!gst_message_parse_context_type(msg, &type))
424 return false;
426}
427
428static GstContext* lcl_wayland_display_handle_context_new(void* display)
429{
430 GstContext *context = gst_context_new(LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE, true);
431 gst_structure_set (gst_context_writable_structure (context),
432 "handle", G_TYPE_POINTER, display, nullptr);
433 return context;
434}
435
436GstBusSyncReply Player::processSyncMessage( GstMessage *message )
437{
438#if OSL_DEBUG_LEVEL > 0
439 if ( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ERROR )
440 {
441 GError* error;
442 gchar* error_debug;
443
444 gst_message_parse_error( message, &error, &error_debug );
445 SAL_WARN(
446 "avmedia.gstreamer",
447 "error: '" << error->message << "' debug: '"
448 << error_debug << "'");
449 }
450 else if ( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_WARNING )
451 {
452 GError* error;
453 gchar* error_debug;
454
455 gst_message_parse_warning( message, &error, &error_debug );
456 SAL_WARN(
457 "avmedia.gstreamer",
458 "warning: '" << error->message << "' debug: '"
459 << error_debug << "'");
460 }
461 else if ( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_INFO )
462 {
463 GError* error;
464 gchar* error_debug;
465
466 gst_message_parse_info( message, &error, &error_debug );
467 SAL_WARN(
468 "avmedia.gstreamer",
469 "info: '" << error->message << "' debug: '"
470 << error_debug << "'");
471 }
472#endif
473
474 if (!mbUseGtkSink)
475 {
476 if (gst_is_video_overlay_prepare_window_handle_message (message) )
477 {
478 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " processSyncMessage prepare window id: " <<
479 GST_MESSAGE_TYPE_NAME( message ) << " " << static_cast<int>(mnWindowID) );
480 if( mpXOverlay )
481 g_object_unref( G_OBJECT ( mpXOverlay ) );
482 g_object_set( GST_MESSAGE_SRC( message ), "force-aspect-ratio", FALSE, nullptr );
483 mpXOverlay = GST_VIDEO_OVERLAY( GST_MESSAGE_SRC( message ) );
484 g_object_ref( G_OBJECT ( mpXOverlay ) );
485 if ( mnWindowID != 0 )
486 {
487 gst_video_overlay_set_window_handle( mpXOverlay, mnWindowID );
488 gst_video_overlay_handle_events(mpXOverlay, 0); // Let the parent window handle events.
489 if (maArea.Width > 0 && maArea.Height > 0)
490 gst_video_overlay_set_render_rectangle(mpXOverlay, maArea.X, maArea.Y, maArea.Width, maArea.Height);
491 }
492
493 return GST_BUS_DROP;
494 }
496 {
498 gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context);
499
500 return GST_BUS_DROP;
501 }
502 }
503
504 if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ASYNC_DONE ) {
505 if( mnDuration == 0) {
506 gint64 gst_duration = 0;
507 if( gst_element_query_duration( mpPlaybin, GST_FORMAT_TIME, &gst_duration) )
508 mnDuration = gst_duration;
509 }
510 if( mnWidth == 0 ) {
511 GstPad *pad = nullptr;
512
513 g_signal_emit_by_name( mpPlaybin, "get-video-pad", 0, &pad );
514
515 if( pad ) {
516 int w = 0, h = 0;
517
518 GstCaps *caps = gst_pad_get_current_caps( pad );
519
520 if( gst_structure_get( gst_caps_get_structure( caps, 0 ),
521 "width", G_TYPE_INT, &w,
522 "height", G_TYPE_INT, &h,
523 nullptr ) ) {
524 mnWidth = w;
525 mnHeight = h;
526
527 SAL_INFO( "avmedia.gstreamer", AVVERSION "queried size: " << mnWidth << "x" << mnHeight );
528
529 }
530 gst_caps_unref( caps );
531 g_object_unref( pad );
532 }
533
534 maSizeCondition.set();
535 }
536 } else if (gst_is_missing_plugin_message(message)) {
537 TheMissingPluginInstaller().report(this, message);
538 if( mnWidth == 0 ) {
539 // an error occurred, set condition so that OOo thread doesn't wait for us
540 maSizeCondition.set();
541 }
542 } else if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ERROR ) {
543 if( mnWidth == 0 ) {
544 // an error occurred, set condition so that OOo thread doesn't wait for us
545 maSizeCondition.set();
546 }
547 }
548
549 return GST_BUS_PASS;
550}
551
552void Player::preparePlaybin( std::u16string_view rURL, GstElement *pSink )
553{
554 if (mpPlaybin != nullptr)
555 {
556 gst_element_set_state( mpPlaybin, GST_STATE_NULL );
557 g_object_unref( mpPlaybin );
558 }
559
560 mpPlaybin = gst_element_factory_make( "playbin", nullptr );
561
562 //tdf#96989 on systems with flat-volumes setting the volume directly on the
563 //playbin to 100% results in setting the global volume to 100% of the
564 //maximum. We expect to set as % of the current volume.
565 mpVolumeControl = gst_element_factory_make( "volume", nullptr );
566 GstElement *pAudioSink = gst_element_factory_make( "autoaudiosink", nullptr );
567 GstElement* pAudioOutput = gst_bin_new("audio-output-bin");
568 assert(pAudioOutput);
569 if (pAudioSink)
570 gst_bin_add(GST_BIN(pAudioOutput), pAudioSink);
571 if (mpVolumeControl)
572 {
573 gst_bin_add(GST_BIN(pAudioOutput), mpVolumeControl);
574 if (pAudioSink)
575 gst_element_link(mpVolumeControl, pAudioSink);
576 GstPad *pPad = gst_element_get_static_pad(mpVolumeControl, "sink");
577 gst_element_add_pad(GST_ELEMENT(pAudioOutput), gst_ghost_pad_new("sink", pPad));
578 gst_object_unref(GST_OBJECT(pPad));
579 }
580 g_object_set(G_OBJECT(mpPlaybin), "audio-sink", pAudioOutput, nullptr);
581
582 if( pSink != nullptr ) // used for getting preferred size etc.
583 {
584 g_object_set( G_OBJECT( mpPlaybin ), "video-sink", pSink, nullptr );
585 mbFakeVideo = true;
586 }
587 else
588 mbFakeVideo = false;
589
590 OString ascURL = OUStringToOString( rURL, RTL_TEXTENCODING_UTF8 );
591 g_object_set( G_OBJECT( mpPlaybin ), "uri", ascURL.getStr() , nullptr );
592
593 GstBus *pBus = gst_element_get_bus( mpPlaybin );
594 if (mbWatchID)
595 {
596 g_source_remove(mnWatchID);
597 mbWatchID = false;
598 }
599 mnWatchID = gst_bus_add_watch( pBus, pipeline_bus_callback, this );
600 mbWatchID = true;
601 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " set sync handler" );
602 gst_bus_set_sync_handler( pBus, pipeline_bus_sync_handler, this, nullptr );
603 g_object_unref( pBus );
604}
605
606
607bool Player::create( const OUString& rURL )
608{
609 bool bRet = false;
610
611 // create all the elements and link them
612
613 SAL_INFO( "avmedia.gstreamer", "create player, URL: '" << rURL << "'" );
614
615 if( mbInitialized && !rURL.isEmpty() )
616 {
617 // fakesink for pre-roll & sizing ...
618 preparePlaybin( rURL, gst_element_factory_make( "fakesink", nullptr ) );
619
620 gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
621
622 bRet = true;
623 }
624
625 if( bRet )
626 maURL = rURL;
627 else
628 maURL.clear();
629
630 return bRet;
631}
632
633void SAL_CALL Player::start()
634{
635 ::osl::MutexGuard aGuard(m_aMutex);
636
637 // set the pipeline state to READY and run the loop
638 if( mbInitialized && mpPlaybin != nullptr )
639 {
640 gst_element_set_state( mpPlaybin, GST_STATE_PLAYING );
641 }
642
643 SAL_INFO( "avmedia.gstreamer", AVVERSION "start " << mpPlaybin );
644}
645
646void SAL_CALL Player::stop()
647{
648 ::osl::MutexGuard aGuard(m_aMutex);
649
650 // set the pipeline in PAUSED STATE
651 if( mpPlaybin )
652 gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
653
654 SAL_INFO( "avmedia.gstreamer", AVVERSION "stop " << mpPlaybin );
655}
656
658{
659 ::osl::MutexGuard aGuard(m_aMutex);
660
661 bool bRet = false;
662
663 // return whether the pipeline target is PLAYING STATE or not
665 {
666 bRet = GST_STATE_TARGET(mpPlaybin) == GST_STATE_PLAYING;
667 }
668
669 return bRet;
670}
671
672double SAL_CALL Player::getDuration()
673{
674 ::osl::MutexGuard aGuard(m_aMutex);
675
676 // slideshow checks for non-zero duration, so cheat here
677 double duration = 0.3;
678
679 if( mpPlaybin && mnDuration > 0 ) {
680 duration = mnDuration / GST_SECOND;
681 }
682
683 return duration;
684}
685
686
687void SAL_CALL Player::setMediaTime( double fTime )
688{
689 ::osl::MutexGuard aGuard(m_aMutex);
690
691 if( !mpPlaybin )
692 return;
693
694 gint64 gst_position = llround (fTime * GST_SECOND);
695
696 gst_element_seek( mpPlaybin, 1.0,
697 GST_FORMAT_TIME,
698 GST_SEEK_FLAG_FLUSH,
699 GST_SEEK_TYPE_SET, gst_position,
700 GST_SEEK_TYPE_NONE, 0 );
701
702 SAL_INFO( "avmedia.gstreamer", AVVERSION "seek to: " << gst_position << " ns original: " << fTime << " s" );
703}
704
705
706double SAL_CALL Player::getMediaTime()
707{
708 ::osl::MutexGuard aGuard(m_aMutex);
709
710 double position = 0.0;
711
712 if( mpPlaybin ) {
713 // get current position in the stream
714 gint64 gst_position;
715 if( gst_element_query_position( mpPlaybin, GST_FORMAT_TIME, &gst_position ) )
716 position = gst_position / GST_SECOND;
717 }
718
719 return position;
720}
721
722
724{
725 ::osl::MutexGuard aGuard(m_aMutex);
726 // TODO check how to do with GST
727 mbLooping = bSet;
728}
729
730
732{
733 ::osl::MutexGuard aGuard(m_aMutex);
734 // TODO check how to do with GST
735 return mbLooping;
736}
737
738
739void SAL_CALL Player::setMute( sal_Bool bSet )
740{
741 ::osl::MutexGuard aGuard(m_aMutex);
742
743 SAL_INFO( "avmedia.gstreamer", AVVERSION "set mute: " << bSet << " muted: " << mbMuted << " unmuted volume: " << mnUnmutedVolume );
744
745 // change the volume to 0 or the unmuted volume
746 if (mpVolumeControl && mbMuted != bool(bSet))
747 {
748 double nVolume = mnUnmutedVolume;
749 if( bSet )
750 {
751 nVolume = 0.0;
752 }
753
754 g_object_set( G_OBJECT( mpVolumeControl ), "volume", nVolume, nullptr );
755
756 mbMuted = bSet;
757 }
758}
759
760
762{
763 ::osl::MutexGuard aGuard(m_aMutex);
764
765 return mbMuted;
766}
767
768
769void SAL_CALL Player::setVolumeDB( sal_Int16 nVolumeDB )
770{
771 ::osl::MutexGuard aGuard(m_aMutex);
772
773 mnUnmutedVolume = pow( 10.0, nVolumeDB / 20.0 );
774
775 SAL_INFO( "avmedia.gstreamer", AVVERSION "set volume: " << nVolumeDB << " gst volume: " << mnUnmutedVolume );
776
777 // change volume
778 if (mpVolumeControl && !mbMuted)
779 {
780 g_object_set( G_OBJECT( mpVolumeControl ), "volume", mnUnmutedVolume, nullptr );
781 }
782}
783
784
785sal_Int16 SAL_CALL Player::getVolumeDB()
786{
787 ::osl::MutexGuard aGuard(m_aMutex);
788
789 sal_Int16 nVolumeDB(0);
790
791 if (mpVolumeControl)
792 {
793 double nGstVolume = 0.0;
794
795 g_object_get( G_OBJECT( mpVolumeControl ), "volume", &nGstVolume, nullptr );
796
797 nVolumeDB = static_cast<sal_Int16>( 20.0*log10 ( nGstVolume ) );
798 }
799
800 return nVolumeDB;
801}
802
803
805{
806 ::osl::MutexGuard aGuard(m_aMutex);
807
808 awt::Size aSize( 0, 0 );
809
810 if( maURL.isEmpty() )
811 {
812 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::getPreferredPlayerWindowSize - empty URL => 0x0" );
813 return aSize;
814 }
815
816 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " pre-Player::getPreferredPlayerWindowSize, member " << mnWidth << "x" << mnHeight );
817
818 osl::Condition::Result aResult = maSizeCondition.wait( std::chrono::seconds(10) );
819
820 SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::getPreferredPlayerWindowSize after waitCondition " << aResult << ", member " << mnWidth << "x" << mnHeight );
821
822 if( mnWidth != 0 && mnHeight != 0 ) {
823 aSize.Width = mnWidth;
824 aSize.Height = mnHeight;
825 }
826
827 return aSize;
828}
829
830uno::Reference< ::media::XPlayerWindow > SAL_CALL Player::createPlayerWindow( const uno::Sequence< uno::Any >& rArguments )
831{
832 ::osl::MutexGuard aGuard(m_aMutex);
833
834 uno::Reference< ::media::XPlayerWindow > xRet;
835
836 if (rArguments.getLength() > 1)
837 rArguments[1] >>= maArea;
838
839 awt::Size aSize = getPreferredPlayerWindowSize();
840
841 if( mbFakeVideo )
842 preparePlaybin( maURL, nullptr );
843
844 SAL_INFO( "avmedia.gstreamer", AVVERSION "Player::createPlayerWindow " << aSize.Width << "x" << aSize.Height << " length: " << rArguments.getLength() );
845
846 if( aSize.Width > 0 && aSize.Height > 0 )
847 {
848 if (rArguments.getLength() <= 2)
849 {
850 xRet = new ::avmedia::gstreamer::Window;
851 return xRet;
852 }
853
854 sal_IntPtr pIntPtr = 0;
855 rArguments[ 2 ] >>= pIntPtr;
856 SystemChildWindow *pParentWindow = reinterpret_cast< SystemChildWindow* >( pIntPtr );
857 if (!pParentWindow)
858 return nullptr;
859
860 const SystemEnvData* pEnvData = pParentWindow->GetSystemData();
861 if (!pEnvData)
862 return nullptr;
863
864 // tdf#124027: the position of embedded window is identical w/ the position
865 // of media object in all other vclplugs (kf5, gen), in gtk3 w/o gtksink it
866 // needs to be translated
867 if (pEnvData->toolkit == SystemEnvData::Toolkit::Gtk)
868 {
869 Point aPoint = pParentWindow->GetPosPixel();
870 maArea.X = aPoint.getX();
871 maArea.Y = aPoint.getY();
872 }
873
874 mbUseGtkSink = false;
875
876 GstElement *pVideosink = static_cast<GstElement*>(pParentWindow->CreateGStreamerSink());
877 if (pVideosink)
878 {
879 if (pEnvData->toolkit == SystemEnvData::Toolkit::Gtk)
880 mbUseGtkSink = true;
881 }
882 else
883 {
885 pVideosink = gst_element_factory_make("waylandsink", "video-output");
886 else
887 pVideosink = gst_element_factory_make("autovideosink", "video-output");
888 if (!pVideosink)
889 return nullptr;
890 }
891
892 xRet = new ::avmedia::gstreamer::Window;
893
894 g_object_set(G_OBJECT(mpPlaybin), "video-sink", pVideosink, nullptr);
895 g_object_set(G_OBJECT(mpPlaybin), "force-aspect-ratio", FALSE, nullptr);
896
897 if ((rArguments.getLength() >= 4) && (rArguments[3] >>= pIntPtr) && pIntPtr)
898 {
899 auto pItem = reinterpret_cast<const avmedia::MediaItem*>(pIntPtr);
900 Graphic aGraphic = pItem->getGraphic();
901 const text::GraphicCrop& rCrop = pItem->getCrop();
902 if (!aGraphic.IsNone() && (rCrop.Bottom > 0 || rCrop.Left > 0 || rCrop.Right > 0 || rCrop.Top > 0))
903 {
904 // The media item has a non-empty cropping set. Try to crop the video accordingly.
905 Size aPref = aGraphic.GetPrefSize();
906 Size aPixel = aGraphic.GetSizePixel();
907 tools::Long nLeft = aPixel.getWidth() * rCrop.Left / aPref.getWidth();
908 tools::Long nTop = aPixel.getHeight() * rCrop.Top / aPref.getHeight();
909 tools::Long nRight = aPixel.getWidth() * rCrop.Right / aPref.getWidth();
910 tools::Long nBottom = aPixel.getHeight() * rCrop.Bottom / aPref.getHeight();
911 GstElement* pVideoFilter = gst_element_factory_make("videocrop", nullptr);
912 if (pVideoFilter)
913 {
914 g_object_set(G_OBJECT(pVideoFilter), "left", nLeft, nullptr);
915 g_object_set(G_OBJECT(pVideoFilter), "top", nTop, nullptr);
916 g_object_set(G_OBJECT(pVideoFilter), "right", nRight, nullptr);
917 g_object_set(G_OBJECT(pVideoFilter), "bottom", nBottom, nullptr);
918 g_object_set(G_OBJECT(mpPlaybin), "video-filter", pVideoFilter, nullptr);
919 }
920 }
921 }
922
923 if (!mbUseGtkSink)
924 {
925 mnWindowID = pEnvData->GetWindowHandle(pParentWindow->ImplGetFrame());
926 mpDisplay = pEnvData->pDisplay;
927 SAL_INFO( "avmedia.gstreamer", AVVERSION "set window id to " << static_cast<int>(mnWindowID) << " XOverlay " << mpXOverlay);
928 }
929 gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
930 if (!mbUseGtkSink && mpXOverlay)
931 gst_video_overlay_set_window_handle( mpXOverlay, mnWindowID );
932 }
933
934 return xRet;
935}
936
937uno::Reference< media::XFrameGrabber > SAL_CALL Player::createFrameGrabber()
938{
939 ::osl::MutexGuard aGuard(m_aMutex);
940 rtl::Reference<FrameGrabber> pFrameGrabber;
941 const awt::Size aPrefSize( getPreferredPlayerWindowSize() );
942
943 if( ( aPrefSize.Width > 0 ) && ( aPrefSize.Height > 0 ) )
944 pFrameGrabber = FrameGrabber::create( maURL );
945 SAL_INFO( "avmedia.gstreamer", AVVERSION "created FrameGrabber " << pFrameGrabber.get() );
946
947 return pFrameGrabber;
948}
949
950
952{
954}
955
956
957sal_Bool SAL_CALL Player::supportsService( const OUString& ServiceName )
958{
960}
961
962
963uno::Sequence< OUString > SAL_CALL Player::getSupportedServiceNames()
964{
966}
967
968} // namespace
969
970/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
bool mbInitialized
static ImplSVEvent * PostUserEvent(const Link< void *, void > &rLink, void *pCaller=nullptr, bool bReferenceLink=false)
Size GetPrefSize() const
bool IsNone() const
Size GetSizePixel(const OutputDevice *pRefDevice=nullptr) const
constexpr tools::Long getX() const
constexpr tools::Long getY() const
constexpr tools::Long getHeight() const
constexpr tools::Long getWidth() const
void * CreateGStreamerSink()
virtual const SystemEnvData * GetSystemData() const override
static rtl::Reference< FrameGrabber > create(std::u16string_view rURL)
virtual css::uno::Reference< css::media::XFrameGrabber > SAL_CALL createFrameGrabber() override
Definition: gstplayer.cxx:937
virtual void SAL_CALL setPlaybackLoop(sal_Bool bSet) override
Definition: gstplayer.cxx:723
GstElement * mpVolumeControl
Definition: gstplayer.hxx:84
virtual ~Player() override
Definition: gstplayer.cxx:325
bool create(const OUString &rURL)
Definition: gstplayer.cxx:607
virtual void SAL_CALL setMediaTime(double fTime) override
Definition: gstplayer.cxx:687
virtual void SAL_CALL stop() override
Definition: gstplayer.cxx:646
virtual sal_Bool SAL_CALL isPlaybackLoop() override
Definition: gstplayer.cxx:731
virtual void SAL_CALL start() override
Definition: gstplayer.cxx:633
virtual void SAL_CALL setVolumeDB(sal_Int16 nVolumeDB) override
Definition: gstplayer.cxx:769
void preparePlaybin(std::u16string_view rURL, GstElement *pSink)
Definition: gstplayer.cxx:552
virtual void SAL_CALL setMute(sal_Bool bSet) override
Definition: gstplayer.cxx:739
virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override
Definition: gstplayer.cxx:963
css::awt::Rectangle maArea
Definition: gstplayer.hxx:100
virtual sal_Bool SAL_CALL isPlaying() override
Definition: gstplayer.cxx:657
virtual double SAL_CALL getMediaTime() override
Definition: gstplayer.cxx:706
virtual sal_Bool SAL_CALL supportsService(const OUString &ServiceName) override
Definition: gstplayer.cxx:957
virtual double SAL_CALL getDuration() override
Definition: gstplayer.cxx:672
virtual css::awt::Size SAL_CALL getPreferredPlayerWindowSize() override
Definition: gstplayer.cxx:804
void processMessage(GstMessage *message)
Definition: gstplayer.cxx:387
GstVideoOverlay * mpXOverlay
Definition: gstplayer.hxx:95
virtual void SAL_CALL disposing() final override
Definition: gstplayer.cxx:333
virtual sal_Int16 SAL_CALL getVolumeDB() override
Definition: gstplayer.cxx:785
osl::Condition maSizeCondition
Definition: gstplayer.hxx:105
virtual OUString SAL_CALL getImplementationName() override
Definition: gstplayer.cxx:951
virtual sal_Bool SAL_CALL isMute() override
Definition: gstplayer.cxx:761
virtual css::uno::Reference< css::media::XPlayerWindow > SAL_CALL createPlayerWindow(const css::uno::Sequence< css::uno::Any > &aArguments) override
GstBusSyncReply processSyncMessage(GstMessage *message)
Definition: gstplayer.cxx:436
mutable::osl::Mutex m_aMutex
virtual Point GetPosPixel() const
SalFrame * ImplGetFrame() const
float u
#define AVVERSION
Definition: gstplayer.cxx:54
std::map< OString, std::set< rtl::Reference< Player > > > queued_
Definition: gstplayer.cxx:103
bool & flag_
Definition: gstplayer.cxx:69
constexpr OUStringLiteral AVMEDIA_GST_PLAYER_SERVICENAME
Definition: gstplayer.cxx:53
std::set< rtl::Reference< Player > > currentSources_
Definition: gstplayer.cxx:106
constexpr OUStringLiteral AVMEDIA_GST_PLAYER_IMPLEMENTATIONNAME
Definition: gstplayer.cxx:52
osl::Mutex mutex_
Definition: gstplayer.cxx:101
rtl::Reference< MissingPluginInstallerThread > currentThread_
Definition: gstplayer.cxx:104
bool inCleanUp_
Definition: gstplayer.cxx:108
std::set< OString > reported_
Definition: gstplayer.cxx:102
bool launchNewThread_
Definition: gstplayer.cxx:107
#define LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE
Definition: gstplayer.cxx:414
std::vector< OString > currentDetails_
Definition: gstplayer.cxx:105
std::mutex m_aMutex
#define FALSE
const char * name
void * p
#define SAL_WARN_IF(condition, area, stream)
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
def position(n=-1)
void SC_DLLPUBLIC join(const ScDocument *pDoc, ::std::vector< ScTokenRef > &rTokens, const ScTokenRef &pToken, const ScAddress &rPos)
static gboolean pipeline_bus_callback(GstBus *, GstMessage *message, gpointer data)
Definition: gstplayer.cxx:369
static GstBusSyncReply pipeline_bus_sync_handler(GstBus *, GstMessage *message, gpointer data)
Definition: gstplayer.cxx:379
static bool lcl_is_wayland_display_handle_need_context_message(GstMessage *msg)
Definition: gstplayer.cxx:416
::cppu::WeakComponentImplHelper< css::media::XPlayer, css::lang::XServiceInfo > GstPlayer_BASE
Definition: gstplayer.hxx:39
static GstContext * lcl_wayland_display_handle_context_new(void *display)
Definition: gstplayer.cxx:428
void set(css::uno::UnoInterfaceReference const &value)
bool CPPUHELPER_DLLPUBLIC supportsService(css::lang::XServiceInfo *implementation, rtl::OUString const &name)
int i
args
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
long Long
sal_Int32 h
sal_Int32 w
IMPL_STATIC_LINK(SwRetrievedInputStreamDataManager, LinkedInputStreamReady, void *, p, void)
double mnWidth
double mnHeight
Toolkit toolkit
sal_uIntPtr GetWindowHandle(const SalFrame *pReference) const
Platform platform
unsigned char sal_Bool
ResultType type