system_media_controls_linux.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. // Copyright 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/system_media_controls/linux/system_media_controls_linux.h"
  5. #include <memory>
  6. #include <utility>
  7. #include "base/bind.h"
  8. #include "base/callback_helpers.h"
  9. #include "base/observer_list.h"
  10. #include "base/process/process.h"
  11. #include "base/strings/string_number_conversions.h"
  12. #include "base/strings/stringprintf.h"
  13. #include "base/strings/utf_string_conversions.h"
  14. #include "components/dbus/properties/dbus_properties.h"
  15. #include "components/dbus/properties/success_barrier_callback.h"
  16. #include "components/dbus/thread_linux/dbus_thread_linux.h"
  17. #include "components/system_media_controls/system_media_controls_observer.h"
  18. #include "dbus/bus.h"
  19. #include "dbus/exported_object.h"
  20. #include "dbus/message.h"
  21. #include "dbus/object_path.h"
  22. #include "dbus/property.h"
  23. namespace system_media_controls {
  24. // static
  25. std::unique_ptr<SystemMediaControls> SystemMediaControls::Create(
  26. const std::string& product_name) {
  27. auto service =
  28. std::make_unique<internal::SystemMediaControlsLinux>(product_name);
  29. service->StartService();
  30. return std::move(service);
  31. }
  32. namespace internal {
  33. namespace {
  34. constexpr int kNumMethodsToExport = 11;
  35. constexpr base::TimeDelta kUpdatePositionInterval = base::Milliseconds(100);
  36. const char kMprisAPINoTrackPath[] = "/org/mpris/MediaPlayer2/TrackList/NoTrack";
  37. const char kMprisAPICurrentTrackPathFormatString[] =
  38. "/org/chromium/MediaPlayer2/TrackList/Track%s";
  39. } // namespace
  40. const char kMprisAPIServiceNameFormatString[] =
  41. "org.mpris.MediaPlayer2.chromium.instance%i";
  42. const char kMprisAPIObjectPath[] = "/org/mpris/MediaPlayer2";
  43. const char kMprisAPIInterfaceName[] = "org.mpris.MediaPlayer2";
  44. const char kMprisAPIPlayerInterfaceName[] = "org.mpris.MediaPlayer2.Player";
  45. const char kMprisAPISignalSeeked[] = "Seeked";
  46. SystemMediaControlsLinux::SystemMediaControlsLinux(
  47. const std::string& product_name)
  48. : product_name_(product_name),
  49. service_name_(base::StringPrintf(kMprisAPIServiceNameFormatString,
  50. base::Process::Current().Pid())) {}
  51. SystemMediaControlsLinux::~SystemMediaControlsLinux() {
  52. if (bus_) {
  53. dbus_thread_linux::GetTaskRunner()->PostTask(
  54. FROM_HERE, base::BindOnce(&dbus::Bus::ShutdownAndBlock, bus_));
  55. }
  56. }
  57. void SystemMediaControlsLinux::StartService() {
  58. if (started_)
  59. return;
  60. started_ = true;
  61. InitializeDbusInterface();
  62. }
  63. void SystemMediaControlsLinux::AddObserver(
  64. SystemMediaControlsObserver* observer) {
  65. observers_.AddObserver(observer);
  66. // If the service is already ready, inform the observer.
  67. if (service_ready_)
  68. observer->OnServiceReady();
  69. }
  70. void SystemMediaControlsLinux::RemoveObserver(
  71. SystemMediaControlsObserver* observer) {
  72. observers_.RemoveObserver(observer);
  73. }
  74. void SystemMediaControlsLinux::SetIsNextEnabled(bool value) {
  75. properties_->SetProperty(kMprisAPIPlayerInterfaceName, "CanGoNext",
  76. DbusBoolean(value));
  77. }
  78. void SystemMediaControlsLinux::SetIsPreviousEnabled(bool value) {
  79. properties_->SetProperty(kMprisAPIPlayerInterfaceName, "CanGoPrevious",
  80. DbusBoolean(value));
  81. }
  82. void SystemMediaControlsLinux::SetIsPlayPauseEnabled(bool value) {
  83. properties_->SetProperty(kMprisAPIPlayerInterfaceName, "CanPlay",
  84. DbusBoolean(value));
  85. properties_->SetProperty(kMprisAPIPlayerInterfaceName, "CanPause",
  86. DbusBoolean(value));
  87. }
  88. void SystemMediaControlsLinux::SetIsSeekToEnabled(bool value) {
  89. properties_->SetProperty(kMprisAPIPlayerInterfaceName, "CanSeek",
  90. DbusBoolean(value));
  91. }
  92. void SystemMediaControlsLinux::SetPlaybackStatus(PlaybackStatus value) {
  93. auto status = [&]() {
  94. switch (value) {
  95. case PlaybackStatus::kPlaying:
  96. return DbusString("Playing");
  97. case PlaybackStatus::kPaused:
  98. return DbusString("Paused");
  99. case PlaybackStatus::kStopped:
  100. return DbusString("Stopped");
  101. }
  102. };
  103. properties_->SetProperty(kMprisAPIPlayerInterfaceName, "PlaybackStatus",
  104. status());
  105. playing_ = (value == PlaybackStatus::kPlaying);
  106. if (playing_ && position_.has_value())
  107. StartPositionUpdateTimer();
  108. else
  109. StopPositionUpdateTimer();
  110. }
  111. void SystemMediaControlsLinux::SetID(const std::string* value) {
  112. if (!value) {
  113. ClearTrackId();
  114. return;
  115. }
  116. const std::string track_id =
  117. base::StringPrintf(kMprisAPICurrentTrackPathFormatString, value->c_str());
  118. SetMetadataPropertyInternal(
  119. "mpris:trackid",
  120. MakeDbusVariant(DbusObjectPath(dbus::ObjectPath(track_id))));
  121. }
  122. void SystemMediaControlsLinux::SetTitle(const std::u16string& value) {
  123. SetMetadataPropertyInternal(
  124. "xesam:title", MakeDbusVariant(DbusString(base::UTF16ToUTF8(value))));
  125. }
  126. void SystemMediaControlsLinux::SetArtist(const std::u16string& value) {
  127. SetMetadataPropertyInternal(
  128. "xesam:artist",
  129. MakeDbusVariant(MakeDbusArray(DbusString(base::UTF16ToUTF8(value)))));
  130. }
  131. void SystemMediaControlsLinux::SetAlbum(const std::u16string& value) {
  132. SetMetadataPropertyInternal(
  133. "xesam:album", MakeDbusVariant(DbusString(base::UTF16ToUTF8(value))));
  134. }
  135. void SystemMediaControlsLinux::SetPosition(
  136. const media_session::MediaPosition& position) {
  137. position_ = position;
  138. UpdatePosition(/*emit_signal=*/true);
  139. if (playing_)
  140. StartPositionUpdateTimer();
  141. }
  142. void SystemMediaControlsLinux::ClearMetadata() {
  143. SetTitle(std::u16string());
  144. SetArtist(std::u16string());
  145. SetAlbum(std::u16string());
  146. ClearTrackId();
  147. ClearPosition();
  148. }
  149. std::string SystemMediaControlsLinux::GetServiceName() const {
  150. return service_name_;
  151. }
  152. void SystemMediaControlsLinux::InitializeProperties() {
  153. // org.mpris.MediaPlayer2 interface properties.
  154. auto set_property = [&](const std::string& property_name, auto&& value) {
  155. properties_->SetProperty(kMprisAPIInterfaceName, property_name,
  156. std::forward<decltype(value)>(value), false);
  157. };
  158. set_property("CanQuit", DbusBoolean(false));
  159. set_property("CanRaise", DbusBoolean(false));
  160. set_property("HasTrackList", DbusBoolean(false));
  161. set_property("Identity", DbusString(product_name_));
  162. set_property("SupportedUriSchemes", DbusArray<DbusString>());
  163. set_property("SupportedMimeTypes", DbusArray<DbusString>());
  164. // org.mpris.MediaPlayer2.Player interface properties.
  165. auto set_player_property = [&](const std::string& property_name,
  166. auto&& value) {
  167. properties_->SetProperty(kMprisAPIPlayerInterfaceName, property_name,
  168. std::forward<decltype(value)>(value), false);
  169. };
  170. set_player_property("PlaybackStatus", DbusString("Stopped"));
  171. set_player_property("Rate", DbusDouble(1.0));
  172. set_player_property("Metadata", DbusDictionary());
  173. set_player_property("Volume", DbusDouble(1.0));
  174. set_player_property("Position", DbusInt64(0));
  175. set_player_property("MinimumRate", DbusDouble(1.0));
  176. set_player_property("MaximumRate", DbusDouble(1.0));
  177. set_player_property("CanGoNext", DbusBoolean(false));
  178. set_player_property("CanGoPrevious", DbusBoolean(false));
  179. set_player_property("CanPlay", DbusBoolean(false));
  180. set_player_property("CanPause", DbusBoolean(false));
  181. set_player_property("CanSeek", DbusBoolean(false));
  182. set_player_property("CanControl", DbusBoolean(true));
  183. }
  184. void SystemMediaControlsLinux::InitializeDbusInterface() {
  185. // Bus may be set for testing.
  186. if (!bus_) {
  187. dbus::Bus::Options bus_options;
  188. bus_options.bus_type = dbus::Bus::SESSION;
  189. bus_options.connection_type = dbus::Bus::PRIVATE;
  190. bus_options.dbus_task_runner = dbus_thread_linux::GetTaskRunner();
  191. bus_ = base::MakeRefCounted<dbus::Bus>(bus_options);
  192. }
  193. exported_object_ =
  194. bus_->GetExportedObject(dbus::ObjectPath(kMprisAPIObjectPath));
  195. int num_methods_attempted_to_export = 0;
  196. // kNumMethodsToExport calls for method export, 1 call for properties
  197. // initialization.
  198. barrier_ = SuccessBarrierCallback(
  199. kNumMethodsToExport + 1,
  200. base::BindOnce(&SystemMediaControlsLinux::OnInitialized,
  201. base::Unretained(this)));
  202. properties_ = std::make_unique<DbusProperties>(exported_object_, barrier_);
  203. properties_->RegisterInterface(kMprisAPIInterfaceName);
  204. properties_->RegisterInterface(kMprisAPIPlayerInterfaceName);
  205. InitializeProperties();
  206. // Helper lambdas for exporting methods while keeping track of the number of
  207. // exported methods.
  208. auto export_method =
  209. [&](const std::string& interface_name, const std::string& method_name,
  210. dbus::ExportedObject::MethodCallCallback method_call_callback) {
  211. exported_object_->ExportMethod(
  212. interface_name, method_name, method_call_callback,
  213. base::BindRepeating(&SystemMediaControlsLinux::OnExported,
  214. base::Unretained(this)));
  215. num_methods_attempted_to_export++;
  216. };
  217. auto export_unhandled_method = [&](const std::string& interface_name,
  218. const std::string& method_name) {
  219. export_method(interface_name, method_name,
  220. base::BindRepeating(&SystemMediaControlsLinux::DoNothing,
  221. base::Unretained(this)));
  222. };
  223. // Set up org.mpris.MediaPlayer2 interface.
  224. // https://specifications.freedesktop.org/mpris-spec/2.2/Media_Player.html
  225. export_unhandled_method(kMprisAPIInterfaceName, "Raise");
  226. export_unhandled_method(kMprisAPIInterfaceName, "Quit");
  227. // Set up org.mpris.MediaPlayer2.Player interface.
  228. // https://specifications.freedesktop.org/mpris-spec/2.2/Player_Interface.html
  229. export_method(kMprisAPIPlayerInterfaceName, "Next",
  230. base::BindRepeating(&SystemMediaControlsLinux::Next,
  231. base::Unretained(this)));
  232. export_method(kMprisAPIPlayerInterfaceName, "Previous",
  233. base::BindRepeating(&SystemMediaControlsLinux::Previous,
  234. base::Unretained(this)));
  235. export_method(kMprisAPIPlayerInterfaceName, "Pause",
  236. base::BindRepeating(&SystemMediaControlsLinux::Pause,
  237. base::Unretained(this)));
  238. export_method(kMprisAPIPlayerInterfaceName, "PlayPause",
  239. base::BindRepeating(&SystemMediaControlsLinux::PlayPause,
  240. base::Unretained(this)));
  241. export_method(kMprisAPIPlayerInterfaceName, "Stop",
  242. base::BindRepeating(&SystemMediaControlsLinux::Stop,
  243. base::Unretained(this)));
  244. export_method(kMprisAPIPlayerInterfaceName, "Play",
  245. base::BindRepeating(&SystemMediaControlsLinux::Play,
  246. base::Unretained(this)));
  247. export_method(kMprisAPIPlayerInterfaceName, "Seek",
  248. base::BindRepeating(&SystemMediaControlsLinux::Seek,
  249. base::Unretained(this)));
  250. export_method(kMprisAPIPlayerInterfaceName, "SetPosition",
  251. base::BindRepeating(&SystemMediaControlsLinux::SetPositionMpris,
  252. base::Unretained(this)));
  253. export_unhandled_method(kMprisAPIPlayerInterfaceName, "OpenUri");
  254. DCHECK_EQ(kNumMethodsToExport, num_methods_attempted_to_export);
  255. }
  256. void SystemMediaControlsLinux::OnExported(const std::string& interface_name,
  257. const std::string& method_name,
  258. bool success) {
  259. barrier_.Run(success);
  260. }
  261. void SystemMediaControlsLinux::OnInitialized(bool success) {
  262. if (success) {
  263. bus_->RequestOwnership(
  264. service_name_, dbus::Bus::ServiceOwnershipOptions::REQUIRE_PRIMARY,
  265. base::BindRepeating(&SystemMediaControlsLinux::OnOwnership,
  266. base::Unretained(this)));
  267. }
  268. }
  269. void SystemMediaControlsLinux::OnOwnership(const std::string& service_name,
  270. bool success) {
  271. if (!success)
  272. return;
  273. service_ready_ = true;
  274. for (SystemMediaControlsObserver& obs : observers_)
  275. obs.OnServiceReady();
  276. }
  277. void SystemMediaControlsLinux::Next(
  278. dbus::MethodCall* method_call,
  279. dbus::ExportedObject::ResponseSender response_sender) {
  280. for (SystemMediaControlsObserver& obs : observers_)
  281. obs.OnNext();
  282. std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
  283. }
  284. void SystemMediaControlsLinux::Previous(
  285. dbus::MethodCall* method_call,
  286. dbus::ExportedObject::ResponseSender response_sender) {
  287. for (SystemMediaControlsObserver& obs : observers_)
  288. obs.OnPrevious();
  289. std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
  290. }
  291. void SystemMediaControlsLinux::Pause(
  292. dbus::MethodCall* method_call,
  293. dbus::ExportedObject::ResponseSender response_sender) {
  294. for (SystemMediaControlsObserver& obs : observers_)
  295. obs.OnPause();
  296. std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
  297. }
  298. void SystemMediaControlsLinux::PlayPause(
  299. dbus::MethodCall* method_call,
  300. dbus::ExportedObject::ResponseSender response_sender) {
  301. for (SystemMediaControlsObserver& obs : observers_)
  302. obs.OnPlayPause();
  303. std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
  304. }
  305. void SystemMediaControlsLinux::Stop(
  306. dbus::MethodCall* method_call,
  307. dbus::ExportedObject::ResponseSender response_sender) {
  308. for (SystemMediaControlsObserver& obs : observers_)
  309. obs.OnStop();
  310. std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
  311. }
  312. void SystemMediaControlsLinux::Play(
  313. dbus::MethodCall* method_call,
  314. dbus::ExportedObject::ResponseSender response_sender) {
  315. for (SystemMediaControlsObserver& obs : observers_)
  316. obs.OnPlay();
  317. std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
  318. }
  319. void SystemMediaControlsLinux::Seek(
  320. dbus::MethodCall* method_call,
  321. dbus::ExportedObject::ResponseSender response_sender) {
  322. int64_t offset;
  323. dbus::MessageReader reader(method_call);
  324. if (!reader.PopInt64(&offset)) {
  325. std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
  326. return;
  327. }
  328. for (SystemMediaControlsObserver& obs : observers_)
  329. obs.OnSeek(base::Microseconds(offset));
  330. std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
  331. }
  332. void SystemMediaControlsLinux::SetPositionMpris(
  333. dbus::MethodCall* method_call,
  334. dbus::ExportedObject::ResponseSender response_sender) {
  335. dbus::ObjectPath track_id;
  336. int64_t position;
  337. dbus::MessageReader reader(method_call);
  338. if (!reader.PopObjectPath(&track_id)) {
  339. std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
  340. return;
  341. }
  342. if (!reader.PopInt64(&position)) {
  343. std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
  344. return;
  345. }
  346. for (SystemMediaControlsObserver& obs : observers_)
  347. obs.OnSeekTo(base::Microseconds(position));
  348. std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
  349. }
  350. void SystemMediaControlsLinux::DoNothing(
  351. dbus::MethodCall* method_call,
  352. dbus::ExportedObject::ResponseSender response_sender) {
  353. std::move(response_sender).Run(dbus::Response::FromMethodCall(method_call));
  354. }
  355. void SystemMediaControlsLinux::SetMetadataPropertyInternal(
  356. const std::string& property_name,
  357. DbusVariant&& new_value) {
  358. DbusVariant* dictionary_variant =
  359. properties_->GetProperty(kMprisAPIPlayerInterfaceName, "Metadata");
  360. DCHECK(dictionary_variant);
  361. DbusDictionary* dictionary = dictionary_variant->GetAs<DbusDictionary>();
  362. DCHECK(dictionary);
  363. if (dictionary->Put(property_name, std::move(new_value)))
  364. properties_->PropertyUpdated(kMprisAPIPlayerInterfaceName, "Metadata");
  365. }
  366. void SystemMediaControlsLinux::ClearTrackId() {
  367. SetMetadataPropertyInternal(
  368. "mpris:trackid",
  369. MakeDbusVariant(DbusObjectPath(dbus::ObjectPath(kMprisAPINoTrackPath))));
  370. }
  371. void SystemMediaControlsLinux::ClearPosition() {
  372. position_ = absl::nullopt;
  373. StopPositionUpdateTimer();
  374. UpdatePosition(/*emit_signal=*/true);
  375. }
  376. void SystemMediaControlsLinux::UpdatePosition(bool emit_signal) {
  377. int64_t position = 0;
  378. double rate = 1.0;
  379. int64_t duration = 0;
  380. if (position_.has_value()) {
  381. position = position_->GetPosition().InMicroseconds();
  382. rate = position_->playback_rate();
  383. duration = position_->duration().InMicroseconds();
  384. }
  385. // We never emit a PropertiesChanged signal for the "Position" property. We
  386. // only emit "Seeked" signals.
  387. properties_->SetProperty(kMprisAPIPlayerInterfaceName, "Position",
  388. DbusInt64(position), /*emit_signal=*/false);
  389. properties_->SetProperty(kMprisAPIPlayerInterfaceName, "Rate",
  390. DbusDouble(rate), emit_signal);
  391. SetMetadataPropertyInternal("mpris:length",
  392. MakeDbusVariant(DbusInt64(duration)));
  393. if (!service_ready_ || !emit_signal || !position_.has_value())
  394. return;
  395. dbus::Signal seeked_signal(kMprisAPIPlayerInterfaceName,
  396. kMprisAPISignalSeeked);
  397. dbus::MessageWriter writer(&seeked_signal);
  398. writer.AppendInt64(position);
  399. exported_object_->SendSignal(&seeked_signal);
  400. }
  401. void SystemMediaControlsLinux::StartPositionUpdateTimer() {
  402. // The timer should only run when the media is playing and has a position.
  403. DCHECK(playing_);
  404. DCHECK(position_.has_value());
  405. // base::Unretained(this) is safe here since |this| owns
  406. // |position_update_timer_|.
  407. position_update_timer_.Start(
  408. FROM_HERE, kUpdatePositionInterval,
  409. base::BindRepeating(&SystemMediaControlsLinux::UpdatePosition,
  410. base::Unretained(this), /*emit_signal=*/false));
  411. }
  412. void SystemMediaControlsLinux::StopPositionUpdateTimer() {
  413. position_update_timer_.Stop();
  414. }
  415. } // namespace internal
  416. } // namespace system_media_controls