blob: 43ccf6ba70c5a509ce23f93eee8efb37e1bddf45 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/media/audio/audio_output_service/output_stream_connection.h"
#include <utility>
#include "base/check.h"
#include "base/logging.h"
#include "net/base/io_buffer.h"
namespace chromecast {
namespace media {
namespace audio_output_service {
namespace {
constexpr base::TimeDelta kHeartbeatTimeout = base::Seconds(2);
enum MessageTypes : int {
kInitial = 1,
kStartTimestamp,
kPlaybackRate,
kStreamVolume,
kEndOfStream,
kUpdateAudioConfig,
kStop,
kHeartbeat,
};
} // namespace
OutputStreamConnection::OutputStreamConnection(
Delegate* delegate,
CmaBackendParams params,
mojo::PendingRemote<mojom::AudioSocketBroker> pending_socket_broker)
: OutputConnection(std::move(pending_socket_broker)),
delegate_(delegate),
params_(std::move(params)) {
DCHECK(delegate_);
}
OutputStreamConnection::~OutputStreamConnection() = default;
void OutputStreamConnection::SendAudioBuffer(
scoped_refptr<net::IOBuffer> audio_buffer,
int buffer_size_bytes,
int64_t pts) {
if (!socket_) {
LOG(INFO) << "Tried to send buffer without a connection.";
return;
}
if (sent_eos_) {
// Should not send any more data after the EOS buffer.
return;
}
if (buffer_size_bytes == 0) {
sent_eos_ = true;
Generic message;
message.mutable_eos_played_out();
socket_->SendProto(kEndOfStream, message);
return;
}
if (socket_->SendAudioBuffer(std::move(audio_buffer), buffer_size_bytes,
pts)) {
LOG_IF(INFO, dropping_audio_) << "Stopped dropping audio.";
dropping_audio_ = false;
} else {
LOG_IF(WARNING, !dropping_audio_) << "Dropping audio.";
dropping_audio_ = true;
}
heartbeat_timer_.Reset();
}
void OutputStreamConnection::StartPlayingFrom(int64_t start_pts) {
if (!socket_) {
return;
}
Generic message;
message.mutable_set_start_timestamp()->set_start_pts(start_pts);
socket_->SendProto(kStartTimestamp, message);
heartbeat_timer_.Reset();
}
void OutputStreamConnection::StopPlayback() {
if (!socket_) {
return;
}
Generic message;
message.mutable_stop_playback();
socket_->SendProto(kStop, message);
heartbeat_timer_.Reset();
}
void OutputStreamConnection::SetPlaybackRate(float playback_rate) {
playback_rate_ = playback_rate;
if (!socket_) {
return;
}
Generic message;
message.mutable_set_playback_rate()->set_playback_rate(playback_rate_);
socket_->SendProto(kPlaybackRate, message);
heartbeat_timer_.Reset();
}
void OutputStreamConnection::SetVolume(float volume) {
volume_ = volume;
if (!socket_) {
return;
}
Generic message;
message.mutable_set_stream_volume()->set_volume(volume_);
socket_->SendProto(kStreamVolume, message);
heartbeat_timer_.Reset();
}
void OutputStreamConnection::UpdateAudioConfig(const CmaBackendParams& params) {
params_.MergeFrom(params);
if (!socket_) {
return;
}
Generic message;
*(message.mutable_backend_params()) = params_;
socket_->SendProto(kUpdateAudioConfig, message);
heartbeat_timer_.Reset();
}
void OutputStreamConnection::Connect() {
if (socket_) {
// Don't reconnect if the connection is already established.
return;
}
OutputConnection::Connect();
}
void OutputStreamConnection::OnConnected(std::unique_ptr<OutputSocket> socket) {
DCHECK(socket);
socket_ = std::move(socket);
socket_->SetDelegate(this);
Generic message;
*(message.mutable_backend_params()) = params_;
if (playback_rate_ != 1.0f) {
message.mutable_set_playback_rate()->set_playback_rate(playback_rate_);
}
if (volume_ != 1.0f) {
message.mutable_set_stream_volume()->set_volume(volume_);
}
socket_->SendProto(kInitial, message);
heartbeat_timer_.Start(FROM_HERE, kHeartbeatTimeout, this,
&OutputStreamConnection::SendHeartbeat);
}
void OutputStreamConnection::OnConnectionFailed() {
BackendInitializationStatus status;
status.set_status(BackendInitializationStatus::ERROR);
delegate_->OnBackendInitialized(status);
}
void OutputStreamConnection::OnConnectionError() {
heartbeat_timer_.Stop();
socket_.reset();
OutputConnection::Connect();
}
void OutputStreamConnection::SendHeartbeat() {
if (!socket_) {
LOG(ERROR) << "No active connection. Skip heartbeat.";
return;
}
Generic message;
message.mutable_heartbeat();
socket_->SendProto(kHeartbeat, message);
heartbeat_timer_.Start(FROM_HERE, kHeartbeatTimeout, this,
&OutputStreamConnection::SendHeartbeat);
}
bool OutputStreamConnection::HandleMetadata(const Generic& message) {
if (message.has_backend_initialization_status()) {
delegate_->OnBackendInitialized(message.backend_initialization_status());
}
if (message.has_current_media_timestamp()) {
delegate_->OnNextBuffer(
message.current_media_timestamp().media_timestamp_microseconds(),
message.current_media_timestamp().reference_timestamp_microseconds(),
message.current_media_timestamp().delay_microseconds(),
message.current_media_timestamp().delay_timestamp_microseconds());
}
return true;
}
} // namespace audio_output_service
} // namespace media
} // namespace chromecast