Version 1.2.1 alexa-client-sdk

Changes in this update
* **Enhancements**..
  * Added comments to `AlexaClientSDKConfig.json`. These descriptions provide additional guidance for what is expected for each field...
  * Enabled pause and resume controls for Pandora...

* **Bug Fixes**..
  * Bug fix for [issue #329](https://github.com/alexa/avs-device-sdk/issues/329) - `HTTP2Transport` instances no longer leak when `SERVER_SIDE
  * Bug fix for [issue #189](https://github.com/alexa/avs-device-sdk/issues/189) - Fixed a race condition in the `Timer` class that sometimes
  * Bug fix for a race condition that caused `SpeechSynthesizer` to ignore subsequent `Speak` directives...
  * Bug fix for corrupted mime attachments.
This commit is contained in:
Andrey Mikheev 2017-11-16 18:49:28 -08:00
parent 2cc16d8cc4
commit a2b84e329c
27 changed files with 748 additions and 185 deletions

View File

@ -363,6 +363,9 @@ private:
/// Whether or not the @c networkLoop is stopping. Serialized by @c m_mutex.
bool m_isStopping;
/// Whether or not the onDisconnected() notification has been sent. Serialized by @c m_mutex.
bool m_disconnectedSent;
/// Queue of @c MessageRequest instances to send. Serialized by @c m_mutex.
std::deque<std::shared_ptr<avsCommon::avs::MessageRequest>> m_requestQueue;

View File

@ -74,11 +74,13 @@ public:
void setObserver(std::shared_ptr<MessageRouterObserverInterface> observer) override;
void onConnected() override;
void onConnected(std::shared_ptr<TransportInterface> transport) override;
void onDisconnected(avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) override;
void onDisconnected(
std::shared_ptr<TransportInterface> transport,
avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) override;
void onServerSideDisconnect() override;
void onServerSideDisconnect(std::shared_ptr<TransportInterface> transport) override;
void consumeMessage(const std::string& contextId, const std::string& message) override;

View File

@ -60,9 +60,11 @@ public:
void onContextAvailable(const std::string& jsonContext) override;
void onContextFailure(const avsCommon::sdkInterfaces::ContextRequestError error) override;
void onServerSideDisconnect() override;
void onConnected() override;
void onDisconnected(avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) override;
void onServerSideDisconnect(std::shared_ptr<TransportInterface> transport) override;
void onConnected(std::shared_ptr<TransportInterface> transport) override;
void onDisconnected(
std::shared_ptr<TransportInterface> transport,
avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) override;
void addObserver(std::shared_ptr<PostConnectObserverInterface> observer) override;
void removeObserver(std::shared_ptr<PostConnectObserverInterface> observer) override;

View File

@ -36,19 +36,27 @@ public:
/**
* Called when a connection to AVS is established.
*
* @param transport The transport that has connected.
*/
virtual void onConnected() = 0;
virtual void onConnected(std::shared_ptr<TransportInterface> transport) = 0;
/**
* Called when we disconnect from AVS.
*
* @param transport The transport that is no longer connected (or attempting to connect).
* @param reason The reason that we disconnected.
*/
virtual void onDisconnected(avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) = 0;
virtual void onDisconnected(
std::shared_ptr<TransportInterface> transport,
avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) = 0;
/**
* Called when the server asks the client to reconnect
*
* @param transport The transport that has received the disconnect request.
*/
virtual void onServerSideDisconnect() = 0;
virtual void onServerSideDisconnect(std::shared_ptr<TransportInterface> transport) = 0;
};
} // namespace acl

View File

