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:
Balazs Varga
2025-07-23 20:34:23 +02:00
parent 056ba8d2db
commit 65d72f816a
11 changed files with 749 additions and 367 deletions
+8
View File
@@ -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,\
+118 -97
View File
@@ -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;
+3 -3
View File
@@ -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
View File
@@ -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 };
+56 -27
View File
@@ -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
-2
View File
@@ -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: */
+50 -34
View File
@@ -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 )
{
+2 -3
View File
@@ -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),
+10 -1
View File
@@ -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>
+10
View File
@@ -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>