/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <sal/config.h>
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstddef>
#include <cstring>
#include <map>
#include <mutex>
#include <set>
#include <vector>
#include <math.h>
#include <com/sun/star/text/GraphicCrop.hpp>
#include <cppuhelper/supportsservice.hxx>
#include <sal/log.hxx>
#include <rtl/string.hxx>
#include <salhelper/thread.hxx>
#include <vcl/svapp.hxx>
#include <vcl/syschild.hxx>
#include <vcl/sysdata.hxx>
#include <vcl/graph.hxx>
#include <avmedia/mediaitem.hxx>
#include "gstplayer.hxx"
#include "gstframegrabber.hxx"
#include "gstwindow.hxx"
#include <gst/video/videooverlay.h>
#include <gst/pbutils/missing-plugins.h>
#include <gst/pbutils/pbutils.h>
#define AVVERSION "gst 1.0: "
using namespace ::com::sun::star;
namespace avmedia::gstreamer {
namespace {
class FlagGuard {
public:
explicit FlagGuard(bool & flag): flag_(flag) { flag_ = true; }
~FlagGuard() { flag_ = false; }
private:
bool & flag_;
};
class MissingPluginInstallerThread: public salhelper::Thread {
public:
MissingPluginInstallerThread(): Thread("MissingPluginInstaller") {}
private:
void execute() override;
};
class MissingPluginInstaller {
friend class MissingPluginInstallerThread;
public:
MissingPluginInstaller(): launchNewThread_(true), inCleanUp_(false) {}
~MissingPluginInstaller();
void report(rtl::Reference<Player> const & source, GstMessage * message);
// Player::~Player calls Player::disposing calls
// MissingPluginInstaller::detach, so do not take Player by rtl::Reference
// here (which would bump its refcount back from 0 to 1):
void detach(Player const * source);
private:
void processQueue();
DECL_STATIC_LINK(MissingPluginInstaller, launchUi, void*, void);
std::recursive_mutex mutex_;
std::set<OString> reported_;
std::map<OString, std::set<rtl::Reference<Player>>> queued_;
rtl::Reference<MissingPluginInstallerThread> currentThread_;
std::vector<OString> currentDetails_;
std::set<rtl::Reference<Player>> currentSources_;
bool launchNewThread_;
bool inCleanUp_;
};
MissingPluginInstaller::~MissingPluginInstaller() {
std::unique_lock g(mutex_);
SAL_WARN_IF(currentThread_.is(), "avmedia.gstreamer", "unjoined thread");
inCleanUp_ = true;
}
void MissingPluginInstaller::report(
rtl::Reference<Player> const & source, GstMessage * message)
{
// assert(gst_is_missing_plugin_message(message));
gchar * det = gst_missing_plugin_message_get_installer_detail(message);
if (det == nullptr) {
SAL_WARN(
"avmedia.gstreamer",
"gst_missing_plugin_message_get_installer_detail failed");
return;
}
std::size_t len = std::strlen(det);
if (len > SAL_MAX_INT32) {
SAL_WARN("avmedia.gstreamer", "detail string too long");
g_free(det);
return;
}
OString detStr(det, len);
g_free(det);
rtl::Reference<MissingPluginInstallerThread> join;
rtl::Reference<MissingPluginInstallerThread> launch;
{
std::unique_lock g(mutex_);
if (reported_.find(detStr) != reported_.end()) {
return;
}
auto & i = queued_[detStr];
bool fresh = i.empty();
i.insert(source);
if (!(fresh && launchNewThread_)) {
return;
}
join = currentThread_;
currentThread_ = new MissingPluginInstallerThread;
{
FlagGuard f(inCleanUp_);
currentSources_.clear();
}
processQueue();
launchNewThread_ = false;
launch = currentThread_;
}
if (join.is()) {
join->join();
}
launch->acquire();
Application::PostUserEvent(
LINK(this, MissingPluginInstaller, launchUi), launch.get());
}
void eraseSource(std::set<rtl::Reference<Player>> & set, Player const * source)
{
auto i = std::find_if(
set.begin(), set.end(),
[source](rtl::Reference<Player> const & el) {
return el.get() == source;
});
if (i != set.end()) {
set.erase(i);
}
}
void MissingPluginInstaller::detach(Player const * source) {
rtl::Reference<MissingPluginInstallerThread> join;
{
std::unique_lock g(mutex_);
if (inCleanUp_) {
// Guard against ~MissingPluginInstaller with erroneously un-joined
// currentThread_ (thus non-empty currentSources_) calling
// destructor of currentSources_, calling ~Player, calling here,
// which would use currentSources_ while it is already being
// destroyed:
return;
}
for (auto i = queued_.begin(); i != queued_.end();) {
eraseSource(i->second, source);
if (i->second.empty()) {
i = queued_.erase(i);
} else {
++i;
}
}
if (currentThread_.is()) {
assert(!currentSources_.empty());
eraseSource(currentSources_, source);
if (currentSources_.empty()) {
join = currentThread_;
currentThread_.clear();
launchNewThread_ = true;
}
}
}
if (join.is()) {
// missing cancellability of gst_install_plugins_sync
join->join();
}
}
void MissingPluginInstaller::processQueue() {
assert(!queued_.empty());
assert(currentDetails_.empty());
for (const auto& rEntry : queued_) {
reported_.insert(rEntry.first);
currentDetails_.push_back(rEntry.first);
currentSources_.insert(rEntry.second.begin(), rEntry.second.end());
}
queued_.clear();
}
IMPL_STATIC_LINK(MissingPluginInstaller, launchUi, void *, p, void)
{
MissingPluginInstallerThread* thread = static_cast<MissingPluginInstallerThread*>(p);
rtl::Reference<MissingPluginInstallerThread> ref(thread, SAL_NO_ACQUIRE);
gst_pb_utils_init();
// not thread safe; hopefully fine to consistently call from our event
// loop (which is the only reason to have this
// Application::PostUserEvent diversion, in case
// MissingPluginInstaller::report might be called from outside our event
// loop), and hopefully fine to call gst_is_missing_plugin_message and
// gst_missing_plugin_message_get_installer_detail before calling
// gst_pb_utils_init
ref->launch();
}
MissingPluginInstaller& TheMissingPluginInstaller()
{
static MissingPluginInstaller theInstaller;
return theInstaller;
}
void MissingPluginInstallerThread::execute() {
MissingPluginInstaller & inst = TheMissingPluginInstaller();
for (;;) {
std::vector<OString> details;
{
std::unique_lock g(inst.mutex_);
assert(!inst.currentDetails_.empty());
details.swap(inst.currentDetails_);
}
std::vector<char *> args;
args.reserve(details.size());
for (auto const& i : details)
{
args.push_back(const_cast<char *>(i.getStr()));
}
args.push_back(nullptr);
gst_install_plugins_sync(args.data(), nullptr);
{
std::unique_lock g(inst.mutex_);
if (inst.queued_.empty() || inst.launchNewThread_) {
inst.launchNewThread_ = true;
break;
}
inst.processQueue();
}
}
}
} // end anonymous namespace
Player::Player() :
GstPlayer_BASE( m_aMutex ),
mpPlaybin( nullptr ),
mpVolumeControl( nullptr ),
mbUseGtkSink( false ),
mbFakeVideo (false ),
mnUnmutedVolume( 0 ),
mbMuted( false ),
mbLooping( false ),
mbInitialized( false ),
mpDisplay( nullptr ),
mnWindowID( 0 ),
mpXOverlay( nullptr ),
mnDuration( 0 ),
mnWidth( 0 ),
mnHeight( 0 ),
mnWatchID( 0 ),
mbWatchID( false )
{
// Initialize GStreamer library
int argc = 1;
char name[] = "libreoffice";
char *arguments[] = { name };
char** argv = arguments;
GError* pError = nullptr;
mbInitialized = gst_init_check( &argc, &argv, &pError );
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::Player" );
if (pError != nullptr)
{
// TODO: throw an exception?
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::Player error '" << pError->message << "'" );
g_error_free (pError);
}
}
Player::~Player()
{
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::~Player" );
if( mbInitialized )
disposing();
}
void SAL_CALL Player::disposing()
{
TheMissingPluginInstaller().detach(this);
::osl::MutexGuard aGuard(m_aMutex);
stop();
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::disposing" );
// Release the elements and pipeline
if( mbInitialized )
{
if( mpPlaybin )
{
gst_element_set_state( mpPlaybin, GST_STATE_NULL );
g_object_unref( G_OBJECT( mpPlaybin ) );
mpPlaybin = nullptr;
mpVolumeControl = nullptr;
}
if( mpXOverlay ) {
g_object_unref( G_OBJECT ( mpXOverlay ) );
mpXOverlay = nullptr;
}
}
if (mbWatchID)
{
g_source_remove(mnWatchID);
mbWatchID = false;
}
}
static gboolean pipeline_bus_callback( GstBus *, GstMessage *message, gpointer data )
{
Player* pPlayer = static_cast<Player*>(data);
pPlayer->processMessage( message );
return true;
}
static GstBusSyncReply pipeline_bus_sync_handler( GstBus *, GstMessage * message, gpointer data )
{
Player* pPlayer = static_cast<Player*>(data);
return pPlayer->processSyncMessage( message );
}
void Player::processMessage( GstMessage *message )
{
switch( GST_MESSAGE_TYPE( message ) ) {
case GST_MESSAGE_EOS:
gst_element_set_state( mpPlaybin, GST_STATE_READY );
if (mbLooping)
start();
break;
case GST_MESSAGE_STATE_CHANGED:
if (message->src == GST_OBJECT(mpPlaybin))
{
GstState newstate, pendingstate;
gst_message_parse_state_changed (message, nullptr, &newstate, &pendingstate);
if (!mbUseGtkSink && newstate == GST_STATE_PAUSED &&
pendingstate == GST_STATE_VOID_PENDING && mpXOverlay)
{
gst_video_overlay_expose(mpXOverlay);
}
}
break;
default:
break;
}
}
#define LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE "GstWaylandDisplayHandleContextType"
static bool lcl_is_wayland_display_handle_need_context_message(GstMessage* msg)
{
g_return_val_if_fail(GST_IS_MESSAGE(msg), false);
if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_NEED_CONTEXT)
return false;
const gchar *type = nullptr;
if (!gst_message_parse_context_type(msg, &type))
return false;
return !g_strcmp0(type, LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE);
}
static GstContext* lcl_wayland_display_handle_context_new(void* display)
{
GstContext *context = gst_context_new(LCL_WAYLAND_DISPLAY_HANDLE_CONTEXT_TYPE, true);
gst_structure_set (gst_context_writable_structure (context),
"handle", G_TYPE_POINTER, display, nullptr);
return context;
}
GstBusSyncReply Player::processSyncMessage( GstMessage *message )
{
#if OSL_DEBUG_LEVEL > 0
if ( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ERROR )
{
GError* error;
gchar* error_debug;
gst_message_parse_error( message, &error, &error_debug );
SAL_WARN(
"avmedia.gstreamer",
"error: '" << error->message << "' debug: '"
<< error_debug << "'");
}
else if ( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_WARNING )
{
GError* error;
gchar* error_debug;
gst_message_parse_warning( message, &error, &error_debug );
SAL_WARN(
"avmedia.gstreamer",
"warning: '" << error->message << "' debug: '"
<< error_debug << "'");
}
else if ( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_INFO )
{
GError* error;
gchar* error_debug;
gst_message_parse_info( message, &error, &error_debug );
SAL_WARN(
"avmedia.gstreamer",
"info: '" << error->message << "' debug: '"
<< error_debug << "'");
}
#endif
if (!mbUseGtkSink)
{
if (gst_is_video_overlay_prepare_window_handle_message (message) )
{
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " processSyncMessage prepare window id: " <<
GST_MESSAGE_TYPE_NAME( message ) << " " << static_cast<int>(mnWindowID) );
if( mpXOverlay )
g_object_unref( G_OBJECT ( mpXOverlay ) );
g_object_set( GST_MESSAGE_SRC( message ), "force-aspect-ratio", FALSE, nullptr );
mpXOverlay = GST_VIDEO_OVERLAY( GST_MESSAGE_SRC( message ) );
g_object_ref( G_OBJECT ( mpXOverlay ) );
if ( mnWindowID != 0 )
{
gst_video_overlay_set_window_handle( mpXOverlay, mnWindowID );
gst_video_overlay_handle_events(mpXOverlay, 0); // Let the parent window handle events.
if (maArea.Width > 0 && maArea.Height > 0)
gst_video_overlay_set_render_rectangle(mpXOverlay, maArea.X, maArea.Y, maArea.Width, maArea.Height);
}
return GST_BUS_DROP;
}
else if (lcl_is_wayland_display_handle_need_context_message(message))
{
GstContext *context = lcl_wayland_display_handle_context_new(mpDisplay);
gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(message)), context);
return GST_BUS_DROP;
}
}
if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ASYNC_DONE ) {
if( mnDuration == 0) {
gint64 gst_duration = 0;
if( gst_element_query_duration( mpPlaybin, GST_FORMAT_TIME, &gst_duration) )
mnDuration = gst_duration;
}
if( mnWidth == 0 ) {
GstPad *pad = nullptr;
g_signal_emit_by_name( mpPlaybin, "get-video-pad", 0, &pad );
if( pad ) {
int w = 0, h = 0;
GstCaps *caps = gst_pad_get_current_caps( pad );
if( gst_structure_get( gst_caps_get_structure( caps, 0 ),
"width", G_TYPE_INT, &w,
"height", G_TYPE_INT, &h,
nullptr ) ) {
mnWidth = w;
mnHeight = h;
SAL_INFO( "avmedia.gstreamer", AVVERSION "queried size: " << mnWidth << "x" << mnHeight );
}
gst_caps_unref( caps );
g_object_unref( pad );
}
maSizeCondition.set();
}
} else if (gst_is_missing_plugin_message(message)) {
TheMissingPluginInstaller().report(this, message);
if( mnWidth == 0 ) {
// an error occurred, set condition so that OOo thread doesn't wait for us
maSizeCondition.set();
}
} else if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ERROR ) {
if( mnWidth == 0 ) {
// an error occurred, set condition so that OOo thread doesn't wait for us
maSizeCondition.set();
}
}
return GST_BUS_PASS;
}
void Player::preparePlaybin( std::u16string_view rURL, GstElement *pSink )
{
if (mpPlaybin != nullptr)
{
gst_element_set_state( mpPlaybin, GST_STATE_NULL );
g_object_unref( mpPlaybin );
}
mpPlaybin = gst_element_factory_make( "playbin", nullptr );
//tdf#96989 on systems with flat-volumes setting the volume directly on the
//playbin to 100% results in setting the global volume to 100% of the
//maximum. We expect to set as % of the current volume.
mpVolumeControl = gst_element_factory_make( "volume", nullptr );
GstElement *pAudioSink = gst_element_factory_make( "autoaudiosink", nullptr );
GstElement* pAudioOutput = gst_bin_new("audio-output-bin");
assert(pAudioOutput);
if (pAudioSink)
gst_bin_add(GST_BIN(pAudioOutput), pAudioSink);
if (mpVolumeControl)
{
gst_bin_add(GST_BIN(pAudioOutput), mpVolumeControl);
if (pAudioSink)
gst_element_link(mpVolumeControl, pAudioSink);
GstPad *pPad = gst_element_get_static_pad(mpVolumeControl, "sink");
gst_element_add_pad(GST_ELEMENT(pAudioOutput), gst_ghost_pad_new("sink", pPad));
gst_object_unref(GST_OBJECT(pPad));
}
g_object_set(G_OBJECT(mpPlaybin), "audio-sink", pAudioOutput, nullptr);
if( pSink != nullptr ) // used for getting preferred size etc.
{
g_object_set( G_OBJECT( mpPlaybin ), "video-sink", pSink, nullptr );
mbFakeVideo = true;
}
else
mbFakeVideo = false;
OString ascURL = OUStringToOString( rURL, RTL_TEXTENCODING_UTF8 );
g_object_set( G_OBJECT( mpPlaybin ), "uri", ascURL.getStr() , nullptr );
GstBus *pBus = gst_element_get_bus( mpPlaybin );
if (mbWatchID)
{
g_source_remove(mnWatchID);
mbWatchID = false;
}
mnWatchID = gst_bus_add_watch( pBus, pipeline_bus_callback, this );
mbWatchID = true;
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " set sync handler" );
gst_bus_set_sync_handler( pBus, pipeline_bus_sync_handler, this, nullptr );
g_object_unref( pBus );
}
bool Player::create( const OUString& rURL )
{
bool bRet = false;
// create all the elements and link them
SAL_INFO( "avmedia.gstreamer", "create player, URL: '" << rURL << "'" );
if( mbInitialized && !rURL.isEmpty() )
{
// fakesink for pre-roll & sizing ...
preparePlaybin( rURL, gst_element_factory_make( "fakesink", nullptr ) );
gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
bRet = true;
}
if( bRet )
maURL = rURL;
else
maURL.clear();
return bRet;
}
void SAL_CALL Player::start()
{
::osl::MutexGuard aGuard(m_aMutex);
// set the pipeline state to READY and run the loop
if( mbInitialized && mpPlaybin != nullptr )
{
gst_element_set_state( mpPlaybin, GST_STATE_PLAYING );
}
SAL_INFO( "avmedia.gstreamer", AVVERSION "start " << mpPlaybin );
}
void SAL_CALL Player::stop()
{
::osl::MutexGuard aGuard(m_aMutex);
// set the pipeline in PAUSED STATE
if( mpPlaybin )
gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
SAL_INFO( "avmedia.gstreamer", AVVERSION "stop " << mpPlaybin );
}
sal_Bool SAL_CALL Player::isPlaying()
{
::osl::MutexGuard aGuard(m_aMutex);
bool bRet = false;
// return whether the pipeline target is PLAYING STATE or not
if (mbInitialized && mpPlaybin)
{
bRet = GST_STATE_TARGET(mpPlaybin) == GST_STATE_PLAYING;
}
return bRet;
}
double SAL_CALL Player::getDuration()
{
::osl::MutexGuard aGuard(m_aMutex);
// slideshow checks for non-zero duration, so cheat here
double duration = 0.3;
if( mpPlaybin && mnDuration > 0 ) {
duration = mnDuration / GST_SECOND;
}
return duration;
}
void SAL_CALL Player::setMediaTime( double fTime )
{
::osl::MutexGuard aGuard(m_aMutex);
if( !mpPlaybin )
return;
gint64 gst_position = llround (fTime * GST_SECOND);
gst_element_seek( mpPlaybin, 1.0,
GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH,
GST_SEEK_TYPE_SET, gst_position,
GST_SEEK_TYPE_NONE, 0 );
SAL_INFO( "avmedia.gstreamer", AVVERSION "seek to: " << gst_position << " ns original: " << fTime << " s" );
}
double SAL_CALL Player::getMediaTime()
{
::osl::MutexGuard aGuard(m_aMutex);
double position = 0.0;
if( mpPlaybin ) {
// get current position in the stream
gint64 gst_position;
if( gst_element_query_position( mpPlaybin, GST_FORMAT_TIME, &gst_position ) )
position = gst_position / GST_SECOND;
}
return position;
}
void SAL_CALL Player::setPlaybackLoop( sal_Bool bSet )
{
::osl::MutexGuard aGuard(m_aMutex);
// TODO check how to do with GST
mbLooping = bSet;
}
sal_Bool SAL_CALL Player::isPlaybackLoop()
{
::osl::MutexGuard aGuard(m_aMutex);
// TODO check how to do with GST
return mbLooping;
}
void SAL_CALL Player::setMute( sal_Bool bSet )
{
::osl::MutexGuard aGuard(m_aMutex);
SAL_INFO( "avmedia.gstreamer", AVVERSION "set mute: " << bSet << " muted: " << mbMuted << " unmuted volume: " << mnUnmutedVolume );
// change the volume to 0 or the unmuted volume
if (mpVolumeControl && mbMuted != bool(bSet))
{
double nVolume = mnUnmutedVolume;
if( bSet )
{
nVolume = 0.0;
}
g_object_set( G_OBJECT( mpVolumeControl ), "volume", nVolume, nullptr );
mbMuted = bSet;
}
}
sal_Bool SAL_CALL Player::isMute()
{
::osl::MutexGuard aGuard(m_aMutex);
return mbMuted;
}
void SAL_CALL Player::setVolumeDB( sal_Int16 nVolumeDB )
{
::osl::MutexGuard aGuard(m_aMutex);
mnUnmutedVolume = pow( 10.0, nVolumeDB / 20.0 );
SAL_INFO( "avmedia.gstreamer", AVVERSION "set volume: " << nVolumeDB << " gst volume: " << mnUnmutedVolume );
// change volume
if (mpVolumeControl && !mbMuted)
{
g_object_set( G_OBJECT( mpVolumeControl ), "volume", mnUnmutedVolume, nullptr );
}
}
sal_Int16 SAL_CALL Player::getVolumeDB()
{
::osl::MutexGuard aGuard(m_aMutex);
sal_Int16 nVolumeDB(0);
if (mpVolumeControl)
{
double nGstVolume = 0.0;
g_object_get( G_OBJECT( mpVolumeControl ), "volume", &nGstVolume, nullptr );
nVolumeDB = static_cast<sal_Int16>( 20.0*log10 ( nGstVolume ) );
}
return nVolumeDB;
}
awt::Size SAL_CALL Player::getPreferredPlayerWindowSize()
{
::osl::MutexGuard aGuard(m_aMutex);
awt::Size aSize( 0, 0 );
if( maURL.isEmpty() )
{
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::getPreferredPlayerWindowSize - empty URL => 0x0" );
return aSize;
}
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " pre-Player::getPreferredPlayerWindowSize, member " << mnWidth << "x" << mnHeight );
osl::Condition::Result aResult = maSizeCondition.wait( std::chrono::seconds(10) );
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::getPreferredPlayerWindowSize after waitCondition " << aResult << ", member " << mnWidth << "x" << mnHeight );
if( mnWidth != 0 && mnHeight != 0 ) {
aSize.Width = mnWidth;
aSize.Height = mnHeight;
}
return aSize;
}
uno::Reference< ::media::XPlayerWindow > SAL_CALL Player::createPlayerWindow( const uno::Sequence< uno::Any >& rArguments )
{
::osl::MutexGuard aGuard(m_aMutex);
uno::Reference< ::media::XPlayerWindow > xRet;
if (rArguments.getLength() > 1)
rArguments[1] >>= maArea;
awt::Size aSize = getPreferredPlayerWindowSize();
if( mbFakeVideo )
preparePlaybin( maURL, nullptr );
SAL_INFO( "avmedia.gstreamer", AVVERSION "Player::createPlayerWindow " << aSize.Width << "x" << aSize.Height << " length: " << rArguments.getLength() );
if( aSize.Width > 0 && aSize.Height > 0 )
{
if (rArguments.getLength() <= 2)
{
xRet = new ::avmedia::gstreamer::Window;
return xRet;
}
sal_IntPtr pIntPtr = 0;
rArguments[ 2 ] >>= pIntPtr;
SystemChildWindow *pParentWindow = reinterpret_cast< SystemChildWindow* >( pIntPtr );
if (!pParentWindow)
return nullptr;
const SystemEnvData* pEnvData = pParentWindow->GetSystemData();
if (!pEnvData)
return nullptr;
// tdf#124027: the position of embedded window is identical w/ the position
// of media object in all other vclplugs (kf5, gen), in gtk3 w/o gtksink it
// needs to be translated
if (pEnvData->toolkit == SystemEnvData::Toolkit::Gtk)
{
Point aPoint = pParentWindow->GetPosPixel();
maArea.X = aPoint.getX();
maArea.Y = aPoint.getY();
}
mbUseGtkSink = false;
GstElement *pVideosink = static_cast<GstElement*>(pParentWindow->CreateGStreamerSink());
if (pVideosink)
{
if (pEnvData->toolkit == SystemEnvData::Toolkit::Gtk)
mbUseGtkSink = true;
}
else
{
if (pEnvData->platform == SystemEnvData::Platform::Wayland)
pVideosink = gst_element_factory_make("waylandsink", "video-output");
else
pVideosink = gst_element_factory_make("autovideosink", "video-output");
if (!pVideosink)
return nullptr;
}
xRet = new ::avmedia::gstreamer::Window;
g_object_set(G_OBJECT(mpPlaybin), "video-sink", pVideosink, nullptr);
g_object_set(G_OBJECT(mpPlaybin), "force-aspect-ratio", FALSE, nullptr);
if ((rArguments.getLength() >= 4) && (rArguments[3] >>= pIntPtr) && pIntPtr)
{
auto pItem = reinterpret_cast<const avmedia::MediaItem*>(pIntPtr);
Graphic aGraphic = pItem->getGraphic();
const text::GraphicCrop& rCrop = pItem->getCrop();
if (!aGraphic.IsNone() && (rCrop.Bottom > 0 || rCrop.Left > 0 || rCrop.Right > 0 || rCrop.Top > 0))
{
// The media item has a non-empty cropping set. Try to crop the video accordingly.
Size aPref = aGraphic.GetPrefSize();
Size aPixel = aGraphic.GetSizePixel();
tools::Long nLeft = aPixel.getWidth() * rCrop.Left / aPref.getWidth();
tools::Long nTop = aPixel.getHeight() * rCrop.Top / aPref.getHeight();
tools::Long nRight = aPixel.getWidth() * rCrop.Right / aPref.getWidth();
tools::Long nBottom = aPixel.getHeight() * rCrop.Bottom / aPref.getHeight();
GstElement* pVideoFilter = gst_element_factory_make("videocrop", nullptr);
if (pVideoFilter)
{
g_object_set(G_OBJECT(pVideoFilter), "left", nLeft, nullptr);
g_object_set(G_OBJECT(pVideoFilter), "top", nTop, nullptr);
g_object_set(G_OBJECT(pVideoFilter), "right", nRight, nullptr);
g_object_set(G_OBJECT(pVideoFilter), "bottom", nBottom, nullptr);
g_object_set(G_OBJECT(mpPlaybin), "video-filter", pVideoFilter, nullptr);
}
}
}
if (!mbUseGtkSink)
{
mnWindowID = pEnvData->GetWindowHandle(pParentWindow->ImplGetFrame());
mpDisplay = pEnvData->pDisplay;
SAL_INFO( "avmedia.gstreamer", AVVERSION "set window id to " << static_cast<int>(mnWindowID) << " XOverlay " << mpXOverlay);
}
gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
if (!mbUseGtkSink && mpXOverlay)
gst_video_overlay_set_window_handle( mpXOverlay, mnWindowID );
}
return xRet;
}
uno::Reference< media::XFrameGrabber > SAL_CALL Player::createFrameGrabber()
{
::osl::MutexGuard aGuard(m_aMutex);
rtl::Reference<FrameGrabber> pFrameGrabber;
const awt::Size aPrefSize( getPreferredPlayerWindowSize() );
if( ( aPrefSize.Width > 0 ) && ( aPrefSize.Height > 0 ) )
pFrameGrabber = FrameGrabber::create( maURL );
SAL_INFO( "avmedia.gstreamer", AVVERSION "created FrameGrabber " << pFrameGrabber.get() );
return pFrameGrabber;
}
OUString SAL_CALL Player::getImplementationName()
{
return u"com.sun.star.comp.avmedia.Player_GStreamer"_ustr;
}
sal_Bool SAL_CALL Player::supportsService( const OUString& ServiceName )
{
return cppu::supportsService(this, ServiceName);
}
uno::Sequence< OUString > SAL_CALL Player::getSupportedServiceNames()
{
return { u"com.sun.star.media.Player_GStreamer"_ustr };
}
} // namespace
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V636 The expression was implicitly cast from 'long' type to 'double' type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) / Y;.
↑ V636 The expression was implicitly cast from 'long' type to 'double' type. Consider utilizing an explicit type cast to avoid the loss of a fractional part. An example: double A = (double)(X) / Y;.
↑ V1019 Compound assignment expression 'rArguments[3] >>= pIntPtr' is used inside condition.