@ -169,6 +169,7 @@ HTTP2Transport::HTTP2Transport(
m_isNetworkThreadRunning{false},
m_isConnected{false},
m_isStopping{false},
m_disconnectedSent{false},
m_postConnectObject{postConnectObject} {
m_observers.insert(observer);
@ -700,9 +701,10 @@ void HTTP2Transport::setIsConnectedFalse() {
auto disconnectReason = ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR;
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_isConnected) {
if (m_disconnectedSent) {
return;
}
m_disconnectedSent = true;
m_isConnected = false;
disconnectReason = m_disconnectReason;
}
@ -775,7 +777,7 @@ void HTTP2Transport::notifyObserversOnServerSideDisconnect() {
lock.unlock();
for (auto observer : observers) {
observer->onServerSideDisconnect();
observer->onServerSideDisconnect(shared_from_this());
}
}
@ -785,7 +787,7 @@ void HTTP2Transport::notifyObserversOnDisconnect(ConnectionStatusObserverInterfa
lock.unlock();
for (auto observer : observers) {
observer->onDisconnected(reason);
observer->onDisconnected(shared_from_this(), reason);
}
}
@ -795,7 +797,7 @@ void HTTP2Transport::notifyObserversOnConnected() {
lock.unlock();
for (auto observer : observers) {
observer->onConnected();
observer->onConnected(shared_from_this());
}
}

View File

@ -112,7 +112,7 @@ void MessageRouter::setAVSEndpoint(const std::string& avsEndpoint) {
}
}
void MessageRouter::onConnected() {
void MessageRouter::onConnected(std::shared_ptr<TransportInterface> transport) {
std::unique_lock<std::mutex> lock{m_connectionMutex};
if (m_isEnabled) {
setConnectionStatusLocked(
@ -121,45 +121,34 @@ void MessageRouter::onConnected() {
}
}
void MessageRouter::onDisconnected(ConnectionStatusObserverInterface::ChangedReason reason) {
void MessageRouter::onDisconnected(
std::shared_ptr<TransportInterface> transport,
ConnectionStatusObserverInterface::ChangedReason reason) {
std::lock_guard<std::mutex> lock{m_connectionMutex};
if (ConnectionStatusObserverInterface::Status::CONNECTED == m_connectionStatus) {
// Reset m_activeTransport of it is not longer connected.
if (m_activeTransport && !m_activeTransport->isConnected()) {
safelyResetActiveTransportLocked();
}
// Trim m_transports to just those transports still connected. Also build list of disconnected transports.
std::vector<std::shared_ptr<TransportInterface>> connected;
std::vector<std::shared_ptr<TransportInterface>> disconnected;
for (auto transport : m_transports) {
if (transport->isConnected()) {
connected.push_back(transport);
} else {
disconnected.push_back(transport);
}
}
m_transports.clear();
std::swap(m_transports, connected);
// Release all the disconnected transports.
for (auto transport : disconnected) {
for (auto it = m_transports.begin(); it != m_transports.end(); it++) {
if (*it == transport) {
m_transports.erase(it);
safelyReleaseTransport(transport);
break;
}
}
if (transport == m_activeTransport) {
m_activeTransport.reset();
// Update status. If transitioning to PENDING, also initiate the reconnect.
if (ConnectionStatusObserverInterface::Status::CONNECTED == m_connectionStatus) {
if (m_isEnabled) {
if (!m_activeTransport) {
setConnectionStatusLocked(ConnectionStatusObserverInterface::Status::PENDING, reason);
createActiveTransportLocked();
}
} else if (m_transports.empty()) {
setConnectionStatusLocked(ConnectionStatusObserverInterface::Status::DISCONNECTED, reason);
}
}
}
}
void MessageRouter::onServerSideDisconnect() {
void MessageRouter::onServerSideDisconnect(std::shared_ptr<TransportInterface> transport) {
std::unique_lock<std::mutex> lock{m_connectionMutex};
if (m_isEnabled) {
setConnectionStatusLocked(

View File

@ -162,6 +162,11 @@ MimeParser::DataParsedStatus MimeParser::writeDataToAttachment(const char* buffe
void MimeParser::partDataCallback(const char* buffer, size_t size, void* userData) {
MimeParser* parser = static_cast<MimeParser*>(userData);
if (MimeParser::DataParsedStatus::INCOMPLETE == parser->m_dataParsedStatus) {
ACSDK_DEBUG9(LX("partDataCallbackIgnored").d("reason", "attachmentWriterFullBuffer"));
return;
}
if (parser->m_dataParsedStatus != MimeParser::DataParsedStatus::OK) {
ACSDK_ERROR(
LX("partDataCallbackFailed").d("reason", "mimeParsingError").d("status", parser->m_dataParsedStatus));
@ -170,7 +175,7 @@ void MimeParser::partDataCallback(const char* buffer, size_t size, void* userDat
// If we've already processed any of this part in a previous incomplete iteration, let's not process it twice.
if (!parser->shouldProcessBytes(size)) {
ACSDK_DEBUG(LX("partDataCallbackSkipped").d("reason", "bytesAlreadyProcessed"));
ACSDK_DEBUG9(LX("partDataCallbackSkipped").d("reason", "bytesAlreadyProcessed"));
parser->updateCurrentByteProgress(size);
parser->m_dataParsedStatus = MimeParser::DataParsedStatus::OK;
return;
@ -304,6 +309,7 @@ MimeParser::DataParsedStatus MimeParser::feed(char* data, size_t length) {
}
// Initialize this before all the feed() callbacks happen (since this persists from previous call).
m_currentByteProgress = 0;
m_dataParsedStatus = DataParsedStatus::OK;
m_multipartReader.feed(data, length);
@ -350,7 +356,7 @@ void MimeParser::setAttachmentWriterBufferFull(bool isFull) {
if (isFull == m_isAttachmentWriterBufferFull) {
return;
}
ACSDK_DEBUG0(LX("setAttachmentWriterBufferFull").d("full", isFull));
ACSDK_DEBUG9(LX("setAttachmentWriterBufferFull").d("full", isFull));
m_isAttachmentWriterBufferFull = isFull;
}

View File

@ -240,16 +240,18 @@ void PostConnectSynchronizer::notifyObservers() {
}
}
void PostConnectSynchronizer::onServerSideDisconnect() {
void PostConnectSynchronizer::onServerSideDisconnect(std::shared_ptr<TransportInterface> transport) {
ACSDK_DEBUG(LX("onServerSideDisconnect()"));
doShutdown();
}
void PostConnectSynchronizer::onConnected() {
void PostConnectSynchronizer::onConnected(std::shared_ptr<TransportInterface> transport) {
ACSDK_DEBUG(LX("onConnected()"));
}
void PostConnectSynchronizer::onDisconnected(ConnectionStatusObserverInterface::ChangedReason reason) {
void PostConnectSynchronizer::onDisconnected(
std::shared_ptr<TransportInterface> transport,
ConnectionStatusObserverInterface::ChangedReason reason) {
ACSDK_DEBUG(LX("onDisconnected()"));
doShutdown();
}

View File

@ -39,7 +39,7 @@ TEST_F(MessageRouterTest, getConnectionStatusReturnsConnectedAfterConnectionEsta
}
TEST_F(MessageRouterTest, getConnectionStatusReturnsConnectedAfterDisconnected) {
m_router->onDisconnected(ConnectionStatusObserverInterface::ChangedReason::ACL_DISABLED);
m_router->onDisconnected(m_mockTransport, ConnectionStatusObserverInterface::ChangedReason::ACL_DISABLED);
ASSERT_EQ(m_router->getConnectionStatus().first, ConnectionStatusObserverInterface::Status::DISCONNECTED);
}
@ -74,7 +74,7 @@ TEST_F(MessageRouterTest, ensureTheMessageRouterObserverIsInformedOfTransportDis
auto reason = ConnectionStatusObserverInterface::ChangedReason::ACL_DISABLED;
disconnectMockTransport(m_mockTransport.get());
m_router->onDisconnected(reason);
m_router->onDisconnected(m_mockTransport, reason);
// wait for the result to propagate by scheduling a task on the client executor
waitOnMessageRouter(SHORT_TIMEOUT_MS);
@ -179,7 +179,7 @@ TEST_F(MessageRouterTest, serverSideDisconnectCreatesANewTransport) {
m_router->setMockTransport(newTransport);
// Reset the MessageRouterObserver, there should be no interactions with the observer
m_router->onServerSideDisconnect();
m_router->onServerSideDisconnect(oldTransport);
waitOnMessageRouter(SHORT_TIMEOUT_MS);
@ -191,7 +191,7 @@ TEST_F(MessageRouterTest, serverSideDisconnectCreatesANewTransport) {
// mock the new transports connection
connectMockTransport(newTransport.get());
m_router->onConnected();
m_router->onConnected(newTransport);
waitOnMessageRouter(SHORT_TIMEOUT_MS);
@ -203,7 +203,7 @@ TEST_F(MessageRouterTest, serverSideDisconnectCreatesANewTransport) {
// mock the old transport disconnecting completely
disconnectMockTransport(oldTransport.get());
m_router->onDisconnected(ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST);
m_router->onDisconnected(oldTransport, ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST);
auto messageRequest = createMessageRequest();

View File

@ -120,7 +120,7 @@ public:
void setupStateToConnected() {
setupStateToPending();
m_router->onConnected();
m_router->onConnected(m_mockTransport);
connectMockTransport(m_mockTransport.get());
}

View File

@ -0,0 +1,269 @@
/*
* MimeParserFuzzTest.cpp
*
* Copyright 2017 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.
*/
/// @file MimeParserFuzzTest.cpp
#include <memory>
#include <random>
#include <thread>
#include <curl/curl.h>
#include <gtest/gtest.h>
#include "ACL/Transport/MessageConsumerInterface.h"
#include "ACL/Transport/HTTP2Stream.h"
#include <AVSCommon/AVS/Attachment/AttachmentReader.h>
#include <AVSCommon/AVS/Attachment/AttachmentWriter.h>
#include "AVSCommon/AVS/Attachment/InProcessAttachment.h"
#include <AVSCommon/SDKInterfaces/MessageObserverInterface.h>
#include <AVSCommon/Utils/Logger/Logger.h>
#include "Common/TestableAttachmentManager.h"
#include "Common/Common.h"
#include "Common/MimeUtils.h"
#include "Common/TestableMessageObserver.h"
#include "MockMessageRequest.h"
#include "TestableConsumer.h"
namespace alexaClientSDK {
namespace acl {
namespace test {
using namespace avsCommon::sdkInterfaces;
using namespace avsCommon::avs::attachment;
/// String to identify log entries originating from this file.
static const std::string TAG("MimeParserFuzzTest");
/**
* Create a LogEntry using this file's TAG and the specified event string.
*
* @param The event string for this @c LogEntry.
*/
#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event)
/// The number of feed() calls to be made in @c MimeParserFuzzTest::feed()
static const int FEED_COUNT = 0x100;
/// Max pseudo-random buffer size to pass to @c feed() (large enough to assure we trigger attachment buffer full)
static const size_t MAX_FEED_SIZE = (InProcessAttachment::SDS_BUFFER_DEFAULT_SIZE_IN_BYTES / FEED_COUNT) * 4;
/// Max buffer size to read from the attachment reader (small enough trigger multiple reads per feed)
static const size_t MAX_READ_SIZE = MAX_FEED_SIZE / 8;
/// Consistent seed (for repeatable tests) with which to generate pseudo-random bytes.
static const unsigned int BYTES_SEED = 1;
/// Consistent seed (for repeatable tests) with which to generate pseudo-random feed sizes.
static const unsigned int FEED_SIZE_SEED = 2;
/// Consistent but different seed (for repeatable tests) with which to generate pseudo-random read sizes.
static const unsigned int READ_SIZE_SEED = FEED_SIZE_SEED + 1;
/// Content ID for mime part.
static const std::string CONTENT_ID = "CONTENT_ID";
/// Context ID for generating attachment ID.
static const std::string CONTEXT_ID = "CONTEXT_ID";
/// CR,LF to separate lines in mime headers and boundaries.
static const std::string CRLF = "\r\n";
/// Dashes used as a prefix or suffix to mime boundaries.
static const std::string DASHES = "--";
/// Mime Boundary string (without CR,LF or dashes).
static const std::string BOUNDARY = "BoundaryBoundaryBoundaryBoundaryBoundaryBoundaryBoundary";
/// Mime prefix to our attachment.
static const std::string ATTACHMENT_PREFIX = CRLF + DASHES + BOUNDARY + CRLF + "Content-ID: " + CONTENT_ID + CRLF +
"Content-Type: application/octet-stream" + CRLF + CRLF;
/// Mime suffix terminating our attachment.
static const std::string ATTACHMENT_SUFFIX = CRLF + DASHES + BOUNDARY + DASHES;
/**
* Class to generate consistent pseudo-random byte stream.
*/
class ByteGenerator {
public:
ByteGenerator();
/**
* Generate the next set of bytes in the stream.
*
* @param[out] buffer The buffer in which to place the generated bytes.
* @param size The number of bytes to generate.
*/
void generateBytes(std::vector<uint8_t>* buffer, size_t size);
private:
/// The underlying random byte generator.
std::independent_bits_engine<std::minstd_rand, CHAR_BIT, uint8_t> m_engine;
};
ByteGenerator::ByteGenerator() {
m_engine.seed(BYTES_SEED);
}
void ByteGenerator::generateBytes(std::vector<uint8_t>* buffer, size_t size) {
for (size_t ix = 0; ix < size; ix++) {
(*buffer)[ix] = m_engine();
}
}
/**
* Our GTest class.
*/
class MimeParserFuzzTest : public ::testing::Test {
public:
/**
* Construct the objects we will use across tests.
*/
void SetUp() override;
/**
* Feed a pseudo-random stream of bytes to the mime parser, making a number of
* calls with pseudo-random sizes.
*/
void feed();
/**
* Read the attachment the mime parser is rendering to, requesting a pseudo-random
* number of bytes with each read.
*/
void read();
/// The AttachmentManager.
std::shared_ptr<AttachmentManager> m_attachmentManager;
/// The MimeParser which we will be primarily testing.
std::shared_ptr<MimeParser> m_parser;
/// Flag to indicate the test has failed and loops should exit.
std::atomic<bool> m_failed;
};
void MimeParserFuzzTest::SetUp() {
m_attachmentManager = std::make_shared<AttachmentManager>(AttachmentManager::AttachmentType::IN_PROCESS);
auto testableConsumer = std::make_shared<TestableConsumer>();
testableConsumer->setMessageObserver(std::make_shared<TestableMessageObserver>());
m_parser = std::make_shared<MimeParser>(testableConsumer, m_attachmentManager);
m_parser->setAttachmentContextId(CONTEXT_ID);
m_parser->setBoundaryString(BOUNDARY);
m_failed = false;
}
void MimeParserFuzzTest::feed() {
m_parser->feed(const_cast<char*>(ATTACHMENT_PREFIX.c_str()), ATTACHMENT_PREFIX.size());
ByteGenerator feedBytesSource;
std::default_random_engine feedSizeGenerator;
feedSizeGenerator.seed(FEED_SIZE_SEED);
std::uniform_int_distribution<int> feedSizeDistribution(1, MAX_FEED_SIZE);
auto feedSizeSource = std::bind(feedSizeDistribution, feedSizeGenerator);
std::vector<uint8_t> feedBuffer(MAX_FEED_SIZE);
size_t totalBytesFed = 0;
int incompleteCount = 0;
for (int ix = 0; !m_failed && ix < FEED_COUNT; ix++) {
auto feedSize = feedSizeSource();
feedBytesSource.generateBytes(&feedBuffer, feedSize);
while (true) {
ACSDK_DEBUG9(LX("callingFeed").d("totalBytesFed", totalBytesFed).d("feedSize", feedSize));
auto status = m_parser->feed((char*)(&feedBuffer[0]), feedSize);
if (MimeParser::DataParsedStatus::OK == status) {
totalBytesFed += feedSize;
break;
} else if (MimeParser::DataParsedStatus::INCOMPLETE == status) {
ACSDK_DEBUG9(LX("feedIncomplete").d("action", "waitAndReDrive"));
++incompleteCount;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
} else {
ASSERT_NE(status, status);
}
}
}
m_parser->feed(const_cast<char*>(ATTACHMENT_SUFFIX.c_str()), ATTACHMENT_SUFFIX.size());
// Make sure there were enough INCOMPLETE return statuses.
ACSDK_DEBUG9(LX("doneFeeding").d("incompleteCount", incompleteCount).d("FEED_COUNT", FEED_COUNT));
ASSERT_GT(incompleteCount, FEED_COUNT);
}
void MimeParserFuzzTest::read() {
ByteGenerator verifyBytesSource;
std::default_random_engine readSizeGenerator;
readSizeGenerator.seed(READ_SIZE_SEED);
std::uniform_int_distribution<int> readSizeDistribution(1, MAX_READ_SIZE);
auto readSizeSource = std::bind(readSizeDistribution, readSizeGenerator);
auto attachmentId = m_attachmentManager->generateAttachmentId(CONTEXT_ID, CONTENT_ID);
auto reader = m_attachmentManager->createReader(attachmentId, AttachmentReader::Policy::BLOCKING);
std::vector<uint8_t> readBuffer(MAX_READ_SIZE);
std::vector<uint8_t> verifyBuffer(MAX_READ_SIZE);
size_t totalBytesRead = 0;
while (true) {
// Delay reads so that AnotherMimeParserTest::feed() blocks on a full attachment buffer
// in a reliable way. This makes the test results consistent run to run.
std::this_thread::sleep_for(std::chrono::milliseconds(5));
auto readSizeIn = readSizeSource();
auto readStatus = AttachmentReader::ReadStatus::OK;
ACSDK_DEBUG9(LX("callingRead").d("totalBytesRead", totalBytesRead).d("readSizeIn", readSizeIn));
auto readSizeOut = reader->read(readBuffer.data(), readSizeIn, &readStatus);
if (readSizeOut != 0) {
ACSDK_DEBUG9(LX("readReturned").d("readSizeOut", readSizeOut));
verifyBytesSource.generateBytes(&verifyBuffer, readSizeOut);
for (size_t ix = 0; ix < readSizeOut; ix++) {
auto good = readBuffer[ix] == verifyBuffer[ix];
if (!good) {
m_failed = true;
ASSERT_EQ(readBuffer[ix], verifyBuffer[ix]) << "UnexpectedByte at: " << totalBytesRead + ix;
}
}
totalBytesRead += readSizeOut;
}
switch (readStatus) {
case AttachmentReader::ReadStatus::OK:
break;
case AttachmentReader::ReadStatus::CLOSED:
ACSDK_DEBUG9(LX("readClosed"));
return;
case AttachmentReader::ReadStatus::OK_WOULDBLOCK:
case AttachmentReader::ReadStatus::OK_TIMEDOUT:
case AttachmentReader::ReadStatus::ERROR_OVERRUN:
case AttachmentReader::ReadStatus::ERROR_BYTES_LESS_THAN_WORD_SIZE:
case AttachmentReader::ReadStatus::ERROR_INTERNAL:
ASSERT_NE(readStatus, readStatus);
break;
}
}
}
/**
* Exercise MimeParser's re-drive, parsing, and attachment buffering by feeding in a large attachment
* consisting of pseudo-random bytes sent in pseudo-random sized chunks. At the same time, read the
* resulting attachment in pseudo-random sized requests. Expect that the data read matches the data
* that was fed.
*/
TEST_F(MimeParserFuzzTest, testRandomFeedAndReadSizesOfRandomData) {
auto readThread = std::thread(&MimeParserFuzzTest::read, this);
feed();
readThread.join();
}
} // namespace test
} // namespace acl
} // namespace alexaClientSDK

View File

@ -367,6 +367,7 @@ void Timer::callTask(
break;
}
}
m_stopping = false;
m_running = false;
}

View File

@ -104,7 +104,7 @@ bool ConfigurationNode::initialize(const std::vector<std::istream*>& jsonStreams
}
IStreamWrapper wrapper(*jsonStream);
Document overlay(&m_document.GetAllocator());
overlay.ParseStream(wrapper);
overlay.ParseStream<kParseCommentsFlag>(wrapper);
if (overlay.HasParseError()) {
ACSDK_ERROR(LX("initializeFailed")
.d("reason", "parseFailure")

View File

@ -439,7 +439,24 @@ TEST_F(TimerTest, deleteDuringTask) {
verifyTimestamps(t0, SHORT_DELAY, SHORT_DELAY, Timer::PeriodType::ABSOLUTE, SHORT_DELAY);
}
/// This test verifies that
/**
* This test verifies that a call to start() succeeds on a timer which was previously stopped while running a task, but
* which is inactive at the time the new start() is called.
*/
TEST_F(TimerTest, startRunningAfterStopDuringTask) {
ASSERT_TRUE(m_timer->start(NO_DELAY, std::bind(&TimerTest::simpleTask, this, MEDIUM_DELAY)).valid());
ASSERT_TRUE(m_timer->isActive());
std::this_thread::sleep_for(SHORT_DELAY);
m_timer->stop();
ASSERT_TRUE(waitForInactive());
m_timestamps.clear();
auto t0 = std::chrono::steady_clock::now();
ASSERT_EQ(
m_timer->start(MEDIUM_DELAY, std::bind(&TimerTest::simpleTask, this, NO_DELAY)).wait_for(TIMEOUT),
std::future_status::ready);
ASSERT_TRUE(waitForInactive());
verifyTimestamps(t0, MEDIUM_DELAY, MEDIUM_DELAY, Timer::PeriodType::ABSOLUTE, NO_DELAY);
}
} // namespace test
} // namespace timing

View File

@ -1,5 +1,17 @@
## ChangeLog
### [1.2.1] - 2017-11-16
* **Enhancements**
* Added comments to `AlexaClientSDKConfig.json`. These descriptions provide additional guidance for what is expected for each field.
* Enabled pause and resume controls for Pandora.
* **Bug Fixes**
* Bug fix for [issue #329](https://github.com/alexa/avs-device-sdk/issues/329) - `HTTP2Transport` instances no longer leak when `SERVER_SIDE_DISCONNECT` is encountered.
* Bug fix for [issue #189](https://github.com/alexa/avs-device-sdk/issues/189) - Fixed a race condition in the `Timer` class that sometimes caused `SpeechSynthesizer` to get stuck in the "Speaking" state.
* Bug fix for a race condition that caused `SpeechSynthesizer` to ignore subsequent `Speak` directives.
* Bug fix for corrupted mime attachments.
### [1.2.0] - 2017-10-27
* **Enhancements**
* Updated MediaPlayer to solve stability issues

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
# Set project information
project(AlexaClientSDK VERSION 1.2.0 LANGUAGES CXX)
project(AlexaClientSDK VERSION 1.2.1 LANGUAGES CXX)
set(PROJECT_BRIEF "A cross-platform, modular SDK for interacting with the Alexa Voice Service")
include(build/BuildDefaults.cmake)

View File

@ -598,8 +598,13 @@ 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()) {
ACSDK_DEBUG1(LX("executeOnFocusChanged").d("action", "playNextItem"));
playNextItem();
} else {
ACSDK_DEBUG1(LX("executeOnFocusChanged").d("action", "releaseChannel"));
m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this());
}
return;
case PlayerActivity::PAUSED: {
// A focus change to foreground when paused means we should resume the current song.
@ -784,7 +789,12 @@ void AudioPlayer::executeOnPlaybackError(SourceId id, const ErrorType& type, std
}
sendPlaybackFailedEvent(m_token, type, error);
executeStop();
/*
* There's no need to call stop() here as the MediaPlayer has already stopped due to the playback error. Instead,
* call executeOnPlaybackStopped() so that the states in AudioPlayer are reset properly.
*/
executeOnPlaybackStopped(m_sourceId);
}
void AudioPlayer::executeOnPlaybackPaused(SourceId id) {

View File

@ -1147,11 +1147,14 @@ TEST_F(AudioPlayerTest, testFocusChangeToNoneInIdleState) {
}
/**
* DISABLE this test because the behavior has been changed such that we no longer expect
* a PlaybackFailedEvent
*
* Test focus change to Foreground in IDLE state
* Expect a PlaybackFailedEvent
*/
TEST_F(AudioPlayerTest, testFocusChangeToForegroundInIdleState) {
TEST_F(AudioPlayerTest, DISABLED_testFocusChangeToForegroundInIdleState) {
bool messageSentResult = false;
// expect PlaybackFailed since there are no queued AudioItems
@ -1215,11 +1218,14 @@ TEST_F(AudioPlayerTest, testFocusChangesInPlayingState) {
}
/**
* * DISABLE this test because the behavior has been changed such that we no longer expect
* a PlaybackFailedEvent
*
* Test focus changes in STOPPED state
* Expect a PlaybackFailedEvent when attempting to switch to FOREGROUND
*/
TEST_F(AudioPlayerTest, testFocusChangesInStoppedState) {
TEST_F(AudioPlayerTest, DISABLED_testFocusChangesInStoppedState) {
sendPlayDirective();
// push AudioPlayer into stopped state

View File

@ -620,9 +620,19 @@ void SpeechSynthesizer::executePlaybackError(const avsCommon::utils::mediaPlayer
}
m_waitOnStateChange.notify_one();
releaseForegroundFocus();
sendExceptionEncounteredAndReportFailed(m_currentInfo, avsCommon::avs::ExceptionErrorType::INTERNAL_ERROR, error);
resetCurrentInfo();
resetMediaSourceId();
{
std::unique_lock<std::mutex> lock(m_mutex);
while (!m_speakInfoQueue.empty()) {
auto speakInfo = m_speakInfoQueue.front();
m_speakInfoQueue.pop_front();
lock.unlock();
sendExceptionEncounteredAndReportFailed(
speakInfo, avsCommon::avs::ExceptionErrorType::INTERNAL_ERROR, error);
lock.lock();
}
}
}
std::string SpeechSynthesizer::buildState(std::string& token, int64_t offsetInMilliseconds) const {

View File

@ -259,6 +259,17 @@ public:
/// Future to notify when @c sendMessage is called.
std::future<void> m_wakeSendMessageFuture;
/**
* Fulfills the @c m_wakeStoppedPromise. This is invoked in response to a @c stop call.
*/
void wakeOnStopped();
/// Promise to be fulfilled when @c stop is called.
std::promise<void> m_wakeStoppedPromise;
/// Future to notify when @c stop is called.
std::future<void> m_wakeStoppedFuture;
/// An exception sender used to send exception encountered events to AVS.
std::shared_ptr<MockExceptionEncounteredSender> m_mockExceptionSender;
@ -278,7 +289,9 @@ SpeechSynthesizerTest::SpeechSynthesizerTest() :
m_wakeSetFailedPromise{},
m_wakeSetFailedFuture{m_wakeSetFailedPromise.get_future()},
m_wakeSendMessagePromise{},
m_wakeSendMessageFuture{m_wakeSendMessagePromise.get_future()} {
m_wakeSendMessageFuture{m_wakeSendMessagePromise.get_future()},
m_wakeStoppedPromise{},
m_wakeStoppedFuture{m_wakeStoppedPromise.get_future()} {
}
void SpeechSynthesizerTest::SetUp() {
@ -334,6 +347,10 @@ void SpeechSynthesizerTest::wakeOnSendMessage() {
m_wakeSendMessagePromise.set_value();
}
void SpeechSynthesizerTest::wakeOnStopped() {
m_wakeStoppedPromise.set_value();
}
/**
* Test call to handleDirectiveImmediately.
* Expected result is that @c acquireChannel is called with the correct channel. On focus changed @c FOREGROUND, audio
@ -632,6 +649,113 @@ TEST_F(SpeechSynthesizerTest, testBargeInWhilePlaying) {
ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT));
}
/**
* Testing calling stop() in @c MediaPlayer twice won't get the @c SpeechSynthesizer into a bad state.
* Call preHandle with a valid SPEAK directive. Then call handleDirective. Expected result is that @c acquireChannel
* is called once. On Focus Changed to foreground, audio should play.
* Call cancel directive. Expect the stop() to be called once.
* Call onFocusChanged, expect the stop() to be called again, and this time the @c MediaPlayer will return false to
* report an error. Expect when handleDirectiveImmediately with a valid SPEAK directive is called, @c SpeechSynthesizer
* will react correctly.
*/
TEST_F(SpeechSynthesizerTest, testCallingStopTwice) {
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(
NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_TEST, DIALOG_REQUEST_ID_TEST);
std::shared_ptr<AVSDirective> directive =
AVSDirective::create("", avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST);
auto avsMessageHeader2 =
std::make_shared<AVSMessageHeader>(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_TEST_2);
std::shared_ptr<AVSDirective> directive2 =
AVSDirective::create("", avsMessageHeader2, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST_2);
EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, FOCUS_MANAGER_ACTIVITY_ID))
.Times(AtLeast(1))
.WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel));
EXPECT_CALL(
*(m_mockSpeechPlayer.get()),
attachmentSetSource(A<std::shared_ptr<avsCommon::avs::attachment::AttachmentReader>>()))
.Times(AtLeast(1));
EXPECT_CALL(*(m_mockSpeechPlayer.get()), play(_)).Times(AtLeast(1));
EXPECT_CALL(*(m_mockSpeechPlayer.get()), getOffset(_))
.Times(AtLeast(1))
.WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST));
EXPECT_CALL(
*(m_mockContextManager.get()),
setState(NAMESPACE_AND_NAME_SPEECH_STATE, PLAYING_STATE_TEST, StateRefreshPolicy::ALWAYS, 0))
.Times(AtLeast(1))
.WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetState));
EXPECT_CALL(
*(m_mockContextManager.get()),
setState(NAMESPACE_AND_NAME_SPEECH_STATE, FINISHED_STATE_TEST, StateRefreshPolicy::NEVER, 0))
.Times(AtLeast(1))
.WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetState));
EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_))
.Times(AtLeast(1))
.WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage));
EXPECT_CALL(*(m_mockFocusManager.get()), releaseChannel(CHANNEL_NAME, _))
.Times(AtLeast(1))
.WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnReleaseChannel));
EXPECT_CALL(*(m_mockSpeechPlayer.get()), stop(_))
.Times(AtLeast(1))
.WillOnce(Invoke([this](avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) {
wakeOnStopped();
return true;
}))
.WillOnce(Invoke([this](avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) {
wakeOnStopped();
return false;
}))
.WillRepeatedly(Return(true));
EXPECT_CALL(*m_mockDirHandlerResult, setFailed(_)).Times(AtLeast(1));
// send Speak directive and getting focus and wait until playback started
m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult));
m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST);
ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT));
m_wakeAcquireChannelPromise = std::promise<void>();
m_wakeAcquireChannelFuture = m_wakeAcquireChannelPromise.get_future();
m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND);
ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted());
ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT));
m_wakeSetStatePromise = std::promise<void>();
m_wakeSetStateFuture = m_wakeSetStatePromise.get_future();
ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT));
m_wakeSendMessagePromise = std::promise<void>();
m_wakeSendMessageFuture = m_wakeSendMessagePromise.get_future();
// cancel directive, this should result in calling stop()
m_speechSynthesizer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST);
ASSERT_TRUE(std::future_status::ready == m_wakeStoppedFuture.wait_for(WAIT_TIMEOUT));
m_wakeStoppedPromise = std::promise<void>();
m_wakeStoppedFuture = m_wakeStoppedPromise.get_future();
// goes to background, this should result in calling the 2nd stop() and MediaPlayer returning an error
m_speechSynthesizer->onFocusChanged(FocusState::BACKGROUND);
ASSERT_TRUE(std::future_status::ready == m_wakeStoppedFuture.wait_for(WAIT_TIMEOUT));
ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT));
m_wakeSetStatePromise = std::promise<void>();
m_wakeSetStateFuture = m_wakeSetStatePromise.get_future();
/*
* onPlaybackStopped, this will result in an error with reason=nullptrDirectiveInfo. But this shouldn't break the
* SpeechSynthesizer
*/
m_speechSynthesizer->onPlaybackStopped(m_mockSpeechPlayer->m_sourceId);
ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(WAIT_TIMEOUT));
m_wakeReleaseChannelPromise = std::promise<void>();
m_wakeReleaseChannelFuture = m_wakeReleaseChannelPromise.get_future();
// send second speak directive and make sure it working
m_speechSynthesizer->handleDirectiveImmediately(directive2);
ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT));
m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND);
ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT));
m_wakeSetStatePromise = std::promise<void>();
m_wakeSetStateFuture = m_wakeSetStatePromise.get_future();
ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted());
}
} // namespace test
} // namespace speechSynthesizer
} // namespace capabilityAgents

