Project Status | On hold |
Project Duration | 1 year |
Software Used | CryEngine 3 |
Languages Used | C++, C |
CryVideo is a system to publish your own video files into the free version of the CryEngine 3, which was significant at the time due there only being support for the proprietary Scaleform GFx codec that wasn't available to the public at large.
Initially I wasn't entirely sure how to incorporate a proper video decoder into the engine, so I chose a very basic approach by playing a series of compressed dds frames instead that were extracted from a video and using an accompanying separate audio file.
The result was the first version of CryVideo which I officially announced with the following trailer:
While I managed to make the video and audio mostly sync, the performance impact and memory overhead of loading so many dds files was dramatic and thus the result wasn't all that great. The cumbersome workflow only added to it, so I decided to go ahead and figure out how to include a proper decoder into the engine. I ended up choosing the VP8/WebM codec.
The next version of CryVideo was already a lot better:
The consequences of implementing a decoder were smoother framerates, drastically reduced file sizes and a much, much easier workflow.
Ultimately the project was shelved due a lack of interest on my part to see it to the end, but a Beta version got released to a few testers and received positive feedback.
The CryVideo.dll library acted as the high level link between the engine and the low level decoder libraries. It hosted the CryVideo initialization code, managed video playback, registered Flow Graph Nodes, communicated with the decoder libraries and handled synchronization of video and audio.
The initialization function:
bool CCryVideo::Init(ISystem *pSystem, IGameFramework *pGameFramework, IGameToEditorInterface *pGameToEditorInterface) { //0.9.8: Dlls moved to CryVideo directory if (m_bIs64Bit) SetDllDirectory("Bin64\\CryVideo"); else SetDllDirectory("Bin32\\CryVideo"); started = false; m_bMenuRendered = false; gEnv = pSystem->GetGlobalEnvironment(); m_pFramework = pGameFramework; gEnv->pRenderer->RegisterCaptureFrame(this); pGameToEditorIface = pGameToEditorInterface; //Timer Manager if (!m_pTimerManager) m_pTimerManager = new CTimerManager(); //Initialize flow nodes (CryVideoPlayer) if (IFlowSystem *pFlow = m_pFramework->GetIFlowSystem()) { CG2AutoRegFlowNodeBase *pFactory = CG2AutoRegFlowNodeBase::m_pFirst; while (pFactory) { pFlow->RegisterType(pFactory->m_sClassName, pFactory); pFactory = pFactory->m_pNext; } } //Init OpenCV plugin if (m_hCryVideoOpenCVHandle) CryFreeLibrary(m_hCryVideoOpenCVHandle); m_hCryVideoOpenCVHandle = CryLoadLibrary("CryVideo\\CryVideoOpenCV.dll"); if (!m_hCryVideoOpenCVHandle) GameWarning("CryVideoOpenCV.DLL Loading Failed"); //Init WebM plugin if (m_hCryVideoWebMHandle) CryFreeLibrary(m_hCryVideoWebMHandle); m_hCryVideoWebMHandle = CryLoadLibrary("CryVideo\\CryVideoWebM.dll"); if (!m_hCryVideoWebMHandle) GameWarning("CryVideoWebM.dll Loading Failed"); //Test if RGB8 is available int testTextureId = m_pFramework->GetIUIDraw()->CreateTexture("Textures\\defaults\\black.tif", false); ITexture *pTestTexture = gEnv->pRenderer->EF_GetTextureByID(testTextureId); unsigned char* textureData = pTestTexture->GetData32(0, 0, (uint8*)0, eTF_A8R8G8B8); int finalTestTextureId = gEnv->pRenderer->DownLoadToVideoMemory(textureData, pTestTexture->GetWidth(), pTestTexture->GetHeight(), eTF_A8R8G8B8, eTF_R8G8B8, 1, false); ITexture *pFinalTestTexture = gEnv->pRenderer->EF_GetTextureByID(finalTestTextureId); if (pFinalTestTexture->IsTextureLoaded()) { isRGB8Usable = true; CryLogAlways("CryVideo: RGB8 is available!"); } else { isRGB8Usable = false; CryLogAlways("CryVideo: RGB8 is NOT available!"); } //Cleanup test textures m_pFramework->GetIUIDraw()->DeleteTexture(testTextureId); m_pFramework->GetIUIDraw()->DeleteTexture(finalTestTextureId); //Register CVars gEnv->pConsole->Register("cv_useLevelAsMenuBg", &cv_useLevelAsMenuBg, 0, VF_NULL, "use level as menu background"); cv_levelMenuBgMapName = gEnv->pConsole->RegisterString("cv_levelMenuBgMapName", "", VF_NULL, "level menu map name"); gEnv->pConsole->Register("cv_useVideoAsMenuBg", &cv_useVideoAsMenuBg, 0, VF_NULL, "use video as menu background"); cv_videoMenuBgVideoName = gEnv->pConsole->RegisterString("cv_videoMenuBgVideoName", "", VF_NULL, "video menu video name"); cv_startingLevel = gEnv->pConsole->RegisterString("cv_startingLevel", "", VF_CHEAT | VF_DUMPTODISK | VF_READONLY, "default starting level when running the launcher"); gEnv->pConsole->Register("cv_renderToSecondWindow", &cv_renderToSecondWindow, 0, VF_NULL, "render all videos to a separate window for debugging"); gEnv->pConsole->Register("cv_forceManualConversion", &cv_forceManualConversion, 0, VF_NULL, "force manual color conversion for debugging"); gEnv->pConsole->Register("cv_swapBGRA", &cv_swapBGRA, -1, VF_CHEAT | VF_DUMPTODISK | VF_READONLY, "-1=Autodetect(Default); 0=BGRA; 1=RGBA"); //Load CryVideo.cfg config file gEnv->pConsole->ExecuteString("exec CryVideo.cfg"); //Swap BGRA Autodetect (CryEngine's DX9 renderer expects textures to be in BGRA ; the DX11 one however requires RGBA) ICVar* swapChannels = gEnv->pConsole->GetCVar("cv_swapBGRA"); if (swapChannels) { if (swapChannels->GetIVal() == -1) { if (gEnv->pRenderer->GetRenderType() == eRT_DX11) { swapChannels->ForceSet("1"); CryLogAlways("CryVideo: DX11 --> RGBA color channel order!"); } else { swapChannels->ForceSet("0"); CryLogAlways("CryVideo: DX9 --> BGRA color channel order!"); } } } //Create global video list on startup CreateGlobalVideoList(); //Start audio timer m_LastSoundUpdate = gEnv->pTimer->GetAsyncCurTime(); started = true; return true; }
Excerpt from the VideoManager class which handles playback control:
////////////////////////////////////////////////////////////////////////// bool CVideoManager::PlaySingleVideo(string videoName, bool ingame, string ingameAudio, float ingameAudioVolume, IEntity* ingameVideoEntity, int slot, int subMtlId, int texSlot, bool loop, bool keepLastFrame) { CryLogAlways("VideoManager: Play Single Video!"); if (!IsEnabled()) { isPlaying = false; isPaused = false; return false; } assert(m_pGameFramework != NULL); if (m_pGameFramework == NULL) { return false; } assert(m_pUiDraw != NULL); if (m_pUiDraw == NULL) { return false; } IRenderer* pRenderer = gEnv->pRenderer; assert(pRenderer != NULL); if (pRenderer == NULL) { return false; } //Find video info bool bFound = false; int iFoundIndex = 0; if (!g_pCryVideo->m_globalVideoList.empty()) { for (int i = 0; i < g_pCryVideo->m_globalVideoList.size(); i++) { SVideoInfo info = g_pCryVideo->m_globalVideoList[i]; if (info.videoName.MakeLower() == videoName.MakeLower()) { //Detect right game folder const char* gameFolder; ICVar *pVar = gEnv->pConsole->GetCVar("sys_game_folder"); if (pVar) gameFolder = pVar->GetString(); else gameFolder = "Game"; //Init video file char fileName[255]; sprintf(fileName, "%s/Videos/Library/%s", gameFolder, videoName.MakeLower()); string fileNameString = fileName; bool success = false; if (fileNameString.substr(fileNameString.size() - 5, 5) == ".webm") { videoFormat = 2; success = m_pCryVideoOpenCV->LoadVideoFile(fileName, videoFormat); if (!success) { GameWarning("CryVideo: WebM file '%s' couldn't be opened. Retrying!", videoName); success = m_pCryVideoOpenCV->LoadVideoFile(fileName, videoFormat); } } else { videoFormat = 1; success = m_pCryVideoOpenCV->LoadVideoFile(fileName, videoFormat); if (!success) { GameWarning("CryVideo: Video file '%s' couldn't be opened. Retrying!", videoName); success = m_pCryVideoOpenCV->LoadVideoFile(fileName, videoFormat); } } if (!success) { GameWarning("CryVideo: Video file '%s' couldn't be opened. Check file?", videoName); } else { bFound = true; iFoundIndex = i; } break; } } } if (bFound) { //Set global values pIngameVideoTargetEntity = ingameVideoEntity; ingameVideoMaterialSlotId = slot; ingameVideoSubMtlId = subMtlId; ingameVideoTexSlot = texSlot; ingameAudioFile = ingameAudio; ingameAudioVol = ingameAudioVolume; isIngameVideo = ingame; isLoopedVideo = loop; freezeOnLastFrame = keepLastFrame; StartVideo(iFoundIndex); if (isPlaying) return true; } else { GameWarning("CryVideo: Video '%s' couldn't be played! Skipping...", videoName); } return false; } ////////////////////////////////////////////////////////////////////////// void CVideoManager::StartVideo(int videoIndex) { IRenderer* pRenderer = gEnv->pRenderer; assert(pRenderer != NULL); if (pRenderer == NULL) { return; } //Set values SVideoInfo info = g_pCryVideo->m_globalVideoList[videoIndex]; pIngameVideoTargetTex = NULL; frameCounter = 0; videoName = info.videoName; //1 second black screen time blackFramesCounter = int_round(gEnv->pTimer->GetFrameRate()); videoWidth = info.videoWidth; videoHeight = info.videoHeight; xPos = info.posX; yPos = info.posY; pSoundId = NULL; //Get video properties from file frameEndIndex = m_pCryVideoOpenCV->GetFrameCount(); frameRate = m_pCryVideoOpenCV->GetVideoFrameRate(); frameWidth = m_pCryVideoOpenCV->GetVideoWidth(); frameHeight = m_pCryVideoOpenCV->GetVideoHeight(); desiredFrameUpdateTime = 1.0f / frameRate; CryLogAlways("VideoManager: Desired Frame Update Time: %f", desiredFrameUpdateTime); //Check sound type string szSoundFilePath; if (info.soundFile == "Default") szSoundFilePath = "videos/library/TempAudio/CryVideo_" + info.videoName + ".mp2"; else szSoundFilePath = "videos/library/" + info.soundFile; if (info.allowSkip == 1) { isSkippable = true; } else { isSkippable = false; } //Don't allow skip on first start if (firstStart && !g_pCryVideo->m_bMenuRendered) { isSkippable = false; firstStartCounter = int_round(frameRate); } //Create master texture CryLogAlways("VideoManager: CreateMasterTexture!"); char* imageData = m_pCryVideoOpenCV->StartPlayback(); if (imageData != NULL) { bool success = false; if (g_pCryVideo->IsRGB8Usable()) { master_texture_id = gEnv->pRenderer->DownLoadToVideoMemory(reinterpret_cast<unsigned char*> (imageData), frameWidth, frameHeight, eTF_R8G8B8, eTF_R8G8B8, 1, false); } else { master_texture_id = gEnv->pRenderer->DownLoadToVideoMemory(reinterpret_cast<unsigned char*> (imageData), frameWidth, frameHeight, eTF_A8R8G8B8, eTF_A8R8G8B8, 1, false); } ITexture *pTexture = gEnv->pRenderer->EF_GetTextureByID(master_texture_id); if (pTexture) if (pTexture->IsTextureLoaded()) success = true; if (!success) { GameWarning("CryVideo: Critical Error. Texture format not recognized. Check video file '%s' !", videoName); StopVideo(); return; } } else { GameWarning("CryVideo: Critical Error. Frame couldn't be retrieved. Check video file '%s' !", videoName); StopVideo(); return; } //Start audio if (!isIngameVideo) //Playback in main menu { if (szSoundFilePath != "" && szSoundFilePath != "videos/library/") { ISound *pSound = gEnv->pSoundSystem->CreateSound(szSoundFilePath.c_str(), FLAG_SOUND_2D | FLAG_SOUND_MOVIE | FLAG_SOUND_PRECACHE_LOAD_SOUND); if (pSound) { pSoundId = pSound->GetId(); if (!isLoopedVideo) pSound->GetInterfaceDeprecated()->SetLoopMode(false); if (firstStart && !g_pCryVideo->m_bMenuRendered) { pSound->Play(0.0f); //Play silent audio if first start } else { pSound->Play(); } } } } else //Playback ingame (fullscreen or on entities) { if (pIngameVideoTargetEntity && szSoundFilePath != "" && szSoundFilePath != "videos/library/") { IEntitySoundProxy *pSoundProxy = (IEntitySoundProxy *)pIngameVideoTargetEntity->GetProxy(ENTITY_PROXY_SOUND); if (!pSoundProxy) pSoundProxy = (IEntitySoundProxy *)pIngameVideoTargetEntity->CreateProxy(ENTITY_PROXY_SOUND); pSoundProxy->StopAllSounds(); ISound *pSound; if (pIngameVideoTargetEntity == g_pCryVideo->m_pFramework->GetClientActor()->GetEntity()) //If the selected entity is the player do fullscreen audio { pSound = gEnv->pSoundSystem->CreateSound(szSoundFilePath.c_str(), FLAG_SOUND_DEFAULT_3D | FLAG_SOUND_VOICE); pSound->SetSemantic(eSoundSemantic_Dialog); } else //3D audio for playback on world entities { pSound = gEnv->pSoundSystem->CreateSound(szSoundFilePath.c_str(), FLAG_SOUND_DEFAULT_3D); pSound->SetSemantic(eSoundSemantic_Living_Entity); } if (pSound) { pSoundId = pSound->GetId(); if (!isLoopedVideo) pSound->GetInterfaceDeprecated()->SetLoopMode(false); pSoundProxy->PlaySound(pSound, Vec3(ZERO), FORWARD_DIRECTION, ingameAudioVol, false); } } if (pIngameVideoTargetEntity) { if (pIngameVideoTargetEntity != g_pCryVideo->m_pFramework->GetClientActor()->GetEntity()) //Playback on an entity, get material { IEntityRenderProxy* pRenderProxy((IEntityRenderProxy*)pIngameVideoTargetEntity->GetProxy(ENTITY_PROXY_RENDER)); if (pRenderProxy) { IMaterial* pMtl(pRenderProxy->GetRenderMaterial(ingameVideoMaterialSlotId)); if (pMtl) { pMtl = pMtl->GetSafeSubMtl(ingameVideoSubMtlId); if (pMtl) { const SShaderItem& shaderItem(pMtl->GetShaderItem()); if (shaderItem.m_pShaderResources) { SEfResTexture* pTex = shaderItem.m_pShaderResources->GetTexture(ingameVideoTexSlot); if (pTex) { pIngameVideoTargetTex = pTex; pDefaultTexture = pTex->m_Sampler.m_pITex; ITexture *pNewTexture = pRenderer->EF_GetTextureByID(master_texture_id); if (pNewTexture) { pIngameVideoTargetTex->m_Sampler.m_pITex = pNewTexture; //Overwrite texture in the selected material slot with first video frame } } } } } } } } } lastVideoFinishedReason = "None"; isPlaying = true; isPaused = false; scheduleUnpause = false; //Schedule timer if (m_TimerID) g_pCryVideo->GetTimerManager()->KillTimer(m_TimerID); m_TimerID = g_pCryVideo->GetTimerManager()->CreateTimer(this, desiredFrameUpdateTime, true, true); } ///////////////////////////////////////////////////////////////////////// void CVideoManager::StopVideo() { if (gEnv->IsDedicated()) return; if (!IsEnabled() || !isPlaying) return; stopVideo = true; isLoopedVideo = false; OnVideoTimer(m_TimerID); //Stop video immediately } ///////////////////////////////////////////////////////////////////////// void CVideoManager::PauseVideo() { if (gEnv->IsDedicated()) return; if (!IsEnabled() || !isPlaying || isPaused || !isIngameVideo) return; isPaused = true; if (pIngameVideoTargetEntity && pIngameVideoTargetEntity->GetProxy(ENTITY_PROXY_SOUND) && pSoundId) { IEntitySoundProxy *pSoundProxy = (IEntitySoundProxy *)pIngameVideoTargetEntity->GetProxy(ENTITY_PROXY_SOUND); if (pSoundProxy) { ISound *pSound = pSoundProxy->GetSound(pSoundId); if (pSound) { pSound->SetPaused(true); } } } } ///////////////////////////////////////////////////////////////////////// void CVideoManager::ResumeVideo() { if (gEnv->IsDedicated()) return; if (!IsEnabled() || !isPlaying || !isPaused || !isIngameVideo) return; isPaused = false; if (pIngameVideoTargetEntity && pIngameVideoTargetEntity->GetProxy(ENTITY_PROXY_SOUND) && pSoundId) { IEntitySoundProxy *pSoundProxy = (IEntitySoundProxy *)pIngameVideoTargetEntity->GetProxy(ENTITY_PROXY_SOUND); if (pSoundProxy) { ISound *pSound = pSoundProxy->GetSound(pSoundId); if (pSound) { pSound->SetPaused(false); } } } OnVideoTimer(m_TimerID); } //////////////////////////////////////////////////////////////////////////
These functions could also get invoked directly by the Flow Graph Node.
The Video:CryVideoPlayer Node was a core feature of CryVideo to control movie playback inside the game. Many exposed options gave the level scripter suitable control over the movie playback such as adjusting the audio volume in real time and pausing/resuming the video whenever needed.
FlowCryVideoPlayer.cpp defines the class CFlowCryVideoPlayer as shown:
#include "StdAfx.h" #include "Nodes/G2FlowBaseNode.h" #include "UIDraw/UIDraw.h" #include "HUD/CryVideo/VideoManager.h" #include "CryVideo.h" #include "Game.h" #include "IRenderer.h" class CFlowCryVideoPlayer : public CFlowBaseNode<eNCT_Instanced>, public IGameFrameworkListener { private: SActivationInfo m_ActInfo; IRenderer* m_pRenderer; CVideoManager* m_pVideoManager; bool m_bEnabled; bool m_bPaused; bool m_bLooped; public: CFlowCryVideoPlayer(SActivationInfo * pActInfo) { m_pRenderer = gEnv->pRenderer; m_pVideoManager = NULL; m_bEnabled = false; m_bPaused = false; m_bLooped = false; } ~CFlowCryVideoPlayer() { if (g_pCryVideo && g_pCryVideo->m_pFramework) g_pCryVideo->m_pFramework->UnregisterListener(this); SAFE_DELETE(m_pVideoManager); } void Serialize(SActivationInfo* pActInfo, TSerialize ser) { ser.Value("m_bEnabled", m_bEnabled); ser.Value("m_bPaused", m_bPaused); ser.Value("m_bLooped", m_bLooped); if (ser.IsReading()) { m_ActInfo = *pActInfo; //Listener if (g_pCryVideo && g_pCryVideo->m_pFramework && m_bEnabled && !m_bPaused && m_pVideoManager->IsPlaying() && !m_pVideoManager->IsPaused()) g_pCryVideo->m_pFramework->RegisterListener(this, "FlowNode_CryVideoPlayer", eFLPriority_Default); } } enum EInputPorts { EIP_Play = 0, EIP_Stop, EIP_Pause, EIP_Resume, EIP_Slot, EIP_SubMtlId, EIP_TexSlot, EIP_VideoName, EIP_AudioVolume, EIP_Looped, EIP_KeepLastFrame, }; enum EOutputPorts { EOP_OnPlay = 0, EOP_OnStop, EOP_OnPause, EOP_OnResume, EOP_OnVideoNotFound, }; virtual void GetConfiguration(SFlowNodeConfig& config) { static const SInputPortConfig inputs[] = { InputPortConfig_Void("Play", _HELP("Start playback")), InputPortConfig_Void("Stop", _HELP("Stop playback")), InputPortConfig_Void("Pause", _HELP("Pause playback")), InputPortConfig_Void("Resume", _HELP("Resume playback")), InputPortConfig<int>("Slot", 0, _HELP("Material Slot (e.g. to use CryVideoPlayer on dynamic textures)")), InputPortConfig<int>("SubMtlId", 0, _HELP("Sub Material Id (e.g. to use CryVideoPlayer on dynamic textures)")), InputPortConfig<int>("TexSlot", 0, _HELP("Texture Slot (e.g. to use CryVideoPlayer on dynamic textures)")), InputPortConfig<string>("VideoName", _HELP("Name of video to play"), 0, _UICONFIG("enum_global:video")), //Patch 0.9.8: Changed InputPortConfig<float>("Volume", 1.0f, _HELP("Volume (dynamic)")), InputPortConfig<bool>("Loop", false, _HELP("Video is looped")), InputPortConfig<bool>("KeepLastFrame", false, _HELP("The video stops on the last frame. Can be used together with loop to create a seamless looping video")), { 0 } }; static const SOutputPortConfig outputs[] = { OutputPortConfig_Void("OnPlay", _HELP("Triggered once the video started")), OutputPortConfig<bool>("OnStop", _HELP("Triggered once the video stopped. True if the video was finished, false if skipped")), OutputPortConfig_Void("OnPause", _HELP("Triggered once the video is paused")), OutputPortConfig_Void("OnResume", _HELP("Triggered once the video resumes")), OutputPortConfig_Void("OnVideoNotFound", _HELP("Triggered when video file was not found")), { 0 } }; config.pInputPorts = inputs; config.pOutputPorts = outputs; config.sDescription = _HELP("CryVideo player node"); config.nFlags |= EFLN_TARGET_ENTITY; config.SetCategory(EFLN_APPROVED); } virtual void ProcessEvent(EFlowEvent event, SActivationInfo *pActInfo) { switch (event) { case eFE_Initialize: { m_ActInfo = *pActInfo; m_pRenderer = gEnv->pRenderer; if (m_pVideoManager == NULL) m_pVideoManager = new CVideoManager(); } break; case eFE_Activate: { if (IsPortActive(pActInfo, EIP_Play) && !m_bEnabled && !m_bPaused) { IUIDraw *pDraw; if (g_pCryVideo) if (IGameFramework *pFW = g_pCryVideo->m_pFramework) pDraw = pFW->GetIUIDraw(); if (!pDraw) return; string videoName = GetPortString(pActInfo, EIP_VideoName); float volume = GetPortFloat(pActInfo, EIP_AudioVolume); //0.9.6: Loop and KeepLastFrame option added m_bLooped = GetPortBool(pActInfo, EIP_Looped); bool keepLastFrame = GetPortBool(pActInfo, EIP_KeepLastFrame); int slot = GetPortInt(pActInfo, EIP_Slot); int subMtlId = GetPortInt(pActInfo, EIP_SubMtlId); int texSlot = GetPortInt(pActInfo, EIP_TexSlot); if (!videoName || videoName == "") { ActivateOutput(pActInfo, EOP_OnVideoNotFound, true); return; } IEntity* pEntity = pActInfo->pEntity; if (pEntity) { bool success = m_pVideoManager->PlaySingleVideo(videoName, true, "", 1.0f, pEntity, slot, subMtlId, texSlot, false, keepLastFrame); if (success) { m_bEnabled = true; if (g_pCryVideo && g_pCryVideo->m_pFramework) g_pCryVideo->m_pFramework->RegisterListener(this, "FlowNode_CryVideoPlayer", eFLPriority_Default); ActivateOutput(pActInfo, EOP_OnPlay, true); pActInfo->pGraph->SetRegularlyUpdated(pActInfo->myID, true); } else { ActivateOutput(pActInfo, EOP_OnVideoNotFound, true); } } } if (IsPortActive(pActInfo, EIP_Stop) && m_bEnabled) { m_bEnabled = false; m_bPaused = false; if (g_pCryVideo && g_pCryVideo->m_pFramework) g_pCryVideo->m_pFramework->UnregisterListener(this); pActInfo->pGraph->SetRegularlyUpdated(pActInfo->myID, false); if (!m_pVideoManager->IsPlaying()) { ActivateOutput(pActInfo, EOP_OnStop, false); } else { m_pVideoManager->StopVideo(); ActivateOutput(pActInfo, EOP_OnStop, false); } } if (IsPortActive(pActInfo, EIP_Pause) && m_bEnabled && !m_bPaused) { m_bPaused = true; if (g_pCryVideo && g_pCryVideo->m_pFramework) g_pCryVideo->m_pFramework->UnregisterListener(this); pActInfo->pGraph->SetRegularlyUpdated(pActInfo->myID, false); if (m_pVideoManager->IsPlaying() && !m_pVideoManager->IsPaused()) { m_pVideoManager->PauseVideo(); ActivateOutput(pActInfo, EOP_OnPause, true); } } if (IsPortActive(pActInfo, EIP_Resume) && m_bEnabled && m_bPaused) { m_bPaused = false; if (m_pVideoManager->IsPlaying() && m_pVideoManager->IsPaused()) { if (g_pCryVideo && g_pCryVideo->m_pFramework) g_pCryVideo->m_pFramework->RegisterListener(this, "FlowNode_CryVideoPlayer", eFLPriority_Default); pActInfo->pGraph->SetRegularlyUpdated(pActInfo->myID, true); m_pVideoManager->ResumeVideo(); ActivateOutput(pActInfo, EOP_OnResume, true); } } } break; case eFE_Update: { if (m_bEnabled && !m_bPaused) { if (m_pVideoManager->IsPlaying() && !m_pVideoManager->IsPaused()) { if (m_pVideoManager->pIngameVideoTargetEntity && m_pVideoManager->pIngameVideoTargetEntity->GetProxy(ENTITY_PROXY_SOUND) && m_pVideoManager->pSoundId) { IEntitySoundProxy *pSoundProxy = (IEntitySoundProxy *)m_pVideoManager->pIngameVideoTargetEntity->GetProxy(ENTITY_PROXY_SOUND); if (pSoundProxy) { ISound *pSound = pSoundProxy->GetSound(m_pVideoManager->pSoundId); if (pSound) { float newVolume = GetPortFloat(pActInfo, EIP_AudioVolume); pSound->GetInterfaceExtended()->SetVolume(newVolume); } } } } } } break; } } IFlowNodePtr Clone(SActivationInfo * pActInfo) { return new CFlowCryVideoPlayer(pActInfo); } virtual void GetMemoryUsage(ICrySizer * s) const { s->Add(*this); } //////////////////////////////////////////////////// // ~IGameFrameworkListener virtual void OnSaveGame(ISaveGame* pSaveGame) {} virtual void OnLoadGame(ILoadGame* pLoadGame) {} virtual void OnLevelEnd(const char* nextLevel) {} virtual void OnActionEvent(const SActionEvent& event) {} virtual void OnPostUpdate(float fDeltaTime) { if (!m_bEnabled || m_bPaused) return; IUIDraw *pDraw; if (g_pCryVideo) if (IGameFramework *pFW = g_pCryVideo->m_pFramework) pDraw = pFW->GetIUIDraw(); if (!pDraw) return; if (!m_pVideoManager) return; if (!m_pVideoManager->IsPlaying() && !m_pVideoManager->IsPaused()) { if (m_pVideoManager->lastVideoFinishedReason == "Finished") { ActivateOutput(&m_ActInfo, EOP_OnStop, true); } else if (m_pVideoManager->lastVideoFinishedReason == "Skipped") { ActivateOutput(&m_ActInfo, EOP_OnStop, false); } m_bEnabled = false; m_bPaused = false; if (g_pCryVideo && g_pCryVideo->m_pFramework) g_pCryVideo->m_pFramework->UnregisterListener(this); m_ActInfo.pGraph->SetRegularlyUpdated(m_ActInfo.myID, false); //0.9.6: Loop and KeepLastFrame option added if (m_pVideoManager->lastVideoFinishedReason == "Finished" && m_bLooped) { string videoName = GetPortString(&m_ActInfo, EIP_VideoName); float volume = GetPortFloat(&m_ActInfo, EIP_AudioVolume); m_bLooped = GetPortBool(&m_ActInfo, EIP_Looped); bool keepLastFrame = GetPortBool(&m_ActInfo, EIP_KeepLastFrame); int slot = GetPortInt(&m_ActInfo, EIP_Slot); int subMtlId = GetPortInt(&m_ActInfo, EIP_SubMtlId); int texSlot = GetPortInt(&m_ActInfo, EIP_TexSlot); if (!videoName || videoName == "") { ActivateOutput(&m_ActInfo, EOP_OnVideoNotFound, true); return; } IEntity* pEntity = m_ActInfo.pEntity; if (pEntity) { bool success = m_pVideoManager->PlaySingleVideo(videoName, true, "", 1.0f, pEntity, slot, subMtlId, texSlot, false, keepLastFrame); if (success) { m_bEnabled = true; if (g_pCryVideo && g_pCryVideo->m_pFramework) g_pCryVideo->m_pFramework->RegisterListener(this, "FlowNode_CryVideoPlayer", eFLPriority_Default); ActivateOutput(&m_ActInfo, EOP_OnPlay, true); m_ActInfo.pGraph->SetRegularlyUpdated(m_ActInfo.myID, true); } else { ActivateOutput(&m_ActInfo, EOP_OnVideoNotFound, true); } } } } } }; //////////////////////////////////////////////////// //////////////////////////////////////////////////// REGISTER_FLOW_NODE("Video:CryVideoPlayer", CFlowCryVideoPlayer);
CryVideo shipped with a host of bonus features such as the ability to play a series of movies at boot time (think company logos) and also to use a video as the background of your main game menu:
Rather unrelated to video playback, but still nice to have:
CryVideo also gave you the option to use an actual game level as the background of your main menu. A default cutscene would play upon a successful load locking the camera in a static position and the menu would be overlayed after. It was possible to breathe life into the scenery with Flow Graphs to make your main menu background both dynamic and nice to look at.
In this example I setup a dynamic and constant Time of Day progression and NPCs who would do random activities such as driving and walking around at certain hours of the day: