/* This file is part of the KDE project.
Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2.1 or 3 of the License.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library. If not, see .
*/
#include // for QApplication::activeWindow
#include
#include
#include
#include // for CCoeEnv
#include
#include "mmf_videoplayer.h"
#include "utils.h"
#ifndef QT_NO_DEBUG
#include "objectdump.h"
#endif
QT_BEGIN_NAMESPACE
using namespace Phonon;
using namespace Phonon::MMF;
/*! \class MMF::VideoPlayer
\internal
*/
//-----------------------------------------------------------------------------
// Constructor / destructor
//-----------------------------------------------------------------------------
MMF::VideoPlayer::VideoPlayer(MediaObject *parent, const AbstractPlayer *player)
: AbstractMediaPlayer(parent, player)
, m_wsSession(CCoeEnv::Static()->WsSession())
, m_screenDevice(*CCoeEnv::Static()->ScreenDevice())
, m_window(0)
, m_totalTime(0)
, m_pendingChanges(false)
, m_dsaActive(false)
{
construct();
}
void MMF::VideoPlayer::construct()
{
TRACE_CONTEXT(VideoPlayer::VideoPlayer, EVideoApi);
TRACE_ENTRY_0();
getVideoWindow();
const TInt priority = 0;
const TMdaPriorityPreference preference = EMdaPriorityPreferenceNone;
CVideoPlayerUtility *player = 0;
QT_TRAP_THROWING(player = CVideoPlayerUtility::NewL
(
*this,
priority, preference,
m_wsSession, m_screenDevice,
*m_window,
m_videoRect, m_videoRect
)
);
m_player.reset(player);
// CVideoPlayerUtility::NewL starts DSA
m_dsaActive = true;
m_player->RegisterForVideoLoadingNotification(*this);
TRACE_EXIT_0();
}
MMF::VideoPlayer::~VideoPlayer()
{
TRACE_CONTEXT(VideoPlayer::~VideoPlayer, EVideoApi);
TRACE_ENTRY_0();
// QObject destructor removes all signal-slot connections involving this
// object, so we do not need to disconnect from m_videoOutput here.
TRACE_EXIT_0();
}
CVideoPlayerUtility* MMF::VideoPlayer::nativePlayer() const
{
return m_player.data();
}
//-----------------------------------------------------------------------------
// Public API
//-----------------------------------------------------------------------------
void MMF::VideoPlayer::doPlay()
{
TRACE_CONTEXT(VideoPlayer::doPlay, EVideoApi);
applyPendingChanges();
m_player->Play();
}
void MMF::VideoPlayer::doPause()
{
TRACE_CONTEXT(VideoPlayer::doPause, EVideoApi);
TRAPD(err, m_player->PauseL());
if (KErrNone != err && state() != ErrorState) {
TRACE("PauseL error %d", err);
setError(tr("Pause failed"), err);
}
}
void MMF::VideoPlayer::doStop()
{
m_player->Stop();
}
void MMF::VideoPlayer::doSeek(qint64 ms)
{
TRACE_CONTEXT(VideoPlayer::doSeek, EVideoApi);
TRAPD(err, m_player->SetPositionL(TTimeIntervalMicroSeconds(ms * 1000)));
if(KErrNone != err)
setError(tr("Seek failed"), err);
}
int MMF::VideoPlayer::setDeviceVolume(int mmfVolume)
{
TRAPD(err, m_player->SetVolumeL(mmfVolume));
return err;
}
int MMF::VideoPlayer::openFile(RFile& file)
{
TRAPD(err, m_player->OpenFileL(file));
return err;
}
int MMF::VideoPlayer::openUrl(const QString& url)
{
TRAPD(err, m_player->OpenUrlL(qt_QString2TPtrC(url)));
return err;
}
int MMF::VideoPlayer::bufferStatus() const
{
int result = 0;
TRAP_IGNORE(m_player->GetVideoLoadingProgressL(result));
return result;
}
void MMF::VideoPlayer::close()
{
m_player->Close();
}
bool MMF::VideoPlayer::hasVideo() const
{
return true;
}
qint64 MMF::VideoPlayer::currentTime() const
{
TRACE_CONTEXT(VideoPlayer::currentTime, EVideoApi);
TTimeIntervalMicroSeconds us;
TRAPD(err, us = m_player->PositionL())
qint64 result = 0;
if (KErrNone == err) {
result = toMilliSeconds(us);
} else {
TRACE("PositionL error %d", err);
// If we don't cast away constness here, we simply have to ignore
// the error.
const_cast(this)->setError(tr("Getting position failed"), err);
}
return result;
}
qint64 MMF::VideoPlayer::totalTime() const
{
return m_totalTime;
}
//-----------------------------------------------------------------------------
// MVideoPlayerUtilityObserver callbacks
//-----------------------------------------------------------------------------
void MMF::VideoPlayer::MvpuoOpenComplete(TInt aError)
{
TRACE_CONTEXT(VideoPlayer::MvpuoOpenComplete, EVideoApi);
TRACE_ENTRY("state %d error %d", state(), aError);
__ASSERT_ALWAYS(LoadingState == state(), Utils::panic(InvalidStatePanic));
if (KErrNone == aError)
m_player->Prepare();
else
setError(tr("Opening clip failed"), aError);
TRACE_EXIT_0();
}
void MMF::VideoPlayer::MvpuoPrepareComplete(TInt aError)
{
TRACE_CONTEXT(VideoPlayer::MvpuoPrepareComplete, EVideoApi);
TRACE_ENTRY("state %d error %d", state(), aError);
__ASSERT_ALWAYS(LoadingState == state(), Utils::panic(InvalidStatePanic));
TRAPD(err, doPrepareCompleteL(aError));
if (KErrNone == err) {
maxVolumeChanged(m_player->MaxVolume());
if (m_videoOutput)
m_videoOutput->setVideoSize(m_videoFrameSize);
updateVideoRect();
applyPendingChanges();
emit totalTimeChanged(totalTime());
changeState(StoppedState);
} else {
setError(tr("Buffering clip failed"), err);
}
TRACE_EXIT_0();
}
void MMF::VideoPlayer::doPrepareCompleteL(TInt aError)
{
User::LeaveIfError(aError);
// Get frame size
TSize size;
m_player->VideoFrameSizeL(size);
m_videoFrameSize = QSize(size.iWidth, size.iHeight);
// Get duration
m_totalTime = toMilliSeconds(m_player->DurationL());
}
void MMF::VideoPlayer::MvpuoFrameReady(CFbsBitmap &aFrame, TInt aError)
{
TRACE_CONTEXT(VideoPlayer::MvpuoFrameReady, EVideoApi);
TRACE_ENTRY("state %d error %d", state(), aError);
Q_UNUSED(aFrame);
Q_UNUSED(aError); // suppress warnings in release builds
TRACE_EXIT_0();
}
void MMF::VideoPlayer::MvpuoPlayComplete(TInt aError)
{
TRACE_CONTEXT(VideoPlayer::MvpuoPlayComplete, EVideoApi)
TRACE_ENTRY("state %d error %d", state(), aError);
// Call base class function which handles end of playback for both
// audio and video clips.
playbackComplete(aError);
TRACE_EXIT_0();
}
void MMF::VideoPlayer::MvpuoEvent(const TMMFEvent &aEvent)
{
TRACE_CONTEXT(VideoPlayer::MvpuoEvent, EVideoApi);
TRACE_ENTRY("state %d", state());
Q_UNUSED(aEvent);
TRACE_EXIT_0();
}
//-----------------------------------------------------------------------------
// MVideoLoadingObserver callbacks
//-----------------------------------------------------------------------------
void MMF::VideoPlayer::MvloLoadingStarted()
{
bufferingStarted();
}
void MMF::VideoPlayer::MvloLoadingComplete()
{
bufferingComplete();
}
//-----------------------------------------------------------------------------
// Video window updates
//-----------------------------------------------------------------------------
void MMF::VideoPlayer::getVideoWindow()
{
TRACE_CONTEXT(VideoPlayer::getVideoWindow, EVideoInternal);
TRACE_ENTRY_0();
if(m_videoOutput) {
// Dump information to log, only in debug builds
m_videoOutput->dump();
initVideoOutput();
videoWindowChanged();
} else
// Top-level window
m_window = QApplication::activeWindow()->effectiveWinId()->DrawableWindow();
TRACE_EXIT_0();
}
void MMF::VideoPlayer::videoOutputChanged()
{
TRACE_CONTEXT(VideoPlayer::videoOutputChanged, EVideoInternal);
TRACE_ENTRY_0();
if (m_videoOutput) {
initVideoOutput();
videoWindowChanged();
}
TRACE_EXIT_0();
}
void MMF::VideoPlayer::initVideoOutput()
{
m_videoOutput->winId();
m_videoOutput->setVideoSize(m_videoFrameSize);
bool connected = connect(
m_videoOutput, SIGNAL(videoWindowChanged()),
this, SLOT(videoWindowChanged())
);
Q_ASSERT(connected);
connected = connect(
m_videoOutput, SIGNAL(beginVideoWindowNativePaint()),
this, SLOT(suspendDirectScreenAccess())
);
Q_ASSERT(connected);
connected = connect(
m_videoOutput, SIGNAL(endVideoWindowNativePaint()),
this, SLOT(resumeDirectScreenAccess())
);
Q_ASSERT(connected);
connected = connect(
m_videoOutput, SIGNAL(aspectRatioChanged()),
this, SLOT(aspectRatioChanged())
);
Q_ASSERT(connected);
connected = connect(
m_videoOutput, SIGNAL(scaleModeChanged()),
this, SLOT(scaleModeChanged())
);
Q_ASSERT(connected);
// Suppress warnings in release builds
Q_UNUSED(connected);
}
void MMF::VideoPlayer::videoWindowChanged()
{
TRACE_CONTEXT(VideoPlayer::videoOutputRegionChanged, EVideoInternal);
TRACE_ENTRY("state %d", state());
m_window = m_videoOutput->videoWindow();
updateVideoRect();
TRACE_EXIT_0();
}
void MMF::VideoPlayer::suspendDirectScreenAccess()
{
m_dsaWasActive = stopDirectScreenAccess();
}
void MMF::VideoPlayer::resumeDirectScreenAccess()
{
if(m_dsaWasActive) {
startDirectScreenAccess();
m_dsaWasActive = false;
}
}
void MMF::VideoPlayer::startDirectScreenAccess()
{
if(!m_dsaActive) {
TRAPD(err, m_player->StartDirectScreenAccessL());
if(KErrNone == err)
m_dsaActive = true;
else
setError(tr("Video display error"), err);
}
}
bool MMF::VideoPlayer::stopDirectScreenAccess()
{
const bool dsaWasActive = m_dsaActive;
if(m_dsaActive) {
TRAPD(err, m_player->StopDirectScreenAccessL());
if(KErrNone == err)
m_dsaActive = false;
else
setError(tr("Video display error"), err);
}
return dsaWasActive;
}
// Helper function for aspect ratio / scale mode handling
QSize scaleToAspect(const QSize& srcRect, int aspectWidth, int aspectHeight)
{
const qreal aspectRatio = qreal(aspectWidth) / aspectHeight;
int width = srcRect.width();
int height = srcRect.width() / aspectRatio;
if (height > srcRect.height()){
height = srcRect.height();
width = srcRect.height() * aspectRatio;
}
return QSize(width, height);
}
void MMF::VideoPlayer::updateVideoRect()
{
QRect videoRect;
QRect windowRect = m_videoOutput->videoWindowRect();
// Clip to physical window size
// This is due to a defect in the layout when running on S60 3.2, which
// results in the rectangle of the video widget extending outside the
// screen in certain circumstances. These include the initial startup
// of the mediaplayer demo in portrait mode. When this rectangle is
// passed to the CVideoPlayerUtility, no video is rendered.
const TSize screenSize = m_screenDevice.SizeInPixels();
const QRect screenRect(0, 0, screenSize.iWidth, screenSize.iHeight);
windowRect = windowRect.intersected(screenRect);
const QSize windowSize = windowRect.size();
// Calculate size of smallest rect which contains video frame size
// and conforms to aspect ratio
switch (m_videoOutput->aspectRatio()) {
case Phonon::VideoWidget::AspectRatioAuto:
videoRect.setSize(m_videoFrameSize);
break;
case Phonon::VideoWidget::AspectRatioWidget:
videoRect.setSize(windowSize);
break;
case Phonon::VideoWidget::AspectRatio4_3:
videoRect.setSize(scaleToAspect(m_videoFrameSize, 4, 3));
break;
case Phonon::VideoWidget::AspectRatio16_9:
videoRect.setSize(scaleToAspect(m_videoFrameSize, 16, 9));
break;
}
// Scale to fill the window width
const int windowWidth = windowSize.width();
const int windowHeight = windowSize.height();
const qreal windowScaleFactor = qreal(windowWidth) / videoRect.width();
int videoWidth = windowWidth;
int videoHeight = videoRect.height() * windowScaleFactor;
const qreal windowToVideoHeightRatio = qreal(windowHeight) / videoHeight;
switch(m_videoOutput->scaleMode()) {
case Phonon::VideoWidget::ScaleAndCrop:
if(videoHeight < windowHeight) {
videoWidth *= windowToVideoHeightRatio;
videoHeight = windowHeight;
}
break;
case Phonon::VideoWidget::FitInView:
default:
if(videoHeight > windowHeight) {
videoWidth *= windowToVideoHeightRatio;
videoHeight = windowHeight;
}
break;
}
// Calculate scale factors
m_scaleWidth = 100.0f * videoWidth / m_videoFrameSize.width();
m_scaleHeight = 100.0f * videoHeight / m_videoFrameSize.height();
m_videoRect = qt_QRect2TRect(windowRect);
if (state() == LoadingState)
m_pendingChanges = true;
else {
applyVideoWindowChange();
m_pendingChanges = false;
}
}
void MMF::VideoPlayer::aspectRatioChanged()
{
TRACE_CONTEXT(VideoPlayer::aspectRatioChanged, EVideoInternal);
TRACE_ENTRY("state %d aspectRatio %d", state());
updateVideoRect();
TRACE_EXIT_0();
}
void MMF::VideoPlayer::scaleModeChanged()
{
TRACE_CONTEXT(VideoPlayer::scaleModeChanged, EVideoInternal);
TRACE_ENTRY("state %d", state());
updateVideoRect();
TRACE_EXIT_0();
}
#ifndef QT_NO_DEBUG
// The following code is for debugging problems related to video visibility. It allows
// the VideoPlayer instance to query the window server in order to determine the
// DSA drawing region for the video window.
class CDummyAO : public CActive
{
public:
CDummyAO() : CActive(CActive::EPriorityStandard) { CActiveScheduler::Add(this); }
void RunL() { }
void DoCancel() { }
TRequestStatus& Status() { return iStatus; }
void SetActive() { CActive::SetActive(); }
};
void getDsaRegion(RWsSession &session, const RWindowBase &window)
{
RDirectScreenAccess dsa(session);
TInt err = dsa.Construct();
CDummyAO ao;
RRegion* region;
err = dsa.Request(region, ao.Status(), window);
ao.SetActive();
dsa.Close();
ao.Cancel();
if (region) {
qDebug() << "Phonon::MMF::getDsaRegion count" << region->Count();
for (int i=0; iCount(); ++i) {
const TRect& rect = region->RectangleList()[i];
qDebug() << "Phonon::MMF::getDsaRegion rect"
<< rect.iTl.iX << rect.iTl.iY << rect.iBr.iX << rect.iBr.iY;
}
region->Close();
}
}
#endif // _DEBUG
void MMF::VideoPlayer::applyPendingChanges()
{
if(m_pendingChanges)
applyVideoWindowChange();
m_pendingChanges = false;
}
void MMF::VideoPlayer::applyVideoWindowChange()
{
TRACE_CONTEXT(VideoPlayer::applyVideoWindowChange, EVideoInternal);
TRACE_ENTRY_0();
#ifndef QT_NO_DEBUG
getDsaRegion(m_wsSession, *m_window);
#endif
static const TBool antialias = ETrue;
TRAPD(err, m_player->SetScaleFactorL(m_scaleWidth, m_scaleHeight, antialias));
if(KErrNone != err) {
TRACE("SetScaleFactorL (1) err %d", err);
setError(tr("Video display error"), err);
}
if(KErrNone == err) {
TRAP(err,
m_player->SetDisplayWindowL
(
m_wsSession, m_screenDevice,
*m_window,
m_videoRect, m_videoRect
)
);
if (KErrNone != err) {
TRACE("SetDisplayWindowL err %d", err);
setError(tr("Video display error"), err);
} else {
m_dsaActive = true;
TRAP(err, m_player->SetScaleFactorL(m_scaleWidth, m_scaleHeight, antialias));
if(KErrNone != err) {
TRACE("SetScaleFactorL (2) err %d", err);
setError(tr("Video display error"), err);
}
}
}
TRACE_EXIT_0();
}
//-----------------------------------------------------------------------------
// Metadata
//-----------------------------------------------------------------------------
int MMF::VideoPlayer::numberOfMetaDataEntries() const
{
int numberOfEntries = 0;
TRAP_IGNORE(numberOfEntries = m_player->NumberOfMetaDataEntriesL());
return numberOfEntries;
}
QPair MMF::VideoPlayer::metaDataEntry(int index) const
{
CMMFMetaDataEntry *entry = 0;
QT_TRAP_THROWING(entry = m_player->MetaDataEntryL(index));
return QPair(qt_TDesC2QString(entry->Name()), qt_TDesC2QString(entry->Value()));
}
QT_END_NAMESPACE