View File

@ -1,28 +1,78 @@
{
"authDelegate":{
// The Client Secret of the Product from developer.amazon.com
"clientSecret":"${SDK_CONFIG_CLIENT_SECRET}",
// Unique device serial number. e.g. 123456
"deviceSerialNumber":"${SDK_CONFIG_DEVICE_SERIAL_NUMBER}",
"refreshToken":"${SDK_CONFIG_REFRESH_TOKEN}",
// Refresh Token populated by running AuthServer.py
"refreshToken":"{SDK_CONFIG_REFRESH_TOKEN}",
// The Client ID of the Product from developer.amazon.com
"clientId":"${SDK_CONFIG_CLIENT_ID}",
// Product ID from developer.amazon.com
"productId":"${SDK_CONFIG_PRODUCT_ID}"
},
"alertsCapabilityAgent":{
// Path to Alerts database file. e.g. /home/ubuntu/Build/alerts.db
// Note: The directory specified must be valid.
// The database file (alerts.db) will be created by SampleApp, do not create it yourself.
// The database file should only be used for alerts (don't use it for other components of SDK)
"databaseFilePath":"${SDK_SQLITE_DATABASE_FILE_PATH}",
// Path to default Alarm sound file. e.g. /home/ubuntu/alert_sounds/alarm_normal.mp3
// Note: The audio file must exist and be a valid file.
"alarmSoundFilePath":"${SDK_ALARM_DEFAULT_SOUND_FILE_PATH}",
// Path to short Alarm sound file. e.g. /home/ubuntu/alert_sounds/alarm_short.wav
// Note: The audio file must exist and be a valid file.
"alarmShortSoundFilePath":"${SDK_ALARM_SHORT_SOUND_FILE_PATH}",
// Path to default timer sound file. e.g. /home/ubuntu/alert_sounds/timer_normal.mp3
// Note: The audio file must exist and be a valid file.
"timerSoundFilePath":"${SDK_TIMER_DEFAULT_SOUND_FILE_PATH}",
// Path to short timer sound file. e.g. /home/ubuntu/alert_sounds/timer_short.wav
// Note: The audio file must exist and be a valid file.
"timerShortSoundFilePath":"${SDK_TIMER_SHORT_SOUND_FILE_PATH}"
},
"settings":{
// Path to Settings database file. e.g. /home/ubuntu/Build/settings.db
// Note: The directory specified must be valid.
// The database file (settings.db) will be created by SampleApp, do not create it yourself.
// The database file should only be used for settings (don't use it for other components of SDK)
"databaseFilePath":"${SDK_SQLITE_SETTINGS_DATABASE_FILE_PATH}",
"defaultAVSClientSettings":{
// Default language for Alexa.
// See https://developer.amazon.com/docs/alexa-voice-service/settings.html#settingsupdated for valid values.
"locale":"${SETTING_LOCALE_VALUE}"
}
},
"certifiedSender":{
"databaseFilePath":"{SDK_CERTIFIED_SENDER_DATABASE_FILE_PATH}"
// Path to Certified Sender database file. e.g. /home/ubuntu/Build/certifiedsender.db
// Note: The directory specified must be valid.
// The database file (certifiedsender.db) will be created by SampleApp, do not create it yourself.
// The database file should only be used for certifiedSender (don't use it for other components of SDK)
"databaseFilePath":"${SDK_CERTIFIED_SENDER_DATABASE_FILE_PATH}"
}
}
// Notes for logging
// The log levels are supported to debug when SampleApp is not working as expected.
// There are 14 levels of logging with DEBUG9 providing the highest level of logging and CRITICAL providing
// the lowest level of logging i.e. if DEBUG9 is specified while running the SampleApp, all the logs at DEBUG9 and
// below are displayed, whereas if CRITICAL is specified, only logs of CRITICAL are displayed.
// The 14 levels are:
// DEBUG9, DEBUG8, DEBUG7, DEBUG6, DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, DEBUG0, INFO, WARN, ERROR, CRTITICAL.
// To selectively see the logging for a particular module, you can specify logging level in this json file.
// Some examples are:
// To only see logs of level INFO and below for ACL and MediaPlayer modules,
// - grep for ACSDK_LOG_MODULE in source folder. Find the log module for ACL and MediaPlayer.
// - Put the following in json:
// "acl":{
// "logLevel":"INFO"
// },
// "mediaPlayer":{
// "logLevel":"INFO"
// }
// To enable DEBUG, build with cmake option -DCMAKE_BUILD_TYPE=DEBUG. By default it is built with RELEASE build.
// And run the SampleApp similar to the following command.
// e.g. TZ=UTC ./SampleApp /home/ubuntu/.../AlexaClientSDKConfig.json /home/ubuntu/KittAiModels/ DEBUG9"

