diff --git a/ACL/include/ACL/AVSConnectionManager.h b/ACL/include/ACL/AVSConnectionManager.h index 22bb22d7ca..3e26b1c2bb 100644 --- a/ACL/include/ACL/AVSConnectionManager.h +++ b/ACL/include/ACL/AVSConnectionManager.h @@ -117,6 +117,11 @@ class AVSConnectionManager */ void setAVSEndpoint(const std::string& avsEndpoint) override; + /** + * @return The current URL endpoint for AVS connection. + */ + std::string getAVSEndpoint(); + /// @name InternetConnectionObserverInterface method overrides. /// @{ void onConnectionStatusChanged(bool connected) override; diff --git a/ACL/include/ACL/Transport/MessageRouter.h b/ACL/include/ACL/Transport/MessageRouter.h index db05946a14..1cbf799155 100644 --- a/ACL/include/ACL/Transport/MessageRouter.h +++ b/ACL/include/ACL/Transport/MessageRouter.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -73,6 +73,8 @@ class MessageRouter void setAVSEndpoint(const std::string& avsEndpoint) override; + std::string getAVSEndpoint() override; + void setObserver(std::shared_ptr observer) override; void onConnected(std::shared_ptr transport) override; diff --git a/ACL/include/ACL/Transport/MessageRouterInterface.h b/ACL/include/ACL/Transport/MessageRouterInterface.h index 4a11d78d00..40e749efd0 100644 --- a/ACL/include/ACL/Transport/MessageRouterInterface.h +++ b/ACL/include/ACL/Transport/MessageRouterInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -80,6 +80,13 @@ class MessageRouterInterface */ virtual void setAVSEndpoint(const std::string& avsEndpoint) = 0; + /** + * Get the URL endpoint for the AVS connection. + * + * @return The URL for the current AVS endpoint. + */ + virtual std::string getAVSEndpoint() = 0; + /** * Set the observer to this object. * @param observer An observer to this class, which will be notified when the connection status changes, diff --git a/ACL/src/AVSConnectionManager.cpp b/ACL/src/AVSConnectionManager.cpp index ee14c568a8..4cbfc64517 100644 --- a/ACL/src/AVSConnectionManager.cpp +++ b/ACL/src/AVSConnectionManager.cpp @@ -148,6 +148,11 @@ bool AVSConnectionManager::isConnected() const { void AVSConnectionManager::setAVSEndpoint(const std::string& avsEndpoint) { m_messageRouter->setAVSEndpoint(avsEndpoint); } + +std::string AVSConnectionManager::getAVSEndpoint() { + return m_messageRouter->getAVSEndpoint(); +} + void AVSConnectionManager::onConnectionStatusChanged(bool connected) { ACSDK_DEBUG5(LX(__func__).d("connected", connected)); if (!connected) { diff --git a/ACL/src/Transport/HTTP2Transport.cpp b/ACL/src/Transport/HTTP2Transport.cpp index e304c5a7fc..44707bd346 100644 --- a/ACL/src/Transport/HTTP2Transport.cpp +++ b/ACL/src/Transport/HTTP2Transport.cpp @@ -176,7 +176,7 @@ HTTP2Transport::HTTP2Transport( m_postConnected{false}, m_configuration{configuration}, m_disconnectReason{ConnectionStatusObserverInterface::ChangedReason::NONE} { - ACSDK_DEBUG5(LX(__func__) + ACSDK_DEBUG7(LX(__func__) .d("authDelegate", authDelegate.get()) .d("avsEndpoint", avsEndpoint) .d("http2Connection", http2Connection.get()) @@ -251,12 +251,12 @@ bool HTTP2Transport::isConnected() { } void HTTP2Transport::send(std::shared_ptr request) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG7(LX(__func__)); enqueueRequest(request, false); } void HTTP2Transport::sendPostConnectMessage(std::shared_ptr request) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG7(LX(__func__)); enqueueRequest(request, true); } @@ -371,7 +371,7 @@ void HTTP2Transport::onMessageRequestSent() { std::lock_guard lock(m_mutex); m_isMessageHandlerAwaitingResponse = true; m_countOfUnfinishedMessageHandlers++; - ACSDK_DEBUG5(LX(__func__).d("countOfUnfinishedMessageHandlers", m_countOfUnfinishedMessageHandlers)); + ACSDK_DEBUG7(LX(__func__).d("countOfUnfinishedMessageHandlers", m_countOfUnfinishedMessageHandlers)); } void HTTP2Transport::onMessageRequestTimeout() { @@ -384,7 +384,7 @@ void HTTP2Transport::onMessageRequestTimeout() { } void HTTP2Transport::onMessageRequestAcknowledged() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG7(LX(__func__)); std::lock_guard lock(m_mutex); m_isMessageHandlerAwaitingResponse = false; m_wakeEvent.notify_all(); @@ -393,12 +393,12 @@ void HTTP2Transport::onMessageRequestAcknowledged() { void HTTP2Transport::onMessageRequestFinished() { std::lock_guard lock(m_mutex); --m_countOfUnfinishedMessageHandlers; - ACSDK_DEBUG5(LX(__func__).d("countOfUnfinishedMessageHandlers", m_countOfUnfinishedMessageHandlers)); + ACSDK_DEBUG7(LX(__func__).d("countOfUnfinishedMessageHandlers", m_countOfUnfinishedMessageHandlers)); m_wakeEvent.notify_all(); } void HTTP2Transport::onPingRequestAcknowledged(bool success) { - ACSDK_DEBUG5(LX(__func__).d("success", success)); + ACSDK_DEBUG7(LX(__func__).d("success", success)); std::lock_guard lock(m_mutex); m_pingHandler.reset(); if (!success) { @@ -417,7 +417,7 @@ void HTTP2Transport::onPingTimeout() { } void HTTP2Transport::onActivity() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG9(LX(__func__)); std::lock_guard lock(m_mutex); m_timeOfLastActivity = std::chrono::steady_clock::now(); } @@ -428,7 +428,7 @@ void HTTP2Transport::onForbidden(const std::string& authToken) { } std::shared_ptr HTTP2Transport::createAndSendRequest(const HTTP2RequestConfig& cfg) { - ACSDK_DEBUG5(LX(__func__).d("type", cfg.getRequestType()).sensitive("url", cfg.getUrl())); + ACSDK_DEBUG7(LX(__func__).d("type", cfg.getRequestType()).sensitive("url", cfg.getUrl())); return m_http2Connection->createAndSendRequest(cfg); } @@ -537,7 +537,7 @@ HTTP2Transport::State HTTP2Transport::handleConnecting() { } HTTP2Transport::State HTTP2Transport::handleWaitingToRetryConnecting() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG7(LX(__func__)); std::chrono::milliseconds timeout = TransportDefines::RETRY_TIMER.calculateTimeToRetry(m_connectRetryCount); ACSDK_DEBUG5( @@ -602,7 +602,7 @@ HTTP2Transport::State HTTP2Transport::handleShutdown() { } void HTTP2Transport::enqueueRequest(std::shared_ptr request, bool beforeConnected) { - ACSDK_DEBUG5(LX(__func__).d("beforeConnected", beforeConnected)); + ACSDK_DEBUG7(LX(__func__).d("beforeConnected", beforeConnected)); if (!request) { ACSDK_ERROR(LX("enqueueRequestFailed").d("reason", "nullRequest")); @@ -638,7 +638,7 @@ void HTTP2Transport::enqueueRequest(std::shared_ptr lock(m_mutex); @@ -705,7 +705,7 @@ HTTP2Transport::State HTTP2Transport::sendMessagesAndPings(alexaClientSDK::acl:: lock.lock(); } else { - ACSDK_DEBUG5(LX("m_pingHandler != nullptr")); + ACSDK_DEBUG7(LX("m_pingHandler != nullptr")); } } } @@ -722,7 +722,7 @@ bool HTTP2Transport::setStateLocked(State newState, ConnectionStatusObserverInte ACSDK_DEBUG5(LX(__func__).d("newState", newState).d("changedReason", changedReason)); if (newState == m_state) { - ACSDK_DEBUG5(LX("alreadyInNewState")); + ACSDK_DEBUG7(LX("alreadyInNewState")); return true; } @@ -787,7 +787,7 @@ bool HTTP2Transport::setStateLocked(State newState, ConnectionStatusObserverInte } void HTTP2Transport::notifyObserversOnConnected() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG7(LX(__func__)); std::unique_lock lock{m_observerMutex}; auto observers = m_observers; @@ -799,7 +799,7 @@ void HTTP2Transport::notifyObserversOnConnected() { } void HTTP2Transport::notifyObserversOnDisconnect(ConnectionStatusObserverInterface::ChangedReason reason) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG7(LX(__func__)); if (m_postConnect) { m_postConnect->onDisconnect(); @@ -816,7 +816,7 @@ void HTTP2Transport::notifyObserversOnDisconnect(ConnectionStatusObserverInterfa } void HTTP2Transport::notifyObserversOnServerSideDisconnect() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG7(LX(__func__)); if (m_postConnect) { m_postConnect->onDisconnect(); diff --git a/ACL/src/Transport/MessageRequestHandler.cpp b/ACL/src/Transport/MessageRequestHandler.cpp index 49b9ff8fe1..12d35e55d5 100644 --- a/ACL/src/Transport/MessageRequestHandler.cpp +++ b/ACL/src/Transport/MessageRequestHandler.cpp @@ -136,11 +136,11 @@ MessageRequestHandler::MessageRequestHandler( m_wasMessageRequestAcknowledgeReported{false}, m_wasMessageRequestFinishedReported{false}, m_responseCode{0} { - ACSDK_DEBUG5(LX(__func__).d("context", context.get()).d("messageRequest", messageRequest.get())); + ACSDK_DEBUG7(LX(__func__).d("context", context.get()).d("messageRequest", messageRequest.get())); } void MessageRequestHandler::reportMessageRequestAcknowledged() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG7(LX(__func__)); if (!m_wasMessageRequestAcknowledgeReported) { m_wasMessageRequestAcknowledgeReported = true; m_context->onMessageRequestAcknowledged(); @@ -148,7 +148,7 @@ void MessageRequestHandler::reportMessageRequestAcknowledged() { } void MessageRequestHandler::reportMessageRequestFinished() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG7(LX(__func__)); if (!m_wasMessageRequestFinishedReported) { m_wasMessageRequestFinishedReported = true; m_context->onMessageRequestFinished(); @@ -156,7 +156,7 @@ void MessageRequestHandler::reportMessageRequestFinished() { } std::vector MessageRequestHandler::getRequestHeaderLines() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG9(LX(__func__)); m_context->onActivity(); @@ -164,7 +164,7 @@ std::vector MessageRequestHandler::getRequestHeaderLines() { } HTTP2GetMimeHeadersResult MessageRequestHandler::getMimePartHeaderLines() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG9(LX(__func__)); m_context->onActivity(); @@ -186,7 +186,7 @@ HTTP2GetMimeHeadersResult MessageRequestHandler::getMimePartHeaderLines() { } HTTP2SendDataResult MessageRequestHandler::onSendMimePartData(char* bytes, size_t size) { - ACSDK_DEBUG5(LX(__func__).d("size", size)); + ACSDK_DEBUG9(LX(__func__).d("size", size)); m_context->onActivity(); @@ -204,7 +204,7 @@ HTTP2SendDataResult MessageRequestHandler::onSendMimePartData(char* bytes, size_ } else if (m_namedReader) { auto readStatus = AttachmentReader::ReadStatus::OK; auto bytesRead = m_namedReader->reader->read(bytes, size, &readStatus); - ACSDK_DEBUG5(LX("attachmentRead").d("readStatus", (int)readStatus).d("bytesRead", bytesRead)); + ACSDK_DEBUG9(LX("attachmentRead").d("readStatus", (int)readStatus).d("bytesRead", bytesRead)); switch (readStatus) { // The good cases. case AttachmentReader::ReadStatus::OK: @@ -241,7 +241,7 @@ void MessageRequestHandler::onActivity() { } bool MessageRequestHandler::onReceiveResponseCode(long responseCode) { - ACSDK_DEBUG5(LX(__func__).d("responseCode", responseCode)); + ACSDK_DEBUG7(LX(__func__).d("responseCode", responseCode)); // TODO ACSDK-1839: Provide MessageRequestObserverInterface immediate notification of receipt of response code. @@ -256,7 +256,7 @@ bool MessageRequestHandler::onReceiveResponseCode(long responseCode) { } void MessageRequestHandler::onResponseFinished(HTTP2ResponseFinishedStatus status, const std::string& nonMimeBody) { - ACSDK_DEBUG5(LX(__func__).d("status", status).d("responseCode", m_responseCode)); + ACSDK_DEBUG7(LX(__func__).d("status", status).d("responseCode", m_responseCode)); if (HTTP2ResponseFinishedStatus::TIMEOUT == status) { m_context->onMessageRequestTimeout(); diff --git a/ACL/src/Transport/MessageRouter.cpp b/ACL/src/Transport/MessageRouter.cpp index 05eee03547..ce9280a44c 100644 --- a/ACL/src/Transport/MessageRouter.cpp +++ b/ACL/src/Transport/MessageRouter.cpp @@ -120,6 +120,11 @@ void MessageRouter::setAVSEndpoint(const std::string& avsEndpoint) { } } +std::string MessageRouter::getAVSEndpoint() { + std::lock_guard lock{m_connectionMutex}; + return m_avsEndpoint; +} + void MessageRouter::onConnected(std::shared_ptr transport) { std::unique_lock lock{m_connectionMutex}; diff --git a/ACL/src/Transport/MimeResponseSink.cpp b/ACL/src/Transport/MimeResponseSink.cpp index 0ef9bcdd05..242dbaaba4 100644 --- a/ACL/src/Transport/MimeResponseSink.cpp +++ b/ACL/src/Transport/MimeResponseSink.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -88,11 +88,11 @@ MimeResponseSink::MimeResponseSink( m_messageConsumer{messageConsumer}, m_attachmentManager{attachmentManager}, m_attachmentContextId{std::move(attachmentContextId)} { - ACSDK_DEBUG5(LX(__func__).d("handler", handler.get())); + ACSDK_DEBUG9(LX(__func__).d("handler", handler.get())); } bool MimeResponseSink::onReceiveResponseCode(long responseCode) { - ACSDK_DEBUG5(LX(__func__).d("responseCode", responseCode)); + ACSDK_DEBUG9(LX(__func__).d("responseCode", responseCode)); if (m_handler) { m_handler->onActivity(); @@ -102,7 +102,7 @@ bool MimeResponseSink::onReceiveResponseCode(long responseCode) { } bool MimeResponseSink::onReceiveHeaderLine(const std::string& line) { - ACSDK_DEBUG5(LX(__func__).d("line", line)); + ACSDK_DEBUG9(LX(__func__).d("line", line)); if (m_handler) { m_handler->onActivity(); @@ -118,7 +118,7 @@ bool MimeResponseSink::onReceiveHeaderLine(const std::string& line) { } bool MimeResponseSink::onBeginMimePart(const std::multimap& headers) { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG9(LX(__func__)); if (m_handler) { m_handler->onActivity(); @@ -158,7 +158,7 @@ bool MimeResponseSink::onBeginMimePart(const std::multimaponActivity(); @@ -177,7 +177,7 @@ HTTP2ReceiveDataStatus MimeResponseSink::onReceiveMimeData(const char* bytes, si } bool MimeResponseSink::onEndMimePart() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG9(LX(__func__)); if (m_handler) { m_handler->onActivity(); @@ -208,7 +208,7 @@ bool MimeResponseSink::onEndMimePart() { } HTTP2ReceiveDataStatus MimeResponseSink::onReceiveNonMimeData(const char* bytes, size_t size) { - ACSDK_DEBUG5(LX(__func__).d("size", size)); + ACSDK_DEBUG9(LX(__func__).d("size", size)); if (m_handler) { m_handler->onActivity(); @@ -228,7 +228,7 @@ HTTP2ReceiveDataStatus MimeResponseSink::onReceiveNonMimeData(const char* bytes, } void MimeResponseSink::onResponseFinished(HTTP2ResponseFinishedStatus status) { - ACSDK_DEBUG5(LX(__func__).d("status", status)); + ACSDK_DEBUG9(LX(__func__).d("status", status)); if (m_handler) { m_handler->onResponseFinished(status, m_nonMimeBody); diff --git a/ACL/test/AVSConnectionManagerTest.cpp b/ACL/test/AVSConnectionManagerTest.cpp index 6a4ba6b0aa..7a89874db3 100644 --- a/ACL/test/AVSConnectionManagerTest.cpp +++ b/ACL/test/AVSConnectionManagerTest.cpp @@ -65,6 +65,7 @@ class MockMessageRouter : public MessageRouterInterface { MOCK_METHOD0(getConnectionStatus, MessageRouterInterface::ConnectionStatus()); MOCK_METHOD1(sendMessage, void(std::shared_ptr request)); MOCK_METHOD1(setAVSEndpoint, void(const std::string& avsEndpoint)); + MOCK_METHOD0(getAVSEndpoint, std::string()); MOCK_METHOD1(setObserver, void(std::shared_ptr observer)); }; @@ -229,6 +230,15 @@ TEST_F(AVSConnectionManagerTest, test_setAVSEndpoint) { m_avsConnectionManager->setAVSEndpoint("AVSEndpoint"); } +/** + * Test getAVSEndpoint and expect a call to messageRouter's getAVSEndpoint. + */ +TEST_F(AVSConnectionManagerTest, getAVSEndpointTest) { + auto endpoint = "AVSEndpoint"; + EXPECT_CALL(*m_messageRouter, getAVSEndpoint()).Times(1).WillOnce(Return(endpoint)); + ASSERT_EQ(endpoint, m_avsConnectionManager->getAVSEndpoint()); +} + /** * Test that onConnectionStatusChanged(false) results in a reconnect attempt when enabled. */ diff --git a/ACL/test/Transport/MessageRouterTest.cpp b/ACL/test/Transport/MessageRouterTest.cpp index 0794b9a948..32c3c5f27a 100644 --- a/ACL/test/Transport/MessageRouterTest.cpp +++ b/ACL/test/Transport/MessageRouterTest.cpp @@ -261,6 +261,12 @@ TEST_F(MessageRouterTest, test_onConnectedOnInactiveTransport) { ASSERT_FALSE(m_mockMessageRouterObserver->wasNotifiedOfStatusChange()); } +TEST_F(MessageRouterTest, setAndGetAVSEndpoint) { + auto endpoint = "Endpoint"; + m_router->setAVSEndpoint(endpoint); + ASSERT_EQ(endpoint, m_router->getAVSEndpoint()); +} + } // namespace test } // namespace acl } // namespace alexaClientSDK diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/ExternalMediaPlayer/AdapterUtils.h b/AVSCommon/AVS/include/AVSCommon/AVS/ExternalMediaPlayer/AdapterUtils.h index cd5672e952..d16c2c95b5 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/ExternalMediaPlayer/AdapterUtils.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/ExternalMediaPlayer/AdapterUtils.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -66,15 +66,15 @@ extern const avsCommon::avs::NamespaceAndName PLAYER_EVENT; extern const avsCommon::avs::NamespaceAndName PLAYER_ERROR_EVENT; /** - * Method to iterate over a vector of supported operation in playback state and convert to JSON. + * Method to iterate over a collection of supported operation in playback state and convert to JSON. * - * @param supportedOperations The array of supported operations from the current playback state. + * @param supportedOperations The collection of supported operations from the current playback state. * @param allocator The rapidjson allocator, required for the results of this function to be mergable with other * rapidjson::Value objects. * @return The rapidjson::Value representing the array. */ rapidjson::Value buildSupportedOperations( - const std::vector& supportedOperations, + const std::set& supportedOperations, rapidjson::Document::AllocatorType& allocator); /** diff --git a/AVSCommon/AVS/include/AVSCommon/AVS/PlayerActivity.h b/AVSCommon/AVS/include/AVSCommon/AVS/PlayerActivity.h index 61fb63bc35..79d2c3d453 100644 --- a/AVSCommon/AVS/include/AVSCommon/AVS/PlayerActivity.h +++ b/AVSCommon/AVS/include/AVSCommon/AVS/PlayerActivity.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_PLAYERACTIVITY_H_ #define ALEXA_CLIENT_SDK_AVSCOMMON_AVS_INCLUDE_AVSCOMMON_AVS_PLAYERACTIVITY_H_ +#include #include namespace alexaClientSDK { @@ -81,6 +82,34 @@ inline std::ostream& operator<<(std::ostream& stream, const PlayerActivity& play return stream << playerActivityToString(playerActivity); } +/** + * Converts an input string stream value to @c PlayerActivity. + * + * @param is The string stream to retrieve the value from. + * @param [out] value The value to write to. + * @return The stream that was passed in. + */ +inline std::istream& operator>>(std::istream& is, PlayerActivity& value) { + std::string str; + is >> str; + if ("IDLE" == str) { + value = PlayerActivity::IDLE; + } else if ("PLAYING" == str) { + value = PlayerActivity::PLAYING; + } else if ("STOPPED" == str) { + value = PlayerActivity::STOPPED; + } else if ("PAUSED" == str) { + value = PlayerActivity::PAUSED; + } else if ("BUFFER_UNDERRUN" == str) { + value = PlayerActivity::BUFFER_UNDERRUN; + } else if ("FINISHED" == str) { + value = PlayerActivity::FINISHED; + } else { + is.setstate(std::ios_base::failbit); + } + return is; +} + } // namespace avs } // namespace avsCommon } // namespace alexaClientSDK diff --git a/AVSCommon/AVS/src/ExternalMediaPlayer/AdapterUtils.cpp b/AVSCommon/AVS/src/ExternalMediaPlayer/AdapterUtils.cpp index 169dda8ce3..7c2348b935 100644 --- a/AVSCommon/AVS/src/ExternalMediaPlayer/AdapterUtils.cpp +++ b/AVSCommon/AVS/src/ExternalMediaPlayer/AdapterUtils.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -56,7 +56,7 @@ const NamespaceAndName PLAYER_ERROR_EVENT("ExternalMediaPlayer", "PlayerError"); const char DEFAULT_STATE[] = "IDLE"; rapidjson::Value buildSupportedOperations( - const std::vector& supportedOperations, + const std::set& supportedOperations, rapidjson::Document::AllocatorType& allocator) { rapidjson::Value opArray(rapidjson::kArrayType); diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioPlayerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioPlayerInterface.h index 672d3b637b..73325ab50a 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioPlayerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AudioPlayerInterface.h @@ -49,14 +49,6 @@ class AudioPlayerInterface { * @param observer The @c AudioPlayerObserverInterface */ virtual void removeObserver(std::shared_ptr observer) = 0; - - /** - * This function retrieves the offset of the current AudioItem the @c AudioPlayer is handling. - * @note This function is blocking. - * - * @return This returns the offset in millisecond. - */ - virtual std::chrono::milliseconds getAudioItemOffset() = 0; }; } // namespace sdkInterfaces diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AuthObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AuthObserverInterface.h index 834a9b903d..8a6884febe 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AuthObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/AuthObserverInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2016-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -101,7 +101,7 @@ class AuthObserverInterface { inline std::ostream& operator<<(std::ostream& stream, const AuthObserverInterface::State& state) { switch (state) { case AuthObserverInterface::State::UNINITIALIZED: - return stream << "UNINTIALIZED"; + return stream << "UNINITIALIZED"; case AuthObserverInterface::State::REFRESHED: return stream << "REFRESHED"; case AuthObserverInterface::State::EXPIRED: diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesObserverInterface.h index 59e0ab165a..b38386e0ac 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/CapabilitiesObserverInterface.h @@ -82,7 +82,7 @@ class CapabilitiesObserverInterface { inline std::ostream& operator<<(std::ostream& stream, const CapabilitiesObserverInterface::State& state) { switch (state) { case CapabilitiesObserverInterface::State::UNINITIALIZED: - return stream << "UNINTIALIZED"; + return stream << "UNINITIALIZED"; case CapabilitiesObserverInterface::State::SUCCESS: return stream << "SUCCESS"; case CapabilitiesObserverInterface::State::FATAL_ERROR: @@ -103,7 +103,7 @@ inline std::ostream& operator<<(std::ostream& stream, const CapabilitiesObserver inline std::ostream& operator<<(std::ostream& stream, const CapabilitiesObserverInterface::Error& error) { switch (error) { case CapabilitiesObserverInterface::Error::UNINITIALIZED: - return stream << "UNINTIALIZED"; + return stream << "UNINITIALIZED"; case CapabilitiesObserverInterface::Error::SUCCESS: return stream << "SUCCESS"; case CapabilitiesObserverInterface::Error::UNKNOWN_ERROR: diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h index 152a62683c..d100866c69 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaAdapterInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ #include "AVSCommon/Utils/RequiresShutdown.h" #include +#include #include -#include namespace alexaClientSDK { namespace avsCommon { @@ -291,7 +291,7 @@ struct AdapterPlaybackState { std::string state; /// The set of states the default player can move into from its current state. - std::vector supportedOperations; + std::set supportedOperations; /// The offset of the track in milliseconds. std::chrono::milliseconds trackOffset; @@ -457,6 +457,13 @@ class ExternalMediaAdapterInterface : public avsCommon::utils::RequiresShutdown /// Method to fetch the state(session state and playback state) of an adapter. virtual AdapterState getState() = 0; + + /** + * This function retrieves the offset of the current track the adapter is handling. + * + * @return This returns the offset in milliseconds. + */ + virtual std::chrono::milliseconds getOffset() = 0; }; inline AdapterSessionState::AdapterSessionState() : loggedIn{false}, isGuest{false}, launched{false}, active{false} { diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaPlayerInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaPlayerInterface.h index 4b676228ce..862df746e7 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaPlayerInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/ExternalMediaPlayerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -43,6 +43,7 @@ class ExternalMediaPlayerInterface { * Method to set the player in focus after an adapter has acquired the channel. * * @param playerInFocus The business name of the adapter that has currently acquired focus. + * @note This function should not be called during the callback in @c ExternalMediaAdapterInterface. */ virtual void setPlayerInFocus(const std::string& playerInFocus) = 0; }; diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/MediaPropertiesInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/MediaPropertiesInterface.h new file mode 100644 index 0000000000..9e7837e9ae --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/MediaPropertiesInterface.h @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_MEDIAPROPERTIESINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_MEDIAPROPERTIESINTERFACE_H_ + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * This class provides an interface to query the properties of a media from a player. + */ +class MediaPropertiesInterface { +public: + /** + * Destructor + */ + virtual ~MediaPropertiesInterface() = default; + + /** + * This function retrieves the offset of the current AudioItem the player is handling. + * + * @return This returns the offset in milliseconds. + */ + virtual std::chrono::milliseconds getAudioItemOffset() = 0; +}; + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_MEDIAPROPERTIESINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RenderPlayerInfoCardsObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RenderPlayerInfoCardsObserverInterface.h new file mode 100644 index 0000000000..76c322a2de --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RenderPlayerInfoCardsObserverInterface.h @@ -0,0 +1,70 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_RENDERPLAYERINFOCARDSOBSERVERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_RENDERPLAYERINFOCARDSOBSERVERINTERFACE_H_ + +#include +#include +#include + +#include +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * This class allows any @c RenderPlayerInfoCardsObserverInterface observers to be notified of changes in the @c Context + * of RenderPlayerInfoCards. + */ +class RenderPlayerInfoCardsObserverInterface { +public: + /** + * Destructor + */ + virtual ~RenderPlayerInfoCardsObserverInterface() = default; + + /// The context of the RenderPlayerInfoCards from the RenderPlayerInfoCards provider. + struct Context { + /// The @c AudioItem ID the RenderPlayerInfoCards provider is handling. + std::string audioItemId; + + /// The offset in millisecond from the start of the @c AudioItem. + std::chrono::milliseconds offset; + + /** + * The @c MediaPropertiesInterface so that the observer can retrieve the live offset. + * + * @note Observer shall not call methods of the interface during the onRenderPlayerCardsInfoChanged callback. + */ + std::shared_ptr mediaProperties; + }; + + /** + * Used to notify the observer when there is a change in @c PlayerActivity or @c Context. + * + * @param state The @c PlayerActivity of the Player. + * @param context The @c Context of the Player. + */ + virtual void onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity state, const Context& context) = 0; +}; + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_RENDERPLAYERINFOCARDSOBSERVERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RenderPlayerInfoCardsProviderInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RenderPlayerInfoCardsProviderInterface.h new file mode 100644 index 0000000000..10ff6a8ebe --- /dev/null +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/RenderPlayerInfoCardsProviderInterface.h @@ -0,0 +1,51 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#ifndef ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_RENDERPLAYERINFOCARDSPROVIDERINTERFACE_H_ +#define ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_RENDERPLAYERINFOCARDSPROVIDERINTERFACE_H_ + +#include + +#include + +namespace alexaClientSDK { +namespace avsCommon { +namespace sdkInterfaces { + +/** + * This class provides an interface to set the provider of the @c RenderPlayerInfoCardsObserverInterface. + */ +class RenderPlayerInfoCardsProviderInterface { +public: + /** + * Destructor + */ + virtual ~RenderPlayerInfoCardsProviderInterface() = default; + + /** + * This function sets an @c RenderPlayerInfoCardsObserverInterface so that it will get notified for + * RenderPlayerInfoCards state changes. This implies that there can be one or no observer at a given time. + * + * @param observer The @c RenderPlayerInfoCardsObserverInterface + */ + virtual void setObserver( + std::shared_ptr observer) = 0; +}; + +} // namespace sdkInterfaces +} // namespace avsCommon +} // namespace alexaClientSDK + +#endif // ALEXA_CLIENT_SDK_AVSCOMMON_SDKINTERFACES_INCLUDE_AVSCOMMON_SDKINTERFACES_RENDERPLAYERINFOCARDSPROVIDERINTERFACE_H_ diff --git a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/TemplateRuntimeObserverInterface.h b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/TemplateRuntimeObserverInterface.h index 921deed045..228ec22e8a 100644 --- a/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/TemplateRuntimeObserverInterface.h +++ b/AVSCommon/SDKInterfaces/include/AVSCommon/SDKInterfaces/TemplateRuntimeObserverInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -38,6 +38,13 @@ class TemplateRuntimeObserverInterface { * @c AudioPlayerInfo is passed to the observers as a parameter in the @c renderPlayerInfoCard callback. */ struct AudioPlayerInfo { + /** + * Default constructor. + */ + AudioPlayerInfo() : + audioPlayerState{avsCommon::avs::PlayerActivity::IDLE}, + offset{std::chrono::milliseconds::zero()} {}; + /** * The state of the @c AudioPlayer. This information is useful for implementing the progress bar * in the display card. It is assumed that the client is responsible for progressing the progress bar diff --git a/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h b/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h index 4265b136ae..842e09f473 100644 --- a/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h +++ b/AVSCommon/Utils/include/AVSCommon/Utils/SDKVersion.h @@ -30,7 +30,7 @@ namespace utils { namespace sdkVersion{ inline static std::string getCurrentVersion(){ - return "1.13.0"; + return "1.14.0"; } inline static int getMajorVersion(){ @@ -38,7 +38,7 @@ inline static int getMajorVersion(){ } inline static int getMinorVersion(){ - return 13; + return 14; } inline static int getPatchVersion(){ diff --git a/AVSCommon/Utils/src/HTTP2/HTTP2MimeRequestEncoder.cpp b/AVSCommon/Utils/src/HTTP2/HTTP2MimeRequestEncoder.cpp index 2674dbb375..6c38426515 100644 --- a/AVSCommon/Utils/src/HTTP2/HTTP2MimeRequestEncoder.cpp +++ b/AVSCommon/Utils/src/HTTP2/HTTP2MimeRequestEncoder.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -82,11 +82,11 @@ HTTP2MimeRequestEncoder::HTTP2MimeRequestEncoder( m_source{source}, m_getMimeHeaderLinesResult{HTTP2GetMimeHeadersResult::ABORT}, m_stringIndex{0} { - ACSDK_DEBUG5(LX(__func__).d("boundary", boundary).d("source", source.get())); + ACSDK_DEBUG9(LX(__func__).d("boundary", boundary).d("source", source.get())); } HTTP2SendDataResult HTTP2MimeRequestEncoder::onSendData(char* bytes, size_t size) { - ACSDK_DEBUG5(LX(__func__).d("size", size).d("state", m_state)); + ACSDK_DEBUG9(LX(__func__).d("size", size).d("state", m_state)); if (!m_source) { return HTTP2SendDataResult::COMPLETE; @@ -240,7 +240,7 @@ HTTP2SendDataResult HTTP2MimeRequestEncoder::onSendData(char* bytes, size_t size } std::vector HTTP2MimeRequestEncoder::getRequestHeaderLines() { - ACSDK_DEBUG5(LX(__func__)); + ACSDK_DEBUG9(LX(__func__)); if (m_source) { auto lines = m_source->getRequestHeaderLines(); lines.push_back(BOUNDARY_HEADER_PREFIX + m_rawBoundary); diff --git a/AVSCommon/Utils/src/HTTP2/HTTP2MimeResponseDecoder.cpp b/AVSCommon/Utils/src/HTTP2/HTTP2MimeResponseDecoder.cpp index 7cdfc1bef7..9ee04e8189 100644 --- a/AVSCommon/Utils/src/HTTP2/HTTP2MimeResponseDecoder.cpp +++ b/AVSCommon/Utils/src/HTTP2/HTTP2MimeResponseDecoder.cpp @@ -63,11 +63,11 @@ HTTP2MimeResponseDecoder::HTTP2MimeResponseDecoder(std::shared_ptr #endif -#ifdef ENABLE_MRM +#if defined(ENABLE_MRM) && defined(ENABLE_MRM_STANDALONE_APP) +#include +#elif ENABLE_MRM #include #endif @@ -49,8 +51,8 @@ #include #ifdef BLUETOOTH_BLUEZ -#include #include +#include #endif namespace alexaClientSDK { @@ -306,15 +308,18 @@ bool DefaultClient::initialize( } /* - * Creating the Attachment Manager - This component deals with managing attachments and allows for readers and + * Creating the Attachment Manager - This component deals with managing + * attachments and allows for readers and * writers to be created to handle the attachment. */ auto attachmentManager = std::make_shared( avsCommon::avs::attachment::AttachmentManager::AttachmentType::IN_PROCESS); /* - * Creating the message router - This component actually maintains the connection to AVS over HTTP2. It is created - * using the auth delegate, which provides authorization to connect to AVS, and the attachment manager, which helps + * Creating the message router - This component actually maintains the + * connection to AVS over HTTP2. It is created + * using the auth delegate, which provides authorization to connect to AVS, + * and the attachment manager, which helps * ACL write attachments received from AVS. */ m_messageRouter = std::make_shared(authDelegate, attachmentManager, transportFactory); @@ -326,7 +331,8 @@ bool DefaultClient::initialize( m_internetConnectionMonitor = internetConnectionMonitor; /* - * Creating the connection manager - This component is the overarching connection manager that glues together all + * Creating the connection manager - This component is the overarching + * connection manager that glues together all * the other networking components into one easy-to-use component. */ m_connectionManager = acl::AVSConnectionManager::create( @@ -337,9 +343,12 @@ bool DefaultClient::initialize( } /* - * Creating our certified sender - this component guarantees that messages given to it (expected to be JSON - * formatted AVS Events) will be sent to AVS. This nicely decouples strict message sending from components which - * require an Event be sent, even in conditions when there is no active AVS connection. + * Creating our certified sender - this component guarantees that messages + * given to it (expected to be JSON + * formatted AVS Events) will be sent to AVS. This nicely decouples strict + * message sending from components which + * require an Event be sent, even in conditions when there is no active AVS + * connection. */ m_certifiedSender = certifiedSender::CertifiedSender::create( m_connectionManager, m_connectionManager, messageStorage, customerDataManager); @@ -349,8 +358,10 @@ bool DefaultClient::initialize( } /* - * Creating the Exception Sender - This component helps the SDK send exceptions when it is unable to handle a - * directive sent by AVS. For that reason, the Directive Sequencer and each Capability Agent will need this + * Creating the Exception Sender - This component helps the SDK send + * exceptions when it is unable to handle a + * directive sent by AVS. For that reason, the Directive Sequencer and each + * Capability Agent will need this * component. */ m_exceptionSender = avsCommon::avs::ExceptionEncounteredSender::create(m_connectionManager); @@ -360,8 +371,10 @@ bool DefaultClient::initialize( } /* - * Creating the Directive Sequencer - This is the component that deals with the sequencing and ordering of - * directives sent from AVS and forwarding them along to the appropriate Capability Agent that deals with + * Creating the Directive Sequencer - This is the component that deals with + * the sequencing and ordering of + * directives sent from AVS and forwarding them along to the appropriate + * Capability Agent that deals with * directives in that Namespace/Name. */ m_directiveSequencer = adsl::DirectiveSequencer::create(m_exceptionSender); @@ -371,8 +384,10 @@ bool DefaultClient::initialize( } /* - * Creating the Message Interpreter - This component takes care of converting ACL messages to Directives for the - * Directive Sequencer to process. This essentially "glues" together the ACL and ADSL. + * Creating the Message Interpreter - This component takes care of converting + * ACL messages to Directives for the + * Directive Sequencer to process. This essentially "glues" together the ACL + * and ADSL. */ auto messageInterpreter = std::make_shared(m_exceptionSender, m_directiveSequencer, attachmentManager); @@ -380,29 +395,36 @@ bool DefaultClient::initialize( m_connectionManager->addMessageObserver(messageInterpreter); /* - * Creating the Registration Manager - This component is responsible for implementing any customer registration + * Creating the Registration Manager - This component is responsible for + * implementing any customer registration * operation such as login and logout */ m_registrationManager = std::make_shared( m_directiveSequencer, m_connectionManager, customerDataManager); /* - * Creating the Audio Activity Tracker - This component is responsibly for reporting the audio channel focus + * Creating the Audio Activity Tracker - This component is responsibly for + * reporting the audio channel focus * information to AVS. */ m_audioActivityTracker = afml::AudioActivityTracker::create(contextManager); /* - * Creating the Focus Manager - This component deals with the management of layered audio focus across various - * components. It handles granting access to Channels as well as pushing different "Channels" to foreground, - * background, or no focus based on which other Channels are active and the priorities of those Channels. Each - * Capability Agent will require the Focus Manager in order to request access to the Channel it wishes to play on. + * Creating the Focus Manager - This component deals with the management of + * layered audio focus across various + * components. It handles granting access to Channels as well as pushing + * different "Channels" to foreground, + * background, or no focus based on which other Channels are active and the + * priorities of those Channels. Each + * Capability Agent will require the Focus Manager in order to request access + * to the Channel it wishes to play on. */ m_audioFocusManager = std::make_shared(afml::FocusManager::getDefaultAudioChannels(), m_audioActivityTracker); /* - * Creating the User Inactivity Monitor - This component is responsibly for updating AVS of user inactivity as + * Creating the User Inactivity Monitor - This component is responsibly for + * updating AVS of user inactivity as * described in the System Interface of AVS. */ m_userInactivityMonitor = @@ -413,7 +435,8 @@ bool DefaultClient::initialize( } /* - * Creating the Audio Input Processor - This component is the Capability Agent that implements the + * Creating the Audio Input Processor - This component is the Capability Agent + * that implements the * SpeechRecognizer interface of AVS. */ #ifdef ENABLE_OPUS @@ -445,7 +468,8 @@ bool DefaultClient::initialize( m_audioInputProcessor->addObserver(m_dialogUXStateAggregator); /* - * Creating the Speech Synthesizer - This component is the Capability Agent that implements the SpeechSynthesizer + * Creating the Speech Synthesizer - This component is the Capability Agent + * that implements the SpeechSynthesizer * interface of AVS. */ m_speechSynthesizer = capabilityAgents::speechSynthesizer::SpeechSynthesizer::create( @@ -463,7 +487,8 @@ bool DefaultClient::initialize( m_speechSynthesizer->addObserver(m_dialogUXStateAggregator); /* - * Creating the PlaybackController Capability Agent - This component is the Capability Agent that implements the + * Creating the PlaybackController Capability Agent - This component is the + * Capability Agent that implements the * PlaybackController interface of AVS. */ m_playbackController = @@ -474,7 +499,8 @@ bool DefaultClient::initialize( } /* - * Creating the PlaybackRouter - This component routes a playback button press to the active handler. + * Creating the PlaybackRouter - This component routes a playback button press + * to the active handler. * The default handler is @c PlaybackController. */ m_playbackRouter = capabilityAgents::playbackController::PlaybackRouter::create(m_playbackController); @@ -484,7 +510,8 @@ bool DefaultClient::initialize( } /* - * Creating the Audio Player - This component is the Capability Agent that implements the AudioPlayer + * Creating the Audio Player - This component is the Capability Agent that + * implements the AudioPlayer * interface of AVS. */ m_audioPlayer = capabilityAgents::audioPlayer::AudioPlayer::create( @@ -513,7 +540,8 @@ bool DefaultClient::initialize( allSpeakers.insert(allSpeakers.end(), additionalSpeakers.begin(), additionalSpeakers.end()); /* - * Creating the SpeakerManager Capability Agent - This component is the Capability Agent that implements the + * Creating the SpeakerManager Capability Agent - This component is the + * Capability Agent that implements the * Speaker interface of AVS. */ m_speakerManager = capabilityAgents::speakerManager::SpeakerManager::create( @@ -524,7 +552,8 @@ bool DefaultClient::initialize( } /* - * Creating the Alerts Capability Agent - This component is the Capability Agent that implements the Alerts + * Creating the Alerts Capability Agent - This component is the Capability + * Agent that implements the Alerts * interface of AVS. */ m_alertsCapabilityAgent = capabilityAgents::alerts::AlertsCapabilityAgent::create( @@ -553,7 +582,8 @@ bool DefaultClient::initialize( } /* - * Creating the Notifications Capability Agent - This component is the Capability Agent that implements the + * Creating the Notifications Capability Agent - This component is the + * Capability Agent that implements the * Notifications interface of AVS. */ m_notificationsCapabilityAgent = capabilityAgents::notifications::NotificationsCapabilityAgent::create( @@ -577,7 +607,8 @@ bool DefaultClient::initialize( #ifdef ENABLE_PCC /* - * Creating the PhoneCallController - This component is the Capability Agent that implements the + * Creating the PhoneCallController - This component is the Capability Agent + * that implements the * PhoneCallController interface of AVS */ m_phoneCallControllerCapabilityAgent = capabilityAgents::phoneCallController::PhoneCallController::create( @@ -598,7 +629,6 @@ bool DefaultClient::initialize( if (!capabilityAgents::callManager::CallManager::create( sipUserAgent, ringtoneMediaPlayer, - ringtoneSpeaker, m_connectionManager, contextManager, m_audioFocusManager, @@ -634,7 +664,8 @@ bool DefaultClient::initialize( } /* - * Creating the Setting object - This component implements the Setting interface of AVS. + * Creating the Setting object - This component implements the Setting + * interface of AVS. */ m_settings = capabilityAgents::settings::Settings::create( settingsStorage, {settingsUpdatedEventSender}, customerDataManager); @@ -645,7 +676,8 @@ bool DefaultClient::initialize( } /* - * Creating the ExternalMediaPlayer CA - This component is the Capability Agent that implements the + * Creating the ExternalMediaPlayer CA - This component is the Capability + * Agent that implements the * ExternalMediaPlayer interface of AVS. */ m_externalMediaPlayer = capabilityAgents::externalMediaPlayer::ExternalMediaPlayer::create( @@ -663,41 +695,21 @@ bool DefaultClient::initialize( return false; } - if (isGuiSupported) { - /* - * Creating the Visual Activity Tracker - This component is responsibly for reporting the visual channel focus - * information to AVS. - */ - m_visualActivityTracker = afml::VisualActivityTracker::create(contextManager); - +#ifdef ENABLE_MRM /* - * Creating the Visual Focus Manager - This component deals with the management of visual focus across various - * components. It handles granting access to Channels as well as pushing different "Channels" to foreground, - * background, or no focus based on which other Channels are active and the priorities of those Channels. Each - * Capability Agent will require the Focus Manager in order to request access to the Channel it wishes to play - * on. + * Creating the MRM (Multi-Room-Music) Capability Agent. */ - m_visualFocusManager = std::make_shared( - afml::FocusManager::getDefaultVisualChannels(), m_visualActivityTracker); - - /* - * Creating the TemplateRuntime Capability Agent - This component is the Capability Agent that implements the - * TemplateRuntime interface of AVS. - */ - m_templateRuntime = capabilityAgents::templateRuntime::TemplateRuntime::create( - m_audioPlayer, m_visualFocusManager, m_exceptionSender); - if (!m_templateRuntime) { - ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToCreateTemplateRuntimeCapabilityAgent")); - return false; - } - m_dialogUXStateAggregator->addObserver(m_templateRuntime); - } - -#ifdef ENABLE_MRM - /* - * Creating the MRM (Multi-Room-Music) Capability Agent. - */ +#ifdef ENABLE_MRM_STANDALONE_APP + auto mrmHandler = capabilityAgents::mrm::mrmHandler::MRMHandlerProxy::create( + m_connectionManager, + m_connectionManager, + m_directiveSequencer, + m_userInactivityMonitor, + contextManager, + m_audioFocusManager, + m_speakerManager); +#else auto mrmHandler = capabilityAgents::mrm::mrmHandler::MRMHandler::create( m_connectionManager, m_connectionManager, @@ -707,6 +719,7 @@ bool DefaultClient::initialize( m_audioFocusManager, m_speakerManager, deviceInfo->getDeviceSerialNumber()); +#endif // ENABLE_MRM_STANDALONE_APP if (!mrmHandler) { ACSDK_ERROR(LX("initializeFailed").d("reason", "Unable to create mrmHandler.")); @@ -721,8 +734,57 @@ bool DefaultClient::initialize( return false; } + /// Reminder that this needs to be called after m_callManager is set up or + /// or it won't do anything + /// MRM cares about the comms state because it needs to: + /// 1) Not start music on devices already in a call + /// 2) Stop music on a cluster if a member enters a call + addCallStateObserver(m_mrmCapabilityAgent); +#endif // ENABLE_MRM + + if (isGuiSupported) { + /* + * Creating the Visual Activity Tracker - This component is responsibly for + * reporting the visual channel focus + * information to AVS. + */ + m_visualActivityTracker = afml::VisualActivityTracker::create(contextManager); + + /* + * Creating the Visual Focus Manager - This component deals with the + * management of visual focus across various + * components. It handles granting access to Channels as well as pushing + * different "Channels" to foreground, + * background, or no focus based on which other Channels are active and the + * priorities of those Channels. Each + * Capability Agent will require the Focus Manager in order to request + * access to the Channel it wishes to play + * on. + */ + m_visualFocusManager = std::make_shared( + afml::FocusManager::getDefaultVisualChannels(), m_visualActivityTracker); + + std::unordered_set> + renderPlayerInfoCardsProviders = {m_audioPlayer, m_externalMediaPlayer}; + +#ifdef ENABLE_MRM + renderPlayerInfoCardsProviders.insert(m_mrmCapabilityAgent); #endif + /* + * Creating the TemplateRuntime Capability Agent - This component is the + * Capability Agent that implements the + * TemplateRuntime interface of AVS. + */ + m_templateRuntime = capabilityAgents::templateRuntime::TemplateRuntime::create( + renderPlayerInfoCardsProviders, m_visualFocusManager, m_exceptionSender); + if (!m_templateRuntime) { + ACSDK_ERROR(LX("initializeFailed").d("reason", "unableToCreateTemplateRuntimeCapabilityAgent")); + return false; + } + m_dialogUXStateAggregator->addObserver(m_templateRuntime); + } + /* * Creating the DoNotDisturb Capability Agent */ @@ -737,7 +799,8 @@ bool DefaultClient::initialize( addConnectionObserver(m_dndCapabilityAgent); /* - * Creating the Equalizer Capability Agent and related implementations if enabled + * Creating the Equalizer Capability Agent and related implementations if + * enabled */ m_equalizerRuntimeSetup = equalizerRuntimeSetup; @@ -780,7 +843,8 @@ bool DefaultClient::initialize( } /* - * Creating the Endpoint Handler - This component is responsible for handling directives from AVS instructing the + * Creating the Endpoint Handler - This component is responsible for handling + * directives from AVS instructing the * client to change the endpoint to connect to. */ auto endpointHandler = capabilityAgents::system::EndpointHandler::create(m_connectionManager, m_exceptionSender); @@ -790,7 +854,8 @@ bool DefaultClient::initialize( } /* - * Creating the SystemCapabilityProvider - This component is responsible for publishing information about the System + * Creating the SystemCapabilityProvider - This component is responsible for + * publishing information about the System * capability agent. */ auto systemCapabilityProvider = capabilityAgents::system::SystemCapabilityProvider::create(); @@ -801,8 +866,10 @@ bool DefaultClient::initialize( #ifdef ENABLE_REVOKE_AUTH /* - * Creating the RevokeAuthorizationHandler - This component is responsible for handling RevokeAuthorization - * directives from AVS to notify the client to clear out authorization and re-enter the registration flow. + * Creating the RevokeAuthorizationHandler - This component is responsible for + * handling RevokeAuthorization + * directives from AVS to notify the client to clear out authorization and + * re-enter the registration flow. */ m_revokeAuthorizationHandler = capabilityAgents::system::RevokeAuthorizationHandler::create(m_exceptionSender); if (!m_revokeAuthorizationHandler) { @@ -831,7 +898,8 @@ bool DefaultClient::initialize( if (bluetoothDeviceManager) { ACSDK_DEBUG5(LX(__func__).m("Creating Bluetooth CA")); - // Create a temporary pointer to the eventBus inside of bluetoothDeviceManager so that + // Create a temporary pointer to the eventBus inside of + // bluetoothDeviceManager so that // the unique ptr for bluetoothDeviceManager can be moved. auto eventBus = bluetoothDeviceManager->getEventBus(); @@ -839,7 +907,8 @@ bool DefaultClient::initialize( capabilityAgents::bluetooth::BluetoothAVRCPTransformer::create(eventBus, m_playbackRouter); /* - * Creating the Bluetooth Capability Agent - This component is responsible for handling directives from AVS + * Creating the Bluetooth Capability Agent - This component is responsible + * for handling directives from AVS * regarding bluetooth functionality. */ m_bluetooth = capabilityAgents::bluetooth::Bluetooth::create( @@ -858,7 +927,8 @@ bool DefaultClient::initialize( } /* - * The following two statements show how to register capability agents to the directive sequencer. + * The following two statements show how to register capability agents to the + * directive sequencer. */ if (!m_directiveSequencer->addDirectiveHandler(m_speechSynthesizer)) { ACSDK_ERROR(LX("initializeFailed") @@ -1170,6 +1240,10 @@ void DefaultClient::disconnect() { m_connectionManager->disable(); } +std::string DefaultClient::getAVSEndpoint() { + return m_connectionManager->getAVSEndpoint(); +} + void DefaultClient::stopForegroundActivity() { m_audioFocusManager->stopForegroundActivity(); } @@ -1562,14 +1636,15 @@ DefaultClient::~DefaultClient() { ACSDK_DEBUG5(LX("UserInactivityMonitorShutdown.")); m_userInactivityMonitor->shutdown(); } - if (m_callManager) { - ACSDK_DEBUG5(LX("CallManagerShutdown.")); - m_callManager->shutdown(); - } if (m_mrmCapabilityAgent) { ACSDK_DEBUG5(LX("MRMCapabilityAgentShutdown")); + removeCallStateObserver(m_mrmCapabilityAgent); m_mrmCapabilityAgent->shutdown(); } + if (m_callManager) { + ACSDK_DEBUG5(LX("CallManagerShutdown.")); + m_callManager->shutdown(); + } #ifdef ENABLE_PCC if (m_phoneCallControllerCapabilityAgent) { ACSDK_DEBUG5(LX("PhoneCallControllerCapabilityAgentShutdown")); diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d734fcb28..71fa6d2dc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ ## ChangeLog +### v1.14.0 released 07/09/2019: + +**Enhancements** + +* AudioPlayer can now pre-buffer audio tracks in the Pre-Handle stage. + +**Bug Fixes** + +* Fixed an issue in the SQLite wrapper code where a `SQLiteStatement` caused a memory corruption issue. +* Fixed a race condition in SpeechSynthesizer that caused crashes. +* Fixed a `cmake` issue that specifies a dependency for Bluetooth incorrectly. +* Fixed a bug that caused Bluetooth playback to start automatically. +* Changed `supportedOperations` from a vector to a set in `ExternalMediaAdapterInterface`. +* Corrected an issue where a `VolumeChanged` event had previously been sent when the volume was unchanged after `setVolume` or `adjustVolume` had been called locally. +* Fixed issue with `IterativePlaylistParser` that prevented live stations on TuneIn from playing on Android. +* Corrected the spelling of "UNINITIALIZED". + +**Known Issues** + +* Music playback history isn't being displayed in the Alexa app for certain account and device types. +* On GCC 8+, issues related to `-Wclass-memaccess` will trigger warnings. However, this won't cause the build to fail and these warnings can be ignored. +* Android error ("libDefaultClient.so" not found) can be resolved by upgrading to ADB version 1.0.40 +* When network connection is lost, lost connection status is not returned via local TTS. +* `ACL` may encounter issues if audio attachments are received but not consumed. +* `SpeechSynthesizerState` currently uses `GAINING_FOCUS` and `LOSING_FOCUS` as a workaround for handling intermediate state. These states may be removed in a future release. +* The Alexa app doesn't always indicate when a device is successfully connected via Bluetooth. +* Connecting a product to streaming media via Bluetooth will sometimes stop media playback within the source application. Resuming playback through the source application or toggling next/previous will correct playback. +* When a source device is streaming silence via Bluetooth, the Alexa app indicates that audio content is streaming. +* The Bluetooth agent assumes that the Bluetooth adapter is always connected to a power source. Disconnecting from a power source during operation is not yet supported. +* On some products, interrupted Bluetooth playback may not resume if other content is locally streamed. +* `make integration` is currently not available for Android. In order to run integration tests on Android, you'll need to manually upload the test binary file along with any input file. At that point, the adb can be used to run the integration tests. +* On Raspberry Pi running Android Things with HDMI output audio, beginning of speech is truncated when Alexa responds to user text-to-speech (TTS). +* When the sample app is restarted and the network connection is lost, the Reminder TTS message does not play. Instead, the default alarm tone will play twice. +* `ServerDisconnectIntegratonTest` tests have been disabled until they can be updated to reflect new service behavior. +* Devices connected before the Bluetooth CA is initialized are ignored. +* The `DirectiveSequencerTest.test_handleBlockingThenImmediatelyThenNonBockingOnSameDialogId` test fails intermittently. + ### v1.13.0 released 05/22/2019: **Enhancements** diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c16aaeb81..8d86e8acd5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.1 FATAL_ERROR) # Set project information -project(AlexaClientSDK VERSION 1.13.0 LANGUAGES CXX) +project(AlexaClientSDK VERSION 1.14.0 LANGUAGES CXX) set(PROJECT_BRIEF "A cross-platform, modular SDK for interacting with the Alexa Voice Service") if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CapabilityAgents/ExternalMediaPlayer/src/ExternalMediaPlayerAdapters") set(HAS_EXTERNAL_MEDIA_PLAYER_ADAPTERS ON) diff --git a/CapabilityAgents/AudioPlayer/include/AudioPlayer/AudioPlayer.h b/CapabilityAgents/AudioPlayer/include/AudioPlayer/AudioPlayer.h index 74945b07f2..eaa3e6ac38 100644 --- a/CapabilityAgents/AudioPlayer/include/AudioPlayer/AudioPlayer.h +++ b/CapabilityAgents/AudioPlayer/include/AudioPlayer/AudioPlayer.h @@ -16,6 +16,8 @@ #ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_AUDIOPLAYER_INCLUDE_AUDIOPLAYER_AUDIOPLAYER_H_ #define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_AUDIOPLAYER_INCLUDE_AUDIOPLAYER_AUDIOPLAYER_H_ +#include +#include #include #include @@ -27,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +58,8 @@ class AudioPlayer : public avsCommon::avs::CapabilityAgent , public ProgressTimer::ContextInterface , public avsCommon::sdkInterfaces::AudioPlayerInterface + , public avsCommon::sdkInterfaces::MediaPropertiesInterface + , public avsCommon::sdkInterfaces::RenderPlayerInfoCardsProviderInterface , public avsCommon::sdkInterfaces::CapabilityConfigurationInterface , public avsCommon::utils::mediaPlayer::MediaPlayerObserverInterface , public avsCommon::utils::RequiresShutdown @@ -124,6 +129,16 @@ class AudioPlayer /// @{ void addObserver(std::shared_ptr observer) override; void removeObserver(std::shared_ptr observer) override; + /// @} + + /// @name RenderPlayerInfoCardsProviderInterface Functions + /// @{ + void setObserver( + std::shared_ptr observer) override; + /// @} + + /// @name MediaPropertiesInterface Functions + /// @{ std::chrono::milliseconds getAudioItemOffset() override; /// @} @@ -133,6 +148,31 @@ class AudioPlayer /// @} private: + /** + * This structure contain the necessary objects from the @c PLAY directive that are used for playing the audio. + */ + struct PlayDirectiveInfo { + /// MessageId from the @c PLAY directive. + const std::string messageId; + + /// This is the @c AudioItem object from the @c PLAY directive. + AudioItem audioItem; + + /// This is the @c PlayBehavior from the @c PLAY directive. + PlayBehavior playBehavior; + + /// The @c SourceId from setSource API call. If the SourceId is not equal to ERROR_SOURCE_ID, it means that + /// this audioItem has buffered by the @c MediaPlayer. + SourceId sourceId; + + /** + * Constructor. + * + * @param messageId The message ID of the @c PLAY directive. + */ + PlayDirectiveInfo(const std::string& messageId); + }; + /** * Constructor. * @@ -166,6 +206,13 @@ class AudioPlayer */ bool parseDirectivePayload(std::shared_ptr info, rapidjson::Document* document); + /** + * This function pre-handles a @c PLAY directive. + * + * @param info The @c DirectiveInfo containing the @c AVSDirective and the @c DirectiveHandlerResultInterface. + */ + void preHandlePlayDirective(std::shared_ptr info); + /** * This function handles a @c PLAY directive. * @@ -280,12 +327,44 @@ class AudioPlayer void executeOnTags(SourceId id, std::shared_ptr vectorOfTags); /** - * This function executes a parsed @c PLAY directive. + * This function checks to see if @c AudioPlayer should start pre-buffering the @c AudioItem in pre-handle stage. * * @param playBehavior Specifies how @c audioItem should be queued/played. - * @param audioItem The new @c AudioItem to play. + * @return true if @c AudioPlayer should start buffering the @c AudioItem in pre-handle, else @c false. */ - void executePlay(PlayBehavior playBehavior, const AudioItem& audioItem); + bool executeShouldPreBufferInPreHandle(PlayBehavior playBehavior); + + /** + * Set a source to play in MediaPlayer. + * + * @param audioItem The @c AudioItem with information for what needs to be played. + * @return The @c SourceId that represents the source being handled as a result of this call. @c ERROR will be + * returned if the source failed to be set. + */ + SourceId setSource(const AudioItem& audioItem); + + /** + * This function checks to see if a @c PlayDirectiveInfo in @c m_preHandlePlayInfoList with the same messageId + * exists or not. + * + * @param messageId The message ID to check against. + * @return true if there exists a @c PlayDirectiveInfo with the same messageId, else @c false. + */ + bool executeIsInPreHandlePlayInfoList(const std::string& messageId); + + /** + * This function executes a parsed @c PLAY directive in pre-handle stage. + * + * @param info The @c PlayDirectiveInfo specifying the information from the @c PLAY directive. + */ + void executePrePlay(std::shared_ptr info); + + /** + * This function executes a parsed @c PLAY directive in handle stage. + * + * @param messageId The message ID of the @C PLAY directive. + */ + void executePlay(const std::string& messageId); /// This function plays the next @c AudioItem in the queue. void playNextItem(); @@ -437,8 +516,14 @@ class AudioPlayer /// The current focus state of the @c AudioPlayer on the content channel. avsCommon::avs::FocusState m_focus; - /// The queue of @c AudioItems to play. - std::deque m_audioItems; + /// The list of @c PlayDirectiveInfo parsed during the pre-handle stage. + std::list> m_preHandlePlayInfoList; + + /* + * The queue of @c PlayDirectiveInfo to play. The @c PlayBehavior is already resolved when items are + * added to the queue. This queue is used to find the next @c AudioItem to play when @c playNextItem() is called. + */ + std::deque> m_audioPlayQueue; /// The token of the currently (or most recently) playing @c AudioItem. std::string m_token; @@ -449,9 +534,12 @@ class AudioPlayer /// The initial offset for the currently (or most recent) playing @c AudioItem. std::chrono::milliseconds m_initialOffset; - /// The id of the currently (or most recently) playing @c MediaPlayer source. + /// The ID of the currently (or most recently) playing @c MediaPlayer source. SourceId m_sourceId; + /// Flag to check if there's a on-going pre-buffering. + bool m_isPreBuffering; + /// When in the @c BUFFER_UNDERRUN state, this records the time at which the state was entered. std::chrono::steady_clock::time_point m_bufferUnderrunTimestamp; @@ -468,6 +556,9 @@ class AudioPlayer /// A set of observers to be notified when there's a change in the audio state. std::unordered_set> m_observers; + /// Observer for changes related to RenderPlayerInfoCards. + std::shared_ptr m_renderPlayerObserver; + /** * A flag which is set when calling @c MediaPlayerInterface::stop(), and used by @c onPlaybackStopped() to decide * whether to continue on to the next queued item. @@ -479,7 +570,6 @@ class AudioPlayer * This flag is used to tell if the @c AudioPlayer is in the process of stopping playback. */ bool m_isStopCalled; - /// @} /// Set of capability configurations that will get published using the Capabilities API diff --git a/CapabilityAgents/AudioPlayer/src/AudioPlayer.cpp b/CapabilityAgents/AudioPlayer/src/AudioPlayer.cpp index 5b9465a481..afabff53ee 100644 --- a/CapabilityAgents/AudioPlayer/src/AudioPlayer.cpp +++ b/CapabilityAgents/AudioPlayer/src/AudioPlayer.cpp @@ -100,6 +100,12 @@ static const std::chrono::seconds TIMEOUT{2}; */ static std::shared_ptr getAudioPlayerCapabilityConfiguration(); +AudioPlayer::PlayDirectiveInfo::PlayDirectiveInfo(const std::string& messageId) : + messageId{messageId}, + playBehavior{PlayBehavior::ENQUEUE}, + sourceId{ERROR_SOURCE_ID} { +} + std::shared_ptr AudioPlayer::create( std::shared_ptr mediaPlayer, std::shared_ptr messageSender, @@ -142,20 +148,30 @@ void AudioPlayer::provideState( } void AudioPlayer::handleDirectiveImmediately(std::shared_ptr directive) { - handleDirective(std::make_shared(directive, nullptr)); + auto info = std::make_shared(directive, nullptr); + preHandleDirective(info); + handleDirective(info); } void AudioPlayer::preHandleDirective(std::shared_ptr info) { - // TODO: Move as much processing up here as possilble (ACSDK415). + if (!info || !info->directive) { + ACSDK_ERROR(LX("preHandleDirectiveFailed").d("reason", "nullDirectiveInfo")); + return; + } + ACSDK_DEBUG5(LX(__func__).d("name", info->directive->getName()).d("messageId", info->directive->getMessageId())); + + if (info->directive->getName() == PLAY.name) { + preHandlePlayDirective(info); + } } void AudioPlayer::handleDirective(std::shared_ptr info) { - ACSDK_DEBUG( - LX("handleDirective").d("name", info->directive->getName()).d("messageId", info->directive->getMessageId())); - if (!info) { + if (!info || !info->directive) { ACSDK_ERROR(LX("handleDirectiveFailed").d("reason", "nullDirectiveInfo")); return; } + ACSDK_DEBUG5( + LX("handleDirective").d("name", info->directive->getName()).d("messageId", info->directive->getMessageId())); if (info->directive->getName() == PLAY.name) { handlePlayDirective(info); } else if (info->directive->getName() == STOP.name) { @@ -175,15 +191,25 @@ void AudioPlayer::handleDirective(std::shared_ptr info) { } void AudioPlayer::cancelDirective(std::shared_ptr info) { - ACSDK_DEBUG(LX("cancelDirective").d("name", info->directive->getName())); removeDirective(info); + if (info && info->directive) { + ACSDK_DEBUG(LX("cancelDirective").d("name", info->directive->getName())); + auto messageId = info->directive->getMessageId(); + m_executor.submit([this, messageId] { + for (auto it = m_preHandlePlayInfoList.begin(); it != m_preHandlePlayInfoList.end(); it++) { + if (messageId == it->get()->messageId) { + m_preHandlePlayInfoList.erase(it); + } + } + }); + } } void AudioPlayer::onDeregistered() { ACSDK_DEBUG(LX("onDeregistered")); m_executor.submit([this] { executeStop(); - m_audioItems.clear(); + m_audioPlayQueue.clear(); }); } @@ -357,6 +383,12 @@ void AudioPlayer::removeObserver(std::shared_ptr observer) { + ACSDK_DEBUG1(LX(__func__)); + m_executor.submit([this, observer] { m_renderPlayerObserver = observer; }); +} + std::chrono::milliseconds AudioPlayer::getAudioItemOffset() { ACSDK_DEBUG1(LX(__func__)); auto offset = m_executor.submit([this] { return getOffset(); }); @@ -381,6 +413,7 @@ AudioPlayer::AudioPlayer( m_focus{FocusState::NONE}, m_initialOffset{0}, m_sourceId{MediaPlayerInterface::ERROR}, + m_isPreBuffering{false}, m_offset{std::chrono::milliseconds{std::chrono::milliseconds::zero()}}, m_isStopCalled{false} { m_capabilityConfigurations.insert(getAudioPlayerCapabilityConfiguration()); @@ -405,7 +438,7 @@ void AudioPlayer::doShutdown() { m_focusManager.reset(); m_contextManager->setStateProvider(STATE, nullptr); m_contextManager.reset(); - m_audioItems.clear(); + m_audioPlayQueue.clear(); m_playbackRouter.reset(); } @@ -424,8 +457,8 @@ bool AudioPlayer::parseDirectivePayload(std::shared_ptr info, rap return false; } -void AudioPlayer::handlePlayDirective(std::shared_ptr info) { - ACSDK_DEBUG1(LX("handlePlayDirective")); +void AudioPlayer::preHandlePlayDirective(std::shared_ptr info) { + ACSDK_DEBUG1(LX("preHandlePlayDirective")); ACSDK_DEBUG9(LX("PLAY").d("payload", info->directive->getPayload())); rapidjson::Document payload; if (!parseDirectivePayload(info, &payload)) { @@ -439,11 +472,10 @@ void AudioPlayer::handlePlayDirective(std::shared_ptr info) { rapidjson::Value::ConstMemberIterator audioItemJson; if (!jsonUtils::findNode(payload, "audioItem", &audioItemJson)) { - ACSDK_ERROR(LX("handlePlayDirectiveFailed") + ACSDK_ERROR(LX("preHandlePlayDirectiveFailed") .d("reason", "missingAudioItem") .d("messageId", info->directive->getMessageId())); sendExceptionEncounteredAndReportFailed(info, "missing AudioItem"); - ; return; } @@ -454,7 +486,7 @@ void AudioPlayer::handlePlayDirective(std::shared_ptr info) { rapidjson::Value::ConstMemberIterator stream; if (!jsonUtils::findNode(audioItemJson->value, "stream", &stream)) { - ACSDK_ERROR(LX("handlePlayDirectiveFailed") + ACSDK_ERROR(LX("preHandlePlayDirectiveFailed") .d("reason", "missingStream") .d("messageId", info->directive->getMessageId())); sendExceptionEncounteredAndReportFailed(info, "missing stream"); @@ -462,8 +494,9 @@ void AudioPlayer::handlePlayDirective(std::shared_ptr info) { } if (!jsonUtils::retrieveValue(stream->value, "url", &audioItem.stream.url)) { - ACSDK_ERROR( - LX("handlePlayDirectiveFailed").d("reason", "missingUrl").d("messageId", info->directive->getMessageId())); + ACSDK_ERROR(LX("preHandlePlayDirectiveFailed") + .d("reason", "missingUrl") + .d("messageId", info->directive->getMessageId())); sendExceptionEncounteredAndReportFailed(info, "missing URL"); return; } @@ -477,17 +510,16 @@ void AudioPlayer::handlePlayDirective(std::shared_ptr info) { std::string contentId = audioItem.stream.url.substr(CID_PREFIX.length()); audioItem.stream.reader = info->directive->getAttachmentReader(contentId, sds::ReaderPolicy::NONBLOCKING); if (nullptr == audioItem.stream.reader) { - ACSDK_ERROR(LX("handlePlayDirectiveFailed") + ACSDK_ERROR(LX("preHandlePlayDirectiveFailed") .d("reason", "getAttachmentReaderFailed") .d("messageId", info->directive->getMessageId())); sendExceptionEncounteredAndReportFailed(info, "unable to obtain attachment reader"); - ; return; } // TODO: Add a method to MediaPlayer to query whether a format is supported (ACSDK-416). if (audioItem.stream.format != StreamFormat::AUDIO_MPEG) { - ACSDK_ERROR(LX("handlePlayDirectiveFailed") + ACSDK_ERROR(LX("preHandlePlayDirectiveFailed") .d("reason", "unsupportedFormat") .d("format", audioItem.stream.format) .d("messageId", info->directive->getMessageId())); @@ -543,12 +575,34 @@ void AudioPlayer::handlePlayDirective(std::shared_ptr info) { audioItem.stream.expectedPreviousToken = ""; } + m_executor.submit([this, info, playBehavior, audioItem] { + auto messageId = info->directive->getMessageId(); + if (executeIsInPreHandlePlayInfoList(messageId)) { + // There's is already a playInfo with the same messageId in the queue. + ACSDK_ERROR(LX("preHandlePlayDirectiveFailed").d("reason", "messageIdAlreadyInPreHandleQueue")); + sendExceptionEncounteredAndReportFailed( + info, "duplicated messageId " + messageId, ExceptionErrorType::UNEXPECTED_INFORMATION_RECEIVED); + return; + } + + auto playDirectiveInfo = std::make_shared(messageId); + playDirectiveInfo->audioItem = audioItem; + playDirectiveInfo->playBehavior = playBehavior; + executePrePlay(playDirectiveInfo); + }); +} + +void AudioPlayer::handlePlayDirective(std::shared_ptr info) { + ACSDK_DEBUG1(LX("handlePlayDirective")); + // Note: Unlike SpeechSynthesizer, AudioPlayer directives are instructing the client to start/stop/queue // content, so directive handling is considered to be complete when we have queued the content for // playback; we don't wait for playback to complete. setHandlingCompleted(info); - m_executor.submit([this, playBehavior, audioItem] { executePlay(playBehavior, audioItem); }); + auto messageId = info->directive->getMessageId(); + + m_executor.submit([this, messageId] { executePlay(messageId); }); } void AudioPlayer::handleStopDirective(std::shared_ptr info) { @@ -577,7 +631,8 @@ void AudioPlayer::removeDirective(std::shared_ptr info) { // Check result too, to catch cases where DirectiveInfo was created locally, without a nullptr result. // In those cases there is no messageId to remove because no result was expected. if (info->directive && info->result) { - CapabilityAgent::removeDirective(info->directive->getMessageId()); + auto messageId = info->directive->getMessageId(); + CapabilityAgent::removeDirective(messageId); } } @@ -636,12 +691,12 @@ void AudioPlayer::executeOnFocusChanged(FocusState newFocus) { case PlayerActivity::STOPPED: case PlayerActivity::FINISHED: // We see a focus change to foreground in these states if we are starting to play a new song. - if (!m_audioItems.empty()) { + if (!m_audioPlayQueue.empty()) { ACSDK_DEBUG1(LX("executeOnFocusChanged").d("action", "playNextItem")); playNextItem(); } - // If m_audioItems is empty and channel wasn't released, that means we are going to + // If m_audioPlayQueue is empty and channel wasn't released, that means we are going to // play the next item. return; @@ -673,7 +728,7 @@ void AudioPlayer::executeOnFocusChanged(FocusState newFocus) { case PlayerActivity::STOPPED: // If we're stopping due to a new play and would have been continuing on to the next song, we want // to block that. - if (m_playNextItemAfterStopped && !m_audioItems.empty()) { + if (m_playNextItemAfterStopped && !m_audioPlayQueue.empty()) { m_playNextItemAfterStopped = false; return; } @@ -714,7 +769,7 @@ void AudioPlayer::executeOnFocusChanged(FocusState newFocus) { case PlayerActivity::BUFFER_UNDERRUN: // If the focus change came in while we were in a 'playing' state, we need to stop because we are // yielding the channel. - m_audioItems.clear(); + m_audioPlayQueue.clear(); ACSDK_DEBUG1(LX("executeOnFocusChanged").d("action", "executeStop")); executeStop(); return; @@ -735,6 +790,8 @@ void AudioPlayer::executeOnPlaybackStarted(SourceId id) { return; } + m_isPreBuffering = false; + // Race condition exists where focus can be lost before the executeOnPlaybackStarted callback. if (avsCommon::avs::FocusState::NONE == m_focus) { ACSDK_WARN(LX(__func__).d("reason", "callbackAfterFocusLost").d("action", "stopping")); @@ -773,10 +830,24 @@ void AudioPlayer::executeOnPlaybackStopped(SourceId id) { m_progressTimer.stop(); sendPlaybackStoppedEvent(); m_isStopCalled = false; - if (!m_playNextItemAfterStopped || m_audioItems.empty()) { + if (!m_playNextItemAfterStopped || (m_audioPlayQueue.empty() && m_preHandlePlayInfoList.empty())) { handlePlaybackCompleted(); } else if (avsCommon::avs::FocusState::FOREGROUND == m_focus) { playNextItem(); + } else if (avsCommon::avs::FocusState::BACKGROUND == m_focus) { + // Check if we have something to pre-buffer. + if (!m_preHandlePlayInfoList.empty() && !m_isPreBuffering) { + auto playInfo = m_preHandlePlayInfoList.front(); + if (playInfo) { + playInfo->sourceId = setSource(playInfo->audioItem); + if (ERROR_SOURCE_ID != playInfo->sourceId) { + m_isPreBuffering = true; + ACSDK_INFO(LX("executeOnPlaybackStoppedPreBuffering").d("id", playInfo->audioItem.id)); + } else { + ACSDK_ERROR(LX("executeOnPlaybackStoppedFailed").d("reason", "SetSourceFailed")); + } + } + } } return; case PlayerActivity::IDLE: @@ -828,7 +899,7 @@ void AudioPlayer::executeOnPlaybackFinished(SourceId id) { sendPlaybackNearlyFinishedEvent(); sendPlaybackFinishedEvent(); - if (m_audioItems.empty()) { + if (m_audioPlayQueue.empty()) { handlePlaybackCompleted(); } else { playNextItem(); @@ -865,6 +936,7 @@ void AudioPlayer::executeOnPlaybackError(SourceId id, const ErrorType& type, std return; } + m_isPreBuffering = false; m_progressTimer.stop(); sendPlaybackFailedEvent(m_token, type, error); @@ -953,37 +1025,168 @@ void AudioPlayer::executeOnTags(SourceId id, std::shared_ptr sendStreamMetadataExtractedEvent(vectorOfTags); } -void AudioPlayer::executePlay(PlayBehavior playBehavior, const AudioItem& audioItem) { - ACSDK_DEBUG1(LX("executePlay").d("playBehavior", playBehavior)); +bool AudioPlayer::executeShouldPreBufferInPreHandle(PlayBehavior playBehavior) { + bool shouldPreBuffer = false; + switch (m_currentActivity) { + case PlayerActivity::PLAYING: + break; + + case PlayerActivity::IDLE: + case PlayerActivity::STOPPED: + case PlayerActivity::FINISHED: + shouldPreBuffer = !m_isPreBuffering || (PlayBehavior::REPLACE_ALL == playBehavior); + break; + + case PlayerActivity::PAUSED: + if (PlayBehavior::REPLACE_ALL == playBehavior) { + // We shouldn't pre-buffer now for this case, but we should call executeStop now and play the next + // item. And in executeOnPlaybackStopped, if the focus is still in BACKGROUND, then we should start + // buffering at that point. + executeStop(true); + } + break; + + case PlayerActivity::BUFFER_UNDERRUN: + break; + } + return shouldPreBuffer; +} + +AudioPlayer::SourceId AudioPlayer::setSource(const AudioItem& audioItem) { + ACSDK_DEBUG1(LX(__func__)); + auto sourceId = ERROR_SOURCE_ID; + if (audioItem.stream.reader) { + sourceId = m_mediaPlayer->setSource(audioItem.stream.reader); + if (MediaPlayerInterface::ERROR == sourceId) { + sendPlaybackFailedEvent( + audioItem.stream.token, + ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, + "failed to set attachment media source"); + ACSDK_ERROR(LX("setSourceFailed").d("reason", "setSourceFailed").d("type", "attachment")); + } + } else { + ACSDK_DEBUG9(LX("settingUrlSource").d("offset", audioItem.stream.offset.count())); + sourceId = m_mediaPlayer->setSource(audioItem.stream.url, audioItem.stream.offset); + if (MediaPlayerInterface::ERROR == sourceId) { + sendPlaybackFailedEvent( + audioItem.stream.token, ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, "failed to set URL media source"); + ACSDK_ERROR(LX("setSourceFailed").d("reason", "setSourceFailed").d("type", "URL")); + } + } + return sourceId; +} + +bool AudioPlayer::executeIsInPreHandlePlayInfoList(const std::string& messageId) { + for (const auto& info : m_preHandlePlayInfoList) { + if (info && info->messageId == messageId) { + return true; + } + } + return false; +} + +void AudioPlayer::executePrePlay(std::shared_ptr info) { + if (!info) { + ACSDK_ERROR(LX("executePrePlayFailed").d("reason", "nullInfo")); + return; + } + ACSDK_DEBUG1(LX(__func__).d("playBehavior", info->playBehavior).d("state", m_currentActivity)); // Per AVS docs, drop/ignore AudioItems that specify an expectedPreviousToken which does not match the // current/previous token. - if (!audioItem.stream.expectedPreviousToken.empty()) { - auto previousToken = m_audioItems.empty() ? m_token : m_audioItems.back().stream.token; - if (previousToken != audioItem.stream.expectedPreviousToken) { - ACSDK_INFO(LX("executePlayDropped") + if (!info->audioItem.stream.expectedPreviousToken.empty()) { + std::string previousToken; + if (!m_preHandlePlayInfoList.empty()) { + const auto& lastPlayInfo = m_preHandlePlayInfoList.back(); + if (lastPlayInfo) { + previousToken = lastPlayInfo->audioItem.stream.token; + } else { + ACSDK_ERROR( + LX("executePrePlayPreviousTokenNotFound").d("reason", "nullPlayInfoInPreHandlePlayInfoQueue")); + } + } else if (!m_audioPlayQueue.empty()) { + const auto& lastPlayInfo = m_audioPlayQueue.back(); + if (lastPlayInfo) { + previousToken = lastPlayInfo->audioItem.stream.token; + } else { + ACSDK_ERROR(LX("executePrePlayPreviousTokenNotFound").d("reason", "nullPlayInfoInAudioPlayQueue")); + } + } else { + previousToken = m_token; + } + if (previousToken != info->audioItem.stream.expectedPreviousToken) { + ACSDK_INFO(LX("executePrePlayDropped") .d("reason", "unexpectedPreviousToken") .d("previous", previousToken) - .d("expected", audioItem.stream.expectedPreviousToken)); + .d("expected", info->audioItem.stream.expectedPreviousToken)); return; } } + if (executeShouldPreBufferInPreHandle(info->playBehavior)) { + info->sourceId = setSource(info->audioItem); + if (info->sourceId != ERROR_SOURCE_ID) { + ACSDK_INFO(LX("executePrePlayPreBuffering").d("id", info->audioItem.id)); + m_isPreBuffering = true; + } + } + + m_preHandlePlayInfoList.push_back(info); +} + +void AudioPlayer::executePlay(const std::string& messageId) { + ACSDK_DEBUG1(LX(__func__)); + if (m_preHandlePlayInfoList.empty()) { + ACSDK_ERROR(LX("executePlayFailed").d("reason", "emptyPlayQueue")); + return; + } + + // If messageId is not found in set, just return + if (!executeIsInPreHandlePlayInfoList(messageId)) { + ACSDK_ERROR(LX("executePlayFailed").d("reason", "messageIdNotFoundInMap")); + return; + } + + std::shared_ptr playInfo; + + // pop m_preHandlePlayInfoList until the playInfo with the matching messageId + while (!m_preHandlePlayInfoList.empty()) { + playInfo = m_preHandlePlayInfoList.front(); + m_preHandlePlayInfoList.pop_front(); + if (!playInfo) { + ACSDK_ERROR(LX("executePlayError").d("reason", "NullPlayInfo")); + continue; + } + + if (playInfo->messageId == messageId) { + break; + } + ACSDK_WARN(LX("executePlay").d("reason", "TrackNotHeadOfQueue")); + } + + if (!playInfo || playInfo->messageId != messageId) { + ACSDK_ERROR(LX("executePlayFailed").d("reason", "playInfoNotFound")); + return; + } + // Do any playback/queue maintenance per playBehavior. - switch (playBehavior) { + switch (playInfo->playBehavior) { case PlayBehavior::REPLACE_ALL: // Note: this will change m_currentActivity to STOPPED. - executeStop(true); + if (!m_isStopCalled) { + executeStop(true); + } // FALL-THROUGH case PlayBehavior::REPLACE_ENQUEUED: - m_audioItems.clear(); + m_audioPlayQueue.clear(); // FALL-THROUGH case PlayBehavior::ENQUEUE: - m_audioItems.push_back(audioItem); + m_audioPlayQueue.push_back(playInfo); break; } - if (m_audioItems.empty()) { - ACSDK_ERROR(LX("executePlayFailed").d("reason", "unhandledPlayBehavior").d("playBehavior", playBehavior)); + if (m_audioPlayQueue.empty()) { + ACSDK_ERROR( + LX("executePlayFailed").d("reason", "unhandledPlayBehavior").d("playBehavior", playInfo->playBehavior)); return; } @@ -1017,37 +1220,37 @@ void AudioPlayer::executePlay(PlayBehavior playBehavior, const AudioItem& audioI } void AudioPlayer::playNextItem() { - ACSDK_DEBUG1(LX("playNextItem").d("m_audioItems.size", m_audioItems.size())); + ACSDK_DEBUG1(LX("playNextItem").d("m_audioPlayQueue.size", m_audioPlayQueue.size())); // Cancel any existing progress timer. The new timer will start when playback starts. m_progressTimer.stop(); - if (m_audioItems.empty()) { + if (m_audioPlayQueue.empty()) { sendPlaybackFailedEvent(m_token, ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, "queue is empty"); ACSDK_ERROR(LX("playNextItemFailed").d("reason", "emptyQueue")); executeStop(); return; } - auto item = m_audioItems.front(); - m_audioItems.pop_front(); - m_token = item.stream.token; + std::shared_ptr info; + do { + info = m_audioPlayQueue.front(); + m_audioPlayQueue.pop_front(); + } while (!info && !m_audioPlayQueue.empty()); + + if (!info) { + ACSDK_ERROR(LX("playNextItemFailed").d("reason", "nullPlayInfo")); + return; + } + + auto item = info->audioItem; m_audioItemId = item.id; + m_token = item.stream.token; m_initialOffset = item.stream.offset; + m_sourceId = info->sourceId; - if (item.stream.reader) { - m_sourceId = m_mediaPlayer->setSource(std::move(item.stream.reader)); - if (MediaPlayerInterface::ERROR == m_sourceId) { - sendPlaybackFailedEvent( - m_token, ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, "failed to set attachment media source"); - ACSDK_ERROR(LX("playNextItemFailed").d("reason", "setSourceFailed").d("type", "attachment")); - return; - } - } else { - ACSDK_DEBUG9(LX("settingUrlSource").d("offset", item.stream.offset.count())); - m_sourceId = m_mediaPlayer->setSource(item.stream.url, item.stream.offset); + if (MediaPlayerInterface::ERROR == m_sourceId) { + m_sourceId = setSource(item); if (MediaPlayerInterface::ERROR == m_sourceId) { - sendPlaybackFailedEvent( - m_token, ErrorType::MEDIA_ERROR_INTERNAL_DEVICE_ERROR, "failed to set URL media source"); - ACSDK_ERROR(LX("playNextItemFailed").d("reason", "setSourceFailed").d("type", "URL")); + ACSDK_ERROR(LX("playNextItemFailed").d("reason", "setSourceFailed")); return; } } @@ -1088,13 +1291,13 @@ void AudioPlayer::executeStop(bool playNextItem) { } void AudioPlayer::executeClearQueue(ClearBehavior clearBehavior) { - ACSDK_DEBUG(LX("executeClearQueue").d("clearBehavior", clearBehavior)); + ACSDK_DEBUG1(LX("executeClearQueue").d("clearBehavior", clearBehavior)); switch (clearBehavior) { case ClearBehavior::CLEAR_ALL: executeStop(); // FALL-THROUGH case ClearBehavior::CLEAR_ENQUEUED: - m_audioItems.clear(); + m_audioPlayQueue.clear(); sendPlaybackQueueClearedEvent(); return; } @@ -1274,6 +1477,14 @@ void AudioPlayer::notifyObserver() { for (auto& observer : m_observers) { observer->onPlayerActivityChanged(m_currentActivity, context); } + + if (m_renderPlayerObserver) { + avsCommon::sdkInterfaces::RenderPlayerInfoCardsObserverInterface::Context renderPlayerInfoContext; + renderPlayerInfoContext.audioItemId = m_audioItemId; + renderPlayerInfoContext.offset = getOffset(); + renderPlayerInfoContext.mediaProperties = shared_from_this(); + m_renderPlayerObserver->onRenderPlayerCardsInfoChanged(m_currentActivity, renderPlayerInfoContext); + } } std::chrono::milliseconds AudioPlayer::getOffset() { diff --git a/CapabilityAgents/AudioPlayer/test/AudioPlayerTest.cpp b/CapabilityAgents/AudioPlayer/test/AudioPlayerTest.cpp index ae2b04b68f..7096795492 100644 --- a/CapabilityAgents/AudioPlayer/test/AudioPlayerTest.cpp +++ b/CapabilityAgents/AudioPlayer/test/AudioPlayerTest.cpp @@ -28,19 +28,20 @@ #include #include -#include -#include -#include +#include #include +#include #include #include #include #include #include #include -#include -#include +#include +#include #include +#include +#include #include "AudioPlayer/AudioPlayer.h" @@ -211,6 +212,31 @@ static const std::string REPLACE_ALL_PAYLOAD_TEST = "}"; // clang-format on +static std::string createUrlPayloadTest(PlayBehavior playbehavior, const std::string& url) { + // clang-format off + const std::string replaceAllPaylaod = + "{" + "\"playBehavior\":\"" + playBehaviorToString(playbehavior) + "\"," + "\"audioItem\": {" + "\"audioItemId\":\"" + AUDIO_ITEM_ID_2 + "\"," + "\"stream\": {" + "\"url\":\"" + url + "\"," + "\"streamFormat\":\"" + FORMAT_TEST + "\"," + "\"offsetInMilliseconds\":" + std::to_string(OFFSET_IN_MILLISECONDS_TEST) + "," + "\"expiryTime\":\"" + EXPIRY_TEST + "\"," + "\"progressReport\": {" + "\"progressReportDelayInMilliseconds\":" + std::to_string(PROGRESS_REPORT_DELAY) + "," + "\"progressReportIntervalInMilliseconds\":" + std::to_string(PROGRESS_REPORT_INTERVAL) + + "}," + "\"token\":\"" + TOKEN_TEST + "\"," + "\"expectedPreviousToken\":\"\"" + "}" + "}" + "}"; + // clang-format on + return replaceAllPaylaod; +} + /// Empty payload for testing. static const std::string EMPTY_PAYLOAD_TEST = "{}"; @@ -349,8 +375,10 @@ class TestAudioPlayerObserver : public AudioPlayerObserverInterface { .d("state", state) .d("audioItemId", context.audioItemId) .d("offsetInMs", std::chrono::duration_cast(context.offset).count())); - std::lock_guard lock(m_mutex); - m_state = state; + { + std::lock_guard lock(m_mutex); + m_state = state; + } m_conditionVariable.notify_all(); } @@ -1709,10 +1737,6 @@ TEST_F(AudioPlayerTest, testTimer_playbackStartedCallbackAfterFocusLost) { // Enforce the sequence. InSequence dummy; - // Wait for acquireFocus(). - ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); - m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); - std::promise playCalledPromise; std::future playCalled = playCalledPromise.get_future(); @@ -1722,16 +1746,180 @@ TEST_F(AudioPlayerTest, testTimer_playbackStartedCallbackAfterFocusLost) { return true; })); + // Wait for acquireFocus(). + ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); + ASSERT_THAT(playCalled.wait_for(WAIT_TIMEOUT), Ne(std::future_status::timeout)); m_audioPlayer->onFocusChanged(FocusState::NONE); m_audioPlayer->onPlaybackStarted(m_mockMediaPlayer->getCurrentSourceId()); - ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + // Make sure AudioPlayer will go to STOPPED. ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); } } +/** + * Test when @c AudioPlayer will only pre-buffer once with multiple ENQUEUE and REPLACE_ENQUEUED Play directives. + */ +TEST_F(AudioPlayerTest, test_MultipleEnqueuePlayDirectiveWillOnlyCallSetSourceOnce) { + auto avsMessageHeader1 = + std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, "1", PLAY_REQUEST_ID_TEST); + + auto avsMessageHeader2 = + std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, "2", PLAY_REQUEST_ID_TEST); + + auto avsMessageHeader3 = + std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, "3", PLAY_REQUEST_ID_TEST); + + PromiseFuturePair setSourcePair; + auto setSourceCalled = [this, &setSourcePair] { + setSourcePair.setValue(); + m_mockMediaPlayer->mockSetSource(); + return m_mockMediaPlayer->getCurrentSourceId(); + }; + + auto directiveHandlerResult1 = std::unique_ptr(new MockDirectiveHandlerResult); + auto directiveHandlerResult2 = std::unique_ptr(new MockDirectiveHandlerResult); + auto directiveHandlerResult3 = std::unique_ptr(new MockDirectiveHandlerResult); + + std::shared_ptr playDirective1 = AVSDirective::create( + "", + avsMessageHeader1, + createUrlPayloadTest(PlayBehavior::ENQUEUE, "www.abc.com"), + m_attachmentManager, + CONTEXT_ID_TEST); + std::shared_ptr playDirective2 = AVSDirective::create( + "", + avsMessageHeader2, + createUrlPayloadTest(PlayBehavior::ENQUEUE, "www.def.com"), + m_attachmentManager, + CONTEXT_ID_TEST); + std::shared_ptr playDirective3 = AVSDirective::create( + "", + avsMessageHeader3, + createUrlPayloadTest(PlayBehavior::REPLACE_ENQUEUED, "www.ghi.com"), + m_attachmentManager, + CONTEXT_ID_TEST); + + EXPECT_CALL(*m_mockMediaPlayer, urlSetSource(_)).Times(1).WillOnce(InvokeWithoutArgs(setSourceCalled)); + + m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective1, std::move(directiveHandlerResult1)); + m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective2, std::move(directiveHandlerResult2)); + m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective3, std::move(directiveHandlerResult3)); + + ASSERT_TRUE(setSourcePair.waitFor(WAIT_TIMEOUT)); +} + +/** + * Test when @c AudioPlayer will only pre-buffer once with ENQUEUE Play directives and call SetSource again when + * REPLACE_ALL PLAY directive is received. + */ +TEST_F(AudioPlayerTest, test_EnqueuePlayDirectiveReplaceAllDirectiveWillCallSetSourceTwice) { + auto avsMessageHeader1 = + std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, "1", PLAY_REQUEST_ID_TEST); + + auto avsMessageHeader2 = + std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, "2", PLAY_REQUEST_ID_TEST); + + auto directiveHandlerResult1 = std::unique_ptr(new MockDirectiveHandlerResult); + auto directiveHandlerResult2 = std::unique_ptr(new MockDirectiveHandlerResult); + + const std::string url1{"www.abc.com"}; + std::shared_ptr playDirective = AVSDirective::create( + "", avsMessageHeader1, createUrlPayloadTest(PlayBehavior::ENQUEUE, url1), m_attachmentManager, CONTEXT_ID_TEST); + + const std::string url2{"www.def.com"}; + std::shared_ptr anotherPlayDirective = AVSDirective::create( + "", + avsMessageHeader2, + createUrlPayloadTest(PlayBehavior::REPLACE_ALL, url2), + m_attachmentManager, + CONTEXT_ID_TEST); + + PromiseFuturePair setSourcePair1; + auto setSourceCalled1 = [this, &setSourcePair1] { + setSourcePair1.setValue(); + m_mockMediaPlayer->mockSetSource(); + return m_mockMediaPlayer->getCurrentSourceId(); + }; + EXPECT_CALL(*m_mockMediaPlayer, urlSetSource(url1)).Times(1).WillOnce(InvokeWithoutArgs(setSourceCalled1)); + m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(directiveHandlerResult1)); + ASSERT_TRUE(setSourcePair1.waitFor(WAIT_TIMEOUT)); + + PromiseFuturePair setSourcePair2; + auto setSourceCalled2 = [this, &setSourcePair2] { + setSourcePair2.setValue(); + m_mockMediaPlayer->mockSetSource(); + return m_mockMediaPlayer->getCurrentSourceId(); + }; + EXPECT_CALL(*m_mockMediaPlayer, urlSetSource(url2)).Times(1).WillOnce(InvokeWithoutArgs(setSourceCalled2)); + m_audioPlayer->CapabilityAgent::preHandleDirective(anotherPlayDirective, std::move(directiveHandlerResult2)); + ASSERT_TRUE(setSourcePair2.waitFor(WAIT_TIMEOUT)); +} + +/** + * Test when @c AudioPlayer is in BACKGROUND focus that it will pre-buffer when a REPLACE_ALL PLAY directive is + * received. + */ +TEST_F(AudioPlayerTest, test_PlayingWhenReplaceAllDirectiveInBackgroundWillPrebuffer) { + auto avsMessageHeader1 = + std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, "1", PLAY_REQUEST_ID_TEST); + + auto avsMessageHeader2 = + std::make_shared(NAMESPACE_AUDIO_PLAYER, NAME_PLAY, "2", PLAY_REQUEST_ID_TEST); + + auto directiveHandlerResult1 = std::unique_ptr(new MockDirectiveHandlerResult); + auto directiveHandlerResult2 = std::unique_ptr(new MockDirectiveHandlerResult); + + const std::string url1{"www.abc.com"}; + std::shared_ptr playDirective = AVSDirective::create( + "", avsMessageHeader1, createUrlPayloadTest(PlayBehavior::ENQUEUE, url1), m_attachmentManager, CONTEXT_ID_TEST); + + const std::string url2{"www.def.com"}; + std::shared_ptr anotherPlayDirective = AVSDirective::create( + "", + avsMessageHeader2, + createUrlPayloadTest(PlayBehavior::REPLACE_ALL, url2), + m_attachmentManager, + CONTEXT_ID_TEST); + + PromiseFuturePair setSourcePair1; + auto setSourceCalled1 = [this, &setSourcePair1] { + setSourcePair1.setValue(); + m_mockMediaPlayer->mockSetSource(); + return m_mockMediaPlayer->getCurrentSourceId(); + }; + + EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, NAMESPACE_AUDIO_PLAYER)) + .Times(1) + .WillOnce(InvokeWithoutArgs(this, &AudioPlayerTest::wakeOnAcquireChannel)); + EXPECT_CALL(*directiveHandlerResult1, setCompleted()); + EXPECT_CALL(*m_mockMediaPlayer, urlSetSource(url1)).Times(1).WillOnce(InvokeWithoutArgs(setSourceCalled1)); + m_audioPlayer->CapabilityAgent::preHandleDirective(playDirective, std::move(directiveHandlerResult1)); + m_audioPlayer->CapabilityAgent::handleDirective("1"); + ASSERT_EQ(std::future_status::ready, m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT)); + m_audioPlayer->onFocusChanged(FocusState::FOREGROUND); + ASSERT_TRUE(setSourcePair1.waitFor(WAIT_TIMEOUT)); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PLAYING, WAIT_TIMEOUT)); + + m_audioPlayer->onFocusChanged(FocusState::BACKGROUND); + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::PAUSED, WAIT_TIMEOUT)); + + PromiseFuturePair setSourcePair2; + auto setSourceCalled2 = [this, &setSourcePair2] { + setSourcePair2.setValue(); + m_mockMediaPlayer->mockSetSource(); + return m_mockMediaPlayer->getCurrentSourceId(); + }; + EXPECT_CALL(*m_mockMediaPlayer, urlSetSource(url2)).Times(1).WillOnce(InvokeWithoutArgs(setSourceCalled2)); + m_audioPlayer->CapabilityAgent::preHandleDirective(anotherPlayDirective, std::move(directiveHandlerResult2)); + + ASSERT_TRUE(m_testAudioPlayerObserver->waitFor(PlayerActivity::STOPPED, WAIT_TIMEOUT)); + ASSERT_TRUE(setSourcePair2.waitFor(WAIT_TIMEOUT)); +} + } // namespace test } // namespace audioPlayer } // namespace capabilityAgents diff --git a/CapabilityAgents/Bluetooth/src/Bluetooth.cpp b/CapabilityAgents/Bluetooth/src/Bluetooth.cpp index 5a8264737e..ae4b1224d4 100644 --- a/CapabilityAgents/Bluetooth/src/Bluetooth.cpp +++ b/CapabilityAgents/Bluetooth/src/Bluetooth.cpp @@ -906,9 +906,12 @@ void Bluetooth::executeEnterForeground() { case StreamingState::PENDING_PAUSED: case StreamingState::PAUSED: case StreamingState::INACTIVE: - // We push a play because some devices do not auto start playback next/previous command. - m_cmdQueue.push_front(AVRCPCommand::PLAY); - executeDrainQueue(); + // We push a play when the BT CA has been backgrounded because some devices do not + // auto start playback next/previous command. + if (m_focusState == FocusState::BACKGROUND) { + m_cmdQueue.push_front(AVRCPCommand::PLAY); + executeDrainQueue(); + } if (m_mediaStream == nullptr) { executeInitializeMediaSource(); } diff --git a/CapabilityAgents/ExternalMediaPlayer/include/ExternalMediaPlayer/ExternalMediaPlayer.h b/CapabilityAgents/ExternalMediaPlayer/include/ExternalMediaPlayer/ExternalMediaPlayer.h index e7919ebf7f..b859abb3bd 100644 --- a/CapabilityAgents/ExternalMediaPlayer/include/ExternalMediaPlayer/ExternalMediaPlayer.h +++ b/CapabilityAgents/ExternalMediaPlayer/include/ExternalMediaPlayer/ExternalMediaPlayer.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +54,8 @@ class ExternalMediaPlayer , public avsCommon::utils::RequiresShutdown , public avsCommon::sdkInterfaces::CapabilityConfigurationInterface , public avsCommon::sdkInterfaces::ExternalMediaPlayerInterface + , public avsCommon::sdkInterfaces::MediaPropertiesInterface + , public avsCommon::sdkInterfaces::RenderPlayerInfoCardsProviderInterface , public avsCommon::sdkInterfaces::PlaybackHandlerInterface , public std::enable_shared_from_this { public: @@ -137,6 +141,17 @@ class ExternalMediaPlayer std::unordered_set> getCapabilityConfigurations() override; /// @} + /// @name RenderPlayerInfoCardsProviderInterface Functions + /// @{ + void setObserver( + std::shared_ptr observer) override; + /// @} + + /// @name MediaPropertiesInterface Functions + /// @{ + std::chrono::milliseconds getAudioItemOffset() override; + /// @} + /** * Adds an observer which will be notified on any observable state changes * @@ -370,6 +385,11 @@ class ExternalMediaPlayer const avsCommon::sdkInterfaces::externalMediaPlayer::ObservableSessionProperties* sessionProperties, const avsCommon::sdkInterfaces::externalMediaPlayer::ObservablePlaybackStateProperties* playbackProperties); + /** + * Calls observer and provides the supplied changes related to RenderPlayerInfoCards for the active adapter. + */ + void notifyRenderPlayerInfoCardsObservers(); + /// The @c SpeakerManagerInterface used to change the volume when requested by @c ExternalMediaAdapterInterface. std::shared_ptr m_speakerManager; @@ -383,9 +403,21 @@ class ExternalMediaPlayer std::map> m_adapters; - /// The id of the player which currently has focus. + /// The id of the player which currently has focus. Access to @c m_playerInFocus is protected by @c + /// m_inFocusAdapterMutex. + /// TODO: ACSDK-2834 Consolidate m_playerInFocus and m_adapterInFocus. std::string m_playerInFocus; + /// The adapter with the @c m_playerInFocus which currently has focus. Access to @c m_adapterInFocus is + // protected by @c m_inFocusAdapterMutex. + std::shared_ptr m_adapterInFocus; + + /// Mutex to serialize access to the @c m_playerInFocus. + std::mutex m_inFocusAdapterMutex; + + /// Mutex to serialize access to @c m_adapters. + std::mutex m_adaptersMutex; + /// Mutex to serialize access to the observers. std::mutex m_observersMutex; @@ -394,6 +426,9 @@ class ExternalMediaPlayer std::shared_ptr> m_observers; + /// Observer for changes related to RenderPlayerInfoCards. + std::shared_ptr m_renderPlayerObserver; + /** * @c Executor which queues up operations from asynchronous API calls. * diff --git a/CapabilityAgents/ExternalMediaPlayer/src/ExternalMediaPlayer.cpp b/CapabilityAgents/ExternalMediaPlayer/src/ExternalMediaPlayer.cpp index 937300dd93..6b9f05796b 100644 --- a/CapabilityAgents/ExternalMediaPlayer/src/ExternalMediaPlayer.cpp +++ b/CapabilityAgents/ExternalMediaPlayer/src/ExternalMediaPlayer.cpp @@ -14,6 +14,9 @@ */ /// @file ExternalMediaPlayer.cpp +#include +#include + #include "ExternalMediaPlayer/ExternalMediaPlayer.h" #include @@ -402,14 +405,19 @@ std::shared_ptr ExternalMediaPlayer::preprocessDi return nullptr; } - auto adapterIt = m_adapters.find(playerId); - if (adapterIt == m_adapters.end()) { - ACSDK_ERROR(LX("preprocessDirectiveFailed").d("reason", "noAdapterForPlayerId").d(PLAYER_ID, playerId)); - sendExceptionEncounteredAndReportFailed(info, "Unrecogonized PlayerId."); - return nullptr; + std::shared_ptr adapter; + + { + std::lock_guard lock{m_adaptersMutex}; + auto adapterIt = m_adapters.find(playerId); + if (adapterIt == m_adapters.end()) { + ACSDK_ERROR(LX("preprocessDirectiveFailed").d("reason", "noAdapterForPlayerId").d(PLAYER_ID, playerId)); + sendExceptionEncounteredAndReportFailed(info, "Unrecogonized PlayerId."); + return nullptr; + } + adapter = adapterIt->second; } - auto adapter = adapterIt->second; if (!adapter) { ACSDK_ERROR(LX("preprocessDirectiveFailed").d("reason", "nullAdapter").d(PLAYER_ID, playerId)); sendExceptionEncounteredAndReportFailed(info, "nullAdapter."); @@ -567,60 +575,76 @@ DirectiveHandlerConfiguration ExternalMediaPlayer::getConfiguration() const { return g_configuration; } +void ExternalMediaPlayer::setObserver( + std::shared_ptr observer) { + ACSDK_DEBUG5(LX(__func__)); + std::lock_guard lock{m_observersMutex}; + m_renderPlayerObserver = observer; +} + +std::chrono::milliseconds ExternalMediaPlayer::getAudioItemOffset() { + ACSDK_DEBUG5(LX(__func__)); + std::lock_guard lock{m_inFocusAdapterMutex}; + if (!m_adapterInFocus) { + ACSDK_ERROR(LX("getAudioItemOffsetFailed").d("reason", "NoActiveAdapter").d("player", m_playerInFocus)); + return std::chrono::milliseconds::zero(); + } + return m_adapterInFocus->getOffset(); +} + void ExternalMediaPlayer::setPlayerInFocus(const std::string& playerInFocus) { ACSDK_DEBUG9(LX("setPlayerInFocus").d("playerInFocus", playerInFocus)); + std::lock_guard lock{m_inFocusAdapterMutex}; m_playerInFocus = playerInFocus; m_playbackRouter->setHandler(shared_from_this()); -} - -void ExternalMediaPlayer::onButtonPressed(PlaybackButton button) { if (!m_playerInFocus.empty()) { + std::lock_guard lock{m_adaptersMutex}; auto adapterIt = m_adapters.find(m_playerInFocus); - if (m_adapters.end() == adapterIt) { - // Should never reach here as playerInFocus is always set based on a contract with AVS. - ACSDK_ERROR(LX("AdapterNotFound").d("player", m_playerInFocus)); + ACSDK_INFO(LX(__func__).d("reason", "AdapterNotFound").d("name", m_playerInFocus)); return; } + m_adapterInFocus = adapterIt->second; + } +} - auto buttonIt = g_buttonToRequestType.find(button); +void ExternalMediaPlayer::onButtonPressed(PlaybackButton button) { + std::lock_guard lock{m_inFocusAdapterMutex}; + if (!m_adapterInFocus) { + ACSDK_ERROR(LX("onButtonPressedFailed").d("reason", "NoActiveAdapter").d("player", m_playerInFocus)); + return; + } - if (g_buttonToRequestType.end() == buttonIt) { - ACSDK_ERROR(LX("ButtonToRequestTypeNotFound").d("button", button)); - return; - } + auto buttonIt = g_buttonToRequestType.find(button); - adapterIt->second->handlePlayControl(buttonIt->second); + if (g_buttonToRequestType.end() == buttonIt) { + ACSDK_ERROR(LX("ButtonToRequestTypeNotFound").d("button", button)); + return; } + + m_adapterInFocus->handlePlayControl(buttonIt->second); } void ExternalMediaPlayer::onTogglePressed(PlaybackToggle toggle, bool action) { - if (!m_playerInFocus.empty()) { - auto adapterIt = m_adapters.find(m_playerInFocus); - - if (m_adapters.end() == adapterIt) { - // Should never reach here as playerInFocus is always set based on a contract with AVS. - ACSDK_ERROR(LX("AdapterNotFound").d("player", m_playerInFocus)); - return; - } - - auto toggleIt = g_toggleToRequestType.find(toggle); - - if (g_toggleToRequestType.end() == toggleIt) { - ACSDK_ERROR(LX("ToggleToRequestTypeNotFound").d("toggle", toggle)); - return; - } + std::lock_guard lock{m_inFocusAdapterMutex}; + if (!m_adapterInFocus) { + ACSDK_ERROR(LX("onTogglePressedFailed").d("reason", "NoActiveAdapter").d("player", m_playerInFocus)); + return; + } - auto adapter = adapterIt->second; + auto toggleIt = g_toggleToRequestType.find(toggle); + if (g_toggleToRequestType.end() == toggleIt) { + ACSDK_ERROR(LX("ToggleToRequestTypeNotFound").d("toggle", toggle)); + return; + } - // toggleStates map is - auto toggleStates = toggleIt->second; + // toggleStates map is + auto toggleStates = toggleIt->second; - if (action) { - adapter->handlePlayControl(toggleStates.first); - } else { - adapterIt->second->handlePlayControl(toggleStates.second); - } + if (action) { + m_adapterInFocus->handlePlayControl(toggleStates.first); + } else { + m_adapterInFocus->handlePlayControl(toggleStates.second); } } @@ -631,14 +655,19 @@ void ExternalMediaPlayer::doShutdown() { m_contextManager->setStateProvider(SESSION_STATE, nullptr); m_contextManager->setStateProvider(PLAYBACK_STATE, nullptr); - for (auto& adapter : m_adapters) { - if (!adapter.second) { - continue; + { + std::unique_lock lock{m_adaptersMutex}; + auto adaptersCopy = m_adapters; + m_adapters.clear(); + lock.unlock(); + + for (const auto& adapter : adaptersCopy) { + if (!adapter.second) { + continue; + } + adapter.second->shutdown(); } - adapter.second->shutdown(); } - - m_adapters.clear(); m_exceptionEncounteredSender.reset(); m_contextManager.reset(); m_playbackRouter.reset(); @@ -708,18 +737,28 @@ std::string ExternalMediaPlayer::provideSessionState() { rapidjson::Document state(rapidjson::kObjectType); rapidjson::Document::AllocatorType& stateAlloc = state.GetAllocator(); - state.AddMember(rapidjson::StringRef(PLAYER_IN_FOCUS), m_playerInFocus, stateAlloc); + { + std::lock_guard lock{m_inFocusAdapterMutex}; + state.AddMember(rapidjson::StringRef(PLAYER_IN_FOCUS), m_playerInFocus, stateAlloc); + } rapidjson::Value players(rapidjson::kArrayType); - for (const auto& adapter : m_adapters) { - if (!adapter.second) { - continue; + std::vector> updates; + { + std::lock_guard lock{m_adaptersMutex}; + for (const auto& adapter : m_adapters) { + if (!adapter.second) { + continue; + } + auto state = adapter.second->getState().sessionState; + rapidjson::Value playerJson = buildSessionState(state, stateAlloc); + players.PushBack(playerJson, stateAlloc); + updates.push_back({state.playerId, {state.loggedIn, state.userName}}); } - auto state = adapter.second->getState().sessionState; - rapidjson::Value playerJson = buildSessionState(state, stateAlloc); - players.PushBack(playerJson, stateAlloc); - ObservableSessionProperties update{state.loggedIn, state.userName}; - notifyObservers(state.playerId, &update); + } + + for (const auto& update : updates) { + notifyObservers(update.first, &update.second); } state.AddMember(rapidjson::StringRef(PLAYERS), players, stateAlloc); @@ -745,17 +784,26 @@ std::string ExternalMediaPlayer::providePlaybackState() { // Fetch actual PlaybackState from every player supported by the ExternalMediaPlayer. rapidjson::Value players(rapidjson::kArrayType); - for (const auto& adapter : m_adapters) { - if (!adapter.second) { - continue; + std::vector> updates; + { + std::lock_guard lock{m_adaptersMutex}; + for (const auto& adapter : m_adapters) { + if (!adapter.second) { + continue; + } + auto state = adapter.second->getState().playbackState; + rapidjson::Value playerJson = buildPlaybackState(state, stateAlloc); + players.PushBack(playerJson, stateAlloc); + updates.push_back({state.playerId, {state.state, state.trackName}}); } - auto state = adapter.second->getState().playbackState; - rapidjson::Value playerJson = buildPlaybackState(state, stateAlloc); - players.PushBack(playerJson, stateAlloc); - ObservablePlaybackStateProperties update{state.state, state.trackName}; - notifyObservers(state.playerId, &update); } + for (const auto& update : updates) { + notifyObservers(update.first, &update.second); + } + + notifyRenderPlayerInfoCardsObservers(); + state.AddMember(PLAYERS, players, stateAlloc); rapidjson::StringBuffer buffer; @@ -800,6 +848,7 @@ void ExternalMediaPlayer::createAdapters( contextManager, shared_from_this()); if (adapter) { + std::lock_guard lock{m_adaptersMutex}; m_adapters[entry.first] = adapter; } else { ACSDK_ERROR(LX("adapterCreationFailed").d(PLAYER_ID, entry.first)); @@ -866,6 +915,35 @@ void ExternalMediaPlayer::notifyObservers( } } +void ExternalMediaPlayer::notifyRenderPlayerInfoCardsObservers() { + ACSDK_DEBUG5(LX(__func__)); + + std::unique_lock lock{m_inFocusAdapterMutex}; + if (m_adapterInFocus) { + auto adapterState = m_adapterInFocus->getState(); + lock.unlock(); + std::stringstream ss{adapterState.playbackState.state}; + avsCommon::avs::PlayerActivity playerActivity = avsCommon::avs::PlayerActivity::IDLE; + ss >> playerActivity; + if (ss.fail()) { + ACSDK_ERROR(LX("notifyRenderPlayerInfoCardsFailed") + .d("reason", "invalidState") + .d("state", adapterState.playbackState.state)); + return; + } + RenderPlayerInfoCardsObserverInterface::Context context; + context.audioItemId = adapterState.playbackState.trackId; + context.offset = getAudioItemOffset(); + context.mediaProperties = shared_from_this(); + { + std::lock_guard lock{m_observersMutex}; + if (m_renderPlayerObserver) { + m_renderPlayerObserver->onRenderPlayerCardsInfoChanged(playerActivity, context); + } + } + } +} + } // namespace externalMediaPlayer } // namespace capabilityAgents } // namespace alexaClientSDK diff --git a/CapabilityAgents/ExternalMediaPlayer/test/ExternalMediaPlayerTest.cpp b/CapabilityAgents/ExternalMediaPlayer/test/ExternalMediaPlayerTest.cpp index 3372b507d2..9ff50b574e 100644 --- a/CapabilityAgents/ExternalMediaPlayer/test/ExternalMediaPlayerTest.cpp +++ b/CapabilityAgents/ExternalMediaPlayer/test/ExternalMediaPlayerTest.cpp @@ -268,6 +268,7 @@ class MockExternalMediaPlayerAdapter : public ExternalMediaAdapterInterface { MOCK_METHOD1(handleSetVolume, void(int8_t volume)); MOCK_METHOD1(handleSetMute, void(bool)); MOCK_METHOD0(getState, AdapterState()); + MOCK_METHOD0(getOffset, std::chrono::milliseconds()); private: /// MockExternalMediaPlayerAdapter private constructor. diff --git a/CapabilityAgents/MRM/CMakeLists.txt b/CapabilityAgents/MRM/CMakeLists.txt index a24c2b15a0..e1c2e92c19 100644 --- a/CapabilityAgents/MRM/CMakeLists.txt +++ b/CapabilityAgents/MRM/CMakeLists.txt @@ -6,6 +6,9 @@ include(../../build/BuildDefaults.cmake) add_subdirectory("src") add_subdirectory("test") -if (MRM) +if(MRM AND MRM_STANDALONE_APP) + add_subdirectory("MRMHandlerProxy") + add_subdirectory("MRMApp") +elseif (MRM) add_subdirectory("MRMHandler") endif() \ No newline at end of file diff --git a/CapabilityAgents/MRM/include/MRM/MRMCapabilityAgent.h b/CapabilityAgents/MRM/include/MRM/MRMCapabilityAgent.h index d7f79ffd70..8775551f34 100644 --- a/CapabilityAgents/MRM/include/MRM/MRMCapabilityAgent.h +++ b/CapabilityAgents/MRM/include/MRM/MRMCapabilityAgent.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -12,7 +12,6 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ - #ifndef ALEXA_CLIENT_SDK_CAPABILITYAGENTS_MRM_INCLUDE_MRM_MRMCAPABILITYAGENT_H_ #define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_MRM_INCLUDE_MRM_MRMCAPABILITYAGENT_H_ @@ -20,12 +19,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -46,21 +47,28 @@ class MRMCapabilityAgent : public avsCommon::avs::CapabilityAgent , public avsCommon::sdkInterfaces::SpeakerManagerObserverInterface , public avsCommon::sdkInterfaces::UserInactivityMonitorObserverInterface + , public avsCommon::sdkInterfaces::CallStateObserverInterface , public avsCommon::sdkInterfaces::CapabilityConfigurationInterface + , public avsCommon::sdkInterfaces::RenderPlayerInfoCardsProviderInterface , public avsCommon::utils::RequiresShutdown , public std::enable_shared_from_this { public: /** * Creates an instance of this Capability Agent. * - * @param handler The MRM Handler, which handles all MRM-specific implementation. - * @param speakerManager An object which allows us to detect changes to device Speakers. - * @param userInactivityMonitor An object which allows us to know about general user activity with the client. - * @param exceptionEncounteredSender An object which may send System.ExceptionEncountered Events to AVS if needed. - * @return A pointer to an object of this type, or nullptr if there were problems during construction. + * @param handler The MRM Handler, which handles all MRM-specific + * implementation. + * @param speakerManager An object which allows us to detect changes to device + * Speakers. + * @param userInactivityMonitor An object which allows us to know about + * general user activity with the client. + * @param exceptionEncounteredSender An object which may send + * System.ExceptionEncountered Events to AVS if needed. + * @return A pointer to an object of this type, or nullptr if there were + * problems during construction. */ static std::shared_ptr create( - std::unique_ptr mrmHandler, + std::shared_ptr mrmHandler, std::shared_ptr speakerManager, std::shared_ptr userInactivityMonitor, std::shared_ptr exceptionEncounteredSender); @@ -77,7 +85,8 @@ class MRMCapabilityAgent void cancelDirective(std::shared_ptr info) override; /// @} - /// @name Overridden DirectiveHandlerInterface methods (which CapabilityAgent derives from). + /// @name Overridden DirectiveHandlerInterface methods (which CapabilityAgent + /// derives from). /// @{ void handleDirectiveImmediately(std::shared_ptr directive) override; avsCommon::avs::DirectiveHandlerConfiguration getConfiguration() const override; @@ -96,20 +105,33 @@ class MRMCapabilityAgent void onUserInactivityReportSent() override; /// @} + /// @name Overridden @c CallStateObserverInterface methods + /// @{ + void onCallStateChange(avsCommon::sdkInterfaces::CallStateObserverInterface::CallState callState) override; + /// @} + /// @name Overridden CapabilityConfigurationInterface methods. /// @{ std::unordered_set> getCapabilityConfigurations() override; /// @} + /// @name Overridden RenderPlayerInfoCardsProviderInterface methods. + /// @{ + void setObserver( + std::shared_ptr observer) override; + /// @} + /// @name Overridden RequiresShutdown methods. /// @{ void doShutdown() override; /// @} /** - * Returns the string representation of the version of this MRM implementation. + * Returns the string representation of the version of this MRM + * implementation. * - * @return The string representation of the version of this MRM implementation. + * @return The string representation of the version of this MRM + * implementation. */ std::string getVersionString() const; @@ -117,13 +139,17 @@ class MRMCapabilityAgent /** * Constructor. * - * @param handler The MRM Handler, which handles all MRM-specific implementation. - * @param speakerManager An object which allows us to detect changes to device Speakers. - * @param userActivityMonitor An object which allows us to know about general user activity with the client. - * @param exceptionEncounteredSender An object which may send System.ExceptionEncountered Events to AVS if needed. + * @param handler The MRM Handler, which handles all MRM-specific + * implementation. + * @param speakerManager An object which allows us to detect changes to device + * Speakers. + * @param userActivityMonitor An object which allows us to know about general + * user activity with the client. + * @param exceptionEncounteredSender An object which may send + * System.ExceptionEncountered Events to AVS if needed. */ MRMCapabilityAgent( - std::unique_ptr handler, + std::shared_ptr handler, std::shared_ptr speakerManager, std::shared_ptr userInactivityMonitor, std::shared_ptr exceptionEncounteredSender); @@ -131,8 +157,10 @@ class MRMCapabilityAgent /** * @name Executor Thread Functions * - * These functions (and only these functions) are called by @c m_executor on a single worker thread. All other - * functions in this class can be called asynchronously, and pass data to the @c Executor thread through parameters + * These functions (and only these functions) are called by @c m_executor on a + * single worker thread. All other + * functions in this class can be called asynchronously, and pass data to the + * @c Executor thread through parameters * to lambda functions. No additional synchronization is needed. */ /// @{ @@ -152,14 +180,27 @@ class MRMCapabilityAgent void executeOnSpeakerSettingsChanged(const avsCommon::sdkInterfaces::SpeakerInterface::Type& type); /** - * This function handles when a System.UserInactivityReport has been sent to AVS. + * This function handles when the CallState has been changed. + */ + void executeOnCallStateChange(const avsCommon::sdkInterfaces::CallStateObserverInterface::CallState state); + + /** + * This function handles when a System.UserInactivityReport has been sent to + * AVS. */ void executeOnUserInactivityReportSent(); + /** + * This function sets the playerInfo observer. + * + * @param observer The RenderPlayerInfoCardsObserverInterface to be set. + */ + void executeSetObserver(std::shared_ptr observer); + /// @} /// Our MRM Handler. - std::unique_ptr m_mrmHandler; + std::shared_ptr m_mrmHandler; /// The Speaker Manager. std::shared_ptr m_speakerManager; /// The User Inactivity Monitor. diff --git a/CapabilityAgents/MRM/include/MRM/MRMHandlerInterface.h b/CapabilityAgents/MRM/include/MRM/MRMHandlerInterface.h index 81b82583d3..1c11e27c43 100644 --- a/CapabilityAgents/MRM/include/MRM/MRMHandlerInterface.h +++ b/CapabilityAgents/MRM/include/MRM/MRMHandlerInterface.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ #include #include +#include #include namespace alexaClientSDK { @@ -29,8 +30,10 @@ namespace capabilityAgents { namespace mrm { /** - * An interface which should be extended by a class which wishes to implement lower level MRM functionality, such as - * device / platform, local network, time synchronization, and audio playback. The api provided here is minimal and + * An interface which should be extended by a class which wishes to implement + * lower level MRM functionality, such as + * device / platform, local network, time synchronization, and audio playback. + * The api provided here is minimal and * sufficient with respect to integration with other AVS Client SDK components. */ class MRMHandlerInterface : public avsCommon::utils::RequiresShutdown { @@ -55,10 +58,17 @@ class MRMHandlerInterface : public avsCommon::utils::RequiresShutdown { /** * Function to handle an MRM Directive. * - * @param directive The MRM @c AVSDirective to be handled. + * @param nameSpace The namespace of the @c AVSDirective to be handled. + * @param name The name of the @c AVSDirective to be handled. + * @param messageId The messageId of the @c AVSDirective to be handled. + * @param payload The payload of the @c AVSDirective to be handled. * @return Whether the Directive was handled successfully. */ - virtual bool handleDirective(std::shared_ptr directive) = 0; + virtual bool handleDirective( + const std::string& nameSpace, + const std::string& name, + const std::string& messageId, + const std::string& payload) = 0; /** * Function to handle if a speaker setting has changed. MRM only needs to know the type of the speaker. @@ -71,6 +81,19 @@ class MRMHandlerInterface : public avsCommon::utils::RequiresShutdown { * Function to be called when a System.UserInactivityReportSent Event has been sent to AVS. */ virtual void onUserInactivityReportSent() = 0; + + /** + * Function to be called when a comms CallState has been changed. + */ + virtual void onCallStateChange(bool active) = 0; + + /** + * Function to set the RenderPlayerInfoCardsProviderInterface. + * + * @param observer The RenderPlayerInfoCardsObserverInterface to be set. + */ + virtual void setObserver( + std::shared_ptr observer) = 0; }; inline MRMHandlerInterface::MRMHandlerInterface(const std::string& shutdownName) : diff --git a/CapabilityAgents/MRM/src/CMakeLists.txt b/CapabilityAgents/MRM/src/CMakeLists.txt index a926b043f4..9f8fd4568f 100644 --- a/CapabilityAgents/MRM/src/CMakeLists.txt +++ b/CapabilityAgents/MRM/src/CMakeLists.txt @@ -8,9 +8,11 @@ target_include_directories(MRM PUBLIC target_link_libraries(MRM AVSCommon) -if (MRM) +if (MRM AND MRM_WITH_IPC) + target_link_libraries(MRM MRMHandlerProxy) +elseif (MRM) target_link_libraries(MRM MRMHandler) endif() # install target -asdk_install() +asdk_install() \ No newline at end of file diff --git a/CapabilityAgents/MRM/src/MRMCapabilityAgent.cpp b/CapabilityAgents/MRM/src/MRMCapabilityAgent.cpp index 700e4216a7..03b97001df 100644 --- a/CapabilityAgents/MRM/src/MRMCapabilityAgent.cpp +++ b/CapabilityAgents/MRM/src/MRMCapabilityAgent.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -38,11 +38,13 @@ using namespace avsCommon::utils; /// The namespace for this capability agent. static const std::string NAMESPACE_STR = "MRM"; -/// The wildcard namespace signature so the DirectiveSequencer will send us all Directives under the namespace. +/// The wildcard namespace signature so the DirectiveSequencer will send us all +/// Directives under the namespace. static const avsCommon::avs::NamespaceAndName WHA_NAMESPACE_WILDCARD{NAMESPACE_STR, "*"}; /** - * Creates the MRM capability configuration, needed to register with Device Capability Framework. + * Creates the MRM capability configuration, needed to register with Device + * Capability Framework. * * @return The MRM capability configuration. */ @@ -52,7 +54,7 @@ static std::shared_ptr getMRMCapabilityConfiguration() } std::shared_ptr MRMCapabilityAgent::create( - std::unique_ptr mrmHandler, + std::shared_ptr mrmHandler, std::shared_ptr speakerManager, std::shared_ptr userInactivityMonitor, std::shared_ptr exceptionEncounteredSender) { @@ -75,8 +77,8 @@ std::shared_ptr MRMCapabilityAgent::create( return nullptr; } - auto agent = std::shared_ptr(new MRMCapabilityAgent( - std::move(mrmHandler), speakerManager, userInactivityMonitor, exceptionEncounteredSender)); + auto agent = std::shared_ptr( + new MRMCapabilityAgent(mrmHandler, speakerManager, userInactivityMonitor, exceptionEncounteredSender)); userInactivityMonitor->addObserver(agent); speakerManager->addSpeakerManagerObserver(agent); @@ -85,13 +87,13 @@ std::shared_ptr MRMCapabilityAgent::create( } MRMCapabilityAgent::MRMCapabilityAgent( - std::unique_ptr handler, + std::shared_ptr handler, std::shared_ptr speakerManager, std::shared_ptr userInactivityMonitor, std::shared_ptr exceptionEncounteredSender) : CapabilityAgent(NAMESPACE_STR, exceptionEncounteredSender), RequiresShutdown("MRMCapabilityAgent"), - m_mrmHandler{std::move(handler)}, + m_mrmHandler{handler}, m_speakerManager{speakerManager}, m_userInactivityMonitor{userInactivityMonitor} { ACSDK_DEBUG5(LX(__func__)); @@ -151,6 +153,11 @@ void MRMCapabilityAgent::onSpeakerSettingsChanged( m_executor.submit([this, type]() { executeOnSpeakerSettingsChanged(type); }); } +void MRMCapabilityAgent::onCallStateChange(avsCommon::sdkInterfaces::CallStateObserverInterface::CallState callState) { + ACSDK_DEBUG5(LX(__func__).d("callState", callState)); + m_executor.submit([this, callState]() { executeOnCallStateChange(callState); }); +} + std::string MRMCapabilityAgent::getVersionString() const { ACSDK_DEBUG5(LX(__func__)); return m_mrmHandler->getVersionString(); @@ -159,7 +166,11 @@ std::string MRMCapabilityAgent::getVersionString() const { void MRMCapabilityAgent::executeHandleDirectiveImmediately(std::shared_ptr info) { ACSDK_DEBUG5(LX(__func__)); - if (m_mrmHandler->handleDirective(info->directive)) { + if (m_mrmHandler->handleDirective( + info->directive->getNamespace(), + info->directive->getName(), + info->directive->getMessageId(), + info->directive->getPayload())) { if (info->result) { info->result->setCompleted(); } @@ -188,12 +199,35 @@ void MRMCapabilityAgent::executeOnUserInactivityReportSent() { m_mrmHandler->onUserInactivityReportSent(); } +void MRMCapabilityAgent::executeSetObserver( + std::shared_ptr observer) { + ACSDK_DEBUG5(LX(__func__)); + m_mrmHandler->setObserver(observer); +} + +void MRMCapabilityAgent::executeOnCallStateChange( + const avsCommon::sdkInterfaces::CallStateObserverInterface::CallState callState) { + ACSDK_DEBUG5(LX(__func__)); + bool active = + (CallStateObserverInterface::CallState::CONNECTING == callState || + CallStateObserverInterface::CallState::INBOUND_RINGING == callState || + CallStateObserverInterface::CallState::CALL_CONNECTED == callState); + + m_mrmHandler->onCallStateChange(active); +} + std::unordered_set> MRMCapabilityAgent::getCapabilityConfigurations() { std::unordered_set> configs; configs.insert(getMRMCapabilityConfiguration()); return configs; } +void MRMCapabilityAgent::setObserver( + std::shared_ptr observer) { + ACSDK_DEBUG5(LX(__func__)); + m_executor.submit([this, observer]() { executeSetObserver(observer); }); +} + void MRMCapabilityAgent::doShutdown() { ACSDK_DEBUG5(LX(__func__)); m_speakerManager->removeSpeakerManagerObserver(shared_from_this()); @@ -205,4 +239,4 @@ void MRMCapabilityAgent::doShutdown() { } // namespace mrm } // namespace capabilityAgents -} // namespace alexaClientSDK \ No newline at end of file +} // namespace alexaClientSDK diff --git a/CapabilityAgents/MRM/test/MRMCapabilityAgentTest.cpp b/CapabilityAgents/MRM/test/MRMCapabilityAgentTest.cpp index 68076f94b6..0e340c6981 100644 --- a/CapabilityAgents/MRM/test/MRMCapabilityAgentTest.cpp +++ b/CapabilityAgents/MRM/test/MRMCapabilityAgentTest.cpp @@ -148,8 +148,10 @@ class MockMRMHandler : public MRMHandlerInterface { } MOCK_CONST_METHOD0(getVersionString, std::string()); - MOCK_METHOD1(handleDirective, bool(std::shared_ptr directive)); + MOCK_METHOD4(handleDirective, bool(const std::string&, const std::string&, const std::string&, const std::string&)); MOCK_METHOD0(doShutdown, void()); + MOCK_METHOD1(onCallStateChange, void(bool)); + MOCK_METHOD1(setObserver, void(std::shared_ptr)); /** * overridden function, minus the explicit override, since gtest does not use override. @@ -296,12 +298,12 @@ TEST_F(MRMCapabilityAgentTest, test_handleMRMDirective) { std::shared_ptr directive = std::move(directivePair.first); // Test that the MRMHandler will receive the Directive and fail to handle it. - EXPECT_CALL(*m_mockMRMHandlerPtr, handleDirective(_)).WillOnce(Return(false)); + EXPECT_CALL(*m_mockMRMHandlerPtr, handleDirective(_, _, _, _)).WillOnce(Return(false)); m_mrmCA->handleDirectiveImmediately(directive); ASSERT_TRUE(m_exceptionSender->wait(WAIT_FOR_INVOCATION_LONG_TIMEOUT)); // Test that the MRMHandler will receive the Directive and successfully handle it. - EXPECT_CALL(*m_mockMRMHandlerPtr, handleDirective(_)).WillOnce(Return(true)); + EXPECT_CALL(*m_mockMRMHandlerPtr, handleDirective(_, _, _, _)).WillOnce(Return(true)); m_mrmCA->handleDirectiveImmediately(directive); ASSERT_FALSE(m_exceptionSender->wait(WAIT_FOR_INVOCATION_SHORT_TIMEOUT)); } diff --git a/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp b/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp index ca1efda9cf..8ea60e5138 100644 --- a/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp +++ b/CapabilityAgents/SpeakerManager/src/SpeakerManager.cpp @@ -508,6 +508,14 @@ bool SpeakerManager::executeSetVolume( ACSDK_ERROR(LX("executeSetVolumeFailed").d("reason", "noSpeakersWithType").d("type", type)); return false; } + + SpeakerInterface::SpeakerSettings settings; + if (!executeGetSpeakerSettings(type, &settings)) { + ACSDK_ERROR(LX("executeSetVolumeFailed").d("reason", "speakerSettingsInconsistent")); + return false; + } + const int8_t previousVolume = settings.volume; + // Go through list of Speakers with SpeakerInterface::Type equal to type, and call setVolume. auto beginIteratorAndEndIterator = m_speakerMap.equal_range(type); auto begin = beginIteratorAndEndIterator.first; @@ -521,11 +529,9 @@ bool SpeakerManager::executeSetVolume( } } - SpeakerInterface::SpeakerSettings settings; - // All initialized speakers controlled by directives with the same type should have the same state. if (!validateSpeakerSettingsConsistency(type, &settings)) { - ACSDK_ERROR(LX("executeSetVolume").d("reason", "speakerSettingsInconsistent")); + ACSDK_ERROR(LX("executeSetVolumeFailed").d("reason", "speakerSettingsInconsistent")); return false; } @@ -533,6 +539,9 @@ bool SpeakerManager::executeSetVolume( if (forceNoNotifications) { ACSDK_INFO(LX("executeSetVolume").m("Skipping sending notifications").d("reason", "forceNoNotifications")); + } else if (previousVolume == settings.volume && SpeakerManagerObserverInterface::Source::LOCAL_API == source) { + ACSDK_INFO(LX("executeAdjustVolume").m("Skipping sending event").d("reason", "volumeUnchanged")); + executeNotifyObserver(source, type, settings); } else { executeNotifySettingsChanged(settings, VOLUME_CHANGED, source, type); } @@ -575,12 +584,11 @@ bool SpeakerManager::executeAdjustVolume( return false; } SpeakerInterface::SpeakerSettings settings; - - // All initialized speakers controlled by directives with the same type should have the same state. - if (!validateSpeakerSettingsConsistency(type, &settings)) { - ACSDK_ERROR(LX("executeAdjustVolumeFailed").d("reason", "initialSpeakerSettingsInconsistent")); + if (!executeGetSpeakerSettings(type, &settings)) { + ACSDK_ERROR(LX("executeAdjustVolumeFailed").d("reason", "speakerSettingsInconsistent")); return false; } + const int8_t previousVolume = settings.volume; // Go through list of Speakers with SpeakerInterface::Type equal to type, and call adjustVolume. auto beginIteratorAndEndIterator = m_speakerMap.equal_range(type); @@ -606,6 +614,9 @@ bool SpeakerManager::executeAdjustVolume( if (forceNoNotifications) { ACSDK_INFO(LX("executeAdjustVolume").m("Skipping sending notifications").d("reason", "forceNoNotifications")); + } else if (previousVolume == settings.volume && SpeakerManagerObserverInterface::Source::LOCAL_API == source) { + ACSDK_INFO(LX("executeAdjustVolume").m("Skipping sending event").d("reason", "volumeUnchanged")); + executeNotifyObserver(source, type, settings); } else { executeNotifySettingsChanged(settings, VOLUME_CHANGED, source, type); } diff --git a/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp b/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp index 272cde00bc..baa97a136d 100644 --- a/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp +++ b/CapabilityAgents/SpeakerManager/test/SpeakerManagerTest.cpp @@ -383,6 +383,79 @@ TEST_F(SpeakerManagerTest, test_adjustVolumeOutOfSync) { ASSERT_FALSE(future.get()); } +/* + * Test adjustVolume when the adjusted volume is unchanged. Should not send an event. + */ +TEST_F(SpeakerManagerTest, test_eventNotSentWhenAdjustVolumeUnchanged) { + auto speaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); + speaker->DelegateToReal(); + EXPECT_CALL(*speaker, adjustVolume(AVS_ADJUST_VOLUME_MIN)).Times(Exactly(1)); + + std::vector> speakers = {speaker}; + + m_speakerManager = + SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + + // The test adjusts the volume by AVS_ADJUST_VOLUME_MIN, which results in the lowest volume possible. + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; + m_speakerManager->addSpeakerManagerObserver(m_observer); + + for (auto type : getUniqueTypes(speakers)) { + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::LOCAL_API, type, expectedSettings)) + .Times(Exactly(1)); + if (SpeakerInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) + .Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } + + std::future future = m_speakerManager->adjustVolume(type, AVS_ADJUST_VOLUME_MIN); + ASSERT_TRUE(future.get()); + } +} + +/* + * Test setVolume when the new volume is unchanged. Should not send an event. + */ +TEST_F(SpeakerManagerTest, test_eventNotSentWhenSetVolumeUnchanged) { + auto speaker = std::make_shared>(SpeakerInterface::Type::AVS_SPEAKER_VOLUME); + speaker->DelegateToReal(); + EXPECT_CALL(*speaker, setVolume(AVS_SET_VOLUME_MIN)).Times(Exactly(1)); + + std::vector> speakers = {speaker}; + + m_speakerManager = + SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); + + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; + m_speakerManager->addSpeakerManagerObserver(m_observer); + + for (auto type : getUniqueTypes(speakers)) { + EXPECT_CALL( + *m_observer, + onSpeakerSettingsChanged(SpeakerManagerObserverInterface::Source::LOCAL_API, type, expectedSettings)) + .Times(Exactly(1)); + if (SpeakerInterface::Type::AVS_SPEAKER_VOLUME == type) { + EXPECT_CALL(*m_mockMessageSender, sendMessage(_)).Times(Exactly(0)); + EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, _, StateRefreshPolicy::NEVER, _)) + .Times(AnyNumber()); + EXPECT_CALL( + *m_mockContextManager, + setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)) + .Times(Exactly(1)); + } + + std::future future = m_speakerManager->setVolume(type, AVS_SET_VOLUME_MIN); + ASSERT_TRUE(future.get()); + } +} + /* * Test setMute when the speaker interfaces are out of sync. The operation should fail. */ @@ -577,15 +650,15 @@ TEST_P(SpeakerManagerTest, test_adjustVolume) { for (auto& typeOfSpeaker : GetParam()) { auto speaker = std::make_shared>(typeOfSpeaker); speaker->DelegateToReal(); - EXPECT_CALL(*speaker, adjustVolume(AVS_ADJUST_VOLUME_MIN)).Times(Exactly(1)); + EXPECT_CALL(*speaker, adjustVolume(AVS_SET_VOLUME_MAX)).Times(Exactly(1)); speakers.push_back(speaker); } m_speakerManager = SpeakerManager::create(speakers, m_mockContextManager, m_mockMessageSender, m_mockExceptionSender); - // The test adjusts the volume by AVS_ADJUST_VOLUME_MIN, which results in the lowest volume possible. - SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MIN, UNMUTE}; + // The test adjusts the volume by AVS_ADJUST_VOLUME_MAX, which results in the lowest volume possible. + SpeakerInterface::SpeakerSettings expectedSettings{AVS_SET_VOLUME_MAX, UNMUTE}; m_speakerManager->addSpeakerManagerObserver(m_observer); for (auto type : getUniqueTypes(speakers)) { @@ -596,7 +669,7 @@ TEST_P(SpeakerManagerTest, test_adjustVolume) { EXPECT_CALL(*m_mockContextManager, setState(VOLUME_STATE, generateVolumeStateJson(expectedSettings), StateRefreshPolicy::NEVER, _)).Times(Exactly(1)); } - std::future future = m_speakerManager->adjustVolume(type, AVS_ADJUST_VOLUME_MIN); + std::future future = m_speakerManager->adjustVolume(type, AVS_ADJUST_VOLUME_MAX); ASSERT_TRUE(future.get()); } } diff --git a/CapabilityAgents/SpeechSynthesizer/include/SpeechSynthesizer/SpeechSynthesizer.h b/CapabilityAgents/SpeechSynthesizer/include/SpeechSynthesizer/SpeechSynthesizer.h index 05364001b2..88dfa5dc03 100644 --- a/CapabilityAgents/SpeechSynthesizer/include/SpeechSynthesizer/SpeechSynthesizer.h +++ b/CapabilityAgents/SpeechSynthesizer/include/SpeechSynthesizer/SpeechSynthesizer.h @@ -488,6 +488,7 @@ class SpeechSynthesizer avsCommon::avs::FocusState m_currentFocus; /// @c SpeakDirectiveInfo instance for the @c AVSDirective currently being handled. + /// Serialized by only accessing it from tasks running under m_executor. std::shared_ptr m_currentInfo; /// Mutex to serialize access to m_currentState, m_desiredState, and m_waitOnStateChange. @@ -508,10 +509,15 @@ class SpeechSynthesizer */ std::mutex m_speakDirectiveInfoMutex; - /// Queue which holds the directives to be processed. + /// Queue which holds the directives to be processed. @c m_speakInfoQueueMutex must he acquired when + /// accessing or modifying this member. std::deque> m_speakInfoQueue; - /// Serializes access to @c m_speakInfoQueue + /// Flag indicating if doShutdown() has been called. @c m_speakInfoQueueMutex must he acquired when + /// accessing or modifying this member. + bool m_isShuttingDown; + + /// Serializes access to @c m_speakInfoQueue. std::mutex m_speakInfoQueueMutex; /// This flag indicates whether the initial dialog UX State has been received. diff --git a/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp b/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp index 50ee59503d..06f5c02fa2 100644 --- a/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp +++ b/CapabilityAgents/SpeechSynthesizer/src/SpeechSynthesizer.cpp @@ -212,25 +212,31 @@ void SpeechSynthesizer::onFocusChanged(FocusState newFocus) { break; } - auto messageId = (m_currentInfo && m_currentInfo->directive) ? m_currentInfo->directive->getMessageId() : ""; m_executor.submit([this]() { executeStateChange(); }); // Block until we achieve the desired state. if (m_waitOnStateChange.wait_for( lock, STATE_CHANGE_TIMEOUT, [this]() { return m_currentState == m_desiredState; })) { ACSDK_DEBUG9(LX("onFocusChangedSuccess")); } else { - ACSDK_ERROR(LX("onFocusChangeFailed").d("reason", "stateChangeTimeout").d("messageId", messageId)); - if (m_currentInfo) { - lock.unlock(); - sendExceptionEncounteredAndReportFailed( - m_currentInfo, avsCommon::avs::ExceptionErrorType::INTERNAL_ERROR, "stateChangeTimeout"); - } + ACSDK_ERROR(LX("onFocusChangeFailed").d("reason", "stateChangeTimeout")); + m_executor.submit([this]() { + if (m_currentInfo) { + std::string error{"stateChangeTimeout"}; + if (m_currentInfo->directive) { + error += " messageId=" + m_currentInfo->directive->getMessageId(); + } + sendExceptionEncounteredAndReportFailed( + m_currentInfo, avsCommon::avs::ExceptionErrorType::INTERNAL_ERROR, error); + } + }); } - m_executor.submit([this, messageId]() { - auto speakInfo = getSpeakDirectiveInfo(messageId); - if (speakInfo && speakInfo->isDelayedCancel) { - executeCancel(speakInfo); + m_executor.submit([this]() { + if (m_currentInfo && m_currentInfo->directive) { + auto speakInfo = getSpeakDirectiveInfo(m_currentInfo->directive->getMessageId()); + if (speakInfo && speakInfo->isDelayedCancel) { + executeCancel(speakInfo); + } } }); } @@ -352,6 +358,7 @@ SpeechSynthesizer::SpeechSynthesizer( m_desiredState{SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED}, m_currentFocus{FocusState::NONE}, m_isAlreadyStopping{false}, + m_isShuttingDown{false}, m_initialDialogUXStateReceived{false} { m_capabilityConfigurations.insert(getSpeechSynthesizerCapabilityConfiguration()); } @@ -367,6 +374,10 @@ std::shared_ptr getSpeechSynthesizerCapabilityConfigura void SpeechSynthesizer::doShutdown() { ACSDK_DEBUG9(LX("doShutdown")); + { + std::lock_guard lock(m_speakInfoQueueMutex); + m_isShuttingDown = true; + } m_speechPlayer->setObserver(nullptr); { std::unique_lock lock(m_mutex); @@ -379,7 +390,6 @@ void SpeechSynthesizer::doShutdown() { lock.unlock(); stopPlaying(); releaseForegroundFocus(); - lock.lock(); m_currentState = SpeechSynthesizerObserverInterface::SpeechSynthesizerState::FINISHED; } @@ -705,7 +715,7 @@ void SpeechSynthesizer::executePlaybackFinished() { resetCurrentInfo(); { std::lock_guard lock_guard(m_speakInfoQueueMutex); - if (!m_speakInfoQueue.empty()) { + if (!m_isShuttingDown && !m_speakInfoQueue.empty()) { m_speakInfoQueue.pop_front(); if (!m_speakInfoQueue.empty()) { executeHandleAfterValidation(m_speakInfoQueue.front()); diff --git a/CapabilityAgents/TemplateRuntime/include/TemplateRuntime/TemplateRuntime.h b/CapabilityAgents/TemplateRuntime/include/TemplateRuntime/TemplateRuntime.h index 174ff452a2..b5ebdeaf87 100644 --- a/CapabilityAgents/TemplateRuntime/include/TemplateRuntime/TemplateRuntime.h +++ b/CapabilityAgents/TemplateRuntime/include/TemplateRuntime/TemplateRuntime.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -17,18 +17,19 @@ #define ALEXA_CLIENT_SDK_CAPABILITYAGENTS_TEMPLATERUNTIME_INCLUDE_TEMPLATERUNTIME_TEMPLATERUNTIME_H_ #include +#include #include -#include #include +#include #include #include #include #include -#include -#include #include #include +#include +#include #include #include #include @@ -54,7 +55,7 @@ namespace templateRuntime { class TemplateRuntime : public avsCommon::avs::CapabilityAgent , public avsCommon::utils::RequiresShutdown - , public avsCommon::sdkInterfaces::AudioPlayerObserverInterface + , public avsCommon::sdkInterfaces::RenderPlayerInfoCardsObserverInterface , public avsCommon::sdkInterfaces::CapabilityConfigurationInterface , public avsCommon::sdkInterfaces::DialogUXStateObserverInterface , public std::enable_shared_from_this { @@ -62,13 +63,14 @@ class TemplateRuntime /** * Create an instance of @c TemplateRuntime. * - * @param audioPlayerInterface The object to use for subscribing @c TemplateRuntime as an observer of - * the @c AudioPlayer. + * @param renderPlayerInfoCardsInterfaces A set of objects to use for subscribing @c TemplateRuntime as an + * observer of changes for RenderPlayerInfoCards. * @param exceptionSender The object to use for sending AVS Exception messages. * @return @c nullptr if the inputs are not defined, else a new instance of @c TemplateRuntime. */ static std::shared_ptr create( - std::shared_ptr audioPlayerInterface, + const std::unordered_set>& + renderPlayerInfoCardsInterfaces, std::shared_ptr focusManager, std::shared_ptr exceptionSender); @@ -91,9 +93,9 @@ class TemplateRuntime void onFocusChanged(avsCommon::avs::FocusState newFocus) override; /// @} - /// @name AudioPlayerObserverInterface Functions + /// @name RenderPlayerInfoCardsObserverInterface Functions /// @{ - void onPlayerActivityChanged(avsCommon::avs::PlayerActivity state, const Context& context) override; + void onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity state, const Context& context) override; /// @} /// @name DialogUXStateObserverInterface Functions @@ -194,12 +196,13 @@ class TemplateRuntime /** * Constructor. * - * @param audioPlayerInterface The object to use for subscribing @c TemplateRuntime as an observer of - * AudioPlayer. + * @param renderPlayerInfoCardsInterfaces A set of objects to use for subscribing @c TemplateRuntime as an + * observer of changes for RenderPlayerInfoCards. * @param exceptionSender The object to use for sending AVS Exception messages. */ TemplateRuntime( - std::shared_ptr audioPlayerInterface, + const std::unordered_set>& + renderPlayerInfoCardsInterfaces, std::shared_ptr focusManager, std::shared_ptr exceptionSender); @@ -342,19 +345,26 @@ class TemplateRuntime std::unordered_set> m_observers; /* - * This is used to store the current executing @c AudioItem based on the callbacks from the - * AudioPlayerObserverInterface. + * This is a map that is used to store the current executing @c AudioItem based on the callbacks from the + * @c RenderPlayerInfoCardsProviderInterface. */ - AudioItemPair m_audioItemInExecution; + std::unordered_map, AudioItemPair> + m_audioItemsInExecution; + + /// The current active RenderPlayerInfoCards provider that has the matching audioItemId. + std::shared_ptr m_activeRenderPlayerInfoCardsProvider; /* * This queue is for storing the @c RenderPlayerInfo directives when its audioItemId does not match the audioItemId * in execution in the @c AudioPlayer. */ - std::queue m_audioItems; + std::deque m_audioItems; - /// This is to store the @c AudioPlayerInfo to be passed to the observers in the renderPlayerInfoCard callback. - avsCommon::sdkInterfaces::TemplateRuntimeObserverInterface::AudioPlayerInfo m_audioPlayerInfo; + /// This map is to store the @c AudioPlayerInfo to be passed to the observers in the renderPlayerInfoCard callback. + std::unordered_map< + std::shared_ptr, + avsCommon::sdkInterfaces::TemplateRuntimeObserverInterface::AudioPlayerInfo> + m_audioPlayerInfo; /// The directive corresponding to the RenderTemplate directive. std::shared_ptr m_lastDisplayedDirective; @@ -370,11 +380,11 @@ class TemplateRuntime /// @} /* - * This is an interface to the @c AudioPlayer. The @c TemplateRuntime CA used this interface to add and remove - * itself as an observer to the @c AudioPlayer. The interface is also used to query the latest offset of the audio - * playback in the @c AudioPlayer. + * This is a set of interfaces to the @c RenderPlayerInfoCardsProviderInterface. The @c TemplateRuntime CA + * used this interface to add and remove itself as an observer. */ - std::shared_ptr m_audioPlayerInterface; + std::unordered_set> + m_renderPlayerInfoCardsInterfaces; /// The @c FocusManager used to manage usage of the visual channel. std::shared_ptr m_focusManager; diff --git a/CapabilityAgents/TemplateRuntime/src/TemplateRuntime.cpp b/CapabilityAgents/TemplateRuntime/src/TemplateRuntime.cpp index 33adb544cc..16dacdd229 100644 --- a/CapabilityAgents/TemplateRuntime/src/TemplateRuntime.cpp +++ b/CapabilityAgents/TemplateRuntime/src/TemplateRuntime.cpp @@ -102,14 +102,10 @@ static const std::chrono::milliseconds DEFAULT_AUDIO_STOPPED_PAUSED_TIMEOUT_MS{6 static std::shared_ptr getTemplateRuntimeCapabilityConfiguration(); std::shared_ptr TemplateRuntime::create( - std::shared_ptr audioPlayerInterface, + const std::unordered_set>& + renderPlayerInfoCardInterface, std::shared_ptr focusManager, std::shared_ptr exceptionSender) { - if (!audioPlayerInterface) { - ACSDK_ERROR(LX("createFailed").d("reason", "nullAudioPlayerInterface")); - return nullptr; - } - if (!focusManager) { ACSDK_ERROR(LX("createFailed").d("reason", "nullFocusManager")); return nullptr; @@ -120,13 +116,21 @@ std::shared_ptr TemplateRuntime::create( return nullptr; } std::shared_ptr templateRuntime( - new TemplateRuntime(audioPlayerInterface, focusManager, exceptionSender)); + new TemplateRuntime(renderPlayerInfoCardInterface, focusManager, exceptionSender)); if (!templateRuntime->initialize()) { ACSDK_ERROR(LX("createFailed").d("reason", "Initialization error.")); return nullptr; } - audioPlayerInterface->addObserver(templateRuntime); + + for (const auto& renderPlayerInfoCardProvider : renderPlayerInfoCardInterface) { + if (!renderPlayerInfoCardProvider) { + ACSDK_ERROR(LX("createFailed").d("reason", "nullRenderPlayerInfoCardInterface")); + return nullptr; + } + renderPlayerInfoCardProvider->setObserver(templateRuntime); + } + return templateRuntime; } @@ -196,8 +200,8 @@ void TemplateRuntime::onFocusChanged(avsCommon::avs::FocusState newFocus) { m_executor.submit([this, newFocus]() { executeOnFocusChangedEvent(newFocus); }); } -void TemplateRuntime::onPlayerActivityChanged(avsCommon::avs::PlayerActivity state, const Context& context) { - ACSDK_DEBUG5(LX("onPlayerActivityChanged")); +void TemplateRuntime::onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity state, const Context& context) { + ACSDK_DEBUG5(LX("onRenderPlayerCardsInfoChanged")); m_executor.submit([this, state, context]() { ACSDK_DEBUG5(LX("onPlayerActivityChangedInExecutor")); executeAudioPlayerInfoUpdates(state, context); @@ -252,7 +256,8 @@ void TemplateRuntime::removeObserver( } TemplateRuntime::TemplateRuntime( - std::shared_ptr audioPlayerInterface, + const std::unordered_set>& + renderPlayerInfoCardsInterfaces, std::shared_ptr focusManager, std::shared_ptr exceptionSender) : CapabilityAgent{NAMESPACE, exceptionSender}, @@ -260,7 +265,7 @@ TemplateRuntime::TemplateRuntime( m_isRenderTemplateLastReceived{false}, m_focus{FocusState::NONE}, m_state{TemplateRuntime::State::IDLE}, - m_audioPlayerInterface{audioPlayerInterface}, + m_renderPlayerInfoCardsInterfaces{renderPlayerInfoCardsInterfaces}, m_focusManager{focusManager} { m_capabilityConfigurations.insert(getTemplateRuntimeCapabilityConfiguration()); } @@ -278,8 +283,13 @@ void TemplateRuntime::doShutdown() { m_executor.shutdown(); m_focusManager.reset(); m_observers.clear(); - m_audioPlayerInterface->removeObserver(shared_from_this()); - m_audioPlayerInterface.reset(); + m_activeRenderPlayerInfoCardsProvider.reset(); + m_audioItemsInExecution.clear(); + m_audioPlayerInfo.clear(); + for (const auto renderPlayerInfoCardsInterface : m_renderPlayerInfoCardsInterfaces) { + renderPlayerInfoCardsInterface->setObserver(nullptr); + } + m_renderPlayerInfoCardsInterfaces.clear(); } void TemplateRuntime::removeDirective(std::shared_ptr info) { @@ -342,29 +352,44 @@ void TemplateRuntime::handleRenderPlayerInfoDirective(std::shared_ptrgetAudioItemOffset(); + executeStopTimer(); + executeDisplayCardEvent(info); + // Since there'a match, we can safely empty m_audioItems. + m_audioItems.clear(); + break; + } + } + + if (std::string::npos == found) { ACSDK_DEBUG3(LX("handleRenderPlayerInfoDirectiveInExecutor") .d("audioItemId", audioItemId) .m("Not matching audioItemId in execution.")); + AudioItemPair itemPair{audioItemId, info}; if (m_audioItems.size() == MAXIMUM_QUEUE_SIZE) { - // Something is wrong, so we pop the front of the queue and log an error. - auto discardedAudioItem = m_audioItems.front(); - m_audioItems.pop(); + // Something is wrong, so we pop the back of the queue and log an error. + auto discardedAudioItem = m_audioItems.back(); + m_audioItems.pop_back(); ACSDK_ERROR(LX("handleRenderPlayerInfoDirective") .d("reason", "queueIsFull") .d("discardedAudioItemId", discardedAudioItem.audioItemId)); } - m_audioItems.push(itemPair); - } else { - ACSDK_DEBUG3(LX("handleRenderPlayerInfoDirectiveInExecutor") - .d("audioItemId", audioItemId) - .m("Matching audioItemId in execution.")); - m_audioItemInExecution.directive = info; - m_audioPlayerInfo.offset = m_audioPlayerInterface->getAudioItemOffset(); - executeStopTimer(); - executeDisplayCardEvent(info); + m_audioItems.push_front(itemPair); } + setHandlingCompleted(info); }); } @@ -399,7 +424,14 @@ void TemplateRuntime::executeAudioPlayerInfoUpdates(avsCommon::avs::PlayerActivi return; } - if (m_audioPlayerInfo.audioPlayerState == state && m_audioItemInExecution.audioItemId == context.audioItemId) { + if (!context.mediaProperties) { + ACSDK_ERROR(LX("executeAudioPlayerInfoUpdatesFailed").d("reason", "nullRenderPlayerInfoCardsInterface")); + return; + } + + const auto& currentRenderPlayerInfoCardsProvider = context.mediaProperties; + if (m_audioPlayerInfo[currentRenderPlayerInfoCardsProvider].audioPlayerState == state && + m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].audioItemId == context.audioItemId) { /* * The AudioPlayer notification is chatty during audio playback as it will frequently toggle between * BUFFER_UNDERRUN and PLAYER state. So we filter out the callbacks if the notification are with the @@ -408,32 +440,31 @@ void TemplateRuntime::executeAudioPlayerInfoUpdates(avsCommon::avs::PlayerActivi return; } - auto isStateUpdated = (m_audioPlayerInfo.audioPlayerState != state); - m_audioPlayerInfo.audioPlayerState = state; - m_audioPlayerInfo.offset = context.offset; - if (m_audioItemInExecution.audioItemId != context.audioItemId) { - m_audioItemInExecution.audioItemId = context.audioItemId; - m_audioItemInExecution.directive.reset(); - while (!m_audioItems.empty()) { - auto audioItem = m_audioItems.front(); - m_audioItems.pop(); - if (audioItem.audioItemId == context.audioItemId) { + auto isStateUpdated = (m_audioPlayerInfo[currentRenderPlayerInfoCardsProvider].audioPlayerState != state); + m_audioPlayerInfo[currentRenderPlayerInfoCardsProvider].audioPlayerState = state; + m_audioPlayerInfo[currentRenderPlayerInfoCardsProvider].offset = context.offset; + if (m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].audioItemId != context.audioItemId) { + m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].audioItemId = context.audioItemId; + m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].directive.reset(); + // iterate from front to back (front is most recent) + for (auto it = m_audioItems.begin(); it != m_audioItems.end(); ++it) { + auto found = it->audioItemId.find(context.audioItemId); + if (std::string::npos != found) { ACSDK_DEBUG3(LX("executeAudioPlayerInfoUpdates") .d("audioItemId", context.audioItemId) .m("Found matching audioItemId in queue.")); - m_audioItemInExecution.directive = audioItem.directive; + m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].directive = it->directive; + m_activeRenderPlayerInfoCardsProvider = currentRenderPlayerInfoCardsProvider; + // We are erasing items older than the current found, as well as the current item. + m_audioItems.erase(it, m_audioItems.end()); break; - } else { - ACSDK_DEBUG3(LX("executeAudioPlayerInfoUpdates") - .d("audioItemId", audioItem.audioItemId) - .m("Dropping out-dated audioItemId in queue.")); } } } if (m_isRenderTemplateLastReceived && state != avsCommon::avs::PlayerActivity::PLAYING) { /* * If RenderTemplate is the last directive received and the AudioPlayer is not notifying a PLAY, - * we shouldn't be notifing the observer to render a PlayerInfo display card. + * we shouldn't be notifying the observer to render a PlayerInfo display card. */ return; } @@ -441,15 +472,15 @@ void TemplateRuntime::executeAudioPlayerInfoUpdates(avsCommon::avs::PlayerActivi /* * If the AudioPlayer notifies a PLAYING state before the RenderPlayerInfo with the corresponding - * audioItemId is received, this function will also be called but the m_audioItemInExecution.directive + * audioItemId is received, this function will also be called but the m_audioItemsInExecution.directive * will be set to nullptr. So we need to do a nullptr check here to make sure there is a RenderPlayerInfo * displayCard to display.. */ - if (m_audioItemInExecution.directive) { + if (m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].directive) { if (isStateUpdated) { executeAudioPlayerStartTimer(state); } - executeDisplayCardEvent(m_audioItemInExecution.directive); + executeDisplayCardEvent(m_audioItemsInExecution[currentRenderPlayerInfoCardsProvider].directive); } else { // The RenderTemplateCard is cleared before it's displayed, so we should release the focus. if (TemplateRuntime::State::ACQUIRING == m_state) { @@ -475,13 +506,19 @@ void TemplateRuntime::executeRenderPlayerInfoCallbacks(bool isClearCard) { observer->clearPlayerInfoCard(); } } else { - if (!m_audioItemInExecution.directive) { - ACSDK_ERROR(LX("executeRenderPlayerInfoCallbacksFao;ed").d("reason", "nullAudioItemInExecution")); + if (!m_activeRenderPlayerInfoCardsProvider) { + ACSDK_ERROR( + LX("executeRenderPlayerInfoCallbacksFailed").d("reason", "nullActiveRenderPlayerInfoCardsProvider")); + return; + } + if (!m_audioItemsInExecution[m_activeRenderPlayerInfoCardsProvider].directive) { + ACSDK_ERROR(LX("executeRenderPlayerInfoCallbacksFailed").d("reason", "nullAudioItemInExecution")); return; } - auto payload = m_audioItemInExecution.directive->directive->getPayload(); + auto payload = + m_audioItemsInExecution[m_activeRenderPlayerInfoCardsProvider].directive->directive->getPayload(); for (auto& observer : m_observers) { - observer->renderPlayerInfoCard(payload, m_audioPlayerInfo, m_focus); + observer->renderPlayerInfoCard(payload, m_audioPlayerInfo[m_activeRenderPlayerInfoCardsProvider], m_focus); } } } diff --git a/CapabilityAgents/TemplateRuntime/test/TemplateRuntimeTest.cpp b/CapabilityAgents/TemplateRuntime/test/TemplateRuntimeTest.cpp index 28976555a3..e996cb7d5f 100644 --- a/CapabilityAgents/TemplateRuntime/test/TemplateRuntimeTest.cpp +++ b/CapabilityAgents/TemplateRuntime/test/TemplateRuntimeTest.cpp @@ -22,11 +22,12 @@ #include #include -#include -#include +#include #include #include #include +#include +#include #include #include #include @@ -113,15 +114,18 @@ static const std::string MALFORM_PLAYERINFO_PAYLOAD = "{" "}"; // clang-format on -class MockAudioPlayer : public AudioPlayerInterface { +class MockMediaPropertiesFetcher : public MediaPropertiesInterface { public: - MOCK_METHOD1(addObserver, void(std::shared_ptr observer)); - MOCK_METHOD1( - removeObserver, - void(std::shared_ptr observer)); MOCK_METHOD0(getAudioItemOffset, std::chrono::milliseconds()); }; +class MockRenderInfoCardsPlayer : public RenderPlayerInfoCardsProviderInterface { +public: + MOCK_METHOD1( + setObserver, + void(std::shared_ptr observer)); +}; + class MockGui : public TemplateRuntimeObserverInterface { public: MOCK_METHOD2(renderTemplateCard, void(const std::string& jsonPayload, avsCommon::avs::FocusState focusState)); @@ -215,8 +219,11 @@ class TemplateRuntimeTest : public ::testing::Test { /// Future to synchronize releaseChannel calls. std::future m_wakeReleaseChannelFuture; - /// A nice mock for the AudioPlayerInterface calls. - std::shared_ptr> m_mockAudioPlayerInterface; + /// A nice mock for the RenderInfoCardsInterface calls. + std::shared_ptr> m_mockRenderPlayerInfoCardsProvider; + + /// A nice mock for the MediaPropertiesInterface calls. + std::shared_ptr m_mediaPropertiesFetcher; /// A strict mock that allows the test to strictly monitor the exceptions being sent. std::shared_ptr> m_mockExceptionSender; @@ -238,9 +245,11 @@ void TemplateRuntimeTest::SetUp() { m_mockExceptionSender = std::make_shared>(); m_mockDirectiveHandlerResult = make_unique>(); m_mockFocusManager = std::make_shared>(); - m_mockAudioPlayerInterface = std::make_shared>(); + m_mediaPropertiesFetcher = std::make_shared>(); + m_mockRenderPlayerInfoCardsProvider = std::make_shared>(); m_mockGui = std::make_shared>(); - m_templateRuntime = TemplateRuntime::create(m_mockAudioPlayerInterface, m_mockFocusManager, m_mockExceptionSender); + m_templateRuntime = + TemplateRuntime::create({m_mockRenderPlayerInfoCardsProvider}, m_mockFocusManager, m_mockExceptionSender); m_templateRuntime->addObserver(m_mockGui); ON_CALL(*m_mockFocusManager, acquireChannel(_, _, _)).WillByDefault(InvokeWithoutArgs([this] { @@ -292,7 +301,7 @@ void TemplateRuntimeTest::wakeOnReleaseChannel() { * Tests creating the TemplateRuntime with a null audioPlayerInterface. */ TEST_F(TemplateRuntimeTest, test_nullAudioPlayerInterface) { - auto templateRuntime = TemplateRuntime::create(nullptr, m_mockFocusManager, m_mockExceptionSender); + auto templateRuntime = TemplateRuntime::create({nullptr}, m_mockFocusManager, m_mockExceptionSender); ASSERT_EQ(templateRuntime, nullptr); } @@ -300,7 +309,8 @@ TEST_F(TemplateRuntimeTest, test_nullAudioPlayerInterface) { * Tests creating the TemplateRuntime with a null focusManagerInterface. */ TEST_F(TemplateRuntimeTest, test_nullFocusManagerInterface) { - auto templateRuntime = TemplateRuntime::create(m_mockAudioPlayerInterface, nullptr, m_mockExceptionSender); + auto templateRuntime = + TemplateRuntime::create({m_mockRenderPlayerInfoCardsProvider}, nullptr, m_mockExceptionSender); ASSERT_EQ(templateRuntime, nullptr); } @@ -308,21 +318,27 @@ TEST_F(TemplateRuntimeTest, test_nullFocusManagerInterface) { * Tests creating the TemplateRuntime with a null exceptionSender. */ TEST_F(TemplateRuntimeTest, test_nullExceptionSender) { - auto templateRuntime = TemplateRuntime::create(m_mockAudioPlayerInterface, m_mockFocusManager, nullptr); + auto templateRuntime = TemplateRuntime::create({m_mockRenderPlayerInfoCardsProvider}, m_mockFocusManager, nullptr); ASSERT_EQ(templateRuntime, nullptr); } /** - * Tests that the TemplateRuntime successfully add itself with the AudioPlayer at constructor time, and - * successfully remove itself with the AudioPlayer during shutdown. + * Tests that the TemplateRuntime successfully add itself with the RenderInfoCardsPlayers at constructor time, and + * successfully remove itself with the RenderPlayerInfoCardsPlayers during shutdown. */ -TEST_F(TemplateRuntimeTest, test_audioPlayerAddRemoveObserver) { - auto mockAudioPlayerInterface = std::make_shared>(); +TEST_F(TemplateRuntimeTest, test_renderInfoCardsPlayersAddRemoveObserver) { + auto mockRenderInfoCardsProvider1 = std::make_shared>(); + auto mockRenderInfoCardsProvider2 = std::make_shared>(); auto mockExceptionSender = std::make_shared>(); auto mockFocusManager = std::make_shared>(); - EXPECT_CALL(*mockAudioPlayerInterface, addObserver(NotNull())).Times(Exactly(1)); - EXPECT_CALL(*mockAudioPlayerInterface, removeObserver(NotNull())).Times(Exactly(1)); - auto templateRuntime = TemplateRuntime::create(mockAudioPlayerInterface, mockFocusManager, mockExceptionSender); + + Expectation setObserver1 = EXPECT_CALL(*mockRenderInfoCardsProvider1, setObserver(NotNull())).Times(Exactly(1)); + EXPECT_CALL(*mockRenderInfoCardsProvider1, setObserver(IsNull())).Times(Exactly(1)).After(setObserver1); + Expectation setObserver2 = EXPECT_CALL(*mockRenderInfoCardsProvider2, setObserver(NotNull())).Times(Exactly(1)); + EXPECT_CALL(*mockRenderInfoCardsProvider2, setObserver(IsNull())).Times(Exactly(1)).After(setObserver2); + + auto templateRuntime = TemplateRuntime::create( + {mockRenderInfoCardsProvider1, mockRenderInfoCardsProvider2}, mockFocusManager, mockExceptionSender); templateRuntime->shutdown(); } @@ -465,10 +481,11 @@ TEST_F(TemplateRuntimeTest, testSlow_renderPlayerInfoDirectiveBefore) { .WillOnce(InvokeWithoutArgs(this, &TemplateRuntimeTest::wakeOnRenderPlayerInfoCard)) .WillOnce(InvokeWithoutArgs([] {})); - AudioPlayerObserverInterface::Context context; + RenderPlayerInfoCardsObserverInterface::Context context; + context.mediaProperties = m_mediaPropertiesFetcher; context.audioItemId = AUDIO_ITEM_ID; context.offset = TIMEOUT; - m_templateRuntime->onPlayerActivityChanged(avsCommon::avs::PlayerActivity::PLAYING, context); + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::PLAYING, context); m_wakeRenderPlayerInfoCardFuture.wait_for(TIMEOUT); @@ -476,7 +493,7 @@ TEST_F(TemplateRuntimeTest, testSlow_renderPlayerInfoDirectiveBefore) { .Times(Exactly(1)) .WillOnce(InvokeWithoutArgs(this, &TemplateRuntimeTest::wakeOnClearPlayerInfoCard)); - m_templateRuntime->onPlayerActivityChanged(avsCommon::avs::PlayerActivity::FINISHED, context); + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::FINISHED, context); m_wakeClearPlayerInfoCardFuture.wait_for(PLAYER_FINISHED_TIMEOUT); } @@ -498,10 +515,11 @@ TEST_F(TemplateRuntimeTest, test_renderPlayerInfoDirectiveAfter) { .Times(Exactly(1)) .WillOnce(InvokeWithoutArgs(this, &TemplateRuntimeTest::wakeOnSetCompleted)); - AudioPlayerObserverInterface::Context context; + RenderPlayerInfoCardsObserverInterface::Context context; + context.mediaProperties = m_mediaPropertiesFetcher; context.audioItemId = AUDIO_ITEM_ID; context.offset = TIMEOUT; - m_templateRuntime->onPlayerActivityChanged(avsCommon::avs::PlayerActivity::PLAYING, context); + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::PLAYING, context); m_templateRuntime->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); m_templateRuntime->CapabilityAgent::handleDirective(MESSAGE_ID); @@ -568,10 +586,11 @@ TEST_F(TemplateRuntimeTest, test_renderPlayerInfoDirectiveDifferentAudioItemId) .Times(Exactly(1)) .WillOnce(InvokeWithoutArgs(this, &TemplateRuntimeTest::wakeOnSetCompleted)); - AudioPlayerObserverInterface::Context context; + RenderPlayerInfoCardsObserverInterface::Context context; + context.mediaProperties = m_mediaPropertiesFetcher; context.audioItemId = AUDIO_ITEM_ID_1; context.offset = TIMEOUT; - m_templateRuntime->onPlayerActivityChanged(avsCommon::avs::PlayerActivity::PLAYING, context); + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::PLAYING, context); m_templateRuntime->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); m_templateRuntime->CapabilityAgent::handleDirective(MESSAGE_ID); m_wakeSetCompletedFuture.wait_for(TIMEOUT); @@ -581,15 +600,60 @@ TEST_F(TemplateRuntimeTest, test_renderPlayerInfoDirectiveDifferentAudioItemId) .WillOnce(InvokeWithoutArgs(this, &TemplateRuntimeTest::wakeOnRenderPlayerInfoCard)); context.audioItemId = AUDIO_ITEM_ID; - m_templateRuntime->onPlayerActivityChanged(avsCommon::avs::PlayerActivity::PLAYING, context); + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::PLAYING, context); + + m_wakeRenderPlayerInfoCardFuture.wait_for(TIMEOUT); +} + +/** + * Tests Provider notified the handling of AUDIO_ITEM_ID_1, and another provider notified the handling of + * AUDIO_ITEM_ID, and then RenderTemplate Directive with AUDIO_ITEM_ID is received. Expect that the + * renderTemplateCard callback will be called and the correct getAudioItemOffset is called. + */ +TEST_F(TemplateRuntimeTest, test_renderPlayerInfoDirectiveWithTwoProviders) { + auto anotherMediaPropertiesFetcher = std::make_shared>(); + + // Create Directive. + auto attachmentManager = std::make_shared>(); + auto avsMessageHeader = std::make_shared(PLAYER_INFO.nameSpace, PLAYER_INFO.name, MESSAGE_ID); + std::shared_ptr directive = + AVSDirective::create("", avsMessageHeader, PLAYERINFO_PAYLOAD, attachmentManager, ""); + + EXPECT_CALL(*m_mockGui, renderPlayerInfoCard(PLAYERINFO_PAYLOAD, _, _)) + .Times(Exactly(1)) + .WillOnce(InvokeWithoutArgs(this, &TemplateRuntimeTest::wakeOnRenderPlayerInfoCard)); + EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) + .Times(Exactly(1)) + .WillOnce(InvokeWithoutArgs(this, &TemplateRuntimeTest::wakeOnSetCompleted)); + + EXPECT_CALL(*anotherMediaPropertiesFetcher, getAudioItemOffset()) + .Times(Exactly(1)) + .WillOnce(Return(std::chrono::milliseconds::zero())); + EXPECT_CALL(*m_mediaPropertiesFetcher, getAudioItemOffset()).Times(Exactly(0)); + + RenderPlayerInfoCardsObserverInterface::Context context; + context.mediaProperties = m_mediaPropertiesFetcher; + context.audioItemId = AUDIO_ITEM_ID_1; + context.offset = TIMEOUT; + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::PLAYING, context); + + RenderPlayerInfoCardsObserverInterface::Context context1; + context1.mediaProperties = anotherMediaPropertiesFetcher; + context1.audioItemId = AUDIO_ITEM_ID; + context1.offset = TIMEOUT; + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::PLAYING, context1); + + m_templateRuntime->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); + m_templateRuntime->CapabilityAgent::handleDirective(MESSAGE_ID); m_wakeRenderPlayerInfoCardFuture.wait_for(TIMEOUT); + m_wakeSetCompletedFuture.wait_for(TIMEOUT); } /** * Tests AudioPlayer callbacks will trigger the correct renderPlayerInfoCard callbacks. Expect * the payload, audioPlayerState and offset to match to the ones passed in by the - * AudioPlayerObserverInterface. + * RenderPlayerInfoCardsObserverInterface. */ TEST_F(TemplateRuntimeTest, test_renderPlayerInfoDirectiveAudioStateUpdate) { // Create Directive. @@ -607,7 +671,8 @@ TEST_F(TemplateRuntimeTest, test_renderPlayerInfoDirectiveAudioStateUpdate) { m_templateRuntime->CapabilityAgent::handleDirective(MESSAGE_ID); m_wakeSetCompletedFuture.wait_for(TIMEOUT); - AudioPlayerObserverInterface::Context context; + RenderPlayerInfoCardsObserverInterface::Context context; + context.mediaProperties = m_mediaPropertiesFetcher; context.audioItemId = AUDIO_ITEM_ID; // Test onAudioPlayed() callback with 100ms offset @@ -624,7 +689,7 @@ TEST_F(TemplateRuntimeTest, test_renderPlayerInfoDirectiveAudioStateUpdate) { EXPECT_EQ(audioPlayerInfo.offset, context.offset); wakePlayPromise.set_value(); })); - m_templateRuntime->onPlayerActivityChanged(avsCommon::avs::PlayerActivity::PLAYING, context); + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::PLAYING, context); wakePlayFuture.wait_for(TIMEOUT); // Test onAudioPaused() callback with 200ms offset @@ -641,7 +706,7 @@ TEST_F(TemplateRuntimeTest, test_renderPlayerInfoDirectiveAudioStateUpdate) { EXPECT_EQ(audioPlayerInfo.offset, context.offset); wakePausePromise.set_value(); })); - m_templateRuntime->onPlayerActivityChanged(avsCommon::avs::PlayerActivity::PAUSED, context); + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::PAUSED, context); wakePauseFuture.wait_for(TIMEOUT); // Test onAudioStopped() callback with 300ms offset @@ -658,7 +723,7 @@ TEST_F(TemplateRuntimeTest, test_renderPlayerInfoDirectiveAudioStateUpdate) { EXPECT_EQ(audioPlayerInfo.offset, context.offset); wakeStopPromise.set_value(); })); - m_templateRuntime->onPlayerActivityChanged(avsCommon::avs::PlayerActivity::STOPPED, context); + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::STOPPED, context); wakeStopFuture.wait_for(TIMEOUT); // Test onAudioFinished() callback with 400ms offset @@ -675,7 +740,7 @@ TEST_F(TemplateRuntimeTest, test_renderPlayerInfoDirectiveAudioStateUpdate) { EXPECT_EQ(audioPlayerInfo.offset, context.offset); wakeFinishPromise.set_value(); })); - m_templateRuntime->onPlayerActivityChanged(avsCommon::avs::PlayerActivity::FINISHED, context); + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::FINISHED, context); wakeFinishFuture.wait_for(TIMEOUT); } @@ -756,10 +821,11 @@ TEST_F(TemplateRuntimeTest, test_reacquireChannel) { .Times(Exactly(1)) .WillOnce(InvokeWithoutArgs(this, &TemplateRuntimeTest::wakeOnRenderPlayerInfoCard)); - AudioPlayerObserverInterface::Context context; + RenderPlayerInfoCardsObserverInterface::Context context; + context.mediaProperties = m_mediaPropertiesFetcher; context.audioItemId = AUDIO_ITEM_ID; context.offset = TIMEOUT; - m_templateRuntime->onPlayerActivityChanged(avsCommon::avs::PlayerActivity::PLAYING, context); + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::PLAYING, context); m_templateRuntime->handleDirectiveImmediately(directive); m_wakeRenderPlayerInfoCardFuture.wait_for(TIMEOUT); @@ -791,16 +857,18 @@ TEST_F(TemplateRuntimeTest, test_reacquireChannel) { /** * Test that we should skip rendering a player info card if the audio has already changed. */ -TEST_F(TemplateRuntimeTest, testRenderPlayerInfoAfterPlayerActivityChanged) { - // Create Directive. +TEST_F(TemplateRuntimeTest, test_RenderPlayerInfoAfterPlayerActivityChanged) { + // Create Directive1. + const std::string messageId1{"messageId1"}; auto attachmentManager = std::make_shared>(); - auto avsMessageHeader = std::make_shared(PLAYER_INFO.nameSpace, PLAYER_INFO.name, MESSAGE_ID); - std::shared_ptr directive = - AVSDirective::create("", avsMessageHeader, PLAYERINFO_PAYLOAD, attachmentManager, ""); + auto avsMessageHeader1 = std::make_shared(PLAYER_INFO.nameSpace, PLAYER_INFO.name, messageId1); + std::shared_ptr directive1 = + AVSDirective::create("", avsMessageHeader1, PLAYERINFO_PAYLOAD, attachmentManager, ""); - AudioPlayerObserverInterface::Context context; + RenderPlayerInfoCardsObserverInterface::Context context; + context.mediaProperties = m_mediaPropertiesFetcher; context.audioItemId = AUDIO_ITEM_ID; - m_templateRuntime->onPlayerActivityChanged(avsCommon::avs::PlayerActivity::PLAYING, context); + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::PLAYING, context); ::testing::InSequence s; EXPECT_CALL(*m_mockFocusManager, acquireChannel(_, _, _)).WillOnce(Return(true)); @@ -808,8 +876,8 @@ TEST_F(TemplateRuntimeTest, testRenderPlayerInfoAfterPlayerActivityChanged) { EXPECT_CALL(*m_mockDirectiveHandlerResult, setCompleted()) .Times(Exactly(1)) .WillOnce(InvokeWithoutArgs(this, &TemplateRuntimeTest::wakeOnSetCompleted)); - m_templateRuntime->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); - m_templateRuntime->CapabilityAgent::handleDirective(MESSAGE_ID); + m_templateRuntime->CapabilityAgent::preHandleDirective(directive1, std::move(m_mockDirectiveHandlerResult)); + m_templateRuntime->CapabilityAgent::handleDirective(messageId1); m_wakeSetCompletedFuture.wait_for(TIMEOUT); // Test onAudioPlayed() callback with 100ms offset @@ -826,14 +894,20 @@ TEST_F(TemplateRuntimeTest, testRenderPlayerInfoAfterPlayerActivityChanged) { return returnValue; })); - m_templateRuntime->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); - m_templateRuntime->CapabilityAgent::handleDirective(MESSAGE_ID); + // Create Directive2. + const std::string messageId2{"messageId2"}; + auto avsMessageHeader2 = std::make_shared(PLAYER_INFO.nameSpace, PLAYER_INFO.name, messageId2); + auto mockDirectiveHandlerResult1 = make_unique>(); + std::shared_ptr directive2 = + AVSDirective::create("", avsMessageHeader1, PLAYERINFO_PAYLOAD, attachmentManager, ""); + m_templateRuntime->CapabilityAgent::preHandleDirective(directive2, std::move(m_mockDirectiveHandlerResult)); + m_templateRuntime->CapabilityAgent::handleDirective(messageId2); m_wakeSetCompletedFuture.wait_for(TIMEOUT); m_wakeRenderTemplateCardFuture.wait_for(TIMEOUT); m_templateRuntime->displayCardCleared(); m_wakeReleaseChannelFuture.wait_for(TIMEOUT); context.audioItemId = AUDIO_ITEM_ID_1; - m_templateRuntime->onPlayerActivityChanged(avsCommon::avs::PlayerActivity::PLAYING, context); + m_templateRuntime->onRenderPlayerCardsInfoChanged(avsCommon::avs::PlayerActivity::PLAYING, context); m_templateRuntime->onFocusChanged(avsCommon::avs::FocusState::FOREGROUND); m_templateRuntime->displayCardCleared(); m_wakeReleaseChannelFuture.wait_for(TIMEOUT); diff --git a/MediaPlayer/GStreamerMediaPlayer/src/MediaPlayer.cpp b/MediaPlayer/GStreamerMediaPlayer/src/MediaPlayer.cpp index e92848a19e..2392f36346 100644 --- a/MediaPlayer/GStreamerMediaPlayer/src/MediaPlayer.cpp +++ b/MediaPlayer/GStreamerMediaPlayer/src/MediaPlayer.cpp @@ -1557,7 +1557,7 @@ void MediaPlayer::handleGetOffset(SourceId id, std::promiseset_value(m_offsetBeforeTeardown); return; } diff --git a/PlaylistParser/src/IterativePlaylistParser.cpp b/PlaylistParser/src/IterativePlaylistParser.cpp index 140e0b195b..159f843791 100644 --- a/PlaylistParser/src/IterativePlaylistParser.cpp +++ b/PlaylistParser/src/IterativePlaylistParser.cpp @@ -181,9 +181,11 @@ PlaylistEntry IterativePlaylistParser::next() { } } } else { + // This is a plain M3U playlist. Plain M3U playlist can contain either media URLs or playlist URLs. + // URLs found in plain M3U playlist are added to the playQueue for further processing. auto entries = m3uContent.entries; for (auto reverseIt = entries.rbegin(); reverseIt != entries.rend(); ++reverseIt) { - m_playQueue.push_front(*reverseIt); + m_playQueue.push_front(reverseIt->url); } } } else if (lowerCaseContentType.find(PLS_CONTENT_TYPE) != std::string::npos) { diff --git a/README.md b/README.md index 053c66b0a1..00c5bde4a0 100644 --- a/README.md +++ b/README.md @@ -60,19 +60,19 @@ Focus management is not specific to Capability Agents or Directive Handlers, and **Capability Agents**: Handle Alexa-driven interactions; specifically directives and events. Each capability agent corresponds to a specific interface exposed by the AVS API. These interfaces include: -* [Alerts](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/alerts) - The interface for setting, stopping, and deleting timers and alarms. -* [AudioPlayer](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/audioplayer) - The interface for managing and controlling audio playback. +* [Alerts](https://developer.amazon.com/docs/alexa-voice-service/alerts.html) - The interface for setting, stopping, and deleting timers and alarms. +* [AudioPlayer](https://developer.amazon.com/docs/alexa-voice-service/audioplayer.html) - The interface for managing and controlling audio playback. * [Bluetooth](https://developer.amazon.com/docs/alexa-voice-service/bluetooth.html) - The interface for managing Bluetooth connections between peer devices and Alexa-enabled products. -* [DoNotDisturb](https://developer.amazon.com/docs/alexa-voice-service/) - The interface for enabling the do not disturb feature. +* [DoNotDisturb](https://developer.amazon.com/docs/alexa-voice-service//donotdisturb.html) - The interface for enabling the do not disturb feature. * [EqualizerController](https://developer.amazon.com/docs/alexa-voice-service/equalizercontroller.html) - The interface for adjusting equalizer settings, such as decibel (dB) levels and modes. * [InteractionModel](https://developer.amazon.com/docs/alexa-voice-service/interactionmodel-interface.html) - This interface allows a client to support complex interactions initiated by Alexa, such as Alexa Routines. -* [Notifications](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/notifications) - The interface for displaying notifications indicators. -* [PlaybackController](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/playbackcontroller) - The interface for navigating a playback queue via GUI or buttons. -* [Speaker](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/speaker) - The interface for volume control, including mute and unmute. -* [SpeechRecognizer](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/speechrecognizer) - The interface for speech capture. -* [SpeechSynthesizer](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/speechsynthesizer) - The interface for Alexa speech output. -* [System](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/system) - The interface for communicating product status/state to AVS. -* [TemplateRuntime](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/templateruntime) - The interface for rendering visual metadata. +* [Notifications](https://developer.amazon.com/docs/alexa-voice-service/notifications.html) - The interface for displaying notifications indicators. +* [PlaybackController](https://developer.amazon.com/docs/alexa-voice-service/playbackcontroller.html) - The interface for navigating a playback queue via GUI or buttons. +* [Speaker](https://developer.amazon.com/docs/alexa-voice-service/speaker.html) - The interface for volume control, including mute and unmute. +* [SpeechRecognizer](https://developer.amazon.com/docs/alexa-voice-service/speechrecognizer.html) - The interface for speech capture. +* [SpeechSynthesizer](https://developer.amazon.com/docs/alexa-voice-service/speechsynthesizer.html) - The interface for Alexa speech output. +* [System](https://developer.amazon.com/docs/alexa-voice-service/system.html) - The interface for communicating product status/state to AVS. +* [TemplateRuntime](https://developer.amazon.com/docs/alexa-voice-service/templateruntime.html) - The interface for rendering visual metadata. ### Security Best Practices @@ -97,30 +97,22 @@ In addition to adopting the [Security Best Practices for Alexa](https://develope **Note**: Feature enhancements, updates, and resolved issues from previous releases are available to view in [CHANGELOG.md](https://github.com/alexa/alexa-client-sdk/blob/master/CHANGELOG.md). -### v1.13.0 released 05/22/2019: +### v1.14.0 released 07/09/2019: **Enhancements** -* When an active Alert moves to the background, the alert now begins after a 10-second delay. Alert loop iteration delays can now no longer last longer than a maximum of 10 seconds, rather than depending on the length of the audio asset. -* Changed NotificationsSpeaker to use Alerts Volume instead of using the speaker volume. -* Allow customers to pass in an implementation of InternetConnectionMonitorInterface which will force AVSConnectionManager to reconnect on internet connectivity loss. -* Added an exponential wait time for retrying transmitting a message via CertifiedSender. -* When Volume is set to 0 and device is unmuted, volume is bumped up to a non-zero value. When Volume is set to 0 and Alexa talks back to you, volume is bumped up to a non-zero value. -* Deprecated HttpResponseCodes.h, which is now present only to ensure backward compatibility. -* The [default base URLs](https://developer.amazon.com/docs/alexa-voice-service/api-overview.html#endpoints) for AVS have changed. These new URLs are supported by SDK v1.13 and later versions. Amazon recommends that all new and existing implementations update to v1.13 or later and use the new base URLs accordingly; however, Amazon will continue to support the legacy base URLs. +* AudioPlayer can now pre-buffer audio tracks in the Pre-Handle stage. **Bug Fixes** -* Fixed bug where receiving a Connected = true Property change from BlueZ without UUID information resulted in BlueZBluetoothDevice transitioning to CONNECTED state. -* Fixed bug where MediaStreamingStateChangedEvent may be sent on non-state related property changes. -* Added null check to SQLiteStatement::getColumnText. -* Fixed an issue where database values with unescaped single quotes passed to miscStorage database will fail to be stored. Added a note on the interface that only non-escaped values should be passed. -* Fixed a loop in audio in live stations based on playlists. -* Fixed a race condition in TemplateRuntime that may result in a crash. -* Fixed a race condition where a recognize event due to a EXPECT_SPEECH may end prematurely. -* Changed the name of Alerts channel to Alert channel within AudioActivityTracker. -* Prevented STOP Wakeword detections from generating Recognize events. -* The SQLiteDeviceSettingsStorageTest no longer fails for Android. +* Fixed an issue in the SQLite wrapper code where a `SQLiteStatement` caused a memory corruption issue. +* Fixed a race condition in SpeechSynthesizer that caused crashes. +* Fixed a `cmake` issue that specifies a dependency for Bluetooth incorrectly. +* Fixed a bug that caused Bluetooth playback to start automatically. +* Changed `supportedOperations` from a vector to a set in `ExternalMediaAdapterInterface`. +* Corrected an issue where a `VolumeChanged` event had previously been sent when the volume was unchanged after `setVolume` or `adjustVolume` had been called locally. +* Fixed issue with `IterativePlaylistParser` that prevented live stations on TuneIn from playing on Android. +* Corrected the spelling of "UNINITIALIZED". **Known Issues** @@ -138,5 +130,6 @@ In addition to adopting the [Security Best Practices for Alexa](https://develope * `make integration` is currently not available for Android. In order to run integration tests on Android, you'll need to manually upload the test binary file along with any input file. At that point, the adb can be used to run the integration tests. * On Raspberry Pi running Android Things with HDMI output audio, beginning of speech is truncated when Alexa responds to user text-to-speech (TTS). * When the sample app is restarted and the network connection is lost, the Reminder TTS message does not play. Instead, the default alarm tone will play twice. -* ServerDisconnectIntegratonTest tests have been disabled until they can be updated to reflect new service behavior. +* `ServerDisconnectIntegratonTest` tests have been disabled until they can be updated to reflect new service behavior. * Devices connected before the Bluetooth CA is initialized are ignored. +* The `DirectiveSequencerTest.test_handleBlockingThenImmediatelyThenNonBockingOnSameDialogId` test fails intermittently. diff --git a/Storage/SQLiteStorage/include/SQLiteStorage/SQLiteStatement.h b/Storage/SQLiteStorage/include/SQLiteStorage/SQLiteStatement.h index 412ffbdaec..230f22b1df 100644 --- a/Storage/SQLiteStorage/include/SQLiteStorage/SQLiteStatement.h +++ b/Storage/SQLiteStorage/include/SQLiteStorage/SQLiteStatement.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ #ifndef ALEXA_CLIENT_SDK_STORAGE_SQLITESTORAGE_INCLUDE_SQLITESTORAGE_SQLITESTATEMENT_H_ #define ALEXA_CLIENT_SDK_STORAGE_SQLITESTORAGE_INCLUDE_SQLITESTORAGE_SQLITESTATEMENT_H_ +#include #include #include @@ -184,6 +185,10 @@ class SQLiteStatement { /// The result of the last step operation. int m_stepResult; + + /// A collection with all the string values that were bound to the current statement. + /// @warning SQLite will store a raw pointer to the string buffer so we must keep the reference alive. + std::list m_boundValues; }; } // namespace sqliteStorage diff --git a/Storage/SQLiteStorage/src/SQLiteStatement.cpp b/Storage/SQLiteStorage/src/SQLiteStatement.cpp index 615303f4f8..48690a190c 100644 --- a/Storage/SQLiteStorage/src/SQLiteStatement.cpp +++ b/Storage/SQLiteStorage/src/SQLiteStatement.cpp @@ -136,10 +136,11 @@ bool SQLiteStatement::bindStringParameter(int index, const std::string& value) { return false; } + m_boundValues.push_back(value); int rcode = sqlite3_bind_text( m_handle, // the statement handle index, // the position to bind to - value.c_str(), // the value to bind + m_boundValues.back().c_str(), // the value to bind SQLITE_PARSE_STRING_UNTIL_NUL_CHARACTER, // SQLite string parsing instruction nullptr); // optional destructor for SQLite to call once done diff --git a/build/cmake/Bluetooth.cmake b/build/cmake/Bluetooth.cmake index d0602e7686..eabd2c67ce 100644 --- a/build/cmake/Bluetooth.cmake +++ b/build/cmake/Bluetooth.cmake @@ -12,7 +12,7 @@ if(BLUETOOTH_BLUEZ) find_package(PkgConfig) pkg_check_modules(GIO REQUIRED gio-2.0>=2.4) pkg_check_modules(GIO_UNIX REQUIRED gio-unix-2.0>=2.4) - pkg_check_modules(SBC REQUIRED) + pkg_check_modules(SBC REQUIRED sbc) add_definitions(-DBLUETOOTH_BLUEZ) # When we have other implementations, add another definition to represent whether Bluetooth is enabled. # add_definitions(-DBLUETOOTH_ENABLED) diff --git a/build/cmake/MRM.cmake b/build/cmake/MRM.cmake index fab6a56a0b..cb3df518ca 100644 --- a/build/cmake/MRM.cmake +++ b/build/cmake/MRM.cmake @@ -7,8 +7,14 @@ # -DMRM_LIB_PATH= # -DMRM_INCLUDE_DIR= # +# To build with MRMApp, also include the following option on the cmake command line. +# -DMRM_STANDALONE_APP=ON +# -DNANOPB_LIB_PATH= +# -DNANOPB_INCLUDE_PATH= +# option(MRM "Enable Multi-Room-Music (MRM)." OFF) +option(MRM_STANDALONE_APP "Enable Multi-Room Music (MRM) as a standalone app." OFF) if(MRM) if(NOT MRM_LIB_PATH) @@ -17,6 +23,17 @@ if(MRM) if(NOT MRM_INCLUDE_DIR) message(FATAL_ERROR "Must pass include directory path to enable MRM support.") endif() - message("Creating ${PROJECT_NAME} with Multi-Room-Music (MRM)") + message("Creating ${PROJECT_NAME} with Multi-Room-Music (MRM).") add_definitions(-DENABLE_MRM) + + if(MRM_STANDALONE_APP) + if (NOT NANOPB_LIB_PATH) + message(FATAL_ERROR "Must pass path to the Nanopb library.") + endif() + if (NOT NANOPB_INCLUDE_PATH) + message(FATAL_ERROR "Must pass path to the Nanopb include.") + endif() + message("Creating ${PROJECT_NAME} with Multi-Room-Music (MRM) App.") + add_definitions(-DENABLE_MRM_STANDALONE_APP) + endif() endif() \ No newline at end of file diff --git a/tools/Testing.cmake b/tools/Testing.cmake index 31007834ae..f3fd00c723 100644 --- a/tools/Testing.cmake +++ b/tools/Testing.cmake @@ -51,12 +51,26 @@ macro(configure_test_command testname inputs testsourcefile) elseif(ANDROID_TEST_AVAILABLE) # Use generator expression to get the test path when available. set(target_path $) - add_test(NAME ${testname} + add_test(NAME "${testname}_fast" COMMAND python ${TESTING_CMAKE_DIR}/Testing/android_test.py -n ${testname} -s ${target_path} -d ${ANDROID_DEVICE_INSTALL_PREFIX} - -i "${inputs}") + -i "${inputs}" " --gtest_filter=*test_*") + add_test(NAME "${testname}_slow" + COMMAND python ${TESTING_CMAKE_DIR}/Testing/android_test.py + -n ${testname} + -s ${target_path} + -d ${ANDROID_DEVICE_INSTALL_PREFIX} + -i "${inputs}" " --gtest_filter=*testSlow_*") + add_test(NAME "${testname}_timer" + COMMAND python ${TESTING_CMAKE_DIR}/Testing/android_test.py + -n ${testname} + -s ${target_path} + -d ${ANDROID_DEVICE_INSTALL_PREFIX} + -i "${inputs}" " --gtest_filter=*testTimer_*") + set_tests_properties("${testname}_slow" PROPERTIES LABELS "slowtest") + set_tests_properties("${testname}_timer" PROPERTIES LABELS "timertest") endif() endmacro() diff --git a/tools/Testing/android_test.py b/tools/Testing/android_test.py index b21138281d..7ef73ee49b 100644 --- a/tools/Testing/android_test.py +++ b/tools/Testing/android_test.py @@ -88,7 +88,8 @@ def process_inputs(inputs_folder, inputs): """ ret = [] for toProcess in inputs: - if path.exists(toProcess): + if path.exists(toProcess) or (path.dirname(toProcess) and + path.exists(path.dirname(toProcess))): # Input is a file / directory. Upload it to the device. push_command = 'adb push {} {}'.format(toProcess, inputs_folder) check_call(push_command.split(), stdout=FNULL) @@ -105,6 +106,7 @@ def parse_output(output): print(string.join(lines[0:-1], '\n')) return int(lines[-1]) + if __name__ == '__main__': args = parse() output = run_test(args)