mirror of
https://github.com/LowganDev/PlayfinPS4.git
synced 2026-10-18 12:00:59 +02:00
135 lines
5.5 KiB
C++
135 lines
5.5 KiB
C++
#include <doctest/doctest.h>
|
|
|
|
#include <string>
|
|
|
|
#include "jellyfin/client.hpp"
|
|
#include "jellyfin/models.hpp"
|
|
#include "support/fake_jellyfin.hpp"
|
|
#include "support/files.hpp"
|
|
|
|
using namespace playfin;
|
|
using jellyfin::StreamType;
|
|
using test::readFixture;
|
|
|
|
namespace {
|
|
|
|
jellyfin::Session signedIn(const std::string& baseUrl) {
|
|
jellyfin::Session s;
|
|
s.baseUrl = baseUrl;
|
|
s.deviceId = "dev-1";
|
|
s.deviceName = "Linux";
|
|
s.token = "tok-demo";
|
|
s.userId = "u-1";
|
|
return s;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("PlaybackInfo: media source, streams and defaults") {
|
|
const auto info = jellyfin::parsePlaybackInfo(readFixture("jellyfin/playbackinfo_direct.json"));
|
|
REQUIRE(info.ok());
|
|
CHECK(info.value().playSessionId == "ps-direct-1");
|
|
REQUIRE(info.value().sources.size() == 1);
|
|
const jellyfin::MediaSource& source = info.value().sources[0];
|
|
CHECK(source.id == "ms-1");
|
|
CHECK(source.container == "mkv");
|
|
CHECK(source.supportsDirectPlay);
|
|
CHECK(source.runTimeTicks == 26000000000);
|
|
CHECK(source.defaultAudioIndex == 1);
|
|
CHECK(source.defaultSubtitleIndex == 3);
|
|
REQUIRE(source.streams.size() == 6);
|
|
CHECK(source.streams[0].type == StreamType::Video);
|
|
CHECK(source.streams[0].height == 1080);
|
|
CHECK(source.streams[1].type == StreamType::Audio);
|
|
CHECK(source.streams[1].channels == 6);
|
|
CHECK(source.streams[1].title == "VF");
|
|
CHECK(source.streams[3].isForced);
|
|
CHECK(source.streams[4].isExternal);
|
|
CHECK(source.streams[4].deliveryMethod == "External");
|
|
CHECK(source.streams[4].deliveryUrl == "/Videos/e-1/ms-1/Subtitles/4/0/Stream.srt");
|
|
CHECK(source.streams[5].codec == "pgssub"); // lower case
|
|
}
|
|
|
|
TEST_CASE("PlaybackInfo: transcoding URL and missing default subtitle") {
|
|
const auto info = jellyfin::parsePlaybackInfo(readFixture("jellyfin/playbackinfo_transcode.json"));
|
|
REQUIRE(info.ok());
|
|
const jellyfin::MediaSource& source = info.value().sources.at(0);
|
|
CHECK_FALSE(source.supportsDirectPlay);
|
|
CHECK(source.transcodingUrl.rfind("/videos/m-transcode/master.m3u8?", 0) == 0);
|
|
CHECK(source.defaultSubtitleIndex == -1);
|
|
}
|
|
|
|
TEST_CASE("PlaybackInfo: hostile replies give an error or capped lists, never a crash") {
|
|
CHECK_FALSE(jellyfin::parsePlaybackInfo("").ok());
|
|
CHECK_FALSE(jellyfin::parsePlaybackInfo("[]").ok());
|
|
const auto noSources = jellyfin::parsePlaybackInfo(R"({"MediaSources":"x","ErrorCode":"NoCompatibleStream"})");
|
|
REQUIRE(noSources.ok());
|
|
CHECK(noSources.value().sources.empty());
|
|
CHECK(noSources.value().errorCode == "NoCompatibleStream");
|
|
std::string many = R"({"MediaSources":[{"Id":"m","MediaStreams":[)";
|
|
for (int i = 0; i < 10000; ++i) {
|
|
many += std::string(i == 0 ? "" : ",") + R"({"Type":"Audio","Index":)" + std::to_string(i) + "}";
|
|
}
|
|
many += "]}]}";
|
|
const auto capped = jellyfin::parsePlaybackInfo(many);
|
|
REQUIRE(capped.ok());
|
|
CHECK(capped.value().sources.at(0).streams.size() == 512);
|
|
}
|
|
|
|
TEST_CASE("media segments: intro and outro, other kinds kept as Other") {
|
|
const auto segments = jellyfin::parseMediaSegments(readFixture("jellyfin/segments.json"));
|
|
REQUIRE(segments.ok());
|
|
REQUIRE(segments.value().size() == 3);
|
|
CHECK(segments.value()[0].type == jellyfin::SegmentType::Intro);
|
|
CHECK(segments.value()[0].endTicks == 20000000);
|
|
CHECK(segments.value()[1].type == jellyfin::SegmentType::Outro);
|
|
CHECK(segments.value()[2].type == jellyfin::SegmentType::Other);
|
|
CHECK_FALSE(jellyfin::parseMediaSegments("{").ok());
|
|
}
|
|
|
|
TEST_CASE("playback calls against the server: info, segments, reporting") {
|
|
test::FakeJellyfin server;
|
|
const net::HttpClient http{net::HttpConfig{}};
|
|
const jellyfin::Client client(http, signedIn(server.baseUrl()));
|
|
|
|
jellyfin::PlaybackRequest request;
|
|
request.startTicks = 1230000000;
|
|
request.deviceProfileJson = R"({"Name":"Playfin"})";
|
|
const auto info = client.playbackInfo("e-1", request);
|
|
REQUIRE(info.ok());
|
|
CHECK(info.value().playSessionId == "ps-direct-1");
|
|
const auto seen = server.last();
|
|
CHECK(seen.method == "POST");
|
|
CHECK(seen.body.find(R"("DeviceProfile":{"Name":"Playfin"})") != std::string::npos);
|
|
CHECK(seen.body.find(R"("StartTimeTicks":1230000000)") != std::string::npos);
|
|
CHECK(seen.body.find(R"("MaxStreamingBitrate":40000000)") != std::string::npos);
|
|
CHECK(seen.target.find("tok-demo") == std::string::npos);
|
|
|
|
const auto segments = client.mediaSegments("e-1");
|
|
REQUIRE(segments.ok());
|
|
CHECK(segments.value().size() == 3);
|
|
const auto none = client.mediaSegments("m-none"); // 404: a server without segments
|
|
REQUIRE(none.ok());
|
|
CHECK(none.value().empty());
|
|
|
|
jellyfin::PlaybackState state;
|
|
state.itemId = "e-1";
|
|
state.mediaSourceId = "ms-1";
|
|
state.playSessionId = "ps-direct-1";
|
|
state.positionTicks = 1230000000;
|
|
state.audioIndex = 1;
|
|
state.subtitleIndex = -1;
|
|
REQUIRE(client.reportStart(state).ok());
|
|
REQUIRE(client.reportProgress(state).ok());
|
|
state.paused = true;
|
|
REQUIRE(client.reportStopped(state).ok());
|
|
const auto reports = server.reports();
|
|
REQUIRE(reports.size() == 3);
|
|
CHECK(reports[0].path == "/Sessions/Playing");
|
|
CHECK(reports[1].path == "/Sessions/Playing/Progress");
|
|
CHECK(reports[2].path == "/Sessions/Playing/Stopped");
|
|
CHECK(reports[1].body.find(R"("PlayMethod":"DirectPlay")") != std::string::npos);
|
|
CHECK(reports[1].body.find(R"("PositionTicks":1230000000)") != std::string::npos);
|
|
CHECK(reports[2].body.find(R"("IsPaused":true)") != std::string::npos);
|
|
}
|