View File

@ -954,11 +954,14 @@ TEST_F(AlertsTest, cancelAlertBeforeItIsActive) {
}
/**
* Disabled until consistent failures can be investigated. Failures are due to the test's expectation of event
* ordering.
*
* Test when the storage is removed before an alert is set
*
* Close the storage before asking for a 5 second timer. SetAlertFailed and DeleteAlertFailed events are then sent.
*/
TEST_F(AlertsTest, RemoveStorageBeforeAlarmIsSet) {
TEST_F(AlertsTest, DISABLED_RemoveStorageBeforeAlarmIsSet) {
m_alertStorage->close();
// Write audio to SDS saying "Set a timer for 5 seconds"

View File

@ -114,13 +114,16 @@ private:
* The @c AudioPipeline consists of the following elements:
* @li @c appsrc The appsrc element is used as the source to which audio data is provided.
* @li @c decoder Decodebin is used as the decoder element to decode audio.
* @li @c decodedQueue A queue is used to store the decoded data.
* @li @c converter An audio-converter is used to convert between audio formats.
* @li @c volume The volume element is used as a volume control.
* @li @c audioSink Sink for the audio.
* @li @c pipeline The pipeline is a bin consisting of the @c appsrc, the @c decoder, the @c converter, and the
* @c audioSink.
*
* The data flow through the elements is appsrc -> decoder -> converter -> volume -> audioSink.
* The data flow through the elements is appsrc -> decoder -> decodedQueue -> converter -> volume -> audioSink.
* Ideally we would want to use playsink or playbin directly to automate as much as possible. However, this
* causes problems with multiple pipelines and volume settings in pulse audio. Pending further investigation.
*/
struct AudioPipeline {
/// The source element.
@ -129,6 +132,9 @@ private:
/// The decoder element.
GstElement* decoder;
/// A queue for decoded elements.
GstElement* decodedQueue;
/// The converter element.
GstElement* converter;
@ -145,6 +151,7 @@ private:
AudioPipeline() :
appsrc{nullptr},
decoder{nullptr},
decodedQueue{nullptr},
converter{nullptr},
volume{nullptr},
audioSink{nullptr},

View File

@ -533,6 +533,14 @@ bool MediaPlayer::init() {
}
bool MediaPlayer::setupPipeline() {
m_pipeline.decodedQueue = gst_element_factory_make("queue", "decodedQueue");
// Do not send signals or messages. Let the decoder buffer messages dictate application logic.
g_object_set(m_pipeline.decodedQueue, "silent", TRUE, NULL);
if (!m_pipeline.decodedQueue) {
ACSDK_ERROR(LX("setupPipelineFailed").d("reason", "createQueueElementFailed"));
return false;
}
m_pipeline.converter = gst_element_factory_make("audioconvert", "converter");
if (!m_pipeline.converter) {
ACSDK_ERROR(LX("setupPipelineFailed").d("reason", "createConverterElementFailed"));
@ -561,11 +569,17 @@ bool MediaPlayer::setupPipeline() {
m_busWatchId = gst_bus_add_watch(bus, &MediaPlayer::onBusMessage, this);
gst_object_unref(bus);
// Link only the volume, converter, and sink here. Src will be linked in respective source files.
// Link only the queue, converter, volume, and sink here. Src will be linked in respective source files.
gst_bin_add_many(
GST_BIN(m_pipeline.pipeline), m_pipeline.converter, m_pipeline.volume, m_pipeline.audioSink, nullptr);
GST_BIN(m_pipeline.pipeline),
m_pipeline.decodedQueue,
m_pipeline.converter,
m_pipeline.volume,
m_pipeline.audioSink,
nullptr);
if (!gst_element_link_many(m_pipeline.converter, m_pipeline.volume, m_pipeline.audioSink, nullptr)) {
if (!gst_element_link_many(
m_pipeline.decodedQueue, m_pipeline.converter, m_pipeline.volume, m_pipeline.audioSink, nullptr)) {
ACSDK_ERROR(LX("setupPipelineFailed").d("reason", "createVolumeToConverterToSinkLinkFailed"));
return false;
}
@ -611,6 +625,7 @@ void MediaPlayer::resetPipeline() {
m_pipeline.pipeline = nullptr;
m_pipeline.appsrc = nullptr;
m_pipeline.decoder = nullptr;
m_pipeline.decodedQueue = nullptr;
m_pipeline.converter = nullptr;
m_pipeline.volume = nullptr;
m_pipeline.audioSink = nullptr;
@ -698,8 +713,7 @@ void MediaPlayer::onPadAdded(GstElement* decoder, GstPad* pad, gpointer pointer)
void MediaPlayer::handlePadAdded(std::promise<void>* promise, GstElement* decoder, GstPad* pad) {
ACSDK_DEBUG9(LX("handlePadAddedSignalCalled"));
GstElement* converter = m_pipeline.converter;
gst_element_link(decoder, converter);
gst_element_link(decoder, m_pipeline.decodedQueue);
promise->set_value();
}
@ -907,7 +921,7 @@ void MediaPlayer::handleSetAttachmentReaderSource(
/*
* Once the source pad for the decoder has been added, the decoder emits the pad-added signal. Connect the signal
* to the callback which performs the linking of the decoder source pad to the converter sink pad.
* to the callback which performs the linking of the decoder source pad to decodedQueue sink pad.
*/
if (!g_signal_connect(m_pipeline.decoder, "pad-added", G_CALLBACK(onPadAdded), this)) {
ACSDK_ERROR(LX("handleSetAttachmentReaderSourceFailed").d("reason", "connectPadAddedSignalFailed"));
@ -938,7 +952,7 @@ void MediaPlayer::handleSetIStreamSource(
/*
* Once the source pad for the decoder has been added, the decoder emits the pad-added signal. Connect the signal
* to the callback which performs the linking of the decoder source pad to the converter sink pad.
* to the callback which performs the linking of the decoder source pad to the decodedQueue sink pad.
*/
if (!g_signal_connect(m_pipeline.decoder, "pad-added", G_CALLBACK(onPadAdded), this)) {
ACSDK_ERROR(LX("handleSetIStreamSourceFailed").d("reason", "connectPadAddedSignalFailed"));
@ -966,7 +980,7 @@ void MediaPlayer::handleSetSource(std::string url, std::promise<MediaPlayer::Sou
* The first pad that is added may not be the correct stream (ie may be a video stream), and will fail.
*
* Once the source pad for the decoder has been added, the decoder emits the pad-added signal. Connect the signal
* to the callback which performs the linking of the decoder source pad to the converter sink pad.
* to the callback which performs the linking of the decoder source pad to the decodedQueue sink pad.
*/
if (!g_signal_connect(m_pipeline.decoder, "pad-added", G_CALLBACK(onPadAdded), this)) {
ACSDK_ERROR(LX("handleSetSourceForUrlFailed").d("reason", "connectPadAddedSignalFailed"));

View File

@ -86,39 +86,16 @@ Focus management is not specific to Capability Agents or Directive Handlers, and
**Note**: Features, 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.2 released 10/27/2017:
v1.2.1 released 11/16/2017:
**Enhancements**
* Updated MediaPlayer to solve stability issues
* All capability agents were refined to work with the updated MediaPlayer
* Added the TemplateRuntime capability agent
* Added the SpeakerManager capability agent
* Added a configuration option ("sampleApp":"endpoint") that allows the endpoint that SampleApp connects to to be specified without changing code or rebuilding
* Added very verbose capture of libcurl debug information
* Added an observer interface to observer audio state changes from AudioPlayer
* Added support for StreamMetadataExtracted Event. Stream tags found in the stream are represented in JSON and sent to AVS
* Added to the SampleApp a simple GuiRenderer as an observer to the TemplateRuntime Capability Agent
* Moved shared libcurl functionality to AVSCommon/Utils
* Added a CMake option to exclude tests from the "make all" build. Use "cmake <absolute-path-to-source>
-DACSDK_EXCLUDE_TEST_FROM_ALL=ON" to enable it. When this option is enabled "make unit" and "make integration" still could be used to build and run the tests
* Added comments to `AlexaClientSDKConfig.json`. These descriptions provide additional guidance for what is expected for each field.
* Enabled pause and resume controls for Pandora.
**Bug Fixes**
* Previously scheduled alerts now play following a restart
* General stability fixes
* Bug fix for CertifiedSender blocking forever if the network goes down while it's trying to send a message to AVS
* Fixes for known issue of Alerts integration tests fail: AlertsTest.UserLongUnrelatedBargeInOnActiveTimer and AlertsTest.handleOneTimerWithVocalStop
* Attempting to end a tap-to-talk interaction with the tap-to-talk button wouldn't work
* SharedDataStream could encounter a race condition due to a combination of a blocking Reader and a Writer closing before writing any data
* Bug-fix for the ordering of notifications within alerts scheduling. This fixes the issue where a local-stop on an alert would also stop a subsequent alert if it were to begin without delay
**Known Issues**
* Capability agent for Notifications is not included in this release
* Inconsistent playback behavior when resuming content ("Alexa, pause." / "Alexa, resume."). Specifically, handling playback offsets, which causes the content to play from the start. This behavior is also occasionally seen with "Next" /
"Previous" as well
* `ACL`'s asynchronous receipt of audio attachments may manage resources poorly in scenarios where attachments are received but not consumed
* Music playback failures may result in an error Text to Speech being rendered repeatedly
* Reminders and named timers don't sound when there is no connection
* GUI cards don't show for Kindle
* Bug fix for [issue #329](https://github.com/alexa/avs-device-sdk/issues/329) - `HTTP2Transport` instances no longer leak when `SERVER_SIDE_DISCONNECT` is encountered.
* Bug fix for [issue #189](https://github.com/alexa/avs-device-sdk/issues/189) - Fixed a race condition in the `Timer` class that sometimes caused `SpeechSynthesizer` to get stuck in the "Speaking" state.
* Bug fix for a race condition that caused `SpeechSynthesizer` to ignore subsequent `Speak` directives.
* Bug fix for corrupted mime attachments.

33
issue_template.md Normal file
View File

@ -0,0 +1,33 @@
**IMPORTANT**: Before you create an issue, please take a look at our [Issue Reporting Guide](https://github.com/alexa/avs-device-sdk/wiki/Issue-Reporting-Guide).
## Briefly summarize your issue:
<!--- Provide a more detailed introduction to the issue itself, and why you consider it to be a bug -->
## What is the expected behavior?
<!--- Tell us what should happen -->
## What behavior are you observing?
<!--- Tell us what's happening instead -->
## Provide the steps to reproduce the issue, if applicable:
<!--- Provide the steps for reproduce -->
## Tell us about your environment:
<!--- Include as many relevant details about the environment you experienced the bug in -->
### What version of the AVS Device SDK are you using?
<x.y.z>
### Tell us what hardware you're using:
- [ ] Desktop / Laptop
- [ ] Raspberry Pi
- [ ] Other - tell us more:
### Tell us about your OS (Type & version):
- [ ] Linux
- [ ] MacOS
- [ ] Raspbian Stretch
- [ ] Raspbian Jessy
- [ ] Other - tell us more:

View File

@ -16,6 +16,7 @@
from flask import Flask, redirect, request
import requests
import json
import commentjson
from os.path import abspath, isfile, dirname
import sys
@ -47,6 +48,9 @@ amazonLwaApiHeaders = {'Content-Type': 'application/x-www-form-urlencoded'}
# Default configuration filename, to be filled by CMake
defaultConfigFilename = "${SDK_CONFIG_FILE_TARGET}"
# Default string for refresh token in configuration file.
defaultRefreshTokenString = "{SDK_CONFIG_REFRESH_TOKEN}"
# JSON keys for config file
CLIENT_ID = 'clientId'
CLIENT_SECRET = 'clientSecret'
@ -71,12 +75,13 @@ if not isfile(configFilename):
try:
configFile = open(configFilename,'r')
except IOError:
print 'File "' + configFilename + '" not found!'
sys.exit(1)
else:
with configFile:
configData = json.load(configFile)
configData = commentjson.load(configFile)
if not configData.has_key(authDelegateKey):
print 'The config file "' + \
configFilename + \
@ -110,6 +115,10 @@ if authDelegateDict.has_key(REFRESH_TOKEN):
amazonLwaApiUrl,
data=urlencode(postData),
headers=amazonLwaApiHeaders)
defaultRefreshTokenString = authDelegateDict[REFRESH_TOKEN];
if defaultRefreshTokenString == "":
print 'Refresh token in the file cannot be empty. Please enter {SDK_CONFIG_REFRESH_TOKEN} in the refresh token'
sys.exit(0)
if 200 == tokenRefreshRequest.status_code:
print 'You have a valid refresh token already in the file.'
sys.exit(0)
@ -159,7 +168,7 @@ def get_refresh_token():
'<br/>' + shutdown()
authDelegateDict['refreshToken'] = tokenRequest.json()['refresh_token']
try:
configFile = open(configFilename,'w')
configFile = open(configFilename,'r')
except IOError:
print 'File "' + configFilename + '" cannot be opened!'
return '<h1>The file "' + \
@ -168,8 +177,15 @@ def get_refresh_token():
shutdown() + \
'</h1>'
else:
with configFile:
json.dump(configData, configFile, indent=4, separators=(',',':'))
fileContent = configFile.read()
if defaultRefreshTokenString not in fileContent:
print '"{defaultRefreshTokenString} not in {configFilename}"'
return '<h1>' + defaultRefreshTokenString + ' string not present in ' + configFilename + \
' please check the file ' + shutdown() + '</h1>'
else:
configFile = open(configFilename,'w')
fileContent = fileContent.replace(defaultRefreshTokenString, tokenRequest.json()['refresh_token'])
configFile.write(fileContent)
return '<h1>The file is written successfully.<br/>' + shutdown() + '</h1>'
app.run(host='127.0.0.1',port='3000')