/*
* Copyright (c) 2002-2007 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: This class is used for playing video.
*
*/
// INCLUDE FILES
#include <mmf/server/mmffile.h>
#include <jdebug.h>
#include "cmmavideoplayer.h"
#include "mmmadisplay.h"
#include "cmmaeventsource.h"
#include "cmmasurfacewindow.h"
// CONSTANTS
const TInt KErrorMessageSize = 32;
_LIT(KVideoControlName, "VideoControl");
_LIT(KAccMonError, "Accessory monitor Error: %d");
CMMAVideoPlayer* CMMAVideoPlayer::NewLC(
CMMAMMFResolver* aResolver)
{
CMMAVideoPlayer* self =
new(ELeave) CMMAVideoPlayer(aResolver);
CleanupStack::PushL(self);
self->ConstructL();
return self;
}
CMMAVideoPlayer::~CMMAVideoPlayer()
{
DEBUG("MMA::CMMAVideoPlayer::~CMMAVideoPlayer");
// Window is not able to send any
// callback requests to UI from now.
if (iSurfaceWindow)
{
iSurfaceWindow->SetDisplay(NULL);
}
if (iDisplay && iDisplay->HasContainer())
{
// Window will delete itself
// after all pending events are processed
// (lazy delete)
iDisplay->UIGetCallback(
*iSurfaceWindow, CMMASurfaceWindow::EDestroyWindow);
}
else
{
delete iSurfaceWindow;
}
if (iDisplay)
{
TRAPD(err, iDisplay->SetWindowL(NULL));
if (err != KErrNone)
{
__ASSERT_DEBUG(EFalse, User::Invariant());
}
}
iAccMonCapabilityArray.Close();
delete iAccMonitor;
delete iEmptySnapshotImage;
delete iActiveSchedulerWait;
}
CMMAVideoPlayer::CMMAVideoPlayer(
CMMAMMFResolver* aResolver):
CMMAAudioPlayer(aResolver),
iVideoControllerCustomCommands(iController),
iVideoPlayControllerCustomCommands(iController),
iVideoPlaySurfaceSupportCustomCommands(iController),
isHDMICableConnected(EFalse)
{
iMMASurface.iPrevSurfaceAvailable = EFalse;
}
void CMMAVideoPlayer::ConstructL()
{
DEBUG("MMA::CMMAVideoPlayer::ConstructL +");
CMMAAudioPlayer::ConstructL();
iActiveSchedulerWait = new(ELeave)CActiveSchedulerWait;
iAccMonitor = CAccMonitor::NewL();
iAccMonCapabilityArray.Append(KAccMonAVDevice);
iAccMonitor->StartObservingL(this, iAccMonCapabilityArray);
DEBUG("MMA::CMMAVideoPlayer::ConstructL -");
}
// static cleanup function
static void PointerArrayCleanup(TAny* aArray)
{
static_cast<RPointerArray<CAccMonitorInfo>*>(aArray)->ResetAndDestroy();
}
EXPORT_C void CMMAVideoPlayer::SetPlayerListenerObjectL(jobject aListenerObject,
JNIEnv* aJni,
MMMAEventPoster* aEventPoster)
{
CMMAPlayer::SetPlayerListenerObjectL(aListenerObject,
aJni,
aEventPoster);
// this method must be called only ones
__ASSERT_DEBUG(!iSurfaceWindow, User::Invariant());
// get audio/video cable connected status
TBool avCableConnStatus(EFalse);
RConnectedAccessories connectedAccessories;
TCleanupItem arrayCleanup(PointerArrayCleanup,
&connectedAccessories);
CleanupStack::PushL(arrayCleanup);
TInt accCount =
iAccMonitor->GetConnectedAccessoriesL(connectedAccessories);
for (TInt i = 0; i < accCount; i++)
{
if (KAccMonAVDevice == connectedAccessories[i]->AccDeviceType())
{
avCableConnStatus = ETrue;
break;
}
}
CleanupStack::PopAndDestroy();
// create window for videoplayer
// event poster is always CMMAEventSource type.
iSurfaceWindow = CMMASurfaceWindow::NewL(
static_cast< CMMAEventSource* >(iEventPoster),
this, avCableConnStatus);
}
EXPORT_C void CMMAVideoPlayer::SetDisplayL(MMMADisplay* aDisplay)
{
// now it is ready to draw
iDisplay = aDisplay;
iSurfaceWindow->SetDisplay(aDisplay);
iDisplay->SetWindowL(iSurfaceWindow);
// if state < prefeteched then we dont know actual source size yet
// and it will be set after prefetch
if (iState >= EPrefetched)
{
SourceSizeChanged();
}
}
void CMMAVideoPlayer::RealizeL()
{
DEBUG("CMMAVideoPlayer::RealizeL");
// DataSource must have at least 1 stream or
// we must have file to play
if ((iSourceStreams.Count() == 0) && !iFileName)
{
User::Leave(KErrNotEnoughStreams);
}
// If file locator is used, then file is prefetched
// in realized state so that FramePositioningControl
// can acquire frame count in REALIZED state
if (iFileName)
{
PrefetchFileL();
if (!iActiveSchedulerWait->IsStarted())
{
iActiveSchedulerWait->Start();
}
// If the player has not changed state during wait,
// Then there is an error condition.
if (iState != ERealized)
{
User::Leave(KErrNotSupported);
}
}
else
{
CMMAPlayer::RealizeL();
}
}
void CMMAVideoPlayer::PrefetchL()
{
DEBUG("CMMAVideoPlayer::PrefetchL");
if (iFileName)
{
// File has already been prefetched when realizing
// If initDisplayMode was called before prefetch,
// then the display must notified about source size.
if (iDisplay)
{
SourceSizeChanged();
}
ChangeState(EPrefetched);
PostActionCompleted(KErrNone);
}
else
{
// Using TDes -- load the whole video
iSourceStreams[ 0 ]->ReadAllL();
}
// CMMASourceStream will notify with ReadCompleted
}
EXPORT_C void CMMAVideoPlayer::ReadCompletedL(TInt aStatus, const TDesC8& aData)
{
DEBUG_INT("CMMAVideoPlayer::ReadCompletedL: status = %d", aStatus);
if (aStatus < KErrNone)
{
PostActionCompleted(aStatus);
}
else
{
TRAPD(err, PrefetchDataL(aData));
if (err != KErrNone)
{
PostActionCompleted(err);
}
// action completed event will be delivered from handleEvent
}
}
void CMMAVideoPlayer::HandleEvent(const TMMFEvent& aEvent)
{
DEBUG_INT("MMA:CMMAVideoPlayer::HandleEvent %d", aEvent.iEventType.iUid);
// event KMMFEventCategoryPlaybackComplete is handled by both Video
// and Audio players. first it should be handled by Video player
if (aEvent.iEventType == KMMFEventCategoryPlaybackComplete)
{
iSurfaceWindow->RemoveSurface();
}
// Start player again and ignore error
if ((aEvent.iEventType == KMMFEventCategoryVideoPlayerGeneralError) &&
(aEvent.iErrorCode == KMMVideoBlitError))
{
// incase of HDMI cable is inserted, start the player again before ignoring the error otherwise simply ignore
if(isHDMICableConnected)
{
TRAPD(error, StartL(EFalse));
if (KErrNone != error)
{
DEBUG_INT("MMA:CMMAVideoPlayer::HandleEvent, StartL() error %d",
error);
}
return;
}
else // no HDMI cable is inserted, Hence ignore the error.
{
return;
}
}
// KNotCompleteVideoError can be notified when video is not complete
// ( missing sound ) but still can be played. Because
// CMMAAudioPlayer::HandleEvent fails with all negative error codes,
// do not call it with KNotCompleteVideoError error when preparing.
if ((aEvent.iErrorCode != KNotCompleteVideoError) ||
(aEvent.iEventType != KMMFEventCategoryVideoPrepareComplete))
{
CMMAAudioPlayer::HandleEvent(aEvent);
}
if (aEvent.iEventType == KMMFEventCategoryVideoSurfaceCreated)
{
if (aEvent.iErrorCode == KErrNone)
{
TSurfaceId surfaceId;
TRect cropRect;
TVideoAspectRatio pixelAspectRatio;
iVideoPlaySurfaceSupportCustomCommands.GetSurfaceParameters(surfaceId,
cropRect,
pixelAspectRatio);
if (iMMASurface.iPrevSurfaceAvailable)
{
// free Surface
TInt error = iVideoPlaySurfaceSupportCustomCommands.SurfaceRemoved(iMMASurface.iPrevSurfaceId);
if (KErrNone != error)
{
DEBUG_INT("CMMAVideoPlayer::HandleEvent:SurfaceRemoved error, %d", aEvent.iErrorCode);
}
}
iMMASurface.iPrevSurfaceId = surfaceId;
iMMASurface.iPrevSurfaceAvailable = ETrue;
iSurfaceWindow->SetSurfaceParameters(surfaceId,
cropRect,
pixelAspectRatio);
DEBUG("CMMAVideoPlayer::HandleEvent: KMMFEventCategoryVideoSurfaceCreated, surface parameters set");
}
else
{
DEBUG_INT("CMMAVideoPlayer::HandleEvent: error getting surface parameters, %d", aEvent.iErrorCode);
}
}
else if (aEvent.iEventType == KMMFEventCategoryVideoSurfaceParametersChanged)
{
if (aEvent.iErrorCode == KErrNone)
{
TSurfaceId surfaceId;
TRect cropRect;
TVideoAspectRatio pixelAspectRatio;
iVideoPlaySurfaceSupportCustomCommands.GetSurfaceParameters(surfaceId,
cropRect,
pixelAspectRatio);
if (iMMASurface.iPrevSurfaceAvailable)
{
// free Surface
TInt error = iVideoPlaySurfaceSupportCustomCommands.SurfaceRemoved(iMMASurface.iPrevSurfaceId);
if (KErrNone != error)
{
DEBUG_INT("CMMAVideoPlayer::HandleEvent:SurfaceRemoved error, %d", aEvent.iErrorCode);
}
}
iMMASurface.iPrevSurfaceAvailable = ETrue;
iMMASurface.iPrevSurfaceId = surfaceId;
iSurfaceWindow->SetChangedSurfaceParameters(surfaceId,
cropRect,
pixelAspectRatio);
DEBUG("CMMAVideoPlayer::HandleEvent: KMMFEventCategoryVideoSurfaceParametersChanged");
}
else
{
DEBUG_INT("CMMAVideoPlayer::HandleEvent: surface parameters changed error, %d", aEvent.iErrorCode);
}
}
else if (aEvent.iEventType == KMMFEventCategoryVideoRemoveSurface)
{
if (aEvent.iErrorCode == KErrNone)
{
if (iMMASurface.iPrevSurfaceAvailable)
{
// free Surface
TInt error = iVideoPlaySurfaceSupportCustomCommands.SurfaceRemoved(iMMASurface.iPrevSurfaceId);
if (KErrNone != error)
{
DEBUG_INT("CMMAVideoPlayer::HandleEvent:SurfaceRemoved error, %d", aEvent.iErrorCode);
}
iMMASurface.iPrevSurfaceAvailable = EFalse;
}
}
else
{
DEBUG_INT("CMMAVideoPlayer::HandleEvent:KMMFEventCategoryVideoRemoveSurface error, %d", aEvent.iErrorCode);
}
}
// video opened, preparing
else if (aEvent.iEventType == KMMFEventCategoryVideoOpenComplete)
{
if (aEvent.iErrorCode == KErrNone)
{
TInt error = iVideoPlaySurfaceSupportCustomCommands.UseSurfaces();
DEBUG_INT("MMA::CMMAVideoPlayer::HandleEvent::After UseSurfaces(), error = %d", error);
TInt prepareError(iVideoPlayControllerCustomCommands.Prepare());
if (prepareError != KErrNone)
{
// opening failed, notifying java
PostActionCompleted(prepareError);
}
}
else
{
// opening failed, notifying java
PostActionCompleted(aEvent.iErrorCode);
}
}
// final state of prefetch ( prepare completed )
else if (aEvent.iEventType == KMMFEventCategoryVideoPrepareComplete)
{
// This callback must be handled differently depending on whether
// player is created for a file locator or for a stream. When file
// locator is used, this callback is made in realized state. For
// stream it is made in prefetched state.
PrepareDisplay();
if (iFileName)
{
DEBUG("CMMAVideoPlayer::HandleEvent: Using filename, change state to REALIZED");
// If there is an error condition, then the player state is not
// changed, which indicates the error condition to StartL when
// the ActiveSchedulerWait returns. KNotCompleteVideoError is not
// considered as an error condition, instead it indicates that some
// elements of the media cannot be played (e.g. video OR audio)
if (aEvent.iErrorCode == KErrNone ||
aEvent.iErrorCode == KNotCompleteVideoError)
{
ChangeState(ERealized);
}
__ASSERT_DEBUG(iActiveSchedulerWait->IsStarted(), User::Invariant());
iActiveSchedulerWait->AsyncStop();
}
else
{
DEBUG("CMMAVideoPlayer::HandleEvent: Not using filename, change state to PREFETCHED");
CompletePrefetch(aEvent.iErrorCode);
}
}
else // in case of any other event
{
if (aEvent.iErrorCode != KErrNone)
{
PostActionCompleted(aEvent.iErrorCode); //some other error
}
}
}
void CMMAVideoPlayer::CompletePrefetch(TInt aError)
{
DEBUG_INT("CMMAVideoPlayer::CompletePrefetch + error = %d",aError);
// Post KNotCompleteVideoError as KErrNone to the Java side, because
// video can be played.
if (aError == KNotCompleteVideoError)
{
DEBUG("CMMAVideoPlayer::CompletePrefetch KNotCompleteVideoError ");
// release java
PostActionCompleted(KErrNone);
}
else
{
DEBUG("CMMAVideoPlayer::CompletePrefetch CompleteVideoError ");
// release java
PostActionCompleted(aError);
}
if (aError == KErrNone || aError == KNotCompleteVideoError)
{
ChangeState(EPrefetched);
}
DEBUG("CMMAVideoPlayer::CompletePrefetch - ");
}
void CMMAVideoPlayer::PrepareDisplay()
{
DEBUG("CMMAVideoPlayer::PrepareDisplay +");
// construction should have leaved if iSurfaceWindow does not exist
__ASSERT_DEBUG(iSurfaceWindow,
User::Panic(_L("CMMVideoPlayer::iSurfaceWindow is null"),
KErrArgument));
//First place where we are certain that source size can be fetched
TSize sourceSize;
TInt err = iVideoControllerCustomCommands.GetVideoFrameSize(sourceSize);
DEBUG_INT("MID::CMMAVideoPlayer::PrepareDisplay: GetVideoFrameSize err = %d", err);
// Still we did not get the size of video
if ((err != KErrNone) ||
(sourceSize.iWidth <= 0) ||
(sourceSize.iHeight <= 0))
{
DEBUG("MID::CMMAVideoPlayer::PrepareDisplay: No sourcesize found, using SurfaceWindow size");
// setting size to window size (client rect)
sourceSize = iSurfaceWindow->WindowSize();
}
// If 1x1 was got (the default size of form), it must be replaced
// with a valid size as controller will not accept 1x1.
if ((sourceSize.iWidth < KMMAVideoMinDimension) ||
(sourceSize.iHeight < KMMAVideoMinDimension))
{
DEBUG("MID::CMMAVideoPlayer::PrepareDisplay: Unacceptable source size, using failsafe");
// This is a special case and ought to be used only in
// the rare case that real size is not got from stream.
sourceSize = TSize(KMMAVideoMinDimension, KMMAVideoMinDimension);
}
iSourceSize = sourceSize;
// If init has been already done
if (iDisplay)
{
DEBUG("MID::CMMAVideoPlayer::PrepareDisplay: display exists, changing source size");
SourceSizeChanged();
}
// Setting (in)visible if something has changed the DSA state
// (e.g. prepare). If initDisplayMode is not called, this will always
// set visibility to false.
iSurfaceWindow->SetVisible(iSurfaceWindow->IsVisible(), EFalse);
DEBUG("CMMAVideoPlayer::PrepareDisplay -");
}
EXPORT_C const TDesC& CMMAVideoPlayer::Type()
{
return KMMAVideoPlayer;
}
EXPORT_C TSize CMMAVideoPlayer::SourceSize()
{
return iSourceSize;
}
EXPORT_C MMMASnapshot::TEncoding CMMAVideoPlayer::TakeSnapshotL(TRequestStatus* aStatus,
const TSize& /*aSize*/,
const CMMAImageSettings& /*aSettings*/)
{
if (iEmptySnapshotImage)
{
// Ealier snapshot was not got with SnapshotBitmap method.
User::Leave(KErrInUse);
}
// frame can't be got from video player, but TCK requires that it should
// be available. So returning empty image
iEmptySnapshotImage = new(ELeave) CFbsBitmap();
User::LeaveIfError(iEmptySnapshotImage->Create(TSize(1, 1),
EColor4K));
User::RequestComplete(aStatus, KErrNone);
// Return raw bitmap encoding and thus SnapshotEncoded() should not
// get called later on.
return EBitmap;
}
EXPORT_C CFbsBitmap* CMMAVideoPlayer::SnapshotBitmap()
{
// snapshot is not supported, returning empty image
CFbsBitmap* image = iEmptySnapshotImage;
// ownership is transferred to caller
iEmptySnapshotImage = NULL;
return image;
}
EXPORT_C HBufC8* CMMAVideoPlayer::SnapshotEncoded()
{
// This method should never be called.
// Asserted in debug build to be sure.
__ASSERT_DEBUG(EFalse, User::Invariant());
return NULL;
}
EXPORT_C void CMMAVideoPlayer::NotifyWithStringEvent(
CMMAPlayerEvent::TEventType aEventType,
const TDesC& aStringEventData)
{
PostStringEvent(aEventType, aStringEventData);
}
EXPORT_C MMMASnapshot* CMMAVideoPlayer::SnapshoterL()
{
return this;
}
void CMMAVideoPlayer::SourceSizeChanged()
{
iDisplay->SourceSizeChanged(iSourceSize);
NotifyWithStringEvent(CMMAPlayerEvent::ESizeChanged, KVideoControlName);
}
void CMMAVideoPlayer::ConnectedL(CAccMonitorInfo* aAccessoryInfo)
{
TAccMonCapability deviceType = aAccessoryInfo->AccDeviceType() ;
DEBUG_INT("MID::CMMAVideoPlayer::ConnectedL %d", deviceType);
if (iSurfaceWindow && (deviceType == KAccMonAVDevice))
{
isHDMICableConnected = ETrue;
iSurfaceWindow->SetAVCableConnStatus(ETrue);
}
}
void CMMAVideoPlayer::DisconnectedL(CAccMonitorInfo* aAccessoryInfo)
{
TAccMonCapability deviceType = aAccessoryInfo->AccDeviceType() ;
DEBUG_INT("MID::CMMAVideoPlayer::DisconnectedL %d", deviceType);
if (iSurfaceWindow && (deviceType == KAccMonAVDevice))
{
isHDMICableConnected = EFalse;
iSurfaceWindow->SetAVCableConnStatus(EFalse);
}
}
void CMMAVideoPlayer::AccMonitorObserverError(TInt aError)
{
DEBUG_INT("MMA::CMMAVideoPlayer::AccMonitorObserverError %d", aError);
TBuf<KErrorMessageSize> errorMessage;
errorMessage.Format(KAccMonError, aError);
PostStringEvent(CMMAPlayerEvent::EError, errorMessage);
}
// END OF FILE