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