/**************************************************************** * RetroShare is distributed under the following license: * * Copyright (C) 2015 * * This program 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; either version 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. ****************************************************************/ #include #include #include #include #include #include "interface/rsVOIP.h" #include "gui/SoundManager.h" #include "util/HandleRichText.h" #include "gui/common/StatusDefs.h" #include "gui/chat/ChatWidget.h" #include "VOIPChatWidgetHolder.h" #include "VideoProcessor.h" #include "QVideoDevice.h" #include #include #define CALL_START ":/images/call-start.png" #define CALL_STOP ":/images/call-stop.png" #define CALL_HOLD ":/images/call-hold.png" VOIPChatWidgetHolder::VOIPChatWidgetHolder(ChatWidget *chatWidget, VOIPNotify *notify) : QObject(), ChatWidgetHolder(chatWidget), mVOIPNotify(notify) { QIcon icon ; icon.addPixmap(QPixmap(":/images/audio-volume-muted.png")) ; icon.addPixmap(QPixmap(":/images/audio-volume-high.png"),QIcon::Normal,QIcon::On) ; icon.addPixmap(QPixmap(":/images/audio-volume-high.png"),QIcon::Disabled,QIcon::On) ; icon.addPixmap(QPixmap(":/images/audio-volume-high.png"),QIcon::Active,QIcon::On) ; icon.addPixmap(QPixmap(":/images/audio-volume-high.png"),QIcon::Selected,QIcon::On) ; audioListenToggleButton = new QToolButton ; audioListenToggleButton->setIcon(icon) ; audioListenToggleButton->setIconSize(QSize(42,42)) ; audioListenToggleButton->setAutoRaise(true) ; audioListenToggleButton->setCheckable(true); audioListenToggleButton->setMinimumSize(QSize(44,44)) ; audioListenToggleButton->setMaximumSize(QSize(44,44)) ; audioListenToggleButton->setText(QString()) ; audioListenToggleButton->setToolTip(tr("Mute")); QIcon icon2 ; icon2.addPixmap(QPixmap(":/images/call-start.png")) ; icon2.addPixmap(QPixmap(":/images/call-hold.png"),QIcon::Normal,QIcon::On) ; icon2.addPixmap(QPixmap(":/images/call-hold.png"),QIcon::Disabled,QIcon::On) ; icon2.addPixmap(QPixmap(":/images/call-hold.png"),QIcon::Active,QIcon::On) ; icon2.addPixmap(QPixmap(":/images/call-hold.png"),QIcon::Selected,QIcon::On) ; audioCaptureToggleButton = new QToolButton ; audioCaptureToggleButton->setMinimumSize(QSize(44,44)) ; audioCaptureToggleButton->setMaximumSize(QSize(44,44)) ; audioCaptureToggleButton->setText(QString()) ; audioCaptureToggleButton->setToolTip(tr("Start Call")); audioCaptureToggleButton->setIcon(icon2) ; audioCaptureToggleButton->setIconSize(QSize(42,42)) ; audioCaptureToggleButton->setAutoRaise(true) ; audioCaptureToggleButton->setCheckable(true) ; hangupButton = new QToolButton ; hangupButton->setIcon(QIcon(":/images/call-stop.png")) ; hangupButton->setIconSize(QSize(42,42)) ; hangupButton->setMinimumSize(QSize(44,44)) ; hangupButton->setMaximumSize(QSize(44,44)) ; hangupButton->setCheckable(false) ; hangupButton->setAutoRaise(true) ; hangupButton->setText(QString()) ; hangupButton->setToolTip(tr("Hangup Call")); hangupButton->hide(); QIcon icon3 ; icon3.addPixmap(QPixmap(":/images/video-icon-on.png")) ; icon3.addPixmap(QPixmap(":/images/video-icon-off.png"),QIcon::Normal,QIcon::On) ; icon3.addPixmap(QPixmap(":/images/video-icon-off.png"),QIcon::Disabled,QIcon::On) ; icon3.addPixmap(QPixmap(":/images/video-icon-off.png"),QIcon::Active,QIcon::On) ; icon3.addPixmap(QPixmap(":/images/video-icon-off.png"),QIcon::Selected,QIcon::On) ; videoCaptureToggleButton = new QToolButton ; videoCaptureToggleButton->setMinimumSize(QSize(44,44)) ; videoCaptureToggleButton->setMaximumSize(QSize(44,44)) ; videoCaptureToggleButton->setText(QString()) ; videoCaptureToggleButton->setToolTip(tr("Start Video Call")); videoCaptureToggleButton->setIcon(icon3) ; videoCaptureToggleButton->setIconSize(QSize(42,42)) ; videoCaptureToggleButton->setAutoRaise(true) ; videoCaptureToggleButton->setCheckable(true) ; connect(videoCaptureToggleButton, SIGNAL(clicked()), this , SLOT(toggleVideoCapture())); connect(audioListenToggleButton, SIGNAL(clicked()), this , SLOT(toggleAudioListen())); connect(audioCaptureToggleButton, SIGNAL(clicked()), this , SLOT(toggleAudioCapture())); connect(hangupButton, SIGNAL(clicked()), this , SLOT(hangupCall())); mChatWidget->addVOIPBarWidget(audioListenToggleButton) ; mChatWidget->addVOIPBarWidget(audioCaptureToggleButton) ; mChatWidget->addVOIPBarWidget(hangupButton) ; mChatWidget->addVOIPBarWidget(videoCaptureToggleButton) ; outputAudioProcessor = NULL ; outputAudioDevice = NULL ; inputAudioProcessor = NULL ; inputAudioDevice = NULL ; inputVideoDevice = new QVideoInputDevice(mChatWidget) ; // not started yet ;-) videoProcessor = new VideoProcessor ; // Make a widget with two video devices, one for echo, and one for the talking peer. videoWidget = new QWidget(mChatWidget) ; videoWidget->setLayout(new QVBoxLayout()) ; videoWidget->layout()->addWidget(outputVideoDevice = new QVideoOutputDevice(videoWidget)) ; videoWidget->layout()->addWidget(echoVideoDevice = new QVideoOutputDevice(videoWidget)) ; videoWidget->hide(); connect(inputVideoDevice, SIGNAL(networkPacketReady()), this, SLOT(sendVideoData())); echoVideoDevice->setMinimumSize(320,256) ; outputVideoDevice->setMinimumSize(320,256) ; echoVideoDevice->setStyleSheet("border: 4px solid #CCCCCC; border-radius: 4px;"); outputVideoDevice->setStyleSheet("border: 4px solid #CCCCCC; border-radius: 4px;"); mChatWidget->addChatHorizontalWidget(videoWidget) ; inputVideoDevice->setEchoVideoTarget(echoVideoDevice) ; inputVideoDevice->setVideoProcessor(videoProcessor) ; videoProcessor->setDisplayTarget(outputVideoDevice) ; } VOIPChatWidgetHolder::~VOIPChatWidgetHolder() { if(inputAudioDevice != NULL) inputAudioDevice->stop() ; delete inputVideoDevice ; delete videoProcessor ; button_map::iterator it = buttonMapTakeVideo.begin(); while (it != buttonMapTakeVideo.end()) { it = buttonMapTakeVideo.erase(it); } } void VOIPChatWidgetHolder::toggleAudioListen() { if (audioListenToggleButton->isChecked()) { audioListenToggleButton->setToolTip(tr("Mute yourself")); } else { audioListenToggleButton->setToolTip(tr("Unmute yourself")); //audioListenToggleButton->setChecked(false); /*if (outputAudioDevice) { outputAudioDevice->stop(); }*/ } } void VOIPChatWidgetHolder::hangupCall() { disconnect(inputAudioProcessor, SIGNAL(networkPacketReady()), this, SLOT(sendAudioData())); if (inputAudioDevice) { inputAudioDevice->stop(); } if (outputAudioDevice) { outputAudioDevice->stop(); } if (mChatWidget) { mChatWidget->addChatMsg(true, tr("VoIP Status"), QDateTime::currentDateTime(), QDateTime::currentDateTime(), tr("Outgoing Call stopped."), ChatWidget::MSGTYPE_SYSTEM); } audioListenToggleButton->setChecked(false); audioCaptureToggleButton->setChecked(false); hangupButton->hide(); } void VOIPChatWidgetHolder::startAudioCapture() { audioCaptureToggleButton->setChecked(true); toggleAudioCapture(); } void VOIPChatWidgetHolder::toggleAudioCapture() { if (audioCaptureToggleButton->isChecked()) { //activate audio output audioListenToggleButton->setChecked(true); audioCaptureToggleButton->setToolTip(tr("Hold Call")); hangupButton->show(); //activate audio input if (!inputAudioProcessor) { inputAudioProcessor = new QtSpeex::SpeexInputProcessor(); if (outputAudioProcessor) { connect(outputAudioProcessor, SIGNAL(playingFrame(QByteArray*)), inputAudioProcessor, SLOT(addEchoFrame(QByteArray*))); } inputAudioProcessor->open(QIODevice::WriteOnly | QIODevice::Unbuffered); } if (!inputAudioDevice) { inputAudioDevice = AudioDeviceHelper::getPreferedInputDevice(); } connect(inputAudioProcessor, SIGNAL(networkPacketReady()), this, SLOT(sendAudioData())); inputAudioDevice->start(inputAudioProcessor); if (mChatWidget) { mChatWidget->addChatMsg(true, tr("VoIP Status"), QDateTime::currentDateTime(), QDateTime::currentDateTime(), tr("Outgoing Call is started..."), ChatWidget::MSGTYPE_SYSTEM); } button_map::iterator it = buttonMapTakeVideo.begin(); while (it != buttonMapTakeVideo.end()) { RSButtonOnText *button = it.value(); delete button; it = buttonMapTakeVideo.erase(it); } } else { disconnect(inputAudioProcessor, SIGNAL(networkPacketReady()), this, SLOT(sendAudioData())); if (inputAudioDevice) { inputAudioDevice->stop(); } audioCaptureToggleButton->setToolTip(tr("Resume Call")); hangupButton->hide(); } } void VOIPChatWidgetHolder::startVideoCapture() { videoCaptureToggleButton->setChecked(true); toggleVideoCapture(); } void VOIPChatWidgetHolder::toggleVideoCapture() { if (videoCaptureToggleButton->isChecked()) { //activate video input // videoWidget->show(); inputVideoDevice->start() ; videoCaptureToggleButton->setToolTip(tr("Shut camera off")); if (mChatWidget) mChatWidget->addChatMsg(true, tr("VoIP Status"), QDateTime::currentDateTime(), QDateTime::currentDateTime() , tr("You're now sending video..."), ChatWidget::MSGTYPE_SYSTEM); button_map::iterator it = buttonMapTakeVideo.begin(); while (it != buttonMapTakeVideo.end()) { RSButtonOnText *button = it.value(); delete button; it = buttonMapTakeVideo.erase(it); } } else { inputVideoDevice->stop() ; videoCaptureToggleButton->setToolTip(tr("Activate camera")); outputVideoDevice->showFrameOff(); videoWidget->hide(); if (mChatWidget) mChatWidget->addChatMsg(true, tr("VoIP Status"), QDateTime::currentDateTime(), QDateTime::currentDateTime() , tr("Video call stopped"), ChatWidget::MSGTYPE_SYSTEM); } } void VOIPChatWidgetHolder::addVideoData(const RsPeerId &peer_id, QByteArray* array) { if (!videoCaptureToggleButton->isChecked()) { if (mChatWidget) { QString buttonName = QString::fromUtf8(rsPeers->getPeerName(peer_id).c_str()); if (buttonName.isEmpty()) buttonName = "VoIP";//TODO maybe change all with GxsId button_map::iterator it = buttonMapTakeVideo.find(buttonName); if (it == buttonMapTakeVideo.end()){ mChatWidget->addChatMsg(true, tr("VoIP Status"), QDateTime::currentDateTime(), QDateTime::currentDateTime() , tr("%1 inviting you to start a video conversation. do you want Accept or Decline the invitation?").arg(buttonName), ChatWidget::MSGTYPE_SYSTEM); RSButtonOnText *button = mChatWidget->getNewButtonOnTextBrowser(tr("Accept Video Call")); button->setToolTip(tr("Activate camera")); button->setStyleSheet(QString("border: 1px solid #199909;") .append("font-size: 12pt; color: white;") .append("min-width: 128px; min-height: 24px;") .append("border-radius: 6px;") .append("background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0.67, " "stop: 0 #22c70d, stop: 1 #116a06);") ); button->updateImage(); connect(button,SIGNAL(clicked()),this,SLOT(startVideoCapture())); connect(button,SIGNAL(mouseEnter()),this,SLOT(botMouseEnter())); connect(button,SIGNAL(mouseLeave()),this,SLOT(botMouseLeave())); buttonMapTakeVideo.insert(buttonName, button); } } //TODO make a sound for the incoming call // soundManager->play(VOIP_SOUND_INCOMING_CALL); if (mVOIPNotify) mVOIPNotify->notifyReceivedVoipVideoCall(peer_id); } else { RsVOIPDataChunk chunk ; chunk.type = RsVOIPDataChunk::RS_VOIP_DATA_TYPE_VIDEO ; chunk.size = array->size() ; chunk.data = array->data() ; videoProcessor->receiveEncodedData(chunk) ; } } void VOIPChatWidgetHolder::botMouseEnter() { RSButtonOnText *source = qobject_cast(QObject::sender()); if (source){ source->setStyleSheet(QString("border: 1px solid #333333;") .append("font-size: 12pt; color: white;") .append("min-width: 128px; min-height: 24px;") .append("border-radius: 6px;") .append("background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0.67, " "stop: 0 #444444, stop: 1 #222222);") ); //source->setDown(true); } } void VOIPChatWidgetHolder::botMouseLeave() { RSButtonOnText *source = qobject_cast(QObject::sender()); if (source){ source->setStyleSheet(QString("border: 1px solid #199909;") .append("font-size: 12pt; color: white;") .append("min-width: 128px; min-height: 24px;") .append("border-radius: 6px;") .append("background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0.67, " "stop: 0 #22c70d, stop: 1 #116a06);") ); //source->setDown(false); } } void VOIPChatWidgetHolder::setAcceptedBandwidth(uint32_t bytes_per_sec) { videoProcessor->setMaximumFrameRate(bytes_per_sec) ; } void VOIPChatWidgetHolder::addAudioData(const RsPeerId &peer_id, QByteArray* array) { if (!audioCaptureToggleButton->isChecked()) { //launch an animation. Don't launch it if already animating if (!audioCaptureToggleButton->graphicsEffect() || (audioCaptureToggleButton->graphicsEffect()->inherits("QGraphicsOpacityEffect") && ((QGraphicsOpacityEffect*)audioCaptureToggleButton->graphicsEffect())->opacity() == 1) ) { QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(audioListenToggleButton); audioCaptureToggleButton->setGraphicsEffect(effect); QPropertyAnimation *anim = new QPropertyAnimation(effect, "opacity", effect); anim->setStartValue(1); anim->setKeyValueAt(0.5,0); anim->setEndValue(1); anim->setDuration(400); anim->start(); } if (mChatWidget) { QString buttonName = QString::fromUtf8(rsPeers->getPeerName(peer_id).c_str()); if (buttonName.isEmpty()) buttonName = "VoIP";//TODO maybe change all with GxsId button_map::iterator it = buttonMapTakeVideo.find(buttonName); if (it == buttonMapTakeVideo.end()){ mChatWidget->addChatMsg(true, tr("VoIP Status"), QDateTime::currentDateTime(), QDateTime::currentDateTime() , tr("%1 inviting you to start a audio conversation. do you want Accept or Decline the invitation?").arg(buttonName), ChatWidget::MSGTYPE_SYSTEM); RSButtonOnText *button = mChatWidget->getNewButtonOnTextBrowser(tr("Accept Call")); button->setToolTip(tr("Activate audio")); button->setStyleSheet(QString("border: 1px solid #199909;") .append("font-size: 12pt; color: white;") .append("min-width: 128px; min-height: 24px;") .append("border-radius: 6px;") .append("background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0.67, " "stop: 0 #22c70d, stop: 1 #116a06);") ); button->updateImage(); connect(button,SIGNAL(clicked()),this,SLOT(startAudioCapture())); connect(button,SIGNAL(mouseEnter()),this,SLOT(botMouseEnter())); connect(button,SIGNAL(mouseLeave()),this,SLOT(botMouseLeave())); buttonMapTakeVideo.insert(buttonName, button); } } audioCaptureToggleButton->setToolTip(tr("Answer")); //TODO make a sound for the incoming call // soundManager->play(VOIP_SOUND_INCOMING_CALL); if (mVOIPNotify) mVOIPNotify->notifyReceivedVoipAudioCall(peer_id); return; } if (!outputAudioDevice) { outputAudioDevice = AudioDeviceHelper::getDefaultOutputDevice(); } if (!outputAudioProcessor) { //start output audio device outputAudioProcessor = new QtSpeex::SpeexOutputProcessor(); if (inputAudioProcessor) { connect(outputAudioProcessor, SIGNAL(playingFrame(QByteArray*)), inputAudioProcessor, SLOT(addEchoFrame(QByteArray*))); } outputAudioProcessor->open(QIODevice::ReadOnly | QIODevice::Unbuffered); outputAudioDevice->start(outputAudioProcessor); } if (outputAudioDevice && outputAudioDevice->error() != QAudio::NoError) { std::cerr << "Restarting output device. Error before reset " << outputAudioDevice->error() << " buffer size : " << outputAudioDevice->bufferSize() << std::endl; outputAudioDevice->stop(); outputAudioDevice->reset(); if (outputAudioDevice->error() == QAudio::UnderrunError) outputAudioDevice->setBufferSize(20); outputAudioDevice->start(outputAudioProcessor); } outputAudioProcessor->putNetworkPacket(QString::fromStdString(peer_id.toStdString()), *array); //check the input device for errors if (inputAudioDevice && inputAudioDevice->error() != QAudio::NoError) { std::cerr << "Restarting input device. Error before reset " << inputAudioDevice->error() << std::endl; inputAudioDevice->stop(); inputAudioDevice->reset(); inputAudioDevice->start(inputAudioProcessor); } } void VOIPChatWidgetHolder::sendVideoData() { RsVOIPDataChunk chunk ; while(inputVideoDevice && inputVideoDevice->getNextEncodedPacket(chunk)) rsVOIP->sendVoipData(mChatWidget->getChatId().toPeerId(),chunk) ; } void VOIPChatWidgetHolder::sendAudioData() { while(inputAudioProcessor && inputAudioProcessor->hasPendingPackets()) { QByteArray qbarray = inputAudioProcessor->getNetworkPacket(); RsVOIPDataChunk chunk; chunk.size = qbarray.size(); chunk.data = (void*)qbarray.constData(); chunk.type = RsVOIPDataChunk::RS_VOIP_DATA_TYPE_AUDIO ; rsVOIP->sendVoipData(mChatWidget->getChatId().toPeerId(),chunk); } } void VOIPChatWidgetHolder::updateStatus(int status) { audioListenToggleButton->setEnabled(true); audioCaptureToggleButton->setEnabled(true); hangupButton->setEnabled(true); switch (status) { case RS_STATUS_OFFLINE: audioListenToggleButton->setEnabled(false); audioCaptureToggleButton->setEnabled(false); hangupButton->setEnabled(false); break; } }