Files
fmviewer3/project/fm_viewer/core/rm_player.cpp
2026-02-21 17:11:31 +09:00

1787 lines
52 KiB
C++

#include "rm_player.h"
#include <QApplication>
#include <QMessageBox>
#include <QFileDialog>
#include <QThread>
#include <QTimer>
#include "../rm_include.h"
#include "../rm_app.h"
#include "../data/rm_video_item_2ch.h"
#include "../ui/rm_button.h"
#include "../ui/rm_slider.h"
//#include "../ui/rm_frame_slider.h"
#include "../ui/rm_play_slider.h"
#include "rm_key_event.h"
#include "fav/fav_common.h"
// 연속해서 press + release 문제 발생시 수정
#define FIX_PRESS_AND_RELEASE 1
#if (RM_MODEL_360 || SUPPORT_WIDE_MODE)
#include "../fav/OpenGLVideo.h"
#include "../fav/OpenGLRendererBase.h"
#endif
#if (MODEL_BBVIEWER)
#include "../ui/rm_eq_widget.h"
#endif // MODEL_BBVIEWER
#if (CAPTURE_DEBUG)
QString g_errorMessage;
#endif
// 플레이 마지막에 AVDemux 가 종료된 시점에서 seek 하면 문제가 발생함.
// 실제 300~400ms 사이에서 발생함..
#define LAST_PLAY_SEEK_TEST 0
#define USE_LAST_PLAY_SEEK 1 // 플레이 종료 시점에서 전방으로 이동시 발생하는 문제 제거
#define LAST_PLAY_SEEK_LIMIT 500 // 100 으로 처리하면 step 이동시 종료되어 다음 파일로 넘어감
//const int kSyncInterval = 500;
//const qreal kVolumeInterval = 0.1;
//const int kMuteInterval = 10;//100;
//const qint64 gPausePosition = 1; // ???
// RMPlayer* RMPlayer::_instance = NULL;
#if (MODEL_BBVIEWER)
float g_PlaySpeedList[PLAY_SPEED_COUNT] = {0.3f,0.5f,1.0f,2.0f,3.0f};
//QList<QString> g_PlaySpeedNames = QList<QString>() << "0.5" << "1.0" << "2.0" << "4.0";
#elif (RM_MODEL == RM_MODEL_TYPE_KEIYO1 || \
RM_MODEL == RM_MODEL_TYPE_MBJ5010 || \
RM_MODEL == RM_MODEL_TYPE_FC_DR232W)
float g_PlaySpeedList[PLAY_SPEED_COUNT] = {0.5f, 0.8f, 1.0f,2.0f};
int DEFAULT_SPEED_INDEX = 2;
QList<QString> g_PlaySpeedNames = QList<QString>() << "0.5 x" << "0.8 x" << "1 x" << "2.0 x";
#elif (RM_MODEL == RM_MODEL_TYPE_AN6000)
float g_PlaySpeedList[PLAY_SPEED_COUNT] = {1.0f, 2.0f,4.0f,16.0f};
QList<QString> g_PlaySpeedNames = QList<QString>() << "1x" << "2x" << "4x" << "16x";
int DEFAULT_SPEED_INDEX = 0;
#elif (RM_MODEL == RM_MODEL_TYPE_TB4000)
float g_PlaySpeedList[PLAY_SPEED_COUNT] = {0.5f, 1.0f, 2.0f,4.0f,8.0f};
QList<QString> g_PlaySpeedNames = QList<QString>() << "0.5 x" << "1 x" << "2 x" << "4 x" << "8 x";
int DEFAULT_SPEED_INDEX = 1;
#elif (RM_MODEL_EMT_KR)
float g_PlaySpeedList[PLAY_SPEED_COUNT] = {0.25f,0.5f, 1.0f, 2.0f,4.0f};
QList<QString> g_PlaySpeedNames = QList<QString>() << FM_WSTR(L"¼ x") << FM_WSTR(L"½ x") << "1 x" << "2 x" << "4 x";
int DEFAULT_SPEED_INDEX = 2;
#else // @OTHER
float g_PlaySpeedList[PLAY_SPEED_COUNT] = {0.5f, 1.0f, 2.0f,8.0f};
QList<QString> g_PlaySpeedNames = QList<QString>() << "0.5 x" << "1 x" << "2 x" << "8 x";
int DEFAULT_SPEED_INDEX = 1;
#endif
//const int g_PlaySpeed1XIndex = 1;
//#if (USE_LIBRARY_MODE) // 360 DLL
//RMPlayer* RMPlayer::libraryInstance = NULL;
//#endif //
RMPlayer::RMPlayer(QObject *parent) : RMPlayerZoom(parent)
{
_sync_time = false; // seek sync timer <- sub
_sync_restart_timer = NULL; // " <- sub
_stepping = false; // seek <- sub
// _sliderReleaseTimer = NULL; // seek <- sub
_refreshSeeking = false; // seek <- sub
_captureFileList = NULL; // capture <- sub
_captureTimer = NULL; // capture <- sub
_captureWatcher = NULL; // "
_pauseWhileSeek = false;
#if (USE_RM_KEYBOARD_EVENT)
_seekAcceleration = 1;
_seekPressedTimer = NULL;
_seekReleasedTimer = NULL;
_seekUpdateTimer = NULL;
#endif
#if (RM_TESTING)
_testFilterF = NULL;
#endif
// 슬라이더 업데이트
//qInfo() << _playerF << __FUNCTION__;
connect(_playerF, SIGNAL(started()), SLOT(updateSlider()));
}
//--------------------------------------------------------------------------
void RMPlayer::onUserStop()
{
#if (PLAY_SEEK_FILE_MOVE)
// 사용자 STOP 요청시 step 모드 종료됨
endStepMode();
#endif
// 다음 파일로 넘어갈때는 호출하면 안됨 ???
#if (PLAY_CONTINUE_EVENT)
stop();
emit playEvent(PLAY_DID_USER_STOP,NULL);
#else
emit playEvent(PLAY_DID_USER_STOP,NULL);
stop();
#endif
// User Stop 시에는 잔상 남지 않도록 Clear
/*
int count = 0;
while(count++ < 100 &&
(_playerF->isPlaying() ||
(_playerR->isLoaded() && _playerR->isPlaying())))
{
QThread::msleep(10);
}
FAV::VideoFrame frame;
if(_videoOutputF != NULL)
{
_videoOutputF->receive(frame);
}
if(_videoOutputR != NULL)
{
_videoOutputR->receive(frame);
}
*/
}
void RMPlayer::stop()
{
RMPlayerBase::stop();
stopSeekUpdateTimer();
}
#if (PLAYER_ONLY_LIBRARY_MODE)
void RMPlayer::clear()
{
if(_playerF != NULL) {
_playerF->stop();
delete _playerF;
_playerF = NULL;
}
}
void RMPlayer::create()
{
if(_playerF == NULL) {
_createVideoPlayers();
}
}
#endif // PLAYER_ONLY_LIBRARY_MODE
void RMPlayer::updatePausedScreen()
{
_pausePosition = MAX(_pausePosition,INITIAL_VIDEO_POSITION);
// 비동기화 seek 가 발생하면 업데이트가 일어남
if(_playerF->isPaused())
{
seek(_pausePosition,true);
}
}
void RMPlayer::updatePausedScreenFrontOnly()
{
if(_playerF == NULL) {
return;
}
_pausePosition = MAX(_pausePosition,INITIAL_VIDEO_POSITION);
// 비동기화 seek 가 발생하면 업데이트가 일어남
if(_playerF->isPaused())
{
seekFrontOnly(_pausePosition,true);
}
}
#if (!(SINGLE_CH_VIEWER || TOGGLE_PLAYER) || PENTA_CHANNEL)
void RMPlayer::updatePausedScreenRearOnly()
{
if(_playerR == NULL) {
return;
}
_pausePosition = MAX(_pausePosition,INITIAL_VIDEO_POSITION);
// 비동기화 seek 가 발생하면 업데이트가 일어남
if(_playerR->isPaused())
{
seekRearOnly(_pausePosition,true);
}
}
#endif
//--------------------------------------------------------------------------
// Slider 컨트롤
/*
void RMPlayer::setSliderFrame(RMFrameSlider* frame)
{
//(QSlider*)
_slider = frame->playSlider->slider;
// PRESSED -> MOVED -> RELEASED 순서대로 호출 되도록 이벤트 처리해야함
// 노브가 아닌 지점 CLICK 시에는 Slider는 (Slider)PRESSED 이벤트 만 호출됨 (RMSlider 내부에서 구현)
// PRESSED 이벤트 발생시 KNOB 가 false 일 경우 released 이벤트 발생하지 않으니 별도의 처리 하지 말아야함
// connect(_slider, SIGNAL(sliderPressedWithKnob(bool)),SLOT(onSliderPress(bool))); // 이벤트 두번씩 발생함
// connect(_slider, SIGNAL(sliderMoved(int)),SLOT(onSliderMove(int))); // 슬라이더 컨트롤
// connect(_slider, SIGNAL(mouseReleased()),SLOT(onSliderRelease()));
// 슬라이더 업데이트
connect(_playerF, SIGNAL(started()), SLOT(updateSlider()));
// _volumeSlider = frame->volumeSlider;
// connect(_volumeSlider, SIGNAL(sliderPressed()), SLOT(onSetVolume()));
// connect(_volumeSlider, SIGNAL(valueChanged(int)), SLOT(onSetVolume()));
// onSetVolume();
// connect(frame->speedSlider, SIGNAL(mouseReleased()),SLOT(onSpeedSliderReleased()));
// connect(frame->speedSlider, SIGNAL(sliderMoved(int)),SLOT(onSpeedSliderMoved(int)));
// 참조용
_muteButton = frame->muteButton;
}
*/
#if (MODEL_BBVIEWER)
void RMPlayer::setEQFrame(RMEQWidget* eq)
{
connect(eq->speedSlider, SIGNAL(mouseReleased()),SLOT(onSpeedSliderReleased()));
connect(eq->speedSlider, SIGNAL(sliderMoved(int)),SLOT(onSpeedSliderMoved(int)));
}
#endif
// ------------------------------------------------------------------------------------------------
// 캡쳐 기능
QString RMPlayer::currentTimeString(double* lat, double* lon)
{
qreal ratio = (qreal)_playerF->position() / (qreal)_playerF->duration();
QDateTime dateTime = _currentItem->dateTimeInPosition(ratio, lat, lon);
if(dateTime.isValid())
{
// range->setProperty("Value",FM_WSTR(L"20XX년 XX월 XX일 XX시 XX분"));
QString dateFormatString = "yyyy" + FM_WSTR(L"") + " MM" + FM_WSTR(L"") + " dd" + FM_WSTR(L"");
QString date = dateTime.toString(dateFormatString);
QString timeFormatString = "HH" + FM_WSTR(L"") + " mm" + FM_WSTR(L"");
QString time = dateTime.toString(timeFormatString);
return date + " " + time;
}
return "";
}
// 0. 캡쳐 요청
bool RMPlayer::requestVideoCapture()
{
bool readyForCapture = (_playerF->isPaused() == true); // 현재 정지된 상태인지 확인
if(_captureWatcher != NULL || prepareForCapture() == false) // 준비 (정지, 전체화면 취소, 확대 취소)
{
return false;
}
#if (CAPTURE_DEBUG)
g_errorMessage = "0-1.Prepare for capture.\n";
#endif
// 혹시 capture 가 성공하지 못했을 경우 2초후 확인
clearCaptureWatcher();
_captureWatcher = new QTimer(this);
_captureWatcher->setSingleShot(true);
_captureWatcher->setInterval(2000);
connect(_captureWatcher,SIGNAL(timeout()),SLOT(onFailToCapture()));
_captureWatcher->start();
QApplication::setOverrideCursor(Qt::WaitCursor); // 커서 변경
if(readyForCapture == true) // 현재 준비 완료 상태이면 0.1초후 캡쳐 시작
{
#if (CAPTURE_DEBUG)
g_errorMessage += "0-2.Ready for capture.\n";
qInfo() << "0-2.Ready for capture.";
#endif
QTimer::singleShot(100, this, SLOT(requestVideoCaptureProcess()));
}
else // 준비가 완료되지 않은 상태이면 준비확인
{
#if (CAPTURE_DEBUG)
g_errorMessage += "0-2.Not ready for capture\n";;
qInfo() << "0-2.Not ready for capture";
#endif
clearCaptureTimer();
_captureTimer = new QTimer(this);
_captureTimer->setSingleShot(false);
_captureTimer->setInterval(500);
connect(_captureTimer,SIGNAL(timeout()),SLOT(onReadyForCapture()));
_captureTimer->start();
}
return true;
}
// 1. 캡쳐 준비 (다른 용도로도 사용될 수 있음)
bool RMPlayer::prepareForCapture()
{
if(_playerF != NULL) {
if(_playerF->isLoaded() == false || _playerF->position() < INITIAL_VIDEO_POSITION)
{
return false;
}
if(_playerF->isPaused() == false)
{
#if (CAPTURE_DEBUG)
g_errorMessage += "0-0.Try to pause\n";;
qInfo() << "0-0.Try to pause";
#endif
playOrPause(false);
}
}
emit cancelFullScreen();
return true;
}
// 0-1 캡쳐가 준비되지 않은 상태이면 전/후방 정지까지 대기
void RMPlayer::onReadyForCapture()
{
#if (CAPTURE_DEBUG)
g_errorMessage += "0-1.Check ready for capture.\n";
qInfo() << "0-1.Check ready for capture.";
#endif
#if (FORCE_SINGLE_PLAYER || SINGLE_CH_VIEWER) // 통합해야함..
if(_playerF->isPaused() == true)
#else
if(_playerF->isPaused() == true &&
(_playerR == NULL || (_playerR->isLoaded() == false || _playerR->isPaused() == true))) // 존재하지 않거나, 종료되었을 경우
#endif
{
#if (CAPTURE_DEBUG)
g_errorMessage += "0-1.Ready for capture all paused done.\n";
qInfo() << "0-1.Ready for capture all paused done.";
#endif
clearCaptureTimer();
QTimer::singleShot(100, this, SLOT(requestVideoCaptureProcess()));
}
}
// 0-2 캡쳐 준비가 완료되면 삭제
void RMPlayer::clearCaptureTimer()
{
if(_captureTimer != NULL)
{
_captureTimer->stop();
delete _captureTimer;
_captureTimer = NULL;
}
}
// 0-99 2초 이후에도 캡쳐가 완료되지 않았을 경우
void RMPlayer::onFailToCapture()
{
#if (CAPTURE_DEBUG)
QString message;
#if !(FORCE_SINGLE_PLAYER)
message.sprintf("capture error.\nplayerF:%d(%d),playerR:%d(%d)\n",(int)_playerF->isPaused(),(int)_playerF->position(),(int)_playerR->isPaused(),(int)_playerR->position());
#else
message.sprintf("capture error.\nplayerF:%d(%d)\n",(int)_playerF->isPaused(),(int)_playerF->position());
#endif
message += g_errorMessage;
QMessageBox msgBox(QMessageBox::Warning,
"",
message,
QMessageBox::Yes,
RMUIManager::window);
msgBox.setWindowFlags(Qt::WindowTitleHint | Qt::Dialog | Qt::WindowMaximizeButtonHint | Qt::CustomizeWindowHint);
msgBox.exec();
#endif
clearCaptureWatcher();
QApplication::restoreOverrideCursor(); // 커서 복구
}
// 2. 캡쳐 프로세스 시작
void RMPlayer::requestVideoCaptureProcess()
{
#if (CAPTURE_DEBUG)
g_errorMessage += "2-1.Capture process started.\n";
qInfo() << "2-1.Capture process started.";
#endif
if(_captureFileList != NULL)
{
delete _captureFileList;
_captureFileList = NULL;
}
_captureFileList = new QList<QString>(); // 파일명 리스트 생성
_captureDir = RMApp::appPath(RMApp::CAPTURE);
FAV::VideoCapture* captureF = _playerF->videoCapture(); // 캡쳐 instance
//#if (CAPTURE_DEBUG)
// qInfo() << "2-1. Capture async:" << captureF->isAsync() << " autosave:" << captureF->autoSave();
//#endif
if(captureF != NULL)
{
captureF->setAsync(false);
captureF->setCaptureDir(_captureDir); // 폴더 지정
captureF->setSaveFormat("jpg"); // 포멧 지정
// Slider 컨트롤에 표시한 시간과 다르게 나올 수 있어
// _playerF->position() 은 사용하지 않으며 최종 전송된 _lastPosition 사용
//
QString captureTitle = _currentItem->titleCapture(titleSeconds); // _lastPosition
captureTitle += "_CH1_";
captureF->setCaptureName(captureTitle);
connect(captureF,SIGNAL(saved(const QString&)),SLOT(onCaptureSavedFront(const QString&)),Qt::UniqueConnection);
#if (CAPTURE_DEBUG)
connect(captureF,SIGNAL(imageCaptured(const QImage&)),SLOT(onImageCaptured(const QImage&)),Qt::UniqueConnection);
connect(captureF,SIGNAL(failed()),SLOT(onCaptureFailed()),Qt::UniqueConnection);
#endif
captureF->capture(); // (전방)캡쳐 시작
// updatePausedScreen(); // 한번더 확인
#if (CAPTURE_DEBUG)
g_errorMessage += "2-2.Start front capture.\n";
qInfo() << "2-2.Start front capture.";
#endif
}
#if (CAPTURE_DEBUG)
else
{
g_errorMessage += "2-2.Error front capture.\n";
qInfo() << "2-2.Error front capture.";
}
#endif
}
// 3. 전방 캡쳐 완료
void RMPlayer::onCaptureSavedFront(const QString& path)
{
if(_playerF != NULL) {
FAV::VideoCapture* captureF = _playerF->videoCapture(); // 캡쳐 instance
disconnect(captureF,SIGNAL(saved(const QString&)),this,SLOT(onCaptureSavedFront(const QString&)));
}
#if (CAPTURE_DEBUG)
g_errorMessage += "3-1.Front capture done:" + path + "\n";
qInfo() << "3-1.Front capture done:" << path;
#endif
if(_captureFileList != NULL)
{
_captureFileList->append(path);
}
#if (!(FORCE_SINGLE_PLAYER || SINGLE_CH_VIEWER || TOGGLE_PLAYER) || PENTA_CHANNEL)
//if(_playerF->videoStreamCount() == 1) // 1CH 파일일 경우 ??? (TELEBIT mp4 1CH)
if(_playerR == NULL || _playerR->isLoaded() == false) // 1CH 파일일 경우 ??? (TELEBIT mp4 1CH)
#endif
{
if(_captureFileList != NULL)
{
QApplication::restoreOverrideCursor(); // 커서 복구
clearCaptureWatcher(); // watcher 삭제
//qInfo() << __LINE__ << __FUNCTION__;
emit videoCaptureDone(_captureFileList); // 완료 시그널
}
}
#if (!(FORCE_SINGLE_PLAYER || SINGLE_CH_VIEWER || TOGGLE_PLAYER) || PENTA_CHANNEL)
else // 후방 캡쳐 시작
{
FAV::VideoCapture* captureR = _playerR->videoCapture();
if(captureR != NULL)
{
captureR->setAsync(false);
captureR->setCaptureDir(_captureDir); // 폴더, 파일, 포멧, 이름 지정
captureR->setSaveFormat("jpg");
QString captureTitle = _currentItem->titleCapture(_playerF->position());
captureTitle += "_CH2_";
captureR->setCaptureName(captureTitle);
// QString name = captureR->captureName();
// if(name.contains("_CH2_",Qt::CaseInsensitive) == false)
// {
// name += "_CH2_";
// captureR->setCaptureName(name);
// }
connect(captureR,SIGNAL(saved(const QString&)),SLOT(onCaptureSavedRear(const QString&)),Qt::UniqueConnection);
#if (CAPTURE_DEBUG)
connect(captureR,SIGNAL(imageCaptured(const QImage&)),SLOT(onImageCaptured(const QImage&)),Qt::UniqueConnection);
connect(captureR,SIGNAL(failed()),SLOT(onCaptureFailed()),Qt::UniqueConnection);
#endif
captureR->capture(); // 후방 캡쳐 시작
//updatePausedScreenRearOnly(); // 한번더 확인
#if (CAPTURE_DEBUG)
g_errorMessage += "3-2.Start rear capture.\n";
qInfo() << "3-2.Start rear capture.";
#endif
}
#if (CAPTURE_DEBUG)
else
{
g_errorMessage += "3-2.Error rear capture.\n";
qInfo() << "3-2.Error rear capture.";
}
#endif
}
#endif
}
// 4. 후방 캡쳐 완료
#if !(FORCE_SINGLE_PLAYER || SINGLE_CH_VIEWER)
void RMPlayer::onCaptureSavedRear(const QString& path)
{
// 연결 해제
if(_playerR != NULL) {
FAV::VideoCapture* captureR = _playerR->videoCapture();
disconnect(captureR,SIGNAL(saved(const QString&)),this,SLOT(onCaptureSavedRear(const QString&)));
}
if(_captureFileList != NULL)
{
#if (CAPTURE_DEBUG)
g_errorMessage += "4.Rear capture done. All OK!:" + path + "\n";
qInfo() << "4.Rear capture done. All OK!:" << path;
#endif
_captureFileList->append(path); // 파일명 추가
QApplication::restoreOverrideCursor(); // 커서 복구
clearCaptureWatcher(); // Watcher 제거
//qInfo() << __LINE__ << __FUNCTION__;
emit videoCaptureDone(_captureFileList); // 시그널
}
}
#endif
// 5. 캡쳐 Watcher 삭제
void RMPlayer::clearCaptureWatcher()
{
if(_captureWatcher != NULL)
{
_captureWatcher->stop();
delete _captureWatcher;
_captureWatcher = NULL;
}
}
#if (CAPTURE_DEBUG)
void RMPlayer::onImageCaptured(const QImage& image)
{
Q_UNUSED(image);
g_errorMessage += "999.image captured.\n";
qInfo() << "999.image captured.";
}
void RMPlayer::onCaptureFailed()
{
g_errorMessage += "999.image capture failed.\n";
qInfo() << "999.image capture failed.";
}
#endif
// 캡쳐 DONE
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// SEEK
qint64 RMPlayer::ratioToSliderValue(qreal ratio)
{
//return _playerF->mediaStopPosition() * ratio;
return _slider->maximum() * ratio;
}
void RMPlayer::on_sync_timer_restarted()
{
_sync_time = true;
}
#if (PENTA_CHANNEL)
#define CHANNEL_PAUSE_MODE 1
void RMPlayer::onUpdateCHMode()
{
changeCHMode(RMApp::instance()->chMode);
}
void RMPlayer::changeCHMode(RMApp::ChannelMode mode)
{
RMApp::instance()->chMode = mode;
// 채널 변경 시작
emit playEvent(PLAY_WILL_CHANGE_CH,_currentItem);
// 이전 채널 확인
FAV::AVPlayer* oldF = _playerF;
FAV::AVPlayer* oldR = _playerR;
// 커넥션 해제
_clearPlayerConnection();
// NULL 처리시 문제가 발생하므로
_playerF = NULL;//_players[RMApp::ChannelFront];
_playerR = NULL;//_players[RMApp::ChannelRear];
// 이미 playOrPause 에서 play 된 상태로 호출됨
for(int i=0;i<5;i++) {
#if (CHANNEL_PAUSE_MODE)
//qInfo() << "PLAYER:" << i << _players[i]->isLoaded() << __FUNCTION__;
if(_players[i]->isLoaded()) {
if(!_players[i]->isPaused()) {
//qInfo() << "PAUSE:" << i << __FUNCTION__;
_players[i]->pause(true);
}
}
#else // CHANNEL_PAUSE_MODE
_players[i]->setSkipVideo(true);
#endif // CHANNEL_PAUSE_MODE
}
switch(RMApp::instance()->chMode) {
case RMApp::ChannelModeFR:
_playerF = _players[RMApp::ChannelFront];
_playerR = _players[RMApp::ChannelRear];
break;
case RMApp::ChannelModeLR:
_playerF = _players[RMApp::ChannelLeft];
_playerR = _players[RMApp::ChannelRight];
break;
case RMApp::ChannelModeFront:
_playerF = _players[RMApp::ChannelFront];
break;
case RMApp::ChannelModeRear:
_playerF = _players[RMApp::ChannelRear];
break;
case RMApp::ChannelModeLeft:
_playerF = _players[RMApp::ChannelLeft];
break;
case RMApp::ChannelModeRight:
_playerF = _players[RMApp::ChannelRight];
break;
case RMApp::ChannelModeSub:
_playerF = _players[RMApp::ChannelSub];
break;
}
if(_playerF != NULL) {
#if (CHANNEL_PAUSE_MODE)
//qInfo() << "PLAYER LOADED:" << _playerF->isLoaded() << "PAUSED:" << _playerF->isPaused() << __FUNCTION__;
// 현재 정지상태일 경우
if(!_pausedState && _playerF->isPaused()) {
_playerF->pause(false);
//qInfo() << "PLAY F2:" << _playerF << __FUNCTION__;
}
if(oldF != NULL && oldF != _playerF) {
_playerF->setPosition(oldF->position());
}
#else // CHANNEL_PAUSE_MODE
_playerF->setSkipVideo(false);
#endif // CHANNEL_PAUSE_MODE
}
if(_playerR != NULL) {
#if (CHANNEL_PAUSE_MODE)
if(!_pausedState && _playerR->isPaused()) {
_playerR->pause(false);
//qInfo() << "PLAY R2:" << _playerR << __FUNCTION__;
}
if(oldR != NULL && oldR != _playerR) {
_playerR->setPosition(oldR->position());
}
#else // CHANNEL_PAUSE_MODE
_playerR->setSkipVideo(false);
#endif // CHANNEL_PAUSE_MODE
}
// 사운드 처리
if(!_isMuted()) {
if(oldF != NULL && _playerF != oldF) {
oldF->audio()->setMute(true);
//_players[RMApp::ChannelFront]->setAudioStream(-1);
_playerF->audio()->setMute(false);
_playerF->audio()->setVolume(oldF->audio()->volume());
oldF->audio()->setVolume(0);
}
// 후방은 무조건 끄기
if(_playerR != NULL && !_playerR->audio()->isMute()) {
_playerR->audio()->setMute(true);
_playerR->audio()->setVolume(0);
}
}
// 비디오 장치 연결
_configureVideoRenderers();
//qInfo() << "PLAYERS:" << _players[0] << _players[1] << _players[2] << _players[3] << _players[4] << __FUNCTION__;
// qInfo() << "F:" << _playerF << "R:" << _playerR << __FUNCTION__;
// 커넥션 처리
_updatePlayerConnection();
if(_playerF != NULL) {
_playerF->masterClock()->updateExternalClock(*_clock);
}
if(_playerR != NULL) {
_playerR->masterClock()->updateExternalClock(*_clock);
}
// 채널변경 종료
emit playEvent(PLAY_DID_CHANGE_CH,_currentItem);
// 필터 업데이트
_updateEQFilter(true);
// FLIP 업데이트
updateFlip(false);
if(_pausedState) {
updatePausedScreen();
}
}
#endif
void RMPlayer::clear_seek_timer()
{
// seek 종료시 일정시간 후 동기화 시작한다
if(_sync_restart_timer != NULL)
{
_sync_restart_timer->stop();
delete _sync_restart_timer;
_sync_restart_timer = NULL;
}
_sync_time = false;
}
void RMPlayer::seek(qint64 pos,bool refresh)
{
//qInfo() << pos << __FUNCTION__ << __LINE__;
#if (LIMIT_SEEK_END)
if(pos > _playerF->duration() - 2000)
{
//qInfo() << pos << _playerF->duration() << __FUNCTION__ << __LINE__;
pos = qMax((qint64)(_playerF->duration() - 2000),(qint64)0);
}
#endif
//
#if (SINGLE_CH_VIEWER || TOGGLE_PLAYER)
if(_playerF->isPlaying() == false)
#else
if(_playerF->isPlaying() == false || (_playerR->isLoaded() == true && _playerR->isPlaying() == false))
#endif
{
//qInfo() << __FUNCTION__ << __LINE__;
return;
}
#if (USE_LAST_PLAY_SEEK && !(PLAY_SEEK_FILE_MOVE))
// 최종 종료단계에 들어간 경우 처리하지 않는다.
// 최종 종료단계로 이동할 경우 처리한다. (하지 않으면 이동하지 않는다.)
if(_playerF->position()+ LAST_PLAY_SEEK_LIMIT > _playerF->duration())
{
return;
}
#endif
//#if (RM_MODEL != RM_MODEL_TYPE_KEIYO1)
clear_seek_timer(); // 동기화 중지
_refreshSeeking = refresh; // 화면갱신을 위한..
#if (PENTA_CHANNEL)
for(int i=0;i<5;i++) {
if(_players[i]->isLoaded()) {
_players[i]->setSeekType(VIDEO_SEEK_TYPE);
_players[i]->seek(pos);
}
}
#else // PENTA_CHANNEL
_playerF->setSeekType(VIDEO_SEEK_TYPE);
_playerF->seek(pos);
#if !(SINGLE_CH_VIEWER || TOGGLE_PLAYER)
if(_playerR->isLoaded())
{
// 이거 왜하는지 모르겠음
if(_pausedState == false)
{
_playerR->pause(false);
}
_playerR->setSeekType(VIDEO_SEEK_TYPE);
_playerR->seek(pos);
}
#endif // #if (SINGLE_CH_VIEWER)
#if (TRI_CHANNEL)
if(_playerI->isLoaded())
{
// 이거 왜하는지 모르겠음
if(_pausedState == false)
{
_playerI->pause(false);
}
_playerI->setSeekType(VIDEO_SEEK_TYPE);
_playerI->seek(pos);
}
#endif
//#endif // RM_MODEL_TYPE_KEIYO1
//#if (MODEL_BBVIEWER)
// // 처리하지 않으면 위치 클릭전 위치로 이동함.
// _clock->updateExternalClock( pos );
// // 처리하지 않으면 seek 가 play seek 되면서 delay 발생함
// _playerF->masterClock()->updateExternalClock(*_clock);
//#if !(FORCE_SINGLE_PLAYER)
// _playerR->masterClock()->updateExternalClock(*_clock);
//#endif
//#endif // MODEL_BBVIEWER
#endif // #else // PENTA_CHANNEL
}
void RMPlayer::onSliderMove(int value)
{
//LOG_TEST;
_refreshSeeking = false; // 초기화 해야함
if(_playerF == NULL)
{
return;
}
if (_playerF->isLoaded())
{
// Release 이후에 Slider Move 가 한번더 호출되어 _sliderMoving == true 처리하면 안됨
// 그러나 _sliderMoving 처리하지 않으면 다시 seek 발생하고 슬라이드 이동 재귀 호출됨
_sliderMoving = true;
#if (PLAY_SYNC_FIX2)
qint64 pos = value;
#else
qint64 pos = MAX(INITIAL_VIDEO_POSITION,value);
#endif
// 완전 뮤트
muteIfRequired(-1);
if(_playerF->isPaused() == true)
{
// 정지된 상태에서만 클럭 업데이트
#if !(PLAY_SYNC_FIX2)
_clock->updateExternalClock( pos );
#endif // PLAY_SYNC_FIX2
//qInfo() << "*** SLIDE MOVE" << pos << __FUNCTION__;
_playerF->setPosition( pos );
#if !(FORCE_SINGLE_PLAYER || SINGLE_CH_VIEWER || TOGGLE_PLAYER)
if(_playerR->isLoaded())
{
_playerR->setPosition( pos );
}
//qInfo() << __FUNCTION__ << "setPosition:" << pos;
#endif
}
else
{
seek(pos,false);
}
_pausePosition = pos; // ???
// 정지 상태일 경우 지도 및 그래프는 업데이트 한다. -> 실제 정지 상태에서 onPositionChanged 가 발생함
//emit positionChanged(pos,_playerF->duration());
}
}
void RMPlayer::onSliderRelease()
{
//FLOG_OT << __FUNCTION__ << __LINE__;
// mouse relese 이후에 move 가 한번더 발생하여
// _sliderMoving 이 다시 true 가 되어 정지됨
_sliderMoving = false;
// SEEK 도중 PAUSE 처리 복구
// && _playerF->isPaused() 는 완전히 처리완료 되지 않은 경우가 있어 확인하지 않는다
if(_pauseWhileSeek ) {
//qInfo() << "_pauseWhileSeek:" << _pauseWhileSeek<< __FUNCTION__;
_pause(false,true);
_pauseWhileSeek = false;
}
onUnMuteIfRequired();
sync_clock();
}
void RMPlayer::onSliderPress(bool bKnob)
{
if(_playerF == NULL) {
return;
}
//FLOG_OT << __FUNCTION__ << __LINE__;
endStepMode();
RMSlider* slider = qobject_cast<RMSlider*>(QObject::sender());
if(slider == NULL)
{
return;
}
// 처리 도중에는 자동 파일 이동 방지
_sliderMoving = true;
// 적용안됨???
bKnob = true;
int sliderPos = slider->value();
// KNOB 가 아닐 경우 이후 이벤트 발생하지 않기 때문에 pause 등 처리할 필요 없음
#if (PLAY_SYNC_FIX2)
// isPaused 는 player 내부에서 SEEK 중 일 경우 사용자 paused 상태에서도 풀릴 수 있음
if(bKnob == true && _pausedState == false) {
#else
if(bKnob == true && _playerF->isPaused() == false) {
#endif
_pauseWhileSeek = true;
_pause(true,true);
}
// 클릭한 지점으로 이동
onSliderMove(sliderPos);
// 클릭한 경우 released 가 호출되지 않기 때문에 _sliderMoving 바로 해제
if(bKnob == false) {
_sliderMoving = false;
// MUTE 도 바로 해제
onUnMuteIfRequired();
}
}
void RMPlayer::startSeekUpdateTimer()
{
// 기존 타이머 제거
stopSeekUpdateTimer();
_seekPressedTimer = new QTimer(this);
_seekPressedTimer->setInterval(300);
connect(_seekPressedTimer,SIGNAL(timeout()),SLOT(onSeekPressed()));
_seekPressedTimer->start();
// pressed -> 중 업데이트
_seekUpdateTimer = new QTimer(this);
_seekUpdateTimer->setInterval(500);
connect(_seekUpdateTimer,SIGNAL(timeout()),SLOT(onSeekUpdate()));
_seekUpdateTimer->start();
}
void RMPlayer::stopSeekUpdateTimer()
{
if(_seekPressedTimer != NULL)
{
_seekPressedTimer->stop();
delete _seekPressedTimer;
_seekPressedTimer = NULL;
}
if(_seekReleasedTimer != NULL)
{
_seekReleasedTimer->stop();
delete _seekReleasedTimer;
_seekReleasedTimer = NULL;
}
if(_seekUpdateTimer != NULL)
{
_seekUpdateTimer->stop();
delete _seekUpdateTimer;
_seekUpdateTimer = NULL;
}
_seekAcceleration = 1;
}
void RMPlayer::onSeekPressed()
{
if(_firstStep == true) // && _stepMode == STEP_MODE_B1
{
return;
}
if(_seekPressedTimer != NULL)
{
_seekPressedTimer->setInterval(150);
}
int step = (_stepMode != STEP_MODE_B1) ? 1 : -1;
qint64 pos = _stepPosition();
if(_isLastStep() && step > 0)
{
// 다음파일이 없을 경우 이후 동작하지 않는다 _playItem == NULL 로 처리해서
stopSeekUpdateTimer();
if(RMVideoFileList::instance()->isNextPlayItemExist(true)) {
_onNaturalEnd = true;
RMVideoFileList::instance()->onPlayNextVideo(-1);
}
return;
}
if(pos < 0 && step < 0)
{
stopSeekUpdateTimer();
if(RMVideoFileList::instance()->isNextPlayItemExist(false)) {
// stepping 처리시
_onNaturalEnd = true;
RMVideoFileList::instance()->onPlayPreviousVideo(-1);
//qInfo() << "BACK";
return;
}
}
pos = MAX(MIN(pos,_playerF->duration()),0);
_slider->setValue(pos);
// 처리하지 않으면 swap 처리시 문제가됨
_pausePosition = pos;
//qInfo() << __FUNCTION__ << pos << ":" << _slider->value() << _playerF->position();
emit RMPlayer::instance()->positionChanged(pos,_playerF->duration());
}
void RMPlayer::onSeekUpdate()
{
// 연속 클릭시 바로 업데이트 하지 않고 취소 할 수 있도록 처리
if(_firstStep == true) // && _stepMode == STEP_MODE_B1
{
return;
}
qint64 pos = _slider->value();
//qInfo() << __FUNCTION__ << pos << _playerF->isLoaded() << _playerR->isLoaded() << _playerF->position() << _playerR->position();
seek(pos,false);
#if (DO_NOT_USE_STEP_PAUSE)
_stepMode = STEP_MODE_NONE;
#endif // DO_NOT_USE_STEP_PAUSE
//qInfo() << __FUNCTION__ << " POS:" << pos;
}
void RMPlayer::onSeekLastUpdate()
{
onSeekUpdate();
stopSeekUpdateTimer();
}
void RMPlayer::onSeekForwardStart()
{
if(!_playerF->isLoaded()) {
return;
}
_stepMode = STEP_MODE_F1;
_firstStep = false;
// 정지
#if !(DO_NOT_USE_STEP_PAUSE)
if(_playerF->isPaused() == false)
{
_pause(true,true);
// _pause 는 slider 등에서 잠깐 이동시에도 사용하기 때문에
// 직접 아이콘 변경 해야함
emit playEvent(PLAY_DID_PAUSED,_currentItem);
}
#endif // DO_NOT_USE_STEP_PAUSE
// 최초 1회 실행 (슬라이드만 이동, 실제 SEEK 는 onSeekUpdate 에서 발생함)
onSeekPressed();
startSeekUpdateTimer();
}
void RMPlayer::onSeekBackwardStart()
{
if(!_playerF->isLoaded()) {
return;
}
// 연속해서 press+release 반복시 마지막 업데이트 취소
stopSeekUpdateTimer();
_stepMode = STEP_MODE_B1;
_firstStep = false;
#if !(DO_NOT_USE_STEP_PAUSE)
// 정지
if(_playerF->isPaused() == false)
{
_pause(true,true);
// _pause 는 slider 등에서 잠깐 이동시에도 사용하기 때문에
// 직접 아이콘 변경 해야함
emit playEvent(PLAY_DID_PAUSED,_currentItem);
}
#endif // DO_NOT_USE_STEP_PAUSE
onSeekPressed();
startSeekUpdateTimer();
}
#if (PLAY_SYNC_FIX2)
void RMPlayer::onSeekFrameBackwardStart()
{
_stepMode = STEP_MODE_BF;
_firstStep = false;
if(_playerF->isPaused() == false)
{
_pause(true,true);
// _pause 는 slider 등에서 잠깐 이동시에도 사용하기 때문에
// 직접 아이콘 변경 해야함
emit playEvent(PLAY_DID_PAUSED,_currentItem);
}
else // 처리 해 주지 않으면 뒤로 돌아가서 시작함
{
//qInfo() << __FUNCTION__ << _slider->value() << _playerF->position();
//_slider->setValue(_playerF->position());
}
onSeekPressed();
startSeekUpdateTimer();
}
#endif // #if (PLAY_SYNC_FIX2)
void RMPlayer::onSeekFrameForwardStart()
{
_stepMode = STEP_MODE_FF;
_firstStep = false;
if(_playerF->isPaused() == false)
{
_pause(true,true);
// _pause 는 slider 등에서 잠깐 이동시에도 사용하기 때문에
// 직접 아이콘 변경 해야함
emit playEvent(PLAY_DID_PAUSED,_currentItem);
}
else // 처리 해 주지 않으면 뒤로 돌아가서 시작함
{
//qInfo() << __FUNCTION__ << _slider->value() << _playerF->position();
//_slider->setValue(_playerF->position());
}
onSeekPressed();
startSeekUpdateTimer();
}
// KEY/MOUSE RELEASE
void RMPlayer::onSeekStepEnd()
{
// 모드 종료는 endStopMode() 에서 처리함..
stopSeekUpdateTimer();
_seekReleasedTimer = new QTimer(this);
#if (FIX_PRESS_AND_RELEASE)
// 연속해서 press+release 처리할 경우 임시로 해결
_seekReleasedTimer->setInterval(300);
#else
_seekReleasedTimer->setInterval(100);
#endif
_seekReleasedTimer->setSingleShot(true);
connect(_seekReleasedTimer,SIGNAL(timeout()),SLOT(onSeekLastUpdate()));
_seekReleasedTimer->start();
}
quint64 RMPlayer::_stepPosition()
{
#if (SEEK_STEP_SIZE == 10)
const double stepSize = 10000.0;
#else
const double stepSize = 1000.0;
#endif
qint64 nexPos = 0;
if(_stepMode == STEP_MODE_F1 || _stepMode == STEP_MODE_B1)
{
int step = _stepMode == STEP_MODE_F1 ? 1 : -1;
//double tolerance = 0;
step *= _seekAcceleration;
// 이전이동 + 마지막일 경우
if(step < 0 && _isLastStep())
{
//tolerance = 100;
}
#if (SEEK_SIMPLE_STEP) // 단순이동 모드
if(_stepMode == STEP_MODE_F1) {
nexPos = MIN((_slider->value() + stepSize),_playerF->duration());
} else {
nexPos = MAX((_slider->value() - stepSize),0);
}
#else // SEEK_SIMPLE_STEP
int currentIndex = (int)(((double)_slider->value()) / stepSize);
currentIndex += step;
nexPos = (qint64)(((double)currentIndex) * stepSize);
if(fabs( (double)(nexPos-_slider->value())) < 100)
{
nexPos = (qint64)(((double)currentIndex + step) * stepSize);
}
// 다음이 마지막 STEP 일 경우 영상 마지막으로 이동
if(step > 0 && (_playerF->duration() - nexPos) < 500)
{
nexPos = _playerF->duration();//_durationAddPTS;
}
#endif // #else // SEEK_SIMPLE_STEP
//qInfo() << nexPos;
}
else if (_stepMode == STEP_MODE_FF)
{
nexPos = MIN((_slider->value() + STEP_SEEK_DURATION),_playerF->duration());
//qInfo() << __FUNCTION__ << nexPos << _playerF->position() << _slider->value();
}
#if (PLAY_SYNC_FIX2)
else if (_stepMode == STEP_MODE_BF)
{
nexPos = MAX((_slider->value() - STEP_SEEK_DURATION),0);
//qInfo() << __FUNCTION__ << nexPos << _playerF->position() << _slider->value();
}
#endif // #if (PLAY_SYNC_FIX2)
_seekAcceleration = MIN(_seekAcceleration+1,3);
return nexPos;
}
bool RMPlayer::_isLastStep()
{
if(_stepMode != STEP_MODE_FF)
{
return (_playerF->duration() - _slider->value()) < 500;
}
return (_playerF->duration() - _slider->value()) < 1;
}
void RMPlayer::onPlayRestart()
{
_clock->updateExternalClock(0);
seek(0,false);
}
void RMPlayer::seekFrontOnly(qint64 pos,bool refresh)
{
_refreshSeeking = refresh; // 화면갱신을 위한..
if(_playerF->isLoaded())
{
_playerF->seek(pos);
}
}
#if !(FORCE_SINGLE_PLAYER || SINGLE_CH_VIEWER)
void RMPlayer::seekRearOnly(qint64 pos,bool refresh)
{
_refreshSeeking = refresh; // 화면갱신을 위한..
if(_playerR->isLoaded())
{
_playerR->seek(pos);
}
}
#endif
#if (CAPTURE_DEBUG)
void RMPlayer::onSeekCapture()
{
requestVideoCapture();
}
#endif
void RMPlayer::onSpeedSliderMoved(int position)
{
RMSlider* slider = qobject_cast<RMSlider *>(QObject::sender());
if(slider != NULL)
{
if(position >= 0 && position < PLAY_SPEED_COUNT)
{
#if !(DEBUG_HIGH_SPEED_STOP)
_speedValue = g_PlaySpeedList[position];
#endif
if(_speedLabel != NULL)
{
_speedLabel->setText(g_PlaySpeedNames.at(position));
}
}
}
}
void RMPlayer::onSpeedSliderReleased()
{
RMSlider* slider = qobject_cast<RMSlider *>(QObject::sender());
if(slider != NULL)
{
int index = slider->value();
if(index >= 0 && index < PLAY_SPEED_COUNT)
{
_speedValue = g_PlaySpeedList[index];
if(_speedLabel != NULL)
{
_speedLabel->setText(g_PlaySpeedNames.at(slider->value()));
}
updateSpeed(_speedValue);
}
}
}
#if !(SINGLE_CH_VIEWER)
#if (TOGGLE_PLAYER)
void RMPlayer::toggleSwap(bool forceEmit)
{
//qInfo() << "TOGGLE SWAP:" << LOG_FL;
// 로딩되지 않은 상태에서도 처리
if(_eventBlocking == true)
{
return;
}
#if !(DO_NOT_USE_ZOOM)
if(_videoOutputZOOMMain != NULL) {
_playerF->removeVideoRenderer(_videoOutputZOOMMain);
}
#endif
if(forceEmit == false)
{
blockEvents(200);
}
disconnect(_playerF,SIGNAL(positionChanged(qint64)),this,SLOT(onPositionChanged(qint64)));
disconnect(_playerF,SIGNAL(firstFrameNotify(qreal)),this,SLOT(onFirstFrame(qreal)));
disconnect(_playerF,SIGNAL(error(const FAV::AVError&)),this,SLOT(onPlayerF(const FAV::AVError&)));
_swaped = !_swaped;
bool isPaused = _playerF->isPaused();
// Player SWAP
FAV::AVPlayer* pt = _playerR;
_playerR = _playerF;
_playerF = pt;
// 전방을 후방에 표시
if(_playerF->isLoaded()) {
// 2021/04/28-> 1000->100 으로 변경
_playerF->setPosition(qMax((qint64)(_playerR->position()-100),(qint64)0));
}
_playerF->setRenderer(_videoOutputF);
_playerR->removeVideoRenderer(_videoOutputF);
if(_playerF->isLoaded()) {
_playerF->pause(isPaused);
_playerR->pause(true);
_playerF->masterClock()->updateExternalClock(*_clock);
_playerR->masterClock()->updateExternalClock(*_clock);
}
connect(_playerF, SIGNAL(positionChanged(qint64)), SLOT(onPositionChanged(qint64)));
connect(_playerF,SIGNAL(firstFrameNotify(qreal)),this,SLOT(onFirstFrame(qreal)));
connect(_playerF,SIGNAL(error(const FAV::AVError&)), SLOT(onPlayerF(const FAV::AVError&)));
//qInfo() << "SET POSITION:" << _playerR->position() << __FUNCTION__;
#if !(DO_NOT_USE_ZOOM)
if(_videoOutputZOOMMain != NULL) {
_playerF->addVideoRenderer(_videoOutputZOOMMain);
}
#endif
_playerF->audio()->setMute(false);
_playerR->audio()->setMute(true);
updatePausedScreen();
emit playEvent(_swaped ? PLAY_DID_SWAPPED : PLAY_DID_UNSWAPPED, _currentItem);
}
#else // TOGGLE_PLAYER
void RMPlayer::toggleSwap(bool forceEmit)
{
// 로딩되지 않은 상태에서도 처리
if(_eventBlocking == true)
{
return;
}
#if !(DO_NOT_USE_ZOOM)
if(_videoOutputZOOMMain != NULL) {
mainScreenPlayer()->removeVideoRenderer(_videoOutputZOOMMain);
}
if(_videoOutputZOOMSub != NULL) {
subScreenPlayer()->removeVideoRenderer(_videoOutputZOOMSub);
}
#endif
if(forceEmit == false)
{
blockEvents(200);
}
_swaped = !_swaped;
//qInfo() << "_swaped:" << _swaped << __FUNCTION__;
if(_isSwaped())
{
#if (RM_MODEL_360)
//_videoOutputF->widget()->setHidden(true);
//_videoOutputR->widget()->setHidden(true);
// 현재 모드/각도 저장 후 복원
FAV::OpenGLVideo::RENDER_INFO_360 fi = {0,};
FAV::OpenGLVideo::RENDER_INFO_360 ri = {0,};
fi.aspectMode = (int)_videoOutputF->outAspectRatioMode();
fi.sourceAspectRatio = _videoOutputF->sourceAspectRatio();
//qInfo() << _videoOutputF->outAspectRatioMode();
ri.aspectMode = (int)_videoOutputR->outAspectRatioMode();
ri.sourceAspectRatio = _videoOutputR->sourceAspectRatio();
_videoOutputF->opengl()->getRenderInfo(&fi);
_videoOutputR->opengl()->getRenderInfo(&ri);
// 전환시 모드 리셋!
fi.mode = _videoOutputF->opengl()->mode(); // 전방 FISHEYE
ri.mode = 0;
#endif
// 전방을 후방에 표시
_playerF->setRenderer(_videoOutputR);
// 전방 파일이 존재하지 않을 경우
#if (FORCE_2CH)
if(_currentItem != NULL && _currentItem->filePath.length() == 0)
#else
if(_currentItem != NULL && (_currentItem->filePath.length() == 0 || _currentItem->filePathCH2.length() == 0))
#endif
{
_videoOutputF->receive(FAV::VideoFrame()); // clear
}
else
{
#if (TRI_CHANNEL)
if(isIndoor()) {
_playerI->setRenderer(_videoOutputF);
} else {
_playerR->setRenderer(_videoOutputF);
}
#else // TRI_CHANNEL
_playerR->setRenderer(_videoOutputF);
#endif // TRI_CHANNEL
}
#if (RM_MODEL_360)
//qInfo() << "----------------------- SWAP F-----------------------";
_videoOutputR->opengl()->setRenderInfo(&fi);
_videoOutputR->setOutAspectRatioMode((FAV::VideoRenderer::OutAspectRatioMode)fi.aspectMode);
_videoOutputR->setSourceAspectRatio(fi.sourceAspectRatio);
//qInfo() << "----------------------- R -----------------------";
_videoOutputF->opengl()->setRenderInfo(&ri);
_videoOutputF->setOutAspectRatioMode((FAV::VideoRenderer::OutAspectRatioMode)ri.aspectMode);
_videoOutputF->setSourceAspectRatio(ri.sourceAspectRatio);
//_videoOutputF->widget()->setHidden(false);
//_videoOutputR->widget()->setHidden(false);
#endif
}
else
{
#if (RM_MODEL_360)
FAV::OpenGLVideo::RENDER_INFO_360 fi = {0,};
FAV::OpenGLVideo::RENDER_INFO_360 ri = {0,};
fi.aspectMode = (int)_videoOutputR->outAspectRatioMode();
fi.sourceAspectRatio = _videoOutputR->sourceAspectRatio();
ri.aspectMode = (int)_videoOutputF->outAspectRatioMode();
ri.sourceAspectRatio = _videoOutputF->sourceAspectRatio();
_videoOutputR->opengl()->getRenderInfo(&fi);
_videoOutputF->opengl()->getRenderInfo(&ri);
ri.mode = 0;
#endif
_playerF->setRenderer(_videoOutputF);
// 후방 파일이 존재하지 않을 경우
#if (FORCE_2CH)
if(_currentItem != NULL && _currentItem->filePath.length() == 0)
#else
if(_currentItem != NULL && (_currentItem->filePath.length() == 0 || _currentItem->filePathCH2.length() == 0))
#endif
{
_videoOutputR->receive(FAV::VideoFrame());
}
else
{
#if (TRI_CHANNEL)
if(isIndoor()) {
_playerI->setRenderer(_videoOutputR);
} else {
_playerR->setRenderer(_videoOutputR);
}
#else // TRI_CHANNEL
_playerR->setRenderer(_videoOutputR); // clear
#endif // TRI_CHANNEL
}
#if (RM_MODEL_360)
//qInfo() << "----------------------- RESTORE F-----------------------";
_videoOutputF->opengl()->setRenderInfo(&fi);
_videoOutputF->setSourceAspectRatio(fi.sourceAspectRatio);
_videoOutputF->setOutAspectRatioMode((FAV::VideoRenderer::OutAspectRatioMode)fi.aspectMode);
//qInfo() << "----------------------- RESTORE R-----------------------";
_videoOutputR->opengl()->setRenderInfo(&ri);
_videoOutputR->setSourceAspectRatio(ri.sourceAspectRatio);
_videoOutputR->setOutAspectRatioMode((FAV::VideoRenderer::OutAspectRatioMode)ri.aspectMode);
#endif
}
#if !(DO_NOT_USE_ZOOM)
if(_videoOutputZOOMMain != NULL) {
mainScreenPlayer()->addVideoRenderer(_videoOutputZOOMMain);
}
if(_videoOutputZOOMSub != NULL) {
subScreenPlayer()->addVideoRenderer(_videoOutputZOOMSub);
}
#endif
#if (RM_MODEL_EMT_KR)
int m0 = _videoOutputR->opengl()->mode();
_videoOutputR->opengl()->setMode(_videoOutputF->opengl()->mode());
_videoOutputF->opengl()->setMode(m0);
#endif // #if (RM_MODEL_EMT_KR)
#if (USE_SHADER_FLIP)
updateFlip(true);
updateFlip(false);
#endif // USE_SHADER_FLIP
updatePausedScreen();
emit playEvent(_swaped ? PLAY_DID_SWAPPED : PLAY_DID_UNSWAPPED, _currentItem);
}
#endif // TOGGLE_PLAYER
#endif // #if !(SINGLE_CH_VIEWER)
#if (TRI_CHANNEL)
void RMPlayer::toggleIndoor(bool forceEmit)
{
// 로딩되지 않은 상태에서도 처리
if(_eventBlocking == true)
{
return;
}
#if !(DO_NOT_USE_ZOOM)
if(_videoOutputZOOMMain != NULL) {
mainScreenPlayer()->removeVideoRenderer(_videoOutputZOOMMain);
}
if(_videoOutputZOOMSub != NULL) {
subScreenPlayer()->removeVideoRenderer(_videoOutputZOOMSub);
}
#endif
if(forceEmit == false)
{
blockEvents(200);
}
_ch3mode = !_ch3mode;
if(_ch3mode)
{
// 전방을 후방에 표시
_playerI->setRenderer(_isSwaped() ? _videoOutputF : _videoOutputR);
// 전방 파일이 존재하지 않을 경우
if(_currentItem != NULL && _currentItem->filePathCH3.length() == 0)
{
_videoOutputR->receive(FAV::VideoFrame()); // clear
}
else
{
_playerR->setRenderer(NULL);
}
}
else
{
_playerI->setRenderer(NULL);
// 전방 파일이 존재하지 않을 경우
if(_currentItem != NULL && (_currentItem->filePathCH2.length() == 0))
{
_videoOutputR->receive(FAV::VideoFrame());
}
else
{
_playerR->setRenderer(_isSwaped() ? _videoOutputF : _videoOutputR); // clear
}
}
#if !(DO_NOT_USE_ZOOM)
if(_videoOutputZOOMMain != NULL) {
mainScreenPlayer()->addVideoRenderer(_videoOutputZOOMMain);
}
if(_videoOutputZOOMSub != NULL) {
subScreenPlayer()->addVideoRenderer(_videoOutputZOOMSub);
}
#endif
updatePausedScreen();
emit playEvent(_ch3mode ? PLAY_DID_INDOOR_CH : PLAY_DID_OUTDOOR_CH, NULL);
}
#elif (TRI_CHANNEL2)
void RMPlayer::toggleIndoor(bool forceEmit)
{
Q_UNUSED(forceEmit)
if(_currentItem->realCHCount() < 3){
return;
}
_ch3mode = !_ch3mode;
_playerR->setVideoStream(_ch3mode ? 2 : 1);
// 정지시에도 업데이트
if(_playerF->isPaused()) {
_playerR->setPosition(_playerF->position());
}
//qInfo() << "_ch3mode:" << _ch3mode << __FUNCTION__;
emit playEvent(_ch3mode ? PLAY_DID_INDOOR_CH : PLAY_DID_OUTDOOR_CH, NULL);
}
#endif // TRI_CHANNEL
#if (SUPPORT_WIDE_MODE)
bool RMPlayer::itemIsWideMode()
{
return _currentItem != NULL && _currentItem->isWideMode();
}
bool RMPlayer::isWideMode()
{
if(_currentItem == NULL || !_currentItem->isWideMode()){
return false;
}
FAV::VideoRenderer *t = _currentVideoRenderer(playerR());
return (t->opengl()->mode() == 1);
}
void RMPlayer::toggleWide() // NORMAL <-> WIDE
{
if(!_currentItem->isWideMode()){
return;
}
FAV::VideoRenderer *t = _currentVideoRenderer(playerR());
int mode = t->opengl()->mode();
t->opengl()->setMode(mode == 1 ? 0 : 1);
// RendererAspectRatio //Use renderer's aspect ratio, i.e. stretch to fit the renderer rect
// , VideoAspectRatio //Use video's aspect ratio and align center in renderer.
// , CustomAspectRation //Use the ratio set by setOutAspectRatio(qreal). Mode will be set to this if that function is called
// 정지시에도 업데이트
t->widget()->update();
// if(_playerF->isPaused()) {
// _playerR->setPosition(_playerF->position());
// }
}
#endif // RM_MODEL_EMT_KR
void RMPlayer::blockEvents(int msec,bool b_show_indicator)
{
#if !(H265_SUPPORT)
Q_UNUSED(b_show_indicator)
#endif
_eventBlocking = true;
if (_blockTimer != NULL)
{
_blockTimer->stop();
delete _blockTimer;
_blockTimer = NULL;
}
_blockTimer = new QTimer(this);
_blockTimer->setSingleShot(true);
_blockTimer->setInterval(msec);
connect(_blockTimer,SIGNAL(timeout()),SLOT(onReleaseBlockEvent()));
_blockTimer->start();
#if (H265_SUPPORT)
if (b_show_indicator == true)
{
emit show_indicator(true);
}
#endif
//QTimer::singleShot(msec,this, SLOT(onReleaseBlockEvent()));
}
void RMPlayer::onReleaseBlockEvent()
{
_eventBlocking = false;
if (_blockTimer != NULL)
{
_blockTimer->stop();
delete _blockTimer;
_blockTimer = NULL;
}
#if (H265_SUPPORT)
emit show_indicator(false);
#endif
}
QDateTime RMPlayer::currentTime(double* lon,double* lat)
{
if(!_playerF->isLoaded()) {
return QDateTime();
}
double ratio = ((double)_playerF->position()) / ((double)_playerF->duration());
return _currentItem->dateTimeInPosition(ratio,lat,lon);
}
// 타이머
void RMPlayer::timerEvent(QTimerEvent *e)
{
if (e->timerId() != _timer_id)
{
return;
}
sync_clock();
//qInfo() << "P:" << _playerF->position() << "D:" << _playerF->duration();
}
void RMPlayer::sync_clock()
{
if (_sync_time == false)
{
return;
}
#if (PLAY_SYNC_FIX2)
return;
#endif //
// seek 직후 clock 을 update 해버리면 seek 와 clock 이 delay / diff 발생 => 비디오 타이밍 매칭 하기위해 정지함
// 하지만 고속 플레이 등을 위해서는 정기적으로 동기화가 필요함....
if (!_clock->isActive())
{
return;
}
// if(_clock->value() >= _playerF->durationF())
// {
// _clock->pause(true);
// _clock->updateValue(_playerF->durationF());
// }
// 화면깨짐 방지
//#if (RM_MODEL == RM_MODEL_TYPE_KEIYO1)
//qInfo() << "$$$SYNC CLOCK" << _clock->value() << __FUNCTION__;
_playerF->masterClock()->updateExternalClock(*_clock);
// if(_playerR->isLoaded()) {
// _playerR->masterClock()->updateExternalClock(*_clock);
// }
FLOG_OT << "TIME SYNC:" << _clock->value() << FLOGE;
//#endif
}
#if (RM_TESTING)
void RMPlayer::applyTestFilter(QString filter)
{
if(_testFilterF != NULL)
{
_testFilterF->uninstall();
delete _testFilterF;
_testFilterF = NULL;
}
if(filter.length() > 0)
{
_testFilterF = new FAV::LibAVFilterVideo(_playerF);
_testFilterF->installTo(_playerF);
_testFilterF->setOptions(filter);
}
updatePausedScreenFrontOnly();
}
#endif