tdf#62408 tdf#159292 Impress: Introduce Microsoft Media Foundation APIs
on Windows to support playback of common codecs. Managing video playback with MFPlay: https://learn.microsoft.com/en-us/windows/win32/medfound/getting-started-with-mfplay Using MFCreateSourceReaderFromURL for getting media informations. Supported video/audio file formats (without any additional codec) should be the same as in PowerPoint: https://support.microsoft.com/en-us/office/video-and-audio-file-formats-supported-in-powerpoint-d8b12450-26db-4c7b-a5c1-593d3418fb59 Change-Id: I6a32f3619b6985753122a17b863e09b3a12ecc0e Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187384 Tested-by: Jenkins Reviewed-by: Balazs Varga <balazs.varga@collabora.com>
This commit is contained in:
@@ -28,6 +28,7 @@ $(eval $(call gb_Library_use_libraries,avmediawin,\
|
||||
tl \
|
||||
utl \
|
||||
vcl \
|
||||
avmedia \
|
||||
))
|
||||
|
||||
$(eval $(call gb_Library_use_system_win32_libs,avmediawin,\
|
||||
@@ -35,6 +36,13 @@ $(eval $(call gb_Library_use_system_win32_libs,avmediawin,\
|
||||
ole32 \
|
||||
oleaut32 \
|
||||
strmiids \
|
||||
propsys \
|
||||
shlwapi \
|
||||
mf \
|
||||
mfuuid \
|
||||
mfplat \
|
||||
mfplay \
|
||||
mfreadwrite \
|
||||
))
|
||||
|
||||
$(eval $(call gb_Library_add_exception_objects,avmediawin,\
|
||||
|
||||
@@ -21,14 +21,16 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <prewin.h>
|
||||
#include <postwin.h>
|
||||
#include <objbase.h>
|
||||
#include <strmif.h>
|
||||
#include <Amvideo.h>
|
||||
#include "interface.hxx"
|
||||
#include <uuids.h>
|
||||
|
||||
// Media Foundation headers
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
|
||||
#include "framegrabber.hxx"
|
||||
#include "player.hxx"
|
||||
|
||||
@@ -37,147 +39,166 @@
|
||||
#include <tools/stream.hxx>
|
||||
#include <vcl/graph.hxx>
|
||||
#include <vcl/dibtools.hxx>
|
||||
#include <vcl/BitmapTools.hxx>
|
||||
#include <o3tl/char16_t2wchar_t.hxx>
|
||||
#include <systools/win32/oleauto.hxx>
|
||||
|
||||
constexpr OUStringLiteral AVMEDIA_WIN_FRAMEGRABBER_IMPLEMENTATIONNAME = u"com.sun.star.comp.avmedia.FrameGrabber_DirectX";
|
||||
constexpr OUString AVMEDIA_WIN_FRAMEGRABBER_SERVICENAME = u"com.sun.star.media.FrameGrabber_DirectX"_ustr;
|
||||
constexpr LONGLONG SEEK_TOLERANCE = 10000000;
|
||||
constexpr LONGLONG MAX_FRAMES_TO_SKIP = 10;
|
||||
|
||||
using namespace ::com::sun::star;
|
||||
|
||||
namespace avmedia::win {
|
||||
|
||||
|
||||
FrameGrabber::FrameGrabber()
|
||||
FrameGrabber::FrameGrabber( const OUString& rURL, UINT32 nFrameWidth, UINT32 nFrameHeight )
|
||||
: sal::systools::CoInitializeGuard(COINIT_APARTMENTTHREADED, false,
|
||||
sal::systools::CoInitializeGuard::WhenFailed::NoThrow)
|
||||
{
|
||||
maURL = rURL;
|
||||
mnFrameWidth = nFrameWidth;
|
||||
mnFrameHeight = nFrameHeight;
|
||||
}
|
||||
|
||||
|
||||
FrameGrabber::~FrameGrabber() = default;
|
||||
|
||||
namespace {
|
||||
|
||||
sal::systools::COMReference<IMediaDet> implCreateMediaDet( const OUString& rURL )
|
||||
{
|
||||
sal::systools::COMReference<IMediaDet> pDet;
|
||||
|
||||
if( SUCCEEDED(pDet.CoCreateInstance(CLSID_MediaDet, nullptr, CLSCTX_INPROC_SERVER)) )
|
||||
{
|
||||
OUString aLocalStr;
|
||||
|
||||
if( osl::FileBase::getSystemPathFromFileURL( rURL, aLocalStr )
|
||||
== osl::FileBase::E_None )
|
||||
{
|
||||
if( !SUCCEEDED( pDet->put_Filename(sal::systools::BStr(aLocalStr)) ) )
|
||||
pDet.clear();
|
||||
}
|
||||
}
|
||||
|
||||
return pDet;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool FrameGrabber::create( const OUString& rURL )
|
||||
{
|
||||
// just check if a MediaDet interface can be created with the given URL
|
||||
if (implCreateMediaDet(rURL))
|
||||
maURL = rURL;
|
||||
else
|
||||
maURL.clear();
|
||||
|
||||
return !maURL.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
uno::Reference< graphic::XGraphic > SAL_CALL FrameGrabber::grabFrame( double fMediaTime )
|
||||
{
|
||||
uno::Reference< graphic::XGraphic > xRet;
|
||||
if (sal::systools::COMReference<IMediaDet> pDet = implCreateMediaDet(maURL))
|
||||
{
|
||||
double fLength;
|
||||
long nStreamCount;
|
||||
bool bFound = false;
|
||||
|
||||
if( SUCCEEDED( pDet->get_OutputStreams( &nStreamCount ) ) )
|
||||
if (mnFrameWidth && mnFrameHeight &&
|
||||
SUCCEEDED(MFStartup(MF_VERSION)))
|
||||
{
|
||||
for( long n = 0; ( n < nStreamCount ) && !bFound; ++n )
|
||||
{
|
||||
GUID aMajorType;
|
||||
HRESULT hr = S_OK;
|
||||
IMFAttributes* pAttributes = nullptr;
|
||||
|
||||
if( SUCCEEDED( pDet->put_CurrentStream( n ) ) &&
|
||||
SUCCEEDED( pDet->get_StreamType( &aMajorType ) ) &&
|
||||
( aMajorType == MEDIATYPE_Video ) )
|
||||
// Configure the source reader to perform video processing.
|
||||
//
|
||||
// This includes:
|
||||
// - YUV to RGB-32
|
||||
// - Software deinterlace
|
||||
if (SUCCEEDED(MFCreateAttributes(&pAttributes, 1)))
|
||||
{
|
||||
bFound = true;
|
||||
}
|
||||
}
|
||||
hr = pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE);
|
||||
}
|
||||
|
||||
if( bFound &&
|
||||
( S_OK == pDet->get_StreamLength( &fLength ) ) &&
|
||||
( fLength > 0.0 ) && ( fMediaTime >= 0.0 ) && ( fMediaTime <= fLength ) )
|
||||
// Create the source reader from the URL.
|
||||
IMFSourceReader* pReader = nullptr; // Create the source reader.
|
||||
if (SUCCEEDED(hr) && SUCCEEDED(MFCreateSourceReaderFromURL(o3tl::toW(maURL.getStr()), pAttributes, &pReader)))
|
||||
{
|
||||
AM_MEDIA_TYPE aMediaType;
|
||||
LONG nWidth = 0, nHeight = 0;
|
||||
long nSize = 0;
|
||||
IMFMediaType* pType = nullptr;
|
||||
|
||||
if( SUCCEEDED( pDet->get_StreamMediaType( &aMediaType ) ) )
|
||||
// Configure the source reader to give us progressive RGB32 frames.
|
||||
// The source reader will load the decoder if needed.
|
||||
if (SUCCEEDED(MFCreateMediaType(&pType)))
|
||||
hr = pType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
hr = pType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
if( ( aMediaType.formattype == FORMAT_VideoInfo ) &&
|
||||
( aMediaType.cbFormat >= sizeof( VIDEOINFOHEADER ) ) )
|
||||
{
|
||||
VIDEOINFOHEADER* pVih = reinterpret_cast< VIDEOINFOHEADER* >( aMediaType.pbFormat );
|
||||
|
||||
nWidth = pVih->bmiHeader.biWidth;
|
||||
nHeight = pVih->bmiHeader.biHeight;
|
||||
|
||||
if( nHeight < 0 )
|
||||
nHeight *= -1;
|
||||
hr = pReader->SetCurrentMediaType(
|
||||
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
||||
nullptr,
|
||||
pType
|
||||
);
|
||||
}
|
||||
|
||||
if( aMediaType.cbFormat != 0 )
|
||||
// Ensure the stream is selected.
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
::CoTaskMemFree( aMediaType.pbFormat );
|
||||
aMediaType.cbFormat = 0;
|
||||
aMediaType.pbFormat = nullptr;
|
||||
hr = pReader->SetStreamSelection(
|
||||
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
||||
TRUE
|
||||
);
|
||||
}
|
||||
|
||||
if( aMediaType.pUnk != nullptr )
|
||||
SafeRelease(&pType);
|
||||
|
||||
if (SUCCEEDED(hr) && fMediaTime > 0.0)
|
||||
{
|
||||
aMediaType.pUnk->Release();
|
||||
aMediaType.pUnk = nullptr;
|
||||
PROPVARIANT var;
|
||||
PropVariantInit(&var);
|
||||
|
||||
var.vt = VT_I8;
|
||||
var.hVal.QuadPart = fMediaTime;
|
||||
|
||||
hr = pReader->SetCurrentPosition(GUID_NULL, var);
|
||||
}
|
||||
|
||||
IMFSample* pSampleTmp = nullptr;
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
DWORD dwFlags = 0;
|
||||
DWORD cSkipped = 0; // Number of skipped frames
|
||||
while (true)
|
||||
{
|
||||
hr = pReader->ReadSample(
|
||||
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
|
||||
0,
|
||||
nullptr,
|
||||
&dwFlags,
|
||||
nullptr,
|
||||
&pSampleTmp
|
||||
);
|
||||
|
||||
if (FAILED(hr) || (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM))
|
||||
break;
|
||||
|
||||
if (pSampleTmp == nullptr)
|
||||
continue;
|
||||
|
||||
LONGLONG hnsTimeStamp = 0;
|
||||
if (SUCCEEDED(pSampleTmp->GetSampleTime(&hnsTimeStamp)))
|
||||
{
|
||||
// Keep going until we get a frame that is within tolerance of the
|
||||
// desired seek position, or until we skip MAX_FRAMES_TO_SKIP frames.
|
||||
|
||||
// During this process, we might reach the end of the file, so we
|
||||
// always cache the last sample that we got (pSample).
|
||||
|
||||
if ((cSkipped < MAX_FRAMES_TO_SKIP) &&
|
||||
(hnsTimeStamp + SEEK_TOLERANCE < fMediaTime))
|
||||
{
|
||||
SafeRelease(&pSampleTmp);
|
||||
|
||||
++cSkipped;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if( ( nWidth > 0 ) && ( nHeight > 0 ) &&
|
||||
SUCCEEDED( pDet->GetBitmapBits( 0, &nSize, nullptr, nWidth, nHeight ) ) &&
|
||||
( nSize > 0 ) )
|
||||
{
|
||||
auto pBuffer = std::make_unique<char[]>(nSize);
|
||||
fMediaTime = hnsTimeStamp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
// We got a sample. Hold onto it.
|
||||
IMFMediaBuffer* pBuffer = nullptr;
|
||||
BYTE* pBitmapData = nullptr; // Bitmap data
|
||||
DWORD cbBitmapData = 0; // Size of data, in bytes
|
||||
if (pSampleTmp && SUCCEEDED(pSampleTmp->ConvertToContiguousBuffer(&pBuffer)))
|
||||
{
|
||||
if( SUCCEEDED( pDet->GetBitmapBits( fMediaTime, nullptr, pBuffer.get(), nWidth, nHeight ) ) )
|
||||
if (SUCCEEDED(pBuffer->Lock(&pBitmapData, nullptr, &cbBitmapData)) && cbBitmapData)
|
||||
{
|
||||
SvMemoryStream aMemStm( pBuffer.get(), nSize, StreamMode::READ | StreamMode::WRITE );
|
||||
Bitmap aBmp;
|
||||
|
||||
if( ReadDIB(aBmp, aMemStm, false ) && !aBmp.IsEmpty() )
|
||||
{
|
||||
BitmapEx aBitmapEx(aBmp);
|
||||
BitmapEx aBitmapEx = vcl::bitmap::CreateFromData(pBitmapData, mnFrameWidth, mnFrameHeight, mnFrameWidth * 4, /*nBitsPerPixel*/32, true);
|
||||
Graphic aGraphic(aBitmapEx);
|
||||
xRet = aGraphic.GetXGraphic();
|
||||
}
|
||||
pBuffer->Unlock();
|
||||
}
|
||||
|
||||
SafeRelease(&pBuffer);
|
||||
SafeRelease(&pSampleTmp);
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SafeRelease(&pAttributes);
|
||||
SafeRelease(&pReader);
|
||||
// Shut down Media Foundation.
|
||||
MFShutdown();
|
||||
}
|
||||
|
||||
return xRet;
|
||||
|
||||
@@ -34,11 +34,9 @@ class FrameGrabber : public ::cppu::WeakImplHelper< css::media::XFrameGrabber,
|
||||
public sal::systools::CoInitializeGuard
|
||||
{
|
||||
public:
|
||||
explicit FrameGrabber();
|
||||
explicit FrameGrabber( const OUString& rURL, UINT32 nFrameWidth, UINT32 nFrameHeight );
|
||||
~FrameGrabber() override;
|
||||
|
||||
bool create( const OUString& rURL );
|
||||
|
||||
// XFrameGrabber
|
||||
virtual css::uno::Reference< css::graphic::XGraphic > SAL_CALL grabFrame( double fMediaTime ) override;
|
||||
|
||||
@@ -49,6 +47,8 @@ public:
|
||||
|
||||
private:
|
||||
OUString maURL;
|
||||
UINT32 mnFrameWidth;
|
||||
UINT32 mnFrameHeight;
|
||||
};
|
||||
|
||||
} // namespace avmedia::win
|
||||
|
||||
+444
-156
@@ -18,10 +18,17 @@
|
||||
*/
|
||||
|
||||
#include <objbase.h>
|
||||
#include <strmif.h>
|
||||
#include <control.h>
|
||||
#include <uuids.h>
|
||||
#include <evcode.h>
|
||||
#include <propvarutil.h>
|
||||
#include <propkey.h>
|
||||
// Media Foundation headers
|
||||
#include <mfapi.h>
|
||||
#include <mfidl.h>
|
||||
#include <mfreadwrite.h>
|
||||
|
||||
#include <avmedia/mediaitem.hxx>
|
||||
#include <avmedia/mediawindow.hxx>
|
||||
|
||||
#include "player.hxx"
|
||||
#include "framegrabber.hxx"
|
||||
@@ -33,154 +40,318 @@
|
||||
|
||||
constexpr OUStringLiteral AVMEDIA_WIN_PLAYER_IMPLEMENTATIONNAME = u"com.sun.star.comp.avmedia.Player_DirectX";
|
||||
constexpr OUString AVMEDIA_WIN_PLAYER_SERVICENAME = u"com.sun.star.media.Player_DirectX"_ustr;
|
||||
constexpr float AVMEDIA_DB_RANGE = 40.0;
|
||||
|
||||
using namespace ::com::sun::star;
|
||||
|
||||
namespace avmedia::win {
|
||||
|
||||
static LRESULT CALLBACK MediaPlayerWndProc_2( HWND hWnd,UINT nMsg, WPARAM nPar1, LPARAM nPar2 )
|
||||
static LRESULT CALLBACK MediaPlayerWndProc_2( HWND /*hWnd*/, UINT /*nMsg*/, WPARAM /*nPar1*/, LPARAM /*nPar2*/ )
|
||||
{
|
||||
Player* pPlayer = reinterpret_cast<Player*>(::GetWindowLongPtrW( hWnd, 0 ));
|
||||
bool bProcessed = true;
|
||||
|
||||
if( pPlayer )
|
||||
{
|
||||
switch( nMsg )
|
||||
{
|
||||
case WM_GRAPHNOTIFY:
|
||||
pPlayer->processEvent();
|
||||
break;
|
||||
default:
|
||||
bProcessed = false;
|
||||
break;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
bProcessed = false;
|
||||
|
||||
return( bProcessed ? 0 : DefWindowProcW( hWnd, nMsg, nPar1, nPar2 ) );
|
||||
}
|
||||
|
||||
|
||||
Player::Player() :
|
||||
Player_BASE(m_aMutex),
|
||||
sal::systools::CoInitializeGuard(COINIT_APARTMENTTHREADED, false,
|
||||
sal::systools::CoInitializeGuard::WhenFailed::NoThrow),
|
||||
mnUnmutedVolume( 0 ),
|
||||
m_cRef( 1 ),
|
||||
mnUnmutedVolume( 1 ),
|
||||
mnFrameWnd( nullptr ),
|
||||
mbMuted( false ),
|
||||
mbLooping( false ),
|
||||
mbAddWindow( true )
|
||||
mbAutoPlayBack( false ),
|
||||
mnFrameWidth( 0 ),
|
||||
mnFrameHeight( 0 ),
|
||||
g_pPlayer( nullptr ),
|
||||
g_bHasVideo( false ),
|
||||
g_bHasAudio( false ),
|
||||
m_state( Closed )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Player::~Player()
|
||||
{
|
||||
if( mnFrameWnd )
|
||||
::DestroyWindow( mnFrameWnd );
|
||||
|
||||
if (g_pPlayer)
|
||||
g_pPlayer->Shutdown();
|
||||
SafeRelease(&g_pPlayer);
|
||||
}
|
||||
|
||||
//***************************** IUnknown methods *****************************//
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// AddRef
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
ULONG Player::AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&m_cRef);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Release
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
ULONG Player::Release()
|
||||
{
|
||||
ULONG uCount = InterlockedDecrement(&m_cRef);
|
||||
if (uCount == 0)
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
return uCount;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// QueryInterface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
STDMETHODIMP Player::QueryInterface(REFIID riid, void** ppv)
|
||||
{
|
||||
static const QITAB qit[] =
|
||||
{
|
||||
QITABENT(Player, IMFPMediaPlayerCallback),
|
||||
{ 0 },
|
||||
};
|
||||
return QISearch(this, qit, riid, ppv);
|
||||
}
|
||||
|
||||
//********************* IMFPMediaPlayerCallback methods **********************//
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// OnMediaPlayerEvent
|
||||
//
|
||||
// Notifies the object of an MFPlay event.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void Player::OnMediaPlayerEvent(MFP_EVENT_HEADER* pEventHeader)
|
||||
{
|
||||
if (FAILED(pEventHeader->hrEvent))
|
||||
{
|
||||
SAL_WARN("avmedia.win",
|
||||
"Player::OnMediaPlayerEvent failed with error code: " << pEventHeader->hrEvent);
|
||||
|
||||
::avmedia::MediaWindow::executeFormatErrorBox(nullptr);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pEventHeader->eEventType)
|
||||
{
|
||||
case MFP_EVENT_TYPE_MEDIAITEM_CREATED:
|
||||
OnMediaItemCreated(MFP_GET_MEDIAITEM_CREATED_EVENT(pEventHeader));
|
||||
break;
|
||||
|
||||
case MFP_EVENT_TYPE_MEDIAITEM_SET:
|
||||
OnMediaItemSet(MFP_GET_MEDIAITEM_SET_EVENT(pEventHeader));
|
||||
break;
|
||||
|
||||
case MFP_EVENT_TYPE_POSITION_SET:
|
||||
OnMediaPosSet(MFP_GET_POSITION_SET_EVENT(pEventHeader));
|
||||
break;
|
||||
|
||||
case MFP_EVENT_TYPE_PLAYBACK_ENDED:
|
||||
OnMediaItemEnded(MFP_GET_PLAYBACK_ENDED_EVENT(pEventHeader));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SAL_CALL Player::disposing()
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
stop();
|
||||
if( mpME )
|
||||
mpME->SetNotifyWindow( 0, WM_GRAPHNOTIFY, 0);
|
||||
OnClose(mnFrameWnd);
|
||||
}
|
||||
|
||||
|
||||
bool Player::create( const OUString& rURL )
|
||||
{
|
||||
bool bRet = false;
|
||||
|
||||
if( SUCCEEDED(mpGB.CoCreateInstance(CLSID_FilterGraph, nullptr, CLSCTX_INPROC_SERVER)) )
|
||||
{
|
||||
// Don't use the overlay mixer on Windows Vista
|
||||
// It disables the desktop composition as soon as RenderFile is called
|
||||
// also causes some other problems: video rendering is not reliable
|
||||
|
||||
// tdf#128057: IGraphBuilder::RenderFile seems to fail to handle file URIs properly when
|
||||
// they contain encoded characters like "%23"; so pass system path in that case instead.
|
||||
OUString aFile(rURL);
|
||||
if (aFile.startsWithIgnoreAsciiCase("file:"))
|
||||
osl::FileBase::getSystemPathFromFileURL(rURL, aFile);
|
||||
|
||||
if( SUCCEEDED( mpGB->RenderFile( o3tl::toW(aFile.getStr()), nullptr ) ) &&
|
||||
mpMC.set(mpGB, sal::systools::COM_QUERY) &&
|
||||
mpME.set(mpGB, sal::systools::COM_QUERY) &&
|
||||
mpMP.set(mpGB, sal::systools::COM_QUERY) )
|
||||
{
|
||||
// Video interfaces
|
||||
mpVW.set(mpGB, sal::systools::COM_QUERY);
|
||||
mpBV.set(mpGB, sal::systools::COM_QUERY);
|
||||
|
||||
// Audio interface
|
||||
mpBA.set(mpGB, sal::systools::COM_QUERY);
|
||||
|
||||
if( mpBA )
|
||||
mpBA->put_Volume( mnUnmutedVolume );
|
||||
|
||||
bRet = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool bRet = !rURL.isEmpty();
|
||||
if (bRet)
|
||||
{
|
||||
maURL = rURL;
|
||||
// Initialize the Media Foundation platform.
|
||||
HRESULT hr = MFStartup(MF_VERSION);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
// Create the source reader.
|
||||
IMFSourceReader* pReader;
|
||||
hr = MFCreateSourceReaderFromURL(o3tl::toW(maURL.getStr()), nullptr, &pReader);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
IMFMediaType* pType = nullptr;
|
||||
DWORD dwMediaTypeIndex = 0;
|
||||
if (SUCCEEDED(pReader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, dwMediaTypeIndex, &pType)))
|
||||
{
|
||||
MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &mnFrameWidth, &mnFrameHeight);
|
||||
SafeRelease(&pType);
|
||||
}
|
||||
SafeRelease(&pReader);
|
||||
}
|
||||
// Shut down Media Foundation.
|
||||
MFShutdown();
|
||||
}
|
||||
bRet = SUCCEEDED(hr);
|
||||
}
|
||||
else
|
||||
maURL.clear();
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
const IVideoWindow* Player::getVideoWindow() const
|
||||
{
|
||||
return mpVW;
|
||||
}
|
||||
|
||||
|
||||
void Player::setNotifyWnd( HWND nNotifyWnd )
|
||||
{
|
||||
mbAddWindow = false;
|
||||
if( mpME )
|
||||
mpME->SetNotifyWindow( reinterpret_cast<OAHWND>(nNotifyWnd), WM_GRAPHNOTIFY, reinterpret_cast< LONG_PTR>( this ) );
|
||||
mnFrameWnd = nNotifyWnd;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// OnClose
|
||||
//
|
||||
// Handles the WM_CLOSE message.
|
||||
//-------------------------------------------------------------------
|
||||
void Player::OnClose(HWND /*hwnd*/)
|
||||
{
|
||||
if (g_pPlayer)
|
||||
g_pPlayer->Shutdown();
|
||||
SafeRelease(&g_pPlayer);
|
||||
|
||||
void Player::processEvent()
|
||||
{
|
||||
long nCode;
|
||||
LONG_PTR nParam1, nParam2;
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
|
||||
while( mpME && SUCCEEDED( mpME->GetEvent( &nCode, &nParam1, &nParam2, 0 ) ) )
|
||||
//-------------------------------------------------------------------
|
||||
// OnPaint
|
||||
//
|
||||
// Handles the WM_PAINT message.
|
||||
//-------------------------------------------------------------------
|
||||
void Player::OnPaint(HWND hwnd)
|
||||
{
|
||||
if( EC_COMPLETE == nCode )
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc = 0;
|
||||
|
||||
hdc = BeginPaint(hwnd, &ps);
|
||||
|
||||
if (g_pPlayer && g_bHasVideo)
|
||||
{
|
||||
if( mbLooping )
|
||||
{
|
||||
setMediaTime( 0.0 );
|
||||
start();
|
||||
// Playback has started and there is video.
|
||||
|
||||
// Do not draw the window background, because the video
|
||||
// frame fills the entire client area.
|
||||
|
||||
g_pPlayer->UpdateVideo();
|
||||
}
|
||||
else
|
||||
{
|
||||
setMediaTime( getDuration() );
|
||||
stop();
|
||||
}
|
||||
// There is no video stream, or playback has not started.
|
||||
// Paint the entire client area.
|
||||
|
||||
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
|
||||
}
|
||||
|
||||
mpME->FreeEventParams( nCode, nParam1, nParam2 );
|
||||
}
|
||||
EndPaint(hwnd, &ps);
|
||||
}
|
||||
|
||||
|
||||
void SAL_CALL Player::start( )
|
||||
//-------------------------------------------------------------------
|
||||
// OnSize
|
||||
//
|
||||
// Handles the WM_SIZE message.
|
||||
//-------------------------------------------------------------------
|
||||
void Player::OnSize(HWND /*hwnd*/, UINT state, int /*cx*/, int /*cy*/)
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
if( mpMC )
|
||||
if (state == SIZE_RESTORED)
|
||||
{
|
||||
if ( mbAddWindow )
|
||||
if (g_pPlayer)
|
||||
{
|
||||
// Resize the video.
|
||||
g_pPlayer->UpdateVideo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// OnMediaItemCreated
|
||||
//
|
||||
// Called when the IMFPMediaPlayer::CreateMediaItemFromURL method
|
||||
// completes.
|
||||
//-------------------------------------------------------------------
|
||||
void Player::OnMediaItemCreated(MFP_MEDIAITEM_CREATED_EVENT* pEvent)
|
||||
{
|
||||
if (g_pPlayer)
|
||||
{
|
||||
BOOL bHasVideoOrAudio = FALSE, bIsSelected = FALSE;
|
||||
|
||||
// Check if the media item contains video or audio.
|
||||
HRESULT hr = pEvent->pMediaItem->HasVideo(&bHasVideoOrAudio, &bIsSelected);
|
||||
if (SUCCEEDED(hr))
|
||||
g_bHasVideo = bHasVideoOrAudio && bIsSelected;
|
||||
|
||||
hr = pEvent->pMediaItem->HasAudio(&bHasVideoOrAudio, &bIsSelected);
|
||||
if (SUCCEEDED(hr))
|
||||
g_bHasAudio = bHasVideoOrAudio && bIsSelected;
|
||||
|
||||
if (SUCCEEDED(hr) && (g_bHasVideo || g_bHasAudio))
|
||||
{
|
||||
// Set the media item on the player. This method completes asynchronously.
|
||||
hr = g_pPlayer->SetMediaItem(pEvent->pMediaItem);
|
||||
}
|
||||
|
||||
if (FAILED(hr) || (!g_bHasVideo && !g_bHasAudio))
|
||||
{
|
||||
SAL_WARN("avmedia.win",
|
||||
"Player::OnMediaItemCreated failed with error code: " << hr);
|
||||
|
||||
::avmedia::MediaWindow::executeFormatErrorBox(nullptr);
|
||||
m_state = Closed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// OnMediaItemSet
|
||||
//
|
||||
// Called when the IMFPMediaPlayer::SetMediaItem method completes.
|
||||
//-------------------------------------------------------------------
|
||||
void Player::OnMediaItemSet(MFP_MEDIAITEM_SET_EVENT* /*pEvent*/)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
if (mbAutoPlayBack)
|
||||
{
|
||||
hr = g_pPlayer->Play();
|
||||
if (SUCCEEDED(hr))
|
||||
m_state = Started;
|
||||
}
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
SAL_WARN("avmedia.win",
|
||||
"Player::OnMediaItemSet failed with error code: " << hr);
|
||||
|
||||
::avmedia::MediaWindow::executeFormatErrorBox(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::OnMediaPosSet(MFP_POSITION_SET_EVENT* /*pEvent*/)
|
||||
{
|
||||
// TODO: Handle position set event if needed with update of MediaPlayer UI.
|
||||
// void MediaWindowControl::update() should be called
|
||||
}
|
||||
|
||||
void Player::OnMediaItemEnded(MFP_PLAYBACK_ENDED_EVENT* /*pEvent*/)
|
||||
{
|
||||
if( mbLooping )
|
||||
start();
|
||||
else
|
||||
m_state = Stopped;
|
||||
}
|
||||
|
||||
HRESULT Player::InitializeWindow(bool bAddSoundWindow)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
SafeRelease(&g_pPlayer);
|
||||
|
||||
if (bAddSoundWindow)
|
||||
{
|
||||
static WNDCLASSW* mpWndClass = nullptr;
|
||||
if ( !mpWndClass )
|
||||
@@ -207,81 +378,172 @@ void SAL_CALL Player::start( )
|
||||
{
|
||||
::ShowWindow(mnFrameWnd, SW_HIDE);
|
||||
SetWindowLongPtrW( mnFrameWnd, 0, reinterpret_cast<LONG_PTR>(this) );
|
||||
// mpVW->put_Owner( (OAHWND) mnFrameWnd );
|
||||
setNotifyWnd( mnFrameWnd );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mpMC->Run();
|
||||
// Create the MFPlayer object.
|
||||
hr = MFPCreateMediaPlayer(
|
||||
nullptr,
|
||||
FALSE, // Start playback automatically?
|
||||
0, // Flags
|
||||
this, // Callback pointer
|
||||
mnFrameWnd, // Video window
|
||||
&g_pPlayer
|
||||
);
|
||||
|
||||
// Create a new media item for this URL.
|
||||
// The CreateMediaItemFromURL method completes asynchronously. When it does,
|
||||
// MFPlay sends an MFP_EVENT_TYPE_MEDIAITEM_CREATED event.
|
||||
if (SUCCEEDED(hr))
|
||||
hr = g_pPlayer->CreateMediaItemFromURL(o3tl::toW(maURL.getStr()), FALSE, 0, nullptr);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
m_state = Stopped;
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
void SAL_CALL Player::start( )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
if( g_pPlayer )
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
if (g_bHasVideo || g_bHasAudio)
|
||||
{
|
||||
MFP_MEDIAPLAYER_STATE state = MFP_MEDIAPLAYER_STATE_EMPTY;
|
||||
hr = g_pPlayer->GetState(&state);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
if (state == MFP_MEDIAPLAYER_STATE_PAUSED || state == MFP_MEDIAPLAYER_STATE_STOPPED)
|
||||
{
|
||||
hr = g_pPlayer->Play();
|
||||
if (SUCCEEDED(hr))
|
||||
m_state = Started;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
SAL_WARN("avmedia.win",
|
||||
"Player::start failed with error code: " << hr);
|
||||
|
||||
::avmedia::MediaWindow::executeFormatErrorBox(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SAL_CALL Player::stop( )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
if( mpMC )
|
||||
mpMC->Stop();
|
||||
if (g_pPlayer && (g_bHasVideo || g_bHasAudio))
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
if (getMediaTime())
|
||||
{
|
||||
hr = g_pPlayer->Pause();
|
||||
if (SUCCEEDED(hr))
|
||||
m_state = Paused;
|
||||
}
|
||||
else
|
||||
{
|
||||
hr = g_pPlayer->Stop();
|
||||
if (SUCCEEDED(hr))
|
||||
m_state = Stopped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sal_Bool SAL_CALL Player::isPlaying()
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
|
||||
OAFilterState eFilterState;
|
||||
bool bRet = false;
|
||||
|
||||
if( mpMC && SUCCEEDED( mpMC->GetState( 10, &eFilterState ) ) )
|
||||
bRet = ( State_Running == eFilterState );
|
||||
if (g_pPlayer && (g_bHasVideo || g_bHasAudio))
|
||||
{
|
||||
MFP_MEDIAPLAYER_STATE state = MFP_MEDIAPLAYER_STATE_EMPTY;
|
||||
HRESULT hr = g_pPlayer->GetState(&state);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
bRet = (state == MFP_MEDIAPLAYER_STATE_PLAYING && m_state == Started);
|
||||
}
|
||||
else
|
||||
{
|
||||
SAL_WARN("avmedia.win",
|
||||
"Player::isPlaying failed with error code: " << hr);
|
||||
}
|
||||
}
|
||||
|
||||
return bRet;
|
||||
}
|
||||
|
||||
|
||||
double SAL_CALL Player::getDuration( )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
|
||||
REFTIME aRefTime( 0.0 );
|
||||
|
||||
if( mpMP )
|
||||
mpMP->get_Duration( &aRefTime );
|
||||
|
||||
if (g_pPlayer && (g_bHasVideo || g_bHasAudio))
|
||||
{
|
||||
PROPVARIANT var;
|
||||
HRESULT hr = g_pPlayer->GetDuration(MFP_POSITIONTYPE_100NS, &var);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = PropVariantToDouble(var, &aRefTime);
|
||||
aRefTime = aRefTime / (1000.0 * 10.0) / 1000.0; // Convert from 100-nanosecond units to seconds
|
||||
PropVariantClear(&var);
|
||||
}
|
||||
}
|
||||
return aRefTime;
|
||||
}
|
||||
|
||||
|
||||
void SAL_CALL Player::setMediaTime( double fTime )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
|
||||
if( mpMP )
|
||||
if (g_pPlayer && (g_bHasVideo || g_bHasAudio))
|
||||
{
|
||||
const bool bPlaying = isPlaying();
|
||||
|
||||
mpMP->put_CurrentPosition( fTime );
|
||||
|
||||
if( !bPlaying && mpMC )
|
||||
mpMC->StopWhenReady();
|
||||
PROPVARIANT var;
|
||||
PropVariantInit(&var);
|
||||
var.vt = VT_I8; // PROPVARIANT type has to be VT_I8.
|
||||
var.hVal.QuadPart = static_cast<MFTIME>(fTime * 1000.0 * (1000.0 * 10.0)); // Convert from seconds to 100-nanosecond units
|
||||
HRESULT hr = g_pPlayer->SetPosition(MFP_POSITIONTYPE_100NS, &var);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
SAL_WARN("avmedia.win",
|
||||
"Player::setMediaTime: setMediaTime failed with error code: " << hr);
|
||||
}
|
||||
PropVariantClear(&var);
|
||||
// on resetting back to zero the reported timestamp doesn't seem to get
|
||||
// updated in a reasonable time, so on zero just force an update of timestamp to 0.
|
||||
// Same as in /core/avmedia/source/gtk/gtkplayer.cxx:GtkPlayer::setMediaTime(double fTime)
|
||||
// With UpdateVideo() it can help on video streams, but doesn't help on audio streams. TODO!
|
||||
if (fTime == 0.0)
|
||||
g_pPlayer->UpdateVideo();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
double SAL_CALL Player::getMediaTime( )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
|
||||
REFTIME aRefTime( 0.0 );
|
||||
|
||||
if( mpMP )
|
||||
mpMP->get_CurrentPosition( &aRefTime );
|
||||
if (g_pPlayer && (g_bHasVideo || g_bHasAudio))
|
||||
{
|
||||
PROPVARIANT var;
|
||||
HRESULT hr = g_pPlayer->GetPosition(MFP_POSITIONTYPE_100NS, &var);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = PropVariantToDouble(var, &aRefTime);
|
||||
aRefTime = aRefTime / (1000.0 * 10.0) / 1000.0; // Convert from 100-nanosecond units to seconds
|
||||
PropVariantClear(&var);
|
||||
}
|
||||
}
|
||||
|
||||
return aRefTime;
|
||||
}
|
||||
|
||||
|
||||
void SAL_CALL Player::setPlaybackLoop( sal_Bool bSet )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
@@ -289,7 +551,6 @@ void SAL_CALL Player::setPlaybackLoop( sal_Bool bSet )
|
||||
mbLooping = bSet;
|
||||
}
|
||||
|
||||
|
||||
sal_Bool SAL_CALL Player::isPlaybackLoop( )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
@@ -297,65 +558,81 @@ sal_Bool SAL_CALL Player::isPlaybackLoop( )
|
||||
return mbLooping;
|
||||
}
|
||||
|
||||
|
||||
void SAL_CALL Player::setMute( sal_Bool bSet )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
|
||||
if (mpBA && (mbMuted != static_cast<bool>(bSet)))
|
||||
if (g_pPlayer && (g_bHasVideo || g_bHasAudio) &&
|
||||
(mbMuted != static_cast<BOOL>(bSet)))
|
||||
{
|
||||
mbMuted = bSet;
|
||||
mpBA->put_Volume( mbMuted ? -10000 : mnUnmutedVolume );
|
||||
HRESULT hr = g_pPlayer->SetMute(bSet);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
SAL_WARN("avmedia.win",
|
||||
"Player::setMute: setMute failed with error code: " << hr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sal_Bool SAL_CALL Player::isMute( )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
|
||||
if (g_pPlayer && (g_bHasVideo || g_bHasAudio))
|
||||
{
|
||||
HRESULT hr = g_pPlayer->GetMute(&mbMuted);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
SAL_WARN("avmedia.win",
|
||||
"Player::isMute: getMute failed with error code: " << hr);
|
||||
mbMuted = false; // Reset to default if error occurs
|
||||
}
|
||||
}
|
||||
return mbMuted;
|
||||
}
|
||||
|
||||
|
||||
void SAL_CALL Player::setVolumeDB( sal_Int16 nVolumeDB )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
|
||||
mnUnmutedVolume = static_cast< long >( nVolumeDB ) * 100;
|
||||
mnUnmutedVolume = static_cast< float >( (nVolumeDB / AVMEDIA_DB_RANGE) + 1.0 );
|
||||
|
||||
if( !mbMuted && mpBA )
|
||||
mpBA->put_Volume( mnUnmutedVolume );
|
||||
if ( g_pPlayer && (g_bHasVideo || g_bHasAudio))
|
||||
{
|
||||
HRESULT hr = g_pPlayer->SetVolume(mnUnmutedVolume);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
SAL_WARN("avmedia.win",
|
||||
"Player::setVolumeDB: setVolumeDB failed with error code: " << hr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sal_Int16 SAL_CALL Player::getVolumeDB( )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
|
||||
return static_cast< sal_Int16 >( mnUnmutedVolume / 100 );
|
||||
if (g_pPlayer && (g_bHasVideo || g_bHasAudio))
|
||||
{
|
||||
HRESULT hr = g_pPlayer->GetVolume(&mnUnmutedVolume);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
SAL_WARN("avmedia.win",
|
||||
"Player::getVolumeDB: getVolumeDB failed with error code: " << hr);
|
||||
mnUnmutedVolume = 1; // Reset to default if error occurs
|
||||
}
|
||||
}
|
||||
return static_cast< sal_Int16 >( (mnUnmutedVolume - 1.0) * AVMEDIA_DB_RANGE);
|
||||
}
|
||||
|
||||
|
||||
awt::Size SAL_CALL Player::getPreferredPlayerWindowSize( )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
|
||||
awt::Size aSize( 0, 0 );
|
||||
|
||||
if( mpBV )
|
||||
{
|
||||
long nWidth = 0, nHeight = 0;
|
||||
|
||||
mpBV->GetVideoSize( &nWidth, &nHeight );
|
||||
aSize.Width = nWidth;
|
||||
aSize.Height = nHeight;
|
||||
return awt::Size(mnFrameWidth, mnFrameHeight);
|
||||
}
|
||||
|
||||
return aSize;
|
||||
}
|
||||
|
||||
|
||||
uno::Reference< ::media::XPlayerWindow > SAL_CALL Player::createPlayerWindow( const uno::Sequence< uno::Any >& aArguments )
|
||||
{
|
||||
::osl::MutexGuard aGuard(m_aMutex);
|
||||
@@ -363,7 +640,7 @@ uno::Reference< ::media::XPlayerWindow > SAL_CALL Player::createPlayerWindow( co
|
||||
uno::Reference< ::media::XPlayerWindow > xRet;
|
||||
awt::Size aSize(getPreferredPlayerWindowSize());
|
||||
|
||||
if( mpVW && aSize.Width > 0 && aSize.Height > 0 )
|
||||
if( aSize.Width > 0 && aSize.Height > 0 )
|
||||
{
|
||||
rtl::Reference<::avmedia::win::Window> pWindow = new ::avmedia::win::Window( *this );
|
||||
|
||||
@@ -372,41 +649,52 @@ uno::Reference< ::media::XPlayerWindow > SAL_CALL Player::createPlayerWindow( co
|
||||
if (!pWindow->create( aArguments ) )
|
||||
xRet.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SUCCEEDED(InitializeWindow(true)))
|
||||
{
|
||||
sal_IntPtr pIntPtr = 0;
|
||||
if ((aArguments.getLength() >= 4) && (aArguments[3] >>= pIntPtr) && pIntPtr)
|
||||
{
|
||||
auto pItem = reinterpret_cast<const avmedia::MediaItem*>(pIntPtr);
|
||||
if (pItem->getState() == avmedia::MediaState::Play)
|
||||
{
|
||||
setAutoPlayBack(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
::avmedia::MediaWindow::executeFormatErrorBox(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
return xRet;
|
||||
}
|
||||
|
||||
|
||||
uno::Reference< media::XFrameGrabber > SAL_CALL Player::createFrameGrabber( )
|
||||
{
|
||||
uno::Reference< media::XFrameGrabber > xRet;
|
||||
|
||||
if( !maURL.isEmpty() )
|
||||
{
|
||||
rtl::Reference<FrameGrabber> pGrabber = new FrameGrabber();
|
||||
|
||||
rtl::Reference<FrameGrabber> pGrabber = new FrameGrabber(maURL, mnFrameWidth, mnFrameHeight);
|
||||
xRet = pGrabber;
|
||||
|
||||
if( !pGrabber->create( maURL ) )
|
||||
xRet.clear();
|
||||
}
|
||||
|
||||
return xRet;
|
||||
}
|
||||
|
||||
|
||||
OUString SAL_CALL Player::getImplementationName( )
|
||||
{
|
||||
return AVMEDIA_WIN_PLAYER_IMPLEMENTATIONNAME;
|
||||
}
|
||||
|
||||
|
||||
sal_Bool SAL_CALL Player::supportsService( const OUString& ServiceName )
|
||||
{
|
||||
return cppu::supportsService(this, ServiceName);
|
||||
}
|
||||
|
||||
|
||||
uno::Sequence< OUString > SAL_CALL Player::getSupportedServiceNames( )
|
||||
{
|
||||
return { AVMEDIA_WIN_PLAYER_SERVICENAME };
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
|
||||
#include <WinDef.h>
|
||||
|
||||
// Media Foundation headers
|
||||
#include <mfplay.h>
|
||||
#include <mferror.h>
|
||||
|
||||
#include "wincommon.hxx"
|
||||
|
||||
#include <com/sun/star/media/XPlayer.hpp>
|
||||
@@ -31,26 +35,31 @@
|
||||
#include <cppuhelper/basemutex.hxx>
|
||||
#include <systools/win32/comtools.hxx>
|
||||
|
||||
struct IGraphBuilder;
|
||||
struct IBaseFilter;
|
||||
struct IMediaControl;
|
||||
struct IMediaEventEx;
|
||||
struct IMediaSeeking;
|
||||
struct IMediaPosition;
|
||||
struct IBasicAudio;
|
||||
struct IBasicVideo;
|
||||
struct IVideoWindow;
|
||||
struct IDDrawExclModeVideo;
|
||||
struct IDirectDraw;
|
||||
struct IDirectDrawSurface;
|
||||
|
||||
namespace avmedia::win {
|
||||
|
||||
enum PlayerState
|
||||
{
|
||||
Closed = 0, // No session.
|
||||
Started, // Session is playing a file.
|
||||
Paused, // Session is paused.
|
||||
Stopped // Session is stopped (ready to play).
|
||||
};
|
||||
|
||||
template <class T> void SafeRelease(T **ppT)
|
||||
{
|
||||
if (*ppT)
|
||||
{
|
||||
(*ppT)->Release();
|
||||
*ppT = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
typedef ::cppu::WeakComponentImplHelper< css::media::XPlayer,
|
||||
css::lang::XServiceInfo > Player_BASE;
|
||||
|
||||
|
||||
class Player : public cppu::BaseMutex,
|
||||
class Player : public IMFPMediaPlayerCallback,
|
||||
public cppu::BaseMutex,
|
||||
public Player_BASE,
|
||||
public sal::systools::CoInitializeGuard
|
||||
{
|
||||
@@ -60,11 +69,31 @@ public:
|
||||
~Player() override;
|
||||
|
||||
bool create( const OUString& rURL );
|
||||
|
||||
HRESULT InitializeWindow( bool bAddSoundWindow );
|
||||
void setNotifyWnd( HWND nNotifyWnd );
|
||||
void processEvent();
|
||||
HWND* getNotifyWnd() { return &mnFrameWnd; }
|
||||
void setAutoPlayBack(bool bVal) { mbAutoPlayBack = bVal; }
|
||||
const UINT32 GetVideoWidth() const { return mnFrameWidth; }
|
||||
const UINT32 GetVideoHeight() const { return mnFrameHeight; }
|
||||
|
||||
const IVideoWindow* getVideoWindow() const;
|
||||
// IUnknown methods
|
||||
STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
|
||||
STDMETHODIMP_(ULONG) AddRef();
|
||||
STDMETHODIMP_(ULONG) Release();
|
||||
|
||||
// IMFPMediaPlayerCallback methods
|
||||
void STDMETHODCALLTYPE OnMediaPlayerEvent(MFP_EVENT_HEADER* pEventHeader);
|
||||
|
||||
// Window message handlers
|
||||
void OnClose(HWND hwnd);
|
||||
void OnPaint(HWND hwnd);
|
||||
void OnSize(HWND hwnd, UINT state, int cx, int cy);
|
||||
|
||||
// MFPlay event handler functions.
|
||||
void OnMediaItemCreated(MFP_MEDIAITEM_CREATED_EVENT* pEvent);
|
||||
void OnMediaItemSet(MFP_MEDIAITEM_SET_EVENT* pEvent);
|
||||
void OnMediaPosSet(MFP_POSITION_SET_EVENT* pEvent);
|
||||
void OnMediaItemEnded(MFP_PLAYBACK_ENDED_EVENT* pEvent);
|
||||
|
||||
// XPlayer
|
||||
virtual void SAL_CALL start( ) override;
|
||||
@@ -93,19 +122,19 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
long m_cRef; // Reference count.
|
||||
OUString maURL;
|
||||
sal::systools::COMReference<IGraphBuilder> mpGB;
|
||||
sal::systools::COMReference<IMediaControl> mpMC;
|
||||
sal::systools::COMReference<IMediaEventEx> mpME;
|
||||
sal::systools::COMReference<IMediaPosition> mpMP;
|
||||
sal::systools::COMReference<IBasicAudio> mpBA;
|
||||
sal::systools::COMReference<IBasicVideo> mpBV;
|
||||
sal::systools::COMReference<IVideoWindow> mpVW;
|
||||
long mnUnmutedVolume;
|
||||
float mnUnmutedVolume;
|
||||
HWND mnFrameWnd;
|
||||
bool mbMuted;
|
||||
BOOL mbMuted;
|
||||
bool mbLooping;
|
||||
bool mbAddWindow;
|
||||
bool mbAutoPlayBack;
|
||||
UINT32 mnFrameWidth;
|
||||
UINT32 mnFrameHeight;
|
||||
IMFPMediaPlayer* g_pPlayer; // The MFPlay player object.
|
||||
BOOL g_bHasVideo;
|
||||
BOOL g_bHasAudio;
|
||||
PlayerState m_state;
|
||||
};
|
||||
|
||||
} // namespace avmedia::win
|
||||
|
||||
@@ -36,6 +36,4 @@
|
||||
#include <com/sun/star/awt/MouseButton.hpp>
|
||||
#include <com/sun/star/media/XManager.hpp>
|
||||
|
||||
#define WM_GRAPHNOTIFY (WM_USER + 567)
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
*/
|
||||
|
||||
#include <objbase.h>
|
||||
#include <strmif.h>
|
||||
#include <control.h>
|
||||
#include <dshow.h>
|
||||
|
||||
#include <com/sun/star/awt/SystemPointer.hpp>
|
||||
#include <cppuhelper/supportsservice.hxx>
|
||||
#include <avmedia/mediaitem.hxx>
|
||||
#include <avmedia/mediawindow.hxx>
|
||||
|
||||
#include "window.hxx"
|
||||
#include "player.hxx"
|
||||
@@ -35,7 +35,7 @@ using namespace ::com::sun::star;
|
||||
|
||||
namespace avmedia::win {
|
||||
|
||||
static LRESULT CALLBACK MediaPlayerWndProc( HWND hWnd,UINT nMsg, WPARAM nPar1, LPARAM nPar2 )
|
||||
static LRESULT CALLBACK MediaPlayerWndProc( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
Window* pWindow = reinterpret_cast<Window*>(GetWindowLongPtrW( hWnd, 0 ));
|
||||
bool bProcessed = true;
|
||||
@@ -44,14 +44,14 @@ static LRESULT CALLBACK MediaPlayerWndProc( HWND hWnd,UINT nMsg, WPARAM nPar1, L
|
||||
{
|
||||
switch( nMsg )
|
||||
{
|
||||
HANDLE_MSG(hWnd, WM_CLOSE, pWindow->getPlayer().OnClose);
|
||||
HANDLE_MSG(hWnd, WM_PAINT, pWindow->getPlayer().OnPaint);
|
||||
HANDLE_MSG(hWnd, WM_SIZE, pWindow->getPlayer().OnSize);
|
||||
|
||||
case WM_SETCURSOR:
|
||||
pWindow->updatePointer();
|
||||
break;
|
||||
|
||||
case WM_GRAPHNOTIFY:
|
||||
pWindow->processGraphEvent();
|
||||
break;
|
||||
|
||||
case WM_MOUSEMOVE:
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_MBUTTONDOWN:
|
||||
@@ -59,7 +59,7 @@ static LRESULT CALLBACK MediaPlayerWndProc( HWND hWnd,UINT nMsg, WPARAM nPar1, L
|
||||
case WM_LBUTTONUP:
|
||||
case WM_MBUTTONUP:
|
||||
case WM_RBUTTONUP:
|
||||
PostMessage(pWindow->getParentWnd(), nMsg, nPar1, nPar2);
|
||||
PostMessage(pWindow->getParentWnd(), nMsg, wParam, lParam);
|
||||
break;
|
||||
|
||||
case WM_SETFOCUS:
|
||||
@@ -77,7 +77,7 @@ static LRESULT CALLBACK MediaPlayerWndProc( HWND hWnd,UINT nMsg, WPARAM nPar1, L
|
||||
else
|
||||
bProcessed = false;
|
||||
|
||||
return( bProcessed ? 0 : DefWindowProcW( hWnd, nMsg, nPar1, nPar2 ) );
|
||||
return( bProcessed ? 0 : DefWindowProcW( hWnd, nMsg, wParam, lParam ) );
|
||||
}
|
||||
|
||||
static WNDCLASSW* lcl_getWndClass()
|
||||
@@ -112,12 +112,32 @@ Window::~Window()
|
||||
::DestroyWindow( mnFrameWnd );
|
||||
}
|
||||
|
||||
const css::awt::Rectangle Window::getParentPosSize() const
|
||||
{
|
||||
awt::Rectangle aRet;
|
||||
|
||||
if (mnParentWnd)
|
||||
{
|
||||
::RECT aWndRect;
|
||||
|
||||
if (::GetClientRect(mnParentWnd, &aWndRect))
|
||||
{
|
||||
aRet.X = aWndRect.left;
|
||||
aRet.Y = aWndRect.top;
|
||||
aRet.Width = aWndRect.right - aWndRect.left + 1;
|
||||
aRet.Height = aWndRect.bottom - aWndRect.top + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return aRet;
|
||||
}
|
||||
|
||||
void Window::ImplLayoutVideoWindow()
|
||||
{
|
||||
if( media::ZoomLevel_NOT_AVAILABLE != meZoomLevel )
|
||||
{
|
||||
awt::Size aPrefSize( mrPlayer.getPreferredPlayerWindowSize() );
|
||||
awt::Rectangle aRect = getPosSize();
|
||||
awt::Rectangle aRect = getParentPosSize();
|
||||
int nW = aRect.Width, nH = aRect.Height;
|
||||
int nVideoW = nW, nVideoH = nH;
|
||||
int nX = 0, nY = 0, nWidth = 0, nHeight = 0;
|
||||
@@ -190,19 +210,16 @@ void Window::ImplLayoutVideoWindow()
|
||||
nX = nY = nWidth = nHeight = 0;
|
||||
}
|
||||
|
||||
IVideoWindow* pVideoWindow = const_cast< IVideoWindow* >( mrPlayer.getVideoWindow() );
|
||||
|
||||
if( pVideoWindow )
|
||||
pVideoWindow->SetWindowPosition( nX, nY, nWidth, nHeight );
|
||||
if (mnFrameWnd && mrPlayer.GetVideoWidth() && mrPlayer.GetVideoHeight() )
|
||||
SetWindowPos( mnFrameWnd, HWND_TOP, nX, nY, nWidth, nHeight, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
bool Window::create( const uno::Sequence< uno::Any >& rArguments )
|
||||
{
|
||||
IVideoWindow* pVideoWindow = const_cast< IVideoWindow* >( mrPlayer.getVideoWindow() );
|
||||
static WNDCLASSW* mpWndClass = lcl_getWndClass();
|
||||
|
||||
if( !mnFrameWnd && pVideoWindow && mpWndClass )
|
||||
if( !mnFrameWnd && mpWndClass )
|
||||
{
|
||||
awt::Rectangle aRect;
|
||||
sal_IntPtr nWnd;
|
||||
@@ -220,25 +237,31 @@ bool Window::create( const uno::Sequence< uno::Any >& rArguments )
|
||||
if( mnFrameWnd )
|
||||
{
|
||||
SetWindowLongPtrW( mnFrameWnd, 0, reinterpret_cast<LONG_PTR>(this) );
|
||||
|
||||
pVideoWindow->put_Owner( reinterpret_cast<OAHWND>(mnFrameWnd) );
|
||||
pVideoWindow->put_MessageDrain( reinterpret_cast<OAHWND>(mnFrameWnd) );
|
||||
pVideoWindow->put_WindowStyle( WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN );
|
||||
|
||||
mrPlayer.setNotifyWnd(mnFrameWnd);
|
||||
|
||||
if (SUCCEEDED(mrPlayer.InitializeWindow(false)))
|
||||
{
|
||||
sal_IntPtr pIntPtr = 0;
|
||||
if ((rArguments.getLength() >= 4) && (rArguments[3] >>= pIntPtr) && pIntPtr)
|
||||
{
|
||||
auto pItem = reinterpret_cast<const avmedia::MediaItem*>(pIntPtr);
|
||||
if (pItem->getState() == avmedia::MediaState::Play)
|
||||
{
|
||||
mrPlayer.setAutoPlayBack(true);
|
||||
}
|
||||
}
|
||||
meZoomLevel = media::ZoomLevel_FIT_TO_WINDOW;
|
||||
ImplLayoutVideoWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
::avmedia::MediaWindow::executeFormatErrorBox(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return( mnFrameWnd != nullptr );
|
||||
}
|
||||
|
||||
void Window::processGraphEvent()
|
||||
{
|
||||
mrPlayer.processEvent();
|
||||
}
|
||||
|
||||
void Window::updatePointer()
|
||||
{
|
||||
@@ -323,16 +346,9 @@ awt::Rectangle SAL_CALL Window::getPosSize()
|
||||
|
||||
void SAL_CALL Window::setVisible( sal_Bool bVisible )
|
||||
{
|
||||
if( mnFrameWnd )
|
||||
{
|
||||
IVideoWindow* pVideoWindow = const_cast< IVideoWindow* >( mrPlayer.getVideoWindow() );
|
||||
|
||||
if( pVideoWindow )
|
||||
pVideoWindow->put_Visible( bVisible ? OATRUE : OAFALSE );
|
||||
|
||||
if( mnFrameWnd && mrPlayer.GetVideoWidth() && mrPlayer.GetVideoHeight() )
|
||||
::ShowWindow( mnFrameWnd, bVisible ? SW_SHOW : SW_HIDE );
|
||||
}
|
||||
}
|
||||
|
||||
void SAL_CALL Window::setEnable( sal_Bool bEnable )
|
||||
{
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
|
||||
#include <com/sun/star/media/XPlayerWindow.hpp>
|
||||
|
||||
struct IVideoWindow;
|
||||
|
||||
namespace avmedia::win {
|
||||
|
||||
class Player;
|
||||
@@ -46,7 +44,6 @@ public:
|
||||
~Window() override;
|
||||
|
||||
bool create( const css::uno::Sequence< css::uno::Any >& aArguments );
|
||||
void processGraphEvent();
|
||||
void updatePointer();
|
||||
|
||||
// XPlayerWindow
|
||||
@@ -93,6 +90,7 @@ public:
|
||||
void fireKeyReleasedEvent( const css::awt::KeyEvent& rEvt );
|
||||
void fireSetFocusEvent( const css::awt::FocusEvent& rEvt );
|
||||
HWND getParentWnd() const { return mnParentWnd; }
|
||||
Player& getPlayer() const { return mrPlayer; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -110,6 +108,7 @@ private:
|
||||
HWND mnParentWnd;
|
||||
int mnPointerType;
|
||||
|
||||
const css::awt::Rectangle getParentPosSize() const;
|
||||
void ImplLayoutVideoWindow();
|
||||
};
|
||||
|
||||
|
||||
@@ -464,12 +464,16 @@ namespace slideshow::internal
|
||||
|
||||
SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape);
|
||||
auto pMediaObj = dynamic_cast<SdrMediaObj*>(pObj);
|
||||
const avmedia::MediaItem* pMediaItem = nullptr;
|
||||
avmedia::MediaItem* pMediaItem = nullptr;
|
||||
if (pMediaObj)
|
||||
{
|
||||
pMediaItem = &pMediaObj->getMediaProperties();
|
||||
pMediaItem = pMediaObj->getMediaProperties().Clone();
|
||||
}
|
||||
|
||||
// TODO In slideshow we cannot play/pause/stop the video
|
||||
if (pMediaItem)
|
||||
pMediaItem->setState(avmedia::MediaState::Play);
|
||||
|
||||
uno::Sequence< uno::Any > aArgs{
|
||||
uno::Any(nParentWindowHandle),
|
||||
uno::Any(aAWTRect),
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<property name="show-arrow">False</property>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="open">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="no-show-all">True</property>
|
||||
<property name="label" translatable="yes" context="medialine|toolbutton_open">Open</property>
|
||||
<property name="use-underline">True</property>
|
||||
@@ -43,6 +44,7 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="apply">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="no-show-all">True</property>
|
||||
<property name="label" translatable="yes" context="medialine|toolbutton_apply">Apply</property>
|
||||
<property name="use-underline">True</property>
|
||||
@@ -66,6 +68,7 @@
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="play">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" context="medialine|toolbutton_play">Play</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">avmedia/res/av02049.png</property>
|
||||
@@ -78,6 +81,7 @@
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="pause">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" context="medialine|toolbutton_pause">Pause</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">avmedia/res/av02050.png</property>
|
||||
@@ -90,6 +94,7 @@
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="stop">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" context="medialine|toolbutton_stop">Stop</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">avmedia/res/av02051.png</property>
|
||||
@@ -112,6 +117,7 @@
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="loop">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" context="medialine|toolbutton_loop">Repeat</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">avmedia/res/av02052.png</property>
|
||||
@@ -196,6 +202,7 @@
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="mute">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" context="medialine|toolbutton_mute">Mute</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">avmedia/res/av02054.png</property>
|
||||
@@ -211,11 +218,13 @@
|
||||
<property name="can-focus">True</property>
|
||||
<child>
|
||||
<object class="GtkScale" id="volumeslider">
|
||||
<property name="width-request">50</property>
|
||||
<property name="width-request">150</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="opacity">0.9882352941176471</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="adjustment">adjustment2</property>
|
||||
<property name="draw-value">False</property>
|
||||
</object>
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
<child>
|
||||
<object class="GtkToolButton" id="open">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" context="mediawindow|toolbutton_open">Open</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">avmedia/res/av02048.png</property>
|
||||
@@ -109,6 +110,7 @@
|
||||
<child>
|
||||
<object class="GtkToolButton" id="apply">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" context="mediawindow|toolbutton_apply">Apply</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">avmedia/res/av02053.png</property>
|
||||
@@ -131,6 +133,7 @@
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="play">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" context="mediawindow|toolbutton_play">Play</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">avmedia/res/av02049.png</property>
|
||||
@@ -143,6 +146,7 @@
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="pause">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" context="mediawindow|toolbutton_pause">Pause</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">avmedia/res/av02050.png</property>
|
||||
@@ -155,6 +159,7 @@
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="stop">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" context="mediawindow|toolbutton_stop">Stop</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">avmedia/res/av02051.png</property>
|
||||
@@ -177,6 +182,7 @@
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="loop">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" context="mediawindow|toolbutton_loop">Repeat</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">avmedia/res/av02052.png</property>
|
||||
@@ -206,6 +212,7 @@
|
||||
<child>
|
||||
<object class="GtkToggleToolButton" id="mute">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes" context="mediawindow|toolbutton_mute">Mute</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="icon-name">avmedia/res/av02054.png</property>
|
||||
@@ -224,10 +231,13 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScale" id="volumeslider">
|
||||
<property name="width-request">150</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="opacity">0.9882352941176471</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="adjustment">adjustment2</property>
|
||||
<property name="draw-value">False</property>
|
||||
</object>
|
||||
|
||||
Reference in New Issue
Block a user