322 lines
10 KiB
C++
322 lines
10 KiB
C++
/*
|
|
* Copyright 2017-2018 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.
|
|
*/
|
|
|
|
#include "AVSCommon/Utils/MediaPlayer/MediaPlayerObserverInterface.h"
|
|
#include "AVSCommon/Utils/MediaPlayer/MockMediaPlayer.h"
|
|
|
|
namespace alexaClientSDK {
|
|
namespace avsCommon {
|
|
namespace utils {
|
|
namespace mediaPlayer {
|
|
namespace test {
|
|
|
|
using namespace testing;
|
|
|
|
const static std::chrono::milliseconds WAIT_LOOP_INTERVAL{1};
|
|
|
|
std::shared_ptr<NiceMock<MockMediaPlayer>> MockMediaPlayer::create() {
|
|
auto result = std::make_shared<NiceMock<MockMediaPlayer>>();
|
|
|
|
ON_CALL(*result.get(), attachmentSetSource(_, _))
|
|
.WillByDefault(InvokeWithoutArgs(result.get(), &MockMediaPlayer::mockSetSource));
|
|
ON_CALL(*result.get(), urlSetSource(_))
|
|
.WillByDefault(InvokeWithoutArgs(result.get(), &MockMediaPlayer::mockSetSource));
|
|
ON_CALL(*result.get(), streamSetSource(_, _))
|
|
.WillByDefault(InvokeWithoutArgs(result.get(), &MockMediaPlayer::mockSetSource));
|
|
ON_CALL(*result.get(), play(_)).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockPlay));
|
|
ON_CALL(*result.get(), stop(_)).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockStop));
|
|
ON_CALL(*result.get(), pause(_)).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockPause));
|
|
ON_CALL(*result.get(), resume(_)).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockResume));
|
|
ON_CALL(*result.get(), getOffset(_)).WillByDefault(Invoke(result.get(), &MockMediaPlayer::mockGetOffset));
|
|
return result;
|
|
}
|
|
|
|
MockMediaPlayer::MockMediaPlayer() : RequiresShutdown{"MockMediaPlayer"}, m_playerObserver{nullptr} {
|
|
// Create a 'source' for sourceId = 0
|
|
mockSetSource();
|
|
}
|
|
|
|
MediaPlayerInterface::SourceId MockMediaPlayer::setSource(
|
|
std::shared_ptr<avsCommon::avs::attachment::AttachmentReader> attachmentReader,
|
|
const avsCommon::utils::AudioFormat* audioFormat) {
|
|
return attachmentSetSource(attachmentReader, audioFormat);
|
|
}
|
|
|
|
MediaPlayerInterface::SourceId MockMediaPlayer::setSource(const std::string& url, std::chrono::milliseconds offset) {
|
|
return urlSetSource(url);
|
|
}
|
|
|
|
MediaPlayerInterface::SourceId MockMediaPlayer::setSource(std::shared_ptr<std::istream> stream, bool repeat) {
|
|
return streamSetSource(stream, repeat);
|
|
}
|
|
|
|
void MockMediaPlayer::setObserver(std::shared_ptr<MediaPlayerObserverInterface> playerObserver) {
|
|
m_playerObserver = playerObserver;
|
|
}
|
|
|
|
void MockMediaPlayer::doShutdown() {
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
m_playerObserver.reset();
|
|
m_sources.clear();
|
|
}
|
|
|
|
MediaPlayerInterface::SourceId MockMediaPlayer::mockSetSource() {
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
SourceId result = m_sources.size();
|
|
m_sources.emplace_back(std::make_shared<Source>(this, result));
|
|
return result;
|
|
}
|
|
|
|
bool MockMediaPlayer::mockPlay(SourceId sourceId) {
|
|
auto source = getCurrentSource(sourceId);
|
|
if (!source) {
|
|
return false;
|
|
}
|
|
source->started.trigger();
|
|
return true;
|
|
}
|
|
|
|
bool MockMediaPlayer::mockPause(SourceId sourceId) {
|
|
auto source = getCurrentSource(sourceId);
|
|
if (!source) {
|
|
return false;
|
|
}
|
|
source->resumed.resetStateReached();
|
|
source->paused.trigger();
|
|
return true;
|
|
}
|
|
|
|
bool MockMediaPlayer::mockResume(SourceId sourceId) {
|
|
auto source = getCurrentSource(sourceId);
|
|
if (!source) {
|
|
return false;
|
|
}
|
|
source->paused.resetStateReached();
|
|
source->resumed.trigger();
|
|
return true;
|
|
}
|
|
|
|
bool MockMediaPlayer::mockStop(SourceId sourceId) {
|
|
auto source = getCurrentSource(sourceId);
|
|
if (!source) {
|
|
return false;
|
|
}
|
|
source->stopped.trigger();
|
|
return true;
|
|
}
|
|
|
|
bool MockMediaPlayer::mockFinished(SourceId sourceId) {
|
|
auto source = getCurrentSource(sourceId);
|
|
if (!source) {
|
|
return false;
|
|
}
|
|
source->finished.trigger();
|
|
return true;
|
|
}
|
|
|
|
bool MockMediaPlayer::mockError(SourceId sourceId) {
|
|
auto source = getCurrentSource(sourceId);
|
|
if (!source) {
|
|
return false;
|
|
}
|
|
source->error.trigger();
|
|
return true;
|
|
}
|
|
|
|
bool MockMediaPlayer::mockSetOffset(SourceId sourceId, std::chrono::milliseconds offset) {
|
|
auto source = getCurrentSource(sourceId);
|
|
if (!source) {
|
|
return false;
|
|
}
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
source->offset = offset;
|
|
return true;
|
|
}
|
|
|
|
std::chrono::milliseconds MockMediaPlayer::mockGetOffset(SourceId sourceId) {
|
|
auto source = getCurrentSource(sourceId);
|
|
if (!source) {
|
|
return MEDIA_PLAYER_INVALID_OFFSET;
|
|
}
|
|
return source->offset;
|
|
}
|
|
|
|
bool MockMediaPlayer::waitUntilNextSetSource(const std::chrono::milliseconds timeout) {
|
|
auto originalSourceId = getCurrentSourceId();
|
|
auto timeLimit = std::chrono::steady_clock::now() + timeout;
|
|
while (std::chrono::steady_clock::now() < timeLimit) {
|
|
if (getCurrentSourceId() != originalSourceId) {
|
|
return true;
|
|
}
|
|
std::this_thread::sleep_for(WAIT_LOOP_INTERVAL);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MockMediaPlayer::waitUntilPlaybackStarted(std::chrono::milliseconds timeout) {
|
|
return m_sources[getCurrentSourceId()]->started.wait(timeout);
|
|
}
|
|
|
|
bool MockMediaPlayer::waitUntilPlaybackPaused(std::chrono::milliseconds timeout) {
|
|
return m_sources[getCurrentSourceId()]->paused.wait(timeout);
|
|
}
|
|
|
|
bool MockMediaPlayer::waitUntilPlaybackResumed(std::chrono::milliseconds timeout) {
|
|
return m_sources[getCurrentSourceId()]->resumed.wait(timeout);
|
|
}
|
|
|
|
bool MockMediaPlayer::waitUntilPlaybackStopped(std::chrono::milliseconds timeout) {
|
|
return m_sources[getCurrentSourceId()]->stopped.wait(timeout);
|
|
}
|
|
|
|
bool MockMediaPlayer::waitUntilPlaybackFinished(std::chrono::milliseconds timeout) {
|
|
return m_sources[getCurrentSourceId()]->finished.wait(timeout);
|
|
}
|
|
|
|
bool MockMediaPlayer::waitUntilPlaybackError(std::chrono::milliseconds timeout) {
|
|
return m_sources[getCurrentSourceId()]->error.wait(timeout);
|
|
}
|
|
|
|
MediaPlayerInterface::SourceId MockMediaPlayer::getCurrentSourceId() {
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
return m_sources.size() - 1;
|
|
}
|
|
|
|
MockMediaPlayer::SourceState::SourceState(
|
|
Source* source,
|
|
const std::string& name,
|
|
std::function<void(std::shared_ptr<observer>, SourceId)> notifyFunction) :
|
|
m_source{source},
|
|
m_name{name},
|
|
m_notifyFunction{notifyFunction},
|
|
m_stateReached{false},
|
|
m_shutdown{false} {
|
|
}
|
|
|
|
MockMediaPlayer::SourceState::~SourceState() {
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
m_shutdown = true;
|
|
}
|
|
m_wake.notify_all();
|
|
if (m_thread.joinable()) {
|
|
m_thread.join();
|
|
}
|
|
}
|
|
|
|
void MockMediaPlayer::SourceState::trigger() {
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
if (m_stateReached) {
|
|
return;
|
|
}
|
|
m_thread = std::thread(&MockMediaPlayer::SourceState::notify, this, DEFAULT_TIME);
|
|
m_stateReached = true;
|
|
m_wake.notify_all();
|
|
}
|
|
|
|
void MockMediaPlayer::SourceState::notify(const std::chrono::milliseconds timeout) {
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
auto observer = m_source->mockMediaPlayer->m_playerObserver;
|
|
if (!m_wake.wait_for(lock, timeout, [this]() { return (m_stateReached || m_shutdown); })) {
|
|
if (observer) {
|
|
lock.unlock();
|
|
observer->onPlaybackError(
|
|
m_source->sourceId, ErrorType::MEDIA_ERROR_UNKNOWN, m_name + ": wait to notify timed out");
|
|
}
|
|
return;
|
|
}
|
|
if (observer) {
|
|
m_notifyFunction(observer, m_source->sourceId);
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool MockMediaPlayer::SourceState::wait(const std::chrono::milliseconds timeout) {
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
if (!m_wake.wait_for(lock, timeout, [this]() { return (m_stateReached || m_shutdown); })) {
|
|
return false;
|
|
}
|
|
return m_stateReached;
|
|
}
|
|
|
|
void MockMediaPlayer::SourceState::resetStateReached() {
|
|
if (m_thread.joinable()) {
|
|
m_thread.join();
|
|
}
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
m_stateReached = false;
|
|
}
|
|
|
|
static void notifyPlaybackStarted(
|
|
std::shared_ptr<MediaPlayerObserverInterface> observer,
|
|
MediaPlayerInterface::SourceId sourceId) {
|
|
observer->onPlaybackStarted(sourceId);
|
|
}
|
|
|
|
static void notifyPlaybackPaused(
|
|
std::shared_ptr<MediaPlayerObserverInterface> observer,
|
|
MediaPlayerInterface::SourceId sourceId) {
|
|
observer->onPlaybackPaused(sourceId);
|
|
}
|
|
|
|
static void notifyPlaybackResumed(
|
|
std::shared_ptr<MediaPlayerObserverInterface> observer,
|
|
MediaPlayerInterface::SourceId sourceId) {
|
|
observer->onPlaybackResumed(sourceId);
|
|
}
|
|
|
|
static void notifyPlaybackStopped(
|
|
std::shared_ptr<MediaPlayerObserverInterface> observer,
|
|
MediaPlayerInterface::SourceId sourceId) {
|
|
observer->onPlaybackStopped(sourceId);
|
|
}
|
|
|
|
static void notifyPlaybackFinished(
|
|
std::shared_ptr<MediaPlayerObserverInterface> observer,
|
|
MediaPlayerInterface::SourceId sourceId) {
|
|
observer->onPlaybackFinished(sourceId);
|
|
}
|
|
|
|
static void notifyPlaybackError(
|
|
std::shared_ptr<MediaPlayerObserverInterface> observer,
|
|
MediaPlayerInterface::SourceId sourceId) {
|
|
observer->onPlaybackError(sourceId, ErrorType::MEDIA_ERROR_INTERNAL_SERVER_ERROR, "mock error");
|
|
}
|
|
|
|
MockMediaPlayer::Source::Source(MockMediaPlayer* player, SourceId id) :
|
|
mockMediaPlayer{player},
|
|
sourceId{id},
|
|
offset{MEDIA_PLAYER_INVALID_OFFSET},
|
|
started{this, "started", notifyPlaybackStarted},
|
|
paused{this, "paused", notifyPlaybackPaused},
|
|
resumed{this, "resumed", notifyPlaybackResumed},
|
|
stopped{this, "stopped", notifyPlaybackStopped},
|
|
finished{this, "finished", notifyPlaybackFinished},
|
|
error{this, "error", notifyPlaybackError} {
|
|
}
|
|
|
|
std::shared_ptr<MockMediaPlayer::Source> MockMediaPlayer::getCurrentSource(SourceId sourceId) {
|
|
if (ERROR == sourceId || sourceId != getCurrentSourceId()) {
|
|
return nullptr;
|
|
}
|
|
return m_sources[static_cast<size_t>(sourceId)];
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace mediaPlayer
|
|
} // namespace utils
|
|
} // namespace avsCommon
|
|
} // namespace alexaClientSDK
|