Коммит b01c7fe7 создал по автору A. Roditelev's avatar A. Roditelev
Просмотр файлов

[example] Refactoring app.

- Added support for content scrolling
- Adapted layout for phablets
- Added the ability to disable the audio generator
- Added validation to the call start delay form
- Added the ability to reset all active calls
- Added microphone activity indication

Authors:
    - Alexander Roditelev
владелец a337f072
/*******************************************************************************
**
** Copyright (C) 2022 ru.auroraos
**
** This file is part of the Моё приложение для ОС Аврора project.
**
** Redistribution and use in source and binary forms,
** with or without modification, are permitted provided
** that the following conditions are met:
**
** * Redistributions of source code must retain the above copyright notice,
** this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright notice,
** this list of conditions and the following disclaimer
** in the documentation and/or other materials provided with the distribution.
** * Neither the name of the copyright holder nor the names of its contributors
** may be used to endorse or promote products derived from this software
** without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
** THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
** FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
** OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS;
** OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
** EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
*******************************************************************************/
import QtQuick 2.0
/**
* SPDX-FileCopyrightText: 2022-2024 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
import QtQuick 2.6
import Sailfish.Silica 1.0
import AudioCallExample 1.0
import QtMultimedia 5.6
import QtQuick.Layouts 1.1
import org.nemomobile.configuration 1.0
Page {
objectName: "mainPage"
......@@ -53,164 +23,256 @@ Page {
return id;
}
Column {
anchors.fill: parent
PageHeader {
title: qsTr("AudioCall")
extraContent.children: [
IconButton {
icon.source: "image://theme/icon-m-about"
anchors.verticalCenter: parent.verticalCenter
SilicaListView {
id : list
onClicked: pageStack.push(Qt.resolvedUrl("AboutPage.qml"))
}
]
anchors {
fill: parent
bottomMargin: Theme.paddingMedium
}
Label {
text: callManager.error
}
model: callManager.calls
header: Column {
id: content
Label {
text: "Calls: " + callManager.calls.length
}
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 2 * Theme.horizontalPageMargin
TextSwitch {
id: holdableSwitch
PageHeader {
anchors.horizontalCenter: parent.horizontalCenter
width: list.width
title: qsTr("AudioCall")
extraContent.children: [
IconButton {
icon.source: "image://theme/icon-m-about"
anchors.verticalCenter: parent.verticalCenter
text: "Make holdable calls"
automaticCheck: false
checked: callManager.holdable
onClicked: pageStack.push(Qt.resolvedUrl("AboutPage.qml"))
}
]
}
onClicked: callManager.holdable = !checked
}
Label {
width: parent.width
text: callManager.error
}
TextSwitch {
id: redialableSwitch
text: "Can redial"
automaticCheck: false
checked: callManager.redialable
Label {
width: parent.width
text: "Calls: " + callManager.calls.length
}
onClicked: callManager.redialable = !checked
}
TextSwitch {
id: holdableSwitch
TextSwitch {
id: dtmfSwitch
text: "Supports DTMF"
}
text: "Make holdable calls"
automaticCheck: false
checked: callManager.holdable
TextSwitch {
id: muteSwitch
text: "Mute microphone"
automaticCheck: false
checked: callManager.audioManager.mute
onClicked: callManager.audioManager.mute = !checked
}
onClicked: callManager.holdable = !checked
}
ComboBox {
id: audioDevice
TextSwitch {
id: redialableSwitch
text: "Can redial"
automaticCheck: false
checked: callManager.redialable
label: "Output audio device"
onClicked: callManager.redialable = !checked
}
TextSwitch {
id: dtmfSwitch
text: "Supports DTMF"
}
automaticSelection: false
menu: ContextMenu {
Repeater {
model: callManager.audioManager.devices
MenuItem {
text: modelData.name ? modelData.name : modelData.id
onClicked: callManager.audioManager.outputDeviceIndex = index
TextSwitch {
id: muteSwitch
text: "Mute microphone"
automaticCheck: false
checked: callManager.audioManager.mute
onClicked: callManager.audioManager.mute = !checked
}
TextSwitch {
text: "Active audio generator"
automaticCheck: false
checked: audioOutputGenerator.value
onClicked: audioOutputGenerator.value = !checked
}
ComboBox {
id: audioDevice
label: "Output audio device"
automaticSelection: false
menu: ContextMenu {
Repeater {
model: callManager.audioManager.devices
MenuItem {
text: modelData.name ? modelData.name : modelData.id
onClicked: callManager.audioManager.outputDeviceIndex = index
}
}
}
currentIndex: callManager.audioManager.outputDeviceIndex
}
currentIndex: callManager.audioManager.outputDeviceIndex
}
TextField {
id: delayValue
TextField {
id: delayValue
label: "Delay before incoming call, ms"
validator: IntValidator{ bottom: 0; top: 10000; }
text: "0"
}
property var _validator: IntValidator{ bottom: 0; top: 10000; }
Button {
text: "Create incoming call"
onClicked: incomingCallTimer.restart()
}
label: "Delay before incoming call, ms"
validator: text.length === 0 ? null : _validator
placeholderText: "0"
inputMethodHints: Qt.ImhDigitsOnly
EnterKey.enabled: text.length > 0
EnterKey.iconSource: "image://theme/icon-m-enter-accept"
EnterKey.onClicked: focus = false
Timer {
id: incomingCallTimer
interval: parseInt(delayValue.text)
running: false
repeat: false
onTriggered: {
var callee = nextCallee();
var call = callManager.reportIncomingCall({
"remoteHandle" : "_" + callee,
"remoteName" : "Incoming call " + callee,
"dtmf" : dtmfSwitch.checked
});
onTextChanged: {
if (text.length > 1) {
var origText = text
while(origText.charAt(0) === '0')
{
origText = origText.substring(1)
}
text = origText
}
}
}
}
Button {
id: outgoungButton
text: "Create outgoing call"
onClicked: {
var callee = nextCallee();
var call = callManager.initiateCall({
"remoteHandle" : "_" + callee,
"remoteName" : "Outgoing call " + callee,
"dtmf" : dtmfSwitch.checked
});
Timer {
id: incomingCallTimer
interval: parseInt(delayValue.text)
running: false
repeat: false
onTriggered: {
var callee = nextCallee();
var call = callManager.reportIncomingCall({
"remoteHandle" : "_" + callee,
"remoteName" : "Incoming call " + callee,
"dtmf" : dtmfSwitch.checked
});
}
}
}
SilicaListView {
anchors.top: outgoungButton.bottom
anchors.bottom: parent.bottom
model: callManager.calls
delegate: Item {
height: callControl.height
property var call: callManager.getCall(modelData)
Column {
id: callControl
Label {
text: call.remoteName + " - " + call.localName
}
GridLayout {
id: buttonLayout
readonly property int numberOfColumns: Screen.sizeCategory > Screen.Medium || !isPortrait
? 3
: 2
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
rowSpacing: Theme.paddingMedium
columnSpacing: rowSpacing
columns: numberOfColumns
Button {
Layout.fillWidth: true
text: "Incoming call"
onClicked: incomingCallTimer.restart()
}
Label {
text: "Status: " + call.status
Button {
Layout.fillWidth: true
text: "Outgoing call"
onClicked: {
var callee = nextCallee();
var call = callManager.initiateCall({
"remoteHandle" : "_" + callee,
"remoteName" : "Outgoing call " + callee,
"dtmf" : dtmfSwitch.checked
});
}
}
Row {
Button {
text: "Accept"
visible: call.status == Call.Ringing
onClicked: call.accept()
}
Button {
Layout.fillWidth: true
text: "Disconnect all"
onClicked: callManager.disconnectAll()
}
}
}
Button {
text: "Disconnect"
onClicked: call.disconnect()
}
delegate: Column {
property var call: callManager.getCall(modelData)
Rectangle {
anchors.bottom: parent.bottom
width: 20
color: "green"
height: parent.height * call.microphoneRms
}
anchors.horizontalCenter: parent.horizontalCenter
rightPadding: Theme.horizontalPageMargin
leftPadding: rightPadding
topPadding: Theme.paddingMedium
width: parent.width - 2 * Theme.horizontalPageMargin
spacing: Theme.paddingMedium
Label {
text: call.dtmfString
visible: call.supportsDtmf
}
}
Label {
width: parent.width - 2 * Theme.horizontalPageMargin
text: call.remoteName + " - " + call.localName
elide: Text.ElideRight
maximumLineCount: 1
}
Row {
spacing: Theme.paddingMedium
width: parent.width - 2 * Theme.horizontalPageMargin
Label {
text: "Status: " + call.status
}
Icon {
source: microphoneRmsLevel.isMuted ? "image://theme/icon-cover-mute"
: "image://theme/icon-cover-mic"
color: microphoneRmsLevel.isMuted ? Theme.highlightColor
: Theme.primaryColor
}
}
Row {
spacing: Theme.paddingMedium
Button {
text: "Accept"
visible: call.status == Call.Ringing
onClicked: call.accept()
}
Button {
text: "Disconnect"
onClicked: call.disconnect()
}
Rectangle {
id: microphoneRmsLevel
readonly property bool isMuted: callManager.audioManager.mute
anchors.bottom: parent.bottom
width: 20
color: !isMuted ? "green" : "red"
height: parent.height * (!isMuted ? call.microphoneRms : 1)
}
Label {
text: call.dtmfString
visible: call.supportsDtmf
}
}
}
VerticalScrollDecorator {}
}
ConfigurationValue {
id: audioOutputGenerator
key: callManager.audioGeneratorConfigPath
}
Component.onCompleted: if (audioOutputGenerator.value == null) audioOutputGenerator.value = true
}
......@@ -7,6 +7,7 @@ URL: https://auroraos.ru
Source0: %{name}-%{version}.tar.zst
Requires: sailfishsilica-qt5 >= 0.10.9
Requires: nemo-qml-plugin-configuration-qt5
BuildRequires: pkgconfig(auroraapp)
BuildRequires: pkgconfig(Qt5Core)
BuildRequires: pkgconfig(Qt5Qml)
......@@ -14,6 +15,7 @@ BuildRequires: pkgconfig(Qt5Quick)
BuildRequires: pkgconfig(Qt5DBus)
BuildRequires: pkgconfig(Qt5Multimedia)
BuildRequires: pkgconfig(callservice)
BuildRequires: pkgconfig(mlite5)
%description
An example of creating VoIP application using Aurora Call API.
......
......@@ -44,7 +44,10 @@ CONFIG += \
system(qdbusxml2cpp dbus/$${TARGET}.xml -a src/urihandleradaptor -c UriHandlerAdaptor)
PKGCONFIG += callservice
PKGCONFIG += callservice mlite5
DEFINES += \
AUDIO_GENERATOR_CONFIG_PATH=\\\"\/apps/$${TARGET}/enableAudioOutputGenerator\\\" \
SOURCES += \
src/audiocallmanager.cpp \
......
/**
* SPDX-FileCopyrightText: 2023-2024 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <QDebug>
#include "audiocall.h"
......@@ -11,9 +16,16 @@ AudioCall::AudioCall(
const QVariantMap &parameters,
QObject *parent)
: Call(manager, id, parameters, parent)
, m_audioOutputGeneratorConfig(AUDIO_GENERATOR_CONFIG_PATH)
{
connect(&m_input, &AudioInput::rmsChanged,
this, &AudioCall::microphoneRmsChanged);
connect(&m_audioOutputGeneratorConfig,
&MGConfItem::valueChanged,
this,
&AudioCall::audioOutputGeneratorsStatusChanged);
m_audioOutputGeneratorConfigValue = m_audioOutputGeneratorConfig.value().value<bool>();
#ifdef STANDALONE_DTMF
auto dtmf = parameters.find("dtmf");
......@@ -120,10 +132,22 @@ void AudioCall::onCallStatusChanged(Call::Status)
switch (status) {
case Call::Dialing:
case Call::Active:
startAudio();
if (m_audioOutputGeneratorConfigValue)
startAudio();
break;
default:
stopAudio();
break;
}
}
void AudioCall::audioOutputGeneratorsStatusChanged()
{
m_audioOutputGeneratorConfigValue = m_audioOutputGeneratorConfig.value().value<bool>();
if (m_audioOutputGeneratorConfigValue) {
startAudio();
} else {
stopAudio();
}
}
/**
* SPDX-FileCopyrightText: 2023-2024 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include <QObject>
#include <QQuickItem>
#include <QVariantMap>
#include <QTimer>
#include <MGConfItem>
#include <audiocontext.h>
#include <call.h>
......@@ -66,6 +72,7 @@ private slots:
#endif
void onCallStatusChanged(Call::Status);
void audioOutputGeneratorsStatusChanged();
private:
QTimer m_answerTimer;
......@@ -73,6 +80,8 @@ private:
AudioOutput m_output;
bool m_supportsDtmf;
QString m_dtmfString;
MGConfItem m_audioOutputGeneratorConfig;
bool m_audioOutputGeneratorConfigValue;
};
QML_DECLARE_TYPE(AudioCall)
......
/**
* SPDX-FileCopyrightText: 2024 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <callfactoryinterface.h>
#include "audiocallmanager.h"
......@@ -78,6 +83,13 @@ Call *AudioCallManager::reportIncomingCall(const QVariantMap &props)
return newCall(properties);
}
void AudioCallManager::disconnectAll()
{
for (const auto &call : calls()) {
removeCall(call);
}
}
void AudioCallManager::openUri(const QString &uri)
{
qInfo() << uri;
......@@ -133,6 +145,11 @@ void AudioCallManager::setRedialable(bool on)
}
}
QString AudioCallManager::audioGeneratorConfigPath() const
{
return QString(AUDIO_GENERATOR_CONFIG_PATH);
}
AudioCall *AudioCallManager::getAudioCall(const QString &callId) const
{
return qobject_cast<AudioCall*>(getCall(callId));
......
/**
* SPDX-FileCopyrightText: 2024 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include <QObject>
......@@ -22,6 +27,7 @@ class AudioCallManager : public CallManager
Q_PROPERTY(bool holdable READ holdable WRITE setHoldable NOTIFY holdableChanged)
Q_PROPERTY(bool redialable READ redialable WRITE setRedialable NOTIFY redialableChanged)
Q_PROPERTY(AudioManager* audioManager READ audioManager CONSTANT)
Q_PROPERTY(QString audioGeneratorConfigPath READ audioGeneratorConfigPath CONSTANT)
public:
explicit AudioCallManager(const QString &accountId, QObject *parent = nullptr);
......@@ -30,6 +36,7 @@ public:
Q_INVOKABLE AudioCall *getAudioCall(const QString &callId) const;
Q_INVOKABLE Call *initiateCall(const QVariantMap &properties);
Q_INVOKABLE Call *reportIncomingCall(const QVariantMap &properties);
Q_INVOKABLE void disconnectAll();
bool holdable() const;
void setHoldable(bool on);
......@@ -42,6 +49,8 @@ public:
return &m_audioManager;
}
QString audioGeneratorConfigPath() const;
public slots:
void openUri(const QString &uri);
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать