Commit a291e8b4 authored by Ghislain MARY's avatar Ghislain MARY

Add call statistics view in the call window.

parent d3949d34
......@@ -175,6 +175,88 @@
<translation>Realm</translation>
</message>
</context>
<context>
<name>CallModel</name>
<message>
<source>callStatsCodec</source>
<translation>Codec</translation>
</message>
<message>
<source>callStatsUploadBandwidth</source>
<translation>Upload bandwidth</translation>
</message>
<message>
<source>callStatsDownloadBandwidth</source>
<translation>Download bandwidth</translation>
</message>
<message>
<source>callStatsIceState</source>
<translation>ICE state</translation>
</message>
<message>
<source>callStatsIpFamily</source>
<translation>IP family</translation>
</message>
<message>
<source>callStatsSenderLossRate</source>
<translation>Sender loss rate</translation>
</message>
<message>
<source>callStatsReceiverLossRate</source>
<translation>Receiver loss rate</translation>
</message>
<message>
<source>callStatsJitterBuffer</source>
<translation>Jitter buffer</translation>
</message>
<message>
<source>callStatsSentVideoDefinition</source>
<translation>Sent video definition</translation>
</message>
<message>
<source>callStatsReceivedVideoDefinition</source>
<translation>Received video definition</translation>
</message>
<message>
<source>iceStateNotActivated</source>
<translation>Not activated</translation>
</message>
<message>
<source>iceStateFailed</source>
<translation>Failed</translation>
</message>
<message>
<source>iceStateInProgress</source>
<translation>In progress</translation>
</message>
<message>
<source>iceStateReflexiveConnection</source>
<translation>Reflexive connection</translation>
</message>
<message>
<source>iceStateHostConnection</source>
<translation>Host connection</translation>
</message>
<message>
<source>iceStateRelayConnection</source>
<translation>Relay connection</translation>
</message>
<message>
<source>iceStateInvalid</source>
<translation>Invalid</translation>
</message>
</context>
<context>
<name>CallStatistics</name>
<message>
<source>audioStatsLabel</source>
<translation>Audio</translation>
</message>
<message>
<source>videoStatsLabel</source>
<translation>Video</translation>
</message>
</context>
<context>
<name>Calls</name>
<message>
......
......@@ -175,6 +175,88 @@
<translation>Realm</translation>
</message>
</context>
<context>
<name>CallModel</name>
<message>
<source>callStatsCodec</source>
<translation>Codec</translation>
</message>
<message>
<source>callStatsUploadBandwidth</source>
<translation>Bande passante d&apos;envoi</translation>
</message>
<message>
<source>callStatsDownloadBandwidth</source>
<translation>Bande passante de réception</translation>
</message>
<message>
<source>callStatsIceState</source>
<translation>État ICE</translation>
</message>
<message>
<source>callStatsIpFamily</source>
<translation>Famille IP</translation>
</message>
<message>
<source>callStatsSenderLossRate</source>
<translation>Taux de perte en envoi</translation>
</message>
<message>
<source>callStatsReceiverLossRate</source>
<translation>Taux de perte en réception</translation>
</message>
<message>
<source>callStatsJitterBuffer</source>
<translation>Tampon de gigue</translation>
</message>
<message>
<source>callStatsSentVideoDefinition</source>
<translation>Définition vidéo envoyée</translation>
</message>
<message>
<source>callStatsReceivedVideoDefinition</source>
<translation>Définition vidéo reçue</translation>
</message>
<message>
<source>iceStateNotActivated</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>iceStateFailed</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>iceStateInProgress</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>iceStateReflexiveConnection</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>iceStateHostConnection</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>iceStateRelayConnection</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>iceStateInvalid</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>CallStatistics</name>
<message>
<source>audioStatsLabel</source>
<translation>Audio</translation>
</message>
<message>
<source>videoStatsLabel</source>
<translation>Vidéo</translation>
</message>
</context>
<context>
<name>Calls</name>
<message>
......
......@@ -280,6 +280,7 @@
<file>ui/modules/Linphone/Calls/CallControls.qml</file>
<file>ui/modules/Linphone/Calls/Calls.js</file>
<file>ui/modules/Linphone/Calls/Calls.qml</file>
<file>ui/modules/Linphone/Calls/CallStatistics.qml</file>
<file>ui/modules/Linphone/Chat/Chat.js</file>
<file>ui/modules/Linphone/Chat/Chat.qml</file>
<file>ui/modules/Linphone/Chat/Event.qml</file>
......@@ -307,6 +308,7 @@
<file>ui/modules/Linphone/Styles/Blocks/CardBlockStyle.qml</file>
<file>ui/modules/Linphone/Styles/Blocks/RequestBlockStyle.qml</file>
<file>ui/modules/Linphone/Styles/Calls/CallControlsStyle.qml</file>
<file>ui/modules/Linphone/Styles/Calls/CallStatisticsStyle.qml</file>
<file>ui/modules/Linphone/Styles/Calls/CallsStyle.qml</file>
<file>ui/modules/Linphone/Styles/Chat/ChatStyle.qml</file>
<file>ui/modules/Linphone/Styles/Codecs/CodecsViewerStyle.qml</file>
......
......@@ -39,6 +39,7 @@ using namespace std;
CallModel::CallModel (shared_ptr<linphone::Call> linphoneCall) {
Q_ASSERT(linphoneCall != nullptr);
mLinphoneCall = linphoneCall;
mLinphoneCall->setData("call-model", *this);
// Deal with auto-answer.
{
......@@ -118,6 +119,21 @@ void CallModel::setRecordFile (shared_ptr<linphone::CallParams> &callParams) {
);
}
void CallModel::updateStats (const linphone::CallStats &stats) {
switch (stats.getType()) {
case linphone::StreamTypeAudio:
updateStats(stats, mAudioStats);
break;
case linphone::StreamTypeVideo:
updateStats(stats, mVideoStats);
break;
default:
break;
}
emit statsUpdated();
}
// -----------------------------------------------------------------------------
void CallModel::accept () {
......@@ -388,3 +404,97 @@ void CallModel::sendDtmf (const QString &dtmf) {
qInfo() << QStringLiteral("Send dtmf: `%1`.").arg(dtmf);
mLinphoneCall->sendDtmf(dtmf.constData()[0].toLatin1());
}
// -----------------------------------------------------------------------------
QVariantList CallModel::getAudioStats () const {
return mAudioStats;
}
QVariantList CallModel::getVideoStats () const {
return mVideoStats;
}
static QVariantMap createStat (const QString &key, const QString &value) {
QVariantMap m;
m["key"] = key;
m["value"] = value;
return m;
}
void CallModel::updateStats (const linphone::CallStats &callStats, QVariantList &stats) {
QString family;
shared_ptr<const linphone::CallParams> params = mLinphoneCall->getCurrentParams();
shared_ptr<const linphone::PayloadType> payloadType;
switch (callStats.getType()) {
case linphone::StreamTypeAudio:
payloadType = params->getUsedAudioPayloadType();
break;
case linphone::StreamTypeVideo:
payloadType = params->getUsedVideoPayloadType();
break;
default:
return;
}
switch (callStats.getIpFamilyOfRemote()) {
case linphone::AddressFamilyInet:
family = "IPv4";
break;
case linphone::AddressFamilyInet6:
family = "IPv6";
break;
default:
family = "Unknown";
break;
}
stats.clear();
stats << createStat(tr("callStatsCodec"), QString("%1 / %2kHz").arg(Utils::linphoneStringToQString(payloadType->getMimeType())).arg(payloadType->getClockRate() / 1000));
stats << createStat(tr("callStatsUploadBandwidth"), QString("%1 kbits/s").arg(int(callStats.getUploadBandwidth())));
stats << createStat(tr("callStatsDownloadBandwidth"), QString("%1 kbits/s").arg(int(callStats.getDownloadBandwidth())));
stats << createStat(tr("callStatsIceState"), iceStateToString(callStats.getIceState()));
stats << createStat(tr("callStatsIpFamily"), family);
stats << createStat(tr("callStatsSenderLossRate"), QString("%1 %").arg(callStats.getSenderLossRate()));
stats << createStat(tr("callStatsReceiverLossRate"), QString("%1 %").arg(callStats.getReceiverLossRate()));
switch (callStats.getType()) {
case linphone::StreamTypeAudio:
stats << createStat(tr("callStatsJitterBuffer"), QString("%1 ms").arg(callStats.getJitterBufferSizeMs()));
break;
case linphone::StreamTypeVideo:
{
QString sentVideoDefinitionName = Utils::linphoneStringToQString(params->getSentVideoDefinition()->getName());
QString sentVideoDefinition = QString("%1x%2").arg(params->getSentVideoDefinition()->getWidth()).arg(params->getSentVideoDefinition()->getHeight());
stats << createStat(tr("callStatsSentVideoDefinition"),
(sentVideoDefinition == sentVideoDefinitionName) ? sentVideoDefinition : QString("%1 (%2)").arg(sentVideoDefinition).arg(sentVideoDefinitionName));
QString receivedVideoDefinitionName = Utils::linphoneStringToQString(params->getReceivedVideoDefinition()->getName());
QString receivedVideoDefinition = QString("%1x%2").arg(params->getReceivedVideoDefinition()->getWidth()).arg(params->getReceivedVideoDefinition()->getHeight());
stats << createStat(tr("callStatsReceivedVideoDefinition"),
(receivedVideoDefinition == receivedVideoDefinitionName) ? receivedVideoDefinition : QString("%1 (%2)").arg(receivedVideoDefinition).arg(receivedVideoDefinitionName));
}
break;
default:
break;
}
}
QString CallModel::iceStateToString (linphone::IceState state) const {
switch (state) {
case linphone::IceStateNotActivated:
return tr("iceStateNotActivated");
case linphone::IceStateFailed:
return tr("iceStateFailed");
case linphone::IceStateInProgress:
return tr("iceStateInProgress");
case linphone::IceStateReflexiveConnection:
return tr("iceStateReflexiveConnection");
case linphone::IceStateHostConnection:
return tr("iceStateHostConnection");
case linphone::IceStateRelayConnection:
return tr("iceStateRelayConnection");
default:
return tr("iceStateInvalid");
}
}
......@@ -49,6 +49,9 @@ class CallModel : public QObject {
Q_PROPERTY(bool recording READ getRecording NOTIFY recordingChanged);
Q_PROPERTY(QVariantList audioStats READ getAudioStats NOTIFY statsUpdated);
Q_PROPERTY(QVariantList videoStats READ getVideoStats NOTIFY statsUpdated);
public:
enum CallStatus {
CallStatusConnected,
......@@ -69,6 +72,7 @@ public:
}
static void setRecordFile (std::shared_ptr<linphone::CallParams> &callParams);
void updateStats (const linphone::CallStats &stats);
Q_INVOKABLE void accept ();
Q_INVOKABLE void acceptWithVideo ();
......@@ -90,6 +94,7 @@ signals:
void microMutedChanged (bool status);
void videoRequested ();
void recordingChanged (bool status);
void statsUpdated ();
private:
void stopAutoAnswerTimer () const;
......@@ -119,9 +124,16 @@ private:
bool getRecording () const;
QVariantList getAudioStats () const;
QVariantList getVideoStats () const;
void updateStats (const linphone::CallStats &call_stats, QVariantList &stats);
QString iceStateToString (linphone::IceState state) const;
bool mPausedByRemote = false;
bool mPausedByUser = false;
bool mRecording = false;
QVariantList mAudioStats;
QVariantList mVideoStats;
std::shared_ptr<linphone::Call> mLinphoneCall;
};
......
......@@ -52,6 +52,14 @@ void CoreHandlers::onCallStateChanged (
App::getInstance()->getNotifier()->notifyReceivedCall(call);
}
void CoreHandlers::onCallStatsUpdated (
const std::shared_ptr<linphone::Core> &,
const std::shared_ptr<linphone::Call> &call,
const linphone::CallStats &stats
) {
call->getData<CallModel>("call-model").updateStats(stats);
}
void CoreHandlers::onMessageReceived (
const shared_ptr<linphone::Core> &,
const shared_ptr<linphone::ChatRoom> &,
......
......@@ -54,6 +54,12 @@ private:
const std::string &message
) override;
void onCallStatsUpdated (
const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Call> &call,
const linphone::CallStats &stats
) override;
void onMessageReceived (
const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &room,
......
......@@ -68,6 +68,7 @@ Collapse 1.0 Misc/Collapse.qml
ForceScrollBar 1.0 Misc/ForceScrollBar.qml
Paned 1.0 Misc/Paned.qml
AbstractDropDownMenu 1.0 Popup/AbstractDropDownMenu.qml
DesktopPopup 1.0 Popup/DesktopPopup.qml
DropDownDynamicMenu 1.0 Popup/DropDownDynamicMenu.qml
DropDownMenu 1.0 Popup/DropDownMenu.qml
......
import QtQuick 2.7
import QtQuick.Layouts 1.3
import Common 1.0
import Linphone 1.0
import Linphone.Styles 1.0
// =============================================================================
AbstractDropDownMenu {
id: callStatistics
property var call
// ---------------------------------------------------------------------------
function _computeHeight () {
return callStatistics.height
}
// ---------------------------------------------------------------------------
Component {
id: line
RowLayout {
spacing: CallStatisticsStyle.spacing
// ---------------------------------------------------------------------------
Text {
Layout.preferredWidth: CallStatisticsStyle.key.width
color: CallStatisticsStyle.key.color
elide: Text.ElideRight
font {
pointSize: CallStatisticsStyle.key.fontSize
bold: true
}
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
text: modelData.key
}
// ---------------------------------------------------------------------------
Text {
Layout.fillWidth: true
color: CallStatisticsStyle.value.color
elide: Text.ElideRight
font.pointSize: CallStatisticsStyle.value.fontSize
text: modelData.value
}
}
}
// ---------------------------------------------------------------------------
Component {
id: media
Column {
width: parent.width
Text {
width: parent.width
color: CallStatisticsStyle.title.color
font {
bold: true
pointSize: CallStatisticsStyle.title.fontSize
}
horizontalAlignment: Text.AlignHCenter
text: $label
}
Repeater {
model: $data
delegate: line
}
}
}
// ---------------------------------------------------------------------------
Rectangle {
anchors.fill: parent
color: CallStatisticsStyle.color
Row {
anchors {
fill: parent
leftMargin: CallStatisticsStyle.leftMargin
rightMargin: CallStatisticsStyle.rightMargin
}
Loader {
property string $label: qsTr("audioStatsLabel")
property var $data: callStatistics.call.audioStats
sourceComponent: media
width: parent.width / 2
}
Loader {
property string $label: qsTr("videoStatsLabel")
property var $data: callStatistics.call.videoStats
sourceComponent: media
width: parent.width / 2
}
}
}
}
pragma Singleton
import QtQuick 2.7
import Common 1.0
// =============================================================================
QtObject {
property color color: Colors.e
property int height: 60
property int leftMargin: 12
property int rightMargin: 12
property int width: 240
property QtObject title: QtObject {
property color color: Colors.l
property int fontSize: 16
}
property QtObject key: QtObject {
property int width: 200
property color color: Colors.l
property int fontSize: 10
}
property QtObject value: QtObject {
property color color: Colors.l
property int fontSize: 10
}
}
......@@ -13,6 +13,7 @@ singleton ChatStyle 1.0 Chat/ChatStyle.qml
singleton CallsStyle 1.0 Calls/CallsStyle.qml
singleton CallControlsStyle 1.0 Calls/CallControlsStyle.qml
singleton CallStatisticsStyle 1.0 Calls/CallStatisticsStyle.qml
singleton CodecsViewerStyle 1.0 Codecs/CodecsViewerStyle.qml
......
......@@ -12,6 +12,7 @@ CardBlock 1.0 Blocks/CardBlock.qml
RequestBlock 1.0 Blocks/RequestBlock.qml
Calls 1.0 Calls/Calls.qml
CallStatistics 1.0 Calls/CallStatistics.qml
Chat 1.0 Chat/Chat.qml
......
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import Common 1.0
......@@ -61,13 +62,16 @@ Rectangle {
Layout.rightMargin: CallStyle.header.rightMargin
Layout.preferredHeight: CallStyle.header.contactDescription.height
Icon {
ActionButton {
id: callQuality
anchors.left: parent.left
icon: 'call_quality_0'
iconSize: CallStyle.header.iconSize
visible: call.status !== CallModel.CallStatusEnded
useStates: false
onClicked: callStatistics.showMenu()
// See: http://www.linphone.org/docs/liblinphone/group__call__misc.html#ga62c7d3d08531b0cc634b797e273a0a73
Timer {
......@@ -78,6 +82,16 @@ Rectangle {
onTriggered: Logic.updateCallQualityIcon()
}
CallStatistics {
id: callStatistics
relativeTo: callQuality
relativeY: info.height + elapsedTime.height * 2
call: incall.call
width: container.width
height: container.height
launcher: callQuality
}
}
ContactDescription {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment