aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJordan Bayles <jophba@chromium.org>2021-07-23 11:55:26 -0700
committerOpenscreen LUCI CQ <openscreen-scoped@luci-project-accounts.iam.gserviceaccount.com>2021-07-27 18:52:43 +0000
commitd3d748595d324fb16043df329c9eb906f1d20bf4 (patch)
treea3cf9ff81ddda233de3c57d39340e48dffab768a
parent7ad58ffa2b37ecb7ebe2e8b0e1b863925203fdd5 (diff)
downloadopenscreen-d3d748595d324fb16043df329c9eb906f1d20bf4.tar.gz
[Cast Standalone] Implement Play/Pause
This patch adds play/pause support to the standalone receiver and sender classes while remoting. The implementation uses SDL keyboard event bindings to pick up on spacebar presses when the SDL player window (e.g. the standalone receiver video) is in focus. This event binding results in a SetPlaybackRate message being sent over the RPCMessenger, and then the standalone sender class uses the message's double value to toggle playback on the simulated capturer implementations. While falling far short of a full media player implementation, this patch may help embedders see the basics of how RPC messenging and keeping the sender and receiver media streams in sync can behave. Change-Id: I4d2e1ed7f6d69f8a24385c9139320492c2101600 Reviewed-on: https://chromium-review.googlesource.com/c/openscreen/+/3034990 Reviewed-by: Ryan Keane <rwkeane@google.com> Commit-Queue: Jordan Bayles <jophba@chromium.org>
-rw-r--r--cast/standalone_receiver/sdl_glue.cc11
-rw-r--r--cast/standalone_receiver/sdl_glue.h12
-rw-r--r--cast/standalone_receiver/simple_remoting_receiver.cc8
-rw-r--r--cast/standalone_receiver/simple_remoting_receiver.h4
-rw-r--r--cast/standalone_receiver/streaming_playback_controller.cc24
-rw-r--r--cast/standalone_receiver/streaming_playback_controller.h5
-rw-r--r--cast/standalone_sender/looping_file_cast_agent.cc8
-rw-r--r--cast/standalone_sender/looping_file_cast_agent.h11
-rw-r--r--cast/standalone_sender/looping_file_sender.cc5
-rw-r--r--cast/standalone_sender/looping_file_sender.h4
-rw-r--r--cast/standalone_sender/remoting_sender.cc32
-rw-r--r--cast/standalone_sender/remoting_sender.h26
-rw-r--r--cast/standalone_sender/simulated_capturer.cc11
-rw-r--r--cast/standalone_sender/simulated_capturer.h6
14 files changed, 138 insertions, 29 deletions
diff --git a/cast/standalone_receiver/sdl_glue.cc b/cast/standalone_receiver/sdl_glue.cc
index 7c2c94da..c4619f09 100644
--- a/cast/standalone_receiver/sdl_glue.cc
+++ b/cast/standalone_receiver/sdl_glue.cc
@@ -4,6 +4,8 @@
#include "cast/standalone_receiver/sdl_glue.h"
+#include <utility>
+
#include "platform/api/task_runner.h"
#include "platform/api/time.h"
#include "util/osp_logging.h"
@@ -21,6 +23,11 @@ SDLEventLoopProcessor::SDLEventLoopProcessor(
SDLEventLoopProcessor::~SDLEventLoopProcessor() = default;
+void SDLEventLoopProcessor::RegisterForKeyboardEvent(
+ SDLEventLoopProcessor::KeyboardEventCallback cb) {
+ keyboard_callbacks_.push_back(std::move(cb));
+}
+
void SDLEventLoopProcessor::ProcessPendingEvents() {
// Process all pending events.
SDL_Event event;
@@ -30,6 +37,10 @@ void SDLEventLoopProcessor::ProcessPendingEvents() {
if (quit_callback_) {
quit_callback_();
}
+ } else if (event.type == SDL_KEYUP) {
+ for (auto& cb : keyboard_callbacks_) {
+ cb(event.key);
+ }
}
}
diff --git a/cast/standalone_receiver/sdl_glue.h b/cast/standalone_receiver/sdl_glue.h
index 59a3a020..7e136074 100644
--- a/cast/standalone_receiver/sdl_glue.h
+++ b/cast/standalone_receiver/sdl_glue.h
@@ -7,14 +7,16 @@
#include <stdint.h>
-#include <functional>
-#include <memory>
-
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
#include <SDL2/SDL.h>
#pragma GCC diagnostic pop
+#include <functional>
+#include <memory>
+#include <utility>
+#include <vector>
+
#include "util/alarm.h"
namespace openscreen {
@@ -66,11 +68,15 @@ class SDLEventLoopProcessor {
std::function<void()> quit_callback);
~SDLEventLoopProcessor();
+ using KeyboardEventCallback = std::function<void(const SDL_KeyboardEvent&)>;
+ void RegisterForKeyboardEvent(KeyboardEventCallback cb);
+
private:
void ProcessPendingEvents();
Alarm alarm_;
std::function<void()> quit_callback_;
+ std::vector<KeyboardEventCallback> keyboard_callbacks_;
};
} // namespace cast
diff --git a/cast/standalone_receiver/simple_remoting_receiver.cc b/cast/standalone_receiver/simple_remoting_receiver.cc
index 119be44f..b685d564 100644
--- a/cast/standalone_receiver/simple_remoting_receiver.cc
+++ b/cast/standalone_receiver/simple_remoting_receiver.cc
@@ -76,6 +76,14 @@ void SimpleRemotingReceiver::SendInitializeMessage(
messenger_->SendMessageToRemote(rpc);
}
+void SimpleRemotingReceiver::SendPlaybackRateMessage(double playback_rate) {
+ openscreen::cast::RpcMessage rpc;
+ rpc.set_handle(RpcMessenger::kAcquireRendererHandle);
+ rpc.set_proc(openscreen::cast::RpcMessage::RPC_R_SETPLAYBACKRATE);
+ rpc.set_double_value(playback_rate);
+ messenger_->SendMessageToRemote(rpc);
+}
+
void SimpleRemotingReceiver::OnInitializeCallbackMessage(
std::unique_ptr<RpcMessage> message) {
OSP_DCHECK(message->proc() == RpcMessage::RPC_DS_INITIALIZE_CALLBACK);
diff --git a/cast/standalone_receiver/simple_remoting_receiver.h b/cast/standalone_receiver/simple_remoting_receiver.h
index 52fe131f..8e672574 100644
--- a/cast/standalone_receiver/simple_remoting_receiver.h
+++ b/cast/standalone_receiver/simple_remoting_receiver.h
@@ -39,6 +39,10 @@ class SimpleRemotingReceiver {
using InitializeCallback = std::function<void(AudioCodec, VideoCodec)>;
void SendInitializeMessage(InitializeCallback initialize_cb);
+ // The speed at which the content is decoded is synchronized with the
+ // playback rate. Pausing is a special case with a playback rate of 0.0.
+ void SendPlaybackRateMessage(double playback_rate);
+
private:
void OnInitializeCallbackMessage(std::unique_ptr<RpcMessage> message);
diff --git a/cast/standalone_receiver/streaming_playback_controller.cc b/cast/standalone_receiver/streaming_playback_controller.cc
index 8c931273..4a81c7ed 100644
--- a/cast/standalone_receiver/streaming_playback_controller.cc
+++ b/cast/standalone_receiver/streaming_playback_controller.cc
@@ -44,6 +44,11 @@ StreamingPlaybackController::StreamingPlaybackController(
OSP_CHECK(window_) << "Failed to create SDL window: " << SDL_GetError();
renderer_ = MakeUniqueSDLRenderer(window_.get(), -1, 0);
OSP_CHECK(renderer_) << "Failed to create SDL renderer: " << SDL_GetError();
+
+ sdl_event_loop_.RegisterForKeyboardEvent(
+ [this](const SDL_KeyboardEvent& event) {
+ this->HandleKeyboardEvent(event);
+ });
}
#else
StreamingPlaybackController::StreamingPlaybackController(
@@ -121,5 +126,24 @@ void StreamingPlaybackController::Initialize(
#endif // defined(CAST_STANDALONE_RECEIVER_HAVE_EXTERNAL_LIBS)
}
+#if defined(CAST_STANDALONE_RECEIVER_HAVE_EXTERNAL_LIBS)
+void StreamingPlaybackController::HandleKeyboardEvent(
+ const SDL_KeyboardEvent& event) {
+ // We only handle keyboard events if we are remoting.
+ if (!remoting_receiver_) {
+ return;
+ }
+
+ switch (event.keysym.sym) {
+ // See codes here: https://wiki.libsdl.org/SDL_Scancode
+ case SDLK_KP_SPACE: // fallthrough, "Keypad Space"
+ case SDLK_SPACE: // "Space"
+ is_playing_ = !is_playing_;
+ remoting_receiver_->SendPlaybackRateMessage(is_playing_ ? 1.0 : 0.0);
+ break;
+ }
+}
+#endif
+
} // namespace cast
} // namespace openscreen
diff --git a/cast/standalone_receiver/streaming_playback_controller.h b/cast/standalone_receiver/streaming_playback_controller.h
index 68f3068d..109b8adb 100644
--- a/cast/standalone_receiver/streaming_playback_controller.h
+++ b/cast/standalone_receiver/streaming_playback_controller.h
@@ -53,17 +53,20 @@ class StreamingPlaybackController final : public ReceiverSession::Client {
void Initialize(ReceiverSession::ConfiguredReceivers receivers);
#if defined(CAST_STANDALONE_RECEIVER_HAVE_EXTERNAL_LIBS)
+ void HandleKeyboardEvent(const SDL_KeyboardEvent& event);
+
// NOTE: member ordering is important, since the sub systems must be
// first-constructed, last-destroyed. Make sure any new SDL related
// members are added below the sub systems.
const ScopedSDLSubSystem<SDL_INIT_AUDIO> sdl_audio_sub_system_;
const ScopedSDLSubSystem<SDL_INIT_VIDEO> sdl_video_sub_system_;
- const SDLEventLoopProcessor sdl_event_loop_;
+ SDLEventLoopProcessor sdl_event_loop_;
SDLWindowUniquePtr window_;
SDLRendererUniquePtr renderer_;
std::unique_ptr<SDLAudioPlayer> audio_player_;
std::unique_ptr<SDLVideoPlayer> video_player_;
+ double is_playing_ = true;
#else
std::unique_ptr<DummyPlayer> audio_player_;
std::unique_ptr<DummyPlayer> video_player_;
diff --git a/cast/standalone_sender/looping_file_cast_agent.cc b/cast/standalone_sender/looping_file_cast_agent.cc
index 4ee2231a..0e17ecab 100644
--- a/cast/standalone_sender/looping_file_cast_agent.cc
+++ b/cast/standalone_sender/looping_file_cast_agent.cc
@@ -273,7 +273,7 @@ void LoopingFileCastAgent::CreateAndStartSession() {
if (connection_settings_->use_remoting) {
remoting_sender_ = std::make_unique<RemotingSender>(
current_session_->rpc_messenger(), AudioCodec::kOpus,
- connection_settings_->codec, [this]() { OnRemotingReceiverReady(); });
+ connection_settings_->codec, this);
negotiation_error =
current_session_->NegotiateRemoting(audio_config, video_config);
@@ -321,13 +321,17 @@ void LoopingFileCastAgent::OnError(const SenderSession* session, Error error) {
Shutdown();
}
-void LoopingFileCastAgent::OnRemotingReceiverReady() {
+void LoopingFileCastAgent::OnReady() {
is_ready_for_remoting_ = true;
if (current_negotiation_) {
StartRemotingSenders();
}
}
+void LoopingFileCastAgent::OnPlaybackRateChange(double rate) {
+ file_sender_->SetPlaybackRate(rate);
+}
+
void LoopingFileCastAgent::StartRemotingSenders() {
OSP_DCHECK(current_negotiation_);
file_sender_ = std::make_unique<LoopingFileSender>(
diff --git a/cast/standalone_sender/looping_file_cast_agent.h b/cast/standalone_sender/looping_file_cast_agent.h
index 895d1324..3ec2e8fa 100644
--- a/cast/standalone_sender/looping_file_cast_agent.h
+++ b/cast/standalone_sender/looping_file_cast_agent.h
@@ -70,7 +70,8 @@ class LoopingFileCastAgent final
public VirtualConnectionRouter::SocketErrorHandler,
public ConnectionNamespaceHandler::VirtualConnectionPolicy,
public CastMessageHandler,
- public SenderSession::Client {
+ public SenderSession::Client,
+ public RemotingSender::Client {
public:
using ShutdownCallback = std::function<void()>;
@@ -107,6 +108,10 @@ class LoopingFileCastAgent final
CastSocket* socket,
::cast::channel::CastMessage message) override;
+ // RemotingSender::Client overrides.
+ void OnReady() override;
+ void OnPlaybackRateChange(double rate) override;
+
// Returns the Cast application ID for either A/V mirroring or audio-only
// mirroring, as configured by the ConnectionSettings.
const char* GetMirroringAppId() const;
@@ -135,10 +140,6 @@ class LoopingFileCastAgent final
SenderSession::RemotingNegotiation negotiation) override;
void OnError(const SenderSession* session, Error error) override;
- // Callback for when the RemotingSender indicates that the receiver
- // is ready.
- void OnRemotingReceiverReady();
-
// Starts the remoting sender. This may occur when remoting is "ready" if the
// session is already negotiated, or upon session negotiation if the receiver
// is already ready.
diff --git a/cast/standalone_sender/looping_file_sender.cc b/cast/standalone_sender/looping_file_sender.cc
index 9f6137dc..7ed5e3fe 100644
--- a/cast/standalone_sender/looping_file_sender.cc
+++ b/cast/standalone_sender/looping_file_sender.cc
@@ -44,6 +44,11 @@ LoopingFileSender::LoopingFileSender(Environment* environment,
LoopingFileSender::~LoopingFileSender() = default;
+void LoopingFileSender::SetPlaybackRate(double rate) {
+ video_capturer_->SetPlaybackRate(rate);
+ audio_capturer_->SetPlaybackRate(rate);
+}
+
void LoopingFileSender::UpdateEncoderBitrates() {
if (bandwidth_being_utilized_ >= kHighBandwidthThreshold) {
audio_encoder_.UseHighQuality();
diff --git a/cast/standalone_sender/looping_file_sender.h b/cast/standalone_sender/looping_file_sender.h
index 56a8aa45..7ad784b9 100644
--- a/cast/standalone_sender/looping_file_sender.h
+++ b/cast/standalone_sender/looping_file_sender.h
@@ -33,6 +33,8 @@ class LoopingFileSender final : public SimulatedAudioCapturer::Client,
~LoopingFileSender() final;
+ void SetPlaybackRate(double rate);
+
private:
void UpdateEncoderBitrates();
void ControlForNetworkCongestion();
@@ -49,7 +51,7 @@ class LoopingFileSender final : public SimulatedAudioCapturer::Client,
void UpdateStatusOnConsole();
- // SimulatedCapturer overrides.
+ // SimulatedCapturer::Client overrides.
void OnEndOfFile(SimulatedCapturer* capturer) final;
void OnError(SimulatedCapturer* capturer, std::string message) final;
diff --git a/cast/standalone_sender/remoting_sender.cc b/cast/standalone_sender/remoting_sender.cc
index 1566f0a7..741fb190 100644
--- a/cast/standalone_sender/remoting_sender.cc
+++ b/cast/standalone_sender/remoting_sender.cc
@@ -41,19 +41,22 @@ AudioDecoderConfig::Codec ToProtoCodec(AudioCodec value) {
} // namespace
+RemotingSender::Client::~Client() = default;
+
RemotingSender::RemotingSender(RpcMessenger* messenger,
AudioCodec audio_codec,
VideoCodec video_codec,
- ReadyCallback ready_cb)
+ Client* client)
: messenger_(messenger),
audio_codec_(audio_codec),
video_codec_(video_codec),
- ready_cb_(std::move(ready_cb)) {
+ client_(client) {
+ OSP_DCHECK(client_);
messenger_->RegisterMessageReceiverCallback(
RpcMessenger::kAcquireRendererHandle,
[this](std::unique_ptr<RpcMessage> message) {
OSP_DCHECK(message);
- this->OnInitializeMessage(*message);
+ this->OnMessage(*message);
});
}
@@ -62,6 +65,19 @@ RemotingSender::~RemotingSender() {
RpcMessenger::kAcquireRendererHandle);
}
+void RemotingSender::OnMessage(const RpcMessage& message) {
+ if (!message.has_proc()) {
+ return;
+ }
+ if (message.proc() == RpcMessage_RpcProc_RPC_DS_INITIALIZE) {
+ OSP_VLOG << "Received initialize message";
+ OnInitializeMessage(message);
+ } else if (message.proc() == RpcMessage_RpcProc_RPC_R_SETPLAYBACKRATE) {
+ OSP_VLOG << "Received playback rate message: " << message.double_value();
+ OnPlaybackRateMessage(message);
+ }
+}
+
void RemotingSender::OnInitializeMessage(const RpcMessage& message) {
receiver_handle_ = message.integer_value();
@@ -84,11 +100,11 @@ void RemotingSender::OnInitializeMessage(const RpcMessage& message) {
<< " and video codec " << CodecToString(video_codec_);
messenger_->SendMessageToRemote(callback_message);
- if (ready_cb_) {
- ready_cb_();
- } else {
- OSP_DLOG_INFO << "Received a ready message, but no ready callback.";
- }
+ client_->OnReady();
+}
+
+void RemotingSender::OnPlaybackRateMessage(const RpcMessage& message) {
+ client_->OnPlaybackRateChange(message.double_value());
}
} // namespace cast
diff --git a/cast/standalone_sender/remoting_sender.h b/cast/standalone_sender/remoting_sender.h
index d331009b..7d09dc69 100644
--- a/cast/standalone_sender/remoting_sender.h
+++ b/cast/standalone_sender/remoting_sender.h
@@ -26,19 +26,29 @@ namespace cast {
// the public APIs.
class RemotingSender {
public:
- using ReadyCallback = std::function<void()>;
+ // The remoting sender expects a valid client to handle received messages.
+ class Client {
+ public:
+ virtual ~Client();
+
+ // Executed when we receive the initialize message from the receiver.
+ virtual void OnReady() = 0;
+
+ // Executed when we receive a playback rate message from the receiver.
+ virtual void OnPlaybackRateChange(double rate) = 0;
+ };
+
RemotingSender(RpcMessenger* messenger,
AudioCodec audio_codec,
VideoCodec video_codec,
- ReadyCallback ready_cb);
+ Client* client);
~RemotingSender();
private:
- // When the receiver indicates that it is ready for initialization, it will
- // The receiver sends us an "initialization" message that we respond to
- // here with an "initialization callback" message that contains codec
- // information.
+ // Helper for parsing any received RPC messages.
+ void OnMessage(const RpcMessage& message);
void OnInitializeMessage(const RpcMessage& message);
+ void OnPlaybackRateMessage(const RpcMessage& message);
// The messenger is the only caller of OnInitializeMessage, so there are no
// lifetime concerns. However, if this class outlives |messenger_|, it will
@@ -52,9 +62,7 @@ class RemotingSender {
const AudioCodec audio_codec_;
const VideoCodec video_codec_;
- // The callback method to be called once we get the initialization message
- // from the receiver.
- ReadyCallback ready_cb_;
+ Client* client_;
// The initialization message from the receiver contains the handle the
// callback should go to.
diff --git a/cast/standalone_sender/simulated_capturer.cc b/cast/standalone_sender/simulated_capturer.cc
index 87313010..713caa24 100644
--- a/cast/standalone_sender/simulated_capturer.cc
+++ b/cast/standalone_sender/simulated_capturer.cc
@@ -85,6 +85,14 @@ SimulatedCapturer::SimulatedCapturer(Environment* environment,
SimulatedCapturer::~SimulatedCapturer() = default;
+void SimulatedCapturer::SetPlaybackRate(double rate) {
+ playback_rate_is_non_zero_ = rate > 0;
+ if (playback_rate_is_non_zero_) {
+ // Restart playback now that playback rate is nonzero.
+ StartDecodingNextFrame();
+ }
+}
+
void SimulatedCapturer::SetAdditionalDecoderParameters(
AVCodecContext* decoder_context) {}
@@ -119,6 +127,9 @@ Clock::duration SimulatedCapturer::ToApproximateClockDuration(
}
void SimulatedCapturer::StartDecodingNextFrame() {
+ if (!playback_rate_is_non_zero_) {
+ return;
+ }
const int read_frame_result =
av_read_frame(format_context_.get(), packet_.get());
if (read_frame_result < 0) {
diff --git a/cast/standalone_sender/simulated_capturer.h b/cast/standalone_sender/simulated_capturer.h
index 8d32085a..61738e1f 100644
--- a/cast/standalone_sender/simulated_capturer.h
+++ b/cast/standalone_sender/simulated_capturer.h
@@ -40,6 +40,8 @@ class SimulatedCapturer {
virtual ~Observer();
};
+ void SetPlaybackRate(double rate);
+
protected:
SimulatedCapturer(Environment* environment,
const char* path,
@@ -103,6 +105,10 @@ class SimulatedCapturer {
// Used to schedule the next task to execute and when it should execute. There
// is only ever one task scheduled/running at any time.
Alarm next_task_;
+
+ // Used to determine playback rate. Currently, we only support "playing"
+ // at 1x speed, or "pausing" at 0x speed.
+ bool playback_rate_is_non_zero_ = true;
};
// Emits the primary audio stream from a file.