Fix vocal messages:

- Keep message preview between chat rooms.
- Hide preview after sending message.
- Keep recording the same file while changing chat room.
- Send message while recording.
Fix forward on contact selection.
This commit is contained in:
Julien Wadel 2021-11-17 16:35:43 +01:00
parent ed99447e05
commit 33c92edfcd
7 changed files with 214 additions and 215 deletions

View file

@ -663,6 +663,7 @@ void ChatRoomModel::sendMessage (const QString &message) {
_message= mChatRoom->createEmptyMessage();
auto recorder = CoreManager::getInstance()->getRecorderManager();
if(recorder->haveVocalRecorder()) {
recorder->getVocalRecorder()->stop();
auto content = recorder->getVocalRecorder()->getRecorder()->createContent();
if(content)
_message->addContent(content);
@ -672,6 +673,8 @@ void ChatRoomModel::sendMessage (const QString &message) {
_message->send();
emit messageSent(_message);
setReply(nullptr);
if(recorder->haveVocalRecorder())
recorder->clearVocalRecorder();
}
void ChatRoomModel::sendFileMessage (const QString &path) {

View file

@ -32,200 +32,200 @@
using namespace std;
namespace {
int ForceCloseTimerInterval = 20;
int ForceCloseTimerInterval = 20;
}
class SoundPlayer::Handlers : public linphone::PlayerListener {
public:
Handlers (SoundPlayer *soundPlayer) {
mSoundPlayer = soundPlayer;
}
Handlers (SoundPlayer *soundPlayer) {
mSoundPlayer = soundPlayer;
}
private:
void onEofReached (const shared_ptr<linphone::Player> &) override {
QMutex &mutex = mSoundPlayer->mForceCloseMutex;
// Workaround.
// This callback is called in a standard thread of mediastreamer, not a QThread.
// Signals, connect functions, timers... are unavailable.
mutex.lock();
mSoundPlayer->mForceClose = true;
mutex.unlock();
}
SoundPlayer *mSoundPlayer;
void onEofReached (const shared_ptr<linphone::Player> &) override {
QMutex &mutex = mSoundPlayer->mForceCloseMutex;
// Workaround.
// This callback is called in a standard thread of mediastreamer, not a QThread.
// Signals, connect functions, timers... are unavailable.
mutex.lock();
mSoundPlayer->mForceClose = true;
mutex.unlock();
}
SoundPlayer *mSoundPlayer;
};
// -----------------------------------------------------------------------------
SoundPlayer::SoundPlayer (QObject *parent) : QObject(parent) {
CoreManager *coreManager = CoreManager::getInstance();
SettingsModel *settingsModel = coreManager->getSettingsModel();
mForceCloseTimer = new QTimer(this);
mForceCloseTimer->setInterval(ForceCloseTimerInterval);
QObject::connect(mForceCloseTimer, &QTimer::timeout, this, &SoundPlayer::handleEof);
mHandlers = make_shared<SoundPlayer::Handlers>(this);
// Connection to rebuilding player when changing ringer selection. This player is only for Ringer.
QObject::connect(settingsModel, &SettingsModel::ringerDeviceChanged, this, [this] {
rebuildInternalPlayer();
});
buildInternalPlayer();
CoreManager *coreManager = CoreManager::getInstance();
SettingsModel *settingsModel = coreManager->getSettingsModel();
mForceCloseTimer = new QTimer(this);
mForceCloseTimer->setInterval(ForceCloseTimerInterval);
QObject::connect(mForceCloseTimer, &QTimer::timeout, this, &SoundPlayer::handleEof);
mHandlers = make_shared<SoundPlayer::Handlers>(this);
// Connection to rebuilding player when changing ringer selection. This player is only for Ringer.
QObject::connect(settingsModel, &SettingsModel::ringerDeviceChanged, this, [this] {
rebuildInternalPlayer();
});
buildInternalPlayer();
}
SoundPlayer::~SoundPlayer () {
mForceCloseTimer->stop();
if( mInternalPlayer)
mInternalPlayer->close();
mForceCloseTimer->stop();
if( mInternalPlayer)
mInternalPlayer->close();
}
// -----------------------------------------------------------------------------
void SoundPlayer::pause () {
if (mPlaybackState == SoundPlayer::PausedState)
return;
if (mInternalPlayer->pause()) {
setError(QStringLiteral("Unable to pause: `%1`").arg(mSource));
return;
}
mForceCloseTimer->stop();
mPlaybackState = SoundPlayer::PausedState;
emit paused();
emit playbackStateChanged(mPlaybackState);
if (mPlaybackState == SoundPlayer::PausedState)
return;
if (mInternalPlayer->pause()) {
setError(QStringLiteral("Unable to pause: `%1`").arg(mSource));
return;
}
mForceCloseTimer->stop();
mPlaybackState = SoundPlayer::PausedState;
emit paused();
emit playbackStateChanged(mPlaybackState);
}
void SoundPlayer::play () {
if (mPlaybackState == SoundPlayer::PlayingState)
return;
if (
(mPlaybackState == SoundPlayer::StoppedState || mPlaybackState == SoundPlayer::ErrorState) &&
mInternalPlayer->open(Utils::appStringToCoreString(mSource))
) {
qWarning() << QStringLiteral("Unable to open: `%1`").arg(mSource);
return;
}
if (mInternalPlayer->start()
) {
setError(QStringLiteral("Unable to play: `%1`").arg(mSource));
return;
}
mForceCloseTimer->start();
mPlaybackState = SoundPlayer::PlayingState;
emit playing();
emit playbackStateChanged(mPlaybackState);
if (mPlaybackState == SoundPlayer::PlayingState || mSource == "")
return;
if (
(mPlaybackState == SoundPlayer::StoppedState || mPlaybackState == SoundPlayer::ErrorState) &&
mInternalPlayer->open(Utils::appStringToCoreString(mSource))
) {
qWarning() << QStringLiteral("Unable to open: `%1`").arg(mSource);
return;
}
if (mInternalPlayer->start()
) {
setError(QStringLiteral("Unable to play: `%1`").arg(mSource));
return;
}
mForceCloseTimer->start();
mPlaybackState = SoundPlayer::PlayingState;
emit playing();
emit playbackStateChanged(mPlaybackState);
}
void SoundPlayer::stop () {
stop(false);
stop(false);
}
// -----------------------------------------------------------------------------
void SoundPlayer::seek (int offset) {
mInternalPlayer->seek(offset);
mInternalPlayer->seek(offset);
}
// -----------------------------------------------------------------------------
int SoundPlayer::getPosition () const {
return mInternalPlayer->getCurrentPosition();
return mInternalPlayer->getCurrentPosition();
}
// -----------------------------------------------------------------------------
void SoundPlayer::buildInternalPlayer () {
CoreManager *coreManager = CoreManager::getInstance();
SettingsModel *settingsModel = coreManager->getSettingsModel();
mInternalPlayer = coreManager->getCore()->createLocalPlayer(
Utils::appStringToCoreString(settingsModel->getRingerDevice()), "", nullptr
);
if(mInternalPlayer)
mInternalPlayer->addListener(mHandlers);
CoreManager *coreManager = CoreManager::getInstance();
SettingsModel *settingsModel = coreManager->getSettingsModel();
mInternalPlayer = coreManager->getCore()->createLocalPlayer(
Utils::appStringToCoreString(settingsModel->getRingerDevice()), "", nullptr
);
if(mInternalPlayer)
mInternalPlayer->addListener(mHandlers);
}
void SoundPlayer::rebuildInternalPlayer () {
stop(true);
buildInternalPlayer();
stop(true);
buildInternalPlayer();
}
void SoundPlayer::stop (bool force) {
if (mPlaybackState == SoundPlayer::StoppedState && !force)
return;
mForceCloseTimer->stop();
mPlaybackState = SoundPlayer::StoppedState;
if(mInternalPlayer)
mInternalPlayer->close();
emit stopped();
emit playbackStateChanged(mPlaybackState);
if (mPlaybackState == SoundPlayer::StoppedState && !force)
return;
mForceCloseTimer->stop();
mPlaybackState = SoundPlayer::StoppedState;
if(mInternalPlayer)
mInternalPlayer->close();
emit stopped();
emit playbackStateChanged(mPlaybackState);
}
// -----------------------------------------------------------------------------
void SoundPlayer::handleEof () {
mForceCloseMutex.lock();
if (mForceClose) {
mForceClose = false;
stop();
}
mForceCloseMutex.unlock();
mForceCloseMutex.lock();
if (mForceClose) {
mForceClose = false;
stop();
}
mForceCloseMutex.unlock();
}
// -----------------------------------------------------------------------------
void SoundPlayer::setError (const QString &message) {
qWarning() << message;
mInternalPlayer->close();
if (mPlaybackState != SoundPlayer::ErrorState) {
mPlaybackState = SoundPlayer::ErrorState;
emit playbackStateChanged(mPlaybackState);
}
qWarning() << message;
mInternalPlayer->close();
if (mPlaybackState != SoundPlayer::ErrorState) {
mPlaybackState = SoundPlayer::ErrorState;
emit playbackStateChanged(mPlaybackState);
}
}
// -----------------------------------------------------------------------------
QString SoundPlayer::getSource () const {
return mSource;
return mSource;
}
void SoundPlayer::setSource (const QString &source) {
if (source == mSource)
return;
stop();
mSource = source;
emit sourceChanged(source);
if (source == mSource)
return;
stop();
mSource = source;
emit sourceChanged(source);
}
// -----------------------------------------------------------------------------
SoundPlayer::PlaybackState SoundPlayer::getPlaybackState () const {
return mPlaybackState;
return mPlaybackState;
}
void SoundPlayer::setPlaybackState (PlaybackState playbackState) {
switch (playbackState) {
case PlayingState:
play();
break;
case PausedState:
pause();
break;
case StoppedState:
stop();
break;
case ErrorState:
break;
}
switch (playbackState) {
case PlayingState:
play();
break;
case PausedState:
pause();
break;
case StoppedState:
stop();
break;
case ErrorState:
break;
}
}
// -----------------------------------------------------------------------------
int SoundPlayer::getDuration () const {
return mInternalPlayer->getDuration();
return mInternalPlayer->getDuration();
}

View file

@ -31,75 +31,75 @@
class QTimer;
namespace linphone {
class Player;
class Player;
}
class SoundPlayer : public QObject {
class Handlers;
Q_OBJECT
Q_PROPERTY(QString source READ getSource WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(PlaybackState playbackState READ getPlaybackState WRITE setPlaybackState NOTIFY playbackStateChanged)
Q_PROPERTY(int duration READ getDuration NOTIFY sourceChanged)
class Handlers;
Q_OBJECT
Q_PROPERTY(QString source READ getSource WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(PlaybackState playbackState READ getPlaybackState WRITE setPlaybackState NOTIFY playbackStateChanged)
Q_PROPERTY(int duration READ getDuration NOTIFY sourceChanged)
public:
enum PlaybackState {
PlayingState,
PausedState,
StoppedState,
ErrorState
};
Q_ENUM(PlaybackState);
SoundPlayer (QObject *parent = Q_NULLPTR);
~SoundPlayer ();
Q_INVOKABLE void pause ();
Q_INVOKABLE void play ();
Q_INVOKABLE void stop ();
Q_INVOKABLE void seek (int offset);
Q_INVOKABLE int getPosition () const;
enum PlaybackState {
PlayingState,
PausedState,
StoppedState,
ErrorState
};
Q_ENUM(PlaybackState);
SoundPlayer (QObject *parent = Q_NULLPTR);
~SoundPlayer ();
Q_INVOKABLE void pause ();
Q_INVOKABLE void play ();
Q_INVOKABLE void stop ();
Q_INVOKABLE void seek (int offset);
Q_INVOKABLE int getPosition () const;
signals:
void sourceChanged (const QString &source);
void paused ();
void playing ();
void stopped ();
void playbackStateChanged (PlaybackState playbackState);
void sourceChanged (const QString &source);
void paused ();
void playing ();
void stopped ();
void playbackStateChanged (PlaybackState playbackState);
private:
void buildInternalPlayer ();
void rebuildInternalPlayer ();
void stop (bool force);
void handleEof ();
void setError (const QString &message);
QString getSource () const;
void setSource (const QString &source);
PlaybackState getPlaybackState () const;
void setPlaybackState (PlaybackState playbackState);
int getDuration () const;
QString mSource;
PlaybackState mPlaybackState = StoppedState;
bool mForceClose = false;
QMutex mForceCloseMutex;
QTimer *mForceCloseTimer = nullptr;
std::shared_ptr<linphone::Player> mInternalPlayer;
std::shared_ptr<Handlers> mHandlers;
void buildInternalPlayer ();
void rebuildInternalPlayer ();
void stop (bool force);
void handleEof ();
void setError (const QString &message);
QString getSource () const;
void setSource (const QString &source);
PlaybackState getPlaybackState () const;
void setPlaybackState (PlaybackState playbackState);
int getDuration () const;
QString mSource;
PlaybackState mPlaybackState = StoppedState;
bool mForceClose = false;
QMutex mForceCloseMutex;
QTimer *mForceCloseTimer = nullptr;
std::shared_ptr<linphone::Player> mInternalPlayer;
std::shared_ptr<Handlers> mHandlers;
};
#endif // SOUND_PLAYER_H_

View file

@ -17,7 +17,7 @@ ProgressBar {
property bool stopAtEnd: true
property bool resetAtEnd: false
property int progressDuration // Max duration
property int progressPosition // Position of pregress bar in [0 ; progressDuration]
property int progressPosition // Position of progress bar in [0 ; progressDuration]
property alias colorSet: progression.colorSet
function start(){
@ -54,7 +54,6 @@ ProgressBar {
progressBar.value = 100// Stay at 100
progressPosition = progressDuration
}
progressBar.endReached()
}else
progression.percentageDisplayed = value

View file

@ -22,10 +22,8 @@ Rectangle{
property bool isRecording : (vocalRecorder ? vocalRecorder.state != LinphoneEnums.RecorderStateClosed : false)
property bool isPlaying : vocalPlayer.item && vocalPlayer.item.playbackState === SoundPlayer.PlayingState
onVocalRecorderChanged: if(haveRecorder)
audioPreviewBlock.state = 'showed'
onIsRecordingChanged: if(isRecording) {
mediaProgressBar.start()
mediaProgressBar.resume()
}else
mediaProgressBar.stop()
onIsPlayingChanged: isPlaying ? mediaProgressBar.resume() : mediaProgressBar.stop()
@ -34,25 +32,22 @@ Rectangle{
color: ChatAudioPreviewStyle.backgroundColor
radius: 0
state: "hidden"
visible: haveRecorder
onVisibleChanged: if(!visible) hide()
state: haveRecorder ? 'showed' : 'hidden'
clip: false
function hide(){
state = 'hidden'
}
Loader {
id: vocalPlayer
active: false
active: haveRecorder && vocalRecorder && !isRecording
sourceComponent: SoundPlayer {
source: (haveRecorder && vocalRecorder? vocalRecorder.file : '')
onStopped:{
mediaProgressBar.value = 100
mediaProgressBar.value = 101
}
Component.onCompleted: {
play()// This will open the file and allow seeking
pause()
mediaProgressBar.value = 101
mediaProgressBar.refresh()
}
}
}
@ -67,7 +62,7 @@ Rectangle{
Layout.alignment: Qt.AlignVCenter
isCustom: true
colorSet: ChatAudioPreviewStyle.deleteAction
onClicked: audioPreviewBlock.hide()
onClicked: RecorderManager.clearVocalRecorder()
}
Item{
Layout.fillHeight: true
@ -78,23 +73,27 @@ Rectangle{
MediaProgressBar{
id: mediaProgressBar
anchors.fill: parent
progressDuration: 0
progressPosition: 0
progressDuration: !vocalPlayer.item ? vocalRecorder.getDuration() : 0
progressPosition: !vocalPlayer.item ? progressDuration : 0
value: !vocalPlayer.item ? 0.01 * progressDuration / 5 : 100
stopAtEnd: !audioPreviewBlock.isRecording
resetAtEnd: false
colorSet: isRecording ? ChatAudioPreviewStyle.recordingProgressionWave : ChatAudioPreviewStyle.progressionWave
function refresh(){
if( vocalPlayer.item){
progressPosition = vocalPlayer.item.getPosition()
value = 100 * ( progressPosition / vocalPlayer.item.duration)
}else{// Recording
progressDuration = vocalRecorder.getDuration()
progressPosition = progressDuration
value = value + 0.01
}
}
onEndReached:{
if(vocalPlayer.item)
vocalPlayer.item.stop()
}
onRefreshPositionRequested: if( vocalPlayer.item){
progressPosition = vocalPlayer.item.getPosition()
value = 100 * ( progressPosition / vocalPlayer.item.duration)
}else{// Recording
progressDuration = vocalRecorder.getDuration()
progressPosition = progressDuration
value = value + 0.01
}
onRefreshPositionRequested: refresh()
onSeekRequested: if( vocalPlayer.item){
vocalPlayer.item.seek(ms)
progressPosition = vocalPlayer.item.getPosition()
@ -115,8 +114,7 @@ Rectangle{
onClicked:{
if(audioPreviewBlock.isRecording){// Stop the record and save the file
audioPreviewBlock.vocalRecorder.stop()
mediaProgressBar.value = 100
vocalPlayer.active = true
//mediaProgressBar.value = 100
}else if(audioPreviewBlock.isPlaying){// Pause the play
vocalPlayer.item.pause()
}else{// Play the audio
@ -128,17 +126,18 @@ Rectangle{
states: [
State {
name: "hidden"
PropertyChanges { target: audioPreviewBlock; opacity: 0 }
PropertyChanges { target: audioPreviewBlock; opacity: 0 ; visible: false }
},
State {
name: "showed"
PropertyChanges { target: audioPreviewBlock; opacity: 1 }
PropertyChanges { target: audioPreviewBlock; opacity: 1 ; visible: true }
}
]
transitions: [
Transition {
from: "*"; to: "showed"
SequentialAnimation{
ScriptAction{ script: audioPreviewBlock.visible = true }
ScriptAction{ script: audioPreviewBlock.vocalRecorder.start() }
NumberAnimation{ properties: "opacity"; easing.type: Easing.OutBounce; duration: 250 }
}
@ -146,9 +145,8 @@ Rectangle{
Transition {
from: "*"; to: "hidden"
SequentialAnimation{
ScriptAction{ script: RecorderManager.clearVocalRecorder()}
ScriptAction{ script: vocalPlayer.active = false }
NumberAnimation{ properties: "opacity"; duration: 250 }
ScriptAction{ script: audioPreviewBlock.visible = false }
}
}
]

View file

@ -22,7 +22,6 @@ ColumnLayout{
spacing: 0
function hide(){
audioPreview.hide()
}
ChatReplyPreview{
id: replyPreview

View file

@ -58,7 +58,7 @@ DialogPlus {
return UtilsCpp.hasCapability(entry.sipAddress, LinphoneEnums.FriendCapabilityLimeX3Dh)
},
handler: function (entry) {
mainItem.addressSelectedCallback(sipAddress)
mainItem.addressSelectedCallback(entry.sipAddress)
exit(1)
},
}]