|
|
|
/*
|
|
|
|
* Copyright (C) 2010-2013 Groupement d'Intérêt Public pour l'Education Numérique en Afrique (GIP ENA)
|
|
|
|
*
|
|
|
|
* This file is part of Open-Sankoré.
|
|
|
|
*
|
|
|
|
* Open-Sankoré is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, version 3 of the License,
|
|
|
|
* with a specific linking exception for the OpenSSL project's
|
|
|
|
* "OpenSSL" library (or with modified versions of it that use the
|
|
|
|
* same license as the "OpenSSL" library).
|
|
|
|
*
|
|
|
|
* Open-Sankoré 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 General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with Open-Sankoré. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "UBPodcastController.h"
|
|
|
|
|
|
|
|
#include "frameworks/UBFileSystemUtils.h"
|
|
|
|
#include "frameworks/UBStringUtils.h"
|
|
|
|
#include "frameworks/UBPlatformUtils.h"
|
|
|
|
|
|
|
|
#include "core/UBApplication.h"
|
|
|
|
#include "core/UBSettings.h"
|
|
|
|
#include "core/UBSetting.h"
|
|
|
|
#include "core/UBDisplayManager.h"
|
|
|
|
|
|
|
|
#include "board/UBBoardController.h"
|
|
|
|
#include "board/UBBoardView.h"
|
|
|
|
#include "board/UBBoardPaletteManager.h"
|
|
|
|
|
|
|
|
#include "gui/UBMainWindow.h"
|
|
|
|
|
|
|
|
#include "web/UBWebController.h"
|
|
|
|
#include "web/browser/WBWebView.h"
|
|
|
|
|
|
|
|
#include "domain/UBGraphicsScene.h"
|
|
|
|
|
|
|
|
#include "UBAbstractVideoEncoder.h"
|
|
|
|
|
|
|
|
#include "podcast/youtube/UBYouTubePublisher.h"
|
|
|
|
#include "podcast/intranet/UBIntranetPodcastPublisher.h"
|
|
|
|
#include "UBPodcastRecordingPalette.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef Q_WS_WIN
|
|
|
|
#include "windowsmedia/UBWindowsMediaVideoEncoder.h"
|
|
|
|
#include "windowsmedia/UBWaveRecorder.h"
|
|
|
|
#elif defined(Q_WS_MAC)
|
|
|
|
#include "quicktime/UBQuickTimeVideoEncoder.h"
|
|
|
|
#include "quicktime/UBAudioQueueRecorder.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "core/memcheck.h"
|
|
|
|
|
|
|
|
UBPodcastController* UBPodcastController::sInstance = 0;
|
|
|
|
|
|
|
|
unsigned int UBPodcastController::sBackgroundColor = 0x00000000; // BBGGRRAA
|
|
|
|
|
|
|
|
|
|
|
|
UBPodcastController::UBPodcastController(QObject* pParent)
|
|
|
|
: QObject(pParent)
|
|
|
|
, mVideoEncoder(0)
|
|
|
|
, mIsGrabbing(false)
|
|
|
|
, mInitialized(false)
|
|
|
|
, mVideoFramesPerSecondAtStart(10)
|
|
|
|
, mVideoFrameSizeAtStart(1024, 768)
|
|
|
|
, mVideoBitsPerSecondAtStart(1700000)
|
|
|
|
, mSourceWidget(0)
|
|
|
|
, mSourceScene(0)
|
|
|
|
, mScreenGrabingTimerEventID(0)
|
|
|
|
, mRecordingProgressTimerEventID(0)
|
|
|
|
, mRecordingPalette(0)
|
|
|
|
, mRecordingState(Stopped)
|
|
|
|
, mApplicationIsClosing(false)
|
|
|
|
, mRecordingTimestampOffset(0)
|
|
|
|
, mDefaultAudioInputDeviceAction(0)
|
|
|
|
, mNoAudioInputDeviceAction(0)
|
|
|
|
, mSmallVideoSizeAction(0)
|
|
|
|
, mMediumVideoSizeAction(0)
|
|
|
|
, mFullVideoSizeAction(0)
|
|
|
|
, mYoutubePublicationAction(0)
|
|
|
|
, mIntranetPublicationAction(0)
|
|
|
|
{
|
|
|
|
connect(UBApplication::applicationController, SIGNAL(mainModeChanged(UBApplicationController::MainMode)),
|
|
|
|
this, SLOT(applicationMainModeChanged(UBApplicationController::MainMode)));
|
|
|
|
|
|
|
|
connect(UBApplication::applicationController, SIGNAL(desktopMode(bool)),
|
|
|
|
this, SLOT(applicationDesktopMode(bool)));
|
|
|
|
|
|
|
|
connect(UBApplication::webController, SIGNAL(activeWebPageChanged(WBWebView*)),
|
|
|
|
this, SLOT(webActiveWebPageChanged(WBWebView*)));
|
|
|
|
|
|
|
|
connect(UBApplication::app(), SIGNAL(lastWindowClosed()),
|
|
|
|
this, SLOT(applicationAboutToQuit()));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UBPodcastController::~UBPodcastController()
|
|
|
|
{
|
|
|
|
// NOOP
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::applicationAboutToQuit()
|
|
|
|
{
|
|
|
|
mApplicationIsClosing = true;
|
|
|
|
|
|
|
|
if(mRecordingState == Recording || mRecordingState == Paused)
|
|
|
|
{
|
|
|
|
stop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::groupActionTriggered(QAction* action)
|
|
|
|
{
|
|
|
|
Q_UNUSED(action);
|
|
|
|
updateActionState();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::actionToggled(bool checked)
|
|
|
|
{
|
|
|
|
Q_UNUSED(checked);
|
|
|
|
updateActionState();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::updateActionState()
|
|
|
|
{
|
|
|
|
if (mSmallVideoSizeAction && mSmallVideoSizeAction->isChecked())
|
|
|
|
UBSettings::settings()->podcastVideoSize->set("Small");
|
|
|
|
else if (mFullVideoSizeAction && mFullVideoSizeAction->isChecked())
|
|
|
|
UBSettings::settings()->podcastVideoSize->set("Full");
|
|
|
|
else
|
|
|
|
UBSettings::settings()->podcastVideoSize->reset();
|
|
|
|
|
|
|
|
UBSettings::settings()->podcastAudioRecordingDevice->reset();
|
|
|
|
|
|
|
|
if (mDefaultAudioInputDeviceAction && mDefaultAudioInputDeviceAction->isChecked())
|
|
|
|
UBSettings::settings()->podcastAudioRecordingDevice->set("Default");
|
|
|
|
else if (mNoAudioInputDeviceAction && mNoAudioInputDeviceAction->isChecked())
|
|
|
|
UBSettings::settings()->podcastAudioRecordingDevice->set("None");
|
|
|
|
else
|
|
|
|
{
|
|
|
|
foreach(QAction* action, mAudioInputDevicesActions)
|
|
|
|
{
|
|
|
|
if (action->isChecked())
|
|
|
|
{
|
|
|
|
UBSettings::settings()->podcastAudioRecordingDevice->set(action->text());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UBSettings::settings()->podcastPublishToYoutube->set(mYoutubePublicationAction && mYoutubePublicationAction->isChecked());
|
|
|
|
UBSettings::settings()->podcastPublishToIntranet->set(mIntranetPublicationAction && mIntranetPublicationAction->isChecked());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::setSourceWidget(QWidget* pWidget)
|
|
|
|
{
|
|
|
|
if (mSourceWidget != pWidget)
|
|
|
|
{
|
|
|
|
if (mSourceWidget == qApp->desktop())
|
|
|
|
{
|
|
|
|
killTimer(mScreenGrabingTimerEventID);
|
|
|
|
mScreenGrabingTimerEventID = 0;
|
|
|
|
}
|
|
|
|
else if (mSourceWidget)
|
|
|
|
{
|
|
|
|
mSourceWidget->removeEventFilter(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
mSourceWidget = pWidget;
|
|
|
|
mInitialized = false;
|
|
|
|
mViewToVideoTransform.reset();
|
|
|
|
mLatestCapture.fill(sBackgroundColor);
|
|
|
|
|
|
|
|
if (mSourceWidget)
|
|
|
|
{
|
|
|
|
QSizeF sourceWidgetSize(mSourceWidget->size());
|
|
|
|
|
|
|
|
if (mSourceWidget == qApp->desktop())
|
|
|
|
sourceWidgetSize = qApp->desktop()->availableGeometry(UBApplication::applicationController->displayManager()->controleScreenIndex()).size();
|
|
|
|
|
|
|
|
QSizeF videoFrameSize(mVideoFrameSizeAtStart);
|
|
|
|
|
|
|
|
qreal scaleHorizontal = videoFrameSize.width() / sourceWidgetSize.width();
|
|
|
|
qreal scaleVertical = videoFrameSize.height() / sourceWidgetSize.height();
|
|
|
|
|
|
|
|
qreal scale = qMin(scaleHorizontal, scaleVertical);
|
|
|
|
|
|
|
|
mViewToVideoTransform.scale(scale, scale);
|
|
|
|
|
|
|
|
UBBoardView *bv = qobject_cast<UBBoardView *>(mSourceWidget);
|
|
|
|
|
|
|
|
if (bv)
|
|
|
|
{
|
|
|
|
connect(UBApplication::boardController, SIGNAL(activeSceneChanged()), this, SLOT(activeSceneChanged()));
|
|
|
|
connect(UBApplication::boardController, SIGNAL(backgroundChanged()), this, SLOT(sceneBackgroundChanged()));
|
|
|
|
connect(UBApplication::boardController, SIGNAL(controlViewportChanged()), this, SLOT(activeSceneChanged()));
|
|
|
|
|
|
|
|
activeSceneChanged();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QSizeF scaledWidgetSize = sourceWidgetSize * scale;
|
|
|
|
|
|
|
|
int offsetX = (videoFrameSize.width() - scaledWidgetSize.width()) / 2;
|
|
|
|
int offsetY = (videoFrameSize.height() - scaledWidgetSize.height()) / 2;
|
|
|
|
|
|
|
|
mViewToVideoTransform.translate(offsetX / scale, offsetY / scale);
|
|
|
|
|
|
|
|
disconnect(UBApplication::boardController, SIGNAL(activeSceneChanged()), this, SLOT(activeSceneChanged()));
|
|
|
|
disconnect(UBApplication::boardController, SIGNAL(backgroundChanged()), this, SLOT(sceneBackgroundChanged()));
|
|
|
|
disconnect(UBApplication::boardController, SIGNAL(controlViewportChanged()), this, SLOT(activeSceneChanged()));
|
|
|
|
|
|
|
|
mSourceScene = 0;
|
|
|
|
|
|
|
|
startNextChapter();
|
|
|
|
|
|
|
|
if(mSourceWidget == qApp->desktop())
|
|
|
|
{
|
|
|
|
mScreenGrabingTimerEventID = startTimer(1000 / mVideoFramesPerSecondAtStart);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mSourceWidget->installEventFilter(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UBPodcastController* UBPodcastController::instance()
|
|
|
|
{
|
|
|
|
if(!sInstance)
|
|
|
|
sInstance = new UBPodcastController(UBApplication::staticMemoryCleaner);
|
|
|
|
|
|
|
|
return sInstance;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::start()
|
|
|
|
{
|
|
|
|
if (mRecordingState == Stopped)
|
|
|
|
{
|
|
|
|
mInitialized = false;
|
|
|
|
|
|
|
|
QSize recommendedSize(1024, 768);
|
|
|
|
|
|
|
|
int fullBitRate = UBSettings::settings()->podcastWindowsMediaBitsPerSecond->get().toInt();
|
|
|
|
|
|
|
|
if (mSmallVideoSizeAction && mSmallVideoSizeAction->isChecked())
|
|
|
|
{
|
|
|
|
recommendedSize = QSize(640, 480);
|
|
|
|
mVideoBitsPerSecondAtStart = fullBitRate / 4;
|
|
|
|
}
|
|
|
|
else if (mMediumVideoSizeAction && mMediumVideoSizeAction->isChecked())
|
|
|
|
{
|
|
|
|
recommendedSize = QSize(1024, 768);
|
|
|
|
mVideoBitsPerSecondAtStart = fullBitRate / 2;
|
|
|
|
}
|
|
|
|
else if (mFullVideoSizeAction && mFullVideoSizeAction->isChecked())
|
|
|
|
{
|
|
|
|
recommendedSize = UBApplication::boardController->controlView()->size();
|
|
|
|
mVideoBitsPerSecondAtStart = fullBitRate;
|
|
|
|
}
|
|
|
|
|
|
|
|
QSize scaledboardSize = UBApplication::boardController->controlView()->size();
|
|
|
|
scaledboardSize.scale(recommendedSize, Qt::KeepAspectRatio);
|
|
|
|
|
|
|
|
// Video width/height should be a multiple of 4
|
|
|
|
|
|
|
|
int width = scaledboardSize.width();
|
|
|
|
int height = scaledboardSize.height();
|
|
|
|
|
|
|
|
if (width % 4 != 0)
|
|
|
|
width = ((width / 4) * 4);
|
|
|
|
|
|
|
|
if (height % 4 != 0)
|
|
|
|
height = ((height / 4) * 4);
|
|
|
|
|
|
|
|
mVideoFrameSizeAtStart = QSize(width, height);
|
|
|
|
|
|
|
|
applicationMainModeChanged(UBApplication::applicationController->displayMode());
|
|
|
|
|
|
|
|
#ifdef Q_WS_WIN
|
|
|
|
mVideoEncoder = new UBWindowsMediaVideoEncoder(this); //deleted on stop
|
|
|
|
#elif defined(Q_WS_MAC)
|
|
|
|
mVideoEncoder = new UBQuickTimeVideoEncoder(this); //deleted on stop
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (mVideoEncoder)
|
|
|
|
{
|
|
|
|
connect(mVideoEncoder, SIGNAL(encodingStatus(const QString&)), this, SLOT(encodingStatus(const QString&)));
|
|
|
|
connect(mVideoEncoder, SIGNAL(encodingFinished(bool)), this, SLOT(encodingFinished(bool)));
|
|
|
|
|
|
|
|
if(mRecordingPalette)
|
|
|
|
{
|
|
|
|
connect(mVideoEncoder, SIGNAL(audioLevelChanged(quint8))
|
|
|
|
, mRecordingPalette, SLOT(audioLevelChanged(quint8)));
|
|
|
|
}
|
|
|
|
|
|
|
|
mVideoEncoder->setRecordAudio(!mNoAudioInputDeviceAction->isChecked());
|
|
|
|
|
|
|
|
QString recordingDevice = "";
|
|
|
|
|
|
|
|
if (!mNoAudioInputDeviceAction->isChecked() && !mDefaultAudioInputDeviceAction->isChecked())
|
|
|
|
{
|
|
|
|
foreach(QAction* audioDevice, mAudioInputDevicesActions)
|
|
|
|
{
|
|
|
|
if (audioDevice->isChecked())
|
|
|
|
{
|
|
|
|
recordingDevice = audioDevice->text();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mVideoEncoder->setAudioRecordingDevice(recordingDevice);
|
|
|
|
|
|
|
|
mVideoEncoder->setFramesPerSecond(mVideoFramesPerSecondAtStart);
|
|
|
|
mVideoEncoder->setVideoSize(mVideoFrameSizeAtStart);
|
|
|
|
mVideoEncoder->setVideoBitsPerSecond(mVideoBitsPerSecondAtStart);
|
|
|
|
|
|
|
|
mPartNumber = 0;
|
|
|
|
|
|
|
|
mPodcastRecordingPath = UBSettings::settings()->userPodcastRecordingDirectory();
|
|
|
|
|
|
|
|
qDebug() << "mPodcastRecordingPath: " << mPodcastRecordingPath;
|
|
|
|
|
|
|
|
QString videoFileName;
|
|
|
|
|
|
|
|
if (mIntranetPublicationAction && mIntranetPublicationAction->isChecked())
|
|
|
|
{
|
|
|
|
videoFileName = mPodcastRecordingPath + "/" + "Podcast-"
|
|
|
|
+ QDateTime::currentDateTime().toString("yyyyMMddhhmmss")
|
|
|
|
+ "-" + UBPlatformUtils::computerName() + "." + mVideoEncoder->videoFileExtension();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
videoFileName = mPodcastRecordingPath + "/" + tr("OpenBoard Cast") + "." + mVideoEncoder->videoFileExtension();
|
|
|
|
}
|
|
|
|
|
|
|
|
videoFileName = UBFileSystemUtils::nextAvailableFileName(videoFileName, " ");
|
|
|
|
|
|
|
|
mVideoEncoder->setVideoFileName(videoFileName);
|
|
|
|
|
|
|
|
mLatestCapture = QImage(mVideoFrameSizeAtStart, QImage::Format_RGB32); //0xffRRGGBB
|
|
|
|
|
|
|
|
mRecordStartTime = QTime::currentTime();
|
|
|
|
|
|
|
|
mRecordingProgressTimerEventID = startTimer(100);
|
|
|
|
|
|
|
|
if(mVideoEncoder->start())
|
|
|
|
{
|
|
|
|
applicationMainModeChanged(UBApplication::applicationController->displayMode());
|
|
|
|
|
|
|
|
setRecordingState(Recording);
|
|
|
|
|
|
|
|
if (mSourceScene)
|
|
|
|
{
|
|
|
|
processScenePaintEvent();
|
|
|
|
}
|
|
|
|
else if (mSourceWidget)
|
|
|
|
{
|
|
|
|
processWidgetPaintEvent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
UBApplication::showMessage(tr("Failed to start encoder ..."), false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
UBApplication::showMessage(tr("No Podcast encoder available ..."), false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void UBPodcastController::pause()
|
|
|
|
{
|
|
|
|
if (mVideoEncoder && mRecordingState == Recording && mVideoEncoder->canPause())
|
|
|
|
{
|
|
|
|
sendLatestPixmapToEncoder();
|
|
|
|
|
|
|
|
mTimeAtPaused = QTime::currentTime();
|
|
|
|
|
|
|
|
if (mVideoEncoder->pause())
|
|
|
|
{
|
|
|
|
setRecordingState(Paused);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::unpause()
|
|
|
|
{
|
|
|
|
if (mVideoEncoder && mRecordingState == Paused && mVideoEncoder->canPause())
|
|
|
|
{
|
|
|
|
if (mVideoEncoder->unpause())
|
|
|
|
{
|
|
|
|
mRecordingTimestampOffset += mTimeAtPaused.msecsTo(QTime::currentTime());
|
|
|
|
sendLatestPixmapToEncoder();
|
|
|
|
|
|
|
|
setRecordingState(Recording);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::stop()
|
|
|
|
{
|
|
|
|
if ((mRecordingState == Recording || mRecordingState == Paused) && mVideoEncoder)
|
|
|
|
{
|
|
|
|
if (mScreenGrabingTimerEventID != 0)
|
|
|
|
{
|
|
|
|
killTimer(mScreenGrabingTimerEventID);
|
|
|
|
mScreenGrabingTimerEventID = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mRecordingProgressTimerEventID != 0)
|
|
|
|
killTimer(mRecordingProgressTimerEventID);
|
|
|
|
|
|
|
|
sendLatestPixmapToEncoder();
|
|
|
|
|
|
|
|
setRecordingState(Stopping);
|
|
|
|
|
|
|
|
mVideoEncoder->stop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool UBPodcastController::eventFilter(QObject *obj, QEvent *event)
|
|
|
|
{
|
|
|
|
if (mRecordingState == Recording && !mIsGrabbing && event->type() == QEvent::Paint)
|
|
|
|
{
|
|
|
|
QPaintEvent *paintEvent = static_cast<QPaintEvent*>(event);
|
|
|
|
|
|
|
|
mWidgetRepaintRectQueue.enqueue(paintEvent->rect());
|
|
|
|
|
|
|
|
if (mWidgetRepaintRectQueue.length() == 1)
|
|
|
|
QTimer::singleShot(1000.0 / mVideoFramesPerSecondAtStart, this, SLOT(processWidgetPaintEvent()));
|
|
|
|
}
|
|
|
|
|
|
|
|
return QObject::eventFilter(obj, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::processWidgetPaintEvent()
|
|
|
|
{
|
|
|
|
if(mRecordingState != Recording)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QRect repaintRect;
|
|
|
|
|
|
|
|
if (!mInitialized)
|
|
|
|
{
|
|
|
|
mWidgetRepaintRectQueue.clear();
|
|
|
|
repaintRect = mSourceWidget->geometry();
|
|
|
|
|
|
|
|
mLatestCapture.fill(sBackgroundColor);
|
|
|
|
|
|
|
|
mInitialized = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
while(mWidgetRepaintRectQueue.size() > 0)
|
|
|
|
{
|
|
|
|
repaintRect = repaintRect.unite(mWidgetRepaintRectQueue.dequeue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!repaintRect.isNull())
|
|
|
|
{
|
|
|
|
mIsGrabbing = true;
|
|
|
|
|
|
|
|
QPainter p(&mLatestCapture);
|
|
|
|
p.setTransform(mViewToVideoTransform);
|
|
|
|
p.setRenderHints(QPainter::Antialiasing);
|
|
|
|
p.setRenderHints(QPainter::SmoothPixmapTransform);
|
|
|
|
|
|
|
|
mSourceWidget->render(&p, repaintRect.topLeft(), QRegion(repaintRect), QWidget::DrawChildren);
|
|
|
|
|
|
|
|
mIsGrabbing = false;
|
|
|
|
|
|
|
|
sendLatestPixmapToEncoder();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::activeSceneChanged()
|
|
|
|
{
|
|
|
|
if (mSourceScene)
|
|
|
|
{
|
|
|
|
disconnect(mSourceScene, SIGNAL(changed(const QList<QRectF>&)),
|
|
|
|
this, SLOT(sceneChanged(const QList<QRectF> &)));
|
|
|
|
}
|
|
|
|
|
|
|
|
mSourceScene = UBApplication::boardController->activeScene();
|
|
|
|
|
|
|
|
connect(mSourceScene, SIGNAL(changed(const QList<QRectF>&)),
|
|
|
|
this, SLOT(sceneChanged(const QList<QRectF> &)));
|
|
|
|
|
|
|
|
mInitialized = false;
|
|
|
|
|
|
|
|
startNextChapter();
|
|
|
|
|
|
|
|
UBBoardView *bv = qobject_cast<UBBoardView*>(mSourceWidget);
|
|
|
|
if (bv)
|
|
|
|
{
|
|
|
|
QRectF viewportRect = bv->mapToScene(bv->geometry()).boundingRect();
|
|
|
|
mSceneRepaintRectQueue.enqueue(viewportRect);
|
|
|
|
}
|
|
|
|
|
|
|
|
processScenePaintEvent();
|
|
|
|
}
|
|
|
|
|
|
|
|
void UBPodcastController::sceneBackgroundChanged()
|
|
|
|
{
|
|
|
|
UBBoardView *bv = qobject_cast<UBBoardView*>(mSourceWidget);
|
|
|
|
|
|
|
|
if (bv)
|
|
|
|
{
|
|
|
|
mInitialized = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
processScenePaintEvent();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
long UBPodcastController::elapsedRecordingMs()
|
|
|
|
{
|
|
|
|
QTime now = QTime::currentTime();
|
|
|
|
long msFromStart = mRecordStartTime.msecsTo(now);
|
|
|
|
|
|
|
|
return msFromStart - mRecordingTimestampOffset;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::startNextChapter()
|
|
|
|
{
|
|
|
|
if (mVideoEncoder)
|
|
|
|
{
|
|
|
|
//punch chapter in
|
|
|
|
++mPartNumber;
|
|
|
|
mVideoEncoder->newChapter(tr("Part %1").arg(mPartNumber), elapsedRecordingMs());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::sceneChanged(const QList<QRectF> & region)
|
|
|
|
{
|
|
|
|
if(mRecordingState != Recording)
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool shouldRepaint = (mSceneRepaintRectQueue.length() == 0);
|
|
|
|
|
|
|
|
UBBoardView *bv = qobject_cast<UBBoardView *>(mSourceWidget);
|
|
|
|
if (bv)
|
|
|
|
{
|
|
|
|
QRectF viewportRect = bv->mapToScene(QRect(0, 0, bv->width(), bv->height())).boundingRect();
|
|
|
|
foreach(const QRectF rect, region)
|
|
|
|
{
|
|
|
|
QRectF maxRect = rect.intersect(viewportRect);
|
|
|
|
mSceneRepaintRectQueue.enqueue(maxRect);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shouldRepaint)
|
|
|
|
QTimer::singleShot(1000.0 / mVideoFramesPerSecondAtStart, this, SLOT(processScenePaintEvent()));
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::processScenePaintEvent()
|
|
|
|
{
|
|
|
|
if(mRecordingState != Recording)
|
|
|
|
return;
|
|
|
|
|
|
|
|
UBBoardView *bv = qobject_cast<UBBoardView *>(mSourceWidget);
|
|
|
|
|
|
|
|
if(!bv)
|
|
|
|
return;
|
|
|
|
|
|
|
|
QRectF repaintRect;
|
|
|
|
|
|
|
|
if (!mInitialized)
|
|
|
|
{
|
|
|
|
mSceneRepaintRectQueue.clear();
|
|
|
|
repaintRect = bv->mapToScene(QRect(0, 0, bv->width(), bv->height())).boundingRect();
|
|
|
|
|
|
|
|
if (bv->scene()->isDarkBackground())
|
|
|
|
mLatestCapture.fill(Qt::black);
|
|
|
|
else
|
|
|
|
mLatestCapture.fill(Qt::white);
|
|
|
|
|
|
|
|
mLatestCapture.fill(Qt::white);
|
|
|
|
|
|
|
|
mInitialized = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
while(mSceneRepaintRectQueue.size() > 0)
|
|
|
|
{
|
|
|
|
repaintRect = repaintRect.unite(mSceneRepaintRectQueue.dequeue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!repaintRect.isNull())
|
|
|
|
{
|
|
|
|
UBGraphicsScene *scene = bv->scene();
|
|
|
|
|
|
|
|
QPainter p(&mLatestCapture);
|
|
|
|
|
|
|
|
p.setTransform(mViewToVideoTransform);
|
|
|
|
p.setTransform(bv->viewportTransform(), true);
|
|
|
|
|
|
|
|
p.setRenderHints(QPainter::Antialiasing);
|
|
|
|
p.setRenderHints(QPainter::SmoothPixmapTransform);
|
|
|
|
|
|
|
|
repaintRect.adjust(-1, -1, 1, 1);
|
|
|
|
|
|
|
|
p.setClipRect(repaintRect);
|
|
|
|
|
|
|
|
if (scene->isDarkBackground())
|
|
|
|
p.fillRect(repaintRect, Qt::black);
|
|
|
|
else
|
|
|
|
p.fillRect(repaintRect, Qt::white);
|
|
|
|
|
|
|
|
scene->setRenderingContext(UBGraphicsScene::Podcast);
|
|
|
|
|
|
|
|
scene->render(&p, repaintRect, repaintRect);
|
|
|
|
|
|
|
|
scene->setRenderingContext(UBGraphicsScene::Screen);
|
|
|
|
|
|
|
|
sendLatestPixmapToEncoder();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::applicationMainModeChanged(UBApplicationController::MainMode pMode)
|
|
|
|
{
|
|
|
|
if (pMode == UBApplicationController::Internet)
|
|
|
|
{
|
|
|
|
setSourceWidget(UBApplication::webController->controlView());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
setSourceWidget(UBApplication::boardController->controlView());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::applicationDesktopMode(bool displayed)
|
|
|
|
{
|
|
|
|
Q_UNUSED(displayed);
|
|
|
|
|
|
|
|
if (displayed)
|
|
|
|
{
|
|
|
|
setSourceWidget(qApp->desktop());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
applicationMainModeChanged(UBApplication::applicationController->displayMode());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::webActiveWebPageChanged(WBWebView* pWebView)
|
|
|
|
{
|
|
|
|
if(UBApplication::applicationController->displayMode() == UBApplicationController::Internet)
|
|
|
|
{
|
|
|
|
setSourceWidget(pWebView);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::encodingStatus(const QString& pStatus)
|
|
|
|
{
|
|
|
|
UBApplication::showMessage(pStatus, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::encodingFinished(bool ok)
|
|
|
|
{
|
|
|
|
if (mVideoEncoder)
|
|
|
|
{
|
|
|
|
if (ok)
|
|
|
|
{
|
|
|
|
if (!mApplicationIsClosing)
|
|
|
|
{
|
|
|
|
QString location;
|
|
|
|
|
|
|
|
if (mPodcastRecordingPath == QDesktopServices::storageLocation(QDesktopServices::DesktopLocation))
|
|
|
|
location = tr("on your desktop ...");
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QDir dir(mPodcastRecordingPath);
|
|
|
|
location = tr("in folder %1").arg(mPodcastRecordingPath);
|
|
|
|
}
|
|
|
|
|
|
|
|
UBApplication::showMessage(tr("Podcast created %1").arg(location), false);
|
|
|
|
|
|
|
|
if (mIntranetPublicationAction && mIntranetPublicationAction->isChecked())
|
|
|
|
{
|
|
|
|
UBIntranetPodcastPublisher* intranet = new UBIntranetPodcastPublisher(this); // Self destroyed
|
|
|
|
intranet->publishVideo(mVideoEncoder->videoFileName(), elapsedRecordingMs());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mYoutubePublicationAction && mYoutubePublicationAction->isChecked())
|
|
|
|
{
|
|
|
|
UBYouTubePublisher* youTube = new UBYouTubePublisher(this); // Self destroyed
|
|
|
|
youTube->uploadVideo(mVideoEncoder->videoFileName());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qWarning() << mVideoEncoder->lastErrorMessage();
|
|
|
|
|
|
|
|
UBApplication::showMessage(tr("Podcast recording error (%1)").arg(mVideoEncoder->lastErrorMessage()), false);
|
|
|
|
}
|
|
|
|
|
|
|
|
mVideoEncoder->deleteLater();
|
|
|
|
|
|
|
|
setRecordingState(Stopped);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::sendLatestPixmapToEncoder()
|
|
|
|
{
|
|
|
|
if (mVideoEncoder)
|
|
|
|
mVideoEncoder->newPixmap(mLatestCapture, elapsedRecordingMs());
|
|
|
|
}
|
|
|
|
|
|
|
|
void UBPodcastController::timerEvent(QTimerEvent *event)
|
|
|
|
{
|
|
|
|
if (mRecordingState == Recording
|
|
|
|
&& event->timerId() == mScreenGrabingTimerEventID
|
|
|
|
&& mSourceWidget == qApp->desktop())
|
|
|
|
{
|
|
|
|
QPixmap desktop = QPixmap::grabWindow(qApp->desktop()->screen(UBApplication::applicationController->displayManager()->controleScreenIndex())->winId());
|
|
|
|
|
|
|
|
{
|
|
|
|
QPainter p(&mLatestCapture);
|
|
|
|
|
|
|
|
if (!mInitialized)
|
|
|
|
{
|
|
|
|
mLatestCapture.fill(sBackgroundColor);
|
|
|
|
mInitialized = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
QRectF targetRect = mViewToVideoTransform.mapRect(QRectF(0, 0, desktop.width(), desktop.height()));
|
|
|
|
|
|
|
|
p.setRenderHints(QPainter::Antialiasing);
|
|
|
|
p.setRenderHints(QPainter::SmoothPixmapTransform);
|
|
|
|
p.drawPixmap(targetRect.left(), targetRect.top(), desktop.scaled(targetRect.width(), targetRect.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
|
|
}
|
|
|
|
|
|
|
|
sendLatestPixmapToEncoder();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mRecordingProgressTimerEventID == event->timerId() && mRecordingState == Recording)
|
|
|
|
{
|
|
|
|
emit recordingProgressChanged(elapsedRecordingMs());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QStringList UBPodcastController::audioRecordingDevices()
|
|
|
|
{
|
|
|
|
QStringList devices;
|
|
|
|
|
|
|
|
#ifdef Q_WS_WIN
|
|
|
|
devices = UBWaveRecorder::waveInDevices();
|
|
|
|
#elif defined(Q_WS_MAC)
|
|
|
|
devices = UBAudioQueueRecorder::waveInDevices();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return devices;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::recordToggled(bool record)
|
|
|
|
{
|
|
|
|
if ((mRecordingState == Stopped) && record)
|
|
|
|
start();
|
|
|
|
else
|
|
|
|
stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void UBPodcastController::pauseToggled(bool paused)
|
|
|
|
{
|
|
|
|
if ((mRecordingState == Recording) && paused)
|
|
|
|
pause();
|
|
|
|
else
|
|
|
|
unpause();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::toggleRecordingPalette(bool visible)
|
|
|
|
{
|
|
|
|
if(!mRecordingPalette)
|
|
|
|
{
|
|
|
|
mRecordingPalette = new UBPodcastRecordingPalette(UBApplication::mainWindow);
|
|
|
|
|
|
|
|
mRecordingPalette->adjustSizeAndPosition();
|
|
|
|
mRecordingPalette->setCustomPosition(true);
|
|
|
|
|
|
|
|
int left = UBApplication::boardController->controlView()->width() * 0.75
|
|
|
|
- mRecordingPalette->width() / 2;
|
|
|
|
|
|
|
|
int top = UBApplication::boardController->controlView()->height()
|
|
|
|
- mRecordingPalette->height() - UBSettings::boardMargin;
|
|
|
|
|
|
|
|
QPoint controlViewPoint(left, top);
|
|
|
|
QPoint mainWindowsPoint = UBApplication::boardController->controlView()->mapTo(UBApplication::mainWindow, controlViewPoint);
|
|
|
|
|
|
|
|
mRecordingPalette->move(mainWindowsPoint);
|
|
|
|
|
|
|
|
connect(UBApplication::mainWindow->actionPodcastRecord, SIGNAL(triggered(bool))
|
|
|
|
, this, SLOT(recordToggled(bool)));
|
|
|
|
|
|
|
|
connect(UBApplication::mainWindow->actionPodcastPause, SIGNAL(toggled(bool))
|
|
|
|
, this, SLOT(pauseToggled(bool)));
|
|
|
|
|
|
|
|
connect(this, SIGNAL(recordingStateChanged(UBPodcastController::RecordingState))
|
|
|
|
, mRecordingPalette, SLOT(recordingStateChanged(UBPodcastController::RecordingState)));
|
|
|
|
connect(this, SIGNAL(recordingProgressChanged(qint64))
|
|
|
|
, mRecordingPalette, SLOT(recordingProgressChanged(qint64)));
|
|
|
|
}
|
|
|
|
|
|
|
|
mRecordingPalette->setVisible(visible);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UBPodcastController::setRecordingState(RecordingState pRecordingState)
|
|
|
|
{
|
|
|
|
if(mRecordingState != pRecordingState)
|
|
|
|
{
|
|
|
|
mRecordingState = pRecordingState;
|
|
|
|
emit recordingStateChanged(mRecordingState);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QList<QAction*> UBPodcastController::audioRecordingDevicesActions()
|
|
|
|
{
|
|
|
|
if (mAudioInputDevicesActions.length() == 0)
|
|
|
|
{
|
|
|
|
QString settingsDevice = UBSettings::settings()->podcastAudioRecordingDevice->get().toString();
|
|
|
|
|
|
|
|
mDefaultAudioInputDeviceAction = new QAction(tr("Default Audio Input"), this);
|
|
|
|
QAction *checkedAction = mDefaultAudioInputDeviceAction;
|
|
|
|
|
|
|
|
mNoAudioInputDeviceAction = new QAction(tr("No Audio Recording"), this);
|
|
|
|
|
|
|
|
if (settingsDevice == "None")
|
|
|
|
checkedAction = mNoAudioInputDeviceAction;
|
|
|
|
|
|
|
|
mAudioInputDevicesActions << mNoAudioInputDeviceAction;
|
|
|
|
mAudioInputDevicesActions << mDefaultAudioInputDeviceAction;
|
|
|
|
|
|
|
|
foreach(QString audioDevice, audioRecordingDevices())
|
|
|
|
{
|
|
|
|
QAction* act = new QAction(audioDevice, this);
|
|
|
|
act->setCheckable(true);
|
|
|
|
mAudioInputDevicesActions << act;
|
|
|
|
if (settingsDevice == audioDevice)
|
|
|
|
checkedAction = act;
|
|
|
|
}
|
|
|
|
|
|
|
|
QActionGroup* audioInputActionGroup = new QActionGroup(this);
|
|
|
|
audioInputActionGroup->setExclusive(true);
|
|
|
|
|
|
|
|
foreach(QAction* action, mAudioInputDevicesActions)
|
|
|
|
{
|
|
|
|
audioInputActionGroup->addAction(action);
|
|
|
|
action->setCheckable(true);
|
|
|
|
}
|
|
|
|
checkedAction->setChecked(true);
|
|
|
|
|
|
|
|
connect(audioInputActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(groupActionTriggered(QAction*)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return mAudioInputDevicesActions;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QList<QAction*> UBPodcastController::videoSizeActions()
|
|
|
|
{
|
|
|
|
if (mVideoSizesActions.length() == 0)
|
|
|
|
{
|
|
|
|
QList<QAction*> videoSizeActions;
|
|
|
|
|
|
|
|
mSmallVideoSizeAction = new QAction(tr("Small"), this);
|
|
|
|
mMediumVideoSizeAction = new QAction(tr("Medium"), this);
|
|
|
|
mFullVideoSizeAction = new QAction(tr("Full"), this);
|
|
|
|
|
|
|
|
mVideoSizesActions << mSmallVideoSizeAction;
|
|
|
|
mVideoSizesActions << mMediumVideoSizeAction;
|
|
|
|
mVideoSizesActions << mFullVideoSizeAction;
|
|
|
|
|
|
|
|
QActionGroup* videoSizeActionGroup = new QActionGroup(this);
|
|
|
|
videoSizeActionGroup->setExclusive(true);
|
|
|
|
|
|
|
|
foreach(QAction* videoSizeAction, mVideoSizesActions)
|
|
|
|
{
|
|
|
|
videoSizeAction->setCheckable(true);
|
|
|
|
videoSizeActionGroup->addAction(videoSizeAction);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString videoSize = UBSettings::settings()->podcastVideoSize->get().toString();
|
|
|
|
|
|
|
|
if (videoSize == "Small")
|
|
|
|
mSmallVideoSizeAction->setChecked(true);
|
|
|
|
else if (videoSize == "Full")
|
|
|
|
mFullVideoSizeAction->setChecked(true);
|
|
|
|
else
|
|
|
|
mMediumVideoSizeAction->setChecked(true);
|
|
|
|
|
|
|
|
connect(videoSizeActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(groupActionTriggered(QAction*)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return mVideoSizesActions;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QList<QAction*> UBPodcastController::podcastPublicationActions()
|
|
|
|
{
|
|
|
|
if (mPodcastPublicationActions.length() == 0)
|
|
|
|
{
|
|
|
|
mIntranetPublicationAction = new QAction(tr("Publish to Intranet"), this);
|
|
|
|
|
|
|
|
mIntranetPublicationAction->setCheckable(true);
|
|
|
|
mIntranetPublicationAction->setChecked(UBSettings::settings()->podcastPublishToIntranet->get().toBool());
|
|
|
|
|
|
|
|
mPodcastPublicationActions << mIntranetPublicationAction;
|
|
|
|
|
|
|
|
mYoutubePublicationAction = new QAction(tr("Publish to Youtube"), this);
|
|
|
|
mYoutubePublicationAction->setCheckable(true);
|
|
|
|
mYoutubePublicationAction->setChecked(UBSettings::settings()->podcastPublishToYoutube->get().toBool());
|
|
|
|
|
|
|
|
mPodcastPublicationActions << mYoutubePublicationAction;
|
|
|
|
|
|
|
|
foreach(QAction* publicationAction, mPodcastPublicationActions)
|
|
|
|
{
|
|
|
|
connect(publicationAction, SIGNAL(toggled(bool)), this, SLOT(actionToggled(bool)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return mPodcastPublicationActions;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|