106ea87205
...which was introduced with3ead3ad52f
"Gradually typed Link" to distinguish the new, typed versions from the old, untyped ones, but is no longer necessary since382eb1a23c
"remove untyped Link<>" removed the old versions. Change-Id: I494025df486a16a45861fcd8192dfe0275b1103c
1002 lines
30 KiB
C++
1002 lines
30 KiB
C++
/* -*- 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 <set>
|
|
#include <vector>
|
|
#include <math.h>
|
|
|
|
#include <cppuhelper/supportsservice.hxx>
|
|
|
|
#include <rtl/string.hxx>
|
|
#include <salhelper/thread.hxx>
|
|
#include <vcl/svapp.hxx>
|
|
#include <vcl/syschild.hxx>
|
|
#include <vcl/sysdata.hxx>
|
|
|
|
#include "gstplayer.hxx"
|
|
#include "gstframegrabber.hxx"
|
|
#include "gstwindow.hxx"
|
|
|
|
#ifdef AVMEDIA_GST_0_10
|
|
# define AVMEDIA_GST_PLAYER_IMPLEMENTATIONNAME "com.sun.star.comp.avmedia.Player_GStreamer_0_10"
|
|
# define AVMEDIA_GST_PLAYER_SERVICENAME "com.sun.star.media.Player_GStreamer_0_10"
|
|
#else
|
|
# include <gst/video/videooverlay.h>
|
|
# define AVMEDIA_GST_PLAYER_IMPLEMENTATIONNAME "com.sun.star.comp.avmedia.Player_GStreamer"
|
|
# define AVMEDIA_GST_PLAYER_SERVICENAME "com.sun.star.media.Player_GStreamer"
|
|
#endif
|
|
|
|
#include <gst/pbutils/missing-plugins.h>
|
|
#include <gst/pbutils/pbutils.h>
|
|
|
|
#ifdef AVMEDIA_GST_0_10
|
|
# define AVVERSION "gst 0.10: "
|
|
#else
|
|
# define AVVERSION "gst 1.0: "
|
|
#endif
|
|
|
|
using namespace ::com::sun::star;
|
|
|
|
namespace avmedia { namespace 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);
|
|
|
|
osl::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() {
|
|
osl::MutexGuard 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_uInt32(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;
|
|
{
|
|
osl::MutexGuard 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;
|
|
{
|
|
osl::MutexGuard 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 (auto i = queued_.begin(); i != queued_.end(); ++i) {
|
|
reported_.insert(i->first);
|
|
currentDetails_.push_back(i->first);
|
|
currentSources_.insert(i->second.begin(), i->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();
|
|
}
|
|
|
|
|
|
struct TheMissingPluginInstaller:
|
|
public rtl::Static<MissingPluginInstaller, TheMissingPluginInstaller>
|
|
{};
|
|
|
|
|
|
void MissingPluginInstallerThread::execute() {
|
|
MissingPluginInstaller & inst = TheMissingPluginInstaller::get();
|
|
for (;;) {
|
|
std::vector<OString> details;
|
|
{
|
|
osl::MutexGuard g(inst.mutex_);
|
|
assert(!inst.currentDetails_.empty());
|
|
details.swap(inst.currentDetails_);
|
|
}
|
|
std::vector<char *> args;
|
|
for (auto const & i: details) {
|
|
args.push_back(const_cast<char *>(i.getStr()));
|
|
}
|
|
args.push_back(nullptr);
|
|
gst_install_plugins_sync(args.data(), nullptr);
|
|
{
|
|
osl::MutexGuard 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 ),
|
|
#if defined(ENABLE_GTKSINK)
|
|
mpGtkWidget( nullptr ),
|
|
#endif
|
|
mbUseGtkSink( false ),
|
|
mbFakeVideo (false ),
|
|
mnUnmutedVolume( 0 ),
|
|
mbPlayPending ( false ),
|
|
mbMuted( false ),
|
|
mbLooping( false ),
|
|
mbInitialized( false ),
|
|
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::get().detach(this);
|
|
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
stop();
|
|
|
|
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " Player::disposing" );
|
|
|
|
// Release the elements and pipeline
|
|
if( mbInitialized )
|
|
{
|
|
#if defined(ENABLE_GTKSINK)
|
|
if (mpGtkWidget)
|
|
{
|
|
gtk_widget_destroy(mpGtkWidget);
|
|
mpGtkWidget = nullptr;
|
|
}
|
|
#endif
|
|
|
|
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 );
|
|
mbPlayPending = false;
|
|
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);
|
|
}
|
|
|
|
if (mbPlayPending)
|
|
mbPlayPending = ((newstate == GST_STATE_READY) || (newstate == GST_STATE_PAUSED));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static gboolean wrap_element_query_position (GstElement *element, GstFormat format, gint64 *cur)
|
|
{
|
|
#ifdef AVMEDIA_GST_0_10
|
|
GstFormat my_format = format;
|
|
return gst_element_query_position( element, &my_format, cur) && my_format == format && *cur > 0;
|
|
#else
|
|
return gst_element_query_position( element, format, cur );
|
|
#endif
|
|
}
|
|
|
|
|
|
static gboolean wrap_element_query_duration (GstElement *element, GstFormat format, gint64 *duration)
|
|
{
|
|
#ifdef AVMEDIA_GST_0_10
|
|
GstFormat my_format = format;
|
|
return gst_element_query_duration( element, &my_format, duration) && my_format == format && *duration > 0;
|
|
#else
|
|
return gst_element_query_duration( element, format, duration );
|
|
#endif
|
|
}
|
|
|
|
|
|
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 << "'");
|
|
}
|
|
#endif
|
|
|
|
if (!mbUseGtkSink)
|
|
{
|
|
#ifdef AVMEDIA_GST_0_10
|
|
if (message->structure &&
|
|
!strcmp( gst_structure_get_name( message->structure ), "prepare-xwindow-id" ) )
|
|
#else
|
|
if (gst_is_video_overlay_prepare_window_handle_message (message) )
|
|
#endif
|
|
{
|
|
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " processSyncMessage prepare window id: " <<
|
|
GST_MESSAGE_TYPE_NAME( message ) << " " << (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 );
|
|
return GST_BUS_DROP;
|
|
}
|
|
}
|
|
|
|
#ifdef AVMEDIA_GST_0_10
|
|
if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_STATE_CHANGED ) {
|
|
if( message->src == GST_OBJECT( mpPlaybin ) ) {
|
|
GstState newstate, pendingstate;
|
|
|
|
gst_message_parse_state_changed (message, nullptr, &newstate, &pendingstate);
|
|
|
|
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " state change received, new state " << (int)newstate << " pending " << (int)pendingstate );
|
|
if( newstate == GST_STATE_PAUSED &&
|
|
pendingstate == GST_STATE_VOID_PENDING ) {
|
|
|
|
SAL_INFO( "avmedia.gstreamer", AVVERSION << this << " change to paused received" );
|
|
|
|
if( mnDuration == 0) {
|
|
gint64 gst_duration = 0;
|
|
if( wrap_element_query_duration( mpPlaybin, GST_FORMAT_TIME, &gst_duration) )
|
|
mnDuration = gst_duration;
|
|
}
|
|
|
|
if( mnWidth == 0 ) {
|
|
GList *pStreamInfo = nullptr;
|
|
|
|
g_object_get( G_OBJECT( mpPlaybin ), "stream-info", &pStreamInfo, nullptr );
|
|
|
|
for ( ; pStreamInfo != nullptr; pStreamInfo = pStreamInfo->next) {
|
|
GObject *pInfo = G_OBJECT( pStreamInfo->data );
|
|
|
|
if( !pInfo )
|
|
continue;
|
|
|
|
int nType;
|
|
g_object_get( pInfo, "type", &nType, nullptr );
|
|
GEnumValue *pValue = g_enum_get_value( G_PARAM_SPEC_ENUM( g_object_class_find_property( G_OBJECT_GET_CLASS( pInfo ), "type" ) )->enum_class,
|
|
nType );
|
|
|
|
if( !g_ascii_strcasecmp( pValue->value_nick, "video" ) ) {
|
|
GstStructure *pStructure;
|
|
GstPad *pPad;
|
|
|
|
g_object_get( pInfo, "object", &pPad, nullptr );
|
|
pStructure = gst_caps_get_structure( GST_PAD_CAPS( pPad ), 0 );
|
|
if( pStructure ) {
|
|
gst_structure_get_int( pStructure, "width", &mnWidth );
|
|
gst_structure_get_int( pStructure, "height", &mnHeight );
|
|
SAL_INFO( "avmedia.gstreamer", AVVERSION "queried size: " << mnWidth << "x" << mnHeight );
|
|
}
|
|
g_object_unref (pPad);
|
|
}
|
|
}
|
|
|
|
maSizeCondition.set();
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
// We get to use the exciting new playbin2 ! (now known as playbin)
|
|
if( GST_MESSAGE_TYPE( message ) == GST_MESSAGE_ASYNC_DONE ) {
|
|
if( mnDuration == 0) {
|
|
gint64 gst_duration = 0;
|
|
if( wrap_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();
|
|
}
|
|
#endif // AVMEDIA_GST_0_10
|
|
} else if (gst_is_missing_plugin_message(message)) {
|
|
TheMissingPluginInstaller::get().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( const OUString& rURL, GstElement *pSink )
|
|
{
|
|
#if defined(ENABLE_GTKSINK)
|
|
if (mpGtkWidget)
|
|
{
|
|
gtk_widget_destroy(mpGtkWidget);
|
|
mpGtkWidget = nullptr;
|
|
}
|
|
#endif
|
|
|
|
if (mpPlaybin != nullptr)
|
|
{
|
|
gst_element_set_state( mpPlaybin, GST_STATE_NULL );
|
|
mbPlayPending = false;
|
|
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 volums 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");
|
|
gst_bin_add_many(GST_BIN(pAudioOutput), mpVolumeControl, pAudioSink, nullptr);
|
|
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" );
|
|
#ifdef AVMEDIA_GST_0_10
|
|
gst_bus_set_sync_handler( pBus, pipeline_bus_sync_handler, this );
|
|
#else
|
|
gst_bus_set_sync_handler( pBus, pipeline_bus_sync_handler, this, nullptr );
|
|
#endif
|
|
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 );
|
|
mbPlayPending = false;
|
|
|
|
bRet = true;
|
|
}
|
|
|
|
if( bRet )
|
|
maURL = rURL;
|
|
else
|
|
maURL.clear();
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
void SAL_CALL Player::start()
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
// set the pipeline state to READY and run the loop
|
|
if( mbInitialized && nullptr != mpPlaybin )
|
|
{
|
|
gst_element_set_state( mpPlaybin, GST_STATE_PLAYING );
|
|
mbPlayPending = true;
|
|
}
|
|
}
|
|
|
|
|
|
void SAL_CALL Player::stop()
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
// set the pipeline in PAUSED STATE
|
|
if( mpPlaybin )
|
|
gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
|
|
|
|
mbPlayPending = false;
|
|
SAL_INFO( "avmedia.gstreamer", AVVERSION "stop " << mpPlaybin );
|
|
}
|
|
|
|
|
|
sal_Bool SAL_CALL Player::isPlaying()
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
bool bRet = mbPlayPending;
|
|
|
|
// return whether the pipeline is in PLAYING STATE or not
|
|
if( !mbPlayPending && mbInitialized && mpPlaybin )
|
|
{
|
|
bRet = GST_STATE_PLAYING == GST_STATE( mpPlaybin );
|
|
}
|
|
|
|
SAL_INFO( "avmedia.gstreamer", AVVERSION "isPlaying " << bRet );
|
|
|
|
return bRet;
|
|
}
|
|
|
|
|
|
double SAL_CALL Player::getDuration()
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
// slideshow checks for non-zero duration, so cheat here
|
|
double duration = 0.01;
|
|
|
|
if( mpPlaybin && mnDuration > 0 ) {
|
|
duration = mnDuration / GST_SECOND;
|
|
}
|
|
|
|
return duration;
|
|
}
|
|
|
|
|
|
void SAL_CALL Player::setMediaTime( double fTime )
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
if( mpPlaybin ) {
|
|
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 );
|
|
if( !isPlaying() )
|
|
gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
|
|
|
|
SAL_INFO( "avmedia.gstreamer", AVVERSION "seek to: " << gst_position << " ns original: " << fTime << " s" );
|
|
}
|
|
}
|
|
|
|
|
|
double SAL_CALL Player::getMediaTime()
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
double position = 0.0;
|
|
|
|
if( mpPlaybin ) {
|
|
// get current position in the stream
|
|
gint64 gst_position;
|
|
if( wrap_element_query_position( mpPlaybin, GST_FORMAT_TIME, &gst_position ) )
|
|
position = gst_position / GST_SECOND;
|
|
}
|
|
|
|
return position;
|
|
}
|
|
|
|
|
|
void SAL_CALL Player::setPlaybackLoop( sal_Bool bSet )
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
// TODO check how to do with GST
|
|
mbLooping = bSet;
|
|
}
|
|
|
|
|
|
sal_Bool SAL_CALL Player::isPlaybackLoop()
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
// TODO check how to do with GST
|
|
return mbLooping;
|
|
}
|
|
|
|
|
|
void SAL_CALL Player::setMute( sal_Bool bSet )
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::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( mpPlaybin && 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()
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
return mbMuted;
|
|
}
|
|
|
|
|
|
void SAL_CALL Player::setVolumeDB( sal_Int16 nVolumeDB )
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::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( !mbMuted && mpPlaybin )
|
|
{
|
|
g_object_set( G_OBJECT( mpVolumeControl ), "volume", mnUnmutedVolume, nullptr );
|
|
}
|
|
}
|
|
|
|
|
|
sal_Int16 SAL_CALL Player::getVolumeDB()
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
sal_Int16 nVolumeDB(0);
|
|
|
|
if( mpPlaybin ) {
|
|
double nGstVolume = 0.0;
|
|
|
|
g_object_get( G_OBJECT( mpVolumeControl ), "volume", &nGstVolume, nullptr );
|
|
|
|
nVolumeDB = (sal_Int16) ( 20.0*log10 ( nGstVolume ) );
|
|
}
|
|
|
|
return nVolumeDB;
|
|
}
|
|
|
|
|
|
awt::Size SAL_CALL Player::getPreferredPlayerWindowSize()
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::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 )
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
|
|
uno::Reference< ::media::XPlayerWindow > xRet;
|
|
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 )
|
|
{
|
|
::avmedia::gstreamer::Window* pWindow = new ::avmedia::gstreamer::Window;
|
|
|
|
xRet = pWindow;
|
|
|
|
if( rArguments.getLength() > 2 )
|
|
{
|
|
sal_IntPtr pIntPtr = 0;
|
|
rArguments[ 2 ] >>= pIntPtr;
|
|
SystemChildWindow *pParentWindow = reinterpret_cast< SystemChildWindow* >( pIntPtr );
|
|
const SystemEnvData* pEnvData = pParentWindow ? pParentWindow->GetSystemData() : nullptr;
|
|
OSL_ASSERT(pEnvData);
|
|
if (pEnvData)
|
|
{
|
|
#if defined(ENABLE_GTKSINK)
|
|
GstElement *pVideosink = g_strcmp0(pEnvData->pToolkit, "gtk3") == 0 ?
|
|
gst_element_factory_make("gtksink", "gtksink") : nullptr;
|
|
if (pVideosink)
|
|
{
|
|
mbUseGtkSink = true;
|
|
g_object_get(pVideosink, "widget", &mpGtkWidget, nullptr);
|
|
GtkWidget *pParent = static_cast<GtkWidget*>(pEnvData->pWidget);
|
|
gtk_container_add (GTK_CONTAINER(pParent), mpGtkWidget);
|
|
|
|
g_object_set( G_OBJECT( mpPlaybin ), "video-sink", pVideosink, nullptr);
|
|
g_object_set( G_OBJECT( mpPlaybin ), "force-aspect-ratio", FALSE, nullptr);
|
|
|
|
gtk_widget_show_all (pParent);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
mbUseGtkSink = false;
|
|
mnWindowID = pEnvData->aWindow;
|
|
SAL_INFO( "avmedia.gstreamer", AVVERSION "set window id to " << (int)mnWindowID << " XOverlay " << mpXOverlay);
|
|
gst_element_set_state( mpPlaybin, GST_STATE_PAUSED );
|
|
if ( mpXOverlay != nullptr )
|
|
gst_video_overlay_set_window_handle( mpXOverlay, mnWindowID );
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return xRet;
|
|
}
|
|
|
|
|
|
uno::Reference< media::XFrameGrabber > SAL_CALL Player::createFrameGrabber()
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
FrameGrabber* pFrameGrabber = nullptr;
|
|
const awt::Size aPrefSize( getPreferredPlayerWindowSize() );
|
|
|
|
if( ( aPrefSize.Width > 0 ) && ( aPrefSize.Height > 0 ) )
|
|
pFrameGrabber = FrameGrabber::create( maURL );
|
|
SAL_INFO( "avmedia.gstreamer", AVVERSION "created FrameGrabber " << pFrameGrabber );
|
|
|
|
return pFrameGrabber;
|
|
}
|
|
|
|
|
|
OUString SAL_CALL Player::getImplementationName()
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
return OUString( AVMEDIA_GST_PLAYER_IMPLEMENTATIONNAME );
|
|
}
|
|
|
|
|
|
sal_Bool SAL_CALL Player::supportsService( const OUString& ServiceName )
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
return cppu::supportsService(this, ServiceName);
|
|
}
|
|
|
|
|
|
uno::Sequence< OUString > SAL_CALL Player::getSupportedServiceNames()
|
|
throw (uno::RuntimeException, std::exception)
|
|
{
|
|
return { AVMEDIA_GST_PLAYER_SERVICENAME };
|
|
}
|
|
|
|
} // namespace gstreamer
|
|
} // namespace avmedia
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|