1 /*
2  * Copyright (C) 2018 Emeric Poupon
3  *
4  * This file is part of LMS.
5  *
6  * LMS is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * LMS is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with LMS.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "MediaPlayer.hpp"
21 
22 #include <Wt/Json/Object.h>
23 #include <Wt/Json/Value.h>
24 #include <Wt/Json/Serializer.h>
25 
26 #include "utils/Logger.hpp"
27 
28 #include "database/Artist.hpp"
29 #include "database/Release.hpp"
30 #include "database/Session.hpp"
31 #include "database/Track.hpp"
32 #include "database/TrackList.hpp"
33 #include "database/Types.hpp"
34 #include "database/User.hpp"
35 
36 #include "resource/CoverResource.hpp"
37 #include "resource/AudioTranscodeResource.hpp"
38 #include "resource/AudioFileResource.hpp"
39 
40 #include "utils/String.hpp"
41 #include "utils/Utils.hpp"
42 
43 #include "LmsApplication.hpp"
44 
45 namespace UserInterface {
46 
settingsToJSString(const MediaPlayer::Settings & settings)47 static std::string settingsToJSString(const MediaPlayer::Settings& settings)
48 {
49 	namespace Json = Wt::Json;
50 
51 	Json::Object res;
52 
53 	{
54 		Json::Object transcode;
55 		transcode["mode"] = static_cast<int>(settings.transcode.mode);
56 		transcode["format"] = static_cast<int>(settings.transcode.format);
57 		transcode["bitrate"] = static_cast<int>(settings.transcode.bitrate);
58 		res["transcode"] = std::move(transcode);
59 	}
60 
61 	{
62 		Json::Object replayGain;
63 		replayGain["mode"] = static_cast<int>(settings.replayGain.mode);
64 		replayGain["preAmpGain"] = settings.replayGain.preAmpGain;
65 		replayGain["preAmpGainIfNoInfo"] = settings.replayGain.preAmpGainIfNoInfo;
66 		res["replayGain"] = std::move(replayGain);
67 	}
68 
69 	return Json::serialize(res);
70 }
71 
72 static
73 std::optional<MediaPlayer::Settings::Transcode::Mode>
transcodeModeFromString(const std::string & str)74 transcodeModeFromString(const std::string& str)
75 {
76 	const auto value {StringUtils::readAs<int>(str)};
77 	if (!value)
78 		return std::nullopt;
79 
80 	MediaPlayer::Settings::Transcode::Mode mode {static_cast<MediaPlayer::Settings::Transcode::Mode>(*value)};
81 	switch (mode)
82 	{
83 		case MediaPlayer::Settings::Transcode::Mode::Never:
84 		case MediaPlayer::Settings::Transcode::Mode::Always:
85 		case MediaPlayer::Settings::Transcode::Mode::IfFormatNotSupported:
86 			return mode;
87 	}
88 
89 	return std::nullopt;
90 }
91 
92 static
93 std::optional<MediaPlayer::Format>
formatFromString(const std::string & str)94 formatFromString(const std::string& str)
95 {
96 	const auto value {StringUtils::readAs<int>(str)};
97 	if (!value)
98 		return std::nullopt;
99 
100 	MediaPlayer::Format format {static_cast<MediaPlayer::Format>(*value)};
101 	switch (format)
102 	{
103 		case MediaPlayer::Format::MP3:
104 		case MediaPlayer::Format::OGG_OPUS:
105 		case MediaPlayer::Format::MATROSKA_OPUS:
106 		case MediaPlayer::Format::OGG_VORBIS:
107 		case MediaPlayer::Format::WEBM_VORBIS:
108 			return format;
109 	}
110 
111 	return std::nullopt;
112 }
113 
114 static
115 std::optional<MediaPlayer::Bitrate>
bitrateFromString(const std::string & str)116 bitrateFromString(const std::string& str)
117 {
118 	const auto value {StringUtils::readAs<int>(str)};
119 	if (!value)
120 		return std::nullopt;
121 
122 	if (Database::User::audioTranscodeAllowedBitrates.find(*value) != std::cend(Database::User::audioTranscodeAllowedBitrates))
123 		return *value;
124 
125 	return std::nullopt;
126 }
127 
128 static
129 std::optional<MediaPlayer::Settings::ReplayGain::Mode>
replayGainModeFromString(const std::string & str)130 replayGainModeFromString(const std::string& str)
131 {
132 	const auto value {StringUtils::readAs<int>(str)};
133 	if (!value)
134 		return std::nullopt;
135 
136 	MediaPlayer::Settings::ReplayGain::Mode mode {static_cast<MediaPlayer::Settings::ReplayGain::Mode>(*value)};
137 	switch (mode)
138 	{
139 		case MediaPlayer::Settings::ReplayGain::Mode::None:
140 		case MediaPlayer::Settings::ReplayGain::Mode::Auto:
141 		case MediaPlayer::Settings::ReplayGain::Mode::Track:
142 		case MediaPlayer::Settings::ReplayGain::Mode::Release:
143 			return mode;
144 	}
145 
146 	return std::nullopt;
147 }
148 
149 static
150 std::optional<float>
replayGainPreAmpGainFromString(const std::string & str)151 replayGainPreAmpGainFromString(const std::string& str)
152 {
153 	const auto value {StringUtils::readAs<double>(str)};
154 	if (!value)
155 		return std::nullopt;
156 
157 	return clamp(*value, (double)MediaPlayer::Settings::ReplayGain::minPreAmpGain, (double)MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
158 }
159 
settingsfromJSString(const std::string & strSettings)160 static MediaPlayer::Settings settingsfromJSString(const std::string& strSettings)
161 {
162 	using Settings = MediaPlayer::Settings;
163 	namespace Json = Wt::Json;
164 	Json::Object parsedSettings;
165 
166 	Json::parse(strSettings, parsedSettings);
167 
168 	MediaPlayer::Settings settings;
169 
170 	{
171 		const Json::Value transcodeValue {parsedSettings.get("transcode")};
172 		if (transcodeValue.type() == Json::Type::Object)
173 		{
174 			const Json::Object transcode {transcodeValue};
175 			settings.transcode.mode = transcodeModeFromString(transcode.get("mode").toString().orIfNull("")).value_or(Settings::Transcode::defaultMode);
176 			settings.transcode.format = formatFromString(transcode.get("format").toString().orIfNull("")).value_or(Settings::Transcode::defaultFormat);
177 			settings.transcode.bitrate = bitrateFromString(transcode.get("bitrate").toString().orIfNull("")).value_or(Settings::Transcode::defaultBitrate);
178 		}
179 	}
180 	{
181 		const Json::Value replayGainValue {parsedSettings.get("replayGain")};
182 		if (replayGainValue.type() == Json::Type::Object)
183 		{
184 			const Json::Object replayGain {replayGainValue};
185 			settings.replayGain.mode = replayGainModeFromString(replayGain.get("mode").toString().orIfNull("")).value_or(Settings::ReplayGain::defaultMode);
186 			settings.replayGain.preAmpGain = replayGainPreAmpGainFromString(replayGain.get("preAmpGain").toString().orIfNull("")).value_or(Settings::ReplayGain::defaultPreAmpGain);
187 			settings.replayGain.preAmpGainIfNoInfo = replayGainPreAmpGainFromString(replayGain.get("preAmpGainIfNoInfo").toString().orIfNull("")).value_or(Settings::ReplayGain::defaultPreAmpGain);
188 		}
189 	}
190 
191 	return settings;
192 }
193 
MediaPlayer()194 MediaPlayer::MediaPlayer()
195 : Wt::WTemplate {Wt::WString::tr("Lms.MediaPlayer.template")}
196 , playPrevious {this, "playPrevious"}
197 , playNext {this, "playNext"}
198 , scrobbleListenNow {this, "scrobbleListenNow"}
199 , scrobbleListenFinished {this, "scrobbleListenFinished"}
200 , playbackEnded {this, "playbackEnded"}
201 , _settingsLoaded {this, "settingsLoaded"}
202 {
203 	addFunction("tr", &Wt::WTemplate::Functions::tr);
204 
205 	_audioTranscodeResource = std::make_unique<AudioTranscodeResource>();
206 	_audioFileResource = std::make_unique<AudioFileResource>();
207 
208 	_title = bindNew<Wt::WText>("title");
209 	_artist = bindNew<Wt::WAnchor>("artist");
210 	_release = bindNew<Wt::WAnchor>("release");
211 
212 	_settingsLoaded.connect([this](const std::string& settings)
__anond0fe43cc0102(const std::string& settings) 213 	{
214 		LMS_LOG(UI, DEBUG) << "Settings loaded! '" << settings << "'";
215 
216 		_settings = settingsfromJSString(settings);
217 
218 		settingsLoaded.emit();
219 	});
220 
221 	{
222 		Settings defaultSettings;
223 
224 		std::ostringstream oss;
225 		oss << "LMS.mediaplayer.init("
226 			<< jsRef()
227 			<< ", defaultSettings = " << settingsToJSString(defaultSettings)
228 			<< ")";
229 
230 		LMS_LOG(UI, DEBUG) << "Running js = '" << oss.str() << "'";
231 		doJavaScript(oss.str());
232 	}
233 }
234 
235 void
loadTrack(Database::IdType trackId,bool play,float replayGain)236 MediaPlayer::loadTrack(Database::IdType trackId, bool play, float replayGain)
237 {
238 	LMS_LOG(UI, DEBUG) << "Playing track ID = " << trackId;
239 
240 	std::ostringstream oss;
241 	{
242 		auto transaction {LmsApp->getDbSession().createSharedTransaction()};
243 
244 		const auto track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
245 		if (!track)
246 			return;
247 
248 		const std::string transcodeResource {_audioTranscodeResource->getUrl(trackId)};
249 		const std::string nativeResource {_audioFileResource->getUrl(trackId)};
250 
251 		const auto artists {track->getArtists({Database::TrackArtistLinkType::Artist})};
252 
253 		oss
254 			<< "var params = {"
255 			<< " trackId :\"" << trackId << "\","
256 			<< " nativeResource: \"" << nativeResource << "\","
257 			<< " transcodeResource: \"" << transcodeResource << "\","
258 			<< " duration: " << std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count() << ","
259 			<< " replayGain: " << replayGain << ","
260 			<< " title: \"" << StringUtils::jsEscape(track->getName()) << "\","
261 			<< " artist: \"" << (!artists.empty() ? StringUtils::jsEscape(artists.front()->getName()) : "") << "\","
262 			<< " release: \"" << (track->getRelease() ? StringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
263 			<< " artwork: ["
264 			<< "   { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Small) << "\", sizes: \"128x128\",	type: \"image/jpeg\" },"
265 			<< "   { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Large) << "\", sizes: \"512x512\",	type: \"image/jpeg\" },"
266 			<< " ]"
267 			<< "};";
268 		oss << "LMS.mediaplayer.loadTrack(params, " << (play ? "true" : "false") << ")"; // true to autoplay
269 
270 		_title->setTextFormat(Wt::TextFormat::Plain);
271 		_title->setText(Wt::WString::fromUTF8(track->getName()));
272 
273 		if (!artists.empty())
274 		{
275 			_artist->setTextFormat(Wt::TextFormat::Plain);
276 			_artist->setText(Wt::WString::fromUTF8(artists.front()->getName()));
277 			_artist->setLink(LmsApp->createArtistLink(artists.front()));
278 		}
279 		else
280 		{
281 			_artist->setText("");
282 			_artist->setLink({});
283 		}
284 
285 		if (track->getRelease())
286 		{
287 			_release->setTextFormat(Wt::TextFormat::Plain);
288 			_release->setText(Wt::WString::fromUTF8(track->getRelease()->getName()));
289 			_release->setLink(LmsApp->createReleaseLink(track->getRelease()));
290 		}
291 		else
292 		{
293 			_release->setText("");
294 			_release->setLink({});
295 		}
296 	}
297 
298 	LMS_LOG(UI, DEBUG) << "Running js = '" << oss.str() << "'";
299 	wApp->doJavaScript(oss.str());
300 
301 	_trackIdLoaded = trackId;
302 	trackLoaded.emit(*_trackIdLoaded);
303 }
304 
305 void
stop()306 MediaPlayer::stop()
307 {
308 	wApp->doJavaScript("LMS.mediaplayer.stop()");
309 }
310 
311 void
setSettings(const Settings & settings)312 MediaPlayer::setSettings(const Settings& settings)
313 {
314 	_settings = settings;
315 
316 	{
317 		std::ostringstream oss;
318 		oss << "LMS.mediaplayer.setSettings(settings = " << settingsToJSString(settings) << ")";
319 
320 		LMS_LOG(UI, DEBUG) << "Running js = '" << oss.str() << "'";
321 		doJavaScript(oss.str());
322 	}
323 }
324 
325 } // namespace UserInterface
326 
327