blob: 1a56e80881ff478febb2d10242a235835a6ae225 [file] [log] [blame]
// Copyright 2017 The Cobalt Authors. 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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License 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 "cobalt/media_session/media_session.h"
#include <limits>
#include <memory>
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "cobalt/bindings/testing/script_object_owner.h"
#include "cobalt/extension/media_session.h"
#include "cobalt/media_session/media_session_client.h"
#include "cobalt/script/callback_function.h"
#include "cobalt/script/script_value.h"
#include "cobalt/script/testing/fake_script_value.h"
#include "cobalt/script/wrappable.h"
#include "starboard/thread.h"
#include "starboard/time.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::cobalt::script::CallbackResult;
using ::cobalt::script::ScriptValue;
using ::cobalt::script::Wrappable;
using ::cobalt::script::testing::FakeScriptValue;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::InvokeWithoutArgs;
using ::testing::Return;
namespace cobalt {
namespace media_session {
namespace {
class MockMediaSessionClient;
class MockCallbackFunction : public MediaSession::MediaSessionActionHandler {
public:
MOCK_CONST_METHOD1(
Run, ReturnValue(const MediaSessionActionDetails& action_details));
};
class MockMediaSessionClient : public MediaSessionClient {
public:
explicit MockMediaSessionClient(MediaSession* media_session) :
MediaSessionClient(media_session) {
}
void OnMediaSessionStateChanged(const MediaSessionState& session_state)
override {
session_state_ = session_state;
++session_change_count_;
MediaSessionClient::OnMediaSessionStateChanged(session_state);
}
void WaitForSessionStateChange() {
size_t current_change_count = session_change_count_;
while (GetMediaSession()->IsChangeTaskQueuedForTesting()) {
base::RunLoop().RunUntilIdle();
if (current_change_count != session_change_count_) {
break;
}
SbThreadSleep(kSbTimeMillisecond);
}
}
MediaSessionState GetMediaSessionState() const { return session_state_; }
size_t GetMediaSessionChangeCount() const { return session_change_count_; }
MediaSessionState session_state_;
size_t session_change_count_ = 0;
};
class MockMediaSession : public MediaSession {
public:
explicit MockMediaSession(MockMediaSessionClient* client)
: MediaSession(client) {}
MockMediaSessionClient* mock_session_client() {
return static_cast<MockMediaSessionClient*>(media_session_client());
}
MOCK_CONST_METHOD0(GetMonotonicNow, SbTimeMonotonic());
};
MATCHER_P(SeekTime, time, "") {
return arg.action() == kMediaSessionActionSeekto && arg.seek_time() == time;
}
MATCHER_P2(SeekOffset, action, offset, "") {
return arg.action() == action && arg.seek_offset() == offset;
}
MATCHER_P(SeekNoOffset, action, "") {
return arg.action() == action && !arg.has_seek_offset();
}
TEST(MediaSessionTest, MediaSessionTest) {
base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
scoped_refptr<MockMediaSession> session =
scoped_refptr<MockMediaSession>(new MockMediaSession(
new MockMediaSessionClient(nullptr)));
session->media_session_client()->set_media_session(session);
EXPECT_EQ(kMediaSessionPlaybackStateNone, session->playback_state());
session->set_playback_state(kMediaSessionPlaybackStatePlaying);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(kMediaSessionPlaybackStatePlaying, session->mock_session_client()
->GetMediaSessionState().actual_playback_state());
EXPECT_EQ(session->mock_session_client()->GetMediaSessionChangeCount(), 1);
}
TEST(MediaSessionTest, ActualPlaybackState) {
base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
scoped_refptr<MockMediaSession> session =
scoped_refptr<MockMediaSession>(new MockMediaSession(
new MockMediaSessionClient(nullptr)));
session->media_session_client()->set_media_session(session);
// Trigger a session state change without impacting playback state.
session->set_metadata(new MediaMetadata);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(session->mock_session_client()->GetMediaSessionChangeCount(), 1);
EXPECT_EQ(kMediaSessionPlaybackStateNone, session->mock_session_client()
->GetMediaSessionState().actual_playback_state());
session->mock_session_client()->UpdatePlatformPlaybackState(
kCobaltExtensionMediaSessionPlaying);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(kMediaSessionPlaybackStatePlaying, session->mock_session_client()
->GetMediaSessionState().actual_playback_state());
session->set_playback_state(kMediaSessionPlaybackStatePlaying);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(kMediaSessionPlaybackStatePlaying, session->mock_session_client()
->GetMediaSessionState().actual_playback_state());
session->set_playback_state(kMediaSessionPlaybackStatePaused);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(kMediaSessionPlaybackStatePlaying, session->mock_session_client()
->GetMediaSessionState().actual_playback_state());
session->mock_session_client()->UpdatePlatformPlaybackState(
kCobaltExtensionMediaSessionPaused);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(kMediaSessionPlaybackStatePaused, session->mock_session_client()
->GetMediaSessionState().actual_playback_state());
session->set_playback_state(kMediaSessionPlaybackStateNone);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(kMediaSessionPlaybackStateNone, session->mock_session_client()
->GetMediaSessionState().actual_playback_state());
session->mock_session_client()->UpdatePlatformPlaybackState(
kCobaltExtensionMediaSessionNone);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(kMediaSessionPlaybackStateNone, session->mock_session_client()
->GetMediaSessionState().actual_playback_state());
EXPECT_GE(session->mock_session_client()->GetMediaSessionChangeCount(), 2);
}
TEST(MediaSessionTest, NullActionClears) {
base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
scoped_refptr<MockMediaSession> session =
scoped_refptr<MockMediaSession>(new MockMediaSession(
new MockMediaSessionClient(nullptr)));
session->media_session_client()->set_media_session(session);
// Trigger a session state change without impacting playback state.
session->set_metadata(new MediaMetadata);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(session->mock_session_client()->GetMediaSessionChangeCount(), 1);
MediaSessionState state =
session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(kMediaSessionPlaybackStateNone, state.actual_playback_state());
EXPECT_EQ(0, state.available_actions().to_ulong());
MockCallbackFunction cf;
EXPECT_CALL(cf, Run(_))
.Times(1)
.WillRepeatedly(Return(CallbackResult<void>()));
FakeScriptValue<MediaSession::MediaSessionActionHandler> holder(&cf);
FakeScriptValue<MediaSession::MediaSessionActionHandler> null_holder(NULL);
session->SetActionHandler(kMediaSessionActionPlay, holder);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(1, session->mock_session_client()
->GetMediaSessionState().available_actions().to_ulong());
session->mock_session_client()->InvokeAction(
kCobaltExtensionMediaSessionActionPlay);
session->SetActionHandler(kMediaSessionActionPlay, null_holder);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(0, session->mock_session_client()
->GetMediaSessionState().available_actions().to_ulong());
session->mock_session_client()->InvokeAction(
kCobaltExtensionMediaSessionActionPlay);
EXPECT_GE(session->mock_session_client()->GetMediaSessionChangeCount(), 3);
}
TEST(MediaSessionTest, AvailableActions) {
base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
MediaSessionState state;
scoped_refptr<MockMediaSession> session =
scoped_refptr<MockMediaSession>(new MockMediaSession(
new MockMediaSessionClient(nullptr)));
session->media_session_client()->set_media_session(session);
// Trigger a session state change without impacting playback state.
session->set_metadata(new MediaMetadata);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(session->mock_session_client()->GetMediaSessionChangeCount(), 1);
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(kMediaSessionPlaybackStateNone, state.actual_playback_state());
EXPECT_EQ(0, state.available_actions().to_ulong());
MockCallbackFunction cf;
EXPECT_CALL(cf, Run(_)).Times(0);
FakeScriptValue<MediaSession::MediaSessionActionHandler> holder(&cf);
session->SetActionHandler(kMediaSessionActionPlay, holder);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(1 << kMediaSessionActionPlay,
state.available_actions().to_ulong());
session->SetActionHandler(kMediaSessionActionPause, holder);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(1 << kMediaSessionActionPlay,
state.available_actions().to_ulong());
session->SetActionHandler(kMediaSessionActionSeekto, holder);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(1 << kMediaSessionActionPlay, state.available_actions().to_ulong());
session->mock_session_client()->UpdatePlatformPlaybackState(
kCobaltExtensionMediaSessionPlaying);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(kMediaSessionPlaybackStatePlaying, state.actual_playback_state());
EXPECT_EQ(1 << kMediaSessionActionPause | 1 << kMediaSessionActionSeekto,
state.available_actions().to_ulong());
session->set_playback_state(kMediaSessionPlaybackStatePlaying);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(kMediaSessionPlaybackStatePlaying, state.actual_playback_state());
EXPECT_EQ(1 << kMediaSessionActionPause | 1 << kMediaSessionActionSeekto,
state.available_actions().to_ulong());
session->set_playback_state(kMediaSessionPlaybackStatePaused);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(kMediaSessionPlaybackStatePlaying, state.actual_playback_state());
EXPECT_EQ(1 << kMediaSessionActionPause | 1 << kMediaSessionActionSeekto,
state.available_actions().to_ulong());
session->set_playback_state(kMediaSessionPlaybackStatePlaying);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(kMediaSessionPlaybackStatePlaying, state.actual_playback_state());
EXPECT_EQ(1 << kMediaSessionActionPause | 1 << kMediaSessionActionSeekto,
state.available_actions().to_ulong());
session->mock_session_client()->UpdatePlatformPlaybackState(
kCobaltExtensionMediaSessionPaused);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(kMediaSessionPlaybackStatePlaying, state.actual_playback_state());
EXPECT_EQ(1 << kMediaSessionActionPause | 1 << kMediaSessionActionSeekto,
state.available_actions().to_ulong());
session->set_playback_state(kMediaSessionPlaybackStateNone);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(kMediaSessionPlaybackStateNone, state.actual_playback_state());
EXPECT_EQ(1 << kMediaSessionActionPlay, state.available_actions().to_ulong());
session->set_playback_state(kMediaSessionPlaybackStatePaused);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(kMediaSessionPlaybackStatePaused, state.actual_playback_state());
EXPECT_EQ(1 << kMediaSessionActionPlay | 1 << kMediaSessionActionSeekto,
state.available_actions().to_ulong());
session->mock_session_client()->UpdatePlatformPlaybackState(
kCobaltExtensionMediaSessionNone);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(kMediaSessionPlaybackStatePaused, state.actual_playback_state());
EXPECT_EQ(1 << kMediaSessionActionPlay | 1 << kMediaSessionActionSeekto,
state.available_actions().to_ulong());
}
TEST(MediaSessionTest, InvokeAction) {
base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
scoped_refptr<MockMediaSession> session =
scoped_refptr<MockMediaSession>(new MockMediaSession(
new MockMediaSessionClient(nullptr)));
session->media_session_client()->set_media_session(session);
MockCallbackFunction cf;
FakeScriptValue<MediaSession::MediaSessionActionHandler> holder(&cf);
CobaltExtensionMediaSessionActionDetails details;
session->SetActionHandler(kMediaSessionActionSeekto, holder);
EXPECT_CALL(cf, Run(SeekTime(1.2))).WillOnce(Return(CallbackResult<void>()));
CobaltExtensionMediaSessionActionDetailsInit(
&details, kCobaltExtensionMediaSessionActionSeekto);
details.seek_time = 1.2;
session->mock_session_client()->InvokeAction(details);
}
TEST(MediaSessionTest, SeekDetails) {
base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
scoped_refptr<MockMediaSession> session =
scoped_refptr<MockMediaSession>(new MockMediaSession(
new MockMediaSessionClient(nullptr)));
session->media_session_client()->set_media_session(session);
MockCallbackFunction cf;
FakeScriptValue<MediaSession::MediaSessionActionHandler> holder(&cf);
CobaltExtensionMediaSessionActionDetails details;
session->SetActionHandler(kMediaSessionActionSeekto, holder);
session->SetActionHandler(kMediaSessionActionSeekforward, holder);
session->SetActionHandler(kMediaSessionActionSeekbackward, holder);
EXPECT_CALL(cf, Run(SeekNoOffset(kMediaSessionActionSeekforward)))
.WillOnce(Return(CallbackResult<void>()));
session->mock_session_client()->InvokeAction(
kCobaltExtensionMediaSessionActionSeekforward);
EXPECT_CALL(cf, Run(SeekNoOffset(kMediaSessionActionSeekbackward)))
.WillOnce(Return(CallbackResult<void>()));
session->mock_session_client()->InvokeAction(
kCobaltExtensionMediaSessionActionSeekbackward);
EXPECT_CALL(cf, Run(SeekTime(1.2))).WillOnce(Return(CallbackResult<void>()));
CobaltExtensionMediaSessionActionDetailsInit(
&details, kCobaltExtensionMediaSessionActionSeekto);
details.seek_time = 1.2;
session->mock_session_client()->InvokeAction(details);
EXPECT_CALL(cf, Run(SeekOffset(kMediaSessionActionSeekforward, 3.4)))
.WillOnce(Return(CallbackResult<void>()));
CobaltExtensionMediaSessionActionDetailsInit(
&details, kCobaltExtensionMediaSessionActionSeekforward);
details.seek_offset = 3.4;
session->mock_session_client()->InvokeAction(details);
EXPECT_CALL(cf, Run(SeekOffset(kMediaSessionActionSeekbackward, 5.6)))
.WillOnce(Return(CallbackResult<void>()));
CobaltExtensionMediaSessionActionDetailsInit(
&details, kCobaltExtensionMediaSessionActionSeekbackward);
details.seek_offset = 5.6;
session->mock_session_client()->InvokeAction(details);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_GE(session->mock_session_client()->GetMediaSessionChangeCount(), 0);
}
TEST(MediaSessionTest, PositionState) {
base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
scoped_refptr<MockMediaSession> session =
scoped_refptr<MockMediaSession>(new MockMediaSession(
new MockMediaSessionClient(nullptr)));
session->media_session_client()->set_media_session(session);
MediaSessionState state;
SbTimeMonotonic start_time = 1111111111;
base::Optional<MediaPositionState> position_state;
position_state.emplace();
position_state->set_duration(100.0);
position_state->set_position(10.0);
// Trigger a session state change without impacting playback state.
session->set_metadata(new MediaMetadata);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(session->mock_session_client()->GetMediaSessionChangeCount(), 1);
// Position state not yet reported
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(0,
state.GetCurrentPlaybackPosition(start_time + 999 * kSbTimeSecond));
EXPECT_EQ(0, state.duration());
EXPECT_EQ(0.0, state.actual_playback_rate());
// Forward playback
EXPECT_CALL(*session, GetMonotonicNow()).WillOnce(Return(start_time));
position_state->set_playback_rate(1.0);
session->SetPositionState(position_state);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ((10 + 50) * kSbTimeSecond,
state.GetCurrentPlaybackPosition(start_time + 50 * kSbTimeSecond));
EXPECT_EQ(100 * kSbTimeSecond,
state.GetCurrentPlaybackPosition(start_time + 150 * kSbTimeSecond));
EXPECT_EQ(100 * kSbTimeSecond, state.duration());
EXPECT_EQ(1.0, state.actual_playback_rate());
// Fast playback
EXPECT_CALL(*session, GetMonotonicNow()).WillOnce(Return(start_time));
position_state->set_playback_rate(2.0);
session->SetPositionState(position_state);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ((10 + 2 * 20) * kSbTimeSecond,
state.GetCurrentPlaybackPosition(start_time + 20 * kSbTimeSecond));
EXPECT_EQ(100 * kSbTimeSecond,
state.GetCurrentPlaybackPosition(start_time + 50 * kSbTimeSecond));
EXPECT_EQ(100 * kSbTimeSecond, state.duration());
EXPECT_EQ(2.0, state.actual_playback_rate());
// Reverse playback
EXPECT_CALL(*session, GetMonotonicNow()).WillOnce(Return(start_time));
position_state->set_playback_rate(-1.0);
session->SetPositionState(position_state);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(0 * kSbTimeSecond,
state.GetCurrentPlaybackPosition(start_time + 20 * kSbTimeSecond));
EXPECT_EQ((10 - 3) * kSbTimeSecond,
state.GetCurrentPlaybackPosition(start_time + 3 * kSbTimeSecond));
EXPECT_EQ(100 * kSbTimeSecond, state.duration());
EXPECT_EQ(-1.0, state.actual_playback_rate());
// Indefinite duration (live) playback
EXPECT_CALL(*session, GetMonotonicNow()).WillOnce(Return(start_time));
position_state->set_duration(std::numeric_limits<double>::infinity());
position_state->set_playback_rate(1.0);
session->SetPositionState(position_state);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(10 * kSbTimeSecond + 1 * kSbTimeDay,
state.GetCurrentPlaybackPosition(start_time + 1 * kSbTimeDay));
EXPECT_EQ(kSbTimeMax, state.duration());
EXPECT_EQ(1.0, state.actual_playback_rate());
// Paused playback
// (Actual playback rate is 0.0, so position is the last reported position.
// The web app should update position and playback states together.)
session->set_playback_state(kMediaSessionPlaybackStatePaused);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(10 * kSbTimeSecond,
state.GetCurrentPlaybackPosition(start_time + 999 * kSbTimeSecond));
EXPECT_EQ(kSbTimeMax, state.duration());
EXPECT_EQ(0.0, state.actual_playback_rate());
session->set_playback_state(kMediaSessionPlaybackStatePlaying);
// Position state cleared
EXPECT_CALL(*session, GetMonotonicNow()).WillOnce(Return(start_time));
session->SetPositionState(base::nullopt);
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
EXPECT_EQ(0,
state.GetCurrentPlaybackPosition(start_time + 999 * kSbTimeSecond));
EXPECT_EQ(0, state.duration());
EXPECT_EQ(0.0, state.actual_playback_rate());
EXPECT_GE(session->mock_session_client()->GetMediaSessionChangeCount(), 3);
}
TEST(MediaSessionTest, Metadata) {
base::MessageLoop message_loop(base::MessageLoop::TYPE_DEFAULT);
scoped_refptr<MockMediaSession> session =
scoped_refptr<MockMediaSession>(new MockMediaSession(
new MockMediaSessionClient(nullptr)));
session->media_session_client()->set_media_session(session);
MediaSessionState state;
MediaMetadataInit init_metadata;
base::Optional<MediaMetadataInit> state_metadata;
// Trigger a session state change without impacting metadata.
session->set_playback_state(kMediaSessionPlaybackStateNone);
session->mock_session_client()->WaitForSessionStateChange();
EXPECT_EQ(session->mock_session_client()->GetMediaSessionChangeCount(), 1);
// Metadata not yet set
state = session->mock_session_client()->GetMediaSessionState();
state_metadata = state.metadata();
EXPECT_FALSE(state.has_metadata());
EXPECT_FALSE(state_metadata.has_value());
// Set metadata and make sure it gets into the MediaSessionState
init_metadata = MediaMetadataInit();
init_metadata.set_title("title");
init_metadata.set_artist("artist");
init_metadata.set_album("album");
MediaImage art_image;
art_image.set_src("http://art.image");
script::Sequence<MediaImage> artwork;
artwork.push_back(art_image);
init_metadata.set_artwork(artwork);
session->set_metadata(
scoped_refptr<MediaMetadata>(new MediaMetadata(init_metadata)));
session->mock_session_client()->WaitForSessionStateChange();
state = session->mock_session_client()->GetMediaSessionState();
state_metadata = state.metadata();
EXPECT_TRUE(state.has_metadata());
EXPECT_TRUE(state_metadata.has_value());
EXPECT_EQ("title", state_metadata->title());
EXPECT_EQ("artist", state_metadata->artist());
EXPECT_EQ("album", state_metadata->album());
EXPECT_EQ(1, state_metadata->artwork().size());
EXPECT_EQ("http://art.image", state_metadata->artwork().at(0).src());
EXPECT_GE(session->mock_session_client()->GetMediaSessionChangeCount(), 2);
}
} // namespace
} // namespace media_session
} // namespace cobalt