/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore Studio
* Music Composition & Notation
*
* Copyright (C) 2024 MuseScore Limited and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* 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, see .
*/
#include "player.h"
#include "audio/common/rpc/rpcpacker.h"
#include "audio/common/audiosanitizer.h"
#include "log.h"
using namespace muse;
using namespace muse::async;
using namespace muse::audio;
using namespace muse::audio::rpc;
rpc::CtxId Player::ctxId() const
{
return rpc::ctxId(iocContext());
}
void Player::init()
{
ONLY_AUDIO_MAIN_THREAD;
//! NOTE Subscribe and request initial state
{
m_playbackStatusChanged.onReceive(this, [this](PlaybackStatus st) {
m_playbackStatus = st;
});
Msg msg = rpc::make_request(ctxId(), MsgCode::GetPlaybackStatus);
channel()->send(msg, [this, alive = std::weak_ptr(m_alive)](const Msg& res) {
ONLY_AUDIO_MAIN_THREAD;
if (alive.expired()) {
return;
}
Ret ret;
PlaybackStatus status = PlaybackStatus::Stopped;
StreamId streamId = 0;
IF_ASSERT_FAILED(RpcPacker::unpack(res.data, ret, status, streamId)) {
return;
}
if (ret) {
m_playbackStatusStreamId = streamId;
channel()->addReceiveStream(StreamName::PlaybackStatusStream, streamId, m_playbackStatusChanged);
//! NOTE Send initial state
m_playbackStatusChanged.send(status);
} else {
LOGE() << "GetPlaybackStatus failed: " << ret.toString();
}
});
}
{
m_playbackPositionChanged.onReceive(this, [this](const secs_t newPos) {
m_playbackPosition = newPos;
});
Msg msg = rpc::make_request(ctxId(), MsgCode::GetPlaybackPosition);
channel()->send(msg, [this, alive = std::weak_ptr(m_alive)](const Msg& res) {
ONLY_AUDIO_MAIN_THREAD;
if (alive.expired()) {
return;
}
Ret ret;
secs_t pos = 0.0;
StreamId streamId = 0;
IF_ASSERT_FAILED(RpcPacker::unpack(res.data, ret, pos, streamId)) {
return;
}
if (ret) {
m_playbackPositionStreamId = streamId;
channel()->addReceiveStream(StreamName::PlaybackPositionStream, streamId, m_playbackPositionChanged);
//! NOTE Send initial state
m_playbackPositionChanged.send(pos);
} else {
LOGE() << "GetPlaybackPosition failed: " << ret.toString();
}
});
}
}
void Player::deinit()
{
ONLY_AUDIO_MAIN_THREAD;
m_alive.reset();
if (m_playbackStatusStreamId != 0) {
channel()->removeStream(m_playbackStatusStreamId);
m_playbackStatusStreamId = 0;
}
if (m_playbackPositionStreamId != 0) {
channel()->removeStream(m_playbackPositionStreamId);
m_playbackPositionStreamId = 0;
}
m_playbackStatusChanged.disconnect(this);
m_playbackPositionChanged.disconnect(this);
}
async::Promise Player::prepareToPlay()
{
ONLY_AUDIO_MAIN_THREAD;
return async::make_promise([this](auto resolve, auto) {
ONLY_AUDIO_MAIN_THREAD;
Msg msg = rpc::make_request(ctxId(), MsgCode::PrepareToPlay);
channel()->send(msg, [resolve](const Msg& res) {
ONLY_AUDIO_MAIN_THREAD;
Ret ret;
IF_ASSERT_FAILED(RpcPacker::unpack(res.data, ret)) {
(void)resolve(make_ret(Ret::Code::UnknownError));
return;
}
(void)resolve(ret);
});
return Promise::dummy_result();
}, PromiseType::AsyncByBody);
}
void Player::play(const secs_t delay)
{
ONLY_AUDIO_MAIN_THREAD;
Msg msg = rpc::make_request(ctxId(), MsgCode::Play, RpcPacker::pack(delay));
channel()->send(msg);
}
void Player::seek(const secs_t newPosition, const bool flushSound)
{
ONLY_AUDIO_MAIN_THREAD;
if (m_playbackPosition == newPosition) {
return;
}
Msg msg = rpc::make_request(ctxId(), MsgCode::Seek, RpcPacker::pack(newPosition, flushSound));
channel()->send(msg);
}
void Player::stop()
{
ONLY_AUDIO_MAIN_THREAD;
Msg msg = rpc::make_request(ctxId(), MsgCode::Stop);
channel()->send(msg);
}
void Player::pause()
{
ONLY_AUDIO_MAIN_THREAD;
Msg msg = rpc::make_request(ctxId(), MsgCode::Pause);
channel()->send(msg);
}
void Player::resume(const secs_t delay)
{
ONLY_AUDIO_MAIN_THREAD;
Msg msg = rpc::make_request(ctxId(), MsgCode::Resume, RpcPacker::pack(delay));
channel()->send(msg);
}
void Player::setDuration(const secs_t duration)
{
ONLY_AUDIO_MAIN_THREAD;
Msg msg = rpc::make_request(ctxId(), MsgCode::SetDuration, RpcPacker::pack(duration));
channel()->send(msg);
}
async::Promise Player::setLoop(const secs_t from, const secs_t to)
{
ONLY_AUDIO_MAIN_THREAD;
return async::make_promise([this, from, to](auto resolve, auto reject) {
ONLY_AUDIO_MAIN_THREAD;
Msg msg = rpc::make_request(ctxId(), MsgCode::SetLoop, RpcPacker::pack(from, to));
channel()->send(msg, [resolve, reject](const Msg& res) {
ONLY_AUDIO_MAIN_THREAD;
Ret ret;
IF_ASSERT_FAILED(RpcPacker::unpack(res.data, ret)) {
return;
}
if (ret) {
(void)resolve(true);
} else {
(void)reject(ret.code(), ret.text());
}
});
return Promise::dummy_result();
}, PromiseType::AsyncByBody);
}
void Player::resetLoop()
{
ONLY_AUDIO_MAIN_THREAD;
Msg msg = rpc::make_request(ctxId(), MsgCode::ResetLoop);
channel()->send(msg);
}
secs_t Player::playbackPosition() const
{
ONLY_AUDIO_MAIN_THREAD;
return m_playbackPosition;
}
async::Channel Player::playbackPositionChanged() const
{
ONLY_AUDIO_MAIN_THREAD;
return m_playbackPositionChanged;
}
PlaybackStatus Player::playbackStatus() const
{
ONLY_AUDIO_MAIN_THREAD;
return m_playbackStatus;
}
async::Channel Player::playbackStatusChanged() const
{
ONLY_AUDIO_MAIN_THREAD;
return m_playbackStatusChanged;
}